├── .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\n

For more information about these vulnerabilities, see the Details section of this advisory. 

\r\n

Cisco 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\n

This 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\n

For more information about these vulnerabilities, see the Details section of this advisory. 

\r\n

Cisco 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\n

This 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\n

For more information about these vulnerabilities, see the Details section of this advisory. 

\r\n

Cisco 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\n

This 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 | --------------------------------------------------------------------------------