├── .dockerignore
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── BUG_REPORT.md
│ ├── FEATURE_REQUEST.md
│ └── SUPPORT_QUESTION.md
├── PULL_REQUEST_TEMPLATE.md
├── dependabot.yml
└── workflows
│ ├── docker-build.yml
│ ├── fetch.yml
│ ├── golangci.yml
│ ├── goreleaser.yml
│ └── test.yml
├── .gitignore
├── .golangci.yml
├── .goreleaser.yml
├── .revive.toml
├── Dockerfile
├── GNUmakefile
├── LICENSE
├── README.md
├── commands
├── fetch.go
├── fetchcisco.go
├── fetchfortinet.go
├── fetchjvn.go
├── fetchmitre.go
├── fetchnvd.go
├── fetchpaloalto.go
├── root.go
├── search.go
├── server.go
└── version.go
├── config
├── config.go
└── config_test.go
├── db
├── db.go
├── db_test.go
├── rdb.go
├── rdb_test.go
├── redis.go
├── redis_test.go
└── util.go
├── fetcher
├── cisco
│ ├── cisco.go
│ ├── cisco_test.go
│ └── types.go
├── fetcher.go
├── fortinet
│ ├── fortinet.go
│ └── types.go
├── jvn
│ ├── jvn.go
│ └── jvn_test.go
├── mitre
│ ├── mitre.go
│ └── types.go
├── nvd
│ ├── nvd.go
│ └── types.go
├── paloalto
│ ├── paloalto.go
│ ├── paloalto_test.go
│ └── types.go
└── util.go
├── go.mod
├── go.sum
├── integration
├── .gitignore
├── README.md
├── cpes.txt
├── cves.txt
├── diff_server_mode.py
└── requirements.txt
├── log
└── log.go
├── main.go
├── models
├── cisco.go
├── fortinet.go
├── jvn.go
├── mitre.go
├── models.go
├── models_test.go
├── nvd.go
└── paloalto.go
├── server
└── server.go
└── util
└── util.go
/.dockerignore:
--------------------------------------------------------------------------------
1 | .dockerignore
2 | Dockerfile
3 | vendor/
4 | cve.sqlite3
5 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: kotakanbe
4 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/BUG_REPORT.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug Report
3 | labels: bug
4 | about: If something isn't working as expected.
5 | ---
6 |
7 | # What did you do? (required. The issue will be **closed** when not provided.)
8 |
9 |
10 | # What did you expect to happen?
11 |
12 |
13 | # What happened instead?
14 |
15 | * Current Output
16 |
17 | Please re-run the command using ```-debug``` and provide the output below.
18 |
19 | # Steps to reproduce the behaviour
20 |
21 |
22 | # Configuration (**MUST** fill this out):
23 |
24 | * Go version (`go version`):
25 |
26 | * Go environment (`go env`):
27 |
28 | * go-cve-dictionary environment:
29 |
30 | Hash : ____
31 |
32 | To check the commit hash of HEAD
33 | $ go-cve-dictionary version
34 |
35 | or
36 |
37 | $ cd $GOPATH/src/github.com/vulsio/go-cve-dictionary
38 | $ git rev-parse --short HEAD
39 |
40 | * command:
41 |
42 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature Request
3 | labels: enhancement
4 | about: I have a suggestion (and might want to implement myself)!
5 | ---
6 |
7 |
10 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/SUPPORT_QUESTION.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Support Question
3 | labels: question
4 | about: If you have a question about go-cve-dictionary.
5 | ---
6 |
7 |
11 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 | If this Pull Request is work in progress, Add a prefix of “[WIP]” in the title.
3 |
4 | # What did you implement:
5 |
6 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context.
7 |
8 | Fixes # (issue)
9 |
10 | ## Type of change
11 |
12 | Please delete options that are not relevant.
13 |
14 | - [ ] Bug fix (non-breaking change which fixes an issue)
15 | - [ ] New feature (non-breaking change which adds functionality)
16 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
17 | - [ ] This change requires a documentation update
18 |
19 | # How Has This Been Tested?
20 |
21 | Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce.
22 |
23 | # Checklist:
24 | You don't have to satisfy all of the following.
25 |
26 | - [ ] Write tests
27 | - [ ] Write documentation
28 | - [ ] Check that there aren't other open pull requests for the same issue/feature
29 | - [ ] Format your source code by `make fmt`
30 | - [ ] Pass the test by `make test`
31 | - [ ] Provide verification config / commands
32 | - [ ] Enable "Allow edits from maintainers" for this PR
33 | - [ ] Update the messages below
34 |
35 | ***Is this ready for review?:*** NO
36 |
37 | # Reference
38 |
39 | * https://blog.github.com/2015-01-21-how-to-write-the-perfect-pull-request/
40 |
41 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "gomod" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "weekly"
12 | target-branch: "master"
13 | ignore:
14 | - dependency-name: "gorm.io/driver/mysql"
15 | - dependency-name: "gorm.io/driver/postgres"
16 | - dependency-name: "gorm.io/gorm"
17 | groups:
18 | all:
19 | patterns:
20 | - "*"
21 | exclude-patterns:
22 | - github.com/glebarez/sqlite
23 | - package-ecosystem: "github-actions" # See documentation for possible values
24 | directory: "/" # Location of package manifests
25 | schedule:
26 | interval: "weekly"
27 | target-branch: "master"
28 | groups:
29 | all:
30 | patterns:
31 | - "*"
32 | - package-ecosystem: "docker"
33 | directory: "/"
34 | schedule:
35 | interval: "weekly"
36 | target-branch: "master"
37 | groups:
38 | all:
39 | patterns:
40 | - "*"
41 |
--------------------------------------------------------------------------------
/.github/workflows/docker-build.yml:
--------------------------------------------------------------------------------
1 | name: Publish Docker image
2 |
3 | on:
4 | push:
5 | branches:
6 | - 'master'
7 | tags:
8 | - '*'
9 |
10 | jobs:
11 | docker:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v4
16 |
17 | - name: Set up QEMU
18 | uses: docker/setup-qemu-action@v3
19 |
20 | - name: Set up Docker Buildx
21 | uses: docker/setup-buildx-action@v3
22 |
23 | - name: Login to DockerHub
24 | uses: docker/login-action@v3
25 | with:
26 | username: ${{ secrets.DOCKERHUB_USERNAME }}
27 | password: ${{ secrets.DOCKERHUB_TOKEN }}
28 |
29 | -
30 | name: Docker meta
31 | id: meta
32 | uses: docker/metadata-action@v5
33 | with:
34 | images: vuls/go-cve-dictionary
35 | tags: |
36 | type=ref,event=tag
37 |
38 | - name: Build and push
39 | uses: docker/build-push-action@v6
40 | with:
41 | push: true
42 | context: .
43 | tags: |
44 | vuls/go-cve-dictionary:latest
45 | ${{ steps.meta.outputs.tags }}
46 | secrets: |
47 | "github_token=${{ secrets.GITHUB_TOKEN }}"
48 | platforms: linux/amd64,linux/arm64
49 |
--------------------------------------------------------------------------------
/.github/workflows/fetch.yml:
--------------------------------------------------------------------------------
1 | name: Fetch Test
2 |
3 | on:
4 | pull_request:
5 | schedule:
6 | - cron: "0 0 * * *"
7 |
8 | jobs:
9 | fetch-nvd:
10 | name: fetch-nvd
11 | runs-on: ubuntu-latest
12 | services:
13 | mysql:
14 | image: mysql
15 | ports:
16 | - 3306:3306
17 | env:
18 | MYSQL_ROOT_PASSWORD: password
19 | MYSQL_DATABASE: test
20 | options: >-
21 | --health-cmd "mysqladmin ping"
22 | --health-interval 10s
23 | --health-timeout 5s
24 | --health-retries 5
25 | postgres:
26 | image: postgres
27 | ports:
28 | - 5432:5432
29 | env:
30 | POSTGRES_PASSWORD: password
31 | POSTGRES_DB: test
32 | options: >-
33 | --health-cmd pg_isready
34 | --health-interval 10s
35 | --health-timeout 5s
36 | --health-retries 5
37 | redis:
38 | image: redis
39 | ports:
40 | - 6379:6379
41 | options: >-
42 | --health-cmd "redis-cli ping"
43 | --health-interval 10s
44 | --health-timeout 5s
45 | --health-retries 5
46 | steps:
47 | - name: Check out code into the Go module directory
48 | uses: actions/checkout@v4
49 | - name: Set up Go
50 | uses: actions/setup-go@v5
51 | with:
52 | go-version-file: go.mod
53 | - name: build
54 | id: build
55 | run: make build
56 | - name: fetch sqlite3
57 | if: ${{ steps.build.conclusion == 'success' && ( success() || failure() )}}
58 | run: ./go-cve-dictionary fetch --dbtype sqlite3 nvd
59 | - name: fetch mysql
60 | if: ${{ steps.build.conclusion == 'success' && ( success() || failure() )}}
61 | run: ./go-cve-dictionary fetch --dbtype mysql --dbpath "root:password@tcp(127.0.0.1:3306)/test?parseTime=true" nvd
62 | - name: fetch postgres
63 | if: ${{ steps.build.conclusion == 'success' && ( success() || failure() )}}
64 | run: ./go-cve-dictionary fetch --dbtype postgres --dbpath "host=127.0.0.1 user=postgres dbname=test sslmode=disable password=password" nvd
65 | - name: fetch redis
66 | if: ${{ steps.build.conclusion == 'success' && ( success() || failure() )}}
67 | run: ./go-cve-dictionary fetch --dbtype redis --dbpath "redis://127.0.0.1:6379/0" nvd
68 |
69 | fetch-jvn:
70 | name: fetch-jvn
71 | runs-on: ubuntu-latest
72 | services:
73 | mysql:
74 | image: mysql
75 | ports:
76 | - 3306:3306
77 | env:
78 | MYSQL_ROOT_PASSWORD: password
79 | MYSQL_DATABASE: test
80 | options: >-
81 | --health-cmd "mysqladmin ping"
82 | --health-interval 10s
83 | --health-timeout 5s
84 | --health-retries 5
85 | postgres:
86 | image: postgres
87 | ports:
88 | - 5432:5432
89 | env:
90 | POSTGRES_PASSWORD: password
91 | POSTGRES_DB: test
92 | options: >-
93 | --health-cmd pg_isready
94 | --health-interval 10s
95 | --health-timeout 5s
96 | --health-retries 5
97 | redis:
98 | image: redis
99 | ports:
100 | - 6379:6379
101 | options: >-
102 | --health-cmd "redis-cli ping"
103 | --health-interval 10s
104 | --health-timeout 5s
105 | --health-retries 5
106 | steps:
107 | - name: Check out code into the Go module directory
108 | uses: actions/checkout@v4
109 | - name: Set up Go
110 | uses: actions/setup-go@v5
111 | with:
112 | go-version-file: go.mod
113 | - name: build
114 | id: build
115 | run: make build
116 | - name: fetch sqlite3
117 | if: ${{ steps.build.conclusion == 'success' && ( success() || failure() )}}
118 | run: ./go-cve-dictionary fetch --dbtype sqlite3 jvn
119 | - name: fetch mysql
120 | if: ${{ steps.build.conclusion == 'success' && ( success() || failure() )}}
121 | run: ./go-cve-dictionary fetch --dbtype mysql --dbpath "root:password@tcp(127.0.0.1:3306)/test?parseTime=true" jvn
122 | - name: fetch postgres
123 | if: ${{ steps.build.conclusion == 'success' && ( success() || failure() )}}
124 | run: ./go-cve-dictionary fetch --dbtype postgres --dbpath "host=127.0.0.1 user=postgres dbname=test sslmode=disable password=password" jvn
125 | - name: fetch redis
126 | if: ${{ steps.build.conclusion == 'success' && ( success() || failure() )}}
127 | run: ./go-cve-dictionary fetch --dbtype redis --dbpath "redis://127.0.0.1:6379/0" jvn
128 |
129 | fetch-fortinet:
130 | name: fetch-fortinet
131 | runs-on: ubuntu-latest
132 | services:
133 | mysql:
134 | image: mysql
135 | ports:
136 | - 3306:3306
137 | env:
138 | MYSQL_ROOT_PASSWORD: password
139 | MYSQL_DATABASE: test
140 | options: >-
141 | --health-cmd "mysqladmin ping"
142 | --health-interval 10s
143 | --health-timeout 5s
144 | --health-retries 5
145 | postgres:
146 | image: postgres
147 | ports:
148 | - 5432:5432
149 | env:
150 | POSTGRES_PASSWORD: password
151 | POSTGRES_DB: test
152 | options: >-
153 | --health-cmd pg_isready
154 | --health-interval 10s
155 | --health-timeout 5s
156 | --health-retries 5
157 | redis:
158 | image: redis
159 | ports:
160 | - 6379:6379
161 | options: >-
162 | --health-cmd "redis-cli ping"
163 | --health-interval 10s
164 | --health-timeout 5s
165 | --health-retries 5
166 | steps:
167 | - name: Check out code into the Go module directory
168 | uses: actions/checkout@v4
169 | - name: Set up Go
170 | uses: actions/setup-go@v5
171 | with:
172 | go-version-file: go.mod
173 | - name: build
174 | id: build
175 | run: make build
176 | - name: fetch sqlite3
177 | if: ${{ steps.build.conclusion == 'success' && ( success() || failure() )}}
178 | run: ./go-cve-dictionary fetch --dbtype sqlite3 fortinet
179 | - name: fetch mysql
180 | if: ${{ steps.build.conclusion == 'success' && ( success() || failure() )}}
181 | run: ./go-cve-dictionary fetch --dbtype mysql --dbpath "root:password@tcp(127.0.0.1:3306)/test?parseTime=true" fortinet
182 | - name: fetch postgres
183 | if: ${{ steps.build.conclusion == 'success' && ( success() || failure() )}}
184 | run: ./go-cve-dictionary fetch --dbtype postgres --dbpath "host=127.0.0.1 user=postgres dbname=test sslmode=disable password=password" fortinet
185 | - name: fetch redis
186 | if: ${{ steps.build.conclusion == 'success' && ( success() || failure() )}}
187 | run: ./go-cve-dictionary fetch --dbtype redis --dbpath "redis://127.0.0.1:6379/0" fortinet
188 |
189 | fetch-mitre:
190 | name: fetch-mitre
191 | runs-on: ubuntu-latest
192 | services:
193 | mysql:
194 | image: mysql
195 | ports:
196 | - 3306:3306
197 | env:
198 | MYSQL_ROOT_PASSWORD: password
199 | MYSQL_DATABASE: test
200 | options: >-
201 | --health-cmd "mysqladmin ping"
202 | --health-interval 10s
203 | --health-timeout 5s
204 | --health-retries 5
205 | postgres:
206 | image: postgres
207 | ports:
208 | - 5432:5432
209 | env:
210 | POSTGRES_PASSWORD: password
211 | POSTGRES_DB: test
212 | options: >-
213 | --health-cmd pg_isready
214 | --health-interval 10s
215 | --health-timeout 5s
216 | --health-retries 5
217 | redis:
218 | image: redis
219 | ports:
220 | - 6379:6379
221 | options: >-
222 | --health-cmd "redis-cli ping"
223 | --health-interval 10s
224 | --health-timeout 5s
225 | --health-retries 5
226 | steps:
227 | - name: Check out code into the Go module directory
228 | uses: actions/checkout@v4
229 | - name: Set up Go
230 | uses: actions/setup-go@v5
231 | with:
232 | go-version-file: go.mod
233 | - name: build
234 | id: build
235 | run: make build
236 | - name: fetch sqlite3
237 | if: ${{ steps.build.conclusion == 'success' && ( success() || failure() )}}
238 | run: ./go-cve-dictionary fetch --dbtype sqlite3 mitre
239 | - name: fetch mysql
240 | if: ${{ steps.build.conclusion == 'success' && ( success() || failure() )}}
241 | run: ./go-cve-dictionary fetch --dbtype mysql --dbpath "root:password@tcp(127.0.0.1:3306)/test?parseTime=true" mitre
242 | - name: fetch postgres
243 | if: ${{ steps.build.conclusion == 'success' && ( success() || failure() )}}
244 | run: ./go-cve-dictionary fetch --dbtype postgres --dbpath "host=127.0.0.1 user=postgres dbname=test sslmode=disable password=password" mitre
245 | - name: fetch redis
246 | if: ${{ steps.build.conclusion == 'success' && ( success() || failure() )}}
247 | run: ./go-cve-dictionary fetch --dbtype redis --dbpath "redis://127.0.0.1:6379/0" mitre
248 |
249 | fetch-paloalto:
250 | name: fetch-paloalto
251 | runs-on: ubuntu-latest
252 | services:
253 | mysql:
254 | image: mysql
255 | ports:
256 | - 3306:3306
257 | env:
258 | MYSQL_ROOT_PASSWORD: password
259 | MYSQL_DATABASE: test
260 | options: >-
261 | --health-cmd "mysqladmin ping"
262 | --health-interval 10s
263 | --health-timeout 5s
264 | --health-retries 5
265 | postgres:
266 | image: postgres
267 | ports:
268 | - 5432:5432
269 | env:
270 | POSTGRES_PASSWORD: password
271 | POSTGRES_DB: test
272 | options: >-
273 | --health-cmd pg_isready
274 | --health-interval 10s
275 | --health-timeout 5s
276 | --health-retries 5
277 | redis:
278 | image: redis
279 | ports:
280 | - 6379:6379
281 | options: >-
282 | --health-cmd "redis-cli ping"
283 | --health-interval 10s
284 | --health-timeout 5s
285 | --health-retries 5
286 | steps:
287 | - name: Check out code into the Go module directory
288 | uses: actions/checkout@v4
289 | - name: Set up Go
290 | uses: actions/setup-go@v5
291 | with:
292 | go-version-file: go.mod
293 | - name: build
294 | id: build
295 | run: make build
296 | - name: fetch sqlite3
297 | if: ${{ steps.build.conclusion == 'success' && ( success() || failure() )}}
298 | run: ./go-cve-dictionary fetch --dbtype sqlite3 paloalto
299 | - name: fetch mysql
300 | if: ${{ steps.build.conclusion == 'success' && ( success() || failure() )}}
301 | run: ./go-cve-dictionary fetch --dbtype mysql --dbpath "root:password@tcp(127.0.0.1:3306)/test?parseTime=true" paloalto
302 | - name: fetch postgres
303 | if: ${{ steps.build.conclusion == 'success' && ( success() || failure() )}}
304 | run: ./go-cve-dictionary fetch --dbtype postgres --dbpath "host=127.0.0.1 user=postgres dbname=test sslmode=disable password=password" paloalto
305 | - name: fetch redis
306 | if: ${{ steps.build.conclusion == 'success' && ( success() || failure() )}}
307 | run: ./go-cve-dictionary fetch --dbtype redis --dbpath "redis://127.0.0.1:6379/0" paloalto
308 |
309 | fetch-cisco:
310 | name: fetch-cisco
311 | runs-on: ubuntu-latest
312 | services:
313 | mysql:
314 | image: mysql
315 | ports:
316 | - 3306:3306
317 | env:
318 | MYSQL_ROOT_PASSWORD: password
319 | MYSQL_DATABASE: test
320 | options: >-
321 | --health-cmd "mysqladmin ping"
322 | --health-interval 10s
323 | --health-timeout 5s
324 | --health-retries 5
325 | postgres:
326 | image: postgres
327 | ports:
328 | - 5432:5432
329 | env:
330 | POSTGRES_PASSWORD: password
331 | POSTGRES_DB: test
332 | options: >-
333 | --health-cmd pg_isready
334 | --health-interval 10s
335 | --health-timeout 5s
336 | --health-retries 5
337 | redis:
338 | image: redis
339 | ports:
340 | - 6379:6379
341 | options: >-
342 | --health-cmd "redis-cli ping"
343 | --health-interval 10s
344 | --health-timeout 5s
345 | --health-retries 5
346 | steps:
347 | - name: Check out code into the Go module directory
348 | uses: actions/checkout@v4
349 | - name: Set up Go
350 | uses: actions/setup-go@v5
351 | with:
352 | go-version-file: go.mod
353 | - name: build
354 | id: build
355 | run: make build
356 | - name: fetch sqlite3
357 | if: ${{ steps.build.conclusion == 'success' && ( success() || failure() )}}
358 | run: ./go-cve-dictionary fetch --dbtype sqlite3 cisco
359 | - name: fetch mysql
360 | if: ${{ steps.build.conclusion == 'success' && ( success() || failure() )}}
361 | run: ./go-cve-dictionary fetch --dbtype mysql --dbpath "root:password@tcp(127.0.0.1:3306)/test?parseTime=true" cisco
362 | - name: fetch postgres
363 | if: ${{ steps.build.conclusion == 'success' && ( success() || failure() )}}
364 | run: ./go-cve-dictionary fetch --dbtype postgres --dbpath "host=127.0.0.1 user=postgres dbname=test sslmode=disable password=password" cisco
365 | - name: fetch redis
366 | if: ${{ steps.build.conclusion == 'success' && ( success() || failure() )}}
367 | run: ./go-cve-dictionary fetch --dbtype redis --dbpath "redis://127.0.0.1:6379/0" cisco
368 |
--------------------------------------------------------------------------------
/.github/workflows/golangci.yml:
--------------------------------------------------------------------------------
1 | name: golangci-lint
2 | on:
3 | push:
4 | tags:
5 | - v*
6 | branches:
7 | - master
8 | pull_request:
9 | jobs:
10 | golangci:
11 | name: lint
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Check out code into the Go module directory
15 | uses: actions/checkout@v4
16 | - name: Set up Go 1.x
17 | uses: actions/setup-go@v5
18 | with:
19 | go-version-file: go.mod
20 | - name: golangci-lint
21 | uses: golangci/golangci-lint-action@v8
22 | with:
23 | # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
24 | version: latest
25 |
26 | # Optional: working directory, useful for monorepos
27 | # working-directory: somedir
28 |
29 | # Optional: golangci-lint command line arguments.
30 | # args: --issues-exit-code=0
31 |
32 | # Optional: show only new issues if it's a pull request. The default value is `false`.
33 | # only-new-issues: true
34 |
--------------------------------------------------------------------------------
/.github/workflows/goreleaser.yml:
--------------------------------------------------------------------------------
1 | name: goreleaser
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*'
7 |
8 | jobs:
9 | goreleaser:
10 | runs-on: ubuntu-latest
11 | steps:
12 | -
13 | name: Checkout
14 | uses: actions/checkout@v4
15 | -
16 | name: Unshallow
17 | run: git fetch --prune --unshallow
18 | -
19 | name: Set up Go
20 | uses: actions/setup-go@v5
21 | with:
22 | go-version-file: go.mod
23 | -
24 | name: Run GoReleaser
25 | uses: goreleaser/goreleaser-action@v6
26 | with:
27 | distribution: goreleaser
28 | version: latest
29 | args: release --clean
30 | env:
31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
32 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on: [pull_request]
4 |
5 | jobs:
6 | build:
7 | name: Build
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Check out code into the Go module directory
11 | uses: actions/checkout@v4
12 | - name: Set up Go 1.x
13 | uses: actions/setup-go@v5
14 | with:
15 | go-version-file: go.mod
16 | - name: Test
17 | run: make test
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode
2 | coverage.out
3 | vendor/
4 | go-cve-dictionary
5 | *.sqlite3
6 | *.sqlite3-shm
7 | *.sqlite3-wal
8 | *.sqlite3-journal
9 | tags
10 | /dist/
11 |
--------------------------------------------------------------------------------
/.golangci.yml:
--------------------------------------------------------------------------------
1 | version: "2"
2 |
3 | linters:
4 | default: none
5 | enable:
6 | - errcheck
7 | - govet
8 | - ineffassign
9 | - misspell
10 | - prealloc
11 | - revive
12 | - staticcheck
13 | settings:
14 | revive: # https://golangci-lint.run/usage/linters/#revive
15 | rules:
16 | - name: blank-imports
17 | - name: context-as-argument
18 | - name: context-keys-type
19 | - name: dot-imports
20 | - name: empty-block
21 | - name: error-naming
22 | - name: error-return
23 | - name: error-strings
24 | - name: errorf
25 | - name: exported
26 | - name: if-return
27 | - name: increment-decrement
28 | - name: indent-error-flow
29 | - name: package-comments
30 | disabled: true
31 | - name: range
32 | - name: receiver-naming
33 | - name: redefines-builtin-id
34 | - name: superfluous-else
35 | - name: time-naming
36 | - name: unexported-return
37 | - name: unreachable-code
38 | - name: unused-parameter
39 | - name: var-declaration
40 | - name: var-naming
41 | arguments:
42 | - [] # AllowList
43 | - [] # DenyList
44 | - - skip-package-name-checks: true
45 | staticcheck: # https://golangci-lint.run/usage/linters/#staticcheck
46 | checks:
47 | - all
48 | - -ST1000 # at least one file in a package should have a package comment
49 | - -ST1005 # error strings should not be capitalized
50 | exclusions:
51 | rules:
52 | - source: "defer .+\\.Close\\(\\)"
53 | linters:
54 | - errcheck
55 | - source: "defer os.RemoveAll\\(.+\\)"
56 | linters:
57 | - errcheck
58 |
59 | formatters:
60 | enable:
61 | - goimports
62 |
63 | run:
64 | timeout: 10m
65 |
--------------------------------------------------------------------------------
/.goreleaser.yml:
--------------------------------------------------------------------------------
1 | project_name: go-cve-dictionary
2 | release:
3 | github:
4 | owner: vulsio
5 | name: go-cve-dictionary
6 | env:
7 | - CGO_ENABLED=0
8 | builds:
9 | - id: go-cve-dictionary
10 | goos:
11 | - linux
12 | - windows
13 | - darwin
14 | goarch:
15 | - amd64
16 | - arm64
17 | main: .
18 | ldflags: -s -w -X github.com/vulsio/go-cve-dictionary/config.Version={{.Version}} -X github.com/vulsio/go-cve-dictionary/config.Revision={{.Commit}}
19 | binary: go-cve-dictionary
20 | archives:
21 | - name_template: '{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
22 | format: tar.gz
23 | files:
24 | - LICENSE
25 | - README*
26 | snapshot:
27 | name_template: SNAPSHOT-{{ .Commit }}
28 |
--------------------------------------------------------------------------------
/.revive.toml:
--------------------------------------------------------------------------------
1 | ignoreGeneratedHeader = false
2 | severity = "warning"
3 | confidence = 0.8
4 | errorCode = 0
5 | warningCode = 0
6 |
7 | [rule.blank-imports]
8 | [rule.context-as-argument]
9 | [rule.context-keys-type]
10 | [rule.dot-imports]
11 | [rule.error-return]
12 | [rule.error-strings]
13 | [rule.error-naming]
14 | [rule.exported]
15 | [rule.if-return]
16 | [rule.increment-decrement]
17 | [rule.var-naming]
18 | [rule.var-declaration]
19 | [rule.package-comments]
20 | [rule.range]
21 | [rule.receiver-naming]
22 | [rule.time-naming]
23 | [rule.unexported-return]
24 | [rule.indent-error-flow]
25 | [rule.errorf]
26 | [rule.empty-block]
27 | [rule.superfluous-else]
28 | [rule.unused-parameter]
29 | [rule.unreachable-code]
30 | [rule.redefines-builtin-id]
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:alpine as builder
2 |
3 | RUN apk add --no-cache \
4 | git \
5 | make \
6 | gcc \
7 | musl-dev
8 |
9 | ENV REPOSITORY github.com/vulsio/go-cve-dictionary
10 | COPY . $GOPATH/src/$REPOSITORY
11 | RUN cd $GOPATH/src/$REPOSITORY && make install
12 |
13 |
14 | FROM alpine:3.22
15 |
16 | ENV LOGDIR /var/log/go-cve-dictionary
17 | ENV WORKDIR /go-cve-dictionary
18 |
19 | RUN apk add --no-cache ca-certificates git \
20 | && mkdir -p $WORKDIR $LOGDIR
21 |
22 | COPY --from=builder /go/bin/go-cve-dictionary /usr/local/bin/
23 |
24 | VOLUME ["$WORKDIR", "$LOGDIR"]
25 | WORKDIR $WORKDIR
26 | ENV PWD $WORKDIR
27 |
28 | ENTRYPOINT ["go-cve-dictionary"]
29 | CMD ["--help"]
30 |
--------------------------------------------------------------------------------
/GNUmakefile:
--------------------------------------------------------------------------------
1 | .PHONY: \
2 | all \
3 | build \
4 | install \
5 | lint \
6 | golangci \
7 | vet \
8 | fmt \
9 | fmtcheck \
10 | pretest \
11 | test \
12 | cov \
13 | clean \
14 | build-integration \
15 | clean-integration \
16 | fetch-rdb \
17 | fetch-redis \
18 | diff-cveid \
19 | diff-package \
20 | diff-server-rdb \
21 | diff-server-redis \
22 | diff-server-rdb-redis
23 |
24 | SRCS = $(shell git ls-files '*.go')
25 | PKGS = $(shell go list ./...)
26 | VERSION := $(shell git describe --tags --abbrev=0)
27 | REVISION := $(shell git rev-parse --short HEAD)
28 | BUILDTIME := $(shell date "+%Y%m%d_%H%M%S")
29 | LDFLAGS := -X 'github.com/vulsio/go-cve-dictionary/config.Version=$(VERSION)' \
30 | -X 'github.com/vulsio/go-cve-dictionary/config.Revision=$(REVISION)'
31 | GO := CGO_ENABLED=0 go
32 |
33 | all: build test
34 |
35 | build: main.go
36 | $(GO) build -ldflags "$(LDFLAGS)" -o go-cve-dictionary $<
37 |
38 | install: main.go
39 | $(GO) install -ldflags "$(LDFLAGS)"
40 |
41 | lint:
42 | go install github.com/mgechev/revive@latest
43 | revive -config ./.revive.toml -formatter plain $(PKGS)
44 |
45 | golangci:
46 | go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
47 | golangci-lint run
48 |
49 | vet:
50 | echo $(PKGS) | xargs env $(GO) vet || exit;
51 |
52 | fmt:
53 | gofmt -w $(SRCS)
54 |
55 | fmtcheck:
56 | $(foreach file,$(SRCS),gofmt -d $(file);)
57 |
58 | pretest: lint vet fmtcheck
59 |
60 | test: pretest
61 | $(GO) test -cover -v ./... || exit;
62 |
63 | cov:
64 | @ go get -v github.com/axw/gocov/gocov
65 | @ go get golang.org/x/tools/cmd/cover
66 | gocov test | gocov report
67 |
68 | clean:
69 | echo $(PKGS) | xargs go clean || exit;
70 |
71 | PWD := $(shell pwd)
72 | BRANCH := $(shell git symbolic-ref --short HEAD)
73 | build-integration:
74 | @ git stash save
75 | $(GO) build -ldflags "$(LDFLAGS)" -o integration/go-cve.new
76 | git checkout $(shell git describe --tags --abbrev=0)
77 | @git reset --hard
78 | $(GO) build -ldflags "$(LDFLAGS)" -o integration/go-cve.old
79 | git checkout $(BRANCH)
80 | -@ git stash apply stash@{0} && git stash drop stash@{0}
81 |
82 | clean-integration:
83 | -pkill go-cve.old
84 | -pkill go-cve.new
85 | -rm integration/go-cve.old integration/go-cve.new integration/cve.old.sqlite3 integration/cve.new.sqlite3
86 | -rm -rf integration/diff
87 | -docker kill redis-old redis-new
88 | -docker rm redis-old redis-new
89 |
90 | fetch-rdb:
91 | integration/go-cve.old fetch nvd --dbpath=$(PWD)/integration/cve.old.sqlite3
92 | integration/go-cve.old fetch jvn --dbpath=$(PWD)/integration/cve.old.sqlite3
93 |
94 | integration/go-cve.new fetch nvd --dbpath=$(PWD)/integration/cve.new.sqlite3
95 | integration/go-cve.new fetch jvn --dbpath=$(PWD)/integration/cve.new.sqlite3
96 |
97 | fetch-redis:
98 | docker run --name redis-old -d -p 127.0.0.1:6379:6379 redis
99 | docker run --name redis-new -d -p 127.0.0.1:6380:6379 redis
100 |
101 | integration/go-cve.old fetch nvd --dbtype redis --dbpath "redis://127.0.0.1:6379/0"
102 | integration/go-cve.old fetch jvn --dbtype redis --dbpath "redis://127.0.0.1:6379/0"
103 |
104 | integration/go-cve.new fetch nvd --dbtype redis --dbpath "redis://127.0.0.1:6380/0"
105 | integration/go-cve.new fetch jvn --dbtype redis --dbpath "redis://127.0.0.1:6380/0"
106 |
107 | diff-cveid:
108 | @ python integration/diff_server_mode.py cves --sample_rate 0.01
109 |
110 | diff-cpes:
111 | @ python integration/diff_server_mode.py cpes --sample_rate 0.01
112 | @ python integration/diff_server_mode.py cpe_ids --sample_rate 0.01
113 |
114 | diff-server-rdb:
115 | - pkill -KILL go-cve.old
116 | - pkill -KILL go-cve.new
117 | integration/go-cve.old server --dbpath=$(PWD)/integration/cve.old.sqlite3 --port 1325 > /dev/null 2>&1 &
118 | integration/go-cve.new server --dbpath=$(PWD)/integration/cve.new.sqlite3 --port 1326 > /dev/null 2>&1 &
119 | make diff-cveid
120 | make diff-cpes
121 | pkill go-cve.old
122 | pkill go-cve.new
123 |
124 | diff-server-redis:
125 | - pkill -KILL go-cve.old
126 | - pkill -KILL go-cve.new
127 | integration/go-cve.old server --dbtype redis --dbpath "redis://127.0.0.1:6379/0" --port 1325 > /dev/null 2>&1 &
128 | integration/go-cve.new server --dbtype redis --dbpath "redis://127.0.0.1:6380/0" --port 1326 > /dev/null 2>&1 &
129 | make diff-cveid
130 | make diff-cpes
131 | pkill go-cve.old
132 | pkill go-cve.new
133 |
134 | diff-server-rdb-redis:
135 | - pkill -KILL go-cve.old
136 | - pkill -KILL go-cve.new
137 | integration/go-cve.new server --dbpath=$(PWD)/integration/cve.new.sqlite3 --port 1325 > /dev/null 2>&1 &
138 | integration/go-cve.new server --dbtype redis --dbpath "redis://127.0.0.1:6380/0" --port 1326 > /dev/null 2>&1 &
139 | make diff-cveid
140 | make diff-cpes
141 | pkill go-cve.new
142 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2016 kotakanbe
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/commands/fetch.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | "github.com/spf13/viper"
6 | )
7 |
8 | var fetchCmd = &cobra.Command{
9 | Use: "fetch",
10 | Short: "Fetch Vulnerability dictionary",
11 | Long: "Fetch Vulnerability dictionary",
12 | }
13 |
14 | func init() {
15 | RootCmd.AddCommand(fetchCmd)
16 |
17 | fetchCmd.PersistentFlags().Int("batch-size", 5, "The number of batch size to insert.")
18 | _ = viper.BindPFlag("batch-size", fetchCmd.PersistentFlags().Lookup("batch-size"))
19 | }
20 |
--------------------------------------------------------------------------------
/commands/fetchcisco.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "errors"
5 | "time"
6 |
7 | "github.com/spf13/cobra"
8 | "github.com/spf13/viper"
9 | "github.com/vulsio/go-cve-dictionary/db"
10 | "github.com/vulsio/go-cve-dictionary/fetcher/cisco"
11 | "github.com/vulsio/go-cve-dictionary/log"
12 | "github.com/vulsio/go-cve-dictionary/models"
13 | "golang.org/x/xerrors"
14 | )
15 |
16 | var fetchCiscoCmd = &cobra.Command{
17 | Use: "cisco",
18 | Short: "Fetch Vulnerability dictionary from Cisco Advisories",
19 | Long: "Fetch Vulnerability dictionary from Cisco Advisories",
20 | RunE: fetchCisco,
21 | }
22 |
23 | func init() {
24 | fetchCmd.AddCommand(fetchCiscoCmd)
25 | }
26 |
27 | func fetchCisco(_ *cobra.Command, _ []string) (err error) {
28 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil {
29 | return xerrors.Errorf("Failed to SetLogger. err: %w", err)
30 | }
31 |
32 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{})
33 | if err != nil {
34 | if errors.Is(err, db.ErrDBLocked) {
35 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err)
36 | }
37 | return xerrors.Errorf("Failed to open DB. err: %w", err)
38 | }
39 |
40 | fetchMeta, err := driver.GetFetchMeta()
41 | if err != nil {
42 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err)
43 | }
44 | if fetchMeta.OutDated() {
45 | return xerrors.Errorf("Failed to Insert CVEs into DB. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion})
46 | }
47 | // If the fetch fails the first time (without SchemaVersion), the DB needs to be cleaned every time, so insert SchemaVersion.
48 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil {
49 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err)
50 | }
51 |
52 | advs, err := cisco.FetchConvert()
53 | if err != nil {
54 | return xerrors.Errorf("Failed to fetch from cisco. err: %w", err)
55 | }
56 |
57 | log.Infof("Inserting Cisco into DB (%s).", driver.Name())
58 | if err := driver.InsertCisco(advs); err != nil {
59 | return xerrors.Errorf("Failed to insert. dbpath: %s, err: %w", viper.GetString("dbpath"), err)
60 | }
61 |
62 | fetchMeta.LastFetchedAt = time.Now()
63 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil {
64 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err)
65 | }
66 |
67 | log.Infof("Finished fetching Cisco.")
68 | return nil
69 | }
70 |
--------------------------------------------------------------------------------
/commands/fetchfortinet.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "errors"
5 | "time"
6 |
7 | "github.com/spf13/cobra"
8 | "github.com/spf13/viper"
9 | "golang.org/x/xerrors"
10 |
11 | "github.com/vulsio/go-cve-dictionary/db"
12 | "github.com/vulsio/go-cve-dictionary/fetcher/fortinet"
13 | "github.com/vulsio/go-cve-dictionary/log"
14 | "github.com/vulsio/go-cve-dictionary/models"
15 | )
16 |
17 | var fetchFortinetCmd = &cobra.Command{
18 | Use: "fortinet",
19 | Short: "Fetch Vulnerability dictionary from Fortinet Advisories",
20 | Long: "Fetch Vulnerability dictionary from Fortinet Advisories",
21 | RunE: fetchFortinet,
22 | }
23 |
24 | func init() {
25 | fetchCmd.AddCommand(fetchFortinetCmd)
26 | }
27 |
28 | func fetchFortinet(_ *cobra.Command, _ []string) (err error) {
29 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil {
30 | return xerrors.Errorf("Failed to SetLogger. err: %w", err)
31 | }
32 |
33 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{})
34 | if err != nil {
35 | if errors.Is(err, db.ErrDBLocked) {
36 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err)
37 | }
38 | return xerrors.Errorf("Failed to open DB. err: %w", err)
39 | }
40 |
41 | fetchMeta, err := driver.GetFetchMeta()
42 | if err != nil {
43 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err)
44 | }
45 | if fetchMeta.OutDated() {
46 | return xerrors.Errorf("Failed to Insert CVEs into DB. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion})
47 | }
48 | // If the fetch fails the first time (without SchemaVersion), the DB needs to be cleaned every time, so insert SchemaVersion.
49 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil {
50 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err)
51 | }
52 |
53 | advs, err := fortinet.FetchConvert()
54 | if err != nil {
55 | return xerrors.Errorf("Failed to fetch from fortinet. err: %w", err)
56 | }
57 |
58 | log.Infof("Inserting Fortinet into DB (%s).", driver.Name())
59 | if err := driver.InsertFortinet(advs); err != nil {
60 | return xerrors.Errorf("Failed to insert. dbpath: %s, err: %w", viper.GetString("dbpath"), err)
61 | }
62 |
63 | fetchMeta.LastFetchedAt = time.Now()
64 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil {
65 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err)
66 | }
67 |
68 | log.Infof("Finished fetching Fortinet.")
69 | return nil
70 | }
71 |
--------------------------------------------------------------------------------
/commands/fetchjvn.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "errors"
5 | "time"
6 |
7 | "github.com/spf13/cobra"
8 | "github.com/spf13/viper"
9 | "golang.org/x/xerrors"
10 |
11 | "github.com/vulsio/go-cve-dictionary/db"
12 | "github.com/vulsio/go-cve-dictionary/log"
13 | "github.com/vulsio/go-cve-dictionary/models"
14 | )
15 |
16 | var fetchJvnCmd = &cobra.Command{
17 | Use: "jvn",
18 | Short: "Fetch Vulnerability dictionary from JVN",
19 | Long: "Fetch Vulnerability dictionary from JVN",
20 | RunE: fetchJvn,
21 | }
22 |
23 | func init() {
24 | fetchCmd.AddCommand(fetchJvnCmd)
25 |
26 | fetchJvnCmd.PersistentFlags().Bool("without-jvncert", false, "not request to jvn cert.")
27 | _ = viper.BindPFlag("without-jvncert", fetchJvnCmd.PersistentFlags().Lookup("without-jvncert"))
28 | }
29 |
30 | func fetchJvn(_ *cobra.Command, args []string) (err error) {
31 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil {
32 | return xerrors.Errorf("Failed to SetLogger. err: %w", err)
33 | }
34 |
35 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{})
36 | if err != nil {
37 | if errors.Is(err, db.ErrDBLocked) {
38 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err)
39 | }
40 | return xerrors.Errorf("Failed to open DB. err: %w", err)
41 | }
42 |
43 | fetchMeta, err := driver.GetFetchMeta()
44 | if err != nil {
45 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err)
46 | }
47 | if fetchMeta.OutDated() {
48 | return xerrors.Errorf("Failed to Insert CVEs into DB. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion})
49 | }
50 | // If the fetch fails the first time (without SchemaVersion), the DB needs to be cleaned every time, so insert SchemaVersion.
51 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil {
52 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err)
53 | }
54 |
55 | log.Infof("Inserting JVN into DB (%s).", driver.Name())
56 | if err := driver.InsertJvn(args); err != nil {
57 | return xerrors.Errorf("Failed to insert. dbpath: %s, err: %w", viper.GetString("dbpath"), err)
58 | }
59 |
60 | fetchMeta.LastFetchedAt = time.Now()
61 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil {
62 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err)
63 | }
64 |
65 | log.Infof("Finished fetching JVN.")
66 | return nil
67 | }
68 |
--------------------------------------------------------------------------------
/commands/fetchmitre.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "errors"
5 | "time"
6 |
7 | "github.com/spf13/cobra"
8 | "github.com/spf13/viper"
9 | "golang.org/x/xerrors"
10 |
11 | "github.com/vulsio/go-cve-dictionary/db"
12 | "github.com/vulsio/go-cve-dictionary/log"
13 | "github.com/vulsio/go-cve-dictionary/models"
14 | )
15 |
16 | var fetchMitreCmd = &cobra.Command{
17 | Use: "mitre",
18 | Short: "Fetch Vulnerability dictionary from MITRE",
19 | Long: "Fetch Vulnerability dictionary from MITRE",
20 | RunE: fetchMitre,
21 | }
22 |
23 | func init() {
24 | fetchCmd.AddCommand(fetchMitreCmd)
25 | }
26 |
27 | func fetchMitre(_ *cobra.Command, args []string) (err error) {
28 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil {
29 | return xerrors.Errorf("Failed to SetLogger. err: %w", err)
30 | }
31 |
32 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{})
33 | if err != nil {
34 | if errors.Is(err, db.ErrDBLocked) {
35 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err)
36 | }
37 | return xerrors.Errorf("Failed to open DB. err: %w", err)
38 | }
39 |
40 | fetchMeta, err := driver.GetFetchMeta()
41 | if err != nil {
42 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err)
43 | }
44 | if fetchMeta.OutDated() {
45 | return xerrors.Errorf("Failed to Insert CVEs into DB. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion})
46 | }
47 | // If the fetch fails the first time (without SchemaVersion), the DB needs to be cleaned every time, so insert SchemaVersion.
48 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil {
49 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err)
50 | }
51 |
52 | log.Infof("Inserting MITRE into DB (%s).", driver.Name())
53 | if err := driver.InsertMitre(args); err != nil {
54 | return xerrors.Errorf("Failed to insert. dbpath: %s, err: %w", viper.GetString("dbpath"), err)
55 | }
56 |
57 | fetchMeta.LastFetchedAt = time.Now()
58 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil {
59 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err)
60 | }
61 |
62 | log.Infof("Finished fetching MITRE.")
63 | return nil
64 | }
65 |
--------------------------------------------------------------------------------
/commands/fetchnvd.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "errors"
5 | "time"
6 |
7 | "github.com/spf13/cobra"
8 | "github.com/spf13/viper"
9 | "golang.org/x/xerrors"
10 |
11 | "github.com/vulsio/go-cve-dictionary/db"
12 | "github.com/vulsio/go-cve-dictionary/log"
13 | "github.com/vulsio/go-cve-dictionary/models"
14 | )
15 |
16 | var fetchNvdCmd = &cobra.Command{
17 | Use: "nvd",
18 | Short: "Fetch Vulnerability dictionary from NVD",
19 | Long: "Fetch Vulnerability dictionary from NVD",
20 | RunE: fetchNvd,
21 | }
22 |
23 | func init() {
24 | fetchCmd.AddCommand(fetchNvdCmd)
25 |
26 | fetchNvdCmd.PersistentFlags().Bool("full", false, "Collect large amounts of CPE relate data")
27 | _ = viper.BindPFlag("full", fetchNvdCmd.PersistentFlags().Lookup("full"))
28 | }
29 |
30 | func fetchNvd(_ *cobra.Command, args []string) (err error) {
31 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil {
32 | return xerrors.Errorf("Failed to SetLogger. err: %w", err)
33 | }
34 |
35 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{})
36 | if err != nil {
37 | if errors.Is(err, db.ErrDBLocked) {
38 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err)
39 | }
40 | return xerrors.Errorf("Failed to open DB. err: %w", err)
41 | }
42 |
43 | fetchMeta, err := driver.GetFetchMeta()
44 | if err != nil {
45 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err)
46 | }
47 | if fetchMeta.OutDated() {
48 | return xerrors.Errorf("Failed to Insert CVEs into DB. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion})
49 | }
50 | // If the fetch fails the first time (without SchemaVersion), the DB needs to be cleaned every time, so insert SchemaVersion.
51 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil {
52 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err)
53 | }
54 |
55 | log.Infof("Inserting NVD into DB (%s).", driver.Name())
56 | if err := driver.InsertNvd(args); err != nil {
57 | return xerrors.Errorf("Failed to insert. dbpath: %s, err: %w", viper.GetString("dbpath"), err)
58 | }
59 |
60 | fetchMeta.LastFetchedAt = time.Now()
61 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil {
62 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err)
63 | }
64 |
65 | log.Infof("Finished fetching NVD.")
66 | return nil
67 | }
68 |
--------------------------------------------------------------------------------
/commands/fetchpaloalto.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "errors"
5 | "time"
6 |
7 | "github.com/spf13/cobra"
8 | "github.com/spf13/viper"
9 | "golang.org/x/xerrors"
10 |
11 | "github.com/vulsio/go-cve-dictionary/db"
12 | "github.com/vulsio/go-cve-dictionary/fetcher/paloalto"
13 | "github.com/vulsio/go-cve-dictionary/log"
14 | "github.com/vulsio/go-cve-dictionary/models"
15 | )
16 |
17 | var fetchPaloaltoCmd = &cobra.Command{
18 | Use: "paloalto",
19 | Short: "Fetch Vulnerability dictionary from Palo Alto Networks Advisories",
20 | Long: "Fetch Vulnerability dictionary from Palo Alto Networks Advisories",
21 | RunE: fetchPaloalto,
22 | }
23 |
24 | func init() {
25 | fetchCmd.AddCommand(fetchPaloaltoCmd)
26 | }
27 |
28 | func fetchPaloalto(_ *cobra.Command, _ []string) (err error) {
29 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil {
30 | return xerrors.Errorf("Failed to SetLogger. err: %w", err)
31 | }
32 |
33 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{})
34 | if err != nil {
35 | if errors.Is(err, db.ErrDBLocked) {
36 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err)
37 | }
38 | return xerrors.Errorf("Failed to open DB. err: %w", err)
39 | }
40 |
41 | fetchMeta, err := driver.GetFetchMeta()
42 | if err != nil {
43 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err)
44 | }
45 | if fetchMeta.OutDated() {
46 | return xerrors.Errorf("Failed to Insert CVEs into DB. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion})
47 | }
48 | // If the fetch fails the first time (without SchemaVersion), the DB needs to be cleaned every time, so insert SchemaVersion.
49 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil {
50 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err)
51 | }
52 |
53 | advs, err := paloalto.FetchConvert()
54 | if err != nil {
55 | return xerrors.Errorf("Failed to fetch from paloalto. err: %w", err)
56 | }
57 |
58 | log.Infof("Inserting Paloalto into DB (%s).", driver.Name())
59 | if err := driver.InsertPaloalto(advs); err != nil {
60 | return xerrors.Errorf("Failed to insert. dbpath: %s, err: %w", viper.GetString("dbpath"), err)
61 | }
62 |
63 | fetchMeta.LastFetchedAt = time.Now()
64 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil {
65 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err)
66 | }
67 |
68 | log.Infof("Finished fetching Paloalto.")
69 | return nil
70 | }
71 |
--------------------------------------------------------------------------------
/commands/root.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "path/filepath"
7 |
8 | homedir "github.com/mitchellh/go-homedir"
9 | "github.com/spf13/cobra"
10 | "github.com/spf13/viper"
11 |
12 | "github.com/vulsio/go-cve-dictionary/log"
13 | )
14 |
15 | var cfgFile string
16 |
17 | // RootCmd represents the base command when called without any subcommands
18 | var RootCmd = &cobra.Command{
19 | Use: "go-cve-dictionary",
20 | Short: "GO CVE Dictionary",
21 | Long: `GO CVE Dictionary`,
22 | SilenceErrors: true,
23 | SilenceUsage: true,
24 | }
25 |
26 | func init() {
27 | cobra.OnInitialize(initConfig)
28 |
29 | RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.go-cve-dictionary.yaml)")
30 |
31 | RootCmd.PersistentFlags().Bool("log-to-file", false, "output log to file")
32 | _ = viper.BindPFlag("log-to-file", RootCmd.PersistentFlags().Lookup("log-to-file"))
33 |
34 | RootCmd.PersistentFlags().String("log-dir", log.GetDefaultLogDir(), "/path/to/log")
35 | _ = viper.BindPFlag("log-dir", RootCmd.PersistentFlags().Lookup("log-dir"))
36 |
37 | RootCmd.PersistentFlags().Bool("log-json", false, "output log as JSON")
38 | _ = viper.BindPFlag("log-json", RootCmd.PersistentFlags().Lookup("log-json"))
39 |
40 | RootCmd.PersistentFlags().Bool("debug", false, "debug mode (default: false)")
41 | _ = viper.BindPFlag("debug", RootCmd.PersistentFlags().Lookup("debug"))
42 |
43 | RootCmd.PersistentFlags().Bool("debug-sql", false, "SQL debug mode")
44 | _ = viper.BindPFlag("debug-sql", RootCmd.PersistentFlags().Lookup("debug-sql"))
45 |
46 | pwd := os.Getenv("PWD")
47 | RootCmd.PersistentFlags().String("dbpath", filepath.Join(pwd, "cve.sqlite3"), "/path/to/sqlite3 or SQL connection string")
48 | _ = viper.BindPFlag("dbpath", RootCmd.PersistentFlags().Lookup("dbpath"))
49 |
50 | RootCmd.PersistentFlags().String("dbtype", "sqlite3", "Database type to store data in (sqlite3, mysql, postgres or redis supported)")
51 | _ = viper.BindPFlag("dbtype", RootCmd.PersistentFlags().Lookup("dbtype"))
52 |
53 | RootCmd.PersistentFlags().String("http-proxy", "", "http://proxy-url:port (default: empty)")
54 | _ = viper.BindPFlag("http-proxy", RootCmd.PersistentFlags().Lookup("http-proxy"))
55 | }
56 |
57 | // initConfig reads in config file and ENV variables if set.
58 | func initConfig() {
59 | if cfgFile != "" {
60 | viper.SetConfigFile(cfgFile)
61 | } else {
62 | // Find home directory.
63 | home, err := homedir.Dir()
64 | if err != nil {
65 | log.Errorf("Failed to find home directory. err: %s", err)
66 | os.Exit(1)
67 | }
68 |
69 | // Search config in home directory with name ".go-cve-dictionary" (without extension).
70 | viper.AddConfigPath(home)
71 | viper.SetConfigName(".go-cve-dictionary")
72 | }
73 |
74 | viper.AutomaticEnv() // read in environment variables that match
75 |
76 | // If a config file is found, read it in.
77 | if err := viper.ReadInConfig(); err == nil {
78 | fmt.Println("Using config file:", viper.ConfigFileUsed())
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/commands/search.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "os"
7 |
8 | "github.com/spf13/cobra"
9 | "github.com/spf13/viper"
10 | "golang.org/x/xerrors"
11 |
12 | "github.com/vulsio/go-cve-dictionary/db"
13 | "github.com/vulsio/go-cve-dictionary/log"
14 | "github.com/vulsio/go-cve-dictionary/models"
15 | )
16 |
17 | var searchCmd = &cobra.Command{
18 | Use: "search",
19 | Short: "Search for Vulnerability in the dictionary",
20 | Long: "Search for Vulnerability in the dictionary",
21 | }
22 |
23 | var searchCVECmd = &cobra.Command{
24 | Use: "cve",
25 | Short: "Search for Vulnerability in the dictionary by CVEID",
26 | Long: "Search for Vulnerability in the dictionary by CVEID",
27 | RunE: searchCVE,
28 | }
29 |
30 | var searchAdvisoryCmd = &cobra.Command{
31 | Use: "advisories",
32 | Short: "Search for Advisories",
33 | Long: "Search for Advisories",
34 | ValidArgs: []string{"jvn", "fortinet", "paloalto", "cisco"},
35 | Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
36 | RunE: searchAdvisories,
37 | }
38 |
39 | var searchCPECmd = &cobra.Command{
40 | Use: "cpe",
41 | Short: "Search for Vulnerability in the dictionary by CPE",
42 | Long: "Search for Vulnerability in the dictionary by CPE",
43 | Args: cobra.ExactArgs(1),
44 | RunE: searchCPE,
45 | }
46 |
47 | func searchCVE(_ *cobra.Command, args []string) error {
48 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil {
49 | return xerrors.Errorf("Failed to SetLogger. err: %w", err)
50 | }
51 |
52 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{})
53 | if err != nil {
54 | if errors.Is(err, db.ErrDBLocked) {
55 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err)
56 | }
57 | return xerrors.Errorf("Failed to open DB. err: %w", err)
58 | }
59 |
60 | fetchMeta, err := driver.GetFetchMeta()
61 | if err != nil {
62 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err)
63 | }
64 | if fetchMeta.OutDated() {
65 | return xerrors.Errorf("Failed to start server. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion})
66 | }
67 |
68 | count := 0
69 | nvdCount, err := driver.CountNvd()
70 | if err != nil {
71 | log.Errorf("Failed to count NVD table: %s", err)
72 | return err
73 | }
74 | count += nvdCount
75 |
76 | jvnCount, err := driver.CountJvn()
77 | if err != nil {
78 | log.Errorf("Failed to count JVN table: %s", err)
79 | return err
80 | }
81 | count += jvnCount
82 |
83 | fortinetCount, err := driver.CountFortinet()
84 | if err != nil {
85 | log.Errorf("Failed to count Fortinet table: %s", err)
86 | return err
87 | }
88 | count += fortinetCount
89 |
90 | mitreCount, err := driver.CountMitre()
91 | if err != nil {
92 | log.Errorf("Failed to count MITRE table: %s", err)
93 | return err
94 | }
95 | count += mitreCount
96 |
97 | paloaltoCount, err := driver.CountPaloalto()
98 | if err != nil {
99 | log.Errorf("Failed to count Paloalto table: %s", err)
100 | return err
101 | }
102 | count += paloaltoCount
103 |
104 | ciscoCount, err := driver.CountCisco()
105 | if err != nil {
106 | log.Errorf("Failed to count Cisco table: %s", err)
107 | return err
108 | }
109 | count += ciscoCount
110 |
111 | if count == 0 {
112 | log.Infof("No Vulnerability data found. Run the below command to fetch data from NVD, JVN, Fortinet, MITRE, Paloalto, Cisco")
113 | log.Infof("")
114 | log.Infof(" go-cve-dictionary fetch nvd")
115 | log.Infof(" go-cve-dictionary fetch jvn")
116 | log.Infof(" go-cve-dictionary fetch fortinet")
117 | log.Infof(" go-cve-dictionary fetch mitre")
118 | log.Infof(" go-cve-dictionary fetch paloalto")
119 | log.Infof(" go-cve-dictionary fetch cisco")
120 | log.Infof("")
121 | return nil
122 | }
123 |
124 | enc := json.NewEncoder(os.Stdout)
125 | enc.SetIndent("", " ")
126 | switch len(args) {
127 | case 0:
128 | cveids, err := driver.GetCveIDs()
129 | if err != nil {
130 | return xerrors.Errorf("Failed to get All CVEIDs. err: %w", err)
131 | }
132 | if err := enc.Encode(cveids); err != nil {
133 | return xerrors.Errorf("Failed to encode All CVEIDs. err: %w", err)
134 | }
135 | case 1:
136 | d, err := driver.Get(args[0])
137 | if err != nil {
138 | return xerrors.Errorf("Failed to get CVEDetail by CVEID. err: %w", err)
139 | }
140 | if err := enc.Encode(d); err != nil {
141 | return xerrors.Errorf("Failed to encode CVEDetail by CVEID. err: %w", err)
142 | }
143 | default:
144 | ds, err := driver.GetMulti(args)
145 | if err != nil {
146 | return xerrors.Errorf("Failed to get CVEDetails by CVEIDs. err: %w", err)
147 | }
148 | if err := enc.Encode(ds); err != nil {
149 | return xerrors.Errorf("Failed to encode CVEDetails by CVEIDs. err: %w", err)
150 | }
151 | }
152 |
153 | return nil
154 | }
155 |
156 | func searchAdvisories(_ *cobra.Command, args []string) error {
157 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil {
158 | return xerrors.Errorf("Failed to SetLogger. err: %w", err)
159 | }
160 |
161 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{})
162 | if err != nil {
163 | if errors.Is(err, db.ErrDBLocked) {
164 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err)
165 | }
166 | return xerrors.Errorf("Failed to open DB. err: %w", err)
167 | }
168 |
169 | fetchMeta, err := driver.GetFetchMeta()
170 | if err != nil {
171 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err)
172 | }
173 | if fetchMeta.OutDated() {
174 | return xerrors.Errorf("Failed to start server. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion})
175 | }
176 |
177 | count := 0
178 | jvnCount, err := driver.CountJvn()
179 | if err != nil {
180 | log.Errorf("Failed to count JVN table: %s", err)
181 | return err
182 | }
183 | count += jvnCount
184 |
185 | fortinetCount, err := driver.CountFortinet()
186 | if err != nil {
187 | log.Errorf("Failed to count Fortinet table: %s", err)
188 | return err
189 | }
190 | count += fortinetCount
191 |
192 | paloaltoCount, err := driver.CountPaloalto()
193 | if err != nil {
194 | log.Errorf("Failed to count Paloalto table: %s", err)
195 | return err
196 | }
197 | count += paloaltoCount
198 |
199 | ciscoCount, err := driver.CountCisco()
200 | if err != nil {
201 | log.Errorf("Failed to count Cisco table: %s", err)
202 | return err
203 | }
204 | count += ciscoCount
205 |
206 | if count == 0 {
207 | log.Infof("No Advisory data found. Run the below command to fetch data from JVN, Fortinet, Paloalto, Cisco")
208 | log.Infof("")
209 | log.Infof(" go-cve-dictionary fetch jvn")
210 | log.Infof(" go-cve-dictionary fetch fortinet")
211 | log.Infof(" go-cve-dictionary fetch paloalto")
212 | log.Infof(" go-cve-dictionary fetch cisco")
213 | log.Infof("")
214 | return nil
215 | }
216 |
217 | enc := json.NewEncoder(os.Stdout)
218 | enc.SetIndent("", " ")
219 | switch args[0] {
220 | case "jvn":
221 | m, err := driver.GetAdvisoriesJvn()
222 | if err != nil {
223 | return xerrors.Errorf("Failed to Get JVN Advisories. err: %w", err)
224 | }
225 | if err := enc.Encode(m); err != nil {
226 | return xerrors.Errorf("Failed to encode All Advisories. err: %w", err)
227 | }
228 | case "fortinet":
229 | m, err := driver.GetAdvisoriesFortinet()
230 | if err != nil {
231 | return xerrors.Errorf("Failed to Get Fortinet Advisories. err: %w", err)
232 | }
233 | if err := enc.Encode(m); err != nil {
234 | return xerrors.Errorf("Failed to encode All Advisories. err: %w", err)
235 | }
236 | case "paloalto":
237 | m, err := driver.GetAdvisoriesPaloalto()
238 | if err != nil {
239 | return xerrors.Errorf("Failed to Get Paloalto Advisories. err: %w", err)
240 | }
241 | if err := enc.Encode(m); err != nil {
242 | return xerrors.Errorf("Failed to encode All Advisories. err: %w", err)
243 | }
244 | case "cisco":
245 | m, err := driver.GetAdvisoriesCisco()
246 | if err != nil {
247 | return xerrors.Errorf("Failed to Get Cisco Advisories. err: %w", err)
248 | }
249 | if err := enc.Encode(m); err != nil {
250 | return xerrors.Errorf("Failed to encode All Advisories. err: %w", err)
251 | }
252 | default:
253 | return xerrors.Errorf("not support type: %s", args[0])
254 | }
255 |
256 | return nil
257 | }
258 |
259 | func searchCPE(_ *cobra.Command, args []string) error {
260 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil {
261 | return xerrors.Errorf("Failed to SetLogger. err: %w", err)
262 | }
263 |
264 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{})
265 | if err != nil {
266 | if errors.Is(err, db.ErrDBLocked) {
267 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err)
268 | }
269 | return xerrors.Errorf("Failed to open DB. err: %w", err)
270 | }
271 |
272 | fetchMeta, err := driver.GetFetchMeta()
273 | if err != nil {
274 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err)
275 | }
276 | if fetchMeta.OutDated() {
277 | return xerrors.Errorf("Failed to start server. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion})
278 | }
279 |
280 | count := 0
281 | nvdCount, err := driver.CountNvd()
282 | if err != nil {
283 | log.Errorf("Failed to count NVD table: %s", err)
284 | return err
285 | }
286 | count += nvdCount
287 |
288 | jvnCount, err := driver.CountJvn()
289 | if err != nil {
290 | log.Errorf("Failed to count JVN table: %s", err)
291 | return err
292 | }
293 | count += jvnCount
294 |
295 | fortinetCount, err := driver.CountFortinet()
296 | if err != nil {
297 | log.Errorf("Failed to count Fortinet table: %s", err)
298 | return err
299 | }
300 | count += fortinetCount
301 |
302 | mitreCount, err := driver.CountMitre()
303 | if err != nil {
304 | log.Errorf("Failed to count MITRE table: %s", err)
305 | return err
306 | }
307 | count += mitreCount
308 |
309 | paloaltoCount, err := driver.CountPaloalto()
310 | if err != nil {
311 | log.Errorf("Failed to count Paloalto table: %s", err)
312 | return err
313 | }
314 | count += paloaltoCount
315 |
316 | ciscoCount, err := driver.CountCisco()
317 | if err != nil {
318 | log.Errorf("Failed to count Cisco table: %s", err)
319 | return err
320 | }
321 | count += ciscoCount
322 |
323 | if count == 0 {
324 | log.Infof("No Vulnerability data found. Run the below command to fetch data from NVD, JVN, Fortinet, MITRE, Paloalto, Cisco")
325 | log.Infof("")
326 | log.Infof(" go-cve-dictionary fetch nvd")
327 | log.Infof(" go-cve-dictionary fetch jvn")
328 | log.Infof(" go-cve-dictionary fetch fortinet")
329 | log.Infof(" go-cve-dictionary fetch mitre")
330 | log.Infof(" go-cve-dictionary fetch paloalto")
331 | log.Infof(" go-cve-dictionary fetch cisco")
332 | log.Infof("")
333 | return nil
334 | }
335 |
336 | enc := json.NewEncoder(os.Stdout)
337 | enc.SetIndent("", " ")
338 | if viper.GetBool("cveid-only") {
339 | cveids, err := driver.GetCveIDsByCpeURI(args[0])
340 | if err != nil {
341 | return xerrors.Errorf("Failed to Get CVEIDs by CPE URI. err: %w", err)
342 | }
343 | if err := enc.Encode(map[string][]string{"NVD": cveids.Nvd, "JVN": cveids.Jvn, "Fortinet": cveids.Fortinet, "Paloalto": cveids.Paloalto, "Cisco": cveids.Cisco}); err != nil {
344 | return xerrors.Errorf("Failed to encode CVEIDs by CPE URI. err: %w", err)
345 | }
346 | return nil
347 | }
348 | cveDetails, err := driver.GetByCpeURI(args[0])
349 | if err != nil {
350 | return xerrors.Errorf("Failed to Get CVEDetails by CPE URI. err: %w", err)
351 | }
352 | if err := enc.Encode(cveDetails); err != nil {
353 | return xerrors.Errorf("Failed to encode CVEDetails by CPE URI. err: %w", err)
354 | }
355 |
356 | return nil
357 | }
358 |
359 | func init() {
360 | RootCmd.AddCommand(searchCmd)
361 | searchCmd.AddCommand(searchCVECmd, searchAdvisoryCmd, searchCPECmd)
362 |
363 | searchCPECmd.PersistentFlags().Bool("cveid-only", false, "show only CVEID in search results")
364 | _ = viper.BindPFlag("cveid-only", searchCPECmd.PersistentFlags().Lookup("cveid-only"))
365 | }
366 |
--------------------------------------------------------------------------------
/commands/server.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/spf13/cobra"
7 | "github.com/spf13/viper"
8 | "golang.org/x/xerrors"
9 |
10 | "github.com/vulsio/go-cve-dictionary/db"
11 | "github.com/vulsio/go-cve-dictionary/log"
12 | "github.com/vulsio/go-cve-dictionary/models"
13 | "github.com/vulsio/go-cve-dictionary/server"
14 | )
15 |
16 | // serverCmd is Subcommand for CVE dictionary HTTP Server
17 | var serverCmd = &cobra.Command{
18 | Use: "server",
19 | Short: "Start CVE dictionary HTTP Server",
20 | Long: `Start CVE dictionary HTTP Server`,
21 | RunE: executeServer,
22 | }
23 |
24 | func init() {
25 | RootCmd.AddCommand(serverCmd)
26 |
27 | serverCmd.PersistentFlags().String("bind", "127.0.0.1", "HTTP server bind to IP address")
28 | _ = viper.BindPFlag("bind", serverCmd.PersistentFlags().Lookup("bind"))
29 |
30 | serverCmd.PersistentFlags().String("port", "1323", "HTTP server port number")
31 | _ = viper.BindPFlag("port", serverCmd.PersistentFlags().Lookup("port"))
32 | }
33 |
34 | func executeServer(_ *cobra.Command, _ []string) (err error) {
35 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil {
36 | return xerrors.Errorf("Failed to SetLogger. err: %w", err)
37 | }
38 |
39 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{})
40 | if err != nil {
41 | if errors.Is(err, db.ErrDBLocked) {
42 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err)
43 | }
44 | return xerrors.Errorf("Failed to open DB. err: %w", err)
45 | }
46 |
47 | fetchMeta, err := driver.GetFetchMeta()
48 | if err != nil {
49 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err)
50 | }
51 | if fetchMeta.OutDated() {
52 | return xerrors.Errorf("Failed to start server. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion})
53 | }
54 |
55 | count := 0
56 | nvdCount, err := driver.CountNvd()
57 | if err != nil {
58 | log.Errorf("Failed to count NVD table: %s", err)
59 | return err
60 | }
61 | count += nvdCount
62 |
63 | jvnCount, err := driver.CountJvn()
64 | if err != nil {
65 | log.Errorf("Failed to count JVN table: %s", err)
66 | return err
67 | }
68 | count += jvnCount
69 |
70 | fortinetCount, err := driver.CountFortinet()
71 | if err != nil {
72 | log.Errorf("Failed to count Fortinet table: %s", err)
73 | return err
74 | }
75 | count += fortinetCount
76 |
77 | mitreCount, err := driver.CountMitre()
78 | if err != nil {
79 | log.Errorf("Failed to count MITRE table: %s", err)
80 | return err
81 | }
82 | count += mitreCount
83 |
84 | paloaltoCount, err := driver.CountPaloalto()
85 | if err != nil {
86 | log.Errorf("Failed to count Paloalto table: %s", err)
87 | return err
88 | }
89 | count += paloaltoCount
90 |
91 | ciscoCount, err := driver.CountCisco()
92 | if err != nil {
93 | log.Errorf("Failed to count Cisco table: %s", err)
94 | return err
95 | }
96 | count += ciscoCount
97 |
98 | if count == 0 {
99 | log.Infof("No Vulnerability data found. Run the below command to fetch data from NVD, JVN, Fortinet, MITRE, Paloalto, Cisco")
100 | log.Infof("")
101 | log.Infof(" go-cve-dictionary fetch nvd")
102 | log.Infof(" go-cve-dictionary fetch jvn")
103 | log.Infof(" go-cve-dictionary fetch fortinet")
104 | log.Infof(" go-cve-dictionary fetch mitre")
105 | log.Infof(" go-cve-dictionary fetch paloalto")
106 | log.Infof(" go-cve-dictionary fetch cisco")
107 | log.Infof("")
108 | return nil
109 | }
110 |
111 | log.Infof("Starting HTTP Server...")
112 | if err = server.Start(viper.GetBool("log-to-file"), viper.GetString("log-dir"), driver); err != nil {
113 | return xerrors.Errorf("Failed to start server. err: %w", err)
114 | }
115 | return nil
116 | }
117 |
--------------------------------------------------------------------------------
/commands/version.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/spf13/cobra"
7 |
8 | "github.com/vulsio/go-cve-dictionary/config"
9 | )
10 |
11 | func init() {
12 | RootCmd.AddCommand(versionCmd)
13 | }
14 |
15 | var versionCmd = &cobra.Command{
16 | Use: "version",
17 | Short: "Show version",
18 | Long: `Show version`,
19 | Run: func(_ *cobra.Command, _ []string) {
20 | fmt.Printf("go-cve-dictionary %s %s\n", config.Version, config.Revision)
21 | },
22 | }
23 |
--------------------------------------------------------------------------------
/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | // Version ... Version
4 | var Version = "`make build` or `make install` will show the version"
5 |
6 | // Revision of Git
7 | var Revision string
8 |
--------------------------------------------------------------------------------
/config/config_test.go:
--------------------------------------------------------------------------------
1 | package config
2 |
--------------------------------------------------------------------------------
/db/rdb_test.go:
--------------------------------------------------------------------------------
1 | package db
2 |
--------------------------------------------------------------------------------
/db/redis_test.go:
--------------------------------------------------------------------------------
1 | package db
2 |
--------------------------------------------------------------------------------
/db/util.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "iter"
5 |
6 | "golang.org/x/xerrors"
7 | )
8 |
9 | // chunk chunks the sequence into n-sized chunks
10 | // Note: slices.Chunk doesn't support iterators as of Go 1.23.
11 | // https://pkg.go.dev/slices#Chunk
12 | func chunk[T any](s iter.Seq2[T, error], n int) iter.Seq2[[]T, error] {
13 | return func(yield func([]T, error) bool) {
14 | if n < 1 {
15 | if !yield(nil, xerrors.New("cannot be less than 1")) {
16 | return
17 | }
18 | }
19 |
20 | chunk := make([]T, 0, n)
21 | for t, err := range s {
22 | if err != nil {
23 | if !yield(nil, err) {
24 | return
25 | }
26 | continue
27 | }
28 | chunk = append(chunk, t)
29 | if len(chunk) != n {
30 | continue
31 | }
32 |
33 | if !yield(chunk, nil) {
34 | return
35 | }
36 | chunk = chunk[:0]
37 | }
38 |
39 | if len(chunk) > 0 {
40 | if !yield(chunk, nil) {
41 | return
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/fetcher/cisco/cisco_test.go:
--------------------------------------------------------------------------------
1 | package cisco
2 |
3 | import (
4 | "iter"
5 | "testing"
6 | "time"
7 |
8 | "github.com/google/go-cmp/cmp"
9 | "github.com/google/go-cmp/cmp/cmpopts"
10 |
11 | "github.com/vulsio/go-cve-dictionary/models"
12 | )
13 |
14 | func Test_convert(t *testing.T) {
15 | type args struct {
16 | raw advisory
17 | }
18 | tests := []struct {
19 | name string
20 | args args
21 | want iter.Seq[models.Cisco]
22 | wantErr bool
23 | }{
24 | {
25 | name: "cisco-sa-19950601-key-packet-bypass",
26 | args: args{
27 | raw: advisory{
28 | AdvisoryID: "cisco-sa-19950601-key-packet-bypass",
29 | AdvisoryTitle: "\"Established\" Keyword May Allow Packets to Bypass Filter",
30 | BugIDs: []string{"NA"},
31 | CsafURL: "NA",
32 | Cves: []string{"NA"},
33 | CvrfURL: "https://sec.cloudapps.cisco.com/security/center/contentxml/CiscoSecurityAdvisory/cisco-sa-19950601-key-packet-bypass/cvrf/cisco-sa-19950601-key-packet-bypass_cvrf.xml",
34 | CvssBaseScore: "NA",
35 | FirstPublished: "1995-06-02T06:37:00",
36 | IpsSignatures: []string{"NA"},
37 | LastUpdated: "1995-06-02T06:37:00",
38 | ProductNames: []string{"NA"},
39 | PublicationURL: "https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-19950601-key-packet-bypass",
40 | Sir: "NA",
41 | Status: "Final",
42 | Summary: "This document describes a vulnerability in Cisco's IOS software when\r\nthe 'established' keyword is used in extended IP access control lists. This bug\r\ncan, under very specific circumstances and only with certain IP host\r\nimplementations, allow unauthorized packets to circumvent a filtering router.\r\n \r\nThis advisory is posted at https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-19950601-key-packet-bypass.",
43 | Version: "1.0",
44 | },
45 | },
46 | want: func(yield func(models.Cisco) bool) {
47 | if !yield(models.Cisco{
48 | AdvisoryID: "cisco-sa-19950601-key-packet-bypass",
49 | Title: "\"Established\" Keyword May Allow Packets to Bypass Filter",
50 | Summary: "This document describes a vulnerability in Cisco's IOS software when\r\nthe 'established' keyword is used in extended IP access control lists. This bug\r\ncan, under very specific circumstances and only with certain IP host\r\nimplementations, allow unauthorized packets to circumvent a filtering router.\r\n \r\nThis advisory is posted at https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-19950601-key-packet-bypass.",
51 | CveID: "cisco-sa-19950601-key-packet-bypass",
52 | References: []models.CiscoReference{
53 | {
54 | Reference: models.Reference{
55 | Link: "https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-19950601-key-packet-bypass",
56 | },
57 | },
58 | {
59 | Reference: models.Reference{
60 | Link: "https://sec.cloudapps.cisco.com/security/center/contentxml/CiscoSecurityAdvisory/cisco-sa-19950601-key-packet-bypass/cvrf/cisco-sa-19950601-key-packet-bypass_cvrf.xml",
61 | },
62 | },
63 | },
64 | FirstPublished: time.Date(1995, 6, 2, 6, 37, 0, 0, time.UTC),
65 | LastUpdated: time.Date(1995, 6, 2, 6, 37, 0, 0, time.UTC),
66 | }) {
67 | return
68 | }
69 | },
70 | },
71 | {
72 | name: "cisco-sa-snmp-dos-sdxnSUcW",
73 | args: args{
74 | raw: advisory{
75 | AdvisoryID: "cisco-sa-snmp-dos-sdxnSUcW",
76 | AdvisoryTitle: "Cisco IOS, IOS XE, and IOS XR Software SNMP Denial of Service Vulnerabilities",
77 | BugIDs: []string{"CSCwm79554", "CSCwm79564"},
78 | CsafURL: "https://sec.cloudapps.cisco.com/security/center/contentjson/CiscoSecurityAdvisory/cisco-sa-snmp-dos-sdxnSUcW/csaf/cisco-sa-snmp-dos-sdxnSUcW.json",
79 | Cves: []string{"CVE-2025-20169", "CVE-2025-20170"},
80 | CvrfURL: "https://sec.cloudapps.cisco.com/security/center/contentxml/CiscoSecurityAdvisory/cisco-sa-snmp-dos-sdxnSUcW/cvrf/cisco-sa-snmp-dos-sdxnSUcW_cvrf.xml",
81 | CvssBaseScore: "7.7",
82 | Cwe: []string{"CWE-805"},
83 | FirstPublished: "2025-02-06T00:00:00",
84 | IpsSignatures: []string{"NA"},
85 | LastUpdated: "2025-03-12T22:22:33",
86 | ProductNames: []string{
87 | "Cisco IOS XR Software ",
88 | "Cisco IOS 12.2(4)B",
89 | "Cisco IOS XE Software 3.2.0SG",
90 | },
91 | PublicationURL: "https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-snmp-dos-sdxnSUcW",
92 | Sir: "High",
93 | Status: "Final",
94 | Summary: "
Multiple vulnerabilities in the Simple Network Management Protocol (SNMP) subsystem of Cisco IOS Software, Cisco IOS XE Software, and Cisco IOS XR Software could allow an authenticated, remote attacker to cause a denial of service (DoS) condition on an affected device.
\r\nFor more information about these vulnerabilities, see the Details section of this advisory.
\r\nCisco plans to release software updates that address these vulnerabilities. There are no workarounds that address these vulnerabilities. There are mitigations that address these vulnerabilities.
\r\nThis advisory is available at the following link:
https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-snmp-dos-sdxnSUcW
",
95 | Version: "1.1",
96 | },
97 | },
98 | want: func(yield func(models.Cisco) bool) {
99 | if !yield(models.Cisco{
100 | AdvisoryID: "cisco-sa-snmp-dos-sdxnSUcW",
101 | Title: "Cisco IOS, IOS XE, and IOS XR Software SNMP Denial of Service Vulnerabilities",
102 | Summary: "Multiple vulnerabilities in the Simple Network Management Protocol (SNMP) subsystem of Cisco IOS Software, Cisco IOS XE Software, and Cisco IOS XR Software could allow an authenticated, remote attacker to cause a denial of service (DoS) condition on an affected device.
\r\nFor more information about these vulnerabilities, see the Details section of this advisory.
\r\nCisco plans to release software updates that address these vulnerabilities. There are no workarounds that address these vulnerabilities. There are mitigations that address these vulnerabilities.
\r\nThis advisory is available at the following link:
https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-snmp-dos-sdxnSUcW
",
103 | SIR: "High",
104 | CveID: "CVE-2025-20169",
105 | BugIDs: []models.CiscoBugID{
106 | {BugID: "CSCwm79554"},
107 | {BugID: "CSCwm79564"},
108 | },
109 | CweIDs: []models.CiscoCweID{
110 | {CweID: "CWE-805"},
111 | },
112 | Affected: []models.CiscoProduct{
113 | {
114 | CpeBase: models.CpeBase{
115 | URI: "cpe:/o:cisco:ios:12.2%284%29b",
116 | FormattedString: "cpe:2.3:o:cisco:ios:12.2\\(4\\)b:*:*:*:*:*:*:*",
117 | WellFormedName: `wfn:[part="o", vendor="cisco", product="ios", version="12\.2\(4\)b", update=ANY, edition=ANY, language=ANY, sw_edition=ANY, target_sw=ANY, target_hw=ANY, other=ANY]`,
118 | CpeWFN: models.CpeWFN{
119 | Part: "o",
120 | Vendor: "cisco",
121 | Product: "ios",
122 | Version: "12\\.2\\(4\\)b",
123 | Update: "ANY",
124 | Edition: "ANY",
125 | Language: "ANY",
126 | SoftwareEdition: "ANY",
127 | TargetSW: "ANY",
128 | TargetHW: "ANY",
129 | Other: "ANY",
130 | },
131 | },
132 | },
133 | {
134 | CpeBase: models.CpeBase{
135 | URI: "cpe:/o:cisco:ios_xe:3.2.0sg",
136 | FormattedString: "cpe:2.3:o:cisco:ios_xe:3.2.0sg:*:*:*:*:*:*:*",
137 | WellFormedName: `wfn:[part="o", vendor="cisco", product="ios_xe", version="3\.2\.0sg", update=ANY, edition=ANY, language=ANY, sw_edition=ANY, target_sw=ANY, target_hw=ANY, other=ANY]`,
138 | CpeWFN: models.CpeWFN{
139 | Part: "o",
140 | Vendor: "cisco",
141 | Product: "ios_xe",
142 | Version: "3\\.2\\.0sg",
143 | Update: "ANY",
144 | Edition: "ANY",
145 | Language: "ANY",
146 | SoftwareEdition: "ANY",
147 | TargetSW: "ANY",
148 | TargetHW: "ANY",
149 | Other: "ANY",
150 | },
151 | },
152 | },
153 | },
154 | References: []models.CiscoReference{
155 | {
156 | Reference: models.Reference{
157 | Link: "https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-snmp-dos-sdxnSUcW",
158 | },
159 | },
160 | {
161 | Reference: models.Reference{
162 | Link: "https://sec.cloudapps.cisco.com/security/center/contentxml/CiscoSecurityAdvisory/cisco-sa-snmp-dos-sdxnSUcW/cvrf/cisco-sa-snmp-dos-sdxnSUcW_cvrf.xml",
163 | },
164 | },
165 | {
166 | Reference: models.Reference{
167 | Link: "https://sec.cloudapps.cisco.com/security/center/contentjson/CiscoSecurityAdvisory/cisco-sa-snmp-dos-sdxnSUcW/csaf/cisco-sa-snmp-dos-sdxnSUcW.json",
168 | },
169 | },
170 | },
171 | FirstPublished: time.Date(2025, 2, 6, 0, 0, 0, 0, time.UTC),
172 | LastUpdated: time.Date(2025, 3, 12, 22, 22, 33, 0, time.UTC),
173 | }) {
174 | return
175 | }
176 |
177 | if !yield(models.Cisco{
178 | AdvisoryID: "cisco-sa-snmp-dos-sdxnSUcW",
179 | Title: "Cisco IOS, IOS XE, and IOS XR Software SNMP Denial of Service Vulnerabilities",
180 | Summary: "Multiple vulnerabilities in the Simple Network Management Protocol (SNMP) subsystem of Cisco IOS Software, Cisco IOS XE Software, and Cisco IOS XR Software could allow an authenticated, remote attacker to cause a denial of service (DoS) condition on an affected device.
\r\nFor more information about these vulnerabilities, see the Details section of this advisory.
\r\nCisco plans to release software updates that address these vulnerabilities. There are no workarounds that address these vulnerabilities. There are mitigations that address these vulnerabilities.
\r\nThis advisory is available at the following link:
https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-snmp-dos-sdxnSUcW
",
181 | SIR: "High",
182 | CveID: "CVE-2025-20170",
183 | BugIDs: []models.CiscoBugID{
184 | {BugID: "CSCwm79554"},
185 | {BugID: "CSCwm79564"},
186 | },
187 | CweIDs: []models.CiscoCweID{
188 | {CweID: "CWE-805"},
189 | },
190 | Affected: []models.CiscoProduct{
191 | {
192 | CpeBase: models.CpeBase{
193 | URI: "cpe:/o:cisco:ios:12.2%284%29b",
194 | FormattedString: "cpe:2.3:o:cisco:ios:12.2\\(4\\)b:*:*:*:*:*:*:*",
195 | WellFormedName: `wfn:[part="o", vendor="cisco", product="ios", version="12\.2\(4\)b", update=ANY, edition=ANY, language=ANY, sw_edition=ANY, target_sw=ANY, target_hw=ANY, other=ANY]`,
196 | CpeWFN: models.CpeWFN{
197 | Part: "o",
198 | Vendor: "cisco",
199 | Product: "ios",
200 | Version: "12\\.2\\(4\\)b",
201 | Update: "ANY",
202 | Edition: "ANY",
203 | Language: "ANY",
204 | SoftwareEdition: "ANY",
205 | TargetSW: "ANY",
206 | TargetHW: "ANY",
207 | Other: "ANY",
208 | },
209 | },
210 | },
211 | {
212 | CpeBase: models.CpeBase{
213 | URI: "cpe:/o:cisco:ios_xe:3.2.0sg",
214 | FormattedString: "cpe:2.3:o:cisco:ios_xe:3.2.0sg:*:*:*:*:*:*:*",
215 | WellFormedName: `wfn:[part="o", vendor="cisco", product="ios_xe", version="3\.2\.0sg", update=ANY, edition=ANY, language=ANY, sw_edition=ANY, target_sw=ANY, target_hw=ANY, other=ANY]`,
216 | CpeWFN: models.CpeWFN{
217 | Part: "o",
218 | Vendor: "cisco",
219 | Product: "ios_xe",
220 | Version: "3\\.2\\.0sg",
221 | Update: "ANY",
222 | Edition: "ANY",
223 | Language: "ANY",
224 | SoftwareEdition: "ANY",
225 | TargetSW: "ANY",
226 | TargetHW: "ANY",
227 | Other: "ANY",
228 | },
229 | },
230 | },
231 | },
232 | References: []models.CiscoReference{
233 | {
234 | Reference: models.Reference{
235 | Link: "https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-snmp-dos-sdxnSUcW",
236 | },
237 | },
238 | {
239 | Reference: models.Reference{
240 | Link: "https://sec.cloudapps.cisco.com/security/center/contentxml/CiscoSecurityAdvisory/cisco-sa-snmp-dos-sdxnSUcW/cvrf/cisco-sa-snmp-dos-sdxnSUcW_cvrf.xml",
241 | },
242 | },
243 | {
244 | Reference: models.Reference{
245 | Link: "https://sec.cloudapps.cisco.com/security/center/contentjson/CiscoSecurityAdvisory/cisco-sa-snmp-dos-sdxnSUcW/csaf/cisco-sa-snmp-dos-sdxnSUcW.json",
246 | },
247 | },
248 | },
249 | FirstPublished: time.Date(2025, 2, 6, 0, 0, 0, 0, time.UTC),
250 | LastUpdated: time.Date(2025, 3, 12, 22, 22, 33, 0, time.UTC),
251 | }) {
252 | return
253 | }
254 | },
255 | },
256 | }
257 | for _, tt := range tests {
258 | t.Run(tt.name, func(t *testing.T) {
259 | got, err := convert(tt.args.raw)
260 | if (err != nil) != tt.wantErr {
261 | t.Errorf("convert() error = %v, wantErr %v", err, tt.wantErr)
262 | return
263 | }
264 |
265 | wnext, wstop := iter.Pull(tt.want)
266 | defer wstop()
267 | gnext, gstop := iter.Pull(got)
268 | defer gstop()
269 |
270 | for {
271 | want, wantOk := wnext()
272 | got, gotOk := gnext()
273 |
274 | if !wantOk || !gotOk {
275 | if wantOk != gotOk {
276 | t.Errorf("want ok: %v, got ok: %v", wantOk, gotOk)
277 | return
278 | }
279 | break
280 | }
281 |
282 | if diff := cmp.Diff(want, got, cmpopts.SortSlices(func(a, b models.CiscoProduct) bool {
283 | return a.FormattedString < b.FormattedString
284 | })); diff != "" {
285 | t.Errorf("convert(). (-expected +got):\n%s", diff)
286 | }
287 | }
288 | })
289 | }
290 | }
291 |
--------------------------------------------------------------------------------
/fetcher/cisco/types.go:
--------------------------------------------------------------------------------
1 | package cisco
2 |
3 | type advisory struct {
4 | AdvisoryID string `json:"advisoryID"`
5 | AdvisoryTitle string `json:"advisoryTitle"`
6 | BugIDs []string `json:"bugIDs"`
7 | CsafURL string `json:"csafURL"`
8 | Cves []string `json:"cves"`
9 | CvrfURL string `json:"cvrfURL"`
10 | CvssBaseScore string `json:"cvssBaseScore"`
11 | Cwe []string `json:"cwe"`
12 | FirstPublished string `json:"firstPublished"`
13 | IpsSignatures interface{} `json:"ipsSignatures"`
14 | LastUpdated string `json:"lastUpdated"`
15 | ProductNames []string `json:"productNames"`
16 | PublicationURL string `json:"publicationURL"`
17 | Sir string `json:"sir"`
18 | Status string `json:"status"`
19 | Summary string `json:"summary"`
20 | Version string `json:"version"`
21 | }
22 |
--------------------------------------------------------------------------------
/fetcher/fetcher.go:
--------------------------------------------------------------------------------
1 | package fetcher
2 |
3 | import (
4 | "bytes"
5 | "compress/gzip"
6 | "io"
7 | "net/http"
8 | "net/url"
9 | "time"
10 |
11 | "github.com/cenkalti/backoff"
12 | "github.com/spf13/viper"
13 | "github.com/vulsio/go-cve-dictionary/log"
14 | "golang.org/x/xerrors"
15 | )
16 |
17 | // FetchFeedFile fetches vulnerability feed file concurrently
18 | func FetchFeedFile(urlstr string, gzip bool) (body []byte, err error) {
19 | log.Infof("Fetching... %s", urlstr)
20 |
21 | count, retryMax := 0, 20
22 | f := func() (err error) {
23 | if body, err = fetchFile(urlstr, gzip); err != nil {
24 | count++
25 | if count == retryMax {
26 | return nil
27 | }
28 | return xerrors.Errorf("HTTP GET error, url: %s, err: %w", urlstr, err)
29 | }
30 | return nil
31 | }
32 | notify := func(err error, t time.Duration) {
33 | log.Warnf("Failed to HTTP GET. retrying in %s seconds. err: %+v", t, err)
34 | }
35 | err = backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
36 | if err != nil {
37 | return nil, xerrors.Errorf("Failed to fetch file: %w", err)
38 | }
39 |
40 | if count == retryMax {
41 | return nil, xerrors.Errorf("Failed to fetch file. Retry count exceeded: %d", retryMax)
42 | }
43 |
44 | return body, nil
45 | }
46 |
47 | func fetchFile(urlstr string, isGzip bool) (body []byte, err error) {
48 | var proxyURL *url.URL
49 | httpClient := &http.Client{
50 | Timeout: time.Duration(180 * time.Second),
51 | }
52 | if viper.GetString("http-proxy") != "" {
53 | if proxyURL, err = url.Parse(viper.GetString("http-proxy")); err != nil {
54 | return nil, xerrors.Errorf("Failed to parse proxy url: %w", err)
55 | }
56 | httpClient = &http.Client{
57 | Transport: &http.Transport{
58 | Proxy: http.ProxyURL(proxyURL),
59 | },
60 | Timeout: time.Duration(180 * time.Second),
61 | }
62 | }
63 | req, err := http.NewRequest("GET", urlstr, nil)
64 | if err != nil {
65 | return nil, xerrors.Errorf("Failed to new request. url: %s, err: %w", urlstr, err)
66 | }
67 | req.Header.Set("User-Agent", "curl/7.58.0")
68 | resp, err := httpClient.Do(req)
69 | if err != nil {
70 | return nil, xerrors.Errorf("Failed to GET. url: %s, err: %w", urlstr, err)
71 | }
72 |
73 | defer resp.Body.Close()
74 | buf, err := io.ReadAll(resp.Body)
75 | if err != nil {
76 | return nil, xerrors.Errorf("Failed to read body. url: %s, err: %w", urlstr, err)
77 | }
78 |
79 | if isGzip {
80 | reader, err := gzip.NewReader(bytes.NewReader(buf))
81 | if err != nil {
82 | return nil, xerrors.Errorf("Failed to decompress NVD feedfile. url: %s, err: %w", urlstr, err)
83 | }
84 | defer reader.Close()
85 |
86 | bytes, err := io.ReadAll(reader)
87 | if err != nil {
88 | return nil, xerrors.Errorf("Failed to Read NVD feedfile. url: %s, err: %w", urlstr, err)
89 | }
90 | return bytes, nil
91 | }
92 |
93 | return buf, nil
94 | }
95 |
--------------------------------------------------------------------------------
/fetcher/fortinet/fortinet.go:
--------------------------------------------------------------------------------
1 | package fortinet
2 |
3 | import (
4 | "archive/zip"
5 | "bytes"
6 | "encoding/json"
7 | "fmt"
8 | "maps"
9 | "slices"
10 | "strings"
11 |
12 | "github.com/knqyf263/go-cpe/common"
13 | "github.com/knqyf263/go-cpe/naming"
14 | "golang.org/x/xerrors"
15 |
16 | "github.com/vulsio/go-cve-dictionary/fetcher"
17 | "github.com/vulsio/go-cve-dictionary/log"
18 | "github.com/vulsio/go-cve-dictionary/models"
19 | )
20 |
21 | // FetchConvert Fetch CVE vulnerability information from Fortinet
22 | func FetchConvert() ([]models.Fortinet, error) {
23 | bs, err := fetcher.FetchFeedFile("https://github.com/vulsio/vuls-data-raw-fortinet/archive/refs/heads/main.zip", false)
24 | if err != nil {
25 | return nil, xerrors.Errorf("Failed to fetch vulsio/vuls-data-raw-fortinet. err: %w", err)
26 | }
27 |
28 | r, err := zip.NewReader(bytes.NewReader(bs), int64(len(bs)))
29 | if err != nil {
30 | return nil, xerrors.Errorf("Failed to create zip reader. err: %w", err)
31 | }
32 |
33 | var advs []models.Fortinet
34 | for _, zf := range r.File {
35 | if zf.FileInfo().IsDir() {
36 | continue
37 | }
38 |
39 | as, err := func() ([]models.Fortinet, error) {
40 | f, err := zf.Open()
41 | if err != nil {
42 | return nil, xerrors.Errorf("Failed to open. err: %w", err)
43 | }
44 | defer f.Close()
45 |
46 | var a advisory
47 | if err := json.NewDecoder(f).Decode(&a); err != nil {
48 | return nil, xerrors.Errorf("Failed to decode json. err: %w", err)
49 | }
50 |
51 | converted, err := convert(a)
52 | if err != nil {
53 | return nil, xerrors.Errorf("Failed to convert advisory. err: %w", err)
54 | }
55 |
56 | return converted, nil
57 | }()
58 | if err != nil {
59 | return nil, xerrors.Errorf("Failed to convert %s. err: %w", zf.Name, err)
60 | }
61 |
62 | advs = append(advs, as...)
63 | }
64 |
65 | return advs, nil
66 | }
67 |
68 | func convert(a advisory) ([]models.Fortinet, error) {
69 | converted := []models.Fortinet{}
70 | for _, v := range a.Vulnerabilities {
71 | if v.CVE == "" {
72 | continue
73 | }
74 |
75 | cs := models.Fortinet{
76 | AdvisoryID: a.ID,
77 | CveID: v.CVE,
78 | Title: a.Title,
79 | Summary: a.Summary,
80 | Descriptions: a.Description,
81 | PublishedDate: a.Published,
82 | LastModifiedDate: a.Updated,
83 | AdvisoryURL: a.AdvisoryURL,
84 | }
85 |
86 | for _, d := range v.Definitions {
87 | m := map[models.FortinetCpe]struct{}{}
88 | for _, c := range d.Configurations {
89 | cpes, err := cpeWalk(c)
90 | if err != nil {
91 | return nil, xerrors.Errorf("Failed to cpe walk. err: %w", err)
92 | }
93 | for _, cpe := range cpes {
94 | m[cpe] = struct{}{}
95 | }
96 | }
97 | cs.Cpes = slices.Collect(maps.Keys(m))
98 |
99 | if d.CVSSv3 != nil {
100 | parsed := parseCvss3VectorStr(d.CVSSv3.Vector)
101 | cs.Cvss3 = models.FortinetCvss3{
102 | Cvss3: models.Cvss3{
103 | VectorString: d.CVSSv3.Vector,
104 | AttackVector: parsed.AttackVector,
105 | AttackComplexity: parsed.AttackComplexity,
106 | PrivilegesRequired: parsed.PrivilegesRequired,
107 | UserInteraction: parsed.UserInteraction,
108 | Scope: parsed.Scope,
109 | ConfidentialityImpact: parsed.ConfidentialityImpact,
110 | IntegrityImpact: parsed.IntegrityImpact,
111 | AvailabilityImpact: parsed.AvailabilityImpact,
112 | },
113 | }
114 | if d.CVSSv3.BaseScore != nil {
115 | cs.Cvss3.BaseScore = *d.CVSSv3.BaseScore
116 | }
117 | }
118 |
119 | for _, cwe := range d.CWE {
120 | cs.Cwes = append(cs.Cwes, models.FortinetCwe{
121 | CweID: cwe,
122 | })
123 | }
124 | }
125 |
126 | for _, r := range a.References {
127 | cs.References = append(cs.References, models.FortinetReference{
128 | Reference: models.Reference{
129 | Name: r.Description,
130 | Link: r.URL,
131 | },
132 | })
133 | }
134 |
135 | converted = append(converted, cs)
136 | }
137 | return converted, nil
138 | }
139 |
140 | func cpeWalk(conf configuration) ([]models.FortinetCpe, error) {
141 | cpes := []models.FortinetCpe{}
142 |
143 | for _, n := range conf.Nodes {
144 | if strings.HasSuffix(n.Description, ":") || ((n.Affected.Eqaul != nil && *n.Affected.Eqaul == "") && (n.Affected.GreaterThan != nil && *n.Affected.GreaterThan == "") && (n.Affected.GreaterEqaul != nil && *n.Affected.GreaterEqaul == "") && (n.Affected.LessThan != nil && *n.Affected.LessThan == "") && (n.Affected.LessEqual != nil && *n.Affected.LessEqual == "")) {
145 | continue
146 | }
147 |
148 | wfn, err := naming.UnbindFS(n.CPE)
149 | if err != nil {
150 | return nil, xerrors.Errorf("Failed to unbind a formatted string to WFN. err: %w", err)
151 | }
152 |
153 | if n.Affected.Eqaul != nil {
154 | // n.Affected.Eqaul: 4.2.0, 5.0.0:beta1, ANY, NA
155 | for i, v := range strings.Split(*n.Affected.Eqaul, ":") {
156 | switch i {
157 | case 0:
158 | switch v {
159 | case "ANY", "NA":
160 | lv, err := common.NewLogicalValue(v)
161 | if err != nil {
162 | return nil, xerrors.Errorf("Failed to new Logicalvalue. err: %w", err)
163 | }
164 | if err := wfn.Set(common.AttributeVersion, lv); err != nil {
165 | return nil, xerrors.Errorf("Failed to set version to WFN. err: %w", err)
166 | }
167 | default:
168 | if err := wfn.Set(common.AttributeVersion, strings.NewReplacer(".", "\\.", "-", "\\-").Replace(v)); err != nil {
169 | return nil, xerrors.Errorf("Failed to set version to WFN. err: %w", err)
170 | }
171 | }
172 | case 1:
173 | switch v {
174 | case "ANY", "NA":
175 | lv, err := common.NewLogicalValue(v)
176 | if err != nil {
177 | return nil, xerrors.Errorf("Failed to new Logicalvalue. err: %w", err)
178 | }
179 | if err := wfn.Set(common.AttributeUpdate, lv); err != nil {
180 | return nil, xerrors.Errorf("Failed to set update to WFN. err: %w", err)
181 | }
182 | default:
183 | if err := wfn.Set(common.AttributeUpdate, v); err != nil {
184 | return nil, xerrors.Errorf("Failed to set update to WFN. err: %w", err)
185 | }
186 | }
187 | }
188 | }
189 | }
190 |
191 | cpeBase := models.CpeBase{
192 | URI: naming.BindToURI(wfn),
193 | FormattedString: naming.BindToFS(wfn),
194 | WellFormedName: wfn.String(),
195 | CpeWFN: models.CpeWFN{
196 | Part: fmt.Sprintf("%s", wfn.Get(common.AttributePart)),
197 | Vendor: fmt.Sprintf("%s", wfn.Get(common.AttributeVendor)),
198 | Product: fmt.Sprintf("%s", wfn.Get(common.AttributeProduct)),
199 | Version: fmt.Sprintf("%s", wfn.Get(common.AttributeVersion)),
200 | Update: fmt.Sprintf("%s", wfn.Get(common.AttributeUpdate)),
201 | Edition: fmt.Sprintf("%s", wfn.Get(common.AttributeEdition)),
202 | Language: fmt.Sprintf("%s", wfn.Get(common.AttributeLanguage)),
203 | SoftwareEdition: fmt.Sprintf("%s", wfn.Get(common.AttributeSwEdition)),
204 | TargetSW: fmt.Sprintf("%s", wfn.Get(common.AttributeTargetSw)),
205 | TargetHW: fmt.Sprintf("%s", wfn.Get(common.AttributeTargetHw)),
206 | Other: fmt.Sprintf("%s", wfn.Get(common.AttributeOther)),
207 | },
208 | }
209 |
210 | if n.Affected.GreaterThan != nil {
211 | cpeBase.VersionStartExcluding = *n.Affected.GreaterThan
212 | }
213 | if n.Affected.GreaterEqaul != nil {
214 | cpeBase.VersionStartIncluding = *n.Affected.GreaterEqaul
215 | }
216 | if n.Affected.LessThan != nil {
217 | cpeBase.VersionEndExcluding = *n.Affected.LessThan
218 | }
219 | if n.Affected.LessEqual != nil {
220 | cpeBase.VersionEndIncluding = *n.Affected.LessEqual
221 | }
222 |
223 | cpes = append(cpes, models.FortinetCpe{
224 | CpeBase: cpeBase,
225 | })
226 | }
227 |
228 | if conf.Children != nil {
229 | cs, err := cpeWalk(*conf.Children)
230 | if err != nil {
231 | return nil, xerrors.Errorf("Failed to cpewalk. err: %w", err)
232 | }
233 | cpes = append(cpes, cs...)
234 | }
235 |
236 | return cpes, nil
237 | }
238 |
239 | func parseCvss3VectorStr(vector string) models.Cvss3 {
240 | cvss := models.Cvss3{VectorString: vector}
241 | for _, s := range strings.Split(strings.TrimPrefix(vector, "CVSS:3.1/"), "/") {
242 | m, v, ok := strings.Cut(s, ":")
243 | if !ok {
244 | continue
245 | }
246 | switch m {
247 | case "AV":
248 | switch v {
249 | case "N":
250 | cvss.AttackVector = "NETWORK"
251 | case "A":
252 | cvss.AttackVector = "ADJACENT_NETWORK"
253 | case "L":
254 | cvss.AttackVector = "LOCAL"
255 | case "P":
256 | cvss.AttackVector = "PHYSICAL"
257 | default:
258 | log.Warnf("%s is unknown Attack Vector", v)
259 | }
260 | case "AC":
261 | switch v {
262 | case "L":
263 | cvss.AttackComplexity = "LOW"
264 | case "H":
265 | cvss.AttackComplexity = "HIGH"
266 | default:
267 | log.Warnf("%s is unknown Attack Complexity", v)
268 | }
269 | case "PR":
270 | switch v {
271 | case "N":
272 | cvss.PrivilegesRequired = "NONE"
273 | case "L":
274 | cvss.PrivilegesRequired = "LOW"
275 | case "H":
276 | cvss.PrivilegesRequired = "HIGH"
277 | default:
278 | log.Warnf("%s is unknown Privileges Required", v)
279 | }
280 | case "UI":
281 | switch v {
282 | case "N":
283 | cvss.UserInteraction = "NONE"
284 | case "R":
285 | cvss.UserInteraction = "REQUIRED"
286 | default:
287 | log.Warnf("%s is unknown User Interaction", v)
288 | }
289 | case "S":
290 | switch v {
291 | case "U":
292 | cvss.Scope = "UNCHANGED"
293 | case "C":
294 | cvss.Scope = "CHANGED"
295 | default:
296 | log.Warnf("%s is unknown Scope", v)
297 | }
298 | case "C":
299 | switch v {
300 | case "N":
301 | cvss.ConfidentialityImpact = "NONE"
302 | case "L":
303 | cvss.ConfidentialityImpact = "LOW"
304 | case "H":
305 | cvss.ConfidentialityImpact = "HIGH"
306 | default:
307 | log.Warnf("%s is unknown Confidentiality", v)
308 | }
309 | case "I":
310 | switch v {
311 | case "N":
312 | cvss.IntegrityImpact = "NONE"
313 | case "L":
314 | cvss.IntegrityImpact = "LOW"
315 | case "H":
316 | cvss.IntegrityImpact = "HIGH"
317 | default:
318 | log.Warnf("%s is unknown Integrity", v)
319 | }
320 | case "A":
321 | switch v {
322 | case "N":
323 | cvss.AvailabilityImpact = "NONE"
324 | case "L":
325 | cvss.AvailabilityImpact = "LOW"
326 | case "H":
327 | cvss.AvailabilityImpact = "HIGH"
328 | default:
329 | log.Warnf("%s is unknown Availability", v)
330 | }
331 | case "E", "RL", "RC", "CR", "IR", "AR", "MAV", "MAC", "MPR", "MUI", "MS", "MC", "MI", "MA":
332 | default:
333 | log.Warnf("%s is unknown metrics", m)
334 | }
335 | }
336 |
337 | return cvss
338 | }
339 |
--------------------------------------------------------------------------------
/fetcher/fortinet/types.go:
--------------------------------------------------------------------------------
1 | package fortinet
2 |
3 | import "time"
4 |
5 | type advisory struct {
6 | ID string `json:"id"`
7 | Title string `json:"title"`
8 | Summary string `json:"summary"`
9 | Description string `json:"description"`
10 | Vulnerabilities []vulnerability `json:"vulnerabilities"`
11 | References []reference `json:"references"`
12 | Published time.Time `json:"published"`
13 | Updated time.Time `json:"updated"`
14 | AdvisoryURL string `json:"advisory_url"`
15 | }
16 |
17 | type vulnerability struct {
18 | ID string `json:"id"`
19 | CVE string `json:"cve"`
20 | Definitions []definition `json:"definitions"`
21 | }
22 |
23 | type definition struct {
24 | Configurations []configuration `json:"configurations"`
25 | CVSSv2 *cvss `json:"cvssv2"`
26 | CVSSv3 *cvss `json:"cvssv3"`
27 | CWE []string `json:"cwe"`
28 | Impact string `json:"impact"`
29 | ExploitStatus string `json:"exploit_status"`
30 | }
31 |
32 | type configuration struct {
33 | Nodes []element `json:"nodes"`
34 | Children *configuration `json:"children"`
35 | }
36 |
37 | type element struct {
38 | Description string `json:"description"`
39 | CPE string `json:"cpe"`
40 | Affected expression `json:"affected"`
41 | FixedIn []string `json:"fixed_in"`
42 | }
43 |
44 | type expression struct {
45 | Eqaul *string `json:"eq"`
46 | GreaterThan *string `json:"gt"`
47 | GreaterEqaul *string `json:"ge"`
48 | LessThan *string `json:"lt"`
49 | LessEqual *string `json:"le"`
50 | }
51 |
52 | type cvss struct {
53 | BaseScore *float64 `json:"base_score"`
54 | TemporalScore *float64 `json:"temporal_score"`
55 | EnvironmentalScore *float64 `json:"environmental_score"`
56 | Vector string `json:"vector"`
57 | }
58 |
59 | type reference struct {
60 | Description string `json:"description"`
61 | URL string `json:"url"`
62 | }
63 |
--------------------------------------------------------------------------------
/fetcher/jvn/jvn.go:
--------------------------------------------------------------------------------
1 | package jvn
2 |
3 | import (
4 | "encoding/xml"
5 | "fmt"
6 | "net/http"
7 | "net/url"
8 | "regexp"
9 | "runtime"
10 | "strings"
11 | "time"
12 |
13 | "github.com/PuerkitoBio/goquery"
14 | "github.com/spf13/viper"
15 | "github.com/vulsio/go-cve-dictionary/fetcher"
16 | "github.com/vulsio/go-cve-dictionary/log"
17 | "github.com/vulsio/go-cve-dictionary/models"
18 | "github.com/vulsio/go-cve-dictionary/util"
19 | "golang.org/x/xerrors"
20 | )
21 |
22 | // Meta ... https://jvndb.jvn.jp/ja/feed/checksum.txt
23 | type Meta struct {
24 | URL string `json:"url"`
25 | Hash string `json:"sha256"`
26 | LastModified string `json:"lastModified"`
27 | }
28 |
29 | type rdf struct {
30 | Items []Item `xml:"item"`
31 | }
32 |
33 | // Item ... http://jvndb.jvn.jp/apis/getVulnOverviewList_api.html
34 | type Item struct {
35 | About string `xml:"about,attr"`
36 | Title string `xml:"title"`
37 | Link string `xml:"link"`
38 | Description string `xml:"description"`
39 | Publisher string `xml:"publisher"`
40 | Identifier string `xml:"identifier"`
41 | References []references `xml:"references"`
42 | Cpes []cpe `xml:"cpe"`
43 | Cvsses []Cvss `xml:"cvss"`
44 | Date string `xml:"date"`
45 | Issued string `xml:"issued"`
46 | Modified string `xml:"modified"`
47 | }
48 |
49 | type cpe struct {
50 | Version string `xml:"version,attr"` // cpe:/a:mysql:mysql
51 | Vendor string `xml:"vendor,attr"`
52 | Product string `xml:"product,attr"`
53 | Value string `xml:",chardata"`
54 | }
55 |
56 | type references struct {
57 | ID string `xml:"id,attr"`
58 | Source string `xml:"source,attr"`
59 | Title string `xml:"title,attr"`
60 | URL string `xml:",chardata"`
61 | }
62 |
63 | // Cvss ... CVSS
64 | type Cvss struct {
65 | Score string `xml:"score,attr"`
66 | Severity string `xml:"severity,attr"`
67 | Vector string `xml:"vector,attr"`
68 | Version string `xml:"version,attr"`
69 | }
70 |
71 | // FetchConvert Fetch CVE vulnerability information from JVN
72 | func FetchConvert(uniqCve map[string]map[string]models.Jvn, years []string) error {
73 | for _, y := range years {
74 | items, err := fetch(y)
75 | if err != nil {
76 | return xerrors.Errorf("Failed to fetch. err: %w", err)
77 | }
78 |
79 | cves, err := convert(items)
80 | if err != nil {
81 | return xerrors.Errorf("Failed to convert. err: %w", err)
82 | }
83 | distributeCvesByYear(uniqCve, cves)
84 | }
85 | return nil
86 | }
87 |
88 | func fetch(year string) ([]Item, error) {
89 | u := func() string {
90 | switch year {
91 | case "modified":
92 | return "https://jvndb.jvn.jp/ja/rss/jvndb.rdf"
93 | case "recent":
94 | return "https://jvndb.jvn.jp/ja/rss/jvndb_new.rdf"
95 | default:
96 | return fmt.Sprintf("https://jvndb.jvn.jp/ja/rss/years/jvndb_%s.rdf", year)
97 | }
98 | }()
99 |
100 | body, err := fetcher.FetchFeedFile(u, false)
101 | if err != nil {
102 | return nil, xerrors.Errorf("Failed to FetchFeedFiles. err: %w", err)
103 | }
104 |
105 | var rdf rdf
106 | items := []Item{}
107 | if err := xml.Unmarshal([]byte(body), &rdf); err != nil {
108 | return nil, xerrors.Errorf("Failed to unmarshal. url: %s, err: %w", u, err)
109 | }
110 | for i, item := range rdf.Items {
111 | if !strings.Contains(item.Description, "** 未確定 **") && !strings.Contains(item.Description, "** サポート外 **") && !strings.Contains(item.Description, "** 削除 **") {
112 | items = append(items, rdf.Items[i])
113 | }
114 | }
115 |
116 | return items, nil
117 | }
118 |
119 | func convert(items []Item) (map[string]models.Jvn, error) {
120 | reqChan := make(chan Item, len(items))
121 | resChan := make(chan []models.Jvn, len(items))
122 | errChan := make(chan error)
123 | defer close(reqChan)
124 | defer close(resChan)
125 | defer close(errChan)
126 |
127 | go func() {
128 | for _, item := range items {
129 | reqChan <- item
130 | }
131 | }()
132 |
133 | concurrency := runtime.NumCPU() + 2
134 | tasks := util.GenWorkers(concurrency)
135 | for range items {
136 | tasks <- func() {
137 | req := <-reqChan
138 | cves, err := convertToModel(&req)
139 | if err != nil {
140 | errChan <- err
141 | return
142 | }
143 | resChan <- cves
144 | }
145 | }
146 |
147 | cves := map[string]models.Jvn{}
148 | errs := []error{}
149 | timeout := time.After(10 * 60 * time.Second)
150 | for range items {
151 | select {
152 | case res := <-resChan:
153 | for _, cve := range res {
154 | uniqJVNID := fmt.Sprintf("%s#%s", cve.JvnID, cve.CveID)
155 | cves[uniqJVNID] = cve
156 | }
157 | case err := <-errChan:
158 | errs = append(errs, err)
159 | case <-timeout:
160 | return nil, fmt.Errorf("Timeout Fetching")
161 | }
162 | }
163 | if 0 < len(errs) {
164 | return nil, xerrors.Errorf("%w", errs)
165 | }
166 | return cves, nil
167 | }
168 |
169 | // convertJvn converts Jvn structure(got from JVN) to model structure.
170 | func convertToModel(item *Item) (cves []models.Jvn, err error) {
171 | var cvss2, cvss3 Cvss
172 | for _, cvss := range item.Cvsses {
173 | if strings.HasPrefix(cvss.Version, "2") {
174 | cvss2 = cvss
175 | } else if strings.HasPrefix(cvss.Version, "3") {
176 | cvss3 = cvss
177 | } else {
178 | log.Warnf("Unknown CVSS version format: %s", cvss.Version)
179 | }
180 | }
181 |
182 | // References
183 | refs, links := []models.JvnReference{}, []string{}
184 | for _, r := range item.References {
185 | ref := models.JvnReference{
186 | Reference: models.Reference{
187 | Link: r.URL,
188 | Name: r.Title,
189 | Source: r.Source,
190 | },
191 | }
192 | refs = append(refs, ref)
193 |
194 | if ref.Source == "JPCERT-AT" {
195 | links = append(links, r.URL)
196 | }
197 | }
198 |
199 | certs, err := collectCertLinks(links)
200 | if err != nil {
201 | return nil,
202 | xerrors.Errorf("Failed to collect links. err: %w", err)
203 | }
204 |
205 | // Cpes
206 | cpes := []models.JvnCpe{}
207 | for _, c := range item.Cpes {
208 | cpeBase, err := fetcher.ParseCpeURI(c.Value)
209 | if err != nil {
210 | // logging only
211 | log.Warnf("Failed to parse CPE URI: %s, %s", c.Value, err)
212 | continue
213 | }
214 | cpes = append(cpes, models.JvnCpe{
215 | CpeBase: *cpeBase,
216 | })
217 | }
218 |
219 | publish, err := parseJvnTime(item.Issued)
220 | if err != nil {
221 | return nil, err
222 | }
223 | modified, err := parseJvnTime(item.Modified)
224 | if err != nil {
225 | return nil, err
226 | }
227 |
228 | cveIDs := getCveIDs(*item)
229 | if len(cveIDs) == 0 {
230 | log.Debugf("No CveIDs in references. JvnID: %s, Link: %s",
231 | item.Identifier, item.Link)
232 | // ignore this item
233 | return nil, nil
234 | }
235 |
236 | for _, cveID := range cveIDs {
237 | v2elems := parseCvss2VectorStr(cvss2.Vector)
238 | v3elems := parseCvss3VectorStr(cvss3.Vector)
239 | cve := models.Jvn{
240 | CveID: cveID,
241 | Title: strings.ReplaceAll(item.Title, "\r", ""),
242 | Summary: strings.ReplaceAll(item.Description, "\r", ""),
243 | JvnLink: item.Link,
244 | JvnID: item.Identifier,
245 |
246 | Cvss2: models.JvnCvss2{
247 | Cvss2: models.Cvss2{
248 | BaseScore: fetcher.StringToFloat(cvss2.Score),
249 | Severity: cvss2.Severity,
250 | VectorString: cvss2.Vector,
251 | AccessVector: v2elems[0],
252 | AccessComplexity: v2elems[1],
253 | Authentication: v2elems[2],
254 | ConfidentialityImpact: v2elems[3],
255 | IntegrityImpact: v2elems[4],
256 | AvailabilityImpact: v2elems[5],
257 | },
258 | },
259 |
260 | Cvss3: models.JvnCvss3{
261 | Cvss3: models.Cvss3{
262 | BaseScore: fetcher.StringToFloat(cvss3.Score),
263 | BaseSeverity: cvss3.Severity,
264 | VectorString: cvss3.Vector,
265 | AttackVector: v3elems[0],
266 | AttackComplexity: v3elems[1],
267 | PrivilegesRequired: v3elems[2],
268 | UserInteraction: v3elems[3],
269 | Scope: v3elems[4],
270 | ConfidentialityImpact: v3elems[5],
271 | IntegrityImpact: v3elems[6],
272 | AvailabilityImpact: v3elems[7],
273 | },
274 | },
275 |
276 | References: append([]models.JvnReference{}, refs...),
277 | Cpes: append([]models.JvnCpe{}, cpes...),
278 | Certs: append([]models.JvnCert{}, certs...),
279 |
280 | PublishedDate: publish,
281 | LastModifiedDate: modified,
282 | }
283 | cves = append(cves, cve)
284 | }
285 | return
286 | }
287 |
288 | func collectCertLinks(links []string) (certs []models.JvnCert, err error) {
289 | var proxyURL *url.URL
290 | httpClient := &http.Client{}
291 | if viper.GetString("http-proxy") != "" {
292 | if proxyURL, err = url.Parse(viper.GetString("http-proxy")); err != nil {
293 | return nil, xerrors.Errorf("failed to parse proxy url: %w", err)
294 | }
295 | httpClient = &http.Client{Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}}
296 | }
297 |
298 | certs = []models.JvnCert{}
299 | for _, link := range links {
300 | title := ""
301 | if !viper.GetBool("without-jvncert") {
302 | if strings.HasSuffix(link, ".html") {
303 | res, err := httpClient.Get(link)
304 | if err != nil {
305 | return nil, xerrors.Errorf("Failed to get %s: err: %w", link, err)
306 | }
307 | defer res.Body.Close()
308 |
309 | doc, err := goquery.NewDocumentFromReader(res.Body)
310 | if err != nil {
311 | return nil, xerrors.Errorf("Failed to NewDocumentFromReader. err: %w", err)
312 | }
313 | title = doc.Find("title").Text()
314 | }
315 | }
316 | certs = append(certs, models.JvnCert{
317 | Cert: models.Cert{
318 | Title: title,
319 | Link: link,
320 | },
321 | })
322 | }
323 |
324 | return certs, nil
325 | }
326 |
327 | var cvss2VectorMap = map[string]string{
328 | "AV:L": "LOCAL",
329 | "AV:A": "ADJACENT_NETWORK",
330 | "AV:N": "NETWORK",
331 |
332 | "AC:L": "LOW",
333 | "AC:M": "MEDIUM",
334 | "AC:H": "HIGH",
335 |
336 | "Au:M": "MULTIPLE",
337 | "Au:S": "SINGLE",
338 | "Au:N": "NONE",
339 |
340 | "C:N": "NONE",
341 | "C:P": "PARTIAL",
342 | "C:C": "COMPLETE",
343 |
344 | "I:N": "NONE",
345 | "I:P": "PARTIAL",
346 | "I:C": "COMPLETE",
347 |
348 | "A:N": "NONE",
349 | "A:P": "PARTIAL",
350 | "A:C": "COMPLETE",
351 | }
352 |
353 | func parseCvss2VectorStr(str string) (elems []string) {
354 | if len(str) == 0 {
355 | return []string{"", "", "", "", "", ""}
356 | }
357 | for _, s := range strings.Split(str, "/") {
358 | elems = append(elems, cvss2VectorMap[s])
359 | }
360 | return
361 | }
362 |
363 | var cvss3VectorMap = map[string]string{
364 | "AV:N": "NETWORK",
365 | "AV:A": "ADJACENT_NETWORK",
366 | "AV:L": "LOCAL",
367 | "AV:P": "PHYSICAL",
368 |
369 | "AC:L": "LOW",
370 | "AC:H": "HIGH",
371 |
372 | "PR:N": "NONE",
373 | "PR:L": "LOW",
374 | "PR:H": "HIGH",
375 |
376 | "UI:N": "NONE",
377 | "UI:R": "REQUIRED",
378 |
379 | "S:U": "UNCHANGED",
380 | "S:C": "CHANGED",
381 |
382 | "C:N": "NONE",
383 | "C:L": "LOW",
384 | "C:H": "HIGH",
385 |
386 | "I:N": "NONE",
387 | "I:L": "LOW",
388 | "I:H": "HIGH",
389 |
390 | "A:N": "NONE",
391 | "A:L": "LOW",
392 | "A:H": "HIGH",
393 | }
394 |
395 | func parseCvss3VectorStr(str string) (elems []string) {
396 | if len(str) == 0 {
397 | return []string{"", "", "", "", "", "", "", ""}
398 | }
399 | str = strings.TrimPrefix(str, "CVSS:3.0/")
400 | for _, s := range strings.Split(str, "/") {
401 | elems = append(elems, cvss3VectorMap[s])
402 | }
403 | return
404 | }
405 |
406 | // convert string time to time.Time
407 | // JVN : "2016-01-26T13:36:23+09:00",
408 | // NVD : "2016-01-20T21:59:01.313-05:00",
409 | func parseJvnTime(strtime string) (t time.Time, err error) {
410 | layout := "2006-01-02T15:04-07:00"
411 | t, err = time.Parse(layout, strtime)
412 | if err != nil {
413 | return t, xerrors.Errorf("Failed to parse time, time: %s, err: %w", strtime, err)
414 | }
415 | return
416 | }
417 |
418 | var cveIDPattern = regexp.MustCompile(`^CVE-[0-9]{4}-[0-9]{4,}$`)
419 |
420 | func getCveIDs(item Item) []string {
421 | cveIDsMap := map[string]bool{}
422 | for _, ref := range item.References {
423 | switch ref.Source {
424 | case "NVD", "CVE":
425 | if cveIDPattern.MatchString(ref.ID) {
426 | cveIDsMap[ref.ID] = true
427 | } else {
428 | id := strings.TrimSpace(ref.ID)
429 | if cveIDPattern.MatchString(id) {
430 | log.Warnf("CVE-ID with extra space. Please email JVNDB (isec-jvndb@ipa.go.jp) to fix the rdf file with the following information. RDF data(Identifier: %s, Reference Source: %s, ID: %s)", item.Identifier, ref.Source, ref.ID)
431 | cveIDsMap[id] = true
432 | } else {
433 | log.Warnf("Failed to get CVE-ID. Invalid CVE-ID. Please email JVNDB (isec-jvndb@ipa.go.jp) to fix the rdf file with the following information. RDF data(Identifier: %s, Reference Source: %s, ID: %s)", item.Identifier, ref.Source, ref.ID)
434 | }
435 | }
436 | }
437 | }
438 | cveIDs := []string{}
439 | for cveID := range cveIDsMap {
440 | cveIDs = append(cveIDs, cveID)
441 | }
442 | return cveIDs
443 | }
444 |
445 | func distributeCvesByYear(uniqCves map[string]map[string]models.Jvn, cves map[string]models.Jvn) {
446 | for uniqJVNID, cve := range cves {
447 | y := strings.Split(cve.JvnID, "-")[1]
448 | if _, ok := uniqCves[y]; !ok {
449 | uniqCves[y] = map[string]models.Jvn{}
450 | }
451 | if destCve, ok := uniqCves[y][uniqJVNID]; !ok {
452 | uniqCves[y][uniqJVNID] = cve
453 | } else {
454 | if cve.LastModifiedDate.After(destCve.LastModifiedDate) {
455 | uniqCves[y][uniqJVNID] = cve
456 | }
457 | }
458 | }
459 | }
460 |
--------------------------------------------------------------------------------
/fetcher/jvn/jvn_test.go:
--------------------------------------------------------------------------------
1 | package jvn
2 |
3 | import (
4 | "reflect"
5 | "testing"
6 | "time"
7 |
8 | "github.com/vulsio/go-cve-dictionary/models"
9 | )
10 |
11 | func TestDistributeCvesByYear(t *testing.T) {
12 | type args struct {
13 | uniqMap map[string]map[string]models.Jvn
14 | cves map[string]models.Jvn
15 | }
16 | var tests = []struct {
17 | in args
18 | expected map[string]map[string]models.Jvn
19 | }{
20 | {
21 | in: args{
22 | uniqMap: map[string]map[string]models.Jvn{},
23 | cves: map[string]models.Jvn{
24 | "JVNDB-2021-0001#CVE-2021-0001": {JvnID: "JVNDB-2021-0001", CveID: "CVE-2021-0001"},
25 | "JVNDB-2021-0001#CVE-2020-0001": {JvnID: "JVNDB-2021-0001", CveID: "CVE-2020-0001"},
26 | "JVNDB-2020-0001#CVE-2020-0001": {JvnID: "JVNDB-2020-0001", CveID: "CVE-2020-0001"},
27 | },
28 | },
29 | expected: map[string]map[string]models.Jvn{
30 | "2020": {
31 | "JVNDB-2020-0001#CVE-2020-0001": {JvnID: "JVNDB-2020-0001", CveID: "CVE-2020-0001"},
32 | },
33 | "2021": {
34 | "JVNDB-2021-0001#CVE-2021-0001": {JvnID: "JVNDB-2021-0001", CveID: "CVE-2021-0001"},
35 | "JVNDB-2021-0001#CVE-2020-0001": {JvnID: "JVNDB-2021-0001", CveID: "CVE-2020-0001"},
36 | },
37 | },
38 | },
39 | {
40 | in: args{
41 | uniqMap: map[string]map[string]models.Jvn{
42 | "2021": {
43 | "JVNDB-2021-0001#CVE-2021-0001": {JvnID: "JVNDB-2021-0001", CveID: "CVE-2021-0001", LastModifiedDate: time.Date(2021, time.September, 3, 0, 0, 0, 0, time.UTC)},
44 | },
45 | },
46 | cves: map[string]models.Jvn{
47 | "JVNDB-2021-0001#CVE-2021-0001": {JvnID: "JVNDB-2021-0001", CveID: "CVE-2021-0001", LastModifiedDate: time.Date(2021, time.September, 4, 0, 0, 0, 0, time.UTC)},
48 | },
49 | },
50 | expected: map[string]map[string]models.Jvn{
51 | "2021": {
52 | "JVNDB-2021-0001#CVE-2021-0001": {JvnID: "JVNDB-2021-0001", CveID: "CVE-2021-0001", LastModifiedDate: time.Date(2021, time.September, 4, 0, 0, 0, 0, time.UTC)},
53 | },
54 | },
55 | },
56 | }
57 |
58 | for i, tt := range tests {
59 | distributeCvesByYear(tt.in.uniqMap, tt.in.cves)
60 | if !reflect.DeepEqual(tt.in.uniqMap, tt.expected) {
61 | t.Errorf("[%d] expected: %v\n actual: %v\n", i, tt.expected, tt.in.uniqMap)
62 | }
63 | }
64 | }
65 |
66 | func TestGetCveIDs(t *testing.T) {
67 | var tests = []struct {
68 | in Item
69 | expected []string
70 | }{
71 | {
72 | in: Item{
73 | Identifier: "success",
74 | References: []references{{
75 | Source: "NVD",
76 | ID: "CVE-0000-0001",
77 | }},
78 | },
79 | expected: []string{"CVE-0000-0001"},
80 | },
81 | {
82 | in: Item{
83 | Identifier: "extra space",
84 | References: []references{{
85 | Source: "NVD",
86 | ID: "CVE-0000-0002 ",
87 | }},
88 | },
89 | expected: []string{"CVE-0000-0002"},
90 | },
91 | {
92 | in: Item{
93 | Identifier: "invalid CVE-ID",
94 | References: []references{{
95 | Source: "NVD",
96 | ID: "CCVE-0000-0003",
97 | }},
98 | },
99 | expected: []string{},
100 | },
101 | }
102 |
103 | for i, tt := range tests {
104 | if got := getCveIDs(tt.in); !reflect.DeepEqual(got, tt.expected) {
105 | t.Errorf("[%d] expected: %v\n actual: %v\n", i, tt.expected, got)
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/fetcher/nvd/nvd.go:
--------------------------------------------------------------------------------
1 | package nvd
2 |
3 | import (
4 | "archive/tar"
5 | "context"
6 | "encoding/json"
7 | "fmt"
8 | "io"
9 | "io/fs"
10 | "os"
11 | "os/exec"
12 | "path/filepath"
13 | "runtime"
14 | "slices"
15 | "strings"
16 | "time"
17 |
18 | "github.com/hashicorp/go-version"
19 | "github.com/klauspost/compress/zstd"
20 | ocispec "github.com/opencontainers/image-spec/specs-go/v1"
21 | "github.com/spf13/viper"
22 | "golang.org/x/xerrors"
23 | "oras.land/oras-go/v2"
24 | "oras.land/oras-go/v2/registry/remote"
25 |
26 | "github.com/vulsio/go-cve-dictionary/fetcher"
27 | "github.com/vulsio/go-cve-dictionary/log"
28 | "github.com/vulsio/go-cve-dictionary/models"
29 | "github.com/vulsio/go-cve-dictionary/util"
30 | )
31 |
32 | // Fetch Fetch CVE vulnerability information from NVD
33 | func Fetch() (string, error) {
34 | dir, err := os.MkdirTemp("", "go-cve-dictionary")
35 | if err != nil {
36 | return "", xerrors.Errorf("Failed to create temp directory. err: %w", err)
37 | }
38 |
39 | if err := fetch(dir); err != nil {
40 | return "", xerrors.Errorf("Failed to fetch vuls-data-raw-nvd-api-cve. err: %w", err)
41 | }
42 |
43 | return dir, nil
44 | }
45 |
46 | func fetch(dir string) error {
47 | ctx := context.TODO()
48 | repo, err := remote.NewRepository("ghcr.io/vulsio/vuls-data-db:vuls-data-raw-nvd-api-cve")
49 | if err != nil {
50 | return xerrors.Errorf("Failed to create client for ghcr.io/vulsio/vuls-data-db:vuls-data-raw-nvd-api-cve. err: %w", err)
51 | }
52 |
53 | _, r, err := oras.Fetch(ctx, repo, repo.Reference.Reference, oras.DefaultFetchOptions)
54 | if err != nil {
55 | return xerrors.Errorf("Failed to fetch manifest. err: %w", err)
56 | }
57 | defer r.Close()
58 |
59 | var manifest ocispec.Manifest
60 | if err := json.NewDecoder(r).Decode(&manifest); err != nil {
61 | return xerrors.Errorf("Failed to decode manifest. err: %w", err)
62 | }
63 |
64 | l := func() *ocispec.Descriptor {
65 | for _, l := range manifest.Layers {
66 | if l.MediaType == "application/vnd.vulsio.vuls-data-db.dotgit.layer.v1.tar+zstd" {
67 | return &l
68 | }
69 | }
70 | return nil
71 | }()
72 | if l == nil {
73 | return xerrors.Errorf("Failed to find digest and filename from layers, actual layers: %#v", manifest.Layers)
74 | }
75 |
76 | r, err = repo.Fetch(ctx, *l)
77 | if err != nil {
78 | return xerrors.Errorf("Failed to fetch content. err: %w", err)
79 | }
80 | defer r.Close()
81 |
82 | zr, err := zstd.NewReader(r)
83 | if err != nil {
84 | return xerrors.Errorf("Failed to new zstd reader. err: %w", err)
85 | }
86 | defer zr.Close()
87 |
88 | tr := tar.NewReader(zr)
89 | for {
90 | hdr, err := tr.Next()
91 | if err == io.EOF {
92 | break
93 | }
94 | if err != nil {
95 | return xerrors.Errorf("Failed to next tar reader. err: %w", err)
96 | }
97 |
98 | p := filepath.Join(dir, hdr.Name)
99 |
100 | switch hdr.Typeflag {
101 | case tar.TypeDir:
102 | if err := os.MkdirAll(p, 0755); err != nil {
103 | return xerrors.Errorf("Failed to mkdir %s. err: %w", p, err)
104 | }
105 | case tar.TypeReg:
106 | if err := os.MkdirAll(filepath.Dir(p), 0755); err != nil {
107 | return xerrors.Errorf("Failed to mkdir %s. err: %w", p, err)
108 | }
109 |
110 | if err := func() error {
111 | f, err := os.Create(p)
112 | if err != nil {
113 | return xerrors.Errorf("Failed to create %s. err: %w", p, err)
114 | }
115 | defer f.Close()
116 |
117 | if _, err := io.Copy(f, tr); err != nil {
118 | return xerrors.Errorf("Failed to copy to %s. err: %w", p, err)
119 | }
120 |
121 | return nil
122 | }(); err != nil {
123 | return xerrors.Errorf("Failed to create %s. err: %w", p, err)
124 | }
125 | }
126 | }
127 |
128 | cmd := exec.Command("git", "-C", filepath.Join(dir, "vuls-data-raw-nvd-api-cve"), "restore", ".")
129 | if err := cmd.Run(); err != nil {
130 | return xerrors.Errorf("Failed to exec %q. err: %w", cmd.String(), err)
131 | }
132 |
133 | return nil
134 | }
135 |
136 | // Convert convert /vuls-data-raw-nvd-api-cve//CVE--\d{4,}.json to []models.Nvd
137 | func Convert(tempDir, year string) ([]models.Nvd, error) {
138 | var ps []string
139 | if err := filepath.WalkDir(filepath.Join(tempDir, "vuls-data-raw-nvd-api-cve", year), func(path string, d fs.DirEntry, err error) error {
140 | if err != nil {
141 | return err
142 | }
143 | if d.IsDir() {
144 | return nil
145 | }
146 | ps = append(ps, path)
147 | return nil
148 | }); err != nil {
149 | return nil, xerrors.Errorf("Failed to walk %s. err: %w", filepath.Join(tempDir, year), err)
150 | }
151 |
152 | reqChan := make(chan string, len(ps))
153 | resChan := make(chan *models.Nvd, len(ps))
154 | errChan := make(chan error, len(ps))
155 | defer close(reqChan)
156 | defer close(resChan)
157 | defer close(errChan)
158 |
159 | go func() {
160 | for _, p := range ps {
161 | reqChan <- p
162 | }
163 | }()
164 |
165 | concurrency := runtime.NumCPU() + 2
166 | tasks := util.GenWorkers(concurrency)
167 | for range ps {
168 | tasks <- func() {
169 | req := <-reqChan
170 | cve, err := convertToModel(req)
171 | if err != nil {
172 | errChan <- err
173 | return
174 | }
175 | resChan <- cve
176 | }
177 | }
178 |
179 | cves := []models.Nvd{}
180 | errs := []error{}
181 | timeout := time.After(10 * 60 * time.Second)
182 | for range ps {
183 | select {
184 | case res := <-resChan:
185 | if res != nil {
186 | cves = append(cves, *res)
187 | }
188 | case err := <-errChan:
189 | errs = append(errs, err)
190 | case <-timeout:
191 | return nil, fmt.Errorf("Timeout Fetching")
192 | }
193 | }
194 | if 0 < len(errs) {
195 | return nil, xerrors.Errorf("%w", errs)
196 | }
197 | return cves, nil
198 | }
199 |
200 | // convertToModel converts Nvd JSON to model structure.
201 | func convertToModel(cvePath string) (*models.Nvd, error) {
202 | f, err := os.Open(cvePath)
203 | if err != nil {
204 | return nil, xerrors.Errorf("Failed to open %s. err: %w", cvePath, err)
205 | }
206 | defer f.Close()
207 |
208 | var item cve
209 | if err := json.NewDecoder(f).Decode(&item); err != nil {
210 | return nil, xerrors.Errorf("Failed to decode %s. err: %w", cvePath, err)
211 | }
212 |
213 | if item.VulnStatus == "Rejected" {
214 | return nil, nil
215 | }
216 |
217 | // Description
218 | descs := []models.NvdDescription{}
219 | for _, desc := range item.Descriptions {
220 | descs = append(descs, models.NvdDescription{
221 | Lang: desc.Lang,
222 | Value: desc.Value,
223 | })
224 | }
225 |
226 | //References
227 | refs := []models.NvdReference{}
228 | for _, r := range item.References {
229 | refs = append(refs, models.NvdReference{
230 | Reference: models.Reference{
231 | Link: r.URL,
232 | Name: r.URL,
233 | Source: r.Source,
234 | Tags: strings.Join(r.Tags, ","),
235 | },
236 | })
237 | }
238 |
239 | // Certs
240 | certs := []models.NvdCert{}
241 | for _, ref := range item.References {
242 | if !strings.HasPrefix(ref.URL, "http") {
243 | continue
244 | }
245 | if strings.Contains(ref.URL, "us-cert") {
246 | ss := strings.Split(ref.URL, "/")
247 | title := fmt.Sprintf("US-CERT-%s", ss[len(ss)-1])
248 | certs = append(certs, models.NvdCert{
249 | Cert: models.Cert{
250 | Link: ref.URL,
251 | Title: title,
252 | },
253 | })
254 | }
255 | }
256 |
257 | // Cwes
258 | cwes := []models.NvdCwe{}
259 | for _, weak := range item.Weaknesses {
260 | for _, desc := range weak.Description {
261 | if !slices.ContainsFunc(cwes, func(e models.NvdCwe) bool {
262 | return e.Source == weak.Source && e.Type == weak.Type && e.CweID == desc.Value
263 | }) {
264 | cwes = append(cwes, models.NvdCwe{
265 | Source: weak.Source,
266 | Type: weak.Type,
267 | CweID: desc.Value,
268 | })
269 | }
270 | }
271 | }
272 |
273 | full := viper.GetBool("full")
274 | cpes := []models.NvdCpe{}
275 | for _, conf := range item.Configurations {
276 | if conf.Negate {
277 | continue
278 | }
279 |
280 | var (
281 | nodeCpes []models.NvdCpe
282 | nodeEnvCpes []models.NvdEnvCpe
283 | )
284 | for _, node := range conf.Nodes {
285 | if node.Negate {
286 | continue
287 | }
288 |
289 | for _, cpe := range node.CPEMatch {
290 | if cpe.Vulnerable {
291 | cpeBase, err := fetcher.ParseCpeURI(cpe.Criteria)
292 | if err != nil {
293 | log.Infof("Failed to parse CpeURI %s: %s", cpe.Criteria, err)
294 | continue
295 | }
296 | cpeBase.VersionStartExcluding = cpe.VersionStartExcluding
297 | cpeBase.VersionStartIncluding = cpe.VersionStartIncluding
298 | cpeBase.VersionEndExcluding = cpe.VersionEndExcluding
299 | cpeBase.VersionEndIncluding = cpe.VersionEndIncluding
300 | nodeCpes = append(nodeCpes, models.NvdCpe{
301 | CpeBase: *cpeBase,
302 | EnvCpes: []models.NvdEnvCpe{},
303 | })
304 | if !checkIfVersionParsable(cpeBase) {
305 | return nil, fmt.Errorf("Version parse err. Please add a issue on [GitHub](https://github.com/vulsio/go-cve-dictionary/issues/new). Title: %s, Content: %v", item.ID, item)
306 | }
307 | } else {
308 | if !full || conf.Operator != "AND" {
309 | continue
310 | }
311 | cpeBase, err := fetcher.ParseCpeURI(cpe.Criteria)
312 | if err != nil {
313 | log.Infof("Failed to parse CpeURI %s: %s", cpe.Criteria, err)
314 | continue
315 | }
316 | cpeBase.VersionStartExcluding = cpe.VersionStartExcluding
317 | cpeBase.VersionStartIncluding = cpe.VersionStartIncluding
318 | cpeBase.VersionEndExcluding = cpe.VersionEndExcluding
319 | cpeBase.VersionEndIncluding = cpe.VersionEndIncluding
320 | nodeEnvCpes = append(nodeEnvCpes, models.NvdEnvCpe{
321 | CpeBase: *cpeBase,
322 | })
323 | if !checkIfVersionParsable(cpeBase) {
324 | return nil, fmt.Errorf("Version parse err. Please add a issue on [GitHub](https://github.com/vulsio/go-cve-dictionary/issues/new). Title: %s, Content: %v", item.ID, item)
325 | }
326 | }
327 | }
328 | }
329 | for _, nodeCpe := range nodeCpes {
330 | nodeCpe.EnvCpes = append(nodeCpe.EnvCpes, nodeEnvCpes...)
331 | cpes = append(cpes, nodeCpe)
332 | }
333 | }
334 |
335 | c2 := make([]models.NvdCvss2Extra, 0, len(item.Metrics.CVSSMetricV2))
336 | for _, v2 := range item.Metrics.CVSSMetricV2 {
337 | c2 = append(c2, models.NvdCvss2Extra{
338 | Source: v2.Source,
339 | Type: v2.Type,
340 | Cvss2: models.Cvss2{
341 | VectorString: v2.CvssData.VectorString,
342 | AccessVector: v2.CvssData.AccessVector,
343 | AccessComplexity: v2.CvssData.AccessComplexity,
344 | Authentication: v2.CvssData.Authentication,
345 | ConfidentialityImpact: v2.CvssData.ConfidentialityImpact,
346 | IntegrityImpact: v2.CvssData.IntegrityImpact,
347 | AvailabilityImpact: v2.CvssData.AvailabilityImpact,
348 | BaseScore: v2.CvssData.BaseScore,
349 | Severity: v2.BaseSeverity,
350 | },
351 | ExploitabilityScore: v2.ExploitabilityScore,
352 | ImpactScore: v2.ImpactScore,
353 | ObtainAllPrivilege: v2.ObtainAllPrivilege,
354 | ObtainUserPrivilege: v2.ObtainUserPrivilege,
355 | ObtainOtherPrivilege: v2.ObtainOtherPrivilege,
356 | UserInteractionRequired: v2.UserInteractionRequired,
357 | })
358 | }
359 |
360 | c3 := make([]models.NvdCvss3, 0, len(item.Metrics.CVSSMetricV30)+len(item.Metrics.CVSSMetricV31))
361 | for _, v30 := range item.Metrics.CVSSMetricV30 {
362 | c3 = append(c3, models.NvdCvss3{
363 | Source: v30.Source,
364 | Type: v30.Type,
365 | Cvss3: models.Cvss3{
366 | VectorString: v30.CVSSData.VectorString,
367 | AttackVector: v30.CVSSData.AttackVector,
368 | AttackComplexity: v30.CVSSData.AttackComplexity,
369 | PrivilegesRequired: v30.CVSSData.PrivilegesRequired,
370 | UserInteraction: v30.CVSSData.UserInteraction,
371 | Scope: v30.CVSSData.Scope,
372 | ConfidentialityImpact: v30.CVSSData.ConfidentialityImpact,
373 | IntegrityImpact: v30.CVSSData.IntegrityImpact,
374 | AvailabilityImpact: v30.CVSSData.AvailabilityImpact,
375 | BaseScore: v30.CVSSData.BaseScore,
376 | BaseSeverity: v30.CVSSData.BaseSeverity,
377 | ExploitabilityScore: v30.ExploitabilityScore,
378 | ImpactScore: v30.ImpactScore,
379 | },
380 | })
381 | }
382 | for _, v31 := range item.Metrics.CVSSMetricV31 {
383 | c3 = append(c3, models.NvdCvss3{
384 | Source: v31.Source,
385 | Type: v31.Type,
386 | Cvss3: models.Cvss3{
387 | VectorString: v31.CVSSData.VectorString,
388 | AttackVector: v31.CVSSData.AttackVector,
389 | AttackComplexity: v31.CVSSData.AttackComplexity,
390 | PrivilegesRequired: v31.CVSSData.PrivilegesRequired,
391 | UserInteraction: v31.CVSSData.UserInteraction,
392 | Scope: v31.CVSSData.Scope,
393 | ConfidentialityImpact: v31.CVSSData.ConfidentialityImpact,
394 | IntegrityImpact: v31.CVSSData.IntegrityImpact,
395 | AvailabilityImpact: v31.CVSSData.AvailabilityImpact,
396 | BaseScore: v31.CVSSData.BaseScore,
397 | BaseSeverity: v31.CVSSData.BaseSeverity,
398 | ExploitabilityScore: func() float64 {
399 | if v31.ExploitabilityScore != nil {
400 | return *v31.ExploitabilityScore
401 | }
402 | return 0
403 | }(),
404 | ImpactScore: func() float64 {
405 | if v31.ImpactScore != nil {
406 | return *v31.ImpactScore
407 | }
408 | return 0
409 | }(),
410 | },
411 | })
412 | }
413 | c40 := make([]models.NvdCvss40, 0, len(item.Metrics.CVSSMetricV40))
414 | for _, v40 := range item.Metrics.CVSSMetricV40 {
415 | c40 = append(c40, models.NvdCvss40{
416 | Source: v40.Source,
417 | Type: v40.Type,
418 | Cvss40: models.Cvss40{
419 | VectorString: v40.CVSSData.VectorString,
420 | BaseScore: v40.CVSSData.BaseScore,
421 | BaseSeverity: v40.CVSSData.BaseSeverity,
422 | ThreatScore: v40.CVSSData.ThreatScore,
423 | ThreatSeverity: v40.CVSSData.ThreatSeverity,
424 | EnvironmentalScore: v40.CVSSData.EnvironmentalScore,
425 | EnvironmentalSeverity: v40.CVSSData.EnvironmentalSeverity,
426 | },
427 | })
428 | }
429 |
430 | publish, err := parseNvdTime(item.Published)
431 | if err != nil {
432 | return nil, xerrors.Errorf("Failed to parse NVD Time. err: %w", err)
433 | }
434 | modified, err := parseNvdTime(item.LastModified)
435 | if err != nil {
436 | return nil, xerrors.Errorf("Failed to parse NVD Time. err: %w", err)
437 | }
438 |
439 | return &models.Nvd{
440 | CveID: item.ID,
441 | Descriptions: descs,
442 | Cvss2: c2,
443 | Cvss3: c3,
444 | Cvss40: c40,
445 | Cwes: cwes,
446 | Cpes: cpes,
447 | References: refs,
448 | Certs: certs,
449 | PublishedDate: publish,
450 | LastModifiedDate: modified,
451 | }, nil
452 | }
453 |
454 | func checkIfVersionParsable(cpeBase *models.CpeBase) bool {
455 | if cpeBase.Version != "ANY" && cpeBase.Version != "NA" {
456 | vers := []string{cpeBase.VersionStartExcluding,
457 | cpeBase.VersionStartIncluding,
458 | cpeBase.VersionEndIncluding,
459 | cpeBase.VersionEndExcluding}
460 | for _, v := range vers {
461 | if v == "" {
462 | continue
463 | }
464 | v := strings.ReplaceAll(v, `\`, "")
465 | if _, err := version.NewVersion(v); err != nil {
466 | return false
467 | }
468 | }
469 | }
470 | return true
471 | }
472 |
473 | func parseNvdTime(strtime string) (t time.Time, err error) {
474 | layout := "2006-01-02T15:04:05.000"
475 | t, err = time.Parse(layout, strtime)
476 | if err != nil {
477 | return t, xerrors.Errorf("Failed to parse time, time: %s, err: %w", strtime, err)
478 | }
479 | return
480 | }
481 |
--------------------------------------------------------------------------------
/fetcher/nvd/types.go:
--------------------------------------------------------------------------------
1 | package nvd
2 |
3 | // https://github.com/MaineK00n/vuls-data-update/blob/38e5f8203f3ba90ce565e4a8eb650c17412ea88d/pkg/fetch/nvd/api/cve/types.go#L18
4 | type cve struct {
5 | ID string `json:"id"`
6 | SourceIdentifier string `json:"sourceIdentifier,omitempty"`
7 | VulnStatus string `json:"vulnStatus,omitempty"`
8 | Published string `json:"published"`
9 | LastModified string `json:"lastModified"`
10 | EvaluatorComment string `json:"evaluatorComment,omitempty"`
11 | EvaluatorSolution string `json:"evaluatorSolution,omitempty"`
12 | EvaluatorImpact string `json:"evaluatorImpact,omitempty"`
13 | CISAExploitAdd string `json:"cisaExploitAdd,omitempty"`
14 | CISAActionDue string `json:"cisaActionDue,omitempty"`
15 | CISARequiredAction string `json:"cisaRequiredAction,omitempty"`
16 | CISAVulnerabilityName string `json:"cisaVulnerabilityName,omitempty"`
17 | Descriptions []struct {
18 | Lang string `json:"lang"`
19 | Value string `json:"value"`
20 | } `json:"descriptions"`
21 | References []struct {
22 | Source string `json:"source,omitempty"`
23 | Tags []string `json:"tags,omitempty"`
24 | URL string `json:"url"`
25 | } `json:"references"`
26 | Metrics struct {
27 | CVSSMetricV2 []struct {
28 | Source string `json:"source"`
29 | Type string `json:"type"`
30 | CvssData struct {
31 | Version string `json:"version"`
32 | VectorString string `json:"vectorString"`
33 | AccessVector string `json:"accessVector,omitempty"`
34 | AccessComplexity string `json:"accessComplexity,omitempty"`
35 | Authentication string `json:"authentication,omitempty"`
36 | ConfidentialityImpact string `json:"confidentialityImpact,omitempty"`
37 | IntegrityImpact string `json:"integrityImpact,omitempty"`
38 | AvailabilityImpact string `json:"availabilityImpact,omitempty"`
39 | BaseScore float64 `json:"baseScore"`
40 | Exploitability string `json:"exploitability,omitempty"`
41 | RemediationLevel string `json:"remediationLevel,omitempty"`
42 | ReportConfidence string `json:"reportConfidence,omitempty"`
43 | TemporalScore float64 `json:"temporalScore,omitempty"`
44 | CollateralDamagePotential string `json:"collateralDamagePotential,omitempty"`
45 | TargetDistribution string `json:"targetDistribution,omitempty"`
46 | ConfidentialityRequirement string `json:"confidentialityRequirement,omitempty"`
47 | IntegrityRequirement string `json:"integrityRequirement,omitempty"`
48 | AvailabilityRequirement string `json:"availabilityRequirement,omitempty"`
49 | EnvironmentalScore float64 `json:"environmentalScore,omitempty"`
50 | } `json:"cvssData"`
51 | BaseSeverity string `json:"baseSeverity,omitempty"`
52 | ExploitabilityScore float64 `json:"exploitabilityScore,omitempty"`
53 | ImpactScore float64 `json:"impactScore,omitempty"`
54 | ACInsufInfo bool `json:"acInsufInfo,omitempty"`
55 | ObtainAllPrivilege bool `json:"obtainAllPrivilege,omitempty"`
56 | ObtainUserPrivilege bool `json:"obtainUserPrivilege,omitempty"`
57 | ObtainOtherPrivilege bool `json:"obtainOtherPrivilege,omitempty"`
58 | UserInteractionRequired bool `json:"userInteractionRequired,omitempty"`
59 | } `json:"cvssMetricV2,omitempty"`
60 | CVSSMetricV30 []struct {
61 | Source string `json:"source"`
62 | Type string `json:"type"`
63 | CVSSData struct {
64 | Version string `json:"version"`
65 | VectorString string `json:"vectorString"`
66 | AttackVector string `json:"attackVector,omitempty"`
67 | AttackComplexity string `json:"attackComplexity,omitempty"`
68 | PrivilegesRequired string `json:"privilegesRequired,omitempty"`
69 | UserInteraction string `json:"userInteraction,omitempty"`
70 | Scope string `json:"scope,omitempty"`
71 | ConfidentialityImpact string `json:"confidentialityImpact,omitempty"`
72 | IntegrityImpact string `json:"integrityImpact,omitempty"`
73 | AvailabilityImpact string `json:"availabilityImpact,omitempty"`
74 | BaseScore float64 `json:"baseScore"`
75 | BaseSeverity string `json:"baseSeverity"`
76 | ExploitCodeMaturity string `json:"exploitCodeMaturity,omitempty"`
77 | RemediationLevel string `json:"remediationLevel,omitempty"`
78 | ReportConfidence string `json:"reportConfidence,omitempty"`
79 | TemporalScore *float64 `json:"temporalScore,omitempty"`
80 | TemporalSeverity string `json:"temporalSeverity,omitempty"`
81 | ConfidentialityRequirement string `json:"confidentialityRequirement,omitempty"`
82 | IntegrityRequirement string `json:"integrityRequirement,omitempty"`
83 | AvailabilityRequirement string `json:"availabilityRequirement,omitempty"`
84 | ModifiedAttackVector string `json:"modifiedAttackVector,omitempty"`
85 | ModifiedAttackComplexity string `json:"modifiedAttackComplexity,omitempty"`
86 | ModifiedPrivilegesRequired string `json:"modifiedPrivilegesRequired,omitempty"`
87 | ModifiedUserInteraction string `json:"modifiedUserInteraction,omitempty"`
88 | ModifiedScope string `json:"modifiedScope,omitempty"`
89 | ModifiedConfidentialityImpact string `json:"modifiedConfidentialityImpact,omitempty"`
90 | ModifiedIntegrityImpact string `json:"modifiedIntegrityImpact,omitempty"`
91 | ModifiedAvailabilityImpact string `json:"modifiedAvailabilityImpact,omitempty"`
92 | EnvironmentalScore *float64 `json:"environmentalScore,omitempty"`
93 | EnvironmentalSeverity string `json:"environmentalSeverity,omitempty"`
94 | } `json:"cvssData"`
95 | ExploitabilityScore float64 `json:"exploitabilityScore,omitempty"`
96 | ImpactScore float64 `json:"impactScore,omitempty"`
97 | } `json:"cvssMetricV30,omitempty"`
98 | CVSSMetricV31 []struct {
99 | Source string `json:"source"`
100 | Type string `json:"type"`
101 | CVSSData struct {
102 | Version string `json:"version"`
103 | VectorString string `json:"vectorString"`
104 | AttackVector string `json:"attackVector,omitempty"`
105 | AttackComplexity string `json:"attackComplexity,omitempty"`
106 | PrivilegesRequired string `json:"privilegesRequired,omitempty"`
107 | UserInteraction string `json:"userInteraction,omitempty"`
108 | Scope string `json:"scope,omitempty"`
109 | ConfidentialityImpact string `json:"confidentialityImpact,omitempty"`
110 | IntegrityImpact string `json:"integrityImpact,omitempty"`
111 | AvailabilityImpact string `json:"availabilityImpact,omitempty"`
112 | BaseScore float64 `json:"baseScore"`
113 | BaseSeverity string `json:"baseSeverity"`
114 | ExploitCodeMaturity string `json:"exploitCodeMaturity,omitempty"`
115 | RemediationLevel string `json:"remediationLevel,omitempty"`
116 | ReportConfidence string `json:"reportConfidence,omitempty"`
117 | TemporalScore float64 `json:"temporalScore,omitempty"`
118 | TemporalSeverity string `json:"temporalSeverity,omitempty"`
119 | ConfidentialityRequirement string `json:"confidentialityRequirement,omitempty"`
120 | IntegrityRequirement string `json:"integrityRequirement,omitempty"`
121 | AvailabilityRequirement string `json:"availabilityRequirement,omitempty"`
122 | ModifiedAttackVector string `json:"modifiedAttackVector,omitempty"`
123 | ModifiedAttackComplexity string `json:"modifiedAttackComplexity,omitempty"`
124 | ModifiedPrivilegesRequired string `json:"modifiedPrivilegesRequired,omitempty"`
125 | ModifiedUserInteraction string `json:"modifiedUserInteraction,omitempty"`
126 | ModifiedScope string `json:"modifiedScope,omitempty"`
127 | ModifiedConfidentialityImpact string `json:"modifiedConfidentialityImpact,omitempty"`
128 | ModifiedIntegrityImpact string `json:"modifiedIntegrityImpact,omitempty"`
129 | ModifiedAvailabilityImpact string `json:"modifiedAvailabilityImpact,omitempty"`
130 | EnvironmentalScore float64 `json:"environmentalScore,omitempty"`
131 | EnvironmentalSeverity string `json:"environmentalSeverity,omitempty"`
132 | } `json:"cvssData"`
133 | ExploitabilityScore *float64 `json:"exploitabilityScore,omitempty"`
134 | ImpactScore *float64 `json:"impactScore,omitempty"`
135 | } `json:"cvssMetricV31,omitempty"`
136 | CVSSMetricV40 []struct {
137 | Source string `json:"source"`
138 | Type string `json:"type"`
139 | CVSSData struct {
140 | Version string `json:"version"`
141 | VectorString string `json:"vectorString"`
142 | BaseScore float64 `json:"baseScore"`
143 | BaseSeverity string `json:"baseSeverity"`
144 | AttackVector *string `json:"attackVector,omitempty"`
145 | AttackComplexity *string `json:"attackComplexity,omitempty"`
146 | AttackRequirements *string `json:"attackRequirements,omitempty"`
147 | PrivilegesRequired *string `json:"privilegesRequired,omitempty"`
148 | UserInteraction *string `json:"userInteraction,omitempty"`
149 | VulnerableSystemConfidentiality *string `json:"vulnerableSystemConfidentiality,omitempty"` // schema property: vulnConfidentialityImpact
150 | VulnerableSystemIntegrity *string `json:"vulnerableSystemIntegrity,omitempty"` // schema property: vulnIntegrityImpact
151 | VulnerableSystemAvailability *string `json:"vulnerableSystemAvailability,omitempty"` // schema property: vulnAvailabilityImpact
152 | SubsequentSystemConfidentiality *string `json:"subsequentSystemConfidentiality,omitempty"` // schema property: subConfidentialityImpact
153 | SubsequentSystemIntegrity *string `json:"subsequentSystemIntegrity,omitempty"` // schema property: subIntegrityImpact
154 | SubsequentSystemAvailability *string `json:"subsequentSystemAvailability,omitempty"` // schema property: subAvailabilityImpact
155 | ExploitMaturity *string `json:"exploitMaturity,omitempty"`
156 | ConfidentialityRequirement *string `json:"confidentialityRequirements,omitempty"`
157 | IntegrityRequirement *string `json:"integrityRequirements,omitempty"`
158 | AvailabilityRequirement *string `json:"availabilityRequirements,omitempty"`
159 | ModifiedAttackVector *string `json:"modifiedAttackVector,omitempty"`
160 | ModifiedAttackComplexity *string `json:"modifiedAttackComplexity,omitempty"`
161 | ModifiedAttackRequirements *string `json:"modifiedAttackRequirements,omitempty"`
162 | ModifiedPrivilegesRequired *string `json:"modifiedPrivilegesRequired,omitempty"`
163 | ModifiedUserInteraction *string `json:"modifiedUserInteraction,omitempty"`
164 | ModifiedVulnerableSystemConfidentiality *string `json:"modifiedVulnerableSystemConfidentiality,omitempty"` // schema property: modifiedVulnConfidentialityImpact
165 | ModifiedVulnerableSystemIntegrity *string `json:"modifiedVulnerableSystemIntegrity,omitempty"` // schema property: modifiedVulnIntegrityImpact
166 | ModifiedVulnerableSystemAvailability *string `json:"modifiedVulnerableSystemAvailability,omitempty"` // schema property: modifiedVulnAvailabilityImpact
167 | ModifiedSubsequentSystemConfidentiality *string `json:"modifiedSubsequentSystemConfidentiality,omitempty"` // schema property: modifiedSubConfidentialityImpact
168 | ModifiedSubsequentSystemIntegrity *string `json:"modifiedSubsequentSystemIntegrity,omitempty"` // schema property: modifiedSubIntegrityImpact
169 | ModifiedSubsequentSystemAvailability *string `json:"modifiedSubsequentSystemAvailability,omitempty"` // schema property: modifiedSubAvailabilityImpact
170 | Safety *string `json:"safety,omitempty"` // schema property: Safety
171 | Automatable *string `json:"automatable,omitempty"` // schema property: Automatable
172 | ProviderUrgency *string `json:"providerUrgency,omitempty"`
173 | Recovery *string `json:"recovery,omitempty"` // schema property: Recovery
174 | ValueDensity *string `json:"valueDensity,omitempty"`
175 | VulnerabilityResponseEffort *string `json:"vulnerabilityResponseEffort,omitempty"`
176 | ThreatScore *float64 `json:"threatScore,omitempty"`
177 | ThreatSeverity *string `json:"threatSeverity,omitempty"`
178 | EnvironmentalScore *float64 `json:"environmentalScore,omitempty"`
179 | EnvironmentalSeverity *string `json:"environmentalSeverity,omitempty"`
180 | } `json:"cvssData"`
181 | } `json:"cvssMetricV40,omitempty"`
182 | } `json:"metrics,omitempty"`
183 | Weaknesses []struct {
184 | Source string `json:"source"`
185 | Type string `json:"type"`
186 | Description []struct {
187 | Lang string `json:"lang"`
188 | Value string `json:"value"`
189 | } `json:"description"`
190 | } `json:"weaknesses,omitempty"`
191 | Configurations []struct {
192 | Operator string `json:"operator,omitempty"`
193 | Negate bool `json:"negate,omitempty"`
194 | Nodes []struct {
195 | Operator string `json:"operator"`
196 | Negate bool `json:"negate,omitempty"`
197 | CPEMatch []struct {
198 | Vulnerable bool `json:"vulnerable"`
199 | Criteria string `json:"criteria"`
200 | MatchCriteriaID string `json:"matchCriteriaId"`
201 | VersionStartExcluding string `json:"versionStartExcluding,omitempty"`
202 | VersionStartIncluding string `json:"versionStartIncluding,omitempty"`
203 | VersionEndExcluding string `json:"versionEndExcluding,omitempty"`
204 | VersionEndIncluding string `json:"versionEndIncluding,omitempty"`
205 | } `json:"cpeMatch"`
206 | } `json:"nodes"`
207 | } `json:"configurations,omitempty"`
208 | VendorComments []struct {
209 | Organization string `json:"organization"`
210 | Comment string `json:"comment"`
211 | LastModified string `json:"lastModified"`
212 | } `json:"vendorComments,omitempty"`
213 | }
214 |
--------------------------------------------------------------------------------
/fetcher/paloalto/types.go:
--------------------------------------------------------------------------------
1 | package paloalto
2 |
3 | import "encoding/json"
4 |
5 | type vulnerability struct {
6 | DataType string `json:"dataType"`
7 | DataVersion string `json:"dataVersion"`
8 | CVEMetadata cveMetadata `json:"cveMetadata"`
9 | Containers struct {
10 | CNA cna `json:"cna"`
11 | } `json:"containers"`
12 | }
13 |
14 | type cveMetadata struct {
15 | CVEID string `json:"cveId"`
16 | AssignerOrgID string `json:"assignerOrgId"`
17 | AssignerShortName *string `json:"assignerShortName,omitempty"`
18 | RequesterUserID *string `json:"requesterUserId,omitempty"`
19 | Serial *int `json:"serial,omitempty"`
20 | State string `json:"state"`
21 | DatePublished *string `json:"datePublished,omitempty"`
22 | DateUpdated *string `json:"dateUpdated,omitempty"`
23 | DateReserved *string `json:"dateReserved,omitempty"`
24 | DateRejected *string `json:"dateRejected,omitempty"`
25 | }
26 |
27 | type cna struct {
28 | ProviderMetadata providerMetadata `json:"providerMetadata"`
29 | Title *string `json:"title,omitempty"`
30 | Descriptions []description `json:"descriptions,omitempty"`
31 | Affected []product `json:"affected,omitempty"`
32 | ProblemTypes []problemType `json:"problemTypes,omitempty"`
33 | Impacts []impact `json:"impacts,omitempty"`
34 | Metrics []metric `json:"metrics,omitempty"`
35 | Workarounds []description `json:"workarounds,omitempty"`
36 | Solutions []description `json:"solutions,omitempty"`
37 | Exploits []description `json:"exploits,omitempty"`
38 | Configurations []description `json:"configurations,omitempty"`
39 | References []reference `json:"references,omitempty"`
40 | Timeline timeline `json:"timeline,omitempty"`
41 | Credits credits `json:"credits,omitempty"`
42 | Source source `json:"source,omitempty"`
43 | Tags []string `json:"tags,omitempty"`
44 | TaxonomyMappings taxonomyMappings `json:"taxonomyMappings,omitempty"`
45 | DateAssigned *string `json:"dateAssigned,omitempty"`
46 | DatePublic *string `json:"datePublic,omitempty"`
47 | XGenerator interface{} `json:"x_generator,omitempty"`
48 | XAffectedList interface{} `json:"x_affectedList,omitempty"`
49 | }
50 |
51 | type providerMetadata struct {
52 | OrgID string `json:"orgID"`
53 | ShortName *string `json:"shortName,omitempty"`
54 | DateUpdated *string `json:"dateUpdated,omitempty"`
55 | }
56 |
57 | type description struct {
58 | Lang string `json:"lang"`
59 | Value string `json:"value"`
60 | SupportingMedia interface{} `json:"supportingMedia,omitempty"`
61 | }
62 |
63 | type product struct {
64 | Vendor *string `json:"vendor,omitempty"`
65 | Product *string `json:"product,omitempty"`
66 | CollectionURL *string `json:"collectionURL,omitempty"`
67 | PackageName *string `json:"packageName,omitempty"`
68 | Cpes []string `json:"cpes,omitempty"`
69 | Modules []string `json:"modules,omitempty"`
70 | ProgramFiles []string `json:"programFiles,omitempty"`
71 | ProgramRoutines []struct {
72 | Name string `json:"name"`
73 | } `json:"programRoutines,omitempty"`
74 | Platforms []string `json:"platforms,omitempty"`
75 | Repo *string `json:"repo,omitempty"`
76 | DefaultStatus *string `json:"defaultStatus,omitempty"`
77 | Versions []version `json:"versions,omitempty"`
78 | }
79 |
80 | type version struct {
81 | Status string `json:"status"`
82 | VersionType *string `json:"versionType,omitempty"`
83 | Version string `json:"version"`
84 | LessThan *string `json:"lessThan,omitempty"`
85 | LessThanOrEqual *string `json:"lessThanOrEqual,omitempty"`
86 | Changes []versionChange `json:"changes,omitempty"`
87 | }
88 |
89 | type versionChange struct {
90 | At string `json:"at"`
91 | Status string `json:"status"`
92 | }
93 |
94 | type problemType struct {
95 | Descriptions []struct {
96 | Type *string `json:"type,omitempty"`
97 | Lang string `json:"lang"`
98 | Description string `json:"description"`
99 | CweID *string `json:"cweId,omitempty"`
100 | References []reference `json:"references,omitempty"`
101 | } `json:"descriptions"`
102 | }
103 |
104 | type impact struct {
105 | Descriptions []description `json:"descriptions"`
106 | CapecID *string `json:"capecId,omitempty"`
107 | }
108 |
109 | type metric struct {
110 | Format *string `json:"format,omitempty"`
111 | Scenarios []struct {
112 | Lang string `json:"lang"`
113 | Value string `json:"value"`
114 | } `json:"scenarios,omitempty"`
115 | CVSSv2 *cvssv2 `json:"cvssV2_0,omitempty"`
116 | CVSSv30 *cvssv30 `json:"cvssV3_0,omitempty"`
117 | CVSSv31 *cvssv31 `json:"cvssV3_1,omitempty"`
118 | CVSSv40 *cvssv40 `json:"cvssV4_0,omitempty"`
119 | Other *struct {
120 | Type string `json:"type"`
121 | Content json.RawMessage `json:"content"`
122 | } `json:"other,omitempty"`
123 | }
124 |
125 | type cvssv2 struct {
126 | Version string `json:"version"`
127 | VectorString string `json:"vectorString"`
128 | AccessVector *string `json:"accessVector,omitempty"`
129 | AccessComplexity *string `json:"accessComplexity,omitempty"`
130 | Authentication *string `json:"authentication,omitempty"`
131 | ConfidentialityImpact *string `json:"confidentialityImpact,omitempty"`
132 | IntegrityImpact *string `json:"integrityImpact,omitempty"`
133 | AvailabilityImpact *string `json:"availabilityImpact,omitempty"`
134 | BaseScore float64 `json:"baseScore"`
135 | Exploitability *string `json:"exploitability,omitempty"`
136 | RemediationLevel *string `json:"remediationLevel,omitempty"`
137 | ReportConfidence *string `json:"reportConfidence,omitempty"`
138 | TemporalScore *float64 `json:"temporalScore,omitempty"`
139 | CollateralDamagePotential *string `json:"collateralDamagePotential,omitempty"`
140 | TargetDistribution *string `json:"targetDistribution,omitempty"`
141 | ConfidentialityRequirement *string `json:"confidentialityRequirement,omitempty"`
142 | IntegrityRequirement *string `json:"integrityRequirement,omitempty"`
143 | AvailabilityRequirement *string `json:"availabilityRequirement,omitempty"`
144 | EnvironmentalScore *float64 `json:"environmentalScore,omitempty"`
145 | }
146 |
147 | type cvssv30 struct {
148 | Version string `json:"version"`
149 | VectorString string `json:"vectorString"`
150 | AttackVector *string `json:"attackVector,omitempty"`
151 | AttackComplexity *string `json:"attackComplexity,omitempty"`
152 | PrivilegesRequired *string `json:"privilegesRequired,omitempty"`
153 | UserInteraction *string `json:"userInteraction,omitempty"`
154 | Scope *string `json:"scope,omitempty"`
155 | ConfidentialityImpact *string `json:"confidentialityImpact,omitempty"`
156 | IntegrityImpact *string `json:"integrityImpact,omitempty"`
157 | AvailabilityImpact *string `json:"availabilityImpact,omitempty"`
158 | BaseScore float64 `json:"baseScore"`
159 | BaseSeverity string `json:"baseSeverity"`
160 | ExploitCodeMaturity *string `json:"exploitCodeMaturity,omitempty"`
161 | RemediationLevel *string `json:"remediationLevel,omitempty"`
162 | ReportConfidence *string `json:"reportConfidence,omitempty"`
163 | TemporalScore *float64 `json:"temporalScore,omitempty"`
164 | TemporalSeverity *string `json:"temporalSeverity,omitempty"`
165 | ConfidentialityRequirement *string `json:"confidentialityRequirement,omitempty"`
166 | IntegrityRequirement *string `json:"integrityRequirement,omitempty"`
167 | AvailabilityRequirement *string `json:"availabilityRequirement,omitempty"`
168 | ModifiedAttackVector *string `json:"modifiedAttackVector,omitempty"`
169 | ModifiedAttackComplexity *string `json:"modifiedAttackComplexity,omitempty"`
170 | ModifiedPrivilegesRequired *string `json:"modifiedPrivilegesRequired,omitempty"`
171 | ModifiedUserInteraction *string `json:"modifiedUserInteraction,omitempty"`
172 | ModifiedScope *string `json:"modifiedScope,omitempty"`
173 | ModifiedConfidentialityImpact *string `json:"modifiedConfidentialityImpact,omitempty"`
174 | ModifiedIntegrityImpact *string `json:"modifiedIntegrityImpact,omitempty"`
175 | ModifiedAvailabilityImpact *string `json:"modifiedAvailabilityImpact,omitempty"`
176 | EnvironmentalScore *float64 `json:"environmentalScore,omitempty"`
177 | EnvironmentalSeverity *string `json:"environmentalSeverity,omitempty"`
178 | }
179 |
180 | type cvssv31 struct {
181 | Version string `json:"version"`
182 | VectorString string `json:"vectorString"`
183 | AttackVector *string `json:"attackVector,omitempty"`
184 | AttackComplexity *string `json:"attackComplexity,omitempty"`
185 | PrivilegesRequired *string `json:"privilegesRequired,omitempty"`
186 | UserInteraction *string `json:"userInteraction,omitempty"`
187 | Scope *string `json:"scope,omitempty"`
188 | ConfidentialityImpact *string `json:"confidentialityImpact,omitempty"`
189 | IntegrityImpact *string `json:"integrityImpact,omitempty"`
190 | AvailabilityImpact *string `json:"availabilityImpact,omitempty"`
191 | BaseScore float64 `json:"baseScore"`
192 | BaseSeverity string `json:"baseSeverity"`
193 | ExploitCodeMaturity *string `json:"exploitCodeMaturity,omitempty"`
194 | RemediationLevel *string `json:"remediationLevel,omitempty"`
195 | ReportConfidence *string `json:"reportConfidence,omitempty"`
196 | TemporalScore *float64 `json:"temporalScore,omitempty"`
197 | TemporalSeverity *string `json:"temporalSeverity,omitempty"`
198 | ConfidentialityRequirement *string `json:"confidentialityRequirement,omitempty"`
199 | IntegrityRequirement *string `json:"integrityRequirement,omitempty"`
200 | AvailabilityRequirement *string `json:"availabilityRequirement,omitempty"`
201 | ModifiedAttackVector *string `json:"modifiedAttackVector,omitempty"`
202 | ModifiedAttackComplexity *string `json:"modifiedAttackComplexity,omitempty"`
203 | ModifiedPrivilegesRequired *string `json:"modifiedPrivilegesRequired,omitempty"`
204 | ModifiedUserInteraction *string `json:"modifiedUserInteraction,omitempty"`
205 | ModifiedScope *string `json:"modifiedScope,omitempty"`
206 | ModifiedConfidentialityImpact *string `json:"modifiedConfidentialityImpact,omitempty"`
207 | ModifiedIntegrityImpact *string `json:"modifiedIntegrityImpact,omitempty"`
208 | ModifiedAvailabilityImpact *string `json:"modifiedAvailabilityImpact,omitempty"`
209 | EnvironmentalScore *float64 `json:"environmentalScore,omitempty"`
210 | EnvironmentalSeverity *string `json:"environmentalSeverity,omitempty"`
211 | }
212 |
213 | type cvssv40 struct {
214 | Version string `json:"version"`
215 | VectorString string `json:"vectorString"`
216 | BaseScore float64 `json:"baseScore"`
217 | BaseSeverity string `json:"baseSeverity"`
218 | AttackVector *string `json:"attackVector,omitempty"`
219 | AttackComplexity *string `json:"attackComplexity,omitempty"`
220 | AttackRequirements *string `json:"attackRequirements,omitempty"`
221 | PrivilegesRequired *string `json:"privilegesRequired,omitempty"`
222 | UserInteraction *string `json:"userInteraction,omitempty"`
223 | VulnConfidentialityImpact *string `json:"vulnConfidentialityImpact,omitempty"`
224 | VulnIntegrityImpact *string `json:"vulnIntegrityImpact,omitempty"`
225 | VulnAvailabilityImpact *string `json:"vulnAvailabilityImpact,omitempty"`
226 | SubConfidentialityImpact *string `json:"subConfidentialityImpact,omitempty"`
227 | SubIntegrityImpact *string `json:"subIntegrityImpact,omitempty"`
228 | SubAvailabilityImpact *string `json:"subAvailabilityImpact,omitempty"`
229 | ExploitMaturity *string `json:"exploitMaturity,omitempty"`
230 | ConfidentialityRequirement *string `json:"confidentialityRequirement,omitempty"`
231 | IntegrityRequirement *string `json:"integrityRequirement,omitempty"`
232 | AvailabilityRequirement *string `json:"availabilityRequirement,omitempty"`
233 | ModifiedAttackVector *string `json:"modifiedAttackVector,omitempty"`
234 | ModifiedAttackComplexity *string `json:"modifiedAttackComplexity,omitempty"`
235 | ModifiedAttackRequirements *string `json:"modifiedAttackRequirements,omitempty"`
236 | ModifiedPrivilegesRequired *string `json:"modifiedPrivilegesRequired,omitempty"`
237 | ModifiedUserInteraction *string `json:"modifiedUserInteraction,omitempty"`
238 | ModifiedVulnConfidentialityImpact *string `json:"modifiedVulnConfidentialityImpact,omitempty"`
239 | ModifiedVulnIntegrityImpact *string `json:"modifiedVulnIntegrityImpact,omitempty"`
240 | ModifiedVulnAvailabilityImpact *string `json:"modifiedVulnAvailabilityImpact,omitempty"`
241 | ModifiedSubConfidentialityImpact *string `json:"modifiedSubConfidentialityImpact,omitempty"`
242 | ModifiedSubIntegrityImpact *string `json:"modifiedSubIntegrityImpact,omitempty"`
243 | ModifiedSubAvailabilityImpact *string `json:"modifiedSubAvailabilityImpact,omitempty"`
244 | Safety *string `json:"Safety,omitempty"`
245 | Automatable *string `json:"Automatable,omitempty"`
246 | Recovery *string `json:"Recovery,omitempty"`
247 | ValueDensity *string `json:"valueDensity,omitempty"`
248 | VulnerabilityResponseEffort *string `json:"vulnerabilityResponseEffort,omitempty"`
249 | ProviderUrgency *string `json:"providerUrgency,omitempty"`
250 | ThreatScore *float64 `json:"threatScore,omitempty"`
251 | ThreatSeverity *string `json:"threatSeverity,omitempty"`
252 | EnvironmentalScore *float64 `json:"environmentalScore,omitempty"`
253 | EnvironmentalSeverity *string `json:"environmentalSeverity,omitempty"`
254 | }
255 |
256 | type reference struct {
257 | Name *string `json:"name,omitempty"`
258 | Tags []string `json:"tags,omitempty"`
259 | URL string `json:"url"`
260 | }
261 |
262 | type timeline []struct {
263 | Time string `json:"time"`
264 | Lang string `json:"lang"`
265 | Value string `json:"value"`
266 | }
267 |
268 | type credits []struct {
269 | Type *string `json:"type,omitempty"`
270 | Lang string `json:"lang"`
271 | User *string `json:"user,omitempty"`
272 | Value string `json:"value"`
273 | }
274 |
275 | type source struct {
276 | Advisory *string `json:"advisory,omitempty"`
277 | Defect []string `json:"defect,omitempty"`
278 | Discovery string `json:"discovery"`
279 | }
280 |
281 | type taxonomyMappings []struct {
282 | TaxonomyVersion *string `json:"taxonomyVersion,omitempty"`
283 | TaxonomyName string `json:"taxonomyName"`
284 | TaxonomyRelations []struct {
285 | TaxonomyID string `json:"taxonomyId"`
286 | RelationshipName string `json:"relationshipName"`
287 | RelationshipValue string `json:"relationshipValue"`
288 | } `json:"taxonomyRelations"`
289 | }
290 |
--------------------------------------------------------------------------------
/fetcher/util.go:
--------------------------------------------------------------------------------
1 | package fetcher
2 |
3 | import (
4 | "fmt"
5 | "strconv"
6 | "strings"
7 |
8 | "github.com/knqyf263/go-cpe/common"
9 | "github.com/knqyf263/go-cpe/naming"
10 | log "github.com/vulsio/go-cve-dictionary/log"
11 | "github.com/vulsio/go-cve-dictionary/models"
12 | )
13 |
14 | // ParseCpeURI parses cpe22uri and set to models.CpeBase
15 | func ParseCpeURI(uri string) (*models.CpeBase, error) {
16 | var wfn common.WellFormedName
17 | var err error
18 | if strings.HasPrefix(uri, "cpe:/") {
19 | val := strings.TrimPrefix(uri, "cpe:/")
20 | if strings.Contains(val, "/") {
21 | uri = "cpe:/" + strings.ReplaceAll(val, "/", `\/`)
22 | }
23 | wfn, err = naming.UnbindURI(uri)
24 | if err != nil {
25 | return nil, err
26 | }
27 | } else {
28 | wfn, err = naming.UnbindFS(uri)
29 | if err != nil {
30 | return nil, err
31 | }
32 | }
33 |
34 | return &models.CpeBase{
35 | URI: naming.BindToURI(wfn),
36 | FormattedString: naming.BindToFS(wfn),
37 | WellFormedName: wfn.String(),
38 | CpeWFN: models.CpeWFN{
39 | Part: fmt.Sprintf("%s", wfn.Get(common.AttributePart)),
40 | Vendor: fmt.Sprintf("%s", wfn.Get(common.AttributeVendor)),
41 | Product: fmt.Sprintf("%s", wfn.Get(common.AttributeProduct)),
42 | Version: fmt.Sprintf("%s", wfn.Get(common.AttributeVersion)),
43 | Update: fmt.Sprintf("%s", wfn.Get(common.AttributeUpdate)),
44 | Edition: fmt.Sprintf("%s", wfn.Get(common.AttributeEdition)),
45 | Language: fmt.Sprintf("%s", wfn.Get(common.AttributeLanguage)),
46 | SoftwareEdition: fmt.Sprintf("%s", wfn.Get(common.AttributeSwEdition)),
47 | TargetSW: fmt.Sprintf("%s", wfn.Get(common.AttributeTargetSw)),
48 | TargetHW: fmt.Sprintf("%s", wfn.Get(common.AttributeTargetHw)),
49 | Other: fmt.Sprintf("%s", wfn.Get(common.AttributeOther)),
50 | },
51 | }, nil
52 | }
53 |
54 | // StringToFloat cast string to float64
55 | func StringToFloat(str string) float64 {
56 | if len(str) == 0 {
57 | return 0
58 | }
59 | var f float64
60 | var ignorableError error
61 | if f, ignorableError = strconv.ParseFloat(str, 64); ignorableError != nil {
62 | log.Errorf("Failed to cast CVSS score. score: %s, err; %s",
63 | str,
64 | ignorableError,
65 | )
66 | f = 0
67 | }
68 | return f
69 | }
70 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/vulsio/go-cve-dictionary
2 |
3 | go 1.24
4 |
5 | require (
6 | github.com/MaineK00n/go-cisco-version v0.0.0-20250611084427-015c6492ef23
7 | github.com/MaineK00n/go-paloalto-version v0.0.0-20250522233912-78724c69edda
8 | github.com/PuerkitoBio/goquery v1.10.3
9 | github.com/cenkalti/backoff v2.2.1+incompatible
10 | github.com/cheggaaa/pb/v3 v3.1.7
11 | github.com/glebarez/sqlite v1.11.0
12 | github.com/go-redis/redis/v8 v8.11.5
13 | github.com/google/go-cmp v0.7.0
14 | github.com/hashicorp/go-version v1.7.0
15 | github.com/inconshreveable/log15 v3.0.0-testing.5+incompatible
16 | github.com/klauspost/compress v1.18.0
17 | github.com/knqyf263/go-cpe v0.0.0-20201213041631-54f6ab28673f
18 | github.com/knqyf263/go-rpm-version v0.0.0-20170716094938-74609b86c936
19 | github.com/labstack/echo/v4 v4.13.4
20 | github.com/mitchellh/go-homedir v1.1.0
21 | github.com/opencontainers/image-spec v1.1.1
22 | github.com/spf13/cobra v1.9.1
23 | github.com/spf13/viper v1.20.1
24 | golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2
25 | gorm.io/driver/mysql v1.5.5
26 | gorm.io/driver/postgres v1.5.7
27 | gorm.io/gorm v1.25.7
28 | oras.land/oras-go/v2 v2.6.0
29 | )
30 |
31 | require (
32 | github.com/VividCortex/ewma v1.2.0 // indirect
33 | github.com/andybalholm/cascadia v1.3.3 // indirect
34 | github.com/cespare/xxhash/v2 v2.3.0 // indirect
35 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
36 | github.com/dustin/go-humanize v1.0.1 // indirect
37 | github.com/fatih/color v1.18.0 // indirect
38 | github.com/fsnotify/fsnotify v1.8.0 // indirect
39 | github.com/glebarez/go-sqlite v1.21.2 // indirect
40 | github.com/go-sql-driver/mysql v1.7.1 // indirect
41 | github.com/go-stack/stack v1.8.0 // indirect
42 | github.com/go-viper/mapstructure/v2 v2.3.0 // indirect
43 | github.com/google/uuid v1.6.0 // indirect
44 | github.com/inconshreveable/mousetrap v1.1.0 // indirect
45 | github.com/jackc/pgpassfile v1.0.0 // indirect
46 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
47 | github.com/jackc/pgx/v5 v5.5.4 // indirect
48 | github.com/jackc/puddle/v2 v2.2.1 // indirect
49 | github.com/jinzhu/inflection v1.0.0 // indirect
50 | github.com/jinzhu/now v1.1.5 // indirect
51 | github.com/labstack/gommon v0.4.2 // indirect
52 | github.com/mattn/go-colorable v0.1.14 // indirect
53 | github.com/mattn/go-isatty v0.0.20 // indirect
54 | github.com/mattn/go-runewidth v0.0.16 // indirect
55 | github.com/opencontainers/go-digest v1.0.0 // indirect
56 | github.com/pelletier/go-toml/v2 v2.2.3 // indirect
57 | github.com/pkg/errors v0.9.1 // indirect
58 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
59 | github.com/rivo/uniseg v0.4.7 // indirect
60 | github.com/sagikazarmark/locafero v0.7.0 // indirect
61 | github.com/sourcegraph/conc v0.3.0 // indirect
62 | github.com/spf13/afero v1.12.0 // indirect
63 | github.com/spf13/cast v1.7.1 // indirect
64 | github.com/spf13/pflag v1.0.6 // indirect
65 | github.com/subosito/gotenv v1.6.0 // indirect
66 | github.com/valyala/bytebufferpool v1.0.0 // indirect
67 | github.com/valyala/fasttemplate v1.2.2 // indirect
68 | go.uber.org/atomic v1.9.0 // indirect
69 | go.uber.org/multierr v1.9.0 // indirect
70 | golang.org/x/crypto v0.38.0 // indirect
71 | golang.org/x/net v0.40.0 // indirect
72 | golang.org/x/sync v0.14.0 // indirect
73 | golang.org/x/sys v0.33.0 // indirect
74 | golang.org/x/term v0.32.0 // indirect
75 | golang.org/x/text v0.25.0 // indirect
76 | golang.org/x/time v0.11.0 // indirect
77 | gopkg.in/yaml.v3 v3.0.1 // indirect
78 | modernc.org/libc v1.22.5 // indirect
79 | modernc.org/mathutil v1.5.0 // indirect
80 | modernc.org/memory v1.5.0 // indirect
81 | modernc.org/sqlite v1.23.1 // indirect
82 | )
83 |
--------------------------------------------------------------------------------
/integration/.gitignore:
--------------------------------------------------------------------------------
1 | go-cve.*
2 | *.sqlite3
3 | diff
--------------------------------------------------------------------------------
/integration/README.md:
--------------------------------------------------------------------------------
1 | # Test Script For go-cve-dictionary
2 | Documentation on testing for developers
3 |
4 | ## Getting Started
5 | ```terminal
6 | $ pip install -r requirements.txt
7 | ```
8 |
9 | ## Run test
10 | Use `127.0.0.1:1325` and `127.0.0.1:1326` to diff the server mode between the latest tag and your working branch.
11 |
12 | If you have prepared the two addresses yourself, you can use the following Python script.
13 | ```terminal
14 | $ python diff_server_mode.py --help
15 |
16 | ```
17 |
18 | [GNUmakefile](../GNUmakefile) has some tasks for testing.
19 | Please run it in the top directory of the go-cve-dictionary repository.
20 |
21 | - build-integration: create the go-cve-dictionary binaries needed for testing
22 | - clean-integration: delete the go-cve-dictionary process, binary, and docker container used in the test
23 | - fetch-rdb: fetch data for RDB for testing
24 | - fetch-redis: fetch data for Redis for testing
25 | - diff-cveid: Run tests for CVE-ID in server mode
26 | - diff-cpes: Run tests for CPE in server mode
27 | - diff-server-rdb: take the result difference of server mode using RDB
28 | - diff-server-redis: take the result difference of server mode using Redis
29 | - diff-server-rdb-redis: take the difference in server mode results between RDB and Redis
30 |
31 | ## About the CVE-ID and CPE_URI used for testing
32 | Duplicates are removed from the latest fetched data and prepared.
33 | For example, for sqlite3, you can get it as follows.
34 |
35 | ```terminal
36 | $ sqlite3 cve.sqlite3
37 | SQLite version 3.31.1 2020-01-27 19:55:54
38 | Enter ".help" for usage hints.
39 | # CVE-ID
40 | sqlite> .output integration/nvd_cves.txt
41 | sqlite> SELECT DISTINCT cve_id FROM nvds;
42 | sqlite> .output integration/jvn_cves.txt
43 | sqlite> SELECT DISTINCT cve_id FROM jvns;
44 |
45 | # CPE URI
46 | sqlite> .output integration/nvd_cpes.txt
47 | sqlite> SELECT DISTINCT uri FROM nvd_cpes;
48 | sqlite> .output integration/jvn_cpes.txt
49 | sqlite> SELECT DISTINCT uri FROM jvn_cpes;
50 |
51 | $ cat integration/nvd_cves.txt integration/jvn_cves.txt | sort | uniq > integration/cves.txt
52 | $ rm integration/nvd_cves.txt integration/jvn_cves.txt
53 | $ cat integration/nvd_cpes.txt integration/jvn_cpes.txt | sort | uniq > integration/cpes.txt
54 | $ rm integration/nvd_cpes.txt integration/jvn_cpes.txt
55 | ```
56 |
--------------------------------------------------------------------------------
/integration/diff_server_mode.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import logging
3 | from typing import Tuple
4 | from deepdiff import DeepDiff
5 | import requests
6 | from requests.adapters import HTTPAdapter
7 | from urllib3.util import Retry
8 | from urllib.parse import quote
9 | import pprint
10 | from concurrent.futures import ThreadPoolExecutor
11 | import os
12 | import json
13 | import random
14 | import math
15 | import shutil
16 | import uuid
17 | import time
18 |
19 |
20 | def diff_response(args: Tuple[str, str]):
21 | # Endpoint
22 | # GET /cves/:id
23 | # POST /cpes
24 | # POST /cpes/ids
25 |
26 | path = ''
27 | if args[0] == 'cves':
28 | path = f'cves/{args[1]}'
29 | if args[0] == 'cpes':
30 | path = 'cpes'
31 | if args[0] == 'cpe_ids':
32 | path = 'cpes/ids'
33 |
34 | session = requests.Session()
35 | retries = Retry(total=5,
36 | backoff_factor=1,
37 | status_forcelist=[503, 504])
38 | session.mount("http://", HTTPAdapter(max_retries=retries))
39 |
40 | try:
41 | if args[0] == 'cves':
42 | response_old = requests.get(
43 | f'http://127.0.0.1:1325/{path}', timeout=(3.0, 10.0)).json()
44 | response_new = requests.get(
45 | f'http://127.0.0.1:1326/{path}', timeout=(3.0, 10.0)).json()
46 | if args[0] in ['cpes', 'cpe_ids']:
47 | payload = {"name": args[1]}
48 | response_old = requests.post(
49 | f'http://127.0.0.1:1325/{path}', data=json.dumps(payload), headers={'content-type': 'application/json'}, timeout=(3.0, 30.0)).json()
50 | response_new = requests.post(
51 | f'http://127.0.0.1:1326/{path}', data=json.dumps(payload), headers={'content-type': 'application/json'}, timeout=(3.0, 30.0)).json()
52 | except requests.ConnectionError as e:
53 | logger.error(
54 | f'Failed to Connection..., err: {e}, {pprint.pformat({"args": args, "path": path}, indent=2)}')
55 | exit(1)
56 | except requests.ReadTimeout as e:
57 | logger.warning(
58 | f'Failed to ReadTimeout..., err: {e}, {pprint.pformat({"args": args, "path": path}, indent=2)}')
59 | except Exception as e:
60 | logger.error(
61 | f'Failed to GET request..., err: {e}, {pprint.pformat({"args": args, "path": path}, indent=2)}')
62 | exit(1)
63 |
64 | diff = DeepDiff(response_old, response_new, ignore_order=True)
65 | if diff != {}:
66 | logger.warning(
67 | f'There is a difference between old and new(or RDB and Redis):\n {pprint.pformat({"args": args, "path": path}, indent=2)}')
68 |
69 | title = args[1]
70 | if args[0] != 'cves':
71 | title = uuid.uuid4()
72 | diff_path = f'integration/diff/{args[0]}/{title}'
73 | with open(f'{diff_path}.old', 'w') as w:
74 | w.write(json.dumps(
75 | {'args': args, 'response': response_old}, indent=4))
76 | with open(f'{diff_path}.new', 'w') as w:
77 | w.write(json.dumps(
78 | {'args': args, 'response': response_new}, indent=4))
79 |
80 |
81 | parser = argparse.ArgumentParser()
82 | parser.add_argument('mode', choices=['cves', 'cpes', 'cpe_ids'],
83 | help='Specify the mode to test.')
84 | parser.add_argument("--sample_rate", type=float, default=0.01,
85 | help="Adjust the rate of data used for testing (len(test_data) * sample_rate)")
86 | parser.add_argument(
87 | '--debug', action=argparse.BooleanOptionalAction, help='print debug message')
88 | args = parser.parse_args()
89 |
90 | logger = logging.getLogger(__name__)
91 | stream_handler = logging.StreamHandler()
92 |
93 | if args.debug:
94 | logger.setLevel(logging.DEBUG)
95 | stream_handler.setLevel(logging.DEBUG)
96 | else:
97 | logger.setLevel(logging.INFO)
98 | stream_handler.setLevel(logging.INFO)
99 |
100 | formatter = logging.Formatter(
101 | '%(levelname)s[%(asctime)s] %(message)s', "%m-%d|%H:%M:%S")
102 | stream_handler.setFormatter(formatter)
103 | logger.addHandler(stream_handler)
104 |
105 | logger.info(
106 | f'start server mode test(mode: {args.mode})')
107 |
108 | logger.info('check the communication with the server')
109 | for i in range(5):
110 | try:
111 | if requests.get('http://127.0.0.1:1325/health').status_code == requests.codes.ok and requests.get('http://127.0.0.1:1326/health').status_code == requests.codes.ok:
112 | logger.info('communication with the server has been confirmed')
113 | break
114 | except Exception:
115 | pass
116 | time.sleep(1)
117 | else:
118 | logger.error('Failed to communicate with server')
119 | exit(1)
120 |
121 | list_path = None
122 | if args.mode == 'cves':
123 | list_path = "integration/cves.txt"
124 | if args.mode in ['cpes', 'cpe_ids']:
125 | list_path = "integration/cpes.txt"
126 |
127 | if not os.path.isfile(list_path):
128 | logger.error(f'Failed to find list path..., list_path: {list_path}')
129 | exit(1)
130 |
131 | diff_path = f'integration/diff/{args.mode}'
132 | if os.path.exists(diff_path):
133 | shutil.rmtree(diff_path)
134 | os.makedirs(diff_path, exist_ok=True)
135 |
136 | with open(list_path) as f:
137 | list = [s.strip() for s in f.readlines()]
138 | list = random.sample(list, math.ceil(len(list) * args.sample_rate))
139 | with ThreadPoolExecutor() as executor:
140 | ins = ((args.mode, e) for e in list)
141 | executor.map(diff_response, ins)
142 |
--------------------------------------------------------------------------------
/integration/requirements.txt:
--------------------------------------------------------------------------------
1 | deepdiff==5.5.0
2 | requests==2.32.4
--------------------------------------------------------------------------------
/log/log.go:
--------------------------------------------------------------------------------
1 | package log
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "path/filepath"
7 | "runtime"
8 |
9 | logger "github.com/inconshreveable/log15"
10 | "golang.org/x/xerrors"
11 | )
12 |
13 | // GetDefaultLogDir returns default log directory
14 | func GetDefaultLogDir() string {
15 | defaultLogDir := "/var/log/go-cve-dictionary"
16 | if runtime.GOOS == "windows" {
17 | defaultLogDir = filepath.Join(os.Getenv("APPDATA"), "go-cve-dictionary")
18 | }
19 | return defaultLogDir
20 | }
21 |
22 | // SetLogger set logger
23 | func SetLogger(logToFile bool, logDir string, debug, logJSON bool) error {
24 | stderrHandler := logger.StderrHandler
25 | logFormat := logger.LogfmtFormat()
26 | if logJSON {
27 | logFormat = logger.JsonFormatEx(false, true)
28 | stderrHandler = logger.StreamHandler(os.Stderr, logFormat)
29 | }
30 |
31 | lvlHandler := logger.LvlFilterHandler(logger.LvlInfo, stderrHandler)
32 | if debug {
33 | lvlHandler = logger.LvlFilterHandler(logger.LvlDebug, stderrHandler)
34 | }
35 |
36 | var handler logger.Handler
37 | if logToFile {
38 | if _, err := os.Stat(logDir); err != nil {
39 | if os.IsNotExist(err) {
40 | if err := os.Mkdir(logDir, 0700); err != nil {
41 | return xerrors.Errorf("Failed to create log directory. err: %w", err)
42 | }
43 | } else {
44 | return xerrors.Errorf("Failed to check log directory. err: %w", err)
45 | }
46 | }
47 |
48 | logPath := filepath.Join(logDir, "go-cve-dictionary.log")
49 | if _, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644); err != nil {
50 | return xerrors.Errorf("Failed to open a log file. err: %w", err)
51 | }
52 | handler = logger.MultiHandler(
53 | logger.Must.FileHandler(logPath, logFormat),
54 | lvlHandler,
55 | )
56 | } else {
57 | handler = lvlHandler
58 | }
59 | logger.Root().SetHandler(handler)
60 | return nil
61 | }
62 |
63 | // Debugf is wrapper function
64 | func Debugf(format string, args ...interface{}) {
65 | logger.Debug(fmt.Sprintf(format, args...))
66 | }
67 |
68 | // Infof is wrapper function
69 | func Infof(format string, args ...interface{}) {
70 | logger.Info(fmt.Sprintf(format, args...))
71 | }
72 |
73 | // Warnf is wrapper function
74 | func Warnf(format string, args ...interface{}) {
75 | logger.Warn(fmt.Sprintf(format, args...))
76 | }
77 |
78 | // Errorf is wrapper function
79 | func Errorf(format string, args ...interface{}) {
80 | logger.Error(fmt.Sprintf(format, args...))
81 | }
82 |
83 | // Fatalf is wrapper function
84 | func Fatalf(format string, args ...interface{}) {
85 | logger.Crit(fmt.Sprintf(format, args...))
86 | }
87 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "strings"
7 |
8 | "github.com/vulsio/go-cve-dictionary/commands"
9 | )
10 |
11 | func main() {
12 | if envArgs := os.Getenv("GO_CVE_DICTIONARY_ARGS"); 0 < len(envArgs) {
13 | commands.RootCmd.SetArgs(strings.Fields(envArgs))
14 | }
15 |
16 | if err := commands.RootCmd.Execute(); err != nil {
17 | fmt.Fprintln(os.Stderr, err)
18 | os.Exit(1)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/models/cisco.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import "time"
4 |
5 | const (
6 | // CiscoType :
7 | CiscoType = "Cisco"
8 |
9 | // CiscoExactVersionMatch :
10 | CiscoExactVersionMatch = "CiscoExactVersionMatch"
11 | // CiscoRoughVersionMatch :
12 | CiscoRoughVersionMatch = "CiscoRoughVersionMatch"
13 | // CiscoVendorProductMatch :
14 | CiscoVendorProductMatch = "CiscoVendorProductMatch"
15 | )
16 |
17 | // Cisco is a model of Cisco
18 | type Cisco struct {
19 | ID int64 `json:"-"`
20 | AdvisoryID string `gorm:"size:255"`
21 | Title string `gorm:"size:256"`
22 | Summary string `gorm:"size:-1"`
23 | SIR string `gorm:"size:255"`
24 | CveID string `gorm:"index:idx_ciscos_cveid;size:255"`
25 | BugIDs []CiscoBugID
26 | CweIDs []CiscoCweID
27 | Affected []CiscoProduct
28 | References []CiscoReference
29 | FirstPublished time.Time
30 | LastUpdated time.Time
31 |
32 | DetectionMethod string `gorm:"-"`
33 | }
34 |
35 | // CiscoBugID :
36 | type CiscoBugID struct {
37 | ID int64 `json:"-"`
38 | CiscoID uint `json:"-" gorm:"index:idx_cisco_bug_ids_cisco_id"`
39 | BugID string `gorm:"index:idx_cisco_bugid;size:255"`
40 | }
41 |
42 | // CiscoCweID :
43 | type CiscoCweID struct {
44 | ID int64 `json:"-"`
45 | CiscoID uint `json:"-" gorm:"index:idx_cisco_cwe_ids_cisco_id"`
46 | CweID string `gorm:"index:idx_cisco_cweid;size:255"`
47 | }
48 |
49 | // CiscoProduct :
50 | type CiscoProduct struct {
51 | ID int64 `json:"-"`
52 | CiscoID uint `json:"-" gorm:"index:idx_cisco_products_cisco_id"`
53 | CpeBase `gorm:"embedded"`
54 | }
55 |
56 | // CiscoReference :
57 | type CiscoReference struct {
58 | ID int64 `json:"-"`
59 | CiscoID uint `json:"-" gorm:"index:idx_cisco_references_cisco_id"`
60 | Reference `gorm:"embedded"`
61 | }
62 |
--------------------------------------------------------------------------------
/models/fortinet.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import "time"
4 |
5 | const (
6 | // FortinetType :
7 | FortinetType = "Fortinet"
8 |
9 | // FortinetExactVersionMatch :
10 | FortinetExactVersionMatch = "FortinetExactVersionMatch"
11 | // FortinetRoughVersionMatch :
12 | FortinetRoughVersionMatch = "FortinetRoughVersionMatch"
13 | // FortinetVendorProductMatch :
14 | FortinetVendorProductMatch = "FortinetVendorProductMatch"
15 | )
16 |
17 | // Fortinet is a model of Fortinet
18 | type Fortinet struct {
19 | ID int64 `json:"-"`
20 | AdvisoryID string `gorm:"type:varchar(255)"`
21 | CveID string `gorm:"index:idx_fortinets_cveid;type:varchar(255)"`
22 | Title string `gorm:"type:varchar(255)"`
23 | Summary string `gorm:"type:text"`
24 | Descriptions string `gorm:"type:text"`
25 | Cvss3 FortinetCvss3
26 | Cwes []FortinetCwe
27 | Cpes []FortinetCpe
28 | References []FortinetReference
29 | PublishedDate time.Time
30 | LastModifiedDate time.Time
31 | AdvisoryURL string `gorm:"type:text"`
32 |
33 | DetectionMethod string `gorm:"-"`
34 | }
35 |
36 | // FortinetCvss3 has Fortinet CVSS3 info
37 | type FortinetCvss3 struct {
38 | ID int64 `json:"-"`
39 | FortinetID uint `json:"-" gorm:"index:idx_fortinet_cvss3_fortinet_id"`
40 | Cvss3 `gorm:"embedded"`
41 | }
42 |
43 | // FortinetCwe has CweID
44 | type FortinetCwe struct {
45 | ID int64 `json:"-"`
46 | FortinetID uint `json:"-" gorm:"index:idx_fortinet_cwes_fortinet_id"`
47 | CweID string `gorm:"type:varchar(255)"`
48 | }
49 |
50 | // FortinetCpe is Child model of Fortinet.
51 | type FortinetCpe struct {
52 | ID int64 `json:"-"`
53 | FortinetID uint `json:"-" gorm:"index:idx_fortinet_cpes_fortinet_id"`
54 | CpeBase `gorm:"embedded"`
55 | }
56 |
57 | // FortinetReference holds reference information about the CVE.
58 | type FortinetReference struct {
59 | ID int64 `json:"-"`
60 | FortinetID uint `json:"-" gorm:"index:idx_fortinet_references_fortinet_id"`
61 | Reference `gorm:"embedded"`
62 | }
63 |
--------------------------------------------------------------------------------
/models/jvn.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import "time"
4 |
5 | const (
6 | // JvnType :
7 | JvnType = "JVN"
8 |
9 | // JvnVendorProductMatch :
10 | JvnVendorProductMatch = "JvnVendorProductMatch"
11 | )
12 |
13 | // Jvn is a model of JVN
14 | type Jvn struct {
15 | ID int64 `json:"-"`
16 | CveID string `gorm:"index:idx_jvns_cveid;type:varchar(255)"`
17 | Title string `gorm:"type:varchar(255)"`
18 | Summary string `gorm:"type:text"`
19 | JvnLink string `gorm:"type:varchar(255)"`
20 | JvnID string `gorm:"type:varchar(255)"`
21 | Cvss2 JvnCvss2
22 | Cvss3 JvnCvss3
23 | Cpes []JvnCpe
24 | References []JvnReference
25 | Certs []JvnCert
26 | PublishedDate time.Time
27 | LastModifiedDate time.Time
28 |
29 | DetectionMethod string `gorm:"-"`
30 | }
31 |
32 | // JvnCvss2 has Jvn CVSS Version 2 info
33 | type JvnCvss2 struct {
34 | ID int64 `json:"-"`
35 | JvnID uint `json:"-" gorm:"index:idx_jvn_cvss2_jvn_id"`
36 | Cvss2 `gorm:"embedded"`
37 | }
38 |
39 | // JvnCvss3 has JVN CVSS3 info
40 | type JvnCvss3 struct {
41 | ID int64 `json:"-"`
42 | JVNID uint `json:"-" gorm:"index:idx_jvn_cvss3_jvn_id"`
43 | Cvss3 `gorm:"embedded"`
44 | }
45 |
46 | // JvnCpe is Child model of Jvn.
47 | // see https://www.ipa.go.jp/security/vuln/CPE.html
48 | type JvnCpe struct {
49 | ID int64 `json:"-"`
50 | JvnID uint `json:"-" gorm:"index:idx_jvn_cpes_jvn_id"`
51 | CpeBase `gorm:"embedded"`
52 | }
53 |
54 | // JvnReference is Child model of Jvn.
55 | type JvnReference struct {
56 | ID int64 `json:"-"`
57 | JvnID uint `json:"-" gorm:"index:idx_jvn_references_jvn_id"`
58 | Reference `gorm:"embedded"`
59 | }
60 |
61 | // JvnCert is Child model of Jvn.
62 | type JvnCert struct {
63 | ID int64 `json:"-"`
64 | JvnID uint `json:"-" gorm:"index:idx_jvn_certs_jvn_id"`
65 | Cert `gorm:"embedded"`
66 | }
67 |
--------------------------------------------------------------------------------
/models/models.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "time"
5 |
6 | "gorm.io/gorm"
7 | )
8 |
9 | // LatestSchemaVersion manages the Schema version used in the latest go-cve-dictionary.
10 | const LatestSchemaVersion = 3
11 |
12 | // FetchMeta has meta information about fetched CVE data
13 | type FetchMeta struct {
14 | gorm.Model `json:"-"`
15 | GoCVEDictRevision string
16 | SchemaVersion uint
17 | LastFetchedAt time.Time
18 | }
19 |
20 | // OutDated checks whether last fetched feed is out dated
21 | func (f FetchMeta) OutDated() bool {
22 | return f.SchemaVersion != LatestSchemaVersion
23 | }
24 |
25 | // CveDetail :
26 | type CveDetail struct {
27 | CveID string
28 | Nvds []Nvd
29 | Jvns []Jvn
30 | Fortinets []Fortinet
31 | Mitres []Mitre
32 | Paloaltos []Paloalto
33 | Ciscos []Cisco
34 | }
35 |
36 | // HasNvd returns true if NVD contents
37 | func (c CveDetail) HasNvd() bool {
38 | return len(c.Nvds) != 0
39 | }
40 |
41 | // HasJvn returns true if JVN contents
42 | func (c CveDetail) HasJvn() bool {
43 | return len(c.Jvns) != 0
44 | }
45 |
46 | // HasFortinet returns true if Fortinet contents
47 | func (c CveDetail) HasFortinet() bool {
48 | return len(c.Fortinets) != 0
49 | }
50 |
51 | // HasMitre returns true if Mitre contents
52 | func (c CveDetail) HasMitre() bool {
53 | return len(c.Mitres) != 0
54 | }
55 |
56 | // HasPaloalto returns true if Paloalto contents
57 | func (c CveDetail) HasPaloalto() bool {
58 | return len(c.Paloaltos) != 0
59 | }
60 |
61 | // HasCisco returns true if Cisco contents
62 | func (c CveDetail) HasCisco() bool {
63 | return len(c.Ciscos) != 0
64 | }
65 |
66 | // CveIDs :
67 | type CveIDs struct {
68 | Nvd []string
69 | Jvn []string
70 | Fortinet []string
71 | Paloalto []string
72 | Cisco []string
73 | }
74 |
75 | // Cvss2 has CVSS Version 2 info
76 | type Cvss2 struct {
77 | VectorString string `gorm:"type:varchar(255)"`
78 | AccessVector string `gorm:"type:varchar(255)"`
79 | AccessComplexity string `gorm:"type:varchar(255)"`
80 | Authentication string `gorm:"type:varchar(255)"`
81 | ConfidentialityImpact string `gorm:"type:varchar(255)"`
82 | IntegrityImpact string `gorm:"type:varchar(255)"`
83 | AvailabilityImpact string `gorm:"type:varchar(255)"`
84 | BaseScore float64
85 | Severity string `gorm:"type:varchar(255)"`
86 | }
87 |
88 | // Cvss3 has CVSS Version 3 info
89 | type Cvss3 struct {
90 | VectorString string `gorm:"type:varchar(255)"`
91 | AttackVector string `gorm:"type:varchar(255)"`
92 | AttackComplexity string `gorm:"type:varchar(255)"`
93 | PrivilegesRequired string `gorm:"type:varchar(255)"`
94 | UserInteraction string `gorm:"type:varchar(255)"`
95 | Scope string `gorm:"type:varchar(255)"`
96 | ConfidentialityImpact string `gorm:"type:varchar(255)"`
97 | IntegrityImpact string `gorm:"type:varchar(255)"`
98 | AvailabilityImpact string `gorm:"type:varchar(255)"`
99 | BaseScore float64
100 | BaseSeverity string `gorm:"type:varchar(255)"`
101 | ExploitabilityScore float64
102 | ImpactScore float64
103 | }
104 |
105 | // Cvss40 has CVSS Version 4.0 info
106 | type Cvss40 struct {
107 | VectorString string `gorm:"type:varchar(255)"`
108 | BaseScore float64 `json:"baseScore"`
109 | BaseSeverity string `gorm:"type:varchar(255)"`
110 | ThreatScore *float64
111 | ThreatSeverity *string `gorm:"type:varchar(255)"`
112 | EnvironmentalScore *float64
113 | EnvironmentalSeverity *string `gorm:"type:varchar(255)"`
114 | }
115 |
116 | // CpeBase has common args of Cpe and EnvCpe
117 | type CpeBase struct {
118 | URI string `gorm:"index;type:varchar(255)"`
119 | FormattedString string `gorm:"index;type:varchar(255)"`
120 | WellFormedName string `gorm:"type:text"`
121 | CpeWFN `gorm:"embedded"`
122 | VersionStartExcluding string `gorm:"type:varchar(255)"`
123 | VersionStartIncluding string `gorm:"type:varchar(255)"`
124 | VersionEndExcluding string `gorm:"type:varchar(255)"`
125 | VersionEndIncluding string `gorm:"type:varchar(255)"`
126 | }
127 |
128 | // CpeWFN has CPE Well Formed name information
129 | type CpeWFN struct {
130 | Part string `gorm:"index;type:varchar(255)"`
131 | Vendor string `gorm:"index;type:varchar(255)"`
132 | Product string `gorm:"index;type:varchar(255)"`
133 | Version string `gorm:"type:varchar(255)"`
134 | Update string `gorm:"type:varchar(255)"`
135 | Edition string `gorm:"type:varchar(255)"`
136 | Language string `gorm:"type:varchar(255)"`
137 | SoftwareEdition string `gorm:"type:varchar(255)"`
138 | TargetSW string `gorm:"type:varchar(255)"`
139 | TargetHW string `gorm:"type:varchar(255)"`
140 | Other string `gorm:"type:varchar(255)"`
141 | }
142 |
143 | // Reference holds reference information about the CVE.
144 | type Reference struct {
145 | Link string `gorm:"type:text"`
146 | Source string `gorm:"type:varchar(255)"`
147 | Tags string `gorm:"type:varchar(255)"`
148 | Name string `gorm:"type:text"`
149 | }
150 |
151 | // Cert holds CERT alerts.
152 | type Cert struct {
153 | Title string `gorm:"type:text"`
154 | Link string `gorm:"type:text"`
155 | }
156 |
--------------------------------------------------------------------------------
/models/models_test.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func Test_FetchMeta(t *testing.T) {
8 | var tests = []struct {
9 | in FetchMeta
10 | outdated bool
11 | }{
12 | {
13 | in: FetchMeta{
14 | SchemaVersion: 1,
15 | },
16 | outdated: true,
17 | },
18 | {
19 | in: FetchMeta{
20 | SchemaVersion: LatestSchemaVersion,
21 | },
22 | outdated: false,
23 | },
24 | }
25 |
26 | for i, tt := range tests {
27 | if aout := tt.in.OutDated(); tt.outdated != aout {
28 | t.Errorf("[%d] outdated expected: %#v\n actual: %#v\n", i, tt.outdated, aout)
29 | }
30 | }
31 | }
32 |
33 | func TestCveDetail_HasJvn(t *testing.T) {
34 | type fields struct {
35 | Nvds []Nvd
36 | Jvns []Jvn
37 | }
38 | tests := []struct {
39 | name string
40 | fields fields
41 | want bool
42 | }{
43 | {
44 | name: "Jvn 0-slice",
45 | fields: fields{
46 | Nvds: []Nvd{{CveID: "CVE-2020-0930"}},
47 | Jvns: []Jvn{},
48 | },
49 | want: false,
50 | },
51 | {
52 | name: "Nvd 0-slice",
53 | fields: fields{
54 | Nvds: []Nvd{},
55 | Jvns: []Jvn{{CveID: "CVE-2020-0930"}},
56 | },
57 | want: true,
58 | },
59 | {
60 | name: "Jvn nil",
61 | fields: fields{
62 | Nvds: []Nvd{{CveID: "CVE-2020-0930"}},
63 | Jvns: nil,
64 | },
65 | want: false,
66 | },
67 | {
68 | name: "Nvd nil",
69 | fields: fields{
70 | Nvds: nil,
71 | Jvns: []Jvn{{CveID: "CVE-2020-0930"}},
72 | },
73 | want: true,
74 | },
75 | }
76 | for _, tt := range tests {
77 | t.Run(tt.name, func(t *testing.T) {
78 | c := CveDetail{
79 | Nvds: tt.fields.Nvds,
80 | Jvns: tt.fields.Jvns,
81 | }
82 | if got := c.HasJvn(); got != tt.want {
83 | t.Errorf("CveDetail.hasJvn() = %v, want %v", got, tt.want)
84 | }
85 | })
86 | }
87 | }
88 |
89 | func TestCveDetail_HasNvd(t *testing.T) {
90 | type fields struct {
91 | Nvds []Nvd
92 | Jvns []Jvn
93 | }
94 | tests := []struct {
95 | name string
96 | fields fields
97 | want bool
98 | }{
99 | {
100 | name: "Jvn 0-slice",
101 | fields: fields{
102 | Nvds: []Nvd{{CveID: "CVE-2020-0930"}},
103 | Jvns: []Jvn{},
104 | },
105 | want: true,
106 | },
107 | {
108 | name: "Nvd 0-slice",
109 | fields: fields{
110 | Nvds: []Nvd{},
111 | Jvns: []Jvn{{CveID: "CVE-2020-0930"}},
112 | },
113 | want: false,
114 | },
115 | {
116 | name: "Jvn nil",
117 | fields: fields{
118 | Nvds: []Nvd{{CveID: "CVE-2020-0930"}},
119 | Jvns: nil,
120 | },
121 | want: true,
122 | },
123 | {
124 | name: "Nvd nil",
125 | fields: fields{
126 | Nvds: nil,
127 | Jvns: []Jvn{{CveID: "CVE-2020-0930"}},
128 | },
129 | want: false,
130 | },
131 | }
132 | for _, tt := range tests {
133 | t.Run(tt.name, func(t *testing.T) {
134 | c := CveDetail{
135 | Nvds: tt.fields.Nvds,
136 | Jvns: tt.fields.Jvns,
137 | }
138 | if got := c.HasNvd(); got != tt.want {
139 | t.Errorf("CveDetail.hasNvd() = %v, want %v", got, tt.want)
140 | }
141 | })
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/models/nvd.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import "time"
4 |
5 | const (
6 | // NvdType :
7 | NvdType = "NVD"
8 |
9 | // NvdExactVersionMatch :
10 | NvdExactVersionMatch = "NvdExactVersionMatch"
11 | // NvdRoughVersionMatch :
12 | NvdRoughVersionMatch = "NvdRoughVersionMatch"
13 | // NvdVendorProductMatch :
14 | NvdVendorProductMatch = "NvdVendorProductMatch"
15 | )
16 |
17 | // Nvd is a struct of NVD JSON
18 | // https://scap.nist.gov/schema/nvd/feed/0.1/nvd_cve_feed_json_0.1_beta.schema
19 | type Nvd struct {
20 | ID int64 `json:"-"`
21 | CveID string `gorm:"index:idx_nvds_cveid;type:varchar(255)"`
22 | Descriptions []NvdDescription
23 | Cvss2 []NvdCvss2Extra
24 | Cvss3 []NvdCvss3
25 | Cvss40 []NvdCvss40
26 | Cwes []NvdCwe
27 | Cpes []NvdCpe
28 | References []NvdReference
29 | Certs []NvdCert
30 | PublishedDate time.Time
31 | LastModifiedDate time.Time
32 |
33 | DetectionMethod string `gorm:"-"`
34 | }
35 |
36 | // NvdDescription has description of the CVE
37 | type NvdDescription struct {
38 | ID int64 `json:"-"`
39 | NvdID uint `json:"-" gorm:"index:idx_nvd_descriptions_nvd_id"`
40 | Lang string `gorm:"type:varchar(255)"`
41 | Value string `gorm:"type:text"`
42 | }
43 |
44 | // NvdCvss2Extra has Nvd extra CVSS V2 info
45 | type NvdCvss2Extra struct {
46 | ID int64 `json:"-"`
47 | NvdID uint `json:"-" gorm:"index:idx_nvd_cvss2_extra_nvd_id"`
48 | Source string `gorm:"type:text"`
49 | Type string `gorm:"type:varchar(255)"`
50 | Cvss2 `gorm:"embedded"`
51 | ExploitabilityScore float64
52 | ImpactScore float64
53 | ObtainAllPrivilege bool
54 | ObtainUserPrivilege bool
55 | ObtainOtherPrivilege bool
56 | UserInteractionRequired bool
57 | }
58 |
59 | // NvdCvss3 has Nvd CVSS3 info
60 | type NvdCvss3 struct {
61 | ID int64 `json:"-"`
62 | NvdID uint `json:"-" gorm:"index:idx_nvd_cvss3_nvd_id"`
63 | Source string `gorm:"type:text"`
64 | Type string `gorm:"type:varchar(255)"`
65 | Cvss3 `gorm:"embedded"`
66 | }
67 |
68 | // NvdCvss40 has Nvd CVSS40 info
69 | type NvdCvss40 struct {
70 | ID int64 `json:"-"`
71 | NvdID uint `json:"-" gorm:"index:idx_nvd_cvss40_nvd_id"`
72 | Source string `gorm:"type:text"`
73 | Type string `gorm:"type:varchar(255)"`
74 | Cvss40 `gorm:"embedded"`
75 | }
76 |
77 | // NvdCwe has CweID
78 | type NvdCwe struct {
79 | ID int64 `json:"-"`
80 | NvdID uint `json:"-" gorm:"index:idx_nvd_cwes_nvd_id"`
81 | Source string `gorm:"type:text"`
82 | Type string `gorm:"type:varchar(255)"`
83 | CweID string `gorm:"type:varchar(255)"`
84 | }
85 |
86 | // NvdCpe is Child model of Nvd.
87 | // see https://www.ipa.go.jp/security/vuln/CPE.html
88 | // In NVD,
89 | // configurations>nodes>cpe>vulnerable: true
90 | type NvdCpe struct {
91 | ID int64 `json:"-"`
92 | NvdID uint `json:"-" gorm:"index:idx_nvd_cpes_nvd_id"`
93 | CpeBase `gorm:"embedded"`
94 | EnvCpes []NvdEnvCpe
95 | }
96 |
97 | // NvdEnvCpe is a Environmental CPE
98 | // Only NVD has this information.
99 | // configurations>nodes>cpe>vulnerable: false
100 | type NvdEnvCpe struct {
101 | ID int64 `json:"-"`
102 | NvdCpeID uint `json:"-" gorm:"index:idx_nvd_env_cpes_nvd_cpe_id"`
103 | CpeBase `gorm:"embedded"`
104 | }
105 |
106 | // NvdReference holds reference information about the CVE.
107 | type NvdReference struct {
108 | ID int64 `json:"-"`
109 | NvdID uint `json:"-" gorm:"index:idx_nvd_references_nvd_id"`
110 | Reference `gorm:"embedded"`
111 | }
112 |
113 | // NvdCert is Child model of Nvd.
114 | type NvdCert struct {
115 | ID int64 `json:"-"`
116 | NvdID uint `json:"-" gorm:"index:idx_nvd_certs_nvd_id"`
117 | Cert `gorm:"embedded"`
118 | }
119 |
--------------------------------------------------------------------------------
/models/paloalto.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import "time"
4 |
5 | const (
6 | // PaloaltoType :
7 | PaloaltoType = "Paloalto"
8 |
9 | // PaloaltoExactVersionMatch :
10 | PaloaltoExactVersionMatch = "PaloaltoExactVersionMatch"
11 | // PaloaltoRoughVersionMatch :
12 | PaloaltoRoughVersionMatch = "PaloaltoRoughVersionMatch"
13 | // PaloaltoVendorProductMatch :
14 | PaloaltoVendorProductMatch = "PaloaltoVendorProductMatch"
15 | )
16 |
17 | // Paloalto is a model of Paloalto
18 | type Paloalto struct {
19 | ID int64 `json:"-"`
20 | AdvisoryID string `gorm:"type:varchar(255)"`
21 | CveID string `gorm:"index:idx_paloaltos_cveid;type:varchar(255)"`
22 | Title string `gorm:"type:varchar(256)"`
23 | Descriptions []PaloaltoDescription
24 | Affected []PaloaltoProduct
25 | ProblemTypes []PaloaltoProblemType
26 | Impacts []PaloaltoImpact
27 | CVSSv3 []PaloaltoCVSS3
28 | CVSSv40 []PaloaltoCVSS40
29 | Workarounds []PaloaltoWorkaround
30 | Solutions []PaloaltoSolution
31 | Exploits []PaloaltoExploit
32 | Configurations []PaloaltoConfiguration
33 | References []PaloaltoReference
34 | DatePublic *time.Time
35 |
36 | DetectionMethod string `gorm:"-"`
37 | }
38 |
39 | // PaloaltoDescription :
40 | type PaloaltoDescription struct {
41 | ID int64 `json:"-"`
42 | PaloaltoID uint `json:"-" gorm:"index:idx_paloalto_descriptions_paloalto_id"`
43 | Description string `gorm:"type:text"`
44 | }
45 |
46 | // PaloaltoProduct :
47 | type PaloaltoProduct struct {
48 | ID int64 `json:"-"`
49 | PaloaltoID uint `json:"-" gorm:"index:idx_paloalto_affected_paloalto_id"`
50 | CpeBase `gorm:"embedded"`
51 | }
52 |
53 | // PaloaltoProblemType :
54 | type PaloaltoProblemType struct {
55 | ID int64 `json:"-"`
56 | PaloaltoID uint `json:"-" gorm:"index:idx_paloalto_problem_types_paloalto_id"`
57 | CweID string `gorm:"type:varchar(255)"`
58 | }
59 |
60 | // PaloaltoImpact :
61 | type PaloaltoImpact struct {
62 | ID int64 `json:"-"`
63 | PaloaltoID uint `json:"-" gorm:"index:idx_paloalto_impacts_paloalto_id"`
64 | CapecID string `gorm:"type:varchar(11)"`
65 | }
66 |
67 | // PaloaltoCVSS3 :
68 | type PaloaltoCVSS3 struct {
69 | ID int64 `json:"-"`
70 | PaloaltoID uint `json:"-" gorm:"index:idx_paloalto_cvss3_paloalto_id"`
71 | Cvss3 `gorm:"embedded"`
72 | }
73 |
74 | // PaloaltoCVSS40 :
75 | type PaloaltoCVSS40 struct {
76 | ID int64 `json:"-"`
77 | PaloaltoID uint `json:"-" gorm:"index:idx_paloalto_cvss40_paloalto_id"`
78 | Cvss40 `gorm:"embedded"`
79 | }
80 |
81 | // PaloaltoWorkaround :
82 | type PaloaltoWorkaround struct {
83 | ID int64 `json:"-"`
84 | PaloaltoID uint `json:"-" gorm:"index:idx_paloalto_workarounds_paloalto_id"`
85 | Workaround string `gorm:"type:text"`
86 | }
87 |
88 | // PaloaltoSolution :
89 | type PaloaltoSolution struct {
90 | ID int64 `json:"-"`
91 | PaloaltoID uint `json:"-" gorm:"index:idx_paloalto_solutions_paloalto_id"`
92 | Solution string `gorm:"type:text"`
93 | }
94 |
95 | // PaloaltoExploit :
96 | type PaloaltoExploit struct {
97 | ID int64 `json:"-"`
98 | PaloaltoID uint `json:"-" gorm:"index:idx_paloalto_exploits_paloalto_id"`
99 | Exploit string `gorm:"type:text"`
100 | }
101 |
102 | // PaloaltoConfiguration :
103 | type PaloaltoConfiguration struct {
104 | ID int64 `json:"-"`
105 | PaloaltoID uint `json:"-" gorm:"index:idx_paloalto_configurations_paloalto_id"`
106 | Configuration string `gorm:"type:text"`
107 | }
108 |
109 | // PaloaltoReference :
110 | type PaloaltoReference struct {
111 | ID int64 `json:"-"`
112 | PaloaltoID uint `json:"-" gorm:"index:idx_paloalto_references_paloalto_id"`
113 | Reference `gorm:"embedded"`
114 | }
115 |
--------------------------------------------------------------------------------
/server/server.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "os"
7 | "path/filepath"
8 | "sort"
9 |
10 | "github.com/labstack/echo/v4"
11 | "github.com/labstack/echo/v4/middleware"
12 | "github.com/spf13/viper"
13 | "golang.org/x/xerrors"
14 |
15 | "github.com/vulsio/go-cve-dictionary/db"
16 | "github.com/vulsio/go-cve-dictionary/log"
17 | )
18 |
19 | // Start starts CVE dictionary HTTP Server.
20 | func Start(logToFile bool, logDir string, driver db.DB) error {
21 | e := echo.New()
22 | e.Debug = viper.GetBool("debug")
23 |
24 | // Middleware
25 | e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{Output: os.Stderr}))
26 | e.Use(middleware.Recover())
27 |
28 | // setup access logger
29 | if logToFile {
30 | logPath := filepath.Join(logDir, "access.log")
31 | f, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
32 | if err != nil {
33 | return xerrors.Errorf("Failed to open a log file: %s", err)
34 | }
35 | defer f.Close()
36 | e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{Output: f}))
37 | }
38 |
39 | // Routes
40 | e.GET("/health", health())
41 | e.GET("/cves/:id", getCve(driver))
42 | e.POST("/cves", getCveMulti(driver))
43 | e.GET("/cves/ids", getCveIDs(driver))
44 | e.GET("/advisories/jvn", getAdvisoriesJvn(driver))
45 | e.GET("/advisories/fortinet", getAdvisoriesFortinet(driver))
46 | e.GET("/advisories/paloalto", getAdvisoriesPaloalto(driver))
47 | e.GET("/advisories/cisco", getAdvisoriesCisco(driver))
48 | e.POST("/cpes", getCveByCpeName(driver))
49 | e.POST("/cpes/ids", getCveIDsByCpeName(driver))
50 |
51 | bindURL := fmt.Sprintf("%s:%s", viper.GetString("bind"), viper.GetString("port"))
52 | log.Infof("Listening on %s", bindURL)
53 |
54 | return e.Start(bindURL)
55 | }
56 |
57 | // Handler
58 | func health() echo.HandlerFunc {
59 | return func(c echo.Context) error {
60 | return c.String(http.StatusOK, "")
61 | }
62 | }
63 |
64 | // Handler
65 | func getCve(driver db.DB) echo.HandlerFunc {
66 | return func(c echo.Context) error {
67 | cveid := c.Param("id")
68 | cveDetail, err := driver.Get(cveid)
69 | if err != nil {
70 | log.Errorf("%s", err)
71 | return err
72 | }
73 | return c.JSON(http.StatusOK, &cveDetail)
74 | }
75 | }
76 |
77 | func getCveMulti(driver db.DB) echo.HandlerFunc {
78 | return func(c echo.Context) error {
79 | var cveIDs []string
80 | if err := c.Bind(&cveIDs); err != nil {
81 | log.Errorf("%s", err)
82 | return err
83 | }
84 |
85 | cveDetails, err := driver.GetMulti(cveIDs)
86 | if err != nil {
87 | log.Errorf("%s", err)
88 | return err
89 | }
90 | return c.JSON(http.StatusOK, &cveDetails)
91 | }
92 | }
93 |
94 | type cpeName struct {
95 | Name string `form:"name"`
96 | }
97 |
98 | func getCveByCpeName(driver db.DB) echo.HandlerFunc {
99 | return func(c echo.Context) error {
100 | cpe := cpeName{}
101 | err := c.Bind(&cpe)
102 | if err != nil {
103 | log.Errorf("%s", err)
104 | return err
105 | }
106 | cveDetails, err := driver.GetByCpeURI(cpe.Name)
107 | if err != nil {
108 | log.Errorf("%s", err)
109 | return err
110 | }
111 |
112 | sort.Slice(cveDetails, func(i, j int) bool {
113 | return cveDetails[i].CveID < cveDetails[j].CveID
114 | })
115 | return c.JSON(http.StatusOK, &cveDetails)
116 | }
117 | }
118 |
119 | func getCveIDs(driver db.DB) echo.HandlerFunc {
120 | return func(c echo.Context) error {
121 | cveIDs, err := driver.GetCveIDs()
122 | if err != nil {
123 | log.Errorf("%s", err)
124 | return err
125 | }
126 | return c.JSON(http.StatusOK, &cveIDs)
127 | }
128 | }
129 |
130 | func getCveIDsByCpeName(driver db.DB) echo.HandlerFunc {
131 | return func(c echo.Context) error {
132 | cpe := cpeName{}
133 | err := c.Bind(&cpe)
134 | if err != nil {
135 | log.Errorf("%s", err)
136 | return err
137 | }
138 | cveids, err := driver.GetCveIDsByCpeURI(cpe.Name)
139 | if err != nil {
140 | log.Errorf("%s", err)
141 | return err
142 | }
143 | cveIDs := map[string][]string{"NVD": cveids.Nvd, "JVN": cveids.Jvn, "Fortinet": cveids.Fortinet, "Paloalto": cveids.Paloalto, "Cisco": cveids.Cisco}
144 | return c.JSON(http.StatusOK, &cveIDs)
145 | }
146 | }
147 |
148 | func getAdvisoriesJvn(driver db.DB) echo.HandlerFunc {
149 | return func(c echo.Context) error {
150 | m, err := driver.GetAdvisoriesJvn()
151 | if err != nil {
152 | log.Errorf("%s", err)
153 | return err
154 | }
155 | return c.JSON(http.StatusOK, &m)
156 | }
157 | }
158 |
159 | func getAdvisoriesFortinet(driver db.DB) echo.HandlerFunc {
160 | return func(c echo.Context) error {
161 | m, err := driver.GetAdvisoriesFortinet()
162 | if err != nil {
163 | log.Errorf("%s", err)
164 | return err
165 | }
166 | return c.JSON(http.StatusOK, &m)
167 | }
168 | }
169 |
170 | func getAdvisoriesPaloalto(driver db.DB) echo.HandlerFunc {
171 | return func(c echo.Context) error {
172 | m, err := driver.GetAdvisoriesPaloalto()
173 | if err != nil {
174 | log.Errorf("%s", err)
175 | return err
176 | }
177 | return c.JSON(http.StatusOK, &m)
178 | }
179 | }
180 |
181 | func getAdvisoriesCisco(driver db.DB) echo.HandlerFunc {
182 | return func(c echo.Context) error {
183 | m, err := driver.GetAdvisoriesCisco()
184 | if err != nil {
185 | log.Errorf("%s", err)
186 | return err
187 | }
188 | return c.JSON(http.StatusOK, &m)
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/util/util.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 | )
7 |
8 | // GenWorkers generate workers
9 | func GenWorkers(num int) chan<- func() {
10 | tasks := make(chan func())
11 | for i := 0; i < num; i++ {
12 | go func() {
13 | for f := range tasks {
14 | f()
15 | }
16 | }()
17 | }
18 | return tasks
19 | }
20 |
21 | // CacheDir return go-cve-dictionary cache directory path
22 | func CacheDir() string {
23 | cacheDir, err := os.UserCacheDir()
24 | if err != nil {
25 | cacheDir = os.TempDir()
26 | }
27 | dir := filepath.Join(cacheDir, "go-cve-dictionary")
28 | return dir
29 | }
30 |
--------------------------------------------------------------------------------