diff --git a/.binny.yaml b/.binny.yaml new file mode 100644 index 00000000..a9673ec3 --- /dev/null +++ b/.binny.yaml @@ -0,0 +1,80 @@ +tools: + # we want to use a pinned version of binny to manage the toolchain (so binny manages itself!) + - name: binny + version: + want: v0.8.0 + method: github-release + with: + repo: anchore/binny + + # used for linting + - name: golangci-lint + version: + want: v1.64.2 + method: github-release + with: + repo: golangci/golangci-lint + + # used for showing the changelog at release + - name: glow + version: + want: v2.0.0 + method: github-release + with: + repo: charmbracelet/glow + + # used to release all artifacts + - name: goreleaser + version: + want: v1.26.2 + method: github-release + with: + repo: goreleaser/goreleaser + + # used for organizing imports during static analysis + - name: gosimports + version: + want: v0.3.8 + method: github-release + with: + repo: rinchsan/gosimports + + # used at release to generate the changelog + - name: chronicle + version: + want: v0.8.0 + method: github-release + with: + repo: anchore/chronicle + + # used during static analysis for license compliance + - name: bouncer + version: + want: v0.4.0 + method: github-release + with: + repo: wagoodman/go-bouncer + + # used for triggering a release + - name: gh + version: + want: v2.65.0 + method: github-release + with: + repo: cli/cli + + # used to upload test fixture cache + - name: oras + version: + want: v1.2.2 + method: github-release + with: + repo: oras-project/oras + + - name: crane + version: + want: v0.20.3 + method: go-install + with: + module: github.com/google/go-containerregistry + entrypoint: cmd/crane \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index edd71d50..3009b9c0 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,6 +1,6 @@ contact_links: - - name: Join the Slack community 💬 - # link to our community Slack registration page - url: https://anchore.com/slack + - name: Join our Discourse community 💬 + # link to our community Discourse site + url: https://anchore.com/discourse about: 'Come chat with us! Ask for help, join our software development efforts, or just give us feedback!' diff --git a/.github/actions/bootstrap/action.yaml b/.github/actions/bootstrap/action.yaml index 477b9f2b..3b1e941d 100644 --- a/.github/actions/bootstrap/action.yaml +++ b/.github/actions/bootstrap/action.yaml @@ -12,31 +12,26 @@ inputs: go-version: description: "Go version to install" required: true - default: "1.21.x" + default: "1.24.x" python-version: description: "Python version to install" required: true - default: "3.11" - poetry-version: - description: "Poetry version to install" - required: true - default: "1.5.1" + default: "3.12" use-go-cache: description: "Restore go cache" required: true default: "true" - use-poetry-cache: - description: "Restore poetry cache" - required: true - default: "true" cache-key-prefix: description: "Prefix all cache keys with this value" required: true - default: "831180ac25" + default: "311808ac26" build-cache-key-prefix: description: "Prefix build cache key with this value" required: true - default: "f8b6d31dea" + default: "6d31debf8b" + tools: + description: "whether to install tools" + default: "true" bootstrap-apt-packages: description: "Space delimited list of tools to install via apt" default: "sqlite3 libsqlite3-dev" @@ -44,38 +39,21 @@ inputs: runs: using: "composite" steps: - - uses: actions/setup-go@v3 + - uses: actions/setup-go@v5 if: inputs.go == 'true' with: go-version: ${{ inputs.go-version }} - - uses: actions/setup-python@v4 + - name: Install uv if: inputs.python == 'true' + uses: astral-sh/setup-uv@v6 with: - python-version: ${{ inputs.python-version }} + enable-cache: true - - name: Install poetry + - uses: actions/setup-python@v5 if: inputs.python == 'true' - uses: abatilo/actions-poetry@v2.2.0 - with: - poetry-version: ${{ inputs.poetry-version }} - - - name: Cache Poetry virtualenv - uses: actions/cache@v3 - if: inputs.python == 'true' && inputs.use-poetry-cache == 'true' - id: cache with: - path: ~/.virtualenvs - key: ${{ inputs.cache-key-prefix }}-python-${{ inputs.python-version }}-poetry-${{ inputs.poetry-version }}-${{ hashFiles('poetry.lock') }} - restore-keys: | - ${{ inputs.cache-key-prefix }}-python-${{ inputs.python-version }}-poetry-${{ inputs.poetry-version }} - - - name: Setup Poetry config - if: inputs.python == 'true' - shell: bash - run: | - poetry config virtualenvs.in-project false - poetry config virtualenvs.path ~/.virtualenvs + python-version: ${{ inputs.python-version }} - name: Bootstrap python dependencies if: inputs.python == 'true' @@ -83,41 +61,18 @@ runs: run: make bootstrap-python - name: Restore tool cache + if: inputs.tools == 'true' id: tool-cache - uses: actions/cache@v3 + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v3.4.0 with: - path: ${{ github.workspace }}/.tmp - key: ${{ inputs.cache-key-prefix }}-${{ runner.os }}-tool-${{ hashFiles('Makefile') }} - - # note: we need to keep restoring the go mod cache before bootstrapping tools since `go install` is used in - # some installations of project tools. - - name: Restore go module cache - id: go-mod-cache - if: inputs.go == 'true' && inputs.use-go-cache == 'true' - uses: actions/cache@v3 - with: - path: | - ~/go/pkg/mod - key: ${{ inputs.cache-key-prefix }}-${{ runner.os }}-go-module-${{ inputs.go-version }}-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ inputs.cache-key-prefix }}-${{ runner.os }}-go-module-${{ inputs.go-version }}- + path: ${{ github.workspace }}/.tool + key: ${{ inputs.cache-key-prefix }}-${{ runner.os }}-tool-${{ hashFiles('.binny.yaml') }} - name: (cache-miss) Bootstrap project tools shell: bash - if: steps.tool-cache.outputs.cache-hit != 'true' + if: steps.tool-cache.outputs.cache-hit != 'true' && inputs.tools == 'true' run: make bootstrap-tools - - name: Restore go build cache - id: go-cache - if: inputs.go == 'true' && inputs.use-go-cache == 'true' - uses: actions/cache@v3 - with: - path: | - ~/.cache/go-build - key: ${{ inputs.cache-key-prefix }}-${{ inputs.build-cache-key-prefix }}-${{ runner.os }}-go-build-${{ inputs.go-version }}-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ inputs.cache-key-prefix }}-${{ inputs.build-cache-key-prefix }}-${{ runner.os }}-go-build-${{ inputs.go-version }}- - - name: (cache-miss) Bootstrap go dependencies shell: bash if: inputs.go == 'true' && steps.go-mod-cache.outputs.cache-hit != 'true' && inputs.use-go-cache == 'true' diff --git a/.github/dependabot.yml b/.github/dependabot.yml index eb2c6595..ad061e4c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,10 +1,25 @@ version: 2 updates: - - package-ecosystem: "github-actions" + - package-ecosystem: gomod directory: "/" schedule: - interval: daily - - package-ecosystem: "gomod" + interval: "daily" + open-pull-requests-limit: 10 + labels: + - "dependencies" + + - package-ecosystem: "github-actions" directory: "/" schedule: - interval: daily + interval: "daily" + open-pull-requests-limit: 10 + labels: + - "dependencies" + + - package-ecosystem: "github-actions" + directory: "/.github/actions/bootstrap" + schedule: + interval: "daily" + open-pull-requests-limit: 10 + labels: + - "dependencies" \ No newline at end of file diff --git a/.github/scripts/goreleaser-install.sh b/.github/scripts/goreleaser-install.sh deleted file mode 100755 index 0a5497d2..00000000 --- a/.github/scripts/goreleaser-install.sh +++ /dev/null @@ -1,398 +0,0 @@ -#!/bin/sh -set -e -# Code generated by godownloader on 2019-12-25T12:47:14Z. DO NOT EDIT. -# - -usage() { - this=$1 - cat </dev/null -} -echoerr() { - echo "$@" 1>&2 -} -log_prefix() { - echo "$0" -} -_logp=6 -log_set_priority() { - _logp="$1" -} -log_priority() { - if test -z "$1"; then - echo "$_logp" - return - fi - [ "$1" -le "$_logp" ] -} -log_tag() { - case $1 in - 0) echo "emerg" ;; - 1) echo "alert" ;; - 2) echo "crit" ;; - 3) echo "err" ;; - 4) echo "warning" ;; - 5) echo "notice" ;; - 6) echo "info" ;; - 7) echo "debug" ;; - *) echo "$1" ;; - esac -} -log_debug() { - log_priority 7 || return 0 - echoerr "$(log_prefix)" "$(log_tag 7)" "$@" -} -log_info() { - log_priority 6 || return 0 - echoerr "$(log_prefix)" "$(log_tag 6)" "$@" -} -log_err() { - log_priority 3 || return 0 - echoerr "$(log_prefix)" "$(log_tag 3)" "$@" -} -log_crit() { - log_priority 2 || return 0 - echoerr "$(log_prefix)" "$(log_tag 2)" "$@" -} -uname_os() { - os=$(uname -s | tr '[:upper:]' '[:lower:]') - case "$os" in - cygwin_nt*) os="windows" ;; - mingw*) os="windows" ;; - msys_nt*) os="windows" ;; - esac - echo "$os" -} -uname_arch() { - arch=$(uname -m) - case $arch in - x86_64) arch="amd64" ;; - x86) arch="386" ;; - i686) arch="386" ;; - i386) arch="386" ;; - aarch64) arch="arm64" ;; - armv5*) arch="armv5" ;; - armv6*) arch="armv6" ;; - armv7*) arch="armv7" ;; - esac - echo ${arch} -} -uname_os_check() { - os=$(uname_os) - case "$os" in - darwin) return 0 ;; - dragonfly) return 0 ;; - freebsd) return 0 ;; - linux) return 0 ;; - android) return 0 ;; - nacl) return 0 ;; - netbsd) return 0 ;; - openbsd) return 0 ;; - plan9) return 0 ;; - solaris) return 0 ;; - windows) return 0 ;; - esac - log_crit "uname_os_check '$(uname -s)' got converted to '$os' which is not a GOOS value. Please file bug at https://github.com/client9/shlib" - return 1 -} -uname_arch_check() { - arch=$(uname_arch) - case "$arch" in - 386) return 0 ;; - amd64) return 0 ;; - arm64) return 0 ;; - armv5) return 0 ;; - armv6) return 0 ;; - armv7) return 0 ;; - ppc64) return 0 ;; - ppc64le) return 0 ;; - mips) return 0 ;; - mipsle) return 0 ;; - mips64) return 0 ;; - mips64le) return 0 ;; - s390x) return 0 ;; - amd64p32) return 0 ;; - esac - log_crit "uname_arch_check '$(uname -m)' got converted to '$arch' which is not a GOARCH value. Please file bug report at https://github.com/client9/shlib" - return 1 -} -untar() { - tarball=$1 - case "${tarball}" in - *.tar.gz | *.tgz) tar --no-same-owner -xzf "${tarball}" ;; - *.tar) tar --no-same-owner -xf "${tarball}" ;; - *.zip) unzip "${tarball}" ;; - *) - log_err "untar unknown archive format for ${tarball}" - return 1 - ;; - esac -} -http_download_curl() { - local_file=$1 - source_url=$2 - header=$3 - if [ -z "$header" ]; then - code=$(curl -w '%{http_code}' -sL -o "$local_file" "$source_url") - else - code=$(curl -w '%{http_code}' -sL -H "$header" -o "$local_file" "$source_url") - fi - if [ "$code" != "200" ]; then - log_debug "http_download_curl received HTTP status $code" - return 1 - fi - return 0 -} -http_download_wget() { - local_file=$1 - source_url=$2 - header=$3 - if [ -z "$header" ]; then - wget -q -O "$local_file" "$source_url" - else - wget -q --header "$header" -O "$local_file" "$source_url" - fi -} -http_download() { - log_debug "http_download $2" - if is_command curl; then - http_download_curl "$@" - return - elif is_command wget; then - http_download_wget "$@" - return - fi - log_crit "http_download unable to find wget or curl" - return 1 -} -http_copy() { - tmp=$(mktemp) - http_download "${tmp}" "$1" "$2" || return 1 - body=$(cat "$tmp") - rm -f "${tmp}" - echo "$body" -} -github_release() { - owner_repo=$1 - version=$2 - test -z "$version" && version="latest" - giturl="https://github.com/${owner_repo}/releases/${version}" - json=$(http_copy "$giturl" "Accept:application/json") - test -z "$json" && return 1 - version=$(echo "$json" | tr -s '\n' ' ' | sed 's/.*"tag_name":"//' | sed 's/".*//') - test -z "$version" && return 1 - echo "$version" -} -hash_sha256() { - TARGET=${1:-/dev/stdin} - if is_command gsha256sum; then - hash=$(gsha256sum "$TARGET") || return 1 - echo "$hash" | cut -d ' ' -f 1 - elif is_command sha256sum; then - hash=$(sha256sum "$TARGET") || return 1 - echo "$hash" | cut -d ' ' -f 1 - elif is_command shasum; then - hash=$(shasum -a 256 "$TARGET" 2>/dev/null) || return 1 - echo "$hash" | cut -d ' ' -f 1 - elif is_command openssl; then - hash=$(openssl -dst openssl dgst -sha256 "$TARGET") || return 1 - echo "$hash" | cut -d ' ' -f a - else - log_crit "hash_sha256 unable to find command to compute sha-256 hash" - return 1 - fi -} -hash_sha256_verify() { - TARGET=$1 - checksums=$2 - if [ -z "$checksums" ]; then - log_err "hash_sha256_verify checksum file not specified in arg2" - return 1 - fi - BASENAME=${TARGET##*/} - want=$(grep "${BASENAME}$" "${checksums}" 2>/dev/null | tr '\t' ' ' | cut -d ' ' -f 1) - if [ -z "$want" ]; then - log_err "hash_sha256_verify unable to find checksum for '${TARGET}' in '${checksums}'" - return 1 - fi - got=$(hash_sha256 "$TARGET") - if [ "$want" != "$got" ]; then - log_err "hash_sha256_verify checksum for '$TARGET' did not verify ${want} vs $got" - return 1 - fi -} -cat /dev/null <> $GITHUB_OUTPUT quality-gate-acceptance-test: needs: read-schema-versions - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 strategy: matrix: schema-version: ${{fromJson(needs.read-schema-versions.outputs.schema-versions)}} steps: - name: Check acceptance test results - uses: fountainhead/action-wait-for-check@297be350cf8393728ea4d4b39435c7d7ae167c93 #v1.1.0 + uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be #v1.2.0 id: acceptance with: token: ${{ secrets.GITHUB_TOKEN }} @@ -122,7 +122,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 with: fetch-depth: 0 diff --git a/.github/workflows/remove-awaiting-response-label.yaml b/.github/workflows/remove-awaiting-response-label.yaml new file mode 100644 index 00000000..0f3b50c4 --- /dev/null +++ b/.github/workflows/remove-awaiting-response-label.yaml @@ -0,0 +1,11 @@ +name: "Manage Awaiting Response Label" + +on: + issue_comment: + types: [created] + +jobs: + run: + uses: "anchore/workflows/.github/workflows/remove-awaiting-response-label.yaml@main" + secrets: + token: ${{ secrets.OSS_PROJECT_GH_TOKEN }} diff --git a/.github/workflows/staging-db-publisher.yaml b/.github/workflows/staging-db-publisher.yaml index 5278f45f..5e54740b 100644 --- a/.github/workflows/staging-db-publisher.yaml +++ b/.github/workflows/staging-db-publisher.yaml @@ -53,7 +53,7 @@ jobs: contents: read steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 with: submodules: true @@ -61,15 +61,17 @@ jobs: uses: ./.github/actions/bootstrap - name: Login to ghcr.io - run: | - echo ${{ secrets.GITHUB_TOKEN }} | oras login ghcr.io --username ${{ github.actor }} --password-stdin + run: make ci-oras-ghcr-login + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_USERNAME: ${{ github.actor }} - name: Setup tmate session # note about workflow dispatch inputs and booleans: # a) booleans come across as string types :( # b) if not using workflow_dispatch the default values are empty, which means we want these to effectively evaluate to true (so only check the negative case) if: github.event.inputs.run-tmate != 'false' - uses: mxschmitt/action-tmate@a283f9441d2d96eb62436dc46d7014f5d357ac22 #v3.17 + uses: mxschmitt/action-tmate@7b6a61a73bbb9793cb80ad69b8dd8ac19261834c #v3.22 timeout-minutes: ${{ fromJSON(github.event.inputs.tmate-duration) }} with: limit-access-to-actor: true @@ -87,12 +89,12 @@ jobs: # b) if not using workflow_dispatch the default values are empty, which means we want these to effectively evaluate to true (so only check the negative case) if: github.event.inputs.publish-databases != 'false' run: | - poetry run \ + uv run \ grype-db-manager \ + -vv \ -c ./config/grype-db-manager/publish-staging.yaml \ db build-and-upload \ - --schema-version ${{ github.event.inputs.schema-version }} \ - -vvv + --schema-version ${{ github.event.inputs.schema-version }} env: AWS_ACCESS_KEY_ID: ${{ secrets.TOOLBOX_AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.TOOLBOX_AWS_SECRET_ACCESS_KEY }} @@ -103,7 +105,7 @@ jobs: # b) if not using workflow_dispatch the default values are empty, which means we want these to effectively evaluate to true (so only check the negative case) if: github.event.inputs.publish-listing != 'false' run: | - poetry run \ + uv run \ grype-db-manager \ -c ./config/grype-db-manager/publish-staging.yaml \ listing update diff --git a/.github/workflows/update-anchore-dependencies.yml b/.github/workflows/update-anchore-dependencies.yml new file mode 100644 index 00000000..bf0a3d19 --- /dev/null +++ b/.github/workflows/update-anchore-dependencies.yml @@ -0,0 +1,49 @@ +name: PR to update Anchore dependencies +on: + workflow_dispatch: + inputs: + repos: + description: "List of dependencies to update" + required: true + type: string + +permissions: + contents: read + +jobs: + update: + runs-on: ubuntu-latest + if: github.repository_owner == 'anchore' # only run for main repo (not forks) + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 + + - name: Bootstrap environment + uses: ./.github/actions/bootstrap + with: + tools: false + bootstrap-apt-packages: "" + + - name: Update dependencies + id: update + uses: anchore/workflows/.github/actions/update-go-dependencies@main + with: + repos: ${{ github.event.inputs.repos }} + + - 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@271a8d0340265f705b14b6d32b9829c1cb33d45e #v7.0.8 + with: + signoff: true + delete-branch: true + draft: ${{ steps.update.outputs.draft }} + # do not change this branch, as other workflows depend on it + branch: auto/integration + labels: dependencies,pre-release + commit-message: "chore(deps): update anchore dependencies" + title: "chore(deps): update anchore dependencies" + body: ${{ steps.update.outputs.summary }} + token: ${{ steps.generate-token.outputs.token }} diff --git a/.github/workflows/update-bootstrap-tools.yml b/.github/workflows/update-bootstrap-tools.yml index 1e7a5c67..a556a13f 100644 --- a/.github/workflows/update-bootstrap-tools.yml +++ b/.github/workflows/update-bootstrap-tools.yml @@ -6,7 +6,7 @@ on: workflow_dispatch: env: - GO_VERSION: "1.21.x" + GO_VERSION: "1.24.x" GO_STABLE_VERSION: true jobs: @@ -14,9 +14,9 @@ jobs: runs-on: ubuntu-latest if: github.repository == 'anchore/grype-db' # only run for main repo steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 - - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 #v5.0.0 + - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 #v5.5.0 with: go-version: ${{ env.GO_VERSION }} stable: ${{ env.GO_STABLE_VERSION }} @@ -49,7 +49,7 @@ jobs: app_id: ${{ secrets.TOKEN_APP_ID }} private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }} - - uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 #v5.0.2 + - uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e #v7.0.8 with: signoff: true delete-branch: true diff --git a/.github/workflows/update-generated-code.yaml b/.github/workflows/update-generated-code.yaml new file mode 100644 index 00000000..5376c24e --- /dev/null +++ b/.github/workflows/update-generated-code.yaml @@ -0,0 +1,53 @@ +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: + run-code-gen: + name: "Run code generation" + runs-on: ubuntu-latest + if: github.repository == 'anchore/grype-db' # 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@271a8d0340265f705b14b6d32b9829c1cb33d45e #v7.0.8 + with: + signoff: true + delete-branch: true + branch: auto/latest-code-gen + 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@1750b5085f3ec60384090fb7c52965ef822e869e #v3.18.0 + 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/.github/workflows/update-grype-release.yml b/.github/workflows/update-grype-release.yml deleted file mode 100644 index eaab883b..00000000 --- a/.github/workflows/update-grype-release.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: PR for latest Grype release -on: - schedule: - - cron: "0 8 * * *" # 3 AM EST - - workflow_dispatch: - -env: - GO_VERSION: "1.21.x" - GO_STABLE_VERSION: true - -jobs: - upgrade-grype: - runs-on: ubuntu-latest - if: github.repository == 'anchore/grype-db' # only run for main repo - steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 - - - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 #v5.0.0 - with: - go-version: ${{ env.GO_VERSION }} - stable: ${{ env.GO_STABLE_VERSION }} - - - run: | - LATEST_VERSION=$(curl "https://api.github.com/repos/anchore/grype/releases/latest" 2>/dev/null | jq -r '.tag_name') - - # update go.mod - go get github.com/anchore/grype@$LATEST_VERSION - go mod tidy - - # export the version for use with create-pull-request - echo "LATEST_VERSION=$LATEST_VERSION" >> $GITHUB_OUTPUT - id: latest-version - - - 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@153407881ec5c347639a548ade7d8ad1d6740e38 #v5.0.2 - with: - signoff: true - delete-branch: true - branch: auto/latest - labels: dependencies - commit-message: "Update Grype to ${{ steps.latest-version.outputs.LATEST_VERSION }}" - title: "Update Grype to ${{ steps.latest-version.outputs.LATEST_VERSION }}" - body: | - Update Grype to ${{ steps.latest-version.outputs.LATEST_VERSION }} - token: ${{ steps.generate-token.outputs.token }} diff --git a/.github/workflows/validations.yaml b/.github/workflows/validations.yaml index 1d66dbc7..bb5f1fc4 100644 --- a/.github/workflows/validations.yaml +++ b/.github/workflows/validations.yaml @@ -16,10 +16,10 @@ jobs: # Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline Static-Analysis: name: "Static analysis" - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 - name: Bootstrap environment uses: ./.github/actions/bootstrap @@ -30,10 +30,10 @@ jobs: # Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline Unit-Test-Go: name: "Unit tests (Go)" - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 - name: Bootstrap environment uses: ./.github/actions/bootstrap @@ -46,10 +46,10 @@ jobs: # Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline Unit-Test-Python: name: "Unit tests (Python)" - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 - name: Bootstrap environment uses: ./.github/actions/bootstrap @@ -62,9 +62,9 @@ jobs: # Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline Build-Snapshot-Artifacts: name: "Build snapshot artifacts" - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 - name: Bootstrap environment uses: ./.github/actions/bootstrap @@ -84,24 +84,24 @@ jobs: # why not use actions/upload-artifact? It is very slow (3 minutes to upload ~600MB of data, vs 10 seconds with this approach). # see https://github.com/actions/upload-artifact/issues/199 for more info - name: Upload snapshot artifacts - uses: actions/cache/save@13aacd865c20de90d75de3b17ebe84f7a17d57d2 #v4.0.0 + uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 #v4.2.3 with: path: snapshot key: snapshot-build-${{ github.run_id }} Discover-Schema-Versions: name: "Discover supported schema versions" - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 outputs: schema-versions: ${{ steps.read-schema-versions.outputs.schema-versions }} steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 - name: Read supported schema versions id: read-schema-versions run: | - content=`cat manager/src/grype_db_manager/data/schema-info.json | jq -c '[.available[] | select(.supported == true) | .schema]'` + content=`cat manager/src/grype_db_manager/data/schema-info.json | jq -c '[.available[] | select(.supported == true) | select(.validate != false) | .schema]'` echo "schema-versions=$content" >> $GITHUB_OUTPUT # Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline @@ -118,7 +118,7 @@ jobs: packages: read steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 with: submodules: true @@ -126,8 +126,10 @@ jobs: uses: ./.github/actions/bootstrap - name: Login to ghcr.io - run: | - echo ${{ secrets.GITHUB_TOKEN }} | oras login ghcr.io --username ${{ github.actor }} --password-stdin + run: make ci-oras-ghcr-login + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_USERNAME: ${{ github.actor }} - name: Pull vulnerability data run: make download-all-provider-cache @@ -142,9 +144,9 @@ jobs: # Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline name: "CLI tests (Go-Linux)" needs: [Build-Snapshot-Artifacts] - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 - name: Bootstrap environment uses: ./.github/actions/bootstrap @@ -152,13 +154,13 @@ jobs: python: false - name: Restore CLI test-fixture cache - uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 #v4.0.0 + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 #v4.2.3 with: path: ${{ github.workspace }}/test/cli/test-fixtures/cache key: ${{ runner.os }}-cli-test-cache-${{ hashFiles('test/cli/test-fixtures/cache.fingerprint') }} - name: Download snapshot build - uses: actions/cache/restore@13aacd865c20de90d75de3b17ebe84f7a17d57d2 #v4.0.0 + uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 #v4.2.3 with: path: snapshot key: snapshot-build-${{ github.run_id }} @@ -171,7 +173,7 @@ jobs: name: "CLI tests (Python)" runs-on: ubuntu-22.04-4core-16gb steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 with: submodules: true diff --git a/.gitignore b/.gitignore index c2acc7cb..4b32b000 100644 --- a/.gitignore +++ b/.gitignore @@ -6,11 +6,20 @@ !/data/yardstick/labels !/data/vulnerability-match-labels +# go work +/go.work +/go.work.sum + +# tools +.mise.toml +.tool + # default data directories /vunnel /bin .yardstick .grype-db-manager +/tmp /grype-db /build @@ -24,6 +33,7 @@ listing.json !**/test-fixtures/**/*.db *.tar *tar.gz +*tar.zst .idea/ *.log .images diff --git a/.gitmodules b/.gitmodules index 09d96bee..c6be59c0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "data/vulnerability-match-labels"] path = data/vulnerability-match-labels - url = git@github.com:anchore/vulnerability-match-labels.git + url = https://github.com/anchore/vulnerability-match-labels.git branch = main diff --git a/.golangci.yaml b/.golangci.yaml index 0a82be56..8721da80 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -1,3 +1,6 @@ +issues: + uniq-by-line: false + linters-settings: funlen: lines: 90 @@ -5,8 +8,6 @@ linters-settings: gocognit: min-complexity: 32 -output: - uniq-by-line: false run: timeout: 10m tests: false @@ -20,7 +21,7 @@ linters: - dogsled - dupl - errcheck - - exportloopref + - copyloopvar - funlen - gocognit - goconst diff --git a/.vunnel.yaml b/.vunnel.yaml index bcd64771..b8d236ed 100644 --- a/.vunnel.yaml +++ b/.vunnel.yaml @@ -8,3 +8,16 @@ providers: ubuntu: # there is a lot of IO when running git log commands in this provider, so some concurrency helps here max_workers: 10 + + nvd: + # apply community-provided overrides to the NVD data + # sourced from the https://github.com/anchore/nvd-data-overrides repo + overrides_enabled: true + + # we're getting a lot of 503s and intermittent failures from the NVD API, so we're going to retry a few times + request_timeout: 125 + request_retry_count: 15 + runtime: + on_error: + retry_count: 10 + retry_delay: 60 \ No newline at end of file diff --git a/.yardstick.yaml b/.yardstick.yaml index dcbbcc71..05e2deab 100644 --- a/.yardstick.yaml +++ b/.yardstick.yaml @@ -1 +1 @@ -store-root: data/yardstick \ No newline at end of file +store-root: data/yardstick diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..5d19a3c2 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +[opensource@anchore.com](mailto:opensource@anchore.com). +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/DEVELOPING.md b/DEVELOPING.md index a6b4a414..7b30abd7 100644 --- a/DEVELOPING.md +++ b/DEVELOPING.md @@ -7,7 +7,8 @@ well as acceptance testing. You will require the following: - Python 3.8+ installed on your system. Consider using [pyenv](https://github.com/pyenv/pyenv) if you do not have a preference for managing python interpreter installations. - +- `zstd` binary utility if you are packaging v6+ DB schemas +- _(optional)_ `xz` binary utility if you have specifically overridden the package command options - [Poetry](https://python-poetry.org/) installed for dependency and virtualenv management for python dependencies, to install: diff --git a/Makefile b/Makefile index 406da121..18a01ae1 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,9 @@ BIN = grype-db +OWNER = anchore SOURCE_REPO_URL = https://github.com/anchore/grype-db TEMP_DIR = ./.tmp +TOOL_DIR = .tool RESULTS_DIR = $(TEMP_DIR)/results DB_ARCHIVE = ./grype-db-cache.tar.gz @@ -10,21 +12,26 @@ GRYPE_DB_DATA_IMAGE_NAME = ghcr.io/anchore/$(BIN)/data date = $(shell date -u +"%y-%m-%d") # Command templates ################################# -LINT_CMD = $(TEMP_DIR)/golangci-lint run --config .golangci.yaml -GOIMPORTS_CMD := $(TEMP_DIR)/gosimports -local github.com/anchore -RELEASE_CMD := $(TEMP_DIR)/goreleaser release --rm-dist -SNAPSHOT_CMD := $(RELEASE_CMD) --skip-publish --skip-sign --snapshot -CHRONICLE_CMD = $(TEMP_DIR)/chronicle -GLOW_CMD = $(TEMP_DIR)/glow +BINNY = $(TOOL_DIR)/binny +LINT_CMD = $(TOOL_DIR)/golangci-lint run --config .golangci.yaml +GOIMPORTS_CMD := $(TOOL_DIR)/gosimports -local github.com/anchore +RELEASE_CMD = $(TOOL_DIR)/goreleaser release --rm-dist +SNAPSHOT_CMD = $(RELEASE_CMD) --skip-publish --snapshot +CHRONICLE_CMD = $(TOOL_DIR)/chronicle +GLOW_CMD = $(TOOL_DIR)/glow +ORAS = $(TOOL_DIR)/oras +BOUNCER = $(TOOL_DIR)/bouncer +CRANE = $(TOOL_DIR)/crane # Tool versions ################################# -GOLANGCILINT_VERSION = v1.54.2 -GOSIMPORTS_VERSION := v0.3.7 +GOLANGCILINT_VERSION = v1.64.8 +GOSIMPORTS_VERSION := v0.3.8 BOUNCER_VERSION = v0.4.0 -CHRONICLE_VERSION = v0.6.0 -GORELEASER_VERSION = v1.13.0 +CHRONICLE_VERSION = v0.8.0 +GORELEASER_VERSION = v1.26.2 CRANE_VERSION=v0.16.1 GLOW_VERSION := v1.5.0 +ORAS_VERSION := v1.2.2 # Formatting variables ################################# BOLD := $(shell tput -T linux bold) @@ -39,8 +46,6 @@ SUCCESS := $(BOLD)$(GREEN) # Test variables ################################# # the quality gate lower threshold for unit test total % coverage (by function statements) COVERAGE_THRESHOLD := 55 -RELEASE_CMD=$(TEMP_DIR)/goreleaser release --rm-dist -SNAPSHOT_CMD=$(RELEASE_CMD) --skip-publish --snapshot DIST_DIR=./dist CHANGELOG := CHANGELOG.md SNAPSHOT_DIR=./snapshot @@ -90,11 +95,11 @@ all: static-analysis test ## Run all checks (linting, license checks, unit, and .PHONY: static-analysis ## Run all static analysis checks (linting and license checks) static-analysis: check-go-mod-tidy lint check-licenses - cd manager && poetry run make static-analysis + cd manager && uv run make static-analysis .PHONY: test test: unit cli ## Run all tests - cd manager && poetry run make test + cd manager && uv run make test ## Bootstrapping targets ################################# @@ -106,15 +111,14 @@ bootstrap: $(TEMP_DIR) bootstrap-go bootstrap-tools bootstrap-python ## Downloa bootstrap-python: cd manager && make bootstrap +# note: we need to assume that binny and task have not already been installed +$(BINNY): + @mkdir -p $(TOOL_DIR) + @curl -sSfL https://raw.githubusercontent.com/$(OWNER)/binny/main/install.sh | sh -s -- -b $(TOOL_DIR) + .PHONY: bootstrap-tools -bootstrap-tools: $(TEMP_DIR) - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(TEMP_DIR)/ $(GOLANGCILINT_VERSION) - curl -sSfL https://raw.githubusercontent.com/wagoodman/go-bouncer/master/bouncer.sh | sh -s -- -b $(TEMP_DIR)/ $(BOUNCER_VERSION) - curl -sSfL https://raw.githubusercontent.com/anchore/chronicle/main/install.sh | sh -s -- -b $(TEMP_DIR)/ $(CHRONICLE_VERSION) - .github/scripts/goreleaser-install.sh -b $(TEMP_DIR)/ $(GORELEASER_VERSION) - GOBIN="$(abspath $(TEMP_DIR))" go install github.com/google/go-containerregistry/cmd/crane@$(CRANE_VERSION) - GOBIN="$(realpath $(TEMP_DIR))" go install github.com/charmbracelet/glow@$(GLOW_VERSION) - GOBIN="$(realpath $(TEMP_DIR))" go install github.com/rinchsan/gosimports/cmd/gosimports@$(GOSIMPORTS_VERSION) +bootstrap-tools: $(BINNY) + $(BINNY) install .PHONY: bootstrap-go bootstrap-go: @@ -154,33 +158,33 @@ check-go-mod-tidy: .PHONY: check-licenses check-licenses: - $(TEMP_DIR)/bouncer check ./cmd/$(BIN) + $(BOUNCER) check ./cmd/$(BIN) ## Testing targets ################################# .PHONY: unit -unit: ## Run Go unit tests (with coverage) +unit: $(TEMP_DIR) ## Run Go unit tests (with coverage) $(call title,Running Go unit tests) - go test -coverprofile $(TEMP_DIR)/unit-coverage-details.txt $(shell go list ./... | grep -v anchore/grype-db/test) + GOEXPERIMENT=nocoverageredesign go test -coverprofile $(TEMP_DIR)/unit-coverage-details.txt $(shell go list ./... | grep -v anchore/grype-db/test) @.github/scripts/coverage.py $(COVERAGE_THRESHOLD) $(TEMP_DIR)/unit-coverage-details.txt .PHONY: unit-python unit-python: ## Run Python unit tests (with coverage) $(call title,Running Python unit tests) - cd manager && poetry run make unit + cd manager && make unit .PHONY: db-acceptance db-acceptance: ## Run acceptance tests $(call title,"Running DB acceptance tests (schema=$(schema))") - poetry run ./test/db/acceptance.sh $(schema) + uv run ./test/db/acceptance.sh $(schema) .PHONY: cli cli: cli-go cli-python ## Run all CLI tests .PHONY: cli-python cli-python: ## Run python CLI tests - cd manager && poetry run make cli + cd manager && uv run make cli .PHONY: cli-go cli-go: $(SNAPSHOT_DIR) ## Run go CLI tests @@ -190,22 +194,6 @@ cli-go: $(SNAPSHOT_DIR) ## Run go CLI tests go test -count=1 -timeout=15m -v ./test/cli -## Test-fixture-related targets ################################# - -.PHONY: update-test-fixtures -update-test-fixtures: - docker run \ - --pull always \ - --rm \ - -it \ - anchore/grype:latest \ - -q \ - -o json \ - centos:8.2.2004 > publish/test-fixtures/centos-8.2.2004.json - dos2unix publish/test-fixtures/centos-8.2.2004.json - cd test/acceptance && poetry install && poetry run python grype-ingest.py capture-test-fixtures - - ## Data management targets ################################# .PHONY: show-providers @@ -213,10 +201,16 @@ show-providers: @# this is used in CI to generate a job matrix, pulling data for each provider concurrently @$(GRYPE_DB) list-providers -q -o json +.PHONY: ci-oras-ghcr-login +ci-oras-ghcr-login: + @[ -n "$(GITHUB_USERNAME)" ] || (echo "Error: GITHUB_USERNAME environment variable is not set" && exit 1) + @[ -n "$(GITHUB_TOKEN)" ] || (echo "Error: GITHUB_TOKEN environment variable is not set" && exit 1) + echo $(GITHUB_TOKEN) | $(ORAS) login ghcr.io --username $(GITHUB_USERNAME) --password-stdin + .PHONY: download-provider-cache download-provider-cache: $(call title,Downloading and restoring todays "$(provider)" provider data cache) - @bash -c "oras pull $(GRYPE_DB_DATA_IMAGE_NAME)/$(provider):$(date) && $(GRYPE_DB) cache restore --path $(DB_ARCHIVE) || (echo 'no data cache found for today' && exit 1)" + @bash -c "$(ORAS) pull $(GRYPE_DB_DATA_IMAGE_NAME)/$(provider):$(date) && $(GRYPE_DB) cache restore --path $(DB_ARCHIVE) || (echo 'no data cache found for today' && exit 1)" .PHONY: refresh-provider-cache refresh-provider-cache: @@ -230,8 +224,8 @@ upload-provider-cache: ci-check @rm -f $(DB_ARCHIVE) $(GRYPE_DB) cache status -p $(provider) $(GRYPE_DB) cache backup -v --path $(DB_ARCHIVE) -p $(provider) - oras push -v $(GRYPE_DB_DATA_IMAGE_NAME)/$(provider):$(date) $(DB_ARCHIVE) --annotation org.opencontainers.image.source=$(SOURCE_REPO_URL) - $(TEMP_DIR)/crane tag $(GRYPE_DB_DATA_IMAGE_NAME)/$(provider):$(date) latest + $(ORAS) push -v $(GRYPE_DB_DATA_IMAGE_NAME)/$(provider):$(date) $(DB_ARCHIVE) --annotation org.opencontainers.image.source=$(SOURCE_REPO_URL) + $(CRANE) tag $(GRYPE_DB_DATA_IMAGE_NAME)/$(provider):$(date) latest .PHONY: aggregate-all-provider-cache aggregate-all-provider-cache: @@ -245,23 +239,30 @@ upload-all-provider-cache: ci-check @rm -f $(DB_ARCHIVE) $(GRYPE_DB) cache status $(GRYPE_DB) cache backup -v --path $(DB_ARCHIVE) - oras push -v $(GRYPE_DB_DATA_IMAGE_NAME):$(date) $(DB_ARCHIVE) --annotation org.opencontainers.image.source=$(SOURCE_REPO_URL) - $(TEMP_DIR)/crane tag $(GRYPE_DB_DATA_IMAGE_NAME):$(date) latest + $(ORAS) push -v $(GRYPE_DB_DATA_IMAGE_NAME):$(date) $(DB_ARCHIVE) --annotation org.opencontainers.image.source=$(SOURCE_REPO_URL) + $(CRANE) tag $(GRYPE_DB_DATA_IMAGE_NAME):$(date) latest .PHONY: download-all-provider-cache download-all-provider-cache: $(call title,Downloading and restoring all of todays provider data cache) @rm -f $(DB_ARCHIVE) - @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)" + @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 build: $(SNAPSHOT_DIR) ## Build release snapshot binaries and packages -$(SNAPSHOT_DIR): ## Build snapshot release binaries and packages +$(SNAPSHOT_DIR): $(TEMP_DIR) ## Build snapshot release binaries and packages $(call title,Building snapshot artifacts) # create a config with the dist dir overridden @@ -284,7 +285,7 @@ release: @.github/scripts/trigger-release.sh .PHONY: release -ci-release: ci-check clean-dist $(CHANGELOG) ## Build and publish final binaries and packages. Intended to be run only on macOS. +ci-release: ci-check $(TEMP_DIR) clean-dist $(CHANGELOG) ## Build and publish final binaries and packages. Intended to be run only on macOS. $(call title,Publishing release artifacts) # create a config with the dist dir overridden diff --git a/README.md b/README.md index e78e8f09..2db69d9c 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,17 @@ # grype-db -Application to create a grype vulnerability database from upstream vulnerability data sources. +**Application to create a [Grype](https://github.com/anchore/grype) vulnerability database from upstream vulnerability data sources.** + +[![GitHub release](https://img.shields.io/github/release/anchore/grype-db.svg)](https://github.com/anchore/grype-db/releases/latest) +[![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/anchore/grype-db/blob/main/LICENSE) +[![Join our Discourse](https://img.shields.io/badge/Discourse-Join-blue?logo=discourse)](https://anchore.com/discourse) ## Installation **Note**: Currently, Grype-DB is built only for Linux and macOS. ### Recommended + ```bash curl -sSfL https://raw.githubusercontent.com/anchore/grype-db/main/install.sh | sh -s -- -b /usr/local/bin ``` @@ -17,58 +22,178 @@ curl -sSfL https://raw.githubusercontent.com/anchore/grype-db/main/install.sh | curl -sSfL https://raw.githubusercontent.com/anchore/grype-db/main/install.sh | sh -s -- -b ``` +> [!IMPORTANT] +> You will require the `zstd` utility installed on your system to support the `package` command. ## Usage -To pull the vulnerability source data, build the `vulnerability.db` file, and package the database into a `tar.gz` run the following: +`grype-db` takes the following options: ```bash grype-db [-g] [--dir=DIR] [--schema=SCHEMA] [--skip-validation] [--publish-base-url=URL] [-p PROVIDER ...] ``` -Or you can choose to run these steps individually: -```bash -# Pull all upstream vulnerability data sources to local cache -grype-db pull [-g] [-p PROVIDER ...] +### Pulling Data and Building the Database + +To pull data from a specific provider or providers and build the database in one step, run `grype-db` with the `-g` flag and specify providers with the `-p` flag: + +```sh +grype-db -g -p nvd +``` + +This example will build the database for the `nvd` provider. Multiple providers can be specified. + +If no providers are specified, `grype-db` defaults to all available providers as generated by `vunnel list`. However, note that that accessing GitHub provider data requires a token to authenticate to the GitHub API. See [Getting Started Step-by-Step](https://github.com/anchore/grype-db#getting-started-step-by-step) for more information on adding a GitHub token.) + +To download all provider data and build the `vulnerability.db` database, run: + +```sh +grype-db -g +``` + +By default, `grype-db` will download provider data and build the database. To run these steps individually, use the `build` and `pull` subcommands. + +### Pulling Provider Data + +To pull provider data without building the database: + +```sh +grype-db pull -g -p nvd +``` + +Omit the `-p` flag to target all available providers. + +Note that you can skip the `pull` step if you already have a local cache of vulnerability data, such as with `make download-all-provider-cache`. + +### Building the Database + +Once provider data has been downloaded, run the following to build the database: -# Build a SQLite DB from the vulnerability data for a particular schema version +```sh +grype-db build -g -p nvd +``` + +The build command can take the following options: + +```sh grype-db build [-g] [--dir=DIR] [--schema=SCHEMA] [--skip-validation] [-p PROVIDER ...] +``` + +### Package the Database + +You can package the `vulnerability.db`, for example to serve the data or for use in CI. Note that you will need the zstd utility to be installed on your system to usethe `package` subcommand. + +To package a built database, run: + +```sh +grype-db package +``` + +This will package any database present in the `build` folder. -# Package the already built DB file into an archive ready for upload and serving +The `package` subcommand takes the following options: + +``` grype-db package [--dir=DIR] [--publish-base-url=URL] ``` +The `package` command archives the `vulnerability.db` file into a `tar.zstd` file. Additionally, a `latest.json` +is generated to aid in serving one or more database archives for downstream consumption, where the consuming application should +use the listing file to discover archives available for download. The base URL used to create the download URL for each +database archive is controlled by the `package.base-url` configuration option. + +### Notes on the Cache + The `pull` command downloads and caches vulnerability data from upstream sources (e.g. NIST, redhat, github, canonical, etc.) into a cache directory. The cache location is a platform dependent XDG directory, however, the location can be overridden with the `cache.dir` -configuration option. The default configuration is to use [vunnel](https://github.com/anchore/vunnel) to fetch and -process the vulnerability data. Use `-g` to generate the list of providers to pull based on the output of "vunnel list". +configuration option. -**note: you can skip the `pull` step if you already have a local cache of vulnerability data (with `make download-all-provider-cache`).** +The `build` command processes the cached vuln data generate a `vulnerability.db` sqlite3 file. Additionally, a `metadata.json` file +is created that is used in packaging and curation of the database file .In addition, a `provider-metadata.json` file is created that includes the last successful run date for each provider. -The `build` command processes the cached vuln data generate a `vulnerability.db` sqlite3 file. Additionally, a `metadata.json` -is created that is used in packaging and curation of the database file by this application and downstream consuming applications. -Use `-g` to generate the list of providers to pull based on the output of "vunnel list". +### Cache Commands -The `package` command archives the `vulnerability.db` and `metadata.json` files into a `tar.gz` file. Additionally, a `listing.json` -is generated to aid in serving one or more database archives for downstream consumption, where the consuming application should -use the listing file to discover available archives available for download. The base URL used to create the download URL for each -database archive is controlled by the `package.base-url` configuration option. +To show the current state of the entire vulnerability data cache or a specific `PROVIDER`: -You can additionally manage vulnerability data cache with the following commands: -```bash -# backup all cached vulnerability data or a specific PROVIDER to a tar.gz file (PATH) +```sh +grype-db cache status [--provider-name=PROVIDER ...] +``` + +To back up all cached vulnerability data or a specific PROVIDER to a tar.gz file (`PATH`): + +```sh grype-db cache backup [--path=PATH] [--provider-name=PROVIDER] +``` -# delete all cached vulnerability data or a specific PROVIDER +To delete all cached vulnerability data or a specific `PROVIDER`: + +```sh grype-db cache delete [--provider-name=PROVIDER] +``` + +To restore vulnerability cache from a tar.gz file (`PATH`) -# restore vulnerability cache from a tar.gz file (PATH) +```sh grype-db cache restore [--path=PATH] [--delete-existing] +``` -# show the current state of the all vulnerability data cache or a specific PROVIDER -grype-db cache status [--provider-name=PROVIDER ...] +## Getting Started Step-by-Step + +If you're running `grype-db` for the first time, you can set up using the following steps. + +First, [create a GitHub access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) to authenticate to the GitHub API. (Tokens can be generated on [this GitHub settings page](https://github.com/settings/tokens).) No specific permissions are needed. Using restrictive permissions settings and creating a short-lived token is highly recommended. + +Set your token as an environment variable: + +```sh +GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +``` + +Create and set a working directory: + +```sh +mkdir -p ~/vulnerability-data && cd $_ +``` + +Create a Python virtual environment and activate it: + +```sh +python -m venv venv && source venv/bin/activate ``` +Install [vunnel](https://github.com/anchore/vunnel): + +```sh +pip install vunnel +``` + +Now install `grype-db` as a binary in the working directory. (Note that this will not place `grype-db` on your path.) + +```sh +curl -sSfL https://raw.githubusercontent.com/anchore/grype-db/main/install.sh | sh -s -- -b . +``` + +Create a configuration file for `grype-db` using the following here document. The command will automatically add the GitHub token you set earlier to the config, so no manual replacement is needed. + +```sh +cat << EOF > ~/vulnerability-data/.grype-db.yaml +provider: + vunnel: + executor: local + generate-configs: true + env: + GITHUB_TOKEN: $GITHUB_TOKEN +EOF +``` + +Build the database from all providers. (Note that this command can take significant time to complete.) + +```sh +./grype-db -g +``` + +Once this command completes, you should have `data` and `build` folders with downloaded data and a built `vulnerability.db` file, among other contents. + ## DB Schemas This repo supports building databases for all supported versions of grype, even when the data shape has changed. @@ -79,12 +204,14 @@ For every change in the data shape over time, a new schema is created (see the D Once a schema has been created, the previous schema should be considered locked unless making bug fixes or updates related to [vunnel](https://github.com/anchore/vunnel), or otherwise upstream data shape changes. If the development being done requires any of the following, then a **new schema is required to be created** (over further developing the current schema): + - If a previous version of grype using the same schema would not function with the new changes - If the current version of grype using a previously published database (but still the same schema) would not function with the new changes Where "would not function" means either grype will error out during processing, or the results are otherwise compromised (e.g. missing data that otherwise could/should have been found and reported). The following kinds of changes **do not necessarily require a new schema**: + - Adding a new data source - Removing an existing data source (as long as the grype matchers are not requiring its presence) @@ -92,8 +219,8 @@ There are plenty of grey areas between these cases (e.g. changing the expected s This repo is responsible for publishing DBs with the latest vulnerability data for every supported schema daily. This is achieved with the [Daily Data Sync](https://github.com/anchore/grype-db/actions/workflows/daily-data-sync.yaml) and [Daily DB Publisher](https://github.com/anchore/grype-db/actions/workflows/daily-db-publisher.yaml) GitHub Actions workflows. -Which schemas are built and which grype versions are used to verify functionality is controlled with the `grype-schema-version-mapping.json` file in the root of this repo -(see the DEVELOPING.md for more details). +Which schemas are built and which grype versions are used to verify functionality is controlled with the `grype-schema-version-mapping.json` file in the root of this repo +(see the DEVELOPING.md for more details). ## Configuration @@ -170,4 +297,4 @@ package: # limit the providers to pull based off of this list. (empty list means pull all providers) provider-names: [] -``` \ No newline at end of file +``` diff --git a/RELEASE.md b/RELEASE.md index a29fdbe5..628d0672 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,5 +1,14 @@ # Release +A release of grype-db comprises: +- a new semver git tag from the current tip of the main branch +- a new [github release](https://github.com/anchore/grype-db/releases) with a changelog and archived binary assets + +Ideally releasing should be done often with small increments when possible. Unless a +breaking change is blocking the release, or no fixes/features have been merged, a good +target release cadence is between every 1 or 2 weeks. + + ## Creating a release This release process itself should be as automated as possible, and has only a few steps: @@ -9,15 +18,9 @@ This release process itself should be as automated as possible, and has only a f you can abort and adjust the labels on the PRs and issues to be included in the release and re-run the release trigger command. -1. A release admin must approve the release on the GitHub Actions release pipeline run page. +1. A release admin must approve the release on the GitHub Actions [release pipeline](https://github.com/anchore/grype-db/actions/workflows/release.yaml) run page. Once approved, the release pipeline will generate all assets and publish a GitHub Release. -1. If there is a release Milestone, close it. - -Ideally releasing should be done often with small increments when possible. Unless a -breaking change is blocking the release, or no fixes/features have been merged, a good -target release cadence is between every 1 or 2 weeks. - ## Retracting a release @@ -29,82 +32,3 @@ If a release is found to be problematic, it can be retracted with the following **Note**: do not delete release tags from the git repository since there may already be references to the release in the go proxy, which will cause confusion when trying to reuse the tag later (the H1 hash will not match and there will be a warning when users try to pull the new release). - - -## Background - -A good release process has the following qualities: - -1. There is a way to plan what should be in a release -1. There is a way to see what is actually in a release -1. Allow for different kinds of releases (major breaking vs backwards compatible enhancements vs patch updates) -1. Specify a repeatable way to build and publish software artifacts - - -### Planning a release - -To indicate a set of features to be released together add each issue to an in-repository -Milestone named with major-minor version to be released (e.g. `v0.1`). It is OK for other -features to be in the release that were not originally planned, and these issues and PRs -do not need to be added to the Milestone in question. Only the set of features that, when -completed, would allow the release to be considered complete. A Milestone is only used to: - -- Plan what is desired to be in a release -- Track progress to indicate when we may be ready to cut a new release - -Not all releases need to be planned. For instance, patch releases for fixes should be -released when they are ready and when releasing would not interfere with another current -release (where some partial or breaking features have already been merged). - -Unless necessary, feature releases should be small and frequent, which may obviate the -need for regular release planning under a Milestone. - - -### What is in a release - -Milestones are specifically for planning a release, not necessarily tracking all changes -that a release may bring (and more importantly, not all releases are necessarily planned -either). - -This is one of the (many) reasons for a Changelog. A good Changelog lists changes grouped -by the type of change (new, enhancement, deprecation, breaking, bug fix, security fix), in -chronological order (within groups), linking the PR where the change was made in the -Changelog line. Furthermore, there should be a place to see all released versions, the -release date for each release, the semantic version of the release, and the set of changes -for each release. - -**This project auto-generates the Changelog contents for each current release and posts the -generated contents to the GitHub Release page**. Leveraging the GitHub Releases feature -allows GitHub to manage the Changelog on each release outside of the git source tree while -still being hosted with the released assets. - -The Changelog is generated from the metadata from in-repository issues and PRs, using -labels to guide what kind of change each item is (e.g. breaking, new feature, bug fix, -etx). Only issues/PRs with select labels are included in the Changelog, and only if the -issue/PR was created after the last release. Additional labels are used to exclude items -from the Changelog. - -The above suggestions imply that we should: - -- Ensure there is a sufficient title for each PR and issue title to be included in the - Changelog -- The appropriate label is applied to PRs and/or issues to drive specific change type - sections (deprecated, breaking, security, bug, etc) - -**With this approach as we cultivate good organization of PRs and issues we automatically -get an equally good Changelog.** - - -### Major, minor, and patch releases - -The latest version of the tool is the only supported version, which implies that multiple -parallel release branches will not be a regular process (if ever). Multiple releases can -be planned in parallel, however, only one can be actively developed at a time. That is, if -PRs attached to a release Milestone have been merged into the main branch, that release is -now the "next" release. **This implies that the source of truth for release lies with the -git log and Changelog, not with the release Milestones** (which are purely for planning and -tracking). - -Semantic versioning should be used to indicate breaking changes, new features, and fixes. -The exception to this is `< 1.0`, where the major version is not bumped for breaking changes, -instead the minor version indicates both new features and breaking changes. diff --git a/cmd/grype-db/application/application.go b/cmd/grype-db/application/application.go index 05092a17..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 @@ -39,7 +40,7 @@ func New() *Application { func (a *Application) Setup(opts options.Interface) func(cmd *cobra.Command, args []string) error { v := newViper() - return func(cmd *cobra.Command, args []string) error { + return func(cmd *cobra.Command, _ []string) error { // bind options to viper if opts != nil { if err := opts.BindFlags(cmd.Flags(), v); err != nil { @@ -169,6 +170,7 @@ func setupLogger(app *Config) error { } log.Set(l) + grype.SetLogger(l) return nil } diff --git a/cmd/grype-db/application/build_info.go b/cmd/grype-db/application/build_info.go index af500493..f27bc14b 100644 --- a/cmd/grype-db/application/build_info.go +++ b/cmd/grype-db/application/build_info.go @@ -5,7 +5,7 @@ import ( "runtime" "runtime/debug" - grypeDB "github.com/anchore/grype/grype/db/v3" + grypeDB "github.com/anchore/grype/grype/db/v6" ) const valueNotProvided = "[not provided]" @@ -70,6 +70,6 @@ func ReadBuildInfo() BuildInfo { GoVersion: runtime.Version(), Compiler: runtime.Compiler, Platform: platform, - DBSchema: grypeDB.SchemaVersion, + DBSchema: grypeDB.ModelVersion, } } diff --git a/cmd/grype-db/cli/commands/build.go b/cmd/grype-db/cli/commands/build.go index c112d4a1..31d05cab 100644 --- a/cmd/grype-db/cli/commands/build.go +++ b/cmd/grype-db/cli/commands/build.go @@ -4,8 +4,8 @@ import ( "errors" "fmt" "os" - "time" + "github.com/scylladb/go-set/strset" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/spf13/viper" @@ -45,7 +45,10 @@ func Build(app *application.Application) *cobra.Command { Short: "build a SQLite DB from the vulnerability feeds data for a particular schema version", Args: cobra.NoArgs, PreRunE: app.Setup(&cfg), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { + if err := validateCPEParts(cfg.Build.IncludeCPEParts); err != nil { + return err + } return app.Run(cmd.Context(), async(func() error { return runBuild(cfg) })) @@ -57,6 +60,19 @@ func Build(app *application.Application) *cobra.Command { return cmd } +func validateCPEParts(parts []string) error { + validParts := strset.New("a", "o", "h") + for _, part := range parts { + if !validParts.Has(part) { + return fmt.Errorf("invalid CPE part: %s", part) + } + } + if len(parts) == 0 { + return errors.New("no CPE parts provided") + } + return nil +} + func runBuild(cfg buildConfig) error { // make the db dir if it does not already exist if _, err := os.Stat(cfg.Build.Directory); os.IsNotExist(err) { @@ -86,38 +102,53 @@ func runBuild(cfg buildConfig) error { pvdrs = pvdrs.Filter(cfg.Provider.IncludeFilter...) } + states, err := providerStates(cfg.SkipValidation, pvdrs) + if err != nil { + return fmt.Errorf("unable to get provider states: %w", err) + } + + earliest, err := provider.States(states).EarliestTimestamp() + if err != nil { + return fmt.Errorf("unable to get earliest timestamp: %w", err) + } + + return process.Build(process.BuildConfig{ + SchemaVersion: cfg.SchemaVersion, + Directory: cfg.Directory, + States: states, + Timestamp: earliest, + IncludeCPEParts: cfg.IncludeCPEParts, + InferNVDFixVersions: cfg.InferNVDFixVersions, + Hydrate: cfg.Hydrate, + }) +} + +func providerStates(skipValidation bool, providers []provider.Provider) ([]provider.State, error) { var states []provider.State - stateTimestamp := time.Now() log.Debug("reading all provider state") - for _, p := range pvdrs { + + if len(providers) == 0 { + return nil, fmt.Errorf("no providers configured") + } + + for _, p := range providers { log.WithFields("provider", p.ID().Name).Debug("reading state") sd, err := p.State() if err != nil { - return fmt.Errorf("unable to read provider state: %w", err) + return nil, fmt.Errorf("unable to read provider state: %w", err) } - if !cfg.SkipValidation { + if !skipValidation { log.WithFields("provider", p.ID().Name).Trace("validating state") if err := sd.Verify(); err != nil { - return fmt.Errorf("invalid provider state: %w", err) + return nil, fmt.Errorf("invalid provider state: %w", err) } } - - if sd.Timestamp.Before(stateTimestamp) { - stateTimestamp = sd.Timestamp - } states = append(states, *sd) } - - if !cfg.SkipValidation { + if !skipValidation { log.Debugf("state validated for all providers") } - - return process.Build(process.BuildConfig{ - SchemaVersion: cfg.SchemaVersion, - Directory: cfg.Directory, - States: states, - Timestamp: stateTimestamp, - }) + return states, nil } diff --git a/cmd/grype-db/cli/commands/cache_backup.go b/cmd/grype-db/cli/commands/cache_backup.go index c2fe1f0d..4b29aab2 100644 --- a/cmd/grype-db/cli/commands/cache_backup.go +++ b/cmd/grype-db/cli/commands/cache_backup.go @@ -1,12 +1,12 @@ package commands import ( - "archive/tar" - "compress/gzip" + "encoding/json" "fmt" - "io" + "io/fs" "os" "path/filepath" + "strings" "github.com/scylladb/go-set/strset" "github.com/spf13/cobra" @@ -16,6 +16,7 @@ import ( "github.com/anchore/grype-db/cmd/grype-db/application" "github.com/anchore/grype-db/cmd/grype-db/cli/options" "github.com/anchore/grype-db/internal/log" + "github.com/anchore/grype-db/internal/tarutil" "github.com/anchore/grype-db/pkg/provider" ) @@ -27,14 +28,18 @@ type cacheBackupConfig struct { options.Store `yaml:",inline" mapstructure:",squash"` options.Selection `yaml:",inline" mapstructure:",squash"` } `yaml:"provider" json:"provider" mapstructure:"provider"` + options.Results `yaml:"results" json:"results" mapstructure:"results"` } func (o *cacheBackupConfig) AddFlags(flags *pflag.FlagSet) { - options.AddAllFlags(flags, &o.CacheArchive, &o.Provider.Store, &o.Provider.Selection) + options.AddAllFlags(flags, &o.CacheArchive, &o.Provider.Store, &o.Provider.Selection, &o.Results) } func (o *cacheBackupConfig) BindFlags(flags *pflag.FlagSet, v *viper.Viper) error { - return options.BindAllFlags(flags, v, &o.CacheArchive, &o.Provider.Store, &o.Provider.Selection) + if err := options.Bind(v, "results.results-only", flags.Lookup("results-only")); err != nil { + return err + } + return options.BindAllFlags(flags, v, &o.CacheArchive, &o.Provider.Store, &o.Provider.Selection, &o.Results) } func CacheBackup(app *application.Application) *cobra.Command { @@ -43,13 +48,14 @@ func CacheBackup(app *application.Application) *cobra.Command { } cfg.Provider.Store = options.DefaultStore() cfg.Provider.Selection = options.DefaultSelection() + cfg.Results = options.DefaultResults() cmd := &cobra.Command{ Use: "backup", Short: "backup provider cache to an archive", Args: cobra.NoArgs, PreRunE: app.Setup(&cfg), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { return app.Run(cmd.Context(), async(func() error { return cacheBackup(cfg) })) @@ -68,23 +74,11 @@ func cacheBackup(cfg cacheBackupConfig) error { } log.WithFields("providers", providers).Info("backing up provider state") - archive, err := os.Create(cfg.CacheArchive.Path) + writer, err := tarutil.NewWriter(cfg.CacheArchive.Path) if err != nil { - return err + return fmt.Errorf("unable to create archive writer: %w", err) } - - gw := gzip.NewWriter(archive) - defer func(gw *gzip.Writer) { - if err := gw.Close(); err != nil { - log.Errorf("unable to close gzip writer: %w", err) - } - }(gw) - tw := tar.NewWriter(gw) - defer func(tw *tar.Writer) { - if err := tw.Close(); err != nil { - log.Errorf("unable to close tar writer: %w", err) - } - }(tw) + defer writer.Close() allowableProviders := strset.New(cfg.Provider.IncludeFilter...) @@ -111,7 +105,7 @@ func cacheBackup(cfg cacheBackupConfig) error { } log.WithFields("provider", name).Debug("archiving data") - if err := archiveProvider(cfg.Provider.Root, name, tw); err != nil { + if err := archiveProvider(cfg, name, writer); err != nil { return err } } @@ -121,70 +115,119 @@ func cacheBackup(cfg cacheBackupConfig) error { return nil } -func archiveProvider(root string, name string, writer *tar.Writer) error { +func archiveProvider(cfg cacheBackupConfig, name string, writer tarutil.Writer) error { wd, err := os.Getwd() if err != nil { return err } - err = os.Chdir(root) + + err = os.Chdir(cfg.Provider.Root) if err != nil { return err } + defer func(dir string) { if err := os.Chdir(dir); err != nil { log.Errorf("unable to restore directory: %w", err) } }(wd) - return filepath.Walk(name, - func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } + var visitor pathVisitor + if cfg.Results.ResultsOnly { + log.WithFields("provider", name).Debug("archiving results only") - if info.IsDir() { - return nil - } + visitor = newCacheResultsOnlyWorkspaceVisitStrategy(writer, name) + } else { + log.WithFields("provider", name).Debug("archiving full workspace") - return addToArchive(writer, path) - }, - ) + visitor = cacheFullWorkspaceVisitStrategy{ + writer: writer, + } + } + + return filepath.Walk(name, visitor.visitPath) } -func addToArchive(writer *tar.Writer, filename string) error { - log.WithFields("path", filename).Trace("adding to archive") +type pathVisitor interface { + visitPath(path string, info fs.FileInfo, err error) error +} - file, err := os.Open(filename) - if err != nil { - return err - } - defer file.Close() +var ( + _ pathVisitor = (*cacheFullWorkspaceVisitStrategy)(nil) + _ pathVisitor = (*cacheResultsOnlyWorkspaceVisitStrategy)(nil) +) + +type cacheFullWorkspaceVisitStrategy struct { + writer tarutil.Writer +} - info, err := file.Stat() +func (t cacheFullWorkspaceVisitStrategy) visitPath(p string, info fs.FileInfo, err error) error { if err != nil { return err } - header, err := tar.FileInfoHeader(info, info.Name()) - if err != nil { - return err + if info.IsDir() { + return nil } - // use full path as name (FileInfoHeader only takes the basename) - // If we don't do this the directory structure would - // not be preserved - // https://golang.org/src/archive/tar/common.go?#L626 - header.Name = filename + return t.writer.WriteEntry(tarutil.NewEntryFromFilePath(p)) +} - err = writer.WriteHeader(header) - if err != nil { - return err +type cacheResultsOnlyWorkspaceVisitStrategy struct { + writer tarutil.Writer + providerName string + metadataPath string + inputPath string +} + +func newCacheResultsOnlyWorkspaceVisitStrategy(writer tarutil.Writer, providerName string) cacheResultsOnlyWorkspaceVisitStrategy { + return cacheResultsOnlyWorkspaceVisitStrategy{ + writer: writer, + providerName: providerName, + metadataPath: filepath.Join(providerName, "metadata.json"), + inputPath: filepath.Join(providerName, "input"), } +} - _, err = io.Copy(writer, file) +func (t cacheResultsOnlyWorkspaceVisitStrategy) visitPath(p string, info fs.FileInfo, err error) error { if err != nil { return err } - return nil + if info.IsDir() { + return nil + } + + switch { + case strings.HasPrefix(p, t.inputPath): + // skip input data + return nil + + case p == t.metadataPath: + // mark metadata stale + + var state provider.State + f, err := os.Open(p) + if err != nil { + return err + } + defer f.Close() + + err = json.NewDecoder(f).Decode(&state) + if err != nil { + return err + } + + state.Stale = true + + // stream this to the archive + stateJSON, err := json.MarshalIndent(state, "", " ") + if err != nil { + return err + } + + return t.writer.WriteEntry(tarutil.NewEntryFromBytes(stateJSON, p, info)) + } + + return t.writer.WriteEntry(tarutil.NewEntryFromFilePath(p)) } diff --git a/cmd/grype-db/cli/commands/cache_backup_test.go b/cmd/grype-db/cli/commands/cache_backup_test.go new file mode 100644 index 00000000..f458e172 --- /dev/null +++ b/cmd/grype-db/cli/commands/cache_backup_test.go @@ -0,0 +1,229 @@ +package commands + +import ( + "archive/tar" + "encoding/json" + "errors" + "io" + "io/fs" + "os" + "path" + "testing" + "time" + + "github.com/scylladb/go-set/strset" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/anchore/grype-db/cmd/grype-db/cli/options" + "github.com/anchore/grype-db/internal/tarutil" + "github.com/anchore/grype-db/pkg/provider" +) + +func Test_archiveProvider(t *testing.T) { + type args struct { + cfg cacheBackupConfig + root string + name string + } + tests := []struct { + name string + args args + wantNames *strset.Set + wantStateStale bool + wantErr require.ErrorAssertionFunc + }{ + { + name: "default config includes input", + args: args{ + cfg: cacheBackupConfig{ + Results: options.Results{ + ResultsOnly: false, + }, + }, + root: "test-fixtures/test-root", + name: "test-provider", + }, + wantStateStale: false, + wantNames: strset.New([]string{ + "test-provider/input/some-input-file.txt", + "test-provider/metadata.json", + "test-provider/results/results.db", + }...), + }, + { + name: "results only excludes input", + args: args{ + cfg: cacheBackupConfig{ + Results: options.Results{ + ResultsOnly: true, + }, + }, + root: "test-fixtures/test-root", + name: "test-provider", + }, + wantNames: strset.New( + "test-provider/metadata.json", + "test-provider/results/results.db", + ), + wantStateStale: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantErr == nil { + tt.wantErr = require.NoError + } + dir := t.TempDir() + archivePath := path.Join(dir, "archive.tar") + tw, err := tarutil.NewWriter(archivePath) + require.NoError(t, err) + + tt.args.cfg.Provider.Store.Root = tt.args.root + err = archiveProvider(tt.args.cfg, tt.args.name, tw) + if tt.wantErr != nil { + tt.wantErr(t, err) + return + } + tt.wantErr(t, err) + require.NoError(t, tw.Close()) + + f, err := os.Open(archivePath) + require.NoError(t, err) + var state provider.State + foundNames := strset.New() + tr := tar.NewReader(f) + for { + next, nextErr := tr.Next() + if errors.Is(nextErr, io.EOF) { + break + } + require.NoError(t, nextErr) + if next.Name == path.Join(tt.args.name, "metadata.json") { + err = json.NewDecoder(tr).Decode(&state) + require.NoError(t, err) + } + foundNames.Add(next.Name) + } + assert.Equalf(t, tt.wantStateStale, state.Stale, "state had wrong staleness") + setDiff := strset.SymmetricDifference(tt.wantNames, foundNames) + assert.True(t, setDiff.IsEmpty()) + }) + } +} + +var _ tarutil.Writer = (*mockWriter)(nil) + +type mockWriter struct { + writtenEntries []tarutil.Entry + closeCalled bool + closeErr error + writeErr error +} + +func (m *mockWriter) WriteEntry(entry tarutil.Entry) error { + m.writtenEntries = append(m.writtenEntries, entry) + return m.writeErr +} + +func (m *mockWriter) Close() error { + m.closeCalled = true + return m.closeErr +} + +type mockFileInfo struct { + name string + size int64 + mode fs.FileMode + modTime time.Time + isDir bool + sys any +} + +func (m mockFileInfo) Name() string { + return m.name +} + +func (m mockFileInfo) Size() int64 { + return m.size +} + +func (m mockFileInfo) Mode() fs.FileMode { + return m.mode +} + +func (m mockFileInfo) ModTime() time.Time { + return m.modTime +} + +func (m mockFileInfo) IsDir() bool { + return m.isDir +} + +func (m mockFileInfo) Sys() any { + return m.sys +} + +func Test_common_visitPath_cases(t *testing.T) { + + type visitorConstructor func(w tarutil.Writer) pathVisitor + + fullConstructor := func(w tarutil.Writer) pathVisitor { return cacheFullWorkspaceVisitStrategy{writer: w} } + resultsOnlyConstructor := func(w tarutil.Writer) pathVisitor { + return cacheResultsOnlyWorkspaceVisitStrategy{ + writer: w, + providerName: "test-provider", + metadataPath: "test-provider/metadata.json", + inputPath: "test-provider/input", + } + } + + constructors := map[string]visitorConstructor{ + "full": fullConstructor, + "results-only": resultsOnlyConstructor, + } + + tests := []struct { + name string + filePath string + fileInfo fs.FileInfo + fileErr error + writer *mockWriter + wantErr require.ErrorAssertionFunc + wantEntryCount int + }{ + { + name: "errors write no entries", + filePath: "some-path", + fileInfo: nil, + fileErr: errors.New("some-error"), + writer: &mockWriter{}, + wantEntryCount: 0, + wantErr: require.Error, + }, + { + name: "directories are skipped", + filePath: "some-path", + fileInfo: mockFileInfo{isDir: true}, + writer: &mockWriter{}, + wantEntryCount: 0, + wantErr: require.NoError, + }, + } + for _, tt := range tests { + for name, constructor := range constructors { + t.Run(name, func(t *testing.T) { + t.Run(tt.name, func(t *testing.T) { + if tt.wantErr == nil { + tt.wantErr = require.NoError + } + s := constructor(tt.writer) + + err := s.visitPath(tt.filePath, tt.fileInfo, tt.fileErr) + tt.wantErr(t, err) + assert.Equal(t, tt.wantEntryCount, len(tt.writer.writtenEntries)) + }) + }) + } + } +} diff --git a/cmd/grype-db/cli/commands/cache_delete.go b/cmd/grype-db/cli/commands/cache_delete.go index 737bf0b5..f529ae3d 100644 --- a/cmd/grype-db/cli/commands/cache_delete.go +++ b/cmd/grype-db/cli/commands/cache_delete.go @@ -42,7 +42,7 @@ func CacheDelete(app *application.Application) *cobra.Command { Short: "delete all provider cache", Args: cobra.NoArgs, PreRunE: app.Setup(&cfg), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { return app.Run(cmd.Context(), async(func() error { return cacheDelete(cfg) })) diff --git a/cmd/grype-db/cli/commands/cache_restore.go b/cmd/grype-db/cli/commands/cache_restore.go index bd320109..b69456fd 100644 --- a/cmd/grype-db/cli/commands/cache_restore.go +++ b/cmd/grype-db/cli/commands/cache_restore.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/scylladb/go-set/strset" + "github.com/spf13/afero" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/spf13/viper" @@ -62,7 +63,7 @@ func CacheRestore(app *application.Application) *cobra.Command { Short: "restore provider cache from a backup archive", Args: cobra.NoArgs, PreRunE: app.Setup(&cfg), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { return app.Run(cmd.Context(), async(func() error { return cacheRestore(cfg) })) @@ -200,6 +201,18 @@ func extractTarGz(reader io.Reader, selectedProviders *strset.Set) error { tr := tar.NewReader(gr) + rootPath, err := os.Getwd() + if err != nil { + return fmt.Errorf("failed to get current working directory: %w", err) + } + + rootPath, err = filepath.Abs(rootPath) + if err != nil { + return fmt.Errorf("failed to get absolute path: %w", err) + } + + var restoredAny bool + fs := afero.NewOsFs() for { header, err := tr.Next() @@ -217,36 +230,131 @@ func extractTarGz(reader io.Reader, selectedProviders *strset.Set) error { continue } - log.WithFields("path", header.Name).Trace("extracting file") + restoredAny = true - switch header.Typeflag { - case tar.TypeDir: - if err := os.Mkdir(header.Name, 0755); err != nil { - return fmt.Errorf("failed to create directory: %w", err) - } - case tar.TypeReg: - parentPath := filepath.Dir(header.Name) - if parentPath != "" { - if err := os.MkdirAll(parentPath, 0755); err != nil { - return fmt.Errorf("failed to create parent directory %q for file %q: %w", parentPath, header.Name, err) - } - } + if err := processTarHeader(fs, rootPath, header, tr); err != nil { + return err + } + } - outFile, err := os.Create(header.Name) - if err != nil { - return fmt.Errorf("failed to create file: %w", err) - } - if err := safeCopy(outFile, tr); err != nil { - return fmt.Errorf("failed to copy file: %w", err) - } - if err := outFile.Close(); err != nil { - return fmt.Errorf("failed to close file: %w", err) - } + if !restoredAny { + return fmt.Errorf("no provider data was restored") + } + return nil +} + +func processTarHeader(fs afero.Fs, rootPath string, header *tar.Header, reader io.Reader) error { + // clean the path to avoid traversal (removes "..", ".", etc.) + cleanedPath := cleanPathRelativeToRoot(rootPath, header.Name) + + if err := detectPathTraversal(rootPath, cleanedPath); err != nil { + return err + } + + log.WithFields("path", cleanedPath).Trace("extracting file") + + switch header.Typeflag { + case tar.TypeDir: + if err := fs.Mkdir(cleanedPath, 0755); err != nil { + return fmt.Errorf("failed to create directory: %w", err) + } + case tar.TypeSymlink: + if err := handleSymlink(fs, rootPath, cleanedPath, header.Linkname); err != nil { + return fmt.Errorf("failed to create symlink: %w", err) + } + case tar.TypeReg: + if err := handleFile(fs, cleanedPath, reader); err != nil { + return fmt.Errorf("failed to handle file: %w", err) + } + default: + log.WithFields("name", cleanedPath, "type", header.Typeflag).Warn("unknown file type in backup archive") + } + return nil +} + +func handleFile(fs afero.Fs, cleanedPath string, reader io.Reader) error { + if cleanedPath == "" { + return fmt.Errorf("empty path") + } + + parentPath := filepath.Dir(cleanedPath) + if parentPath != "" { + if err := fs.MkdirAll(parentPath, 0755); err != nil { + return fmt.Errorf("failed to create parent directory %q for file %q: %w", parentPath, cleanedPath, err) + } + } - default: - log.WithFields("name", header.Name, "type", header.Typeflag).Warn("unknown file type in backup archive") + outFile, err := fs.Create(cleanedPath) + if err != nil { + return fmt.Errorf("failed to create file: %w", err) + } + if err := safeCopy(outFile, reader); err != nil { + return fmt.Errorf("failed to copy file: %w", err) + } + if err := outFile.Close(); err != nil { + return fmt.Errorf("failed to close file: %w", err) + } + return nil +} + +func handleSymlink(fs afero.Fs, rootPath, cleanedPath, linkName string) error { + if err := detectLinkTraversal(rootPath, cleanedPath, linkName); err != nil { + return err + } + + linkReader, ok := fs.(afero.LinkReader) + if !ok { + return afero.ErrNoReadlink + } + + // check if the symlink already exists and is pointing to the correct target + if linkTarget, err := linkReader.ReadlinkIfPossible(cleanedPath); err == nil { + if linkTarget == linkName { + return nil + } + + if err := fs.Remove(cleanedPath); err != nil { + return fmt.Errorf("failed to remove existing symlink: %w", err) } } + + linker, ok := fs.(afero.Linker) + if !ok { + return afero.ErrNoSymlink + } + + if err := linker.SymlinkIfPossible(linkName, cleanedPath); err != nil { + return fmt.Errorf("failed to create symlink: %w", err) + } + return nil +} + +func cleanPathRelativeToRoot(rootPath, path string) string { + return filepath.Join(rootPath, filepath.Clean(path)) +} + +func detectLinkTraversal(rootPath, cleanedPath, linkTarget string) error { + linkTarget = filepath.Clean(linkTarget) + if filepath.IsAbs(linkTarget) { + return detectPathTraversal(rootPath, linkTarget) + } + + linkTarget = filepath.Join(filepath.Dir(cleanedPath), linkTarget) + + if !strings.HasPrefix(linkTarget, rootPath) { + return fmt.Errorf("symlink points outside root: %s -> %s", cleanedPath, linkTarget) + } + return nil +} + +func detectPathTraversal(rootPath, cleanedPath string) error { + if cleanedPath == "" { + return nil + } + + if !strings.HasPrefix(cleanedPath, rootPath) { + return fmt.Errorf("path traversal detected: %s", cleanedPath) + } return nil } diff --git a/cmd/grype-db/cli/commands/cache_restore_test.go b/cmd/grype-db/cli/commands/cache_restore_test.go new file mode 100644 index 00000000..c2d56906 --- /dev/null +++ b/cmd/grype-db/cli/commands/cache_restore_test.go @@ -0,0 +1,242 @@ +package commands + +import ( + "bytes" + "path/filepath" + "testing" + + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDetectLinkTraversal(t *testing.T) { + rootPath := "/safe/root" + + tests := []struct { + name string + cleanedPath string + linkTarget string + wantErr bool + }{ + { + name: "valid symlink inside root", + cleanedPath: "/safe/root/some/file", + linkTarget: "target/file", + wantErr: false, + }, + { + name: "symlink outside root", + cleanedPath: "/safe/root/some/file", + linkTarget: "../../outside/file", + wantErr: true, + }, + { + name: "absolute symlink outside root", + cleanedPath: "/safe/root/some/file", + linkTarget: "/other/path/file", + wantErr: true, + }, + { + name: "valid symlink to a deeper path", + cleanedPath: "/safe/root/some/file", + linkTarget: "another/file", + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := detectLinkTraversal(rootPath, tt.cleanedPath, tt.linkTarget) + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestDetectPathTraversal(t *testing.T) { + rootPath := "/safe/root" + + tests := []struct { + name string + cleanedPath string + wantErr bool + }{ + { + name: "valid path inside root", + cleanedPath: "/safe/root/some/file", + wantErr: false, + }, + { + name: "path outside root", + cleanedPath: "/unsafe/root/some/file", + wantErr: true, + }, + { + name: "empty path", + cleanedPath: "", + wantErr: false, + }, + { + name: "root path itself", + cleanedPath: "/safe/root", + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := detectPathTraversal(rootPath, tt.cleanedPath) + if tt.wantErr { + assert.Error(t, err) + assert.Contains(t, err.Error(), "path traversal detected") + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestHandleFile(t *testing.T) { + fs := afero.NewMemMapFs() + + tests := []struct { + name string + path string + content string + wantErr require.ErrorAssertionFunc + verifyFunc func(t *testing.T, fs afero.Fs, path, content string) + }{ + { + name: "valid file creation", + path: "/testdir/file.txt", + content: "hello world", + verifyFunc: func(t *testing.T, fs afero.Fs, path, expected string) { + fileExists, err := afero.Exists(fs, path) + require.NoError(t, err) + assert.True(t, fileExists) + + fileContent, err := afero.ReadFile(fs, path) + require.NoError(t, err) + assert.Equal(t, expected, string(fileContent)) + }, + }, + { + name: "parent directory creation", + path: "/newdir/subdir/file.txt", + content: "content in nested directory", + verifyFunc: func(t *testing.T, fs afero.Fs, path, expected string) { + fileExists, err := afero.Exists(fs, path) + require.NoError(t, err) + assert.True(t, fileExists) + + fileContent, err := afero.ReadFile(fs, path) + require.NoError(t, err) + assert.Equal(t, expected, string(fileContent)) + + dirExists, err := afero.DirExists(fs, "/newdir/subdir") + require.NoError(t, err) + assert.True(t, dirExists) + }, + }, + { + name: "file creation failure", + path: "", + content: "should fail", + wantErr: require.Error, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantErr == nil { + tt.wantErr = require.NoError + } + reader := bytes.NewReader([]byte(tt.content)) + + err := handleFile(fs, tt.path, reader) + tt.wantErr(t, err) + if tt.verifyFunc != nil { + tt.verifyFunc(t, fs, tt.path, tt.content) + } + }) + } +} + +func TestHandleSymlink(t *testing.T) { + + tests := []struct { + name string + headerName string + linkName string + setupFunc func(fs afero.Fs, rootPath, path, linkTarget string) error + wantErr require.ErrorAssertionFunc + }{ + { + name: "valid symlink creation", + headerName: "symlink", + linkName: "target/file", + }, + { + name: "symlink already exists and points to the correct target", + headerName: "symlink", + linkName: "target/file", + setupFunc: func(fs afero.Fs, rootPath, path, linkName string) error { + linker, ok := fs.(afero.Linker) + require.True(t, ok) + return linker.SymlinkIfPossible(linkName, filepath.Join(rootPath, path)) + }, + }, + { + name: "symlink exists and points to a different target", + headerName: "symlink", + linkName: "target/file", + setupFunc: func(fs afero.Fs, rootPath, path, linkName string) error { + linker, ok := fs.(afero.Linker) + require.True(t, ok) + return linker.SymlinkIfPossible("wrong/target", filepath.Join(rootPath, path)) + }, + }, + { + name: "detectLinkTraversal error", + headerName: "symlink", + linkName: "../../outside", + wantErr: require.Error, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fs := afero.NewOsFs() + + rootPath := t.TempDir() + + if tt.wantErr == nil { + tt.wantErr = require.NoError + } + if tt.setupFunc != nil { + err := tt.setupFunc(fs, rootPath, tt.headerName, tt.linkName) + require.NoError(t, err) + } + + cleanPath := cleanPathRelativeToRoot(rootPath, tt.headerName) + err := handleSymlink(fs, rootPath, cleanPath, tt.linkName) + tt.wantErr(t, err) + if err != nil { + return + } + + // check if symlink was created and points to the correct target + linkReader, ok := fs.(afero.LinkReader) + require.True(t, ok) + + linkTarget, err := linkReader.ReadlinkIfPossible(cleanPath) + require.NoError(t, err) + assert.Equal(t, tt.linkName, linkTarget) + + }) + } +} diff --git a/cmd/grype-db/cli/commands/cache_status.go b/cmd/grype-db/cli/commands/cache_status.go index 3541d58d..c245b07d 100644 --- a/cmd/grype-db/cli/commands/cache_status.go +++ b/cmd/grype-db/cli/commands/cache_status.go @@ -3,6 +3,7 @@ package commands import ( "fmt" "os" + "strings" "time" "github.com/gookit/color" @@ -13,7 +14,6 @@ import ( "github.com/anchore/grype-db/cmd/grype-db/application" "github.com/anchore/grype-db/cmd/grype-db/cli/options" - "github.com/anchore/grype-db/internal/log" "github.com/anchore/grype-db/pkg/provider" "github.com/anchore/grype-db/pkg/provider/entry" ) @@ -25,9 +25,15 @@ type cacheStatusConfig struct { options.Store `yaml:",inline" mapstructure:",squash"` options.Selection `yaml:",inline" mapstructure:",squash"` } `yaml:"provider" json:"provider" mapstructure:"provider"` + minRows int64 `yaml:"min-rows" mapstructure:"min-rows"` } func (o *cacheStatusConfig) AddFlags(flags *pflag.FlagSet) { + flags.Int64VarP( + &o.minRows, + "min-rows", "", o.minRows, + "fail validation unless more than this many rows are present in the provider results", + ) options.AddAllFlags(flags, &o.Provider.Store, &o.Provider.Selection) } @@ -45,7 +51,7 @@ func CacheStatus(app *application.Application) *cobra.Command { Short: "verify the status of the existing provider cache", Args: cobra.NoArgs, PreRunE: app.Setup(&cfg), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { return app.Run(cmd.Context(), async(func() error { return cacheStatus(cfg) })) @@ -63,22 +69,12 @@ func cacheStatus(cfg cacheStatusConfig) error { return err } - if len(providerNames) == 0 { - fmt.Println("no provider state cache found") - return nil - } + providerNames, missingProvidersErr := validateRequestedProviders(providerNames, cfg.Provider.IncludeFilter) var sds []*provider.State var errs []error - allowableProviders := strset.New(cfg.Provider.IncludeFilter...) - for _, name := range providerNames { - if allowableProviders.Size() > 0 && !allowableProviders.Has(name) { - log.WithFields("provider", name).Trace("skipping...") - continue - } - workspace := provider.NewWorkspace(cfg.Provider.Root, name) sd, err := workspace.ReadState() if err != nil { @@ -115,10 +111,13 @@ func cacheStatus(cfg cacheStatusConfig) error { if sd != nil { name = sd.Provider - count, err = entry.Count(sd.Store, sd.ResultPaths()) + counter := func() (int64, error) { + return entry.Count(sd.Store, sd.ResultPaths()) + } + count, err = validateCount(cfg, counter) if err != nil { isValid = false - validMsg = fmt.Sprintf("INVALID (unable to count entries: %s)", err.Error()) + validMsg = fmt.Sprintf("INVALID (%s)", err.Error()) } } @@ -135,6 +134,11 @@ func cacheStatus(cfg cacheStatusConfig) error { fmt.Printf(" └── status: %s\n", statusFmt.Sprint(validMsg)) } + if missingProvidersErr != nil { + success = false + fmt.Printf("INVALID (%s)\n", missingProvidersErr.Error()) + } + if !success { os.Exit(1) } @@ -142,6 +146,39 @@ func cacheStatus(cfg cacheStatusConfig) error { return nil } +func validateCount(cfg cacheStatusConfig, counter func() (int64, error)) (int64, error) { + count, err := counter() + if err != nil { + return 0, fmt.Errorf("unable to count entries: %w", err) + } + if count <= cfg.minRows { + return 0, fmt.Errorf("data has %d rows, must have more than %d", count, cfg.minRows) + } + return count, nil +} + +// validateRequestedProviders takes the set of providers found on disk, and the set of providers +// requested at the command line. It returns the subset of providers on disk that were requested. +// If providers were requested that are not present on disk, it returns an error. +// If no providers are explicitly requested, it returns the entire set. +func validateRequestedProviders(providersOnDisk []string, requestedProviders []string) ([]string, error) { + if len(requestedProviders) == 0 { + return providersOnDisk, nil + } + var result []string + requestedSet := strset.New(requestedProviders...) + for _, p := range providersOnDisk { + if requestedSet.Has(p) { + result = append(result, p) + requestedSet.Remove(p) + } + } + if requestedSet.Size() > 0 { + return nil, fmt.Errorf("providers requested but not present on disk: %s", strings.Join(requestedSet.List(), ", ")) + } + return result, nil +} + func readProviderNamesFromRoot(root string) ([]string, error) { // list all the directories in "root" listing, err := os.ReadDir(root) diff --git a/cmd/grype-db/cli/commands/cache_status_test.go b/cmd/grype-db/cli/commands/cache_status_test.go new file mode 100644 index 00000000..65990fdd --- /dev/null +++ b/cmd/grype-db/cli/commands/cache_status_test.go @@ -0,0 +1,101 @@ +package commands + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_validateCount(t *testing.T) { + tests := []struct { + name string + cfg cacheStatusConfig + counter func() (int64, error) + want int64 + wantErr assert.ErrorAssertionFunc + }{ + { + name: "empty count passes when min-rows is -1", + cfg: cacheStatusConfig{minRows: -1}, + counter: func() (int64, error) { + return 0, nil + }, + }, + { + name: "empty count fails when min-rows is 0", + cfg: cacheStatusConfig{minRows: 0}, + counter: func() (int64, error) { + return 0, nil + }, + wantErr: assert.Error, + }, + { + name: "large count passes when min-rows is less", + cfg: cacheStatusConfig{minRows: 12}, + counter: func() (int64, error) { + return 13, nil + }, + want: 13, + }, + { + name: "error is reported when counter returns error", + counter: func() (int64, error) { + return 0, fmt.Errorf("could not count records") + }, + wantErr: assert.Error, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantErr == nil { + tt.wantErr = assert.NoError + } + count, err := validateCount(tt.cfg, tt.counter) + if !tt.wantErr(t, err) { + return + } + assert.Equal(t, tt.want, count) + }) + } +} + +func Test_validateRequestedProviders(t *testing.T) { + tests := []struct { + name string + providersOnDisk []string + requestedProviders []string + want []string + wantErr assert.ErrorAssertionFunc + }{ + { + name: "no requested providers means on disk state is ok", + providersOnDisk: []string{"alpine", "some-provider", "void-linux", "gentoo"}, + want: []string{"alpine", "some-provider", "void-linux", "gentoo"}, + }, + { + name: "requesting subset of providers works", + providersOnDisk: []string{"alpine", "some-provider", "void-linux", "gentoo"}, + requestedProviders: []string{"alpine", "void-linux"}, + want: []string{"alpine", "void-linux"}, + }, + { + name: "requesting missing provider result in error", + providersOnDisk: []string{"alpine", "some-provider", "void-linux", "gentoo"}, + requestedProviders: []string{"alpine", "void-linux", "missing"}, + wantErr: assert.Error, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := validateRequestedProviders(tt.providersOnDisk, tt.requestedProviders) + if tt.wantErr == nil { + tt.wantErr = assert.NoError + } + if !tt.wantErr(t, err, fmt.Sprintf("validateRequestedProviders(%v, %v)", tt.providersOnDisk, tt.requestedProviders)) { + return + } + assert.Equalf(t, tt.want, got, "validateRequestedProviders(%v, %v)", tt.providersOnDisk, tt.requestedProviders) + }) + } +} diff --git a/cmd/grype-db/cli/commands/list_providers.go b/cmd/grype-db/cli/commands/list_providers.go index 2993d604..0cbb8dd1 100644 --- a/cmd/grype-db/cli/commands/list_providers.go +++ b/cmd/grype-db/cli/commands/list_providers.go @@ -47,7 +47,7 @@ func ListProviders(app *application.Application) *cobra.Command { Short: "list all configured providers", Args: chainArgs( cobra.NoArgs, - func(cmd *cobra.Command, args []string) error { + func(_ *cobra.Command, _ []string) error { allowableOutputs := strset.New(cfg.Format.AllowableFormats...) if !allowableOutputs.Has(cfg.Format.Output) { return fmt.Errorf("invalid output format: %s (allowable: %s)", cfg.Format.Output, strings.Join(cfg.Format.AllowableFormats, ", ")) @@ -56,7 +56,7 @@ func ListProviders(app *application.Application) *cobra.Command { }, ), PreRunE: app.Setup(&cfg), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { return app.Run(cmd.Context(), async(func() error { return runListProviders(cfg) })) diff --git a/cmd/grype-db/cli/commands/package.go b/cmd/grype-db/cli/commands/package.go index 152c1f2b..94dbabf1 100644 --- a/cmd/grype-db/cli/commands/package.go +++ b/cmd/grype-db/cli/commands/package.go @@ -39,7 +39,7 @@ func Package(app *application.Application) *cobra.Command { Short: "package the already built database file into an archive ready for upload and serving", Args: cobra.NoArgs, PreRunE: app.Setup(&cfg), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { if cfg.OverrideArchiveExtension != "" { if !strset.New("tar.gz", "tar.zst").Has(cfg.OverrideArchiveExtension) { return fmt.Errorf("archive-extension must be 'tar.gz' or 'tar.zst'") diff --git a/cmd/grype-db/cli/commands/pull.go b/cmd/grype-db/cli/commands/pull.go index 96640170..b5a6309b 100644 --- a/cmd/grype-db/cli/commands/pull.go +++ b/cmd/grype-db/cli/commands/pull.go @@ -42,7 +42,7 @@ func Pull(app *application.Application) *cobra.Command { Short: "pull and process all upstream vulnerability data", Args: cobra.NoArgs, PreRunE: app.Setup(&cfg), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { return app.Run(cmd.Context(), async(func() error { return runPull(cfg) })) diff --git a/cmd/grype-db/cli/commands/root.go b/cmd/grype-db/cli/commands/root.go index 3a8616fd..945944e7 100644 --- a/cmd/grype-db/cli/commands/root.go +++ b/cmd/grype-db/cli/commands/root.go @@ -45,7 +45,7 @@ func Root(app *application.Application) *cobra.Command { Version: application.ReadBuildInfo().Version, PreRunE: app.Setup(&cfg), Example: formatRootExamples(), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { return app.Run(cmd.Context(), async(func() error { if err := runPull(pullConfig{ Pull: cfg.Pull, diff --git a/cmd/grype-db/cli/commands/test-fixtures/test-root/test-provider/input/some-input-file.txt b/cmd/grype-db/cli/commands/test-fixtures/test-root/test-provider/input/some-input-file.txt new file mode 100644 index 00000000..a37672b3 --- /dev/null +++ b/cmd/grype-db/cli/commands/test-fixtures/test-root/test-provider/input/some-input-file.txt @@ -0,0 +1 @@ +input-file \ No newline at end of file diff --git a/cmd/grype-db/cli/commands/test-fixtures/test-root/test-provider/metadata.json b/cmd/grype-db/cli/commands/test-fixtures/test-root/test-provider/metadata.json new file mode 100644 index 00000000..228cef37 --- /dev/null +++ b/cmd/grype-db/cli/commands/test-fixtures/test-root/test-provider/metadata.json @@ -0,0 +1,18 @@ +{ + "provider": "wolfi", + "urls": [ + "https://packages.wolfi.dev/os/security.json" + ], + "store": "sqlite", + "timestamp": "2024-03-25T01:26:38.371438+00:00", + "version": 1, + "listing": { + "digest": "00dfa461d5d03954", + "path": "checksums", + "algorithm": "xxh64" + }, + "schema": { + "version": "1.0.1", + "url": "https://raw.githubusercontent.com/anchore/vunnel/main/schema/provider-workspace-state/schema-1.0.1.json" + } +} \ No newline at end of file diff --git a/cmd/grype-db/cli/commands/test-fixtures/test-root/test-provider/results/results.db b/cmd/grype-db/cli/commands/test-fixtures/test-root/test-provider/results/results.db new file mode 100644 index 00000000..1d8b1aec --- /dev/null +++ b/cmd/grype-db/cli/commands/test-fixtures/test-root/test-provider/results/results.db @@ -0,0 +1 @@ +results-file \ No newline at end of file diff --git a/cmd/grype-db/cli/commands/utils.go b/cmd/grype-db/cli/commands/utils.go index 42adbe7b..a1a35fc0 100644 --- a/cmd/grype-db/cli/commands/utils.go +++ b/cmd/grype-db/cli/commands/utils.go @@ -27,7 +27,7 @@ func commonConfiguration(app *application.Application, cmd *cobra.Command, opts if app != nil { // we want to be able to attach config binding information to the help output - cmd.SetHelpFunc(func(passCmd *cobra.Command, args []string) { + cmd.SetHelpFunc(func(_ *cobra.Command, args []string) { _ = app.Setup(opts)(cmd, args) cmd.Parent().HelpFunc()(cmd, args) }) diff --git a/cmd/grype-db/cli/commands/version.go b/cmd/grype-db/cli/commands/version.go index 40af5685..527a027f 100644 --- a/cmd/grype-db/cli/commands/version.go +++ b/cmd/grype-db/cli/commands/version.go @@ -24,7 +24,7 @@ func Version(_ *application.Application) *cobra.Command { return nil }, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, _ []string) error { // note: we intentionally do not execute through the application infrastructure (no app config is required for this command) buildInfo := application.ReadBuildInfo() diff --git a/cmd/grype-db/cli/options/build.go b/cmd/grype-db/cli/options/build.go index 1db55b71..5b5c1164 100644 --- a/cmd/grype-db/cli/options/build.go +++ b/cmd/grype-db/cli/options/build.go @@ -17,14 +17,19 @@ type Build struct { SchemaVersion int `yaml:"schema-version" json:"schema-version" mapstructure:"schema-version"` // unbound options - // (none) + IncludeCPEParts []string `yaml:"include-cpe-parts" json:"include-cpe-parts" mapstructure:"include-cpe-parts"` + InferNVDFixVersions bool `yaml:"infer-nvd-fix-versions" json:"infer-nvd-fix-versions" mapstructure:"infer-nvd-fix-versions"` + Hydrate bool `yaml:"hydrate" json:"hydrate" mapstructure:"hydrate"` } func DefaultBuild() Build { return Build{ - DBLocation: DefaultDBLocation(), - SkipValidation: false, - SchemaVersion: process.DefaultSchemaVersion, + DBLocation: DefaultDBLocation(), + SkipValidation: false, + SchemaVersion: process.DefaultSchemaVersion, + IncludeCPEParts: []string{"a", "h", "o"}, + InferNVDFixVersions: true, + Hydrate: false, } } @@ -54,7 +59,9 @@ func (o *Build) BindFlags(flags *pflag.FlagSet, v *viper.Viper) error { } // set default values for non-bound struct items - // (none) + v.SetDefault("build.include-cpe-parts", o.IncludeCPEParts) + v.SetDefault("build.infer-nvd-fix-versions", o.InferNVDFixVersions) + v.SetDefault("build.hydrate", o.Hydrate) return o.DBLocation.BindFlags(flags, v) } diff --git a/cmd/grype-db/cli/options/results.go b/cmd/grype-db/cli/options/results.go new file mode 100644 index 00000000..3ceac9f4 --- /dev/null +++ b/cmd/grype-db/cli/options/results.go @@ -0,0 +1,28 @@ +package options + +import ( + "github.com/spf13/pflag" + "github.com/spf13/viper" +) + +var _ Interface = &Results{} + +type Results struct { + ResultsOnly bool `yaml:"results-only" json:"results-only" mapstructure:"results-only"` +} + +func (r Results) AddFlags(flags *pflag.FlagSet) { + flags.BoolVarP( + &r.ResultsOnly, + "results-only", "r", r.ResultsOnly, + "only backup the results and ignore the input directories (default: false)", + ) +} + +func (r Results) BindFlags(flags *pflag.FlagSet, v *viper.Viper) error { + return Bind(v, "results.results-only", flags.Lookup("results-only")) +} + +func DefaultResults() Results { + return Results{ResultsOnly: false} +} diff --git a/config/grype-db-manager/include.d/distribution-production-r2.yaml b/config/grype-db-manager/include.d/distribution-production-r2.yaml new file mode 100644 index 00000000..d22f09e4 --- /dev/null +++ b/config/grype-db-manager/include.d/distribution-production-r2.yaml @@ -0,0 +1,7 @@ +# distribution: + +listing-file-name: listing.json +s3-path: databases +s3-bucket: oss-prod-anchore +aws-region: auto +download-url-prefix: https://grype.anchore.io diff --git a/config/grype-db-manager/include.d/grype-db-local-build-r2.yaml b/config/grype-db-manager/include.d/grype-db-local-build-r2.yaml new file mode 100644 index 00000000..88720c51 --- /dev/null +++ b/config/grype-db-manager/include.d/grype-db-local-build-r2.yaml @@ -0,0 +1,9 @@ +# grype-db: + +# use the current repo at the current commit as the source of truth for the grype-db build source. +# note: assume this will be invoked from the root of the repo +version: file://. + +# grype-db application configuration to use. +# note: assume this will be invoked from the root of the repo +config: config/grype-db/publish-nightly-r2.yaml diff --git a/config/grype-db-manager/include.d/validate.yaml b/config/grype-db-manager/include.d/validate.yaml index 7a043ce4..fc6bd1fd 100644 --- a/config/grype-db-manager/include.d/validate.yaml +++ b/config/grype-db-manager/include.d/validate.yaml @@ -1,41 +1,74 @@ # validate: listing: - image: "centos:8.2.2004" - minimum-packages: 85 - minimum-vulnerabilities: 400 + image: "alpine:3.9.2" + minimum-packages: 5 + minimum-vulnerabilities: 40 -db: +expected-providers: + - alpine + - amazon + - chainguard + - debian + - epss + - github + - kev + - mariner + - nvd + - oracle + - rhel + - sles + - ubuntu + - wolfi + +default-max-year: 2021 +gates: # new vulnerabilities are added all the time, instead of keeping up it's easier to ignore newer entries. # This approach helps tremendously with keeping the analysis relatively stable. - default-max-year: 2021 - - gate: - # float between 0 and 1, the maximum allowable difference below the OSS F1 score before the gate fails (default 0, - # meaning the test F1 score must be equal to or greater than the OSS F1 score to pass the gate) - f1-score-threshold: 0.15 - - # float between 0 and 100, the maximum % of unlabeled matches for a scan result before the gate fails (default 10%, - # meaning the test scan must have less than 10% unlabeled matches to pass the gate) - # TODO: this should be at 25.0 after we sunset v1 and v2 schemas - unlabeled-matches-threshold: 50.0 - - # integer, the maximum allowable introduced FNs by the test scan (but found by the OSS scan) before the gate fails - # (default 0, meaning the test scan must have the same or fewer FNs than the OSS scan to pass the gate) - introduced-fns-threshold: 10 - - # these are the set of images we will capture grype scans for using an existing published DB and a newly build DB. - # The assumption is that they should perform similarly or the new DB should perform better. We do allow for the new - # DB to perform slightly worse based on the above gate configuration, but not by much. These images are NOT - # meant to be exhaustive or even cover all namespaces. - # note: always reference images with BOTH a tag and a digest - images: - - docker.io/anchore/test_images:grype-quality-node-d89207b@sha256:f56164678054e5eb59ab838367373a49df723b324617b1ba6de775749d7f91d4 - - docker.io/anchore/test_images:grype-quality-python-d89207b@sha256:b2b58a55c0b03c1626d2aaae2add9832208b02124dda7b7b41811e14f0fb272c - - docker.io/anchore/test_images:grype-quality-java-d89207b@sha256:b3534fc2e37943136d5b54e3a58b55d4ccd4363d926cf7aa5bf55a524cf8275b - - docker.io/anchore/test_images:grype-quality-golang-d89207b@sha256:7536ee345532f674ec9e448e3768db4e546c48220ba2b6ec9bc9cfbfb3b7b74a - - docker.io/anchore/test_images:grype-quality-ruby-d89207b@sha256:1a5a5f870924e88a6f0f2b8089cf276ef0a79b5244a052cdfe4a47bb9e5a2c10 - - docker.io/alpine:3.2@sha256:ddac200f3ebc9902fb8cfcd599f41feb2151f1118929da21bcef57dc276975f9 - - docker.io/debian:7@sha256:81e88820a7759038ffa61cff59dfcc12d3772c3a2e75b7cfe963c952da2ad264 - - registry.access.redhat.com/ubi8@sha256:68fecea0d255ee253acbf0c860eaebb7017ef5ef007c25bee9eeffd29ce85b29 - - docker.io/ubuntu:20.04@sha256:9d42d0e3e57bc067d10a75ee33bdd1a5298e95e5fc3c5d1fce98b455cb879249 + - gate: + # float between 0 and 1, the maximum allowable difference below the OSS F1 score before the gate fails (default 0, + # meaning the test F1 score must be equal to or greater than the OSS F1 score to pass the gate) + max_f1_regression: 0.15 + + # float between 0 and 100, the maximum % of unlabeled matches for a scan result before the gate fails (default 10%, + # meaning the test scan must have less than 10% unlabeled matches to pass the gate) + # TODO: this should be at 25 after we sunset v1 and v2 schemas + max_unlabeled_percent: 50 + + # integer, the maximum allowable introduced FNs by the test scan (but found by the OSS scan) before the gate fails + # (default 0, meaning the test scan must have the same or fewer FNs than the OSS scan to pass the gate) + max_new_false_negatives: 10 + max_year: 2021 + candidate_tool_label: 'custom-db' + + # these are the set of images we will capture grype scans for using an existing published DB and a newly build DB. + # The assumption is that they should perform similarly or the new DB should perform better. We do allow for the new + # DB to perform slightly worse based on the above gate configuration, but not by much. These images are NOT + # meant to be exhaustive or even cover all namespaces. + # note: always reference images with BOTH a tag and a digest + images: + - docker.io/anchore/test_images:grype-quality-node-d89207b@sha256:f56164678054e5eb59ab838367373a49df723b324617b1ba6de775749d7f91d4 + - docker.io/anchore/test_images:grype-quality-python-d89207b@sha256:b2b58a55c0b03c1626d2aaae2add9832208b02124dda7b7b41811e14f0fb272c + - docker.io/anchore/test_images:grype-quality-java-d89207b@sha256:b3534fc2e37943136d5b54e3a58b55d4ccd4363d926cf7aa5bf55a524cf8275b + - docker.io/anchore/test_images:grype-quality-golang-d89207b@sha256:7536ee345532f674ec9e448e3768db4e546c48220ba2b6ec9bc9cfbfb3b7b74a + - docker.io/anchore/test_images:grype-quality-ruby-d89207b@sha256:1a5a5f870924e88a6f0f2b8089cf276ef0a79b5244a052cdfe4a47bb9e5a2c10 + - docker.io/alpine:3.2@sha256:ddac200f3ebc9902fb8cfcd599f41feb2151f1118929da21bcef57dc276975f9 + - docker.io/debian:7@sha256:81e88820a7759038ffa61cff59dfcc12d3772c3a2e75b7cfe963c952da2ad264 + - registry.access.redhat.com/ubi8@sha256:68fecea0d255ee253acbf0c860eaebb7017ef5ef007c25bee9eeffd29ce85b29 + - docker.io/ubuntu:20.04@sha256:9d42d0e3e57bc067d10a75ee33bdd1a5298e95e5fc3c5d1fce98b455cb879249 + # Old versions of grype may find no matches for some newer images + # Do not fail quality gate over this + allow_empty_results_for_schemas: [1,2] + + - gate: + max_f1_regression: 0.15 + max_unlabeled_percent: 25 + max_new_false_negatives: 10 + max_year: 2022 # important - Azure Linux 3 doesn't have enough CVEs going back to 2021 + candidate_tool_label: 'custom-db' + # Old versions of grype don't recognize Azure Linux 3, and so will make an + # empty result set + allow_empty_results_for_schemas: [1,2,3,4] + + images: + - docker.io/anchore/test_images:azurelinux3-63671fe@sha256:2d761ba36575ddd4e07d446f4f2a05448298c20e5bdcd3dedfbbc00f9865240d diff --git a/config/grype-db-manager/publish-production-r2.yaml b/config/grype-db-manager/publish-production-r2.yaml new file mode 100644 index 00000000..4955a8db --- /dev/null +++ b/config/grype-db-manager/publish-production-r2.yaml @@ -0,0 +1,11 @@ +# this configuration is intended to be used for nightly builds of the database in production + +data: !include config/grype-db-manager/include.d/data.yaml + +grype-db: !include config/grype-db-manager/include.d/grype-db-local-build-r2.yaml + +distribution: !include config/grype-db-manager/include.d/distribution-production-r2.yaml + +validate: !include config/grype-db-manager/include.d/validate.yaml + +assert-aws-credentials: false \ No newline at end of file diff --git a/config/grype-db/publish-nightly-r2.yaml b/config/grype-db/publish-nightly-r2.yaml new file mode 100644 index 00000000..44154b89 --- /dev/null +++ b/config/grype-db/publish-nightly-r2.yaml @@ -0,0 +1,41 @@ +# this is a grype-db application configuration file intended for use with the daily db publisher workflow + +provider: + root: data/vunnel + + # No manual configs are provided since 'provider.vunnel.generateConfigs' is set to true + # this means that well run vunnel to get the list of supported providers. All supported providers + # will be included in the database build. This prevents the need from manually updating this file + # for every new provider that is added. + # + # Any providers that should be excluded from processing should be added to the 'provider.vunnel.excludeProviders' list. + configs: + - name: nvd + - name: alpine + - name: amazon + - name: chainguard + - name: debian + - name: epss + - name: github + - name: kev + - name: mariner + - name: oracle + - name: rhel + - name: sles + - name: ubuntu + - name: wolfi + + vunnel: + executor: docker + docker-tag: latest + generate-configs: false + env: + GITHUB_TOKEN: $GITHUB_TOKEN + NVD_API_KEY: $NVD_API_KEY + +pull: + parallelism: 4 + +package: + # required for v5 + publish-base-url: https://grype.anchore.io/databases diff --git a/config/grype-db/publish-nightly.yaml b/config/grype-db/publish-nightly.yaml index e6a04f1e..1be50b1d 100644 --- a/config/grype-db/publish-nightly.yaml +++ b/config/grype-db/publish-nightly.yaml @@ -9,12 +9,26 @@ provider: # for every new provider that is added. # # Any providers that should be excluded from processing should be added to the 'provider.vunnel.excludeProviders' list. - configs: [] + configs: + - name: nvd + - name: alpine + - name: amazon + - name: chainguard + - name: debian + - name: epss + - name: github + - name: kev + - name: mariner + - name: oracle + - name: rhel + - name: sles + - name: ubuntu + - name: wolfi vunnel: executor: docker docker-tag: latest - generate-configs: true + generate-configs: false env: GITHUB_TOKEN: $GITHUB_TOKEN NVD_API_KEY: $NVD_API_KEY @@ -23,4 +37,5 @@ pull: parallelism: 4 package: + # required for v5 publish-base-url: https://toolbox-data.anchore.io/grype/databases diff --git a/data/vulnerability-match-labels b/data/vulnerability-match-labels index 56a4bb53..30c7404c 160000 --- a/data/vulnerability-match-labels +++ b/data/vulnerability-match-labels @@ -1 +1 @@ -Subproject commit 56a4bb5373cfa2ec1c5ddbe2c0f6eba72e77c6da +Subproject commit 30c7404cd3c6157db672b5f4a0dde483ddbed52d diff --git a/go.mod b/go.mod index ec24e6f0..e0bebb0b 100644 --- a/go.mod +++ b/go.mod @@ -1,129 +1,170 @@ module github.com/anchore/grype-db -go 1.21.1 +go 1.24.1 require ( - github.com/Masterminds/semver/v3 v3.2.1 + github.com/Masterminds/semver/v3 v3.3.1 github.com/OneOfOne/xxhash v1.2.8 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d - github.com/adrg/xdg v0.4.0 - github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a - github.com/anchore/grype v0.74.1 - github.com/anchore/syft v0.101.0 + github.com/adrg/xdg v0.5.3 + github.com/anchore/go-logger v0.0.0-20250318195838-07ae343dd722 + github.com/anchore/grype v0.92.1 + github.com/anchore/syft v1.25.1 + github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de + github.com/dave/jennifer v1.7.1 github.com/dustin/go-humanize v1.0.1 - github.com/glebarez/sqlite v1.10.0 - github.com/go-test/deep v1.1.0 - github.com/google/go-cmp v0.6.0 + github.com/glebarez/sqlite v1.11.0 + github.com/go-test/deep v1.1.1 + github.com/google/go-cmp v0.7.0 + github.com/google/osv-scanner v1.9.2 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 + github.com/google/uuid v1.6.0 github.com/gookit/color v1.5.4 github.com/hashicorp/go-cleanhttp v0.5.2 - github.com/hashicorp/go-getter v1.7.3 + github.com/hashicorp/go-getter v1.7.8 github.com/hashicorp/go-multierror v1.1.1 github.com/iancoleman/strcase v0.3.0 github.com/jinzhu/copier v0.4.0 - github.com/klauspost/compress v1.17.4 + github.com/klauspost/compress v1.18.0 github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/mapstructure v1.5.0 github.com/pkg/profile v1.7.0 github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e - github.com/sergi/go-diff v1.3.1 - github.com/spf13/afero v1.11.0 - github.com/spf13/cobra v1.8.0 - github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.18.2 - github.com/stretchr/testify v1.8.4 + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 + github.com/spf13/afero v1.14.0 + github.com/spf13/cobra v1.9.1 + github.com/spf13/pflag v1.0.6 + github.com/spf13/viper v1.20.1 + github.com/stretchr/testify v1.10.0 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.6.0 - golang.org/x/text v0.14.0 + golang.org/x/sync v0.14.0 + golang.org/x/text v0.25.0 gopkg.in/yaml.v3 v3.0.1 - gorm.io/gorm v1.25.5 + gorm.io/gorm v1.26.1 ) require ( - cloud.google.com/go v0.110.10 // indirect - cloud.google.com/go/compute v1.23.3 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v1.1.5 // indirect - cloud.google.com/go/storage v1.35.1 // indirect - dario.cat/mergo v1.0.0 // indirect + cel.dev/expr v0.16.2 // indirect + cloud.google.com/go v0.116.0 // indirect + cloud.google.com/go/auth v0.13.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect + cloud.google.com/go/compute/metadata v0.6.0 // indirect + cloud.google.com/go/iam v1.2.2 // indirect + cloud.google.com/go/monitoring v1.21.2 // indirect + cloud.google.com/go/storage v1.49.0 // indirect + dario.cat/mergo v1.0.1 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 // indirect - github.com/CycloneDX/cyclonedx-go v0.8.0 // indirect - github.com/DataDog/zstd v1.4.5 // indirect - github.com/Masterminds/semver v1.5.0 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect + github.com/BurntSushi/toml v1.5.0 // indirect + github.com/CycloneDX/cyclonedx-go v0.9.2 // indirect + github.com/DataDog/zstd v1.5.5 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/sprig/v3 v3.3.0 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/Microsoft/hcsshim v0.11.7 // indirect + github.com/ProtonMail/go-crypto v1.2.0 // indirect + github.com/STARRY-S/zip v0.2.1 // indirect github.com/acobaugh/osrelease v0.1.0 // indirect - github.com/anchore/clio v0.0.0-20231016125544-c98a83e1c7fc // indirect - github.com/anchore/fangs v0.0.0-20231201140849-5075d28d6d8b // indirect + github.com/agext/levenshtein v1.2.1 // indirect + github.com/anchore/archiver/v3 v3.5.3-0.20241210171143-5b1d8d1c7c51 // indirect + github.com/anchore/clio v0.0.0-20250408180537-ec8fa27f0d9f // indirect + github.com/anchore/fangs v0.0.0-20250402135612-96e29e45f3fe // indirect + github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537 // indirect + github.com/anchore/go-homedir v0.0.0-20250319154043-c29668562e4d // 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-rpmdb v0.0.0-20250516171929-f77691e1faec // indirect + github.com/anchore/go-struct-converter v0.0.0-20230627203149-c72ef8859ca9 // indirect + github.com/anchore/go-sync v0.0.0-20250326131806-4eda43a485b6 // indirect github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4 // indirect - github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501 // indirect - github.com/anchore/stereoscope v0.0.0-20231220161148-590920dabc54 // 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 + github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115 // indirect + github.com/anchore/stereoscope v0.1.4 // indirect + github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/aquasecurity/go-pep440-version v0.0.1 // indirect + github.com/aquasecurity/go-version v0.0.1 // indirect github.com/aws/aws-sdk-go v1.44.288 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/becheran/wildmatch-go v1.0.0 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect + github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef // indirect + github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect github.com/bmatcuk/doublestar/v2 v2.0.4 // indirect - github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect - github.com/cloudflare/circl v1.3.7 // indirect + github.com/bmatcuk/doublestar/v4 v4.8.1 // indirect + github.com/bodgit/plumbing v1.3.0 // indirect + github.com/bodgit/sevenzip v1.6.0 // indirect + github.com/bodgit/windows v1.0.1 // indirect + github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect + github.com/charmbracelet/lipgloss v1.1.0 // indirect + github.com/charmbracelet/x/ansi v0.8.0 // indirect + github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect + github.com/charmbracelet/x/term v0.2.1 // indirect + github.com/cloudflare/circl v1.6.1 // indirect + github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect github.com/containerd/cgroups v1.1.0 // indirect - github.com/containerd/containerd v1.7.11 // indirect - github.com/containerd/continuity v0.4.2 // indirect + github.com/containerd/containerd v1.7.27 // indirect + github.com/containerd/containerd/api v1.8.0 // indirect + github.com/containerd/continuity v0.4.4 // indirect + github.com/containerd/errdefs v0.3.0 // indirect github.com/containerd/fifo v1.1.0 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect - github.com/containerd/ttrpc v1.2.2 // indirect + github.com/containerd/platforms v0.2.1 // indirect + github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect + github.com/containerd/ttrpc v1.2.7 // indirect github.com/containerd/typeurl/v2 v2.1.1 // indirect - github.com/cyphar/filepath-securejoin v0.2.4 // indirect + github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/cli v24.0.0+incompatible // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/cli v28.1.1+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect - github.com/docker/docker v24.0.7+incompatible // indirect - github.com/docker/docker-credential-helpers v0.7.0 // indirect - github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/docker v28.1.1+incompatible // indirect + github.com/docker/docker-credential-helpers v0.8.2 // indirect + github.com/docker/go-connections v0.5.0 // indirect 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/edsrzf/mmap-go v1.1.0 // indirect + github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect + github.com/elliotchance/phpserialize v1.4.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect + github.com/envoyproxy/go-control-plane v0.13.1 // indirect + github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect github.com/facebookincubator/nvdtools v0.1.5 // indirect - github.com/felixge/fgprof v0.9.3 // indirect - github.com/felixge/httpsnoop v1.0.3 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.3 // indirect - github.com/github/go-spdx/v2 v2.2.0 // indirect - github.com/glebarez/go-sqlite v1.21.2 // indirect + github.com/felixge/fgprof v0.9.5 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.9 // indirect + github.com/github/go-spdx/v2 v2.3.3 // indirect + github.com/glebarez/go-sqlite v1.22.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.5.0 // indirect - github.com/go-git/go-git/v5 v5.11.0 // indirect - github.com/go-logr/logr v1.2.4 // indirect + github.com/go-git/go-billy/v5 v5.6.2 // indirect + github.com/go-git/go-git/v5 v5.16.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-restruct/restruct v1.2.0-alpha // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/gohugoio/hashstructure v0.5.0 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/google/go-containerregistry v0.17.0 // indirect + github.com/google/go-containerregistry v0.20.3 // indirect github.com/google/licensecheck v0.3.1 // indirect - github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 // indirect - github.com/google/s2a-go v0.1.7 // indirect - github.com/google/uuid v1.5.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.0 // indirect + github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect + github.com/google/s2a-go v0.1.8 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect + github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect - github.com/hashicorp/go-version v1.6.0 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect + github.com/hashicorp/go-version v1.7.0 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/hashicorp/hcl/v2 v2.23.0 // indirect + github.com/huandu/xstrings v1.5.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jinzhu/inflection v1.0.0 // indirect @@ -131,89 +172,114 @@ require ( github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/klauspost/pgzip v1.2.5 // indirect + github.com/klauspost/pgzip v1.2.6 // indirect github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f // indirect github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d // indirect - github.com/knqyf263/go-rpmdb v0.0.0-20230301153543-ba94b245509b // indirect - github.com/magiconair/properties v1.8.7 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.18 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect - github.com/mholt/archiver/v3 v3.5.1 // indirect - github.com/microsoft/go-rustaudit v0.0.0-20220730194248-4b17361d90a5 // indirect + github.com/mholt/archives v0.1.2 // indirect + github.com/minio/minlz v1.0.0 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect - github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/locker v1.0.1 // indirect - github.com/moby/sys/mountinfo v0.6.2 // indirect - github.com/moby/sys/sequential v0.5.0 // indirect + github.com/moby/sys/mountinfo v0.7.2 // indirect + github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/sys/signal v0.7.0 // indirect - github.com/nwaples/rardecode v1.1.0 // indirect + github.com/moby/sys/user v0.3.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect + github.com/muesli/termenv v0.16.0 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/nix-community/go-nix v0.0.0-20250101154619-4bdde671e0a1 // indirect + github.com/nwaples/rardecode v1.1.3 // indirect + github.com/nwaples/rardecode/v2 v2.1.0 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0-rc3 // indirect - github.com/opencontainers/runc v1.1.5 // indirect - github.com/opencontainers/runtime-spec v1.1.0-rc.1 // indirect + github.com/opencontainers/image-spec v1.1.1 // 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.3 // indirect + github.com/pandatix/go-cvss v0.6.2 // 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.1.0 // indirect - github.com/pierrec/lz4/v4 v4.1.15 // indirect - github.com/pjbgf/sha1cd v0.3.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/pierrec/lz4/v4 v4.1.22 // indirect + github.com/pjbgf/sha1cd v0.3.2 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/saferwall/pe v1.4.8 // indirect - github.com/sagikazarmark/locafero v0.4.0 // indirect - github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/rust-secure-code/go-rustaudit v0.0.0-20250226111315-e20ec32e963c // indirect + github.com/sagikazarmark/locafero v0.9.0 // indirect github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect - github.com/sassoftware/go-rpmutils v0.2.0 // indirect + github.com/sassoftware/go-rpmutils v0.4.0 // indirect + github.com/shopspring/decimal v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/skeema/knownhosts v1.2.1 // indirect + github.com/skeema/knownhosts v1.3.1 // indirect + github.com/sorairolake/lzip-go v0.3.5 // indirect github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spdx/tools-golang v0.5.3 // indirect - github.com/spf13/cast v1.6.0 // indirect - github.com/stretchr/objx v0.5.0 // indirect + github.com/spdx/gordf v0.0.0-20221230105357-b735bd5aac89 // indirect + github.com/spdx/tools-golang v0.5.5 // indirect + github.com/spf13/cast v1.7.1 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect - github.com/sylabs/sif/v2 v2.11.5 // indirect - github.com/sylabs/squashfs v0.6.1 // indirect + github.com/sylabs/sif/v2 v2.21.1 // indirect + github.com/sylabs/squashfs v1.0.6 // indirect github.com/therootcompany/xz v1.0.1 // indirect - github.com/ulikunitz/xz v0.5.10 // indirect - github.com/vbatts/go-mtree v0.5.3 // indirect - github.com/vbatts/tar-split v0.11.3 // indirect + github.com/ulikunitz/xz v0.5.12 // indirect + github.com/vbatts/go-mtree v0.5.4 // indirect + github.com/vbatts/tar-split v0.11.6 // indirect github.com/vifraa/gopom v1.0.0 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect - github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect - go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + github.com/zclconf/go-cty v1.14.0 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 // indirect - go.opentelemetry.io/otel v1.19.0 // indirect - go.opentelemetry.io/otel/metric v1.19.0 // indirect - go.opentelemetry.io/otel/trace v1.19.0 // indirect - go.uber.org/atomic v1.9.0 // indirect - go.uber.org/multierr v1.9.0 // indirect - golang.org/x/crypto v0.18.0 // indirect - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.20.0 // indirect - golang.org/x/oauth2 v0.15.0 // indirect - golang.org/x/sys v0.16.0 // indirect - golang.org/x/term v0.16.0 // indirect - golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.13.0 // indirect - golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/api v0.153.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect - google.golang.org/grpc v1.59.0 // indirect - google.golang.org/protobuf v1.31.0 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.31.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect + go.opentelemetry.io/otel v1.33.0 // indirect + go.opentelemetry.io/otel/metric v1.33.0 // indirect + go.opentelemetry.io/otel/sdk v1.33.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.31.0 // indirect + go.opentelemetry.io/otel/trace v1.33.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go4.org v0.0.0-20230225012048-214862532bf5 // indirect + golang.org/x/crypto v0.38.0 // indirect + golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect + golang.org/x/mod v0.24.0 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/oauth2 v0.25.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/term v0.32.0 // indirect + golang.org/x/time v0.11.0 // indirect + golang.org/x/tools v0.33.0 // indirect + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect + google.golang.org/api v0.215.0 // indirect + google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect + google.golang.org/grpc v1.69.4 // indirect + google.golang.org/protobuf v1.36.4 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gotest.tools/v3 v3.1.0 // indirect - modernc.org/libc v1.29.0 // indirect - modernc.org/mathutil v1.6.0 // indirect - modernc.org/memory v1.7.2 // indirect - modernc.org/sqlite v1.28.0 // indirect + modernc.org/libc v1.62.1 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.9.1 // indirect + modernc.org/sqlite v1.37.0 // indirect ) + +// this is a breaking change, so we need to pin the version until glebarez/go-sqlite is updated to use internal/libc +replace modernc.org/sqlite v1.33.0 => modernc.org/sqlite v1.32.0 + +// the go.mod file did not have the correct minimum go version at the time of release tagging +retract v0.30.0 diff --git a/go.sum b/go.sum index 8e312a2e..ca0a5457 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,11 @@ +cel.dev/expr v0.16.2 h1:RwRhoH17VhAu9U5CMvMhH1PDVgf0tuz9FT+24AfMLfU= +cel.dev/expr v0.16.2/go.mod h1:gXngZQMkWJoSbE8mOzehJlXQyubn/Vg0vR9/F3W7iw8= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= @@ -15,6 +18,7 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= @@ -27,28 +31,96 @@ cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= -cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y= -cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic= +cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= +cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= +cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= +cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= +cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= +cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= +cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= +cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= +cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o= +cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE= +cloud.google.com/go/accesscontextmanager v1.6.0/go.mod h1:8XCvZWfYw3K/ji0iVnp+6pu7huxoQTLmxAbVjbloTtM= +cloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ= cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= +cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg= +cloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6lKv3w0+tFTgXQ= +cloud.google.com/go/aiplatform v1.36.1/go.mod h1:WTm12vJRPARNvJ+v6P52RDHCNe4AhvjcIZ/9/RRHy/k= +cloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw= cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= +cloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M= +cloud.google.com/go/analytics v0.18.0/go.mod h1:ZkeHGQlcIPkw0R/GW+boWHhCOR43xz9RN/jn7WcqfIE= +cloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE= +cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk= +cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= +cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= +cloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc= +cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04= +cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8= +cloud.google.com/go/apigeeregistry v0.4.0/go.mod h1:EUG4PGcsZvxOXAdyEghIdXwAEi/4MEaoqLMLDMIwKXY= +cloud.google.com/go/apigeeregistry v0.5.0/go.mod h1:YR5+s0BVNZfVOUkMa5pAR2xGd0A473vA5M7j247o1wM= +cloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc= +cloud.google.com/go/apikeys v0.4.0/go.mod h1:XATS/yqZbaBK0HOssf+ALHp8jAlNHUgyfprvNcBIszU= +cloud.google.com/go/apikeys v0.5.0/go.mod h1:5aQfwY4D+ewMMWScd3hm2en3hCj+BROlyrt3ytS7KLI= +cloud.google.com/go/apikeys v0.6.0/go.mod h1:kbpXu5upyiAlGkKrJgQl8A0rKNNJ7dQ377pdroRSSi8= +cloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno= +cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak= +cloud.google.com/go/appengine v1.6.0/go.mod h1:hg6i0J/BD2cKmDJbaFSYHFyZkgBEfQrDg/X0V5fJn84= +cloud.google.com/go/appengine v1.7.0/go.mod h1:eZqpbHFCqRGa2aCdope7eC0SWLV1j0neb/QnMJVWx6A= +cloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E= cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= +cloud.google.com/go/area120 v0.7.0/go.mod h1:a3+8EUD1SX5RUcCs3MY5YasiO1z6yLiNLRiFrykbynY= +cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k= cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= +cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0= +cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc= +cloud.google.com/go/artifactregistry v1.11.1/go.mod h1:lLYghw+Itq9SONbCa1YWBoWs1nOucMH0pwXN1rOBZFI= +cloud.google.com/go/artifactregistry v1.11.2/go.mod h1:nLZns771ZGAwVLzTX/7Al6R9ehma4WUEhZGWV6CeQNQ= +cloud.google.com/go/artifactregistry v1.12.0/go.mod h1:o6P3MIvtzTOnmvGagO9v/rOjjA0HmhJ+/6KAXrmYDCI= +cloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08= cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= +cloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ= +cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY= +cloud.google.com/go/asset v1.11.1/go.mod h1:fSwLhbRvC9p9CXQHJ3BgFeQNM4c9x10lqlrdEUYXlJo= +cloud.google.com/go/asset v1.12.0/go.mod h1:h9/sFOa4eDIyKmH6QMpm4eUK3pDojWnUhTgJlk762Hg= +cloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw= cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= +cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo= +cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= +cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= +cloud.google.com/go/auth v0.13.0 h1:8Fu8TZy167JkW8Tj3q7dIkr2v4cndv41ouecJx0PAHs= +cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q= +cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU= +cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= +cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= +cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM= +cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU= +cloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc= +cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI= +cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss= +cloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE= +cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE= +cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g= +cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4= +cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= +cloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM= +cloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -56,12 +128,44 @@ cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUM cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= +cloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw= +cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc= +cloud.google.com/go/bigquery v1.47.0/go.mod h1:sA9XOgy0A8vQK9+MWhEQTY6Tix87M/ZurWFIxmF9I/E= +cloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm0Nf1fysJac= +cloud.google.com/go/bigquery v1.49.0/go.mod h1:Sv8hMmTFFYBlt/ftw2uN6dFdQPzBlREY9yBh7Oy7/4Q= +cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU= cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= +cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= +cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y= +cloud.google.com/go/billing v1.12.0/go.mod h1:yKrZio/eu+okO/2McZEbch17O5CB5NpZhhXG6Z766ss= +cloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc= cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= +cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0= +cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk= +cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q= +cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg= +cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= +cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= +cloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk= +cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk= +cloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE= +cloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU= +cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U= +cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= +cloud.google.com/go/cloudbuild v1.6.0/go.mod h1:UIbc/w9QCbH12xX+ezUsgblrWv+Cv4Tw83GiSMHOn9M= +cloud.google.com/go/cloudbuild v1.7.0/go.mod h1:zb5tWh2XI6lR9zQmsm1VRA+7OCuve5d8S+zJUul8KTg= +cloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s= +cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM= +cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= +cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= +cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4= +cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI= +cloud.google.com/go/cloudtasks v1.9.0/go.mod h1:w+EyLsVkLWHcOaqNEyvcKAsWp9p29dL6uL9Nst1cI7Y= +cloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs= cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= @@ -69,198 +173,564 @@ cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= -cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= -cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE= +cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= +cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= +cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= +cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= +cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= +cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= +cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= +cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= +cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= +cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= +cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= +cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= +cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= +cloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4= +cloud.google.com/go/container v1.14.0/go.mod h1:3AoJMPhHfLDxLvrlVWaK57IXzaPnLaZq63WX59aQBfM= +cloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA= cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= +cloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI= +cloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s= cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= +cloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H0/tc8f8ZbZIE= +cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM= +cloud.google.com/go/datacatalog v1.8.1/go.mod h1:RJ58z4rMp3gvETA465Vg+ag8BGgBdnRPEMMSTr5Uv+M= +cloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3NXLzVJ1d1mRm0= +cloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8= cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= +cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= +cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0= +cloud.google.com/go/dataform v0.6.0/go.mod h1:QPflImQy33e29VuapFdf19oPbE4aYTJxr31OAPV+ulA= +cloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE= +cloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38= +cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w= +cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8= cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= +cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM= +cloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA= +cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A= +cloud.google.com/go/dataplex v1.5.2/go.mod h1:cVMgQHsmfRoI5KFYq4JtIBEUbYwc3c7tXmIDhRmNNVQ= +cloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs= +cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s= +cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= +cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= +cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= +cloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c= cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= +cloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g= +cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4= +cloud.google.com/go/datastream v1.6.0/go.mod h1:6LQSuswqLa7S4rPAOZFVjHIG3wJIjZcZrw8JDEDJuIs= +cloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5CjMqVIUHrww= +cloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c= +cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s= +cloud.google.com/go/deploy v1.6.0/go.mod h1:f9PTHehG/DjCom3QH0cntOVRm93uGBDt2vKzAPwpXQI= +cloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ= cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= +cloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz0njv7sMx/iek= +cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0= +cloud.google.com/go/dialogflow v1.29.0/go.mod h1:b+2bzMe+k1s9V+F2jbJwpHPzrnIyHihAdRFMtn2WXuM= +cloud.google.com/go/dialogflow v1.31.0/go.mod h1:cuoUccuL1Z+HADhyIA7dci3N5zUssgpBJmCzI6fNRB4= +cloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE= +cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= +cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= +cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= +cloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k= +cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4= +cloud.google.com/go/documentai v1.16.0/go.mod h1:o0o0DLTEZ+YnJZ+J4wNfTxmDVyrkzFvttBXXtYRMHkM= +cloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs= cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= +cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= +cloud.google.com/go/edgecontainer v0.3.0/go.mod h1:FLDpP4nykgwwIfcLt6zInhprzw0lEi2P1fjO6Ie0qbc= +cloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY= +cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= +cloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI= +cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8= +cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M= +cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc= +cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw= +cloud.google.com/go/eventarc v1.10.0/go.mod h1:u3R35tmZ9HvswGRBnF48IlYgYeBcPUCjkr4BTdem2Kw= +cloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY= +cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w= +cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= +cloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs= +cloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg= cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= +cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= +cloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY= +cloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08= +cloud.google.com/go/functions v1.10.0/go.mod h1:0D3hEOe3DbEvCXtYOZHQZmD+SzYsi1YbI7dGvHfldXw= +cloud.google.com/go/functions v1.12.0/go.mod h1:AXWGrF3e2C/5ehvwYo/GH6O5s09tOPksiKhz+hH8WkA= +cloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c= cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= +cloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w= +cloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM= +cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0= +cloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60= +cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo= +cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg= cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= +cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw= cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= +cloud.google.com/go/gkehub v0.11.0/go.mod h1:JOWHlmN+GHyIbuWQPl47/C2RFhnFKH38jH9Ascu3n0E= +cloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw= +cloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA= +cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI= +cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y= cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= +cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM= +cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o= +cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo= +cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= -cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= -cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= +cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= +cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= +cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= +cloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY= +cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= +cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= +cloud.google.com/go/iam v1.2.2 h1:ozUSofHUGf/F4tCNy/mu9tHLTaxZFLOUiKzjcgWHGIA= +cloud.google.com/go/iam v1.2.2/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY= +cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= +cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= +cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= +cloud.google.com/go/iap v1.7.0/go.mod h1:beqQx56T9O1G1yNPph+spKpNibDlYIiIixiqsQXxLIo= +cloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74= +cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM= +cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY= +cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4= +cloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs= +cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g= +cloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Qql9o= +cloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE= +cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= +cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg= +cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= +cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg= +cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w= +cloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24= +cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI= cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= +cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= +cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8= +cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY= cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= +cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= +cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= +cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= +cloud.google.com/go/logging v1.12.0 h1:ex1igYcGFd4S/RZWOCU51StlIEuey5bjqwH9ZYjHibk= +cloud.google.com/go/logging v1.12.0/go.mod h1:wwYBt5HlYP1InnrtYI0wtwttpVU1rifnMT7RejksUAM= +cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= +cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= +cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= +cloud.google.com/go/longrunning v0.6.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0MK+hc= +cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= +cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE= +cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM= +cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= +cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI= +cloud.google.com/go/maps v0.6.0/go.mod h1:o6DAMMfb+aINHz/p/jbcY+mYeXBoZoxTfdSQ8VAJaCw= +cloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY= cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= +cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= +cloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA= +cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY= +cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM= cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= +cloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8= +cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI= +cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo= +cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk= +cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= +cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w= +cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw= +cloud.google.com/go/monitoring v1.21.2 h1:FChwVtClH19E7pJ+e0xUhJPGksctZNVOk2UhMmblmdU= +cloud.google.com/go/monitoring v1.21.2/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU= cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= +cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= +cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8= +cloud.google.com/go/networkconnectivity v1.10.0/go.mod h1:UP4O4sWXJG13AqrTdQCD9TnLGEbtNRqjuaaA7bNjF5E= +cloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM= +cloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8= +cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4= +cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= +cloud.google.com/go/networksecurity v0.7.0/go.mod h1:mAnzoxx/8TBSyXEeESMy9OOYwo1v+gZ5eMRnsT5bC8k= +cloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU= cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= +cloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA= +cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0= +cloud.google.com/go/notebooks v1.7.0/go.mod h1:PVlaDGfJgj1fl1S3dUwhFMXFgfYGhYQt2164xOMONmE= +cloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ= +cloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4= +cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs= +cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= +cloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA= +cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk= +cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ= +cloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE= +cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc= +cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc= cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= +cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo= +cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw= +cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw= cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= +cloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70= +cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo= +cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs= cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= +cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= +cloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg= +cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE= +cloud.google.com/go/policytroubleshooter v1.5.0/go.mod h1:Rz1WfV+1oIpPdN2VvvuboLVRsB1Hclg3CKQ53j9l8vw= +cloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc= cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= +cloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg= +cloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI= +cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0= +cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8= +cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= +cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= +cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k= +cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= +cloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE= +cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U= +cloud.google.com/go/recaptchaenterprise/v2 v2.6.0/go.mod h1:RPauz9jeLtB3JVzg6nCbe12qNoaa8pXc4d/YukAmcnA= +cloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c= cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= +cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= +cloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs= +cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70= +cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ= cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= +cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA= +cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM= +cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= +cloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA= +cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0= +cloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots= +cloud.google.com/go/resourcemanager v1.6.0/go.mod h1:YcpXGRs8fDzcUl1Xw8uOVmI8JEadvhRIkoXXUNVYcVo= +cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI= +cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU= +cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg= +cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= +cloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc= +cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y= +cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14= +cloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do= +cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo= +cloud.google.com/go/run v0.8.0/go.mod h1:VniEnuBwqjigv0A7ONfQUaEItaiCRVujlMqerPPiktM= +cloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg= cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= +cloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk= +cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44= +cloud.google.com/go/scheduler v1.8.0/go.mod h1:TCET+Y5Gp1YgHT8py4nlg2Sew8nUHMqcpousDgXJVQc= +cloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc= cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= +cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= +cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= +cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU= cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= +cloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q= +cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA= +cloud.google.com/go/security v1.12.0/go.mod h1:rV6EhrpbNHrrxqlvW0BWAIawFWq3X90SduMJdFwtLB8= +cloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0= cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= +cloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk= +cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk= +cloud.google.com/go/securitycenter v1.18.1/go.mod h1:0/25gAzCM/9OL9vVx4ChPeM/+DlfGQJDwBy/UC8AKK0= +cloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag= +cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU= +cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= +cloud.google.com/go/servicecontrol v1.10.0/go.mod h1:pQvyvSRh7YzUF2efw7H87V92mxU8FnFDawMClGCNuAA= +cloud.google.com/go/servicecontrol v1.11.0/go.mod h1:kFmTzYzTUIuZs0ycVqRHNaNhgR+UMUpw9n02l/pY+mc= +cloud.google.com/go/servicecontrol v1.11.1/go.mod h1:aSnNNlwEFBY+PWGQ2DoM0JJ/QUXqV5/ZD9DOLB7SnUk= cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= +cloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4= +cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U= +cloud.google.com/go/servicedirectory v1.8.0/go.mod h1:srXodfhY1GFIPvltunswqXpVxFPpZjf8nkKQT7XcXaY= +cloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s= +cloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco= +cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo= +cloud.google.com/go/servicemanagement v1.6.0/go.mod h1:aWns7EeeCOtGEX4OvZUWCCJONRZeFKiptqKf1D0l/Jc= +cloud.google.com/go/servicemanagement v1.8.0/go.mod h1:MSS2TDlIEQD/fzsSGfCdJItQveu9NXnUniTrq/L8LK4= +cloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E= +cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU= +cloud.google.com/go/serviceusage v1.5.0/go.mod h1:w8U1JvqUqwJNPEOTQjrMHkw3IaIFLoLsPLvsE3xueec= +cloud.google.com/go/serviceusage v1.6.0/go.mod h1:R5wwQcbOWsyuOfbP9tGdAnCAc6B9DRwPG1xtWMDeuPA= +cloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4= +cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw= +cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= +cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= +cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk= +cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M= cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= +cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= +cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco= +cloud.google.com/go/speech v1.14.1/go.mod h1:gEosVRPJ9waG7zqqnsHpYTOoAS4KouMRLDFMekpJ0J0= +cloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= -cloud.google.com/go/storage v1.35.1 h1:B59ahL//eDfx2IIKFBeT5Atm9wnNmj3+8xG/W4WB//w= -cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= +cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= +cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= +cloud.google.com/go/storage v1.49.0 h1:zenOPBOWHCnojRd9aJZAyQXBYqkJkdQS42dxL55CIMw= +cloud.google.com/go/storage v1.49.0/go.mod h1:k1eHhhpLvrPjVGfo0mOUPEJ4Y2+a/Hv5PiwehZI9qGU= +cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= +cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= +cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= +cloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw= cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= +cloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM= +cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA= +cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c= +cloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8= +cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4= +cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc= +cloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ= +cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg= +cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM= +cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28= +cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= +cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA= +cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk= +cloud.google.com/go/trace v1.11.2 h1:4ZmaBdL8Ng/ajrgKqY5jfvzqMXbrDcBsUGXOT9aqTtI= +cloud.google.com/go/trace v1.11.2/go.mod h1:bn7OwXd4pd5rFuAnTrzBuoZ4ax2XQeG3qNgYmfCy0Io= +cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs= +cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg= +cloud.google.com/go/translate v1.5.0/go.mod h1:29YDSYveqqpA1CQFD7NQuP49xymq17RXNaUDdc0mNu0= +cloud.google.com/go/translate v1.6.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= +cloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= +cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk= +cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= +cloud.google.com/go/video v1.12.0/go.mod h1:MLQew95eTuaNDEGriQdcYn0dTwf9oWiA4uYebxM5kdg= +cloud.google.com/go/video v1.13.0/go.mod h1:ulzkYlYgCp15N2AokzKjy7MQ9ejuynOJdf1tR5lGthk= +cloud.google.com/go/video v1.14.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= +cloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= +cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M= +cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU= +cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU= cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= +cloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY= +cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E= +cloud.google.com/go/vision/v2 v2.6.0/go.mod h1:158Hes0MvOS9Z/bDMSFpjwsUrZ5fPrdwuyyvKSGAGMY= +cloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0= +cloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE= +cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g= +cloud.google.com/go/vmmigration v1.5.0/go.mod h1:E4YQ8q7/4W9gobHjQg4JJSgXXSgY21nA5r8swQV+Xxc= +cloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY= +cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208= +cloud.google.com/go/vmwareengine v0.2.2/go.mod h1:sKdctNJxb3KLZkE/6Oui94iw/xs9PRNC2wnNLXsHvH8= +cloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY= +cloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w= +cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8= +cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= +cloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc= +cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A= +cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= +cloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo= +cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ= +cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= +cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= +cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= +git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 h1:59MxjQVfjXsBpLy+dbd2/ELV5ofnUkUZBvWSC85sheA= github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0/go.mod h1:OahwfttHWG6eJ0clwcfBAHoDI6X/LV/15hx/wlMZSrU= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/CycloneDX/cyclonedx-go v0.8.0 h1:FyWVj6x6hoJrui5uRQdYZcSievw3Z32Z88uYzG/0D6M= -github.com/CycloneDX/cyclonedx-go v0.8.0/go.mod h1:K2bA+324+Og0X84fA8HhN2X066K7Bxz4rpMQ4ZhjtSk= +github.com/CycloneDX/cyclonedx-go v0.9.2 h1:688QHn2X/5nRezKe2ueIVCt+NRqf7fl3AVQk+vaFcIo= +github.com/CycloneDX/cyclonedx-go v0.9.2/go.mod h1:vcK6pKgO1WanCdd61qx4bFnSsDJQ6SbM2ZuMIgq86Jg= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= -github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= -github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= -github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= -github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= +github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 h1:UQ0AhxogsIRZDkElkblfnwjc3IaltCm2HUMvezQaL7s= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1/go.mod h1:jyqM3eLpJ3IbIFDTKVz2rF9T/xWGW0rIriGwnz8l9Tk= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1 h1:oTX4vsorBZo/Zdum6OKPA4o7544hm6smoRv1QjpTwGo= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1/go.mod h1:0wEl7vrAD8mehJyohS9HZy+WyEOaQO2mJx86Cvh93kM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 h1:8nn+rsCvTq9axyEh382S0PFLBeaFwNsT43IrPWzctRU= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1/go.mod h1:viRWSEhtMZqz1rhwmOVKkWl6SwmVowfL9O2YR5gI2PE= +github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= +github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/Microsoft/hcsshim v0.11.7 h1:vl/nj3Bar/CvJSYo7gIQPyRWc9f3c6IeSNavBTSZNZQ= +github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= -github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= -github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/ProtonMail/go-crypto v1.2.0 h1:+PhXXn4SPGd+qk76TlEePBfOfivE0zkWFenhGhFLzWs= +github.com/ProtonMail/go-crypto v1.2.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= +github.com/STARRY-S/zip v0.2.1 h1:pWBd4tuSGm3wtpoqRZZ2EAwOmcHK6XFf7bU9qcJXyFg= +github.com/STARRY-S/zip v0.2.1/go.mod h1:xNvshLODWtC4EJ702g7cTYn13G53o1+X9BWnPFpcWV4= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/acobaugh/osrelease v0.1.0 h1:Yb59HQDGGNhCj4suHaFQQfBps5wyoKLSSX/J/+UifRE= github.com/acobaugh/osrelease v0.1.0/go.mod h1:4bFEs0MtgHNHBrmHCt67gNisnabCRAlzdVasCEGHTWY= -github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= -github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= +github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= +github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/anchore/clio v0.0.0-20231016125544-c98a83e1c7fc h1:A1KFO+zZZmbNlz1+WKsCF0RKVx6XRoxsAG3lrqH9hUQ= -github.com/anchore/clio v0.0.0-20231016125544-c98a83e1c7fc/go.mod h1:QeWvNzxsrUNxcs6haQo3OtISfXUXW0qAuiG4EQiz0GU= -github.com/anchore/fangs v0.0.0-20231201140849-5075d28d6d8b h1:L/djgY7ZbZ/38+wUtdkk398W3PIBJLkt1N8nU/7e47A= -github.com/anchore/fangs v0.0.0-20231201140849-5075d28d6d8b/go.mod h1:TLcE0RE5+8oIx2/NPWem/dq1DeaMoC+fPEH7hoSzPLo= -github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a h1:nJ2G8zWKASyVClGVgG7sfM5mwoZlZ2zYpIzN2OhjWkw= -github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a/go.mod h1:ubLFmlsv8/DFUQrZwY5syT5/8Er3ugSr4rDFwHsE3hg= +github.com/anchore/archiver/v3 v3.5.3-0.20241210171143-5b1d8d1c7c51 h1:yhk+P8lF3ZiROjmaVRao9WGTRo4b/wYjoKEiAHWrKwc= +github.com/anchore/archiver/v3 v3.5.3-0.20241210171143-5b1d8d1c7c51/go.mod h1:nwuGSd7aZp0rtYt79YggCGafz1RYsclE7pi3fhLwvuw= +github.com/anchore/clio v0.0.0-20250408180537-ec8fa27f0d9f h1:jTeN+fKTXz1VFo3Zj7Msnx//s5kD6Htd+SS0z9/o7Ss= +github.com/anchore/clio v0.0.0-20250408180537-ec8fa27f0d9f/go.mod h1:jQ+jv7v9RQnc5oA+Z0rAyXsQfaCAZHwY/CJZiLVggQ4= +github.com/anchore/fangs v0.0.0-20250402135612-96e29e45f3fe h1:qv/xxpjF5RdKPqZjx8RM0aBi3HUCAO0DhRBMs2xhY1I= +github.com/anchore/fangs v0.0.0-20250402135612-96e29e45f3fe/go.mod h1:vrcYMDps9YXwwx2a9AsvipM6Fi5H9//9bymGb8G8BIQ= +github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537 h1:GjNGuwK5jWjJMyVppBjYS54eOiiSNv4Ba869k4wh72Q= +github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537/go.mod h1:1aiktV46ATCkuVg0O573ZrH56BUawTECPETbZyBcqT8= +github.com/anchore/go-homedir v0.0.0-20250319154043-c29668562e4d h1:gT69osH9AsdpOfqxbRwtxcNnSZ1zg4aKy2BevO3ZBdc= +github.com/anchore/go-homedir v0.0.0-20250319154043-c29668562e4d/go.mod h1:PhSnuFYknwPZkOWKB1jXBNToChBA+l0FjwOxtViIc50= +github.com/anchore/go-logger v0.0.0-20250318195838-07ae343dd722 h1:2SqmFgE7h+Ql4VyBzhjLkRF/3gDrcpUBj8LjvvO6OOM= +github.com/anchore/go-logger v0.0.0-20250318195838-07ae343dd722/go.mod h1:oFuE8YuTCM+spgMXhePGzk3asS94yO9biUfDzVTFqNw= github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb h1:iDMnx6LIjtjZ46C0akqveX83WFzhpTD3eqOthawb5vU= github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb/go.mod h1:DmTY2Mfcv38hsHbG78xMiTDdxFtkHpgYNVDPsF2TgHk= -github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc= +github.com/anchore/go-rpmdb v0.0.0-20250516171929-f77691e1faec h1:SjjPMOXTzpuU1ZME4XeoHyek+dry3/C7I8gzaCo02eg= +github.com/anchore/go-rpmdb v0.0.0-20250516171929-f77691e1faec/go.mod h1:eQVa6QFGzKy0qMcnW2pez0XBczvgwSjw9vA23qifEyU= github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA= +github.com/anchore/go-struct-converter v0.0.0-20230627203149-c72ef8859ca9 h1:6COpXWpHbhWM1wgcQN95TdsmrLTba8KQfPgImBXzkjA= +github.com/anchore/go-struct-converter v0.0.0-20230627203149-c72ef8859ca9/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA= +github.com/anchore/go-sync v0.0.0-20250326131806-4eda43a485b6 h1:Ha+LSCVuXYSYGi7wIkJK6G8g6jI3LH7y6LbyEVyp4Io= +github.com/anchore/go-sync v0.0.0-20250326131806-4eda43a485b6/go.mod h1:+9oM3XUy8iea/vWj9FhZ9bQGUBN8JpPxxJm5Wbcx9XM= github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 h1:VzprUTpc0vW0nnNKJfJieyH/TZ9UYAnTZs5/gHTdAe8= 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.74.1 h1:dVJ1pqZ15i/UwsvxgSGvczDABA/fnPtzwhKwxOqFeoY= -github.com/anchore/grype v0.74.1/go.mod h1:p1xBrBFJ5YAQ32pZ0MSsntVEg0GzXjBgQmlVRrEGl2g= -github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501 h1:AV7qjwMcM4r8wFhJq3jLRztew3ywIyPTRapl2T1s9o8= -github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501/go.mod h1:Blo6OgJNiYF41ufcgHKkbCKF2MDOMlrqhXv/ij6ocR4= -github.com/anchore/stereoscope v0.0.0-20231220161148-590920dabc54 h1:i2YK5QEs9H2YB3B2zv+AGR44ves0nmAGOD07lMphH14= -github.com/anchore/stereoscope v0.0.0-20231220161148-590920dabc54/go.mod h1:IylG7ofLoUKHwS1XDF6rPhOmaE3GgpAgsMdvvYfooTU= -github.com/anchore/syft v0.101.0 h1:1CI6dHS8Kd1F4/LzJFRvaMTswZnJELBdWd9sIlHw5s4= -github.com/anchore/syft v0.101.0/go.mod h1:oRQuHODu6IYHhb9SDw9kmNhAmdZngVbTU1kR2jVq48E= +github.com/anchore/grype v0.92.1 h1:yTXyMQedr7Ixq/iZFki+Y3ldQMV+6f7dQ012vdAkQ+o= +github.com/anchore/grype v0.92.1/go.mod h1:dIvQEqek4Sf3yyRLZBBngHkWtFbTTJ2EgaXXEuIeqzM= +github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115 h1:ZyRCmiEjnoGJZ1+Ah0ZZ/mKKqNhGcUZBl0s7PTTDzvY= +github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115/go.mod h1:KoYIv7tdP5+CC9VGkeZV4/vGCKsY55VvoG+5dadg4YI= +github.com/anchore/stereoscope v0.1.4 h1:e+iT9UdUzLBabWGe84hn5sTHDRioY+4IHsVzJXuJlek= +github.com/anchore/stereoscope v0.1.4/go.mod h1:omWgXDEp/XfqCJlZXIByEo1c3ArZg/qTJ5LBKVLAIdw= +github.com/anchore/syft v1.25.1 h1:HaG5/0r1UdZ7zyscEFeFz0pQsBLTXdCgEDXa5LqFjcg= +github.com/anchore/syft v1.25.1/go.mod h1:xa15pYmHrXKe7IlvaO+EAD/krawWYUtILTpMcL/S+Gw= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= -github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 h1:8PmGpDEZl9yDpcdEr6Odf23feCxK3LNUNMxjXg41pZQ= +github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 h1:vmXNl+HDfqqXgr0uY1UgK1GAhps8nbAAtqHNBcgyf+4= -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/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= +github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= +github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= +github.com/aquasecurity/go-pep440-version v0.0.1 h1:8VKKQtH2aV61+0hovZS3T//rUF+6GDn18paFTVS0h0M= +github.com/aquasecurity/go-pep440-version v0.0.1/go.mod h1:3naPe+Bp6wi3n4l5iBFCZgS0JG8vY6FT0H4NGhFJ+i4= +github.com/aquasecurity/go-version v0.0.1 h1:4cNl516agK0TCn5F7mmYN+xVs1E3S45LkgZk3cbaW2E= +github.com/aquasecurity/go-version v0.0.1/go.mod h1:s1UU6/v2hctXcOa3OLwfj5d9yoXHa3ahf+ipSwEvGT0= +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= @@ -283,74 +753,112 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef h1:TSFnfbbu2oAOuWbeDDTtwXWE6z+PmpgbSsMBeV7l0ww= +github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef/go.mod h1:9iglf1GG4oNRJ39bZ5AZrjgAFD2RwQbXw6Qf7Cs47wo= +github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= +github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= github.com/bmatcuk/doublestar/v2 v2.0.4 h1:6I6oUiT/sU27eE2OFcWqBhL1SwjyvQuOssxT4a1yidI= github.com/bmatcuk/doublestar/v2 v2.0.4/go.mod h1:QMmcs3H2AUQICWhfzLXz+IYln8lRQmTZRptLie8RgRw= -github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= -github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38= +github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU= +github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs= +github.com/bodgit/sevenzip v1.6.0 h1:a4R0Wu6/P1o1pP/3VV++aEOcyeBxeO/xE2Y9NSTrr6A= +github.com/bodgit/sevenzip v1.6.0/go.mod h1:zOBh9nJUof7tcrlqJFv1koWRrhz3LbDbUNngkuZxLMc= +github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4= +github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM= +github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= -github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/charmbracelet/bubbles v0.17.1 h1:0SIyjOnkrsfDo88YvPgAWvZMwXe26TP6drRvmkjyUu4= -github.com/charmbracelet/bubbles v0.17.1/go.mod h1:9HxZWlkCqz2PRwsCbYl7a3KXvGzFaDHpYbSYMJ+nE3o= -github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM= -github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg= -github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg= -github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I= -github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs= +github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg= +github.com/charmbracelet/bubbletea v1.3.5 h1:JAMNLTbqMOhSwoELIr0qyP4VidFq72/6E9j7HHmRKQc= +github.com/charmbracelet/bubbletea v1.3.5/go.mod h1:TkCnmH+aBd4LrXhXcqrKiYwRs7qyQx5rBgH5fVY3v54= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= +github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= +github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= +github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE= +github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q= +github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= +github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= +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/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= +github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs= +github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= +github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= -github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= -github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= +github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 h1:QVw89YDxXxEe+l8gU8ETbOasdwEV+avkR75ZzsVV9WI= +github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= -github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= -github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= -github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= -github.com/containerd/containerd v1.7.11 h1:lfGKw3eU35sjV0aG2eYZTiwFEY1pCzxdzicHP3SZILw= -github.com/containerd/containerd v1.7.11/go.mod h1:5UluHxHTX2rdvYuZ5OJTC5m/KJNs0Zs9wVoJm9zf5ZE= -github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= -github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= +github.com/containerd/containerd v1.7.27 h1:yFyEyojddO3MIGVER2xJLWoCIn+Up4GaHFquP7hsFII= +github.com/containerd/containerd v1.7.27/go.mod h1:xZmPnl75Vc+BLGt4MIfu6bp+fy03gdHAn9bz+FreFR0= +github.com/containerd/containerd/api v1.8.0 h1:hVTNJKR8fMc/2Tiw60ZRijntNMd1U+JVMyTRdsD2bS0= +github.com/containerd/containerd/api v1.8.0/go.mod h1:dFv4lt6S20wTu/hMcP4350RL87qPWLVa/OHOwmmdnYc= +github.com/containerd/continuity v0.4.4 h1:/fNVfTJ7wIl/YPMHjf+5H32uFhl63JucB34PlCpMKII= +github.com/containerd/continuity v0.4.4/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= +github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= +github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY= github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k= -github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o= -github.com/containerd/ttrpc v1.2.2 h1:9vqZr0pxwOF5koz6N0N3kJ0zDHokrcPxIR/ZR2YFtOs= -github.com/containerd/ttrpc v1.2.2/go.mod h1:sIT6l32Ph/H9cvnJsfXM5drIVzTr5A2flTf1G5tYZak= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8= +github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU= +github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ= +github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= github.com/containerd/typeurl/v2 v2.1.1 h1:3Q4Pt7i8nYwy2KmQWIw2+1hTvwTE/6w9FqcttATPO/4= github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= -github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= -github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= +github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +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= @@ -358,32 +866,33 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da h1:ZOjWpVsFZ06eIhnh4mkaceTiVoktdU67+M7KDHJ268M= github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da/go.mod h1:B3tI9iGHi4imdLi4Asdha1Sc6feLMTfPLXh9IUYmysk= github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4= -github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v24.0.0+incompatible h1:0+1VshNwBQzQAx9lOl+OYCTCEAD8fKs/qeXMx3O0wqM= -github.com/docker/cli v24.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/cli v28.1.1+incompatible h1:eyUemzeI45DY7eDPuwUcmDyDj1pM98oD5MdSpiItp8k= +github.com/docker/cli v28.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= -github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= -github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I= +github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= +github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY= -github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4= +github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= -github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= -github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= -github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= +github.com/elliotchance/phpserialize v1.4.0 h1:cAp/9+KSnEbUC8oYCE32n2n84BeW8HOY3HMDI8hG2OY= +github.com/elliotchance/phpserialize v1.4.0/go.mod h1:gt7XX9+ETUcLXbtTKEuyrqW3lcLUAeS/AnGZ2e49TZs= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -396,8 +905,19 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= +github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q= +github.com/envoyproxy/go-control-plane v0.13.1 h1:vPfJZCkob6yTMEgS+0TwfTUfbHjfy/6vOJ8hUWX/uXE= +github.com/envoyproxy/go-control-plane v0.13.1/go.mod h1:X45hY0mufo6Fd0KW3rqsGvQMw58jvjymeCzBU3mWyHw= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= +github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= +github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= +github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= +github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= +github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/facebookincubator/flog v0.0.0-20190930132826-d2511d0ce33c/go.mod h1:QGzNH9ujQ2ZUr/CjDGZGWeDAVStrWNjHeEcjJL96Nuk= github.com/facebookincubator/nvdtools v0.1.5 h1:jbmDT1nd6+k+rlvKhnkgMokrCAzHoASWE5LtHbX2qFQ= github.com/facebookincubator/nvdtools v0.1.5/go.mod h1:Kh55SAWnjckS96TBSrXI99KrEKH4iB0OJby3N8GRJO4= @@ -406,64 +926,94 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/set v0.2.1 h1:nn2CaJyknWE/6txyUDGwysr3G5QC6xWB/PtVjPBbeaA= github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI= -github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= -github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= -github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY= +github.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= -github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= +github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/github/go-spdx/v2 v2.2.0 h1:yBBLMasHA70Ujd35OpL/OjJOWWVNXcJGbars0GinGRI= -github.com/github/go-spdx/v2 v2.2.0/go.mod h1:hMCrsFgT0QnCwn7G8gxy/MxMpy67WgZrwFeISTn0o6w= -github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= -github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= -github.com/glebarez/sqlite v1.10.0 h1:u4gt8y7OND/cCei/NMHmfbLxF6xP2wgKcT/BJf2pYkc= -github.com/glebarez/sqlite v1.10.0/go.mod h1:IJ+lfSOmiekhQsFTJRx/lHtGYmCdtAiTaf5wI9u5uHA= -github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= -github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= +github.com/github/go-spdx/v2 v2.3.3 h1:QI7evnHWEfWkT54eJwkoV/f3a0xD3gLlnVmT5wQG6LE= +github.com/github/go-spdx/v2 v2.3.3/go.mod h1:2ZxKsOhvBp+OYBDlsGnUMcchLeo2mrpEBn2L1C+U3IQ= +github.com/gkampitakis/ciinfo v0.3.1 h1:lzjbemlGI4Q+XimPg64ss89x8Mf3xihJqy/0Mgagapo= +github.com/gkampitakis/ciinfo v0.3.1/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= +github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= +github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= +github.com/gkampitakis/go-snaps v0.5.11 h1:LFG0ggUKR+KEiiaOvFCmLgJ5NO2zf93AxxddkBn3LdQ= +github.com/gkampitakis/go-snaps v0.5.11/go.mod h1:PcKmy8q5Se7p48ywpogN5Td13reipz1Iivah4wrTIvY= +github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= +github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= +github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= +github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= +github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= +github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= -github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= +github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= -github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= +github.com/go-git/go-git/v5 v5.16.0 h1:k3kuOEpkc0DeY7xlL6NaaNg39xdgQbtH5mwCafHO9AQ= +github.com/go-git/go-git/v5 v5.16.0/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= +github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= +github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-restruct/restruct v1.2.0-alpha h1:2Lp474S/9660+SJjpVxoKuWX09JsXHSrdV7Nv3/gkvc= github.com/go-restruct/restruct v1.2.0-alpha/go.mod h1:KqrpKpn4M8OLznErihXTGLlsXFGeLxHUrLRRI/1YjGk= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= -github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= +github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= +github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= +github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-yaml v1.15.13 h1:Xd87Yddmr2rC1SLLTm2MNDcTjeO/GYo0JGiww6gSTDg= +github.com/goccy/go-yaml v1.15.13/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/gohugoio/hashstructure v0.5.0 h1:G2fjSBU36RdwEJBWJ+919ERvOVqAg9tfcYp47K9swqg= +github.com/gohugoio/hashstructure v0.5.0/go.mod h1:Ser0TniXuu/eauYmrwM4o64EBvySxNzITEOLlm4igec= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -490,14 +1040,15 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -513,10 +1064,11 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-containerregistry v0.17.0 h1:5p+zYs/R4VGHkhyvgWurWrpJ2hW4Vv9fQI+GzdcwXLk= -github.com/google/go-containerregistry v0.17.0/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-containerregistry v0.20.3 h1:oNx7IdTI936V8CQRveCjaxOiegWwvM7kqkbXTpyiovI= +github.com/google/go-containerregistry v0.20.3/go.mod h1:w00pIgBRDVUDFM6bq+Qx8lwNWK+cxgCuX1vd3PIBDNI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/licensecheck v0.3.1 h1:QoxgoDkaeC4nFrtGN1jV7IPmDCHFNIVh54e5hSt6sPs= github.com/google/licensecheck v0.3.1/go.mod h1:ORkR35t/JjW+emNKtfJDII0zlciG9JgbT7SmsohlHmY= @@ -525,8 +1077,11 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= +github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= +github.com/google/osv-scanner v1.9.2 h1:N5Arl9SA75afbjmX8mKURgOIaKyuK3NUjCaxDlj1KHI= +github.com/google/osv-scanner v1.9.2/go.mod h1:ZTL8Dp9z/7Jr9kkQSOGqo8z6Csqt83qMIr58aZVx+pM= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -536,28 +1091,32 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= -github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= -github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= -github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= +github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= -github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= -github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= -github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= +github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= @@ -567,13 +1126,21 @@ github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99 github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= -github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= -github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= +github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= +github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b h1:wDUNC2eKiL35DbLvsDhiblTUXHxcOPwQSCzi7xpQUN4= github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b/go.mod h1:VzxiSdG6j1pi7rwGm/xYI5RbtpBgM8sARDXlvEvxlu0= github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= @@ -585,8 +1152,8 @@ github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-getter v1.7.3 h1:bN2+Fw9XPFvOCjB0UOevFIMICZ7G2XSQHzfvLUyOM5E= -github.com/hashicorp/go-getter v1.7.3/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= +github.com/hashicorp/go-getter v1.7.8 h1:mshVHx1Fto0/MydBekWan5zUipGq7jO0novchgMmSiY= +github.com/hashicorp/go-getter v1.7.8/go.mod h1:2c6CboOEb9jG6YvmC9xdD+tyAFsrUaJPedwXDGr0TM4= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -604,13 +1171,17 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos= +github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= @@ -618,12 +1189,15 @@ github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= +github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -639,6 +1213,7 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -646,47 +1221,57 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953 h1:WdAeg/imY2JFPc/9CST4bZ80nNJbiBFCAdSZCSgrS5Y= github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953/go.mod h1:6o+UrvuZWc4UTyBhQf0LGjW9Ld7qJxLz/OqvSOWWlEc= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= -github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= +github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f h1:GvCU5GXhHq+7LeOzx/haG7HSIZokl3/0GkoUFzsRJjg= github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f/go.mod h1:q59u9px8b7UTj0nIjEjvmTWekazka6xIt6Uogz5Dm+8= github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d h1:X4cedH4Kn3JPupAwwWuo4AzYp16P0OyLO9d7OnMZc/c= github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d/go.mod h1:o8sgWoz3JADecfc/cTYD92/Et1yMqMy0utV1z+VaZao= -github.com/knqyf263/go-rpmdb v0.0.0-20230301153543-ba94b245509b h1:boYyvL3tbUuKcMN029mpCl7oYYJ7yIXujLj+fiW4Alc= -github.com/knqyf263/go-rpmdb v0.0.0-20230301153543-ba94b245509b/go.mod h1:9LQcoMCMQ9vrF7HcDtXfvqGO4+ddxFQ8+YF/0CVGDww= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 h1:bqDmpDG49ZRnB5PcgP0RXtQvnMSgIF14M7CBd2shtXs= -github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= +github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= +github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= +github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= +github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= +github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08 h1:AevUBW4cc99rAF8q8vmddIP8qd/0J5s/UyltGbp66dg= github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08/go.mod h1:JOkBRrE1HvgTyjk6diFtNGgr8XJMtIfiBzkL5krqzVk= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -694,55 +1279,70 @@ github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= -github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 h1:P8UmIzZMYDR+NGImiFvErt6VWfIRPuGM+vyjiEdkmIw= 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.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +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/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= -github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo= -github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= -github.com/microsoft/go-rustaudit v0.0.0-20220730194248-4b17361d90a5 h1:tQRHcLQwnwrPq2j2Qra/NnyjyESBGwdeBeVdAE9kXYg= -github.com/microsoft/go-rustaudit v0.0.0-20220730194248-4b17361d90a5/go.mod h1:vYT9HE7WCvL64iVeZylKmCsWKfE+JZ8105iuh2Trk8g= +github.com/mholt/archives v0.1.2 h1:UBSe5NfYKHI1sy+S5dJsEsG9jsKKk8NJA4HCC+xTI4A= +github.com/mholt/archives v0.1.2/go.mod h1:D7QzTHgw3ctfS6wgOO9dN+MFgdZpbksGCxprUOwZWDs= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= +github.com/minio/minlz v1.0.0 h1:Kj7aJZ1//LlTP1DM8Jm7lNKvvJS2m74gyyXXn3+uJWQ= +github.com/minio/minlz v1.0.0/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= -github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= -github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= -github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= -github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= -github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= -github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= -github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= +github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= +github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= +github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= +github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= +github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/sys/signal v0.7.0 h1:25RW3d5TnQEoKvRbEKUGay6DCQ46IxAVTT9CUMgmsSI= github.com/moby/sys/signal v0.7.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= +github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= +github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -752,32 +1352,42 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= -github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 h1:kMlmsLSbjkikxQJ1IPwaM+7LJ9ltFu/fi8CRzvSnQmA= -github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= -github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= -github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= -github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= -github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ= -github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= -github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= -github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/nix-community/go-nix v0.0.0-20250101154619-4bdde671e0a1 h1:kpt9ZfKcm+EDG4s40hMwE//d5SBgDjUOrITReV2u4aA= +github.com/nix-community/go-nix v0.0.0-20250101154619-4bdde671e0a1/go.mod h1:qgCw4bBKZX8qMgGeEZzGFVT3notl42dBjNqO2jut0M0= +github.com/nsf/jsondiff v0.0.0-20210926074059-1e845ec5d249 h1:NHrXEjTNQY7P0Zfx1aMrNhpgxHmow66XQtm0aQLY0AE= +github.com/nsf/jsondiff v0.0.0-20210926074059-1e845ec5d249/go.mod h1:mpRZBD8SJ55OIICQ3iWH0Yz3cjzA61JdqMLoWXeB2+8= +github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= +github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= +github.com/nwaples/rardecode/v2 v2.1.0 h1:JQl9ZoBPDy+nIZGb1mx8+anfHp/LV3NE2MjMiv0ct/U= +github.com/nwaples/rardecode/v2 v2.1.0/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0-rc3 h1:fzg1mXZFj8YdPeNkRXMg+zb88BFV0Ys52cJydRwBkb8= -github.com/opencontainers/image-spec v1.1.0-rc3/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= -github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs= -github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= -github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.1.0-rc.1 h1:wHa9jroFfKGQqFHj0I1fMRKLl0pfj+ynAqBxo3v6u9w= -github.com/opencontainers/runtime-spec v1.1.0-rc.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg= +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/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= +github.com/package-url/packageurl-go v0.1.3 h1:4juMED3hHiz0set3Vq3KeQ75KD1avthoXLtmE3I0PLs= +github.com/package-url/packageurl-go v0.1.3/go.mod h1:nKAWB8E6uk1MHqiS/lQb9pYBGH2+mdJ2PJc2s50dQY0= +github.com/pandatix/go-cvss v0.6.2 h1:TFiHlzUkT67s6UkelHmK6s1INKVUG7nlKYiWWDTITGI= +github.com/pandatix/go-cvss v0.6.2/go.mod h1:jDXYlQBZrc8nvrMUVVvTG8PhmuShOnKrxP53nOFkt8Q= 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= @@ -785,13 +1395,17 @@ github.com/pborman/indent v1.2.1/go.mod h1:FitS+t35kIYtB5xWTZAPhnmrxcciEEOdbyrrp github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= -github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= -github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= +github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= -github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= +github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -799,6 +1413,9 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA= github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -811,88 +1428,99 @@ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1: github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= -github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= +github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 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.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +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= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/rust-secure-code/go-rustaudit v0.0.0-20250226111315-e20ec32e963c h1:8gOLsYwaY2JwlTMT4brS5/9XJdrdIbmk2obvQ748CC0= +github.com/rust-secure-code/go-rustaudit v0.0.0-20250226111315-e20ec32e963c/go.mod h1:kwM/7r/rVluTE8qJbHAffduuqmSv4knVQT2IajGvSiA= +github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= +github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= +github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/saferwall/pe v1.4.8 h1:ey/L8FGBMrJ1Xh+Rltj1MAFPZ4LOQYGJqNa5B1Na6B0= -github.com/saferwall/pe v1.4.8/go.mod h1:SNzv3cdgk8SBI0UwHfyTcdjawfdnN+nbydnEL7GZ25s= github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= -github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= -github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= -github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= -github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f h1:MvTmaQdww/z0Q4wrYjDSCcZ78NoftLQyHBSLW/Cx79Y= -github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= +github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k= +github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk= +github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= +github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA= github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= -github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= -github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= -github.com/sassoftware/go-rpmutils v0.2.0 h1:pKW0HDYMFWQ5b4JQPiI3WI12hGsVoW0V8+GMoZiI/JE= -github.com/sassoftware/go-rpmutils v0.2.0/go.mod h1:TJJQYtLe/BeEmEjelI3b7xNZjzAukEkeWKmoakvaOoI= +github.com/sanity-io/litter v1.5.8 h1:uM/2lKrWdGbRXDrIq08Lh9XtVYoeGtcQxk9rtQ7+rYg= +github.com/sanity-io/litter v1.5.8/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= +github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg= +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.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y= -github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= -github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= +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= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= -github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= -github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= +github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= +github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= +github.com/sorairolake/lzip-go v0.3.5 h1:ms5Xri9o1JBIWvOFAorYtUNik6HI3HgBTkISiqu0Cwg= +github.com/sorairolake/lzip-go v0.3.5/go.mod h1:N0KYq5iWrMXI0ZEXKXaS9hCyOjZUQdBDEIbXfoUwbdk= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM= -github.com/spdx/tools-golang v0.5.3 h1:ialnHeEYUC4+hkm5vJm4qz2x+oEJbS0mAMFrNXdQraY= -github.com/spdx/tools-golang v0.5.3/go.mod h1:/ETOahiAo96Ob0/RAIBmFZw6XN0yTnyr/uFZm2NTMhI= +github.com/spdx/gordf v0.0.0-20221230105357-b735bd5aac89 h1:dArkMwZ7Mf2JiU8OfdmqIv8QaHT4oyifLIe1UhsF1SY= +github.com/spdx/gordf v0.0.0-20221230105357-b735bd5aac89/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM= +github.com/spdx/tools-golang v0.5.5 h1:61c0KLfAcNqAjlg6UNMdkwpMernhw3zVRwDZ2x9XOmk= +github.com/spdx/tools-golang v0.5.5/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYecciXgrw5vE= github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= +github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= -github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= -github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= +github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -902,38 +1530,43 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/sylabs/sif/v2 v2.11.5 h1:7ssPH3epSonsTrzbS1YxeJ9KuqAN7ISlSM61a7j/mQM= -github.com/sylabs/sif/v2 v2.11.5/go.mod h1:GBoZs9LU3e4yJH1dcZ3Akf/jsqYgy5SeguJQC+zd75Y= -github.com/sylabs/squashfs v0.6.1 h1:4hgvHnD9JGlYWwT0bPYNt9zaz23mAV3Js+VEgQoRGYQ= -github.com/sylabs/squashfs v0.6.1/go.mod h1:ZwpbPCj0ocIvMy2br6KZmix6Gzh6fsGQcCnydMF+Kx8= -github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/terminalstatic/go-xsd-validate v0.1.5 h1:RqpJnf6HGE2CB/lZB1A8BYguk8uRtcvYAPLCF15qguo= -github.com/terminalstatic/go-xsd-validate v0.1.5/go.mod h1:18lsvYFofBflqCrvo1umpABZ99+GneNTw2kEEc8UPJw= +github.com/sylabs/sif/v2 v2.21.1 h1:GZ0b5//AFAqJEChd8wHV/uSKx/l1iuGYwjR8nx+4wPI= +github.com/sylabs/sif/v2 v2.21.1/go.mod h1:YoqEGQnb5x/ItV653bawXHZJOXQaEWpGwHsSD3YePJI= +github.com/sylabs/squashfs v1.0.6 h1:PvJcDzxr+vIm2kH56mEMbaOzvGu79gK7P7IX+R7BDZI= +github.com/sylabs/squashfs v1.0.6/go.mod h1:DlDeUawVXLWAsSRa085Eo0ZenGzAB32JdAUFaB0LZfE= +github.com/terminalstatic/go-xsd-validate v0.1.6 h1:TenYeQ3eY631qNi1/cTmLH/s2slHPRKTTHT+XSHkepo= +github.com/terminalstatic/go-xsd-validate v0.1.6/go.mod h1:18lsvYFofBflqCrvo1umpABZ99+GneNTw2kEEc8UPJw= github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= +github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/umisama/go-cpe v0.0.0-20190323060751-cdd6c3c28a23 h1:+168JmE638t0OxroPRx7BUbkB91hF3GWS1OkvITgdT0= github.com/umisama/go-cpe v0.0.0-20190323060751-cdd6c3c28a23/go.mod h1:Jv/KoYWD3+46wW8r3pEwISwtgv5Q8NTfFto2wFRKvoA= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= -github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= -github.com/vbatts/go-mtree v0.5.3 h1:S/jYlfG8rZ+a0bhZd+RANXejy7M4Js8fq9U+XoWTd5w= -github.com/vbatts/go-mtree v0.5.3/go.mod h1:eXsdoPMdL2jcJx6HweWi9lYQxBsTp4lNhqqAjgkZUg8= -github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck= -github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY= +github.com/vbatts/go-mtree v0.5.4 h1:OMAb8jaCyiFA7zXj0Zc/oARcxBDBoeu2LizjB8BVJl0= +github.com/vbatts/go-mtree v0.5.4/go.mod h1:5GqJbVhm9BBiCc4K5uc/c42FPgXulHaQs4sFUEfIWMo= +github.com/vbatts/tar-split v0.11.6 h1:4SjTW5+PU11n6fZenf2IPoV8/tz3AaYHMWjf23envGs= +github.com/vbatts/tar-split v0.11.6/go.mod h1:dqKNtesIOr2j2Qv3W/cHjnvk9I8+G7oAkFDFN6TCBEI= github.com/vifraa/gopom v1.0.0 h1:L9XlKbyvid8PAIK8nr0lihMApJQg/12OBvMA28BcWh0= github.com/vifraa/gopom v1.0.0/go.mod h1:oPa1dcrGrtlO37WPDBm5SqHAT+wTgF8An1Q71Z6Vv4o= -github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651 h1:jIVmlAFIqV3d+DOxazTR9v+zgj8+VYuQBzPgBZvWBHA= github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651/go.mod h1:b26F2tHLqaoRQf8DywqzVaV1MQ9yvjb0OMcNl7Nxu20= github.com/wagoodman/go-progress v0.0.0-20230925121702-07e42b3cdba0 h1:0KGbf+0SMg+UFy4e1A/CPVvXn21f1qtWdeJwxZFoQG8= @@ -948,19 +1581,26 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= -github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= -github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zclconf/go-cty v1.14.0 h1:/Xrd39K7DXbHzlisFP9c4pHao4yyf+/Ug9LEz+Y/yhc= +github.com/zclconf/go-cty v1.14.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= -go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 h1:CCriYyAfq1Br1aIYettdHZTy8mBTIPo7We18TuO/bak= -go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -970,25 +1610,44 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q= -go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= -go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= -go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= -go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= -go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= -go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/detectors/gcp v1.31.0 h1:G1JQOreVrfhRkner+l4mrGxmfqYCAuy76asTDAo0xsA= +go.opentelemetry.io/contrib/detectors/gcp v1.31.0/go.mod h1:tzQL6E1l+iV44YFTkcAeNQqzXUiekSYP9jjJjXwEd00= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= +go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= +go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 h1:wpMfgF8E1rkrT1Z6meFh1NDtownE9Ii3n3X2GJYjsaU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0/go.mod h1:wAy0T/dUbs468uOlkT31xjvqQgEVXv58BRFWEgn5v/0= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I= +go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ= +go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M= +go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM= +go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM= +go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= +go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= +go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= +go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= -go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= -go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc= +go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -998,28 +1657,47 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1044,10 +1722,16 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1090,6 +1774,7 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -1101,13 +1786,23 @@ golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1132,10 +1827,14 @@ golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7Lm golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= -golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= -golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= +golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= +golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= +golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= +golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1149,10 +1848,15 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/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.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 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= @@ -1166,7 +1870,6 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1174,7 +1877,6 @@ golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1200,13 +1902,16 @@ golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1216,12 +1921,11 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1229,6 +1933,7 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1241,23 +1946,38 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.4.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.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= 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.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= 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.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= -golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= 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= @@ -1267,18 +1987,31 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -1292,8 +2025,8 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1322,20 +2055,27 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= +golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1343,8 +2083,17 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= +gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= +gonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -1394,16 +2143,24 @@ google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaE google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08= google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= -google.golang.org/api v0.153.0 h1:N1AwGhielyKFaUqH07/ZSIQR3uNPcV7NVw0vj+j4iR4= -google.golang.org/api v0.153.0/go.mod h1:3qNJX5eOmhiWYc67jRA/3GsDw97UFb5ivv7Y2PrriAY= +google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= +google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= +google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= +google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= +google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= +google.golang.org/api v0.215.0 h1:jdYF4qnyczlEz2ReWIsosNLDuzXyvFHJtI5gcr0J7t0= +google.golang.org/api v0.215.0/go.mod h1:fta3CVtuJYOEdugLNWm6WodzOS8KdFckABwN4I40hzY= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -1440,7 +2197,9 @@ google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -1477,6 +2236,7 @@ google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2 google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= @@ -1509,13 +2269,41 @@ google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53B google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= -google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= -google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= -google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo= -google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= +google.golang.org/genproto v0.0.0-20221024153911-1573dae28c9c/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= +google.golang.org/genproto v0.0.0-20221109142239-94d6d90a7d66/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221201204527-e3fa12d562f3/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE= +google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230112194545-e10362b5ecf9/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230127162408-596548ed4efa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA= +google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA= +google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1552,8 +2340,13 @@ google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= -google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= +google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1570,8 +2363,11 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= +google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1581,8 +2377,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1597,8 +2391,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= -gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.26.1 h1:ghB2gUI9FkS46luZtn6DLZ0f6ooBJ5IbVej2ENFDjRw= +gorm.io/gorm v1.26.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= gotest.tools/v3 v3.1.0 h1:rVV8Tcg/8jHUkPUorwjaMTtemIMVXfIPKiOqnhEhakk= gotest.tools/v3 v3.1.0/go.mod h1:fHy7eyTmJFO5bQbUsEGQ1v4m2J3Jz9eWL54TP2/ZuYQ= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1608,15 +2402,67 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -modernc.org/libc v1.29.0 h1:tTFRFq69YKCF2QyGNuRUQxKBm1uZZLubf6Cjh/pVHXs= -modernc.org/libc v1.29.0/go.mod h1:DaG/4Q3LRRdqpiLyP0C2m1B8ZMGkQ+cCgOIjEtQlYhQ= -modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= -modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= -modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= -modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= -modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ= -modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= +honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v4 v4.25.2 h1:T2oH7sZdGvTaie0BRNFbIYsabzCxUQg8nLqCdQ2i0ic= +modernc.org/cc/v4 v4.25.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= +modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= +modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= +modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= +modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= +modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= +modernc.org/ccgo/v4 v4.25.1 h1:TFSzPrAGmDsdnhT9X2UrcPMI3N/mJ9/X9ykKXwLhDsU= +modernc.org/ccgo/v4 v4.25.1/go.mod h1:njjuAYiPflywOOrm3B7kCB444ONP5pAVr8PIEoE0uDw= +modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= +modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= +modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= +modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= +modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU= +modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= +modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= +modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= +modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= +modernc.org/libc v1.62.1 h1:s0+fv5E3FymN8eJVmnk0llBe6rOxCu/DEU+XygRbS8s= +modernc.org/libc v1.62.1/go.mod h1:iXhATfJQLjG3NWy56a6WVU73lWOcdYVxsvwCgoPljuo= +modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= +modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= +modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/memory v1.9.1 h1:V/Z1solwAVmMW1yttq3nDdZPJqV1rM05Ccq6KMSZ34g= +modernc.org/memory v1.9.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= +modernc.org/sqlite v1.37.0 h1:s1TMe7T3Q3ovQiK2Ouz4Jwh7dw4ZDqbebSDTlSJdfjI= +modernc.org/sqlite v1.37.0/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM= +modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= +modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= +modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/internal/tar/tar.go b/internal/tar/tar.go deleted file mode 100644 index a4786f50..00000000 --- a/internal/tar/tar.go +++ /dev/null @@ -1,83 +0,0 @@ -package tar - -import ( - "archive/tar" - "compress/gzip" - "fmt" - "io" - "os" - "strings" - - "github.com/klauspost/compress/zstd" -) - -// Populate creates a gzipped tar from the given paths. -func Populate(tarPath string, filePaths ...string) error { - f, err := os.Create(tarPath) - if err != nil { - return fmt.Errorf("unable to create tar (%s): %w", tarPath, err) - } - defer f.Close() - - var compressionWriter io.WriteCloser - switch { - case strings.HasSuffix(tarPath, ".tar.gz"): - compressionWriter = gzip.NewWriter(f) - case strings.HasSuffix(tarPath, ".tar.zst"): - // adding zstd.WithWindowSize(zstd.MaxWindowSize), zstd.WithAllLitEntropyCompression(true) - // will have slightly better results, but use a lot more memory - compressionWriter, err = zstd.NewWriter(f, zstd.WithEncoderLevel(zstd.SpeedBestCompression)) - if err != nil { - return fmt.Errorf("unable to get compression stream: %w", err) - } - default: - return fmt.Errorf("archive name has an unsupported suffix: %q", tarPath) - } - - defer compressionWriter.Close() - - tarWriter := tar.NewWriter(compressionWriter) - defer tarWriter.Close() - - for _, filePath := range filePaths { - err := addFileToTarWriter(filePath, tarWriter) - if err != nil { - return fmt.Errorf("unable to add file to tar (file='%s'): %w", filePath, err) - } - } - - return nil -} - -// addFileToTarWriter takes a given filepath and saves the content to the given tar.Writer. -func addFileToTarWriter(filePath string, tarWriter *tar.Writer) error { - f, err := os.Open(filePath) - if err != nil { - return fmt.Errorf("unable to open file (%s): %w", filePath, err) - } - defer f.Close() - - stat, err := f.Stat() - if err != nil { - return fmt.Errorf("unable to get stat for file (%s): %w", filePath, err) - } - - header := &tar.Header{ - Name: filePath, - Size: stat.Size(), - Mode: int64(stat.Mode()), - ModTime: stat.ModTime(), - } - - err = tarWriter.WriteHeader(header) - if err != nil { - return fmt.Errorf("unable to write header for file (%s): %w", filePath, err) - } - - _, err = io.Copy(tarWriter, f) - if err != nil { - return fmt.Errorf("unable to copy data to the tar (file='%s'): %w", filePath, err) - } - - return nil -} diff --git a/internal/tarutil/file_entry.go b/internal/tarutil/file_entry.go new file mode 100644 index 00000000..c7778b69 --- /dev/null +++ b/internal/tarutil/file_entry.go @@ -0,0 +1,37 @@ +package tarutil + +import ( + "fmt" + "io" + "os" +) + +var _ Entry = (*FileEntry)(nil) + +type FileEntry struct { + Path string +} + +func NewEntryFromFilePath(path string) Entry { + return FileEntry{ + Path: path, + } +} + +func NewEntryFromFilePaths(paths ...string) []Entry { + var entries []Entry + for _, path := range paths { + entries = append(entries, NewEntryFromFilePath(path)) + } + return entries +} + +func (t FileEntry) writeEntry(tw lowLevelWriter) error { + fi, err := os.Lstat(t.Path) + if err != nil { + return fmt.Errorf("unable to stat file %q: %w", t.Path, err) + } + return writeEntry(tw, t.Path, fi, func() (io.Reader, error) { + return os.Open(t.Path) + }) +} diff --git a/internal/tarutil/file_entry_test.go b/internal/tarutil/file_entry_test.go new file mode 100644 index 00000000..0ce7b739 --- /dev/null +++ b/internal/tarutil/file_entry_test.go @@ -0,0 +1,94 @@ +package tarutil + +import ( + "archive/tar" + "bytes" + "os" + "path/filepath" + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var _ lowLevelWriter = (*mockTarWriter)(nil) + +type mockTarWriter struct { + headers []*tar.Header + buffers []*bytes.Buffer + closeCalled bool + flushCalled bool + closeErr error + flushErr error +} + +func (m *mockTarWriter) Flush() error { + m.flushCalled = true + return m.flushErr +} + +func (m *mockTarWriter) Close() error { + m.closeCalled = true + return m.closeErr +} + +func (m *mockTarWriter) WriteHeader(header *tar.Header) error { + m.headers = append(m.headers, header) + m.buffers = append(m.buffers, &bytes.Buffer{}) + return nil +} + +func (m *mockTarWriter) Write(b []byte) (int, error) { + return m.buffers[len(m.buffers)-1].Write(b) +} + +func TestFileEntry_writeEntry(t *testing.T) { + testStr := "hello world" + tests := []struct { + name string + file func(t *testing.T) string + wantErr require.ErrorAssertionFunc + }{ + { + name: "valid file", + file: func(t *testing.T) string { + dir := t.TempDir() + dest := filepath.Join(dir, "file.txt") + require.NoError(t, os.WriteFile(dest, []byte(testStr), 0644)) + return dest + }, + }, + { + name: "invalid file", + file: func(t *testing.T) string { + return filepath.Join("/tmp/invalid/path", uuid.New().String()) + }, + wantErr: require.Error, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantErr == nil { + tt.wantErr = require.NoError + } + + expectedName := tt.file(t) + fe := NewEntryFromFilePath(expectedName) + tw := &mockTarWriter{} + + err := fe.writeEntry(tw) + tt.wantErr(t, err) + if err != nil { + return + } + + assert.NoError(t, err) + require.Len(t, tw.headers, 1) + assert.Equal(t, expectedName, tw.headers[0].Name) + assert.Equal(t, int64(len(testStr)), tw.headers[0].Size) + assert.Equal(t, testStr, tw.buffers[0].String()) + }) + } +} diff --git a/internal/tarutil/populate.go b/internal/tarutil/populate.go new file mode 100644 index 00000000..bb10cd15 --- /dev/null +++ b/internal/tarutil/populate.go @@ -0,0 +1,18 @@ +package tarutil + +// PopulateWithPaths creates a compressed tar from the given paths. +func PopulateWithPaths(tarPath string, filePaths ...string) error { + w, err := NewWriter(tarPath) + if err != nil { + return err + } + defer w.Close() + + for _, entry := range NewEntryFromFilePaths(filePaths...) { + if err := w.WriteEntry(entry); err != nil { + return err + } + } + + return nil +} diff --git a/internal/tarutil/populate_test.go b/internal/tarutil/populate_test.go new file mode 100644 index 00000000..f473e9b6 --- /dev/null +++ b/internal/tarutil/populate_test.go @@ -0,0 +1,77 @@ +package tarutil + +import ( + "archive/tar" + "compress/gzip" + "io" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/klauspost/compress/zstd" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPopulateWithPaths(t *testing.T) { + tests := []struct { + name string + tarPath string + wantErr bool + }{ + { + name: "plain tar", + tarPath: "foo.tar", + wantErr: false, + }, + { + name: "tar gz", + tarPath: "foo.tar.gz", + wantErr: false, + }, + { + name: "tar zst", + tarPath: "foo.tar.zst", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dir := t.TempDir() + tempPath := filepath.Join(dir, "some-path.txt") + f, err := os.Create(tempPath) + require.NoError(t, err) + _, err = f.Write([]byte("hello world\n")) + require.NoError(t, err) + archivePath := filepath.Join(dir, tt.tarPath) + err = PopulateWithPaths(archivePath, tempPath) + require.NoError(t, err) + + var r io.Reader + f, err = os.Open(archivePath) + require.NoError(t, err) + defer f.Close() + + switch { + case strings.HasSuffix(archivePath, ".tar.gz"): + r, err = gzip.NewReader(f) + require.NoError(t, err) + case strings.HasSuffix(archivePath, ".tar.zst"): + r, err = zstd.NewReader(f) + require.NoError(t, err) + case strings.HasSuffix(archivePath, ".tar"): + r = f + default: + t.Fatalf("unsupported archive type: %s", archivePath) + } + + tr := tar.NewReader(r) + h, err := tr.Next() + require.NoError(t, err) + assert.Equal(t, h.Name, tempPath) + b, err := io.ReadAll(tr) + assert.Equal(t, []byte("hello world\n"), b) + }) + } +} diff --git a/internal/tarutil/reader_entry.go b/internal/tarutil/reader_entry.go new file mode 100644 index 00000000..4bf74b79 --- /dev/null +++ b/internal/tarutil/reader_entry.go @@ -0,0 +1,93 @@ +package tarutil + +import ( + "archive/tar" + "bytes" + "io" + "os" + + "github.com/anchore/grype-db/internal/log" +) + +var _ Entry = (*ReaderEntry)(nil) + +type ReaderEntry struct { + Reader io.Reader + Filename string + FileInfo os.FileInfo +} + +func NewEntryFromBytes(by []byte, filename string, fileInfo os.FileInfo) Entry { + return ReaderEntry{ + Reader: bytes.NewReader(by), + Filename: filename, + FileInfo: fileInfo, + } +} + +func (t ReaderEntry) writeEntry(tw lowLevelWriter) error { + log.WithFields("path", t.Filename).Trace("adding stream to archive") + return writeEntry(tw, t.Filename, t.FileInfo, func() (io.Reader, error) { + return t.Reader, nil + }) +} + +func writeEntry(tw lowLevelWriter, filename string, fileInfo os.FileInfo, opener func() (io.Reader, error)) error { + log.WithFields("path", filename).Trace("adding file to archive") + + header, err := tar.FileInfoHeader(fileInfo, "") + if err != nil { + return err + } + + header.Name = filename + switch fileInfo.Mode() & os.ModeType { + case os.ModeDir: + header.Size = 0 + err = tw.WriteHeader(header) + if err != nil { + return err + } + return nil + + case os.ModeSymlink: + linkTarget, err := os.Readlink(filename) + if err != nil { + return err + } + header.Linkname = linkTarget + header.Size = 0 + err = tw.WriteHeader(header) + if err != nil { + return err + } + return nil + + default: + reader, err := opener() + if err != nil { + return err + } + + contents, err := io.ReadAll(reader) + if err != nil { + return err + } + header.Size = int64(len(contents)) + + if err := tw.WriteHeader(header); err != nil { + return err + } + + if _, err := tw.Write(contents); err != nil { + return err + } + + // ensure proper alignment in the tar archive (padding with zeros) + if err := tw.Flush(); err != nil { + return err + } + } + + return nil +} diff --git a/internal/tarutil/reader_entry_test.go b/internal/tarutil/reader_entry_test.go new file mode 100644 index 00000000..56b7bcb2 --- /dev/null +++ b/internal/tarutil/reader_entry_test.go @@ -0,0 +1,143 @@ +package tarutil + +import ( + "archive/tar" + "io/fs" + "os" + "path/filepath" + "testing" + "time" + + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var _ lowLevelWriter = (*mockTarWriter)(nil) + +var _ os.FileInfo = (*mockFileInfo)(nil) + +type mockFileInfo struct { + name string + size int64 + mode fs.FileMode + modTime time.Time + isDir bool + sys any +} + +func (m mockFileInfo) Name() string { + return m.name +} + +func (m mockFileInfo) Size() int64 { + return m.size +} + +func (m mockFileInfo) Mode() fs.FileMode { + return m.mode +} + +func (m mockFileInfo) ModTime() time.Time { + return m.modTime +} + +func (m mockFileInfo) IsDir() bool { + return m.isDir +} + +func (m mockFileInfo) Sys() any { + return m.sys +} + +func TestReaderEntry_writeEntry(t *testing.T) { + d := t.TempDir() + file := filepath.Join(d, "file.txt") + require.NoError(t, os.WriteFile(file, []byte("hello world"), 0644)) + + link := filepath.Join(d, "link") + require.NoError(t, os.Symlink(file, link)) + + dir := filepath.Join(d, "dir") + require.NoError(t, os.Mkdir(dir, 0755)) + + tests := []struct { + name string + typeFlag byte + bytes []byte + filename string + fileinfo os.FileInfo + wantErr require.ErrorAssertionFunc + expectFlush bool + fs afero.Fs + }{ + { + name: "valid file", + typeFlag: tar.TypeReg, + bytes: []byte("hello world"), + filename: file, + expectFlush: true, + fileinfo: &mockFileInfo{ + name: file, + size: 11, + mode: 0644, + modTime: time.Now(), + isDir: false, + sys: nil, + }, + }, + { + name: "symlink", + typeFlag: tar.TypeSymlink, + bytes: nil, + filename: link, + expectFlush: false, + fileinfo: &mockFileInfo{ + name: link, + size: 0, + mode: os.ModeSymlink, + modTime: time.Now(), + isDir: false, + }, + }, + { + name: "directory", + typeFlag: tar.TypeDir, + bytes: nil, + filename: dir, + expectFlush: false, + fileinfo: &mockFileInfo{ + name: dir, + size: 0, + mode: os.ModeDir, + modTime: time.Now(), + isDir: true, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantErr == nil { + tt.wantErr = require.NoError + } + + fe := NewEntryFromBytes(tt.bytes, tt.filename, tt.fileinfo) + tw := &mockTarWriter{} + + err := fe.writeEntry(tw) + tt.wantErr(t, err) + if err != nil { + return + } + + assert.NoError(t, err) + require.Len(t, tw.headers, 1) + assert.Equal(t, tt.typeFlag, tw.headers[0].Typeflag) + assert.Equal(t, tt.filename, tw.headers[0].Name) + assert.Equal(t, int64(len(tt.bytes)), tw.headers[0].Size) + assert.Equal(t, string(tt.bytes), tw.buffers[0].String()) + assert.Equal(t, tt.expectFlush, tw.flushCalled) + }) + } +} diff --git a/internal/tarutil/tar.go b/internal/tarutil/tar.go new file mode 100644 index 00000000..ced2b437 --- /dev/null +++ b/internal/tarutil/tar.go @@ -0,0 +1,24 @@ +package tarutil + +import ( + "archive/tar" + "io" +) + +// Writer represents a facade for writing entries to a tar file. +type Writer interface { + WriteEntry(Entry) error + io.Closer +} + +// lowLevelWriter abstracts the *tar.Writer from the standard library. +type lowLevelWriter interface { + WriteHeader(*tar.Header) error + Flush() error + io.WriteCloser +} + +// Entry represents an entry that can be written to a tar file via a tar.Writer from the standard library. +type Entry interface { + writeEntry(writer lowLevelWriter) error +} diff --git a/internal/tarutil/writer.go b/internal/tarutil/writer.go new file mode 100644 index 00000000..a32f2a15 --- /dev/null +++ b/internal/tarutil/writer.go @@ -0,0 +1,154 @@ +package tarutil + +import ( + "archive/tar" + "bufio" + "compress/gzip" + "fmt" + "io" + "os" + "os/exec" + "strings" + + "github.com/google/shlex" + + "github.com/anchore/grype-db/internal/log" +) + +var ErrUnsupportedArchiveSuffix = fmt.Errorf("archive name has an unsupported suffix") + +var _ Writer = (*writer)(nil) + +type writer struct { + compressor io.WriteCloser + writer *tar.Writer +} + +// 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 { + return nil, err + } + + tw := tar.NewWriter(w) + + return &writer{ + compressor: w, + writer: tw, + }, nil +} + +func newCompressor(archivePath string) (io.WriteCloser, error) { + archive, err := os.Create(archivePath) + if err != nil { + return nil, err + } + + switch { + case strings.HasSuffix(archivePath, ".tar.gz"): + return gzip.NewWriter(archive), nil + case strings.HasSuffix(archivePath, ".tar.zst"): + // note: since we're using --ultra this tends to have a high memory usage at decompression time + // For ~700 MB payload that is compressing down to ~60 MB, that would need ~130 MB of memory (--ultra -22) + // for the same payload compressing down to ~65MB, that would need ~70MB of memory (--ultra -21) + return newShellCompressor("zstd -T0 -22 --ultra -c -vv", archive) + case strings.HasSuffix(archivePath, ".tar.xz"): + return newShellCompressor("xz -9 --threads=0 -c -vv", archive) + case strings.HasSuffix(archivePath, ".tar"): + return archive, nil + } + return nil, ErrUnsupportedArchiveSuffix +} + +// shellCompressor wraps the stdin pipe of an external compression process and ensures proper cleanup. +type shellCompressor struct { + cmd *exec.Cmd + pipe io.WriteCloser +} + +func newShellCompressor(c string, archive io.Writer) (*shellCompressor, error) { + args, err := shlex.Split(c) + if err != nil { + return nil, fmt.Errorf("unable to parse command: %w", err) + } + binary := args[0] + + binPath, err := exec.LookPath(binary) + if err != nil { + return nil, fmt.Errorf("unable to find binary %q: %w", binary, err) + } + if binPath == "" { + return nil, fmt.Errorf("unable to find binary %q in PATH", binary) + } + + args = args[1:] + cmd := exec.Command(binary, args...) + log.Debug(strings.Join(cmd.Args, " ")) + cmd.Stdout = archive + + stderrPipe, err := cmd.StderrPipe() + if err != nil { + return nil, fmt.Errorf("unable to create stderr pipe: %w", err) + } + + pipe, err := cmd.StdinPipe() + if err != nil { + return nil, fmt.Errorf("unable to create stdin pipe: %w", err) + } + + if err := cmd.Start(); err != nil { + return nil, fmt.Errorf("unable to start process: %w", err) + } + + go func() { + scanner := bufio.NewScanner(stderrPipe) + for scanner.Scan() { + log.Debugf("[%s] %s", binary, scanner.Text()) + } + if err := scanner.Err(); err != nil { + log.Errorf("[%s] error reading stderr: %v", binary, err) + } + }() + + return &shellCompressor{ + cmd: cmd, + pipe: pipe, + }, nil +} + +func (sc *shellCompressor) Write(p []byte) (int, error) { + return sc.pipe.Write(p) +} + +func (sc *shellCompressor) Close() error { + if err := sc.pipe.Close(); err != nil { + return fmt.Errorf("unable to close compression stdin pipe: %w", err) + } + if err := sc.cmd.Wait(); err != nil { + return fmt.Errorf("compression process error: %w", err) + } + return nil +} + +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 +} diff --git a/internal/tarutil/writer_test.go b/internal/tarutil/writer_test.go new file mode 100644 index 00000000..38319f99 --- /dev/null +++ b/internal/tarutil/writer_test.go @@ -0,0 +1,104 @@ +package tarutil + +import ( + "archive/tar" + "compress/gzip" + "io" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/klauspost/compress/zstd" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewWriter(t *testing.T) { + tests := []struct { + name string + archivePath string + wantErr bool + }{ + { + name: "tar.gz compressor", + archivePath: "test.tar.gz", + wantErr: false, + }, + { + name: "tar.zst compressor", + archivePath: "test.tar.zst", + wantErr: false, + }, + { + name: "tar compressor", + archivePath: "test.tar", + wantErr: false, + }, + { + name: "unsupported compressor", + archivePath: "test.txt", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dir := t.TempDir() + require.NoError(t, os.Chdir(dir)) + testFilePath := "testfile" + testString := "hello world" + require.NoError(t, os.WriteFile(testFilePath, []byte(testString), 0644)) + + archivePath := filepath.Join(dir, tt.archivePath) + + w, err := NewWriter(archivePath) + if tt.wantErr { + require.Error(t, err) + return + } else { + require.NoError(t, err) + } + + entry := NewEntryFromFilePath(testFilePath) + + err = w.WriteEntry(entry) + require.NoError(t, err) + + err = w.Close() + require.NoError(t, err) + + _, err = os.Stat(archivePath) + assert.NoError(t, err) + + var r io.Reader + f, err := os.Open(archivePath) + require.NoError(t, err) + defer f.Close() + + switch { + case strings.HasSuffix(archivePath, ".tar.gz"): + r, err = gzip.NewReader(f) + require.NoError(t, err) + case strings.HasSuffix(archivePath, ".tar.zst"): + r, err = zstd.NewReader(f) + require.NoError(t, err) + case strings.HasSuffix(archivePath, ".tar"): + r = f + default: + t.Fatalf("unsupported archive type: %s", archivePath) + } + + tr := tar.NewReader(r) + hdr, err := tr.Next() + require.NoError(t, err) + + assert.Equal(t, testFilePath, hdr.Name) + assert.Equal(t, int64(len(testString)), hdr.Size) + + content, err := io.ReadAll(tr) + require.NoError(t, err) + assert.Equal(t, testString, string(content)) + }) + } +} diff --git a/manager/.coveragerc b/manager/.coveragerc new file mode 100644 index 00000000..49c25a4e --- /dev/null +++ b/manager/.coveragerc @@ -0,0 +1,17 @@ +[run] +source = grype_db_manager +omit = + */__main__.py + */cli/__init__.py + */tests/* + */site-packages/* + */venv/* + */.venv/* + */.cache/* + +[report] +show_missing = true +fail_under = 50 + +[html] +directory = htmlcov diff --git a/manager/Makefile b/manager/Makefile index cf63e6d0..9c9a6336 100644 --- a/manager/Makefile +++ b/manager/Makefile @@ -1,70 +1,63 @@ # in percent -COVERAGE_THRESHOLD := 60 - .DEFAULT_GOAL := all .PHONY: all all: static-analysis test ## Run all validations .PHONY: static-analysis -static-analysis: format lint ## Run all static analyses +static-analysis: lint ## Run all static analyses .PHONY: test test: unit ## Run all tests -virtual-env-check: - @ if [ "${VIRTUAL_ENV}" = "" ]; then \ - echo "$(ERROR)Not in a virtual environment. Try running with 'poetry run' or enter a 'poetry shell' session.$(RESET)"; \ - exit 1; \ - fi - ## Bootstrapping targets ################################# .PHONY: bootstrap bootstrap: ## Install all dependencies $(call title,Bootstrapping dependencies) - poetry install + uv sync --all-extras --dev ## Static analysis targets ################################# .PHONY: lint -lint: virtual-env-check ## Show linting issues (ruff) - ruff check . +lint: ## Show linting issues (ruff) + uv run ruff format --check + uv run ruff check . .PHONY: lint-fix -lint-fix: virtual-env-check ## Fix linting issues (ruff) - ruff check . --fix +lint-fix: format ## Fix linting issues (ruff) + uv run ruff check . --fix .PHONY: format -format: virtual-env-check ## Format all code (black) - black src tests +format: ## Format (ruff) + uv run ruff format ## Testing targets ################################# .PHONY: unit -unit: virtual-env-check ## Run unit tests - pytest --cov-fail-under=$(COVERAGE_THRESHOLD) --config-file ../pyproject.toml --cov-report html --cov grype_db_manager -v tests/unit/ +unit: ## Run unit tests + uv run pytest --cov=grype_db_manager --cov-report=html -vv tests/unit/ .PHONY: cli -cli: virtual-env-check ## Run cli tests - cd tests/cli && make +cli: ## Run cli tests + cd tests/cli && uv run make ## DB Testing targets ################################# .PHONY: db-acceptance -db-acceptance: virtual-env-check ## Run DB acceptance tests +db-acceptance: ## Run DB acceptance tests @ echo "Building and testing DB schema=$(schema_version)" if [ -z "$(schema_version)" ]; then \ echo "schema_version is not set"; \ exit 1; \ fi - export DB_ID=$(shell grype-db-manager db build --schema-version $(schema_version)) - grype-db-manager db validaate $(DB_ID) + export DB_ID=$(shell uv run grype-db-manager db build --schema-version $(schema_version)) + uv run grype-db-manager db validaate $(DB_ID) ## Halp! ################################# diff --git a/manager/src/grype_db_manager/cli/cli.py b/manager/src/grype_db_manager/cli/cli.py index d74cd165..8a0cf839 100644 --- a/manager/src/grype_db_manager/cli/cli.py +++ b/manager/src/grype_db_manager/cli/cli.py @@ -11,17 +11,17 @@ from grype_db_manager.db.format import Format -@click.option("--verbose", "-v", default=False, help="show more verbose logging", count=True) +@click.option("--verbose", "-v", "verbosity", count=True, help="show details of all comparisons") @click.option("--config", "-c", "config_path", default=None, help="override config path") @click.group(help="A tool for publishing validated grype databases to S3 for distribution.") @click.version_option(package_name=package_name, message="%(prog)s %(version)s") @click.pass_context -def cli(ctx: click.core.Context, verbose: bool, config_path: str | None) -> None: +def cli(ctx: click.core.Context, verbosity: int, config_path: str | None) -> None: import logging.config import colorlog - ctx.obj = config.load(path=config_path) + ctx.obj = config.load(path=config_path, verbosity=verbosity) class DeltaTimeFormatter(colorlog.ColoredFormatter): def __init__(self, *args: Any, **kwargs: Any): @@ -35,9 +35,9 @@ def format(self, record: logging.LogRecord) -> str: # noqa: A003 return super().format(record) log_level = ctx.obj.log.level - if verbose == 1: + if verbosity == 1: log_level = "DEBUG" - elif verbose >= 2: + elif verbosity >= 2: log_level = "TRACE" logging.config.dictConfig( diff --git a/manager/src/grype_db_manager/cli/config.py b/manager/src/grype_db_manager/cli/config.py index b3fd2839..8de6495c 100644 --- a/manager/src/grype_db_manager/cli/config.py +++ b/manager/src/grype_db_manager/cli/config.py @@ -10,6 +10,7 @@ import yaml from dataclass_wizard import asdict, fromdict from yamlinclude import YamlIncludeConstructor +from yardstick.cli.config import Validation from grype_db_manager import db, s3utils @@ -49,8 +50,8 @@ class Grype: class ValidateDB: images: list[str] = field(default_factory=list) grype: Grype = field(default_factory=Grype) - default_max_year: int = 2021 - gate: db.validation.GateConfig = field(default_factory=db.validation.GateConfig) + gate: Validation = field(default_factory=Validation) + allow_empty_results_for_schemas: list[int] = field(default_factory=list) def __post_init__(self): # flatten elements in images (in case yaml anchors are used) @@ -77,8 +78,33 @@ class ValidateListing: @dataclass() class Validate: - db: ValidateDB = field(default_factory=ValidateDB) + default_max_year: int = 2021 + gates: list[ValidateDB] = field(default_factory=list) listing: ValidateListing = field(default_factory=ValidateListing) + expected_providers: list[str] = field( + default_factory=lambda: [ + "alpine", + "amazon", + "chainguard", + "debian", + "github", + "mariner", + "nvd", + "oracle", + "rhel", + "sles", + "ubuntu", + "wolfi", + ], + ) + + +@dataclass() +class ListingReplica: + listing_file_name: str = "listing.json" + s3_path: str | None = None + s3_bucket: str | None = None + aws_region: str | None = None @dataclass() @@ -87,8 +113,10 @@ class Distribution: s3_path: str | None = None s3_bucket: str | None = None s3_endpoint_url: str | None = None + s3_always_suffix_schema_version: bool = False download_url_prefix: str | None = None aws_region: str | None = None + listing_replicas: list[ListingReplica] = field(default_factory=list) @dataclass @@ -100,9 +128,10 @@ class Data: @dataclass class Application: + verbosity: int = 0 data: Data = field(default_factory=Data) log: Log = field(default_factory=Log) - + schema_mapping_file: str = "" # default is to use built-in schema mapping grype_db: GrypeDB = field(default_factory=GrypeDB) validate: Validate = field(default_factory=Validate) distribution: Distribution = field(default_factory=Distribution) @@ -165,9 +194,10 @@ def convert_value(obj: Any) -> Any: def load( path: None | str | list[str] | tuple[str] = DEFAULT_CONFIGS, wire_values: bool = True, + verbosity: int = 0, env: Mapping | None = None, ) -> Application: - cfg = _load_paths(path, wire_values=wire_values, env=env) + cfg = _load_paths(path, wire_values=wire_values, env=env, verbosity=verbosity) if not cfg: msg = "no config found" @@ -180,6 +210,7 @@ def _load_paths( path: None | str | list[str] | tuple[str], wire_values: bool = True, env: Mapping | None = None, + verbosity: int = 0, ) -> Application | None: if not path: path = DEFAULT_CONFIGS @@ -198,7 +229,7 @@ def _load_paths( return _load(p, wire_values=wire_values, env=env) # use the default application config - return Application() + return Application(verbosity=verbosity) msg = f"invalid path type {type(path)}" raise ValueError(msg) @@ -233,13 +264,6 @@ def _load(path: str, wire_values: bool = True, env: Mapping | None = None) -> Ap override_from_environment(cfg, prefix="GRYPE_DB_MANAGER", env=env) if wire_values: - # wire up the gate configuration so any gates created will use values from the application config - db.validation.set_default_gate_config(cfg.validate.db.gate) - gate_instance = db.validation.Gate(None, None) - if gate_instance.config != cfg.validate.db.gate: - msg = f"failed to set default gate config: {gate_instance.config} != {cfg.validate.db.gate}" - raise ValueError(msg) - # setup the endpoint url and region for all s3 calls if cfg.distribution.s3_endpoint_url: sys.stderr.write(f"Overriding S3 endpoint URL: {cfg.distribution.s3_endpoint_url}\n") @@ -254,6 +278,10 @@ def _load(path: str, wire_values: bool = True, env: Mapping | None = None) -> Ap # in case this is used back-to-back with a grype-db-manager run, reset the region s3utils.ClientFactory.set_region_name(None) + # ensure we're using the correct schema mapping file + if cfg.schema_mapping_file: + db.schema.register_mapping(cfg.schema_mapping_file) + return cfg diff --git a/manager/src/grype_db_manager/cli/db.py b/manager/src/grype_db_manager/cli/db.py index c8644d53..425e6567 100644 --- a/manager/src/grype_db_manager/cli/db.py +++ b/manager/src/grype_db_manager/cli/db.py @@ -1,14 +1,14 @@ import logging import os import shutil -import sys import click import yardstick from tabulate import tabulate from yardstick.cli import config as ycfg +from yardstick.cli.validate import validate as yardstick_validate -from grype_db_manager import db, s3utils +from grype_db_manager import db, grypedb, s3utils from grype_db_manager.cli import config, error from grype_db_manager.db.format import Format from grype_db_manager.grypedb import DB_DIR, DBManager, GrypeDB @@ -54,6 +54,13 @@ def clear_dbs(cfg: config.Application) -> None: click.echo("no databases to clear") +def remove_db(cfg: config.Application, db_uuid: str) -> None: + db_manager = DBManager(root_dir=cfg.data.root) + if db_manager.remove_db(db_uuid=db_uuid): + click.echo(f"database {db_uuid!r} removed") + click.echo(f"no database found with session id {db_uuid}") + + @group.command(name="build", help="build and validate a grype database") @click.option("--schema-version", "-s", required=True, help="the DB schema version to build") @click.pass_obj @@ -90,29 +97,28 @@ def show_db(cfg: config.Application, db_uuid: str) -> None: multiple=True, help="the image (or images) to validate with (default is to use all configured images)", ) -@click.option("--verbose", "-v", "verbosity", count=True, help="show details of all comparisons") @click.option("--recapture", "-r", is_flag=True, help="recapture grype results (even if not stale)") @click.option( "--skip-namespace-check", "skip_namespace_check", is_flag=True, - help="do not ensure the minimum expected namespaces are present", + help="do not ensure the minimum expected namespaces are present (for v6+ this is a providers-based check)", ) +@click.option("--force", "-f", is_flag=True, help="force validation even if disabled for the schema version") @click.argument("db-uuid") @click.pass_obj +@click.pass_context def validate_db( + ctx: click.Context, cfg: config.Application, db_uuid: str, images: list[str], - verbosity: int, recapture: bool, skip_namespace_check: bool, + force: bool, ) -> None: logging.info(f"validating DB {db_uuid}") - if not images: - images = cfg.validate.db.images - db_manager = DBManager(root_dir=cfg.data.root) db_info = db_manager.get_db_info(db_uuid=db_uuid) @@ -121,62 +127,156 @@ def validate_db( return if not skip_namespace_check: - # ensure the minimum number of namespaces are present - db_manager.validate_namespaces(db_uuid=db_uuid) + if db_info.schema_version < 6: + # ensure the minimum number of namespaces are present + db_manager.validate_namespaces(db_uuid=db_uuid) + else: + # ensure the minimum number of namespaces are present + db_manager.validate_providers(db_uuid=db_uuid, expected=cfg.validate.expected_providers) + + _validate_db(ctx, cfg, db_info, images, db_uuid, recapture, force=force) + + if db_info.schema_version >= 6: + validations_enabled = db.schema.validations_enabled(db_info.schema_version) + if not validations_enabled and not force: + click.echo(f"{Format.BOLD}{Format.OKGREEN}Validation disabled, skipping{Format.RESET}") + return + + _validate_latest(cfg, db_info.latest_path, db_info.archive_path) + click.echo(f"{Format.BOLD}{Format.OKGREEN}Validation passed{Format.RESET}") + + +def _validate_db( + ctx: click.Context, + cfg: config.Application, + db_info: grypedb.DBInfo, + images: list[str], + db_uuid: str, + recapture: bool, + force: bool = False, +) -> None: # resolve tool versions and install them yardstick.store.config.set_values(store_root=cfg.data.yardstick_root) - grype_version = db.schema.grype_version(db_info.schema_version) + validations_enabled = db.schema.validations_enabled(db_info.schema_version) + if not validations_enabled and not force: + click.echo(f"{Format.BOLD}{Format.OKGREEN}Validation disabled, skipping{Format.RESET}") + return - result_set = "db-validation" + grype_version = db.schema.grype_version(db_info.schema_version) + basis_grype_version = grype_version + + yardstick_profile_data = ycfg.Profiles() + profile_name = None + # this is a good example of how to add a custom profile configuration by a profile when working on a new schema version + # if db_info.schema_version >= 6: + # profile_name = f"v{db_info.schema_version}" + # yardstick_profile_data = ycfg.Profiles( + # data={ + # "grype[custom-db]": { + # profile_name: {"config_path": f"./.grype-v{db_info.schema_version}.yaml"}, + # }, + # }, + # ) + + result_sets = {} + for idx, rs in enumerate(cfg.validate.gates): + if images: + logging.info(f"filtering images for gate {idx}") + rs.images = [i for i in rs.images if i in images] + + if not rs.images: + logging.info(f"no images found for gate {idx}") + continue + + if db_info.schema_version in rs.allow_empty_results_for_schemas: + rs.gate.fail_on_empty_match_set = False + + logging.info(f"writing config for result set result_set_{idx}") + + result_sets[f"result_set_{idx}"] = ycfg.ResultSet( + description=f"generated result set for gate {idx}", + validations=[rs.gate], + matrix=ycfg.ScanMatrix( + images=rs.images, + tools=[ + ycfg.Tool( + label="custom-db", + name="grype", + version=grype_version + f"+import-db={db_info.archive_path}", + profile=profile_name, + ), + ycfg.Tool( + name="grype", + version=basis_grype_version, + ), + ], + ), + ) yardstick_cfg = ycfg.Application( + profiles=yardstick_profile_data, store_root=cfg.data.yardstick_root, - default_max_year=cfg.validate.db.default_max_year, - result_sets={ - result_set: ycfg.ResultSet( - description="compare the latest published OSS DB with the latest (local) built DB", - matrix=ycfg.ScanMatrix( - images=images, - tools=[ - ycfg.Tool( - label="custom-db", - name="grype", - version=grype_version + f"+import-db={db_info.archive_path}", - ), - ycfg.Tool( - name="grype", - version=grype_version, - ), - ], - ), - ), - }, + default_max_year=cfg.validate.default_max_year, + result_sets=result_sets, ) - gates = db.validate( - cfg=yardstick_cfg, - result_set=result_set, - db_uuid=db_uuid, - verbosity=verbosity, - recapture=recapture, - root_dir=cfg.data.root, + for r in result_sets: + # workaround for test setup issues + os.environ["GRYPE_DB_VALIDATE_BY_HASH_ON_START"] = "true" + db.capture_results( + cfg=yardstick_cfg, + db_uuid=db_uuid, + result_set=r, + recapture=recapture, + root_dir=cfg.data.root, + ) + + ctx.obj = yardstick_cfg + + try: + ctx.invoke( + yardstick_validate, + always_run_label_comparison=False, + breakdown_by_ecosystem=False, + verbosity=cfg.verbosity, + result_sets=[], + all_result_sets=True, + ) + except: + if not validations_enabled and not force: + click.echo(f"{Format.BOLD}{Format.OKGREEN}Validation disabled, ignoring failure{Format.RESET}") + return + raise + + +def _validate_latest(cfg: config.Application, latest_file: str, archive_path: str) -> None: + with open(latest_file) as f: + latest_obj = db.Latest.from_json(f.read()) + + if not cfg.validate.listing.image: + msg = "no image specified to validate against" + raise ValueError(msg) + + if not cfg.validate.listing.minimum_packages: + msg = "minimum packages must be specified" + raise ValueError(msg) + + if not cfg.validate.listing.minimum_vulnerabilities: + msg = "minimum vulnerabilities must be specified" + raise ValueError(msg) + + logging.debug(f"validating latest.json with {cfg.validate.listing.image}") + + db.latest.smoke_test( + latest_obj, + archive_path, + image=cfg.validate.listing.image, + minimum_packages=cfg.validate.listing.minimum_packages, + minimum_vulnerabilities=cfg.validate.listing.minimum_vulnerabilities, ) - failure = not all(gate.passed() for gate in gates) - if failure: - click.echo(f"{Format.BOLD}{Format.FAIL}Validation failed{Format.RESET}") - click.echo("Reasons for quality gate failure:") - - for gate in gates: - for reason in gate.reasons: - click.echo(f" - {reason}") - - sys.exit(1) - - click.echo(f"{Format.BOLD}{Format.OKGREEN}Validation passed{Format.RESET}") - @group.command(name="upload", help="upload a grype database") @click.option("--ttl-seconds", "-t", default=DEFAULT_TTL_SECONDS, help="the TTL for the uploaded DB (should be relatively high)") @@ -189,26 +289,42 @@ def upload_db(cfg: config.Application, db_uuid: str, ttl_seconds: int) -> None: s3_bucket = cfg.distribution.s3_bucket s3_path = cfg.distribution.s3_path + s3_always_suffix_schema_version = cfg.distribution.s3_always_suffix_schema_version db_manager = DBManager(root_dir=cfg.data.root) db_info = db_manager.get_db_info(db_uuid=db_uuid) - key = f"{s3_path}/{os.path.basename(db_info.archive_path)}" + if db_info.schema_version >= 6 and not os.path.exists(db_info.archive_path): + msg = f"latest.json file not found for DB {db_uuid!r}" + raise ValueError(msg) - # TODO: we have folks that require legacy behavior, where the content type was application/x-tar - kwargs = {} - if db_info.archive_path.endswith(".tar.gz"): - kwargs["ContentType"] = "application/x-tar" + if s3_always_suffix_schema_version: + s3_path = f"{s3_path}/v{db_info.schema_version}" + elif db_info.schema_version >= 6: + # /databases -> /databases/v6 , and is dynamic based on the schema version + s3_path = f"{s3_path}/v{db_info.schema_version}" + + db_key = f"{s3_path}/{os.path.basename(db_info.archive_path)}" + latest_key = f"{s3_path}/latest.json" s3utils.upload_file( bucket=s3_bucket, - key=key, + key=db_key, path=db_info.archive_path, CacheControl=f"public,max-age={ttl_seconds}", - **kwargs, ) - click.echo(f"DB {db_uuid!r} uploaded to s3://{s3_bucket}/{s3_path}") + click.echo(f"DB archive {db_uuid!r} uploaded to s3://{s3_bucket}/{s3_path}") + + if db_info.schema_version >= 6: + s3utils.upload_file( + bucket=s3_bucket, + key=latest_key, + path=db_info.latest_path, + CacheControl="public,max-age=300", # 5 minutes + ) + + click.echo(f"DB latest.json {db_uuid!r} uploaded to s3://{s3_bucket}/{s3_path}") @group.command(name="build-and-upload", help="upload a grype database") @@ -219,9 +335,8 @@ def upload_db(cfg: config.Application, db_uuid: str, ttl_seconds: int) -> None: "--skip-namespace-check", "skip_namespace_check", is_flag=True, - help="do not ensure the minimum expected namespaces are present", + help="do not ensure the minimum expected namespaces are present (for v6+ this is a providers-based check)", ) -@click.option("--verbose", "-v", "verbosity", count=True, help="show details of all comparisons") @click.pass_obj @click.pass_context @error.handle_exception(handle=(ValueError, s3utils.CredentialsError)) @@ -232,7 +347,6 @@ def build_and_upload_db( skip_validate: bool, skip_namespace_check: bool, dry_run: bool, - verbosity: bool, ) -> None: if dry_run: click.echo(f"{Format.ITALIC}Dry run! Will skip uploading the listing file to S3{Format.RESET}") @@ -246,7 +360,7 @@ def build_and_upload_db( click.echo(f"{Format.ITALIC}Skipping validation of DB {db_uuid!r}{Format.RESET}") else: click.echo(f"{Format.BOLD}Validating DB {db_uuid!r}{Format.RESET}") - ctx.invoke(validate_db, db_uuid=db_uuid, verbosity=verbosity, skip_namespace_check=skip_namespace_check) + ctx.invoke(validate_db, db_uuid=db_uuid, skip_namespace_check=skip_namespace_check) if not dry_run: click.echo(f"{Format.BOLD}Uploading DB {db_uuid!r}{Format.RESET}") diff --git a/manager/src/grype_db_manager/cli/listing.py b/manager/src/grype_db_manager/cli/listing.py index b9b6592d..ea7d7b31 100644 --- a/manager/src/grype_db_manager/cli/listing.py +++ b/manager/src/grype_db_manager/cli/listing.py @@ -9,7 +9,7 @@ from grype_db_manager.db.format import Format -@click.group(name="listing", help="manage the grype-db listing file") +@click.group(name="listing", help="manage the grype-db listing file (only schemas v1-v5)") @click.pass_obj def group(_: config.Application) -> None: pass @@ -22,15 +22,24 @@ def group(_: config.Application) -> None: def create_listing(cfg: config.Application, ignore_missing_listing: bool) -> str: s3_bucket = cfg.distribution.s3_bucket s3_path = cfg.distribution.s3_path + db_s3_path = s3_path + if cfg.distribution.s3_always_suffix_schema_version: + # Listing file logic is only supported for v5 + db_s3_path = f"{s3_path}/v5" download_url_prefix = cfg.distribution.download_url_prefix # get existing listing file... - the_listing = db.listing.fetch(bucket=s3_bucket, path=s3_path, create_if_missing=ignore_missing_listing) + the_listing = db.listing.fetch( + bucket=s3_bucket, + path=s3_path, + filename=cfg.distribution.listing_file_name, + create_if_missing=ignore_missing_listing, + ) # look for existing DBs in S3 existing_paths_by_basename = distribution.existing_dbs_in_s3( s3_bucket=s3_bucket, - s3_path=s3_path, + s3_path=db_s3_path, ) # determine what basenames are new relative to the listing file and the current S3 state @@ -40,8 +49,7 @@ def create_listing(cfg: config.Application, ignore_missing_listing: bool) -> str if missing_basenames: logging.warning( - f"missing {len(missing_basenames)} databases in S3 which were in the existing" - " listing file (removing entries in the next listing file)", + f"missing {len(missing_basenames)} databases in S3 which were in the existing listing file (removing entries in the next listing file)", ) for basename in missing_basenames: logging.warning(f" - {basename}") @@ -54,7 +62,7 @@ def create_listing(cfg: config.Application, ignore_missing_listing: bool) -> str basenames=new_basenames, paths_by_basename=existing_paths_by_basename, s3_bucket=s3_bucket, - s3_path=s3_path, + s3_path=db_s3_path, download_url_prefix=download_url_prefix, ): the_listing.add(entry) @@ -66,6 +74,9 @@ def create_listing(cfg: config.Application, ignore_missing_listing: bool) -> str minimum_elements=distribution.MINIMUM_DB_COUNT, ) + # sort all listing elements by URL (which is by timestamp) + the_listing.sort() + total_entries = sum([len(v) for k, v in the_listing.available.items()]) logging.info(f"wrote {total_entries} total database entries to the listing") @@ -102,7 +113,7 @@ def validate_listing(cfg: config.Application, listing_file: str) -> None: raise ValueError(msg) if cfg.validate.listing.override_grype_version and not cfg.validate.listing.override_db_schema_version: - msg = "ovrerride db schema version must be specified if override grype version is specified" + msg = "override db schema version must be specified if override grype version is specified" raise ValueError(msg) override_schema_release = None @@ -131,26 +142,48 @@ def upload_listing(cfg: config.Application, listing_file: str, ttl_seconds: int) s3_bucket = cfg.distribution.s3_bucket s3_path = cfg.distribution.s3_path + filename = cfg.distribution.listing_file_name with open(listing_file) as f: the_listing = db.Listing.from_json(f.read()) + # upload the primary listing file s3utils.upload( bucket=s3_bucket, - key=the_listing.url(s3_path), + key=the_listing.url(s3_path, filename), contents=the_listing.to_json(), CacheControl=f"public,max-age={ttl_seconds}", ) click.echo(f"{listing_file} uploaded to s3://{s3_bucket}/{s3_path}") + # upload to replicas + for replica in cfg.distribution.listing_replicas: + replica_s3_bucket = replica.s3_bucket or s3_bucket + replica_s3_path = replica.s3_path or s3_path + replica_filename = replica.listing_file_name or filename + + if not replica_s3_bucket or not replica_s3_path: + logging.warning(f"skipping replica upload for {replica} because s3_bucket and s3_path are required") + continue + + s3utils.upload( + bucket=replica_s3_bucket, + key=the_listing.url(replica_s3_path, replica_filename), + contents=the_listing.to_json(), + CacheControl=f"public,max-age={ttl_seconds}", + ) + + click.echo(f"{listing_file} uploaded to s3://{replica_s3_bucket}/{replica_s3_path}") + @group.command(name="update", help="recreate a listing based off of S3 state, validate it, and upload it") @click.option("--dry-run", "-d", default=False, help="do not upload the listing file to S3", is_flag=True) +@click.option("--skip-validate", default=False, help="skip validation of the listing file", is_flag=True) @click.pass_obj @click.pass_context @error.handle_exception(handle=(ValueError, s3utils.CredentialsError)) -def update_listing(ctx: click.core.Context, cfg: config.Application, dry_run: bool) -> None: +def update_listing(ctx: click.core.Context, cfg: config.Application, dry_run: bool, skip_validate: bool) -> None: if dry_run: click.echo(f"{Format.ITALIC}Dry run! Will skip uploading the listing file to S3{Format.RESET}") elif cfg.assert_aws_credentials: @@ -159,8 +192,11 @@ def update_listing(ctx: click.core.Context, cfg: config.Application, dry_run: bo click.echo(f"{Format.BOLD}Creating listing file from S3 state{Format.RESET}") listing_file_name = ctx.invoke(create_listing) - click.echo(f"{Format.BOLD}Validating listing file {listing_file_name!r}{Format.RESET}") - ctx.invoke(validate_listing, listing_file=listing_file_name) + if not skip_validate: + click.echo(f"{Format.BOLD}Validating listing file {listing_file_name!r}{Format.RESET}") + ctx.invoke(validate_listing, listing_file=listing_file_name) + else: + click.echo(f"{Format.ITALIC}Skipping the validation of the listing file{Format.RESET}") if not dry_run: click.echo(f"{Format.BOLD}Uploading listing file {listing_file_name!r}{Format.RESET}") diff --git a/manager/src/grype_db_manager/data/schema-info.json b/manager/src/grype_db_manager/data/schema-info.json index 8164f944..8857b9a2 100644 --- a/manager/src/grype_db_manager/data/schema-info.json +++ b/manager/src/grype_db_manager/data/schema-info.json @@ -3,25 +3,30 @@ { "schema": "1", "grype-version": "v0.7.0", - "supported": true + "supported": false }, { "schema": "2", "grype-version": "v0.12.1", - "supported": true + "supported": false }, { "schema": "3", "grype-version": "v0.40.1", - "supported": true + "supported": false }, { "schema": "4", "grype-version": "v0.50.2", - "supported": true + "supported": false }, { "schema": "5", + "grype-version": "v0.87.0", + "supported": true + }, + { + "schema": "6", "grype-version": "main", "supported": true } diff --git a/manager/src/grype_db_manager/db/__init__.py b/manager/src/grype_db_manager/db/__init__.py index 626e4e7c..80c651f4 100644 --- a/manager/src/grype_db_manager/db/__init__.py +++ b/manager/src/grype_db_manager/db/__init__.py @@ -1,13 +1,16 @@ from . import listing, metadata, schema +from .latest import Latest from .listing import Listing from .metadata import Metadata -from .validation import validate +from .validation import capture_results __all__ = [ + "Latest", "Listing", "Metadata", + "capture_results", + "latest", "listing", "metadata", - "validate", "schema", ] diff --git a/manager/src/grype_db_manager/db/format.py b/manager/src/grype_db_manager/db/format.py index d71668a0..ac0adbfb 100644 --- a/manager/src/grype_db_manager/db/format.py +++ b/manager/src/grype_db_manager/db/format.py @@ -4,8 +4,6 @@ import re from typing import Any -from colr import color as Color -from supports_color import supportsColor from tabulate import tabulate from yardstick import artifact, comparison @@ -26,7 +24,7 @@ class Format(enum.Enum): RESET = "\033[0m" def __init__(self, value: str): - if not supportsColor.stderr or os.environ.get("NO_COLOR", None): + if os.environ.get("NO_COLOR", None): self._value = "" else: self._value = value @@ -68,6 +66,14 @@ def get_section_index(value: int, min_value: int, max_value: int, sections: int, return min(max(int(sections * value_ratio), 0), sections - 1), value_ratio +def rgb_ansi(r: int, g: int, b: int) -> str: + return f"\033[38;2;{r};{g};{b}m" + + +def reset_ansi() -> str: + return "\033[0m" + + def format_value_red_green_spectrum( value: int, min_value: int = 0, @@ -78,7 +84,7 @@ def format_value_red_green_spectrum( index, value_ratio = get_section_index(value, min_value, max_value, sections, invert) color_rgb_tuple = get_section_rgb_tuple(index, sections) - formatted_value = Color(f"{value:6.2f}", fore=color_rgb_tuple) + formatted_value = f"{rgb_ansi(*color_rgb_tuple)}{value:6.2f}{reset_ansi()}" if value_ratio > 0.9: # bold diff --git a/manager/src/grype_db_manager/db/latest.py b/manager/src/grype_db_manager/db/latest.py new file mode 100644 index 00000000..4cb432cd --- /dev/null +++ b/manager/src/grype_db_manager/db/latest.py @@ -0,0 +1,170 @@ +from __future__ import annotations + +import contextlib +import functools +import json +import logging +import os +import tempfile +import threading +from dataclasses import dataclass + +# note: this is needed for dataclass loading from json (do not port to a type check block) +from datetime import datetime # noqa: TC003 +from http.server import HTTPServer, SimpleHTTPRequestHandler +from typing import TYPE_CHECKING + +from dataclass_wizard import asdict, fromdict + +from grype_db_manager import grype + +if TYPE_CHECKING: + from collections.abc import Iterator + +LATEST_FILENAME = "latest.json" + + +# Latest is a dataclass that represents the latest.json document for schemas v6. +@dataclass +class Latest: + # status indicates if the database is actively being maintained and distributed + status: str | None = None + + # schema version of the DB schema + schema_version: str | None = None + + # timestamp the database was built + built: datetime | None = None + + # path to a DB archive relative to the listing file hosted location (NOT the absolute URL) + path: str = "" + + # self-describing digest of the database archive referenced in path + checksum: str = "" + + @classmethod + def from_json(cls, contents: str) -> Latest: + return cls.from_dict(json.loads(contents)) + + @classmethod + def from_dict(cls, contents: dict) -> Latest: + return fromdict(cls, contents) + + def to_json(self, indent: int | None = None) -> str: + return json.dumps(self.to_dict(), indent=indent, sort_keys=True) + + def to_dict(self) -> dict: + return asdict(self) + + +@contextlib.contextmanager +def _http_server(directory: str, schema_version: str) -> Iterator[str]: + major_version = schema_version.split(".")[0].removeprefix("v") + server_address = ("127.0.0.1", 5555) + url = f"http://{server_address[0]}:{server_address[1]}" + latest_url = f"{url}/v{major_version}/{LATEST_FILENAME}" + + def serve() -> None: + httpd = HTTPServer( + server_address, + functools.partial(SimpleHTTPRequestHandler, directory=directory), + ) + logging.info(f"starting test server at {url!r}") + # show tree output of the given directory to the log + _log_dir(directory) + + httpd.serve_forever() + + thread = threading.Thread(target=serve) + thread.daemon = True + thread.start() + try: + yield latest_url + finally: + pass + + +def _log_dir(path: str, prefix: str = "") -> None: + items = sorted(os.listdir(path)) + for i, item in enumerate(items): + is_last = i == len(items) - 1 + connector = "└── " if is_last else "├── " + logging.info(f"{prefix}{connector}{item}") + new_prefix = prefix + (" " if is_last else "│ ") + item_path = os.path.join(path, item) + if os.path.isdir(item_path): + _log_dir(item_path, new_prefix) + + +def _smoke_test( + schema_version: str, + listing_url: str, + image: str, + minimum_packages: int, + minimum_vulnerabilities: int, + store_root: str, +) -> None: + logging.info(f"testing latest.json grype schema-version={schema_version!r}") + tool_obj = grype.Grype( + schema_version=schema_version, + store_root=store_root, + update_url=listing_url, + ) + + output = tool_obj.run(user_input=image) + packages, vulnerabilities = grype.Report(report_contents=output).parse() + logging.info(f"scan result with downloaded DB: packages={len(packages)} vulnerabilities={len(vulnerabilities)}") + if not packages or not vulnerabilities: + msg = "validation failed: missing packages and/or vulnerabilities" + raise ValueError(msg) + + if len(packages) < minimum_packages: + msg = f"validation failed: expected at least {minimum_packages} packages, got {len(packages)}" + raise ValueError(msg) + + if len(vulnerabilities) < minimum_vulnerabilities: + msg = f"validation failed: expected at least {minimum_vulnerabilities} vulnerabilities, got {len(vulnerabilities)}" + raise ValueError(msg) + + +def smoke_test( + test_latest: Latest, + archive_path: str, + image: str, + minimum_packages: int, + minimum_vulnerabilities: int, +) -> None: + # write the listing to a temp dir that is served up locally on an HTTP server. This is used by grype to locally + # download the latest.json file and check that it works against S3 (since the listing entries have DB urls that + # reside in S3). + with tempfile.TemporaryDirectory(prefix="grype-db-smoke-test") as tempdir: + listing_contents = test_latest.to_json() + + installation_path = os.path.join(tempdir, "grype-install") + + major_version = test_latest.schema_version.split(".")[0].removeprefix("v") + + sub_path = os.path.join(tempdir, "v" + major_version) + os.makedirs(sub_path, exist_ok=True) + + logging.info(listing_contents) + with open(os.path.join(sub_path, LATEST_FILENAME), "w") as f: + f.write(listing_contents) + + # make the archive available at the expected location via symlink + archive_dest = os.path.join(sub_path, test_latest.path) + os.link(archive_path, archive_dest) + + # ensure grype can perform a db update for all supported schema versions. Note: we are only testing the + # latest.json for the DB is usable (the download succeeds and grype and the update process, which does + # checksum verifications, passes). This test does NOT check the integrity of the DB since that has already + # been tested in the build steps. + with _http_server(directory=tempdir, schema_version=test_latest.schema_version) as listing_url: + _smoke_test( + schema_version=test_latest.schema_version, + listing_url=listing_url, + image=image, + minimum_packages=minimum_packages, + minimum_vulnerabilities=minimum_vulnerabilities, + store_root=installation_path, + ) diff --git a/manager/src/grype_db_manager/db/listing.py b/manager/src/grype_db_manager/db/listing.py index 5d4c6971..5e68d62b 100644 --- a/manager/src/grype_db_manager/db/listing.py +++ b/manager/src/grype_db_manager/db/listing.py @@ -25,6 +25,7 @@ LISTING_FILENAME = "listing.json" +# Entry is a dataclass that represents a single entry from a listing.json for schemas v1-v5. @dataclass class Entry: built: str @@ -42,10 +43,11 @@ def basename(self) -> str: def age_in_days(self, now: datetime.datetime | None = None) -> int: if not now: - now = datetime.datetime.now(tz=datetime.timezone.utc) + now = datetime.datetime.now(tz=datetime.UTC) return (now - iso8601.parse_date(self.built)).days +# Listing is a dataclass that represents the listing.json for schemas v1-v5. @dataclass class Listing: available: dict[int, list[Entry]] @@ -65,6 +67,8 @@ def to_dict(self) -> dict: return asdict(self) def prune(self, max_age_days: int, minimum_elements: int, now: datetime.datetime | None = None) -> None: + self.sort() + for schema_version, entries in self.available.items(): kept = [] pruned = [] @@ -81,19 +85,8 @@ def prune(self, max_age_days: int, minimum_elements: int, now: datetime.datetime else: kept.append(entry) - # latest elements are in the back - pruned.sort( - key=lambda x: iso8601.parse_date(x.built), - ) - while len(kept) < minimum_elements and len(pruned) > 0: - kept.append(pruned.pop()) - - # latest elements are in the front - kept.sort( - key=lambda x: iso8601.parse_date(x.built), - reverse=True, - ) + kept.append(pruned.pop(0)) if not pruned: logging.debug(f"no entries to prune from schema version {schema_version}") @@ -113,7 +106,7 @@ def add(self, entry: Entry, quiet: bool = False) -> None: # keep listing entries sorted by date (rfc3339 formatted entries, which iso8601 is a superset of) self.available[entry.version].sort( - key=lambda x: iso8601.parse_date(x.built), + key=lambda x: x.url, reverse=True, ) @@ -140,8 +133,8 @@ def log(self) -> None: logging.info(f" entry: {entry}") @staticmethod - def url(path: str) -> str: - url = os.path.normpath("/".join([path, LISTING_FILENAME]).lstrip("/")) + def url(path: str, filename: str) -> str: + url = os.path.normpath("/".join([path, filename]).lstrip("/")) return urlunparse(urlparse(url)) # normalize the url def basenames(self) -> set[str]: @@ -160,6 +153,10 @@ def basename_difference(self, other: set[str]) -> tuple[set[str], set[str]]: def latest(self, schema_version: int) -> Entry: return self.available[schema_version][0] + def sort(self) -> None: + for _, v in self.available.items(): + v.sort(key=lambda x: x.url, reverse=True) + def has_suffix(el: str, suffixes: set[str] | None) -> bool: if not suffixes: @@ -171,7 +168,7 @@ def empty_listing() -> Listing: return Listing(available={}) -def fetch(bucket: str, path: str, create_if_missing: bool = False) -> Listing: +def fetch(bucket: str, path: str, filename: str, create_if_missing: bool = False) -> Listing: if not path or not bucket: if create_if_missing: logging.warning("no path or bucket specified, creating empty listing") @@ -180,7 +177,7 @@ def fetch(bucket: str, path: str, create_if_missing: bool = False) -> Listing: raise ValueError(msg) logging.info(f"fetching existing listing from s3://{bucket}/{path}") - listing_path = Listing.url(path) + listing_path = Listing.url(path, filename) try: listing_contents = s3utils.get_s3_object_contents( bucket=bucket, @@ -227,14 +224,14 @@ def serve() -> None: def _smoke_test( - schema_version: str, + schema_version: str | int, listing_url: str, image: str, minimum_packages: int, minimum_vulnerabilities: int, store_root: str, ) -> None: - logging.info(f"testing grype schema-version={schema_version!r}") + logging.info(f"testing listing.json grype schema-version={schema_version!r}") tool_obj = grype.Grype( schema_version=schema_version, store_root=store_root, @@ -273,7 +270,7 @@ def smoke_test( installation_path = os.path.join(tempdir, "grype-install") # way too verbose! - # logging.info(listing_contents) + logging.info(listing_contents) with open(os.path.join(tempdir, LISTING_FILENAME), "w") as f: f.write(listing_contents) @@ -296,7 +293,9 @@ def smoke_test( else: schema_versions = schema.supported_schema_versions() - logging.info(f"testing all supported schema-versions={schema_versions}") + # only accept schema versions up through v5 + schema_versions = [s for s in schema_versions if int(s) <= 5] + logging.info(f"testing listing.json all supported schema-versions={schema_versions}") for schema_version in schema_versions: _smoke_test( schema_version=schema_version, diff --git a/manager/src/grype_db_manager/db/metadata.py b/manager/src/grype_db_manager/db/metadata.py index 633b9306..3d3c235f 100644 --- a/manager/src/grype_db_manager/db/metadata.py +++ b/manager/src/grype_db_manager/db/metadata.py @@ -10,6 +10,7 @@ FILE = "metadata.json" +# Metadata is a dataclass that represents the metadata.json for schemas v1-v5. @dataclass class Metadata: built: str diff --git a/manager/src/grype_db_manager/db/schema.py b/manager/src/grype_db_manager/db/schema.py index 618e5fcc..26465fa3 100644 --- a/manager/src/grype_db_manager/db/schema.py +++ b/manager/src/grype_db_manager/db/schema.py @@ -4,23 +4,34 @@ from dataclasses import dataclass, field from functools import lru_cache from importlib.resources import files +from typing import Any import mergedeep from dataclass_wizard import asdict, fromdict +_mapping_file_content = None + @dataclass class SchemaEntry: schema: str grype_version: str supported: bool + validate: bool = True @dataclass class SchemaMapping: Available: list[SchemaEntry] = field(default_factory=list) - def grype_version(self, schema_version: int) -> str | None: + def validations_enabled(self, schema_version: int | str) -> bool: + schema_version = str(schema_version) + for entry in self.Available: + if entry.schema == schema_version: + return entry.validate + return True + + def grype_version(self, schema_version: int | str) -> str | None: schema_version = str(schema_version) for entry in self.Available: if entry.schema == schema_version: @@ -35,10 +46,20 @@ def supported_schema_versions(self) -> list[int]: return supported +def register_mapping(file: str) -> None: + with open(file) as f: + global _mapping_file_content # noqa: PLW0603 + _mapping_file_content = f.read() + + @lru_cache +def _mapping() -> dict[str, Any]: + content = files("grype_db_manager.data").joinpath("schema-info.json").read_text() if _mapping_file_content is None else _mapping_file_content + return json.loads(content) + + def _load() -> SchemaMapping: - content = files("grype_db_manager.data").joinpath("schema-info.json").read_text() - mapping_object = json.loads(content) + mapping_object = _mapping() # we need a full default application config first then merge the loaded config on top. # Why? dataclass_wizard.fromdict() will create instances from the dataclass default @@ -60,7 +81,11 @@ def _load() -> SchemaMapping: return cfg -def grype_version(schema_version: int) -> str: +def validations_enabled(schema_version: int | str) -> bool: + return _load().validations_enabled(schema_version) + + +def grype_version(schema_version: int | str) -> str: return _load().grype_version(schema_version) diff --git a/manager/src/grype_db_manager/db/validation.py b/manager/src/grype_db_manager/db/validation.py index 9bda243c..cbd49320 100644 --- a/manager/src/grype_db_manager/db/validation.py +++ b/manager/src/grype_db_manager/db/validation.py @@ -2,15 +2,13 @@ import collections import logging -from dataclasses import InitVar, dataclass, field -from typing import TYPE_CHECKING, Any +from dataclasses import dataclass +from typing import TYPE_CHECKING import yardstick -from yardstick import artifact, capture, comparison, store +from yardstick import artifact, capture from grype_db_manager import grypedb -from grype_db_manager.db import format -from grype_db_manager.db.format import Format if TYPE_CHECKING: from yardstick.cli import config as ycfg @@ -45,166 +43,27 @@ def set_default_gate_config(config: GateConfig) -> None: _default_gate_config = config -@dataclass -class Gate: - label_comparisons: InitVar[list[comparison.AgainstLabels] | None] - label_comparison_stats: InitVar[comparison.ImageToolLabelStats | None] - - reasons: list[str] = field(default_factory=list) - config: GateConfig = field(default_factory=_get_config) - - def __post_init__( - self, - label_comparisons: list[comparison.AgainstLabels] | None, - label_comparison_stats: comparison.ImageToolLabelStats | None, - ): - if not label_comparisons and not label_comparison_stats: - return - - if not self.config: - msg = "default GateConfig must be specified before creating a Gate instance" - raise RuntimeError(msg) - - reasons = [] - - latest_release_tool, current_tool = guess_tool_orientation(label_comparison_stats.tools) - - latest_release_comparisons_by_image = { - comp.config.image: comp for comp in label_comparisons if comp.config.tool == latest_release_tool - } - current_comparisons_by_image = {comp.config.image: comp for comp in label_comparisons if comp.config.tool == current_tool} - - for image, comp in current_comparisons_by_image.items(): - reasons.extend( - not_empty( - [ - self._evaluate_f1_score( - last_f1_score=latest_release_comparisons_by_image[image].summary.f1_score, - current_f1_score=comp.summary.f1_score, - context=image, - ), - self._evaluate_indeterminate_percent( - indeterminate_percent=comp.summary.indeterminate_percent, - context=image, - ), - self._evaluate_fns( - last_fns=latest_release_comparisons_by_image[image].summary.false_negatives, - current_fns=comp.summary.false_negatives, - context=image, - ), - ], - ), - ) - - self.reasons = reasons - - def _evaluate_f1_score(self, last_f1_score: float, current_f1_score: float, context: str) -> str | None: - test_f1_value = last_f1_score - self.config.f1_score_threshold - if current_f1_score < test_f1_value: - return str( - f"current F1 score is lower than the last release F1 score: {Format.BOLD}{Format.UNDERLINE}" - f"current={current_f1_score:0.2f} last={last_f1_score:0.2f} " - f"by-margin={self.config.f1_score_threshold:0.2f}{Format.RESET} image={context}", - ) - return None - - def _evaluate_indeterminate_percent(self, indeterminate_percent: float, context: str) -> str | None: - if indeterminate_percent > self.config.unlabeled_matches_threshold: - return str( - f"current indeterminate matches % is greater than {self.config.unlabeled_matches_threshold}%: {Format.BOLD}{Format.UNDERLINE}" - f"current={indeterminate_percent:0.2f}%{Format.RESET} image={context}", - ) - return None - - def _evaluate_fns(self, last_fns: int, current_fns: int, context: str) -> str | None: - test_fns = last_fns + self.config.introduced_fns_threshold - if current_fns > test_fns: - return str( - f"current false negatives is greater than the last release false negatives: {Format.BOLD}{Format.UNDERLINE}" - f"current={current_fns} last={last_fns} " - f"by-margin={self.config.introduced_fns_threshold}{Format.RESET} image={context}", - ) - return None - - def passed(self) -> bool: - return len(self.reasons) == 0 - - -def not_empty(value: list[str | None]) -> list[str]: - return [v for v in value if v is not None] - - -def validate( - cfg: ycfg.Application, - result_set: str, - db_uuid: str, - root_dir: str, - verbosity: int = 0, - recapture: bool = False, -) -> list[Gate]: - # get new grype scans and SBOMs (or use any existing ones) - - capture_results( - cfg=cfg, - db_uuid=db_uuid, - result_set=result_set, - recapture=recapture, - root_dir=root_dir, - ) - - # validate the DB based on the grype scan results... - - result_set_obj = store.result_set.load(name=result_set) - images = sorted({s.config.image for s in result_set_obj.state}) - logging.info("loading labels...") - label_entries = store.labels.load_for_image(images, year_max_limit=cfg.default_max_year) - # label_collection = artifact.LabelEntryCollection(label_entries) - - lines = [f"result-set: {result_set}", f"labels: {len(label_entries)}", f"max-cve-year: {cfg.default_max_year}"] - banner = f"validating db={db_uuid}\n{format.treeify(lines)}" - logging.info(banner) - - ret = [] - states = result_set_obj.result_state_by_image.items() - for idx, (image, result_states) in enumerate(states): - if images and image not in images: - logging.debug(f"skipping image={image}") - continue - - lines = [f"{state.config.ID} : {state.config.tool}" for state in result_states] - image_banner = f"{Format.HEADER}comparing image results {idx+1} of {len(states)} image={image}{Format.RESET} with\n{format.treeify(lines)}" - logging.info(image_banner) - - gate = validate_image( - cfg, - descriptions=[s.config.path for s in result_states], - verbosity=verbosity, - # label_entries=label_collection.for_image(image), - label_entries=label_entries, - ) - ret.append(gate) - - failure = not gate.passed() - if failure: - logging.info(f"{Format.FAIL}failed quality gate{Format.RESET} for image={image}\n{format.treeify(gate.reasons)}") - - return ret - - def capture_results(cfg: ycfg.Application, db_uuid: str, result_set: str, root_dir: str, recapture: bool = False) -> None: dbm = grypedb.DBManager(root_dir=root_dir) db_info = dbm.get_db_info(db_uuid) request_images = cfg.result_sets[result_set].images() - is_stale = _is_result_set_stale( - request_images=request_images, - result_set=result_set, - db_info=db_info, - yardstick_root_dir=cfg.store_root, - ) + + is_stale = True + if not recapture: + is_stale = _is_result_set_stale( + request_images=request_images, + result_set=result_set, + db_info=db_info, + yardstick_root_dir=cfg.store_root, + ) if is_stale or recapture: - capture.result_set(result_set=result_set, scan_requests=cfg.result_sets[result_set].scan_requests()) + capture.result_set( + result_set=result_set, + scan_requests=cfg.result_sets[result_set].scan_requests(), + profiles=cfg.profiles.data, + ) else: logging.info(f"skipping grype capture for result-set={result_set} (already exists)") @@ -228,10 +87,7 @@ def _is_result_set_stale( if _is_db_checksums_stale(result_set_object, db_info): return True - if _is_result_set_consistent(result_set_object, request_images): - return True - - return False + return bool(_is_result_set_consistent(result_set_object, request_images)) def _is_result_set_consistent(result_set_object: artifact.ResultSet, request_images: list[str]) -> bool: @@ -246,9 +102,7 @@ def _is_result_set_consistent(result_set_object: artifact.ResultSet, request_ima if s.config and "grype" in s.config.tool: grype_requests_by_image[s.config.full_image].append(s.request) - missing_grype_requests = [ - image for image in request_images if image not in grype_requests_by_image or len(grype_requests_by_image[image]) != 2 - ] + missing_grype_requests = [image for image in request_images if image not in grype_requests_by_image or len(grype_requests_by_image[image]) != 2] if missing_grype_requests: logging.warning(f"result-set has missing grype requests: {missing_grype_requests}") @@ -288,133 +142,10 @@ def _is_db_checksums_stale(result_set_object: artifact.ResultSet, db_info: grype return False -def validate_image( - cfg: ycfg.Application, - descriptions: list[str], - verbosity: int = 0, - label_entries: list[artifact.LabelEntry] | None = None, - store_root: str | None = None, -) -> Gate: - # compare each grype result against one another, looking for matching differences - relative_comparison = yardstick.compare_results( - descriptions=descriptions, - year_max_limit=cfg.default_max_year, - store_root=store_root, - ) - - # apply labels to all results - results, label_entries, comparisons_by_result_id, stats_by_image_tool_pair = yardstick.compare_results_against_labels( - descriptions=descriptions, - year_max_limit=cfg.default_max_year, - label_entries=label_entries, - store_root=store_root, - ) - - # show the label stats for each image/tool pair - # as well as qualify all of the match differences relative to the labels to help understand - # if the matching performance is getting better or worse, outputting enough information for - # someone to start investigating why. - log_validation_results( - relative_comparison=relative_comparison, - results=results, - comparisons_by_result_id=comparisons_by_result_id, - stats_by_image_tool_pair=stats_by_image_tool_pair, - verbosity=verbosity, - ) - - # populate the quality gate with data that can evaluate pass/fail conditions for this script - found_differences = sum([len(relative_comparison.unique[result.ID]) for result in relative_comparison.results]) - if not found_differences: - return Gate(None, None) - - return Gate(label_comparisons=comparisons_by_result_id.values(), label_comparison_stats=stats_by_image_tool_pair) - - -def log_validation_results( - relative_comparison: comparison.ByPreservedMatch, - results: list[artifact.ScanResult], - comparisons_by_result_id: dict[str, list[comparison.AgainstLabels]], - stats_by_image_tool_pair: comparison.ImageToolLabelStats, - verbosity: int = 0, -) -> None: - if verbosity > 2: - _log_stats(stats_by_image_tool_pair) - - if verbosity > 1: - _log_fns(results, comparisons_by_result_id) - - _log_differences(relative_comparison, results, comparisons_by_result_id, verbosity) - - -def _log_differences( - relative_comparison: comparison.ByPreservedMatch, - results: list[artifact.ScanResult], - comparisons_by_result_id: dict[str, list[comparison.AgainstLabels]], - verbosity: int, -) -> None: - latest_release_tool, current_tool = guess_tool_orientation([r.config.tool for r in results]) - - table, diffs = format.match_differences_table( - latest_release_tool=latest_release_tool, - relative_comparison=relative_comparison, - comparisons_by_result_id=comparisons_by_result_id, - ) - - if not diffs: - logging.info("no differences found between tooling") - else: # noqa: PLR5501 - if verbosity > 0: - logging.info(f"match differences found between tooling:\n{table}") - else: - logging.info(f"match differences found between tooling: {diffs}") - - -def _log_stats(stats_by_image_tool_pair: comparison.ImageToolLabelStats) -> None: - image = sorted(stats_by_image_tool_pair.true_positives.keys())[0] - tools = sorted(stats_by_image_tool_pair.true_positives[image].keys()) - table = format.stats_table_by_tool( - tools, - image, - stats_by_image_tool_pair, - ) - - logging.info(table) - - -def _log_fns(results: list[artifact.ScanResult], comparisons_by_result_id: dict[str, list[comparison.AgainstLabels]]) -> None: - # show false negative label entries - fns_by_id = {} - for result in results: - comp = comparisons_by_result_id[result.ID] - fns = comp.false_negative_label_entries - fns_by_id[result.ID] = fns - - unique_fns_by_id = collections.defaultdict(list) - for result_id, fns in fns_by_id.items(): - for fn in fns: - if _is_unique_fn(fns_by_id, result_id, fn): - unique_fns_by_id[result_id].append(fn) - - for result in results: - fns = unique_fns_by_id[result.ID] - ret = f"false negatives found uniquely in result={result.ID}: {len(fns)}\n" - for label in fns: - ret += f"{format.space} {label.summarize()}\n" - logging.info(ret.rstrip()) - - -def _is_unique_fn(fns_by_id: dict[str, set[str]], result_id: str, fn: Any) -> bool: - for other_result_id, other_fns in fns_by_id.items(): - if other_result_id == result_id: - continue - if fn in other_fns: - return False - return True - - def guess_tool_orientation(tools: list[str]) -> tuple[str, str]: if len(tools) != 2: - raise RuntimeError("expected 2 tools, got %s" % tools) + msg = f"expected 2 tools, got {tools}" + raise RuntimeError(msg) current_tool = None latest_release_tool = None diff --git a/manager/src/grype_db_manager/distribution.py b/manager/src/grype_db_manager/distribution.py index 71ab3e82..41ea4998 100644 --- a/manager/src/grype_db_manager/distribution.py +++ b/manager/src/grype_db_manager/distribution.py @@ -17,8 +17,8 @@ from collections.abc import Generator DB_SUFFIXES = {".tar.gz", ".tar.zst"} -MAX_DB_AGE = 120 # ~4 months in days -MINIMUM_DB_COUNT = MAX_DB_AGE # number of db entries per schema +MAX_DB_AGE = 3 # old db listings are making the file large and slowing download times +MINIMUM_DB_COUNT = 3 # always include at least 2 databases, no matter how old def listing_entries_dbs_in_s3( @@ -67,7 +67,7 @@ def listing_entries_dbs_in_s3( meta = metadata.from_archive(path=local_path) # create a new listing entry and add it to the listing - url = f"{download_url_prefix}/{s3_path}/{basename}" + url = f"{download_url_prefix.strip('/')}/{s3_path.strip('/')}/{basename.strip('/')}" url = urlunparse(urlparse(url)) # normalize the url yield listing.Entry( @@ -133,7 +133,7 @@ def age_from_basename(basename: str) -> int | None: def _now() -> datetime.datetime: - return datetime.datetime.now(tz=datetime.timezone.utc) + return datetime.datetime.now(tz=datetime.UTC) def hash_file(path: str) -> str: @@ -146,4 +146,4 @@ def hash_file(path: str) -> str: break hasher.update(data) - return "sha256:%s" % hasher.hexdigest() + return f"sha256:{hasher.hexdigest()}" diff --git a/manager/src/grype_db_manager/grype.py b/manager/src/grype_db_manager/grype.py index d9db9806..cb3d59cc 100644 --- a/manager/src/grype_db_manager/grype.py +++ b/manager/src/grype_db_manager/grype.py @@ -22,7 +22,9 @@ class Grype: BIN = "grype" - def __init__(self, schema_version: int, store_root: str, update_url: str = "", release: str | None = None): + def __init__(self, schema_version: int | str, store_root: str, update_url: str = "", release: str | None = None): + if isinstance(schema_version, str): + schema_version = int(schema_version.split(".")[0].removeprefix("v")) self.schema_version = schema_version if release: logging.warning(f"overriding grype release for schema={schema_version!r} with release={release!r}") @@ -31,7 +33,7 @@ def __init__(self, schema_version: int, store_root: str, update_url: str = "", r self.release = schema.grype_version(schema_version) logging.debug(f"using grype release={self.release!r} for schema={schema_version!r}") - env = {} + env = self._env() if update_url: env["GRYPE_DB_UPDATE_URL"] = update_url self.tool = grype.Grype.install(version=self.release, path=os.path.join(store_root, self.release), env=env) @@ -43,20 +45,25 @@ def supported_schema_versions() -> list[str]: obj = json.load(fh) return obj.keys() + def _env(self, env: dict[str, str] | None = None) -> dict[str, str]: + if not env: + env = os.environ.copy() + return env + def update_db(self) -> None: - self.tool.run("db", "update", "-vv") + self.tool.run("db", "update", "-vv", env=self._env()) # ensure the db cache is not empty for the current schema check_db_cache_dir(self.schema_version, os.path.join(self.tool.path, "db")) def import_db(self, db_path: str) -> None: - self.tool.run("db", "import", db_path) + self.tool.run("db", "import", db_path, env=self._env()) # ensure the db cache is not empty for the current schema check_db_cache_dir(self.schema_version, os.path.join(self.tool.path, "db")) def run(self, user_input: str) -> str: - return self.tool.run("-o", "json", "-v", user_input) + return self.tool.run("-o", "json", "-v", user_input, env=self._env()) class Report: @@ -97,17 +104,17 @@ def parse(self) -> tuple[set[Package], set[Vulnerability]]: return packages, vulnerabilities -def check_db_cache_dir(schema_version: str, db_runtime_dir: str) -> None: +def check_db_cache_dir(schema_version: int, db_runtime_dir: str) -> None: """ Ensure that there is a `metadata.json` file for the cache directory, which signals that there are files related to a database pull """ # ensure the db cache is not empty for the current schema - if schema_version == "1": + if schema_version == 1: # older grype versions do not support schema-based cache directories db_metadata_file = os.path.join(db_runtime_dir, "metadata.json") else: - db_metadata_file = os.path.join(db_runtime_dir, schema_version, "metadata.json") + db_metadata_file = os.path.join(db_runtime_dir, str(schema_version), "metadata.json") if os.path.exists(db_metadata_file): # the metadata.json file exists and grype will be able to work with it @@ -121,6 +128,7 @@ def check_db_cache_dir(schema_version: str, db_runtime_dir: str) -> None: for _f in os.listdir(db_runtime_dir): logging.error(f"{_f}") + msg = f"db import appears to have failed, was expecting path: {db_metadata_file}" raise RuntimeError( - "db import appears to have failed, was expecting path: %s" % db_metadata_file, + msg, ) diff --git a/manager/src/grype_db_manager/grypedb.py b/manager/src/grype_db_manager/grypedb.py index c0acbe28..9ba60a7a 100644 --- a/manager/src/grype_db_manager/grypedb.py +++ b/manager/src/grype_db_manager/grypedb.py @@ -15,6 +15,7 @@ import uuid import requests +import xxhash from grype_db_manager.db.format import Format @@ -32,7 +33,11 @@ # however, its important to use the file for the same version of vunnel used by grype-db to build the DB, which # isn't always possible to know. Ideally this version info would be captured in the vunnel data directory directly. # For the meantime this is a snapshot of the expected namespaces for vunnel 0.17.2 in Oct 2023 (boo! 👻). -expected_namespaces = [ +v5_additional_namespaces = [ + "mariner:distro:azurelinux:3.0", +] + +v4_expected_namespaces = [ "alpine:distro:alpine:3.10", "alpine:distro:alpine:3.11", "alpine:distro:alpine:3.12", @@ -126,6 +131,7 @@ "ubuntu:distro:ubuntu:22.10", "ubuntu:distro:ubuntu:23.04", "ubuntu:distro:ubuntu:23.10", + "ubuntu:distro:ubuntu:24.04", "wolfi:distro:wolfi:rolling", ] @@ -223,10 +229,19 @@ "ubuntu:22.10", "ubuntu:23.04", "ubuntu:23.10", + "ubuntu:24.04", "wolfi:rolling", ] +def expected_namespaces(schema_version: int) -> list[str]: + if schema_version <= 3: + return v3_expected_namespaces + if schema_version == 4: + return v4_expected_namespaces + return v4_expected_namespaces + v5_additional_namespaces + + @dataclasses.dataclass class DBInfo: uuid: str @@ -235,6 +250,7 @@ class DBInfo: db_created: datetime.datetime data_created: datetime.datetime archive_path: str + latest_path: str | None = None class DBInvalidException(Exception): @@ -245,6 +261,10 @@ class DBNamespaceException(Exception): pass +class DBProviderException(Exception): + pass + + class DBManager: def __init__(self, root_dir: str): self.db_dir = os.path.join(root_dir, DB_DIR) @@ -265,16 +285,36 @@ def new_session(self) -> str: session_dir = os.path.join(self.db_dir, db_uuid) with open(os.path.join(session_dir, "timestamp"), "w") as f: - now = datetime.datetime.now(tz=datetime.timezone.utc) + now = datetime.datetime.now(tz=datetime.UTC) f.write(now.isoformat()) return db_uuid + def list_providers(self, db_uuid: str) -> list[str]: + _, build_dir = self.db_paths(db_uuid=db_uuid) + # a sqlite3 db + db_path = os.path.join(build_dir, "vulnerability.db") + + # select distinct values in the "namespace" column of the "vulnerability" table + con = sqlite3.connect(db_path) + crsr = con.cursor() + crsr.execute("SELECT DISTINCT id FROM providers") + result = crsr.fetchall() + con.close() + + return sorted([r[0] for r in result]) + def list_namespaces(self, db_uuid: str) -> list[str]: _, build_dir = self.db_paths(db_uuid=db_uuid) # a sqlite3 db db_path = os.path.join(build_dir, "vulnerability.db") + # check if there is a metadata.json file in the build directory + metadata_path = os.path.join(build_dir, "metadata.json") + if not os.path.exists(metadata_path): + msg = f"missing metadata.json for DB {db_uuid!r}" + raise DBInvalidException(msg) + # select distinct values in the "namespace" column of the "vulnerability" table con = sqlite3.connect(db_path) crsr = con.cursor() @@ -284,11 +324,22 @@ def list_namespaces(self, db_uuid: str) -> list[str]: return sorted([r[0] for r in result]) - def validate_namespaces(self, db_uuid: str) -> None: - db_info = self.get_db_info(db_uuid) + def validate_providers(self, db_uuid: str, expected: list[str]) -> None: + if not expected: + msg = "expected at least one provider" + raise DBProviderException(msg) - expected = v3_expected_namespaces if db_info.schema_version <= 3 else expected_namespaces + missing_providers = set(expected) - set(self.list_providers(db_uuid=db_uuid)) + if missing_providers: + msg = f"missing providers in DB {db_uuid!r}: {sorted(missing_providers)!r}" + raise DBProviderException(msg) + + logging.info(f"minimum expected providers present in {db_uuid!r}") + + def validate_namespaces(self, db_uuid: str) -> None: + db_info = self.get_db_info(db_uuid) + expected = expected_namespaces(db_info.schema_version) missing_namespaces = set(expected) - set(self.list_namespaces(db_uuid=db_uuid)) if missing_namespaces: @@ -310,14 +361,8 @@ def get_db_info(self, db_uuid: str) -> DBInfo | None: with open(timestamp_path) as f: db_created_timestamp = datetime.datetime.fromisoformat(f.read()) - # read info from the metadata file in build/metadata.json - metadata_path = os.path.join(session_dir, "build", "metadata.json") - if not os.path.exists(metadata_path): - msg = f"missing metadata.json for DB {db_uuid!r}" - raise DBInvalidException(msg) - - with open(metadata_path) as f: - metadata = json.load(f) + # read info from the metadata file in build/metadata.json (v1 - v5) or build/latest.json (v6+) + metadata = db_metadata(build_dir=os.path.join(session_dir, "build")) stage_dir, _ = self.db_paths(db_uuid=db_uuid) db_pattern = os.path.join( @@ -335,13 +380,18 @@ def get_db_info(self, db_uuid: str) -> DBInfo | None: abs_archive_path = os.path.abspath(matches[0]) + db_created = db_created_timestamp.strftime("%Y-%m-%dT%H:%M:%SZ") + if "db_created" in metadata: + db_created = metadata["db_created"] + return DBInfo( uuid=db_uuid, schema_version=metadata["version"], - db_checksum=metadata["checksum"], - db_created=db_created_timestamp.strftime("%Y-%m-%dT%H:%M:%SZ"), - data_created=metadata["built"], + db_checksum=metadata["db_checksum"], + db_created=db_created, + data_created=metadata["data_created"], archive_path=abs_archive_path, + latest_path=metadata.get("latest_path", None), ) def list_dbs(self) -> list[DBInfo]: @@ -360,10 +410,74 @@ def list_dbs(self) -> list[DBInfo]: return sorted(sessions, key=lambda x: x.db_created) + def remove_db(self, db_uuid: str) -> bool: + session_dir = os.path.join(self.db_dir, db_uuid) + if os.path.exists(session_dir): + shutil.rmtree(session_dir) + return True + return False + + +def db_metadata(build_dir: str) -> dict: + metadata_path = os.path.join(build_dir, "metadata.json") + + if os.path.exists(metadata_path): + # supports v1 - v5 + with open(metadata_path) as f: + metadata = json.load(f) + return { + "version": int(metadata["version"]), + "db_checksum": metadata["checksum"], + "data_created": metadata["built"], + } + + db_path = os.path.join(build_dir, "vulnerability.db") + if not os.path.exists(db_path): + msg = "missing vulnerability.db for DB" + raise DBInvalidException(msg) + + db_checksum = xxhash.xxh64() + with open(db_path, "rb") as f: + for chunk in iter(lambda: f.read(4096), b""): + db_checksum.update(chunk) + + latest_path = os.path.join(build_dir, "latest.json") + if os.path.exists(latest_path): + # supports v6+ + with open(latest_path) as f: + metadata = json.load(f) + # example data: + # { + # "status": "active", + # "schemaVersion": "v6.0.0", + # "built": "2024-11-26T20:24:24Z", + # "path": "vulnerability-db_v6.0.0_2024-11-25T01:31:56Z_1732652663.tar.zst", + # "checksum": "sha256:1a0ec0ba815083d0ef50790c8c94307c822fd7d09632dee9c3edb6bf5a58e6ff" + # } + return { + "version": int(metadata["schemaVersion"].split(".")[0].removeprefix("v")), + "db_checksum": "xxh64:" + db_checksum.hexdigest(), + "db_created": metadata["built"], + "data_created": parse_datetime(metadata["path"].split("_")[2]), + "latest_path": os.path.abspath(latest_path), + } + + msg = "missing metadata.json and latest.json for DB" + raise DBInvalidException(msg) + + +def parse_datetime(s: str) -> datetime.datetime: + return datetime.datetime.strptime(s, "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=datetime.UTC) + class GrypeDB: def __init__(self, bin_path: str, config_path: str = ""): - self.version = os.path.basename(bin_path).removeprefix("grype-db-") + if bin_path: + self.version = os.path.basename(bin_path).removeprefix("grype-db-") + else: + logging.info("using existing grype-db that is on path") + self.version = "" + self.bin_path = bin_path self.config_path = config_path @@ -383,11 +497,14 @@ def list_installed(cls, root_dir: str) -> list[GrypeDB]: @classmethod def install(cls, version: str, config_path: str, root_dir: str) -> GrypeDB: - bin_path = _install_grype_db( - input_version=version, - bin_dir=os.path.join(root_dir, BIN_DIR), - clone_dir=os.path.join(root_dir, CLONE_DIR), - ) + bin_path = None + if version != "disabled": + bin_path = _install_grype_db( + input_version=version, + bin_dir=os.path.join(root_dir, BIN_DIR), + clone_dir=os.path.join(root_dir, CLONE_DIR), + ) + return cls(bin_path=bin_path, config_path=config_path) def build_and_package(self, schema_version: int, provider_root_dir: str, root_dir: str) -> str: @@ -404,7 +521,7 @@ def build_and_package(self, schema_version: int, provider_root_dir: str, root_di db_pattern = os.path.join( build_dir, - f"*_v{schema_version}_*.tar.*", + f"*_v{schema_version}[._]*.tar.*", ) matches = glob.glob(db_pattern) @@ -443,7 +560,7 @@ def package_db(self, build_dir: str, provider_root_dir: str) -> None: ) def run(self, *args, provider_root_dir: str, config: str) -> int: - cmd = " ".join([self.bin_path, *args]) + cmd = " ".join([self.bin_path, *args]) if self.bin_path else " ".join(["grype-db", *args]) level = logging.getLevelName(logging.getLogger().getEffectiveLevel()) if level == "TRACE": # trace is not supported in grype-db yet @@ -454,11 +571,9 @@ def run(self, *args, provider_root_dir: str, config: str) -> int: env = dict( # noqa: PIE804 **os.environ.copy(), - **{ - "GRYPE_DB_VUNNEL_ROOT": provider_root_dir, - "GRYPE_DB_CONFIG": config, - "GRYPE_DB_LOG_LEVEL": level, - }, + GRYPE_DB_VUNNEL_ROOT=provider_root_dir, + GRYPE_DB_CONFIG=config, + GRYPE_DB_LOG_LEVEL=level, ) ret = subprocess.check_call(cmd, env=env, shell=True) # noqa: S602 diff --git a/manager/src/grype_db_manager/s3utils.py b/manager/src/grype_db_manager/s3utils.py index 91d5141c..a4b7965c 100644 --- a/manager/src/grype_db_manager/s3utils.py +++ b/manager/src/grype_db_manager/s3utils.py @@ -74,13 +74,13 @@ def upload(bucket: str, key: str, contents: str, client_factory: type[ClientFact def upload_file(bucket: str, key: str, path: str, client_factory: type[ClientFactory] = ClientFactory, **kwargs) -> None: - logging.debug(f"uploading file={path} to s3 bucket={bucket} key={key}") - if "ContentType" not in kwargs: content_type = mime.from_file(path) if content_type: kwargs["ContentType"] = content_type + logging.debug(f"uploading file={path} to s3 bucket={bucket} key={key} content-type={kwargs.get('ContentType', '')}") + # boto is a little too verbose... let's tone that down just for a bit with LoggingContext(level=logging.WARNING): s3 = client_factory.new() diff --git a/manager/tests/cli/.gitignore b/manager/tests/cli/.gitignore index 41589d42..6b508874 100644 --- a/manager/tests/cli/.gitignore +++ b/manager/tests/cli/.gitignore @@ -1,6 +1,7 @@ -cli-test-data +cli-test-data* .yardstick .grype-db-manager grype-db-cache.tar.gz +grype-db-cache.tar.zst venv bin \ No newline at end of file diff --git a/manager/tests/cli/.grype-db-manager.yaml b/manager/tests/cli/.grype-db-manager.yaml index 5cd0886f..8c044fcc 100644 --- a/manager/tests/cli/.grype-db-manager.yaml +++ b/manager/tests/cli/.grype-db-manager.yaml @@ -24,37 +24,46 @@ distribution: download-url-prefix: http://localhost:4566/testbucket validate: + expected-providers: + # this is what we expect for the test... + - oracle + # this next value is not true, but we include it to check if we can detect missing providers + - nvd listing: image: "docker.io/oraclelinux:6@sha256:a06327c0f1d18d753f2a60bb17864c84a850bb6dcbcf5946dd1a8123f6e75495" minimum-packages: 10 # 14 as of 2023-08-14, leaving some room for possible out-of-band changes to the data minimum-vulnerabilities: 10 # 15 as of 2023-08-14, leaving some room for possible out-of-band changes to the data - db: + default-max-year: 2021 + gates: # new vulnerabilities are added all of the time, instead of keeping up it's easier to ignore newer entries. # This approach helps tremendously with keeping the analysis relatively stable. - default-max-year: 2021 - gate: - # float between 0 and 1, the maximum allowable difference below the OSS F1 score before the gate fails (default 0, - # meaning the test F1 score must be equal to or greater than the OSS F1 score to pass the gate) - f1-score-threshold: 0.5 + - gate: + # float between 0 and 1, the maximum allowable difference below the OSS F1 score before the gate fails (default 0, + # meaning the test F1 score must be equal to or greater than the OSS F1 score to pass the gate) + max_f1_regression: 0.5 - # float between 0 and 100, the maximum % of unlabeled matches for a scan result before the gate fails (default 10%, - # meaning the test scan must have less than 10% unlabeled matches to pass the gate) - unlabeled-matches-threshold: 10.0 + # float between 0 and 100, the maximum % of unlabeled matches for a scan result before the gate fails (default 10%, + # meaning the test scan must have less than 10% unlabeled matches to pass the gate) + max_unlabeled_percent: 10 - # integer, the maximum allowable introduced FNs by the test scan (but found by the OSS scan) before the gate fails - # (default 0, meaning the test scan must have the same or fewer FNs than the OSS scan to pass the gate) - introduced-fns-threshold: 10 + # integer, the maximum allowable introduced FNs by the test scan (but found by the OSS scan) before the gate fails + # (default 0, meaning the test scan must have the same or fewer FNs than the OSS scan to pass the gate) + max_new_false_negatives: 10 + # determines which tool is being validated against which + # in other words, this gate asserts that the tool labeled 'custom-db' + # performs as well as or better than the tool not thus labeled. + candidate_tool_label: 'custom-db' - grype: - version: latest + grype: + version: latest - syft: - # this should USUALLY match the version of syft that is referenced in vulnerability-match-labels on the main branch. - # However, in this case we are going to allow for this version to drift in testing. It's ok if we download - # the SBOM and NOT use it, since we are testing the workflow generally works. - version: v0.86.1 + syft: + # this should USUALLY match the version of syft that is referenced in vulnerability-match-labels on the main branch. + # However, in this case we are going to allow for this version to drift in testing. It's ok if we download + # the SBOM and NOT use it, since we are testing the workflow generally works. + version: v0.86.1 - # note: always reference images with BOTH a tag and a digest - images: - - docker.io/oraclelinux:6@sha256:a06327c0f1d18d753f2a60bb17864c84a850bb6dcbcf5946dd1a8123f6e75495 + # note: always reference images with BOTH a tag and a digest + images: + - docker.io/oraclelinux:6@sha256:a06327c0f1d18d753f2a60bb17864c84a850bb6dcbcf5946dd1a8123f6e75495 diff --git a/manager/tests/cli/.grype-db.yaml b/manager/tests/cli/.grype-db.yaml index e7860813..88b97a12 100644 --- a/manager/tests/cli/.grype-db.yaml +++ b/manager/tests/cli/.grype-db.yaml @@ -4,7 +4,7 @@ provider: root: cli-test-data/vunnel configs: - # let's use a single provider that we can show in isolation the setup is generally working. We don't + # let's use a limited set of providers that we can show in isolation the setup is generally working. We don't # need all providers / an entire database to test the workflow. - name: oracle kind: vunnel diff --git a/manager/tests/cli/Makefile b/manager/tests/cli/Makefile index 57c53715..03ade28d 100644 --- a/manager/tests/cli/Makefile +++ b/manager/tests/cli/Makefile @@ -5,18 +5,20 @@ BOLD := $(shell tput -T linux bold)) CYAN := $(shell tput -T linux setaf 6) RESET := $(shell tput -T linux sgr0) -test: virtual-env-check ## Run CLI tests - ./run.sh +test: ## Run CLI tests + uv run pytest . -vv -o log_cli=true -cli-test-data/vunnel/oracle: ## Prepare data for CLI tests +.PHONY: vunnel-oracle-data +vunnel-oracle-data: cli-test-data/vunnel/oracle + +cli-test-data/vunnel/oracle: ## Prepare oracle data for CLI tests mkdir -p cli-test-data/vunnel - oras pull ghcr.io/anchore/grype-db/data/oracle:latest && go run ../../../cmd/grype-db cache restore --path ./grype-db-cache.tar.gz + ../../../.tool/oras pull ghcr.io/anchore/grype-db/data/oracle:latest && go run ../../../cmd/grype-db cache restore --path ./grype-db-cache.tar.gz -virtual-env-check: - @ if [ "${VIRTUAL_ENV}" = "" ]; then \ - echo "$(ERROR)Not in a virtual environment. Try running with 'poetry run' or enter a 'poetry shell' session.$(RESET)"; \ - exit 1; \ - fi +.PHONY: install-oracle-labels +install-oracle-labels: + mkdir -p cli-test-data/yardstick/labels + cp -a ../../../data/vulnerability-match-labels/labels/docker.io+oraclelinux* ./cli-test-data/yardstick/labels/ .PHONY: clean clean: clean-data ## Clear all existing yardstick results and delete python environment diff --git a/manager/tests/cli/README.md b/manager/tests/cli/README.md index a511ec5f..b6f7363b 100644 --- a/manager/tests/cli/README.md +++ b/manager/tests/cli/README.md @@ -17,9 +17,8 @@ If you'd like to run a single test: ```shell # from the manager/tests/cli directory -./run.sh +pytest . -vv -o log_cli=true -k # e.g. -# ./run.sh workflow-3-update-listing.sh -# ./run.sh workflow-*db.sh +# pytest . -vv -o log_cli=true -k test_workflow_4 ``` diff --git a/manager/tests/cli/conftest.py b/manager/tests/cli/conftest.py new file mode 100644 index 00000000..95cf99e2 --- /dev/null +++ b/manager/tests/cli/conftest.py @@ -0,0 +1,205 @@ +import os +import shlex +import subprocess +import pytest +import logging +from enum import Enum +from pathlib import Path +from contextlib import contextmanager +from tempfile import TemporaryDirectory + + +class Format(Enum): + RESET = "\033[0m" + GREEN = "\033[1;32m" + RED = "\033[1;31m" + GREY = "\033[0;37m" + PURPLE = "\033[1;35m" + ORANGE_BOLD = "\033[1;33m" + ITALIC = "\033[3m" + BOLD = "\033[1m" + + def render(self, text: str) -> str: + return f"{self.value}{text}{Format.RESET.value}" + + +class CustomLogger(logging.Logger): + + def __init__(self, name, level=logging.NOTSET): + super().__init__(name, level) + self.test_function = None # Placeholder for test-specific context + + def step(self, message: str): + if self.test_function: + message = f"[{self.test_function}] {message}" + self.info(Format.GREEN.render(message)) + + +@pytest.fixture(scope="function") +def logger(request): + logging.setLoggerClass(CustomLogger) + logger = logging.getLogger(f"test_logger_{id(object())}") + logger.setLevel(logging.DEBUG) + + # set the test function name dynamically + logger.test_function = request.node.name + + return logger + + +@pytest.fixture(scope="function", autouse=True) +def change_to_cli_dir(request): + """ + Automatically change the working directory to the directory containing the test file + if it's not already set, and revert back after the test. + """ + # the directory of the current test file (which is in manage/tests/cli) + cli_dir = request.fspath.dirname + original_dir = os.getcwd() + + # bail if already in the target directory + if os.path.samefile(original_dir, cli_dir): + yield # run the test + return + + # change to the target directory + if not os.path.isdir(cli_dir): + raise FileNotFoundError(f"Expected directory '{cli_dir}' does not exist.") + + os.chdir(cli_dir) + try: + yield # run the test + finally: + os.chdir(original_dir) # revert to the original directory + + +@pytest.fixture(scope="session") +def temporary_dir() -> str: + with TemporaryDirectory() as tmp_dir: + yield tmp_dir + + +@pytest.fixture(scope="session") +def cli_env() -> dict[str, str]: + env = os.environ.copy() + env["PATH"] = f"{os.path.abspath('bin')}:{env['PATH']}" # add `bin` to PATH + return env + + +class CommandHelper: + + def __init__(self, logger: logging.Logger): + self.logger = logger + + def run(self, command: str, env=None, expect_fail=False, use_shell=True, **kwargs) -> tuple[str, str]: + self.logger.info(Format.ITALIC.render(f"{command}")) + + process = subprocess.run( + command if use_shell else shlex.split(command), + shell=use_shell, # use shell expansion if requested + capture_output=True, + text=True, + env=env, + **kwargs, + ) + + # log stdout and stderr when an error occurs + if process.returncode != 0 and not expect_fail: + self.logger.error(Format.RED.render("└── command failed unexpectedly")) + log_lines(process.stdout, " ", self.logger.error, Format.RED.render) + log_lines(process.stderr, " ", self.logger.error, Format.RED.render) + raise AssertionError("command failed unexpectedly") + elif process.returncode == 0 and expect_fail: + self.logger.error(Format.RED.render("└── expected failure, but command succeeded")) + log_lines(process.stdout, " ", self.logger.error, Format.RED.render) + log_lines(process.stderr, " ", self.logger.error, Format.RED.render) + raise AssertionError("command succeeded but was expected to fail") + + # log success + self.logger.debug(Format.GREY.render("└── command succeeded")) + return process.stdout.strip(), process.stderr.strip() + + @contextmanager + def pushd(self, path, logger): + """Temporarily change directory.""" + prev_dir = os.getcwd() + logger.info(f"pushd {path}") + os.chdir(path) + try: + yield + finally: + logger.info(f"popd # {prev_dir}") + os.chdir(prev_dir) + + +def log_lines(text: str, prefix: str, lgr, renderer=None): + for line in text.splitlines(): + msg = f"{prefix}{line}" + if renderer: + msg = renderer(msg) + lgr(msg) + + +@pytest.fixture +def command(logger) -> CommandHelper: + return CommandHelper(logger) + + +class GrypeHelper: + def __init__(self, bin_dir: str | Path | None = None): + if bin_dir: + self.bin_dir = Path(bin_dir) + else: + self.bin_dir = None + self.command = CommandHelper(logging.getLogger("grype")) + + def run(self, cmd: str, env: dict[str, str] | None = None, **kwargs) -> tuple[str, str]: + return self.command.run(f"{self.bin_dir}/grype {cmd}", env=env, **kwargs) + + def install(self, branch_or_version: str, bin_dir: str | None = None, env: dict[str, str] | None = None) -> "GrypeHelper": + """ + Install Grype either by building from a feature branch or downloading a prebuilt binary. + """ + if not bin_dir and not self.bin_dir: + raise ValueError("bin_dir is required for Grype installation") + + if bin_dir: + bin_dir = Path(bin_dir) + else: + bin_dir = self.bin_dir + + grype_binary = Path(bin_dir) / "grype" + + if branch_or_version.startswith("v"): + self.command.run( + f"curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b {bin_dir} {branch_or_version}", + use_shell=True, + env=env, + ) + + if not grype_binary.exists(): + raise RuntimeError("Grype binary installation failed via install.sh") + + else: + with TemporaryDirectory() as temp_dir: + self.command.run( + f"git clone --branch {branch_or_version} https://github.com/anchore/grype.git {temp_dir}", + check=True, + env=env, + ) + self.command.run( + f"go build -o {grype_binary} -ldflags '-X github.com/anchore/grype-db/pkg/grypedb.Version={branch_or_version}' ./cmd/grype", + cwd=temp_dir, + check=True, + env=env, + ) + + if not grype_binary.exists(): + raise RuntimeError("Grype binary build failed from feature branch") + + return GrypeHelper(bin_dir) + + +@pytest.fixture(scope="session") +def grype(): + return GrypeHelper() diff --git a/manager/tests/cli/run.sh b/manager/tests/cli/run.sh deleted file mode 100755 index 9f292765..00000000 --- a/manager/tests/cli/run.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env bash - -. utils.sh - -# if no arguments are given then use case-*.sh, otherwise use the files given -if [ $# -eq 0 ]; then - files=$(find . -maxdepth 1 -type f -name "workflow-*.sh" | sort) -else - files=$@ -fi - -if [ -z "$files" ]; then - echo "No test files found" - exit 1 -fi - -title "Test scripts to run:" -for script in $files; do - echo " $script" -done -echo - -# run all scripts in the current directory named case-*.sh and exit on first failure -status=0 -for script in $files; do - bash -c "./$script" || { status=1; break; } -done - -if [ $status -eq 0 ]; then - echo -e "${SUCCESS}All tests passed${RESET}" -else - echo -e "${ERROR}Some tests failed${RESET}" -fi - -exit $status \ No newline at end of file diff --git a/manager/tests/cli/s3-mock/setup-workflow-3.py b/manager/tests/cli/s3-mock/setup-legacy-workflow-3.py similarity index 100% rename from manager/tests/cli/s3-mock/setup-workflow-3.py rename to manager/tests/cli/s3-mock/setup-legacy-workflow-3.py diff --git a/manager/tests/cli/s3-mock/setup-workflow-4.py b/manager/tests/cli/s3-mock/setup-legacy-workflow-4.py similarity index 100% rename from manager/tests/cli/s3-mock/setup-workflow-4.py rename to manager/tests/cli/s3-mock/setup-legacy-workflow-4.py diff --git a/manager/tests/cli/s3-mock/setup-workflow-1.py b/manager/tests/cli/s3-mock/setup-workflow-1.py new file mode 100644 index 00000000..dec6fdcc --- /dev/null +++ b/manager/tests/cli/s3-mock/setup-workflow-1.py @@ -0,0 +1,37 @@ +import os +import requests +import shutil + +# the credentials are not required for localstack, but the boto3 client will complain if they are not set +os.environ["AWS_ACCESS_KEY_ID"] = "test" +os.environ["AWS_SECRET_ACCESS_KEY"] = "test" + +from grype_db_manager import s3utils +from grype_db_manager.cli import config + + +def main(): + cfg = config.load() + + s3_bucket = cfg.distribution.s3_bucket + region = cfg.distribution.aws_region + + if not bucket_exists(s3_bucket): + print(f"creating bucket {s3_bucket!r}") + s3 = s3utils.ClientFactory.new() + s3.create_bucket(Bucket=s3_bucket, CreateBucketConfiguration={"LocationConstraint": region}) + + print("done!") + + +def bucket_exists(bucket: str): + try: + list(s3utils.get_matching_s3_objects(bucket=bucket, prefix="")) + return True + except Exception as e: + pass + return False + + +if __name__ == "__main__": + main() diff --git a/manager/tests/cli/test_legacy_workflows.py b/manager/tests/cli/test_legacy_workflows.py new file mode 100644 index 00000000..b1abef78 --- /dev/null +++ b/manager/tests/cli/test_legacy_workflows.py @@ -0,0 +1,236 @@ +import pytest + +from grype_db_manager.db import schema + + +@pytest.mark.usefixtures("cli_env") +def test_workflow_1(cli_env, command, logger): + """ + workflow 1: create and delete a DB + """ + + logger.step("setup: clear previous data") + command.run("make clean-manager", env=cli_env) + command.run("make vunnel-oracle-data", env=cli_env) + + logger.step("case 1: create the DB") + stdout, _ = command.run("grype-db-manager -v db build -s 5", env=cli_env) + assert stdout.strip(), "Expected non-empty output" + db_id = stdout.splitlines()[-1] # assume DB ID is the last line of output + + stdout, _ = command.run("grype-db-manager db list", env=cli_env) + assert db_id in stdout, f"Expected DB ID {db_id} in output" + + logger.step("case 2: delete the DB") + command.run("grype-db-manager db clear", env=cli_env) + stdout, _ = command.run("grype-db-manager db list", env=cli_env) + assert db_id not in stdout, f"Did not expect DB ID {db_id} in output" + + +@pytest.mark.usefixtures("cli_env") +def test_workflow_2(cli_env, command, logger): + """ + workflow 2: validate DB + This test creates a database from raw vunnel data and performs validations under different conditions. + """ + + logger.step("setup: create the DB") + command.run("make clean-manager", env=cli_env) + command.run("make vunnel-oracle-data", env=cli_env) + + # create the database + stdout, _ = command.run("grype-db-manager -v db build -s 5", env=cli_env) + assert stdout.strip(), "Expected non-empty output" + db_id = stdout.splitlines()[-1] # Get the last line as the DB ID + + ### case 1: fail DB validation (too many unknowns) ### + logger.step("case 1: fail DB validation (too many unknowns)") + command.run("make clean-yardstick-labels", env=cli_env) + + # workaround for Go 1.23+ parent directory module lookup + cli_env["GOWORK"] = "off" + + stdout, _ = command.run( + f"grype-db-manager -vv db validate {db_id} --skip-namespace-check --recapture", + env=cli_env, + expect_fail=True, + ) + assert "current indeterminate matches % is greater than 10%" in stdout + + ### case 2: fail DB validation (missing namespaces) ### + logger.step("case 2: fail DB validation (missing namespaces)") + command.run("make clean-yardstick-labels", env=cli_env) + + logger.info("installing labels") + command.run("make install-oracle-labels", env=cli_env) + + _, stderr = command.run( + f"grype-db-manager -vv db validate {db_id}", + env=cli_env, + expect_fail=True, + ) + assert "missing namespaces in DB" in stderr + + ### case 3: pass DB validation ### + logger.step("case 3: pass DB validation") + command.run("make clean-yardstick-labels", env=cli_env) + + logger.info("installing labels") + command.run("make install-oracle-labels", env=cli_env) + + stdout, _ = command.run( + f"grype-db-manager -vv db validate {db_id} --skip-namespace-check", + env=cli_env, + ) + assert "Quality gate passed!" in stdout + + +@pytest.mark.usefixtures("cli_env") +def test_workflow_3(cli_env, command, logger, tmp_path, grype): + """ + workflow 3: update an existing listing file + This test uses a mock S3 setup to upload databases, generate a new listing file, and validate that the updated + listing file works with grype for scanning. + """ + + logger.step("setup: prepare environment variables and directories") + + # set environment variables for aws and grype + bin_dir = tmp_path / "bin" + bin_dir.mkdir(parents=True, exist_ok=True) + + # deep copy cli_env to avoid modifying the original + cli_env = cli_env.copy() + cli_env.update( + { + "AWS_ACCESS_KEY_ID": "test", + "AWS_SECRET_ACCESS_KEY": "test", + "AWS_REGION": "us-west-2", + "PATH": f"{bin_dir}:{cli_env['PATH']}", # ensure `bin` directory is in PATH + } + ) + + grype = grype.install("v0.65.0", bin_dir) + + logger.step("setup: start mock S3 and upload databases") + with command.pushd("s3-mock", logger): + command.run("docker compose up -d", env=cli_env) + command.run("python setup-legacy-workflow-3.py", env=cli_env) + + ### start of testing ### + logger.step("case 1: update a listing file based on S3 state") + + # generate a new listing file + stdout, _ = command.run("grype-db-manager listing update", env=cli_env) + assert "Validation passed" in stdout + assert "listing.json uploaded to s3://testbucket/grype/databases" in stdout + + # setup grype for DB updates and scans + cli_env.update( + { + "GRYPE_DB_UPDATE_URL": "http://localhost:4566/testbucket/grype/databases/listing.json", + "GRYPE_DB_CACHE_DIR": str(bin_dir), + } + ) + + # validate grype DB listing and scanning + stdout, _ = grype.run(f"db list", env=cli_env) + assert "http://localhost:4566" in stdout + + stdout, _ = grype.run(f"db update", env=cli_env) + + stdout, _ = grype.run(f"--platform linux/amd64 --by-cve alpine:3.2", env=cli_env) + assert "CVE-2016-2148" in stdout + + ### end of testing ### + + logger.step("teardown: stop mock S3 and clean up") + with command.pushd("s3-mock", logger): + command.run("docker compose down -t 1 -v", env=cli_env) + + +@pytest.mark.usefixtures("cli_env") +def test_workflow_4(cli_env, command, logger, tmp_path, grype): + """ + workflow 4: full publish workflow + This test builds and validates a new DB from raw vunnel data, uploads the DB to a mock S3, updates the listing file, + and uses the updated listing file in a grype scan. + """ + + logger.step("setup: prepare environment variables and directories") + + # set environment variables for aws, grype, and schema versions + bin_dir = tmp_path / "bin" + bin_dir.mkdir(parents=True, exist_ok=True) + + schema_version = "5" + # deep copy cli_env to avoid modifying the original + cli_env = cli_env.copy() + cli_env.update( + { + "AWS_ACCESS_KEY_ID": "test", + "AWS_SECRET_ACCESS_KEY": "test", + "AWS_REGION": "us-west-2", + "SCHEMA_VERSION": schema_version, + "GRYPE_DB_MANAGER_VALIDATE_LISTING_OVERRIDE_GRYPE_VERSION": "v0.65.0", + "GRYPE_DB_MANAGER_VALIDATE_LISTING_OVERRIDE_DB_SCHEMA_VERSION": "5", + "PATH": f"{bin_dir}:{cli_env['PATH']}", # ensure `bin` directory is in PATH + } + ) + + grype = grype.install(schema.grype_version(schema_version), bin_dir) + + logger.step("setup: clean manager and prepare data") + command.run("make clean-manager", env=cli_env) + command.run("make vunnel-oracle-data", env=cli_env) + command.run("make install-oracle-labels", env=cli_env) + + logger.step("setup: start mock S3 and upload initial data") + with command.pushd("s3-mock", logger): + command.run("docker compose up -d", env=cli_env) + command.run("python setup-legacy-workflow-4.py", env=cli_env) + + ### start of testing ### + logger.step("case 1: create and publish a DB") + + # build, validate, and upload the database + stdout, _ = command.run( + f"grype-db-manager db build-and-upload --schema-version {schema_version} --skip-namespace-check", + env=cli_env, + ) + assert "Quality gate passed!" in stdout + assert "' uploaded to s3://testbucket/grype/databases" in stdout + + logger.step("case 2: update the listing file based on the DB uploaded") + + # update the listing file and validate + stdout, _ = command.run("grype-db-manager -v listing update", env=cli_env) + assert "Validation passed" in stdout + assert "listing.json uploaded to s3://testbucket/grype/databases" in stdout + + # set grype environment variables + cli_env.update( + { + "GRYPE_DB_UPDATE_URL": "http://localhost:4566/testbucket/grype/databases/listing.json", + "GRYPE_DB_CACHE_DIR": str(bin_dir), + } + ) + + # validate grype DB listing and scanning + stdout, _ = grype.run("db list", env=cli_env) + assert "http://localhost:4566" in stdout + + stdout, _ = grype.run("db update", env=cli_env) + assert "Vulnerability database updated" in stdout + + stdout, _ = grype.run( + "docker.io/oraclelinux:6@sha256:a06327c0f1d18d753f2a60bb17864c84a850bb6dcbcf5946dd1a8123f6e75495 --by-cve", + env=cli_env, + ) + assert "ELSA-2021-9591" in stdout + + ### end of testing ### + + logger.step("teardown: stop mock S3 and clean up") + with command.pushd("s3-mock", logger): + command.run("docker compose down -t 1 -v", env=cli_env) diff --git a/manager/tests/cli/test_workflows.py b/manager/tests/cli/test_workflows.py new file mode 100644 index 00000000..e21c02b0 --- /dev/null +++ b/manager/tests/cli/test_workflows.py @@ -0,0 +1,137 @@ +import pytest + +from grype_db_manager.db import schema +from grype_db_manager.cli import config + + +@pytest.mark.usefixtures("cli_env") +def test_workflow_1(cli_env, command, logger, tmp_path, grype): + """ + workflow 1: create, upload, and delete a DB + """ + logger.step("setup: prepare environment variables and directories") + + # set environment variables for aws and grype + bin_dir = tmp_path / "bin" + bin_dir.mkdir(parents=True, exist_ok=True) + schema_version = "6" + # deep copy cli_env to avoid modifying the original + cli_env = cli_env.copy() + cli_env.update( + { + "AWS_ACCESS_KEY_ID": "test", + "AWS_SECRET_ACCESS_KEY": "test", + "AWS_REGION": "us-west-2", + "GRYPE_DB_AUTO_UPDATE": "false", # disable auto-updating the database to avoid unexpected behavior + "GOWORK": "off", # workaround for Go 1.23+ parent directory module lookup + "PATH": f"{bin_dir}:{cli_env['PATH']}", # ensure `bin` directory is in PATH + "GOBIN": bin_dir, + "GRYPE_DB_UPDATE_URL": f"http://localhost:4566/testbucket/grype/databases/v{schema_version}/latest.json", + "GRYPE_DB_CACHE_DIR": str(bin_dir), + } + ) + + cfg = config.load() + image = cfg.validate.gates[0].images[0] + + grype = grype.install(schema.grype_version(schema_version), bin_dir) + + logger.step("setup: clear previous data") + command.run("make clean-manager", env=cli_env) + command.run("make vunnel-oracle-data", env=cli_env) + + logger.step("setup: start mock S3") + with command.pushd("s3-mock", logger): + command.run("docker compose up -d", env=cli_env) + command.run("python setup-workflow-1.py", env=cli_env) + + logger.step("case 1: create the DB") + stdout, _ = command.run(f"grype-db-manager -v db build -s {schema_version}", env=cli_env) + assert stdout.strip(), "Expected non-empty output" + db_id = stdout.splitlines()[-1] # assume DB ID is the last line of output + + stdout, _ = command.run("grype-db-manager db list", env=cli_env) + assert db_id in stdout, f"Expected DB ID {db_id} in output" + + logger.step("case 2: upload the DB") + stdout, _ = command.run(f"grype-db-manager db upload {db_id}", env=cli_env) + assert f"DB archive '{db_id}' uploaded to s3://testbucket/grype/databases/v{schema_version}" in stdout + assert f"latest.json '{db_id}' uploaded to s3://testbucket/grype/databases/v{schema_version}" in stdout + + logger.step("case 3: use the DB with grype") + stdout, _ = grype.run("db update -v", env=cli_env) + assert "Vulnerability database updated" in stdout + + stdout, _ = grype.run(f"--platform linux/amd64 {image}", env=cli_env) + assert "ELSA-2021-9314" in stdout + + logger.step("case 4: delete the DB") + command.run("grype-db-manager db clear", env=cli_env) + stdout, _ = command.run("grype-db-manager db list", env=cli_env) + assert db_id not in stdout, f"Did not expect DB ID {db_id} in output" + + ### end of testing ### + + logger.step("teardown: stop mock S3 and clean up") + with command.pushd("s3-mock", logger): + command.run("docker compose down -t 1 -v", env=cli_env) + + +@pytest.mark.usefixtures("cli_env") +def test_workflow_2(cli_env, command, logger): + """ + workflow 2: validate DB + This test creates a database from raw vunnel data and performs validations via the quality gate. + """ + + logger.step("setup: create the DB") + command.run("make clean-manager", env=cli_env) + command.run("make vunnel-oracle-data", env=cli_env) + + # create the database + stdout, _ = command.run("grype-db-manager -v db build -s 6", env=cli_env) + assert stdout.strip(), "Expected non-empty output" + db_id = stdout.splitlines()[-1] # Get the last line as the DB ID + + ### case 1: fail DB validation (too many unknowns) ### + logger.step("case 1: fail DB validation (too many unknowns)") + command.run("make clean-yardstick-labels", env=cli_env) + + # workaround for Go 1.23+ parent directory module lookup + cli_env["GOWORK"] = "off" + + # note: we add --force to ensure we're checking validations (even if it's disabled for the schema) + stdout, stderr = command.run( + f"grype-db-manager -vv db validate {db_id} --skip-namespace-check --force --recapture", + env=cli_env, + expect_fail=True, + ) + + assert "current indeterminate matches % is greater than 10%" in stdout + + ## case 2: fail DB validation (missing providers) ### + logger.step("case 2: fail DB validation (missing providers)") + command.run("make clean-yardstick-labels", env=cli_env) + + logger.info("installing labels") + command.run("make install-oracle-labels", env=cli_env) + + _, stderr = command.run( + f"grype-db-manager -vv db validate {db_id} --force", + env=cli_env, + expect_fail=True, + ) + assert "missing providers in DB" in stderr + + ### case 3: pass DB validation ### + logger.step("case 3: pass DB validation") + command.run("make clean-yardstick-labels", env=cli_env) + + logger.info("installing labels") + command.run("make install-oracle-labels", env=cli_env) + + stdout, _ = command.run( + f"grype-db-manager -vv db validate {db_id} --skip-namespace-check --force", + env=cli_env, + ) + assert "Quality gate passed!" in stdout diff --git a/manager/tests/cli/utils.sh b/manager/tests/cli/utils.sh deleted file mode 100755 index 312496a9..00000000 --- a/manager/tests/cli/utils.sh +++ /dev/null @@ -1,128 +0,0 @@ -#!/usr/bin/env bash -set -u - -ERROR="\033[1;31m" -SUCCESS="\033[1;32m" -STEP="\033[1;33m" -HEADER="\033[1;34m" -TITLE="\033[1;35m" -RESET="\033[0m" - -i=0 - -stdout_files=() -stderr_files=() - - - -function _run_and_capture() { - stdout_tmp_file=$(mktemp /tmp/grype-db-manager-test-stdout.XXXXXX) - stderr_tmp_file=$(mktemp /tmp/grype-db-manager-test-stderr.XXXXXX) - stdout_files+=( $stdout_tmp_file ) - stderr_files+=( $stderr_tmp_file ) - - echo -e "${STEP}$i| Running $@${RESET}" - - # we want to capture stdout and stderr to files but also print them to the screen in realtime. Using tee is the - # best resource for this, but there is an added challenge of needing the return code of the original command - # (which is now in a subshell). The "exit PIPESTATUS[0]" solves this by promoting the first command's return - # code as the subshell's return code. - ($@ | tee $stdout_tmp_file ; exit ${PIPESTATUS[0]}) 3>&1 1>&2 2>&3 | tee $stderr_tmp_file - rc=${PIPESTATUS[0]} - return $rc -} - -function run() { - _run_and_capture $@ - rc=$? - if [ $rc -eq 0 ]; then - echo -e "${SUCCESS}Success${RESET}" - else - echo -e "${ERROR}Failed: expected zero return code but got $rc${RESET}" - exit 1 - fi - ((i++)) -} - -function run_expect_fail() { - _run_and_capture $@ - rc=$? - if [ $rc -eq 0 ]; then - echo -e "${ERROR}Failed: expected non-zero return code but got $rc${RESET}" - exit 1 - else - echo -e "${SUCCESS}Success: exited with non-zero return code: $rc${RESET}" - fi - ((i++)) -} - -function last_stdout_file() { - echo ${stdout_files[${#stdout_files[@]} - 1]} -} - -function last_stderr_file() { - echo ${stderr_files[${#stderr_files[@]} - 1]} -} - -function last_stdout() { - cat $(last_stdout_file) -} - -function last_stderr() { - cat $(last_stderr_file) -} - -function assert_not_empty() { - output_file=$1 - len=$(cat $output_file | wc -l | tr -d ' ') - if [[ "$len" -gt 0 ]]; then - return - fi - echo -e "${ERROR}Unexpected length $len${RESET}" - exit 1 -} - -function assert_contains() { - output_file=$1 - target=$2 - is_in_file=$(cat $output_file | grep -c "$target") - if [ $is_in_file -eq 0 ]; then - echo -e "${ERROR}Target not found in contents '$target'${RESET}" - echo -e "${ERROR}...contents:\n$(cat $output_file)${RESET}" - exit 1 - fi -} - -function assert_does_not_contain() { - output_file=$1 - target=$1 - is_in_file=$(cat $output_file | grep -c "$target") - if [ $is_in_file -ne 0 ]; then - echo -e "${ERROR}Target found in contents '$target'${RESET}" - echo -e "${ERROR}...contents:\n$(cat output_file)${RESET}" - exit 1 - fi -} - -function header() { - echo -e "${HEADER}$@${RESET}" -} - -function title() { - echo -e "${TITLE}$@${RESET}" -} - -function end_testing() { - echo "cleaning up temp files created:" - for i in ${!stdout_files[@]}; do - echo " " ${stdout_files[$i]} - rm ${stdout_files[$i]} - done - - for i in ${!stderr_files[@]}; do - echo " " ${stderr_files[$i]} - rm ${stderr_files[$i]} - done - - echo -e "\n${SUCCESS}PASS${RESET}" -} diff --git a/manager/tests/cli/workflow-1-create-and-delete-db.sh b/manager/tests/cli/workflow-1-create-and-delete-db.sh deleted file mode 100755 index 83170857..00000000 --- a/manager/tests/cli/workflow-1-create-and-delete-db.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env bash - -. utils.sh - -title "Starting workflow 1: create and delete DB" - -header "Setup: clear previous data" - -make clean-manager -make cli-test-data/vunnel/oracle - - -### Start of testing ######################## -header "Case 1: create the DB" - -run grype-db-manager -v db build -s 5 -assert_not_empty $(last_stdout_file) -DB_ID="$(last_stdout)" -run grype-db-manager db list - -assert_contains "$(last_stdout_file)" $DB_ID - - -############################################# -header "Case 2: delete the DB" - -run grype-db-manager db clear -run grype-db-manager db list -assert_does_not_contain "$(last_stdout_file)" $DB_ID - - -### End of testing ######################## -end_testing diff --git a/manager/tests/cli/workflow-2-validate-db.sh b/manager/tests/cli/workflow-2-validate-db.sh deleted file mode 100755 index 377a7d90..00000000 --- a/manager/tests/cli/workflow-2-validate-db.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env bash - -. utils.sh - -title "Starting workflow 2: validate DB" -# this test uses raw vunnel data to create a DB from scratch and see if it passes validation. There are different sets -# of labels to trigger a failing validation as well as passing validations under other non-ideal conditions. -# ultimately it is up to unit tests to fully exercise the validation logic, but this test is a good sanity check -# that the data needed for validations is wired up correctly. - -header "Setup: create the DB" -make clean-manager -make cli-test-data/vunnel/oracle -run grype-db-manager -v db build -s 5 -assert_not_empty $(last_stdout_file) -DB_ID="$(last_stdout)" - -### Start of testing ######################## -header "Case 1: fail DB validation (too many unknowns)" - -make clean-yardstick-labels - -run_expect_fail grype-db-manager db validate $DB_ID -vvv --skip-namespace-check -assert_contains $(last_stderr_file) "current indeterminate matches % is greater than 10.0%" - - -############################################# -header "Case 2: fail DB validation (missing namespaces)" - -make clean-yardstick-labels -echo "installing labels" -# use the real labels -cp -a ../../../data/vulnerability-match-labels/labels/docker.io+oraclelinux* ./cli-test-data/yardstick/labels/ -tree ./cli-test-data/yardstick/labels/ - -run_expect_fail grype-db-manager db validate $DB_ID -vvv -assert_contains $(last_stderr_file) "missing namespaces in DB" - - -############################################# -header "Case 3: pass DB validation" - -make clean-yardstick-labels -echo "installing labels" -# use the real labels -cp -a ../../../data/vulnerability-match-labels/labels/docker.io+oraclelinux* ./cli-test-data/yardstick/labels/ -tree ./cli-test-data/yardstick/labels/ - -run grype-db-manager db validate $DB_ID -vvv --skip-namespace-check -assert_contains $(last_stdout_file) "Validation passed" - - -### End of testing ######################## -end_testing diff --git a/manager/tests/cli/workflow-3-update-listing.sh b/manager/tests/cli/workflow-3-update-listing.sh deleted file mode 100755 index eb30b666..00000000 --- a/manager/tests/cli/workflow-3-update-listing.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env bash - -. utils.sh - -title "Starting workflow 3: update the listing file" -# this uses real, already-built DBs (from the production workflow) to exercise the listing file update logic. -# an S3 mock is used to upload a set of DBs and to generate a new listing file from. The uploaded listing file -# is then used by grype to download the correct DB and run a scan. - -# note: these credentials / configurations must match the ones used in s3-mock/setup.py and .grype-db-manager.yaml -export AWS_ACCESS_KEY_ID="test" -export AWS_SECRET_ACCESS_KEY="test" -export AWS_REGION="us-west-2" - -GRYPE_VERSION="v0.65.0" - -set -e - -BIN_DIR="./bin" - -rm -rf $BIN_DIR - -curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b $BIN_DIR $GRYPE_VERSION - -pushd s3-mock -docker-compose up -d -python setup-workflow-3.py -popd - -set +e - -### Start of testing ######################## -header "Case 1: update a listing file based on S3 state" - -# note: this test is exercising the following commands: -# grype-db-manager listing create -# grype-db-manager listing validate - -run grype-db-manager listing update -assert_contains $(last_stdout_file) "Validation passed" -assert_contains $(last_stdout_file) "listing.json uploaded to s3://testbucket/grype/databases" - -# check if grype works with this updated listing file -export GRYPE_DB_UPDATE_URL="http://localhost:4566/testbucket/grype/databases/listing.json" -export GRYPE_DB_CACHE_DIR=$BIN_DIR - -run bin/grype db list - -assert_contains $(last_stdout_file) "http://localhost:4566" - -run bin/grype db update - -run bin/grype alpine:3.2 - -assert_contains $(last_stdout_file) "CVE-2016-2148" - - -### End of testing ######################## - -pushd s3-mock -docker-compose down -t 1 -v -popd - -end_testing diff --git a/manager/tests/cli/workflow-4-full-publish.sh b/manager/tests/cli/workflow-4-full-publish.sh deleted file mode 100755 index b5e8ff38..00000000 --- a/manager/tests/cli/workflow-4-full-publish.sh +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env bash - -. utils.sh - -title "Starting workflow 4: full publish workflow" -# this test exercises the full publish workflow, by building and validating a new DB from raw vunnel data, -# uploading the DB to an S3 mock, updating and upload the listing file, and then using the updated listing file -# in a grype scan. - -# note: these credentials / configurations must match the ones used in s3-mock/setup.py and .grype-db-manager.yaml -export AWS_ACCESS_KEY_ID="test" -export AWS_SECRET_ACCESS_KEY="test" -export AWS_REGION="us-west-2" - -GRYPE_VERSION="v0.65.0" -SCHEMA_VERSION="5" - -# there are what are used in the staging pipeline for a single DB build -export GRYPE_DB_MANAGER_VALIDATE_LISTING_OVERRIDE_GRYPE_VERSION=$GRYPE_VERSION -export GRYPE_DB_MANAGER_VALIDATE_LISTING_OVERRIDE_DB_SCHEMA_VERSION=$SCHEMA_VERSION - -set -e - -BIN_DIR="./bin" - -rm -rf $BIN_DIR - -curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b $BIN_DIR $GRYPE_VERSION - -make clean-manager -make cli-test-data/vunnel/oracle - -pushd s3-mock -docker-compose up -d -python setup-workflow-4.py -popd - -set +e - -### Start of testing ######################## -header "Case 1: create and publish a DB" - -# note: this test is exercising the following commands: -# grype-db-manager db build -# grype-db-manager db validate --skip-namespace-check -# grype-db-manager db upload - -run grype-db-manager db build-and-upload --schema-version $SCHEMA_VERSION --skip-namespace-check -assert_contains $(last_stdout_file) "Validation passed" -assert_contains $(last_stdout_file) "' uploaded to s3://testbucket/grype/databases" - - -header "Case 2: update the listing file based on the DB uploaded" - -# note: this test is exercising the following commands: -# grype-db-manager listing create -# grype-db-manager listing validate - -run grype-db-manager listing update -assert_contains $(last_stdout_file) "Validation passed" -assert_contains $(last_stdout_file) "listing.json uploaded to s3://testbucket/grype/databases" - -# check if grype works with this updated listing file -export GRYPE_DB_UPDATE_URL="http://localhost:4566/testbucket/grype/databases/listing.json" -export GRYPE_DB_CACHE_DIR="./bin" - -run bin/grype db list - -assert_contains $(last_stdout_file) "http://localhost:4566" - -run bin/grype db update - -run bin/grype docker.io/oraclelinux:6@sha256:a06327c0f1d18d753f2a60bb17864c84a850bb6dcbcf5946dd1a8123f6e75495 - -assert_contains $(last_stdout_file) "ELSA-2021-9591" - - -### End of testing ######################## - -pushd s3-mock -docker-compose down -t 1 -v -popd - -end_testing diff --git a/manager/tests/unit/cli/fixtures/config/full.yaml b/manager/tests/unit/cli/fixtures/config/full.yaml index ec416b16..d90dd99c 100644 --- a/manager/tests/unit/cli/fixtures/config/full.yaml +++ b/manager/tests/unit/cli/fixtures/config/full.yaml @@ -21,6 +21,14 @@ distribution: aws-region: us-west-2 s3-endpoint-url: http://localhost:4566 download-url-prefix: http://localhost:4566/testbucket + s3-always-suffix-schema-version: false + listing_replicas: + - awsRegion: us-west-2 + listingFileName: listing.json + s3Bucket: testbucket + s3Path: grype/databases + +schemaMappingFile: "mapping.json" validate: listing: @@ -28,13 +36,16 @@ validate: minimum-packages: 85 minimum-vulnerabilities: 400 - db: - default-max-year: 2021 - - gate: - f1-score-threshold: 0.0 - unlabeled-matches-threshold: 10.0 - introduced-fns-threshold: 0 - - images: - - docker.io/cloudbees/cloudbees-core-agent:2.289.2.2@sha256:d48f0546b4cf5ef4626136242ce302f94a42751156b7be42f4b1b75a66608880 + default-max-year: 2021 + expectedProviders: + - "alpine" + gates: + - gate: + max_f1_regression: 0.15 + max_unlabeled_percent: 50 + max_new_false_negatives: 10 + max_year: 2021 + allow_empty_results_for_schemas: [1,2,3] + images: + - docker.io/cloudbees/cloudbees-core-agent:2.289.2.2@sha256:d48f0546b4cf5ef4626136242ce302f94a42751156b7be42f4b1b75a66608880 +verbosity: 2 \ No newline at end of file diff --git a/manager/tests/unit/cli/fixtures/listing/create-all-exists/.grype-db-manager.yaml b/manager/tests/unit/cli/fixtures/listing/create-all-exists/.grype-db-manager.yaml index bec25cd7..ad9e8847 100644 --- a/manager/tests/unit/cli/fixtures/listing/create-all-exists/.grype-db-manager.yaml +++ b/manager/tests/unit/cli/fixtures/listing/create-all-exists/.grype-db-manager.yaml @@ -3,7 +3,7 @@ log: distribution: listing-file-name: listing.json - s3-path: grype/databases + s3-path: databases s3-bucket: testbucket aws-region: us-west-2 download-url-prefix: http://localhost:4566/testbucket \ No newline at end of file diff --git a/manager/tests/unit/cli/fixtures/listing/create-all-exists/expected-listing.json b/manager/tests/unit/cli/fixtures/listing/create-all-exists/expected-listing.json index 77033d0e..75ecc1ff 100644 --- a/manager/tests/unit/cli/fixtures/listing/create-all-exists/expected-listing.json +++ b/manager/tests/unit/cli/fixtures/listing/create-all-exists/expected-listing.json @@ -4,19 +4,19 @@ { "built": "2023-08-10T01:32:21Z", "checksum": "sha256:35ac11b43eb8b1058d9038ce28a5901f2dd5b33223a85df60a739421cfea9d2e", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v1_2023-08-10T01:32:21Z_993f1c0a71d3cedd726a.tar.gz", + "url": "http://localhost:4566/databases/vulnerability-db_v1_2023-08-10T01:32:21Z_993f1c0a71d3cedd726a.tar.gz", "version": 1 }, { "built": "2023-08-09T01:13:56Z", "checksum": "sha256:d35e4591d88e8e7271106406834f5f10615fc4059da5b64db9f83b896685c475", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v1_2023-08-09T01:13:56Z_a7ef97ffb2f108cfa489.tar.gz", + "url": "http://localhost:4566/databases/vulnerability-db_v1_2023-08-09T01:13:56Z_a7ef97ffb2f108cfa489.tar.gz", "version": 1 }, { "built": "2023-08-08T01:33:25Z", "checksum": "sha256:3f6e09c624dadf47159b8f8a045fffc9a3377d7f3a22ca5ce1eb17ed04e04e8a", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v1_2023-08-08T01:33:25Z_45f59b141d7256bf2c4d.tar.gz", + "url": "http://localhost:4566/databases/vulnerability-db_v1_2023-08-08T01:33:25Z_45f59b141d7256bf2c4d.tar.gz", "version": 1 } ], @@ -24,19 +24,19 @@ { "built": "2023-08-10T01:32:21Z", "checksum": "sha256:1ce996f58b8a0cbb274737da34b9435470ffe42dd3689c06027cc18940206b07", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v2_2023-08-10T01:32:21Z_89457f888a3d4a11f827.tar.gz", + "url": "http://localhost:4566/databases/vulnerability-db_v2_2023-08-10T01:32:21Z_89457f888a3d4a11f827.tar.gz", "version": 2 }, { "built": "2023-08-09T01:13:56Z", "checksum": "sha256:7da9dc585b8586f07a34246129fcaa0e57445b5dfba2da5f105d5b343095e407", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v2_2023-08-09T01:13:56Z_969ed9cba092f7606b6f.tar.gz", + "url": "http://localhost:4566/databases/vulnerability-db_v2_2023-08-09T01:13:56Z_969ed9cba092f7606b6f.tar.gz", "version": 2 }, { "built": "2023-08-08T01:33:25Z", "checksum": "sha256:b955be05115f78d8473f915a165465bc1a9c60c3d01d2bc770e776dd496fdb82", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v2_2023-08-08T01:33:25Z_a89e961c0943175eb6a0.tar.gz", + "url": "http://localhost:4566/databases/vulnerability-db_v2_2023-08-08T01:33:25Z_a89e961c0943175eb6a0.tar.gz", "version": 2 } ], @@ -44,19 +44,19 @@ { "built": "2023-08-10T01:32:21Z", "checksum": "sha256:092c5661622a6d733f5bdf3cdfb5a8f966e88e443914794b038c949777efbb43", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v3_2023-08-10T01:32:21Z_dcee097f2c55f5663d12.tar.gz", + "url": "http://localhost:4566/databases/vulnerability-db_v3_2023-08-10T01:32:21Z_dcee097f2c55f5663d12.tar.gz", "version": 3 }, { "built": "2023-08-09T01:13:56Z", "checksum": "sha256:8c1f2ee053f6d04dbe49000c4cc82cdd2c91ecdfaacb642a880e120e180e8c0c", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v3_2023-08-09T01:13:56Z_eb863ee941d52d07a511.tar.gz", + "url": "http://localhost:4566/databases/vulnerability-db_v3_2023-08-09T01:13:56Z_eb863ee941d52d07a511.tar.gz", "version": 3 }, { "built": "2023-08-08T01:33:25Z", "checksum": "sha256:cecca62792f90548ab804d11148d812ace0163b901d8f80e3a190fe544b07323", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v3_2023-08-08T01:33:25Z_c6eb70d1d2bcff836ede.tar.gz", + "url": "http://localhost:4566/databases/vulnerability-db_v3_2023-08-08T01:33:25Z_c6eb70d1d2bcff836ede.tar.gz", "version": 3 } ], @@ -64,19 +64,19 @@ { "built": "2023-08-10T01:32:21Z", "checksum": "sha256:233a1c70b3021061a615382d66de5c20cf334c693c025eb5885a623fe9d08d5b", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v4_2023-08-10T01:32:21Z_2926495f3f0dd45c48ba.tar.gz", + "url": "http://localhost:4566/databases/vulnerability-db_v4_2023-08-10T01:32:21Z_2926495f3f0dd45c48ba.tar.gz", "version": 4 }, { "built": "2023-08-09T01:13:56Z", "checksum": "sha256:0eeeb3977d018339e88cb380df480e0b54a0d63ab95c27a52b8a4736098336a9", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v4_2023-08-09T01:13:56Z_48e4c6334d25b83b76cb.tar.gz", + "url": "http://localhost:4566/databases/vulnerability-db_v4_2023-08-09T01:13:56Z_48e4c6334d25b83b76cb.tar.gz", "version": 4 }, { "built": "2023-08-08T01:33:25Z", "checksum": "sha256:e135014c062974975f7e93343af581e7d3ceb44001a125ca1978230780e0406b", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v4_2023-08-08T01:33:25Z_fe44be95fbb6ae335497.tar.gz", + "url": "http://localhost:4566/databases/vulnerability-db_v4_2023-08-08T01:33:25Z_fe44be95fbb6ae335497.tar.gz", "version": 4 } ], @@ -84,19 +84,19 @@ { "built": "2023-08-10T01:32:21Z", "checksum": "sha256:78045f3ed82435569a3681bc1da3c1e1a5cf80ac2536580dee35c8aa8e7f2cf6", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v5_2023-08-10T01:32:21Z_7bf3acd8d0424bb6e3d9.tar.gz", + "url": "http://localhost:4566/databases/vulnerability-db_v5_2023-08-10T01:32:21Z_7bf3acd8d0424bb6e3d9.tar.gz", "version": 5 }, { "built": "2023-08-09T01:13:56Z", "checksum": "sha256:d01ef3541b527cafeb35bde9d713204f05934a5147009ee97a8e3f722a39403c", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v5_2023-08-09T01:13:56Z_4a7133ef05bf13c2f8be.tar.gz", + "url": "http://localhost:4566/databases/vulnerability-db_v5_2023-08-09T01:13:56Z_4a7133ef05bf13c2f8be.tar.gz", "version": 5 }, { "built": "2023-08-08T01:33:25Z", "checksum": "sha256:a38154687be58cf188669bcf78c4724d8a10c09a769eefa51e3e68eeb2f2eb9d", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v5_2023-08-08T01:33:25Z_1072b8f15e5d53338836.tar.gz", + "url": "http://localhost:4566/databases/vulnerability-db_v5_2023-08-08T01:33:25Z_1072b8f15e5d53338836.tar.gz", "version": 5 } ] diff --git a/manager/tests/unit/cli/fixtures/listing/create-all-exists/input-listing.json b/manager/tests/unit/cli/fixtures/listing/create-all-exists/input-listing.json index 77033d0e..75ecc1ff 100644 --- a/manager/tests/unit/cli/fixtures/listing/create-all-exists/input-listing.json +++ b/manager/tests/unit/cli/fixtures/listing/create-all-exists/input-listing.json @@ -4,19 +4,19 @@ { "built": "2023-08-10T01:32:21Z", "checksum": "sha256:35ac11b43eb8b1058d9038ce28a5901f2dd5b33223a85df60a739421cfea9d2e", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v1_2023-08-10T01:32:21Z_993f1c0a71d3cedd726a.tar.gz", + "url": "http://localhost:4566/databases/vulnerability-db_v1_2023-08-10T01:32:21Z_993f1c0a71d3cedd726a.tar.gz", "version": 1 }, { "built": "2023-08-09T01:13:56Z", "checksum": "sha256:d35e4591d88e8e7271106406834f5f10615fc4059da5b64db9f83b896685c475", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v1_2023-08-09T01:13:56Z_a7ef97ffb2f108cfa489.tar.gz", + "url": "http://localhost:4566/databases/vulnerability-db_v1_2023-08-09T01:13:56Z_a7ef97ffb2f108cfa489.tar.gz", "version": 1 }, { "built": "2023-08-08T01:33:25Z", "checksum": "sha256:3f6e09c624dadf47159b8f8a045fffc9a3377d7f3a22ca5ce1eb17ed04e04e8a", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v1_2023-08-08T01:33:25Z_45f59b141d7256bf2c4d.tar.gz", + "url": "http://localhost:4566/databases/vulnerability-db_v1_2023-08-08T01:33:25Z_45f59b141d7256bf2c4d.tar.gz", "version": 1 } ], @@ -24,19 +24,19 @@ { "built": "2023-08-10T01:32:21Z", "checksum": "sha256:1ce996f58b8a0cbb274737da34b9435470ffe42dd3689c06027cc18940206b07", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v2_2023-08-10T01:32:21Z_89457f888a3d4a11f827.tar.gz", + "url": "http://localhost:4566/databases/vulnerability-db_v2_2023-08-10T01:32:21Z_89457f888a3d4a11f827.tar.gz", "version": 2 }, { "built": "2023-08-09T01:13:56Z", "checksum": "sha256:7da9dc585b8586f07a34246129fcaa0e57445b5dfba2da5f105d5b343095e407", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v2_2023-08-09T01:13:56Z_969ed9cba092f7606b6f.tar.gz", + "url": "http://localhost:4566/databases/vulnerability-db_v2_2023-08-09T01:13:56Z_969ed9cba092f7606b6f.tar.gz", "version": 2 }, { "built": "2023-08-08T01:33:25Z", "checksum": "sha256:b955be05115f78d8473f915a165465bc1a9c60c3d01d2bc770e776dd496fdb82", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v2_2023-08-08T01:33:25Z_a89e961c0943175eb6a0.tar.gz", + "url": "http://localhost:4566/databases/vulnerability-db_v2_2023-08-08T01:33:25Z_a89e961c0943175eb6a0.tar.gz", "version": 2 } ], @@ -44,19 +44,19 @@ { "built": "2023-08-10T01:32:21Z", "checksum": "sha256:092c5661622a6d733f5bdf3cdfb5a8f966e88e443914794b038c949777efbb43", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v3_2023-08-10T01:32:21Z_dcee097f2c55f5663d12.tar.gz", + "url": "http://localhost:4566/databases/vulnerability-db_v3_2023-08-10T01:32:21Z_dcee097f2c55f5663d12.tar.gz", "version": 3 }, { "built": "2023-08-09T01:13:56Z", "checksum": "sha256:8c1f2ee053f6d04dbe49000c4cc82cdd2c91ecdfaacb642a880e120e180e8c0c", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v3_2023-08-09T01:13:56Z_eb863ee941d52d07a511.tar.gz", + "url": "http://localhost:4566/databases/vulnerability-db_v3_2023-08-09T01:13:56Z_eb863ee941d52d07a511.tar.gz", "version": 3 }, { "built": "2023-08-08T01:33:25Z", "checksum": "sha256:cecca62792f90548ab804d11148d812ace0163b901d8f80e3a190fe544b07323", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v3_2023-08-08T01:33:25Z_c6eb70d1d2bcff836ede.tar.gz", + "url": "http://localhost:4566/databases/vulnerability-db_v3_2023-08-08T01:33:25Z_c6eb70d1d2bcff836ede.tar.gz", "version": 3 } ], @@ -64,19 +64,19 @@ { "built": "2023-08-10T01:32:21Z", "checksum": "sha256:233a1c70b3021061a615382d66de5c20cf334c693c025eb5885a623fe9d08d5b", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v4_2023-08-10T01:32:21Z_2926495f3f0dd45c48ba.tar.gz", + "url": "http://localhost:4566/databases/vulnerability-db_v4_2023-08-10T01:32:21Z_2926495f3f0dd45c48ba.tar.gz", "version": 4 }, { "built": "2023-08-09T01:13:56Z", "checksum": "sha256:0eeeb3977d018339e88cb380df480e0b54a0d63ab95c27a52b8a4736098336a9", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v4_2023-08-09T01:13:56Z_48e4c6334d25b83b76cb.tar.gz", + "url": "http://localhost:4566/databases/vulnerability-db_v4_2023-08-09T01:13:56Z_48e4c6334d25b83b76cb.tar.gz", "version": 4 }, { "built": "2023-08-08T01:33:25Z", "checksum": "sha256:e135014c062974975f7e93343af581e7d3ceb44001a125ca1978230780e0406b", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v4_2023-08-08T01:33:25Z_fe44be95fbb6ae335497.tar.gz", + "url": "http://localhost:4566/databases/vulnerability-db_v4_2023-08-08T01:33:25Z_fe44be95fbb6ae335497.tar.gz", "version": 4 } ], @@ -84,19 +84,19 @@ { "built": "2023-08-10T01:32:21Z", "checksum": "sha256:78045f3ed82435569a3681bc1da3c1e1a5cf80ac2536580dee35c8aa8e7f2cf6", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v5_2023-08-10T01:32:21Z_7bf3acd8d0424bb6e3d9.tar.gz", + "url": "http://localhost:4566/databases/vulnerability-db_v5_2023-08-10T01:32:21Z_7bf3acd8d0424bb6e3d9.tar.gz", "version": 5 }, { "built": "2023-08-09T01:13:56Z", "checksum": "sha256:d01ef3541b527cafeb35bde9d713204f05934a5147009ee97a8e3f722a39403c", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v5_2023-08-09T01:13:56Z_4a7133ef05bf13c2f8be.tar.gz", + "url": "http://localhost:4566/databases/vulnerability-db_v5_2023-08-09T01:13:56Z_4a7133ef05bf13c2f8be.tar.gz", "version": 5 }, { "built": "2023-08-08T01:33:25Z", "checksum": "sha256:a38154687be58cf188669bcf78c4724d8a10c09a769eefa51e3e68eeb2f2eb9d", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v5_2023-08-08T01:33:25Z_1072b8f15e5d53338836.tar.gz", + "url": "http://localhost:4566/databases/vulnerability-db_v5_2023-08-08T01:33:25Z_1072b8f15e5d53338836.tar.gz", "version": 5 } ] diff --git a/manager/tests/unit/cli/fixtures/listing/create-new-db/.grype-db-manager.yaml b/manager/tests/unit/cli/fixtures/listing/create-new-db/.grype-db-manager.yaml index d9c59e7b..a2975717 100644 --- a/manager/tests/unit/cli/fixtures/listing/create-new-db/.grype-db-manager.yaml +++ b/manager/tests/unit/cli/fixtures/listing/create-new-db/.grype-db-manager.yaml @@ -3,7 +3,7 @@ log: distribution: listing-file-name: listing.json - s3-path: grype/databases + s3-path: databases s3-bucket: testbucket aws-region: us-west-2 - download-url-prefix: http://localhost:4566/testbucket + download-url-prefix: http://localhost:4566/testbucket/ # assert that trailing / is trimmed diff --git a/manager/tests/unit/cli/fixtures/listing/create-new-db/expected-listing.json b/manager/tests/unit/cli/fixtures/listing/create-new-db/expected-listing.json index 2a40d8fd..975cc4df 100644 --- a/manager/tests/unit/cli/fixtures/listing/create-new-db/expected-listing.json +++ b/manager/tests/unit/cli/fixtures/listing/create-new-db/expected-listing.json @@ -2,103 +2,103 @@ "available": { "1": [ { - "built": "2023-08-10T01:32:21Z", - "checksum": "sha256:35ac11b43eb8b1058d9038ce28a5901f2dd5b33223a85df60a739421cfea9d2e", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v1_2023-08-10T01:32:21Z_993f1c0a71d3cedd726a.tar.gz", + "built": "2024-08-22T01:31:37Z", + "checksum": "sha256:7a62753ddb1f12994fdcd41244106cc459406620c522cb32c54da46b12b86634", + "url": "http://localhost:4566/testbucket/databases/vulnerability-db_v1_2024-08-22T01:31:37Z_1724340381.tar.gz", "version": 1 }, { - "built": "2023-08-09T01:13:56Z", - "checksum": "sha256:d35e4591d88e8e7271106406834f5f10615fc4059da5b64db9f83b896685c475", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v1_2023-08-09T01:13:56Z_a7ef97ffb2f108cfa489.tar.gz", + "built": "2024-08-22T01:31:37Z", + "checksum": "sha256:5ff9f2c047514ba4fbfc45e84df234ecbd2f09c11002c2f008be3a5c2c73b6f1", + "url": "http://localhost:4566/databases/vulnerability-db_v1_2024-08-22T01:31:37Z_1724300289.tar.gz", "version": 1 }, { - "built": "2023-08-08T01:33:25Z", - "checksum": "sha256:9ece0b838be60974aee62087cf366b84e5b7cb74e7a665d86de7735aca927d36", - "url": "http://localhost:4566/testbucket/grype/databases/vulnerability-db_v1_2023-08-08T01:33:25Z_45f59b141d7256bf2c4d.tar.gz", + "built": "2024-08-21T01:31:31Z", + "checksum": "sha256:d0e8ca5357ebc152b767fe0a9caba8aba0dd106eacf936c653f02932a2c8e238", + "url": "http://localhost:4566/databases/vulnerability-db_v1_2024-08-21T01:31:31Z_1724213864.tar.gz", "version": 1 } ], "2": [ { - "built": "2023-08-10T01:32:21Z", - "checksum": "sha256:1ce996f58b8a0cbb274737da34b9435470ffe42dd3689c06027cc18940206b07", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v2_2023-08-10T01:32:21Z_89457f888a3d4a11f827.tar.gz", + "built": "2024-08-22T01:31:37Z", + "checksum": "sha256:330c91514fbc2f810f834ebcada5d87d3295cabee0878ae94ed044480e9a395e", + "url": "http://localhost:4566/testbucket/databases/vulnerability-db_v2_2024-08-22T01:31:37Z_1724340410.tar.gz", "version": 2 }, { - "built": "2023-08-09T01:13:56Z", - "checksum": "sha256:7da9dc585b8586f07a34246129fcaa0e57445b5dfba2da5f105d5b343095e407", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v2_2023-08-09T01:13:56Z_969ed9cba092f7606b6f.tar.gz", + "built": "2024-08-22T01:31:37Z", + "checksum": "sha256:18c0f3314a1b3f350d3c2cc20bc750411dfe7985ef3db3bdb134ef423fc7b43c", + "url": "http://localhost:4566/databases/vulnerability-db_v2_2024-08-22T01:31:37Z_1724300266.tar.gz", "version": 2 }, { - "built": "2023-08-08T01:33:25Z", - "checksum": "sha256:ac567d4894ad6cb41bff9aafad3e37dbff65bf3708b88e0888fef2be278128b9", - "url": "http://localhost:4566/testbucket/grype/databases/vulnerability-db_v2_2023-08-08T01:33:25Z_a89e961c0943175eb6a0.tar.gz", + "built": "2024-08-21T01:31:31Z", + "checksum": "sha256:d69d765bf1ee648cb5a50e3c2443e454c8b8b4820d007823528fd784620f9eaa", + "url": "http://localhost:4566/databases/vulnerability-db_v2_2024-08-21T01:31:31Z_1724213871.tar.gz", "version": 2 } ], "3": [ { - "built": "2023-08-10T01:32:21Z", - "checksum": "sha256:092c5661622a6d733f5bdf3cdfb5a8f966e88e443914794b038c949777efbb43", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v3_2023-08-10T01:32:21Z_dcee097f2c55f5663d12.tar.gz", + "built": "2024-08-22T01:31:37Z", + "checksum": "sha256:1f260352be56b1f5fbcc9f904e5597656b865b8d67ae6c7f21837c761588ad7b", + "url": "http://localhost:4566/testbucket/databases/vulnerability-db_v3_2024-08-22T01:31:37Z_1724340460.tar.gz", "version": 3 }, { - "built": "2023-08-09T01:13:56Z", - "checksum": "sha256:8c1f2ee053f6d04dbe49000c4cc82cdd2c91ecdfaacb642a880e120e180e8c0c", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v3_2023-08-09T01:13:56Z_eb863ee941d52d07a511.tar.gz", + "built": "2024-08-22T01:31:37Z", + "checksum": "sha256:7f34f48fc8776dc7d297d655d38b224390301d048374e4a51c59df58af57e109", + "url": "http://localhost:4566/databases/vulnerability-db_v3_2024-08-22T01:31:37Z_1724300325.tar.gz", "version": 3 }, { - "built": "2023-08-08T01:33:25Z", - "checksum": "sha256:1cbf29901c0b3721bf54306394eb8a50c64aa312a1c28c030fd348c0ae858ac5", - "url": "http://localhost:4566/testbucket/grype/databases/vulnerability-db_v3_2023-08-08T01:33:25Z_c6eb70d1d2bcff836ede.tar.gz", + "built": "2024-08-21T01:31:31Z", + "checksum": "sha256:4f66681b5be7c4eb192107bac6cf59b5e6ccf9a6f10ea9b23110af402f22507a", + "url": "http://localhost:4566/databases/vulnerability-db_v3_2024-08-21T01:31:31Z_1724213901.tar.gz", "version": 3 } ], "4": [ { - "built": "2023-08-10T01:32:21Z", - "checksum": "sha256:233a1c70b3021061a615382d66de5c20cf334c693c025eb5885a623fe9d08d5b", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v4_2023-08-10T01:32:21Z_2926495f3f0dd45c48ba.tar.gz", + "built": "2024-08-22T01:31:37Z", + "checksum": "sha256:4b224aef183a59d3dae1c18468591d3616c0be01dd47dc07f65fb77ee68f8000", + "url": "http://localhost:4566/testbucket/databases/vulnerability-db_v4_2024-08-22T01:31:37Z_1724340541.tar.gz", "version": 4 }, { - "built": "2023-08-09T01:13:56Z", - "checksum": "sha256:0eeeb3977d018339e88cb380df480e0b54a0d63ab95c27a52b8a4736098336a9", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v4_2023-08-09T01:13:56Z_48e4c6334d25b83b76cb.tar.gz", + "built": "2024-08-22T01:31:37Z", + "checksum": "sha256:497c8109d4c717ef1214b8cbd1954fccab7e89a77cd03469f01512ba566b7c14", + "url": "http://localhost:4566/databases/vulnerability-db_v4_2024-08-22T01:31:37Z_1724300355.tar.gz", "version": 4 }, { - "built": "2023-08-08T01:33:25Z", - "checksum": "sha256:b0a652d53fe87c6fdd7206f792575c714fc5a592c09d9edd62b70babf2bfc9f0", - "url": "http://localhost:4566/testbucket/grype/databases/vulnerability-db_v4_2023-08-08T01:33:25Z_fe44be95fbb6ae335497.tar.gz", + "built": "2024-08-21T01:31:31Z", + "checksum": "sha256:826fbbefbe631e2dd48e039e761b3ddba2d9ded0f2944c2d0c9db21b821695e2", + "url": "http://localhost:4566/databases/vulnerability-db_v4_2024-08-21T01:31:31Z_1724213974.tar.gz", "version": 4 } ], "5": [ { - "built": "2023-08-10T01:32:21Z", - "checksum": "sha256:78045f3ed82435569a3681bc1da3c1e1a5cf80ac2536580dee35c8aa8e7f2cf6", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v5_2023-08-10T01:32:21Z_7bf3acd8d0424bb6e3d9.tar.gz", + "built": "2024-08-22T01:31:37Z", + "checksum": "sha256:d7acab56538166c3543eaf11ec4622ef657f196323fa8d06670f46ad21bd2851", + "url": "http://localhost:4566/testbucket/databases/vulnerability-db_v5_2024-08-22T01:31:37Z_1724340606.tar.gz", "version": 5 }, { - "built": "2023-08-09T01:13:56Z", - "checksum": "sha256:d01ef3541b527cafeb35bde9d713204f05934a5147009ee97a8e3f722a39403c", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v5_2023-08-09T01:13:56Z_4a7133ef05bf13c2f8be.tar.gz", + "built": "2024-08-22T01:31:37Z", + "checksum": "sha256:1c0d7e9c027c31e476352157e83575181e9420f2788ff23b01e0b4b096971541", + "url": "http://localhost:4566/databases/vulnerability-db_v5_2024-08-22T01:31:37Z_1724300383.tar.gz", "version": 5 }, { - "built": "2023-08-08T01:33:25Z", - "checksum": "sha256:00014c5c33e7a6561afc51d9e6171b3adf9cec39c2ad3c3946676227a04917d6", - "url": "http://localhost:4566/testbucket/grype/databases/vulnerability-db_v5_2023-08-08T01:33:25Z_1072b8f15e5d53338836.tar.gz", + "built": "2024-08-21T01:31:31Z", + "checksum": "sha256:2aff16956eb083b6f3444b1b7c80ac64929dad293474dfc75ea20138aedb3bb1", + "url": "http://localhost:4566/databases/vulnerability-db_v5_2024-08-21T01:31:31Z_1724213998.tar.gz", "version": 5 } ] } -} \ No newline at end of file +} diff --git a/manager/tests/unit/cli/fixtures/listing/create-new-db/input-listing.json b/manager/tests/unit/cli/fixtures/listing/create-new-db/input-listing.json index b68afaef..f68a60f4 100644 --- a/manager/tests/unit/cli/fixtures/listing/create-new-db/input-listing.json +++ b/manager/tests/unit/cli/fixtures/listing/create-new-db/input-listing.json @@ -2,73 +2,73 @@ "available": { "1": [ { - "built": "2023-08-10T01:32:21Z", - "checksum": "sha256:35ac11b43eb8b1058d9038ce28a5901f2dd5b33223a85df60a739421cfea9d2e", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v1_2023-08-10T01:32:21Z_993f1c0a71d3cedd726a.tar.gz", + "built": "2024-08-22T01:31:37Z", + "checksum": "sha256:5ff9f2c047514ba4fbfc45e84df234ecbd2f09c11002c2f008be3a5c2c73b6f1", + "url": "http://localhost:4566/databases/vulnerability-db_v1_2024-08-22T01:31:37Z_1724300289.tar.gz", "version": 1 }, { - "built": "2023-08-09T01:13:56Z", - "checksum": "sha256:d35e4591d88e8e7271106406834f5f10615fc4059da5b64db9f83b896685c475", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v1_2023-08-09T01:13:56Z_a7ef97ffb2f108cfa489.tar.gz", + "built": "2024-08-21T01:31:31Z", + "checksum": "sha256:d0e8ca5357ebc152b767fe0a9caba8aba0dd106eacf936c653f02932a2c8e238", + "url": "http://localhost:4566/databases/vulnerability-db_v1_2024-08-21T01:31:31Z_1724213864.tar.gz", "version": 1 } ], "2": [ { - "built": "2023-08-10T01:32:21Z", - "checksum": "sha256:1ce996f58b8a0cbb274737da34b9435470ffe42dd3689c06027cc18940206b07", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v2_2023-08-10T01:32:21Z_89457f888a3d4a11f827.tar.gz", + "built": "2024-08-22T01:31:37Z", + "checksum": "sha256:18c0f3314a1b3f350d3c2cc20bc750411dfe7985ef3db3bdb134ef423fc7b43c", + "url": "http://localhost:4566/databases/vulnerability-db_v2_2024-08-22T01:31:37Z_1724300266.tar.gz", "version": 2 }, { - "built": "2023-08-09T01:13:56Z", - "checksum": "sha256:7da9dc585b8586f07a34246129fcaa0e57445b5dfba2da5f105d5b343095e407", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v2_2023-08-09T01:13:56Z_969ed9cba092f7606b6f.tar.gz", + "built": "2024-08-21T01:31:31Z", + "checksum": "sha256:d69d765bf1ee648cb5a50e3c2443e454c8b8b4820d007823528fd784620f9eaa", + "url": "http://localhost:4566/databases/vulnerability-db_v2_2024-08-21T01:31:31Z_1724213871.tar.gz", "version": 2 } ], "3": [ { - "built": "2023-08-10T01:32:21Z", - "checksum": "sha256:092c5661622a6d733f5bdf3cdfb5a8f966e88e443914794b038c949777efbb43", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v3_2023-08-10T01:32:21Z_dcee097f2c55f5663d12.tar.gz", + "built": "2024-08-22T01:31:37Z", + "checksum": "sha256:7f34f48fc8776dc7d297d655d38b224390301d048374e4a51c59df58af57e109", + "url": "http://localhost:4566/databases/vulnerability-db_v3_2024-08-22T01:31:37Z_1724300325.tar.gz", "version": 3 }, { - "built": "2023-08-09T01:13:56Z", - "checksum": "sha256:8c1f2ee053f6d04dbe49000c4cc82cdd2c91ecdfaacb642a880e120e180e8c0c", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v3_2023-08-09T01:13:56Z_eb863ee941d52d07a511.tar.gz", + "built": "2024-08-21T01:31:31Z", + "checksum": "sha256:4f66681b5be7c4eb192107bac6cf59b5e6ccf9a6f10ea9b23110af402f22507a", + "url": "http://localhost:4566/databases/vulnerability-db_v3_2024-08-21T01:31:31Z_1724213901.tar.gz", "version": 3 } ], "4": [ { - "built": "2023-08-10T01:32:21Z", - "checksum": "sha256:233a1c70b3021061a615382d66de5c20cf334c693c025eb5885a623fe9d08d5b", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v4_2023-08-10T01:32:21Z_2926495f3f0dd45c48ba.tar.gz", + "built": "2024-08-22T01:31:37Z", + "checksum": "sha256:497c8109d4c717ef1214b8cbd1954fccab7e89a77cd03469f01512ba566b7c14", + "url": "http://localhost:4566/databases/vulnerability-db_v4_2024-08-22T01:31:37Z_1724300355.tar.gz", "version": 4 }, { - "built": "2023-08-09T01:13:56Z", - "checksum": "sha256:0eeeb3977d018339e88cb380df480e0b54a0d63ab95c27a52b8a4736098336a9", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v4_2023-08-09T01:13:56Z_48e4c6334d25b83b76cb.tar.gz", + "built": "2024-08-21T01:31:31Z", + "checksum": "sha256:826fbbefbe631e2dd48e039e761b3ddba2d9ded0f2944c2d0c9db21b821695e2", + "url": "http://localhost:4566/databases/vulnerability-db_v4_2024-08-21T01:31:31Z_1724213974.tar.gz", "version": 4 } ], "5": [ { - "built": "2023-08-10T01:32:21Z", - "checksum": "sha256:78045f3ed82435569a3681bc1da3c1e1a5cf80ac2536580dee35c8aa8e7f2cf6", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v5_2023-08-10T01:32:21Z_7bf3acd8d0424bb6e3d9.tar.gz", + "built": "2024-08-22T01:31:37Z", + "checksum": "sha256:1c0d7e9c027c31e476352157e83575181e9420f2788ff23b01e0b4b096971541", + "url": "http://localhost:4566/databases/vulnerability-db_v5_2024-08-22T01:31:37Z_1724300383.tar.gz", "version": 5 }, { - "built": "2023-08-09T01:13:56Z", - "checksum": "sha256:d01ef3541b527cafeb35bde9d713204f05934a5147009ee97a8e3f722a39403c", - "url": "https://toolbox-data.anchore.io/grype/databases/vulnerability-db_v5_2023-08-09T01:13:56Z_4a7133ef05bf13c2f8be.tar.gz", + "built": "2024-08-21T01:31:31Z", + "checksum": "sha256:2aff16956eb083b6f3444b1b7c80ac64929dad293474dfc75ea20138aedb3bb1", + "url": "http://localhost:4566/databases/vulnerability-db_v5_2024-08-21T01:31:31Z_1724213998.tar.gz", "version": 5 } ] } -} \ No newline at end of file +} diff --git a/manager/tests/unit/cli/test_config.py b/manager/tests/unit/cli/test_config.py index 9ff1fd62..498a3c6b 100644 --- a/manager/tests/unit/cli/test_config.py +++ b/manager/tests/unit/cli/test_config.py @@ -50,6 +50,8 @@ def test_load_default(): awsRegion: null downloadUrlPrefix: null listingFileName: listing.json + listingReplicas: [] + s3AlwaysSuffixSchemaVersion: false s3Bucket: null s3EndpointUrl: null s3Path: null @@ -58,22 +60,30 @@ def test_load_default(): version: latest log: level: INFO +schemaMappingFile: '' validate: - db: - defaultMaxYear: 2021 - gate: - f1ScoreThreshold: 0.0 - introducedFnsThreshold: 0 - unlabeledMatchesThreshold: 10.0 - grype: - config: '' - images: [] + defaultMaxYear: 2021 + expectedProviders: + - alpine + - amazon + - chainguard + - debian + - github + - mariner + - nvd + - oracle + - rhel + - sles + - ubuntu + - wolfi + gates: [] listing: image: null minimumPackages: null minimumVulnerabilities: null overrideDbSchemaVersion: null overrideGrypeVersion: null +verbosity: 0 """ assert actual == expected @@ -96,6 +106,12 @@ def test_load(test_dir_path): awsRegion: us-west-2 downloadUrlPrefix: http://localhost:4566/testbucket listingFileName: listing.json + listingReplicas: + - awsRegion: us-west-2 + listingFileName: listing.json + s3Bucket: testbucket + s3Path: grype/databases + s3AlwaysSuffixSchemaVersion: false s3Bucket: testbucket s3EndpointUrl: http://localhost:4566 s3Path: grype/databases @@ -104,23 +120,38 @@ def test_load(test_dir_path): version: file://. log: level: INFO +schemaMappingFile: mapping.json validate: - db: - defaultMaxYear: 2021 - gate: - f1ScoreThreshold: 0.0 - introducedFnsThreshold: 0 - unlabeledMatchesThreshold: 10.0 - grype: - config: '' - images: - - docker.io/cloudbees/cloudbees-core-agent:2.289.2.2@sha256:d48f0546b4cf5ef4626136242ce302f94a42751156b7be42f4b1b75a66608880 + defaultMaxYear: 2021 + expectedProviders: + - alpine + gates: + - allowEmptyResultsForSchemas: + - 1 + - 2 + - 3 + gate: + allowedNamespaces: [] + candidateToolLabel: candidate + failOnEmptyMatchSet: true + maxF1Regression: 0.15 + maxNewFalseNegatives: 10 + maxUnlabeledPercent: 50 + maxYear: 2021 + name: default + referenceToolLabel: reference + requiredNamespaces: [] + grype: + config: '' + images: + - docker.io/cloudbees/cloudbees-core-agent:2.289.2.2@sha256:d48f0546b4cf5ef4626136242ce302f94a42751156b7be42f4b1b75a66608880 listing: image: centos:8.2.2004 minimumPackages: 85 minimumVulnerabilities: 400 overrideDbSchemaVersion: null overrideGrypeVersion: null +verbosity: 2 """ assert actual == expected diff --git a/manager/tests/unit/cli/test_db.py b/manager/tests/unit/cli/test_db.py index 0f1d7f6d..1fc9b375 100644 --- a/manager/tests/unit/cli/test_db.py +++ b/manager/tests/unit/cli/test_db.py @@ -34,5 +34,4 @@ def test_upload_db(mocker, test_dir_path, redact_aws_credentials): bucket="testbucket", key="grype/databases/archive.tar.gz", CacheControl="public,max-age=31536000", - ContentType="application/x-tar", # this is legacy behavior, remove me ) diff --git a/manager/tests/unit/cli/test_listing.py b/manager/tests/unit/cli/test_listing.py index 303740b5..6c5ab3cb 100644 --- a/manager/tests/unit/cli/test_listing.py +++ b/manager/tests/unit/cli/test_listing.py @@ -63,7 +63,7 @@ def run(dir_with_config: str, skip_db_in_s3: list[str] = None, extra_dbs: list[s lst = db.Listing.from_json(contents) # create a DB entry for each artifact - url_prefix = "https://toolbox-data.anchore.io/" + url_prefix = "http://localhost:4566/" for entries in lst.available.values(): for entry in entries: db_bucket_path = entry.url.removeprefix(url_prefix) @@ -110,19 +110,19 @@ def create_tar_gz(built: str, version: int): "create-new-db", 0, [ - "grype/databases/vulnerability-db_v1_2023-08-08T01:33:25Z_45f59b141d7256bf2c4d.tar.gz", - "grype/databases/vulnerability-db_v2_2023-08-08T01:33:25Z_a89e961c0943175eb6a0.tar.gz", - "grype/databases/vulnerability-db_v3_2023-08-08T01:33:25Z_c6eb70d1d2bcff836ede.tar.gz", - "grype/databases/vulnerability-db_v4_2023-08-08T01:33:25Z_fe44be95fbb6ae335497.tar.gz", - "grype/databases/vulnerability-db_v5_2023-08-08T01:33:25Z_1072b8f15e5d53338836.tar.gz", + "databases/vulnerability-db_v1_2024-08-22T01:31:37Z_1724340381.tar.gz", + "databases/vulnerability-db_v2_2024-08-22T01:31:37Z_1724340410.tar.gz", + "databases/vulnerability-db_v3_2024-08-22T01:31:37Z_1724340460.tar.gz", + "databases/vulnerability-db_v4_2024-08-22T01:31:37Z_1724340541.tar.gz", + "databases/vulnerability-db_v5_2024-08-22T01:31:37Z_1724340606.tar.gz", ], [ "discovered 5 new database candidates to add to the listing", - "new db: grype/databases/vulnerability-db_v1_2023-08-08T01:33:25Z_45f59b141d7256bf2c4d.tar.gz", - "downloading file from s3 bucket=testbucket key=grype/databases/vulnerability-db_v1_2023-08-08T01:33:25Z_45f59b141d7256bf2c4d.tar.gz", + "new db: databases/vulnerability-db_v1_2024-08-22T01:31:37Z_1724340381.tar.gz", + "downloading file from s3 bucket=testbucket key=databases/vulnerability-db_v1_2024-08-22T01:31:37Z_1724340381.tar.gz", # note that the download URL isn't right relative to production values (where the existing listing was pulled from) # but instead it's correct relative to the configuration, which specifies a localhost route. - "adding new listing entry: Entry(built='2023-08-08T01:33:25Z', version=1, url='http://localhost:4566/testbucket/grype/databases/vulnerability-db_v1_2023-08-08T01:33:25Z_45f59b141d7256bf2c4d.tar.gz', checksum='sha256:9ece0b838be60974aee62087cf366b84e5b7cb74e7a665d86de7735aca927d36')", + "adding new listing entry: Entry(built='2024-08-22T01:31:37Z', version=1, url='http://localhost:4566/testbucket/databases/vulnerability-db_v1_2024-08-22T01:31:37Z_1724340381.tar.gz', checksum='sha256:7a62753ddb1f12994fdcd41244106cc459406620c522cb32c54da46b12b86634')", "wrote 15 total database entries to the listing", ], id="create-new-db", @@ -137,7 +137,7 @@ def test_create_listing( # contains an application config file config_dir_path = test_dir_path(f"fixtures/listing/{case_dir}") listing_s3_mock(config_dir_path, extra_dbs=extra_dbs) - mock_file_age.return_value = 42 # needs to be less than distribution.MAX_DB_AGE + mock_file_age.return_value = 2 # needs to be less than distribution.MAX_DB_AGE with utils.set_directory(config_dir_path): with open("expected-listing.json") as f: diff --git a/manager/tests/unit/db/test_listing.py b/manager/tests/unit/db/test_listing.py index eb3fff9a..cf1b95fb 100644 --- a/manager/tests/unit/db/test_listing.py +++ b/manager/tests/unit/db/test_listing.py @@ -85,7 +85,7 @@ def test_listing_add_sorts_by_date(): ), ) def test_listing_url(s3_path, expected): - assert expected == db.Listing.url(s3_path) + assert expected == db.Listing.url(s3_path, "listing.json") def test_listing_basenames(): @@ -592,3 +592,58 @@ def test_to_and_from_json(): got_from = db.Listing.from_json(got_to) assert subject == got_from + + +@pytest.fixture +def listing(): + # out-of-order entries, including entries with the same timestamp but different suffixes + entries = [ + db.listing.Entry( + built="2024-08-22T01:31:37Z", + version=1, + url="https://grype.anchore.io/databases/vulnerability-db_v1_2024-08-21T01:31:31Z_1724213864.tar.gz", + checksum="sha256:d0e8ca5357ebc152b767fe0a9caba8aba0dd106eacf936c653f02932a2c8e238", + ), + db.listing.Entry( + built="2024-08-22T01:31:37Z", + version=1, + url="https://grype.anchore.io/databases/vulnerability-db_v1_2024-08-22T01:31:37Z_1724300289.tar.gz", + checksum="sha256:5ff9f2c047514ba4fbfc45e84df234ecbd2f09c11002c2f008be3a5c2c73b6f1", + ), + db.listing.Entry( + built="2024-08-22T01:31:37Z", + version=1, + url="https://grype.anchore.io/databases/vulnerability-db_v1_2024-08-22T01:31:37Z_1724340381.tar.gz", + checksum="sha256:d3a298876eba3802bef8e3d9abb1941aa1f9ef2405333624951baeb85ea8f3da", + ), + db.listing.Entry( + built="2024-08-21T01:31:31Z", + version=1, + url="https://grype.anchore.io/databases/vulnerability-db_v1_2024-08-21T01:31:31Z_1724213870.tar.gz", + checksum="sha256:e1a298876eba3802bef8e3d9abb1941aa1f9ef2405333624951baeb85ea8f3dc", + ), + db.listing.Entry( + built="2024-08-22T01:31:37Z", + version=1, + url="https://grype.anchore.io/databases/vulnerability-db_v1_2024-08-22T01:31:37Z_1724340382.tar.gz", + checksum="sha256:f3a298876eba3802bef8e3d9abb1941aa1f9ef2405333624951baeb85ea8f3fb", + ), + ] + + available = {1: entries} + return db.listing.Listing(available=available) + + +def test_listing_sort(listing): + + listing.sort() + + sorted_urls = [ + "https://grype.anchore.io/databases/vulnerability-db_v1_2024-08-22T01:31:37Z_1724340382.tar.gz", + "https://grype.anchore.io/databases/vulnerability-db_v1_2024-08-22T01:31:37Z_1724340381.tar.gz", + "https://grype.anchore.io/databases/vulnerability-db_v1_2024-08-22T01:31:37Z_1724300289.tar.gz", + "https://grype.anchore.io/databases/vulnerability-db_v1_2024-08-21T01:31:31Z_1724213870.tar.gz", + "https://grype.anchore.io/databases/vulnerability-db_v1_2024-08-21T01:31:31Z_1724213864.tar.gz", + ] + + assert [entry.url for entry in listing.available[1]] == sorted_urls diff --git a/manager/tests/unit/db/test_schema.py b/manager/tests/unit/db/test_schema.py index 1f063503..42cbea30 100644 --- a/manager/tests/unit/db/test_schema.py +++ b/manager/tests/unit/db/test_schema.py @@ -10,8 +10,9 @@ def test_grype_version(): assert "v0.12.1" == schema.grype_version(2) assert "v0.40.1" == schema.grype_version(3) assert "v0.50.2" == schema.grype_version(4) - assert "main" == schema.grype_version(5) + assert "v0.87.0" == schema.grype_version(5) + assert "main" == schema.grype_version(6) def test_supported_schema_versions(): - assert schema.supported_schema_versions() == [1, 2, 3, 4, 5] + assert schema.supported_schema_versions() == [5, 6] diff --git a/manager/tests/unit/db/test_validation.py b/manager/tests/unit/db/test_validation.py index 4214a089..6beaa100 100644 --- a/manager/tests/unit/db/test_validation.py +++ b/manager/tests/unit/db/test_validation.py @@ -9,19 +9,6 @@ from grype_db_manager.db import validation -def test_guess_tool_orientation(): - latest_release_tool, current_tool = validation.guess_tool_orientation(["grype@latest", "grype[custom-db]@latest"]) - - assert latest_release_tool == "grype@latest" - assert current_tool == "grype[custom-db]@latest" - - with pytest.raises(ValueError): - validation.guess_tool_orientation(["grype@latest", "grype@latest"]) - - with pytest.raises(ValueError): - validation.guess_tool_orientation(["grype@latest", "grype[custom-db]"]) - - def _partial_db_info(checksum: str): return grypedb.DBInfo( uuid="session-id", @@ -98,302 +85,3 @@ def test_is_result_set_stale(test_dir_path, test_case, db_info, expected): ) assert is_stale == expected - - -@pytest.mark.parametrize( - "result_set, label_set, expected_reasons", - [ - pytest.param( - "go-case", - "all-tp", - [], - id="pass-when-no-differences", - ), - pytest.param( - "new-db-run-missing-half", - "all-tp", - [ - "current F1 score is lower than the last release F1 score: current=0.53 last=1.00 by-margin=0.00 image=docker.io/oraclelinux@sha256:a06327c0f1d18d753f2a60bb17864c84a850bb6dcbcf5946dd1a8123f6e75495", - "current false negatives is greater than the last release false negatives: current=9 last=0 by-margin=0 image=docker.io/oraclelinux@sha256:a06327c0f1d18d753f2a60bb17864c84a850bb6dcbcf5946dd1a8123f6e75495", - ], - id="fail-when-introduced-fns", - ), - pytest.param( - "old-db-run-missing-half", - "all-tp", - [], - id="pass-when-introduced-tps", - ), - pytest.param( - "new-db-run-missing-half", - "first-half-fp", - [], - id="pass-when-eliminated-fps", - ), - pytest.param( - "old-db-run-missing-half", - "first-half-fp", - [ - "current F1 score is lower than the last release F1 score: current=0.53 last=1.00 by-margin=0.00 image=docker.io/oraclelinux@sha256:a06327c0f1d18d753f2a60bb17864c84a850bb6dcbcf5946dd1a8123f6e75495" - ], - id="fail-when-introduced-fps", - ), - ], -) -def test_validate_image(test_dir_path, result_set, label_set, expected_reasons): - yardstick_root = test_dir_path(f"fixtures/validate-image/yardstick") - expected_pass = len(expected_reasons) == 0 - - result_set_obj = store.result_set.load(result_set, store_root=yardstick_root) - images = sorted({s.config.image for s in result_set_obj.state}) - - assert len(images) == 1 - - image = images[0] - - tools = [ - ycfg.Tool( - label=s.request.label, - name=s.request.tool.split("@")[0], - takes=s.request.takes, - version=s.request.tool.split("@")[1], - ) - for s in result_set_obj.state - ] - - assert len(tools) == 2 - - yardstick_cfg = ycfg.Application( - store_root=yardstick_root, - default_max_year=2022, - result_sets={ - result_set: ycfg.ResultSet( - description="test", - matrix=ycfg.ScanMatrix( - images=[image], - tools=tools, - ), - ), - }, - ) - - label_set_root = test_dir_path(f"fixtures/validate-image/label-sets/{label_set}") - label_entries = store.labels.load_for_image(images, year_max_limit=yardstick_cfg.default_max_year, store_root=label_set_root) - - gate = validation.validate_image( - cfg=yardstick_cfg, - descriptions=result_set_obj.descriptions, - label_entries=label_entries, - store_root=yardstick_root, - ) - - assert gate - assert gate.passed() == expected_pass - assert gate.reasons == expected_reasons - - -@pytest.mark.parametrize( - "last, current, config, expect_fail", - [ - pytest.param( - 0.5, - 0.5, - validation.GateConfig(), - False, - id="default-config-pass-when-no-difference", - ), - pytest.param( - 0.5, - 0.55, - validation.GateConfig(), - False, - id="default-config-pass-when-there-is-better-performance", - ), - pytest.param( - 0.5, - 0.49, - validation.GateConfig(), - True, - id="default-config-fail-when-below-0-threshold", - ), - pytest.param( - 0.5, - 0.5, - validation.GateConfig(f1_score_threshold=0.1), - False, - id="custom-config-pass-when-no-difference", - ), - pytest.param( - 0.5, - 0.55, - validation.GateConfig(f1_score_threshold=0.1), - False, - id="custom-config-pass-when-there-is-better-performance", - ), - pytest.param( - 0.5, - 0.49, - validation.GateConfig(f1_score_threshold=0.1), - False, - id="custom-config-pass-when-within-margin", - ), - pytest.param( - 0.5, - 0.4, - validation.GateConfig(f1_score_threshold=0.1), - False, - id="custom-config-pass-when-at-margin", - ), - pytest.param( - 0.5, - 0.39, - validation.GateConfig(f1_score_threshold=0.1), - True, - id="custom-config-fail-when-below-margin", - ), - ], -) -def test_gate_evaluate_f1_score(last, current, config, expect_fail): - gate = validation.Gate(None, None, config=config) - reason = gate._evaluate_f1_score( - last_f1_score=last, - current_f1_score=current, - context="test", - ) - - reason_is_fail = reason is not None - - assert reason_is_fail == expect_fail - - -@pytest.mark.parametrize( - "last, current, config, expect_fail", - [ - pytest.param( - 5, - 5, - validation.GateConfig(), - False, - id="default-config-pass-when-no-difference", - ), - pytest.param( - 5, - 4, - validation.GateConfig(), - False, - id="default-config-pass-when-there-is-better-performance", - ), - pytest.param( - 5, - 6, - validation.GateConfig(), - True, - id="default-config-fail-when-below-0-threshold", - ), - pytest.param( - 5, - 5, - validation.GateConfig(introduced_fns_threshold=2), - False, - id="custom-config-pass-when-no-difference", - ), - pytest.param( - 5, - 4, - validation.GateConfig(introduced_fns_threshold=2), - False, - id="custom-config-pass-when-there-is-better-performance", - ), - pytest.param( - 5, - 6, - validation.GateConfig(introduced_fns_threshold=2), - False, - id="custom-config-pass-when-within-margin", - ), - pytest.param( - 5, - 7, - validation.GateConfig(introduced_fns_threshold=2), - False, - id="custom-config-pass-when-at-margin", - ), - pytest.param( - 5, - 8, - validation.GateConfig(introduced_fns_threshold=2), - True, - id="custom-config-fail-when-below-margin", - ), - ], -) -def test_gate_evaluate_fns(last, current, config, expect_fail): - gate = validation.Gate(None, None, config=config) - reason = gate._evaluate_fns( - last_fns=last, - current_fns=current, - context="test", - ) - - reason_is_fail = reason is not None - - assert reason_is_fail == expect_fail - - -@pytest.mark.parametrize( - "indeterminate_percent, config, expect_fail", - [ - pytest.param( - 9, - validation.GateConfig(), - False, - id="default-config-pass-when-below-threshold", - ), - pytest.param( - 10, - validation.GateConfig(), - False, - id="default-config-pass-when-at-threshold", - ), - pytest.param( - 11, - validation.GateConfig(), - True, - id="default-config-fail-when-above-threshold", - ), - pytest.param( - 9, - validation.GateConfig(unlabeled_matches_threshold=20), - False, - id="custom-config-pass-when-below-margin", - ), - pytest.param( - 20, - validation.GateConfig(unlabeled_matches_threshold=20), - False, - id="custom-config-pass-when-at-margin", - ), - pytest.param( - 19, - validation.GateConfig(unlabeled_matches_threshold=20), - False, - id="custom-config-pass-when-within-margin", - ), - pytest.param( - 21, - validation.GateConfig(unlabeled_matches_threshold=20), - True, - id="custom-config-fail-when-above-margin", - ), - ], -) -def test_evaluate_indeterminate_percent(indeterminate_percent, config, expect_fail): - gate = validation.Gate(None, None, config=config) - reason = gate._evaluate_indeterminate_percent( - indeterminate_percent=indeterminate_percent, - context="test", - ) - - reason_is_fail = reason is not None - - assert reason_is_fail == expect_fail diff --git a/manager/tests/unit/test_grypedb.py b/manager/tests/unit/test_grypedb.py index 7c52c610..f2f8772a 100644 --- a/manager/tests/unit/test_grypedb.py +++ b/manager/tests/unit/test_grypedb.py @@ -70,16 +70,16 @@ def test_new_session(self, tmp_path: pathlib.Path): [ pytest.param([], 5, True, id="empty"), pytest.param(["namespace1"], 5, True, id="too few namespaces"), - pytest.param(grypedb.expected_namespaces, 5, False, id="v5 matches"), - pytest.param(grypedb.expected_namespaces + ["extra_items"], 5, False, id="v5 with extra items"), - pytest.param(list(grypedb.expected_namespaces)[:-5], 5, True, id="v5 missing items"), - pytest.param(grypedb.v3_expected_namespaces, 3, False, id="v3 matches"), - pytest.param(grypedb.v3_expected_namespaces + ["extra_items"], 3, False, id="v3 with extra items"), - pytest.param(list(grypedb.v3_expected_namespaces)[:-5], 3, True, id="v3 missing items"), + pytest.param(grypedb.expected_namespaces(5), 5, False, id="v5 matches"), + pytest.param(grypedb.expected_namespaces(5) + ["extra_items"], 5, False, id="v5 with extra items"), + pytest.param(list(grypedb.expected_namespaces(5))[:-5], 5, True, id="v5 missing items"), + pytest.param(grypedb.expected_namespaces(3), 3, False, id="v3 matches"), + pytest.param(grypedb.expected_namespaces(3) + ["extra_items"], 3, False, id="v3 with extra items"), + pytest.param(list(grypedb.expected_namespaces(3))[:-5], 3, True, id="v3 missing items"), ], ) def test_validate_namespaces(self, tmp_path: pathlib.Path, mocker, schema_version, listed_namespaces, expect_error): - assert len(grypedb.expected_namespaces) > 0 + assert len(grypedb.expected_namespaces(schema_version)) > 0 dbm = grypedb.DBManager(root_dir=tmp_path.as_posix()) session_id = dbm.new_session() @@ -108,6 +108,33 @@ def test_validate_namespaces(self, tmp_path: pathlib.Path, mocker, schema_versio dbm.list_namespaces.assert_called_once() dbm.get_db_info.assert_called_once() + @pytest.mark.parametrize( + "listed_providers, configured_providers, expect_error", + [ + pytest.param([], [], grypedb.DBProviderException, id="empty"), + pytest.param(["nvd"], ["nvd", "alpine"], grypedb.DBProviderException, id="too few providers"), + pytest.param(["nvd", "alpine"], ["nvd", "alpine"], None, id="valid"), + pytest.param(["nvd", "alpine", "extra"], ["nvd", "alpine"], None, id="extra items"), + ], + ) + def test_validate_providers(self, tmp_path: pathlib.Path, mocker, listed_providers, configured_providers, expect_error): + + dbm = grypedb.DBManager(root_dir=tmp_path.as_posix()) + session_id = dbm.new_session() + + # patch list_namespaces to return a mock + dbm.list_providers = mocker.MagicMock() + dbm.list_providers.return_value = listed_providers + + if expect_error: + with pytest.raises(expect_error): + dbm.validate_providers(session_id, configured_providers) + else: + dbm.validate_providers(session_id, configured_providers) + + if configured_providers: + dbm.list_providers.assert_called_once() + class TestGrypeDB: def test_list_installed(self, top_level_fixture): 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..56de3348 100644 --- a/pkg/data/transformers.go +++ b/pkg/data/transformers.go @@ -1,13 +1,29 @@ 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 // into commonly-shared data.Processors in the individual process.v* packages. +// all v1 transformers (schema v1 - v5) + type GitHubTransformer func(entry unmarshal.GitHubAdvisory) ([]Entry, error) 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) + +// all v2 transformers (schema v6+) +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) + +type KnownExploitedVulnerabilityTransformerV2 func(entry unmarshal.KnownExploitedVulnerability, state provider.State) ([]Entry, error) +type EPSSTransformerV2 func(entry unmarshal.EPSS, state provider.State) ([]Entry, error) +type OSVTransformerV2 func(entry unmarshal.OSVVulnerability, state provider.State) ([]Entry, error) diff --git a/pkg/process/build.go b/pkg/process/build.go index 39acf874..03981dc0 100644 --- a/pkg/process/build.go +++ b/pkg/process/build.go @@ -3,32 +3,31 @@ package process import ( "bytes" "fmt" + "sort" "time" "github.com/dustin/go-humanize" + "github.com/spf13/afero" "github.com/anchore/grype-db/internal/log" "github.com/anchore/grype-db/pkg/data" - v1 "github.com/anchore/grype-db/pkg/process/v1" - v2 "github.com/anchore/grype-db/pkg/process/v2" - 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" - grypeDBv1 "github.com/anchore/grype/grype/db/v1" - grypeDBv2 "github.com/anchore/grype/grype/db/v2" - 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 { - SchemaVersion int - Directory string - States provider.States - Timestamp time.Time + SchemaVersion int + Directory string + States provider.States + Timestamp time.Time + IncludeCPEParts []string + InferNVDFixVersions bool + Hydrate bool } func Build(cfg BuildConfig) error { @@ -38,128 +37,237 @@ func Build(cfg BuildConfig) error { "providers", cfg.States.Names()). Info("building database") - processors, err := getProcessors(cfg.SchemaVersion) + processors, err := getProcessors(cfg) if err != nil { return err } - writer, err := getWriter(cfg.SchemaVersion, cfg.Timestamp, cfg.Directory) + writer, err := getWriter(cfg.SchemaVersion, cfg.Timestamp, cfg.Directory, cfg.States) if err != nil { 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() -} + if err := writer.Close(); err != nil { + return err + } -type openerEntry struct { - openers <-chan entry.Opener - name string - count int64 -} + if cfg.Hydrate && cfg.SchemaVersion > 5 { + if err := hydrate(cfg); err != nil { + return err + } + } -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") + return nil +} - for opener := range e.openers { - out <- opener - } - } - }() - return out +type providerResults struct { + openers <-chan entry.Opener + provider provider.State + count int64 } -func getProcessors(schemaVersion int) ([]data.Processor, error) { - switch schemaVersion { - case grypeDBv1.SchemaVersion: - return v1.Processors(), nil - case grypeDBv2.SchemaVersion: - return v2.Processors(), nil - case grypeDBv3.SchemaVersion: - return v3.Processors(), nil - case grypeDBv4.SchemaVersion: - return v4.Processors(), nil +func getProcessors(cfg BuildConfig) ([]data.Processor, error) { + switch cfg.SchemaVersion { case grypeDBv5.SchemaVersion: - return v5.Processors(), nil + 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", schemaVersion) + return nil, fmt.Errorf("unable to create processor: unsupported schema version: %+v", cfg.SchemaVersion) } } -func getWriter(schemaVersion int, dataAge time.Time, directory string) (data.Writer, error) { +func getWriter(schemaVersion int, dataAge time.Time, directory string, states provider.States) (data.Writer, error) { switch schemaVersion { - case grypeDBv1.SchemaVersion: - return v1.NewWriter(directory, dataAge) - case grypeDBv2.SchemaVersion: - return v2.NewWriter(directory, dataAge) - case grypeDBv3.SchemaVersion: - return v3.NewWriter(directory, dataAge) - case grypeDBv4.SchemaVersion: - return v4.NewWriter(directory, dataAge) case grypeDBv5.SchemaVersion: - return v5.NewWriter(directory, dataAge) + 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() + var totalRecords int + for _, result := range results { + totalRecords += int(result.count) + } + log.WithFields("total", humanize.Comma(int64(totalRecords))).Info("processing all records") - 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 exponential moving average, choose an alpha between 0 and 1, where 1 biases towards the most recent sample + // and 0 biases towards the average of all samples. + rateWindow := newEMA(0.4) + + var recordsProcessed, recordsObserved, dropped int + droppedElementsByProvider := make(map[string]int) + droppedSchemaElements := make(map[string]int) + + for _, result := range results { + log.WithFields("provider", result.provider.Provider, "total", humanize.Comma(result.count)).Info("processing provider records") + providerRecordsObserved := 0 + recordsObservedInStatusCycle := 0 + for opener := range result.openers { + providerRecordsObserved++ + recordsObserved++ + recordsObservedInStatusCycle++ + var processor data.Processor - for _, candidate := range processors { - if candidate.IsSupported(envelope.Schema) { - processor = candidate - log.WithFields("schema", envelope.Schema).Trace("matched with processor") - break + if time.Since(lastUpdate) > 3*time.Second { + r := recordsPerSecond(recordsObservedInStatusCycle, lastUpdate) + rateWindow.Add(r) + + log.WithFields( + "provider", fmt.Sprintf("%q %1.0f/s (%1.2f%%)", result.provider.Provider, r, percent(providerRecordsObserved, int(result.count))), + "overall", fmt.Sprintf("%1.2f%%", percent(recordsObserved, totalRecords)), + "eta", eta(recordsObserved, totalRecords, rateWindow.Average()).String(), + ).Debug("status") + lastUpdate = time.Now() + recordsObservedInStatusCycle = 0 } - } - 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) - } + 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) + } - 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) + for _, candidate := range processors { + if candidate.IsSupported(envelope.Schema) { + processor = candidate + break + } + } + if processor == nil { + droppedElementsByProvider[result.provider.Provider]++ + droppedSchemaElements[envelope.Schema]++ + dropped++ + continue + } + recordsProcessed++ + + 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) + } } } - log.Debugf("wrote all provider state") + logDropped(droppedElementsByProvider, droppedSchemaElements) + + log.WithFields("processed", recordsProcessed, "dropped", dropped, "observed", recordsObserved).Debugf("wrote all provider state") + + if recordsProcessed == 0 { + return fmt.Errorf("no records were processed") + } return nil } + +func hydrate(cfg BuildConfig) error { + hydrator := grypeDBv6.Hydrater() + fs := afero.NewOsFs() + + if err := hydrator(cfg.Directory); err != nil { + return fmt.Errorf("failed to hydrate db: %w", err) + } + + doc, err := grypeDBv6.WriteImportMetadata(fs, cfg.Directory, "grype db build") + if err != nil { + return fmt.Errorf("failed to write checksums file: %w", err) + } + + log.WithFields("digest", doc.Digest).Trace("captured DB digest") + + return nil +} + +func logDropped(droppedElementsByProvider, droppedSchemaElements map[string]int) { + sortedKeys := func(m map[string]int) []string { + var keys []string + for k := range m { + keys = append(keys, k) + } + sort.Strings(keys) + return keys + } + sortedProviders := sortedKeys(droppedElementsByProvider) + for _, p := range sortedProviders { + log.WithFields("provider", p, "count", droppedElementsByProvider[p]).Warn("dropped records for provider") + } + + sortedSchemas := sortedKeys(droppedSchemaElements) + for _, s := range sortedSchemas { + log.WithFields("schema", s, "count", droppedSchemaElements[s]).Warn("dropped records by schema") + } +} + +type expMovingAverage struct { + alpha float64 + value float64 + count int +} + +func newEMA(alpha float64) *expMovingAverage { + return &expMovingAverage{alpha: alpha} +} + +func (e *expMovingAverage) Add(sample float64) { + if e.count == 0 { + e.value = sample // initialize with the first sample + } else { + e.value = e.alpha*sample + (1-e.alpha)*e.value + } + e.count++ +} + +func (e *expMovingAverage) Average() float64 { + return e.value +} + +func recordsPerSecond(idx int, lastUpdate time.Time) float64 { + sec := time.Since(lastUpdate).Seconds() + if sec == 0 { + return 0 + } + return float64(idx) / sec +} + +func percent(idx, total int) float64 { + if total == 0 { + return 0 + } + return float64(idx) / float64(total) * 100 +} + +func eta(idx, total int, rate float64) time.Duration { + if rate == 0 { + return 0 + } + return time.Duration(float64(total-idx)/rate) * time.Second +} 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..77c2b444 --- /dev/null +++ b/pkg/process/internal/codename/codenames_generated.go @@ -0,0 +1,115 @@ +// 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", + }, + "25": {"4": "plucky"}, + "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 86% rename from pkg/process/common/constraint.go rename to pkg/process/internal/common/constraint.go index 821c8286..936a1be8 100644 --- a/pkg/process/common/constraint.go +++ b/pkg/process/internal/common/constraint.go @@ -17,12 +17,16 @@ var forceSemVerPattern = regexp.MustCompile(`[><=]+\s*[^<>=]+`) func EnforceSemVerConstraint(constraint string) string { constraint = CleanConstraint(constraint) - if len(constraint) == 0 { + if constraint == "" { return "" } return strings.ReplaceAll(strings.Join(forceSemVerPattern.FindAllString(constraint, -1), ", "), " ", "") } +func AndConstraints(c ...string) string { + return strings.Join(c, " ") +} + func OrConstraints(c ...string) string { return strings.Join(c, " || ") } diff --git a/pkg/process/common/constraint_test.go b/pkg/process/internal/common/constraint_test.go similarity index 91% rename from pkg/process/common/constraint_test.go rename to pkg/process/internal/common/constraint_test.go index 0bf8568c..d4c23e21 100644 --- a/pkg/process/common/constraint_test.go +++ b/pkg/process/internal/common/constraint_test.go @@ -15,6 +15,10 @@ func TestEnforceSemVerConstraint(t *testing.T) { value: "None", expected: "", }, + { + value: "", + expected: "", + }, } for _, test := range tests { t.Run(test.value, func(t *testing.T) { 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 5ebf591b..6162e5cd 100644 --- a/pkg/process/package.go +++ b/pkg/process/package.go @@ -1,139 +1,18 @@ package process import ( - "crypto/rand" - "encoding/hex" - "fmt" - "net/url" "os" - "path" - "strings" - "time" + "path/filepath" - "github.com/spf13/afero" - - "github.com/anchore/grype-db/internal/log" - "github.com/anchore/grype-db/internal/tar" - "github.com/anchore/grype/grype/db" + v6process "github.com/anchore/grype-db/pkg/process/v6" + grypeDBLegacyDistribution "github.com/anchore/grype/grype/db/v5/distribution" ) -func randomString() (string, error) { - b := make([]byte, 10) - _, err := rand.Read(b) - return hex.EncodeToString(b), err -} - func Package(dbDir, publishBaseURL, overrideArchiveExtension string) error { - log.WithFields("from", dbDir, "url", publishBaseURL, "extension-override", overrideArchiveExtension).Info("packaging database") - - fs := afero.NewOsFs() - metadata, err := db.NewMetadataFromDir(fs, dbDir) - if err != nil { - return err - } - - if metadata == nil { - return fmt.Errorf("no metadata found in %q", dbDir) - } - - u, err := url.Parse(publishBaseURL) - if err != nil { - return err - } - - trailer, err := randomString() - if err != nil { - return fmt.Errorf("unable to create random archive trailer: %w", err) - } - - // TODO (alex): supporting tar.zst - // var extension = "tar.zst" - var extension = "tar.gz" - - if overrideArchiveExtension != "" { - extension = strings.TrimLeft(overrideArchiveExtension, ".") - } - // TODO (alex): supporting tar.zst - // else if metadata.Version < 5 { - // extension = "tar.gz" - // } - - var found bool - for _, valid := range []string{"tar.zst", "tar.gz"} { - if valid == extension { - found = true - break - } - } - - if !found { - return fmt.Errorf("invalid archive extension %q", extension) - } - - // we attach a random value at the end of the file name to prevent from overwriting DBs in S3 that are already - // cached in the CDN. Ideally this would be based off of the archive checksum but a random string is simpler. - tarName := fmt.Sprintf( - "vulnerability-db_v%d_%s_%s.%s", - metadata.Version, - metadata.Built.Format(time.RFC3339), - trailer, - extension, - ) - tarPath := path.Join(dbDir, tarName) - - if err := populate(tarName, dbDir); err != nil { - return err + // check if metadata file exists, if so, then this + if _, err := os.Stat(filepath.Join(dbDir, grypeDBLegacyDistribution.MetadataFileName)); os.IsNotExist(err) { + // TODO: detect from disk which version of the DB is present + return v6process.CreateArchive(dbDir, overrideArchiveExtension) } - - log.WithFields("path", tarPath).Info("created database archive") - - entry, err := db.NewListingEntryFromArchive(fs, *metadata, tarPath, u) - if err != nil { - return fmt.Errorf("unable to create listing entry from archive: %w", err) - } - - listing := db.NewListing(entry) - listingPath := path.Join(dbDir, db.ListingFileName) - if err = listing.Write(listingPath); err != nil { - return err - } - - log.WithFields("path", listingPath).Debug("created initial listing file") - - return nil -} - -func populate(tarName, dbDir string) error { - originalDir, err := os.Getwd() - if err != nil { - return fmt.Errorf("unable to get CWD: %w", err) - } - - if err = os.Chdir(dbDir); err != nil { - return fmt.Errorf("unable to cd to build dir: %w", err) - } - - defer func() { - if err = os.Chdir(originalDir); err != nil { - log.Errorf("unable to cd to original dir: %w", err) - } - }() - - fileInfos, err := os.ReadDir("./") - if err != nil { - return fmt.Errorf("unable to list db directory: %w", err) - } - - var files []string - for _, fi := range fileInfos { - if fi.Name() != "listing.json" && !strings.Contains(fi.Name(), ".tar.") { - files = append(files, fi.Name()) - } - } - - if err = tar.Populate(tarName, files...); err != nil { - return fmt.Errorf("unable to create db archive: %w", err) - } - - return nil + return packageLegacyDB(dbDir, publishBaseURL, overrideArchiveExtension) } diff --git a/pkg/process/package_legacy.go b/pkg/process/package_legacy.go new file mode 100644 index 00000000..0131d5d8 --- /dev/null +++ b/pkg/process/package_legacy.go @@ -0,0 +1,154 @@ +package process + +import ( + "fmt" + "net/url" + "os" + "path" + "path/filepath" + "strings" + "time" + + "github.com/scylladb/go-set/strset" + "github.com/spf13/afero" + + "github.com/anchore/grype-db/internal/log" + "github.com/anchore/grype-db/internal/tarutil" + grypeDBLegacy "github.com/anchore/grype/grype/db/v5" + grypeDBLegacyDistribution "github.com/anchore/grype/grype/db/v5/distribution" + grypeDBLegacyStore "github.com/anchore/grype/grype/db/v5/store" +) + +// listingFiles is a set of files that should not be included in the archive +var listingFiles = strset.New("listing.json", "latest.json", "history.json") + +func packageLegacyDB(dbDir, publishBaseURL, overrideArchiveExtension string) error { //nolint:funlen + log.WithFields("from", dbDir, "url", publishBaseURL, "extension-override", overrideArchiveExtension).Info("packaging database") + + fs := afero.NewOsFs() + metadata, err := grypeDBLegacyDistribution.NewMetadataFromDir(fs, dbDir) + if err != nil { + return err + } + + if metadata == nil { + 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 + } + + // we need a well-ordered string to append to the archive name to ensure uniqueness (to avoid overwriting + // existing archives in the CDN) as well as to ensure that multiple archives created in the same day are + // put in the correct order in the listing file. The DB timestamp represents the age of the data in the DB + // not when the DB was created. The trailer represents the time the DB was packaged. + trailer := fmt.Sprintf("%d", secondsSinceEpoch()) + + var extension = "tar.gz" + if overrideArchiveExtension != "" { + extension = strings.TrimLeft(overrideArchiveExtension, ".") + } + + var found bool + for _, valid := range []string{"tar.zst", "tar.gz"} { + if valid == extension { + found = true + break + } + } + + if !found { + return fmt.Errorf("invalid archive extension %q", extension) + } + + // we attach a random value at the end of the file name to prevent from overwriting DBs in S3 that are already + // cached in the CDN. Ideally this would be based off of the archive checksum but a random string is simpler. + tarName := fmt.Sprintf( + "vulnerability-db_v%d_%s_%s.%s", + metadata.Version, + metadata.Built.Format(time.RFC3339), + trailer, + extension, + ) + tarPath := path.Join(dbDir, tarName) + + if err := populateLegacyTar(tarPath); err != nil { + return err + } + + log.WithFields("path", tarPath).Info("created database archive") + + entry, err := grypeDBLegacyDistribution.NewListingEntryFromArchive(fs, *metadata, tarPath, u) + if err != nil { + return fmt.Errorf("unable to create listing entry from archive: %w", err) + } + + listing := grypeDBLegacyDistribution.NewListing(entry) + listingPath := path.Join(dbDir, grypeDBLegacyDistribution.ListingFileName) + if err = listing.Write(listingPath); err != nil { + return err + } + + log.WithFields("path", listingPath).Debug("created initial listing file") + + return nil +} + +func populateLegacyTar(tarPath string) error { + originalDir, err := os.Getwd() + if err != nil { + return fmt.Errorf("unable to get CWD: %w", err) + } + + dbDir, tarName := filepath.Split(tarPath) + + if dbDir != "" { + if err = os.Chdir(dbDir); err != nil { + return fmt.Errorf("unable to cd to build dir: %w", err) + } + + defer func() { + if err = os.Chdir(originalDir); err != nil { + log.Errorf("unable to cd to original dir: %v", err) + } + }() + } + + fileInfos, err := os.ReadDir("./") + if err != nil { + return fmt.Errorf("unable to list db directory: %w", err) + } + + var files []string + for _, fi := range fileInfos { + if !listingFiles.Has(fi.Name()) && !strings.Contains(fi.Name(), ".tar.") { + files = append(files, fi.Name()) + } + } + + if err = tarutil.PopulateWithPaths(tarName, files...); err != nil { + return fmt.Errorf("unable to create db archive: %w", err) + } + + return nil +} + +func secondsSinceEpoch() int64 { + return time.Now().UTC().Unix() +} diff --git a/pkg/process/processors/epss_processor.go b/pkg/process/processors/epss_processor.go new file mode 100644 index 00000000..abe08209 --- /dev/null +++ b/pkg/process/processors/epss_processor.go @@ -0,0 +1,60 @@ +//nolint:dupl +package processors + +import ( + "io" + + "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 epssProcessor struct { + transformer data.EPSSTransformerV2 +} + +func NewV2EPSSProcessor(transformer data.EPSSTransformerV2) data.Processor { + return &epssProcessor{ + transformer: transformer, + } +} + +func (p epssProcessor) Process(reader io.Reader, state provider.State) ([]data.Entry, error) { + var results []data.Entry + + entries, err := unmarshal.EPSSEntries(reader) + if err != nil { + return nil, err + } + + for _, entry := range entries { + if entry.IsEmpty() { + log.Warn("dropping empty EPSS entry") + continue + } + + transformedEntries, err := p.transformer(entry, state) + if err != nil { + return nil, err + } + + results = append(results, transformedEntries...) + } + + return results, nil +} + +func (p epssProcessor) IsSupported(schemaURL string) bool { + if !hasSchemaSegment(schemaURL, "epss") { + return false + } + + parsedVersion, err := parseVersion(schemaURL) + if err != nil { + log.WithFields("schema", schemaURL, "error", err).Error("failed to parse EPSS schema version") + return false + } + + return parsedVersion.Major == 1 +} diff --git a/pkg/process/processors/epss_processor_test.go b/pkg/process/processors/epss_processor_test.go new file mode 100644 index 00000000..9aebd6fa --- /dev/null +++ b/pkg/process/processors/epss_processor_test.go @@ -0,0 +1,83 @@ +package processors + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/anchore/grype-db/pkg/data" + "github.com/anchore/grype-db/pkg/provider" + "github.com/anchore/grype-db/pkg/provider/unmarshal" +) + +func mockEPSSProcessorTransform(entry unmarshal.EPSS, state provider.State) ([]data.Entry, error) { + return []data.Entry{ + { + DBSchemaVersion: 0, + Data: entry, + }, + }, nil +} + +func TestEPSSProcessor_Process(t *testing.T) { + f, err := os.Open("test-fixtures/epss.json") + require.NoError(t, err) + defer f.Close() + + processor := NewV2EPSSProcessor(mockEPSSProcessorTransform) + entries, err := processor.Process(f, provider.State{ + Provider: "epss", + }) + + assert.NoError(t, err) + assert.Len(t, entries, 2) +} + +func TestEPSSProcessor_IsSupported(t *testing.T) { + tc := []struct { + name string + schemaURL string + expected bool + }{ + { + name: "valid schema URL with version 1.0.0", + schemaURL: "https://example.com/vunnel/path/vulnerability/epss/schema-1.0.0.json", + expected: true, + }, + { + name: "valid schema URL with version 1.2.3", + schemaURL: "https://example.com/vunnel/path/vulnerability/epss/schema-1.2.3.json", + expected: true, + }, + { + name: "invalid schema URL with unsupported version", + schemaURL: "https://example.com/vunnel/path/vulnerability/epss/schema-2.0.0.json", + expected: false, + }, + { + name: "invalid schema URL with missing version", + schemaURL: "https://example.com/vunnel/path/vulnerability/epss/schema.json", + expected: false, + }, + { + name: "completely invalid URL", + schemaURL: "https://example.com/invalid/schema/url", + expected: false, + }, + { + name: "invalid schema segment", + schemaURL: "https://example.com/vunnel/path/vulnerability/not-epss/schema-1.0.0.json", + expected: false, + }, + } + + p := epssProcessor{} + + for _, tt := range tc { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, p.IsSupported(tt.schemaURL)) + }) + } +} diff --git a/pkg/process/processors/github_processor.go b/pkg/process/processors/github_processor.go index ccab8984..bce116e3 100644 --- a/pkg/process/processors/github_processor.go +++ b/pkg/process/processors/github_processor.go @@ -3,15 +3,15 @@ package processors import ( "io" - "strings" "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 +20,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 +34,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 } @@ -46,15 +64,15 @@ func (p githubProcessor) Process(reader io.Reader) ([]data.Entry, error) { } func (p githubProcessor) IsSupported(schemaURL string) bool { - matchesSchemaType := strings.Contains(schemaURL, "https://raw.githubusercontent.com/anchore/vunnel/main/schema/vulnerability/github-security-advisory/schema-") - if !matchesSchemaType { + if !hasSchemaSegment(schemaURL, "github-security-advisory") { return false } - if !strings.HasSuffix(schemaURL, "schema-1.0.0.json") && !strings.HasSuffix(schemaURL, "schema-1.0.1.json") { - log.WithFields("schema", schemaURL).Trace("unsupported GHSA schema version") + parsedVersion, err := parseVersion(schemaURL) + if err != nil { + log.WithFields("schema", schemaURL, "error", err).Error("failed to parse GHSA schema version") return false } - return true + return parsedVersion.Major == 1 } diff --git a/pkg/process/processors/github_processor_test.go b/pkg/process/processors/github_processor_test.go index f4c203ce..adc46294 100644 --- a/pkg/process/processors/github_processor_test.go +++ b/pkg/process/processors/github_processor_test.go @@ -8,7 +8,8 @@ import ( "github.com/stretchr/testify/require" "github.com/anchore/grype-db/pkg/data" - testUtils "github.com/anchore/grype-db/pkg/process/tests" + "github.com/anchore/grype-db/pkg/process/internal/tests" + "github.com/anchore/grype-db/pkg/provider" "github.com/anchore/grype-db/pkg/provider/unmarshal" ) @@ -24,11 +25,55 @@ func mockGithubProcessorTransform(vulnerability unmarshal.GitHubAdvisory) ([]dat func TestGitHubProcessor_Process(t *testing.T) { f, err := os.Open("test-fixtures/github.json") require.NoError(t, err) - defer testUtils.CloseFile(f) + defer tests.CloseFile(f) processor := NewGitHubProcessor(mockGithubProcessorTransform) - entries, err := processor.Process(f) + entries, err := processor.Process(f, provider.State{ + Provider: "github", + }) assert.NoError(t, err) assert.Len(t, entries, 3) } + +func TestGithubProcessor_IsSupported(t *testing.T) { + tc := []struct { + name string + schemaURL string + expected bool + }{ + { + name: "valid schema URL with version 1.0.0", + schemaURL: "https://example.com/vunnel/path/vulnerability/github-security-advisory/schema-1.0.0.json", + expected: true, + }, + { + name: "valid schema URL with version 1.2.3", + schemaURL: "https://example.com/vunnel/path/vulnerability/github-security-advisory/schema-1.2.3.json", + expected: true, + }, + { + name: "invalid schema URL with unsupported version", + schemaURL: "https://example.com/vunnel/path/vulnerability/github-security-advisory/schema-2.0.0.json", + expected: false, + }, + { + name: "invalid schema URL with missing version", + schemaURL: "https://example.com/vunnel/path/vulnerability/github-security-advisory/schema.json", + expected: false, + }, + { + name: "completely invalid URL", + schemaURL: "https://example.com/invalid/schema/url", + expected: false, + }, + } + + p := githubProcessor{} + + for _, tt := range tc { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, p.IsSupported(tt.schemaURL)) + }) + } +} diff --git a/pkg/process/processors/kev_processor.go b/pkg/process/processors/kev_processor.go new file mode 100644 index 00000000..04d338d3 --- /dev/null +++ b/pkg/process/processors/kev_processor.go @@ -0,0 +1,60 @@ +//nolint:dupl +package processors + +import ( + "io" + + "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 kevProcessor struct { + transformer data.KnownExploitedVulnerabilityTransformerV2 +} + +func NewV2KEVProcessor(transformer data.KnownExploitedVulnerabilityTransformerV2) data.Processor { + return &kevProcessor{ + transformer: transformer, + } +} + +func (p kevProcessor) Process(reader io.Reader, state provider.State) ([]data.Entry, error) { + var results []data.Entry + + entries, err := unmarshal.KnownExploitedVulnerabilityEntries(reader) + if err != nil { + return nil, err + } + + for _, entry := range entries { + if entry.IsEmpty() { + log.Warn("dropping empty KEV entry") + continue + } + + transformedEntries, err := p.transformer(entry, state) + if err != nil { + return nil, err + } + + results = append(results, transformedEntries...) + } + + return results, nil +} + +func (p kevProcessor) IsSupported(schemaURL string) bool { + if !hasSchemaSegment(schemaURL, "known-exploited") { + return false + } + + parsedVersion, err := parseVersion(schemaURL) + if err != nil { + log.WithFields("schema", schemaURL, "error", err).Error("failed to parse KEV schema version") + return false + } + + return parsedVersion.Major == 1 +} diff --git a/pkg/process/processors/kev_processor_test.go b/pkg/process/processors/kev_processor_test.go new file mode 100644 index 00000000..48138a15 --- /dev/null +++ b/pkg/process/processors/kev_processor_test.go @@ -0,0 +1,83 @@ +package processors + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/anchore/grype-db/pkg/data" + "github.com/anchore/grype-db/pkg/provider" + "github.com/anchore/grype-db/pkg/provider/unmarshal" +) + +func mockKEVProcessorTransform(vulnerability unmarshal.KnownExploitedVulnerability, state provider.State) ([]data.Entry, error) { + return []data.Entry{ + { + DBSchemaVersion: 0, + Data: vulnerability, + }, + }, nil +} + +func TestKEVProcessor_Process(t *testing.T) { + f, err := os.Open("test-fixtures/kev.json") + require.NoError(t, err) + defer f.Close() + + processor := NewV2KEVProcessor(mockKEVProcessorTransform) + entries, err := processor.Process(f, provider.State{ + Provider: "kev", + }) + + assert.NoError(t, err) + assert.Len(t, entries, 4) +} + +func TestKEVProcessor_IsSupported(t *testing.T) { + tc := []struct { + name string + schemaURL string + expected bool + }{ + { + name: "valid schema URL with version 1.0.0", + schemaURL: "https://example.com/vunnel/path/vulnerability/known-exploited/schema-1.0.0.json", + expected: true, + }, + { + name: "valid schema URL with version 1.2.3", + schemaURL: "https://example.com/vunnel/path/vulnerability/known-exploited/schema-1.2.3.json", + expected: true, + }, + { + name: "invalid schema URL with unsupported version", + schemaURL: "https://example.com/vunnel/path/vulnerability/known-exploited/schema-2.0.0.json", + expected: false, + }, + { + name: "invalid schema URL with missing version", + schemaURL: "https://example.com/vunnel/path/vulnerability/known-exploited/schema.json", + expected: false, + }, + { + name: "completely invalid URL", + schemaURL: "https://example.com/invalid/schema/url", + expected: false, + }, + { + name: "invalid schema segment", + schemaURL: "https://example.com/vunnel/path/vulnerability/not-kev/schema-1.0.0.json", + expected: false, + }, + } + + p := kevProcessor{} + + for _, tt := range tc { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, p.IsSupported(tt.schemaURL)) + }) + } +} diff --git a/pkg/process/processors/match_exclusion_processor.go b/pkg/process/processors/match_exclusion_processor.go index cc4b1f9c..a2b98568 100644 --- a/pkg/process/processors/match_exclusion_processor.go +++ b/pkg/process/processors/match_exclusion_processor.go @@ -3,10 +3,10 @@ package processors import ( "io" - "strings" "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 +20,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) @@ -46,15 +46,15 @@ func (p matchExclusionProcessor) Process(reader io.Reader) ([]data.Entry, error) } func (p matchExclusionProcessor) IsSupported(schemaURL string) bool { - matchesSchemaType := strings.Contains(schemaURL, "https://raw.githubusercontent.com/anchore/vunnel/main/schema/match-exclusion/schema-") - if !matchesSchemaType { + if !hasSchemaSegment(schemaURL, "match-exclusion") { return false } - if !strings.HasSuffix(schemaURL, "schema-1.0.0.json") { - log.WithFields("schema", schemaURL).Trace("unsupported match-exclusion schema version") + parsedVersion, err := parseVersion(schemaURL) + if err != nil { + log.WithFields("schema", schemaURL, "error", err).Error("failed to parse match-exclusion schema version") return false } - return true + return parsedVersion.Major == 1 } diff --git a/pkg/process/processors/match_exclusion_processor_test.go b/pkg/process/processors/match_exclusion_processor_test.go index 060e27bc..eaa4a2f0 100644 --- a/pkg/process/processors/match_exclusion_processor_test.go +++ b/pkg/process/processors/match_exclusion_processor_test.go @@ -8,7 +8,8 @@ import ( "github.com/stretchr/testify/require" "github.com/anchore/grype-db/pkg/data" - testUtils "github.com/anchore/grype-db/pkg/process/tests" + "github.com/anchore/grype-db/pkg/process/internal/tests" + "github.com/anchore/grype-db/pkg/provider" "github.com/anchore/grype-db/pkg/provider/unmarshal" ) @@ -24,11 +25,55 @@ func mockMatchExclusionProcessorTransform(vulnerability unmarshal.MatchExclusion func TestMatchExclusionProcessor_Process(t *testing.T) { f, err := os.Open("test-fixtures/exclusions.json") require.NoError(t, err) - defer testUtils.CloseFile(f) + defer tests.CloseFile(f) processor := NewMatchExclusionProcessor(mockMatchExclusionProcessorTransform) - entries, err := processor.Process(f) + entries, err := processor.Process(f, provider.State{ + Provider: "match-exclusions", + }) require.NoError(t, err) assert.Len(t, entries, 3) } + +func TestMatchExclusionProcessor_IsSupported(t *testing.T) { + tc := []struct { + name string + schemaURL string + expected bool + }{ + { + name: "valid schema URL with version 1.0.0", + schemaURL: "https://example.com/vunnel/path/match-exclusion/schema-1.0.0.json", + expected: true, + }, + { + name: "valid schema URL with version 1.3.4", + schemaURL: "https://example.com/vunnel/path/match-exclusion/schema-1.3.4.json", + expected: true, + }, + { + name: "invalid schema URL with unsupported version", + schemaURL: "https://example.com/vunnel/path/match-exclusion/schema-2.0.0.json", + expected: false, + }, + { + name: "invalid schema URL with missing version", + schemaURL: "https://example.com/vunnel/path/match-exclusion/schema.json", + expected: false, + }, + { + name: "completely invalid URL", + schemaURL: "https://example.com/invalid/schema/url", + expected: false, + }, + } + + p := matchExclusionProcessor{} + + for _, tt := range tc { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, p.IsSupported(tt.schemaURL)) + }) + } +} diff --git a/pkg/process/processors/msrc_processor.go b/pkg/process/processors/msrc_processor.go index 1fa04044..f46db34f 100644 --- a/pkg/process/processors/msrc_processor.go +++ b/pkg/process/processors/msrc_processor.go @@ -1,17 +1,18 @@ +//nolint:dupl package processors import ( "io" - "strings" "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" ) // msrcProcessor defines the regular expression needed to signal what is supported type msrcProcessor struct { - transformer data.MSRCTransformer + transformer any } // NewMSRCProcessor creates a new instance of msrcProcessor particular to MSRC @@ -21,8 +22,13 @@ 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 NewV2MSRCProcessor(transformer data.MSRCTransformerV2) data.Processor { + return &msrcProcessor{ + transformer: transformer, + } +} + +func (p msrcProcessor) Process(reader io.Reader, state provider.State) ([]data.Entry, error) { var results []data.Entry entries, err := unmarshal.MSRCVulnerabilityEntries(reader) @@ -30,13 +36,25 @@ func (p msrcProcessor) Process(reader io.Reader) ([]data.Entry, error) { return nil, err } + var handle func(entry unmarshal.MSRCVulnerability) ([]data.Entry, error) + switch t := p.transformer.(type) { + case data.MSRCTransformer: + handle = func(entry unmarshal.MSRCVulnerability) ([]data.Entry, error) { + return t(entry) + } + case data.MSRCTransformerV2: + handle = func(entry unmarshal.MSRCVulnerability) ([]data.Entry, error) { + return t(entry, state) + } + } + for _, entry := range entries { - if entry.ID == "" { + if entry.IsEmpty() { log.Warn("dropping empty MSRC entry") continue } - transformedEntries, err := p.transformer(entry) + transformedEntries, err := handle(entry) if err != nil { return nil, err } @@ -48,15 +66,15 @@ func (p msrcProcessor) Process(reader io.Reader) ([]data.Entry, error) { } func (p msrcProcessor) IsSupported(schemaURL string) bool { - matchesSchemaType := strings.Contains(schemaURL, "https://raw.githubusercontent.com/anchore/vunnel/main/schema/vulnerability/msrc/schema-") - if !matchesSchemaType { + if !hasSchemaSegment(schemaURL, "msrc") { return false } - if !strings.HasSuffix(schemaURL, "schema-1.0.0.json") { - log.WithFields("schema", schemaURL).Trace("unsupported MSRC schema version") + parsedVersion, err := parseVersion(schemaURL) + if err != nil { + log.WithFields("schema", schemaURL, "error", err).Error("failed to parse MSRC schema version") return false } - return true + return parsedVersion.Major == 1 } diff --git a/pkg/process/processors/msrc_processor_test.go b/pkg/process/processors/msrc_processor_test.go index e95f2f66..c56bf28f 100644 --- a/pkg/process/processors/msrc_processor_test.go +++ b/pkg/process/processors/msrc_processor_test.go @@ -8,7 +8,8 @@ import ( "github.com/stretchr/testify/require" "github.com/anchore/grype-db/pkg/data" - testUtils "github.com/anchore/grype-db/pkg/process/tests" + "github.com/anchore/grype-db/pkg/process/internal/tests" + "github.com/anchore/grype-db/pkg/provider" "github.com/anchore/grype-db/pkg/provider/unmarshal" ) @@ -24,11 +25,55 @@ func mockMSRCProcessorTransform(vulnerability unmarshal.MSRCVulnerability) ([]da func TestMSRCProcessor_Process(t *testing.T) { f, err := os.Open("test-fixtures/msrc.json") require.NoError(t, err) - defer testUtils.CloseFile(f) + defer tests.CloseFile(f) processor := NewMSRCProcessor(mockMSRCProcessorTransform) - entries, err := processor.Process(f) + entries, err := processor.Process(f, provider.State{ + Provider: "msrc", + }) require.NoError(t, err) assert.Len(t, entries, 2) } + +func TestMsrcProcessor_IsSupported(t *testing.T) { + tc := []struct { + name string + schemaURL string + expected bool + }{ + { + name: "valid schema URL with version 1.0.0", + schemaURL: "https://example.com/vunnel/path/vulnerability/msrc/schema-1.0.0.json", + expected: true, + }, + { + name: "valid schema URL with version 1.2.3", + schemaURL: "https://example.com/vunnel/path/vulnerability/msrc/schema-1.2.3.json", + expected: true, + }, + { + name: "invalid schema URL with unsupported version", + schemaURL: "https://example.com/vunnel/path/vulnerability/msrc/schema-2.0.0.json", + expected: false, + }, + { + name: "invalid schema URL with missing version", + schemaURL: "https://example.com/vunnel/path/vulnerability/msrc/schema.json", + expected: false, + }, + { + name: "completely invalid URL", + schemaURL: "https://example.com/invalid/schema/url", + expected: false, + }, + } + + p := msrcProcessor{} + + for _, tt := range tc { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, p.IsSupported(tt.schemaURL)) + }) + } +} diff --git a/pkg/process/processors/nvd_processor.go b/pkg/process/processors/nvd_processor.go index 3d7d4985..e8a77089 100644 --- a/pkg/process/processors/nvd_processor.go +++ b/pkg/process/processors/nvd_processor.go @@ -2,15 +2,15 @@ package processors import ( "io" - "strings" "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 +19,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 +33,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 } @@ -45,15 +63,15 @@ func (p nvdProcessor) Process(reader io.Reader) ([]data.Entry, error) { } func (p nvdProcessor) IsSupported(schemaURL string) bool { - matchesSchemaType := strings.Contains(schemaURL, "https://raw.githubusercontent.com/anchore/vunnel/main/schema/vulnerability/nvd/schema-") - if !matchesSchemaType { + if !hasSchemaSegment(schemaURL, "nvd") { return false } - if !strings.HasSuffix(schemaURL, "schema-1.0.0.json") { - log.WithFields("schema", schemaURL).Trace("unsupported NVD schema version") + parsedVersion, err := parseVersion(schemaURL) + if err != nil { + log.WithFields("schema", schemaURL, "error", err).Error("failed to parse NVD schema version") return false } - return true + return parsedVersion.Major == 1 } diff --git a/pkg/process/processors/nvd_processor_test.go b/pkg/process/processors/nvd_processor_test.go index e2179f83..5d58ac28 100644 --- a/pkg/process/processors/nvd_processor_test.go +++ b/pkg/process/processors/nvd_processor_test.go @@ -8,7 +8,8 @@ import ( "github.com/stretchr/testify/require" "github.com/anchore/grype-db/pkg/data" - testUtils "github.com/anchore/grype-db/pkg/process/tests" + "github.com/anchore/grype-db/pkg/process/internal/tests" + "github.com/anchore/grype-db/pkg/provider" "github.com/anchore/grype-db/pkg/provider/unmarshal" ) @@ -24,11 +25,55 @@ func mockNVDProcessorTransform(vulnerability unmarshal.NVDVulnerability) ([]data func TestNVDProcessor_Process(t *testing.T) { f, err := os.Open("test-fixtures/nvd.json") require.NoError(t, err) - defer testUtils.CloseFile(f) + defer tests.CloseFile(f) processor := NewNVDProcessor(mockNVDProcessorTransform) - entries, err := processor.Process(f) + entries, err := processor.Process(f, provider.State{ + Provider: "nvd", + }) require.NoError(t, err) assert.Len(t, entries, 3) } + +func TestNvdProcessor_IsSupported(t *testing.T) { + tc := []struct { + name string + schemaURL string + expected bool + }{ + { + name: "valid schema URL with version 1.0.0", + schemaURL: "https://example.com/vunnel/path/vulnerability/nvd/schema-1.0.0.json", + expected: true, + }, + { + name: "valid schema URL with version 1.4.7", + schemaURL: "https://example.com/vunnel/path/vulnerability/nvd/schema-1.4.7.json", + expected: true, + }, + { + name: "invalid schema URL with unsupported version", + schemaURL: "https://example.com/vunnel/path/vulnerability/nvd/schema-2.0.0.json", + expected: false, + }, + { + name: "invalid schema URL with missing version", + schemaURL: "https://example.com/vunnel/path/vulnerability/nvd/schema.json", + expected: false, + }, + { + name: "completely invalid URL", + schemaURL: "https://example.com/invalid/schema/url", + expected: false, + }, + } + + p := nvdProcessor{} + + for _, tt := range tc { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, p.IsSupported(tt.schemaURL)) + }) + } +} diff --git a/pkg/process/processors/os_processor.go b/pkg/process/processors/os_processor.go index 268fc26d..f77a98c3 100644 --- a/pkg/process/processors/os_processor.go +++ b/pkg/process/processors/os_processor.go @@ -3,15 +3,15 @@ package processors import ( "io" - "strings" "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 +20,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 +34,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 } @@ -46,15 +64,15 @@ func (p osProcessor) Process(reader io.Reader) ([]data.Entry, error) { } func (p osProcessor) IsSupported(schemaURL string) bool { - matchesSchemaType := strings.Contains(schemaURL, "https://raw.githubusercontent.com/anchore/vunnel/main/schema/vulnerability/os/schema-") - if !matchesSchemaType { + if !hasSchemaSegment(schemaURL, "os") { return false } - if !strings.HasSuffix(schemaURL, "schema-1.0.0.json") { - log.WithFields("schema", schemaURL).Trace("unsupported OS schema version") + parsedVersion, err := parseVersion(schemaURL) + if err != nil { + log.WithFields("schema", schemaURL, "error", err).Error("failed to parse OS schema version") return false } - return true + return parsedVersion.Major == 1 } diff --git a/pkg/process/processors/os_processor_test.go b/pkg/process/processors/os_processor_test.go index 3718fd1c..5826caed 100644 --- a/pkg/process/processors/os_processor_test.go +++ b/pkg/process/processors/os_processor_test.go @@ -8,7 +8,8 @@ import ( "github.com/stretchr/testify/require" "github.com/anchore/grype-db/pkg/data" - testUtils "github.com/anchore/grype-db/pkg/process/tests" + "github.com/anchore/grype-db/pkg/process/internal/tests" + "github.com/anchore/grype-db/pkg/provider" "github.com/anchore/grype-db/pkg/provider/unmarshal" ) @@ -24,11 +25,55 @@ func mockOSProcessorTransform(vulnerability unmarshal.OSVulnerability) ([]data.E func TestOSProcessor_Process(t *testing.T) { f, err := os.Open("test-fixtures/os.json") require.NoError(t, err) - defer testUtils.CloseFile(f) + defer tests.CloseFile(f) processor := NewOSProcessor(mockOSProcessorTransform) - entries, err := processor.Process(f) + entries, err := processor.Process(f, provider.State{ + Provider: "rhel", + }) require.NoError(t, err) assert.Len(t, entries, 4) } + +func TestOsProcessor_IsSupported(t *testing.T) { + tc := []struct { + name string + schemaURL string + expected bool + }{ + { + name: "valid schema URL with version 1.0.0", + schemaURL: "https://example.com/vunnel/path/vulnerability/os/schema-1.0.0.json", + expected: true, + }, + { + name: "valid schema URL with version 1.5.2", + schemaURL: "https://example.com/vunnel/path/vulnerability/os/schema-1.5.2.json", + expected: true, + }, + { + name: "invalid schema URL with unsupported version", + schemaURL: "https://example.com/vunnel/path/vulnerability/os/schema-2.0.0.json", + expected: false, + }, + { + name: "invalid schema URL with missing version", + schemaURL: "https://example.com/vunnel/path/vulnerability/os/schema.json", + expected: false, + }, + { + name: "completely invalid URL", + schemaURL: "https://example.com/invalid/schema/url", + expected: false, + }, + } + + p := osProcessor{} + + for _, tt := range tc { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, p.IsSupported(tt.schemaURL)) + }) + } +} diff --git a/pkg/process/processors/osv_processor.go b/pkg/process/processors/osv_processor.go new file mode 100644 index 00000000..049d6b25 --- /dev/null +++ b/pkg/process/processors/osv_processor.go @@ -0,0 +1,54 @@ +package processors + +import ( + "io" + + "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 osvProcessor struct { + transformer data.OSVTransformerV2 +} + +func NewV2OSVProcessor(transformer data.OSVTransformerV2) data.Processor { + return &osvProcessor{ + transformer: transformer, + } +} + +func (p osvProcessor) Process(reader io.Reader, state provider.State) ([]data.Entry, error) { + var results []data.Entry + + entries, err := unmarshal.OSVVulnerabilityEntries(reader) + if err != nil { + return nil, err + } + + for _, entry := range entries { + transformedEntries, err := p.transformer(entry, state) + if err != nil { + return nil, err + } + + results = append(results, transformedEntries...) + } + + return results, nil +} + +func (p osvProcessor) IsSupported(schemaURL string) bool { + if !hasSchemaSegment(schemaURL, "osv") { + return false + } + + parsedVersion, err := parseVersion(schemaURL) + if err != nil { + log.WithFields("schema", schemaURL, "error", err).Error("failed to parse NVD schema version") + return false + } + + return parsedVersion.Major == 1 +} diff --git a/pkg/process/processors/osv_processor_test.go b/pkg/process/processors/osv_processor_test.go new file mode 100644 index 00000000..660a6236 --- /dev/null +++ b/pkg/process/processors/osv_processor_test.go @@ -0,0 +1,83 @@ +package processors + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/anchore/grype-db/pkg/data" + "github.com/anchore/grype-db/pkg/process/internal/tests" + "github.com/anchore/grype-db/pkg/provider" + "github.com/anchore/grype-db/pkg/provider/unmarshal" +) + +func mockOSVProcessorTransform(vulnerability unmarshal.OSVVulnerability, state provider.State) ([]data.Entry, error) { + return []data.Entry{ + { + DBSchemaVersion: 0, + Data: vulnerability, + }, + }, nil +} + +func TestV2OSVProcessor_Process(t *testing.T) { + f, err := os.Open("test-fixtures/osv.json") + require.NoError(t, err) + defer tests.CloseFile(f) + + processor := NewV2OSVProcessor(mockOSVProcessorTransform) + entries, err := processor.Process(f, provider.State{ + Provider: "osv", + }) + + require.NoError(t, err) + assert.Len(t, entries, 2) +} + +func TestOSVProcessor_IsSupported(t *testing.T) { + tests := []struct { + name string + schemaURL string + want bool + }{ + { + name: "one actually used by vunnel is supported", + schemaURL: "https://raw.githubusercontent.com/anchore/vunnel/main/schema/vulnerability/osv/schema-1.5.0.json", + want: true, + }, + { + name: "osv schema 1.6.1 is supported", + schemaURL: "https://example.com/osv/schema-1.6.1.json", + want: true, + }, + { + name: "osv schema 1.5.0 is supported", + schemaURL: "https://example.com/osv/schema-1.5.0.json", + want: true, + }, + { + name: "lower major version is not supported", + schemaURL: "https://example.com/osv/schema-0.4.0.json", + want: false, + }, + { + name: "higher schema is not supported", + schemaURL: "https://example.com/osv/schema-2.4.0.json", + want: false, + }, + { + name: "non-osv schema is not supported", + schemaURL: "https://example.com/nvd/schema-1.4.0.json", + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := NewV2OSVProcessor(mockOSVProcessorTransform) + assert.Equal(t, tt.want, p.IsSupported(tt.schemaURL)) + }) + } +} diff --git a/pkg/process/processors/test-fixtures/epss.json b/pkg/process/processors/test-fixtures/epss.json new file mode 100644 index 00000000..b07738ab --- /dev/null +++ b/pkg/process/processors/test-fixtures/epss.json @@ -0,0 +1,14 @@ +[ + { + "cve": "CVE-2025-0108", + "epss": 0.328, + "percentile": 0.9929, + "date": "2025-02-18" + }, + { + "cve": "CVE-2025-0109", + "epss": 0.283, + "percentile": 0.9297, + "date": "2025-02-18" + } +] \ No newline at end of file diff --git a/pkg/process/processors/test-fixtures/kev.json b/pkg/process/processors/test-fixtures/kev.json new file mode 100644 index 00000000..ac5351fa --- /dev/null +++ b/pkg/process/processors/test-fixtures/kev.json @@ -0,0 +1,62 @@ +[ + { + "cveID": "CVE-2025-24989", + "vendorProject": "Microsoft", + "product": "Power Pages", + "vulnerabilityName": "Microsoft Power Pages Improper Access Control Vulnerability", + "dateAdded": "2025-02-21", + "shortDescription": "Microsoft Power Pages contains an improper access control vulnerability that allows an unauthorized attacker to elevate privileges over a network potentially bypassing the user registration control.", + "requiredAction": "Apply mitigations per vendor instructions, follow BOD 22-01 guidance for cloud services, or discontinue use of the product if mitigations are unavailable.", + "dueDate": "2025-03-14", + "knownRansomwareCampaignUse": "Unknown", + "notes": "https:\/\/msrc.microsoft.com\/update-guide\/en-US\/advisory\/CVE-2025-24989 ; https:\/\/nvd.nist.gov\/vuln\/detail\/CVE-2025-24989", + "cwes": [ + "CWE-284" + ] + }, + { + "cveID": "CVE-2025-0111", + "vendorProject": "Palo Alto Networks", + "product": "PAN-OS", + "vulnerabilityName": "Palo Alto Networks PAN-OS File Read Vulnerability", + "dateAdded": "2025-02-20", + "shortDescription": "Palo Alto Networks PAN-OS contains an external control of file name or path vulnerability. Successful exploitation enables an authenticated attacker with network access to the management web interface to read files on the PAN-OS filesystem that are readable by the \u201cnobody\u201d user.", + "requiredAction": "Apply mitigations per vendor instructions or discontinue use of the product if mitigations are unavailable.", + "dueDate": "2025-03-13", + "knownRansomwareCampaignUse": "Unknown", + "notes": "https:\/\/security.paloaltonetworks.com\/CVE-2025-0111 ; https:\/\/nvd.nist.gov\/vuln\/detail\/CVE-2025-0111", + "cwes": [ + "CWE-73" + ] + }, + { + "cveID": "CVE-2025-23209", + "vendorProject": "Craft CMS", + "product": "Craft CMS", + "vulnerabilityName": "Craft CMS Code Injection Vulnerability", + "dateAdded": "2025-02-20", + "shortDescription": "Craft CMS contains a code injection vulnerability caused by improper validation of the database backup path, ultimately enabling remote code execution.", + "requiredAction": "Apply mitigations per vendor instructions or discontinue use of the product if mitigations are unavailable.", + "dueDate": "2025-03-13", + "knownRansomwareCampaignUse": "Unknown", + "notes": "https:\/\/github.com\/craftcms\/cms\/security\/advisories\/GHSA-x684-96hh-833x ; https:\/\/nvd.nist.gov\/vuln\/detail\/CVE-2025-23209", + "cwes": [ + "CWE-94" + ] + }, + { + "cveID": "CVE-2025-0108", + "vendorProject": "Palo Alto Networks", + "product": "PAN-OS", + "vulnerabilityName": "Palo Alto Networks PAN-OS Authentication Bypass Vulnerability", + "dateAdded": "2025-02-18", + "shortDescription": "Palo Alto Networks PAN-OS contains an authentication bypass vulnerability in its management web interface. This vulnerability allows an unauthenticated attacker with network access to the management web interface to bypass the authentication normally required and invoke certain PHP scripts.", + "requiredAction": "Apply mitigations per vendor instructions or discontinue use of the product if mitigations are unavailable.", + "dueDate": "2025-03-11", + "knownRansomwareCampaignUse": "Unknown", + "notes": "https:\/\/security.paloaltonetworks.com\/CVE-2025-0108 ; https:\/\/nvd.nist.gov\/vuln\/detail\/CVE-2025-0108", + "cwes": [ + "CWE-306" + ] + } +] \ No newline at end of file diff --git a/pkg/process/processors/test-fixtures/osv.json b/pkg/process/processors/test-fixtures/osv.json new file mode 100644 index 00000000..9ddd5bf5 --- /dev/null +++ b/pkg/process/processors/test-fixtures/osv.json @@ -0,0 +1,130 @@ +[ + { + "schema_version": "1.3.1", + "id": "GO-2023-2412", + "modified": "0001-01-01T00:00:00Z", + "published": "0001-01-01T00:00:00Z", + "aliases": [ + "GHSA-7ww5-4wqc-m92c" + ], + "summary": "RAPL accessibility in github.com/containerd/containerd", + "details": "RAPL accessibility in github.com/containerd/containerd", + "affected": [ + { + "package": { + "name": "github.com/containerd/containerd", + "ecosystem": "Go" + }, + "ranges": [ + { + "type": "SEMVER", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "1.6.26" + }, + { + "introduced": "1.7.0" + }, + { + "fixed": "1.7.11" + } + ] + } + ], + "ecosystem_specific": { + "imports": [ + { + "path": "github.com/containerd/containerd/contrib/apparmor", + "symbols": [ + "DumpDefaultProfile", + "LoadDefaultProfile", + "generate" + ] + } + ] + } + } + ], + "references": [ + { + "type": "ADVISORY", + "url": "https://github.com/containerd/containerd/security/advisories/GHSA-7ww5-4wqc-m92c" + }, + { + "type": "FIX", + "url": "https://github.com/containerd/containerd/commit/67d356cb3095f3e8f8ad7d36f9a733fea1e7e28c" + }, + { + "type": "FIX", + "url": "https://github.com/containerd/containerd/commit/746b910f05855c8bfdb4415a1c0f958b234910e5" + } + ], + "database_specific": { + "url": "https://pkg.go.dev/vuln/GO-2023-2412" + } + }, + { + "schema_version": "1.3.1", + "id": "GO-2023-2413", + "modified": "0001-01-01T00:00:00Z", + "published": "0001-01-01T00:00:00Z", + "aliases": [ + "CVE-2023-49922", + "GHSA-hj4r-2c9c-29h3" + ], + "summary": "Sensitive information logged in github.com/elastic/beats/v7", + "details": "Sensitive information logged in github.com/elastic/beats/v7", + "affected": [ + { + "package": { + "name": "github.com/elastic/beats/v7", + "ecosystem": "Go" + }, + "ranges": [ + { + "type": "SEMVER", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "7.17.16" + } + ] + } + ], + "ecosystem_specific": { + "imports": [ + { + "path": "github.com/elastic/beats/v7/libbeat/processors/script/javascript", + "symbols": [ + "jsProcessor.Run", + "session.runProcessFunc" + ] + } + ] + } + } + ], + "references": [ + { + "type": "ADVISORY", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-49922" + }, + { + "type": "FIX", + "url": "https://github.com/elastic/beats/commit/9bd7de84ab9c31bb4e1c0a348a7b7c26817a0996" + }, + { + "type": "WEB", + "url": "https://discuss.elastic.co/t/beats-and-elastic-agent-8-11-3-7-17-16-security-update-esa-2023-30/349180" + } + ], + "database_specific": { + "url": "https://pkg.go.dev/vuln/GO-2023-2413" + } + } +] diff --git a/pkg/process/processors/version.go b/pkg/process/processors/version.go new file mode 100644 index 00000000..c8a3b8d0 --- /dev/null +++ b/pkg/process/processors/version.go @@ -0,0 +1,48 @@ +package processors + +import ( + "fmt" + "regexp" + "strconv" + "strings" +) + +var schemaFilePattern = regexp.MustCompile(`schema-(?P\d+)\.(?P\d+)\.(?P\d+)\.json`) + +type version struct { + Major int + Minor int + Patch int +} + +func parseVersion(schemaURL string) (*version, error) { + matches := schemaFilePattern.FindStringSubmatch(schemaURL) + if matches == nil { + return nil, fmt.Errorf("invalid version format in URL: %s", schemaURL) + } + + v := &version{} + for i, name := range schemaFilePattern.SubexpNames() { + if name == "" { + continue + } + value, err := strconv.Atoi(matches[i]) + if err != nil { + return nil, fmt.Errorf("failed to parse %s: %v", name, err) + } + switch name { + case "major": + v.Major = value + case "minor": + v.Minor = value + case "patch": + v.Patch = value + } + } + + return v, nil +} + +func hasSchemaSegment(schemaURL string, segment string) bool { + return strings.Contains(schemaURL, "/"+segment+"/") +} diff --git a/pkg/process/processors/version_test.go b/pkg/process/processors/version_test.go new file mode 100644 index 00000000..ac8afec9 --- /dev/null +++ b/pkg/process/processors/version_test.go @@ -0,0 +1,66 @@ +package processors + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParseVersion(t *testing.T) { + tests := []struct { + name string + schemaURL string + expected *version + wantErr bool + }{ + { + name: "valid version 1.0.0", + schemaURL: "https://example.com/vunnel/path/schema-1.0.0.json", + expected: &version{Major: 1, Minor: 0, Patch: 0}, + wantErr: false, + }, + { + name: "valid version 2.3.4", + schemaURL: "https://example.com/vunnel/path/schema-2.3.4.json", + expected: &version{Major: 2, Minor: 3, Patch: 4}, + wantErr: false, + }, + { + name: "missing patch version", + schemaURL: "https://example.com/vunnel/path/schema-1.0.json", + expected: nil, + wantErr: true, + }, + { + name: "invalid format", + schemaURL: "https://example.com/vunnel/path/schema.json", + expected: nil, + wantErr: true, + }, + { + name: "non-numeric version", + schemaURL: "https://example.com/vunnel/path/schema-1.a.0.json", + expected: nil, + wantErr: true, + }, + { + name: "valid version with extra path", + schemaURL: "https://example.com/vunnel/path/vulnerability/schema-1.2.3.json", + expected: &version{Major: 1, Minor: 2, Patch: 3}, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := parseVersion(tt.schemaURL) + if tt.wantErr { + assert.Error(t, err) + assert.Nil(t, result) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.expected, result) + } + }) + } +} diff --git a/pkg/process/v1/processors.go b/pkg/process/v1/processors.go deleted file mode 100644 index 3f05d004..00000000 --- a/pkg/process/v1/processors.go +++ /dev/null @@ -1,17 +0,0 @@ -package v1 - -import ( - "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype-db/pkg/process/processors" - "github.com/anchore/grype-db/pkg/process/v1/transformers/github" - "github.com/anchore/grype-db/pkg/process/v1/transformers/nvd" - "github.com/anchore/grype-db/pkg/process/v1/transformers/os" -) - -func Processors() []data.Processor { - return []data.Processor{ - processors.NewGitHubProcessor(github.Transform), - processors.NewNVDProcessor(nvd.Transform), - processors.NewOSProcessor(os.Transform), - } -} diff --git a/pkg/process/v1/transformers/entry.go b/pkg/process/v1/transformers/entry.go deleted file mode 100644 index 172f7aa9..00000000 --- a/pkg/process/v1/transformers/entry.go +++ /dev/null @@ -1,22 +0,0 @@ -package transformers - -import ( - "github.com/anchore/grype-db/pkg/data" - grypeDB "github.com/anchore/grype/grype/db/v1" -) - -func NewEntries(vs []grypeDB.Vulnerability, metadata grypeDB.VulnerabilityMetadata) []data.Entry { - entries := []data.Entry{ - { - DBSchemaVersion: grypeDB.SchemaVersion, - Data: metadata, - }, - } - for _, vuln := range vs { - entries = append(entries, data.Entry{ - DBSchemaVersion: grypeDB.SchemaVersion, - Data: vuln, - }) - } - return entries -} diff --git a/pkg/process/v1/transformers/github/test-fixtures/github-github-npm-0.json b/pkg/process/v1/transformers/github/test-fixtures/github-github-npm-0.json deleted file mode 100644 index b0a7d1e9..00000000 --- a/pkg/process/v1/transformers/github/test-fixtures/github-github-npm-0.json +++ /dev/null @@ -1,31 +0,0 @@ - -{ - "Advisory": { - "CVE": [ - "CVE-2020-14000" - ], - "FixedIn": [ - { - "ecosystem": "npm", - "identifier": "0.2.0-prerelease.20200714185213", - "name": "scratch-vm", - "namespace": "github:npm", - "range": "<= 0.2.0-prerelease.20200709173451" - } - ], - "Metadata": { - "CVE": [ - "CVE-2020-14000" - ] - }, - "Severity": "High", - "Summary": "Remote Code Execution in scratch-vm", - "ghsaId": "GHSA-vc9j-fhvv-8vrf", - "namespace": "github:npm", - "url": "https://github.com/advisories/GHSA-vc9j-fhvv-8vrf", - "withdrawn": null - }, - "Vulnerability": {} -} - - diff --git a/pkg/process/v1/transformers/github/test-fixtures/github-github-python-1.json b/pkg/process/v1/transformers/github/test-fixtures/github-github-python-1.json deleted file mode 100644 index bfa84922..00000000 --- a/pkg/process/v1/transformers/github/test-fixtures/github-github-python-1.json +++ /dev/null @@ -1,43 +0,0 @@ - -{ - "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", - "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/v1/transformers/github/transform.go b/pkg/process/v1/transformers/github/transform.go deleted file mode 100644 index 75fbaf22..00000000 --- a/pkg/process/v1/transformers/github/transform.go +++ /dev/null @@ -1,65 +0,0 @@ -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/v1/transformers" - "github.com/anchore/grype-db/pkg/provider/unmarshal" - grypeDB "github.com/anchore/grype/grype/db/v1" -) - -const ( - // TODO: tech debt from a previous design - feed = "github" -) - -func Transform(vulnerability unmarshal.GitHubAdvisory) ([]data.Entry, error) { - var allVulns []grypeDB.Vulnerability - - // Exclude entries marked as withdrawn - if vulnerability.Advisory.Withdrawn != nil { - return nil, nil - } - - recordSource := grypeDB.RecordSource(feed, vulnerability.Advisory.Namespace) - - // there may be multiple packages indicated within the FixedIn field, we should make - // separate vulnerability entries (one for each name|namespace combo) while merging - // constraint ranges as they are found. - for _, advisory := range vulnerability.Advisory.FixedIn { - constraint := common.EnforceSemVerConstraint(advisory.Range) - - var versionFormat string - switch vulnerability.Advisory.Namespace { - case "github:python": - versionFormat = "python" - default: - versionFormat = "unknown" - } - - // create vulnerability entry - vuln := grypeDB.Vulnerability{ - ID: vulnerability.Advisory.GhsaID, - RecordSource: recordSource, - VersionConstraint: constraint, - VersionFormat: versionFormat, // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: vulnerability.Advisory.CVE, - PackageName: advisory.Name, - Namespace: advisory.Namespace, - FixedInVersion: common.CleanFixedInVersion(advisory.Identifier), - } - - allVulns = append(allVulns, vuln) - } - - // create vulnerability metadata entry (a single entry keyed off of the vulnerability ID) - metadata := grypeDB.VulnerabilityMetadata{ - ID: vulnerability.Advisory.GhsaID, - RecordSource: recordSource, - Severity: vulnerability.Advisory.Severity, - Links: []string{vulnerability.Advisory.URL}, - Description: vulnerability.Advisory.Summary, - } - - return transformers.NewEntries(allVulns, metadata), nil -} diff --git a/pkg/process/v1/transformers/github/transform_test.go b/pkg/process/v1/transformers/github/transform_test.go deleted file mode 100644 index d0cc3f1d..00000000 --- a/pkg/process/v1/transformers/github/transform_test.go +++ /dev/null @@ -1,168 +0,0 @@ -package github - -import ( - "os" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - testUtils "github.com/anchore/grype-db/pkg/process/tests" - "github.com/anchore/grype-db/pkg/provider/unmarshal" - grypeDB "github.com/anchore/grype/grype/db/v1" -) - -func TestUnmarshalGitHubEntries(t *testing.T) { - f, err := os.Open("test-fixtures/github-github-python-0.json") - require.NoError(t, err) - defer testUtils.CloseFile(f) - - entries, err := unmarshal.GitHubAdvisoryEntries(f) - require.NoError(t, err) - - assert.Len(t, entries, 2) -} - -func TestParseGitHubEntry(t *testing.T) { - expectedVulns := []grypeDB.Vulnerability{ - { - ID: "GHSA-p5wr-vp8g-q5p4", - RecordSource: "github:python", - VersionConstraint: ">=4.0,<4.3.12", - VersionFormat: "python", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{"CVE-2017-5524"}, - PackageName: "Plone", - Namespace: "github:python", - FixedInVersion: "4.3.12", - }, - { - ID: "GHSA-p5wr-vp8g-q5p4", - RecordSource: "github:python", - VersionConstraint: ">=5.1a1,<5.1b1", - VersionFormat: "python", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{"CVE-2017-5524"}, - PackageName: "Plone", - Namespace: "github:python", - FixedInVersion: "5.1b1", - }, - { - ID: "GHSA-p5wr-vp8g-q5p4", - RecordSource: "github:python", - VersionConstraint: ">=5.0rc1,<5.0.7", - VersionFormat: "python", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{"CVE-2017-5524"}, - PackageName: "Plone", - Namespace: "github:python", - FixedInVersion: "5.0.7", - }, - } - - expectedMetadata := grypeDB.VulnerabilityMetadata{ - ID: "GHSA-p5wr-vp8g-q5p4", - RecordSource: "github:python", - Severity: "Medium", - Links: []string{"https://github.com/advisories/GHSA-p5wr-vp8g-q5p4"}, - Description: "Moderate severity vulnerability that affects Plone", - } - - f, err := os.Open("test-fixtures/github-github-python-1.json") - require.NoError(t, err) - defer testUtils.CloseFile(f) - - entries, err := unmarshal.GitHubAdvisoryEntries(f) - assert.NoError(t, err) - assert.Len(t, entries, 1) - - entry := entries[0] - - dataEntries, err := Transform(entry) - assert.NoError(t, err) - - var vulns []grypeDB.Vulnerability - for _, entry := range dataEntries { - switch vuln := entry.Data.(type) { - case grypeDB.Vulnerability: - vulns = append(vulns, vuln) - case grypeDB.VulnerabilityMetadata: - assert.Equal(t, expectedMetadata, vuln) - default: - t.Fatalf("unexpected condition: data entry does not have a vulnerability or a metadata") - } - } - - // check vulnerability - assert.Len(t, vulns, len(expectedVulns)) - - assert.ElementsMatch(t, expectedVulns, vulns) - -} - -func TestDefaultVersionFormatNpmGitHubEntry(t *testing.T) { - expectedVulns := []grypeDB.Vulnerability{ - { - ID: "GHSA-vc9j-fhvv-8vrf", - RecordSource: "github:npm", - VersionConstraint: "<=0.2.0-prerelease.20200709173451", - VersionFormat: "unknown", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{"CVE-2020-14000"}, - PackageName: "scratch-vm", - Namespace: "github:npm", - FixedInVersion: "0.2.0-prerelease.20200714185213", - }, - } - - expectedMetadata := grypeDB.VulnerabilityMetadata{ - ID: "GHSA-vc9j-fhvv-8vrf", - RecordSource: "github:npm", - Severity: "High", - Links: []string{"https://github.com/advisories/GHSA-vc9j-fhvv-8vrf"}, - Description: "Remote Code Execution in scratch-vm", - } - - f, err := os.Open("test-fixtures/github-github-npm-0.json") - require.NoError(t, err) - defer testUtils.CloseFile(f) - - entries, err := unmarshal.GitHubAdvisoryEntries(f) - assert.NoError(t, err) - assert.Len(t, entries, 1) - - entry := entries[0] - - dataEntries, err := Transform(entry) - assert.NoError(t, err) - - var vulns []grypeDB.Vulnerability - for _, entry := range dataEntries { - switch vuln := entry.Data.(type) { - case grypeDB.Vulnerability: - vulns = append(vulns, vuln) - case grypeDB.VulnerabilityMetadata: - assert.Equal(t, expectedMetadata, vuln) - default: - t.Fatalf("unexpected condition: data entry does not have a vulnerability or a metadata") - } - } - - // check vulnerability - assert.Len(t, vulns, len(expectedVulns)) - - assert.ElementsMatch(t, expectedVulns, vulns) -} - -func TestFilterWithdrawnEntries(t *testing.T) { - f, err := os.Open("test-fixtures/github-withdrawn.json") - require.NoError(t, err) - defer testUtils.CloseFile(f) - - entries, err := unmarshal.GitHubAdvisoryEntries(f) - require.NoError(t, err) - - require.Len(t, entries, 1) - - entry := entries[0] - - dataEntries, err := Transform(entry) - assert.NoError(t, err) - assert.Nil(t, dataEntries) -} diff --git a/pkg/process/v1/transformers/nvd/test-fixtures/unmarshal-test.json b/pkg/process/v1/transformers/nvd/test-fixtures/unmarshal-test.json deleted file mode 100644 index 2dc698fa..00000000 --- a/pkg/process/v1/transformers/nvd/test-fixtures/unmarshal-test.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "cve": { - "id": "CVE-2003-0349", - "sourceIdentifier": "cve@mitre.org", - "published": "2003-07-24T04:00:00.000", - "lastModified": "2018-10-12T21:32:41.083", - "vulnStatus": "Modified", - "descriptions": [ - { - "lang": "en", - "value": "Buffer overflow in the streaming media component for logging multicast requests in the ISAPI for the logging capability of Microsoft Windows Media Services (nsiislog.dll), as installed in IIS 5.0, allows remote attackers to execute arbitrary code via a large POST request to nsiislog.dll." - }, - { - "lang": "es", - "value": "Desbordamiento de búfer en el componente de secuenciamiento (streaming) de medios para registrar peticiones de multidifusión en la librería ISAPI de la capacidad de registro (logging) de Microsoft Windows Media Services (nsiislog.dll), como el instalado en IIS 5.9, permite a atacantes remotos ejecutar código arbitrario mediante una petición POST larga a nsiislog.dll." - } - ], - "metrics": { - "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": false, - "obtainAllPrivilege": false, - "obtainUserPrivilege": true, - "obtainOtherPrivilege": false, - "userInteractionRequired": false - } - ] - }, - "weaknesses": [ - { - "source": "nvd@nist.gov", - "type": "Primary", - "description": [ - { - "lang": "en", - "value": "NVD-CWE-Other" - } - ] - } - ], - "configurations": [ - { - "nodes": [ - { - "operator": "OR", - "negate": false, - "cpeMatch": [ - { - "vulnerable": true, - "criteria": "cpe:2.3:o:microsoft:windows_2000:*:*:*:*:*:*:*:*", - "matchCriteriaId": "4E545C63-FE9C-4CA1-AF0F-D999D84D2AFD" - } - ] - } - ] - } - ], - "references": [ - { - "url": "http://marc.info/?l=bugtraq&m=105665030925504&w=2", - "source": "cve@mitre.org" - }, - { - "url": "http://securitytracker.com/id?1007059", - "source": "cve@mitre.org" - }, - { - "url": "http://www.kb.cert.org/vuls/id/113716", - "source": "cve@mitre.org", - "tags": [ - "US Government Resource" - ] - }, - { - "url": "http://www.ntbugtraq.com/default.asp?pid=36&sid=1&A2=ind0306&L=NTBUGTRAQ&P=R4563", - "source": "cve@mitre.org", - "tags": [ - "Exploit", - "Patch", - "Vendor Advisory" - ] - }, - { - "url": "https://docs.microsoft.com/en-us/security-updates/securitybulletins/2003/ms03-022", - "source": "cve@mitre.org" - }, - { - "url": "https://oval.cisecurity.org/repository/search/definition/oval%3Aorg.mitre.oval%3Adef%3A938", - "source": "cve@mitre.org" - } - ] - } -} diff --git a/pkg/process/v1/transformers/nvd/test-fixtures/version-range.json b/pkg/process/v1/transformers/nvd/test-fixtures/version-range.json deleted file mode 100644 index 3df5b86d..00000000 --- a/pkg/process/v1/transformers/nvd/test-fixtures/version-range.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "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/v1/transformers/nvd/transform.go b/pkg/process/v1/transformers/nvd/transform.go deleted file mode 100644 index f5b241b4..00000000 --- a/pkg/process/v1/transformers/nvd/transform.go +++ /dev/null @@ -1,110 +0,0 @@ -package nvd - -import ( - "strings" - - "github.com/anchore/grype-db/internal" - "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype-db/pkg/process/v1/transformers" - "github.com/anchore/grype-db/pkg/provider/unmarshal" - "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd" - grypeDB "github.com/anchore/grype/grype/db/v1" -) - -const ( - // TODO: tech debt from a previous design - feed = "nvdv2" - group = "nvdv2:cves" -) - -func Transform(vulnerability unmarshal.NVDVulnerability) ([]data.Entry, error) { - var allVulns []grypeDB.Vulnerability - - recordSource := grypeDB.RecordSource(feed, group) - - uniquePkgs := findUniquePkgs(vulnerability.Configurations...) - - // extract all links - var links []string - for _, externalRefs := range vulnerability.References { - // TODO: should we capture other information here? - if externalRefs.URL != "" { - links = append(links, externalRefs.URL) - } - } - // duplicate the vulnerabilities based on the set of unique packages the vulnerability is for - for _, p := range uniquePkgs.All() { - matches := uniquePkgs.Matches(p) - cpes := internal.NewStringSet() - for _, m := range matches { - cpes.Add(m.Criteria) - } - - // create vulnerability entry - vuln := grypeDB.Vulnerability{ - ID: vulnerability.ID, - RecordSource: recordSource, - VersionConstraint: buildConstraints(uniquePkgs.Matches(p)), - VersionFormat: "unknown", // TODO: derive this from the target software - PackageName: p.Product, - Namespace: "nvd", // should the vendor be here? or in other metadata? - ProxyVulnerabilities: []string{}, - CPEs: cpes.ToSlice(), - } - - allVulns = append(allVulns, vuln) - } - - // If all the CPEs are invalid and no vulnerabilities were generated then there is no point - // in creating metadata, so just return - if len(allVulns) == 0 { - return nil, nil - } - - // create vulnerability metadata entry (a single entry keyed off of the vulnerability ID) - allCVSS := vulnerability.CVSS() - - metadata := grypeDB.VulnerabilityMetadata{ - ID: vulnerability.ID, - RecordSource: recordSource, - Severity: nvd.CvssSummaries(allCVSS).Sorted().Severity(), - Links: links, - Description: vulnerability.Description(), - } - - for _, c := range allCVSS { - if strings.HasPrefix(c.Version, "2.") { - newCvss := &grypeDB.Cvss{ - BaseScore: c.BaseScore, - Vector: c.Vector, - } - if c.ExploitabilityScore != nil { - newCvss.ExploitabilityScore = *c.ExploitabilityScore - } - if c.ImpactScore != nil { - newCvss.ImpactScore = *c.ImpactScore - } - metadata.CvssV2 = newCvss - break - } - } - - for _, c := range allCVSS { - if strings.HasPrefix(c.Version, "3.") { - newCvss := &grypeDB.Cvss{ - BaseScore: c.BaseScore, - Vector: c.Vector, - } - if c.ExploitabilityScore != nil { - newCvss.ExploitabilityScore = *c.ExploitabilityScore - } - if c.ImpactScore != nil { - newCvss.ImpactScore = *c.ImpactScore - } - metadata.CvssV3 = newCvss - break - } - } - - return transformers.NewEntries(allVulns, metadata), nil -} diff --git a/pkg/process/v1/transformers/nvd/transform_test.go b/pkg/process/v1/transformers/nvd/transform_test.go deleted file mode 100644 index 57e140b2..00000000 --- a/pkg/process/v1/transformers/nvd/transform_test.go +++ /dev/null @@ -1,192 +0,0 @@ -package nvd - -import ( - "os" - "testing" - - "github.com/go-test/deep" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - testUtils "github.com/anchore/grype-db/pkg/process/tests" - "github.com/anchore/grype-db/pkg/provider/unmarshal" - grypeDB "github.com/anchore/grype/grype/db/v1" -) - -const recordSource = "nvdv2:cves" - -func TestUnmarshalVulnerabilitiesEntries(t *testing.T) { - f, err := os.Open("test-fixtures/unmarshal-test.json") - require.NoError(t, err) - defer testUtils.CloseFile(f) - - entries, err := unmarshal.NvdVulnerabilityEntries(f) - require.NoError(t, err) - - assert.Len(t, entries, 1) -} - -func TestParseVulnerabilitiesAllEntries(t *testing.T) { - tests := []struct { - name string - numEntries int - fixture string - vulns []grypeDB.Vulnerability - metadata grypeDB.VulnerabilityMetadata - }{ - { - name: "AppVersionRange", - numEntries: 1, - fixture: "test-fixtures/version-range.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2018-5487", - RecordSource: recordSource, - PackageName: "oncommand_unified_manager", - VersionConstraint: ">= 7.2, <= 7.3", - VersionFormat: "unknown", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{}, - Namespace: "nvd", - CPEs: []string{"cpe:2.3:a:netapp:oncommand_unified_manager:*:*:*:*:*:*:*:*"}, - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2018-5487", - RecordSource: recordSource, - Severity: "Critical", - Links: []string{"https://security.netapp.com/advisory/ntap-20180523-0001/"}, - 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.", - CvssV2: &grypeDB.Cvss{ - BaseScore: 7.5, - ExploitabilityScore: 10, - ImpactScore: 6.4, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P", - }, - CvssV3: &grypeDB.Cvss{ - BaseScore: 9.8, - ExploitabilityScore: 3.9, - ImpactScore: 5.9, - Vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", - }, - }, - }, - { - name: "App+OS", - numEntries: 1, - fixture: "test-fixtures/single-package-multi-distro.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2018-1000222", - RecordSource: recordSource, - PackageName: "libgd", - VersionConstraint: "= 2.2.5", - VersionFormat: "unknown", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{}, - Namespace: "nvd", - CPEs: []string{"cpe:2.3:a:libgd:libgd:2.2.5:*:*:*:*:*:*:*"}, - }, - // TODO: Question: should this match also the OS's? (as in the vulnerable_cpes list)... this seems wrong! - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2018-1000222", - RecordSource: recordSource, - Severity: "High", - Links: []string{"https://github.com/libgd/libgd/issues/447", "https://lists.debian.org/debian-lts-announce/2019/01/msg00028.html", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/3CZ2QADQTKRHTGB2AHD7J4QQNDLBEMM6/", "https://security.gentoo.org/glsa/201903-18", "https://usn.ubuntu.com/3755-1/"}, - 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.", - CvssV2: &grypeDB.Cvss{ - BaseScore: 6.8, - ExploitabilityScore: 8.6, - ImpactScore: 6.4, - Vector: "AV:N/AC:M/Au:N/C:P/I:P/A:P", - }, - CvssV3: &grypeDB.Cvss{ - BaseScore: 8.8, - ExploitabilityScore: 2.8, - ImpactScore: 5.9, - Vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", - }, - }, - }, - { - name: "AppCompoundVersionRange", - numEntries: 1, - fixture: "test-fixtures/compound-pkg.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2018-10189", - RecordSource: recordSource, - PackageName: "mautic", - VersionConstraint: ">= 1.0.0, <= 1.4.1 || >= 2.0.0, < 2.13.0", - VersionFormat: "unknown", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{}, - Namespace: "nvd", - CPEs: []string{"cpe:2.3:a:mautic:mautic:*:*:*:*:*:*:*:*"}, // note: entry was dedupicated - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2018-10189", - RecordSource: recordSource, - Severity: "High", - Links: []string{"https://github.com/mautic/mautic/releases/tag/2.13.0"}, - 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.", - CvssV2: &grypeDB.Cvss{ - BaseScore: 5, - ExploitabilityScore: 10, - ImpactScore: 2.9, - Vector: "AV:N/AC:L/Au:N/C:P/I:N/A:N", - }, - CvssV3: &grypeDB.Cvss{ - BaseScore: 7.5, - ExploitabilityScore: 3.9, - ImpactScore: 3.6, - Vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", - }, - }, - }, - { - name: "InvalidCPE", - numEntries: 1, - fixture: "test-fixtures/invalid_cpe.json", - vulns: []grypeDB.Vulnerability{}, - metadata: grypeDB.VulnerabilityMetadata{}, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - f, err := os.Open(test.fixture) - require.NoError(t, err) - t.Cleanup(func() { - assert.NoError(t, f.Close()) - }) - - entries, err := unmarshal.NvdVulnerabilityEntries(f) - assert.NoError(t, err) - - var vulns []grypeDB.Vulnerability - for _, entry := range entries { - dataEntries, err := Transform(entry.Cve) - assert.NoError(t, err) - - for _, entry := range dataEntries { - switch vuln := entry.Data.(type) { - case grypeDB.Vulnerability: - vulns = append(vulns, vuln) - case grypeDB.VulnerabilityMetadata: - // check metadata - if diff := deep.Equal(test.metadata, vuln); diff != nil { - for _, d := range diff { - t.Errorf("metadata diff: %+v", d) - } - } - default: - t.Fatalf("unexpected condition: data entry does not have a vulnerability or a metadata") - } - } - - } - - assert.ElementsMatch(t, test.vulns, vulns) - }) - } -} diff --git a/pkg/process/v1/transformers/nvd/unique_pkg.go b/pkg/process/v1/transformers/nvd/unique_pkg.go deleted file mode 100644 index 20a62777..00000000 --- a/pkg/process/v1/transformers/nvd/unique_pkg.go +++ /dev/null @@ -1,115 +0,0 @@ -package nvd - -import ( - "fmt" - "strings" - - "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/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(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 only interested in applications, not hardware or operating systems - if c.Part() != cpe.Application { - return nil, nil - } - - return &pkgCandidate{ - Product: c.Product().String(), - Vendor: c.Vendor().String(), - TargetSoftware: c.TargetSw().String(), - }, nil -} - -func findUniquePkgs(cfgs ...nvd.Configuration) uniquePkgTracker { - set := newUniquePkgTracker() - for _, c := range cfgs { - _findUniquePkgs(set, c.Nodes...) - } - return set -} - -func _findUniquePkgs(set uniquePkgTracker, ns ...nvd.Node) { - if len(ns) == 0 { - return - } - for _, node := range ns { - for _, match := range node.CpeMatch { - candidate, err := newPkgCandidate(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.Add(*candidate, match) - } - } - } -} - -func buildConstraints(matches []nvd.CpeMatch) string { - constraints := make([]string, 0) - for _, match := range matches { - constraints = append(constraints, buildConstraint(match)) - } - return common.OrConstraints(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() - if version != ANY && version != NA { - constraints = append(constraints, fmt.Sprintf("= %s", version)) - } - } - - return strings.Join(constraints, ", ") -} diff --git a/pkg/process/v1/transformers/nvd/unique_pkg_test.go b/pkg/process/v1/transformers/nvd/unique_pkg_test.go deleted file mode 100644 index beb050fc..00000000 --- a/pkg/process/v1/transformers/nvd/unique_pkg_test.go +++ /dev/null @@ -1,353 +0,0 @@ -package nvd - -import ( - "testing" - - "github.com/sergi/go-diff/diffmatchpatch" - - "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd" -) - -func newUniquePkgTrackerFromSlice(candidates []pkgCandidate) uniquePkgTracker { - set := newUniquePkgTracker() - for _, c := range candidates { - set[c] = nil - } - return set -} - -func TestFindUniquePkgs(t *testing.T) { - tests := []struct { - name string - nodes []nvd.Node - 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", - }, - }), - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - actual := findUniquePkgs(nvd.Configuration{Nodes: test.nodes}) - 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)) - } - }) - } - -} diff --git a/pkg/process/v1/transformers/nvd/unique_pkg_tracker.go b/pkg/process/v1/transformers/nvd/unique_pkg_tracker.go deleted file mode 100644 index 2b7e405d..00000000 --- a/pkg/process/v1/transformers/nvd/unique_pkg_tracker.go +++ /dev/null @@ -1,64 +0,0 @@ -package nvd - -import ( - "sort" - - "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd" -) - -type uniquePkgTracker map[pkgCandidate][]nvd.CpeMatch - -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 (s uniquePkgTracker) Matches(i pkgCandidate) []nvd.CpeMatch { - return s[i] -} - -func (s uniquePkgTracker) Add(i pkgCandidate, match nvd.CpeMatch) { - if _, ok := s[i]; !ok { - s[i] = make([]nvd.CpeMatch, 0) - } - s[i] = append(s[i], match) -} - -func (s uniquePkgTracker) Remove(i pkgCandidate) { - delete(s, i) -} - -func (s uniquePkgTracker) Contains(i pkgCandidate) bool { - _, ok := s[i] - return ok -} - -func (s uniquePkgTracker) All() []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/v1/transformers/os/test-fixtures/amzn.json b/pkg/process/v1/transformers/os/test-fixtures/amzn.json deleted file mode 100644 index a862c32e..00000000 --- a/pkg/process/v1/transformers/os/test-fixtures/amzn.json +++ /dev/null @@ -1,49 +0,0 @@ -[ - { - "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/v1/transformers/os/test-fixtures/unmarshal-test.json b/pkg/process/v1/transformers/os/test-fixtures/unmarshal-test.json deleted file mode 100644 index edc6d25b..00000000 --- a/pkg/process/v1/transformers/os/test-fixtures/unmarshal-test.json +++ /dev/null @@ -1,104 +0,0 @@ -[ - { - "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" - } - }, - { - "Vulnerability": { - "Description": "", - "FixedIn": [ - { - "Name": "kernel-livepatch-4.14.173-137.228", - "NamespaceName": "amzn:2", - "Version": "1.0-3.amzn2", - "VersionFormat": "rpm" - }, - { - "Name": "kernel-livepatch-4.14.173-137.228-debuginfo", - "NamespaceName": "amzn:2", - "Version": "1.0-3.amzn2", - "VersionFormat": "rpm" - } - ], - "Link": "https://alas.aws.amazon.com/AL2/ALASLIVEPATCH-2020-012.html", - "Metadata": { - "CVE": [ - {"Name": "CVE-2020-12657"} - ] - }, - "Name": "ALASLIVEPATCH-2020-012", - "NamespaceName": "amzn:2", - "Severity": "High" - } - }, - { - "Vulnerability": { - "Description": "", - "FixedIn": [ - { - "Name": "kernel-livepatch-4.14.171-136.231", - "NamespaceName": "amzn:2", - "Version": "1.0-5.amzn2", - "VersionFormat": "rpm" - }, - { - "Name": "kernel-livepatch-4.14.171-136.231-debuginfo", - "NamespaceName": "amzn:2", - "Version": "1.0-5.amzn2", - "VersionFormat": "rpm" - } - ], - "Link": "https://alas.aws.amazon.com/AL2/ALASLIVEPATCH-2020-011.html", - "Metadata": { - "CVE": [ - {"Name": "CVE-2020-12657"} - ] - }, - "Name": "ALASLIVEPATCH-2020-011", - "NamespaceName": "amzn:2", - "Severity": "High" - } - } -] \ No newline at end of file diff --git a/pkg/process/v1/transformers/os/transform.go b/pkg/process/v1/transformers/os/transform.go deleted file mode 100644 index dcb270e1..00000000 --- a/pkg/process/v1/transformers/os/transform.go +++ /dev/null @@ -1,118 +0,0 @@ -package os - -import ( - "fmt" - "strings" - - "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype-db/pkg/process/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" -) - -const ( - // TODO: tech debt from a previous design - feed = "vulnerabilities" -) - -func Transform(vulnerability unmarshal.OSVulnerability) ([]data.Entry, error) { - group := vulnerability.Vulnerability.NamespaceName - - var allVulns []grypeDB.Vulnerability - - recordSource := grypeDB.RecordSource(feed, group) - vulnerability.Vulnerability.FixedIn = vulnerability.Vulnerability.FixedIn.FilterToHighestModularity() - - // there may be multiple packages indicated within the FixedIn field, we should make - // separate vulnerability entries (one for each name|namespace combo) while merging - // constraint ranges as they are found. - for _, advisory := range vulnerability.Vulnerability.FixedIn { - // create vulnerability entry - vuln := grypeDB.Vulnerability{ - ID: vulnerability.Vulnerability.Name, - RecordSource: recordSource, - VersionConstraint: enforceConstraint(advisory.Version, advisory.VersionFormat, vulnerability.Vulnerability.Name), - VersionFormat: advisory.VersionFormat, - PackageName: advisory.Name, - Namespace: advisory.NamespaceName, - ProxyVulnerabilities: []string{}, - FixedInVersion: common.CleanFixedInVersion(advisory.Version), - } - - // associate related vulnerabilities - // note: an example of multiple CVEs for a record is centos:5 RHSA-2007:0055 which maps to CVE-2007-0002 and CVE-2007-1466 - for _, ref := range vulnerability.Vulnerability.Metadata.CVE { - vuln.ProxyVulnerabilities = append(vuln.ProxyVulnerabilities, ref.Name) - } - - allVulns = append(allVulns, vuln) - } - - var cvssV2 *grypeDB.Cvss - if vulnerability.Vulnerability.Metadata.NVD.CVSSv2.Vectors != "" { - cvssV2 = &grypeDB.Cvss{ - BaseScore: vulnerability.Vulnerability.Metadata.NVD.CVSSv2.Score, - ExploitabilityScore: 0, - ImpactScore: 0, - Vector: vulnerability.Vulnerability.Metadata.NVD.CVSSv2.Vectors, - } - } - - // find all URLs related to the vulnerability - links := []string{vulnerability.Vulnerability.Link} - if vulnerability.Vulnerability.Metadata.CVE != nil { - for _, cve := range vulnerability.Vulnerability.Metadata.CVE { - if cve.Link != "" { - links = append(links, cve.Link) - } - } - } - - // create vulnerability metadata entry (a single entry keyed off of the vulnerability ID) - metadata := grypeDB.VulnerabilityMetadata{ - ID: vulnerability.Vulnerability.Name, - RecordSource: recordSource, - Severity: vulnerability.Vulnerability.Severity, - Links: links, - Description: vulnerability.Vulnerability.Description, - CvssV2: cvssV2, - } - - return transformers.NewEntries(allVulns, metadata), nil -} - -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 -} - -func enforceConstraint(constraint, format, vulnerabilityID string) string { - constraint = common.CleanConstraint(constraint) - if len(constraint) == 0 { - return "" - } - switch strings.ToLower(format) { - case "semver": - return common.EnforceSemVerConstraint(constraint) - default: - // the passed constraint is a fixed version - return deriveConstraintFromFix(constraint, vulnerabilityID) - } -} diff --git a/pkg/process/v1/transformers/os/transform_test.go b/pkg/process/v1/transformers/os/transform_test.go deleted file mode 100644 index bd0334df..00000000 --- a/pkg/process/v1/transformers/os/transform_test.go +++ /dev/null @@ -1,489 +0,0 @@ -package os - -import ( - "os" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - testUtils "github.com/anchore/grype-db/pkg/process/tests" - "github.com/anchore/grype-db/pkg/provider/unmarshal" - grypeDB "github.com/anchore/grype/grype/db/v1" -) - -func TestUnmarshalVulnerabilitiesEntries(t *testing.T) { - f, err := os.Open("test-fixtures/unmarshal-test.json") - require.NoError(t, err) - defer testUtils.CloseFile(f) - - entries, err := unmarshal.OSVulnerabilityEntries(f) - require.NoError(t, err) - - require.Len(t, entries, 3) -} - -func TestParseVulnerabilitiesEntry(t *testing.T) { - tests := []struct { - name string - numEntries int - fixture string - vulns []grypeDB.Vulnerability - metadata grypeDB.VulnerabilityMetadata - }{ - { - name: "Amazon", - numEntries: 1, - fixture: "test-fixtures/amzn.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "ALAS-2018-1106", - RecordSource: "vulnerabilities:amzn:2", - VersionConstraint: "< 1.3.8.4-15.amzn2.0.1", - VersionFormat: "rpm", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{"CVE-2018-14648"}, - PackageName: "389-ds-base", - Namespace: "amzn:2", - FixedInVersion: "1.3.8.4-15.amzn2.0.1", - }, - { - ID: "ALAS-2018-1106", - RecordSource: "vulnerabilities:amzn:2", - VersionConstraint: "< 1.3.8.4-15.amzn2.0.1", - VersionFormat: "rpm", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{"CVE-2018-14648"}, - PackageName: "389-ds-base-debuginfo", - Namespace: "amzn:2", - FixedInVersion: "1.3.8.4-15.amzn2.0.1", - }, - { - ID: "ALAS-2018-1106", - RecordSource: "vulnerabilities:amzn:2", - VersionConstraint: "< 1.3.8.4-15.amzn2.0.1", - VersionFormat: "rpm", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{"CVE-2018-14648"}, - PackageName: "389-ds-base-devel", - Namespace: "amzn:2", - FixedInVersion: "1.3.8.4-15.amzn2.0.1", - }, - { - ID: "ALAS-2018-1106", - RecordSource: "vulnerabilities:amzn:2", - VersionConstraint: "< 1.3.8.4-15.amzn2.0.1", - VersionFormat: "rpm", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{"CVE-2018-14648"}, - PackageName: "389-ds-base-libs", - Namespace: "amzn:2", - FixedInVersion: "1.3.8.4-15.amzn2.0.1", - }, - { - ID: "ALAS-2018-1106", - RecordSource: "vulnerabilities:amzn:2", - VersionConstraint: "< 1.3.8.4-15.amzn2.0.1", - VersionFormat: "rpm", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{"CVE-2018-14648"}, - PackageName: "389-ds-base-snmp", - Namespace: "amzn:2", - FixedInVersion: "1.3.8.4-15.amzn2.0.1", - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "ALAS-2018-1106", - RecordSource: "vulnerabilities:amzn:2", - Severity: "Medium", - Links: []string{"https://alas.aws.amazon.com/AL2/ALAS-2018-1106.html"}, - }, - }, - { - name: "Debian", - numEntries: 1, - fixture: "test-fixtures/debian-8.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2008-7220", - RecordSource: "vulnerabilities:debian:8", - PackageName: "asterisk", - VersionConstraint: "< 1:1.6.2.0~rc3-1", - VersionFormat: "dpkg", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{}, - Namespace: "debian:8", - FixedInVersion: "1:1.6.2.0~rc3-1", - }, - { - ID: "CVE-2008-7220", - RecordSource: "vulnerabilities:debian:8", - PackageName: "auth2db", - VersionConstraint: "< 0.2.5-2+dfsg-1", - VersionFormat: "dpkg", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{}, - Namespace: "debian:8", - FixedInVersion: "0.2.5-2+dfsg-1", - }, - { - ID: "CVE-2008-7220", - RecordSource: "vulnerabilities:debian:8", - PackageName: "exaile", - VersionConstraint: "< 0.2.14+debian-2.2", - VersionFormat: "dpkg", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{}, - Namespace: "debian:8", - FixedInVersion: "0.2.14+debian-2.2", - }, - { - ID: "CVE-2008-7220", - RecordSource: "vulnerabilities:debian:8", - PackageName: "wordpress", - VersionConstraint: "", - VersionFormat: "dpkg", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{}, - Namespace: "debian:8", - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2008-7220", - RecordSource: "vulnerabilities:debian:8", - Severity: "High", - Links: []string{"https://security-tracker.debian.org/tracker/CVE-2008-7220"}, - Description: "", - CvssV2: &grypeDB.Cvss{ - BaseScore: 7.5, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P", - }, - }, - }, - { - name: "RHEL", - numEntries: 1, - fixture: "test-fixtures/rhel-8.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2020-6819", - RecordSource: "vulnerabilities:rhel:8", - PackageName: "firefox", - VersionConstraint: "< 0:68.6.1-1.el8_1", - VersionFormat: "rpm", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{}, - Namespace: "rhel:8", - FixedInVersion: "0:68.6.1-1.el8_1", - }, - { - ID: "CVE-2020-6819", - RecordSource: "vulnerabilities:rhel:8", - PackageName: "thunderbird", - VersionConstraint: "< 0:68.7.0-1.el8_1", - VersionFormat: "rpm", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{}, - Namespace: "rhel:8", - FixedInVersion: "0:68.7.0-1.el8_1", - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2020-6819", - RecordSource: "vulnerabilities:rhel:8", - Severity: "Critical", - Links: []string{"https://access.redhat.com/security/cve/CVE-2020-6819"}, - 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.", - }, - }, - { - name: "RHEL with modularity", - numEntries: 1, - fixture: "test-fixtures/rhel-8-modules.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2020-14350", - RecordSource: "vulnerabilities:rhel:8", - PackageName: "postgresql", - VersionConstraint: "< 0:12.5-1.module+el8.3.0+9042+664538f4", - VersionFormat: "rpm", - ProxyVulnerabilities: []string{}, - Namespace: "rhel:8", - FixedInVersion: "0:12.5-1.module+el8.3.0+9042+664538f4", - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2020-14350", - RecordSource: "vulnerabilities:rhel:8", - Severity: "Medium", - Links: []string{"https://access.redhat.com/security/cve/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.", - }, - }, - { - name: "Alpine", - numEntries: 1, - fixture: "test-fixtures/alpine-3.9.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2018-19967", - RecordSource: "vulnerabilities:alpine:3.9", - PackageName: "xen", - VersionConstraint: "< 4.11.1-r0", - VersionFormat: "apk", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{}, - Namespace: "alpine:3.9", - FixedInVersion: "4.11.1-r0", - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2018-19967", - RecordSource: "vulnerabilities:alpine:3.9", - Severity: "Medium", - Links: []string{"http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-19967"}, - Description: "", - CvssV2: &grypeDB.Cvss{ - BaseScore: 4.9, - ExploitabilityScore: 0, - ImpactScore: 0, - Vector: "AV:L/AC:L/Au:N/C:N/I:N/A:C", - }, - }, - }, - { - name: "Oracle", - numEntries: 1, - fixture: "test-fixtures/ol-8.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "ELSA-2020-2550", - RecordSource: "vulnerabilities:ol:8", - PackageName: "libexif", - VersionConstraint: "< 0:0.6.21-17.el8_2", - VersionFormat: "rpm", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{"CVE-2020-13112"}, - Namespace: "ol:8", - FixedInVersion: "0:0.6.21-17.el8_2", - }, - { - ID: "ELSA-2020-2550", - RecordSource: "vulnerabilities:ol:8", - PackageName: "libexif-devel", - VersionConstraint: "< 0:0.6.21-17.el8_2", - VersionFormat: "rpm", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{"CVE-2020-13112"}, - Namespace: "ol:8", - FixedInVersion: "0:0.6.21-17.el8_2", - }, - { - ID: "ELSA-2020-2550", - RecordSource: "vulnerabilities:ol:8", - PackageName: "libexif-dummy", - VersionConstraint: "", - VersionFormat: "rpm", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{"CVE-2020-13112"}, - Namespace: "ol:8", - FixedInVersion: "", - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "ELSA-2020-2550", - RecordSource: "vulnerabilities:ol:8", - Severity: "Medium", - Links: []string{"http://linux.oracle.com/errata/ELSA-2020-2550.html", "http://linux.oracle.com/cve/CVE-2020-13112.html"}, - }, - }, - { - name: "Oracle Linux 8 with modularity", - numEntries: 1, - fixture: "test-fixtures/ol-8-modules.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2020-14350", - RecordSource: "vulnerabilities:ol:8", - PackageName: "postgresql", - VersionConstraint: "< 0:12.5-1.module+el8.3.0+9042+664538f4", - VersionFormat: "rpm", - ProxyVulnerabilities: []string{}, - Namespace: "ol:8", - FixedInVersion: "0:12.5-1.module+el8.3.0+9042+664538f4", - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2020-14350", - RecordSource: "vulnerabilities:ol:8", - Severity: "Medium", - Links: []string{"https://access.redhat.com/security/cve/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.", - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - f, err := os.Open(test.fixture) - require.NoError(t, err) - t.Cleanup(func() { - assert.NoError(t, f.Close()) - }) - - entries, err := unmarshal.OSVulnerabilityEntries(f) - assert.NoError(t, err) - assert.Len(t, entries, 1) - - entry := entries[0] - - dataEntries, err := Transform(entry) - assert.NoError(t, err) - - var vulns []grypeDB.Vulnerability - for _, entry := range dataEntries { - switch vuln := entry.Data.(type) { - case grypeDB.Vulnerability: - vulns = append(vulns, vuln) - case grypeDB.VulnerabilityMetadata: - assert.Equal(t, test.metadata, vuln) - default: - t.Fatalf("unexpected condition: data entry does not have a vulnerability or a metadata") - } - } - - if diff := cmp.Diff(test.vulns, vulns); diff != "" { - t.Errorf("vulnerabilities do not match (-want +got):\n%s", diff) - } - - }) - } - -} - -func TestParseVulnerabilitiesAllEntries(t *testing.T) { - - tests := []struct { - name string - numEntries int - fixture string - vulns []grypeDB.Vulnerability - }{ - { - name: "Debian", - numEntries: 2, - fixture: "test-fixtures/debian-8-multiple-entries-for-same-package.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2011-4623", - RecordSource: "vulnerabilities:debian:8", - PackageName: "rsyslog", - VersionConstraint: "< 5.7.4-1", - VersionFormat: "dpkg", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{}, - Namespace: "debian:8", - FixedInVersion: "5.7.4-1", - }, - { - ID: "CVE-2008-5618", - RecordSource: "vulnerabilities:debian:8", - PackageName: "rsyslog", - VersionConstraint: "< 3.18.6-1", - VersionFormat: "dpkg", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{}, - Namespace: "debian:8", - FixedInVersion: "3.18.6-1", - }, - }, - }, - { - name: "Amazon", - numEntries: 3, - fixture: "test-fixtures/amazon-multiple-kernel-advisories.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "ALAS-2021-1704", - RecordSource: "vulnerabilities:amzn:2", - PackageName: "kernel-headers", - VersionConstraint: "< 4.14.246-187.474.amzn2", - VersionFormat: "rpm", - Namespace: "amzn:2", - ProxyVulnerabilities: []string{"CVE-2021-3653", "CVE-2021-3656", "CVE-2021-3732"}, - FixedInVersion: "4.14.246-187.474.amzn2", - }, - { - ID: "ALAS-2021-1704", - RecordSource: "vulnerabilities:amzn:2", - PackageName: "kernel", - VersionConstraint: "< 4.14.246-187.474.amzn2", - VersionFormat: "rpm", - Namespace: "amzn:2", - ProxyVulnerabilities: []string{"CVE-2021-3653", "CVE-2021-3656", "CVE-2021-3732"}, - FixedInVersion: "4.14.246-187.474.amzn2", - }, - { - ID: "ALASKERNEL-5.4-2022-007", - RecordSource: "vulnerabilities:amzn:2", - PackageName: "kernel-headers", - VersionConstraint: ">= 5.4, < 5.4.144-69.257.amzn2", - VersionFormat: "rpm", - Namespace: "amzn:2", - ProxyVulnerabilities: []string{"CVE-2021-3753", "CVE-2021-40490"}, - FixedInVersion: "5.4.144-69.257.amzn2", - }, - { - ID: "ALASKERNEL-5.4-2022-007", - RecordSource: "vulnerabilities:amzn:2", - PackageName: "kernel", - VersionConstraint: ">= 5.4, < 5.4.144-69.257.amzn2", - VersionFormat: "rpm", - Namespace: "amzn:2", - ProxyVulnerabilities: []string{"CVE-2021-3753", "CVE-2021-40490"}, - FixedInVersion: "5.4.144-69.257.amzn2", - }, - { - ID: "ALASKERNEL-5.10-2022-005", - RecordSource: "vulnerabilities:amzn:2", - PackageName: "kernel-headers", - VersionConstraint: ">= 5.10, < 5.10.62-55.141.amzn2", - VersionFormat: "rpm", - Namespace: "amzn:2", - ProxyVulnerabilities: []string{"CVE-2021-3753", "CVE-2021-40490"}, - FixedInVersion: "5.10.62-55.141.amzn2", - }, - { - ID: "ALASKERNEL-5.10-2022-005", - RecordSource: "vulnerabilities:amzn:2", - PackageName: "kernel", - VersionConstraint: ">= 5.10, < 5.10.62-55.141.amzn2", - VersionFormat: "rpm", - Namespace: "amzn:2", - ProxyVulnerabilities: []string{"CVE-2021-3753", "CVE-2021-40490"}, - FixedInVersion: "5.10.62-55.141.amzn2", - }, - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - f, err := os.Open(test.fixture) - require.NoError(t, err) - t.Cleanup(func() { - assert.NoError(t, f.Close()) - }) - - entries, err := unmarshal.OSVulnerabilityEntries(f) - assert.NoError(t, err) - assert.Len(t, entries, test.numEntries) - - var vulns []grypeDB.Vulnerability - for _, entry := range entries { - assert.NoError(t, err) - dataEntries, err := Transform(entry) - assert.NoError(t, err) - - for _, entry := range dataEntries { - switch vuln := entry.Data.(type) { - case grypeDB.Vulnerability: - vulns = append(vulns, vuln) - case grypeDB.VulnerabilityMetadata: - default: - t.Fatalf("unexpected condition: data entry does not have a vulnerability or a metadata") - } - } - } - - if diff := cmp.Diff(test.vulns, vulns); diff != "" { - t.Errorf("vulnerabilities do not match (-want +got):\n%s", diff) - } - }) - } - -} diff --git a/pkg/process/v1/writer.go b/pkg/process/v1/writer.go deleted file mode 100644 index 50dd1805..00000000 --- a/pkg/process/v1/writer.go +++ /dev/null @@ -1,130 +0,0 @@ -package v1 - -import ( - "crypto/sha256" - "fmt" - "path" - "path/filepath" - "strings" - "time" - - "github.com/spf13/afero" - - "github.com/anchore/grype-db/internal/file" - "github.com/anchore/grype-db/internal/log" - "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype/grype/db" - grypeDB "github.com/anchore/grype/grype/db/v1" - grypeDBStore "github.com/anchore/grype/grype/db/v1/store" -) - -var _ data.Writer = (*writer)(nil) - -type writer struct { - dbPath string - store grypeDB.Store -} - -func NewWriter(directory string, dataAge time.Time) (data.Writer, error) { - dbPath := path.Join(directory, grypeDB.VulnerabilityStoreFileName) - theStore, err := grypeDBStore.New(dbPath, true) - if err != nil { - return nil, fmt.Errorf("unable to create writer: %w", err) - } - - if err := theStore.SetID(grypeDB.NewID(dataAge)); err != nil { - return nil, fmt.Errorf("unable to set DB ID: %w", err) - } - - return &writer{ - dbPath: dbPath, - store: theStore, - }, nil -} - -func (w writer) Write(entries ...data.Entry) error { - for _, entry := range entries { - if entry.DBSchemaVersion != grypeDB.SchemaVersion { - return fmt.Errorf("wrong schema version: want %+v got %+v", grypeDB.SchemaVersion, entry.DBSchemaVersion) - } - switch row := entry.Data.(type) { - case grypeDB.Vulnerability: - if err := w.store.AddVulnerability(row); err != nil { - return fmt.Errorf("unable to write vulnerability to store: %w", err) - } - case grypeDB.VulnerabilityMetadata: - normalizeSeverity(&row, w.store) - if err := w.store.AddVulnerabilityMetadata(row); err != nil { - return fmt.Errorf("unable to write vulnerability metadata to store: %w", err) - } - default: - return fmt.Errorf("data entry does not have a vulnerability or a metadata: %T", row) - } - } - - return nil -} - -func (w writer) metadata() (*db.Metadata, error) { - hashStr, err := file.ContentDigest(afero.NewOsFs(), w.dbPath, sha256.New()) - if err != nil { - return nil, fmt.Errorf("failed to hash database file (%s): %w", w.dbPath, err) - } - - storeID, err := w.store.GetID() - if err != nil { - return nil, fmt.Errorf("failed to fetch store ID: %w", err) - } - - metadata := db.Metadata{ - Built: storeID.BuildTimestamp, - Version: storeID.SchemaVersion, - Checksum: "sha256:" + hashStr, - } - return &metadata, nil -} - -func (w writer) Close() error { - w.store.Close() - metadata, err := w.metadata() - if err != nil { - return err - } - - metadataPath := path.Join(filepath.Dir(w.dbPath), db.MetadataFileName) - if err = metadata.Write(metadataPath); err != nil { - return err - } - - log.WithFields("path", w.dbPath).Info("database created") - log.WithFields("path", metadataPath).Debug("database metadata created") - - return nil -} - -func normalizeSeverity(metadata *grypeDB.VulnerabilityMetadata, reader grypeDB.VulnerabilityMetadataStoreReader) { - if metadata.Severity != "" && strings.ToLower(metadata.Severity) != "unknown" { - return - } - if !strings.HasPrefix(strings.ToLower(metadata.ID), "cve") { - return - } - if strings.Contains(metadata.RecordSource, grypeDB.NVDNamespace) { - return - } - m, err := reader.GetVulnerabilityMetadata(metadata.ID, grypeDB.NVDNamespace) - if err != nil { - log.WithFields("id", metadata.ID, "error", err).Warn("error fetching vulnerability metadata from NVD namespace") - return - } - if m == nil { - log.WithFields("id", metadata.ID).Trace("unable to find vulnerability metadata from NVD namespace") - return - } - - newSeverity := string(data.ParseSeverity(m.Severity)) - - log.WithFields("id", metadata.ID, "record-source", metadata.RecordSource, "from", metadata.Severity, "to", newSeverity).Trace("overriding irrelevant severity with data from NVD record") - - metadata.Severity = newSeverity -} diff --git a/pkg/process/v1/writer_test.go b/pkg/process/v1/writer_test.go deleted file mode 100644 index 4593346b..00000000 --- a/pkg/process/v1/writer_test.go +++ /dev/null @@ -1,118 +0,0 @@ -package v1 - -import ( - "errors" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/anchore/grype-db/pkg/data" - grypeDB "github.com/anchore/grype/grype/db/v1" -) - -var _ grypeDB.VulnerabilityMetadataStoreReader = (*mockReader)(nil) - -type mockReader struct { - metadata *grypeDB.VulnerabilityMetadata - err error -} - -func newMockReader(sev string) *mockReader { - return &mockReader{ - metadata: &grypeDB.VulnerabilityMetadata{ - Severity: sev, - RecordSource: "nvdv2:cves", - }, - } -} - -func newDeadMockReader() *mockReader { - return &mockReader{ - err: errors.New("dead"), - } -} - -func (m mockReader) GetVulnerabilityMetadata(_, _ string) (*grypeDB.VulnerabilityMetadata, error) { - return m.metadata, m.err -} - -func (m mockReader) GetAllVulnerabilityMetadata() (*[]grypeDB.VulnerabilityMetadata, error) { - panic("implement me") -} - -func Test_normalizeSeverity(t *testing.T) { - - tests := []struct { - name string - initialSeverity string - recordSource string - cveID string - reader grypeDB.VulnerabilityMetadataStoreReader - expected data.Severity - }{ - { - name: "skip missing metadata", - initialSeverity: "", - recordSource: "test", - reader: &mockReader{}, - expected: "", - }, - { - name: "skip non-cve records metadata", - cveID: "GHSA-1234-1234-1234", - initialSeverity: "", - recordSource: "test", - reader: newDeadMockReader(), // should not be used - expected: "", - }, - { - name: "override empty severity", - initialSeverity: "", - recordSource: "test", - reader: newMockReader("low"), - expected: data.SeverityLow, - }, - { - name: "override unknown severity", - initialSeverity: "unknown", - recordSource: "test", - reader: newMockReader("low"), - expected: data.SeverityLow, - }, - { - name: "ignore record with severity already set", - initialSeverity: "Low", - recordSource: "test", - reader: newMockReader("critical"), // should not be used - expected: data.SeverityLow, - }, - { - name: "ignore nvd records", - initialSeverity: "Low", - recordSource: "nvdv2:cves", - reader: newDeadMockReader(), // should not be used - expected: data.SeverityLow, - }, - { - name: "db errors should not fail or modify the record", - initialSeverity: "", - recordSource: "test", - reader: newDeadMockReader(), - expected: "", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - record := &grypeDB.VulnerabilityMetadata{ - ID: "cve-2020-0000", - Severity: tt.initialSeverity, - RecordSource: tt.recordSource, - } - if tt.cveID != "" { - record.ID = tt.cveID - } - normalizeSeverity(record, tt.reader) - assert.Equal(t, string(tt.expected), record.Severity) - }) - } -} diff --git a/pkg/process/v2/processors.go b/pkg/process/v2/processors.go deleted file mode 100644 index 19eb1ead..00000000 --- a/pkg/process/v2/processors.go +++ /dev/null @@ -1,17 +0,0 @@ -package v2 - -import ( - "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype-db/pkg/process/processors" - "github.com/anchore/grype-db/pkg/process/v2/transformers/github" - "github.com/anchore/grype-db/pkg/process/v2/transformers/nvd" - "github.com/anchore/grype-db/pkg/process/v2/transformers/os" -) - -func Processors() []data.Processor { - return []data.Processor{ - processors.NewGitHubProcessor(github.Transform), - processors.NewNVDProcessor(nvd.Transform), - processors.NewOSProcessor(os.Transform), - } -} diff --git a/pkg/process/v2/transformers/entry.go b/pkg/process/v2/transformers/entry.go deleted file mode 100644 index 6109add5..00000000 --- a/pkg/process/v2/transformers/entry.go +++ /dev/null @@ -1,22 +0,0 @@ -package transformers - -import ( - "github.com/anchore/grype-db/pkg/data" - grypeDB "github.com/anchore/grype/grype/db/v2" -) - -func NewEntries(vs []grypeDB.Vulnerability, metadata grypeDB.VulnerabilityMetadata) []data.Entry { - entries := []data.Entry{ - { - DBSchemaVersion: grypeDB.SchemaVersion, - Data: metadata, - }, - } - for _, vuln := range vs { - entries = append(entries, data.Entry{ - DBSchemaVersion: grypeDB.SchemaVersion, - Data: vuln, - }) - } - return entries -} diff --git a/pkg/process/v2/transformers/github/test-fixtures/github-github-npm-0.json b/pkg/process/v2/transformers/github/test-fixtures/github-github-npm-0.json deleted file mode 100644 index b0a7d1e9..00000000 --- a/pkg/process/v2/transformers/github/test-fixtures/github-github-npm-0.json +++ /dev/null @@ -1,31 +0,0 @@ - -{ - "Advisory": { - "CVE": [ - "CVE-2020-14000" - ], - "FixedIn": [ - { - "ecosystem": "npm", - "identifier": "0.2.0-prerelease.20200714185213", - "name": "scratch-vm", - "namespace": "github:npm", - "range": "<= 0.2.0-prerelease.20200709173451" - } - ], - "Metadata": { - "CVE": [ - "CVE-2020-14000" - ] - }, - "Severity": "High", - "Summary": "Remote Code Execution in scratch-vm", - "ghsaId": "GHSA-vc9j-fhvv-8vrf", - "namespace": "github:npm", - "url": "https://github.com/advisories/GHSA-vc9j-fhvv-8vrf", - "withdrawn": null - }, - "Vulnerability": {} -} - - diff --git a/pkg/process/v2/transformers/github/test-fixtures/github-github-python-0.json b/pkg/process/v2/transformers/github/test-fixtures/github-github-python-0.json deleted file mode 100644 index ad14aa60..00000000 --- a/pkg/process/v2/transformers/github/test-fixtures/github-github-python-0.json +++ /dev/null @@ -1,58 +0,0 @@ -[ - { - "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/v2/transformers/github/test-fixtures/github-github-python-1.json b/pkg/process/v2/transformers/github/test-fixtures/github-github-python-1.json deleted file mode 100644 index bfa84922..00000000 --- a/pkg/process/v2/transformers/github/test-fixtures/github-github-python-1.json +++ /dev/null @@ -1,43 +0,0 @@ - -{ - "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", - "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/v2/transformers/github/test-fixtures/github-withdrawn.json b/pkg/process/v2/transformers/github/test-fixtures/github-withdrawn.json deleted file mode 100644 index 04995e38..00000000 --- a/pkg/process/v2/transformers/github/test-fixtures/github-withdrawn.json +++ /dev/null @@ -1,29 +0,0 @@ - -{ - "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/v2/transformers/github/test-fixtures/multiple-fixed-in-names.json b/pkg/process/v2/transformers/github/test-fixtures/multiple-fixed-in-names.json deleted file mode 100644 index ac1ef982..00000000 --- a/pkg/process/v2/transformers/github/test-fixtures/multiple-fixed-in-names.json +++ /dev/null @@ -1,43 +0,0 @@ - -{ - "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/v2/transformers/github/transform.go b/pkg/process/v2/transformers/github/transform.go deleted file mode 100644 index 873ac1a9..00000000 --- a/pkg/process/v2/transformers/github/transform.go +++ /dev/null @@ -1,65 +0,0 @@ -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/v2/transformers" - "github.com/anchore/grype-db/pkg/provider/unmarshal" - grypeDB "github.com/anchore/grype/grype/db/v2" -) - -const ( - // TODO: tech debt from a previous design - feed = "github" -) - -func Transform(vulnerability unmarshal.GitHubAdvisory) ([]data.Entry, error) { - var allVulns []grypeDB.Vulnerability - - // Exclude entries marked as withdrawn - if vulnerability.Advisory.Withdrawn != nil { - return nil, nil - } - - recordSource := grypeDB.RecordSource(feed, vulnerability.Advisory.Namespace) - - // there may be multiple packages indicated within the FixedIn field, we should make - // separate vulnerability entries (one for each name|namespace combo) while merging - // constraint ranges as they are found. - for _, advisory := range vulnerability.Advisory.FixedIn { - constraint := common.EnforceSemVerConstraint(advisory.Range) - - var versionFormat string - switch vulnerability.Advisory.Namespace { - case "github:python": - versionFormat = "python" - default: - versionFormat = "unknown" - } - - // create vulnerability entry - vuln := grypeDB.Vulnerability{ - ID: vulnerability.Advisory.GhsaID, - RecordSource: recordSource, - VersionConstraint: constraint, - VersionFormat: versionFormat, // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: vulnerability.Advisory.CVE, - PackageName: advisory.Name, - Namespace: advisory.Namespace, - FixedInVersion: common.CleanFixedInVersion(advisory.Identifier), - } - - allVulns = append(allVulns, vuln) - } - - // create vulnerability metadata entry (a single entry keyed off of the vulnerability ID) - metadata := grypeDB.VulnerabilityMetadata{ - ID: vulnerability.Advisory.GhsaID, - RecordSource: recordSource, - Severity: vulnerability.Advisory.Severity, - Links: []string{vulnerability.Advisory.URL}, - Description: vulnerability.Advisory.Summary, - } - - return transformers.NewEntries(allVulns, metadata), nil -} diff --git a/pkg/process/v2/transformers/github/transform_test.go b/pkg/process/v2/transformers/github/transform_test.go deleted file mode 100644 index c548233c..00000000 --- a/pkg/process/v2/transformers/github/transform_test.go +++ /dev/null @@ -1,170 +0,0 @@ -package github - -import ( - "os" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - testUtils "github.com/anchore/grype-db/pkg/process/tests" - "github.com/anchore/grype-db/pkg/provider/unmarshal" - grypeDB "github.com/anchore/grype/grype/db/v2" -) - -func TestUnmarshalGitHubEntries(t *testing.T) { - f, err := os.Open("test-fixtures/github-github-python-0.json") - require.NoError(t, err) - t.Cleanup(func() { - assert.NoError(t, f.Close()) - }) - - entries, err := unmarshal.GitHubAdvisoryEntries(f) - require.NoError(t, err) - - require.Len(t, entries, 2) -} - -func TestParseGitHubEntry(t *testing.T) { - expectedVulns := []grypeDB.Vulnerability{ - { - ID: "GHSA-p5wr-vp8g-q5p4", - RecordSource: "github:python", - VersionConstraint: ">=4.0,<4.3.12", - VersionFormat: "python", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{"CVE-2017-5524"}, - PackageName: "Plone", - Namespace: "github:python", - FixedInVersion: "4.3.12", - }, - { - ID: "GHSA-p5wr-vp8g-q5p4", - RecordSource: "github:python", - VersionConstraint: ">=5.1a1,<5.1b1", - VersionFormat: "python", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{"CVE-2017-5524"}, - PackageName: "Plone", - Namespace: "github:python", - FixedInVersion: "5.1b1", - }, - { - ID: "GHSA-p5wr-vp8g-q5p4", - RecordSource: "github:python", - VersionConstraint: ">=5.0rc1,<5.0.7", - VersionFormat: "python", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{"CVE-2017-5524"}, - PackageName: "Plone", - Namespace: "github:python", - FixedInVersion: "5.0.7", - }, - } - - expectedMetadata := grypeDB.VulnerabilityMetadata{ - ID: "GHSA-p5wr-vp8g-q5p4", - RecordSource: "github:python", - Severity: "Medium", - Links: []string{"https://github.com/advisories/GHSA-p5wr-vp8g-q5p4"}, - Description: "Moderate severity vulnerability that affects Plone", - } - - f, err := os.Open("test-fixtures/github-github-python-1.json") - require.NoError(t, err) - defer testUtils.CloseFile(f) - - entries, err := unmarshal.GitHubAdvisoryEntries(f) - assert.NoError(t, err) - assert.Len(t, entries, 1) - - entry := entries[0] - - dataEntries, err := Transform(entry) - assert.NoError(t, err) - - var vulns []grypeDB.Vulnerability - for _, entry := range dataEntries { - switch vuln := entry.Data.(type) { - case grypeDB.Vulnerability: - vulns = append(vulns, vuln) - case grypeDB.VulnerabilityMetadata: - assert.Equal(t, expectedMetadata, vuln) - default: - t.Fatalf("unexpected condition: data entry does not have a vulnerability or a metadata") - } - } - - // check vulnerability - assert.Len(t, vulns, len(expectedVulns)) - - assert.ElementsMatch(t, expectedVulns, vulns) - -} - -func TestDefaultVersionFormatNpmGitHubEntry(t *testing.T) { - expectedVulns := []grypeDB.Vulnerability{ - { - ID: "GHSA-vc9j-fhvv-8vrf", - RecordSource: "github:npm", - VersionConstraint: "<=0.2.0-prerelease.20200709173451", - VersionFormat: "unknown", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{"CVE-2020-14000"}, - PackageName: "scratch-vm", - Namespace: "github:npm", - FixedInVersion: "0.2.0-prerelease.20200714185213", - }, - } - - expectedMetadata := grypeDB.VulnerabilityMetadata{ - ID: "GHSA-vc9j-fhvv-8vrf", - RecordSource: "github:npm", - Severity: "High", - Links: []string{"https://github.com/advisories/GHSA-vc9j-fhvv-8vrf"}, - Description: "Remote Code Execution in scratch-vm", - } - - f, err := os.Open("test-fixtures/github-github-npm-0.json") - require.NoError(t, err) - defer testUtils.CloseFile(f) - - entries, err := unmarshal.GitHubAdvisoryEntries(f) - assert.NoError(t, err) - assert.Len(t, entries, 1) - - entry := entries[0] - - dataEntries, err := Transform(entry) - assert.NoError(t, err) - - var vulns []grypeDB.Vulnerability - for _, entry := range dataEntries { - switch vuln := entry.Data.(type) { - case grypeDB.Vulnerability: - vulns = append(vulns, vuln) - case grypeDB.VulnerabilityMetadata: - assert.Equal(t, expectedMetadata, vuln) - default: - t.Fatalf("unexpected condition: data entry does not have a vulnerability or a metadata") - } - } - - // check vulnerability - assert.Len(t, vulns, len(expectedVulns)) - - assert.ElementsMatch(t, expectedVulns, vulns) -} - -func TestFilterWithdrawnEntries(t *testing.T) { - f, err := os.Open("test-fixtures/github-withdrawn.json") - require.NoError(t, err) - defer testUtils.CloseFile(f) - - entries, err := unmarshal.GitHubAdvisoryEntries(f) - require.NoError(t, err) - - require.Len(t, entries, 1) - - entry := entries[0] - - dataEntries, err := Transform(entry) - assert.NoError(t, err) - assert.Nil(t, dataEntries) -} diff --git a/pkg/process/v2/transformers/nvd/test-fixtures/compound-pkg.json b/pkg/process/v2/transformers/nvd/test-fixtures/compound-pkg.json deleted file mode 100644 index 8e658dcd..00000000 --- a/pkg/process/v2/transformers/nvd/test-fixtures/compound-pkg.json +++ /dev/null @@ -1,115 +0,0 @@ -{ - "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/v2/transformers/nvd/test-fixtures/invalid_cpe.json b/pkg/process/v2/transformers/nvd/test-fixtures/invalid_cpe.json deleted file mode 100644 index eac2ebd4..00000000 --- a/pkg/process/v2/transformers/nvd/test-fixtures/invalid_cpe.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "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/v2/transformers/nvd/test-fixtures/single-package-multi-distro.json b/pkg/process/v2/transformers/nvd/test-fixtures/single-package-multi-distro.json deleted file mode 100644 index ed108475..00000000 --- a/pkg/process/v2/transformers/nvd/test-fixtures/single-package-multi-distro.json +++ /dev/null @@ -1,174 +0,0 @@ -{ - "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/v2/transformers/nvd/test-fixtures/unmarshal-test.json b/pkg/process/v2/transformers/nvd/test-fixtures/unmarshal-test.json deleted file mode 100644 index 2dc698fa..00000000 --- a/pkg/process/v2/transformers/nvd/test-fixtures/unmarshal-test.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "cve": { - "id": "CVE-2003-0349", - "sourceIdentifier": "cve@mitre.org", - "published": "2003-07-24T04:00:00.000", - "lastModified": "2018-10-12T21:32:41.083", - "vulnStatus": "Modified", - "descriptions": [ - { - "lang": "en", - "value": "Buffer overflow in the streaming media component for logging multicast requests in the ISAPI for the logging capability of Microsoft Windows Media Services (nsiislog.dll), as installed in IIS 5.0, allows remote attackers to execute arbitrary code via a large POST request to nsiislog.dll." - }, - { - "lang": "es", - "value": "Desbordamiento de búfer en el componente de secuenciamiento (streaming) de medios para registrar peticiones de multidifusión en la librería ISAPI de la capacidad de registro (logging) de Microsoft Windows Media Services (nsiislog.dll), como el instalado en IIS 5.9, permite a atacantes remotos ejecutar código arbitrario mediante una petición POST larga a nsiislog.dll." - } - ], - "metrics": { - "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": false, - "obtainAllPrivilege": false, - "obtainUserPrivilege": true, - "obtainOtherPrivilege": false, - "userInteractionRequired": false - } - ] - }, - "weaknesses": [ - { - "source": "nvd@nist.gov", - "type": "Primary", - "description": [ - { - "lang": "en", - "value": "NVD-CWE-Other" - } - ] - } - ], - "configurations": [ - { - "nodes": [ - { - "operator": "OR", - "negate": false, - "cpeMatch": [ - { - "vulnerable": true, - "criteria": "cpe:2.3:o:microsoft:windows_2000:*:*:*:*:*:*:*:*", - "matchCriteriaId": "4E545C63-FE9C-4CA1-AF0F-D999D84D2AFD" - } - ] - } - ] - } - ], - "references": [ - { - "url": "http://marc.info/?l=bugtraq&m=105665030925504&w=2", - "source": "cve@mitre.org" - }, - { - "url": "http://securitytracker.com/id?1007059", - "source": "cve@mitre.org" - }, - { - "url": "http://www.kb.cert.org/vuls/id/113716", - "source": "cve@mitre.org", - "tags": [ - "US Government Resource" - ] - }, - { - "url": "http://www.ntbugtraq.com/default.asp?pid=36&sid=1&A2=ind0306&L=NTBUGTRAQ&P=R4563", - "source": "cve@mitre.org", - "tags": [ - "Exploit", - "Patch", - "Vendor Advisory" - ] - }, - { - "url": "https://docs.microsoft.com/en-us/security-updates/securitybulletins/2003/ms03-022", - "source": "cve@mitre.org" - }, - { - "url": "https://oval.cisecurity.org/repository/search/definition/oval%3Aorg.mitre.oval%3Adef%3A938", - "source": "cve@mitre.org" - } - ] - } -} diff --git a/pkg/process/v2/transformers/nvd/test-fixtures/version-range.json b/pkg/process/v2/transformers/nvd/test-fixtures/version-range.json deleted file mode 100644 index 3df5b86d..00000000 --- a/pkg/process/v2/transformers/nvd/test-fixtures/version-range.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "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/v2/transformers/nvd/transform.go b/pkg/process/v2/transformers/nvd/transform.go deleted file mode 100644 index bd189580..00000000 --- a/pkg/process/v2/transformers/nvd/transform.go +++ /dev/null @@ -1,111 +0,0 @@ -package nvd - -import ( - "strings" - - "github.com/anchore/grype-db/internal" - "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype-db/pkg/process/v2/transformers" - "github.com/anchore/grype-db/pkg/provider/unmarshal" - "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd" - grypeDB "github.com/anchore/grype/grype/db/v2" -) - -const ( - // TODO: tech debt from a previous design - feed = "nvdv2" - group = "nvdv2:cves" -) - -func Transform(vulnerability unmarshal.NVDVulnerability) ([]data.Entry, error) { - var allVulns []grypeDB.Vulnerability - - recordSource := grypeDB.RecordSource(feed, group) - - uniquePkgs := findUniquePkgs(vulnerability.Configurations...) - - // extract all links - var links []string - for _, externalRefs := range vulnerability.References { - // TODO: should we capture other information here? - if externalRefs.URL != "" { - links = append(links, externalRefs.URL) - } - } - - // duplicate the vulnerabilities based on the set of unique packages the vulnerability is for - for _, p := range uniquePkgs.All() { - matches := uniquePkgs.Matches(p) - cpes := internal.NewStringSet() - for _, m := range matches { - cpes.Add(m.Criteria) - } - - // create vulnerability entry - vuln := grypeDB.Vulnerability{ - ID: vulnerability.ID, - RecordSource: recordSource, - VersionConstraint: buildConstraints(uniquePkgs.Matches(p)), - VersionFormat: "unknown", // TODO: derive this from the target software - PackageName: p.Product, - Namespace: "nvd", // should the vendor be here? or in other metadata? - ProxyVulnerabilities: []string{}, - CPEs: cpes.ToSlice(), - } - - allVulns = append(allVulns, vuln) - } - - // If all the CPEs are invalid and no vulnerabilities were generated then there is no point - // in creating metadata, so just return - if len(allVulns) == 0 { - return nil, nil - } - - // create vulnerability metadata entry (a single entry keyed off of the vulnerability ID) - allCVSS := vulnerability.CVSS() - - metadata := grypeDB.VulnerabilityMetadata{ - ID: vulnerability.ID, - RecordSource: recordSource, - Severity: nvd.CvssSummaries(allCVSS).Sorted().Severity(), - Links: links, - Description: vulnerability.Description(), - } - - for _, c := range allCVSS { - if strings.HasPrefix(c.Version, "2.") { - newCvss := &grypeDB.Cvss{ - BaseScore: c.BaseScore, - Vector: c.Vector, - } - if c.ExploitabilityScore != nil { - newCvss.ExploitabilityScore = *c.ExploitabilityScore - } - if c.ImpactScore != nil { - newCvss.ImpactScore = *c.ImpactScore - } - metadata.CvssV2 = newCvss - break - } - } - - for _, c := range allCVSS { - if strings.HasPrefix(c.Version, "3.") { - newCvss := &grypeDB.Cvss{ - BaseScore: c.BaseScore, - Vector: c.Vector, - } - if c.ExploitabilityScore != nil { - newCvss.ExploitabilityScore = *c.ExploitabilityScore - } - if c.ImpactScore != nil { - newCvss.ImpactScore = *c.ImpactScore - } - metadata.CvssV3 = newCvss - break - } - } - - return transformers.NewEntries(allVulns, metadata), nil -} diff --git a/pkg/process/v2/transformers/nvd/transform_test.go b/pkg/process/v2/transformers/nvd/transform_test.go deleted file mode 100644 index 7d10d18a..00000000 --- a/pkg/process/v2/transformers/nvd/transform_test.go +++ /dev/null @@ -1,190 +0,0 @@ -package nvd - -import ( - "os" - "testing" - - "github.com/go-test/deep" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - testUtils "github.com/anchore/grype-db/pkg/process/tests" - "github.com/anchore/grype-db/pkg/provider/unmarshal" - grypeDB "github.com/anchore/grype/grype/db/v2" -) - -func TestUnmarshalVulnerabilitiesEntries(t *testing.T) { - f, err := os.Open("test-fixtures/unmarshal-test.json") - require.NoError(t, err) - defer testUtils.CloseFile(f) - - entries, err := unmarshal.NvdVulnerabilityEntries(f) - require.NoError(t, err) - - require.Len(t, entries, 1) -} - -func TestParseVulnerabilitiesAllEntries(t *testing.T) { - tests := []struct { - name string - numEntries int - fixture string - vulns []grypeDB.Vulnerability - metadata grypeDB.VulnerabilityMetadata - }{ - { - name: "AppVersionRange", - numEntries: 1, - fixture: "test-fixtures/version-range.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2018-5487", - RecordSource: "nvdv2:cves", - PackageName: "oncommand_unified_manager", - VersionConstraint: ">= 7.2, <= 7.3", - VersionFormat: "unknown", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{}, - Namespace: "nvd", - CPEs: []string{"cpe:2.3:a:netapp:oncommand_unified_manager:*:*:*:*:*:*:*:*"}, - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2018-5487", - RecordSource: "nvdv2:cves", - Severity: "Critical", - Links: []string{"https://security.netapp.com/advisory/ntap-20180523-0001/"}, - 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.", - CvssV2: &grypeDB.Cvss{ - BaseScore: 7.5, - ExploitabilityScore: 10, - ImpactScore: 6.4, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P", - }, - CvssV3: &grypeDB.Cvss{ - BaseScore: 9.8, - ExploitabilityScore: 3.9, - ImpactScore: 5.9, - Vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", - }, - }, - }, - { - name: "App+OS", - numEntries: 1, - fixture: "test-fixtures/single-package-multi-distro.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2018-1000222", - RecordSource: "nvdv2:cves", - PackageName: "libgd", - VersionConstraint: "= 2.2.5", - VersionFormat: "unknown", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{}, - Namespace: "nvd", - CPEs: []string{"cpe:2.3:a:libgd:libgd:2.2.5:*:*:*:*:*:*:*"}, - }, - // TODO: Question: should this match also the OS's? (as in the vulnerable_cpes list)... this seems wrong! - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2018-1000222", - RecordSource: "nvdv2:cves", - Severity: "High", - Links: []string{"https://github.com/libgd/libgd/issues/447", "https://lists.debian.org/debian-lts-announce/2019/01/msg00028.html", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/3CZ2QADQTKRHTGB2AHD7J4QQNDLBEMM6/", "https://security.gentoo.org/glsa/201903-18", "https://usn.ubuntu.com/3755-1/"}, - 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.", - CvssV2: &grypeDB.Cvss{ - BaseScore: 6.8, - ExploitabilityScore: 8.6, - ImpactScore: 6.4, - Vector: "AV:N/AC:M/Au:N/C:P/I:P/A:P", - }, - CvssV3: &grypeDB.Cvss{ - BaseScore: 8.8, - ExploitabilityScore: 2.8, - ImpactScore: 5.9, - Vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", - }, - }, - }, - { - name: "AppCompoundVersionRange", - numEntries: 1, - fixture: "test-fixtures/compound-pkg.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2018-10189", - RecordSource: "nvdv2:cves", - PackageName: "mautic", - VersionConstraint: ">= 1.0.0, <= 1.4.1 || >= 2.0.0, < 2.13.0", - VersionFormat: "unknown", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{}, - Namespace: "nvd", - CPEs: []string{"cpe:2.3:a:mautic:mautic:*:*:*:*:*:*:*:*"}, // note: entry was dedupicated - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2018-10189", - RecordSource: "nvdv2:cves", - Severity: "High", - Links: []string{"https://github.com/mautic/mautic/releases/tag/2.13.0"}, - 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.", - CvssV2: &grypeDB.Cvss{ - BaseScore: 5, - ExploitabilityScore: 10, - ImpactScore: 2.9, - Vector: "AV:N/AC:L/Au:N/C:P/I:N/A:N", - }, - CvssV3: &grypeDB.Cvss{ - BaseScore: 7.5, - ExploitabilityScore: 3.9, - ImpactScore: 3.6, - Vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", - }, - }, - }, - { - name: "InvalidCPE", - numEntries: 1, - fixture: "test-fixtures/invalid_cpe.json", - vulns: []grypeDB.Vulnerability{}, - metadata: grypeDB.VulnerabilityMetadata{}, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - f, err := os.Open(test.fixture) - require.NoError(t, err) - t.Cleanup(func() { - assert.NoError(t, f.Close()) - }) - - entries, err := unmarshal.NvdVulnerabilityEntries(f) - assert.NoError(t, err) - - var vulns []grypeDB.Vulnerability - for _, entry := range entries { - dataEntries, err := Transform(entry.Cve) - assert.NoError(t, err) - - for _, entry := range dataEntries { - switch vuln := entry.Data.(type) { - case grypeDB.Vulnerability: - vulns = append(vulns, vuln) - case grypeDB.VulnerabilityMetadata: - // check metadata - if diff := deep.Equal(test.metadata, vuln); diff != nil { - for _, d := range diff { - t.Errorf("metadata diff: %+v", d) - } - } - default: - t.Fatalf("unexpected condition: data entry does not have a vulnerability or a metadata") - } - } - - } - - assert.ElementsMatch(t, test.vulns, vulns) - }) - } -} diff --git a/pkg/process/v2/transformers/nvd/unique_pkg.go b/pkg/process/v2/transformers/nvd/unique_pkg.go deleted file mode 100644 index 20a62777..00000000 --- a/pkg/process/v2/transformers/nvd/unique_pkg.go +++ /dev/null @@ -1,115 +0,0 @@ -package nvd - -import ( - "fmt" - "strings" - - "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/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(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 only interested in applications, not hardware or operating systems - if c.Part() != cpe.Application { - return nil, nil - } - - return &pkgCandidate{ - Product: c.Product().String(), - Vendor: c.Vendor().String(), - TargetSoftware: c.TargetSw().String(), - }, nil -} - -func findUniquePkgs(cfgs ...nvd.Configuration) uniquePkgTracker { - set := newUniquePkgTracker() - for _, c := range cfgs { - _findUniquePkgs(set, c.Nodes...) - } - return set -} - -func _findUniquePkgs(set uniquePkgTracker, ns ...nvd.Node) { - if len(ns) == 0 { - return - } - for _, node := range ns { - for _, match := range node.CpeMatch { - candidate, err := newPkgCandidate(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.Add(*candidate, match) - } - } - } -} - -func buildConstraints(matches []nvd.CpeMatch) string { - constraints := make([]string, 0) - for _, match := range matches { - constraints = append(constraints, buildConstraint(match)) - } - return common.OrConstraints(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() - if version != ANY && version != NA { - constraints = append(constraints, fmt.Sprintf("= %s", version)) - } - } - - return strings.Join(constraints, ", ") -} diff --git a/pkg/process/v2/transformers/nvd/unique_pkg_test.go b/pkg/process/v2/transformers/nvd/unique_pkg_test.go deleted file mode 100644 index beb050fc..00000000 --- a/pkg/process/v2/transformers/nvd/unique_pkg_test.go +++ /dev/null @@ -1,353 +0,0 @@ -package nvd - -import ( - "testing" - - "github.com/sergi/go-diff/diffmatchpatch" - - "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd" -) - -func newUniquePkgTrackerFromSlice(candidates []pkgCandidate) uniquePkgTracker { - set := newUniquePkgTracker() - for _, c := range candidates { - set[c] = nil - } - return set -} - -func TestFindUniquePkgs(t *testing.T) { - tests := []struct { - name string - nodes []nvd.Node - 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", - }, - }), - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - actual := findUniquePkgs(nvd.Configuration{Nodes: test.nodes}) - 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)) - } - }) - } - -} diff --git a/pkg/process/v2/transformers/nvd/unique_pkg_tracker.go b/pkg/process/v2/transformers/nvd/unique_pkg_tracker.go deleted file mode 100644 index 2b7e405d..00000000 --- a/pkg/process/v2/transformers/nvd/unique_pkg_tracker.go +++ /dev/null @@ -1,64 +0,0 @@ -package nvd - -import ( - "sort" - - "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd" -) - -type uniquePkgTracker map[pkgCandidate][]nvd.CpeMatch - -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 (s uniquePkgTracker) Matches(i pkgCandidate) []nvd.CpeMatch { - return s[i] -} - -func (s uniquePkgTracker) Add(i pkgCandidate, match nvd.CpeMatch) { - if _, ok := s[i]; !ok { - s[i] = make([]nvd.CpeMatch, 0) - } - s[i] = append(s[i], match) -} - -func (s uniquePkgTracker) Remove(i pkgCandidate) { - delete(s, i) -} - -func (s uniquePkgTracker) Contains(i pkgCandidate) bool { - _, ok := s[i] - return ok -} - -func (s uniquePkgTracker) All() []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/v2/transformers/os/test-fixtures/alpine-3.9.json b/pkg/process/v2/transformers/os/test-fixtures/alpine-3.9.json deleted file mode 100644 index b9d84395..00000000 --- a/pkg/process/v2/transformers/os/test-fixtures/alpine-3.9.json +++ /dev/null @@ -1,28 +0,0 @@ -[ - { - "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/v2/transformers/os/test-fixtures/amazon-multiple-kernel-advisories.json b/pkg/process/v2/transformers/os/test-fixtures/amazon-multiple-kernel-advisories.json deleted file mode 100644 index 82f2b45b..00000000 --- a/pkg/process/v2/transformers/os/test-fixtures/amazon-multiple-kernel-advisories.json +++ /dev/null @@ -1,104 +0,0 @@ -[ - { - "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/v2/transformers/os/test-fixtures/amzn.json b/pkg/process/v2/transformers/os/test-fixtures/amzn.json deleted file mode 100644 index a862c32e..00000000 --- a/pkg/process/v2/transformers/os/test-fixtures/amzn.json +++ /dev/null @@ -1,49 +0,0 @@ -[ - { - "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/v2/transformers/os/test-fixtures/debian-8-multiple-entries-for-same-package.json b/pkg/process/v2/transformers/os/test-fixtures/debian-8-multiple-entries-for-same-package.json deleted file mode 100644 index 5025b56e..00000000 --- a/pkg/process/v2/transformers/os/test-fixtures/debian-8-multiple-entries-for-same-package.json +++ /dev/null @@ -1,62 +0,0 @@ -[ - { - "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/v2/transformers/os/test-fixtures/debian-8.json b/pkg/process/v2/transformers/os/test-fixtures/debian-8.json deleted file mode 100644 index a758f13c..00000000 --- a/pkg/process/v2/transformers/os/test-fixtures/debian-8.json +++ /dev/null @@ -1,62 +0,0 @@ -[ - { - "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/v2/transformers/os/test-fixtures/ol-8-modules.json b/pkg/process/v2/transformers/os/test-fixtures/ol-8-modules.json deleted file mode 100644 index f1d7372b..00000000 --- a/pkg/process/v2/transformers/os/test-fixtures/ol-8-modules.json +++ /dev/null @@ -1,36 +0,0 @@ -[ - { - "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/v2/transformers/os/test-fixtures/ol-8.json b/pkg/process/v2/transformers/os/test-fixtures/ol-8.json deleted file mode 100644 index 09439ece..00000000 --- a/pkg/process/v2/transformers/os/test-fixtures/ol-8.json +++ /dev/null @@ -1,42 +0,0 @@ -[ - { - "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/v2/transformers/os/test-fixtures/rhel-8-modules.json b/pkg/process/v2/transformers/os/test-fixtures/rhel-8-modules.json deleted file mode 100644 index c0400ad5..00000000 --- a/pkg/process/v2/transformers/os/test-fixtures/rhel-8-modules.json +++ /dev/null @@ -1,75 +0,0 @@ -[ - { - "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/v2/transformers/os/test-fixtures/rhel-8.json b/pkg/process/v2/transformers/os/test-fixtures/rhel-8.json deleted file mode 100644 index 2779708c..00000000 --- a/pkg/process/v2/transformers/os/test-fixtures/rhel-8.json +++ /dev/null @@ -1,57 +0,0 @@ -[ - { - "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/v2/transformers/os/test-fixtures/unmarshal-test.json b/pkg/process/v2/transformers/os/test-fixtures/unmarshal-test.json deleted file mode 100644 index edc6d25b..00000000 --- a/pkg/process/v2/transformers/os/test-fixtures/unmarshal-test.json +++ /dev/null @@ -1,104 +0,0 @@ -[ - { - "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" - } - }, - { - "Vulnerability": { - "Description": "", - "FixedIn": [ - { - "Name": "kernel-livepatch-4.14.173-137.228", - "NamespaceName": "amzn:2", - "Version": "1.0-3.amzn2", - "VersionFormat": "rpm" - }, - { - "Name": "kernel-livepatch-4.14.173-137.228-debuginfo", - "NamespaceName": "amzn:2", - "Version": "1.0-3.amzn2", - "VersionFormat": "rpm" - } - ], - "Link": "https://alas.aws.amazon.com/AL2/ALASLIVEPATCH-2020-012.html", - "Metadata": { - "CVE": [ - {"Name": "CVE-2020-12657"} - ] - }, - "Name": "ALASLIVEPATCH-2020-012", - "NamespaceName": "amzn:2", - "Severity": "High" - } - }, - { - "Vulnerability": { - "Description": "", - "FixedIn": [ - { - "Name": "kernel-livepatch-4.14.171-136.231", - "NamespaceName": "amzn:2", - "Version": "1.0-5.amzn2", - "VersionFormat": "rpm" - }, - { - "Name": "kernel-livepatch-4.14.171-136.231-debuginfo", - "NamespaceName": "amzn:2", - "Version": "1.0-5.amzn2", - "VersionFormat": "rpm" - } - ], - "Link": "https://alas.aws.amazon.com/AL2/ALASLIVEPATCH-2020-011.html", - "Metadata": { - "CVE": [ - {"Name": "CVE-2020-12657"} - ] - }, - "Name": "ALASLIVEPATCH-2020-011", - "NamespaceName": "amzn:2", - "Severity": "High" - } - } -] \ No newline at end of file diff --git a/pkg/process/v2/transformers/os/transform.go b/pkg/process/v2/transformers/os/transform.go deleted file mode 100644 index f1c7dafc..00000000 --- a/pkg/process/v2/transformers/os/transform.go +++ /dev/null @@ -1,118 +0,0 @@ -package os - -import ( - "fmt" - "strings" - - "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype-db/pkg/process/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" -) - -const ( - // TODO: tech debt from a previous design - feed = "vulnerabilities" -) - -func Transform(vulnerability unmarshal.OSVulnerability) ([]data.Entry, error) { - group := vulnerability.Vulnerability.NamespaceName - - var allVulns []grypeDB.Vulnerability - - recordSource := grypeDB.RecordSource(feed, group) - vulnerability.Vulnerability.FixedIn = vulnerability.Vulnerability.FixedIn.FilterToHighestModularity() - - // there may be multiple packages indicated within the FixedIn field, we should make - // separate vulnerability entries (one for each name|namespace combo) while merging - // constraint ranges as they are found. - for _, advisory := range vulnerability.Vulnerability.FixedIn { - // create vulnerability entry - vuln := grypeDB.Vulnerability{ - ID: vulnerability.Vulnerability.Name, - RecordSource: recordSource, - VersionConstraint: enforceConstraint(advisory.Version, advisory.VersionFormat, vulnerability.Vulnerability.Name), - VersionFormat: advisory.VersionFormat, - PackageName: advisory.Name, - Namespace: advisory.NamespaceName, - ProxyVulnerabilities: []string{}, - FixedInVersion: common.CleanFixedInVersion(advisory.Version), - } - - // associate related vulnerabilities - // note: an example of multiple CVEs for a record is centos:5 RHSA-2007:0055 which maps to CVE-2007-0002 and CVE-2007-1466 - for _, ref := range vulnerability.Vulnerability.Metadata.CVE { - vuln.ProxyVulnerabilities = append(vuln.ProxyVulnerabilities, ref.Name) - } - - allVulns = append(allVulns, vuln) - } - - var cvssV2 *grypeDB.Cvss - if vulnerability.Vulnerability.Metadata.NVD.CVSSv2.Vectors != "" { - cvssV2 = &grypeDB.Cvss{ - BaseScore: vulnerability.Vulnerability.Metadata.NVD.CVSSv2.Score, - ExploitabilityScore: 0, - ImpactScore: 0, - Vector: vulnerability.Vulnerability.Metadata.NVD.CVSSv2.Vectors, - } - } - - // find all URLs related to the vulnerability - links := []string{vulnerability.Vulnerability.Link} - if vulnerability.Vulnerability.Metadata.CVE != nil { - for _, cve := range vulnerability.Vulnerability.Metadata.CVE { - if cve.Link != "" { - links = append(links, cve.Link) - } - } - } - - // create vulnerability metadata entry (a single entry keyed off of the vulnerability ID) - metadata := grypeDB.VulnerabilityMetadata{ - ID: vulnerability.Vulnerability.Name, - RecordSource: recordSource, - Severity: vulnerability.Vulnerability.Severity, - Links: links, - Description: vulnerability.Vulnerability.Description, - CvssV2: cvssV2, - } - - return transformers.NewEntries(allVulns, metadata), nil -} - -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 -} - -func enforceConstraint(constraint, format, vulnerabilityID string) string { - constraint = common.CleanConstraint(constraint) - if len(constraint) == 0 { - return "" - } - switch strings.ToLower(format) { - case "semver": - return common.EnforceSemVerConstraint(constraint) - default: - // the passed constraint is a fixed version - return deriveConstraintFromFix(constraint, vulnerabilityID) - } -} diff --git a/pkg/process/v2/transformers/os/transform_test.go b/pkg/process/v2/transformers/os/transform_test.go deleted file mode 100644 index cae8160c..00000000 --- a/pkg/process/v2/transformers/os/transform_test.go +++ /dev/null @@ -1,500 +0,0 @@ -package os - -import ( - "os" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - testUtils "github.com/anchore/grype-db/pkg/process/tests" - "github.com/anchore/grype-db/pkg/provider/unmarshal" - grypeDB "github.com/anchore/grype/grype/db/v2" -) - -func TestUnmarshalVulnerabilitiesEntries(t *testing.T) { - f, err := os.Open("test-fixtures/unmarshal-test.json") - require.NoError(t, err) - defer testUtils.CloseFile(f) - - entries, err := unmarshal.OSVulnerabilityEntries(f) - require.NoError(t, err) - - require.Len(t, entries, 3) -} - -func TestParseVulnerabilitiesEntry(t *testing.T) { - tests := []struct { - name string - numEntries int - fixture string - vulns []grypeDB.Vulnerability - metadata grypeDB.VulnerabilityMetadata - feed, group string - }{ - { - name: "Amazon", - numEntries: 1, - fixture: "test-fixtures/amzn.json", - feed: "vulnerabilities", - group: "amzn:2", - vulns: []grypeDB.Vulnerability{ - { - ID: "ALAS-2018-1106", - RecordSource: "vulnerabilities:amzn:2", - VersionConstraint: "< 1.3.8.4-15.amzn2.0.1", - VersionFormat: "rpm", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{"CVE-2018-14648"}, - PackageName: "389-ds-base", - Namespace: "amzn:2", - FixedInVersion: "1.3.8.4-15.amzn2.0.1", - }, - { - ID: "ALAS-2018-1106", - RecordSource: "vulnerabilities:amzn:2", - VersionConstraint: "< 1.3.8.4-15.amzn2.0.1", - VersionFormat: "rpm", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{"CVE-2018-14648"}, - PackageName: "389-ds-base-debuginfo", - Namespace: "amzn:2", - FixedInVersion: "1.3.8.4-15.amzn2.0.1", - }, - { - ID: "ALAS-2018-1106", - RecordSource: "vulnerabilities:amzn:2", - VersionConstraint: "< 1.3.8.4-15.amzn2.0.1", - VersionFormat: "rpm", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{"CVE-2018-14648"}, - PackageName: "389-ds-base-devel", - Namespace: "amzn:2", - FixedInVersion: "1.3.8.4-15.amzn2.0.1", - }, - { - ID: "ALAS-2018-1106", - RecordSource: "vulnerabilities:amzn:2", - VersionConstraint: "< 1.3.8.4-15.amzn2.0.1", - VersionFormat: "rpm", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{"CVE-2018-14648"}, - PackageName: "389-ds-base-libs", - Namespace: "amzn:2", - FixedInVersion: "1.3.8.4-15.amzn2.0.1", - }, - { - ID: "ALAS-2018-1106", - RecordSource: "vulnerabilities:amzn:2", - VersionConstraint: "< 1.3.8.4-15.amzn2.0.1", - VersionFormat: "rpm", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{"CVE-2018-14648"}, - PackageName: "389-ds-base-snmp", - Namespace: "amzn:2", - FixedInVersion: "1.3.8.4-15.amzn2.0.1", - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "ALAS-2018-1106", - RecordSource: "vulnerabilities:amzn:2", - Severity: "Medium", - Links: []string{"https://alas.aws.amazon.com/AL2/ALAS-2018-1106.html"}, - }, - }, - { - name: "Debian", - numEntries: 1, - fixture: "test-fixtures/debian-8.json", - feed: "vulnerabilities", - group: "debian:8", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2008-7220", - RecordSource: "vulnerabilities:debian:8", - PackageName: "asterisk", - VersionConstraint: "< 1:1.6.2.0~rc3-1", - VersionFormat: "dpkg", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{}, - Namespace: "debian:8", - FixedInVersion: "1:1.6.2.0~rc3-1", - }, - { - ID: "CVE-2008-7220", - RecordSource: "vulnerabilities:debian:8", - PackageName: "auth2db", - VersionConstraint: "< 0.2.5-2+dfsg-1", - VersionFormat: "dpkg", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{}, - Namespace: "debian:8", - FixedInVersion: "0.2.5-2+dfsg-1", - }, - { - ID: "CVE-2008-7220", - RecordSource: "vulnerabilities:debian:8", - PackageName: "exaile", - VersionConstraint: "< 0.2.14+debian-2.2", - VersionFormat: "dpkg", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{}, - Namespace: "debian:8", - FixedInVersion: "0.2.14+debian-2.2", - }, - { - ID: "CVE-2008-7220", - RecordSource: "vulnerabilities:debian:8", - PackageName: "wordpress", - VersionConstraint: "", - VersionFormat: "dpkg", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{}, - Namespace: "debian:8", - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2008-7220", - RecordSource: "vulnerabilities:debian:8", - Severity: "High", - Links: []string{"https://security-tracker.debian.org/tracker/CVE-2008-7220"}, - Description: "", - CvssV2: &grypeDB.Cvss{ - BaseScore: 7.5, - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P", - }, - }, - }, - { - name: "RHEL", - numEntries: 1, - fixture: "test-fixtures/rhel-8.json", - feed: "vulnerabilities", - group: "rhel:8", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2020-6819", - RecordSource: "vulnerabilities:rhel:8", - PackageName: "firefox", - VersionConstraint: "< 0:68.6.1-1.el8_1", - VersionFormat: "rpm", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{}, - Namespace: "rhel:8", - FixedInVersion: "0:68.6.1-1.el8_1", - }, - { - ID: "CVE-2020-6819", - RecordSource: "vulnerabilities:rhel:8", - PackageName: "thunderbird", - VersionConstraint: "< 0:68.7.0-1.el8_1", - VersionFormat: "rpm", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{}, - Namespace: "rhel:8", - FixedInVersion: "0:68.7.0-1.el8_1", - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2020-6819", - RecordSource: "vulnerabilities:rhel:8", - Severity: "Critical", - Links: []string{"https://access.redhat.com/security/cve/CVE-2020-6819"}, - 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.", - }, - }, - { - name: "RHEL with modularity", - numEntries: 1, - fixture: "test-fixtures/rhel-8-modules.json", - feed: "vulnerabilities", - group: "rhel:8", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2020-14350", - RecordSource: "vulnerabilities:rhel:8", - PackageName: "postgresql", - VersionConstraint: "< 0:12.5-1.module+el8.3.0+9042+664538f4", - VersionFormat: "rpm", - ProxyVulnerabilities: []string{}, - Namespace: "rhel:8", - FixedInVersion: "0:12.5-1.module+el8.3.0+9042+664538f4", - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2020-14350", - RecordSource: "vulnerabilities:rhel:8", - Severity: "Medium", - Links: []string{"https://access.redhat.com/security/cve/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.", - }, - }, - { - name: "Alpine", - numEntries: 1, - fixture: "test-fixtures/alpine-3.9.json", - feed: "vulnerabilities", - group: "alpine:3.9", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2018-19967", - RecordSource: "vulnerabilities:alpine:3.9", - PackageName: "xen", - VersionConstraint: "< 4.11.1-r0", - VersionFormat: "apk", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{}, - Namespace: "alpine:3.9", - FixedInVersion: "4.11.1-r0", - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2018-19967", - RecordSource: "vulnerabilities:alpine:3.9", - Severity: "Medium", - Links: []string{"http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-19967"}, - Description: "", - CvssV2: &grypeDB.Cvss{ - BaseScore: 4.9, - ExploitabilityScore: 0, - ImpactScore: 0, - Vector: "AV:L/AC:L/Au:N/C:N/I:N/A:C", - }, - }, - }, - { - name: "Oracle", - numEntries: 1, - fixture: "test-fixtures/ol-8.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "ELSA-2020-2550", - RecordSource: "vulnerabilities:ol:8", - PackageName: "libexif", - VersionConstraint: "< 0:0.6.21-17.el8_2", - VersionFormat: "rpm", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{"CVE-2020-13112"}, - Namespace: "ol:8", - FixedInVersion: "0:0.6.21-17.el8_2", - }, - { - ID: "ELSA-2020-2550", - RecordSource: "vulnerabilities:ol:8", - PackageName: "libexif-devel", - VersionConstraint: "< 0:0.6.21-17.el8_2", - VersionFormat: "rpm", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{"CVE-2020-13112"}, - Namespace: "ol:8", - FixedInVersion: "0:0.6.21-17.el8_2", - }, - { - ID: "ELSA-2020-2550", - RecordSource: "vulnerabilities:ol:8", - PackageName: "libexif-dummy", - VersionConstraint: "", - VersionFormat: "rpm", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{"CVE-2020-13112"}, - Namespace: "ol:8", - FixedInVersion: "", - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "ELSA-2020-2550", - RecordSource: "vulnerabilities:ol:8", - Severity: "Medium", - Links: []string{"http://linux.oracle.com/errata/ELSA-2020-2550.html", "http://linux.oracle.com/cve/CVE-2020-13112.html"}, - }, - }, - { - name: "Oracle Linux 8 with modularity", - numEntries: 1, - fixture: "test-fixtures/ol-8-modules.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2020-14350", - RecordSource: "vulnerabilities:ol:8", - PackageName: "postgresql", - VersionConstraint: "< 0:12.5-1.module+el8.3.0+9042+664538f4", - VersionFormat: "rpm", - ProxyVulnerabilities: []string{}, - Namespace: "ol:8", - FixedInVersion: "0:12.5-1.module+el8.3.0+9042+664538f4", - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2020-14350", - RecordSource: "vulnerabilities:ol:8", - Severity: "Medium", - Links: []string{"https://access.redhat.com/security/cve/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.", - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - f, err := os.Open(test.fixture) - require.NoError(t, err) - t.Cleanup(func() { - assert.NoError(t, f.Close()) - }) - - entries, err := unmarshal.OSVulnerabilityEntries(f) - assert.NoError(t, err) - assert.Len(t, entries, 1) - - entry := entries[0] - - dataEntries, err := Transform(entry) - assert.NoError(t, err) - - var vulns []grypeDB.Vulnerability - for _, entry := range dataEntries { - switch vuln := entry.Data.(type) { - case grypeDB.Vulnerability: - vulns = append(vulns, vuln) - case grypeDB.VulnerabilityMetadata: - assert.Equal(t, test.metadata, vuln) - default: - t.Fatalf("unexpected condition: data entry does not have a vulnerability or a metadata") - } - } - - if diff := cmp.Diff(test.vulns, vulns); diff != "" { - t.Errorf("vulnerabilities do not match (-want +got):\n%s", diff) - } - - }) - } - -} - -func TestParseVulnerabilitiesAllEntries(t *testing.T) { - - tests := []struct { - name string - numEntries int - fixture string - vulns []grypeDB.Vulnerability - }{ - { - name: "Debian", - numEntries: 2, - fixture: "test-fixtures/debian-8-multiple-entries-for-same-package.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2011-4623", - RecordSource: "vulnerabilities:debian:8", - PackageName: "rsyslog", - VersionConstraint: "< 5.7.4-1", - VersionFormat: "dpkg", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{}, - Namespace: "debian:8", - FixedInVersion: "5.7.4-1", - }, - { - ID: "CVE-2008-5618", - RecordSource: "vulnerabilities:debian:8", - PackageName: "rsyslog", - VersionConstraint: "< 3.18.6-1", - VersionFormat: "dpkg", // TODO: this should reference a format, yes? (not a string) - ProxyVulnerabilities: []string{}, - Namespace: "debian:8", - FixedInVersion: "3.18.6-1", - }, - }, - }, - { - name: "Amazon", - numEntries: 3, - fixture: "test-fixtures/amazon-multiple-kernel-advisories.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "ALAS-2021-1704", - RecordSource: "vulnerabilities:amzn:2", - PackageName: "kernel-headers", - VersionConstraint: "< 4.14.246-187.474.amzn2", - VersionFormat: "rpm", - Namespace: "amzn:2", - ProxyVulnerabilities: []string{"CVE-2021-3653", "CVE-2021-3656", "CVE-2021-3732"}, - FixedInVersion: "4.14.246-187.474.amzn2", - }, - { - ID: "ALAS-2021-1704", - RecordSource: "vulnerabilities:amzn:2", - PackageName: "kernel", - VersionConstraint: "< 4.14.246-187.474.amzn2", - VersionFormat: "rpm", - Namespace: "amzn:2", - ProxyVulnerabilities: []string{"CVE-2021-3653", "CVE-2021-3656", "CVE-2021-3732"}, - FixedInVersion: "4.14.246-187.474.amzn2", - }, - { - ID: "ALASKERNEL-5.4-2022-007", - RecordSource: "vulnerabilities:amzn:2", - PackageName: "kernel-headers", - VersionConstraint: ">= 5.4, < 5.4.144-69.257.amzn2", - VersionFormat: "rpm", - Namespace: "amzn:2", - ProxyVulnerabilities: []string{"CVE-2021-3753", "CVE-2021-40490"}, - FixedInVersion: "5.4.144-69.257.amzn2", - }, - { - ID: "ALASKERNEL-5.4-2022-007", - RecordSource: "vulnerabilities:amzn:2", - PackageName: "kernel", - VersionConstraint: ">= 5.4, < 5.4.144-69.257.amzn2", - VersionFormat: "rpm", - Namespace: "amzn:2", - ProxyVulnerabilities: []string{"CVE-2021-3753", "CVE-2021-40490"}, - FixedInVersion: "5.4.144-69.257.amzn2", - }, - { - ID: "ALASKERNEL-5.10-2022-005", - RecordSource: "vulnerabilities:amzn:2", - PackageName: "kernel-headers", - VersionConstraint: ">= 5.10, < 5.10.62-55.141.amzn2", - VersionFormat: "rpm", - Namespace: "amzn:2", - ProxyVulnerabilities: []string{"CVE-2021-3753", "CVE-2021-40490"}, - FixedInVersion: "5.10.62-55.141.amzn2", - }, - { - ID: "ALASKERNEL-5.10-2022-005", - RecordSource: "vulnerabilities:amzn:2", - PackageName: "kernel", - VersionConstraint: ">= 5.10, < 5.10.62-55.141.amzn2", - VersionFormat: "rpm", - Namespace: "amzn:2", - ProxyVulnerabilities: []string{"CVE-2021-3753", "CVE-2021-40490"}, - FixedInVersion: "5.10.62-55.141.amzn2", - }, - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - f, err := os.Open(test.fixture) - require.NoError(t, err) - t.Cleanup(func() { - assert.NoError(t, f.Close()) - }) - - entries, err := unmarshal.OSVulnerabilityEntries(f) - assert.NoError(t, err) - assert.Len(t, entries, test.numEntries) - - var vulns []grypeDB.Vulnerability - for _, entry := range entries { - assert.NoError(t, err) - dataEntries, err := Transform(entry) - assert.NoError(t, err) - - for _, entry := range dataEntries { - switch vuln := entry.Data.(type) { - case grypeDB.Vulnerability: - vulns = append(vulns, vuln) - case grypeDB.VulnerabilityMetadata: - default: - t.Fatalf("unexpected condition: data entry does not have a vulnerability or a metadata") - } - } - } - - if diff := cmp.Diff(test.vulns, vulns); diff != "" { - t.Errorf("vulnerabilities do not match (-want +got):\n%s", diff) - } - }) - } - -} diff --git a/pkg/process/v2/writer.go b/pkg/process/v2/writer.go deleted file mode 100644 index c8029d57..00000000 --- a/pkg/process/v2/writer.go +++ /dev/null @@ -1,130 +0,0 @@ -package v2 - -import ( - "crypto/sha256" - "fmt" - "path" - "path/filepath" - "strings" - "time" - - "github.com/spf13/afero" - - "github.com/anchore/grype-db/internal/file" - "github.com/anchore/grype-db/internal/log" - "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype/grype/db" - grypeDB "github.com/anchore/grype/grype/db/v2" - grypeDBStore "github.com/anchore/grype/grype/db/v2/store" -) - -var _ data.Writer = (*writer)(nil) - -type writer struct { - dbPath string - store grypeDB.Store -} - -func NewWriter(directory string, dataAge time.Time) (data.Writer, error) { - dbPath := path.Join(directory, grypeDB.VulnerabilityStoreFileName) - theStore, err := grypeDBStore.New(dbPath, true) - if err != nil { - return nil, fmt.Errorf("unable to create writer: %w", err) - } - - if err := theStore.SetID(grypeDB.NewID(dataAge)); err != nil { - return nil, fmt.Errorf("unable to set DB ID: %w", err) - } - - return &writer{ - dbPath: dbPath, - store: theStore, - }, nil -} - -func (w writer) Write(entries ...data.Entry) error { - for _, entry := range entries { - if entry.DBSchemaVersion != grypeDB.SchemaVersion { - return fmt.Errorf("wrong schema version: want %+v got %+v", grypeDB.SchemaVersion, entry.DBSchemaVersion) - } - switch row := entry.Data.(type) { - case grypeDB.Vulnerability: - if err := w.store.AddVulnerability(row); err != nil { - return fmt.Errorf("unable to write vulnerability to store: %w", err) - } - case grypeDB.VulnerabilityMetadata: - normalizeSeverity(&row, w.store) - if err := w.store.AddVulnerabilityMetadata(row); err != nil { - return fmt.Errorf("unable to write vulnerability metadata to store: %w", err) - } - default: - return fmt.Errorf("data entry does not have a vulnerability or a metadata: %T", row) - } - } - - return nil -} - -func (w writer) metadata() (*db.Metadata, error) { - hashStr, err := file.ContentDigest(afero.NewOsFs(), w.dbPath, sha256.New()) - if err != nil { - return nil, fmt.Errorf("failed to hash database file (%s): %w", w.dbPath, err) - } - - storeID, err := w.store.GetID() - if err != nil { - return nil, fmt.Errorf("failed to fetch store ID: %w", err) - } - - metadata := db.Metadata{ - Built: storeID.BuildTimestamp, - Version: storeID.SchemaVersion, - Checksum: "sha256:" + hashStr, - } - return &metadata, nil -} - -func (w writer) Close() error { - w.store.Close() - metadata, err := w.metadata() - if err != nil { - return err - } - - metadataPath := path.Join(filepath.Dir(w.dbPath), db.MetadataFileName) - if err = metadata.Write(metadataPath); err != nil { - return err - } - - log.WithFields("path", w.dbPath).Info("database created") - log.WithFields("path", metadataPath).Debug("database metadata created") - - return nil -} - -func normalizeSeverity(metadata *grypeDB.VulnerabilityMetadata, reader grypeDB.VulnerabilityMetadataStoreReader) { - if metadata.Severity != "" && strings.ToLower(metadata.Severity) != "unknown" { - return - } - if !strings.HasPrefix(strings.ToLower(metadata.ID), "cve") { - return - } - if strings.Contains(metadata.RecordSource, grypeDB.NVDNamespace) { - return - } - m, err := reader.GetVulnerabilityMetadata(metadata.ID, grypeDB.NVDNamespace) - if err != nil { - log.WithFields("id", metadata.ID, "error", err).Warn("error fetching vulnerability metadata from NVD namespace") - return - } - if m == nil { - log.WithFields("id", metadata.ID).Trace("unable to find vulnerability metadata from NVD namespace") - return - } - - newSeverity := string(data.ParseSeverity(m.Severity)) - - log.WithFields("id", metadata.ID, "record-source", metadata.RecordSource, "from", metadata.Severity, "to", newSeverity).Trace("overriding irrelevant severity with data from NVD record") - - metadata.Severity = newSeverity -} diff --git a/pkg/process/v2/writer_test.go b/pkg/process/v2/writer_test.go deleted file mode 100644 index f0327707..00000000 --- a/pkg/process/v2/writer_test.go +++ /dev/null @@ -1,118 +0,0 @@ -package v2 - -import ( - "errors" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/anchore/grype-db/pkg/data" - grypeDB "github.com/anchore/grype/grype/db/v2" -) - -var _ grypeDB.VulnerabilityMetadataStoreReader = (*mockReader)(nil) - -type mockReader struct { - metadata *grypeDB.VulnerabilityMetadata - err error -} - -func newMockReader(sev string) *mockReader { - return &mockReader{ - metadata: &grypeDB.VulnerabilityMetadata{ - Severity: sev, - RecordSource: "nvdv2:cves", - }, - } -} - -func newDeadMockReader() *mockReader { - return &mockReader{ - err: errors.New("dead"), - } -} - -func (m mockReader) GetVulnerabilityMetadata(_, _ string) (*grypeDB.VulnerabilityMetadata, error) { - return m.metadata, m.err -} - -func (m mockReader) GetAllVulnerabilityMetadata() (*[]grypeDB.VulnerabilityMetadata, error) { - panic("implement me") -} - -func Test_normalizeSeverity(t *testing.T) { - - tests := []struct { - name string - initialSeverity string - recordSource string - cveID string - reader grypeDB.VulnerabilityMetadataStoreReader - expected data.Severity - }{ - { - name: "skip missing metadata", - initialSeverity: "", - recordSource: "test", - reader: &mockReader{}, - expected: "", - }, - { - name: "skip non-cve records metadata", - cveID: "GHSA-1234-1234-1234", - initialSeverity: "", - recordSource: "test", - reader: newDeadMockReader(), // should not be used - expected: "", - }, - { - name: "override empty severity", - initialSeverity: "", - recordSource: "test", - reader: newMockReader("low"), - expected: data.SeverityLow, - }, - { - name: "override unknown severity", - initialSeverity: "unknown", - recordSource: "test", - reader: newMockReader("low"), - expected: data.SeverityLow, - }, - { - name: "ignore record with severity already set", - initialSeverity: "Low", - recordSource: "test", - reader: newMockReader("critical"), // should not be used - expected: data.SeverityLow, - }, - { - name: "ignore nvd records", - initialSeverity: "Low", - recordSource: "nvdv2:cves", - reader: newDeadMockReader(), // should not be used - expected: data.SeverityLow, - }, - { - name: "db errors should not fail or modify the record", - initialSeverity: "", - recordSource: "test", - reader: newDeadMockReader(), - expected: "", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - record := &grypeDB.VulnerabilityMetadata{ - ID: "cve-2020-0000", - Severity: tt.initialSeverity, - RecordSource: tt.recordSource, - } - if tt.cveID != "" { - record.ID = tt.cveID - } - normalizeSeverity(record, tt.reader) - assert.Equal(t, string(tt.expected), record.Severity) - }) - } -} diff --git a/pkg/process/v3/processors.go b/pkg/process/v3/processors.go deleted file mode 100644 index e7d51f4e..00000000 --- a/pkg/process/v3/processors.go +++ /dev/null @@ -1,19 +0,0 @@ -package v3 - -import ( - "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype-db/pkg/process/processors" - "github.com/anchore/grype-db/pkg/process/v3/transformers/github" - "github.com/anchore/grype-db/pkg/process/v3/transformers/msrc" - "github.com/anchore/grype-db/pkg/process/v3/transformers/nvd" - "github.com/anchore/grype-db/pkg/process/v3/transformers/os" -) - -func Processors() []data.Processor { - return []data.Processor{ - processors.NewGitHubProcessor(github.Transform), - processors.NewMSRCProcessor(msrc.Transform), - processors.NewNVDProcessor(nvd.Transform), - processors.NewOSProcessor(os.Transform), - } -} diff --git a/pkg/process/v3/transformers/entry.go b/pkg/process/v3/transformers/entry.go deleted file mode 100644 index ae8f3715..00000000 --- a/pkg/process/v3/transformers/entry.go +++ /dev/null @@ -1,22 +0,0 @@ -package transformers - -import ( - "github.com/anchore/grype-db/pkg/data" - grypeDB "github.com/anchore/grype/grype/db/v3" -) - -func NewEntries(vs []grypeDB.Vulnerability, metadata grypeDB.VulnerabilityMetadata) []data.Entry { - entries := []data.Entry{ - { - DBSchemaVersion: grypeDB.SchemaVersion, - Data: metadata, - }, - } - for _, vuln := range vs { - entries = append(entries, data.Entry{ - DBSchemaVersion: grypeDB.SchemaVersion, - Data: vuln, - }) - } - return entries -} diff --git a/pkg/process/v3/transformers/github/test-fixtures/github-github-npm-0.json b/pkg/process/v3/transformers/github/test-fixtures/github-github-npm-0.json deleted file mode 100644 index b0a7d1e9..00000000 --- a/pkg/process/v3/transformers/github/test-fixtures/github-github-npm-0.json +++ /dev/null @@ -1,31 +0,0 @@ - -{ - "Advisory": { - "CVE": [ - "CVE-2020-14000" - ], - "FixedIn": [ - { - "ecosystem": "npm", - "identifier": "0.2.0-prerelease.20200714185213", - "name": "scratch-vm", - "namespace": "github:npm", - "range": "<= 0.2.0-prerelease.20200709173451" - } - ], - "Metadata": { - "CVE": [ - "CVE-2020-14000" - ] - }, - "Severity": "High", - "Summary": "Remote Code Execution in scratch-vm", - "ghsaId": "GHSA-vc9j-fhvv-8vrf", - "namespace": "github:npm", - "url": "https://github.com/advisories/GHSA-vc9j-fhvv-8vrf", - "withdrawn": null - }, - "Vulnerability": {} -} - - diff --git a/pkg/process/v3/transformers/github/test-fixtures/github-github-python-0.json b/pkg/process/v3/transformers/github/test-fixtures/github-github-python-0.json deleted file mode 100644 index ad14aa60..00000000 --- a/pkg/process/v3/transformers/github/test-fixtures/github-github-python-0.json +++ /dev/null @@ -1,58 +0,0 @@ -[ - { - "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/v3/transformers/github/test-fixtures/github-github-python-1.json b/pkg/process/v3/transformers/github/test-fixtures/github-github-python-1.json deleted file mode 100644 index bfa84922..00000000 --- a/pkg/process/v3/transformers/github/test-fixtures/github-github-python-1.json +++ /dev/null @@ -1,43 +0,0 @@ - -{ - "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", - "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/v3/transformers/github/test-fixtures/github-withdrawn.json b/pkg/process/v3/transformers/github/test-fixtures/github-withdrawn.json deleted file mode 100644 index 04995e38..00000000 --- a/pkg/process/v3/transformers/github/test-fixtures/github-withdrawn.json +++ /dev/null @@ -1,29 +0,0 @@ - -{ - "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/v3/transformers/github/test-fixtures/multiple-fixed-in-names.json b/pkg/process/v3/transformers/github/test-fixtures/multiple-fixed-in-names.json deleted file mode 100644 index ac1ef982..00000000 --- a/pkg/process/v3/transformers/github/test-fixtures/multiple-fixed-in-names.json +++ /dev/null @@ -1,43 +0,0 @@ - -{ - "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/v3/transformers/github/transform.go b/pkg/process/v3/transformers/github/transform.go deleted file mode 100644 index f9ec70cf..00000000 --- a/pkg/process/v3/transformers/github/transform.go +++ /dev/null @@ -1,99 +0,0 @@ -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/v3/transformers" - "github.com/anchore/grype-db/pkg/provider/unmarshal" - grypeDB "github.com/anchore/grype/grype/db/v3" -) - -const ( - // TODO: tech debt from a previous design - feed = "github" -) - -func Transform(vulnerability unmarshal.GitHubAdvisory) ([]data.Entry, error) { - var allVulns []grypeDB.Vulnerability - - // Exclude entries marked as withdrawn - if vulnerability.Advisory.Withdrawn != nil { - return nil, nil - } - - recordSource := grypeDB.RecordSource(feed, vulnerability.Advisory.Namespace) - entryNamespace, err := grypeDB.NamespaceForFeedGroup(feed, vulnerability.Advisory.Namespace) - if err != nil { - return nil, err - } - - // there may be multiple packages indicated within the FixedIn field, we should make - // separate vulnerability entries (one for each name|namespaces combo) while merging - // constraint ranges as they are found. - for idx, fixedInEntry := range vulnerability.Advisory.FixedIn { - constraint := common.EnforceSemVerConstraint(fixedInEntry.Range) - - var versionFormat string - switch vulnerability.Advisory.Namespace { - case "github:python": - versionFormat = "python" - default: - versionFormat = "unknown" - } - - // create vulnerability entry - allVulns = append(allVulns, grypeDB.Vulnerability{ - ID: vulnerability.Advisory.GhsaID, - VersionConstraint: constraint, - VersionFormat: versionFormat, - RelatedVulnerabilities: getRelatedVulnerabilities(vulnerability), - PackageName: fixedInEntry.Name, - Namespace: entryNamespace, - Fix: getFix(vulnerability, idx), - }) - } - - // create vulnerability metadata entry (a single entry keyed off of the vulnerability ID) - metadata := grypeDB.VulnerabilityMetadata{ - ID: vulnerability.Advisory.GhsaID, - DataSource: vulnerability.Advisory.URL, - Namespace: entryNamespace, - RecordSource: recordSource, - Severity: vulnerability.Advisory.Severity, - URLs: []string{vulnerability.Advisory.URL}, - Description: vulnerability.Advisory.Summary, - } - - return transformers.NewEntries(allVulns, metadata), nil -} - -func getFix(entry unmarshal.GitHubAdvisory, idx int) grypeDB.Fix { - fixedInEntry := entry.Advisory.FixedIn[idx] - - var fixedInVersions []string - fixedInVersion := common.CleanFixedInVersion(fixedInEntry.Identifier) - if fixedInVersion != "" { - fixedInVersions = append(fixedInVersions, fixedInVersion) - } - - fixState := grypeDB.NotFixedState - if len(fixedInVersions) > 0 { - fixState = grypeDB.FixedState - } - - return grypeDB.Fix{ - Versions: fixedInVersions, - State: fixState, - } -} - -func getRelatedVulnerabilities(entry unmarshal.GitHubAdvisory) []grypeDB.VulnerabilityReference { - vulns := make([]grypeDB.VulnerabilityReference, len(entry.Advisory.CVE)) - for idx, cve := range entry.Advisory.CVE { - vulns[idx] = grypeDB.VulnerabilityReference{ - ID: cve, - Namespace: grypeDB.NVDNamespace, - } - } - return vulns -} diff --git a/pkg/process/v3/transformers/github/transform_test.go b/pkg/process/v3/transformers/github/transform_test.go deleted file mode 100644 index ce0bc28c..00000000 --- a/pkg/process/v3/transformers/github/transform_test.go +++ /dev/null @@ -1,198 +0,0 @@ -package github - -import ( - "os" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - testUtils "github.com/anchore/grype-db/pkg/process/tests" - "github.com/anchore/grype-db/pkg/provider/unmarshal" - grypeDB "github.com/anchore/grype/grype/db/v3" -) - -func TestUnmarshalGitHubEntries(t *testing.T) { - f, err := os.Open("test-fixtures/github-github-python-0.json") - require.NoError(t, err) - defer testUtils.CloseFile(f) - - entries, err := unmarshal.GitHubAdvisoryEntries(f) - require.NoError(t, err) - - assert.Len(t, entries, 2) - -} - -func TestParseGitHubEntry(t *testing.T) { - expectedVulns := []grypeDB.Vulnerability{ - { - ID: "GHSA-p5wr-vp8g-q5p4", - VersionConstraint: ">=4.0,<4.3.12", - VersionFormat: "python", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2017-5524", - Namespace: grypeDB.NVDNamespace, - }, - }, - PackageName: "Plone", - Namespace: "github:python", - Fix: grypeDB.Fix{ - Versions: []string{"4.3.12"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "GHSA-p5wr-vp8g-q5p4", - VersionConstraint: ">=5.1a1,<5.1b1", - VersionFormat: "python", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2017-5524", - Namespace: grypeDB.NVDNamespace, - }, - }, - PackageName: "Plone", - Namespace: "github:python", - Fix: grypeDB.Fix{ - Versions: []string{"5.1b1"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "GHSA-p5wr-vp8g-q5p4", - VersionConstraint: ">=5.0rc1,<5.0.7", - VersionFormat: "python", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2017-5524", - Namespace: grypeDB.NVDNamespace, - }, - }, - PackageName: "Plone", - Namespace: "github:python", - Fix: grypeDB.Fix{ - Versions: []string{"5.0.7"}, - State: grypeDB.FixedState, - }, - }, - } - - expectedMetadata := grypeDB.VulnerabilityMetadata{ - ID: "GHSA-p5wr-vp8g-q5p4", - Namespace: "github:python", - RecordSource: "github:github:python", - DataSource: "https://github.com/advisories/GHSA-p5wr-vp8g-q5p4", - Severity: "Medium", - URLs: []string{"https://github.com/advisories/GHSA-p5wr-vp8g-q5p4"}, - Description: "Moderate severity vulnerability that affects Plone", - } - - f, err := os.Open("test-fixtures/github-github-python-1.json") - require.NoError(t, err) - defer testUtils.CloseFile(f) - - entries, err := unmarshal.GitHubAdvisoryEntries(f) - require.NoError(t, err) - - require.Len(t, entries, 1) - entry := entries[0] - - dataEntries, err := Transform(entry) - assert.NoError(t, err) - - var vulns []grypeDB.Vulnerability - for _, entry := range dataEntries { - switch vuln := entry.Data.(type) { - case grypeDB.Vulnerability: - vulns = append(vulns, vuln) - case grypeDB.VulnerabilityMetadata: - assert.Equal(t, expectedMetadata, vuln) - default: - t.Fatalf("unexpected condition: data entry does not have a vulnerability or a metadata") - } - } - - // check vulnerability - assert.Len(t, vulns, len(expectedVulns)) - - if diff := cmp.Diff(expectedVulns, vulns); diff != "" { - t.Errorf("vulnerabilities do not match (-want +got):\n%s", diff) - } -} - -func TestDefaultVersionFormatNpmGitHubEntry(t *testing.T) { - expectedVuln := grypeDB.Vulnerability{ - ID: "GHSA-vc9j-fhvv-8vrf", - VersionConstraint: "<=0.2.0-prerelease.20200709173451", - VersionFormat: "unknown", // TODO: this should reference a format, yes? (not a string) - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2020-14000", - Namespace: grypeDB.NVDNamespace, - }, - }, - PackageName: "scratch-vm", - Namespace: "github:npm", - Fix: grypeDB.Fix{ - Versions: []string{"0.2.0-prerelease.20200714185213"}, - State: grypeDB.FixedState, - }, - } - - expectedMetadata := grypeDB.VulnerabilityMetadata{ - ID: "GHSA-vc9j-fhvv-8vrf", - Namespace: "github:npm", - RecordSource: "github:github:npm", - DataSource: "https://github.com/advisories/GHSA-vc9j-fhvv-8vrf", - Severity: "High", - URLs: []string{"https://github.com/advisories/GHSA-vc9j-fhvv-8vrf"}, - Description: "Remote Code Execution in scratch-vm", - } - - f, err := os.Open("test-fixtures/github-github-npm-0.json") - require.NoError(t, err) - defer testUtils.CloseFile(f) - - entries, err := unmarshal.GitHubAdvisoryEntries(f) - require.NoError(t, err) - - require.Len(t, entries, 1) - - entry := entries[0] - - dataEntries, err := Transform(entry) - assert.NoError(t, err) - - for _, entry := range dataEntries { - switch vuln := entry.Data.(type) { - case grypeDB.Vulnerability: - assert.Equal(t, expectedVuln, vuln) - case grypeDB.VulnerabilityMetadata: - assert.Equal(t, expectedMetadata, vuln) - default: - t.Fatalf("unexpected condition: data entry does not have a vulnerability or a metadata") - } - } - - // check vulnerability - assert.Len(t, dataEntries, 2) -} - -func TestFilterWithdrawnEntries(t *testing.T) { - f, err := os.Open("test-fixtures/github-withdrawn.json") - require.NoError(t, err) - defer testUtils.CloseFile(f) - - entries, err := unmarshal.GitHubAdvisoryEntries(f) - require.NoError(t, err) - - require.Len(t, entries, 1) - entry := entries[0] - - dataEntries, err := Transform(entry) - assert.NoError(t, err) - assert.Nil(t, dataEntries) -} diff --git a/pkg/process/v3/transformers/msrc/transform.go b/pkg/process/v3/transformers/msrc/transform.go deleted file mode 100644 index d1a1ea48..00000000 --- a/pkg/process/v3/transformers/msrc/transform.go +++ /dev/null @@ -1,91 +0,0 @@ -package msrc - -import ( - "fmt" - - "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype-db/pkg/process/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" -) - -const ( - // TODO: tech debt from a previous design - feed = "microsoft" - groupPrefix = "msrc" -) - -// Transform gets called by the parser, which consumes entries from the JSON files previously pulled. Each VulnDBVulnerability represents -// a single unmarshalled entry from the feed service -func Transform(vulnerability unmarshal.MSRCVulnerability) ([]data.Entry, error) { - group := fmt.Sprintf("%s:%s", groupPrefix, vulnerability.Product.ID) - recordSource := grypeDB.RecordSource(feed, group) - entryNamespace, err := grypeDB.NamespaceForFeedGroup(feed, group) - if err != nil { - return nil, err - } - - // In anchore-enterprise windows analyzer, "base" represents unpatched windows images (images with no KBs). - // If a vulnerability exists for a Microsoft Product ID and the image has no KBs (which are patches), - // then the image must be vulnerable to the image. - //nolint:gocritic - versionConstraint := append(vulnerability.Vulnerable, "base") - - allVulns := []grypeDB.Vulnerability{ - { - ID: vulnerability.ID, - VersionConstraint: common.OrConstraints(versionConstraint...), - VersionFormat: "kb", - PackageName: vulnerability.Product.ID, - Namespace: entryNamespace, - Fix: getFix(vulnerability), - }, - } - - // create vulnerability metadata entry (a single entry keyed off of the vulnerability ID) - metadata := grypeDB.VulnerabilityMetadata{ - ID: vulnerability.ID, - DataSource: vulnerability.Link, - Namespace: entryNamespace, - RecordSource: recordSource, - Severity: vulnerability.Severity, - URLs: []string{vulnerability.Link}, - // There is no description for vulnerabilities from the feed service - // summary gives something like "windows information disclosure vulnerability" - //Description: vulnerability.Summary, - Cvss: []grypeDB.Cvss{ - { - Metrics: grypeDB.CvssMetrics{BaseScore: vulnerability.Cvss.BaseScore}, - Vector: vulnerability.Cvss.Vector, - }, - }, - } - - return transformers.NewEntries(allVulns, metadata), nil -} - -func getFix(entry unmarshal.MSRCVulnerability) grypeDB.Fix { - fixedInVersion := fixedInKB(entry) - fixState := grypeDB.FixedState - - if fixedInVersion == "" { - fixState = grypeDB.NotFixedState - } - - return grypeDB.Fix{ - Versions: []string{fixedInVersion}, - State: fixState, - } -} - -// fixedInKB finds the "latest" patch (KB id) amongst the available microsoft patches and returns it -// if the "latest" patch cannot be found, an error is returned -func fixedInKB(vulnerability unmarshal.MSRCVulnerability) string { - for _, fixedIn := range vulnerability.FixedIn { - if fixedIn.IsLatest { - return fixedIn.ID - } - } - return "" -} diff --git a/pkg/process/v3/transformers/msrc/transform_test.go b/pkg/process/v3/transformers/msrc/transform_test.go deleted file mode 100644 index 4bb431c8..00000000 --- a/pkg/process/v3/transformers/msrc/transform_test.go +++ /dev/null @@ -1,120 +0,0 @@ -package msrc - -import ( - "os" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - testUtils "github.com/anchore/grype-db/pkg/process/tests" - "github.com/anchore/grype-db/pkg/provider/unmarshal" - grypeDB "github.com/anchore/grype/grype/db/v3" -) - -func TestUnmarshalMsrcVulnerabilities(t *testing.T) { - f, err := os.Open("test-fixtures/microsoft-msrc-0.json") - require.NoError(t, err) - defer testUtils.CloseFile(f) - - entries, err := unmarshal.MSRCVulnerabilityEntries(f) - require.NoError(t, err) - - assert.Equal(t, len(entries), 2) -} - -func TestParseMSRCEntry(t *testing.T) { - expectedVulns := []struct { - vulnerability grypeDB.Vulnerability - metadata grypeDB.VulnerabilityMetadata - }{ - { - vulnerability: grypeDB.Vulnerability{ - ID: "CVE-2019-0671", - VersionConstraint: `4480961 || 4483229 || 4487026 || 4489882 || base`, - VersionFormat: "kb", - PackageName: "10852", - Namespace: "msrc:10852", - Fix: grypeDB.Fix{ - Versions: []string{"4516044"}, - State: grypeDB.FixedState, - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2019-0671", - Severity: "High", - DataSource: "https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2019-0671", - URLs: []string{"https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2019-0671"}, - Description: "", - RecordSource: "microsoft:msrc:10852", - Namespace: "msrc:10852", - Cvss: []grypeDB.Cvss{ - { - Metrics: grypeDB.CvssMetrics{ - BaseScore: 7.8, - ImpactScore: nil, - }, - Vector: "CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H/E:P/RL:O/RC:C", - }, - }, - }, - }, - { - vulnerability: grypeDB.Vulnerability{ - ID: "CVE-2018-8116", - VersionConstraint: `3213986 || 4013429 || 4015217 || 4019472 || 4022715 || 4025339 || 4034658 || 4038782 || 4041691 || 4048953 || 4053579 || 4056890 || 4074590 || 4088787 || base`, - VersionFormat: "kb", - PackageName: "10852", - Namespace: "msrc:10852", - Fix: grypeDB.Fix{ - Versions: []string{"4345418"}, - State: grypeDB.FixedState, - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2018-8116", - Namespace: "msrc:10852", - DataSource: "https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2018-8116", - RecordSource: "microsoft:msrc:10852", - Severity: "Medium", - URLs: []string{"https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2018-8116"}, - Description: "", - Cvss: []grypeDB.Cvss{ - { - Metrics: grypeDB.CvssMetrics{ - BaseScore: 4.4, - ImpactScore: nil, - }, - Vector: "CVSS:3.0/AV:L/AC:H/PR:L/UI:R/S:U/C:N/I:N/A:H/E:P/RL:O/RC:C", - }, - }, - }, - }, - } - - f, err := os.Open("test-fixtures/microsoft-msrc-0.json") - require.NoError(t, err) - defer testUtils.CloseFile(f) - - entries, err := unmarshal.MSRCVulnerabilityEntries(f) - require.NoError(t, err) - - require.Equal(t, len(entries), 2) - - for idx, entry := range entries { - dataEntries, err := Transform(entry) - require.NoError(t, err) - assert.Len(t, dataEntries, 2) - expected := expectedVulns[idx] - for _, entry := range dataEntries { - switch vuln := entry.Data.(type) { - case grypeDB.Vulnerability: - assert.Equal(t, expected.vulnerability, vuln) - case grypeDB.VulnerabilityMetadata: - assert.Equal(t, expected.metadata, vuln) - default: - t.Fatalf("unexpected condition: data entry does not have a vulnerability or a metadata") - } - } - } -} diff --git a/pkg/process/v3/transformers/nvd/test-fixtures/compound-pkg.json b/pkg/process/v3/transformers/nvd/test-fixtures/compound-pkg.json deleted file mode 100644 index 8e658dcd..00000000 --- a/pkg/process/v3/transformers/nvd/test-fixtures/compound-pkg.json +++ /dev/null @@ -1,115 +0,0 @@ -{ - "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/v3/transformers/nvd/test-fixtures/invalid_cpe.json b/pkg/process/v3/transformers/nvd/test-fixtures/invalid_cpe.json deleted file mode 100644 index eac2ebd4..00000000 --- a/pkg/process/v3/transformers/nvd/test-fixtures/invalid_cpe.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "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/v3/transformers/nvd/test-fixtures/single-package-multi-distro.json b/pkg/process/v3/transformers/nvd/test-fixtures/single-package-multi-distro.json deleted file mode 100644 index ed108475..00000000 --- a/pkg/process/v3/transformers/nvd/test-fixtures/single-package-multi-distro.json +++ /dev/null @@ -1,174 +0,0 @@ -{ - "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/v3/transformers/nvd/test-fixtures/unmarshal-test.json b/pkg/process/v3/transformers/nvd/test-fixtures/unmarshal-test.json deleted file mode 100644 index 2dc698fa..00000000 --- a/pkg/process/v3/transformers/nvd/test-fixtures/unmarshal-test.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "cve": { - "id": "CVE-2003-0349", - "sourceIdentifier": "cve@mitre.org", - "published": "2003-07-24T04:00:00.000", - "lastModified": "2018-10-12T21:32:41.083", - "vulnStatus": "Modified", - "descriptions": [ - { - "lang": "en", - "value": "Buffer overflow in the streaming media component for logging multicast requests in the ISAPI for the logging capability of Microsoft Windows Media Services (nsiislog.dll), as installed in IIS 5.0, allows remote attackers to execute arbitrary code via a large POST request to nsiislog.dll." - }, - { - "lang": "es", - "value": "Desbordamiento de búfer en el componente de secuenciamiento (streaming) de medios para registrar peticiones de multidifusión en la librería ISAPI de la capacidad de registro (logging) de Microsoft Windows Media Services (nsiislog.dll), como el instalado en IIS 5.9, permite a atacantes remotos ejecutar código arbitrario mediante una petición POST larga a nsiislog.dll." - } - ], - "metrics": { - "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": false, - "obtainAllPrivilege": false, - "obtainUserPrivilege": true, - "obtainOtherPrivilege": false, - "userInteractionRequired": false - } - ] - }, - "weaknesses": [ - { - "source": "nvd@nist.gov", - "type": "Primary", - "description": [ - { - "lang": "en", - "value": "NVD-CWE-Other" - } - ] - } - ], - "configurations": [ - { - "nodes": [ - { - "operator": "OR", - "negate": false, - "cpeMatch": [ - { - "vulnerable": true, - "criteria": "cpe:2.3:o:microsoft:windows_2000:*:*:*:*:*:*:*:*", - "matchCriteriaId": "4E545C63-FE9C-4CA1-AF0F-D999D84D2AFD" - } - ] - } - ] - } - ], - "references": [ - { - "url": "http://marc.info/?l=bugtraq&m=105665030925504&w=2", - "source": "cve@mitre.org" - }, - { - "url": "http://securitytracker.com/id?1007059", - "source": "cve@mitre.org" - }, - { - "url": "http://www.kb.cert.org/vuls/id/113716", - "source": "cve@mitre.org", - "tags": [ - "US Government Resource" - ] - }, - { - "url": "http://www.ntbugtraq.com/default.asp?pid=36&sid=1&A2=ind0306&L=NTBUGTRAQ&P=R4563", - "source": "cve@mitre.org", - "tags": [ - "Exploit", - "Patch", - "Vendor Advisory" - ] - }, - { - "url": "https://docs.microsoft.com/en-us/security-updates/securitybulletins/2003/ms03-022", - "source": "cve@mitre.org" - }, - { - "url": "https://oval.cisecurity.org/repository/search/definition/oval%3Aorg.mitre.oval%3Adef%3A938", - "source": "cve@mitre.org" - } - ] - } -} diff --git a/pkg/process/v3/transformers/nvd/transform.go b/pkg/process/v3/transformers/nvd/transform.go deleted file mode 100644 index 3b3f484c..00000000 --- a/pkg/process/v3/transformers/nvd/transform.go +++ /dev/null @@ -1,95 +0,0 @@ -package nvd - -import ( - "fmt" - - "github.com/anchore/grype-db/internal" - "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype-db/pkg/process/v3/transformers" - "github.com/anchore/grype-db/pkg/provider/unmarshal" - "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd" - grypeDB "github.com/anchore/grype/grype/db/v3" -) - -const ( - // TODO: tech debt from a previous design - feed = "nvdv2" - group = "nvdv2:cves" -) - -func Transform(vulnerability unmarshal.NVDVulnerability) ([]data.Entry, error) { - recordSource := grypeDB.RecordSource(feed, group) - entryNamespace, err := grypeDB.NamespaceForFeedGroup(feed, group) - if err != nil { - return nil, err - } - - uniquePkgs := findUniquePkgs(vulnerability.Configurations...) - - if err != nil { - return nil, fmt.Errorf("unable to parse NVD entry: %w", err) - } - - // extract all links - var links []string - for _, externalRefs := range vulnerability.References { - // TODO: should we capture other information here? - if externalRefs.URL != "" { - links = append(links, externalRefs.URL) - } - } - - // duplicate the vulnerabilities based on the set of unique packages the vulnerability is for - var allVulns []grypeDB.Vulnerability - for _, p := range uniquePkgs.All() { - matches := uniquePkgs.Matches(p) - cpes := internal.NewStringSet() - for _, m := range matches { - cpes.Add(m.Criteria) - } - - // create vulnerability entry - allVulns = append(allVulns, grypeDB.Vulnerability{ - ID: vulnerability.ID, - VersionConstraint: buildConstraints(uniquePkgs.Matches(p)), - VersionFormat: "unknown", - PackageName: p.Product, - Namespace: entryNamespace, - CPEs: cpes.ToSlice(), - Fix: grypeDB.Fix{ - State: grypeDB.UnknownFixState, - }, - }) - } - - // create vulnerability metadata entry (a single entry keyed off of the vulnerability ID) - allCVSS := vulnerability.CVSS() - metadata := grypeDB.VulnerabilityMetadata{ - ID: vulnerability.ID, - DataSource: "https://nvd.nist.gov/vuln/detail/" + vulnerability.ID, - Namespace: entryNamespace, - RecordSource: recordSource, - Severity: nvd.CvssSummaries(allCVSS).Sorted().Severity(), - URLs: links, - Description: vulnerability.Description(), - Cvss: getCvss(allCVSS...), - } - - return transformers.NewEntries(allVulns, metadata), nil -} - -func getCvss(cvss ...nvd.CvssSummary) []grypeDB.Cvss { - var results []grypeDB.Cvss - for _, c := range cvss { - results = append(results, grypeDB.Cvss{ - Version: c.Version, - Vector: c.Vector, - Metrics: grypeDB.CvssMetrics{ - BaseScore: c.BaseScore, - ExploitabilityScore: c.ExploitabilityScore, - ImpactScore: c.ImpactScore, - }, - }) - } - return results -} diff --git a/pkg/process/v3/transformers/nvd/transform_test.go b/pkg/process/v3/transformers/nvd/transform_test.go deleted file mode 100644 index 1f136144..00000000 --- a/pkg/process/v3/transformers/nvd/transform_test.go +++ /dev/null @@ -1,257 +0,0 @@ -package nvd - -import ( - "os" - "testing" - - "github.com/go-test/deep" - "github.com/google/go-cmp/cmp" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - testUtils "github.com/anchore/grype-db/pkg/process/tests" - "github.com/anchore/grype-db/pkg/provider/unmarshal" - grypeDB "github.com/anchore/grype/grype/db/v3" -) - -func TestUnmarshalNVDVulnerabilitiesEntries(t *testing.T) { - f, err := os.Open("test-fixtures/unmarshal-test.json") - require.NoError(t, err) - defer testUtils.CloseFile(f) - - entries, err := unmarshal.NvdVulnerabilityEntries(f) - require.NoError(t, err) - - assert.Len(t, entries, 1) -} - -func TestParseAllNVDVulnerabilityEntries(t *testing.T) { - - tests := []struct { - name string - numEntries int - fixture string - vulns []grypeDB.Vulnerability - metadata grypeDB.VulnerabilityMetadata - }{ - { - name: "AppVersionRange", - numEntries: 1, - fixture: "test-fixtures/version-range.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2018-5487", - PackageName: "oncommand_unified_manager", - VersionConstraint: ">= 7.2, <= 7.3", - VersionFormat: "unknown", // TODO: this should reference a format, yes? (not a string) - Namespace: "nvd", - CPEs: []string{"cpe:2.3:a:netapp:oncommand_unified_manager:*:*:*:*:*:*:*:*"}, - Fix: grypeDB.Fix{ - State: "unknown", - }, - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2018-5487", - DataSource: "https://nvd.nist.gov/vuln/detail/CVE-2018-5487", - Namespace: "nvd", - RecordSource: "nvdv2:nvdv2:cves", - Severity: "Critical", - URLs: []string{"https://security.netapp.com/advisory/ntap-20180523-0001/"}, - 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.", - Cvss: []grypeDB.Cvss{ - { - Metrics: grypeDB.NewCvssMetrics( - 7.5, - 10, - 6.4, - ), - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P", - Version: "2.0", - }, - { - Metrics: grypeDB.NewCvssMetrics( - 9.8, - 3.9, - 5.9, - ), - Vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", - Version: "3.0", - }, - }, - }, - }, - { - name: "App+OS", - numEntries: 1, - fixture: "test-fixtures/single-package-multi-distro.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2018-1000222", - PackageName: "libgd", - VersionConstraint: "= 2.2.5", - VersionFormat: "unknown", // TODO: this should reference a format, yes? (not a string) - Namespace: "nvd", - CPEs: []string{"cpe:2.3:a:libgd:libgd:2.2.5:*:*:*:*:*:*:*"}, - Fix: grypeDB.Fix{ - State: "unknown", - }, - }, - // TODO: Question: should this match also the OS's? (as in the vulnerable_cpes list)... this seems wrong! - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2018-1000222", - DataSource: "https://nvd.nist.gov/vuln/detail/CVE-2018-1000222", - Namespace: "nvd", - RecordSource: "nvdv2:nvdv2:cves", - Severity: "High", - URLs: []string{"https://github.com/libgd/libgd/issues/447", "https://lists.debian.org/debian-lts-announce/2019/01/msg00028.html", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/3CZ2QADQTKRHTGB2AHD7J4QQNDLBEMM6/", "https://security.gentoo.org/glsa/201903-18", "https://usn.ubuntu.com/3755-1/"}, - 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.", - Cvss: []grypeDB.Cvss{ - { - Metrics: grypeDB.NewCvssMetrics( - 6.8, - 8.6, - 6.4, - ), - Vector: "AV:N/AC:M/Au:N/C:P/I:P/A:P", - Version: "2.0", - }, - { - Metrics: grypeDB.NewCvssMetrics( - 8.8, - 2.8, - 5.9, - ), - Vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", - Version: "3.0", - }, - }, - }, - }, - { - name: "AppCompoundVersionRange", - numEntries: 1, - fixture: "test-fixtures/compound-pkg.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2018-10189", - PackageName: "mautic", - VersionConstraint: ">= 1.0.0, <= 1.4.1 || >= 2.0.0, < 2.13.0", - VersionFormat: "unknown", - Namespace: "nvd", - CPEs: []string{"cpe:2.3:a:mautic:mautic:*:*:*:*:*:*:*:*"}, // note: entry was dedupicated - Fix: grypeDB.Fix{ - State: "unknown", - }, - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2018-10189", - DataSource: "https://nvd.nist.gov/vuln/detail/CVE-2018-10189", - Namespace: "nvd", - RecordSource: "nvdv2:nvdv2:cves", - Severity: "High", - URLs: []string{"https://github.com/mautic/mautic/releases/tag/2.13.0"}, - 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.", - Cvss: []grypeDB.Cvss{ - { - Metrics: grypeDB.NewCvssMetrics( - 5, - 10, - 2.9, - ), - Vector: "AV:N/AC:L/Au:N/C:P/I:N/A:N", - Version: "2.0", - }, - { - Metrics: grypeDB.NewCvssMetrics( - 7.5, - 3.9, - 3.6, - ), - Vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", - Version: "3.0", - }, - }, - }, - }, - { - // we always keep the metadata even though there are no vulnerability entries for it - name: "InvalidCPE", - numEntries: 1, - fixture: "test-fixtures/invalid_cpe.json", - vulns: nil, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2015-8978", - Namespace: "nvd", - DataSource: "https://nvd.nist.gov/vuln/detail/CVE-2015-8978", - RecordSource: "nvdv2:nvdv2:cves", - Severity: "High", - URLs: []string{ - "http://cpansearch.perl.org/src/PHRED/SOAP-Lite-1.20/Changes", - "http://www.securityfocus.com/bid/94487", - }, - 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.", - Cvss: []grypeDB.Cvss{ - { - Metrics: grypeDB.NewCvssMetrics( - 5, - 10, - 2.9, - ), - Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P", - Version: "2.0", - }, - { - Metrics: grypeDB.NewCvssMetrics( - 7.5, - 3.9, - 3.6, - ), - Vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", - Version: "3.0", - }, - }, - }, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - f, err := os.Open(test.fixture) - require.NoError(t, err) - t.Cleanup(func() { - assert.NoError(t, f.Close()) - }) - - entries, err := unmarshal.NvdVulnerabilityEntries(f) - require.NoError(t, err) - - var vulns []grypeDB.Vulnerability - for _, entry := range entries { - dataEntries, err := Transform(entry.Cve) - require.NoError(t, err) - - for _, entry := range dataEntries { - switch vuln := entry.Data.(type) { - case grypeDB.Vulnerability: - vulns = append(vulns, vuln) - case grypeDB.VulnerabilityMetadata: - // check metadata - if diff := deep.Equal(test.metadata, vuln); diff != nil { - for _, d := range diff { - t.Errorf("metadata diff: %+v", d) - } - } - default: - t.Fatalf("unexpected condition: data entry does not have a vulnerability or a metadata") - } - } - } - - if diff := cmp.Diff(test.vulns, vulns); diff != "" { - t.Errorf("vulnerabilities do not match (-want +got):\n%s", diff) - } - }) - } -} diff --git a/pkg/process/v3/transformers/nvd/unique_pkg.go b/pkg/process/v3/transformers/nvd/unique_pkg.go deleted file mode 100644 index 4c994341..00000000 --- a/pkg/process/v3/transformers/nvd/unique_pkg.go +++ /dev/null @@ -1,115 +0,0 @@ -package nvd - -import ( - "fmt" - "strings" - - "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/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(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 only interested in applications, not hardware or operating systems - if c.Part() != cpe.Application { - return nil, nil - } - - return &pkgCandidate{ - Product: c.Product().String(), - Vendor: c.Vendor().String(), - TargetSoftware: c.TargetSw().String(), - }, nil -} - -func findUniquePkgs(cfgs ...nvd.Configuration) uniquePkgTracker { - set := newUniquePkgTracker() - for _, c := range cfgs { - _findUniquePkgs(set, c.Nodes...) - } - return set -} - -func _findUniquePkgs(set uniquePkgTracker, ns ...nvd.Node) { - if len(ns) == 0 { - return - } - for _, node := range ns { - for _, match := range node.CpeMatch { - candidate, err := newPkgCandidate(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 pkg: %v", err) - continue - } - if candidate != nil { - set.Add(*candidate, match) - } - } - } -} - -func buildConstraints(matches []nvd.CpeMatch) string { - constraints := make([]string, 0) - for _, match := range matches { - constraints = append(constraints, buildConstraint(match)) - } - return common.OrConstraints(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() - if version != ANY && version != NA { - constraints = append(constraints, fmt.Sprintf("= %s", version)) - } - } - - return strings.Join(constraints, ", ") -} diff --git a/pkg/process/v3/transformers/nvd/unique_pkg_test.go b/pkg/process/v3/transformers/nvd/unique_pkg_test.go deleted file mode 100644 index dc662c36..00000000 --- a/pkg/process/v3/transformers/nvd/unique_pkg_test.go +++ /dev/null @@ -1,353 +0,0 @@ -package nvd - -import ( - "testing" - - "github.com/sergi/go-diff/diffmatchpatch" - - "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd" -) - -func newUniquePkgTrackerFromSlice(candidates []pkgCandidate) uniquePkgTracker { - set := newUniquePkgTracker() - for _, c := range candidates { - set[c] = nil - } - return set -} - -func TestFindUniquePkgs(t *testing.T) { - tests := []struct { - name string - nodes []nvd.Node - 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", - }, - }), - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - actual := findUniquePkgs(nvd.Configuration{Nodes: test.nodes}) - 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: '%s'", test.expected) - t.Errorf("Got : '%s'", actual) - t.Errorf("Diff : '%s'", dmp.DiffPrettyText(diffs)) - } - }) - } - -} diff --git a/pkg/process/v3/transformers/nvd/unique_pkg_tracker.go b/pkg/process/v3/transformers/nvd/unique_pkg_tracker.go deleted file mode 100644 index 2b7e405d..00000000 --- a/pkg/process/v3/transformers/nvd/unique_pkg_tracker.go +++ /dev/null @@ -1,64 +0,0 @@ -package nvd - -import ( - "sort" - - "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd" -) - -type uniquePkgTracker map[pkgCandidate][]nvd.CpeMatch - -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 (s uniquePkgTracker) Matches(i pkgCandidate) []nvd.CpeMatch { - return s[i] -} - -func (s uniquePkgTracker) Add(i pkgCandidate, match nvd.CpeMatch) { - if _, ok := s[i]; !ok { - s[i] = make([]nvd.CpeMatch, 0) - } - s[i] = append(s[i], match) -} - -func (s uniquePkgTracker) Remove(i pkgCandidate) { - delete(s, i) -} - -func (s uniquePkgTracker) Contains(i pkgCandidate) bool { - _, ok := s[i] - return ok -} - -func (s uniquePkgTracker) All() []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/v3/transformers/os/test-fixtures/alpine-3.9.json b/pkg/process/v3/transformers/os/test-fixtures/alpine-3.9.json deleted file mode 100644 index b9d84395..00000000 --- a/pkg/process/v3/transformers/os/test-fixtures/alpine-3.9.json +++ /dev/null @@ -1,28 +0,0 @@ -[ - { - "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/v3/transformers/os/test-fixtures/amazon-multiple-kernel-advisories.json b/pkg/process/v3/transformers/os/test-fixtures/amazon-multiple-kernel-advisories.json deleted file mode 100644 index 82f2b45b..00000000 --- a/pkg/process/v3/transformers/os/test-fixtures/amazon-multiple-kernel-advisories.json +++ /dev/null @@ -1,104 +0,0 @@ -[ - { - "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/v3/transformers/os/test-fixtures/debian-8-multiple-entries-for-same-package.json b/pkg/process/v3/transformers/os/test-fixtures/debian-8-multiple-entries-for-same-package.json deleted file mode 100644 index 5025b56e..00000000 --- a/pkg/process/v3/transformers/os/test-fixtures/debian-8-multiple-entries-for-same-package.json +++ /dev/null @@ -1,62 +0,0 @@ -[ - { - "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/v3/transformers/os/test-fixtures/debian-8.json b/pkg/process/v3/transformers/os/test-fixtures/debian-8.json deleted file mode 100644 index a758f13c..00000000 --- a/pkg/process/v3/transformers/os/test-fixtures/debian-8.json +++ /dev/null @@ -1,62 +0,0 @@ -[ - { - "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/v3/transformers/os/test-fixtures/ol-8-modules.json b/pkg/process/v3/transformers/os/test-fixtures/ol-8-modules.json deleted file mode 100644 index f1d7372b..00000000 --- a/pkg/process/v3/transformers/os/test-fixtures/ol-8-modules.json +++ /dev/null @@ -1,36 +0,0 @@ -[ - { - "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/v3/transformers/os/test-fixtures/ol-8.json b/pkg/process/v3/transformers/os/test-fixtures/ol-8.json deleted file mode 100644 index 09439ece..00000000 --- a/pkg/process/v3/transformers/os/test-fixtures/ol-8.json +++ /dev/null @@ -1,42 +0,0 @@ -[ - { - "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/v3/transformers/os/test-fixtures/rhel-8-modules.json b/pkg/process/v3/transformers/os/test-fixtures/rhel-8-modules.json deleted file mode 100644 index c0400ad5..00000000 --- a/pkg/process/v3/transformers/os/test-fixtures/rhel-8-modules.json +++ /dev/null @@ -1,75 +0,0 @@ -[ - { - "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/v3/transformers/os/test-fixtures/rhel-8.json b/pkg/process/v3/transformers/os/test-fixtures/rhel-8.json deleted file mode 100644 index 2779708c..00000000 --- a/pkg/process/v3/transformers/os/test-fixtures/rhel-8.json +++ /dev/null @@ -1,57 +0,0 @@ -[ - { - "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/v3/transformers/os/test-fixtures/unmarshal-test.json b/pkg/process/v3/transformers/os/test-fixtures/unmarshal-test.json deleted file mode 100644 index edc6d25b..00000000 --- a/pkg/process/v3/transformers/os/test-fixtures/unmarshal-test.json +++ /dev/null @@ -1,104 +0,0 @@ -[ - { - "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" - } - }, - { - "Vulnerability": { - "Description": "", - "FixedIn": [ - { - "Name": "kernel-livepatch-4.14.173-137.228", - "NamespaceName": "amzn:2", - "Version": "1.0-3.amzn2", - "VersionFormat": "rpm" - }, - { - "Name": "kernel-livepatch-4.14.173-137.228-debuginfo", - "NamespaceName": "amzn:2", - "Version": "1.0-3.amzn2", - "VersionFormat": "rpm" - } - ], - "Link": "https://alas.aws.amazon.com/AL2/ALASLIVEPATCH-2020-012.html", - "Metadata": { - "CVE": [ - {"Name": "CVE-2020-12657"} - ] - }, - "Name": "ALASLIVEPATCH-2020-012", - "NamespaceName": "amzn:2", - "Severity": "High" - } - }, - { - "Vulnerability": { - "Description": "", - "FixedIn": [ - { - "Name": "kernel-livepatch-4.14.171-136.231", - "NamespaceName": "amzn:2", - "Version": "1.0-5.amzn2", - "VersionFormat": "rpm" - }, - { - "Name": "kernel-livepatch-4.14.171-136.231-debuginfo", - "NamespaceName": "amzn:2", - "Version": "1.0-5.amzn2", - "VersionFormat": "rpm" - } - ], - "Link": "https://alas.aws.amazon.com/AL2/ALASLIVEPATCH-2020-011.html", - "Metadata": { - "CVE": [ - {"Name": "CVE-2020-12657"} - ] - }, - "Name": "ALASLIVEPATCH-2020-011", - "NamespaceName": "amzn:2", - "Severity": "High" - } - } -] \ No newline at end of file diff --git a/pkg/process/v3/transformers/os/transform.go b/pkg/process/v3/transformers/os/transform.go deleted file mode 100644 index 1ab8d60f..00000000 --- a/pkg/process/v3/transformers/os/transform.go +++ /dev/null @@ -1,182 +0,0 @@ -package os - -import ( - "fmt" - "strings" - - "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype-db/pkg/process/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" -) - -const ( - // TODO: tech debt from a previous design - feed = "vulnerabilities" -) - -func Transform(vulnerability unmarshal.OSVulnerability) ([]data.Entry, error) { - group := vulnerability.Vulnerability.NamespaceName - - var allVulns []grypeDB.Vulnerability - - recordSource := grypeDB.RecordSource(feed, group) - entryNamespace, err := grypeDB.NamespaceForFeedGroup(feed, group) - if err != nil { - return nil, err - } - - vulnerability.Vulnerability.FixedIn = vulnerability.Vulnerability.FixedIn.FilterToHighestModularity() - - // there may be multiple packages indicated within the FixedIn field, we should make - // separate vulnerability entries (one for each name|namespace combo) while merging - // constraint ranges as they are found. - for idx, fixedInEntry := range vulnerability.Vulnerability.FixedIn { - // create vulnerability entry - allVulns = append(allVulns, grypeDB.Vulnerability{ - ID: vulnerability.Vulnerability.Name, - VersionConstraint: enforceConstraint(fixedInEntry.Version, fixedInEntry.VersionFormat, vulnerability.Vulnerability.Name), - VersionFormat: fixedInEntry.VersionFormat, - PackageName: fixedInEntry.Name, - Namespace: entryNamespace, - RelatedVulnerabilities: getRelatedVulnerabilities(vulnerability), - Fix: getFix(vulnerability, idx), - Advisories: getAdvisories(vulnerability, idx), - }) - } - - // create vulnerability metadata entry (a single entry keyed off of the vulnerability ID) - metadata := grypeDB.VulnerabilityMetadata{ - ID: vulnerability.Vulnerability.Name, - Namespace: entryNamespace, - DataSource: vulnerability.Vulnerability.Link, - RecordSource: recordSource, - Severity: vulnerability.Vulnerability.Severity, - URLs: getLinks(vulnerability), - Description: vulnerability.Vulnerability.Description, - Cvss: getCvss(vulnerability), - } - - return transformers.NewEntries(allVulns, metadata), nil -} - -func getLinks(entry unmarshal.OSVulnerability) []string { - // find all URLs related to the vulnerability - links := []string{entry.Vulnerability.Link} - if entry.Vulnerability.Metadata.CVE != nil { - for _, cve := range entry.Vulnerability.Metadata.CVE { - if cve.Link != "" { - links = append(links, cve.Link) - } - } - } - return links -} - -func getCvss(entry unmarshal.OSVulnerability) (cvss []grypeDB.Cvss) { - for _, vendorCvss := range entry.Vulnerability.CVSS { - cvss = append(cvss, grypeDB.Cvss{ - Version: vendorCvss.Version, - Vector: vendorCvss.VectorString, - Metrics: grypeDB.NewCvssMetrics( - vendorCvss.BaseMetrics.BaseScore, - vendorCvss.BaseMetrics.ExploitabilityScore, - vendorCvss.BaseMetrics.ImpactScore, - ), - VendorMetadata: transformers.VendorBaseMetrics{ - BaseSeverity: vendorCvss.BaseMetrics.BaseSeverity, - Status: vendorCvss.Status, - }, - }) - } - return cvss -} - -func getAdvisories(entry unmarshal.OSVulnerability, idx int) (advisories []grypeDB.Advisory) { - fixedInEntry := entry.Vulnerability.FixedIn[idx] - - for _, advisory := range fixedInEntry.VendorAdvisory.AdvisorySummary { - advisories = append(advisories, grypeDB.Advisory{ - ID: advisory.ID, - Link: advisory.Link, - }) - } - return advisories -} - -func getFix(entry unmarshal.OSVulnerability, idx int) grypeDB.Fix { - fixedInEntry := entry.Vulnerability.FixedIn[idx] - - var fixedInVersions []string - fixedInVersion := common.CleanFixedInVersion(fixedInEntry.Version) - if fixedInVersion != "" { - fixedInVersions = append(fixedInVersions, fixedInVersion) - } - - fixState := grypeDB.NotFixedState - if len(fixedInVersions) > 0 { - fixState = grypeDB.FixedState - } else if fixedInEntry.VendorAdvisory.NoAdvisory { - fixState = grypeDB.WontFixState - } - - return grypeDB.Fix{ - Versions: fixedInVersions, - State: fixState, - } -} - -func getRelatedVulnerabilities(entry unmarshal.OSVulnerability) (vulns []grypeDB.VulnerabilityReference) { - // associate related vulnerabilities from the NVD namespace - if strings.HasPrefix(entry.Vulnerability.Name, "CVE") { - vulns = append(vulns, grypeDB.VulnerabilityReference{ - ID: entry.Vulnerability.Name, - Namespace: grypeDB.NVDNamespace, - }) - } - - // note: an example of multiple CVEs for a record is centos:5 RHSA-2007:0055 which maps to CVE-2007-0002 and CVE-2007-1466 - for _, ref := range entry.Vulnerability.Metadata.CVE { - vulns = append(vulns, grypeDB.VulnerabilityReference{ - ID: ref.Name, - Namespace: grypeDB.NVDNamespace, - }) - } - return vulns -} - -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 -} - -func enforceConstraint(constraint, format, vulnerabilityID string) string { - constraint = common.CleanConstraint(constraint) - if len(constraint) == 0 { - return "" - } - switch strings.ToLower(format) { - case "semver": - return common.EnforceSemVerConstraint(constraint) - default: - // the passed constraint is a fixed version - return deriveConstraintFromFix(constraint, vulnerabilityID) - } -} diff --git a/pkg/process/v3/transformers/os/transform_test.go b/pkg/process/v3/transformers/os/transform_test.go deleted file mode 100644 index 09834c2c..00000000 --- a/pkg/process/v3/transformers/os/transform_test.go +++ /dev/null @@ -1,758 +0,0 @@ -package os - -import ( - "os" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - testUtils "github.com/anchore/grype-db/pkg/process/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" -) - -func TestUnmarshalOSVulnerabilitiesEntries(t *testing.T) { - f, err := os.Open("test-fixtures/unmarshal-test.json") - require.NoError(t, err) - defer testUtils.CloseFile(f) - - entries, err := unmarshal.OSVulnerabilityEntries(f) - require.NoError(t, err) - - assert.Len(t, entries, 3) - -} - -func TestParseVulnerabilitiesEntry(t *testing.T) { - tests := []struct { - name string - numEntries int - fixture string - vulns []grypeDB.Vulnerability - metadata grypeDB.VulnerabilityMetadata - feed, group string - }{ - { - name: "Amazon", - numEntries: 1, - fixture: "test-fixtures/amzn.json", - feed: "vulnerabilities", - group: "amzn:2", - vulns: []grypeDB.Vulnerability{ - { - ID: "ALAS-2018-1106", - VersionConstraint: "< 1.3.8.4-15.amzn2.0.1", - VersionFormat: "rpm", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2018-14648", - Namespace: grypeDB.NVDNamespace, - }, - }, - PackageName: "389-ds-base", - Namespace: "amzn:2", - Fix: grypeDB.Fix{ - Versions: []string{"1.3.8.4-15.amzn2.0.1"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "ALAS-2018-1106", - VersionConstraint: "< 1.3.8.4-15.amzn2.0.1", - VersionFormat: "rpm", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2018-14648", - Namespace: grypeDB.NVDNamespace, - }, - }, - PackageName: "389-ds-base-debuginfo", - Namespace: "amzn:2", - Fix: grypeDB.Fix{ - Versions: []string{"1.3.8.4-15.amzn2.0.1"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "ALAS-2018-1106", - VersionConstraint: "< 1.3.8.4-15.amzn2.0.1", - VersionFormat: "rpm", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2018-14648", - Namespace: grypeDB.NVDNamespace, - }, - }, - PackageName: "389-ds-base-devel", - Namespace: "amzn:2", - Fix: grypeDB.Fix{ - Versions: []string{"1.3.8.4-15.amzn2.0.1"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "ALAS-2018-1106", - VersionConstraint: "< 1.3.8.4-15.amzn2.0.1", - VersionFormat: "rpm", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2018-14648", - Namespace: grypeDB.NVDNamespace, - }, - }, - PackageName: "389-ds-base-libs", - Namespace: "amzn:2", - Fix: grypeDB.Fix{ - Versions: []string{"1.3.8.4-15.amzn2.0.1"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "ALAS-2018-1106", - VersionConstraint: "< 1.3.8.4-15.amzn2.0.1", - VersionFormat: "rpm", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2018-14648", - Namespace: grypeDB.NVDNamespace, - }, - }, - PackageName: "389-ds-base-snmp", - Namespace: "amzn:2", - Fix: grypeDB.Fix{ - Versions: []string{"1.3.8.4-15.amzn2.0.1"}, - State: grypeDB.FixedState, - }, - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "ALAS-2018-1106", - Namespace: "amzn:2", - DataSource: "https://alas.aws.amazon.com/AL2/ALAS-2018-1106.html", - RecordSource: "vulnerabilities:amzn:2", - Severity: "Medium", - URLs: []string{"https://alas.aws.amazon.com/AL2/ALAS-2018-1106.html"}, - }, - }, - { - name: "Debian", - numEntries: 1, - fixture: "test-fixtures/debian-8.json", - feed: "vulnerabilities", - group: "debian:8", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2008-7220", - PackageName: "asterisk", - VersionConstraint: "< 1:1.6.2.0~rc3-1", - VersionFormat: "dpkg", - Namespace: "debian:8", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2008-7220", - Namespace: "nvd", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"1:1.6.2.0~rc3-1"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "CVE-2008-7220", - PackageName: "auth2db", - VersionConstraint: "< 0.2.5-2+dfsg-1", - VersionFormat: "dpkg", - Namespace: "debian:8", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2008-7220", - Namespace: "nvd", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"0.2.5-2+dfsg-1"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "CVE-2008-7220", - PackageName: "exaile", - VersionConstraint: "< 0.2.14+debian-2.2", - VersionFormat: "dpkg", - Namespace: "debian:8", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2008-7220", - Namespace: "nvd", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"0.2.14+debian-2.2"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "CVE-2008-7220", - PackageName: "wordpress", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2008-7220", - Namespace: "nvd", - }, - }, - Fix: grypeDB.Fix{ - State: grypeDB.NotFixedState, - }, - VersionConstraint: "", - VersionFormat: "dpkg", - Namespace: "debian:8", - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2008-7220", - Namespace: "debian:8", - DataSource: "https://security-tracker.debian.org/tracker/CVE-2008-7220", - RecordSource: "vulnerabilities:debian:8", - Severity: "High", - URLs: []string{"https://security-tracker.debian.org/tracker/CVE-2008-7220"}, - Description: "", - }, - }, - { - name: "RHEL", - numEntries: 1, - fixture: "test-fixtures/rhel-8.json", - feed: "vulnerabilities", - group: "rhel:8", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2020-6819", - PackageName: "firefox", - VersionConstraint: "< 0:68.6.1-1.el8_1", - VersionFormat: "rpm", - Namespace: "rhel:8", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2020-6819", - Namespace: "nvd", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"0:68.6.1-1.el8_1"}, - State: grypeDB.FixedState, - }, - Advisories: []grypeDB.Advisory{ - { - ID: "RHSA-2020:1341", - Link: "https://access.redhat.com/errata/RHSA-2020:1341", - }, - }, - }, - { - ID: "CVE-2020-6819", - PackageName: "thunderbird", - VersionConstraint: "< 0:68.7.0-1.el8_1", - VersionFormat: "rpm", - Namespace: "rhel:8", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2020-6819", - Namespace: "nvd", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"0:68.7.0-1.el8_1"}, - State: grypeDB.FixedState, - }, - Advisories: []grypeDB.Advisory{ - { - ID: "RHSA-2020:1495", - Link: "https://access.redhat.com/errata/RHSA-2020:1495", - }, - }, - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2020-6819", - DataSource: "https://access.redhat.com/security/cve/CVE-2020-6819", - Namespace: "rhel:8", - RecordSource: "vulnerabilities:rhel:8", - Severity: "Critical", - URLs: []string{"https://access.redhat.com/security/cve/CVE-2020-6819"}, - 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.", - Cvss: []grypeDB.Cvss{ - { - Version: "3.1", - Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", - Metrics: grypeDB.NewCvssMetrics( - 8.8, - 2.8, - 5.9, - ), - VendorMetadata: transformers.VendorBaseMetrics{ - Status: "verified", - BaseSeverity: "High", - }, - }, - }, - }, - }, - { - name: "RHEL with modularity", - numEntries: 1, - fixture: "test-fixtures/rhel-8-modules.json", - feed: "vulnerabilities", - group: "rhel:8", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2020-14350", - PackageName: "postgresql", - VersionConstraint: "< 0:12.5-1.module+el8.3.0+9042+664538f4", - VersionFormat: "rpm", - Namespace: "rhel:8", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2020-14350", - Namespace: "nvd", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"0:12.5-1.module+el8.3.0+9042+664538f4"}, - State: grypeDB.FixedState, - }, - Advisories: []grypeDB.Advisory{ - { - ID: "RHSA-2020:5620", - Link: "https://access.redhat.com/errata/RHSA-2020:5620", - }, - }, - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2020-14350", - DataSource: "https://access.redhat.com/security/cve/CVE-2020-14350", - Namespace: "rhel:8", - RecordSource: "vulnerabilities:rhel:8", - Severity: "Medium", - URLs: []string{"https://access.redhat.com/security/cve/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.", - Cvss: []grypeDB.Cvss{ - { - Version: "3.1", - Vector: "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:H", - Metrics: grypeDB.NewCvssMetrics( - 7.1, - 1.2, - 5.9, - ), - VendorMetadata: transformers.VendorBaseMetrics{ - Status: "verified", - BaseSeverity: "High", - }, - }, - }, - }, - }, - { - name: "Alpine", - numEntries: 1, - fixture: "test-fixtures/alpine-3.9.json", - feed: "vulnerabilities", - group: "alpine:3.9", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2018-19967", - PackageName: "xen", - VersionConstraint: "< 4.11.1-r0", - VersionFormat: "apk", - Namespace: "alpine:3.9", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2018-19967", - Namespace: "nvd", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"4.11.1-r0"}, - State: grypeDB.FixedState, - }, - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2018-19967", - DataSource: "http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-19967", - Namespace: "alpine:3.9", - RecordSource: "vulnerabilities:alpine:3.9", - Severity: "Medium", - URLs: []string{"http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-19967"}, - Description: "", - }, - }, - { - name: "Oracle", - numEntries: 1, - fixture: "test-fixtures/ol-8.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "ELSA-2020-2550", - PackageName: "libexif", - VersionConstraint: "< 0:0.6.21-17.el8_2", - VersionFormat: "rpm", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2020-13112", - Namespace: grypeDB.NVDNamespace, - }, - }, - Namespace: "ol:8", - Fix: grypeDB.Fix{ - Versions: []string{"0:0.6.21-17.el8_2"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "ELSA-2020-2550", - PackageName: "libexif-devel", - VersionConstraint: "< 0:0.6.21-17.el8_2", - VersionFormat: "rpm", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2020-13112", - Namespace: grypeDB.NVDNamespace, - }, - }, - Namespace: "ol:8", - Fix: grypeDB.Fix{ - Versions: []string{"0:0.6.21-17.el8_2"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "ELSA-2020-2550", - PackageName: "libexif-dummy", - VersionConstraint: "", - VersionFormat: "rpm", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2020-13112", - Namespace: grypeDB.NVDNamespace, - }, - }, - Namespace: "ol:8", - Fix: grypeDB.Fix{ - Versions: nil, - State: grypeDB.NotFixedState, - }, - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "ELSA-2020-2550", - DataSource: "http://linux.oracle.com/errata/ELSA-2020-2550.html", - Namespace: "ol:8", - RecordSource: "vulnerabilities:ol:8", - Severity: "Medium", - URLs: []string{"http://linux.oracle.com/errata/ELSA-2020-2550.html", "http://linux.oracle.com/cve/CVE-2020-13112.html"}, - }, - }, - { - name: "Oracle Linux 8 with modularity", - numEntries: 1, - fixture: "test-fixtures/ol-8-modules.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2020-14350", - PackageName: "postgresql", - VersionConstraint: "< 0:12.5-1.module+el8.3.0+9042+664538f4", - VersionFormat: "rpm", - Namespace: "ol:8", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2020-14350", - Namespace: "nvd", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"0:12.5-1.module+el8.3.0+9042+664538f4"}, - State: grypeDB.FixedState, - }, - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2020-14350", - DataSource: "https://access.redhat.com/security/cve/CVE-2020-14350", - Namespace: "ol:8", - RecordSource: "vulnerabilities:ol:8", - Severity: "Medium", - URLs: []string{"https://access.redhat.com/security/cve/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.", - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - f, err := os.Open(test.fixture) - require.NoError(t, err) - t.Cleanup(func() { - assert.NoError(t, f.Close()) - }) - - entries, err := unmarshal.OSVulnerabilityEntries(f) - require.NoError(t, err) - require.Len(t, entries, 1) - - entry := entries[0] - - dataEntries, err := Transform(entry) - require.NoError(t, err) - - var vulns []grypeDB.Vulnerability - for _, entry := range dataEntries { - switch vuln := entry.Data.(type) { - case grypeDB.Vulnerability: - vulns = append(vulns, vuln) - case grypeDB.VulnerabilityMetadata: - assert.Equal(t, test.metadata, vuln) - default: - t.Fatalf("unexpected condition: data entry does not have a vulnerability or a metadata") - } - } - - if diff := cmp.Diff(test.vulns, vulns); diff != "" { - t.Errorf("vulnerabilities do not match (-want +got):\n%s", diff) - } - - }) - } - -} - -func TestParseVulnerabilitiesAllEntries(t *testing.T) { - tests := []struct { - name string - numEntries int - fixture string - vulns []grypeDB.Vulnerability - }{ - { - name: "Debian", - numEntries: 2, - fixture: "test-fixtures/debian-8-multiple-entries-for-same-package.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2011-4623", - PackageName: "rsyslog", - VersionConstraint: "< 5.7.4-1", - VersionFormat: "dpkg", - Namespace: "debian:8", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2011-4623", - Namespace: "nvd", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"5.7.4-1"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "CVE-2008-5618", - PackageName: "rsyslog", - VersionConstraint: "< 3.18.6-1", - VersionFormat: "dpkg", - Namespace: "debian:8", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2008-5618", - Namespace: "nvd", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"3.18.6-1"}, - State: grypeDB.FixedState, - }, - }, - }, - }, - { - name: "Amazon", - numEntries: 3, - fixture: "test-fixtures/amazon-multiple-kernel-advisories.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "ALAS-2021-1704", - PackageName: "kernel-headers", - VersionConstraint: "< 4.14.246-187.474.amzn2", - VersionFormat: "rpm", - Namespace: "amzn:2", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2021-3653", - Namespace: "nvd", - }, - { - ID: "CVE-2021-3656", - Namespace: "nvd", - }, - { - ID: "CVE-2021-3732", - Namespace: "nvd", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"4.14.246-187.474.amzn2"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "ALAS-2021-1704", - PackageName: "kernel", - VersionConstraint: "< 4.14.246-187.474.amzn2", - VersionFormat: "rpm", - Namespace: "amzn:2", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2021-3653", - Namespace: "nvd", - }, - { - ID: "CVE-2021-3656", - Namespace: "nvd", - }, - { - ID: "CVE-2021-3732", - Namespace: "nvd", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"4.14.246-187.474.amzn2"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "ALASKERNEL-5.4-2022-007", - PackageName: "kernel-headers", - VersionConstraint: ">= 5.4, < 5.4.144-69.257.amzn2", - VersionFormat: "rpm", - Namespace: "amzn:2", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2021-3753", - Namespace: "nvd", - }, - { - ID: "CVE-2021-40490", - Namespace: "nvd", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"5.4.144-69.257.amzn2"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "ALASKERNEL-5.4-2022-007", - PackageName: "kernel", - VersionConstraint: ">= 5.4, < 5.4.144-69.257.amzn2", - VersionFormat: "rpm", - Namespace: "amzn:2", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2021-3753", - Namespace: "nvd", - }, - { - ID: "CVE-2021-40490", - Namespace: "nvd", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"5.4.144-69.257.amzn2"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "ALASKERNEL-5.10-2022-005", - PackageName: "kernel-headers", - VersionConstraint: ">= 5.10, < 5.10.62-55.141.amzn2", - VersionFormat: "rpm", - Namespace: "amzn:2", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2021-3753", - Namespace: "nvd", - }, - { - ID: "CVE-2021-40490", - Namespace: "nvd", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"5.10.62-55.141.amzn2"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "ALASKERNEL-5.10-2022-005", - PackageName: "kernel", - VersionConstraint: ">= 5.10, < 5.10.62-55.141.amzn2", - VersionFormat: "rpm", - Namespace: "amzn:2", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2021-3753", - Namespace: "nvd", - }, - { - ID: "CVE-2021-40490", - Namespace: "nvd", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"5.10.62-55.141.amzn2"}, - State: grypeDB.FixedState, - }, - }, - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - f, err := os.Open(test.fixture) - require.NoError(t, err) - t.Cleanup(func() { - assert.NoError(t, f.Close()) - }) - - entries, err := unmarshal.OSVulnerabilityEntries(f) - require.NoError(t, err) - require.Len(t, entries, test.numEntries) - - var vulns []grypeDB.Vulnerability - for _, entry := range entries { - dataEntries, err := Transform(entry) - require.NoError(t, err) - - for _, entry := range dataEntries { - switch vuln := entry.Data.(type) { - case grypeDB.Vulnerability: - vulns = append(vulns, vuln) - case grypeDB.VulnerabilityMetadata: - default: - t.Fatalf("unexpected condition: data entry does not have a vulnerability or a metadata") - } - } - } - - if diff := cmp.Diff(test.vulns, vulns); diff != "" { - t.Errorf("vulnerabilities do not match (-want +got):\n%s", diff) - } - }) - } -} diff --git a/pkg/process/v3/transformers/vulnerability_metadata.go b/pkg/process/v3/transformers/vulnerability_metadata.go deleted file mode 100644 index da214315..00000000 --- a/pkg/process/v3/transformers/vulnerability_metadata.go +++ /dev/null @@ -1,8 +0,0 @@ -package transformers - -// VendorBaseMetrics captures extra metrics that do not fit into a common CVSS -// struct, like Status and BaseSeverity -type VendorBaseMetrics struct { - BaseSeverity string - Status string -} diff --git a/pkg/process/v3/writer.go b/pkg/process/v3/writer.go deleted file mode 100644 index 051a55ee..00000000 --- a/pkg/process/v3/writer.go +++ /dev/null @@ -1,131 +0,0 @@ -package v3 - -import ( - "crypto/sha256" - "fmt" - "path" - "path/filepath" - "strings" - "time" - - "github.com/spf13/afero" - - "github.com/anchore/grype-db/internal/file" - "github.com/anchore/grype-db/internal/log" - "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype/grype/db" - grypeDB "github.com/anchore/grype/grype/db/v3" - grypeDBStore "github.com/anchore/grype/grype/db/v3/store" -) - -var _ data.Writer = (*writer)(nil) - -type writer struct { - dbPath string - store grypeDB.Store -} - -func NewWriter(directory string, dataAge time.Time) (data.Writer, error) { - dbPath := path.Join(directory, grypeDB.VulnerabilityStoreFileName) - theStore, err := grypeDBStore.New(dbPath, true) - if err != nil { - return nil, fmt.Errorf("unable to create writer: %w", err) - } - - if err := theStore.SetID(grypeDB.NewID(dataAge)); err != nil { - return nil, fmt.Errorf("unable to set DB ID: %w", err) - } - - return &writer{ - dbPath: dbPath, - store: theStore, - }, nil -} - -func (w writer) Write(entries ...data.Entry) error { - for _, entry := range entries { - if entry.DBSchemaVersion != grypeDB.SchemaVersion { - return fmt.Errorf("wrong schema version: want %+v got %+v", grypeDB.SchemaVersion, entry.DBSchemaVersion) - } - - switch row := entry.Data.(type) { - case grypeDB.Vulnerability: - if err := w.store.AddVulnerability(row); err != nil { - return fmt.Errorf("unable to write vulnerability to store: %w", err) - } - case grypeDB.VulnerabilityMetadata: - normalizeSeverity(&row, w.store) - if err := w.store.AddVulnerabilityMetadata(row); err != nil { - return fmt.Errorf("unable to write vulnerability metadata to store: %w", err) - } - default: - return fmt.Errorf("data entry does not have a vulnerability or a metadata: %T", row) - } - } - - return nil -} - -func (w writer) metadata() (*db.Metadata, error) { - hashStr, err := file.ContentDigest(afero.NewOsFs(), w.dbPath, sha256.New()) - if err != nil { - return nil, fmt.Errorf("failed to hash database file (%s): %w", w.dbPath, err) - } - - storeID, err := w.store.GetID() - if err != nil { - return nil, fmt.Errorf("failed to fetch store ID: %w", err) - } - - metadata := db.Metadata{ - Built: storeID.BuildTimestamp, - Version: storeID.SchemaVersion, - Checksum: "sha256:" + hashStr, - } - return &metadata, nil -} - -func (w writer) Close() error { - w.store.Close() - metadata, err := w.metadata() - if err != nil { - return err - } - - metadataPath := path.Join(filepath.Dir(w.dbPath), db.MetadataFileName) - if err = metadata.Write(metadataPath); err != nil { - return err - } - - log.WithFields("path", w.dbPath).Info("database created") - log.WithFields("path", metadataPath).Debug("database metadata created") - - return nil -} - -func normalizeSeverity(metadata *grypeDB.VulnerabilityMetadata, reader grypeDB.VulnerabilityMetadataStoreReader) { - if metadata.Severity != "" && strings.ToLower(metadata.Severity) != "unknown" { - return - } - if !strings.HasPrefix(strings.ToLower(metadata.ID), "cve") { - return - } - if strings.HasPrefix(metadata.Namespace, grypeDB.NVDNamespace) { - return - } - m, err := reader.GetVulnerabilityMetadata(metadata.ID, grypeDB.NVDNamespace) - if err != nil { - log.WithFields("id", metadata.ID, "error", err).Warn("error fetching vulnerability metadata from NVD namespace") - return - } - if m == nil { - log.WithFields("id", metadata.ID).Trace("unable to find vulnerability metadata from NVD namespace") - return - } - - newSeverity := string(data.ParseSeverity(m.Severity)) - - log.WithFields("id", metadata.ID, "namespace", metadata.Namespace, "from", metadata.Severity, "to", newSeverity).Trace("overriding irrelevant severity with data from NVD record") - - metadata.Severity = newSeverity -} diff --git a/pkg/process/v3/writer_test.go b/pkg/process/v3/writer_test.go deleted file mode 100644 index 0f8cfb62..00000000 --- a/pkg/process/v3/writer_test.go +++ /dev/null @@ -1,117 +0,0 @@ -package v3 - -import ( - "errors" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/anchore/grype-db/pkg/data" - grypeDB "github.com/anchore/grype/grype/db/v3" -) - -var _ grypeDB.VulnerabilityMetadataStoreReader = (*mockReader)(nil) - -type mockReader struct { - metadata *grypeDB.VulnerabilityMetadata - err error -} - -func newMockReader(sev string) *mockReader { - return &mockReader{ - metadata: &grypeDB.VulnerabilityMetadata{ - Severity: sev, - Namespace: "nvd", - }, - } -} - -func newDeadMockReader() *mockReader { - return &mockReader{ - err: errors.New("dead"), - } -} - -func (m mockReader) GetVulnerabilityMetadata(_, _ string) (*grypeDB.VulnerabilityMetadata, error) { - return m.metadata, m.err -} - -func (m mockReader) GetAllVulnerabilityMetadata() (*[]grypeDB.VulnerabilityMetadata, error) { - panic("implement me") -} - -func Test_normalizeSeverity(t *testing.T) { - - tests := []struct { - name string - initialSeverity string - namespace string - cveID string - reader grypeDB.VulnerabilityMetadataStoreReader - expected data.Severity - }{ - { - name: "skip missing metadata", - initialSeverity: "", - namespace: "test", - reader: &mockReader{}, - expected: "", - }, - { - name: "skip non-cve records metadata", - cveID: "GHSA-1234-1234-1234", - initialSeverity: "", - namespace: "test", - reader: newDeadMockReader(), // should not be used - expected: "", - }, { - name: "override empty severity", - initialSeverity: "", - namespace: "test", - reader: newMockReader("low"), - expected: data.SeverityLow, - }, - { - name: "override unknown severity", - initialSeverity: "unknown", - namespace: "test", - reader: newMockReader("low"), - expected: data.SeverityLow, - }, - { - name: "ignore record with severity already set", - initialSeverity: "Low", - namespace: "test", - reader: newMockReader("critical"), // should not be used - expected: data.SeverityLow, - }, - { - name: "ignore nvd records", - initialSeverity: "Low", - namespace: "nvd", - reader: newDeadMockReader(), // should not be used - expected: data.SeverityLow, - }, - { - name: "db errors should not fail or modify the record", - initialSeverity: "", - namespace: "test", - reader: newDeadMockReader(), - expected: "", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - record := &grypeDB.VulnerabilityMetadata{ - ID: "cve-2020-0000", - Severity: tt.initialSeverity, - Namespace: tt.namespace, - } - if tt.cveID != "" { - record.ID = tt.cveID - } - normalizeSeverity(record, tt.reader) - assert.Equal(t, string(tt.expected), record.Severity) - }) - } -} diff --git a/pkg/process/v4/processors.go b/pkg/process/v4/processors.go deleted file mode 100644 index 9e0a69a1..00000000 --- a/pkg/process/v4/processors.go +++ /dev/null @@ -1,21 +0,0 @@ -package v4 - -import ( - "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype-db/pkg/process/processors" - "github.com/anchore/grype-db/pkg/process/v4/transformers/github" - "github.com/anchore/grype-db/pkg/process/v4/transformers/matchexclusions" - "github.com/anchore/grype-db/pkg/process/v4/transformers/msrc" - "github.com/anchore/grype-db/pkg/process/v4/transformers/nvd" - "github.com/anchore/grype-db/pkg/process/v4/transformers/os" -) - -func Processors() []data.Processor { - return []data.Processor{ - processors.NewGitHubProcessor(github.Transform), - processors.NewMSRCProcessor(msrc.Transform), - processors.NewNVDProcessor(nvd.Transform), - processors.NewOSProcessor(os.Transform), - processors.NewMatchExclusionProcessor(matchexclusions.Transform), - } -} diff --git a/pkg/process/v4/transformers/entry.go b/pkg/process/v4/transformers/entry.go deleted file mode 100644 index a60d5daf..00000000 --- a/pkg/process/v4/transformers/entry.go +++ /dev/null @@ -1,22 +0,0 @@ -package transformers - -import ( - "github.com/anchore/grype-db/pkg/data" - grypeDB "github.com/anchore/grype/grype/db/v4" -) - -func NewEntries(vs []grypeDB.Vulnerability, metadata grypeDB.VulnerabilityMetadata) []data.Entry { - entries := []data.Entry{ - { - DBSchemaVersion: grypeDB.SchemaVersion, - Data: metadata, - }, - } - for _, vuln := range vs { - entries = append(entries, data.Entry{ - DBSchemaVersion: grypeDB.SchemaVersion, - Data: vuln, - }) - } - return entries -} diff --git a/pkg/process/v4/transformers/github/test-fixtures/github-github-npm-0.json b/pkg/process/v4/transformers/github/test-fixtures/github-github-npm-0.json deleted file mode 100644 index b0a7d1e9..00000000 --- a/pkg/process/v4/transformers/github/test-fixtures/github-github-npm-0.json +++ /dev/null @@ -1,31 +0,0 @@ - -{ - "Advisory": { - "CVE": [ - "CVE-2020-14000" - ], - "FixedIn": [ - { - "ecosystem": "npm", - "identifier": "0.2.0-prerelease.20200714185213", - "name": "scratch-vm", - "namespace": "github:npm", - "range": "<= 0.2.0-prerelease.20200709173451" - } - ], - "Metadata": { - "CVE": [ - "CVE-2020-14000" - ] - }, - "Severity": "High", - "Summary": "Remote Code Execution in scratch-vm", - "ghsaId": "GHSA-vc9j-fhvv-8vrf", - "namespace": "github:npm", - "url": "https://github.com/advisories/GHSA-vc9j-fhvv-8vrf", - "withdrawn": null - }, - "Vulnerability": {} -} - - diff --git a/pkg/process/v4/transformers/github/test-fixtures/github-github-python-0.json b/pkg/process/v4/transformers/github/test-fixtures/github-github-python-0.json deleted file mode 100644 index ad14aa60..00000000 --- a/pkg/process/v4/transformers/github/test-fixtures/github-github-python-0.json +++ /dev/null @@ -1,58 +0,0 @@ -[ - { - "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/v4/transformers/github/test-fixtures/github-github-python-1.json b/pkg/process/v4/transformers/github/test-fixtures/github-github-python-1.json deleted file mode 100644 index bfa84922..00000000 --- a/pkg/process/v4/transformers/github/test-fixtures/github-github-python-1.json +++ /dev/null @@ -1,43 +0,0 @@ - -{ - "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", - "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/v4/transformers/github/test-fixtures/github-withdrawn.json b/pkg/process/v4/transformers/github/test-fixtures/github-withdrawn.json deleted file mode 100644 index 04995e38..00000000 --- a/pkg/process/v4/transformers/github/test-fixtures/github-withdrawn.json +++ /dev/null @@ -1,29 +0,0 @@ - -{ - "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/v4/transformers/github/test-fixtures/multiple-fixed-in-names.json b/pkg/process/v4/transformers/github/test-fixtures/multiple-fixed-in-names.json deleted file mode 100644 index ac1ef982..00000000 --- a/pkg/process/v4/transformers/github/test-fixtures/multiple-fixed-in-names.json +++ /dev/null @@ -1,43 +0,0 @@ - -{ - "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/v4/transformers/github/transform.go b/pkg/process/v4/transformers/github/transform.go deleted file mode 100644 index d1599f6d..00000000 --- a/pkg/process/v4/transformers/github/transform.go +++ /dev/null @@ -1,138 +0,0 @@ -package github - -import ( - "fmt" - "strings" - - "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype-db/pkg/process/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" - "github.com/anchore/grype/grype/db/v4/namespace" - syftPkg "github.com/anchore/syft/syft/pkg" -) - -const ( - // TODO: tech debt from a previous design - feed = "github" -) - -func buildGrypeNamespace(feed, group string) (namespace.Namespace, error) { - if feed != "github" { - return nil, fmt.Errorf("unable to determine grype namespace for enterprise feed=%s, group=%s", feed, group) - } - - feedGroupComponents := strings.Split(group, ":") - - if len(feedGroupComponents) < 2 { - return nil, fmt.Errorf("unable to determine grype namespace for enterprise feed=%s, group=%s", feed, group) - } - - feedGroupLang := feedGroupComponents[1] - syftLanguage := syftPkg.LanguageByName(feedGroupLang) - - if syftLanguage == syftPkg.UnknownLanguage { - // For now map nuget to dotnet as the language. - if feedGroupLang == "nuget" { - syftLanguage = syftPkg.Dotnet - } else { - return nil, fmt.Errorf("unable to determine grype namespace for enterprise feed=%s, group=%s", feed, group) - } - } - - ns, err := namespace.FromString(fmt.Sprintf("github:language:%s", string(syftLanguage))) - - if err != nil { - return nil, err - } - - return ns, nil -} - -func Transform(vulnerability unmarshal.GitHubAdvisory) ([]data.Entry, error) { - var allVulns []grypeDB.Vulnerability - - // Exclude entries marked as withdrawn - if vulnerability.Advisory.Withdrawn != nil { - return nil, nil - } - - recordSource := fmt.Sprintf("%s:%s", feed, vulnerability.Advisory.Namespace) - grypeNamespace, err := buildGrypeNamespace(feed, vulnerability.Advisory.Namespace) - if err != nil { - return nil, err - } - - entryNamespace := grypeNamespace.String() - - // there may be multiple packages indicated within the FixedIn field, we should make - // separate vulnerability entries (one for each name|namespaces combo) while merging - // constraint ranges as they are found. - for idx, fixedInEntry := range vulnerability.Advisory.FixedIn { - constraint := common.EnforceSemVerConstraint(fixedInEntry.Range) - - var versionFormat string - switch entryNamespace { - case "github:language:python": - versionFormat = "python" - default: - versionFormat = "unknown" - } - - // create vulnerability entry - allVulns = append(allVulns, grypeDB.Vulnerability{ - ID: vulnerability.Advisory.GhsaID, - VersionConstraint: constraint, - VersionFormat: versionFormat, - RelatedVulnerabilities: getRelatedVulnerabilities(vulnerability), - PackageName: grypeNamespace.Resolver().Normalize(fixedInEntry.Name), - Namespace: entryNamespace, - Fix: getFix(vulnerability, idx), - }) - } - - // create vulnerability metadata entry (a single entry keyed off of the vulnerability ID) - metadata := grypeDB.VulnerabilityMetadata{ - ID: vulnerability.Advisory.GhsaID, - DataSource: vulnerability.Advisory.URL, - Namespace: entryNamespace, - RecordSource: recordSource, - Severity: vulnerability.Advisory.Severity, - URLs: []string{vulnerability.Advisory.URL}, - Description: vulnerability.Advisory.Summary, - } - - return transformers.NewEntries(allVulns, metadata), nil -} - -func getFix(entry unmarshal.GitHubAdvisory, idx int) grypeDB.Fix { - fixedInEntry := entry.Advisory.FixedIn[idx] - - var fixedInVersions []string - fixedInVersion := common.CleanFixedInVersion(fixedInEntry.Identifier) - if fixedInVersion != "" { - fixedInVersions = append(fixedInVersions, fixedInVersion) - } - - fixState := grypeDB.NotFixedState - if len(fixedInVersions) > 0 { - fixState = grypeDB.FixedState - } - - return grypeDB.Fix{ - Versions: fixedInVersions, - State: fixState, - } -} - -func getRelatedVulnerabilities(entry unmarshal.GitHubAdvisory) []grypeDB.VulnerabilityReference { - vulns := make([]grypeDB.VulnerabilityReference, len(entry.Advisory.CVE)) - for idx, cve := range entry.Advisory.CVE { - vulns[idx] = grypeDB.VulnerabilityReference{ - ID: cve, - Namespace: "nvd:cpe", - } - } - return vulns -} diff --git a/pkg/process/v4/transformers/github/transform_test.go b/pkg/process/v4/transformers/github/transform_test.go deleted file mode 100644 index 4e5c1c85..00000000 --- a/pkg/process/v4/transformers/github/transform_test.go +++ /dev/null @@ -1,255 +0,0 @@ -package github - -import ( - "os" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - testUtils "github.com/anchore/grype-db/pkg/process/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" - "github.com/anchore/grype/grype/db/v4/namespace/language" - syftPkg "github.com/anchore/syft/syft/pkg" -) - -func TestBuildGrypeNamespace(t *testing.T) { - tests := []struct { - feed string - group string - namespace namespace.Namespace - }{ - { - feed: "github", - group: "github:python", - namespace: language.NewNamespace("github", syftPkg.Python, ""), - }, - { - feed: "github", - group: "github:composer", - namespace: language.NewNamespace("github", syftPkg.PHP, ""), - }, - { - feed: "github", - group: "github:gem", - namespace: language.NewNamespace("github", syftPkg.Ruby, ""), - }, - { - feed: "github", - group: "github:npm", - namespace: language.NewNamespace("github", syftPkg.JavaScript, ""), - }, - { - feed: "github", - group: "github:go", - namespace: language.NewNamespace("github", syftPkg.Go, ""), - }, - { - feed: "github", - group: "github:nuget", - namespace: language.NewNamespace("github", syftPkg.Dotnet, ""), - }, - { - feed: "github", - group: "github:rust", - namespace: language.NewNamespace("github", syftPkg.Rust, ""), - }, - } - - for _, test := range tests { - ns, err := buildGrypeNamespace(test.feed, test.group) - - assert.NoError(t, err) - assert.Equal(t, test.namespace, ns) - } -} - -func TestUnmarshalGitHubEntries(t *testing.T) { - f, err := os.Open("test-fixtures/github-github-python-0.json") - require.NoError(t, err) - defer testUtils.CloseFile(f) - - entries, err := unmarshal.GitHubAdvisoryEntries(f) - require.NoError(t, err) - - assert.Len(t, entries, 2) - -} - -func TestParseGitHubEntry(t *testing.T) { - expectedVulns := []grypeDB.Vulnerability{ - { - ID: "GHSA-p5wr-vp8g-q5p4", - VersionConstraint: ">=4.0,<4.3.12", - VersionFormat: "python", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2017-5524", - Namespace: "nvd:cpe", - }, - }, - PackageName: "plone", - Namespace: "github:language:python", - Fix: grypeDB.Fix{ - Versions: []string{"4.3.12"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "GHSA-p5wr-vp8g-q5p4", - VersionConstraint: ">=5.1a1,<5.1b1", - VersionFormat: "python", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2017-5524", - Namespace: "nvd:cpe", - }, - }, - PackageName: "plone", - Namespace: "github:language:python", - Fix: grypeDB.Fix{ - Versions: []string{"5.1b1"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "GHSA-p5wr-vp8g-q5p4", - VersionConstraint: ">=5.0rc1,<5.0.7", - VersionFormat: "python", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2017-5524", - Namespace: "nvd:cpe", - }, - }, - PackageName: "plone", - Namespace: "github:language:python", - Fix: grypeDB.Fix{ - Versions: []string{"5.0.7"}, - State: grypeDB.FixedState, - }, - }, - } - - expectedMetadata := grypeDB.VulnerabilityMetadata{ - ID: "GHSA-p5wr-vp8g-q5p4", - Namespace: "github:language:python", - RecordSource: "github:github:python", - DataSource: "https://github.com/advisories/GHSA-p5wr-vp8g-q5p4", - Severity: "Medium", - URLs: []string{"https://github.com/advisories/GHSA-p5wr-vp8g-q5p4"}, - Description: "Moderate severity vulnerability that affects Plone", - } - - f, err := os.Open("test-fixtures/github-github-python-1.json") - require.NoError(t, err) - defer testUtils.CloseFile(f) - - entries, err := unmarshal.GitHubAdvisoryEntries(f) - require.NoError(t, err) - - require.Len(t, entries, 1) - - entry := entries[0] - - dataEntries, err := Transform(entry) - require.NoError(t, err) - - var vulns []grypeDB.Vulnerability - for _, entry := range dataEntries { - switch vuln := entry.Data.(type) { - case grypeDB.Vulnerability: - vulns = append(vulns, vuln) - case grypeDB.VulnerabilityMetadata: - assert.Equal(t, expectedMetadata, vuln) - default: - t.Fatalf("unexpected condition: data entry does not have a vulnerability or a metadata") - } - } - - // check vulnerability - assert.Len(t, vulns, len(expectedVulns)) - - if diff := cmp.Diff(expectedVulns, vulns); diff != "" { - t.Errorf("vulnerabilities do not match (-want +got):\n%s", diff) - } - -} - -func TestDefaultVersionFormatNpmGitHubEntry(t *testing.T) { - expectedVuln := grypeDB.Vulnerability{ - ID: "GHSA-vc9j-fhvv-8vrf", - VersionConstraint: "<=0.2.0-prerelease.20200709173451", - VersionFormat: "unknown", // TODO: this should reference a format, yes? (not a string) - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2020-14000", - Namespace: "nvd:cpe", - }, - }, - PackageName: "scratch-vm", - Namespace: "github:language:javascript", - Fix: grypeDB.Fix{ - Versions: []string{"0.2.0-prerelease.20200714185213"}, - State: grypeDB.FixedState, - }, - } - - expectedMetadata := grypeDB.VulnerabilityMetadata{ - ID: "GHSA-vc9j-fhvv-8vrf", - Namespace: "github:language:javascript", - RecordSource: "github:github:npm", - DataSource: "https://github.com/advisories/GHSA-vc9j-fhvv-8vrf", - Severity: "High", - URLs: []string{"https://github.com/advisories/GHSA-vc9j-fhvv-8vrf"}, - Description: "Remote Code Execution in scratch-vm", - } - - f, err := os.Open("test-fixtures/github-github-npm-0.json") - require.NoError(t, err) - defer testUtils.CloseFile(f) - - entries, err := unmarshal.GitHubAdvisoryEntries(f) - require.NoError(t, err) - - require.Len(t, entries, 1) - - entry := entries[0] - - dataEntries, err := Transform(entry) - assert.NoError(t, err) - - for _, entry := range dataEntries { - switch vuln := entry.Data.(type) { - case grypeDB.Vulnerability: - assert.Equal(t, expectedVuln, vuln) - case grypeDB.VulnerabilityMetadata: - assert.Equal(t, expectedMetadata, vuln) - default: - t.Fatalf("unexpected condition: data entry does not have a vulnerability or a metadata") - } - } - - // check vulnerability - assert.Len(t, dataEntries, 2) -} - -func TestFilterWithdrawnEntries(t *testing.T) { - f, err := os.Open("test-fixtures/github-withdrawn.json") - require.NoError(t, err) - defer testUtils.CloseFile(f) - - entries, err := unmarshal.GitHubAdvisoryEntries(f) - require.NoError(t, err) - - require.Len(t, entries, 1) - - entry := entries[0] - - dataEntries, err := Transform(entry) - assert.NoError(t, err) - assert.Nil(t, dataEntries) -} diff --git a/pkg/process/v4/transformers/matchexclusions/transform.go b/pkg/process/v4/transformers/matchexclusions/transform.go deleted file mode 100644 index c4831be1..00000000 --- a/pkg/process/v4/transformers/matchexclusions/transform.go +++ /dev/null @@ -1,42 +0,0 @@ -package matchexclusions - -import ( - "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype-db/pkg/provider/unmarshal" - grypeDB "github.com/anchore/grype/grype/db/v4" -) - -func Transform(matchExclusion unmarshal.MatchExclusion) ([]data.Entry, error) { - exclusion := grypeDB.VulnerabilityMatchExclusion{ - ID: matchExclusion.ID, - Constraints: nil, - Justification: matchExclusion.Justification, - } - - for _, c := range matchExclusion.Constraints { - constraint := &grypeDB.VulnerabilityMatchExclusionConstraint{ - Vulnerability: grypeDB.VulnerabilityExclusionConstraint{ - Namespace: c.Vulnerability.Namespace, - FixState: grypeDB.FixState(c.Vulnerability.FixState), - }, - Package: grypeDB.PackageExclusionConstraint{ - Name: c.Package.Name, - Language: c.Package.Language, - Type: c.Package.Type, - Version: c.Package.Version, - Location: c.Package.Location, - }, - } - - exclusion.Constraints = append(exclusion.Constraints, *constraint) - } - - entries := []data.Entry{ - { - DBSchemaVersion: grypeDB.SchemaVersion, - Data: exclusion, - }, - } - - return entries, nil -} diff --git a/pkg/process/v4/transformers/msrc/test-fixtures/microsoft-msrc-0.json b/pkg/process/v4/transformers/msrc/test-fixtures/microsoft-msrc-0.json deleted file mode 100644 index 474b23b2..00000000 --- a/pkg/process/v4/transformers/msrc/test-fixtures/microsoft-msrc-0.json +++ /dev/null @@ -1,194 +0,0 @@ -[ - { - "cvss": { - "base_score": 7.8, - "temporal_score": 7, - "vector": "CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H/E:P/RL:O/RC:C" - }, - "fixed_in": [ - { - "id": "4493470", - "is_first": true, - "is_latest": false, - "links": [ - "https://catalog.update.microsoft.com/v7/site/Search.aspx?q=KB4493470", - "https://support.microsoft.com/help/4493470" - ] - }, - { - "id": "4494440", - "is_first": false, - "is_latest": false, - "links": [ - "https://catalog.update.microsoft.com/v7/site/Search.aspx?q=KB4494440", - "https://support.microsoft.com/help/4494440" - ] - }, - { - "id": "4503267", - "is_first": false, - "is_latest": false, - "links": [ - "https://catalog.update.microsoft.com/v7/site/Search.aspx?q=KB4503267", - "https://support.microsoft.com/en-us/help/4503267" - ] - }, - { - "id": "4507460", - "is_first": false, - "is_latest": false, - "links": [ - "https://catalog.update.microsoft.com/v7/site/Search.aspx?q=KB4507460", - "https://support.microsoft.com/help/4507460" - ] - }, - { - "id": "4512517", - "is_first": false, - "is_latest": false, - "links": [ - "https://catalog.update.microsoft.com/v7/site/Search.aspx?q=KB4512517", - "https://support.microsoft.com/help/4512517" - ] - }, - { - "id": "4516044", - "is_first": false, - "is_latest": true, - "links": [ - "https://catalog.update.microsoft.com/v7/site/Search.aspx?q=KB4516044", - "https://support.microsoft.com/help/4516044" - ] - } - ], - "id": "CVE-2019-0671", - "link": "https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2019-0671", - "product": { - "family": "Windows", - "id": "10852", - "name": "Windows 10 Version 1607 for 32-bit Systems" - }, - "severity": "High", - "summary": "Microsoft Office Access Connectivity Engine Remote Code Execution Vulnerability", - "vulnerable": [ - "4480961", - "4483229", - "4487026", - "4489882" - ] - }, -{ - "cvss": { - "base_score": 4.4, - "temporal_score": 4, - "vector": "CVSS:3.0/AV:L/AC:H/PR:L/UI:R/S:U/C:N/I:N/A:H/E:P/RL:O/RC:C" - }, - "fixed_in": [ - { - "id": "4093119", - "is_first": true, - "is_latest": false, - "links": [ - "https://catalog.update.microsoft.com/v7/site/Search.aspx?q=KB4093119" - ] - }, - { - "id": "4103723", - "is_first": false, - "is_latest": false, - "links": [ - "https://catalog.update.microsoft.com/v7/site/Search.aspx?q=KB4103723" - ] - }, - { - "id": "4284880", - "is_first": false, - "is_latest": false, - "links": [ - "https://catalog.update.microsoft.com/v7/site/Search.aspx?q=KB4284880" - ] - }, - { - "id": "4338814", - "is_first": false, - "is_latest": false, - "links": [ - "https://catalog.update.microsoft.com/v7/site/Search.aspx?q=KB4338814" - ] - }, - { - "id": "4343887", - "is_first": false, - "is_latest": false, - "links": [ - "https://catalog.update.microsoft.com/v7/site/Search.aspx?q=KB4343887" - ] - }, - { - "id": "4345418", - "is_first": false, - "is_latest": true, - "links": [ - "https://catalog.update.microsoft.com/v7/site/Search.aspx?q=KB4345418" - ] - }, - { - "id": "4457131", - "is_first": false, - "is_latest": false, - "links": [ - "https://catalog.update.microsoft.com/v7/site/Search.aspx?q=KB4457131" - ] - }, - { - "id": "4462917", - "is_first": false, - "is_latest": false, - "links": [ - "https://catalog.update.microsoft.com/v7/site/Search.aspx?q=KB4462917" - ] - }, - { - "id": "4467691", - "is_first": false, - "is_latest": false, - "links": [ - "https://catalog.update.microsoft.com/v7/site/Search.aspx?q=KB4467691" - ] - }, - { - "id": "4471321", - "is_first": false, - "is_latest": true, - "links": [ - "https://catalog.update.microsoft.com/v7/site/Search.aspx?q=KB4471321" - ] - } - ], - "id": "CVE-2018-8116", - "link": "https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2018-8116", - "product": { - "family": "Windows", - "id": "10852", - "name": "Windows 10 Version 1607 for 32-bit Systems" - }, - "severity": "Medium", - "summary": "Microsoft Graphics Component Denial of Service Vulnerability", - "vulnerable": [ - "3213986", - "4013429", - "4015217", - "4019472", - "4022715", - "4025339", - "4034658", - "4038782", - "4041691", - "4048953", - "4053579", - "4056890", - "4074590", - "4088787" - ] - } -] diff --git a/pkg/process/v4/transformers/msrc/transform.go b/pkg/process/v4/transformers/msrc/transform.go deleted file mode 100644 index b15dd881..00000000 --- a/pkg/process/v4/transformers/msrc/transform.go +++ /dev/null @@ -1,114 +0,0 @@ -package msrc - -import ( - "fmt" - "strings" - - "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype-db/pkg/process/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" - "github.com/anchore/grype/grype/db/v4/namespace" - "github.com/anchore/grype/grype/distro" -) - -const ( - // TODO: tech debt from a previous design - feed = "microsoft" - groupPrefix = "msrc" -) - -func buildGrypeNamespace(feed, group string) (namespace.Namespace, error) { - if feed != "microsoft" || !strings.HasPrefix(group, "msrc:") { - return nil, fmt.Errorf("invalid source for feed=%s, group=%s", feed, group) - } - components := strings.Split(group, ":") - - if len(components) != 2 { - return nil, fmt.Errorf("invalid source for feed=%s, group=%s", feed, group) - } - ns, err := namespace.FromString(fmt.Sprintf("msrc:distro:%s:%s", distro.Windows, components[1])) - - if err != nil { - return nil, err - } - - return ns, nil -} - -// Transform gets called by the parser, which consumes entries from the JSON files previously pulled. Each VulnDBVulnerability represents -// a single unmarshalled entry from the feed service -func Transform(vulnerability unmarshal.MSRCVulnerability) ([]data.Entry, error) { - group := fmt.Sprintf("%s:%s", groupPrefix, vulnerability.Product.ID) - recordSource := fmt.Sprintf("%s:%s", feed, group) - grypeNamespace, err := buildGrypeNamespace(feed, group) - if err != nil { - return nil, err - } - - entryNamespace := grypeNamespace.String() - - // In anchore-enterprise windows analyzer, "base" represents unpatched windows images (images with no KBs). - // If a vulnerability exists for a Microsoft Product ID and the image has no KBs (which are patches), - // then the image must be vulnerable to the image. - //nolint:gocritic - versionConstraint := append(vulnerability.Vulnerable, "base") - - allVulns := []grypeDB.Vulnerability{ - { - ID: vulnerability.ID, - VersionConstraint: common.OrConstraints(versionConstraint...), - VersionFormat: "kb", - PackageName: grypeNamespace.Resolver().Normalize(vulnerability.Product.ID), - Namespace: entryNamespace, - Fix: getFix(vulnerability), - }, - } - - // create vulnerability metadata entry (a single entry keyed off of the vulnerability ID) - metadata := grypeDB.VulnerabilityMetadata{ - ID: vulnerability.ID, - DataSource: vulnerability.Link, - Namespace: entryNamespace, - RecordSource: recordSource, - Severity: vulnerability.Severity, - URLs: []string{vulnerability.Link}, - // There is no description for vulnerabilities from the feed service - // summary gives something like "windows information disclosure vulnerability" - //Description: vulnerability.Summary, - Cvss: []grypeDB.Cvss{ - { - Metrics: grypeDB.CvssMetrics{BaseScore: vulnerability.Cvss.BaseScore}, - Vector: vulnerability.Cvss.Vector, - }, - }, - } - - return transformers.NewEntries(allVulns, metadata), nil -} - -func getFix(entry unmarshal.MSRCVulnerability) grypeDB.Fix { - fixedInVersion := fixedInKB(entry) - fixState := grypeDB.FixedState - - if fixedInVersion == "" { - fixState = grypeDB.NotFixedState - } - - return grypeDB.Fix{ - Versions: []string{fixedInVersion}, - State: fixState, - } -} - -// fixedInKB finds the "latest" patch (KB id) amongst the available microsoft patches and returns it -// if the "latest" patch cannot be found, an error is returned -func fixedInKB(vulnerability unmarshal.MSRCVulnerability) string { - for _, fixedIn := range vulnerability.FixedIn { - if fixedIn.IsLatest { - return fixedIn.ID - } - } - return "" -} diff --git a/pkg/process/v4/transformers/msrc/transform_test.go b/pkg/process/v4/transformers/msrc/transform_test.go deleted file mode 100644 index d768204b..00000000 --- a/pkg/process/v4/transformers/msrc/transform_test.go +++ /dev/null @@ -1,120 +0,0 @@ -package msrc - -import ( - "os" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - testUtils "github.com/anchore/grype-db/pkg/process/tests" - "github.com/anchore/grype-db/pkg/provider/unmarshal" - grypeDB "github.com/anchore/grype/grype/db/v4" -) - -func TestUnmarshalMsrcVulnerabilities(t *testing.T) { - f, err := os.Open("test-fixtures/microsoft-msrc-0.json") - require.NoError(t, err) - defer testUtils.CloseFile(f) - - entries, err := unmarshal.MSRCVulnerabilityEntries(f) - require.NoError(t, err) - - assert.Equal(t, len(entries), 2) -} - -func TestParseMSRCEntry(t *testing.T) { - expectedVulns := []struct { - vulnerability grypeDB.Vulnerability - metadata grypeDB.VulnerabilityMetadata - }{ - { - vulnerability: grypeDB.Vulnerability{ - ID: "CVE-2019-0671", - VersionConstraint: `4480961 || 4483229 || 4487026 || 4489882 || base`, - VersionFormat: "kb", - PackageName: "10852", - Namespace: "msrc:distro:windows:10852", - Fix: grypeDB.Fix{ - Versions: []string{"4516044"}, - State: grypeDB.FixedState, - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2019-0671", - Severity: "High", - DataSource: "https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2019-0671", - URLs: []string{"https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2019-0671"}, - Description: "", - RecordSource: "microsoft:msrc:10852", - Namespace: "msrc:distro:windows:10852", - Cvss: []grypeDB.Cvss{ - { - Metrics: grypeDB.CvssMetrics{ - BaseScore: 7.8, - ImpactScore: nil, - }, - Vector: "CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H/E:P/RL:O/RC:C", - }, - }, - }, - }, - { - vulnerability: grypeDB.Vulnerability{ - ID: "CVE-2018-8116", - VersionConstraint: `3213986 || 4013429 || 4015217 || 4019472 || 4022715 || 4025339 || 4034658 || 4038782 || 4041691 || 4048953 || 4053579 || 4056890 || 4074590 || 4088787 || base`, - VersionFormat: "kb", - PackageName: "10852", - Namespace: "msrc:distro:windows:10852", - Fix: grypeDB.Fix{ - Versions: []string{"4345418"}, - State: grypeDB.FixedState, - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2018-8116", - Namespace: "msrc:distro:windows:10852", - DataSource: "https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2018-8116", - RecordSource: "microsoft:msrc:10852", - Severity: "Medium", - URLs: []string{"https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2018-8116"}, - Description: "", - Cvss: []grypeDB.Cvss{ - { - Metrics: grypeDB.CvssMetrics{ - BaseScore: 4.4, - ImpactScore: nil, - }, - Vector: "CVSS:3.0/AV:L/AC:H/PR:L/UI:R/S:U/C:N/I:N/A:H/E:P/RL:O/RC:C", - }, - }, - }, - }, - } - - f, err := os.Open("test-fixtures/microsoft-msrc-0.json") - require.NoError(t, err) - defer testUtils.CloseFile(f) - - entries, err := unmarshal.MSRCVulnerabilityEntries(f) - require.NoError(t, err) - - require.Equal(t, len(entries), 2) - - for idx, entry := range entries { - dataEntries, err := Transform(entry) - require.NoError(t, err) - assert.Len(t, dataEntries, 2) - expected := expectedVulns[idx] - for _, entry := range dataEntries { - switch vuln := entry.Data.(type) { - case grypeDB.Vulnerability: - assert.Equal(t, expected.vulnerability, vuln) - case grypeDB.VulnerabilityMetadata: - assert.Equal(t, expected.metadata, vuln) - default: - t.Fatalf("unexpected condition: data entry does not have a vulnerability or a metadata") - } - } - } -} diff --git a/pkg/process/v4/transformers/nvd/test-fixtures/compound-pkg.json b/pkg/process/v4/transformers/nvd/test-fixtures/compound-pkg.json deleted file mode 100644 index 8e658dcd..00000000 --- a/pkg/process/v4/transformers/nvd/test-fixtures/compound-pkg.json +++ /dev/null @@ -1,115 +0,0 @@ -{ - "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/v4/transformers/nvd/test-fixtures/invalid_cpe.json b/pkg/process/v4/transformers/nvd/test-fixtures/invalid_cpe.json deleted file mode 100644 index eac2ebd4..00000000 --- a/pkg/process/v4/transformers/nvd/test-fixtures/invalid_cpe.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "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/v4/transformers/nvd/test-fixtures/single-package-multi-distro.json b/pkg/process/v4/transformers/nvd/test-fixtures/single-package-multi-distro.json deleted file mode 100644 index ed108475..00000000 --- a/pkg/process/v4/transformers/nvd/test-fixtures/single-package-multi-distro.json +++ /dev/null @@ -1,174 +0,0 @@ -{ - "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/v4/transformers/nvd/test-fixtures/unmarshal-test.json b/pkg/process/v4/transformers/nvd/test-fixtures/unmarshal-test.json deleted file mode 100644 index 2dc698fa..00000000 --- a/pkg/process/v4/transformers/nvd/test-fixtures/unmarshal-test.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "cve": { - "id": "CVE-2003-0349", - "sourceIdentifier": "cve@mitre.org", - "published": "2003-07-24T04:00:00.000", - "lastModified": "2018-10-12T21:32:41.083", - "vulnStatus": "Modified", - "descriptions": [ - { - "lang": "en", - "value": "Buffer overflow in the streaming media component for logging multicast requests in the ISAPI for the logging capability of Microsoft Windows Media Services (nsiislog.dll), as installed in IIS 5.0, allows remote attackers to execute arbitrary code via a large POST request to nsiislog.dll." - }, - { - "lang": "es", - "value": "Desbordamiento de búfer en el componente de secuenciamiento (streaming) de medios para registrar peticiones de multidifusión en la librería ISAPI de la capacidad de registro (logging) de Microsoft Windows Media Services (nsiislog.dll), como el instalado en IIS 5.9, permite a atacantes remotos ejecutar código arbitrario mediante una petición POST larga a nsiislog.dll." - } - ], - "metrics": { - "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": false, - "obtainAllPrivilege": false, - "obtainUserPrivilege": true, - "obtainOtherPrivilege": false, - "userInteractionRequired": false - } - ] - }, - "weaknesses": [ - { - "source": "nvd@nist.gov", - "type": "Primary", - "description": [ - { - "lang": "en", - "value": "NVD-CWE-Other" - } - ] - } - ], - "configurations": [ - { - "nodes": [ - { - "operator": "OR", - "negate": false, - "cpeMatch": [ - { - "vulnerable": true, - "criteria": "cpe:2.3:o:microsoft:windows_2000:*:*:*:*:*:*:*:*", - "matchCriteriaId": "4E545C63-FE9C-4CA1-AF0F-D999D84D2AFD" - } - ] - } - ] - } - ], - "references": [ - { - "url": "http://marc.info/?l=bugtraq&m=105665030925504&w=2", - "source": "cve@mitre.org" - }, - { - "url": "http://securitytracker.com/id?1007059", - "source": "cve@mitre.org" - }, - { - "url": "http://www.kb.cert.org/vuls/id/113716", - "source": "cve@mitre.org", - "tags": [ - "US Government Resource" - ] - }, - { - "url": "http://www.ntbugtraq.com/default.asp?pid=36&sid=1&A2=ind0306&L=NTBUGTRAQ&P=R4563", - "source": "cve@mitre.org", - "tags": [ - "Exploit", - "Patch", - "Vendor Advisory" - ] - }, - { - "url": "https://docs.microsoft.com/en-us/security-updates/securitybulletins/2003/ms03-022", - "source": "cve@mitre.org" - }, - { - "url": "https://oval.cisecurity.org/repository/search/definition/oval%3Aorg.mitre.oval%3Adef%3A938", - "source": "cve@mitre.org" - } - ] - } -} diff --git a/pkg/process/v4/transformers/nvd/test-fixtures/version-range.json b/pkg/process/v4/transformers/nvd/test-fixtures/version-range.json deleted file mode 100644 index 3df5b86d..00000000 --- a/pkg/process/v4/transformers/nvd/test-fixtures/version-range.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "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/v4/transformers/nvd/transform.go b/pkg/process/v4/transformers/nvd/transform.go deleted file mode 100644 index c7bf18bc..00000000 --- a/pkg/process/v4/transformers/nvd/transform.go +++ /dev/null @@ -1,109 +0,0 @@ -package nvd - -import ( - "fmt" - - "github.com/anchore/grype-db/internal" - "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype-db/pkg/process/v4/transformers" - "github.com/anchore/grype-db/pkg/provider/unmarshal" - "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd" - grypeDB "github.com/anchore/grype/grype/db/v4" - "github.com/anchore/grype/grype/db/v4/namespace" -) - -const ( - // TODO: tech debt from a previous design - feed = "nvdv2" - group = "nvdv2:cves" -) - -func buildGrypeNamespace(feed, group string) (namespace.Namespace, error) { - if feed != "nvdv2" || group != "nvdv2:cves" { - return nil, fmt.Errorf("invalid source for feed=%s, group=%s", feed, group) - } - - ns, err := namespace.FromString("nvd:cpe") - - if err != nil { - return nil, err - } - - return ns, nil -} - -func Transform(vulnerability unmarshal.NVDVulnerability) ([]data.Entry, error) { - recordSource := fmt.Sprintf("%s:%s", feed, group) - grypeNamespace, err := buildGrypeNamespace(feed, group) - if err != nil { - return nil, err - } - - entryNamespace := grypeNamespace.String() - - uniquePkgs := findUniquePkgs(vulnerability.Configurations...) - - // extract all links - var links []string - for _, externalRefs := range vulnerability.References { - // TODO: should we capture other information here? - if externalRefs.URL != "" { - links = append(links, externalRefs.URL) - } - } - - // duplicate the vulnerabilities based on the set of unique packages the vulnerability is for - var allVulns []grypeDB.Vulnerability - for _, p := range uniquePkgs.All() { - matches := uniquePkgs.Matches(p) - cpes := internal.NewStringSet() - for _, m := range matches { - cpes.Add(grypeNamespace.Resolver().Normalize(m.Criteria)) - } - - // create vulnerability entry - allVulns = append(allVulns, grypeDB.Vulnerability{ - ID: vulnerability.ID, - VersionConstraint: buildConstraints(uniquePkgs.Matches(p)), - VersionFormat: "unknown", - PackageName: grypeNamespace.Resolver().Normalize(p.Product), - Namespace: entryNamespace, - CPEs: cpes.ToSlice(), - Fix: grypeDB.Fix{ - State: grypeDB.UnknownFixState, - }, - }) - } - - // create vulnerability metadata entry (a single entry keyed off of the vulnerability ID) - - allCVSS := vulnerability.CVSS() - metadata := grypeDB.VulnerabilityMetadata{ - ID: vulnerability.ID, - DataSource: "https://nvd.nist.gov/vuln/detail/" + vulnerability.ID, - Namespace: entryNamespace, - RecordSource: recordSource, - Severity: nvd.CvssSummaries(allCVSS).Sorted().Severity(), - URLs: links, - Description: vulnerability.Description(), - Cvss: getCvss(allCVSS...), - } - - return transformers.NewEntries(allVulns, metadata), nil -} - -func getCvss(cvss ...nvd.CvssSummary) []grypeDB.Cvss { - var results []grypeDB.Cvss - for _, c := range cvss { - results = append(results, grypeDB.Cvss{ - Version: c.Version, - Vector: c.Vector, - Metrics: grypeDB.CvssMetrics{ - BaseScore: c.BaseScore, - ExploitabilityScore: c.ExploitabilityScore, - ImpactScore: c.ImpactScore, - }, - }) - } - return results -} diff --git a/pkg/process/v4/transformers/nvd/transform_test.go b/pkg/process/v4/transformers/nvd/transform_test.go deleted file mode 100644 index 277527b8..00000000 --- a/pkg/process/v4/transformers/nvd/transform_test.go +++ /dev/null @@ -1,257 +0,0 @@ -package nvd - -import ( - "os" - "testing" - - "github.com/go-test/deep" - "github.com/google/go-cmp/cmp" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - testUtils "github.com/anchore/grype-db/pkg/process/tests" - "github.com/anchore/grype-db/pkg/provider/unmarshal" - grypeDB "github.com/anchore/grype/grype/db/v4" -) - -func TestUnmarshalNVDVulnerabilitiesEntries(t *testing.T) { - f, err := os.Open("test-fixtures/unmarshal-test.json") - require.NoError(t, err) - defer testUtils.CloseFile(f) - - entries, err := unmarshal.NvdVulnerabilityEntries(f) - require.NoError(t, err) - - assert.Len(t, entries, 1) -} - -func TestParseAllNVDVulnerabilityEntries(t *testing.T) { - - tests := []struct { - name string - numEntries int - fixture string - vulns []grypeDB.Vulnerability - metadata grypeDB.VulnerabilityMetadata - }{ - { - name: "AppVersionRange", - numEntries: 1, - fixture: "test-fixtures/version-range.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2018-5487", - PackageName: "oncommand_unified_manager", - VersionConstraint: ">= 7.2, <= 7.3", - VersionFormat: "unknown", // TODO: this should reference a format, yes? (not a string) - Namespace: "nvd:cpe", - CPEs: []string{"cpe:2.3:a:netapp:oncommand_unified_manager:*:*:*:*:*:*:*:*"}, - Fix: grypeDB.Fix{ - State: "unknown", - }, - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2018-5487", - DataSource: "https://nvd.nist.gov/vuln/detail/CVE-2018-5487", - Namespace: "nvd:cpe", - RecordSource: "nvdv2:nvdv2:cves", - Severity: "Critical", - URLs: []string{"https://security.netapp.com/advisory/ntap-20180523-0001/"}, - 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.", - Cvss: []grypeDB.Cvss{ - { - Metrics: grypeDB.NewCvssMetrics( - 7.5, - 10, - 6.4, - ), - Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P", - Version: "2.0", - }, - { - Metrics: grypeDB.NewCvssMetrics( - 9.8, - 3.9, - 5.9, - ), - Vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", - Version: "3.0", - }, - }, - }, - }, - { - name: "App+OS", - numEntries: 1, - fixture: "test-fixtures/single-package-multi-distro.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2018-1000222", - PackageName: "libgd", - VersionConstraint: "= 2.2.5", - VersionFormat: "unknown", // TODO: this should reference a format, yes? (not a string) - Namespace: "nvd:cpe", - CPEs: []string{"cpe:2.3:a:libgd:libgd:2.2.5:*:*:*:*:*:*:*"}, - Fix: grypeDB.Fix{ - State: "unknown", - }, - }, - // TODO: Question: should this match also the OS's? (as in the vulnerable_cpes list)... this seems wrong! - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2018-1000222", - DataSource: "https://nvd.nist.gov/vuln/detail/CVE-2018-1000222", - Namespace: "nvd:cpe", - RecordSource: "nvdv2:nvdv2:cves", - Severity: "High", - URLs: []string{"https://github.com/libgd/libgd/issues/447", "https://lists.debian.org/debian-lts-announce/2019/01/msg00028.html", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/3CZ2QADQTKRHTGB2AHD7J4QQNDLBEMM6/", "https://security.gentoo.org/glsa/201903-18", "https://usn.ubuntu.com/3755-1/"}, - 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.", - Cvss: []grypeDB.Cvss{ - { - Metrics: grypeDB.NewCvssMetrics( - 6.8, - 8.6, - 6.4, - ), - Vector: "AV:N/AC:M/Au:N/C:P/I:P/A:P", - Version: "2.0", - }, - { - Metrics: grypeDB.NewCvssMetrics( - 8.8, - 2.8, - 5.9, - ), - Vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", - Version: "3.0", - }, - }, - }, - }, - { - name: "AppCompoundVersionRange", - numEntries: 1, - fixture: "test-fixtures/compound-pkg.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2018-10189", - PackageName: "mautic", - VersionConstraint: ">= 1.0.0, <= 1.4.1 || >= 2.0.0, < 2.13.0", - VersionFormat: "unknown", - Namespace: "nvd:cpe", - CPEs: []string{"cpe:2.3:a:mautic:mautic:*:*:*:*:*:*:*:*"}, // note: entry was dedupicated - Fix: grypeDB.Fix{ - State: "unknown", - }, - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2018-10189", - DataSource: "https://nvd.nist.gov/vuln/detail/CVE-2018-10189", - Namespace: "nvd:cpe", - RecordSource: "nvdv2:nvdv2:cves", - Severity: "High", - URLs: []string{"https://github.com/mautic/mautic/releases/tag/2.13.0"}, - 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.", - Cvss: []grypeDB.Cvss{ - { - Metrics: grypeDB.NewCvssMetrics( - 5, - 10, - 2.9, - ), - Vector: "AV:N/AC:L/Au:N/C:P/I:N/A:N", - Version: "2.0", - }, - { - Metrics: grypeDB.NewCvssMetrics( - 7.5, - 3.9, - 3.6, - ), - Vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", - Version: "3.0", - }, - }, - }, - }, - { - // we always keep the metadata even though there are no vulnerability entries for it - name: "InvalidCPE", - numEntries: 1, - fixture: "test-fixtures/invalid_cpe.json", - vulns: nil, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2015-8978", - Namespace: "nvd:cpe", - DataSource: "https://nvd.nist.gov/vuln/detail/CVE-2015-8978", - RecordSource: "nvdv2:nvdv2:cves", - Severity: "High", - URLs: []string{ - "http://cpansearch.perl.org/src/PHRED/SOAP-Lite-1.20/Changes", - "http://www.securityfocus.com/bid/94487", - }, - 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.", - Cvss: []grypeDB.Cvss{ - { - Metrics: grypeDB.NewCvssMetrics( - 5, - 10, - 2.9, - ), - Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P", - Version: "2.0", - }, - { - Metrics: grypeDB.NewCvssMetrics( - 7.5, - 3.9, - 3.6, - ), - Vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", - Version: "3.0", - }, - }, - }, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - f, err := os.Open(test.fixture) - require.NoError(t, err) - t.Cleanup(func() { - assert.NoError(t, f.Close()) - }) - - entries, err := unmarshal.NvdVulnerabilityEntries(f) - require.NoError(t, err) - - var vulns []grypeDB.Vulnerability - for _, entry := range entries { - dataEntries, err := Transform(entry.Cve) - require.NoError(t, err) - - for _, entry := range dataEntries { - switch vuln := entry.Data.(type) { - case grypeDB.Vulnerability: - vulns = append(vulns, vuln) - case grypeDB.VulnerabilityMetadata: - // check metadata - if diff := deep.Equal(test.metadata, vuln); diff != nil { - for _, d := range diff { - t.Errorf("metadata diff: %+v", d) - } - } - default: - t.Fatalf("unexpected condition: data entry does not have a vulnerability or a metadata") - } - } - } - - if diff := cmp.Diff(test.vulns, vulns); diff != "" { - t.Errorf("vulnerabilities do not match (-want +got):\n%s", diff) - } - }) - } -} diff --git a/pkg/process/v4/transformers/nvd/unique_pkg.go b/pkg/process/v4/transformers/nvd/unique_pkg.go deleted file mode 100644 index 20a62777..00000000 --- a/pkg/process/v4/transformers/nvd/unique_pkg.go +++ /dev/null @@ -1,115 +0,0 @@ -package nvd - -import ( - "fmt" - "strings" - - "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/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(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 only interested in applications, not hardware or operating systems - if c.Part() != cpe.Application { - return nil, nil - } - - return &pkgCandidate{ - Product: c.Product().String(), - Vendor: c.Vendor().String(), - TargetSoftware: c.TargetSw().String(), - }, nil -} - -func findUniquePkgs(cfgs ...nvd.Configuration) uniquePkgTracker { - set := newUniquePkgTracker() - for _, c := range cfgs { - _findUniquePkgs(set, c.Nodes...) - } - return set -} - -func _findUniquePkgs(set uniquePkgTracker, ns ...nvd.Node) { - if len(ns) == 0 { - return - } - for _, node := range ns { - for _, match := range node.CpeMatch { - candidate, err := newPkgCandidate(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.Add(*candidate, match) - } - } - } -} - -func buildConstraints(matches []nvd.CpeMatch) string { - constraints := make([]string, 0) - for _, match := range matches { - constraints = append(constraints, buildConstraint(match)) - } - return common.OrConstraints(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() - if version != ANY && version != NA { - constraints = append(constraints, fmt.Sprintf("= %s", version)) - } - } - - return strings.Join(constraints, ", ") -} diff --git a/pkg/process/v4/transformers/nvd/unique_pkg_test.go b/pkg/process/v4/transformers/nvd/unique_pkg_test.go deleted file mode 100644 index dc662c36..00000000 --- a/pkg/process/v4/transformers/nvd/unique_pkg_test.go +++ /dev/null @@ -1,353 +0,0 @@ -package nvd - -import ( - "testing" - - "github.com/sergi/go-diff/diffmatchpatch" - - "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd" -) - -func newUniquePkgTrackerFromSlice(candidates []pkgCandidate) uniquePkgTracker { - set := newUniquePkgTracker() - for _, c := range candidates { - set[c] = nil - } - return set -} - -func TestFindUniquePkgs(t *testing.T) { - tests := []struct { - name string - nodes []nvd.Node - 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", - }, - }), - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - actual := findUniquePkgs(nvd.Configuration{Nodes: test.nodes}) - 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: '%s'", test.expected) - t.Errorf("Got : '%s'", actual) - t.Errorf("Diff : '%s'", dmp.DiffPrettyText(diffs)) - } - }) - } - -} diff --git a/pkg/process/v4/transformers/nvd/unique_pkg_tracker.go b/pkg/process/v4/transformers/nvd/unique_pkg_tracker.go deleted file mode 100644 index 2b7e405d..00000000 --- a/pkg/process/v4/transformers/nvd/unique_pkg_tracker.go +++ /dev/null @@ -1,64 +0,0 @@ -package nvd - -import ( - "sort" - - "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd" -) - -type uniquePkgTracker map[pkgCandidate][]nvd.CpeMatch - -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 (s uniquePkgTracker) Matches(i pkgCandidate) []nvd.CpeMatch { - return s[i] -} - -func (s uniquePkgTracker) Add(i pkgCandidate, match nvd.CpeMatch) { - if _, ok := s[i]; !ok { - s[i] = make([]nvd.CpeMatch, 0) - } - s[i] = append(s[i], match) -} - -func (s uniquePkgTracker) Remove(i pkgCandidate) { - delete(s, i) -} - -func (s uniquePkgTracker) Contains(i pkgCandidate) bool { - _, ok := s[i] - return ok -} - -func (s uniquePkgTracker) All() []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/v4/transformers/os/test-fixtures/alpine-3.9.json b/pkg/process/v4/transformers/os/test-fixtures/alpine-3.9.json deleted file mode 100644 index b9d84395..00000000 --- a/pkg/process/v4/transformers/os/test-fixtures/alpine-3.9.json +++ /dev/null @@ -1,28 +0,0 @@ -[ - { - "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/v4/transformers/os/test-fixtures/amazon-multiple-kernel-advisories.json b/pkg/process/v4/transformers/os/test-fixtures/amazon-multiple-kernel-advisories.json deleted file mode 100644 index 82f2b45b..00000000 --- a/pkg/process/v4/transformers/os/test-fixtures/amazon-multiple-kernel-advisories.json +++ /dev/null @@ -1,104 +0,0 @@ -[ - { - "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/v4/transformers/os/test-fixtures/amzn.json b/pkg/process/v4/transformers/os/test-fixtures/amzn.json deleted file mode 100644 index a862c32e..00000000 --- a/pkg/process/v4/transformers/os/test-fixtures/amzn.json +++ /dev/null @@ -1,49 +0,0 @@ -[ - { - "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/v4/transformers/os/test-fixtures/debian-8-multiple-entries-for-same-package.json b/pkg/process/v4/transformers/os/test-fixtures/debian-8-multiple-entries-for-same-package.json deleted file mode 100644 index 5025b56e..00000000 --- a/pkg/process/v4/transformers/os/test-fixtures/debian-8-multiple-entries-for-same-package.json +++ /dev/null @@ -1,62 +0,0 @@ -[ - { - "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/v4/transformers/os/test-fixtures/debian-8.json b/pkg/process/v4/transformers/os/test-fixtures/debian-8.json deleted file mode 100644 index a758f13c..00000000 --- a/pkg/process/v4/transformers/os/test-fixtures/debian-8.json +++ /dev/null @@ -1,62 +0,0 @@ -[ - { - "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/v4/transformers/os/test-fixtures/ol-8-modules.json b/pkg/process/v4/transformers/os/test-fixtures/ol-8-modules.json deleted file mode 100644 index f1d7372b..00000000 --- a/pkg/process/v4/transformers/os/test-fixtures/ol-8-modules.json +++ /dev/null @@ -1,36 +0,0 @@ -[ - { - "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/v4/transformers/os/test-fixtures/ol-8.json b/pkg/process/v4/transformers/os/test-fixtures/ol-8.json deleted file mode 100644 index 09439ece..00000000 --- a/pkg/process/v4/transformers/os/test-fixtures/ol-8.json +++ /dev/null @@ -1,42 +0,0 @@ -[ - { - "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/v4/transformers/os/test-fixtures/rhel-8-modules.json b/pkg/process/v4/transformers/os/test-fixtures/rhel-8-modules.json deleted file mode 100644 index c0400ad5..00000000 --- a/pkg/process/v4/transformers/os/test-fixtures/rhel-8-modules.json +++ /dev/null @@ -1,75 +0,0 @@ -[ - { - "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/v4/transformers/os/test-fixtures/rhel-8.json b/pkg/process/v4/transformers/os/test-fixtures/rhel-8.json deleted file mode 100644 index 2779708c..00000000 --- a/pkg/process/v4/transformers/os/test-fixtures/rhel-8.json +++ /dev/null @@ -1,57 +0,0 @@ -[ - { - "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/v4/transformers/os/test-fixtures/unmarshal-test.json b/pkg/process/v4/transformers/os/test-fixtures/unmarshal-test.json deleted file mode 100644 index edc6d25b..00000000 --- a/pkg/process/v4/transformers/os/test-fixtures/unmarshal-test.json +++ /dev/null @@ -1,104 +0,0 @@ -[ - { - "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" - } - }, - { - "Vulnerability": { - "Description": "", - "FixedIn": [ - { - "Name": "kernel-livepatch-4.14.173-137.228", - "NamespaceName": "amzn:2", - "Version": "1.0-3.amzn2", - "VersionFormat": "rpm" - }, - { - "Name": "kernel-livepatch-4.14.173-137.228-debuginfo", - "NamespaceName": "amzn:2", - "Version": "1.0-3.amzn2", - "VersionFormat": "rpm" - } - ], - "Link": "https://alas.aws.amazon.com/AL2/ALASLIVEPATCH-2020-012.html", - "Metadata": { - "CVE": [ - {"Name": "CVE-2020-12657"} - ] - }, - "Name": "ALASLIVEPATCH-2020-012", - "NamespaceName": "amzn:2", - "Severity": "High" - } - }, - { - "Vulnerability": { - "Description": "", - "FixedIn": [ - { - "Name": "kernel-livepatch-4.14.171-136.231", - "NamespaceName": "amzn:2", - "Version": "1.0-5.amzn2", - "VersionFormat": "rpm" - }, - { - "Name": "kernel-livepatch-4.14.171-136.231-debuginfo", - "NamespaceName": "amzn:2", - "Version": "1.0-5.amzn2", - "VersionFormat": "rpm" - } - ], - "Link": "https://alas.aws.amazon.com/AL2/ALASLIVEPATCH-2020-011.html", - "Metadata": { - "CVE": [ - {"Name": "CVE-2020-12657"} - ] - }, - "Name": "ALASLIVEPATCH-2020-011", - "NamespaceName": "amzn:2", - "Severity": "High" - } - } -] \ No newline at end of file diff --git a/pkg/process/v4/transformers/os/transform.go b/pkg/process/v4/transformers/os/transform.go deleted file mode 100644 index 5a9796b4..00000000 --- a/pkg/process/v4/transformers/os/transform.go +++ /dev/null @@ -1,218 +0,0 @@ -package os - -import ( - "fmt" - "strings" - - "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype-db/pkg/process/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" - "github.com/anchore/grype/grype/db/v4/namespace" - "github.com/anchore/grype/grype/distro" -) - -const ( - // TODO: tech debt from a previous design - feed = "vulnerabilities" -) - -func Transform(vulnerability unmarshal.OSVulnerability) ([]data.Entry, error) { - group := vulnerability.Vulnerability.NamespaceName - - var allVulns []grypeDB.Vulnerability - - recordSource := fmt.Sprintf("%s:%s", feed, group) - grypeNamespace, err := buildGrypeNamespace(feed, group) - if err != nil { - return nil, err - } - - entryNamespace := grypeNamespace.String() - vulnerability.Vulnerability.FixedIn = vulnerability.Vulnerability.FixedIn.FilterToHighestModularity() - - for idx, fixedInEntry := range vulnerability.Vulnerability.FixedIn { - // create vulnerability entry - allVulns = append(allVulns, grypeDB.Vulnerability{ - ID: vulnerability.Vulnerability.Name, - VersionConstraint: enforceConstraint(fixedInEntry.Version, fixedInEntry.VersionFormat, vulnerability.Vulnerability.Name), - VersionFormat: fixedInEntry.VersionFormat, - PackageName: grypeNamespace.Resolver().Normalize(fixedInEntry.Name), - Namespace: entryNamespace, - RelatedVulnerabilities: getRelatedVulnerabilities(vulnerability), - Fix: getFix(vulnerability, idx), - Advisories: getAdvisories(vulnerability, idx), - }) - } - - // create vulnerability metadata entry (a single entry keyed off of the vulnerability ID) - metadata := grypeDB.VulnerabilityMetadata{ - ID: vulnerability.Vulnerability.Name, - Namespace: entryNamespace, - DataSource: vulnerability.Vulnerability.Link, - RecordSource: recordSource, - Severity: vulnerability.Vulnerability.Severity, - URLs: getLinks(vulnerability), - Description: vulnerability.Vulnerability.Description, - Cvss: getCvss(vulnerability), - } - - return transformers.NewEntries(allVulns, metadata), nil -} - -func buildGrypeNamespace(feed, group string) (namespace.Namespace, error) { - if feed != "vulnerabilities" { - return nil, fmt.Errorf("unable to determine grype namespace for enterprise feed=%s, group=%s", feed, group) - } - - feedGroupComponents := strings.Split(group, ":") - - if len(feedGroupComponents) < 2 { - return nil, fmt.Errorf("unable to determine grype namespace for enterprise feed=%s, group=%s", feed, group) - } - - // Currently known enterprise feed groups are expected to be of the form {distroID}:{version} - feedGroupDistroID := feedGroupComponents[0] - d, ok := distro.IDMapping[feedGroupDistroID] - if !ok { - return nil, fmt.Errorf("unable to determine grype namespace for enterprise feed=%s, group=%s", feed, group) - } - - providerName := d.String() - - switch d { - case distro.OracleLinux: - providerName = "oracle" - case distro.AmazonLinux: - providerName = "amazon" - } - - ns, err := namespace.FromString(fmt.Sprintf("%s:distro:%s:%s", providerName, d.String(), feedGroupComponents[1])) - - if err != nil { - return nil, err - } - - return ns, nil -} - -func getLinks(entry unmarshal.OSVulnerability) []string { - // find all URLs related to the vulnerability - links := []string{entry.Vulnerability.Link} - if entry.Vulnerability.Metadata.CVE != nil { - for _, cve := range entry.Vulnerability.Metadata.CVE { - if cve.Link != "" { - links = append(links, cve.Link) - } - } - } - return links -} - -func getCvss(entry unmarshal.OSVulnerability) (cvss []grypeDB.Cvss) { - for _, vendorCvss := range entry.Vulnerability.CVSS { - cvss = append(cvss, grypeDB.Cvss{ - Version: vendorCvss.Version, - Vector: vendorCvss.VectorString, - Metrics: grypeDB.NewCvssMetrics( - vendorCvss.BaseMetrics.BaseScore, - vendorCvss.BaseMetrics.ExploitabilityScore, - vendorCvss.BaseMetrics.ImpactScore, - ), - VendorMetadata: transformers.VendorBaseMetrics{ - BaseSeverity: vendorCvss.BaseMetrics.BaseSeverity, - Status: vendorCvss.Status, - }, - }) - } - return cvss -} - -func getAdvisories(entry unmarshal.OSVulnerability, idx int) (advisories []grypeDB.Advisory) { - fixedInEntry := entry.Vulnerability.FixedIn[idx] - - for _, advisory := range fixedInEntry.VendorAdvisory.AdvisorySummary { - advisories = append(advisories, grypeDB.Advisory{ - ID: advisory.ID, - Link: advisory.Link, - }) - } - return advisories -} - -func getFix(entry unmarshal.OSVulnerability, idx int) grypeDB.Fix { - fixedInEntry := entry.Vulnerability.FixedIn[idx] - - var fixedInVersions []string - fixedInVersion := common.CleanFixedInVersion(fixedInEntry.Version) - if fixedInVersion != "" { - fixedInVersions = append(fixedInVersions, fixedInVersion) - } - - fixState := grypeDB.NotFixedState - if len(fixedInVersions) > 0 { - fixState = grypeDB.FixedState - } else if fixedInEntry.VendorAdvisory.NoAdvisory { - fixState = grypeDB.WontFixState - } - - return grypeDB.Fix{ - Versions: fixedInVersions, - State: fixState, - } -} - -func getRelatedVulnerabilities(entry unmarshal.OSVulnerability) (vulns []grypeDB.VulnerabilityReference) { - // associate related vulnerabilities from the NVD namespace - if strings.HasPrefix(entry.Vulnerability.Name, "CVE") { - vulns = append(vulns, grypeDB.VulnerabilityReference{ - ID: entry.Vulnerability.Name, - Namespace: "nvd:cpe", - }) - } - - // note: an example of multiple CVEs for a record is centos:5 RHSA-2007:0055 which maps to CVE-2007-0002 and CVE-2007-1466 - for _, ref := range entry.Vulnerability.Metadata.CVE { - vulns = append(vulns, grypeDB.VulnerabilityReference{ - ID: ref.Name, - Namespace: "nvd:cpe", - }) - } - return vulns -} - -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 -} - -func enforceConstraint(constraint, format, vulnerabilityID string) string { - constraint = common.CleanConstraint(constraint) - if len(constraint) == 0 { - return "" - } - switch strings.ToLower(format) { - case "semver": - return common.EnforceSemVerConstraint(constraint) - default: - // the passed constraint is a fixed version - return deriveConstraintFromFix(constraint, vulnerabilityID) - } -} diff --git a/pkg/process/v4/transformers/os/transform_test.go b/pkg/process/v4/transformers/os/transform_test.go deleted file mode 100644 index 745916a8..00000000 --- a/pkg/process/v4/transformers/os/transform_test.go +++ /dev/null @@ -1,757 +0,0 @@ -package os - -import ( - "os" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - testUtils "github.com/anchore/grype-db/pkg/process/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" -) - -func TestUnmarshalOSVulnerabilitiesEntries(t *testing.T) { - f, err := os.Open("test-fixtures/unmarshal-test.json") - require.NoError(t, err) - defer testUtils.CloseFile(f) - - entries, err := unmarshal.OSVulnerabilityEntries(f) - require.NoError(t, err) - - assert.Len(t, entries, 3) -} - -func TestParseVulnerabilitiesEntry(t *testing.T) { - tests := []struct { - name string - numEntries int - fixture string - vulns []grypeDB.Vulnerability - metadata grypeDB.VulnerabilityMetadata - feed, group string - }{ - { - name: "Amazon", - numEntries: 1, - fixture: "test-fixtures/amzn.json", - feed: "vulnerabilities", - group: "amzn:2", - vulns: []grypeDB.Vulnerability{ - { - ID: "ALAS-2018-1106", - VersionConstraint: "< 1.3.8.4-15.amzn2.0.1", - VersionFormat: "rpm", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2018-14648", - Namespace: "nvd:cpe", - }, - }, - PackageName: "389-ds-base", - Namespace: "amazon:distro:amazonlinux:2", - Fix: grypeDB.Fix{ - Versions: []string{"1.3.8.4-15.amzn2.0.1"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "ALAS-2018-1106", - VersionConstraint: "< 1.3.8.4-15.amzn2.0.1", - VersionFormat: "rpm", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2018-14648", - Namespace: "nvd:cpe", - }, - }, - PackageName: "389-ds-base-debuginfo", - Namespace: "amazon:distro:amazonlinux:2", - Fix: grypeDB.Fix{ - Versions: []string{"1.3.8.4-15.amzn2.0.1"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "ALAS-2018-1106", - VersionConstraint: "< 1.3.8.4-15.amzn2.0.1", - VersionFormat: "rpm", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2018-14648", - Namespace: "nvd:cpe", - }, - }, - PackageName: "389-ds-base-devel", - Namespace: "amazon:distro:amazonlinux:2", - Fix: grypeDB.Fix{ - Versions: []string{"1.3.8.4-15.amzn2.0.1"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "ALAS-2018-1106", - VersionConstraint: "< 1.3.8.4-15.amzn2.0.1", - VersionFormat: "rpm", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2018-14648", - Namespace: "nvd:cpe", - }, - }, - PackageName: "389-ds-base-libs", - Namespace: "amazon:distro:amazonlinux:2", - Fix: grypeDB.Fix{ - Versions: []string{"1.3.8.4-15.amzn2.0.1"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "ALAS-2018-1106", - VersionConstraint: "< 1.3.8.4-15.amzn2.0.1", - VersionFormat: "rpm", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2018-14648", - Namespace: "nvd:cpe", - }, - }, - PackageName: "389-ds-base-snmp", - Namespace: "amazon:distro:amazonlinux:2", - Fix: grypeDB.Fix{ - Versions: []string{"1.3.8.4-15.amzn2.0.1"}, - State: grypeDB.FixedState, - }, - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "ALAS-2018-1106", - Namespace: "amazon:distro:amazonlinux:2", - DataSource: "https://alas.aws.amazon.com/AL2/ALAS-2018-1106.html", - RecordSource: "vulnerabilities:amzn:2", - Severity: "Medium", - URLs: []string{"https://alas.aws.amazon.com/AL2/ALAS-2018-1106.html"}, - }, - }, - { - name: "Debian", - numEntries: 1, - fixture: "test-fixtures/debian-8.json", - feed: "vulnerabilities", - group: "debian:8", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2008-7220", - PackageName: "asterisk", - VersionConstraint: "< 1:1.6.2.0~rc3-1", - VersionFormat: "dpkg", - Namespace: "debian:distro:debian:8", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2008-7220", - Namespace: "nvd:cpe", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"1:1.6.2.0~rc3-1"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "CVE-2008-7220", - PackageName: "auth2db", - VersionConstraint: "< 0.2.5-2+dfsg-1", - VersionFormat: "dpkg", - Namespace: "debian:distro:debian:8", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2008-7220", - Namespace: "nvd:cpe", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"0.2.5-2+dfsg-1"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "CVE-2008-7220", - PackageName: "exaile", - VersionConstraint: "< 0.2.14+debian-2.2", - VersionFormat: "dpkg", - Namespace: "debian:distro:debian:8", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2008-7220", - Namespace: "nvd:cpe", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"0.2.14+debian-2.2"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "CVE-2008-7220", - PackageName: "wordpress", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2008-7220", - Namespace: "nvd:cpe", - }, - }, - Fix: grypeDB.Fix{ - State: grypeDB.NotFixedState, - }, - VersionConstraint: "", - VersionFormat: "dpkg", - Namespace: "debian:distro:debian:8", - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2008-7220", - Namespace: "debian:distro:debian:8", - DataSource: "https://security-tracker.debian.org/tracker/CVE-2008-7220", - RecordSource: "vulnerabilities:debian:8", - Severity: "High", - URLs: []string{"https://security-tracker.debian.org/tracker/CVE-2008-7220"}, - Description: "", - }, - }, - { - name: "RHEL", - numEntries: 1, - fixture: "test-fixtures/rhel-8.json", - feed: "vulnerabilities", - group: "rhel:8", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2020-6819", - PackageName: "firefox", - VersionConstraint: "< 0:68.6.1-1.el8_1", - VersionFormat: "rpm", - Namespace: "redhat:distro:redhat:8", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2020-6819", - Namespace: "nvd:cpe", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"0:68.6.1-1.el8_1"}, - State: grypeDB.FixedState, - }, - Advisories: []grypeDB.Advisory{ - { - ID: "RHSA-2020:1341", - Link: "https://access.redhat.com/errata/RHSA-2020:1341", - }, - }, - }, - { - ID: "CVE-2020-6819", - PackageName: "thunderbird", - VersionConstraint: "< 0:68.7.0-1.el8_1", - VersionFormat: "rpm", - Namespace: "redhat:distro:redhat:8", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2020-6819", - Namespace: "nvd:cpe", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"0:68.7.0-1.el8_1"}, - State: grypeDB.FixedState, - }, - Advisories: []grypeDB.Advisory{ - { - ID: "RHSA-2020:1495", - Link: "https://access.redhat.com/errata/RHSA-2020:1495", - }, - }, - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2020-6819", - DataSource: "https://access.redhat.com/security/cve/CVE-2020-6819", - Namespace: "redhat:distro:redhat:8", - RecordSource: "vulnerabilities:rhel:8", - Severity: "Critical", - URLs: []string{"https://access.redhat.com/security/cve/CVE-2020-6819"}, - 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.", - Cvss: []grypeDB.Cvss{ - { - Version: "3.1", - Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", - Metrics: grypeDB.NewCvssMetrics( - 8.8, - 2.8, - 5.9, - ), - VendorMetadata: transformers.VendorBaseMetrics{ - Status: "verified", - BaseSeverity: "High", - }, - }, - }, - }, - }, - { - name: "RHEL with modularity", - numEntries: 1, - fixture: "test-fixtures/rhel-8-modules.json", - feed: "vulnerabilities", - group: "rhel:8", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2020-14350", - PackageName: "postgresql", - VersionConstraint: "< 0:12.5-1.module+el8.3.0+9042+664538f4", - VersionFormat: "rpm", - Namespace: "redhat:distro:redhat:8", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2020-14350", - Namespace: "nvd:cpe", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"0:12.5-1.module+el8.3.0+9042+664538f4"}, - State: grypeDB.FixedState, - }, - Advisories: []grypeDB.Advisory{ - { - ID: "RHSA-2020:5620", - Link: "https://access.redhat.com/errata/RHSA-2020:5620", - }, - }, - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2020-14350", - DataSource: "https://access.redhat.com/security/cve/CVE-2020-14350", - Namespace: "redhat:distro:redhat:8", - RecordSource: "vulnerabilities:rhel:8", - Severity: "Medium", - URLs: []string{"https://access.redhat.com/security/cve/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.", - Cvss: []grypeDB.Cvss{ - { - Version: "3.1", - Vector: "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:H", - Metrics: grypeDB.NewCvssMetrics( - 7.1, - 1.2, - 5.9, - ), - VendorMetadata: transformers.VendorBaseMetrics{ - Status: "verified", - BaseSeverity: "High", - }, - }, - }, - }, - }, - { - name: "Alpine", - numEntries: 1, - fixture: "test-fixtures/alpine-3.9.json", - feed: "vulnerabilities", - group: "alpine:3.9", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2018-19967", - PackageName: "xen", - VersionConstraint: "< 4.11.1-r0", - VersionFormat: "apk", - Namespace: "alpine:distro:alpine:3.9", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2018-19967", - Namespace: "nvd:cpe", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"4.11.1-r0"}, - State: grypeDB.FixedState, - }, - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2018-19967", - DataSource: "http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-19967", - Namespace: "alpine:distro:alpine:3.9", - RecordSource: "vulnerabilities:alpine:3.9", - Severity: "Medium", - URLs: []string{"http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-19967"}, - Description: "", - }, - }, - { - name: "Oracle", - numEntries: 1, - fixture: "test-fixtures/ol-8.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "ELSA-2020-2550", - PackageName: "libexif", - VersionConstraint: "< 0:0.6.21-17.el8_2", - VersionFormat: "rpm", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2020-13112", - Namespace: "nvd:cpe", - }, - }, - Namespace: "oracle:distro:oraclelinux:8", - Fix: grypeDB.Fix{ - Versions: []string{"0:0.6.21-17.el8_2"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "ELSA-2020-2550", - PackageName: "libexif-devel", - VersionConstraint: "< 0:0.6.21-17.el8_2", - VersionFormat: "rpm", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2020-13112", - Namespace: "nvd:cpe", - }, - }, - Namespace: "oracle:distro:oraclelinux:8", - Fix: grypeDB.Fix{ - Versions: []string{"0:0.6.21-17.el8_2"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "ELSA-2020-2550", - PackageName: "libexif-dummy", - VersionConstraint: "", - VersionFormat: "rpm", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2020-13112", - Namespace: "nvd:cpe", - }, - }, - Namespace: "oracle:distro:oraclelinux:8", - Fix: grypeDB.Fix{ - Versions: nil, - State: grypeDB.NotFixedState, - }, - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "ELSA-2020-2550", - DataSource: "http://linux.oracle.com/errata/ELSA-2020-2550.html", - Namespace: "oracle:distro:oraclelinux:8", - RecordSource: "vulnerabilities:ol:8", - Severity: "Medium", - URLs: []string{"http://linux.oracle.com/errata/ELSA-2020-2550.html", "http://linux.oracle.com/cve/CVE-2020-13112.html"}, - }, - }, - { - name: "Oracle Linux 8 with modularity", - numEntries: 1, - fixture: "test-fixtures/ol-8-modules.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2020-14350", - PackageName: "postgresql", - VersionConstraint: "< 0:12.5-1.module+el8.3.0+9042+664538f4", - VersionFormat: "rpm", - Namespace: "oracle:distro:oraclelinux:8", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2020-14350", - Namespace: "nvd:cpe", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"0:12.5-1.module+el8.3.0+9042+664538f4"}, - State: grypeDB.FixedState, - }, - }, - }, - metadata: grypeDB.VulnerabilityMetadata{ - ID: "CVE-2020-14350", - DataSource: "https://access.redhat.com/security/cve/CVE-2020-14350", - Namespace: "oracle:distro:oraclelinux:8", - RecordSource: "vulnerabilities:ol:8", - Severity: "Medium", - URLs: []string{"https://access.redhat.com/security/cve/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.", - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - f, err := os.Open(test.fixture) - require.NoError(t, err) - t.Cleanup(func() { - assert.NoError(t, f.Close()) - }) - - entries, err := unmarshal.OSVulnerabilityEntries(f) - assert.NoError(t, err) - assert.Len(t, entries, 1) - - entry := entries[0] - - dataEntries, err := Transform(entry) - assert.NoError(t, err) - - var vulns []grypeDB.Vulnerability - for _, entry := range dataEntries { - switch vuln := entry.Data.(type) { - case grypeDB.Vulnerability: - vulns = append(vulns, vuln) - case grypeDB.VulnerabilityMetadata: - assert.Equal(t, test.metadata, vuln) - default: - t.Fatalf("unexpected condition: data entry does not have a vulnerability or a metadata: %+v", vuln) - } - } - - if diff := cmp.Diff(test.vulns, vulns); diff != "" { - t.Errorf("vulnerabilities do not match (-want +got):\n%s", diff) - } - - }) - } - -} - -func TestParseVulnerabilitiesAllEntries(t *testing.T) { - tests := []struct { - name string - numEntries int - fixture string - vulns []grypeDB.Vulnerability - }{ - { - name: "Debian", - numEntries: 2, - fixture: "test-fixtures/debian-8-multiple-entries-for-same-package.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "CVE-2011-4623", - PackageName: "rsyslog", - VersionConstraint: "< 5.7.4-1", - VersionFormat: "dpkg", - Namespace: "debian:distro:debian:8", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2011-4623", - Namespace: "nvd:cpe", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"5.7.4-1"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "CVE-2008-5618", - PackageName: "rsyslog", - VersionConstraint: "< 3.18.6-1", - VersionFormat: "dpkg", - Namespace: "debian:distro:debian:8", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2008-5618", - Namespace: "nvd:cpe", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"3.18.6-1"}, - State: grypeDB.FixedState, - }, - }, - }, - }, - { - name: "Amazon", - numEntries: 3, - fixture: "test-fixtures/amazon-multiple-kernel-advisories.json", - vulns: []grypeDB.Vulnerability{ - { - ID: "ALAS-2021-1704", - PackageName: "kernel-headers", - VersionConstraint: "< 4.14.246-187.474.amzn2", - VersionFormat: "rpm", - Namespace: "amazon:distro:amazonlinux:2", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2021-3653", - Namespace: "nvd:cpe", - }, - { - ID: "CVE-2021-3656", - Namespace: "nvd:cpe", - }, - { - ID: "CVE-2021-3732", - Namespace: "nvd:cpe", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"4.14.246-187.474.amzn2"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "ALAS-2021-1704", - PackageName: "kernel", - VersionConstraint: "< 4.14.246-187.474.amzn2", - VersionFormat: "rpm", - Namespace: "amazon:distro:amazonlinux:2", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2021-3653", - Namespace: "nvd:cpe", - }, - { - ID: "CVE-2021-3656", - Namespace: "nvd:cpe", - }, - { - ID: "CVE-2021-3732", - Namespace: "nvd:cpe", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"4.14.246-187.474.amzn2"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "ALASKERNEL-5.4-2022-007", - PackageName: "kernel-headers", - VersionConstraint: ">= 5.4, < 5.4.144-69.257.amzn2", - VersionFormat: "rpm", - Namespace: "amazon:distro:amazonlinux:2", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2021-3753", - Namespace: "nvd:cpe", - }, - { - ID: "CVE-2021-40490", - Namespace: "nvd:cpe", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"5.4.144-69.257.amzn2"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "ALASKERNEL-5.4-2022-007", - PackageName: "kernel", - VersionConstraint: ">= 5.4, < 5.4.144-69.257.amzn2", - VersionFormat: "rpm", - Namespace: "amazon:distro:amazonlinux:2", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2021-3753", - Namespace: "nvd:cpe", - }, - { - ID: "CVE-2021-40490", - Namespace: "nvd:cpe", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"5.4.144-69.257.amzn2"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "ALASKERNEL-5.10-2022-005", - PackageName: "kernel-headers", - VersionConstraint: ">= 5.10, < 5.10.62-55.141.amzn2", - VersionFormat: "rpm", - Namespace: "amazon:distro:amazonlinux:2", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2021-3753", - Namespace: "nvd:cpe", - }, - { - ID: "CVE-2021-40490", - Namespace: "nvd:cpe", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"5.10.62-55.141.amzn2"}, - State: grypeDB.FixedState, - }, - }, - { - ID: "ALASKERNEL-5.10-2022-005", - PackageName: "kernel", - VersionConstraint: ">= 5.10, < 5.10.62-55.141.amzn2", - VersionFormat: "rpm", - Namespace: "amazon:distro:amazonlinux:2", - RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ - { - ID: "CVE-2021-3753", - Namespace: "nvd:cpe", - }, - { - ID: "CVE-2021-40490", - Namespace: "nvd:cpe", - }, - }, - Fix: grypeDB.Fix{ - Versions: []string{"5.10.62-55.141.amzn2"}, - State: grypeDB.FixedState, - }, - }, - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - f, err := os.Open(test.fixture) - require.NoError(t, err) - t.Cleanup(func() { - assert.NoError(t, f.Close()) - }) - - entries, err := unmarshal.OSVulnerabilityEntries(f) - assert.NoError(t, err) - assert.Len(t, entries, test.numEntries) - - var vulns []grypeDB.Vulnerability - for _, entry := range entries { - dataEntries, err := Transform(entry) - assert.NoError(t, err) - - for _, entry := range dataEntries { - switch vuln := entry.Data.(type) { - case grypeDB.Vulnerability: - vulns = append(vulns, vuln) - case grypeDB.VulnerabilityMetadata: - default: - t.Fatalf("unexpected condition: data entry does not have a vulnerability or a metadata: %+v", vuln) - } - } - } - - if diff := cmp.Diff(test.vulns, vulns); diff != "" { - t.Errorf("vulnerabilities do not match (-want +got):\n%s", diff) - } - }) - } -} diff --git a/pkg/process/v4/transformers/vulnerability_metadata.go b/pkg/process/v4/transformers/vulnerability_metadata.go deleted file mode 100644 index c40e9f6b..00000000 --- a/pkg/process/v4/transformers/vulnerability_metadata.go +++ /dev/null @@ -1,8 +0,0 @@ -package transformers - -// VendorBaseMetrics captures extra metrics that do not fit into a common CVSS -// struct, like Status and BaseSeverity -type VendorBaseMetrics struct { - BaseSeverity string `json:"base_severity"` - Status string `json:"status"` -} diff --git a/pkg/process/v4/writer.go b/pkg/process/v4/writer.go deleted file mode 100644 index 376b14a9..00000000 --- a/pkg/process/v4/writer.go +++ /dev/null @@ -1,138 +0,0 @@ -package v4 - -import ( - "crypto/sha256" - "fmt" - "path" - "path/filepath" - "strings" - "time" - - "github.com/spf13/afero" - - "github.com/anchore/grype-db/internal/file" - "github.com/anchore/grype-db/internal/log" - "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype/grype/db" - grypeDB "github.com/anchore/grype/grype/db/v4" - grypeDBStore "github.com/anchore/grype/grype/db/v4/store" -) - -// TODO: add NVDNamespace const to grype.db package? -const nvdNamespace = "nvd:cpe" - -var _ data.Writer = (*writer)(nil) - -type writer struct { - dbPath string - store grypeDB.Store -} - -func NewWriter(directory string, dataAge time.Time) (data.Writer, error) { - dbPath := path.Join(directory, grypeDB.VulnerabilityStoreFileName) - theStore, err := grypeDBStore.New(dbPath, true) - if err != nil { - return nil, fmt.Errorf("unable to create writer: %w", err) - } - - if err := theStore.SetID(grypeDB.NewID(dataAge)); err != nil { - return nil, fmt.Errorf("unable to set DB ID: %w", err) - } - - return &writer{ - dbPath: dbPath, - store: theStore, - }, nil -} - -func (w writer) Write(entries ...data.Entry) error { - for _, entry := range entries { - if entry.DBSchemaVersion != grypeDB.SchemaVersion { - return fmt.Errorf("wrong schema version: want %+v got %+v", grypeDB.SchemaVersion, entry.DBSchemaVersion) - } - - switch row := entry.Data.(type) { - case grypeDB.Vulnerability: - if err := w.store.AddVulnerability(row); err != nil { - return fmt.Errorf("unable to write vulnerability to store: %w", err) - } - case grypeDB.VulnerabilityMetadata: - normalizeSeverity(&row, w.store) - if err := w.store.AddVulnerabilityMetadata(row); err != nil { - return fmt.Errorf("unable to write vulnerability metadata to store: %w", err) - } - case grypeDB.VulnerabilityMatchExclusion: - if err := w.store.AddVulnerabilityMatchExclusion(row); err != nil { - return fmt.Errorf("unable to write vulnerability match exclusion 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) metadata() (*db.Metadata, error) { - hashStr, err := file.ContentDigest(afero.NewOsFs(), w.dbPath, sha256.New()) - if err != nil { - return nil, fmt.Errorf("failed to hash database file (%s): %w", w.dbPath, err) - } - - storeID, err := w.store.GetID() - if err != nil { - return nil, fmt.Errorf("failed to fetch store ID: %w", err) - } - - metadata := db.Metadata{ - Built: storeID.BuildTimestamp, - Version: storeID.SchemaVersion, - Checksum: "sha256:" + hashStr, - } - return &metadata, nil -} - -func (w writer) Close() error { - w.store.Close() - metadata, err := w.metadata() - if err != nil { - return err - } - - metadataPath := path.Join(filepath.Dir(w.dbPath), db.MetadataFileName) - if err = metadata.Write(metadataPath); err != nil { - return err - } - - log.WithFields("path", w.dbPath).Info("database created") - log.WithFields("path", metadataPath).Debug("database metadata created") - - return nil -} - -func normalizeSeverity(metadata *grypeDB.VulnerabilityMetadata, reader grypeDB.VulnerabilityMetadataStoreReader) { - if metadata.Severity != "" && strings.ToLower(metadata.Severity) != "unknown" { - return - } - if !strings.HasPrefix(strings.ToLower(metadata.ID), "cve") { - return - } - if strings.HasPrefix(metadata.Namespace, nvdNamespace) { - return - } - m, err := reader.GetVulnerabilityMetadata(metadata.ID, nvdNamespace) - if err != nil { - log.WithFields("id", metadata.ID, "error", err).Warn("error fetching vulnerability metadata from NVD namespace") - return - } - if m == nil { - log.WithFields("id", metadata.ID).Trace("unable to find vulnerability metadata from NVD namespace") - return - } - - newSeverity := string(data.ParseSeverity(m.Severity)) - - log.WithFields("id", metadata.ID, "namespace", metadata.Namespace, "from", metadata.Severity, "to", newSeverity).Trace("overriding irrelevant severity with data from NVD record") - - metadata.Severity = newSeverity -} diff --git a/pkg/process/v4/writer_test.go b/pkg/process/v4/writer_test.go deleted file mode 100644 index 76d5384e..00000000 --- a/pkg/process/v4/writer_test.go +++ /dev/null @@ -1,118 +0,0 @@ -package v4 - -import ( - "errors" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/anchore/grype-db/pkg/data" - grypeDB "github.com/anchore/grype/grype/db/v4" -) - -var _ grypeDB.VulnerabilityMetadataStoreReader = (*mockReader)(nil) - -type mockReader struct { - metadata *grypeDB.VulnerabilityMetadata - err error -} - -func newMockReader(sev string) *mockReader { - return &mockReader{ - metadata: &grypeDB.VulnerabilityMetadata{ - Severity: sev, - Namespace: "nvd", - }, - } -} - -func newDeadMockReader() *mockReader { - return &mockReader{ - err: errors.New("dead"), - } -} - -func (m mockReader) GetVulnerabilityMetadata(_, _ string) (*grypeDB.VulnerabilityMetadata, error) { - return m.metadata, m.err -} - -func (m mockReader) GetAllVulnerabilityMetadata() (*[]grypeDB.VulnerabilityMetadata, error) { - panic("implement me") -} - -func Test_normalizeSeverity(t *testing.T) { - - tests := []struct { - name string - initialSeverity string - namespace string - cveID string - reader grypeDB.VulnerabilityMetadataStoreReader - expected data.Severity - }{ - { - name: "skip missing metadata", - initialSeverity: "", - namespace: "test", - reader: &mockReader{}, - expected: "", - }, - { - name: "skip non-cve records metadata", - cveID: "GHSA-1234-1234-1234", - initialSeverity: "", - namespace: "test", - reader: newDeadMockReader(), // should not be used - expected: "", - }, - { - name: "override empty severity", - initialSeverity: "", - namespace: "test", - reader: newMockReader("low"), - expected: data.SeverityLow, - }, - { - name: "override unknown severity", - initialSeverity: "unknown", - namespace: "test", - reader: newMockReader("low"), - expected: data.SeverityLow, - }, - { - name: "ignore record with severity already set", - initialSeverity: "Low", - namespace: "test", - reader: newMockReader("critical"), // should not be used - expected: data.SeverityLow, - }, - { - name: "ignore nvd records", - initialSeverity: "Low", - namespace: "nvd:cpe", - reader: newDeadMockReader(), // should not be used - expected: data.SeverityLow, - }, - { - name: "db errors should not fail or modify the record", - initialSeverity: "", - namespace: "test", - reader: newDeadMockReader(), - expected: "", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - record := &grypeDB.VulnerabilityMetadata{ - ID: "cve-2020-0000", - Severity: tt.initialSeverity, - Namespace: tt.namespace, - } - if tt.cveID != "" { - record.ID = tt.cveID - } - normalizeSeverity(record, tt.reader) - assert.Equal(t, string(tt.expected), record.Severity) - }) - } -} diff --git a/pkg/process/v5/processors.go b/pkg/process/v5/processors.go index cdc5b982..4a976bff 100644 --- a/pkg/process/v5/processors.go +++ b/pkg/process/v5/processors.go @@ -1,6 +1,8 @@ package v5 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/v5/transformers/github" @@ -10,11 +12,38 @@ import ( "github.com/anchore/grype-db/pkg/process/v5/transformers/os" ) -func Processors() []data.Processor { +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.NewGitHubProcessor(github.Transform), processors.NewMSRCProcessor(msrc.Transform), - processors.NewNVDProcessor(nvd.Transform), + processors.NewNVDProcessor(nvd.Transformer(cfg.NVD)), processors.NewOSProcessor(os.Transform), processors.NewMatchExclusionProcessor(matchexclusions.Transform), } diff --git a/pkg/process/v5/transformers/github/transform.go b/pkg/process/v5/transformers/github/transform.go index 91b31f7d..292acf72 100644 --- a/pkg/process/v5/transformers/github/transform.go +++ b/pkg/process/v5/transformers/github/transform.go @@ -1,11 +1,12 @@ package github import ( + "errors" "fmt" "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" @@ -13,6 +14,8 @@ import ( syftPkg "github.com/anchore/syft/syft/pkg" ) +var errSkip = fmt.Errorf("skipping advisory") + func buildGrypeNamespace(group string) (namespace.Namespace, error) { feedGroupComponents := strings.Split(group, ":") @@ -24,10 +27,13 @@ func buildGrypeNamespace(group string) (namespace.Namespace, error) { syftLanguage := syftPkg.LanguageByName(feedGroupLang) if syftLanguage == syftPkg.UnknownLanguage { - // For now map nuget to dotnet as the language. - if feedGroupLang == "nuget" { + switch feedGroupLang { + case "nuget": syftLanguage = syftPkg.Dotnet - } else { + case "github-action": + // we don't want to error out on this, but grype at this version does not support github-action matching + return nil, errSkip + default: return nil, fmt.Errorf("unable to determine grype namespace for enterprise namespace=%s", group) } } @@ -45,7 +51,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 } @@ -54,6 +60,9 @@ func Transform(vulnerability unmarshal.GitHubAdvisory) ([]data.Entry, error) { grypeNamespace, err := buildGrypeNamespace(vulnerability.Advisory.Namespace) if err != nil { + if errors.Is(err, errSkip) { + return nil, nil + } return nil, err } diff --git a/pkg/process/v5/transformers/github/transform_test.go b/pkg/process/v5/transformers/github/transform_test.go index c6727816..b6735c03 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" @@ -22,6 +22,7 @@ func TestBuildGrypeNamespace(t *testing.T) { tests := []struct { group string namespace namespace.Namespace + wantErr require.ErrorAssertionFunc }{ { group: "github:python", @@ -51,12 +52,24 @@ func TestBuildGrypeNamespace(t *testing.T) { group: "github:rust", namespace: language.NewNamespace("github", syftPkg.Rust, ""), }, + { + group: "github:github-action", + wantErr: func(t require.TestingT, err error, i ...interface{}) { + assert.Error(t, err) + assert.ErrorIs(t, errSkip, err) + }, + }, } for _, test := range tests { + if test.wantErr == nil { + test.wantErr = require.NoError + } ns, err := buildGrypeNamespace(test.group) - - assert.NoError(t, err) + test.wantErr(t, err) + if err != nil { + return + } assert.Equal(t, test.namespace, ns) } } 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/test-fixtures/CVE-2023-45283-platform-cpe-first.json b/pkg/process/v5/transformers/nvd/test-fixtures/CVE-2023-45283-platform-cpe-first.json new file mode 100644 index 00000000..92d845f8 --- /dev/null +++ b/pkg/process/v5/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/v5/transformers/nvd/test-fixtures/CVE-2023-45283-platform-cpe-last.json b/pkg/process/v5/transformers/nvd/test-fixtures/CVE-2023-45283-platform-cpe-last.json new file mode 100644 index 00000000..549878ed --- /dev/null +++ b/pkg/process/v5/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/v5/transformers/nvd/transform.go b/pkg/process/v5/transformers/nvd/transform.go index 72d92bbd..b78052a1 100644 --- a/pkg/process/v5/transformers/nvd/transform.go +++ b/pkg/process/v5/transformers/nvd/transform.go @@ -1,7 +1,11 @@ package nvd import ( + "slices" "sort" + "strings" + + "github.com/scylladb/go-set/strset" "github.com/anchore/grype-db/internal" "github.com/anchore/grype-db/pkg/data" @@ -12,9 +16,33 @@ import ( "github.com/anchore/grype/grype/db/v5/namespace" "github.com/anchore/grype/grype/db/v5/pkg/qualifier" "github.com/anchore/grype/grype/db/v5/pkg/qualifier/platformcpe" + "github.com/anchore/grype/grype/pkg" + "github.com/anchore/grype/grype/version" + "github.com/anchore/syft/syft/cpe" ) -func Transform(vulnerability unmarshal.NVDVulnerability) ([]data.Entry, error) { +type Config struct { + CPEParts *strset.Set + InferNVDFixVersions bool +} + +func defaultConfig() Config { + return Config{ + CPEParts: strset.New("a"), + InferNVDFixVersions: true, + } +} + +func Transformer(cfg Config) data.NVDTransformer { + if cfg == (Config{}) { + cfg = defaultConfig() + } + return func(vulnerability unmarshal.NVDVulnerability) ([]data.Entry, error) { + return transform(cfg, vulnerability) + } +} + +func transform(cfg Config, vulnerability unmarshal.NVDVulnerability) ([]data.Entry, error) { // TODO: stop capturing record source in the vulnerability metadata record (now that feed groups are not real) recordSource := "nvdv2:nvdv2:cves" @@ -25,7 +53,7 @@ func Transform(vulnerability unmarshal.NVDVulnerability) ([]data.Entry, error) { entryNamespace := grypeNamespace.String() - uniquePkgs := findUniquePkgs(vulnerability.Configurations...) + uniquePkgs := findUniquePkgs(cfg, vulnerability.Configurations...) // extract all links var links []string @@ -60,14 +88,12 @@ func Transform(vulnerability unmarshal.NVDVulnerability) ([]data.Entry, error) { allVulns = append(allVulns, grypeDB.Vulnerability{ ID: vulnerability.ID, PackageQualifiers: qualifiers, - VersionConstraint: buildConstraints(uniquePkgs.Matches(p)), - VersionFormat: "unknown", + VersionConstraint: buildConstraints(matches), + VersionFormat: strings.ToLower(getVersionFormat(p.Product, orderedCPEs).String()), PackageName: grypeNamespace.Resolver().Normalize(p.Product), Namespace: entryNamespace, CPEs: orderedCPEs, - Fix: grypeDB.Fix{ - State: grypeDB.UnknownFixState, - }, + Fix: getFix(matches, cfg.InferNVDFixVersions), }) } @@ -87,6 +113,76 @@ func Transform(vulnerability unmarshal.NVDVulnerability) ([]data.Entry, error) { return transformers.NewEntries(allVulns, metadata), nil } +func getVersionFormat(name string, cpes []string) version.Format { + if pkg.HasJvmPackageName(name) { + return version.JVMFormat + } + for _, c := range cpes { + att, err := cpe.NewAttributes(c) + if err != nil { + continue + } + if pkg.HasJvmPackageName(att.Product) { + return version.JVMFormat + } + } + return version.UnknownFormat +} + +func getFix(matches []nvd.CpeMatch, inferNVDFixVersions bool) grypeDB.Fix { + if !inferNVDFixVersions { + return grypeDB.Fix{ + State: grypeDB.UnknownFixState, + } + } + + possiblyFixed := strset.New() + knownAffected := strset.New() + unspecifiedSet := strset.New("*", "-", "*") + + for _, match := range matches { + if !match.Vulnerable { + continue + } + + 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 { + continue + } + + if !unspecifiedSet.Has(matchCPE.Attributes.Version) { + knownAffected.Add(matchCPE.Attributes.Version) + } + } + + possiblyFixed.Remove(knownAffected.List()...) + + var fixes []string + fixState := grypeDB.UnknownFixState + if possiblyFixed.Size() > 0 { + fixState = grypeDB.FixedState + fixes = possiblyFixed.List() + slices.Sort(fixes) + } + + return grypeDB.Fix{ + Versions: fixes, + State: fixState, + } +} + func getCvss(cvss ...nvd.CvssSummary) []grypeDB.Cvss { var results []grypeDB.Cvss for _, c := range cvss { diff --git a/pkg/process/v5/transformers/nvd/transform_test.go b/pkg/process/v5/transformers/nvd/transform_test.go index 493d10b7..bcc52a29 100644 --- a/pkg/process/v5/transformers/nvd/transform_test.go +++ b/pkg/process/v5/transformers/nvd/transform_test.go @@ -9,11 +9,13 @@ 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" "github.com/anchore/grype/grype/db/v5/pkg/qualifier" "github.com/anchore/grype/grype/db/v5/pkg/qualifier/platformcpe" + "github.com/anchore/grype/grype/version" ) func TestUnmarshalNVDVulnerabilitiesEntries(t *testing.T) { @@ -30,6 +32,7 @@ func TestParseAllNVDVulnerabilityEntries(t *testing.T) { tests := []struct { name string + config Config numEntries int fixture string vulns []grypeDB.Vulnerability @@ -155,7 +158,8 @@ func TestParseAllNVDVulnerabilityEntries(t *testing.T) { Namespace: "nvd:cpe", CPEs: []string{"cpe:2.3:a:mautic:mautic:*:*:*:*:*:*:*:*"}, // note: entry was dedupicated Fix: grypeDB.Fix{ - State: "unknown", + Versions: []string{"2.13.0"}, + State: "fixed", }, }, }, @@ -457,8 +461,11 @@ func TestParseAllNVDVulnerabilityEntries(t *testing.T) { VersionFormat: "unknown", CPEs: []string{"cpe:2.3:a:redhat:ansible_engine:*:*:*:*:*:*:*:*"}, RelatedVulnerabilities: nil, - Fix: grypeDB.Fix{State: "unknown"}, - Advisories: nil, + Fix: grypeDB.Fix{ + Versions: []string{"2.9.6"}, + State: "fixed", + }, + Advisories: nil, }, { ID: "CVE-2020-10729", @@ -472,8 +479,11 @@ func TestParseAllNVDVulnerabilityEntries(t *testing.T) { VersionFormat: "unknown", CPEs: []string{"cpe:2.3:a:redhat:ansible_engine:*:*:*:*:*:*:*:*"}, RelatedVulnerabilities: nil, - Fix: grypeDB.Fix{State: "unknown"}, - Advisories: nil, + Fix: grypeDB.Fix{ + Versions: []string{"2.9.6"}, + State: "fixed", + }, + Advisories: nil, }, }, metadata: grypeDB.VulnerabilityMetadata{ @@ -587,10 +597,121 @@ func TestParseAllNVDVulnerabilityEntries(t *testing.T) { }, }, }, + { + name: "Platform CPE first in CPE config list", + numEntries: 1, + fixture: "test-fixtures/CVE-2023-45283-platform-cpe-first.json", + vulns: []grypeDB.Vulnerability{ + { + ID: "CVE-2023-45283", + PackageName: "go", + Namespace: "nvd:cpe", + PackageQualifiers: []qualifier.Qualifier{platformcpe.Qualifier{ + Kind: "platform-cpe", + CPE: "cpe:2.3:o:microsoft:windows:-:*:*:*:*:*:*:*", + }}, + VersionConstraint: "< 1.20.11 || >= 1.21.0-0, < 1.21.4", + VersionFormat: "unknown", + CPEs: []string{"cpe:2.3:a:golang:go:*:*:*:*:*:*:*:*"}, + RelatedVulnerabilities: nil, + Fix: grypeDB.Fix{ + Versions: []string{"1.20.11", "1.21.4"}, + State: "fixed", + }, + Advisories: nil, + }, + }, + metadata: grypeDB.VulnerabilityMetadata{ + ID: "CVE-2023-45283", + Namespace: "nvd:cpe", + DataSource: "https://nvd.nist.gov/vuln/detail/CVE-2023-45283", + RecordSource: "nvdv2:nvdv2:cves", + Severity: "High", + URLs: []string{ + "http://www.openwall.com/lists/oss-security/2023/12/05/2", + "https://go.dev/cl/540277", + "https://go.dev/cl/541175", + "https://go.dev/issue/63713", + "https://go.dev/issue/64028", + "https://groups.google.com/g/golang-announce/c/4tU8LZfBFkY", + "https://groups.google.com/g/golang-dev/c/6ypN5EjibjM/m/KmLVYH_uAgAJ", + "https://pkg.go.dev/vuln/GO-2023-2185", + "https://security.netapp.com/advisory/ntap-20231214-0008/", + }, + 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.", + Cvss: []grypeDB.Cvss{ + { + VendorMetadata: nil, + Metrics: grypeDB.NewCvssMetrics(7.5, 3.9, 3.6), + Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + Version: "3.1", + Source: "nvd@nist.gov", + Type: "Primary", + }, + }, + }, + }, + { + name: "Platform CPE last in CPE config list", + numEntries: 1, + fixture: "test-fixtures/CVE-2023-45283-platform-cpe-last.json", + vulns: []grypeDB.Vulnerability{ + { + ID: "CVE-2023-45283", + PackageName: "go", + Namespace: "nvd:cpe", + PackageQualifiers: []qualifier.Qualifier{platformcpe.Qualifier{ + Kind: "platform-cpe", + CPE: "cpe:2.3:o:microsoft:windows:-:*:*:*:*:*:*:*", + }}, + VersionConstraint: "< 1.20.11 || >= 1.21.0-0, < 1.21.4", + VersionFormat: "unknown", + CPEs: []string{"cpe:2.3:a:golang:go:*:*:*:*:*:*:*:*"}, + RelatedVulnerabilities: nil, + Fix: grypeDB.Fix{ + Versions: []string{"1.20.11", "1.21.4"}, + State: "fixed", + }, + Advisories: nil, + }, + }, + metadata: grypeDB.VulnerabilityMetadata{ + ID: "CVE-2023-45283", + Namespace: "nvd:cpe", + DataSource: "https://nvd.nist.gov/vuln/detail/CVE-2023-45283", + RecordSource: "nvdv2:nvdv2:cves", + Severity: "High", + URLs: []string{ + "http://www.openwall.com/lists/oss-security/2023/12/05/2", + "https://go.dev/cl/540277", + "https://go.dev/cl/541175", + "https://go.dev/issue/63713", + "https://go.dev/issue/64028", + "https://groups.google.com/g/golang-announce/c/4tU8LZfBFkY", + "https://groups.google.com/g/golang-dev/c/6ypN5EjibjM/m/KmLVYH_uAgAJ", + "https://pkg.go.dev/vuln/GO-2023-2185", + "https://security.netapp.com/advisory/ntap-20231214-0008/", + }, + 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.", + Cvss: []grypeDB.Cvss{ + { + VendorMetadata: nil, + Metrics: grypeDB.NewCvssMetrics(7.5, 3.9, 3.6), + Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + Version: "3.1", + Source: "nvd@nist.gov", + Type: "Primary", + }, + }, + }, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { + if test.config == (Config{}) { + test.config = defaultConfig() + } f, err := os.Open(test.fixture) require.NoError(t, err) t.Cleanup(func() { @@ -602,7 +723,7 @@ func TestParseAllNVDVulnerabilityEntries(t *testing.T) { var vulns []grypeDB.Vulnerability for _, entry := range entries { - dataEntries, err := Transform(entry.Cve) + dataEntries, err := transform(test.config, entry.Cve) require.NoError(t, err) for _, entry := range dataEntries { @@ -628,3 +749,328 @@ func TestParseAllNVDVulnerabilityEntries(t *testing.T) { }) } } + +func TestGetVersionFormat(t *testing.T) { + tests := []struct { + name string + input string + cpes []string + expected version.Format + }{ + { + name: "detects JVM format from name", + input: "java_se", + cpes: []string{}, + expected: version.JVMFormat, + }, + { + name: "detects JVM format from CPEs", + input: "other_product", + cpes: []string{"cpe:2.3:a:oracle:openjdk:11:update53:*:*:*:*:*:*"}, + expected: version.JVMFormat, + }, + { + name: "detects JVM format from another CPE (zulu)", + input: "other_product", + cpes: []string{"cpe:2.3:a:zula:zulu:15:*:*:*:*:*:*:*"}, + expected: version.JVMFormat, + }, + { + name: "detects JVM format from another CPE (jdk)", + input: "other_product", + cpes: []string{"cpe:2.3:a:oracle:jdk:11.0:*:*:*:*:*:*:*"}, + expected: version.JVMFormat, + }, + { + name: "detects JVM format from another CPE (jre)", + input: "other_product", + cpes: []string{"cpe:2.3:a:oracle:jre:11.0:*:*:*:*:*:*:*"}, + expected: version.JVMFormat, + }, + { + name: "returns unknown format for non-JVM product and non-JVM CPEs", + input: "non_jvm_product", + cpes: []string{"cpe:2.3:a:some_other_product:product_name:1.0:*:*:*:*:*:*"}, + expected: version.UnknownFormat, + }, + { + name: "handles invalid CPE gracefully", + input: "non_jvm_product", + cpes: []string{"invalid_cpe_format"}, + expected: version.UnknownFormat, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + format := getVersionFormat(tt.input, tt.cpes) + assert.Equal(t, tt.expected, format) + }) + } +} + +func TestGetFix(t *testing.T) { + tests := []struct { + name string + matches []nvd.CpeMatch + expected grypeDB.Fix + }{ + { + name: "Equals", + matches: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:vendor:product:2.2.0:*:*:*:*:target:*:*", + Vulnerable: true, + }, + }, + expected: grypeDB.Fix{ + Versions: nil, + State: "unknown", + }, + }, + { + name: "VersionEndExcluding", + matches: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:vendor:product:*:*:*:*:*:target:*:*", + VersionEndExcluding: strRef("2.3.0"), + Vulnerable: true, + }, + }, + expected: grypeDB.Fix{ + Versions: []string{"2.3.0"}, + State: "fixed", + }, + }, + { + name: "VersionEndIncluding", + matches: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:vendor:product:*:*:*:*:*:target:*:*", + VersionEndIncluding: strRef("2.3.0"), + Vulnerable: true, + }, + }, + expected: grypeDB.Fix{ + Versions: nil, + State: "unknown", + }, + }, + { + name: "VersionStartExcluding", + matches: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:vendor:product:*:*:*:*:*:target:*:*", + VersionStartExcluding: strRef("2.3.0"), + Vulnerable: true, + }, + }, + expected: grypeDB.Fix{ + Versions: nil, + State: "unknown", + }, + }, + { + name: "VersionStartIncluding", + matches: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:vendor:product:*:*:*:*:*:target:*:*", + VersionStartIncluding: strRef("2.3.0"), + Vulnerable: true, + }, + }, + expected: grypeDB.Fix{ + Versions: nil, + State: "unknown", + }, + }, + { + name: "Version Range", + matches: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:vendor:product:*:*:*:*:*:target:*:*", + VersionStartIncluding: strRef("2.3.0"), + VersionEndIncluding: strRef("2.5.0"), + Vulnerable: true, + }, + }, + expected: grypeDB.Fix{ + Versions: nil, + State: "unknown", + }, + }, + { + 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"), + Vulnerable: true, + }, + { + Criteria: "cpe:2.3:a:vendor:product:*:*:*:*:*:target:*:*", + VersionStartExcluding: strRef("3.3.0"), + VersionEndExcluding: strRef("3.5.0"), + Vulnerable: true, + }, + }, + expected: grypeDB.Fix{ + Versions: []string{"3.5.0"}, + State: "fixed", + }, + }, + { + name: "Empty end exclude treated as unknown", + matches: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:vendor:product:*:*:*:*:*:target:*:*", + VersionStartExcluding: strRef("3.3.0"), + VersionEndExcluding: strRef(""), + Vulnerable: true, + }, + }, + expected: grypeDB.Fix{ + Versions: nil, + State: "unknown", + }, + }, + { + name: "Multiple fixes with deduplication", + matches: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:vendor:product:*:*:*:*:*:target:*:*", + VersionStartIncluding: strRef("3.3.0"), + VersionEndExcluding: strRef("3.5.0"), + Vulnerable: true, + }, + { + Criteria: "cpe:2.3:a:vendor:product:*:*:*:*:*:target:*:*", + VersionStartIncluding: strRef("0"), + VersionEndExcluding: strRef("1.7.0"), + Vulnerable: true, + }, + { + Criteria: "cpe:2.3:a:vendor:product:*:*:*:*:*:target-2:*:*", + VersionStartIncluding: strRef("0"), + VersionEndExcluding: strRef("1.7.0"), + Vulnerable: true, + }, + }, + expected: grypeDB.Fix{ + Versions: []string{"1.7.0", "3.5.0"}, + State: "fixed", + }, + }, + { + name: "< version as end in a separate affected >= range", + matches: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:vendor:product:*:*:*:*:*:target:*:*", + VersionStartIncluding: strRef("2.3.0"), + VersionEndExcluding: strRef("2.5.0"), + Vulnerable: true, + }, + { + Criteria: "cpe:2.3:a:vendor:product:*:*:*:*:*:target:*:*", + VersionStartIncluding: strRef("2.5.0"), + VersionEndExcluding: strRef("3.5.0"), + Vulnerable: true, + }, + }, + expected: grypeDB.Fix{ + Versions: []string{"3.5.0"}, + State: "fixed", + }, + }, + { + name: "< version as start in a separate affected <= range", + matches: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:vendor:product:*:*:*:*:*:target:*:*", + VersionStartIncluding: strRef("2.3.0"), + VersionEndExcluding: strRef("2.5.0"), + Vulnerable: true, + }, + { + Criteria: "cpe:2.3:a:vendor:product:*:*:*:*:*:target:*:*", + VersionStartIncluding: strRef("2.1.0"), + VersionEndIncluding: strRef("2.5.0"), + Vulnerable: true, + }, + }, + expected: grypeDB.Fix{ + Versions: nil, + State: "unknown", + }, + }, + { + name: "< range with same version affected == critera", + matches: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:vendor:product:*:*:*:*:*:target:*:*", + VersionStartIncluding: strRef("2.3.0"), + VersionEndExcluding: strRef("2.5.0"), + Vulnerable: true, + }, + { + Criteria: "cpe:2.3:a:vendor:product:2.5.0:*:*:*:*:target:*:*", + Vulnerable: true, + }, + }, + expected: grypeDB.Fix{ + Versions: nil, + State: "unknown", + }, + }, + { + name: "< range with another unaffected entry", + matches: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:vendor:product:*:*:*:*:*:target:*:*", + VersionStartIncluding: strRef("2.3.0"), + VersionEndExcluding: strRef("2.5.0"), + Vulnerable: true, + }, + { + Criteria: "cpe:2.3:a:vendor:product:2.5.0:*:*:*:*:target:*:*", + Vulnerable: false, + }, + }, + expected: grypeDB.Fix{ + Versions: []string{"2.5.0"}, + State: "fixed", + }, + }, + { + name: "treat * in < as unknown fix state", + matches: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:vendor:product:*:*:*:*:*:target:*:*", + VersionStartIncluding: strRef("2.3.0"), + VersionEndExcluding: strRef("*"), + Vulnerable: true, + }, + }, + expected: grypeDB.Fix{ + Versions: nil, + State: "unknown", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fix := getFix(tt.matches, true) + assert.Equal(t, tt.expected, fix) + }) + + t.Run(tt.name+" don't infer NVD fixes", func(t *testing.T) { + fix := getFix(tt.matches, false) + assert.Equal(t, grypeDB.Fix{ + Versions: nil, + State: "unknown", + }, fix) + }) + } +} diff --git a/pkg/process/v5/transformers/nvd/unique_pkg.go b/pkg/process/v5/transformers/nvd/unique_pkg.go index 3224a562..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" ) @@ -31,7 +31,7 @@ func (p pkgCandidate) String() string { return fmt.Sprintf("%s|%s|%s|%s", p.Vendor, p.Product, p.TargetSoftware, p.PlatformCPE) } -func newPkgCandidate(match nvd.CpeMatch, platformCPE string) (*pkgCandidate, error) { +func newPkgCandidate(tCfg Config, match nvd.CpeMatch, platformCPE string) (*pkgCandidate, error) { // we are only interested in packages that are vulnerable (not related to secondary match conditioning) if !match.Vulnerable { return nil, nil @@ -42,8 +42,9 @@ func newPkgCandidate(match nvd.CpeMatch, platformCPE string) (*pkgCandidate, err return nil, fmt.Errorf("unable to create uniquePkgEntry from '%s': %w", match.Criteria, err) } - // we are only interested in applications, not hardware or operating systems - if c.Part() != cpe.Application { + // we are interested in applications, conditionally operating systems, but never hardware + part := c.Part() + if !tCfg.CPEParts.Has(string(part)) { return nil, nil } @@ -55,15 +56,15 @@ func newPkgCandidate(match nvd.CpeMatch, platformCPE string) (*pkgCandidate, err }, nil } -func findUniquePkgs(cfgs ...nvd.Configuration) uniquePkgTracker { +func findUniquePkgs(tCfg Config, cfgs ...nvd.Configuration) uniquePkgTracker { set := newUniquePkgTracker() for _, c := range cfgs { - _findUniquePkgs(set, c) + _findUniquePkgs(tCfg, set, c) } return set } -func platformPackageCandidates(set uniquePkgTracker, c nvd.Configuration) bool { +func platformPackageCandidates(tCfg Config, set uniquePkgTracker, c nvd.Configuration) bool { nodes := c.Nodes /* Turn a configuration like this: @@ -97,7 +98,7 @@ func platformPackageCandidates(set uniquePkgTracker, c nvd.Configuration) bool { platformsNode = n } } - if platformsNode.Operator != nvd.Or || len(platformsNode.CpeMatch) < 2 { + if platformsNode.Operator != nvd.Or { return false } if applicationNode.Operator != nvd.Or { @@ -108,7 +109,7 @@ func platformPackageCandidates(set uniquePkgTracker, c nvd.Configuration) bool { for _, application := range applicationNode.CpeMatch { for _, maybePlatform := range matches { platform := maybePlatform.Criteria - candidate, err := newPkgCandidate(application, platform) + candidate, err := newPkgCandidate(tCfg, application, platform) if err != nil { log.Debugf("unable processing uniquePkg with multiple platforms: %v", err) continue @@ -151,33 +152,18 @@ func noCPEsVulnerable(node nvd.Node) bool { return true } -func determinePlatformCPEAndNodes(c nvd.Configuration) (string, []nvd.Node) { - var platformCPE string - 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 { - platformCPE = nodes[1].CpeMatch[0].Criteria - nodes = []nvd.Node{nodes[0]} - } - } - - return platformCPE, nodes -} - -func _findUniquePkgs(set uniquePkgTracker, c nvd.Configuration) { +func _findUniquePkgs(tCfg Config, set uniquePkgTracker, c nvd.Configuration) { if len(c.Nodes) == 0 { return } - if platformPackageCandidates(set, c) { + if platformPackageCandidates(tCfg, set, c) { return } - platformCPE, nodes := determinePlatformCPEAndNodes(c) - for _, node := range nodes { + for _, node := range c.Nodes { for _, match := range node.CpeMatch { - candidate, err := newPkgCandidate(match, platformCPE) + 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 diff --git a/pkg/process/v5/transformers/nvd/unique_pkg_test.go b/pkg/process/v5/transformers/nvd/unique_pkg_test.go index a684ef90..d178578b 100644 --- a/pkg/process/v5/transformers/nvd/unique_pkg_test.go +++ b/pkg/process/v5/transformers/nvd/unique_pkg_test.go @@ -4,6 +4,7 @@ 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" @@ -22,6 +23,7 @@ func newUniquePkgTrackerFromSlice(candidates []pkgCandidate) uniquePkgTracker { func TestFindUniquePkgs(t *testing.T) { tests := []struct { name string + config Config nodes []nvd.Node operator *nvd.Operator expected uniquePkgTracker @@ -62,7 +64,7 @@ func TestFindUniquePkgs(t *testing.T) { expected: newUniquePkgTrackerFromSlice([]pkgCandidate{}), }, { - name: "skip-os", + name: "skip-os-by-default", nodes: []nvd.Node{ { CpeMatch: []nvd.CpeMatch{ @@ -75,6 +77,29 @@ func TestFindUniquePkgs(t *testing.T) { }, expected: newUniquePkgTrackerFromSlice([]pkgCandidate{}), }, + { + 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", + }, + }), + }, { name: "duplicate-by-product", nodes: []nvd.Node{ @@ -310,11 +335,102 @@ func TestFindUniquePkgs(t *testing.T) { }, }), }, + { + name: "single platform CPE as first element", + operator: opRef(nvd.And), + nodes: []nvd.Node{ + { + Negate: boolRef(false), + Operator: nvd.Or, + CpeMatch: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:o:microsoft:windows:-:*:*:*:*:*:*:*", + MatchCriteriaID: "902B8056-9E37-443B-8905-8AA93E2447FB", + Vulnerable: false, + }, + }, + }, + { + Negate: boolRef(false), + Operator: nvd.Or, + CpeMatch: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:golang:go:*:*:*:*:*:*:*:*", + VersionEndExcluding: strRef("1.22.2"), + VersionStartIncluding: strRef("1.22"), + MatchCriteriaID: "5EBE5E1C-C881-4A76-9E36-4FB7C48427E6", + Vulnerable: true, + }, + { + Criteria: "cpe:2.3:a:golang:go:*:*:*:*:*:*:*:*", + VersionEndExcluding: strRef("1.21.8"), + MatchCriteriaID: "5EBE5E1C-C881-4A76-9E36-4FB7C48427E6", + Vulnerable: true, + }, + }, + }, + }, + expected: newUniquePkgTrackerFromSlice([]pkgCandidate{ + { + Product: "go", + Vendor: "golang", + TargetSoftware: ANY, + PlatformCPE: "cpe:2.3:o:microsoft:windows:-:*:*:*:*:*:*:*", + }, + }), + }, + { + name: "single platform CPE as last element", + operator: opRef(nvd.And), + nodes: []nvd.Node{ + { + Negate: boolRef(false), + Operator: nvd.Or, + CpeMatch: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:golang:go:*:*:*:*:*:*:*:*", + VersionEndExcluding: strRef("1.22.2"), + VersionStartIncluding: strRef("1.22"), + MatchCriteriaID: "5EBE5E1C-C881-4A76-9E36-4FB7C48427E6", + Vulnerable: true, + }, + { + Criteria: "cpe:2.3:a:golang:go:*:*:*:*:*:*:*:*", + VersionEndExcluding: strRef("1.21.8"), + MatchCriteriaID: "5EBE5E1C-C881-4A76-9E36-4FB7C48427E6", + Vulnerable: true, + }, + }, + }, + { + Negate: boolRef(false), + Operator: nvd.Or, + CpeMatch: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:o:microsoft:windows:-:*:*:*:*:*:*:*", + MatchCriteriaID: "902B8056-9E37-443B-8905-8AA93E2447FB", + Vulnerable: false, + }, + }, + }, + }, + expected: newUniquePkgTrackerFromSlice([]pkgCandidate{ + { + Product: "go", + Vendor: "golang", + TargetSoftware: ANY, + PlatformCPE: "cpe:2.3:o:microsoft:windows:-:*:*:*:*:*:*:*", + }, + }), + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - actual := findUniquePkgs(nvd.Configuration{Nodes: test.nodes, Operator: test.operator}) + 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 { @@ -478,7 +594,7 @@ func Test_UniquePackageTrackerHandlesOnlyPlatformDiff(t *testing.T) { } tracker := newUniquePkgTracker() for _, c := range candidates { - candidate, err := newPkgCandidate(applicationNode, c.PlatformCPE) + candidate, err := newPkgCandidate(defaultConfig(), applicationNode, c.PlatformCPE) require.NoError(t, err) tracker.Add(*candidate, cpeMatch) } @@ -488,14 +604,15 @@ func Test_UniquePackageTrackerHandlesOnlyPlatformDiff(t *testing.T) { func TestPlatformPackageCandidates(t *testing.T) { type testCase struct { name string - config nvd.Configuration + config Config + state nvd.Configuration wantChanged bool wantSet uniquePkgTracker } tests := []testCase{ { name: "application X platform", - config: nvd.Configuration{ + state: nvd.Configuration{ Negate: nil, Nodes: []nvd.Node{ { @@ -553,7 +670,7 @@ func TestPlatformPackageCandidates(t *testing.T) { }, { name: "top-level OR is excluded", - config: nvd.Configuration{ + state: nvd.Configuration{ Operator: opRef(nvd.Or), }, wantChanged: false, @@ -561,14 +678,14 @@ func TestPlatformPackageCandidates(t *testing.T) { }, { name: "top-level nil op is excluded", - config: nvd.Configuration{ + state: nvd.Configuration{ Operator: nil, }, wantChanged: false, }, { name: "single hardware node results in exclusion", - config: nvd.Configuration{ + state: nvd.Configuration{ Negate: nil, Nodes: []nvd.Node{ { @@ -607,8 +724,11 @@ func TestPlatformPackageCandidates(t *testing.T) { } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { + if tc.config == (Config{}) { + tc.config = defaultConfig() + } set := newUniquePkgTracker() - result := platformPackageCandidates(set, tc.config) + result := platformPackageCandidates(tc.config, set, tc.state) assert.Equal(t, result, tc.wantChanged) if tc.wantSet == nil { tc.wantSet = newUniquePkgTracker() @@ -629,8 +749,18 @@ func boolRef(b bool) *bool { return &b } -func mustNewPackage(t *testing.T, match nvd.CpeMatch, platformCPE string) pkgCandidate { - p, err := newPkgCandidate(match, platformCPE) +func mustNewPackage(t *testing.T, match nvd.CpeMatch, platformCPE string, 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, platformCPE) require.NoError(t, err) return *p } diff --git a/pkg/process/v5/transformers/os/test-fixtures/azure-linux-3.json b/pkg/process/v5/transformers/os/test-fixtures/azure-linux-3.json new file mode 100644 index 00000000..ce25f4de --- /dev/null +++ b/pkg/process/v5/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/v5/transformers/os/test-fixtures/mariner-20.json b/pkg/process/v5/transformers/os/test-fixtures/mariner-20.json new file mode 100644 index 00000000..20cb1465 --- /dev/null +++ b/pkg/process/v5/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/v5/transformers/os/test-fixtures/mariner-range.json b/pkg/process/v5/transformers/os/test-fixtures/mariner-range.json new file mode 100644 index 00000000..3ec9731f --- /dev/null +++ b/pkg/process/v5/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/v5/transformers/os/transform.go b/pkg/process/v5/transformers/os/transform.go index 18fd7bab..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" @@ -30,16 +30,21 @@ func buildGrypeNamespace(group string) (namespace.Namespace, error) { } providerName := d.String() + distroName := d.String() switch d { case distro.OracleLinux: providerName = "oracle" case distro.AmazonLinux: providerName = "amazon" + case distro.Mariner, distro.Azure: + providerName = "mariner" + if strings.HasPrefix(feedGroupComponents[1], "3") { + distroName = distro.Azure.String() // Mariner Linux 3 is known as "Azure Linux 3" + } } - ns, err := namespace.FromString(fmt.Sprintf("%s:distro:%s:%s", providerName, d.String(), feedGroupComponents[1])) - + ns, err := namespace.FromString(fmt.Sprintf("%s:distro:%s:%s", providerName, distroName, feedGroupComponents[1])) if err != nil { return nil, err } @@ -47,6 +52,22 @@ func buildGrypeNamespace(group string) (namespace.Namespace, error) { return ns, nil } +func buildPackageQualifiers(fixedInEntry unmarshal.OSFixedIn) (qualifiers []qualifier.Qualifier) { + if fixedInEntry.VersionFormat == "rpm" { + module := "" + if fixedInEntry.Module != nil { + module = *fixedInEntry.Module + } + + qualifiers = []qualifier.Qualifier{rpmmodularity.Qualifier{ + Kind: "rpm-modularity", + Module: module, + }} + } + + return qualifiers +} + func Transform(vulnerability unmarshal.OSVulnerability) ([]data.Entry, error) { var allVulns []grypeDB.Vulnerability @@ -64,20 +85,11 @@ func Transform(vulnerability unmarshal.OSVulnerability) ([]data.Entry, error) { // separate vulnerability entries (one for each name|namespace combo) while merging // constraint ranges as they are found. for idx, fixedInEntry := range vulnerability.Vulnerability.FixedIn { - var qualifiers []qualifier.Qualifier - - if fixedInEntry.Module != nil { - qualifiers = []qualifier.Qualifier{rpmmodularity.Qualifier{ - Kind: "rpm-modularity", - Module: *fixedInEntry.Module, - }} - } - // create vulnerability entry allVulns = append(allVulns, grypeDB.Vulnerability{ ID: vulnerability.Vulnerability.Name, - PackageQualifiers: qualifiers, - VersionConstraint: enforceConstraint(fixedInEntry.Version, fixedInEntry.VersionFormat, vulnerability.Vulnerability.Name), + PackageQualifiers: buildPackageQualifiers(fixedInEntry), + VersionConstraint: enforceConstraint(fixedInEntry.Version, fixedInEntry.VulnerableRange, fixedInEntry.VersionFormat, vulnerability.Vulnerability.Name), VersionFormat: fixedInEntry.VersionFormat, PackageName: grypeNamespace.Resolver().Normalize(fixedInEntry.Name), Namespace: entryNamespace, @@ -208,16 +220,19 @@ func deriveConstraintFromFix(fixVersion, vulnerabilityID string) string { return constraint } -func enforceConstraint(constraint, format, vulnerabilityID string) string { - constraint = common.CleanConstraint(constraint) - if len(constraint) == 0 { +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(constraint) + return common.EnforceSemVerConstraint(fixedVersion) default: // the passed constraint is a fixed version - return deriveConstraintFromFix(constraint, vulnerabilityID) + return deriveConstraintFromFix(fixedVersion, vulnerabilityID) } } diff --git a/pkg/process/v5/transformers/os/transform_test.go b/pkg/process/v5/transformers/os/transform_test.go index 55919589..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" @@ -52,7 +52,11 @@ func TestParseVulnerabilitiesEntry(t *testing.T) { }, }, PackageName: "389-ds-base", - Namespace: "amazon:distro:amazonlinux:2", + PackageQualifiers: []qualifier.Qualifier{rpmmodularity.Qualifier{ + Kind: "rpm-modularity", + Module: "", + }}, + Namespace: "amazon:distro:amazonlinux:2", Fix: grypeDB.Fix{ Versions: []string{"1.3.8.4-15.amzn2.0.1"}, State: grypeDB.FixedState, @@ -69,7 +73,11 @@ func TestParseVulnerabilitiesEntry(t *testing.T) { }, }, PackageName: "389-ds-base-debuginfo", - Namespace: "amazon:distro:amazonlinux:2", + PackageQualifiers: []qualifier.Qualifier{rpmmodularity.Qualifier{ + Kind: "rpm-modularity", + Module: "", + }}, + Namespace: "amazon:distro:amazonlinux:2", Fix: grypeDB.Fix{ Versions: []string{"1.3.8.4-15.amzn2.0.1"}, State: grypeDB.FixedState, @@ -86,7 +94,11 @@ func TestParseVulnerabilitiesEntry(t *testing.T) { }, }, PackageName: "389-ds-base-devel", - Namespace: "amazon:distro:amazonlinux:2", + PackageQualifiers: []qualifier.Qualifier{rpmmodularity.Qualifier{ + Kind: "rpm-modularity", + Module: "", + }}, + Namespace: "amazon:distro:amazonlinux:2", Fix: grypeDB.Fix{ Versions: []string{"1.3.8.4-15.amzn2.0.1"}, State: grypeDB.FixedState, @@ -103,7 +115,11 @@ func TestParseVulnerabilitiesEntry(t *testing.T) { }, }, PackageName: "389-ds-base-libs", - Namespace: "amazon:distro:amazonlinux:2", + PackageQualifiers: []qualifier.Qualifier{rpmmodularity.Qualifier{ + Kind: "rpm-modularity", + Module: "", + }}, + Namespace: "amazon:distro:amazonlinux:2", Fix: grypeDB.Fix{ Versions: []string{"1.3.8.4-15.amzn2.0.1"}, State: grypeDB.FixedState, @@ -120,7 +136,11 @@ func TestParseVulnerabilitiesEntry(t *testing.T) { }, }, PackageName: "389-ds-base-snmp", - Namespace: "amazon:distro:amazonlinux:2", + PackageQualifiers: []qualifier.Qualifier{rpmmodularity.Qualifier{ + Kind: "rpm-modularity", + Module: "", + }}, + Namespace: "amazon:distro:amazonlinux:2", Fix: grypeDB.Fix{ Versions: []string{"1.3.8.4-15.amzn2.0.1"}, State: grypeDB.FixedState, @@ -225,8 +245,12 @@ func TestParseVulnerabilitiesEntry(t *testing.T) { fixture: "test-fixtures/rhel-8.json", vulns: []grypeDB.Vulnerability{ { - ID: "CVE-2020-6819", - PackageName: "firefox", + ID: "CVE-2020-6819", + PackageName: "firefox", + PackageQualifiers: []qualifier.Qualifier{rpmmodularity.Qualifier{ + Kind: "rpm-modularity", + Module: "", + }}, VersionConstraint: "< 0:68.6.1-1.el8_1", VersionFormat: "rpm", Namespace: "redhat:distro:redhat:8", @@ -248,8 +272,12 @@ func TestParseVulnerabilitiesEntry(t *testing.T) { }, }, { - ID: "CVE-2020-6819", - PackageName: "thunderbird", + ID: "CVE-2020-6819", + PackageName: "thunderbird", + PackageQualifiers: []qualifier.Qualifier{rpmmodularity.Qualifier{ + Kind: "rpm-modularity", + Module: "", + }}, VersionConstraint: "< 0:68.7.0-1.el8_1", VersionFormat: "rpm", Namespace: "redhat:distro:redhat:8", @@ -447,8 +475,12 @@ func TestParseVulnerabilitiesEntry(t *testing.T) { fixture: "test-fixtures/ol-8.json", vulns: []grypeDB.Vulnerability{ { - ID: "ELSA-2020-2550", - PackageName: "libexif", + ID: "ELSA-2020-2550", + PackageName: "libexif", + PackageQualifiers: []qualifier.Qualifier{rpmmodularity.Qualifier{ + Kind: "rpm-modularity", + Module: "", + }}, VersionConstraint: "< 0:0.6.21-17.el8_2", VersionFormat: "rpm", RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ @@ -464,8 +496,12 @@ func TestParseVulnerabilitiesEntry(t *testing.T) { }, }, { - ID: "ELSA-2020-2550", - PackageName: "libexif-devel", + ID: "ELSA-2020-2550", + PackageName: "libexif-devel", + PackageQualifiers: []qualifier.Qualifier{rpmmodularity.Qualifier{ + Kind: "rpm-modularity", + Module: "", + }}, VersionConstraint: "< 0:0.6.21-17.el8_2", VersionFormat: "rpm", RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ @@ -481,8 +517,12 @@ func TestParseVulnerabilitiesEntry(t *testing.T) { }, }, { - ID: "ELSA-2020-2550", - PackageName: "libexif-dummy", + ID: "ELSA-2020-2550", + PackageName: "libexif-dummy", + PackageQualifiers: []qualifier.Qualifier{rpmmodularity.Qualifier{ + Kind: "rpm-modularity", + Module: "", + }}, VersionConstraint: "", VersionFormat: "rpm", RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ @@ -586,6 +626,123 @@ func TestParseVulnerabilitiesEntry(t *testing.T) { 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.", }, }, + { + name: "mariner linux 2.0", + numEntries: 1, + fixture: "test-fixtures/mariner-20.json", + vulns: []grypeDB.Vulnerability{ + { + ID: "CVE-2021-37621", + PackageName: "exiv2", + Namespace: "mariner:distro:mariner:2.0", + PackageQualifiers: []qualifier.Qualifier{ + rpmmodularity.Qualifier{ + Kind: "rpm-modularity", + }, + }, + RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ + { + ID: "CVE-2021-37621", + Namespace: "nvd:cpe", + }, + }, + VersionConstraint: "< 0:0.27.5-1.cm2", + VersionFormat: "rpm", + Fix: grypeDB.Fix{ + Versions: []string{"0:0.27.5-1.cm2"}, + State: grypeDB.FixedState, + }, + Advisories: nil, + }, + }, + metadata: grypeDB.VulnerabilityMetadata{ + ID: "CVE-2021-37621", + Namespace: "mariner:distro:mariner:2.0", + DataSource: "https://nvd.nist.gov/vuln/detail/CVE-2021-37621", + RecordSource: "vulnerabilities:mariner:2.0", + Severity: "Medium", + URLs: []string{"https://nvd.nist.gov/vuln/detail/CVE-2021-37621"}, + 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.", + Cvss: nil, + }, + }, + { + name: "azure linux 3", + numEntries: 1, + fixture: "test-fixtures/azure-linux-3.json", + vulns: []grypeDB.Vulnerability{ + { + ID: "CVE-2023-29403", + PackageName: "golang", + Namespace: "mariner:distro:azurelinux:3.0", + PackageQualifiers: []qualifier.Qualifier{ + rpmmodularity.Qualifier{ + Kind: "rpm-modularity", + }, + }, + RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ + { + ID: "CVE-2023-29403", + Namespace: "nvd:cpe", + }, + }, + VersionConstraint: "< 0:1.20.7-1.azl3", + VersionFormat: "rpm", + Fix: grypeDB.Fix{ + Versions: []string{"0:1.20.7-1.azl3"}, + State: grypeDB.FixedState, + }, + }, + }, + metadata: grypeDB.VulnerabilityMetadata{ + ID: "CVE-2023-29403", + Namespace: "mariner:distro:azurelinux:3.0", + DataSource: "https://nvd.nist.gov/vuln/detail/CVE-2023-29403", + RecordSource: "vulnerabilities:mariner:3.0", + Severity: "High", + URLs: []string{"https://nvd.nist.gov/vuln/detail/CVE-2023-29403"}, + Description: "CVE-2023-29403 affecting package golang for versions less than 1.20.7-1. A patched version of the package is available.", + }, + }, + { + name: "mariner entry with version range", + numEntries: 1, + fixture: "test-fixtures/mariner-range.json", + vulns: []grypeDB.Vulnerability{ + { + ID: "CVE-2023-29404", + PackageName: "golang", + Namespace: "mariner:distro:mariner:2.0", + PackageQualifiers: []qualifier.Qualifier{ + rpmmodularity.Qualifier{ + Kind: "rpm-modularity", + Module: "", + }, + }, + VersionConstraint: "> 0:1.19.0.cm2, < 0:1.20.7-1.cm2", + VersionFormat: "rpm", + RelatedVulnerabilities: []grypeDB.VulnerabilityReference{ + { + ID: "CVE-2023-29404", + Namespace: "nvd:cpe", + }, + }, + Fix: grypeDB.Fix{ + Versions: []string{"0:1.20.7-1.cm2"}, + State: grypeDB.FixedState, + }, + }, + }, + metadata: grypeDB.VulnerabilityMetadata{ + ID: "CVE-2023-29404", + Namespace: "mariner:distro:mariner:2.0", + DataSource: "https://nvd.nist.gov/vuln/detail/CVE-2023-29404", + RecordSource: "vulnerabilities:mariner:2.0", + Severity: "Critical", + URLs: []string{"https://nvd.nist.gov/vuln/detail/CVE-2023-29404"}, + Description: "CVE-2023-29404 affecting package golang for versions less than 1.20.7-1. A patched version of the package is available.", + }, + }, } for _, test := range tests { @@ -680,8 +837,12 @@ func TestParseVulnerabilitiesAllEntries(t *testing.T) { fixture: "test-fixtures/amazon-multiple-kernel-advisories.json", vulns: []grypeDB.Vulnerability{ { - ID: "ALAS-2021-1704", - PackageName: "kernel-headers", + ID: "ALAS-2021-1704", + PackageName: "kernel-headers", + PackageQualifiers: []qualifier.Qualifier{rpmmodularity.Qualifier{ + Kind: "rpm-modularity", + Module: "", + }}, VersionConstraint: "< 4.14.246-187.474.amzn2", VersionFormat: "rpm", Namespace: "amazon:distro:amazonlinux:2", @@ -705,8 +866,12 @@ func TestParseVulnerabilitiesAllEntries(t *testing.T) { }, }, { - ID: "ALAS-2021-1704", - PackageName: "kernel", + ID: "ALAS-2021-1704", + PackageName: "kernel", + PackageQualifiers: []qualifier.Qualifier{rpmmodularity.Qualifier{ + Kind: "rpm-modularity", + Module: "", + }}, VersionConstraint: "< 4.14.246-187.474.amzn2", VersionFormat: "rpm", Namespace: "amazon:distro:amazonlinux:2", @@ -730,8 +895,12 @@ func TestParseVulnerabilitiesAllEntries(t *testing.T) { }, }, { - ID: "ALASKERNEL-5.4-2022-007", - PackageName: "kernel-headers", + ID: "ALASKERNEL-5.4-2022-007", + PackageName: "kernel-headers", + PackageQualifiers: []qualifier.Qualifier{rpmmodularity.Qualifier{ + Kind: "rpm-modularity", + Module: "", + }}, VersionConstraint: ">= 5.4, < 5.4.144-69.257.amzn2", VersionFormat: "rpm", Namespace: "amazon:distro:amazonlinux:2", @@ -751,8 +920,12 @@ func TestParseVulnerabilitiesAllEntries(t *testing.T) { }, }, { - ID: "ALASKERNEL-5.4-2022-007", - PackageName: "kernel", + ID: "ALASKERNEL-5.4-2022-007", + PackageName: "kernel", + PackageQualifiers: []qualifier.Qualifier{rpmmodularity.Qualifier{ + Kind: "rpm-modularity", + Module: "", + }}, VersionConstraint: ">= 5.4, < 5.4.144-69.257.amzn2", VersionFormat: "rpm", Namespace: "amazon:distro:amazonlinux:2", @@ -772,8 +945,12 @@ func TestParseVulnerabilitiesAllEntries(t *testing.T) { }, }, { - ID: "ALASKERNEL-5.10-2022-005", - PackageName: "kernel-headers", + ID: "ALASKERNEL-5.10-2022-005", + PackageName: "kernel-headers", + PackageQualifiers: []qualifier.Qualifier{rpmmodularity.Qualifier{ + Kind: "rpm-modularity", + Module: "", + }}, VersionConstraint: ">= 5.10, < 5.10.62-55.141.amzn2", VersionFormat: "rpm", Namespace: "amazon:distro:amazonlinux:2", @@ -793,8 +970,12 @@ func TestParseVulnerabilitiesAllEntries(t *testing.T) { }, }, { - ID: "ALASKERNEL-5.10-2022-005", - PackageName: "kernel", + ID: "ALASKERNEL-5.10-2022-005", + PackageName: "kernel", + PackageQualifiers: []qualifier.Qualifier{rpmmodularity.Qualifier{ + Kind: "rpm-modularity", + Module: "", + }}, VersionConstraint: ">= 5.10, < 5.10.62-55.141.amzn2", VersionFormat: "rpm", Namespace: "amazon:distro:amazonlinux:2", diff --git a/pkg/process/v5/writer.go b/pkg/process/v5/writer.go index 4acf5a71..5dc2c3b3 100644 --- a/pkg/process/v5/writer.go +++ b/pkg/process/v5/writer.go @@ -2,7 +2,9 @@ package v5 import ( "crypto/sha256" + "encoding/json" "fmt" + "os" "path" "path/filepath" "strings" @@ -13,22 +15,36 @@ import ( "github.com/anchore/grype-db/internal/file" "github.com/anchore/grype-db/internal/log" "github.com/anchore/grype-db/pkg/data" - "github.com/anchore/grype/grype/db" + "github.com/anchore/grype-db/pkg/provider" grypeDB "github.com/anchore/grype/grype/db/v5" + "github.com/anchore/grype/grype/db/v5/distribution" grypeDBStore "github.com/anchore/grype/grype/db/v5/store" ) // TODO: add NVDNamespace const to grype.db package? -const nvdNamespace = "nvd:cpe" +const ( + nvdNamespace = "nvd:cpe" + providerMetadataFileName = "provider-metadata.json" +) var _ data.Writer = (*writer)(nil) type writer struct { dbPath string store grypeDB.Store + 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, dataAge time.Time) (data.Writer, error) { +func NewWriter(directory string, dataAge time.Time, states provider.States) (data.Writer, error) { dbPath := path.Join(directory, grypeDB.VulnerabilityStoreFileName) theStore, err := grypeDBStore.New(dbPath, true) if err != nil { @@ -42,6 +58,7 @@ func NewWriter(directory string, dataAge time.Time) (data.Writer, error) { return &writer{ dbPath: dbPath, store: theStore, + states: states, }, nil } @@ -74,18 +91,22 @@ func (w writer) Write(entries ...data.Entry) error { return nil } -func (w writer) metadata() (*db.Metadata, error) { - hashStr, err := file.ContentDigest(afero.NewOsFs(), w.dbPath, sha256.New()) - if err != nil { - return nil, fmt.Errorf("failed to hash database file (%s): %w", w.dbPath, err) - } - +// metadataAndClose closes the database and returns its metadata. +// The reason this is a compound action is that getting the built time and +// schema version from the database is an operation on the open database, +// but the checksum must be computed after the database is compacted and closed. +func (w writer) metadataAndClose() (*distribution.Metadata, error) { storeID, err := w.store.GetID() if err != nil { return nil, fmt.Errorf("failed to fetch store ID: %w", err) } + w.store.Close() + hashStr, err := file.ContentDigest(afero.NewOsFs(), w.dbPath, sha256.New()) + if err != nil { + return nil, fmt.Errorf("failed to hash database file (%s): %w", w.dbPath, err) + } - metadata := db.Metadata{ + metadata := distribution.Metadata{ Built: storeID.BuildTimestamp, Version: storeID.SchemaVersion, Checksum: "sha256:" + hashStr, @@ -93,25 +114,49 @@ func (w writer) metadata() (*db.Metadata, error) { return &metadata, nil } +func NewProviderMetadata() ProviderMetadata { + return ProviderMetadata{ + Providers: make([]Provider, 0), + } +} + +func (w writer) ProviderMetadata() *ProviderMetadata { + metadata := NewProviderMetadata() + // Set provider time from states + for _, state := range w.states { + metadata.Providers = append(metadata.Providers, Provider{ + Name: state.Provider, + LastSuccessfulRun: state.Timestamp, + }) + } + return &metadata +} + func (w writer) Close() error { - w.store.Close() - metadata, err := w.metadata() + metadata, err := w.metadataAndClose() if err != nil { return err } - metadataPath := path.Join(filepath.Dir(w.dbPath), db.MetadataFileName) + metadataPath := path.Join(filepath.Dir(w.dbPath), distribution.MetadataFileName) if err = metadata.Write(metadataPath); err != nil { return err } + providerMetadataPath := path.Join(filepath.Dir(w.dbPath), providerMetadataFileName) + if err = w.ProviderMetadata().Write(providerMetadataPath); err != nil { + return err + } + log.WithFields("path", w.dbPath).Info("database created") log.WithFields("path", metadataPath).Debug("database metadata created") + log.WithFields("path", providerMetadataPath).Debug("provider metadata created") return nil } func normalizeSeverity(metadata *grypeDB.VulnerabilityMetadata, reader grypeDB.VulnerabilityMetadataStoreReader) { + metadata.Severity = string(data.ParseSeverity(metadata.Severity)) if metadata.Severity != "" && strings.ToLower(metadata.Severity) != "unknown" { return } @@ -132,8 +177,20 @@ func normalizeSeverity(metadata *grypeDB.VulnerabilityMetadata, reader grypeDB.V } newSeverity := string(data.ParseSeverity(m.Severity)) - - log.WithFields("id", metadata.ID, "namespace", metadata.Namespace, "sev-from", metadata.Severity, "sev-to", newSeverity).Trace("overriding irrelevant severity with data from NVD record") - + if newSeverity != metadata.Severity { + log.WithFields("id", metadata.ID, "namespace", metadata.Namespace, "sev-from", metadata.Severity, "sev-to", newSeverity).Trace("overriding irrelevant severity with data from NVD record") + } metadata.Severity = newSeverity } + +func (p ProviderMetadata) Write(path string) error { + providerMetadataJSON, err := json.MarshalIndent(p, "", " ") + if err != nil { + return fmt.Errorf("unable to marshal provider metadata: %w", err) + } + //nolint:gosec + if err = os.WriteFile(path, providerMetadataJSON, 0644); err != nil { + return fmt.Errorf("unable to write provider metadata: %w", err) + } + return nil +} diff --git a/pkg/process/v5/writer_test.go b/pkg/process/v5/writer_test.go index 7c552361..6785aa7a 100644 --- a/pkg/process/v5/writer_test.go +++ b/pkg/process/v5/writer_test.go @@ -51,29 +51,37 @@ func Test_normalizeSeverity(t *testing.T) { expected data.Severity }{ { - name: "skip missing metadata", + name: "missing severity set to Unknown", initialSeverity: "", namespace: "test", reader: &mockReader{}, - expected: "", + expected: data.SeverityUnknown, }, { - name: "skip non-cve records metadata", + name: "non-cve records metadata missing severity set to Unknown", cveID: "GHSA-1234-1234-1234", initialSeverity: "", namespace: "test", reader: newDeadMockReader(), // should not be used - expected: "", + expected: data.SeverityUnknown, }, { - name: "override empty severity", + name: "non-cve records metadata with severity set should not be overriden", + cveID: "GHSA-1234-1234-1234", + initialSeverity: "high", + namespace: "test", + reader: newMockReader("critical"), // should not be used + expected: data.SeverityHigh, + }, + { + name: "override empty severity from NVD", initialSeverity: "", namespace: "test", reader: newMockReader("low"), expected: data.SeverityLow, }, { - name: "override unknown severity", + name: "override unknown severity from NVD", initialSeverity: "unknown", namespace: "test", reader: newMockReader("low"), @@ -89,16 +97,16 @@ func Test_normalizeSeverity(t *testing.T) { { name: "ignore nvd records", initialSeverity: "Low", - namespace: "nvd:cpe", + namespace: "nvdv2:cves", reader: newDeadMockReader(), // should not be used expected: data.SeverityLow, }, { - name: "db errors should not fail or modify the record", + name: "db errors should not fail or modify the record other than normalizing unset value", initialSeverity: "", namespace: "test", reader: newDeadMockReader(), - expected: "", + expected: data.SeverityUnknown, }, } for _, tt := range tests { diff --git a/pkg/process/v6/archive.go b/pkg/process/v6/archive.go new file mode 100644 index 00000000..bb6c8a48 --- /dev/null +++ b/pkg/process/v6/archive.go @@ -0,0 +1,171 @@ +package v6 + +import ( + "errors" + "fmt" + "os" + "path" + "path/filepath" + "strings" + "time" + + "github.com/anchore/grype-db/internal/log" + "github.com/anchore/grype-db/internal/tarutil" + "github.com/anchore/grype-db/pkg/provider" + v6 "github.com/anchore/grype/grype/db/v6" + v6Distribution "github.com/anchore/grype/grype/db/v6/distribution" +) + +func CreateArchive(dbDir, overrideArchiveExtension string) error { + extension, err := resolveExtension(overrideArchiveExtension) + if err != nil { + return err + } + log.WithFields("from", dbDir, "extension", extension).Info("packaging database") + + cfg := v6.Config{DBDirPath: dbDir} + r, err := v6.NewReader(cfg) + if err != nil { + return fmt.Errorf("unable to open vulnerability store: %w", err) + } + + metadata, err := r.GetDBMetadata() + if err != nil || metadata == 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) + } + + providerModels, err := r.AllProviders() + if err != nil { + return fmt.Errorf("unable to get all providers: %w", err) + } + + if len(providerModels) == 0 { + return fmt.Errorf("no providers found in the vulnerability store") + } + + eldest, err := toProviders(providerModels).EarliestTimestamp() + if err != nil { + return err + } + + // output archive vulnerability-db_VERSION_OLDESTDATADATE_BUILTEPOCH.tar.gz, where: + // - VERSION: schema version in the form of v#.#.# + // - OLDESTDATADATE: RFC3339 formatted value (e.g. 2020-06-18T17:24:53Z) of the oldest date capture date found for all contained providers + // - BUILTEPOCH: linux epoch formatted value of the database metadata built field + tarName := fmt.Sprintf( + "vulnerability-db_v%s_%s_%d.%s", + fmt.Sprintf("%d.%d.%d", metadata.Model, metadata.Revision, metadata.Addition), + eldest.UTC().Format(time.RFC3339), + metadata.BuildTimestamp.Unix(), + extension, + ) + + tarPath := filepath.Join(dbDir, tarName) + files := []string{v6.VulnerabilityDBFileName} + + if _, err := os.Stat(path.Join(dbDir, v6.ImportMetadataFileName)); err == nil { + files = append(files, v6.ImportMetadataFileName) + } + + if err := populateTar(dbDir, tarName, files...); err != nil { + return err + } + + log.WithFields("path", tarPath).Info("created database archive") + + return writeLatestDocument(tarPath, *metadata) +} + +func toProviders(states []v6.Provider) provider.States { + var result provider.States + for _, state := range states { + result = append(result, provider.State{ + Provider: state.ID, + Timestamp: *state.DateCaptured, + }) + } + return result +} + +func resolveExtension(overrideArchiveExtension string) (string, error) { + var extension = "tar.zst" + + if overrideArchiveExtension != "" { + extension = strings.TrimLeft(overrideArchiveExtension, ".") + } + + var found bool + for _, valid := range []string{"tar.zst", "tar.xz", "tar.gz"} { + if valid == extension { + found = true + break + } + } + + if !found { + return "", fmt.Errorf("unsupported archive extension %q", extension) + } + return extension, nil +} + +func populateTar(dbDir, tarName string, files ...string) error { + originalDir, err := os.Getwd() + if err != nil { + return fmt.Errorf("unable to get CWD: %w", err) + } + + if dbDir != "" { + if err = os.Chdir(dbDir); err != nil { + return fmt.Errorf("unable to cd to build dir: %w", err) + } + + defer func() { + if err = os.Chdir(originalDir); err != nil { + log.Errorf("unable to cd to original dir: %v", err) + } + }() + } + + for _, f := range files { + _, err := os.Stat(f) + if err != nil { + return fmt.Errorf("unable to stat file %q: %w", f, err) + } + } + + if err = tarutil.PopulateWithPaths(tarName, files...); err != nil { + return fmt.Errorf("unable to create db archive: %w", err) + } + + return nil +} + +func writeLatestDocument(tarPath string, metadata v6.DBMetadata) error { + archive, err := v6Distribution.NewArchive(tarPath, *metadata.BuildTimestamp, metadata.Model, metadata.Revision, metadata.Addition) + if err != nil || archive == nil { + return fmt.Errorf("unable to create archive: %w", err) + } + + doc := v6Distribution.NewLatestDocument(*archive) + if doc == nil { + return errors.New("unable to create latest document") + } + + dbDir := filepath.Dir(tarPath) + + latestPath := path.Join(dbDir, v6Distribution.LatestFileName) + + fh, err := os.OpenFile(latestPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) + if err != nil { + return fmt.Errorf("unable to create latest file: %w", err) + } + + if err = doc.Write(fh); err != nil { + return fmt.Errorf("unable to write latest document: %w", err) + } + return nil +} diff --git a/pkg/process/v6/internal/tests/utils.go b/pkg/process/v6/internal/tests/utils.go new file mode 100644 index 00000000..ddfb6028 --- /dev/null +++ b/pkg/process/v6/internal/tests/utils.go @@ -0,0 +1,14 @@ +package tests + +import ( + "log" + "os" +) + +func CloseFile(f *os.File) { + err := f.Close() + + if err != nil { + log.Fatal("error closing file") + } +} diff --git a/pkg/process/v6/processors.go b/pkg/process/v6/processors.go new file mode 100644 index 00000000..a5debaf5 --- /dev/null +++ b/pkg/process/v6/processors.go @@ -0,0 +1,54 @@ +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/epss" + "github.com/anchore/grype-db/pkg/process/v6/transformers/github" + "github.com/anchore/grype-db/pkg/process/v6/transformers/kev" + "github.com/anchore/grype-db/pkg/process/v6/transformers/msrc" + "github.com/anchore/grype-db/pkg/process/v6/transformers/nvd" + "github.com/anchore/grype-db/pkg/process/v6/transformers/os" + "github.com/anchore/grype-db/pkg/process/v6/transformers/osv" +) + +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.NewV2MSRCProcessor(msrc.Transform), + processors.NewV2NVDProcessor(nvd.Transformer(cfg.NVD)), + processors.NewV2OSProcessor(os.Transform), + processors.NewV2OSVProcessor(osv.Transform), + processors.NewV2KEVProcessor(kev.Transform), + processors.NewV2EPSSProcessor(epss.Transform), + } +} diff --git a/pkg/process/v6/transformers/entry.go b/pkg/process/v6/transformers/entry.go new file mode 100644 index 00000000..11b2eb35 --- /dev/null +++ b/pkg/process/v6/transformers/entry.go @@ -0,0 +1,71 @@ +package transformers + +import ( + "fmt" + "strings" + + "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 i := range models { + model := models[i] + switch m := model.(type) { + case grypeDB.VulnerabilityHandle: + entry.VulnerabilityHandle = &m + case grypeDB.AffectedPackageHandle: + entry.Related = append(entry.Related, m) + case grypeDB.AffectedCPEHandle: + entry.Related = append(entry.Related, m) + case grypeDB.KnownExploitedVulnerabilityHandle: + entry.Related = append(entry.Related, m) + case grypeDB.Provider: + entry.Provider = &m + case grypeDB.EpssHandle: + entry.Related = append(entry.Related, m) + default: + panic(fmt.Sprintf("unsupported model type: %T", m)) + } + } + + return []data.Entry{ + { + DBSchemaVersion: grypeDB.ModelVersion, + Data: entry, + }, + } +} + +func (re RelatedEntries) String() string { + var pkgs []string + for _, r := range re.Related { + switch v := r.(type) { + case grypeDB.AffectedPackageHandle: + pkgs = append(pkgs, v.Package.String()) + case grypeDB.AffectedCPEHandle: + pkgs = append(pkgs, fmt.Sprintf("%s/%s", v.CPE.Vendor, v.CPE.Product)) + case grypeDB.KnownExploitedVulnerabilityHandle: + pkgs = append(pkgs, "kev="+v.Cve) + } + } + var fields []string + if re.VulnerabilityHandle != nil { + fields = append(fields, fmt.Sprintf("vuln=%q", re.VulnerabilityHandle.Name)) + fields = append(fields, fmt.Sprintf("provider=%q", re.VulnerabilityHandle.ProviderID)) + } else if re.Provider != nil { + fields = append(fields, fmt.Sprintf("provider=%q", re.Provider.ID)) + } + + fields = append(fields, fmt.Sprintf("entries=%d", len(re.Related))) + + return fmt.Sprintf("%s: %s", strings.Join(fields, " "), strings.Join(pkgs, ", ")) +} diff --git a/pkg/process/v6/transformers/epss/test-fixtures/go-case.json b/pkg/process/v6/transformers/epss/test-fixtures/go-case.json new file mode 100644 index 00000000..402e145e --- /dev/null +++ b/pkg/process/v6/transformers/epss/test-fixtures/go-case.json @@ -0,0 +1,6 @@ +{ + "cve": "CVE-2025-0108", + "epss": 0.328, + "percentile": 0.9929, + "date": "2025-02-18" +} \ No newline at end of file diff --git a/pkg/process/v6/transformers/epss/transform.go b/pkg/process/v6/transformers/epss/transform.go new file mode 100644 index 00000000..c98ee21d --- /dev/null +++ b/pkg/process/v6/transformers/epss/transform.go @@ -0,0 +1,30 @@ +package epss + +import ( + "fmt" + "time" + + "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" + grypeDB "github.com/anchore/grype/grype/db/v6" +) + +func Transform(entry unmarshal.EPSS, state provider.State) ([]data.Entry, error) { + date := internal.ParseTime(entry.Date) + if date == nil { + return nil, fmt.Errorf("failed to parse date: %q", entry.Date) + } + return transformers.NewEntries(*internal.ProviderModel(state), getEPSS(entry, *date)), nil +} + +func getEPSS(entry unmarshal.EPSS, date time.Time) grypeDB.EpssHandle { + return grypeDB.EpssHandle{ + Cve: entry.CVE, + Epss: entry.EPSS, + Percentile: entry.Percentile, + Date: date, + } +} diff --git a/pkg/process/v6/transformers/epss/transform_test.go b/pkg/process/v6/transformers/epss/transform_test.go new file mode 100644 index 00000000..9c8af5d2 --- /dev/null +++ b/pkg/process/v6/transformers/epss/transform_test.go @@ -0,0 +1,102 @@ +package epss + +import ( + "os" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" + + "github.com/anchore/grype-db/pkg/process/internal/tests" + "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) { + + var timeVal = time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC) + var listing = provider.File{ + Path: "some", + Digest: "123456", + Algorithm: "sha256", + } + + tests := []struct { + name string + want []transformers.RelatedEntries + }{ + { + name: "test-fixtures/go-case.json", + want: []transformers.RelatedEntries{ + { + Provider: &grypeDB.Provider{ + ID: "epss", + Version: "12", + Processor: "vunnel@1.2.3", + DateCaptured: &timeVal, + InputDigest: "sha256:123456", + }, + Related: epssSlice( + grypeDB.EpssHandle{ + Cve: "CVE-2025-0108", + Epss: 0.328, + Percentile: 0.9929, + Date: *internal.ParseTime("2025-02-18"), + }, + ), + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + entries := loadFixture(t, test.name) + + var actual []transformers.RelatedEntries + for _, vuln := range entries { + entries, err := Transform(vuln, provider.State{ + Provider: "epss", + Version: 12, + Processor: "vunnel@1.2.3", + Timestamp: timeVal, + Listing: &listing, + }) + 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 epssSlice(a ...grypeDB.EpssHandle) []any { + var r []any + for _, v := range a { + r = append(r, v) + } + return r +} + +func loadFixture(t *testing.T, fixturePath string) []unmarshal.EPSS { + t.Helper() + + f, err := os.Open(fixturePath) + require.NoError(t, err) + defer tests.CloseFile(f) + + entries, err := unmarshal.EPSSEntries(f) + require.NoError(t, err) + return entries +} 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/GHSA-92cp-5422-2mw7.json b/pkg/process/v6/transformers/github/test-fixtures/GHSA-92cp-5422-2mw7.json new file mode 100644 index 00000000..eda80f94 --- /dev/null +++ b/pkg/process/v6/transformers/github/test-fixtures/GHSA-92cp-5422-2mw7.json @@ -0,0 +1,56 @@ +{ + "Vulnerability": {}, + "Advisory": { + "Classification": "GENERAL", + "Severity": "Low", + "CVSS": { + "version": "3.1", + "vector_string": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N", + "base_metrics": { + "base_score": 3.7, + "exploitability_score": 2.2, + "impact_score": 1.4, + "base_severity": "Low" + }, + "status": "N/A" + }, + "FixedIn": [ + { + "name": "github.com/redis/go-redis/v9", + "identifier": "9.7.3", + "ecosystem": "go", + "namespace": "github:go", + "range": ">= 9.7.0-beta.1 < 9.7.3" + }, + { + "name": "github.com/redis/go-redis/v9", + "identifier": "9.6.3", + "ecosystem": "go", + "namespace": "github:go", + "range": ">= 9.6.0b1 < 9.6.3" + }, + { + "name": "github.com/redis/go-redis/v9", + "identifier": "9.5.5", + "ecosystem": "go", + "namespace": "github:go", + "range": ">= 9.5.1 < 9.5.5" + } + ], + "Summary": "go-redis allows potential out of order responses when `CLIENT SETINFO` times out during connection establishment", + "url": "https://github.com/advisories/GHSA-92cp-5422-2mw7", + "CVE": [ + "CVE-2025-29923" + ], + "Metadata": { + "CVE": [ + "CVE-2025-29923" + ] + }, + "ghsaId": "GHSA-92cp-5422-2mw7", + "published": "2025-03-20T18:49:59Z", + "updated": "2025-03-20T18:50:01Z", + "withdrawn": null, + "namespace": "github:go" + } +} 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/v1/transformers/github/test-fixtures/github-github-python-0.json b/pkg/process/v6/transformers/github/test-fixtures/github-github-python-0.json similarity index 100% rename from pkg/process/v1/transformers/github/test-fixtures/github-github-python-0.json rename to pkg/process/v6/transformers/github/test-fixtures/github-github-python-0.json diff --git a/pkg/process/v1/transformers/github/test-fixtures/github-withdrawn.json b/pkg/process/v6/transformers/github/test-fixtures/github-withdrawn.json similarity index 100% rename from pkg/process/v1/transformers/github/test-fixtures/github-withdrawn.json rename to pkg/process/v6/transformers/github/test-fixtures/github-withdrawn.json diff --git a/pkg/process/v1/transformers/github/test-fixtures/multiple-fixed-in-names.json b/pkg/process/v6/transformers/github/test-fixtures/multiple-fixed-in-names.json similarity index 100% rename from pkg/process/v1/transformers/github/test-fixtures/multiple-fixed-in-names.json rename to pkg/process/v6/transformers/github/test-fixtures/multiple-fixed-in-names.json diff --git a/pkg/process/v6/transformers/github/transform.go b/pkg/process/v6/transformers/github/transform.go new file mode 100644 index 00000000..5b0dac51 --- /dev/null +++ b/pkg/process/v6/transformers/github/transform.go @@ -0,0 +1,253 @@ +package github + +import ( + "sort" + "strings" + + "github.com/anchore/grype-db/internal/log" + "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" + "github.com/anchore/grype/grype/db/v6/name" + "github.com/anchore/grype/grype/version" + "github.com/anchore/syft/syft/pkg" +) + +func Transform(vulnerability unmarshal.GitHubAdvisory, state provider.State) ([]data.Entry, error) { + ins := []any{ + getVulnerability(vulnerability, state), + } + + for _, a := range getAffectedPackage(vulnerability) { + ins = append(ins, a) + } + + return transformers.NewEntries(ins...), nil +} + +func getVulnerability(vuln unmarshal.GitHubAdvisory, state provider.State) grypeDB.VulnerabilityHandle { + return grypeDB.VulnerabilityHandle{ + Name: vuln.Advisory.GhsaID, + ProviderID: state.Provider, + Provider: internal.ProviderModel(state), + ModifiedDate: internal.ParseTime(vuln.Advisory.Updated), + PublishedDate: internal.ParseTime(vuln.Advisory.Published), + WithdrawnDate: internal.ParseTime(vuln.Advisory.Withdrawn), + Status: getVulnStatus(vuln), + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: vuln.Advisory.GhsaID, + // 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. + Assigners: nil, + Description: strings.TrimSpace(vuln.Advisory.Summary), + 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) + hasRangeErr := false + for group, fixedIns := range groups { + for _, fixedInEntry := range fixedIns { + ranges, rangeErr := getRanges(fixedInEntry) + if rangeErr != nil { + hasRangeErr = true + } + afs = append(afs, grypeDB.AffectedPackageHandle{ + Package: getPackage(group), + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: getAliases(vuln), + Ranges: ranges, + }, + }) + } + } + + // stable ordering + sort.Sort(internal.ByAffectedPackage(afs)) + + if hasRangeErr { + log.Warnf("for %s falling back to fuzzy matching on at least one constraint range", vuln.Advisory.GhsaID) + } + return afs +} + +func getRanges(fixedInEntry unmarshal.GithubFixedIn) ([]grypeDB.AffectedRange, error) { + fixedVersion := grypeDB.AffectedVersion{ + Type: getAffectedVersionFormat(fixedInEntry), + Constraint: common.EnforceSemVerConstraint(fixedInEntry.Range), + } + err := validateAffectedVersion(fixedVersion) + if err != nil { + log.Warnf("failed to validate affected version: %v", err) + fixedVersion.Type = "Unknown" + } + return []grypeDB.AffectedRange{ + { + Version: fixedVersion, + Fix: getFix(fixedInEntry), + }, + }, err +} + +func validateAffectedVersion(v grypeDB.AffectedVersion) error { + versionFormat := version.ParseFormat(v.Type) + _, err := version.GetConstraint(v.Constraint, versionFormat) + return err +} + +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) pkg.Type { + ecosystem = strings.ToLower(ecosystem) + switch ecosystem { + case "composer": + return pkg.PhpComposerPkg + case "rust", "cargo": + return pkg.RustPkg + case "dart": + return pkg.DartPubPkg + case "nuget", ".net": + return pkg.DotnetPkg + case "go", "golang": + return pkg.GoModulePkg + case "maven", "java": + return pkg.JavaPkg + case "npm": + return pkg.NpmPkg + case "pypi", "python", "pip": + return pkg.PythonPkg + case "swift": + return pkg.SwiftPkg + case "rubygems", "ruby", "gem": + return pkg.GemPkg + case "apk": + return pkg.ApkPkg + case "rpm": + return pkg.RpmPkg + case "deb": + return pkg.DebPkg + case "github-action": + return pkg.GithubActionPkg + } + ty := pkg.TypeByName(ecosystem) + if ty != pkg.UnknownPkg { + return ty + } + + log.Warnf("using unknown ecosystem intead of syft pkg type (this will probably cause issues when matching): %q", ecosystem) + + return pkg.Type(ecosystem) +} + +func getPackage(group groupIndex) *grypeDB.Package { + t := getPackageType(group.ecosystem) + return &grypeDB.Package{ + Name: name.Normalize(group.name, t), + Ecosystem: string(t), + } +} + +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, + }, + }) + } + + 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{ + { + 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..51713667 --- /dev/null +++ b/pkg/process/v6/transformers/github/transform_test.go @@ -0,0 +1,661 @@ +package github + +import ( + "os" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" + "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" + "github.com/anchore/grype/grype/version" + "github.com/anchore/syft/syft/pkg" +) + +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.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) { + now := time.Date(2024, 03, 01, 12, 0, 0, 0, time.UTC) + tests := []struct { + name string + expected []grypeDB.VulnerabilityHandle + }{ + { + name: "test-fixtures/GHSA-2wgc-48g2-cj5w.json", + expected: []grypeDB.VulnerabilityHandle{ + { + Name: "GHSA-2wgc-48g2-cj5w", + ProviderID: "github", + Provider: &grypeDB.Provider{ + ID: "github", + Version: "1", + DateCaptured: &now, + }, + ModifiedDate: internal.ParseTime("2024-02-08T22:48:31Z"), + PublishedDate: internal.ParseTime("2024-01-30T20:56:46Z"), + WithdrawnDate: nil, + Status: grypeDB.VulnerabilityActive, + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "GHSA-2wgc-48g2-cj5w", + Description: "vantage6 has insecure SSH configuration for node and server containers", + References: []grypeDB.Reference{ + { + 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", + }, + }, + }, + }, + }, + }, + }, + { + name: "test-fixtures/GHSA-3x74-v64j-qc3f.json", + expected: []grypeDB.VulnerabilityHandle{ + { + Name: "GHSA-3x74-v64j-qc3f", + ProviderID: "github", + Provider: &grypeDB.Provider{ + ID: "github", + Version: "1", + DateCaptured: &now, + }, + 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, + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "GHSA-3x74-v64j-qc3f", + Description: "Withdrawn Advisory: CraftCMS Server-Side Template Injection vulnerability", + References: []grypeDB.Reference{ + { + 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", + }, + }, + }, + }, + }, + }, + }, + { + name: "test-fixtures/github-github-npm-0.json", + expected: []grypeDB.VulnerabilityHandle{ + { + Name: "GHSA-vc9j-fhvv-8vrf", + ProviderID: "github", + Provider: &grypeDB.Provider{ + ID: "github", + Version: "1", + DateCaptured: &now, + }, + ModifiedDate: internal.ParseTime("2023-01-09T05:03:39Z"), + PublishedDate: internal.ParseTime("2020-07-27T19:55:52Z"), + WithdrawnDate: nil, + Status: grypeDB.VulnerabilityActive, + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "GHSA-vc9j-fhvv-8vrf", + Description: "Remote Code Execution in scratch-vm", + References: []grypeDB.Reference{ + { + 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", + }, + }, + }, + }, + }, + }, + }, + { + name: "test-fixtures/github-github-python-0.json", + expected: []grypeDB.VulnerabilityHandle{ + { + Name: "GHSA-6cwv-x26c-w2q4", + ProviderID: "github", + Provider: &grypeDB.Provider{ + ID: "github", + Version: "1", + DateCaptured: &now, + }, + Status: "active", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "GHSA-6cwv-x26c-w2q4", + Description: "Low severity vulnerability that affects notebook", + References: []grypeDB.Reference{ + { + URL: "https://github.com/advisories/GHSA-6cwv-x26c-w2q4", + }, + }, + + Aliases: []string{"CVE-2018-8768"}, + + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHML, + Value: "low", + }, + }, + }, + }, + { + Name: "GHSA-p5wr-vp8g-q5p4", + ProviderID: "github", + Provider: &grypeDB.Provider{ + ID: "github", + Version: "1", + DateCaptured: &now, + }, + Status: "active", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "GHSA-p5wr-vp8g-q5p4", + Description: "Moderate severity vulnerability that affects Plone", + References: []grypeDB.Reference{ + { + URL: "https://github.com/advisories/GHSA-p5wr-vp8g-q5p4", + }, + }, + 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", + ProviderID: "github", + Provider: &grypeDB.Provider{ + ID: "github", + Version: "1", + DateCaptured: &now, + }, + ModifiedDate: nil, + PublishedDate: nil, + WithdrawnDate: internal.ParseTime("2022-01-31T14:32:09Z"), + Status: grypeDB.VulnerabilityRejected, + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "GHSA-6cwv-x26c-w2q4", + Description: "Low severity vulnerability that affects notebook", + References: []grypeDB.Reference{ + { + 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", + ProviderID: "github", + Provider: &grypeDB.Provider{ + ID: "github", + Version: "1", + DateCaptured: &now, + }, + Status: grypeDB.VulnerabilityActive, + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "GHSA-p5wr-vp8g-q5p4", + Description: "Moderate severity vulnerability that affects Plone", + References: []grypeDB.Reference{ + { + 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, provider.State{Provider: "github", Version: 1, Timestamp: now}) + 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", + Ecosystem: "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", + Ecosystem: "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", + Ecosystem: "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{ + Ecosystem: "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{ + Ecosystem: "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", + Ecosystem: "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", + Ecosystem: "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", + Ecosystem: "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 TestGetPackageType(t *testing.T) { + tests := []struct { + ecosystem string + expectedType pkg.Type + }{ + {"composer", pkg.PhpComposerPkg}, + {"Composer", pkg.PhpComposerPkg}, // testing case insensitivity + {"COMPOSER", pkg.PhpComposerPkg}, // testing case insensitivity + {"rust", pkg.RustPkg}, + {"cargo", pkg.RustPkg}, + {"dart", pkg.DartPubPkg}, + {"nuget", pkg.DotnetPkg}, + {".net", pkg.DotnetPkg}, + {"go", pkg.GoModulePkg}, + {"golang", pkg.GoModulePkg}, + {"maven", pkg.JavaPkg}, + {"java", pkg.JavaPkg}, + {"npm", pkg.NpmPkg}, + {"pypi", pkg.PythonPkg}, + {"python", pkg.PythonPkg}, + {"pip", pkg.PythonPkg}, + {"swift", pkg.SwiftPkg}, + {"rubygems", pkg.GemPkg}, + {"ruby", pkg.GemPkg}, + {"gem", pkg.GemPkg}, + {"apk", pkg.ApkPkg}, + {"rpm", pkg.RpmPkg}, + {"deb", pkg.DebPkg}, + {"github-action", pkg.GithubActionPkg}, + + // test for unknown type fallback + {"unknown-ecosystem", pkg.Type("unknown-ecosystem")}, + {"", pkg.Type("")}, + } + + for _, tc := range tests { + t.Run(tc.ecosystem, func(t *testing.T) { + gotType := getPackageType(tc.ecosystem) + if gotType != tc.expectedType { + t.Errorf("getPackageType(%q) = %v, want %v", tc.ecosystem, gotType, tc.expectedType) + } + }) + } +} + +func TestGetRanges(t *testing.T) { + advisories := loadFixture(t, "test-fixtures/GHSA-92cp-5422-2mw7.json") + require.Len(t, advisories, 1) + advisory := advisories[0] + var ranges []grypeDB.AffectedRange + expectedRanges := []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Type: "go", + Constraint: ">=9.7.0-beta.1,<9.7.3", + }, + Fix: &grypeDB.Fix{ + Version: "9.7.3", + State: grypeDB.FixedStatus, + }, + }, + { + Version: grypeDB.AffectedVersion{ + // important: this emits an unknown constraint type, + // triggering fuzzy matching when the input is not + // valid semver + Type: "Unknown", + Constraint: ">=9.6.0b1,<9.6.3", + }, + Fix: &grypeDB.Fix{ + Version: "9.6.3", + State: grypeDB.FixedStatus, + }, + }, + { + Version: grypeDB.AffectedVersion{ + Type: "go", + Constraint: ">=9.5.1,<9.5.5", + }, + Fix: &grypeDB.Fix{ + Version: "9.5.5", + State: grypeDB.FixedStatus, + }, + }, + } + var errors []error + for _, fixedIn := range advisory.Advisory.FixedIn { + rng, err := getRanges(fixedIn) + if err != nil { + errors = append(errors, err) + } + ranges = append(ranges, rng...) + } + + assert.Equal(t, 1, len(errors)) + assert.ErrorIs(t, errors[0], version.ErrFallbackToFuzzy) + if diff := cmp.Diff(expectedRanges, ranges); diff != "" { + t.Errorf("getRanges() mismatch (-want +got):\n%s", diff) + } +} + +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..1ab1c26c --- /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..15e58634 --- /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..19fbbe02 --- /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.Ecosystem == a[j].Package.Ecosystem { + 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.Ecosystem < a[j].Package.Ecosystem + } + 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/kev/test-fixtures/go-case.json b/pkg/process/v6/transformers/kev/test-fixtures/go-case.json new file mode 100644 index 00000000..6c1985e8 --- /dev/null +++ b/pkg/process/v6/transformers/kev/test-fixtures/go-case.json @@ -0,0 +1,15 @@ +{ + "cveID": "CVE-2025-0108", + "vendorProject": "Palo Alto Networks", + "product": "PAN-OS", + "vulnerabilityName": "Palo Alto Networks PAN-OS Authentication Bypass Vulnerability", + "dateAdded": "2025-02-18", + "shortDescription": "Palo Alto Networks PAN-OS contains an authentication bypass vulnerability in its management web interface. This vulnerability allows an unauthenticated attacker with network access to the management web interface to bypass the authentication normally required and invoke certain PHP scripts.", + "requiredAction": "Apply mitigations per vendor instructions [https://www.vendor.com/instructions] or discontinue use of the product if mitigations are unavailable [https:\/\/www.vendor.com\/something-else].", + "dueDate": "2025-03-11", + "knownRansomwareCampaignUse": "Unknown", + "notes": "https:\/\/security.paloaltonetworks.com\/CVE-2025-0108 ; https:\/\/nvd.nist.gov\/vuln\/detail\/CVE-2025-0108 ; remaining information", + "cwes": [ + "CWE-306" + ] +} diff --git a/pkg/process/v6/transformers/kev/transform.go b/pkg/process/v6/transformers/kev/transform.go new file mode 100644 index 00000000..a0a1e912 --- /dev/null +++ b/pkg/process/v6/transformers/kev/transform.go @@ -0,0 +1,84 @@ +package kev + +import ( + "regexp" + "strings" + + "github.com/scylladb/go-set/strset" + + "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" + grypeDB "github.com/anchore/grype/grype/db/v6" +) + +func Transform(kev unmarshal.KnownExploitedVulnerability, state provider.State) ([]data.Entry, error) { + return transformers.NewEntries(*internal.ProviderModel(state), getKev(kev)), nil +} + +func getKev(kev unmarshal.KnownExploitedVulnerability) grypeDB.KnownExploitedVulnerabilityHandle { + urls, notes := getURLs([]string{kev.ShortDescription, kev.RequiredAction}, kev.Notes) + return grypeDB.KnownExploitedVulnerabilityHandle{ + Cve: kev.CveID, + BlobValue: &grypeDB.KnownExploitedVulnerabilityBlob{ + Cve: kev.CveID, + VendorProject: kev.VendorProject, + Product: kev.Product, + DateAdded: internal.ParseTime(kev.DateAdded), + RequiredAction: kev.RequiredAction, + DueDate: internal.ParseTime(kev.DueDate), + KnownRansomwareCampaignUse: strings.ToLower(kev.KnownRansomwareCampaignUse), + Notes: notes, + CWEs: kev.CWEs, + URLs: urls, + }, + } +} + +var bracketURLPattern = regexp.MustCompile(`\[(https?://[^\]]+)\]`) + +func getURLs(aux []string, notes string) ([]string, string) { + // let's keep the URLs we find in order but also deduplicate them since we're combining URLs from multiple sources + urlSet := strset.New() + var urls []string + + // add URLs from notes first... + if notes != "" { + parts := strings.Split(notes, ";") + cleanedParts := make([]string, 0, len(parts)) + + for _, part := range parts { + part = strings.TrimSpace(part) + + if strings.HasPrefix(strings.ToLower(part), "http") { + url := part + if !urlSet.Has(url) { + urlSet.Add(url) + urls = append(urls, url) + } + } else if part != "" { + cleanedParts = append(cleanedParts, part) + } + } + + notes = strings.Join(cleanedParts, "; ") + } + + // ...then add URLs from the other fields + for _, text := range aux { + matches := bracketURLPattern.FindAllStringSubmatch(text, -1) + for _, match := range matches { + if len(match) > 1 { + url := match[1] + if !urlSet.Has(url) { + urlSet.Add(url) + urls = append(urls, url) + } + } + } + } + + return urls, notes +} diff --git a/pkg/process/v6/transformers/kev/transform_test.go b/pkg/process/v6/transformers/kev/transform_test.go new file mode 100644 index 00000000..0ba8fa05 --- /dev/null +++ b/pkg/process/v6/transformers/kev/transform_test.go @@ -0,0 +1,116 @@ +package kev + +import ( + "os" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" + + "github.com/anchore/grype-db/pkg/process/v6/internal/tests" + "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) { + + var timeVal = time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC) + var listing = provider.File{ + Path: "some", + Digest: "123456", + Algorithm: "sha256", + } + + tests := []struct { + name string + want []transformers.RelatedEntries + }{ + { + name: "test-fixtures/go-case.json", + want: []transformers.RelatedEntries{ + { + Provider: &grypeDB.Provider{ + ID: "kev", + Version: "12", + Processor: "vunnel@1.2.3", + DateCaptured: &timeVal, + InputDigest: "sha256:123456", + }, + Related: kevSlice( + grypeDB.KnownExploitedVulnerabilityHandle{ + Cve: "CVE-2025-0108", + BlobValue: &grypeDB.KnownExploitedVulnerabilityBlob{ + Cve: "CVE-2025-0108", + VendorProject: "Palo Alto Networks", + Product: "PAN-OS", + DateAdded: internal.ParseTime("2025-02-18"), + RequiredAction: "Apply mitigations per vendor instructions [https://www.vendor.com/instructions] or discontinue use of the product if mitigations are unavailable [https://www.vendor.com/something-else].", + DueDate: internal.ParseTime("2025-03-11"), + KnownRansomwareCampaignUse: "unknown", + Notes: "remaining information", + URLs: []string{ + "https://security.paloaltonetworks.com/CVE-2025-0108", + "https://nvd.nist.gov/vuln/detail/CVE-2025-0108", + "https://www.vendor.com/instructions", + "https://www.vendor.com/something-else", + }, + CWEs: []string{"CWE-306"}, + }, + }, + ), + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + entries := loadFixture(t, test.name) + + var actual []transformers.RelatedEntries + for _, vuln := range entries { + entries, err := Transform(vuln, provider.State{ + Provider: "kev", + Version: 12, + Processor: "vunnel@1.2.3", + Timestamp: timeVal, + Listing: &listing, + }) + 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 kevSlice(a ...grypeDB.KnownExploitedVulnerabilityHandle) []any { + var r []any + for _, v := range a { + r = append(r, v) + } + return r +} + +func loadFixture(t *testing.T, fixturePath string) []unmarshal.KnownExploitedVulnerability { + t.Helper() + + f, err := os.Open(fixturePath) + require.NoError(t, err) + defer tests.CloseFile(f) + + entries, err := unmarshal.KnownExploitedVulnerabilityEntries(f) + require.NoError(t, err) + return entries +} diff --git a/pkg/process/v3/transformers/msrc/test-fixtures/microsoft-msrc-0.json b/pkg/process/v6/transformers/msrc/test-fixtures/microsoft-msrc-0.json similarity index 100% rename from pkg/process/v3/transformers/msrc/test-fixtures/microsoft-msrc-0.json rename to pkg/process/v6/transformers/msrc/test-fixtures/microsoft-msrc-0.json diff --git a/pkg/process/v6/transformers/msrc/transform.go b/pkg/process/v6/transformers/msrc/transform.go new file mode 100644 index 00000000..b0974e22 --- /dev/null +++ b/pkg/process/v6/transformers/msrc/transform.go @@ -0,0 +1,132 @@ +package msrc + +import ( + "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" + "github.com/anchore/grype/grype/db/v6/name" + "github.com/anchore/syft/syft/pkg" +) + +func Transform(vulnerability unmarshal.MSRCVulnerability, state provider.State) ([]data.Entry, error) { + ins := []any{ + getVulnerability(vulnerability, state), + } + + ins = append(ins, getAffectedPackage(vulnerability)) + + return transformers.NewEntries(ins...), nil +} + +func getVulnerability(vuln unmarshal.MSRCVulnerability, state provider.State) grypeDB.VulnerabilityHandle { + return grypeDB.VulnerabilityHandle{ + Name: vuln.ID, + ProviderID: state.Provider, + Provider: internal.ProviderModel(state), + Status: grypeDB.VulnerabilityActive, + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: vuln.ID, + Description: strings.TrimSpace(vuln.Summary), + References: getReferences(vuln), + Severities: getSeverities(vuln), + }, + } +} + +func getAffectedPackage(vuln unmarshal.MSRCVulnerability) grypeDB.AffectedPackageHandle { + return grypeDB.AffectedPackageHandle{ + Package: getPackage(vuln), + BlobValue: &grypeDB.AffectedPackageBlob{ + Ranges: getRanges(vuln), + }, + } +} + +func getPackage(vuln unmarshal.MSRCVulnerability) *grypeDB.Package { + return &grypeDB.Package{ + Name: name.Normalize(vuln.Product.ID, pkg.KbPkg), + Ecosystem: string(pkg.KbPkg), + } +} + +func getRanges(vuln unmarshal.MSRCVulnerability) []grypeDB.AffectedRange { + // In anchore-enterprise windows analyzer, "base" represents unpatched windows images (images with no KBs) + // If a vulnerability exists for a Microsoft Product ID and the image has no KBs (which are patches), + // then the image must be vulnerable to the image. + vuln.Vulnerable = append(vuln.Vulnerable, "base") + + return []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Type: "kb", + Constraint: common.OrConstraints(vuln.Vulnerable...), + }, + Fix: getFix(vuln), + }, + } +} + +func getFix(vuln unmarshal.MSRCVulnerability) *grypeDB.Fix { + fixedInVersion := fixedInKB(vuln) + + fixState := grypeDB.FixedStatus + if fixedInVersion == "" { + fixState = grypeDB.NotFixedStatus + } + + return &grypeDB.Fix{ + Version: fixedInVersion, + State: fixState, + } +} + +// fixedInKB finds the "latest" patch (KB id) amongst the available microsoft patches and returns it +// if the "latest" patch cannot be found, an empty string is returned +func fixedInKB(vulnerability unmarshal.MSRCVulnerability) string { + for _, fixedIn := range vulnerability.FixedIn { + if fixedIn.IsLatest { + return fixedIn.ID + } + } + return "" +} + +func getReferences(vuln unmarshal.MSRCVulnerability) []grypeDB.Reference { + refs := []grypeDB.Reference{ + { + URL: vuln.Link, + }, + } + + return refs +} + +func getSeverities(vuln unmarshal.MSRCVulnerability) []grypeDB.Severity { + var severities []grypeDB.Severity + + cleanSeverity := strings.ToLower(strings.TrimSpace(vuln.Severity)) + if cleanSeverity != "" { + severities = append(severities, grypeDB.Severity{ + Scheme: grypeDB.SeveritySchemeCHML, + Value: cleanSeverity, + }) + } + + if vuln.Cvss.Vector != "" { + severities = append(severities, grypeDB.Severity{ + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: vuln.Cvss.Vector, + Version: "3.0", // TODO: assuming CVSS v3, update if different + }, + }) + } + + return severities +} diff --git a/pkg/process/v6/transformers/msrc/transform_test.go b/pkg/process/v6/transformers/msrc/transform_test.go new file mode 100644 index 00000000..43b58f44 --- /dev/null +++ b/pkg/process/v6/transformers/msrc/transform_test.go @@ -0,0 +1,185 @@ +package msrc + +import ( + "os" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/anchore/grype-db/pkg/data" + 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" +) + +func TestUnmarshalMsrcVulnerabilities(t *testing.T) { + f, err := os.Open("test-fixtures/microsoft-msrc-0.json") + require.NoError(t, err) + defer testUtils.CloseFile(f) + + entries, err := unmarshal.MSRCVulnerabilityEntries(f) + require.NoError(t, err) + + assert.Equal(t, len(entries), 2) +} + +func TestParseMSRCEntry(t *testing.T) { + x := time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC) + + providerState := provider.State{ + Provider: "msrc", + Version: 1, + DistributionVersion: 0, + Processor: "", + Schema: provider.Schema{}, + URLs: nil, + Timestamp: x, + Listing: nil, + Store: "", + Stale: false, + } + + expectedVulns := []data.Entry{ + { + DBSchemaVersion: grypeDB.ModelVersion, + Data: transformers.RelatedEntries{ + VulnerabilityHandle: &grypeDB.VulnerabilityHandle{ + Name: "CVE-2019-0671", + ProviderID: "msrc", + Provider: &grypeDB.Provider{ + ID: "msrc", + Version: "1", + DateCaptured: &x, + }, + Status: grypeDB.VulnerabilityActive, + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2019-0671", + Description: "Microsoft Office Access Connectivity Engine Remote Code Execution Vulnerability", + References: []grypeDB.Reference{ + { + URL: "https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2019-0671", + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHML, + Value: "high", + }, + { + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: "CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H/E:P/RL:O/RC:C", + Version: "3.0", + }, + }, + }, + }, + }, + Related: []any{ + grypeDB.AffectedPackageHandle{ + Package: &grypeDB.Package{ + Name: "10852", + Ecosystem: "msrc-kb", + }, + BlobValue: &grypeDB.AffectedPackageBlob{ + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Type: "kb", + Constraint: `4480961 || 4483229 || 4487026 || 4489882 || base`, + }, + Fix: &grypeDB.Fix{ + Version: "4516044", + State: grypeDB.FixedStatus, + }, + }, + }, + }, + }, + }, + }, + }, + { + DBSchemaVersion: grypeDB.ModelVersion, + Data: transformers.RelatedEntries{ + VulnerabilityHandle: &grypeDB.VulnerabilityHandle{ + Name: "CVE-2018-8116", + ProviderID: "msrc", + Provider: &grypeDB.Provider{ + ID: "msrc", + Version: "1", + DateCaptured: &x, + }, + Status: grypeDB.VulnerabilityActive, + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2018-8116", + Description: "Microsoft Graphics Component Denial of Service Vulnerability", + References: []grypeDB.Reference{ + { + URL: "https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2018-8116", + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHML, + Value: "medium", + }, + { + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: "CVSS:3.0/AV:L/AC:H/PR:L/UI:R/S:U/C:N/I:N/A:H/E:P/RL:O/RC:C", + Version: "3.0", + }, + }, + }, + }, + }, + Related: []any{ + grypeDB.AffectedPackageHandle{ + Package: &grypeDB.Package{ + Name: "10852", + Ecosystem: "msrc-kb", + }, + BlobValue: &grypeDB.AffectedPackageBlob{ + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Type: "kb", + Constraint: `3213986 || 4013429 || 4015217 || 4019472 || 4022715 || 4025339 || 4034658 || 4038782 || 4041691 || 4048953 || 4053579 || 4056890 || 4074590 || 4088787 || base`, + }, + Fix: &grypeDB.Fix{ + Version: "4345418", + State: grypeDB.FixedStatus, + }, + }, + }, + }, + }, + }, + }, + }, + } + + f, err := os.Open("test-fixtures/microsoft-msrc-0.json") + require.NoError(t, err) + defer testUtils.CloseFile(f) + + entries, err := unmarshal.MSRCVulnerabilityEntries(f) + require.NoError(t, err) + require.Equal(t, len(entries), 2) + + for idx, entry := range entries { + dataEntries, err := Transform(entry, providerState) + require.NoError(t, err) + require.Len(t, dataEntries, 1, "expected a single data entry to be returned") + + if diff := cmp.Diff(expectedVulns[idx], dataEntries[0]); diff != "" { + t.Errorf("data entry mismatch (-expected +actual):\n%s", diff) + } + } +} diff --git a/pkg/process/v6/transformers/nvd/affected_range.go b/pkg/process/v6/transformers/nvd/affected_range.go new file mode 100644 index 00000000..53fa3d40 --- /dev/null +++ b/pkg/process/v6/transformers/nvd/affected_range.go @@ -0,0 +1,107 @@ +package nvd + +import ( + "fmt" + "sort" + "strings" + + "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd" + "github.com/anchore/syft/syft/cpe" +) + +type affectedRangeSet map[affectedCPERange]struct{} + +type affectedCPERange struct { + ExactVersion string + ExactUpdate string + VersionStartIncluding string + VersionStartExcluding string + VersionEndIncluding string + VersionEndExcluding string +} + +func newAffectedRanges(rs ...affectedCPERange) affectedRangeSet { + s := make(affectedRangeSet) + s.addRanges(rs...) + return s +} + +func newAffectedRange(match nvd.CpeMatch) affectedCPERange { + return affectedCPERange{ + VersionStartIncluding: nonEmptyValue(match.VersionStartIncluding), + VersionStartExcluding: nonEmptyValue(match.VersionStartExcluding), + VersionEndIncluding: nonEmptyValue(match.VersionEndIncluding), + VersionEndExcluding: nonEmptyValue(match.VersionEndExcluding), + } +} + +func (s affectedRangeSet) addRanges(rs ...affectedCPERange) { + for _, r := range rs { + s[r] = struct{}{} + } +} + +func (s affectedRangeSet) toSlice() []affectedCPERange { + var result []affectedCPERange + for r := range s { + result = append(result, r) + } + sort.Slice(result, func(i, j int) bool { + if result[i].ExactVersion != result[j].ExactVersion { + return result[i].ExactVersion < result[j].ExactVersion + } + if result[i].ExactUpdate != result[j].ExactUpdate { + return result[i].ExactUpdate < result[j].ExactUpdate + } + if result[i].VersionStartIncluding != result[j].VersionStartIncluding { + return result[i].VersionStartIncluding < result[j].VersionStartIncluding + } + if result[i].VersionStartExcluding != result[j].VersionStartExcluding { + return result[i].VersionStartExcluding < result[j].VersionStartExcluding + } + if result[i].VersionEndIncluding != result[j].VersionEndIncluding { + return result[i].VersionEndIncluding < result[j].VersionEndIncluding + } + if result[i].VersionEndExcluding != result[j].VersionEndExcluding { + return result[i].VersionEndExcluding < result[j].VersionEndExcluding + } + return false + }) + return result +} + +func (r affectedCPERange) String() string { + constraints := make([]string, 0) + if r.VersionStartIncluding != "" { + constraints = append(constraints, fmt.Sprintf(">= %s", r.VersionStartIncluding)) + } else if r.VersionStartExcluding != "" { + constraints = append(constraints, fmt.Sprintf("> %s", r.VersionStartExcluding)) + } + + if r.VersionEndExcluding != "" { + constraints = append(constraints, fmt.Sprintf("< %s", r.VersionEndExcluding)) + } else if r.VersionEndIncluding != "" { + constraints = append(constraints, fmt.Sprintf("<= %s", r.VersionEndIncluding)) + } + + if len(constraints) == 0 { + version := r.ExactVersion + update := r.ExactUpdate + if version != cpe.Any && version != "-" { + if update != cpe.Any && update != "-" { + version = fmt.Sprintf("%s-%s", version, update) + } + + constraints = append(constraints, fmt.Sprintf("= %s", version)) + } + } + + return strings.Join(constraints, ", ") +} + +func nonEmptyValue(value *string) string { + if value == nil { + return "" + } + return *value +} diff --git a/pkg/process/v6/transformers/nvd/affected_range_test.go b/pkg/process/v6/transformers/nvd/affected_range_test.go new file mode 100644 index 00000000..65b2fd4c --- /dev/null +++ b/pkg/process/v6/transformers/nvd/affected_range_test.go @@ -0,0 +1,126 @@ +package nvd + +import ( + "testing" + + "github.com/google/go-cmp/cmp" +) + +func Test_AffectedCPERange_String(t *testing.T) { + tests := []struct { + name string + input affectedCPERange + expected string + }{ + { + name: "empty range", + input: affectedCPERange{}, + expected: "", + }, + { + name: "exact version match", + input: affectedCPERange{ + ExactVersion: "1.0", + }, + expected: "= 1.0", + }, + { + name: "exact version and update match", + input: affectedCPERange{ + ExactVersion: "1.0", + ExactUpdate: "p1", + }, + expected: "= 1.0-p1", + }, + { + name: "version start including only", + input: affectedCPERange{ + VersionStartIncluding: "1.0", + }, + expected: ">= 1.0", + }, + { + name: "version start excluding only", + input: affectedCPERange{ + VersionStartExcluding: "1.0", + }, + expected: "> 1.0", + }, + { + name: "version end including only", + input: affectedCPERange{ + VersionEndIncluding: "2.0", + }, + expected: "<= 2.0", + }, + { + name: "version end excluding only", + input: affectedCPERange{ + VersionEndExcluding: "2.0", + }, + expected: "< 2.0", + }, + { + name: "version range with start and end including", + input: affectedCPERange{ + VersionStartIncluding: "1.0", + VersionEndIncluding: "2.0", + }, + expected: ">= 1.0, <= 2.0", + }, + { + name: "version range with start including and end excluding", + input: affectedCPERange{ + VersionStartIncluding: "1.0", + VersionEndExcluding: "2.0", + }, + expected: ">= 1.0, < 2.0", + }, + { + name: "version range with start excluding and end including", + input: affectedCPERange{ + VersionStartExcluding: "1.0", + VersionEndIncluding: "2.0", + }, + expected: "> 1.0, <= 2.0", + }, + { + name: "version range with start and end excluding", + input: affectedCPERange{ + VersionStartExcluding: "1.0", + VersionEndExcluding: "2.0", + }, + expected: "> 1.0, < 2.0", + }, + { + name: "version range with all bounds (prefer outer bounds)", + input: affectedCPERange{ + VersionStartIncluding: "1.0", + VersionStartExcluding: "0.9", + VersionEndIncluding: "2.0", + VersionEndExcluding: "2.1", + }, + expected: ">= 1.0, < 2.1", + }, + { + name: "range constraints overrides exact version", + input: affectedCPERange{ + ExactVersion: "1.5", + ExactUpdate: "p2", + VersionStartIncluding: "1.0", + VersionEndExcluding: "2.0", + }, + expected: ">= 1.0, < 2.0", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := tt.input.String() + + if diff := cmp.Diff(tt.expected, actual); diff != "" { + t.Errorf("buildConstraints() mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/pkg/process/v6/transformers/nvd/node.go b/pkg/process/v6/transformers/nvd/node.go new file mode 100644 index 00000000..aff6c3a7 --- /dev/null +++ b/pkg/process/v6/transformers/nvd/node.go @@ -0,0 +1,295 @@ +package nvd + +import ( + "fmt" + "sort" + "strings" + + "github.com/anchore/grype-db/internal/log" + "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd" + "github.com/anchore/syft/syft/cpe" +) + +type affectedPackageCandidate struct { + VulnerableCPE cpe.Attributes + PlatformCPEs []cpe.Attributes + Ranges affectedRangeSet +} + +func allCandidates(cve string, configs []nvd.Configuration, cfg Config) ([]affectedPackageCandidate, error) { + var candidates []affectedPackageCandidate + + for _, config := range configs { + cs, err := processConfiguration(cve, config, cfg) + if err != nil { + return nil, err + } + candidates = append(candidates, cs...) + } + + return deduplicateCandidates(candidates), nil +} + +// processConfiguration processes a configuration recursively +func processConfiguration(cve string, config nvd.Configuration, cfg Config) ([]affectedPackageCandidate, error) { + var opPtr = config.Operator + var op nvd.Operator + if opPtr != nil { + op = *opPtr + } else { + op = nvd.Or + } + + if op == nvd.And { + return processANDNodes(cve, config.Nodes, cfg, 0) + } + return processORNodes(cve, config.Nodes, cfg, 0) +} + +// processANDNodes handles AND configurations +func processANDNodes(cve string, nodes []nvd.Node, cfg Config, depth int) ([]affectedPackageCandidate, error) { + depth++ + if depth > 2 { + log.WithFields("depth", depth, "cve", cve, "operator", "and").Warn("unexpected NVD node configuration depth") + } + var candidates []affectedPackageCandidate + + // find all vulnerable CPEs and all platform CPEs across all nodes + var allVulnerableCPEs []cpe.Attributes + var allRanges []affectedCPERange + var allPlatformCPEs []cpe.Attributes + + for _, node := range nodes { + switch node.Operator { + case nvd.Or: + vulnCPEs, ranges, err := extractVulnerableCPEs(node, cfg) + if err != nil { + return nil, err + } + allVulnerableCPEs = append(allVulnerableCPEs, vulnCPEs...) + allRanges = append(allRanges, ranges...) + + platformCPEs, err := extractPlatformCPEs(node) + if err != nil { + return nil, err + } + allPlatformCPEs = append(allPlatformCPEs, platformCPEs...) + case nvd.And: + // TODO: when we're processing AND'd nodes at this depth this tends to mean that all the given CPEs must + // be present in the environment for the vulnerability to be applicable. This isn't something we can + // express as a single affected package in grype today. We should consider how to handle this case in + // the future. + var names []string + for _, match := range node.CpeMatch { + short := strings.ReplaceAll(strings.ReplaceAll(match.Criteria, ":*", ""), ":-", "") + postfix := "" + if !match.Vulnerable { + postfix = " (not vulnerable)" + } + names = append(names, fmt.Sprintf("%q%s", short, postfix)) + } + log.WithFields("cve", cve, "criteria", strings.Join(names, " AND ")).Warnf("unsupported NVD node configuration (dropping criteria)") + } + } + + // group ranges by CPE + rangesByCPE := make(map[string][]affectedCPERange) + for i, c := range allVulnerableCPEs { + key := cpeKey(c) + if i < len(allRanges) { + rangesByCPE[key] = append(rangesByCPE[key], allRanges[i]) + } + } + + // deduplicate CPEs + uniqueVulnCPEs := make(map[string]cpe.Attributes) + for _, c := range allVulnerableCPEs { + uniqueVulnCPEs[cpeKey(c)] = c + } + + // combine all unique vulnerable CPEs with their associated ranges + for key, vulnCPE := range uniqueVulnCPEs { + if len(allPlatformCPEs) == 0 { + // no platform constraints, app is vulnerable on all platforms + candidates = append(candidates, affectedPackageCandidate{ + VulnerableCPE: vulnCPE, + Ranges: newAffectedRanges(rangesByCPE[key]...), + }) + } else { + // associate this vulnerable CPE with all platform CPEs + candidates = append(candidates, affectedPackageCandidate{ + VulnerableCPE: vulnCPE, + PlatformCPEs: allPlatformCPEs, + Ranges: newAffectedRanges(rangesByCPE[key]...), + }) + } + } + + return candidates, nil +} + +// processORNodes handles OR configurations +func processORNodes(cve string, nodes []nvd.Node, cfg Config, depth int) ([]affectedPackageCandidate, error) { + depth++ + if depth > 2 { + log.WithFields("depth", depth, "cve", cve, "operator", "or").Warnf("unexpected NVD node configuration depth") + } + var candidates []affectedPackageCandidate + + for _, node := range nodes { + switch node.Operator { + case nvd.And: + andCandidates, err := processANDNodes(cve, []nvd.Node{node}, cfg, depth) + if err != nil { + return nil, err + } + candidates = append(candidates, andCandidates...) + case nvd.Or: + vulnCPEs, ranges, err := extractVulnerableCPEs(node, cfg) + if err != nil { + return nil, err + } + + // associate each vulnerable CPE with its ranges + for i, vulnCPE := range vulnCPEs { + var cpeRanges []affectedCPERange + if i < len(ranges) { + cpeRanges = append(cpeRanges, ranges[i]) + } + + candidates = append(candidates, affectedPackageCandidate{ + VulnerableCPE: vulnCPE, + Ranges: newAffectedRanges(cpeRanges...), + }) + } + } + } + + return candidates, nil +} + +func deduplicateCandidates(candidates []affectedPackageCandidate) []affectedPackageCandidate { + candidateMap := make(map[string]*affectedPackageCandidate) + + for _, candidate := range candidates { + key := cpeKey(candidate.VulnerableCPE) + + existing, exists := candidateMap[key] + if !exists { + newCandidate := candidate + candidateMap[key] = &newCandidate + continue + } + + // merge platform CPEs... + platformMap := make(map[string]struct{}) + for _, platform := range existing.PlatformCPEs { + platformKey := cpeKey(platform) + platformMap[platformKey] = struct{}{} + } + + for _, platform := range candidate.PlatformCPEs { + platformKey := cpeKey(platform) + if _, ok := platformMap[platformKey]; !ok { + existing.PlatformCPEs = append(existing.PlatformCPEs, platform) + platformMap[platformKey] = struct{}{} + } + } + + // merge ranges... + existing.Ranges.addRanges(candidate.Ranges.toSlice()...) + } + + var result []affectedPackageCandidate + for _, candidate := range candidateMap { + if len(candidate.Ranges) == 0 { + candidate.Ranges.addRanges(deriveRangesFromCPE(candidate.VulnerableCPE)...) + } + result = append(result, *candidate) + } + + // sort the slice for deterministic output + sort.Slice(result, func(i, j int) bool { + return result[i].VulnerableCPE.String() < result[j].VulnerableCPE.String() + }) + + return result +} + +func deriveRangesFromCPE(attr cpe.Attributes) []affectedCPERange { + if attr.Version == cpe.Any { + return nil + } + + var update string + if attr.Update != "-" { + update = attr.Update + } + + return []affectedCPERange{ + { + ExactVersion: attr.Version, + ExactUpdate: update, + }, + } +} + +// extractVulnerableCPEs extracts CPES that are both within the CPE part configuration and are explicitly marked as vulnerable +func extractVulnerableCPEs(node nvd.Node, cfg Config) ([]cpe.Attributes, []affectedCPERange, error) { + var vulnCPEs []cpe.Attributes + var ranges []affectedCPERange + + for _, match := range node.CpeMatch { + if !match.Vulnerable { + continue + } + + cpeAttr, err := cpe.NewAttributes(match.Criteria) + if err != nil { + return nil, nil, fmt.Errorf("unable to parse CPE '%s': %w", match.Criteria, err) + } + + // check if this CPE part is in our configured set of parts to process, if not then it should not be considered + // as an affected package at all + if !cfg.CPEParts.Has(cpeAttr.Part) { + continue + } + + vulnCPEs = append(vulnCPEs, cpeAttr) + + if match.VersionStartIncluding != nil || match.VersionStartExcluding != nil || + match.VersionEndIncluding != nil || match.VersionEndExcluding != nil { + ranges = append(ranges, newAffectedRange(match)) + } else { + // no explicit version ranges in the match, check the CPE attributes for an exact version + ranges = append(ranges, deriveRangesFromCPE(cpeAttr)...) + } + } + + return vulnCPEs, ranges, nil +} + +// extractPlatformCPEs extracts all platform CPEs from a node (explicitly non-vulnerable CPEs). Why not just +// use the part indication (i.e. 'h' & 'o' are platform and 'a' is the vulnerable candidate)? Because you can +// find cases where an application is the platform (e.g. kubernetes or openshift). +func extractPlatformCPEs(node nvd.Node) ([]cpe.Attributes, error) { + var platformCPEs []cpe.Attributes + + for _, match := range node.CpeMatch { + cpeAttr, err := cpe.NewAttributes(match.Criteria) + if err != nil { + return nil, fmt.Errorf("unable to parse CPE '%s': %w", match.Criteria, err) + } + + if !match.Vulnerable { + platformCPEs = append(platformCPEs, cpeAttr) + } + } + + return platformCPEs, nil +} + +// cpeKey generates a unique key for a CPE (everything except for the version and update) +func cpeKey(cpe cpe.Attributes) string { + return fmt.Sprintf("%s|%s|%s|%s|%s|%s|%s|%s|%s", cpe.Part, cpe.Vendor, cpe.Product, cpe.Edition, cpe.SWEdition, cpe.TargetSW, cpe.TargetHW, cpe.Other, cpe.Language) +} diff --git a/pkg/process/v6/transformers/nvd/node_test.go b/pkg/process/v6/transformers/nvd/node_test.go new file mode 100644 index 00000000..7f273929 --- /dev/null +++ b/pkg/process/v6/transformers/nvd/node_test.go @@ -0,0 +1,461 @@ +package nvd + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" + + "github.com/anchore/syft/syft/cpe" +) + +func TestDeduplicateCandidates(t *testing.T) { + aVendorProduct1 := cpe.Attributes{ + Part: "a", + Vendor: "vendor1", + Product: "product1", + } + + aVendorProduct2 := cpe.Attributes{ + Part: "a", + Vendor: "vendor2", + Product: "product2", + } + + osProduct1 := cpe.Attributes{ + Part: "o", + Vendor: "os1", + Product: "os1product", + } + + osProduct2 := cpe.Attributes{ + + Part: "o", + Vendor: "os2", + Product: "os2product", + } + + tests := []struct { + name string + input []affectedPackageCandidate + expected []affectedPackageCandidate + }{ + { + name: "empty input", + input: []affectedPackageCandidate{}, + expected: nil, + }, + { + name: "go case", + input: []affectedPackageCandidate{ + { + VulnerableCPE: aVendorProduct1, + Ranges: newAffectedRanges(affectedCPERange{ + ExactVersion: "1.0", + }), + }, + }, + expected: []affectedPackageCandidate{ + { + VulnerableCPE: aVendorProduct1, + Ranges: newAffectedRanges(affectedCPERange{ + ExactVersion: "1.0", + }), + }, + }, + }, + { + name: "deduplicate identical candidates", + input: []affectedPackageCandidate{ + { + VulnerableCPE: aVendorProduct1, + Ranges: newAffectedRanges(affectedCPERange{ + ExactVersion: "1.0", + }), + }, + { + VulnerableCPE: aVendorProduct1, + Ranges: newAffectedRanges(affectedCPERange{ + ExactVersion: "1.0", + }), + }, + }, + expected: []affectedPackageCandidate{ + { + VulnerableCPE: aVendorProduct1, + Ranges: newAffectedRanges(affectedCPERange{ + ExactVersion: "1.0", + }), + }, + }, + }, + { + name: "merge ranges for same CPE", + input: []affectedPackageCandidate{ + { + VulnerableCPE: aVendorProduct1, + Ranges: newAffectedRanges(affectedCPERange{ + ExactVersion: "1.0", + }), + }, + { + VulnerableCPE: aVendorProduct1, + Ranges: newAffectedRanges(affectedCPERange{ + ExactVersion: "2.0", + }), + }, + }, + expected: []affectedPackageCandidate{ + { + VulnerableCPE: aVendorProduct1, + Ranges: newAffectedRanges( + affectedCPERange{ExactVersion: "1.0"}, + affectedCPERange{ExactVersion: "2.0"}, + ), + }, + }, + }, + { + name: "merge platform CPEs for same vulnerable CPE", + input: []affectedPackageCandidate{ + { + VulnerableCPE: aVendorProduct1, + PlatformCPEs: []cpe.Attributes{ + osProduct1, + }, + Ranges: newAffectedRanges(affectedCPERange{ + ExactVersion: "1.0", + }), + }, + { + VulnerableCPE: aVendorProduct1, + PlatformCPEs: []cpe.Attributes{ + osProduct2, + }, + Ranges: newAffectedRanges(affectedCPERange{ + ExactVersion: "1.0", + }), + }, + }, + expected: []affectedPackageCandidate{ + { + VulnerableCPE: aVendorProduct1, + PlatformCPEs: []cpe.Attributes{ + osProduct1, + osProduct2, + }, + Ranges: newAffectedRanges(affectedCPERange{ + ExactVersion: "1.0", + }), + }, + }, + }, + { + name: "different CPEs not deduplicated", + input: []affectedPackageCandidate{ + { + VulnerableCPE: aVendorProduct1, + Ranges: newAffectedRanges(affectedCPERange{ + ExactVersion: "1.0", + }), + }, + { + VulnerableCPE: aVendorProduct2, + Ranges: newAffectedRanges(affectedCPERange{ + ExactVersion: "2.0", + }), + }, + }, + expected: []affectedPackageCandidate{ + { + VulnerableCPE: aVendorProduct1, + Ranges: newAffectedRanges(affectedCPERange{ + ExactVersion: "1.0", + }), + }, + { + VulnerableCPE: aVendorProduct2, + Ranges: newAffectedRanges(affectedCPERange{ + ExactVersion: "2.0", + }), + }, + }, + }, + { + name: "deduplicate based on target software", + input: []affectedPackageCandidate{ + { + VulnerableCPE: cpe.Attributes{ + Part: "a", + Vendor: "vendor", + Product: "product", + TargetSW: "target1", + }, + Ranges: newAffectedRanges(affectedCPERange{ + ExactVersion: "1.0", + }), + }, + { + VulnerableCPE: cpe.Attributes{ + Part: "a", + Vendor: "vendor", + Product: "product", + TargetSW: "target2", + }, + Ranges: newAffectedRanges(affectedCPERange{ + ExactVersion: "1.0", + }), + }, + }, + expected: []affectedPackageCandidate{ + { + VulnerableCPE: cpe.Attributes{ + Part: "a", + Vendor: "vendor", + Product: "product", + TargetSW: "target1", + }, + Ranges: newAffectedRanges(affectedCPERange{ + ExactVersion: "1.0", + }), + }, + { + VulnerableCPE: cpe.Attributes{ + Part: "a", + Vendor: "vendor", + Product: "product", + TargetSW: "target2", + }, + Ranges: newAffectedRanges(affectedCPERange{ + ExactVersion: "1.0", + }), + }, + }, + }, + { + name: "derive ranges when none specified", + input: []affectedPackageCandidate{ + { + VulnerableCPE: cpe.Attributes{ + Part: "a", + Vendor: "vendor", + Product: "product", + Version: "3.0", + Update: "p2", + }, + Ranges: newAffectedRanges(), + }, + }, + expected: []affectedPackageCandidate{ + { + VulnerableCPE: cpe.Attributes{ + Part: "a", + Vendor: "vendor", + Product: "product", + Version: "3.0", + Update: "p2", + }, + Ranges: newAffectedRanges(affectedCPERange{ + ExactVersion: "3.0", + ExactUpdate: "p2", + }), + }, + }, + }, + { + name: "derive ranges for one candidate but not others", + input: []affectedPackageCandidate{ + { + VulnerableCPE: cpe.Attributes{ + Part: "a", + Vendor: "vendor", + Product: "product1", + Version: "3.0", + }, + Ranges: newAffectedRanges(), + }, + { + VulnerableCPE: cpe.Attributes{ + Part: "a", + Vendor: "vendor", + Product: "product2", + }, + Ranges: newAffectedRanges(affectedCPERange{ + ExactVersion: "1.0", + }), + }, + }, + expected: []affectedPackageCandidate{ + { + VulnerableCPE: cpe.Attributes{ + Part: "a", + Vendor: "vendor", + Product: "product1", + Version: "3.0", + }, + Ranges: newAffectedRanges(affectedCPERange{ + ExactVersion: "3.0", + }), + }, + { + VulnerableCPE: cpe.Attributes{ + Part: "a", + Vendor: "vendor", + Product: "product2", + }, + Ranges: newAffectedRanges(affectedCPERange{ + ExactVersion: "1.0", + }), + }, + }, + }, + { + name: "complex case with mixed input", + input: []affectedPackageCandidate{ + { + VulnerableCPE: cpe.Attributes{ + Part: "a", + Vendor: "vendor", + Product: "product", + Version: "1.0", + SWEdition: "enterprise", + }, + PlatformCPEs: []cpe.Attributes{ + osProduct1, + }, + Ranges: newAffectedRanges(affectedCPERange{ + ExactVersion: "1.0", + }), + }, + { + VulnerableCPE: cpe.Attributes{ + Part: "a", + Vendor: "vendor", + Product: "product", + Version: "1.0", + SWEdition: "enterprise", + }, + PlatformCPEs: []cpe.Attributes{ + osProduct2, + }, + Ranges: newAffectedRanges(affectedCPERange{ + VersionStartIncluding: "1.0", + VersionEndExcluding: "2.0", + }), + }, + { + VulnerableCPE: cpe.Attributes{ + Part: "a", + Vendor: "vendor", + Product: "product", + Version: "1.0", + SWEdition: "community", + }, + Ranges: newAffectedRanges(affectedCPERange{ + ExactVersion: "1.0", + }), + }, + }, + expected: []affectedPackageCandidate{ + { + VulnerableCPE: cpe.Attributes{ + Part: "a", + Vendor: "vendor", + Product: "product", + Version: "1.0", + SWEdition: "community", + }, + Ranges: newAffectedRanges(affectedCPERange{ + ExactVersion: "1.0", + }), + }, + { + VulnerableCPE: cpe.Attributes{ + Part: "a", + Vendor: "vendor", + Product: "product", + Version: "1.0", + SWEdition: "enterprise", + }, + PlatformCPEs: []cpe.Attributes{ + osProduct1, + osProduct2, + }, + Ranges: newAffectedRanges( + affectedCPERange{ + ExactVersion: "1.0", + }, + affectedCPERange{ + VersionStartIncluding: "1.0", + VersionEndExcluding: "2.0", + }, + ), + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := deduplicateCandidates(tt.input) + + if diff := cmp.Diff(tt.expected, actual); diff != "" { + t.Errorf("deduplicateCandidates() mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func TestDeduplicateCandidates_SensitiveToAllCPEFields(t *testing.T) { + base := cpe.Attributes{ + Part: "a", + Vendor: "vendor", + Product: "product", + Version: "1.0", + Update: "update", + Edition: "edition", + SWEdition: "sw-edition", + TargetSW: "target-sw", + TargetHW: "target-hw", + Language: "lang", + Other: "other", + } + + // note: we do not care about version and update fields for this part of the test... + for field, mutate := range map[string]func(cpe.Attributes) cpe.Attributes{ + "Part": func(c cpe.Attributes) cpe.Attributes { c.Part = "h"; return c }, + "Vendor": func(c cpe.Attributes) cpe.Attributes { c.Vendor = "other-vendor"; return c }, + "Product": func(c cpe.Attributes) cpe.Attributes { c.Product = "other-product"; return c }, + "Edition": func(c cpe.Attributes) cpe.Attributes { c.Edition = "other-edition"; return c }, + "SWEdition": func(c cpe.Attributes) cpe.Attributes { c.SWEdition = "other-sw-edition"; return c }, + "TargetSW": func(c cpe.Attributes) cpe.Attributes { c.TargetSW = "other-target-sw"; return c }, + "TargetHW": func(c cpe.Attributes) cpe.Attributes { c.TargetHW = "other-target-hw"; return c }, + "Language": func(c cpe.Attributes) cpe.Attributes { c.Language = "other-lang"; return c }, + "Other": func(c cpe.Attributes) cpe.Attributes { c.Other = "other-other"; return c }, + } { + t.Run("field="+field, func(t *testing.T) { + a := affectedPackageCandidate{VulnerableCPE: base, Ranges: newAffectedRanges()} + b := affectedPackageCandidate{VulnerableCPE: mutate(base), Ranges: newAffectedRanges()} + result := deduplicateCandidates([]affectedPackageCandidate{a, b}) + require.Len(t, result, 2, "field %s should cause deduplication to treat entries as separate", field) + }) + } + + // now that all other fields have been tested, prove that we do not care about version and update fields... + t.Run("Version and Update do not matter", func(t *testing.T) { + c1 := base + c1.Version = "1.0" + c1.Update = "u1" + + c2 := base + c2.Version = "2.0" + c2.Update = "u2" + + a := affectedPackageCandidate{VulnerableCPE: c1, Ranges: newAffectedRanges(affectedCPERange{ExactVersion: "1.0"})} + b := affectedPackageCandidate{VulnerableCPE: c2, Ranges: newAffectedRanges(affectedCPERange{ExactVersion: "2.0"})} + + result := deduplicateCandidates([]affectedPackageCandidate{a, b}) + require.Len(t, result, 1) + require.Len(t, result[0].Ranges, 2) + }) +} diff --git a/pkg/process/v6/transformers/nvd/test-fixtures/CVE-2008-3442.json b/pkg/process/v6/transformers/nvd/test-fixtures/CVE-2008-3442.json new file mode 100644 index 00000000..08962fce --- /dev/null +++ b/pkg/process/v6/transformers/nvd/test-fixtures/CVE-2008-3442.json @@ -0,0 +1,83 @@ +{ + "cve": { + "id": "CVE-2008-3442", + "sourceIdentifier": "cve@mitre.org", + "published": "2008-08-01T14:41:00.000", + "lastModified": "2008-09-05T21:43:05.500", + "vulnStatus": "Analyzed", + "descriptions": [ + { + "lang": "en", + "value": "desc." + }, + { + "lang": "es", + "value": "WinZip anterior a 11.0 no verifica adecuadamente la autenticidad de las actualizaciones, lo cual permite a atacantes de tipo 'hombre en el medio' (man-in-the-middle) ejecutar código de su elección a través de la actualización de un Caballo de Troya, que se manifiesta en el grado de daño y el envenenamiento de la caché DNS.\r\n\r\n" + } + ], + "metrics": { + "cvssMetricV2": [ ] + }, + "weaknesses": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "description": [ + { + "lang": "en", + "value": "CWE-94" + } + ] + } + ], + "configurations": [ + { + "nodes": [ + { + "operator": "OR", + "negate": false, + "cpeMatch": [ + { + "vulnerable": true, + "criteria": "cpe:2.3:a:winzip:winzip:7.0:*:*:*:*:*:*:*", + "matchCriteriaId": "A2ACBE01-B77A-4D09-8FB3-D6365786C44F" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:a:winzip:winzip:8.0:*:*:*:*:*:*:*", + "matchCriteriaId": "FDE7DCD6-90B3-4259-9BE6-B9F7A30A64AF" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:a:winzip:winzip:8.1:*:*:*:*:*:*:*", + "matchCriteriaId": "4088C545-249E-47AD-8BF8-A6A2E5B2BF18" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:a:winzip:winzip:8.1:*:sr1:*:*:*:*:*", + "matchCriteriaId": "FD308C7B-E9F6-4874-965D-E4271CF360DF" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:a:winzip:winzip:9.0:*:*:*:*:*:*:*", + "matchCriteriaId": "523ADB29-C3D5-4C06-89B6-22B5FC68C240" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:a:winzip:winzip:9.0:*:sr1:*:*:*:*:*", + "matchCriteriaId": "C79A7C70-F1CE-448B-B980-FB976609C48D" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:a:winzip:winzip:10.0:*:*:*:*:*:*:*", + "matchCriteriaId": "B4FD09AC-2C56-4DB1-B00A-903103B453AD" + } + ] + } + ] + } + ], + "references": [ + ] + } +} \ No newline at end of file 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/v1/transformers/nvd/test-fixtures/compound-pkg.json b/pkg/process/v6/transformers/nvd/test-fixtures/compound-pkg.json similarity index 100% rename from pkg/process/v1/transformers/nvd/test-fixtures/compound-pkg.json rename to pkg/process/v6/transformers/nvd/test-fixtures/compound-pkg.json 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-2021-1566.json b/pkg/process/v6/transformers/nvd/test-fixtures/cve-2021-1566.json new file mode 100644 index 00000000..3dcd7a8e --- /dev/null +++ b/pkg/process/v6/transformers/nvd/test-fixtures/cve-2021-1566.json @@ -0,0 +1,245 @@ +{ + "cve": { + "id": "CVE-2021-1566", + "sourceIdentifier": "psirt@cisco.com", + "published": "2021-06-16T18:15:08.710", + "lastModified": "2024-11-21T05:44:38.237", + "vulnStatus": "Modified", + "cveTags": [], + "descriptions": [ + { + "lang": "en", + "value": "description." + }, + { + "lang": "es", + "value": "description" + } + ], + "metrics": { + "cvssMetricV31": [ + { + "source": "psirt@cisco.com", + "type": "Secondary", + "cvssData": { + "version": "3.1", + "vectorString": "CVSS:3.1\/AV:N\/AC:H\/PR:N\/UI:N\/S:U\/C:H\/I:H\/A:N", + "baseScore": 7.4, + "baseSeverity": "HIGH", + "attackVector": "NETWORK", + "attackComplexity": "HIGH", + "privilegesRequired": "NONE", + "userInteraction": "NONE", + "scope": "UNCHANGED", + "confidentialityImpact": "HIGH", + "integrityImpact": "HIGH", + "availabilityImpact": "NONE" + }, + "exploitabilityScore": 2.2, + "impactScore": 5.2 + }, + { + "source": "nvd@nist.gov", + "type": "Primary", + "cvssData": { + "version": "3.1", + "vectorString": "CVSS:3.1\/AV:N\/AC:H\/PR:N\/UI:N\/S:U\/C:H\/I:H\/A:N", + "baseScore": 7.4, + "baseSeverity": "HIGH", + "attackVector": "NETWORK", + "attackComplexity": "HIGH", + "privilegesRequired": "NONE", + "userInteraction": "NONE", + "scope": "UNCHANGED", + "confidentialityImpact": "HIGH", + "integrityImpact": "HIGH", + "availabilityImpact": "NONE" + }, + "exploitabilityScore": 2.2, + "impactScore": 5.2 + } + ], + "cvssMetricV2": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "cvssData": { + "version": "2.0", + "vectorString": "AV:N\/AC:M\/Au:N\/C:P\/I:P\/A:N", + "baseScore": 5.8, + "accessVector": "NETWORK", + "accessComplexity": "MEDIUM", + "authentication": "NONE", + "confidentialityImpact": "PARTIAL", + "integrityImpact": "PARTIAL", + "availabilityImpact": "NONE" + }, + "baseSeverity": "MEDIUM", + "exploitabilityScore": 8.6, + "impactScore": 4.9, + "acInsufInfo": false, + "obtainAllPrivilege": false, + "obtainUserPrivilege": false, + "obtainOtherPrivilege": false, + "userInteractionRequired": false + } + ] + }, + "weaknesses": [ + { + "source": "psirt@cisco.com", + "type": "Secondary", + "description": [{ "lang": "en", "value": "CWE-296" }] + }, + { + "source": "nvd@nist.gov", + "type": "Primary", + "description": [{ "lang": "en", "value": "CWE-295" }] + } + ], + "configurations": [ + { + "nodes": [ + { + "operator": "AND", + "negate": false, + "cpeMatch": [ + { + "vulnerable": true, + "criteria": "cpe:2.3:a:cisco:email_security_appliance:-:*:*:*:*:*:*:*", + "matchCriteriaId": "678C2C6F-6D46-4BBE-A902-7AD031D8EBA8" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:o:cisco:asyncos:*:*:*:*:*:*:*:*", + "versionEndExcluding": "12.5.3-035", + "matchCriteriaId": "6C3A8C94-CD5C-4309-8F1B-B151B3D091CC" + } + ] + } + ] + }, + { + "nodes": [ + { + "operator": "AND", + "negate": false, + "cpeMatch": [ + { + "vulnerable": true, + "criteria": "cpe:2.3:a:cisco:email_security_appliance:-:*:*:*:*:*:*:*", + "matchCriteriaId": "678C2C6F-6D46-4BBE-A902-7AD031D8EBA8" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:o:cisco:asyncos:*:*:*:*:*:*:*:*", + "versionStartIncluding": "13.0", + "versionEndExcluding": "13.0.0-030", + "matchCriteriaId": "BE1DE406-EA9E-40DD-B18B-C19DF63EC13B" + } + ] + } + ] + }, + { + "nodes": [ + { + "operator": "AND", + "negate": false, + "cpeMatch": [ + { + "vulnerable": true, + "criteria": "cpe:2.3:a:cisco:email_security_appliance:-:*:*:*:*:*:*:*", + "matchCriteriaId": "678C2C6F-6D46-4BBE-A902-7AD031D8EBA8" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:o:cisco:asyncos:*:*:*:*:*:*:*:*", + "versionStartIncluding": "13.5", + "versionEndExcluding": "13.5.3-010", + "matchCriteriaId": "39DEA2BD-4772-4F8D-9CD2-1BB377ECF64B" + } + ] + } + ] + }, + { + "nodes": [ + { + "operator": "AND", + "negate": false, + "cpeMatch": [ + { + "vulnerable": true, + "criteria": "cpe:2.3:a:cisco:web_security_appliance:-:*:*:*:*:*:*:*", + "matchCriteriaId": "A7C2555C-7E97-475F-9EDC-027B51A40708" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:o:cisco:asyncos:*:*:*:*:*:*:*:*", + "versionEndExcluding": "11.8.3-021", + "matchCriteriaId": "33FDC1BE-F1C3-4030-82CE-38D99DC30B5B" + } + ] + } + ] + }, + { + "nodes": [ + { + "operator": "AND", + "negate": false, + "cpeMatch": [ + { + "vulnerable": true, + "criteria": "cpe:2.3:a:cisco:web_security_appliance:-:*:*:*:*:*:*:*", + "matchCriteriaId": "A7C2555C-7E97-475F-9EDC-027B51A40708" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:o:cisco:asyncos:*:*:*:*:*:*:*:*", + "versionStartIncluding": "12.0.0", + "versionEndExcluding": "12.0.3-005", + "matchCriteriaId": "D1CC6572-4281-45E1-9B33-6993B45E6B4F" + } + ] + } + ] + }, + { + "nodes": [ + { + "operator": "AND", + "negate": false, + "cpeMatch": [ + { + "vulnerable": true, + "criteria": "cpe:2.3:a:cisco:web_security_appliance:-:*:*:*:*:*:*:*", + "matchCriteriaId": "A7C2555C-7E97-475F-9EDC-027B51A40708" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:o:cisco:asyncos:*:*:*:*:*:*:*:*", + "versionStartIncluding": "12.5.0", + "versionEndExcluding": "12.5.1-043", + "matchCriteriaId": "AA889DAF-1699-4A22-8A4C-D589F7BF10A8" + } + ] + } + ] + } + ], + "references": [ + { + "url": "https:\/\/tools.cisco.com\/security\/center\/content\/CiscoSecurityAdvisory\/cisco-sa-esa-wsa-cert-vali-n8L97RW", + "source": "psirt@cisco.com", + "tags": ["Vendor Advisory"] + }, + { + "url": "https:\/\/tools.cisco.com\/security\/center\/content\/CiscoSecurityAdvisory\/cisco-sa-esa-wsa-cert-vali-n8L97RW", + "source": "af854a3a-2127-422b-91ae-364da2661108", + "tags": ["Vendor 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/cve-2024-26663-standalone-os.json b/pkg/process/v6/transformers/nvd/test-fixtures/cve-2024-26663-standalone-os.json new file mode 100644 index 00000000..deca7f9c --- /dev/null +++ b/pkg/process/v6/transformers/nvd/test-fixtures/cve-2024-26663-standalone-os.json @@ -0,0 +1,114 @@ +{ + "cve": { + "id": "CVE-2024-26663", + "sourceIdentifier": "416baaa9-dc9f-4396-8d5f-8c081fb06d67", + "published": "2024-04-02T07:15:43.287", + "lastModified": "2025-01-07T17:20:30.367", + "vulnStatus": "Analyzed", + "cveTags": [], + "descriptions": [ + { + "lang": "en", + "value": "the description..." + }, + { + "lang": "es", + "value": "el description..." + } + ], + "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:N\/I:N\/A:H", + "baseScore": 5.5, + "baseSeverity": "MEDIUM", + "attackVector": "LOCAL", + "attackComplexity": "LOW", + "privilegesRequired": "LOW", + "userInteraction": "NONE", + "scope": "UNCHANGED", + "confidentialityImpact": "NONE", + "integrityImpact": "NONE", + "availabilityImpact": "HIGH" + }, + "exploitabilityScore": 1.8, + "impactScore": 3.6 + } + ] + }, + "weaknesses": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "description": [{ "lang": "en", "value": "CWE-476" }] + } + ], + "configurations": [ + { + "nodes": [ + { + "operator": "OR", + "negate": false, + "cpeMatch": [ + { + "vulnerable": true, + "criteria": "cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*", + "versionStartIncluding": "4.9", + "versionEndExcluding": "4.19.307", + "matchCriteriaId": "A1A227E7-C02C-4FC4-84AA-230362C5E2C6" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*", + "versionStartIncluding": "6.7", + "versionEndExcluding": "6.7.5", + "matchCriteriaId": "01925741-2C95-47C1-A7EA-3DC2BB0012D3" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:o:linux:linux_kernel:6.8:rc1:*:*:*:*:*:*", + "matchCriteriaId": "B9F4EA73-0894-400F-A490-3A397AB7A517" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:o:linux:linux_kernel:6.8:rc2:*:*:*:*:*:*", + "matchCriteriaId": "056BD938-0A27-4569-B391-30578B309EE3" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:o:linux:linux_kernel:6.8:rc3:*:*:*:*:*:*", + "matchCriteriaId": "F02056A5-B362-4370-9FF8-6F0BD384D520" + } + ] + } + ] + }, + { + "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:\/\/git.kernel.org\/stable\/c\/0cd331dfd6023640c9669d0592bc0fd491205f87", + "source": "416baaa9-dc9f-4396-8d5f-8c081fb06d67", + "tags": ["Patch"] + } + ] + } +} diff --git a/pkg/process/v1/transformers/nvd/test-fixtures/invalid_cpe.json b/pkg/process/v6/transformers/nvd/test-fixtures/invalid_cpe.json similarity index 100% rename from pkg/process/v1/transformers/nvd/test-fixtures/invalid_cpe.json rename to pkg/process/v6/transformers/nvd/test-fixtures/invalid_cpe.json 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/v1/transformers/nvd/test-fixtures/single-package-multi-distro.json b/pkg/process/v6/transformers/nvd/test-fixtures/single-package-multi-distro.json similarity index 100% rename from pkg/process/v1/transformers/nvd/test-fixtures/single-package-multi-distro.json rename to pkg/process/v6/transformers/nvd/test-fixtures/single-package-multi-distro.json diff --git a/pkg/process/v3/transformers/nvd/test-fixtures/version-range.json b/pkg/process/v6/transformers/nvd/test-fixtures/version-range.json similarity index 64% rename from pkg/process/v3/transformers/nvd/test-fixtures/version-range.json rename to pkg/process/v6/transformers/nvd/test-fixtures/version-range.json index 3df5b86d..1b9c4581 100644 --- a/pkg/process/v3/transformers/nvd/test-fixtures/version-range.json +++ b/pkg/process/v6/transformers/nvd/test-fixtures/version-range.json @@ -16,6 +16,50 @@ } ], "metrics": { + "cvssMetricV40": [ + { + "source": "security@zabbix.com", + "type": "Secondary", + "cvssData": { + "version": "4.0", + "vectorString": "CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X", + "baseScore": 7.5, + "baseSeverity": "HIGH", + "attackVector": "NETWORK", + "attackComplexity": "HIGH", + "attackRequirements": "NONE", + "privilegesRequired": "NONE", + "userInteraction": "ACTIVE", + "vulnConfidentialityImpact": "HIGH", + "vulnIntegrityImpact": "HIGH", + "vulnAvailabilityImpact": "HIGH", + "subConfidentialityImpact": "NONE", + "subIntegrityImpact": "NONE", + "subAvailabilityImpact": "NONE", + "exploitMaturity": "NOT_DEFINED", + "confidentialityRequirement": "NOT_DEFINED", + "integrityRequirement": "NOT_DEFINED", + "availabilityRequirement": "NOT_DEFINED", + "modifiedAttackVector": "NOT_DEFINED", + "modifiedAttackComplexity": "NOT_DEFINED", + "modifiedAttackRequirements": "NOT_DEFINED", + "modifiedPrivilegesRequired": "NOT_DEFINED", + "modifiedUserInteraction": "NOT_DEFINED", + "modifiedVulnConfidentialityImpact": "NOT_DEFINED", + "modifiedVulnIntegrityImpact": "NOT_DEFINED", + "modifiedVulnAvailabilityImpact": "NOT_DEFINED", + "modifiedSubConfidentialityImpact": "NOT_DEFINED", + "modifiedSubIntegrityImpact": "NOT_DEFINED", + "modifiedSubAvailabilityImpact": "NOT_DEFINED", + "Safety": "NOT_DEFINED", + "Automatable": "NOT_DEFINED", + "Recovery": "NOT_DEFINED", + "valueDensity": "NOT_DEFINED", + "vulnerabilityResponseEffort": "NOT_DEFINED", + "providerUrgency": "NOT_DEFINED" + } + } + ], "cvssMetricV30": [ { "source": "nvd@nist.gov", diff --git a/pkg/process/v6/transformers/nvd/transform.go b/pkg/process/v6/transformers/nvd/transform.go new file mode 100644 index 00000000..b9564caa --- /dev/null +++ b/pkg/process/v6/transformers/nvd/transform.go @@ -0,0 +1,295 @@ +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", "h", "o"), + 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{ + grypeDB.VulnerabilityHandle{ + Name: vulnerability.ID, + ProviderID: state.Provider, + Provider: internal.ProviderModel(state), + ModifiedDate: internal.ParseTime(vulnerability.LastModified), + PublishedDate: internal.ParseTime(vulnerability.Published), + Status: getVulnStatus(vulnerability), + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: vulnerability.ID, + Assigners: getAssigner(vulnerability), + Description: strings.TrimSpace(vulnerability.Description()), + 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 { + candidates, err := allCandidates(vulnerability.ID, vulnerability.Configurations, cfg) + if err != nil { + log.WithFields("error", err).Warn("failed to process affected NVD CPEs") + return nil + } + + var affs []grypeDB.AffectedCPEHandle + for _, candidate := range candidates { + affs = append(affs, affectedApplicationPackage(cfg, vulnerability, candidate)...) + } + + return affs +} + +func encodeCPEs(cpes []cpe.Attributes) []string { + var results []string + for _, c := range cpes { + results = append(results, c.String()) + } + return results +} + +func affectedApplicationPackage(cfg Config, vulnerability unmarshal.NVDVulnerability, p affectedPackageCandidate) []grypeDB.AffectedCPEHandle { + var affs []grypeDB.AffectedCPEHandle + + var qualifiers *grypeDB.AffectedPackageQualifiers + if len(p.PlatformCPEs) > 0 { + qualifiers = &grypeDB.AffectedPackageQualifiers{ + PlatformCPEs: encodeCPEs(p.PlatformCPEs), + } + } + + affs = append(affs, grypeDB.AffectedCPEHandle{ + CPE: getCPEFromAttributes(p.VulnerableCPE), + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{vulnerability.ID}, + Qualifiers: qualifiers, + Ranges: getRanges(cfg, p.VulnerableCPE, p.Ranges.toSlice()), + }, + }) + + return affs +} + +func getRanges(cfg Config, c cpe.Attributes, ras []affectedCPERange) []grypeDB.AffectedRange { + var ranges []grypeDB.AffectedRange + for _, ra := range ras { + r := getRange(cfg, c, ra) + if r != nil { + ranges = append(ranges, *r) + } + } + + return ranges +} + +func getRange(cfg Config, c cpe.Attributes, ra affectedCPERange) *grypeDB.AffectedRange { + return &grypeDB.AffectedRange{ + Version: grypeDB.AffectedVersion{ + Type: "", // we explicitly do not know what the versioning scheme is + Constraint: ra.String(), + }, + Fix: getFix(cfg, c, ra), + } +} + +func getFix(cfg Config, vulnCPE cpe.Attributes, ra affectedCPERange) *grypeDB.Fix { + if !cfg.InferNVDFixVersions { + return nil + } + + possiblyFixed := strset.New() + knownAffected := strset.New() + unspecifiedSet := strset.New("*", "-", "*") + + if ra.VersionEndExcluding != "" && !unspecifiedSet.Has(ra.VersionEndExcluding) { + possiblyFixed.Add(ra.VersionEndExcluding) + } + + if ra.VersionStartIncluding != "" && !unspecifiedSet.Has(ra.VersionStartIncluding) { + knownAffected.Add(ra.VersionStartIncluding) + } + + if ra.VersionEndIncluding != "" && !unspecifiedSet.Has(ra.VersionEndIncluding) { + knownAffected.Add(ra.VersionEndIncluding) + } + + if !unspecifiedSet.Has(vulnCPE.Version) { + knownAffected.Add(vulnCPE.Version) + } + + possiblyFixed.Remove(knownAffected.List()...) + + if possiblyFixed.Size() != 1 { + return nil + } + + return &grypeDB.Fix{ + Version: possiblyFixed.List()[0], + State: grypeDB.FixedStatus, + } +} + +func getCPEFromAttributes(atts cpe.Attributes) *grypeDB.Cpe { + return &grypeDB.Cpe{ + Part: 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 _, sev := range sevs { + priority := 2 + if sev.Type == nvd.Primary { + priority = 1 + } + results = append(results, grypeDB.Severity{ + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: sev.Vector, + Version: sev.Version, + }, + Source: sev.Source, + Rank: priority, + }) + } + + return results +} + +func getReferences(vuln unmarshal.NVDVulnerability) []grypeDB.Reference { + references := []grypeDB.Reference{ + { + 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..21cf6386 --- /dev/null +++ b/pkg/process/v6/transformers/nvd/transform_test.go @@ -0,0 +1,1434 @@ +package nvd + +import ( + "os" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" + + "github.com/anchore/grype-db/pkg/process/v6/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 + fixture string + config Config + provider string + want []transformers.RelatedEntries + }{ + { + name: "basic version range", + fixture: "test-fixtures/version-range.json", + provider: "nvd", + config: defaultConfig(), + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: &grypeDB.VulnerabilityHandle{ + Name: "CVE-2018-5487", + ProviderID: "nvd", + Provider: expectedProvider("nvd"), + 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, + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2018-5487", + Assigners: []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.", + References: []grypeDB.Reference{ + { + + 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", + }, + 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", + }, + Source: "nvd@nist.gov", + Rank: 1, + }, + { + Scheme: "CVSS", + Value: grypeDB.CVSSSeverity{ + Vector: "CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X", + Version: "4.0", + }, + Source: "security@zabbix.com", + Rank: 2, + }, + }, + }, + }, + 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{ + Part: "a", + Vendor: "netapp", + Product: "oncommand_unified_manager", + }, + }, + ), + }, + }, + }, + { + name: "single package, multiple distros", + fixture: "test-fixtures/single-package-multi-distro.json", + provider: "nvd", + config: defaultConfig(), + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: &grypeDB.VulnerabilityHandle{ + Name: "CVE-2018-1000222", + ProviderID: "nvd", + Provider: expectedProvider("nvd"), + 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, + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2018-1000222", + Assigners: []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.", + References: []grypeDB.Reference{ + { + + 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"}, + 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"}, + Source: "nvd@nist.gov", + Rank: 1, + }, + }, + }, + }, + Related: affectedPkgSlice( + // the application package... + grypeDB.AffectedCPEHandle{ + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2018-1000222"}, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Constraint: "= 2.2.5", + }, + }, + }, + }, + CPE: &grypeDB.Cpe{ + Part: "a", + Vendor: "libgd", + Product: "libgd", + }, + }, + // ubuntu OS ... (since the default config has all parts enabled, we should see this) + grypeDB.AffectedCPEHandle{ + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2018-1000222"}, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Constraint: "= 14.04", + }, + }, + { + Version: grypeDB.AffectedVersion{ + Constraint: "= 16.04", + }, + }, + { + Version: grypeDB.AffectedVersion{ + Constraint: "= 18.04", + }, + }, + }, + }, + CPE: &grypeDB.Cpe{ + Part: "o", + Vendor: "canonical", + Product: "ubuntu_linux", + SoftwareEdition: "lts", + }, + }, + // debian OS ... (since the default config has all parts enabled, we should see this) + grypeDB.AffectedCPEHandle{ + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2018-1000222"}, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Constraint: "= 8.0", + }, + }, + }, + }, + CPE: &grypeDB.Cpe{ + Part: "o", + Vendor: "debian", + Product: "debian_linux", + }, + }, + ), + }, + }, + }, + { + name: "single package, multiple distros (application types only)", + fixture: "test-fixtures/single-package-multi-distro.json", + provider: "nvd", + config: func() Config { + c := defaultConfig() + c.CPEParts.Remove("h", "o") // important! + return c + }(), + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: &grypeDB.VulnerabilityHandle{ + Name: "CVE-2018-1000222", + ProviderID: "nvd", + Provider: expectedProvider("nvd"), + 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, + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2018-1000222", + Assigners: []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.", + References: []grypeDB.Reference{ + { + + 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"}, + 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"}, + Source: "nvd@nist.gov", + Rank: 1, + }, + }, + }, + }, + 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{ + Part: "a", + Vendor: "libgd", + Product: "libgd", + }, + }, + ), + }, + }, + }, + { + name: "multiple packages, multiple distros", + fixture: "test-fixtures/compound-pkg.json", + provider: "nvd", + config: defaultConfig(), + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: &grypeDB.VulnerabilityHandle{ + Name: "CVE-2018-10189", + ProviderID: "nvd", + Provider: expectedProvider("nvd"), + 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, + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2018-10189", + Assigners: []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.", + References: []grypeDB.Reference{ + { + + 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"}, + 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"}, + Source: "nvd@nist.gov", + Rank: 1, + }, + }, + }, + }, + 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{ + Part: "a", + Vendor: "mautic", + Product: "mautic", + }, + }, + ), + }, + }, + }, + { + name: "invalid CPE", + fixture: "test-fixtures/invalid_cpe.json", + provider: "nvd", + config: defaultConfig(), + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: &grypeDB.VulnerabilityHandle{ + Name: "CVE-2015-8978", + ProviderID: "nvd", + Provider: expectedProvider("nvd"), + 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, + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2015-8978", + Assigners: []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.", + References: []grypeDB.Reference{ + { + + 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"}, + 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"}, + Source: "nvd@nist.gov", + Rank: 1, + }, + }, + }, + }, + Related: nil, // when we can't parse the CPE we should not add any affected blobs (but we do add the vuln blob) + }, + }, + }, + { + name: "basic platform CPE", + fixture: "test-fixtures/platform-cpe.json", + provider: "nvd", + config: defaultConfig(), + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: &grypeDB.VulnerabilityHandle{ + Name: "CVE-2022-26488", + ProviderID: "nvd", + Provider: expectedProvider("nvd"), + 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, + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2022-26488", + Assigners: []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.", + References: []grypeDB.Reference{ + { + + 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"}, + 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"}, + Source: "nvd@nist.gov", + Rank: 1, + }, + }, + }, + }, + Related: affectedPkgSlice( + grypeDB.AffectedCPEHandle{ + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2022-26488"}, + Ranges: []grypeDB.AffectedRange{ + { + // match all versions + Version: grypeDB.AffectedVersion{Constraint: ""}, + }, + }, + }, + CPE: &grypeDB.Cpe{ + Part: "a", + Vendor: "netapp", + Product: "active_iq_unified_manager", + TargetSoftware: "windows", + }, + }, + grypeDB.AffectedCPEHandle{ + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2022-26488"}, + Ranges: []grypeDB.AffectedRange{ + { + // match all versions + Version: grypeDB.AffectedVersion{Constraint: ""}, + }, + }, + }, + CPE: &grypeDB.Cpe{ + Part: "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:-:*:*:*:*:*:*:*"}, // important! + }, + Ranges: []grypeDB.AffectedRange{ + {Version: grypeDB.AffectedVersion{Constraint: "<= 3.7.12"}}, + {Version: grypeDB.AffectedVersion{Constraint: ">= 3.10.0, <= 3.10.2"}}, + {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.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-alpha5"}}, + {Version: grypeDB.AffectedVersion{Constraint: "= 3.11.0-alpha6"}}, + }, + }, + CPE: &grypeDB.Cpe{ + Part: "a", + Vendor: "python", + Product: "python", + }, + }, + ), + }, + }, + }, + { + name: "multiple platform CPEs for single package", + fixture: "test-fixtures/cve-2022-0543.json", + provider: "nvd", + config: defaultConfig(), + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: &grypeDB.VulnerabilityHandle{ + Name: "CVE-2022-0543", + ProviderID: "nvd", + Provider: expectedProvider("nvd"), + 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, + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2022-0543", + Assigners: []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.", + References: []grypeDB.Reference{ + { + + 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", + }, + 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", + }, + Source: "nvd@nist.gov", + Rank: 1, + }, + }, + }, + }, + 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:9.0:*:*:*:*:*:*:*", + "cpe:2.3:o:debian:debian_linux:10.0:*:*:*:*:*:*:*", + "cpe:2.3:o:debian:debian_linux:11.0:*:*:*:*:*:*:*", + }, + }, + Ranges: []grypeDB.AffectedRange{ + { + // match all versions + Version: grypeDB.AffectedVersion{Constraint: ""}, + }, + }, + }, + CPE: &grypeDB.Cpe{ + Part: "a", + Vendor: "redis", + Product: "redis", + }, + }, + ), + }, + }, + }, + { + name: "multiple platform CPEs for single package + fix and OS match", + fixture: "test-fixtures/cve-2020-10729.json", + provider: "nvd", + config: defaultConfig(), + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: &grypeDB.VulnerabilityHandle{ + Name: "CVE-2020-10729", + ProviderID: "nvd", + Provider: expectedProvider("nvd"), + 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, + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2020-10729", + Assigners: []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.", + References: []grypeDB.Reference{ + { + + 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"}, + 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"}, + Source: "nvd@nist.gov", + Rank: 1, + }, + }, + }, + }, + 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{ + Part: "a", + Vendor: "redhat", + Product: "ansible_engine", + }, + }, + grypeDB.AffectedCPEHandle{ + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2020-10729"}, + // note: no qualifiers ! + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Constraint: "= 10.0", + }, + // note: no fix! + }, + }, + }, + CPE: &grypeDB.Cpe{ + Part: "o", + Vendor: "debian", + Product: "debian_linux", + }, + }, + ), + }, + }, + }, + { + name: "application type as platform CPE", + fixture: "test-fixtures/multiple-platforms-with-application-cpe.json", + provider: "nvd", + config: defaultConfig(), + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: &grypeDB.VulnerabilityHandle{ + Name: "CVE-2023-38733", + ProviderID: "nvd", + Provider: expectedProvider("nvd"), + 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, + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2023-38733", + Assigners: []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.", + References: []grypeDB.Reference{ + { + + 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"}, + 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"}, + Source: "psirt@us.ibm.com", + Rank: 2, + }, + }, + }, + }, + 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{ + Part: "a", + Vendor: "ibm", + Product: "robotic_process_automation", + }, + }, + ), + }, + }, + }, + { + name: "can process entries when the platform CPE is first", + fixture: "test-fixtures/CVE-2023-45283-platform-cpe-first.json", + provider: "nvd", + config: defaultConfig(), + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: &grypeDB.VulnerabilityHandle{ + Name: "CVE-2023-45283", + ProviderID: "nvd", + Provider: expectedProvider("nvd"), + 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, + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2023-45283", + Assigners: []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.", + References: []grypeDB.Reference{ + { + + 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"}, + Source: "nvd@nist.gov", + Rank: 1, + }, + }, + }, + }, + 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{ + Part: "a", + Vendor: "golang", + Product: "go", + }, + }, + ), + }, + }, + }, + { + name: "can process entries when the platform CPE is last", + fixture: "test-fixtures/CVE-2023-45283-platform-cpe-last.json", + provider: "nvd", + config: defaultConfig(), + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: &grypeDB.VulnerabilityHandle{ + Name: "CVE-2023-45283", + ProviderID: "nvd", + Provider: expectedProvider("nvd"), + 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, + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2023-45283", + Assigners: []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.", + References: []grypeDB.Reference{ + { + + 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"}, + Source: "nvd@nist.gov", + Rank: 1, + }, + }, + }, + }, + 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{ + Part: "a", + Vendor: "golang", + Product: "go", + }, + }, + ), + }, + }, + }, + { + name: "a simple list of OS matches", + // note: this was modified relative to the upstream data to account for additional interesting cases + fixture: "test-fixtures/cve-2024-26663-standalone-os.json", + provider: "nvd", + config: defaultConfig(), + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: &grypeDB.VulnerabilityHandle{ + Name: "CVE-2024-26663", + ProviderID: "nvd", + Provider: expectedProvider("nvd"), + ModifiedDate: timeRef(time.Date(2025, 1, 7, 17, 20, 30, 367000000, time.UTC)), + PublishedDate: timeRef(time.Date(2024, 4, 2, 7, 15, 43, 287000000, time.UTC)), + Status: grypeDB.VulnerabilityActive, + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2024-26663", + Assigners: []string{"416baaa9-dc9f-4396-8d5f-8c081fb06d67"}, + Description: "the description...", + References: []grypeDB.Reference{ + {URL: "https://nvd.nist.gov/vuln/detail/CVE-2024-26663"}, + { + URL: "https://git.kernel.org/stable/c/0cd331dfd6023640c9669d0592bc0fd491205f87", + Tags: []string{"patch"}, + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: "CVSS", + Value: grypeDB.CVSSSeverity{ + Vector: "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H", + Version: "3.1", + }, + Source: "nvd@nist.gov", + Rank: 1, + }, + }, + }, + }, + Related: affectedPkgSlice( + grypeDB.AffectedCPEHandle{ + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2024-26663"}, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Constraint: "= 10.0", + }, + }, + }, + }, + CPE: &grypeDB.Cpe{ + Part: "o", + Vendor: "debian", + Product: "debian_linux", + }, + }, + grypeDB.AffectedCPEHandle{ + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2024-26663"}, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Constraint: ">= 4.9, < 4.19.307", + }, + Fix: &grypeDB.Fix{ + State: grypeDB.FixedStatus, + Version: "4.19.307", + }, + }, + { + Version: grypeDB.AffectedVersion{ + Constraint: ">= 6.7, < 6.7.5", + }, + Fix: &grypeDB.Fix{ + State: grypeDB.FixedStatus, + Version: "6.7.5", + }, + }, + { + Version: grypeDB.AffectedVersion{ + Constraint: "= 6.8-rc1", + }, + }, + { + Version: grypeDB.AffectedVersion{ + Constraint: "= 6.8-rc2", + }, + }, + { + Version: grypeDB.AffectedVersion{ + Constraint: "= 6.8-rc3", + }, + }, + }, + }, + CPE: &grypeDB.Cpe{ + Part: "o", + Vendor: "linux", + Product: "linux_kernel", + }, + }, + ), + }, + }, + }, + { + name: "drops nodes with unsupported topology", + fixture: "test-fixtures/cve-2021-1566.json", + provider: "nvd", + config: defaultConfig(), + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: &grypeDB.VulnerabilityHandle{ + Name: "CVE-2021-1566", + ProviderID: "nvd", + Provider: expectedProvider("nvd"), + ModifiedDate: timeRef(time.Date(2024, 11, 21, 5, 44, 38, 237000000, time.UTC)), + PublishedDate: timeRef(time.Date(2021, 6, 16, 18, 15, 8, 710000000, time.UTC)), + Status: grypeDB.VulnerabilityActive, + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2021-1566", + Assigners: []string{"psirt@cisco.com"}, + Description: "description.", + References: []grypeDB.Reference{ + {URL: "https://nvd.nist.gov/vuln/detail/CVE-2021-1566"}, + { + URL: "https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-esa-wsa-cert-vali-n8L97RW", + Tags: []string{"vendor-advisory"}, + }, + { + URL: "https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-esa-wsa-cert-vali-n8L97RW", + Tags: []string{"vendor-advisory"}, + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: "CVSS", + Value: grypeDB.CVSSSeverity{ + Vector: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N", + Version: "3.1", + }, + Source: "nvd@nist.gov", + Rank: 1, + }, + { + Scheme: "CVSS", + Value: grypeDB.CVSSSeverity{ + Vector: "AV:N/AC:M/Au:N/C:P/I:P/A:N", + Version: "2.0", + }, + Source: "nvd@nist.gov", + Rank: 1, + }, + { + Scheme: "CVSS", + Value: grypeDB.CVSSSeverity{ + Vector: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N", + Version: "3.1", + }, + Source: "psirt@cisco.com", + Rank: 2, + }, + }, + }, + }, + Related: nil, // important! we dropped all of the node criteria since the topology is unsupported + }, + }, + }, + { + name: "considers non-standard CPE fields", + fixture: "test-fixtures/CVE-2008-3442.json", + provider: "nvd", + config: defaultConfig(), + want: []transformers.RelatedEntries{ + { + VulnerabilityHandle: &grypeDB.VulnerabilityHandle{ + Name: "CVE-2008-3442", + ProviderID: "nvd", + Provider: expectedProvider("nvd"), + ModifiedDate: timeRef(time.Date(2008, 9, 5, 21, 43, 5, 500000000, time.UTC)), + PublishedDate: timeRef(time.Date(2008, 8, 1, 14, 41, 0, 0, time.UTC)), + Status: grypeDB.VulnerabilityActive, + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2008-3442", + Assigners: []string{"cve@mitre.org"}, + Description: "desc.", + References: []grypeDB.Reference{{URL: "https://nvd.nist.gov/vuln/detail/CVE-2008-3442"}}, + }, + }, + Related: affectedPkgSlice( + grypeDB.AffectedCPEHandle{ + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2008-3442"}, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Constraint: "= 10.0", + }, + }, + { + Version: grypeDB.AffectedVersion{ + Constraint: "= 7.0", + }, + }, + { + Version: grypeDB.AffectedVersion{ + Constraint: "= 8.0", + }, + }, + { + Version: grypeDB.AffectedVersion{ + Constraint: "= 8.1", + }, + }, + { + Version: grypeDB.AffectedVersion{ + Constraint: "= 9.0", + }, + }, + }, + }, + CPE: &grypeDB.Cpe{ + Part: "a", + Vendor: "winzip", + Product: "winzip", + }, + }, + grypeDB.AffectedCPEHandle{ + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2008-3442"}, + Ranges: []grypeDB.AffectedRange{ + { + Version: grypeDB.AffectedVersion{ + Constraint: "= 8.1", + }, + }, + { + Version: grypeDB.AffectedVersion{ + Constraint: "= 9.0", + }, + }, + }, + }, + CPE: &grypeDB.Cpe{ + Part: "a", + Vendor: "winzip", + Product: "winzip", + Edition: "sr1", + }, + }, + ), + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + vulns := loadFixture(t, test.fixture) + + 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 tests.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/v1/transformers/os/test-fixtures/alpine-3.9.json b/pkg/process/v6/transformers/os/test-fixtures/alpine-3.9.json similarity index 100% rename from pkg/process/v1/transformers/os/test-fixtures/alpine-3.9.json rename to pkg/process/v6/transformers/os/test-fixtures/alpine-3.9.json diff --git a/pkg/process/v1/transformers/os/test-fixtures/amazon-multiple-kernel-advisories.json b/pkg/process/v6/transformers/os/test-fixtures/amazon-multiple-kernel-advisories.json similarity index 100% rename from pkg/process/v1/transformers/os/test-fixtures/amazon-multiple-kernel-advisories.json rename to pkg/process/v6/transformers/os/test-fixtures/amazon-multiple-kernel-advisories.json diff --git a/pkg/process/v3/transformers/os/test-fixtures/amzn.json b/pkg/process/v6/transformers/os/test-fixtures/amzn.json similarity index 95% rename from pkg/process/v3/transformers/os/test-fixtures/amzn.json rename to pkg/process/v6/transformers/os/test-fixtures/amzn.json index a862c32e..2c551050 100644 --- a/pkg/process/v3/transformers/os/test-fixtures/amzn.json +++ b/pkg/process/v6/transformers/os/test-fixtures/amzn.json @@ -37,8 +37,9 @@ "Link": "https://alas.aws.amazon.com/AL2/ALAS-2018-1106.html", "Metadata": { "CVE": [ - - {"Name": "CVE-2018-14648"} + { + "Name": "CVE-2018-14648" + } ] }, "Name": "ALAS-2018-1106", 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/v1/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 similarity index 100% rename from pkg/process/v1/transformers/os/test-fixtures/debian-8-multiple-entries-for-same-package.json rename to pkg/process/v6/transformers/os/test-fixtures/debian-8-multiple-entries-for-same-package.json diff --git a/pkg/process/v1/transformers/os/test-fixtures/debian-8.json b/pkg/process/v6/transformers/os/test-fixtures/debian-8.json similarity index 100% rename from pkg/process/v1/transformers/os/test-fixtures/debian-8.json rename to pkg/process/v6/transformers/os/test-fixtures/debian-8.json 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/v1/transformers/os/test-fixtures/ol-8-modules.json b/pkg/process/v6/transformers/os/test-fixtures/ol-8-modules.json similarity index 100% rename from pkg/process/v1/transformers/os/test-fixtures/ol-8-modules.json rename to pkg/process/v6/transformers/os/test-fixtures/ol-8-modules.json diff --git a/pkg/process/v1/transformers/os/test-fixtures/ol-8.json b/pkg/process/v6/transformers/os/test-fixtures/ol-8.json similarity index 100% rename from pkg/process/v1/transformers/os/test-fixtures/ol-8.json rename to pkg/process/v6/transformers/os/test-fixtures/ol-8.json diff --git a/pkg/process/v1/transformers/os/test-fixtures/rhel-8-modules.json b/pkg/process/v6/transformers/os/test-fixtures/rhel-8-modules.json similarity index 100% rename from pkg/process/v1/transformers/os/test-fixtures/rhel-8-modules.json rename to pkg/process/v6/transformers/os/test-fixtures/rhel-8-modules.json diff --git a/pkg/process/v1/transformers/os/test-fixtures/rhel-8.json b/pkg/process/v6/transformers/os/test-fixtures/rhel-8.json similarity index 100% rename from pkg/process/v1/transformers/os/test-fixtures/rhel-8.json rename to pkg/process/v6/transformers/os/test-fixtures/rhel-8.json diff --git a/pkg/process/v6/transformers/os/transform.go b/pkg/process/v6/transformers/os/transform.go new file mode 100644 index 00000000..68d5f3d5 --- /dev/null +++ b/pkg/process/v6/transformers/os/transform.go @@ -0,0 +1,360 @@ +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/db/v6/name" + "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{ + grypeDB.VulnerabilityHandle{ + Name: vulnerability.Vulnerability.Name, + ProviderID: state.Provider, + Provider: internal.ProviderModel(state), + Status: grypeDB.VulnerabilityActive, + ModifiedDate: internal.ParseTime(vulnerability.Vulnerability.Metadata.Updated), + PublishedDate: internal.ParseTime(vulnerability.Vulnerability.Metadata.Issued), + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: vulnerability.Vulnerability.Name, + Assigners: nil, + Description: strings.TrimSpace(vulnerability.Vulnerability.Description), + References: getReferences(vulnerability), + Aliases: getAliases(vulnerability), + Severities: getSeverities(vulnerability), + }, + }, + } + + 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 { + // we only care about a single qualifier: rpm modules. The important thing to note about this is that + // a package with no module vs a package with a module should be detectable in the DB. + var qualifiers *grypeDB.AffectedPackageQualifiers + if group.format == "rpm" { + module := "" // means the target package must have no module (where as nil means the module has no sway on matching) + if group.hasModule { + module = group.module + } + qualifiers = &grypeDB.AffectedPackageQualifiers{ + RpmModularity: &module, + } + } + + aph := grypeDB.AffectedPackageHandle{ + OperatingSystem: getOperatingSystem(group.osName, group.id, 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{ + URL: l, + Tags: []string{grypeDB.AdvisoryReferenceTag}, + }) + } + + 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 + id string + osName string + osVersion string + hasModule bool + module string + format string +} + +func groupFixedIns(vuln unmarshal.OSVulnerability) map[groupIndex][]unmarshal.OSFixedIn { + grouped := make(map[groupIndex][]unmarshal.OSFixedIn) + osName, osID, 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, + id: osID, + osName: osName, + osVersion: osVersion, + hasModule: fixedIn.Module != nil, + module: mod, + format: fixedIn.VersionFormat, + } + + grouped[g] = append(grouped[g], fixedIn) + } + return grouped +} + +func getPackageType(osName string) pkg.Type { + switch osName { + case "redhat", "amazonlinux", "oraclelinux", "sles", "mariner", "azurelinux": + return pkg.RpmPkg + case "ubuntu", "debian": + return pkg.DebPkg + case "alpine", "chainguard", "wolfi": + return pkg.ApkPkg + case "windows": + return pkg.KbPkg + } + + return "" +} + +func getPackage(group groupIndex) *grypeDB.Package { + t := getPackageType(group.osName) + return &grypeDB.Package{ + Ecosystem: string(t), + Name: name.Normalize(group.name, t), + } +} + +func getOSInfo(group string) (string, string, string) { + // derived from enterprise feed groups, expected to be of the form {distro release ID}:{version} + feedGroupComponents := strings.Split(group, ":") + + id := feedGroupComponents[0] + version := feedGroupComponents[1] + if strings.ToLower(id) == "mariner" { + verFields := strings.Split(version, ".") + majorVersionStr := verFields[0] + majorVer, err := strconv.Atoi(majorVersionStr) + if err == nil { + if majorVer >= 3 { + id = string(distro.Azure) + } + } + } + + return normalizeOsName(id), id, version +} + +func normalizeOsName(id string) string { + d, ok := distro.IDMapping[id] + if !ok { + log.WithFields("distro", id).Warn("unknown distro name") + + return id + } + + return d.String() +} + +func getOperatingSystem(osName, osID, osVersion string) *grypeDB.OperatingSystem { + if osName == "" || osVersion == "" { + return nil + } + + versionFields := strings.Split(osVersion, ".") + var majorVersion, minorVersion, labelVersion string + majorVersion = versionFields[0] + if len(majorVersion) > 0 { + // is the first field a number? + _, err := strconv.Atoi(majorVersion[0:1]) + if err != nil { + labelVersion = majorVersion + majorVersion = "" + } else if len(versionFields) > 1 { + minorVersion = versionFields[1] + } + } + + return &grypeDB.OperatingSystem{ + Name: osName, + ReleaseID: osID, + MajorVersion: majorVersion, + MinorVersion: minorVersion, + LabelVersion: labelVersion, + 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{ + 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, + }, + 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..a5baf7f5 --- /dev/null +++ b/pkg/process/v6/transformers/os/transform_test.go @@ -0,0 +1,1285 @@ +package os + +import ( + "os" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" + + "github.com/anchore/grype-db/pkg/process/v6/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) { + + alpineOS := &grypeDB.OperatingSystem{ + Name: "alpine", + ReleaseID: "alpine", + MajorVersion: "3", + MinorVersion: "9", + } + + amazonOS := &grypeDB.OperatingSystem{ + Name: "amazonlinux", + ReleaseID: "amzn", + MajorVersion: "2", + } + azure3OS := &grypeDB.OperatingSystem{ + Name: "azurelinux", + ReleaseID: "azurelinux", + MajorVersion: "3", + MinorVersion: "0", // TODO: is this right? + } + debian8OS := &grypeDB.OperatingSystem{ + Name: "debian", + ReleaseID: "debian", + MajorVersion: "8", + Codename: "jessie", + } + + mariner2OS := &grypeDB.OperatingSystem{ + Name: "mariner", + ReleaseID: "mariner", + MajorVersion: "2", + MinorVersion: "0", // TODO: is this right? + } + ol8OS := &grypeDB.OperatingSystem{ + Name: "oraclelinux", + ReleaseID: "ol", + MajorVersion: "8", + } + rhel8OS := &grypeDB.OperatingSystem{ + Name: "redhat", + ReleaseID: "rhel", + 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", + Status: "active", + ProviderID: "alpine", + Provider: expectedProvider("alpine"), + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2018-19967", + References: []grypeDB.Reference{ + { + URL: "http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-19967", + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHMLN, + Value: "medium", + Rank: 1, + }, + }, + }, + }, + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + OperatingSystem: alpineOS, + Package: &grypeDB.Package{Ecosystem: "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", + ProviderID: "amazon", + Provider: expectedProvider("amazon"), + Status: "active", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "ALAS-2018-1106", + References: []grypeDB.Reference{ + { + URL: "https://alas.aws.amazon.com/AL2/ALAS-2018-1106.html", + }, + }, + Aliases: []string{"CVE-2018-14648"}, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHMLN, + Value: "medium", + Rank: 1, + }, + }, + }, + }, + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + OperatingSystem: amazonOS, + Package: &grypeDB.Package{ + Name: "389-ds-base", + Ecosystem: "rpm", + }, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2018-14648"}, + Qualifiers: &grypeDB.AffectedPackageQualifiers{RpmModularity: strRef("")}, + 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", + Ecosystem: "rpm", + }, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2018-14648"}, + Qualifiers: &grypeDB.AffectedPackageQualifiers{RpmModularity: strRef("")}, + 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", + Ecosystem: "rpm", + }, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2018-14648"}, + Qualifiers: &grypeDB.AffectedPackageQualifiers{RpmModularity: strRef("")}, + 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", + Ecosystem: "rpm", + }, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2018-14648"}, + Qualifiers: &grypeDB.AffectedPackageQualifiers{RpmModularity: strRef("")}, + 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", + Ecosystem: "rpm", + }, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2018-14648"}, + Qualifiers: &grypeDB.AffectedPackageQualifiers{RpmModularity: strRef("")}, + 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", + ProviderID: "amazon", + Provider: expectedProvider("amazon"), + Status: "active", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "ALAS-2021-1704", + + References: []grypeDB.Reference{ + { + URL: "https://alas.aws.amazon.com/AL2/ALAS-2021-1704.html", + }, + }, + Aliases: []string{"CVE-2021-3653", "CVE-2021-3656", "CVE-2021-3732"}, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHMLN, + Value: "medium", + Rank: 1, + }, + }, + }, + }, + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + OperatingSystem: amazonOS, + Package: &grypeDB.Package{Ecosystem: "rpm", Name: "kernel"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2021-3653", "CVE-2021-3656", "CVE-2021-3732"}, + Qualifiers: &grypeDB.AffectedPackageQualifiers{RpmModularity: strRef("")}, + 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{Ecosystem: "rpm", Name: "kernel-headers"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2021-3653", "CVE-2021-3656", "CVE-2021-3732"}, + Qualifiers: &grypeDB.AffectedPackageQualifiers{RpmModularity: strRef("")}, + 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", + ProviderID: "amazon", + Provider: expectedProvider("amazon"), + Status: "active", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "ALASKERNEL-5.4-2022-007", + References: []grypeDB.Reference{ + { + URL: "https://alas.aws.amazon.com/AL2/ALASKERNEL-5.4-2022-007.html", + }, + }, + Aliases: []string{"CVE-2021-3753", "CVE-2021-40490"}, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHMLN, + Value: "medium", + Rank: 1, + }, + }, + }, + }, + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + OperatingSystem: amazonOS, + Package: &grypeDB.Package{Ecosystem: "rpm", Name: "kernel"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2021-3753", "CVE-2021-40490"}, + Qualifiers: &grypeDB.AffectedPackageQualifiers{RpmModularity: strRef("")}, + 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{Ecosystem: "rpm", Name: "kernel-headers"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2021-3753", "CVE-2021-40490"}, + Qualifiers: &grypeDB.AffectedPackageQualifiers{RpmModularity: strRef("")}, + 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", + ProviderID: "amazon", + Provider: expectedProvider("amazon"), + Status: "active", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "ALASKERNEL-5.10-2022-005", + References: []grypeDB.Reference{ + { + URL: "https://alas.aws.amazon.com/AL2/ALASKERNEL-5.10-2022-005.html", + }, + }, + Aliases: []string{"CVE-2021-3753", "CVE-2021-40490"}, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHMLN, + Value: "medium", + Rank: 1, + }, + }, + }, + }, + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + OperatingSystem: amazonOS, + Package: &grypeDB.Package{Ecosystem: "rpm", Name: "kernel"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2021-3753", "CVE-2021-40490"}, + Qualifiers: &grypeDB.AffectedPackageQualifiers{RpmModularity: strRef("")}, + 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{Ecosystem: "rpm", Name: "kernel-headers"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2021-3753", "CVE-2021-40490"}, + Qualifiers: &grypeDB.AffectedPackageQualifiers{RpmModularity: strRef("")}, + 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", + ProviderID: "mariner", + Provider: expectedProvider("mariner"), + Status: "active", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2023-29403", + 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", + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHMLN, + Value: "high", + Rank: 1, + }, + }, + }, + }, + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + OperatingSystem: azure3OS, + Package: &grypeDB.Package{Ecosystem: "rpm", Name: "golang"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + Qualifiers: &grypeDB.AffectedPackageQualifiers{RpmModularity: strRef("")}, + 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", + ProviderID: "debian", + Provider: expectedProvider("debian"), + Status: "active", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2008-7220", + References: []grypeDB.Reference{ + { + URL: "https://security-tracker.debian.org/tracker/CVE-2008-7220", + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHMLN, + Value: "high", + Rank: 1, + }, + }, + }, + }, + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + OperatingSystem: debian8OS, + Package: &grypeDB.Package{Ecosystem: "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{Ecosystem: "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{Ecosystem: "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{Ecosystem: "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", + ProviderID: "debian", + Provider: expectedProvider("debian"), + Status: "active", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2011-4623", + References: []grypeDB.Reference{ + { + URL: "https://security-tracker.debian.org/tracker/CVE-2011-4623", + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHMLN, + Value: "low", + Rank: 1, + }, + }, + }, + }, + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + OperatingSystem: debian8OS, + Package: &grypeDB.Package{Ecosystem: "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", + ProviderID: "debian", + Provider: expectedProvider("debian"), + Status: "active", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2008-5618", + References: []grypeDB.Reference{ + { + URL: "https://security-tracker.debian.org/tracker/CVE-2008-5618", + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHMLN, + Value: "low", + Rank: 1, + }, + }, + }, + }, + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + OperatingSystem: debian8OS, + Package: &grypeDB.Package{Ecosystem: "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", + ProviderID: "mariner", + Provider: expectedProvider("mariner"), + Status: "active", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2021-37621", + 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", + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHMLN, + Value: "medium", + Rank: 1, + }, + }, + }, + }, + + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + OperatingSystem: mariner2OS, + Package: &grypeDB.Package{Ecosystem: "rpm", Name: "exiv2"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + Qualifiers: &grypeDB.AffectedPackageQualifiers{RpmModularity: strRef("")}, + 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", + ProviderID: "mariner", + Provider: expectedProvider("mariner"), + Status: "active", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2023-29404", + 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", + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHMLN, + Value: "critical", + Rank: 1, + }, + }, + }, + }, + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + OperatingSystem: mariner2OS, + Package: &grypeDB.Package{Ecosystem: "rpm", Name: "golang"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + Qualifiers: &grypeDB.AffectedPackageQualifiers{RpmModularity: strRef("")}, + 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", + ProviderID: "oracle", + Provider: expectedProvider("oracle"), + Status: "active", + PublishedDate: timeRef(time.Date(2020, 6, 15, 0, 0, 0, 0, time.UTC)), + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "ELSA-2020-2550", + Aliases: []string{"CVE-2020-13112"}, + References: []grypeDB.Reference{ + { + URL: "http://linux.oracle.com/errata/ELSA-2020-2550.html", + }, + { + URL: "http://linux.oracle.com/cve/CVE-2020-13112.html", + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHMLN, + Value: "medium", + Rank: 1, + }, + }, + }, + }, + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + OperatingSystem: ol8OS, + Package: &grypeDB.Package{Ecosystem: "rpm", Name: "libexif"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2020-13112"}, + Qualifiers: &grypeDB.AffectedPackageQualifiers{RpmModularity: strRef("")}, + 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{Ecosystem: "rpm", Name: "libexif-devel"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2020-13112"}, + Qualifiers: &grypeDB.AffectedPackageQualifiers{RpmModularity: strRef("")}, + 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{Ecosystem: "rpm", Name: "libexif-dummy"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2020-13112"}, + Qualifiers: &grypeDB.AffectedPackageQualifiers{RpmModularity: strRef("")}, + 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", + ProviderID: "oracle", + Provider: expectedProvider("oracle"), + Status: "active", + 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.", + References: []grypeDB.Reference{ + { + URL: "https://access.redhat.com/security/cve/CVE-2020-14350", + }, + }, + Severities: []grypeDB.Severity{ + { + Scheme: grypeDB.SeveritySchemeCHMLN, + Value: "medium", + Rank: 1, + }, + }, + }, + }, + + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + OperatingSystem: ol8OS, + Package: &grypeDB.Package{Ecosystem: "rpm", Name: "postgresql"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + Qualifiers: &grypeDB.AffectedPackageQualifiers{ + RpmModularity: strRef("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{Ecosystem: "rpm", Name: "postgresql"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + Qualifiers: &grypeDB.AffectedPackageQualifiers{ + RpmModularity: strRef("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{Ecosystem: "rpm", Name: "postgresql"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + Qualifiers: &grypeDB.AffectedPackageQualifiers{ + RpmModularity: strRef("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", + ProviderID: "redhat", + Provider: expectedProvider("redhat"), + Status: "active", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2020-6819", + 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", + }, + }, + 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", + }, + Rank: 2, + }, + }, + }, + }, + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + OperatingSystem: rhel8OS, + Package: &grypeDB.Package{Ecosystem: "rpm", Name: "firefox"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + Qualifiers: &grypeDB.AffectedPackageQualifiers{RpmModularity: strRef("")}, + 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{Ecosystem: "rpm", Name: "thunderbird"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + Qualifiers: &grypeDB.AffectedPackageQualifiers{RpmModularity: strRef("")}, + 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", + ProviderID: "redhat", + Provider: expectedProvider("redhat"), + Status: "active", + 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.", + References: []grypeDB.Reference{ + { + URL: "https://access.redhat.com/security/cve/CVE-2020-14350", + }, + }, + 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", + }, + Rank: 2, + }, + }, + }, + }, + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + OperatingSystem: rhel8OS, + Package: &grypeDB.Package{Ecosystem: "rpm", Name: "postgresql"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + Qualifiers: &grypeDB.AffectedPackageQualifiers{ + RpmModularity: strRef("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{ + { + URL: "https://access.redhat.com/errata/RHSA-2020:3669", + Tags: []string{grypeDB.AdvisoryReferenceTag}, + }, + }, + }, + }, + }, + }, + }, + }, + grypeDB.AffectedPackageHandle{ + OperatingSystem: rhel8OS, + Package: &grypeDB.Package{Ecosystem: "rpm", Name: "postgresql"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + Qualifiers: &grypeDB.AffectedPackageQualifiers{ + RpmModularity: strRef("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{ + { + URL: "https://access.redhat.com/errata/RHSA-2020:5620", + Tags: []string{grypeDB.AdvisoryReferenceTag}, + }, + }, + }, + }, + }, + }, + }, + }, + grypeDB.AffectedPackageHandle{ + OperatingSystem: rhel8OS, + Package: &grypeDB.Package{Ecosystem: "rpm", Name: "postgresql"}, + BlobValue: &grypeDB.AffectedPackageBlob{ + Qualifiers: &grypeDB.AffectedPackageQualifiers{ + RpmModularity: strRef("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{ + { + URL: "https://access.redhat.com/errata/RHSA-2020:5619", + Tags: []string{grypeDB.AdvisoryReferenceTag}, + }, + }, + }, + }, + }, + }, + }, + }, + ), + }, + }, + }, + } + + 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 TestGetOperatingSystem(t *testing.T) { + tests := []struct { + name string + osName string + osID string + osVersion string + expected *grypeDB.OperatingSystem + }{ + { + name: "works with given args", + osName: "alpine", + osID: "alpine", + osVersion: "3.10", + expected: &grypeDB.OperatingSystem{ + Name: "alpine", + ReleaseID: "alpine", + MajorVersion: "3", + MinorVersion: "10", + LabelVersion: "", + Codename: "", + }, + }, + { + name: "does codename lookup (debian)", + osName: "debian", + osID: "debian", + osVersion: "11", + expected: &grypeDB.OperatingSystem{ + Name: "debian", + ReleaseID: "debian", + MajorVersion: "11", + MinorVersion: "", + LabelVersion: "", + Codename: "bullseye", + }, + }, + { + name: "does codename lookup (ubuntu)", + osName: "ubuntu", + osID: "ubuntu", + osVersion: "22.04", + expected: &grypeDB.OperatingSystem{ + Name: "ubuntu", + ReleaseID: "ubuntu", + MajorVersion: "22", + MinorVersion: "04", + LabelVersion: "", + Codename: "jammy", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := getOperatingSystem(tt.osName, tt.osID, tt.osVersion) + require.Equal(t, tt.expected, result) + }) + } +} + +func TestGetOSInfo(t *testing.T) { + tests := []struct { + name string + group string + expectedOS string + expectedID string + expectedV string + }{ + { + name: "alpine 3.10", + group: "alpine:3.10", + expectedOS: "alpine", + expectedID: "alpine", + expectedV: "3.10", + }, + { + name: "debian bullseye", + group: "debian:11", + expectedOS: "debian", + expectedID: "debian", + expectedV: "11", + }, + { + name: "mariner version 1", + group: "mariner:1.0", + expectedOS: "mariner", + expectedID: "mariner", + expectedV: "1.0", + }, + { + name: "mariner version 3 (azurelinux conversion)", + group: "mariner:3.0", + expectedOS: "azurelinux", + expectedID: "azurelinux", + expectedV: "3.0", + }, + { + name: "ubuntu focal", + group: "ubuntu:20.04", + expectedOS: "ubuntu", + expectedID: "ubuntu", + expectedV: "20.04", + }, + { + name: "oracle linux", + group: "ol:8", + expectedOS: "oraclelinux", // normalize name + expectedID: "ol", // keep original ID + expectedV: "8", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + osName, id, version := getOSInfo(tt.group) + require.Equal(t, tt.expectedOS, osName) + require.Equal(t, tt.expectedID, id) + require.Equal(t, tt.expectedV, version) + }) + } +} + +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 tests.CloseFile(f) + + entries, err := unmarshal.OSVulnerabilityEntries(f) + require.NoError(t, err) + return entries +} + +func timeRef(ti time.Time) *time.Time { + return &ti +} + +func strRef(s string) *string { + return &s +} diff --git a/pkg/process/v6/transformers/osv/test-fixtures/BIT-apache-2020-11984.json b/pkg/process/v6/transformers/osv/test-fixtures/BIT-apache-2020-11984.json new file mode 100644 index 00000000..1ac07671 --- /dev/null +++ b/pkg/process/v6/transformers/osv/test-fixtures/BIT-apache-2020-11984.json @@ -0,0 +1,54 @@ +{ + "schema_version": "1.5.0", + "id": "BIT-apache-2020-11984", + "details": "Apache HTTP server 2.4.32 to 2.4.44 mod_proxy_uwsgi info disclosure and possible RCE", + "aliases": [ + "CVE-2020-11984" + ], + "affected": [ + { + "package": { + "ecosystem": "Bitnami", + "name": "apache", + "purl": "pkg:bitnami/apache" + }, + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + } + ], + "ranges": [ + { + "type": "SEMVER", + "events": [ + { + "introduced": "2.4.32" + }, + { + "last_affected": "2.4.43" + } + ] + } + ] + } + ], + "database_specific": { + "severity": "Critical", + "cpes": [ + "cpe:2.3:a:apache:http_server:*:*:*:*:*:*:*:*" + ] + }, + "references": [ + { + "type": "WEB", + "url": "http://www.openwall.com/lists/oss-security/2020/08/08/1" + }, + { + "type": "WEB", + "url": "http://www.openwall.com/lists/oss-security/2020/08/08/10" + } + ], + "published": "2024-03-06T10:57:57.770Z", + "modified": "2025-01-17T15:26:01.971Z" +} diff --git a/pkg/process/v6/transformers/osv/test-fixtures/BIT-node-2020-8201.json b/pkg/process/v6/transformers/osv/test-fixtures/BIT-node-2020-8201.json new file mode 100644 index 00000000..5c4be0db --- /dev/null +++ b/pkg/process/v6/transformers/osv/test-fixtures/BIT-node-2020-8201.json @@ -0,0 +1,61 @@ +{ + "schema_version": "1.5.0", + "id": "BIT-node-2020-8201", + "details": "Node.js < 12.18.4 and < 14.11 can be exploited to perform HTTP desync attacks and deliver malicious payloads to unsuspecting users. The payloads can be crafted by an attacker to hijack user sessions, poison cookies, perform clickjacking, and a multitude of other attacks depending on the architecture of the underlying system. The attack was possible due to a bug in processing of carrier-return symbols in the HTTP header names.", + "aliases": [ + "CVE-2020-8201" + ], + "affected": [ + { + "package": { + "ecosystem": "Bitnami", + "name": "node", + "purl": "pkg:bitnami/node" + }, + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N" + } + ], + "ranges": [ + { + "type": "SEMVER", + "events": [ + { + "introduced": "12.0.0" + }, + { + "fixed": "12.18.4" + }, + { + "introduced": "14.0.0" + }, + { + "fixed": "14.11.0" + } + ] + } + ] + } + ], + "database_specific": { + "severity": "High", + "cpes": [ + "cpe:2.3:a:nodejs:node.js:*:*:*:*:*:*:*:*", + "cpe:2.3:a:nodejs:node.js:*:*:*:*:lts:*:*:*" + ] + }, + "references": [ + { + "type": "WEB", + "url": "https://nodejs.org/en/blog/vulnerability/september-2020-security-releases/" + }, + { + "type": "WEB", + "url": "https://security.gentoo.org/glsa/202101-07" + } + ], + "published": "2024-03-06T11:08:09.371Z", + "modified": "2024-03-06T11:25:28.861Z" +} diff --git a/pkg/process/v6/transformers/osv/transform.go b/pkg/process/v6/transformers/osv/transform.go new file mode 100644 index 00000000..a6ae9687 --- /dev/null +++ b/pkg/process/v6/transformers/osv/transform.go @@ -0,0 +1,306 @@ +package osv + +import ( + "fmt" + "regexp" + "sort" + "strings" + + "github.com/google/osv-scanner/pkg/models" + + "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" + "github.com/anchore/grype/grype/db/v6/name" + "github.com/anchore/syft/syft/pkg" +) + +func Transform(vulnerability unmarshal.OSVVulnerability, state provider.State) ([]data.Entry, error) { + severities, err := getSeverities(vulnerability) + if err != nil { + return nil, fmt.Errorf("unable to obtain severities: %w", err) + } + + in := []any{ + grypeDB.VulnerabilityHandle{ + Name: vulnerability.ID, + ProviderID: state.Provider, + Provider: internal.ProviderModel(state), + Status: grypeDB.VulnerabilityActive, + ModifiedDate: &vulnerability.Modified, + PublishedDate: &vulnerability.Published, + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: vulnerability.ID, + Assigners: nil, + Description: vulnerability.Details, + References: getReferences(vulnerability), + Aliases: vulnerability.Aliases, + Severities: severities, + }, + }, + } + + for _, a := range getAffectedPackages(vulnerability) { + in = append(in, a) + } + + return transformers.NewEntries(in...), nil +} + +func getAffectedPackages(vuln unmarshal.OSVVulnerability) []grypeDB.AffectedPackageHandle { + if len(vuln.Affected) == 0 { + return nil + } + + // CPES might be in the database_specific information + cpes, withCPE := vuln.DatabaseSpecific["cpes"] + if withCPE { + if _, ok := cpes.([]string); !ok { + withCPE = false + } + } + + var aphs []grypeDB.AffectedPackageHandle + for _, affected := range vuln.Affected { + aph := grypeDB.AffectedPackageHandle{ + Package: getPackage(affected.Package), + BlobValue: &grypeDB.AffectedPackageBlob{CVEs: vuln.Aliases}, + } + + if withCPE { + aph.BlobValue.Qualifiers = &grypeDB.AffectedPackageQualifiers{ + PlatformCPEs: cpes.([]string), + } + } + + var ranges []grypeDB.AffectedRange + for _, r := range affected.Ranges { + ranges = append(ranges, getGrypeRangesFromRange(r)...) + } + aph.BlobValue.Ranges = ranges + aphs = append(aphs, aph) + } + + // stable ordering + sort.Sort(internal.ByAffectedPackage(aphs)) + + return aphs +} + +// OSV supports flattered ranges, so both formats below are valid: +// "ranges": [ +// +// { +// "type": "SEMVER", +// "events": [ +// { +// "introduced": "12.0.0" +// }, +// { +// "fixed": "12.18.4" +// } +// ] +// }, +// { +// "type": "SEMVER", +// "events": [ +// { +// "introduced": "14.0.0" +// }, +// { +// "fixed": "14.11.0" +// } +// ] +// } +// +// ] +// "ranges": [ +// +// { +// "type": "SEMVER", +// "events": [ +// { +// "introduced": "12.0.0" +// }, +// { +// "fixed": "12.18.4" +// }, +// { +// "introduced": "14.0.0" +// }, +// { +// "fixed": "14.11.0" +// } +// ] +// } +// +// ] +func getGrypeRangesFromRange(r models.Range) []grypeDB.AffectedRange { + var ranges []grypeDB.AffectedRange + if len(r.Events) == 0 { + return nil + } + + var constraint string + updateConstraint := func(c string) { + if constraint == "" { + constraint = c + } else { + constraint = common.AndConstraints(constraint, c) + } + } + + rangeType := normalizeRangeType(r.Type) + for _, e := range r.Events { + switch { + case e.Introduced != "" && e.Introduced != "0": + constraint = fmt.Sprintf(">= %s", e.Introduced) + case e.LastAffected != "": + updateConstraint(fmt.Sprintf("<= %s", e.LastAffected)) + // We don't know the fix if last affected is set + ranges = append(ranges, grypeDB.AffectedRange{ + Version: grypeDB.AffectedVersion{ + Type: rangeType, + Constraint: normalizeConstraint(constraint, rangeType), + }, + }) + // Reset the constraint + constraint = "" + case e.Fixed != "": + updateConstraint(fmt.Sprintf("< %s", e.Fixed)) + ranges = append(ranges, grypeDB.AffectedRange{ + Fix: normalizeFix(e.Fixed), + Version: grypeDB.AffectedVersion{ + Type: rangeType, + Constraint: normalizeConstraint(constraint, rangeType), + }, + }) + // Reset the constraint + constraint = "" + } + } + + // Check if there's an event that "introduced" but never had a "fixed" or "last affected" event + if constraint != "" { + ranges = append(ranges, grypeDB.AffectedRange{ + Version: grypeDB.AffectedVersion{ + Type: rangeType, + Constraint: normalizeConstraint(constraint, rangeType), + }, + }) + } + + return ranges +} + +func normalizeConstraint(constraint string, rangeType string) string { + if rangeType == "semver" { + return common.EnforceSemVerConstraint(constraint) + } + return constraint +} + +func normalizeFix(fix string) *grypeDB.Fix { + fixedInVersion := common.CleanFixedInVersion(fix) + fixState := grypeDB.NotFixedStatus + if len(fixedInVersion) > 0 { + fixState = grypeDB.FixedStatus + } + + return &grypeDB.Fix{ + State: fixState, + Version: fixedInVersion, + } +} + +func normalizeRangeType(t models.RangeType) string { + switch t { + case models.RangeSemVer, models.RangeEcosystem, models.RangeGit: + return strings.ToLower(string(t)) + default: + return "unknown" + } +} + +func getPackage(p models.Package) *grypeDB.Package { + return &grypeDB.Package{ + Ecosystem: string(p.Ecosystem), + Name: name.Normalize(p.Name, pkg.TypeFromPURL(p.Purl)), + } +} + +func getReferences(vuln unmarshal.OSVVulnerability) []grypeDB.Reference { + var refs []grypeDB.Reference + for _, ref := range vuln.References { + refs = append(refs, + grypeDB.Reference{ + URL: ref.URL, + Tags: []string{string(ref.Type)}, + }, + ) + } + + return refs +} + +// extractCVSSInfo extracts the CVSS version and vector from the CVSS string +func extractCVSSInfo(cvss string) (string, string, error) { + re := regexp.MustCompile(`^CVSS:(\d+\.\d+)/(.+)$`) + matches := re.FindStringSubmatch(cvss) + + if len(matches) != 3 { + return "", "", fmt.Errorf("invalid CVSS format") + } + + return matches[1], matches[2], nil +} + +func normalizeSeverity(severity models.Severity) (grypeDB.Severity, error) { + switch severity.Type { + case models.SeverityCVSSV2, models.SeverityCVSSV3, models.SeverityCVSSV4: + version, vector, err := extractCVSSInfo(severity.Score) + if err != nil { + return grypeDB.Severity{}, err + } + + return grypeDB.Severity{ + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: vector, + Version: version, + }, + }, nil + default: + return grypeDB.Severity{ + Scheme: grypeDB.UnknownSeverityScheme, + Value: severity.Score, + }, nil + } +} + +func getSeverities(vuln unmarshal.OSVVulnerability) ([]grypeDB.Severity, error) { + var severities []grypeDB.Severity + for _, sev := range vuln.Severity { + severity, err := normalizeSeverity(sev) + if err != nil { + return nil, err + } + severities = append(severities, severity) + } + + for _, affected := range vuln.Affected { + for _, sev := range affected.Severity { + severity, err := normalizeSeverity(sev) + if err != nil { + return nil, err + } + severities = append(severities, severity) + } + } + + return severities, nil +} diff --git a/pkg/process/v6/transformers/osv/transform_test.go b/pkg/process/v6/transformers/osv/transform_test.go new file mode 100644 index 00000000..05264af1 --- /dev/null +++ b/pkg/process/v6/transformers/osv/transform_test.go @@ -0,0 +1,448 @@ +package osv + +import ( + "os" + "reflect" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/osv-scanner/pkg/models" + "github.com/stretchr/testify/require" + + "github.com/anchore/grype-db/pkg/process/v6/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() provider.State { + return provider.State{ + Provider: "osv", + Version: 12, + Processor: "vunnel@1.2.3", + Timestamp: timeVal, + Listing: &listing, + } +} + +func expectedProvider() *grypeDB.Provider { + return &grypeDB.Provider{ + ID: "osv", + Version: "12", + Processor: "vunnel@1.2.3", + DateCaptured: &timeVal, + InputDigest: "sha256:123456", + } +} + +func loadFixture(t *testing.T, fixturePath string) []unmarshal.OSVVulnerability { + t.Helper() + + f, err := os.Open(fixturePath) + require.NoError(t, err) + defer tests.CloseFile(f) + + entries, err := unmarshal.OSVVulnerabilityEntries(f) + require.NoError(t, err) + return entries +} + +func affectedPkgSlice(a ...grypeDB.AffectedPackageHandle) []any { + var r []any + for _, v := range a { + r = append(r, v) + } + return r +} + +func TestTransform(t *testing.T) { + tests := []struct { + name string + fixturePath string + want []transformers.RelatedEntries + }{ + { + name: "Apache 2020-11984", + fixturePath: "test-fixtures/BIT-apache-2020-11984.json", + want: []transformers.RelatedEntries{{ + VulnerabilityHandle: &grypeDB.VulnerabilityHandle{ + Name: "BIT-apache-2020-11984", + Status: grypeDB.VulnerabilityActive, + ProviderID: "osv", + Provider: expectedProvider(), + ModifiedDate: &[]time.Time{time.Date(2025, time.January, 17, 15, 26, 01, 971000000, time.UTC)}[0], + PublishedDate: &[]time.Time{time.Date(2024, time.March, 6, 10, 57, 57, 770000000, time.UTC)}[0], + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "BIT-apache-2020-11984", + Description: "Apache HTTP server 2.4.32 to 2.4.44 mod_proxy_uwsgi info disclosure and possible RCE", + References: []grypeDB.Reference{{ + URL: "http://www.openwall.com/lists/oss-security/2020/08/08/1", + Tags: []string{"WEB"}, + }, { + URL: "http://www.openwall.com/lists/oss-security/2020/08/08/10", + Tags: []string{"WEB"}, + }}, + Aliases: []string{"CVE-2020-11984"}, + Severities: []grypeDB.Severity{{ + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: "AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + Version: "3.1", + }, + }}, + }, + }, + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + Package: &grypeDB.Package{ + Name: "apache", + Ecosystem: "Bitnami", + }, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2020-11984"}, + Ranges: []grypeDB.AffectedRange{{ + Version: grypeDB.AffectedVersion{ + Type: "semver", + Constraint: ">=2.4.32,<=2.4.43", + }, + }}, + }, + }, + ), + }}, + }, + { + name: "Node 2020-8201", + fixturePath: "test-fixtures/BIT-node-2020-8201.json", + want: []transformers.RelatedEntries{{ + VulnerabilityHandle: &grypeDB.VulnerabilityHandle{ + Name: "BIT-node-2020-8201", + Status: grypeDB.VulnerabilityActive, + ProviderID: "osv", + Provider: expectedProvider(), + ModifiedDate: &[]time.Time{time.Date(2024, time.March, 6, 11, 25, 28, 861000000, time.UTC)}[0], + PublishedDate: &[]time.Time{time.Date(2024, time.March, 6, 11, 8, 9, 371000000, time.UTC)}[0], + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "BIT-node-2020-8201", + Description: "Node.js < 12.18.4 and < 14.11 can be exploited to perform HTTP desync attacks and deliver malicious payloads to unsuspecting users. The payloads can be crafted by an attacker to hijack user sessions, poison cookies, perform clickjacking, and a multitude of other attacks depending on the architecture of the underlying system. The attack was possible due to a bug in processing of carrier-return symbols in the HTTP header names.", + References: []grypeDB.Reference{{ + URL: "https://nodejs.org/en/blog/vulnerability/september-2020-security-releases/", + Tags: []string{"WEB"}, + }, { + URL: "https://security.gentoo.org/glsa/202101-07", + Tags: []string{"WEB"}, + }}, + Aliases: []string{"CVE-2020-8201"}, + Severities: []grypeDB.Severity{{ + Scheme: grypeDB.SeveritySchemeCVSS, + Value: grypeDB.CVSSSeverity{ + Vector: "AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N", + Version: "3.1", + }, + }}, + }, + }, + Related: affectedPkgSlice( + grypeDB.AffectedPackageHandle{ + Package: &grypeDB.Package{ + Name: "node", + Ecosystem: "Bitnami", + }, + BlobValue: &grypeDB.AffectedPackageBlob{ + CVEs: []string{"CVE-2020-8201"}, + Ranges: []grypeDB.AffectedRange{{ + Version: grypeDB.AffectedVersion{ + Type: "semver", + Constraint: ">=12.0.0,<12.18.4", + }, + Fix: &grypeDB.Fix{ + Version: "12.18.4", + State: grypeDB.FixedStatus, + }, + }, { + Version: grypeDB.AffectedVersion{ + Type: "semver", + Constraint: ">=14.0.0,<14.11.0", + }, + Fix: &grypeDB.Fix{ + Version: "14.11.0", + State: grypeDB.FixedStatus, + }, + }}, + }, + }, + ), + }}, + }, + } + t.Parallel() + for _, testToRun := range tests { + test := testToRun + t.Run(test.name, func(tt *testing.T) { + tt.Parallel() + vulns := loadFixture(t, test.fixturePath) + var actual []transformers.RelatedEntries + for _, vuln := range vulns { + entries, err := Transform(vuln, inputProviderState()) + 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 Test_getGrypeRangesFromRange(t *testing.T) { + tests := []struct { + name string + rnge models.Range + want []grypeDB.AffectedRange + }{ + { + name: "single range with 'fixed' status", + rnge: models.Range{ + Type: models.RangeSemVer, + Events: []models.Event{{ + Introduced: "0.0.1", + }, { + Fixed: "0.0.5", + }}, + }, + want: []grypeDB.AffectedRange{{ + Version: grypeDB.AffectedVersion{ + Type: "semver", + Constraint: ">=0.0.1,<0.0.5", + }, + Fix: &grypeDB.Fix{ + Version: "0.0.5", + State: grypeDB.FixedStatus, + }, + }}, + }, + { + name: "single range with 'last affected' status", + rnge: models.Range{ + Type: models.RangeSemVer, + Events: []models.Event{{ + Introduced: "0.0.1", + }, { + LastAffected: "0.0.5", + }}, + }, + want: []grypeDB.AffectedRange{{ + Version: grypeDB.AffectedVersion{ + Type: "semver", + Constraint: ">=0.0.1,<=0.0.5", + }, + }}, + }, + { + name: "single range with no 'fixed' or 'last affected' status", + rnge: models.Range{ + Type: models.RangeSemVer, + Events: []models.Event{{ + Introduced: "0.0.1", + }}, + }, + want: []grypeDB.AffectedRange{{ + Version: grypeDB.AffectedVersion{ + Type: "semver", + Constraint: ">=0.0.1", + }, + }}, + }, + { + name: "single range introduced with '0'", + rnge: models.Range{ + Type: models.RangeSemVer, + Events: []models.Event{{ + Introduced: "0", + }, { + LastAffected: "0.0.5", + }}, + }, + want: []grypeDB.AffectedRange{{ + Version: grypeDB.AffectedVersion{ + Type: "semver", + Constraint: "<=0.0.5", + }, + }}, + }, + { + name: "multiple ranges", + rnge: models.Range{ + Type: models.RangeSemVer, + Events: []models.Event{{ + Introduced: "0.0.1", + }, { + Fixed: "0.0.5", + }, { + Introduced: "1.0.1", + }, { + Fixed: "1.0.5", + }}, + }, + want: []grypeDB.AffectedRange{{ + Version: grypeDB.AffectedVersion{ + Type: "semver", + Constraint: ">=0.0.1,<0.0.5", + }, + Fix: &grypeDB.Fix{ + Version: "0.0.5", + State: grypeDB.FixedStatus, + }, + }, { + Version: grypeDB.AffectedVersion{ + Type: "semver", + Constraint: ">=1.0.1,<1.0.5", + }, + Fix: &grypeDB.Fix{ + Version: "1.0.5", + State: grypeDB.FixedStatus, + }, + }, + }, + }, + } + t.Parallel() + for _, testToRun := range tests { + test := testToRun + t.Run(test.name, func(tt *testing.T) { + tt.Parallel() + if got := getGrypeRangesFromRange(test.rnge); !reflect.DeepEqual(got, test.want) { + t.Errorf("getGrypeRangesFromRange() = %v, want %v", got, test.want) + } + }) + } +} + +func Test_getPackage(t *testing.T) { + tests := []struct { + name string + pkg models.Package + want *grypeDB.Package + }{ + { + name: "valid package", + pkg: models.Package{ + Ecosystem: "Bitnami", + Name: "apache", + Purl: "pkg:bitnami/apache", + }, + want: &grypeDB.Package{ + Name: "apache", + Ecosystem: "Bitnami", + }, + }, + { + name: "package with empty purl", + pkg: models.Package{ + Ecosystem: "Bitnami", + Name: "apache", + Purl: "", + }, + want: &grypeDB.Package{ + Name: "apache", + Ecosystem: "Bitnami", + }, + }, + { + name: "package with empty ecosystem", + pkg: models.Package{ + Ecosystem: "", + Name: "apache", + Purl: "pkg:bitnami/apache", + }, + want: &grypeDB.Package{ + Name: "apache", + Ecosystem: "", + }, + }, + } + t.Parallel() + for _, testToRun := range tests { + test := testToRun + t.Run(test.name, func(tt *testing.T) { + tt.Parallel() + got := getPackage(test.pkg) + if got.Name != test.want.Name { + t.Errorf("getPackage() got name = %v, want %v", got.Name, test.want.Name) + } + if got.Ecosystem != test.want.Ecosystem { + t.Errorf("getPackage() got ecosystem = %v, want %v", got.Ecosystem, test.want.Ecosystem) + } + }) + } +} + +func Test_extractCVSSInfo(t *testing.T) { + tests := []struct { + name string + cvss string + wantVersion string + wantVector string + wantErr bool + }{ + { + name: "valid cvss", + cvss: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + wantVersion: "3.1", + wantVector: "AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + wantErr: false, + }, + { + name: "invalid cvss", + cvss: "foo:3.1/bar", + wantVersion: "", + wantVector: "", + wantErr: true, + }, + { + name: "empty cvss", + cvss: "", + wantVersion: "", + wantVector: "", + wantErr: true, + }, + { + name: "invalid cvss version", + cvss: "CVSS:foo/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + wantVersion: "", + wantVector: "", + wantErr: true, + }, + } + t.Parallel() + for _, testToRun := range tests { + test := testToRun + t.Run(test.name, func(tt *testing.T) { + tt.Parallel() + gotVersion, gotVector, err := extractCVSSInfo(test.cvss) + if (err != nil) != test.wantErr { + t.Errorf("extractCVSSInfo() error = %v, wantErr %v", err, test.wantErr) + return + } + if gotVersion != test.wantVersion { + t.Errorf("extractCVSSInfo() got version = %v, want %v", gotVersion, test.wantVersion) + } + if gotVector != test.wantVector { + t.Errorf("extractCVSSInfo() got vector = %v, want %v", gotVector, test.wantVector) + } + }) + } +} diff --git a/pkg/process/v6/writer.go b/pkg/process/v6/writer.go new file mode 100644 index 00000000..3c77b4dc --- /dev/null +++ b/pkg/process/v6/writer.go @@ -0,0 +1,213 @@ +package v6 + +import ( + "fmt" + "strings" + "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 + providerCache map[string]grypeDB.Provider + states provider.States + severityCache map[string]grypeDB.Severity +} + +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(), + providerCache: make(map[string]grypeDB.Provider), + store: s, + states: states, + severityCache: make(map[string]grypeDB.Severity), + }, nil +} + +func (w writer) Write(entries ...data.Entry) error { + 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 { + log.WithFields("entry", entry.String()).Trace("writing entry") + + if entry.VulnerabilityHandle != nil { + w.fillInMissingSeverity(entry.VulnerabilityHandle) + + if err := w.store.AddVulnerabilities(entry.VulnerabilityHandle); err != nil { + return fmt.Errorf("unable to write vulnerability to store: %w", err) + } + } + + if entry.Provider != nil { + if err := w.store.AddProvider(*entry.Provider); err != nil { + return fmt.Errorf("unable to write provider to store: %w", err) + } + } + + for i := range entry.Related { + related := entry.Related[i] + switch row := related.(type) { + case grypeDB.AffectedPackageHandle: + if entry.VulnerabilityHandle != nil { + row.VulnerabilityID = entry.VulnerabilityHandle.ID + } else { + log.WithFields("package", row.Package).Warn("affected package entry does not have a vulnerability ID") + } + if err := w.store.AddAffectedPackages(&row); err != nil { + return fmt.Errorf("unable to write affected-package to store: %w", err) + } + case grypeDB.AffectedCPEHandle: + if entry.VulnerabilityHandle != nil { + row.VulnerabilityID = entry.VulnerabilityHandle.ID + } else { + log.WithFields("cpe", row.CPE).Warn("affected CPE entry does not have a vulnerability ID") + } + if err := w.store.AddAffectedCPEs(&row); err != nil { + return fmt.Errorf("unable to write affected-cpe to store: %w", err) + } + case grypeDB.KnownExploitedVulnerabilityHandle: + if err := w.store.AddKnownExploitedVulnerabilities(&row); err != nil { + return fmt.Errorf("unable to write known exploited vulnerability to store: %w", err) + } + case grypeDB.EpssHandle: + if err := w.store.AddEpss(&row); err != nil { + return fmt.Errorf("unable to write EPSS to store: %w", err) + } + default: + return fmt.Errorf("data entry is not of type vulnerability, vulnerability metadata, or exclusion: %T", row) + } + } + + return nil +} + +// fillInMissingSeverity will add a severity entry to the vulnerability record if it is missing, empty, or "unknown". +// The upstream NVD record is used to fill in these missing values. Note that the NVD provider is always guaranteed +// to be processed first before other providers. +func (w *writer) fillInMissingSeverity(handle *grypeDB.VulnerabilityHandle) { + if handle == nil { + return + } + + blob := handle.BlobValue + if blob == nil { + return + } + + id := strings.ToLower(blob.ID) + isCVE := strings.HasPrefix(id, "cve-") + if strings.ToLower(handle.ProviderID) == "nvd" && isCVE { + if len(blob.Severities) > 0 { + w.severityCache[id] = blob.Severities[0] + } + return + } + + if !isCVE { + return + } + + // parse all string severities and remove all unknown values + sevs := filterUnknownSeverities(blob.Severities) + + topSevStr := "none" + if len(sevs) > 0 { + switch v := sevs[0].Value.(type) { + case string: + topSevStr = v + case fmt.Stringer: + topSevStr = v.String() + default: + topSevStr = fmt.Sprintf("%v", sevs[0].Value) + } + } + + if len(sevs) > 0 { + return // already has a severity, don't normalize + } + + // add the top NVD severity value + nvdSev, ok := w.severityCache[id] + if !ok { + log.WithFields("id", blob.ID).Trace("unable to find NVD severity") + return + } + + log.WithFields("id", blob.ID, "provider", handle.Provider, "sev-from", topSevStr, "sev-to", nvdSev).Trace("overriding irrelevant severity with data from NVD record") + sevs = append([]grypeDB.Severity{nvdSev}, sevs...) + handle.BlobValue.Severities = sevs +} + +func filterUnknownSeverities(sevs []grypeDB.Severity) []grypeDB.Severity { + var out []grypeDB.Severity + for _, s := range sevs { + if isKnownSeverity(s) { + out = append(out, s) + } + } + return out +} + +func isKnownSeverity(s grypeDB.Severity) bool { + switch v := s.Value.(type) { + case string: + return v != "" && strings.ToLower(v) != "unknown" + default: + return v != 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/process/v6/writer_test.go b/pkg/process/v6/writer_test.go new file mode 100644 index 00000000..bd9cdd05 --- /dev/null +++ b/pkg/process/v6/writer_test.go @@ -0,0 +1,233 @@ +package v6 + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + + grypeDB "github.com/anchore/grype/grype/db/v6" +) + +func TestFillInMissingSeverity(t *testing.T) { + tests := []struct { + name string + handle *grypeDB.VulnerabilityHandle + severityCache map[string]grypeDB.Severity + expected []grypeDB.Severity + expectCacheUpdate bool + }{ + { + name: "nil handle", + handle: nil, + severityCache: map[string]grypeDB.Severity{}, + expected: nil, + }, + { + name: "nil metadata", + handle: &grypeDB.VulnerabilityHandle{ + BlobValue: nil, + }, + severityCache: map[string]grypeDB.Severity{}, + expected: nil, + }, + { + name: "non-CVE ID", + handle: &grypeDB.VulnerabilityHandle{ + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "GHSA-123", + Severities: []grypeDB.Severity{ + {Value: "high"}, + }, + }, + }, + severityCache: map[string]grypeDB.Severity{}, + expected: []grypeDB.Severity{{Value: "high"}}, + }, + { + name: "NVD provider with CVE", + handle: &grypeDB.VulnerabilityHandle{ + ProviderID: "nvd", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2023-1234", + Severities: []grypeDB.Severity{ + {Value: "critical"}, + }, + }, + }, + severityCache: map[string]grypeDB.Severity{}, + expected: []grypeDB.Severity{{Value: "critical"}}, + expectCacheUpdate: true, + }, + { + name: "CVE with existing severities", + handle: &grypeDB.VulnerabilityHandle{ + ProviderID: "github", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2023-5678", + Severities: []grypeDB.Severity{ + {Value: "medium"}, + {Value: "high"}, + }, + }, + }, + severityCache: map[string]grypeDB.Severity{ + "cve-2023-5678": {Value: "critical"}, + }, + expected: []grypeDB.Severity{ + {Value: "medium"}, + {Value: "high"}, + }, + }, + { + name: "CVE with no severities, using cache", + handle: &grypeDB.VulnerabilityHandle{ + ProviderID: "github", + BlobValue: &grypeDB.VulnerabilityBlob{ + ID: "CVE-2023-9012", + Severities: []grypeDB.Severity{}, + }, + }, + severityCache: map[string]grypeDB.Severity{ + "cve-2023-9012": {Value: "high"}, + }, + expected: []grypeDB.Severity{{Value: "high"}}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + w := &writer{ + severityCache: tt.severityCache, + } + + if tt.expectCacheUpdate { + // assert expected ids are not in the cache + if tt.handle != nil && tt.handle.BlobValue != nil { + assert.NotContains(t, tt.severityCache, strings.ToLower(tt.handle.BlobValue.ID)) + } + } + + w.fillInMissingSeverity(tt.handle) + + if tt.handle == nil || tt.handle.BlobValue == nil { + return + } + + if tt.expectCacheUpdate { + // assert expected ids are not in the cache + if tt.handle != nil && tt.handle.BlobValue != nil { + id := strings.ToLower(tt.handle.BlobValue.ID) + assert.Equal(t, tt.severityCache[id], w.severityCache[id]) + } + } + + assert.Equal(t, tt.expected, tt.handle.BlobValue.Severities) + }) + } +} + +func TestFilterUnknownSeverities(t *testing.T) { + tests := []struct { + name string + input []grypeDB.Severity + expected []grypeDB.Severity + }{ + { + name: "empty input", + input: []grypeDB.Severity{}, + expected: nil, + }, + { + name: "all known severities", + input: []grypeDB.Severity{ + {Value: "critical"}, + {Value: "high"}, + {Value: "medium"}, + }, + expected: []grypeDB.Severity{ + {Value: "critical"}, + {Value: "high"}, + {Value: "medium"}, + }, + }, + { + name: "mix of known and unknown", + input: []grypeDB.Severity{ + {Value: "high"}, + {Value: "unknown"}, + {Value: "medium"}, + {Value: ""}, + }, + expected: []grypeDB.Severity{ + {Value: "high"}, + {Value: "medium"}, + }, + }, + { + name: "non-string values", + input: []grypeDB.Severity{ + {Value: 5}, + {Value: nil}, + {Value: "high"}, + }, + expected: []grypeDB.Severity{ + {Value: 5}, + {Value: "high"}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := filterUnknownSeverities(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestIsKnownSeverity(t *testing.T) { + tests := []struct { + name string + severity grypeDB.Severity + expected bool + }{ + { + name: "empty string", + severity: grypeDB.Severity{Value: ""}, + expected: false, + }, + { + name: "unknown string", + severity: grypeDB.Severity{Value: "unknown"}, + expected: false, + }, + { + name: "case insensitive", + severity: grypeDB.Severity{Value: "UNKNOWN"}, + expected: false, + }, + { + name: "valid string severity", + severity: grypeDB.Severity{Value: "high"}, + expected: true, + }, + { + name: "nil value", + severity: grypeDB.Severity{Value: nil}, + expected: false, + }, + { + name: "numeric value", + severity: grypeDB.Severity{Value: 7}, + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := isKnownSeverity(tt.severity) + assert.Equal(t, tt.expected, result) + }) + } +} diff --git a/pkg/provider/entry/sqlite.go b/pkg/provider/entry/sqlite.go index 0b50f496..c827c782 100644 --- a/pkg/provider/entry/sqlite.go +++ b/pkg/provider/entry/sqlite.go @@ -88,7 +88,7 @@ func sqliteOpeners(resultPaths []string) (<-chan Opener, int64, error) { var models []results current := 0 - check := db.FindInBatches(&models, 100, func(tx *gorm.DB, batch int) error { + check := db.FindInBatches(&models, 100, func(_ *gorm.DB, _ int) error { for _, result := range models { openers <- bytesOpener{ contents: result.Record, @@ -98,7 +98,7 @@ func sqliteOpeners(resultPaths []string) (<-chan Opener, int64, error) { current += len(models) - log.WithFields("count", current).Trace("records read from the provider cache DB") + // log.WithFields("count", current).Trace("records read from the provider cache DB") // note: returning an error will stop future batches return nil diff --git a/pkg/provider/state.go b/pkg/provider/state.go index 44693df8..1a04842d 100644 --- a/pkg/provider/state.go +++ b/pkg/provider/state.go @@ -18,15 +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"` - 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 { @@ -130,3 +134,38 @@ func (s States) Names() []string { } return names } + +func (s States) EarliestTimestamp() (time.Time, error) { + if len(s) == 0 { + return time.Time{}, fmt.Errorf("cannot find earliest timestamp: no states provided") + } + + // special case when there is exactly 1 state, return its timestamp even + // if it is nvd, because otherwise quality gates that pull only nvd deterministically fail. + if len(s) == 1 { + return s[0].Timestamp, nil + } + + var earliest time.Time + for _, curState := range s { + // the NVD api is constantly down, so we don't want to consider it for the earliest timestamp + if curState.Provider == "nvd" { + log.WithFields("provider", curState.Provider).Debug("not considering data age for provider") + continue + } + if earliest.IsZero() { + earliest = curState.Timestamp + continue + } + if curState.Timestamp.Before(earliest) { + earliest = curState.Timestamp + } + } + + if earliest.IsZero() { + return time.Time{}, fmt.Errorf("unable to determine earliest timestamp") + } + + log.WithFields("timestamp", earliest).Debug("earliest data timestamp") + return earliest, nil +} diff --git a/pkg/provider/state_test.go b/pkg/provider/state_test.go new file mode 100644 index 00000000..7544860c --- /dev/null +++ b/pkg/provider/state_test.go @@ -0,0 +1,130 @@ +package provider + +import ( + "reflect" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func Test_earliestTimestamp(t *testing.T) { + tests := []struct { + name string + states []State + want time.Time + wantErr require.ErrorAssertionFunc + }{ + { + name: "happy path", + states: []State{ + { + Timestamp: time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC), + }, + { + Timestamp: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + }, + { + Timestamp: time.Date(2021, 1, 3, 0, 0, 0, 0, time.UTC), + }, + }, + want: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + }, + { + name: "empty states", + states: []State{}, + want: time.Time{}, + wantErr: requireErrorContains("cannot find earliest timestamp: no states provided"), + }, + { + name: "single state", + states: []State{ + { + Timestamp: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + }, + }, + want: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + }, + { + name: "single state, but it's nvd", + states: []State{ + { + Provider: "nvd", + Timestamp: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + }, + }, + want: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + }, + { + name: "all states have provider nvd", + states: []State{ + { + Provider: "nvd", + Timestamp: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + }, + { + Provider: "nvd", + Timestamp: time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC), + }, + }, + want: time.Time{}, + wantErr: requireErrorContains("unable to determine earliest timestamp"), + }, + { + name: "mix of nvd and non-nvd providers", + states: []State{ + { + Provider: "nvd", + Timestamp: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + }, + { + Provider: "other", + Timestamp: time.Date(2021, 1, 3, 0, 0, 0, 0, time.UTC), + }, + { + Provider: "other", + Timestamp: time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC), + }, + }, + want: time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC), + }, + { + name: "timestamps are the same", + states: []State{ + { + Timestamp: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + }, + { + Timestamp: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + }, + { + Timestamp: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + }, + }, + want: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantErr == nil { + tt.wantErr = require.NoError + } + got, err := States(tt.states).EarliestTimestamp() + tt.wantErr(t, err) + if err != nil { + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("earliestTimestamp() = %v, want %v", got, tt.want) + } + }) + } +} + +func requireErrorContains(text string) require.ErrorAssertionFunc { + return func(t require.TestingT, err error, msgAndArgs ...interface{}) { + require.Error(t, err, msgAndArgs...) + require.Contains(t, err.Error(), text, msgAndArgs...) + } +} diff --git a/pkg/provider/unmarshal/epss.go b/pkg/provider/unmarshal/epss.go new file mode 100644 index 00000000..1579dbb7 --- /dev/null +++ b/pkg/provider/unmarshal/epss.go @@ -0,0 +1,18 @@ +package unmarshal + +import "io" + +type EPSS struct { + CVE string `json:"cve"` + EPSS float64 `json:"epss"` + Percentile float64 `json:"percentile"` + Date string `json:"date"` +} + +func (o EPSS) IsEmpty() bool { + return o.CVE == "" +} + +func EPSSEntries(reader io.Reader) ([]EPSS, error) { + return unmarshalSingleOrMulti[EPSS](reader) +} 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/known_exploited_vulnerability.go b/pkg/provider/unmarshal/known_exploited_vulnerability.go new file mode 100644 index 00000000..1f5f0c43 --- /dev/null +++ b/pkg/provider/unmarshal/known_exploited_vulnerability.go @@ -0,0 +1,25 @@ +package unmarshal + +import "io" + +type KnownExploitedVulnerability struct { + CveID string `json:"cveID"` + VendorProject string `json:"vendorProject"` + Product string `json:"product"` + VulnerabilityName string `json:"vulnerabilityName"` + DateAdded string `json:"dateAdded"` + ShortDescription string `json:"shortDescription"` + RequiredAction string `json:"requiredAction"` + DueDate string `json:"dueDate"` + KnownRansomwareCampaignUse string `json:"knownRansomwareCampaignUse"` + Notes string `json:"notes"` + CWEs []string `json:"cwes"` +} + +func (g KnownExploitedVulnerability) IsEmpty() bool { + return g.CveID == "" +} + +func KnownExploitedVulnerabilityEntries(reader io.Reader) ([]KnownExploitedVulnerability, error) { + return unmarshalSingleOrMulti[KnownExploitedVulnerability](reader) +} diff --git a/pkg/provider/unmarshal/nvd/cve.go b/pkg/provider/unmarshal/nvd/cve.go index 8c478487..49efd5ea 100644 --- a/pkg/provider/unmarshal/nvd/cve.go +++ b/pkg/provider/unmarshal/nvd/cve.go @@ -11,6 +11,7 @@ import ( "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd/cvss20" "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd/cvss30" "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd/cvss31" + "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd/cvss40" ) // note: this was autogenerated with some manual tweaking (see schema/nvd/cve-api-json/README.md) @@ -50,11 +51,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,11 +88,12 @@ 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. CvssMetricV31 []CvssV31 `json:"cvssMetricV31,omitempty"` // CVSS V3.1 score. + CvssMetricV40 []CvssV40 `json:"cvssMetricV40,omitempty"` // CVSS V4.1 score. } type CvssV2 struct { @@ -124,10 +126,18 @@ 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." +type CvssV40 struct { + CvssData cvss40.Cvss40 `json:"cvssData"` + ExploitabilityScore *float64 `json:"exploitabilityScore,omitempty"` + ImpactScore *float64 `json:"impactScore,omitempty"` + Source string `json:"source"` + Type CvssType `json:"type"` +} + +// 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 ( @@ -197,16 +207,24 @@ func (o CvssSummaries) Len() int { func (o CvssSummaries) Less(i, j int) bool { iEntry := o[i] jEntry := o[j] - iV := iEntry.version() - jV := jEntry.version() - if iV == jV { - if iEntry.Type == Primary && jEntry.Type == Secondary { + + // first compare by type (Primary/Secondary) + if iEntry.Type != jEntry.Type { + return iEntry.Type == Secondary + } + + // prefer NVD as primary source + if iEntry.Source != jEntry.Source { + if iEntry.Source == "nvd@nist.gov" { return false - } else if iEntry.Type == Secondary && jEntry.Type == Primary { + } else if jEntry.Source == "nvd@nist.gov" { return true } - return false } + + // if types are the same, then compare by version + iV := iEntry.version() + jV := jEntry.version() return iV.LessThan(jV) } @@ -285,6 +303,22 @@ func (o CveItem) CVSS() []CvssSummary { ) } + for _, c := range o.Metrics.CvssMetricV40 { + sev := string(c.CvssData.BaseSeverity) + results = append(results, + CvssSummary{ + Source: c.Source, + Type: c.Type, + Version: c.CvssData.Version, + Vector: c.CvssData.VectorString, + BaseScore: c.CvssData.BaseScore, + ExploitabilityScore: c.ExploitabilityScore, + ImpactScore: c.ImpactScore, + baseSeverity: &sev, + }, + ) + } + return results } diff --git a/pkg/provider/unmarshal/nvd/cve_test.go b/pkg/provider/unmarshal/nvd/cve_test.go new file mode 100644 index 00000000..f64e6877 --- /dev/null +++ b/pkg/provider/unmarshal/nvd/cve_test.go @@ -0,0 +1,184 @@ +package nvd + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" +) + +func TestCvssSummariesSorted(t *testing.T) { + tests := []struct { + name string + input CvssSummaries + expected CvssSummaries + }{ + { + name: "primary types sorted by version descending", + input: CvssSummaries{ + {Type: Primary, Version: "2.0", Source: "A"}, + {Type: Primary, Version: "3.1", Source: "B"}, + {Type: Primary, Version: "3.0", Source: "C"}, + {Type: Primary, Version: "4.0", Source: "D"}, + }, + expected: CvssSummaries{ + {Type: Primary, Version: "4.0", Source: "D"}, + {Type: Primary, Version: "3.1", Source: "B"}, + {Type: Primary, Version: "3.0", Source: "C"}, + {Type: Primary, Version: "2.0", Source: "A"}, + }, + }, + { + name: "secondary types sorted by version descending", + input: CvssSummaries{ + {Type: Secondary, Version: "2.0", Source: "D"}, + {Type: Secondary, Version: "3.1", Source: "E"}, + {Type: Secondary, Version: "3.0", Source: "F"}, + }, + expected: CvssSummaries{ + {Type: Secondary, Version: "3.1", Source: "E"}, + {Type: Secondary, Version: "3.0", Source: "F"}, + {Type: Secondary, Version: "2.0", Source: "D"}, + }, + }, + { + name: "primary types before secondary types", + input: CvssSummaries{ + {Type: Secondary, Version: "3.1", Source: "G"}, + {Type: Primary, Version: "2.0", Source: "H"}, + {Type: Secondary, Version: "2.0", Source: "I"}, + {Type: Primary, Version: "3.0", Source: "J"}, + }, + expected: CvssSummaries{ + {Type: Primary, Version: "3.0", Source: "J"}, + {Type: Primary, Version: "2.0", Source: "H"}, + {Type: Secondary, Version: "3.1", Source: "G"}, + {Type: Secondary, Version: "2.0", Source: "I"}, + }, + }, + { + name: "mix of versions and types", + input: CvssSummaries{ + {Type: Secondary, Version: "3.1", Source: "K"}, + {Type: Primary, Version: "3.1", Source: "L"}, + {Type: Primary, Version: "2.0", Source: "M"}, + {Type: Secondary, Version: "2.0", Source: "N"}, + {Type: Primary, Version: "3.0", Source: "O"}, + {Type: Secondary, Version: "3.0", Source: "P"}, + }, + expected: CvssSummaries{ + {Type: Primary, Version: "3.1", Source: "L"}, + {Type: Primary, Version: "3.0", Source: "O"}, + {Type: Primary, Version: "2.0", Source: "M"}, + {Type: Secondary, Version: "3.1", Source: "K"}, + {Type: Secondary, Version: "3.0", Source: "P"}, + {Type: Secondary, Version: "2.0", Source: "N"}, + }, + }, + { + name: "nvd source preferred within same type and version", + input: CvssSummaries{ + {Type: Primary, Version: "3.0", Source: "random-source"}, + {Type: Primary, Version: "3.0", Source: "nvd@nist.gov"}, + }, + expected: CvssSummaries{ + {Type: Primary, Version: "3.0", Source: "nvd@nist.gov"}, + {Type: Primary, Version: "3.0", Source: "random-source"}, + }, + }, + { + name: "nvd source preferred but type takes precedence", + input: CvssSummaries{ + {Type: Secondary, Version: "3.0", Source: "nvd@nist.gov"}, + {Type: Primary, Version: "3.0", Source: "random-source"}, + }, + expected: CvssSummaries{ + {Type: Primary, Version: "3.0", Source: "random-source"}, + {Type: Secondary, Version: "3.0", Source: "nvd@nist.gov"}, + }, + }, + { + name: "multiple nvd sources sorted by version", + input: CvssSummaries{ + {Type: Primary, Version: "2.0", Source: "nvd@nist.gov"}, + {Type: Primary, Version: "3.1", Source: "nvd@nist.gov"}, + {Type: Primary, Version: "3.0", Source: "nvd@nist.gov"}, + }, + expected: CvssSummaries{ + {Type: Primary, Version: "3.1", Source: "nvd@nist.gov"}, + {Type: Primary, Version: "3.0", Source: "nvd@nist.gov"}, + {Type: Primary, Version: "2.0", Source: "nvd@nist.gov"}, + }, + }, + { + name: "complex sorting with types, versions, and sources", + input: CvssSummaries{ + {Type: Secondary, Version: "3.1", Source: "nvd@nist.gov"}, + {Type: Primary, Version: "2.0", Source: "random-source"}, + {Type: Primary, Version: "3.0", Source: "nvd@nist.gov"}, + {Type: Primary, Version: "3.0", Source: "other-source"}, + {Type: Secondary, Version: "2.0", Source: "other-source"}, + {Type: Secondary, Version: "3.0", Source: "nvd@nist.gov"}, + }, + expected: CvssSummaries{ + {Type: Primary, Version: "3.0", Source: "nvd@nist.gov"}, + {Type: Primary, Version: "3.0", Source: "other-source"}, + {Type: Primary, Version: "2.0", Source: "random-source"}, + {Type: Secondary, Version: "3.1", Source: "nvd@nist.gov"}, + {Type: Secondary, Version: "3.0", Source: "nvd@nist.gov"}, + {Type: Secondary, Version: "2.0", Source: "other-source"}, + }, + }, + { + name: "empty input", + input: CvssSummaries{}, + expected: CvssSummaries{}, + }, + { + name: "invalid version handling", + input: CvssSummaries{ + {Type: Primary, Version: "invalid", Source: "Q"}, + {Type: Primary, Version: "3.0", Source: "R"}, + }, + expected: CvssSummaries{ + {Type: Primary, Version: "3.0", Source: "R"}, + {Type: Primary, Version: "invalid", Source: "Q"}, // should use default "2.0" + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := tc.input.Sorted() + + if d := cmp.Diff(tc.expected, result, cmpopts.IgnoreUnexported(CvssSummary{})); d != "" { + t.Errorf("unexpected result (-want +got):\n%s", d) + } + }) + } +} + +func TestCvssSummaryVersion(t *testing.T) { + tests := []struct { + input string + expected string + }{ + {"4.0", "4.0.0"}, + {"3.1", "3.1.0"}, + {"3.0", "3.0.0"}, + {"2.0", "2.0.0"}, + {"invalid", "2.0.0"}, // default to 2.0 for invalid versions + {"3.1.5", "3.1.5"}, + {"", "2.0.0"}, // empty string is invalid + } + + for _, tc := range tests { + t.Run(tc.input, func(t *testing.T) { + summary := CvssSummary{Version: tc.input} + version := summary.version() + if version.String() != tc.expected { + t.Errorf("Expected version %s, got %s", tc.expected, version.String()) + } + }) + } +} diff --git a/pkg/provider/unmarshal/nvd/cvss40/cvss40.go b/pkg/provider/unmarshal/nvd/cvss40/cvss40.go new file mode 100644 index 00000000..7b2bd1ce --- /dev/null +++ b/pkg/provider/unmarshal/nvd/cvss40/cvss40.go @@ -0,0 +1,52 @@ +package cvss40 + +// note: this was autogenerated with some manual tweaking + +type Cvss40 struct { + Version string `json:"version"` + VectorString string `json:"vectorString"` + BaseScore float64 `json:"baseScore"` + BaseSeverity SeverityType `json:"baseSeverity"` + // AttackVector string `json:"attackVector"` + // AttackComplexity string `json:"attackComplexity"` + // AttackRequirements string `json:"attackRequirements"` + // PrivilegesRequired string `json:"privilegesRequired"` + // UserInteraction string `json:"userInteraction"` + // VulnConfidentialityImpact string `json:"vulnConfidentialityImpact"` + // VulnIntegrityImpact string `json:"vulnIntegrityImpact"` + // VulnAvailabilityImpact string `json:"vulnAvailabilityImpact"` + // SubConfidentialityImpact string `json:"subConfidentialityImpact"` + // SubIntegrityImpact string `json:"subIntegrityImpact"` + // SubAvailabilityImpact string `json:"subAvailabilityImpact"` + // ExploitMaturity string `json:"exploitMaturity"` + // ConfidentialityRequirement string `json:"confidentialityRequirement"` + // IntegrityRequirement string `json:"integrityRequirement"` + // AvailabilityRequirement string `json:"availabilityRequirement"` + // ModifiedAttackVector string `json:"modifiedAttackVector"` + // ModifiedAttackComplexity string `json:"modifiedAttackComplexity"` + // ModifiedAttackRequirements string `json:"modifiedAttackRequirements"` + // ModifiedPrivilegesRequired string `json:"modifiedPrivilegesRequired"` + // ModifiedUserInteraction string `json:"modifiedUserInteraction"` + // ModifiedVulnConfidentialityImpact string `json:"modifiedVulnConfidentialityImpact"` + // ModifiedVulnIntegrityImpact string `json:"modifiedVulnIntegrityImpact"` + // ModifiedVulnAvailabilityImpact string `json:"modifiedVulnAvailabilityImpact"` + // ModifiedSubConfidentialityImpact string `json:"modifiedSubConfidentialityImpact"` + // ModifiedSubIntegrityImpact string `json:"modifiedSubIntegrityImpact"` + // ModifiedSubAvailabilityImpact string `json:"modifiedSubAvailabilityImpact"` + // Safety string `json:"Safety"` + // Automatable string `json:"Automatable"` + // Recovery string `json:"Recovery"` + // ValueDensity string `json:"valueDensity"` + // VulnerabilityResponseEffort string `json:"vulnerabilityResponseEffort"` + // ProviderUrgency string `json:"providerUrgency"` +} + +type SeverityType string + +const ( + SeverityTypeCritical SeverityType = "CRITICAL" + SeverityTypeHIGH SeverityType = "HIGH" + SeverityTypeLOW SeverityType = "LOW" + SeverityTypeMEDIUM SeverityType = "MEDIUM" + SeverityTypeNONE SeverityType = "NONE" +) diff --git a/pkg/provider/unmarshal/os_vulnerability.go b/pkg/provider/unmarshal/os_vulnerability.go index 8dec9f52..dd846082 100644 --- a/pkg/provider/unmarshal/os_vulnerability.go +++ b/pkg/provider/unmarshal/os_vulnerability.go @@ -21,8 +21,9 @@ type OSFixedIn struct { } `json:"AdvisorySummary"` NoAdvisory bool `json:"NoAdvisory"` } `json:"VendorAdvisory"` - Version string `json:"Version"` - VersionFormat string `json:"VersionFormat"` + Version string `json:"Version"` + VersionFormat string `json:"VersionFormat"` + VulnerableRange string `json:"VulnerableRange"` } type OSFixedIns []OSFixedIn diff --git a/pkg/provider/unmarshal/osv_vulnerability.go b/pkg/provider/unmarshal/osv_vulnerability.go new file mode 100644 index 00000000..0fa38e21 --- /dev/null +++ b/pkg/provider/unmarshal/osv_vulnerability.go @@ -0,0 +1,13 @@ +package unmarshal + +import ( + "io" + + "github.com/google/osv-scanner/pkg/models" +) + +type OSVVulnerability = models.Vulnerability + +func OSVVulnerabilityEntries(reader io.Reader) ([]OSVVulnerability, error) { + return unmarshalSingleOrMulti[OSVVulnerability](reader) +} diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index d42ab325..00000000 --- a/poetry.lock +++ /dev/null @@ -1,2177 +0,0 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. - -[[package]] -name = "black" -version = "23.7.0" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.8" -files = [ - {file = "black-23.7.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587"}, - {file = "black-23.7.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f"}, - {file = "black-23.7.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be"}, - {file = "black-23.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc"}, - {file = "black-23.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995"}, - {file = "black-23.7.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2"}, - {file = "black-23.7.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd"}, - {file = "black-23.7.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a"}, - {file = "black-23.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831d8f54c3a8c8cf55f64d0422ee875eecac26f5f649fb6c1df65316b67c8926"}, - {file = "black-23.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad"}, - {file = "black-23.7.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f"}, - {file = "black-23.7.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3"}, - {file = "black-23.7.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6"}, - {file = "black-23.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1c6022b86f83b632d06f2b02774134def5d4d4f1dac8bef16d90cda18ba28a"}, - {file = "black-23.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320"}, - {file = "black-23.7.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9"}, - {file = "black-23.7.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3"}, - {file = "black-23.7.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087"}, - {file = "black-23.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:642496b675095d423f9b8448243336f8ec71c9d4d57ec17bf795b67f08132a91"}, - {file = "black-23.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491"}, - {file = "black-23.7.0-py3-none-any.whl", hash = "sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96"}, - {file = "black-23.7.0.tar.gz", hash = "sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - -[[package]] -name = "boto3" -version = "1.28.16" -description = "The AWS SDK for Python" -optional = false -python-versions = ">= 3.7" -files = [ - {file = "boto3-1.28.16-py3-none-any.whl", hash = "sha256:d8e31f69fb919025a5961f8fbeb51fe92e2f753beb37fc1853138667a231cdaa"}, - {file = "boto3-1.28.16.tar.gz", hash = "sha256:aea48aedf3e8676e598e3202e732295064a4fcad5f2d2d2a699368b8c3ab492c"}, -] - -[package.dependencies] -botocore = ">=1.31.16,<1.32.0" -jmespath = ">=0.7.1,<2.0.0" -s3transfer = ">=0.6.0,<0.7.0" - -[package.extras] -crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] - -[[package]] -name = "botocore" -version = "1.31.16" -description = "Low-level, data-driven core of boto 3." -optional = false -python-versions = ">= 3.7" -files = [ - {file = "botocore-1.31.16-py3-none-any.whl", hash = "sha256:92b240e2cb7b3afae5361651d2f48ee582f45d2dab53aef76eef7eec1d3ce582"}, - {file = "botocore-1.31.16.tar.gz", hash = "sha256:563e15979e763b93d78de58d0fc065f8615be12f41bab42f5ad9f412b6a224b3"}, -] - -[package.dependencies] -jmespath = ">=0.7.1,<2.0.0" -python-dateutil = ">=2.1,<3.0.0" -urllib3 = ">=1.25.4,<1.27" - -[package.extras] -crt = ["awscrt (==0.16.26)"] - -[[package]] -name = "certifi" -version = "2023.7.22" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -files = [ - {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, - {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, -] - -[[package]] -name = "cffi" -version = "1.15.1" -description = "Foreign Function Interface for Python calling C code." -optional = false -python-versions = "*" -files = [ - {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, - {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, - {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, - {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, - {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, - {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, - {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, - {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, - {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, - {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, - {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, - {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, - {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, - {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, - {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, - {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, - {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, - {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, - {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, -] - -[package.dependencies] -pycparser = "*" - -[[package]] -name = "charset-normalizer" -version = "3.2.0" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, - {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, -] - -[[package]] -name = "click" -version = "8.1.6" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.7" -files = [ - {file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"}, - {file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "click-default-group" -version = "1.2.4" -description = "click_default_group" -optional = false -python-versions = ">=2.7" -files = [ - {file = "click_default_group-1.2.4-py2.py3-none-any.whl", hash = "sha256:9b60486923720e7fc61731bdb32b617039aba820e22e1c88766b1125592eaa5f"}, - {file = "click_default_group-1.2.4.tar.gz", hash = "sha256:eb3f3c99ec0d456ca6cd2a7f08f7d4e91771bef51b01bdd9580cc6450fe1251e"}, -] - -[package.dependencies] -click = "*" - -[package.extras] -test = ["pytest"] - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "colorlog" -version = "6.7.0" -description = "Add colours to the output of Python's logging module." -optional = false -python-versions = ">=3.6" -files = [ - {file = "colorlog-6.7.0-py2.py3-none-any.whl", hash = "sha256:0d33ca236784a1ba3ff9c532d4964126d8a2c44f1f0cb1d2b0728196f512f662"}, - {file = "colorlog-6.7.0.tar.gz", hash = "sha256:bd94bd21c1e13fac7bd3153f4bc3a7dc0eb0974b8bc2fdf1a989e474f6e582e5"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} - -[package.extras] -development = ["black", "flake8", "mypy", "pytest", "types-colorama"] - -[[package]] -name = "colr" -version = "0.9.1" -description = "Easy terminal colors, with chainable methods.\n" -optional = false -python-versions = "*" -files = [ - {file = "Colr-0.9.1.tar.gz", hash = "sha256:8c15437eeb2ec8821c6df24b62946dfc6b79f69a1d84c1a6c131945a5ff4623c"}, -] - -[[package]] -name = "coverage" -version = "7.2.7" -description = "Code coverage measurement for Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, - {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, - {file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, - {file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, - {file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, - {file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, - {file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, - {file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, - {file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, - {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, - {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, - {file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, - {file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, - {file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, - {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, - {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, - {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, - {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, - {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, - {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, - {file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, - {file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, - {file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, - {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, -] - -[package.dependencies] -tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} - -[package.extras] -toml = ["tomli"] - -[[package]] -name = "cryptography" -version = "41.0.6" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -optional = false -python-versions = ">=3.7" -files = [ - {file = "cryptography-41.0.6-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:0f27acb55a4e77b9be8d550d762b0513ef3fc658cd3eb15110ebbcbd626db12c"}, - {file = "cryptography-41.0.6-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:ae236bb8760c1e55b7a39b6d4d32d2279bc6c7c8500b7d5a13b6fb9fc97be35b"}, - {file = "cryptography-41.0.6-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afda76d84b053923c27ede5edc1ed7d53e3c9f475ebaf63c68e69f1403c405a8"}, - {file = "cryptography-41.0.6-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da46e2b5df770070412c46f87bac0849b8d685c5f2679771de277a422c7d0b86"}, - {file = "cryptography-41.0.6-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ff369dd19e8fe0528b02e8df9f2aeb2479f89b1270d90f96a63500afe9af5cae"}, - {file = "cryptography-41.0.6-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b648fe2a45e426aaee684ddca2632f62ec4613ef362f4d681a9a6283d10e079d"}, - {file = "cryptography-41.0.6-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5daeb18e7886a358064a68dbcaf441c036cbdb7da52ae744e7b9207b04d3908c"}, - {file = "cryptography-41.0.6-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:068bc551698c234742c40049e46840843f3d98ad7ce265fd2bd4ec0d11306596"}, - {file = "cryptography-41.0.6-cp37-abi3-win32.whl", hash = "sha256:2132d5865eea673fe6712c2ed5fb4fa49dba10768bb4cc798345748380ee3660"}, - {file = "cryptography-41.0.6-cp37-abi3-win_amd64.whl", hash = "sha256:48783b7e2bef51224020efb61b42704207dde583d7e371ef8fc2a5fb6c0aabc7"}, - {file = "cryptography-41.0.6-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:8efb2af8d4ba9dbc9c9dd8f04d19a7abb5b49eab1f3694e7b5a16a5fc2856f5c"}, - {file = "cryptography-41.0.6-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c5a550dc7a3b50b116323e3d376241829fd326ac47bc195e04eb33a8170902a9"}, - {file = "cryptography-41.0.6-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:85abd057699b98fce40b41737afb234fef05c67e116f6f3650782c10862c43da"}, - {file = "cryptography-41.0.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f39812f70fc5c71a15aa3c97b2bbe213c3f2a460b79bd21c40d033bb34a9bf36"}, - {file = "cryptography-41.0.6-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:742ae5e9a2310e9dade7932f9576606836ed174da3c7d26bc3d3ab4bd49b9f65"}, - {file = "cryptography-41.0.6-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:35f3f288e83c3f6f10752467c48919a7a94b7d88cc00b0668372a0d2ad4f8ead"}, - {file = "cryptography-41.0.6-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4d03186af98b1c01a4eda396b137f29e4e3fb0173e30f885e27acec8823c1b09"}, - {file = "cryptography-41.0.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b27a7fd4229abef715e064269d98a7e2909ebf92eb6912a9603c7e14c181928c"}, - {file = "cryptography-41.0.6-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:398ae1fc711b5eb78e977daa3cbf47cec20f2c08c5da129b7a296055fbb22aed"}, - {file = "cryptography-41.0.6-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7e00fb556bda398b99b0da289ce7053639d33b572847181d6483ad89835115f6"}, - {file = "cryptography-41.0.6-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:60e746b11b937911dc70d164060d28d273e31853bb359e2b2033c9e93e6f3c43"}, - {file = "cryptography-41.0.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3288acccef021e3c3c10d58933f44e8602cf04dba96d9796d70d537bb2f4bbc4"}, - {file = "cryptography-41.0.6.tar.gz", hash = "sha256:422e3e31d63743855e43e5a6fcc8b4acab860f560f9321b0ee6269cc7ed70cc3"}, -] - -[package.dependencies] -cffi = ">=1.12" - -[package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] -docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] -nox = ["nox"] -pep8test = ["black", "check-sdist", "mypy", "ruff"] -sdist = ["build"] -ssh = ["bcrypt (>=3.1.5)"] -test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] -test-randomorder = ["pytest-randomly"] - -[[package]] -name = "cvss" -version = "2.6" -description = "CVSS2/3 library with interactive calculator for Python 2 and Python 3" -optional = false -python-versions = "*" -files = [ - {file = "cvss-2.6-py2.py3-none-any.whl", hash = "sha256:7006a025e1587f94fc7ce6bd2f3fa10b27a85d2f843d72d4346122779919c63b"}, - {file = "cvss-2.6.tar.gz", hash = "sha256:1e8f0c7ac1c1d7f4fb6d901950aa216358809de25ee7c41bc138615a23936c80"}, -] - -[[package]] -name = "dataclass-wizard" -version = "0.22.2" -description = "Marshal dataclasses to/from JSON. Use field properties with initial values. Construct a dataclass schema with JSON input." -optional = false -python-versions = "*" -files = [ - {file = "dataclass-wizard-0.22.2.tar.gz", hash = "sha256:211f842e5e9a8ace50ba891ef428cd78c82579fb98024f80f3e630ca8d1946f6"}, - {file = "dataclass_wizard-0.22.2-py2.py3-none-any.whl", hash = "sha256:49be36ecc64bc5a1e9a35a6bad1d71d33b6b9b06877404931a17c6a3a6dfbb10"}, -] - -[package.extras] -timedelta = ["pytimeparse (>=1.1.7)"] -yaml = ["PyYAML (>=5.3)"] - -[[package]] -name = "dataclasses-json" -version = "0.5.9" -description = "Easily serialize dataclasses to and from JSON" -optional = false -python-versions = ">=3.6" -files = [ - {file = "dataclasses-json-0.5.9.tar.gz", hash = "sha256:e9ac87b73edc0141aafbce02b44e93553c3123ad574958f0fe52a534b6707e8e"}, - {file = "dataclasses_json-0.5.9-py3-none-any.whl", hash = "sha256:1280542631df1c375b7bc92e5b86d39e06c44760d7e3571a537b3b8acabf2f0c"}, -] - -[package.dependencies] -marshmallow = ">=3.3.0,<4.0.0" -marshmallow-enum = ">=1.5.1,<2.0.0" -typing-inspect = ">=0.4.0" - -[package.extras] -dev = ["flake8", "hypothesis", "ipython", "mypy (>=0.710)", "portray", "pytest (>=7.2.0)", "setuptools", "simplejson", "twine", "types-dataclasses", "wheel"] - -[[package]] -name = "defusedxml" -version = "0.7.1" -description = "XML bomb protection for Python stdlib modules" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, - {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, -] - -[[package]] -name = "dict" -version = "2020.12.3" -description = "dict replacement" -optional = false -python-versions = "*" -files = [ - {file = "dict-2020.12.3.tar.gz", hash = "sha256:8a14ff62e2a9dff5ef751a2e5eee07715c79ddab6b9ca06c59e73622f80761b5"}, -] - -[package.dependencies] -values = "*" - -[[package]] -name = "docformatter" -version = "1.7.5" -description = "Formats docstrings to follow PEP 257" -optional = false -python-versions = ">=3.7,<4.0" -files = [ - {file = "docformatter-1.7.5-py3-none-any.whl", hash = "sha256:a24f5545ed1f30af00d106f5d85dc2fce4959295687c24c8f39f5263afaf9186"}, - {file = "docformatter-1.7.5.tar.gz", hash = "sha256:ffed3da0daffa2e77f80ccba4f0e50bfa2755e1c10e130102571c890a61b246e"}, -] - -[package.dependencies] -charset_normalizer = ">=3.0.0,<4.0.0" -untokenize = ">=0.1.1,<0.2.0" - -[package.extras] -tomli = ["tomli (>=2.0.0,<3.0.0)"] - -[[package]] -name = "exceptiongroup" -version = "1.1.2" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.1.2-py3-none-any.whl", hash = "sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"}, - {file = "exceptiongroup-1.1.2.tar.gz", hash = "sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5"}, -] - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "execnet" -version = "2.0.2" -description = "execnet: rapid multi-Python deployment" -optional = false -python-versions = ">=3.7" -files = [ - {file = "execnet-2.0.2-py3-none-any.whl", hash = "sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41"}, - {file = "execnet-2.0.2.tar.gz", hash = "sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af"}, -] - -[package.extras] -testing = ["hatch", "pre-commit", "pytest", "tox"] - -[[package]] -name = "future" -version = "0.18.3" -description = "Clean single-source support for Python 3 and 2" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "future-0.18.3.tar.gz", hash = "sha256:34a17436ed1e96697a86f9de3d15a3b0be01d8bc8de9c1dffd59fb8234ed5307"}, -] - -[[package]] -name = "gitdb" -version = "4.0.10" -description = "Git Object Database" -optional = false -python-versions = ">=3.7" -files = [ - {file = "gitdb-4.0.10-py3-none-any.whl", hash = "sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7"}, - {file = "gitdb-4.0.10.tar.gz", hash = "sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a"}, -] - -[package.dependencies] -smmap = ">=3.0.1,<6" - -[[package]] -name = "gitpython" -version = "3.1.41" -description = "GitPython is a Python library used to interact with Git repositories" -optional = false -python-versions = ">=3.7" -files = [ - {file = "GitPython-3.1.41-py3-none-any.whl", hash = "sha256:c36b6634d069b3f719610175020a9aed919421c87552185b085e04fbbdb10b7c"}, - {file = "GitPython-3.1.41.tar.gz", hash = "sha256:ed66e624884f76df22c8e16066d567aaa5a37d5b5fa19db2c6df6f7156db9048"}, -] - -[package.dependencies] -gitdb = ">=4.0.1,<5" - -[package.extras] -test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "sumtypes"] - -[[package]] -name = "greenlet" -version = "2.0.2" -description = "Lightweight in-process concurrent programming" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" -files = [ - {file = "greenlet-2.0.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d"}, - {file = "greenlet-2.0.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9"}, - {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"}, - {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"}, - {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"}, - {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d967650d3f56af314b72df7089d96cda1083a7fc2da05b375d2bc48c82ab3f3c"}, - {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470"}, - {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a"}, - {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"}, - {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"}, - {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"}, - {file = "greenlet-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d4606a527e30548153be1a9f155f4e283d109ffba663a15856089fb55f933e47"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"}, - {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19"}, - {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3"}, - {file = "greenlet-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5"}, - {file = "greenlet-2.0.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6"}, - {file = "greenlet-2.0.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43"}, - {file = "greenlet-2.0.2-cp35-cp35m-win32.whl", hash = "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a"}, - {file = "greenlet-2.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394"}, - {file = "greenlet-2.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75"}, - {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf"}, - {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292"}, - {file = "greenlet-2.0.2-cp36-cp36m-win32.whl", hash = "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9"}, - {file = "greenlet-2.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f"}, - {file = "greenlet-2.0.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73"}, - {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86"}, - {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33"}, - {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"}, - {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"}, - {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"}, - {file = "greenlet-2.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1087300cf9700bbf455b1b97e24db18f2f77b55302a68272c56209d5587c12d1"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857"}, - {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a"}, - {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"}, - {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"}, - {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"}, - {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8512a0c38cfd4e66a858ddd1b17705587900dd760c6003998e9472b77b56d417"}, - {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b"}, - {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8"}, - {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9"}, - {file = "greenlet-2.0.2-cp39-cp39-win32.whl", hash = "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5"}, - {file = "greenlet-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564"}, - {file = "greenlet-2.0.2.tar.gz", hash = "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0"}, -] - -[package.extras] -docs = ["Sphinx", "docutils (<0.18)"] -test = ["objgraph", "psutil"] - -[[package]] -name = "has-flag" -version = "0.1.1" -description = "Check if argv has a specific flag" -optional = false -python-versions = ">=3.6,<4.0" -files = [ - {file = "has-flag-0.1.1.tar.gz", hash = "sha256:2ade83150630ce9058d4ab07c57f65c10e6d894e8c90a2275b14a9185a3bc533"}, - {file = "has_flag-0.1.1-py3-none-any.whl", hash = "sha256:7db35c8fff4d58c9e483868dc1271387e1e838d545ab9734eddeeb8b5378f2ac"}, -] - -[[package]] -name = "idna" -version = "3.4" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.5" -files = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, -] - -[[package]] -name = "ijson" -version = "2.6.1" -description = "Iterative JSON parser with a standard Python iterator interface" -optional = false -python-versions = "*" -files = [ - {file = "ijson-2.6.1-cp27-cp27m-macosx_10_6_x86_64.whl", hash = "sha256:60393946d73792d5adeeaa25e82ff2f5bf19b17f6617a468743a4db4a07298a0"}, - {file = "ijson-2.6.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:d320dc1c1c9adbe404668b0fed6bfa0ac8693911159564f4655a5f2059746993"}, - {file = "ijson-2.6.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:a4cd7f8ecf035d0e23db1cc6d6036e6c563f31abacbceae88904bb8b7f88b1f6"}, - {file = "ijson-2.6.1-cp34-cp34m-macosx_10_6_x86_64.whl", hash = "sha256:9904bf55bc1f170353c32144861d8295f0bdc41034e5e6ae58cbf30610023ca6"}, - {file = "ijson-2.6.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:7bac04b23691e6ab122d8f9ff06b26dbbb6df01babbf6bf8856ccad1c505278b"}, - {file = "ijson-2.6.1-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:ae9cc3ebbe8fa030b923b5dff912a61980edd03dc00b92f5c0223e44cbc51d9f"}, - {file = "ijson-2.6.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:8ce67b7d3435c3fc831d5c06f60b2d20a853b599cdf885478e575a3416fbf655"}, - {file = "ijson-2.6.1-cp36-cp36m-macosx_10_6_x86_64.whl", hash = "sha256:a8b486bdf24e389947e588f4021498f6cc56cafdfaec1c78e9952e0f338aef23"}, - {file = "ijson-2.6.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:c0042bb768fb890c177af923c0ead157cdc70c6dfa64827765c1a3676a879190"}, - {file = "ijson-2.6.1-cp37-cp37m-macosx_10_6_x86_64.whl", hash = "sha256:26978c02314233c87bddad8800b7b9a56a052334f495e2bce93b282397c6931d"}, - {file = "ijson-2.6.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:25d4d159405f75a7443c1fe83b6d7be5a7da017b4aa9cc1bb5cda3feb74aaf32"}, - {file = "ijson-2.6.1.tar.gz", hash = "sha256:75ebc60b23abfb1c97f475ab5d07a5ed725ad4bd1f58513d8b258c21f02703d0"}, -] - -[[package]] -name = "importlib-metadata" -version = "6.8.0" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, - {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, -] - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - -[[package]] -name = "iso8601" -version = "2.0.0" -description = "Simple module to parse ISO 8601 dates" -optional = false -python-versions = ">=3.7,<4.0" -files = [ - {file = "iso8601-2.0.0-py3-none-any.whl", hash = "sha256:ebe10061b932edb8a8e33cc635d661926c59b9c3bed7a4f4edca8c62d400af10"}, - {file = "iso8601-2.0.0.tar.gz", hash = "sha256:739960d37c74c77bd9bd546a76562ccb581fe3d4820ff5c3141eb49c839fda8f"}, -] - -[[package]] -name = "jinja2" -version = "3.1.3" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -files = [ - {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, - {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "jmespath" -version = "1.0.1" -description = "JSON Matching Expressions" -optional = false -python-versions = ">=3.7" -files = [ - {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, - {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, -] - -[[package]] -name = "lxml" -version = "4.9.3" -description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" -files = [ - {file = "lxml-4.9.3-cp27-cp27m-macosx_11_0_x86_64.whl", hash = "sha256:b0a545b46b526d418eb91754565ba5b63b1c0b12f9bd2f808c852d9b4b2f9b5c"}, - {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:075b731ddd9e7f68ad24c635374211376aa05a281673ede86cbe1d1b3455279d"}, - {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1e224d5755dba2f4a9498e150c43792392ac9b5380aa1b845f98a1618c94eeef"}, - {file = "lxml-4.9.3-cp27-cp27m-win32.whl", hash = "sha256:2c74524e179f2ad6d2a4f7caf70e2d96639c0954c943ad601a9e146c76408ed7"}, - {file = "lxml-4.9.3-cp27-cp27m-win_amd64.whl", hash = "sha256:4f1026bc732b6a7f96369f7bfe1a4f2290fb34dce00d8644bc3036fb351a4ca1"}, - {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0781a98ff5e6586926293e59480b64ddd46282953203c76ae15dbbbf302e8bb"}, - {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cef2502e7e8a96fe5ad686d60b49e1ab03e438bd9123987994528febd569868e"}, - {file = "lxml-4.9.3-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:b86164d2cff4d3aaa1f04a14685cbc072efd0b4f99ca5708b2ad1b9b5988a991"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:42871176e7896d5d45138f6d28751053c711ed4d48d8e30b498da155af39aebd"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ae8b9c6deb1e634ba4f1930eb67ef6e6bf6a44b6eb5ad605642b2d6d5ed9ce3c"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:411007c0d88188d9f621b11d252cce90c4a2d1a49db6c068e3c16422f306eab8"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:cd47b4a0d41d2afa3e58e5bf1f62069255aa2fd6ff5ee41604418ca925911d76"}, - {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e2cb47860da1f7e9a5256254b74ae331687b9672dfa780eed355c4c9c3dbd23"}, - {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1247694b26342a7bf47c02e513d32225ededd18045264d40758abeb3c838a51f"}, - {file = "lxml-4.9.3-cp310-cp310-win32.whl", hash = "sha256:cdb650fc86227eba20de1a29d4b2c1bfe139dc75a0669270033cb2ea3d391b85"}, - {file = "lxml-4.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:97047f0d25cd4bcae81f9ec9dc290ca3e15927c192df17331b53bebe0e3ff96d"}, - {file = "lxml-4.9.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:1f447ea5429b54f9582d4b955f5f1985f278ce5cf169f72eea8afd9502973dd5"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:57d6ba0ca2b0c462f339640d22882acc711de224d769edf29962b09f77129cbf"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:9767e79108424fb6c3edf8f81e6730666a50feb01a328f4a016464a5893f835a"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:71c52db65e4b56b8ddc5bb89fb2e66c558ed9d1a74a45ceb7dcb20c191c3df2f"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d73d8ecf8ecf10a3bd007f2192725a34bd62898e8da27eb9d32a58084f93962b"}, - {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0a3d3487f07c1d7f150894c238299934a2a074ef590b583103a45002035be120"}, - {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e28c51fa0ce5674be9f560c6761c1b441631901993f76700b1b30ca6c8378d6"}, - {file = "lxml-4.9.3-cp311-cp311-win32.whl", hash = "sha256:0bfd0767c5c1de2551a120673b72e5d4b628737cb05414f03c3277bf9bed3305"}, - {file = "lxml-4.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:25f32acefac14ef7bd53e4218fe93b804ef6f6b92ffdb4322bb6d49d94cad2bc"}, - {file = "lxml-4.9.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:d3ff32724f98fbbbfa9f49d82852b159e9784d6094983d9a8b7f2ddaebb063d4"}, - {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:48d6ed886b343d11493129e019da91d4039826794a3e3027321c56d9e71505be"}, - {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9a92d3faef50658dd2c5470af249985782bf754c4e18e15afb67d3ab06233f13"}, - {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b4e4bc18382088514ebde9328da057775055940a1f2e18f6ad2d78aa0f3ec5b9"}, - {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fc9b106a1bf918db68619fdcd6d5ad4f972fdd19c01d19bdb6bf63f3589a9ec5"}, - {file = "lxml-4.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:d37017287a7adb6ab77e1c5bee9bcf9660f90ff445042b790402a654d2ad81d8"}, - {file = "lxml-4.9.3-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:56dc1f1ebccc656d1b3ed288f11e27172a01503fc016bcabdcbc0978b19352b7"}, - {file = "lxml-4.9.3-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:578695735c5a3f51569810dfebd05dd6f888147a34f0f98d4bb27e92b76e05c2"}, - {file = "lxml-4.9.3-cp35-cp35m-win32.whl", hash = "sha256:704f61ba8c1283c71b16135caf697557f5ecf3e74d9e453233e4771d68a1f42d"}, - {file = "lxml-4.9.3-cp35-cp35m-win_amd64.whl", hash = "sha256:c41bfca0bd3532d53d16fd34d20806d5c2b1ace22a2f2e4c0008570bf2c58833"}, - {file = "lxml-4.9.3-cp36-cp36m-macosx_11_0_x86_64.whl", hash = "sha256:64f479d719dc9f4c813ad9bb6b28f8390360660b73b2e4beb4cb0ae7104f1c12"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:dd708cf4ee4408cf46a48b108fb9427bfa00b9b85812a9262b5c668af2533ea5"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c31c7462abdf8f2ac0577d9f05279727e698f97ecbb02f17939ea99ae8daa98"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e3cd95e10c2610c360154afdc2f1480aea394f4a4f1ea0a5eacce49640c9b190"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:4930be26af26ac545c3dffb662521d4e6268352866956672231887d18f0eaab2"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4aec80cde9197340bc353d2768e2a75f5f60bacda2bab72ab1dc499589b3878c"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:14e019fd83b831b2e61baed40cab76222139926b1fb5ed0e79225bc0cae14584"}, - {file = "lxml-4.9.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0c0850c8b02c298d3c7006b23e98249515ac57430e16a166873fc47a5d549287"}, - {file = "lxml-4.9.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:aca086dc5f9ef98c512bac8efea4483eb84abbf926eaeedf7b91479feb092458"}, - {file = "lxml-4.9.3-cp36-cp36m-win32.whl", hash = "sha256:50baa9c1c47efcaef189f31e3d00d697c6d4afda5c3cde0302d063492ff9b477"}, - {file = "lxml-4.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bef4e656f7d98aaa3486d2627e7d2df1157d7e88e7efd43a65aa5dd4714916cf"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:46f409a2d60f634fe550f7133ed30ad5321ae2e6630f13657fb9479506b00601"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:4c28a9144688aef80d6ea666c809b4b0e50010a2aca784c97f5e6bf143d9f129"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:141f1d1a9b663c679dc524af3ea1773e618907e96075262726c7612c02b149a4"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:53ace1c1fd5a74ef662f844a0413446c0629d151055340e9893da958a374f70d"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17a753023436a18e27dd7769e798ce302963c236bc4114ceee5b25c18c52c693"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7d298a1bd60c067ea75d9f684f5f3992c9d6766fadbc0bcedd39750bf344c2f4"}, - {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:081d32421db5df44c41b7f08a334a090a545c54ba977e47fd7cc2deece78809a"}, - {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:23eed6d7b1a3336ad92d8e39d4bfe09073c31bfe502f20ca5116b2a334f8ec02"}, - {file = "lxml-4.9.3-cp37-cp37m-win32.whl", hash = "sha256:1509dd12b773c02acd154582088820893109f6ca27ef7291b003d0e81666109f"}, - {file = "lxml-4.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:120fa9349a24c7043854c53cae8cec227e1f79195a7493e09e0c12e29f918e52"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4d2d1edbca80b510443f51afd8496be95529db04a509bc8faee49c7b0fb6d2cc"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8d7e43bd40f65f7d97ad8ef5c9b1778943d02f04febef12def25f7583d19baac"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:71d66ee82e7417828af6ecd7db817913cb0cf9d4e61aa0ac1fde0583d84358db"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:6fc3c450eaa0b56f815c7b62f2b7fba7266c4779adcf1cece9e6deb1de7305ce"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65299ea57d82fb91c7f019300d24050c4ddeb7c5a190e076b5f48a2b43d19c42"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:eadfbbbfb41b44034a4c757fd5d70baccd43296fb894dba0295606a7cf3124aa"}, - {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3e9bdd30efde2b9ccfa9cb5768ba04fe71b018a25ea093379c857c9dad262c40"}, - {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fcdd00edfd0a3001e0181eab3e63bd5c74ad3e67152c84f93f13769a40e073a7"}, - {file = "lxml-4.9.3-cp38-cp38-win32.whl", hash = "sha256:57aba1bbdf450b726d58b2aea5fe47c7875f5afb2c4a23784ed78f19a0462574"}, - {file = "lxml-4.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:92af161ecbdb2883c4593d5ed4815ea71b31fafd7fd05789b23100d081ecac96"}, - {file = "lxml-4.9.3-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:9bb6ad405121241e99a86efff22d3ef469024ce22875a7ae045896ad23ba2340"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8ed74706b26ad100433da4b9d807eae371efaa266ffc3e9191ea436087a9d6a7"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fbf521479bcac1e25a663df882c46a641a9bff6b56dc8b0fafaebd2f66fb231b"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:303bf1edce6ced16bf67a18a1cf8339d0db79577eec5d9a6d4a80f0fb10aa2da"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:5515edd2a6d1a5a70bfcdee23b42ec33425e405c5b351478ab7dc9347228f96e"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:690dafd0b187ed38583a648076865d8c229661ed20e48f2335d68e2cf7dc829d"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b6420a005548ad52154c8ceab4a1290ff78d757f9e5cbc68f8c77089acd3c432"}, - {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bb3bb49c7a6ad9d981d734ef7c7193bc349ac338776a0360cc671eaee89bcf69"}, - {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d27be7405547d1f958b60837dc4c1007da90b8b23f54ba1f8b728c78fdb19d50"}, - {file = "lxml-4.9.3-cp39-cp39-win32.whl", hash = "sha256:8df133a2ea5e74eef5e8fc6f19b9e085f758768a16e9877a60aec455ed2609b2"}, - {file = "lxml-4.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:4dd9a263e845a72eacb60d12401e37c616438ea2e5442885f65082c276dfb2b2"}, - {file = "lxml-4.9.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6689a3d7fd13dc687e9102a27e98ef33730ac4fe37795d5036d18b4d527abd35"}, - {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f6bdac493b949141b733c5345b6ba8f87a226029cbabc7e9e121a413e49441e0"}, - {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:05186a0f1346ae12553d66df1cfce6f251589fea3ad3da4f3ef4e34b2d58c6a3"}, - {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2006f5c8d28dee289f7020f721354362fa304acbaaf9745751ac4006650254b"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-macosx_11_0_x86_64.whl", hash = "sha256:5c245b783db29c4e4fbbbfc9c5a78be496c9fea25517f90606aa1f6b2b3d5f7b"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4fb960a632a49f2f089d522f70496640fdf1218f1243889da3822e0a9f5f3ba7"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:50670615eaf97227d5dc60de2dc99fb134a7130d310d783314e7724bf163f75d"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9719fe17307a9e814580af1f5c6e05ca593b12fb7e44fe62450a5384dbf61b4b"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3331bece23c9ee066e0fb3f96c61322b9e0f54d775fccefff4c38ca488de283a"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-macosx_11_0_x86_64.whl", hash = "sha256:ed667f49b11360951e201453fc3967344d0d0263aa415e1619e85ae7fd17b4e0"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8b77946fd508cbf0fccd8e400a7f71d4ac0e1595812e66025bac475a8e811694"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e4da8ca0c0c0aea88fd46be8e44bd49716772358d648cce45fe387f7b92374a7"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fe4bda6bd4340caa6e5cf95e73f8fea5c4bfc55763dd42f1b50a94c1b4a2fbd4"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f3df3db1d336b9356dd3112eae5f5c2b8b377f3bc826848567f10bfddfee77e9"}, - {file = "lxml-4.9.3.tar.gz", hash = "sha256:48628bd53a426c9eb9bc066a923acaa0878d1e86129fd5359aee99285f4eed9c"}, -] - -[package.extras] -cssselect = ["cssselect (>=0.7)"] -html5 = ["html5lib"] -htmlsoup = ["BeautifulSoup4"] -source = ["Cython (>=0.29.35)"] - -[[package]] -name = "markupsafe" -version = "2.1.3" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.7" -files = [ - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, - {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, -] - -[[package]] -name = "marshmallow" -version = "3.20.1" -description = "A lightweight library for converting complex datatypes to and from native Python datatypes." -optional = false -python-versions = ">=3.8" -files = [ - {file = "marshmallow-3.20.1-py3-none-any.whl", hash = "sha256:684939db93e80ad3561392f47be0230743131560a41c5110684c16e21ade0a5c"}, - {file = "marshmallow-3.20.1.tar.gz", hash = "sha256:5d2371bbe42000f2b3fb5eaa065224df7d8f8597bc19a1bbfa5bfe7fba8da889"}, -] - -[package.dependencies] -packaging = ">=17.0" - -[package.extras] -dev = ["flake8 (==6.0.0)", "flake8-bugbear (==23.7.10)", "mypy (==1.4.1)", "pre-commit (>=2.4,<4.0)", "pytest", "pytz", "simplejson", "tox"] -docs = ["alabaster (==0.7.13)", "autodocsumm (==0.2.11)", "sphinx (==7.0.1)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"] -lint = ["flake8 (==6.0.0)", "flake8-bugbear (==23.7.10)", "mypy (==1.4.1)", "pre-commit (>=2.4,<4.0)"] -tests = ["pytest", "pytz", "simplejson"] - -[[package]] -name = "marshmallow-enum" -version = "1.5.1" -description = "Enum field for Marshmallow" -optional = false -python-versions = "*" -files = [ - {file = "marshmallow-enum-1.5.1.tar.gz", hash = "sha256:38e697e11f45a8e64b4a1e664000897c659b60aa57bfa18d44e226a9920b6e58"}, - {file = "marshmallow_enum-1.5.1-py2.py3-none-any.whl", hash = "sha256:57161ab3dbfde4f57adeb12090f39592e992b9c86d206d02f6bd03ebec60f072"}, -] - -[package.dependencies] -marshmallow = ">=2.0.0" - -[[package]] -name = "mergedeep" -version = "1.3.4" -description = "A deep merge function for 🐍." -optional = false -python-versions = ">=3.6" -files = [ - {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, - {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, -] - -[[package]] -name = "moto" -version = "4.1.14" -description = "" -optional = false -python-versions = ">=3.7" -files = [ - {file = "moto-4.1.14-py2.py3-none-any.whl", hash = "sha256:7d3bd748a34641715ba469c761f72fb8ec18f349987c98f5a0f9be85a07a9911"}, - {file = "moto-4.1.14.tar.gz", hash = "sha256:545afeb4df94dfa730e2d7e87366dc26b4a33c2891f462cbb049f040c80ed1ec"}, -] - -[package.dependencies] -boto3 = ">=1.9.201" -botocore = ">=1.12.201" -cryptography = ">=3.3.1" -Jinja2 = ">=2.10.1" -python-dateutil = ">=2.1,<3.0.0" -requests = ">=2.5" -responses = ">=0.13.0" -werkzeug = ">=0.5,<2.2.0 || >2.2.0,<2.2.1 || >2.2.1" -xmltodict = "*" - -[package.extras] -all = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "ecdsa (!=0.15)", "graphql-core", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.2.8)", "py-partiql-parser (==0.3.6)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"] -apigateway = ["PyYAML (>=5.1)", "ecdsa (!=0.15)", "openapi-spec-validator (>=0.2.8)", "python-jose[cryptography] (>=3.1.0,<4.0.0)"] -apigatewayv2 = ["PyYAML (>=5.1)"] -appsync = ["graphql-core"] -awslambda = ["docker (>=3.0.0)"] -batch = ["docker (>=3.0.0)"] -cloudformation = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "ecdsa (!=0.15)", "graphql-core", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.2.8)", "py-partiql-parser (==0.3.6)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"] -cognitoidp = ["ecdsa (!=0.15)", "python-jose[cryptography] (>=3.1.0,<4.0.0)"] -ds = ["sshpubkeys (>=3.1.0)"] -dynamodb = ["docker (>=3.0.0)", "py-partiql-parser (==0.3.6)"] -dynamodbstreams = ["docker (>=3.0.0)", "py-partiql-parser (==0.3.6)"] -ebs = ["sshpubkeys (>=3.1.0)"] -ec2 = ["sshpubkeys (>=3.1.0)"] -efs = ["sshpubkeys (>=3.1.0)"] -eks = ["sshpubkeys (>=3.1.0)"] -glue = ["pyparsing (>=3.0.7)"] -iotdata = ["jsondiff (>=1.1.2)"] -route53resolver = ["sshpubkeys (>=3.1.0)"] -s3 = ["PyYAML (>=5.1)", "py-partiql-parser (==0.3.6)"] -s3crc32c = ["PyYAML (>=5.1)", "crc32c", "py-partiql-parser (==0.3.6)"] -server = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "ecdsa (!=0.15)", "flask (!=2.2.0,!=2.2.1)", "flask-cors", "graphql-core", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.2.8)", "py-partiql-parser (==0.3.6)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"] -ssm = ["PyYAML (>=5.1)"] -xray = ["aws-xray-sdk (>=0.93,!=0.96)", "setuptools"] - -[[package]] -name = "mypy" -version = "1.4.1" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"}, - {file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"}, - {file = "mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd"}, - {file = "mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc"}, - {file = "mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1"}, - {file = "mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462"}, - {file = "mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258"}, - {file = "mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2"}, - {file = "mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7"}, - {file = "mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01"}, - {file = "mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b"}, - {file = "mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"}, - {file = "mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7"}, - {file = "mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9"}, - {file = "mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042"}, - {file = "mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3"}, - {file = "mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6"}, - {file = "mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f"}, - {file = "mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc"}, - {file = "mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828"}, - {file = "mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3"}, - {file = "mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816"}, - {file = "mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c"}, - {file = "mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f"}, - {file = "mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4"}, - {file = "mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b"}, -] - -[package.dependencies] -mypy-extensions = ">=1.0.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=4.1.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -install-types = ["pip"] -python2 = ["typed-ast (>=1.4.0,<2)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - -[[package]] -name = "omitempty" -version = "0.1.1" -description = "enums for Python" -optional = false -python-versions = "*" -files = [ - {file = "omitempty-0.1.1.tar.gz", hash = "sha256:761fea43d0edb7a31e3322158f73c97d77e939e57c1e62754be23e081ab853d8"}, -] - -[[package]] -name = "orjson" -version = "3.9.4" -description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" -optional = false -python-versions = ">=3.7" -files = [ - {file = "orjson-3.9.4-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:2e83ec1ee66d83b558a6d273d8a01b86563daa60bea9bc040e2c1cb8008de61f"}, - {file = "orjson-3.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32a9e0f140c7d0d52f79553cabd1a471f6a4f187c59742239939f1139258a053"}, - {file = "orjson-3.9.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb429c56ea645e084e34976c2ea0efca7661ee961f61e51405f28bc5a9d1fb24"}, - {file = "orjson-3.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2fb7963c17ab347428412a0689f5c89ea480f5d5f7ba3e46c6c2f14f3159ee4"}, - {file = "orjson-3.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:224ad19dcdc21bb220d893807f2563e219319a8891ead3c54243b51a4882d767"}, - {file = "orjson-3.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4974cc2ebb53196081fef96743c02c8b073242b20a40b65d2aa2365ba8c949df"}, - {file = "orjson-3.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b39747f8e57728b9d8c26bd1d28e9a31c028717617a5938a179244b9436c0b31"}, - {file = "orjson-3.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0a31c2cab0ba86998205c2eba550c178a8b4ee7905cadeb402eed45392edb178"}, - {file = "orjson-3.9.4-cp310-none-win32.whl", hash = "sha256:04cd7f4a4f4cd2fe43d104eb70e7435c6fcbdde7aa0cde4230e444fbc66924d3"}, - {file = "orjson-3.9.4-cp310-none-win_amd64.whl", hash = "sha256:4fdb59cfa00e10c82e09d1c32a9ce08a38bd29496ba20a73cd7f498e3a0a5024"}, - {file = "orjson-3.9.4-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:daeed2502ddf1f2b29ec8da2fe2ea82807a5c4acf869608ce6c476db8171d070"}, - {file = "orjson-3.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73d9507a547202f0dd0672e529ce3ca45582d152369c684a9ce75677ce5ae089"}, - {file = "orjson-3.9.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:144a3b8c7cbdd301e1b8cd7dd33e3cbfe7b011df2bebd45b84bacc8cb490302d"}, - {file = "orjson-3.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ef7119ebc9b76d5e37c330596616c697d1957779c916aec30cefd28df808f796"}, - {file = "orjson-3.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b75f0fc7a64a95027c6f0c70f17969299bdf2b6a85e342b29fc23be2788bad6f"}, - {file = "orjson-3.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e4b20164809b21966b63e063f894927bc85391e60d0a96fa0bb552090f1319c"}, - {file = "orjson-3.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e7c3b7e29572ef2d845a59853475f40fdabec53b8b7d6effda4bb26119c07f5"}, - {file = "orjson-3.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d73c0fd54a52a1a1abfad69d4f1dfb7048cd0b3ef1828ddb4920ef2d3739d8fb"}, - {file = "orjson-3.9.4-cp311-none-win32.whl", hash = "sha256:e12492ce65cb10f385e70a88badc6046bc720fa7d468db27b7429d85d41beaeb"}, - {file = "orjson-3.9.4-cp311-none-win_amd64.whl", hash = "sha256:3b9f8bf43a5367d5522f80e7d533c98d880868cd0b640b9088c9237306eca6e8"}, - {file = "orjson-3.9.4-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:0b400cf89c15958cd829c8a4ade8f5dd73588e63d2fb71a00483e7a74e9f92da"}, - {file = "orjson-3.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d3b6f2706cb324661899901e6b1fcaee4f5aac7d7588306df3f43e68173840"}, - {file = "orjson-3.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3932b06abf49135c93816c74139c7937fa54079fce3f44db2d598859894c344a"}, - {file = "orjson-3.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:562cf24f9f11df8099e0e78859ba6729e7caa25c2f3947cb228d9152946c854b"}, - {file = "orjson-3.9.4-cp312-none-win_amd64.whl", hash = "sha256:a533e664a0e3904307d662c5d45775544dc2b38df6e39e213ff6a86ceaa3d53c"}, - {file = "orjson-3.9.4-cp37-cp37m-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:149d1b7630771222f73ecb024ab5dd8e7f41502402b02015494d429bacc4d5c1"}, - {file = "orjson-3.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:004f0d307473af210717260dab2ddceab26750ef5d2c6b1f7454c33f7bb69f0c"}, - {file = "orjson-3.9.4-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ba21fe581a83555024f3cfc9182a2390a61bc50430364855022c518b8ba285a4"}, - {file = "orjson-3.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1fb36efdf2a35286fb87cfaa195fc34621389da1c7b28a8eb51a4d212d60e56d"}, - {file = "orjson-3.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:644728d803200d7774164d252a247e2fcb0d19e4ef7a4a19a1a139ae472c551b"}, - {file = "orjson-3.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bae10f4e7a9145b120e37b6456f1d3853a953e5131fe4740a764e46420289f5"}, - {file = "orjson-3.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c416c50f63bfcf453b6e28d1df956938486191fd1a15aeb95107e810e6e219c8"}, - {file = "orjson-3.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:220ca4125416636a3d6b53a77d50434987a83da243f9080ee4cce7ac6a34bb4a"}, - {file = "orjson-3.9.4-cp37-none-win32.whl", hash = "sha256:bcda6179eb863c295eb5ea832676d33ef12c04d227b4c98267876c8322e5a96e"}, - {file = "orjson-3.9.4-cp37-none-win_amd64.whl", hash = "sha256:3d947366127abef192419257eb7db7fcee0841ced2b49ccceba43b65e9ce5e3f"}, - {file = "orjson-3.9.4-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a7d029fc34a516f7eae29b778b30371fcb621134b2acfe4c51c785102aefc6cf"}, - {file = "orjson-3.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c65df12f92e771361dca45765fcac3d97491799ee8ab3c6c5ecf0155a397a313"}, - {file = "orjson-3.9.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b749d06a3d84ac27311cb85fb5e8f965efd1c5f27556ad8fcfd1853c323b4d54"}, - {file = "orjson-3.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:161cc72dd3ff569fd67da4af3a23c0c837029085300f0cebc287586ae3b559e0"}, - {file = "orjson-3.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:edcbccfe852d1d3d56cc8bfc5fa3688c866619328a73cb2394e79b29b4ab24d2"}, - {file = "orjson-3.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0725260a12d7102b6e66f9925a027f55567255d8455f8288b02d5eedc8925c3e"}, - {file = "orjson-3.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:53b417cc9465dbb42ec9cd7be744a921a0ce583556315d172a246d6e71aa043b"}, - {file = "orjson-3.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9ab3720fba68cc1c0bad00803d2c5e2c70177da5af12c45e18cc4d14426d56d8"}, - {file = "orjson-3.9.4-cp38-none-win32.whl", hash = "sha256:94d15ee45c2aaed334688e511aa73b4681f7c08a0810884c6b3ae5824dea1222"}, - {file = "orjson-3.9.4-cp38-none-win_amd64.whl", hash = "sha256:336ec8471102851f0699198031924617b7a77baadea889df3ffda6000bd59f4c"}, - {file = "orjson-3.9.4-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:2f57ccb50e9e123709e9f2d7b1a9e09e694e49d1fa5c5585e34b8e3f01929dc3"}, - {file = "orjson-3.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e876ef36801b3d4d3a4b0613b6144b0b47f13f3043fd1fcdfafd783c174b538"}, - {file = "orjson-3.9.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f009c1a02773bdecdd1157036918fef1da47f7193d4ad599c9edb1e1960a0491"}, - {file = "orjson-3.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f0a4cf31bfa94cd235aa50030bef3df529e4eb2893ea6a7771c0fb087e4e53b2"}, - {file = "orjson-3.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c32dea3b27a97ac88783c1eb61ccb531865bf478a37df3707cbc96ca8f34a04"}, - {file = "orjson-3.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:264637cad35a1755ab90a8ea290076d444deda20753e55a0eb75496a4645f7bc"}, - {file = "orjson-3.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a4f12e9ec62679c3f2717d9ec41b497a2c2af0b1361229db0dc86ef078a4c034"}, - {file = "orjson-3.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c4fcd1ac0b7850f85398fd9fdbc7150ac4e82d2ae6754cc6acaf49ca7c30d79a"}, - {file = "orjson-3.9.4-cp39-none-win32.whl", hash = "sha256:b5b5038187b74e2d33e5caee8a7e83ddeb6a21da86837fa2aac95c69aeb366e6"}, - {file = "orjson-3.9.4-cp39-none-win_amd64.whl", hash = "sha256:915da36bc93ef0c659fa50fe7939d4f208804ad252fc4fc8d55adbbb82293c48"}, - {file = "orjson-3.9.4.tar.gz", hash = "sha256:a4c9254d21fc44526a3850355b89afd0d00ed73bdf902a5ab416df14a61eac6b"}, -] - -[[package]] -name = "packaging" -version = "23.1" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.7" -files = [ - {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, - {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, -] - -[[package]] -name = "pathspec" -version = "0.11.2" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.7" -files = [ - {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, - {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, -] - -[[package]] -name = "platformdirs" -version = "3.10.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -optional = false -python-versions = ">=3.7" -files = [ - {file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"}, - {file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"}, -] - -[package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] - -[[package]] -name = "pluggy" -version = "1.2.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, - {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "prompt-toolkit" -version = "3.0.39" -description = "Library for building powerful interactive command lines in Python" -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "prompt_toolkit-3.0.39-py3-none-any.whl", hash = "sha256:9dffbe1d8acf91e3de75f3b544e4842382fc06c6babe903ac9acb74dc6e08d88"}, - {file = "prompt_toolkit-3.0.39.tar.gz", hash = "sha256:04505ade687dc26dc4284b1ad19a83be2f2afe83e7a828ace0c72f3a1df72aac"}, -] - -[package.dependencies] -wcwidth = "*" - -[[package]] -name = "pycparser" -version = "2.21" -description = "C parser in Python" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, - {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, -] - -[[package]] -name = "pygments" -version = "2.16.1" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.7" -files = [ - {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, - {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, -] - -[package.extras] -plugins = ["importlib-metadata"] - -[[package]] -name = "pytest" -version = "7.4.0" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, - {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} - -[package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pytest-cov" -version = "4.1.0" -description = "Pytest plugin for measuring coverage." -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, - {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, -] - -[package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} -pytest = ">=4.6" - -[package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] - -[[package]] -name = "pytest-mock" -version = "3.11.1" -description = "Thin-wrapper around the mock package for easier use with pytest" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest-mock-3.11.1.tar.gz", hash = "sha256:7f6b125602ac6d743e523ae0bfa71e1a697a2f5534064528c6ff84c2f7c2fc7f"}, - {file = "pytest_mock-3.11.1-py3-none-any.whl", hash = "sha256:21c279fff83d70763b05f8874cc9cfb3fcacd6d354247a976f9529d19f9acf39"}, -] - -[package.dependencies] -pytest = ">=5.0" - -[package.extras] -dev = ["pre-commit", "pytest-asyncio", "tox"] - -[[package]] -name = "pytest-picked" -version = "0.5.0" -description = "Run the tests related to the changed files" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest-picked-0.5.0.tar.gz", hash = "sha256:b39cd43b1f5e6efd2fc896f318e23c2c77effde8dd6efa58653a2940d8a384d9"}, - {file = "pytest_picked-0.5.0-py3-none-any.whl", hash = "sha256:6d22771a857a2cd8691fc0802f3e1371fe4063fa1ecbd216d9584bbe089fcfd3"}, -] - -[package.dependencies] -pytest = ">=3.7.0" - -[[package]] -name = "pytest-sugar" -version = "0.9.7" -description = "pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly)." -optional = false -python-versions = "*" -files = [ - {file = "pytest-sugar-0.9.7.tar.gz", hash = "sha256:f1e74c1abfa55f7241cf7088032b6e378566f16b938f3f08905e2cf4494edd46"}, - {file = "pytest_sugar-0.9.7-py2.py3-none-any.whl", hash = "sha256:8cb5a4e5f8bbcd834622b0235db9e50432f4cbd71fef55b467fe44e43701e062"}, -] - -[package.dependencies] -packaging = ">=21.3" -pytest = ">=6.2.0" -termcolor = ">=2.1.0" - -[package.extras] -dev = ["black", "flake8", "pre-commit"] - -[[package]] -name = "pytest-unordered" -version = "0.5.2" -description = "Test equality of unordered collections in pytest" -optional = false -python-versions = "*" -files = [ - {file = "pytest-unordered-0.5.2.tar.gz", hash = "sha256:8187e6d68a7d54e5447e88c229cbeafa38205e55baf7da7ae57cc965c1ecdbb3"}, - {file = "pytest_unordered-0.5.2-py3-none-any.whl", hash = "sha256:b01bb0e8ba80db6dd8c840fe24ad1804c8672919303dc9302688221390a7dc29"}, -] - -[package.dependencies] -pytest = ">=6.0.0" - -[[package]] -name = "pytest-xdist" -version = "3.3.1" -description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest-xdist-3.3.1.tar.gz", hash = "sha256:d5ee0520eb1b7bcca50a60a518ab7a7707992812c578198f8b44fdfac78e8c93"}, - {file = "pytest_xdist-3.3.1-py3-none-any.whl", hash = "sha256:ff9daa7793569e6a68544850fd3927cd257cc03a7ef76c95e86915355e82b5f2"}, -] - -[package.dependencies] -execnet = ">=1.1" -pytest = ">=6.2.0" - -[package.extras] -psutil = ["psutil (>=3.0)"] -setproctitle = ["setproctitle"] -testing = ["filelock"] - -[[package]] -name = "python-dateutil" -version = "2.8.2" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "python-magic" -version = "0.4.27" -description = "File type identification using libmagic" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "python-magic-0.4.27.tar.gz", hash = "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b"}, - {file = "python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3"}, -] - -[[package]] -name = "pyyaml" -version = "6.0.1" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.6" -files = [ - {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, - {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, - {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, - {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, - {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, - {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, - {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, - {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, - {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, - {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, - {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, - {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, - {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, -] - -[[package]] -name = "pyyaml-include" -version = "1.3.1" -description = "Extending PyYAML with a custom constructor for including YAML files within YAML files" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pyyaml-include-1.3.1.tar.gz", hash = "sha256:4cb3b4e1baae2ec251808fe1e8aed5d3d20699c541864c8e47ed866ab2f15039"}, - {file = "pyyaml_include-1.3.1-py3-none-any.whl", hash = "sha256:e58525721a2938d29c4046350f8aad86f848660a770c29605e6f2700925fa753"}, -] - -[package.dependencies] -PyYAML = ">=5.1,<7.0" - -[package.extras] -toml = ["toml"] - -[[package]] -name = "requests" -version = "2.31.0" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.7" -files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "responses" -version = "0.23.3" -description = "A utility library for mocking out the `requests` Python library." -optional = false -python-versions = ">=3.7" -files = [ - {file = "responses-0.23.3-py3-none-any.whl", hash = "sha256:e6fbcf5d82172fecc0aa1860fd91e58cbfd96cee5e96da5b63fa6eb3caa10dd3"}, - {file = "responses-0.23.3.tar.gz", hash = "sha256:205029e1cb334c21cb4ec64fc7599be48b859a0fd381a42443cdd600bfe8b16a"}, -] - -[package.dependencies] -pyyaml = "*" -requests = ">=2.30.0,<3.0" -types-PyYAML = "*" -urllib3 = ">=1.25.10,<3.0" - -[package.extras] -tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli", "tomli-w", "types-requests"] - -[[package]] -name = "rfc3339" -version = "6.2" -description = "Format dates according to the RFC 3339." -optional = false -python-versions = "*" -files = [ - {file = "rfc3339-6.2-py3-none-any.whl", hash = "sha256:f44316b21b21db90a625cde04ebb0d46268f153e6093021fa5893e92a96f58a3"}, - {file = "rfc3339-6.2.tar.gz", hash = "sha256:d53c3b5eefaef892b7240ba2a91fef012e86faa4d0a0ca782359c490e00ad4d0"}, -] - -[[package]] -name = "ruff" -version = "0.0.282" -description = "An extremely fast Python linter, written in Rust." -optional = false -python-versions = ">=3.7" -files = [ - {file = "ruff-0.0.282-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:01b76309ddab16eb258dabc5e86e73e6542f59f3ea6b4ab886ecbcfc80ce062c"}, - {file = "ruff-0.0.282-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:e177cbb6dc0b1dbef5e999900d798b73e33602abf9b6c62d5d2cbe101026d931"}, - {file = "ruff-0.0.282-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5374b40b6d860d334d28678a53a92f0bf04b53acdf0395900361ad54ce71cd1d"}, - {file = "ruff-0.0.282-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d1ccbceb44e94fe2205b63996166e98a513a19ed23ec01d7193b7494b94ba30d"}, - {file = "ruff-0.0.282-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eee9c8c50bc77eb9c0811c91d9d67ff39fe4f394c2f44ada37dac6d45e50c9f1"}, - {file = "ruff-0.0.282-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:826e4de98e91450a6fe699a4e4a7cf33b9a90a2c5c270dc5b202241c37359ff8"}, - {file = "ruff-0.0.282-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d99758f8bbcb8f8da99acabf711ffad5e7a015247adf27211100b3586777fd56"}, - {file = "ruff-0.0.282-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3f30c9958ab9cb02bf0c574c629e87c19454cbbdb82750e49e3d1559a5a8f216"}, - {file = "ruff-0.0.282-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47a7a9366ab8e4ee20df9339bef172eec7b2e9e123643bf3ede005058f5b114e"}, - {file = "ruff-0.0.282-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1f05f5e6d6df6f8b1974c08f963c33f0a4d8cfa15cba12d35ca3ece8e9be5b1f"}, - {file = "ruff-0.0.282-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0710ea2cadc504b96c1d94c414a7802369d0fff2ab7c94460344bba69135cb40"}, - {file = "ruff-0.0.282-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2ca52536e1c7603fe4cbb5ad9dc141df47c3200df782f5ec559364716ea27f96"}, - {file = "ruff-0.0.282-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:aab9ed5bfba6b0a2242a7ec9a72858c802ceeaf0076fe72b2ad455639275f22c"}, - {file = "ruff-0.0.282-py3-none-win32.whl", hash = "sha256:f51bbb64f8f29e444c16d21b269ba82e25f8d536beda3df7c9fe1816297e508e"}, - {file = "ruff-0.0.282-py3-none-win_amd64.whl", hash = "sha256:bd25085c42ebaffe336ed7bda8a0ae7b6c454a5f386ec8b2299503f79bd12bdf"}, - {file = "ruff-0.0.282-py3-none-win_arm64.whl", hash = "sha256:f03fba9621533d67d7ab995847467d78b9337e3697779ef2cea6f1deaee5fbef"}, - {file = "ruff-0.0.282.tar.gz", hash = "sha256:ef677c26bae756e4c98af6d8972da83caea550bc92ffef97a6e939ca5b24ad06"}, -] - -[[package]] -name = "s3transfer" -version = "0.6.1" -description = "An Amazon S3 Transfer Manager" -optional = false -python-versions = ">= 3.7" -files = [ - {file = "s3transfer-0.6.1-py3-none-any.whl", hash = "sha256:3c0da2d074bf35d6870ef157158641178a4204a6e689e82546083e31e0311346"}, - {file = "s3transfer-0.6.1.tar.gz", hash = "sha256:640bb492711f4c0c0905e1f62b6aaeb771881935ad27884852411f8e9cacbca9"}, -] - -[package.dependencies] -botocore = ">=1.12.36,<2.0a.0" - -[package.extras] -crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"] - -[[package]] -name = "semver" -version = "3.0.1" -description = "Python helper for Semantic Versioning (https://semver.org)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "semver-3.0.1-py3-none-any.whl", hash = "sha256:2a23844ba1647362c7490fe3995a86e097bb590d16f0f32dfc383008f19e4cdf"}, - {file = "semver-3.0.1.tar.gz", hash = "sha256:9ec78c5447883c67b97f98c3b6212796708191d22e4ad30f4570f840171cbce1"}, -] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] - -[[package]] -name = "smmap" -version = "5.0.0" -description = "A pure Python implementation of a sliding window memory map manager" -optional = false -python-versions = ">=3.6" -files = [ - {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, - {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, -] - -[[package]] -name = "sqlalchemy" -version = "1.4.49" -description = "Database Abstraction Library" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" -files = [ - {file = "SQLAlchemy-1.4.49-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2e126cf98b7fd38f1e33c64484406b78e937b1a280e078ef558b95bf5b6895f6"}, - {file = "SQLAlchemy-1.4.49-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:03db81b89fe7ef3857b4a00b63dedd632d6183d4ea5a31c5d8a92e000a41fc71"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:95b9df9afd680b7a3b13b38adf6e3a38995da5e162cc7524ef08e3be4e5ed3e1"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a63e43bf3f668c11bb0444ce6e809c1227b8f067ca1068898f3008a273f52b09"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca46de16650d143a928d10842939dab208e8d8c3a9a8757600cae9b7c579c5cd"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f835c050ebaa4e48b18403bed2c0fda986525896efd76c245bdd4db995e51a4c"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c21b172dfb22e0db303ff6419451f0cac891d2e911bb9fbf8003d717f1bcf91"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-win32.whl", hash = "sha256:5fb1ebdfc8373b5a291485757bd6431de8d7ed42c27439f543c81f6c8febd729"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-win_amd64.whl", hash = "sha256:f8a65990c9c490f4651b5c02abccc9f113a7f56fa482031ac8cb88b70bc8ccaa"}, - {file = "SQLAlchemy-1.4.49-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8923dfdf24d5aa8a3adb59723f54118dd4fe62cf59ed0d0d65d940579c1170a4"}, - {file = "SQLAlchemy-1.4.49-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9ab2c507a7a439f13ca4499db6d3f50423d1d65dc9b5ed897e70941d9e135b0"}, - {file = "SQLAlchemy-1.4.49-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5debe7d49b8acf1f3035317e63d9ec8d5e4d904c6e75a2a9246a119f5f2fdf3d"}, - {file = "SQLAlchemy-1.4.49-cp311-cp311-win32.whl", hash = "sha256:82b08e82da3756765c2e75f327b9bf6b0f043c9c3925fb95fb51e1567fa4ee87"}, - {file = "SQLAlchemy-1.4.49-cp311-cp311-win_amd64.whl", hash = "sha256:171e04eeb5d1c0d96a544caf982621a1711d078dbc5c96f11d6469169bd003f1"}, - {file = "SQLAlchemy-1.4.49-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f23755c384c2969ca2f7667a83f7c5648fcf8b62a3f2bbd883d805454964a800"}, - {file = "SQLAlchemy-1.4.49-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8396e896e08e37032e87e7fbf4a15f431aa878c286dc7f79e616c2feacdb366c"}, - {file = "SQLAlchemy-1.4.49-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66da9627cfcc43bbdebd47bfe0145bb662041472393c03b7802253993b6b7c90"}, - {file = "SQLAlchemy-1.4.49-cp312-cp312-win32.whl", hash = "sha256:9a06e046ffeb8a484279e54bda0a5abfd9675f594a2e38ef3133d7e4d75b6214"}, - {file = "SQLAlchemy-1.4.49-cp312-cp312-win_amd64.whl", hash = "sha256:7cf8b90ad84ad3a45098b1c9f56f2b161601e4670827d6b892ea0e884569bd1d"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:36e58f8c4fe43984384e3fbe6341ac99b6b4e083de2fe838f0fdb91cebe9e9cb"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b31e67ff419013f99ad6f8fc73ee19ea31585e1e9fe773744c0f3ce58c039c30"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc22807a7e161c0d8f3da34018ab7c97ef6223578fcdd99b1d3e7ed1100a5db"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c14b29d9e1529f99efd550cd04dbb6db6ba5d690abb96d52de2bff4ed518bc95"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c40f3470e084d31247aea228aa1c39bbc0904c2b9ccbf5d3cfa2ea2dac06f26d"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-win32.whl", hash = "sha256:706bfa02157b97c136547c406f263e4c6274a7b061b3eb9742915dd774bbc264"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-win_amd64.whl", hash = "sha256:a7f7b5c07ae5c0cfd24c2db86071fb2a3d947da7bd487e359cc91e67ac1c6d2e"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:4afbbf5ef41ac18e02c8dc1f86c04b22b7a2125f2a030e25bbb4aff31abb224b"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24e300c0c2147484a002b175f4e1361f102e82c345bf263242f0449672a4bccf"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:393cd06c3b00b57f5421e2133e088df9cabcececcea180327e43b937b5a7caa5"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:201de072b818f8ad55c80d18d1a788729cccf9be6d9dc3b9d8613b053cd4836d"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7653ed6817c710d0c95558232aba799307d14ae084cc9b1f4c389157ec50df5c"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-win32.whl", hash = "sha256:647e0b309cb4512b1f1b78471fdaf72921b6fa6e750b9f891e09c6e2f0e5326f"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-win_amd64.whl", hash = "sha256:ab73ed1a05ff539afc4a7f8cf371764cdf79768ecb7d2ec691e3ff89abbc541e"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:37ce517c011560d68f1ffb28af65d7e06f873f191eb3a73af5671e9c3fada08a"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1878ce508edea4a879015ab5215546c444233881301e97ca16fe251e89f1c55"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95ab792ca493891d7a45a077e35b418f68435efb3e1706cb8155e20e86a9013c"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0e8e608983e6f85d0852ca61f97e521b62e67969e6e640fe6c6b575d4db68557"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccf956da45290df6e809ea12c54c02ace7f8ff4d765d6d3dfb3655ee876ce58d"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-win32.whl", hash = "sha256:f167c8175ab908ce48bd6550679cc6ea20ae169379e73c7720a28f89e53aa532"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-win_amd64.whl", hash = "sha256:45806315aae81a0c202752558f0df52b42d11dd7ba0097bf71e253b4215f34f4"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:b6d0c4b15d65087738a6e22e0ff461b407533ff65a73b818089efc8eb2b3e1de"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a843e34abfd4c797018fd8d00ffffa99fd5184c421f190b6ca99def4087689bd"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:738d7321212941ab19ba2acf02a68b8ee64987b248ffa2101630e8fccb549e0d"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1c890421651b45a681181301b3497e4d57c0d01dc001e10438a40e9a9c25ee77"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d26f280b8f0a8f497bc10573849ad6dc62e671d2468826e5c748d04ed9e670d5"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-win32.whl", hash = "sha256:ec2268de67f73b43320383947e74700e95c6770d0c68c4e615e9897e46296294"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-win_amd64.whl", hash = "sha256:bbdf16372859b8ed3f4d05f925a984771cd2abd18bd187042f24be4886c2a15f"}, - {file = "SQLAlchemy-1.4.49.tar.gz", hash = "sha256:06ff25cbae30c396c4b7737464f2a7fc37a67b7da409993b182b024cec80aed9"}, -] - -[package.dependencies] -greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} - -[package.extras] -aiomysql = ["aiomysql", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] -asyncio = ["greenlet (!=0.4.17)"] -asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"] -mssql = ["pyodbc"] -mssql-pymssql = ["pymssql"] -mssql-pyodbc = ["pyodbc"] -mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"] -mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"] -mysql-connector = ["mysql-connector-python"] -oracle = ["cx-oracle (>=7)", "cx-oracle (>=7,<8)"] -postgresql = ["psycopg2 (>=2.7)"] -postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] -postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"] -postgresql-psycopg2binary = ["psycopg2-binary"] -postgresql-psycopg2cffi = ["psycopg2cffi"] -pymysql = ["pymysql", "pymysql (<1)"] -sqlcipher = ["sqlcipher3-binary"] - -[[package]] -name = "supports-color" -version = "0.1.2" -description = "Detect whether a terminal supports color" -optional = false -python-versions = ">=3.6,<4.0" -files = [ - {file = "supports-color-0.1.2.tar.gz", hash = "sha256:37f03ce1a46b2f12b50e1a75c3597ba7f27915b5b91e21099c554242468eaddf"}, - {file = "supports_color-0.1.2-py3-none-any.whl", hash = "sha256:43403dd59a40583d3da13330cbcfcd41711748881671598c27f260441d2cb3d1"}, -] - -[package.dependencies] -dict = ">=2020.12.3,<2021.0.0" -has-flag = ">=0.1.1,<0.2.0" - -[[package]] -name = "tabulate" -version = "0.9.0" -description = "Pretty-print tabular data" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, - {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, -] - -[package.extras] -widechars = ["wcwidth"] - -[[package]] -name = "termcolor" -version = "2.3.0" -description = "ANSI color formatting for output in terminal" -optional = false -python-versions = ">=3.7" -files = [ - {file = "termcolor-2.3.0-py3-none-any.whl", hash = "sha256:3afb05607b89aed0ffe25202399ee0867ad4d3cb4180d98aaf8eefa6a5f7d475"}, - {file = "termcolor-2.3.0.tar.gz", hash = "sha256:b5b08f68937f138fe92f6c089b99f1e2da0ae56c52b78bf7075fd95420fd9a5a"}, -] - -[package.extras] -tests = ["pytest", "pytest-cov"] - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] - -[[package]] -name = "toposort" -version = "1.10" -description = "Implements a topological sort algorithm." -optional = false -python-versions = "*" -files = [ - {file = "toposort-1.10-py3-none-any.whl", hash = "sha256:cbdbc0d0bee4d2695ab2ceec97fe0679e9c10eab4b2a87a9372b929e70563a87"}, - {file = "toposort-1.10.tar.gz", hash = "sha256:bfbb479c53d0a696ea7402601f4e693c97b0367837c8898bc6471adfca37a6bd"}, -] - -[[package]] -name = "types-pyyaml" -version = "6.0.12.11" -description = "Typing stubs for PyYAML" -optional = false -python-versions = "*" -files = [ - {file = "types-PyYAML-6.0.12.11.tar.gz", hash = "sha256:7d340b19ca28cddfdba438ee638cd4084bde213e501a3978738543e27094775b"}, - {file = "types_PyYAML-6.0.12.11-py3-none-any.whl", hash = "sha256:a461508f3096d1d5810ec5ab95d7eeecb651f3a15b71959999988942063bf01d"}, -] - -[[package]] -name = "types-requests" -version = "2.31.0.2" -description = "Typing stubs for requests" -optional = false -python-versions = "*" -files = [ - {file = "types-requests-2.31.0.2.tar.gz", hash = "sha256:6aa3f7faf0ea52d728bb18c0a0d1522d9bfd8c72d26ff6f61bfc3d06a411cf40"}, - {file = "types_requests-2.31.0.2-py3-none-any.whl", hash = "sha256:56d181c85b5925cbc59f4489a57e72a8b2166f18273fd8ba7b6fe0c0b986f12a"}, -] - -[package.dependencies] -types-urllib3 = "*" - -[[package]] -name = "types-urllib3" -version = "1.26.25.14" -description = "Typing stubs for urllib3" -optional = false -python-versions = "*" -files = [ - {file = "types-urllib3-1.26.25.14.tar.gz", hash = "sha256:229b7f577c951b8c1b92c1bc2b2fdb0b49847bd2af6d1cc2a2e3dd340f3bda8f"}, - {file = "types_urllib3-1.26.25.14-py3-none-any.whl", hash = "sha256:9683bbb7fb72e32bfe9d2be6e04875fbe1b3eeec3cbb4ea231435aa7fd6b4f0e"}, -] - -[[package]] -name = "typing-extensions" -version = "4.7.1" -description = "Backported and Experimental Type Hints for Python 3.7+" -optional = false -python-versions = ">=3.7" -files = [ - {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, - {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, -] - -[[package]] -name = "typing-inspect" -version = "0.9.0" -description = "Runtime inspection utilities for typing module." -optional = false -python-versions = "*" -files = [ - {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"}, - {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"}, -] - -[package.dependencies] -mypy-extensions = ">=0.3.0" -typing-extensions = ">=3.7.4" - -[[package]] -name = "untokenize" -version = "0.1.1" -description = "Transforms tokens into original source code (while preserving whitespace)." -optional = false -python-versions = "*" -files = [ - {file = "untokenize-0.1.1.tar.gz", hash = "sha256:3865dbbbb8efb4bb5eaa72f1be7f3e0be00ea8b7f125c69cbd1f5fda926f37a2"}, -] - -[[package]] -name = "urllib3" -version = "1.26.18" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" -files = [ - {file = "urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"}, - {file = "urllib3-1.26.18.tar.gz", hash = "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"}, -] - -[package.extras] -brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - -[[package]] -name = "values" -version = "2020.12.3" -description = "values.get(input) - create list from input" -optional = false -python-versions = "*" -files = [ - {file = "values-2020.12.3.tar.gz", hash = "sha256:30b1b74674b546188b77d8398de17ea49f3692191114da43c703bda5b54402f5"}, -] - -[[package]] -name = "vunnel" -version = "0.15.2" -description = "vunnel ~= 'vulnerability data funnel'" -optional = false -python-versions = ">=3.9,<4.0" -files = [ - {file = "vunnel-0.15.2-py3-none-any.whl", hash = "sha256:1ece930c0ad090aed9a4160fce08fc36cddee8fe71e26a68c3f45fa1aece0bf6"}, - {file = "vunnel-0.15.2.tar.gz", hash = "sha256:29f005b6972466736fad6f9f53211500052f12408ff6ace0f5d707a9cbf7ecd6"}, -] - -[package.dependencies] -click = ">=8.1.3,<9.0.0" -colorlog = ">=6.7.0,<7.0.0" -cvss = ">=2.6,<3.0" -dataclass-wizard = ">=0.22.2,<0.23.0" -defusedxml = ">=0.7.1,<0.8.0" -future = ">=0.18.3,<0.19.0" -ijson = ">=2.5.1,<3.0" -importlib-metadata = ">=6.1.0,<7.0.0" -mergedeep = ">=1.3.4,<2.0.0" -orjson = ">=3.8.6,<4.0.0" -python-dateutil = ">=2.8.2,<3.0.0" -PyYAML = ">=6.0,<7.0" -requests = ">=2.28.1,<3.0.0" -rfc3339 = ">=6.2,<7.0" -SQLAlchemy = ">=1.4.46,<2.0" -xsdata = {version = ">=22.12,<24.0", extras = ["cli", "lxml", "soap"]} -xxhash = ">=3.1.0,<4.0.0" - -[[package]] -name = "wcwidth" -version = "0.2.6" -description = "Measures the displayed width of unicode strings in a terminal" -optional = false -python-versions = "*" -files = [ - {file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"}, - {file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"}, -] - -[[package]] -name = "werkzeug" -version = "2.3.8" -description = "The comprehensive WSGI web application library." -optional = false -python-versions = ">=3.8" -files = [ - {file = "werkzeug-2.3.8-py3-none-any.whl", hash = "sha256:bba1f19f8ec89d4d607a3bd62f1904bd2e609472d93cd85e9d4e178f472c3748"}, - {file = "werkzeug-2.3.8.tar.gz", hash = "sha256:554b257c74bbeb7a0d254160a4f8ffe185243f52a52035060b761ca62d977f03"}, -] - -[package.dependencies] -MarkupSafe = ">=2.1.1" - -[package.extras] -watchdog = ["watchdog (>=2.3)"] - -[[package]] -name = "xmltodict" -version = "0.13.0" -description = "Makes working with XML feel like you are working with JSON" -optional = false -python-versions = ">=3.4" -files = [ - {file = "xmltodict-0.13.0-py2.py3-none-any.whl", hash = "sha256:aa89e8fd76320154a40d19a0df04a4695fb9dc5ba977cbb68ab3e4eb225e7852"}, - {file = "xmltodict-0.13.0.tar.gz", hash = "sha256:341595a488e3e01a85a9d8911d8912fd922ede5fecc4dce437eb4b6c8d037e56"}, -] - -[[package]] -name = "xsdata" -version = "23.7" -description = "Python XML Binding" -optional = false -python-versions = ">=3.7" -files = [ - {file = "xsdata-23.7-py3-none-any.whl", hash = "sha256:ccfc72098275dcb65c558cf7850d71e01af3e1c212483f4df4e5d273f6d0e1dc"}, - {file = "xsdata-23.7.tar.gz", hash = "sha256:8d79e9078d8ff4f8c4830ecda1208613a4b5e283a41e4cd304561bf6fca96aac"}, -] - -[package.dependencies] -click = {version = ">=5.0", optional = true, markers = "extra == \"cli\""} -click-default-group = {version = ">=1.2", optional = true, markers = "extra == \"cli\""} -docformatter = {version = ">=1.7.2", optional = true, markers = "extra == \"cli\""} -jinja2 = {version = ">=2.10", optional = true, markers = "extra == \"cli\""} -lxml = {version = ">=4.4.1", optional = true, markers = "extra == \"lxml\""} -requests = {version = "*", optional = true, markers = "extra == \"soap\""} -toposort = {version = ">=1.5", optional = true, markers = "extra == \"cli\""} -typing-extensions = "*" - -[package.extras] -cli = ["click (>=5.0)", "click-default-group (>=1.2)", "docformatter (>=1.7.2)", "jinja2 (>=2.10)", "toposort (>=1.5)"] -docs = ["furo", "sphinx", "sphinx-autobuild", "sphinx-autodoc-typehints", "sphinx-copybutton", "sphinx-inline-tabs"] -lxml = ["lxml (>=4.4.1)"] -soap = ["requests"] -test = ["pre-commit", "pytest", "pytest-benchmark", "pytest-cov"] - -[[package]] -name = "xxhash" -version = "3.3.0" -description = "Python binding for xxHash" -optional = false -python-versions = ">=3.7" -files = [ - {file = "xxhash-3.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:70ef7288d1cb1ad16e02d101ea43bb0e392d985d60b9b0035aee80663530960d"}, - {file = "xxhash-3.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:44ff8c673cab50be46784e0aec62aa6f0ca9ea765e2b0690e8945d0cd950dcaf"}, - {file = "xxhash-3.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfebc90273ae2beb813d8118a2bfffb5a5a81ac054fbfd061ea18fd0a81db0ac"}, - {file = "xxhash-3.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9084e68bedbd665c7e9241a7b597c28f4775edeb3941bf608ecb38732a5f8fb5"}, - {file = "xxhash-3.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d72493a14a3e89564b1a6c7400b9b40621e8f4692410706ef27c66aeadc7b431"}, - {file = "xxhash-3.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98779cbe9068dd7734cc3210693894d5cc9b156920e9c336f10fb99f46bebbd8"}, - {file = "xxhash-3.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:499f8a12767dd28b98ab6b7c7da7d294564e4c9024a2aaa5d0b0b98a8bef2f92"}, - {file = "xxhash-3.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dabda7f42c548f98d8e07e390bda2953fc58302c0e07ded7b3fe0637e7ecd2f"}, - {file = "xxhash-3.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c416409646c793c46370f0f1859253302ee70aeda5278c2a0ca41462f8ec1244"}, - {file = "xxhash-3.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b8bd31aaad8a80a7302730676cec26bea3ef1fd9835875aa47fea073aca9fe05"}, - {file = "xxhash-3.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:3af8e3bcd630f905efbdfe7a51b51fc1ca3c9dca8b155f841925f3ad41685d41"}, - {file = "xxhash-3.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d86b79c707fc7025d967af71db652429a06a8179175e45bd2e9f17b8af6f5949"}, - {file = "xxhash-3.3.0-cp310-cp310-win32.whl", hash = "sha256:98fe771f36ee9d3a1f5741424a956a2ba9651d9508a9f64a024b57f2cf796414"}, - {file = "xxhash-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:0a65131f7f731ecf7e3dd27f09d877aff3000a79a446caaa2c0d8d0ec0bc7186"}, - {file = "xxhash-3.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a9761e425e79d23797fa0bec2d781dbadb9fe5dcc2bf69030855f5e393c3bec8"}, - {file = "xxhash-3.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d28c7ef1deb3c3ac5f5290176ca3d501daa97c2e1f7443bf5d8b61ac651794b2"}, - {file = "xxhash-3.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:701b7cefffc25de1b7ddfae6505da70a3b3a11e312c2e2b33b09e180bbceb43d"}, - {file = "xxhash-3.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b1644f8b8e19a242c3047a089541067248a651038cabb9fcab3c13eb1dfcd757"}, - {file = "xxhash-3.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:20e7d0e3488cc0f0dbe360731b7fe32e1f2df46bf2de2db3317d301efb93084c"}, - {file = "xxhash-3.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:156c52eca2b20f9839723bef0b929a290f6c2f1c98ccb24e82f58f96f3c16007"}, - {file = "xxhash-3.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d6ce4d3828d79044ed08994e196c20f69c18133ed8a4286afe3e98989adeeac"}, - {file = "xxhash-3.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b85b63757ade2439c8d7d71842c40d42c0ab3b69279ed02afbd3b1635f7d2b4b"}, - {file = "xxhash-3.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b2b9051e40b7b649a9a2a38fb223ca6a593d332012df885746b81968948f9435"}, - {file = "xxhash-3.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:81b7ce050f26fc1daaaa0d24e320815306736d14608e1ba31920e693a7ca9afb"}, - {file = "xxhash-3.3.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:7442500fcce71669953ca959682dcd47452bc3f9c95c8d88315874aeabec9f82"}, - {file = "xxhash-3.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:36a05bf59a515cfb07f3f83373c527fff2ecaa77eaf30c968c788aea582070a1"}, - {file = "xxhash-3.3.0-cp311-cp311-win32.whl", hash = "sha256:da16f9cd62c6fde74683be1b28c28ef865e706da13e3bee4ba836fcc520de0cc"}, - {file = "xxhash-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:40fd49ef6964b1c90c0bea63cd184f6d0b36e59144a080e8b3ac2c4c06bf6bf2"}, - {file = "xxhash-3.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:672c60cce1f8026ae32c651f877aa64f342876083a36a4b1ff91bc876aaf0e34"}, - {file = "xxhash-3.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bb6c83d7a65dd3065566c77425ba72df96982174e8ef613d809052d68ae77ab"}, - {file = "xxhash-3.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a4170f3016b621e3200ebfcc18de6f50eb8e8fc1303e16324b1f5625afd51b57"}, - {file = "xxhash-3.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bfb9c45d502ab38c0f4edf98a678694ae0f345613ef4900ade98c71f64db4d78"}, - {file = "xxhash-3.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48af026a2b1569666da42a478248a1f03f4e2350a34eb661afe3cb45429ca1d7"}, - {file = "xxhash-3.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe627de8fe8ddfa8b6477bda4ae5d5843ad1a0c83601dcff72247039465cc901"}, - {file = "xxhash-3.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:427fc60a188e345534f35b0aa76f7640c5ddf0354f1c9ad826a2bc086282982d"}, - {file = "xxhash-3.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d80acb20c7f268fe3150ac0be6a6b798062af56a1795eef855b26c9eae11a99c"}, - {file = "xxhash-3.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:e71100818943422d1fbbe460e7be7fc4f2d2ba9371b2a745eb09e29ef0493f4a"}, - {file = "xxhash-3.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:e3b9bb5fdbe284c7b61c5d82c76688e52bbaf48ab1e53de98c072cc696fa331f"}, - {file = "xxhash-3.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1e25f6c8c46cf1ed8237f610abb231093a748c97d6c2c092789a7cad7e7ef290"}, - {file = "xxhash-3.3.0-cp37-cp37m-win32.whl", hash = "sha256:928208dfecc563be59ae91868d1658e78809cb1e6a0bd74960a96c915db6390c"}, - {file = "xxhash-3.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bd1b4531a66da6dde1974662c1fd6fb1a2f27e40542e3df5e5e5dbab8ea4aee7"}, - {file = "xxhash-3.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:deebb296df92e082b6d0171a7d6227b503e2897cea4f8bdd3d708094974d4cf6"}, - {file = "xxhash-3.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cd96e9cb0e2baa294e6d572207d9731c3bb8e2511f1ff70f2bf17266b4488bd9"}, - {file = "xxhash-3.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3756b44bf247e422a2e47a38f25d03cf4a5ed539fdc2be3c60043e872e6ff13d"}, - {file = "xxhash-3.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69550c3c053b8f135ceac97b85dc1b2bc54b7613a966f550f32b43bed81c788a"}, - {file = "xxhash-3.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fc8736fc3e0c5aad435520873b9d2e27ddcc5a830b07e00e9c4d3a61ded9675"}, - {file = "xxhash-3.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80ead7774392efbd95f9f701155048f9ca26cf55133db6f5bb5a0ec69376bda5"}, - {file = "xxhash-3.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8737c9b3fd944d856faafa92c95f6198649ad57987935b6d965d086938be917"}, - {file = "xxhash-3.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2c8e078d0b9f85212801c41bd9eec8122003929686b0ee33360ffbfdf1a189ab"}, - {file = "xxhash-3.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f399269d20ef1dd910331f9ad49e8510c3ba2aa657b623293b536038f266a5c5"}, - {file = "xxhash-3.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f3661decef5f9ff7ab50edbef463bf7dc717621b56755dbae5458a946a033b10"}, - {file = "xxhash-3.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5ec374d0f1e7d43ef48a4ff643600833d7a325ecc6933b4d6ad9282f55751cf7"}, - {file = "xxhash-3.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:39a947ff02d9a85673f5ce1f6f34059e24c714a797440485bd81b2c3cb69a7ff"}, - {file = "xxhash-3.3.0-cp38-cp38-win32.whl", hash = "sha256:4a4f0645a0ec03b229fb04f2e66bdbcb1ffd341a70d6c86c3ee015ffdcd70fad"}, - {file = "xxhash-3.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:8af5a687c0fb4357c230eec8a57ca07d3172faa3cb69beb0cbad40672ae6fa4b"}, - {file = "xxhash-3.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e5bfafda019ecc6202af6f3cf08220fa66af9612ba16ef831033ae3ac7bd1f89"}, - {file = "xxhash-3.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d113b433bc817adf845689a051363777835577858263ec4325d1934fcb7e394"}, - {file = "xxhash-3.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56aacf4bf65f575c0392be958aceff719d850950bb6af7d804b32d4bc293159c"}, - {file = "xxhash-3.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f5d3e4e0937dad05585e9bd772bbdf0ca40cd8b2f54789d7a1f3091b608118c"}, - {file = "xxhash-3.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23605d7fc67bc7daa0d263b3a26de3375cfcc0b51ab7de5026625415c05b6fed"}, - {file = "xxhash-3.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe525be0392d493558a2b10d764bcaae9850cc262b417176a8b001f16e085fc6"}, - {file = "xxhash-3.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b234d08786884f5c8d55dfebb839cfbd846d812e3a052c39ca7e8ce7055fed68"}, - {file = "xxhash-3.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b031395b4b9c3085d9ea1ce89896ab01a65fc63172b2bfda5dd318fefe5e2f93"}, - {file = "xxhash-3.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:5afe44da46b48c75169e622a532dca3fe585343c0577cfd7c18ecd3f1200305d"}, - {file = "xxhash-3.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c59f233f38b6a49d5e4ddf16be910a5bbf36a2989b6b2c8591853fb9f5a5e691"}, - {file = "xxhash-3.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:ed016e278c5c4633270903c7cf3b9dfb0bd293b7335e43fe695cb95541da53c9"}, - {file = "xxhash-3.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7a8bd6612fb35487e9ab329bb37b3df44f58baf752010dde9282593edbfed7e7"}, - {file = "xxhash-3.3.0-cp39-cp39-win32.whl", hash = "sha256:015a0498bde85364abc53fcc713af962dd4555391929736d9c0ff2c555436a03"}, - {file = "xxhash-3.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:06a484097af32caf1cfffadd60c3ca140c9e52b40a551fb1f6f0fdfd6f7f8977"}, - {file = "xxhash-3.3.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6c3809740124bbc777d29e3ae53de24f4c13fd5e62878086a8feadf0dcb654a5"}, - {file = "xxhash-3.3.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae092f0daaeece2acdd6ec46e2ab307d8d6f22b01ecca14dc6078844dbd88339"}, - {file = "xxhash-3.3.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3498e72ff2610b049b97bb81d1ea6e7bfa5b7a45efb3f255d77ec2fa2bc91653"}, - {file = "xxhash-3.3.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0004dded9d86f129961326e980420187640fb7ba65a184009429861c1d09df7"}, - {file = "xxhash-3.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:41c8bfd27191928bae6fd2b66872965532267785094a03c0ee5f358d9dba51c2"}, - {file = "xxhash-3.3.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:71db8498e329cef3588b0617f762a3fe31d899872e76a68ce2840e35a1318a5b"}, - {file = "xxhash-3.3.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d1d24d71b6209bc0124286932c4f0660c1103cb996fe34cb374bc12ac251940"}, - {file = "xxhash-3.3.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61004587a09b5b385e43d95ffe3a76c9d934dfd79ea38272d5c20ddfba8eab8f"}, - {file = "xxhash-3.3.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f0c92e3fa826425c73acafb31e022a719c85423847a9433d3a9e61e4ac97543"}, - {file = "xxhash-3.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:367e03f1484ce471c94e731b98f5e4a05b43e7188b16692998e1cc89fd1159a5"}, - {file = "xxhash-3.3.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ed04c47dfaab98fcda0b748af9ee6fe8c888a0a0fbd13720e0f0221671e387e1"}, - {file = "xxhash-3.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cbfde62516435ca198220aff048a8793383cb7047c7b88714a061968bca786d"}, - {file = "xxhash-3.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73682225faa973ee56743f0fcd36bfcbfec503be258e0e420fb34313f52f1e7b"}, - {file = "xxhash-3.3.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d49efdce2086c2c506af20ed18a1115b40af7aad6d4ee27cb31d7c810585a3f2"}, - {file = "xxhash-3.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:546a0bb8e5a657cadf0da290b30ccd561cb89c256a5421ab8d5eb12eaf087349"}, - {file = "xxhash-3.3.0.tar.gz", hash = "sha256:c3f9e322b1ebeebd44e3d9d2d9b124e0c550c1ef41bd552afdcdd719516ee41a"}, -] - -[[package]] -name = "yardstick" -version = "0.0.0" -description = "Tool for comparing the results from vulnerability scanners" -optional = false -python-versions = "^3.9" -files = [] -develop = false - -[package.dependencies] -click = "^8" -Colr = "^0.9.1" -dataclass-wizard = "^0.22.2" -dataclasses-json = "^0.5.8" -GitPython = "^3.1.32" -importlib-metadata = "^6.7.0" -mergedeep = "^1.3.4" -omitempty = "^0.1.1" -prompt-toolkit = "^3.0.18" -Pygments = "^2.16.1" -PyYAML = ">= 6.0.0, < 7.0" -requests = "^2.31.0" -rfc3339 = "^6.2" -tabulate = "^0.9.0" - -[package.source] -type = "git" -url = "https://github.com/anchore/yardstick" -reference = "dc54ea796677e9c310041514875f56647f52b78d" -resolved_reference = "dc54ea796677e9c310041514875f56647f52b78d" - -[[package]] -name = "zipp" -version = "3.16.2" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.8" -files = [ - {file = "zipp-3.16.2-py3-none-any.whl", hash = "sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0"}, - {file = "zipp-3.16.2.tar.gz", hash = "sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] - -[[package]] -name = "zstandard" -version = "0.21.0" -description = "Zstandard bindings for Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "zstandard-0.21.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:649a67643257e3b2cff1c0a73130609679a5673bf389564bc6d4b164d822a7ce"}, - {file = "zstandard-0.21.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:144a4fe4be2e747bf9c646deab212666e39048faa4372abb6a250dab0f347a29"}, - {file = "zstandard-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b72060402524ab91e075881f6b6b3f37ab715663313030d0ce983da44960a86f"}, - {file = "zstandard-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8257752b97134477fb4e413529edaa04fc0457361d304c1319573de00ba796b1"}, - {file = "zstandard-0.21.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c053b7c4cbf71cc26808ed67ae955836232f7638444d709bfc302d3e499364fa"}, - {file = "zstandard-0.21.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2769730c13638e08b7a983b32cb67775650024632cd0476bf1ba0e6360f5ac7d"}, - {file = "zstandard-0.21.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7d3bc4de588b987f3934ca79140e226785d7b5e47e31756761e48644a45a6766"}, - {file = "zstandard-0.21.0-cp310-cp310-win32.whl", hash = "sha256:67829fdb82e7393ca68e543894cd0581a79243cc4ec74a836c305c70a5943f07"}, - {file = "zstandard-0.21.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6048a287f8d2d6e8bc67f6b42a766c61923641dd4022b7fd3f7439e17ba5a4d"}, - {file = "zstandard-0.21.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7f2afab2c727b6a3d466faee6974a7dad0d9991241c498e7317e5ccf53dbc766"}, - {file = "zstandard-0.21.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff0852da2abe86326b20abae912d0367878dd0854b8931897d44cfeb18985472"}, - {file = "zstandard-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d12fa383e315b62630bd407477d750ec96a0f438447d0e6e496ab67b8b451d39"}, - {file = "zstandard-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1b9703fe2e6b6811886c44052647df7c37478af1b4a1a9078585806f42e5b15"}, - {file = "zstandard-0.21.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df28aa5c241f59a7ab524f8ad8bb75d9a23f7ed9d501b0fed6d40ec3064784e8"}, - {file = "zstandard-0.21.0-cp311-cp311-win32.whl", hash = "sha256:0aad6090ac164a9d237d096c8af241b8dcd015524ac6dbec1330092dba151657"}, - {file = "zstandard-0.21.0-cp311-cp311-win_amd64.whl", hash = "sha256:48b6233b5c4cacb7afb0ee6b4f91820afbb6c0e3ae0fa10abbc20000acdf4f11"}, - {file = "zstandard-0.21.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e7d560ce14fd209db6adacce8908244503a009c6c39eee0c10f138996cd66d3e"}, - {file = "zstandard-0.21.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e6e131a4df2eb6f64961cea6f979cdff22d6e0d5516feb0d09492c8fd36f3bc"}, - {file = "zstandard-0.21.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1e0c62a67ff425927898cf43da2cf6b852289ebcc2054514ea9bf121bec10a5"}, - {file = "zstandard-0.21.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1545fb9cb93e043351d0cb2ee73fa0ab32e61298968667bb924aac166278c3fc"}, - {file = "zstandard-0.21.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe6c821eb6870f81d73bf10e5deed80edcac1e63fbc40610e61f340723fd5f7c"}, - {file = "zstandard-0.21.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ddb086ea3b915e50f6604be93f4f64f168d3fc3cef3585bb9a375d5834392d4f"}, - {file = "zstandard-0.21.0-cp37-cp37m-win32.whl", hash = "sha256:57ac078ad7333c9db7a74804684099c4c77f98971c151cee18d17a12649bc25c"}, - {file = "zstandard-0.21.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1243b01fb7926a5a0417120c57d4c28b25a0200284af0525fddba812d575f605"}, - {file = "zstandard-0.21.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ea68b1ba4f9678ac3d3e370d96442a6332d431e5050223626bdce748692226ea"}, - {file = "zstandard-0.21.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8070c1cdb4587a8aa038638acda3bd97c43c59e1e31705f2766d5576b329e97c"}, - {file = "zstandard-0.21.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4af612c96599b17e4930fe58bffd6514e6c25509d120f4eae6031b7595912f85"}, - {file = "zstandard-0.21.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cff891e37b167bc477f35562cda1248acc115dbafbea4f3af54ec70821090965"}, - {file = "zstandard-0.21.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a9fec02ce2b38e8b2e86079ff0b912445495e8ab0b137f9c0505f88ad0d61296"}, - {file = "zstandard-0.21.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0bdbe350691dec3078b187b8304e6a9c4d9db3eb2d50ab5b1d748533e746d099"}, - {file = "zstandard-0.21.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b69cccd06a4a0a1d9fb3ec9a97600055cf03030ed7048d4bcb88c574f7895773"}, - {file = "zstandard-0.21.0-cp38-cp38-win32.whl", hash = "sha256:9980489f066a391c5572bc7dc471e903fb134e0b0001ea9b1d3eff85af0a6f1b"}, - {file = "zstandard-0.21.0-cp38-cp38-win_amd64.whl", hash = "sha256:0e1e94a9d9e35dc04bf90055e914077c80b1e0c15454cc5419e82529d3e70728"}, - {file = "zstandard-0.21.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d2d61675b2a73edcef5e327e38eb62bdfc89009960f0e3991eae5cc3d54718de"}, - {file = "zstandard-0.21.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:25fbfef672ad798afab12e8fd204d122fca3bc8e2dcb0a2ba73bf0a0ac0f5f07"}, - {file = "zstandard-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62957069a7c2626ae80023998757e27bd28d933b165c487ab6f83ad3337f773d"}, - {file = "zstandard-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14e10ed461e4807471075d4b7a2af51f5234c8f1e2a0c1d37d5ca49aaaad49e8"}, - {file = "zstandard-0.21.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9cff89a036c639a6a9299bf19e16bfb9ac7def9a7634c52c257166db09d950e7"}, - {file = "zstandard-0.21.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52b2b5e3e7670bd25835e0e0730a236f2b0df87672d99d3bf4bf87248aa659fb"}, - {file = "zstandard-0.21.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b1367da0dde8ae5040ef0413fb57b5baeac39d8931c70536d5f013b11d3fc3a5"}, - {file = "zstandard-0.21.0-cp39-cp39-win32.whl", hash = "sha256:db62cbe7a965e68ad2217a056107cc43d41764c66c895be05cf9c8b19578ce9c"}, - {file = "zstandard-0.21.0-cp39-cp39-win_amd64.whl", hash = "sha256:a8d200617d5c876221304b0e3fe43307adde291b4a897e7b0617a61611dfff6a"}, - {file = "zstandard-0.21.0.tar.gz", hash = "sha256:f08e3a10d01a247877e4cb61a82a319ea746c356a3786558bed2481e6c405546"}, -] - -[package.dependencies] -cffi = {version = ">=1.11", markers = "platform_python_implementation == \"PyPy\""} - -[package.extras] -cffi = ["cffi (>=1.11)"] - -[metadata] -lock-version = "2.0" -python-versions = ">=3.10,<=3.13" -content-hash = "1faf17e63a9c58f3f6549d6b345993cdf0cbb0851ab563fb539086bbc95948d4" diff --git a/pyproject.toml b/pyproject.toml index bc3303d2..e8915c6e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,18 +1,53 @@ -[tool.poetry] + +[project] name = "grype-db-manager" -version = "0.1.0" description = "a tool for publishing validated grype databases to S3 for distribution" -authors = ["Alex Goodman "] -license = "Apache 2.0" -packages = [{include = "grype_db_manager", from = "manager/src"}] -repository = "https://github.com/anchore/grype-db" -exclude = [ - "manager/tests/**/*" +dynamic = ["version"] +authors = [ + {name = "Alex Goodman", email = "wagoodman@users.noreply.github.com"}, +] +license = {text = "Apache 2.0"} +requires-python = "<3.14,>=3.11" +dependencies = [ + "boto3<2,>=1.28.16", + "click<9,>=8.1.6", + "dataclass-wizard<1,>=0.22.2", + "iso8601<3,>=2.0.0", + "requests<3,>=2.32.3", + "semver<4,>=3.0.1", + "tabulate<1,>=0.9.0", + "zstandard<1,>=0.21.0", + "colorlog<7,>=6.7.0", + "mergedeep<2,>=1.3.4", + "pyyaml<7,>=5.0.1", + "pyyaml-include<2,>=1.3.1", + "python-magic<1,>=0.4.27", + "yardstick<1,>=0.12.0", ] -[tool.poetry.scripts] +[project.urls] +repository = "https://github.com/anchore/grype-db" + +[project.scripts] grype-db-manager = "grype_db_manager.cli:run" +[dependency-groups] +dev = [ + "mypy<2,>=1.4.1", + "pytest<8,>=7.4.0", + "pytest-sugar>=0.9.7", + "pytest-unordered>=0.5.2", + "pytest-cov>=4.1.0", + "pytest-picked>=0.5.0", + "pytest-mock>=3.11.1", + "pytest-xdist>=3.3.1", + "ruff<1,>=0.0.282", + "types-requests<3,>=2.31.0.2", + "moto<5,>=4.1.14", + "coverage[toml]<8.0.0,>=7.6.10", + "uv-dynamic-versioning>=0.6.0", +] + # this section enables searching for package data... [tool.setuptools.packages.find] namespaces = true @@ -23,69 +58,46 @@ where = ["manager/src"] [tool.setuptools.package-data] "grype_db_manager.data" = ["*.json"] # the grype-schema-version-mapping.json file -[tool.poetry.dependencies] -python = ">=3.10,<=3.13" -boto3 = ">=1.28.16, <2" -click = ">=8.1.6, <9" -dataclass-wizard = ">=0.22.2, <1" -iso8601 = ">=2.0.0, <3" -requests = ">=2.31.0, <3" -semver = ">=3.0.1, <4" -tabulate = ">=0.9.0, <1" -zstandard = ">=0.21.0, <1" -colorlog = "^6.7.0" -mergedeep = "^1.3.4" -pyyaml = ">=5.0.1, <7" - yardstick = {git = "https://github.com/anchore/yardstick", rev = "dc54ea796677e9c310041514875f56647f52b78d"} -#yardstick = {path = "../yardstick", develop = true} -colr = "^0.9.1" -supports-color = "^0.1.2" -pyyaml-include = "^1.3.1" -python-magic = "^0.4.27" - -[tool.poetry.group.dev.dependencies] -black = ">=23.7.0, <24" -mypy = ">=1.4.1, <2" -pytest = ">=7.4.0, <8" -pytest-sugar = ">=0.9.7" -pytest-unordered = ">=0.5.2" -pytest-cov = ">=4.1.0" -pytest-picked = ">=0.5.0" -pytest-mock = ">=3.11.1" -pytest-xdist = ">=3.3.1" -ruff = "0.0.282" # the ruff project is pretty early, even a patch bump tends to break stuff -types-requests = "^2.31.0.2" -vunnel = "^0.15.2" -moto = "^4.1.14" [build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" +build-backend = "hatchling.build" +requires = ["hatchling", "uv-dynamic-versioning"] + +[tool.hatch.build.targets.sdist] +include = [ + "manager/src/grype_db_manager/**" +] +exclude = [ + "manager/tests/**" +] + +[tool.hatch.build.targets.wheel] +packages = ["manager/src/grype_db_manager"] + +[tool.hatch.version] +source = "uv-dynamic-versioning" + +[tool.uv-dynamic-versioning] +vcs = "git" +style = "semver" [tool.pytest.ini_options] testpaths = ["manager/tests"] cache_dir = ".cache/pytest" pythonpath = ["manager/src"] norecursedirs = ["data"] - -[tool.black] -line-length = 130 - -[tool.coverage.run] -source = ['grype_db_manager'] -omit = [ - ".*", - "*/site-packages/*", - "*/venv/*", - "*/tests/*", - "*/src/grype_db_manager/__main__.py", - "*/src/grype_db_manager/cli/__init__.py", -] +log_format = "%(levelname)-6s %(message)s" +log_cli_level = "INFO" [tool.ruff] cache-dir = ".cache/ruff" # allow for a wide-birth relative to what black will correct to line-length = 150 +extend-exclude = [ + "**/tests/**", +] + +[tool.ruff.lint] select = [ "A", # flake8-builtins "ANN", # flake8-annotations @@ -130,8 +142,6 @@ ignore = [ "ANN401", # should allow for typing.Any in *args and **kwargs "ANN002", # don't require type annotations for arbirary *args "ANN003", # don't require type annotations for arbirary **kwargs - "ANN101", # annotating self if useless on an instance method - "ANN102", # annotating cls if useless on a class method "ANN204", # annotating return type of __init__ and __post_init__ is useless "ARG001", # unused args are ok, as they communicate intent in interfaces, even if not used in impls. "ARG002", # unused args are ok, as they communicate intent in interfaces, even if not used in impls. @@ -144,9 +154,7 @@ ignore = [ "UP038", # Use `X | Y` in `isinstance` call instead of `(X, Y)` -- not compatible with python 3.9 (even with __future__ import) ] -extend-exclude = [ - "**/tests/**", -] -[tool.ruff.pylint] +[tool.ruff.lint.pylint] max-args = 8 + diff --git a/test/db/acceptance.sh b/test/db/acceptance.sh index 7925b2c7..5f197bbc 100755 --- a/test/db/acceptance.sh +++ b/test/db/acceptance.sh @@ -26,4 +26,4 @@ fi title "Validating DB" -grype-db-manager db validate $DB_ID -vvv +grype-db-manager -vv db validate $DB_ID diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..5e7dadf5 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1324 @@ +version = 1 +revision = 1 +requires-python = ">=3.11, <3.14" + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, +] + +[[package]] +name = "boto3" +version = "1.36.20" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, + { name = "jmespath" }, + { name = "s3transfer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/9a/f63b820ece3f0411645e686fe48a9ea9daf9ee07fa5dcb07dfb5df8267ec/boto3-1.36.20.tar.gz", hash = "sha256:4a27ffc0543c2a429600542047f00c6a1e95270139d36d8cc636e9cc9a78b835", size = 111015 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/44/882ab747cfe2164eecd6ab780c11e3d875cffb53b86118664a44b9fb71c1/boto3-1.36.20-py3-none-any.whl", hash = "sha256:e132e31232ee107f1c187f566d96863a907433e5bdd8d8928effddd30a96242f", size = 139179 }, +] + +[[package]] +name = "botocore" +version = "1.36.20" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jmespath" }, + { name = "python-dateutil" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/d5/2d54195cefa89708186c036db360d9be23aabdb93d8982df9c08e6df1ef8/botocore-1.36.20.tar.gz", hash = "sha256:3815a05518ff03a8dbc8d5a3c29b95889409a25ac87a282067f6e26fefb7c40a", size = 13521540 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/6c/0553a1641c749ec8838e153fb77ff17f4de56792d289adf039385294257f/botocore-1.36.20-py3-none-any.whl", hash = "sha256:0110bf2208e4569659d0ccfca94baa4999501334397987b02712a94493cbf48b", size = 13349288 }, +] + +[[package]] +name = "certifi" +version = "2025.1.31" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264 }, + { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651 }, + { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 }, + { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200 }, + { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235 }, + { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721 }, + { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242 }, + { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999 }, + { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242 }, + { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604 }, + { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727 }, + { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400 }, + { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 }, + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995 }, + { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471 }, + { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831 }, + { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335 }, + { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862 }, + { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673 }, + { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211 }, + { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039 }, + { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939 }, + { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075 }, + { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340 }, + { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205 }, + { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441 }, + { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, + { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, + { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, + { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, + { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, + { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, + { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, + { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, + { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, + { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, + { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, + { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, + { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, + { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, + { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, + { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, + { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, + { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, + { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, + { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, + { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, + { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, + { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, + { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, + { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, + { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, + { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, +] + +[[package]] +name = "click" +version = "8.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "colorlog" +version = "6.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d3/7a/359f4d5df2353f26172b3cc39ea32daa39af8de522205f512f458923e677/colorlog-6.9.0.tar.gz", hash = "sha256:bfba54a1b93b94f54e1f4fe48395725a3d92fd2a4af702f6bd70946bdc0c6ac2", size = 16624 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/51/9b208e85196941db2f0654ad0357ca6388ab3ed67efdbfc799f35d1f83aa/colorlog-6.9.0-py3-none-any.whl", hash = "sha256:5906e71acd67cb07a71e779c47c4bcb45fb8c2993eebe9e5adcd6a6f1b283eff", size = 11424 }, +] + +[[package]] +name = "coverage" +version = "7.6.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/d6/2b53ab3ee99f2262e6f0b8369a43f6d66658eab45510331c0b3d5c8c4272/coverage-7.6.12.tar.gz", hash = "sha256:48cfc4641d95d34766ad41d9573cc0f22a48aa88d22657a1fe01dca0dbae4de2", size = 805941 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/2d/da78abbfff98468c91fd63a73cccdfa0e99051676ded8dd36123e3a2d4d5/coverage-7.6.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e18aafdfb3e9ec0d261c942d35bd7c28d031c5855dadb491d2723ba54f4c3015", size = 208464 }, + { url = "https://files.pythonhosted.org/packages/31/f2/c269f46c470bdabe83a69e860c80a82e5e76840e9f4bbd7f38f8cebbee2f/coverage-7.6.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66fe626fd7aa5982cdebad23e49e78ef7dbb3e3c2a5960a2b53632f1f703ea45", size = 208893 }, + { url = "https://files.pythonhosted.org/packages/47/63/5682bf14d2ce20819998a49c0deadb81e608a59eed64d6bc2191bc8046b9/coverage-7.6.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ef01d70198431719af0b1f5dcbefc557d44a190e749004042927b2a3fed0702", size = 241545 }, + { url = "https://files.pythonhosted.org/packages/6a/b6/6b6631f1172d437e11067e1c2edfdb7238b65dff965a12bce3b6d1bf2be2/coverage-7.6.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e92ae5a289a4bc4c0aae710c0948d3c7892e20fd3588224ebe242039573bf0", size = 239230 }, + { url = "https://files.pythonhosted.org/packages/c7/01/9cd06cbb1be53e837e16f1b4309f6357e2dfcbdab0dd7cd3b1a50589e4e1/coverage-7.6.12-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e695df2c58ce526eeab11a2e915448d3eb76f75dffe338ea613c1201b33bab2f", size = 241013 }, + { url = "https://files.pythonhosted.org/packages/4b/26/56afefc03c30871326e3d99709a70d327ac1f33da383cba108c79bd71563/coverage-7.6.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d74c08e9aaef995f8c4ef6d202dbd219c318450fe2a76da624f2ebb9c8ec5d9f", size = 239750 }, + { url = "https://files.pythonhosted.org/packages/dd/ea/88a1ff951ed288f56aa561558ebe380107cf9132facd0b50bced63ba7238/coverage-7.6.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e995b3b76ccedc27fe4f477b349b7d64597e53a43fc2961db9d3fbace085d69d", size = 238462 }, + { url = "https://files.pythonhosted.org/packages/6e/d4/1d9404566f553728889409eff82151d515fbb46dc92cbd13b5337fa0de8c/coverage-7.6.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b1f097878d74fe51e1ddd1be62d8e3682748875b461232cf4b52ddc6e6db0bba", size = 239307 }, + { url = "https://files.pythonhosted.org/packages/12/c1/e453d3b794cde1e232ee8ac1d194fde8e2ba329c18bbf1b93f6f5eef606b/coverage-7.6.12-cp311-cp311-win32.whl", hash = "sha256:1f7ffa05da41754e20512202c866d0ebfc440bba3b0ed15133070e20bf5aeb5f", size = 211117 }, + { url = "https://files.pythonhosted.org/packages/d5/db/829185120c1686fa297294f8fcd23e0422f71070bf85ef1cc1a72ecb2930/coverage-7.6.12-cp311-cp311-win_amd64.whl", hash = "sha256:e216c5c45f89ef8971373fd1c5d8d1164b81f7f5f06bbf23c37e7908d19e8558", size = 212019 }, + { url = "https://files.pythonhosted.org/packages/e2/7f/4af2ed1d06ce6bee7eafc03b2ef748b14132b0bdae04388e451e4b2c529b/coverage-7.6.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b172f8e030e8ef247b3104902cc671e20df80163b60a203653150d2fc204d1ad", size = 208645 }, + { url = "https://files.pythonhosted.org/packages/dc/60/d19df912989117caa95123524d26fc973f56dc14aecdec5ccd7d0084e131/coverage-7.6.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:641dfe0ab73deb7069fb972d4d9725bf11c239c309ce694dd50b1473c0f641c3", size = 208898 }, + { url = "https://files.pythonhosted.org/packages/bd/10/fecabcf438ba676f706bf90186ccf6ff9f6158cc494286965c76e58742fa/coverage-7.6.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e549f54ac5f301e8e04c569dfdb907f7be71b06b88b5063ce9d6953d2d58574", size = 242987 }, + { url = "https://files.pythonhosted.org/packages/4c/53/4e208440389e8ea936f5f2b0762dcd4cb03281a7722def8e2bf9dc9c3d68/coverage-7.6.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959244a17184515f8c52dcb65fb662808767c0bd233c1d8a166e7cf74c9ea985", size = 239881 }, + { url = "https://files.pythonhosted.org/packages/c4/47/2ba744af8d2f0caa1f17e7746147e34dfc5f811fb65fc153153722d58835/coverage-7.6.12-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bda1c5f347550c359f841d6614fb8ca42ae5cb0b74d39f8a1e204815ebe25750", size = 242142 }, + { url = "https://files.pythonhosted.org/packages/e9/90/df726af8ee74d92ee7e3bf113bf101ea4315d71508952bd21abc3fae471e/coverage-7.6.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ceeb90c3eda1f2d8c4c578c14167dbd8c674ecd7d38e45647543f19839dd6ea", size = 241437 }, + { url = "https://files.pythonhosted.org/packages/f6/af/995263fd04ae5f9cf12521150295bf03b6ba940d0aea97953bb4a6db3e2b/coverage-7.6.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f16f44025c06792e0fb09571ae454bcc7a3ec75eeb3c36b025eccf501b1a4c3", size = 239724 }, + { url = "https://files.pythonhosted.org/packages/1c/8e/5bb04f0318805e190984c6ce106b4c3968a9562a400180e549855d8211bd/coverage-7.6.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b076e625396e787448d27a411aefff867db2bffac8ed04e8f7056b07024eed5a", size = 241329 }, + { url = "https://files.pythonhosted.org/packages/9e/9d/fa04d9e6c3f6459f4e0b231925277cfc33d72dfab7fa19c312c03e59da99/coverage-7.6.12-cp312-cp312-win32.whl", hash = "sha256:00b2086892cf06c7c2d74983c9595dc511acca00665480b3ddff749ec4fb2a95", size = 211289 }, + { url = "https://files.pythonhosted.org/packages/53/40/53c7ffe3c0c3fff4d708bc99e65f3d78c129110d6629736faf2dbd60ad57/coverage-7.6.12-cp312-cp312-win_amd64.whl", hash = "sha256:7ae6eabf519bc7871ce117fb18bf14e0e343eeb96c377667e3e5dd12095e0288", size = 212079 }, + { url = "https://files.pythonhosted.org/packages/76/89/1adf3e634753c0de3dad2f02aac1e73dba58bc5a3a914ac94a25b2ef418f/coverage-7.6.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:488c27b3db0ebee97a830e6b5a3ea930c4a6e2c07f27a5e67e1b3532e76b9ef1", size = 208673 }, + { url = "https://files.pythonhosted.org/packages/ce/64/92a4e239d64d798535c5b45baac6b891c205a8a2e7c9cc8590ad386693dc/coverage-7.6.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d1095bbee1851269f79fd8e0c9b5544e4c00c0c24965e66d8cba2eb5bb535fd", size = 208945 }, + { url = "https://files.pythonhosted.org/packages/b4/d0/4596a3ef3bca20a94539c9b1e10fd250225d1dec57ea78b0867a1cf9742e/coverage-7.6.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0533adc29adf6a69c1baa88c3d7dbcaadcffa21afbed3ca7a225a440e4744bf9", size = 242484 }, + { url = "https://files.pythonhosted.org/packages/1c/ef/6fd0d344695af6718a38d0861408af48a709327335486a7ad7e85936dc6e/coverage-7.6.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53c56358d470fa507a2b6e67a68fd002364d23c83741dbc4c2e0680d80ca227e", size = 239525 }, + { url = "https://files.pythonhosted.org/packages/0c/4b/373be2be7dd42f2bcd6964059fd8fa307d265a29d2b9bcf1d044bcc156ed/coverage-7.6.12-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64cbb1a3027c79ca6310bf101014614f6e6e18c226474606cf725238cf5bc2d4", size = 241545 }, + { url = "https://files.pythonhosted.org/packages/a6/7d/0e83cc2673a7790650851ee92f72a343827ecaaea07960587c8f442b5cd3/coverage-7.6.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79cac3390bfa9836bb795be377395f28410811c9066bc4eefd8015258a7578c6", size = 241179 }, + { url = "https://files.pythonhosted.org/packages/ff/8c/566ea92ce2bb7627b0900124e24a99f9244b6c8c92d09ff9f7633eb7c3c8/coverage-7.6.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b148068e881faa26d878ff63e79650e208e95cf1c22bd3f77c3ca7b1d9821a3", size = 239288 }, + { url = "https://files.pythonhosted.org/packages/7d/e4/869a138e50b622f796782d642c15fb5f25a5870c6d0059a663667a201638/coverage-7.6.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8bec2ac5da793c2685ce5319ca9bcf4eee683b8a1679051f8e6ec04c4f2fd7dc", size = 241032 }, + { url = "https://files.pythonhosted.org/packages/ae/28/a52ff5d62a9f9e9fe9c4f17759b98632edd3a3489fce70154c7d66054dd3/coverage-7.6.12-cp313-cp313-win32.whl", hash = "sha256:200e10beb6ddd7c3ded322a4186313d5ca9e63e33d8fab4faa67ef46d3460af3", size = 211315 }, + { url = "https://files.pythonhosted.org/packages/bc/17/ab849b7429a639f9722fa5628364c28d675c7ff37ebc3268fe9840dda13c/coverage-7.6.12-cp313-cp313-win_amd64.whl", hash = "sha256:2b996819ced9f7dbb812c701485d58f261bef08f9b85304d41219b1496b591ef", size = 212099 }, + { url = "https://files.pythonhosted.org/packages/d2/1c/b9965bf23e171d98505eb5eb4fb4d05c44efd256f2e0f19ad1ba8c3f54b0/coverage-7.6.12-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:299cf973a7abff87a30609879c10df0b3bfc33d021e1adabc29138a48888841e", size = 209511 }, + { url = "https://files.pythonhosted.org/packages/57/b3/119c201d3b692d5e17784fee876a9a78e1b3051327de2709392962877ca8/coverage-7.6.12-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4b467a8c56974bf06e543e69ad803c6865249d7a5ccf6980457ed2bc50312703", size = 209729 }, + { url = "https://files.pythonhosted.org/packages/52/4e/a7feb5a56b266304bc59f872ea07b728e14d5a64f1ad3a2cc01a3259c965/coverage-7.6.12-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2458f275944db8129f95d91aee32c828a408481ecde3b30af31d552c2ce284a0", size = 253988 }, + { url = "https://files.pythonhosted.org/packages/65/19/069fec4d6908d0dae98126aa7ad08ce5130a6decc8509da7740d36e8e8d2/coverage-7.6.12-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a9d8be07fb0832636a0f72b80d2a652fe665e80e720301fb22b191c3434d924", size = 249697 }, + { url = "https://files.pythonhosted.org/packages/1c/da/5b19f09ba39df7c55f77820736bf17bbe2416bbf5216a3100ac019e15839/coverage-7.6.12-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d47376a4f445e9743f6c83291e60adb1b127607a3618e3185bbc8091f0467b", size = 252033 }, + { url = "https://files.pythonhosted.org/packages/1e/89/4c2750df7f80a7872267f7c5fe497c69d45f688f7b3afe1297e52e33f791/coverage-7.6.12-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b95574d06aa9d2bd6e5cc35a5bbe35696342c96760b69dc4287dbd5abd4ad51d", size = 251535 }, + { url = "https://files.pythonhosted.org/packages/78/3b/6d3ae3c1cc05f1b0460c51e6f6dcf567598cbd7c6121e5ad06643974703c/coverage-7.6.12-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ecea0c38c9079570163d663c0433a9af4094a60aafdca491c6a3d248c7432827", size = 249192 }, + { url = "https://files.pythonhosted.org/packages/6e/8e/c14a79f535ce41af7d436bbad0d3d90c43d9e38ec409b4770c894031422e/coverage-7.6.12-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2251fabcfee0a55a8578a9d29cecfee5f2de02f11530e7d5c5a05859aa85aee9", size = 250627 }, + { url = "https://files.pythonhosted.org/packages/cb/79/b7cee656cfb17a7f2c1b9c3cee03dd5d8000ca299ad4038ba64b61a9b044/coverage-7.6.12-cp313-cp313t-win32.whl", hash = "sha256:eb5507795caabd9b2ae3f1adc95f67b1104971c22c624bb354232d65c4fc90b3", size = 212033 }, + { url = "https://files.pythonhosted.org/packages/b6/c3/f7aaa3813f1fa9a4228175a7bd368199659d392897e184435a3b66408dd3/coverage-7.6.12-cp313-cp313t-win_amd64.whl", hash = "sha256:f60a297c3987c6c02ffb29effc70eadcbb412fe76947d394a1091a3615948e2f", size = 213240 }, + { url = "https://files.pythonhosted.org/packages/fb/b2/f655700e1024dec98b10ebaafd0cedbc25e40e4abe62a3c8e2ceef4f8f0a/coverage-7.6.12-py3-none-any.whl", hash = "sha256:eb8668cfbc279a536c633137deeb9435d2962caec279c3f8cf8b91fff6ff8953", size = 200552 }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + +[[package]] +name = "cryptography" +version = "44.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/67/545c79fe50f7af51dbad56d16b23fe33f63ee6a5d956b3cb68ea110cbe64/cryptography-44.0.1.tar.gz", hash = "sha256:f51f5705ab27898afda1aaa430f34ad90dc117421057782022edf0600bec5f14", size = 710819 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/27/5e3524053b4c8889da65cf7814a9d0d8514a05194a25e1e34f46852ee6eb/cryptography-44.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf688f615c29bfe9dfc44312ca470989279f0e94bb9f631f85e3459af8efc009", size = 6642022 }, + { url = "https://files.pythonhosted.org/packages/34/b9/4d1fa8d73ae6ec350012f89c3abfbff19fc95fe5420cf972e12a8d182986/cryptography-44.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd7c7e2d71d908dc0f8d2027e1604102140d84b155e658c20e8ad1304317691f", size = 3943865 }, + { url = "https://files.pythonhosted.org/packages/6e/57/371a9f3f3a4500807b5fcd29fec77f418ba27ffc629d88597d0d1049696e/cryptography-44.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:887143b9ff6bad2b7570da75a7fe8bbf5f65276365ac259a5d2d5147a73775f2", size = 4162562 }, + { url = "https://files.pythonhosted.org/packages/c5/1d/5b77815e7d9cf1e3166988647f336f87d5634a5ccecec2ffbe08ef8dd481/cryptography-44.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:322eb03ecc62784536bc173f1483e76747aafeb69c8728df48537eb431cd1911", size = 3951923 }, + { url = "https://files.pythonhosted.org/packages/28/01/604508cd34a4024467cd4105887cf27da128cba3edd435b54e2395064bfb/cryptography-44.0.1-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:21377472ca4ada2906bc313168c9dc7b1d7ca417b63c1c3011d0c74b7de9ae69", size = 3685194 }, + { url = "https://files.pythonhosted.org/packages/c6/3d/d3c55d4f1d24580a236a6753902ef6d8aafd04da942a1ee9efb9dc8fd0cb/cryptography-44.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:df978682c1504fc93b3209de21aeabf2375cb1571d4e61907b3e7a2540e83026", size = 4187790 }, + { url = "https://files.pythonhosted.org/packages/ea/a6/44d63950c8588bfa8594fd234d3d46e93c3841b8e84a066649c566afb972/cryptography-44.0.1-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:eb3889330f2a4a148abead555399ec9a32b13b7c8ba969b72d8e500eb7ef84cd", size = 3951343 }, + { url = "https://files.pythonhosted.org/packages/c1/17/f5282661b57301204cbf188254c1a0267dbd8b18f76337f0a7ce1038888c/cryptography-44.0.1-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:8e6a85a93d0642bd774460a86513c5d9d80b5c002ca9693e63f6e540f1815ed0", size = 4187127 }, + { url = "https://files.pythonhosted.org/packages/f3/68/abbae29ed4f9d96596687f3ceea8e233f65c9645fbbec68adb7c756bb85a/cryptography-44.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6f76fdd6fd048576a04c5210d53aa04ca34d2ed63336d4abd306d0cbe298fddf", size = 4070666 }, + { url = "https://files.pythonhosted.org/packages/0f/10/cf91691064a9e0a88ae27e31779200b1505d3aee877dbe1e4e0d73b4f155/cryptography-44.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6c8acf6f3d1f47acb2248ec3ea261171a671f3d9428e34ad0357148d492c7864", size = 4288811 }, + { url = "https://files.pythonhosted.org/packages/38/78/74ea9eb547d13c34e984e07ec8a473eb55b19c1451fe7fc8077c6a4b0548/cryptography-44.0.1-cp37-abi3-win32.whl", hash = "sha256:24979e9f2040c953a94bf3c6782e67795a4c260734e5264dceea65c8f4bae64a", size = 2771882 }, + { url = "https://files.pythonhosted.org/packages/cf/6c/3907271ee485679e15c9f5e93eac6aa318f859b0aed8d369afd636fafa87/cryptography-44.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:fd0ee90072861e276b0ff08bd627abec29e32a53b2be44e41dbcdf87cbee2b00", size = 3206989 }, + { url = "https://files.pythonhosted.org/packages/9f/f1/676e69c56a9be9fd1bffa9bc3492366901f6e1f8f4079428b05f1414e65c/cryptography-44.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a2d8a7045e1ab9b9f803f0d9531ead85f90c5f2859e653b61497228b18452008", size = 6643714 }, + { url = "https://files.pythonhosted.org/packages/ba/9f/1775600eb69e72d8f9931a104120f2667107a0ee478f6ad4fe4001559345/cryptography-44.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8272f257cf1cbd3f2e120f14c68bff2b6bdfcc157fafdee84a1b795efd72862", size = 3943269 }, + { url = "https://files.pythonhosted.org/packages/25/ba/e00d5ad6b58183829615be7f11f55a7b6baa5a06910faabdc9961527ba44/cryptography-44.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e8d181e90a777b63f3f0caa836844a1182f1f265687fac2115fcf245f5fbec3", size = 4166461 }, + { url = "https://files.pythonhosted.org/packages/b3/45/690a02c748d719a95ab08b6e4decb9d81e0ec1bac510358f61624c86e8a3/cryptography-44.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:436df4f203482f41aad60ed1813811ac4ab102765ecae7a2bbb1dbb66dcff5a7", size = 3950314 }, + { url = "https://files.pythonhosted.org/packages/e6/50/bf8d090911347f9b75adc20f6f6569ed6ca9b9bff552e6e390f53c2a1233/cryptography-44.0.1-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4f422e8c6a28cf8b7f883eb790695d6d45b0c385a2583073f3cec434cc705e1a", size = 3686675 }, + { url = "https://files.pythonhosted.org/packages/e1/e7/cfb18011821cc5f9b21efb3f94f3241e3a658d267a3bf3a0f45543858ed8/cryptography-44.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:72198e2b5925155497a5a3e8c216c7fb3e64c16ccee11f0e7da272fa93b35c4c", size = 4190429 }, + { url = "https://files.pythonhosted.org/packages/07/ef/77c74d94a8bfc1a8a47b3cafe54af3db537f081742ee7a8a9bd982b62774/cryptography-44.0.1-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:2a46a89ad3e6176223b632056f321bc7de36b9f9b93b2cc1cccf935a3849dc62", size = 3950039 }, + { url = "https://files.pythonhosted.org/packages/6d/b9/8be0ff57c4592382b77406269b1e15650c9f1a167f9e34941b8515b97159/cryptography-44.0.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:53f23339864b617a3dfc2b0ac8d5c432625c80014c25caac9082314e9de56f41", size = 4189713 }, + { url = "https://files.pythonhosted.org/packages/78/e1/4b6ac5f4100545513b0847a4d276fe3c7ce0eacfa73e3b5ebd31776816ee/cryptography-44.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:888fcc3fce0c888785a4876ca55f9f43787f4c5c1cc1e2e0da71ad481ff82c5b", size = 4071193 }, + { url = "https://files.pythonhosted.org/packages/3d/cb/afff48ceaed15531eab70445abe500f07f8f96af2bb35d98af6bfa89ebd4/cryptography-44.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:00918d859aa4e57db8299607086f793fa7813ae2ff5a4637e318a25ef82730f7", size = 4289566 }, + { url = "https://files.pythonhosted.org/packages/30/6f/4eca9e2e0f13ae459acd1ca7d9f0257ab86e68f44304847610afcb813dc9/cryptography-44.0.1-cp39-abi3-win32.whl", hash = "sha256:9b336599e2cb77b1008cb2ac264b290803ec5e8e89d618a5e978ff5eb6f715d9", size = 2772371 }, + { url = "https://files.pythonhosted.org/packages/d2/05/5533d30f53f10239616a357f080892026db2d550a40c393d0a8a7af834a9/cryptography-44.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:e403f7f766ded778ecdb790da786b418a9f2394f36e8cc8b796cc056ab05f44f", size = 3207303 }, +] + +[[package]] +name = "dataclass-wizard" +version = "0.35.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/c5/10f2bd575b4fee1cf26b0ffa5fead2d6525f950a797566437a51fa08a94f/dataclass-wizard-0.35.0.tar.gz", hash = "sha256:8e4b254991bf93416a48e2911bb985e3787cff11f00270c3d1165d2523cb3fb6", size = 295578 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/95/968dff23cfd82806bf0e2d1c155227717046aed5e7afd8bc0fefdc7eaaf3/dataclass_wizard-0.35.0-py2.py3-none-any.whl", hash = "sha256:3bb19292477f0bebb12e9cc9178f1a6b93d133af4ae065abf14b713142b32edf", size = 176558 }, +] + +[[package]] +name = "dataclasses-json" +version = "0.6.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "marshmallow" }, + { name = "typing-inspect" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/a4/f71d9cf3a5ac257c993b5ca3f93df5f7fb395c725e7f1e6479d2514173c3/dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0", size = 32227 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/be/d0d44e092656fe7a06b55e6103cbce807cdbdee17884a5367c68c9860853/dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a", size = 28686 }, +] + +[[package]] +name = "dunamai" +version = "1.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/4e/a5c8c337a1d9ac0384298ade02d322741fb5998041a5ea74d1cd2a4a1d47/dunamai-1.23.0.tar.gz", hash = "sha256:a163746de7ea5acb6dacdab3a6ad621ebc612ed1e528aaa8beedb8887fccd2c4", size = 44681 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/4c/963169386309fec4f96fd61210ac0a0666887d0fb0a50205395674d20b71/dunamai-1.23.0-py3-none-any.whl", hash = "sha256:a0906d876e92441793c6a423e16a4802752e723e9c9a5aabdc5535df02dbe041", size = 26342 }, +] + +[[package]] +name = "execnet" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/ff/b4c0dc78fbe20c3e59c0c7334de0c27eb4001a2b2017999af398bf730817/execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3", size = 166524 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc", size = 40612 }, +] + +[[package]] +name = "gitdb" +version = "4.0.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "smmap" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794 }, +] + +[[package]] +name = "gitpython" +version = "3.1.44" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gitdb" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/89/37df0b71473153574a5cdef8f242de422a0f5d26d7a9e231e6f169b4ad14/gitpython-3.1.44.tar.gz", hash = "sha256:c87e30b26253bf5418b01b0660f818967f3c503193838337fe5e573331249269", size = 214196 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/9a/4114a9057db2f1462d5c8f8390ab7383925fe1ac012eaa42402ad65c2963/GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110", size = 207599 }, +] + +[[package]] +name = "grype-db-manager" +source = { editable = "." } +dependencies = [ + { name = "boto3" }, + { name = "click" }, + { name = "colorlog" }, + { name = "dataclass-wizard" }, + { name = "iso8601" }, + { name = "mergedeep" }, + { name = "python-magic" }, + { name = "pyyaml" }, + { name = "pyyaml-include" }, + { name = "requests" }, + { name = "semver" }, + { name = "tabulate" }, + { name = "yardstick" }, + { name = "zstandard" }, +] + +[package.dev-dependencies] +dev = [ + { name = "coverage", extra = ["toml"] }, + { name = "moto" }, + { name = "mypy" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "pytest-mock" }, + { name = "pytest-picked" }, + { name = "pytest-sugar" }, + { name = "pytest-unordered" }, + { name = "pytest-xdist" }, + { name = "ruff" }, + { name = "types-requests" }, + { name = "uv-dynamic-versioning" }, +] + +[package.metadata] +requires-dist = [ + { name = "boto3", specifier = ">=1.28.16,<2" }, + { name = "click", specifier = ">=8.1.6,<9" }, + { name = "colorlog", specifier = ">=6.7.0,<7" }, + { name = "dataclass-wizard", specifier = ">=0.22.2,<1" }, + { name = "iso8601", specifier = ">=2.0.0,<3" }, + { name = "mergedeep", specifier = ">=1.3.4,<2" }, + { name = "python-magic", specifier = ">=0.4.27,<1" }, + { name = "pyyaml", specifier = ">=5.0.1,<7" }, + { name = "pyyaml-include", specifier = ">=1.3.1,<2" }, + { name = "requests", specifier = ">=2.32.3,<3" }, + { name = "semver", specifier = ">=3.0.1,<4" }, + { name = "tabulate", specifier = ">=0.9.0,<1" }, + { name = "yardstick", specifier = ">=0.12.0,<1" }, + { name = "zstandard", specifier = ">=0.21.0,<1" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "coverage", extras = ["toml"], specifier = ">=7.6.10,<8.0.0" }, + { name = "moto", specifier = ">=4.1.14,<5" }, + { name = "mypy", specifier = ">=1.4.1,<2" }, + { name = "pytest", specifier = ">=7.4.0,<8" }, + { name = "pytest-cov", specifier = ">=4.1.0" }, + { name = "pytest-mock", specifier = ">=3.11.1" }, + { name = "pytest-picked", specifier = ">=0.5.0" }, + { name = "pytest-sugar", specifier = ">=0.9.7" }, + { name = "pytest-unordered", specifier = ">=0.5.2" }, + { name = "pytest-xdist", specifier = ">=3.3.1" }, + { name = "ruff", specifier = ">=0.0.282,<1" }, + { name = "types-requests", specifier = ">=2.31.0.2,<3" }, + { name = "uv-dynamic-versioning", specifier = ">=0.6.0" }, +] + +[[package]] +name = "hatchling" +version = "1.27.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "pathspec" }, + { name = "pluggy" }, + { name = "trove-classifiers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8f/8a/cc1debe3514da292094f1c3a700e4ca25442489731ef7c0814358816bb03/hatchling-1.27.0.tar.gz", hash = "sha256:971c296d9819abb3811112fc52c7a9751c8d381898f36533bb16f9791e941fd6", size = 54983 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/e7/ae38d7a6dfba0533684e0b2136817d667588ae3ec984c1a4e5df5eb88482/hatchling-1.27.0-py3-none-any.whl", hash = "sha256:d3a2f3567c4f926ea39849cdf924c7e99e6686c9c8e288ae1037c8fa2a5d937b", size = 75794 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "importlib-metadata" +version = "8.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/08/c1395a292bb23fd03bdf572a1357c5a733d3eecbab877641ceacab23db6e/importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580", size = 55767 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/9d/0fb148dc4d6fa4a7dd1d8378168d9b4cd8d4560a6fbf6f0121c5fc34eb68/importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e", size = 26971 }, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + +[[package]] +name = "iso8601" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/f3/ef59cee614d5e0accf6fd0cbba025b93b272e626ca89fb70a3e9187c5d15/iso8601-2.1.0.tar.gz", hash = "sha256:6b1d3829ee8921c4301998c909f7829fa9ed3cbdac0d3b16af2d743aed1ba8df", size = 6522 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/0c/f37b6a241f0759b7653ffa7213889d89ad49a2b76eb2ddf3b57b2738c347/iso8601-2.1.0-py3-none-any.whl", hash = "sha256:aac4145c4dcb66ad8b648a02830f5e2ff6c24af20f4f482689be402db2429242", size = 7545 }, +] + +[[package]] +name = "jinja2" +version = "3.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/af/92/b3130cbbf5591acf9ade8708c365f3238046ac7cb8ccba6e81abccb0ccff/jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb", size = 244674 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/0f/2ba5fbcd631e3e88689309dbe978c5769e883e4b84ebfe7da30b43275c5a/jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb", size = 134596 }, +] + +[[package]] +name = "jmespath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/2a/e867e8531cf3e36b41201936b7fa7ba7b5702dbef42922193f05c8976cd6/jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe", size = 25843 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256 }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, + { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, + { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, + { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 }, + { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 }, + { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 }, + { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 }, + { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 }, + { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 }, + { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 }, + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, +] + +[[package]] +name = "marshmallow" +version = "3.26.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/5e/5e53d26b42ab75491cda89b871dab9e97c840bf12c63ec58a1919710cd06/marshmallow-3.26.1.tar.gz", hash = "sha256:e6d8affb6cb61d39d26402096dc0aee12d5a26d490a121f118d2e81dc0719dc6", size = 221825 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/75/51952c7b2d3873b44a0028b1bd26a25078c18f92f256608e8d1dc61b39fd/marshmallow-3.26.1-py3-none-any.whl", hash = "sha256:3350409f20a70a7e4e11a27661187b77cdcaeb20abca41c1454fe33636bea09c", size = 50878 }, +] + +[[package]] +name = "mergedeep" +version = "1.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354 }, +] + +[[package]] +name = "moto" +version = "4.2.14" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "boto3" }, + { name = "botocore" }, + { name = "cryptography" }, + { name = "jinja2" }, + { name = "python-dateutil" }, + { name = "requests" }, + { name = "responses" }, + { name = "werkzeug" }, + { name = "xmltodict" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b2/06671ae921e7c7ebf18a20a6218fb55e83c3784b026eaf3d5670f9315924/moto-4.2.14.tar.gz", hash = "sha256:8f9263ca70b646f091edcc93e97cda864a542e6d16ed04066b1370ed217bd190", size = 4933741 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/0f/ef410e6660d381e04cb6f33065d03b3aefd3444d558fcdd41ef235a1802c/moto-4.2.14-py2.py3-none-any.whl", hash = "sha256:6d242dbbabe925bb385ddb6958449e5c827670b13b8e153ed63f91dbdb50372c", size = 3312702 }, +] + +[[package]] +name = "mypy" +version = "1.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/43/d5e49a86afa64bd3839ea0d5b9c7103487007d728e1293f52525d6d5486a/mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43", size = 3239717 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/bc/f6339726c627bd7ca1ce0fa56c9ae2d0144604a319e0e339bdadafbbb599/mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f", size = 10662338 }, + { url = "https://files.pythonhosted.org/packages/e2/90/8dcf506ca1a09b0d17555cc00cd69aee402c203911410136cd716559efe7/mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5", size = 9787540 }, + { url = "https://files.pythonhosted.org/packages/05/05/a10f9479681e5da09ef2f9426f650d7b550d4bafbef683b69aad1ba87457/mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e", size = 11538051 }, + { url = "https://files.pythonhosted.org/packages/e9/9a/1f7d18b30edd57441a6411fcbc0c6869448d1a4bacbaee60656ac0fc29c8/mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c", size = 12286751 }, + { url = "https://files.pythonhosted.org/packages/72/af/19ff499b6f1dafcaf56f9881f7a965ac2f474f69f6f618b5175b044299f5/mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f", size = 12421783 }, + { url = "https://files.pythonhosted.org/packages/96/39/11b57431a1f686c1aed54bf794870efe0f6aeca11aca281a0bd87a5ad42c/mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f", size = 9265618 }, + { url = "https://files.pythonhosted.org/packages/98/3a/03c74331c5eb8bd025734e04c9840532226775c47a2c39b56a0c8d4f128d/mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd", size = 10793981 }, + { url = "https://files.pythonhosted.org/packages/f0/1a/41759b18f2cfd568848a37c89030aeb03534411eef981df621d8fad08a1d/mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f", size = 9749175 }, + { url = "https://files.pythonhosted.org/packages/12/7e/873481abf1ef112c582db832740f4c11b2bfa510e829d6da29b0ab8c3f9c/mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464", size = 11455675 }, + { url = "https://files.pythonhosted.org/packages/b3/d0/92ae4cde706923a2d3f2d6c39629134063ff64b9dedca9c1388363da072d/mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee", size = 12410020 }, + { url = "https://files.pythonhosted.org/packages/46/8b/df49974b337cce35f828ba6fda228152d6db45fed4c86ba56ffe442434fd/mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e", size = 12498582 }, + { url = "https://files.pythonhosted.org/packages/13/50/da5203fcf6c53044a0b699939f31075c45ae8a4cadf538a9069b165c1050/mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22", size = 9366614 }, + { url = "https://files.pythonhosted.org/packages/6a/9b/fd2e05d6ffff24d912f150b87db9e364fa8282045c875654ce7e32fffa66/mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445", size = 10788592 }, + { url = "https://files.pythonhosted.org/packages/74/37/b246d711c28a03ead1fd906bbc7106659aed7c089d55fe40dd58db812628/mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d", size = 9753611 }, + { url = "https://files.pythonhosted.org/packages/a6/ac/395808a92e10cfdac8003c3de9a2ab6dc7cde6c0d2a4df3df1b815ffd067/mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5", size = 11438443 }, + { url = "https://files.pythonhosted.org/packages/d2/8b/801aa06445d2de3895f59e476f38f3f8d610ef5d6908245f07d002676cbf/mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036", size = 12402541 }, + { url = "https://files.pythonhosted.org/packages/c7/67/5a4268782eb77344cc613a4cf23540928e41f018a9a1ec4c6882baf20ab8/mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357", size = 12494348 }, + { url = "https://files.pythonhosted.org/packages/83/3e/57bb447f7bbbfaabf1712d96f9df142624a386d98fb026a761532526057e/mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf", size = 9373648 }, + { url = "https://files.pythonhosted.org/packages/09/4e/a7d65c7322c510de2c409ff3828b03354a7c43f5a8ed458a7a131b41c7b9/mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e", size = 2221777 }, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, +] + +[[package]] +name = "omitempty" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/37/41f883fd5ca0606a410eddc37fa50db8d19d4f2b763aa57bf25607bf5bad/omitempty-0.1.1.tar.gz", hash = "sha256:761fea43d0edb7a31e3322158f73c97d77e939e57c1e62754be23e081ab853d8", size = 2699 } + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.50" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/e1/bd15cb8ffdcfeeb2bdc215de3c3cffca11408d829e4b8416dcfe71ba8854/prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab", size = 429087 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/ea/d836f008d33151c7a1f62caf3d8dd782e4d15f6a43897f64480c2b8de2ad/prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198", size = 387816 }, +] + +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, +] + +[[package]] +name = "pydantic" +version = "2.10.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696 }, +] + +[[package]] +name = "pydantic-core" +version = "2.27.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/89/f3450af9d09d44eea1f2c369f49e8f181d742f28220f88cc4dfaae91ea6e/pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", size = 1893421 }, + { url = "https://files.pythonhosted.org/packages/9e/e3/71fe85af2021f3f386da42d291412e5baf6ce7716bd7101ea49c810eda90/pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", size = 1814998 }, + { url = "https://files.pythonhosted.org/packages/a6/3c/724039e0d848fd69dbf5806894e26479577316c6f0f112bacaf67aa889ac/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", size = 1826167 }, + { url = "https://files.pythonhosted.org/packages/2b/5b/1b29e8c1fb5f3199a9a57c1452004ff39f494bbe9bdbe9a81e18172e40d3/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", size = 1865071 }, + { url = "https://files.pythonhosted.org/packages/89/6c/3985203863d76bb7d7266e36970d7e3b6385148c18a68cc8915fd8c84d57/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", size = 2036244 }, + { url = "https://files.pythonhosted.org/packages/0e/41/f15316858a246b5d723f7d7f599f79e37493b2e84bfc789e58d88c209f8a/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", size = 2737470 }, + { url = "https://files.pythonhosted.org/packages/a8/7c/b860618c25678bbd6d1d99dbdfdf0510ccb50790099b963ff78a124b754f/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", size = 1992291 }, + { url = "https://files.pythonhosted.org/packages/bf/73/42c3742a391eccbeab39f15213ecda3104ae8682ba3c0c28069fbcb8c10d/pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", size = 1994613 }, + { url = "https://files.pythonhosted.org/packages/94/7a/941e89096d1175d56f59340f3a8ebaf20762fef222c298ea96d36a6328c5/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", size = 2002355 }, + { url = "https://files.pythonhosted.org/packages/6e/95/2359937a73d49e336a5a19848713555605d4d8d6940c3ec6c6c0ca4dcf25/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", size = 2126661 }, + { url = "https://files.pythonhosted.org/packages/2b/4c/ca02b7bdb6012a1adef21a50625b14f43ed4d11f1fc237f9d7490aa5078c/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", size = 2153261 }, + { url = "https://files.pythonhosted.org/packages/72/9d/a241db83f973049a1092a079272ffe2e3e82e98561ef6214ab53fe53b1c7/pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", size = 1812361 }, + { url = "https://files.pythonhosted.org/packages/e8/ef/013f07248041b74abd48a385e2110aa3a9bbfef0fbd97d4e6d07d2f5b89a/pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", size = 1982484 }, + { url = "https://files.pythonhosted.org/packages/10/1c/16b3a3e3398fd29dca77cea0a1d998d6bde3902fa2706985191e2313cc76/pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", size = 1867102 }, + { url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127 }, + { url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340 }, + { url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900 }, + { url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177 }, + { url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046 }, + { url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386 }, + { url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060 }, + { url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870 }, + { url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822 }, + { url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364 }, + { url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303 }, + { url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064 }, + { url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046 }, + { url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092 }, + { url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709 }, + { url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273 }, + { url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027 }, + { url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888 }, + { url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738 }, + { url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138 }, + { url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025 }, + { url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633 }, + { url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404 }, + { url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130 }, + { url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946 }, + { url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387 }, + { url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453 }, + { url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186 }, +] + +[[package]] +name = "pygments" +version = "2.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, +] + +[[package]] +name = "pytest" +version = "7.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/1f/9d8e98e4133ffb16c90f3b405c43e38d3abb715bb5d7a63a5a684f7e46a3/pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280", size = 1357116 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/ff/f6e8b8f39e08547faece4bd80f89d5a8de68a38b2d179cc1c4490ffa3286/pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8", size = 325287 }, +] + +[[package]] +name = "pytest-cov" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/45/9b538de8cef30e17c7b45ef42f538a94889ed6a16f2387a6c89e73220651/pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0", size = 66945 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/3b/48e79f2cd6a61dbbd4807b4ed46cb564b4fd50a76166b1c4ea5c1d9e2371/pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35", size = 22949 }, +] + +[[package]] +name = "pytest-mock" +version = "3.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/90/a955c3ab35ccd41ad4de556596fa86685bf4fc5ffcc62d22d856cfd4e29a/pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0", size = 32814 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/3b/b26f90f74e2986a82df6e7ac7e319b8ea7ccece1caec9f8ab6104dc70603/pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f", size = 9863 }, +] + +[[package]] +name = "pytest-picked" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/e4/51a54dd6638fd4a7c45bb20a737235fd92cbb4d24b5ff681d64ace5d02e9/pytest_picked-0.5.1.tar.gz", hash = "sha256:6634c4356a560a5dc3dba35471865e6eb06bbd356b56b69c540593e9d5620ded", size = 8401 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/81/450c017746caab376c4b6700439de9f1cc7d8e1f22dec3c1eb235cd9ad3e/pytest_picked-0.5.1-py3-none-any.whl", hash = "sha256:af65c4763b51dc095ae4bc5073a962406902422ad9629c26d8b01122b677d998", size = 6608 }, +] + +[[package]] +name = "pytest-sugar" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "pytest" }, + { name = "termcolor" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/ac/5754f5edd6d508bc6493bc37d74b928f102a5fff82d9a80347e180998f08/pytest-sugar-1.0.0.tar.gz", hash = "sha256:6422e83258f5b0c04ce7c632176c7732cab5fdb909cb39cca5c9139f81276c0a", size = 14992 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/fb/889f1b69da2f13691de09a111c16c4766a433382d44aa0ecf221deded44a/pytest_sugar-1.0.0-py3-none-any.whl", hash = "sha256:70ebcd8fc5795dc457ff8b69d266a4e2e8a74ae0c3edc749381c64b5246c8dfd", size = 10171 }, +] + +[[package]] +name = "pytest-unordered" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a0/8f/85275d036f702a5af3b24a5e6460df3a5d3ae8ffae3ed2625fb4cae97f84/pytest_unordered-0.6.1.tar.gz", hash = "sha256:061f7a538247f8adc97a4fcf7415d36e0db4b16548c42d5b49168e6ec2cd95b0", size = 7239 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/65/aae0ad8a7f4cc3e7117ac69ccd81cbd0bc90192485a2f60327c263c22344/pytest_unordered-0.6.1-py3-none-any.whl", hash = "sha256:baa809a0ff811d97cfd85f138dbca52e2d7831612b4e19225b3a65ebd9fce068", size = 5983 }, +] + +[[package]] +name = "pytest-xdist" +version = "3.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "execnet" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/41/c4/3c310a19bc1f1e9ef50075582652673ef2bfc8cd62afef9585683821902f/pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d", size = 84060 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/82/1d96bf03ee4c0fdc3c0cbe61470070e659ca78dc0086fb88b66c185e2449/pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7", size = 46108 }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, +] + +[[package]] +name = "python-magic" +version = "0.4.27" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/db/0b3e28ac047452d079d375ec6798bf76a036a08182dbb39ed38116a49130/python-magic-0.4.27.tar.gz", hash = "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b", size = 14677 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/73/9f872cb81fc5c3bb48f7227872c28975f998f3e7c2b1c16e95e6432bbb90/python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3", size = 13840 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, +] + +[[package]] +name = "pyyaml-include" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7f/be/2d07ad85e3d593d69640876a8686eae2c533db8cb7bf298d25c421b4d2d5/pyyaml-include-1.4.1.tar.gz", hash = "sha256:1a96e33a99a3e56235f5221273832464025f02ff3d8539309a3bf00dec624471", size = 20592 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/ca/6a2cc3a73170d10b5af1f1613baa2ed1f8f46f62dd0bfab2bffd2c2fe260/pyyaml_include-1.4.1-py3-none-any.whl", hash = "sha256:323c7f3a19c82fbc4d73abbaab7ef4f793e146a13383866831631b26ccc7fb00", size = 19079 }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, +] + +[[package]] +name = "responses" +version = "0.25.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, + { name = "requests" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/97/63/759996eea0f17e8dc4c9ea9c60765292d28a7750bdbee073ad55d83caa57/responses-0.25.6.tar.gz", hash = "sha256:eae7ce61a9603004e76c05691e7c389e59652d91e94b419623c12bbfb8e331d8", size = 79145 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/c4/8d23584b3a3471ea6f5a18cfb035e11eeb9fa9b3112d901477c6ad10cc4e/responses-0.25.6-py3-none-any.whl", hash = "sha256:9cac8f21e1193bb150ec557875377e41ed56248aed94e4567ed644db564bacf1", size = 34730 }, +] + +[[package]] +name = "returns" +version = "0.24.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/d4/af041bedc0c304592f052b9a4d6dcbd220a46d730a1fc690c3bfd4dc8c76/returns-0.24.0.tar.gz", hash = "sha256:735091cc798cac3f7cf8566b171922119f7796f2e49d6f65939f3e5a9c7c97f7", size = 107005 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/41/16c192627016fff4989350d883ad82c39f58ade2810a967a67d942658a2d/returns-0.24.0-py3-none-any.whl", hash = "sha256:de82455a76b9fcd86810a49099fa7645eb20920105bc78b942b2f4923c829624", size = 158037 }, +] + +[[package]] +name = "rfc3339" +version = "6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/fb/2835a62f2de226796fce76411daec6b9831eaf6d2fd04994ac1de055dc13/rfc3339-6.2.tar.gz", hash = "sha256:d53c3b5eefaef892b7240ba2a91fef012e86faa4d0a0ca782359c490e00ad4d0", size = 4144 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/2e/48d6cf57dec789c90a7b1cb59a21c3cad509f0ec1284632152f33bb1d88d/rfc3339-6.2-py3-none-any.whl", hash = "sha256:f44316b21b21db90a625cde04ebb0d46268f153e6093021fa5893e92a96f58a3", size = 5515 }, +] + +[[package]] +name = "ruff" +version = "0.9.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/e1/e265aba384343dd8ddd3083f5e33536cd17e1566c41453a5517b5dd443be/ruff-0.9.6.tar.gz", hash = "sha256:81761592f72b620ec8fa1068a6fd00e98a5ebee342a3642efd84454f3031dca9", size = 3639454 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/e3/3d2c022e687e18cf5d93d6bfa2722d46afc64eaa438c7fbbdd603b3597be/ruff-0.9.6-py3-none-linux_armv6l.whl", hash = "sha256:2f218f356dd2d995839f1941322ff021c72a492c470f0b26a34f844c29cdf5ba", size = 11714128 }, + { url = "https://files.pythonhosted.org/packages/e1/22/aff073b70f95c052e5c58153cba735748c9e70107a77d03420d7850710a0/ruff-0.9.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b908ff4df65dad7b251c9968a2e4560836d8f5487c2f0cc238321ed951ea0504", size = 11682539 }, + { url = "https://files.pythonhosted.org/packages/75/a7/f5b7390afd98a7918582a3d256cd3e78ba0a26165a467c1820084587cbf9/ruff-0.9.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b109c0ad2ececf42e75fa99dc4043ff72a357436bb171900714a9ea581ddef83", size = 11132512 }, + { url = "https://files.pythonhosted.org/packages/a6/e3/45de13ef65047fea2e33f7e573d848206e15c715e5cd56095589a7733d04/ruff-0.9.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1de4367cca3dac99bcbd15c161404e849bb0bfd543664db39232648dc00112dc", size = 11929275 }, + { url = "https://files.pythonhosted.org/packages/7d/f2/23d04cd6c43b2e641ab961ade8d0b5edb212ecebd112506188c91f2a6e6c/ruff-0.9.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac3ee4d7c2c92ddfdaedf0bf31b2b176fa7aa8950efc454628d477394d35638b", size = 11466502 }, + { url = "https://files.pythonhosted.org/packages/b5/6f/3a8cf166f2d7f1627dd2201e6cbc4cb81f8b7d58099348f0c1ff7b733792/ruff-0.9.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dc1edd1775270e6aa2386119aea692039781429f0be1e0949ea5884e011aa8e", size = 12676364 }, + { url = "https://files.pythonhosted.org/packages/f5/c4/db52e2189983c70114ff2b7e3997e48c8318af44fe83e1ce9517570a50c6/ruff-0.9.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4a091729086dffa4bd070aa5dab7e39cc6b9d62eb2bef8f3d91172d30d599666", size = 13335518 }, + { url = "https://files.pythonhosted.org/packages/66/44/545f8a4d136830f08f4d24324e7db957c5374bf3a3f7a6c0bc7be4623a37/ruff-0.9.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1bbc6808bf7b15796cef0815e1dfb796fbd383e7dbd4334709642649625e7c5", size = 12823287 }, + { url = "https://files.pythonhosted.org/packages/c5/26/8208ef9ee7431032c143649a9967c3ae1aae4257d95e6f8519f07309aa66/ruff-0.9.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:589d1d9f25b5754ff230dce914a174a7c951a85a4e9270613a2b74231fdac2f5", size = 14592374 }, + { url = "https://files.pythonhosted.org/packages/31/70/e917781e55ff39c5b5208bda384fd397ffd76605e68544d71a7e40944945/ruff-0.9.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc61dd5131742e21103fbbdcad683a8813be0e3c204472d520d9a5021ca8b217", size = 12500173 }, + { url = "https://files.pythonhosted.org/packages/84/f5/e4ddee07660f5a9622a9c2b639afd8f3104988dc4f6ba0b73ffacffa9a8c/ruff-0.9.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5e2d9126161d0357e5c8f30b0bd6168d2c3872372f14481136d13de9937f79b6", size = 11906555 }, + { url = "https://files.pythonhosted.org/packages/f1/2b/6ff2fe383667075eef8656b9892e73dd9b119b5e3add51298628b87f6429/ruff-0.9.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:68660eab1a8e65babb5229a1f97b46e3120923757a68b5413d8561f8a85d4897", size = 11538958 }, + { url = "https://files.pythonhosted.org/packages/3c/db/98e59e90de45d1eb46649151c10a062d5707b5b7f76f64eb1e29edf6ebb1/ruff-0.9.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c4cae6c4cc7b9b4017c71114115db0445b00a16de3bcde0946273e8392856f08", size = 12117247 }, + { url = "https://files.pythonhosted.org/packages/ec/bc/54e38f6d219013a9204a5a2015c09e7a8c36cedcd50a4b01ac69a550b9d9/ruff-0.9.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:19f505b643228b417c1111a2a536424ddde0db4ef9023b9e04a46ed8a1cb4656", size = 12554647 }, + { url = "https://files.pythonhosted.org/packages/a5/7d/7b461ab0e2404293c0627125bb70ac642c2e8d55bf590f6fce85f508f1b2/ruff-0.9.6-py3-none-win32.whl", hash = "sha256:194d8402bceef1b31164909540a597e0d913c0e4952015a5b40e28c146121b5d", size = 9949214 }, + { url = "https://files.pythonhosted.org/packages/ee/30/c3cee10f915ed75a5c29c1e57311282d1a15855551a64795c1b2bbe5cf37/ruff-0.9.6-py3-none-win_amd64.whl", hash = "sha256:03482d5c09d90d4ee3f40d97578423698ad895c87314c4de39ed2af945633caa", size = 10999914 }, + { url = "https://files.pythonhosted.org/packages/e8/a8/d71f44b93e3aa86ae232af1f2126ca7b95c0f515ec135462b3e1f351441c/ruff-0.9.6-py3-none-win_arm64.whl", hash = "sha256:0e2bb706a2be7ddfea4a4af918562fdc1bcb16df255e5fa595bbd800ce322a5a", size = 10177499 }, +] + +[[package]] +name = "s3transfer" +version = "0.11.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/45/2323b5928f86fd29f9afdcef4659f68fa73eaa5356912b774227f5cf46b5/s3transfer-0.11.2.tar.gz", hash = "sha256:3b39185cb72f5acc77db1a58b6e25b977f28d20496b6e58d6813d75f464d632f", size = 147885 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/ac/e7dc469e49048dc57f62e0c555d2ee3117fa30813d2a1a2962cce3a2a82a/s3transfer-0.11.2-py3-none-any.whl", hash = "sha256:be6ecb39fadd986ef1701097771f87e4d2f821f27f6071c872143884d2950fbc", size = 84151 }, +] + +[[package]] +name = "semver" +version = "3.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/d1/d3159231aec234a59dd7d601e9dd9fe96f3afff15efd33c1070019b26132/semver-3.0.4.tar.gz", hash = "sha256:afc7d8c584a5ed0a11033af086e8af226a9c0b206f313e0301f8dd7b6b589602", size = 269730 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/24/4d91e05817e92e3a61c8a21e08fd0f390f5301f1c448b137c57c4bc6e543/semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746", size = 17912 }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, +] + +[[package]] +name = "smmap" +version = "5.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303 }, +] + +[[package]] +name = "tabulate" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252 }, +] + +[[package]] +name = "termcolor" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/72/88311445fd44c455c7d553e61f95412cf89054308a1aa2434ab835075fc5/termcolor-2.5.0.tar.gz", hash = "sha256:998d8d27da6d48442e8e1f016119076b690d962507531df4890fcd2db2ef8a6f", size = 13057 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/be/df630c387a0a054815d60be6a97eb4e8f17385d5d6fe660e1c02750062b4/termcolor-2.5.0-py3-none-any.whl", hash = "sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8", size = 7755 }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, +] + +[[package]] +name = "tomlkit" +version = "0.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/09/a439bec5888f00a54b8b9f05fa94d7f901d6735ef4e55dcec9bc37b5d8fa/tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79", size = 192885 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/b6/a447b5e4ec71e13871be01ba81f5dfc9d0af7e473da256ff46bc0e24026f/tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde", size = 37955 }, +] + +[[package]] +name = "trove-classifiers" +version = "2025.1.15.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/cb/8f6a91c74049180e395590901834d68bef5d6a2ce4c9ca9792cfadc1b9b4/trove_classifiers-2025.1.15.22.tar.gz", hash = "sha256:90af74358d3a01b3532bc7b3c88d8c6a094c2fd50a563d13d9576179326d7ed9", size = 16236 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/c5/6422dbc59954389b20b2aba85b737ab4a552e357e7ea14b52f40312e7c84/trove_classifiers-2025.1.15.22-py3-none-any.whl", hash = "sha256:5f19c789d4f17f501d36c94dbbf969fb3e8c2784d008e6f5164dd2c3d6a2b07c", size = 13610 }, +] + +[[package]] +name = "types-requests" +version = "2.32.0.20241016" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fa/3c/4f2a430c01a22abd49a583b6b944173e39e7d01b688190a5618bd59a2e22/types-requests-2.32.0.20241016.tar.gz", hash = "sha256:0d9cad2f27515d0e3e3da7134a1b6f28fb97129d86b867f24d9c726452634d95", size = 18065 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/01/485b3026ff90e5190b5e24f1711522e06c79f4a56c8f4b95848ac072e20f/types_requests-2.32.0.20241016-py3-none-any.whl", hash = "sha256:4195d62d6d3e043a4eaaf08ff8a62184584d2e8684e9d2aa178c7915a7da3747", size = 15836 }, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, +] + +[[package]] +name = "typing-inspect" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/74/1789779d91f1961fa9438e9a8710cdae6bd138c80d7303996933d117264a/typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78", size = 13825 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/f3/107a22063bf27bdccf2024833d3445f4eea42b2e598abfbd46f6a63b6cb0/typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", size = 8827 }, +] + +[[package]] +name = "urllib3" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, +] + +[[package]] +name = "uv-dynamic-versioning" +version = "0.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dunamai" }, + { name = "hatchling" }, + { name = "pydantic" }, + { name = "returns" }, + { name = "tomlkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/a3/2a639c01dbcd3b27726366a1ed7508bc797ceee7c5ba9aec41f3ff5b014f/uv_dynamic_versioning-0.6.0.tar.gz", hash = "sha256:43a2a18cd60fd7c24abe18a3a0573a9501de92f6267c54a6fa71851c01c3aa87", size = 30897 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/f9/015ec98f688603f18630d2c78101b9251273abf76ae36cccf041eb3fd5ec/uv_dynamic_versioning-0.6.0-py3-none-any.whl", hash = "sha256:2671783473e50377ae800fb9253b5651af40d30300e1485ce1db23718b1e3196", size = 8385 }, +] + +[[package]] +name = "wcwidth" +version = "0.2.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 }, +] + +[[package]] +name = "werkzeug" +version = "3.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498 }, +] + +[[package]] +name = "xmltodict" +version = "0.14.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/50/05/51dcca9a9bf5e1bce52582683ce50980bcadbc4fa5143b9f2b19ab99958f/xmltodict-0.14.2.tar.gz", hash = "sha256:201e7c28bb210e374999d1dde6382923ab0ed1a8a5faeece48ab525b7810a553", size = 51942 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/45/fc303eb433e8a2a271739c98e953728422fa61a3c1f36077a49e395c972e/xmltodict-0.14.2-py2.py3-none-any.whl", hash = "sha256:20cc7d723ed729276e808f26fb6b3599f786cbc37e06c65e192ba77c40f20aac", size = 9981 }, +] + +[[package]] +name = "xxhash" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/5e/d6e5258d69df8b4ed8c83b6664f2b47d30d2dec551a29ad72a6c69eafd31/xxhash-3.5.0.tar.gz", hash = "sha256:84f2caddf951c9cbf8dc2e22a89d4ccf5d86391ac6418fe81e3c67d0cf60b45f", size = 84241 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/c7/afed0f131fbda960ff15eee7f304fa0eeb2d58770fade99897984852ef23/xxhash-3.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02c2e816896dc6f85922ced60097bcf6f008dedfc5073dcba32f9c8dd786f3c1", size = 31969 }, + { url = "https://files.pythonhosted.org/packages/8c/0c/7c3bc6d87e5235672fcc2fb42fd5ad79fe1033925f71bf549ee068c7d1ca/xxhash-3.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6027dcd885e21581e46d3c7f682cfb2b870942feeed58a21c29583512c3f09f8", size = 30800 }, + { url = "https://files.pythonhosted.org/packages/04/9e/01067981d98069eec1c20201f8c145367698e9056f8bc295346e4ea32dd1/xxhash-3.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1308fa542bbdbf2fa85e9e66b1077eea3a88bef38ee8a06270b4298a7a62a166", size = 221566 }, + { url = "https://files.pythonhosted.org/packages/d4/09/d4996de4059c3ce5342b6e1e6a77c9d6c91acce31f6ed979891872dd162b/xxhash-3.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c28b2fdcee797e1c1961cd3bcd3d545cab22ad202c846235197935e1df2f8ef7", size = 201214 }, + { url = "https://files.pythonhosted.org/packages/62/f5/6d2dc9f8d55a7ce0f5e7bfef916e67536f01b85d32a9fbf137d4cadbee38/xxhash-3.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:924361811732ddad75ff23e90efd9ccfda4f664132feecb90895bade6a1b4623", size = 429433 }, + { url = "https://files.pythonhosted.org/packages/d9/72/9256303f10e41ab004799a4aa74b80b3c5977d6383ae4550548b24bd1971/xxhash-3.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89997aa1c4b6a5b1e5b588979d1da048a3c6f15e55c11d117a56b75c84531f5a", size = 194822 }, + { url = "https://files.pythonhosted.org/packages/34/92/1a3a29acd08248a34b0e6a94f4e0ed9b8379a4ff471f1668e4dce7bdbaa8/xxhash-3.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:685c4f4e8c59837de103344eb1c8a3851f670309eb5c361f746805c5471b8c88", size = 208538 }, + { url = "https://files.pythonhosted.org/packages/53/ad/7fa1a109663366de42f724a1cdb8e796a260dbac45047bce153bc1e18abf/xxhash-3.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbd2ecfbfee70bc1a4acb7461fa6af7748ec2ab08ac0fa298f281c51518f982c", size = 216953 }, + { url = "https://files.pythonhosted.org/packages/35/02/137300e24203bf2b2a49b48ce898ecce6fd01789c0fcd9c686c0a002d129/xxhash-3.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:25b5a51dc3dfb20a10833c8eee25903fd2e14059e9afcd329c9da20609a307b2", size = 203594 }, + { url = "https://files.pythonhosted.org/packages/23/03/aeceb273933d7eee248c4322b98b8e971f06cc3880e5f7602c94e5578af5/xxhash-3.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a8fb786fb754ef6ff8c120cb96629fb518f8eb5a61a16aac3a979a9dbd40a084", size = 210971 }, + { url = "https://files.pythonhosted.org/packages/e3/64/ed82ec09489474cbb35c716b189ddc1521d8b3de12b1b5ab41ce7f70253c/xxhash-3.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a905ad00ad1e1c34fe4e9d7c1d949ab09c6fa90c919860c1534ff479f40fd12d", size = 415050 }, + { url = "https://files.pythonhosted.org/packages/71/43/6db4c02dcb488ad4e03bc86d70506c3d40a384ee73c9b5c93338eb1f3c23/xxhash-3.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:963be41bcd49f53af6d795f65c0da9b4cc518c0dd9c47145c98f61cb464f4839", size = 192216 }, + { url = "https://files.pythonhosted.org/packages/22/6d/db4abec29e7a567455344433d095fdb39c97db6955bb4a2c432e486b4d28/xxhash-3.5.0-cp311-cp311-win32.whl", hash = "sha256:109b436096d0a2dd039c355fa3414160ec4d843dfecc64a14077332a00aeb7da", size = 30120 }, + { url = "https://files.pythonhosted.org/packages/52/1c/fa3b61c0cf03e1da4767213672efe186b1dfa4fc901a4a694fb184a513d1/xxhash-3.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:b702f806693201ad6c0a05ddbbe4c8f359626d0b3305f766077d51388a6bac58", size = 30003 }, + { url = "https://files.pythonhosted.org/packages/6b/8e/9e6fc572acf6e1cc7ccb01973c213f895cb8668a9d4c2b58a99350da14b7/xxhash-3.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:c4dcb4120d0cc3cc448624147dba64e9021b278c63e34a38789b688fd0da9bf3", size = 26777 }, + { url = "https://files.pythonhosted.org/packages/07/0e/1bfce2502c57d7e2e787600b31c83535af83746885aa1a5f153d8c8059d6/xxhash-3.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:14470ace8bd3b5d51318782cd94e6f94431974f16cb3b8dc15d52f3b69df8e00", size = 31969 }, + { url = "https://files.pythonhosted.org/packages/3f/d6/8ca450d6fe5b71ce521b4e5db69622383d039e2b253e9b2f24f93265b52c/xxhash-3.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59aa1203de1cb96dbeab595ded0ad0c0056bb2245ae11fac11c0ceea861382b9", size = 30787 }, + { url = "https://files.pythonhosted.org/packages/5b/84/de7c89bc6ef63d750159086a6ada6416cc4349eab23f76ab870407178b93/xxhash-3.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08424f6648526076e28fae6ea2806c0a7d504b9ef05ae61d196d571e5c879c84", size = 220959 }, + { url = "https://files.pythonhosted.org/packages/fe/86/51258d3e8a8545ff26468c977101964c14d56a8a37f5835bc0082426c672/xxhash-3.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61a1ff00674879725b194695e17f23d3248998b843eb5e933007ca743310f793", size = 200006 }, + { url = "https://files.pythonhosted.org/packages/02/0a/96973bd325412feccf23cf3680fd2246aebf4b789122f938d5557c54a6b2/xxhash-3.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2f2c61bee5844d41c3eb015ac652a0229e901074951ae48581d58bfb2ba01be", size = 428326 }, + { url = "https://files.pythonhosted.org/packages/11/a7/81dba5010f7e733de88af9555725146fc133be97ce36533867f4c7e75066/xxhash-3.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d32a592cac88d18cc09a89172e1c32d7f2a6e516c3dfde1b9adb90ab5df54a6", size = 194380 }, + { url = "https://files.pythonhosted.org/packages/fb/7d/f29006ab398a173f4501c0e4977ba288f1c621d878ec217b4ff516810c04/xxhash-3.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70dabf941dede727cca579e8c205e61121afc9b28516752fd65724be1355cc90", size = 207934 }, + { url = "https://files.pythonhosted.org/packages/8a/6e/6e88b8f24612510e73d4d70d9b0c7dff62a2e78451b9f0d042a5462c8d03/xxhash-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e5d0ddaca65ecca9c10dcf01730165fd858533d0be84c75c327487c37a906a27", size = 216301 }, + { url = "https://files.pythonhosted.org/packages/af/51/7862f4fa4b75a25c3b4163c8a873f070532fe5f2d3f9b3fc869c8337a398/xxhash-3.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e5b5e16c5a480fe5f59f56c30abdeba09ffd75da8d13f6b9b6fd224d0b4d0a2", size = 203351 }, + { url = "https://files.pythonhosted.org/packages/22/61/8d6a40f288f791cf79ed5bb113159abf0c81d6efb86e734334f698eb4c59/xxhash-3.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149b7914451eb154b3dfaa721315117ea1dac2cc55a01bfbd4df7c68c5dd683d", size = 210294 }, + { url = "https://files.pythonhosted.org/packages/17/02/215c4698955762d45a8158117190261b2dbefe9ae7e5b906768c09d8bc74/xxhash-3.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:eade977f5c96c677035ff39c56ac74d851b1cca7d607ab3d8f23c6b859379cab", size = 414674 }, + { url = "https://files.pythonhosted.org/packages/31/5c/b7a8db8a3237cff3d535261325d95de509f6a8ae439a5a7a4ffcff478189/xxhash-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa9f547bd98f5553d03160967866a71056a60960be00356a15ecc44efb40ba8e", size = 192022 }, + { url = "https://files.pythonhosted.org/packages/78/e3/dd76659b2811b3fd06892a8beb850e1996b63e9235af5a86ea348f053e9e/xxhash-3.5.0-cp312-cp312-win32.whl", hash = "sha256:f7b58d1fd3551b8c80a971199543379be1cee3d0d409e1f6d8b01c1a2eebf1f8", size = 30170 }, + { url = "https://files.pythonhosted.org/packages/d9/6b/1c443fe6cfeb4ad1dcf231cdec96eb94fb43d6498b4469ed8b51f8b59a37/xxhash-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:fa0cafd3a2af231b4e113fba24a65d7922af91aeb23774a8b78228e6cd785e3e", size = 30040 }, + { url = "https://files.pythonhosted.org/packages/0f/eb/04405305f290173acc0350eba6d2f1a794b57925df0398861a20fbafa415/xxhash-3.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:586886c7e89cb9828bcd8a5686b12e161368e0064d040e225e72607b43858ba2", size = 26796 }, + { url = "https://files.pythonhosted.org/packages/c9/b8/e4b3ad92d249be5c83fa72916c9091b0965cb0faeff05d9a0a3870ae6bff/xxhash-3.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37889a0d13b0b7d739cfc128b1c902f04e32de17b33d74b637ad42f1c55101f6", size = 31795 }, + { url = "https://files.pythonhosted.org/packages/fc/d8/b3627a0aebfbfa4c12a41e22af3742cf08c8ea84f5cc3367b5de2d039cce/xxhash-3.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:97a662338797c660178e682f3bc180277b9569a59abfb5925e8620fba00b9fc5", size = 30792 }, + { url = "https://files.pythonhosted.org/packages/c3/cc/762312960691da989c7cd0545cb120ba2a4148741c6ba458aa723c00a3f8/xxhash-3.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f85e0108d51092bdda90672476c7d909c04ada6923c14ff9d913c4f7dc8a3bc", size = 220950 }, + { url = "https://files.pythonhosted.org/packages/fe/e9/cc266f1042c3c13750e86a535496b58beb12bf8c50a915c336136f6168dc/xxhash-3.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2fd827b0ba763ac919440042302315c564fdb797294d86e8cdd4578e3bc7f3", size = 199980 }, + { url = "https://files.pythonhosted.org/packages/bf/85/a836cd0dc5cc20376de26b346858d0ac9656f8f730998ca4324921a010b9/xxhash-3.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82085c2abec437abebf457c1d12fccb30cc8b3774a0814872511f0f0562c768c", size = 428324 }, + { url = "https://files.pythonhosted.org/packages/b4/0e/15c243775342ce840b9ba34aceace06a1148fa1630cd8ca269e3223987f5/xxhash-3.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07fda5de378626e502b42b311b049848c2ef38784d0d67b6f30bb5008642f8eb", size = 194370 }, + { url = "https://files.pythonhosted.org/packages/87/a1/b028bb02636dfdc190da01951d0703b3d904301ed0ef6094d948983bef0e/xxhash-3.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c279f0d2b34ef15f922b77966640ade58b4ccdfef1c4d94b20f2a364617a493f", size = 207911 }, + { url = "https://files.pythonhosted.org/packages/80/d5/73c73b03fc0ac73dacf069fdf6036c9abad82de0a47549e9912c955ab449/xxhash-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:89e66ceed67b213dec5a773e2f7a9e8c58f64daeb38c7859d8815d2c89f39ad7", size = 216352 }, + { url = "https://files.pythonhosted.org/packages/b6/2a/5043dba5ddbe35b4fe6ea0a111280ad9c3d4ba477dd0f2d1fe1129bda9d0/xxhash-3.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bcd51708a633410737111e998ceb3b45d3dbc98c0931f743d9bb0a209033a326", size = 203410 }, + { url = "https://files.pythonhosted.org/packages/a2/b2/9a8ded888b7b190aed75b484eb5c853ddd48aa2896e7b59bbfbce442f0a1/xxhash-3.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ff2c0a34eae7df88c868be53a8dd56fbdf592109e21d4bfa092a27b0bf4a7bf", size = 210322 }, + { url = "https://files.pythonhosted.org/packages/98/62/440083fafbc917bf3e4b67c2ade621920dd905517e85631c10aac955c1d2/xxhash-3.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4e28503dccc7d32e0b9817aa0cbfc1f45f563b2c995b7a66c4c8a0d232e840c7", size = 414725 }, + { url = "https://files.pythonhosted.org/packages/75/db/009206f7076ad60a517e016bb0058381d96a007ce3f79fa91d3010f49cc2/xxhash-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a6c50017518329ed65a9e4829154626f008916d36295b6a3ba336e2458824c8c", size = 192070 }, + { url = "https://files.pythonhosted.org/packages/1f/6d/c61e0668943a034abc3a569cdc5aeae37d686d9da7e39cf2ed621d533e36/xxhash-3.5.0-cp313-cp313-win32.whl", hash = "sha256:53a068fe70301ec30d868ece566ac90d873e3bb059cf83c32e76012c889b8637", size = 30172 }, + { url = "https://files.pythonhosted.org/packages/96/14/8416dce965f35e3d24722cdf79361ae154fa23e2ab730e5323aa98d7919e/xxhash-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:80babcc30e7a1a484eab952d76a4f4673ff601f54d5142c26826502740e70b43", size = 30041 }, + { url = "https://files.pythonhosted.org/packages/27/ee/518b72faa2073f5aa8e3262408d284892cb79cf2754ba0c3a5870645ef73/xxhash-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:4811336f1ce11cac89dcbd18f3a25c527c16311709a89313c3acaf771def2d4b", size = 26801 }, +] + +[[package]] +name = "yardstick" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "dataclass-wizard" }, + { name = "dataclasses-json" }, + { name = "gitpython" }, + { name = "importlib-metadata" }, + { name = "mergedeep" }, + { name = "omitempty" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "rfc3339" }, + { name = "tabulate" }, + { name = "xxhash" }, + { name = "zstandard" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/45/5528fbfb6a364614d7fb071affd9878ea13b087c274e8ad20fd7c0db0126/yardstick-0.12.1.tar.gz", hash = "sha256:eecf2885b9fae42bffa06d12031639bd11495b74079cff1a938d586a7e853969", size = 142074 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/3d/008b9ca22873aeca5da68184ae63ccce9b37503bba97d4ef01c7415a1085/yardstick-0.12.1-py3-none-any.whl", hash = "sha256:481ddf04cd535f8e2dc8dea4526c9dbfb007f1d18ad4a38cbc9d55f4762a15d0", size = 93499 }, +] + +[[package]] +name = "zipp" +version = "3.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/50/bad581df71744867e9468ebd0bcd6505de3b275e06f202c2cb016e3ff56f/zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4", size = 24545 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/1a/7e4798e9339adc931158c9d69ecc34f5e6791489d469f5e50ec15e35f458/zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931", size = 9630 }, +] + +[[package]] +name = "zstandard" +version = "0.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation == 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/f6/2ac0287b442160a89d726b17a9184a4c615bb5237db763791a7fd16d9df1/zstandard-0.23.0.tar.gz", hash = "sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09", size = 681701 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/40/f67e7d2c25a0e2dc1744dd781110b0b60306657f8696cafb7ad7579469bd/zstandard-0.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:34895a41273ad33347b2fc70e1bff4240556de3c46c6ea430a7ed91f9042aa4e", size = 788699 }, + { url = "https://files.pythonhosted.org/packages/e8/46/66d5b55f4d737dd6ab75851b224abf0afe5774976fe511a54d2eb9063a41/zstandard-0.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:77ea385f7dd5b5676d7fd943292ffa18fbf5c72ba98f7d09fc1fb9e819b34c23", size = 633681 }, + { url = "https://files.pythonhosted.org/packages/63/b6/677e65c095d8e12b66b8f862b069bcf1f1d781b9c9c6f12eb55000d57583/zstandard-0.23.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:983b6efd649723474f29ed42e1467f90a35a74793437d0bc64a5bf482bedfa0a", size = 4944328 }, + { url = "https://files.pythonhosted.org/packages/59/cc/e76acb4c42afa05a9d20827116d1f9287e9c32b7ad58cc3af0721ce2b481/zstandard-0.23.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80a539906390591dd39ebb8d773771dc4db82ace6372c4d41e2d293f8e32b8db", size = 5311955 }, + { url = "https://files.pythonhosted.org/packages/78/e4/644b8075f18fc7f632130c32e8f36f6dc1b93065bf2dd87f03223b187f26/zstandard-0.23.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:445e4cb5048b04e90ce96a79b4b63140e3f4ab5f662321975679b5f6360b90e2", size = 5344944 }, + { url = "https://files.pythonhosted.org/packages/76/3f/dbafccf19cfeca25bbabf6f2dd81796b7218f768ec400f043edc767015a6/zstandard-0.23.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd30d9c67d13d891f2360b2a120186729c111238ac63b43dbd37a5a40670b8ca", size = 5442927 }, + { url = "https://files.pythonhosted.org/packages/0c/c3/d24a01a19b6733b9f218e94d1a87c477d523237e07f94899e1c10f6fd06c/zstandard-0.23.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d20fd853fbb5807c8e84c136c278827b6167ded66c72ec6f9a14b863d809211c", size = 4864910 }, + { url = "https://files.pythonhosted.org/packages/1c/a9/cf8f78ead4597264f7618d0875be01f9bc23c9d1d11afb6d225b867cb423/zstandard-0.23.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed1708dbf4d2e3a1c5c69110ba2b4eb6678262028afd6c6fbcc5a8dac9cda68e", size = 4935544 }, + { url = "https://files.pythonhosted.org/packages/2c/96/8af1e3731b67965fb995a940c04a2c20997a7b3b14826b9d1301cf160879/zstandard-0.23.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:be9b5b8659dff1f913039c2feee1aca499cfbc19e98fa12bc85e037c17ec6ca5", size = 5467094 }, + { url = "https://files.pythonhosted.org/packages/ff/57/43ea9df642c636cb79f88a13ab07d92d88d3bfe3e550b55a25a07a26d878/zstandard-0.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:65308f4b4890aa12d9b6ad9f2844b7ee42c7f7a4fd3390425b242ffc57498f48", size = 4860440 }, + { url = "https://files.pythonhosted.org/packages/46/37/edb78f33c7f44f806525f27baa300341918fd4c4af9472fbc2c3094be2e8/zstandard-0.23.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98da17ce9cbf3bfe4617e836d561e433f871129e3a7ac16d6ef4c680f13a839c", size = 4700091 }, + { url = "https://files.pythonhosted.org/packages/c1/f1/454ac3962671a754f3cb49242472df5c2cced4eb959ae203a377b45b1a3c/zstandard-0.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8ed7d27cb56b3e058d3cf684d7200703bcae623e1dcc06ed1e18ecda39fee003", size = 5208682 }, + { url = "https://files.pythonhosted.org/packages/85/b2/1734b0fff1634390b1b887202d557d2dd542de84a4c155c258cf75da4773/zstandard-0.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:b69bb4f51daf461b15e7b3db033160937d3ff88303a7bc808c67bbc1eaf98c78", size = 5669707 }, + { url = "https://files.pythonhosted.org/packages/52/5a/87d6971f0997c4b9b09c495bf92189fb63de86a83cadc4977dc19735f652/zstandard-0.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:034b88913ecc1b097f528e42b539453fa82c3557e414b3de9d5632c80439a473", size = 5201792 }, + { url = "https://files.pythonhosted.org/packages/79/02/6f6a42cc84459d399bd1a4e1adfc78d4dfe45e56d05b072008d10040e13b/zstandard-0.23.0-cp311-cp311-win32.whl", hash = "sha256:f2d4380bf5f62daabd7b751ea2339c1a21d1c9463f1feb7fc2bdcea2c29c3160", size = 430586 }, + { url = "https://files.pythonhosted.org/packages/be/a2/4272175d47c623ff78196f3c10e9dc7045c1b9caf3735bf041e65271eca4/zstandard-0.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:62136da96a973bd2557f06ddd4e8e807f9e13cbb0bfb9cc06cfe6d98ea90dfe0", size = 495420 }, + { url = "https://files.pythonhosted.org/packages/7b/83/f23338c963bd9de687d47bf32efe9fd30164e722ba27fb59df33e6b1719b/zstandard-0.23.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b4567955a6bc1b20e9c31612e615af6b53733491aeaa19a6b3b37f3b65477094", size = 788713 }, + { url = "https://files.pythonhosted.org/packages/5b/b3/1a028f6750fd9227ee0b937a278a434ab7f7fdc3066c3173f64366fe2466/zstandard-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e172f57cd78c20f13a3415cc8dfe24bf388614324d25539146594c16d78fcc8", size = 633459 }, + { url = "https://files.pythonhosted.org/packages/26/af/36d89aae0c1f95a0a98e50711bc5d92c144939efc1f81a2fcd3e78d7f4c1/zstandard-0.23.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0e166f698c5a3e914947388c162be2583e0c638a4703fc6a543e23a88dea3c1", size = 4945707 }, + { url = "https://files.pythonhosted.org/packages/cd/2e/2051f5c772f4dfc0aae3741d5fc72c3dcfe3aaeb461cc231668a4db1ce14/zstandard-0.23.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a289832e520c6bd4dcaad68e944b86da3bad0d339ef7989fb7e88f92e96072", size = 5306545 }, + { url = "https://files.pythonhosted.org/packages/0a/9e/a11c97b087f89cab030fa71206963090d2fecd8eb83e67bb8f3ffb84c024/zstandard-0.23.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d50d31bfedd53a928fed6707b15a8dbeef011bb6366297cc435accc888b27c20", size = 5337533 }, + { url = "https://files.pythonhosted.org/packages/fc/79/edeb217c57fe1bf16d890aa91a1c2c96b28c07b46afed54a5dcf310c3f6f/zstandard-0.23.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72c68dda124a1a138340fb62fa21b9bf4848437d9ca60bd35db36f2d3345f373", size = 5436510 }, + { url = "https://files.pythonhosted.org/packages/81/4f/c21383d97cb7a422ddf1ae824b53ce4b51063d0eeb2afa757eb40804a8ef/zstandard-0.23.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53dd9d5e3d29f95acd5de6802e909ada8d8d8cfa37a3ac64836f3bc4bc5512db", size = 4859973 }, + { url = "https://files.pythonhosted.org/packages/ab/15/08d22e87753304405ccac8be2493a495f529edd81d39a0870621462276ef/zstandard-0.23.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6a41c120c3dbc0d81a8e8adc73312d668cd34acd7725f036992b1b72d22c1772", size = 4936968 }, + { url = "https://files.pythonhosted.org/packages/eb/fa/f3670a597949fe7dcf38119a39f7da49a8a84a6f0b1a2e46b2f71a0ab83f/zstandard-0.23.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:40b33d93c6eddf02d2c19f5773196068d875c41ca25730e8288e9b672897c105", size = 5467179 }, + { url = "https://files.pythonhosted.org/packages/4e/a9/dad2ab22020211e380adc477a1dbf9f109b1f8d94c614944843e20dc2a99/zstandard-0.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9206649ec587e6b02bd124fb7799b86cddec350f6f6c14bc82a2b70183e708ba", size = 4848577 }, + { url = "https://files.pythonhosted.org/packages/08/03/dd28b4484b0770f1e23478413e01bee476ae8227bbc81561f9c329e12564/zstandard-0.23.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76e79bc28a65f467e0409098fa2c4376931fd3207fbeb6b956c7c476d53746dd", size = 4693899 }, + { url = "https://files.pythonhosted.org/packages/2b/64/3da7497eb635d025841e958bcd66a86117ae320c3b14b0ae86e9e8627518/zstandard-0.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:66b689c107857eceabf2cf3d3fc699c3c0fe8ccd18df2219d978c0283e4c508a", size = 5199964 }, + { url = "https://files.pythonhosted.org/packages/43/a4/d82decbab158a0e8a6ebb7fc98bc4d903266bce85b6e9aaedea1d288338c/zstandard-0.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9c236e635582742fee16603042553d276cca506e824fa2e6489db04039521e90", size = 5655398 }, + { url = "https://files.pythonhosted.org/packages/f2/61/ac78a1263bc83a5cf29e7458b77a568eda5a8f81980691bbc6eb6a0d45cc/zstandard-0.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8fffdbd9d1408006baaf02f1068d7dd1f016c6bcb7538682622c556e7b68e35", size = 5191313 }, + { url = "https://files.pythonhosted.org/packages/e7/54/967c478314e16af5baf849b6ee9d6ea724ae5b100eb506011f045d3d4e16/zstandard-0.23.0-cp312-cp312-win32.whl", hash = "sha256:dc1d33abb8a0d754ea4763bad944fd965d3d95b5baef6b121c0c9013eaf1907d", size = 430877 }, + { url = "https://files.pythonhosted.org/packages/75/37/872d74bd7739639c4553bf94c84af7d54d8211b626b352bc57f0fd8d1e3f/zstandard-0.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:64585e1dba664dc67c7cdabd56c1e5685233fbb1fc1966cfba2a340ec0dfff7b", size = 495595 }, + { url = "https://files.pythonhosted.org/packages/80/f1/8386f3f7c10261fe85fbc2c012fdb3d4db793b921c9abcc995d8da1b7a80/zstandard-0.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:576856e8594e6649aee06ddbfc738fec6a834f7c85bf7cadd1c53d4a58186ef9", size = 788975 }, + { url = "https://files.pythonhosted.org/packages/16/e8/cbf01077550b3e5dc86089035ff8f6fbbb312bc0983757c2d1117ebba242/zstandard-0.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38302b78a850ff82656beaddeb0bb989a0322a8bbb1bf1ab10c17506681d772a", size = 633448 }, + { url = "https://files.pythonhosted.org/packages/06/27/4a1b4c267c29a464a161aeb2589aff212b4db653a1d96bffe3598f3f0d22/zstandard-0.23.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2240ddc86b74966c34554c49d00eaafa8200a18d3a5b6ffbf7da63b11d74ee2", size = 4945269 }, + { url = "https://files.pythonhosted.org/packages/7c/64/d99261cc57afd9ae65b707e38045ed8269fbdae73544fd2e4a4d50d0ed83/zstandard-0.23.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ef230a8fd217a2015bc91b74f6b3b7d6522ba48be29ad4ea0ca3a3775bf7dd5", size = 5306228 }, + { url = "https://files.pythonhosted.org/packages/7a/cf/27b74c6f22541f0263016a0fd6369b1b7818941de639215c84e4e94b2a1c/zstandard-0.23.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:774d45b1fac1461f48698a9d4b5fa19a69d47ece02fa469825b442263f04021f", size = 5336891 }, + { url = "https://files.pythonhosted.org/packages/fa/18/89ac62eac46b69948bf35fcd90d37103f38722968e2981f752d69081ec4d/zstandard-0.23.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f77fa49079891a4aab203d0b1744acc85577ed16d767b52fc089d83faf8d8ed", size = 5436310 }, + { url = "https://files.pythonhosted.org/packages/a8/a8/5ca5328ee568a873f5118d5b5f70d1f36c6387716efe2e369010289a5738/zstandard-0.23.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac184f87ff521f4840e6ea0b10c0ec90c6b1dcd0bad2f1e4a9a1b4fa177982ea", size = 4859912 }, + { url = "https://files.pythonhosted.org/packages/ea/ca/3781059c95fd0868658b1cf0440edd832b942f84ae60685d0cfdb808bca1/zstandard-0.23.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c363b53e257246a954ebc7c488304b5592b9c53fbe74d03bc1c64dda153fb847", size = 4936946 }, + { url = "https://files.pythonhosted.org/packages/ce/11/41a58986f809532742c2b832c53b74ba0e0a5dae7e8ab4642bf5876f35de/zstandard-0.23.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e7792606d606c8df5277c32ccb58f29b9b8603bf83b48639b7aedf6df4fe8171", size = 5466994 }, + { url = "https://files.pythonhosted.org/packages/83/e3/97d84fe95edd38d7053af05159465d298c8b20cebe9ccb3d26783faa9094/zstandard-0.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a0817825b900fcd43ac5d05b8b3079937073d2b1ff9cf89427590718b70dd840", size = 4848681 }, + { url = "https://files.pythonhosted.org/packages/6e/99/cb1e63e931de15c88af26085e3f2d9af9ce53ccafac73b6e48418fd5a6e6/zstandard-0.23.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9da6bc32faac9a293ddfdcb9108d4b20416219461e4ec64dfea8383cac186690", size = 4694239 }, + { url = "https://files.pythonhosted.org/packages/ab/50/b1e703016eebbc6501fc92f34db7b1c68e54e567ef39e6e59cf5fb6f2ec0/zstandard-0.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fd7699e8fd9969f455ef2926221e0233f81a2542921471382e77a9e2f2b57f4b", size = 5200149 }, + { url = "https://files.pythonhosted.org/packages/aa/e0/932388630aaba70197c78bdb10cce2c91fae01a7e553b76ce85471aec690/zstandard-0.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d477ed829077cd945b01fc3115edd132c47e6540ddcd96ca169facff28173057", size = 5655392 }, + { url = "https://files.pythonhosted.org/packages/02/90/2633473864f67a15526324b007a9f96c96f56d5f32ef2a56cc12f9548723/zstandard-0.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ce8b52c5987b3e34d5674b0ab529a4602b632ebab0a93b07bfb4dfc8f8a33", size = 5191299 }, + { url = "https://files.pythonhosted.org/packages/b0/4c/315ca5c32da7e2dc3455f3b2caee5c8c2246074a61aac6ec3378a97b7136/zstandard-0.23.0-cp313-cp313-win32.whl", hash = "sha256:a9b07268d0c3ca5c170a385a0ab9fb7fdd9f5fd866be004c4ea39e44edce47dd", size = 430862 }, + { url = "https://files.pythonhosted.org/packages/a2/bf/c6aaba098e2d04781e8f4f7c0ba3c7aa73d00e4c436bcc0cf059a66691d1/zstandard-0.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:f3513916e8c645d0610815c257cbfd3242adfd5c4cfa78be514e5a3ebb42a41b", size = 495578 }, +]