diff --git a/.github/workflows/update-generated-code.yaml b/.github/workflows/update-generated-code.yaml new file mode 100644 index 00000000..1c08583e --- /dev/null +++ b/.github/workflows/update-generated-code.yaml @@ -0,0 +1,52 @@ +name: PR to update generated code +on: + schedule: + - cron: "0 1 * * 1" # every monday at 1 AM + + workflow_dispatch: + +permissions: + contents: read + +env: + SLACK_NOTIFICATIONS: true + +jobs: + upgrade-cpe-dictionary-index: + runs-on: ubuntu-latest + if: github.repository == 'anchore/grype' # only run for main repo + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 + + - name: Bootstrap environment + uses: ./.github/actions/bootstrap + + - run: | + make generate-processor-code + + - uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a #v2.1.0 + id: generate-token + with: + app_id: ${{ secrets.TOKEN_APP_ID }} + private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }} + + - uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f #v7.0.5 + with: + signoff: true + delete-branch: true + branch: auto/latest-cpe-dictionary-index + labels: dependencies + commit-message: "chore(deps): update generated code" + title: "chore(deps): update generated code" + body: | + Update generated code from external sources + token: ${{ steps.generate-token.outputs.token }} + + - uses: 8398a7/action-slack@28ba43ae48961b90635b50953d216767a6bea486 #v3.16.2 + with: + status: ${{ job.status }} + fields: workflow,eventName,job + text: Grype-DB generated code update failed + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TOOLBOX_WEBHOOK_URL }} + if: ${{ failure() && env.SLACK_NOTIFICATIONS == 'true' }} \ No newline at end of file diff --git a/Makefile b/Makefile index 5a9c8dec..73c98fde 100644 --- a/Makefile +++ b/Makefile @@ -256,6 +256,13 @@ download-all-provider-cache: @bash -c "oras pull $(GRYPE_DB_DATA_IMAGE_NAME):$(date) && $(GRYPE_DB) cache restore --path $(DB_ARCHIVE) || (echo 'no data cache found for today' && exit 1)" +## Code and data generation targets ################################# + +.PHONY: generate-processor-code +generate-processor-code: + go generate ./pkg/process + make format + ## Build-related targets ################################# .PHONY: build diff --git a/cmd/grype-db/application/application.go b/cmd/grype-db/application/application.go index 7d17a78c..61de0cfa 100644 --- a/cmd/grype-db/application/application.go +++ b/cmd/grype-db/application/application.go @@ -22,6 +22,7 @@ import ( "github.com/anchore/grype-db/internal/log" "github.com/anchore/grype-db/internal/ui" "github.com/anchore/grype-db/internal/utils" + "github.com/anchore/grype/grype" ) const Name = internal.ApplicationName @@ -169,6 +170,7 @@ func setupLogger(app *Config) error { } log.Set(l) + grype.SetLogger(l) return nil } diff --git a/go.mod b/go.mod index 51b2a16c..fef3f854 100644 --- a/go.mod +++ b/go.mod @@ -8,9 +8,10 @@ require ( github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/adrg/xdg v0.5.3 github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a - github.com/anchore/grype v0.84.0 + github.com/anchore/grype v0.84.1-0.20241120030844-ce4acf74b750 github.com/anchore/syft v1.16.0 - github.com/dustin/go-humanize v1.0.1 + github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de + github.com/dave/jennifer v1.7.1 github.com/glebarez/sqlite v1.11.0 github.com/go-test/deep v1.1.1 github.com/google/go-cmp v0.6.0 @@ -36,7 +37,7 @@ require ( github.com/umisama/go-cpe v0.0.0-20190323060751-cdd6c3c28a23 github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651 github.com/wagoodman/go-progress v0.0.0-20230925121702-07e42b3cdba0 - golang.org/x/sync v0.8.0 + golang.org/x/sync v0.9.0 golang.org/x/text v0.19.0 gopkg.in/yaml.v3 v3.0.1 gorm.io/gorm v1.25.12 @@ -61,14 +62,14 @@ require ( github.com/Microsoft/hcsshim v0.11.7 // indirect github.com/ProtonMail/go-crypto v1.0.0 // indirect github.com/acobaugh/osrelease v0.1.0 // indirect - github.com/anchore/clio v0.0.0-20241015191535-f538a9016e10 // indirect + github.com/anchore/clio v0.0.0-20241115144204-29e89f9fa837 // indirect github.com/anchore/fangs v0.0.0-20241014225144-4e1713cafd77 // indirect github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537 // indirect github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb // indirect github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4 // indirect github.com/anchore/packageurl-go v0.1.1-0.20241018175412-5c22e6360c4f // indirect - github.com/anchore/stereoscope v0.0.6-0.20241101185849-cbd43fb4e5d3 // indirect + github.com/anchore/stereoscope v0.0.8 // indirect github.com/andybalholm/brotli v1.0.4 // indirect github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 // indirect github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492 // indirect @@ -79,7 +80,7 @@ require ( github.com/bmatcuk/doublestar/v2 v2.0.4 // indirect github.com/bmatcuk/doublestar/v4 v4.7.1 // indirect github.com/charmbracelet/lipgloss v1.0.0 // indirect - github.com/charmbracelet/x/ansi v0.4.2 // indirect + github.com/charmbracelet/x/ansi v0.4.5 // indirect github.com/cloudflare/circl v1.3.8 // indirect github.com/containerd/cgroups v1.1.0 // indirect github.com/containerd/containerd v1.7.23 // indirect @@ -104,6 +105,7 @@ require ( github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/edsrzf/mmap-go v1.1.0 // indirect github.com/elliotchance/phpserialize v1.4.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect @@ -175,6 +177,8 @@ require ( github.com/opencontainers/image-spec v1.1.0 // indirect github.com/opencontainers/runtime-spec v1.1.0 // indirect github.com/opencontainers/selinux v1.11.0 // indirect + github.com/openvex/go-vex v0.2.5 // indirect + github.com/package-url/packageurl-go v0.1.1 // indirect github.com/pborman/indent v1.2.1 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect @@ -221,8 +225,8 @@ require ( golang.org/x/mod v0.21.0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/oauth2 v0.19.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/term v0.25.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/term v0.26.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/api v0.171.0 // indirect diff --git a/go.sum b/go.sum index 98b4b0e2..05ab9287 100644 --- a/go.sum +++ b/go.sum @@ -236,8 +236,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/anchore/archiver/v3 v3.5.2 h1:Bjemm2NzuRhmHy3m0lRe5tNoClB9A4zYyDV58PaB6aA= github.com/anchore/archiver/v3 v3.5.2/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= -github.com/anchore/clio v0.0.0-20241015191535-f538a9016e10 h1:3xmanFdoQEH0REvPA+gLm3Km0/981F4z2a/7ADTlv8k= -github.com/anchore/clio v0.0.0-20241015191535-f538a9016e10/go.mod h1:h6Ly2hlKjQoPtI3rA8oB5afSmB/XimhcY55xbuW4Dwo= +github.com/anchore/clio v0.0.0-20241115144204-29e89f9fa837 h1:bIG3WsfosZsJ5LMC7PB9J/ekFM3a0j0ZEDvN3ID6GTI= +github.com/anchore/clio v0.0.0-20241115144204-29e89f9fa837/go.mod h1:tRQVKkjYeejrh9AdM0s1esbwtMU7rdHAHSQWkv4qskE= github.com/anchore/fangs v0.0.0-20241014225144-4e1713cafd77 h1:h7+GCqazHVS5GDJYYS6wjjglYi8xFnVWMdSUukoImTM= github.com/anchore/fangs v0.0.0-20241014225144-4e1713cafd77/go.mod h1:qbev5czQeyDO74fPNThiEKYkgt0mx1axb+5wQcxDPFY= github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537 h1:GjNGuwK5jWjJMyVppBjYS54eOiiSNv4Ba869k4wh72Q= @@ -252,12 +252,12 @@ github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 h1:VzprUTpc0v github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04/go.mod h1:6dK64g27Qi1qGQZ67gFmBFvEHScy0/C8qhQhNe5B5pQ= github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4 h1:rmZG77uXgE+o2gozGEBoUMpX27lsku+xrMwlmBZJtbg= github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E= -github.com/anchore/grype v0.84.0 h1:3BwhY+ctiNBmdGd53R36xw5ndC7WhSSKEc0fqaAErw0= -github.com/anchore/grype v0.84.0/go.mod h1:CQQGcLEJBaQ8b5x3ut3T4WoO0KHnjKMFHrh5J6c4Fh4= +github.com/anchore/grype v0.84.1-0.20241120030844-ce4acf74b750 h1:P6bUmjeHSSEWw2qxIWGNaeohdVJNhD0iPxsv5CFiV5M= +github.com/anchore/grype v0.84.1-0.20241120030844-ce4acf74b750/go.mod h1:K1L+fDE7YjSaPe6sIMpgaZp/YkFS4SgUSKFqVhKMIXs= github.com/anchore/packageurl-go v0.1.1-0.20241018175412-5c22e6360c4f h1:dAQPIrQ3a5PBqZeZ+B9NGZsGmodk4NO9OjDIsQmQyQM= github.com/anchore/packageurl-go v0.1.1-0.20241018175412-5c22e6360c4f/go.mod h1:KoYIv7tdP5+CC9VGkeZV4/vGCKsY55VvoG+5dadg4YI= -github.com/anchore/stereoscope v0.0.6-0.20241101185849-cbd43fb4e5d3 h1:T1LMkKwzSg1s8+xylq51xroPoo83Nt3zQPvdRRDB9Bw= -github.com/anchore/stereoscope v0.0.6-0.20241101185849-cbd43fb4e5d3/go.mod h1:jgLIzIwEkfMhjhKmuzaSEeU2/vFCqlO3XTrSeSLfOaM= +github.com/anchore/stereoscope v0.0.8 h1:ma8A7SnM5WWU0HJ2p6YBq7myN7zKa0JnUQOY/4enekk= +github.com/anchore/stereoscope v0.0.8/go.mod h1:o1eDg6BZwORU6nh4vRe3C2ZmAmmH+MWRLNl55uAF/v8= github.com/anchore/syft v1.16.0 h1:iHPqE2q7gmvRDdmh5/897ycRbetfmLwor17/YBNVQNw= github.com/anchore/syft v1.16.0/go.mod h1:x8JNItb+Dj3xwG1tRfyCbJj9Xl/vlcBfXz7q3M2GmjA= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= @@ -271,6 +271,8 @@ github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 h1: github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46/go.mod h1:olhPNdiiAAMiSujemd1O/sc6GcyePr23f/6uGKtthNg= github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492 h1:rcEG5HI490FF0a7zuvxOxen52ddygCfNVjP0XOCMl+M= github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492/go.mod h1:9Beu8XsUNNfzml7WBf3QmyPToP1wm1Gj/Vc5UJKqTzU= +github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA= +github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= @@ -309,14 +311,14 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= -github.com/charmbracelet/bubbletea v1.1.2 h1:naQXF2laRxyLyil/i7fxdpiz1/k06IKquhm4vBfHsIc= -github.com/charmbracelet/bubbletea v1.1.2/go.mod h1:9HIU/hBV24qKjlehyj8z1r/tR9TYTQEag+cWZnuXo8E= +github.com/charmbracelet/bubbletea v1.2.2 h1:EMz//Ky/aFS2uLcKqpCst5UOE6z5CFDGRsUpyXz0chs= +github.com/charmbracelet/bubbletea v1.2.2/go.mod h1:Qr6fVQw+wX7JkWWkVyXYk/ZUQ92a6XNekLXa3rR18MM= github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg= github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo= -github.com/charmbracelet/x/ansi v0.4.2 h1:0JM6Aj/g/KC154/gOP4vfxun0ff6itogDYk41kof+qk= -github.com/charmbracelet/x/ansi v0.4.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= -github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0= -github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0= +github.com/charmbracelet/x/ansi v0.4.5 h1:LqK4vwBNaXw2AyGIICa5/29Sbdq58GbGdFngSexTdRM= +github.com/charmbracelet/x/ansi v0.4.5/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= +github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= +github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -367,6 +369,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo= github.com/cyphar/filepath-securejoin v0.2.5/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo= +github.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -734,6 +738,7 @@ github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 h1:P8UmIzZ github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -807,6 +812,10 @@ github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bl github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU= github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= +github.com/openvex/go-vex v0.2.5 h1:41utdp2rHgAGCsG+UbjmfMG5CWQxs15nGqir1eRgSrQ= +github.com/openvex/go-vex v0.2.5/go.mod h1:j+oadBxSUELkrKh4NfNb+BPo77U3q7gdKME88IO/0Wo= +github.com/package-url/packageurl-go v0.1.1 h1:KTRE0bK3sKbFKAk3yy63DpeskU7Cvs/x/Da5l+RtzyU= +github.com/package-url/packageurl-go v0.1.1/go.mod h1:uQd4a7Rh3ZsVg5j0lNyAfyxIeGde9yrlhjF78GzeW0c= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/indent v1.2.1 h1:lFiviAbISHv3Rf0jcuh489bi06hj98JsVMtIDZQb9yM= @@ -849,6 +858,7 @@ github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5 github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -876,6 +886,7 @@ github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtC github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI= github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e h1:7q6NSFZDeGfvvtIRwBrU/aegEYJYmvev0cHAwo17zZQ= github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e/go.mod h1:DkpGd78rljTxKAnTDPFqXSGxvETQnJyuSOQwsHycqfs= +github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sebdah/goldie/v2 v2.5.5 h1:rx1mwF95RxZ3/83sdS4Yp7t2C5TCokvWP4TBRbAyEWY= github.com/sebdah/goldie/v2 v2.5.5/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= @@ -1186,8 +1197,8 @@ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1278,16 +1289,16 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= +golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/internal/tarutil/writer.go b/internal/tarutil/writer.go index a22984fe..11a97635 100644 --- a/internal/tarutil/writer.go +++ b/internal/tarutil/writer.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "os" + "os/exec" "strings" "github.com/klauspost/compress/zstd" @@ -20,7 +21,7 @@ type writer struct { writer *tar.Writer } -// NewWriter creates a new tar writer that writes to the specified archive path. Supports .tar.gz and .tar.zst file extensions. +// NewWriter creates a new tar writer that writes to the specified archive path. Supports .tar.gz, .tar.zst, .tar.xz, and .tar file extensions. func NewWriter(archivePath string) (Writer, error) { w, err := newCompressor(archivePath) if err != nil { @@ -45,19 +46,58 @@ func newCompressor(archivePath string) (io.WriteCloser, error) { case strings.HasSuffix(archivePath, ".tar.gz"): return gzip.NewWriter(archive), nil case strings.HasSuffix(archivePath, ".tar.zst"): - // adding zstd.WithWindowSize(zstd.MaxWindowSize), zstd.WithAllLitEntropyCompression(true) - // will have slightly better results, but use a lot more memory + // adding zstd options for better compression w, err := zstd.NewWriter(archive, zstd.WithEncoderLevel(zstd.SpeedBestCompression)) if err != nil { return nil, fmt.Errorf("unable to get zst compression stream: %w", err) } return w, nil + case strings.HasSuffix(archivePath, ".tar.xz"): + // use xz for compression via an external process + cmd := exec.Command("xz", "-9", "--threads=0", "-c") + cmd.Stdout = archive + cmd.Stderr = os.Stderr + + pipe, err := cmd.StdinPipe() + if err != nil { + return nil, fmt.Errorf("unable to create xz stdin pipe: %w", err) + } + + if err := cmd.Start(); err != nil { + return nil, fmt.Errorf("unable to start xz process: %w", err) + } + + // return a WriteCloser that handles the xz process + return &xzCompressor{ + cmd: cmd, + pipe: pipe, + }, nil case strings.HasSuffix(archivePath, ".tar"): return archive, nil } return nil, ErrUnsupportedArchiveSuffix } +// xzCompressor wraps the stdin pipe of the xz process and ensures proper cleanup. +type xzCompressor struct { + cmd *exec.Cmd + pipe io.WriteCloser +} + +func (x *xzCompressor) Write(p []byte) (int, error) { + return x.pipe.Write(p) +} + +func (x *xzCompressor) Close() error { + if err := x.pipe.Close(); err != nil { + return fmt.Errorf("unable to close xz stdin pipe: %w", err) + } + if err := x.cmd.Wait(); err != nil { + return fmt.Errorf("xz process error: %w", err) + } + return nil +} + func (w *writer) WriteEntry(entry Entry) error { return entry.writeEntry(w.writer) } @@ -79,3 +119,194 @@ func (w *writer) Close() error { return nil } + +// type writer struct { +// compressor io.WriteCloser +// writer *tar.Writer +// spy *writeSpy +//} +// +//// NewWriter creates a new tar writer that writes to the specified archive path. Supports .tar.gz, .tar.zst, .tar.xz, and .tar file extensions. +// func NewWriter(archivePath string) (Writer, error) { +// w, spy, err := newCompressor(archivePath) +// if err != nil { +// return nil, err +// } +// +// tw := tar.NewWriter(w) +// +// return &writer{ +// compressor: w, +// writer: tw, +// spy: spy, +// }, nil +//} +// +//func newCompressor(archivePath string) (io.WriteCloser, *writeSpy, error) { +// archive, err := os.Create(archivePath) +// if err != nil { +// return nil, nil, err +// } +// +// var baseWriter io.WriteCloser +// switch { +// case strings.HasSuffix(archivePath, ".tar.gz"): +// baseWriter = gzip.NewWriter(archive) +// case strings.HasSuffix(archivePath, ".tar.zst"): +// // adding zstd.WithWindowSize(zstd.MaxWindowSize), zstd.WithAllLitEntropyCompression(true) +// // will have slightly better results, but use a lot more memory +// w, err := zstd.NewWriter(archive, zstd.WithEncoderLevel(zstd.SpeedBestCompression)) +// if err != nil { +// return nil, nil, fmt.Errorf("unable to get zst compression stream: %w", err) +// } +// baseWriter = w +// case strings.HasSuffix(archivePath, ".tar.xz"): +// // Use xz for compression via an external process +// cmd := exec.Command("xz", "-9", "--threads=0", "-c") +// cmd.Stdout = archive +// cmd.Stderr = os.Stderr +// +// pipe, err := cmd.StdinPipe() +// if err != nil { +// return nil, nil, fmt.Errorf("unable to create xz stdin pipe: %w", err) +// } +// +// if err := cmd.Start(); err != nil { +// return nil, nil, fmt.Errorf("unable to start xz process: %w", err) +// } +// +// xz := &xzCompressor{ +// cmd: cmd, +// pipe: pipe, +// done: make(chan struct{}), +// } +// +// baseWriter = xz +// case strings.HasSuffix(archivePath, ".tar"): +// baseWriter = archive +// default: +// return nil, nil, ErrUnsupportedArchiveSuffix +// } +// +// spy := newWriteSpy(baseWriter, sizer(archivePath)) +// return spy, spy, nil +//} +// +//func sizer(path string) func() int64 { +// return func() int64 { +// stat, err := os.Stat(path) +// if err != nil { +// return 0 +// } +// return stat.Size() +// } +//} +// +//// writeSpy wraps an io.WriteCloser to track the number of bytes written. +//type writeSpy struct { +// writer io.WriteCloser +// mu sync.Mutex +// total int64 +// done <-chan struct{} +// sizer func() int64 +//} +// +//func newWriteSpy(w io.WriteCloser, sizer func() int64) *writeSpy { +// var done <-chan struct{} +// if xz, ok := w.(*xzCompressor); ok { +// done = xz.done +// } else { +// // Create a dummy channel for non-xz writers +// ch := make(chan struct{}) +// close(ch) +// done = ch +// } +// +// spy := &writeSpy{ +// writer: w, +// done: done, +// sizer: sizer, +// } +// +// // Start the progress logger +// go spy.logProgress() +// +// return spy +//} +// +//func (s *writeSpy) Write(p []byte) (int, error) { +// n, err := s.writer.Write(p) +// s.mu.Lock() +// s.total += int64(n) +// s.mu.Unlock() +// return n, err +//} +// +//func (s *writeSpy) Close() error { +// return s.writer.Close() +//} +// +//func (s *writeSpy) logProgress() { +// ticker := time.NewTicker(5 * time.Second) +// defer ticker.Stop() +// +// logProgress := func() { +// s.mu.Lock() +// log.WithFields("db-size", humanize.Bytes(uint64(s.total)), "archive-size", humanize.Bytes(uint64(s.sizer()))).Debug("archive write status") +// s.mu.Unlock() +// } +// +// for { +// select { +// case <-ticker.C: +// logProgress() +// case <-s.done: +// logProgress() +// return +// } +// } +//} +// +//func (w *writer) WriteEntry(entry Entry) error { +// return entry.writeEntry(w.writer) +//} +// +//func (w *writer) Close() error { +// if w.writer != nil { +// err := w.writer.Close() +// w.writer = nil +// if err != nil { +// return fmt.Errorf("unable to close tar writer: %w", err) +// } +// } +// +// if w.compressor != nil { +// err := w.compressor.Close() +// w.compressor = nil +// return err +// } +// +// return nil +//} +// +//// xzCompressor wraps the stdin pipe of the xz process and ensures proper cleanup. +//type xzCompressor struct { +// cmd *exec.Cmd +// pipe io.WriteCloser +// done chan struct{} +//} +// +//func (x *xzCompressor) Write(p []byte) (int, error) { +// return x.pipe.Write(p) +//} +// +//func (x *xzCompressor) Close() error { +// if err := x.pipe.Close(); err != nil { +// return fmt.Errorf("unable to close xz stdin pipe: %w", err) +// } +// if err := x.cmd.Wait(); err != nil { +// return fmt.Errorf("xz process error: %w", err) +// } +// close(x.done) // Signal that the xz process has completed +// return nil +//} diff --git a/pkg/data/processor.go b/pkg/data/processor.go index 0258f614..3181f40c 100644 --- a/pkg/data/processor.go +++ b/pkg/data/processor.go @@ -1,10 +1,14 @@ package data -import "io" +import ( + "io" + + "github.com/anchore/grype-db/pkg/provider" +) // Processor takes individual feed group cache files (for select feed groups) and is responsible to producing // data.Entry objects to be written to the DB. type Processor interface { IsSupported(schemaURL string) bool - Process(reader io.Reader) ([]Entry, error) + Process(reader io.Reader, state provider.State) ([]Entry, error) } diff --git a/pkg/data/transformers.go b/pkg/data/transformers.go index 531651f0..d6f0ead2 100644 --- a/pkg/data/transformers.go +++ b/pkg/data/transformers.go @@ -1,6 +1,9 @@ package data -import "github.com/anchore/grype-db/pkg/provider/unmarshal" +import ( + "github.com/anchore/grype-db/pkg/provider" + "github.com/anchore/grype-db/pkg/provider/unmarshal" +) // Transformers are functions that know how ta take individual data shapes defined in the unmarshal package and // reshape the data into data.Entry objects that are writable by a data.Writer. Transformers are dependency-injected @@ -11,3 +14,9 @@ type MSRCTransformer func(entry unmarshal.MSRCVulnerability) ([]Entry, error) type NVDTransformer func(entry unmarshal.NVDVulnerability) ([]Entry, error) type OSTransformer func(entry unmarshal.OSVulnerability) ([]Entry, error) type MatchExclusionTransformer func(entry unmarshal.MatchExclusion) ([]Entry, error) + +type GitHubTransformerV2 func(entry unmarshal.GitHubAdvisory, state provider.State) ([]Entry, error) +type MSRCTransformerV2 func(entry unmarshal.MSRCVulnerability, state provider.State) ([]Entry, error) +type NVDTransformerV2 func(entry unmarshal.NVDVulnerability, state provider.State) ([]Entry, error) +type OSTransformerV2 func(entry unmarshal.OSVulnerability, state provider.State) ([]Entry, error) +type MatchExclusionTransformerV2 func(entry unmarshal.MatchExclusion, state provider.State) ([]Entry, error) diff --git a/pkg/process/build.go b/pkg/process/build.go index 8a61fc1a..2012efa3 100644 --- a/pkg/process/build.go +++ b/pkg/process/build.go @@ -5,8 +5,6 @@ import ( "fmt" "time" - "github.com/dustin/go-humanize" - "github.com/anchore/grype-db/internal/log" "github.com/anchore/grype-db/pkg/data" v1 "github.com/anchore/grype-db/pkg/process/v1" @@ -14,6 +12,7 @@ import ( v3 "github.com/anchore/grype-db/pkg/process/v3" v4 "github.com/anchore/grype-db/pkg/process/v4" v5 "github.com/anchore/grype-db/pkg/process/v5" + v6 "github.com/anchore/grype-db/pkg/process/v6" "github.com/anchore/grype-db/pkg/provider" "github.com/anchore/grype-db/pkg/provider/entry" "github.com/anchore/grype-db/pkg/provider/unmarshal" @@ -22,6 +21,7 @@ import ( grypeDBv3 "github.com/anchore/grype/grype/db/v3" grypeDBv4 "github.com/anchore/grype/grype/db/v4" grypeDBv5 "github.com/anchore/grype/grype/db/v5" + grypeDBv6 "github.com/anchore/grype/grype/db/v6" ) type BuildConfig struct { @@ -50,45 +50,30 @@ func Build(cfg BuildConfig) error { return err } - var openers []openerEntry + var openers []providerResults for _, sd := range cfg.States { sdOpeners, count, err := entry.Openers(sd.Store, sd.ResultPaths()) if err != nil { return fmt.Errorf("failed to open provider result files: %w", err) } - openers = append(openers, openerEntry{ - openers: sdOpeners, - name: sd.Provider, - count: count, + openers = append(openers, providerResults{ + openers: sdOpeners, + provider: sd, + count: count, }) } - if err := build(mergeOpeners(openers), writer, processors...); err != nil { + if err := build(openers, writer, processors...); err != nil { return err } return writer.Close() } -type openerEntry struct { - openers <-chan entry.Opener - name string - count int64 -} - -func mergeOpeners(entries []openerEntry) <-chan entry.Opener { - out := make(chan entry.Opener) - go func() { - defer close(out) - for _, e := range entries { - log.WithFields("provider", e.name, "records", humanize.Comma(e.count)).Debug("writing to DB") - - for opener := range e.openers { - out <- opener - } - } - }() - return out +type providerResults struct { + openers <-chan entry.Opener + provider provider.State + count int64 } func getProcessors(cfg BuildConfig) ([]data.Processor, error) { @@ -103,6 +88,8 @@ func getProcessors(cfg BuildConfig) ([]data.Processor, error) { return v4.Processors(), nil case grypeDBv5.SchemaVersion: return v5.Processors(v5.NewConfig(v5.WithCPEParts(cfg.IncludeCPEParts), v5.WithInferNVDFixVersions(cfg.InferNVDFixVersions))), nil + case grypeDBv6.ModelVersion: + return v6.Processors(v6.NewConfig(v6.WithCPEParts(cfg.IncludeCPEParts), v6.WithInferNVDFixVersions(cfg.InferNVDFixVersions))), nil default: return nil, fmt.Errorf("unable to create processor: unsupported schema version: %+v", cfg.SchemaVersion) } @@ -120,44 +107,57 @@ func getWriter(schemaVersion int, dataAge time.Time, directory string, states pr return v4.NewWriter(directory, dataAge) case grypeDBv5.SchemaVersion: return v5.NewWriter(directory, dataAge, states) + case grypeDBv6.ModelVersion: + return v6.NewWriter(directory, states) default: return nil, fmt.Errorf("unable to create writer: unsupported schema version: %+v", schemaVersion) } } -func build(openers <-chan entry.Opener, writer data.Writer, processors ...data.Processor) error { - for opener := range openers { - log.WithFields("entry", opener.String()).Tracef("processing") - var processor data.Processor +func build(results []providerResults, writer data.Writer, processors ...data.Processor) error { + lastUpdate := time.Now() + for _, result := range results { + log.WithFields("provider", result.provider.Provider, "count", result.count).Info("processing provider records") + idx := 0 + for opener := range result.openers { + idx++ + log.WithFields("entry", opener.String()).Tracef("processing") + var processor data.Processor + + if time.Since(lastUpdate) > 3*time.Second { + log.WithFields("provider", result.provider.Provider, "count", result.count, "processed", idx).Debug("processing provider records") + lastUpdate = time.Now() + } - f, err := opener.Open() - if err != nil { - return fmt.Errorf("failed to open cache entry %q: %w", opener.String(), err) - } - envelope, err := unmarshal.Envelope(f) - if err != nil { - return fmt.Errorf("failed to unmarshal cache entry %q: %w", opener.String(), err) - } + f, err := opener.Open() + if err != nil { + return fmt.Errorf("failed to open cache entry %q: %w", opener.String(), err) + } + envelope, err := unmarshal.Envelope(f) + if err != nil { + return fmt.Errorf("failed to unmarshal cache entry %q: %w", opener.String(), err) + } - for _, candidate := range processors { - if candidate.IsSupported(envelope.Schema) { - processor = candidate - log.WithFields("schema", envelope.Schema).Trace("matched with processor") - break + for _, candidate := range processors { + if candidate.IsSupported(envelope.Schema) { + processor = candidate + log.WithFields("schema", envelope.Schema).Trace("matched with processor") + break + } + } + if processor == nil { + log.WithFields("schema", envelope.Schema).Warnf("schema is not implemented for any processor. Dropping item") + continue } - } - if processor == nil { - log.WithFields("schema", envelope.Schema).Warnf("schema is not implemented for any processor. Dropping item") - continue - } - entries, err := processor.Process(bytes.NewReader(envelope.Item)) - if err != nil { - return fmt.Errorf("failed to process cache entry %q: %w", opener.String(), err) - } + entries, err := processor.Process(bytes.NewReader(envelope.Item), result.provider) + if err != nil { + return fmt.Errorf("failed to process cache entry %q: %w", opener.String(), err) + } - if err := writer.Write(entries...); err != nil { - return fmt.Errorf("failed to write records to the DB for cache entry %q: %w", opener.String(), err) + if err := writer.Write(entries...); err != nil { + return fmt.Errorf("failed to write records to the DB for cache entry %q: %w", opener.String(), err) + } } } diff --git a/pkg/process/default_schema_version.go b/pkg/process/default_schema_version.go index 369d8ecf..3f64bf84 100644 --- a/pkg/process/default_schema_version.go +++ b/pkg/process/default_schema_version.go @@ -1,5 +1,5 @@ package process -import grypeDB "github.com/anchore/grype/grype/db/v5" +import grypeDB "github.com/anchore/grype/grype/db/v6" -const DefaultSchemaVersion = grypeDB.SchemaVersion +const DefaultSchemaVersion = grypeDB.ModelVersion diff --git a/pkg/process/generate.go b/pkg/process/generate.go new file mode 100644 index 00000000..923c6fc3 --- /dev/null +++ b/pkg/process/generate.go @@ -0,0 +1,3 @@ +package process + +//go:generate go run ./internal/codename/generate/main.go diff --git a/pkg/process/internal/codename/codename.go b/pkg/process/internal/codename/codename.go new file mode 100644 index 00000000..e874cd64 --- /dev/null +++ b/pkg/process/internal/codename/codename.go @@ -0,0 +1,24 @@ +package codename + +import "strings" + +func LookupOS(osName, majorVersion, minorVersion string) string { + majorVersion = strings.TrimLeft(majorVersion, "0") + if minorVersion != "0" { + minorVersion = strings.TrimLeft(minorVersion, "0") + } + + // try to find the most specific match (major and minor version) + if versions, ok := normalizedOSCodenames[osName]; ok { + if minorMap, ok := versions[majorVersion]; ok { + if codename, ok := minorMap[minorVersion]; ok { + return codename + } + // fall back to the least specific match (only major version, allowing for any minor version explicitly) + if codename, ok := minorMap["*"]; ok { + return codename + } + } + } + return "" +} diff --git a/pkg/process/internal/codename/codename_test.go b/pkg/process/internal/codename/codename_test.go new file mode 100644 index 00000000..f43d3e34 --- /dev/null +++ b/pkg/process/internal/codename/codename_test.go @@ -0,0 +1,40 @@ +package codename + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLookupOSCodename(t *testing.T) { + tests := []struct { + Name string + OSName string + MajorVersion string + MinorVersion string + ExpectedCodename string + }{ + {Name: "Ubuntu 20.04 exact", OSName: "ubuntu", MajorVersion: "20", MinorVersion: "04", ExpectedCodename: "focal"}, + {Name: "Ubuntu 20.4 exact", OSName: "ubuntu", MajorVersion: "20", MinorVersion: "4", ExpectedCodename: "focal"}, + {Name: "Ubuntu 0 (non existent) minor", OSName: "ubuntu", MajorVersion: "20", MinorVersion: "0", ExpectedCodename: ""}, + {Name: "Ubuntu empty minor", OSName: "ubuntu", MajorVersion: "10", MinorVersion: "", ExpectedCodename: ""}, + {Name: "Debian empty minor", OSName: "debian", MajorVersion: "10", MinorVersion: "", ExpectedCodename: "buster"}, + {Name: "Ubuntu leading zeros in major", OSName: "ubuntu", MajorVersion: "020", MinorVersion: "04", ExpectedCodename: "focal"}, + {Name: "Debian leading zeros in major", OSName: "debian", MajorVersion: "010", MinorVersion: "", ExpectedCodename: "buster"}, + {Name: "Debian bad minor", OSName: "debian", MajorVersion: "11", MinorVersion: "99", ExpectedCodename: "bullseye"}, + {Name: "Ubuntu bad minor", OSName: "ubuntu", MajorVersion: "22", MinorVersion: "99", ExpectedCodename: ""}, + {Name: "Ubuntu 6.10 exact (legacy)", OSName: "ubuntu", MajorVersion: "6", MinorVersion: "10", ExpectedCodename: "edgy"}, + {Name: "Ubuntu 6.6 exact (legacy)", OSName: "ubuntu", MajorVersion: "6", MinorVersion: "6", ExpectedCodename: "dapper"}, + {Name: "Debian 2.1 exact", OSName: "debian", MajorVersion: "2", MinorVersion: "1", ExpectedCodename: "slink"}, + {Name: "Debian 2 fallback to *", OSName: "debian", MajorVersion: "2", MinorVersion: "0", ExpectedCodename: "hamm"}, + {Name: "Invalid OS name", OSName: "nonexistentOS", MajorVersion: "10", MinorVersion: "04", ExpectedCodename: ""}, + {Name: "Invalid major version", OSName: "ubuntu", MajorVersion: "99", MinorVersion: "04", ExpectedCodename: ""}, + } + + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + actualCodename := LookupOS(tt.OSName, tt.MajorVersion, tt.MinorVersion) + assert.Equal(t, tt.ExpectedCodename, actualCodename) + }) + } +} diff --git a/pkg/process/internal/codename/codenames_generated.go b/pkg/process/internal/codename/codenames_generated.go new file mode 100644 index 00000000..6dfc99c9 --- /dev/null +++ b/pkg/process/internal/codename/codenames_generated.go @@ -0,0 +1,114 @@ +// DO NOT EDIT: generated by pkg/process/v6/transformers/internal/codename/main.go + +package codename + +var normalizedOSCodenames = map[string]map[string]map[string]string{ + "debian": { + "1": { + "1": "buzz", + "2": "rex", + "3": "bo", + }, + "10": {"*": "buster"}, + "11": {"*": "bullseye"}, + "12": {"*": "bookworm"}, + "2": { + "0": "hamm", + "1": "slink", + "2": "potato", + }, + "3": { + "0": "woody", + "1": "sarge", + }, + "4": {"*": "etch"}, + "5": {"*": "lenny"}, + "6": {"*": "squeeze"}, + "7": {"*": "wheezy"}, + "8": {"*": "jessie"}, + "9": {"*": "stretch"}, + }, + "ubuntu": { + "10": { + "10": "maverick", + "4": "lucid", + }, + "11": { + "10": "oneiric", + "4": "natty", + }, + "12": { + "10": "quantal", + "4": "precise", + }, + "13": { + "10": "saucy", + "4": "raring", + }, + "14": { + "10": "utopic", + "4": "trusty", + }, + "15": { + "10": "wily", + "4": "vivid", + }, + "16": { + "10": "yakkety", + "4": "xenial", + }, + "17": { + "10": "artful", + "4": "zesty", + }, + "18": { + "10": "cosmic", + "4": "bionic", + }, + "19": { + "10": "eoan", + "4": "disco", + }, + "20": { + "10": "groovy", + "4": "focal", + }, + "21": { + "10": "impish", + "4": "hirsute", + }, + "22": { + "10": "kinetic", + "4": "jammy", + }, + "23": { + "10": "mantic", + "4": "lunar", + }, + "24": { + "10": "oracular", + "4": "noble", + }, + "4": {"10": "warty"}, + "5": { + "10": "breezy", + "4": "hoary", + }, + "6": { + "10": "edgy", + "6": "dapper", + }, + "7": { + "10": "gutsy", + "4": "feisty", + }, + "8": { + "10": "intrepid", + "4": "hardy", + }, + "9": { + "10": "karmic", + "4": "jaunty", + }, + }, +} diff --git a/pkg/process/internal/codename/generate/main.go b/pkg/process/internal/codename/generate/main.go new file mode 100644 index 00000000..6a29d3ac --- /dev/null +++ b/pkg/process/internal/codename/generate/main.go @@ -0,0 +1,122 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "strings" + + "github.com/dave/jennifer/jen" +) + +const ( + outputPackage = "pkg/process/internal/codename" + outputPath = "internal/codename/codenames_generated.go" // relative to where go generate is called +) + +type Version struct { + Cycle string `json:"cycle"` + Codename string `json:"codename"` +} + +func main() { + osCodenames := make(map[string]map[string]map[string]string) + + fmt.Println("Fetching and parsing data for operating system codenames") + + fmt.Println("ubuntu:") + osCodenames["ubuntu"] = fetchAndParse("https://endoflife.date/api/ubuntu.json", ubuntuHandler) + + fmt.Println("debian:") + osCodenames["debian"] = fetchAndParse("https://endoflife.date/api/debian.json", lowercaseHandler) + + fmt.Printf("Generating code for %d operating system codenames\n", len(osCodenames)) + + f := jen.NewFile("codename") + f.HeaderComment("DO NOT EDIT: generated by pkg/process/v6/transformers/internal/codename/main.go") + f.ImportName(outputPackage, "pkg") + f.Var().Id("normalizedOSCodenames").Op("=").Map(jen.String()).Map(jen.String()).Map(jen.String()).String().Values(jen.DictFunc(func(d jen.Dict) { + for osName, versions := range osCodenames { + majorMap := jen.Dict{} + for major, minors := range versions { + minorMap := jen.Dict{} + for minor, codename := range minors { + minorMap[jen.Lit(minor)] = jen.Lit(codename) + } + majorMap[jen.Lit(major)] = jen.Values(minorMap) + } + d[jen.Lit(osName)] = jen.Values(majorMap) + } + })) + + rendered := fmt.Sprintf("%#v", f) + + file, err := os.OpenFile(outputPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + panic(fmt.Errorf("unable to open file: %w", err)) + } + defer file.Close() + + if _, err := file.WriteString(rendered); err != nil { + panic(fmt.Errorf("unable to write file: %w", err)) + } + + fmt.Printf("Code generation completed and written to %s\n", outputPath) +} + +// fetchAndParse fetches the JSON data from a URL, parses it, and organizes it into a map. +func fetchAndParse(url string, handler func(string) string) map[string]map[string]string { + resp, err := http.Get(url) //nolint:gosec + if err != nil { + panic(fmt.Errorf("error fetching data from %s: %w", url, err)) + } + defer resp.Body.Close() + + data, err := io.ReadAll(resp.Body) + if err != nil { + panic(fmt.Errorf("error reading response: %w", err)) + } + + var versions []Version + if err := json.Unmarshal(data, &versions); err != nil { + panic(fmt.Errorf("error parsing JSON: %w", err)) + } + + parsedData := make(map[string]map[string]string) + for _, version := range versions { + major, minor := parseVersion(version.Cycle) + if parsedData[major] == nil { + parsedData[major] = make(map[string]string) + } + codename := handler(version.Codename) + fmt.Printf(" adding %s.%s --> %s\n", major, minor, codename) + parsedData[major][minor] = codename + } + + return parsedData +} + +func lowercaseHandler(codename string) string { + return strings.ToLower(codename) +} + +func ubuntuHandler(codename string) string { + return strings.ToLower(strings.Split(codename, " ")[0]) +} + +// parseVersion splits a version string like "20.04" into major "20" and minor "04". +func parseVersion(version string) (string, string) { + parts := strings.Split(version, ".") + major := strings.TrimLeft(parts[0], "0") + minor := "*" + if len(parts) > 1 { + if parts[1] == "0" { + minor = parts[1] + } else { + minor = strings.TrimLeft(parts[1], "0") + } + } + return major, minor +} diff --git a/pkg/process/common/clean_fixed_in_version.go b/pkg/process/internal/common/clean_fixed_in_version.go similarity index 100% rename from pkg/process/common/clean_fixed_in_version.go rename to pkg/process/internal/common/clean_fixed_in_version.go diff --git a/pkg/process/common/constraint.go b/pkg/process/internal/common/constraint.go similarity index 100% rename from pkg/process/common/constraint.go rename to pkg/process/internal/common/constraint.go diff --git a/pkg/process/common/constraint_test.go b/pkg/process/internal/common/constraint_test.go similarity index 100% rename from pkg/process/common/constraint_test.go rename to pkg/process/internal/common/constraint_test.go diff --git a/pkg/process/tests/utils.go b/pkg/process/internal/tests/utils.go similarity index 100% rename from pkg/process/tests/utils.go rename to pkg/process/internal/tests/utils.go diff --git a/pkg/process/package.go b/pkg/process/package.go index 2d010082..1ab78ca8 100644 --- a/pkg/process/package.go +++ b/pkg/process/package.go @@ -5,6 +5,7 @@ import ( "net/url" "os" "path" + "path/filepath" "strings" "time" @@ -13,6 +14,9 @@ import ( "github.com/anchore/grype-db/internal/log" "github.com/anchore/grype-db/internal/tarutil" "github.com/anchore/grype/grype/db/legacy/distribution" + grypeDBLegacy "github.com/anchore/grype/grype/db/v5" + grypeDBLegacyStore "github.com/anchore/grype/grype/db/v5/store" + v6 "github.com/anchore/grype/grype/db/v6" ) func secondsSinceEpoch() int64 { @@ -20,6 +24,78 @@ func secondsSinceEpoch() int64 { } func Package(dbDir, publishBaseURL, overrideArchiveExtension string) error { + // check if metadata file exists + if _, err := os.Stat(filepath.Join(dbDir, distribution.MetadataFileName)); os.IsNotExist(err) { + return v6Package(dbDir, overrideArchiveExtension) + } + return legacyPackage(dbDir, publishBaseURL, overrideArchiveExtension) +} + +func v6Package(dbDir, overrideArchiveExtension string) error { + extension, err := resolveV6Extension(overrideArchiveExtension) + if err != nil { + return err + } + log.WithFields("from", dbDir, "extension", extension).Info("packaging database") + + s, err := v6.NewReader(v6.Config{DBDirPath: dbDir}) + if err != nil { + return fmt.Errorf("unable to open vulnerability store: %w", err) + } + + metadata, err := s.GetDBMetadata() + if err != nil { + return fmt.Errorf("unable to get vulnerability store metadata: %w", err) + } + + if metadata.Model != v6.ModelVersion { + return fmt.Errorf("metadata model %d does not match vulnerability store model %d", v6.ModelVersion, metadata.Model) + } + + // TODO: add support for fetching all provider data + + tarName := fmt.Sprintf( + "vulnerability-db_v%s_%s_%s.%s", + fmt.Sprintf("%d.%d.%d", metadata.Model, metadata.Revision, metadata.Addition), + "TODOPROVIDERTIME", + metadata.BuildTimestamp.UTC().Format(time.RFC3339), + extension, + ) + tarPath := path.Join(dbDir, tarName) + + if err := populate(tarName, dbDir); err != nil { + return err + } + + log.WithFields("path", tarPath).Info("created database archive") + + // TODO: write out latest.json file + + return nil +} + +func resolveV6Extension(overrideArchiveExtension string) (string, error) { + var extension = "tar.xz" + + if overrideArchiveExtension != "" { + extension = strings.TrimLeft(overrideArchiveExtension, ".") + } + + var found bool + for _, valid := range []string{"tar.xz", "tar.zst", "tar.gz"} { + if valid == extension { + found = true + break + } + } + + if !found { + return "", fmt.Errorf("unsupported archive extension %q", extension) + } + return extension, nil +} + +func legacyPackage(dbDir, publishBaseURL, overrideArchiveExtension string) error { //nolint:funlen log.WithFields("from", dbDir, "url", publishBaseURL, "extension-override", overrideArchiveExtension).Info("packaging database") fs := afero.NewOsFs() @@ -32,6 +108,20 @@ func Package(dbDir, publishBaseURL, overrideArchiveExtension string) error { return fmt.Errorf("no metadata found in %q", dbDir) } + s, err := grypeDBLegacyStore.New(filepath.Join(dbDir, grypeDBLegacy.VulnerabilityStoreFileName), false) + if err != nil { + return fmt.Errorf("unable to open vulnerability store: %w", err) + } + + id, err := s.GetID() + if err != nil { + return fmt.Errorf("unable to get vulnerability store ID: %w", err) + } + + if id.SchemaVersion != metadata.Version { + return fmt.Errorf("metadata version %d does not match vulnerability store version %d", metadata.Version, id.SchemaVersion) + } + u, err := url.Parse(publishBaseURL) if err != nil { return err diff --git a/pkg/process/processors/github_processor.go b/pkg/process/processors/github_processor.go index ccab8984..febace36 100644 --- a/pkg/process/processors/github_processor.go +++ b/pkg/process/processors/github_processor.go @@ -7,11 +7,12 @@ import ( "github.com/anchore/grype-db/internal/log" "github.com/anchore/grype-db/pkg/data" + "github.com/anchore/grype-db/pkg/provider" "github.com/anchore/grype-db/pkg/provider/unmarshal" ) type githubProcessor struct { - transformer data.GitHubTransformer + transformer any } func NewGitHubProcessor(transformer data.GitHubTransformer) data.Processor { @@ -20,7 +21,13 @@ func NewGitHubProcessor(transformer data.GitHubTransformer) data.Processor { } } -func (p githubProcessor) Process(reader io.Reader) ([]data.Entry, error) { +func NewV2GitHubProcessor(transformer data.GitHubTransformerV2) data.Processor { + return &githubProcessor{ + transformer: transformer, + } +} + +func (p githubProcessor) Process(reader io.Reader, state provider.State) ([]data.Entry, error) { var results []data.Entry entries, err := unmarshal.GitHubAdvisoryEntries(reader) @@ -28,13 +35,25 @@ func (p githubProcessor) Process(reader io.Reader) ([]data.Entry, error) { return nil, err } + var handle func(entry unmarshal.GitHubAdvisory) ([]data.Entry, error) + switch t := p.transformer.(type) { + case data.GitHubTransformer: + handle = func(entry unmarshal.GitHubAdvisory) ([]data.Entry, error) { + return t(entry) + } + case data.GitHubTransformerV2: + handle = func(entry unmarshal.GitHubAdvisory) ([]data.Entry, error) { + return t(entry, state) + } + } + for _, entry := range entries { if entry.IsEmpty() { log.Warn("dropping empty GHSA entry") continue } - transformedEntries, err := p.transformer(entry) + transformedEntries, err := handle(entry) if err != nil { return nil, err } diff --git a/pkg/process/processors/github_processor_test.go b/pkg/process/processors/github_processor_test.go index f4c203ce..ea7d549f 100644 --- a/pkg/process/processors/github_processor_test.go +++ b/pkg/process/processors/github_processor_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" "github.com/anchore/grype-db/pkg/data" - testUtils "github.com/anchore/grype-db/pkg/process/tests" + testUtils "github.com/anchore/grype-db/pkg/process/internal/tests" "github.com/anchore/grype-db/pkg/provider/unmarshal" ) diff --git a/pkg/process/processors/match_exclusion_processor.go b/pkg/process/processors/match_exclusion_processor.go index cc4b1f9c..415dc54a 100644 --- a/pkg/process/processors/match_exclusion_processor.go +++ b/pkg/process/processors/match_exclusion_processor.go @@ -7,6 +7,7 @@ import ( "github.com/anchore/grype-db/internal/log" "github.com/anchore/grype-db/pkg/data" + "github.com/anchore/grype-db/pkg/provider" "github.com/anchore/grype-db/pkg/provider/unmarshal" ) @@ -20,7 +21,7 @@ func NewMatchExclusionProcessor(transformer data.MatchExclusionTransformer) data } } -func (p matchExclusionProcessor) Process(reader io.Reader) ([]data.Entry, error) { +func (p matchExclusionProcessor) Process(reader io.Reader, _ provider.State) ([]data.Entry, error) { var results []data.Entry entries, err := unmarshal.MatchExclusions(reader) diff --git a/pkg/process/processors/match_exclusion_processor_test.go b/pkg/process/processors/match_exclusion_processor_test.go index 060e27bc..4fef6d6f 100644 --- a/pkg/process/processors/match_exclusion_processor_test.go +++ b/pkg/process/processors/match_exclusion_processor_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" "github.com/anchore/grype-db/pkg/data" - testUtils "github.com/anchore/grype-db/pkg/process/tests" + testUtils "github.com/anchore/grype-db/pkg/process/internal/tests" "github.com/anchore/grype-db/pkg/provider/unmarshal" ) diff --git a/pkg/process/processors/msrc_processor.go b/pkg/process/processors/msrc_processor.go index 1fa04044..f6801e71 100644 --- a/pkg/process/processors/msrc_processor.go +++ b/pkg/process/processors/msrc_processor.go @@ -6,6 +6,7 @@ import ( "github.com/anchore/grype-db/internal/log" "github.com/anchore/grype-db/pkg/data" + "github.com/anchore/grype-db/pkg/provider" "github.com/anchore/grype-db/pkg/provider/unmarshal" ) @@ -22,7 +23,7 @@ func NewMSRCProcessor(transformer data.MSRCTransformer) data.Processor { } // Parse reads all entries in all metadata matching the supported schema and produces vulnerabilities and their corresponding metadata -func (p msrcProcessor) Process(reader io.Reader) ([]data.Entry, error) { +func (p msrcProcessor) Process(reader io.Reader, _ provider.State) ([]data.Entry, error) { var results []data.Entry entries, err := unmarshal.MSRCVulnerabilityEntries(reader) diff --git a/pkg/process/processors/msrc_processor_test.go b/pkg/process/processors/msrc_processor_test.go index e95f2f66..c1a7d439 100644 --- a/pkg/process/processors/msrc_processor_test.go +++ b/pkg/process/processors/msrc_processor_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" "github.com/anchore/grype-db/pkg/data" - testUtils "github.com/anchore/grype-db/pkg/process/tests" + testUtils "github.com/anchore/grype-db/pkg/process/internal/tests" "github.com/anchore/grype-db/pkg/provider/unmarshal" ) diff --git a/pkg/process/processors/nvd_processor.go b/pkg/process/processors/nvd_processor.go index 3d7d4985..81a92e38 100644 --- a/pkg/process/processors/nvd_processor.go +++ b/pkg/process/processors/nvd_processor.go @@ -6,11 +6,12 @@ import ( "github.com/anchore/grype-db/internal/log" "github.com/anchore/grype-db/pkg/data" + "github.com/anchore/grype-db/pkg/provider" "github.com/anchore/grype-db/pkg/provider/unmarshal" ) type nvdProcessor struct { - transformer data.NVDTransformer + transformer any } func NewNVDProcessor(transformer data.NVDTransformer) data.Processor { @@ -19,7 +20,13 @@ func NewNVDProcessor(transformer data.NVDTransformer) data.Processor { } } -func (p nvdProcessor) Process(reader io.Reader) ([]data.Entry, error) { +func NewV2NVDProcessor(transformer data.NVDTransformerV2) data.Processor { + return &nvdProcessor{ + transformer: transformer, + } +} + +func (p nvdProcessor) Process(reader io.Reader, state provider.State) ([]data.Entry, error) { var results []data.Entry entries, err := unmarshal.NvdVulnerabilityEntries(reader) @@ -27,13 +34,25 @@ func (p nvdProcessor) Process(reader io.Reader) ([]data.Entry, error) { return nil, err } + var handle func(entry unmarshal.NVDVulnerability) ([]data.Entry, error) + switch t := p.transformer.(type) { + case data.NVDTransformer: + handle = func(entry unmarshal.NVDVulnerability) ([]data.Entry, error) { + return t(entry) + } + case data.NVDTransformerV2: + handle = func(entry unmarshal.NVDVulnerability) ([]data.Entry, error) { + return t(entry, state) + } + } + for _, entry := range entries { if entry.IsEmpty() { log.Warn("dropping empty NVD entry") continue } - transformedEntries, err := p.transformer(entry.Cve) + transformedEntries, err := handle(entry.Cve) if err != nil { return nil, err } diff --git a/pkg/process/processors/nvd_processor_test.go b/pkg/process/processors/nvd_processor_test.go index e2179f83..8fa214f9 100644 --- a/pkg/process/processors/nvd_processor_test.go +++ b/pkg/process/processors/nvd_processor_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" "github.com/anchore/grype-db/pkg/data" - testUtils "github.com/anchore/grype-db/pkg/process/tests" + testUtils "github.com/anchore/grype-db/pkg/process/internal/tests" "github.com/anchore/grype-db/pkg/provider/unmarshal" ) diff --git a/pkg/process/processors/os_processor.go b/pkg/process/processors/os_processor.go index 268fc26d..4ab0d4c8 100644 --- a/pkg/process/processors/os_processor.go +++ b/pkg/process/processors/os_processor.go @@ -7,11 +7,12 @@ import ( "github.com/anchore/grype-db/internal/log" "github.com/anchore/grype-db/pkg/data" + "github.com/anchore/grype-db/pkg/provider" "github.com/anchore/grype-db/pkg/provider/unmarshal" ) type osProcessor struct { - transformer data.OSTransformer + transformer any } func NewOSProcessor(transformer data.OSTransformer) data.Processor { @@ -20,7 +21,13 @@ func NewOSProcessor(transformer data.OSTransformer) data.Processor { } } -func (p osProcessor) Process(reader io.Reader) ([]data.Entry, error) { +func NewV2OSProcessor(transformer data.OSTransformerV2) data.Processor { + return &osProcessor{ + transformer: transformer, + } +} + +func (p osProcessor) Process(reader io.Reader, state provider.State) ([]data.Entry, error) { var results []data.Entry entries, err := unmarshal.OSVulnerabilityEntries(reader) @@ -28,13 +35,25 @@ func (p osProcessor) Process(reader io.Reader) ([]data.Entry, error) { return nil, err } + var handle func(entry unmarshal.OSVulnerability) ([]data.Entry, error) + switch t := p.transformer.(type) { + case data.OSTransformer: + handle = func(entry unmarshal.OSVulnerability) ([]data.Entry, error) { + return t(entry) + } + case data.OSTransformerV2: + handle = func(entry unmarshal.OSVulnerability) ([]data.Entry, error) { + return t(entry, state) + } + } + for _, entry := range entries { if entry.IsEmpty() { log.Warn("dropping empty OS entry") continue } - transformedEntries, err := p.transformer(entry) + transformedEntries, err := handle(entry) if err != nil { return nil, err } diff --git a/pkg/process/processors/os_processor_test.go b/pkg/process/processors/os_processor_test.go index 3718fd1c..0cfdfd0a 100644 --- a/pkg/process/processors/os_processor_test.go +++ b/pkg/process/processors/os_processor_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" "github.com/anchore/grype-db/pkg/data" - testUtils "github.com/anchore/grype-db/pkg/process/tests" + testUtils "github.com/anchore/grype-db/pkg/process/internal/tests" "github.com/anchore/grype-db/pkg/provider/unmarshal" ) diff --git a/pkg/process/v1/transformers/github/transform.go b/pkg/process/v1/transformers/github/transform.go index 75fbaf22..ba2469a3 100644 --- a/pkg/process/v1/transformers/github/transform.go +++ b/pkg/process/v1/transformers/github/transform.go @@ -2,7 +2,7 @@ package github import ( "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype-db/pkg/process/common" + "github.com/anchore/grype-db/pkg/process/internal/common" "github.com/anchore/grype-db/pkg/process/v1/transformers" "github.com/anchore/grype-db/pkg/provider/unmarshal" grypeDB "github.com/anchore/grype/grype/db/v1" @@ -17,7 +17,7 @@ func Transform(vulnerability unmarshal.GitHubAdvisory) ([]data.Entry, error) { var allVulns []grypeDB.Vulnerability // Exclude entries marked as withdrawn - if vulnerability.Advisory.Withdrawn != nil { + if vulnerability.Advisory.Withdrawn != "" { return nil, nil } diff --git a/pkg/process/v1/transformers/github/transform_test.go b/pkg/process/v1/transformers/github/transform_test.go index d0cc3f1d..9e8ab758 100644 --- a/pkg/process/v1/transformers/github/transform_test.go +++ b/pkg/process/v1/transformers/github/transform_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - testUtils "github.com/anchore/grype-db/pkg/process/tests" + testUtils "github.com/anchore/grype-db/pkg/process/internal/tests" "github.com/anchore/grype-db/pkg/provider/unmarshal" grypeDB "github.com/anchore/grype/grype/db/v1" ) diff --git a/pkg/process/v1/transformers/nvd/transform_test.go b/pkg/process/v1/transformers/nvd/transform_test.go index 57e140b2..cecb2f98 100644 --- a/pkg/process/v1/transformers/nvd/transform_test.go +++ b/pkg/process/v1/transformers/nvd/transform_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - testUtils "github.com/anchore/grype-db/pkg/process/tests" + testUtils "github.com/anchore/grype-db/pkg/process/internal/tests" "github.com/anchore/grype-db/pkg/provider/unmarshal" grypeDB "github.com/anchore/grype/grype/db/v1" ) diff --git a/pkg/process/v1/transformers/nvd/unique_pkg.go b/pkg/process/v1/transformers/nvd/unique_pkg.go index 20a62777..34621368 100644 --- a/pkg/process/v1/transformers/nvd/unique_pkg.go +++ b/pkg/process/v1/transformers/nvd/unique_pkg.go @@ -7,7 +7,7 @@ import ( "github.com/umisama/go-cpe" "github.com/anchore/grype-db/internal/log" - "github.com/anchore/grype-db/pkg/process/common" + "github.com/anchore/grype-db/pkg/process/internal/common" "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd" ) diff --git a/pkg/process/v1/transformers/os/transform.go b/pkg/process/v1/transformers/os/transform.go index dcb270e1..df8c3b24 100644 --- a/pkg/process/v1/transformers/os/transform.go +++ b/pkg/process/v1/transformers/os/transform.go @@ -5,7 +5,7 @@ import ( "strings" "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype-db/pkg/process/common" + "github.com/anchore/grype-db/pkg/process/internal/common" "github.com/anchore/grype-db/pkg/process/v1/transformers" "github.com/anchore/grype-db/pkg/provider/unmarshal" grypeDB "github.com/anchore/grype/grype/db/v1" diff --git a/pkg/process/v1/transformers/os/transform_test.go b/pkg/process/v1/transformers/os/transform_test.go index bd0334df..1b3b7560 100644 --- a/pkg/process/v1/transformers/os/transform_test.go +++ b/pkg/process/v1/transformers/os/transform_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - testUtils "github.com/anchore/grype-db/pkg/process/tests" + testUtils "github.com/anchore/grype-db/pkg/process/internal/tests" "github.com/anchore/grype-db/pkg/provider/unmarshal" grypeDB "github.com/anchore/grype/grype/db/v1" ) diff --git a/pkg/process/v2/transformers/github/transform.go b/pkg/process/v2/transformers/github/transform.go index 873ac1a9..8f3fe6aa 100644 --- a/pkg/process/v2/transformers/github/transform.go +++ b/pkg/process/v2/transformers/github/transform.go @@ -2,7 +2,7 @@ package github import ( "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype-db/pkg/process/common" + "github.com/anchore/grype-db/pkg/process/internal/common" "github.com/anchore/grype-db/pkg/process/v2/transformers" "github.com/anchore/grype-db/pkg/provider/unmarshal" grypeDB "github.com/anchore/grype/grype/db/v2" @@ -17,7 +17,7 @@ func Transform(vulnerability unmarshal.GitHubAdvisory) ([]data.Entry, error) { var allVulns []grypeDB.Vulnerability // Exclude entries marked as withdrawn - if vulnerability.Advisory.Withdrawn != nil { + if vulnerability.Advisory.Withdrawn != "" { return nil, nil } diff --git a/pkg/process/v2/transformers/github/transform_test.go b/pkg/process/v2/transformers/github/transform_test.go index c548233c..d5ceed9e 100644 --- a/pkg/process/v2/transformers/github/transform_test.go +++ b/pkg/process/v2/transformers/github/transform_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - testUtils "github.com/anchore/grype-db/pkg/process/tests" + testUtils "github.com/anchore/grype-db/pkg/process/internal/tests" "github.com/anchore/grype-db/pkg/provider/unmarshal" grypeDB "github.com/anchore/grype/grype/db/v2" ) diff --git a/pkg/process/v2/transformers/nvd/transform_test.go b/pkg/process/v2/transformers/nvd/transform_test.go index 7d10d18a..b508d0ac 100644 --- a/pkg/process/v2/transformers/nvd/transform_test.go +++ b/pkg/process/v2/transformers/nvd/transform_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - testUtils "github.com/anchore/grype-db/pkg/process/tests" + testUtils "github.com/anchore/grype-db/pkg/process/internal/tests" "github.com/anchore/grype-db/pkg/provider/unmarshal" grypeDB "github.com/anchore/grype/grype/db/v2" ) diff --git a/pkg/process/v2/transformers/nvd/unique_pkg.go b/pkg/process/v2/transformers/nvd/unique_pkg.go index 20a62777..34621368 100644 --- a/pkg/process/v2/transformers/nvd/unique_pkg.go +++ b/pkg/process/v2/transformers/nvd/unique_pkg.go @@ -7,7 +7,7 @@ import ( "github.com/umisama/go-cpe" "github.com/anchore/grype-db/internal/log" - "github.com/anchore/grype-db/pkg/process/common" + "github.com/anchore/grype-db/pkg/process/internal/common" "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd" ) diff --git a/pkg/process/v2/transformers/os/transform.go b/pkg/process/v2/transformers/os/transform.go index f1c7dafc..436b7c17 100644 --- a/pkg/process/v2/transformers/os/transform.go +++ b/pkg/process/v2/transformers/os/transform.go @@ -5,7 +5,7 @@ import ( "strings" "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype-db/pkg/process/common" + "github.com/anchore/grype-db/pkg/process/internal/common" "github.com/anchore/grype-db/pkg/process/v2/transformers" "github.com/anchore/grype-db/pkg/provider/unmarshal" grypeDB "github.com/anchore/grype/grype/db/v2" diff --git a/pkg/process/v2/transformers/os/transform_test.go b/pkg/process/v2/transformers/os/transform_test.go index cae8160c..b699fb40 100644 --- a/pkg/process/v2/transformers/os/transform_test.go +++ b/pkg/process/v2/transformers/os/transform_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - testUtils "github.com/anchore/grype-db/pkg/process/tests" + testUtils "github.com/anchore/grype-db/pkg/process/internal/tests" "github.com/anchore/grype-db/pkg/provider/unmarshal" grypeDB "github.com/anchore/grype/grype/db/v2" ) diff --git a/pkg/process/v3/transformers/github/transform.go b/pkg/process/v3/transformers/github/transform.go index f9ec70cf..882e8ee7 100644 --- a/pkg/process/v3/transformers/github/transform.go +++ b/pkg/process/v3/transformers/github/transform.go @@ -2,7 +2,7 @@ package github import ( "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype-db/pkg/process/common" + "github.com/anchore/grype-db/pkg/process/internal/common" "github.com/anchore/grype-db/pkg/process/v3/transformers" "github.com/anchore/grype-db/pkg/provider/unmarshal" grypeDB "github.com/anchore/grype/grype/db/v3" @@ -17,7 +17,7 @@ func Transform(vulnerability unmarshal.GitHubAdvisory) ([]data.Entry, error) { var allVulns []grypeDB.Vulnerability // Exclude entries marked as withdrawn - if vulnerability.Advisory.Withdrawn != nil { + if vulnerability.Advisory.Withdrawn != "" { return nil, nil } diff --git a/pkg/process/v3/transformers/github/transform_test.go b/pkg/process/v3/transformers/github/transform_test.go index ce0bc28c..ff4c8af3 100644 --- a/pkg/process/v3/transformers/github/transform_test.go +++ b/pkg/process/v3/transformers/github/transform_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - testUtils "github.com/anchore/grype-db/pkg/process/tests" + testUtils "github.com/anchore/grype-db/pkg/process/internal/tests" "github.com/anchore/grype-db/pkg/provider/unmarshal" grypeDB "github.com/anchore/grype/grype/db/v3" ) diff --git a/pkg/process/v3/transformers/msrc/transform.go b/pkg/process/v3/transformers/msrc/transform.go index d1a1ea48..14d84da9 100644 --- a/pkg/process/v3/transformers/msrc/transform.go +++ b/pkg/process/v3/transformers/msrc/transform.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype-db/pkg/process/common" + "github.com/anchore/grype-db/pkg/process/internal/common" "github.com/anchore/grype-db/pkg/process/v3/transformers" "github.com/anchore/grype-db/pkg/provider/unmarshal" grypeDB "github.com/anchore/grype/grype/db/v3" diff --git a/pkg/process/v3/transformers/msrc/transform_test.go b/pkg/process/v3/transformers/msrc/transform_test.go index 4bb431c8..c9e63760 100644 --- a/pkg/process/v3/transformers/msrc/transform_test.go +++ b/pkg/process/v3/transformers/msrc/transform_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - testUtils "github.com/anchore/grype-db/pkg/process/tests" + testUtils "github.com/anchore/grype-db/pkg/process/internal/tests" "github.com/anchore/grype-db/pkg/provider/unmarshal" grypeDB "github.com/anchore/grype/grype/db/v3" ) diff --git a/pkg/process/v3/transformers/nvd/transform_test.go b/pkg/process/v3/transformers/nvd/transform_test.go index 1f136144..47232461 100644 --- a/pkg/process/v3/transformers/nvd/transform_test.go +++ b/pkg/process/v3/transformers/nvd/transform_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - testUtils "github.com/anchore/grype-db/pkg/process/tests" + testUtils "github.com/anchore/grype-db/pkg/process/internal/tests" "github.com/anchore/grype-db/pkg/provider/unmarshal" grypeDB "github.com/anchore/grype/grype/db/v3" ) diff --git a/pkg/process/v3/transformers/nvd/unique_pkg.go b/pkg/process/v3/transformers/nvd/unique_pkg.go index 4c994341..081e972b 100644 --- a/pkg/process/v3/transformers/nvd/unique_pkg.go +++ b/pkg/process/v3/transformers/nvd/unique_pkg.go @@ -7,7 +7,7 @@ import ( "github.com/umisama/go-cpe" "github.com/anchore/grype-db/internal/log" - "github.com/anchore/grype-db/pkg/process/common" + "github.com/anchore/grype-db/pkg/process/internal/common" "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd" ) diff --git a/pkg/process/v3/transformers/os/transform.go b/pkg/process/v3/transformers/os/transform.go index 1ab8d60f..6caffd18 100644 --- a/pkg/process/v3/transformers/os/transform.go +++ b/pkg/process/v3/transformers/os/transform.go @@ -5,7 +5,7 @@ import ( "strings" "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype-db/pkg/process/common" + "github.com/anchore/grype-db/pkg/process/internal/common" "github.com/anchore/grype-db/pkg/process/v3/transformers" "github.com/anchore/grype-db/pkg/provider/unmarshal" grypeDB "github.com/anchore/grype/grype/db/v3" diff --git a/pkg/process/v3/transformers/os/transform_test.go b/pkg/process/v3/transformers/os/transform_test.go index 09834c2c..823e0056 100644 --- a/pkg/process/v3/transformers/os/transform_test.go +++ b/pkg/process/v3/transformers/os/transform_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - testUtils "github.com/anchore/grype-db/pkg/process/tests" + testUtils "github.com/anchore/grype-db/pkg/process/internal/tests" "github.com/anchore/grype-db/pkg/process/v3/transformers" "github.com/anchore/grype-db/pkg/provider/unmarshal" grypeDB "github.com/anchore/grype/grype/db/v3" diff --git a/pkg/process/v4/transformers/github/transform.go b/pkg/process/v4/transformers/github/transform.go index d1599f6d..7b88d608 100644 --- a/pkg/process/v4/transformers/github/transform.go +++ b/pkg/process/v4/transformers/github/transform.go @@ -5,7 +5,7 @@ import ( "strings" "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype-db/pkg/process/common" + "github.com/anchore/grype-db/pkg/process/internal/common" "github.com/anchore/grype-db/pkg/process/v4/transformers" "github.com/anchore/grype-db/pkg/provider/unmarshal" grypeDB "github.com/anchore/grype/grype/db/v4" @@ -54,7 +54,7 @@ func Transform(vulnerability unmarshal.GitHubAdvisory) ([]data.Entry, error) { var allVulns []grypeDB.Vulnerability // Exclude entries marked as withdrawn - if vulnerability.Advisory.Withdrawn != nil { + if vulnerability.Advisory.Withdrawn != "" { return nil, nil } diff --git a/pkg/process/v4/transformers/github/transform_test.go b/pkg/process/v4/transformers/github/transform_test.go index 4e5c1c85..85343c43 100644 --- a/pkg/process/v4/transformers/github/transform_test.go +++ b/pkg/process/v4/transformers/github/transform_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - testUtils "github.com/anchore/grype-db/pkg/process/tests" + testUtils "github.com/anchore/grype-db/pkg/process/internal/tests" "github.com/anchore/grype-db/pkg/provider/unmarshal" grypeDB "github.com/anchore/grype/grype/db/v4" "github.com/anchore/grype/grype/db/v4/namespace" diff --git a/pkg/process/v4/transformers/msrc/transform.go b/pkg/process/v4/transformers/msrc/transform.go index b15dd881..6ccc225c 100644 --- a/pkg/process/v4/transformers/msrc/transform.go +++ b/pkg/process/v4/transformers/msrc/transform.go @@ -5,7 +5,7 @@ import ( "strings" "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype-db/pkg/process/common" + "github.com/anchore/grype-db/pkg/process/internal/common" "github.com/anchore/grype-db/pkg/process/v4/transformers" "github.com/anchore/grype-db/pkg/provider/unmarshal" grypeDB "github.com/anchore/grype/grype/db/v4" diff --git a/pkg/process/v4/transformers/msrc/transform_test.go b/pkg/process/v4/transformers/msrc/transform_test.go index d768204b..565df2bb 100644 --- a/pkg/process/v4/transformers/msrc/transform_test.go +++ b/pkg/process/v4/transformers/msrc/transform_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - testUtils "github.com/anchore/grype-db/pkg/process/tests" + testUtils "github.com/anchore/grype-db/pkg/process/internal/tests" "github.com/anchore/grype-db/pkg/provider/unmarshal" grypeDB "github.com/anchore/grype/grype/db/v4" ) diff --git a/pkg/process/v4/transformers/nvd/transform_test.go b/pkg/process/v4/transformers/nvd/transform_test.go index 277527b8..68ff578e 100644 --- a/pkg/process/v4/transformers/nvd/transform_test.go +++ b/pkg/process/v4/transformers/nvd/transform_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - testUtils "github.com/anchore/grype-db/pkg/process/tests" + testUtils "github.com/anchore/grype-db/pkg/process/internal/tests" "github.com/anchore/grype-db/pkg/provider/unmarshal" grypeDB "github.com/anchore/grype/grype/db/v4" ) diff --git a/pkg/process/v4/transformers/nvd/unique_pkg.go b/pkg/process/v4/transformers/nvd/unique_pkg.go index 20a62777..34621368 100644 --- a/pkg/process/v4/transformers/nvd/unique_pkg.go +++ b/pkg/process/v4/transformers/nvd/unique_pkg.go @@ -7,7 +7,7 @@ import ( "github.com/umisama/go-cpe" "github.com/anchore/grype-db/internal/log" - "github.com/anchore/grype-db/pkg/process/common" + "github.com/anchore/grype-db/pkg/process/internal/common" "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd" ) diff --git a/pkg/process/v4/transformers/os/transform.go b/pkg/process/v4/transformers/os/transform.go index 5a9796b4..9a6b5c09 100644 --- a/pkg/process/v4/transformers/os/transform.go +++ b/pkg/process/v4/transformers/os/transform.go @@ -5,7 +5,7 @@ import ( "strings" "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype-db/pkg/process/common" + "github.com/anchore/grype-db/pkg/process/internal/common" "github.com/anchore/grype-db/pkg/process/v4/transformers" "github.com/anchore/grype-db/pkg/provider/unmarshal" grypeDB "github.com/anchore/grype/grype/db/v4" diff --git a/pkg/process/v4/transformers/os/transform_test.go b/pkg/process/v4/transformers/os/transform_test.go index 745916a8..1e61445c 100644 --- a/pkg/process/v4/transformers/os/transform_test.go +++ b/pkg/process/v4/transformers/os/transform_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - testUtils "github.com/anchore/grype-db/pkg/process/tests" + testUtils "github.com/anchore/grype-db/pkg/process/internal/tests" "github.com/anchore/grype-db/pkg/process/v4/transformers" "github.com/anchore/grype-db/pkg/provider/unmarshal" grypeDB "github.com/anchore/grype/grype/db/v4" diff --git a/pkg/process/v5/transformers/github/transform.go b/pkg/process/v5/transformers/github/transform.go index 91b31f7d..d98fcd2a 100644 --- a/pkg/process/v5/transformers/github/transform.go +++ b/pkg/process/v5/transformers/github/transform.go @@ -5,7 +5,7 @@ import ( "strings" "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype-db/pkg/process/common" + "github.com/anchore/grype-db/pkg/process/internal/common" "github.com/anchore/grype-db/pkg/process/v5/transformers" "github.com/anchore/grype-db/pkg/provider/unmarshal" grypeDB "github.com/anchore/grype/grype/db/v5" @@ -45,7 +45,7 @@ func Transform(vulnerability unmarshal.GitHubAdvisory) ([]data.Entry, error) { var allVulns []grypeDB.Vulnerability // Exclude entries marked as withdrawn - if vulnerability.Advisory.Withdrawn != nil { + if vulnerability.Advisory.Withdrawn != "" { return nil, nil } diff --git a/pkg/process/v5/transformers/github/transform_test.go b/pkg/process/v5/transformers/github/transform_test.go index c6727816..96c70141 100644 --- a/pkg/process/v5/transformers/github/transform_test.go +++ b/pkg/process/v5/transformers/github/transform_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - testUtils "github.com/anchore/grype-db/pkg/process/tests" + testUtils "github.com/anchore/grype-db/pkg/process/internal/tests" "github.com/anchore/grype-db/pkg/process/v5/transformers" "github.com/anchore/grype-db/pkg/provider/unmarshal" grypeDB "github.com/anchore/grype/grype/db/v5" diff --git a/pkg/process/v5/transformers/msrc/transform.go b/pkg/process/v5/transformers/msrc/transform.go index a35e17e1..2f913bf6 100644 --- a/pkg/process/v5/transformers/msrc/transform.go +++ b/pkg/process/v5/transformers/msrc/transform.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype-db/pkg/process/common" + "github.com/anchore/grype-db/pkg/process/internal/common" "github.com/anchore/grype-db/pkg/process/v5/transformers" "github.com/anchore/grype-db/pkg/provider/unmarshal" grypeDB "github.com/anchore/grype/grype/db/v5" diff --git a/pkg/process/v5/transformers/msrc/transform_test.go b/pkg/process/v5/transformers/msrc/transform_test.go index 081965c9..2b38714a 100644 --- a/pkg/process/v5/transformers/msrc/transform_test.go +++ b/pkg/process/v5/transformers/msrc/transform_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - testUtils "github.com/anchore/grype-db/pkg/process/tests" + testUtils "github.com/anchore/grype-db/pkg/process/internal/tests" "github.com/anchore/grype-db/pkg/provider/unmarshal" grypeDB "github.com/anchore/grype/grype/db/v5" ) diff --git a/pkg/process/v5/transformers/nvd/transform_test.go b/pkg/process/v5/transformers/nvd/transform_test.go index 530094e0..bcc52a29 100644 --- a/pkg/process/v5/transformers/nvd/transform_test.go +++ b/pkg/process/v5/transformers/nvd/transform_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - testUtils "github.com/anchore/grype-db/pkg/process/tests" + testUtils "github.com/anchore/grype-db/pkg/process/internal/tests" "github.com/anchore/grype-db/pkg/provider/unmarshal" "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd" grypeDB "github.com/anchore/grype/grype/db/v5" diff --git a/pkg/process/v5/transformers/nvd/unique_pkg.go b/pkg/process/v5/transformers/nvd/unique_pkg.go index b1284ebf..c4605277 100644 --- a/pkg/process/v5/transformers/nvd/unique_pkg.go +++ b/pkg/process/v5/transformers/nvd/unique_pkg.go @@ -7,7 +7,7 @@ import ( "github.com/umisama/go-cpe" "github.com/anchore/grype-db/internal/log" - "github.com/anchore/grype-db/pkg/process/common" + "github.com/anchore/grype-db/pkg/process/internal/common" "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd" ) diff --git a/pkg/process/v5/transformers/os/transform.go b/pkg/process/v5/transformers/os/transform.go index 54b3032e..1f75a08d 100644 --- a/pkg/process/v5/transformers/os/transform.go +++ b/pkg/process/v5/transformers/os/transform.go @@ -5,7 +5,7 @@ import ( "strings" "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype-db/pkg/process/common" + "github.com/anchore/grype-db/pkg/process/internal/common" "github.com/anchore/grype-db/pkg/process/v5/transformers" "github.com/anchore/grype-db/pkg/provider/unmarshal" grypeDB "github.com/anchore/grype/grype/db/v5" diff --git a/pkg/process/v5/transformers/os/transform_test.go b/pkg/process/v5/transformers/os/transform_test.go index 5b6514ce..04caf5c9 100644 --- a/pkg/process/v5/transformers/os/transform_test.go +++ b/pkg/process/v5/transformers/os/transform_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - testUtils "github.com/anchore/grype-db/pkg/process/tests" + testUtils "github.com/anchore/grype-db/pkg/process/internal/tests" "github.com/anchore/grype-db/pkg/process/v5/transformers" "github.com/anchore/grype-db/pkg/provider/unmarshal" grypeDB "github.com/anchore/grype/grype/db/v5" diff --git a/pkg/process/v6/processors.go b/pkg/process/v6/processors.go new file mode 100644 index 00000000..e44cd7c1 --- /dev/null +++ b/pkg/process/v6/processors.go @@ -0,0 +1,47 @@ +package v6 + +import ( + "github.com/scylladb/go-set/strset" + + "github.com/anchore/grype-db/pkg/data" + "github.com/anchore/grype-db/pkg/process/processors" + "github.com/anchore/grype-db/pkg/process/v6/transformers/github" + "github.com/anchore/grype-db/pkg/process/v6/transformers/nvd" + "github.com/anchore/grype-db/pkg/process/v6/transformers/os" +) + +type Config struct { + NVD nvd.Config +} + +type Option func(cfg *Config) + +func WithCPEParts(included []string) Option { + return func(cfg *Config) { + cfg.NVD.CPEParts = strset.New(included...) + } +} + +func WithInferNVDFixVersions(infer bool) Option { + return func(cfg *Config) { + cfg.NVD.InferNVDFixVersions = infer + } +} + +func NewConfig(options ...Option) Config { + var cfg Config + for _, option := range options { + option(&cfg) + } + + return cfg +} + +func Processors(cfg Config) []data.Processor { + return []data.Processor{ + processors.NewV2GitHubProcessor(github.Transform), + // processors.NewMSRCProcessor(msrc.Transform), + processors.NewV2NVDProcessor(nvd.Transformer(cfg.NVD)), + processors.NewV2OSProcessor(os.Transform), + } +} diff --git a/pkg/process/v6/transformers/entry.go b/pkg/process/v6/transformers/entry.go new file mode 100644 index 00000000..4a970787 --- /dev/null +++ b/pkg/process/v6/transformers/entry.go @@ -0,0 +1,40 @@ +package transformers + +import ( + "fmt" + + "github.com/anchore/grype-db/pkg/data" + grypeDB "github.com/anchore/grype/grype/db/v6" +) + +type RelatedEntries struct { + VulnerabilityHandle grypeDB.VulnerabilityHandle + Provider grypeDB.Provider + Related []any +} + +func NewEntries(models ...any) []data.Entry { + var entry RelatedEntries + + for _, model := range models { + switch m := model.(type) { + case grypeDB.VulnerabilityHandle: + entry.VulnerabilityHandle = m + case grypeDB.Provider: + entry.Provider = m + case grypeDB.AffectedPackageHandle: + entry.Related = append(entry.Related, m) + case grypeDB.AffectedCPEHandle: + entry.Related = append(entry.Related, m) + default: + panic(fmt.Sprintf("unsupported model type: %T", m)) + } + } + + return []data.Entry{ + { + DBSchemaVersion: grypeDB.ModelVersion, + Data: entry, + }, + } +} diff --git a/pkg/process/v6/transformers/github/test-fixtures/GHSA-2wgc-48g2-cj5w.json b/pkg/process/v6/transformers/github/test-fixtures/GHSA-2wgc-48g2-cj5w.json new file mode 100644 index 00000000..571bd86a --- /dev/null +++ b/pkg/process/v6/transformers/github/test-fixtures/GHSA-2wgc-48g2-cj5w.json @@ -0,0 +1,42 @@ +{ + "Vulnerability": {}, + "Advisory": { + "Classification": "GENERAL", + "Severity": "Medium", + "CVSS": { + "version": "3.1", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N", + "base_metrics": { + "base_score": 6.5, + "exploitability_score": 3.9, + "impact_score": 2.5, + "base_severity": "Medium" + }, + "status": "N/A" + }, + "FixedIn": [ + { + "name": "vantage6", + "identifier": "4.2.0", + "ecosystem": "python", + "namespace": "github:python", + "range": "< 4.2.0" + } + ], + "Summary": "vantage6 has insecure SSH configuration for node and server containers", + "url": "https://github.com/advisories/GHSA-2wgc-48g2-cj5w", + "CVE": [ + "CVE-2024-21653" + ], + "Metadata": { + "CVE": [ + "CVE-2024-21653" + ] + }, + "ghsaId": "GHSA-2wgc-48g2-cj5w", + "published": "2024-01-30T20:56:46Z", + "updated": "2024-02-08T22:48:31Z", + "withdrawn": null, + "namespace": "github:python" + } +} \ No newline at end of file diff --git a/pkg/process/v6/transformers/github/test-fixtures/GHSA-3x74-v64j-qc3f.json b/pkg/process/v6/transformers/github/test-fixtures/GHSA-3x74-v64j-qc3f.json new file mode 100644 index 00000000..c46bda66 --- /dev/null +++ b/pkg/process/v6/transformers/github/test-fixtures/GHSA-3x74-v64j-qc3f.json @@ -0,0 +1,42 @@ +{ + "Vulnerability": {}, + "Advisory": { + "Classification": "GENERAL", + "Severity": "HIGH", + "CVSS": { + "version": "3.1", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H", + "base_metrics": { + "base_score": 9.8, + "exploitability_score": null, + "impact_score": null, + "base_severity": "HIGH" + }, + "status": "N/A" + }, + "FixedIn": [ + { + "name": "craftcms/cms", + "identifier": "4.4.2", + "ecosystem": "Packagist", + "namespace": "github:Packagist", + "range": "< 4.4.2" + } + ], + "Summary": "Withdrawn Advisory: CraftCMS Server-Side Template Injection vulnerability", + "url": "https://github.com/advisories/GHSA-3x74-v64j-qc3f", + "CVE": [ + "CVE-2023-30179" + ], + "Metadata": { + "CVE": [ + "CVE-2023-30179" + ] + }, + "ghsaId": "GHSA-3x74-v64j-qc3f", + "published": "2023-06-13T18:30:39Z", + "updated": "2024-03-21T17:48:19Z", + "withdrawn": "2023-06-28T23:54:39Z", + "namespace": "github:Packagist" + } +} diff --git a/pkg/process/v6/transformers/github/test-fixtures/github-github-npm-0.json b/pkg/process/v6/transformers/github/test-fixtures/github-github-npm-0.json new file mode 100644 index 00000000..d5bc11c4 --- /dev/null +++ b/pkg/process/v6/transformers/github/test-fixtures/github-github-npm-0.json @@ -0,0 +1,41 @@ +{ + "Advisory": { + "Classification": "GENERAL", + "Severity": "Critical", + "CVSS": { + "version": "3.1", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "base_metrics": { + "base_score": 9.8, + "exploitability_score": 3.9, + "impact_score": 5.9, + "base_severity": "Critical" + }, + "status": "N/A" + }, + "FixedIn": [ + { + "name": "scratch-vm", + "identifier": "0.2.0-prerelease.20200714185213", + "ecosystem": "npm", + "namespace": "github:npm", + "range": "<= 0.2.0-prerelease.20200709173451" + } + ], + "Summary": "Remote Code Execution in scratch-vm", + "url": "https://github.com/advisories/GHSA-vc9j-fhvv-8vrf", + "CVE": [ + "CVE-2020-14000" + ], + "Metadata": { + "CVE": [ + "CVE-2020-14000" + ] + }, + "ghsaId": "GHSA-vc9j-fhvv-8vrf", + "published": "2020-07-27T19:55:52Z", + "updated": "2023-01-09T05:03:39Z", + "withdrawn": null, + "namespace": "github:npm" + } +} \ No newline at end of file diff --git a/pkg/process/v6/transformers/github/test-fixtures/github-github-python-0.json b/pkg/process/v6/transformers/github/test-fixtures/github-github-python-0.json new file mode 100644 index 00000000..ad14aa60 --- /dev/null +++ b/pkg/process/v6/transformers/github/test-fixtures/github-github-python-0.json @@ -0,0 +1,58 @@ +[ + { + "Advisory": { + "CVE": [ + "CVE-2018-8768" + ], + "FixedIn": [ + { + "ecosystem": "python", + "identifier": "5.4.1", + "name": "notebook", + "namespace": "github:python", + "range": "< 5.4.1" + } + ], + "Metadata": { + "CVE": [ + "CVE-2018-8768" + ] + }, + "Severity": "Low", + "Summary": "Low severity vulnerability that affects notebook", + "ghsaId": "GHSA-6cwv-x26c-w2q4", + "namespace": "github:python", + "url": "https://github.com/advisories/GHSA-6cwv-x26c-w2q4", + "withdrawn": null + }, + "Vulnerability": {} + }, + { + "Advisory": { + "CVE": [ + "CVE-2017-5524" + ], + "FixedIn": [ + { + "ecosystem": "python", + "identifier": "4.3.12", + "name": "Plone", + "namespace": "github:python", + "range": ">= 4.0 < 4.3.12" + } + ], + "Metadata": { + "CVE": [ + "CVE-2017-5524" + ] + }, + "Severity": "Medium", + "Summary": "Moderate severity vulnerability that affects Plone", + "ghsaId": "GHSA-p5wr-vp8g-q5p4", + "namespace": "github:python", + "url": "https://github.com/advisories/GHSA-p5wr-vp8g-q5p4", + "withdrawn": null + }, + "Vulnerability": {} + } +] \ No newline at end of file diff --git a/pkg/process/v6/transformers/github/test-fixtures/github-withdrawn.json b/pkg/process/v6/transformers/github/test-fixtures/github-withdrawn.json new file mode 100644 index 00000000..04995e38 --- /dev/null +++ b/pkg/process/v6/transformers/github/test-fixtures/github-withdrawn.json @@ -0,0 +1,29 @@ + +{ + "Advisory": { + "CVE": [ + "CVE-2018-8768" + ], + "FixedIn": [ + { + "ecosystem": "python", + "identifier": "5.4.1", + "name": "notebook", + "namespace": "github:python", + "range": "< 5.4.1" + } + ], + "Metadata": { + "CVE": [ + "CVE-2018-8768" + ] + }, + "Severity": "Low", + "Summary": "Low severity vulnerability that affects notebook", + "ghsaId": "GHSA-6cwv-x26c-w2q4", + "namespace": "github:python", + "url": "https://github.com/advisories/GHSA-6cwv-x26c-w2q4", + "withdrawn": "2022-01-31T14:32:09Z" + }, + "Vulnerability": {} +} diff --git a/pkg/process/v6/transformers/github/test-fixtures/multiple-fixed-in-names.json b/pkg/process/v6/transformers/github/test-fixtures/multiple-fixed-in-names.json new file mode 100644 index 00000000..ac1ef982 --- /dev/null +++ b/pkg/process/v6/transformers/github/test-fixtures/multiple-fixed-in-names.json @@ -0,0 +1,43 @@ + +{ + "Advisory": { + "CVE": [ + "CVE-2017-5524" + ], + "FixedIn": [ + { + "ecosystem": "python", + "identifier": "4.3.12", + "name": "Plone", + "namespace": "github:python", + "range": ">= 4.0 < 4.3.12" + }, + { + "ecosystem": "python", + "identifier": "5.1b1", + "name": "Plone", + "namespace": "github:python", + "range": ">= 5.1a1 < 5.1b1" + }, + { + "ecosystem": "python", + "identifier": "5.0.7", + "name": "Plone-debug", + "namespace": "github:python", + "range": ">= 5.0rc1 < 5.0.7" + } + ], + "Metadata": { + "CVE": [ + "CVE-2017-5524" + ] + }, + "Severity": "Medium", + "Summary": "Moderate severity vulnerability that affects Plone", + "ghsaId": "GHSA-p5wr-vp8g-q5p4", + "namespace": "github:python", + "url": "https://github.com/advisories/GHSA-p5wr-vp8g-q5p4", + "withdrawn": null + }, + "Vulnerability": {} +} diff --git a/pkg/process/v6/transformers/github/transform.go b/pkg/process/v6/transformers/github/transform.go new file mode 100644 index 00000000..720b2b47 --- /dev/null +++ b/pkg/process/v6/transformers/github/transform.go @@ -0,0 +1,214 @@ +package github + +import ( + "sort" + "strings" + + "github.com/anchore/grype-db/pkg/data" + "github.com/anchore/grype-db/pkg/process/internal/common" + "github.com/anchore/grype-db/pkg/process/v6/transformers" + "github.com/anchore/grype-db/pkg/process/v6/transformers/internal" + "github.com/anchore/grype-db/pkg/provider" + "github.com/anchore/grype-db/pkg/provider/unmarshal" + grypeDB "github.com/anchore/grype/grype/db/v6" +) + +func Transform(vulnerability unmarshal.GitHubAdvisory, state provider.State) ([]data.Entry, error) { + ins := []any{ + internal.ProviderModel(state), + getVulnerability(vulnerability, state.Provider), + } + + for _, a := range getAffectedPackage(vulnerability) { + ins = append(ins, a) + } + + return transformers.NewEntries(ins...), nil +} + +func getVulnerability(vuln unmarshal.GitHubAdvisory, provider string) grypeDB.VulnerabilityHandle { + return grypeDB.VulnerabilityHandle{ + Name: vuln.Advisory.GhsaID, + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: vuln.Advisory.GhsaID, + ProviderName: provider, + // it does not appear to be possible to get "credits" or any user information from the graphql API + // for security advisories (see https://docs.github.com/en/graphql/reference/queries#securityadvisories), + // thus assigner is left empty. + Assigner: nil, + Description: strings.TrimSpace(vuln.Advisory.Summary), + ModifiedDate: internal.ParseTime(vuln.Advisory.Updated), + PublishedDate: internal.ParseTime(vuln.Advisory.Published), + WithdrawnDate: internal.ParseTime(vuln.Advisory.Withdrawn), + Status: getVulnStatus(vuln), + References: getReferences(vuln), + Aliases: getAliases(vuln), + Severities: getSeverities(vuln), + }, + } +} + +func getVulnStatus(vuln unmarshal.GitHubAdvisory) grypeDB.VulnerabilityStatus { + if vuln.Advisory.Withdrawn == "" { + return grypeDB.VulnerabilityActive + } + + return grypeDB.VulnerabilityRejected +} + +func getAffectedPackage(vuln unmarshal.GitHubAdvisory) []grypeDB.AffectedPackageHandle { + var afs []grypeDB.AffectedPackageHandle + groups := groupFixedIns(vuln) + for group, fixedIns := range groups { + for _, fixedInEntry := range fixedIns { + afs = append(afs, grypeDB.AffectedPackageHandle{ + Package: getPackage(group), + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: getAliases(vuln), + Ranges: getRanges(fixedInEntry), + }, + }) + } + } + + // stable ordering + sort.Sort(internal.ByAffectedPackage(afs)) + + return afs +} + +func getRanges(fixedInEntry unmarshal.GithubFixedIn) []grypeDB.AffectedRange { + constraint := common.EnforceSemVerConstraint(fixedInEntry.Range) + + if constraint == "" { + return nil + } + + return []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Type: getAffectedVersionFormat(fixedInEntry), + Constraint: constraint, + }, + Fix: getFix(fixedInEntry), + }, + } +} + +func getAffectedVersionFormat(fixedInEntry unmarshal.GithubFixedIn) string { + versionFormat := strings.ToLower(fixedInEntry.Ecosystem) + + if versionFormat == "pip" { + versionFormat = "python" + } + + return versionFormat +} + +func getFix(fixedInEntry unmarshal.GithubFixedIn) *grypeDB.Fix { + fixedInVersion := common.CleanFixedInVersion(fixedInEntry.Identifier) + + fixState := grypeDB.NotFixedStatus + if len(fixedInVersion) > 0 { + fixState = grypeDB.FixedStatus + } + + return &grypeDB.Fix{ + Version: fixedInVersion, + State: fixState, + } +} + +type groupIndex struct { + name string + ecosystem string +} + +func groupFixedIns(vuln unmarshal.GitHubAdvisory) map[groupIndex][]unmarshal.GithubFixedIn { + grouped := make(map[groupIndex][]unmarshal.GithubFixedIn) + + for _, fixedIn := range vuln.Advisory.FixedIn { + g := groupIndex{ + name: fixedIn.Name, + ecosystem: fixedIn.Ecosystem, + } + + grouped[g] = append(grouped[g], fixedIn) + } + return grouped +} + +func getPackageType(ecosystem string) string { + ecosystem = strings.ToLower(ecosystem) + switch ecosystem { + case "composer": + return "php-composer" + case "rust": + return "rust-crate" + case "dart": + return "dart-pub" + case "nuget": + return "dotnet" + case "go": + return "go-module" + case "java": + return "maven" // TODO: consider jenkins-plugin as a separate type. For now can determine based off of groupID + } + + return ecosystem +} + +func getPackage(group groupIndex) *grypeDB.Package { + return &grypeDB.Package{ + Name: group.name, + Type: getPackageType(group.ecosystem), + } +} + +func getSeverities(vulnerability unmarshal.GitHubAdvisory) []grypeDB.Severity { + var severities []grypeDB.Severity + + // the string severity and CVSS is not necessarily correlated (nor is CVSS guaranteed to be provided + // at all... see https://github.com/advisories/GHSA-xwg4-93c6-3h42 for example), so we need to keep them separate + cleanSeverity := strings.ToLower(strings.TrimSpace(vulnerability.Advisory.Severity)) + + if cleanSeverity != "" { + severities = append(severities, grypeDB.Severity{ + // This is the string severity based off of CVSS v3 + // see https://docs.github.com/en/code-security/security-advisories/working-with-global-security-advisories-from-the-github-advisory-database/about-the-github-advisory-database?learn=security_advisories&learnProduct=code-security#about-cvss-levels + Scheme: grypeDB.SeveritySchemeCHML, + Value: cleanSeverity, + }) + } + + if vulnerability.Advisory.CVSS != nil { + severities = append(severities, grypeDB.Severity{ + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: vulnerability.Advisory.CVSS.VectorString, + Version: vulnerability.Advisory.CVSS.Version, + Score: vulnerability.Advisory.CVSS.BaseMetrics.BaseScore, + }, + }) + } + + return severities +} + +func getAliases(vulnerability unmarshal.GitHubAdvisory) (aliases []string) { + aliases = append(aliases, vulnerability.Advisory.CVE...) + return +} + +func getReferences(vulnerability unmarshal.GitHubAdvisory) []grypeDB.Reference { + // TODO: The additional reference links are not currently captured in the vunnel result, but should be enhanced to + // https://github.com/anchore/vunnel/issues/646 to capture this + refs := []grypeDB.Reference{ + { + Tags: []string{grypeDB.AdvisoryReferenceTag}, + URL: vulnerability.Advisory.URL, + }, + } + + return refs +} diff --git a/pkg/process/v6/transformers/github/transform_test.go b/pkg/process/v6/transformers/github/transform_test.go new file mode 100644 index 00000000..8b3121cf --- /dev/null +++ b/pkg/process/v6/transformers/github/transform_test.go @@ -0,0 +1,534 @@ +package github + +import ( + "os" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" + + "github.com/anchore/grype-db/pkg/process/v6/transformers" + "github.com/anchore/grype-db/pkg/process/v6/transformers/internal" + "github.com/anchore/grype-db/pkg/provider" + "github.com/anchore/grype-db/pkg/provider/unmarshal" + grypeDB "github.com/anchore/grype/grype/db/v6" +) + +func TestTransform(t *testing.T) { + type counts struct { + providerCount int + vulnerabilityCount int + affectedPackageCount int + } + + tests := []struct { + name string + fixture string + state provider.State + wantCounts counts + }{ + { + name: "multiple fixed versions for Plone", + fixture: "test-fixtures/multiple-fixed-in-names.json", + state: provider.State{ + Provider: "github", + Version: 1, + Timestamp: time.Date(2024, 03, 01, 12, 0, 0, 0, time.UTC), + }, + wantCounts: counts{ + providerCount: 1, + vulnerabilityCount: 1, + affectedPackageCount: 3, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + advisories := loadFixture(t, tt.fixture) + require.Len(t, advisories, 1, "expected exactly one advisory") + advisory := advisories[0] + + entries, err := Transform(advisory, tt.state) + require.NoError(t, err) + require.Len(t, entries, 1, "expected exactly one data.Entry") + + entry := entries[0] + require.NotNil(t, entry.Data) + + data, ok := entry.Data.(transformers.RelatedEntries) + require.True(t, ok, "expected entry.Data to be of type RelatedEntries") + + require.NotNil(t, data.Provider, "expected a Provider") + require.Equal(t, tt.wantCounts.providerCount, 1) + + require.NotNil(t, data.VulnerabilityHandle, "expected a VulnerabilityHandle") + require.Equal(t, tt.wantCounts.vulnerabilityCount, 1) + + require.Len(t, data.Related, tt.wantCounts.affectedPackageCount, "unexpected number of related entries") + }) + } +} + +func TestGetVulnerability(t *testing.T) { + tests := []struct { + name string + expected []grypeDB.VulnerabilityHandle + }{ + { + name: "test-fixtures/GHSA-2wgc-48g2-cj5w.json", + expected: []grypeDB.VulnerabilityHandle{ + { + Name: "GHSA-2wgc-48g2-cj5w", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "GHSA-2wgc-48g2-cj5w", + ProviderName: "github", + Description: "vantage6 has insecure SSH configuration for node and server containers", + ModifiedDate: internal.ParseTime("2024-02-08T22:48:31Z"), + PublishedDate: internal.ParseTime("2024-01-30T20:56:46Z"), + WithdrawnDate: nil, + Status: grypeDB.VulnerabilityActive, + References: []grypeDB.Reference{ + { + Tags: []string{grypeDB.AdvisoryReferenceTag}, + URL: "https://github.com/advisories/GHSA-2wgc-48g2-cj5w", + }, + }, + Aliases: []string{"CVE-2024-21653"}, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHML, + Value: "medium", + }, + { + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N", + Version: "3.1", + Score: 6.5, + }, + }, + }, + }, + }, + }, + }, + { + name: "test-fixtures/GHSA-3x74-v64j-qc3f.json", + expected: []grypeDB.VulnerabilityHandle{ + { + Name: "GHSA-3x74-v64j-qc3f", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "GHSA-3x74-v64j-qc3f", + ProviderName: "github", + Description: "Withdrawn Advisory: CraftCMS Server-Side Template Injection vulnerability", + ModifiedDate: internal.ParseTime("2024-03-21T17:48:19Z"), + PublishedDate: internal.ParseTime("2023-06-13T18:30:39Z"), + WithdrawnDate: internal.ParseTime("2023-06-28T23:54:39Z"), + Status: grypeDB.VulnerabilityRejected, + References: []grypeDB.Reference{ + { + Tags: []string{grypeDB.AdvisoryReferenceTag}, + URL: "https://github.com/advisories/GHSA-3x74-v64j-qc3f", + }, + }, + Aliases: []string{"CVE-2023-30179"}, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHML, + Value: "high", + }, + { + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H", + Version: "3.1", + Score: 9.8, + }, + }, + }, + }, + }, + }, + }, + { + name: "test-fixtures/github-github-npm-0.json", + expected: []grypeDB.VulnerabilityHandle{ + { + Name: "GHSA-vc9j-fhvv-8vrf", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "GHSA-vc9j-fhvv-8vrf", + ProviderName: "github", + Description: "Remote Code Execution in scratch-vm", + ModifiedDate: internal.ParseTime("2023-01-09T05:03:39Z"), + PublishedDate: internal.ParseTime("2020-07-27T19:55:52Z"), + WithdrawnDate: nil, + Status: grypeDB.VulnerabilityActive, + References: []grypeDB.Reference{ + { + Tags: []string{grypeDB.AdvisoryReferenceTag}, + URL: "https://github.com/advisories/GHSA-vc9j-fhvv-8vrf", + }, + }, + Aliases: []string{"CVE-2020-14000"}, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHML, + Value: "critical", + }, + { + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + Version: "3.1", + Score: 9.8, + }, + }, + }, + }, + }, + }, + }, + { + name: "test-fixtures/github-github-python-0.json", + expected: []grypeDB.VulnerabilityHandle{ + { + Name: "GHSA-6cwv-x26c-w2q4", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "GHSA-6cwv-x26c-w2q4", + ProviderName: "github", + Status: "active", + Description: "Low severity vulnerability that affects notebook", + References: []grypeDB.Reference{ + { + URL: "https://github.com/advisories/GHSA-6cwv-x26c-w2q4", + Tags: []string{"advisory"}, + }, + }, + + Aliases: []string{"CVE-2018-8768"}, + + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHML, + Value: "low", + }, + }, + }, + }, + { + Name: "GHSA-p5wr-vp8g-q5p4", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "GHSA-p5wr-vp8g-q5p4", + ProviderName: "github", + Status: "active", + Description: "Moderate severity vulnerability that affects Plone", + References: []grypeDB.Reference{ + { + URL: "https://github.com/advisories/GHSA-p5wr-vp8g-q5p4", + Tags: []string{"advisory"}, + }, + }, + Aliases: []string{"CVE-2017-5524"}, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHML, + Value: "medium", + }, + }, + }, + }, + }, + }, + { + name: "test-fixtures/github-withdrawn.json", + expected: []grypeDB.VulnerabilityHandle{ + { + Name: "GHSA-6cwv-x26c-w2q4", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "GHSA-6cwv-x26c-w2q4", + ProviderName: "github", + Description: "Low severity vulnerability that affects notebook", + ModifiedDate: nil, + PublishedDate: nil, + WithdrawnDate: internal.ParseTime("2022-01-31T14:32:09Z"), + Status: grypeDB.VulnerabilityRejected, + References: []grypeDB.Reference{ + { + Tags: []string{grypeDB.AdvisoryReferenceTag}, + URL: "https://github.com/advisories/GHSA-6cwv-x26c-w2q4", + }, + }, + Aliases: []string{"CVE-2018-8768"}, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHML, + Value: "low", + }, + }, + }, + }, + }, + }, + { + name: "test-fixtures/multiple-fixed-in-names.json", + expected: []grypeDB.VulnerabilityHandle{ + { + Name: "GHSA-p5wr-vp8g-q5p4", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "GHSA-p5wr-vp8g-q5p4", + ProviderName: "github", + Description: "Moderate severity vulnerability that affects Plone", + Status: grypeDB.VulnerabilityActive, + References: []grypeDB.Reference{ + { + Tags: []string{grypeDB.AdvisoryReferenceTag}, + URL: "https://github.com/advisories/GHSA-p5wr-vp8g-q5p4", + }, + }, + Aliases: []string{"CVE-2017-5524"}, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHML, + Value: "medium", + }, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + advisories := loadFixture(t, tt.name) + var results []grypeDB.VulnerabilityHandle + + for _, advisory := range advisories { + result := getVulnerability(advisory, "github") + results = append(results, result) + } + if d := cmp.Diff(tt.expected, results); d != "" { + t.Fatalf("unexpected result: %s", d) + } + }) + } +} + +func TestGetAffectedPackage(t *testing.T) { + tests := []struct { + name string + expected []grypeDB.AffectedPackageHandle + }{ + { + name: "test-fixtures/GHSA-2wgc-48g2-cj5w.json", + expected: []grypeDB.AffectedPackageHandle{ + { + Package: &grypeDB.Package{ + Name: "vantage6", + Type: "python", + }, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2024-21653"}, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Type: "python", + Constraint: "<4.2.0", + }, + Fix: &grypeDB.Fix{ + Version: "4.2.0", + State: grypeDB.FixedStatus, + }, + }, + }, + }, + }, + }, + }, + { + name: "test-fixtures/GHSA-3x74-v64j-qc3f.json", + expected: []grypeDB.AffectedPackageHandle{ + { + Package: &grypeDB.Package{ + Name: "craftcms/cms", + Type: "packagist", + }, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2023-30179"}, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Type: "packagist", + Constraint: "<4.4.2", + }, + Fix: &grypeDB.Fix{ + Version: "4.4.2", + State: grypeDB.FixedStatus, + }, + }, + }, + }, + }, + }, + }, + { + name: "test-fixtures/github-github-npm-0.json", + expected: []grypeDB.AffectedPackageHandle{ + { + Package: &grypeDB.Package{ + Name: "scratch-vm", + Type: "npm", + }, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2020-14000"}, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Type: "npm", + Constraint: "<=0.2.0-prerelease.20200709173451", + }, + Fix: &grypeDB.Fix{ + Version: "0.2.0-prerelease.20200714185213", + State: grypeDB.FixedStatus, + }, + }, + }, + }, + }, + }, + }, + { + name: "test-fixtures/github-github-python-0.json", + expected: []grypeDB.AffectedPackageHandle{ + { + Package: &grypeDB.Package{ + Type: "python", + Name: "notebook", + }, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2018-8768"}, + Qualifiers: nil, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{Type: "python", Constraint: "<5.4.1"}, + Fix: &grypeDB.Fix{Version: "5.4.1", State: grypeDB.FixedStatus}, + }, + }, + }, + }, + { + Package: &grypeDB.Package{ + Type: "python", + Name: "Plone", + }, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2017-5524"}, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{Type: "python", Constraint: ">=4.0,<4.3.12"}, + Fix: &grypeDB.Fix{Version: "4.3.12", State: grypeDB.FixedStatus}, + }, + }, + }, + }, + }, + }, + { + name: "test-fixtures/multiple-fixed-in-names.json", + expected: []grypeDB.AffectedPackageHandle{ + { + Package: &grypeDB.Package{ + Name: "Plone", + Type: "python", + }, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2017-5524"}, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Type: "python", + Constraint: ">=4.0,<4.3.12", + }, + Fix: &grypeDB.Fix{ + Version: "4.3.12", + State: grypeDB.FixedStatus, + }, + }, + }, + }, + }, + { + Package: &grypeDB.Package{ + Name: "Plone", + Type: "python", + }, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2017-5524"}, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Type: "python", + Constraint: ">=5.1a1,<5.1b1", + }, + Fix: &grypeDB.Fix{ + Version: "5.1b1", + State: grypeDB.FixedStatus, + }, + }, + }, + }, + }, + { + Package: &grypeDB.Package{ + Name: "Plone-debug", + Type: "python", + }, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2017-5524"}, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Type: "python", + Constraint: ">=5.0rc1,<5.0.7", + }, + Fix: &grypeDB.Fix{ + Version: "5.0.7", + State: grypeDB.FixedStatus, + }, + }, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + advisories := loadFixture(t, tt.name) + var results []grypeDB.AffectedPackageHandle + for _, advisor := range advisories { + result := getAffectedPackage(advisor) + results = append(results, result...) + } + if d := cmp.Diff(tt.expected, results); d != "" { + t.Fatalf("unexpected result: %s", d) + } + }) + } +} + +func loadFixture(t *testing.T, path string) []unmarshal.GitHubAdvisory { + f, err := os.Open(path) + t.Cleanup(func() { + require.NoError(t, f.Close()) + }) + require.NoError(t, err) + + entries, err := unmarshal.GitHubAdvisoryEntries(f) + require.NoError(t, err) + + return entries +} diff --git a/pkg/process/v6/transformers/internal/provider.go b/pkg/process/v6/transformers/internal/provider.go new file mode 100644 index 00000000..65d33930 --- /dev/null +++ b/pkg/process/v6/transformers/internal/provider.go @@ -0,0 +1,24 @@ +package internal + +import ( + "fmt" + + "github.com/anchore/grype-db/pkg/provider" + grypeDB "github.com/anchore/grype/grype/db/v6" +) + +func ProviderModel(state provider.State) grypeDB.Provider { + var digest string + if state.Listing != nil { + if state.Listing.Algorithm != "" && state.Listing.Digest != "" { + digest = state.Listing.Algorithm + ":" + state.Listing.Digest + } + } + return grypeDB.Provider{ + ID: state.Provider, + Version: fmt.Sprintf("%d", state.Version), + Processor: state.Processor, + DateCaptured: &state.Timestamp, + InputDigest: digest, + } +} diff --git a/pkg/process/v6/transformers/internal/provider_test.go b/pkg/process/v6/transformers/internal/provider_test.go new file mode 100644 index 00000000..05afaf45 --- /dev/null +++ b/pkg/process/v6/transformers/internal/provider_test.go @@ -0,0 +1,84 @@ +package internal + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/anchore/grype-db/pkg/provider" + grypeDB "github.com/anchore/grype/grype/db/v6" +) + +func TestProviderModel(t *testing.T) { + tests := []struct { + name string + state provider.State + expected grypeDB.Provider + }{ + { + name: "valid state with listing", + state: provider.State{ + Provider: "test-provider", + Version: 2, + Processor: "test-processor", + Timestamp: time.Date(2024, 11, 15, 12, 34, 56, 0, time.UTC), + Listing: &provider.File{ + Algorithm: "sha256", + Digest: "abc123", + }, + }, + expected: grypeDB.Provider{ + ID: "test-provider", + Version: "2", + Processor: "test-processor", + DateCaptured: func() *time.Time { t := time.Date(2024, 11, 15, 12, 34, 56, 0, time.UTC); return &t }(), + InputDigest: "sha256:abc123", + }, + }, + { + name: "valid state without listing", + state: provider.State{ + Provider: "test-provider", + Version: 1, + Processor: "test-processor", + Timestamp: time.Date(2024, 11, 15, 12, 34, 56, 0, time.UTC), + Listing: nil, + }, + expected: grypeDB.Provider{ + ID: "test-provider", + Version: "1", + Processor: "test-processor", + DateCaptured: func() *time.Time { t := time.Date(2024, 11, 15, 12, 34, 56, 0, time.UTC); return &t }(), + InputDigest: "", + }, + }, + { + name: "valid state with empty listing fields", + state: provider.State{ + Provider: "test-provider", + Version: 3, + Processor: "test-processor", + Timestamp: time.Date(2024, 11, 15, 12, 34, 56, 0, time.UTC), + Listing: &provider.File{ + Algorithm: "", + Digest: "", + }, + }, + expected: grypeDB.Provider{ + ID: "test-provider", + Version: "3", + Processor: "test-processor", + DateCaptured: func() *time.Time { t := time.Date(2024, 11, 15, 12, 34, 56, 0, time.UTC); return &t }(), + InputDigest: "", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := ProviderModel(tt.state) + require.Equal(t, tt.expected, result) + }) + } +} diff --git a/pkg/process/v6/transformers/internal/sort.go b/pkg/process/v6/transformers/internal/sort.go new file mode 100644 index 00000000..63906347 --- /dev/null +++ b/pkg/process/v6/transformers/internal/sort.go @@ -0,0 +1,23 @@ +package internal + +import grypeDB "github.com/anchore/grype/grype/db/v6" + +type ByAffectedPackage []grypeDB.AffectedPackageHandle + +func (a ByAffectedPackage) Len() int { return len(a) } +func (a ByAffectedPackage) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a ByAffectedPackage) Less(i, j int) bool { + if a[i].Package.Name == a[j].Package.Name { + if a[i].Package.Type == a[j].Package.Type { + for _, b := range a[i].BlobValue.Ranges { + for _, c := range a[j].BlobValue.Ranges { + if b.Version.Constraint != c.Version.Constraint { + return b.Version.Constraint < c.Version.Constraint + } + } + } + } + return a[i].Package.Type < a[j].Package.Type + } + return a[i].Package.Name < a[j].Package.Name +} diff --git a/pkg/process/v6/transformers/internal/time.go b/pkg/process/v6/transformers/internal/time.go new file mode 100644 index 00000000..181e863b --- /dev/null +++ b/pkg/process/v6/transformers/internal/time.go @@ -0,0 +1,55 @@ +package internal + +import ( + "strings" + "time" + + "github.com/araddon/dateparse" + + "github.com/anchore/grype-db/internal/log" +) + +func ParseTime(s string) *time.Time { + s = strings.TrimSpace(s) + if s == "" { + return nil + } + t, err := time.Parse(time.RFC3339, s) + if err == nil { + return &t + } + + // check if the timezone information is missing and append UTC if needed + if !strings.Contains(s, "Z") && !strings.Contains(s, "+") && !strings.Contains(s, "-") { + s += "Z" + t, err = time.Parse(time.RFC3339, s) + if err == nil { + t = t.UTC() + return &t + } + } + + // handle formats with milliseconds but no timezone + formats := []string{ + "2006-01-02T15:04:05.000", + "2006-01-02T15:04:05.000Z", + } + + for _, format := range formats { + t, err = time.Parse(format, s) + if err == nil { + t = t.UTC() + return &t + } + } + + // handle a wide variety of other formats + t, err = dateparse.ParseAny(s) + if err == nil { + t = t.UTC() + return &t + } + + log.WithFields("time", s).Warnf("could not parse time: %v", err) + return nil +} diff --git a/pkg/process/v6/transformers/internal/time_test.go b/pkg/process/v6/transformers/internal/time_test.go new file mode 100644 index 00000000..094aaf39 --- /dev/null +++ b/pkg/process/v6/transformers/internal/time_test.go @@ -0,0 +1,97 @@ +package internal + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestParseTime(t *testing.T) { + tests := []struct { + name string + input string + expected *time.Time + }{ + { + name: "empty string", + input: "", + expected: nil, + }, + { + name: "valid RFC3339 with Z", + input: "2024-11-15T12:34:56Z", + expected: func() *time.Time { + t, _ := time.Parse(time.RFC3339, "2024-11-15T12:34:56Z") + return &t + }(), + }, + { + name: "valid RFC3339 without Z", + input: "2024-11-15T12:34:56", + expected: func() *time.Time { + t, _ := time.Parse(time.RFC3339, "2024-11-15T12:34:56Z") + return &t + }(), + }, + { + name: "valid with milliseconds no timezone", + input: "2024-11-15T12:34:56.789", + expected: func() *time.Time { + t, _ := time.Parse("2006-01-02T15:04:05.000", "2024-11-15T12:34:56.789") + utc := t.UTC() + return &utc + }(), + }, + { + name: "valid with milliseconds and Z", + input: "2024-11-15T12:34:56.789Z", + expected: func() *time.Time { + t, _ := time.Parse("2006-01-02T15:04:05.000Z", "2024-11-15T12:34:56.789Z") + utc := t.UTC() + return &utc + }(), + }, + { + name: "valid dateparse format", + input: "November 15, 2024 12:34 PM UTC", + expected: func() *time.Time { + t, _ := time.Parse(time.RFC3339, "2024-11-15T12:34:00Z") + return &t + }(), + }, + { + name: "valid date only", + input: "2024-11-15", + expected: func() *time.Time { + t, _ := time.Parse("2006-01-02", "2024-11-15") + utc := t.UTC() + return &utc + }(), + }, + { + name: "valid date with time", + input: "2024-11-15 01:02:03", + expected: func() *time.Time { + t, _ := time.Parse(time.RFC3339, "2024-11-15T01:02:03Z") + return &t + }(), + }, + { + name: "invalid time format", + input: "invalid-time", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := ParseTime(tt.input) + if tt.expected == nil { + require.Nil(t, result) + } else { + require.NotNil(t, result) + require.Equal(t, tt.expected.UTC(), result.UTC()) + } + }) + } +} diff --git a/pkg/process/v6/transformers/nvd/test-fixtures/CVE-2023-45283-platform-cpe-first.json b/pkg/process/v6/transformers/nvd/test-fixtures/CVE-2023-45283-platform-cpe-first.json new file mode 100644 index 00000000..92d845f8 --- /dev/null +++ b/pkg/process/v6/transformers/nvd/test-fixtures/CVE-2023-45283-platform-cpe-first.json @@ -0,0 +1,148 @@ +{ + "cve": { + "id": "CVE-2023-45283", + "sourceIdentifier": "security@golang.org", + "published": "2023-11-09T17:15:08.757", + "lastModified": "2023-12-14T10:15:07.947", + "vulnStatus": "Modified", + "cveTags": [], + "descriptions": [ + { + "lang": "en", + "value": "The filepath package does not recognize paths with a \\??\\ prefix as special. On Windows, a path beginning with \\??\\ is a Root Local Device path equivalent to a path beginning with \\\\?\\. Paths with a \\??\\ prefix may be used to access arbitrary locations on the system. For example, the path \\??\\c:\\x is equivalent to the more common path c:\\x. Before fix, Clean could convert a rooted path such as \\a\\..\\??\\b into the root local device path \\??\\b. Clean will now convert this to .\\??\\b. Similarly, Join(\\, ??, b) could convert a seemingly innocent sequence of path elements into the root local device path \\??\\b. Join will now convert this to \\.\\??\\b. In addition, with fix, IsAbs now correctly reports paths beginning with \\??\\ as absolute, and VolumeName correctly reports the \\??\\ prefix as a volume name. UPDATE: Go 1.20.11 and Go 1.21.4 inadvertently changed the definition of the volume name in Windows paths starting with \\?, resulting in filepath.Clean(\\?\\c:) returning \\?\\c: rather than \\?\\c:\\ (among other effects). The previous behavior has been restored." + }, + { + "lang": "es", + "value": "El paquete filepath no reconoce las rutas con el prefijo \\??\\ como especiales. En Windows, una ruta que comienza con \\??\\ es una ruta de dispositivo local raíz equivalente a una ruta que comienza con \\\\?\\. Se pueden utilizar rutas con un prefijo \\??\\ para acceder a ubicaciones arbitrarias en el sistema. Por ejemplo, la ruta \\??\\c:\\x es equivalente a la ruta más común c:\\x. Antes de la solución, Clean podía convertir una ruta raíz como \\a\\..\\??\\b en la ruta raíz del dispositivo local \\??\\b. Clean ahora convertirá esto a .\\??\\b. De manera similar, Join(\\, ??, b) podría convertir una secuencia aparentemente inocente de elementos de ruta en la ruta del dispositivo local raíz \\??\\b. Unirse ahora convertirá esto a \\.\\??\\b. Además, con la solución, IsAbs ahora informa correctamente las rutas que comienzan con \\??\\ como absolutas, y VolumeName informa correctamente el prefijo \\??\\ como nombre de volumen." + } + ], + "metrics": { + "cvssMetricV31": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "cvssData": { + "version": "3.1", + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + "attackVector": "NETWORK", + "attackComplexity": "LOW", + "privilegesRequired": "NONE", + "userInteraction": "NONE", + "scope": "UNCHANGED", + "confidentialityImpact": "HIGH", + "integrityImpact": "NONE", + "availabilityImpact": "NONE", + "baseScore": 7.5, + "baseSeverity": "HIGH" + }, + "exploitabilityScore": 3.9, + "impactScore": 3.6 + } + ] + }, + "weaknesses": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "description": [ + { + "lang": "en", + "value": "CWE-22" + } + ] + } + ], + "configurations": [ + { + "operator": "AND", + "nodes": [ + { + "operator": "OR", + "negate": false, + "cpeMatch": [ + { + "vulnerable": false, + "criteria": "cpe:2.3:o:microsoft:windows:-:*:*:*:*:*:*:*", + "matchCriteriaId": "A2572D17-1DE6-457B-99CC-64AFD54487EA" + } + ] + }, + { + "operator": "OR", + "negate": false, + "cpeMatch": [ + { + "vulnerable": true, + "criteria": "cpe:2.3:a:golang:go:*:*:*:*:*:*:*:*", + "versionEndExcluding": "1.20.11", + "matchCriteriaId": "C1E7C289-7484-4AA8-A96B-07D2E2933258" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:a:golang:go:*:*:*:*:*:*:*:*", + "versionStartIncluding": "1.21.0-0", + "versionEndExcluding": "1.21.4", + "matchCriteriaId": "4E3FC16C-41B2-4900-901F-48BDA3DC9ED2" + } + ] + } + ] + } + ], + "references": [ + { + "url": "http://www.openwall.com/lists/oss-security/2023/12/05/2", + "source": "security@golang.org" + }, + { + "url": "https://go.dev/cl/540277", + "source": "security@golang.org", + "tags": [ + "Issue Tracking", + "Vendor Advisory" + ] + }, + { + "url": "https://go.dev/cl/541175", + "source": "security@golang.org" + }, + { + "url": "https://go.dev/issue/63713", + "source": "security@golang.org", + "tags": [ + "Issue Tracking", + "Vendor Advisory" + ] + }, + { + "url": "https://go.dev/issue/64028", + "source": "security@golang.org" + }, + { + "url": "https://groups.google.com/g/golang-announce/c/4tU8LZfBFkY", + "source": "security@golang.org", + "tags": [ + "Issue Tracking", + "Mailing List", + "Vendor Advisory" + ] + }, + { + "url": "https://groups.google.com/g/golang-dev/c/6ypN5EjibjM/m/KmLVYH_uAgAJ", + "source": "security@golang.org" + }, + { + "url": "https://pkg.go.dev/vuln/GO-2023-2185", + "source": "security@golang.org", + "tags": [ + "Issue Tracking", + "Vendor Advisory" + ] + }, + { + "url": "https://security.netapp.com/advisory/ntap-20231214-0008/", + "source": "security@golang.org" + } + ] + } +} \ No newline at end of file diff --git a/pkg/process/v6/transformers/nvd/test-fixtures/CVE-2023-45283-platform-cpe-last.json b/pkg/process/v6/transformers/nvd/test-fixtures/CVE-2023-45283-platform-cpe-last.json new file mode 100644 index 00000000..549878ed --- /dev/null +++ b/pkg/process/v6/transformers/nvd/test-fixtures/CVE-2023-45283-platform-cpe-last.json @@ -0,0 +1,147 @@ +{ + "cve": { + "id": "CVE-2023-45283", + "sourceIdentifier": "security@golang.org", + "published": "2023-11-09T17:15:08.757", + "lastModified": "2023-12-14T10:15:07.947", + "vulnStatus": "Modified", + "descriptions": [ + { + "lang": "en", + "value": "The filepath package does not recognize paths with a \\??\\ prefix as special. On Windows, a path beginning with \\??\\ is a Root Local Device path equivalent to a path beginning with \\\\?\\. Paths with a \\??\\ prefix may be used to access arbitrary locations on the system. For example, the path \\??\\c:\\x is equivalent to the more common path c:\\x. Before fix, Clean could convert a rooted path such as \\a\\..\\??\\b into the root local device path \\??\\b. Clean will now convert this to .\\??\\b. Similarly, Join(\\, ??, b) could convert a seemingly innocent sequence of path elements into the root local device path \\??\\b. Join will now convert this to \\.\\??\\b. In addition, with fix, IsAbs now correctly reports paths beginning with \\??\\ as absolute, and VolumeName correctly reports the \\??\\ prefix as a volume name. UPDATE: Go 1.20.11 and Go 1.21.4 inadvertently changed the definition of the volume name in Windows paths starting with \\?, resulting in filepath.Clean(\\?\\c:) returning \\?\\c: rather than \\?\\c:\\ (among other effects). The previous behavior has been restored." + }, + { + "lang": "es", + "value": "El paquete filepath no reconoce las rutas con el prefijo \\??\\ como especiales. En Windows, una ruta que comienza con \\??\\ es una ruta de dispositivo local raíz equivalente a una ruta que comienza con \\\\?\\. Se pueden utilizar rutas con un prefijo \\??\\ para acceder a ubicaciones arbitrarias en el sistema. Por ejemplo, la ruta \\??\\c:\\x es equivalente a la ruta más común c:\\x. Antes de la solución, Clean podía convertir una ruta raíz como \\a\\..\\??\\b en la ruta raíz del dispositivo local \\??\\b. Clean ahora convertirá esto a .\\??\\b. De manera similar, Join(\\, ??, b) podría convertir una secuencia aparentemente inocente de elementos de ruta en la ruta del dispositivo local raíz \\??\\b. Unirse ahora convertirá esto a \\.\\??\\b. Además, con la solución, IsAbs ahora informa correctamente las rutas que comienzan con \\??\\ como absolutas, y VolumeName informa correctamente el prefijo \\??\\ como nombre de volumen." + } + ], + "metrics": { + "cvssMetricV31": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "cvssData": { + "version": "3.1", + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + "attackVector": "NETWORK", + "attackComplexity": "LOW", + "privilegesRequired": "NONE", + "userInteraction": "NONE", + "scope": "UNCHANGED", + "confidentialityImpact": "HIGH", + "integrityImpact": "NONE", + "availabilityImpact": "NONE", + "baseScore": 7.5, + "baseSeverity": "HIGH" + }, + "exploitabilityScore": 3.9, + "impactScore": 3.6 + } + ] + }, + "weaknesses": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "description": [ + { + "lang": "en", + "value": "CWE-22" + } + ] + } + ], + "configurations": [ + { + "operator": "AND", + "nodes": [ + { + "operator": "OR", + "negate": false, + "cpeMatch": [ + { + "vulnerable": true, + "criteria": "cpe:2.3:a:golang:go:*:*:*:*:*:*:*:*", + "versionEndExcluding": "1.20.11", + "matchCriteriaId": "C1E7C289-7484-4AA8-A96B-07D2E2933258" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:a:golang:go:*:*:*:*:*:*:*:*", + "versionStartIncluding": "1.21.0-0", + "versionEndExcluding": "1.21.4", + "matchCriteriaId": "4E3FC16C-41B2-4900-901F-48BDA3DC9ED2" + } + ] + }, + { + "operator": "OR", + "negate": false, + "cpeMatch": [ + { + "vulnerable": false, + "criteria": "cpe:2.3:o:microsoft:windows:-:*:*:*:*:*:*:*", + "matchCriteriaId": "A2572D17-1DE6-457B-99CC-64AFD54487EA" + } + ] + } + ] + } + ], + "references": [ + { + "url": "http://www.openwall.com/lists/oss-security/2023/12/05/2", + "source": "security@golang.org" + }, + { + "url": "https://go.dev/cl/540277", + "source": "security@golang.org", + "tags": [ + "Issue Tracking", + "Vendor Advisory" + ] + }, + { + "url": "https://go.dev/cl/541175", + "source": "security@golang.org" + }, + { + "url": "https://go.dev/issue/63713", + "source": "security@golang.org", + "tags": [ + "Issue Tracking", + "Vendor Advisory" + ] + }, + { + "url": "https://go.dev/issue/64028", + "source": "security@golang.org" + }, + { + "url": "https://groups.google.com/g/golang-announce/c/4tU8LZfBFkY", + "source": "security@golang.org", + "tags": [ + "Issue Tracking", + "Mailing List", + "Vendor Advisory" + ] + }, + { + "url": "https://groups.google.com/g/golang-dev/c/6ypN5EjibjM/m/KmLVYH_uAgAJ", + "source": "security@golang.org" + }, + { + "url": "https://pkg.go.dev/vuln/GO-2023-2185", + "source": "security@golang.org", + "tags": [ + "Issue Tracking", + "Vendor Advisory" + ] + }, + { + "url": "https://security.netapp.com/advisory/ntap-20231214-0008/", + "source": "security@golang.org" + } + ] + } +} \ No newline at end of file diff --git a/pkg/process/v6/transformers/nvd/test-fixtures/compound-pkg.json b/pkg/process/v6/transformers/nvd/test-fixtures/compound-pkg.json new file mode 100644 index 00000000..8e658dcd --- /dev/null +++ b/pkg/process/v6/transformers/nvd/test-fixtures/compound-pkg.json @@ -0,0 +1,115 @@ +{ + "cve": { + "id": "CVE-2018-10189", + "sourceIdentifier": "cve@mitre.org", + "published": "2018-04-17T20:29:00.410", + "lastModified": "2018-05-23T14:41:49.073", + "vulnStatus": "Analyzed", + "descriptions": [ + { + "lang": "en", + "value": "An issue was discovered in Mautic 1.x and 2.x before 2.13.0. It is possible to systematically emulate tracking cookies per contact due to tracking the contact by their auto-incremented ID. Thus, a third party can manipulate the cookie value with +1 to systematically assume being tracked as each contact in Mautic. It is then possible to retrieve information about the contact through forms that have progressive profiling enabled." + }, + { + "lang": "es", + "value": "Se ha descubierto un problema en Mautic, en versiones 1.x y 2.x anteriores a la 2.13.0. Es posible emular de forma sistemática el rastreo de cookies por contacto debido al rastreo de contacto por su ID autoincrementada. Por lo tanto, un tercero puede manipular el valor de la cookie con un +1 para asumir sistemáticamente que se está rastreando como cada contacto en Mautic. Así, sería posible recuperar información sobre el contacto a través de formularios que tengan habilitada la generación de perfiles progresiva." + } + ], + "metrics": { + "cvssMetricV30": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "cvssData": { + "version": "3.0", + "vectorString": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + "attackVector": "NETWORK", + "attackComplexity": "LOW", + "privilegesRequired": "NONE", + "userInteraction": "NONE", + "scope": "UNCHANGED", + "confidentialityImpact": "HIGH", + "integrityImpact": "NONE", + "availabilityImpact": "NONE", + "baseScore": 7.5, + "baseSeverity": "HIGH" + }, + "exploitabilityScore": 3.9, + "impactScore": 3.6 + } + ], + "cvssMetricV2": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "cvssData": { + "version": "2.0", + "vectorString": "AV:N/AC:L/Au:N/C:P/I:N/A:N", + "accessVector": "NETWORK", + "accessComplexity": "LOW", + "authentication": "NONE", + "confidentialityImpact": "PARTIAL", + "integrityImpact": "NONE", + "availabilityImpact": "NONE", + "baseScore": 5.0 + }, + "baseSeverity": "MEDIUM", + "exploitabilityScore": 10.0, + "impactScore": 2.9, + "acInsufInfo": false, + "obtainAllPrivilege": false, + "obtainUserPrivilege": false, + "obtainOtherPrivilege": false, + "userInteractionRequired": false + } + ] + }, + "weaknesses": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "description": [ + { + "lang": "en", + "value": "CWE-200" + } + ] + } + ], + "configurations": [ + { + "nodes": [ + { + "operator": "OR", + "negate": false, + "cpeMatch": [ + { + "vulnerable": true, + "criteria": "cpe:2.3:a:mautic:mautic:*:*:*:*:*:*:*:*", + "versionStartIncluding": "1.0.0", + "versionEndIncluding": "1.4.1", + "matchCriteriaId": "5779710D-099E-40EE-8DF3-55BD3179A50C" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:a:mautic:mautic:*:*:*:*:*:*:*:*", + "versionStartIncluding": "2.0.0", + "versionEndExcluding": "2.13.0", + "matchCriteriaId": "4EFAEE48-4AEF-4F8C-95E0-6E8D848D900F" + } + ] + } + ] + } + ], + "references": [ + { + "url": "https://github.com/mautic/mautic/releases/tag/2.13.0", + "source": "cve@mitre.org", + "tags": [ + "Third Party Advisory" + ] + } + ] + } +} diff --git a/pkg/process/v6/transformers/nvd/test-fixtures/cve-2020-10729.json b/pkg/process/v6/transformers/nvd/test-fixtures/cve-2020-10729.json new file mode 100644 index 00000000..89677497 --- /dev/null +++ b/pkg/process/v6/transformers/nvd/test-fixtures/cve-2020-10729.json @@ -0,0 +1,166 @@ +{ + "cve": { + "id": "CVE-2020-10729", + "sourceIdentifier": "secalert@redhat.com", + "published": "2021-05-27T19:15:07.880", + "lastModified": "2021-12-10T19:57:06.357", + "vulnStatus": "Analyzed", + "descriptions": [ + { + "lang": "en", + "value": "A flaw was found in the use of insufficiently random values in Ansible. Two random password lookups of the same length generate the equal value as the template caching action for the same file since no re-evaluation happens. The highest threat from this vulnerability would be that all passwords are exposed at once for the file. This flaw affects Ansible Engine versions before 2.9.6." + }, + { + "lang": "es", + "value": "Se encontró un fallo en el uso de valores insuficientemente aleatorios en Ansible. Dos búsquedas de contraseñas aleatorias de la misma longitud generan el mismo valor que la acción de almacenamiento en caché de la plantilla para el mismo archivo, ya que no se realiza una reevaluación. La mayor amenaza de esta vulnerabilidad sería que todas las contraseñas estén expuestas a la vez para el archivo. Este fallo afecta a Ansible Engine versiones anteriores a 2.9.6" + } + ], + "metrics": { + "cvssMetricV31": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "cvssData": { + "version": "3.1", + "vectorString": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N", + "attackVector": "LOCAL", + "attackComplexity": "LOW", + "privilegesRequired": "LOW", + "userInteraction": "NONE", + "scope": "UNCHANGED", + "confidentialityImpact": "HIGH", + "integrityImpact": "NONE", + "availabilityImpact": "NONE", + "baseScore": 5.5, + "baseSeverity": "MEDIUM" + }, + "exploitabilityScore": 1.8, + "impactScore": 3.6 + } + ], + "cvssMetricV2": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "cvssData": { + "version": "2.0", + "vectorString": "AV:L/AC:L/Au:N/C:P/I:N/A:N", + "accessVector": "LOCAL", + "accessComplexity": "LOW", + "authentication": "NONE", + "confidentialityImpact": "PARTIAL", + "integrityImpact": "NONE", + "availabilityImpact": "NONE", + "baseScore": 2.1 + }, + "baseSeverity": "LOW", + "exploitabilityScore": 3.9, + "impactScore": 2.9, + "acInsufInfo": false, + "obtainAllPrivilege": false, + "obtainUserPrivilege": false, + "obtainOtherPrivilege": false, + "userInteractionRequired": false + } + ] + }, + "weaknesses": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "description": [ + { + "lang": "en", + "value": "CWE-330" + } + ] + }, + { + "source": "secalert@redhat.com", + "type": "Secondary", + "description": [ + { + "lang": "en", + "value": "CWE-330" + } + ] + } + ], + "configurations": [ + { + "operator": "AND", + "nodes": [ + { + "operator": "OR", + "negate": false, + "cpeMatch": [ + { + "vulnerable": true, + "criteria": "cpe:2.3:a:redhat:ansible_engine:*:*:*:*:*:*:*:*", + "versionEndExcluding": "2.9.6", + "matchCriteriaId": "EDFA8005-6FBE-4032-A499-608B7FA34F56" + } + ] + }, + { + "operator": "OR", + "negate": false, + "cpeMatch": [ + { + "vulnerable": false, + "criteria": "cpe:2.3:o:redhat:enterprise_linux:7.0:*:*:*:*:*:*:*", + "matchCriteriaId": "142AD0DD-4CF3-4D74-9442-459CE3347E3A" + }, + { + "vulnerable": false, + "criteria": "cpe:2.3:o:redhat:enterprise_linux:8.0:*:*:*:*:*:*:*", + "matchCriteriaId": "F4CFF558-3C47-480D-A2F0-BABF26042943" + } + ] + } + ] + }, + { + "nodes": [ + { + "operator": "OR", + "negate": false, + "cpeMatch": [ + { + "vulnerable": true, + "criteria": "cpe:2.3:o:debian:debian_linux:10.0:*:*:*:*:*:*:*", + "matchCriteriaId": "07B237A9-69A3-4A9C-9DA0-4E06BD37AE73" + } + ] + } + ] + } + ], + "references": [ + { + "url": "https://bugzilla.redhat.com/show_bug.cgi?id=1831089", + "source": "secalert@redhat.com", + "tags": [ + "Issue Tracking", + "Vendor Advisory" + ] + }, + { + "url": "https://github.com/ansible/ansible/issues/34144", + "source": "secalert@redhat.com", + "tags": [ + "Exploit", + "Issue Tracking", + "Third Party Advisory" + ] + }, + { + "url": "https://www.debian.org/security/2021/dsa-4950", + "source": "secalert@redhat.com", + "tags": [ + "Third Party Advisory" + ] + } + ] + } +} diff --git a/pkg/process/v6/transformers/nvd/test-fixtures/cve-2022-0543.json b/pkg/process/v6/transformers/nvd/test-fixtures/cve-2022-0543.json new file mode 100644 index 00000000..09d5c187 --- /dev/null +++ b/pkg/process/v6/transformers/nvd/test-fixtures/cve-2022-0543.json @@ -0,0 +1,183 @@ +{ + "cve": { + "id": "CVE-2022-0543", + "sourceIdentifier": "security@debian.org", + "published": "2022-02-18T20:15:17.583", + "lastModified": "2023-09-29T15:55:24.533", + "vulnStatus": "Analyzed", + "cisaExploitAdd": "2022-03-28", + "cisaActionDue": "2022-04-18", + "cisaRequiredAction": "Apply updates per vendor instructions.", + "cisaVulnerabilityName": "Debian-specific Redis Server Lua Sandbox Escape Vulnerability", + "descriptions": [ + { + "lang": "en", + "value": "It was discovered, that redis, a persistent key-value database, due to a packaging issue, is prone to a (Debian-specific) Lua sandbox escape, which could result in remote code execution." + }, + { + "lang": "es", + "value": "Se ha detectado que redis, una base de datos persistente de valores clave, debido a un problema de empaquetado, es propenso a un escape del sandbox de Lua (específico de Debian), que podría resultar en una ejecución de código remota" + } + ], + "metrics": { + "cvssMetricV31": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "cvssData": { + "version": "3.1", + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", + "attackVector": "NETWORK", + "attackComplexity": "LOW", + "privilegesRequired": "NONE", + "userInteraction": "NONE", + "scope": "CHANGED", + "confidentialityImpact": "HIGH", + "integrityImpact": "HIGH", + "availabilityImpact": "HIGH", + "baseScore": 10, + "baseSeverity": "CRITICAL" + }, + "exploitabilityScore": 3.9, + "impactScore": 6 + } + ], + "cvssMetricV2": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "cvssData": { + "version": "2.0", + "vectorString": "AV:N/AC:L/Au:N/C:C/I:C/A:C", + "accessVector": "NETWORK", + "accessComplexity": "LOW", + "authentication": "NONE", + "confidentialityImpact": "COMPLETE", + "integrityImpact": "COMPLETE", + "availabilityImpact": "COMPLETE", + "baseScore": 10 + }, + "baseSeverity": "HIGH", + "exploitabilityScore": 10, + "impactScore": 10, + "acInsufInfo": false, + "obtainAllPrivilege": false, + "obtainUserPrivilege": false, + "obtainOtherPrivilege": false, + "userInteractionRequired": false + } + ] + }, + "weaknesses": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "description": [ + { + "lang": "en", + "value": "CWE-862" + } + ] + } + ], + "configurations": [ + { + "operator": "AND", + "nodes": [ + { + "operator": "OR", + "negate": false, + "cpeMatch": [ + { + "vulnerable": true, + "criteria": "cpe:2.3:a:redis:redis:-:*:*:*:*:*:*:*", + "matchCriteriaId": "5EBE5E1C-C881-4A76-9E36-4FB7C48427E6" + } + ] + }, + { + "operator": "OR", + "negate": false, + "cpeMatch": [ + { + "vulnerable": false, + "criteria": "cpe:2.3:o:canonical:ubuntu_linux:20.04:*:*:*:lts:*:*:*", + "matchCriteriaId": "902B8056-9E37-443B-8905-8AA93E2447FB" + }, + { + "vulnerable": false, + "criteria": "cpe:2.3:o:canonical:ubuntu_linux:21.10:*:*:*:-:*:*:*", + "matchCriteriaId": "3D94DA3B-FA74-4526-A0A0-A872684598C6" + }, + { + "vulnerable": false, + "criteria": "cpe:2.3:o:debian:debian_linux:9.0:*:*:*:*:*:*:*", + "matchCriteriaId": "DEECE5FC-CACF-4496-A3E7-164736409252" + }, + { + "vulnerable": false, + "criteria": "cpe:2.3:o:debian:debian_linux:10.0:*:*:*:*:*:*:*", + "matchCriteriaId": "07B237A9-69A3-4A9C-9DA0-4E06BD37AE73" + }, + { + "vulnerable": false, + "criteria": "cpe:2.3:o:debian:debian_linux:11.0:*:*:*:*:*:*:*", + "matchCriteriaId": "FA6FEEC2-9F11-4643-8827-749718254FED" + } + ] + } + ] + } + ], + "references": [ + { + "url": "http://packetstormsecurity.com/files/166885/Redis-Lua-Sandbox-Escape.html", + "source": "security@debian.org", + "tags": [ + "Exploit", + "Third Party Advisory", + "VDB Entry" + ] + }, + { + "url": "https://bugs.debian.org/1005787", + "source": "security@debian.org", + "tags": [ + "Issue Tracking", + "Patch", + "Third Party Advisory" + ] + }, + { + "url": "https://lists.debian.org/debian-security-announce/2022/msg00048.html", + "source": "security@debian.org", + "tags": [ + "Mailing List", + "Third Party Advisory" + ] + }, + { + "url": "https://security.netapp.com/advisory/ntap-20220331-0004/", + "source": "security@debian.org", + "tags": [ + "Third Party Advisory" + ] + }, + { + "url": "https://www.debian.org/security/2022/dsa-5081", + "source": "security@debian.org", + "tags": [ + "Mailing List", + "Third Party Advisory" + ] + }, + { + "url": "https://www.ubercomp.com/posts/2022-01-20_redis_on_debian_rce", + "source": "security@debian.org", + "tags": [ + "Third Party Advisory" + ] + } + ] + } +} diff --git a/pkg/process/v6/transformers/nvd/test-fixtures/invalid_cpe.json b/pkg/process/v6/transformers/nvd/test-fixtures/invalid_cpe.json new file mode 100644 index 00000000..eac2ebd4 --- /dev/null +++ b/pkg/process/v6/transformers/nvd/test-fixtures/invalid_cpe.json @@ -0,0 +1,111 @@ +{ + "cve": { + "id": "CVE-2015-8978", + "sourceIdentifier": "cve@mitre.org", + "published": "2016-11-22T17:59:00.180", + "lastModified": "2016-11-28T19:50:59.600", + "vulnStatus": "Modified", + "descriptions": [ + { + "lang": "en", + "value": "In Soap Lite (aka the SOAP::Lite extension for Perl) 1.14 and earlier, an example attack consists of defining 10 or more XML entities, each defined as consisting of 10 of the previous entity, with the document consisting of a single instance of the largest entity, which expands to one billion copies of the first entity. The amount of computer memory used for handling an external SOAP call would likely exceed that available to the process parsing the XML." + }, + { + "lang": "es", + "value": "En Soap Lite (también conocido como la extensión SOAP::Lite para Perl) 1.14 y versiones anteriores, un ejemplo de ataque consiste en definir 10 o más entidades XML, cada una definida como consistente de 10 de la entidad anterior, con el documento consistente de una única instancia de la entidad más grande, que se expande a mil millones de copias de la primera entidad. La suma de la memoria del ordenador utilizada para manejar una llamada SOAP externa probablemente superaría el disponible para el proceso de análisis del XML." + } + ], + "metrics": { + "cvssMetricV30": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "cvssData": { + "version": "3.0", + "vectorString": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "attackVector": "NETWORK", + "attackComplexity": "LOW", + "privilegesRequired": "NONE", + "userInteraction": "NONE", + "scope": "UNCHANGED", + "confidentialityImpact": "NONE", + "integrityImpact": "NONE", + "availabilityImpact": "HIGH", + "baseScore": 7.5, + "baseSeverity": "HIGH" + }, + "exploitabilityScore": 3.9, + "impactScore": 3.6 + } + ], + "cvssMetricV2": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "cvssData": { + "version": "2.0", + "vectorString": "AV:N/AC:L/Au:N/C:N/I:N/A:P", + "accessVector": "NETWORK", + "accessComplexity": "LOW", + "authentication": "NONE", + "confidentialityImpact": "NONE", + "integrityImpact": "NONE", + "availabilityImpact": "PARTIAL", + "baseScore": 5.0 + }, + "baseSeverity": "MEDIUM", + "exploitabilityScore": 10.0, + "impactScore": 2.9, + "acInsufInfo": false, + "obtainAllPrivilege": false, + "obtainUserPrivilege": false, + "obtainOtherPrivilege": false, + "userInteractionRequired": false + } + ] + }, + "weaknesses": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "description": [ + { + "lang": "en", + "value": "CWE-399" + } + ] + } + ], + "configurations": [ + { + "nodes": [ + { + "operator": "OR", + "negate": false, + "cpeMatch": [ + { + "vulnerable": true, + "criteria": "cpe:2.3:a:soap::lite_project:soap::lite:*:*:*:*:*:perl:*:*", + "versionEndIncluding": "1.14", + "matchCriteriaId": "FB4DACB9-2E9E-4CBE-825F-FC0303D8CC86" + } + ] + } + ] + } + ], + "references": [ + { + "url": "http://cpansearch.perl.org/src/PHRED/SOAP-Lite-1.20/Changes", + "source": "cve@mitre.org", + "tags": [ + "Vendor Advisory" + ] + }, + { + "url": "http://www.securityfocus.com/bid/94487", + "source": "cve@mitre.org" + } + ] + } +} diff --git a/pkg/process/v6/transformers/nvd/test-fixtures/multiple-platforms-with-application-cpe.json b/pkg/process/v6/transformers/nvd/test-fixtures/multiple-platforms-with-application-cpe.json new file mode 100644 index 00000000..40c0fa56 --- /dev/null +++ b/pkg/process/v6/transformers/nvd/test-fixtures/multiple-platforms-with-application-cpe.json @@ -0,0 +1,142 @@ +{ + "cve": { + "id": "CVE-2023-38733", + "sourceIdentifier": "psirt@us.ibm.com", + "published": "2023-08-22T22:15:08.460", + "lastModified": "2023-08-26T02:25:42.957", + "vulnStatus": "Analyzed", + "descriptions": [ + { + "lang": "en", + "value": "\nIBM Robotic Process Automation 21.0.0 through 21.0.7.1 and 23.0.0 through 23.0.1 server could allow an authenticated user to view sensitive information from installation logs. IBM X-Force Id: 262293.\n\n" + } + ], + "metrics": { + "cvssMetricV31": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "cvssData": { + "version": "3.1", + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N", + "attackVector": "NETWORK", + "attackComplexity": "LOW", + "privilegesRequired": "LOW", + "userInteraction": "NONE", + "scope": "UNCHANGED", + "confidentialityImpact": "LOW", + "integrityImpact": "NONE", + "availabilityImpact": "NONE", + "baseScore": 4.3, + "baseSeverity": "MEDIUM" + }, + "exploitabilityScore": 2.8, + "impactScore": 1.4 + }, + { + "source": "psirt@us.ibm.com", + "type": "Secondary", + "cvssData": { + "version": "3.1", + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N", + "attackVector": "NETWORK", + "attackComplexity": "LOW", + "privilegesRequired": "LOW", + "userInteraction": "NONE", + "scope": "UNCHANGED", + "confidentialityImpact": "LOW", + "integrityImpact": "NONE", + "availabilityImpact": "NONE", + "baseScore": 4.3, + "baseSeverity": "MEDIUM" + }, + "exploitabilityScore": 2.8, + "impactScore": 1.4 + } + ] + }, + "weaknesses": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "description": [ + { + "lang": "en", + "value": "CWE-532" + } + ] + }, + { + "source": "psirt@us.ibm.com", + "type": "Secondary", + "description": [ + { + "lang": "en", + "value": "CWE-532" + } + ] + } + ], + "configurations": [ + { + "operator": "AND", + "nodes": [ + { + "operator": "OR", + "negate": false, + "cpeMatch": [ + { + "vulnerable": true, + "criteria": "cpe:2.3:a:ibm:robotic_process_automation:*:*:*:*:*:*:*:*", + "versionStartIncluding": "21.0.0", + "versionEndIncluding": "21.0.7.3", + "matchCriteriaId": "DDF503DD-23DC-4B22-8873-BE94BF0F1CD1" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:a:ibm:robotic_process_automation:*:*:*:*:*:*:*:*", + "versionStartIncluding": "23.0.0", + "versionEndIncluding": "23.0.3", + "matchCriteriaId": "F513AA2B-F457-408B-8D5F-EBE657439000" + } + ] + }, + { + "operator": "OR", + "negate": false, + "cpeMatch": [ + { + "vulnerable": false, + "criteria": "cpe:2.3:a:redhat:openshift:-:*:*:*:*:*:*:*", + "matchCriteriaId": "F08E234C-BDCF-4B41-87B9-96BD5578CBBF" + }, + { + "vulnerable": false, + "criteria": "cpe:2.3:o:microsoft:windows:-:*:*:*:*:*:*:*", + "matchCriteriaId": "A2572D17-1DE6-457B-99CC-64AFD54487EA" + } + ] + } + ] + } + ], + "references": [ + { + "url": "https://exchange.xforce.ibmcloud.com/vulnerabilities/262293", + "source": "psirt@us.ibm.com", + "tags": [ + "VDB Entry", + "Vendor Advisory" + ] + }, + { + "url": "https://www.ibm.com/support/pages/node/7028223", + "source": "psirt@us.ibm.com", + "tags": [ + "Patch", + "Vendor Advisory" + ] + } + ] + } +} diff --git a/pkg/process/v6/transformers/nvd/test-fixtures/platform-cpe.json b/pkg/process/v6/transformers/nvd/test-fixtures/platform-cpe.json new file mode 100644 index 00000000..d3822e85 --- /dev/null +++ b/pkg/process/v6/transformers/nvd/test-fixtures/platform-cpe.json @@ -0,0 +1,204 @@ +{ + "cve": { + "id": "CVE-2022-26488", + "sourceIdentifier": "cve@mitre.org", + "published": "2022-03-10T17:47:45.383", + "lastModified": "2022-09-03T03:34:19.933", + "vulnStatus": "Analyzed", + "descriptions": [ + { + "lang": "en", + "value": "In Python before 3.10.3 on Windows, local users can gain privileges because the search path is inadequately secured. The installer may allow a local attacker to add user-writable directories to the system search path. To exploit, an administrator must have installed Python for all users and enabled PATH entries. A non-administrative user can trigger a repair that incorrectly adds user-writable paths into PATH, enabling search-path hijacking of other users and system services. This affects Python (CPython) through 3.7.12, 3.8.x through 3.8.12, 3.9.x through 3.9.10, and 3.10.x through 3.10.2." + }, + { + "lang": "es", + "value": "En Python versiones anteriores a 3.10.3 en Windows, los usuarios locales pueden alcanzar privilegios porque la ruta de búsqueda no está asegurada apropiadamente. El instalador puede permitir a un atacante local añadir directorios escribibles por el usuario a la ruta de búsqueda del sistema. Para explotarla, un administrador debe haber instalado Python para todos los usuarios y habilitar las entradas PATH. Un usuario no administrador puede desencadenar una reparación que añada incorrectamente rutas escribibles por el usuario en el PATH, permitiendo el secuestro de la ruta de búsqueda de otros usuarios y servicios del sistema. Esto afecta a Python (CPython) versiones hasta 3.7.12, versiones 3.8.x hasta 3.8.12, versiones 3.9.x hasta 3.9.10, y versiones 3.10.x hasta 3.10.2" + } + ], + "metrics": { + "cvssMetricV31": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "cvssData": { + "version": "3.1", + "vectorString": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H", + "attackVector": "LOCAL", + "attackComplexity": "HIGH", + "privilegesRequired": "LOW", + "userInteraction": "NONE", + "scope": "UNCHANGED", + "confidentialityImpact": "HIGH", + "integrityImpact": "HIGH", + "availabilityImpact": "HIGH", + "baseScore": 7, + "baseSeverity": "HIGH" + }, + "exploitabilityScore": 1, + "impactScore": 5.9 + } + ], + "cvssMetricV2": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "cvssData": { + "version": "2.0", + "vectorString": "AV:L/AC:M/Au:N/C:P/I:P/A:P", + "accessVector": "LOCAL", + "accessComplexity": "MEDIUM", + "authentication": "NONE", + "confidentialityImpact": "PARTIAL", + "integrityImpact": "PARTIAL", + "availabilityImpact": "PARTIAL", + "baseScore": 4.4 + }, + "baseSeverity": "MEDIUM", + "exploitabilityScore": 3.4, + "impactScore": 6.4, + "acInsufInfo": false, + "obtainAllPrivilege": false, + "obtainUserPrivilege": false, + "obtainOtherPrivilege": false, + "userInteractionRequired": false + } + ] + }, + "weaknesses": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "description": [ + { + "lang": "en", + "value": "CWE-426" + } + ] + } + ], + "configurations": [ + { + "operator": "AND", + "nodes": [ + { + "operator": "OR", + "negate": false, + "cpeMatch": [ + { + "vulnerable": true, + "criteria": "cpe:2.3:a:python:python:*:*:*:*:*:*:*:*", + "versionEndIncluding": "3.7.12", + "matchCriteriaId": "1E05F88A-70C2-4DB6-9CCC-1D599AD26D4C" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:a:python:python:*:*:*:*:*:*:*:*", + "versionStartIncluding": "3.8.0", + "versionEndIncluding": "3.8.12", + "matchCriteriaId": "E80CA0FB-E708-4E92-BF36-7267F799FF8D" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:a:python:python:*:*:*:*:*:*:*:*", + "versionStartIncluding": "3.9.0", + "versionEndIncluding": "3.9.10", + "matchCriteriaId": "DD4B9F29-F505-4721-A630-C75103942F29" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:a:python:python:*:*:*:*:*:*:*:*", + "versionStartIncluding": "3.10.0", + "versionEndIncluding": "3.10.2", + "matchCriteriaId": "D5B55D1D-031C-4006-A368-BB66C2057916" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:a:python:python:3.11.0:alpha1:*:*:*:*:*:*", + "matchCriteriaId": "514A577E-5E60-40BA-ABD0-A8C5EB28BD90" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:a:python:python:3.11.0:alpha2:*:*:*:*:*:*", + "matchCriteriaId": "83B71795-9C81-4E5F-967C-C11808F24B05" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:a:python:python:3.11.0:alpha3:*:*:*:*:*:*", + "matchCriteriaId": "3F6F71F3-299E-4A4B-ADD1-EAD5A1D433E2" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:a:python:python:3.11.0:alpha4:*:*:*:*:*:*", + "matchCriteriaId": "09BBF4E9-EA54-41B5-948E-8E3D2660B7EF" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:a:python:python:3.11.0:alpha4:*:*:*:*:*:*", + "matchCriteriaId": "D9BBF4E9-EA54-41B5-948E-8E3D2660B7EF" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:a:python:python:3.11.0:alpha5:*:*:*:*:*:*", + "matchCriteriaId": "AEBFDCE7-81D4-4741-BB88-12C704515F5C" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:a:python:python:3.11.0:alpha6:*:*:*:*:*:*", + "matchCriteriaId": "156EB4C2-EFB7-4CEB-804D-93DB62992A63" + } + ] + }, + { + "operator": "OR", + "negate": false, + "cpeMatch": [ + { + "vulnerable": false, + "criteria": "cpe:2.3:o:microsoft:windows:-:*:*:*:*:*:*:*", + "matchCriteriaId": "A2572D17-1DE6-457B-99CC-64AFD54487EA" + } + ] + } + ] + }, + { + "operator": "AND", + "nodes": [ + { + "operator": "OR", + "negate": false, + "cpeMatch": [ + { + "vulnerable": true, + "criteria": "cpe:2.3:a:netapp:active_iq_unified_manager:-:*:*:*:*:windows:*:*", + "matchCriteriaId": "B55E8D50-99B4-47EC-86F9-699B67D473CE" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:a:netapp:ontap_select_deploy_administration_utility:-:*:*:*:*:*:*:*", + "matchCriteriaId": "E7CF3019-975D-40BB-A8A4-894E62BD3797" + } + ] + } + ] + } + ], + "references": [ + { + "url": "https://mail.python.org/archives/list/security-announce@python.org/thread/657Z4XULWZNIY5FRP3OWXHYKUSIH6DMN/", + "source": "cve@mitre.org", + "tags": [ + "Patch", + "Vendor Advisory" + ] + }, + { + "url": "https://security.netapp.com/advisory/ntap-20220419-0005/", + "source": "cve@mitre.org", + "tags": [ + "Third Party Advisory" + ] + } + ] + } + } diff --git a/pkg/process/v6/transformers/nvd/test-fixtures/single-package-multi-distro.json b/pkg/process/v6/transformers/nvd/test-fixtures/single-package-multi-distro.json new file mode 100644 index 00000000..ed108475 --- /dev/null +++ b/pkg/process/v6/transformers/nvd/test-fixtures/single-package-multi-distro.json @@ -0,0 +1,174 @@ +{ + "cve": { + "id": "CVE-2018-1000222", + "sourceIdentifier": "cve@mitre.org", + "published": "2018-08-20T20:29:01.347", + "lastModified": "2020-03-31T02:15:12.667", + "vulnStatus": "Modified", + "descriptions": [ + { + "lang": "en", + "value": "Libgd version 2.2.5 contains a Double Free Vulnerability vulnerability in gdImageBmpPtr Function that can result in Remote Code Execution . This attack appear to be exploitable via Specially Crafted Jpeg Image can trigger double free. This vulnerability appears to have been fixed in after commit ac16bdf2d41724b5a65255d4c28fb0ec46bc42f5." + }, + { + "lang": "es", + "value": "Libgd 2.2.5 contiene una vulnerabilidad de doble liberación (double free) en la función gdImageBmpPtr que puede resultar en la ejecución remota de código. Este ataque parece ser explotable mediante una imagen JPEG especialmente manipulada que desencadene una doble liberación (double free). La vulnerabilidad parece haber sido solucionada tras el commit con ID ac16bdf2d41724b5a65255d4c28fb0ec46bc42f5." + } + ], + "metrics": { + "cvssMetricV30": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "cvssData": { + "version": "3.0", + "vectorString": "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", + "attackVector": "NETWORK", + "attackComplexity": "LOW", + "privilegesRequired": "NONE", + "userInteraction": "REQUIRED", + "scope": "UNCHANGED", + "confidentialityImpact": "HIGH", + "integrityImpact": "HIGH", + "availabilityImpact": "HIGH", + "baseScore": 8.8, + "baseSeverity": "HIGH" + }, + "exploitabilityScore": 2.8, + "impactScore": 5.9 + } + ], + "cvssMetricV2": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "cvssData": { + "version": "2.0", + "vectorString": "AV:N/AC:M/Au:N/C:P/I:P/A:P", + "accessVector": "NETWORK", + "accessComplexity": "MEDIUM", + "authentication": "NONE", + "confidentialityImpact": "PARTIAL", + "integrityImpact": "PARTIAL", + "availabilityImpact": "PARTIAL", + "baseScore": 6.8 + }, + "baseSeverity": "MEDIUM", + "exploitabilityScore": 8.6, + "impactScore": 6.4, + "acInsufInfo": false, + "obtainAllPrivilege": false, + "obtainUserPrivilege": false, + "obtainOtherPrivilege": false, + "userInteractionRequired": true + } + ] + }, + "weaknesses": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "description": [ + { + "lang": "en", + "value": "CWE-415" + } + ] + } + ], + "configurations": [ + { + "nodes": [ + { + "operator": "OR", + "negate": false, + "cpeMatch": [ + { + "vulnerable": true, + "criteria": "cpe:2.3:a:libgd:libgd:2.2.5:*:*:*:*:*:*:*", + "matchCriteriaId": "C257CC1C-BF6A-4125-AA61-9C2D09096084" + } + ] + } + ] + }, + { + "nodes": [ + { + "operator": "OR", + "negate": false, + "cpeMatch": [ + { + "vulnerable": true, + "criteria": "cpe:2.3:o:canonical:ubuntu_linux:14.04:*:*:*:lts:*:*:*", + "matchCriteriaId": "B5A6F2F3-4894-4392-8296-3B8DD2679084" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:o:canonical:ubuntu_linux:16.04:*:*:*:lts:*:*:*", + "matchCriteriaId": "F7016A2A-8365-4F1A-89A2-7A19F2BCAE5B" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:o:canonical:ubuntu_linux:18.04:*:*:*:lts:*:*:*", + "matchCriteriaId": "23A7C53F-B80F-4E6A-AFA9-58EEA84BE11D" + } + ] + } + ] + }, + { + "nodes": [ + { + "operator": "OR", + "negate": false, + "cpeMatch": [ + { + "vulnerable": true, + "criteria": "cpe:2.3:o:debian:debian_linux:8.0:*:*:*:*:*:*:*", + "matchCriteriaId": "C11E6FB0-C8C0-4527-9AA0-CB9B316F8F43" + } + ] + } + ] + } + ], + "references": [ + { + "url": "https://github.com/libgd/libgd/issues/447", + "source": "cve@mitre.org", + "tags": [ + "Issue Tracking", + "Third Party Advisory" + ] + }, + { + "url": "https://lists.debian.org/debian-lts-announce/2019/01/msg00028.html", + "source": "cve@mitre.org", + "tags": [ + "Mailing List", + "Third Party Advisory" + ] + }, + { + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/3CZ2QADQTKRHTGB2AHD7J4QQNDLBEMM6/", + "source": "cve@mitre.org" + }, + { + "url": "https://security.gentoo.org/glsa/201903-18", + "source": "cve@mitre.org", + "tags": [ + "Third Party Advisory" + ] + }, + { + "url": "https://usn.ubuntu.com/3755-1/", + "source": "cve@mitre.org", + "tags": [ + "Mitigation", + "Third Party Advisory" + ] + } + ] + } +} diff --git a/pkg/process/v6/transformers/nvd/test-fixtures/version-range.json b/pkg/process/v6/transformers/nvd/test-fixtures/version-range.json new file mode 100644 index 00000000..3df5b86d --- /dev/null +++ b/pkg/process/v6/transformers/nvd/test-fixtures/version-range.json @@ -0,0 +1,121 @@ +{ + "cve": { + "id": "CVE-2018-5487", + "sourceIdentifier": "security-alert@netapp.com", + "published": "2018-05-24T14:29:00.390", + "lastModified": "2018-07-05T13:52:30.627", + "vulnStatus": "Analyzed", + "descriptions": [ + { + "lang": "en", + "value": "NetApp OnCommand Unified Manager for Linux versions 7.2 through 7.3 ship with the Java Management Extension Remote Method Invocation (JMX RMI) service bound to the network, and are susceptible to unauthenticated remote code execution." + }, + { + "lang": "es", + "value": "NetApp OnCommand Unified Manager for Linux, de la versión 7.2 hasta la 7.3, se distribuye con el servicio Java Management Extension Remote Method Invocation (JMX RMI) enlazado a la red y es susceptible a la ejecución remota de código sin autenticación." + } + ], + "metrics": { + "cvssMetricV30": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "cvssData": { + "version": "3.0", + "vectorString": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "attackVector": "NETWORK", + "attackComplexity": "LOW", + "privilegesRequired": "NONE", + "userInteraction": "NONE", + "scope": "UNCHANGED", + "confidentialityImpact": "HIGH", + "integrityImpact": "HIGH", + "availabilityImpact": "HIGH", + "baseScore": 9.8, + "baseSeverity": "CRITICAL" + }, + "exploitabilityScore": 3.9, + "impactScore": 5.9 + } + ], + "cvssMetricV2": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "cvssData": { + "version": "2.0", + "vectorString": "AV:N/AC:L/Au:N/C:P/I:P/A:P", + "accessVector": "NETWORK", + "accessComplexity": "LOW", + "authentication": "NONE", + "confidentialityImpact": "PARTIAL", + "integrityImpact": "PARTIAL", + "availabilityImpact": "PARTIAL", + "baseScore": 7.5 + }, + "baseSeverity": "HIGH", + "exploitabilityScore": 10.0, + "impactScore": 6.4, + "acInsufInfo": true, + "obtainAllPrivilege": false, + "obtainUserPrivilege": false, + "obtainOtherPrivilege": false, + "userInteractionRequired": false + } + ] + }, + "weaknesses": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "description": [ + { + "lang": "en", + "value": "CWE-20" + } + ] + } + ], + "configurations": [ + { + "operator": "AND", + "nodes": [ + { + "operator": "OR", + "negate": false, + "cpeMatch": [ + { + "vulnerable": true, + "criteria": "cpe:2.3:a:netapp:oncommand_unified_manager:*:*:*:*:*:*:*:*", + "versionStartIncluding": "7.2", + "versionEndIncluding": "7.3", + "matchCriteriaId": "A5949307-3E9B-441F-B008-81A0E0228DC0" + } + ] + }, + { + "operator": "OR", + "negate": false, + "cpeMatch": [ + { + "vulnerable": false, + "criteria": "cpe:2.3:o:linux:linux_kernel:-:*:*:*:*:*:*:*", + "matchCriteriaId": "703AF700-7A70-47E2-BC3A-7FD03B3CA9C1" + } + ] + } + ] + } + ], + "references": [ + { + "url": "https://security.netapp.com/advisory/ntap-20180523-0001/", + "source": "security-alert@netapp.com", + "tags": [ + "Patch", + "Vendor Advisory" + ] + } + ] + } +} diff --git a/pkg/process/v6/transformers/nvd/transform.go b/pkg/process/v6/transformers/nvd/transform.go new file mode 100644 index 00000000..81496406 --- /dev/null +++ b/pkg/process/v6/transformers/nvd/transform.go @@ -0,0 +1,303 @@ +package nvd + +import ( + "strings" + + "github.com/scylladb/go-set/strset" + + "github.com/anchore/grype-db/internal/log" + "github.com/anchore/grype-db/pkg/data" + "github.com/anchore/grype-db/pkg/process/v6/transformers" + "github.com/anchore/grype-db/pkg/process/v6/transformers/internal" + "github.com/anchore/grype-db/pkg/provider" + "github.com/anchore/grype-db/pkg/provider/unmarshal" + "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd" + grypeDB "github.com/anchore/grype/grype/db/v6" + "github.com/anchore/syft/syft/cpe" +) + +type Config struct { + CPEParts *strset.Set + InferNVDFixVersions bool +} + +func defaultConfig() Config { + return Config{ + CPEParts: strset.New("a"), + InferNVDFixVersions: true, + } +} + +func Transformer(cfg Config) data.NVDTransformerV2 { + if cfg == (Config{}) { + cfg = defaultConfig() + } + return func(vulnerability unmarshal.NVDVulnerability, state provider.State) ([]data.Entry, error) { + return transform(cfg, vulnerability, state) + } +} + +func transform(cfg Config, vulnerability unmarshal.NVDVulnerability, state provider.State) ([]data.Entry, error) { + in := []any{ + internal.ProviderModel(state), + grypeDB.VulnerabilityHandle{ + Name: vulnerability.ID, + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: vulnerability.ID, + ProviderName: state.Provider, + Assigner: getAssigner(vulnerability), + Description: strings.TrimSpace(vulnerability.Description()), + ModifiedDate: internal.ParseTime(vulnerability.LastModified), + PublishedDate: internal.ParseTime(vulnerability.Published), + Status: getVulnStatus(vulnerability), + References: getReferences(vulnerability), + Severities: getSeverities(vulnerability), + }, + }, + } + + for _, a := range getAffected(cfg, vulnerability) { + in = append(in, a) + } + + return transformers.NewEntries(in...), nil +} + +func getAssigner(vuln unmarshal.NVDVulnerability) []string { + if vuln.SourceIdentifier == nil { + return nil + } + + assigner := *vuln.SourceIdentifier + + if assigner == "" { + return nil + } + + return []string{assigner} +} + +func getVulnStatus(vuln unmarshal.NVDVulnerability) grypeDB.VulnerabilityStatus { + if vuln.VulnStatus == nil { + return grypeDB.UnknownVulnerabilityStatus + } + + // TODO: there is no path for withdrawn? + + // based off of the NVD or CVE list status, set the current vulnerability record status + // see https://nvd.nist.gov/vuln/vulnerability-status + s := strings.TrimSpace(strings.ReplaceAll(strings.ToLower(*vuln.VulnStatus), " ", "")) + switch s { + case "reserved", "received": + // reserved (CVE list): A CVE Entry is marked as "RESERVED" when it has been reserved for use by a CVE Numbering Authority (CNA) or security + // researcher, but the details of it are not yet populated. A CVE Entry can change from the RESERVED state to being populated at any time + // based on a number of factors both internal and external to the CVE List. + // + // received (NVD): CVE has been recently published to the CVE List and has been received by the NVD. + // + return grypeDB.UnknownVulnerabilityStatus + case "awaitinganalysis", "undergoinganalysis": + // awaiting analysis (NVD): CVE has been marked for Analysis. Normally once in this state the CVE will be analyzed by NVD staff within 24 hours. + // + // undergoing analysis (NVD): CVE has been marked for Analysis. Normally once in this state the CVE will be analyzed by NVD staff within 24 hours. + // + return grypeDB.VulnerabilityAnalyzing + case "disputed": + // disputed (CVE list): When one party disagrees with another party's assertion that a particular issue in software is a vulnerability, a CVE Entry assigned + // to that issue may be designated as being "DISPUTED". In these cases, CVE is making no determination as to which party is correct. Instead, we make + // note of this dispute and try to offer any public references that will better inform those trying to understand the facts of the issue. + // When you see a CVE Entry that is "DISPUTED", we encourage you to research the issue through the references or by contacting the affected + // vendor or developer for more information. + // + return grypeDB.VulnerabilityDisputed + case "rejected", "reject": + // reject (CVE list): A CVE Entry listed as "REJECT" is a CVE Entry that is not accepted as a CVE Entry. The reason a CVE Entry is marked + // REJECT will most often be stated in the description of the CVE Entry. Possible examples include it being a duplicate CVE Entry, it being + // withdrawn by the original requester, it being assigned incorrectly, or some other administrative reason. + // As a rule, REJECT CVE Entries should be ignored. + // + // rejected (NVD): CVE has been marked as "**REJECT**" in the CVE List. These CVEs are stored in the NVD, but do not show up in search results. + return grypeDB.VulnerabilityRejected + case "modified", "analyzed", "published": + // modified (NVD): CVE has been amended by a source (CVE Primary CNA or another CNA). Analysis data supplied by the NVD may be no longer be accurate due to these changes. + // + // analyzed (NVD): CVE has had analysis completed and all data associations made. Each Analysis has three sub-types, Initial, Modified and Reanalysis: + // Initial: Used to show the first time analysis was performed on a given CVE. + // Modified: Used to show that analysis was performed due to a modification the CVE’s information. + // Reanalysis: Used to show that new analysis occurred, but was not due to a modification from an external source.Analyzed CVEs do not show a banner on the vulnerability detail page. + // + // published (CVE list): The CVE Entry is populated with details. These are a CVE Description and reference link[s] regarding details of the CVE. + // + return grypeDB.VulnerabilityActive + } + + return grypeDB.UnknownVulnerabilityStatus +} + +func getAffected(cfg Config, vulnerability unmarshal.NVDVulnerability) []grypeDB.AffectedCPEHandle { + uniquePkgs := findUniquePkgs(cfg, vulnerability.Configurations...) + + var affs []grypeDB.AffectedCPEHandle + for _, p := range uniquePkgs.AllCandidates() { + appMatches := uniquePkgs.ApplicationMatches(p) + platformCPEs := uniquePkgs.PlatformMatches(p).CPEs() + + var qualifiers *grypeDB.AffectedPackageQualifiers + if len(platformCPEs) > 0 { + qualifiers = &grypeDB.AffectedPackageQualifiers{ + PlatformCPEs: platformCPEs, + } + } + + for _, c := range appMatches.CPEs() { + affs = append(affs, grypeDB.AffectedCPEHandle{ + CPE: getCPEs(c), + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{vulnerability.ID}, + Qualifiers: qualifiers, + Ranges: getRanges(cfg, appMatches), + }, + }) + } + } + return affs +} + +func getRanges(cfg Config, matches applicationMatches) []grypeDB.AffectedRange { + var ranges []grypeDB.AffectedRange + for _, m := range matches { + r := getRange(cfg, m) + if r != nil { + ranges = append(ranges, *r) + } + } + + return ranges +} + +func getRange(cfg Config, match nvd.CpeMatch) *grypeDB.AffectedRange { + constraint := buildConstraints(match) + fix := getFix(cfg, match) + + if constraint == "" && fix == nil { + return nil + } + + return &grypeDB.AffectedRange{ + Version: grypeDB.AffectedVersion{ + Type: "", + Constraint: constraint, + }, + Fix: fix, + } +} + +func getFix(cfg Config, match nvd.CpeMatch) *grypeDB.Fix { + if !cfg.InferNVDFixVersions { + return nil + } + + possiblyFixed := strset.New() + knownAffected := strset.New() + unspecifiedSet := strset.New("*", "-", "*") + + if !match.Vulnerable { + return nil + } + + if match.VersionEndExcluding != nil && !unspecifiedSet.Has(*match.VersionEndExcluding) { + possiblyFixed.Add(*match.VersionEndExcluding) + } + + if match.VersionStartIncluding != nil && !unspecifiedSet.Has(*match.VersionStartIncluding) { + knownAffected.Add(*match.VersionStartIncluding) + } + + if match.VersionEndIncluding != nil && !unspecifiedSet.Has(*match.VersionEndIncluding) { + knownAffected.Add(*match.VersionEndIncluding) + } + + matchCPE, err := cpe.New(match.Criteria, cpe.DeclaredSource) + if err != nil { + log.WithFields("error", err, "cpe", match.Criteria).Warn("could not parse CPE as fix, dropping...") + return nil + } + + if !unspecifiedSet.Has(matchCPE.Attributes.Version) { + knownAffected.Add(matchCPE.Attributes.Version) + } + + possiblyFixed.Remove(knownAffected.List()...) + + if possiblyFixed.Size() != 1 { + return nil + } + + return &grypeDB.Fix{ + Version: possiblyFixed.List()[0], + State: grypeDB.FixedStatus, + } +} + +func getCPEs(in string) *grypeDB.Cpe { + atts, err := cpe.NewAttributes(in) + if err != nil { + log.WithFields("cpe", in).Warn("could not parse CPE, dropping...") + return nil + } + + return &grypeDB.Cpe{ + Type: atts.Part, + Vendor: atts.Vendor, + Product: atts.Product, + Edition: atts.Edition, + Language: atts.Language, + SoftwareEdition: atts.SWEdition, + TargetHardware: atts.TargetHW, + TargetSoftware: atts.TargetSW, + Other: atts.Other, + } +} +func getSeverities(vuln unmarshal.NVDVulnerability) []grypeDB.Severity { + sevs := nvd.CvssSummaries(vuln.CVSS()).Sorted() + var results []grypeDB.Severity + for i, sev := range sevs { + priority := 2 + if i == 0 { + priority = 1 + } + results = append(results, grypeDB.Severity{ + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: sev.Vector, + Version: sev.Version, + Score: sev.BaseScore, + }, + Source: sev.Source, + Rank: priority, + }) + } + + return results +} + +func getReferences(vuln unmarshal.NVDVulnerability) []grypeDB.Reference { + references := []grypeDB.Reference{ + { + Tags: []string{grypeDB.AdvisoryReferenceTag}, + URL: "https://nvd.nist.gov/vuln/detail/" + vuln.ID, + }, + } + for _, reference := range vuln.References { + if reference.URL == "" { + continue + } + // TODO there is other info we could be capturing too (source) + references = append(references, grypeDB.Reference{ + URL: reference.URL, + Tags: grypeDB.NormalizeReferenceTags(reference.Tags), + }) + } + + return references +} diff --git a/pkg/process/v6/transformers/nvd/transform_test.go b/pkg/process/v6/transformers/nvd/transform_test.go new file mode 100644 index 00000000..dbb46171 --- /dev/null +++ b/pkg/process/v6/transformers/nvd/transform_test.go @@ -0,0 +1,1017 @@ +package nvd + +import ( + "os" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" + + testUtils "github.com/anchore/grype-db/pkg/process/internal/tests" + "github.com/anchore/grype-db/pkg/process/v6/transformers" + "github.com/anchore/grype-db/pkg/provider" + "github.com/anchore/grype-db/pkg/provider/unmarshal" + grypeDB "github.com/anchore/grype/grype/db/v6" +) + +var timeVal = time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC) +var listing = provider.File{ + Path: "some", + Digest: "123456", + Algorithm: "sha256", +} + +func inputProviderState(name string) provider.State { + return provider.State{ + Provider: name, + Version: 12, + Processor: "vunnel@1.2.3", + Timestamp: timeVal, + Listing: &listing, + } +} + +func expectedProvider(name string) grypeDB.Provider { + return grypeDB.Provider{ + ID: name, + Version: "12", + Processor: "vunnel@1.2.3", + DateCaptured: &timeVal, + InputDigest: "sha256:123456", + } +} + +func TestTransform(t *testing.T) { + + tests := []struct { + name string + config Config + provider string + want []transformers.RelatedEntries + }{ + { + name: "test-fixtures/version-range.json", + provider: "nvd", + config: defaultConfig(), + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: grypeDB.VulnerabilityHandle{ + Name: "CVE-2018-5487", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2018-5487", + ProviderName: "nvd", + Assigner: []string{"security-alert@netapp.com"}, + Description: "NetApp OnCommand Unified Manager for Linux versions 7.2 through 7.3 ship with the Java Management Extension Remote Method Invocation (JMX RMI) service bound to the network, and are susceptible to unauthenticated remote code execution.", + ModifiedDate: timeRef(time.Date(2018, 7, 5, 13, 52, 30, 627000000, time.UTC)), + PublishedDate: timeRef(time.Date(2018, 5, 24, 14, 29, 0, 390000000, time.UTC)), + Status: grypeDB.VulnerabilityActive, + References: []grypeDB.Reference{ + { + Tags: []string{grypeDB.AdvisoryReferenceTag}, + URL: "https://nvd.nist.gov/vuln/detail/CVE-2018-5487", + }, + { + URL: "https://security.netapp.com/advisory/ntap-20180523-0001/", + Tags: []string{"patch", "vendor-advisory"}, + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + Version: "3.0", + Score: 9.8, + }, + Source: "nvd@nist.gov", + Rank: 1, + }, + { + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P", + Version: "2.0", + Score: 7.5, + }, + Source: "nvd@nist.gov", + Rank: 2, + }, + }, + }, + }, + Provider: expectedProvider("nvd"), + Related: affectedPkgSlice( + grypeDB.AffectedCPEHandle{ + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2018-5487"}, + Qualifiers: &grypeDB.AffectedPackageQualifiers{ + PlatformCPEs: []string{"cpe:2.3:o:linux:linux_kernel:-:*:*:*:*:*:*:*"}, + }, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Constraint: ">= 7.2, <= 7.3", + }, + }, + }, + }, + CPE: &grypeDB.Cpe{ + Type: "a", + Vendor: "netapp", + Product: "oncommand_unified_manager", + }, + }, + ), + }, + }, + }, + { + name: "test-fixtures/single-package-multi-distro.json", + provider: "nvd", + config: defaultConfig(), + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: grypeDB.VulnerabilityHandle{ + Name: "CVE-2018-1000222", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2018-1000222", + ProviderName: "nvd", + Assigner: []string{"cve@mitre.org"}, + Description: "Libgd version 2.2.5 contains a Double Free Vulnerability vulnerability in gdImageBmpPtr Function that can result in Remote Code Execution . This attack appear to be exploitable via Specially Crafted Jpeg Image can trigger double free. This vulnerability appears to have been fixed in after commit ac16bdf2d41724b5a65255d4c28fb0ec46bc42f5.", + ModifiedDate: timeRef(time.Date(2020, 3, 31, 2, 15, 12, 667000000, time.UTC)), + PublishedDate: timeRef(time.Date(2018, 8, 20, 20, 29, 1, 347000000, time.UTC)), + Status: grypeDB.VulnerabilityActive, + References: []grypeDB.Reference{ + { + Tags: []string{grypeDB.AdvisoryReferenceTag}, + URL: "https://nvd.nist.gov/vuln/detail/CVE-2018-1000222", + }, + { + URL: "https://github.com/libgd/libgd/issues/447", + Tags: []string{"issue-tracking", "third-party-advisory"}, + }, + { + URL: "https://lists.debian.org/debian-lts-announce/2019/01/msg00028.html", + Tags: []string{"mailing-list", "third-party-advisory"}, + }, + { + URL: "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/3CZ2QADQTKRHTGB2AHD7J4QQNDLBEMM6/", + }, + { + URL: "https://security.gentoo.org/glsa/201903-18", + Tags: []string{"third-party-advisory"}, + }, + { + URL: "https://usn.ubuntu.com/3755-1/", + Tags: []string{"mitigation", "third-party-advisory"}, + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", + Version: "3.0", + Score: 8.8, + }, + Source: "nvd@nist.gov", + Rank: 1, + }, + { + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: "AV:N/AC:M/Au:N/C:P/I:P/A:P", + Version: "2.0", + Score: 6.8, + }, + Source: "nvd@nist.gov", + Rank: 2, + }, + }, + }, + }, + Provider: expectedProvider("nvd"), + Related: affectedPkgSlice( + grypeDB.AffectedCPEHandle{ + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2018-1000222"}, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Constraint: "= 2.2.5", + }, + }, + }, + }, + CPE: &grypeDB.Cpe{ + Type: "a", + Vendor: "libgd", + Product: "libgd", + }, + }, + ), + }, + }, + }, + { + name: "test-fixtures/compound-pkg.json", + provider: "nvd", + config: defaultConfig(), + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: grypeDB.VulnerabilityHandle{ + Name: "CVE-2018-10189", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2018-10189", + ProviderName: "nvd", + Assigner: []string{"cve@mitre.org"}, + Description: "An issue was discovered in Mautic 1.x and 2.x before 2.13.0. It is possible to systematically emulate tracking cookies per contact due to tracking the contact by their auto-incremented ID. Thus, a third party can manipulate the cookie value with +1 to systematically assume being tracked as each contact in Mautic. It is then possible to retrieve information about the contact through forms that have progressive profiling enabled.", + ModifiedDate: timeRef(time.Date(2018, 5, 23, 14, 41, 49, 73000000, time.UTC)), + PublishedDate: timeRef(time.Date(2018, 4, 17, 20, 29, 0, 410000000, time.UTC)), + Status: grypeDB.VulnerabilityActive, + References: []grypeDB.Reference{ + { + Tags: []string{grypeDB.AdvisoryReferenceTag}, + URL: "https://nvd.nist.gov/vuln/detail/CVE-2018-10189", + }, + { + URL: "https://github.com/mautic/mautic/releases/tag/2.13.0", + Tags: []string{"third-party-advisory"}, + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + Version: "3.0", + Score: 7.5, + }, + Source: "nvd@nist.gov", + Rank: 1, + }, + { + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: "AV:N/AC:L/Au:N/C:P/I:N/A:N", + Version: "2.0", + Score: 5.0, + }, + Source: "nvd@nist.gov", + Rank: 2, + }, + }, + }, + }, + Provider: expectedProvider("nvd"), + Related: affectedPkgSlice( + grypeDB.AffectedCPEHandle{ + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2018-10189"}, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Constraint: ">= 1.0.0, <= 1.4.1", + }, + // since the top range operator is <= we cannot infer a fix + }, + { + Version: grypeDB.AffectedVersion{ + Constraint: ">= 2.0.0, < 2.13.0", + }, + Fix: &grypeDB.Fix{ + Version: "2.13.0", + State: grypeDB.FixedStatus, + }, + }, + }, + }, + CPE: &grypeDB.Cpe{ + Type: "a", + Vendor: "mautic", + Product: "mautic", + }, + }, + ), + }, + }, + }, + { + name: "test-fixtures/invalid_cpe.json", + provider: "nvd", + config: defaultConfig(), + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: grypeDB.VulnerabilityHandle{ + Name: "CVE-2015-8978", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2015-8978", + ProviderName: "nvd", + Assigner: []string{"cve@mitre.org"}, + Description: "In Soap Lite (aka the SOAP::Lite extension for Perl) 1.14 and earlier, an example attack consists of defining 10 or more XML entities, each defined as consisting of 10 of the previous entity, with the document consisting of a single instance of the largest entity, which expands to one billion copies of the first entity. The amount of computer memory used for handling an external SOAP call would likely exceed that available to the process parsing the XML.", + ModifiedDate: timeRef(time.Date(2016, 11, 28, 19, 50, 59, 600000000, time.UTC)), + PublishedDate: timeRef(time.Date(2016, 11, 22, 17, 59, 0, 180000000, time.UTC)), + Status: grypeDB.VulnerabilityActive, + References: []grypeDB.Reference{ + { + Tags: []string{grypeDB.AdvisoryReferenceTag}, + URL: "https://nvd.nist.gov/vuln/detail/CVE-2015-8978", + }, + { + URL: "http://cpansearch.perl.org/src/PHRED/SOAP-Lite-1.20/Changes", + Tags: []string{"vendor-advisory"}, + }, + { + URL: "http://www.securityfocus.com/bid/94487", + Tags: nil, + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + Version: "3.0", + Score: 7.5, + }, + Source: "nvd@nist.gov", + Rank: 1, + }, + { + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P", + Version: "2.0", + Score: 5.0, + }, + Source: "nvd@nist.gov", + Rank: 2, + }, + }, + }, + }, + Provider: expectedProvider("nvd"), + Related: nil, // when we can't parse the CPE we should not add any affected blobs (but we do add the vuln blob) + }, + }, + }, + { + name: "test-fixtures/platform-cpe.json", + provider: "nvd", + config: defaultConfig(), + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: grypeDB.VulnerabilityHandle{ + Name: "CVE-2022-26488", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2022-26488", + ProviderName: "nvd", + Assigner: []string{"cve@mitre.org"}, + Description: "In Python before 3.10.3 on Windows, local users can gain privileges because the search path is inadequately secured. The installer may allow a local attacker to add user-writable directories to the system search path. To exploit, an administrator must have installed Python for all users and enabled PATH entries. A non-administrative user can trigger a repair that incorrectly adds user-writable paths into PATH, enabling search-path hijacking of other users and system services. This affects Python (CPython) through 3.7.12, 3.8.x through 3.8.12, 3.9.x through 3.9.10, and 3.10.x through 3.10.2.", + ModifiedDate: timeRef(time.Date(2022, 9, 3, 3, 34, 19, 933000000, time.UTC)), + PublishedDate: timeRef(time.Date(2022, 3, 10, 17, 47, 45, 383000000, time.UTC)), + Status: grypeDB.VulnerabilityActive, + References: []grypeDB.Reference{ + { + Tags: []string{grypeDB.AdvisoryReferenceTag}, + URL: "https://nvd.nist.gov/vuln/detail/CVE-2022-26488", + }, + { + URL: "https://mail.python.org/archives/list/security-announce@python.org/thread/657Z4XULWZNIY5FRP3OWXHYKUSIH6DMN/", + Tags: []string{"patch", "vendor-advisory"}, + }, + { + URL: "https://security.netapp.com/advisory/ntap-20220419-0005/", + Tags: []string{"third-party-advisory"}, + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H", + Version: "3.1", + Score: 7.0, + }, + Source: "nvd@nist.gov", + Rank: 1, + }, + { + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: "AV:L/AC:M/Au:N/C:P/I:P/A:P", + Version: "2.0", + Score: 4.4, + }, + Source: "nvd@nist.gov", + Rank: 2, + }, + }, + }, + }, + Provider: expectedProvider("nvd"), + Related: affectedPkgSlice( + grypeDB.AffectedCPEHandle{ + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2022-26488"}, + }, + CPE: &grypeDB.Cpe{ + Type: "a", + Vendor: "netapp", + Product: "active_iq_unified_manager", + TargetSoftware: "windows", + }, + }, + grypeDB.AffectedCPEHandle{ + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2022-26488"}, + }, + CPE: &grypeDB.Cpe{ + Type: "a", + Vendor: "netapp", + Product: "ontap_select_deploy_administration_utility", + }, + }, + grypeDB.AffectedCPEHandle{ + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2022-26488"}, + Qualifiers: &grypeDB.AffectedPackageQualifiers{ + PlatformCPEs: []string{"cpe:2.3:o:microsoft:windows:-:*:*:*:*:*:*:*"}, + }, + Ranges: []grypeDB.AffectedRange{ + {Version: grypeDB.AffectedVersion{Constraint: "<= 3.7.12"}}, + {Version: grypeDB.AffectedVersion{Constraint: ">= 3.8.0, <= 3.8.12"}}, + {Version: grypeDB.AffectedVersion{Constraint: ">= 3.9.0, <= 3.9.10"}}, + {Version: grypeDB.AffectedVersion{Constraint: ">= 3.10.0, <= 3.10.2"}}, + {Version: grypeDB.AffectedVersion{Constraint: "= 3.11.0-alpha1"}}, + {Version: grypeDB.AffectedVersion{Constraint: "= 3.11.0-alpha2"}}, + {Version: grypeDB.AffectedVersion{Constraint: "= 3.11.0-alpha3"}}, + {Version: grypeDB.AffectedVersion{Constraint: "= 3.11.0-alpha4"}}, + {Version: grypeDB.AffectedVersion{Constraint: "= 3.11.0-alpha4"}}, + {Version: grypeDB.AffectedVersion{Constraint: "= 3.11.0-alpha5"}}, + {Version: grypeDB.AffectedVersion{Constraint: "= 3.11.0-alpha6"}}, + }, + }, + CPE: &grypeDB.Cpe{ + Type: "a", + Vendor: "python", + Product: "python", + }, + }, + ), + }, + }, + }, + { + name: "test-fixtures/cve-2022-0543.json", + provider: "nvd", + config: defaultConfig(), + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: grypeDB.VulnerabilityHandle{ + Name: "CVE-2022-0543", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2022-0543", + ProviderName: "nvd", + Assigner: []string{"security@debian.org"}, + Description: "It was discovered, that redis, a persistent key-value database, due to a packaging issue, is prone to a (Debian-specific) Lua sandbox escape, which could result in remote code execution.", + ModifiedDate: timeRef(time.Date(2023, 9, 29, 15, 55, 24, 533000000, time.UTC)), + PublishedDate: timeRef(time.Date(2022, 2, 18, 20, 15, 17, 583000000, time.UTC)), + Status: grypeDB.VulnerabilityActive, + References: []grypeDB.Reference{ + { + Tags: []string{grypeDB.AdvisoryReferenceTag}, + URL: "https://nvd.nist.gov/vuln/detail/CVE-2022-0543", + }, + { + URL: "http://packetstormsecurity.com/files/166885/Redis-Lua-Sandbox-Escape.html", + Tags: []string{"exploit", "third-party-advisory", "vdb-entry"}, + }, + { + URL: "https://bugs.debian.org/1005787", + Tags: []string{"issue-tracking", "patch", "third-party-advisory"}, + }, + { + URL: "https://lists.debian.org/debian-security-announce/2022/msg00048.html", + Tags: []string{"mailing-list", "third-party-advisory"}, + }, + { + URL: "https://security.netapp.com/advisory/ntap-20220331-0004/", + Tags: []string{"third-party-advisory"}, + }, + { + URL: "https://www.debian.org/security/2022/dsa-5081", + Tags: []string{"mailing-list", "third-party-advisory"}, + }, + { + URL: "https://www.ubercomp.com/posts/2022-01-20_redis_on_debian_rce", + Tags: []string{"third-party-advisory"}, + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", + Version: "3.1", + Score: 10.0, + }, + Source: "nvd@nist.gov", + Rank: 1, + }, + { + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: "AV:N/AC:L/Au:N/C:C/I:C/A:C", + Version: "2.0", + Score: 10.0, + }, + Source: "nvd@nist.gov", + Rank: 2, + }, + }, + }, + }, + Provider: expectedProvider("nvd"), + Related: affectedPkgSlice( + grypeDB.AffectedCPEHandle{ + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2022-0543"}, + Qualifiers: &grypeDB.AffectedPackageQualifiers{ + PlatformCPEs: []string{ + "cpe:2.3:o:canonical:ubuntu_linux:20.04:*:*:*:lts:*:*:*", + "cpe:2.3:o:canonical:ubuntu_linux:21.10:*:*:*:-:*:*:*", + "cpe:2.3:o:debian:debian_linux:10.0:*:*:*:*:*:*:*", + "cpe:2.3:o:debian:debian_linux:11.0:*:*:*:*:*:*:*", + "cpe:2.3:o:debian:debian_linux:9.0:*:*:*:*:*:*:*", + }, + }, + }, + CPE: &grypeDB.Cpe{ + Type: "a", + Vendor: "redis", + Product: "redis", + }, + }, + ), + }, + }, + }, + { + name: "test-fixtures/cve-2020-10729.json", + provider: "nvd", + config: defaultConfig(), + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: grypeDB.VulnerabilityHandle{ + Name: "CVE-2020-10729", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2020-10729", + ProviderName: "nvd", + Assigner: []string{"secalert@redhat.com"}, + Description: "A flaw was found in the use of insufficiently random values in Ansible. Two random password lookups of the same length generate the equal value as the template caching action for the same file since no re-evaluation happens. The highest threat from this vulnerability would be that all passwords are exposed at once for the file. This flaw affects Ansible Engine versions before 2.9.6.", + ModifiedDate: timeRef(time.Date(2021, 12, 10, 19, 57, 6, 357000000, time.UTC)), + PublishedDate: timeRef(time.Date(2021, 5, 27, 19, 15, 7, 880000000, time.UTC)), + Status: grypeDB.VulnerabilityActive, + References: []grypeDB.Reference{ + { + Tags: []string{grypeDB.AdvisoryReferenceTag}, + URL: "https://nvd.nist.gov/vuln/detail/CVE-2020-10729", + }, + { + URL: "https://bugzilla.redhat.com/show_bug.cgi?id=1831089", + Tags: []string{"issue-tracking", "vendor-advisory"}, + }, + { + URL: "https://github.com/ansible/ansible/issues/34144", + Tags: []string{"exploit", "issue-tracking", "third-party-advisory"}, + }, + { + URL: "https://www.debian.org/security/2021/dsa-4950", + Tags: []string{"third-party-advisory"}, + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N", + Version: "3.1", + Score: 5.5, + }, + Source: "nvd@nist.gov", + Rank: 1, + }, + { + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: "AV:L/AC:L/Au:N/C:P/I:N/A:N", + Version: "2.0", + Score: 2.1, + }, + Source: "nvd@nist.gov", + Rank: 2, + }, + }, + }, + }, + Provider: expectedProvider("nvd"), + Related: affectedPkgSlice( + grypeDB.AffectedCPEHandle{ + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2020-10729"}, + Qualifiers: &grypeDB.AffectedPackageQualifiers{ + PlatformCPEs: []string{ + "cpe:2.3:o:redhat:enterprise_linux:7.0:*:*:*:*:*:*:*", + "cpe:2.3:o:redhat:enterprise_linux:8.0:*:*:*:*:*:*:*", + }, + }, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Constraint: "< 2.9.6", + }, + Fix: &grypeDB.Fix{ + Version: "2.9.6", + State: grypeDB.FixedStatus, + }, + }, + }, + }, + CPE: &grypeDB.Cpe{ + Type: "a", + Vendor: "redhat", + Product: "ansible_engine", + }, + }, + ), + }, + }, + }, + { + name: "test-fixtures/multiple-platforms-with-application-cpe.json", + provider: "nvd", + config: defaultConfig(), + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: grypeDB.VulnerabilityHandle{ + Name: "CVE-2023-38733", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2023-38733", + ProviderName: "nvd", + Assigner: []string{"psirt@us.ibm.com"}, + Description: "IBM Robotic Process Automation 21.0.0 through 21.0.7.1 and 23.0.0 through 23.0.1 server could allow an authenticated user to view sensitive information from installation logs. IBM X-Force Id: 262293.", + ModifiedDate: timeRef(time.Date(2023, 8, 26, 2, 25, 42, 957000000, time.UTC)), + PublishedDate: timeRef(time.Date(2023, 8, 22, 22, 15, 8, 460000000, time.UTC)), + Status: grypeDB.VulnerabilityActive, + References: []grypeDB.Reference{ + { + Tags: []string{grypeDB.AdvisoryReferenceTag}, + URL: "https://nvd.nist.gov/vuln/detail/CVE-2023-38733", + }, + { + URL: "https://exchange.xforce.ibmcloud.com/vulnerabilities/262293", + Tags: []string{"vdb-entry", "vendor-advisory"}, + }, + { + URL: "https://www.ibm.com/support/pages/node/7028223", + Tags: []string{"patch", "vendor-advisory"}, + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N", + Version: "3.1", + Score: 4.3, + }, + Source: "nvd@nist.gov", + Rank: 1, + }, + { + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N", + Version: "3.1", + Score: 4.3, + }, + Source: "psirt@us.ibm.com", + Rank: 2, + }, + }, + }, + }, + Provider: expectedProvider("nvd"), + Related: affectedPkgSlice( + grypeDB.AffectedCPEHandle{ + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2023-38733"}, + Qualifiers: &grypeDB.AffectedPackageQualifiers{ + PlatformCPEs: []string{ + "cpe:2.3:a:redhat:openshift:-:*:*:*:*:*:*:*", + "cpe:2.3:o:microsoft:windows:-:*:*:*:*:*:*:*", + }, + }, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Constraint: ">= 21.0.0, <= 21.0.7.3", + }, + }, + { + Version: grypeDB.AffectedVersion{ + Constraint: ">= 23.0.0, <= 23.0.3", + }, + }, + }, + }, + CPE: &grypeDB.Cpe{ + Type: "a", + Vendor: "ibm", + Product: "robotic_process_automation", + }, + }, + ), + }, + }, + }, + { + name: "test-fixtures/CVE-2023-45283-platform-cpe-first.json", + provider: "nvd", + config: defaultConfig(), + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: grypeDB.VulnerabilityHandle{ + Name: "CVE-2023-45283", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2023-45283", + ProviderName: "nvd", + Assigner: []string{"security@golang.org"}, + Description: "The filepath package does not recognize paths with a \\??\\ prefix as special. On Windows, a path beginning with \\??\\ is a Root Local Device path equivalent to a path beginning with \\\\?\\. Paths with a \\??\\ prefix may be used to access arbitrary locations on the system. For example, the path \\??\\c:\\x is equivalent to the more common path c:\\x. Before fix, Clean could convert a rooted path such as \\a\\..\\??\\b into the root local device path \\??\\b. Clean will now convert this to .\\??\\b. Similarly, Join(\\, ??, b) could convert a seemingly innocent sequence of path elements into the root local device path \\??\\b. Join will now convert this to \\.\\??\\b. In addition, with fix, IsAbs now correctly reports paths beginning with \\??\\ as absolute, and VolumeName correctly reports the \\??\\ prefix as a volume name. UPDATE: Go 1.20.11 and Go 1.21.4 inadvertently changed the definition of the volume name in Windows paths starting with \\?, resulting in filepath.Clean(\\?\\c:) returning \\?\\c: rather than \\?\\c:\\ (among other effects). The previous behavior has been restored.", + ModifiedDate: timeRef(time.Date(2023, 12, 14, 10, 15, 7, 947000000, time.UTC)), + PublishedDate: timeRef(time.Date(2023, 11, 9, 17, 15, 8, 757000000, time.UTC)), + Status: grypeDB.VulnerabilityActive, + References: []grypeDB.Reference{ + { + Tags: []string{grypeDB.AdvisoryReferenceTag}, + URL: "https://nvd.nist.gov/vuln/detail/CVE-2023-45283", + }, + { + URL: "http://www.openwall.com/lists/oss-security/2023/12/05/2", + Tags: nil, + }, + { + URL: "https://go.dev/cl/540277", + Tags: []string{"issue-tracking", "vendor-advisory"}, + }, + { + URL: "https://go.dev/cl/541175", + Tags: nil, + }, + { + URL: "https://go.dev/issue/63713", + Tags: []string{"issue-tracking", "vendor-advisory"}, + }, + { + URL: "https://go.dev/issue/64028", + Tags: nil, + }, + { + URL: "https://groups.google.com/g/golang-announce/c/4tU8LZfBFkY", + Tags: []string{"issue-tracking", "mailing-list", "vendor-advisory"}, + }, + { + URL: "https://groups.google.com/g/golang-dev/c/6ypN5EjibjM/m/KmLVYH_uAgAJ", + Tags: nil, + }, + { + URL: "https://pkg.go.dev/vuln/GO-2023-2185", + Tags: []string{"issue-tracking", "vendor-advisory"}, + }, + { + URL: "https://security.netapp.com/advisory/ntap-20231214-0008/", + Tags: nil, + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + Version: "3.1", + Score: 7.5, + }, + Source: "nvd@nist.gov", + Rank: 1, + }, + }, + }, + }, + Provider: expectedProvider("nvd"), + Related: affectedPkgSlice( + grypeDB.AffectedCPEHandle{ + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2023-45283"}, + Qualifiers: &grypeDB.AffectedPackageQualifiers{ + PlatformCPEs: []string{"cpe:2.3:o:microsoft:windows:-:*:*:*:*:*:*:*"}, + }, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Constraint: "< 1.20.11", + }, + Fix: &grypeDB.Fix{ + Version: "1.20.11", + State: grypeDB.FixedStatus, + }, + }, + { + Version: grypeDB.AffectedVersion{ + Constraint: ">= 1.21.0-0, < 1.21.4", + }, + Fix: &grypeDB.Fix{ + Version: "1.21.4", + State: grypeDB.FixedStatus, + }, + }, + }, + }, + CPE: &grypeDB.Cpe{ + Type: "a", + Vendor: "golang", + Product: "go", + }, + }, + ), + }, + }, + }, + { + name: "test-fixtures/CVE-2023-45283-platform-cpe-last.json", + provider: "nvd", + config: defaultConfig(), + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: grypeDB.VulnerabilityHandle{ + Name: "CVE-2023-45283", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2023-45283", + ProviderName: "nvd", + Assigner: []string{"security@golang.org"}, + Description: "The filepath package does not recognize paths with a \\??\\ prefix as special. On Windows, a path beginning with \\??\\ is a Root Local Device path equivalent to a path beginning with \\\\?\\. Paths with a \\??\\ prefix may be used to access arbitrary locations on the system. For example, the path \\??\\c:\\x is equivalent to the more common path c:\\x. Before fix, Clean could convert a rooted path such as \\a\\..\\??\\b into the root local device path \\??\\b. Clean will now convert this to .\\??\\b. Similarly, Join(\\, ??, b) could convert a seemingly innocent sequence of path elements into the root local device path \\??\\b. Join will now convert this to \\.\\??\\b. In addition, with fix, IsAbs now correctly reports paths beginning with \\??\\ as absolute, and VolumeName correctly reports the \\??\\ prefix as a volume name. UPDATE: Go 1.20.11 and Go 1.21.4 inadvertently changed the definition of the volume name in Windows paths starting with \\?, resulting in filepath.Clean(\\?\\c:) returning \\?\\c: rather than \\?\\c:\\ (among other effects). The previous behavior has been restored.", + ModifiedDate: timeRef(time.Date(2023, 12, 14, 10, 15, 7, 947000000, time.UTC)), + PublishedDate: timeRef(time.Date(2023, 11, 9, 17, 15, 8, 757000000, time.UTC)), + Status: grypeDB.VulnerabilityActive, + References: []grypeDB.Reference{ + { + Tags: []string{grypeDB.AdvisoryReferenceTag}, + URL: "https://nvd.nist.gov/vuln/detail/CVE-2023-45283", + }, + { + URL: "http://www.openwall.com/lists/oss-security/2023/12/05/2", + Tags: nil, + }, + { + URL: "https://go.dev/cl/540277", + Tags: []string{"issue-tracking", "vendor-advisory"}, + }, + { + URL: "https://go.dev/cl/541175", + Tags: nil, + }, + { + URL: "https://go.dev/issue/63713", + Tags: []string{"issue-tracking", "vendor-advisory"}, + }, + { + URL: "https://go.dev/issue/64028", + Tags: nil, + }, + { + URL: "https://groups.google.com/g/golang-announce/c/4tU8LZfBFkY", + Tags: []string{"issue-tracking", "mailing-list", "vendor-advisory"}, + }, + { + URL: "https://groups.google.com/g/golang-dev/c/6ypN5EjibjM/m/KmLVYH_uAgAJ", + Tags: nil, + }, + { + URL: "https://pkg.go.dev/vuln/GO-2023-2185", + Tags: []string{"issue-tracking", "vendor-advisory"}, + }, + { + URL: "https://security.netapp.com/advisory/ntap-20231214-0008/", + Tags: nil, + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + Version: "3.1", + Score: 7.5, + }, + Source: "nvd@nist.gov", + Rank: 1, + }, + }, + }, + }, + Provider: expectedProvider("nvd"), + Related: affectedPkgSlice( + grypeDB.AffectedCPEHandle{ + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2023-45283"}, + Qualifiers: &grypeDB.AffectedPackageQualifiers{ + PlatformCPEs: []string{"cpe:2.3:o:microsoft:windows:-:*:*:*:*:*:*:*"}, + }, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Constraint: "< 1.20.11", + }, + Fix: &grypeDB.Fix{ + Version: "1.20.11", + State: grypeDB.FixedStatus, + }, + }, + { + Version: grypeDB.AffectedVersion{ + Constraint: ">= 1.21.0-0, < 1.21.4", + }, + Fix: &grypeDB.Fix{ + Version: "1.21.4", + State: grypeDB.FixedStatus, + }, + }, + }, + }, + CPE: &grypeDB.Cpe{ + Type: "a", + Vendor: "golang", + Product: "go", + }, + }, + ), + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + vulns := loadFixture(t, test.name) + + var actual []transformers.RelatedEntries + for _, vuln := range vulns { + if test.config == (Config{}) { + test.config = defaultConfig() + } + entries, err := Transformer(test.config)(vuln, inputProviderState(test.provider)) + require.NoError(t, err) + for _, entry := range entries { + e, ok := entry.Data.(transformers.RelatedEntries) + require.True(t, ok) + actual = append(actual, e) + } + } + + if diff := cmp.Diff(test.want, actual); diff != "" { + t.Errorf("data entries mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func affectedPkgSlice(a ...grypeDB.AffectedCPEHandle) []any { + var r []any + for _, v := range a { + r = append(r, v) + } + return r +} + +func loadFixture(t *testing.T, fixturePath string) []unmarshal.NVDVulnerability { + t.Helper() + + f, err := os.Open(fixturePath) + require.NoError(t, err) + defer testUtils.CloseFile(f) + + entries, err := unmarshal.NvdVulnerabilityEntries(f) + require.NoError(t, err) + + var vulns []unmarshal.NVDVulnerability + for _, entry := range entries { + vulns = append(vulns, entry.Cve) + } + + return vulns +} + +func timeRef(ti time.Time) *time.Time { + return &ti +} diff --git a/pkg/process/v6/transformers/nvd/unique_pkg.go b/pkg/process/v6/transformers/nvd/unique_pkg.go new file mode 100644 index 00000000..b35d8435 --- /dev/null +++ b/pkg/process/v6/transformers/nvd/unique_pkg.go @@ -0,0 +1,241 @@ +package nvd + +import ( + "fmt" + "strings" + + "github.com/umisama/go-cpe" + + "github.com/anchore/grype-db/internal/log" + "github.com/anchore/grype-db/pkg/process/internal/common" + "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd" +) + +const ( + ANY = "*" + NA = "-" +) + +type pkgCandidate struct { + Product string + Vendor string + TargetSoftware string +} + +func (p pkgCandidate) String() string { + return fmt.Sprintf("%s|%s|%s", p.Vendor, p.Product, p.TargetSoftware) +} + +func newPkgCandidate(tCfg Config, match nvd.CpeMatch) (*pkgCandidate, error) { + // we are only interested in packages that are vulnerable (not related to secondary match conditioning) + if !match.Vulnerable { + return nil, nil + } + + c, err := cpe.NewItemFromFormattedString(match.Criteria) + if err != nil { + return nil, fmt.Errorf("unable to create uniquePkgEntry from '%s': %w", match.Criteria, err) + } + + // we are interested in applications, conditionally operating systems, but never hardware + part := c.Part() + if !tCfg.CPEParts.Has(string(part)) { + return nil, nil + } + + return &pkgCandidate{ + Product: c.Product().String(), + Vendor: c.Vendor().String(), + TargetSoftware: c.TargetSw().String(), + }, nil +} + +func findUniquePkgs(tCfg Config, cfgs ...nvd.Configuration) uniquePkgTracker { + set := newUniquePkgTracker() + for _, c := range cfgs { + _findUniquePkgs(tCfg, set, c) + } + return set +} + +func platformPackageCandidates(tCfg Config, set uniquePkgTracker, c nvd.Configuration) bool { + nodes := c.Nodes + /* + Turn a configuration like this: + (AND + (OR (cpe:2.3:a:redis:...whatever) (cpe:2.3.:something:...whatever) + (OR (cpe:2.3:o:debian:9....) (cpe:2.3:o:ubuntu:22..)) + ) + Into a configuration like this: + (OR + (AND (cpe:2.3:a:redis:...whatever) (cpe:2.3:o:debian:9...)) + (AND (cpe:2.3:a:redis:...whatever) (cpe:2.3:o:ubuntu:22...)) + (AND (cpe:2.3:a:something:...whatever) (cpe:2.3:o:debian:9...)) + (AND (cpe:2.3:a:something:...whatever) (cpe:2.3:o:ubuntu:22...)) + ) + Because in schema v5, rows in Grype DB can only have zero or one platform CPE + constraint. + */ + if len(nodes) != 2 || c.Operator == nil || *c.Operator != nvd.And { + return false + } + var platformsNode nvd.Node + var applicationNode nvd.Node + for _, n := range nodes { + if anyHardwareCPEPresent(n) { + return false + } + if allCPEsVulnerable(n) { + applicationNode = n + } + if noCPEsVulnerable(n) { + platformsNode = n + } + } + if platformsNode.Operator != nvd.Or { + return false + } + if applicationNode.Operator != nvd.Or { + return false + } + result := false + for _, application := range applicationNode.CpeMatch { + candidate, err := newPkgCandidate(tCfg, application) + if err != nil { + log.Debugf("unable processing uniquePkg with multiple platforms: %v", err) + continue + } + if candidate == nil { + continue + } + + set.AddExplicit(*candidate, application, platformsNode.CpeMatch) + result = true + } + return result +} + +func anyHardwareCPEPresent(n nvd.Node) bool { + for _, c := range n.CpeMatch { + parts := strings.Split(c.Criteria, ":") + if len(parts) < 3 || parts[2] == "h" { + return true + } + } + return false +} + +func allCPEsVulnerable(node nvd.Node) bool { + for _, c := range node.CpeMatch { + if !c.Vulnerable { + return false + } + } + return true +} + +func noCPEsVulnerable(node nvd.Node) bool { + for _, c := range node.CpeMatch { + if c.Vulnerable { + return false + } + } + return true +} + +func determineNodes(c nvd.Configuration) []nvd.Node { + nodes := c.Nodes + + if len(nodes) == 2 && c.Operator != nil && *c.Operator == nvd.And { + if len(nodes[1].CpeMatch) == 1 && !nodes[1].CpeMatch[0].Vulnerable { + nodes = []nvd.Node{nodes[0]} + } + } + + return nodes +} + +func _findUniquePkgs(tCfg Config, set uniquePkgTracker, c nvd.Configuration) { + if len(c.Nodes) == 0 { + return + } + + if platformPackageCandidates(tCfg, set, c) { + return + } + + nodes := determineNodes(c) + for _, node := range nodes { + for _, match := range node.CpeMatch { + candidate, err := newPkgCandidate(tCfg, match) + if err != nil { + // Do not halt all execution because of being unable to create + // a PkgCandidate. This can happen when a CPE is invalid which + // could avoid creating a database + log.Debugf("unable processing uniquePkg: %v", err) + continue + } + if candidate != nil { + set.AddWithDetection(*candidate, match) + } + } + } +} + +func buildConstraints(matches ...nvd.CpeMatch) string { + return common.OrConstraints(buildConstraintRanges(matches)...) +} + +func buildConstraintRanges(matches []nvd.CpeMatch) []string { + constraints := make([]string, 0) + for _, match := range matches { + constraints = append(constraints, buildConstraint(match)) + } + + return removeDuplicateConstraints(constraints) +} + +func buildConstraint(match nvd.CpeMatch) string { + constraints := make([]string, 0) + if match.VersionStartIncluding != nil && *match.VersionStartIncluding != "" { + constraints = append(constraints, fmt.Sprintf(">= %s", *match.VersionStartIncluding)) + } else if match.VersionStartExcluding != nil && *match.VersionStartExcluding != "" { + constraints = append(constraints, fmt.Sprintf("> %s", *match.VersionStartExcluding)) + } + + if match.VersionEndIncluding != nil && *match.VersionEndIncluding != "" { + constraints = append(constraints, fmt.Sprintf("<= %s", *match.VersionEndIncluding)) + } else if match.VersionEndExcluding != nil && *match.VersionEndExcluding != "" { + constraints = append(constraints, fmt.Sprintf("< %s", *match.VersionEndExcluding)) + } + + if len(constraints) == 0 { + c, err := cpe.NewItemFromFormattedString(match.Criteria) + if err != nil { + return "" + } + version := c.Version().String() + update := c.Update().String() + if version != ANY && version != NA { + if update != ANY && update != NA { + version = fmt.Sprintf("%s-%s", version, update) + } + + constraints = append(constraints, fmt.Sprintf("= %s", version)) + } + } + + return strings.Join(constraints, ", ") +} + +func removeDuplicateConstraints(constraints []string) []string { + constraintMap := make(map[string]struct{}) + var uniqueConstraints []string + for _, constraint := range constraints { + if _, exists := constraintMap[constraint]; !exists { + constraintMap[constraint] = struct{}{} + uniqueConstraints = append(uniqueConstraints, constraint) + } + } + return uniqueConstraints +} diff --git a/pkg/process/v6/transformers/nvd/unique_pkg_test.go b/pkg/process/v6/transformers/nvd/unique_pkg_test.go new file mode 100644 index 00000000..24b9d0ff --- /dev/null +++ b/pkg/process/v6/transformers/nvd/unique_pkg_test.go @@ -0,0 +1,602 @@ +package nvd + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/scylladb/go-set/strset" + "github.com/sergi/go-diff/diffmatchpatch" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd" +) + +func newUniquePkgTrackerFromSlice(candidates []pkgCandidate) uniquePkgTracker { + set := newUniquePkgTracker() + for _, c := range candidates { + set[c] = matches{} + } + return set +} + +func TestFindUniquePkgs(t *testing.T) { + tests := []struct { + name string + config Config + nodes []nvd.Node + operator *nvd.Operator + expected uniquePkgTracker + }{ + { + name: "simple-match", + nodes: []nvd.Node{ + { + CpeMatch: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:vendor:product:2.2.0:*:*:*:*:target:*:*", + Vulnerable: true, + }, + }, + }, + }, + expected: newUniquePkgTrackerFromSlice( + []pkgCandidate{ + { + Product: "product", + Vendor: "vendor", + TargetSoftware: "target", + }, + }), + }, + { + name: "skip-hw", + nodes: []nvd.Node{ + { + CpeMatch: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:h:vendor:product:2.2.0:*:*:*:*:target:*:*", + Vulnerable: true, + }, + }, + }, + }, + expected: newUniquePkgTrackerFromSlice([]pkgCandidate{}), + }, + { + name: "skip-os", + nodes: []nvd.Node{ + { + CpeMatch: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:o:vendor:product:2.2.0:*:*:*:*:target:*:*", + Vulnerable: true, + }, + }, + }, + }, + expected: newUniquePkgTrackerFromSlice([]pkgCandidate{}), + }, + { + name: "duplicate-by-product", + nodes: []nvd.Node{ + { + CpeMatch: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:vendor:productA:3.3.3:*:*:*:*:target:*:*", + Vulnerable: true, + }, + { + Criteria: "cpe:2.3:a:vendor:productB:2.2.0:*:*:*:*:target:*:*", + Vulnerable: true, + }, + }, + Operator: "OR", + }, + }, + expected: newUniquePkgTrackerFromSlice( + []pkgCandidate{ + { + Product: "productA", + Vendor: "vendor", + TargetSoftware: "target", + }, + { + Product: "productB", + Vendor: "vendor", + TargetSoftware: "target", + }, + }), + }, + { + name: "duplicate-by-target", + nodes: []nvd.Node{ + { + CpeMatch: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:vendor:product:3.3.3:*:*:*:*:targetA:*:*", + Vulnerable: true, + }, + { + Criteria: "cpe:2.3:a:vendor:product:2.2.0:*:*:*:*:targetB:*:*", + Vulnerable: true, + }, + }, + Operator: "OR", + }, + }, + expected: newUniquePkgTrackerFromSlice( + []pkgCandidate{ + { + Product: "product", + Vendor: "vendor", + TargetSoftware: "targetA", + }, + { + Product: "product", + Vendor: "vendor", + TargetSoftware: "targetB", + }, + }), + }, + { + name: "duplicate-by-vendor", + nodes: []nvd.Node{ + { + CpeMatch: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:vendorA:product:3.3.3:*:*:*:*:target:*:*", + Vulnerable: true, + }, + { + Criteria: "cpe:2.3:a:vendorB:product:2.2.0:*:*:*:*:target:*:*", + Vulnerable: true, + }, + }, + Operator: "OR", + }, + }, + expected: newUniquePkgTrackerFromSlice( + []pkgCandidate{ + { + Product: "product", + Vendor: "vendorA", + TargetSoftware: "target", + }, + { + Product: "product", + Vendor: "vendorB", + TargetSoftware: "target", + }, + }), + }, + { + name: "de-duplicate-case", + nodes: []nvd.Node{ + { + CpeMatch: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:vendor:product:3.3.3:A:B:C:D:target:E:F", + Vulnerable: true, + }, + { + Criteria: "cpe:2.3:a:vendor:product:2.2.0:Q:R:S:T:target:U:V", + Vulnerable: true, + }, + }, + Operator: "OR", + }, + }, + expected: newUniquePkgTrackerFromSlice( + []pkgCandidate{ + { + Product: "product", + Vendor: "vendor", + TargetSoftware: "target", + }, + }), + }, + { + name: "duplicate-from-nested-nodes", + nodes: []nvd.Node{ + { + CpeMatch: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:vendorB:product:2.2.0:*:*:*:*:target:*:*", + Vulnerable: true, + }, + }, + Operator: "OR", + }, + { + CpeMatch: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:vendorA:product:2.2.0:*:*:*:*:target:*:*", + Vulnerable: true, + }, + }, + Operator: "OR", + }, + }, + expected: newUniquePkgTrackerFromSlice( + []pkgCandidate{ + { + Product: "product", + Vendor: "vendorA", + TargetSoftware: "target", + }, + { + Product: "product", + Vendor: "vendorB", + TargetSoftware: "target", + }, + }), + }, + { + name: "cpe with multiple platforms", + operator: opRef(nvd.And), + nodes: []nvd.Node{ + { + Negate: boolRef(false), + Operator: nvd.Or, + CpeMatch: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:o:canonical:ubuntu_linux:20.04:*:*:*:lts:*:*:*", + MatchCriteriaID: "902B8056-9E37-443B-8905-8AA93E2447FB", + Vulnerable: false, + }, + { + Criteria: "cpe:2.3:o:canonical:ubuntu_linux:21.10:*:*:*:-:*:*:*", + MatchCriteriaID: "3D94DA3B-FA74-4526-A0A0-A872684598C6", + Vulnerable: false, + }, + { + Criteria: "cpe:2.3:o:debian:debian_linux:9.0:*:*:*:*:*:*:*", + MatchCriteriaID: "DEECE5FC-CACF-4496-A3E7-164736409252", + Vulnerable: false, + }, + { + Criteria: "cpe:2.3:o:debian:debian_linux:10.0:*:*:*:*:*:*:*", + MatchCriteriaID: "07B237A9-69A3-4A9C-9DA0-4E06BD37AE73", + Vulnerable: false, + }, + { + Criteria: "cpe:2.3:o:debian:debian_linux:11.0:*:*:*:*:*:*:*", + MatchCriteriaID: "FA6FEEC2-9F11-4643-8827-749718254FED", + Vulnerable: false, + }, + }, + }, + { + Negate: boolRef(false), + Operator: nvd.Or, + CpeMatch: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:redis:redis:-:*:*:*:*:*:*:*", + MatchCriteriaID: "5EBE5E1C-C881-4A76-9E36-4FB7C48427E6", + Vulnerable: true, + }, + }, + }, + }, + expected: newUniquePkgTrackerFromSlice([]pkgCandidate{ + { + Product: "redis", + Vendor: "redis", + TargetSoftware: ANY, + }, + }), + }, + { + name: "include-os-explicitly", + config: Config{ + CPEParts: strset.New("a", "o"), + }, + nodes: []nvd.Node{ + { + CpeMatch: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:o:vendor:product:2.2.0:*:*:*:*:target:*:*", + Vulnerable: true, + }, + }, + }, + }, + expected: newUniquePkgTrackerFromSlice([]pkgCandidate{ + { + Product: "product", + Vendor: "vendor", + TargetSoftware: "target", + }, + }), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if test.config == (Config{}) { + test.config = defaultConfig() + } + actual := findUniquePkgs(test.config, nvd.Configuration{Nodes: test.nodes, Operator: test.operator}) + missing, extra := test.expected.Diff(actual) + if len(missing) != 0 { + for _, c := range missing { + t.Errorf("missing candidate: %+v", c) + } + } + + if len(extra) != 0 { + for _, c := range extra { + t.Errorf("extra candidate: %+v", c) + } + } + }) + } +} + +func strRef(s string) *string { + return &s +} + +func TestBuildConstraints(t *testing.T) { + tests := []struct { + name string + matches []nvd.CpeMatch + expected string + }{ + { + name: "Equals", + matches: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:vendor:product:2.2.0:*:*:*:*:target:*:*", + }, + }, + expected: "= 2.2.0", + }, + { + name: "VersionEndExcluding", + matches: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:vendor:product:*:*:*:*:*:target:*:*", + VersionEndExcluding: strRef("2.3.0"), + }, + }, + expected: "< 2.3.0", + }, + { + name: "VersionEndIncluding", + matches: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:vendor:product:*:*:*:*:*:target:*:*", + VersionEndIncluding: strRef("2.3.0"), + }, + }, + expected: "<= 2.3.0", + }, + { + name: "VersionStartExcluding", + matches: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:vendor:product:*:*:*:*:*:target:*:*", + VersionStartExcluding: strRef("2.3.0"), + }, + }, + expected: "> 2.3.0", + }, + { + name: "VersionStartIncluding", + matches: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:vendor:product:*:*:*:*:*:target:*:*", + VersionStartIncluding: strRef("2.3.0"), + }, + }, + expected: ">= 2.3.0", + }, + { + name: "Version Range", + matches: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:vendor:product:*:*:*:*:*:target:*:*", + VersionStartIncluding: strRef("2.3.0"), + VersionEndIncluding: strRef("2.5.0"), + }, + }, + expected: ">= 2.3.0, <= 2.5.0", + }, + { + name: "Multiple Version Ranges", + matches: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:vendor:product:*:*:*:*:*:target:*:*", + VersionStartIncluding: strRef("2.3.0"), + VersionEndIncluding: strRef("2.5.0"), + }, + { + Criteria: "cpe:2.3:a:vendor:product:*:*:*:*:*:target:*:*", + VersionStartExcluding: strRef("3.3.0"), + VersionEndExcluding: strRef("3.5.0"), + }, + }, + expected: ">= 2.3.0, <= 2.5.0 || > 3.3.0, < 3.5.0", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + actual := buildConstraints(test.matches...) + + if actual != test.expected { + dmp := diffmatchpatch.New() + diffs := dmp.DiffMain(actual, test.expected, true) + t.Errorf("Expected: %q", test.expected) + t.Errorf("Got : %q", actual) + t.Errorf("Diff : %q", dmp.DiffPrettyText(diffs)) + } + }) + } +} + +func TestPlatformPackageCandidates(t *testing.T) { + type testCase struct { + name string + config Config + state nvd.Configuration + wantChanged bool + wantSet uniquePkgTracker + } + tests := []testCase{ + { + name: "application X platform", + state: nvd.Configuration{ + Negate: nil, + Nodes: []nvd.Node{ + { + CpeMatch: []nvd.CpeMatch{ + { + Vulnerable: true, + Criteria: "cpe:2.3:a:some-vendor:some-app:*:*:*:*:*:*:*:*", + }, + { + Vulnerable: true, + Criteria: "cpe:2.3:a:some-vendor:other-app:*:*:*:*:*:*:*:*", + }, + }, + Negate: nil, + Operator: nvd.Or, + }, + { + CpeMatch: []nvd.CpeMatch{ + { + Vulnerable: false, + Criteria: "cpe:2.3:o:some-vendor:some-platform:*:*:*:*:*:*:*:*", + }, + { + Vulnerable: false, + Criteria: "cpe:2.3:o:some-vendor:other-platform:*:*:*:*:*:*:*:*", + }, + }, + Negate: nil, + Operator: nvd.Or, + }, + }, + Operator: opRef(nvd.And), + }, + wantChanged: true, + wantSet: newUniquePkgTrackerFromSlice( + []pkgCandidate{ + mustNewPackage(t, nvd.CpeMatch{ + Vulnerable: true, + Criteria: "cpe:2.3:a:some-vendor:some-app:*:*:*:*:*:*:*:*", + }), + mustNewPackage(t, nvd.CpeMatch{ + Vulnerable: true, + Criteria: "cpe:2.3:a:some-vendor:other-app:*:*:*:*:*:*:*:*", + }), + mustNewPackage(t, nvd.CpeMatch{ + Vulnerable: true, + Criteria: "cpe:2.3:a:some-vendor:some-app:*:*:*:*:*:*:*:*", + }), + mustNewPackage(t, nvd.CpeMatch{ + Vulnerable: true, + Criteria: "cpe:2.3:a:some-vendor:other-app:*:*:*:*:*:*:*:*", + }), + }, + ), + }, + { + name: "top-level OR is excluded", + state: nvd.Configuration{ + Operator: opRef(nvd.Or), + }, + wantChanged: false, + wantSet: newUniquePkgTracker(), + }, + { + name: "top-level nil op is excluded", + state: nvd.Configuration{ + Operator: nil, + }, + wantChanged: false, + }, + { + name: "single hardware node results in exclusion", + state: nvd.Configuration{ + Negate: nil, + Nodes: []nvd.Node{ + { + CpeMatch: []nvd.CpeMatch{ + { + Vulnerable: true, + Criteria: "cpe:2.3:a:some-vendor:some-app:*:*:*:*:*:*:*:*", + }, + { + Vulnerable: true, + Criteria: "cpe:2.3:a:some-vendor:other-app:*:*:*:*:*:*:*:*", + }, + }, + Negate: nil, + Operator: nvd.Or, + }, + { + CpeMatch: []nvd.CpeMatch{ + { + Vulnerable: false, + Criteria: "cpe:2.3:o:some-vendor:some-platform:*:*:*:*:*:*:*:*", + }, + { + Vulnerable: false, + Criteria: "cpe:2.3:h:some-vendor:some-device:*:*:*:*:*:*:*:*", + }, + }, + Negate: nil, + Operator: nvd.Or, + }, + }, + Operator: opRef(nvd.And), + }, + wantChanged: false, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if tc.config == (Config{}) { + tc.config = defaultConfig() + } + set := newUniquePkgTracker() + result := platformPackageCandidates(tc.config, set, tc.state) + assert.Equal(t, result, tc.wantChanged) + if tc.wantSet == nil { + tc.wantSet = newUniquePkgTracker() + } + if diff := cmp.Diff(tc.wantSet.AllCandidates(), set.AllCandidates()); diff != "" { + t.Errorf("unexpected diff (-want +got)\n%s", diff) + } + }) + + } +} + +func opRef(op nvd.Operator) *nvd.Operator { + return &op +} + +func boolRef(b bool) *bool { + return &b +} + +func mustNewPackage(t *testing.T, match nvd.CpeMatch, cfg ...Config) pkgCandidate { + var tCfg *Config + switch len(cfg) { + case 0: + c := defaultConfig() + tCfg = &c + case 1: + tCfg = &cfg[0] + default: + t.Fatalf("too many configs provided") + } + p, err := newPkgCandidate(*tCfg, match) + require.NoError(t, err) + return *p +} diff --git a/pkg/process/v6/transformers/nvd/unique_pkg_tracker.go b/pkg/process/v6/transformers/nvd/unique_pkg_tracker.go new file mode 100644 index 00000000..cb9384ba --- /dev/null +++ b/pkg/process/v6/transformers/nvd/unique_pkg_tracker.go @@ -0,0 +1,133 @@ +package nvd + +import ( + "sort" + "strings" + + "github.com/scylladb/go-set/strset" + + "github.com/anchore/grype-db/internal/log" + "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd" + "github.com/anchore/syft/syft/cpe" +) + +type applicationMatches []nvd.CpeMatch + +type platformMatches []nvd.CpeMatch + +type matches struct { + platformMatches []nvd.CpeMatch + matches []nvd.CpeMatch +} + +type uniquePkgTracker map[pkgCandidate]matches + +func newUniquePkgTracker() uniquePkgTracker { + return make(uniquePkgTracker) +} + +func (s uniquePkgTracker) Diff(other uniquePkgTracker) (missing []pkgCandidate, extra []pkgCandidate) { + for k := range s { + if !other.Contains(k) { + missing = append(missing, k) + } + } + + for k := range other { + if !s.Contains(k) { + extra = append(extra, k) + } + } + + return +} + +func (a applicationMatches) CPEs() []string { + cpes := strset.New() + for _, m := range a { + atts, err := cpe.NewAttributes(strings.ToLower(m.Criteria)) + if err != nil { + log.WithFields("cpe", m.Criteria, "error", err).Warn("could not parse CPE, dropping...") + continue + } + // we reason about version information later, so we can ignore it here + atts.Version = cpe.Any + atts.Update = cpe.Any + cpes.Add(atts.String()) + } + cpeList := cpes.List() + sort.Strings(cpeList) + return cpeList +} + +func (s uniquePkgTracker) ApplicationMatches(i pkgCandidate) applicationMatches { + return s[i].matches +} + +func (a platformMatches) CPEs() []string { + cpes := strset.New() + for _, m := range a { + cpes.Add(strings.ToLower(m.Criteria)) + } + if cpes.Size() == 0 { + return nil + } + cpeList := cpes.List() + sort.Strings(cpeList) + return cpeList +} + +func (s uniquePkgTracker) PlatformMatches(i pkgCandidate) platformMatches { + return s[i].platformMatches +} + +func (s uniquePkgTracker) AddWithDetection(i pkgCandidate, matches ...nvd.CpeMatch) { + m := s[i] + for _, match := range matches { + if isPlatformMatch(match) { + m.platformMatches = append(m.platformMatches, match) + } else { + m.matches = append(m.matches, match) + } + } + s[i] = m +} + +func (s uniquePkgTracker) AddExplicit(i pkgCandidate, applicationMatches nvd.CpeMatch, platformMatches []nvd.CpeMatch) { + m := s[i] + m.platformMatches = append(m.platformMatches, platformMatches...) + m.matches = append(m.matches, applicationMatches) + s[i] = m +} + +func isPlatformMatch(match nvd.CpeMatch) bool { + fields := strings.Split(match.Criteria, ":") + if len(fields) > 2 { + return fields[2] == "o" + } + return false +} + +func (s uniquePkgTracker) Remove(i pkgCandidate) { + delete(s, i) +} + +func (s uniquePkgTracker) Contains(i pkgCandidate) bool { + _, ok := s[i] + return ok +} + +func (s uniquePkgTracker) AllCandidates() []pkgCandidate { + res := make([]pkgCandidate, len(s)) + idx := 0 + for k := range s { + res[idx] = k + idx++ + } + + sort.SliceStable(res, func(i, j int) bool { + return res[i].String() < res[j].String() + }) + + return res +} diff --git a/pkg/process/v6/transformers/nvd/unique_pkg_tracker_test.go b/pkg/process/v6/transformers/nvd/unique_pkg_tracker_test.go new file mode 100644 index 00000000..deb2d3bc --- /dev/null +++ b/pkg/process/v6/transformers/nvd/unique_pkg_tracker_test.go @@ -0,0 +1,150 @@ +package nvd + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd" +) + +func TestUniquePkgTracker(t *testing.T) { + + t.Run("AddWithDetection and AllCandidates", func(t *testing.T) { + tracker := newUniquePkgTracker() + pkg1 := pkgCandidate{ + Product: "product1", + Vendor: "vendor1", + TargetSoftware: "software1", + } + match := nvd.CpeMatch{Criteria: "cpe:2.3:o:vendor1:product1:1.0:*:*:*:*:*:*:*"} + + tracker.AddWithDetection(pkg1, match) + + require.Len(t, tracker.AllCandidates(), 1) + assert.Equal(t, pkg1, tracker.AllCandidates()[0]) + }) + + t.Run("AddExplicit", func(t *testing.T) { + tracker := newUniquePkgTracker() + pkg1 := pkgCandidate{ + Product: "product1", + Vendor: "vendor1", + TargetSoftware: "software1", + } + appMatch := nvd.CpeMatch{Criteria: "cpe:2.3:a:vendor1:product1:1.0:*:*:*:*:*:*:*"} + platformMatch := nvd.CpeMatch{Criteria: "cpe:2.3:o:vendor1:platform:2.0:*:*:*:*:*:*:*"} + + tracker.AddExplicit(pkg1, appMatch, []nvd.CpeMatch{platformMatch}) + + require.Len(t, tracker.AllCandidates(), 1) + assert.Equal(t, pkg1, tracker.AllCandidates()[0]) + + appMatches := tracker.ApplicationMatches(pkg1) + require.Len(t, appMatches, 1) + assert.Equal(t, appMatch.Criteria, appMatches[0].Criteria) + + platformMatches := tracker.PlatformMatches(pkg1) + require.Len(t, platformMatches, 1) + assert.Equal(t, platformMatch.Criteria, platformMatches[0].Criteria) + }) + + t.Run("Remove", func(t *testing.T) { + tracker := newUniquePkgTracker() + pkg1 := pkgCandidate{ + Product: "product1", + Vendor: "vendor1", + TargetSoftware: "software1", + } + match := nvd.CpeMatch{Criteria: "cpe:2.3:o:vendor1:product1:1.0:*:*:*:*:*:*:*"} + + tracker.AddWithDetection(pkg1, match) + tracker.Remove(pkg1) + + assert.Empty(t, tracker.AllCandidates()) + }) + + t.Run("Contains", func(t *testing.T) { + tracker := newUniquePkgTracker() + pkg1 := pkgCandidate{ + Product: "product1", + Vendor: "vendor1", + TargetSoftware: "software1", + } + match := nvd.CpeMatch{Criteria: "cpe:2.3:o:vendor1:product1:1.0:*:*:*:*:*:*:*"} + + assert.False(t, tracker.Contains(pkg1)) + + tracker.AddWithDetection(pkg1, match) + assert.True(t, tracker.Contains(pkg1)) + }) + + t.Run("Diff", func(t *testing.T) { + tracker1 := newUniquePkgTracker() + tracker2 := newUniquePkgTracker() + + pkg1 := pkgCandidate{ + Product: "product1", + Vendor: "vendor1", + TargetSoftware: "software1", + } + pkg2 := pkgCandidate{ + Product: "product2", + Vendor: "vendor2", + TargetSoftware: "software2", + } + pkg3 := pkgCandidate{ + Product: "product3", + Vendor: "vendor3", + TargetSoftware: "software3", + } + + match := nvd.CpeMatch{Criteria: "cpe:2.3:o:vendor1:product1:1.0:*:*:*:*:*:*:*"} + tracker1.AddWithDetection(pkg1, match) + tracker1.AddWithDetection(pkg2, match) + + tracker2.AddWithDetection(pkg2, match) + tracker2.AddWithDetection(pkg3, match) + + missing, extra := tracker1.Diff(tracker2) + + require.Len(t, missing, 1) + assert.Equal(t, pkg1, missing[0]) + + require.Len(t, extra, 1) + assert.Equal(t, pkg3, extra[0]) + }) + + t.Run("platformMatches", func(t *testing.T) { + tracker := newUniquePkgTracker() + pkg1 := pkgCandidate{ + Product: "product1", + Vendor: "vendor1", + TargetSoftware: "software1", + } + platformMatch := nvd.CpeMatch{Criteria: "cpe:2.3:o:vendor1:platform:2.0:*:*:*:*:*:*:*"} + + tracker.AddWithDetection(pkg1, platformMatch) + + platformMatches := tracker.PlatformMatches(pkg1) + require.Len(t, platformMatches, 1) + assert.Equal(t, platformMatch.Criteria, platformMatches[0].Criteria) + }) + + t.Run("applicationMatches", func(t *testing.T) { + tracker := newUniquePkgTracker() + pkg1 := pkgCandidate{ + Product: "product1", + Vendor: "vendor1", + TargetSoftware: "software1", + } + appMatch := nvd.CpeMatch{Criteria: "cpe:2.3:a:vendor1:product1:1.0:*:*:*:*:*:*:*"} + + tracker.AddWithDetection(pkg1, appMatch) + + appMatches := tracker.ApplicationMatches(pkg1) + require.Len(t, appMatches, 1) + assert.Equal(t, appMatch.Criteria, appMatches[0].Criteria) + }) +} diff --git a/pkg/process/v6/transformers/os/test-fixtures/alpine-3.9.json b/pkg/process/v6/transformers/os/test-fixtures/alpine-3.9.json new file mode 100644 index 00000000..b9d84395 --- /dev/null +++ b/pkg/process/v6/transformers/os/test-fixtures/alpine-3.9.json @@ -0,0 +1,28 @@ +[ + { + "Vulnerability": { + "CVSS": [], + "Description": "", + "FixedIn": [ + { + "Name": "xen", + "NamespaceName": "alpine:3.9", + "Version": "4.11.1-r0", + "VersionFormat": "apk" + } + ], + "Link": "http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-19967", + "Metadata": { + "NVD": { + "CVSSv2": { + "Score": 4.9, + "Vectors": "AV:L/AC:L/Au:N/C:N/I:N/A:C" + } + } + }, + "Name": "CVE-2018-19967", + "NamespaceName": "alpine:3.9", + "Severity": "Medium" + } + } +] \ No newline at end of file diff --git a/pkg/process/v6/transformers/os/test-fixtures/amazon-multiple-kernel-advisories.json b/pkg/process/v6/transformers/os/test-fixtures/amazon-multiple-kernel-advisories.json new file mode 100644 index 00000000..82f2b45b --- /dev/null +++ b/pkg/process/v6/transformers/os/test-fixtures/amazon-multiple-kernel-advisories.json @@ -0,0 +1,104 @@ +[ + { + "Vulnerability": { + "Name": "ALAS-2021-1704", + "NamespaceName": "amzn:2", + "Description": "", + "Severity": "Medium", + "Metadata": { + "CVE": [ + { + "Name": "CVE-2021-3653" + }, + { + "Name": "CVE-2021-3656" + }, + { + "Name": "CVE-2021-3732" + } + ] + }, + "Link": "https://alas.aws.amazon.com/AL2/ALAS-2021-1704.html", + "FixedIn": [ + { + "Name": "kernel-headers", + "NamespaceName": "amzn:2", + "VersionFormat": "rpm", + "Version": "4.14.246-187.474.amzn2" + }, + { + "Name": "kernel", + "NamespaceName": "amzn:2", + "VersionFormat": "rpm", + "Version": "4.14.246-187.474.amzn2" + } + ] + } + }, + { + "Vulnerability": { + "Name": "ALASKERNEL-5.4-2022-007", + "NamespaceName": "amzn:2", + "Description": "", + "Severity": "Medium", + "Metadata": { + "CVE": [ + { + "Name": "CVE-2021-3753" + }, + { + "Name": "CVE-2021-40490" + } + ] + }, + "Link": "https://alas.aws.amazon.com/AL2/ALASKERNEL-5.4-2022-007.html", + "FixedIn": [ + { + "Name": "kernel-headers", + "NamespaceName": "amzn:2", + "VersionFormat": "rpm", + "Version": "5.4.144-69.257.amzn2" + }, + { + "Name": "kernel", + "NamespaceName": "amzn:2", + "VersionFormat": "rpm", + "Version": "5.4.144-69.257.amzn2" + } + ] + } + }, + { + "Vulnerability": { + "Name": "ALASKERNEL-5.10-2022-005", + "NamespaceName": "amzn:2", + "Description": "", + "Severity": "Medium", + "Metadata": { + "CVE": [ + { + "Name": "CVE-2021-3753" + }, + { + "Name": "CVE-2021-40490" + } + ] + }, + "Link": "https://alas.aws.amazon.com/AL2/ALASKERNEL-5.10-2022-005.html", + "FixedIn": [ + { + "Name": "kernel-headers", + "NamespaceName": "amzn:2", + "VersionFormat": "rpm", + "Version": "5.10.62-55.141.amzn2" + }, + { + "Name": "kernel", + "NamespaceName": "amzn:2", + "VersionFormat": "rpm", + "Version": "5.10.62-55.141.amzn2" + } + ] + } + } +] \ No newline at end of file diff --git a/pkg/process/v6/transformers/os/test-fixtures/amzn.json b/pkg/process/v6/transformers/os/test-fixtures/amzn.json new file mode 100644 index 00000000..2c551050 --- /dev/null +++ b/pkg/process/v6/transformers/os/test-fixtures/amzn.json @@ -0,0 +1,50 @@ +[ + { + "Vulnerability": { + "Description": "", + "FixedIn": [ + { + "Name": "389-ds-base", + "NamespaceName": "amzn:2", + "Version": "1.3.8.4-15.amzn2.0.1", + "VersionFormat": "rpm" + }, + { + "Name": "389-ds-base-debuginfo", + "NamespaceName": "amzn:2", + "Version": "1.3.8.4-15.amzn2.0.1", + "VersionFormat": "rpm" + }, + { + "Name": "389-ds-base-devel", + "NamespaceName": "amzn:2", + "Version": "1.3.8.4-15.amzn2.0.1", + "VersionFormat": "rpm" + }, + { + "Name": "389-ds-base-libs", + "NamespaceName": "amzn:2", + "Version": "1.3.8.4-15.amzn2.0.1", + "VersionFormat": "rpm" + }, + { + "Name": "389-ds-base-snmp", + "NamespaceName": "amzn:2", + "Version": "1.3.8.4-15.amzn2.0.1", + "VersionFormat": "rpm" + } + ], + "Link": "https://alas.aws.amazon.com/AL2/ALAS-2018-1106.html", + "Metadata": { + "CVE": [ + { + "Name": "CVE-2018-14648" + } + ] + }, + "Name": "ALAS-2018-1106", + "NamespaceName": "amzn:2", + "Severity": "Medium" + } + } +] \ No newline at end of file diff --git a/pkg/process/v6/transformers/os/test-fixtures/azure-linux-3.json b/pkg/process/v6/transformers/os/test-fixtures/azure-linux-3.json new file mode 100644 index 00000000..ce25f4de --- /dev/null +++ b/pkg/process/v6/transformers/os/test-fixtures/azure-linux-3.json @@ -0,0 +1,26 @@ +[ + { + "Vulnerability": { + "Name": "CVE-2023-29403", + "NamespaceName": "mariner:3.0", + "Description": "CVE-2023-29403 affecting package golang for versions less than 1.20.7-1. A patched version of the package is available.", + "Severity": "High", + "Link": "https://nvd.nist.gov/vuln/detail/CVE-2023-29403", + "CVSS": [], + "FixedIn": [ + { + "Name": "golang", + "NamespaceName": "mariner:3.0", + "VersionFormat": "rpm", + "Version": "0:1.20.7-1.azl3", + "Module": "", + "VendorAdvisory": { + "NoAdvisory": false, + "AdvisorySummary": [] + } + } + ], + "Metadata": {} + } + } +] diff --git a/pkg/process/v6/transformers/os/test-fixtures/debian-8-multiple-entries-for-same-package.json b/pkg/process/v6/transformers/os/test-fixtures/debian-8-multiple-entries-for-same-package.json new file mode 100644 index 00000000..5025b56e --- /dev/null +++ b/pkg/process/v6/transformers/os/test-fixtures/debian-8-multiple-entries-for-same-package.json @@ -0,0 +1,62 @@ +[ + { + "Vulnerability": { + "CVSS": [], + "Description": "", + "FixedIn": [ + { + "Name": "rsyslog", + "NamespaceName": "debian:8", + "VendorAdvisory": { + "AdvisorySummary": [], + "NoAdvisory": false + }, + "Version": "5.7.4-1", + "VersionFormat": "dpkg" + } + ], + "Link": "https://security-tracker.debian.org/tracker/CVE-2011-4623", + "Metadata": { + "NVD": { + "CVSSv2": { + "Score": 2.1, + "Vectors": "AV:L/AC:L/Au:N/C:N/I:N/A:P" + } + } + }, + "Name": "CVE-2011-4623", + "NamespaceName": "debian:8", + "Severity": "Low" + } + }, + { + "Vulnerability": { + "CVSS": [], + "Description": "", + "FixedIn": [ + { + "Name": "rsyslog", + "NamespaceName": "debian:8", + "VendorAdvisory": { + "AdvisorySummary": [], + "NoAdvisory": false + }, + "Version": "3.18.6-1", + "VersionFormat": "dpkg" + } + ], + "Link": "https://security-tracker.debian.org/tracker/CVE-2008-5618", + "Metadata": { + "NVD": { + "CVSSv2": { + "Score": 5, + "Vectors": "AV:N/AC:L/Au:N/C:N/I:N/A:P" + } + } + }, + "Name": "CVE-2008-5618", + "NamespaceName": "debian:8", + "Severity": "Low" + } + } +] \ No newline at end of file diff --git a/pkg/process/v6/transformers/os/test-fixtures/debian-8.json b/pkg/process/v6/transformers/os/test-fixtures/debian-8.json new file mode 100644 index 00000000..a758f13c --- /dev/null +++ b/pkg/process/v6/transformers/os/test-fixtures/debian-8.json @@ -0,0 +1,62 @@ +[ + { + "Vulnerability": { + "CVSS": [], + "Description": "", + "FixedIn": [ + { + "Name": "asterisk", + "NamespaceName": "debian:8", + "VendorAdvisory": { + "AdvisorySummary": [], + "NoAdvisory": false + }, + "Version": "1:1.6.2.0~rc3-1", + "VersionFormat": "dpkg" + }, + { + "Name": "auth2db", + "NamespaceName": "debian:8", + "VendorAdvisory": { + "AdvisorySummary": [], + "NoAdvisory": false + }, + "Version": "0.2.5-2+dfsg-1", + "VersionFormat": "dpkg" + }, + { + "Name": "exaile", + "NamespaceName": "debian:8", + "VendorAdvisory": { + "AdvisorySummary": [], + "NoAdvisory": false + }, + "Version": "0.2.14+debian-2.2", + "VersionFormat": "dpkg" + }, + { + "Name": "wordpress", + "NamespaceName": "debian:8", + "VendorAdvisory": { + "AdvisorySummary": [], + "NoAdvisory": false + }, + "Version": "", + "VersionFormat": "dpkg" + } + ], + "Link": "https://security-tracker.debian.org/tracker/CVE-2008-7220", + "Metadata": { + "NVD": { + "CVSSv2": { + "Score": 7.5, + "Vectors": "AV:N/AC:L/Au:N/C:P/I:P/A:P" + } + } + }, + "Name": "CVE-2008-7220", + "NamespaceName": "debian:8", + "Severity": "High" + } + } +] \ No newline at end of file diff --git a/pkg/process/v6/transformers/os/test-fixtures/mariner-20.json b/pkg/process/v6/transformers/os/test-fixtures/mariner-20.json new file mode 100644 index 00000000..20cb1465 --- /dev/null +++ b/pkg/process/v6/transformers/os/test-fixtures/mariner-20.json @@ -0,0 +1,26 @@ +[ + { + "Vulnerability": { + "Name": "CVE-2021-37621", + "NamespaceName": "mariner:2.0", + "Description": "CVE-2021-37621 affecting package exiv2 for versions less than 0.27.5-1. An upgraded version of the package is available that resolves this issue.", + "Severity": "Medium", + "Link": "https://nvd.nist.gov/vuln/detail/CVE-2021-37621", + "CVSS": [], + "FixedIn": [ + { + "Name": "exiv2", + "NamespaceName": "mariner:2.0", + "VersionFormat": "rpm", + "Version": "0:0.27.5-1.cm2", + "Module": "", + "VendorAdvisory": { + "NoAdvisory": false, + "AdvisorySummary": [] + } + } + ], + "Metadata": {} + } + } +] diff --git a/pkg/process/v6/transformers/os/test-fixtures/mariner-range.json b/pkg/process/v6/transformers/os/test-fixtures/mariner-range.json new file mode 100644 index 00000000..3ec9731f --- /dev/null +++ b/pkg/process/v6/transformers/os/test-fixtures/mariner-range.json @@ -0,0 +1,27 @@ +[ + { + "Vulnerability": { + "Name": "CVE-2023-29404", + "NamespaceName": "mariner:2.0", + "Description": "CVE-2023-29404 affecting package golang for versions less than 1.20.7-1. A patched version of the package is available.", + "Severity": "Critical", + "Link": "https://nvd.nist.gov/vuln/detail/CVE-2023-29404", + "CVSS": [], + "FixedIn": [ + { + "Name": "golang", + "NamespaceName": "mariner:2.0", + "VersionFormat": "rpm", + "Version": "0:1.20.7-1.cm2", + "Module": "", + "VendorAdvisory": { + "NoAdvisory": false, + "AdvisorySummary": [] + }, + "VulnerableRange": "> 0:1.19.0.cm2, < 0:1.20.7-1.cm2" + } + ], + "Metadata": {} + } + } +] diff --git a/pkg/process/v6/transformers/os/test-fixtures/ol-8-modules.json b/pkg/process/v6/transformers/os/test-fixtures/ol-8-modules.json new file mode 100644 index 00000000..f1d7372b --- /dev/null +++ b/pkg/process/v6/transformers/os/test-fixtures/ol-8-modules.json @@ -0,0 +1,36 @@ +[ + { + "Vulnerability": { + "CVSS": [], + "Description": "A flaw was found in PostgreSQL, where some PostgreSQL extensions did not use the search_path safely in their installation script. This flaw allows an attacker with sufficient privileges to trick an administrator into executing a specially crafted script during the extension's installation or update. The highest threat from this vulnerability is to confidentiality, integrity, as well as system availability.", + "FixedIn": [ + { + "Module": "postgresql:10", + "Name": "postgresql", + "NamespaceName": "ol:8", + "Version": "0:10.14-1.module+el8.2.0+7801+be0fed80", + "VersionFormat": "rpm" + }, + { + "Module": "postgresql:12", + "Name": "postgresql", + "NamespaceName": "ol:8", + "Version": "0:12.5-1.module+el8.3.0+9042+664538f4", + "VersionFormat": "rpm" + }, + { + "Module": "postgresql:9.6", + "Name": "postgresql", + "NamespaceName": "ol:8", + "Version": "0:9.6.20-1.module+el8.3.0+8938+7f0e88b6", + "VersionFormat": "rpm" + } + ], + "Link": "https://access.redhat.com/security/cve/CVE-2020-14350", + "Metadata": {}, + "Name": "CVE-2020-14350", + "NamespaceName": "ol:8", + "Severity": "Medium" + } + } +] \ No newline at end of file diff --git a/pkg/process/v6/transformers/os/test-fixtures/ol-8.json b/pkg/process/v6/transformers/os/test-fixtures/ol-8.json new file mode 100644 index 00000000..09439ece --- /dev/null +++ b/pkg/process/v6/transformers/os/test-fixtures/ol-8.json @@ -0,0 +1,42 @@ +[ + { + "Vulnerability": { + "CVSS": [], + "Description": "", + "FixedIn": [ + { + "Name": "libexif", + "NamespaceName": "ol:8", + "Version": "0:0.6.21-17.el8_2", + "VersionFormat": "rpm" + }, + { + "Name": "libexif-devel", + "NamespaceName": "ol:8", + "Version": "0:0.6.21-17.el8_2", + "VersionFormat": "rpm" + }, + { + "Name": "libexif-dummy", + "NamespaceName": "ol:8", + "Version": "None", + "VersionFormat": "rpm" + } + ], + "Link": "http://linux.oracle.com/errata/ELSA-2020-2550.html", + "Metadata": { + "CVE": [ + { + "Link": "http://linux.oracle.com/cve/CVE-2020-13112.html", + "Name": "CVE-2020-13112" + } + ], + "Issued": "2020-06-15", + "RefId": "ELSA-2020-2550" + }, + "Name": "ELSA-2020-2550", + "NamespaceName": "ol:8", + "Severity": "Medium" + } + } +] \ No newline at end of file diff --git a/pkg/process/v6/transformers/os/test-fixtures/rhel-8-modules.json b/pkg/process/v6/transformers/os/test-fixtures/rhel-8-modules.json new file mode 100644 index 00000000..c0400ad5 --- /dev/null +++ b/pkg/process/v6/transformers/os/test-fixtures/rhel-8-modules.json @@ -0,0 +1,75 @@ +[ + { + "Vulnerability": { + "CVSS": [ + { + "base_metrics": { + "base_score": 7.1, + "base_severity": "High", + "exploitability_score": 1.2, + "impact_score": 5.9 + }, + "status": "verified", + "vector_string": "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:H", + "version": "3.1" + } + ], + "Description": "A flaw was found in PostgreSQL, where some PostgreSQL extensions did not use the search_path safely in their installation script. This flaw allows an attacker with sufficient privileges to trick an administrator into executing a specially crafted script during the extension's installation or update. The highest threat from this vulnerability is to confidentiality, integrity, as well as system availability.", + "FixedIn": [ + { + "Module": "postgresql:10", + "Name": "postgresql", + "NamespaceName": "rhel:8", + "VendorAdvisory": { + "AdvisorySummary": [ + { + "ID": "RHSA-2020:3669", + "Link": "https://access.redhat.com/errata/RHSA-2020:3669" + } + ], + "NoAdvisory": false + }, + "Version": "0:10.14-1.module+el8.2.0+7801+be0fed80", + "VersionFormat": "rpm" + }, + { + "Module": "postgresql:12", + "Name": "postgresql", + "NamespaceName": "rhel:8", + "VendorAdvisory": { + "AdvisorySummary": [ + { + "ID": "RHSA-2020:5620", + "Link": "https://access.redhat.com/errata/RHSA-2020:5620" + } + ], + "NoAdvisory": false + }, + "Version": "0:12.5-1.module+el8.3.0+9042+664538f4", + "VersionFormat": "rpm" + }, + { + "Module": "postgresql:9.6", + "Name": "postgresql", + "NamespaceName": "rhel:8", + "VendorAdvisory": { + "AdvisorySummary": [ + { + "ID": "RHSA-2020:5619", + "Link": "https://access.redhat.com/errata/RHSA-2020:5619" + } + ], + "NoAdvisory": false + }, + "Version": "0:9.6.20-1.module+el8.3.0+8938+7f0e88b6", + "VersionFormat": "rpm" + } + ], + "Link": "https://access.redhat.com/security/cve/CVE-2020-14350", + "Metadata": {}, + "Name": "CVE-2020-14350", + "NamespaceName": "rhel:8", + "Severity": "Medium" + } + } +] \ No newline at end of file diff --git a/pkg/process/v6/transformers/os/test-fixtures/rhel-8.json b/pkg/process/v6/transformers/os/test-fixtures/rhel-8.json new file mode 100644 index 00000000..2779708c --- /dev/null +++ b/pkg/process/v6/transformers/os/test-fixtures/rhel-8.json @@ -0,0 +1,57 @@ +[ + { + "Vulnerability": { + "CVSS": [ + { + "base_metrics": { + "base_score": 8.8, + "base_severity": "High", + "exploitability_score": 2.8, + "impact_score": 5.9 + }, + "status": "verified", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", + "version": "3.1" + } + ], + "Description": "A flaw was found in Mozilla Firefox. A race condition can occur while running the nsDocShell destructor causing a use-after-free memory issue. The highest threat from this vulnerability is to data confidentiality and integrity as well as system availability.", + "FixedIn": [ + { + "Name": "firefox", + "NamespaceName": "rhel:8", + "VendorAdvisory": { + "AdvisorySummary": [ + { + "ID": "RHSA-2020:1341", + "Link": "https://access.redhat.com/errata/RHSA-2020:1341" + } + ], + "NoAdvisory": false + }, + "Version": "0:68.6.1-1.el8_1", + "VersionFormat": "rpm" + }, + { + "Name": "thunderbird", + "NamespaceName": "rhel:8", + "VendorAdvisory": { + "AdvisorySummary": [ + { + "ID": "RHSA-2020:1495", + "Link": "https://access.redhat.com/errata/RHSA-2020:1495" + } + ], + "NoAdvisory": false + }, + "Version": "0:68.7.0-1.el8_1", + "VersionFormat": "rpm" + } + ], + "Link": "https://access.redhat.com/security/cve/CVE-2020-6819", + "Metadata": {}, + "Name": "CVE-2020-6819", + "NamespaceName": "rhel:8", + "Severity": "Critical" + } + } +] \ No newline at end of file diff --git a/pkg/process/v6/transformers/os/transform.go b/pkg/process/v6/transformers/os/transform.go new file mode 100644 index 00000000..a177bf55 --- /dev/null +++ b/pkg/process/v6/transformers/os/transform.go @@ -0,0 +1,354 @@ +package os + +import ( + "fmt" + "sort" + "strconv" + "strings" + + "github.com/scylladb/go-set/strset" + + "github.com/anchore/grype-db/internal/log" + "github.com/anchore/grype-db/pkg/data" + "github.com/anchore/grype-db/pkg/process/internal/codename" + "github.com/anchore/grype-db/pkg/process/internal/common" + "github.com/anchore/grype-db/pkg/process/v6/transformers" + "github.com/anchore/grype-db/pkg/process/v6/transformers/internal" + "github.com/anchore/grype-db/pkg/provider" + "github.com/anchore/grype-db/pkg/provider/unmarshal" + grypeDB "github.com/anchore/grype/grype/db/v6" + "github.com/anchore/grype/grype/distro" + "github.com/anchore/syft/syft/pkg" +) + +func Transform(vulnerability unmarshal.OSVulnerability, state provider.State) ([]data.Entry, error) { + in := []any{ + internal.ProviderModel(state), + grypeDB.VulnerabilityHandle{ + Name: vulnerability.Vulnerability.Name, + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: vulnerability.Vulnerability.Name, + ProviderName: state.Provider, + Assigner: nil, + Description: strings.TrimSpace(vulnerability.Vulnerability.Description), + Status: grypeDB.VulnerabilityActive, + References: getReferences(vulnerability), + Aliases: getAliases(vulnerability), + Severities: getSeverities(vulnerability), + ModifiedDate: internal.ParseTime(vulnerability.Vulnerability.Metadata.Updated), + PublishedDate: internal.ParseTime(vulnerability.Vulnerability.Metadata.Issued), + }, + }, + } + + for _, a := range getAffectedPackages(vulnerability) { + in = append(in, a) + } + + return transformers.NewEntries(in...), nil +} + +func getAffectedPackages(vuln unmarshal.OSVulnerability) []grypeDB.AffectedPackageHandle { + var afs []grypeDB.AffectedPackageHandle + groups := groupFixedIns(vuln) + for group, fixedIns := range groups { + var qualifiers *grypeDB.AffectedPackageQualifiers + if group.module != "" { + qualifiers = &grypeDB.AffectedPackageQualifiers{ + RpmModularity: group.module, + } + } + + aph := grypeDB.AffectedPackageHandle{ + OperatingSystem: getOperatingSystem(group.osName, group.osVersion), + Package: getPackage(group), + + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: getAliases(vuln), + Qualifiers: qualifiers, + Ranges: nil, + }, + } + + var ranges []grypeDB.AffectedRange + for _, fixedInEntry := range fixedIns { + ranges = append(ranges, grypeDB.AffectedRange{ + Version: grypeDB.AffectedVersion{ + Type: fixedInEntry.VersionFormat, + Constraint: enforceConstraint(fixedInEntry.Version, fixedInEntry.VulnerableRange, fixedInEntry.VersionFormat, vuln.Vulnerability.Name), + }, + Fix: getFix(fixedInEntry), + }) + } + aph.BlobValue.Ranges = ranges + afs = append(afs, aph) + } + + // stable ordering + sort.Sort(internal.ByAffectedPackage(afs)) + + return afs +} + +func getFix(fixedInEntry unmarshal.OSFixedIn) *grypeDB.Fix { + fixedInVersion := common.CleanFixedInVersion(fixedInEntry.Version) + + fixState := grypeDB.NotFixedStatus + if len(fixedInVersion) > 0 { + fixState = grypeDB.FixedStatus + } else if fixedInEntry.VendorAdvisory.NoAdvisory { + fixState = grypeDB.WontFixStatus + } + + var linkOrder []string + linkSet := strset.New() + for _, a := range fixedInEntry.VendorAdvisory.AdvisorySummary { + if a.Link != "" && !linkSet.Has(a.Link) { + linkOrder = append(linkOrder, a.Link) + linkSet.Add(a.Link) + } + } + + var refs []grypeDB.Reference + for _, l := range linkOrder { + refs = append(refs, grypeDB.Reference{ + Tags: []string{grypeDB.AdvisoryReferenceTag}, + URL: l, + }) + } + + var detail *grypeDB.FixDetail + if len(refs) > 0 { + detail = &grypeDB.FixDetail{ + References: refs, + } + } + + return &grypeDB.Fix{ + Version: fixedInVersion, + State: fixState, + Detail: detail, + } +} + +func enforceConstraint(fixedVersion, vulnerableRange, format, vulnerabilityID string) string { + if len(vulnerableRange) > 0 { + return vulnerableRange + } + fixedVersion = common.CleanConstraint(fixedVersion) + if len(fixedVersion) == 0 { + return "" + } + switch strings.ToLower(format) { + case "semver": + return common.EnforceSemVerConstraint(fixedVersion) + default: + // the passed constraint is a fixed version + return deriveConstraintFromFix(fixedVersion, vulnerabilityID) + } +} + +func deriveConstraintFromFix(fixVersion, vulnerabilityID string) string { + constraint := fmt.Sprintf("< %s", fixVersion) + + if strings.HasPrefix(vulnerabilityID, "ALASKERNEL-") { + // Amazon advisories of the form ALASKERNEL-5.4-2023-048 should be interpreted as only applying to + // the 5.4.x kernel line since Amazon issue a separate advisory per affected line, thus the constraint + // should be >= 5.4, < {fix version}. In the future the vunnel schema for OS vulns should be enhanced + // to emit actual constraints rather than fixed-in entries (tracked in https://github.com/anchore/vunnel/issues/266) + // at which point this workaround in grype-db can be removed. + + components := strings.Split(vulnerabilityID, "-") + + if len(components) == 4 { + base := components[1] + constraint = fmt.Sprintf(">= %s, < %s", base, fixVersion) + } + } + + return constraint +} + +type groupIndex struct { + name string + osName string + osVersion string + module string +} + +func groupFixedIns(vuln unmarshal.OSVulnerability) map[groupIndex][]unmarshal.OSFixedIn { + grouped := make(map[groupIndex][]unmarshal.OSFixedIn) + osName, osVersion := getOSInfo(vuln.Vulnerability.NamespaceName) + + for _, fixedIn := range vuln.Vulnerability.FixedIn { + var mod string + if fixedIn.Module != nil { + mod = *fixedIn.Module + } + g := groupIndex{ + name: fixedIn.Name, + osName: osName, + osVersion: osVersion, + module: mod, + } + + grouped[g] = append(grouped[g], fixedIn) + } + return grouped +} + +func getPackageType(osName string) string { + switch osName { + case "redhat", "amazon", "oracle", "sles", "mariner", "azurelinux": + return string(pkg.RpmPkg) + case "ubuntu", "debian": + return string(pkg.DebPkg) + case "alpine", "chainguard", "wolfi": + return string(pkg.ApkPkg) + case "windows": + return "msrc-kb" + } + + return "" +} + +func getPackage(group groupIndex) *grypeDB.Package { + return &grypeDB.Package{ + Type: getPackageType(group.osName), + Name: group.name, + } +} + +func getOSInfo(group string) (string, string) { + // derived from enterprise feed groups, expected to be of the form {distroID}:{version} + feedGroupComponents := strings.Split(group, ":") + + return normalizeOsName(feedGroupComponents[0], feedGroupComponents[1]), feedGroupComponents[1] +} + +// add new fields to OS schema: release-id, release-version-id +// update vunnel providers to emit these fields (they are based on the /etc/os-release values) +// update this code to STOP parsing namespace and start using those new fields +// now when a user searches by OS (from the /etc/os-release values) they will get the correct results +// what's missing: +// - when to search by major version vs major.minor version... +// - edge/rolling behavior +// - aliases: user has centos 8, but the feed has rhel 8, use that instead +func normalizeOsName(name, version string) string { + if strings.ToLower(name) == "mariner" { + verFields := strings.Split(version, ".") + majorVersionStr := verFields[0] + majorVer, err := strconv.Atoi(majorVersionStr) + if err == nil { + if majorVer >= 3 { + name = string(distro.Azure) + } + } + } + d, ok := distro.IDMapping[name] + if !ok { + log.WithFields("distro", name).Warn("unknown distro name") + + return name + } + + distroName := d.String() + + // TODO: this doesn't seem right + switch d { + case distro.OracleLinux: + distroName = "oracle" + case distro.AmazonLinux: + distroName = "amazon" + } + return distroName +} + +func getOperatingSystem(osName, osVersion string) *grypeDB.OperatingSystem { + if osName == "" || osVersion == "" { + return nil + } + + versionFields := strings.Split(osVersion, ".") + var majorVersion, minorVersion string + majorVersion = versionFields[0] + if len(versionFields) > 1 { + minorVersion = versionFields[1] + } + + return &grypeDB.OperatingSystem{ + Name: osName, + MajorVersion: majorVersion, + MinorVersion: minorVersion, + Codename: codename.LookupOS(osName, majorVersion, minorVersion), + } +} + +func getReferences(vuln unmarshal.OSVulnerability) []grypeDB.Reference { + clean := strings.TrimSpace(vuln.Vulnerability.Link) + if clean == "" { + return nil + } + + var linkOrder []string + linkSet := strset.New() + if vuln.Vulnerability.Link != "" { + linkSet.Add(vuln.Vulnerability.Link) + linkOrder = append(linkOrder, vuln.Vulnerability.Link) + } + for _, a := range vuln.Vulnerability.Metadata.CVE { + if a.Link != "" && !linkSet.Has(a.Link) { + linkOrder = append(linkOrder, a.Link) + } + } + + var refs []grypeDB.Reference + for _, l := range linkOrder { + refs = append(refs, + grypeDB.Reference{ + Tags: []string{grypeDB.AdvisoryReferenceTag}, + URL: l, + }, + ) + } + + return refs +} + +func getAliases(vuln unmarshal.OSVulnerability) []string { + var aliases []string + for _, cve := range vuln.Vulnerability.Metadata.CVE { + aliases = append(aliases, + cve.Name, + ) + } + return aliases +} + +func getSeverities(vuln unmarshal.OSVulnerability) []grypeDB.Severity { + var severities []grypeDB.Severity + + // TODO: should we clean this here or not? + if vuln.Vulnerability.Severity != "" && strings.ToLower(vuln.Vulnerability.Severity) != "unknown" { + severities = append(severities, grypeDB.Severity{ + Scheme: grypeDB.SeveritySchemeCHMLN, + Value: strings.ToLower(vuln.Vulnerability.Severity), + Rank: 1, // TODO: enum this + // TODO Source? + }) + } + for _, vendorSeverity := range vuln.Vulnerability.CVSS { + severities = append(severities, grypeDB.Severity{ + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: vendorSeverity.VectorString, + Version: vendorSeverity.Version, + Score: vendorSeverity.BaseMetrics.BaseScore, + }, + Rank: 2, + // TODO: source? + }) + } + + return severities +} diff --git a/pkg/process/v6/transformers/os/transform_test.go b/pkg/process/v6/transformers/os/transform_test.go new file mode 100644 index 00000000..2d1bf72d --- /dev/null +++ b/pkg/process/v6/transformers/os/transform_test.go @@ -0,0 +1,1143 @@ +package os + +import ( + "os" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" + + testUtils "github.com/anchore/grype-db/pkg/process/internal/tests" + "github.com/anchore/grype-db/pkg/process/v6/transformers" + "github.com/anchore/grype-db/pkg/provider" + "github.com/anchore/grype-db/pkg/provider/unmarshal" + grypeDB "github.com/anchore/grype/grype/db/v6" +) + +var timeVal = time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC) +var listing = provider.File{ + Path: "some", + Digest: "123456", + Algorithm: "sha256", +} + +func inputProviderState(name string) provider.State { + return provider.State{ + Provider: name, + Version: 12, + Processor: "vunnel@1.2.3", + Timestamp: timeVal, + Listing: &listing, + } +} + +func expectedProvider(name string) grypeDB.Provider { + return grypeDB.Provider{ + ID: name, + Version: "12", + Processor: "vunnel@1.2.3", + DateCaptured: &timeVal, + InputDigest: "sha256:123456", + } +} + +func TestTransform(t *testing.T) { + + amazonOS := &grypeDB.OperatingSystem{ + Name: "amazon", + MajorVersion: "2", + } + azure3OS := &grypeDB.OperatingSystem{ + Name: "azurelinux", + MajorVersion: "3", + MinorVersion: "0", // TODO: is this right? + } + debian8OS := &grypeDB.OperatingSystem{ + Name: "debian", + MajorVersion: "8", + Codename: "jessie", + } + + mariner2OS := &grypeDB.OperatingSystem{ + Name: "mariner", + MajorVersion: "2", + MinorVersion: "0", // TODO: is this right? + } + ol8OS := &grypeDB.OperatingSystem{ + Name: "oracle", + MajorVersion: "8", + } + rhel8OS := &grypeDB.OperatingSystem{ + Name: "redhat", + MajorVersion: "8", + } + tests := []struct { + name string + provider string + want []transformers.RelatedEntries + }{ + { + name: "test-fixtures/alpine-3.9.json", + provider: "alpine", + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: grypeDB.VulnerabilityHandle{ + Name: "CVE-2018-19967", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2018-19967", + ProviderName: "alpine", + Status: "active", + References: []grypeDB.Reference{ + { + URL: "http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-19967", + Tags: []string{grypeDB.AdvisoryReferenceTag}, + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHMLN, + Value: "medium", + Rank: 1, + }, + }, + }, + }, + Provider: expectedProvider("alpine"), + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + OperatingSystem: &grypeDB.OperatingSystem{Name: "alpine", MajorVersion: "3", MinorVersion: "9"}, + Package: &grypeDB.Package{Type: "apk", Name: "xen"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{Type: "apk", Constraint: "< 4.11.1-r0"}, + Fix: &grypeDB.Fix{Version: "4.11.1-r0", State: grypeDB.FixedStatus}, + }, + }, + }, + }, + ), + }, + }, + }, + { + name: "test-fixtures/amzn.json", + provider: "amazon", + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: grypeDB.VulnerabilityHandle{ + Name: "ALAS-2018-1106", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "ALAS-2018-1106", + ProviderName: "amazon", + Status: "active", + References: []grypeDB.Reference{ + { + URL: "https://alas.aws.amazon.com/AL2/ALAS-2018-1106.html", + Tags: []string{grypeDB.AdvisoryReferenceTag}, + }, + }, + Aliases: []string{"CVE-2018-14648"}, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHMLN, + Value: "medium", + Rank: 1, + }, + }, + }, + }, + Provider: expectedProvider("amazon"), + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + OperatingSystem: amazonOS, + Package: &grypeDB.Package{ + Name: "389-ds-base", + Type: "rpm", + }, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2018-14648"}, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{Type: "rpm", Constraint: "< 1.3.8.4-15.amzn2.0.1"}, + Fix: &grypeDB.Fix{Version: "1.3.8.4-15.amzn2.0.1", State: grypeDB.FixedStatus}, + }, + }, + }, + }, + grypeDB.AffectedPackageHandle{ + OperatingSystem: amazonOS, + Package: &grypeDB.Package{ + Name: "389-ds-base-debuginfo", + Type: "rpm", + }, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2018-14648"}, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{Type: "rpm", Constraint: "< 1.3.8.4-15.amzn2.0.1"}, + Fix: &grypeDB.Fix{Version: "1.3.8.4-15.amzn2.0.1", State: grypeDB.FixedStatus}, + }, + }, + }, + }, + grypeDB.AffectedPackageHandle{ + OperatingSystem: amazonOS, + Package: &grypeDB.Package{ + Name: "389-ds-base-devel", + Type: "rpm", + }, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2018-14648"}, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{Type: "rpm", Constraint: "< 1.3.8.4-15.amzn2.0.1"}, + Fix: &grypeDB.Fix{Version: "1.3.8.4-15.amzn2.0.1", State: grypeDB.FixedStatus}, + }, + }, + }, + }, + grypeDB.AffectedPackageHandle{ + OperatingSystem: amazonOS, + Package: &grypeDB.Package{ + Name: "389-ds-base-libs", + Type: "rpm", + }, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2018-14648"}, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{Type: "rpm", Constraint: "< 1.3.8.4-15.amzn2.0.1"}, + Fix: &grypeDB.Fix{Version: "1.3.8.4-15.amzn2.0.1", State: grypeDB.FixedStatus}, + }, + }, + }, + }, + grypeDB.AffectedPackageHandle{ + OperatingSystem: amazonOS, + Package: &grypeDB.Package{ + Name: "389-ds-base-snmp", + Type: "rpm", + }, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2018-14648"}, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{Type: "rpm", Constraint: "< 1.3.8.4-15.amzn2.0.1"}, + Fix: &grypeDB.Fix{Version: "1.3.8.4-15.amzn2.0.1", State: grypeDB.FixedStatus}, + }, + }, + }, + }, + ), + }, + }, + }, + { + name: "test-fixtures/amazon-multiple-kernel-advisories.json", + provider: "amazon", + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: grypeDB.VulnerabilityHandle{ + Name: "ALAS-2021-1704", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "ALAS-2021-1704", + ProviderName: "amazon", + Status: "active", + References: []grypeDB.Reference{ + { + URL: "https://alas.aws.amazon.com/AL2/ALAS-2021-1704.html", + Tags: []string{grypeDB.AdvisoryReferenceTag}, + }, + }, + Aliases: []string{"CVE-2021-3653", "CVE-2021-3656", "CVE-2021-3732"}, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHMLN, + Value: "medium", + Rank: 1, + }, + }, + }, + }, + Provider: expectedProvider("amazon"), + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + OperatingSystem: amazonOS, + Package: &grypeDB.Package{Type: "rpm", Name: "kernel"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2021-3653", "CVE-2021-3656", "CVE-2021-3732"}, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{Type: "rpm", Constraint: "< 4.14.246-187.474.amzn2"}, + Fix: &grypeDB.Fix{Version: "4.14.246-187.474.amzn2", State: grypeDB.FixedStatus}, + }, + }, + }, + }, + grypeDB.AffectedPackageHandle{ + OperatingSystem: amazonOS, + Package: &grypeDB.Package{Type: "rpm", Name: "kernel-headers"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2021-3653", "CVE-2021-3656", "CVE-2021-3732"}, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{Type: "rpm", Constraint: "< 4.14.246-187.474.amzn2"}, + Fix: &grypeDB.Fix{Version: "4.14.246-187.474.amzn2", State: grypeDB.FixedStatus}, + }, + }, + }, + }, + ), + }, + { + VulnerabilityHandle: grypeDB.VulnerabilityHandle{ + Name: "ALASKERNEL-5.4-2022-007", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "ALASKERNEL-5.4-2022-007", + ProviderName: "amazon", + Status: "active", + References: []grypeDB.Reference{ + { + URL: "https://alas.aws.amazon.com/AL2/ALASKERNEL-5.4-2022-007.html", + Tags: []string{grypeDB.AdvisoryReferenceTag}, + }, + }, + Aliases: []string{"CVE-2021-3753", "CVE-2021-40490"}, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHMLN, + Value: "medium", + Rank: 1, + }, + }, + }, + }, + Provider: expectedProvider("amazon"), + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + OperatingSystem: amazonOS, + Package: &grypeDB.Package{Type: "rpm", Name: "kernel"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2021-3753", "CVE-2021-40490"}, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{Type: "rpm", Constraint: ">= 5.4, < 5.4.144-69.257.amzn2"}, + Fix: &grypeDB.Fix{Version: "5.4.144-69.257.amzn2", State: grypeDB.FixedStatus}, + }, + }, + }, + }, + grypeDB.AffectedPackageHandle{ + OperatingSystem: amazonOS, + Package: &grypeDB.Package{Type: "rpm", Name: "kernel-headers"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2021-3753", "CVE-2021-40490"}, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{Type: "rpm", Constraint: ">= 5.4, < 5.4.144-69.257.amzn2"}, + Fix: &grypeDB.Fix{Version: "5.4.144-69.257.amzn2", State: grypeDB.FixedStatus}, + }, + }, + }, + }, + ), + }, + { + VulnerabilityHandle: grypeDB.VulnerabilityHandle{ + Name: "ALASKERNEL-5.10-2022-005", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "ALASKERNEL-5.10-2022-005", + ProviderName: "amazon", + Status: "active", + References: []grypeDB.Reference{ + { + URL: "https://alas.aws.amazon.com/AL2/ALASKERNEL-5.10-2022-005.html", + Tags: []string{grypeDB.AdvisoryReferenceTag}, + }, + }, + Aliases: []string{"CVE-2021-3753", "CVE-2021-40490"}, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHMLN, + Value: "medium", + Rank: 1, + }, + }, + }, + }, + Provider: expectedProvider("amazon"), + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + OperatingSystem: amazonOS, + Package: &grypeDB.Package{Type: "rpm", Name: "kernel"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2021-3753", "CVE-2021-40490"}, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{Type: "rpm", Constraint: ">= 5.10, < 5.10.62-55.141.amzn2"}, + Fix: &grypeDB.Fix{Version: "5.10.62-55.141.amzn2", State: grypeDB.FixedStatus}, + }, + }, + }, + }, + grypeDB.AffectedPackageHandle{ + OperatingSystem: amazonOS, + Package: &grypeDB.Package{Type: "rpm", Name: "kernel-headers"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2021-3753", "CVE-2021-40490"}, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{Type: "rpm", Constraint: ">= 5.10, < 5.10.62-55.141.amzn2"}, + Fix: &grypeDB.Fix{Version: "5.10.62-55.141.amzn2", State: grypeDB.FixedStatus}, + }, + }, + }, + }, + ), + }, + }, + }, + { + name: "test-fixtures/azure-linux-3.json", + provider: "mariner", + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: grypeDB.VulnerabilityHandle{ + Name: "CVE-2023-29403", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2023-29403", + ProviderName: "mariner", + Status: "active", + Description: "CVE-2023-29403 affecting package golang for versions less than 1.20.7-1. A patched version of the package is available.", + References: []grypeDB.Reference{ + { + URL: "https://nvd.nist.gov/vuln/detail/CVE-2023-29403", + Tags: []string{grypeDB.AdvisoryReferenceTag}, + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHMLN, + Value: "high", + Rank: 1, + }, + }, + }, + }, + Provider: expectedProvider("mariner"), + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + OperatingSystem: azure3OS, + Package: &grypeDB.Package{Type: "rpm", Name: "golang"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{Type: "rpm", Constraint: "< 0:1.20.7-1.azl3"}, + Fix: &grypeDB.Fix{Version: "0:1.20.7-1.azl3", State: grypeDB.FixedStatus}, + }, + }, + }, + }, + ), + }, + }, + }, + { + name: "test-fixtures/debian-8.json", + provider: "debian", + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: grypeDB.VulnerabilityHandle{ + Name: "CVE-2008-7220", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2008-7220", + ProviderName: "debian", + Status: "active", + References: []grypeDB.Reference{ + { + URL: "https://security-tracker.debian.org/tracker/CVE-2008-7220", + Tags: []string{grypeDB.AdvisoryReferenceTag}, + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHMLN, + Value: "high", + Rank: 1, + }, + }, + }, + }, + Provider: expectedProvider("debian"), + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + OperatingSystem: debian8OS, + Package: &grypeDB.Package{Type: "deb", Name: "asterisk"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{Type: "dpkg", Constraint: "< 1:1.6.2.0~rc3-1"}, + Fix: &grypeDB.Fix{Version: "1:1.6.2.0~rc3-1", State: grypeDB.FixedStatus}, + }, + }, + }, + }, + grypeDB.AffectedPackageHandle{ + OperatingSystem: debian8OS, + Package: &grypeDB.Package{Type: "deb", Name: "auth2db"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{Type: "dpkg", Constraint: "< 0.2.5-2+dfsg-1"}, + Fix: &grypeDB.Fix{Version: "0.2.5-2+dfsg-1", State: grypeDB.FixedStatus}, + }, + }, + }, + }, + grypeDB.AffectedPackageHandle{ + OperatingSystem: debian8OS, + Package: &grypeDB.Package{Type: "deb", Name: "exaile"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{Type: "dpkg", Constraint: "< 0.2.14+debian-2.2"}, + Fix: &grypeDB.Fix{Version: "0.2.14+debian-2.2", State: grypeDB.FixedStatus}, + }, + }, + }, + }, + grypeDB.AffectedPackageHandle{ + OperatingSystem: debian8OS, + Package: &grypeDB.Package{Type: "deb", Name: "wordpress"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{Type: "dpkg", Constraint: ""}, + Fix: &grypeDB.Fix{Version: "", State: grypeDB.NotFixedStatus}, + }, + }, + }, + }, + ), + }, + }, + }, + { + name: "test-fixtures/debian-8-multiple-entries-for-same-package.json", + provider: "debian", + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: grypeDB.VulnerabilityHandle{ + Name: "CVE-2011-4623", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2011-4623", + ProviderName: "debian", + Status: "active", + PublishedDate: &timeVal, + References: []grypeDB.Reference{ + { + URL: "https://security-tracker.debian.org/tracker/CVE-2011-4623", + Tags: []string{grypeDB.AdvisoryReferenceTag}, + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHMLN, + Value: "low", + Rank: 1, + }, + }, + }, + }, + Provider: expectedProvider("debian"), + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + OperatingSystem: debian8OS, + Package: &grypeDB.Package{Type: "deb", Name: "rsyslog"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{Type: "dpkg", Constraint: "< 5.7.4-1"}, + Fix: &grypeDB.Fix{Version: "5.7.4-1", State: grypeDB.FixedStatus}, + }, + }, + }, + }, + ), + }, + { + VulnerabilityHandle: grypeDB.VulnerabilityHandle{ + Name: "CVE-2008-5618", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2008-5618", + ProviderName: "debian", + Status: "active", + References: []grypeDB.Reference{ + { + URL: "https://security-tracker.debian.org/tracker/CVE-2008-5618", + Tags: []string{grypeDB.AdvisoryReferenceTag}, + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHMLN, + Value: "low", + Rank: 1, + }, + }, + }, + }, + Provider: expectedProvider("debian"), + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + OperatingSystem: debian8OS, + Package: &grypeDB.Package{Type: "deb", Name: "rsyslog"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{Type: "dpkg", Constraint: "< 3.18.6-1"}, + Fix: &grypeDB.Fix{Version: "3.18.6-1", State: grypeDB.FixedStatus}, + }, + }, + }, + }, + ), + }, + }, + }, + { + name: "test-fixtures/mariner-20.json", + provider: "mariner", + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: grypeDB.VulnerabilityHandle{ + Name: "CVE-2021-37621", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2021-37621", + ProviderName: "mariner", + Status: "active", + Description: "CVE-2021-37621 affecting package exiv2 for versions less than 0.27.5-1. An upgraded version of the package is available that resolves this issue.", + References: []grypeDB.Reference{ + { + URL: "https://nvd.nist.gov/vuln/detail/CVE-2021-37621", + Tags: []string{grypeDB.AdvisoryReferenceTag}, + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHMLN, + Value: "medium", + Rank: 1, + }, + }, + }, + }, + Provider: expectedProvider("mariner"), + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + OperatingSystem: mariner2OS, + Package: &grypeDB.Package{Type: "rpm", Name: "exiv2"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{Type: "rpm", Constraint: "< 0:0.27.5-1.cm2"}, + Fix: &grypeDB.Fix{Version: "0:0.27.5-1.cm2", State: grypeDB.FixedStatus}, + }, + }, + }, + }, + ), + }, + }, + }, + + { + name: "test-fixtures/mariner-range.json", + provider: "mariner", + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: grypeDB.VulnerabilityHandle{ + Name: "CVE-2023-29404", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2023-29404", + ProviderName: "mariner", + Status: "active", + Description: "CVE-2023-29404 affecting package golang for versions less than 1.20.7-1. A patched version of the package is available.", + References: []grypeDB.Reference{ + { + URL: "https://nvd.nist.gov/vuln/detail/CVE-2023-29404", + Tags: []string{grypeDB.AdvisoryReferenceTag}, + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHMLN, + Value: "critical", + Rank: 1, + }, + }, + }, + }, + Provider: expectedProvider("mariner"), + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + OperatingSystem: mariner2OS, + Package: &grypeDB.Package{Type: "rpm", Name: "golang"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{Type: "rpm", Constraint: "> 0:1.19.0.cm2, < 0:1.20.7-1.cm2"}, + Fix: &grypeDB.Fix{Version: "0:1.20.7-1.cm2", State: grypeDB.FixedStatus}, + }, + }, + }, + }, + ), + }, + }, + }, + { + name: "test-fixtures/ol-8.json", + provider: "oracle", + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: grypeDB.VulnerabilityHandle{ + Name: "ELSA-2020-2550", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "ELSA-2020-2550", + ProviderName: "oracle", + Status: "active", + PublishedDate: timeRef(time.Date(2020, 6, 15, 0, 0, 0, 0, time.UTC)), + Aliases: []string{"CVE-2020-13112"}, + References: []grypeDB.Reference{ + { + URL: "http://linux.oracle.com/errata/ELSA-2020-2550.html", + Tags: []string{grypeDB.AdvisoryReferenceTag}, + }, + { + URL: "http://linux.oracle.com/cve/CVE-2020-13112.html", + Tags: []string{grypeDB.AdvisoryReferenceTag}, + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHMLN, + Value: "medium", + Rank: 1, + }, + }, + }, + }, + Provider: expectedProvider("oracle"), + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + OperatingSystem: ol8OS, + Package: &grypeDB.Package{Type: "rpm", Name: "libexif"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2020-13112"}, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{Type: "rpm", Constraint: "< 0:0.6.21-17.el8_2"}, + Fix: &grypeDB.Fix{Version: "0:0.6.21-17.el8_2", State: grypeDB.FixedStatus}, + }, + }, + }, + }, + grypeDB.AffectedPackageHandle{ + OperatingSystem: ol8OS, + Package: &grypeDB.Package{Type: "rpm", Name: "libexif-devel"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2020-13112"}, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{Type: "rpm", Constraint: "< 0:0.6.21-17.el8_2"}, + Fix: &grypeDB.Fix{Version: "0:0.6.21-17.el8_2", State: grypeDB.FixedStatus}, + }, + }, + }, + }, + grypeDB.AffectedPackageHandle{ + OperatingSystem: ol8OS, + Package: &grypeDB.Package{Type: "rpm", Name: "libexif-dummy"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2020-13112"}, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{Type: "rpm", Constraint: ""}, + Fix: &grypeDB.Fix{State: grypeDB.NotFixedStatus}, + }, + }, + }, + }, + ), + }, + }, + }, + { + name: "test-fixtures/ol-8-modules.json", + provider: "oracle", + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: grypeDB.VulnerabilityHandle{ + Name: "CVE-2020-14350", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2020-14350", + Description: "A flaw was found in PostgreSQL, where some PostgreSQL extensions did not use the search_path safely in their installation script. This flaw allows an attacker with sufficient privileges to trick an administrator into executing a specially crafted script during the extension's installation or update. The highest threat from this vulnerability is to confidentiality, integrity, as well as system availability.", + ProviderName: "oracle", + Status: "active", + References: []grypeDB.Reference{ + { + URL: "https://access.redhat.com/security/cve/CVE-2020-14350", + Tags: []string{grypeDB.AdvisoryReferenceTag}, + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHMLN, + Value: "medium", + Rank: 1, + }, + }, + }, + }, + Provider: expectedProvider("oracle"), + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + OperatingSystem: ol8OS, + Package: &grypeDB.Package{Type: "rpm", Name: "postgresql"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + Qualifiers: &grypeDB.AffectedPackageQualifiers{ + RpmModularity: "postgresql:10", + }, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Type: "rpm", + Constraint: "< 0:10.14-1.module+el8.2.0+7801+be0fed80", + }, + Fix: &grypeDB.Fix{ + Version: "0:10.14-1.module+el8.2.0+7801+be0fed80", + State: grypeDB.FixedStatus, + }, + }, + }, + }, + }, + grypeDB.AffectedPackageHandle{ + OperatingSystem: ol8OS, + Package: &grypeDB.Package{Type: "rpm", Name: "postgresql"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + Qualifiers: &grypeDB.AffectedPackageQualifiers{ + RpmModularity: "postgresql:12", + }, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Type: "rpm", + Constraint: "< 0:12.5-1.module+el8.3.0+9042+664538f4", + }, + Fix: &grypeDB.Fix{ + Version: "0:12.5-1.module+el8.3.0+9042+664538f4", + State: grypeDB.FixedStatus, + }, + }, + }, + }, + }, + grypeDB.AffectedPackageHandle{ + OperatingSystem: ol8OS, + Package: &grypeDB.Package{Type: "rpm", Name: "postgresql"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + Qualifiers: &grypeDB.AffectedPackageQualifiers{ + RpmModularity: "postgresql:9.6", + }, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Type: "rpm", + Constraint: "< 0:9.6.20-1.module+el8.3.0+8938+7f0e88b6", + }, + Fix: &grypeDB.Fix{ + Version: "0:9.6.20-1.module+el8.3.0+8938+7f0e88b6", + State: grypeDB.FixedStatus, + }, + }, + }, + }, + }, + ), + }, + }, + }, + { + name: "test-fixtures/rhel-8.json", + provider: "redhat", + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: grypeDB.VulnerabilityHandle{ + Name: "CVE-2020-6819", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2020-6819", + ProviderName: "redhat", + Status: "active", + Description: "A flaw was found in Mozilla Firefox. A race condition can occur while running the nsDocShell destructor causing a use-after-free memory issue. The highest threat from this vulnerability is to data confidentiality and integrity as well as system availability.", + References: []grypeDB.Reference{ + { + URL: "https://access.redhat.com/security/cve/CVE-2020-6819", + Tags: []string{grypeDB.AdvisoryReferenceTag}, + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHMLN, + Value: "critical", + Rank: 1, + }, + { + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", + Version: "3.1", + Score: 8.8, + }, + Rank: 2, + }, + }, + }, + }, + Provider: expectedProvider("redhat"), + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + OperatingSystem: rhel8OS, + Package: &grypeDB.Package{Type: "rpm", Name: "firefox"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Type: "rpm", + Constraint: "< 0:68.6.1-1.el8_1", + }, + Fix: &grypeDB.Fix{ + Version: "0:68.6.1-1.el8_1", + State: grypeDB.FixedStatus, + Detail: &grypeDB.FixDetail{ + References: []grypeDB.Reference{ + { + URL: "https://access.redhat.com/errata/RHSA-2020:1341", + Tags: []string{grypeDB.AdvisoryReferenceTag}, + }, + }, + }, + }, + }, + }, + }, + }, + grypeDB.AffectedPackageHandle{ + OperatingSystem: rhel8OS, + Package: &grypeDB.Package{Type: "rpm", Name: "thunderbird"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Type: "rpm", + Constraint: "< 0:68.7.0-1.el8_1", + }, + Fix: &grypeDB.Fix{ + Version: "0:68.7.0-1.el8_1", + State: grypeDB.FixedStatus, + Detail: &grypeDB.FixDetail{ + References: []grypeDB.Reference{ + { + URL: "https://access.redhat.com/errata/RHSA-2020:1495", + Tags: []string{grypeDB.AdvisoryReferenceTag}, + }, + }, + }, + }, + }, + }, + }, + }, + ), + }, + }, + }, + { + name: "test-fixtures/rhel-8-modules.json", + provider: "redhat", + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: grypeDB.VulnerabilityHandle{ + Name: "CVE-2020-14350", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2020-14350", + ProviderName: "redhat", + Status: "active", + Description: "A flaw was found in PostgreSQL, where some PostgreSQL extensions did not use the search_path safely in their installation script. This flaw allows an attacker with sufficient privileges to trick an administrator into executing a specially crafted script during the extension's installation or update. The highest threat from this vulnerability is to confidentiality, integrity, as well as system availability.", + References: []grypeDB.Reference{ + { + URL: "https://access.redhat.com/security/cve/CVE-2020-14350", + Tags: []string{grypeDB.AdvisoryReferenceTag}, + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHMLN, + Value: "medium", + Rank: 1, + }, + { + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:H", + Version: "3.1", + Score: 7.1, + }, + Rank: 2, + }, + }, + }, + }, + Provider: expectedProvider("redhat"), + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + OperatingSystem: rhel8OS, + Package: &grypeDB.Package{Type: "rpm", Name: "postgresql"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + Qualifiers: &grypeDB.AffectedPackageQualifiers{ + RpmModularity: "postgresql:10", + }, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Type: "rpm", + Constraint: "< 0:10.14-1.module+el8.2.0+7801+be0fed80", + }, + Fix: &grypeDB.Fix{ + Version: "0:10.14-1.module+el8.2.0+7801+be0fed80", + State: grypeDB.FixedStatus, + Detail: &grypeDB.FixDetail{ + References: []grypeDB.Reference{ + { + Tags: []string{grypeDB.AdvisoryReferenceTag}, + URL: "https://access.redhat.com/errata/RHSA-2020:3669", + }, + }, + }, + }, + }, + }, + }, + }, + grypeDB.AffectedPackageHandle{ + OperatingSystem: rhel8OS, + Package: &grypeDB.Package{Type: "rpm", Name: "postgresql"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + Qualifiers: &grypeDB.AffectedPackageQualifiers{ + RpmModularity: "postgresql:12", + }, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Type: "rpm", + Constraint: "< 0:12.5-1.module+el8.3.0+9042+664538f4", + }, + Fix: &grypeDB.Fix{ + Version: "0:12.5-1.module+el8.3.0+9042+664538f4", + State: grypeDB.FixedStatus, + Detail: &grypeDB.FixDetail{ + References: []grypeDB.Reference{ + { + Tags: []string{grypeDB.AdvisoryReferenceTag}, + URL: "https://access.redhat.com/errata/RHSA-2020:5620", + }, + }, + }, + }, + }, + }, + }, + }, + grypeDB.AffectedPackageHandle{ + OperatingSystem: rhel8OS, + Package: &grypeDB.Package{Type: "rpm", Name: "postgresql"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + Qualifiers: &grypeDB.AffectedPackageQualifiers{ + RpmModularity: "postgresql:9.6", + }, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Type: "rpm", + Constraint: "< 0:9.6.20-1.module+el8.3.0+8938+7f0e88b6", + }, + Fix: &grypeDB.Fix{ + Version: "0:9.6.20-1.module+el8.3.0+8938+7f0e88b6", + State: grypeDB.FixedStatus, + Detail: &grypeDB.FixDetail{ + References: []grypeDB.Reference{ + { + Tags: []string{grypeDB.AdvisoryReferenceTag}, + URL: "https://access.redhat.com/errata/RHSA-2020:5619", + }, + }, + }, + }, + }, + }, + }, + }, + ), + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + vulns := loadFixture(t, test.name) + + var actual []transformers.RelatedEntries + for _, vuln := range vulns { + entries, err := Transform(vuln, inputProviderState(test.provider)) + require.NoError(t, err) + for _, entry := range entries { + e, ok := entry.Data.(transformers.RelatedEntries) + require.True(t, ok) + actual = append(actual, e) + } + } + + if diff := cmp.Diff(test.want, actual); diff != "" { + t.Errorf("data entries mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func affectedPkgSlice(a ...grypeDB.AffectedPackageHandle) []any { + var r []any + for _, v := range a { + r = append(r, v) + } + return r +} + +func loadFixture(t *testing.T, fixturePath string) []unmarshal.OSVulnerability { + t.Helper() + + f, err := os.Open(fixturePath) + require.NoError(t, err) + defer testUtils.CloseFile(f) + + entries, err := unmarshal.OSVulnerabilityEntries(f) + require.NoError(t, err) + return entries +} + +func timeRef(ti time.Time) *time.Time { + return &ti +} diff --git a/pkg/process/v6/writer.go b/pkg/process/v6/writer.go new file mode 100644 index 00000000..7a7d5f75 --- /dev/null +++ b/pkg/process/v6/writer.go @@ -0,0 +1,109 @@ +package v6 + +import ( + "fmt" + "time" + + "github.com/anchore/grype-db/internal/log" + "github.com/anchore/grype-db/pkg/data" + "github.com/anchore/grype-db/pkg/process/v6/transformers" + "github.com/anchore/grype-db/pkg/provider" + grypeDB "github.com/anchore/grype/grype/db/v6" +) + +var _ data.Writer = (*writer)(nil) + +type writer struct { + dbPath string + store grypeDB.ReadWriter + states provider.States +} + +type ProviderMetadata struct { + Providers []Provider `json:"providers"` +} + +type Provider struct { + Name string `json:"name"` + LastSuccessfulRun time.Time `json:"lastSuccessfulRun"` +} + +func NewWriter(directory string, states provider.States) (data.Writer, error) { + cfg := grypeDB.Config{ + DBDirPath: directory, + } + s, err := grypeDB.NewWriter(cfg) + if err != nil { + return nil, fmt.Errorf("unable to create store: %w", err) + } + + if err := s.SetDBMetadata(); err != nil { + return nil, fmt.Errorf("unable to set DB ID: %w", err) + } + + return &writer{ + dbPath: cfg.DBFilePath(), + store: s, + states: states, + }, nil +} + +func (w writer) Write(entries ...data.Entry) error { + log.WithFields("records", len(entries)).Trace("writing records to DB") + for _, entry := range entries { + if entry.DBSchemaVersion != grypeDB.ModelVersion { + return fmt.Errorf("wrong schema version: want %+v got %+v", grypeDB.ModelVersion, entry.DBSchemaVersion) + } + + switch row := entry.Data.(type) { + case transformers.RelatedEntries: + if err := w.writeEntry(row); err != nil { + return fmt.Errorf("unable to write entry to store: %w", err) + } + default: + return fmt.Errorf("data entry is not of type vulnerability, vulnerability metadata, or exclusion: %T", row) + } + } + + return nil +} + +func (w writer) writeEntry(entry transformers.RelatedEntries) error { + if err := w.store.AddProvider(&entry.Provider); err != nil { + return fmt.Errorf("unable to write provider to store: %w", err) + } + + if err := w.store.AddVulnerabilities(&entry.VulnerabilityHandle); err != nil { + return fmt.Errorf("unable to write vulnerability to store: %w", err) + } + + for i := range entry.Related { + related := entry.Related[i] + switch row := related.(type) { + case grypeDB.AffectedPackageHandle: + row.VulnerabilityID = entry.VulnerabilityHandle.ID + if err := w.store.AddAffectedPackages(&row); err != nil { + return fmt.Errorf("unable to write affected-package to store: %w", err) + } + case grypeDB.AffectedCPEHandle: + row.VulnerabilityID = entry.VulnerabilityHandle.ID + if err := w.store.AddAffectedCPEs(&row); err != nil { + return fmt.Errorf("unable to write affected-cpe to store: %w", err) + } + default: + return fmt.Errorf("data entry is not of type vulnerability, vulnerability metadata, or exclusion: %T", row) + } + } + + return nil +} + +func (w writer) Close() error { + if err := w.store.Close(); err != nil { + return fmt.Errorf("unable to close store: %w", err) + } + + log.WithFields("path", w.dbPath).Info("database created") + + return nil +} diff --git a/pkg/provider/state.go b/pkg/provider/state.go index 0bc0f6b6..81c5755f 100644 --- a/pkg/provider/state.go +++ b/pkg/provider/state.go @@ -18,16 +18,19 @@ import ( // data shape dictated by vunnel "provider workspace state" schema definition type State struct { - location string - root string - Provider string `json:"provider"` - Schema Schema `json:"schema"` - URLs []string `json:"urls"` - Timestamp time.Time `json:"timestamp"` - Listing *File `json:"listing"` - Store string `json:"store"` - Stale bool `json:"stale"` - resultFileStates []File + location string + root string + Provider string `json:"provider"` + Version int `json:"version"` + DistributionVersion int `json:"distribution_version"` + Processor string `json:"processor"` + Schema Schema `json:"schema"` + URLs []string `json:"urls"` + Timestamp time.Time `json:"timestamp"` + Listing *File `json:"listing"` + Store string `json:"store"` + Stale bool `json:"stale"` + resultFileStates []File } type Schema struct { diff --git a/pkg/provider/unmarshal/github_advisory.go b/pkg/provider/unmarshal/github_advisory.go index 1b10d5c1..7967c47a 100644 --- a/pkg/provider/unmarshal/github_advisory.go +++ b/pkg/provider/unmarshal/github_advisory.go @@ -19,24 +19,18 @@ type GitHubAdvisory struct { VectorString string `json:"vector_string"` Version string `json:"version"` } `json:"CVSS"` - FixedIn []struct { - Ecosystem string `json:"ecosystem"` - Identifier string `json:"identifier"` - Name string `json:"name"` - Namespace string `json:"namespace"` - Range string `json:"range"` - } `json:"FixedIn"` + FixedIn []GithubFixedIn `json:"FixedIn"` Metadata struct { CVE []string `json:"CVE"` } `json:"Metadata"` - Severity string `json:"Severity"` - Summary string `json:"Summary"` - GhsaID string `json:"ghsaId"` - Namespace string `json:"namespace"` - URL string `json:"url"` - Published interface{} `json:"published"` - Updated interface{} `json:"updated"` - Withdrawn interface{} `json:"withdrawn"` + Severity string `json:"Severity"` + Summary string `json:"Summary"` + GhsaID string `json:"ghsaId"` + Namespace string `json:"namespace"` + URL string `json:"url"` + Published string `json:"published"` + Updated string `json:"updated"` + Withdrawn string `json:"withdrawn"` } `json:"Advisory"` } @@ -47,3 +41,11 @@ func (g GitHubAdvisory) IsEmpty() bool { func GitHubAdvisoryEntries(reader io.Reader) ([]GitHubAdvisory, error) { return unmarshalSingleOrMulti[GitHubAdvisory](reader) } + +type GithubFixedIn struct { + Ecosystem string `json:"ecosystem"` + Identifier string `json:"identifier"` + Name string `json:"name"` + Namespace string `json:"namespace"` + Range string `json:"range"` +} diff --git a/pkg/provider/unmarshal/nvd/cve.go b/pkg/provider/unmarshal/nvd/cve.go index 8c478487..35e4e88c 100644 --- a/pkg/provider/unmarshal/nvd/cve.go +++ b/pkg/provider/unmarshal/nvd/cve.go @@ -50,11 +50,11 @@ type CveItem struct { // EvaluatorComment *string `json:"evaluatorComment,omitempty"` // EvaluatorImpact *string `json:"evaluatorImpact,omitempty"` // EvaluatorSolution *string `json:"evaluatorSolution,omitempty"` - LastModified string `json:"lastModified"` - Metrics *Metrics `json:"metrics,omitempty"` - Published string `json:"published"` - References []Reference `json:"references"` - // SourceIdentifier *string `json:"sourceIdentifier,omitempty"` + LastModified string `json:"lastModified"` + Metrics *Metrics `json:"metrics,omitempty"` + Published string `json:"published"` + References []Reference `json:"references"` + SourceIdentifier *string `json:"sourceIdentifier,omitempty"` // VendorComments []VendorComment `json:"vendorComments,omitempty"` VulnStatus *string `json:"vulnStatus,omitempty"` // Weaknesses []Weakness `json:"weaknesses,omitempty"` @@ -87,7 +87,7 @@ type LangString struct { Value string `json:"value"` } -// Metric scores for a vulnerability as found on NVD. +// Metrics scores for a vulnerability as found on NVD. type Metrics struct { CvssMetricV2 []CvssV2 `json:"cvssMetricV2,omitempty"` // CVSS V2.0 score. CvssMetricV30 []CvssV30 `json:"cvssMetricV30,omitempty"` // CVSS V3.0 score. @@ -124,10 +124,10 @@ type CvssV31 struct { Type CvssType `json:"type"` } -// "type identifies whether the organization is a primary or secondary source. Primary sources -// include the NVD and CNA who have reached the provider level in CVMAP. 10% of provider level -// submissions are audited by the NVD. If a submission has been audited the NVD will appear as -// the primary source and the provider level CNA will appear as the secondary source." +// CvssType relative to the NVD docs: "type identifies whether the organization is a primary or secondary source. +// Primary sources include the NVD and CNA who have reached the provider level in CVMAP. 10% of provider level +// submissions are audited by the NVD. If a submission has been audited the NVD will appear as the primary source +// and the provider level CNA will appear as the secondary source." type CvssType string const (