├── .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 ├── 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 ├── 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 ├── 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 | - package-ecosystem: "github-actions" # See documentation for possible values 14 | directory: "/" # Location of package manifests 15 | schedule: 16 | interval: "weekly" 17 | target-branch: "master" 18 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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@v7 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: v2.0.2 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 | staticcheck: # https://golangci-lint.run/usage/linters/#staticcheck 42 | checks: 43 | - all 44 | - -ST1000 # at least one file in a package should have a package comment 45 | - -ST1005 # error strings should not be capitalized 46 | exclusions: 47 | rules: 48 | - source: "defer .+\\.Close\\(\\)" 49 | linters: 50 | - errcheck 51 | - source: "defer os.RemoveAll\\(.+\\)" 52 | linters: 53 | - errcheck 54 | 55 | formatters: 56 | enable: 57 | - goimports 58 | 59 | run: 60 | timeout: 10m 61 | -------------------------------------------------------------------------------- /.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.21 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/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"}, 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 | if count == 0 { 105 | log.Infof("No Vulnerability data found. Run the below command to fetch data from NVD, JVN, Fortinet, MITRE, Paloalto") 106 | log.Infof("") 107 | log.Infof(" go-cve-dictionary fetch nvd") 108 | log.Infof(" go-cve-dictionary fetch jvn") 109 | log.Infof(" go-cve-dictionary fetch fortinet") 110 | log.Infof(" go-cve-dictionary fetch mitre") 111 | log.Infof(" go-cve-dictionary fetch paloalto") 112 | log.Infof("") 113 | return nil 114 | } 115 | 116 | enc := json.NewEncoder(os.Stdout) 117 | enc.SetIndent("", " ") 118 | switch len(args) { 119 | case 0: 120 | cveids, err := driver.GetCveIDs() 121 | if err != nil { 122 | return xerrors.Errorf("Failed to get All CVEIDs. err: %w", err) 123 | } 124 | if err := enc.Encode(cveids); err != nil { 125 | return xerrors.Errorf("Failed to encode All CVEIDs. err: %w", err) 126 | } 127 | case 1: 128 | d, err := driver.Get(args[0]) 129 | if err != nil { 130 | return xerrors.Errorf("Failed to get CVEDetail by CVEID. err: %w", err) 131 | } 132 | if err := enc.Encode(d); err != nil { 133 | return xerrors.Errorf("Failed to encode CVEDetail by CVEID. err: %w", err) 134 | } 135 | default: 136 | ds, err := driver.GetMulti(args) 137 | if err != nil { 138 | return xerrors.Errorf("Failed to get CVEDetails by CVEIDs. err: %w", err) 139 | } 140 | if err := enc.Encode(ds); err != nil { 141 | return xerrors.Errorf("Failed to encode CVEDetails by CVEIDs. err: %w", err) 142 | } 143 | } 144 | 145 | return nil 146 | } 147 | 148 | func searchAdvisories(_ *cobra.Command, args []string) error { 149 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil { 150 | return xerrors.Errorf("Failed to SetLogger. err: %w", err) 151 | } 152 | 153 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{}) 154 | if err != nil { 155 | if errors.Is(err, db.ErrDBLocked) { 156 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err) 157 | } 158 | return xerrors.Errorf("Failed to open DB. err: %w", err) 159 | } 160 | 161 | fetchMeta, err := driver.GetFetchMeta() 162 | if err != nil { 163 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err) 164 | } 165 | if fetchMeta.OutDated() { 166 | return xerrors.Errorf("Failed to start server. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion}) 167 | } 168 | 169 | count := 0 170 | jvnCount, err := driver.CountJvn() 171 | if err != nil { 172 | log.Errorf("Failed to count JVN table: %s", err) 173 | return err 174 | } 175 | count += jvnCount 176 | 177 | fortinetCount, err := driver.CountFortinet() 178 | if err != nil { 179 | log.Errorf("Failed to count Fortinet table: %s", err) 180 | return err 181 | } 182 | count += fortinetCount 183 | 184 | paloaltoCount, err := driver.CountPaloalto() 185 | if err != nil { 186 | log.Errorf("Failed to count Paloalto table: %s", err) 187 | return err 188 | } 189 | count += paloaltoCount 190 | 191 | if count == 0 { 192 | log.Infof("No Advisory data found. Run the below command to fetch data from JVN, Fortinet, Paloalto") 193 | log.Infof("") 194 | log.Infof(" go-cve-dictionary fetch jvn") 195 | log.Infof(" go-cve-dictionary fetch fortinet") 196 | log.Infof(" go-cve-dictionary fetch paloalto") 197 | log.Infof("") 198 | return nil 199 | } 200 | 201 | enc := json.NewEncoder(os.Stdout) 202 | enc.SetIndent("", " ") 203 | switch args[0] { 204 | case "jvn": 205 | m, err := driver.GetAdvisoriesJvn() 206 | if err != nil { 207 | return xerrors.Errorf("Failed to Get JVN Advisories. err: %w", err) 208 | } 209 | if err := enc.Encode(m); err != nil { 210 | return xerrors.Errorf("Failed to encode All Advisories. err: %w", err) 211 | } 212 | case "fortinet": 213 | m, err := driver.GetAdvisoriesFortinet() 214 | if err != nil { 215 | return xerrors.Errorf("Failed to Get Fortinet Advisories. err: %w", err) 216 | } 217 | if err := enc.Encode(m); err != nil { 218 | return xerrors.Errorf("Failed to encode All Advisories. err: %w", err) 219 | } 220 | case "paloalto": 221 | m, err := driver.GetAdvisoriesPaloalto() 222 | if err != nil { 223 | return xerrors.Errorf("Failed to Get Paloalto 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 | default: 229 | return xerrors.Errorf("not support type: %s", args[0]) 230 | } 231 | 232 | return nil 233 | } 234 | 235 | func searchCPE(_ *cobra.Command, args []string) error { 236 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil { 237 | return xerrors.Errorf("Failed to SetLogger. err: %w", err) 238 | } 239 | 240 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{}) 241 | if err != nil { 242 | if errors.Is(err, db.ErrDBLocked) { 243 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err) 244 | } 245 | return xerrors.Errorf("Failed to open DB. err: %w", err) 246 | } 247 | 248 | fetchMeta, err := driver.GetFetchMeta() 249 | if err != nil { 250 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err) 251 | } 252 | if fetchMeta.OutDated() { 253 | return xerrors.Errorf("Failed to start server. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion}) 254 | } 255 | 256 | count := 0 257 | nvdCount, err := driver.CountNvd() 258 | if err != nil { 259 | log.Errorf("Failed to count NVD table: %s", err) 260 | return err 261 | } 262 | count += nvdCount 263 | 264 | jvnCount, err := driver.CountJvn() 265 | if err != nil { 266 | log.Errorf("Failed to count JVN table: %s", err) 267 | return err 268 | } 269 | count += jvnCount 270 | 271 | fortinetCount, err := driver.CountFortinet() 272 | if err != nil { 273 | log.Errorf("Failed to count Fortinet table: %s", err) 274 | return err 275 | } 276 | count += fortinetCount 277 | 278 | mitreCount, err := driver.CountMitre() 279 | if err != nil { 280 | log.Errorf("Failed to count MITRE table: %s", err) 281 | return err 282 | } 283 | count += mitreCount 284 | 285 | paloaltoCount, err := driver.CountPaloalto() 286 | if err != nil { 287 | log.Errorf("Failed to count Paloalto table: %s", err) 288 | return err 289 | } 290 | count += paloaltoCount 291 | 292 | if count == 0 { 293 | log.Infof("No Vulnerability data found. Run the below command to fetch data from NVD, JVN, Fortinet, MITRE, Paloalto") 294 | log.Infof("") 295 | log.Infof(" go-cve-dictionary fetch nvd") 296 | log.Infof(" go-cve-dictionary fetch jvn") 297 | log.Infof(" go-cve-dictionary fetch fortinet") 298 | log.Infof(" go-cve-dictionary fetch mitre") 299 | log.Infof(" go-cve-dictionary fetch paloalto") 300 | log.Infof("") 301 | return nil 302 | } 303 | 304 | enc := json.NewEncoder(os.Stdout) 305 | enc.SetIndent("", " ") 306 | if viper.GetBool("cveid-only") { 307 | cveids, err := driver.GetCveIDsByCpeURI(args[0]) 308 | if err != nil { 309 | return xerrors.Errorf("Failed to Get CVEIDs by CPE URI. err: %w", err) 310 | } 311 | if err := enc.Encode(map[string][]string{"NVD": cveids.Nvd, "JVN": cveids.Jvn, "Fortinet": cveids.Fortinet, "Paloalto": cveids.Paloalto}); err != nil { 312 | return xerrors.Errorf("Failed to encode CVEIDs by CPE URI. err: %w", err) 313 | } 314 | return nil 315 | } 316 | cveDetails, err := driver.GetByCpeURI(args[0]) 317 | if err != nil { 318 | return xerrors.Errorf("Failed to Get CVEDetails by CPE URI. err: %w", err) 319 | } 320 | if err := enc.Encode(cveDetails); err != nil { 321 | return xerrors.Errorf("Failed to encode CVEDetails by CPE URI. err: %w", err) 322 | } 323 | 324 | return nil 325 | } 326 | 327 | func init() { 328 | RootCmd.AddCommand(searchCmd) 329 | searchCmd.AddCommand(searchCVECmd, searchAdvisoryCmd, searchCPECmd) 330 | 331 | searchCPECmd.PersistentFlags().Bool("cveid-only", false, "show only CVEID in search results") 332 | _ = viper.BindPFlag("cveid-only", searchCPECmd.PersistentFlags().Lookup("cveid-only")) 333 | } 334 | -------------------------------------------------------------------------------- /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 | if count == 0 { 92 | log.Infof("No Vulnerability data found. Run the below command to fetch data from NVD, JVN, Fortinet, MITRE, Paloalto") 93 | log.Infof("") 94 | log.Infof(" go-cve-dictionary fetch nvd") 95 | log.Infof(" go-cve-dictionary fetch jvn") 96 | log.Infof(" go-cve-dictionary fetch fortinet") 97 | log.Infof(" go-cve-dictionary fetch mitre") 98 | log.Infof(" go-cve-dictionary fetch paloalto") 99 | log.Infof("") 100 | return nil 101 | } 102 | 103 | log.Infof("Starting HTTP Server...") 104 | if err = server.Start(viper.GetBool("log-to-file"), viper.GetString("log-dir"), driver); err != nil { 105 | return xerrors.Errorf("Failed to start server. err: %w", err) 106 | } 107 | return nil 108 | } 109 | -------------------------------------------------------------------------------- /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 | 9 | // Latest is the fetch option of fetching modified NVD 10 | const Latest = -1 11 | -------------------------------------------------------------------------------- /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/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-paloalto-version v0.0.0-20250522233912-78724c69edda 7 | github.com/PuerkitoBio/goquery v1.10.2 8 | github.com/cenkalti/backoff v2.2.1+incompatible 9 | github.com/cheggaaa/pb/v3 v3.1.7 10 | github.com/glebarez/sqlite v1.11.0 11 | github.com/go-redis/redis/v8 v8.11.5 12 | github.com/google/go-cmp v0.6.0 13 | github.com/hashicorp/go-version v1.7.0 14 | github.com/inconshreveable/log15 v3.0.0-testing.5+incompatible 15 | github.com/klauspost/compress v1.18.0 16 | github.com/knqyf263/go-cpe v0.0.0-20201213041631-54f6ab28673f 17 | github.com/knqyf263/go-rpm-version v0.0.0-20170716094938-74609b86c936 18 | github.com/labstack/echo/v4 v4.13.3 19 | github.com/mitchellh/go-homedir v1.1.0 20 | github.com/opencontainers/image-spec v1.1.1 21 | github.com/spf13/cobra v1.9.1 22 | github.com/spf13/viper v1.20.0 23 | golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 24 | gorm.io/driver/mysql v1.5.5 25 | gorm.io/driver/postgres v1.5.7 26 | gorm.io/gorm v1.25.7 27 | oras.land/oras-go/v2 v2.5.0 28 | ) 29 | 30 | require ( 31 | github.com/VividCortex/ewma v1.2.0 // indirect 32 | github.com/andybalholm/cascadia v1.3.3 // indirect 33 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 34 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 35 | github.com/dustin/go-humanize v1.0.1 // indirect 36 | github.com/fatih/color v1.18.0 // indirect 37 | github.com/fsnotify/fsnotify v1.8.0 // indirect 38 | github.com/glebarez/go-sqlite v1.21.2 // indirect 39 | github.com/go-sql-driver/mysql v1.7.1 // indirect 40 | github.com/go-stack/stack v1.8.0 // indirect 41 | github.com/go-viper/mapstructure/v2 v2.2.1 // indirect 42 | github.com/google/uuid v1.6.0 // indirect 43 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 44 | github.com/jackc/pgpassfile v1.0.0 // indirect 45 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect 46 | github.com/jackc/pgx/v5 v5.5.4 // indirect 47 | github.com/jackc/puddle/v2 v2.2.1 // indirect 48 | github.com/jinzhu/inflection v1.0.0 // indirect 49 | github.com/jinzhu/now v1.1.5 // indirect 50 | github.com/labstack/gommon v0.4.2 // indirect 51 | github.com/mattn/go-colorable v0.1.14 // indirect 52 | github.com/mattn/go-isatty v0.0.20 // indirect 53 | github.com/mattn/go-runewidth v0.0.16 // indirect 54 | github.com/opencontainers/go-digest v1.0.0 // indirect 55 | github.com/pelletier/go-toml/v2 v2.2.3 // indirect 56 | github.com/pkg/errors v0.9.1 // indirect 57 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect 58 | github.com/rivo/uniseg v0.4.7 // indirect 59 | github.com/sagikazarmark/locafero v0.7.0 // indirect 60 | github.com/sourcegraph/conc v0.3.0 // indirect 61 | github.com/spf13/afero v1.12.0 // indirect 62 | github.com/spf13/cast v1.7.1 // indirect 63 | github.com/spf13/pflag v1.0.6 // indirect 64 | github.com/subosito/gotenv v1.6.0 // indirect 65 | github.com/valyala/bytebufferpool v1.0.0 // indirect 66 | github.com/valyala/fasttemplate v1.2.2 // indirect 67 | go.uber.org/atomic v1.9.0 // indirect 68 | go.uber.org/multierr v1.9.0 // indirect 69 | golang.org/x/crypto v0.35.0 // indirect 70 | golang.org/x/net v0.36.0 // indirect 71 | golang.org/x/sync v0.11.0 // indirect 72 | golang.org/x/sys v0.30.0 // indirect 73 | golang.org/x/term v0.29.0 // indirect 74 | golang.org/x/text v0.22.0 // indirect 75 | golang.org/x/time v0.8.0 // indirect 76 | gopkg.in/yaml.v3 v3.0.1 // indirect 77 | modernc.org/libc v1.22.5 // indirect 78 | modernc.org/mathutil v1.5.0 // indirect 79 | modernc.org/memory v1.5.0 // indirect 80 | modernc.org/sqlite v1.23.1 // indirect 81 | ) 82 | -------------------------------------------------------------------------------- /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.0 -------------------------------------------------------------------------------- /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/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/mitre.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "time" 4 | 5 | const ( 6 | // MitreType : 7 | MitreType = "Mitre" 8 | ) 9 | 10 | // Mitre : https://cveproject.github.io/cve-schema/schema/CVE_Record_Format.json 11 | type Mitre struct { 12 | ID int64 `json:"-"` 13 | DataType string `gorm:"type:varchar(255)"` 14 | DataVersion string `gorm:"type:varchar(255)"` 15 | CVEMetadata MitreCVEMetadata 16 | Containers []MitreContainer 17 | } 18 | 19 | // MitreCVEMetadata : #/definitions/cveMetadataPublished 20 | type MitreCVEMetadata struct { 21 | ID int64 `json:"-"` 22 | MitreID uint `json:"-" gorm:"index:idx_mitre_cve_metadata"` 23 | CVEID string `gorm:"index:idx_mitre_cve_metadata_cveid;type:varchar(255)"` 24 | AssignerOrgID string `gorm:"type:varchar(255)"` 25 | AssignerShortName *string `gorm:"type:varchar(32)"` 26 | RequesterUserID *string `gorm:"type:varchar(255)"` 27 | Serial *int 28 | State string `gorm:"type:varchar(255)"` 29 | DatePublished *time.Time 30 | DateUpdated *time.Time 31 | DateReserved *time.Time 32 | DateRejected *time.Time 33 | } 34 | 35 | // MitreContainer : #/definitions/cnaPublishedContainer, #/definitions/adpContainer 36 | type MitreContainer struct { 37 | ID int64 `json:"-"` 38 | MitreID uint `json:"-" gorm:"index:idx_mitre_containers"` 39 | ContainerType string `gorm:"type:varchar(255)"` 40 | ProviderMetadata MitreProviderMetadata 41 | Title *string `gorm:"type:varchar(256)"` 42 | Descriptions []MitreDescription 43 | Affected []MitreProduct 44 | ProblemTypes []MitreProblemType 45 | Impacts []MitreImpact 46 | Metrics []MitreMetric 47 | Workarounds []MitreWorkaround 48 | Solutions []MitreSolution 49 | Exploits []MitreExploit 50 | Configurations []MitreConfiguration 51 | References []MitreReference 52 | Timeline []MitreTimeline 53 | Credits []MitreCredit 54 | Source string `gorm:"type:text"` 55 | Tags []MitreTag 56 | TaxonomyMappings []MitreTaxonomyMapping 57 | DateAssigned *time.Time 58 | DatePublic *time.Time 59 | } 60 | 61 | // MitreProviderMetadata : #/definitions/providerMetadata 62 | type MitreProviderMetadata struct { 63 | ID int64 `json:"-"` 64 | MitreContainerID uint `json:"-" gorm:"index:idx_mitre_provider_metadata"` 65 | OrgID string `gorm:"type:varchar(255)"` 66 | ShortName *string `gorm:"type:varchar(32)"` 67 | DateUpdated *time.Time 68 | } 69 | 70 | // MitreDescription : #/definitions/description 71 | type MitreDescription struct { 72 | ID int64 `json:"-"` 73 | MitreContainerID uint `json:"-" gorm:"index:idx_mitre_description"` 74 | Lang string `gorm:"type:varchar(255)"` 75 | Value string `gorm:"type:text"` 76 | SupportingMedia []MitreDescriptionSupportingMedia 77 | } 78 | 79 | // MitreDescriptionSupportingMedia : #/definitions/description 80 | type MitreDescriptionSupportingMedia struct { 81 | ID int64 `json:"-"` 82 | MitreDescriptionID uint `json:"-" gorm:"index:idx_mitre_description_supporting_media"` 83 | Type string `gorm:"type:varchar(256)"` 84 | Base64 *bool 85 | Value string `gorm:"type:text"` 86 | } 87 | 88 | // MitreProduct : #/definitions/product 89 | type MitreProduct struct { 90 | ID int64 `json:"-"` 91 | MitreContainerID uint `json:"-" gorm:"index:idx_mitre_product"` 92 | Vendor *string `gorm:"type:text"` 93 | Product *string `gorm:"type:text"` 94 | CollectionURL *string `gorm:"type:text"` 95 | PackageName *string `gorm:"type:text"` 96 | Cpes []MitreProductCPE 97 | Modules []MitreProductModule 98 | ProgramFiles []MitreProductProgramFile 99 | ProgramRoutines []MitreProductProgramRoutine 100 | Platforms []MitreProductPlatform 101 | Repo *string `gorm:"type:text"` 102 | DefaultStatus *string 103 | Versions []MitreProductVersion 104 | } 105 | 106 | // MitreProductCPE : #/definitions/product 107 | type MitreProductCPE struct { 108 | ID int64 `json:"-"` 109 | MitreProductID uint `json:"-" gorm:"index:idx_mitre_product_cpe"` 110 | CPE string `gorm:"type:text"` 111 | } 112 | 113 | // MitreProductModule : #/definitions/product 114 | type MitreProductModule struct { 115 | ID int64 `json:"-"` 116 | MitreProductID uint `json:"-" gorm:"index:idx_mitre_product_module"` 117 | Module string `gorm:"type:text"` 118 | } 119 | 120 | // MitreProductProgramFile : #/definitions/product 121 | type MitreProductProgramFile struct { 122 | ID int64 `json:"-"` 123 | MitreProductID uint `json:"-" gorm:"index:idx_mitre_product_program_file"` 124 | ProgramFile string `gorm:"type:text"` 125 | } 126 | 127 | // MitreProductProgramRoutine : #/definitions/product 128 | type MitreProductProgramRoutine struct { 129 | ID int64 `json:"-"` 130 | MitreProductID uint `json:"-" gorm:"index:idx_mitre_product_program_routine"` 131 | Name string `gorm:"type:text"` 132 | } 133 | 134 | // MitreProductPlatform : #/definitions/product 135 | type MitreProductPlatform struct { 136 | ID int64 `json:"-"` 137 | MitreProductID uint `json:"-" gorm:"index:idx_mitre_product_platform"` 138 | Platform string `gorm:"type:text"` 139 | } 140 | 141 | // MitreProductVersion : #/definitions/product 142 | type MitreProductVersion struct { 143 | ID int64 `json:"-"` 144 | MitreProductID uint `json:"-" gorm:"index:idx_mitre_product_version"` 145 | Status string `gorm:"type:varchar(255)"` 146 | VersionType *string `gorm:"type:varchar(128)"` 147 | Version string `gorm:"type:text"` 148 | LessThan *string `gorm:"type:text"` 149 | LessThanOrEqual *string `gorm:"type:text"` 150 | Changes []MitreProductVersionChange 151 | } 152 | 153 | // MitreProductVersionChange : #/definitions/product 154 | type MitreProductVersionChange struct { 155 | ID int64 `json:"-"` 156 | MitreProductVersionID uint `json:"-" gorm:"index:idx_mitre_product_version_change"` 157 | At string `gorm:"type:text"` 158 | Status string `gorm:"type:varchar(255)"` 159 | } 160 | 161 | // MitreProblemType : #/definitions/problemTypes 162 | type MitreProblemType struct { 163 | ID int64 `json:"-"` 164 | MitreContainerID uint `json:"-" gorm:"index:idx_mitre_problem_type"` 165 | Descriptions []MitreProblemTypeDescription 166 | } 167 | 168 | // MitreProblemTypeDescription : #/definitions/problemTypes 169 | type MitreProblemTypeDescription struct { 170 | ID int64 `json:"-"` 171 | MitreProblemTypeID uint `json:"-" gorm:"index:idx_mitre_problem_type_description"` 172 | Type *string `gorm:"type:varchar(255)"` 173 | Lang string `gorm:"type:varchar(128)"` 174 | Description string `gorm:"type:text"` 175 | CweID *string `gorm:"type:varchar(9)"` 176 | References []MitreProblemTypeDescriptionReference 177 | } 178 | 179 | // MitreProblemTypeDescriptionReference : #/definitions/references 180 | type MitreProblemTypeDescriptionReference struct { 181 | ID int64 `json:"-"` 182 | MitreProblemTypeDescriptionID uint `json:"-" gorm:"index:idx_mitre_problem_type_description_reference"` 183 | Reference `gorm:"embedded"` 184 | } 185 | 186 | // MitreImpact : #/definitions/impacts 187 | type MitreImpact struct { 188 | ID int64 `json:"-"` 189 | MitreContainerID uint `json:"-" gorm:"index:idx_mitre_impact"` 190 | Descriptions []MitreImpactDescription 191 | CapecID *string `gorm:"type:varchar(11)"` 192 | } 193 | 194 | // MitreImpactDescription : #/definitions/description 195 | type MitreImpactDescription struct { 196 | ID int64 `json:"-"` 197 | MitreImpactID uint `json:"-" gorm:"index:idx_mitre_impact_description"` 198 | Lang string `gorm:"type:varchar(255)"` 199 | Value string `gorm:"type:text"` 200 | SupportingMedia []MitreImpactDescriptionSupportingMedia 201 | } 202 | 203 | // MitreImpactDescriptionSupportingMedia : #/definitions/description 204 | type MitreImpactDescriptionSupportingMedia struct { 205 | ID int64 `json:"-"` 206 | MitreImpactDescriptionID uint `json:"-" gorm:"index:idx_mitre_impact_description_supporting_media"` 207 | Type string `gorm:"type:varchar(256)"` 208 | Base64 *bool 209 | Value string `gorm:"type:text"` 210 | } 211 | 212 | // MitreMetric : #/definitions/metrics 213 | type MitreMetric struct { 214 | ID int64 `json:"-"` 215 | MitreContainerID uint `json:"-" gorm:"index:idx_mitre_metric"` 216 | Format string `gorm:"type:varchar(64)"` 217 | Scenarios []MitreMetricScenario 218 | CVSSv2 *MitreMetricCVSS2 219 | CVSSv30 *MitreMetricCVSS30 220 | CVSSv31 *MitreMetricCVSS31 221 | CVSSv40 *MitreMetricCVSS40 222 | SSVC *MitreMetricSSVC 223 | KEV *MitreMetricKEV 224 | Other *MitreMetricOther 225 | } 226 | 227 | // MitreMetricScenario : #/definitions/metrics 228 | type MitreMetricScenario struct { 229 | ID int64 `json:"-"` 230 | MitreMetricID uint `json:"-" gorm:"index:idx_mitre_metric_scenario"` 231 | Lang string `gorm:"type:varchar(255)"` 232 | Value string `gorm:"type:text"` 233 | } 234 | 235 | // MitreMetricCVSS2 : https://www.first.org/cvss/cvss-v2.0.json?20170531 236 | type MitreMetricCVSS2 struct { 237 | ID int64 `json:"-"` 238 | MitreMetricID uint `json:"-" gorm:"index:idx_mitre_metric_cvss2"` 239 | Cvss2 `gorm:"embedded"` 240 | } 241 | 242 | // MitreMetricCVSS30 : https://www.first.org/cvss/cvss-v3.0.json?20170531 243 | type MitreMetricCVSS30 struct { 244 | ID int64 `json:"-"` 245 | MitreMetricID uint `json:"-" gorm:"index:idx_mitre_metric_cvss30"` 246 | Cvss3 `gorm:"embedded"` 247 | } 248 | 249 | // MitreMetricCVSS31 : https://www.first.org/cvss/cvss-v3.1.json?20210501 250 | type MitreMetricCVSS31 struct { 251 | ID int64 `json:"-"` 252 | MitreMetricID uint `json:"-" gorm:"index:idx_mitre_metric_cvss31"` 253 | Cvss3 `gorm:"embedded"` 254 | } 255 | 256 | // MitreMetricCVSS40 : https://www.first.org/cvss/cvss-v4.0.json?20231011 257 | type MitreMetricCVSS40 struct { 258 | ID int64 `json:"-"` 259 | MitreMetricID uint `json:"-" gorm:"index:idx_mitre_metric_cvss40"` 260 | Cvss40 `gorm:"embedded"` 261 | } 262 | 263 | // MitreMetricSSVC : 264 | type MitreMetricSSVC struct { 265 | ID int64 `json:"-"` 266 | MitreMetricID uint `json:"-" gorm:"index:idx_mitre_metric_ssvc"` 267 | Role string `gorm:"type:varchar(255)"` 268 | Version string `gorm:"type:varchar(255)"` 269 | Timestamp time.Time 270 | Exploitation *string `gorm:"type:varchar(255)"` 271 | Automatable *string `gorm:"type:varchar(255)"` 272 | TechnicalImpact *string `gorm:"type:varchar(255)"` 273 | } 274 | 275 | // MitreMetricKEV : https://github.com/cisagov/vulnrichment/blob/3f9d69632037fae3b7abdf47fc848c287702ffaa/assets/kev_metrics_schema-1.0.json 276 | type MitreMetricKEV struct { 277 | ID int64 `json:"-"` 278 | MitreMetricID uint `json:"-" gorm:"index:idx_mitre_metric_kev"` 279 | DateAdded time.Time 280 | Reference string `gorm:"type:varchar(255)"` 281 | } 282 | 283 | // MitreMetricOther : https://github.com/CVEProject/cve-schema/blob/30f59c7de92fbc77bddade302601cb500c66f718/schema/CVE_Record_Format.json#L901-L923 284 | type MitreMetricOther struct { 285 | ID int64 `json:"-"` 286 | MitreMetricID uint `json:"-" gorm:"index:idx_mitre_metric_other"` 287 | Type string `gorm:"type:varchar(128)"` 288 | Content string `gorm:"type:text"` 289 | } 290 | 291 | // MitreWorkaround : #/definitions/workarounds 292 | type MitreWorkaround struct { 293 | ID int64 `json:"-"` 294 | MitreContainerID uint `json:"-" gorm:"index:idx_mitre_workaround"` 295 | Lang string `gorm:"type:varchar(255)"` 296 | Value string `gorm:"type:text"` 297 | SupportingMedia []MitreWorkaroundSupportingMedia 298 | } 299 | 300 | // MitreWorkaroundSupportingMedia : #/definitions/description 301 | type MitreWorkaroundSupportingMedia struct { 302 | ID int64 `json:"-"` 303 | MitreWorkaroundID uint `json:"-" gorm:"index:idx_mitre_workaround_supporting_media"` 304 | Type string `gorm:"type:varchar(256)"` 305 | Base64 *bool 306 | Value string `gorm:"type:text"` 307 | } 308 | 309 | // MitreSolution : #/definitions/solutions 310 | type MitreSolution struct { 311 | ID int64 `json:"-"` 312 | MitreContainerID uint `json:"-" gorm:"index:idx_mitre_solution"` 313 | Lang string `gorm:"type:varchar(255)"` 314 | Value string `gorm:"type:text"` 315 | SupportingMedia []MitreSolutionSupportingMedia 316 | } 317 | 318 | // MitreSolutionSupportingMedia : #/definitions/description 319 | type MitreSolutionSupportingMedia struct { 320 | ID int64 `json:"-"` 321 | MitreSolutionID uint `json:"-" gorm:"index:idx_mitre_solution_supporting_media"` 322 | Type string `gorm:"type:varchar(256)"` 323 | Base64 *bool 324 | Value string `gorm:"type:text"` 325 | } 326 | 327 | // MitreExploit : #/definitions/exploits 328 | type MitreExploit struct { 329 | ID int64 `json:"-"` 330 | MitreContainerID uint `json:"-" gorm:"index:idx_mitre_exploit"` 331 | Lang string `gorm:"type:varchar(255)"` 332 | Value string `gorm:"type:text"` 333 | SupportingMedia []MitreExploitSupportingMedia 334 | } 335 | 336 | // MitreExploitSupportingMedia : #/definitions/description 337 | type MitreExploitSupportingMedia struct { 338 | ID int64 `json:"-"` 339 | MitreExploitID uint `json:"-" gorm:"index:idx_mitre_exploit_supporting_media"` 340 | Type string `gorm:"type:varchar(256)"` 341 | Base64 *bool 342 | Value string `gorm:"type:text"` 343 | } 344 | 345 | // MitreConfiguration : #/definitions/configurations 346 | type MitreConfiguration struct { 347 | ID int64 `json:"-"` 348 | MitreContainerID uint `json:"-" gorm:"index:idx_mitre_configuration"` 349 | Lang string `gorm:"type:varchar(255)"` 350 | Value string `gorm:"type:text"` 351 | SupportingMedia []MitreConfigurationSupportingMedia 352 | } 353 | 354 | // MitreConfigurationSupportingMedia : #/definitions/description 355 | type MitreConfigurationSupportingMedia struct { 356 | ID int64 `json:"-"` 357 | MitreConfigurationID uint `json:"-" gorm:"index:idx_mitre_configuration_supporting_media"` 358 | Type string `gorm:"type:varchar(256)"` 359 | Base64 *bool 360 | Value string `gorm:"type:text"` 361 | } 362 | 363 | // MitreReference : #/definitions/references 364 | type MitreReference struct { 365 | ID int64 `json:"-"` 366 | MitreContainerID uint `json:"-" gorm:"index:idx_mitre_reference"` 367 | Reference `gorm:"embedded"` 368 | } 369 | 370 | // MitreTimeline : #/definitions/timeline 371 | type MitreTimeline struct { 372 | ID int64 `json:"-"` 373 | MitreContainerID uint `json:"-" gorm:"index:idx_mitre_timeline"` 374 | Time time.Time 375 | Lang string `gorm:"type:varchar(255)"` 376 | Value string `gorm:"type:text"` 377 | } 378 | 379 | // MitreCredit : #/definitions/credits 380 | type MitreCredit struct { 381 | ID int64 `json:"-"` 382 | MitreContainerID uint `json:"-" gorm:"index:idx_mitre_credit"` 383 | Type *string `gorm:"type:varchar(255)"` 384 | Lang string `gorm:"type:varchar(255)"` 385 | User *string `gorm:"type:varchar(255)"` 386 | Value string `gorm:"type:text"` 387 | } 388 | 389 | // MitreTag : #/definitions/cnaTags, #/definitions/adpTags 390 | type MitreTag struct { 391 | ID int64 `json:"-"` 392 | MitreContainerID uint `json:"-" gorm:"index:idx_mitre_tag"` 393 | Tag string `gorm:"type:varchar(255)"` 394 | } 395 | 396 | // MitreTaxonomyMapping : #/definitions/taxonomyMappings 397 | type MitreTaxonomyMapping struct { 398 | ID int64 `json:"-"` 399 | MitreContainerID uint `json:"-" gorm:"index:idx_mitre_taxonomy_mapping"` 400 | TaxonomyVersion *string `gorm:"type:varchar(128)"` 401 | TaxonomyName string `gorm:"type:varchar(128)"` 402 | TaxonomyRelations []MitreTaxonomyRelation 403 | } 404 | 405 | // MitreTaxonomyRelation : #/definitions/taxonomyMappings 406 | type MitreTaxonomyRelation struct { 407 | ID int64 `json:"-"` 408 | MitreTaxonomyMappingID uint `json:"-" gorm:"index:idx_mitre_taxonomy_relation"` 409 | TaxonomyID string `gorm:"type:text"` 410 | RelationshipName string `gorm:"type:varchar(128)"` 411 | RelationshipValue string `gorm:"type:text"` 412 | } 413 | -------------------------------------------------------------------------------- /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 | } 34 | 35 | // HasNvd returns true if NVD contents 36 | func (c CveDetail) HasNvd() bool { 37 | return len(c.Nvds) != 0 38 | } 39 | 40 | // HasJvn returns true if JVN contents 41 | func (c CveDetail) HasJvn() bool { 42 | return len(c.Jvns) != 0 43 | } 44 | 45 | // HasFortinet returns true if Fortinet contents 46 | func (c CveDetail) HasFortinet() bool { 47 | return len(c.Fortinets) != 0 48 | } 49 | 50 | // HasMitre returns true if Mitre contents 51 | func (c CveDetail) HasMitre() bool { 52 | return len(c.Mitres) != 0 53 | } 54 | 55 | // HasPaloalto returns true if Paloalto contents 56 | func (c CveDetail) HasPaloalto() bool { 57 | return len(c.Paloaltos) != 0 58 | } 59 | 60 | // CveIDs : 61 | type CveIDs struct { 62 | Nvd []string 63 | Jvn []string 64 | Fortinet []string 65 | Paloalto []string 66 | } 67 | 68 | // Cvss2 has CVSS Version 2 info 69 | type Cvss2 struct { 70 | VectorString string `gorm:"type:varchar(255)"` 71 | AccessVector string `gorm:"type:varchar(255)"` 72 | AccessComplexity string `gorm:"type:varchar(255)"` 73 | Authentication string `gorm:"type:varchar(255)"` 74 | ConfidentialityImpact string `gorm:"type:varchar(255)"` 75 | IntegrityImpact string `gorm:"type:varchar(255)"` 76 | AvailabilityImpact string `gorm:"type:varchar(255)"` 77 | BaseScore float64 78 | Severity string `gorm:"type:varchar(255)"` 79 | } 80 | 81 | // Cvss3 has CVSS Version 3 info 82 | type Cvss3 struct { 83 | VectorString string `gorm:"type:varchar(255)"` 84 | AttackVector string `gorm:"type:varchar(255)"` 85 | AttackComplexity string `gorm:"type:varchar(255)"` 86 | PrivilegesRequired string `gorm:"type:varchar(255)"` 87 | UserInteraction string `gorm:"type:varchar(255)"` 88 | Scope string `gorm:"type:varchar(255)"` 89 | ConfidentialityImpact string `gorm:"type:varchar(255)"` 90 | IntegrityImpact string `gorm:"type:varchar(255)"` 91 | AvailabilityImpact string `gorm:"type:varchar(255)"` 92 | BaseScore float64 93 | BaseSeverity string `gorm:"type:varchar(255)"` 94 | ExploitabilityScore float64 95 | ImpactScore float64 96 | } 97 | 98 | // Cvss40 has CVSS Version 4.0 info 99 | type Cvss40 struct { 100 | VectorString string `gorm:"type:varchar(255)"` 101 | BaseScore float64 `json:"baseScore"` 102 | BaseSeverity string `gorm:"type:varchar(255)"` 103 | ThreatScore *float64 104 | ThreatSeverity *string `gorm:"type:varchar(255)"` 105 | EnvironmentalScore *float64 106 | EnvironmentalSeverity *string `gorm:"type:varchar(255)"` 107 | } 108 | 109 | // CpeBase has common args of Cpe and EnvCpe 110 | type CpeBase struct { 111 | URI string `gorm:"index;type:varchar(255)"` 112 | FormattedString string `gorm:"index;type:varchar(255)"` 113 | WellFormedName string `gorm:"type:text"` 114 | CpeWFN `gorm:"embedded"` 115 | VersionStartExcluding string `gorm:"type:varchar(255)"` 116 | VersionStartIncluding string `gorm:"type:varchar(255)"` 117 | VersionEndExcluding string `gorm:"type:varchar(255)"` 118 | VersionEndIncluding string `gorm:"type:varchar(255)"` 119 | } 120 | 121 | // CpeWFN has CPE Well Formed name information 122 | type CpeWFN struct { 123 | Part string `gorm:"index;type:varchar(255)"` 124 | Vendor string `gorm:"index;type:varchar(255)"` 125 | Product string `gorm:"index;type:varchar(255)"` 126 | Version string `gorm:"type:varchar(255)"` 127 | Update string `gorm:"type:varchar(255)"` 128 | Edition string `gorm:"type:varchar(255)"` 129 | Language string `gorm:"type:varchar(255)"` 130 | SoftwareEdition string `gorm:"type:varchar(255)"` 131 | TargetSW string `gorm:"type:varchar(255)"` 132 | TargetHW string `gorm:"type:varchar(255)"` 133 | Other string `gorm:"type:varchar(255)"` 134 | } 135 | 136 | // Reference holds reference information about the CVE. 137 | type Reference struct { 138 | Link string `gorm:"type:text"` 139 | Source string `gorm:"type:varchar(255)"` 140 | Tags string `gorm:"type:varchar(255)"` 141 | Name string `gorm:"type:text"` 142 | } 143 | 144 | // Cert holds CERT alerts. 145 | type Cert struct { 146 | Title string `gorm:"type:text"` 147 | Link string `gorm:"type:text"` 148 | } 149 | -------------------------------------------------------------------------------- /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.POST("/cpes", getCveByCpeName(driver)) 47 | e.POST("/cpes/ids", getCveIDsByCpeName(driver)) 48 | 49 | bindURL := fmt.Sprintf("%s:%s", viper.GetString("bind"), viper.GetString("port")) 50 | log.Infof("Listening on %s", bindURL) 51 | 52 | return e.Start(bindURL) 53 | } 54 | 55 | // Handler 56 | func health() echo.HandlerFunc { 57 | return func(c echo.Context) error { 58 | return c.String(http.StatusOK, "") 59 | } 60 | } 61 | 62 | // Handler 63 | func getCve(driver db.DB) echo.HandlerFunc { 64 | return func(c echo.Context) error { 65 | cveid := c.Param("id") 66 | cveDetail, err := driver.Get(cveid) 67 | if err != nil { 68 | log.Errorf("%s", err) 69 | return err 70 | } 71 | return c.JSON(http.StatusOK, &cveDetail) 72 | } 73 | } 74 | 75 | func getCveMulti(driver db.DB) echo.HandlerFunc { 76 | return func(c echo.Context) error { 77 | var cveIDs []string 78 | if err := c.Bind(&cveIDs); err != nil { 79 | log.Errorf("%s", err) 80 | return err 81 | } 82 | 83 | cveDetails, err := driver.GetMulti(cveIDs) 84 | if err != nil { 85 | log.Errorf("%s", err) 86 | return err 87 | } 88 | return c.JSON(http.StatusOK, &cveDetails) 89 | } 90 | } 91 | 92 | type cpeName struct { 93 | Name string `form:"name"` 94 | } 95 | 96 | func getCveByCpeName(driver db.DB) echo.HandlerFunc { 97 | return func(c echo.Context) error { 98 | cpe := cpeName{} 99 | err := c.Bind(&cpe) 100 | if err != nil { 101 | log.Errorf("%s", err) 102 | return err 103 | } 104 | cveDetails, err := driver.GetByCpeURI(cpe.Name) 105 | if err != nil { 106 | log.Errorf("%s", err) 107 | return err 108 | } 109 | 110 | sort.Slice(cveDetails, func(i, j int) bool { 111 | return cveDetails[i].CveID < cveDetails[j].CveID 112 | }) 113 | return c.JSON(http.StatusOK, &cveDetails) 114 | } 115 | } 116 | 117 | func getCveIDs(driver db.DB) echo.HandlerFunc { 118 | return func(c echo.Context) error { 119 | cveIDs, err := driver.GetCveIDs() 120 | if err != nil { 121 | log.Errorf("%s", err) 122 | return err 123 | } 124 | return c.JSON(http.StatusOK, &cveIDs) 125 | } 126 | } 127 | 128 | func getCveIDsByCpeName(driver db.DB) echo.HandlerFunc { 129 | return func(c echo.Context) error { 130 | cpe := cpeName{} 131 | err := c.Bind(&cpe) 132 | if err != nil { 133 | log.Errorf("%s", err) 134 | return err 135 | } 136 | cveids, err := driver.GetCveIDsByCpeURI(cpe.Name) 137 | if err != nil { 138 | log.Errorf("%s", err) 139 | return err 140 | } 141 | cveIDs := map[string][]string{"NVD": cveids.Nvd, "JVN": cveids.Jvn, "Fortinet": cveids.Fortinet, "Paloalto": cveids.Paloalto} 142 | return c.JSON(http.StatusOK, &cveIDs) 143 | } 144 | } 145 | 146 | func getAdvisoriesJvn(driver db.DB) echo.HandlerFunc { 147 | return func(c echo.Context) error { 148 | m, err := driver.GetAdvisoriesJvn() 149 | if err != nil { 150 | log.Errorf("%s", err) 151 | return err 152 | } 153 | return c.JSON(http.StatusOK, &m) 154 | } 155 | } 156 | 157 | func getAdvisoriesFortinet(driver db.DB) echo.HandlerFunc { 158 | return func(c echo.Context) error { 159 | m, err := driver.GetAdvisoriesFortinet() 160 | if err != nil { 161 | log.Errorf("%s", err) 162 | return err 163 | } 164 | return c.JSON(http.StatusOK, &m) 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------