diff --git a/.github/ISSUE_TEMPLATE/maintainer.md b/.github/ISSUE_TEMPLATE/maintainer.md new file mode 100644 index 000000000000..719a9684db48 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/maintainer.md @@ -0,0 +1,11 @@ +--- +name: Maintainer +about: Create an issue by maintainers +title: '' +labels: '' +assignees: '' + +--- + +## Are you a maintainer of the Trivy project? +If not, please open [a discussion](https://github.com/aquasecurity/trivy/discussions); if you are, please review [the guideline](https://trivy.dev/latest/community/contribute/discussion/). diff --git a/.github/workflows/publish-chart.yaml b/.github/workflows/publish-chart.yaml index ba36eed683cf..1f3bfd63fec4 100644 --- a/.github/workflows/publish-chart.yaml +++ b/.github/workflows/publish-chart.yaml @@ -13,9 +13,6 @@ on: - main paths: - 'helm/trivy/**' - push: - tags: - - "v*" env: HELM_REP: helm-charts GH_OWNER: aquasecurity @@ -25,7 +22,6 @@ env: jobs: # `test-chart` job starts if a PR with Helm Chart is created, merged etc. test-chart: - if: github.event_name != 'push' runs-on: ubuntu-24.04 steps: - name: Checkout @@ -56,35 +52,6 @@ jobs: sed -i -e '136s,false,'true',g' ./helm/trivy/values.yaml ct lint-and-install --validate-maintainers=false --charts helm/trivy - # `update-chart-version` job starts if a new tag is pushed - update-chart-version: - if: github.event_name == 'push' - runs-on: ubuntu-24.04 - steps: - - name: Checkout - uses: actions/checkout@v4.1.6 - with: - fetch-depth: 0 - - - name: Set up Git user - run: | - git config --global user.email "actions@github.com" - git config --global user.name "GitHub Actions" - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version-file: go.mod - - - name: Install Go tools - run: go install tool # GOBIN is added to the PATH by the setup-go action - - - name: Create a PR with Trivy version - run: mage helm:updateVersion - env: - # Use ORG_REPO_TOKEN instead of GITHUB_TOKEN - # This allows the created PR to trigger tests and other workflows - GITHUB_TOKEN: ${{ secrets.ORG_REPO_TOKEN }} # `publish-chart` job starts if a PR with a new Helm Chart is merged or manually publish-chart: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index d7fb59bc2942..02f5943144c8 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -55,3 +55,33 @@ jobs: - name: Create deb repository run: ci/deploy-deb.sh + + # `update-chart-version` creates a new PR for updating the helm chart + update-chart-version: + needs: deploy-packages + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v4.1.6 + with: + fetch-depth: 0 + + - name: Set up Git user + run: | + git config --global user.email "actions@github.com" + git config --global user.name "GitHub Actions" + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Install Go tools + run: go install tool # GOBIN is added to the PATH by the setup-go action + + - name: Create a PR with Trivy version + run: mage helm:updateVersion + env: + # Use ORG_REPO_TOKEN instead of GITHUB_TOKEN + # This allows the created PR to trigger tests and other workflows + GITHUB_TOKEN: ${{ secrets.ORG_REPO_TOKEN }} diff --git a/.github/workflows/semantic-pr.yaml b/.github/workflows/semantic-pr.yaml index 4005520f1949..8286e70cf1e9 100644 --- a/.github/workflows/semantic-pr.yaml +++ b/.github/workflows/semantic-pr.yaml @@ -1,22 +1,23 @@ -name: "Lint PR title" +name: "Validate PR Title" on: - pull_request_target: + pull_request: types: - opened - edited - synchronize jobs: - main: + validate: name: Validate PR title runs-on: ubuntu-latest steps: - - uses: amannn/action-semantic-pull-request@v5 + - name: Validate PR title + shell: bash env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - types: | + PR_TITLE: ${{ github.event.pull_request.title }} + # Valid types + VALID_TYPES: | feat fix docs @@ -29,13 +30,15 @@ jobs: chore revert release - - scopes: | + # Valid scopes categorized by area + VALID_SCOPES: | + # Scanners vuln misconf secret license + # Targets image fs repo @@ -46,6 +49,7 @@ jobs: vm plugin + # OS alpine wolfi chainguard @@ -62,6 +66,7 @@ jobs: distroless windows + # Languages ruby php python @@ -71,7 +76,7 @@ jobs: java go c - c\+\+ + c++ elixir dart swift @@ -79,29 +84,80 @@ jobs: conda julia + # Package types os lang + # IaC kubernetes dockerfile terraform cloudformation + # Container docker podman containerd oci + # SBOM + sbom + spdx + cyclonedx + + # Misc cli flag - - cyclonedx - spdx purl vex - helm report db parser deps + run: | + set -euo pipefail + + # Convert env vars to regex alternatives, excluding comments and empty lines + TYPES_REGEX=$(echo "$VALID_TYPES" | grep -v '^$' | paste -sd '|') + SCOPES_REGEX=$(echo "$VALID_SCOPES" | grep -v '^$' | grep -v '^#' | paste -sd '|') + + # Basic format check (should match: type(scope): description or type: description) + FORMAT_REGEX="^[a-z]+(\([a-z0-9+]+\))?!?: .+$" + if ! echo "$PR_TITLE" | grep -qE "$FORMAT_REGEX"; then + echo "Error: Invalid PR title format" + echo "Expected format: (): or : " + echo "Examples:" + echo " feat(vuln): add new vulnerability detection" + echo " fix: correct parsing logic" + echo " docs(kubernetes): update installation guide" + echo -e "\nCurrent title: $PR_TITLE" + exit 1 + fi + + # Extract type and scope for validation + TYPE=$(echo "$PR_TITLE" | sed -E 's/^([a-z]+)(\([a-z0-9+]+\))?!?: .+$/\1/') + SCOPE=$(echo "$PR_TITLE" | sed -E 's/^[a-z]+\(([a-z0-9+]+)\)!?: .+$/\1/; t; s/.*//') + + # Validate type + if ! echo "$VALID_TYPES" | grep -qx "$TYPE"; then + echo "Error: Invalid type '${TYPE}'" + echo -e "\nValid types:" + echo "$VALID_TYPES" | grep -v '^$' | sed 's/^/- /' + echo -e "\nCurrent title: $PR_TITLE" + exit 1 + fi + + # Validate scope if present + if [ -n "$SCOPE" ]; then + if ! echo "$VALID_SCOPES" | grep -v '^#' | grep -qx "$SCOPE"; then + echo "Error: Invalid scope '${SCOPE}'" + echo -e "\nValid scopes:" + echo "$VALID_SCOPES" | grep -v '^$' | grep -v '^#' | sed 's/^/- /' + echo -e "\nCurrent title: $PR_TITLE" + exit 1 + fi + fi + + echo "PR title validation passed ✅" + echo "Current title: $PR_TITLE" diff --git a/.github/workflows/spdx-cron.yaml b/.github/workflows/spdx-cron.yaml index cdc9533c7ea8..976d762db807 100644 --- a/.github/workflows/spdx-cron.yaml +++ b/.github/workflows/spdx-cron.yaml @@ -29,10 +29,7 @@ jobs: fi - name: Microsoft Teams Notification - ## Until the PR with the fix for the AdaptivCard version is merged yet - ## https://github.com/Skitionek/notify-microsoft-teams/pull/96 - ## Use the aquasecurity fork - uses: aquasecurity/notify-microsoft-teams@master + uses: Skitionek/notify-microsoft-teams@e7a2493ac87dad8aa7a62f079f295e54ff511d88 if: failure() with: webhook_url: ${{ secrets.TRIVY_MSTEAMS_WEBHOOK }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index b8088cee4776..7b8caea02299 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -38,10 +38,10 @@ jobs: - name: Lint id: lint - uses: golangci/golangci-lint-action@v6.5.0 + uses: golangci/golangci-lint-action@v7.0.0 with: - version: v1.64 - args: --verbose --out-format=line-number + version: v2.1 + args: --verbose if: matrix.operating-system == 'ubuntu-latest' - name: Check if linter failed diff --git a/.golangci.yaml b/.golangci.yaml index a60d00057c0e..8a3a134ee7d2 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -1,112 +1,145 @@ -linters-settings: - depguard: - rules: - main: - list-mode: lax - deny: - # Cannot use gomodguard, which examines go.mod, as "golang.org/x/exp/slices" is not a module and doesn't appear in go.mod. - - pkg: "golang.org/x/exp/slices" - desc: "Use 'slices' instead" - - pkg: "golang.org/x/exp/maps" - desc: "Use 'maps' or 'github.com/samber/lo' instead" - dupl: - threshold: 100 - errcheck: - check-type-assertions: true - check-blank: true - gci: - sections: - - standard - - default - - prefix(github.com/aquasecurity/) - - blank - - dot - goconst: - min-len: 3 - min-occurrences: 3 - gocritic: - disabled-checks: - - appendAssign - - unnamedResult - - whyNoLint - - indexAlloc - - octalLiteral - - hugeParam - - rangeValCopy - - regexpSimplify - - sloppyReassign - - commentedOutCode - enabled-tags: - - diagnostic - - style - - performance - - experimental - - opinionated - settings: - ruleguard: - failOn: all - rules: '${configDir}/misc/lint/rules.go' - gocyclo: - min-complexity: 20 - gofmt: - simplify: false - rewrite-rules: - - pattern: 'interface{}' - replacement: 'any' - gomodguard: - blocked: - modules: - - github.com/hashicorp/go-version: - recommendations: - - github.com/aquasecurity/go-version - reason: "`aquasecurity/go-version` is designed for our use-cases" - - github.com/Masterminds/semver: - recommendations: - - github.com/aquasecurity/go-version - reason: "`aquasecurity/go-version` is designed for our use-cases" - gosec: - excludes: - - G101 - - G114 - - G115 - - G204 - - G304 - - G402 - govet: - disable: - - shadow - misspell: - locale: US - ignore-words: - - behaviour - - licence - - optimise - - simmilar - perfsprint: - # Optimizes even if it requires an int or uint type cast. - int-conversion: true - # Optimizes into `err.Error()` even if it is only equivalent for non-nil errors. - err-error: true - # Optimizes `fmt.Errorf`. - errorf: true - # Optimizes `fmt.Sprintf` with only one argument. - sprintf1: false - # Optimizes into strings concatenation. - strconcat: false - revive: - ignore-generated-header: true - testifylint: - enable-all: true +issues: + max-issues-per-linter: 0 + max-same-issues: 0 + linters: - disable-all: true + settings: + depguard: + rules: + main: + list-mode: lax + deny: + # Cannot use gomodguard, which examines go.mod, as "golang.org/x/exp/slices" is not a module and doesn't appear in go.mod. + - pkg: "golang.org/x/exp/slices" + desc: "Use 'slices' instead" + - pkg: "golang.org/x/exp/maps" + desc: "Use 'maps' or 'github.com/samber/lo' instead" + dupl: + threshold: 100 + errcheck: + check-type-assertions: true + check-blank: true + goconst: + min-len: 3 + min-occurrences: 3 + gocritic: + disabled-checks: + - appendAssign + - commentedOutCode + - hugeParam + - importShadow # FIXME + - indexAlloc + - rangeValCopy + - regexpSimplify + - sloppyReassign + - unnamedResult + - whyNoLint + enabled-tags: + - diagnostic + - style + - performance + - experimental + - opinionated + settings: + ruleguard: + failOn: all + rules: '${base-path}/misc/lint/rules.go' + gocyclo: + min-complexity: 20 + gomodguard: + blocked: + modules: + - github.com/hashicorp/go-version: + recommendations: + - github.com/aquasecurity/go-version + reason: "`aquasecurity/go-version` is designed for our use-cases" + - github.com/Masterminds/semver: + recommendations: + - github.com/aquasecurity/go-version + reason: "`aquasecurity/go-version` is designed for our use-cases" + gosec: + excludes: + - G101 + - G114 + - G115 + - G204 + - G304 + - G402 + govet: + disable: + - shadow + misspell: + locale: US + ignore-rules: + - behaviour + - licence + - optimise + - simmilar + perfsprint: + # Optimizes even if it requires an int or uint type cast. + int-conversion: true + # Optimizes into `err.Error()` even if it is only equivalent for non-nil errors. + err-error: true + # Optimizes `fmt.Errorf`. + errorf: true + # Optimizes `fmt.Sprintf` with only one argument. + sprintf1: false + # Optimizes into strings concatenation. + strconcat: false + revive: + max-open-files: 2048 + # https://github.com/mgechev/revive/blob/HEAD/RULES_DESCRIPTIONS.md + rules: + - name: bool-literal-in-expr + - name: context-as-argument + arguments: + - allowTypesBefore: "*testing.T" + - name: duplicated-imports + - name: early-return + arguments: + - preserve-scope + - name: if-return + - name: increment-decrement + - name: indent-error-flow + arguments: + - preserve-scope + - name: range + - name: range-val-address + - name: superfluous-else + arguments: + - preserve-scope + - name: time-equal + - name: unnecessary-stmt + - name: unused-parameter + - name: use-any + + staticcheck: + checks: + - all + - -QF1008 # Omit embedded fields from selector expression + - -S1007 # Simplify regular expression by using raw string literal + - -S1011 # Use a single append to concatenate two slices + - -S1023 # Omit redundant control flow + - -SA1019 # Using a deprecated function, variable, constant or field + - -SA1024 # A string cutset contains duplicate characters + - -SA4004 # The loop exits unconditionally after one iteration + - -SA4023 # Impossible comparison of interface value with untyped nil + - -SA4032 # Comparing runtime.GOOS or runtime.GOARCH against impossible value + - -SA5011 # Possible nil pointer dereference + - -ST1003 # Poorly chosen identifier + - -ST1012 # Poorly chosen name for error variable + + testifylint: + enable-all: true + + default: none + enable: - bodyclose - depguard - - gci - goconst - gocritic - gocyclo - - gofmt - gomodguard - gosec - govet @@ -114,46 +147,70 @@ linters: - misspell - perfsprint - revive - - usetesting + - staticcheck - testifylint - - typecheck - unconvert - unused - usestdlibvars + - usetesting + + exclusions: + generated: lax + paths: + - "pkg/iac/scanners/terraform/parser/funcs" # copies of Terraform functions + rules: + - path: ".*_test.go$" + linters: + - goconst + - gosec + - unused + - path: ".*_test.go$" + linters: + - govet + text: "copylocks:" + - path: ".*_test.go$" + linters: + - gocritic + text: "commentFormatting:" + - path: ".*_test.go$" + linters: + - gocritic + text: "exitAfterDefer:" + - path: ".*_test.go$" + linters: + - gocritic + text: "importShadow:" + - linters: + - goconst + text: "string `each` has 3 occurrences, make it a constant" # FIXME + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + warn-unused: true run: go: '1.24' timeout: 30m -issues: - exclude-files: - - "examples/*" - exclude-dirs: - - "pkg/iac/scanners/terraform/parser/funcs" # copies of Terraform functions - exclude-rules: - - path: ".*_test.go$" - linters: - - goconst - - gosec - - unused - - path: ".*_test.go$" - linters: - - govet - text: "copylocks:" - - path: ".*_test.go$" - linters: - - gocritic - text: "commentFormatting:" - - path: ".*_test.go$" - linters: - - gocritic - text: "exitAfterDefer:" - - path: ".*_test.go$" - linters: - - gocritic - text: "importShadow:" - - linters: - - perfsprint - text: "fmt.Sprint" - exclude-use-default: false - max-same-issues: 0 +formatters: + enable: + - gci + - gofmt + + exclusions: + generated: lax + + settings: + gci: + sections: + - standard + - default + - prefix(github.com/aquasecurity/) + - blank + - dot + gofmt: + simplify: false + +version: "2" diff --git a/.release-please-manifest.json b/.release-please-manifest.json index e87b0eca8310..fa504b3fb32a 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1 +1 @@ -{".":"0.61.0"} +{".":"0.62.0"} diff --git a/CHANGELOG.md b/CHANGELOG.md index eca562d7ef55..465000f75186 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,37 @@ # Changelog +## [0.62.0](https://github.com/aquasecurity/trivy/compare/v0.61.0...v0.62.0) (2025-04-30) + + +### Features + +* **image:** save layers metadata into report ([#8394](https://github.com/aquasecurity/trivy/issues/8394)) ([a95cab0](https://github.com/aquasecurity/trivy/commit/a95cab0eab0fcaab57eb554e74e17da71bc4809f)) +* **misconf:** add option to pass Rego scanner to IaC scanner ([#8369](https://github.com/aquasecurity/trivy/issues/8369)) ([890a360](https://github.com/aquasecurity/trivy/commit/890a3602444ad2e5320044c9b8cc79ca883d17ec)) +* **misconf:** convert AWS managed policy to document ([#8757](https://github.com/aquasecurity/trivy/issues/8757)) ([7abf5f0](https://github.com/aquasecurity/trivy/commit/7abf5f0199ec65c40056d4f9addc3d27e373725a)) +* **misconf:** support auto_provisioning_defaults in google_container_cluster ([#8705](https://github.com/aquasecurity/trivy/issues/8705)) ([9792611](https://github.com/aquasecurity/trivy/commit/9792611b36271efbf79f635deebae7e51f497b70)) +* **nodejs:** add root and workspace for `yarn` packages ([#8535](https://github.com/aquasecurity/trivy/issues/8535)) ([bf4cd4f](https://github.com/aquasecurity/trivy/commit/bf4cd4f2d2dda0bb3a7018606db9a6c1e56e4f38)) +* **rust:** add root and workspace relationships/package for `cargo` lock files ([#8676](https://github.com/aquasecurity/trivy/issues/8676)) ([93efe07](https://github.com/aquasecurity/trivy/commit/93efe0789ed9d9a71e04e93d87be63032ad9cae7)) + + +### Bug Fixes + +* early-return, indent-error-flow and superfluous-else rules from revive ([#8796](https://github.com/aquasecurity/trivy/issues/8796)) ([43350dd](https://github.com/aquasecurity/trivy/commit/43350dd9b487b39d7d19bd0875274c90262dbed9)) +* **k8s:** correct compare artifact versions ([#8682](https://github.com/aquasecurity/trivy/issues/8682)) ([cc47711](https://github.com/aquasecurity/trivy/commit/cc4771158b72b88258057fa379deba9f39190994)) +* **k8s:** remove using `last-applied-configuration` ([#8791](https://github.com/aquasecurity/trivy/issues/8791)) ([7a58ccb](https://github.com/aquasecurity/trivy/commit/7a58ccbc7fffdfb1e5ccff9fd4cb6ca08c03a9ea)) +* **k8s:** skip passed misconfigs for the summary report ([#8684](https://github.com/aquasecurity/trivy/issues/8684)) ([bff0e9b](https://github.com/aquasecurity/trivy/commit/bff0e9b034f39d0d1ca02457558b1f89847009ac)) +* **misconf:** add missing variable as unknown ([#8683](https://github.com/aquasecurity/trivy/issues/8683)) ([9dcd06f](https://github.com/aquasecurity/trivy/commit/9dcd06fda717347eab1ac8ef0710687a3bfd8588)) +* **misconf:** check if metadata is not nil ([#8647](https://github.com/aquasecurity/trivy/issues/8647)) ([b7dfd64](https://github.com/aquasecurity/trivy/commit/b7dfd64987b94b4bdd8b7c5a68ba2b8f1a0a9198)) +* **misconf:** filter null nodes when parsing json manifest ([#8785](https://github.com/aquasecurity/trivy/issues/8785)) ([e10929a](https://github.com/aquasecurity/trivy/commit/e10929a669f43861bae80652bdfc9f39fad7225f)) +* **misconf:** perform operations on attribute safely ([#8774](https://github.com/aquasecurity/trivy/issues/8774)) ([3ce7d59](https://github.com/aquasecurity/trivy/commit/3ce7d59bb16553ab487762a5a660a046bcd63334)) +* **misconf:** populate context correctly for module instances ([#8656](https://github.com/aquasecurity/trivy/issues/8656)) ([efd177b](https://github.com/aquasecurity/trivy/commit/efd177b300950d82e381992e1dea39308cc39bc3)) +* **report:** clean buffer after flushing ([#8725](https://github.com/aquasecurity/trivy/issues/8725)) ([9a5383e](https://github.com/aquasecurity/trivy/commit/9a5383e993222d919d63f8d9934729cf4e291c06)) +* **secret:** ignore .dist-info directories during secret scanning ([#8646](https://github.com/aquasecurity/trivy/issues/8646)) ([a032ad6](https://github.com/aquasecurity/trivy/commit/a032ad696aa58850b9576d889128559149282ad3)) +* **server:** fix redis key when trying to delete blob ([#8649](https://github.com/aquasecurity/trivy/issues/8649)) ([36f8d0f](https://github.com/aquasecurity/trivy/commit/36f8d0fd6705bb0da5b43507128c772b153dafec)) +* **terraform:** `evaluateStep` to correctly set `EvalContext` for multiple instances of blocks ([#8555](https://github.com/aquasecurity/trivy/issues/8555)) ([e25de25](https://github.com/aquasecurity/trivy/commit/e25de25262fd1cd559879dee07bb2db2747eedd4)) +* **terraform:** hcl object expressions to return references ([#8271](https://github.com/aquasecurity/trivy/issues/8271)) ([0d3efa5](https://github.com/aquasecurity/trivy/commit/0d3efa5dc150dba437d975a2f8335de8786f94d6)) +* testifylint last issues ([#8768](https://github.com/aquasecurity/trivy/issues/8768)) ([ee4f7dc](https://github.com/aquasecurity/trivy/commit/ee4f7dc6b4be437666e91383406bba8443eec199)) +* unused-parameter rule from revive ([#8794](https://github.com/aquasecurity/trivy/issues/8794)) ([6562082](https://github.com/aquasecurity/trivy/commit/6562082e280a9df6199892927f2e3f7dc8f0c8ce)) + ## [0.61.0](https://github.com/aquasecurity/trivy/compare/v0.60.0...v0.61.0) (2025-03-28) diff --git a/cmd/trivy/main.go b/cmd/trivy/main.go index 84d8879fbdbc..80d7ab8f7104 100644 --- a/cmd/trivy/main.go +++ b/cmd/trivy/main.go @@ -42,8 +42,5 @@ func run() error { } app := commands.NewApp() - if err := app.Execute(); err != nil { - return err - } - return nil + return app.Execute() } diff --git a/contrib/junit.tpl b/contrib/junit.tpl index b351f62bb43e..e4313c124f63 100644 --- a/contrib/junit.tpl +++ b/contrib/junit.tpl @@ -15,6 +15,7 @@ {{- end }} +{{- $target := .Target }} {{- if .MisconfSummary }} {{- else }} @@ -28,7 +29,23 @@ {{ range .Misconfigurations }} {{- if (eq .Status "FAIL") }} - {{ escapeXML .Description }} + + {{- $target }}: + {{- with .CauseMetadata }} + {{- .StartLine }} + {{- if lt .StartLine .EndLine }}:{{ .EndLine }}{{ end }}: Occurrences: + {{- range $i := .Occurrences -}} + via {{ .Filename }}: + {{- .Location.StartLine }} + {{- if lt .Location.StartLine .Location.EndLine }}:{{ .Location.EndLine }}{{ end }} ({{ .Resource }}) + {{- end -}} + Code: + {{- range .Code.Lines }} + {{- if .IsCause }}{{ escapeXML .Content }} {{- end }} + {{- end }} + {{- end }} + {{- escapeXML .Description }} + {{- end }} {{- end }} diff --git a/docs/docs/advanced/container/unpacked-filesystem.md b/docs/docs/advanced/container/unpacked-filesystem.md index 74232db9a26a..cbc27fbf07f5 100644 --- a/docs/docs/advanced/container/unpacked-filesystem.md +++ b/docs/docs/advanced/container/unpacked-filesystem.md @@ -113,4 +113,4 @@ Total: 20 (UNKNOWN: 0, LOW: 2, MEDIUM: 10, HIGH: 8, CRITICAL: 0) +--------------+------------------+----------+-------------------+---------------+---------------------------------------+ ``` - + \ No newline at end of file diff --git a/docs/docs/configuration/skipping.md b/docs/docs/configuration/skipping.md index 7e228d696fff..508d80c05bc5 100644 --- a/docs/docs/configuration/skipping.md +++ b/docs/docs/configuration/skipping.md @@ -1,6 +1,6 @@ -# Skipping Files and Directories +# Skipping Artifacts -This section details ways to specify the files and directories that Trivy should not scan. +This section details ways to specify the files, directories and resources that Trivy should not scan. ## Skip Files | Scanner | Supported | @@ -116,4 +116,8 @@ A file pattern contains the analyzer it is used for, and the pattern itself, joi The prefixes are listed [here](https://github.com/aquasecurity/trivy/tree/{{ git.commit }}/pkg/fanal/analyzer/const.go) -[^1]: Only work with the [license-full](../scanner/license.md) flag) \ No newline at end of file +[^1]: Only work with the [license-full](../scanner/license.md) flag) + +## Skip Resources + +You can skip IaC resources by providing inline comments. More details are provided [here](../scanner/misconfiguration/config/config.md#skipping-resources-by-inline-comments) \ No newline at end of file diff --git a/docs/docs/coverage/language/golang.md b/docs/docs/coverage/language/golang.md index b78c76a97c7e..5dcd643cffda 100644 --- a/docs/docs/coverage/language/golang.md +++ b/docs/docs/coverage/language/golang.md @@ -82,8 +82,9 @@ It possibly produces false positives. See [the caveat](#stdlib-vulnerabilities) for details. ### License -To identify licenses, you need to download modules to local cache beforehand, such as `go mod download`, `go mod tidy`, etc. -Trivy traverses `$GOPATH/pkg/mod` and collects those extra information. +To identify licenses, you need to download modules to local cache beforehand, such as `go mod download`, `go mod tidy`, `go mod vendor`, etc. +If the `vendor` directory exists, Trivy uses this directory when scanning for license files. +For other cases Trivy traverses `$GOPATH/pkg/mod`dir and collects those extra information. ### Dependency Graph Same as licenses, you need to download modules to local cache beforehand. diff --git a/docs/docs/coverage/language/java.md b/docs/docs/coverage/language/java.md index 934d4149d4d8..83272f91739e 100644 --- a/docs/docs/coverage/language/java.md +++ b/docs/docs/coverage/language/java.md @@ -12,12 +12,12 @@ Each artifact supports the following scanners: The following table provides an outline of the features Trivy offers. -| Artifact | Internet access | Dev dependencies | [Dependency graph][dependency-graph] | Position | [Detection Priority][detection-priority] | -|------------------|:---------------------:|:----------------:|:------------------------------------:|:--------:|:----------------------------------------:| -| JAR/WAR/PAR/EAR | Trivy Java DB | Include | - | - | Not needed | -| pom.xml | Maven repository [^1] | Exclude | ✓ | ✓[^7] | - | -| *gradle.lockfile | - | Exclude | ✓ | ✓ | Not needed | -| *.sbt.lock | - | Exclude | - | ✓ | Not needed | +| Artifact | Internet access | Dev dependencies | [Dependency graph][dependency-graph] | Position | [Detection Priority][detection-priority] | +|------------------|:---------------------:|:----------------------:|:------------------------------------:|:--------:|:----------------------------------------:| +| JAR/WAR/PAR/EAR | Trivy Java DB | Include | - | - | Not needed | +| pom.xml | Maven repository [^1] | Exclude | ✓ | ✓[^7] | - | +| *gradle.lockfile | - | [Exclude](#gradlelock) | ✓ | ✓ | Not needed | +| *.sbt.lock | - | Exclude | - | ✓ | Not needed | These may be enabled or disabled depending on the target. See [here](./index.md) for the detail. @@ -96,6 +96,9 @@ If you need to show them, use the `--include-dev-deps` flag. !!!note All necessary files are checked locally. Gradle file scanning doesn't require internet access. +By default, Trivy doesn't report development dependencies. +Use the `--include-dev-deps` flag to include them in the results. + ### Dependency-tree !!! warning "EXPERIMENTAL" This feature might change without preserving backwards compatibility. @@ -105,7 +108,7 @@ But there is no reliable way to determine direct dependencies (even using other Therefore, we mark all dependencies as indirect to use logic to guess direct dependencies and build a dependency tree. ### Licenses -Trity also can detect licenses for dependencies. +Trivy also can detect licenses for dependencies. Make sure that you have cache[^8] directory to find licenses from `*.pom` dependency files. diff --git a/docs/docs/coverage/language/nodejs.md b/docs/docs/coverage/language/nodejs.md index 623f54d0ca6c..ada178633de2 100644 --- a/docs/docs/coverage/language/nodejs.md +++ b/docs/docs/coverage/language/nodejs.md @@ -43,14 +43,26 @@ Trivy analyzes `node_modules` for licenses. By default, Trivy doesn't report development dependencies. Use the `--include-dev-deps` flag to include them. ### Yarn -Trivy parses `yarn.lock`, which doesn't contain information about development dependencies. -Trivy also uses `package.json` file to handle [aliases](https://classic.yarnpkg.com/lang/en/docs/cli/add/#toc-yarn-add-alias). +Trivy parses `yarn.lock`. -To exclude devDependencies and allow aliases, `package.json` also needs to be present next to `yarn.lock`. +Trivy also analyzes additional files to gather more information about the detected dependencies. -Trivy analyzes `.yarn` (Yarn 2+) or `node_modules` (Yarn Classic) folder next to the yarn.lock file to detect licenses. +- package.json +- node_modules/** -By default, Trivy doesn't report development dependencies. Use the `--include-dev-deps` flag to include them. +#### Package relationships +`yarn.lock` files don't contain information about package relationships, such as direct or indirect dependencies. +To enrich this information, Trivy parses the `package.json` file located next to the `yarn.lock` file as well as workspace `package.json` files. + +By default, Trivy doesn't report development dependencies. +Use the `--include-dev-deps` flag to include them in the results. + +#### Development dependencies +`yarn.lock` files don't contain information about package groups, such as production and development dependencies. +To identify dev dependencies and support [aliases][yarn-aliases], Trivy parses the `package.json` file located next to the `yarn.lock` file as well as workspace `package.json` files. + +#### Licenses +Trivy analyzes the `.yarn` directory (for Yarn 2+) or the `node_modules` directory (for Yarn Classic) located next to the `yarn.lock` file to detect licenses. ### pnpm Trivy parses `pnpm-lock.yaml`, then finds production dependencies and builds a [tree][dependency-graph] of dependencies with vulnerabilities. @@ -74,5 +86,6 @@ It only extracts package names, versions and licenses for those packages. [dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies [pnpm-lockfile-v6]: https://github.com/pnpm/spec/blob/fd3238639af86c09b7032cc942bab3438b497036/lockfile/6.0.md +[yarn-aliases]: https://classic.yarnpkg.com/lang/en/docs/cli/add/#toc-yarn-add-alias [^1]: [yarn.lock](#bun) must be generated diff --git a/docs/docs/coverage/os/bottlerocket.md b/docs/docs/coverage/os/bottlerocket.md new file mode 100644 index 000000000000..8c3e39bb44c2 --- /dev/null +++ b/docs/docs/coverage/os/bottlerocket.md @@ -0,0 +1,15 @@ +# Bottlerocket +Trivy supports the following scanners for OS packages. + +| Scanner | Supported | +| :-----------: | :-------: | +| SBOM | ✓ | +| Vulnerability | - | +| License | - | + +Please see [here](index.md#supported-os) for supported versions. + +## SBOM +Trivy detects packages that are listed in the [software inventory]. + +[software inventory]: https://bottlerocket.dev/en/os/1.37.x/concepts/variants/#software-inventory diff --git a/docs/docs/coverage/os/index.md b/docs/docs/coverage/os/index.md index 74a40787c600..e82e680451d3 100644 --- a/docs/docs/coverage/os/index.md +++ b/docs/docs/coverage/os/index.md @@ -28,6 +28,7 @@ Trivy supports operating systems for | [Photon OS](photon.md) | 1.0, 2.0, 3.0, 4.0 | tndf/yum/rpm | | [Debian GNU/Linux](debian.md) | 7, 8, 9, 10, 11, 12 | apt/dpkg | | [Ubuntu](ubuntu.md) | All versions supported by Canonical | apt/dpkg | +| [Bottlerocket](bottlerocket.md) | 1.7.0 and upper | bottlerocket | | [OSs with installed Conda](../others/conda.md) | - | conda | ## Supported container images diff --git a/docs/docs/coverage/os/rhel.md b/docs/docs/coverage/os/rhel.md index 8300005a4968..5694c99b5999 100644 --- a/docs/docs/coverage/os/rhel.md +++ b/docs/docs/coverage/os/rhel.md @@ -22,6 +22,13 @@ Trivy detects packages that have been installed through package managers such as ## Vulnerability Red Hat offers its own security advisories, and these are utilized when scanning Red Hat Enterprise Linux (RHEL) for vulnerabilities. +### Content manifests +Red Hat’s security advisories use CPEs to identify product sets. For example, even packages installed in the same container image can have different CPEs. +For this reason, Red Hat’s container images include stored content manifests, which we convert to CPEs, and perform vulnerability scanning. + +Since this system ties each content manifest to its packages on a per-layer basis, +if layers get merged (for instance, by using `docker run` or `docker export`) we can no longer determine the correct CPE, which may lead to false detection. + ### Data Source See [here](../../scanner/vulnerability.md#data-sources). @@ -82,3 +89,5 @@ Trivy identifies licenses by examining the metadata of RPM packages. [NVD]: https://nvd.nist.gov/vuln/detail/CVE-2023-0464 [vulnerability statuses]: ../../configuration/filtering.md#by-status + +[content-set-default]: https://github.com/aquasecurity/trivy/blob/c80310d7690d8aeb7d3d77416c18c0c8b9aebe17/pkg/detector/ospkg/redhat/redhat.go#L25-L42 diff --git a/docs/docs/references/configuration/cli/trivy_config.md b/docs/docs/references/configuration/cli/trivy_config.md index 3d2eaeffcfdb..ccb82a415211 100644 --- a/docs/docs/references/configuration/cli/trivy_config.md +++ b/docs/docs/references/configuration/cli/trivy_config.md @@ -51,6 +51,7 @@ trivy config [flags] DIR --output-plugin-arg string [EXPERIMENTAL] output plugin arguments --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. --password-stdin password from stdin. Comma-separated passwords are not supported. + --raw-config-scanners strings specify the types of scanners that will also scan raw configurations. For example, scanners will scan a non-adapted configuration into a shared state (allowed values: terraform) --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend --redis-key string redis key file location, if using redis as cache backend diff --git a/docs/docs/references/configuration/cli/trivy_filesystem.md b/docs/docs/references/configuration/cli/trivy_filesystem.md index f0db05b75b8b..00fecefa6d72 100644 --- a/docs/docs/references/configuration/cli/trivy_filesystem.md +++ b/docs/docs/references/configuration/cli/trivy_filesystem.md @@ -75,7 +75,7 @@ trivy filesystem [flags] PATH --ignored-licenses strings specify a list of license to ignore --ignorefile string specify .trivyignore file (default ".trivyignore") --include-deprecated-checks include deprecated checks - --include-dev-deps include development dependencies in the report (supported: npm, yarn) + --include-dev-deps include development dependencies in the report (supported: npm, yarn, gradle) --include-non-failures include successes, available with '--scanners misconfig' --java-db-repository strings OCI repository(ies) to retrieve trivy-java-db in order of priority (default [mirror.gcr.io/aquasec/trivy-java-db:1,ghcr.io/aquasecurity/trivy-java-db:1]) --license-confidence-level float specify license classifier's confidence level (default 0.9) @@ -99,6 +99,7 @@ trivy filesystem [flags] PATH - indirect (default [unknown,root,workspace,direct,indirect]) --pkg-types strings list of package types (allowed values: os,library) (default [os,library]) + --raw-config-scanners strings specify the types of scanners that will also scan raw configurations. For example, scanners will scan a non-adapted configuration into a shared state (allowed values: terraform) --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend --redis-key string redis key file location, if using redis as cache backend diff --git a/docs/docs/references/configuration/cli/trivy_image.md b/docs/docs/references/configuration/cli/trivy_image.md index 2ed59e26b3ed..d8c3cd605185 100644 --- a/docs/docs/references/configuration/cli/trivy_image.md +++ b/docs/docs/references/configuration/cli/trivy_image.md @@ -38,7 +38,7 @@ trivy image [flags] IMAGE_NAME --cache-ttl duration cache TTL when using redis as cache backend --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:1") - --compliance string compliance report to generate (allowed values: docker-cis-1.6.0) + --compliance string compliance report to generate (built-in compliance's: docker-cis-1.6.0) --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded --config-file-schemas strings specify paths to JSON configuration file schemas to determine that a file matches some configuration and pass the schema to Rego checks for type checking @@ -120,6 +120,7 @@ trivy image [flags] IMAGE_NAME --pkg-types strings list of package types (allowed values: os,library) (default [os,library]) --platform string set platform in the form os/arch if image is multi-platform capable --podman-host string unix podman socket path to use for podman scanning + --raw-config-scanners strings specify the types of scanners that will also scan raw configurations. For example, scanners will scan a non-adapted configuration into a shared state (allowed values: terraform) --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend --redis-key string redis key file location, if using redis as cache backend diff --git a/docs/docs/references/configuration/cli/trivy_kubernetes.md b/docs/docs/references/configuration/cli/trivy_kubernetes.md index 30ecd4c56b49..1308daa2ce6a 100644 --- a/docs/docs/references/configuration/cli/trivy_kubernetes.md +++ b/docs/docs/references/configuration/cli/trivy_kubernetes.md @@ -35,7 +35,7 @@ trivy kubernetes [flags] [CONTEXT] --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:1") --compliance string compliance report to generate - Allowed values: + Built-in compliance's: - k8s-nsa-1.0 - k8s-cis-1.23 - eks-cis-1.4 @@ -111,6 +111,7 @@ trivy kubernetes [flags] [CONTEXT] (default [unknown,root,workspace,direct,indirect]) --pkg-types strings list of package types (allowed values: os,library) (default [os,library]) --qps float specify the maximum QPS to the master from this client (default 5) + --raw-config-scanners strings specify the types of scanners that will also scan raw configurations. For example, scanners will scan a non-adapted configuration into a shared state (allowed values: terraform) --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend --redis-key string redis key file location, if using redis as cache backend diff --git a/docs/docs/references/configuration/cli/trivy_repository.md b/docs/docs/references/configuration/cli/trivy_repository.md index 19e839fe5bc7..f43be8e9fc6e 100644 --- a/docs/docs/references/configuration/cli/trivy_repository.md +++ b/docs/docs/references/configuration/cli/trivy_repository.md @@ -74,7 +74,7 @@ trivy repository [flags] (REPO_PATH | REPO_URL) --ignored-licenses strings specify a list of license to ignore --ignorefile string specify .trivyignore file (default ".trivyignore") --include-deprecated-checks include deprecated checks - --include-dev-deps include development dependencies in the report (supported: npm, yarn) + --include-dev-deps include development dependencies in the report (supported: npm, yarn, gradle) --include-non-failures include successes, available with '--scanners misconfig' --java-db-repository strings OCI repository(ies) to retrieve trivy-java-db in order of priority (default [mirror.gcr.io/aquasec/trivy-java-db:1,ghcr.io/aquasecurity/trivy-java-db:1]) --license-confidence-level float specify license classifier's confidence level (default 0.9) @@ -98,6 +98,7 @@ trivy repository [flags] (REPO_PATH | REPO_URL) - indirect (default [unknown,root,workspace,direct,indirect]) --pkg-types strings list of package types (allowed values: os,library) (default [os,library]) + --raw-config-scanners strings specify the types of scanners that will also scan raw configurations. For example, scanners will scan a non-adapted configuration into a shared state (allowed values: terraform) --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend --redis-key string redis key file location, if using redis as cache backend diff --git a/docs/docs/references/configuration/cli/trivy_rootfs.md b/docs/docs/references/configuration/cli/trivy_rootfs.md index 3cec50ecd6e4..4efa10afa814 100644 --- a/docs/docs/references/configuration/cli/trivy_rootfs.md +++ b/docs/docs/references/configuration/cli/trivy_rootfs.md @@ -101,6 +101,7 @@ trivy rootfs [flags] ROOTDIR - indirect (default [unknown,root,workspace,direct,indirect]) --pkg-types strings list of package types (allowed values: os,library) (default [os,library]) + --raw-config-scanners strings specify the types of scanners that will also scan raw configurations. For example, scanners will scan a non-adapted configuration into a shared state (allowed values: terraform) --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend --redis-key string redis key file location, if using redis as cache backend diff --git a/docs/docs/references/configuration/cli/trivy_sbom.md b/docs/docs/references/configuration/cli/trivy_sbom.md index 0d44a9eaf6be..abd04694b176 100644 --- a/docs/docs/references/configuration/cli/trivy_sbom.md +++ b/docs/docs/references/configuration/cli/trivy_sbom.md @@ -98,8 +98,6 @@ trivy sbom [flags] SBOM_PATH (default [UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL]) --show-suppressed [EXPERIMENTAL] show suppressed vulnerabilities --skip-db-update skip updating vulnerability database - --skip-dirs strings specify the directories or glob patterns to skip - --skip-files strings specify the files or glob patterns to skip --skip-java-db-update skip updating Java index database --skip-vex-repo-update [EXPERIMENTAL] Skip VEX Repository update --table-mode strings [EXPERIMENTAL] tables that will be displayed in 'table' format (allowed values: summary,detailed) (default [summary,detailed]) diff --git a/docs/docs/references/configuration/cli/trivy_vm.md b/docs/docs/references/configuration/cli/trivy_vm.md index fffa275c0d30..2b5c83ca026a 100644 --- a/docs/docs/references/configuration/cli/trivy_vm.md +++ b/docs/docs/references/configuration/cli/trivy_vm.md @@ -91,6 +91,7 @@ trivy vm [flags] VM_IMAGE - indirect (default [unknown,root,workspace,direct,indirect]) --pkg-types strings list of package types (allowed values: os,library) (default [os,library]) + --raw-config-scanners strings specify the types of scanners that will also scan raw configurations. For example, scanners will scan a non-adapted configuration into a shared state (allowed values: terraform) --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend --redis-key string redis key file location, if using redis as cache backend diff --git a/docs/docs/references/configuration/config-file.md b/docs/docs/references/configuration/config-file.md index 6a2cd1e47dea..6149d7000824 100644 --- a/docs/docs/references/configuration/config-file.md +++ b/docs/docs/references/configuration/config-file.md @@ -409,6 +409,9 @@ misconfiguration: # Same as '--include-non-failures' include-non-failures: false + # Same as '--raw-config-scanners' + raw-config-scanners: [] + # Same as '--render-cause' render-cause: [] diff --git a/docs/docs/scanner/license.md b/docs/docs/scanner/license.md index cb7b341032ab..a33dc905335f 100644 --- a/docs/docs/scanner/license.md +++ b/docs/docs/scanner/license.md @@ -342,6 +342,28 @@ license: permissive: [] ``` +#### Text licenses +By default, Trivy categorizes a license as UNKNOWN if it cannot determine the license name from the license text. + +To define a category for a text license, you need to add license with the `text://` prefix to license classification. +For example: +```yaml +license: + forbidden: + - "text://Text of Apache Software Foundation License" +``` + +But a text license can by large. So for these cases Trivy supports using `regex` in license classification. +For example: +```yaml +license: + forbidden: + - "text://.* Apache Software .*" +``` + +!!! note + `regex` is only used for text licenses and can't be used to configure license IDs. + [^1]: See the list of supported language files [here](../coverage/language/index.md). [^2]: Some lock files require additional files (e.g. files from the cache directory) to detect licenses. Check [coverage][coverage] for more information. diff --git a/docs/docs/scanner/misconfiguration/config/config.md b/docs/docs/scanner/misconfiguration/config/config.md new file mode 100644 index 000000000000..7d21425996cf --- /dev/null +++ b/docs/docs/scanner/misconfiguration/config/config.md @@ -0,0 +1,259 @@ +This page describes misconfiguration-specific configuration. + +### Enabling a subset of misconfiguration scanners +It's possible to only enable certain misconfiguration scanners if you prefer. +You can do so by passing the `--misconfig-scanners` option. +This flag takes a comma-separated list of configuration scanner types. + +```bash +trivy config --misconfig-scanners=terraform,dockerfile . +``` + +Will only scan for misconfigurations that pertain to Terraform and Dockerfiles. + +### Loading custom checks +You can load check files or directories including your custom checks using the `--config-check` flag. +This can be repeated for specifying multiple files or directories. + +```bash +trivy config --config-check custom-policy/policy --config-check combine/policy --config-check policy.rego --namespaces user myapp +``` + +You can load checks bundle as OCI Image from a Container Registry using the `--checks-bundle-repository` flag. + +```bash +trivy config --checks-bundle-repository myregistry.local/mychecks --namespaces user myapp +``` + + +### Passing custom data +You can pass directories including your custom data through `--data` option. +This can be repeated for specifying multiple directories. + +```bash +cd examples/misconf/custom-data +trivy config --config-check ./my-check --data ./data --namespaces user ./configs +``` + +For more details, see [Custom Data](./custom/data.md). + +### Passing namespaces +By default, Trivy evaluates checks defined in `builtin.*`. +If you want to evaluate custom checks in other packages, you have to specify package prefixes through `--namespaces` option. +This can be repeated for specifying multiple packages. + +``` bash +trivy config --config-check ./my-check --namespaces main --namespaces user ./configs +``` + +### Private Terraform registries +Trivy can download Terraform code from private registries. +To pass credentials you must use the `TF_TOKEN_` environment variables. +You cannot use a `.terraformrc` or `terraform.rc` file, these are not supported by trivy yet. + +From the Terraform [docs](https://developer.hashicorp.com/terraform/cli/config/config-file#environment-variable-credentials): + +> Environment variable names should have the prefix TF_TOKEN_ added to the domain name, with periods encoded as underscores. +> For example, the value of a variable named `TF_TOKEN_app_terraform_io` will be used as a bearer authorization token when the CLI makes service requests to the hostname `app.terraform.io`. +> +> You must convert domain names containing non-ASCII characters to their punycode equivalent with an ACE prefix. +> For example, token credentials for `例えば.com` must be set in a variable called `TF_TOKEN_xn--r8j3dr99h_com`. +> +> Hyphens are also valid within host names but usually invalid as variable names and may be encoded as double underscores. +> For example, you can set a token for the domain name café.fr as TF_TOKEN_xn--caf-dma_fr or TF_TOKEN_xn____caf__dma_fr. + +If multiple variables evaluate to the same hostname, Trivy will choose the environment variable name where the dashes have not been encoded as double underscores. + + +### Skipping resources by inline comments + +Trivy supports ignoring misconfigured resources by inline comments for Terraform, CloudFormation and Helm configuration files only. + +In cases where Trivy can detect comments of a specific format immediately adjacent to resource definitions, it is possible to ignore findings from a single source of resource definition (in contrast to `.trivyignore`, which has a directory-wide scope on all of the files scanned). The format for these comments is `trivy:ignore:` immediately following the format-specific line-comment [token](https://developer.hashicorp.com/terraform/language/syntax/configuration#comments). + +The ignore rule must contain one of the possible check IDs that can be found in its metadata: ID, short code or alias. The `id` from the metadata is not case-sensitive, so you can specify, for example, `AVD-AWS-0089` or `avd-aws-0089`. + +For example, to ignore a misconfiguration ID `AVD-GCP-0051` in a Terraform HCL file: + +```terraform +#trivy:ignore:AVD-GCP-0051 +resource "google_container_cluster" "example" { + name = var.cluster_name + location = var.region +} +``` + +You can add multiple ignores on the same comment line: +```terraform +#trivy:ignore:AVD-GCP-0051 trivy:ignore:AVD-GCP-0053 +resource "google_container_cluster" "example" { + name = var.cluster_name + location = var.region +} +``` + +You can also specify a long ID, which is formed as follows: `--`. + +As an example, consider the following check metadata: + +```yaml +# custom: + # id: AVD-AWS-0089 + # avd_id: AVD-AWS-0089 + # provider: aws + # service: s3 + # severity: LOW + # short_code: enable-logging +``` + +Long ID would look like the following: `aws-s3-enable-logging`. + +Example for CloudFromation: +```yaml +AWSTemplateFormatVersion: "2010-09-09" +Resources: +#trivy:ignore:* + S3Bucket: + Type: 'AWS::S3::Bucket' + Properties: + BucketName: test-bucket +``` + +!!!note +Ignore rules for Helm files should be placed before the YAML object, since only it contains the location data needed for ignoring. + +Example for Helm: +```yaml + serviceAccountName: "testchart.serviceAccountName" + containers: + # trivy:ignore:KSV018 + - name: "testchart" + securityContext: + runAsUser: 1000 + runAsGroup: 3000 + image: "your-repository/your-image:your-tag" + imagePullPolicy: "Always" + + +#### Expiration Date + +You can specify the expiration date of the ignore rule in `yyyy-mm-dd` format. This is a useful feature when you want to make sure that an ignored issue is not forgotten and worth revisiting in the future. For example: +```tf +#trivy:ignore:aws-s3-enable-logging:exp:2024-03-10 +resource "aws_s3_bucket" "example" { + bucket = "test" +} +``` + +The `aws-s3-enable-logging` check will be ignored until `2024-03-10` until the ignore rule expires. + +#### Ignoring by attributes + +You can ignore a resource by its attribute value. This is useful when using the `for-each` meta-argument. For example: + +```tf +locals { + ports = ["3306", "5432"] +} + +#trivy:ignore:aws-ec2-no-public-ingress-sgr[from_port=3306] +resource "aws_security_group_rule" "example" { + for_each = toset(local.ports) + type = "ingress" + from_port = each.key + to_port = each.key + protocol = "TCP" + cidr_blocks = ["0.0.0.0/0"] + security_group_id = aws_security_group.example.id + source_security_group_id = aws_security_group.example.id +} +``` + +The `aws-ec2-no-public-ingress-sgr` check will be ignored only for the `aws_security_group_rule` resource with port number `5432`. It is important to note that the ignore rule should not enclose the attribute value in quotes, despite the fact that the port is represented as a string. + +If you want to ignore multiple resources on different attributes, you can specify multiple ignore rules: + +```tf +#trivy:ignore:aws-ec2-no-public-ingress-sgr[from_port=3306] +#trivy:ignore:aws-ec2-no-public-ingress-sgr[from_port=5432] +``` + +You can also ignore a resource on multiple attributes in the same rule: +```tf +locals { + rules = { + first = { + port = 1000 + type = "ingress" + }, + second = { + port = 1000 + type = "egress" + } + } +} + +#trivy:ignore:aws-ec2-no-public-ingress-sgr[from_port=1000,type=egress] +resource "aws_security_group_rule" "example" { + for_each = { for k, v in local.rules : k => v } + + type = each.value.type + from_port = each.value.port + to_port = each.value.port + protocol = "TCP" + cidr_blocks = ["0.0.0.0/0"] + security_group_id = aws_security_group.example.id + source_security_group_id = aws_security_group.example.id +} +``` + +Checks can also be ignored by nested attributes: + +```tf +#trivy:ignore:*[logging_config.prefix=myprefix] +resource "aws_cloudfront_distribution" "example" { + logging_config { + include_cookies = false + bucket = "mylogs.s3.amazonaws.com" + prefix = "myprefix" + } +} +``` + +#### Ignoring module issues + +Issues in third-party modules cannot be ignored using the method described above, because you may not have access to modify the module source code. In such a situation you can add ignore rules above the module block, for example: + +```tf +#trivy:ignore:aws-s3-enable-logging +module "s3_bucket" { + source = "terraform-aws-modules/s3-bucket/aws" + + bucket = "my-s3-bucket" +} +``` + +An example of ignoring checks for a specific bucket in a module: +```tf +locals { + bucket = ["test1", "test2"] +} + +#trivy:ignore:*[bucket=test1] +module "s3_bucket" { + for_each = toset(local.bucket) + source = "terraform-aws-modules/s3-bucket/aws" + bucket = each.value +} +``` + +#### Support for Wildcards + +You can use wildcards in the `ws` (workspace) and `ignore` sections of the ignore rules. + +```tf +# trivy:ignore:aws-s3-*:ws:dev-* +``` + +This example ignores all checks starting with `aws-s3-` for workspaces matching the pattern `dev-*`. + diff --git a/docs/docs/scanner/misconfiguration/custom/index.md b/docs/docs/scanner/misconfiguration/custom/index.md index ad6cc90ee352..4b2449640743 100644 --- a/docs/docs/scanner/misconfiguration/custom/index.md +++ b/docs/docs/scanner/misconfiguration/custom/index.md @@ -207,6 +207,7 @@ You can specify input format via the `custom.input` annotation. - `yaml` (Generic YAML) - `json` (Generic JSON) - `toml` (Generic TOML) + - `terraform-raw` (Terraform configuration is not converted to common state as for the Cloud format, allowing for more flexible and direct checks on the original code) When configuration languages such as Kubernetes are not identified, file formats such as JSON will be used as `type`. When a configuration language is identified, it will overwrite `type`. diff --git a/docs/docs/scanner/misconfiguration/custom/schema.md b/docs/docs/scanner/misconfiguration/custom/schema.md index 7b305cc10dbf..9bb21cf71826 100644 --- a/docs/docs/scanner/misconfiguration/custom/schema.md +++ b/docs/docs/scanner/misconfiguration/custom/schema.md @@ -1,11 +1,38 @@ # Input Schema ## Overview -Checks can be defined with custom schemas that allow inputs to be verified against them. Adding a policy schema + +Schemas are declarative documents that define the structure, data types and constraints of inputs being scanned. Trivy provides certain schemas out of the box as seen in the explorer [here](https://aquasecurity.github.io/trivy-schemas/). You can also find the source code for the schemas [here](https://github.com/aquasecurity/trivy/tree/main/pkg/iac/rego/schemas). + +It is not required to pass in schemas, in order to scan inputs by Trivy but are required if type-checking is needed. + +Checks can be defined with custom schemas that allow inputs to be verified against them. Adding an input schema enables Trivy to show more detailed error messages when an invalid input is encountered. -In Trivy we have been able to define a schema for a [Dockerfile](https://github.com/aquasecurity/trivy/tree/main/pkg/iac/rego/schemas) -Without input schemas, a policy would be as follows: +## Unified Schema + +One of the unique advantages of Trivy is to take a variety of inputs, such as IaC files (e.g. CloudFormation, Terraform etc.) and also live cloud scanning +(e.g. [Trivy AWS plugin](https://github.com/aquasecurity/trivy-aws)) and normalize them into a standard structure, as defined by the schema. + +An example of such an application would be scanning AWS resources. You can scan them prior to deployment via the Trivy misconfiguration scanner and also +scan them after they've been deployed in the cloud with Trivy AWS scanning. Both scan methods should yield the same result as resources are gathered into +a unified representation as defined by the [Cloud schema](https://github.com/aquasecurity/trivy/blob/main/pkg/iac/rego/schemas/cloud.json). + + +## Supported Schemas +Currently out of the box the following schemas are supported natively: + +1. [Docker](https://github.com/aquasecurity/trivy/blob/main/pkg/iac/rego/schemas/dockerfile.json) +2. [Kubernetes](https://github.com/aquasecurity/trivy/blob/main/pkg/iac/rego/schemas/kubernetes.json) +3. [Cloud](https://github.com/aquasecurity/trivy/blob/main/pkg/iac/rego/schemas/cloud.json) +4. [Terraform Raw Format](https://github.com/aquasecurity/trivy/blob/main/pkg/iac/rego/schemas/terraform-raw.json) + +You can interactively view these schemas with the [Trivy Schema Explorer](https://aquasecurity.github.io/trivy-schemas/) + + +## Example +As mentioned earlier, amongst other built-in schemas, Trivy offers a built in-schema for scanning Dockerfiles. It is available [here](https://github.com/aquasecurity/trivy/tree/main/pkg/iac/rego/schemas) +Without input schemas, a check would be as follows: !!! example ``` @@ -17,10 +44,10 @@ Without input schemas, a policy would be as follows: } ``` -If this policy is run against offending Dockerfile(s), there will not be any issues as the policy will fail to evaluate. -Although the policy's failure to evaluate is legitimate, this should not result in a positive result for the scan. +If this check is run against an offending Dockerfile(s), there will not be any issues as the check will fail to evaluate. +Although the check's failure to evaluate is legitimate, this should not result in a positive result for the scan. -For instance if we have a policy that checks for misconfigurations in a `Dockerfile`, we could define the +For instance if we have a check that checks for misconfigurations in a `Dockerfile`, we could define the schema as such !!! example @@ -38,26 +65,20 @@ schema as such Here `input: schema["dockerfile"]` points to a schema that expects a valid `Dockerfile` as input. An example of this can be found [here](https://github.com/aquasecurity/trivy/blob/main/pkg/iac/rego/schemas/dockerfile.json). -Now if this policy is evaluated against, a more descriptive error will be available to help fix the problem. +Now if this check is evaluated against, a more descriptive error will be available to help fix the problem. ```bash -1 error occurred: testpolicy.rego:8: rego_type_error: undefined ref: input.evil +1 error occurred: testcheck.rego:8: rego_type_error: undefined ref: input.evil input.evil ^ have: "evil" want (one of): ["Stages"] ``` -Currently, out of the box the following schemas are supported natively: - -1. [Docker](https://github.com/aquasecurity/trivy/blob/main/pkg/iac/rego/schemas/dockerfile.json) -2. [Kubernetes](https://github.com/aquasecurity/trivy/blob/main/pkg/iac/rego/schemas/kubernetes.json) -3. [Cloud](https://github.com/aquasecurity/trivy/blob/main/pkg/iac/rego/schemas/cloud.json) - ## Custom Checks with Custom Schemas -You can also bring a custom policy that defines one or more custom schema. +You can also bring a custom check that defines one or more custom schema. !!! example ``` @@ -77,16 +98,83 @@ The checks can be placed in a structure as follows !!! example ``` /Users/user/my-custom-checks - ├── my_policy.rego + ├── my_check.rego └── schemas └── fooschema.json └── barschema.json ``` -To use such a policy with Trivy, use the `--config-policy` flag that points to the policy file or to the directory where the schemas and checks are contained. +To use such a check with Trivy, use the `--config-check` flag that points to the check file or to the directory where the schemas and checks are contained. ```bash -$ trivy --config-policy=/Users/user/my-custom-checks +$ trivy --config-check=/Users/user/my-custom-checks ``` -For more details on how to define schemas within Rego checks, please see the [OPA guide](https://www.openpolicyagent.org/docs/latest/policy-language/#schema-annotations) that describes it in more detail. \ No newline at end of file +For more details on how to define schemas within Rego checks, please see the [OPA guide](https://www.openpolicyagent.org/docs/latest/policy-language/#schema-annotations) that describes it in more detail. + +### Scan arbitrary JSON and YAML configurations +By default, scanning JSON and YAML configurations is disabled, since Trivy does not contain built-in checks for these configurations. To enable it, pass the `json` or `yaml` to `--misconfig-scanners`. Trivy will pass each file as is to the checks input. + + +!!! example +```bash +$ cat iac/serverless.yaml +service: serverless-rest-api-with-pynamodb + +frameworkVersion: ">=2.24.0" + +plugins: + - serverless-python-requirements +... + +$ cat serverless.rego +# METADATA +# title: Serverless Framework service name not starting with "aws-" +# description: Ensure that Serverless Framework service names start with "aws-" +# schemas: +# - input: schema["serverless-schema"] +# custom: +# id: SF001 +# severity: LOW +package user.serverless001 + +deny[res] { + not startswith(input.service, "aws-") + res := result.new( + sprintf("Service name %q is not allowed", [input.service]), + input.service + ) +} + +$ trivy config --misconfig-scanners=json,yaml --config-check ./serverless.rego --check-namespaces user ./iac +serverless.yaml (yaml) + +Tests: 4 (SUCCESSES: 3, FAILURES: 1) +Failures: 1 (UNKNOWN: 0, LOW: 1, MEDIUM: 0, HIGH: 0, CRITICAL: 0) + +LOW: Service name "serverless-rest-api-with-pynamodb" is not allowed +═════════════════════════════════════════════════════════════════════════════════════════════════════════ +Ensure that Serverless Framework service names start with "aws-" +``` + +!!! note +In the case above, the custom check specified has a metadata annotation for the input schema `input: schema["serverless-schema"]`. This allows Trivy to type check the input IaC files provided. + +Optionally, you can also pass schemas using the `config-file-schemas` flag. Trivy will use these schemas for file filtering and type checking in Rego checks. + +!!! example +```bash +$ trivy config --misconfig-scanners=json,yaml --config-check ./serverless.rego --check-namespaces user --config-file-schemas ./serverless-schema.json ./iac +``` + +If the `--config-file-schemas` flag is specified Trivy ensures that each input IaC config file being scanned is type-checked against the schema. If the input file does not match any of the passed schemas, it will be ignored. + +If the schema is specified in the check metadata and is in the directory specified in the `--config-check` argument, it will be automatically loaded as specified [here](./custom/schema.md#custom-checks-with-custom-schemas), and will only be used for type checking in Rego. + +!!! note +If a user specifies the `--config-file-schemas` flag, all input IaC config files are ensured that they pass type-checking. It is not required to pass an input schema in case type checking is not required. This is helpful for scenarios where you simply want to write a Rego check and pass in IaC input for it. Such a use case could include scanning for a new service which Trivy might not support just yet. + +!!! tip +It is also possible to specify multiple input schemas with `--config-file-schema` flag as it can accept a comma seperated list of file paths or a directory as input. In the case of multiple schemas being specified, all of them will be evaluated against all the input files. + + diff --git a/docs/docs/scanner/vulnerability.md b/docs/docs/scanner/vulnerability.md index 0eb47aaaa877..c8d6c5460973 100644 --- a/docs/docs/scanner/vulnerability.md +++ b/docs/docs/scanner/vulnerability.md @@ -120,7 +120,6 @@ See [here](../coverage/language/index.md#supported-languages) for the supported | PHP | [PHP Security Advisories Database][php] | ✅ | - | | | [GitHub Advisory Database (Composer)][php-ghsa] | ✅ | - | | Python | [GitHub Advisory Database (pip)][python-ghsa] | ✅ | - | -| | [Open Source Vulnerabilities (PyPI)][python-osv] | ✅ | - | | Ruby | [Ruby Advisory Database][ruby] | ✅ | - | | | [GitHub Advisory Database (RubyGems)][ruby-ghsa] | ✅ | - | | Node.js | [Ecosystem Security Working Group][nodejs] | ✅ | - | @@ -294,9 +293,10 @@ This feature allows you to focus on vulnerabilities in specific types of depende In Trivy, there are four types of package relationships: 1. `root`: The root package being scanned -2. `direct`: Direct dependencies of the root package -3. `indirect`: Transitive dependencies -4. `unknown`: Packages whose relationship cannot be determined +2. `workspace`: Workspaces of the root package (Currently only `pom.xml`, `yarn.lock` and `cargo.lock` files are supported) +3. `direct`: Direct dependencies of the root/workspace package +4. `indirect`: Transitive dependencies +5. `unknown`: Packages whose relationship cannot be determined The available relationships may vary depending on the ecosystem. To see which relationships are supported for a particular project, you can use the JSON output format and check the `Relationship` field: diff --git a/docs/docs/target/rootfs.md b/docs/docs/target/rootfs.md index ab6c0ee02798..8a403f57d5e2 100644 --- a/docs/docs/target/rootfs.md +++ b/docs/docs/target/rootfs.md @@ -14,6 +14,9 @@ $ trivy rootfs /path/to/rootfs You should use `trivy fs` to scan your local projects in CI/CD. See [here](../scanner/vulnerability.md) for the differences. +!!! note + Scanning vulnerabilities for `Red Hat` has a limitation, see the [Red Hat](../coverage/os/rhel.md#content-manifests) page for details. + ## Performance Optimization By default, Trivy traverses all files from the specified root directory to find target files for scanning. diff --git a/docs/docs/target/vm.md b/docs/docs/target/vm.md index 4bba67626463..8cbe5284af45 100644 --- a/docs/docs/target/vm.md +++ b/docs/docs/target/vm.md @@ -150,6 +150,9 @@ See [here](../scanner/vulnerability.md) for the detail. $ trivy vm [YOUR_VM_IMAGE] ``` +!!! note + Scanning `Red Hat` has a limitation, see the [Red Hat](../coverage/os/rhel.md#content-manifests) page for details. + ### Misconfigurations It is supported, but it is not useful in most cases. As mentioned [here](../scanner/misconfiguration/index.md), Trivy mainly supports Infrastructure as Code (IaC) files for misconfigurations. diff --git a/docs/tutorials/misconfiguration/custom-checks.md b/docs/tutorials/misconfiguration/custom-checks.md index f36b11644465..3a6bddb86de1 100644 --- a/docs/tutorials/misconfiguration/custom-checks.md +++ b/docs/tutorials/misconfiguration/custom-checks.md @@ -48,7 +48,7 @@ package custom.dockerfile.ID001 import future.keywords.in ``` -Every rego check has a package name. In our case, we will call it `custom.dockerfile.ID001` to avoid confusion between custom checks and built-in checks. The group name `dockerfile` has no effect on the package name. Note that each package has to contain only one check. However, we can pass multiple checks into our Trivy scan. +Every Rego check has a package name. In our case, we will call it `custom.dockerfile.ID001` to avoid confusion between custom checks and built-in checks. The group name `dockerfile` has no effect on the package name. Note that each package has to contain only one check. However, we can pass multiple checks into our Trivy scan. The first keyword of the package, in this case `custom`, will be reused in the `trivy` command as the `--namespace`. ## Allowed data diff --git a/docs/tutorials/misconfiguration/terraform.md b/docs/tutorials/misconfiguration/terraform.md index e51ec8b5574a..6934a411183a 100644 --- a/docs/tutorials/misconfiguration/terraform.md +++ b/docs/tutorials/misconfiguration/terraform.md @@ -90,7 +90,7 @@ trivy config --tf-vars terraform.tfvars ./ ``` ### Custom Checks -We have lots of examples in the [documentation](https://trivy.dev/latest/docs/scanner/misconfiguration/custom/) on how you can write and pass custom Rego checks into terraform misconfiguration scans. +We have lots of examples in the [documentation](https://trivy.dev/latest/docs/scanner/misconfiguration/custom/) on how you can write and pass custom Rego checks into terraform misconfiguration scans. ## Secret and vulnerability scans diff --git a/go.mod b/go.mod index 0423ed47cc7b..32b2054c9869 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,12 @@ module github.com/aquasecurity/trivy -go 1.24 +go 1.24.2 require ( github.com/Azure/azure-sdk-for-go v68.0.0+incompatible - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 - github.com/BurntSushi/toml v1.4.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.0 + github.com/BurntSushi/toml v1.5.0 github.com/CycloneDX/cyclonedx-go v0.9.2 github.com/GoogleCloudPlatform/docker-credential-gcr v2.0.5+incompatible github.com/Masterminds/sprig/v3 v3.3.0 @@ -20,39 +20,36 @@ require ( github.com/aquasecurity/go-pep440-version v0.0.1 github.com/aquasecurity/go-version v0.0.1 github.com/aquasecurity/iamgo v0.0.10 - github.com/aquasecurity/jfather v0.0.8 - github.com/aquasecurity/table v1.8.0 + github.com/aquasecurity/table v1.10.0 github.com/aquasecurity/testdocker v0.0.0-20240730042311-4642e94c7fc8 github.com/aquasecurity/tml v0.6.1 - github.com/aquasecurity/trivy-checks v1.8.1 + github.com/aquasecurity/trivy-checks v1.10.0 github.com/aquasecurity/trivy-db v0.0.0-20250227071930-8bd8a9b89e2d github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 - github.com/aquasecurity/trivy-kubernetes v0.8.1 + github.com/aquasecurity/trivy-kubernetes v0.8.2 github.com/aws/aws-sdk-go-v2 v1.36.3 - github.com/aws/aws-sdk-go-v2/config v1.29.13 - github.com/aws/aws-sdk-go-v2/credentials v1.17.66 - github.com/aws/aws-sdk-go-v2/service/ec2 v1.211.2 - github.com/aws/aws-sdk-go-v2/service/ecr v1.43.2 - github.com/aws/aws-sdk-go-v2/service/s3 v1.79.1 + github.com/aws/aws-sdk-go-v2/config v1.29.14 + github.com/aws/aws-sdk-go-v2/credentials v1.17.67 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.218.0 + github.com/aws/aws-sdk-go-v2/service/ecr v1.44.0 + github.com/aws/aws-sdk-go-v2/service/s3 v1.79.3 github.com/aws/smithy-go v1.22.3 github.com/bitnami/go-version v0.0.0-20231130084017-bb00604d650c github.com/bmatcuk/doublestar/v4 v4.8.1 github.com/cenkalti/backoff/v4 v4.3.0 github.com/cheggaaa/pb/v3 v3.1.7 - github.com/containerd/containerd/v2 v2.0.4 + github.com/containerd/containerd/v2 v2.1.1 github.com/containerd/platforms v1.0.0-rc.1 github.com/distribution/reference v0.6.0 - github.com/docker/cli v28.0.4+incompatible - github.com/docker/docker v28.0.4+incompatible + github.com/docker/cli v28.1.1+incompatible + github.com/docker/docker v28.1.1+incompatible github.com/docker/go-connections v0.5.0 github.com/docker/go-units v0.5.0 github.com/fatih/color v1.18.0 - github.com/go-git/go-git/v5 v5.14.0 + github.com/go-git/go-git/v5 v5.16.0 github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 // Replace with encoding/json/v2 when proposal is accepted. Track https://github.com/golang/go/issues/71497 - github.com/go-openapi/runtime v0.28.0 // indirect - github.com/go-openapi/strfmt v0.23.0 // indirect github.com/go-redis/redis/v8 v8.11.5 - github.com/gocsaf/csaf/v3 v3.1.1 + github.com/gocsaf/csaf/v3 v3.2.0 github.com/golang-jwt/jwt/v5 v5.2.2 github.com/google/go-containerregistry v0.20.3 github.com/google/go-github/v62 v62.0.0 @@ -65,9 +62,9 @@ require ( github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.7.0 github.com/hashicorp/golang-lru/v2 v2.0.7 - github.com/hashicorp/hc-install v0.9.1 + github.com/hashicorp/hc-install v0.9.2 github.com/hashicorp/hcl/v2 v2.23.0 - github.com/hashicorp/terraform-exec v0.22.0 + github.com/hashicorp/terraform-exec v0.23.0 github.com/in-toto/in-toto-golang v0.9.0 github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f github.com/knqyf263/go-deb-version v0.0.0-20241115132648-6f4aee6ccd23 @@ -87,69 +84,65 @@ require ( github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/mitchellh/mapstructure v1.5.0 - github.com/moby/buildkit v0.18.2 - github.com/open-policy-agent/opa v1.2.0 + github.com/moby/buildkit v0.21.1 + github.com/open-policy-agent/opa v1.4.2 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.1 github.com/openvex/discovery v0.1.1-0.20240802171711-7c54efc57553 github.com/openvex/go-vex v0.2.5 github.com/owenrumney/go-sarif/v2 v2.3.3 - github.com/owenrumney/squealer v1.2.11 // indirect github.com/package-url/packageurl-go v0.1.3 github.com/quasilyte/go-ruleguard/dsl v0.3.22 github.com/rust-secure-code/go-rustaudit v0.0.0-20250226111315-e20ec32e963c - github.com/samber/lo v1.49.1 + github.com/samber/lo v1.50.0 github.com/sassoftware/go-rpmutils v0.4.0 github.com/secure-systems-lab/go-securesystemslib v0.9.0 - github.com/sigstore/rekor v1.3.9 + github.com/sigstore/rekor v1.3.10 github.com/sirupsen/logrus v1.9.3 github.com/sosedoff/gitkit v0.4.0 github.com/spdx/tools-golang v0.5.5 // v0.5.3 with necessary changes. Can be upgraded to version 0.5.4 after release. - github.com/spf13/cast v1.7.1 + github.com/spf13/cast v1.8.0 github.com/spf13/cobra v1.9.1 github.com/spf13/pflag v1.0.6 - github.com/spf13/viper v1.20.0 + github.com/spf13/viper v1.20.1 github.com/stretchr/testify v1.10.0 - github.com/testcontainers/testcontainers-go v0.36.0 - github.com/testcontainers/testcontainers-go/modules/localstack v0.36.0 + github.com/testcontainers/testcontainers-go v0.37.0 + github.com/testcontainers/testcontainers-go/modules/localstack v0.37.0 github.com/tetratelabs/wazero v1.9.0 github.com/twitchtv/twirp v8.1.3+incompatible github.com/xeipuuv/gojsonschema v1.2.0 github.com/xlab/treeprint v1.2.0 - github.com/zclconf/go-cty v1.16.2 + github.com/zclconf/go-cty v1.16.3 github.com/zclconf/go-cty-yaml v1.1.0 go.etcd.io/bbolt v1.4.0 - golang.org/x/crypto v0.36.0 - golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect + golang.org/x/crypto v0.38.0 golang.org/x/mod v0.24.0 - golang.org/x/net v0.37.0 - golang.org/x/sync v0.12.0 - golang.org/x/term v0.30.0 - golang.org/x/text v0.23.0 + golang.org/x/net v0.40.0 + golang.org/x/sync v0.14.0 + golang.org/x/term v0.32.0 + golang.org/x/text v0.25.0 golang.org/x/vuln v1.1.4 golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 - google.golang.org/protobuf v1.36.5 + google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 - helm.sh/helm/v3 v3.17.2 - k8s.io/api v0.32.3 + helm.sh/helm/v3 v3.17.3 + k8s.io/api v0.33.1 k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 - modernc.org/sqlite v1.36.1 - sigs.k8s.io/yaml v1.4.0 // indirect + modernc.org/sqlite v1.37.0 ) require ( - cel.dev/expr v0.19.0 // indirect - cloud.google.com/go v0.116.0 // indirect - cloud.google.com/go/auth v0.14.0 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect + cel.dev/expr v0.20.0 // indirect + cloud.google.com/go v0.118.3 // indirect + cloud.google.com/go/auth v0.15.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.8 // 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 + cloud.google.com/go/iam v1.4.1 // indirect + cloud.google.com/go/monitoring v1.24.0 // indirect + cloud.google.com/go/storage v1.50.0 // indirect dario.cat/mergo v1.0.1 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect - github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.29 // indirect @@ -157,11 +150,11 @@ require ( github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.4.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/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 // indirect github.com/Intevation/gval v1.3.0 // indirect github.com/Intevation/jsonpath v0.2.1 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect @@ -169,28 +162,40 @@ require ( github.com/Masterminds/semver/v3 v3.3.0 // indirect github.com/Masterminds/squirrel v1.5.4 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/Microsoft/hcsshim v0.12.9 // indirect - github.com/ProtonMail/go-crypto v1.1.5 // indirect + github.com/Microsoft/hcsshim v0.13.0 // indirect + github.com/ProtonMail/go-crypto v1.1.6 // indirect github.com/VividCortex/ewma v1.2.0 // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/agnivade/levenshtein v1.2.1 // indirect + github.com/alessio/shellescape v1.4.1 // indirect github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/aquasecurity/jfather v0.0.8 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/aws/aws-sdk-go v1.55.7 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect + github.com/aws/aws-sdk-go-v2/service/ebs v1.22.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/blang/semver v3.5.1+incompatible // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/briandowns/spinner v1.23.0 // indirect - github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect - github.com/cloudflare/circl v1.6.0 // indirect - github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect - github.com/containerd/cgroups/v3 v3.0.3 // indirect + github.com/cloudflare/circl v1.6.1 // indirect + github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect + github.com/containerd/cgroups/v3 v3.0.5 // indirect github.com/containerd/containerd v1.7.27 // indirect - github.com/containerd/containerd/api v1.8.0 // indirect + github.com/containerd/containerd/api v1.9.0 // indirect github.com/containerd/continuity v0.4.5 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect @@ -210,19 +215,21 @@ require ( github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect github.com/docker/distribution v2.8.3+incompatible // indirect - github.com/docker/docker-credential-helpers v0.8.2 // indirect + github.com/docker/docker-credential-helpers v0.9.3 // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/ebitengine/purego v0.8.2 // indirect github.com/emicklei/go-restful/v3 v3.11.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/envoyproxy/go-control-plane/envoy v1.32.4 // indirect + github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect github.com/evanphx/json-patch v5.9.0+incompatible // indirect + github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.8.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-chi/chi v4.1.2+incompatible // indirect github.com/go-errors/errors v1.4.2 // indirect @@ -235,33 +242,37 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-openapi/analysis v0.23.0 // indirect - github.com/go-openapi/errors v0.22.0 // indirect + github.com/go-openapi/errors v0.22.1 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/loads v0.22.0 // indirect + github.com/go-openapi/runtime v0.28.0 // indirect github.com/go-openapi/spec v0.21.0 // indirect - github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-openapi/strfmt v0.23.0 // indirect + github.com/go-openapi/swag v0.23.1 // indirect github.com/go-openapi/validate v0.24.0 // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/goccy/go-yaml v1.15.23 // indirect github.com/gofrs/uuid v4.3.1+incompatible // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect - github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/google/btree v1.1.2 // indirect + github.com/google/btree v1.1.3 // indirect github.com/google/certificate-transparency-go v1.1.8 // indirect - github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect + github.com/google/gnostic-models v0.6.9 // indirect github.com/google/go-cmp v0.7.0 // indirect + github.com/google/go-github/v31 v31.0.0 // indirect github.com/google/go-querystring v1.1.0 // indirect - github.com/google/gofuzz v1.2.0 // indirect github.com/google/s2a-go v0.1.9 // indirect + github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect + github.com/google/subcommands v1.2.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/gorilla/websocket v1.5.0 // indirect + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/gosuri/uitable v0.0.4 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect @@ -278,7 +289,8 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/klauspost/compress v1.17.11 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/knqyf263/labeler v0.0.0-20200423181506-7a6e545148c3 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec // indirect @@ -286,8 +298,8 @@ require ( github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a // indirect github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect - github.com/magiconair/properties v1.8.9 // indirect - github.com/mailru/easyjson v0.7.7 // indirect + github.com/magiconair/properties v1.8.10 // indirect + github.com/mailru/easyjson v0.9.0 // 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 @@ -296,13 +308,15 @@ require ( 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/go-archive v0.1.0 // indirect github.com/moby/locker v1.0.1 // indirect github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/spdystream v0.5.0 // indirect + github.com/moby/sys/atomicwriter v0.1.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.1 // indirect - github.com/moby/sys/user v0.3.0 // indirect + github.com/moby/sys/user v0.4.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -314,10 +328,13 @@ require ( github.com/ncruces/go-strftime v0.1.9 // indirect github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 // indirect github.com/oklog/ulid v1.3.1 // indirect - github.com/opencontainers/runtime-spec v1.2.0 // indirect - github.com/opencontainers/selinux v1.11.1 // indirect + github.com/oklog/ulid/v2 v2.1.0 // indirect + github.com/opencontainers/runtime-spec v1.2.1 // indirect + github.com/opencontainers/selinux v1.12.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect - github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/owenrumney/squealer v1.2.11 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pjbgf/sha1cd v0.3.2 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect @@ -325,7 +342,7 @@ require ( 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/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect - github.com/prometheus/client_golang v1.21.0 // indirect + github.com/prometheus/client_golang v1.22.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect @@ -335,17 +352,21 @@ require ( github.com/rubenv/sql-migrate v1.7.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.7.0 // indirect + github.com/samber/oops v1.15.0 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/sassoftware/relic v7.2.1+incompatible // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect + github.com/shirou/gopsutil/v4 v4.25.1 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/sigstore/cosign/v2 v2.2.4 // indirect - github.com/sigstore/sigstore v1.8.12 // indirect + github.com/sigstore/protobuf-specs v0.4.1 // indirect + github.com/sigstore/sigstore v1.9.1 // indirect github.com/sigstore/timestamp-authority v1.2.2 // indirect github.com/skeema/knownhosts v1.3.1 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.12.0 // indirect + github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect @@ -354,6 +375,7 @@ require ( github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect github.com/tklauser/go-sysconf v0.3.13 // indirect github.com/tklauser/numcpus v0.7.0 // indirect + github.com/tonglil/versioning v0.0.0-20170205083536-8b2a4334bd1d // indirect github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 // indirect github.com/transparency-dev/merkle v0.0.2 // indirect github.com/ulikunitz/xz v0.5.12 // indirect @@ -368,80 +390,57 @@ require ( github.com/yashtewari/glob-intersection v0.2.0 // indirect github.com/yuin/gopher-lua v1.1.1 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect + github.com/zeebo/errs v1.4.0 // indirect go.mongodb.org/mongo-driver v1.14.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/detectors/gcp v1.32.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect go.opentelemetry.io/otel v1.35.0 // indirect go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/sdk v1.34.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect + go.opentelemetry.io/otel/sdk v1.35.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect go.opentelemetry.io/otel/trace v1.35.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/oauth2 v0.26.0 // indirect - golang.org/x/sys v0.31.0 // indirect + golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect + golang.org/x/oauth2 v0.28.0 // indirect + golang.org/x/sys v0.33.0 // indirect golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7 // indirect - golang.org/x/time v0.10.0 // indirect - golang.org/x/tools v0.29.0 // indirect - google.golang.org/api v0.218.0 // indirect - google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect - google.golang.org/grpc v1.70.0 // indirect + golang.org/x/time v0.11.0 // indirect + golang.org/x/tools v0.32.0 // indirect + google.golang.org/api v0.228.0 // indirect + google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect + google.golang.org/grpc v1.72.0 // indirect gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/apiextensions-apiserver v0.32.2 // indirect - k8s.io/apimachinery v0.32.3 // indirect - k8s.io/apiserver v0.32.2 // indirect - k8s.io/cli-runtime v0.32.3 // indirect - k8s.io/client-go v0.32.3 // indirect - k8s.io/component-base v0.32.3 // indirect + k8s.io/apimachinery v0.33.1 // indirect + k8s.io/apiserver v0.32.3 // indirect + k8s.io/cli-runtime v0.33.0 // indirect + k8s.io/client-go v0.33.0 // indirect + k8s.io/component-base v0.33.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect - k8s.io/kubectl v0.32.3 // indirect - modernc.org/libc v1.61.13 // indirect + k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect + k8s.io/kubectl v0.33.0 // indirect + modernc.org/libc v1.62.1 // indirect modernc.org/mathutil v1.7.1 // indirect - modernc.org/memory v1.8.2 // indirect + modernc.org/memory v1.9.1 // indirect mvdan.cc/sh/v3 v3.11.0 // indirect oras.land/oras-go v1.2.5 // indirect sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect - sigs.k8s.io/kustomize/api v0.18.0 // indirect - sigs.k8s.io/kustomize/kyaml v0.18.1 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect -) - -require ( - github.com/alessio/shellescape v1.4.1 // indirect - github.com/aws/aws-sdk-go v1.55.6 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect - github.com/aws/aws-sdk-go-v2/service/ebs v1.22.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.33.18 // indirect - github.com/ebitengine/purego v0.8.2 // indirect - github.com/evanphx/json-patch/v5 v5.6.0 // indirect - github.com/go-viper/mapstructure/v2 v2.2.1 // indirect - github.com/google/go-github/v31 v31.0.0 // indirect - github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2 // indirect - github.com/google/subcommands v1.2.0 // indirect - github.com/knqyf263/labeler v0.0.0-20200423181506-7a6e545148c3 // indirect - github.com/oklog/ulid/v2 v2.1.0 // indirect - github.com/pelletier/go-toml v1.9.5 // indirect - github.com/samber/oops v1.15.0 // indirect - github.com/shirou/gopsutil/v4 v4.25.1 // indirect - github.com/tonglil/versioning v0.0.0-20170205083536-8b2a4334bd1d // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect sigs.k8s.io/kind v0.19.0 // indirect + sigs.k8s.io/kustomize/api v0.19.0 // indirect + sigs.k8s.io/kustomize/kyaml v0.19.0 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect ) tool ( diff --git a/go.sum b/go.sum index 12a5be3aeeb1..a5629e188e9a 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -cel.dev/expr v0.19.0 h1:lXuo+nDhpyJSpWxpPVi5cPUwzKb+dsdOiw6IreM5yt0= -cel.dev/expr v0.19.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +cel.dev/expr v0.20.0 h1:OunBvVCfvpWlt4dN7zg3FM6TDkzOePe1+foGJ9AXeeI= +cel.dev/expr v0.20.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= 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= @@ -38,8 +38,8 @@ cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRY 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 v0.118.3 h1:jsypSnrE/w4mJysioGdMBg4MiW/hHx/sArFpaBWHdME= +cloud.google.com/go v0.118.3/go.mod h1:Lhs3YLnBlwJ4KA6nuObNMZ/fCbOQBPuWKPoE0Wa/9Vc= 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= @@ -101,10 +101,10 @@ cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVo 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.14.0 h1:A5C4dKV/Spdvxcl0ggWwWEzzP7AZMJSEIgrkngwhGYM= -cloud.google.com/go/auth v0.14.0/go.mod h1:CYsoRL1PdiDuqeQpZE0bP2pnPrGqFcOkI0nldEQis+A= -cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M= -cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc= +cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps= +cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8= +cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= 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= @@ -319,8 +319,8 @@ cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGE 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/iam v1.4.1 h1:cFC25Nv+u5BkTR/BT1tXdoF2daiVbZ1RLx2eqfQ9RMM= +cloud.google.com/go/iam v1.4.1/go.mod h1:2vUEJpUG3Q9p2UdsyksaKpDzlwOrnMzS30isdReIcLM= 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= @@ -340,8 +340,8 @@ cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4 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/kms v1.20.4 h1:CJ0hMpOg1ANN9tx/a/GPJ+Uxudy8k6f3fvGFuTHiE5A= -cloud.google.com/go/kms v1.20.4/go.mod h1:gPLsp1r4FblUgBYPOcvI/bUPpdMg2Jm1ZVKU4tQUfcc= +cloud.google.com/go/kms v1.21.1 h1:r1Auo+jlfJSf8B7mUnVw5K0fI7jWyoUy65bV53VjKyk= +cloud.google.com/go/kms v1.21.1/go.mod h1:s0wCyByc9LjTdCjG88toVs70U9W+cc6RKFc8zAqX7nE= 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= @@ -352,13 +352,13 @@ cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6 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/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc= +cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA= 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/longrunning v0.6.5 h1:sD+t8DO8j4HKW4QfouCklg7ZC1qC4uzVZt8iz3uTW+Q= +cloud.google.com/go/longrunning v0.6.5/go.mod h1:Et04XK+0TTLKa5IPYryKf5DkpwImy6TluQ1QTLwlKmY= 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= @@ -382,8 +382,8 @@ cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhI 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/monitoring v1.24.0 h1:csSKiCJ+WVRgNkRzzz3BPoGjFhjPY23ZTcaenToJxMM= +cloud.google.com/go/monitoring v1.24.0/go.mod h1:Bd1PRK5bmQBQNnuGwHBfUamAV1ys9049oEPHnn4pcsc= 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= @@ -547,8 +547,8 @@ cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeL cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= 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/storage v1.50.0 h1:3TbVkzTooBvnZsk7WaAQfOsNrdoM8QHusXA1cpk6QJs= +cloud.google.com/go/storage v1.50.0/go.mod h1:l7XeiD//vx5lfqE3RavfmU9yvk5Pp0Zhcv482poyafY= 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= @@ -568,8 +568,8 @@ cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg 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/trace v1.11.3 h1:c+I4YFjxRQjvAhRmSsmjpASUKq88chOX854ied0K/pE= +cloud.google.com/go/trace v1.11.3/go.mod h1:pt7zCYiDSQjC9Y2oqCsh9jF4GStB/hmjrYLsxRR27q8= 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= @@ -629,26 +629,24 @@ gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zum git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= -github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2 h1:dIScnXFlF784X79oi7MzVT6GWqr/W1uUt0pB5CsDs9M= -github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2/go.mod h1:gCLVsLfv1egrcZu+GoJATN5ts75F2s62ih/457eWzOw= github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d h1:zjqpY4C7H15HjRPEenkS4SAn3Jy2eRRjkjZbGR30TOg= github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d/go.mod h1:XNqJ7hv2kY++g8XEHREpi+JqZo3+0l+CH2egBVN4yqM= github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0 h1:8+4G8JaejP8Xa6W46PzJEwisNgBXMvFcz78N6zG/ARw= github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0/go.mod h1:GgeIE+1be8Ivm7Sh4RgwI42aTtC9qrcj+Y9Y6CjJhJs= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 h1:F0gBpfdPLGsw+nsgk6aqqkZS1jiixa5WwFe3fk/T3Ys= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2/go.mod h1:SqINnQ9lVVdRlyC8cd1lCI0SdX4n2paeABd2K8ggfnE= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.0 h1:j8BorDEigD8UFOSZQiSqAMOOleyQOOQPnUAwV+Ls1gA= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.0/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.0 h1:7rKG7UmnrxX4N53TFhkYqjc+kVUZuw0fL8I3Fh+Ld9E= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.0/go.mod h1:Wjo+24QJVhhl/L7jy6w9yzFF2yDOf3cKECAa8ecf9vE= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.0 h1:eXnN9kaS8TiDwXjoie3hMRLuwdUBUMW9KRgOqB3mCaw= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.0/go.mod h1:XIpam8wumeZ5rVMuhdDQLMfIPDf1WO3IzrCRO3e3e3o= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1 h1:Wgf5rZba3YZqeTNJPtvqZoBu1sBN/L4sry+u2U3Y75w= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1/go.mod h1:xxCBG/f/4Vbmh2XQJBsOmNdxWUY5j/s27jujKPbQf14= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuoEKg+gImo7pvkiQEFAc8ocibADgXeiLAxWhWmkI= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww= 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/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= @@ -673,12 +671,12 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= -github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 h1:H5xDQaE3XowWfhZRUpnfC+rGZMEVoSiji+b+/HFAPU4= -github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= -github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +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.9.2 h1:688QHn2X/5nRezKe2ueIVCt+NRqf7fl3AVQk+vaFcIo= github.com/CycloneDX/cyclonedx-go v0.9.2/go.mod h1:vcK6pKgO1WanCdd61qx4bFnSsDJQ6SbM2ZuMIgq86Jg= @@ -688,14 +686,14 @@ github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/GoogleCloudPlatform/docker-credential-gcr v2.0.5+incompatible h1:juIaKLLVhqzP55d8x4cSVgwyQv76Z55/fRv/UBr2KkQ= github.com/GoogleCloudPlatform/docker-credential-gcr v2.0.5+incompatible/go.mod h1:BB1eHdMLYEFuFdBlRMb0N7YGVdM5s6Pt0njxgvfbGGs= -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/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0 h1:f2Qw/Ehhimh5uO1fayV0QIW7DShEQqhtUfhYc+cBPlw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0/go.mod h1:2bIszWvQRlJVmJLiuLhukLImRjKPcYdzzsx6darK02A= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 h1:o90wcURuxekmXrtxmYWTyNla0+ZEHhud6DI1ZTxd1vI= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0/go.mod h1:6fTWu4m3jocfUZLYF5KsZC1TUfRvEjs7lM4crme/irw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.49.0 h1:jJKWl98inONJAr/IZrdFQUWcwUO95DLY1XMD1ZIut+g= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.49.0/go.mod h1:l2fIqmwB+FKSfvn3bAD/0i+AXAxhIZjTK2svT/mgUXs= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 h1:GYUJLfvd++4DMuMhCFLgLXvFwofIxh/qOwoGuS/LTew= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0/go.mod h1:wRbFgBQUVm1YXrvWKofAEmq9HNJTDphbAaJSSX01KUI= github.com/Intevation/gval v1.3.0 h1:+Ze5sft5MmGbZrHj06NVUbcxCb67l9RaPTLMNr37mjw= github.com/Intevation/gval v1.3.0/go.mod h1:xmGyGpP5be12EL0P12h+dqiYG8qn2j3PJxIgkoOHO5o= github.com/Intevation/jsonpath v0.2.1 h1:rINNQJ0Pts5XTFEG+zamtdL7l9uuE1z0FBA+r55Sw+A= @@ -714,13 +712,13 @@ github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA4 github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= 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.12.9 h1:2zJy5KA+l0loz1HzEGqyNnjd3fyZA31ZBCGKacp6lLg= -github.com/Microsoft/hcsshim v0.12.9/go.mod h1:fJ0gkFAna6ukt0bLdKB8djt4XIJhF/vEPuoIWYVvZ8Y= +github.com/Microsoft/hcsshim v0.13.0 h1:/BcXOiS6Qi7N9XqUcv27vkIuVOkBEcWstd2pMlWSeaA= +github.com/Microsoft/hcsshim v0.13.0/go.mod h1:9KWJ/8DgU+QzYGupX4tzMhRQE8h6w90lH6HAaclpEok= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4= -github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= +github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/ThalesIgnite/crypto11 v1.2.5 h1:1IiIIEqYmBvUYFeMnHqRft4bwf/O36jryEUpY+9ef8E= @@ -796,20 +794,20 @@ github.com/aquasecurity/iamgo v0.0.10 h1:t/HG/MI1eSephztDc+Rzh/YfgEa+NqgYRSfr6pH github.com/aquasecurity/iamgo v0.0.10/go.mod h1:GI9IQJL2a+C+V2+i3vcwnNKuIJXZ+HAfqxZytwy+cPk= github.com/aquasecurity/jfather v0.0.8 h1:tUjPoLGdlkJU0qE7dSzd1MHk2nQFNPR0ZfF+6shaExE= github.com/aquasecurity/jfather v0.0.8/go.mod h1:Ag+L/KuR/f8vn8okUi8Wc1d7u8yOpi2QTaGX10h71oY= -github.com/aquasecurity/table v1.8.0 h1:9ntpSwrUfjrM6/YviArlx/ZBGd6ix8W+MtojQcM7tv0= -github.com/aquasecurity/table v1.8.0/go.mod h1:eqOmvjjB7AhXFgFqpJUEE/ietg7RrMSJZXyTN8E/wZw= +github.com/aquasecurity/table v1.10.0 h1:gPWV28qp9XSlvXdT3ku8yKQoZE6II0vsmegKpW+dB08= +github.com/aquasecurity/table v1.10.0/go.mod h1:eqOmvjjB7AhXFgFqpJUEE/ietg7RrMSJZXyTN8E/wZw= github.com/aquasecurity/testdocker v0.0.0-20240730042311-4642e94c7fc8 h1:b43UVqYjz7qDqK+cVOtF2Lk6CxjytYItP6Pgf3wGsNE= github.com/aquasecurity/testdocker v0.0.0-20240730042311-4642e94c7fc8/go.mod h1:wXA9k3uuaxY3yu7gxrxZDPo/04FEMJtwyecdAlYrEIo= github.com/aquasecurity/tml v0.6.1 h1:y2ZlGSfrhnn7t4ZJ/0rotuH+v5Jgv6BDDO5jB6A9gwo= github.com/aquasecurity/tml v0.6.1/go.mod h1:OnYMWY5lvI9ejU7yH9LCberWaaTBW7hBFsITiIMY2yY= -github.com/aquasecurity/trivy-checks v1.8.1 h1:7df8KhZ0du2WAdGCUNcKYdz74iubAmP89+vaCUmxGbU= -github.com/aquasecurity/trivy-checks v1.8.1/go.mod h1:zc1DGUFDUP/NUEMXlfaMsnVAEEEsygJrcd4SRQ7Mpko= +github.com/aquasecurity/trivy-checks v1.10.0 h1:Q0FWsYy/uwvr/icRSOzNu55yDZ1ME8hZlpglNs62ZfE= +github.com/aquasecurity/trivy-checks v1.10.0/go.mod h1:/b633SOFNp8RjkxSq+FOg4SgxjklUp+BIQEyTWCnN1k= github.com/aquasecurity/trivy-db v0.0.0-20250227071930-8bd8a9b89e2d h1:T16WrTi21YsMLQVhtp1r1hOIYK3x4BjnftpL9cp64Eo= github.com/aquasecurity/trivy-db v0.0.0-20250227071930-8bd8a9b89e2d/go.mod h1:4bTsQPtMBN8v+UfUlE1aQBN1imftefnDafHBF85+aT8= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 h1:JVgBIuIYbwG+ekC5lUHUpGJboPYiCcxiz06RCtz8neI= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48/go.mod h1:Ldya37FLi0e/5Cjq2T5Bty7cFkzUDwTcPeQua+2M8i8= -github.com/aquasecurity/trivy-kubernetes v0.8.1 h1:MMy4bUSAoWJ4rQUANPu6dhao2AC81SnIfi3jxIHBk00= -github.com/aquasecurity/trivy-kubernetes v0.8.1/go.mod h1:FOrdN3IKBcyRoFPtmTFyDx8U3eBch+djCvmmBW4awM4= +github.com/aquasecurity/trivy-kubernetes v0.8.2 h1:8QP8fbUeC5y0kETDpk04XK52hYF6PsJOkd6HrbVnJqo= +github.com/aquasecurity/trivy-kubernetes v0.8.2/go.mod h1:O2SMbA3pnShkOCxBFT04ANYyDEvACbmd5t/XMH4uAFM= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= @@ -817,14 +815,14 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkY github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk= -github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE= +github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM= github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg= -github.com/aws/aws-sdk-go-v2/config v1.29.13 h1:RgdPqWoE8nPpIekpVpDJsBckbqT4Liiaq9f35pbTh1Y= -github.com/aws/aws-sdk-go-v2/config v1.29.13/go.mod h1:NI28qs/IOUIRhsR7GQ/JdexoqRN9tDxkIrYZq0SOF44= -github.com/aws/aws-sdk-go-v2/credentials v1.17.66 h1:aKpEKaTy6n4CEJeYI1MNj97oSDLi4xro3UzQfwf5RWE= -github.com/aws/aws-sdk-go-v2/credentials v1.17.66/go.mod h1:xQ5SusDmHb/fy55wU0QqTy0yNfLqxzec59YcsRZB+rI= +github.com/aws/aws-sdk-go-v2/config v1.29.14 h1:f+eEi/2cKCg9pqKBoAIwRGzVb70MRKqWX4dg1BDcSJM= +github.com/aws/aws-sdk-go-v2/config v1.29.14/go.mod h1:wVPHWcIFv3WO89w0rE10gzf17ZYy+UVS1Geq8Iei34g= +github.com/aws/aws-sdk-go-v2/credentials v1.17.67 h1:9KxtdcIA/5xPNQyZRgUSpYOE6j9Bc4+D7nZua0KGYOM= +github.com/aws/aws-sdk-go-v2/credentials v1.17.67/go.mod h1:p3C44m+cfnbv763s52gCqrjaqyPikj9Sg47kUVaNZQQ= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 h1:x793wxmUWVDhshP8WW2mlnXuFrO4cOd3HLBroh1paFw= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30/go.mod h1:Jpne2tDnYiFascUEs2AWHJL9Yp7A5ZVy3TNyxaAjD6M= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 h1:ZK5jHhnrioRkUNOc+hOgQKlUL5JeC3S6JgLxtQ+Rm0Q= @@ -835,26 +833,26 @@ github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= github.com/aws/aws-sdk-go-v2/service/ebs v1.22.1 h1:SeDJWG4pmye+/aO6k+zt9clPTUy1MXqUmkW8rbAddQg= github.com/aws/aws-sdk-go-v2/service/ebs v1.22.1/go.mod h1:wRzaW0v9GGQS0h//wpsVDw3Hah5gs5UP+NxoyGeZIGM= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.211.2 h1:KMoQ43HysbPqs1vufMn9h2UcUyc2WCMaKxYhExKJZuo= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.211.2/go.mod h1:ouvGEfHbLaIlWwpDpOVWPWR+YwO0HDv3vm5tYLq8ImY= -github.com/aws/aws-sdk-go-v2/service/ecr v1.43.2 h1:G6Rj8A8zgukXWSSB8evVE8az2S3Ci/Q3CpSMxJsK4G0= -github.com/aws/aws-sdk-go-v2/service/ecr v1.43.2/go.mod h1:iQ1skgw1XRK+6Lgkb0I9ODatAP72WoTILh0zXQ5DtbU= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.218.0 h1:QPYsTfcPpPhkF+37pxLcl3xbQz2SRxsShQNB6VCkvLo= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.218.0/go.mod h1:ouvGEfHbLaIlWwpDpOVWPWR+YwO0HDv3vm5tYLq8ImY= +github.com/aws/aws-sdk-go-v2/service/ecr v1.44.0 h1:E+UTVTDH6XTSjqxHWRuY8nB6s+05UllneWxnycplHFk= +github.com/aws/aws-sdk-go-v2/service/ecr v1.44.0/go.mod h1:iQ1skgw1XRK+6Lgkb0I9ODatAP72WoTILh0zXQ5DtbU= github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.18.2 h1:PpbXaecV3sLAS6rjQiaKw4/jyq3Z8gNzmoJupHAoBp0= github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.18.2/go.mod h1:fUHpGXr4DrXkEDpGAjClPsviWf+Bszeb0daKE0blxv8= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY= -github.com/aws/aws-sdk-go-v2/service/kms v1.37.8 h1:KbLZjYqhQ9hyB4HwXiheiflTlYQa0+Fz0Ms/rh5f3mk= -github.com/aws/aws-sdk-go-v2/service/kms v1.37.8/go.mod h1:ANs9kBhK4Ghj9z1W+bsr3WsNaPF71qkgd6eE6Ekol/Y= -github.com/aws/aws-sdk-go-v2/service/s3 v1.79.1 h1:2Ku1xwAohSSXHR1tpAnyVDSQSxoDMA+/NZBytW+f4qg= -github.com/aws/aws-sdk-go-v2/service/s3 v1.79.1/go.mod h1:U5SNqwhXB3Xe6F47kXvWihPl/ilGaEDe8HD/50Z9wxc= +github.com/aws/aws-sdk-go-v2/service/kms v1.38.1 h1:tecq7+mAav5byF+Mr+iONJnCBf4B4gon8RSp4BrweSc= +github.com/aws/aws-sdk-go-v2/service/kms v1.38.1/go.mod h1:cQn6tAF77Di6m4huxovNM7NVAozWTZLsDRp9t8Z/WYk= +github.com/aws/aws-sdk-go-v2/service/s3 v1.79.3 h1:BRXS0U76Z8wfF+bnkilA2QwpIch6URlm++yPUt9QPmQ= +github.com/aws/aws-sdk-go-v2/service/s3 v1.79.3/go.mod h1:bNXKFFyaiVvWuR6O16h/I1724+aXe/tAkA9/QS01t5k= github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 h1:1Gw+9ajCV1jogloEv1RRnvfRFia2cL6c9cuKV2Ps+G8= github.com/aws/aws-sdk-go-v2/service/sso v1.25.3/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 h1:hXmVKytPfTy5axZ+fYbR5d0cFmC3JvwLm5kM83luako= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.18 h1:xz7WvTMfSStb9Y8NpCT82FXLNC3QasqBfuAFHY4Pk5g= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.18/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 h1:1XuUZ8mYJw9B6lzAkXhqHlJd/XvaX32evhproijJEZY= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.19/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4= github.com/aws/smithy-go v1.22.3 h1:Z//5NuZCSW6R4PhQ93hShNbyBbn8BWCmCVCt+Q8Io5k= github.com/aws/smithy-go v1.22.3/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8 h1:SoFYaT9UyGkR0+nogNyD/Lj+bsixB+SNuAS4ABlEs6M= @@ -899,7 +897,6 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3 github.com/cenkalti/backoff/v4 v4.3.0/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= @@ -919,8 +916,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME= github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= -github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +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= @@ -934,22 +931,22 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH 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/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk= +github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL2kskAlV9ckgEsNQXscjIaLiOYiZ75d4e94E6dcQ= github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w= -github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0= -github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0= +github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo= +github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins= 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/containerd/v2 v2.0.4 h1:+r7yJMwhTfMm3CDyiBjMBQO8a9CTBxL2Bg/JtqtIwB8= -github.com/containerd/containerd/v2 v2.0.4/go.mod h1:5j9QUUaV/cy9ZeAx4S+8n9ffpf+iYnEj4jiExgcbuLY= +github.com/containerd/containerd/api v1.9.0 h1:HZ/licowTRazus+wt9fM6r/9BQO7S0vD5lMcWspGIg0= +github.com/containerd/containerd/api v1.9.0/go.mod h1:GhghKFmTR3hNtyznBoQ0EMWr9ju5AqHjcZPsSpTKutI= +github.com/containerd/containerd/v2 v2.1.1 h1:znnkm7Ajz8lg8BcIPMhc/9yjBRN3B+OkNKqKisKfwwM= +github.com/containerd/containerd/v2 v2.1.1/go.mod h1:zIfkQj4RIodclYQkX7GSSswSwgP8d/XxDOtOAoSDIGU= github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= @@ -986,16 +983,16 @@ github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46 h github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw= 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/danieljoos/wincred v1.2.1 h1:dl9cBrupW8+r5250DYkYxocLeZ1Y4vB1kxgtjxw8GQs= -github.com/danieljoos/wincred v1.2.1/go.mod h1:uGaFL9fDn3OLTvzCGulzE+SzjEe5NGlh5FdCcyfPwps= +github.com/danieljoos/wincred v1.2.2 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0= +github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8= 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= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgraph-io/badger/v4 v4.5.1 h1:7DCIXrQjo1LKmM96YD+hLVJ2EEsyyoWxJfpdd56HLps= -github.com/dgraph-io/badger/v4 v4.5.1/go.mod h1:qn3Be0j3TfV4kPbVoK0arXCD1/nr1ftth6sbL5jxdoA= -github.com/dgraph-io/ristretto/v2 v2.1.0 h1:59LjpOJLNDULHh8MC4UaegN52lC4JnO2dITsie/Pa8I= -github.com/dgraph-io/ristretto/v2 v2.1.0/go.mod h1:uejeqfYXpUomfse0+lO+13ATz4TypQYLJZzBSAemuB4= +github.com/dgraph-io/badger/v4 v4.7.0 h1:Q+J8HApYAY7UMpL8d9owqiB+odzEc0zn/aqOD9jhc6Y= +github.com/dgraph-io/badger/v4 v4.7.0/go.mod h1:He7TzG3YBy3j4f5baj5B7Zl2XyfNe5bl4Udl0aPemVA= +github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM= +github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo= @@ -1013,14 +1010,14 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= -github.com/docker/cli v28.0.4+incompatible h1:pBJSJeNd9QeIWPjRcV91RVJihd/TXB77q1ef64XEu4A= -github.com/docker/cli v28.0.4+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +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 v28.0.4+incompatible h1:JNNkBctYKurkw6FrHfKqY0nKIDf5nrbxjVBtS+cdcok= -github.com/docker/docker v28.0.4+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/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.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= +github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= 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= @@ -1059,14 +1056,18 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go. 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/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= +github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= +github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= +github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 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/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= +github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= @@ -1090,8 +1091,8 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -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/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -1114,8 +1115,8 @@ github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UN 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.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi60= -github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k= +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= @@ -1144,8 +1145,8 @@ github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU= github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo= -github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w= -github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE= +github.com/go-openapi/errors v0.22.1 h1:kslMRRnK7NCb/CvR1q1VWuEQCEIsBGn5GgKD9e+HYhU= +github.com/go-openapi/errors v0.22.1/go.mod h1:+n/5UdIqdVnLIJ6Q9Se8HNGUXYaY6CN8ImWzfi/Gzp0= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= @@ -1158,8 +1159,8 @@ github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9Z github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c= github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4= -github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= -github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= +github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58= github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= @@ -1172,8 +1173,9 @@ github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7 github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= -github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/go-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtSb/iI= +github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= @@ -1189,8 +1191,8 @@ github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG github.com/goccy/go-yaml v1.8.1/go.mod h1:wS4gNoLalDSJxo/SpngzPQ2BN4uuZVLCmbM4S3vd4+Y= github.com/goccy/go-yaml v1.15.23 h1:WS0GAX1uNPDLUvLkNU2vXq6oTnsmfVFocjQ/4qA48qo= github.com/goccy/go-yaml v1.15.23/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= -github.com/gocsaf/csaf/v3 v3.1.1 h1:g8kmqIwf8zqcMdQgaQT5UcPcyzvXxJyleT/T8Rt2OmQ= -github.com/gocsaf/csaf/v3 v3.1.1/go.mod h1:EpUCrQg69i+Y66MphmQvVbcj333GFLjXOYHg1zoXVso= +github.com/gocsaf/csaf/v3 v3.2.0 h1:LF9j1ou4Cm5MT4+oHbk16Ae0hSmCztwPJqciTapVNIU= +github.com/gocsaf/csaf/v3 v3.2.0/go.mod h1:EpUCrQg69i+Y66MphmQvVbcj333GFLjXOYHg1zoXVso= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= @@ -1252,15 +1254,15 @@ github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= 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/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= -github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/certificate-transparency-go v1.1.8 h1:LGYKkgZF7satzgTak9R4yzfJXEeYVAjV6/EAEJOf1to= github.com/google/certificate-transparency-go v1.1.8/go.mod h1:bV/o8r0TBKRf1X//iiiSgWrvII4d7/8OiA+3vG26gI8= github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/flatbuffers v24.12.23+incompatible h1:ubBKR94NR4pXUCY/MUsRVzd9umNW7ht7EG9hHfS9FX8= -github.com/google/flatbuffers v24.12.23+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU= -github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M= +github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= +github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= +github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= 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= @@ -1319,8 +1321,8 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLe 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-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= -github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +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.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= @@ -1345,8 +1347,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= 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/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= +github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= 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= @@ -1366,8 +1368,8 @@ github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= @@ -1376,8 +1378,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4 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.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -1412,18 +1414,18 @@ github.com/hashicorp/golang-lru v0.6.0 h1:uL2shRDx7RTrOrTCUZEGP/wJUFiUI8QT6E7z5o github.com/hashicorp/golang-lru v0.6.0/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 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/hc-install v0.9.1 h1:gkqTfE3vVbafGQo6VZXcy2v5yoz2bE0+nhZXruCuODQ= -github.com/hashicorp/hc-install v0.9.1/go.mod h1:pWWvN/IrfeBK4XPeXXYkL6EjMufHkCK5DvwxeLKuBf0= +github.com/hashicorp/hc-install v0.9.2 h1:v80EtNX4fCVHqzL9Lg/2xkp62bbvQMnvPQ0G+OmtO24= +github.com/hashicorp/hc-install v0.9.2/go.mod h1:XUqBQNnuT4RsxoxiM9ZaUk0NX8hi2h+Lb6/c0OZnC/I= github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM= github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= 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/terraform-exec v0.22.0 h1:G5+4Sz6jYZfRYUCg6eQgDsqTzkNXV+fP8l+uRmZHj64= -github.com/hashicorp/terraform-exec v0.22.0/go.mod h1:bjVbsncaeh8jVdhttWYZuBGj21FcYw6Ia/XfHcNO7lQ= +github.com/hashicorp/terraform-exec v0.23.0 h1:MUiBM1s0CNlRFsCLJuM5wXZrzA3MnPYEsiXmzATMW/I= +github.com/hashicorp/terraform-exec v0.23.0/go.mod h1:mA+qnx1R8eePycfwKkCRk3Wy65mwInvlpAeOwmA7vlY= github.com/hashicorp/terraform-json v0.24.0 h1:rUiyF+x1kYawXeRth6fKFm/MdfBS6+lW4NbeATsYz8Q= github.com/hashicorp/terraform-json v0.24.0/go.mod h1:Nfj5ubo9xbu9uiAoZVBsNOjvNKB66Oyrvtit74kC7ow= -github.com/hashicorp/vault/api v1.15.0 h1:O24FYQCWwhwKnF7CuSqP30S51rTV7vz1iACXE/pj5DA= -github.com/hashicorp/vault/api v1.15.0/go.mod h1:+5YTO09JGn0u+b6ySD/LLVf8WkJCPLAL2Vkmrn2+CM8= +github.com/hashicorp/vault/api v1.16.0 h1:nbEYGJiAPGzT9U4oWgaaB0g+Rj8E59QuHKyA5LhwQN4= +github.com/hashicorp/vault/api v1.16.0/go.mod h1:KhuUhzOD8lDSk29AtzNjgAu2kxRA9jL9NAbkFlqvkBA= github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb28sjiS3i7tcKCkflWFEkHfuAgM= github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -1467,16 +1469,16 @@ github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E 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/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= -github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw= +github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= +github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= 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.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +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/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f h1:GvCU5GXhHq+7LeOzx/haG7HSIZokl3/0GkoUFzsRJjg= @@ -1527,10 +1529,10 @@ github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuz github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= -github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= -github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +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.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/masahiro331/go-disk v0.0.0-20240625071113-56c933208fee h1:cgm8mE25x5XXX2oyvJDlyJ72K+rDu/4ZCYce2worNb8= github.com/masahiro331/go-disk v0.0.0-20240625071113-56c933208fee/go.mod h1:rojbW5tVhH1cuVYFKZS+QX+VGXK45JVsRO+jW92kkKM= github.com/masahiro331/go-ebs-file v0.0.0-20240917043618-e6d2bea5c32e h1:nCgF1JEYIS8KNuJtIeUrmjjhktIMKWNmASZqwK2ynu0= @@ -1586,24 +1588,28 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua 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/buildkit v0.18.2 h1:l86uBvxh4ntNoUUg3Y0eGTbKg1PbUh6tawJ4Xt75SpQ= -github.com/moby/buildkit v0.18.2/go.mod h1:vCR5CX8NGsPTthTg681+9kdmfvkvqJBXEv71GZe5msU= +github.com/moby/buildkit v0.21.1 h1:wTjVLfirh7skZt9piaIlNo8WdiPjza1CDl2EArDV9bA= +github.com/moby/buildkit v0.21.1/go.mod h1:mBq0D44uCyz2PdX8T/qym5LBbkBO3GGv0wqgX9ABYYw= 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/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= +github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= 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/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +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.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0= github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8= -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/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= +github.com/moby/sys/user v0.4.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= @@ -1655,16 +1661,16 @@ github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAl github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= -github.com/open-policy-agent/opa v1.2.0 h1:88NDVCM0of1eO6Z4AFeL3utTEtMuwloFmWWU7dRV1z0= -github.com/open-policy-agent/opa v1.2.0/go.mod h1:30euUmOvuBoebRCcJ7DMF42bRBOPznvt0ACUMYDUGVY= +github.com/open-policy-agent/opa v1.4.2 h1:ag4upP7zMsa4WE2p1pwAFeG4Pn3mNwfAx9DLhhJfbjU= +github.com/open-policy-agent/opa v1.4.2/go.mod h1:DNzZPKqKh4U0n0ANxcCVlw8lCSv2c+h5G/3QvSYdWZ8= 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.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= -github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk= -github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/selinux v1.11.1 h1:nHFvthhM0qY8/m+vfhJylliSshm8G1jJ2jDMcgULaH8= -github.com/opencontainers/selinux v1.11.1/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= +github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww= +github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplUkdTrmPb8= +github.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/openvex/discovery v0.1.1-0.20240802171711-7c54efc57553 h1:c4u0GIH0w2Q57Pm2Oldrq6EiHFnLCCnRs98A+ggj/YQ= @@ -1684,8 +1690,8 @@ github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtP 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.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= @@ -1717,8 +1723,8 @@ github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjz github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA= -github.com/prometheus/client_golang v1.21.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 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= @@ -1741,8 +1747,8 @@ github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E= -github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= +github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI= +github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= 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= @@ -1768,8 +1774,8 @@ github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkB github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= -github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= -github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= +github.com/samber/lo v1.50.0 h1:XrG0xOeHs+4FQ8gJR97zDz5uOFMW7OwFWiFVzqopKgY= +github.com/samber/lo v1.50.0/go.mod h1:RjZyNk6WSnUFRKK6EyOhsRJMqft3G+pg7dCWHQCWvsc= github.com/samber/oops v1.15.0 h1:/mF33KAqA2TugU6y/tomFpK6G6mJB7g0aqRyHkaSIeg= github.com/samber/oops v1.15.0/go.mod h1:9LpLZkpjojEt/of7EpG5o65i/Lp23ddDvGhg2L871Ow= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= @@ -1799,18 +1805,20 @@ github.com/sigstore/cosign/v2 v2.2.4 h1:iY4vtEacmu2hkNj1Fh+8EBqBwKs2DHM27/lbNWDF github.com/sigstore/cosign/v2 v2.2.4/go.mod h1:JZlRD2uaEjVAvZ1XJ3QkkZJhTqSDVtLaet+C/TMR81Y= github.com/sigstore/fulcio v1.4.5 h1:WWNnrOknD0DbruuZWCbN+86WRROpEl3Xts+WT2Ek1yc= github.com/sigstore/fulcio v1.4.5/go.mod h1:oz3Qwlma8dWcSS/IENR/6SjbW4ipN0cxpRVfgdsjMU8= -github.com/sigstore/rekor v1.3.9 h1:sUjRpKVh/hhgqGMs0t+TubgYsksArZ6poLEC3MsGAzU= -github.com/sigstore/rekor v1.3.9/go.mod h1:xThNUhm6eNEmkJ/SiU/FVU7pLY2f380fSDZFsdDWlcM= -github.com/sigstore/sigstore v1.8.12 h1:S8xMVZbE2z9ZBuQUEG737pxdLjnbOIcFi5v9UFfkJFc= -github.com/sigstore/sigstore v1.8.12/go.mod h1:+PYQAa8rfw0QdPpBcT+Gl3egKD9c+TUgAlF12H3Nmjo= -github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.12 h1:EC3UmIaa7nV9sCgSpVevmvgvTYTkMqyrRbj5ojPp7tE= -github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.12/go.mod h1:aw60vs3crnQdM/DYH+yF2P0MVKtItwAX34nuaMrY7Lk= -github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.12 h1:FPpliDTywSy0woLHMAdmTSZ5IS/lVBZ0dY0I+2HmnSY= -github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.12/go.mod h1:NkPiz4XA0JcBSXzJUrjMj7Xi7oSTew1Ip3Zmt56mHlw= -github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.12 h1:kweBChR6M9FEvmxN3BMEcl7SNnwxTwKF7THYFKLOE5U= -github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.12/go.mod h1:6+d+A6oYt1W5OgtzgEVb21V7tAZ/C2Ihtzc5MNJbayY= -github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.12 h1:jvY1B9bjP+tKzdKDyuq5K7O19CG2IKzGJNTy5tuL2Gs= -github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.12/go.mod h1:2uEeOb8xE2RC6OvzxKux1wkS39Zv8gA27z92m49xUTc= +github.com/sigstore/protobuf-specs v0.4.1 h1:5SsMqZbdkcO/DNHudaxuCUEjj6x29tS2Xby1BxGU7Zc= +github.com/sigstore/protobuf-specs v0.4.1/go.mod h1:+gXR+38nIa2oEupqDdzg4qSBT0Os+sP7oYv6alWewWc= +github.com/sigstore/rekor v1.3.10 h1:/mSvRo4MZ/59ECIlARhyykAlQlkmeAQpvBPlmJtZOCU= +github.com/sigstore/rekor v1.3.10/go.mod h1:JvryKJ40O0XA48MdzYUPu0y4fyvqt0C4iSY7ri9iu3A= +github.com/sigstore/sigstore v1.9.1 h1:bNMsfFATsMPaagcf+uppLk4C9rQZ2dh5ysmCxQBYWaw= +github.com/sigstore/sigstore v1.9.1/go.mod h1:zUoATYzR1J3rLNp3jmp4fzIJtWdhC3ZM6MnpcBtnsE4= +github.com/sigstore/sigstore/pkg/signature/kms/aws v1.9.1 h1:/YcNq687WnXpIRXl04nLfJX741G4iW+w+7Nem2Zy0f4= +github.com/sigstore/sigstore/pkg/signature/kms/aws v1.9.1/go.mod h1:ApL9RpKsi7gkSYN0bMNdm/3jZ9EefxMmfYHfUmq2ZYM= +github.com/sigstore/sigstore/pkg/signature/kms/azure v1.9.1 h1:FnusXyTIInnwfIOzzl5PFilRm1I97dxMSOcCkZBu9Kc= +github.com/sigstore/sigstore/pkg/signature/kms/azure v1.9.1/go.mod h1:d5m5LOa/69a+t2YC9pDPwS1n2i/PhqB4cUKbpVDlKKE= +github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.9.1 h1:LFiYK1DEWQ6Hf/nroFzBMM+s5rVSjVL45Alpb5Ctl5A= +github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.9.1/go.mod h1:GFyFmDsE2wDuIHZD+4+JErGpA0S4zJsKNz5l2JVJd8s= +github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.9.1 h1:sIW6xe4yU5eIMH8fve2C78d+r29KmHnIb+7po+80bsY= +github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.9.1/go.mod h1:3pNf99GnK9eu3XUa5ebHzgEQSVYf9hqAoPFwbwD6O6M= github.com/sigstore/timestamp-authority v1.2.2 h1:X4qyutnCQqJ0apMewFyx+3t7Tws00JQ/JonBiu3QvLE= github.com/sigstore/timestamp-authority v1.2.2/go.mod h1:nEah4Eq4wpliDjlY342rXclGSO7Kb9hoRrl9tqLW13A= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -1834,8 +1842,8 @@ github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= -github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= -github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.8.0 h1:gEN9K4b8Xws4EX0+a0reLmhq8moKn7ntRlQYgjPeCDk= +github.com/spf13/cast v1.8.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v0.0.0-20170130214531-35136c09d8da/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= @@ -1844,10 +1852,10 @@ github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzu 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.20.0 h1:zrxIyR3RQIOsarIrgL8+sAvALXul9jeEPa06Y0Ph6vY= -github.com/spf13/viper v1.20.0/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= -github.com/spiffe/go-spiffe/v2 v2.2.0 h1:9Vf06UsvsDbLYK/zJ4sYsIsHmMFknUD+feA7IYoWMQY= -github.com/spiffe/go-spiffe/v2 v2.2.0/go.mod h1:Urzb779b3+IwDJD2ZbN8fVl3Aa8G4N/PiUe6iXC0XxU= +github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= +github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= +github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= +github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= 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= @@ -1878,10 +1886,12 @@ github.com/tchap/go-patricia/v2 v2.3.2 h1:xTHFutuitO2zqKAQ5rCROYgUb7Or/+IC3fts9/ github.com/tchap/go-patricia/v2 v2.3.2/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= 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/testcontainers/testcontainers-go v0.36.0 h1:YpffyLuHtdp5EUsI5mT4sRw8GZhO/5ozyDT1xWGXt00= -github.com/testcontainers/testcontainers-go v0.36.0/go.mod h1:yk73GVJ0KUZIHUtFna6MO7QS144qYpoY8lEEtU9Hed0= -github.com/testcontainers/testcontainers-go/modules/localstack v0.36.0 h1:zVwbe46NYg2vtC26aF0ndClK5S9J7TgAliQbTLyHm+0= -github.com/testcontainers/testcontainers-go/modules/localstack v0.36.0/go.mod h1:rxyzj5nX/OUn7QK5PVxKYHJg1eeNtNzWMX2hSbNNJk0= +github.com/testcontainers/testcontainers-go v0.37.0 h1:L2Qc0vkTw2EHWQ08djon0D2uw7Z/PtHS/QzZZ5Ra/hg= +github.com/testcontainers/testcontainers-go v0.37.0/go.mod h1:QPzbxZhQ6Bclip9igjLFj6z0hs01bU8lrl2dHQmgFGM= +github.com/testcontainers/testcontainers-go/modules/k3s v0.37.0 h1:lqwknybf56hBLi2YsKs01VLSUK8qXnIcG1FM/6/L5qI= +github.com/testcontainers/testcontainers-go/modules/k3s v0.37.0/go.mod h1:RIsXAxAUiaDNfsGsYcZB1TyDn2mqy52lO0HrGFts8cs= +github.com/testcontainers/testcontainers-go/modules/localstack v0.37.0 h1:nPuxUYseqS0eYJg7KDJd95PhoMhdpTnSNtkDLwWFngo= +github.com/testcontainers/testcontainers-go/modules/localstack v0.37.0/go.mod h1:Mw+N4qqJ5iWbg45yWsdLzICfeCEwvYNudfAHHFqCU8Q= github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I= github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM= github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg= @@ -1962,15 +1972,15 @@ github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms= github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= -github.com/zclconf/go-cty v1.16.2 h1:LAJSwc3v81IRBZyUVQDUdZ7hs3SYs9jv0eZJDWHD/70= -github.com/zclconf/go-cty v1.16.2/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty v1.16.3 h1:osr++gw2T61A8KVYHoQiFbFd1Lh3JOCXc/jFLJXKTxk= +github.com/zclconf/go-cty v1.16.3/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/zclconf/go-cty-yaml v1.1.0 h1:nP+jp0qPHv2IhUVqmQSzjvqAWcObN0KBkUl2rWBdig0= github.com/zclconf/go-cty-yaml v1.1.0/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= -github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs= -github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= +github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= +github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk= @@ -1988,28 +1998,28 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= 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.32.0 h1:P78qWqkLSShicHmAzfECaTgvslqHxblNE9j62Ws1NK8= -go.opentelemetry.io/contrib/detectors/gcp v1.32.0/go.mod h1:TVqo0Sda4Cv8gCIixd7LuLwW4EylumVWfhjZJjDD4DU= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0 h1:yMkBS9yViCc7U7yeLzJPM2XizlfdVvBRSmsQDWu6qc0= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0/go.mod h1:n8MR6/liuGB5EmTETUBeU5ZgqMOlqKRxUaqPQBOANZ8= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= +go.opentelemetry.io/contrib/detectors/gcp v1.34.0 h1:JRxssobiPg23otYU5SbWtQC//snGVIM3Tx6QRzlQBao= +go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= -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/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk= 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.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= -go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= +go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= +go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= +go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= +go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= @@ -2017,8 +2027,8 @@ go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= -go.step.sm/crypto v0.57.0 h1:YjoRQDaJYAxHLVwjst0Bl0xcnoKzVwuHCJtEo2VSHYU= -go.step.sm/crypto v0.57.0/go.mod h1:+Lwp5gOVPaTa3H/Ul/TzGbxQPXZZcKIUGMS0lG6n9Go= +go.step.sm/crypto v0.60.0 h1:UgSw8DFG5xUOGB3GUID17UA32G4j1iNQ4qoMhBmsVFw= +go.step.sm/crypto v0.60.0/go.mod h1:Ep83Lv818L4gV0vhFTdPWRKnL6/5fRMpi8SaoP5ArSw= 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.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -2044,8 +2054,8 @@ golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1m 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.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +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= @@ -2061,8 +2071,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 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-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= -golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= -golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= +golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= +golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= 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= @@ -2180,8 +2190,8 @@ golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= 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.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +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= @@ -2211,8 +2221,8 @@ 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.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= -golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= +golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= 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= @@ -2233,8 +2243,8 @@ 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.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +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-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -2336,8 +2346,8 @@ golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +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/telemetry v0.0.0-20240522233618-39ace7a40ae7 h1:FemxDzfMUcK2f3YY4H+05K9CDzbSVr2+q/JKN45pey0= golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= @@ -2355,8 +2365,8 @@ golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= 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.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +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= @@ -2377,16 +2387,16 @@ 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.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +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.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.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= -golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +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= @@ -2454,8 +2464,8 @@ 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.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= -golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= +golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= +golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= golang.org/x/vuln v1.1.4 h1:Ju8QsuyhX3Hk8ma3CesTbO8vfJD9EvUBgHvkxHBzj0I= golang.org/x/vuln v1.1.4/go.mod h1:F+45wmU18ym/ca5PLTPLsSzr2KppzswxPP603ldA67s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -2533,8 +2543,8 @@ google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/ 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.218.0 h1:x6JCjEWeZ9PFCRe9z0FBrNwj7pB7DOAqT35N+IPnAUA= -google.golang.org/api v0.218.0/go.mod h1:5VGHBAkxrA/8EFjLVEYmMUJ8/8+gWWQ3s4cFH0FxG2M= +google.golang.org/api v0.228.0 h1:X2DJ/uoWGnY5obVjewbp8icSL5U4FzuCfy9OjbLSnLs= +google.golang.org/api v0.228.0/go.mod h1:wNvRS1Pbe8r4+IfBIniV8fwCpGwTrYa+kMUDiC5z5a4= 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= @@ -2674,12 +2684,12 @@ google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOl 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-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= -google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb h1:ITgPrl429bc6+2ZraNSzMDk3I95nmQln2fuPstKwFDE= +google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE= +google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= +google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= 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= @@ -2721,8 +2731,8 @@ google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5v 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.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= -google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= +google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM= +google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= 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= @@ -2742,8 +2752,8 @@ google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw 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.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 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= @@ -2781,10 +2791,10 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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= -gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= -gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -helm.sh/helm/v3 v3.17.2 h1:agYQ5ew2jq5vdx2K7q5W44KyKQrnSubUMCQsjkiv3/o= -helm.sh/helm/v3 v3.17.2/go.mod h1:+uJKMH/UiMzZQOALR3XUf3BLIoczI2RKKD6bMhPh4G8= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= +helm.sh/helm/v3 v3.17.3 h1:3n5rW3D0ArjFl0p4/oWO8IbY/HKaNNwJtOQFdH2AZHg= +helm.sh/helm/v3 v3.17.3/go.mod h1:+uJKMH/UiMzZQOALR3XUf3BLIoczI2RKKD6bMhPh4G8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -2793,26 +2803,26 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt 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= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= -k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= -k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= +k8s.io/api v0.33.1 h1:tA6Cf3bHnLIrUK4IqEgb2v++/GYUtqiu9sRVk3iBXyw= +k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw= k8s.io/apiextensions-apiserver v0.32.2 h1:2YMk285jWMk2188V2AERy5yDwBYrjgWYggscghPCvV4= k8s.io/apiextensions-apiserver v0.32.2/go.mod h1:GPwf8sph7YlJT3H6aKUWtd0E+oyShk/YHWQHf/OOgCA= -k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= -k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= -k8s.io/apiserver v0.32.2 h1:WzyxAu4mvLkQxwD9hGa4ZfExo3yZZaYzoYvvVDlM6vw= -k8s.io/apiserver v0.32.2/go.mod h1:PEwREHiHNU2oFdte7BjzA1ZyjWjuckORLIK/wLV5goM= -k8s.io/cli-runtime v0.32.3 h1:khLF2ivU2T6Q77H97atx3REY9tXiA3OLOjWJxUrdvss= -k8s.io/cli-runtime v0.32.3/go.mod h1:vZT6dZq7mZAca53rwUfdFSZjdtLyfF61mkf/8q+Xjak= -k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU= -k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY= -k8s.io/component-base v0.32.3 h1:98WJvvMs3QZ2LYHBzvltFSeJjEx7t5+8s71P7M74u8k= -k8s.io/component-base v0.32.3/go.mod h1:LWi9cR+yPAv7cu2X9rZanTiFKB2kHA+JjmhkKjCZRpI= +k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4= +k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/apiserver v0.32.3 h1:kOw2KBuHOA+wetX1MkmrxgBr648ksz653j26ESuWNY8= +k8s.io/apiserver v0.32.3/go.mod h1:q1x9B8E/WzShF49wh3ADOh6muSfpmFL0I2t+TG0Zdgc= +k8s.io/cli-runtime v0.33.0 h1:Lbl/pq/1o8BaIuyn+aVLdEPHVN665tBAXUePs8wjX7c= +k8s.io/cli-runtime v0.33.0/go.mod h1:QcA+r43HeUM9jXFJx7A+yiTPfCooau/iCcP1wQh4NFw= +k8s.io/client-go v0.33.0 h1:UASR0sAYVUzs2kYuKn/ZakZlcs2bEHaizrrHUZg0G98= +k8s.io/client-go v0.33.0/go.mod h1:kGkd+l/gNGg8GYWAPr0xF1rRKvVWvzh9vmZAMXtaKOg= +k8s.io/component-base v0.33.0 h1:Ot4PyJI+0JAD9covDhwLp9UNkUja209OzsJ4FzScBNk= +k8s.io/component-base v0.33.0/go.mod h1:aXYZLbw3kihdkOPMDhWbjGCO6sg+luw554KP51t8qCU= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= -k8s.io/kubectl v0.32.3 h1:VMi584rbboso+yjfv0d8uBHwwxbC438LKq+dXd5tOAI= -k8s.io/kubectl v0.32.3/go.mod h1:6Euv2aso5GKzo/UVMacV6C7miuyevpfI91SvBvV9Zdg= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= +k8s.io/kubectl v0.33.0 h1:HiRb1yqibBSCqic4pRZP+viiOBAnIdwYDpzUFejs07g= +k8s.io/kubectl v0.33.0/go.mod h1:gAlGBuS1Jq1fYZ9AjGWbI/5Vk3M/VW2DK4g10Fpyn/0= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= @@ -2820,21 +2830,21 @@ lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl 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.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0= -modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +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.23.16 h1:Z2N+kk38b7SfySC1ZkpGLN2vthNJP1+ZzGZIlH7uBxo= -modernc.org/ccgo/v4 v4.23.16/go.mod h1:nNma8goMTY7aQZQNTyN9AIoJfxav4nvTnvKThAeMDdo= +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.3 h1:aJVhcqAte49LF+mGveZ5KPlsp4tdGdAOT4sipJXADjw= -modernc.org/gc/v2 v2.6.3/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +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= @@ -2843,8 +2853,8 @@ 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.61.13 h1:3LRd6ZO1ezsFiX1y+bHd1ipyEHIJKvuprv0sLTBwLW8= -modernc.org/libc v1.61.13/go.mod h1:8F/uJWL/3nNil0Lgt1Dpz+GgkApWh04N3el3hxJcA6E= +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= @@ -2853,8 +2863,8 @@ modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJ 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.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI= -modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU= +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= @@ -2862,8 +2872,8 @@ 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.36.1 h1:bDa8BJUH4lg6EGkLbahKe/8QqoF8p9gArSc6fTqYhyQ= -modernc.org/sqlite v1.36.1/go.mod h1:7MPwH7Z6bREicF9ZVUR78P1IKuxfZ8mRIDHD0iD+8TU= +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= @@ -2885,14 +2895,17 @@ sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1 sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= sigs.k8s.io/kind v0.19.0 h1:ZSUh6/kpab6fiowT6EqL4k8xSbedI2NWxyuUOtoPFe4= sigs.k8s.io/kind v0.19.0/go.mod h1:aBlbxg08cauDgZ612shr017/rZwqd7AS563FvpWKPVs= -sigs.k8s.io/kustomize/api v0.18.0 h1:hTzp67k+3NEVInwz5BHyzc9rGxIauoXferXyjv5lWPo= -sigs.k8s.io/kustomize/api v0.18.0/go.mod h1:f8isXnX+8b+SGLHQ6yO4JG1rdkZlvhaCf/uZbLVMb0U= -sigs.k8s.io/kustomize/kyaml v0.18.1 h1:WvBo56Wzw3fjS+7vBjN6TeivvpbW9GmRaWZ9CIVmt4E= -sigs.k8s.io/kustomize/kyaml v0.18.1/go.mod h1:C3L2BFVU1jgcddNBE1TxuVLgS46TjObMwW5FT9FcjYo= +sigs.k8s.io/kustomize/api v0.19.0 h1:F+2HB2mU1MSiR9Hp1NEgoU2q9ItNOaBJl0I4Dlus5SQ= +sigs.k8s.io/kustomize/api v0.19.0/go.mod h1:/BbwnivGVcBh1r+8m3tH1VNxJmHSk1PzP5fkP6lbL1o= +sigs.k8s.io/kustomize/kyaml v0.19.0 h1:RFge5qsO1uHhwJsu3ipV7RNolC7Uozc0jUBC/61XSlA= +sigs.k8s.io/kustomize/kyaml v0.19.0/go.mod h1:FeKD5jEOH+FbZPpqUghBP8mrLjJ3+zD3/rf9NNu1cwY= +sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/release-utils v0.8.4 h1:4QVr3UgbyY/d9p74LBhg0njSVQofUsAZqYOzVZBhdBw= sigs.k8s.io/release-utils v0.8.4/go.mod h1:m1bHfscTemQp+z+pLCZnkXih9n0+WukIUU70n6nFnU0= -sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= -sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= +sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= +sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/helm/trivy/Chart.yaml b/helm/trivy/Chart.yaml index c9f1d47c112e..505d12b8c6cd 100644 --- a/helm/trivy/Chart.yaml +++ b/helm/trivy/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v2 name: trivy -version: 0.13.0 -appVersion: 0.61.0 +version: 0.14.1 +appVersion: 0.62.1 description: Trivy helm chart keywords: - scanner diff --git a/integration/integration_test.go b/integration/integration_test.go index 21cdfe4facd9..de236c9f05af 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -60,9 +60,10 @@ func initDB(t *testing.T) string { defer dbtest.Close() err = metadata.NewClient(db.Dir(cacheDir)).Update(metadata.Metadata{ - Version: db.SchemaVersion, - NextUpdate: time.Now().Add(24 * time.Hour), - UpdatedAt: time.Now(), + Version: db.SchemaVersion, + NextUpdate: time.Now().Add(24 * time.Hour), + UpdatedAt: time.Now(), + DownloadedAt: time.Now(), }) require.NoError(t, err) @@ -153,6 +154,9 @@ func readReport(t *testing.T, filePath string) types.Report { // We don't compare repo tags because the archive doesn't support it report.Metadata.RepoTags = nil report.Metadata.RepoDigests = nil + for i := range report.Metadata.Layers { + report.Metadata.Layers[i].Digest = "" + } for i, result := range report.Results { for j := range result.Vulnerabilities { diff --git a/integration/repo_test.go b/integration/repo_test.go index 5a87d196f6db..a5cc880aeba2 100644 --- a/integration/repo_test.go +++ b/integration/repo_test.go @@ -286,6 +286,15 @@ func TestRepository(t *testing.T) { }, golden: "testdata/composer.lock.json.golden", }, + { + name: "cargo.lock", + args: args{ + scanner: types.VulnerabilityScanner, + listAllPkgs: true, + input: "testdata/fixtures/repo/cargo", + }, + golden: "testdata/cargo.lock.json.golden", + }, { name: "multiple lockfiles", args: args{ diff --git a/integration/sbom_test.go b/integration/sbom_test.go index 9ee8f187af98..f97a7c0c61d5 100644 --- a/integration/sbom_test.go +++ b/integration/sbom_test.go @@ -47,6 +47,10 @@ func TestSBOM(t *testing.T) { want.Results[0].Vulnerabilities[0].PkgIdentifier.BOMRef = "pkg:rpm/centos/bash@4.2.46-31.el7?arch=x86_64&distro=centos-7.6.1810" want.Results[0].Vulnerabilities[1].PkgIdentifier.BOMRef = "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810" want.Results[0].Vulnerabilities[2].PkgIdentifier.BOMRef = "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810" + + // SBOM file doesn't contain info about layers + want.Metadata.Size = 0 + want.Metadata.Layers = nil }, }, { @@ -96,6 +100,10 @@ func TestSBOM(t *testing.T) { want.Results[0].Vulnerabilities[0].PkgIdentifier.BOMRef = "pkg:rpm/centos/bash@4.2.46-31.el7?arch=x86_64&distro=centos-7.6.1810" want.Results[0].Vulnerabilities[1].PkgIdentifier.BOMRef = "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810" want.Results[0].Vulnerabilities[2].PkgIdentifier.BOMRef = "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810" + + // SBOM file doesn't contain info about layers + want.Metadata.Size = 0 + want.Metadata.Layers = nil }, }, { @@ -112,6 +120,10 @@ func TestSBOM(t *testing.T) { require.Len(t, got.Results, 1) want.Results[0].Target = "testdata/fixtures/sbom/centos-7-spdx.txt (centos 7.6.1810)" + + // SBOM file doesn't contain info about layers + want.Metadata.Size = 0 + want.Metadata.Layers = nil }, }, { @@ -128,6 +140,10 @@ func TestSBOM(t *testing.T) { require.Len(t, got.Results, 1) want.Results[0].Target = "testdata/fixtures/sbom/centos-7-spdx.json (centos 7.6.1810)" + + // SBOM file doesn't contain info about layers + want.Metadata.Size = 0 + want.Metadata.Layers = nil }, }, { diff --git a/integration/standalone_tar_test.go b/integration/standalone_tar_test.go index 4a0027e7743a..490ef07159d6 100644 --- a/integration/standalone_tar_test.go +++ b/integration/standalone_tar_test.go @@ -588,7 +588,7 @@ cache: for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { configPath := filepath.Join(t.TempDir(), "trivy.yaml") - err := os.WriteFile(configPath, []byte(tt.configFile), 0600) + err := os.WriteFile(configPath, []byte(tt.configFile), 0o600) require.NoError(t, err) osArgs := []string{ diff --git a/integration/testdata/almalinux-8.json.golden b/integration/testdata/almalinux-8.json.golden index 3f513e20b9f8..b3a5d6c6571c 100644 --- a/integration/testdata/almalinux-8.json.golden +++ b/integration/testdata/almalinux-8.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/almalinux-8.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 204637184, "OS": { "Family": "alma", "Name": "8.5" @@ -44,7 +45,14 @@ ], "Image": "sha256:d38d2eac03bc19e080df596d6148863a0f8293f3a277a7524f378da79a1feb0f" } - } + }, + "Layers": [ + { + "Size": 204637184, + "Digest": "sha256:a1f18d9dc5496c63197eb9a4f1d4bf5cc88c6a34f64f0fe11ea233070392ce48", + "DiffID": "sha256:124d41c237c5e823577dda97e87cebaecce62d585c725d07e709ce410681de4d" + } + ] }, "Results": [ { diff --git a/integration/testdata/alpine-310.json.golden b/integration/testdata/alpine-310.json.golden index 35010bb44794..acdf3f8f5c2f 100644 --- a/integration/testdata/alpine-310.json.golden +++ b/integration/testdata/alpine-310.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/alpine-310.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 5843968, "OS": { "Family": "alpine", "Name": "3.10.2", @@ -46,7 +47,14 @@ "Image": "sha256:06f4121dff4d0123ce11bd2e44f48da9ba9ddcd23ae376ea1f363f63ea0849b5", "ArgsEscaped": true } - } + }, + "Layers": [ + { + "Size": 5843968, + "Digest": "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", + "DiffID": "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" + } + ] }, "Results": [ { diff --git a/integration/testdata/alpine-39-high-critical.json.golden b/integration/testdata/alpine-39-high-critical.json.golden index 26931273fd01..e4852b51062b 100644 --- a/integration/testdata/alpine-39-high-critical.json.golden +++ b/integration/testdata/alpine-39-high-critical.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/alpine-39.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 5796352, "OS": { "Family": "alpine", "Name": "3.9.4", @@ -46,7 +47,14 @@ "Image": "sha256:09f2bbe58e774849d74dc1391c2e01731896c745c4aba1ecf69a283bdb4b537a", "ArgsEscaped": true } - } + }, + "Layers": [ + { + "Size": 5796352, + "Digest": "sha256:e7c96db7181be991f19a9fb6975cdbbd73c65f4a2681348e63a141a2192a5f10", + "DiffID": "sha256:f1b5933fe4b5f49bbe8258745cf396afe07e625bdab3168e364daf7c956b6b81" + } + ] }, "Results": [ { diff --git a/integration/testdata/alpine-39-ignore-cveids.json.golden b/integration/testdata/alpine-39-ignore-cveids.json.golden index 14cda282c1aa..25e1f3c57ebf 100644 --- a/integration/testdata/alpine-39-ignore-cveids.json.golden +++ b/integration/testdata/alpine-39-ignore-cveids.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/alpine-39.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 5796352, "OS": { "Family": "alpine", "Name": "3.9.4", @@ -46,7 +47,14 @@ "Image": "sha256:09f2bbe58e774849d74dc1391c2e01731896c745c4aba1ecf69a283bdb4b537a", "ArgsEscaped": true } - } + }, + "Layers": [ + { + "Size": 5796352, + "Digest": "sha256:e7c96db7181be991f19a9fb6975cdbbd73c65f4a2681348e63a141a2192a5f10", + "DiffID": "sha256:f1b5933fe4b5f49bbe8258745cf396afe07e625bdab3168e364daf7c956b6b81" + } + ] }, "Results": [ { diff --git a/integration/testdata/alpine-39-skip.json.golden b/integration/testdata/alpine-39-skip.json.golden index 2584d251c0b7..8a84c358e45e 100644 --- a/integration/testdata/alpine-39-skip.json.golden +++ b/integration/testdata/alpine-39-skip.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/alpine-39.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 5796352, "OS": { "Family": "none", "Name": "" @@ -45,6 +46,13 @@ "Image": "sha256:09f2bbe58e774849d74dc1391c2e01731896c745c4aba1ecf69a283bdb4b537a", "ArgsEscaped": true } - } + }, + "Layers": [ + { + "Size": 5796352, + "Digest": "sha256:e7c96db7181be991f19a9fb6975cdbbd73c65f4a2681348e63a141a2192a5f10", + "DiffID": "sha256:f1b5933fe4b5f49bbe8258745cf396afe07e625bdab3168e364daf7c956b6b81" + } + ] } } diff --git a/integration/testdata/alpine-39.json.golden b/integration/testdata/alpine-39.json.golden index 35d3e2a5c1b7..3ccfde92789f 100644 --- a/integration/testdata/alpine-39.json.golden +++ b/integration/testdata/alpine-39.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/alpine-39.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 5796352, "OS": { "Family": "alpine", "Name": "3.9.4", @@ -46,7 +47,14 @@ "Image": "sha256:09f2bbe58e774849d74dc1391c2e01731896c745c4aba1ecf69a283bdb4b537a", "ArgsEscaped": true } - } + }, + "Layers": [ + { + "Size": 5796352, + "Digest": "sha256:e7c96db7181be991f19a9fb6975cdbbd73c65f4a2681348e63a141a2192a5f10", + "DiffID": "sha256:f1b5933fe4b5f49bbe8258745cf396afe07e625bdab3168e364daf7c956b6b81" + } + ] }, "Results": [ { diff --git a/integration/testdata/alpine-distroless.json.golden b/integration/testdata/alpine-distroless.json.golden index da03075d0cd5..c0f59de9b0f0 100644 --- a/integration/testdata/alpine-distroless.json.golden +++ b/integration/testdata/alpine-distroless.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/alpine-distroless.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 35812864, "OS": { "Family": "alpine", "Name": "3.16" @@ -41,7 +42,14 @@ ], "User": "65532" } - } + }, + "Layers": [ + { + "Size": 35812864, + "Digest": "sha256:6c6f69aa25501b090c54c62a9c17e978064c2f1328f67a7ef88c81ce5f2d7983", + "DiffID": "sha256:89da7cc836da4b53ab1ceb572576458c005e7e444b8bb79abda196668a2f0c92" + } + ] }, "Results": [ { diff --git a/integration/testdata/amazon-1.json.golden b/integration/testdata/amazon-1.json.golden index 6472eb1859d2..63a15b50de28 100644 --- a/integration/testdata/amazon-1.json.golden +++ b/integration/testdata/amazon-1.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/amazon-1.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 172655616, "OS": { "Family": "amazon", "Name": "AMI release 2018.03" @@ -45,7 +46,14 @@ "Image": "sha256:8db654f611aca1693ac658bd981ee35e4b6517e6ef74fa608c4b3b3595a986c8", "ArgsEscaped": true } - } + }, + "Layers": [ + { + "Size": 172655616, + "Digest": "sha256:105ff6bf468b1422ad7c47ea9d63eae82f875c93310cb8d34551951e754ef43b", + "DiffID": "sha256:984fe1509738f6f00f34d9be7398b07ebeb8b98dda077ff6be2cdb87111b73cf" + } + ] }, "Results": [ { diff --git a/integration/testdata/amazon-2.json.golden b/integration/testdata/amazon-2.json.golden index 530f2b6daf69..bd5729a59d49 100644 --- a/integration/testdata/amazon-2.json.golden +++ b/integration/testdata/amazon-2.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/amazon-2.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 168852480, "OS": { "Family": "amazon", "Name": "2 (Karoo)" @@ -45,7 +46,14 @@ "Image": "sha256:648b8b37f8b5087423bec7f4331271253f8aff63154761a67c22cd0c3ba2661b", "ArgsEscaped": true } - } + }, + "Layers": [ + { + "Size": 168852480, + "Digest": "sha256:72d97abdfae3b3c933ff41e39779cc72853d7bd9dc1e4800c5294d6715257799", + "DiffID": "sha256:f387c8b346c85cae37abd1f1a63015acb69f593dc425d0269f57d1012c3a81f6" + } + ] }, "Results": [ { diff --git a/integration/testdata/busybox-with-lockfile.json.golden b/integration/testdata/busybox-with-lockfile.json.golden index 1420c166d012..7cc71da240bb 100644 --- a/integration/testdata/busybox-with-lockfile.json.golden +++ b/integration/testdata/busybox-with-lockfile.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/busybox-with-lockfile.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 1491456, "ImageID": "sha256:88702f6b6133bf06cc46af48437d0c0fc661239155548757c65916504a0e5eee", "DiffIDs": [ "sha256:797ac4999b67d8c38a596919efa5b7b6a4a8fd5814cb8564efa482c5d8403e6d", @@ -45,7 +46,19 @@ ], "Image": "sha256:2fb6fc2d97e10c79983aa10e013824cc7fc8bae50630e32159821197dda95fe3" } - } + }, + "Layers": [ + { + "Size": 1459200, + "Digest": "sha256:554879bb300427c7301c1cbdf266a7eba24a85b10d19f270b3d348b9eb9ca7df", + "DiffID": "sha256:797ac4999b67d8c38a596919efa5b7b6a4a8fd5814cb8564efa482c5d8403e6d" + }, + { + "Size": 32256, + "Digest": "sha256:fd2e3bc9bccc9c677572a542d020998389de94f127ca2c252ae627fc7c241cee", + "DiffID": "sha256:ea6f6933da66090da8bfe233d68f083792a68f944cd2d8f9fbb52da795813a4f" + } + ] }, "Results": [ { diff --git a/integration/testdata/cargo.lock.json.golden b/integration/testdata/cargo.lock.json.golden new file mode 100644 index 000000000000..e289f008fb59 --- /dev/null +++ b/integration/testdata/cargo.lock.json.golden @@ -0,0 +1,165 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/cargo", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "Cargo.lock", + "Class": "lang-pkgs", + "Type": "cargo", + "Packages": [ + { + "ID": "app@0.1.0", + "Name": "app", + "Identifier": { + "PURL": "pkg:cargo/app@0.1.0", + "UID": "a4ce1e2c46af5d56" + }, + "Version": "0.1.0", + "Relationship": "root", + "DependsOn": [ + "memchr@1.0.2", + "regex@1.7.3" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 14, + "EndLine": 21 + } + ] + }, + { + "ID": "memchr@1.0.2", + "Name": "memchr", + "Identifier": { + "PURL": "pkg:cargo/memchr@1.0.2", + "UID": "427a73f0e28dc7df" + }, + "Version": "1.0.2", + "Relationship": "direct", + "DependsOn": [ + "libc@0.2.171" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 29, + "EndLine": 36 + } + ] + }, + { + "ID": "regex@1.7.3", + "Name": "regex", + "Identifier": { + "PURL": "pkg:cargo/regex@1.7.3", + "UID": "4633c68363763fad" + }, + "Version": "1.7.3", + "Relationship": "direct", + "DependsOn": [ + "aho-corasick@0.7.20", + "memchr@2.7.4", + "regex-syntax@0.6.29" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 44, + "EndLine": 53 + } + ] + }, + { + "ID": "aho-corasick@0.7.20", + "Name": "aho-corasick", + "Identifier": { + "PURL": "pkg:cargo/aho-corasick@0.7.20", + "UID": "994a6d343f8da957" + }, + "Version": "0.7.20", + "Indirect": true, + "Relationship": "indirect", + "DependsOn": [ + "memchr@2.7.4" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 5, + "EndLine": 12 + } + ] + }, + { + "ID": "libc@0.2.171", + "Name": "libc", + "Identifier": { + "PURL": "pkg:cargo/libc@0.2.171", + "UID": "5395cf65d65d1f19" + }, + "Version": "0.2.171", + "Indirect": true, + "Relationship": "indirect", + "Layer": {}, + "Locations": [ + { + "StartLine": 23, + "EndLine": 27 + } + ] + }, + { + "ID": "memchr@2.7.4", + "Name": "memchr", + "Identifier": { + "PURL": "pkg:cargo/memchr@2.7.4", + "UID": "3f037d5da23e5826" + }, + "Version": "2.7.4", + "Indirect": true, + "Relationship": "indirect", + "Layer": {}, + "Locations": [ + { + "StartLine": 38, + "EndLine": 42 + } + ] + }, + { + "ID": "regex-syntax@0.6.29", + "Name": "regex-syntax", + "Identifier": { + "PURL": "pkg:cargo/regex-syntax@0.6.29", + "UID": "2c8dd93ce2f15b00" + }, + "Version": "0.6.29", + "Indirect": true, + "Relationship": "indirect", + "Layer": {}, + "Locations": [ + { + "StartLine": 55, + "EndLine": 59 + } + ] + } + ] + } + ] +} diff --git a/integration/testdata/centos-6.json.golden b/integration/testdata/centos-6.json.golden index aefd6f2652e0..81ee4a0b8c8c 100644 --- a/integration/testdata/centos-6.json.golden +++ b/integration/testdata/centos-6.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/centos-6.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 201540608, "OS": { "Family": "centos", "Name": "6.10", @@ -67,7 +68,14 @@ "org.label-schema.vendor": "CentOS" } } - } + }, + "Layers": [ + { + "Size": 201540608, + "Digest": "sha256:ff50d722b38227ec8f2bbf0cdbce428b66745077c173d8117d91376128fa532e", + "DiffID": "sha256:af6bf1987c2eb07d73f33836b0d8fd825d7c785273526b077e46780e8b4b2ae9" + } + ] }, "Results": [ { diff --git a/integration/testdata/centos-7-ignore-unfixed.json.golden b/integration/testdata/centos-7-ignore-unfixed.json.golden index 9a5deaa4e5fa..a6450628ecc7 100644 --- a/integration/testdata/centos-7-ignore-unfixed.json.golden +++ b/integration/testdata/centos-7-ignore-unfixed.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/centos-7.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 209451008, "OS": { "Family": "centos", "Name": "7.6.1810" @@ -57,7 +58,14 @@ }, "ArgsEscaped": true } - } + }, + "Layers": [ + { + "Size": 209451008, + "Digest": "sha256:ac9208207adaac3a48e54a4dc6b49c69e78c3072d2b3add7efdabf814db2133b", + "DiffID": "sha256:89169d87dbe2b72ba42bfbb3579c957322baca28e03a1e558076542a1c1b2b4a" + } + ] }, "Results": [ { diff --git a/integration/testdata/centos-7-medium.json.golden b/integration/testdata/centos-7-medium.json.golden index 479640858f19..187159cd11a6 100644 --- a/integration/testdata/centos-7-medium.json.golden +++ b/integration/testdata/centos-7-medium.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/centos-7.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 209451008, "OS": { "Family": "centos", "Name": "7.6.1810" @@ -57,7 +58,14 @@ }, "ArgsEscaped": true } - } + }, + "Layers": [ + { + "Size": 209451008, + "Digest": "sha256:ac9208207adaac3a48e54a4dc6b49c69e78c3072d2b3add7efdabf814db2133b", + "DiffID": "sha256:89169d87dbe2b72ba42bfbb3579c957322baca28e03a1e558076542a1c1b2b4a" + } + ] }, "Results": [ { diff --git a/integration/testdata/centos-7.json.golden b/integration/testdata/centos-7.json.golden index d130399fcd53..c9b71a0360cb 100644 --- a/integration/testdata/centos-7.json.golden +++ b/integration/testdata/centos-7.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/centos-7.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 209451008, "OS": { "Family": "centos", "Name": "7.6.1810" @@ -57,7 +58,14 @@ }, "ArgsEscaped": true } - } + }, + "Layers": [ + { + "Size": 209451008, + "Digest": "sha256:ac9208207adaac3a48e54a4dc6b49c69e78c3072d2b3add7efdabf814db2133b", + "DiffID": "sha256:89169d87dbe2b72ba42bfbb3579c957322baca28e03a1e558076542a1c1b2b4a" + } + ] }, "Results": [ { diff --git a/integration/testdata/debian-buster-ignore-unfixed.json.golden b/integration/testdata/debian-buster-ignore-unfixed.json.golden index cb5a606e3a94..c14738ff5f65 100644 --- a/integration/testdata/debian-buster-ignore-unfixed.json.golden +++ b/integration/testdata/debian-buster-ignore-unfixed.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/debian-buster.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 119199744, "OS": { "Family": "debian", "Name": "10.1" @@ -45,7 +46,14 @@ "Image": "sha256:5519bb349f72eef81944da56843c995b1b81ed67c8e7e48ac29dd6c543c1dd2d", "ArgsEscaped": true } - } + }, + "Layers": [ + { + "Size": 119199744, + "Digest": "sha256:4a56a430b2bac33260d6449e162017e2b23076c6411a17b46db67f5b84dde2bd", + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + ] }, "Results": [ { diff --git a/integration/testdata/debian-buster.json.golden b/integration/testdata/debian-buster.json.golden index 4c9d64fc8347..1a23e8f12607 100644 --- a/integration/testdata/debian-buster.json.golden +++ b/integration/testdata/debian-buster.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/debian-buster.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 119199744, "OS": { "Family": "debian", "Name": "10.1" @@ -45,7 +46,14 @@ "Image": "sha256:5519bb349f72eef81944da56843c995b1b81ed67c8e7e48ac29dd6c543c1dd2d", "ArgsEscaped": true } - } + }, + "Layers": [ + { + "Size": 119199744, + "Digest": "sha256:4a56a430b2bac33260d6449e162017e2b23076c6411a17b46db67f5b84dde2bd", + "DiffID": "sha256:78c1b9419976227e05be9d243b7fa583bea44a5258e52018b2af4cdfe23d148d" + } + ] }, "Results": [ { diff --git a/integration/testdata/debian-stretch.json.golden b/integration/testdata/debian-stretch.json.golden index e4be6f91f1f7..57acb708d5c8 100644 --- a/integration/testdata/debian-stretch.json.golden +++ b/integration/testdata/debian-stretch.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/debian-stretch.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 105582080, "OS": { "Family": "debian", "Name": "9.9" @@ -45,7 +46,14 @@ "Image": "sha256:2ce0e924e5d43d66387e476478ce3c857b1eaae74b5c74693ed47b3502bbdc3e", "ArgsEscaped": true } - } + }, + "Layers": [ + { + "Size": 105582080, + "Digest": "sha256:9cc2ad81d40d54dcae7fa5e8e17d9c34e8bba3b7c2cc7e26fb22734608bda32e", + "DiffID": "sha256:f73e7e79899a33b4b9b78da62efb71520844f8dd518f3c390e27bc3063bce307" + } + ] }, "Results": [ { diff --git a/integration/testdata/distroless-base.json.golden b/integration/testdata/distroless-base.json.golden index c5872a901494..cabbc7f35bfe 100644 --- a/integration/testdata/distroless-base.json.golden +++ b/integration/testdata/distroless-base.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/distroless-base.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 18503680, "OS": { "Family": "debian", "Name": "9.9" @@ -43,7 +44,19 @@ "SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt" ] } - } + }, + "Layers": [ + { + "Size": 3061760, + "Digest": "sha256:e8d8785a314f385d3675a017f4e2df1707c528c06e7a7989663fdab4900bd8ff", + "DiffID": "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02" + }, + { + "Size": 15441920, + "Digest": "sha256:e005d777a298a3529b1c8cf890883359e050cc966089ce84fea4d17b111907db", + "DiffID": "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5" + } + ] }, "Results": [ { diff --git a/integration/testdata/distroless-python27.json.golden b/integration/testdata/distroless-python27.json.golden index 403b34c2dba8..ab0aca48849a 100644 --- a/integration/testdata/distroless-python27.json.golden +++ b/integration/testdata/distroless-python27.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/distroless-python27.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 48271360, "OS": { "Family": "debian", "Name": "9.9" @@ -60,7 +61,29 @@ "SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt" ] } - } + }, + "Layers": [ + { + "Size": 3061760, + "Digest": "sha256:e8d8785a314f385d3675a017f4e2df1707c528c06e7a7989663fdab4900bd8ff", + "DiffID": "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02" + }, + { + "Size": 15441920, + "Digest": "sha256:e005d777a298a3529b1c8cf890883359e050cc966089ce84fea4d17b111907db", + "DiffID": "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5" + }, + { + "Size": 1966080, + "Digest": "sha256:3e010093287c245d72a774033b4cddd6451a820bfbb1948c97798e1838858dd2", + "DiffID": "sha256:6189abe095d53c1c9f2bfc8f50128ee876b9a5d10f9eda1564e5f5357d6ffe61" + }, + { + "Size": 27801600, + "Digest": "sha256:0fedbb4c6207e6eebb78477eb49d550cafa9eccbf1079c57c026a36d727d684a", + "DiffID": "sha256:e92caab8efcf25a24bea5213ab7e54d4a5f5f08644836bb2d296070b1ae1044e" + } + ] }, "Results": [ { diff --git a/integration/testdata/fixtures/repo/cargo/Cargo.lock b/integration/testdata/fixtures/repo/cargo/Cargo.lock new file mode 100644 index 000000000000..d3b73a3baf7e --- /dev/null +++ b/integration/testdata/fixtures/repo/cargo/Cargo.lock @@ -0,0 +1,81 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr 2.7.4", +] + +[[package]] +name = "app" +version = "0.1.0" +dependencies = [ + "memchr 1.0.2", + "regex", + "winapi", +] + +[[package]] +name = "libc" +version = "0.2.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" + +[[package]] +name = "memchr" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "regex" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +dependencies = [ + "aho-corasick", + "memchr 2.7.4", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/integration/testdata/fixtures/repo/cargo/Cargo.toml b/integration/testdata/fixtures/repo/cargo/Cargo.toml new file mode 100644 index 000000000000..86bc84d93087 --- /dev/null +++ b/integration/testdata/fixtures/repo/cargo/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "app" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +regex = "=1.7.3" + +[target.'cfg(not(target_os = "windows"))'.dependencies] +memchr = { version = "1.*", optional = true } + +[dev-dependencies] +winapi = "*" \ No newline at end of file diff --git a/integration/testdata/fluentd-gems.json.golden b/integration/testdata/fluentd-gems.json.golden index ef3a04bb7a50..5355410962cc 100644 --- a/integration/testdata/fluentd-gems.json.golden +++ b/integration/testdata/fluentd-gems.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/fluentd-multiple-lockfiles.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 157340160, "OS": { "Family": "debian", "Name": "10.2" @@ -98,7 +99,39 @@ "80/tcp": {} } } - } + }, + "Layers": [ + { + "Size": 72479232, + "Digest": "sha256:000eee12ec04cc914bf96e8f5dee7767510c2aca3816af6078bd9fbe3150920c", + "DiffID": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "Size": 3584, + "Digest": "sha256:aa8e216680f366acbe83d055382ae12208c341300fefd996fbebae49c59a14a5", + "DiffID": "sha256:02874b2b269dea8dde0f7edb4c9906904dfe38a09de1a214f20c650cfb15c60e" + }, + { + "Size": 2048, + "Digest": "sha256:2b92c5265c693f3aa71c011dabcfca1da81b3e5aef0530c2192fe9237a0725ed", + "DiffID": "sha256:3752e1f6fd759c795c13aff2c93c081529366e27635ba6621e849b0f9cfc77f0" + }, + { + "Size": 84848640, + "Digest": "sha256:a8877cad19f14a7044524a145ce33170085441a7922458017db1631dcd5f7602", + "DiffID": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "Size": 3584, + "Digest": "sha256:8c669d8f22e42bdc70ab962cd36f36c44d5299a04fbf8de62654eaf1aa70a01a", + "DiffID": "sha256:788c00e2cfc8f2a018ae4344ccf0b2c226ebd756d7effd1ce50eea1a4252cd89" + }, + { + "Size": 3072, + "Digest": "sha256:de335f1b3d0014660d2c8617930e431e23ee369075e14597c3fcad6903a81255", + "DiffID": "sha256:25165eb51d15842f870f97873e0a58409d5e860e6108e3dd829bd10e484c0065" + } + ] }, "Results": [ { diff --git a/integration/testdata/fluentd-multiple-lockfiles.cdx.json.golden b/integration/testdata/fluentd-multiple-lockfiles.cdx.json.golden index 1da5298a7398..ace7ce2d701b 100644 --- a/integration/testdata/fluentd-multiple-lockfiles.cdx.json.golden +++ b/integration/testdata/fluentd-multiple-lockfiles.cdx.json.golden @@ -52,6 +52,10 @@ { "name": "aquasecurity:trivy:SchemaVersion", "value": "2" + }, + { + "name": "aquasecurity:trivy:Size", + "value": "157340160" } ] } diff --git a/integration/testdata/helm.json.golden b/integration/testdata/helm.json.golden index 9b2c98c1d2d0..a6467d25484d 100644 --- a/integration/testdata/helm.json.golden +++ b/integration/testdata/helm.json.golden @@ -22,7 +22,7 @@ "Type": "helm", "MisconfSummary": { "Successes": 78, - "Failures": 22 + "Failures": 20 }, "Misconfigurations": [ { @@ -1072,264 +1072,6 @@ "RenderedCause": {} } }, - { - "Type": "Helm Security Check", - "ID": "KSV039", - "AVDID": "AVD-KSV-0039", - "Title": "limit range usage", - "Description": "ensure limit range policy has configure in order to limit resource usage for namespaces or nodes", - "Message": "limit range policy with a default request and limit, min and max request, for each container should be configure", - "Namespace": "builtin.kubernetes.KSV039", - "Query": "data.builtin.kubernetes.KSV039.deny", - "Resolution": "create limit range policy with a default request and limit, min and max request, for each container.", - "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv039", - "References": [ - "https://kubernetes.io/docs/tasks/administer-cluster/declare-network-policy/", - "https://avd.aquasec.com/misconfig/ksv039" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Provider": "Kubernetes", - "Service": "general", - "StartLine": 9, - "EndLine": 22, - "Code": { - "Lines": [ - { - "Number": 9, - "Content": " replicas: 3", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mreplicas\u001b[0m: \u001b[38;5;37m3", - "FirstCause": true, - "LastCause": false - }, - { - "Number": 10, - "Content": " selector:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": "\u001b[0m \u001b[38;5;33mselector\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 11, - "Content": " matchLabels:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mmatchLabels\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 12, - "Content": " app: nginx", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mapp\u001b[0m: nginx", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 13, - "Content": " template:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mtemplate\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 14, - "Content": " metadata:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mmetadata\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 15, - "Content": " labels:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mlabels\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 16, - "Content": " app: nginx", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mapp\u001b[0m: nginx", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 17, - "Content": " spec:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mspec\u001b[0m:", - "FirstCause": false, - "LastCause": true - }, - { - "Number": 18, - "Content": "", - "IsCause": false, - "Annotation": "", - "Truncated": true, - "FirstCause": false, - "LastCause": false - } - ] - }, - "RenderedCause": {} - } - }, - { - "Type": "Helm Security Check", - "ID": "KSV040", - "AVDID": "AVD-KSV-0040", - "Title": "resource quota usage", - "Description": "ensure resource quota policy has configure in order to limit aggregate resource usage within namespace", - "Message": "resource quota policy with hard memory and cpu quota per namespace should be configure", - "Namespace": "builtin.kubernetes.KSV040", - "Query": "data.builtin.kubernetes.KSV040.deny", - "Resolution": "create resource quota policy with mem and cpu quota per each namespace", - "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv040", - "References": [ - "https://kubernetes.io/docs/tasks/administer-cluster/manage-resources/quota-memory-cpu-namespace/", - "https://avd.aquasec.com/misconfig/ksv040" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Provider": "Kubernetes", - "Service": "general", - "StartLine": 9, - "EndLine": 22, - "Code": { - "Lines": [ - { - "Number": 9, - "Content": " replicas: 3", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mreplicas\u001b[0m: \u001b[38;5;37m3", - "FirstCause": true, - "LastCause": false - }, - { - "Number": 10, - "Content": " selector:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": "\u001b[0m \u001b[38;5;33mselector\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 11, - "Content": " matchLabels:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mmatchLabels\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 12, - "Content": " app: nginx", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mapp\u001b[0m: nginx", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 13, - "Content": " template:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mtemplate\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 14, - "Content": " metadata:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mmetadata\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 15, - "Content": " labels:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mlabels\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 16, - "Content": " app: nginx", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mapp\u001b[0m: nginx", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 17, - "Content": " spec:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mspec\u001b[0m:", - "FirstCause": false, - "LastCause": true - }, - { - "Number": 18, - "Content": "", - "IsCause": false, - "Annotation": "", - "Truncated": true, - "FirstCause": false, - "LastCause": false - } - ] - }, - "RenderedCause": {} - } - }, { "Type": "Helm Security Check", "ID": "KSV104", diff --git a/integration/testdata/helm_testchart.json.golden b/integration/testdata/helm_testchart.json.golden index 469074dccc68..541ea875e2cb 100644 --- a/integration/testdata/helm_testchart.json.golden +++ b/integration/testdata/helm_testchart.json.golden @@ -22,7 +22,7 @@ "Type": "helm", "MisconfSummary": { "Successes": 90, - "Failures": 10 + "Failures": 8 }, "Misconfigurations": [ { @@ -667,264 +667,6 @@ "RenderedCause": {} } }, - { - "Type": "Helm Security Check", - "ID": "KSV039", - "AVDID": "AVD-KSV-0039", - "Title": "limit range usage", - "Description": "ensure limit range policy has configure in order to limit resource usage for namespaces or nodes", - "Message": "limit range policy with a default request and limit, min and max request, for each container should be configure", - "Namespace": "builtin.kubernetes.KSV039", - "Query": "data.builtin.kubernetes.KSV039.deny", - "Resolution": "create limit range policy with a default request and limit, min and max request, for each container.", - "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv039", - "References": [ - "https://kubernetes.io/docs/tasks/administer-cluster/declare-network-policy/", - "https://avd.aquasec.com/misconfig/ksv039" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Provider": "Kubernetes", - "Service": "general", - "StartLine": 13, - "EndLine": 57, - "Code": { - "Lines": [ - { - "Number": 13, - "Content": " replicas: 1", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mreplicas\u001b[0m: \u001b[38;5;37m1", - "FirstCause": true, - "LastCause": false - }, - { - "Number": 14, - "Content": " selector:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": "\u001b[0m \u001b[38;5;33mselector\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 15, - "Content": " matchLabels:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mmatchLabels\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 16, - "Content": " app.kubernetes.io/name: testchart", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mapp.kubernetes.io/name\u001b[0m: testchart", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 17, - "Content": " app.kubernetes.io/instance: testchart", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mapp.kubernetes.io/instance\u001b[0m: testchart", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 18, - "Content": " template:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mtemplate\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 19, - "Content": " metadata:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mmetadata\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 20, - "Content": " labels:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mlabels\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 21, - "Content": " app.kubernetes.io/name: testchart", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mapp.kubernetes.io/name\u001b[0m: testchart", - "FirstCause": false, - "LastCause": true - }, - { - "Number": 22, - "Content": "", - "IsCause": false, - "Annotation": "", - "Truncated": true, - "FirstCause": false, - "LastCause": false - } - ] - }, - "RenderedCause": {} - } - }, - { - "Type": "Helm Security Check", - "ID": "KSV040", - "AVDID": "AVD-KSV-0040", - "Title": "resource quota usage", - "Description": "ensure resource quota policy has configure in order to limit aggregate resource usage within namespace", - "Message": "resource quota policy with hard memory and cpu quota per namespace should be configure", - "Namespace": "builtin.kubernetes.KSV040", - "Query": "data.builtin.kubernetes.KSV040.deny", - "Resolution": "create resource quota policy with mem and cpu quota per each namespace", - "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv040", - "References": [ - "https://kubernetes.io/docs/tasks/administer-cluster/manage-resources/quota-memory-cpu-namespace/", - "https://avd.aquasec.com/misconfig/ksv040" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Provider": "Kubernetes", - "Service": "general", - "StartLine": 13, - "EndLine": 57, - "Code": { - "Lines": [ - { - "Number": 13, - "Content": " replicas: 1", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mreplicas\u001b[0m: \u001b[38;5;37m1", - "FirstCause": true, - "LastCause": false - }, - { - "Number": 14, - "Content": " selector:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": "\u001b[0m \u001b[38;5;33mselector\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 15, - "Content": " matchLabels:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mmatchLabels\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 16, - "Content": " app.kubernetes.io/name: testchart", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mapp.kubernetes.io/name\u001b[0m: testchart", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 17, - "Content": " app.kubernetes.io/instance: testchart", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mapp.kubernetes.io/instance\u001b[0m: testchart", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 18, - "Content": " template:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mtemplate\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 19, - "Content": " metadata:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mmetadata\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 20, - "Content": " labels:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mlabels\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 21, - "Content": " app.kubernetes.io/name: testchart", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mapp.kubernetes.io/name\u001b[0m: testchart", - "FirstCause": false, - "LastCause": true - }, - { - "Number": 22, - "Content": "", - "IsCause": false, - "Annotation": "", - "Truncated": true, - "FirstCause": false, - "LastCause": false - } - ] - }, - "RenderedCause": {} - } - }, { "Type": "Helm Security Check", "ID": "KSV104", @@ -1190,257 +932,15 @@ "Type": "helm", "MisconfSummary": { "Successes": 59, - "Failures": 2 - }, - "Misconfigurations": [ - { - "Type": "Helm Security Check", - "ID": "KSV039", - "AVDID": "AVD-KSV-0039", - "Title": "limit range usage", - "Description": "ensure limit range policy has configure in order to limit resource usage for namespaces or nodes", - "Message": "limit range policy with a default request and limit, min and max request, for each container should be configure", - "Namespace": "builtin.kubernetes.KSV039", - "Query": "data.builtin.kubernetes.KSV039.deny", - "Resolution": "create limit range policy with a default request and limit, min and max request, for each container.", - "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv039", - "References": [ - "https://kubernetes.io/docs/tasks/administer-cluster/declare-network-policy/", - "https://avd.aquasec.com/misconfig/ksv039" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Provider": "Kubernetes", - "Service": "general", - "StartLine": 13, - "EndLine": 21, - "Code": { - "Lines": [ - { - "Number": 13, - "Content": " type: ClusterIP", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mtype\u001b[0m: ClusterIP", - "FirstCause": true, - "LastCause": false - }, - { - "Number": 14, - "Content": " ports:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mports\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 15, - "Content": " - port: 80", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " - \u001b[38;5;33mport\u001b[0m: \u001b[38;5;37m80", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 16, - "Content": " targetPort: http", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": "\u001b[0m \u001b[38;5;33mtargetPort\u001b[0m: http", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 17, - "Content": " protocol: TCP", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mprotocol\u001b[0m: TCP", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 18, - "Content": " name: http", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mname\u001b[0m: http", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 19, - "Content": " selector:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mselector\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 20, - "Content": " app.kubernetes.io/name: testchart", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mapp.kubernetes.io/name\u001b[0m: testchart", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 21, - "Content": " app.kubernetes.io/instance: testchart", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mapp.kubernetes.io/instance\u001b[0m: testchart", - "FirstCause": false, - "LastCause": true - } - ] - }, - "RenderedCause": {} - } - }, - { - "Type": "Helm Security Check", - "ID": "KSV040", - "AVDID": "AVD-KSV-0040", - "Title": "resource quota usage", - "Description": "ensure resource quota policy has configure in order to limit aggregate resource usage within namespace", - "Message": "resource quota policy with hard memory and cpu quota per namespace should be configure", - "Namespace": "builtin.kubernetes.KSV040", - "Query": "data.builtin.kubernetes.KSV040.deny", - "Resolution": "create resource quota policy with mem and cpu quota per each namespace", - "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv040", - "References": [ - "https://kubernetes.io/docs/tasks/administer-cluster/manage-resources/quota-memory-cpu-namespace/", - "https://avd.aquasec.com/misconfig/ksv040" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Provider": "Kubernetes", - "Service": "general", - "StartLine": 13, - "EndLine": 21, - "Code": { - "Lines": [ - { - "Number": 13, - "Content": " type: ClusterIP", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mtype\u001b[0m: ClusterIP", - "FirstCause": true, - "LastCause": false - }, - { - "Number": 14, - "Content": " ports:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mports\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 15, - "Content": " - port: 80", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " - \u001b[38;5;33mport\u001b[0m: \u001b[38;5;37m80", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 16, - "Content": " targetPort: http", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": "\u001b[0m \u001b[38;5;33mtargetPort\u001b[0m: http", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 17, - "Content": " protocol: TCP", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mprotocol\u001b[0m: TCP", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 18, - "Content": " name: http", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mname\u001b[0m: http", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 19, - "Content": " selector:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mselector\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 20, - "Content": " app.kubernetes.io/name: testchart", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mapp.kubernetes.io/name\u001b[0m: testchart", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 21, - "Content": " app.kubernetes.io/instance: testchart", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mapp.kubernetes.io/instance\u001b[0m: testchart", - "FirstCause": false, - "LastCause": true - } - ] - }, - "RenderedCause": {} - } - } - ] + "Failures": 0 + } }, { "Target": "templates/serviceaccount.yaml", "Class": "config", "Type": "helm", "MisconfSummary": { - "Successes": 60, + "Successes": 58, "Failures": 0 } } diff --git a/integration/testdata/helm_testchart.overridden.json.golden b/integration/testdata/helm_testchart.overridden.json.golden index aa00873d1de5..0f1d14ade623 100644 --- a/integration/testdata/helm_testchart.overridden.json.golden +++ b/integration/testdata/helm_testchart.overridden.json.golden @@ -22,7 +22,7 @@ "Type": "helm", "MisconfSummary": { "Successes": 88, - "Failures": 12 + "Failures": 10 }, "Misconfigurations": [ { @@ -796,264 +796,6 @@ "RenderedCause": {} } }, - { - "Type": "Helm Security Check", - "ID": "KSV039", - "AVDID": "AVD-KSV-0039", - "Title": "limit range usage", - "Description": "ensure limit range policy has configure in order to limit resource usage for namespaces or nodes", - "Message": "limit range policy with a default request and limit, min and max request, for each container should be configure", - "Namespace": "builtin.kubernetes.KSV039", - "Query": "data.builtin.kubernetes.KSV039.deny", - "Resolution": "create limit range policy with a default request and limit, min and max request, for each container.", - "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv039", - "References": [ - "https://kubernetes.io/docs/tasks/administer-cluster/declare-network-policy/", - "https://avd.aquasec.com/misconfig/ksv039" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Provider": "Kubernetes", - "Service": "general", - "StartLine": 13, - "EndLine": 57, - "Code": { - "Lines": [ - { - "Number": 13, - "Content": " replicas: 1", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mreplicas\u001b[0m: \u001b[38;5;37m1", - "FirstCause": true, - "LastCause": false - }, - { - "Number": 14, - "Content": " selector:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": "\u001b[0m \u001b[38;5;33mselector\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 15, - "Content": " matchLabels:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mmatchLabels\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 16, - "Content": " app.kubernetes.io/name: testchart", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mapp.kubernetes.io/name\u001b[0m: testchart", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 17, - "Content": " app.kubernetes.io/instance: testchart", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mapp.kubernetes.io/instance\u001b[0m: testchart", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 18, - "Content": " template:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mtemplate\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 19, - "Content": " metadata:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mmetadata\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 20, - "Content": " labels:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mlabels\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 21, - "Content": " app.kubernetes.io/name: testchart", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mapp.kubernetes.io/name\u001b[0m: testchart", - "FirstCause": false, - "LastCause": true - }, - { - "Number": 22, - "Content": "", - "IsCause": false, - "Annotation": "", - "Truncated": true, - "FirstCause": false, - "LastCause": false - } - ] - }, - "RenderedCause": {} - } - }, - { - "Type": "Helm Security Check", - "ID": "KSV040", - "AVDID": "AVD-KSV-0040", - "Title": "resource quota usage", - "Description": "ensure resource quota policy has configure in order to limit aggregate resource usage within namespace", - "Message": "resource quota policy with hard memory and cpu quota per namespace should be configure", - "Namespace": "builtin.kubernetes.KSV040", - "Query": "data.builtin.kubernetes.KSV040.deny", - "Resolution": "create resource quota policy with mem and cpu quota per each namespace", - "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv040", - "References": [ - "https://kubernetes.io/docs/tasks/administer-cluster/manage-resources/quota-memory-cpu-namespace/", - "https://avd.aquasec.com/misconfig/ksv040" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Provider": "Kubernetes", - "Service": "general", - "StartLine": 13, - "EndLine": 57, - "Code": { - "Lines": [ - { - "Number": 13, - "Content": " replicas: 1", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mreplicas\u001b[0m: \u001b[38;5;37m1", - "FirstCause": true, - "LastCause": false - }, - { - "Number": 14, - "Content": " selector:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": "\u001b[0m \u001b[38;5;33mselector\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 15, - "Content": " matchLabels:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mmatchLabels\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 16, - "Content": " app.kubernetes.io/name: testchart", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mapp.kubernetes.io/name\u001b[0m: testchart", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 17, - "Content": " app.kubernetes.io/instance: testchart", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mapp.kubernetes.io/instance\u001b[0m: testchart", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 18, - "Content": " template:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mtemplate\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 19, - "Content": " metadata:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mmetadata\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 20, - "Content": " labels:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mlabels\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 21, - "Content": " app.kubernetes.io/name: testchart", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mapp.kubernetes.io/name\u001b[0m: testchart", - "FirstCause": false, - "LastCause": true - }, - { - "Number": 22, - "Content": "", - "IsCause": false, - "Annotation": "", - "Truncated": true, - "FirstCause": false, - "LastCause": false - } - ] - }, - "RenderedCause": {} - } - }, { "Type": "Helm Security Check", "ID": "KSV104", @@ -1419,257 +1161,15 @@ "Type": "helm", "MisconfSummary": { "Successes": 59, - "Failures": 2 - }, - "Misconfigurations": [ - { - "Type": "Helm Security Check", - "ID": "KSV039", - "AVDID": "AVD-KSV-0039", - "Title": "limit range usage", - "Description": "ensure limit range policy has configure in order to limit resource usage for namespaces or nodes", - "Message": "limit range policy with a default request and limit, min and max request, for each container should be configure", - "Namespace": "builtin.kubernetes.KSV039", - "Query": "data.builtin.kubernetes.KSV039.deny", - "Resolution": "create limit range policy with a default request and limit, min and max request, for each container.", - "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv039", - "References": [ - "https://kubernetes.io/docs/tasks/administer-cluster/declare-network-policy/", - "https://avd.aquasec.com/misconfig/ksv039" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Provider": "Kubernetes", - "Service": "general", - "StartLine": 13, - "EndLine": 21, - "Code": { - "Lines": [ - { - "Number": 13, - "Content": " type: ClusterIP", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mtype\u001b[0m: ClusterIP", - "FirstCause": true, - "LastCause": false - }, - { - "Number": 14, - "Content": " ports:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mports\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 15, - "Content": " - port: 80", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " - \u001b[38;5;33mport\u001b[0m: \u001b[38;5;37m80", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 16, - "Content": " targetPort: http", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": "\u001b[0m \u001b[38;5;33mtargetPort\u001b[0m: http", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 17, - "Content": " protocol: TCP", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mprotocol\u001b[0m: TCP", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 18, - "Content": " name: http", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mname\u001b[0m: http", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 19, - "Content": " selector:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mselector\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 20, - "Content": " app.kubernetes.io/name: testchart", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mapp.kubernetes.io/name\u001b[0m: testchart", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 21, - "Content": " app.kubernetes.io/instance: testchart", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mapp.kubernetes.io/instance\u001b[0m: testchart", - "FirstCause": false, - "LastCause": true - } - ] - }, - "RenderedCause": {} - } - }, - { - "Type": "Helm Security Check", - "ID": "KSV040", - "AVDID": "AVD-KSV-0040", - "Title": "resource quota usage", - "Description": "ensure resource quota policy has configure in order to limit aggregate resource usage within namespace", - "Message": "resource quota policy with hard memory and cpu quota per namespace should be configure", - "Namespace": "builtin.kubernetes.KSV040", - "Query": "data.builtin.kubernetes.KSV040.deny", - "Resolution": "create resource quota policy with mem and cpu quota per each namespace", - "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv040", - "References": [ - "https://kubernetes.io/docs/tasks/administer-cluster/manage-resources/quota-memory-cpu-namespace/", - "https://avd.aquasec.com/misconfig/ksv040" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Provider": "Kubernetes", - "Service": "general", - "StartLine": 13, - "EndLine": 21, - "Code": { - "Lines": [ - { - "Number": 13, - "Content": " type: ClusterIP", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mtype\u001b[0m: ClusterIP", - "FirstCause": true, - "LastCause": false - }, - { - "Number": 14, - "Content": " ports:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mports\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 15, - "Content": " - port: 80", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " - \u001b[38;5;33mport\u001b[0m: \u001b[38;5;37m80", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 16, - "Content": " targetPort: http", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": "\u001b[0m \u001b[38;5;33mtargetPort\u001b[0m: http", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 17, - "Content": " protocol: TCP", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mprotocol\u001b[0m: TCP", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 18, - "Content": " name: http", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mname\u001b[0m: http", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 19, - "Content": " selector:", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mselector\u001b[0m:", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 20, - "Content": " app.kubernetes.io/name: testchart", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mapp.kubernetes.io/name\u001b[0m: testchart", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 21, - "Content": " app.kubernetes.io/instance: testchart", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;33mapp.kubernetes.io/instance\u001b[0m: testchart", - "FirstCause": false, - "LastCause": true - } - ] - }, - "RenderedCause": {} - } - } - ] + "Failures": 0 + } }, { "Target": "templates/serviceaccount.yaml", "Class": "config", "Type": "helm", "MisconfSummary": { - "Successes": 60, + "Successes": 58, "Failures": 0 } } diff --git a/integration/testdata/mariner-1.0.json.golden b/integration/testdata/mariner-1.0.json.golden index 8805f104c60c..56fc83b404e7 100644 --- a/integration/testdata/mariner-1.0.json.golden +++ b/integration/testdata/mariner-1.0.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/mariner-1.0.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 177582080, "OS": { "Family": "cbl-mariner", "Name": "1.0" @@ -30,7 +31,14 @@ ] }, "config": {} - } + }, + "Layers": [ + { + "Size": 177582080, + "Digest": "sha256:3df36548ffbf2fa7319966e038058a3d2a922880009e535202546a6b250b9d57", + "DiffID": "sha256:4266328c97a194b2ca52ec83bc05496596303f5e9b244ffa99cf84763a487804" + } + ] }, "Results": [ { diff --git a/integration/testdata/opensuse-leap-151.json.golden b/integration/testdata/opensuse-leap-151.json.golden index 704f4fa10b03..032deac0dd72 100644 --- a/integration/testdata/opensuse-leap-151.json.golden +++ b/integration/testdata/opensuse-leap-151.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/opensuse-leap-151.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 105899520, "OS": { "Family": "opensuse-leap", "Name": "15.1", @@ -53,7 +54,14 @@ "org.opensuse.reference": "registry.opensuse.org/opensuse/leap:15.1.3.67" } } - } + }, + "Layers": [ + { + "Size": 105899520, + "Digest": "sha256:5c5a844f54abd051851758624820ae6a08a9d6ddffddaebbb335601c32608fb3", + "DiffID": "sha256:f7f9ae80878a1c56d8f9ca977a5d844168f7afc0c1429feef9366e713eac06ff" + } + ] }, "Results": [ { diff --git a/integration/testdata/opensuse-tumbleweed.json.golden b/integration/testdata/opensuse-tumbleweed.json.golden index d8bfb9940ebc..8a3eb628b293 100644 --- a/integration/testdata/opensuse-tumbleweed.json.golden +++ b/integration/testdata/opensuse-tumbleweed.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/opensuse-tumbleweed.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 115281408, "OS": { "Family": "opensuse-tumbleweed", "Name": "20240607" @@ -56,7 +57,14 @@ "org.opensuse.reference": "registry.opensuse.org/opensuse/tumbleweed:20240607.30.45" } } - } + }, + "Layers": [ + { + "Size": 115281408, + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + } + ] }, "Results": [ { diff --git a/integration/testdata/oraclelinux-8.json.golden b/integration/testdata/oraclelinux-8.json.golden index 71a0411cba93..b12899b5e150 100644 --- a/integration/testdata/oraclelinux-8.json.golden +++ b/integration/testdata/oraclelinux-8.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/oraclelinux-8.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 416893952, "OS": { "Family": "oracle", "Name": "8.0" @@ -54,7 +55,14 @@ "Image": "sha256:d2f0ba2a964f3d0b1935be99979b6930f8b989217ff6a5e6d4093e9df9baee11", "ArgsEscaped": true } - } + }, + "Layers": [ + { + "Size": 416893952, + "Digest": "sha256:e1b9aa33b064e76023cc29e9fac51bcebe62740c92ed38f09ba6205ddd9aa6f4", + "DiffID": "sha256:91bac58a9ffae0dc2031e3f90d7bf04f66ccf019f180372152b0916d6e8a796f" + } + ] }, "Results": [ { diff --git a/integration/testdata/photon-30.json.golden b/integration/testdata/photon-30.json.golden index 08597fd526ce..1a43ad89f20d 100644 --- a/integration/testdata/photon-30.json.golden +++ b/integration/testdata/photon-30.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/photon-30.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 34946560, "OS": { "Family": "photon", "Name": "3.0" @@ -55,7 +56,14 @@ }, "ArgsEscaped": true } - } + }, + "Layers": [ + { + "Size": 34946560, + "Digest": "sha256:675aead3dff5e25094cb9f4d7cc64f05e9f04a3f3397d5d45bfbc1c8a99c3a73", + "DiffID": "sha256:0f379947a276b7b051643960392fa66c2f0cb493bc1dcd471abb5545005949fd" + } + ] }, "Results": [ { diff --git a/integration/testdata/rockylinux-8.json.golden b/integration/testdata/rockylinux-8.json.golden index f6e377a49ca0..1a8cd83bf600 100644 --- a/integration/testdata/rockylinux-8.json.golden +++ b/integration/testdata/rockylinux-8.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/rockylinux-8.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 211280384, "OS": { "Family": "rocky", "Name": "8.5" @@ -44,7 +45,14 @@ ], "Image": "sha256:b3d7893772a2427ad53224d9db4c70be399de0a28c09804ac0c5cb203ab0244e" } - } + }, + "Layers": [ + { + "Size": 211280384, + "Digest": "sha256:72a2451028f11c6927678e5f1bb8f35b4e723d3b342ec1a6980d7b5591cf81d6", + "DiffID": "sha256:65dbea0a4b39709e0a2cc8624fd99478e9f302c0a5661d7676d6d3bd3cb6d181" + } + ] }, "Results": [ { diff --git a/integration/testdata/sl-micro-rancher5.4.json.golden b/integration/testdata/sl-micro-rancher5.4.json.golden index 1ff0660a9b34..bdc24d1f1bd6 100644 --- a/integration/testdata/sl-micro-rancher5.4.json.golden +++ b/integration/testdata/sl-micro-rancher5.4.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/sle-micro-rancher-5.4_ndb.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 709748736, "OS": { "Family": "slem", "Name": "5.4" @@ -57,7 +58,14 @@ "org.suse.reference": "registry.suse.com/suse/sle-micro-rancher/5.4:%PKG_VERSION%-%RELEASE" } } - } + }, + "Layers": [ + { + "Size": 709748736, + "Digest": "sha256:832d9b8a90d142f11cf1b5d3f0b5d6c099be9b7af791b426138a4205b6ee78de", + "DiffID": "sha256:7cdd3aec849d122d63dc83a5e1e2fb89b341c67b03e25979131ca335a463bb57" + } + ] }, "Results": [ { diff --git a/integration/testdata/spring4shell-jre11.json.golden b/integration/testdata/spring4shell-jre11.json.golden index 927db3df8d96..6aaef4f15947 100644 --- a/integration/testdata/spring4shell-jre11.json.golden +++ b/integration/testdata/spring4shell-jre11.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/spring4shell-jre11.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 270418944, "OS": { "Family": "debian", "Name": "11.3" @@ -181,7 +182,54 @@ "8080/tcp": {} } } - } + }, + "Layers": [ + { + "Size": 83895296, + "Digest": "sha256:c229119241af7b23b121052a1cae4c03e0a477a72ea6a7f463ad7623ff8f274b", + "DiffID": "sha256:608f3a074261105f129d707e4d9ad3d41b5baa94887f092b7c2857f7274a2fce" + }, + { + "Size": 5177344, + "Digest": "sha256:2f1dc05f270bad654ee17f1143c48586c188a72929a128d61fd8ae15905d7b00", + "DiffID": "sha256:1f6e409d1c59c8e06608a024b82d50490313abc3b2ff93730e43135d5be0cd72" + }, + { + "Size": 3584, + "Digest": "sha256:39b2c24c052eb115ae98ac01ea7a403af9bd678866744f0eea033d71d18f893b", + "DiffID": "sha256:1f0e278ace87a84577de56c99e5c05c6af6f8b582d1eb8dfd7de7be4cf215775" + }, + { + "Size": 142896128, + "Digest": "sha256:e94fd7d3bf7a9b78b61be8303cd35eb9da3f8d121cf572a3b8878cbf11e84818", + "DiffID": "sha256:64272e9218cd019d57b84ac283aa35036cbd8c1dcface8c69f756088a0a13c45" + }, + { + "Size": 3072, + "Digest": "sha256:b7bcef2d90f7acb11e94822db33bc4011654954b978fa35c89acac5f7b8d9620", + "DiffID": "sha256:8e6776c643c1db15d540016171fe04137ee2a26c7d0b18bfebdcbd31c6b0d8b3" + }, + { + "Size": 18070016, + "Digest": "sha256:ac3639dc6fd33e9eeead58a99c277cb06b8f69ba6a30fe7028e9677a67d94bd8", + "DiffID": "sha256:0b201a611e5455d637c719d70eb5dd76fd4154bc4a5cf597d67ed2fb6647cc42" + }, + { + "Size": 1369088, + "Digest": "sha256:7b4e8e3a75a692d7c80924f540916787b858aac41b99d55e2786a6edb9520c19", + "DiffID": "sha256:19da2426772aaa344a242e474fd7906d272fc8ded6eef5b4e461a4aa0725d7e5" + }, + { + "Size": 2048, + "Digest": "sha256:9177f8e1f9863beb4f13bfeb344734617e6c30d09ccbbaa4ff2830c2874a3c99", + "DiffID": "sha256:1fdc094b0e85888d2204310083e3c09fff6a4daeecf22692aa6be5e8b4001f94" + }, + { + "Size": 19002368, + "Digest": "sha256:b47862f824700e0ea830e568e989fba777d8223c1f8321c6256b0c965b9f61ee", + "DiffID": "sha256:192960b65b1579403b36581de471fd2bd75a043b4743552f27ba16623f02c68f" + } + ] }, "Results": [ { diff --git a/integration/testdata/spring4shell-jre8.json.golden b/integration/testdata/spring4shell-jre8.json.golden index b41aa8878c5a..a7e176a5429d 100644 --- a/integration/testdata/spring4shell-jre8.json.golden +++ b/integration/testdata/spring4shell-jre8.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/spring4shell-jre8.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 236810240, "OS": { "Family": "debian", "Name": "11.3" @@ -181,7 +182,54 @@ "8080/tcp": {} } } - } + }, + "Layers": [ + { + "Size": 83895296, + "Digest": "sha256:c229119241af7b23b121052a1cae4c03e0a477a72ea6a7f463ad7623ff8f274b", + "DiffID": "sha256:608f3a074261105f129d707e4d9ad3d41b5baa94887f092b7c2857f7274a2fce" + }, + { + "Size": 5177344, + "Digest": "sha256:2f1dc05f270bad654ee17f1143c48586c188a72929a128d61fd8ae15905d7b00", + "DiffID": "sha256:1f6e409d1c59c8e06608a024b82d50490313abc3b2ff93730e43135d5be0cd72" + }, + { + "Size": 3584, + "Digest": "sha256:69e68111de204b0c46a897b6ac5dd87c1e8aafae5aca100c679358a7a00513a8", + "DiffID": "sha256:0e78b1e5673e8cc7c102fdda9e6b830b7dee2b29b178f34d25d9be59387e6950" + }, + { + "Size": 109289984, + "Digest": "sha256:d7b564a873af313eb2dbcb1ed0d393c57543e3666bdedcbe5d75841d72b1f791", + "DiffID": "sha256:ba40706eccba610401e4942e29f50bdf36807f8638942ce20805b359ae3ac1c1" + }, + { + "Size": 3072, + "Digest": "sha256:3a1b5fc9d258d3604588adb7cdea42ffb34c49f273d1e81dd7d94dab6dc3c1e9", + "DiffID": "sha256:053db4876c0df3df3294ee00e32e140b130ba33807d088750cb69b0e6fad158e" + }, + { + "Size": 18067968, + "Digest": "sha256:59c0978ccb117247fd40d936973c40df89195f60466118c5acc6a55f8ba29f06", + "DiffID": "sha256:85595543df2b1115a18284a8ef62d0b235c4bc29e3d33b55f89b54ee1eadf4c6" + }, + { + "Size": 1368576, + "Digest": "sha256:457ac2ac52ffcc6b352c6a8b314f7bc364bf1f407506e3337da06955597433cf", + "DiffID": "sha256:868d710aa4dc5fc4793508564fc45c991ed8d5f6ab3e4cf52bb856f29546f3d8" + }, + { + "Size": 2048, + "Digest": "sha256:c87852fd4f78fd9aa07b6dc3ca5e6292a466246036c517fa6abf1532da9be38f", + "DiffID": "sha256:77b2d158369254d5055183f5483f8b6661170857b61768d1d95d18c2ec1714b6" + }, + { + "Size": 19002368, + "Digest": "sha256:cc44af318e91e6f9f9bf73793fa4f0639487613f46aa1f819b02b6e8fb5c6c07", + "DiffID": "sha256:eb769943b91f10a0418f2fc3b4a4fde6c6293be60c37293fcc0fa319edaf27a5" + } + ] }, "Results": [ { diff --git a/integration/testdata/ubi-7-comprehensive.json.golden b/integration/testdata/ubi-7-comprehensive.json.golden index b500dc5d4bc5..4bb088fdd50f 100644 --- a/integration/testdata/ubi-7-comprehensive.json.golden +++ b/integration/testdata/ubi-7-comprehensive.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/ubi-7.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 215162880, "OS": { "Family": "redhat", "Name": "7.7" @@ -68,7 +69,19 @@ }, "ArgsEscaped": true } - } + }, + "Layers": [ + { + "Size": 215142400, + "Digest": "sha256:7b1c937e0f6794db2535be6e4cb6d60a0b668ef78c2576611a3fb9c97a95ccdf", + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + }, + { + "Size": 20480, + "Digest": "sha256:bff3b73cbcc496de1de4ea51df88b7249169d0b6eb7d677169eaf90b8a92240e", + "DiffID": "sha256:ecb0311889b3478bc9b62660fa9391d5ebf8da4c6ae143cb33434873668f9e36" + } + ] }, "Results": [ { diff --git a/integration/testdata/ubi-7.json.golden b/integration/testdata/ubi-7.json.golden index 41d78d2be351..a2797a54f034 100644 --- a/integration/testdata/ubi-7.json.golden +++ b/integration/testdata/ubi-7.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/ubi-7.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 215162880, "OS": { "Family": "redhat", "Name": "7.7" @@ -68,7 +69,19 @@ }, "ArgsEscaped": true } - } + }, + "Layers": [ + { + "Size": 215142400, + "Digest": "sha256:7b1c937e0f6794db2535be6e4cb6d60a0b668ef78c2576611a3fb9c97a95ccdf", + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + }, + { + "Size": 20480, + "Digest": "sha256:bff3b73cbcc496de1de4ea51df88b7249169d0b6eb7d677169eaf90b8a92240e", + "DiffID": "sha256:ecb0311889b3478bc9b62660fa9391d5ebf8da4c6ae143cb33434873668f9e36" + } + ] }, "Results": [ { diff --git a/integration/testdata/ubuntu-1804-ignore-unfixed.json.golden b/integration/testdata/ubuntu-1804-ignore-unfixed.json.golden index 5ec55a3fc397..da5f95b9f74a 100644 --- a/integration/testdata/ubuntu-1804-ignore-unfixed.json.golden +++ b/integration/testdata/ubuntu-1804-ignore-unfixed.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/ubuntu-1804.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 66571264, "OS": { "Family": "ubuntu", "Name": "18.04" @@ -63,7 +64,29 @@ "Image": "sha256:bcbe079849fdbb50b3eb04798547e046bdbc82020b8b780d767cf29f7e60b396", "ArgsEscaped": true } - } + }, + "Layers": [ + { + "Size": 65561088, + "Digest": "sha256:35c102085707f703de2d9eaad8752d6fe1b8f02b5d2149f1d8357c9cc7fb7d0a", + "DiffID": "sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Size": 991232, + "Digest": "sha256:251f5509d51d9e4119d4ffb70d4820f8e2d7dc72ad15df3ebd7cd755539e40fd", + "DiffID": "sha256:f7eae43028b334123c3a1d778f7bdf9783bbe651c8b15371df0120fd13ec35c5" + }, + { + "Size": 15872, + "Digest": "sha256:8e829fe70a46e3ac4334823560e98b257234c23629f19f05460e21a453091e6d", + "DiffID": "sha256:7beb13bce073c21c9ee608acb13c7e851845245dc76ce81b418fdf580c45076b" + }, + { + "Size": 3072, + "Digest": "sha256:6001e1789921cf851f6fb2e5fe05be70f482fe9c2286f66892fe5a3bc404569c", + "DiffID": "sha256:122be11ab4a29e554786b4a1ec4764dd55656b59d6228a0a3de78eaf5c1f226c" + } + ] }, "Results": [ { diff --git a/integration/testdata/ubuntu-1804.json.golden b/integration/testdata/ubuntu-1804.json.golden index 7ae275b7e5a6..59860bba306f 100644 --- a/integration/testdata/ubuntu-1804.json.golden +++ b/integration/testdata/ubuntu-1804.json.golden @@ -4,6 +4,7 @@ "ArtifactName": "testdata/fixtures/images/ubuntu-1804.tar.gz", "ArtifactType": "container_image", "Metadata": { + "Size": 66571264, "OS": { "Family": "ubuntu", "Name": "18.04" @@ -63,7 +64,29 @@ "Image": "sha256:bcbe079849fdbb50b3eb04798547e046bdbc82020b8b780d767cf29f7e60b396", "ArgsEscaped": true } - } + }, + "Layers": [ + { + "Size": 65561088, + "Digest": "sha256:35c102085707f703de2d9eaad8752d6fe1b8f02b5d2149f1d8357c9cc7fb7d0a", + "DiffID": "sha256:6cebf3abed5fac58d2e792ce8461454e92c245d5312c42118f02e231a73b317f" + }, + { + "Size": 991232, + "Digest": "sha256:251f5509d51d9e4119d4ffb70d4820f8e2d7dc72ad15df3ebd7cd755539e40fd", + "DiffID": "sha256:f7eae43028b334123c3a1d778f7bdf9783bbe651c8b15371df0120fd13ec35c5" + }, + { + "Size": 15872, + "Digest": "sha256:8e829fe70a46e3ac4334823560e98b257234c23629f19f05460e21a453091e6d", + "DiffID": "sha256:7beb13bce073c21c9ee608acb13c7e851845245dc76ce81b418fdf580c45076b" + }, + { + "Size": 3072, + "Digest": "sha256:6001e1789921cf851f6fb2e5fe05be70f482fe9c2286f66892fe5a3bc404569c", + "DiffID": "sha256:122be11ab4a29e554786b4a1ec4764dd55656b59d6228a0a3de78eaf5c1f226c" + } + ] }, "Results": [ { diff --git a/integration/testdata/yarn.json.golden b/integration/testdata/yarn.json.golden index c90131a428ed..4b587832eaae 100644 --- a/integration/testdata/yarn.json.golden +++ b/integration/testdata/yarn.json.golden @@ -21,6 +21,23 @@ "Class": "lang-pkgs", "Type": "yarn", "Packages": [ + { + "ID": "integration@1.0.0", + "Name": "integration", + "Identifier": { + "PURL": "pkg:npm/integration@1.0.0", + "UID": "830dfbb17accac93" + }, + "Version": "1.0.0", + "Licenses": [ + "MIT" + ], + "Relationship": "root", + "DependsOn": [ + "jquery@3.2.1" + ], + "Layer": {} + }, { "ID": "jquery@3.2.1", "Name": "jquery", diff --git a/internal/dbtest/db.go b/internal/dbtest/db.go index 7976a54e8b5f..b8cbf58196cf 100644 --- a/internal/dbtest/db.go +++ b/internal/dbtest/db.go @@ -21,7 +21,7 @@ func InitDB(t *testing.T, fixtureFiles []string) string { dbDir := db.Dir(cacheDir) dbPath := trivydb.Path(dbDir) - err := os.MkdirAll(dbDir, 0700) + err := os.MkdirAll(dbDir, 0o700) require.NoError(t, err) // Load testdata into BoltDB diff --git a/internal/hooktest/hook.go b/internal/hooktest/hook.go index 1a40c1b1d14a..c4b41fec6459 100644 --- a/internal/hooktest/hook.go +++ b/internal/hooktest/hook.go @@ -21,14 +21,14 @@ func (*testHook) Version() int { } // RunHook implementation -func (*testHook) PreRun(ctx context.Context, opts flag.Options) error { +func (*testHook) PreRun(_ context.Context, opts flag.Options) error { if opts.GlobalOptions.ConfigFile == "bad-config" { return errors.New("bad pre-run") } return nil } -func (*testHook) PostRun(ctx context.Context, opts flag.Options) error { +func (*testHook) PostRun(_ context.Context, opts flag.Options) error { if opts.GlobalOptions.ConfigFile == "bad-config" { return errors.New("bad post-run") } @@ -36,7 +36,7 @@ func (*testHook) PostRun(ctx context.Context, opts flag.Options) error { } // ScanHook implementation -func (*testHook) PreScan(ctx context.Context, target *types.ScanTarget, options types.ScanOptions) error { +func (*testHook) PreScan(_ context.Context, target *types.ScanTarget, _ types.ScanOptions) error { if target.Name == "bad-pre" { return errors.New("bad pre-scan") } @@ -44,7 +44,7 @@ func (*testHook) PreScan(ctx context.Context, target *types.ScanTarget, options return nil } -func (*testHook) PostScan(ctx context.Context, results types.Results) (types.Results, error) { +func (*testHook) PostScan(_ context.Context, results types.Results) (types.Results, error) { for i, r := range results { if r.Target == "bad" { return nil, errors.New("bad") @@ -59,7 +59,7 @@ func (*testHook) PostScan(ctx context.Context, results types.Results) (types.Res } // ReportHook implementation -func (*testHook) PreReport(ctx context.Context, report *types.Report, opts flag.Options) error { +func (*testHook) PreReport(_ context.Context, report *types.Report, _ flag.Options) error { if report.ArtifactName == "bad-report" { return errors.New("bad pre-report") } @@ -73,7 +73,7 @@ func (*testHook) PreReport(ctx context.Context, report *types.Report, opts flag. return nil } -func (*testHook) PostReport(ctx context.Context, report *types.Report, opts flag.Options) error { +func (*testHook) PostReport(_ context.Context, report *types.Report, _ flag.Options) error { if report.ArtifactName == "bad-report" { return errors.New("bad post-report") } diff --git a/internal/testutil/fs.go b/internal/testutil/fs.go index 842cf7042c55..1ed303933e7b 100644 --- a/internal/testutil/fs.go +++ b/internal/testutil/fs.go @@ -65,7 +65,7 @@ func MustReadYAML(t *testing.T, path string, out any) { } func MustMkdirAll(t *testing.T, dir string) { - err := os.MkdirAll(dir, 0750) + err := os.MkdirAll(dir, 0o750) require.NoError(t, err) } @@ -87,6 +87,6 @@ func MustWriteFile(t *testing.T, filePath string, content []byte) { dir := filepath.Dir(filePath) MustMkdirAll(t, dir) - err := os.WriteFile(filePath, content, 0600) + err := os.WriteFile(filePath, content, 0o600) require.NoError(t, err) } diff --git a/magefiles/fixture.go b/magefiles/fixture.go index 39e5bd0ddae2..b466c85977f5 100644 --- a/magefiles/fixture.go +++ b/magefiles/fixture.go @@ -23,7 +23,7 @@ var auth = crane.WithAuthFromKeychain(authn.NewMultiKeychain(authn.DefaultKeycha func fixtureContainerImages() error { var testImages = testutil.ImageName("", "", "") - if err := os.MkdirAll(dir, 0750); err != nil { + if err := os.MkdirAll(dir, 0o750); err != nil { return err } tags, err := crane.ListTags(testImages, auth) @@ -38,10 +38,7 @@ func fixtureContainerImages() error { } // Save trivy-test-images/containerd image - if err := saveImage("containerd", "latest"); err != nil { - return err - } - return nil + return saveImage("containerd", "latest") } func saveImage(subpath, tag string) error { @@ -65,10 +62,7 @@ func saveImage(subpath, tag string) error { if err = crane.Save(img, imgName, tarPath); err != nil { return err } - if err = sh.Run("gzip", tarPath); err != nil { - return err - } - return nil + return sh.Run("gzip", tarPath) } func fixtureVMImages() error { @@ -77,7 +71,7 @@ func fixtureVMImages() error { titleAnnotation = "org.opencontainers.image.title" dir = "integration/testdata/fixtures/vm-images/" ) - if err := os.MkdirAll(dir, 0750); err != nil { + if err := os.MkdirAll(dir, 0o750); err != nil { return err } tags, err := crane.ListTags(testVMImages, auth) diff --git a/magefiles/helm.go b/magefiles/helm.go index 443c57eed029..1d904fccd96e 100644 --- a/magefiles/helm.go +++ b/magefiles/helm.go @@ -22,6 +22,12 @@ func main() { log.Fatalf("could not determine Trivy version: %v", err) } + // Checkout the main branch to get the latest chart version, that was changed after the previous release + // It needs for correctly updating the chart version of patch releases + if err := sh.Run("git", "checkout", "main"); err != nil { + log.Fatalf("failed to run `git checkout main`: %w", err) + } + newHelmVersion, err := bumpHelmChart(chartFile, trivyVersion) if err != nil { log.Fatalf("could not bump Trivy version to %q: %v", trivyVersion, err) diff --git a/magefiles/helm_test.go b/magefiles/helm_test.go index f2e3233d2879..355dacf04980 100644 --- a/magefiles/helm_test.go +++ b/magefiles/helm_test.go @@ -78,7 +78,7 @@ keywords: - trivy - vulnerability ` - err = os.WriteFile(tempFile.Name(), []byte(content), 0644) + err = os.WriteFile(tempFile.Name(), []byte(content), 0o644) assert.NoError(t, err) newVersion, err := bumpHelmChart(tempFile.Name(), "0.55.1") diff --git a/magefiles/magefile.go b/magefiles/magefile.go index de706e0a8c6c..8dafe81e676c 100644 --- a/magefiles/magefile.go +++ b/magefiles/magefile.go @@ -43,12 +43,12 @@ func init() { } func version() (string, error) { - if ver, err := sh.Output("git", "describe", "--tags", "--always"); err != nil { + ver, err := sh.Output("git", "describe", "--tags", "--always") + if err != nil { return "", err - } else { - // Strips the v prefix from the tag - return strings.TrimPrefix(ver, "v"), nil } + // Strips the v prefix from the tag + return strings.TrimPrefix(ver, "v"), nil } func buildLdflags() (string, error) { @@ -79,7 +79,7 @@ func (Tool) PipTools() error { // GolangciLint installs golangci-lint func (t Tool) GolangciLint() error { - const version = "v1.64.2" + const version = "v2.1.2" bin := filepath.Join(GOBIN, "golangci-lint") if exists(bin) && t.matchGolangciLintVersion(bin, version) { return nil @@ -91,7 +91,7 @@ func (t Tool) GolangciLint() error { } func (Tool) matchGolangciLintVersion(bin, version string) bool { - out, err := sh.Output(bin, "version", "--format", "json") + out, err := sh.Output(bin, "version", "--json") if err != nil { slog.Error("Unable to get golangci-lint version", slog.Any("err", err)) return false @@ -189,19 +189,13 @@ func (Test) FixtureTerraformPlanSnapshots() error { // GenerateModules compiles WASM modules for unit tests func (Test) GenerateModules() error { pattern := filepath.Join("pkg", "module", "testdata", "*", "*.go") - if err := compileWasmModules(pattern); err != nil { - return err - } - return nil + return compileWasmModules(pattern) } // GenerateExampleModules compiles example Wasm modules for integration tests func (Test) GenerateExampleModules() error { pattern := filepath.Join("examples", "module", "*", "*.go") - if err := compileWasmModules(pattern); err != nil { - return err - } - return nil + return compileWasmModules(pattern) } // UpdateGolden updates golden files for integration tests diff --git a/magefiles/schema.go b/magefiles/schema.go index 6cbf8b950ad0..5b29487d4001 100644 --- a/magefiles/schema.go +++ b/magefiles/schema.go @@ -45,7 +45,7 @@ func GenSchema() error { if err != nil { return err } - if err := os.WriteFile(schemaPath, data, 0600); err != nil { + if err := os.WriteFile(schemaPath, data, 0o600); err != nil { return err } return nil diff --git a/mkdocs.yml b/mkdocs.yml index 45ccfbf3bddb..e84198a17a2c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -54,6 +54,7 @@ nav: - Vulnerability: docs/scanner/vulnerability.md - Misconfiguration: - Overview: docs/scanner/misconfiguration/index.md + - Configuration: docs/scanner/misconfiguration/config/config.md - Policy: - Built-in Checks: docs/scanner/misconfiguration/check/builtin.md - Custom Checks: @@ -75,6 +76,7 @@ nav: - Alpine Linux: docs/coverage/os/alpine.md - Amazon Linux: docs/coverage/os/amazon.md - Azure Linux (CBL-Mariner): docs/coverage/os/azure.md + - Bottlerocket: docs/coverage/os/bottlerocket.md - CentOS: docs/coverage/os/centos.md - Chainguard: docs/coverage/os/chainguard.md - Debian: docs/coverage/os/debian.md diff --git a/pkg/cache/fs.go b/pkg/cache/fs.go index edfac70b04e5..5b43a1105078 100644 --- a/pkg/cache/fs.go +++ b/pkg/cache/fs.go @@ -21,11 +21,11 @@ type FSCache struct { func NewFSCache(cacheDir string) (FSCache, error) { dir := filepath.Join(cacheDir, scanCacheDirName) - if err := os.MkdirAll(dir, 0700); err != nil { + if err := os.MkdirAll(dir, 0o700); err != nil { return FSCache{}, xerrors.Errorf("failed to create cache dir: %w", err) } - db, err := bolt.Open(filepath.Join(dir, "fanal.db"), 0600, nil) + db, err := bolt.Open(filepath.Join(dir, "fanal.db"), 0o600, nil) if err != nil { return FSCache{}, xerrors.Errorf("unable to open DB: %w", err) } diff --git a/pkg/cache/fs_test.go b/pkg/cache/fs_test.go index 819df36ee22f..3729a0763e47 100644 --- a/pkg/cache/fs_test.go +++ b/pkg/cache/fs_test.go @@ -20,7 +20,7 @@ func newTempDB(t *testing.T, dbPath string) (string, error) { dir := t.TempDir() if dbPath != "" { d := filepath.Join(dir, "fanal") - if err := os.MkdirAll(d, 0700); err != nil { + if err := os.MkdirAll(d, 0o700); err != nil { return "", err } @@ -142,11 +142,15 @@ func TestFSCache_PutBlob(t *testing.T) { Family: "alpine", Release: "3.10", }, + Size: 1000, + DiffID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", }, }, want: ` { "SchemaVersion": 1, + "Size": 1000, + "DiffID": "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", "OS": { "Family": "alpine", "Name": "3.10" @@ -164,8 +168,11 @@ func TestFSCache_PutBlob(t *testing.T) { diffID: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", layerInfo: types.BlobInfo{ SchemaVersion: 1, + Size: 1000, Digest: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", DiffID: "sha256:dab15cac9ebd43beceeeda3ce95c574d6714ed3d3969071caead678c065813ec", + OpaqueDirs: []string{"php-app/"}, + WhiteoutFiles: []string{"etc/foobar"}, OS: types.OS{ Family: "alpine", Name: "3.10", @@ -201,15 +208,11 @@ func TestFSCache_PutBlob(t *testing.T) { }, }, }, - OpaqueDirs: []string{"php-app/"}, - WhiteoutFiles: []string{"etc/foobar"}, }, }, want: ` { "SchemaVersion": 1, - "Digest": "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", - "DiffID": "sha256:dab15cac9ebd43beceeeda3ce95c574d6714ed3d3969071caead678c065813ec", "OS": { "Family": "alpine", "Name": "3.10" @@ -251,11 +254,14 @@ func TestFSCache_PutBlob(t *testing.T) { ] } ], + "Size": 1000, + "Digest": "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + "DiffID": "sha256:dab15cac9ebd43beceeeda3ce95c574d6714ed3d3969071caead678c065813ec", "OpaqueDirs": [ - "php-app/" + "php-app/" ], "WhiteoutFiles": [ - "etc/foobar" + "etc/foobar" ] }`, wantLayerID: "sha256:dab15cac9ebd43beceeeda3ce95c574d6714ed3d3969071caead678c065813ec", @@ -288,9 +294,8 @@ func TestFSCache_PutBlob(t *testing.T) { if tt.wantErr != "" { require.ErrorContains(t, err, tt.wantErr, tt.name) return - } else { - require.NoError(t, err, tt.name) } + require.NoError(t, err, tt.name) fs.db.View(func(tx *bolt.Tx) error { layerBucket := tx.Bucket([]byte(blobBucket)) @@ -367,9 +372,8 @@ func TestFSCache_PutArtifact(t *testing.T) { if tt.wantErr != "" { require.ErrorContains(t, err, tt.wantErr, tt.name) return - } else { - require.NoError(t, err, tt.name) } + require.NoError(t, err, tt.name) err = fs.db.View(func(tx *bolt.Tx) error { // check decompressedDigestBucket diff --git a/pkg/cache/key.go b/pkg/cache/key.go index 0ad0fde82fe1..3e006ee26ca4 100644 --- a/pkg/cache/key.go +++ b/pkg/cache/key.go @@ -16,7 +16,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/types" ) -func CalcKey(id string, analyzerVersions analyzer.Versions, hookVersions map[string]int, artifactOpt artifact.Option) (string, error) { +func CalcKey(id string, artifactVersion int, analyzerVersions analyzer.Versions, hookVersions map[string]int, artifactOpt artifact.Option) (string, error) { // Sort options for consistent results artifactOpt.Sort() artifactOpt.MisconfScannerOption.Sort() @@ -26,6 +26,7 @@ func CalcKey(id string, analyzerVersions analyzer.Versions, hookVersions map[str // Write ID, analyzer/handler versions, skipped files/dirs and file patterns keyBase := struct { ID string + ArtifactVersion int `json:",omitzero"` AnalyzerVersions analyzer.Versions HookVersions map[string]int SkipFiles []string @@ -34,6 +35,7 @@ func CalcKey(id string, analyzerVersions analyzer.Versions, hookVersions map[str DetectionPriority types.DetectionPriority `json:",omitempty"` }{ id, + artifactVersion, analyzerVersions, hookVersions, artifactOpt.WalkerOption.SkipFiles, diff --git a/pkg/cache/key_test.go b/pkg/cache/key_test.go index d80748567683..996cb5bbf940 100644 --- a/pkg/cache/key_test.go +++ b/pkg/cache/key_test.go @@ -16,6 +16,7 @@ import ( func TestCalcKey(t *testing.T) { type args struct { key string + artifactVersion int analyzerVersions analyzer.Versions hookVersions map[string]int skipFiles []string @@ -35,7 +36,8 @@ func TestCalcKey(t *testing.T) { { name: "happy path", args: args{ - key: "sha256:5c534be56eca62e756ef2ef51523feda0f19cd7c15bb0c015e3d6e3ae090bf6e", + key: "sha256:5c534be56eca62e756ef2ef51523feda0f19cd7c15bb0c015e3d6e3ae090bf6e", + artifactVersion: 1, analyzerVersions: analyzer.Versions{ Analyzers: map[string]int{ "alpine": 1, @@ -46,7 +48,7 @@ func TestCalcKey(t *testing.T) { "python-pkg": 1, }, }, - want: "sha256:c720b502991465ea11929cfefc71cf4b5aeaa9a8c0ae59fdaf597f957f5cdb18", + want: "sha256:9ac2cf39c18b5c936694820bc9c380dd8c74d33484ebd08183c5620215a1d33c", }, { name: "with disabled analyzer", @@ -273,7 +275,7 @@ func TestCalcKey(t *testing.T) { SkipDirs: tt.args.skipDirs, }, } - got, err := CalcKey(tt.args.key, tt.args.analyzerVersions, tt.args.hookVersions, artifactOpt) + got, err := CalcKey(tt.args.key, tt.args.artifactVersion, tt.args.analyzerVersions, tt.args.hookVersions, artifactOpt) if tt.wantErr != "" { assert.ErrorContains(t, err, tt.wantErr) return diff --git a/pkg/cache/memory.go b/pkg/cache/memory.go index d08ac5908d90..33b6a22b3f0a 100644 --- a/pkg/cache/memory.go +++ b/pkg/cache/memory.go @@ -100,7 +100,7 @@ func (c *MemoryCache) Clear() error { // BlobIDs returns all the blob IDs in the memory cache for testing func (c *MemoryCache) BlobIDs() []string { var blobIDs []string - c.blobs.Range(func(key, value any) bool { + c.blobs.Range(func(key, _ any) bool { blobID, ok := key.(string) if !ok { return false diff --git a/pkg/cache/remote_test.go b/pkg/cache/remote_test.go index 867878b99857..7c464c65938d 100644 --- a/pkg/cache/remote_test.go +++ b/pkg/cache/remote_test.go @@ -154,9 +154,8 @@ func TestRemoteCache_PutArtifact(t *testing.T) { if tt.wantErr != "" { require.ErrorContains(t, err, tt.wantErr, tt.name) return - } else { - require.NoError(t, err, tt.name) } + require.NoError(t, err, tt.name) }) } } @@ -218,9 +217,8 @@ func TestRemoteCache_PutBlob(t *testing.T) { if tt.wantErr != "" { require.ErrorContains(t, err, tt.wantErr, tt.name) return - } else { - require.NoError(t, err, tt.name) } + require.NoError(t, err, tt.name) }) } } @@ -299,10 +297,8 @@ func TestRemoteCache_MissingBlobs(t *testing.T) { if tt.wantErr != "" { require.ErrorContains(t, err, tt.wantErr, tt.name) return - } else { - require.NoError(t, err, tt.name) } - + require.NoError(t, err, tt.name) assert.Equal(t, tt.wantMissingImage, gotMissingImage) assert.Equal(t, tt.wantMissingLayerIDs, gotMissingLayerIDs) }) @@ -310,7 +306,7 @@ func TestRemoteCache_MissingBlobs(t *testing.T) { } func TestRemoteCache_PutArtifactInsecure(t *testing.T) { - ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) + ts := httptest.NewTLSServer(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {})) defer ts.Close() type args struct { diff --git a/pkg/cloud/aws/config/config.go b/pkg/cloud/aws/config/config.go index e173840cdaec..a4f45dd0edfa 100644 --- a/pkg/cloud/aws/config/config.go +++ b/pkg/cloud/aws/config/config.go @@ -9,7 +9,7 @@ import ( ) func EndpointResolver(endpoint string) aws.EndpointResolverWithOptionsFunc { - return func(_, reg string, options ...any) (aws.Endpoint, error) { + return func(_, reg string, _ ...any) (aws.Endpoint, error) { return aws.Endpoint{ PartitionID: "aws", URL: endpoint, diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 86e3428e5925..a48709510f51 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -211,28 +211,28 @@ func NewRootCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { return err } - globalOptions, err := globalFlags.ToOptions() + flags := flag.Flags{globalFlags} + opts, err := flags.ToOptions(args) if err != nil { return err } - // Initialize logger - log.InitLogger(globalOptions.Debug, globalOptions.Quiet) + log.InitLogger(opts.Debug, opts.Quiet) return nil }, RunE: func(cmd *cobra.Command, args []string) error { - globalOptions, err := globalFlags.ToOptions() + flags := flag.Flags{globalFlags} + opts, err := flags.ToOptions(args) if err != nil { return err } - if globalOptions.ShowVersion { + if opts.ShowVersion { // Customize version output - return showVersion(globalOptions.CacheDir, versionFormat, cmd.OutOrStdout()) - } else { - return cmd.Help() + return showVersion(opts.CacheDir, versionFormat, cmd.OutOrStdout()) } + return cmd.Help() }, } @@ -252,30 +252,33 @@ func NewImageCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { reportFlagGroup.ReportFormat = report compliance := flag.ComplianceFlag.Clone() - compliance.Values = []string{types.ComplianceDockerCIS160} + compliance.Usage = fmt.Sprintf("%s (built-in compliance's: %s)", compliance.Usage, types.ComplianceDockerCIS160) reportFlagGroup.Compliance = compliance // override usage as the accepted values differ for each subcommand. - imageFlags := &flag.Flags{ - GlobalFlagGroup: globalFlags, - CacheFlagGroup: flag.NewCacheFlagGroup(), - DBFlagGroup: flag.NewDBFlagGroup(), - ImageFlagGroup: flag.NewImageFlagGroup(), // container image specific - LicenseFlagGroup: flag.NewLicenseFlagGroup(), - MisconfFlagGroup: flag.NewMisconfFlagGroup(), - ModuleFlagGroup: flag.NewModuleFlagGroup(), - PackageFlagGroup: flag.NewPackageFlagGroup(), - RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode - RegistryFlagGroup: flag.NewRegistryFlagGroup(), - RegoFlagGroup: flag.NewRegoFlagGroup(), - ReportFlagGroup: reportFlagGroup, - ScanFlagGroup: flag.NewScanFlagGroup(), - SecretFlagGroup: flag.NewSecretFlagGroup(), - VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), - } + packageFlagGroup := flag.NewPackageFlagGroup() + packageFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps' + + misconfFlagGroup := flag.NewMisconfFlagGroup() + misconfFlagGroup.CloudformationParamVars = nil // disable '--cf-params' + misconfFlagGroup.TerraformTFVars = nil // disable '--tf-vars' - imageFlags.PackageFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps' - imageFlags.MisconfFlagGroup.CloudformationParamVars = nil // disable '--cf-params' - imageFlags.MisconfFlagGroup.TerraformTFVars = nil // disable '--tf-vars' + imageFlags := flag.Flags{ + globalFlags, + flag.NewCacheFlagGroup(), + flag.NewDBFlagGroup(), + flag.NewImageFlagGroup(), // container image specific flags + flag.NewLicenseFlagGroup(), + misconfFlagGroup, + flag.NewModuleFlagGroup(), + packageFlagGroup, + flag.NewClientFlags(), + flag.NewRegistryFlagGroup(), + flag.NewRegoFlagGroup(), + reportFlagGroup, + flag.NewScanFlagGroup(), + flag.NewSecretFlagGroup(), + flag.NewVulnerabilityFlagGroup(), + } cmd := &cobra.Command{ Use: "image [flags] IMAGE_NAME", @@ -335,26 +338,29 @@ func NewImageCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { } func NewFilesystemCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { - fsFlags := &flag.Flags{ - GlobalFlagGroup: globalFlags, - CacheFlagGroup: flag.NewCacheFlagGroup(), - DBFlagGroup: flag.NewDBFlagGroup(), - LicenseFlagGroup: flag.NewLicenseFlagGroup(), - MisconfFlagGroup: flag.NewMisconfFlagGroup(), - ModuleFlagGroup: flag.NewModuleFlagGroup(), - PackageFlagGroup: flag.NewPackageFlagGroup(), - RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode - RegistryFlagGroup: flag.NewRegistryFlagGroup(), - RegoFlagGroup: flag.NewRegoFlagGroup(), - ReportFlagGroup: flag.NewReportFlagGroup(), - ScanFlagGroup: flag.NewScanFlagGroup(), - SecretFlagGroup: flag.NewSecretFlagGroup(), - VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), - } + cacheFlagGroup := flag.NewCacheFlagGroup() + cacheFlagGroup.CacheBackend.Default = string(cache.TypeMemory) // Use memory cache by default - fsFlags.CacheFlagGroup.CacheBackend.Default = string(cache.TypeMemory) // Use memory cache by default - fsFlags.ReportFlagGroup.ReportFormat.Usage = "specify a compliance report format for the output" // @TODO: support --report summary for non compliance reports - fsFlags.ReportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol' + reportFlagGroup := flag.NewReportFlagGroup() + reportFlagGroup.ReportFormat.Usage = "specify a compliance report format for the output" // @TODO: support --report summary for non compliance reports + reportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol' + + fsFlags := flag.Flags{ + globalFlags, + cacheFlagGroup, + flag.NewDBFlagGroup(), + flag.NewLicenseFlagGroup(), + flag.NewMisconfFlagGroup(), + flag.NewModuleFlagGroup(), + flag.NewPackageFlagGroup(), + flag.NewClientFlags(), // for client/server mode + flag.NewRegistryFlagGroup(), + flag.NewRegoFlagGroup(), + reportFlagGroup, + flag.NewScanFlagGroup(), + flag.NewSecretFlagGroup(), + flag.NewVulnerabilityFlagGroup(), + } cmd := &cobra.Command{ Use: "filesystem [flags] PATH", @@ -394,27 +400,33 @@ func NewFilesystemCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { } func NewRootfsCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { - rootfsFlags := &flag.Flags{ - GlobalFlagGroup: globalFlags, - CacheFlagGroup: flag.NewCacheFlagGroup(), - DBFlagGroup: flag.NewDBFlagGroup(), - LicenseFlagGroup: flag.NewLicenseFlagGroup(), - MisconfFlagGroup: flag.NewMisconfFlagGroup(), - ModuleFlagGroup: flag.NewModuleFlagGroup(), - PackageFlagGroup: flag.NewPackageFlagGroup(), - RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode - RegistryFlagGroup: flag.NewRegistryFlagGroup(), - RegoFlagGroup: flag.NewRegoFlagGroup(), - ReportFlagGroup: flag.NewReportFlagGroup(), - ScanFlagGroup: flag.NewScanFlagGroup(), - SecretFlagGroup: flag.NewSecretFlagGroup(), - VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), + reportFlagGroup := flag.NewReportFlagGroup() + reportFlagGroup.ReportFormat = nil // TODO: support --report summary + reportFlagGroup.Compliance = nil // disable '--compliance' + reportFlagGroup.ReportFormat = nil // disable '--report' + + packageFlagGroup := flag.NewPackageFlagGroup() + packageFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps' + + cacheFlagGroup := flag.NewCacheFlagGroup() + cacheFlagGroup.CacheBackend.Default = string(cache.TypeMemory) // Use memory cache by default + + rootfsFlags := flag.Flags{ + globalFlags, + cacheFlagGroup, + flag.NewDBFlagGroup(), + flag.NewLicenseFlagGroup(), + flag.NewMisconfFlagGroup(), + flag.NewModuleFlagGroup(), + packageFlagGroup, + flag.NewClientFlags(), // for client/server mode + flag.NewRegistryFlagGroup(), + flag.NewRegoFlagGroup(), + reportFlagGroup, + flag.NewScanFlagGroup(), + flag.NewSecretFlagGroup(), + flag.NewVulnerabilityFlagGroup(), } - rootfsFlags.ReportFlagGroup.ReportFormat = nil // TODO: support --report summary - rootfsFlags.ReportFlagGroup.Compliance = nil // disable '--compliance' - rootfsFlags.ReportFlagGroup.ReportFormat = nil // disable '--report' - rootfsFlags.PackageFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps' - rootfsFlags.CacheFlagGroup.CacheBackend.Default = string(cache.TypeMemory) // Use memory cache by default cmd := &cobra.Command{ Use: "rootfs [flags] ROOTDIR", @@ -455,28 +467,31 @@ func NewRootfsCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { } func NewRepositoryCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { - repoFlags := &flag.Flags{ - GlobalFlagGroup: globalFlags, - CacheFlagGroup: flag.NewCacheFlagGroup(), - DBFlagGroup: flag.NewDBFlagGroup(), - LicenseFlagGroup: flag.NewLicenseFlagGroup(), - MisconfFlagGroup: flag.NewMisconfFlagGroup(), - ModuleFlagGroup: flag.NewModuleFlagGroup(), - PackageFlagGroup: flag.NewPackageFlagGroup(), - RegistryFlagGroup: flag.NewRegistryFlagGroup(), - RegoFlagGroup: flag.NewRegoFlagGroup(), - RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode - ReportFlagGroup: flag.NewReportFlagGroup(), - ScanFlagGroup: flag.NewScanFlagGroup(), - SecretFlagGroup: flag.NewSecretFlagGroup(), - VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), - RepoFlagGroup: flag.NewRepoFlagGroup(), - } - repoFlags.ReportFlagGroup.ReportFormat = nil // TODO: support --report summary - repoFlags.ReportFlagGroup.Compliance = nil // disable '--compliance' - repoFlags.ReportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol' + reportFlagGroup := flag.NewReportFlagGroup() + reportFlagGroup.ReportFormat = nil // TODO: support --report summary + reportFlagGroup.Compliance = nil // disable '--compliance' + reportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol' - repoFlags.ScanFlagGroup.DistroFlag = nil // `repo` subcommand doesn't support scanning OS packages, so we can disable `--distro` + scanFlagGroup := flag.NewScanFlagGroup() + scanFlagGroup.DistroFlag = nil // repo subcommand doesn't support scanning OS packages + + repoFlags := flag.Flags{ + globalFlags, + flag.NewCacheFlagGroup(), + flag.NewDBFlagGroup(), + flag.NewLicenseFlagGroup(), + flag.NewMisconfFlagGroup(), + flag.NewModuleFlagGroup(), + flag.NewPackageFlagGroup(), + flag.NewRegistryFlagGroup(), + flag.NewRegoFlagGroup(), + flag.NewClientFlags(), // for client/server mode + reportFlagGroup, + scanFlagGroup, + flag.NewSecretFlagGroup(), + flag.NewVulnerabilityFlagGroup(), + flag.NewRepoFlagGroup(), + } cmd := &cobra.Command{ Use: "repository [flags] (REPO_PATH | REPO_URL)", @@ -514,18 +529,20 @@ func NewRepositoryCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { } func NewConvertCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { - convertFlags := &flag.Flags{ - GlobalFlagGroup: globalFlags, - ScanFlagGroup: &flag.ScanFlagGroup{}, - ReportFlagGroup: flag.NewReportFlagGroup(), - } - // To display the summary table, we need to enable scanners (to build columns). // We can't get scanner information from the report (we don't include empty licenses and secrets in the report). // So we need to ask the user to configure scanners (if needed). - convertFlags.ScanFlagGroup.Scanners = flag.ScannersFlag.Clone() - convertFlags.ScanFlagGroup.Scanners.Default = nil // disable default scanners - convertFlags.ScanFlagGroup.Scanners.Usage = "List of scanners included when generating the json report. Used only for rendering the summary table." + scanFlagGroup := &flag.ScanFlagGroup{ + Scanners: flag.ScannersFlag.Clone(), + } + scanFlagGroup.Scanners.Default = nil // disable default scanners + scanFlagGroup.Scanners.Usage = "List of scanners included when generating the json report. Used only for rendering the summary table." + + convertFlags := flag.Flags{ + globalFlags, + scanFlagGroup, + flag.NewReportFlagGroup(), + } cmd := &cobra.Command{ Use: "convert [flags] RESULT_JSON", @@ -575,17 +592,17 @@ func NewClientCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { } remoteFlags.ServerAddr = &remoteAddr // disable '--server' and enable '--remote' instead. - clientFlags := &flag.Flags{ - GlobalFlagGroup: globalFlags, - CacheFlagGroup: flag.NewCacheFlagGroup(), - DBFlagGroup: flag.NewDBFlagGroup(), - MisconfFlagGroup: flag.NewMisconfFlagGroup(), - RegistryFlagGroup: flag.NewRegistryFlagGroup(), - RegoFlagGroup: flag.NewRegoFlagGroup(), - RemoteFlagGroup: remoteFlags, - ReportFlagGroup: flag.NewReportFlagGroup(), - ScanFlagGroup: flag.NewScanFlagGroup(), - VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), + clientFlags := flag.Flags{ + globalFlags, + flag.NewCacheFlagGroup(), + flag.NewDBFlagGroup(), + flag.NewMisconfFlagGroup(), + flag.NewRegistryFlagGroup(), + flag.NewRegoFlagGroup(), + remoteFlags, + flag.NewReportFlagGroup(), + flag.NewScanFlagGroup(), + flag.NewVulnerabilityFlagGroup(), } cmd := &cobra.Command{ @@ -621,20 +638,21 @@ func NewClientCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { } func NewServerCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { - serverFlags := &flag.Flags{ - GlobalFlagGroup: globalFlags, - CacheFlagGroup: flag.NewCacheFlagGroup(), - DBFlagGroup: flag.NewDBFlagGroup(), - ModuleFlagGroup: flag.NewModuleFlagGroup(), - RemoteFlagGroup: flag.NewServerFlags(), - RegistryFlagGroup: flag.NewRegistryFlagGroup(), + // The Java DB is not needed for the server mode + dbFlagGroup := flag.NewDBFlagGroup() + dbFlagGroup.DownloadJavaDBOnly = nil // disable '--download-java-db-only' + dbFlagGroup.SkipJavaDBUpdate = nil // disable '--skip-java-db-update' + dbFlagGroup.JavaDBRepositories = nil // disable '--java-db-repository' + + serverFlags := flag.Flags{ + globalFlags, + flag.NewCacheFlagGroup(), + dbFlagGroup, + flag.NewModuleFlagGroup(), + flag.NewServerFlags(), + flag.NewRegistryFlagGroup(), } - // java-db only works on client side. - serverFlags.DBFlagGroup.DownloadJavaDBOnly = nil // disable '--download-java-db-only' - serverFlags.DBFlagGroup.SkipJavaDBUpdate = nil // disable '--skip-java-db-update' - serverFlags.DBFlagGroup.JavaDBRepositories = nil // disable '--java-db-repository' - cmd := &cobra.Command{ Use: "server [flags]", Aliases: []string{"s"}, @@ -675,28 +693,31 @@ func NewConfigCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { FilePatterns: flag.FilePatternsFlag.Clone(), } + reportFlagGroup := flag.NewReportFlagGroup() + reportFlagGroup.DependencyTree = nil // disable '--dependency-tree' + reportFlagGroup.ListAllPkgs = nil // disable '--list-all-pkgs' + reportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol' + reportFlagGroup.ShowSuppressed = nil // disable '--show-suppressed' + reportFlagGroup.ReportFormat.Usage = "specify a compliance report format for the output" // @TODO: support --report summary for non compliance reports + + cacheFlagGroup := flag.NewCacheFlagGroup() + cacheFlagGroup.CacheBackend.Default = string(cache.TypeMemory) + configFlags := &flag.Flags{ - GlobalFlagGroup: globalFlags, - CacheFlagGroup: flag.NewCacheFlagGroup(), - MisconfFlagGroup: flag.NewMisconfFlagGroup(), - ModuleFlagGroup: flag.NewModuleFlagGroup(), - RegistryFlagGroup: flag.NewRegistryFlagGroup(), - RegoFlagGroup: flag.NewRegoFlagGroup(), - K8sFlagGroup: &flag.K8sFlagGroup{ - // disable unneeded flags + globalFlags, + cacheFlagGroup, + flag.NewMisconfFlagGroup(), + flag.NewModuleFlagGroup(), + flag.NewRegistryFlagGroup(), + flag.NewRegoFlagGroup(), + &flag.K8sFlagGroup{ + // Keep only --k8s-version flag and disable others K8sVersion: flag.K8sVersionFlag.Clone(), }, - ReportFlagGroup: flag.NewReportFlagGroup(), - ScanFlagGroup: scanFlags, + reportFlagGroup, + scanFlags, } - configFlags.ReportFlagGroup.DependencyTree = nil // disable '--dependency-tree' - configFlags.ReportFlagGroup.ListAllPkgs = nil // disable '--list-all-pkgs' - configFlags.ReportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol' - configFlags.ReportFlagGroup.ShowSuppressed = nil // disable '--show-suppressed' - configFlags.ReportFlagGroup.ReportFormat.Usage = "specify a compliance report format for the output" // @TODO: support --report summary for non compliance reports - configFlags.CacheFlagGroup.CacheBackend.Default = string(cache.TypeMemory) - cmd := &cobra.Command{ Use: "config [flags] DIR", Aliases: []string{"conf"}, @@ -738,7 +759,7 @@ func NewConfigCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { func NewPluginCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { var pluginOptions flag.Options pluginFlags := &flag.Flags{ - GlobalFlagGroup: globalFlags, + globalFlags, } cmd := &cobra.Command{ Use: "plugin subcommand", @@ -747,7 +768,8 @@ func NewPluginCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { Short: "Manage plugins", SilenceErrors: true, SilenceUsage: true, - PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { + PersistentPreRunE: func(_ *cobra.Command, args []string) error { + var err error pluginOptions, err = pluginFlags.ToOptions(args) if err != nil { return err @@ -802,7 +824,7 @@ func NewPluginCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { SilenceUsage: true, Short: "List installed plugin", Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { if err := plugin.List(cmd.Context()); err != nil { return xerrors.Errorf("plugin list display error: %w", err) } @@ -887,8 +909,8 @@ func NewPluginCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { func NewModuleCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { moduleFlags := &flag.Flags{ - GlobalFlagGroup: globalFlags, - ModuleFlagGroup: flag.NewModuleFlagGroup(), + globalFlags, + flag.NewModuleFlagGroup(), } cmd := &cobra.Command{ @@ -907,7 +929,7 @@ func NewModuleCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { Aliases: []string{"i"}, Short: "Install a module", Args: cobra.ExactArgs(1), - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if err := moduleFlags.Bind(cmd); err != nil { return xerrors.Errorf("flag bind error: %w", err) } @@ -931,7 +953,7 @@ func NewModuleCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { Aliases: []string{"u"}, Short: "Uninstall a module", Args: cobra.ExactArgs(1), - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if err := moduleFlags.Bind(cmd); err != nil { return xerrors.Errorf("flag bind error: %w", err) } @@ -973,18 +995,15 @@ func NewKubernetesCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { imageFlags := &flag.ImageFlagGroup{ImageSources: flag.SourceFlag.Clone()} reportFlagGroup := flag.NewReportFlagGroup() + reportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol' + reportFlagGroup.TableMode = nil // disable '--table-mode's compliance := flag.ComplianceFlag.Clone() - compliance.Values = []string{ - types.ComplianceK8sNsa10, - types.ComplianceK8sCIS123, - types.ComplianceEksCIS14, - types.ComplianceRke2CIS124, - types.ComplianceK8sPSSBaseline01, - types.ComplianceK8sPSSRestricted01, + var compliances string + for _, val := range types.BuiltInK8sCompiances { + compliances += fmt.Sprintf("\n - %s", val) } + compliance.Usage = fmt.Sprintf("%s\nBuilt-in compliance's:%s", compliance.Usage, compliances) reportFlagGroup.Compliance = compliance // override usage as the accepted values differ for each subcommand. - reportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol' - reportFlagGroup.TableMode = nil // disable '--table-mode' formatFlag := flag.FormatFlag.Clone() formatFlag.Values = xstrings.ToStringSlice([]types.Format{ @@ -998,22 +1017,24 @@ func NewKubernetesCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { misconfFlagGroup.CloudformationParamVars = nil // disable '--cf-params' misconfFlagGroup.TerraformTFVars = nil // disable '--tf-vars' + packageFlagGroup := flag.NewPackageFlagGroup() + packageFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps' + k8sFlags := &flag.Flags{ - GlobalFlagGroup: globalFlags, - CacheFlagGroup: flag.NewCacheFlagGroup(), - DBFlagGroup: flag.NewDBFlagGroup(), - ImageFlagGroup: imageFlags, - K8sFlagGroup: flag.NewK8sFlagGroup(), // kubernetes-specific flags - MisconfFlagGroup: misconfFlagGroup, - PackageFlagGroup: flag.NewPackageFlagGroup(), - RegoFlagGroup: flag.NewRegoFlagGroup(), - ReportFlagGroup: reportFlagGroup, - ScanFlagGroup: scanFlags, - SecretFlagGroup: flag.NewSecretFlagGroup(), - RegistryFlagGroup: flag.NewRegistryFlagGroup(), - VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), + globalFlags, + flag.NewCacheFlagGroup(), + flag.NewDBFlagGroup(), + imageFlags, + flag.NewK8sFlagGroup(), // kubernetes-specific flags + misconfFlagGroup, + packageFlagGroup, + flag.NewRegoFlagGroup(), + reportFlagGroup, + scanFlags, + flag.NewSecretFlagGroup(), + flag.NewRegistryFlagGroup(), + flag.NewVulnerabilityFlagGroup(), } - k8sFlags.PackageFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps' cmd := &cobra.Command{ Use: "kubernetes [flags] [CONTEXT]", @@ -1060,19 +1081,29 @@ func NewKubernetesCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { } func NewVMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { + reportFlagGroup := flag.NewReportFlagGroup() + reportFlagGroup.ReportFormat = nil // disable '--report' + + packageFlagGroup := flag.NewPackageFlagGroup() + packageFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps' + + misconfFlagGroup := flag.NewMisconfFlagGroup() + misconfFlagGroup.CloudformationParamVars = nil // disable '--cf-params' + misconfFlagGroup.TerraformTFVars = nil // disable '--tf-vars' + vmFlags := &flag.Flags{ - GlobalFlagGroup: globalFlags, - CacheFlagGroup: flag.NewCacheFlagGroup(), - DBFlagGroup: flag.NewDBFlagGroup(), - MisconfFlagGroup: flag.NewMisconfFlagGroup(), - ModuleFlagGroup: flag.NewModuleFlagGroup(), - PackageFlagGroup: flag.NewPackageFlagGroup(), - RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode - ReportFlagGroup: flag.NewReportFlagGroup(), - ScanFlagGroup: flag.NewScanFlagGroup(), - SecretFlagGroup: flag.NewSecretFlagGroup(), - VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), - AWSFlagGroup: &flag.AWSFlagGroup{ + globalFlags, + flag.NewCacheFlagGroup(), + flag.NewDBFlagGroup(), + misconfFlagGroup, + flag.NewModuleFlagGroup(), + packageFlagGroup, + flag.NewClientFlags(), // for client/server mode + reportFlagGroup, + flag.NewScanFlagGroup(), + flag.NewSecretFlagGroup(), + flag.NewVulnerabilityFlagGroup(), + &flag.AWSFlagGroup{ Region: &flag.Flag[string]{ Name: "aws-region", ConfigName: "aws.region", @@ -1080,10 +1111,6 @@ func NewVMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { }, }, } - vmFlags.ReportFlagGroup.ReportFormat = nil // disable '--report' - vmFlags.PackageFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps' - vmFlags.MisconfFlagGroup.CloudformationParamVars = nil // disable '--cf-params' - vmFlags.MisconfFlagGroup.TerraformTFVars = nil // disable '--tf-vars' cmd := &cobra.Command{ Use: "vm [flags] VM_IMAGE", @@ -1142,28 +1169,33 @@ func NewSBOMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { scanFlagGroup := flag.NewScanFlagGroup() scanFlagGroup.Scanners = scanners // allow only 'vuln' and 'license' options for '--scanners' scanFlagGroup.Parallel = nil // disable '--parallel' + scanFlagGroup.SkipFiles = nil // disable `--skip-files` since `sbom` command only supports scanning one file. + scanFlagGroup.SkipDirs = nil // disable `--skip-dirs` since `sbom` command only supports scanning one file. licenseFlagGroup := flag.NewLicenseFlagGroup() // License full-scan and confidence-level are for file content only licenseFlagGroup.LicenseFull = nil licenseFlagGroup.LicenseConfidenceLevel = nil + cacheFlagGroup := flag.NewCacheFlagGroup() + cacheFlagGroup.CacheBackend.Default = string(cache.TypeMemory) // Use memory cache by default + + packageFlagGroup := flag.NewPackageFlagGroup() + packageFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps' + sbomFlags := &flag.Flags{ - GlobalFlagGroup: globalFlags, - CacheFlagGroup: flag.NewCacheFlagGroup(), - DBFlagGroup: flag.NewDBFlagGroup(), - PackageFlagGroup: flag.NewPackageFlagGroup(), - RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode - RegistryFlagGroup: flag.NewRegistryFlagGroup(), // for DBs in private registries - ReportFlagGroup: reportFlagGroup, - ScanFlagGroup: scanFlagGroup, - VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), - LicenseFlagGroup: licenseFlagGroup, + globalFlags, + cacheFlagGroup, + flag.NewDBFlagGroup(), + packageFlagGroup, + flag.NewClientFlags(), // for client/server mode + flag.NewRegistryFlagGroup(), // for DBs in private registries + reportFlagGroup, + scanFlagGroup, + flag.NewVulnerabilityFlagGroup(), + licenseFlagGroup, } - sbomFlags.CacheFlagGroup.CacheBackend.Default = string(cache.TypeMemory) // Use memory cache by default - sbomFlags.PackageFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps' - cmd := &cobra.Command{ Use: "sbom [flags] SBOM_PATH", Short: "Scan SBOM for vulnerabilities and licenses", @@ -1203,8 +1235,8 @@ func NewSBOMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { func NewCleanCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { cleanFlags := &flag.Flags{ - GlobalFlagGroup: globalFlags, - CleanFlagGroup: flag.NewCleanFlagGroup(), + globalFlags, + flag.NewCleanFlagGroup(), } cmd := &cobra.Command{ Use: "clean [flags]", @@ -1219,7 +1251,7 @@ func NewCleanCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { # Remove vulnerability database $ trivy clean --vuln-db `, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if err := cleanFlags.Bind(cmd); err != nil { return xerrors.Errorf("flag bind error: %w", err) } @@ -1252,11 +1284,13 @@ func NewRegistryCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { SilenceUsage: true, } + registryFlagGroup := flag.NewRegistryFlagGroup() + registryFlagGroup.RegistryToken = nil + loginFlags := &flag.Flags{ - GlobalFlagGroup: globalFlags, - RegistryFlagGroup: flag.NewRegistryFlagGroup(), + globalFlags, + registryFlagGroup, } - loginFlags.RegistryFlagGroup.RegistryToken = nil // disable '--registry-token' loginCmd := &cobra.Command{ Use: "login SERVER", Short: "Log in to a registry", @@ -1265,7 +1299,7 @@ func NewRegistryCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { Example: ` # Log in to reg.example.com cat ~/my_password.txt | trivy registry login --username foo --password-stdin reg.example.com`, Args: cobra.ExactArgs(1), - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(cmd *cobra.Command, _ []string) error { if err := loginFlags.Bind(cmd); err != nil { return xerrors.Errorf("flag bind error: %w", err) } @@ -1300,23 +1334,25 @@ func NewRegistryCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { } func NewVEXCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { - vexFlags := &flag.Flags{ - GlobalFlagGroup: globalFlags, + vexFlags := flag.Flags{ + globalFlags, } var vexOptions flag.Options + cmd := &cobra.Command{ Use: "vex subcommand", GroupID: groupManagement, Short: "[EXPERIMENTAL] VEX utilities", SilenceErrors: true, SilenceUsage: true, - PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { cmd.SetContext(log.WithContextPrefix(cmd.Context(), "vex")) - vexOptions, err = vexFlags.ToOptions(args) + opts, err := vexFlags.ToOptions(args) if err != nil { return err } + vexOptions = opts return nil }, } @@ -1344,7 +1380,7 @@ func NewVEXCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { SilenceErrors: true, SilenceUsage: true, Args: cobra.ExactArgs(0), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { if err := vexrepo.NewManager(vexOptions.CacheDir).Init(cmd.Context()); err != nil { return xerrors.Errorf("config init error: %w", err) } @@ -1357,7 +1393,7 @@ func NewVEXCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { SilenceErrors: true, SilenceUsage: true, Args: cobra.ExactArgs(0), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { if err := vexrepo.NewManager(vexOptions.CacheDir).List(cmd.Context()); err != nil { return xerrors.Errorf("list error: %w", err) } @@ -1393,11 +1429,12 @@ func NewVersionCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { GroupID: groupUtility, Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - options, err := globalFlags.ToOptions() + flags := flag.Flags{globalFlags} + opts, err := flags.ToOptions(args) if err != nil { return err } - return showVersion(options.CacheDir, versionFormat, cmd.OutOrStdout()) + return showVersion(opts.CacheDir, versionFormat, cmd.OutOrStdout()) }, SilenceErrors: true, SilenceUsage: true, diff --git a/pkg/commands/app_test.go b/pkg/commands/app_test.go index 308732ce918b..52f03d2c8618 100644 --- a/pkg/commands/app_test.go +++ b/pkg/commands/app_test.go @@ -315,9 +315,9 @@ func TestFlags(t *testing.T) { rootCmd.SetOut(io.Discard) flags := &flag.Flags{ - GlobalFlagGroup: globalFlags, - ReportFlagGroup: flag.NewReportFlagGroup(), - ScanFlagGroup: flag.NewScanFlagGroup(), + globalFlags, + flag.NewReportFlagGroup(), + flag.NewScanFlagGroup(), } cmd := &cobra.Command{ Use: "test", diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index c688ed5781b5..c99c5ae19cb9 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -703,6 +703,7 @@ func initMisconfScannerOption(ctx context.Context, opts flag.Options) (misconf.S DisableEmbeddedLibraries: disableEmbedded, IncludeDeprecatedChecks: opts.IncludeDeprecatedChecks, TfExcludeDownloaded: opts.TfExcludeDownloaded, + RawConfigScanners: opts.RawConfigScanners, FilePatterns: opts.FilePatterns, ConfigFileSchemas: configSchemas, SkipFiles: opts.SkipFiles, diff --git a/pkg/commands/auth/run_test.go b/pkg/commands/auth/run_test.go index 37495eca23c2..5b9ea3a0b051 100644 --- a/pkg/commands/auth/run_test.go +++ b/pkg/commands/auth/run_test.go @@ -120,7 +120,7 @@ func TestLogout(t *testing.T) { t.Run("success", func(t *testing.T) { configFile := filepath.Join(tmpDir, "config.json") - err := os.WriteFile(configFile, []byte(`{"auths": {"auth.test": {"auth": "dXNlcjpwYXNz"}}}`), 0600) + err := os.WriteFile(configFile, []byte(`{"auths": {"auth.test": {"auth": "dXNlcjpwYXNz"}}}`), 0o600) require.NoError(t, err) err = auth.Logout(t.Context(), "auth.test") diff --git a/pkg/commands/clean/run_test.go b/pkg/commands/clean/run_test.go index 4f1c26bc4c88..84080a88d9b6 100644 --- a/pkg/commands/clean/run_test.go +++ b/pkg/commands/clean/run_test.go @@ -152,11 +152,11 @@ func createTestFiles(t *testing.T, dir string) { "vex", } for _, subdir := range subdirs { - err := os.MkdirAll(filepath.Join(dir, subdir), 0755) + err := os.MkdirAll(filepath.Join(dir, subdir), 0o755) require.NoError(t, err) testFile := filepath.Join(dir, subdir, "testfile.txt") - err = os.WriteFile(testFile, []byte("test content"), 0644) + err = os.WriteFile(testFile, []byte("test content"), 0o644) require.NoError(t, err) } } diff --git a/pkg/commands/convert/run.go b/pkg/commands/convert/run.go index 928b6a65f40d..0b9bb2c1d2fc 100644 --- a/pkg/commands/convert/run.go +++ b/pkg/commands/convert/run.go @@ -78,11 +78,11 @@ func compat(r *types.Report) { if vuln.PkgIdentifier.UID != "" { continue } - if pkg, ok := pkgs[vuln.PkgID+vuln.PkgPath]; !ok { + pkg, ok := pkgs[vuln.PkgID+vuln.PkgPath] + if !ok { continue - } else { - r.Results[i].Vulnerabilities[j].PkgIdentifier = pkg.Identifier } + r.Results[i].Vulnerabilities[j].PkgIdentifier = pkg.Identifier } } } diff --git a/pkg/db/db.go b/pkg/db/db.go index 2d617e0942a2..56680883f54d 100644 --- a/pkg/db/db.go +++ b/pkg/db/db.go @@ -110,9 +110,18 @@ func (c *Client) NeedsUpdate(ctx context.Context, cliVersion string, skip bool) meta = metadata.Metadata{Version: db.SchemaVersion} } + // There are 2 cases when DownloadAt field is zero: + // - trivy-db was downloaded with `oras`. In this case user can use `--skip-db-update` (like for air-gapped) or re-download trivy-db. + // - trivy-db was corrupted while copying from tmp directory to cache directory. We should update this trivy-db. + // We can't detect these cases, so we will show warning for users who use oras + air-gapped. + if meta.DownloadedAt.IsZero() && !skip { + log.WarnContext(ctx, "Trivy DB may be corrupted and will be re-downloaded. If you manually downloaded DB - use the `--skip-db-update` flag to skip updating DB.") + return true, nil + } + if skip && noRequiredFiles { log.ErrorContext(ctx, "The first run cannot skip downloading DB") - return false, xerrors.New("--skip-update cannot be specified on the first run") + return false, xerrors.New("--skip-db-update cannot be specified on the first run") } if db.SchemaVersion < meta.Version { @@ -141,7 +150,7 @@ func (c *Client) NeedsUpdate(ctx context.Context, cliVersion string, skip bool) func (c *Client) validate(meta metadata.Metadata) error { if db.SchemaVersion != meta.Version { log.Error("The local DB has an old schema version which is not supported by the current version of Trivy CLI. DB needs to be updated.") - return xerrors.Errorf("--skip-update cannot be specified with the old DB schema. Local DB: %d, Expected: %d", + return xerrors.Errorf("--skip-db-update cannot be specified with the old DB schema. Local DB: %d, Expected: %d", meta.Version, db.SchemaVersion) } return nil @@ -163,11 +172,6 @@ func (c *Client) isNewDB(ctx context.Context, meta metadata.Metadata) bool { // Download downloads the DB file func (c *Client) Download(ctx context.Context, dst string, opt types.RegistryOptions) error { - // Remove the metadata file under the cache directory before downloading DB - if err := c.metadata.Delete(); err != nil { - log.DebugContext(ctx, "No metadata file") - } - if err := c.downloadDB(ctx, opt, dst); err != nil { return xerrors.Errorf("OCI artifact error: %w", err) } diff --git a/pkg/db/db_test.go b/pkg/db/db_test.go index 30d99f894320..dbfc7aae916c 100644 --- a/pkg/db/db_test.go +++ b/pkg/db/db_test.go @@ -19,6 +19,8 @@ func TestClient_NeedsUpdate(t *testing.T) { timeNextUpdateDay1 := time.Date(2019, 9, 1, 0, 0, 0, 0, time.UTC) timeNextUpdateDay2 := time.Date(2019, 10, 2, 0, 0, 0, 0, time.UTC) + timeDownloadAt := time.Date(2019, 9, 30, 22, 30, 0, 0, time.UTC) + tests := []struct { name string skip bool @@ -57,11 +59,12 @@ func TestClient_NeedsUpdate(t *testing.T) { want: true, }, { - name: "happy path with --skip-update", + name: "happy path with --skip-db-update", dbFileExists: true, metadata: metadata.Metadata{ - Version: db.SchemaVersion, - NextUpdate: timeNextUpdateDay1, + Version: db.SchemaVersion, + NextUpdate: timeNextUpdateDay1, + DownloadedAt: timeDownloadAt, }, skip: true, want: false, @@ -70,8 +73,9 @@ func TestClient_NeedsUpdate(t *testing.T) { name: "skip downloading DB", dbFileExists: true, metadata: metadata.Metadata{ - Version: db.SchemaVersion, - NextUpdate: timeNextUpdateDay2, + Version: db.SchemaVersion, + NextUpdate: timeNextUpdateDay2, + DownloadedAt: timeDownloadAt, }, want: false, }, @@ -79,34 +83,36 @@ func TestClient_NeedsUpdate(t *testing.T) { name: "newer schema version", dbFileExists: true, metadata: metadata.Metadata{ - Version: db.SchemaVersion + 1, - NextUpdate: timeNextUpdateDay2, + Version: db.SchemaVersion + 1, + NextUpdate: timeNextUpdateDay2, + DownloadedAt: timeDownloadAt, }, wantErr: fmt.Sprintf("the version of DB schema doesn't match. Local DB: %d, Expected: %d", db.SchemaVersion+1, db.SchemaVersion), }, { - name: "--skip-update without trivy.db on the first run", + name: "--skip-db-update without trivy.db on the first run", dbFileExists: false, skip: true, - wantErr: "--skip-update cannot be specified on the first run", + wantErr: "--skip-db-update cannot be specified on the first run", }, { - name: "--skip-update without metadata.json on the first run", + name: "--skip-db-update without metadata.json on the first run", dbFileExists: true, metadata: metadata.Metadata{}, skip: true, - wantErr: "--skip-update cannot be specified on the first run", + wantErr: "--skip-db-update cannot be specified on the first run", }, { - name: "--skip-update with different schema version", + name: "--skip-db-update with different schema version", dbFileExists: true, metadata: metadata.Metadata{ - Version: 0, - NextUpdate: timeNextUpdateDay1, + Version: 0, + NextUpdate: timeNextUpdateDay1, + DownloadedAt: timeDownloadAt, }, skip: true, - wantErr: fmt.Sprintf("--skip-update cannot be specified with the old DB schema. Local DB: %d, Expected: %d", + wantErr: fmt.Sprintf("--skip-db-update cannot be specified with the old DB schema. Local DB: %d, Expected: %d", 0, db.SchemaVersion), }, { @@ -115,7 +121,7 @@ func TestClient_NeedsUpdate(t *testing.T) { metadata: metadata.Metadata{ Version: db.SchemaVersion, NextUpdate: timeNextUpdateDay1, - DownloadedAt: time.Date(2019, 9, 30, 22, 30, 0, 0, time.UTC), + DownloadedAt: timeDownloadAt, }, want: true, }, @@ -129,6 +135,40 @@ func TestClient_NeedsUpdate(t *testing.T) { }, want: false, }, + { + name: "DownloadedAt is zero, skip is false", + dbFileExists: true, + skip: false, + metadata: metadata.Metadata{ + Version: db.SchemaVersion, + DownloadedAt: time.Time{}, // zero time + NextUpdate: timeNextUpdateDay1, + }, + want: true, + }, + { + name: "DownloadedAt is zero, skip is true", + dbFileExists: true, + skip: true, + metadata: metadata.Metadata{ + Version: db.SchemaVersion, + DownloadedAt: time.Time{}, // zero time + NextUpdate: timeNextUpdateDay1, + }, + want: false, + }, + { + name: "DownloadedAt is zero, skip is true, old schema version", + dbFileExists: true, + skip: true, + metadata: metadata.Metadata{ + Version: 0, + DownloadedAt: time.Time{}, // zero time + NextUpdate: timeNextUpdateDay1, + }, + wantErr: "--skip-db-update cannot be specified with the old DB schema. Local DB: 0, Expected: 2", + want: false, + }, } for _, tt := range tests { diff --git a/pkg/dependency/parser/c/conan/parse.go b/pkg/dependency/parser/c/conan/parse.go index efcb64e9463a..f721f3f8871a 100644 --- a/pkg/dependency/parser/c/conan/parse.go +++ b/pkg/dependency/parser/c/conan/parse.go @@ -128,11 +128,10 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc if lock.GraphLock.Nodes != nil { p.logger.Debug("Handling conan lockfile as v1.x") return p.parseV1(lock) - } else { - // try to parse requirements as conan v2.x - p.logger.Debug("Handling conan lockfile as v2.x") - return p.parseV2(lock) } + // try to parse requirements as conan v2.x + p.logger.Debug("Handling conan lockfile as v2.x") + return p.parseV2(lock) } func parsePackage(text string) (string, string, error) { diff --git a/pkg/dependency/parser/gradle/lockfile/parse.go b/pkg/dependency/parser/gradle/lockfile/parse.go index 9210d6d2f865..93e2fc68dc97 100644 --- a/pkg/dependency/parser/gradle/lockfile/parse.go +++ b/pkg/dependency/parser/gradle/lockfile/parse.go @@ -34,11 +34,23 @@ func (Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, } name := strings.Join(dep[:2], ":") - version := strings.Split(dep[2], "=")[0] // remove classPaths + version, classPathsString, _ := strings.Cut(dep[2], "=") + + classPaths := strings.Split(classPathsString, ",") + dev := true + + for _, classPath := range classPaths { + if !strings.HasPrefix(classPath, "test") { + dev = false + break + } + } + pkgs = append(pkgs, ftypes.Package{ ID: dependency.ID(ftypes.Gradle, name, version), Name: name, Version: version, + Dev: dev, Locations: []ftypes.Location{ { StartLine: lineNum, diff --git a/pkg/dependency/parser/gradle/lockfile/parse_test.go b/pkg/dependency/parser/gradle/lockfile/parse_test.go index 521581c2f2cd..290117b8755b 100644 --- a/pkg/dependency/parser/gradle/lockfile/parse_test.go +++ b/pkg/dependency/parser/gradle/lockfile/parse_test.go @@ -25,6 +25,7 @@ func TestParser_Parse(t *testing.T) { ID: "cglib:cglib-nodep:2.1.2", Name: "cglib:cglib-nodep", Version: "2.1.2", + Dev: false, Locations: []ftypes.Location{ { StartLine: 4, @@ -32,14 +33,27 @@ func TestParser_Parse(t *testing.T) { }, }, }, + { + ID: "junit:junit:4.13.2", + Name: "junit:junit", + Version: "4.13.2", + Dev: true, + Locations: []ftypes.Location{ + { + StartLine: 5, + EndLine: 5, + }, + }, + }, { ID: "org.springframework:spring-asm:3.1.3.RELEASE", Name: "org.springframework:spring-asm", Version: "3.1.3.RELEASE", + Dev: false, Locations: []ftypes.Location{ { - StartLine: 5, - EndLine: 5, + StartLine: 6, + EndLine: 6, }, }, }, @@ -47,10 +61,11 @@ func TestParser_Parse(t *testing.T) { ID: "org.springframework:spring-beans:5.0.5.RELEASE", Name: "org.springframework:spring-beans", Version: "5.0.5.RELEASE", + Dev: false, Locations: []ftypes.Location{ { - StartLine: 6, - EndLine: 6, + StartLine: 7, + EndLine: 7, }, }, }, diff --git a/pkg/dependency/parser/gradle/lockfile/testdata/happy.lockfile b/pkg/dependency/parser/gradle/lockfile/testdata/happy.lockfile index d2b164ee42c5..ee7f69aed983 100644 --- a/pkg/dependency/parser/gradle/lockfile/testdata/happy.lockfile +++ b/pkg/dependency/parser/gradle/lockfile/testdata/happy.lockfile @@ -2,7 +2,8 @@ # Manual edits can break the build and are not advised. # This file is expected to be part of source control. cglib:cglib-nodep:2.1.2=testRuntimeClasspath,classpath +junit:junit:4.13.2=testCompileClasspath,testRuntimeClasspath org.springframework:spring-asm:3.1.3.RELEASE=classpath org.springframework:spring-beans:5.0.5.RELEASE=compileClasspath, runtimeClasspath # io.grpc:grpc-api:1.21.1=classpath -empty= \ No newline at end of file +empty= diff --git a/pkg/dependency/parser/java/pom/parse.go b/pkg/dependency/parser/java/pom/parse.go index 2ce4fb4e3936..aa70b5602a65 100644 --- a/pkg/dependency/parser/java/pom/parse.go +++ b/pkg/dependency/parser/java/pom/parse.go @@ -428,7 +428,8 @@ func (p *Parser) mergeDependencyManagements(depManagements ...[]pomDependency) [ } func (p *Parser) parseDependencies(deps []pomDependency, props map[string]string, depManagement []pomDependency, - opts analysisOptions) []artifact { + opts analysisOptions, +) []artifact { // Imported POMs often have no dependencies, so dependencyManagement resolution can be skipped. if len(deps) == 0 { return nil @@ -549,28 +550,25 @@ func (p *Parser) retrieveParent(currentPath, relativePath string, target artifac // Try relativePath if relativePath != "" { pom, err := p.tryRelativePath(target, currentPath, relativePath) - if err != nil { - errs = multierror.Append(errs, err) - } else { + if err == nil { return pom, nil } + errs = multierror.Append(errs, err) } // If not found, search the parent director pom, err := p.tryRelativePath(target, currentPath, "../pom.xml") - if err != nil { - errs = multierror.Append(errs, err) - } else { + if err == nil { return pom, nil } + errs = multierror.Append(errs, err) // If not found, search local/remote remoteRepositories pom, err = p.tryRepository(target.GroupID, target.ArtifactID, target.Version.String()) - if err != nil { - errs = multierror.Append(errs, err) - } else { + if err == nil { return pom, nil } + errs = multierror.Append(errs, err) // Reaching here means the POM wasn't found return nil, errs @@ -640,6 +638,7 @@ func (p *Parser) openPom(filePath string) (*pom, error) { content: content, }, nil } + func (p *Parser) tryRepository(groupID, artifactID, version string) (*pom, error) { if version == "" { return nil, xerrors.Errorf("Version missing for %s:%s", groupID, artifactID) diff --git a/pkg/dependency/parser/julia/manifest/naive_pkg_parser.go b/pkg/dependency/parser/julia/manifest/naive_pkg_parser.go index 21f899ca177a..0847ad52780c 100644 --- a/pkg/dependency/parser/julia/manifest/naive_pkg_parser.go +++ b/pkg/dependency/parser/julia/manifest/naive_pkg_parser.go @@ -27,7 +27,7 @@ type naivePkgParser struct { } func (parser *naivePkgParser) parse() map[string]pkgPosition { - var currentPkg minPkg = minPkg{} + var currentPkg = minPkg{} var idx = make(map[string]pkgPosition, 0) scanner := bufio.NewScanner(parser.r) diff --git a/pkg/dependency/parser/julia/manifest/parse.go b/pkg/dependency/parser/julia/manifest/parse.go index aa0112a17e67..bac2e33118b7 100644 --- a/pkg/dependency/parser/julia/manifest/parse.go +++ b/pkg/dependency/parser/julia/manifest/parse.go @@ -140,6 +140,9 @@ func decodeDependency(man *primitiveManifest, dep primitiveDependency, metadata var possibleUuids []string for _, depName := range possibleDeps { primDep := man.Dependencies[depName] + if len(primDep) == 0 { + return primitiveDependency{}, xerrors.Errorf("Dependency %q has invalid format (parsed no deps): %s", depName, primDep) + } if len(primDep) > 1 { return primitiveDependency{}, xerrors.Errorf("Dependency %q has invalid format (parsed multiple deps): %s", depName, primDep) } diff --git a/pkg/dependency/parser/julia/manifest/parse_test.go b/pkg/dependency/parser/julia/manifest/parse_test.go index efbc0bfcbee5..ff191e48d0dc 100644 --- a/pkg/dependency/parser/julia/manifest/parse_test.go +++ b/pkg/dependency/parser/julia/manifest/parse_test.go @@ -17,6 +17,7 @@ func TestParse(t *testing.T) { file string // Test input file want []ftypes.Package wantDeps []ftypes.Dependency + wantErr string }{ { name: "Manifest v1.6", @@ -60,6 +61,16 @@ func TestParse(t *testing.T) { want: juliaV10FormatPkgs, wantDeps: juliaV10FormatDeps, }, + { + name: "Manifest file doesn't contain child dependency of another dependency", + file: "testdata/missed-child-dep/Manifest.toml", + wantErr: "has invalid format (parsed no deps)", + }, + { + name: "Manifest file contains multiple dependencies with same name", + file: "testdata/multiple-same-deps/Manifest.toml", + wantErr: "has invalid format (parsed multiple deps)", + }, } for _, tt := range tests { @@ -68,6 +79,11 @@ func TestParse(t *testing.T) { require.NoError(t, err) gotPkgs, gotDeps, err := NewParser().Parse(f) + + if tt.wantErr != "" { + require.ErrorContains(t, err, tt.wantErr) + return + } require.NoError(t, err) sort.Sort(ftypes.Packages(tt.want)) diff --git a/pkg/dependency/parser/julia/manifest/testdata/missed-child-dep/Manifest.toml b/pkg/dependency/parser/julia/manifest/testdata/missed-child-dep/Manifest.toml new file mode 100644 index 000000000000..d49d82a34392 --- /dev/null +++ b/pkg/dependency/parser/julia/manifest/testdata/missed-child-dep/Manifest.toml @@ -0,0 +1,16 @@ +# This file is machine-generated - editing it directly is not advised + +[[Base64]] +uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" + +[[Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" + +[[InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" + +[[LibGit2]] +deps = ["Printf"] +uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" \ No newline at end of file diff --git a/pkg/dependency/parser/julia/manifest/testdata/multiple-same-deps/Manifest.toml b/pkg/dependency/parser/julia/manifest/testdata/multiple-same-deps/Manifest.toml new file mode 100644 index 000000000000..e43943743bd8 --- /dev/null +++ b/pkg/dependency/parser/julia/manifest/testdata/multiple-same-deps/Manifest.toml @@ -0,0 +1,11 @@ +# This file is machine-generated - editing it directly is not advised + +[[Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" + +[[Printf]] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" + +[[Printf]] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d8" diff --git a/pkg/dependency/parser/nodejs/bun/parse.go b/pkg/dependency/parser/nodejs/bun/parse.go new file mode 100644 index 000000000000..41d4954824ba --- /dev/null +++ b/pkg/dependency/parser/nodejs/bun/parse.go @@ -0,0 +1,176 @@ +package bun + +import ( + "fmt" + "io" + "sort" + "strings" + + "github.com/go-json-experiment/json" + "github.com/go-json-experiment/json/jsontext" + "github.com/samber/lo" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/set" + xio "github.com/aquasecurity/trivy/pkg/x/io" + xjson "github.com/aquasecurity/trivy/pkg/x/json" +) + +type LockFile struct { + Packages map[string]ParsedPackage `json:"packages"` + Workspaces map[string]Workspace `json:"workspaces"` + LockfileVersion int `json:"lockfileVersion"` +} + +type Workspace struct { + Name string `json:"name"` + Version string `json:"version"` + Dependencies map[string]string `json:"dependencies"` + DevDependencies map[string]string `json:"devDependencies"` + OptionalDependencies map[string]string `json:"optionalDependencies"` + PeerDependencies map[string]string `json:"peerDependencies"` +} + +type ParsedPackage struct { + Identifier string + Meta map[string]any + xjson.Location +} + +type Parser struct{} + +func NewParser() *Parser { + return &Parser{} +} + +func (p *ParsedPackage) UnmarshalJSON(data []byte) error { + var raw []jsontext.Value + if err := json.Unmarshal(data, &raw); err != nil { + return fmt.Errorf("expected package format: %w", err) + } + if len(raw) < 1 { + return fmt.Errorf("invalid package entry: not enough elements: %s", string(data)) + } + + if err := json.Unmarshal(raw[0], &p.Identifier); err != nil { + return err + } + + if len(raw) > 2 { + if err := json.Unmarshal(raw[2], &p.Meta); err != nil { + return err + } + } + return nil +} + +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { + var lockFile LockFile + data, err := io.ReadAll(r) + if err != nil { + return nil, nil, xerrors.Errorf("file read error: %w", err) + } + if err = xjson.UnmarshalJSONC(data, &lockFile); err != nil { + return nil, nil, xerrors.Errorf("JSON decode error: %w", err) + } + + pkgs := make(map[string]ftypes.Package, len(lockFile.Packages)) + deps := make(map[string][]string) + + prodDirectDeps := set.New[string]() + devDirectDeps := set.New[string]() + + for _, ws := range lockFile.Workspaces { + prodDirectDeps.Append(lo.Keys(ws.Dependencies)...) + prodDirectDeps.Append(lo.Keys(ws.PeerDependencies)...) + prodDirectDeps.Append(lo.Keys(ws.OptionalDependencies)...) + devDirectDeps.Append(lo.Keys(ws.DevDependencies)...) + } + for pkgName, parsed := range lockFile.Packages { + pkgVersion := strings.TrimPrefix(parsed.Identifier, pkgName+"@") + if strings.HasPrefix(pkgVersion, "workspace") { + pkgVersion = lockFile.Workspaces[pkgName].Version + } + pkgId := packageID(pkgName, pkgVersion) + isDirect := prodDirectDeps.Contains(pkgName) || devDirectDeps.Contains(pkgName) + + relationship := ftypes.RelationshipIndirect + if _, ok := lockFile.Workspaces[pkgName]; ok { + relationship = ftypes.RelationshipWorkspace + } else if isDirect { + relationship = ftypes.RelationshipDirect + } + + newPkg := ftypes.Package{ + ID: pkgId, + Name: pkgName, + Version: pkgVersion, + Relationship: relationship, + Dev: true, // Mark all dependencies as Dev. We will handle them later. + Locations: []ftypes.Location{ftypes.Location(parsed.Location)}, + } + pkgs[pkgName] = newPkg + + var dependsOn []string + if depMap, ok := parsed.Meta["dependencies"].(map[string]any); ok { + dependsOn = lo.Keys(depMap) + } + + if len(dependsOn) > 0 { + sort.Strings(dependsOn) + deps[pkgName] = dependsOn + } + } + + for _, pkg := range pkgs { + // Workspaces are always prod deps. + if pkg.Relationship == ftypes.RelationshipWorkspace { + pkg.Dev = false + pkgs[pkg.Name] = pkg + continue + } + if pkg.Relationship != ftypes.RelationshipDirect || !prodDirectDeps.Contains(pkg.Name) { + continue + } + walkProdPackages(pkg.Name, pkgs, deps, set.New[string]()) + } + + depSlice := lo.MapToSlice(deps, func(depName string, dependsOn []string) ftypes.Dependency { + id := pkgs[depName] + dependsOnIDs := make([]string, 0, len(dependsOn)) + for _, d := range dependsOn { + dependsOnIDs = append(dependsOnIDs, pkgs[d].ID) + } + return ftypes.Dependency{ + ID: id.ID, + DependsOn: dependsOnIDs, + } + }) + pkgSlice := lo.Values(pkgs) + sort.Sort(ftypes.Packages(pkgSlice)) + sort.Sort(ftypes.Dependencies(depSlice)) + return pkgSlice, depSlice, nil +} + +// walkProdPackages marks all packages in the dependency tree of the given package as prod packages (Dev == false). +func walkProdPackages(pkgName string, pkgs map[string]ftypes.Package, deps map[string][]string, visited set.Set[string]) { + if visited.Contains(pkgName) { + return + } + + // Disable Dev field for prod pkgs. + pkg := pkgs[pkgName] + pkg.Dev = false + pkgs[pkgName] = pkg + + visited.Append(pkgName) + for _, dep := range deps[pkgName] { + walkProdPackages(dep, pkgs, deps, visited) + } +} + +func packageID(name, version string) string { + return dependency.ID(ftypes.Bun, name, version) +} diff --git a/pkg/dependency/parser/nodejs/bun/parse_test.go b/pkg/dependency/parser/nodejs/bun/parse_test.go new file mode 100644 index 000000000000..ed05abcd7244 --- /dev/null +++ b/pkg/dependency/parser/nodejs/bun/parse_test.go @@ -0,0 +1,57 @@ +package bun + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func TestParse(t *testing.T) { + tests := []struct { + name string + file string // Test input file + want []ftypes.Package + wantDeps []ftypes.Dependency + wantErr string + }{ + { + name: "normal", + file: "testdata/bun_happy.lock", + want: normalPkgs, + wantDeps: normalDeps, + }, + { + name: "invalid lockfile", + file: "testdata/bun_invalid.lock", + wantErr: "JSON decode error", + }, + { + name: "multiple workspaces", + file: "testdata/bun_multiple_ws.lock", + want: multipleWsPkgs, + wantDeps: multipleWsDeps, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.file) + require.NoError(t, err) + defer f.Close() + got, deps, err := NewParser().Parse(f) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + require.NoError(t, err) + assert.Equal(t, tt.want, got) + if tt.wantDeps != nil { + assert.Equal(t, tt.wantDeps, deps) + } + }) + } +} diff --git a/pkg/dependency/parser/nodejs/bun/parse_testcase.go b/pkg/dependency/parser/nodejs/bun/parse_testcase.go new file mode 100644 index 000000000000..28cee86b844e --- /dev/null +++ b/pkg/dependency/parser/nodejs/bun/parse_testcase.go @@ -0,0 +1,160 @@ +package bun + +import ( + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +var ( + normalPkgs = []ftypes.Package{ + { + ID: "@types/bun@1.2.13", + Name: "@types/bun", + Version: "1.2.13", + Relationship: ftypes.RelationshipDirect, + Dev: true, + Locations: ftypes.Locations{ + { + StartLine: 18, + EndLine: 18, + }, + }, + }, + { + ID: "typescript@5.8.3", + Name: "typescript", + Version: "5.8.3", + Relationship: ftypes.RelationshipDirect, + Dev: false, + Locations: ftypes.Locations{ + { + StartLine: 24, + EndLine: 24, + }, + }, + }, + { + ID: "zod@3.24.4", + Name: "zod", + Version: "3.24.4", + Relationship: ftypes.RelationshipDirect, + Dev: false, + Locations: ftypes.Locations{ + { + StartLine: 28, + EndLine: 28, + }, + }, + }, + { + ID: "@types/node@22.15.18", + Name: "@types/node", + Version: "22.15.18", + Relationship: ftypes.RelationshipIndirect, + Dev: true, + Locations: ftypes.Locations{ + { + StartLine: 20, + EndLine: 20, + }, + }, + }, + { + ID: "bun-types@1.2.13", + Name: "bun-types", + Version: "1.2.13", + Relationship: ftypes.RelationshipIndirect, + Dev: true, + Locations: ftypes.Locations{ + { + StartLine: 22, + EndLine: 22, + }, + }, + }, + { + ID: "undici-types@6.21.0", + Name: "undici-types", + Version: "6.21.0", + Relationship: ftypes.RelationshipIndirect, + Dev: false, + Locations: ftypes.Locations{ + { + StartLine: 26, + EndLine: 26, + }, + }, + }, + } + + multipleWsPkgs = []ftypes.Package{ + { + ID: "my-app@1.0.0", + Name: "my-app", + Version: "1.0.0", + Relationship: ftypes.RelationshipWorkspace, + Locations: ftypes.Locations{ + { + StartLine: 27, + EndLine: 27, + }, + }, + }, + { + ID: "my-lib@1.0.0", + Name: "my-lib", + Version: "1.0.0", + Relationship: ftypes.RelationshipWorkspace, + Locations: ftypes.Locations{ + { + StartLine: 29, + EndLine: 29, + }, + }, + }, + { + ID: "chalk@5.0.1", + Name: "chalk", + Version: "5.0.1", + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ + { + StartLine: 23, + EndLine: 23, + }, + }, + }, + { + ID: "lodash@4.17.21", + Name: "lodash", + Version: "4.17.21", + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ + { + StartLine: 25, + EndLine: 25, + }, + }, + }, + } + + normalDeps = []ftypes.Dependency{ + { + ID: "@types/bun@1.2.13", + DependsOn: []string{"bun-types@1.2.13"}, + }, + { + ID: "@types/node@22.15.18", + DependsOn: []string{"undici-types@6.21.0"}, + }, + { + ID: "bun-types@1.2.13", + DependsOn: []string{"@types/node@22.15.18"}, + }, + { + ID: "zod@3.24.4", + DependsOn: []string{"undici-types@6.21.0"}, + }, + } + + multipleWsDeps = []ftypes.Dependency(nil) +) diff --git a/pkg/dependency/parser/nodejs/bun/testdata/bun_happy.lock b/pkg/dependency/parser/nodejs/bun/testdata/bun_happy.lock new file mode 100644 index 000000000000..295f4612e604 --- /dev/null +++ b/pkg/dependency/parser/nodejs/bun/testdata/bun_happy.lock @@ -0,0 +1,30 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "buntest", + "devDependencies": { + "@types/bun": "latest", + }, + "optionalDependencies": { + "zod": "^3.24.4", + }, + "peerDependencies": { + "typescript": "^5", + }, + }, + }, + "packages": { + "@types/bun": ["@types/bun@1.2.13", "", { "dependencies": { "bun-types": "1.2.13" } }, "sha512-u6vXep/i9VBxoJl3GjZsl/BFIsvML8DfVDO0RYLEwtSZSp981kEO1V5NwRcO1CPJ7AmvpbnDCiMKo3JvbDEjAg=="], + + "@types/node": ["@types/node@22.15.18", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-v1DKRfUdyW+jJhZNEI1PYy29S2YRxMV5AOO/x/SjKmW0acCIOqmbj6Haf9eHAhsPmrhlHSxEhv/1WszcLWV4cg=="], + + "bun-types": ["bun-types@1.2.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-rRjA1T6n7wto4gxhAO/ErZEtOXyEZEmnIHQfl0Dt1QQSB4QV0iP6BZ9/YB5fZaHFQ2dwHFrmPaRQ9GGMX01k9Q=="], + + "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "zod": ["zod@3.24.4", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg=="], + } +} diff --git a/pkg/dependency/parser/nodejs/bun/testdata/bun_invalid.lock b/pkg/dependency/parser/nodejs/bun/testdata/bun_invalid.lock new file mode 100644 index 000000000000..e0e5a0b1d84e --- /dev/null +++ b/pkg/dependency/parser/nodejs/bun/testdata/bun_invalid.lock @@ -0,0 +1,3 @@ +{ + "lockfileVersion": "not-a-number", +} diff --git a/pkg/dependency/parser/nodejs/bun/testdata/bun_multiple_ws.lock b/pkg/dependency/parser/nodejs/bun/testdata/bun_multiple_ws.lock new file mode 100644 index 000000000000..aa9e87961022 --- /dev/null +++ b/pkg/dependency/parser/nodejs/bun/testdata/bun_multiple_ws.lock @@ -0,0 +1,31 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "buntest", + }, + "my-app": { + "name": "my-app", + "version": "1.0.0", + "dependencies": { + "lodash": "4.17.21", + }, + }, + "my-lib": { + "name": "my-lib", + "version": "1.0.0", + "dependencies": { + "chalk": "5.0.1", + }, + }, + }, + "packages": { + "chalk": ["chalk@5.0.1", "", {}, "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w=="], + + "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], + + "my-app": ["my-app@workspace:my-app"], + + "my-lib": ["my-lib@workspace:my-lib"], + } +} diff --git a/pkg/dependency/parser/nodejs/pnpm/parse.go b/pkg/dependency/parser/nodejs/pnpm/parse.go index 0817fd48899f..efad36c52aa1 100644 --- a/pkg/dependency/parser/nodejs/pnpm/parse.go +++ b/pkg/dependency/parser/nodejs/pnpm/parse.go @@ -245,7 +245,6 @@ func (p *Parser) markRootPkgs(id string, pkgs map[string]ftypes.Package, deps ma for _, depID := range deps[id].DependsOn { p.markRootPkgs(depID, pkgs, deps, visited) } - return } func (p *Parser) parseLockfileVersion(lockFile LockFile) float64 { @@ -255,12 +254,12 @@ func (p *Parser) parseLockfileVersion(lockFile LockFile) float64 { return v // v6+ case string: - if lockVer, err := strconv.ParseFloat(v, 64); err != nil { + lockVer, err := strconv.ParseFloat(v, 64) + if err != nil { p.logger.Debug("Unable to convert the lock file version to float", log.Err(err)) return -1 - } else { - return lockVer } + return lockVer default: p.logger.Debug("Unknown type for the lock file version", log.Any("version", lockFile.LockfileVersion)) diff --git a/pkg/dependency/parser/nodejs/yarn/parse.go b/pkg/dependency/parser/nodejs/yarn/parse.go index fbe77913a1e4..77b2bafff716 100644 --- a/pkg/dependency/parser/nodejs/yarn/parse.go +++ b/pkg/dependency/parser/nodejs/yarn/parse.go @@ -131,7 +131,7 @@ func ignoreProtocol(protocol string) bool { func parseResults(patternIDs map[string]string, dependsOn map[string][]string) (deps ftypes.Dependencies) { // find dependencies by patterns for pkgID, depPatterns := range dependsOn { - depIDs := lo.Map(depPatterns, func(pattern string, index int) string { + depIDs := lo.Map(depPatterns, func(pattern string, _ int) string { return patternIDs[pattern] }) deps = append(deps, ftypes.Dependency{ @@ -221,11 +221,10 @@ func (p *Parser) parseBlock(block []byte, lineNum int) (lib Library, deps []stri continue } continue - } else { - lib.Patterns = patterns - lib.Name = name - continue } + lib.Patterns = patterns + lib.Name = name + continue } } @@ -251,23 +250,23 @@ func (p *Parser) parseBlock(block []byte, lineNum int) (lib Library, deps []stri func parseDependencies(scanner *LineScanner) (deps []string) { for scanner.Scan() { line := scanner.Text() - if dep, err := parseDependency(line); err != nil { + dep, err := parseDependency(line) + if err != nil { // finished dependencies block return deps - } else { - deps = append(deps, dep) } + deps = append(deps, dep) } return } func parseDependency(line string) (string, error) { - if name, version, err := getDependency(line); err != nil { + name, version, err := getDependency(line) + if err != nil { return "", err - } else { - return packageID(name, version), nil } + return packageID(name, version), nil } func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, map[string][]string, error) { diff --git a/pkg/dependency/parser/ruby/bundler/parse.go b/pkg/dependency/parser/ruby/bundler/parse.go index 12dff78bc7c0..d203c148d1b3 100644 --- a/pkg/dependency/parser/ruby/bundler/parse.go +++ b/pkg/dependency/parser/ruby/bundler/parse.go @@ -111,11 +111,10 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc func countLeadingSpace(line string) int { i := 0 for _, runeValue := range line { - if runeValue == ' ' { - i++ - } else { + if runeValue != ' ' { break } + i++ } return i } diff --git a/pkg/dependency/parser/rust/cargo/naive_pkg_parser.go b/pkg/dependency/parser/rust/cargo/naive_pkg_parser.go index 775dae13477f..88f037c32b12 100644 --- a/pkg/dependency/parser/rust/cargo/naive_pkg_parser.go +++ b/pkg/dependency/parser/rust/cargo/naive_pkg_parser.go @@ -28,7 +28,7 @@ type naivePkgParser struct { } func (parser *naivePkgParser) parse() map[string]pkgPosition { - var currentPkg minPkg = minPkg{} + var currentPkg = minPkg{} var idx = make(map[string]pkgPosition, 0) scanner := bufio.NewScanner(parser.r) diff --git a/pkg/dependency/parser/rust/cargo/parse.go b/pkg/dependency/parser/rust/cargo/parse.go index d3fbbce00bb4..f7ec6d1926eb 100644 --- a/pkg/dependency/parser/rust/cargo/parse.go +++ b/pkg/dependency/parser/rust/cargo/parse.go @@ -121,9 +121,8 @@ func (p *Parser) parseDependencies(pkgId string, pkg cargoPkg, pkgs map[string]c ID: pkgId, DependsOn: dependOn, } - } else { - return nil } + return nil } func packageID(name, version string) string { diff --git a/pkg/detector/ospkg/alma/alma_test.go b/pkg/detector/ospkg/alma/alma_test.go index e77f2aa62f4a..77d563100d46 100644 --- a/pkg/detector/ospkg/alma/alma_test.go +++ b/pkg/detector/ospkg/alma/alma_test.go @@ -162,7 +162,7 @@ func TestScanner_Detect(t *testing.T) { defer db.Close() s := alma.NewScanner() - got, err := s.Detect(nil, tt.args.osVer, nil, tt.args.pkgs) + got, err := s.Detect(t.Context(), tt.args.osVer, nil, tt.args.pkgs) if tt.wantErr != "" { require.ErrorContains(t, err, tt.wantErr) return diff --git a/pkg/detector/ospkg/alpine/alpine.go b/pkg/detector/ospkg/alpine/alpine.go index f29e4e9ee52a..6a04bbe267f1 100644 --- a/pkg/detector/ospkg/alpine/alpine.go +++ b/pkg/detector/ospkg/alpine/alpine.go @@ -17,41 +17,39 @@ import ( "github.com/aquasecurity/trivy/pkg/types" ) -var ( - eolDates = map[string]time.Time{ - "2.0": time.Date(2012, 4, 1, 23, 59, 59, 0, time.UTC), - "2.1": time.Date(2012, 11, 1, 23, 59, 59, 0, time.UTC), - "2.2": time.Date(2013, 5, 1, 23, 59, 59, 0, time.UTC), - "2.3": time.Date(2013, 11, 1, 23, 59, 59, 0, time.UTC), - "2.4": time.Date(2014, 5, 1, 23, 59, 59, 0, time.UTC), - "2.5": time.Date(2014, 11, 1, 23, 59, 59, 0, time.UTC), - "2.6": time.Date(2015, 5, 1, 23, 59, 59, 0, time.UTC), - "2.7": time.Date(2015, 11, 1, 23, 59, 59, 0, time.UTC), - "3.0": time.Date(2016, 5, 1, 23, 59, 59, 0, time.UTC), - "3.1": time.Date(2016, 11, 1, 23, 59, 59, 0, time.UTC), - "3.2": time.Date(2017, 5, 1, 23, 59, 59, 0, time.UTC), - "3.3": time.Date(2017, 11, 1, 23, 59, 59, 0, time.UTC), - "3.4": time.Date(2018, 5, 1, 23, 59, 59, 0, time.UTC), - "3.5": time.Date(2018, 11, 1, 23, 59, 59, 0, time.UTC), - "3.6": time.Date(2019, 5, 1, 23, 59, 59, 0, time.UTC), - "3.7": time.Date(2019, 11, 1, 23, 59, 59, 0, time.UTC), - "3.8": time.Date(2020, 5, 1, 23, 59, 59, 0, time.UTC), - "3.9": time.Date(2020, 11, 1, 23, 59, 59, 0, time.UTC), - "3.10": time.Date(2021, 5, 1, 23, 59, 59, 0, time.UTC), - "3.11": time.Date(2021, 11, 1, 23, 59, 59, 0, time.UTC), - "3.12": time.Date(2022, 5, 1, 23, 59, 59, 0, time.UTC), - "3.13": time.Date(2022, 11, 1, 23, 59, 59, 0, time.UTC), - "3.14": time.Date(2023, 5, 1, 23, 59, 59, 0, time.UTC), - "3.15": time.Date(2023, 11, 1, 23, 59, 59, 0, time.UTC), - "3.16": time.Date(2024, 5, 23, 23, 59, 59, 0, time.UTC), - "3.17": time.Date(2024, 11, 22, 23, 59, 59, 0, time.UTC), - "3.18": time.Date(2025, 5, 9, 23, 59, 59, 0, time.UTC), - "3.19": time.Date(2025, 11, 1, 23, 59, 59, 0, time.UTC), - "3.20": time.Date(2026, 04, 1, 23, 59, 59, 0, time.UTC), - "3.21": time.Date(2026, 12, 5, 23, 59, 59, 0, time.UTC), - "edge": time.Date(9999, 1, 1, 0, 0, 0, 0, time.UTC), - } -) +var eolDates = map[string]time.Time{ + "2.0": time.Date(2012, 4, 1, 23, 59, 59, 0, time.UTC), + "2.1": time.Date(2012, 11, 1, 23, 59, 59, 0, time.UTC), + "2.2": time.Date(2013, 5, 1, 23, 59, 59, 0, time.UTC), + "2.3": time.Date(2013, 11, 1, 23, 59, 59, 0, time.UTC), + "2.4": time.Date(2014, 5, 1, 23, 59, 59, 0, time.UTC), + "2.5": time.Date(2014, 11, 1, 23, 59, 59, 0, time.UTC), + "2.6": time.Date(2015, 5, 1, 23, 59, 59, 0, time.UTC), + "2.7": time.Date(2015, 11, 1, 23, 59, 59, 0, time.UTC), + "3.0": time.Date(2016, 5, 1, 23, 59, 59, 0, time.UTC), + "3.1": time.Date(2016, 11, 1, 23, 59, 59, 0, time.UTC), + "3.2": time.Date(2017, 5, 1, 23, 59, 59, 0, time.UTC), + "3.3": time.Date(2017, 11, 1, 23, 59, 59, 0, time.UTC), + "3.4": time.Date(2018, 5, 1, 23, 59, 59, 0, time.UTC), + "3.5": time.Date(2018, 11, 1, 23, 59, 59, 0, time.UTC), + "3.6": time.Date(2019, 5, 1, 23, 59, 59, 0, time.UTC), + "3.7": time.Date(2019, 11, 1, 23, 59, 59, 0, time.UTC), + "3.8": time.Date(2020, 5, 1, 23, 59, 59, 0, time.UTC), + "3.9": time.Date(2020, 11, 1, 23, 59, 59, 0, time.UTC), + "3.10": time.Date(2021, 5, 1, 23, 59, 59, 0, time.UTC), + "3.11": time.Date(2021, 11, 1, 23, 59, 59, 0, time.UTC), + "3.12": time.Date(2022, 5, 1, 23, 59, 59, 0, time.UTC), + "3.13": time.Date(2022, 11, 1, 23, 59, 59, 0, time.UTC), + "3.14": time.Date(2023, 5, 1, 23, 59, 59, 0, time.UTC), + "3.15": time.Date(2023, 11, 1, 23, 59, 59, 0, time.UTC), + "3.16": time.Date(2024, 5, 23, 23, 59, 59, 0, time.UTC), + "3.17": time.Date(2024, 11, 22, 23, 59, 59, 0, time.UTC), + "3.18": time.Date(2025, 5, 9, 23, 59, 59, 0, time.UTC), + "3.19": time.Date(2025, 11, 1, 23, 59, 59, 0, time.UTC), + "3.20": time.Date(2026, 4, 1, 23, 59, 59, 0, time.UTC), + "3.21": time.Date(2026, 12, 5, 23, 59, 59, 0, time.UTC), + "edge": time.Date(9999, 1, 1, 0, 0, 0, 0, time.UTC), +} // Scanner implements the Alpine scanner type Scanner struct { diff --git a/pkg/detector/ospkg/alpine/alpine_test.go b/pkg/detector/ospkg/alpine/alpine_test.go index 1f1e5ffe23c9..8f66d48d841d 100644 --- a/pkg/detector/ospkg/alpine/alpine_test.go +++ b/pkg/detector/ospkg/alpine/alpine_test.go @@ -251,7 +251,7 @@ func TestScanner_Detect(t *testing.T) { defer db.Close() s := alpine.NewScanner() - got, err := s.Detect(nil, tt.args.osVer, tt.args.repo, tt.args.pkgs) + got, err := s.Detect(t.Context(), tt.args.osVer, tt.args.repo, tt.args.pkgs) if tt.wantErr != "" { require.ErrorContains(t, err, tt.wantErr) return diff --git a/pkg/detector/ospkg/amazon/amazon_test.go b/pkg/detector/ospkg/amazon/amazon_test.go index a9ae773f23b2..f09eaa87ab1c 100644 --- a/pkg/detector/ospkg/amazon/amazon_test.go +++ b/pkg/detector/ospkg/amazon/amazon_test.go @@ -177,7 +177,7 @@ func TestScanner_Detect(t *testing.T) { defer db.Close() s := amazon.NewScanner() - got, err := s.Detect(nil, tt.args.osVer, nil, tt.args.pkgs) + got, err := s.Detect(t.Context(), tt.args.osVer, nil, tt.args.pkgs) if tt.wantErr != "" { require.ErrorContains(t, err, tt.wantErr) return diff --git a/pkg/detector/ospkg/azure/azure_test.go b/pkg/detector/ospkg/azure/azure_test.go index dd428f197e4d..1436a7a5e4e1 100644 --- a/pkg/detector/ospkg/azure/azure_test.go +++ b/pkg/detector/ospkg/azure/azure_test.go @@ -185,7 +185,7 @@ func TestScanner_Detect(t *testing.T) { if tt.args.dist == azurevs.Mariner { s = azure.NewMarinerScanner() } - got, err := s.Detect(nil, tt.args.osVer, nil, tt.args.pkgs) + got, err := s.Detect(t.Context(), tt.args.osVer, nil, tt.args.pkgs) if tt.wantErr != "" { require.ErrorContains(t, err, tt.wantErr) return diff --git a/pkg/detector/ospkg/bottlerocket/bottlerocket.go b/pkg/detector/ospkg/bottlerocket/bottlerocket.go new file mode 100644 index 000000000000..9dcd6ac43383 --- /dev/null +++ b/pkg/detector/ospkg/bottlerocket/bottlerocket.go @@ -0,0 +1,28 @@ +package bottlerocket + +import ( + "context" + + osver "github.com/aquasecurity/trivy/pkg/detector/ospkg/version" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/types" +) + +// Scanner implements the Bottlerocket scanner +type Scanner struct { +} + +// NewScanner is the factory method for Scanner +func NewScanner() *Scanner { + return &Scanner{} +} + +func (s *Scanner) Detect(ctx context.Context, _ string, _ *ftypes.Repository, _ []ftypes.Package) ([]types.DetectedVulnerability, error) { + log.InfoContext(ctx, "Vulnerability detection of Bottlerocket packages is currently not supported.") + return nil, nil +} + +func (s *Scanner) IsSupportedVersion(ctx context.Context, osFamily ftypes.OSType, osVer string) bool { + return osver.Supported(ctx, nil, osFamily, osver.Minor(osVer)) +} diff --git a/pkg/detector/ospkg/chainguard/chainguard_test.go b/pkg/detector/ospkg/chainguard/chainguard_test.go index cd5d7daea335..f92d46d0c83f 100644 --- a/pkg/detector/ospkg/chainguard/chainguard_test.go +++ b/pkg/detector/ospkg/chainguard/chainguard_test.go @@ -194,7 +194,7 @@ func TestScanner_Detect(t *testing.T) { defer db.Close() s := chainguard.NewScanner() - got, err := s.Detect(nil, "", tt.args.repo, tt.args.pkgs) + got, err := s.Detect(t.Context(), "", tt.args.repo, tt.args.pkgs) if tt.wantErr != "" { require.ErrorContains(t, err, tt.wantErr) return diff --git a/pkg/detector/ospkg/debian/debian_test.go b/pkg/detector/ospkg/debian/debian_test.go index bd99ca931ce2..b038c359890a 100644 --- a/pkg/detector/ospkg/debian/debian_test.go +++ b/pkg/detector/ospkg/debian/debian_test.go @@ -115,7 +115,7 @@ func TestScanner_Detect(t *testing.T) { defer db.Close() s := debian.NewScanner() - got, err := s.Detect(nil, tt.args.osVer, nil, tt.args.pkgs) + got, err := s.Detect(t.Context(), tt.args.osVer, nil, tt.args.pkgs) if tt.wantErr != "" { require.ErrorContains(t, err, tt.wantErr) return diff --git a/pkg/detector/ospkg/detect.go b/pkg/detector/ospkg/detect.go index fedc8d31c9c2..f83088f29abd 100644 --- a/pkg/detector/ospkg/detect.go +++ b/pkg/detector/ospkg/detect.go @@ -11,6 +11,7 @@ import ( "github.com/aquasecurity/trivy/pkg/detector/ospkg/alpine" "github.com/aquasecurity/trivy/pkg/detector/ospkg/amazon" "github.com/aquasecurity/trivy/pkg/detector/ospkg/azure" + "github.com/aquasecurity/trivy/pkg/detector/ospkg/bottlerocket" "github.com/aquasecurity/trivy/pkg/detector/ospkg/chainguard" "github.com/aquasecurity/trivy/pkg/detector/ospkg/debian" "github.com/aquasecurity/trivy/pkg/detector/ospkg/oracle" @@ -34,6 +35,7 @@ var ( ftypes.Alma: alma.NewScanner(), ftypes.Amazon: amazon.NewScanner(), ftypes.Azure: azure.NewAzureScanner(), + ftypes.Bottlerocket: bottlerocket.NewScanner(), ftypes.CBLMariner: azure.NewMarinerScanner(), ftypes.Debian: debian.NewScanner(), ftypes.Ubuntu: ubuntu.NewScanner(), @@ -75,7 +77,7 @@ func Detect(ctx context.Context, _, osFamily ftypes.OSType, osName string, repo // Package `gpg-pubkey` doesn't use the correct version. // We don't need to find vulnerabilities for this package. - filteredPkgs := lo.Filter(pkgs, func(pkg ftypes.Package, index int) bool { + filteredPkgs := lo.Filter(pkgs, func(pkg ftypes.Package, _ int) bool { return pkg.Name != "gpg-pubkey" }) vulns, err := driver.Detect(ctx, osName, repo, filteredPkgs) diff --git a/pkg/detector/ospkg/oracle/oracle_test.go b/pkg/detector/ospkg/oracle/oracle_test.go index a8eff135e2a9..0ecd4b2c0bf4 100644 --- a/pkg/detector/ospkg/oracle/oracle_test.go +++ b/pkg/detector/ospkg/oracle/oracle_test.go @@ -89,7 +89,6 @@ func TestScanner_IsSupportedVersion(t *testing.T) { } }) } - } func TestScanner_Detect(t *testing.T) { @@ -340,14 +339,12 @@ func TestScanner_Detect(t *testing.T) { defer db.Close() s := NewScanner() - got, err := s.Detect(nil, tt.args.osVer, nil, tt.args.pkgs) + got, err := s.Detect(t.Context(), tt.args.osVer, nil, tt.args.pkgs) if tt.wantErr != "" { require.ErrorContains(t, err, tt.wantErr) return - } else { - require.NoError(t, err) } - + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/detector/ospkg/photon/photon_test.go b/pkg/detector/ospkg/photon/photon_test.go index d6038fc19580..b2b83dbd3cd8 100644 --- a/pkg/detector/ospkg/photon/photon_test.go +++ b/pkg/detector/ospkg/photon/photon_test.go @@ -94,7 +94,7 @@ func TestScanner_Detect(t *testing.T) { defer db.Close() s := photon.NewScanner() - got, err := s.Detect(nil, tt.args.osVer, nil, tt.args.pkgs) + got, err := s.Detect(t.Context(), tt.args.osVer, nil, tt.args.pkgs) if tt.wantErr != "" { require.ErrorContains(t, err, tt.wantErr) return diff --git a/pkg/detector/ospkg/redhat/redhat.go b/pkg/detector/ospkg/redhat/redhat.go index 0aa6aaab7a11..7fda23142813 100644 --- a/pkg/detector/ospkg/redhat/redhat.go +++ b/pkg/detector/ospkg/redhat/redhat.go @@ -3,12 +3,14 @@ package redhat import ( "context" "fmt" + "regexp" "slices" "sort" "strings" "time" version "github.com/knqyf263/go-rpm-version" + "github.com/samber/lo" "golang.org/x/xerrors" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" @@ -109,6 +111,9 @@ func (s *Scanner) detect(osVer string, pkg ftypes.Package) ([]types.DetectedVuln nvr = fmt.Sprintf("%s-%s", pkg.BuildInfo.Nvr, pkg.BuildInfo.Arch) } + // Clean content sets from generic suffixes (__8, __9, etc.) + contentSets = cleanContentSets(contentSets) + advisories, err := s.vs.Get(pkgName, contentSets, []string{nvr}) if err != nil { return nil, xerrors.Errorf("failed to get Red Hat advisories: %w", err) @@ -197,3 +202,23 @@ func addModularNamespace(name, label string) string { } return name } + +// Match generic version suffixes like "__8", "__9", "__10", but preserve EUS suffixes like "__9_DOT_2". +// Examples: +// - Matches: "repo__8", "repo__10" +// - Does not match: "repo__9_DOT_2", "repo__10_DOT_1" +var genericSuffixPattern = regexp.MustCompile(`__\d+$`) + +// cleanContentSets removes generic suffixes like "__8" from content sets. +// These are Red Hat image build artifacts and not valid repository names. +// Examples: +// +// Input: []string{"repo__8", "repo__9_DOT_2", "repo__10"} +// Output: []string{"repo", "repo__9_DOT_2", "repo"} +// +// cf. https://github.com/aquasecurity/trivy-db/issues/435 +func cleanContentSets(contentSets []string) []string { + return lo.Map(contentSets, func(cs string, _ int) string { + return genericSuffixPattern.ReplaceAllString(cs, "") + }) +} diff --git a/pkg/detector/ospkg/redhat/redhat_test.go b/pkg/detector/ospkg/redhat/redhat_test.go index 22c6e9d1b4b7..7d4cc41d6cff 100644 --- a/pkg/detector/ospkg/redhat/redhat_test.go +++ b/pkg/detector/ospkg/redhat/redhat_test.go @@ -420,6 +420,56 @@ func TestScanner_Detect(t *testing.T) { }, wantErr: true, }, + { + name: "content sets with invalid suffix", + fixtures: []string{ + "testdata/fixtures/redhat.yaml", + "testdata/fixtures/cpe.yaml", + }, + args: args{ + osVer: "8.3", + pkgs: []ftypes.Package{ + { + Name: "vim-minimal", + Version: "7.4.160", + Release: "5.el8", + Epoch: 2, + Arch: "x86_64", + SrcName: "vim", + SrcVersion: "7.4.160", + SrcRelease: "5.el8", + SrcEpoch: 2, + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + BuildInfo: &ftypes.BuildInfo{ + ContentSets: []string{ + "rhel-8-for-x86_64-baseos-rpms__8", + "rhel-8-for-x86_64-appstream-rpms__8", + }, + }, + }, + }, + }, + want: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-12735", + VendorIDs: []string{ + "RHSA-2019:1619", + }, + PkgName: "vim-minimal", + InstalledVersion: "2:7.4.160-5.el8", + FixedVersion: "2:7.4.160-7.el8_7", + SeveritySource: vulnerability.RedHat, + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityMedium.String(), + }, + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -427,7 +477,7 @@ func TestScanner_Detect(t *testing.T) { defer func() { _ = dbtest.Close() }() s := redhat.NewScanner() - got, err := s.Detect(nil, tt.args.osVer, nil, tt.args.pkgs) + got, err := s.Detect(t.Context(), tt.args.osVer, nil, tt.args.pkgs) require.Equal(t, tt.wantErr, err != nil, err) assert.Equal(t, tt.want, got) }) diff --git a/pkg/detector/ospkg/rocky/rocky_test.go b/pkg/detector/ospkg/rocky/rocky_test.go index 5990bbc5b868..f88b91667bab 100644 --- a/pkg/detector/ospkg/rocky/rocky_test.go +++ b/pkg/detector/ospkg/rocky/rocky_test.go @@ -122,7 +122,7 @@ func TestScanner_Detect(t *testing.T) { defer db.Close() s := rocky.NewScanner() - got, err := s.Detect(nil, tt.args.osVer, nil, tt.args.pkgs) + got, err := s.Detect(t.Context(), tt.args.osVer, nil, tt.args.pkgs) if tt.wantErr != "" { require.ErrorContains(t, err, tt.wantErr) return diff --git a/pkg/detector/ospkg/suse/suse_test.go b/pkg/detector/ospkg/suse/suse_test.go index 1c1f5792e025..f1067a124b56 100644 --- a/pkg/detector/ospkg/suse/suse_test.go +++ b/pkg/detector/ospkg/suse/suse_test.go @@ -217,7 +217,7 @@ func TestScanner_Detect(t *testing.T) { defer db.Close() s := suse.NewScanner(tt.distribution) - got, err := s.Detect(nil, tt.args.osVer, nil, tt.args.pkgs) + got, err := s.Detect(t.Context(), tt.args.osVer, nil, tt.args.pkgs) if tt.wantErr != "" { require.ErrorContains(t, err, tt.wantErr) return diff --git a/pkg/detector/ospkg/ubuntu/ubuntu.go b/pkg/detector/ospkg/ubuntu/ubuntu.go index d271e315ae69..880a57c78341 100644 --- a/pkg/detector/ospkg/ubuntu/ubuntu.go +++ b/pkg/detector/ospkg/ubuntu/ubuntu.go @@ -9,6 +9,7 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/ubuntu" + "github.com/aquasecurity/trivy/pkg/clock" osver "github.com/aquasecurity/trivy/pkg/detector/ospkg/version" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" @@ -85,7 +86,7 @@ func (s *Scanner) Detect(ctx context.Context, osVer string, _ *ftypes.Repository var vulns []types.DetectedVulnerability for _, pkg := range pkgs { - osVer = s.versionFromEolDates(osVer) + osVer = s.versionFromEolDates(ctx, osVer) advisories, err := s.vs.Get(osVer, pkg.SrcName) if err != nil { return nil, xerrors.Errorf("failed to get Ubuntu advisories: %w", err) @@ -136,7 +137,7 @@ func (s *Scanner) IsSupportedVersion(ctx context.Context, osFamily ftypes.OSType } // versionFromEolDates checks if actual (not ESM) version is not outdated -func (s *Scanner) versionFromEolDates(osVer string) string { +func (s *Scanner) versionFromEolDates(ctx context.Context, osVer string) string { if _, ok := eolDates[osVer]; ok { return osVer } @@ -147,7 +148,7 @@ func (s *Scanner) versionFromEolDates(osVer string) string { // then we need to get vulnerabilities for `18.04` // if `18.04` is outdated - we need to use `18.04-ESM` (we will return error until we add `18.04-ESM` to eolDates) ver := strings.TrimRight(osVer, "-ESM") - if eol, ok := eolDates[ver]; ok && time.Now().Before(eol) { // TODO: time.Now() should be replaced with clock.Now() + if eol, ok := eolDates[ver]; ok && clock.Now(ctx).Before(eol) { return ver } return osVer diff --git a/pkg/detector/ospkg/ubuntu/ubuntu_test.go b/pkg/detector/ospkg/ubuntu/ubuntu_test.go index 1649160a4cbe..4c6a445a2fe9 100644 --- a/pkg/detector/ospkg/ubuntu/ubuntu_test.go +++ b/pkg/detector/ospkg/ubuntu/ubuntu_test.go @@ -175,11 +175,13 @@ func TestScanner_Detect(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + ctx := clock.With(t.Context(), time.Date(2024, 4, 1, 0, 0, 0, 0, time.UTC)) + _ = dbtest.InitDB(t, tt.fixtures) defer db.Close() s := ubuntu.NewScanner() - got, err := s.Detect(nil, tt.args.osVer, nil, tt.args.pkgs) + got, err := s.Detect(ctx, tt.args.osVer, nil, tt.args.pkgs) if tt.wantErr != "" { require.ErrorContains(t, err, tt.wantErr) return diff --git a/pkg/detector/ospkg/wolfi/wolfi_test.go b/pkg/detector/ospkg/wolfi/wolfi_test.go index 92e9b5fc2d48..6ebd6c03d1bd 100644 --- a/pkg/detector/ospkg/wolfi/wolfi_test.go +++ b/pkg/detector/ospkg/wolfi/wolfi_test.go @@ -194,7 +194,7 @@ func TestScanner_Detect(t *testing.T) { defer db.Close() s := wolfi.NewScanner() - got, err := s.Detect(nil, "", tt.args.repo, tt.args.pkgs) + got, err := s.Detect(t.Context(), "", tt.args.repo, tt.args.pkgs) if tt.wantErr != "" { require.ErrorContains(t, err, tt.wantErr) return diff --git a/pkg/downloader/downloader_test.go b/pkg/downloader/downloader_test.go index 28fc9381d540..67eb6428e328 100644 --- a/pkg/downloader/downloader_test.go +++ b/pkg/downloader/downloader_test.go @@ -14,7 +14,7 @@ import ( func TestDownload(t *testing.T) { // Set up a test server with a self-signed certificate - server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { _, err := w.Write([]byte("test content")) assert.NoError(t, err) })) diff --git a/pkg/fanal/analyzer/all/import.go b/pkg/fanal/analyzer/all/import.go index c9d50dd4f924..0a863f7ee2cb 100644 --- a/pkg/fanal/analyzer/all/import.go +++ b/pkg/fanal/analyzer/all/import.go @@ -46,6 +46,7 @@ import ( _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/release" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/ubuntu" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/pkg/apk" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/pkg/bottlerocket_inventory" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/pkg/dpkg" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/pkg/rpm" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/repo/apk" diff --git a/pkg/fanal/analyzer/analyzer.go b/pkg/fanal/analyzer/analyzer.go index 6e357c6a9a6f..83f01bccf97d 100644 --- a/pkg/fanal/analyzer/analyzer.go +++ b/pkg/fanal/analyzer/analyzer.go @@ -235,9 +235,8 @@ func (r *AnalysisResult) Sort() { sort.Slice(r.Misconfigurations, func(i, j int) bool { if r.Misconfigurations[i].FileType != r.Misconfigurations[j].FileType { return r.Misconfigurations[i].FileType < r.Misconfigurations[j].FileType - } else { - return r.Misconfigurations[i].FilePath < r.Misconfigurations[j].FilePath } + return r.Misconfigurations[i].FilePath < r.Misconfigurations[j].FilePath }) // Secrets @@ -258,9 +257,8 @@ func (r *AnalysisResult) Sort() { if r.Licenses[i].Type == r.Licenses[j].Type { if r.Licenses[i].FilePath == r.Licenses[j].FilePath { return r.Licenses[i].Layer.DiffID < r.Licenses[j].Layer.DiffID - } else { - return r.Licenses[i].FilePath < r.Licenses[j].FilePath } + return r.Licenses[i].FilePath < r.Licenses[j].FilePath } return r.Licenses[i].Type < r.Licenses[j].Type @@ -541,7 +539,13 @@ func (ag AnalyzerGroup) PostAnalyzerFS() (*CompositeFS, error) { func (ag AnalyzerGroup) StaticPaths(disabled []Type) ([]string, bool) { var paths []string - for _, a := range ag.analyzers { + type analyzerType interface{ Type() Type } + allAnalyzers := append( + lo.Map(ag.analyzers, func(a analyzer, _ int) analyzerType { return a }), + lo.Map(ag.postAnalyzers, func(a PostAnalyzer, _ int) analyzerType { return a })..., + ) + + for _, a := range allAnalyzers { // Skip disabled analyzers if slices.Contains(disabled, a.Type()) { continue @@ -563,14 +567,6 @@ func (ag AnalyzerGroup) StaticPaths(disabled []Type) ([]string, bool) { paths = append(paths, staticPathAnalyzer.StaticPaths()...) } - // PostAnalyzers don't implement StaticPathAnalyzer. - // So if at least one postAnalyzer is enabled - we should not use StaticPath. - if allPostAnalyzersDisabled := lo.EveryBy(ag.postAnalyzers, func(a PostAnalyzer) bool { - return slices.Contains(disabled, a.Type()) - }); !allPostAnalyzersDisabled { - return nil, false - } - // Remove duplicates return lo.Uniq(paths), true } diff --git a/pkg/fanal/analyzer/analyzer_test.go b/pkg/fanal/analyzer/analyzer_test.go index d096b32b1cab..f0946ae5a7aa 100644 --- a/pkg/fanal/analyzer/analyzer_test.go +++ b/pkg/fanal/analyzer/analyzer_test.go @@ -2,6 +2,7 @@ package analyzer_test import ( "os" + "slices" "sync" "testing" @@ -24,6 +25,7 @@ import ( _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/alpine" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/ubuntu" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/pkg/apk" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/pkg/dpkg" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/repo/apk" _ "github.com/aquasecurity/trivy/pkg/fanal/handler/all" _ "modernc.org/sqlite" @@ -531,12 +533,13 @@ func TestAnalyzerGroup_AnalyzeFile(t *testing.T) { ctx := t.Context() err = a.AnalyzeFile(ctx, &wg, limit, got, "", tt.args.filePath, info, func() (xio.ReadSeekCloserAt, error) { - if tt.args.testFilePath == "testdata/error" { + switch tt.args.testFilePath { + case "testdata/error": return nil, xerrors.New("error") - } else if tt.args.testFilePath == "testdata/no-permission" { - os.Chmod(tt.args.testFilePath, 0000) + case "testdata/no-permission": + os.Chmod(tt.args.testFilePath, 0o000) t.Cleanup(func() { - os.Chmod(tt.args.testFilePath, 0644) + os.Chmod(tt.args.testFilePath, 0o644) }) } return os.Open(tt.args.testFilePath) @@ -661,14 +664,16 @@ func TestAnalyzerGroup_AnalyzerVersions(t *testing.T) { disabled: []analyzer.Type{}, want: analyzer.Versions{ Analyzers: map[string]int{ - "alpine": 1, - "apk-repo": 1, - "apk": 2, - "bundler": 1, - "ubuntu": 1, - "ubuntu-esm": 1, + "alpine": 1, + "apk-repo": 1, + "apk": 2, + "bundler": 1, + "dpkg-license": 1, + "ubuntu": 1, + "ubuntu-esm": 1, }, PostAnalyzers: map[string]int{ + "dpkg": 5, "jar": 1, "poetry": 1, }, @@ -679,6 +684,8 @@ func TestAnalyzerGroup_AnalyzerVersions(t *testing.T) { disabled: []analyzer.Type{ analyzer.TypeAlpine, analyzer.TypeApkRepo, + analyzer.TypeDpkg, + analyzer.TypeDpkgLicense, analyzer.TypeUbuntu, analyzer.TypeUbuntuESM, analyzer.TypeJar, @@ -705,3 +712,72 @@ func TestAnalyzerGroup_AnalyzerVersions(t *testing.T) { }) } } + +// TestAnalyzerGroup_StaticPaths tests the StaticPaths method of AnalyzerGroup +func TestAnalyzerGroup_StaticPaths(t *testing.T) { + tests := []struct { + name string + disabledAnalyzers []analyzer.Type + filePatterns []string + want []string + wantAllStatic bool + }{ + { + name: "all analyzers including post-analyzers implement StaticPathAnalyzer", + disabledAnalyzers: []analyzer.Type{analyzer.TypeApkCommand, analyzer.TypeJar, analyzer.TypePoetry, analyzer.TypeBundler}, + want: []string{ + "etc/apk/repositories", + "etc/lsb-release", + "lib/apk/db/installed", + "etc/alpine-release", + + "usr/share/doc/", + "var/lib/dpkg/status", + "var/lib/dpkg/status.d/", + "var/lib/dpkg/available", + "var/lib/dpkg/info/", + "var/lib/ubuntu-advantage/status.json", + }, + wantAllStatic: true, + }, + { + name: "all analyzers implement StaticPathAnalyzer, but there is file pattern", + disabledAnalyzers: []analyzer.Type{analyzer.TypeApkCommand, analyzer.TypeJar, analyzer.TypePoetry, analyzer.TypeBundler}, + filePatterns: []string{ + "alpine:etc/alpine-release-custom", + }, + want: []string{}, + wantAllStatic: false, + }, + { + name: "some analyzers don't implement StaticPathAnalyzer", + want: []string{}, + wantAllStatic: false, + }, + { + name: "disable all analyzers", + disabledAnalyzers: slices.Concat(analyzer.TypeOSes, analyzer.TypeLanguages), + want: []string{}, + wantAllStatic: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a new analyzer group + a, err := analyzer.NewAnalyzerGroup(analyzer.AnalyzerOptions{ + FilePatterns: tt.filePatterns, + }) + require.NoError(t, err) + + // Get static paths + gotPaths, gotAllStatic := a.StaticPaths(tt.disabledAnalyzers) + + // Check if all analyzers implement StaticPathAnalyzer + assert.Equal(t, tt.wantAllStatic, gotAllStatic) + + // Check paths + assert.ElementsMatch(t, tt.want, gotPaths) + }) + } +} diff --git a/pkg/fanal/analyzer/buildinfo/dockerfile.go b/pkg/fanal/analyzer/buildinfo/dockerfile.go index f0dfbaa4cc55..a4e5baded691 100644 --- a/pkg/fanal/analyzer/buildinfo/dockerfile.go +++ b/pkg/fanal/analyzer/buildinfo/dockerfile.go @@ -60,10 +60,11 @@ func (a dockerfileAnalyzer) Analyze(_ context.Context, target analyzer.AnalysisI } key := strings.ToLower(workResult.Result) - if key == "com.redhat.component" || key == "bzcomponent" { + switch key { + case "com.redhat.component", "bzcomponent": workResult, err = shlex.ProcessWordWithMatches(kvp.Value, envs) component = workResult.Result - } else if key == "architecture" { + case "architecture": workResult, err = shlex.ProcessWordWithMatches(kvp.Value, envs) arch = workResult.Result } diff --git a/pkg/fanal/analyzer/config/terraformplan/snapshot/snapshot.go b/pkg/fanal/analyzer/config/terraformplan/snapshot/snapshot.go index 13316914874d..5bac997e6a11 100644 --- a/pkg/fanal/analyzer/config/terraformplan/snapshot/snapshot.go +++ b/pkg/fanal/analyzer/config/terraformplan/snapshot/snapshot.go @@ -32,6 +32,6 @@ func newTerraformPlanSnapshotConfigAnalyzer(opts analyzer.AnalyzerOptions) (anal return &terraformPlanConfigAnalyzer{Analyzer: a}, nil } -func (*terraformPlanConfigAnalyzer) Required(filePath string, fi os.FileInfo) bool { +func (*terraformPlanConfigAnalyzer) Required(filePath string, _ os.FileInfo) bool { return filepath.Ext(filePath) == ".tfplan" || filepath.Base(filePath) == "tfplan" } diff --git a/pkg/fanal/analyzer/const.go b/pkg/fanal/analyzer/const.go index 13f5fa650748..387826d97439 100644 --- a/pkg/fanal/analyzer/const.go +++ b/pkg/fanal/analyzer/const.go @@ -28,12 +28,13 @@ const ( TypeUbuntuESM Type = "ubuntu-esm" // OS Package - TypeApk Type = "apk" - TypeDpkg Type = "dpkg" - TypeDpkgLicense Type = "dpkg-license" // For analyzing licenses - TypeRpm Type = "rpm" - TypeRpmArchive Type = "rpm-archive" - TypeRpmqa Type = "rpmqa" + TypeApk Type = "apk" + TypeBottlerocketInventory Type = "bottlerocket-inventory" + TypeDpkg Type = "dpkg" + TypeDpkgLicense Type = "dpkg-license" // For analyzing licenses + TypeRpm Type = "rpm" + TypeRpmArchive Type = "rpm-archive" + TypeRpmqa Type = "rpmqa" // OS Package Repository TypeApkRepo Type = "apk-repo" @@ -164,12 +165,15 @@ var ( TypeRedHatBase, TypeSUSE, TypeUbuntu, + TypeUbuntuESM, TypeApk, + TypeBottlerocketInventory, TypeDpkg, TypeDpkgLicense, TypeRpm, TypeRpmqa, TypeApkRepo, + TypeApkCommand, } // TypeLanguages has all language analyzers diff --git a/pkg/fanal/analyzer/fs.go b/pkg/fanal/analyzer/fs.go index 4fec721e3cf6..244bd5bdaa65 100644 --- a/pkg/fanal/analyzer/fs.go +++ b/pkg/fanal/analyzer/fs.go @@ -32,7 +32,7 @@ func NewCompositeFS() (*CompositeFS, error) { } // CopyFileToTemp takes a file path and information, opens the file, copies its contents to a temporary file -func (c *CompositeFS) CopyFileToTemp(opener Opener, info os.FileInfo) (string, error) { +func (c *CompositeFS) CopyFileToTemp(opener Opener, _ os.FileInfo) (string, error) { // Create a temporary file to which the file in the layer will be copied // so that all the files will not be loaded into memory f, err := os.CreateTemp(c.dir, "file-*") @@ -54,7 +54,7 @@ func (c *CompositeFS) CopyFileToTemp(opener Opener, info os.FileInfo) (string, e } // Use 0600 instead of file permissions to avoid errors when a file uses incorrect permissions (e.g. 0044). - if err = os.Chmod(f.Name(), 0600); err != nil { + if err = os.Chmod(f.Name(), 0o600); err != nil { return "", xerrors.Errorf("chmod error: %w", err) } diff --git a/pkg/fanal/analyzer/imgconf/apk/apk_test.go b/pkg/fanal/analyzer/imgconf/apk/apk_test.go index 35e2016e9dbb..f03faf4522b6 100644 --- a/pkg/fanal/analyzer/imgconf/apk/apk_test.go +++ b/pkg/fanal/analyzer/imgconf/apk/apk_test.go @@ -1362,7 +1362,7 @@ var ( ) func TestAnalyze(t *testing.T) { - testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { content, err := os.ReadFile("testdata/history_v3.9.json") if err != nil { http.Error(res, err.Error(), http.StatusInternalServerError) diff --git a/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile.go b/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile.go index 5dcd0fc19a7b..6dea2cd81e82 100644 --- a/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile.go +++ b/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile.go @@ -58,7 +58,7 @@ func (a *historyAnalyzer) Analyze(ctx context.Context, input analyzer.ConfigAnal fsys := mapfs.New() if err := fsys.WriteVirtualFile( - "Dockerfile", imageConfigToDockerfile(input.Config), 0600); err != nil { + "Dockerfile", imageConfigToDockerfile(input.Config), 0o600); err != nil { return nil, xerrors.Errorf("mapfs write error: %w", err) } diff --git a/pkg/fanal/analyzer/language/c/conan/conan.go b/pkg/fanal/analyzer/language/c/conan/conan.go index ddce8063d67e..a671b8a4f58c 100644 --- a/pkg/fanal/analyzer/language/c/conan/conan.go +++ b/pkg/fanal/analyzer/language/c/conan/conan.go @@ -44,7 +44,7 @@ func newConanLockAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, er } func (a conanLockAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { - required := func(filePath string, d fs.DirEntry) bool { + required := func(_ string, _ fs.DirEntry) bool { // Parse all required files: `conan.lock` (from a.Required func) + input.FilePatterns.Match() return true } @@ -92,12 +92,12 @@ func licensesFromCache() (map[string]string, error) { return nil, err } - required := func(filePath string, d fs.DirEntry) bool { + required := func(filePath string, _ fs.DirEntry) bool { return filepath.Base(filePath) == "conanfile.py" } licenses := make(map[string]string) - if err := fsutils.WalkDir(os.DirFS(cacheDir), ".", required, func(filePath string, _ fs.DirEntry, r io.Reader) error { + if err := fsutils.WalkDir(os.DirFS(cacheDir), ".", required, func(_ string, _ fs.DirEntry, r io.Reader) error { scanner := bufio.NewScanner(r) var name, license string for scanner.Scan() { diff --git a/pkg/fanal/analyzer/language/dart/pub/pubspec.go b/pkg/fanal/analyzer/language/dart/pub/pubspec.go index 783c88f01a44..85fb25f2373e 100644 --- a/pkg/fanal/analyzer/language/dart/pub/pubspec.go +++ b/pkg/fanal/analyzer/language/dart/pub/pubspec.go @@ -54,7 +54,7 @@ func (a pubSpecLockAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostA a.logger.Warn("Unable to parse cache dir", log.Err(err)) } - required := func(path string, d fs.DirEntry) bool { + required := func(_ string, _ fs.DirEntry) bool { // Parse all required files: `pubspec.lock` (from a.Required func) + input.FilePatterns.Match() return true } @@ -107,12 +107,12 @@ func (a pubSpecLockAnalyzer) findDependsOn() (map[string][]string, error) { return nil, nil } - required := func(path string, d fs.DirEntry) bool { + required := func(path string, _ fs.DirEntry) bool { return filepath.Base(path) == pubSpecYamlFileName } deps := make(map[string][]string) - if err := fsutils.WalkDir(os.DirFS(dir), ".", required, func(path string, d fs.DirEntry, r io.Reader) error { + if err := fsutils.WalkDir(os.DirFS(dir), ".", required, func(path string, _ fs.DirEntry, r io.Reader) error { id, dependsOn, err := parsePubSpecYaml(r) if err != nil { a.logger.Debug("Unable to parse pubspec.yaml", log.FilePath(path), log.Err(err)) diff --git a/pkg/fanal/analyzer/language/dotnet/nuget/nuget.go b/pkg/fanal/analyzer/language/dotnet/nuget/nuget.go index 68b332d31e03..61d359a4fb15 100644 --- a/pkg/fanal/analyzer/language/dotnet/nuget/nuget.go +++ b/pkg/fanal/analyzer/language/dotnet/nuget/nuget.go @@ -59,12 +59,12 @@ func (a *nugetLibraryAnalyzer) PostAnalyze(_ context.Context, input analyzer.Pos a.logger.Debug("The nuget packages directory couldn't be found. License search disabled") } - required := func(path string, d fs.DirEntry) bool { + required := func(_ string, _ fs.DirEntry) bool { // Parse all required files: `packages.lock.json`, `packages.config` (from a.Required func) + input.FilePatterns.Match() return true } - err := fsutils.WalkDir(input.FS, ".", required, func(path string, d fs.DirEntry, r io.Reader) error { + err := fsutils.WalkDir(input.FS, ".", required, func(path string, _ fs.DirEntry, r io.Reader) error { // Set the default parser parser := a.lockParser diff --git a/pkg/fanal/analyzer/language/elixir/mix/mix_test.go b/pkg/fanal/analyzer/language/elixir/mix/mix_test.go index 39e44a128db5..b450abe9269d 100644 --- a/pkg/fanal/analyzer/language/elixir/mix/mix_test.go +++ b/pkg/fanal/analyzer/language/elixir/mix/mix_test.go @@ -58,7 +58,7 @@ func Test_mixLockAnalyzer_Analyze(t *testing.T) { }() a := mixLockAnalyzer{} - got, err := a.Analyze(nil, analyzer.AnalysisInput{ + got, err := a.Analyze(t.Context(), analyzer.AnalysisInput{ FilePath: tt.inputFile, Content: f, }) diff --git a/pkg/fanal/analyzer/language/golang/binary/binary_test.go b/pkg/fanal/analyzer/language/golang/binary/binary_test.go index 352c918a9286..f4a7a4242a5c 100644 --- a/pkg/fanal/analyzer/language/golang/binary/binary_test.go +++ b/pkg/fanal/analyzer/language/golang/binary/binary_test.go @@ -125,5 +125,4 @@ func Test_gobinaryLibraryAnalyzer_Required(t *testing.T) { assert.Equal(t, tt.want, got, fileInfo.Mode().Perm()) }) } - } diff --git a/pkg/fanal/analyzer/language/golang/mod/mod.go b/pkg/fanal/analyzer/language/golang/mod/mod.go index b0c8958a0393..cd2dd928f03f 100644 --- a/pkg/fanal/analyzer/language/golang/mod/mod.go +++ b/pkg/fanal/analyzer/language/golang/mod/mod.go @@ -1,6 +1,7 @@ package mod import ( + "cmp" "context" "errors" "fmt" @@ -25,6 +26,7 @@ import ( "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/utils/fsutils" xio "github.com/aquasecurity/trivy/pkg/x/io" + xpath "github.com/aquasecurity/trivy/pkg/x/path" ) func init() { @@ -67,11 +69,11 @@ func newGoModAnalyzer(opt analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, erro func (a *gomodAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { var apps []types.Application - required := func(path string, d fs.DirEntry) bool { + required := func(path string, _ fs.DirEntry) bool { return filepath.Base(path) == types.GoMod || input.FilePatterns.Match(path) } - err := fsutils.WalkDir(input.FS, ".", required, func(path string, d fs.DirEntry, _ io.Reader) error { + err := fsutils.WalkDir(input.FS, ".", required, func(path string, _ fs.DirEntry, _ io.Reader) error { // Parse go.mod gomod, err := parse(input.FS, path, a.modParser) if err != nil { @@ -97,7 +99,7 @@ func (a *gomodAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalys return nil, xerrors.Errorf("walk error: %w", err) } - if err = a.fillAdditionalData(apps); err != nil { + if err = a.fillAdditionalData(input.FS, apps); err != nil { a.logger.Warn("Unable to collect additional info", log.Err(err)) } @@ -111,7 +113,20 @@ func (a *gomodAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalys func (a *gomodAnalyzer) Required(filePath string, _ os.FileInfo) bool { fileName := filepath.Base(filePath) - return slices.Contains(requiredFiles, fileName) + + // Save required files (go.mod/go.sum) + // Note: vendor directory doesn't contain these files, so we can skip checking for this. + // See: https://github.com/aquasecurity/trivy/issues/8527#issuecomment-2777848027 + if slices.Contains(requiredFiles, fileName) { + return true + } + + // Save license files from vendor directory + if licenseRegexp.MatchString(fileName) && xpath.Contains(filePath, "vendor") { + return true + } + + return false } func (a *gomodAnalyzer) Type() analyzer.Type { @@ -123,96 +138,117 @@ func (a *gomodAnalyzer) Version() int { } // fillAdditionalData collects licenses and dependency relationships, then update applications. -func (a *gomodAnalyzer) fillAdditionalData(apps []types.Application) error { - gopath := os.Getenv("GOPATH") - if gopath == "" { - gopath = build.Default.GOPATH - } +func (a *gomodAnalyzer) fillAdditionalData(fsys fs.FS, apps []types.Application) error { + var modSearchDirs []searchDir // $GOPATH/pkg/mod - modPath := filepath.Join(gopath, "pkg", "mod") - if !fsutils.DirExists(modPath) { - a.logger.Debug("GOPATH not found. Need 'go mod download' to fill licenses and dependency relationships", - log.String("GOPATH", modPath)) - return nil + if gopath, err := newGOPATH(); err != nil { + a.logger.Debug("GOPATH not found. Run 'go mod download' or 'go mod tidy' for identifying dependency graph and licenses", log.Err(err)) + } else { + modSearchDirs = append(modSearchDirs, gopath) } licenses := make(map[string][]string) for i, app := range apps { + licenseSearchDirs := modSearchDirs + + // vendor directory next to go.mod + if vendorDir, err := newVendorDir(fsys, app.FilePath); err == nil { + licenseSearchDirs = append(licenseSearchDirs, vendorDir) + } + // Actually used dependencies usedPkgs := lo.SliceToMap(app.Packages, func(pkg types.Package) (string, types.Package) { return pkg.Name, pkg }) - for j, lib := range app.Packages { - if l, ok := licenses[lib.ID]; ok { - // Fill licenses - apps[i].Packages[j].Licenses = l - continue - } - // e.g. $GOPATH/pkg/mod/github.com/aquasecurity/go-dep-parser@v1.0.0 - modDir := filepath.Join(modPath, fmt.Sprintf("%s@%s", normalizeModName(lib.Name), lib.Version)) + // Check if either $GOPATH/pkg/mod or the vendor directory exists + if len(licenseSearchDirs) == 0 { + continue + } + for j, pkg := range app.Packages { // Collect licenses - if licenseNames, err := findLicense(modDir, a.licenseClassifierConfidenceLevel); err != nil { - return xerrors.Errorf("license error: %w", err) + if licenseNames, err := findLicense(licenseSearchDirs, pkg, licenses, a.licenseClassifierConfidenceLevel); err != nil { + return xerrors.Errorf("unable to collect license: %w", err) } else { - // Cache the detected licenses - licenses[lib.ID] = licenseNames - // Fill licenses apps[i].Packages[j].Licenses = licenseNames } - // Collect dependencies of the direct dependency - if dep, err := a.collectDeps(modDir, lib.ID); err != nil { + // Collect dependencies of the direct dependency from $GOPATH/pkg/mod because the vendor directory doesn't have go.mod files. + dep, err := a.collectDeps(modSearchDirs, pkg) + if err != nil { return xerrors.Errorf("dependency graph error: %w", err) } else if dep.ID == "" { // go.mod not found continue - } else { - // Filter out unused dependencies and convert module names to module IDs - apps[i].Packages[j].DependsOn = lo.FilterMap(dep.DependsOn, func(modName string, _ int) (string, bool) { - if m, ok := usedPkgs[modName]; !ok { - return "", false - } else { - return m.ID, true - } - }) } + // Filter out unused dependencies and convert module names to module IDs + apps[i].Packages[j].DependsOn = lo.FilterMap(dep.DependsOn, func(modName string, _ int) (string, bool) { + m, ok := usedPkgs[modName] + if !ok { + return "", false + } + return m.ID, true + }) } } return nil } -func (a *gomodAnalyzer) collectDeps(modDir, pkgID string) (types.Dependency, error) { - // e.g. $GOPATH/pkg/mod/github.com/aquasecurity/go-dep-parser@v0.0.0-20220406074731-71021a481237/go.mod - modPath := filepath.Join(modDir, "go.mod") - f, err := os.Open(modPath) - if errors.Is(err, fs.ErrNotExist) { - a.logger.Debug("Unable to identify dependencies as it doesn't support Go modules", - log.String("module", pkgID)) - return types.Dependency{}, nil - } else if err != nil { - return types.Dependency{}, xerrors.Errorf("file open error: %w", err) +func (a *gomodAnalyzer) collectDeps(searchDirs []searchDir, pkg types.Package) (types.Dependency, error) { + for _, searchDir := range searchDirs { + // e.g. $GOPATH/pkg/mod/github.com/aquasecurity/go-dep-parser@v0.1.0 + modDir, err := searchDir.Resolve(pkg) + if err != nil { + continue + } + + dependsOn, err := a.resolveDeps(modDir) + if errors.Is(err, fs.ErrNotExist) { + a.logger.Debug("Unable to identify dependencies as it doesn't support Go modules", + log.String("module", pkg.ID)) + return types.Dependency{}, nil + } else if err != nil { + return types.Dependency{}, xerrors.Errorf("resolve deps error: %w", err) + } + + return types.Dependency{ + ID: pkg.ID, + DependsOn: dependsOn, + }, nil + } + return types.Dependency{}, nil +} + +// resolveDeps parses go.mod under $GOPATH/pkg/mod and returns the dependencies +func (a *gomodAnalyzer) resolveDeps(modDir fs.FS) ([]string, error) { + // e.g. $GOPATH/pkg/mod/github.com/aquasecurity/go-dep-parser@v0.1.0/go.mod + f, err := modDir.Open("go.mod") + if err != nil { + return nil, xerrors.Errorf("file open error: %w", err) } defer f.Close() + file, ok := f.(xio.ReadSeekCloserAt) + if !ok { + return nil, xerrors.Errorf("type assertion error: %w", err) + } + // Parse go.mod under $GOPATH/pkg/mod - pkgs, _, err := a.leafModParser.Parse(f) + pkgs, _, err := a.leafModParser.Parse(file) if err != nil { - return types.Dependency{}, xerrors.Errorf("%s parse error: %w", modPath, err) + return nil, xerrors.Errorf("parse error: %w", err) } // Filter out indirect dependencies - dependsOn := lo.FilterMap(pkgs, func(lib types.Package, index int) (string, bool) { + dependsOn := lo.FilterMap(pkgs, func(lib types.Package, _ int) (string, bool) { return lib.Name, lib.Relationship == types.RelationshipDirect }) - return types.Dependency{ - ID: pkgID, - DependsOn: dependsOn, - }, nil + return dependsOn, nil + } // addOrphanIndirectDepsUnderRoot handles indirect dependencies that have no identifiable parent packages in the dependency tree. @@ -301,47 +337,60 @@ func mergeGoSum(gomod, gosum *types.Application) { gomod.Packages = lo.Values(uniq) } -func findLicense(dir string, classifierConfidenceLevel float64) ([]string, error) { +func findLicense(searchDirs []searchDir, pkg types.Package, licenses map[string][]string, classifierConfidenceLevel float64) ([]string, error) { + if licenses[pkg.ID] != nil { + return licenses[pkg.ID], nil + } + var license *types.LicenseFile - err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { + for _, searchDir := range searchDirs { + sub, err := searchDir.Resolve(pkg) if err != nil { - return err - } else if !d.Type().IsRegular() { - return nil + continue } - if !licenseRegexp.MatchString(filepath.Base(path)) { + + err = fs.WalkDir(sub, ".", func(path string, d fs.DirEntry, err error) error { + switch { + case err != nil: + return err + case !d.Type().IsRegular(): + return nil + case !licenseRegexp.MatchString(filepath.Base(path)): + return nil + } + + // e.g. $GOPATH/pkg/mod/github.com/aquasecurity/go-dep-parser@v0.1.0/LICENSE + f, err := sub.Open(path) + if err != nil { + return xerrors.Errorf("file (%s) open error: %w", path, err) + } + defer f.Close() + + if l, err := licensing.Classify(path, f, classifierConfidenceLevel); err != nil { + return xerrors.Errorf("license classify error: %w", err) + } else if l != nil && len(l.Findings) > 0 { // License found + license = l + return fs.SkipAll // Stop walking + } return nil - } - // e.g. $GOPATH/pkg/mod/github.com/aquasecurity/go-dep-parser@v0.0.0-20220406074731-71021a481237/LICENSE - f, err := os.Open(path) - if err != nil { - return xerrors.Errorf("file (%s) open error: %w", path, err) - } - defer f.Close() + }) - l, err := licensing.Classify(path, f, classifierConfidenceLevel) - if err != nil { - return xerrors.Errorf("license classify error: %w", err) - } - // License found - if l != nil && len(l.Findings) > 0 { - license = l - return io.EOF + switch { + // The module path may not exist + case errors.Is(err, os.ErrNotExist): + continue + case err != nil && !errors.Is(err, io.EOF): + return nil, xerrors.Errorf("unable to find a known open source license: %w", err) + case license == nil || len(license.Findings) == 0: + continue } - return nil - }) - switch { - // The module path may not exist - case errors.Is(err, os.ErrNotExist): - return nil, nil - case err != nil && !errors.Is(err, io.EOF): - return nil, fmt.Errorf("finding a known open source license: %w", err) - case license == nil || len(license.Findings) == 0: - return nil, nil + // License found + licenseNames := license.Findings.Names() + licenses[pkg.ID] = licenseNames + return licenseNames, nil } - - return license.Findings.Names(), nil + return nil, nil } // normalizeModName escapes upper characters @@ -358,3 +407,58 @@ func normalizeModName(name string) string { } return string(newName) } + +type searchDir interface { + Resolve(pkg types.Package) (fs.FS, error) +} + +type gopathDir struct { + root string +} + +func newGOPATH() (searchDir, error) { + gopath := cmp.Or(os.Getenv("GOPATH"), build.Default.GOPATH) + + // $GOPATH/pkg/mod + modPath := filepath.Join(gopath, "pkg", "mod") + if !fsutils.DirExists(modPath) { + return nil, xerrors.Errorf("GOPATH not found: %s", modPath) + } + return &gopathDir{root: modPath}, nil +} + +// Resolve resolves the module directory for a given package. +// It adds the version suffix to the module name and returns the directory as an fs.FS. +// e.g. $GOPATH/pkg/mod => $GOPATH/pkg/mod/github.com/aquasecurity/go-dep-parser@v1.0.0 +func (d *gopathDir) Resolve(pkg types.Package) (fs.FS, error) { + name := normalizeModName(pkg.Name) + + // Add version suffix for packages from $GOPATH + // e.g. github.com/aquasecurity/go-dep-parser@v1.0.0 + modDirName := fmt.Sprintf("%s@%s", name, pkg.Version) + + // e.g. $GOPATH/pkg/mod/github.com/aquasecurity/go-dep-parser@v1.0.0 + modDir := filepath.Join(d.root, modDirName) + return os.DirFS(modDir), nil +} + +type vendorDir struct { + root fs.FS +} + +func newVendorDir(fsys fs.FS, modPath string) (vendorDir, error) { + // vendor directory is in the same directory as go.mod + vendor := filepath.Join(filepath.Dir(modPath), "vendor") + sub, err := fs.Sub(fsys, vendor) + if err != nil { + return vendorDir{}, xerrors.Errorf("vendor directory not found: %w", err) + } + return vendorDir{root: sub}, nil +} + +// Resolve resolves the module directory for a given package. +// It doesn't add the version suffix to the module name. +// e.g. vendor/ => vendor/github.com/aquasecurity/go-dep-parser +func (d vendorDir) Resolve(pkg types.Package) (fs.FS, error) { + return fs.Sub(d.root, normalizeModName(pkg.Name)) +} diff --git a/pkg/fanal/analyzer/language/golang/mod/mod_test.go b/pkg/fanal/analyzer/language/golang/mod/mod_test.go index 075c0dc24ac1..4d4dcdccfe24 100644 --- a/pkg/fanal/analyzer/language/golang/mod/mod_test.go +++ b/pkg/fanal/analyzer/language/golang/mod/mod_test.go @@ -284,6 +284,60 @@ func Test_gomodAnalyzer_Analyze(t *testing.T) { }, want: &analyzer.AnalysisResult{}, }, + { + name: "deps from GOPATH and license from vendor dir", + files: []string{ + "testdata/vendor-dir-exists/mod", + "testdata/vendor-dir-exists/vendor", + }, + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.GoModule, + FilePath: "go.mod", + Packages: types.Packages{ + { + ID: "github.com/org/repo", + Name: "github.com/org/repo", + Relationship: types.RelationshipRoot, + DependsOn: []string{ + "github.com/aquasecurity/go-dep-parser@v0.0.1", + }, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/org/repo", + }, + }, + }, + { + ID: "github.com/aquasecurity/go-dep-parser@v0.0.1", + Name: "github.com/aquasecurity/go-dep-parser", + Version: "v0.0.1", + Relationship: types.RelationshipDirect, + Licenses: []string{"Apache-2.0"}, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/aquasecurity/go-dep-parser", + }, + }, + DependsOn: []string{ + "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", + }, + }, + { + ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", + Name: "golang.org/x/xerrors", + Version: "v0.0.0-20200804184101-5ec99f83aff1", + Relationship: types.RelationshipIndirect, + Indirect: true, + }, + }, + }, + }, + }, + }, } for _, tt := range tests { t.Setenv("GOPATH", "testdata") @@ -294,10 +348,13 @@ func Test_gomodAnalyzer_Analyze(t *testing.T) { mfs := mapfs.New() for _, file := range tt.files { // Since broken go.mod files bothers IDE, we should use other file names than "go.mod" and "go.sum". - if filepath.Base(file) == "mod" { + switch filepath.Base(file) { + case "mod": require.NoError(t, mfs.WriteFile("go.mod", file)) - } else if filepath.Base(file) == "sum" { + case "sum": require.NoError(t, mfs.WriteFile("go.sum", file)) + case "vendor": + require.NoError(t, mfs.CopyDir(file, ".")) } } diff --git a/pkg/fanal/analyzer/language/golang/mod/testdata/pkg/mod/github.com/aquasecurity/go-dep-parser@v0.0.1/go.mod b/pkg/fanal/analyzer/language/golang/mod/testdata/pkg/mod/github.com/aquasecurity/go-dep-parser@v0.0.1/go.mod new file mode 100644 index 000000000000..578bed18686e --- /dev/null +++ b/pkg/fanal/analyzer/language/golang/mod/testdata/pkg/mod/github.com/aquasecurity/go-dep-parser@v0.0.1/go.mod @@ -0,0 +1,7 @@ +module github.com/aquasecurity/go-dep-parser + +go 1.18 + +require ( + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 +) \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/golang/mod/testdata/vendor-dir-exists/mod b/pkg/fanal/analyzer/language/golang/mod/testdata/vendor-dir-exists/mod new file mode 100644 index 000000000000..55585492f747 --- /dev/null +++ b/pkg/fanal/analyzer/language/golang/mod/testdata/vendor-dir-exists/mod @@ -0,0 +1,7 @@ +module github.com/org/repo + +go 1.17 + +require github.com/aquasecurity/go-dep-parser v0.0.1 + +require golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/golang/mod/testdata/vendor-dir-exists/vendor/github.com/aquasecurity/go-dep-parser/LICENSE b/pkg/fanal/analyzer/language/golang/mod/testdata/vendor-dir-exists/vendor/github.com/aquasecurity/go-dep-parser/LICENSE new file mode 100644 index 000000000000..f49a4e16e68b --- /dev/null +++ b/pkg/fanal/analyzer/language/golang/mod/testdata/vendor-dir-exists/vendor/github.com/aquasecurity/go-dep-parser/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/java/gradle/lockfile.go b/pkg/fanal/analyzer/language/java/gradle/lockfile.go index c75cd04d287a..7ad6615a8ea9 100644 --- a/pkg/fanal/analyzer/language/java/gradle/lockfile.go +++ b/pkg/fanal/analyzer/language/java/gradle/lockfile.go @@ -48,7 +48,7 @@ func (a gradleLockAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAn a.logger.Warn("Unable to get licenses and dependencies", log.Err(err)) } - required := func(path string, d fs.DirEntry) bool { + required := func(_ string, _ fs.DirEntry) bool { // Parse all required files: `*gradle.lockfile` (from a.Required func) + input.FilePatterns.Match() return true } diff --git a/pkg/fanal/analyzer/language/java/gradle/pom.go b/pkg/fanal/analyzer/language/java/gradle/pom.go index fcad9969b306..de40ce743c61 100644 --- a/pkg/fanal/analyzer/language/java/gradle/pom.go +++ b/pkg/fanal/analyzer/language/java/gradle/pom.go @@ -72,7 +72,7 @@ func (a gradleLockAnalyzer) parsePoms() (map[string]pomXML, error) { return nil, nil } - required := func(path string, d fs.DirEntry) bool { + required := func(path string, _ fs.DirEntry) bool { return filepath.Ext(path) == ".pom" } diff --git a/pkg/fanal/analyzer/language/java/pom/pom_test.go b/pkg/fanal/analyzer/language/java/pom/pom_test.go index 53896d518e68..3c834f4497aa 100644 --- a/pkg/fanal/analyzer/language/java/pom/pom_test.go +++ b/pkg/fanal/analyzer/language/java/pom/pom_test.go @@ -182,7 +182,7 @@ func Test_pomAnalyzer_Analyze(t *testing.T) { defer f.Close() a := pomAnalyzer{} - got, err := a.Analyze(nil, analyzer.AnalysisInput{ + got, err := a.Analyze(t.Context(), analyzer.AnalysisInput{ Dir: tt.inputDir, FilePath: tt.inputFile, Content: f, diff --git a/pkg/fanal/analyzer/language/julia/pkg/pkg.go b/pkg/fanal/analyzer/language/julia/pkg/pkg.go index 2161964e2aa0..411e07723480 100644 --- a/pkg/fanal/analyzer/language/julia/pkg/pkg.go +++ b/pkg/fanal/analyzer/language/julia/pkg/pkg.go @@ -53,11 +53,11 @@ func newJuliaAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) func (a juliaAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { var apps []types.Application - required := func(path string, d fs.DirEntry) bool { + required := func(path string, _ fs.DirEntry) bool { return filepath.Base(path) == types.JuliaManifest || input.FilePatterns.Match(path) } - err := fsutils.WalkDir(input.FS, ".", required, func(path string, d fs.DirEntry, r io.Reader) error { + err := fsutils.WalkDir(input.FS, ".", required, func(path string, _ fs.DirEntry, r io.Reader) error { // Parse Manifest.toml app, err := a.parseJuliaManifest(path, r) if err != nil { diff --git a/pkg/fanal/analyzer/language/nodejs/license/license.go b/pkg/fanal/analyzer/language/nodejs/license/license.go index b6a0f3fb7678..d70dd92ea741 100644 --- a/pkg/fanal/analyzer/language/nodejs/license/license.go +++ b/pkg/fanal/analyzer/language/nodejs/license/license.go @@ -32,7 +32,7 @@ func NewLicense(classifierConfidenceLevel float64) *License { func (l *License) Traverse(fsys fs.FS, root string) (map[string][]string, error) { licenses := make(map[string][]string) - walkDirFunc := func(pkgJSONPath string, d fs.DirEntry, r io.Reader) error { + walkDirFunc := func(pkgJSONPath string, _ fs.DirEntry, r io.Reader) error { pkg, err := l.parser.Parse(r) if err != nil { return xerrors.Errorf("unable to parse %q: %w", pkgJSONPath, err) diff --git a/pkg/fanal/analyzer/language/nodejs/npm/npm.go b/pkg/fanal/analyzer/language/nodejs/npm/npm.go index eecc06a06e46..0a2128f25f0e 100644 --- a/pkg/fanal/analyzer/language/nodejs/npm/npm.go +++ b/pkg/fanal/analyzer/language/nodejs/npm/npm.go @@ -51,7 +51,7 @@ func (a npmLibraryAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAn } var apps []types.Application - err := fsutils.WalkDir(input.FS, ".", required, func(filePath string, d fs.DirEntry, r io.Reader) error { + err := fsutils.WalkDir(input.FS, ".", required, func(filePath string, _ fs.DirEntry, _ io.Reader) error { // Find all licenses from package.json files under node_modules dirs licenses, err := a.findLicenses(input.FS, filePath) if err != nil { @@ -142,7 +142,7 @@ func (a npmLibraryAnalyzer) findLicenses(fsys fs.FS, lockPath string) (map[strin // Note that fs.FS is always slashed regardless of the platform, // and path.Join should be used rather than filepath.Join. licenses := make(map[string][]string) - err := fsutils.WalkDir(fsys, root, required, func(filePath string, d fs.DirEntry, r io.Reader) error { + err := fsutils.WalkDir(fsys, root, required, func(filePath string, _ fs.DirEntry, r io.Reader) error { pkg, err := a.packageParser.Parse(r) if err != nil { return xerrors.Errorf("unable to parse %q: %w", filePath, err) diff --git a/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm.go b/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm.go index a020697b0025..16c8c4c49ea2 100644 --- a/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm.go +++ b/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm.go @@ -44,11 +44,11 @@ func newPnpmAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) func (a pnpmAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { var apps []types.Application - required := func(path string, d fs.DirEntry) bool { + required := func(path string, _ fs.DirEntry) bool { return filepath.Base(path) == types.PnpmLock || input.FilePatterns.Match(path) } - err := fsutils.WalkDir(input.FS, ".", required, func(filePath string, d fs.DirEntry, r io.Reader) error { + err := fsutils.WalkDir(input.FS, ".", required, func(filePath string, _ fs.DirEntry, r io.Reader) error { // Find licenses licenses, err := a.findLicenses(input.FS, filePath) if err != nil { @@ -126,7 +126,7 @@ func (a pnpmAnalyzer) findLicenses(fsys fs.FS, lockPath string) (map[string][]st // Note that fs.FS is always slashed regardless of the platform, // and path.Join should be used rather than filepath.Join. licenses := make(map[string][]string) - err := fsutils.WalkDir(fsys, root, required, func(filePath string, d fs.DirEntry, r io.Reader) error { + err := fsutils.WalkDir(fsys, root, required, func(filePath string, _ fs.DirEntry, r io.Reader) error { pkg, err := a.packageJsonParser.Parse(r) if err != nil { return xerrors.Errorf("unable to parse %q: %w", filePath, err) diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/packages/package2/package.json b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/packages/package2/package.json index 29b60ba87602..cd5d2069c69c 100644 --- a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/packages/package2/package.json +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/packages/package2/package.json @@ -1,7 +1,5 @@ { - "name": "package2", "private": true, - "version": "0.0.0", "type": "module", "dependencies": { "is-odd": "^3.0.1" diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go b/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go index c93207bf9e7c..5961dcafda9f 100644 --- a/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go +++ b/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go @@ -2,6 +2,7 @@ package yarn import ( "archive/zip" + "cmp" "context" "errors" "io" @@ -27,6 +28,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/nodejs/license" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/set" "github.com/aquasecurity/trivy/pkg/utils/fsutils" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -70,11 +72,11 @@ func (p *parserWithPatterns) Parse(r xio.ReadSeekerAt) ([]types.Package, []types func (a yarnAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { var apps []types.Application - required := func(path string, d fs.DirEntry) bool { + required := func(path string, _ fs.DirEntry) bool { return filepath.Base(path) == types.YarnLock || input.FilePatterns.Match(path) } - err := fsutils.WalkDir(input.FS, ".", required, func(filePath string, d fs.DirEntry, r io.Reader) error { + err := fsutils.WalkDir(input.FS, ".", required, func(filePath string, _ fs.DirEntry, r io.Reader) error { parser := &parserWithPatterns{} // Parse yarn.lock app, err := language.Parse(types.Yarn, filePath, r, parser) @@ -162,36 +164,28 @@ func (a yarnAnalyzer) Version() int { // distinguishing between direct and transitive dependencies as well as production and development dependencies. func (a yarnAnalyzer) analyzeDependencies(fsys fs.FS, dir string, app *types.Application, patterns map[string][]string) error { packageJsonPath := path.Join(dir, types.NpmPkg) - directDeps, directDevDeps, err := a.parsePackageJsonDependencies(fsys, packageJsonPath) + root, workspaces, err := a.parsePackageJSON(fsys, packageJsonPath) if errors.Is(err, fs.ErrNotExist) { a.logger.Debug("package.json not found", log.FilePath(packageJsonPath)) return nil } else if err != nil { - return xerrors.Errorf("unable to parse %s: %w", dir, err) + return xerrors.Errorf("unable to parse root package.json: %w", err) } - // yarn.lock file can contain same packages with different versions - // save versions separately for version comparison by comparator - pkgIDs := lo.SliceToMap(app.Packages, func(pkg types.Package) (string, types.Package) { + // Since yarn.lock file can contain same packages with different versions + // we need to save versions separately for version comparison. + pkgs := lo.SliceToMap(app.Packages, func(pkg types.Package) (string, types.Package) { return pkg.ID, pkg }) - // Walk prod dependencies - pkgs, err := a.walkDependencies(app.Packages, pkgIDs, directDeps, patterns, false) - if err != nil { - return xerrors.Errorf("unable to walk dependencies: %w", err) + if err := a.resolveRootDependencies(&root, pkgs, patterns); err != nil { + return xerrors.Errorf("unable to resolve root dependencies: %w", err) } - // Walk dev dependencies - devPkgs, err := a.walkDependencies(app.Packages, pkgIDs, directDevDeps, patterns, true) - if err != nil { - return xerrors.Errorf("unable to walk dependencies: %w", err) + if err := a.resolveWorkspaceDependencies(workspaces, pkgs, patterns); err != nil { + return xerrors.Errorf("unable to resolve workspace dependencies: %w", err) } - // Merge prod and dev dependencies. - // If the same package is found in both prod and dev dependencies, use the one in prod. - pkgs = lo.Assign(devPkgs, pkgs) - pkgSlice := lo.Values(pkgs) sort.Sort(types.Packages(pkgSlice)) @@ -200,11 +194,94 @@ func (a yarnAnalyzer) analyzeDependencies(fsys fs.FS, dir string, app *types.App return nil } -func (a yarnAnalyzer) walkDependencies(pkgs []types.Package, pkgIDs map[string]types.Package, - directDeps map[string]string, patterns map[string][]string, dev bool) (map[string]types.Package, error) { +func (a yarnAnalyzer) parsePackageJSON(fsys fs.FS, filePath string) (packagejson.Package, []packagejson.Package, error) { + // Parse package.json + f, err := fsys.Open(filePath) + if err != nil { + return packagejson.Package{}, nil, xerrors.Errorf("file open error: %w", err) + } + defer func() { _ = f.Close() }() + + root, err := a.packageJsonParser.Parse(f) + if err != nil { + return packagejson.Package{}, nil, xerrors.Errorf("parse error: %w", err) + } + + root.Package.ID = cmp.Or(root.Package.ID, filePath) // In case the package.json doesn't have a name or version + root.Package.Relationship = types.RelationshipRoot + + workspaces, err := a.traverseWorkspaces(fsys, path.Dir(filePath), root.Workspaces) + if err != nil { + return packagejson.Package{}, nil, xerrors.Errorf("traverse workspaces error: %w", err) + } + for i := range workspaces { + workspaces[i].Package.Relationship = types.RelationshipWorkspace + + // Add workspace as a child of root + root.DependsOn = append(root.DependsOn, workspaces[i].ID) + } + + return root, workspaces, nil +} + +func (a yarnAnalyzer) resolveRootDependencies(root *packagejson.Package, pkgs map[string]types.Package, + patterns map[string][]string) error { + if err := a.resolveDependencies(root, pkgs, patterns); err != nil { + return xerrors.Errorf("unable to resolve dependencies: %w", err) + } + + // Add root package to the package map + slices.Sort(root.Package.DependsOn) + pkgs[root.Package.ID] = root.Package + + return nil +} + +func (a yarnAnalyzer) resolveWorkspaceDependencies(workspaces []packagejson.Package, pkgs map[string]types.Package, + patterns map[string][]string) error { + if len(workspaces) == 0 { + return nil + } + + for _, workspace := range workspaces { + if err := a.resolveDependencies(&workspace, pkgs, patterns); err != nil { + return xerrors.Errorf("unable to resolve dependencies: %w", err) + } + + // Add workspace to the package map + slices.Sort(workspace.Package.DependsOn) + pkgs[workspace.ID] = workspace.Package + } + + return nil +} + +// resolveDependencies resolves production and development dependencies from direct dependencies and patterns. +// It also flags dependencies as direct or indirect and updates the dependencies of the parent package. +func (a yarnAnalyzer) resolveDependencies(pkg *packagejson.Package, pkgs map[string]types.Package, patterns map[string][]string) error { + // Recursively walk dependencies and flags development dependencies. + // Walk development dependencies first to avoid overwriting production dependencies. + directDevDeps := pkg.DevDependencies + if err := a.walkDependencies(&pkg.Package, pkgs, directDevDeps, patterns, true); err != nil { + return xerrors.Errorf("unable to walk dependencies: %w", err) + } + + // Recursively walk dependencies and flags production dependencies. + directProdDeps := lo.Assign(pkg.Dependencies, pkg.OptionalDependencies) + if err := a.walkDependencies(&pkg.Package, pkgs, directProdDeps, patterns, false); err != nil { + return xerrors.Errorf("unable to walk dependencies: %w", err) + } + + return nil +} + +// walkDependencies recursively walk dependencies and flags them as direct or indirect. +// Note that it overwrites the existing package map. +func (a yarnAnalyzer) walkDependencies(parent *types.Package, pkgs map[string]types.Package, directDeps map[string]string, + patterns map[string][]string, dev bool) error { // Identify direct dependencies - directPkgs := make(map[string]types.Package) + seenIDs := set.New[string]() for _, pkg := range pkgs { constraint, ok := directDeps[pkg.Name] if !ok { @@ -224,76 +301,59 @@ func (a yarnAnalyzer) walkDependencies(pkgs []types.Package, pkgIDs map[string]t if pkgPatterns, found := patterns[pkg.ID]; !found || !slices.Contains(pkgPatterns, dependency.ID(types.Yarn, pkg.Name, constraint)) { // npm has own comparer to compare versions if match, err := a.comparer.MatchVersion(pkg.Version, constraint); err != nil { - return nil, xerrors.Errorf("unable to match version for %s", pkg.Name) + return xerrors.Errorf("unable to match version for %s", pkg.Name) } else if !match { continue } } + // If the package is already marked as a production dependency, skip overwriting it. + // Since the dev field is boolean, it cannot determine if the package is already processed, + // so we need to check the relationship field. + if pkg.Relationship == types.RelationshipUnknown || pkg.Dev { + pkg.Dev = dev + } + // Mark as a direct dependency pkg.Indirect = false pkg.Relationship = types.RelationshipDirect - pkg.Dev = dev - directPkgs[pkg.ID] = pkg - } + pkgs[pkg.ID] = pkg + seenIDs.Append(pkg.ID) - // Walk indirect dependencies - for _, pkg := range directPkgs { - a.walkIndirectDependencies(pkg, pkgIDs, directPkgs) + // Add a direct dependency to the parent package + parent.DependsOn = append(parent.DependsOn, pkg.ID) + + // Walk indirect dependencies + a.walkIndirectDependencies(pkg, pkgs, seenIDs) } - return directPkgs, nil + return nil } -func (a yarnAnalyzer) walkIndirectDependencies(pkg types.Package, pkgIDs, deps map[string]types.Package) { +func (a yarnAnalyzer) walkIndirectDependencies(pkg types.Package, pkgs map[string]types.Package, seenIDs set.Set[string]) { for _, pkgID := range pkg.DependsOn { - if _, ok := deps[pkgID]; ok { - continue + if seenIDs.Contains(pkgID) { + continue // Skip if we've already seen this package } - dep, ok := pkgIDs[pkgID] + dep, ok := pkgs[pkgID] if !ok { continue } + if dep.Relationship == types.RelationshipUnknown || dep.Dev { + dep.Dev = pkg.Dev + } dep.Indirect = true dep.Relationship = types.RelationshipIndirect - dep.Dev = pkg.Dev - deps[dep.ID] = dep - a.walkIndirectDependencies(dep, pkgIDs, deps) - } -} + pkgs[dep.ID] = dep -func (a yarnAnalyzer) parsePackageJsonDependencies(fsys fs.FS, filePath string) (map[string]string, map[string]string, error) { - // Parse package.json - f, err := fsys.Open(filePath) - if err != nil { - return nil, nil, xerrors.Errorf("file open error: %w", err) - } - defer func() { _ = f.Close() }() + seenIDs.Append(dep.ID) - rootPkg, err := a.packageJsonParser.Parse(f) - if err != nil { - return nil, nil, xerrors.Errorf("parse error: %w", err) - } - - // Merge dependencies and optionalDependencies - dependencies := lo.Assign(rootPkg.Dependencies, rootPkg.OptionalDependencies) - devDependencies := rootPkg.DevDependencies - - if len(rootPkg.Workspaces) > 0 { - pkgs, err := a.traverseWorkspaces(fsys, path.Dir(filePath), rootPkg.Workspaces) - if err != nil { - return nil, nil, xerrors.Errorf("traverse workspaces error: %w", err) - } - for _, pkg := range pkgs { - dependencies = lo.Assign(dependencies, pkg.Dependencies, pkg.OptionalDependencies) - devDependencies = lo.Assign(devDependencies, pkg.DevDependencies) - } + // Recursively walk dependencies + a.walkIndirectDependencies(dep, pkgs, seenIDs) } - - return dependencies, devDependencies, nil } func (a yarnAnalyzer) traverseWorkspaces(fsys fs.FS, dir string, workspaces []string) ([]packagejson.Package, error) { @@ -303,11 +363,12 @@ func (a yarnAnalyzer) traverseWorkspaces(fsys fs.FS, dir string, workspaces []st return filepath.Base(path) == types.NpmPkg } - walkDirFunc := func(path string, d fs.DirEntry, r io.Reader) error { + walkDirFunc := func(path string, _ fs.DirEntry, r io.Reader) error { pkg, err := a.packageJsonParser.Parse(r) if err != nil { return xerrors.Errorf("unable to parse %q: %w", path, err) } + pkg.Package.ID = cmp.Or(pkg.Package.ID, path) // In case the package.json doesn't have a name or version pkgs = append(pkgs, pkg) return nil } @@ -396,7 +457,7 @@ func (a yarnAnalyzer) traverseCacheDir(fsys fs.FS) (map[string][]string, error) // Traverse .yarn/cache dir licenses := make(map[string][]string) err := fsutils.WalkDir(fsys, "cache", fsutils.RequiredExt(".zip"), - func(filePath string, d fs.DirEntry, r io.Reader) error { + func(_ string, d fs.DirEntry, r io.Reader) error { fi, err := d.Info() if err != nil { return xerrors.Errorf("file stat error: %w", err) diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/yarn_test.go b/pkg/fanal/analyzer/language/nodejs/yarn/yarn_test.go index 6978bd78dddf..e8efb274c3b4 100644 --- a/pkg/fanal/analyzer/language/nodejs/yarn/yarn_test.go +++ b/pkg/fanal/analyzer/language/nodejs/yarn/yarn_test.go @@ -26,6 +26,20 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { Type: types.Yarn, FilePath: "yarn.lock", Packages: types.Packages{ + { + ID: "90@1.0.0", + Name: "90", + Version: "1.0.0", + Relationship: types.RelationshipRoot, + Licenses: []string{ + "MIT", + }, + DependsOn: []string{ + "js-tokens@2.0.0", + "prop-types@15.7.2", + "scheduler@0.13.6", + }, + }, { ID: "js-tokens@2.0.0", Name: "js-tokens", @@ -134,7 +148,7 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - name: "Project with workspace placed in sub dir", + name: "project with workspace placed in sub dir", dir: "testdata/project-with-workspace-in-subdir", want: &analyzer.AnalysisResult{ Applications: []types.Application{ @@ -142,6 +156,27 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { Type: types.Yarn, FilePath: "foo/yarn.lock", Packages: types.Packages{ + { + ID: "@test/foo@1.0.0", + Name: "@test/foo", + Version: "1.0.0", + Relationship: types.RelationshipRoot, + Licenses: []string{ + "MIT", + }, + DependsOn: []string{ + "@test/bar-generators@0.0.1", + }, + }, + { + ID: "@test/bar-generators@0.0.1", + Name: "@test/bar-generators", + Version: "0.0.1", + Relationship: types.RelationshipWorkspace, + DependsOn: []string{ + "hoek@6.1.3", + }, + }, { ID: "hoek@6.1.3", Name: "hoek", @@ -307,6 +342,16 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { Type: types.Yarn, FilePath: "yarn.lock", Packages: []types.Package{ + { + ID: "yarn-3-licenses@1.0.0", + Name: "yarn-3-licenses", + Version: "1.0.0", + Relationship: types.RelationshipRoot, + DependsOn: []string{ + "is-callable@1.2.7", + "is-odd@3.0.1", + }, + }, { ID: "is-callable@1.2.7", Name: "is-callable", @@ -362,6 +407,14 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { Type: types.Yarn, FilePath: "yarn.lock", Packages: types.Packages{ + { + ID: "package.json", + Relationship: types.RelationshipRoot, + DependsOn: []string{ + "debug@4.3.5", + "js-tokens@9.0.0", + }, + }, { ID: "debug@4.3.5", Name: "debug", @@ -417,6 +470,21 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { Type: types.Yarn, FilePath: "yarn.lock", Packages: types.Packages{ + { + ID: "test@1.0.0", + Name: "test", + Version: "1.0.0", + Relationship: types.RelationshipRoot, + Licenses: []string{ + "MIT", + }, + DependsOn: []string{ + "foo-debug@4.3.4", + "foo-json@0.8.33", + "foo-ms@2.1.3", + "foo-uuid@9.0.7", + }, + }, { ID: "foo-json@0.8.33", Name: "@types/jsonstream", @@ -535,6 +603,54 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { Type: types.Yarn, FilePath: "yarn.lock", Packages: types.Packages{ + { + ID: "yarn-workspace-test@1.0.0", + Name: "yarn-workspace-test", + Version: "1.0.0", + Relationship: types.RelationshipRoot, + DependsOn: []string{ + "c@0.0.0", + "package1@0.0.0", + "packages/package2/package.json", + "prettier@2.8.8", + "util1@0.0.0", + }, + }, + { + ID: "packages/package2/package.json", + Relationship: types.RelationshipWorkspace, + DependsOn: []string{ + "is-odd@3.0.1", + }, + }, + { + ID: "c@0.0.0", + Name: "c", + Version: "0.0.0", + Relationship: types.RelationshipWorkspace, + DependsOn: []string{ + "is-number@7.0.0", + }, + }, + { + ID: "package1@0.0.0", + Name: "package1", + Version: "0.0.0", + Relationship: types.RelationshipWorkspace, + DependsOn: []string{ + "scheduler@0.23.0", + }, + }, + { + ID: "util1@0.0.0", + Name: "util1", + Version: "0.0.0", + Relationship: types.RelationshipWorkspace, + DependsOn: []string{ + "js-tokens@8.0.1", + "prop-types@15.8.1", + }, + }, { ID: "is-number@7.0.0", Name: "is-number", @@ -702,6 +818,13 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { Type: types.Yarn, FilePath: "yarn.lock", Packages: []types.Package{ + { + ID: "package.json", + Relationship: types.RelationshipRoot, + DependsOn: []string{ + "@vue/compiler-sfc@2.7.14", + }, + }, { ID: "@vue/compiler-sfc@2.7.14", Name: "@vue/compiler-sfc", diff --git a/pkg/fanal/analyzer/language/php/composer/composer.go b/pkg/fanal/analyzer/language/php/composer/composer.go index 0d48d2ad02eb..95f7f54d74b4 100644 --- a/pkg/fanal/analyzer/language/php/composer/composer.go +++ b/pkg/fanal/analyzer/language/php/composer/composer.go @@ -46,11 +46,11 @@ func newComposerAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, err func (a composerAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { var apps []types.Application - required := func(path string, d fs.DirEntry) bool { + required := func(path string, _ fs.DirEntry) bool { return filepath.Base(path) == types.ComposerLock || input.FilePatterns.Match(path) } - err := fsutils.WalkDir(input.FS, ".", required, func(path string, d fs.DirEntry, r io.Reader) error { + err := fsutils.WalkDir(input.FS, ".", required, func(path string, _ fs.DirEntry, r io.Reader) error { // Parse composer.lock app, err := a.parseComposerLock(path, r) if err != nil { diff --git a/pkg/fanal/analyzer/language/php/composer/vendor_test.go b/pkg/fanal/analyzer/language/php/composer/vendor_test.go index 887c5d404039..bb7a051bfc2b 100644 --- a/pkg/fanal/analyzer/language/php/composer/vendor_test.go +++ b/pkg/fanal/analyzer/language/php/composer/vendor_test.go @@ -77,7 +77,7 @@ func Test_composerVendorAnalyzer_Analyze(t *testing.T) { }() a := composerVendorAnalyzer{} - got, err := a.Analyze(nil, analyzer.AnalysisInput{ + got, err := a.Analyze(t.Context(), analyzer.AnalysisInput{ FilePath: tt.inputFile, Content: f, }) diff --git a/pkg/fanal/analyzer/language/python/packaging/packaging.go b/pkg/fanal/analyzer/language/python/packaging/packaging.go index 59fecf84476e..51dd551d40d7 100644 --- a/pkg/fanal/analyzer/language/python/packaging/packaging.go +++ b/pkg/fanal/analyzer/language/python/packaging/packaging.go @@ -66,7 +66,7 @@ func (a packagingAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAna return filepath.Base(path) == "METADATA" || isEggFile(path) || input.FilePatterns.Match(path) } - err := fsutils.WalkDir(input.FS, ".", required, func(filePath string, d fs.DirEntry, r io.Reader) error { + err := fsutils.WalkDir(input.FS, ".", required, func(filePath string, _ fs.DirEntry, r io.Reader) error { rsa, ok := r.(xio.ReadSeekerAt) if !ok { return xerrors.New("invalid reader") diff --git a/pkg/fanal/analyzer/language/python/pip/pip.go b/pkg/fanal/analyzer/language/python/pip/pip.go index f213ba0f7a1c..9eaaff441db7 100644 --- a/pkg/fanal/analyzer/language/python/pip/pip.go +++ b/pkg/fanal/analyzer/language/python/pip/pip.go @@ -66,7 +66,7 @@ func (a pipLibraryAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAn useMinVersion := a.detectionPriority == types.PriorityComprehensive - if err = fsutils.WalkDir(input.FS, ".", required, func(pathPath string, d fs.DirEntry, r io.Reader) error { + if err = fsutils.WalkDir(input.FS, ".", required, func(pathPath string, _ fs.DirEntry, r io.Reader) error { app, err := language.Parse(types.Pip, pathPath, r, pip.NewParser(useMinVersion)) if err != nil { return xerrors.Errorf("unable to parse requirements.txt: %w", err) diff --git a/pkg/fanal/analyzer/language/python/pip/pip_test.go b/pkg/fanal/analyzer/language/python/pip/pip_test.go index eb07ed409e25..8121d0ad4144 100644 --- a/pkg/fanal/analyzer/language/python/pip/pip_test.go +++ b/pkg/fanal/analyzer/language/python/pip/pip_test.go @@ -163,7 +163,7 @@ func Test_pipAnalyzer_Analyze(t *testing.T) { pythonExecFileName = "python.exe" } // create temp python3 Executable - err = os.WriteFile(filepath.Join(tt.pythonExecDir, pythonExecFileName), nil, 0755) + err = os.WriteFile(filepath.Join(tt.pythonExecDir, pythonExecFileName), nil, 0o755) require.NoError(t, err) newPATH, err = filepath.Abs(tt.pythonExecDir) @@ -245,7 +245,7 @@ func Test_pythonExecutablePath(t *testing.T) { if runtime.GOOS == "windows" { tt.execName += ".exe" } - err = os.WriteFile(filepath.Join(binDir, tt.execName), nil, 0755) + err = os.WriteFile(filepath.Join(binDir, tt.execName), nil, 0o755) require.NoError(t, err) t.Setenv("PATH", binDir) diff --git a/pkg/fanal/analyzer/language/python/poetry/poetry.go b/pkg/fanal/analyzer/language/python/poetry/poetry.go index ed6230169956..154e2bd7c26f 100644 --- a/pkg/fanal/analyzer/language/python/poetry/poetry.go +++ b/pkg/fanal/analyzer/language/python/poetry/poetry.go @@ -44,11 +44,11 @@ func newPoetryAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error func (a poetryAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { var apps []types.Application - required := func(path string, d fs.DirEntry) bool { + required := func(path string, _ fs.DirEntry) bool { return filepath.Base(path) == types.PoetryLock || input.FilePatterns.Match(path) } - err := fsutils.WalkDir(input.FS, ".", required, func(path string, d fs.DirEntry, r io.Reader) error { + err := fsutils.WalkDir(input.FS, ".", required, func(path string, _ fs.DirEntry, r io.Reader) error { // Parse poetry.lock app, err := a.parsePoetryLock(path, r) if err != nil { diff --git a/pkg/fanal/analyzer/language/python/uv/uv.go b/pkg/fanal/analyzer/language/python/uv/uv.go index f3da9fc452e9..d6ef979b6840 100644 --- a/pkg/fanal/analyzer/language/python/uv/uv.go +++ b/pkg/fanal/analyzer/language/python/uv/uv.go @@ -42,7 +42,7 @@ func (a *uvAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisI return true } - err := fsutils.WalkDir(input.FS, ".", required, func(path string, d fs.DirEntry, r io.Reader) error { + err := fsutils.WalkDir(input.FS, ".", required, func(path string, _ fs.DirEntry, r io.Reader) error { // Parse uv.lock app, err := language.Parse(types.Uv, path, r, a.lockParser) if err != nil { diff --git a/pkg/fanal/analyzer/language/rust/cargo/cargo.go b/pkg/fanal/analyzer/language/rust/cargo/cargo.go index a13ea0bfda96..9af843171fd3 100644 --- a/pkg/fanal/analyzer/language/rust/cargo/cargo.go +++ b/pkg/fanal/analyzer/language/rust/cargo/cargo.go @@ -12,13 +12,16 @@ import ( "path/filepath" "slices" "sort" + "strconv" "github.com/BurntSushi/toml" + "github.com/mitchellh/hashstructure/v2" "github.com/samber/lo" "golang.org/x/xerrors" "github.com/aquasecurity/go-version/pkg/semver" goversion "github.com/aquasecurity/go-version/pkg/version" + "github.com/aquasecurity/trivy/pkg/dependency" "github.com/aquasecurity/trivy/pkg/dependency/parser/rust/cargo" "github.com/aquasecurity/trivy/pkg/detector/library/compare" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" @@ -56,11 +59,11 @@ func newCargoAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) func (a cargoAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { var apps []types.Application - required := func(path string, d fs.DirEntry) bool { + required := func(path string, _ fs.DirEntry) bool { return filepath.Base(path) == types.CargoLock || input.FilePatterns.Match(path) } - err := fsutils.WalkDir(input.FS, ".", required, func(filePath string, d fs.DirEntry, r io.Reader) error { + err := fsutils.WalkDir(input.FS, ".", required, func(filePath string, _ fs.DirEntry, r io.Reader) error { // Parse Cargo.lock app, err := a.parseCargoLock(filePath, r) if err != nil { @@ -107,7 +110,7 @@ func (a cargoAnalyzer) parseCargoLock(filePath string, r io.Reader) (*types.Appl func (a cargoAnalyzer) removeDevDependencies(fsys fs.FS, dir string, app *types.Application) error { cargoTOMLPath := path.Join(dir, types.CargoToml) - directDeps, err := a.parseRootCargoTOML(fsys, cargoTOMLPath) + root, workspaces, directDeps, err := a.parseRootCargoTOML(fsys, cargoTOMLPath) if errors.Is(err, fs.ErrNotExist) { a.logger.Debug("Cargo.toml not found", log.FilePath(cargoTOMLPath)) return nil @@ -148,6 +151,37 @@ func (a cargoAnalyzer) removeDevDependencies(fsys fs.FS, dir string, app *types. a.walkIndirectDependencies(pkg, pkgIDs, pkgs) } + // Identify root and workspace packages + for pkgID, pkg := range pkgIDs { + switch { + case pkgID == root: + pkg.Relationship = types.RelationshipRoot + case slices.Contains(workspaces, pkgID): + pkg.Relationship = types.RelationshipWorkspace + default: + continue + } + + // Root/workspace package may include dev dependencies in lock file, so we need to remove them. + pkg.DependsOn = lo.Filter(pkg.DependsOn, func(dep string, _ int) bool { + _, ok := pkgs[dep] + return ok + }) + pkgs[pkgID] = pkg + } + + // Cargo allows creating cargo.toml files without name and version. + // In this case, the lock file will not include this package. + // e.g. when root cargo.toml contains only workspaces. + // So we have to add it ourselves, and the ID in this case will be the hash of the toml file. + if _, ok := pkgs[root]; !ok { + pkgs[root] = types.Package{ + ID: root, + Relationship: types.RelationshipRoot, + DependsOn: workspaces, + } + } + pkgSlice := lo.Values(pkgs) sort.Sort(types.Packages(pkgSlice)) @@ -157,11 +191,17 @@ func (a cargoAnalyzer) removeDevDependencies(fsys fs.FS, dir string, app *types. } type cargoToml struct { + Package Package `toml:"package"` Dependencies Dependencies `toml:"dependencies"` Target map[string]map[string]Dependencies `toml:"target"` Workspace cargoTomlWorkspace `toml:"workspace"` } +type Package struct { + Name string `toml:"name"` + Version string `toml:"version"` +} + type cargoTomlWorkspace struct { Dependencies Dependencies `toml:"dependencies"` Members []string `toml:"members"` @@ -171,20 +211,24 @@ type Dependencies map[string]any // parseRootCargoTOML parses top-level Cargo.toml and returns dependencies. // It also parses workspace members and their dependencies. -func (a cargoAnalyzer) parseRootCargoTOML(fsys fs.FS, filePath string) (map[string]string, error) { - dependencies, members, err := parseCargoTOML(fsys, filePath) +func (a cargoAnalyzer) parseRootCargoTOML(fsys fs.FS, filePath string) (string, []string, map[string]string, error) { + rootPkg, dependencies, members, err := a.parseCargoTOML(fsys, filePath) if err != nil { - return nil, xerrors.Errorf("unable to parse %s: %w", filePath, err) + return "", nil, nil, xerrors.Errorf("unable to parse %s: %w", filePath, err) } + // According to Cargo workspace RFC, workspaces can't be nested: // https://github.com/nox/rust-rfcs/blob/master/text/1525-cargo-workspace.md#validating-a-workspace + var workspaces []string for _, member := range members { memberPath := path.Join(path.Dir(filePath), member, types.CargoToml) - memberDeps, _, err := parseCargoTOML(fsys, memberPath) + memberPkg, memberDeps, _, err := a.parseCargoTOML(fsys, memberPath) if err != nil { a.logger.Warn("Unable to parse Cargo.toml", log.String("member_path", memberPath), log.Err(err)) continue } + workspaces = append(workspaces, memberPkg) + // Member dependencies shouldn't overwrite dependencies from root cargo.toml file maps.Copy(memberDeps, dependencies) dependencies = memberDeps @@ -209,7 +253,7 @@ func (a cargoAnalyzer) parseRootCargoTOML(fsys fs.FS, filePath string) (map[stri } } - return deps, nil + return rootPkg, workspaces, deps, nil } func (a cargoAnalyzer) walkIndirectDependencies(pkg types.Package, pkgIDs, deps map[string]types.Package) { @@ -254,11 +298,11 @@ func (a cargoAnalyzer) matchVersion(currentVersion, constraint string) (bool, er return c.Check(ver), nil } -func parseCargoTOML(fsys fs.FS, filePath string) (Dependencies, []string, error) { +func (a cargoAnalyzer) parseCargoTOML(fsys fs.FS, filePath string) (string, Dependencies, []string, error) { // Parse Cargo.toml f, err := fsys.Open(filePath) if err != nil { - return nil, nil, xerrors.Errorf("file open error: %w", err) + return "", nil, nil, xerrors.Errorf("file open error: %w", err) } defer func() { _ = f.Close() }() @@ -268,9 +312,11 @@ func parseCargoTOML(fsys fs.FS, filePath string) (Dependencies, []string, error) // declare `dependencies` to avoid panic dependencies := Dependencies{} if _, err = toml.NewDecoder(f).Decode(&tomlFile); err != nil { - return nil, nil, xerrors.Errorf("toml decode error: %w", err) + return "", nil, nil, xerrors.Errorf("toml decode error: %w", err) } + pkgID := a.packageID(tomlFile) + maps.Copy(dependencies, tomlFile.Dependencies) // https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#platform-specific-dependencies @@ -281,5 +327,23 @@ func parseCargoTOML(fsys fs.FS, filePath string) (Dependencies, []string, error) // https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#inheriting-a-dependency-from-a-workspace maps.Copy(dependencies, tomlFile.Workspace.Dependencies) // https://doc.rust-lang.org/cargo/reference/workspaces.html#the-members-and-exclude-fields - return dependencies, tomlFile.Workspace.Members, nil + return pkgID, dependencies, tomlFile.Workspace.Members, nil +} + +// packageID builds PackageID by Package name and version. +// If name is empty - use hash of cargoToml. +func (a cargoAnalyzer) packageID(cargoToml cargoToml) string { + if cargoToml.Package.Name != "" { + return dependency.ID(types.Cargo, cargoToml.Package.Name, cargoToml.Package.Version) + } + + hash, err := hashstructure.Hash(cargoToml, hashstructure.FormatV2, &hashstructure.HashOptions{ + ZeroNil: true, + IgnoreZeroValue: true, + }) + if err != nil { + a.logger.Warn("unable to hash package", log.String("package", cargoToml.Package.Name), log.Err(err)) + } + + return strconv.FormatUint(hash, 16) } diff --git a/pkg/fanal/analyzer/language/rust/cargo/cargo_test.go b/pkg/fanal/analyzer/language/rust/cargo/cargo_test.go index 43bdc3afbb62..11fa15bde991 100644 --- a/pkg/fanal/analyzer/language/rust/cargo/cargo_test.go +++ b/pkg/fanal/analyzer/language/rust/cargo/cargo_test.go @@ -27,6 +27,23 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { Type: types.Cargo, FilePath: "Cargo.lock", Packages: types.Packages{ + { + ID: "app@0.1.0", + Name: "app", + Version: "0.1.0", + Relationship: types.RelationshipRoot, + Locations: []types.Location{ + { + StartLine: 13, + EndLine: 20, + }, + }, + DependsOn: []string{ + "memchr@1.0.2", + "regex-syntax@0.5.6", + "regex@1.7.3", + }, + }, { ID: "memchr@1.0.2", Name: "memchr", @@ -153,6 +170,21 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { Type: types.Cargo, FilePath: "Cargo.lock", Packages: types.Packages{ + { + ID: "app@0.1.0", + Name: "app", + Version: "0.1.0", + Relationship: types.RelationshipRoot, + Locations: []types.Location{ + { + StartLine: 4, + EndLine: 9, + }, + }, + DependsOn: []string{ + "memchr@2.5.0", + }, + }, { ID: "memchr@2.5.0", Name: "memchr", @@ -413,6 +445,44 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { Type: types.Cargo, FilePath: "Cargo.lock", Packages: types.Packages{ + { + ID: "d0e1231acd612a0f", + Relationship: types.RelationshipRoot, + DependsOn: []string{ + "member@0.1.0", + "member2@0.1.0", + }, + }, + { + ID: "member@0.1.0", + Name: "member", + Version: "0.1.0", + Relationship: types.RelationshipWorkspace, + Locations: []types.Location{ + { + StartLine: 30, + EndLine: 35, + }, + }, + DependsOn: []string{ + "gdb-command@0.7.6", + }, + }, + { + ID: "member2@0.1.0", + Name: "member2", + Version: "0.1.0", + Relationship: types.RelationshipWorkspace, + Locations: []types.Location{ + { + StartLine: 37, + EndLine: 42, + }, + }, + DependsOn: []string{ + "regex@1.10.2", + }, + }, { ID: "gdb-command@0.7.6", Name: "gdb-command", diff --git a/pkg/fanal/analyzer/language/swift/cocoapods/cocoapods_test.go b/pkg/fanal/analyzer/language/swift/cocoapods/cocoapods_test.go index 79e2a84078e7..4e890acd4ec4 100644 --- a/pkg/fanal/analyzer/language/swift/cocoapods/cocoapods_test.go +++ b/pkg/fanal/analyzer/language/swift/cocoapods/cocoapods_test.go @@ -80,7 +80,7 @@ func Test_cocoaPodsLockAnalyzer_Analyze(t *testing.T) { defer f.Close() a := cocoaPodsLockAnalyzer{} - got, err := a.Analyze(nil, analyzer.AnalysisInput{ + got, err := a.Analyze(t.Context(), analyzer.AnalysisInput{ FilePath: tt.inputFile, Content: f, }) diff --git a/pkg/fanal/analyzer/language/swift/swift/swift_test.go b/pkg/fanal/analyzer/language/swift/swift/swift_test.go index 244a96e076b3..b4b3c5d408a7 100644 --- a/pkg/fanal/analyzer/language/swift/swift/swift_test.go +++ b/pkg/fanal/analyzer/language/swift/swift/swift_test.go @@ -26,7 +26,6 @@ func Test_swiftLockAnalyzer_Analyze(t *testing.T) { Type: types.Swift, FilePath: "testdata/happy/Package.resolved", Packages: types.Packages{ - { ID: "github.com/Quick/Nimble@9.2.1", Name: "github.com/Quick/Nimble", @@ -78,7 +77,7 @@ func Test_swiftLockAnalyzer_Analyze(t *testing.T) { defer f.Close() a := swiftLockAnalyzer{} - got, err := a.Analyze(nil, analyzer.AnalysisInput{ + got, err := a.Analyze(t.Context(), analyzer.AnalysisInput{ FilePath: tt.inputFile, Content: f, }) diff --git a/pkg/fanal/analyzer/os/amazonlinux/amazonlinux_test.go b/pkg/fanal/analyzer/os/amazonlinux/amazonlinux_test.go index f4957f3ea6a4..e26212df826e 100644 --- a/pkg/fanal/analyzer/os/amazonlinux/amazonlinux_test.go +++ b/pkg/fanal/analyzer/os/amazonlinux/amazonlinux_test.go @@ -96,10 +96,8 @@ func Test_amazonlinuxOSAnalyzer_Analyze(t *testing.T) { if tt.wantErr != "" { require.ErrorContains(t, err, tt.wantErr) return - } else { - require.NoError(t, err) } - + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/os/debian/debian_test.go b/pkg/fanal/analyzer/os/debian/debian_test.go index 3c37ead0078a..f9137f28e45f 100644 --- a/pkg/fanal/analyzer/os/debian/debian_test.go +++ b/pkg/fanal/analyzer/os/debian/debian_test.go @@ -60,9 +60,8 @@ func Test_debianOSAnalyzer_Analyze(t *testing.T) { if tt.wantErr != "" { require.ErrorContains(t, err, tt.wantErr) return - } else { - require.NoError(t, err) } + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/os/redhatbase/centos_test.go b/pkg/fanal/analyzer/os/redhatbase/centos_test.go index 05242cd12f4a..f409740485cc 100644 --- a/pkg/fanal/analyzer/os/redhatbase/centos_test.go +++ b/pkg/fanal/analyzer/os/redhatbase/centos_test.go @@ -46,9 +46,8 @@ func Test_centosOSAnalyzer_Analyze(t *testing.T) { if tt.wantErr != "" { require.ErrorContains(t, err, tt.wantErr) return - } else { - require.NoError(t, err) } + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/os/redhatbase/fedora_test.go b/pkg/fanal/analyzer/os/redhatbase/fedora_test.go index db437f7372f2..0b84c66901a2 100644 --- a/pkg/fanal/analyzer/os/redhatbase/fedora_test.go +++ b/pkg/fanal/analyzer/os/redhatbase/fedora_test.go @@ -46,9 +46,8 @@ func Test_fedoraOSAnalyzer_Analyze(t *testing.T) { if tt.wantErr != "" { require.ErrorContains(t, err, tt.wantErr) return - } else { - require.NoError(t, err) } + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/os/redhatbase/oracle_test.go b/pkg/fanal/analyzer/os/redhatbase/oracle_test.go index 425b4ed71fb4..a7e45132177a 100644 --- a/pkg/fanal/analyzer/os/redhatbase/oracle_test.go +++ b/pkg/fanal/analyzer/os/redhatbase/oracle_test.go @@ -46,9 +46,8 @@ func Test_oracleOSAnalyzer_Analyze(t *testing.T) { if tt.wantErr != "" { require.ErrorContains(t, err, tt.wantErr) return - } else { - require.NoError(t, err) } + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/os/redhatbase/redhatbase_test.go b/pkg/fanal/analyzer/os/redhatbase/redhatbase_test.go index fa6fde970483..589637a0c97b 100644 --- a/pkg/fanal/analyzer/os/redhatbase/redhatbase_test.go +++ b/pkg/fanal/analyzer/os/redhatbase/redhatbase_test.go @@ -46,9 +46,8 @@ func Test_redhatOSAnalyzer_Analyze(t *testing.T) { if tt.wantErr != "" { require.ErrorContains(t, err, tt.wantErr) return - } else { - require.NoError(t, err) } + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/os/release/release.go b/pkg/fanal/analyzer/os/release/release.go index 286e803406aa..9e40758ff90c 100644 --- a/pkg/fanal/analyzer/os/release/release.go +++ b/pkg/fanal/analyzer/os/release/release.go @@ -20,6 +20,8 @@ const version = 1 var requiredFiles = []string{ "etc/os-release", "usr/lib/os-release", + "aarch64-bottlerocket-linux-gnu/sys-root/usr/lib/os-release", + "x86_64-bottlerocket-linux-gnu/sys-root/usr/lib/os-release", } type osReleaseAnalyzer struct{} @@ -49,6 +51,8 @@ func (a osReleaseAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInp switch id { case "alpine": family = types.Alpine + case "bottlerocket": + family = types.Bottlerocket case "opensuse-tumbleweed": family = types.OpenSUSETumbleweed case "opensuse-leap", "opensuse": // opensuse for leap:42, opensuse-leap for leap:15 diff --git a/pkg/fanal/analyzer/os/release/release_test.go b/pkg/fanal/analyzer/os/release/release_test.go index 9fdc0e4194e2..6ca42e3f7eb5 100644 --- a/pkg/fanal/analyzer/os/release/release_test.go +++ b/pkg/fanal/analyzer/os/release/release_test.go @@ -149,6 +149,16 @@ func Test_osReleaseAnalyzer_Analyze(t *testing.T) { }, }, }, + { + name: "Bottlerocket", + inputFile: "testdata/bottlerocket", + want: &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.Bottlerocket, + Name: "1.34.0", + }, + }, + }, { name: "Unknown OS", inputFile: "testdata/unknown", diff --git a/pkg/fanal/analyzer/os/release/testdata/bottlerocket b/pkg/fanal/analyzer/os/release/testdata/bottlerocket new file mode 100644 index 000000000000..3bde7c6b6e3e --- /dev/null +++ b/pkg/fanal/analyzer/os/release/testdata/bottlerocket @@ -0,0 +1,11 @@ +NAME=Bottlerocket +ID=bottlerocket +VERSION="1.34.0 (aws-ecs-2)" +PRETTY_NAME="Bottlerocket OS 1.34.0 (aws-ecs-2)" +VARIANT_ID=aws-ecs-2 +VERSION_ID=1.34.0 +BUILD_ID=18d04e52 +HOME_URL="https://github.com/bottlerocket-os/bottlerocket" +SUPPORT_URL="https://github.com/bottlerocket-os/bottlerocket/discussions" +BUG_REPORT_URL="https://github.com/bottlerocket-os/bottlerocket/issues" +DOCUMENTATION_URL="https://bottlerocket.dev" diff --git a/pkg/fanal/analyzer/os/ubuntu/ubuntu_test.go b/pkg/fanal/analyzer/os/ubuntu/ubuntu_test.go index 1e4b08e59e34..27a159b12c39 100644 --- a/pkg/fanal/analyzer/os/ubuntu/ubuntu_test.go +++ b/pkg/fanal/analyzer/os/ubuntu/ubuntu_test.go @@ -46,9 +46,8 @@ func Test_ubuntuOSAnalyzer_Analyze(t *testing.T) { if tt.wantErr != "" { require.ErrorContains(t, err, tt.wantErr) return - } else { - require.NoError(t, err) } + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/pkg/bottlerocket_inventory/bottlerocket_inventory.go b/pkg/fanal/analyzer/pkg/bottlerocket_inventory/bottlerocket_inventory.go new file mode 100644 index 000000000000..ec83b1f4ac8f --- /dev/null +++ b/pkg/fanal/analyzer/pkg/bottlerocket_inventory/bottlerocket_inventory.go @@ -0,0 +1,110 @@ +package bottlerocket_inventory + +import ( + "context" + "encoding/json" + "fmt" + "io" + "os" + "slices" + "strconv" + "time" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func init() { + analyzer.RegisterAnalyzer(newBottlerocketInventoryAnalyzer()) +} + +const analyzerVersion = 1 + +var requiredFiles = []string{ + "aarch64-bottlerocket-linux-gnu/sys-root/usr/share/bottlerocket/application-inventory.json", + "x86_64-bottlerocket-linux-gnu/sys-root/usr/share/bottlerocket/application-inventory.json", +} + +type bottlerocketInventoryAnalyzer struct{} + +func newBottlerocketInventoryAnalyzer() *bottlerocketInventoryAnalyzer { + return &bottlerocketInventoryAnalyzer{} +} + +type ApplicationInventory struct { + Content []struct { + Name string `json:"Name"` + Publisher string `json:"Publisher"` + Version string `json:"Version"` + Release string `json:"Release"` + Epoch string `json:"Epoch"` + InstalledTime time.Time `json:"InstalledTime"` + ApplicationType string `json:"ApplicationType"` + Architecture string `json:"Architecture"` + URL string `json:"Url"` + Summary string `json:"Summary"` + } `json:"Content"` +} + +func (a bottlerocketInventoryAnalyzer) Analyze(ctx context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + parsedInventorys, err := a.parseApplicationInventory(ctx, input.Content) + if err != nil { + return nil, err + } + + return &analyzer.AnalysisResult{ + PackageInfos: []types.PackageInfo{ + { + FilePath: input.FilePath, + Packages: parsedInventorys, + }, + }, + }, nil +} + +func (a bottlerocketInventoryAnalyzer) parseApplicationInventory(_ context.Context, r io.Reader) ([]types.Package, error) { + var pkgs []types.Package + + var applicationInventory ApplicationInventory + if err := json.NewDecoder(r).Decode(&applicationInventory); err != nil { + return nil, err + } + + for _, app := range applicationInventory.Content { + epoch, err := strconv.Atoi(app.Epoch) + if err != nil { + return nil, err + } + pkg := types.Package{ + Arch: app.Architecture, + Epoch: epoch, + Name: app.Name, + Version: app.Version, + } + + if pkg.Name != "" && pkg.Version != "" { + pkg.ID = fmt.Sprintf("%s@%s", pkg.Name, pkg.Version) + } + + pkgs = append(pkgs, pkg) + } + + return pkgs, nil +} + +func (a bottlerocketInventoryAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return slices.Contains(requiredFiles, filePath) +} + +func (a bottlerocketInventoryAnalyzer) Type() analyzer.Type { + return analyzer.TypeBottlerocketInventory +} + +func (a bottlerocketInventoryAnalyzer) Version() int { + return analyzerVersion +} + +// StaticPaths returns a list of static file paths to analyze +func (a bottlerocketInventoryAnalyzer) StaticPaths() []string { + return requiredFiles +} diff --git a/pkg/fanal/analyzer/pkg/bottlerocket_inventory/bottlerocket_inventory_test.go b/pkg/fanal/analyzer/pkg/bottlerocket_inventory/bottlerocket_inventory_test.go new file mode 100644 index 000000000000..8e570303326c --- /dev/null +++ b/pkg/fanal/analyzer/pkg/bottlerocket_inventory/bottlerocket_inventory_test.go @@ -0,0 +1,62 @@ +package bottlerocket_inventory + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +var pkgs = []types.Package{ + { + ID: "glibc@2.40", + Name: "glibc", + Version: "2.40", + Epoch: 1, + Arch: "x86_64", + }, + { + ID: "kernel-6.1@6.1.128", + Name: "kernel-6.1", + Version: "6.1.128", + Epoch: 0, + Arch: "x86_64", + }, + { + ID: "systemd@252.22", + Name: "systemd", + Version: "252.22", + Epoch: 0, + Arch: "x86_64", + }, +} + +func TestParseApplicationInventory(t *testing.T) { + var tests = []struct { + name string + path string + wantPkgs []types.Package + }{ + { + name: "happy path", + path: "./testdata/application-inventory.json", + wantPkgs: pkgs, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := bottlerocketInventoryAnalyzer{} + f, err := os.Open(tt.path) + require.NoError(t, err) + defer f.Close() + gotPkgs, err := a.parseApplicationInventory(t.Context(), f) + require.NoError(t, err) + + assert.Equal(t, tt.wantPkgs, gotPkgs) + }) + } +} diff --git a/pkg/fanal/analyzer/pkg/bottlerocket_inventory/testdata/application-inventory.json b/pkg/fanal/analyzer/pkg/bottlerocket_inventory/testdata/application-inventory.json new file mode 100644 index 000000000000..3183c4e1e186 --- /dev/null +++ b/pkg/fanal/analyzer/pkg/bottlerocket_inventory/testdata/application-inventory.json @@ -0,0 +1,40 @@ +{ + "Content": [ + { + "Name": "glibc", + "Publisher": "bottlerocket-core-kit", + "Version": "2.40", + "Release": "1.1740525475.e3a5862c.br1", + "Epoch": "1", + "InstalledTime": "2025-02-27T20:55:41Z", + "ApplicationType": "Unspecified", + "Architecture": "x86_64", + "Url": "http://www.gnu.org/software/glibc/", + "Summary": "The GNU libc libraries" + }, + { + "Name": "kernel-6.1", + "Publisher": "bottlerocket-kernel-kit", + "Version": "6.1.128", + "Release": "1.1740603423.4d405dc9.br1", + "Epoch": "0", + "InstalledTime": "2025-02-27T20:55:41Z", + "ApplicationType": "Unspecified", + "Architecture": "x86_64", + "Url": "https://www.kernel.org/", + "Summary": "The Linux kernel" + }, + { + "Name": "systemd", + "Publisher": "bottlerocket-core-kit", + "Version": "252.22", + "Release": "1.1740525475.e3a5862c.br1", + "Epoch": "0", + "InstalledTime": "2025-02-27T20:55:41Z", + "ApplicationType": "Unspecified", + "Architecture": "x86_64", + "Url": "https://www.freedesktop.org/wiki/Software/systemd", + "Summary": "System and Service Manager" + } + ] +} diff --git a/pkg/fanal/analyzer/pkg/dpkg/dpkg.go b/pkg/fanal/analyzer/pkg/dpkg/dpkg.go index 753bcf15997c..098e97800e62 100644 --- a/pkg/fanal/analyzer/pkg/dpkg/dpkg.go +++ b/pkg/fanal/analyzer/pkg/dpkg/dpkg.go @@ -64,14 +64,14 @@ func (a dpkgAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysis a.logger.Debug("Unable to parse the available file", log.FilePath(availableFile), log.Err(err)) } - required := func(path string, d fs.DirEntry) bool { + required := func(path string, _ fs.DirEntry) bool { return path != availableFile } packageFiles := make(map[string][]string) // parse other files - err = fsutils.WalkDir(input.FS, ".", required, func(path string, d fs.DirEntry, r io.Reader) error { + err = fsutils.WalkDir(input.FS, ".", required, func(path string, _ fs.DirEntry, r io.Reader) error { // parse list files if a.isListFile(filepath.Split(path)) { scanner := bufio.NewScanner(r) diff --git a/pkg/fanal/applier/applier.go b/pkg/fanal/applier/applier.go index 0ddeef11eba2..365bc44e79ba 100644 --- a/pkg/fanal/applier/applier.go +++ b/pkg/fanal/applier/applier.go @@ -1,6 +1,7 @@ package applier import ( + "github.com/samber/lo" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/cache" @@ -23,11 +24,15 @@ func NewApplier(c cache.LocalArtifactCache) Applier { func (a *applier) ApplyLayers(imageID string, layerKeys []string) (ftypes.ArtifactDetail, error) { var layers []ftypes.BlobInfo + var layerInfoList ftypes.Layers for _, key := range layerKeys { blob, _ := a.cache.GetBlob(key) // nolint if blob.SchemaVersion == 0 { return ftypes.ArtifactDetail{}, xerrors.Errorf("layer cache missing: %s", key) } + if l := blob.Layer(); !lo.IsEmpty(l) { + layerInfoList = append(layerInfoList, l) + } layers = append(layers, blob) } @@ -40,6 +45,9 @@ func (a *applier) ApplyLayers(imageID string, layerKeys []string) (ftypes.Artifa Secret: imageInfo.Secret, } + // Fill layers info + mergedLayer.Layers = layerInfoList + if !mergedLayer.OS.Detected() { return mergedLayer, analyzer.ErrUnknownOS // send back package and apps info regardless } else if mergedLayer.Packages == nil { diff --git a/pkg/fanal/applier/applier_test.go b/pkg/fanal/applier/applier_test.go index 85cd8991bb8a..070b5788a041 100644 --- a/pkg/fanal/applier/applier_test.go +++ b/pkg/fanal/applier/applier_test.go @@ -49,6 +49,7 @@ func TestApplier_ApplyLayers(t *testing.T) { require.NoError(t, c.PutBlob("sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", types.BlobInfo{ SchemaVersion: 1, + Size: 1000, Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", OS: types.OS{ @@ -72,6 +73,7 @@ func TestApplier_ApplyLayers(t *testing.T) { require.NoError(t, c.PutBlob("sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", types.BlobInfo{ SchemaVersion: 1, + Size: 2000, Digest: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", PackageInfos: []types.PackageInfo{ @@ -91,6 +93,7 @@ func TestApplier_ApplyLayers(t *testing.T) { require.NoError(t, c.PutBlob("sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", types.BlobInfo{ SchemaVersion: 1, + Size: 3000, Digest: "sha256:beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203", DiffID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", Applications: []types.Application{ @@ -118,6 +121,23 @@ func TestApplier_ApplyLayers(t *testing.T) { Family: "debian", Name: "9.9", }, + Layers: types.Layers{ + { + Size: 1000, + Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + }, + { + Size: 2000, + Digest: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", + }, + { + Size: 3000, + Digest: "sha256:beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203", + DiffID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + }, + }, Packages: types.Packages{ { Name: "libc6", @@ -264,6 +284,7 @@ func TestApplier_ApplyLayers(t *testing.T) { require.NoError(t, c.PutBlob("sha256:531743b7098cb2aaf615641007a129173f63ed86ca32fe7b5a246a1c47286028", types.BlobInfo{ SchemaVersion: 1, + Size: 1000, Digest: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", DiffID: "sha256:531743b7098cb2aaf615641007a129173f63ed86ca32fe7b5a246a1c47286028", OS: types.OS{ @@ -306,6 +327,13 @@ func TestApplier_ApplyLayers(t *testing.T) { Family: "alpine", Name: "3.10.4", }, + Layers: types.Layers{ + { + Size: 1000, + Digest: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + DiffID: "sha256:531743b7098cb2aaf615641007a129173f63ed86ca32fe7b5a246a1c47286028", + }, + }, Packages: types.Packages{ { Name: "busybox", @@ -469,7 +497,7 @@ func TestApplier_ApplyLayers(t *testing.T) { "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", }, }, - setUpCache: func(t *testing.T) cache.Cache { + setUpCache: func(_ *testing.T) cache.Cache { return cachetest.NewErrorCache(cachetest.ErrorCacheOptions{ GetBlob: true, }) @@ -508,6 +536,7 @@ func TestApplier_ApplyLayers(t *testing.T) { require.NoError(t, c.PutBlob("sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", types.BlobInfo{ SchemaVersion: 1, + Size: 1000, Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", PackageInfos: []types.PackageInfo{ @@ -527,6 +556,7 @@ func TestApplier_ApplyLayers(t *testing.T) { require.NoError(t, c.PutBlob("sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", types.BlobInfo{ SchemaVersion: 1, + Size: 2000, Digest: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", PackageInfos: []types.PackageInfo{ @@ -546,6 +576,7 @@ func TestApplier_ApplyLayers(t *testing.T) { require.NoError(t, c.PutBlob("sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", types.BlobInfo{ SchemaVersion: 1, + Size: 3000, Digest: "sha256:beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203", DiffID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", Applications: []types.Application{ @@ -639,6 +670,23 @@ func TestApplier_ApplyLayers(t *testing.T) { }, }, }, + Layers: types.Layers{ + { + Size: 1000, + Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + }, + { + Size: 2000, + Digest: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", + }, + { + Size: 3000, + Digest: "sha256:beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203", + DiffID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", + }, + }, }, wantErr: "unknown OS", }, @@ -690,6 +738,7 @@ func TestApplier_ApplyLayers(t *testing.T) { require.NoError(t, c.PutBlob("sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", types.BlobInfo{ SchemaVersion: 1, + Size: 1000, Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", PackageInfos: []types.PackageInfo{ @@ -725,6 +774,7 @@ func TestApplier_ApplyLayers(t *testing.T) { require.NoError(t, c.PutBlob("sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", types.BlobInfo{ SchemaVersion: 1, + Size: 2000, Digest: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", Applications: []types.Application{ @@ -856,6 +906,18 @@ func TestApplier_ApplyLayers(t *testing.T) { }, }, }, + Layers: types.Layers{ + { + Size: 1000, + Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + }, + { + Size: 2000, + Digest: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", + DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", + }, + }, }, wantErr: "unknown OS", }, @@ -875,6 +937,7 @@ func TestApplier_ApplyLayers(t *testing.T) { require.NoError(t, c.PutBlob("sha256:2615f175cf3da67c48c6542914744943ee5e9c253547b03e3cfe8aae605c3199", types.BlobInfo{ SchemaVersion: 1, + Size: 1000, Digest: "sha256:fb44d01953611ba18d43d88e158c25579d18eff42db671182245010620a283f3", DiffID: "sha256:d555e1b0b42f21a1cf198e52bcb12fe66aa015348e4390d2d5acddd327d79073", OS: types.OS{ @@ -930,6 +993,13 @@ func TestApplier_ApplyLayers(t *testing.T) { }, }, }, + Layers: types.Layers{ + { + Size: 1000, + Digest: "sha256:fb44d01953611ba18d43d88e158c25579d18eff42db671182245010620a283f3", + DiffID: "sha256:d555e1b0b42f21a1cf198e52bcb12fe66aa015348e4390d2d5acddd327d79073", + }, + }, }, }, } diff --git a/pkg/fanal/applier/docker.go b/pkg/fanal/applier/docker.go index bc979c3a599b..bb7c5a7cb079 100644 --- a/pkg/fanal/applier/docker.go +++ b/pkg/fanal/applier/docker.go @@ -267,6 +267,13 @@ func ApplyLayers(layers []ftypes.BlobInfo) ftypes.ArtifactDetail { } func newPURL(pkgType ftypes.TargetType, metadata types.Metadata, pkg ftypes.Package) *packageurl.PackageURL { + // Possible cases when package doesn't have name/version (e.g. local package.json). + // For these cases we don't need to create PURL, because this PURL will be incorrect. + // TODO Dmitriy - move to `purl` package + if pkg.Name == "" { + return nil + } + p, err := purl.New(pkgType, metadata, pkg) if err != nil { log.Error("Failed to create PackageURL", log.Err(err)) diff --git a/pkg/fanal/applier/docker_test.go b/pkg/fanal/applier/docker_test.go index 0269bbc7442c..e4cde96dcca0 100644 --- a/pkg/fanal/applier/docker_test.go +++ b/pkg/fanal/applier/docker_test.go @@ -22,6 +22,7 @@ func TestApplyLayers(t *testing.T) { inputLayers: []types.BlobInfo{ { SchemaVersion: 1, + Size: 1000, Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", OS: types.OS{ @@ -76,8 +77,10 @@ func TestApplyLayers(t *testing.T) { }, { SchemaVersion: 1, + Size: 2000, Digest: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", + WhiteoutFiles: []string{"app/composer.lock"}, PackageInfos: []types.PackageInfo{ { FilePath: "lib/apk/db/installed", @@ -96,10 +99,10 @@ func TestApplyLayers(t *testing.T) { }, }, }, - WhiteoutFiles: []string{"app/composer.lock"}, }, { SchemaVersion: 1, + Size: 3000, Digest: "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4", DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", PackageInfos: []types.PackageInfo{ @@ -259,6 +262,7 @@ func TestApplyLayers(t *testing.T) { inputLayers: []types.BlobInfo{ { SchemaVersion: 2, + Size: 1000, DiffID: "sha256:96e320b34b5478d8b369ca43ffaa88ff6dd9499ec72b792ca21b1e8b0c55670f", PackageInfos: []types.PackageInfo{ { @@ -276,6 +280,7 @@ func TestApplyLayers(t *testing.T) { }, { SchemaVersion: 2, + Size: 2000, DiffID: "sha256:5e087d956f3e62bd034dd0712bc4cbef8fda55fba0b11a7d0564f294887c7079", PackageInfos: []types.PackageInfo{ { @@ -421,6 +426,7 @@ func TestApplyLayers(t *testing.T) { inputLayers: []types.BlobInfo{ { SchemaVersion: 1, + Size: 1000, Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", OS: types.OS{ @@ -430,6 +436,7 @@ func TestApplyLayers(t *testing.T) { }, { SchemaVersion: 1, + Size: 2000, Digest: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", OS: types.OS{ @@ -451,6 +458,7 @@ func TestApplyLayers(t *testing.T) { inputLayers: []types.BlobInfo{ { SchemaVersion: 1, + Size: 1000, Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", OS: types.OS{ @@ -497,8 +505,13 @@ func TestApplyLayers(t *testing.T) { }, { SchemaVersion: 1, + Size: 2000, Digest: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", + WhiteoutFiles: []string{ + "app/composer.lock", + "var/lib/gems/2.5.0/specifications/activesupport-6.0.2.1.gemspec", + }, Applications: []types.Application{ { Type: types.Bundler, @@ -525,10 +538,6 @@ func TestApplyLayers(t *testing.T) { }, }, }, - WhiteoutFiles: []string{ - "app/composer.lock", - "var/lib/gems/2.5.0/specifications/activesupport-6.0.2.1.gemspec", - }, }, }, want: types.ArtifactDetail{ @@ -605,6 +614,7 @@ func TestApplyLayers(t *testing.T) { inputLayers: []types.BlobInfo{ { SchemaVersion: 2, + Size: 1000, Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", CreatedBy: "Line_1", @@ -639,6 +649,7 @@ func TestApplyLayers(t *testing.T) { }, { SchemaVersion: 2, + Size: 2000, Digest: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", CreatedBy: "Line_2", @@ -694,6 +705,7 @@ func TestApplyLayers(t *testing.T) { }, { SchemaVersion: 2, + Size: 3000, Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", CreatedBy: "Line_3", @@ -769,6 +781,7 @@ func TestApplyLayers(t *testing.T) { inputLayers: []types.BlobInfo{ { SchemaVersion: 1, + Size: 1000, Digest: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", OS: types.OS{ @@ -812,8 +825,10 @@ func TestApplyLayers(t *testing.T) { }, { SchemaVersion: 1, + Size: 2000, Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + OpaqueDirs: []string{"app"}, PackageInfos: []types.PackageInfo{ { FilePath: "var/lib/dpkg/status.d/libc", @@ -836,7 +851,6 @@ func TestApplyLayers(t *testing.T) { PkgName: "libc", }, }, - OpaqueDirs: []string{"app"}, }, }, want: types.ArtifactDetail{ @@ -903,6 +917,7 @@ func TestApplyLayers(t *testing.T) { inputLayers: []types.BlobInfo{ { SchemaVersion: 2, + Size: 1000, DiffID: "sha256:cdd7c73923174e45ea648d66996665c288e1b17a0f45efdbeca860f6dafdf731", OS: types.OS{ Family: "ubuntu", @@ -933,6 +948,7 @@ func TestApplyLayers(t *testing.T) { // Install `curl` { SchemaVersion: 2, + Size: 2000, DiffID: "sha256:faf30fa9c41c10f93b3b134d7b2c16e07753320393e020c481f0c97d10db067d", PackageInfos: []types.PackageInfo{ { @@ -971,6 +987,7 @@ func TestApplyLayers(t *testing.T) { // Upgrade `apt` { SchemaVersion: 2, + Size: 3000, DiffID: "sha256:440e26edc0eb9b4fee6e1d40d8af9eb59500d38e25edfc5d5302c55f59394c1e", PackageInfos: []types.PackageInfo{ { @@ -1008,6 +1025,7 @@ func TestApplyLayers(t *testing.T) { // Remove curl { SchemaVersion: 2, + Size: 4000, DiffID: "sha256:cb04e1d437de723d8d04bc7df89dc42271530c5f8ea1724c6072e3f0e7d6d38a", WhiteoutFiles: []string{ "usr/bin/curl", @@ -1085,6 +1103,7 @@ func TestApplyLayers(t *testing.T) { inputLayers: []types.BlobInfo{ { SchemaVersion: 1, + Size: 1000, Digest: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", Applications: []types.Application{ @@ -1102,6 +1121,7 @@ func TestApplyLayers(t *testing.T) { }, { SchemaVersion: 1, + Size: 2000, Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", OpaqueDirs: []string{"app/"}, @@ -1114,6 +1134,7 @@ func TestApplyLayers(t *testing.T) { inputLayers: []types.BlobInfo{ { SchemaVersion: 1, + Size: 1000, Digest: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", OS: types.OS{ @@ -1135,6 +1156,7 @@ func TestApplyLayers(t *testing.T) { }, { SchemaVersion: 1, + Size: 2000, Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", BuildInfo: &types.BuildInfo{ @@ -1163,6 +1185,7 @@ func TestApplyLayers(t *testing.T) { }, { SchemaVersion: 1, + Size: 3000, Digest: "sha256:a64e5f34c33ed4c5121498e721e24d95dae2c9599bee4aa6d07850702b401406", DiffID: "sha256:0abd3f2c73de6f02e033f410590111f9339b9500dc07270234f283f2d9a2694b", BuildInfo: &types.BuildInfo{ @@ -1172,6 +1195,7 @@ func TestApplyLayers(t *testing.T) { }, { SchemaVersion: 1, + Size: 4000, Digest: "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4", DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", PackageInfos: []types.PackageInfo{ @@ -1300,6 +1324,7 @@ func TestApplyLayers(t *testing.T) { inputLayers: []types.BlobInfo{ { SchemaVersion: 1, + Size: 1000, Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", Applications: []types.Application{ diff --git a/pkg/fanal/artifact/image/image.go b/pkg/fanal/artifact/image/image.go index 30f446476dbf..5af4b24b0b3d 100644 --- a/pkg/fanal/artifact/image/image.go +++ b/pkg/fanal/artifact/image/image.go @@ -28,8 +28,11 @@ import ( "github.com/aquasecurity/trivy/pkg/parallel" "github.com/aquasecurity/trivy/pkg/semaphore" trivyTypes "github.com/aquasecurity/trivy/pkg/types" + xio "github.com/aquasecurity/trivy/pkg/x/io" ) +const artifactVersion = 1 + type Artifact struct { logger *log.Logger image types.Image @@ -44,11 +47,6 @@ type Artifact struct { layerCacheDir string } -type LayerInfo struct { - DiffID string - CreatedBy string // can be empty -} - func NewArtifact(img types.Image, c cache.ArtifactCache, opt artifact.Option) (artifact.Artifact, error) { // Initialize handlers handlerManager, err := handler.NewManager(opt) @@ -168,7 +166,7 @@ func (a Artifact) Clean(_ artifact.Reference) error { func (a Artifact) calcCacheKeys(imageID string, diffIDs []string) (string, []string, error) { // Pass an empty config scanner option so that the cache key can be the same, even when policies are updated. - imageKey, err := cache.CalcKey(imageID, a.configAnalyzer.AnalyzerVersions(), nil, artifact.Option{}) + imageKey, err := cache.CalcKey(imageID, artifactVersion, a.configAnalyzer.AnalyzerVersions(), nil, artifact.Option{}) if err != nil { return "", nil, err } @@ -176,7 +174,7 @@ func (a Artifact) calcCacheKeys(imageID string, diffIDs []string) (string, []str hookVersions := a.handlerManager.Versions() var layerKeys []string for _, diffID := range diffIDs { - blobKey, err := cache.CalcKey(diffID, a.analyzer.AnalyzerVersions(), hookVersions, a.artifactOption) + blobKey, err := cache.CalcKey(diffID, artifactVersion, a.analyzer.AnalyzerVersions(), hookVersions, a.artifactOption) if err != nil { return "", nil, err } @@ -185,7 +183,7 @@ func (a Artifact) calcCacheKeys(imageID string, diffIDs []string) (string, []str return imageKey, layerKeys, nil } -func (a Artifact) consolidateCreatedBy(diffIDs, layerKeys []string, configFile *v1.ConfigFile) map[string]LayerInfo { +func (a Artifact) consolidateCreatedBy(diffIDs, layerKeys []string, configFile *v1.ConfigFile) map[string]types.Layer { // save createdBy fields in order of layers var createdBy []string for _, h := range configFile.History { @@ -202,16 +200,16 @@ func (a Artifact) consolidateCreatedBy(diffIDs, layerKeys []string, configFile * // TODO: our current logic may not detect empty layers correctly in rare cases. validCreatedBy := len(diffIDs) == len(createdBy) - layerKeyMap := make(map[string]LayerInfo) + layerKeyMap := make(map[string]types.Layer) for i, diffID := range diffIDs { - c := "" + var c string if validCreatedBy { c = createdBy[i] } layerKey := layerKeys[i] - layerKeyMap[layerKey] = LayerInfo{ + layerKeyMap[layerKey] = types.Layer{ DiffID: diffID, CreatedBy: c, } @@ -316,7 +314,7 @@ func (a Artifact) saveLayer(diffID string) (int64, error) { } func (a Artifact) inspect(ctx context.Context, missingImage string, layerKeys, baseDiffIDs []string, - layerKeyMap map[string]LayerInfo, configFile *v1.ConfigFile) error { + layerKeyMap map[string]types.Layer, configFile *v1.ConfigFile) error { var osFound types.OS p := parallel.NewPipeline(a.artifactOption.Parallel, false, layerKeys, func(ctx context.Context, @@ -356,15 +354,18 @@ func (a Artifact) inspect(ctx context.Context, missingImage string, layerKeys, b return nil } -func (a Artifact) inspectLayer(ctx context.Context, layerInfo LayerInfo, disabled []analyzer.Type) (types.BlobInfo, error) { - a.logger.Debug("Missing diff ID in cache", log.String("diff_id", layerInfo.DiffID)) +func (a Artifact) inspectLayer(ctx context.Context, layer types.Layer, disabled []analyzer.Type) (types.BlobInfo, error) { + a.logger.Debug("Missing diff ID in cache", log.String("diff_id", layer.DiffID)) - layerDigest, rc, err := a.uncompressedLayer(layerInfo.DiffID) + layerDigest, rc, err := a.uncompressedLayer(layer.DiffID) if err != nil { - return types.BlobInfo{}, xerrors.Errorf("unable to get uncompressed layer %s: %w", layerInfo.DiffID, err) + return types.BlobInfo{}, xerrors.Errorf("unable to get uncompressed layer %s: %w", layer.DiffID, err) } defer rc.Close() + // Count the bytes read from the layer + cr := xio.NewCountingReader(rc) + // Prepare variables var wg sync.WaitGroup opts := analyzer.AnalysisOptions{ @@ -382,7 +383,7 @@ func (a Artifact) inspectLayer(ctx context.Context, layerInfo LayerInfo, disable defer composite.Cleanup() // Walk a tar layer - opqDirs, whFiles, err := a.walker.Walk(rc, func(filePath string, info os.FileInfo, opener analyzer.Opener) error { + opqDirs, whFiles, err := a.walker.Walk(cr, func(filePath string, info os.FileInfo, opener analyzer.Opener) error { if err = a.analyzer.AnalyzeFile(ctx, &wg, limit, result, "", filePath, info, opener, disabled, opts); err != nil { return xerrors.Errorf("failed to analyze %s: %w", filePath, err) } @@ -416,14 +417,19 @@ func (a Artifact) inspectLayer(ctx context.Context, layerInfo LayerInfo, disable return types.BlobInfo{}, xerrors.Errorf("post analysis error: %w", err) } + // Read the remaining bytes for blocking factor to calculate the correct layer size + // cf. https://www.reddit.com/r/devops/comments/1gwpvrm/a_deep_dive_into_the_tar_format/ + _, _ = io.Copy(io.Discard, cr) + // Sort the analysis result for consistent results result.Sort() blobInfo := types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, + Size: cr.BytesRead(), Digest: layerDigest, - DiffID: layerInfo.DiffID, - CreatedBy: layerInfo.CreatedBy, + DiffID: layer.DiffID, + CreatedBy: layer.CreatedBy, OpaqueDirs: opqDirs, WhiteoutFiles: whFiles, OS: result.OS, diff --git a/pkg/fanal/artifact/image/image_test.go b/pkg/fanal/artifact/image/image_test.go index c5f2988f9f6a..fe6da5fb4ceb 100644 --- a/pkg/fanal/artifact/image/image_test.go +++ b/pkg/fanal/artifact/image/image_test.go @@ -353,9 +353,10 @@ func TestArtifact_Inspect(t *testing.T) { }, wantBlobs: []cachetest.WantBlob{ { - ID: "sha256:24a7af33784fabfedf01999d9e0dc456e8e1c1943f7d4421f7c05164026788a4", + ID: "sha256:5d77c13a4b76f19d2a01bb971b3d6c67e550dffdfb82aed6a0086e87218f33cb", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, + Size: 5861888, Digest: "", DiffID: "sha256:beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203", CreatedBy: "ADD file:0c4555f363c2672e350001f1293e689875a3760afe7b3f9146886afe67121cba in / ", @@ -401,7 +402,7 @@ func TestArtifact_Inspect(t *testing.T) { }, }, wantArtifact: cachetest.WantArtifact{ - ID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", + ID: "sha256:3c709d2a158be3a97051e10cd0e30f047225cb9505101feb3fadcd395c2e0408", ArtifactInfo: types.ArtifactInfo{ SchemaVersion: types.ArtifactJSONSchemaVersion, Architecture: "amd64", @@ -413,8 +414,8 @@ func TestArtifact_Inspect(t *testing.T) { want: artifact.Reference{ Name: "../../test/testdata/alpine-311.tar.gz", Type: types.TypeContainerImage, - ID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", - BlobIDs: []string{"sha256:24a7af33784fabfedf01999d9e0dc456e8e1c1943f7d4421f7c05164026788a4"}, + ID: "sha256:3c709d2a158be3a97051e10cd0e30f047225cb9505101feb3fadcd395c2e0408", + BlobIDs: []string{"sha256:5d77c13a4b76f19d2a01bb971b3d6c67e550dffdfb82aed6a0086e87218f33cb"}, ImageMetadata: artifact.ImageMetadata{ ID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", DiffIDs: []string{ @@ -471,22 +472,23 @@ func TestArtifact_Inspect(t *testing.T) { }, setupCache: func(t *testing.T) cache.Cache { c := cache.NewMemoryCache() - require.NoError(t, c.PutArtifact("sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", types.ArtifactInfo{ + require.NoError(t, c.PutArtifact("sha256:0bebf0773ffd87baa7c64fbdbdf79a24ae125e3f99a8adebe52d1ccbe6bed16b", types.ArtifactInfo{ SchemaVersion: types.ArtifactJSONSchemaVersion, })) return c }, wantArtifact: cachetest.WantArtifact{ - ID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", + ID: "sha256:0bebf0773ffd87baa7c64fbdbdf79a24ae125e3f99a8adebe52d1ccbe6bed16b", ArtifactInfo: types.ArtifactInfo{ SchemaVersion: types.ArtifactJSONSchemaVersion, }, }, wantBlobs: []cachetest.WantBlob{ { - ID: "sha256:4a26915356c961f038d5a7b7f73f24cd1eec53dcf6fdeecd39b310ddc066faec", + ID: "sha256:67c88202c1f92978398a3ceff0c536282eb31972d92c1b913afb055f05dd05fd", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, + Size: 3061760, Digest: "", DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", CreatedBy: "bazel build ...", @@ -571,9 +573,10 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "sha256:e23e1d428a2a4ca9607cde5c556f744c7e9f3a1d3bfe835707c0fea107caf453", + ID: "sha256:b81648381fb192c7034e72d517dd46aedf392f628b2a242c0ed0b06b8e9c2bec", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, + Size: 15441920, Digest: "", DiffID: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", CreatedBy: "bazel build ...", @@ -665,12 +668,14 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "sha256:b8ae022ed4f8b8bf827c04a825c2e6998217581d44c0b28b59a4e66ca65bbaa5", + ID: "sha256:2d916a1f47dadcbf5c842a0dd26fe4fb2f94743d829e23dc614c0883e5473aaf", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, + Size: 29696, Digest: "", DiffID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", CreatedBy: "COPY file:842584685f26edb24dc305d76894f51cfda2bad0c24a05e727f9d4905d184a70 in /php-app/composer.lock ", + OpaqueDirs: []string{"php-app/"}, Applications: []types.Application{ { Type: "composer", @@ -867,16 +872,19 @@ func TestArtifact_Inspect(t *testing.T) { }, }, }, - OpaqueDirs: []string{"php-app/"}, }, }, { - ID: "sha256:8c51dcc708602d983f3f0507f0d26de609819c4391db92497639417e54378d11", + ID: "sha256:ed1912a62cc8057cbb5ed9e1cfa47d34270e2417dd51514185e24852d8001686", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, + Size: 6656, Digest: "", DiffID: "sha256:a4595c43a874856bf95f3bfc4fbf78bbaa04c92c726276d4f64193a47ced0566", CreatedBy: "COPY file:c6d0373d380252b91829a5bb3c81d5b1afa574c91cef7752d18170a231c31f6d in /ruby-app/Gemfile.lock ", + OpaqueDirs: []string{ + "ruby-app/", + }, Applications: []types.Application{ { Type: "bundler", @@ -1722,21 +1730,18 @@ func TestArtifact_Inspect(t *testing.T) { }, }, }, - OpaqueDirs: []string{ - "ruby-app/", - }, }, }, }, want: artifact.Reference{ Name: "../../test/testdata/vuln-image.tar.gz", Type: types.TypeContainerImage, - ID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", + ID: "sha256:0bebf0773ffd87baa7c64fbdbdf79a24ae125e3f99a8adebe52d1ccbe6bed16b", BlobIDs: []string{ - "sha256:4a26915356c961f038d5a7b7f73f24cd1eec53dcf6fdeecd39b310ddc066faec", - "sha256:e23e1d428a2a4ca9607cde5c556f744c7e9f3a1d3bfe835707c0fea107caf453", - "sha256:b8ae022ed4f8b8bf827c04a825c2e6998217581d44c0b28b59a4e66ca65bbaa5", - "sha256:8c51dcc708602d983f3f0507f0d26de609819c4391db92497639417e54378d11", + "sha256:67c88202c1f92978398a3ceff0c536282eb31972d92c1b913afb055f05dd05fd", + "sha256:b81648381fb192c7034e72d517dd46aedf392f628b2a242c0ed0b06b8e9c2bec", + "sha256:2d916a1f47dadcbf5c842a0dd26fe4fb2f94743d829e23dc614c0883e5473aaf", + "sha256:ed1912a62cc8057cbb5ed9e1cfa47d34270e2417dd51514185e24852d8001686", }, ImageMetadata: artifact.ImageMetadata{ ID: "sha256:58701fd185bda36cab0557bb6438661831267aa4a9e0b54211c4d5317a48aff4", @@ -1754,13 +1759,13 @@ func TestArtifact_Inspect(t *testing.T) { History: []v1.History{ { Author: "Bazel", - Created: v1.Time{Time: time.Date(1970, 01, 01, 0, 0, 0, 0, time.UTC)}, + Created: v1.Time{Time: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)}, CreatedBy: "bazel build ...", EmptyLayer: false, }, { Author: "Bazel", - Created: v1.Time{Time: time.Date(1970, 01, 01, 0, 0, 0, 0, time.UTC)}, + Created: v1.Time{Time: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)}, CreatedBy: "bazel build ...", EmptyLayer: false, }, @@ -1828,40 +1833,43 @@ func TestArtifact_Inspect(t *testing.T) { }, setupCache: func(t *testing.T) cache.Cache { c := cache.NewMemoryCache() - require.NoError(t, c.PutArtifact("sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", types.ArtifactInfo{ + require.NoError(t, c.PutArtifact("sha256:0bebf0773ffd87baa7c64fbdbdf79a24ae125e3f99a8adebe52d1ccbe6bed16b", types.ArtifactInfo{ SchemaVersion: types.ArtifactJSONSchemaVersion, })) return c }, wantArtifact: cachetest.WantArtifact{ - ID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", + ID: "sha256:0bebf0773ffd87baa7c64fbdbdf79a24ae125e3f99a8adebe52d1ccbe6bed16b", ArtifactInfo: types.ArtifactInfo{ SchemaVersion: types.ArtifactJSONSchemaVersion, }, }, wantBlobs: []cachetest.WantBlob{ { - ID: "sha256:139bc12e936e0c46090b9380c4a29456d3ad8d8abd50c7bdc6160018cd887462", + ID: "sha256:f804054cbf18c82fbb7a136450a161264bb3e531d29aedb4c13eada052b379e6", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, + Size: 3061760, Digest: "", DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", CreatedBy: "bazel build ...", }, }, { - ID: "sha256:c491838e70ff0fcfdd0605af1ba84e86d6958c0846b16c52a84e06bb344e8e8d", + ID: "sha256:5d76e1081a0f708a98132175ac81b897c8efa5d39ab75e5df87f42ac619962d8", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, + Size: 15441920, Digest: "", DiffID: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", CreatedBy: "bazel build ...", }, }, { - ID: "sha256:25e775ef81049a93eafd865447b0b79da9e9956ab74bc02b5916eaea21c87c7c", + ID: "sha256:6937ca98d84de22331089747a24e9e8b032c61133958a42990d5a5af718fdcd8", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, + Size: 29696, Digest: "", DiffID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", CreatedBy: "COPY file:842584685f26edb24dc305d76894f51cfda2bad0c24a05e727f9d4905d184a70 in /php-app/composer.lock ", @@ -1869,9 +1877,10 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "sha256:a8a4798a22b65739cda9ca99ddb2cd86125c1dd86df6fc3971f937a0ff5b9ec3", + ID: "sha256:b6d38e35ff57fda90aa2e57f02fd96e9919d29dbb22d45d95fe3534d79af08fc", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, + Size: 6656, Digest: "", DiffID: "sha256:a4595c43a874856bf95f3bfc4fbf78bbaa04c92c726276d4f64193a47ced0566", CreatedBy: "COPY file:c6d0373d380252b91829a5bb3c81d5b1afa574c91cef7752d18170a231c31f6d in /ruby-app/Gemfile.lock ", @@ -1882,12 +1891,12 @@ func TestArtifact_Inspect(t *testing.T) { want: artifact.Reference{ Name: "../../test/testdata/vuln-image.tar.gz", Type: types.TypeContainerImage, - ID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", + ID: "sha256:0bebf0773ffd87baa7c64fbdbdf79a24ae125e3f99a8adebe52d1ccbe6bed16b", BlobIDs: []string{ - "sha256:139bc12e936e0c46090b9380c4a29456d3ad8d8abd50c7bdc6160018cd887462", - "sha256:c491838e70ff0fcfdd0605af1ba84e86d6958c0846b16c52a84e06bb344e8e8d", - "sha256:25e775ef81049a93eafd865447b0b79da9e9956ab74bc02b5916eaea21c87c7c", - "sha256:a8a4798a22b65739cda9ca99ddb2cd86125c1dd86df6fc3971f937a0ff5b9ec3", + "sha256:f804054cbf18c82fbb7a136450a161264bb3e531d29aedb4c13eada052b379e6", + "sha256:5d76e1081a0f708a98132175ac81b897c8efa5d39ab75e5df87f42ac619962d8", + "sha256:6937ca98d84de22331089747a24e9e8b032c61133958a42990d5a5af718fdcd8", + "sha256:b6d38e35ff57fda90aa2e57f02fd96e9919d29dbb22d45d95fe3534d79af08fc", }, ImageMetadata: artifact.ImageMetadata{ ID: "sha256:58701fd185bda36cab0557bb6438661831267aa4a9e0b54211c4d5317a48aff4", @@ -1905,14 +1914,14 @@ func TestArtifact_Inspect(t *testing.T) { History: []v1.History{ { Author: "Bazel", - Created: v1.Time{Time: time.Date(1970, 01, 01, 0, 0, 0, 0, time.UTC)}, + Created: v1.Time{Time: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)}, CreatedBy: "bazel build ...", Comment: "", EmptyLayer: false, }, { Author: "Bazel", - Created: v1.Time{Time: time.Date(1970, 01, 01, 0, 0, 0, 0, time.UTC)}, + Created: v1.Time{Time: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)}, CreatedBy: "bazel build ...", Comment: "", EmptyLayer: false, @@ -1967,7 +1976,7 @@ func TestArtifact_Inspect(t *testing.T) { { name: "sad path, MissingBlobs returns an error", imagePath: "../../test/testdata/alpine-311.tar.gz", - setupCache: func(t *testing.T) cache.Cache { + setupCache: func(_ *testing.T) cache.Cache { return cachetest.NewErrorCache(cachetest.ErrorCacheOptions{ MissingBlobs: true, }) @@ -1977,7 +1986,7 @@ func TestArtifact_Inspect(t *testing.T) { { name: "sad path, PutBlob returns an error", imagePath: "../../test/testdata/alpine-311.tar.gz", - setupCache: func(t *testing.T) cache.Cache { + setupCache: func(_ *testing.T) cache.Cache { return cachetest.NewErrorCache(cachetest.ErrorCacheOptions{ PutBlob: true, }) @@ -1987,7 +1996,7 @@ func TestArtifact_Inspect(t *testing.T) { { name: "sad path, PutBlob returns an error with multiple layers", imagePath: "../../test/testdata/vuln-image.tar.gz", - setupCache: func(t *testing.T) cache.Cache { + setupCache: func(_ *testing.T) cache.Cache { return cachetest.NewErrorCache(cachetest.ErrorCacheOptions{ PutBlob: true, }) @@ -1997,7 +2006,7 @@ func TestArtifact_Inspect(t *testing.T) { { name: "sad path, PutArtifact returns an error", imagePath: "../../test/testdata/alpine-311.tar.gz", - setupCache: func(t *testing.T) cache.Cache { + setupCache: func(_ *testing.T) cache.Cache { return cachetest.NewErrorCache(cachetest.ErrorCacheOptions{ PutArtifact: true, }) diff --git a/pkg/fanal/artifact/local/fs.go b/pkg/fanal/artifact/local/fs.go index 71e16ed51230..5b9ba9e6585a 100644 --- a/pkg/fanal/artifact/local/fs.go +++ b/pkg/fanal/artifact/local/fs.go @@ -30,6 +30,8 @@ import ( "github.com/aquasecurity/trivy/pkg/uuid" ) +const artifactVersion = 0 + var ( ArtifactSet = wire.NewSet( walker.NewFS, @@ -205,6 +207,9 @@ func (a Artifact) Inspect(ctx context.Context) (artifact.Reference, error) { Secrets: result.Secrets, Licenses: result.Licenses, CustomResources: result.CustomResources, + + // For Red Hat + BuildInfo: result.BuildInfo, } if err = a.handlerManager.PostHandle(ctx, result, &blobInfo); err != nil { @@ -299,7 +304,7 @@ func (a Artifact) Clean(reference artifact.Reference) error { func (a Artifact) calcCacheKey() (string, error) { // If this is a clean git repository, use the commit hash as cache key if a.commitHash != "" { - return cache.CalcKey(a.commitHash, a.analyzer.AnalyzerVersions(), a.handlerManager.Versions(), a.artifactOption) + return cache.CalcKey(a.commitHash, artifactVersion, a.analyzer.AnalyzerVersions(), a.handlerManager.Versions(), a.artifactOption) } // For non-git repositories or dirty git repositories, use UUID as cache key diff --git a/pkg/fanal/artifact/local/fs_test.go b/pkg/fanal/artifact/local/fs_test.go index 36228e24f29e..0c646994c2d3 100644 --- a/pkg/fanal/artifact/local/fs_test.go +++ b/pkg/fanal/artifact/local/fs_test.go @@ -123,7 +123,7 @@ func TestArtifact_Inspect(t *testing.T) { fields: fields{ dir: "./testdata/alpine", }, - setupCache: func(t *testing.T) cache.Cache { + setupCache: func(_ *testing.T) cache.Cache { return cachetest.NewErrorCache(cachetest.ErrorCacheOptions{ PutBlob: true, }) @@ -622,6 +622,73 @@ func TestTerraformMisconfigurationScan(t *testing.T) { }, }, }, + { + name: "scan raw config", + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RawConfigScanners: []types.ConfigType{types.Terraform}, + }, + }, + fields: fields{ + dir: "./testdata/misconfig/terraform/single-failure", + }, + wantBlobs: []cachetest.WantBlob{ + { + ID: "sha256:6f4672e139d4066fd00391df614cdf42bda5f7a3f005d39e1d8600be86157098", + BlobInfo: types.BlobInfo{ + SchemaVersion: 2, + Misconfigurations: []types.Misconfiguration{ + { + FileType: "terraform", + FilePath: "main.tf", + Failures: types.MisconfResults{ + { + Namespace: "user.something", + Query: "data.user.something.deny", + Message: "Empty bucket name!", + PolicyMetadata: terraformPolicyMetadata, + CauseMetadata: types.CauseMetadata{ + Resource: "aws_s3_bucket.asd", + Provider: "Generic", + Service: "general", + StartLine: 1, + EndLine: 3, + }, + }, + { + Namespace: "user.test002", + Query: "data.user.test002.deny", + Message: "Empty bucket name!", + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST002", + AVDID: "AVD-TEST-0002", + Type: "Terraform Security Check", + Title: "Test policy", + Severity: "LOW", + }, + CauseMetadata: types.CauseMetadata{ + Resource: "aws_s3_bucket.asd", + Provider: "Terraform-Raw", + Service: "general", + StartLine: 1, + EndLine: 3, + }, + }, + }, + }, + }, + }, + }, + }, + want: artifact.Reference{ + Name: "testdata/misconfig/terraform/single-failure", + Type: types.TypeFilesystem, + ID: "sha256:6f4672e139d4066fd00391df614cdf42bda5f7a3f005d39e1d8600be86157098", + BlobIDs: []string{ + "sha256:6f4672e139d4066fd00391df614cdf42bda5f7a3f005d39e1d8600be86157098", + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -704,7 +771,6 @@ func TestTerraformPlanSnapshotMisconfScan(t *testing.T) { { name: "single failure", fields: fields{ - dir: "./testdata/misconfig/terraformplan/snapshots/single-failure", }, wantBlobs: []cachetest.WantBlob{ @@ -2463,72 +2529,3 @@ func TestArtifact_AnalysisStrategy(t *testing.T) { }) } } - -// TestAnalyzerGroup_StaticPaths tests the StaticPaths method of AnalyzerGroup -func TestAnalyzerGroup_StaticPaths(t *testing.T) { - tests := []struct { - name string - disabledAnalyzers []analyzer.Type - filePatterns []string - want []string - wantAllStatic bool - }{ - { - name: "all analyzers implement StaticPathAnalyzer", - disabledAnalyzers: append(analyzer.TypeConfigFiles, analyzer.TypePip, analyzer.TypeSecret), - want: []string{ - "lib/apk/db/installed", - "etc/alpine-release", - }, - wantAllStatic: true, - }, - { - name: "all analyzers implement StaticPathAnalyzer, but there is file pattern", - disabledAnalyzers: append(analyzer.TypeConfigFiles, analyzer.TypePip, analyzer.TypeSecret), - filePatterns: []string{ - "alpine:etc/alpine-release-custom", - }, - want: []string{}, - wantAllStatic: false, - }, - { - name: "some analyzers don't implement StaticPathAnalyzer", - want: []string{}, - wantAllStatic: false, - }, - { - name: "only PostAnalyzers are enabled", - disabledAnalyzers: []analyzer.Type{ - analyzer.TypePip, - analyzer.TypeSecret, - }, - want: []string{}, - wantAllStatic: false, - }, - { - name: "disable all analyzers", - disabledAnalyzers: append(analyzer.TypeConfigFiles, analyzer.TypePip, analyzer.TypeApk, analyzer.TypeAlpine, analyzer.TypeSecret), - want: []string{}, - wantAllStatic: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Create a new analyzer group - a, err := analyzer.NewAnalyzerGroup(analyzer.AnalyzerOptions{ - FilePatterns: tt.filePatterns, - }) - require.NoError(t, err) - - // Get static paths - gotPaths, gotAllStatic := a.StaticPaths(tt.disabledAnalyzers) - - // Check if all analyzers implement StaticPathAnalyzer - assert.Equal(t, tt.wantAllStatic, gotAllStatic) - - // Check paths - assert.ElementsMatch(t, tt.want, gotPaths) - }) - } -} diff --git a/pkg/fanal/artifact/local/testdata/misconfig/terraform/rego/policy-raw.rego b/pkg/fanal/artifact/local/testdata/misconfig/terraform/rego/policy-raw.rego new file mode 100644 index 000000000000..15cc54dfd006 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/terraform/rego/policy-raw.rego @@ -0,0 +1,23 @@ +# METADATA +# title: Test policy +# schemas: +# - input: schema["terraform-raw"] +# custom: +# id: TEST002 +# avd_id: AVD-TEST-0002 +# short_code: empty-bucket-name +# severity: LOW +# input: +# selector: +# - type: terraform-raw +package user.test002 + +import rego.v1 + +deny contains res if { + some block in input.modules[_].blocks + block.kind == "resource" + block.type == "aws_s3_bucket" + not "bucket" in block.attributes + res := result.new("Empty bucket name!", block) +} diff --git a/pkg/fanal/artifact/repo/git_test.go b/pkg/fanal/artifact/repo/git_test.go index 664ed036a67f..70c1014b1d0a 100644 --- a/pkg/fanal/artifact/repo/git_test.go +++ b/pkg/fanal/artifact/repo/git_test.go @@ -99,7 +99,7 @@ func TestNewArtifact(t *testing.T) { c: nil, noProgress: false, }, - assertion: func(t assert.TestingT, err error, args ...any) bool { + assertion: func(t assert.TestingT, err error, _ ...any) bool { return assert.ErrorContains(t, err, "repository not found") }, }, @@ -110,7 +110,7 @@ func TestNewArtifact(t *testing.T) { c: nil, noProgress: false, }, - assertion: func(t assert.TestingT, err error, args ...any) bool { + assertion: func(t assert.TestingT, err error, _ ...any) bool { return assert.ErrorContains(t, err, "url parse error") }, }, @@ -121,7 +121,7 @@ func TestNewArtifact(t *testing.T) { c: nil, repoBranch: "invalid-branch", }, - assertion: func(t assert.TestingT, err error, args ...any) bool { + assertion: func(t assert.TestingT, err error, _ ...any) bool { return assert.ErrorContains(t, err, `couldn't find remote ref "refs/heads/invalid-branch"`) }, }, @@ -132,7 +132,7 @@ func TestNewArtifact(t *testing.T) { c: nil, repoTag: "v1.0.9", }, - assertion: func(t assert.TestingT, err error, args ...any) bool { + assertion: func(t assert.TestingT, err error, _ ...any) bool { return assert.ErrorContains(t, err, `couldn't find remote ref "refs/tags/v1.0.9"`) }, }, @@ -143,7 +143,7 @@ func TestNewArtifact(t *testing.T) { c: nil, repoCommit: "6ac152fe2b87cb5e243414df71790a32912e778e", }, - assertion: func(t assert.TestingT, err error, args ...any) bool { + assertion: func(t assert.TestingT, err error, _ ...any) bool { return assert.ErrorContains(t, err, "git checkout error: object not found") }, }, @@ -209,7 +209,7 @@ func TestArtifact_Inspect(t *testing.T) { name: "dirty repository", rawurl: "../../../../internal/gittest/testdata/test-repo", setup: func(t *testing.T, dir string, _ cache.ArtifactCache) { - require.NoError(t, os.WriteFile(filepath.Join(dir, "new-file.txt"), []byte("test"), 0644)) + require.NoError(t, os.WriteFile(filepath.Join(dir, "new-file.txt"), []byte("test"), 0o644)) t.Cleanup(func() { require.NoError(t, os.Remove(filepath.Join(dir, "new-file.txt"))) }) @@ -229,7 +229,7 @@ func TestArtifact_Inspect(t *testing.T) { { name: "cache hit", rawurl: "../../../../internal/gittest/testdata/test-repo", - setup: func(t *testing.T, dir string, c cache.ArtifactCache) { + setup: func(t *testing.T, _ string, c cache.ArtifactCache) { blobInfo := types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, OS: types.OS{ diff --git a/pkg/fanal/artifact/sbom/sbom.go b/pkg/fanal/artifact/sbom/sbom.go index f964eb12f9e1..fdb31defcc8b 100644 --- a/pkg/fanal/artifact/sbom/sbom.go +++ b/pkg/fanal/artifact/sbom/sbom.go @@ -20,6 +20,8 @@ import ( "github.com/aquasecurity/trivy/pkg/sbom" ) +const artifactVersion = 0 + type Artifact struct { filePath string cache cache.ArtifactCache @@ -105,7 +107,7 @@ func (a Artifact) calcCacheKey(blobInfo types.BlobInfo) (string, error) { } d := digest.NewDigest(digest.SHA256, h) - cacheKey, err := cache.CalcKey(d.String(), a.analyzer.AnalyzerVersions(), a.handlerManager.Versions(), a.artifactOption) + cacheKey, err := cache.CalcKey(d.String(), artifactVersion, a.analyzer.AnalyzerVersions(), a.handlerManager.Versions(), a.artifactOption) if err != nil { return "", xerrors.Errorf("cache key: %w", err) } diff --git a/pkg/fanal/artifact/sbom/sbom_test.go b/pkg/fanal/artifact/sbom/sbom_test.go index c52c7fb2d8fb..53cadf905123 100644 --- a/pkg/fanal/artifact/sbom/sbom_test.go +++ b/pkg/fanal/artifact/sbom/sbom_test.go @@ -378,7 +378,7 @@ func TestArtifact_Inspect(t *testing.T) { { name: "sad path PutBlob returns an error", filePath: filepath.Join("testdata", "os-only-bom.json"), - setUpCache: func(t *testing.T) cache.Cache { + setUpCache: func(_ *testing.T) cache.Cache { return cachetest.NewErrorCache(cachetest.ErrorCacheOptions{ PutBlob: true, }) diff --git a/pkg/fanal/artifact/vm/ebs.go b/pkg/fanal/artifact/vm/ebs.go index a4c47c676eaf..e100ec2f5a7b 100644 --- a/pkg/fanal/artifact/vm/ebs.go +++ b/pkg/fanal/artifact/vm/ebs.go @@ -19,6 +19,8 @@ import ( // Max cache memory size 64 MB const storageEBSCacheSize = 128 +const ebsArtifactVersion = 0 + // EBS represents an artifact for AWS EBS snapshots type EBS struct { Storage @@ -100,7 +102,7 @@ func (a *EBS) SetEBS(ebs ebsfile.EBSAPI) { } func (a *EBS) calcCacheKey(key string) (string, error) { - s, err := cache.CalcKey(key, a.analyzer.AnalyzerVersions(), a.handlerManager.Versions(), a.artifactOption) + s, err := cache.CalcKey(key, ebsArtifactVersion, a.analyzer.AnalyzerVersions(), a.handlerManager.Versions(), a.artifactOption) if err != nil { return "", xerrors.Errorf("failed to calculate cache key: %w", err) } diff --git a/pkg/fanal/artifact/vm/file.go b/pkg/fanal/artifact/vm/file.go index 3793c528972b..43c629fd159f 100644 --- a/pkg/fanal/artifact/vm/file.go +++ b/pkg/fanal/artifact/vm/file.go @@ -24,6 +24,8 @@ import ( // If vm type vmdk max cache memory size 64 MB const storageFILECacheSize = 1024 +const imageFileArtifactVersion = 0 + // ImageFile represents an local VM image file type ImageFile struct { Storage @@ -100,7 +102,7 @@ func (a *ImageFile) calcCacheKey(blobInfo types.BlobInfo) (string, error) { } d := digest.NewDigest(digest.SHA256, h) - cacheKey, err := cache.CalcKey(d.String(), a.analyzer.AnalyzerVersions(), a.handlerManager.Versions(), a.artifactOption) + cacheKey, err := cache.CalcKey(d.String(), imageFileArtifactVersion, a.analyzer.AnalyzerVersions(), a.handlerManager.Versions(), a.artifactOption) if err != nil { return "", xerrors.Errorf("cache key: %w", err) } diff --git a/pkg/fanal/artifact/vm/vm.go b/pkg/fanal/artifact/vm/vm.go index 56f7a0f5fa88..c10d3912993e 100644 --- a/pkg/fanal/artifact/vm/vm.go +++ b/pkg/fanal/artifact/vm/vm.go @@ -157,6 +157,9 @@ func (a *Storage) Analyze(ctx context.Context, r *io.SectionReader) (types.BlobI Secrets: result.Secrets, Licenses: result.Licenses, CustomResources: result.CustomResources, + + // For Red Hat + BuildInfo: result.BuildInfo, } if err = a.handlerManager.PostHandle(ctx, result, &blobInfo); err != nil { diff --git a/pkg/fanal/artifact/vm/vm_test.go b/pkg/fanal/artifact/vm/vm_test.go index ab74d389dfbb..4231026de219 100644 --- a/pkg/fanal/artifact/vm/vm_test.go +++ b/pkg/fanal/artifact/vm/vm_test.go @@ -78,14 +78,14 @@ func TestNewArtifact(t *testing.T) { { name: "sad path unsupported vm format", target: "testdata/monolithicSparse.vmdk", - wantErr: func(t assert.TestingT, err error, args ...any) bool { + wantErr: func(t assert.TestingT, err error, _ ...any) bool { return assert.ErrorContains(t, err, "unsupported type error") }, }, { name: "sad path file not found", target: "testdata/no-file", - wantErr: func(t assert.TestingT, err error, args ...any) bool { + wantErr: func(t assert.TestingT, err error, _ ...any) bool { return assert.ErrorContains(t, err, "file open error") }, }, diff --git a/pkg/fanal/handler/handler_test.go b/pkg/fanal/handler/handler_test.go index 72dead239eab..b182584a145a 100644 --- a/pkg/fanal/handler/handler_test.go +++ b/pkg/fanal/handler/handler_test.go @@ -15,7 +15,7 @@ import ( type fakeHook struct{} -func (h fakeHook) Handle(ctx context.Context, result *analyzer.AnalysisResult, info *types.BlobInfo) error { +func (h fakeHook) Handle(_ context.Context, _ *analyzer.AnalysisResult, info *types.BlobInfo) error { info.DiffID = "fake" return nil } diff --git a/pkg/fanal/handler/sysfile/filter.go b/pkg/fanal/handler/sysfile/filter.go index cbe1e84f18d4..2d854ab45d29 100644 --- a/pkg/fanal/handler/sysfile/filter.go +++ b/pkg/fanal/handler/sysfile/filter.go @@ -17,32 +17,13 @@ func init() { const version = 1 -var ( - defaultSystemFiles = []string{ - // TODO: Google Distroless removes /var/lib/dpkg/info/*.list, so we cannot know which files are installed by dpkg. - // We have to hardcode these files at the moment, but should look for the better way. - "/usr/lib/python2.7/argparse.egg-info", - "/usr/lib/python2.7/lib-dynload/Python-2.7.egg-info", - "/usr/lib/python2.7/wsgiref.egg-info", - } - - affectedTypes = []types.LangType{ - // ruby - types.GemSpec, - - // python - types.PythonPkg, - - // conda - types.CondaPkg, - - // node.js - types.NodePkg, - - // Go binaries - types.GoBinary, - } -) +var defaultSystemFiles = []string{ + // TODO: Google Distroless removes /var/lib/dpkg/info/*.list, so we cannot know which files are installed by dpkg. + // We have to hardcode these files at the moment, but should look for the better way. + "/usr/lib/python2.7/argparse.egg-info", + "/usr/lib/python2.7/lib-dynload/Python-2.7.egg-info", + "/usr/lib/python2.7/wsgiref.egg-info", +} type systemFileFilteringPostHandler struct{} @@ -67,7 +48,7 @@ func (h systemFileFilteringPostHandler) Handle(_ context.Context, result *analyz for _, app := range blob.Applications { // If the lang-specific package was installed by OS package manager, it should not be taken. // Otherwise, the package version will be wrong, then it will lead to false positive. - if slices.Contains(systemFiles, app.FilePath) && slices.Contains(affectedTypes, app.Type) { + if slices.Contains(systemFiles, app.FilePath) { continue } diff --git a/pkg/fanal/handler/sysfile/filter_test.go b/pkg/fanal/handler/sysfile/filter_test.go index 663949193391..5bbee9432dca 100644 --- a/pkg/fanal/handler/sysfile/filter_test.go +++ b/pkg/fanal/handler/sysfile/filter_test.go @@ -219,42 +219,6 @@ func Test_systemFileFilterHook_Hook(t *testing.T) { }, want: &types.BlobInfo{}, }, - { - name: "Rust will not be skipped", - result: &analyzer.AnalysisResult{ - SystemInstalledFiles: []string{ - "app/Cargo.lock", - }, - }, - blob: &types.BlobInfo{ - Applications: []types.Application{ - { - Type: types.Cargo, - FilePath: "app/Cargo.lock", - Packages: types.Packages{ - { - Name: "ghash", - Version: "0.4.4", - }, - }, - }, - }, - }, - want: &types.BlobInfo{ - Applications: []types.Application{ - { - Type: types.Cargo, - FilePath: "app/Cargo.lock", - Packages: types.Packages{ - { - Name: "ghash", - Version: "0.4.4", - }, - }, - }, - }, - }, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/fanal/image/daemon/containerd.go b/pkg/fanal/image/daemon/containerd.go index 6622d3c81852..5153906797d5 100644 --- a/pkg/fanal/image/daemon/containerd.go +++ b/pkg/fanal/image/daemon/containerd.go @@ -54,7 +54,7 @@ func (n familiarNamed) String() string { } func imageWriter(c *client.Client, img client.Image, platform types.Platform) imageSave { - return func(ctx context.Context, ref []string, saveOptions ...dockerClient.ImageSaveOption) (io.ReadCloser, error) { + return func(ctx context.Context, ref []string, _ ...dockerClient.ImageSaveOption) (io.ReadCloser, error) { if len(ref) < 1 { return nil, xerrors.New("no image reference") } diff --git a/pkg/fanal/image/daemon/podman_test.go b/pkg/fanal/image/daemon/podman_test.go index 066f9dbaee68..141458b7f890 100644 --- a/pkg/fanal/image/daemon/podman_test.go +++ b/pkg/fanal/image/daemon/podman_test.go @@ -99,7 +99,7 @@ func TestPodmanImage(t *testing.T) { confFile, err := img.ConfigFile() require.NoError(t, err) - assert.Equal(t, len(confFile.History), len(tt.wantCreateBy)) + assert.Len(t, tt.wantCreateBy, len(confFile.History)) for _, h := range confFile.History { assert.Contains(t, tt.wantCreateBy, h.CreatedBy) } diff --git a/pkg/fanal/image/image_test.go b/pkg/fanal/image/image_test.go index abdd3467fd7c..43d3154d5440 100644 --- a/pkg/fanal/image/image_test.go +++ b/pkg/fanal/image/image_test.go @@ -163,16 +163,16 @@ func TestNewDockerImage(t *testing.T) { wantConfigFile: &v1.ConfigFile{ Architecture: "amd64", Container: "7f4a36a667d138b079b5ff059485ff65bfbb5ebc48f24a89f983b918e73f4f28", - Created: v1.Time{Time: time.Date(2020, 1, 23, 16, 53, 06, 686519038, time.UTC)}, + Created: v1.Time{Time: time.Date(2020, 1, 23, 16, 53, 6, 686519038, time.UTC)}, DockerVersion: "18.06.1-ce", History: []v1.History{ { - Created: v1.Time{Time: time.Date(2020, 1, 23, 16, 53, 06, 551172402, time.UTC)}, + Created: v1.Time{Time: time.Date(2020, 1, 23, 16, 53, 6, 551172402, time.UTC)}, CreatedBy: "/bin/sh -c #(nop) ADD file:d48cac34fac385cbc1de6adfdd88300f76f9bbe346cd17e64fd834d042a98326 in / ", EmptyLayer: false, }, { - Created: v1.Time{Time: time.Date(2020, 1, 23, 16, 53, 06, 686519038, time.UTC)}, + Created: v1.Time{Time: time.Date(2020, 1, 23, 16, 53, 6, 686519038, time.UTC)}, CreatedBy: "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", Comment: "", EmptyLayer: true, @@ -222,16 +222,16 @@ func TestNewDockerImage(t *testing.T) { wantConfigFile: &v1.ConfigFile{ Architecture: "amd64", Container: "7f4a36a667d138b079b5ff059485ff65bfbb5ebc48f24a89f983b918e73f4f28", - Created: v1.Time{Time: time.Date(2020, 1, 23, 16, 53, 06, 686519038, time.UTC)}, + Created: v1.Time{Time: time.Date(2020, 1, 23, 16, 53, 6, 686519038, time.UTC)}, DockerVersion: "18.06.1-ce", History: []v1.History{ { - Created: v1.Time{Time: time.Date(2020, 1, 23, 16, 53, 06, 551172402, time.UTC)}, + Created: v1.Time{Time: time.Date(2020, 1, 23, 16, 53, 6, 551172402, time.UTC)}, CreatedBy: "/bin/sh -c #(nop) ADD file:d48cac34fac385cbc1de6adfdd88300f76f9bbe346cd17e64fd834d042a98326 in / ", EmptyLayer: false, }, { - Created: v1.Time{Time: time.Date(2020, 1, 23, 16, 53, 06, 686519038, time.UTC)}, + Created: v1.Time{Time: time.Date(2020, 1, 23, 16, 53, 6, 686519038, time.UTC)}, CreatedBy: "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", Comment: "", EmptyLayer: true, diff --git a/pkg/fanal/image/registry/ecr/ecr_test.go b/pkg/fanal/image/registry/ecr/ecr_test.go index f780e6565516..ab824514e2b2 100644 --- a/pkg/fanal/image/registry/ecr/ecr_test.go +++ b/pkg/fanal/image/registry/ecr/ecr_test.go @@ -18,7 +18,7 @@ type testECRClient interface { } func TestCheckOptions(t *testing.T) { - var tests = map[string]struct { + tests := map[string]struct { domain string expectedRegion string wantErr error @@ -108,7 +108,7 @@ type mockedECR struct { Resp ecr.GetAuthorizationTokenOutput } -func (m mockedECR) GetAuthorizationToken(ctx context.Context, params *ecr.GetAuthorizationTokenInput, optFns ...func(*ecr.Options)) (*ecr.GetAuthorizationTokenOutput, error) { +func (m mockedECR) GetAuthorizationToken(_ context.Context, _ *ecr.GetAuthorizationTokenInput, _ ...func(*ecr.Options)) (*ecr.GetAuthorizationTokenOutput, error) { return &m.Resp, nil } diff --git a/pkg/fanal/secret/builtin-rules.go b/pkg/fanal/secret/builtin-rules.go index 2efd3e7658f5..f497dd840c80 100644 --- a/pkg/fanal/secret/builtin-rules.go +++ b/pkg/fanal/secret/builtin-rules.go @@ -90,7 +90,7 @@ func GetBuiltinRules() []Rule { // This function is exported for trivy-plugin-aqua purposes only func GetSecretRulesMetadata() []iacRules.Check { - return lo.Map(builtinRules, func(rule Rule, i int) iacRules.Check { + return lo.Map(builtinRules, func(rule Rule, _ int) iacRules.Check { return iacRules.Check{ Name: rule.ID, Description: rule.Title, @@ -185,7 +185,7 @@ var builtinRules = []Rule{ Category: CategoryAsymmetricPrivateKey, Title: "Asymmetric Private Key", Severity: "HIGH", - Regex: MustCompile(`(?i)-----\s*?BEGIN[ A-Z0-9_-]*?PRIVATE KEY( BLOCK)?\s*?-----[\s]*?(?P[A-Za-z0-9=+/\\\r\n][A-Za-z0-9=+/\\\s]+)[\s]*?-----\s*?END[ A-Z0-9_-]*? PRIVATE KEY( BLOCK)?\s*?-----`), + Regex: MustCompile(`(?i)-----\s*?BEGIN[ A-Z0-9_-]*?PRIVATE KEY( BLOCK)?\s*?-----[\s]*?(?P[A-Za-z0-9=+/\\\r\n][A-Za-z0-9=+/\\\s]{31,})[\s]*?-----\s*?END[ A-Z0-9_-]*? PRIVATE KEY( BLOCK)?\s*?-----`), SecretGroupName: "secret", Keywords: []string{"-----"}, }, diff --git a/pkg/fanal/secret/scanner.go b/pkg/fanal/secret/scanner.go index 01283ea42a46..b1c62dfea29e 100644 --- a/pkg/fanal/secret/scanner.go +++ b/pkg/fanal/secret/scanner.go @@ -175,7 +175,7 @@ func (r *Rule) MatchKeywords(content []byte) bool { if len(r.Keywords) == 0 { return true } - var contentLower = bytes.ToLower(content) + contentLower := bytes.ToLower(content) for _, kw := range r.Keywords { if bytes.Contains(contentLower, []byte(strings.ToLower(kw))) { return true @@ -499,7 +499,7 @@ func findLocation(start, end int, content []byte) (int, int, types.Code, string) if lineStart == -1 { lineStart = 0 } else { - lineStart += 1 + lineStart++ } lineEnd := bytes.Index(content[start:], lineSep) diff --git a/pkg/fanal/secret/scanner_test.go b/pkg/fanal/secret/scanner_test.go index 0df6403c13a7..4e828e55cddb 100644 --- a/pkg/fanal/secret/scanner_test.go +++ b/pkg/fanal/secret/scanner_test.go @@ -575,6 +575,43 @@ func TestSecretScanner(t *testing.T) { }, }, } + wantFindingMinimumAsymmSecretKey := types.SecretFinding{ + RuleID: "private-key", + Category: secret.CategoryAsymmetricPrivateKey, + Title: "Asymmetric Private Key", + Severity: "HIGH", + StartLine: 6, + EndLine: 6, + Match: "-----BEGIN PRIVATE KEY-----********************************-----END PRIVATE KEY-----", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 4, + Content: "", + Highlighted: "", + IsCause: false, + FirstCause: false, + LastCause: false, + }, + { + Number: 5, + Content: "## Theoretically Asymmetric Private Key of minimum length (RSA 128 bits)", + Highlighted: "## Theoretically Asymmetric Private Key of minimum length (RSA 128 bits)", + IsCause: false, + FirstCause: false, + LastCause: false, + }, + { + Number: 6, + Content: "-----BEGIN PRIVATE KEY-----********************************-----END PRIVATE KEY-----", + Highlighted: "-----BEGIN PRIVATE KEY-----********************************-----END PRIVATE KEY-----", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + }, + }, + } wantFindingAlibabaAccessKeyId := types.SecretFinding{ RuleID: "alibaba-access-key-id", Category: secret.CategoryAlibaba, @@ -1300,7 +1337,7 @@ func TestSecretScanner(t *testing.T) { inputFilePath: "testdata/asymmetric-private-key.txt", want: types.Secret{ FilePath: "testdata/asymmetric-private-key.txt", - Findings: []types.SecretFinding{wantFindingAsymmSecretKey}, + Findings: []types.SecretFinding{wantFindingMinimumAsymmSecretKey, wantFindingAsymmSecretKey}, }, }, { diff --git a/pkg/fanal/secret/testdata/asymmetric-private-key.txt b/pkg/fanal/secret/testdata/asymmetric-private-key.txt index 68e459e1a8c7..f29c4af917f5 100644 --- a/pkg/fanal/secret/testdata/asymmetric-private-key.txt +++ b/pkg/fanal/secret/testdata/asymmetric-private-key.txt @@ -3,4 +3,7 @@ MIIEowIBAAKCAQEAu/Nua0/1y08gkbnBfKd6VDHia8Na0ATgMQqZ4YEbi/t73g84IEPQPkLbPF3X De+ vKftqLPCPbAPPxkaqQi0Ico/1fzD10znRy66aosPBrbleduiynubgk+GVm9y/R6bDYhR -----END RSA PRIVATE KEY----- ------BEGIN RSA PRIVATE KEY----- -----END RSA PRIVATE KEY----- \ No newline at end of file +-----BEGIN RSA PRIVATE KEY----- -----END RSA PRIVATE KEY----- + +## Theoretically Asymmetric Private Key of minimum length (RSA 128 bits) +-----BEGIN PRIVATE KEY-----4c6f4a5cb2d395f828384c2ab29d1f55-----END PRIVATE KEY----- \ No newline at end of file diff --git a/pkg/fanal/test/integration/library_test.go b/pkg/fanal/test/integration/library_test.go index f655d4587143..1777fa91c0b4 100644 --- a/pkg/fanal/test/integration/library_test.go +++ b/pkg/fanal/test/integration/library_test.go @@ -160,19 +160,19 @@ func TestFanal_Library_DockerMode(t *testing.T) { require.NoError(t, err, tt.name) defer cleanup() - ar, err := aimage.NewArtifact(img, c, artifact.Option{ - // disable license checking in the test - in parallel it will fail because of resource requirement - DisabledAnalyzers: []analyzer.Type{ - analyzer.TypeExecutable, - analyzer.TypeLicenseFile, - }, - }) - require.NoError(t, err) - - applier := applier.NewApplier(c) - // run tests twice, one without cache and with cache for i := 1; i <= 2; i++ { + ar, err := aimage.NewArtifact(img, c, artifact.Option{ + // disable license checking in the test - in parallel it will fail because of resource requirement + DisabledAnalyzers: []analyzer.Type{ + analyzer.TypeExecutable, + analyzer.TypeLicenseFile, + }, + }) + require.NoError(t, err) + + applier := applier.NewApplier(c) + runChecks(t, ctx, ar, applier, tt) } diff --git a/pkg/fanal/types/artifact.go b/pkg/fanal/types/artifact.go index 24ff13ba0500..82cb97890c38 100644 --- a/pkg/fanal/types/artifact.go +++ b/pkg/fanal/types/artifact.go @@ -54,11 +54,11 @@ func (o *OS) Merge(newOS OS) { return } - switch { + switch o.Family { // OLE also has /etc/redhat-release and it detects OLE as RHEL by mistake. // In that case, OS must be overwritten with the content of /etc/oracle-release. // There is the same problem between Debian and Ubuntu. - case o.Family == RedHat, o.Family == Debian: + case RedHat, Debian: *o = newOS default: if o.Family == "" { @@ -87,11 +87,22 @@ type Repository struct { } type Layer struct { + Size int64 `json:",omitempty"` Digest string `json:",omitempty"` DiffID string `json:",omitempty"` CreatedBy string `json:",omitempty"` } +type Layers []Layer + +func (lm Layers) TotalSize() int64 { + var totalSize int64 + for _, layer := range lm { + totalSize += layer.Size + } + return totalSize +} + type PackageInfo struct { FilePath string Packages Packages @@ -157,7 +168,8 @@ type ArtifactInfo struct { type BlobInfo struct { SchemaVersion int - // Layer information + // Layer info + Size int64 `json:",omitempty"` Digest string `json:",omitempty"` DiffID string `json:",omitempty"` CreatedBy string `json:",omitempty"` @@ -183,6 +195,15 @@ type BlobInfo struct { CustomResources []CustomResource `json:",omitempty"` } +func (b BlobInfo) Layer() Layer { + return Layer{ + Size: b.Size, + Digest: b.Digest, + DiffID: b.DiffID, + CreatedBy: b.CreatedBy, + } +} + // ArtifactDetail represents the analysis result. type ArtifactDetail struct { OS OS `json:",omitempty"` @@ -199,6 +220,8 @@ type ArtifactDetail struct { // CustomResources hold analysis results from custom analyzers. // It is for extensibility and not used in OSS. CustomResources []CustomResource `json:",omitempty"` + + Layers Layers `json:",omitzero"` } // Sort sorts packages and applications in ArtifactDetail @@ -255,26 +278,6 @@ type ImageConfigDetail struct { Secret *Secret `json:",omitempty"` } -// ToBlobInfo is used to store a merged layer in cache. -func (a *ArtifactDetail) ToBlobInfo() BlobInfo { - return BlobInfo{ - SchemaVersion: BlobJSONSchemaVersion, - OS: a.OS, - Repository: a.Repository, - PackageInfos: []PackageInfo{ - { - FilePath: "merged", // Set a dummy file path - Packages: a.Packages, - }, - }, - Applications: a.Applications, - Misconfigurations: a.Misconfigurations, - Secrets: a.Secrets, - Licenses: a.Licenses, - CustomResources: a.CustomResources, - } -} - // CustomResource holds the analysis result from a custom analyzer. // It is for extensibility and not used in OSS. type CustomResource struct { diff --git a/pkg/fanal/types/const.go b/pkg/fanal/types/const.go index 4ffeb2fb5660..6196805b8d8a 100644 --- a/pkg/fanal/types/const.go +++ b/pkg/fanal/types/const.go @@ -25,6 +25,7 @@ const ( Alpine OSType = "alpine" Amazon OSType = "amazon" Azure OSType = "azurelinux" + Bottlerocket OSType = "bottlerocket" CBLMariner OSType = "cbl-mariner" CentOS OSType = "centos" Chainguard OSType = "chainguard" @@ -64,6 +65,7 @@ const ( Composer LangType = "composer" ComposerVendor LangType = "composer-vendor" Npm LangType = "npm" + Bun LangType = "bun" NuGet LangType = "nuget" DotNetCore LangType = "dotnet-core" PackagesProps LangType = "packages-props" diff --git a/pkg/fanal/utils/utils.go b/pkg/fanal/utils/utils.go index 5f96990cd703..9b7fd017393a 100644 --- a/pkg/fanal/utils/utils.go +++ b/pkg/fanal/utils/utils.go @@ -20,9 +20,7 @@ import ( xio "github.com/aquasecurity/trivy/pkg/x/io" ) -var ( - PathSeparator = fmt.Sprintf("%c", os.PathSeparator) -) +var PathSeparator = fmt.Sprintf("%c", os.PathSeparator) func CacheDir() string { cacheDir, err := os.UserCacheDir() @@ -59,7 +57,7 @@ func IsExecutable(fileInfo os.FileInfo) bool { } // Check unpackaged file - if mode.Perm()&0111 != 0 { + if mode.Perm()&0o111 != 0 { return true } return false @@ -86,7 +84,7 @@ func IsBinary(content xio.ReadSeekerAt, fileSize int64) (bool, error) { } func CleanSkipPaths(skipPaths []string) []string { - return lo.Map(skipPaths, func(skipPath string, index int) string { + return lo.Map(skipPaths, func(skipPath string, _ int) string { skipPath = filepath.ToSlash(filepath.Clean(skipPath)) return strings.TrimLeft(skipPath, "/") }) diff --git a/pkg/fanal/walker/fs_test.go b/pkg/fanal/walker/fs_test.go index 6eec99571073..714748cb4ac2 100644 --- a/pkg/fanal/walker/fs_test.go +++ b/pkg/fanal/walker/fs_test.go @@ -28,7 +28,7 @@ func TestFS_Walk(t *testing.T) { { name: "happy path", rootDir: "testdata/fs", - analyzeFn: func(filePath string, info os.FileInfo, opener analyzer.Opener) error { + analyzeFn: func(filePath string, _ os.FileInfo, opener analyzer.Opener) error { if filePath == "testdata/fs/bar" { got, err := opener() require.NoError(t, err) @@ -47,7 +47,7 @@ func TestFS_Walk(t *testing.T) { option: walker.Option{ SkipFiles: []string{"testdata/fs/bar"}, }, - analyzeFn: func(filePath string, info os.FileInfo, opener analyzer.Opener) error { + analyzeFn: func(filePath string, _ os.FileInfo, _ analyzer.Opener) error { if filePath == "testdata/fs/bar" { assert.Fail(t, "skip files error", "%s should be skipped", filePath) } @@ -60,7 +60,7 @@ func TestFS_Walk(t *testing.T) { option: walker.Option{ SkipDirs: []string{"/testdata/fs/app"}, }, - analyzeFn: func(filePath string, info os.FileInfo, opener analyzer.Opener) error { + analyzeFn: func(filePath string, _ os.FileInfo, _ analyzer.Opener) error { if strings.HasPrefix(filePath, "testdata/fs/app") { assert.Fail(t, "skip dirs error", "%s should be skipped", filePath) } diff --git a/pkg/fanal/walker/tar_test.go b/pkg/fanal/walker/tar_test.go index f00ca1e7e3bc..245d3b36170e 100644 --- a/pkg/fanal/walker/tar_test.go +++ b/pkg/fanal/walker/tar_test.go @@ -27,7 +27,7 @@ func TestLayerTar_Walk(t *testing.T) { { name: "happy path", inputFile: filepath.Join("testdata", "test.tar"), - analyzeFn: func(filePath string, info os.FileInfo, opener analyzer.Opener) error { + analyzeFn: func(_ string, _ os.FileInfo, _ analyzer.Opener) error { return nil }, wantOpqDirs: []string{"etc/"}, @@ -39,7 +39,7 @@ func TestLayerTar_Walk(t *testing.T) { option: walker.Option{ SkipFiles: []string{"/app/myweb/index.html"}, }, - analyzeFn: func(filePath string, info os.FileInfo, opener analyzer.Opener) error { + analyzeFn: func(filePath string, _ os.FileInfo, _ analyzer.Opener) error { if filePath == "app/myweb/index.html" { assert.Fail(t, "skip files error", "%s should be skipped", filePath) } @@ -54,7 +54,7 @@ func TestLayerTar_Walk(t *testing.T) { option: walker.Option{ SkipDirs: []string{"/app"}, }, - analyzeFn: func(filePath string, info os.FileInfo, opener analyzer.Opener) error { + analyzeFn: func(filePath string, _ os.FileInfo, _ analyzer.Opener) error { if strings.HasPrefix(filePath, "app") { assert.Fail(t, "skip dirs error", "%s should be skipped", filePath) } @@ -66,7 +66,7 @@ func TestLayerTar_Walk(t *testing.T) { { name: "sad path", inputFile: "testdata/test.tar", - analyzeFn: func(filePath string, info os.FileInfo, opener analyzer.Opener) error { + analyzeFn: func(_ string, _ os.FileInfo, _ analyzer.Opener) error { return errors.New("error") }, wantErr: "failed to analyze file", diff --git a/pkg/flag/aws_flags.go b/pkg/flag/aws_flags.go index 6801d8b1ae5a..77c04d1fc7eb 100644 --- a/pkg/flag/aws_flags.go +++ b/pkg/flag/aws_flags.go @@ -77,16 +77,14 @@ func (f *AWSFlagGroup) Flags() []Flagger { } } -func (f *AWSFlagGroup) ToOptions() (AWSOptions, error) { - if err := parseFlags(f); err != nil { - return AWSOptions{}, err - } - return AWSOptions{ +func (f *AWSFlagGroup) ToOptions(opts *Options) error { + opts.AWSOptions = AWSOptions{ Region: f.Region.Value(), Endpoint: f.Endpoint.Value(), Services: f.Services.Value(), SkipServices: f.SkipServices.Value(), Account: f.Account.Value(), ARN: f.ARN.Value(), - }, nil + } + return nil } diff --git a/pkg/flag/cache_flags.go b/pkg/flag/cache_flags.go index 9cf8403a1e56..0ec7053189f8 100644 --- a/pkg/flag/cache_flags.go +++ b/pkg/flag/cache_flags.go @@ -106,17 +106,15 @@ func (fg *CacheFlagGroup) Flags() []Flagger { } } -func (fg *CacheFlagGroup) ToOptions() (CacheOptions, error) { - if err := parseFlags(fg); err != nil { - return CacheOptions{}, err - } - - return CacheOptions{ +func (fg *CacheFlagGroup) ToOptions(opts *Options) error { + opts.CacheOptions = CacheOptions{ + ClearCache: fg.ClearCache.Value(), CacheBackend: fg.CacheBackend.Value(), CacheTTL: fg.CacheTTL.Value(), RedisTLS: fg.RedisTLS.Value(), RedisCACert: fg.RedisCACert.Value(), RedisCert: fg.RedisCert.Value(), RedisKey: fg.RedisKey.Value(), - }, nil + } + return nil } diff --git a/pkg/flag/clean_flags.go b/pkg/flag/clean_flags.go index 11e0e5934e90..40b9c99f24b4 100644 --- a/pkg/flag/clean_flags.go +++ b/pkg/flag/clean_flags.go @@ -78,17 +78,14 @@ func (fg *CleanFlagGroup) Flags() []Flagger { } } -func (fg *CleanFlagGroup) ToOptions() (CleanOptions, error) { - if err := parseFlags(fg); err != nil { - return CleanOptions{}, err - } - - return CleanOptions{ +func (fg *CleanFlagGroup) ToOptions(opts *Options) error { + opts.CleanOptions = CleanOptions{ CleanAll: fg.CleanAll.Value(), CleanVulnerabilityDB: fg.CleanVulnerabilityDB.Value(), CleanJavaDB: fg.CleanJavaDB.Value(), CleanChecksBundle: fg.CleanChecksBundle.Value(), CleanScanCache: fg.CleanScanCache.Value(), CleanVEXRepositories: fg.CleanVEXRepositories.Value(), - }, nil + } + return nil } diff --git a/pkg/flag/db_flags.go b/pkg/flag/db_flags.go index c6887c98292e..3b00f8a5f994 100644 --- a/pkg/flag/db_flags.go +++ b/pkg/flag/db_flags.go @@ -127,31 +127,27 @@ func (f *DBFlagGroup) Flags() []Flagger { } } -func (f *DBFlagGroup) ToOptions() (DBOptions, error) { - if err := parseFlags(f); err != nil { - return DBOptions{}, err - } - +func (f *DBFlagGroup) ToOptions(opts *Options) error { skipDBUpdate := f.SkipDBUpdate.Value() skipJavaDBUpdate := f.SkipJavaDBUpdate.Value() downloadDBOnly := f.DownloadDBOnly.Value() downloadJavaDBOnly := f.DownloadJavaDBOnly.Value() if downloadDBOnly && downloadJavaDBOnly { - return DBOptions{}, xerrors.New("--download-db-only and --download-java-db-only options can not be specified both") + return xerrors.New("--download-db-only and --download-java-db-only options can not be specified both") } if downloadDBOnly && skipDBUpdate { - return DBOptions{}, xerrors.New("--skip-db-update and --download-db-only options can not be specified both") + return xerrors.New("--skip-db-update and --download-db-only options can not be specified both") } if downloadJavaDBOnly && skipJavaDBUpdate { - return DBOptions{}, xerrors.New("--skip-java-db-update and --download-java-db-only options can not be specified both") + return xerrors.New("--skip-java-db-update and --download-java-db-only options can not be specified both") } var dbRepositories, javaDBRepositories []name.Reference for _, repo := range f.DBRepositories.Value() { ref, err := parseRepository(repo, db.SchemaVersion) if err != nil { - return DBOptions{}, xerrors.Errorf("invalid DB repository: %w", err) + return xerrors.Errorf("invalid DB repository: %w", err) } dbRepositories = append(dbRepositories, ref) } @@ -159,12 +155,12 @@ func (f *DBFlagGroup) ToOptions() (DBOptions, error) { for _, repo := range f.JavaDBRepositories.Value() { ref, err := parseRepository(repo, javadb.SchemaVersion) if err != nil { - return DBOptions{}, xerrors.Errorf("invalid javadb repository: %w", err) + return xerrors.Errorf("invalid javadb repository: %w", err) } javaDBRepositories = append(javaDBRepositories, ref) } - return DBOptions{ + opts.DBOptions = DBOptions{ Reset: f.Reset.Value(), DownloadDBOnly: downloadDBOnly, SkipDBUpdate: skipDBUpdate, @@ -173,7 +169,8 @@ func (f *DBFlagGroup) ToOptions() (DBOptions, error) { NoProgress: f.NoProgress.Value(), DBRepositories: dbRepositories, JavaDBRepositories: javaDBRepositories, - }, nil + } + return nil } func parseRepository(repo string, dbSchemaVersion int) (name.Reference, error) { diff --git a/pkg/flag/db_flags_test.go b/pkg/flag/db_flags_test.go index 33c0b6fdee04..2b8fae9c2193 100644 --- a/pkg/flag/db_flags_test.go +++ b/pkg/flag/db_flags_test.go @@ -101,15 +101,15 @@ func TestDBFlagGroup_ToOptions(t *testing.T) { DBRepositories: flag.DBRepositoryFlag.Clone(), JavaDBRepositories: flag.JavaDBRepositoryFlag.Clone(), } - got, err := f.ToOptions() + flags := flag.Flags{f} + got, err := flags.ToOptions(nil) if tt.wantErr != "" { - require.Error(t, err) assert.ErrorContains(t, err, tt.wantErr) return } require.NoError(t, err) - assert.EqualExportedValues(t, tt.want, got) + assert.EqualExportedValues(t, tt.want, got.DBOptions) // Assert log messages assert.Equal(t, tt.wantLogs, out.Messages(), tt.name) diff --git a/pkg/flag/global_flags.go b/pkg/flag/global_flags.go index 2d20611b8b72..eb846406cd82 100644 --- a/pkg/flag/global_flags.go +++ b/pkg/flag/global_flags.go @@ -137,17 +137,13 @@ func (f *GlobalFlagGroup) Bind(cmd *cobra.Command) error { return nil } -func (f *GlobalFlagGroup) ToOptions() (GlobalOptions, error) { - if err := parseFlags(f); err != nil { - return GlobalOptions{}, err - } - +func (f *GlobalFlagGroup) ToOptions(opts *Options) error { // Keep TRIVY_NON_SSL for backward compatibility insecure := f.Insecure.Value() || os.Getenv("TRIVY_NON_SSL") != "" log.Debug("Cache dir", log.String("dir", f.CacheDir.Value())) - return GlobalOptions{ + opts.GlobalOptions = GlobalOptions{ ConfigFile: f.ConfigFile.Value(), ShowVersion: f.ShowVersion.Value(), Quiet: f.Quiet.Value(), @@ -156,5 +152,6 @@ func (f *GlobalFlagGroup) ToOptions() (GlobalOptions, error) { Timeout: f.Timeout.Value(), CacheDir: f.CacheDir.Value(), GenerateDefaultConfig: f.GenerateDefaultConfig.Value(), - }, nil + } + return nil } diff --git a/pkg/flag/image_flags.go b/pkg/flag/image_flags.go index 587448f8d0d9..93ec050d04b6 100644 --- a/pkg/flag/image_flags.go +++ b/pkg/flag/image_flags.go @@ -119,16 +119,12 @@ func (f *ImageFlagGroup) Flags() []Flagger { } } -func (f *ImageFlagGroup) ToOptions() (ImageOptions, error) { - if err := parseFlags(f); err != nil { - return ImageOptions{}, err - } - +func (f *ImageFlagGroup) ToOptions(opts *Options) error { var platform ftypes.Platform if p := f.Platform.Value(); p != "" { pl, err := v1.ParsePlatform(p) if err != nil { - return ImageOptions{}, xerrors.Errorf("unable to parse platform: %w", err) + return xerrors.Errorf("unable to parse platform: %w", err) } if pl.OS == "*" { pl.OS = "" // Empty OS means any OS @@ -139,12 +135,12 @@ func (f *ImageFlagGroup) ToOptions() (ImageOptions, error) { if value := f.MaxImageSize.Value(); value != "" { parsedSize, err := units.FromHumanSize(value) if err != nil { - return ImageOptions{}, xerrors.Errorf("invalid max image size %q: %w", value, err) + return xerrors.Errorf("invalid max image size %q: %w", value, err) } maxSize = parsedSize } - return ImageOptions{ + opts.ImageOptions = ImageOptions{ Input: f.Input.Value(), ImageConfigScanners: xstrings.ToTSlice[types.Scanner](f.ImageConfigScanners.Value()), ScanRemovedPkgs: f.ScanRemovedPkgs.Value(), @@ -153,5 +149,6 @@ func (f *ImageFlagGroup) ToOptions() (ImageOptions, error) { PodmanHost: f.PodmanHost.Value(), ImageSources: xstrings.ToTSlice[ftypes.ImageSource](f.ImageSources.Value()), MaxImageSize: maxSize, - }, nil + } + return nil } diff --git a/pkg/flag/image_flags_test.go b/pkg/flag/image_flags_test.go index 97105f6b8449..d2bbe03ea575 100644 --- a/pkg/flag/image_flags_test.go +++ b/pkg/flag/image_flags_test.go @@ -79,13 +79,14 @@ func TestImageFlagGroup_ToOptions(t *testing.T) { Platform: flag.PlatformFlag.Clone(), } - got, err := f.ToOptions() + flags := flag.Flags{f} + got, err := flags.ToOptions(nil) if tt.wantErr != "" { assert.ErrorContains(t, err, tt.wantErr) return } require.NoError(t, err) - assert.EqualExportedValues(t, tt.want, got) + assert.EqualExportedValues(t, tt.want, got.ImageOptions) }) } } diff --git a/pkg/flag/kubernetes_flags.go b/pkg/flag/kubernetes_flags.go index b8ec034d7169..7ef328b9090b 100644 --- a/pkg/flag/kubernetes_flags.go +++ b/pkg/flag/kubernetes_flags.go @@ -2,11 +2,11 @@ package flag import ( "errors" - "fmt" "strconv" "strings" "github.com/samber/lo" + "golang.org/x/xerrors" corev1 "k8s.io/api/core/v1" ) @@ -173,14 +173,10 @@ func (f *K8sFlagGroup) Flags() []Flagger { } } -func (f *K8sFlagGroup) ToOptions() (K8sOptions, error) { - if err := parseFlags(f); err != nil { - return K8sOptions{}, err - } - +func (f *K8sFlagGroup) ToOptions(opts *Options) error { tolerations, err := optionToTolerations(f.Tolerations.Value()) if err != nil { - return K8sOptions{}, err + return err } exludeNodeLabels := make(map[string]string) @@ -188,18 +184,18 @@ func (f *K8sFlagGroup) ToOptions() (K8sOptions, error) { for _, exludeNodeValue := range exludeNodes { excludeNodeParts := strings.Split(exludeNodeValue, ":") if len(excludeNodeParts) != 2 { - return K8sOptions{}, fmt.Errorf("exclude node %s must be a key:value", exludeNodeValue) + return xerrors.Errorf("exclude node %s must be a key:value", exludeNodeValue) } exludeNodeLabels[excludeNodeParts[0]] = excludeNodeParts[1] } if len(f.ExcludeNamespaces.Value()) > 0 && len(f.IncludeNamespaces.Value()) > 0 { - return K8sOptions{}, errors.New("include-namespaces and exclude-namespaces flags cannot be used together") + return xerrors.New("include-namespaces and exclude-namespaces flags cannot be used together") } if len(f.ExcludeKinds.Value()) > 0 && len(f.IncludeKinds.Value()) > 0 { - return K8sOptions{}, errors.New("include-kinds and exclude-kinds flags cannot be used together") + return xerrors.New("include-kinds and exclude-kinds flags cannot be used together") } - return K8sOptions{ + opts.K8sOptions = K8sOptions{ KubeConfig: f.KubeConfig.Value(), K8sVersion: f.K8sVersion.Value(), Tolerations: tolerations, @@ -215,7 +211,8 @@ func (f *K8sFlagGroup) ToOptions() (K8sOptions, error) { ExcludeNamespaces: f.ExcludeNamespaces.Value(), IncludeNamespaces: f.IncludeNamespaces.Value(), Burst: f.Burst.Value(), - }, nil + } + return nil } func optionToTolerations(tolerationsOptions []string) ([]corev1.Toleration, error) { diff --git a/pkg/flag/license_flags.go b/pkg/flag/license_flags.go index 5ff4c912e2a9..1db10b68ab86 100644 --- a/pkg/flag/license_flags.go +++ b/pkg/flag/license_flags.go @@ -115,11 +115,7 @@ func (f *LicenseFlagGroup) Flags() []Flagger { } } -func (f *LicenseFlagGroup) ToOptions() (LicenseOptions, error) { - if err := parseFlags(f); err != nil { - return LicenseOptions{}, err - } - +func (f *LicenseFlagGroup) ToOptions(opts *Options) error { licenseCategories := make(map[types.LicenseCategory][]string) licenseCategories[types.CategoryForbidden] = f.LicenseForbidden.Value() licenseCategories[types.CategoryRestricted] = f.LicenseRestricted.Value() @@ -128,10 +124,11 @@ func (f *LicenseFlagGroup) ToOptions() (LicenseOptions, error) { licenseCategories[types.CategoryPermissive] = f.LicensePermissive.Value() licenseCategories[types.CategoryUnencumbered] = f.LicenseUnencumbered.Value() - return LicenseOptions{ + opts.LicenseOptions = LicenseOptions{ LicenseFull: f.LicenseFull.Value(), IgnoredLicenses: f.IgnoredLicenses.Value(), LicenseConfidenceLevel: f.LicenseConfidenceLevel.Value(), LicenseCategories: licenseCategories, - }, nil + } + return nil } diff --git a/pkg/flag/misconf_flags.go b/pkg/flag/misconf_flags.go index 361e8a3edb8b..09a9b1566c68 100644 --- a/pkg/flag/misconf_flags.go +++ b/pkg/flag/misconf_flags.go @@ -116,6 +116,13 @@ var ( Values: xstrings.ToStringSlice([]types.ConfigType{types.Terraform}), // TODO: add Plan and JSON? Default: []string{}, } + RawConfigScanners = Flag[[]string]{ + Name: "raw-config-scanners", + ConfigName: "misconfiguration.raw-config-scanners", + Usage: "specify the types of scanners that will also scan raw configurations. For example, scanners will scan a non-adapted configuration into a shared state", + Values: xstrings.ToStringSlice([]types.ConfigType{types.Terraform}), + Default: []string{}, + } ) // MisconfFlagGroup composes common printer flag structs used for commands providing misconfiguration scanning. @@ -137,6 +144,7 @@ type MisconfFlagGroup struct { MisconfigScanners *Flag[[]string] ConfigFileSchemas *Flag[[]string] RenderCause *Flag[[]string] + RawConfigScanners *Flag[[]string] } type MisconfOptions struct { @@ -157,6 +165,7 @@ type MisconfOptions struct { MisconfigScanners []analyzer.Type ConfigFileSchemas []string RenderCause []types.ConfigType + RawConfigScanners []types.ConfigType } func NewMisconfFlagGroup() *MisconfFlagGroup { @@ -177,6 +186,7 @@ func NewMisconfFlagGroup() *MisconfFlagGroup { MisconfigScanners: MisconfigScannersFlag.Clone(), ConfigFileSchemas: ConfigFileSchemasFlag.Clone(), RenderCause: RenderCauseFlag.Clone(), + RawConfigScanners: RawConfigScanners.Clone(), } } @@ -201,15 +211,12 @@ func (f *MisconfFlagGroup) Flags() []Flagger { f.MisconfigScanners, f.ConfigFileSchemas, f.RenderCause, + f.RawConfigScanners, } } -func (f *MisconfFlagGroup) ToOptions() (MisconfOptions, error) { - if err := parseFlags(f); err != nil { - return MisconfOptions{}, err - } - - return MisconfOptions{ +func (f *MisconfFlagGroup) ToOptions(opts *Options) error { + opts.MisconfOptions = MisconfOptions{ IncludeNonFailures: f.IncludeNonFailures.Value(), ResetChecksBundle: f.ResetChecksBundle.Value(), ChecksBundleRepository: f.ChecksBundleRepository.Value(), @@ -225,5 +232,7 @@ func (f *MisconfFlagGroup) ToOptions() (MisconfOptions, error) { MisconfigScanners: xstrings.ToTSlice[analyzer.Type](f.MisconfigScanners.Value()), ConfigFileSchemas: f.ConfigFileSchemas.Value(), RenderCause: xstrings.ToTSlice[types.ConfigType](f.RenderCause.Value()), - }, nil + RawConfigScanners: xstrings.ToTSlice[types.ConfigType](f.RawConfigScanners.Value()), + } + return nil } diff --git a/pkg/flag/module_flags.go b/pkg/flag/module_flags.go index d0f8bb8f4def..d0a5fd882430 100644 --- a/pkg/flag/module_flags.go +++ b/pkg/flag/module_flags.go @@ -58,13 +58,10 @@ func (f *ModuleFlagGroup) Flags() []Flagger { } } -func (f *ModuleFlagGroup) ToOptions() (ModuleOptions, error) { - if err := parseFlags(f); err != nil { - return ModuleOptions{}, err - } - - return ModuleOptions{ +func (f *ModuleFlagGroup) ToOptions(opts *Options) error { + opts.ModuleOptions = ModuleOptions{ ModuleDir: f.Dir.Value(), EnabledModules: f.EnabledModules.Value(), - }, nil + } + return nil } diff --git a/pkg/flag/options.go b/pkg/flag/options.go index 6ea9cc82d2a5..f0f8276a5654 100644 --- a/pkg/flag/options.go +++ b/pkg/flag/options.go @@ -312,11 +312,7 @@ func (f *Flag[T]) Bind(cmd *cobra.Command) error { } // Bind environmental variable - if err := f.BindEnv(); err != nil { - return err - } - - return nil + return f.BindEnv() } func (f *Flag[T]) BindEnv() error { @@ -344,6 +340,7 @@ func (f *Flag[T]) BindEnv() error { type FlagGroup interface { Name() string Flags() []Flagger + ToOptions(*Options) error } type Flagger interface { @@ -358,27 +355,7 @@ type Flagger interface { Bind(cmd *cobra.Command) error } -type Flags struct { - GlobalFlagGroup *GlobalFlagGroup - AWSFlagGroup *AWSFlagGroup - CacheFlagGroup *CacheFlagGroup - CleanFlagGroup *CleanFlagGroup - DBFlagGroup *DBFlagGroup - ImageFlagGroup *ImageFlagGroup - K8sFlagGroup *K8sFlagGroup - LicenseFlagGroup *LicenseFlagGroup - MisconfFlagGroup *MisconfFlagGroup - ModuleFlagGroup *ModuleFlagGroup - PackageFlagGroup *PackageFlagGroup - RemoteFlagGroup *RemoteFlagGroup - RegistryFlagGroup *RegistryFlagGroup - RegoFlagGroup *RegoFlagGroup - RepoFlagGroup *RepoFlagGroup - ReportFlagGroup *ReportFlagGroup - ScanFlagGroup *ScanFlagGroup - SecretFlagGroup *SecretFlagGroup - VulnerabilityFlagGroup *VulnerabilityFlagGroup -} +type Flags []FlagGroup // Options holds all the runtime configuration type Options struct { @@ -411,15 +388,19 @@ type Options struct { // outputWriter is not initialized via the CLI. // It is mainly used for testing purposes or by tools that use Trivy as a library. outputWriter io.Writer + + // args is the arguments passed to the command. + args []string } // Align takes consistency of options func (o *Options) Align(f *Flags) error { - if f.ScanFlagGroup != nil && f.ScanFlagGroup.Scanners != nil { + if scanFlagGroup, ok := findFlagGroup[*ScanFlagGroup](f); ok && scanFlagGroup.Scanners != nil { o.enableSBOM() } - if f.PackageFlagGroup != nil && f.PackageFlagGroup.PkgRelationships != nil && + if packageFlagGroup, ok := findFlagGroup[*PackageFlagGroup](f); ok && + packageFlagGroup.PkgRelationships != nil && slices.Compare(o.PkgRelationships, ftypes.Relationships) != 0 && (o.DependencyTree || slices.Contains(types.SupportedSBOMFormats, o.Format) || len(o.VEXSources) != 0) { return xerrors.Errorf("'--pkg-relationships' cannot be used with '--dependency-tree', '--vex' or SBOM formats") @@ -601,63 +582,9 @@ func (o *Options) outputPluginWriter(ctx context.Context) (io.Writer, func() err // groups returns all the flag groups other than global flags func (f *Flags) groups() []FlagGroup { - var groups []FlagGroup - // This order affects the usage message, so they are sorted by frequency of use. - if f.ScanFlagGroup != nil { - groups = append(groups, f.ScanFlagGroup) - } - if f.ReportFlagGroup != nil { - groups = append(groups, f.ReportFlagGroup) - } - if f.CacheFlagGroup != nil { - groups = append(groups, f.CacheFlagGroup) - } - if f.CleanFlagGroup != nil { - groups = append(groups, f.CleanFlagGroup) - } - if f.DBFlagGroup != nil { - groups = append(groups, f.DBFlagGroup) - } - if f.RegistryFlagGroup != nil { - groups = append(groups, f.RegistryFlagGroup) - } - if f.ImageFlagGroup != nil { - groups = append(groups, f.ImageFlagGroup) - } - if f.VulnerabilityFlagGroup != nil { - groups = append(groups, f.VulnerabilityFlagGroup) - } - if f.MisconfFlagGroup != nil { - groups = append(groups, f.MisconfFlagGroup) - } - if f.ModuleFlagGroup != nil { - groups = append(groups, f.ModuleFlagGroup) - } - if f.SecretFlagGroup != nil { - groups = append(groups, f.SecretFlagGroup) - } - if f.LicenseFlagGroup != nil { - groups = append(groups, f.LicenseFlagGroup) - } - if f.RegoFlagGroup != nil { - groups = append(groups, f.RegoFlagGroup) - } - if f.AWSFlagGroup != nil { - groups = append(groups, f.AWSFlagGroup) - } - if f.K8sFlagGroup != nil { - groups = append(groups, f.K8sFlagGroup) - } - if f.PackageFlagGroup != nil { - groups = append(groups, f.PackageFlagGroup) - } - if f.RemoteFlagGroup != nil { - groups = append(groups, f.RemoteFlagGroup) - } - if f.RepoFlagGroup != nil { - groups = append(groups, f.RepoFlagGroup) - } - return groups + return lo.Filter(*f, func(group FlagGroup, _ int) bool { + return group != nil && group.Name() != "Global" + }) } func (f *Flags) AddFlags(cmd *cobra.Command) { @@ -715,141 +642,18 @@ func (f *Flags) Bind(cmd *cobra.Command) error { // nolint: gocyclo func (f *Flags) ToOptions(args []string) (Options, error) { - var err error opts := Options{ AppVersion: app.Version(), + args: args, } - if f.GlobalFlagGroup != nil { - opts.GlobalOptions, err = f.GlobalFlagGroup.ToOptions() - if err != nil { - return Options{}, xerrors.Errorf("global flag error: %w", err) - } - } - - if f.AWSFlagGroup != nil { - opts.AWSOptions, err = f.AWSFlagGroup.ToOptions() - if err != nil { - return Options{}, xerrors.Errorf("aws flag error: %w", err) + for _, group := range *f { // Include global flags + if err := parseFlags(group); err != nil { + return Options{}, xerrors.Errorf("unable to parse flags: %w", err) } - } - if f.CacheFlagGroup != nil { - opts.CacheOptions, err = f.CacheFlagGroup.ToOptions() - if err != nil { - return Options{}, xerrors.Errorf("cache flag error: %w", err) - } - } - - if f.CleanFlagGroup != nil { - opts.CleanOptions, err = f.CleanFlagGroup.ToOptions() - if err != nil { - return Options{}, xerrors.Errorf("clean flag error: %w", err) - } - } - - if f.DBFlagGroup != nil { - opts.DBOptions, err = f.DBFlagGroup.ToOptions() - if err != nil { - return Options{}, xerrors.Errorf("db flag error: %w", err) - } - } - - if f.ImageFlagGroup != nil { - opts.ImageOptions, err = f.ImageFlagGroup.ToOptions() - if err != nil { - return Options{}, xerrors.Errorf("image flag error: %w", err) - } - } - - if f.K8sFlagGroup != nil { - opts.K8sOptions, err = f.K8sFlagGroup.ToOptions() - if err != nil { - return Options{}, xerrors.Errorf("k8s flag error: %w", err) - } - } - - if f.LicenseFlagGroup != nil { - opts.LicenseOptions, err = f.LicenseFlagGroup.ToOptions() - if err != nil { - return Options{}, xerrors.Errorf("license flag error: %w", err) - } - } - - if f.MisconfFlagGroup != nil { - opts.MisconfOptions, err = f.MisconfFlagGroup.ToOptions() - if err != nil { - return Options{}, xerrors.Errorf("misconfiguration flag error: %w", err) - } - } - - if f.ModuleFlagGroup != nil { - opts.ModuleOptions, err = f.ModuleFlagGroup.ToOptions() - if err != nil { - return Options{}, xerrors.Errorf("module flag error: %w", err) - } - } - - if f.PackageFlagGroup != nil { - opts.PackageOptions, err = f.PackageFlagGroup.ToOptions() - if err != nil { - return Options{}, xerrors.Errorf("package flag error: %w", err) - } - } - - if f.RegoFlagGroup != nil { - opts.RegoOptions, err = f.RegoFlagGroup.ToOptions() - if err != nil { - return Options{}, xerrors.Errorf("rego flag error: %w", err) - } - } - - if f.RemoteFlagGroup != nil { - opts.RemoteOptions, err = f.RemoteFlagGroup.ToOptions() - if err != nil { - return Options{}, xerrors.Errorf("remote flag error: %w", err) - } - } - - if f.RegistryFlagGroup != nil { - opts.RegistryOptions, err = f.RegistryFlagGroup.ToOptions() - if err != nil { - return Options{}, xerrors.Errorf("registry flag error: %w", err) - } - } - - if f.RepoFlagGroup != nil { - opts.RepoOptions, err = f.RepoFlagGroup.ToOptions() - if err != nil { - return Options{}, xerrors.Errorf("repo flag error: %w", err) - } - } - - if f.ReportFlagGroup != nil { - opts.ReportOptions, err = f.ReportFlagGroup.ToOptions() - if err != nil { - return Options{}, xerrors.Errorf("report flag error: %w", err) - } - } - - if f.ScanFlagGroup != nil { - opts.ScanOptions, err = f.ScanFlagGroup.ToOptions(args) - if err != nil { - return Options{}, xerrors.Errorf("scan flag error: %w", err) - } - } - - if f.SecretFlagGroup != nil { - opts.SecretOptions, err = f.SecretFlagGroup.ToOptions() - if err != nil { - return Options{}, xerrors.Errorf("secret flag error: %w", err) - } - } - - if f.VulnerabilityFlagGroup != nil { - opts.VulnerabilityOptions, err = f.VulnerabilityFlagGroup.ToOptions() - if err != nil { - return Options{}, xerrors.Errorf("vulnerability flag error: %w", err) + if err := group.ToOptions(&opts); err != nil { + return Options{}, xerrors.Errorf("unable to convert flags to options: %w", err) } } @@ -935,3 +739,16 @@ func HiddenFlags() []string { } return hiddenFlags } + +// findFlagGroup finds a flag group by type T +// Note that Go generics doesn't support methods today. +// cf. https://github.com/golang/go/issues/49085 +func findFlagGroup[T FlagGroup](f *Flags) (T, bool) { + for _, group := range *f { + if g, ok := group.(T); ok { + return g, true + } + } + var zero T + return zero, false +} diff --git a/pkg/flag/package_flags.go b/pkg/flag/package_flags.go index 17abcbfe81c8..8de4d265f0b7 100644 --- a/pkg/flag/package_flags.go +++ b/pkg/flag/package_flags.go @@ -10,7 +10,7 @@ var ( IncludeDevDepsFlag = Flag[bool]{ Name: "include-dev-deps", ConfigName: "pkg.include-dev-deps", - Usage: "include development dependencies in the report (supported: npm, yarn)", + Usage: "include development dependencies in the report (supported: npm, yarn, gradle)", } PkgTypesFlag = Flag[[]string]{ Name: "pkg-types", @@ -69,23 +69,20 @@ func (f *PackageFlagGroup) Flags() []Flagger { } } -func (f *PackageFlagGroup) ToOptions() (PackageOptions, error) { - if err := parseFlags(f); err != nil { - return PackageOptions{}, err - } - +func (f *PackageFlagGroup) ToOptions(opts *Options) error { var relationships []ftypes.Relationship for _, r := range f.PkgRelationships.Value() { relationship, err := ftypes.NewRelationship(r) if err != nil { - return PackageOptions{}, err + return err } relationships = append(relationships, relationship) } - return PackageOptions{ + opts.PackageOptions = PackageOptions{ IncludeDevDeps: f.IncludeDevDeps.Value(), PkgTypes: f.PkgTypes.Value(), PkgRelationships: relationships, - }, nil + } + return nil } diff --git a/pkg/flag/package_flags_test.go b/pkg/flag/package_flags_test.go index b79a149e3891..8cab1d1f765b 100644 --- a/pkg/flag/package_flags_test.go +++ b/pkg/flag/package_flags_test.go @@ -76,9 +76,10 @@ func TestPackageFlagGroup_ToOptions(t *testing.T) { PkgRelationships: flag.PkgRelationshipsFlag.Clone(), } - got, err := f.ToOptions() + flags := flag.Flags{f} + got, err := flags.ToOptions(nil) require.NoError(t, err) - assert.EqualExportedValuesf(t, tt.want, got, "PackageFlagGroup") + assert.EqualExportedValues(t, tt.want, got.PackageOptions) }) } } diff --git a/pkg/flag/registry_flags.go b/pkg/flag/registry_flags.go index 50de707ea602..88cc2a6985d1 100644 --- a/pkg/flag/registry_flags.go +++ b/pkg/flag/registry_flags.go @@ -75,27 +75,23 @@ func (f *RegistryFlagGroup) Flags() []Flagger { } } -func (f *RegistryFlagGroup) ToOptions() (RegistryOptions, error) { - if err := parseFlags(f); err != nil { - return RegistryOptions{}, err - } - +func (f *RegistryFlagGroup) ToOptions(opts *Options) error { var credentials []types.Credential users := f.Username.Value() passwords := f.Password.Value() if f.PasswordStdin.Value() { if len(passwords) != 0 { - return RegistryOptions{}, xerrors.New("'--password' and '--password-stdin' can't be used at the same time") + return xerrors.New("'--password' and '--password-stdin' can't be used at the same time") } contents, err := io.ReadAll(os.Stdin) if err != nil { - return RegistryOptions{}, xerrors.Errorf("failed to read from stdin: %w", err) + return xerrors.Errorf("failed to read from stdin: %w", err) } // "--password-stdin" doesn't support comma-separated passwords passwords = []string{strings.TrimRight(string(contents), "\r\n")} } if len(users) != len(passwords) { - return RegistryOptions{}, xerrors.New("the number of usernames and passwords must match") + return xerrors.New("the number of usernames and passwords must match") } for i, user := range users { credentials = append(credentials, types.Credential{ @@ -104,9 +100,10 @@ func (f *RegistryFlagGroup) ToOptions() (RegistryOptions, error) { }) } - return RegistryOptions{ + opts.RegistryOptions = RegistryOptions{ Credentials: credentials, RegistryToken: f.RegistryToken.Value(), RegistryMirrors: f.RegistryMirrors.Value(), - }, nil + } + return nil } diff --git a/pkg/flag/rego_flags.go b/pkg/flag/rego_flags.go index 84e0aed910f4..8d75128a555f 100644 --- a/pkg/flag/rego_flags.go +++ b/pkg/flag/rego_flags.go @@ -102,17 +102,14 @@ func (f *RegoFlagGroup) Flags() []Flagger { } } -func (f *RegoFlagGroup) ToOptions() (RegoOptions, error) { - if err := parseFlags(f); err != nil { - return RegoOptions{}, err - } - - return RegoOptions{ +func (f *RegoFlagGroup) ToOptions(opts *Options) error { + opts.RegoOptions = RegoOptions{ IncludeDeprecatedChecks: f.IncludeDeprecatedChecks.Value(), SkipCheckUpdate: f.SkipCheckUpdate.Value(), Trace: f.Trace.Value(), CheckPaths: f.CheckPaths.Value(), DataPaths: f.DataPaths.Value(), CheckNamespaces: f.CheckNamespaces.Value(), - }, nil + } + return nil } diff --git a/pkg/flag/remote_flags.go b/pkg/flag/remote_flags.go index b09e89a48104..c821041ec2b5 100644 --- a/pkg/flag/remote_flags.go +++ b/pkg/flag/remote_flags.go @@ -110,11 +110,7 @@ func (f *RemoteFlagGroup) Flags() []Flagger { } } -func (f *RemoteFlagGroup) ToOptions() (RemoteOptions, error) { - if err := parseFlags(f); err != nil { - return RemoteOptions{}, err - } - +func (f *RemoteFlagGroup) ToOptions(opts *Options) error { serverAddr := f.ServerAddr.Value() customHeaders := splitCustomHeaders(f.CustomHeaders.Value()) listen := f.Listen.Value() @@ -140,14 +136,15 @@ func (f *RemoteFlagGroup) ToOptions() (RemoteOptions, error) { customHeaders.Set(tokenHeader, token) } - return RemoteOptions{ + opts.RemoteOptions = RemoteOptions{ Token: token, TokenHeader: tokenHeader, PathPrefix: f.PathPrefix.Value(), ServerAddr: serverAddr, CustomHeaders: customHeaders, Listen: listen, - }, nil + } + return nil } func splitCustomHeaders(headers []string) http.Header { diff --git a/pkg/flag/remote_flags_test.go b/pkg/flag/remote_flags_test.go index 844831093ba8..c11b9e7e055d 100644 --- a/pkg/flag/remote_flags_test.go +++ b/pkg/flag/remote_flags_test.go @@ -110,9 +110,10 @@ func TestRemoteFlagGroup_ToOptions(t *testing.T) { Token: flag.ServerTokenFlag.Clone(), TokenHeader: flag.ServerTokenHeaderFlag.Clone(), } - got, err := f.ToOptions() + flags := flag.Flags{f} + got, err := flags.ToOptions(nil) require.NoError(t, err) - assert.Equalf(t, tt.want, got, "ToOptions()") + assert.Equal(t, tt.want, got.RemoteOptions) // Assert log messages assert.Equal(t, tt.wantLogs, out.Messages(), tt.name) diff --git a/pkg/flag/repo_flags.go b/pkg/flag/repo_flags.go index 31ac0b634a3e..4468bdbb3114 100644 --- a/pkg/flag/repo_flags.go +++ b/pkg/flag/repo_flags.go @@ -50,14 +50,11 @@ func (f *RepoFlagGroup) Flags() []Flagger { } } -func (f *RepoFlagGroup) ToOptions() (RepoOptions, error) { - if err := parseFlags(f); err != nil { - return RepoOptions{}, err - } - - return RepoOptions{ +func (f *RepoFlagGroup) ToOptions(opts *Options) error { + opts.RepoOptions = RepoOptions{ RepoBranch: f.Branch.Value(), RepoCommit: f.Commit.Value(), RepoTag: f.Tag.Value(), - }, nil + } + return nil } diff --git a/pkg/flag/report_flags.go b/pkg/flag/report_flags.go index 90ac077abcee..13d9a51b75ea 100644 --- a/pkg/flag/report_flags.go +++ b/pkg/flag/report_flags.go @@ -200,11 +200,7 @@ func (f *ReportFlagGroup) Flags() []Flagger { } } -func (f *ReportFlagGroup) ToOptions() (ReportOptions, error) { - if err := parseFlags(f); err != nil { - return ReportOptions{}, err - } - +func (f *ReportFlagGroup) ToOptions(opts *Options) error { format := types.Format(f.Format.Value()) template := f.Template.Value() dependencyTree := f.DependencyTree.Value() @@ -241,27 +237,27 @@ func (f *ReportFlagGroup) ToOptions() (ReportOptions, error) { // "--table-mode" option is available only with "--format table". if viper.IsSet(TableModeFlag.ConfigName) && format != types.FormatTable { - return ReportOptions{}, xerrors.New(`"--table-mode" can be used only with "--format table".`) + return xerrors.New(`"--table-mode" can be used only with "--format table".`) } cs, err := loadComplianceTypes(f.Compliance.Value()) if err != nil { - return ReportOptions{}, xerrors.Errorf("unable to load compliance spec: %w", err) + return xerrors.Errorf("unable to load compliance spec: %w", err) } var outputPluginArgs []string if arg := f.OutputPluginArg.Value(); arg != "" { outputPluginArgs, err = shellwords.Parse(arg) if err != nil { - return ReportOptions{}, xerrors.Errorf("unable to parse output plugin argument: %w", err) + return xerrors.Errorf("unable to parse output plugin argument: %w", err) } } if viper.IsSet(f.IgnoreFile.ConfigName) && !fsutils.FileExists(f.IgnoreFile.Value()) { - return ReportOptions{}, xerrors.Errorf("ignore file not found: %s", f.IgnoreFile.Value()) + return xerrors.Errorf("ignore file not found: %s", f.IgnoreFile.Value()) } - return ReportOptions{ + opts.ReportOptions = ReportOptions{ Format: format, ReportFormat: f.ReportFormat.Value(), Template: template, @@ -277,7 +273,8 @@ func (f *ReportFlagGroup) ToOptions() (ReportOptions, error) { Compliance: cs, ShowSuppressed: f.ShowSuppressed.Value(), TableModes: xstrings.ToTSlice[types.TableMode](tableModes), - }, nil + } + return nil } func loadComplianceTypes(compliance string) (spec.ComplianceSpec, error) { diff --git a/pkg/flag/report_flags_test.go b/pkg/flag/report_flags_test.go index 7999ab6c84bd..fcced5fa0b43 100644 --- a/pkg/flag/report_flags_test.go +++ b/pkg/flag/report_flags_test.go @@ -214,13 +214,14 @@ func TestReportFlagGroup_ToOptions(t *testing.T) { TableMode: flag.TableModeFlag.Clone(), } - got, err := f.ToOptions() + flags := flag.Flags{f} + got, err := flags.ToOptions(nil) if tt.wantErr != "" { - require.Contains(t, err.Error(), tt.wantErr) + require.ErrorContains(t, err, tt.wantErr) return } - assert.EqualExportedValuesf(t, tt.want, got, "ToOptions()") + assert.EqualExportedValues(t, tt.want, got.ReportOptions) // Assert log messages assert.Equal(t, tt.wantLogs, out.Messages(), tt.name) @@ -235,7 +236,8 @@ func TestReportFlagGroup_ToOptions(t *testing.T) { IgnoreFile: flag.IgnoreFileFlag.Clone(), } - _, err := f.ToOptions() + flags := flag.Flags{f} + _, err := flags.ToOptions(nil) assert.ErrorContains(t, err, "ignore file not found: doesntexist") }) } diff --git a/pkg/flag/scan_flags.go b/pkg/flag/scan_flags.go index 15a1e04ce139..f40370a97e23 100644 --- a/pkg/flag/scan_flags.go +++ b/pkg/flag/scan_flags.go @@ -184,14 +184,10 @@ func (f *ScanFlagGroup) Flags() []Flagger { } } -func (f *ScanFlagGroup) ToOptions(args []string) (ScanOptions, error) { - if err := parseFlags(f); err != nil { - return ScanOptions{}, err - } - +func (f *ScanFlagGroup) ToOptions(opts *Options) error { var target string - if len(args) == 1 { - target = args[0] + if len(opts.args) == 1 { + target = opts.args[0] } parallel := f.Parallel.Value() @@ -204,7 +200,7 @@ func (f *ScanFlagGroup) ToOptions(args []string) (ScanOptions, error) { if f.DistroFlag != nil && f.DistroFlag.Value() != "" { family, version, _ := strings.Cut(f.DistroFlag.Value(), "/") if !slices.Contains(ftypes.OSTypes, ftypes.OSType(family)) { - return ScanOptions{}, xerrors.Errorf("unknown OS family: %s, must be %q", family, ftypes.OSTypes) + return xerrors.Errorf("unknown OS family: %s, must be %q", family, ftypes.OSTypes) } distro = ftypes.OS{ Family: ftypes.OSType(family), @@ -212,7 +208,7 @@ func (f *ScanFlagGroup) ToOptions(args []string) (ScanOptions, error) { } } - return ScanOptions{ + opts.ScanOptions = ScanOptions{ Target: target, SkipDirs: f.SkipDirs.Value(), SkipFiles: f.SkipFiles.Value(), @@ -224,5 +220,6 @@ func (f *ScanFlagGroup) ToOptions(args []string) (ScanOptions, error) { RekorURL: f.RekorURL.Value(), DetectionPriority: ftypes.DetectionPriority(f.DetectionPriority.Value()), Distro: distro, - }, nil + } + return nil } diff --git a/pkg/flag/scan_flags_test.go b/pkg/flag/scan_flags_test.go index e0f8ffb9deb6..c509a987743a 100644 --- a/pkg/flag/scan_flags_test.go +++ b/pkg/flag/scan_flags_test.go @@ -147,9 +147,10 @@ func TestScanFlagGroup_ToOptions(t *testing.T) { DistroFlag: flag.DistroFlag.Clone(), } - got, err := f.ToOptions(tt.args) + flags := flag.Flags{f} + got, err := flags.ToOptions(tt.args) tt.assertion(t, err) - assert.Equalf(t, tt.want, got, "ToOptions()") + assert.Equal(t, tt.want, got.ScanOptions) }) } } diff --git a/pkg/flag/secret_flags.go b/pkg/flag/secret_flags.go index 295753c48cc5..517ecea6bd4e 100644 --- a/pkg/flag/secret_flags.go +++ b/pkg/flag/secret_flags.go @@ -31,12 +31,9 @@ func (f *SecretFlagGroup) Flags() []Flagger { return []Flagger{f.SecretConfig} } -func (f *SecretFlagGroup) ToOptions() (SecretOptions, error) { - if err := parseFlags(f); err != nil { - return SecretOptions{}, err - } - - return SecretOptions{ +func (f *SecretFlagGroup) ToOptions(opts *Options) error { + opts.SecretOptions = SecretOptions{ SecretConfigPath: f.SecretConfig.Value(), - }, nil + } + return nil } diff --git a/pkg/flag/vulnerability_flags.go b/pkg/flag/vulnerability_flags.go index 0ce06a4ced37..50a5984e92b9 100644 --- a/pkg/flag/vulnerability_flags.go +++ b/pkg/flag/vulnerability_flags.go @@ -82,11 +82,7 @@ func (f *VulnerabilityFlagGroup) Flags() []Flagger { } } -func (f *VulnerabilityFlagGroup) ToOptions() (VulnerabilityOptions, error) { - if err := parseFlags(f); err != nil { - return VulnerabilityOptions{}, err - } - +func (f *VulnerabilityFlagGroup) ToOptions(opts *Options) error { // Just convert string to dbTypes.Status as the validated values are passed here. ignoreStatuses := lo.Map(f.IgnoreStatus.Value(), func(s string, _ int) dbTypes.Status { return dbTypes.NewStatus(s) @@ -110,12 +106,13 @@ func (f *VulnerabilityFlagGroup) ToOptions() (VulnerabilityOptions, error) { } log.Debug("Ignore statuses", log.Any("statuses", ignoreStatuses)) - return VulnerabilityOptions{ + opts.VulnerabilityOptions = VulnerabilityOptions{ IgnoreStatuses: ignoreStatuses, VEXSources: lo.Map(f.VEX.Value(), func(s string, _ int) vex.Source { return vex.NewSource(s) }), SkipVEXRepoUpdate: f.SkipVEXRepoUpdate.Value(), VulnSeveritySources: xstrings.ToTSlice[dbTypes.SourceID](f.VulnSeveritySource.Value()), - }, nil + } + return nil } diff --git a/pkg/iac/adapters/arm/adapt.go b/pkg/iac/adapters/arm/adapt.go index a6bcbec2a42e..82d3cfafb800 100644 --- a/pkg/iac/adapters/arm/adapt.go +++ b/pkg/iac/adapters/arm/adapt.go @@ -22,7 +22,7 @@ import ( ) // Adapt adapts an azure arm instance -func Adapt(ctx context.Context, deployment scanner.Deployment) *state.State { +func Adapt(_ context.Context, deployment scanner.Deployment) *state.State { return &state.State{ Azure: adaptAzure(deployment), } diff --git a/pkg/iac/adapters/arm/container/adapt.go b/pkg/iac/adapters/arm/container/adapt.go index 2172553e4cfb..298aa83969a0 100644 --- a/pkg/iac/adapters/arm/container/adapt.go +++ b/pkg/iac/adapters/arm/container/adapt.go @@ -11,7 +11,7 @@ func Adapt(deployment azure.Deployment) container.Container { } } -func adaptKubernetesClusters(deployment azure.Deployment) []container.KubernetesCluster { +func adaptKubernetesClusters(_ azure.Deployment) []container.KubernetesCluster { return nil } diff --git a/pkg/iac/adapters/arm/database/adapt.go b/pkg/iac/adapters/arm/database/adapt.go index 834865fc22d0..8eab5d42f16a 100644 --- a/pkg/iac/adapters/arm/database/adapt.go +++ b/pkg/iac/adapters/arm/database/adapt.go @@ -21,7 +21,7 @@ func adaptMySQLServers(deployment azure.Deployment) (mysqlDbServers []database.M return mysqlDbServers } -func adaptMySQLServer(resource azure.Resource, deployment azure.Deployment) database.MySQLServer { +func adaptMySQLServer(resource azure.Resource, _ azure.Deployment) database.MySQLServer { return database.MySQLServer{ Metadata: resource.Metadata, Server: database.Server{ diff --git a/pkg/iac/adapters/arm/database/maria.go b/pkg/iac/adapters/arm/database/maria.go index 083c3b0540e5..b576813be73b 100644 --- a/pkg/iac/adapters/arm/database/maria.go +++ b/pkg/iac/adapters/arm/database/maria.go @@ -13,7 +13,7 @@ func adaptMariaDBServers(deployment azure.Deployment) (mariaDbServers []database } -func adaptMariaDBServer(resource azure.Resource, deployment azure.Deployment) database.MariaDBServer { +func adaptMariaDBServer(resource azure.Resource, _ azure.Deployment) database.MariaDBServer { return database.MariaDBServer{ Metadata: resource.Metadata, Server: database.Server{ diff --git a/pkg/iac/adapters/arm/database/mssql.go b/pkg/iac/adapters/arm/database/mssql.go index 95cc41ed6b25..430e3c5686b4 100644 --- a/pkg/iac/adapters/arm/database/mssql.go +++ b/pkg/iac/adapters/arm/database/mssql.go @@ -28,7 +28,7 @@ func adaptMSSQLServer(resource azure2.Resource, deployment azure2.Deployment) da } } -func adaptExtendedAuditingPolicies(resource azure2.Resource, deployment azure2.Deployment) (policies []database.ExtendedAuditingPolicy) { +func adaptExtendedAuditingPolicies(_ azure2.Resource, deployment azure2.Deployment) (policies []database.ExtendedAuditingPolicy) { for _, policy := range deployment.GetResourcesByType("Microsoft.Sql/servers/extendedAuditingSettings") { policies = append(policies, database.ExtendedAuditingPolicy{ @@ -40,7 +40,7 @@ func adaptExtendedAuditingPolicies(resource azure2.Resource, deployment azure2.D return policies } -func adaptSecurityAlertPolicies(resource azure2.Resource, deployment azure2.Deployment) (policies []database.SecurityAlertPolicy) { +func adaptSecurityAlertPolicies(_ azure2.Resource, deployment azure2.Deployment) (policies []database.SecurityAlertPolicy) { for _, policy := range deployment.GetResourcesByType("Microsoft.Sql/servers/securityAlertPolicies") { policies = append(policies, database.SecurityAlertPolicy{ Metadata: policy.Metadata, diff --git a/pkg/iac/adapters/arm/keyvault/adapt.go b/pkg/iac/adapters/arm/keyvault/adapt.go index 71a1fbd54f33..cae3db33a0ca 100644 --- a/pkg/iac/adapters/arm/keyvault/adapt.go +++ b/pkg/iac/adapters/arm/keyvault/adapt.go @@ -33,7 +33,7 @@ func adaptVault(resource azure.Resource, deployment azure.Deployment) keyvault.V } } -func adaptKeys(resource azure.Resource, deployment azure.Deployment) (keys []keyvault.Key) { +func adaptKeys(_ azure.Resource, deployment azure.Deployment) (keys []keyvault.Key) { for _, resource := range deployment.GetResourcesByType("Microsoft.KeyVault/vaults/keys") { keys = append(keys, adaptKey(resource)) } @@ -48,7 +48,7 @@ func adaptKey(resource azure.Resource) keyvault.Key { } } -func adaptSecrets(resource azure.Resource, deployment azure.Deployment) (secrets []keyvault.Secret) { +func adaptSecrets(_ azure.Resource, deployment azure.Deployment) (secrets []keyvault.Secret) { for _, resource := range deployment.GetResourcesByType("Microsoft.KeyVault/vaults/secrets") { secrets = append(secrets, adaptSecret(resource)) } diff --git a/pkg/iac/adapters/arm/storage/adapt_test.go b/pkg/iac/adapters/arm/storage/adapt_test.go index 372986f23529..cdc3cbcdd1e2 100644 --- a/pkg/iac/adapters/arm/storage/adapt_test.go +++ b/pkg/iac/adapters/arm/storage/adapt_test.go @@ -28,7 +28,7 @@ func Test_AdaptStorageDefaults(t *testing.T) { require.Len(t, output.Accounts, 1) account := output.Accounts[0] - assert.Equal(t, "", account.MinimumTLSVersion.Value()) + assert.Empty(t, account.MinimumTLSVersion.Value()) assert.False(t, account.EnforceHTTPS.Value()) assert.True(t, account.PublicNetworkAccess.Value()) diff --git a/pkg/iac/adapters/cloudformation/aws/s3/bucket.go b/pkg/iac/adapters/cloudformation/aws/s3/bucket.go index 957278a93a29..b7d82cb208f1 100644 --- a/pkg/iac/adapters/cloudformation/aws/s3/bucket.go +++ b/pkg/iac/adapters/cloudformation/aws/s3/bucket.go @@ -96,11 +96,8 @@ func hasVersioning(r *parser.Resource) iacTypes.BoolValue { return iacTypes.BoolDefault(false, r.Metadata()) } - versioningEnabled := false - if versioningProp.EqualTo("Enabled") { - versioningEnabled = true + versioningEnabled := versioningProp.EqualTo("Enabled") - } return iacTypes.Bool(versioningEnabled, versioningProp.Metadata()) } @@ -151,17 +148,16 @@ func getLifecycle(resource *parser.Resource) []s3.Rules { } func getWebsite(r *parser.Resource) *s3.Website { - if block := r.GetProperty("WebsiteConfiguration"); block.IsNil() { + block := r.GetProperty("WebsiteConfiguration") + if block.IsNil() { return nil - } else { - return &s3.Website{ - Metadata: block.Metadata(), - } + } + return &s3.Website{ + Metadata: block.Metadata(), } } func getBucketPolicies(fctx parser.FileContext, r *parser.Resource) []iam.Policy { - var policies []iam.Policy for _, bucketPolicy := range fctx.GetResourcesByType("AWS::S3::BucketPolicy") { bucket := bucketPolicy.GetStringProperty("Bucket") diff --git a/pkg/iac/adapters/terraform/aws/codebuild/adapt.go b/pkg/iac/adapters/terraform/aws/codebuild/adapt.go index f7fa4b4f35b5..b026151ef8c1 100644 --- a/pkg/iac/adapters/terraform/aws/codebuild/adapt.go +++ b/pkg/iac/adapters/terraform/aws/codebuild/adapt.go @@ -39,7 +39,7 @@ func adaptProject(resource *terraform.Block) codebuild.Project { project.ArtifactSettings.Metadata = artifactsBlock.GetMetadata() typeAttr := artifactsBlock.GetAttribute("type") encryptionDisabledAttr := artifactsBlock.GetAttribute("encryption_disabled") - hasArtifacts = typeAttr.NotEqual("NO_ARTIFACTS") + hasArtifacts = !typeAttr.Equals("NO_ARTIFACTS") if encryptionDisabledAttr.IsTrue() && hasArtifacts { project.ArtifactSettings.EncryptionEnabled = types.Bool(false, artifactsBlock.GetMetadata()) } else { diff --git a/pkg/iac/adapters/terraform/aws/dynamodb/adapt.go b/pkg/iac/adapters/terraform/aws/dynamodb/adapt.go index c8057bfc8334..833587927413 100644 --- a/pkg/iac/adapters/terraform/aws/dynamodb/adapt.go +++ b/pkg/iac/adapters/terraform/aws/dynamodb/adapt.go @@ -33,7 +33,7 @@ func adaptTables(modules terraform.Modules) []dynamodb.Table { return tables } -func adaptCluster(resource *terraform.Block, module *terraform.Module) dynamodb.DAXCluster { +func adaptCluster(resource *terraform.Block, _ *terraform.Module) dynamodb.DAXCluster { cluster := dynamodb.DAXCluster{ Metadata: resource.GetMetadata(), diff --git a/pkg/iac/adapters/terraform/aws/ec2/adapt.go b/pkg/iac/adapters/terraform/aws/ec2/adapt.go index 843fcf7c312d..92420c4dcf39 100644 --- a/pkg/iac/adapters/terraform/aws/ec2/adapt.go +++ b/pkg/iac/adapters/terraform/aws/ec2/adapt.go @@ -62,7 +62,7 @@ func getInstances(modules terraform.Modules) []ec2.Instance { } for _, resource := range modules.GetResourcesByType("aws_ebs_encryption_by_default") { - if resource.GetAttribute("enabled").NotEqual(false) { + if !resource.GetAttribute("enabled").Equals(false) { instance.RootBlockDevice.Encrypted = types.BoolDefault(true, resource.GetMetadata()) for i := 0; i < len(instance.EBSBlockDevices); i++ { ebs := instance.EBSBlockDevices[i] diff --git a/pkg/iac/adapters/terraform/aws/ec2/autoscaling.go b/pkg/iac/adapters/terraform/aws/ec2/autoscaling.go index 7974506e6460..38a017258c85 100644 --- a/pkg/iac/adapters/terraform/aws/ec2/autoscaling.go +++ b/pkg/iac/adapters/terraform/aws/ec2/autoscaling.go @@ -37,7 +37,7 @@ func adaptLaunchConfigurations(modules terraform.Modules) []ec2.LaunchConfigurat for _, resource := range module.GetResourcesByType("aws_launch_configuration") { launchConfig := adaptLaunchConfiguration(resource) for _, resource := range module.GetResourcesByType("aws_ebs_encryption_by_default") { - if resource.GetAttribute("enabled").NotEqual(false) { + if !resource.GetAttribute("enabled").Equals(false) { launchConfig.RootBlockDevice.Encrypted = iacTypes.BoolDefault(true, resource.GetMetadata()) for i := 0; i < len(launchConfig.EBSBlockDevices); i++ { ebs := launchConfig.EBSBlockDevices[i] diff --git a/pkg/iac/adapters/terraform/aws/elb/adapt.go b/pkg/iac/adapters/terraform/aws/elb/adapt.go index 2cf5ec95efe5..a5fab5b9a9fb 100644 --- a/pkg/iac/adapters/terraform/aws/elb/adapt.go +++ b/pkg/iac/adapters/terraform/aws/elb/adapt.go @@ -79,7 +79,7 @@ func (a *adapter) adaptLoadBalancer(resource *terraform.Block, module terraform. } } -func (a *adapter) adaptClassicLoadBalancer(resource *terraform.Block, module terraform.Modules) elb.LoadBalancer { +func (a *adapter) adaptClassicLoadBalancer(resource *terraform.Block, _ terraform.Modules) elb.LoadBalancer { internalAttr := resource.GetAttribute("internal") internalVal := internalAttr.AsBoolValueOrDefault(false, resource) diff --git a/pkg/iac/adapters/terraform/aws/iam/convert.go b/pkg/iac/adapters/terraform/aws/iam/convert.go index dcc61cd6bc97..3663d81c571e 100644 --- a/pkg/iac/adapters/terraform/aws/iam/convert.go +++ b/pkg/iac/adapters/terraform/aws/iam/convert.go @@ -15,30 +15,36 @@ type wrappedDocument struct { } func ParsePolicyFromAttr(attr *terraform.Attribute, owner *terraform.Block, modules terraform.Modules) (*iam.Document, error) { - if attr.IsString() { - dataBlock, err := modules.GetBlockById(attr.Value().AsString()) + if !attr.IsString() { + return &iam.Document{ + Metadata: owner.GetMetadata(), + }, nil + } + + attrValue := attr.Value().AsString() + + dataBlock, err := modules.GetBlockById(attrValue) + if err != nil { + parsed, err := iamgo.Parse([]byte(unescapeVars(attrValue))) if err != nil { - parsed, err := iamgo.Parse([]byte(unescapeVars(attr.Value().AsString()))) - if err != nil { - return nil, err - } - return &iam.Document{ - Parsed: *parsed, - Metadata: attr.GetMetadata(), - IsOffset: false, - HasRefs: len(attr.AllReferences()) > 0, - }, nil + return nil, err } + return &iam.Document{ + Parsed: *parsed, + Metadata: attr.GetMetadata(), + IsOffset: false, + HasRefs: len(attr.AllReferences()) > 0, + }, nil + } - if dataBlock.Type() == "data" && dataBlock.TypeLabel() == "aws_iam_policy_document" { - if doc, err := ConvertTerraformDocument(modules, dataBlock); err == nil { - return &iam.Document{ - Metadata: dataBlock.GetMetadata(), - Parsed: doc.Document, - IsOffset: true, - HasRefs: false, - }, nil - } + if dataBlock.Type() == "data" && dataBlock.TypeLabel() == "aws_iam_policy_document" { + if doc, err := ConvertTerraformDocument(modules, dataBlock); err == nil { + return &iam.Document{ + Metadata: dataBlock.GetMetadata(), + Parsed: doc.Document, + IsOffset: true, + HasRefs: false, + }, nil } } diff --git a/pkg/iac/adapters/terraform/aws/iam/policies.go b/pkg/iac/adapters/terraform/aws/iam/policies.go index 7b662d3e5d30..c74c3b3f8298 100644 --- a/pkg/iac/adapters/terraform/aws/iam/policies.go +++ b/pkg/iac/adapters/terraform/aws/iam/policies.go @@ -8,9 +8,10 @@ import ( ) func parsePolicy(policyBlock *terraform.Block, modules terraform.Modules) (iam.Policy, error) { + nameAttr := policyBlock.GetAttribute("name") policy := iam.Policy{ Metadata: policyBlock.GetMetadata(), - Name: policyBlock.GetAttribute("name").AsStringValueOrDefault("", policyBlock), + Name: nameAttr.AsStringValueOrDefault("", policyBlock), Document: iam.Document{ Metadata: iacTypes.NewUnmanagedMetadata(), Parsed: iamgo.Document{}, @@ -19,7 +20,19 @@ func parsePolicy(policyBlock *terraform.Block, modules terraform.Modules) (iam.P }, Builtin: iacTypes.Bool(false, policyBlock.GetMetadata()), } - var err error + + if policyBlock.Type() == "data" && policyBlock.TypeLabel() == "aws_iam_policy" && + nameAttr.IsString() { + doc, exists := awsManagedPolicies[nameAttr.Value().AsString()] + if exists { + policy.Document = iam.Document{ + Metadata: nameAttr.GetMetadata(), + Parsed: doc, + } + return policy, nil + } + } + doc, err := ParsePolicyFromAttr(policyBlock.GetAttribute("policy"), policyBlock, modules) if err != nil { return policy, err @@ -96,14 +109,58 @@ func findPolicy(modules terraform.Modules) func(resource *terraform.Block) *iam. func findAttachmentPolicy(modules terraform.Modules) func(resource *terraform.Block) *iam.Policy { return func(resource *terraform.Block) *iam.Policy { - policyAttr := resource.GetAttribute("policy_arn") - if policyAttr.IsNil() { + attr := resource.GetAttribute("policy_arn") + if attr.IsNil() { return nil } - policyBlock, err := modules.GetReferencedBlock(policyAttr, resource) - if err != nil { - return nil + + if attr.IsString() { + arn := attr.Value().AsString() + if doc, ok := awsManagedPolicies[arn]; ok { + if block, err := modules.GetReferencedBlock(attr, resource); err == nil { + meta := block.GetMetadata() + if arnAttr := block.GetAttribute("arn"); arnAttr.IsNotNil() { + meta = arnAttr.GetMetadata() + } + return &iam.Policy{ + Metadata: block.GetMetadata(), + Document: iam.Document{ + Metadata: meta, + Parsed: doc, + }, + } + } + return &iam.Policy{ + Metadata: resource.GetMetadata(), + Document: iam.Document{ + Metadata: attr.GetMetadata(), + Parsed: doc, + }, + } + } } - return findPolicy(modules)(policyBlock) + + if block, err := modules.GetReferencedBlock(attr, resource); err == nil { + return findPolicy(modules)(block) + } + + return nil } } + +var awsManagedPolicies = map[string]iamgo.Document{ + // https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AmazonS3FullAccess.html + "arn:aws:iam::aws:policy/AmazonS3FullAccess": s3FullAccessPolicyDocument, + "AmazonS3FullAccess": s3FullAccessPolicyDocument, +} + +var s3FullAccessPolicyDocument = iamgo.NewPolicyBuilder(). + WithVersion("2012-10-17"). + WithStatement( + iamgo.NewStatementBuilder(). + WithEffect("Allow"). + WithActions([]string{"s3:*", "s3-object-lambda:*"}). + WithResources([]string{"*"}). + Build(), + ). + Build() diff --git a/pkg/iac/adapters/terraform/aws/iam/roles_test.go b/pkg/iac/adapters/terraform/aws/iam/roles_test.go index 803faf6bfb3a..687a9f17f98e 100644 --- a/pkg/iac/adapters/terraform/aws/iam/roles_test.go +++ b/pkg/iac/adapters/terraform/aws/iam/roles_test.go @@ -260,6 +260,84 @@ data "aws_iam_policy_document" "s3_policy" { }, }, }, + { + name: "attach AWS managed policy using ARN directly", + terraform: `resource "aws_iam_role" "test" { + name = "example-role" +} + +resource "aws_iam_role_policy_attachment" "test" { + role = aws_iam_role.test.name + policy_arn = "arn:aws:iam::aws:policy/AmazonS3FullAccess" +}`, + expected: []iam.Role{ + { + Name: iacTypes.StringTest("example-role"), + Policies: []iam.Policy{ + { + Document: iam.Document{ + Parsed: s3FullAccessPolicyDocument, + }, + }, + }, + }, + }, + }, + { + name: "attach AWS managed policy using ARN from data source", + terraform: `data "aws_iam_policy" "s3_full_access" { + arn = "arn:aws:iam::aws:policy/AmazonS3FullAccess" +} + +resource "aws_iam_role" "test" { + name = "example-role" +} + +resource "aws_iam_role_policy_attachment" "test" { + role = aws_iam_role.test.name + policy_arn = data.aws_iam_policy.s3_full_access.arn +}`, + expected: []iam.Role{ + { + Name: iacTypes.StringTest("example-role"), + Policies: []iam.Policy{ + { + Document: iam.Document{ + Parsed: s3FullAccessPolicyDocument, + }, + }, + }, + }, + }, + }, + { + name: "attach AWS managed policy using data source with policy name", + terraform: `data "aws_iam_policy" "s3_full_access" { + name = "AmazonS3FullAccess" +} + +resource "aws_iam_role" "test" { + name = "example-role" +} + +resource "aws_iam_role_policy_attachment" "test" { + role = aws_iam_role.test.name + policy_arn = data.aws_iam_policy.s3_full_access.arn +}`, + expected: []iam.Role{ + { + Name: iacTypes.StringTest("example-role"), + Policies: []iam.Policy{ + { + Name: iacTypes.StringTest("AmazonS3FullAccess"), + Document: iam.Document{ + Parsed: s3FullAccessPolicyDocument, + }, + }, + }, + }, + }, + }, } for _, test := range tests { diff --git a/pkg/iac/adapters/terraform/aws/lambda/adapt.go b/pkg/iac/adapters/terraform/aws/lambda/adapt.go index 3a28107d2197..3545dcb192f4 100644 --- a/pkg/iac/adapters/terraform/aws/lambda/adapt.go +++ b/pkg/iac/adapters/terraform/aws/lambda/adapt.go @@ -86,8 +86,8 @@ func (a *adapter) adaptPermission(permission *terraform.Block) lambda.Permission sourceARNAttr := permission.GetAttribute("source_arn") sourceARN := sourceARNAttr.AsStringValueOrDefault("", permission) - if len(sourceARNAttr.AllReferences()) > 0 { - sourceARN = iacTypes.String(sourceARNAttr.AllReferences()[0].NameLabel(), sourceARNAttr.GetMetadata()) + if refs := sourceARNAttr.AllReferences(); len(refs) > 0 { + sourceARN = iacTypes.String(refs[0].NameLabel(), sourceARNAttr.GetMetadata()) } return lambda.Permission{ diff --git a/pkg/iac/adapters/terraform/aws/rds/adapt.go b/pkg/iac/adapters/terraform/aws/rds/adapt.go index d99821c23950..68d302e26a99 100644 --- a/pkg/iac/adapters/terraform/aws/rds/adapt.go +++ b/pkg/iac/adapters/terraform/aws/rds/adapt.go @@ -169,7 +169,7 @@ func adaptInstance(resource *terraform.Block, modules terraform.Modules) rds.Ins } } -func adaptDBParameterGroups(resource *terraform.Block, modules terraform.Modules) rds.ParameterGroups { +func adaptDBParameterGroups(resource *terraform.Block, _ terraform.Modules) rds.ParameterGroups { var Parameters []rds.Parameters paramres := resource.GetBlocks("parameter") @@ -190,7 +190,7 @@ func adaptDBParameterGroups(resource *terraform.Block, modules terraform.Modules } } -func adaptDBSnapshots(resource *terraform.Block, modules terraform.Modules) rds.Snapshots { +func adaptDBSnapshots(resource *terraform.Block, _ terraform.Modules) rds.Snapshots { return rds.Snapshots{ Metadata: resource.GetMetadata(), diff --git a/pkg/iac/adapters/terraform/aws/s3/bucket.go b/pkg/iac/adapters/terraform/aws/s3/bucket.go index 206b0cbdabdf..945e8734ec89 100644 --- a/pkg/iac/adapters/terraform/aws/s3/bucket.go +++ b/pkg/iac/adapters/terraform/aws/s3/bucket.go @@ -263,7 +263,7 @@ func isEncrypted(sseConfgihuration *terraform.Block) iacTypes.BoolValue { func hasLogging(b *terraform.Block) iacTypes.BoolValue { if loggingBlock := b.GetBlock("logging"); loggingBlock.IsNotNil() { - if targetAttr := loggingBlock.GetAttribute("target_bucket"); targetAttr.IsNotNil() && targetAttr.IsNotEmpty() { + if targetAttr := loggingBlock.GetAttribute("target_bucket"); targetAttr.IsNotNil() && !targetAttr.IsEmpty() { return iacTypes.Bool(true, targetAttr.GetMetadata()) } return iacTypes.BoolDefault(false, loggingBlock.GetMetadata()) diff --git a/pkg/iac/adapters/terraform/nifcloud/computing/security_group.go b/pkg/iac/adapters/terraform/nifcloud/computing/security_group.go index 38699a6f2555..66c0ec51a73b 100644 --- a/pkg/iac/adapters/terraform/nifcloud/computing/security_group.go +++ b/pkg/iac/adapters/terraform/nifcloud/computing/security_group.go @@ -61,7 +61,7 @@ func (a *sgAdapter) adaptSecurityGroup(resource *terraform.Block, module terrafo } } -func adaptSGRule(resource *terraform.Block, modules terraform.Modules) computing.SecurityGroupRule { +func adaptSGRule(resource *terraform.Block, _ terraform.Modules) computing.SecurityGroupRule { ruleDescAttr := resource.GetAttribute("description") ruleDescVal := ruleDescAttr.AsStringValueOrDefault("", resource) diff --git a/pkg/iac/adapters/terraform/tftestutil/testutil.go b/pkg/iac/adapters/terraform/tftestutil/testutil.go index a3a10e74a4b7..96045b50b214 100644 --- a/pkg/iac/adapters/terraform/tftestutil/testutil.go +++ b/pkg/iac/adapters/terraform/tftestutil/testutil.go @@ -3,6 +3,8 @@ package tftestutil import ( "testing" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser" "github.com/aquasecurity/trivy/pkg/iac/terraform" @@ -13,12 +15,8 @@ func CreateModulesFromSource(t *testing.T, source, ext string) terraform.Modules "source" + ext: source, }) p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) - if err := p.ParseFS(t.Context(), "."); err != nil { - t.Fatal(err) - } - modules, _, err := p.EvaluateAll(t.Context()) - if err != nil { - t.Fatalf("parse error: %s", err) - } + require.NoError(t, p.ParseFS(t.Context(), ".")) + modules, err := p.EvaluateAll(t.Context()) + require.NoError(t, err) return modules } diff --git a/pkg/iac/detection/detect_test.go b/pkg/iac/detection/detect_test.go index 1ebd7ca67337..3fd2b4465695 100644 --- a/pkg/iac/detection/detect_test.go +++ b/pkg/iac/detection/detect_test.go @@ -449,7 +449,7 @@ rules: t.Run(test.name, func(t *testing.T) { t.Run("GetTypes", func(t *testing.T) { actualDetections := GetTypes(test.path, test.r) - assert.Equal(t, len(test.expected), len(actualDetections)) + assert.Len(t, actualDetections, len(test.expected)) for _, expected := range test.expected { resetReader(test.r) found := slices.Contains(actualDetections, expected) diff --git a/pkg/iac/providers/aws/cloudtrail/cloudtrail.go b/pkg/iac/providers/aws/cloudtrail/cloudtrail.go index 8e5da87e7c89..edb28c826c9d 100755 --- a/pkg/iac/providers/aws/cloudtrail/cloudtrail.go +++ b/pkg/iac/providers/aws/cloudtrail/cloudtrail.go @@ -8,15 +8,6 @@ type CloudTrail struct { Trails []Trail } -func (c CloudTrail) MultiRegionTrails() (multiRegionTrails []Trail) { - for _, trail := range c.Trails { - if trail.IsMultiRegion.IsTrue() { - multiRegionTrails = append(multiRegionTrails, trail) - } - } - return multiRegionTrails -} - type Trail struct { Metadata iacTypes.Metadata Name iacTypes.StringValue diff --git a/pkg/iac/providers/aws/cloudwatch/cloudwatch.go b/pkg/iac/providers/aws/cloudwatch/cloudwatch.go index 630ed84e64ef..182d3c8a9dd4 100755 --- a/pkg/iac/providers/aws/cloudwatch/cloudwatch.go +++ b/pkg/iac/providers/aws/cloudwatch/cloudwatch.go @@ -9,24 +9,6 @@ type CloudWatch struct { Alarms []Alarm } -func (w CloudWatch) GetLogGroupByArn(arn string) (logGroup *LogGroup) { - for _, logGroup := range w.LogGroups { - if logGroup.Arn.EqualTo(arn) { - return &logGroup - } - } - return nil -} - -func (w CloudWatch) GetAlarmByMetricName(metricName string) (alarm *Alarm) { - for _, alarm := range w.Alarms { - if alarm.MetricName.EqualTo(metricName) { - return &alarm - } - } - return nil -} - type Alarm struct { Metadata iacTypes.Metadata AlarmName iacTypes.StringValue diff --git a/pkg/iac/providers/aws/ec2/launch.go b/pkg/iac/providers/aws/ec2/launch.go index 9b7c4c711e01..77fa12f8305a 100644 --- a/pkg/iac/providers/aws/ec2/launch.go +++ b/pkg/iac/providers/aws/ec2/launch.go @@ -19,11 +19,3 @@ type LaunchTemplate struct { Name iacTypes.StringValue Instance } - -func (i *LaunchConfiguration) RequiresIMDSToken() bool { - return i.MetadataOptions.HttpTokens.EqualTo("required") -} - -func (i *LaunchConfiguration) HasHTTPEndpointDisabled() bool { - return i.MetadataOptions.HttpEndpoint.EqualTo("disabled") -} diff --git a/pkg/iac/providers/aws/iam/iam.go b/pkg/iac/providers/aws/iam/iam.go index 9a90a857c9d7..216c236a9b9a 100644 --- a/pkg/iac/providers/aws/iam/iam.go +++ b/pkg/iac/providers/aws/iam/iam.go @@ -70,10 +70,6 @@ type User struct { LastAccess iacTypes.TimeValue } -func (u *User) HasLoggedIn() bool { - return u.LastAccess.GetMetadata().IsResolvable() && !u.LastAccess.IsNever() -} - type MFADevice struct { Metadata iacTypes.Metadata IsVirtual iacTypes.BoolValue diff --git a/pkg/iac/providers/aws/s3/bucket.go b/pkg/iac/providers/aws/s3/bucket.go index ccabe6974c99..6f723c29ab6d 100755 --- a/pkg/iac/providers/aws/s3/bucket.go +++ b/pkg/iac/providers/aws/s3/bucket.go @@ -22,17 +22,22 @@ type Bucket struct { Website *Website } -func (b *Bucket) HasPublicExposureACL() bool { - for _, publicACL := range []string{"public-read", "public-read-write", "website", "authenticated-read"} { - if b.ACL.EqualTo(publicACL) { - // if there is a public access block, check the public ACL blocks - if b.PublicAccessBlock != nil && b.PublicAccessBlock.Metadata.IsManaged() { - return b.PublicAccessBlock.IgnorePublicACLs.IsFalse() && b.PublicAccessBlock.BlockPublicACLs.IsFalse() - } - return true - } +type PublicAccessBlock struct { + Metadata iacTypes.Metadata + BlockPublicACLs iacTypes.BoolValue + BlockPublicPolicy iacTypes.BoolValue + IgnorePublicACLs iacTypes.BoolValue + RestrictPublicBuckets iacTypes.BoolValue +} + +func NewPublicAccessBlock(metadata iacTypes.Metadata) PublicAccessBlock { + return PublicAccessBlock{ + Metadata: metadata, + BlockPublicPolicy: iacTypes.BoolDefault(false, metadata), + BlockPublicACLs: iacTypes.BoolDefault(false, metadata), + IgnorePublicACLs: iacTypes.BoolDefault(false, metadata), + RestrictPublicBuckets: iacTypes.BoolDefault(false, metadata), } - return false } type Logging struct { diff --git a/pkg/iac/providers/aws/s3/bucket_public_access_block.go b/pkg/iac/providers/aws/s3/bucket_public_access_block.go deleted file mode 100755 index 573cf6a1c0f7..000000000000 --- a/pkg/iac/providers/aws/s3/bucket_public_access_block.go +++ /dev/null @@ -1,23 +0,0 @@ -package s3 - -import ( - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" -) - -type PublicAccessBlock struct { - Metadata iacTypes.Metadata - BlockPublicACLs iacTypes.BoolValue - BlockPublicPolicy iacTypes.BoolValue - IgnorePublicACLs iacTypes.BoolValue - RestrictPublicBuckets iacTypes.BoolValue -} - -func NewPublicAccessBlock(metadata iacTypes.Metadata) PublicAccessBlock { - return PublicAccessBlock{ - Metadata: metadata, - BlockPublicPolicy: iacTypes.BoolDefault(false, metadata), - BlockPublicACLs: iacTypes.BoolDefault(false, metadata), - IgnorePublicACLs: iacTypes.BoolDefault(false, metadata), - RestrictPublicBuckets: iacTypes.BoolDefault(false, metadata), - } -} diff --git a/pkg/iac/providers/aws/sns/sns.go b/pkg/iac/providers/aws/sns/sns.go index 49c2d370d56f..d502968fae08 100755 --- a/pkg/iac/providers/aws/sns/sns.go +++ b/pkg/iac/providers/aws/sns/sns.go @@ -8,17 +8,6 @@ type SNS struct { Topics []Topic } -func NewTopic(arn string, metadata iacTypes.Metadata) *Topic { - return &Topic{ - Metadata: metadata, - ARN: iacTypes.String(arn, metadata), - Encryption: Encryption{ - Metadata: metadata, - KMSKeyID: iacTypes.StringDefault("", metadata), - }, - } -} - type Topic struct { Metadata iacTypes.Metadata ARN iacTypes.StringValue diff --git a/pkg/iac/providers/azure/network/network.go b/pkg/iac/providers/azure/network/network.go index 4fdc56e44e86..14b230d74782 100755 --- a/pkg/iac/providers/azure/network/network.go +++ b/pkg/iac/providers/azure/network/network.go @@ -31,10 +31,6 @@ type PortRange struct { End iacTypes.IntValue } -func (r PortRange) Includes(port int) bool { - return port >= r.Start.Value() && port <= r.End.Value() -} - type NetworkWatcherFlowLog struct { Metadata iacTypes.Metadata RetentionPolicy RetentionPolicy diff --git a/pkg/iac/providers/github/branch_protections.go b/pkg/iac/providers/github/branch_protections.go index 5d9f59599457..6288fa55dec3 100755 --- a/pkg/iac/providers/github/branch_protections.go +++ b/pkg/iac/providers/github/branch_protections.go @@ -8,7 +8,3 @@ type BranchProtection struct { Metadata iacTypes.Metadata RequireSignedCommits iacTypes.BoolValue } - -func (b BranchProtection) RequiresSignedCommits() bool { - return b.RequireSignedCommits.IsTrue() -} diff --git a/pkg/iac/providers/github/repositories.go b/pkg/iac/providers/github/repositories.go index 0fc4c96080d2..1917f7721df7 100755 --- a/pkg/iac/providers/github/repositories.go +++ b/pkg/iac/providers/github/repositories.go @@ -10,7 +10,3 @@ type Repository struct { VulnerabilityAlerts iacTypes.BoolValue Archived iacTypes.BoolValue } - -func (r Repository) IsArchived() bool { - return r.Archived.IsTrue() -} diff --git a/pkg/iac/providers/google/dns/dns.go b/pkg/iac/providers/google/dns/dns.go index 93bc6fbb02c2..3ce557f2b17a 100755 --- a/pkg/iac/providers/google/dns/dns.go +++ b/pkg/iac/providers/google/dns/dns.go @@ -14,10 +14,6 @@ type ManagedZone struct { Visibility iacTypes.StringValue } -func (m ManagedZone) IsPrivate() bool { - return m.Visibility.EqualTo("private", iacTypes.IgnoreCase) -} - type DNSSec struct { Metadata iacTypes.Metadata Enabled iacTypes.BoolValue diff --git a/pkg/iac/providers/google/iam/iam.go b/pkg/iac/providers/google/iam/iam.go index 1711f8609bf0..83914dfd815c 100755 --- a/pkg/iac/providers/google/iam/iam.go +++ b/pkg/iac/providers/google/iam/iam.go @@ -50,11 +50,3 @@ type WorkloadIdentityPoolProvider struct { WorkloadIdentityPoolProviderId iacTypes.StringValue AttributeCondition iacTypes.StringValue } - -func (p *IAM) AllProjects() []Project { - return p.Projects -} - -func (p *IAM) AllFolders() []Folder { - return p.Folders -} diff --git a/pkg/iac/providers/google/sql/sql.go b/pkg/iac/providers/google/sql/sql.go index 672e78a42fbd..e3cc7061651d 100755 --- a/pkg/iac/providers/google/sql/sql.go +++ b/pkg/iac/providers/google/sql/sql.go @@ -1,8 +1,6 @@ package sql import ( - "strings" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) @@ -73,7 +71,3 @@ type IPConfiguration struct { CIDR iacTypes.StringValue } } - -func (i *DatabaseInstance) DatabaseFamily() string { - return strings.Split(i.DatabaseVersion.Value(), "_")[0] -} diff --git a/pkg/iac/rego/load.go b/pkg/iac/rego/load.go index 30520e8264f3..0151c6842d0f 100644 --- a/pkg/iac/rego/load.go +++ b/pkg/iac/rego/load.go @@ -197,13 +197,13 @@ func (s *Scanner) findMatchedEmbeddedCheck(badPolicy *ast.Module) *ast.Module { } badPolicyMeta, err := MetadataFromAnnotations(badPolicy) - if err != nil { + if err != nil || badPolicyMeta == nil { return nil } for _, embeddedCheck := range s.embeddedChecks { meta, err := MetadataFromAnnotations(embeddedCheck) - if err != nil { + if err != nil || meta == nil { continue } if badPolicyMeta.AVDID != "" && badPolicyMeta.AVDID == meta.AVDID { diff --git a/pkg/iac/rego/load_test.go b/pkg/iac/rego/load_test.go index fec5efe06257..1112115b64c3 100644 --- a/pkg/iac/rego/load_test.go +++ b/pkg/iac/rego/load_test.go @@ -205,7 +205,11 @@ deny { }`), } + originalFS := checks.EmbeddedPolicyFileSystem checks.EmbeddedPolicyFileSystem = embeddedChecksFS + t.Cleanup(func() { + checks.EmbeddedPolicyFileSystem = originalFS + }) err := scanner.LoadPolicies(fstest.MapFS(tt.files)) if tt.expectedErr != "" { @@ -253,3 +257,19 @@ deny { err := scanner.LoadPolicies(fsys) require.Error(t, err) } + +func TestFallback_CheckWithoutAnnotation(t *testing.T) { + fsys := fstest.MapFS{ + "check.rego": &fstest.MapFile{Data: []byte(`package builtin.test +import data.some_func +deny := some_func(input) +`)}, + } + scanner := rego.NewScanner( + rego.WithPolicyDirs("."), + rego.WithEmbeddedLibraries(false), + rego.WithPolicyFilesystem(fsys), + ) + err := scanner.LoadPolicies(nil) + require.NoError(t, err) +} diff --git a/pkg/iac/rego/metadata.go b/pkg/iac/rego/metadata.go index 53ed62796c81..fcc18298eeea 100644 --- a/pkg/iac/rego/metadata.go +++ b/pkg/iac/rego/metadata.go @@ -173,13 +173,13 @@ func (sm *StaticMetadata) updateAliases(meta map[string]any) { } } -func (m *StaticMetadata) hasAnyFramework(frameworks []framework.Framework) bool { +func (sm *StaticMetadata) hasAnyFramework(frameworks []framework.Framework) bool { if len(frameworks) == 0 { frameworks = []framework.Framework{framework.Default} } return slices.ContainsFunc(frameworks, func(fw framework.Framework) bool { - _, exists := m.Frameworks[fw] + _, exists := sm.Frameworks[fw] return exists }) } @@ -255,33 +255,33 @@ type SubType struct { Provider string // only for cloud } -func (m *StaticMetadata) ToRule() scan.Rule { +func (sm *StaticMetadata) ToRule() scan.Rule { provider := "generic" - if m.Provider != "" { - provider = m.Provider - } else if len(m.InputOptions.Selectors) > 0 { - provider = m.InputOptions.Selectors[0].Type + if sm.Provider != "" { + provider = sm.Provider + } else if len(sm.InputOptions.Selectors) > 0 { + provider = sm.InputOptions.Selectors[0].Type } return scan.Rule{ - Deprecated: m.Deprecated, - AVDID: m.AVDID, - Aliases: append(m.Aliases, m.ID), - ShortCode: m.ShortCode, - Summary: m.Title, - Explanation: m.Description, + Deprecated: sm.Deprecated, + AVDID: sm.AVDID, + Aliases: append(sm.Aliases, sm.ID), + ShortCode: sm.ShortCode, + Summary: sm.Title, + Explanation: sm.Description, Impact: "", - Resolution: m.RecommendedActions, + Resolution: sm.RecommendedActions, Provider: providers.Provider(provider), - Service: cmp.Or(m.Service, "general"), - Links: m.References, - Severity: severity.Severity(m.Severity), - RegoPackage: m.Package, - Frameworks: m.Frameworks, - CloudFormation: m.CloudFormation, - Terraform: m.Terraform, - Examples: m.Examples, + Service: cmp.Or(sm.Service, "general"), + Links: sm.References, + Severity: severity.Severity(sm.Severity), + RegoPackage: sm.Package, + Frameworks: sm.Frameworks, + CloudFormation: sm.CloudFormation, + Terraform: sm.Terraform, + Examples: sm.Examples, } } diff --git a/pkg/iac/rego/options.go b/pkg/iac/rego/options.go index 31026b2f3b57..45d1fb61ce28 100644 --- a/pkg/iac/rego/options.go +++ b/pkg/iac/rego/options.go @@ -115,7 +115,7 @@ func WithDisabledCheckIDs(ids ...string) options.ScannerOption { } } -func WithIncludeDeprecatedChecks(enabled bool) options.ScannerOption { +func WithIncludeDeprecatedChecks(_ bool) options.ScannerOption { return func(s options.ConfigurableScanner) { if ss, ok := s.(*Scanner); ok { ss.includeDeprecatedChecks = true diff --git a/pkg/iac/rego/scanner.go b/pkg/iac/rego/scanner.go index 7f0424089d8c..b9a0c0d554ef 100644 --- a/pkg/iac/rego/scanner.go +++ b/pkg/iac/rego/scanner.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "io/fs" + "slices" "strings" "github.com/open-policy-agent/opa/v1/ast" @@ -286,7 +287,17 @@ func checkSubtype(ii map[string]any, provider string, subTypes []SubType) bool { return false } +var sourcesWithExplicitSelectors = []types.Source{ + // apply terrafrom-specific checks only if selectors exist + types.SourceTerraformRaw, +} + func isPolicyApplicable(sourceType types.Source, staticMetadata *StaticMetadata, inputs ...Input) bool { + if len(staticMetadata.InputOptions.Selectors) == 0 && + slices.Contains(sourcesWithExplicitSelectors, sourceType) { + return false + } + if len(staticMetadata.InputOptions.Selectors) == 0 { // check always applies if no selectors return true } diff --git a/pkg/iac/rego/scanner_test.go b/pkg/iac/rego/scanner_test.go index 5443eec83316..90f80929189c 100644 --- a/pkg/iac/rego/scanner_test.go +++ b/pkg/iac/rego/scanner_test.go @@ -66,12 +66,12 @@ deny { func Test_RegoScanning_AbsolutePolicyPath_Deny(t *testing.T) { tmp := t.TempDir() - require.NoError(t, os.Mkdir(filepath.Join(tmp, "policies"), 0755)) + require.NoError(t, os.Mkdir(filepath.Join(tmp, "policies"), 0o755)) require.NoError(t, os.WriteFile(filepath.Join(tmp, "policies", "test.rego"), []byte(`package defsec.test deny { input.evil -}`), 0600)) +}`), 0o600)) srcFS := os.DirFS(tmp) diff --git a/pkg/iac/rego/schemas/00_schema.go b/pkg/iac/rego/schemas/00_schema.go index e6674912fe58..f98c5f8bda1e 100644 --- a/pkg/iac/rego/schemas/00_schema.go +++ b/pkg/iac/rego/schemas/00_schema.go @@ -19,4 +19,7 @@ var ( //go:embed cloud.json Cloud Schema + + //go:embed terraform-raw.json + TerraformRaw Schema ) diff --git a/pkg/iac/rego/schemas/schemas.go b/pkg/iac/rego/schemas/schemas.go index 73f92c10fc89..1b3fbbd24ac2 100644 --- a/pkg/iac/rego/schemas/schemas.go +++ b/pkg/iac/rego/schemas/schemas.go @@ -5,12 +5,13 @@ import ( ) var SchemaMap = map[types.Source]Schema{ - types.SourceDefsec: Cloud, - types.SourceCloud: Cloud, - types.SourceKubernetes: Kubernetes, - types.SourceRbac: Kubernetes, - types.SourceDockerfile: Dockerfile, - types.SourceTOML: Anything, - types.SourceYAML: Anything, - types.SourceJSON: Anything, + types.SourceDefsec: Cloud, + types.SourceCloud: Cloud, + types.SourceKubernetes: Kubernetes, + types.SourceRbac: Kubernetes, + types.SourceDockerfile: Dockerfile, + types.SourceTOML: Anything, + types.SourceYAML: Anything, + types.SourceJSON: Anything, + types.SourceTerraformRaw: TerraformRaw, } diff --git a/pkg/iac/rego/schemas/terraform-raw.json b/pkg/iac/rego/schemas/terraform-raw.json new file mode 100644 index 000000000000..1577f3bf3ed9 --- /dev/null +++ b/pkg/iac/rego/schemas/terraform-raw.json @@ -0,0 +1,137 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/aquasecurity/trivy/blob/main/pkg/iac/rego/schemas/terraform.json", + "type": "object", + "properties": { + "modules": { + "type": "array", + "items": { + "$ref": "#/$defs/Module" + }, + "description": "List of Terraform modules present in the configuration." + } + }, + "$defs": { + "Module": { + "type": "object", + "properties": { + "root_path": { + "type": "string", + "description": "The Terraform root directory of the project." + }, + "module_path": { + "type": "string", + "description": "Path to the current module. For remote modules, this is the path relative to the module's code directory." + }, + "blocks": { + "type": "array", + "items": { + "$ref": "#/$defs/Block" + }, + "description": "List of blocks (e.g., resource, data, variable) within the module." + } + } + }, + "Block": { + "type": "object", + "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/$defs/Metadata", + "description": "Metadata related to the block." + }, + "kind": { + "type": "string", + "description": "Kind of the block (e.g., resource, data, module)." + }, + "type": { + "type": "string", + "description": "Type of the block (e.g., aws_s3_bucket for a resource). For blocks that can only have 1 label (such as module or variable) this attribute will be empty." + }, + "name": { + "type": "string", + "description": "Name of the block defined by the user." + }, + "attributes": { + "type": "object", + "patternProperties": { + ".*": { + "$ref": "#/$defs/Attribute" + } + }, + "description": "Key-value attributes associated with the block." + } + } + }, + "Attribute": { + "type": "object", + "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/$defs/Metadata", + "description": "Metadata related to the attribute." + }, + "name": { + "type": "string", + "description": "Name of the attribute." + }, + "known": { + "type": "boolean", + "description": "Indicates whether the value of the attribute is known during analysis." + }, + "value": { + "description": "The actual value of the attribute. If unknown, then null. Can be a primitive, object, or array.", + "oneOf": [ + { "type": "null" }, + { "type": "string" }, + { "type": "number" }, + { "type": "boolean" }, + { "type": "object" }, + { "type": "array" } + ] + } + } + }, + "Metadata": { + "type": "object", + "properties": { + "filepath": { + "type": "string", + "description": "Path to the source file where the object is defined relative to the module's file system." + }, + "startline": { + "type": "number", + "description": "Line number where the object starts in the source file." + }, + "endline": { + "type": "number", + "description": "Line number where the object ends in the source file." + }, + "sourceprefix": { + "type": "string", + "description": "Module source. E.g. interface terraform-aws-modules/s3-bucket/aws" + }, + "managed": { + "type": "boolean", + "description": "Indicates whether the object is controlled by this source. Not relevant for Terraform." + }, + "explicit": { + "type": "boolean", + "description": "True if the object is explicitly defined by the user." + }, + "unresolvable": { + "type": "boolean", + "description": "True if the value cannot be determined statically." + }, + "fskey": { + "type": "string", + "description": "Internal filesystem key for uniquely identifying the object." + }, + "resource": { + "type": "string", + "description": "Fully qualified resource name if applicable. E.g. aws_s3_bucket.test[0]" + } + } + } + } +} diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_array_test.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_array_test.go index 0d373c6da241..538094c95de5 100644 --- a/pkg/iac/scanners/azure/arm/parser/armjson/parse_array_test.go +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_array_test.go @@ -23,7 +23,7 @@ func Test_Array_ToSlice(t *testing.T) { metadata := types.NewTestMetadata() require.NoError(t, Unmarshal(example, &target, &metadata)) assert.Len(t, target, 3) - assert.EqualValues(t, []int{1, 2, 3}, target) + assert.Equal(t, []int{1, 2, 3}, target) } func Test_Array_ToArray(t *testing.T) { @@ -32,7 +32,7 @@ func Test_Array_ToArray(t *testing.T) { metadata := types.NewTestMetadata() require.NoError(t, Unmarshal(example, &target, &metadata)) assert.Len(t, target, 3) - assert.EqualValues(t, [3]int{3, 2, 1}, target) + assert.Equal(t, [3]int{3, 2, 1}, target) } func Test_Array_ToInterface(t *testing.T) { diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/unmarshal.go b/pkg/iac/scanners/azure/arm/parser/armjson/unmarshal.go index b198bb7b7073..d077dc99a325 100644 --- a/pkg/iac/scanners/azure/arm/parser/armjson/unmarshal.go +++ b/pkg/iac/scanners/azure/arm/parser/armjson/unmarshal.go @@ -20,11 +20,8 @@ func Unmarshal(data []byte, target any, metadata *types.Metadata) error { if err != nil { return err } - if err := node.Decode(target); err != nil { - return err - } - return nil + return node.Decode(target) } func UnmarshalFromReader(r io.ReadSeeker, target any, metadata *types.Metadata) error { @@ -32,9 +29,6 @@ func UnmarshalFromReader(r io.ReadSeeker, target any, metadata *types.Metadata) if err != nil { return err } - if err := node.Decode(target); err != nil { - return err - } - return nil + return node.Decode(target) } diff --git a/pkg/iac/scanners/azure/arm/parser/parser_test.go b/pkg/iac/scanners/azure/arm/parser/parser_test.go index 05b06c384194..892f41baffa0 100644 --- a/pkg/iac/scanners/azure/arm/parser/parser_test.go +++ b/pkg/iac/scanners/azure/arm/parser/parser_test.go @@ -22,7 +22,6 @@ func createMetadata(targetFS fs.FS, filename string, start, end int, ref string, } func TestParser_Parse(t *testing.T) { - filename := "example.json" targetFS := memoryfs.New() @@ -49,7 +48,6 @@ func TestParser_Parse(t *testing.T) { "resources": [] }`, want: func() azure2.Deployment { - root := createMetadata(targetFS, filename, 0, 0, "", nil).WithInternal(resolver.NewResolver()) metadata := createMetadata(targetFS, filename, 1, 13, "", &root) parametersMetadata := createMetadata(targetFS, filename, 4, 11, "parameters", &metadata) @@ -120,7 +118,6 @@ func TestParser_Parse(t *testing.T) { ] }`, want: func() azure2.Deployment { - rootMetadata := createMetadata(targetFS, filename, 0, 0, "", nil).WithInternal(resolver.NewResolver()) fileMetadata := createMetadata(targetFS, filename, 1, 45, "", &rootMetadata) resourcesMetadata := createMetadata(targetFS, filename, 5, 44, "resources", &fileMetadata) @@ -199,8 +196,7 @@ func TestParser_Parse(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - - require.NoError(t, targetFS.WriteFile(filename, []byte(tt.input), 0644)) + require.NoError(t, targetFS.WriteFile(filename, []byte(tt.input), 0o644)) p := New(targetFS) got, err := p.ParseFS(t.Context(), ".") @@ -221,7 +217,6 @@ func TestParser_Parse(t *testing.T) { } func Test_NestedResourceParsing(t *testing.T) { - input := ` { "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", @@ -288,7 +283,7 @@ func Test_NestedResourceParsing(t *testing.T) { targetFS := memoryfs.New() - require.NoError(t, targetFS.WriteFile("nested.json", []byte(input), 0644)) + require.NoError(t, targetFS.WriteFile("nested.json", []byte(input), 0o644)) p := New(targetFS) got, err := p.ParseFS(t.Context(), ".") @@ -316,7 +311,7 @@ func Test_NestedResourceParsing(t *testing.T) { // // targetFS := memoryfs.New() // -// require.NoError(t, targetFS.WriteFile("postgres.json", input, 0644)) +// require.NoError(t, targetFS.WriteFile("postgres.json", input, 0o644)) // // p := New(targetFS, options.ParserWithDebug(os.Stderr)) // got, err := p.ParseFS(context.Background(), ".") diff --git a/pkg/iac/scanners/azure/arm/parser/template.go b/pkg/iac/scanners/azure/arm/parser/template.go index f7aa3253336b..1337262a4b15 100644 --- a/pkg/iac/scanners/azure/arm/parser/template.go +++ b/pkg/iac/scanners/azure/arm/parser/template.go @@ -57,13 +57,13 @@ type innerResource struct { Resources []Resource `json:"resources"` } -func (v *Resource) UnmarshalJSONWithMetadata(node armjson.Node) error { +func (r *Resource) UnmarshalJSONWithMetadata(node armjson.Node) error { - if err := node.Decode(&v.innerResource); err != nil { + if err := node.Decode(&r.innerResource); err != nil { return err } - v.Metadata = node.Metadata() + r.Metadata = node.Metadata() for _, comment := range node.Comments() { var str string diff --git a/pkg/iac/scanners/azure/functions/date_time_add.go b/pkg/iac/scanners/azure/functions/date_time_add.go index 2e3c06ae8220..39f88ddead80 100644 --- a/pkg/iac/scanners/azure/functions/date_time_add.go +++ b/pkg/iac/scanners/azure/functions/date_time_add.go @@ -63,11 +63,10 @@ func parseISO8601(from string) (Iso8601Duration, error) { var match []string var d Iso8601Duration - if pattern.MatchString(from) { - match = pattern.FindStringSubmatch(from) - } else { + if !pattern.MatchString(from) { return d, errors.New("could not parse duration string") } + match = pattern.FindStringSubmatch(from) for i, name := range pattern.SubexpNames() { part := match[i] diff --git a/pkg/iac/scanners/azure/functions/deployment.go b/pkg/iac/scanners/azure/functions/deployment.go index bde37b4915ff..caabf1f26c24 100644 --- a/pkg/iac/scanners/azure/functions/deployment.go +++ b/pkg/iac/scanners/azure/functions/deployment.go @@ -6,7 +6,7 @@ type DeploymentData interface { GetEnvVariable(envVariableName string) any } -func Deployment(deploymentProvider DeploymentData, args ...any) any { +func Deployment(_ DeploymentData, _ ...any) any { /* diff --git a/pkg/iac/scanners/azure/functions/false.go b/pkg/iac/scanners/azure/functions/false.go index 267cb936846e..926eda794b24 100644 --- a/pkg/iac/scanners/azure/functions/false.go +++ b/pkg/iac/scanners/azure/functions/false.go @@ -1,5 +1,5 @@ package functions -func False(args ...any) any { +func False(_ ...any) any { return false } diff --git a/pkg/iac/scanners/azure/functions/guid.go b/pkg/iac/scanners/azure/functions/guid.go index 203accc93292..b82612b3b082 100644 --- a/pkg/iac/scanners/azure/functions/guid.go +++ b/pkg/iac/scanners/azure/functions/guid.go @@ -39,6 +39,6 @@ func generateSeededGUID(seedParts ...string) (uuid.UUID, error) { return id, nil } -func NewGuid(args ...any) any { +func NewGuid(_ ...any) any { return uuid.NewString() } diff --git a/pkg/iac/scanners/azure/functions/intersection.go b/pkg/iac/scanners/azure/functions/intersection.go index b813fa77544e..590ebfaf68e7 100644 --- a/pkg/iac/scanners/azure/functions/intersection.go +++ b/pkg/iac/scanners/azure/functions/intersection.go @@ -4,7 +4,7 @@ import "sort" func Intersection(args ...any) any { - if args == nil || len(args) < 2 { + if len(args) < 2 { return []any{} } diff --git a/pkg/iac/scanners/azure/functions/items.go b/pkg/iac/scanners/azure/functions/items.go index 3f68d8a15ba7..6f4e3af77f8b 100644 --- a/pkg/iac/scanners/azure/functions/items.go +++ b/pkg/iac/scanners/azure/functions/items.go @@ -1,6 +1,5 @@ package functions -func Items(args ...any) any { - +func Items(_ ...any) any { return nil } diff --git a/pkg/iac/scanners/azure/functions/null.go b/pkg/iac/scanners/azure/functions/null.go index 21ccb9714b7c..363e23ebadbf 100644 --- a/pkg/iac/scanners/azure/functions/null.go +++ b/pkg/iac/scanners/azure/functions/null.go @@ -1,5 +1,5 @@ package functions -func Null(args ...any) any { +func Null(_ ...any) any { return nil } diff --git a/pkg/iac/scanners/azure/functions/resource.go b/pkg/iac/scanners/azure/functions/resource.go index 164fc6e42819..33dffbc9e0c2 100644 --- a/pkg/iac/scanners/azure/functions/resource.go +++ b/pkg/iac/scanners/azure/functions/resource.go @@ -32,7 +32,7 @@ func ExtensionResourceID(args ...any) any { return resourceID } -func ResourceGroup(args ...any) any { +func ResourceGroup(_ ...any) any { return fmt.Sprintf(`{ "id": "/subscriptions/%s/resourceGroups/PlaceHolderResourceGroup", "name": "Placeholder Resource Group", diff --git a/pkg/iac/scanners/azure/functions/true.go b/pkg/iac/scanners/azure/functions/true.go index 422b2beb58eb..cc15cd5a1443 100644 --- a/pkg/iac/scanners/azure/functions/true.go +++ b/pkg/iac/scanners/azure/functions/true.go @@ -1,5 +1,5 @@ package functions -func True(args ...any) any { +func True(_ ...any) any { return true } diff --git a/pkg/iac/scanners/cloudformation/parser/file_context.go b/pkg/iac/scanners/cloudformation/parser/file_context.go index e1c8cfa87f40..b1b2732eff20 100644 --- a/pkg/iac/scanners/cloudformation/parser/file_context.go +++ b/pkg/iac/scanners/cloudformation/parser/file_context.go @@ -42,7 +42,6 @@ func (t *FileContext) GetResourcesByType(names ...string) []*Resource { for _, r := range t.Resources { for _, name := range names { if name == r.Type() { - // resources = append(resources, r) } } @@ -56,6 +55,7 @@ func (t *FileContext) Metadata() iacTypes.Metadata { return iacTypes.NewMetadata(rng, NewCFReference("Template", rng).String()) } +// TODO: use map[string]string func (t *FileContext) overrideParameters(params map[string]any) { for key := range t.Parameters { if val, ok := params[key]; ok { @@ -76,7 +76,7 @@ func (t *FileContext) missingParameterValues() []string { func (t *FileContext) stripNullProperties() { for _, resource := range t.Resources { - resource.Inner.Properties = lo.OmitBy(resource.Inner.Properties, func(k string, v *Property) bool { + resource.properties = lo.OmitBy(resource.properties, func(_ string, v *Property) bool { return v.IsNil() }) } diff --git a/pkg/iac/scanners/cloudformation/parser/fn_and_test.go b/pkg/iac/scanners/cloudformation/parser/fn_and_test.go index 5222decef06f..a014583d5e1a 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_and_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_and_test.go @@ -7,35 +7,24 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" - "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_resolve_and_value(t *testing.T) { property1 := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Equals": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "foo", + }, + { + Type: cftypes.String, + Value: "foo", }, }, }, @@ -43,49 +32,33 @@ func Test_resolve_and_value(t *testing.T) { } property2 := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Equals": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "foo", + }, + { + Type: cftypes.String, + Value: "foo", }, }, }, }, } andProperty := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::And": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - property1, - property2, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::And": { + Type: cftypes.List, + Value: []*Property{ + property1, + property2, }, }, }, @@ -100,29 +73,19 @@ func Test_resolve_and_value(t *testing.T) { func Test_resolve_and_value_not_the_same(t *testing.T) { property1 := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Equals": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "bar", - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "foo", + }, + { + Type: cftypes.String, + Value: "bar", }, }, }, @@ -130,49 +93,33 @@ func Test_resolve_and_value_not_the_same(t *testing.T) { } property2 := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Equals": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "foo", + }, + { + Type: cftypes.String, + Value: "foo", }, }, }, }, } andProperty := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::And": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - property1, - property2, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::And": { + Type: cftypes.List, + Value: []*Property{ + property1, + property2, }, }, }, diff --git a/pkg/iac/scanners/cloudformation/parser/fn_base64_test.go b/pkg/iac/scanners/cloudformation/parser/fn_base64_test.go index 7fdc77b30fa5..71a2ba2bdfae 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_base64_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_base64_test.go @@ -7,24 +7,17 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" - "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_resolve_base64_value(t *testing.T) { property := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Base64": { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "HelloWorld", - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Base64": { + Type: cftypes.String, + Value: "HelloWorld", }, }, } diff --git a/pkg/iac/scanners/cloudformation/parser/fn_builtin_test.go b/pkg/iac/scanners/cloudformation/parser/fn_builtin_test.go index c5fbce41b489..bbfa1cbeb32b 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_builtin_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_builtin_test.go @@ -13,10 +13,8 @@ func Test_cidr_generator(t *testing.T) { ctx: nil, name: "cidr", comment: "", - Inner: PropertyInner{ - Type: "", - Value: nil, - }, + Type: "", + Value: nil, } ranges, err := calculateCidrs("10.1.0.0/16", 4, 4, original) @@ -40,10 +38,8 @@ func Test_cidr_generator_8_bits(t *testing.T) { ctx: nil, name: "cidr", comment: "", - Inner: PropertyInner{ - Type: "", - Value: nil, - }, + Type: "", + Value: nil, } ranges, err := calculateCidrs("10.1.0.0/16", 4, 8, original) diff --git a/pkg/iac/scanners/cloudformation/parser/fn_condition_test.go b/pkg/iac/scanners/cloudformation/parser/fn_condition_test.go index c5f0af721c36..d4150dcee654 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_condition_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_condition_test.go @@ -14,78 +14,56 @@ func Test_resolve_condition_value(t *testing.T) { fctx := new(FileContext) fctx.Conditions = map[string]Property{ "SomeCondition": { - ctx: fctx, - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Equals": { - ctx: fctx, - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "some val", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "some val", - }, - }, - }, + ctx: fctx, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + ctx: fctx, + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "some val", + }, + { + Type: cftypes.String, + Value: "some val", }, }, }, }, }, "EnableVersioning": { - ctx: fctx, - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Condition": { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "SomeCondition", - }, - }, + ctx: fctx, + Type: cftypes.Map, + Value: map[string]*Property{ + "Condition": { + Type: cftypes.String, + Value: "SomeCondition", }, }, }, } property := &Property{ - ctx: fctx, - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::If": { - ctx: fctx, - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "EnableVersioning", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "Enabled", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "Suspended", - }, - }, - }, + ctx: fctx, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::If": { + ctx: fctx, + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "EnableVersioning", + }, + { + Type: cftypes.String, + Value: "Enabled", + }, + { + Type: cftypes.String, + Value: "Suspended", }, }, }, diff --git a/pkg/iac/scanners/cloudformation/parser/fn_equals_test.go b/pkg/iac/scanners/cloudformation/parser/fn_equals_test.go index 0e550d162986..a83dc049d7ec 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_equals_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_equals_test.go @@ -7,35 +7,24 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" - "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_resolve_equals_value(t *testing.T) { property := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Equals": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "foo", + }, + { + Type: cftypes.String, + Value: "foo", }, }, }, @@ -51,29 +40,19 @@ func Test_resolve_equals_value(t *testing.T) { func Test_resolve_equals_value_to_false(t *testing.T) { property := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Equals": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "bar", - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "foo", + }, + { + Type: cftypes.String, + Value: "bar", }, }, }, @@ -89,29 +68,19 @@ func Test_resolve_equals_value_to_false(t *testing.T) { func Test_resolve_equals_value_to_true_when_boolean(t *testing.T) { property := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Equals": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.Bool, - Value: true, - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.Bool, - Value: true, - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.Bool, + Value: true, + }, + { + Type: cftypes.Bool, + Value: true, }, }, }, @@ -127,45 +96,33 @@ func Test_resolve_equals_value_when_one_is_a_reference(t *testing.T) { property := &Property{ name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Equals": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "staging", - }, - }, - { - ctx: &FileContext{ - filepath: "", - Parameters: map[string]*Parameter{ - "Environment": { - inner: parameterInner{ - Type: "string", - Default: "staging", - }, - }, - }, - }, - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Ref": { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "Environment", - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "staging", + }, + { + ctx: &FileContext{ + Parameters: map[string]*Parameter{ + "Environment": { + inner: parameterInner{ + Type: "string", + Default: "staging", }, }, }, }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Ref": { + Type: cftypes.String, + Value: "Environment", + }, + }, }, }, }, diff --git a/pkg/iac/scanners/cloudformation/parser/fn_find_in_map.go b/pkg/iac/scanners/cloudformation/parser/fn_find_in_map.go index b379cba3527e..29af01adc3d9 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_find_in_map.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_find_in_map.go @@ -37,9 +37,9 @@ func ResolveFindInMap(property *Property) (resolved *Property, success bool) { mapValues := k.(map[string]any) - if prop, ok := mapValues[secondaryLevelKey]; !ok { + prop, ok := mapValues[secondaryLevelKey] + if !ok { return abortIntrinsic(property, "could not find a value for %s in %s, returning original Property", secondaryLevelKey, topLevelKey) - } else { - return property.deriveResolved(cftypes.String, prop), true } + return property.deriveResolved(cftypes.String, prop), true } diff --git a/pkg/iac/scanners/cloudformation/parser/fn_get_attr.go b/pkg/iac/scanners/cloudformation/parser/fn_get_attr.go index f6754d16a9b3..d3deb3fd49c9 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_get_attr.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_get_attr.go @@ -42,5 +42,5 @@ func ResolveGetAtt(property *Property) (resolved *Property, success bool) { return property.deriveResolved(cftypes.String, referencedResource.ID()), true } - return property.deriveResolved(referencedProperty.Type(), referencedProperty.RawValue()), true + return property.deriveResolved(referencedProperty.Type, referencedProperty.RawValue()), true } diff --git a/pkg/iac/scanners/cloudformation/parser/fn_if.go b/pkg/iac/scanners/cloudformation/parser/fn_if.go index d444952ff38a..97becbc90e21 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_if.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_if.go @@ -34,7 +34,6 @@ func ResolveIf(property *Property) (resolved *Property, success bool) { if conditionMet { return trueState, true - } else { - return falseState, true } + return falseState, true } diff --git a/pkg/iac/scanners/cloudformation/parser/fn_if_test.go b/pkg/iac/scanners/cloudformation/parser/fn_if_test.go index 4ecd5f9483c8..f4043adcb597 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_if_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_if_test.go @@ -7,41 +7,28 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" - "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_resolve_if_value(t *testing.T) { property := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::If": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.Bool, - Value: true, - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "bar", - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::If": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.Bool, + Value: true, + }, + { + Type: cftypes.String, + Value: "foo", + }, + { + Type: cftypes.String, + Value: "bar", }, }, }, diff --git a/pkg/iac/scanners/cloudformation/parser/fn_join_test.go b/pkg/iac/scanners/cloudformation/parser/fn_join_test.go index 628ade9a1acf..74df00f4f64e 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_join_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_join_test.go @@ -7,52 +7,35 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" - "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_resolve_join_value(t *testing.T) { property := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Join": { - Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Join": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "::", + }, + { Type: cftypes.List, Value: []*Property{ { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "::", - }, + Type: cftypes.String, + Value: "s3", }, { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "s3", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "part1", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "part2", - }, - }, - }, - }, + Type: cftypes.String, + Value: "part1", + }, + { + Type: cftypes.String, + Value: "part2", }, }, }, @@ -70,7 +53,6 @@ func Test_resolve_join_value_with_reference(t *testing.T) { property := &Property{ ctx: &FileContext{ - filepath: "", Parameters: map[string]*Parameter{ "Environment": { inner: parameterInner{ @@ -81,62 +63,44 @@ func Test_resolve_join_value_with_reference(t *testing.T) { }, }, name: "EnvironmentBucket", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Join": { - Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Join": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "::", + }, + { Type: cftypes.List, Value: []*Property{ { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "::", - }, + Type: cftypes.String, + Value: "s3", }, { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "s3", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "part1", - }, - }, - { - ctx: &FileContext{ - filepath: "", - Parameters: map[string]*Parameter{ - "Environment": { - inner: parameterInner{ - Type: "string", - Default: "staging", - }, - }, - }, - }, - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Ref": { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "Environment", - }, - }, - }, + Type: cftypes.String, + Value: "part1", + }, + { + ctx: &FileContext{ + Parameters: map[string]*Parameter{ + "Environment": { + inner: parameterInner{ + Type: "string", + Default: "staging", }, }, }, }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Ref": { + Type: cftypes.String, + Value: "Environment", + }, + }, }, }, }, diff --git a/pkg/iac/scanners/cloudformation/parser/fn_length_test.go b/pkg/iac/scanners/cloudformation/parser/fn_length_test.go index 402211bdcd98..252a2026ffb7 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_length_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_length_test.go @@ -10,26 +10,18 @@ import ( func Test_ResolveLength_WhenPropIsArray(t *testing.T) { prop := &Property{ - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Length": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.Int, - Value: 1, - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "IntParameter", - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Length": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.Int, + Value: 1, + }, + { + Type: cftypes.String, + Value: "IntParameter", }, }, }, @@ -53,37 +45,25 @@ func Test_ResolveLength_WhenPropIsIntrinsicFunction(t *testing.T) { }, } prop := &Property{ - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Length": { - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Split": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "|", - }, - }, - { - ctx: fctx, - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Ref": { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "SomeParameter", - }, - }, - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Length": { + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Split": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "|", + }, + { + ctx: fctx, + Type: cftypes.Map, + Value: map[string]*Property{ + "Ref": { + Type: cftypes.String, + Value: "SomeParameter", }, }, }, diff --git a/pkg/iac/scanners/cloudformation/parser/fn_not_test.go b/pkg/iac/scanners/cloudformation/parser/fn_not_test.go index 563964c32290..88c673bfdec0 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_not_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_not_test.go @@ -7,34 +7,23 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" - "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_resolve_not_value(t *testing.T) { property1 := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Equals": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "bar", - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "foo", + }, + { + Type: cftypes.String, + Value: "bar", }, }, }, @@ -42,19 +31,13 @@ func Test_resolve_not_value(t *testing.T) { } notProperty := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Not": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - property1, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Not": { + Type: cftypes.List, + Value: []*Property{ + property1, }, }, }, @@ -68,29 +51,19 @@ func Test_resolve_not_value(t *testing.T) { func Test_resolve_not_value_when_true(t *testing.T) { property1 := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Equals": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "foo", + }, + { + Type: cftypes.String, + Value: "foo", }, }, }, @@ -98,19 +71,13 @@ func Test_resolve_not_value_when_true(t *testing.T) { } notProperty := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Not": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - property1, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Not": { + Type: cftypes.List, + Value: []*Property{ + property1, }, }, }, diff --git a/pkg/iac/scanners/cloudformation/parser/fn_or_test.go b/pkg/iac/scanners/cloudformation/parser/fn_or_test.go index 7e0e1dcf0f0a..69ede90c5f08 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_or_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_or_test.go @@ -7,34 +7,23 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" - "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_resolve_or_value(t *testing.T) { property1 := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Equals": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "bar", - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "foo", + }, + { + Type: cftypes.String, + Value: "bar", }, }, }, @@ -42,49 +31,33 @@ func Test_resolve_or_value(t *testing.T) { } property2 := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Equals": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "foo", + }, + { + Type: cftypes.String, + Value: "foo", }, }, }, }, } orProperty := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Or": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - property1, - property2, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Or": { + Type: cftypes.List, + Value: []*Property{ + property1, + property2, }, }, }, @@ -98,29 +71,19 @@ func Test_resolve_or_value(t *testing.T) { func Test_resolve_or_value_when_neither_true(t *testing.T) { property1 := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Equals": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "bar", - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "foo", + }, + { + Type: cftypes.String, + Value: "bar", }, }, }, @@ -128,49 +91,33 @@ func Test_resolve_or_value_when_neither_true(t *testing.T) { } property2 := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Equals": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "bar", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "bar", + }, + { + Type: cftypes.String, + Value: "foo", }, }, }, }, } orProperty := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Or": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - property1, - property2, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Or": { + Type: cftypes.List, + Value: []*Property{ + property1, + property2, }, }, }, diff --git a/pkg/iac/scanners/cloudformation/parser/fn_ref_test.go b/pkg/iac/scanners/cloudformation/parser/fn_ref_test.go index 7f0e141b96a6..7eca1418fb77 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_ref_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_ref_test.go @@ -14,7 +14,6 @@ func Test_resolve_referenced_value(t *testing.T) { property := &Property{ ctx: &FileContext{ - filepath: "", Parameters: map[string]*Parameter{ "BucketName": { inner: parameterInner{ @@ -25,16 +24,11 @@ func Test_resolve_referenced_value(t *testing.T) { }, }, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Ref": { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "BucketName", - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Ref": { + Type: cftypes.String, + Value: "BucketName", }, }, } @@ -48,15 +42,10 @@ func Test_resolve_referenced_value(t *testing.T) { func Test_property_value_correct_when_not_reference(t *testing.T) { property := &Property{ - ctx: &FileContext{ - filepath: "", - }, - name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.String, - Value: "someBucketName", - }, + name: "BucketName", + rng: types.NewRange("testfile", 1, 1, "", nil), + Type: cftypes.String, + Value: "someBucketName", } // should fail when trying to resolve function that is not in fact a function diff --git a/pkg/iac/scanners/cloudformation/parser/fn_select.go b/pkg/iac/scanners/cloudformation/parser/fn_select.go index c528223a2325..195db83802c6 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_select.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_select.go @@ -19,12 +19,10 @@ func ResolveSelect(property *Property) (resolved *Property, success bool) { list := refValue[1] if index.IsNotInt() { - if index.IsConvertableTo(cftypes.Int) { - // - index = index.ConvertTo(cftypes.Int) - } else { + if !index.IsConvertableTo(cftypes.Int) { return abortIntrinsic(property, "index on property [%s] should be an int, returning original Property", property.name) } + index = index.ConvertTo(cftypes.Int) } if list.IsNotList() { diff --git a/pkg/iac/scanners/cloudformation/parser/fn_split_test.go b/pkg/iac/scanners/cloudformation/parser/fn_split_test.go index 33ca111c15ea..2cc79c8fbf27 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_split_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_split_test.go @@ -7,7 +7,6 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" - "github.com/aquasecurity/trivy/pkg/iac/types" ) /* @@ -18,29 +17,19 @@ import ( func Test_resolve_split_value(t *testing.T) { property := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Split": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "::", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "s3::bucket::to::split", - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Split": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "::", + }, + { + Type: cftypes.String, + Value: "s3::bucket::to::split", }, }, }, diff --git a/pkg/iac/scanners/cloudformation/parser/fn_sub.go b/pkg/iac/scanners/cloudformation/parser/fn_sub.go index ad990bba3b32..8664f406f285 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_sub.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_sub.go @@ -37,7 +37,7 @@ func resolveMapSub(refValue, original *Property) (*Property, bool) { for k, v := range components { replacement := "[failed to resolve]" - switch v.Type() { + switch v.Type { case cftypes.Map: resolved, _ := ResolveIntrinsicFunc(v) replacement = resolved.AsString() diff --git a/pkg/iac/scanners/cloudformation/parser/parameter.go b/pkg/iac/scanners/cloudformation/parser/parameter.go index 4cfdfd1705b6..5efa72651412 100644 --- a/pkg/iac/scanners/cloudformation/parser/parameter.go +++ b/pkg/iac/scanners/cloudformation/parser/parameter.go @@ -1,20 +1,22 @@ package parser import ( - "bytes" - "encoding/json" "errors" "fmt" + "io" + "maps" "strconv" "strings" + "github.com/go-json-experiment/json" + "github.com/go-json-experiment/json/jsontext" "gopkg.in/yaml.v3" - "github.com/aquasecurity/jfather" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" ) type Parameter struct { + // TODO: remove inner inner parameterInner } @@ -27,27 +29,34 @@ func (p *Parameter) UnmarshalYAML(node *yaml.Node) error { return node.Decode(&p.inner) } -func (p *Parameter) UnmarshalJSONWithMetadata(node jfather.Node) error { +func (p *Parameter) UnmarshalJSONFrom(dec *jsontext.Decoder) error { var inner parameterInner - if err := node.Decode(&inner); err != nil { + if err := json.UnmarshalDecode(dec, &inner, + json.WithUnmarshalers(json.UnmarshalFromFunc(unmarshalIntFirst)), + ); err != nil { return err } - // jfather parses Number without fraction as int64 - // https://github.com/liamg/jfather/blob/4ef05d70c05af167226d3333a4ec7d8ac3c9c281/parse_number.go#L33-L42 - switch v := inner.Default.(type) { - case int64: - inner.Default = int(v) - default: - inner.Default = v - } - p.inner = inner return nil } +func unmarshalIntFirst(dec *jsontext.Decoder, v *any) error { + if dec.PeekKind() == '0' { + if jval, err := dec.ReadValue(); err != nil { + return err + } else if v1, err := strconv.ParseInt(string(jval), 10, 64); err == nil { + *v = int(v1) + } else if v1, err := strconv.ParseFloat(string(jval), 64); err == nil { + *v = v1 + } + return nil + } + return json.SkipFunc +} + func (p *Parameter) Type() cftypes.CfType { switch p.inner.Type { case "Boolean": @@ -83,35 +92,34 @@ func (p *Parameter) UpdateDefault(inVal any) { type Parameters map[string]any func (p *Parameters) Merge(other Parameters) { - for k, v := range other { - (*p)[k] = v - } + maps.Copy((*p), other) } -func (p *Parameters) UnmarshalJSON(data []byte) error { +func (p *Parameters) UnmarshalJSONFrom(d *jsontext.Decoder) error { (*p) = make(Parameters) - if len(data) == 0 { - return nil - } - - switch { - case data[0] == '{' && data[len(data)-1] == '}': // object + switch d.PeekKind() { + case '{': // CodePipeline like format var params struct { Params map[string]any `json:"Parameters"` } - if err := json.Unmarshal(data, ¶ms); err != nil { + if err := json.UnmarshalDecode(d, ¶ms); err != nil { return err } (*p) = params.Params - case data[0] == '[' && data[len(data)-1] == ']': // array + case '[': // Original format var params []string - if err := json.Unmarshal(data, ¶ms); err == nil { + jval, err := d.ReadValue() + if err != nil { + return err + } + + if err := json.Unmarshal(jval, ¶ms); err == nil { for _, param := range params { parts := strings.Split(param, "=") if len(parts) != 2 { @@ -128,9 +136,7 @@ func (p *Parameters) UnmarshalJSON(data []byte) error { ParameterValue string `json:"ParameterValue"` } - d := json.NewDecoder(bytes.NewReader(data)) - d.DisallowUnknownFields() - if err := d.Decode(&cfparams); err != nil { + if err := json.Unmarshal(jval, &cfparams, json.RejectUnknownMembers(true)); err != nil { return err } @@ -143,3 +149,11 @@ func (p *Parameters) UnmarshalJSON(data []byte) error { return nil } + +func ParseParameters(r io.Reader) (Parameters, error) { + var parameters Parameters + if err := json.UnmarshalRead(r, ¶meters); err != nil { + return nil, err + } + return parameters, nil +} diff --git a/pkg/iac/scanners/cloudformation/parser/parameters_test.go b/pkg/iac/scanners/cloudformation/parser/parameters_test.go index 703f07f5fe12..320ba9bc6d04 100644 --- a/pkg/iac/scanners/cloudformation/parser/parameters_test.go +++ b/pkg/iac/scanners/cloudformation/parser/parameters_test.go @@ -1,7 +1,7 @@ package parser import ( - "encoding/json" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -75,9 +75,7 @@ func TestParameters_UnmarshalJSON(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var params Parameters - - err := json.Unmarshal([]byte(tt.source), ¶ms) + params, err := ParseParameters(strings.NewReader(tt.source)) if tt.wantErr { require.Error(t, err) return diff --git a/pkg/iac/scanners/cloudformation/parser/parser.go b/pkg/iac/scanners/cloudformation/parser/parser.go index eda88408ec06..8ac14b32706a 100644 --- a/pkg/iac/scanners/cloudformation/parser/parser.go +++ b/pkg/iac/scanners/cloudformation/parser/parser.go @@ -2,7 +2,6 @@ package parser import ( "context" - "encoding/json" "fmt" "io" "io/fs" @@ -13,9 +12,9 @@ import ( "github.com/hashicorp/go-multierror" "gopkg.in/yaml.v3" - "github.com/aquasecurity/jfather" "github.com/aquasecurity/trivy/pkg/iac/ignore" "github.com/aquasecurity/trivy/pkg/log" + xjson "github.com/aquasecurity/trivy/pkg/x/json" ) type Parser struct { @@ -136,7 +135,7 @@ func (p *Parser) ParseFile(ctx context.Context, fsys fs.FS, filePath string) (fc } fctx.Ignores = ignore.Parse(string(content), filePath, "") case JsonSourceFormat: - if err := jfather.Unmarshal(content, fctx); err != nil { + if err := xjson.Unmarshal(content, fctx); err != nil { return nil, NewErrInvalidContent(filePath, err) } } @@ -176,11 +175,18 @@ func (p *Parser) parseParams() error { var errs error for _, path := range p.parameterFiles { - if parameters, err := p.parseParametersFile(path); err != nil { + f, err := p.configsFS.Open(path) + if err != nil { + errs = multierror.Append(errs, fmt.Errorf("open file: %w", err)) + continue + } + + if parameters, err := ParseParameters(f); err != nil { errs = multierror.Append(errs, err) } else { params.Merge(parameters) } + _ = f.Close() } if errs != nil { @@ -192,16 +198,3 @@ func (p *Parser) parseParams() error { p.overridedParameters = params return nil } - -func (p *Parser) parseParametersFile(filePath string) (Parameters, error) { - f, err := p.configsFS.Open(filePath) - if err != nil { - return nil, fmt.Errorf("parameters file %q open error: %w", filePath, err) - } - - var parameters Parameters - if err := json.NewDecoder(f).Decode(¶meters); err != nil { - return nil, err - } - return parameters, nil -} diff --git a/pkg/iac/scanners/cloudformation/parser/parser_test.go b/pkg/iac/scanners/cloudformation/parser/parser_test.go index 4242f02a7678..8cc8031e8d97 100644 --- a/pkg/iac/scanners/cloudformation/parser/parser_test.go +++ b/pkg/iac/scanners/cloudformation/parser/parser_test.go @@ -14,13 +14,12 @@ import ( func parseFile(t *testing.T, source, name string) (FileContexts, error) { tmp := t.TempDir() - require.NoError(t, os.WriteFile(filepath.Join(tmp, name), []byte(source), 0600)) + require.NoError(t, os.WriteFile(filepath.Join(tmp, name), []byte(source), 0o600)) fs := os.DirFS(tmp) return New().ParseFS(t.Context(), fs, ".") } func Test_parse_yaml(t *testing.T) { - source := `--- Parameters: BucketName: @@ -98,7 +97,6 @@ func Test_parse_json(t *testing.T) { } func Test_parse_yaml_with_map_ref(t *testing.T) { - source := `--- Parameters: BucketName: @@ -135,7 +133,6 @@ Resources: } func Test_parse_yaml_with_intrinsic_functions(t *testing.T) { - source := `--- Parameters: BucketName: @@ -229,7 +226,6 @@ Resources: } func TestParse_WithParameters(t *testing.T) { - fs := testutil.CreateFS(t, map[string]string{ "main.yaml": `AWSTemplateFormatVersion: 2010-09-09 Parameters: diff --git a/pkg/iac/scanners/cloudformation/parser/property.go b/pkg/iac/scanners/cloudformation/parser/property.go index 606b9d28b341..c05e443dac9e 100644 --- a/pkg/iac/scanners/cloudformation/parser/property.go +++ b/pkg/iac/scanners/cloudformation/parser/property.go @@ -1,16 +1,19 @@ package parser import ( - "encoding/json" + "fmt" "io/fs" + "reflect" "strconv" "strings" + "github.com/go-json-experiment/json" + "github.com/go-json-experiment/json/jsontext" "gopkg.in/yaml.v3" - "github.com/aquasecurity/jfather" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + xjson "github.com/aquasecurity/trivy/pkg/x/json" ) type EqualityOptions = int @@ -20,28 +23,25 @@ const ( ) type Property struct { + xjson.Location ctx *FileContext + Type cftypes.CfType + Value any `json:"Value" yaml:"Value"` name string comment string rng iacTypes.Range parentRange iacTypes.Range - Inner PropertyInner logicalId string unresolved bool } -type PropertyInner struct { - Type cftypes.CfType - Value any `json:"Value" yaml:"Value"` -} - func (p *Property) Comment() string { return p.comment } func (p *Property) setName(name string) { p.name = name - if p.Type() == cftypes.Map { + if p.Type == cftypes.Map { for n, subProp := range p.AsMap() { if subProp == nil { continue @@ -71,10 +71,10 @@ func (p *Property) setContext(ctx *FileContext) { } func (p *Property) setFileAndParentRange(target fs.FS, filepath string, parentRange iacTypes.Range) { - p.rng = iacTypes.NewRange(filepath, p.rng.GetStartLine(), p.rng.GetEndLine(), p.rng.GetSourcePrefix(), target) + p.rng = iacTypes.NewRange(filepath, p.StartLine, p.EndLine, p.rng.GetSourcePrefix(), target) p.parentRange = parentRange - switch p.Type() { + switch p.Type { case cftypes.Map: for _, subProp := range p.AsMap() { if subProp == nil { @@ -93,27 +93,68 @@ func (p *Property) setFileAndParentRange(target fs.FS, filepath string, parentRa } func (p *Property) UnmarshalYAML(node *yaml.Node) error { - p.rng = iacTypes.NewRange("", node.Line, calculateEndLine(node), "", nil) - + p.StartLine = node.Line + p.EndLine = calculateEndLine(node) p.comment = node.LineComment - return setPropertyValueFromYaml(node, &p.Inner) + return setPropertyValueFromYaml(node, p) } -func (p *Property) UnmarshalJSONWithMetadata(node jfather.Node) error { - p.rng = iacTypes.NewRange("", node.Range().Start.Line, node.Range().End.Line, "", nil) - return setPropertyValueFromJson(node, &p.Inner) -} +func (p *Property) UnmarshalJSONFrom(dec *jsontext.Decoder) error { + var valPtr any + var nodeType cftypes.CfType -func (p *Property) Type() cftypes.CfType { - return p.Inner.Type + switch k := dec.PeekKind(); k { + case 't', 'f': + valPtr = new(bool) + nodeType = cftypes.Bool + case '"': + valPtr = new(string) + nodeType = cftypes.String + case '0': + return p.parseNumericValue(dec) + case '[', 'n': + valPtr = new([]*Property) + nodeType = cftypes.List + case '{': + valPtr = new(map[string]*Property) + nodeType = cftypes.Map + case 0: + return dec.SkipValue() + default: + return fmt.Errorf("unexpected token kind %q at %d", k.String(), dec.InputOffset()) + } + + if err := json.UnmarshalDecode(dec, valPtr); err != nil { + return err + } + + p.Value = reflect.ValueOf(valPtr).Elem().Interface() + p.Type = nodeType + return nil } -func (p *Property) Range() iacTypes.Range { - return p.rng +func (p *Property) parseNumericValue(dec *jsontext.Decoder) error { + raw, err := dec.ReadValue() + if err != nil { + return err + } + strVal := string(raw) + + if v, err := strconv.ParseInt(strVal, 10, 64); err == nil { + p.Value = int(v) + p.Type = cftypes.Int + return nil + } + if v, err := strconv.ParseFloat(strVal, 64); err == nil { + p.Value = v + p.Type = cftypes.Float64 + return nil + } + return fmt.Errorf("invalid numeric value: %q", strVal) } func (p *Property) Metadata() iacTypes.Metadata { - return iacTypes.NewMetadata(p.Range(), p.name). + return iacTypes.NewMetadata(p.rng, p.name). WithParent(iacTypes.NewMetadata(p.parentRange, p.logicalId)) } @@ -121,7 +162,7 @@ func (p *Property) isFunction() bool { if p == nil { return false } - if p.Type() == cftypes.Map { + if p.Type == cftypes.Map { for n := range p.AsMap() { return IsIntrinsic(n) } @@ -130,11 +171,10 @@ func (p *Property) isFunction() bool { } func (p *Property) RawValue() any { - return p.Inner.Value + return p.Value } func (p *Property) AsRawStrings() ([]string, error) { - if len(p.ctx.lines) < p.rng.GetEndLine() { return p.ctx.lines, nil } @@ -225,7 +265,6 @@ func (p *Property) IntDefault(defaultValue int) iacTypes.IntValue { } func (p *Property) GetProperty(path string) *Property { - pathParts := strings.Split(path, ".") first := pathParts[0] @@ -254,9 +293,8 @@ func (p *Property) GetProperty(path string) *Property { if nestedProperty.isFunction() { resolved, _ := nestedProperty.resolveValue() return resolved - } else { - return nestedProperty } + return nestedProperty } return &Property{} @@ -264,16 +302,15 @@ func (p *Property) GetProperty(path string) *Property { func (p *Property) deriveResolved(propType cftypes.CfType, propValue any) *Property { return &Property{ + Location: p.Location, + Value: propValue, + Type: propType, ctx: p.ctx, name: p.name, comment: p.comment, rng: p.rng, parentRange: p.parentRange, logicalId: p.logicalId, - Inner: PropertyInner{ - Type: propType, - Value: propValue, - }, } } @@ -317,7 +354,7 @@ func (p *Property) inferBool(prop *Property, defaultValue bool) iacTypes.BoolVal func (p *Property) String() string { r := "" - switch p.Type() { + switch p.Type { case cftypes.String: r = p.AsString() case cftypes.Int: @@ -326,7 +363,7 @@ func (p *Property) String() string { return r } -func (p *Property) SetLogicalResource(id string) { +func (p *Property) setLogicalResource(id string) { p.logicalId = id if p.isFunction() { @@ -338,16 +375,15 @@ func (p *Property) SetLogicalResource(id string) { if subProp == nil { continue } - subProp.SetLogicalResource(id) + subProp.setLogicalResource(id) } } if p.IsList() { for _, subProp := range p.AsList() { - subProp.SetLogicalResource(id) + subProp.setLogicalResource(id) } } - } func (p *Property) GetJsonBytes(squashList ...bool) []byte { @@ -416,9 +452,9 @@ func convert(input any) any { } func (p *Property) inferType() { - typ := cftypes.TypeFromGoValue(p.Inner.Value) + typ := cftypes.TypeFromGoValue(p.Value) if typ == cftypes.Unknown { return } - p.Inner.Type = typ + p.Type = typ } diff --git a/pkg/iac/scanners/cloudformation/parser/property_conversion.go b/pkg/iac/scanners/cloudformation/parser/property_conversion.go index 1053afa1b3f7..66a3ff8f0f23 100644 --- a/pkg/iac/scanners/cloudformation/parser/property_conversion.go +++ b/pkg/iac/scanners/cloudformation/parser/property_conversion.go @@ -26,7 +26,7 @@ func (p *Property) IsConvertableTo(conversionType cftypes.CfType) bool { } func (p *Property) isConvertableToString() bool { - switch p.Type() { + switch p.Type { case cftypes.Map: return false case cftypes.List: @@ -40,7 +40,7 @@ func (p *Property) isConvertableToString() bool { } func (p *Property) isConvertableToBool() bool { - switch p.Type() { + switch p.Type { case cftypes.String: return p.EqualTo("true", IgnoreCase) || p.EqualTo("false", IgnoreCase) || p.EqualTo("1", IgnoreCase) || p.EqualTo("0", IgnoreCase) @@ -54,7 +54,7 @@ func (p *Property) isConvertableToBool() bool { } func (p *Property) isConvertableToInt() bool { - switch p.Type() { + switch p.Type { case cftypes.String: if _, err := strconv.Atoi(p.AsString()); err == nil { return true @@ -70,15 +70,15 @@ func (p *Property) ConvertTo(conversionType cftypes.CfType) *Property { return nil } - if p.Type() == conversionType { + if p.Type == conversionType { return p } if !p.IsConvertableTo(conversionType) { log.Debug("Failed to convert property", - log.String("from", string(p.Type())), + log.String("from", string(p.Type)), log.String("to", string(conversionType)), - log.Any("range", p.Range().String()), + log.Any("range", p.rng.String()), ) return p } @@ -94,7 +94,7 @@ func (p *Property) ConvertTo(conversionType cftypes.CfType) *Property { } func (p *Property) convertToString() *Property { - switch p.Type() { + switch p.Type { case cftypes.Int: return p.deriveResolved(cftypes.String, strconv.Itoa(p.AsInt())) case cftypes.Bool: @@ -110,7 +110,7 @@ func (p *Property) convertToString() *Property { } func (p *Property) convertToBool() *Property { - switch p.Type() { + switch p.Type { case cftypes.String: if p.EqualTo("true", IgnoreCase) || p.EqualTo("1") { return p.deriveResolved(cftypes.Bool, true) @@ -130,8 +130,7 @@ func (p *Property) convertToBool() *Property { } func (p *Property) convertToInt() *Property { - // - switch p.Type() { + switch p.Type { case cftypes.String: if val, err := strconv.Atoi(p.AsString()); err == nil { return p.deriveResolved(cftypes.Int, val) diff --git a/pkg/iac/scanners/cloudformation/parser/property_helpers.go b/pkg/iac/scanners/cloudformation/parser/property_helpers.go index 868f4d9231cd..2887b68bf537 100644 --- a/pkg/iac/scanners/cloudformation/parser/property_helpers.go +++ b/pkg/iac/scanners/cloudformation/parser/property_helpers.go @@ -9,7 +9,7 @@ import ( ) func (p *Property) IsNil() bool { - return p == nil || p.Inner.Value == nil + return p == nil || p.Value == nil } func (p *Property) IsNotNil() bool { @@ -25,7 +25,7 @@ func (p *Property) Is(t cftypes.CfType) bool { return prop.Is(t) } } - return p.Inner.Type == t + return p.Type == t } func (p *Property) IsString() bool { @@ -48,7 +48,7 @@ func (p *Property) IsMap() bool { if p.IsNil() || p.IsUnresolved() { return false } - return p.Inner.Type == cftypes.Map + return p.Type == cftypes.Map } func (p *Property) IsNotMap() bool { @@ -89,7 +89,7 @@ func (p *Property) AsString() string { return "" } - return p.Inner.Value.(string) + return p.Value.(string) } func (p *Property) AsStringValue() iacTypes.StringValue { @@ -113,7 +113,7 @@ func (p *Property) AsInt() int { return 0 } - return p.Inner.Value.(int) + return p.Value.(int) } func (p *Property) AsIntValue() iacTypes.IntValue { @@ -133,7 +133,7 @@ func (p *Property) AsBool() bool { if !p.IsBool() { return false } - return p.Inner.Value.(bool) + return p.Value.(bool) } func (p *Property) AsBoolValue() iacTypes.BoolValue { @@ -144,7 +144,7 @@ func (p *Property) AsBoolValue() iacTypes.BoolValue { } func (p *Property) AsMap() map[string]*Property { - val, ok := p.Inner.Value.(map[string]*Property) + val, ok := p.Value.(map[string]*Property) if !ok { return nil } @@ -159,7 +159,7 @@ func (p *Property) AsList() []*Property { return []*Property{} } - if list, ok := p.Inner.Value.([]*Property); ok { + if list, ok := p.Value.([]*Property); ok { return list } return nil @@ -183,23 +183,23 @@ func (p *Property) EqualTo(checkValue any, equalityOptions ...EqualityOptions) b return false } - if p.Inner.Type == cftypes.String || p.IsString() { + if p.Type == cftypes.String || p.IsString() { if ignoreCase { return strings.EqualFold(p.AsString(), checkerVal) } return p.AsString() == checkerVal - } else if p.Inner.Type == cftypes.Int || p.IsInt() { + } else if p.Type == cftypes.Int || p.IsInt() { if val, err := strconv.Atoi(checkerVal); err == nil { return p.AsInt() == val } } return false case bool: - if p.Inner.Type == cftypes.Bool || p.IsBool() { + if p.Type == cftypes.Bool || p.IsBool() { return p.AsBool() == checkerVal } case int: - if p.Inner.Type == cftypes.Int || p.IsInt() { + if p.Type == cftypes.Int || p.IsInt() { return p.AsInt() == checkerVal } } @@ -225,7 +225,7 @@ func (p *Property) IsEmpty() bool { return false } - switch p.Inner.Type { + switch p.Type { case cftypes.String: return p.AsString() == "" case cftypes.List, cftypes.Map: @@ -240,7 +240,7 @@ func (p *Property) Contains(checkVal any) bool { return false } - switch p.Type() { + switch p.Type { case cftypes.List: for _, p := range p.AsList() { if p.EqualTo(checkVal) { diff --git a/pkg/iac/scanners/cloudformation/parser/property_helpers_test.go b/pkg/iac/scanners/cloudformation/parser/property_helpers_test.go index 36f2cba89289..69e3290ad666 100644 --- a/pkg/iac/scanners/cloudformation/parser/property_helpers_test.go +++ b/pkg/iac/scanners/cloudformation/parser/property_helpers_test.go @@ -9,15 +9,6 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/types" ) -func newProp(inner PropertyInner) *Property { - return &Property{ - name: "test_prop", - ctx: &FileContext{}, - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: inner, - } -} - func Test_EqualTo(t *testing.T) { tests := []struct { name string @@ -34,155 +25,136 @@ func Test_EqualTo(t *testing.T) { }, { name: "compare strings", - property: newProp(PropertyInner{ + property: &Property{ + name: "test_prop", + ctx: &FileContext{}, + rng: types.NewRange("testfile", 1, 1, "", nil), Type: cftypes.String, Value: "is str", - }), + }, checkValue: "is str", isEqual: true, }, { name: "compare strings ignoring case", - property: newProp(PropertyInner{ + property: &Property{ Type: cftypes.String, Value: "is str", - }), + }, opts: []EqualityOptions{IgnoreCase}, checkValue: "Is StR", isEqual: true, }, { name: "strings ate not equal", - property: newProp(PropertyInner{ + property: &Property{ Type: cftypes.String, Value: "some value", - }), + }, checkValue: "some other value", isEqual: false, }, { name: "compare prop with a int represented by a string", - property: newProp(PropertyInner{ + property: &Property{ Type: cftypes.Int, Value: 147, - }), + }, checkValue: "147", isEqual: true, }, { name: "compare ints", - property: newProp(PropertyInner{ + property: &Property{ Type: cftypes.Int, Value: 701, - }), + }, checkValue: 701, isEqual: true, }, { name: "compare bools", - property: newProp(PropertyInner{ + property: &Property{ Type: cftypes.Bool, Value: true, - }), + }, checkValue: true, isEqual: true, }, { name: "prop is string fn", - property: newProp(PropertyInner{ + property: &Property{ Type: cftypes.Map, Value: map[string]*Property{ "Fn::If": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.Bool, - Value: false, - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "bad", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "some value", - }, - }, + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.Bool, + Value: false, + }, + { + Type: cftypes.String, + Value: "bad", + }, + { + Type: cftypes.String, + Value: "some value", }, }, }, }, - }), + }, checkValue: "some value", isEqual: true, }, { name: "prop is int fn", - property: newProp(PropertyInner{ + property: &Property{ Type: cftypes.Map, Value: map[string]*Property{ "Fn::If": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.Bool, - Value: true, - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.Int, - Value: 121, - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.Int, - Value: -1, - }, - }, + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.Bool, + Value: true, + }, + { + Type: cftypes.Int, + Value: 121, + }, + { + Type: cftypes.Int, + Value: -1, }, }, }, }, - }), + }, checkValue: 121, isEqual: true, }, { name: "prop is bool fn", - property: newProp(PropertyInner{ + property: &Property{ Type: cftypes.Map, Value: map[string]*Property{ "Fn::Equals": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "foo", + }, + { + Type: cftypes.String, + Value: "foo", }, }, }, }, - }), + }, checkValue: true, isEqual: true, }, diff --git a/pkg/iac/scanners/cloudformation/parser/pseudo_parameters.go b/pkg/iac/scanners/cloudformation/parser/pseudo_parameters.go index 814ba52ee61e..243b7ca3483d 100644 --- a/pkg/iac/scanners/cloudformation/parser/pseudo_parameters.go +++ b/pkg/iac/scanners/cloudformation/parser/pseudo_parameters.go @@ -16,16 +16,12 @@ var pseudoParameters = map[string]pseudoParameter{ t: cftypes.List, val: []*Property{ { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "notification::arn::1", - }, + Type: cftypes.String, + Value: "notification::arn::1", }, { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "notification::arn::2", - }, + Type: cftypes.String, + Value: "notification::arn::2", }, }, raw: []string{"notification::arn::1", "notification::arn::2"}, diff --git a/pkg/iac/scanners/cloudformation/parser/resource.go b/pkg/iac/scanners/cloudformation/parser/resource.go index b5910c144bcf..548bd86f7ead 100644 --- a/pkg/iac/scanners/cloudformation/parser/resource.go +++ b/pkg/iac/scanners/cloudformation/parser/resource.go @@ -4,23 +4,22 @@ import ( "io/fs" "strings" + "github.com/go-json-experiment/json" + "github.com/go-json-experiment/json/jsontext" "gopkg.in/yaml.v3" - "github.com/aquasecurity/jfather" iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + xjson "github.com/aquasecurity/trivy/pkg/x/json" ) type Resource struct { - ctx *FileContext - rng iacTypes.Range - id string - comment string - Inner ResourceInner -} - -type ResourceInner struct { - Type string `json:"Type" yaml:"Type"` - Properties map[string]*Property `json:"Properties" yaml:"Properties"` + xjson.Location + typ string + properties map[string]*Property + ctx *FileContext + rng iacTypes.Range + id string + comment string } func (r *Resource) configureResource(id string, target fs.FS, filepath string, ctx *FileContext) { @@ -32,15 +31,15 @@ func (r *Resource) configureResource(id string, target fs.FS, filepath string, c func (r *Resource) setId(id string) { r.id = id - for n, p := range r.properties() { + for n, p := range r.properties { p.setName(n) } } func (r *Resource) setFile(target fs.FS, filepath string) { - r.rng = iacTypes.NewRange(filepath, r.rng.GetStartLine(), r.rng.GetEndLine(), r.rng.GetSourcePrefix(), target) + r.rng = iacTypes.NewRange(filepath, r.StartLine, r.EndLine, r.rng.GetSourcePrefix(), target) - for _, p := range r.Inner.Properties { + for _, p := range r.properties { p.setFileAndParentRange(target, filepath, r.rng) } } @@ -48,21 +47,39 @@ func (r *Resource) setFile(target fs.FS, filepath string) { func (r *Resource) setContext(ctx *FileContext) { r.ctx = ctx - for _, p := range r.Inner.Properties { - p.SetLogicalResource(r.id) + for _, p := range r.properties { + p.setLogicalResource(r.id) p.setContext(ctx) } } -func (r *Resource) UnmarshalYAML(value *yaml.Node) error { - r.rng = iacTypes.NewRange("", value.Line-1, calculateEndLine(value), "", nil) - r.comment = value.LineComment - return value.Decode(&r.Inner) +type resourceInner struct { + Type string `json:"Type" yaml:"Type"` + Properties map[string]*Property `json:"Properties" yaml:"Properties"` } -func (r *Resource) UnmarshalJSONWithMetadata(node jfather.Node) error { - r.rng = iacTypes.NewRange("", node.Range().Start.Line, node.Range().End.Line, "", nil) - return node.Decode(&r.Inner) +func (r *Resource) UnmarshalYAML(node *yaml.Node) error { + r.StartLine = node.Line - 1 + r.EndLine = calculateEndLine(node) + r.comment = node.LineComment + + var i resourceInner + if err := node.Decode(&i); err != nil { + return err + } + r.typ = i.Type + r.properties = i.Properties + return nil +} + +func (r *Resource) UnmarshalJSONFrom(dec *jsontext.Decoder) error { + var i resourceInner + if err := json.UnmarshalDecode(dec, &i); err != nil { + return err + } + r.typ = i.Type + r.properties = i.Properties + return nil } func (r *Resource) ID() string { @@ -70,7 +87,7 @@ func (r *Resource) ID() string { } func (r *Resource) Type() string { - return r.Inner.Type + return r.typ } func (r *Resource) Range() iacTypes.Range { @@ -85,10 +102,6 @@ func (r *Resource) Metadata() iacTypes.Metadata { return iacTypes.NewMetadata(r.Range(), NewCFReference(r.id, r.rng).String()) } -func (r *Resource) properties() map[string]*Property { - return r.Inner.Properties -} - func (r *Resource) IsNil() bool { return r.id == "" } @@ -100,7 +113,7 @@ func (r *Resource) GetProperty(path string) *Property { first := pathParts[0] property := &Property{} - if p, exists := r.properties()[first]; exists { + if p, exists := r.properties[first]; exists { property = p } diff --git a/pkg/iac/scanners/cloudformation/parser/resource_test.go b/pkg/iac/scanners/cloudformation/parser/resource_test.go index 1b67fc1d773b..a202b7eff700 100644 --- a/pkg/iac/scanners/cloudformation/parser/resource_test.go +++ b/pkg/iac/scanners/cloudformation/parser/resource_test.go @@ -10,55 +10,37 @@ import ( func Test_GetProperty_PropIsFunction(t *testing.T) { resource := Resource{ - Inner: ResourceInner{ - Type: "AWS::S3::Bucket", - Properties: map[string]*Property{ - "BucketName": { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "mybucket", - }, - }, - "VersioningConfiguration": { - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::If": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.Bool, - Value: false, - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Status": { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "Enabled", - }, - }, - }, - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Status": { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "Suspended", - }, - }, - }, - }, - }, + typ: "AWS::S3::Bucket", + properties: map[string]*Property{ + "BucketName": { + Type: cftypes.String, + Value: "mybucket", + }, + "VersioningConfiguration": { + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::If": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.Bool, + Value: false, + }, + { + Type: cftypes.Map, + Value: map[string]*Property{ + "Status": { + Type: cftypes.String, + Value: "Enabled", + }, + }, + }, + { + Type: cftypes.Map, + Value: map[string]*Property{ + "Status": { + Type: cftypes.String, + Value: "Suspended", }, }, }, diff --git a/pkg/iac/scanners/cloudformation/parser/util.go b/pkg/iac/scanners/cloudformation/parser/util.go index c6babcb7a8f3..14d60c12d4fe 100644 --- a/pkg/iac/scanners/cloudformation/parser/util.go +++ b/pkg/iac/scanners/cloudformation/parser/util.go @@ -5,58 +5,11 @@ import ( "gopkg.in/yaml.v3" - "github.com/aquasecurity/jfather" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" "github.com/aquasecurity/trivy/pkg/iac/scanners/kubernetes/parser" ) -func setPropertyValueFromJson(node jfather.Node, propertyData *PropertyInner) error { - - switch node.Kind() { - case jfather.KindNumber: - var val any - if err := node.Decode(&val); err != nil { - return err - } - switch v := val.(type) { - case float64: - propertyData.Type = cftypes.Float64 - propertyData.Value = v - case int64: - propertyData.Type = cftypes.Int - propertyData.Value = int(v) - } - return nil - case jfather.KindBoolean: - propertyData.Type = cftypes.Bool - return node.Decode(&propertyData.Value) - case jfather.KindString: - propertyData.Type = cftypes.String - return node.Decode(&propertyData.Value) - case jfather.KindObject: - var childData map[string]*Property - if err := node.Decode(&childData); err != nil { - return err - } - propertyData.Type = cftypes.Map - propertyData.Value = childData - return nil - case jfather.KindArray: - var childData []*Property - if err := node.Decode(&childData); err != nil { - return err - } - propertyData.Type = cftypes.List - propertyData.Value = childData - return nil - default: - propertyData.Type = cftypes.String - return node.Decode(&propertyData.Value) - } - -} - -func setPropertyValueFromYaml(node *yaml.Node, propertyData *PropertyInner) error { +func setPropertyValueFromYaml(node *yaml.Node, propertyData *Property) error { if IsIntrinsicFunc(node) { var newContent []*yaml.Node diff --git a/pkg/iac/scanners/generic/scanner.go b/pkg/iac/scanners/generic/scanner.go index 0ac53b3525e9..98106309c00f 100644 --- a/pkg/iac/scanners/generic/scanner.go +++ b/pkg/iac/scanners/generic/scanner.go @@ -190,7 +190,7 @@ func (s *GenericScanner) applyIgnoreRules(fsys fs.FS, results scan.Results) erro return nil } -func parseJson(ctx context.Context, r io.Reader, _ string) (any, error) { +func parseJson(_ context.Context, r io.Reader, _ string) (any, error) { var target any if err := json.NewDecoder(r).Decode(&target); err != nil { return nil, err @@ -198,7 +198,7 @@ func parseJson(ctx context.Context, r io.Reader, _ string) (any, error) { return target, nil } -func parseYaml(ctx context.Context, r io.Reader, _ string) (any, error) { +func parseYaml(_ context.Context, r io.Reader, _ string) (any, error) { contents, err := io.ReadAll(r) if err != nil { return nil, err @@ -223,7 +223,7 @@ func parseYaml(ctx context.Context, r io.Reader, _ string) (any, error) { return results, nil } -func parseTOML(ctx context.Context, r io.Reader, _ string) (any, error) { +func parseTOML(_ context.Context, r io.Reader, _ string) (any, error) { var target any if _, err := toml.NewDecoder(r).Decode(&target); err != nil { return nil, err diff --git a/pkg/iac/scanners/helm/parser/vals.go b/pkg/iac/scanners/helm/parser/vals.go index f2589b3caec7..891cae957c0a 100644 --- a/pkg/iac/scanners/helm/parser/vals.go +++ b/pkg/iac/scanners/helm/parser/vals.go @@ -108,7 +108,6 @@ func readFile(filePath string) ([]byte, error) { return nil, err } return data.Bytes(), err - } else { - return os.ReadFile(filePath) } + return os.ReadFile(filePath) } diff --git a/pkg/iac/scanners/helm/scanner.go b/pkg/iac/scanners/helm/scanner.go index f8f6c1ae89ff..fc6173d9801e 100644 --- a/pkg/iac/scanners/helm/scanner.go +++ b/pkg/iac/scanners/helm/scanner.go @@ -74,13 +74,13 @@ func (s *Scanner) ScanFS(ctx context.Context, fsys fs.FS, dir string) (scan.Resu } if detection.IsArchive(filePath) { - scanResults, err := s.getScanResults(filePath, ctx, fsys) + scanResults, err := s.getScanResults(ctx, filePath, fsys) if err != nil { return err } results = append(results, scanResults...) } else if path.Base(filePath) == chartutil.ChartfileName { - if scanResults, err := s.getScanResults(filepath.Dir(filePath), ctx, fsys); err != nil { + if scanResults, err := s.getScanResults(ctx, filepath.Dir(filePath), fsys); err != nil { return err } else { results = append(results, scanResults...) @@ -97,7 +97,7 @@ func (s *Scanner) ScanFS(ctx context.Context, fsys fs.FS, dir string) (scan.Resu } -func (s *Scanner) getScanResults(path string, ctx context.Context, target fs.FS) (results []scan.Result, err error) { +func (s *Scanner) getScanResults(ctx context.Context, path string, target fs.FS) (results []scan.Result, err error) { helmParser, err := parser.New(path, s.parserOptions...) if err != nil { return nil, err diff --git a/pkg/iac/scanners/helm/test/scanner_test.go b/pkg/iac/scanners/helm/test/scanner_test.go index 41fc1b5abf68..c421b94dac7e 100644 --- a/pkg/iac/scanners/helm/test/scanner_test.go +++ b/pkg/iac/scanners/helm/test/scanner_test.go @@ -37,8 +37,6 @@ func TestScanner_ScanFS(t *testing.T) { "AVD-KSV-0020", "AVD-KSV-0021", "AVD-KSV-0030", "AVD-KSV-0104", "AVD-KSV-0106", "AVD-KSV-0032", - "AVD-KSV-0040", - "AVD-KSV-0039", "AVD-KSV-0004", "AVD-KSV-0035", "AVD-KSV-0033", @@ -57,8 +55,6 @@ func TestScanner_ScanFS(t *testing.T) { "AVD-KSV-0104", "AVD-KSV-0106", "AVD-KSV-0117", "AVD-KSV-0110", "AVD-KSV-0032", - "AVD-KSV-0040", - "AVD-KSV-0039", "AVD-KSV-0004", "AVD-KSV-0035", "AVD-KSV-0033", @@ -82,8 +78,6 @@ func TestScanner_ScanFS(t *testing.T) { "AVD-KSV-0016", "AVD-KSV-0001", "AVD-KSV-0011", "AVD-KSV-0015", "AVD-KSV-0021", "AVD-KSV-0110", "AVD-KSV-0020", "AVD-KSV-0032", - "AVD-KSV-0040", - "AVD-KSV-0039", "AVD-KSV-0004", "AVD-KSV-0035", }), @@ -121,8 +115,6 @@ deny[res] { "AVD-KSV-0020", "AVD-KSV-0021", "AVD-KSV-0030", "AVD-KSV-0104", "AVD-KSV-0106", "AVD-USR-ID001", "AVD-KSV-0032", - "AVD-KSV-0040", - "AVD-KSV-0039", "AVD-KSV-0004", "AVD-KSV-0035", "AVD-KSV-0033", diff --git a/pkg/iac/scanners/kubernetes/parser/manifest.go b/pkg/iac/scanners/kubernetes/parser/manifest.go index 72d471ee9d91..95eb70aecd00 100644 --- a/pkg/iac/scanners/kubernetes/parser/manifest.go +++ b/pkg/iac/scanners/kubernetes/parser/manifest.go @@ -1,15 +1,12 @@ package parser import ( - "bytes" - "errors" "fmt" - "io" - "reflect" - "github.com/go-json-experiment/json" - "github.com/go-json-experiment/json/jsontext" + "github.com/samber/lo" "gopkg.in/yaml.v3" + + xjson "github.com/aquasecurity/trivy/pkg/x/json" ) type Manifest struct { @@ -17,6 +14,26 @@ type Manifest struct { Content *ManifestNode } +func NewManifest(path string, root *ManifestNode) *Manifest { + root.Walk(func(n *ManifestNode) { + n.Path = path + switch v := n.Value.(type) { + case []*ManifestNode: + n.Value = lo.Filter(v, func(vv *ManifestNode, _ int) bool { + return vv != nil + }) + case map[string]*ManifestNode: + n.Value = lo.OmitBy(v, func(_ string, vv *ManifestNode) bool { + return vv == nil + }) + } + }) + return &Manifest{ + Path: path, + Content: root, + } +} + func (m *Manifest) UnmarshalYAML(value *yaml.Node) error { switch value.Tag { @@ -39,66 +56,11 @@ func (m *Manifest) ToRego() any { } func ManifestFromJSON(path string, data []byte) (*Manifest, error) { - root := &ManifestNode{ - Path: path, - } + var root = &ManifestNode{} - if err := json.Unmarshal(data, root, json.WithUnmarshalers( - json.UnmarshalFromFunc(func(dec *jsontext.Decoder, node *ManifestNode) error { - startOffset := dec.InputOffset() - if err := unmarshalManifestNode(dec, node); err != nil { - return err - } - endOffset := dec.InputOffset() - node.StartLine = 1 + countLines(data, int(startOffset)) - node.EndLine = 1 + countLines(data, int(endOffset)) - node.Path = path - return nil - })), - ); err != nil && !errors.Is(err, io.EOF) { + if err := xjson.Unmarshal(data, root); err != nil { return nil, err } - return &Manifest{ - Path: path, - Content: root, - }, nil -} - -func unmarshalManifestNode(dec *jsontext.Decoder, node *ManifestNode) error { - var valPtr any - var nodeType TagType - switch k := dec.PeekKind(); k { - case 't', 'f': - valPtr = new(bool) - nodeType = TagBool - case '"': - nodeType = TagStr - valPtr = new(string) - case '0': - nodeType = TagInt - valPtr = new(uint64) - case '[', 'n': - valPtr = new([]*ManifestNode) - nodeType = TagSlice - case '{': - valPtr = new(map[string]*ManifestNode) - nodeType = TagMap - case 0: - return dec.SkipValue() - default: - return fmt.Errorf("unexpected token kind %q at %d", k.String(), dec.InputOffset()) - } - - if err := json.UnmarshalDecode(dec, valPtr); err != nil { - return err - } - - node.Value = reflect.ValueOf(valPtr).Elem().Interface() - node.Type = nodeType - return nil -} - -func countLines(data []byte, offset int) int { - return bytes.Count(data[:offset], []byte("\n")) + return NewManifest(path, root), nil } diff --git a/pkg/iac/scanners/kubernetes/parser/manifest_node.go b/pkg/iac/scanners/kubernetes/parser/manifest_node.go index 4a047b264317..1c2f4285410e 100644 --- a/pkg/iac/scanners/kubernetes/parser/manifest_node.go +++ b/pkg/iac/scanners/kubernetes/parser/manifest_node.go @@ -3,12 +3,16 @@ package parser import ( "encoding/base64" "fmt" + "reflect" "strconv" "time" + "github.com/go-json-experiment/json" + "github.com/go-json-experiment/json/jsontext" "gopkg.in/yaml.v3" "github.com/aquasecurity/trivy/pkg/log" + xjson "github.com/aquasecurity/trivy/pkg/x/json" ) type TagType string @@ -26,42 +30,37 @@ const ( ) type ManifestNode struct { - StartLine int - EndLine int - Offset int - Value any - Type TagType - Path string + xjson.Location + Offset int + Value any + Type TagType + Path string } -func (r *ManifestNode) ToRego() any { - if r == nil { +func (n *ManifestNode) ToRego() any { + if n == nil { return nil } - switch r.Type { + switch n.Type { case TagBool, TagInt, TagFloat, TagString, TagStr, TagBinary: - return r.Value + return n.Value case TagTimestamp: - t, ok := r.Value.(time.Time) + t, ok := n.Value.(time.Time) if !ok { return nil } return t.Format(time.RFC3339) case TagSlice: var output []any - for _, node := range r.Value.([]*ManifestNode) { + for _, node := range n.Value.([]*ManifestNode) { output = append(output, node.ToRego()) } return output case TagMap: - output := make(map[string]any) - output["__defsec_metadata"] = map[string]any{ - "startline": r.StartLine, - "endline": r.EndLine, - "filepath": r.Path, - "offset": r.Offset, + output := map[string]any{ + "__defsec_metadata": n.metadata(), } - for key, node := range r.Value.(map[string]*ManifestNode) { + for key, node := range n.Value.(map[string]*ManifestNode) { output[key] = node.ToRego() } return output @@ -69,64 +68,73 @@ func (r *ManifestNode) ToRego() any { return nil } -func (r *ManifestNode) UnmarshalYAML(node *yaml.Node) error { - r.StartLine = node.Line - r.EndLine = node.Line - r.Type = TagType(node.Tag) +func (n *ManifestNode) metadata() map[string]any { + return map[string]any{ + "startline": n.StartLine, + "endline": n.EndLine, + "filepath": n.Path, + "offset": n.Offset, + } +} + +func (n *ManifestNode) UnmarshalYAML(node *yaml.Node) error { + n.StartLine = node.Line + n.EndLine = node.Line + n.Type = TagType(node.Tag) switch TagType(node.Tag) { case TagString, TagStr: - r.Value = node.Value + n.Value = node.Value case TagInt: val, err := strconv.Atoi(node.Value) if err != nil { return fmt.Errorf("failed to parse int: %w", err) } - r.Value = val + n.Value = val case TagFloat: val, err := strconv.ParseFloat(node.Value, 64) if err != nil { return fmt.Errorf("failed to parse float: %w", err) } - r.Value = val + n.Value = val case TagBool: val, err := strconv.ParseBool(node.Value) if err != nil { return fmt.Errorf("failed to parse bool: %w", err) } - r.Value = val + n.Value = val case TagTimestamp: var val time.Time if err := node.Decode(&val); err != nil { return fmt.Errorf("failed to decode timestamp: %w", err) } - r.Value = val + n.Value = val case TagBinary: val, err := base64.StdEncoding.DecodeString(node.Value) if err != nil { return fmt.Errorf("failed to decode binary data: %w", err) } - r.Value = val + n.Value = val case TagMap: - return r.handleMapTag(node) + return n.handleMapTag(node) case TagSlice: - return r.handleSliceTag(node) + return n.handleSliceTag(node) default: log.WithPrefix("k8s").Debug("Skipping unsupported node tag", log.String("tag", node.Tag), - log.FilePath(r.Path), + log.FilePath(n.Path), log.Int("line", node.Line), ) } return nil } -func (r *ManifestNode) handleSliceTag(node *yaml.Node) error { +func (n *ManifestNode) handleSliceTag(node *yaml.Node) error { var nodes []*ManifestNode maxLine := node.Line for _, contentNode := range node.Content { newNode := new(ManifestNode) - newNode.Path = r.Path + newNode.Path = n.Path if err := contentNode.Decode(newNode); err != nil { return err } @@ -135,12 +143,12 @@ func (r *ManifestNode) handleSliceTag(node *yaml.Node) error { } nodes = append(nodes, newNode) } - r.EndLine = maxLine - r.Value = nodes + n.EndLine = maxLine + n.Value = nodes return nil } -func (r *ManifestNode) handleMapTag(node *yaml.Node) error { +func (n *ManifestNode) handleMapTag(node *yaml.Node) error { output := make(map[string]*ManifestNode) var key string maxLine := node.Line @@ -149,7 +157,7 @@ func (r *ManifestNode) handleMapTag(node *yaml.Node) error { key = contentNode.Value } else { newNode := new(ManifestNode) - newNode.Path = r.Path + newNode.Path = n.Path if err := contentNode.Decode(newNode); err != nil { return err } @@ -159,7 +167,58 @@ func (r *ManifestNode) handleMapTag(node *yaml.Node) error { } } } - r.EndLine = maxLine - r.Value = output + n.EndLine = maxLine + n.Value = output + return nil +} + +func (n *ManifestNode) UnmarshalJSONFrom(dec *jsontext.Decoder) error { + var valPtr any + var nodeType TagType + switch k := dec.PeekKind(); k { + case 't', 'f': + valPtr = new(bool) + nodeType = TagBool + case '"': + nodeType = TagStr + valPtr = new(string) + case '0': + nodeType = TagInt + valPtr = new(uint64) + case '[': + valPtr = new([]*ManifestNode) + nodeType = TagSlice + case '{': + valPtr = new(map[string]*ManifestNode) + nodeType = TagMap + case 'n': + // TODO: UnmarshalJSONFrom is called only for the root null + return dec.SkipValue() + case 0: + return dec.SkipValue() + default: + return fmt.Errorf("unexpected token kind %q at %d", k.String(), dec.InputOffset()) + } + + if err := json.UnmarshalDecode(dec, valPtr); err != nil { + return err + } + + n.Value = reflect.ValueOf(valPtr).Elem().Interface() + n.Type = nodeType return nil } + +func (n *ManifestNode) Walk(f func(n *ManifestNode)) { + f(n) + switch n.Type { + case TagSlice: + for _, node := range n.Value.([]*ManifestNode) { + node.Walk(f) + } + case TagMap: + for _, node := range n.Value.(map[string]*ManifestNode) { + node.Walk(f) + } + } +} diff --git a/pkg/iac/scanners/kubernetes/parser/manifest_test.go b/pkg/iac/scanners/kubernetes/parser/manifest_test.go index fd1d34f3a967..b2e9e323251c 100644 --- a/pkg/iac/scanners/kubernetes/parser/manifest_test.go +++ b/pkg/iac/scanners/kubernetes/parser/manifest_test.go @@ -15,7 +15,8 @@ func TestJsonManifestToRego(t *testing.T) { "apiVersion": "v1", "kind": "Pod", "metadata": { - "name": "hello-cpu-limit" + "name": "hello-cpu-limit", + "foo": null }, "spec": { "containers": [ @@ -23,7 +24,8 @@ func TestJsonManifestToRego(t *testing.T) { "command": [ "sh", "-c", - "echo 'Hello' && sleep 1h" + "echo 'Hello' && sleep 1h", + null ], "image": "busybox", "name": "hello" @@ -41,7 +43,7 @@ func TestJsonManifestToRego(t *testing.T) { "filepath": filePath, "offset": 0, "startline": 1, - "endline": 20, + "endline": 22, }, "apiVersion": "v1", "kind": "Pod", @@ -50,7 +52,7 @@ func TestJsonManifestToRego(t *testing.T) { "filepath": filePath, "offset": 0, "startline": 4, - "endline": 6, + "endline": 7, }, "name": "hello-cpu-limit", }, @@ -58,16 +60,16 @@ func TestJsonManifestToRego(t *testing.T) { "__defsec_metadata": map[string]any{ "filepath": filePath, "offset": 0, - "startline": 7, - "endline": 19, + "startline": 8, + "endline": 21, }, "containers": []any{ map[string]any{ "__defsec_metadata": map[string]any{ "filepath": filePath, "offset": 0, - "startline": 8, - "endline": 17, + "startline": 10, + "endline": 19, }, "command": []any{ "sh", diff --git a/pkg/iac/scanners/kubernetes/parser/parser.go b/pkg/iac/scanners/kubernetes/parser/parser.go index 5c6b2ba3fe9b..187957d26d3c 100644 --- a/pkg/iac/scanners/kubernetes/parser/parser.go +++ b/pkg/iac/scanners/kubernetes/parser/parser.go @@ -1,6 +1,7 @@ package parser import ( + "bytes" "context" "fmt" "io" @@ -20,7 +21,7 @@ func Parse(_ context.Context, r io.Reader, path string) ([]any, error) { return nil, nil } - if strings.TrimSpace(string(contents))[0] == '{' { + if bytes.TrimSpace(contents)[0] == '{' { manifest, err := ManifestFromJSON(path, contents) if err != nil { return nil, err diff --git a/pkg/iac/scanners/options/scanner.go b/pkg/iac/scanners/options/scanner.go index 420909095bd8..95b7903528ae 100644 --- a/pkg/iac/scanners/options/scanner.go +++ b/pkg/iac/scanners/options/scanner.go @@ -3,3 +3,15 @@ package options type ConfigurableScanner any type ScannerOption func(s ConfigurableScanner) + +type RawConfigScanner interface { + SetScanRawConfig(v bool) +} + +func WithScanRawConfig(v bool) ScannerOption { + return func(s ConfigurableScanner) { + if ss, ok := s.(RawConfigScanner); ok { + ss.SetScanRawConfig(v) + } + } +} diff --git a/pkg/iac/scanners/terraform/executor/executor.go b/pkg/iac/scanners/terraform/executor/executor.go index 8906ef7db750..329a6d11c3de 100644 --- a/pkg/iac/scanners/terraform/executor/executor.go +++ b/pkg/iac/scanners/terraform/executor/executor.go @@ -25,6 +25,7 @@ type Executor struct { logger *log.Logger resultsFilters []func(scan.Results) scan.Results regoScanner *rego.Scanner + scanRawConfig bool } // New creates a new Executor @@ -44,6 +45,7 @@ func (e *Executor) Execute(ctx context.Context, modules terraform.Modules, baseP infra := adapter.Adapt(modules) e.logger.Debug("Adapted module(s) into state data.", log.Int("count", len(modules))) + e.logger.Debug("Scan state data") results, err := e.regoScanner.ScanInput(ctx, types.SourceCloud, rego.Input{ Contents: infra.ToRego(), Path: basePath, @@ -52,7 +54,21 @@ func (e *Executor) Execute(ctx context.Context, modules terraform.Modules, baseP return nil, err } - e.logger.Debug("Finished applying rules.") + if e.scanRawConfig { + e.logger.Debug("Scan raw Terraform data") + results2, err := e.regoScanner.ScanInput(ctx, types.SourceTerraformRaw, rego.Input{ + Contents: terraform.ExportModules(modules), + Path: basePath, + }) + if err != nil { + e.logger.Error("Failed to scan raw Terraform data", + log.FilePath(basePath), log.Err(err)) + } else { + results = append(results, results2...) + } + } + + e.logger.Debug("Finished applying checks") e.logger.Debug("Applying ignores...") var ignores ignore.Rules diff --git a/pkg/iac/scanners/terraform/executor/option.go b/pkg/iac/scanners/terraform/executor/option.go index 65a6180d7e67..4459a6861d00 100644 --- a/pkg/iac/scanners/terraform/executor/option.go +++ b/pkg/iac/scanners/terraform/executor/option.go @@ -24,3 +24,9 @@ func OptionWithRegoScanner(s *rego.Scanner) Option { e.regoScanner = s } } + +func OptionWithScanRawConfig(b bool) Option { + return func(e *Executor) { + e.scanRawConfig = b + } +} diff --git a/pkg/iac/scanners/terraform/parser/ctylist.go b/pkg/iac/scanners/terraform/parser/ctylist.go new file mode 100644 index 000000000000..13132824d9f7 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/ctylist.go @@ -0,0 +1,34 @@ +package parser + +import "github.com/zclconf/go-cty/cty" + +// insertTupleElement inserts a value into a tuple at the specified index. +// If the idx is outside the bounds of the list, it grows the tuple to +// the new size, and fills in `cty.NilVal` for the missing elements. +// +// This function will not panic. If the list value is not a list, it will +// be replaced with an empty list. +func insertTupleElement(list cty.Value, idx int, val cty.Value) cty.Value { + if list.IsNull() || !list.Type().IsTupleType() { + // better than a panic + list = cty.EmptyTupleVal + } + + if idx < 0 { + // Nothing to do? + return list + } + + // Create a new list of the correct length, copying in the old list + // values for matching indices. + newList := make([]cty.Value, max(idx+1, list.LengthInt())) + for it := list.ElementIterator(); it.Next(); { + key, elem := it.Element() + elemIdx, _ := key.AsBigFloat().Int64() + newList[elemIdx] = elem + } + // Insert the new value. + newList[idx] = val + + return cty.TupleVal(newList) +} diff --git a/pkg/iac/scanners/terraform/parser/ctylist_test.go b/pkg/iac/scanners/terraform/parser/ctylist_test.go new file mode 100644 index 000000000000..874d3a217ee9 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/ctylist_test.go @@ -0,0 +1,103 @@ +package parser + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/zclconf/go-cty/cty" +) + +func Test_insertTupleElement(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + start cty.Value + index int + value cty.Value + want cty.Value + }{ + { + name: "empty", + start: cty.Value{}, + index: 0, + value: cty.NilVal, + want: cty.TupleVal([]cty.Value{cty.NilVal}), + }, + { + name: "empty to length", + start: cty.Value{}, + index: 2, + value: cty.NilVal, + want: cty.TupleVal([]cty.Value{cty.NilVal, cty.NilVal, cty.NilVal}), + }, + { + name: "insert to empty", + start: cty.EmptyTupleVal, + index: 1, + value: cty.NumberIntVal(5), + want: cty.TupleVal([]cty.Value{cty.NilVal, cty.NumberIntVal(5)}), + }, + { + name: "insert to existing", + start: cty.TupleVal([]cty.Value{cty.NumberIntVal(1), cty.NumberIntVal(2), cty.NumberIntVal(3)}), + index: 1, + value: cty.NumberIntVal(5), + want: cty.TupleVal([]cty.Value{cty.NumberIntVal(1), cty.NumberIntVal(5), cty.NumberIntVal(3)}), + }, + { + name: "insert to existing, extends", + start: cty.TupleVal([]cty.Value{cty.NumberIntVal(1), cty.NumberIntVal(2), cty.NumberIntVal(3)}), + index: 4, + value: cty.NumberIntVal(5), + want: cty.TupleVal([]cty.Value{ + cty.NumberIntVal(1), cty.NumberIntVal(2), + cty.NumberIntVal(3), cty.NilVal, + cty.NumberIntVal(5), + }), + }, + { + name: "mixed list", + start: cty.TupleVal([]cty.Value{cty.StringVal("a"), cty.NumberIntVal(2), cty.NumberIntVal(3)}), + index: 1, + value: cty.BoolVal(true), + want: cty.TupleVal([]cty.Value{ + cty.StringVal("a"), cty.BoolVal(true), cty.NumberIntVal(3), + }), + }, + { + name: "replace end", + start: cty.TupleVal([]cty.Value{cty.StringVal("a"), cty.NumberIntVal(2), cty.NumberIntVal(3)}), + index: 2, + value: cty.StringVal("end"), + want: cty.TupleVal([]cty.Value{ + cty.StringVal("a"), cty.NumberIntVal(2), cty.StringVal("end"), + }), + }, + + // Some bad arguments + { + name: "negative index", + start: cty.TupleVal([]cty.Value{cty.StringVal("a"), cty.NumberIntVal(2), cty.NumberIntVal(3)}), + index: -1, + value: cty.BoolVal(true), + want: cty.TupleVal([]cty.Value{cty.StringVal("a"), cty.NumberIntVal(2), cty.NumberIntVal(3)}), + }, + { + name: "non-list", + start: cty.BoolVal(true), + index: 1, + value: cty.BoolVal(true), + want: cty.TupleVal([]cty.Value{cty.NilVal, cty.BoolVal(true)}), + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + require.Equal(t, tt.want, insertTupleElement(tt.start, tt.index, tt.value)) + }) + } +} diff --git a/pkg/iac/scanners/terraform/parser/evaluator.go b/pkg/iac/scanners/terraform/parser/evaluator.go index 59aa69fc87d5..cc2e98d93916 100644 --- a/pkg/iac/scanners/terraform/parser/evaluator.go +++ b/pkg/iac/scanners/terraform/parser/evaluator.go @@ -4,6 +4,7 @@ import ( "context" "errors" "io/fs" + "maps" "reflect" "slices" @@ -38,6 +39,9 @@ type evaluator struct { parentParser *Parser allowDownloads bool skipCachedModules bool + // stepHooks are functions that are called after each evaluation step. + // They can be used to provide additional semantics to other terraform blocks. + stepHooks []EvaluateStepHook } func newEvaluator( @@ -55,6 +59,7 @@ func newEvaluator( logger *log.Logger, allowDownloads bool, skipCachedModules bool, + stepHooks []EvaluateStepHook, ) *evaluator { // create a context to store variables and make functions available @@ -87,9 +92,12 @@ func newEvaluator( logger: logger, allowDownloads: allowDownloads, skipCachedModules: skipCachedModules, + stepHooks: stepHooks, } } +type EvaluateStepHook func(ctx *tfcontext.Context, blocks terraform.Blocks, inputVars map[string]cty.Value) + func (e *evaluator) evaluateStep() { e.ctx.Set(e.getValuesByBlockType("variable"), "var") @@ -103,6 +111,10 @@ func (e *evaluator) evaluateStep() { e.ctx.Set(e.getValuesByBlockType("data"), "data") e.ctx.Set(e.getValuesByBlockType("output"), "output") e.ctx.Set(e.getValuesByBlockType("module"), "module") + + for _, hook := range e.stepHooks { + hook(e.ctx, e.blocks, e.inputVars) + } } // exportOutputs is used to export module outputs to the parent module @@ -136,18 +148,26 @@ func (e *evaluator) EvaluateAll(ctx context.Context) (terraform.Modules, map[str // expand out resources and modules via count, for-each and dynamic // (not a typo, we do this twice so every order is processed) + // TODO: using a module in for_each or count does not work, + // because the child module is evaluated later e.blocks = e.expandBlocks(e.blocks) e.blocks = e.expandBlocks(e.blocks) // rootModule is initialized here, but not fully evaluated until all submodules are evaluated. - // Initializing it up front to keep the module hierarchy of parents correct. - rootModule := terraform.NewModule(e.projectRootPath, e.modulePath, e.blocks, e.ignores) + // A pointer for this module is needed up front to correctly set the module parent hierarchy. + // The actual instance is created at the end, when all terraform blocks + // are evaluated. + rootModule := new(terraform.Module) + submodules := e.evaluateSubmodules(ctx, rootModule, fsMap) e.logger.Debug("Starting post-submodules evaluation...") e.evaluateSteps() e.logger.Debug("Module evaluation complete.") + // terraform.NewModule must be called at the end, as `e.blocks` can be + // changed up until the last moment. + *rootModule = *terraform.NewModule(e.projectRootPath, e.modulePath, e.blocks, e.ignores) return append(terraform.Modules{rootModule}, submodules...), fsMap } @@ -238,10 +258,17 @@ func (e *evaluator) evaluateSubmodule(ctx context.Context, sm *submodule) bool { sm.modules, sm.fsMap = sm.eval.EvaluateAll(ctx) outputs := sm.eval.exportOutputs() + valueMap := e.ctx.Get("module").AsValueMap() + if valueMap == nil { + valueMap = make(map[string]cty.Value) + } + // lastState needs to be captured after applying outputs – so that they // don't get treated as changes – but before running post-submodule // evaluation, so that changes from that can trigger re-evaluations of // the submodule if/when they feed back into inputs. + ref := sm.definition.Definition.Reference() + e.ctx.Set(blockInstanceValues(sm.definition.Definition, valueMap, outputs), "module", ref.NameLabel()) e.ctx.Set(outputs, "module", sm.definition.Name) sm.lastState = sm.definition.inputVars() e.evaluateSteps() @@ -254,6 +281,9 @@ func (e *evaluator) evaluateSteps() { e.logger.Debug("Starting iteration", log.Int("iteration", i)) e.evaluateStep() + // Always attempt to expand any blocks that might now be expandable + // due to new context being set. + e.blocks = e.expandBlocks(e.blocks) // if ctx matches the last evaluation, we can bail, nothing left to resolve if i > 0 && reflect.DeepEqual(lastContext.Variables, e.ctx.Inner().Variables) { @@ -305,8 +335,14 @@ func (e *evaluator) expandBlockForEaches(blocks terraform.Blocks) terraform.Bloc } forEachVal := forEachAttr.Value() + if !forEachVal.IsKnown() { + // Defer the expansion of the block if it is unknown. It might be known at a later + // execution step. + forEachFiltered = append(forEachFiltered, block) + continue + } - if forEachVal.IsNull() || !forEachVal.IsKnown() || !forEachAttr.IsIterable() { + if forEachVal.IsNull() || !forEachAttr.IsIterable() { e.logger.Debug(`Failed to expand block. Invalid "for-each" argument. Must be known and iterable.`, log.String("block", block.FullName()), log.String("value", forEachVal.GoString()), @@ -401,8 +437,15 @@ func (e *evaluator) expandBlockCounts(blocks terraform.Blocks) terraform.Blocks countFiltered = append(countFiltered, block) continue } - count := 1 + countAttrVal := countAttr.Value() + if !countAttrVal.IsKnown() { + // Defer to the next pass when the count might be known + countFiltered = append(countFiltered, block) + continue + } + + count := 1 if !countAttrVal.IsNull() && countAttrVal.IsKnown() && countAttrVal.Type() == cty.Number { count = int(countAttr.AsNumber()) } @@ -521,7 +564,6 @@ func (e *evaluator) getValuesByBlockType(blockType string) cty.Value { values := make(map[string]cty.Value) for _, b := range blocksOfType { - switch b.Type() { case "variable": // variables are special in that their value comes from the "default" attribute val, err := e.evaluateVariable(b) @@ -536,9 +578,7 @@ func (e *evaluator) getValuesByBlockType(blockType string) cty.Value { } values[b.Label()] = val case "locals", "moved", "import": - for key, val := range b.Values().AsValueMap() { - values[key] = val - } + maps.Copy(values, b.Values().AsValueMap()) case "provider", "module", "check": if b.Label() == "" { continue @@ -549,19 +589,27 @@ func (e *evaluator) getValuesByBlockType(blockType string) cty.Value { continue } - blockMap, ok := values[b.Labels()[0]] + // Data blocks should all be loaded into the top level 'values' + // object. The hierarchy of the map is: + // values = map[]map[] = + // Block -> Block's attributes as a cty.Object + // Tuple(Block) -> Instances of the block + // Object(Block) -> Field values are instances of the block + ref := b.Reference() + typeValues, ok := values[ref.TypeLabel()] if !ok { - values[b.Labels()[0]] = cty.ObjectVal(make(map[string]cty.Value)) - blockMap = values[b.Labels()[0]] + typeValues = cty.ObjectVal(make(map[string]cty.Value)) + values[ref.TypeLabel()] = typeValues } - valueMap := blockMap.AsValueMap() + valueMap := typeValues.AsValueMap() if valueMap == nil { valueMap = make(map[string]cty.Value) } + valueMap[ref.NameLabel()] = blockInstanceValues(b, valueMap, b.Values()) - valueMap[b.Labels()[1]] = b.Values() - values[b.Labels()[0]] = cty.ObjectVal(valueMap) + // Update the map of all blocks with the same type. + values[ref.TypeLabel()] = cty.ObjectVal(valueMap) } } @@ -572,23 +620,56 @@ func (e *evaluator) getResources() map[string]cty.Value { values := make(map[string]map[string]cty.Value) for _, b := range e.blocks { - if b.Type() != "resource" { - continue - } - - if len(b.Labels()) < 2 { + if b.Type() != "resource" || len(b.Labels()) < 2 { continue } - val, exists := values[b.Labels()[0]] + ref := b.Reference() + typeValues, exists := values[ref.TypeLabel()] if !exists { - val = make(map[string]cty.Value) - values[b.Labels()[0]] = val + typeValues = make(map[string]cty.Value) + values[ref.TypeLabel()] = typeValues } - val[b.Labels()[1]] = b.Values() + typeValues[ref.NameLabel()] = blockInstanceValues(b, typeValues, b.Values()) } return lo.MapValues(values, func(v map[string]cty.Value, _ string) cty.Value { return cty.ObjectVal(v) }) } + +// blockInstanceValues returns a cty.Value containing the values of the block instances. +// If the count argument is used, a tuple is returned where the index corresponds to the argument index. +// If the for_each argument is used, an object is returned where the key corresponds to the argument key. +// In other cases, the values of the block itself are returned. +func blockInstanceValues(b *terraform.Block, typeValues map[string]cty.Value, values cty.Value) cty.Value { + ref := b.Reference() + key := ref.RawKey() + + switch { + case key.Type().Equals(cty.Number) && b.GetAttribute("count") != nil: + idx, _ := key.AsBigFloat().Int64() + return insertTupleElement(typeValues[ref.NameLabel()], int(idx), values) + case isForEachKey(key) && b.GetAttribute("for_each") != nil: + keyStr := ref.Key() + + instancesVal, exists := typeValues[ref.NameLabel()] + if !exists || !instancesVal.CanIterateElements() { + instancesVal = cty.EmptyObjectVal + } + + instances := instancesVal.AsValueMap() + if instances == nil { + instances = make(map[string]cty.Value) + } + + instances[keyStr] = values + return cty.ObjectVal(instances) + default: + return values + } +} + +func isForEachKey(key cty.Value) bool { + return key.Type().Equals(cty.Number) || key.Type().Equals(cty.String) +} diff --git a/pkg/iac/scanners/terraform/parser/funcs/filesystem.go b/pkg/iac/scanners/terraform/parser/funcs/filesystem.go index 24ac8580ee77..b338c3ef505a 100644 --- a/pkg/iac/scanners/terraform/parser/funcs/filesystem.go +++ b/pkg/iac/scanners/terraform/parser/funcs/filesystem.go @@ -368,10 +368,11 @@ func openFile(target fs.FS, baseDir, path string) (fs.File, error) { // Trivy uses a virtual file system path = filepath.ToSlash(path) - if target != nil { - return target.Open(path) + if target == nil { + return nil, fmt.Errorf("open file %q, filesystem is nil", path) } - return os.Open(path) + + return target.Open(path) } func readFileBytes(target fs.FS, baseDir, path string) ([]byte, error) { diff --git a/pkg/iac/scanners/terraform/parser/load_module.go b/pkg/iac/scanners/terraform/parser/load_module.go index 878bf075baec..cfc57e1f2f37 100644 --- a/pkg/iac/scanners/terraform/parser/load_module.go +++ b/pkg/iac/scanners/terraform/parser/load_module.go @@ -143,7 +143,7 @@ func (e *evaluator) loadExternalModule(ctx context.Context, b *terraform.Block, WorkingDir: e.projectRootPath, Name: b.FullName(), ModulePath: e.modulePath, - Logger: log.WithPrefix("module resolver"), + Logger: e.logger.With(log.Prefix("module resolver")), AllowDownloads: e.allowDownloads, SkipCache: e.skipCachedModules, } diff --git a/pkg/iac/scanners/terraform/parser/option.go b/pkg/iac/scanners/terraform/parser/option.go index f9ada6545225..80dbeec14c37 100644 --- a/pkg/iac/scanners/terraform/parser/option.go +++ b/pkg/iac/scanners/terraform/parser/option.go @@ -4,10 +4,18 @@ import ( "io/fs" "github.com/zclconf/go-cty/cty" + + "github.com/aquasecurity/trivy/pkg/log" ) type Option func(p *Parser) +func OptionWithEvalHook(hooks EvaluateStepHook) Option { + return func(p *Parser) { + p.stepHooks = append(p.stepHooks, hooks) + } +} + func OptionWithTFVarsPaths(paths ...string) Option { return func(p *Parser) { p.tfvarsPaths = paths @@ -20,6 +28,18 @@ func OptionStopOnHCLError(stop bool) Option { } } +func OptionWithLogger(log *log.Logger) Option { + return func(p *Parser) { + p.logger = log + } +} + +func OptionWithWorkingDirectoryPath(cwd string) Option { + return func(p *Parser) { + p.cwd = cwd + } +} + func OptionsWithTfVars(vars map[string]cty.Value) Option { return func(p *Parser) { p.tfvars = vars diff --git a/pkg/iac/scanners/terraform/parser/parser.go b/pkg/iac/scanners/terraform/parser/parser.go index cc299b1e6b3b..e505611b41bb 100644 --- a/pkg/iac/scanners/terraform/parser/parser.go +++ b/pkg/iac/scanners/terraform/parser/parser.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "io/fs" + "log/slog" "os" "path" "path/filepath" @@ -51,6 +52,10 @@ type Parser struct { fsMap map[string]fs.FS configsFS fs.FS skipPaths []string + stepHooks []EvaluateStepHook + // cwd is optional, if left to empty string, 'os.Getwd' + // will be used for populating 'path.cwd' in terraform. + cwd string } // New creates a new Parser @@ -64,14 +69,18 @@ func New(moduleFS fs.FS, moduleSource string, opts ...Option) *Parser { moduleFS: moduleFS, moduleSource: moduleSource, configsFS: moduleFS, - logger: log.WithPrefix("terraform parser").With("module", "root"), + logger: slog.Default(), tfvars: make(map[string]cty.Value), + stepHooks: make([]EvaluateStepHook, 0), } for _, option := range opts { option(p) } + // Scope the logger to the parser + p.logger = p.logger.With(log.Prefix("terraform parser")).With("module", "root") + return p } @@ -80,7 +89,7 @@ func (p *Parser) newModuleParser(moduleFS fs.FS, moduleSource, modulePath, modul mp.modulePath = modulePath mp.moduleBlock = moduleBlock mp.moduleName = moduleName - mp.logger = log.WithPrefix("terraform parser").With("module", moduleName) + mp.logger = p.logger mp.projectRoot = p.projectRoot mp.skipPaths = p.skipPaths mp.options = p.options @@ -88,6 +97,10 @@ func (p *Parser) newModuleParser(moduleFS fs.FS, moduleSource, modulePath, modul for _, option := range p.options { option(mp) } + + // The options above can reset the logger, so set the logging prefix after the + // options. + mp.logger = mp.logger.With(log.Prefix("terraform parser")).With("module", moduleName) return mp } @@ -143,7 +156,9 @@ func (p *Parser) ParseFile(_ context.Context, fullPath string) error { // ParseFS parses a root module, where it exists at the root of the provided filesystem func (p *Parser) ParseFS(ctx context.Context, dir string) error { - + if p.moduleFS == nil { + return errors.New("module filesystem is nil, nothing to parse") + } dir = path.Clean(dir) if p.projectRoot == "" { @@ -238,7 +253,7 @@ func readLinesFromFile(f io.Reader, from, to int) ([]string, error) { var ErrNoFiles = errors.New("no files found") -func (p *Parser) Load(ctx context.Context) (*evaluator, error) { +func (p *Parser) Load(_ context.Context) (*evaluator, error) { p.logger.Debug("Loading module", log.String("module", p.moduleName)) if len(p.files) == 0 { @@ -283,9 +298,14 @@ func (p *Parser) Load(ctx context.Context) (*evaluator, error) { ) } - workingDir, err := os.Getwd() - if err != nil { - return nil, err + var workingDir string + if p.cwd != "" { + workingDir = p.cwd + } else { + workingDir, err = os.Getwd() + if err != nil { + return nil, err + } } p.logger.Debug("Working directory for module evaluation", log.FilePath(workingDir)) @@ -301,9 +321,10 @@ func (p *Parser) Load(ctx context.Context) (*evaluator, error) { modulesMetadata, p.workspaceName, ignores, - log.WithPrefix("terraform evaluator"), + p.logger.With(log.Prefix("terraform evaluator")), p.allowDownloads, p.skipCachedModules, + p.stepHooks, ), nil } @@ -357,19 +378,18 @@ func inputVariableType(b *terraform.Block) cty.Type { return ty } -func (p *Parser) EvaluateAll(ctx context.Context) (terraform.Modules, cty.Value, error) { - +func (p *Parser) EvaluateAll(ctx context.Context) (terraform.Modules, error) { e, err := p.Load(ctx) if errors.Is(err, ErrNoFiles) { - return nil, cty.NilVal, nil + return nil, nil } else if err != nil { - return nil, cty.NilVal, err + return nil, err } modules, fsMap := e.EvaluateAll(ctx) p.logger.Debug("Finished parsing module") p.fsMap = fsMap - return modules, e.exportOutputs(), nil + return modules, nil } func (p *Parser) GetFilesystemMap() map[string]fs.FS { diff --git a/pkg/iac/scanners/terraform/parser/parser_integration_test.go b/pkg/iac/scanners/terraform/parser/parser_integration_test.go index 12605567f420..6cbac9c38851 100644 --- a/pkg/iac/scanners/terraform/parser/parser_integration_test.go +++ b/pkg/iac/scanners/terraform/parser/parser_integration_test.go @@ -24,7 +24,7 @@ module "registry" { parser := New(fsys, "", OptionStopOnHCLError(true), OptionWithSkipCachedModules(true)) require.NoError(t, parser.ParseFS(t.Context(), "code")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) require.Len(t, modules, 2) } @@ -45,7 +45,7 @@ module "registry" { parser := New(fsys, "", OptionStopOnHCLError(true), OptionWithSkipCachedModules(true)) require.NoError(t, parser.ParseFS(t.Context(), "code")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) require.Len(t, modules, 2) } @@ -68,7 +68,7 @@ module "registry" { parser := New(fsys, "", OptionStopOnHCLError(true), OptionWithSkipCachedModules(true)) require.NoError(t, parser.ParseFS(t.Context(), "code")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) require.Len(t, modules, 2) } @@ -89,7 +89,7 @@ module "object" { parser := New(fsys, "", OptionStopOnHCLError(true), OptionWithSkipCachedModules(true)) require.NoError(t, parser.ParseFS(t.Context(), "code")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) require.Len(t, modules, 2) } diff --git a/pkg/iac/scanners/terraform/parser/parser_test.go b/pkg/iac/scanners/terraform/parser/parser_test.go index ff9624734a0f..3c6800bb6d07 100644 --- a/pkg/iac/scanners/terraform/parser/parser_test.go +++ b/pkg/iac/scanners/terraform/parser/parser_test.go @@ -17,6 +17,7 @@ import ( "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/terraform" + tfcontext "github.com/aquasecurity/trivy/pkg/iac/terraform/context" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/set" ) @@ -77,7 +78,7 @@ check "cats_mittens_is_special" { parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(t.Context(), ".")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) blocks := modules[0].GetBlocks() @@ -179,7 +180,7 @@ output "mod_result" { parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(t.Context(), "code")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) require.Len(t, modules, 2) @@ -240,7 +241,7 @@ output "mod_result" { parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(t.Context(), "code")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) require.Len(t, modules, 2) rootModule := modules[0] @@ -285,7 +286,7 @@ resource "something" "blah" { parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(t.Context(), "code")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) require.Len(t, modules, 1) rootModule := modules[0] @@ -312,7 +313,7 @@ resource "something" "blah" { parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(t.Context(), "code")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) require.Len(t, modules, 1) rootModule := modules[0] @@ -355,7 +356,7 @@ resource "something" "blah" { parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(t.Context(), "code")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) require.Len(t, modules, 1) rootModule := modules[0] @@ -403,7 +404,7 @@ resource "something" "blah" { parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(t.Context(), "code")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) require.Len(t, modules, 1) rootModule := modules[0] @@ -444,7 +445,7 @@ resource "something" "blah" { parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(t.Context(), "code")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) require.Len(t, modules, 1) rootModule := modules[0] @@ -492,7 +493,7 @@ resource "something" "blah" { parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(t.Context(), "code")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) require.Len(t, modules, 1) rootModule := modules[0] @@ -536,7 +537,7 @@ resource "something" "blah" { parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(t.Context(), "code")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) require.Len(t, modules, 1) @@ -580,7 +581,7 @@ resource "aws_s3_bucket" "default" { parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(t.Context(), ".")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) require.Len(t, modules, 1) @@ -640,7 +641,7 @@ resource "aws_s3_bucket_server_side_encryption_configuration" "this2" { parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(t.Context(), ".")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) assert.Len(t, modules, 1) @@ -675,7 +676,7 @@ resource "aws_s3_bucket" "main" { ) require.NoError(t, parser.ParseFS(t.Context(), ".")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) assert.Len(t, modules, 1) @@ -708,7 +709,7 @@ resource "aws_s3_bucket" "this" { parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(t.Context(), ".")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) assert.Len(t, modules, 1) @@ -742,7 +743,7 @@ resource "aws_s3_bucket" "this" { parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(t.Context(), ".")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) assert.Len(t, modules, 1) @@ -796,7 +797,7 @@ policy_rules = { parser := New(fs, "", OptionStopOnHCLError(true), OptionWithTFVarsPaths("main.tfvars")) require.NoError(t, parser.ParseFS(t.Context(), ".")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) assert.Len(t, modules, 1) @@ -832,7 +833,7 @@ resource "aws_s3_bucket" "this" { parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(t.Context(), ".")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) assert.Len(t, modules, 1) @@ -864,7 +865,7 @@ data "http" "example" { parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(t.Context(), ".")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) assert.Len(t, modules, 1) @@ -901,7 +902,7 @@ data "http" "example" { parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(t.Context(), ".")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) assert.Len(t, modules, 1) @@ -1147,7 +1148,7 @@ resource "aws_internet_gateway" "example" { parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(t.Context(), ".")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) require.Len(t, modules, 1) @@ -1172,7 +1173,7 @@ func TestArnAttributeOfBucketIsCorrect(t *testing.T) { parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(t.Context(), ".")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) require.Len(t, modules, 1) @@ -1233,7 +1234,7 @@ data "aws_iam_policy_document" "this" { parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(t.Context(), ".")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) require.Len(t, modules, 1) @@ -1273,7 +1274,7 @@ func TestForEachWithObjectsOfDifferentTypes(t *testing.T) { parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(t.Context(), ".")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) assert.Len(t, modules, 1) } @@ -1308,7 +1309,7 @@ func TestCountMetaArgument(t *testing.T) { parser := New(fsys, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(t.Context(), ".")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) assert.Len(t, modules, 1) @@ -1357,7 +1358,7 @@ func TestCountMetaArgumentInModule(t *testing.T) { parser := New(fsys, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(t.Context(), ".")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) assert.Len(t, modules, tt.expectedCountModules) @@ -1588,6 +1589,15 @@ resource "test_resource" "test" { bar = foo.value } } +}`, + expected: []any{}, + }, + { + name: "unknown for-each", + src: `resource "test_resource" "test" { + dynamic "foo" { + for_each = lookup(foo, "") ? [] : [] + } }`, expected: []any{}, }, @@ -1655,7 +1665,7 @@ func parse(t *testing.T, files map[string]string, opts ...Option) terraform.Modu parser := New(fs, "", opts...) require.NoError(t, parser.ParseFS(t.Context(), ".")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) return modules @@ -1704,6 +1714,299 @@ resource "test_resource" "this" { assert.Equal(t, "test_value", attr.GetRawValue()) } +func TestPopulateContextWithBlockInstances(t *testing.T) { + + tests := []struct { + name string + blockType string + files map[string]string + }{ + { + name: "data blocks with count", + blockType: "data", + files: map[string]string{ + "main.tf": `data "d" "foo" { + count = 1 + value = "Index ${count.index}" +} + +data "b" "foo" { + count = 1 + value = data.d.foo[0].value +} + +data "c" "foo" { + count = 1 + value = data.b.foo[0].value +}`, + }, + }, + { + name: "resource blocks with count", + blockType: "resource", + files: map[string]string{ + "main.tf": `resource "d" "foo" { + count = 1 + value = "Index ${count.index}" +} + +resource "b" "foo" { + count = 1 + value = d.foo[0].value +} + +resource "c" "foo" { + count = 1 + value = b.foo[0].value +}`, + }, + }, + { + name: "module block with count", + blockType: "data", + files: map[string]string{ + "main.tf": `module "a" { + source = "./modules/a" + count = 2 + inp = "Index ${count.index}" +} + +data "b" "foo" { + count = 1 + value = module.a[0].value +} + +data "c" "foo" { + count = 1 + value = data.b.foo[0].value +}`, + "modules/a/main.tf": `variable "inp" {} +output "value" { + value = var.inp +}`, + }, + }, + { + name: "data blocks with for_each", + blockType: "data", + files: map[string]string{ + "main.tf": `data "d" "foo" { + for_each = toset([0]) + value = "Index ${each.key}" +} + +data "b" "foo" { + for_each = data.d.foo + value = each.value.value +} + +data "c" "foo" { + for_each = data.b.foo + value = each.value.value +}`, + }, + }, + { + name: "resource blocks with for_each", + blockType: "resource", + files: map[string]string{ + "main.tf": `resource "d" "foo" { + for_each = toset([0]) + value = "Index ${each.key}" +} + +resource "b" "foo" { + for_each = d.foo + value = each.value.value +} + +resource "c" "foo" { + for_each = b.foo + value = each.value.value +}`, + }, + }, + { + name: "module block with for_each", + blockType: "data", + files: map[string]string{ + "main.tf": `module "a" { + for_each = toset([0]) + source = "./modules/a" + inp = "Index ${each.key}" +} + +data "b" "foo" { + value = module.a["0"].value +}`, + "modules/a/main.tf": `variable "inp" {} +output "value" { + value = var.inp +}`, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + modules := parse(t, tt.files) + require.GreaterOrEqual(t, len(modules), 1) + for _, b := range modules.GetBlocks().OfType(tt.blockType) { + attr := b.GetAttribute("value") + assert.Equal(t, "Index 0", attr.Value().AsString()) + } + }) + } +} + +func TestBlockExpandWithSubmoduleOutput(t *testing.T) { + // `count` meta attributes are incorrectly handled when referencing + // a module output. + files := map[string]string{ + "main.tf": ` +module "foo" { + source = "./modules/foo" +} +data "this_resource" "this" { + count = module.foo.staticZero +} +data "that_resource" "this" { + count = module.foo.staticFive +} + +data "for_each_resource_empty" "this" { + for_each = module.foo.empty_list +} +data "for_each_resource_abc" "this" { + for_each = module.foo.list_abc +} + +data "dynamic_block" "that" { + dynamic "element" { + for_each = module.foo.list_abc + content { + foo = element.value + } + } +} +`, + "modules/foo/main.tf": ` +output "staticZero" { + value = 0 +} +output "staticFive" { + value = 5 +} + +output "empty_list" { + value = [] +} +output "list_abc" { + value = ["a", "b", "c"] +} +`, + } + + modules := parse(t, files) + require.Len(t, modules, 2) + + datas := modules.GetDatasByType("this_resource") + require.Empty(t, datas) + + datas = modules.GetDatasByType("that_resource") + require.Len(t, datas, 5) + + datas = modules.GetDatasByType("for_each_resource_empty") + require.Empty(t, datas) + + datas = modules.GetDatasByType("for_each_resource_abc") + require.Len(t, datas, 3) + + dyn := modules.GetDatasByType("dynamic_block") + require.Len(t, dyn, 1) + require.Len(t, dyn[0].GetBlocks("element"), 3, "dynamic expand") +} + +func TestBlockExpandWithSubmoduleOutputNested(t *testing.T) { + files := map[string]string{ + "main.tf": ` +module "alpha" { + source = "./nestedcount" + set_count = 2 +} +module "beta" { + source = "./nestedcount" + set_count = module.alpha.set_count +} +module "charlie" { + count = module.beta.set_count - 1 + source = "./nestedcount" + set_count = module.beta.set_count +} +data "repeatable" "foo" { + count = module.charlie[0].set_count + value = "foo" +} +`, + "setcount/main.tf": ` +variable "set_count" { + type = number +} +output "set_count" { + value = var.set_count +} +`, + "nestedcount/main.tf": ` +variable "set_count" { + type = number +} +module "nested_mod" { + source = "../setcount" + set_count = var.set_count +} +output "set_count" { + value = module.nested_mod.set_count +} +`, + } + + modules := parse(t, files) + require.Len(t, modules, 7) + + datas := modules.GetDatasByType("repeatable") + assert.Len(t, datas, 2) +} + +func TestBlockCountModules(t *testing.T) { + t.Skip( + "This test is currently failing. " + + "The count passed to `module bar` is not being set correctly. " + + "The count value is sourced from the output of `module foo`. " + + "Submodules cannot be dependent on the output of other submodules right now. ", + ) + // `count` meta attributes are incorrectly handled when referencing + // a module output. + files := map[string]string{ + "main.tf": ` +module "foo" { + source = "./modules/foo" +} +module "bar" { + source = "./modules/foo" + count = module.foo.staticZero +} +`, + "modules/foo/main.tf": ` +output "staticZero" { + value = 0 +} +`, + } + + modules := parse(t, files) + require.Len(t, modules, 2) +} + // TestNestedModulesOptions ensures parser options are carried to the nested // submodule evaluators. // The test will include an invalid module that will fail to download @@ -1815,7 +2118,7 @@ func TestModuleParents(t *testing.T) { ) require.NoError(t, parser.ParseFS(t.Context(), ".")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) // modules only have 'parent'. They do not have children, so create @@ -1877,7 +2180,7 @@ func TestModuleParents(t *testing.T) { children := modChildren[mod] t.Run(n.modulePath, func(t *testing.T) { - if !assert.Equal(t, len(n.children), len(children), "modChildren count for %s", n.modulePath) { + if !assert.Len(t, children, len(n.children), "modChildren count for %s", n.modulePath) { return } for _, child := range children { @@ -2094,7 +2397,7 @@ func Test_LoadLocalCachedModule(t *testing.T) { ) require.NoError(t, parser.ParseFS(t.Context(), ".")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) assert.Len(t, modules, 2) @@ -2123,10 +2426,84 @@ func TestTFVarsFileDoesNotExist(t *testing.T) { ) require.NoError(t, parser.ParseFS(t.Context(), ".")) - _, _, err := parser.EvaluateAll(t.Context()) + _, err := parser.EvaluateAll(t.Context()) assert.ErrorContains(t, err, "file does not exist") } +func Test_OptionsWithEvalHook(t *testing.T) { + fs := testutil.CreateFS(t, map[string]string{ + "main.tf": ` +data "your_custom_data" "this" { + default = ["foo", "foh", "fum"] + unaffected = "bar" +} + +// Testing the hook affects some value, which is used in another evaluateStep +// action (expanding blocks) +data "random_thing" "that" { + dynamic "repeated" { + for_each = data.your_custom_data.this.value + content { + value = repeated.value + } + } +} + +locals { + referenced = data.your_custom_data.this.value + static_ref = data.your_custom_data.this.unaffected +} +`}) + + parser := New(fs, "", OptionWithEvalHook( + // A basic example of how to have a 'default' value for a data block. + // To see a more practical example, see how 'evaluateVariable' handles + // the 'default' value of a variable. + func(ctx *tfcontext.Context, blocks terraform.Blocks, inputVars map[string]cty.Value) { + dataBlocks := blocks.OfType("data") + for _, block := range dataBlocks { + if len(block.Labels()) >= 1 && block.Labels()[0] == "your_custom_data" { + def := block.GetAttribute("default") + ctx.Set(cty.ObjectVal(map[string]cty.Value{ + "value": def.Value(), + }), "data", "your_custom_data", "this") + } + } + + }, + )) + + require.NoError(t, parser.ParseFS(t.Context(), ".")) + + modules, err := parser.EvaluateAll(t.Context()) + require.NoError(t, err) + assert.Len(t, modules, 1) + + rootModule := modules[0] + + // Check the default value of the data block + blocks := rootModule.GetDatasByType("your_custom_data") + assert.Len(t, blocks, 1) + expList := cty.TupleVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("foh"), cty.StringVal("fum")}) + assert.True(t, expList.Equals(blocks[0].GetAttribute("default").Value()).True(), "default value matched list") + assert.Equal(t, "bar", blocks[0].GetAttribute("unaffected").Value().AsString()) + + // Check the referenced 'data.your_custom_data.this.value' exists in the eval + // context, and it is the default value of the data block. + locals := rootModule.GetBlocks().OfType("locals") + assert.Len(t, locals, 1) + assert.True(t, expList.Equals(locals[0].GetAttribute("referenced").Value()).True(), "referenced value matched list") + assert.Equal(t, "bar", locals[0].GetAttribute("static_ref").Value().AsString()) + + // Check the dynamic block is expanded correctly + dynamicBlocks := rootModule.GetDatasByType("random_thing") + assert.Len(t, dynamicBlocks, 1) + assert.Len(t, dynamicBlocks[0].GetBlocks("repeated"), 3) + for i, repeat := range dynamicBlocks[0].GetBlocks("repeated") { + assert.Equal(t, expList.Index(cty.NumberIntVal(int64(i))), repeat.GetAttribute("value").Value()) + } +} + func Test_OptionsWithTfVars(t *testing.T) { fs := testutil.CreateFS(t, map[string]string{ "main.tf": `resource "test" "this" { @@ -2143,7 +2520,7 @@ variable "foo" {} require.NoError(t, parser.ParseFS(t.Context(), ".")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) assert.Len(t, modules, 1) @@ -2173,7 +2550,7 @@ resource "something" "blah" { parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(t.Context(), "code")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) require.Len(t, modules, 1) rootModule := modules[0] @@ -2229,9 +2606,6 @@ variable "baz" {} } func TestLoadChildModulesFromLocalCache(t *testing.T) { - var buf bytes.Buffer - slog.SetDefault(slog.New(log.NewHandler(&buf, &log.Options{Level: log.LevelDebug}))) - fsys := fstest.MapFS{ "main.tf": &fstest.MapFile{Data: []byte(`module "level_1" { source = "./modules/level_1" @@ -2266,21 +2640,33 @@ func TestLoadChildModulesFromLocalCache(t *testing.T) { }`)}, } + var buf bytes.Buffer + logger := slog.New(log.NewHandler(&buf, &log.Options{Level: log.LevelDebug})) + parser := New( fsys, "", OptionStopOnHCLError(true), + OptionWithLogger(logger), ) require.NoError(t, parser.ParseFS(t.Context(), ".")) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) assert.Len(t, modules, 5) - assert.Contains(t, buf.String(), "Using module from Terraform cache .terraform/modules\tsource=\"./modules/level_1\"") - assert.Contains(t, buf.String(), "Using module from Terraform cache .terraform/modules\tsource=\"../level_2\"") - assert.Contains(t, buf.String(), "Using module from Terraform cache .terraform/modules\tsource=\"../level_3\"") - assert.Contains(t, buf.String(), "Using module from Terraform cache .terraform/modules\tsource=\"../level_3\"") + assert.Contains(t, buf.String(), "Using module from Terraform cache .terraform/modules\tmodule=\"root\" source=\"./modules/level_1\"") + assert.Contains(t, buf.String(), "Using module from Terraform cache .terraform/modules\tmodule=\"level_1\" source=\"../level_2\"") + assert.Contains(t, buf.String(), "Using module from Terraform cache .terraform/modules\tmodule=\"level_2\" source=\"../level_3\"") + assert.Contains(t, buf.String(), "Using module from Terraform cache .terraform/modules\tmodule=\"level_2\" source=\"../level_3\"") +} + +func TestNilParser(t *testing.T) { + parser := New( + nil, "", + ) + err := parser.ParseFS(t.Context(), ".") + require.Error(t, err) } func TestLogParseErrors(t *testing.T) { @@ -2369,7 +2755,7 @@ resource "foo" "this" { _, err := parser.Load(t.Context()) require.NoError(t, err) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) res := modules.GetResourcesByType("foo")[0] @@ -2398,13 +2784,45 @@ resource "aws_s3_bucket" "example" { _, err := parser.Load(t.Context()) require.NoError(t, err) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) val := modules.GetResourcesByType("aws_s3_bucket")[0].GetAttribute("bucket").GetRawValue() assert.Nil(t, val) } +func Test_AttrIsRefToOtherBlock(t *testing.T) { + fsys := testutil.CreateFS(t, map[string]string{ + "main.tf": `locals { + baz_idx = 0 +} + +resource "foo" "bar" { + attr = baz.qux[local.baz_idx].attr +} + +resource "baz" "qux" { + count = 1 + attr = "test" +} +`, + }) + + parser := New(fsys, "", OptionStopOnHCLError(true)) + require.NoError(t, parser.ParseFS(t.Context(), ".")) + + modules, err := parser.EvaluateAll(t.Context()) + require.NoError(t, err) + + require.Len(t, modules, 1) + root := modules[0] + foo := root.GetResourcesByType("foo")[0] + fooAttr := foo.GetAttribute("attr") + b, err := root.GetReferencedBlock(fooAttr, foo) + require.NoError(t, err) + require.NotNil(t, b) +} + func TestConfigWithEphemeralBlock(t *testing.T) { fsys := fstest.MapFS{ "main.tf": &fstest.MapFile{Data: []byte(`ephemeral "random_password" "password" { @@ -2451,7 +2869,7 @@ module "bar" { _, err := parser.Load(t.Context()) require.NoError(t, err) - _, _, err = parser.EvaluateAll(t.Context()) + _, err = parser.EvaluateAll(t.Context()) require.NoError(t, err) } @@ -2473,10 +2891,148 @@ resource "foo" "bar" { _, err := parser.Load(t.Context()) require.NoError(t, err) - modules, _, err := parser.EvaluateAll(t.Context()) + modules, err := parser.EvaluateAll(t.Context()) require.NoError(t, err) require.Len(t, modules, 1) foo := modules[0].GetResourcesByType("foo")[0] attr := foo.GetAttribute("attr") assert.False(t, attr.IsResolvable()) } + +// TestInstancedLogger checks if any global logs are generated by the terraform +// parser + evaluation. All logs should be attached to the instanced logger. This +// test does not run all error cases, so it will not capture all log output. +func TestInstancedLogger(t *testing.T) { + // reset global logger + prevLog := slog.Default() + defer slog.SetDefault(prevLog) + + // capture logs to the global logger + var buf bytes.Buffer + + slog.SetDefault(slog.New(log.NewHandler(&buf, &log.Options{ + Level: slog.LevelDebug, + }))) + + // create a new logger for the parser + var instance bytes.Buffer + logger := slog.New(log.NewHandler(&instance, &log.Options{ + Level: slog.LevelDebug, + })) + + opts := []Option{ + OptionWithLogger(logger), + OptionStopOnHCLError(false), + OptionWithDownloads(false), + } + // Run some scenarios that will trigger logs + t.Run("ParserLogs", func(t *testing.T) { + fsys := fstest.MapFS{ + "main.tf": &fstest.MapFile{ + Data: []byte(` + bare words + `)}, + } + + parser := New(fsys, "", opts...) + + // No error is returned, but some parser logs are expected + err := parser.ParseFS(t.Context(), ".") + require.NoError(t, err) + require.NotEmpty(t, instance.String()) + instance.Reset() + }) + + t.Run("Evaluator", func(t *testing.T) { + fsys := fstest.MapFS{ + "main.tf": &fstest.MapFile{ + Data: []byte(` + locals { + phrase = "hello world" + } + + output "phrase" { + value = locals.phrase + } + `)}, + } + + parser := New(fsys, "", opts...) + + err := parser.ParseFS(t.Context(), ".") + require.NoError(t, err) + + _, err = parser.EvaluateAll(t.Context()) + require.NoError(t, err) + + require.NotEmpty(t, instance.String()) + instance.Reset() + }) + + t.Run("ModuleFetching", func(t *testing.T) { + fsys := testutil.CreateFS(t, map[string]string{ + "main.tf": ` + module "invalid" { + source = "totally.invalid" + } + + module "echo" { + source = "./echo" + input = "hello" + } + + locals { + foo = module.echo.value + } + `, + "echo/main.tf": ` + variable "input" { + type = string + } + output "value" { + value = var.input + } + `, + }) + + parser := New(fsys, "", opts...) + + err := parser.ParseFS(t.Context(), ".") + require.NoError(t, err) + + modules, err := parser.EvaluateAll(t.Context()) + require.NoError(t, err) + + require.NotEmpty(t, instance.String()) + require.Len(t, modules, 2) + instance.Reset() + }) + + //nolint:testifylint // linter wants `emptyf`, but the output is not legible + if !assert.Emptyf(t, buf.Bytes(), "logs detected in global logger, all logs should be using the instanced logger") { + t.Log(buf.String()) // Helpful for debugging + } +} + +func TestProvidedWorkingDirectory(t *testing.T) { + const fakeCwd = "/some/path" + fsys := testutil.CreateFS(t, map[string]string{ + "main.tf": ` + resource "foo" "bar" { + cwd = path.cwd + } + `, + }) + + parser := New(fsys, "", OptionWithWorkingDirectoryPath(fakeCwd)) + err := parser.ParseFS(t.Context(), ".") + require.NoError(t, err) + + modules, err := parser.EvaluateAll(t.Context()) + require.NoError(t, err) + + require.Len(t, modules, 1) + foo := modules[0].GetResourcesByType("foo")[0] + attr := foo.GetAttribute("cwd") + require.Equal(t, fakeCwd, attr.Value().AsString()) +} diff --git a/pkg/iac/scanners/terraform/parser/resolvers/cache_integration_test.go b/pkg/iac/scanners/terraform/parser/resolvers/cache_integration_test.go index 6783609c4269..213a896372cf 100644 --- a/pkg/iac/scanners/terraform/parser/resolvers/cache_integration_test.go +++ b/pkg/iac/scanners/terraform/parser/resolvers/cache_integration_test.go @@ -38,7 +38,7 @@ func testOptions(t *testing.T, source string) resolvers.Options { func newRegistry(repoURL string) *httptest.Server { mux := http.NewServeMux() - mux.HandleFunc("/v1/modules/terraform-aws-modules/s3-bucket/aws/download", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/v1/modules/terraform-aws-modules/s3-bucket/aws/download", func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("X-Terraform-Get", repoURL) w.WriteHeader(http.StatusNoContent) }) diff --git a/pkg/iac/scanners/terraform/parser/resolvers/registry_test.go b/pkg/iac/scanners/terraform/parser/resolvers/registry_test.go index d0e679e3dc03..5319a45947ae 100644 --- a/pkg/iac/scanners/terraform/parser/resolvers/registry_test.go +++ b/pkg/iac/scanners/terraform/parser/resolvers/registry_test.go @@ -9,7 +9,7 @@ import ( func Test_getPrivateRegistryTokenFromEnvVars_ErrorsWithNoEnvVarSet(t *testing.T) { token, err := getPrivateRegistryTokenFromEnvVars("registry.example.com") - assert.Equal(t, "", token) + assert.Empty(t, token) assert.Equal(t, "no token was found for the registry at registry.example.com", err.Error()) } diff --git a/pkg/iac/scanners/terraform/performance_test.go b/pkg/iac/scanners/terraform/performance_test.go index ed1a087acc02..640facb603fe 100644 --- a/pkg/iac/scanners/terraform/performance_test.go +++ b/pkg/iac/scanners/terraform/performance_test.go @@ -5,6 +5,8 @@ import ( "io/fs" "testing" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/rules" "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/executor" @@ -21,13 +23,9 @@ func BenchmarkCalculate(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { p := parser.New(f, "", parser.OptionStopOnHCLError(true)) - if err := p.ParseFS(b.Context(), "project"); err != nil { - b.Fatal(err) - } - modules, _, err := p.EvaluateAll(b.Context()) - if err != nil { - b.Fatal(err) - } + require.NoError(b, p.ParseFS(b.Context(), "project")) + modules, err := p.EvaluateAll(b.Context()) + require.NoError(b, err) executor.New().Execute(b.Context(), modules, "project") } } diff --git a/pkg/iac/scanners/terraform/scanner.go b/pkg/iac/scanners/terraform/scanner.go index 5975facab448..d3fdea112079 100644 --- a/pkg/iac/scanners/terraform/scanner.go +++ b/pkg/iac/scanners/terraform/scanner.go @@ -23,6 +23,7 @@ import ( var _ scanners.FSScanner = (*Scanner)(nil) var _ options.ConfigurableScanner = (*Scanner)(nil) +var _ options.RawConfigScanner = (*Scanner)(nil) var _ ConfigurableTerraformScanner = (*Scanner)(nil) type Scanner struct { @@ -44,6 +45,10 @@ func (s *Scanner) SetForceAllDirs(b bool) { s.forceAllDirs = b } +func (s *Scanner) SetScanRawConfig(b bool) { + s.AddExecutorOptions(executor.OptionWithScanRawConfig(b)) +} + func (s *Scanner) AddParserOptions(opts ...parser.Option) { s.parserOpt = append(s.parserOpt, opts...) } @@ -115,7 +120,7 @@ func (s *Scanner) ScanFS(ctx context.Context, target fs.FS, dir string) (scan.Re return nil, err } - modules, _, err := p.EvaluateAll(ctx) + modules, err := p.EvaluateAll(ctx) if err != nil { return nil, err } diff --git a/pkg/iac/scanners/terraform/scanner_test.go b/pkg/iac/scanners/terraform/scanner_test.go index 97eb4972f8e1..6d845c68456f 100644 --- a/pkg/iac/scanners/terraform/scanner_test.go +++ b/pkg/iac/scanners/terraform/scanner_test.go @@ -13,6 +13,7 @@ import ( "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" ) func Test_OptionWithPolicyDirs(t *testing.T) { @@ -28,11 +29,9 @@ func Test_OptionWithPolicyDirs(t *testing.T) { rego.WithPolicyNamespaces("user"), ) require.NoError(t, err) - require.Len(t, results.GetFailed(), 1) failure := results.GetFailed()[0] - assert.Equal(t, "USER-TEST-0123", failure.Rule().AVDID) actualCode, err := failure.GetCode() @@ -181,19 +180,19 @@ resource "aws_sqs_queue_policy" "bad_example" { }`, "/rules/test.rego": ` # METADATA -# title: Buckets should not be evil -# description: You should not allow buckets to be evil +# title: SQS policies should not allow wildcard actions +# description: SQS queue policies should avoid using "*" for actions, as this allows overly permissive access. # scope: package # schemas: # - input: schema.input # related_resources: -# - https://google.com/search?q=is+my+bucket+evil +# - https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-using-identity-based-policies.html # custom: # id: TEST123 # avd_id: AVD-TEST-0123 -# short_code: no-evil-buckets +# short_code: no-wildcard-actions # severity: CRITICAL -# recommended_action: Use a good bucket instead +# recommended_action: Avoid using "*" for actions in SQS policies and specify only required actions. # input: # selector: # - type: cloud @@ -1196,3 +1195,50 @@ data "google_storage_transfer_project_service_account" "production" { }) } } + +func TestScanRawTerraform(t *testing.T) { + check := `# METADATA +# title: Buckets should not be evil +# schemas: +# - input: schema["terraform-raw"] +# custom: +# id: USER0001 +# short_code: evil-bucket +# severity: HIGH +# input: +# selector: +# - type: terraform-raw +package user.bucket001 + +import rego.v1 + +deny contains res if { + some block in input.modules[_].blocks + block.kind == "resource" + block.type == "aws_s3_bucket" + name := block.attributes["bucket"] + name.value == "evil" + res := result.new("Buckets should not be evil", name) +}` + + fsys := fstest.MapFS{ + "main.tf": &fstest.MapFile{Data: []byte(`resource "aws_s3_bucket" "test" { + bucket = "evil" +}`)}, + } + + scanner := New( + ScannerWithAllDirectories(true), + options.WithScanRawConfig(true), + rego.WithEmbeddedLibraries(true), + rego.WithPolicyReader(strings.NewReader(check)), + rego.WithPolicyNamespaces("user"), + ) + + results, err := scanner.ScanFS(t.Context(), fsys, ".") + require.NoError(t, err) + + failed := results.GetFailed() + + assert.Len(t, failed, 1) +} diff --git a/pkg/iac/scanners/terraform/setup_test.go b/pkg/iac/scanners/terraform/setup_test.go index 29ce3dd6c7a8..c40884d04586 100644 --- a/pkg/iac/scanners/terraform/setup_test.go +++ b/pkg/iac/scanners/terraform/setup_test.go @@ -83,13 +83,9 @@ func createModulesFromSource(t *testing.T, source, ext string) terraform.Modules }) p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) - if err := p.ParseFS(t.Context(), "."); err != nil { - t.Fatal(err) - } - modules, _, err := p.EvaluateAll(t.Context()) - if err != nil { - t.Fatalf("parse error: %s", err) - } + require.NoError(t, p.ParseFS(t.Context(), ".")) + modules, err := p.EvaluateAll(t.Context()) + require.NoError(t, err) return modules } diff --git a/pkg/iac/scanners/terraformplan/snapshot/scanner.go b/pkg/iac/scanners/terraformplan/snapshot/scanner.go index 654c9dcaa078..6c678e06ee60 100644 --- a/pkg/iac/scanners/terraformplan/snapshot/scanner.go +++ b/pkg/iac/scanners/terraformplan/snapshot/scanner.go @@ -23,8 +23,11 @@ func (s *Scanner) Name() string { func New(opts ...options.ScannerOption) *Scanner { scanner := &Scanner{ - inner: tfscanner.New(opts...), + inner: tfscanner.New( + append(opts, options.WithScanRawConfig(false))..., + ), } + return scanner } diff --git a/pkg/iac/scanners/terraformplan/tfjson/scanner.go b/pkg/iac/scanners/terraformplan/tfjson/scanner.go index 7fe6969beef0..449bcd244a70 100644 --- a/pkg/iac/scanners/terraformplan/tfjson/scanner.go +++ b/pkg/iac/scanners/terraformplan/tfjson/scanner.go @@ -24,7 +24,7 @@ func (s *Scanner) Name() string { return "Terraform Plan JSON" } -func (s *Scanner) ScanFS(ctx context.Context, fsys fs.FS, dir string) (scan.Results, error) { +func (s *Scanner) ScanFS(_ context.Context, fsys fs.FS, dir string) (scan.Results, error) { var results scan.Results @@ -55,7 +55,9 @@ func (s *Scanner) ScanFS(ctx context.Context, fsys fs.FS, dir string) (scan.Resu func New(opts ...options.ScannerOption) *Scanner { scanner := &Scanner{ - inner: terraform.New(opts...), + inner: terraform.New( + append(opts, options.WithScanRawConfig(false))..., + ), parser: parser.New(), logger: log.WithPrefix("tfjson scanner"), options: opts, diff --git a/pkg/iac/terraform/attribute.go b/pkg/iac/terraform/attribute.go index a9e165596f0b..1526c28eed59 100644 --- a/pkg/iac/terraform/attribute.go +++ b/pkg/iac/terraform/attribute.go @@ -4,6 +4,7 @@ import ( "fmt" "io/fs" "reflect" + "slices" "strconv" "strings" @@ -69,53 +70,55 @@ func (a *Attribute) GetMetadata() iacTypes.Metadata { } func (a *Attribute) GetRawValue() any { - switch typ := a.Type(); typ { - case cty.String: - return a.Value().AsString() - case cty.Bool: - return a.Value().True() - case cty.Number: - float, _ := a.Value().AsBigFloat().Float64() - return float - default: - switch { - case typ.IsTupleType(), typ.IsListType(), typ.IsSetType(): - values := a.Value().AsValueSlice() - if len(values) == 0 { - return []string{} - } - switch values[0].Type() { - case cty.String: - var output []string - for _, value := range values { - output = append(output, value.AsString()) - } - return output - case cty.Number: - var output []float64 - for _, value := range values { - bf := value.AsBigFloat() - f, _ := bf.Float64() - output = append(output, f) + return safeOp(a, func(v cty.Value) any { + switch typ := v.Type(); typ { + case cty.String: + return v.AsString() + case cty.Bool: + return v.True() + case cty.Number: + float, _ := v.AsBigFloat().Float64() + return float + default: + switch { + case typ.IsTupleType(), typ.IsListType(), typ.IsSetType(): + values := a.Value().AsValueSlice() + if len(values) == 0 { + return []string{} } - return output - case cty.Bool: - var output []bool - for _, value := range values { - output = append(output, value.True()) + switch values[0].Type() { + case cty.String: + var output []string + for _, value := range values { + output = append(output, value.AsString()) + } + return output + case cty.Number: + var output []float64 + for _, value := range values { + bf := value.AsBigFloat() + f, _ := bf.Float64() + output = append(output, f) + } + return output + case cty.Bool: + var output []bool + for _, value := range values { + output = append(output, value.True()) + } + return output } - return output } } - } - return nil + return nil + }) } func (a *Attribute) AsBytesValueOrDefault(defaultValue []byte, parent *Block) iacTypes.BytesValue { if a.IsNil() { return iacTypes.BytesDefault(defaultValue, parent.GetMetadata()) } - if a.IsNotResolvable() || !a.IsString() { + if !a.IsResolvable() || !a.IsString() { return iacTypes.BytesUnresolvable(a.GetMetadata()) } return iacTypes.BytesExplicit( @@ -128,7 +131,7 @@ func (a *Attribute) AsStringValueOrDefault(defaultValue string, parent *Block) i if a.IsNil() { return iacTypes.StringDefault(defaultValue, parent.GetMetadata()) } - if a.IsNotResolvable() || !a.IsString() { + if !a.IsResolvable() || !a.IsString() { return iacTypes.StringUnresolvable(a.GetMetadata()) } return iacTypes.StringExplicit( @@ -159,7 +162,7 @@ func (a *Attribute) AsBoolValueOrDefault(defaultValue bool, parent *Block) iacTy if a.IsNil() { return iacTypes.BoolDefault(defaultValue, parent.GetMetadata()) } - if a.IsNotResolvable() || !a.IsBool() { + if !a.IsResolvable() || !a.IsBool() { return iacTypes.BoolUnresolvable(a.GetMetadata()) } return iacTypes.BoolExplicit( @@ -172,7 +175,7 @@ func (a *Attribute) AsIntValueOrDefault(defaultValue int, parent *Block) iacType if a.IsNil() { return iacTypes.IntDefault(defaultValue, parent.GetMetadata()) } - if a.IsNotResolvable() || !a.IsNumber() { + if !a.IsResolvable() || !a.IsNumber() { return iacTypes.IntUnresolvable(a.GetMetadata()) } flt := a.AsNumber() @@ -196,10 +199,6 @@ func (a *Attribute) IsResolvable() bool { return a.Value() != cty.NilVal && a.Value().IsKnown() } -func (a *Attribute) IsNotResolvable() bool { - return !a.IsResolvable() -} - func (a *Attribute) Type() cty.Type { if a == nil { return cty.NilType @@ -211,7 +210,10 @@ func (a *Attribute) IsIterable() bool { if a == nil { return false } - return a.Value().Type().IsListType() || a.Value().Type().IsCollectionType() || a.Value().Type().IsObjectType() || a.Value().Type().IsMapType() || a.Value().Type().IsListType() || a.Value().Type().IsSetType() || a.Value().Type().IsTupleType() + + ty := a.Value().Type() + return ty.IsListType() || ty.IsObjectType() || ty.IsMapType() || + ty.IsSetType() || ty.IsTupleType() } func (a *Attribute) Each(f func(key cty.Value, val cty.Value)) error { @@ -233,47 +235,44 @@ func (a *Attribute) Each(f func(key cty.Value, val cty.Value)) error { } func (a *Attribute) IsString() bool { - if a == nil { - return false - } - return !a.Value().IsNull() && a.Value().IsKnown() && a.Value().Type() == cty.String + return safeOp(a, func(v cty.Value) bool { + return v.Type() == cty.String + }) } func (a *Attribute) IsMapOrObject() bool { - if a == nil || a.Value().IsNull() || !a.Value().IsKnown() { - return false - } - - return a.Value().Type().IsObjectType() || a.Value().Type().IsMapType() + return safeOp(a, func(v cty.Value) bool { + return v.Type().IsObjectType() || v.Type().IsMapType() + }) } func (a *Attribute) IsNumber() bool { - if a != nil && !a.Value().IsNull() && a.Value().IsKnown() { - if a.Value().Type() == cty.Number { + return safeOp(a, func(v cty.Value) bool { + switch v.Type() { + case cty.Number: return true - } - if a.Value().Type() == cty.String { - _, err := strconv.ParseFloat(a.Value().AsString(), 64) + case cty.String: + _, err := strconv.ParseFloat(v.AsString(), 64) return err == nil + default: + return false } - } - - return false + }) } func (a *Attribute) IsBool() bool { - if a == nil { - return false - } - switch a.Value().Type() { - case cty.Bool, cty.Number: - return true - case cty.String: - val := a.Value().AsString() - val = strings.Trim(val, "\"") - return strings.EqualFold(val, "false") || strings.EqualFold(val, "true") - } - return false + return safeOp(a, func(v cty.Value) bool { + switch v.Type() { + case cty.Bool, cty.Number: + return true + case cty.String: + val := v.AsString() + val = strings.Trim(val, "\"") + return strings.EqualFold(val, "false") || strings.EqualFold(val, "true") + default: + return false + } + }) } func (a *Attribute) Value() (ctyVal cty.Value) { @@ -425,41 +424,36 @@ func (a *Attribute) valueToString(value cty.Value) (result iacTypes.StringValue) } } -func (a *Attribute) listContains(val cty.Value, stringToLookFor string, ignoreCase bool) bool { - if a == nil { - return false - } - - valueSlice := val.AsValueSlice() - for _, value := range valueSlice { - if value.IsNull() || !value.IsKnown() { - // there is nothing we can do with this value - continue - } - stringToTest := value - if value.Type().IsObjectType() || value.Type().IsMapType() { - valueMap := value.AsValueMap() - stringToTest = valueMap["key"] - } - if value.Type().HasDynamicTypes() { - for _, extracted := range a.extractListValues() { - if extracted == stringToLookFor { - return true - } - } +func (a *Attribute) listContains(stringToLookFor string, ignoreCase bool) bool { + return safeOp(a, func(v cty.Value) bool { + if !v.Type().IsListType() && !v.Type().IsTupleType() { return false } - if !value.IsKnown() { - continue - } - if ignoreCase && strings.EqualFold(stringToTest.AsString(), stringToLookFor) { - return true - } - if stringToTest.AsString() == stringToLookFor { - return true + + elems := v.AsValueSlice() + for _, el := range elems { + if el.IsNull() || !el.IsKnown() { + // there is nothing we can do with this value + continue + } + stringToTest := el + if el.Type().IsObjectType() || el.Type().IsMapType() { + valueMap := el.AsValueMap() + stringToTest = valueMap["key"] + } + if el.Type().HasDynamicTypes() { + return slices.Contains(a.extractListValues(), stringToLookFor) + } + if ignoreCase { + return strings.EqualFold(stringToTest.AsString(), stringToLookFor) + } + if stringToTest.AsString() == stringToLookFor { + return true + } } - } - return false + + return false + }) } func (a *Attribute) extractListValues() []string { @@ -473,126 +467,64 @@ func (a *Attribute) extractListValues() []string { return values } -func (a *Attribute) mapContains(checkValue any, val cty.Value) bool { - if a == nil { - return false - } - valueMap := val.AsValueMap() - switch t := checkValue.(type) { - case map[any]any: - for k, v := range t { - for key, value := range valueMap { - rawValue := getRawValue(value) - if key == k && evaluate(v, rawValue) { - return true +func (a *Attribute) mapContains(checkValue any) bool { + return safeOp(a, func(v cty.Value) bool { + if !v.Type().IsObjectType() && !v.Type().IsMapType() { + return false + } + valueMap := v.AsValueMap() + switch t := checkValue.(type) { + case map[any]any: + for k, v := range t { + for key, value := range valueMap { + rawValue := getRawValue(value) + if key == k && evaluate(v, rawValue) { + return true + } } } - } - return false - case map[string]any: - for k, v := range t { - for key, value := range valueMap { - rawValue := getRawValue(value) - if key == k && evaluate(v, rawValue) { - return true + return false + case map[string]any: + for k, v := range t { + for key, value := range valueMap { + rawValue := getRawValue(value) + if key == k && evaluate(v, rawValue) { + return true + } } } - } - return false - default: - for key := range valueMap { - if key == checkValue { - return true + return false + default: + for key := range valueMap { + if key == checkValue { + return true + } } + return false } - return false - } + }) } func (a *Attribute) Contains(checkValue any, equalityOptions ...EqualityOption) bool { - if a == nil { - return false - } - ignoreCase := false - for _, option := range equalityOptions { - if option == IgnoreCase { - ignoreCase = true - } - } - val := a.Value() - if val.IsNull() { - return false - } - - if val.Type().IsObjectType() || val.Type().IsMapType() { - return a.mapContains(checkValue, val) - } - - stringToLookFor := fmt.Sprintf("%v", checkValue) - - if val.Type().IsListType() || val.Type().IsTupleType() { - return a.listContains(val, stringToLookFor, ignoreCase) - } + return safeOp(a, func(v cty.Value) bool { + ignoreCase := slices.Contains(equalityOptions, IgnoreCase) - if ignoreCase && containsIgnoreCase(val.AsString(), stringToLookFor) { - return true - } - - return strings.Contains(val.AsString(), stringToLookFor) -} - -func (a *Attribute) OnlyContains(checkValue any) bool { - if a == nil { - return false - } - val := a.Value() - if val.IsNull() { - return false - } + if a.IsMapOrObject() { + return a.mapContains(checkValue) + } - checkSlice, ok := checkValue.([]any) - if !ok { - return false - } + stringToLookFor := fmt.Sprintf("%v", checkValue) - if val.Type().IsListType() || val.Type().IsTupleType() { - for _, value := range val.AsValueSlice() { - found := false - for _, cVal := range checkSlice { - switch t := cVal.(type) { - case string: - if t == value.AsString() { - found = true - break - } - case bool: - if t == value.True() { - found = true - break - } - case int, int8, int16, int32, int64: - i, _ := value.AsBigFloat().Int64() - if t == i { - found = true - break - } - case float32, float64: - f, _ := value.AsBigFloat().Float64() - if t == f { - found = true - break - } - } + if v.Type().IsListType() || v.Type().IsTupleType() { + return a.listContains(stringToLookFor, ignoreCase) + } - } - if !found { - return false - } + if ignoreCase { + return containsIgnoreCase(v.AsString(), stringToLookFor) } - return true - } - return false + return strings.Contains(v.AsString(), stringToLookFor) + }) } func containsIgnoreCase(left, substring string) bool { @@ -606,100 +538,94 @@ const ( ) func (a *Attribute) Equals(checkValue any, equalityOptions ...EqualityOption) bool { - if a == nil { - return false - } - if a.Value().Type() == cty.String { - for _, option := range equalityOptions { - if option == IgnoreCase { - return strings.EqualFold(strings.ToLower(a.Value().AsString()), strings.ToLower(fmt.Sprintf("%v", checkValue))) + return safeOp(a, func(v cty.Value) bool { + switch v.Type() { + case cty.String: + if slices.Contains(equalityOptions, IgnoreCase) { + return strings.EqualFold(strings.ToLower(v.AsString()), strings.ToLower(fmt.Sprintf("%v", checkValue))) } - } - result := strings.EqualFold(a.Value().AsString(), fmt.Sprintf("%v", checkValue)) - return result - } - if a.Value().Type() == cty.Bool { - return a.Value().True() == checkValue - } - if a.Value().Type() == cty.Number { - checkNumber, err := gocty.ToCtyValue(checkValue, cty.Number) - if err != nil { + return strings.EqualFold(v.AsString(), fmt.Sprintf("%v", checkValue)) + case cty.Number: + checkNumber, err := gocty.ToCtyValue(checkValue, cty.Number) + if err != nil { + return false + } + return a.Value().RawEquals(checkNumber) + case cty.Bool: + return v.True() == checkValue + default: return false } - return a.Value().RawEquals(checkNumber) - } - - return false -} - -func (a *Attribute) NotEqual(checkValue any, equalityOptions ...EqualityOption) bool { - return !a.Equals(checkValue, equalityOptions...) + }) } func (a *Attribute) IsTrue() bool { - if a == nil { - return false - } - val := a.Value() - switch val.Type() { - case cty.Bool: - return val.True() - case cty.String: - val := val.AsString() - val = strings.Trim(val, "\"") - return strings.EqualFold(val, "true") - case cty.Number: - val := val.AsBigFloat() - f, _ := val.Float64() - return f > 0 - } - return false + return safeOp(a, func(v cty.Value) bool { + switch v.Type() { + case cty.Bool: + return v.True() + case cty.String: + val := v.AsString() + val = strings.Trim(val, "\"") + return strings.EqualFold(val, "true") + case cty.Number: + bf := v.AsBigFloat() + f, _ := bf.Float64() + return f > 0 + default: + return false + } + }) } func (a *Attribute) IsFalse() bool { - if a == nil { - return false - } - switch a.Value().Type() { - case cty.Bool: - return a.Value().False() - case cty.String: - val := a.Value().AsString() - val = strings.Trim(val, "\"") - return strings.EqualFold(val, "false") - case cty.Number: - val := a.Value().AsBigFloat() - f, _ := val.Float64() - return f == 0 - } - return false + return safeOp(a, func(v cty.Value) bool { + switch v.Type() { + case cty.Bool: + return v.False() + case cty.String: + val := v.AsString() + val = strings.Trim(val, "\"") + return strings.EqualFold(val, "false") + case cty.Number: + bf := v.AsBigFloat() + f, _ := bf.Float64() + return f == 0 + default: + return false + } + }) } func (a *Attribute) IsEmpty() bool { if a == nil { return false } - if a.Value().Type() == cty.String { - return a.Value().AsString() == "" + val := a.Value() + ty := val.Type() + if ty.IsTupleType() || ty.IsObjectType() { + return val.LengthInt() == 0 } - if a.Type().IsListType() || a.Type().IsTupleType() { - return len(a.Value().AsValueSlice()) == 0 + + if val.IsNull() { + return a.isNullAttributeEmpty() } - if a.Type().IsMapType() || a.Type().IsObjectType() { - return len(a.Value().AsValueMap()) == 0 + + if !val.IsKnown() { + return false } - if a.Value().Type() == cty.Number { + + switch { + case ty == cty.String: + return val.AsString() == "" + case ty == cty.Number: // a number can't ever be empty return false + case ty.IsListType(), ty.IsSetType(), ty.IsMapType(): + return val.LengthInt() == 0 } - if a.Value().IsNull() { - return a.isNullAttributeEmpty() - } - return true -} -func (a *Attribute) IsNotEmpty() bool { - return !a.IsEmpty() + return true } func (a *Attribute) isNullAttributeEmpty() bool { @@ -727,33 +653,34 @@ func (a *Attribute) isNullAttributeEmpty() bool { } func (a *Attribute) MapValue(mapKey string) cty.Value { - if a == nil { - return cty.NilVal - } - if a.Type().IsObjectType() || a.Type().IsMapType() { - attrMap := a.Value().AsValueMap() - for key, value := range attrMap { - if key == mapKey { - return value - } + return safeOp(a, func(v cty.Value) cty.Value { + if !v.Type().IsObjectType() && !v.Type().IsMapType() { + return cty.NilVal } - } - return cty.NilVal + m := v.AsValueMap() + if m == nil { + return cty.NilVal + } + return m[mapKey] + }) } func (a *Attribute) AsMapValue() iacTypes.MapValue { - if a.IsNil() || a.IsNotResolvable() || !a.IsMapOrObject() { - return iacTypes.MapValue{} - } - - values := make(map[string]string) - _ = a.Each(func(key, val cty.Value) { - if key.Type() == cty.String && val.Type() == cty.String { - values[key.AsString()] = val.AsString() + return safeOp(a, func(v cty.Value) iacTypes.MapValue { + if !a.IsMapOrObject() { + return iacTypes.MapValue{} } - }) - return iacTypes.Map(values, a.GetMetadata()) + values := make(map[string]string) + v.ForEachElement(func(key cty.Value, val cty.Value) (stop bool) { + if key.Type() == cty.String && key.IsKnown() && + val.Type() == cty.String && val.IsKnown() { + values[key.AsString()] = val.AsString() + } + return false + }) + return iacTypes.Map(values, a.GetMetadata()) + }) } func createDotReferenceFromTraversal(parentRef string, traversals ...hcl.Traversal) (*Reference, error) { @@ -791,88 +718,40 @@ func (a *Attribute) ReferencesBlock(b *Block) bool { return false } -// nolint -func (a *Attribute) AllReferences(blocks ...*Block) []*Reference { +func (a *Attribute) AllReferences() []*Reference { if a == nil { return nil } - refs := a.extractReferences() - for _, block := range blocks { - for _, ref := range refs { - if ref.TypeLabel() == "each" { - if forEachAttr := block.GetAttribute("for_each"); forEachAttr.IsNotNil() { - refs = append(refs, forEachAttr.AllReferences()...) - } - } - } - } - return refs + return a.referencesFromExpression(a.hclAttribute.Expr) } -// nolint -func (a *Attribute) referencesFromExpression(expression hcl.Expression) []*Reference { - var refs []*Reference - switch t := expression.(type) { - case *hclsyntax.ConditionalExpr: - if ref, err := createDotReferenceFromTraversal(a.module, t.TrueResult.Variables()...); err == nil { - refs = append(refs, ref) - } - if ref, err := createDotReferenceFromTraversal(a.module, t.FalseResult.Variables()...); err == nil { - refs = append(refs, ref) +func (a *Attribute) referencesFromExpression(expr hcl.Expression) []*Reference { + if reflect.TypeOf(expr).String() == "*json.expression" { + if ref, err := createDotReferenceFromTraversal(a.module, expr.Variables()...); err == nil { + return []*Reference{ref} } - if ref, err := createDotReferenceFromTraversal(a.module, t.Condition.Variables()...); err == nil { - refs = append(refs, ref) - } - case *hclsyntax.ScopeTraversalExpr: - if ref, err := createDotReferenceFromTraversal(a.module, t.Variables()...); err == nil { - refs = append(refs, ref) - } - case *hclsyntax.TemplateWrapExpr: - refs = a.referencesFromExpression(t.Wrapped) - case *hclsyntax.TemplateExpr: - for _, part := range t.Parts { - ref, err := createDotReferenceFromTraversal(a.module, part.Variables()...) - if err != nil { - continue - } - refs = append(refs, ref) - } - case *hclsyntax.TupleConsExpr: - for _, v := range t.Variables() { - if ref, err := createDotReferenceFromTraversal(a.module, v); err == nil { - refs = append(refs, ref) - } - } - case *hclsyntax.RelativeTraversalExpr: - switch s := t.Source.(type) { - case *hclsyntax.IndexExpr: - if collectionRef, err := createDotReferenceFromTraversal(a.module, s.Collection.Variables()...); err == nil { - key, _ := s.Key.Value(a.ctx.Inner()) - collectionRef.SetKey(key) - refs = append(refs, collectionRef) - } - default: - if ref, err := createDotReferenceFromTraversal(a.module, t.Source.Variables()...); err == nil { - refs = append(refs, ref) - } + return nil + } + + vars := expr.Variables() + refs := make([]*Reference, 0, len(vars)) + for _, v := range vars { + ref, err := createDotReferenceFromTraversal(a.module, v) + if err != nil { + continue } - default: - if reflect.TypeOf(expression).String() == "*json.expression" { - if ref, err := createDotReferenceFromTraversal(a.module, expression.Variables()...); err == nil { - refs = append(refs, ref) + + if relExpr, ok := expr.(*hclsyntax.RelativeTraversalExpr); ok { + if idxExpr, ok := relExpr.Source.(*hclsyntax.IndexExpr); ok { + key, _ := idxExpr.Key.Value(a.ctx.Inner()) + ref.SetKey(key) } } + refs = append(refs, ref) } return refs } -func (a *Attribute) extractReferences() []*Reference { - if a == nil { - return nil - } - return a.referencesFromExpression(a.hclAttribute.Expr) -} - func (a *Attribute) IsResourceBlockReference(resourceType string) bool { if a == nil { return false @@ -901,18 +780,16 @@ func getRawValue(value cty.Value) any { return value } - typeName := value.Type().FriendlyName() - - switch typeName { - case "string": + switch value.Type() { + case cty.String: return value.AsString() - case "number": + case cty.Number: return value.AsBigFloat() - case "bool": + case cty.Bool: return value.True() + default: + return value } - - return value } func (a *Attribute) IsNil() bool { @@ -923,28 +800,31 @@ func (a *Attribute) IsNotNil() bool { return !a.IsNil() } -func (a *Attribute) HasIntersect(checkValues ...any) bool { - if !a.Type().IsListType() && !a.Type().IsTupleType() { - return false - } - - for _, item := range checkValues { - if a.Contains(item) { - return true +func (a *Attribute) AsNumber() float64 { + return safeOp(a, func(v cty.Value) float64 { + switch v.Type() { + case cty.Number: + v, _ := v.AsBigFloat().Float64() + return v + case cty.String: + v, _ := strconv.ParseFloat(v.AsString(), 64) + return v + default: + return 0 } - } - return false - + }) } -func (a *Attribute) AsNumber() float64 { - if a.Value().Type() == cty.Number { - v, _ := a.Value().AsBigFloat().Float64() - return v +func safeOp[T any](a *Attribute, fn func(cty.Value) T) T { + var res T + if a == nil { + return res } - if a.Value().Type() == cty.String { - v, _ := strconv.ParseFloat(a.Value().AsString(), 64) - return v + + val := a.Value() + if val.IsNull() || !val.IsKnown() { + return res } - panic("Attribute is not a number") + + return fn(val) } diff --git a/pkg/iac/terraform/attribute_test.go b/pkg/iac/terraform/attribute_test.go new file mode 100644 index 000000000000..5cbf976e702d --- /dev/null +++ b/pkg/iac/terraform/attribute_test.go @@ -0,0 +1,132 @@ +package terraform + +import ( + "testing" + + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/hashicorp/hcl/v2/json" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/terraform/context" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func Test_AllReferences(t *testing.T) { + cases := []struct { + input string + refs []string + }{ + { + input: "42", // literal + refs: []string{}, + }, + { + input: "5 == 5", // comparison + refs: []string{}, + }, + { + input: "var.foo", + refs: []string{"variable.foo"}, + }, + { + input: "resource.foo.bar[local.idx].name", + refs: []string{"foo.bar", "locals.idx"}, + }, + { + input: "resource.foo.bar[0].name", + refs: []string{"foo.bar[0].name"}, + }, + { + input: "resource.aws_instance.id", + refs: []string{"aws_instance.id"}, + }, + { + input: "data.aws_ami.ubuntu.most_recent", + refs: []string{"data.aws_ami.ubuntu.most_recent"}, + }, + { + input: "5 == 5 ? var.foo : data.aws_ami.ubuntu.most_recent", // conditional + refs: []string{"variable.foo", "data.aws_ami.ubuntu.most_recent"}, + }, + { + input: `{x = 1, y = data.aws_ami.ubuntu.most_recent}`, + refs: []string{"data.aws_ami.ubuntu.most_recent"}, + }, + { + input: `{foo = 1 == 1 ? var.bar : data.aws_ami.ubuntu.most_recent}`, + refs: []string{"variable.bar", "data.aws_ami.ubuntu.most_recent"}, + }, + { + input: `[var.foo, var.bar]`, + refs: []string{"variable.foo", "variable.bar"}, + }, + { + // Expression in the key + input: `{(local.foo): local.bar}`, + refs: []string{"locals.foo", "locals.bar"}, + }, + } + + for _, test := range cases { + t.Run(test.input, func(t *testing.T) { + ctx := context.NewContext(&hcl.EvalContext{}, nil) + + exp, diag := hclsyntax.ParseExpression([]byte(test.input), "", hcl.Pos{Line: 1, Column: 1}) + if diag.HasErrors() { + require.NoError(t, diag) + } + + a := NewAttribute(&hcl.Attribute{ + Name: "test", + Expr: exp, + Range: hcl.Range{}, + NameRange: hcl.Range{}, + }, ctx, "", types.Metadata{}, Reference{}, "", nil) + + refs := a.AllReferences() + humanRefs := make([]string, 0, len(refs)) + for _, ref := range refs { + humanRefs = append(humanRefs, ref.HumanReadable()) + } + + require.ElementsMatch(t, test.refs, humanRefs) + }) + } +} + +func Test_AllReferences_JSON(t *testing.T) { + tests := []struct { + src string + expected []string + }{ + { + src: `"hello ${noun}"`, + expected: []string{"noun"}, + }, + } + + for _, tt := range tests { + t.Run(tt.src, func(t *testing.T) { + expr, diag := json.ParseExpression([]byte(tt.src), "") + if diag.HasErrors() { + require.NoError(t, diag) + } + + attr := NewAttribute(&hcl.Attribute{ + Name: "test", + Expr: expr, + Range: hcl.Range{}, + NameRange: hcl.Range{}, + }, context.NewContext(&hcl.EvalContext{}, nil), "", types.Metadata{}, Reference{}, "", nil) + + refs := attr.AllReferences() + humanRefs := make([]string, 0, len(refs)) + for _, ref := range refs { + humanRefs = append(humanRefs, ref.HumanReadable()) + } + + require.ElementsMatch(t, tt.expected, humanRefs) + }) + } +} diff --git a/pkg/iac/terraform/block.go b/pkg/iac/terraform/block.go index 3dd45389153a..e474fd4bc1c6 100644 --- a/pkg/iac/terraform/block.go +++ b/pkg/iac/terraform/block.go @@ -35,7 +35,8 @@ type Block struct { } func NewBlock(hclBlock *hcl.Block, ctx *context.Context, moduleBlock *Block, parentBlock *Block, moduleSource string, - moduleFS fs.FS, index ...cty.Value) *Block { + moduleFS fs.FS, index ...cty.Value, +) *Block { if ctx == nil { ctx = context.NewContext(&hcl.EvalContext{}, nil) } @@ -317,7 +318,6 @@ func (b *Block) GetAttribute(name string) *Attribute { // Supports special paths like "count.index," "each.key," and "each.value." // The path may contain indices, keys and dots (used as separators). func (b *Block) GetValueByPath(path string) cty.Value { - if path == "count.index" || path == "each.key" || path == "each.value" { return b.Context().GetByDot(path) } @@ -428,18 +428,17 @@ func getValueByPath(val cty.Value, path []string) (cty.Value, error) { } func (b *Block) GetNestedAttribute(name string) (*Attribute, *Block) { - parts := strings.Split(name, ".") blocks := parts[:len(parts)-1] attrName := parts[len(parts)-1] working := b for _, subBlock := range blocks { - if checkBlock := working.GetBlock(subBlock); checkBlock == nil { + checkBlock := working.GetBlock(subBlock) + if checkBlock == nil { return nil, working - } else { - working = checkBlock } + working = checkBlock } if working != nil { @@ -472,7 +471,6 @@ func (b *Block) FullLocalName() string { } func (b *Block) FullName() string { - if b.moduleBlock != nil { return fmt.Sprintf( "%s.%s", @@ -585,7 +583,7 @@ func (b *Block) ExpandBlock() error { if child.Type() == "dynamic" { blocks, err := child.expandDynamic() if err != nil { - errs = multierror.Append(errs, err) + errs = multierror.Append(errs, fmt.Errorf("block %q: %w", child.TypeLabel(), err)) continue } expanded = append(expanded, blocks...) @@ -614,6 +612,10 @@ func (b *Block) expandDynamic() ([]*Block, error) { return nil, fmt.Errorf("invalid for-each in %s block: %w", b.FullLocalName(), err) } + if !forEachVal.IsKnown() { + return nil, errors.New("for-each must be known") + } + var ( expanded []*Block errs error diff --git a/pkg/iac/terraform/context/context.go b/pkg/iac/terraform/context/context.go index 14e29a9a8378..be7acb7af933 100644 --- a/pkg/iac/terraform/context/context.go +++ b/pkg/iac/terraform/context/context.go @@ -63,7 +63,7 @@ func (c *Context) Get(parts ...string) cty.Value { return attr } - if !(attr.IsKnown() && attr.Type().IsObjectType()) { + if !attr.IsKnown() || !attr.Type().IsObjectType() { return cty.NilVal } curr = attr diff --git a/pkg/iac/terraform/context/context_test.go b/pkg/iac/terraform/context/context_test.go index dfd8e05e5fac..f44a2f44db3c 100644 --- a/pkg/iac/terraform/context/context_test.go +++ b/pkg/iac/terraform/context/context_test.go @@ -267,7 +267,7 @@ func TestReplace(t *testing.T) { assert.Equal(t, cty.NumberIntVal(-1), ctx.GetByDot("my.value")) }) - t.Run("empty path", func(t *testing.T) { + t.Run("empty path", func(_ *testing.T) { underlying := &hcl.EvalContext{} ctx := NewContext(underlying, nil) ctx.Replace(cty.NumberIntVal(-1), "") diff --git a/pkg/iac/terraform/export.go b/pkg/iac/terraform/export.go new file mode 100644 index 000000000000..532abce6b4c6 --- /dev/null +++ b/pkg/iac/terraform/export.go @@ -0,0 +1,109 @@ +package terraform + +import ( + "encoding/json" + + "github.com/samber/lo" + "github.com/zclconf/go-cty/cty" + ctyjson "github.com/zclconf/go-cty/cty/json" + + "github.com/aquasecurity/trivy/pkg/log" +) + +func ExportModules(modules Modules) TerraformConfigExport { + return TerraformConfigExport{ + Modules: lo.Map(modules, func(m *Module, _ int) ModuleExport { + return m.ToModuleExport() + }), + } +} + +// TODO(nikpivkin): export directly to OPA values +type TerraformConfigExport struct { + Modules []ModuleExport `json:"modules"` +} + +type ModuleExport struct { + RootPath string `json:"root_path"` + ModulePath string `json:"module_path"` + ParentPath string `json:"parent_path"` + Blocks []BlockExport `json:"blocks"` +} + +type BlockExport struct { + Metadata any `json:"__defsec_metadata"` + Kind string `json:"kind"` + Type string `json:"type"` + Name string `json:"name"` + Attributes map[string]AttributeExport `json:"attributes"` +} + +type AttributeExport struct { + Metadata any `json:"__defsec_metadata"` + Name string `json:"name"` + Value json.RawMessage `json:"value"` + Known bool `json:"known"` +} + +func (c *Module) ToModuleExport() ModuleExport { + var parentPath string + if parentModule := c.Parent(); parentModule != nil { + parentPath = parentModule.ModulePath() + } + return ModuleExport{ + RootPath: c.RootPath(), + ModulePath: c.ModulePath(), + ParentPath: parentPath, + Blocks: lo.Map(c.blocks, func(b *Block, _ int) BlockExport { + return b.ToBlockExport() + }), + } +} + +func (b *Block) ToBlockExport() BlockExport { + typeLabel := b.TypeLabel() + nameLabel := b.NameLabel() + + if len(b.Labels()) == 1 { + nameLabel = typeLabel + typeLabel = "" + } + + return BlockExport{ + Metadata: b.metadata.ToRego(), + Kind: b.Type(), + Type: typeLabel, + Name: nameLabel, + Attributes: lo.SliceToMap( + b.attributes, func(a *Attribute) (string, AttributeExport) { + return a.Name(), a.ToAttributeExport() + }, + ), + } +} + +func (a *Attribute) ToAttributeExport() AttributeExport { + value, known := ExportCtyValueToJSON(a.Value()) + return AttributeExport{ + Metadata: a.metadata.ToRego(), + Name: a.Name(), + Known: known, + Value: value, + } +} + +func ExportCtyValueToJSON(v cty.Value) (json.RawMessage, bool) { + if v.IsNull() || !v.IsKnown() { + return json.RawMessage("null"), false + } + + ty := v.Type() + bytes, err := ctyjson.Marshal(v, ty) + if err != nil { + log.WithPrefix("terraform").Debug("Failed to marshal cty value", + log.String("value", v.GoString()), log.Err(err)) + return json.RawMessage("null"), false + } + + return json.RawMessage(bytes), true +} diff --git a/pkg/iac/terraform/module.go b/pkg/iac/terraform/module.go index d098e56f7a6e..48771fb2c195 100644 --- a/pkg/iac/terraform/module.go +++ b/pkg/iac/terraform/module.go @@ -58,8 +58,8 @@ func (c *Module) GetBlocks() Blocks { return c.blocks } -func (h *Module) GetBlocksByTypeLabel(typeLabel string) Blocks { - return h.blockMap[typeLabel] +func (c *Module) GetBlocksByTypeLabel(typeLabel string) Blocks { + return c.blockMap[typeLabel] } func (c *Module) getBlocksByType(blockType string, labels ...string) Blocks { diff --git a/pkg/iac/types/bool.go b/pkg/iac/types/bool.go index ae12d39198a2..4830e5e94f17 100755 --- a/pkg/iac/types/bool.go +++ b/pkg/iac/types/bool.go @@ -89,8 +89,8 @@ func (b BoolValue) IsFalse() bool { return !b.Value() } -func (s BoolValue) ToRego() any { - m := s.metadata.ToRego().(map[string]any) - m["value"] = s.Value() +func (b BoolValue) ToRego() any { + m := b.metadata.ToRego().(map[string]any) + m["value"] = b.Value() return m } diff --git a/pkg/iac/types/bytes.go b/pkg/iac/types/bytes.go index 01358ad3c8d9..7949213d7e80 100755 --- a/pkg/iac/types/bytes.go +++ b/pkg/iac/types/bytes.go @@ -87,8 +87,8 @@ func BytesUnresolvable(m Metadata) BytesValue { return b } -func (s BytesValue) ToRego() any { - m := s.metadata.ToRego().(map[string]any) - m["value"] = string(s.Value()) +func (b BytesValue) ToRego() any { + m := b.metadata.ToRego().(map[string]any) + m["value"] = string(b.Value()) return m } diff --git a/pkg/iac/types/int.go b/pkg/iac/types/int.go index 24c06b72753e..7e46f6899a5b 100755 --- a/pkg/iac/types/int.go +++ b/pkg/iac/types/int.go @@ -83,13 +83,6 @@ func (b IntValue) GetRawValue() any { return b.value } -func (b IntValue) NotEqualTo(i int) bool { - if b.metadata.isUnresolvable { - return false - } - return b.value != i -} - func (b IntValue) EqualTo(i int) bool { if b.metadata.isUnresolvable { return false @@ -111,8 +104,8 @@ func (b IntValue) GreaterThan(i int) bool { return b.value > i } -func (s IntValue) ToRego() any { - m := s.metadata.ToRego().(map[string]any) - m["value"] = s.Value() +func (b IntValue) ToRego() any { + m := b.metadata.ToRego().(map[string]any) + m["value"] = b.Value() return m } diff --git a/pkg/iac/types/map.go b/pkg/iac/types/map.go index 01d90543b4e8..95aa625e9eda 100755 --- a/pkg/iac/types/map.go +++ b/pkg/iac/types/map.go @@ -85,8 +85,8 @@ func (b MapValue) HasKey(key string) bool { return ok } -func (s MapValue) ToRego() any { - m := s.metadata.ToRego().(map[string]any) - m["value"] = s.Value() +func (b MapValue) ToRego() any { + m := b.metadata.ToRego().(map[string]any) + m["value"] = b.Value() return m } diff --git a/pkg/iac/types/sources.go b/pkg/iac/types/sources.go index 02e43dac3a35..6b7a612ec200 100644 --- a/pkg/iac/types/sources.go +++ b/pkg/iac/types/sources.go @@ -5,10 +5,13 @@ type Source string const ( SourceDockerfile Source = "dockerfile" SourceKubernetes Source = "kubernetes" - SourceRbac Source = "rbac" // deprecated - please use "kubernetes" instead - SourceDefsec Source = "defsec" // deprecated - please use "cloud" instead - SourceCloud Source = "cloud" - SourceYAML Source = "yaml" - SourceJSON Source = "json" - SourceTOML Source = "toml" + // Deprecated: use "kubernetes" instead + SourceRbac Source = "rbac" + // Deprecated: use "cloud" instead + SourceDefsec Source = "defsec" + SourceCloud Source = "cloud" + SourceYAML Source = "yaml" + SourceJSON Source = "json" + SourceTOML Source = "toml" + SourceTerraformRaw Source = "terraform-raw" ) diff --git a/pkg/iac/types/string.go b/pkg/iac/types/string.go index 0630d18b9c8e..0e6ba02113f2 100755 --- a/pkg/iac/types/string.go +++ b/pkg/iac/types/string.go @@ -5,14 +5,6 @@ import ( "strings" ) -type StringEqualityOption int - -const ( - IgnoreCase StringEqualityOption = iota - IsPallindrome - IgnoreWhitespace -) - func String(str string, m Metadata) StringValue { return StringValue{ value: str, @@ -56,22 +48,20 @@ func (l StringValueList) AsStrings() (output []string) { return output } -type stringCheckFunc func(string, string) bool - -func (b StringValue) MarshalJSON() ([]byte, error) { +func (s StringValue) MarshalJSON() ([]byte, error) { return json.Marshal(map[string]any{ - "value": b.value, - "metadata": b.metadata, + "value": s.value, + "metadata": s.metadata, }) } -func (b *StringValue) UnmarshalJSON(data []byte) error { +func (s *StringValue) UnmarshalJSON(data []byte) error { var keys map[string]any if err := json.Unmarshal(data, &keys); err != nil { return err } if keys["value"] != nil { - b.value = keys["value"].(string) + s.value = keys["value"].(string) } if keys["metadata"] != nil { raw, err := json.Marshal(keys["metadata"]) @@ -82,7 +72,7 @@ func (b *StringValue) UnmarshalJSON(data []byte) error { if err := json.Unmarshal(raw, &m); err != nil { return err } - b.metadata = m + s.metadata = m } return nil } @@ -113,8 +103,8 @@ func (s StringValue) Value() string { return s.value } -func (b StringValue) GetRawValue() any { - return b.value +func (s StringValue) GetRawValue() any { + return s.value } func (s StringValue) IsEmpty() bool { @@ -131,64 +121,40 @@ func (s StringValue) IsNotEmpty() bool { return s.value != "" } -func (s StringValue) EqualTo(value string, equalityOptions ...StringEqualityOption) bool { +func (s StringValue) EqualTo(value string) bool { if s.metadata.isUnresolvable { return false } - return s.executePredicate(value, func(a, b string) bool { return a == b }, equalityOptions...) + return s.value == value } -func (s StringValue) NotEqualTo(value string, equalityOptions ...StringEqualityOption) bool { +func (s StringValue) NotEqualTo(value string) bool { if s.metadata.isUnresolvable { return false } - return !s.EqualTo(value, equalityOptions...) + return s.value != value } -func (s StringValue) StartsWith(prefix string, equalityOptions ...StringEqualityOption) bool { +func (s StringValue) StartsWith(prefix string) bool { if s.metadata.isUnresolvable { return false } - return s.executePredicate(prefix, strings.HasPrefix, equalityOptions...) + return strings.HasPrefix(s.value, prefix) } -func (s StringValue) EndsWith(suffix string, equalityOptions ...StringEqualityOption) bool { +func (s StringValue) EndsWith(suffix string) bool { if s.metadata.isUnresolvable { return false } - return s.executePredicate(suffix, strings.HasSuffix, equalityOptions...) + return strings.HasSuffix(s.value, suffix) } -func (s StringValue) Contains(value string, equalityOptions ...StringEqualityOption) bool { +func (s StringValue) Contains(value string) bool { if s.metadata.isUnresolvable { return false } - return s.executePredicate(value, strings.Contains, equalityOptions...) -} - -func (s StringValue) executePredicate(value string, fn stringCheckFunc, equalityOptions ...StringEqualityOption) bool { - subjectString := s.value - searchString := value - - for _, eqOpt := range equalityOptions { - switch eqOpt { - case IgnoreCase: - subjectString = strings.ToLower(subjectString) - searchString = strings.ToLower(searchString) - case IsPallindrome: - var result string - for _, v := range subjectString { - result = string(v) + result - } - subjectString = result - case IgnoreWhitespace: - subjectString = strings.ReplaceAll(subjectString, " ", "") - searchString = strings.ReplaceAll(searchString, " ", "") - } - } - - return fn(subjectString, searchString) + return strings.Contains(s.value, value) } diff --git a/pkg/iac/types/string_test.go b/pkg/iac/types/string_test.go index a923d193d85e..39f50e00299e 100644 --- a/pkg/iac/types/string_test.go +++ b/pkg/iac/types/string_test.go @@ -8,35 +8,12 @@ import ( "github.com/stretchr/testify/require" ) -func Test_StringValueEqualTo(t *testing.T) { - testCases := []struct { - desc string - input string - check string - ignoreCase bool - expected bool - }{ - { - desc: "return truw when string is equal", - input: "something", - check: "", - expected: false, - }, - } - for _, tC := range testCases { - t.Run(tC.desc, func(t *testing.T) { - - }) - } -} - func Test_StringValueStartsWith(t *testing.T) { testCases := []struct { - desc string - input string - prefix string - ignoreCase bool - expected bool + desc string + input string + prefix string + expected bool }{ { desc: "return true when starts with", @@ -50,32 +27,10 @@ func Test_StringValueStartsWith(t *testing.T) { prefix: "nothing", expected: false, }, - { - desc: "return true when starts with", - input: "something", - prefix: "SOME", - ignoreCase: true, - expected: true, - }, - { - desc: "return false when does not start with", - input: "something", - prefix: "SOME", - expected: false, - }, } - for _, tC := range testCases { - t.Run(tC.desc, func(t *testing.T) { - - val := String(tC.input, fakeMetadata) - - var options []StringEqualityOption - - if tC.ignoreCase { - options = append(options, IgnoreCase) - } - - assert.Equal(t, tC.expected, val.StartsWith(tC.prefix, options...)) + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + assert.Equal(t, tc.expected, String(tc.input, fakeMetadata).StartsWith(tc.prefix)) }) } } diff --git a/pkg/iac/types/time.go b/pkg/iac/types/time.go index e04d090964a8..f031a60d4522 100755 --- a/pkg/iac/types/time.go +++ b/pkg/iac/types/time.go @@ -10,21 +10,21 @@ type TimeValue struct { value time.Time } -func (b TimeValue) MarshalJSON() ([]byte, error) { +func (t TimeValue) MarshalJSON() ([]byte, error) { return json.Marshal(map[string]any{ - "value": b.value.Format(time.RFC3339), - "metadata": b.metadata, + "value": t.value.Format(time.RFC3339), + "metadata": t.metadata, }) } -func (b *TimeValue) UnmarshalJSON(data []byte) error { +func (t *TimeValue) UnmarshalJSON(data []byte) error { var keys map[string]any if err := json.Unmarshal(data, &keys); err != nil { return err } if keys["value"] != nil { if ti, err := time.Parse(time.RFC3339, keys["value"].(string)); err == nil { - b.value = ti + t.value = ti } } if keys["metadata"] != nil { @@ -36,7 +36,7 @@ func (b *TimeValue) UnmarshalJSON(data []byte) error { if err := json.Unmarshal(raw, &m); err != nil { return err } - b.metadata = m + t.metadata = m } return nil } diff --git a/pkg/javadb/client.go b/pkg/javadb/client.go index 1655c3bc1fdd..76ad94c8688d 100644 --- a/pkg/javadb/client.go +++ b/pkg/javadb/client.go @@ -135,7 +135,7 @@ func Update() error { return err } -func Clear(ctx context.Context, cacheDir string) error { +func Clear(_ context.Context, cacheDir string) error { return os.RemoveAll(dbDir(cacheDir)) } diff --git a/pkg/k8s/report/report_test.go b/pkg/k8s/report/report_test.go index 61d382246cd0..0d4cd16d3fbc 100644 --- a/pkg/k8s/report/report_test.go +++ b/pkg/k8s/report/report_test.go @@ -828,10 +828,10 @@ func Test_separateMisconfigReports(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { reports := SeparateMisconfigReports(tt.k8sReport, tt.scanners) - assert.Equal(t, len(tt.expectedReports), len(reports)) + assert.Len(t, reports, len(tt.expectedReports)) for i := range reports { - assert.Equal(t, len(tt.expectedReports[i].Resources), len(reports[i].Report.Resources)) + assert.Len(t, reports[i].Report.Resources, len(tt.expectedReports[i].Resources)) for j, m := range tt.expectedReports[i].Resources { assert.Equal(t, m.Kind, reports[i].Report.Resources[j].Kind) } diff --git a/pkg/k8s/scanner/scanner.go b/pkg/k8s/scanner/scanner.go index 764ab9718795..05c0ff33711d 100644 --- a/pkg/k8s/scanner/scanner.go +++ b/pkg/k8s/scanner/scanner.go @@ -16,6 +16,7 @@ import ( "github.com/aquasecurity/go-version/pkg/version" "github.com/aquasecurity/trivy-kubernetes/pkg/artifacts" "github.com/aquasecurity/trivy-kubernetes/pkg/bom" + "github.com/aquasecurity/trivy/pkg/cache" cmd "github.com/aquasecurity/trivy/pkg/commands/artifact" "github.com/aquasecurity/trivy/pkg/digest" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -174,10 +175,19 @@ func (s *Scanner) scanMisconfigs(ctx context.Context, k8sArtifacts []*artifacts. } s.opts.Target = dir + origCacheBackend := s.opts.CacheBackend + // Using an in-memory cache is safe since scanning k8s is not supported in client/server mode, + // and the cache created during file system scanning is removed after each scan. + s.opts.CacheBackend = string(cache.TypeMemory) + + defer func() { + // remove config files + removeDir(dir) + // restore cache backend + s.opts.CacheBackend = origCacheBackend + }() configReport, err := s.runner.ScanFilesystem(ctx, s.opts) - // remove config files after scanning - removeDir(dir) if err != nil { return nil, xerrors.Errorf("failed to scan filesystem: %w", err) @@ -205,6 +215,7 @@ func (s *Scanner) scanMisconfigs(ctx context.Context, k8sArtifacts []*artifacts. return resources, nil } + func (s *Scanner) filter(ctx context.Context, r types.Report, artifact *artifacts.Artifact) (report.Resource, error) { var err error r, err = s.runner.Filter(ctx, s.opts, r) diff --git a/pkg/k8s/scanner/scanner_test.go b/pkg/k8s/scanner/scanner_test.go index d22e05014bc1..2e7441e438db 100644 --- a/pkg/k8s/scanner/scanner_test.go +++ b/pkg/k8s/scanner/scanner_test.go @@ -284,7 +284,7 @@ func TestScanner_Scan(t *testing.T) { require.NoError(t, err) gotComponents := lo.Values(got.BOM.Components()) - require.Equal(t, len(tt.wantComponents), len(gotComponents)) + require.Len(t, gotComponents, len(tt.wantComponents)) sort.Slice(gotComponents, func(i, j int) bool { switch { diff --git a/pkg/k8s/writer_test.go b/pkg/k8s/writer_test.go index 80e99a301ce8..c50fdb17b0d6 100644 --- a/pkg/k8s/writer_test.go +++ b/pkg/k8s/writer_test.go @@ -320,6 +320,42 @@ Failures: 0 (CRITICAL: 0) ════════════════════════════════════════ Your config file is not good. +See https://google.com/search?q=bad%20config +────────────────────────────────────────`, + }, + { + name: "vulns and misconfig with `--report all`", + report: report.Report{ + ClusterName: "test", + Resources: []report.Resource{ + deployOrionWithSingleVuln, + deployOrionWithSingleMisconfig, + }, + }, + scanners: types.Scanners{types.VulnerabilityScanner, types.MisconfigScanner}, + severities: []dbTypes.Severity{ + dbTypes.SeverityCritical, dbTypes.SeverityLow, + }, + reportType: report.AllReport, + expectedOutput: `namespace: default, deploy: orion () +==================================== +Total: 1 (LOW: 1, CRITICAL: 0) + +┌─────────┬───────────────┬──────────┬─────────┬───────────────────┬───────────────┬───────────────────────────────────────────┐ +│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │ +├─────────┼───────────────┼──────────┼─────────┼───────────────────┼───────────────┼───────────────────────────────────────────┤ +│ foo/bar │ CVE-2022-1111 │ LOW │ unknown │ v0.0.1 │ v0.0.2 │ https://avd.aquasec.com/nvd/cve-2022-1111 │ +└─────────┴───────────────┴──────────┴─────────┴───────────────────┴───────────────┴───────────────────────────────────────────┘ + +namespace: default, deploy: orion () +==================================== +Tests: 1 (SUCCESSES: 0, FAILURES: 1) +Failures: 1 (LOW: 1, CRITICAL: 0) + + (LOW): Oh no, a bad config. +════════════════════════════════════════ +Your config file is not good. + See https://google.com/search?q=bad%20config ────────────────────────────────────────`, }, diff --git a/pkg/licensing/expression/expression.go b/pkg/licensing/expression/expression.go index 3758408da72c..48d944515948 100644 --- a/pkg/licensing/expression/expression.go +++ b/pkg/licensing/expression/expression.go @@ -24,16 +24,16 @@ func parse(license string) (Expression, error) { return l.result, nil } -func Normalize(license string, funcs ...NormalizeFunc) (string, error) { +func Normalize(license string, funcs ...NormalizeFunc) (Expression, error) { expr, err := parse(license) if err != nil { - return "", xerrors.Errorf("license (%s) parse error: %w", license, err) + return nil, xerrors.Errorf("license (%s) parse error: %w", license, err) } for _, fn := range funcs { expr = normalize(expr, fn) } - return expr.String(), nil + return expr, nil } func normalize(expr Expression, fn NormalizeFunc) Expression { diff --git a/pkg/licensing/expression/expression_test.go b/pkg/licensing/expression/expression_test.go index e476d6be0fcc..ae36f5185db7 100644 --- a/pkg/licensing/expression/expression_test.go +++ b/pkg/licensing/expression/expression_test.go @@ -50,7 +50,7 @@ func TestNormalize(t *testing.T) { } require.NoError(t, err) - assert.Equalf(t, tt.want, got, "NormalizeWithExpression(%v)", tt.license) + assert.Equalf(t, tt.want, got.String(), "NormalizeWithExpression(%v)", tt.license) }) } } diff --git a/pkg/licensing/normalize.go b/pkg/licensing/normalize.go index 006a1f27600f..d7d51e3cb1a4 100644 --- a/pkg/licensing/normalize.go +++ b/pkg/licensing/normalize.go @@ -754,10 +754,10 @@ func LaxSplitLicenses(str string) []string { str = versionRegexp.ReplaceAllString(str, "$1-$4") for _, s := range strings.Fields(str) { s = strings.Trim(s, "()") - switch { - case s == "": + switch s { + case "": continue - case s == "AND" || s == "OR": + case "AND", "OR": continue default: licenses = append(licenses, Normalize(s)) diff --git a/pkg/licensing/scanner.go b/pkg/licensing/scanner.go index 70747d8ceb76..3234e5476698 100644 --- a/pkg/licensing/scanner.go +++ b/pkg/licensing/scanner.go @@ -1,11 +1,16 @@ package licensing import ( - "slices" + "regexp" + "strings" + + "github.com/samber/lo" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/licensing/expression" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/set" ) type ScannerOption struct { @@ -22,18 +27,63 @@ func NewScanner(categories map[types.LicenseCategory][]string) Scanner { } func (s *Scanner) Scan(licenseName string) (types.LicenseCategory, string) { - normalized := NormalizeLicense(expression.SimpleExpr{License: licenseName}) - var normalizedName string - switch normalized := normalized.(type) { + expr, err := expression.Normalize(licenseName, NormalizeLicense) + if err != nil { + return types.CategoryUnknown, "" + } + category := s.detectCategory(expr) + + return category, categoryToSeverity(category).String() +} + +// detectCategory recursively parses license expression to detect correct license category: +// For the simple expression - use category of license +// For the compound expression: +// - `AND` operator - use category with maximum severity +// - `OR` operator - use category with minimum severity +// - one of expression has `UNKNOWN` category - use `UNKNOWN` category +func (s *Scanner) detectCategory(license expression.Expression) types.LicenseCategory { + var category types.LicenseCategory + + switch e := license.(type) { case expression.SimpleExpr: - normalizedName = normalized.License + category = s.licenseToCategory(e) case expression.CompoundExpr: - normalizedName = normalized.String() + left := s.detectCategory(e.Left()) + right := s.detectCategory(e.Right()) + if left == types.CategoryUnknown || right == types.CategoryUnknown { + category = types.CategoryUnknown + break + } + comparison := func(a, b types.LicenseCategory) bool { + if e.Conjunction() == expression.TokenAnd { + return int(categoryToSeverity(a)) > int(categoryToSeverity(b)) // Take the maximum severity for `AND` operator + } + return int(categoryToSeverity(a)) < int(categoryToSeverity(b)) // Take the minimum severity for `OR` operator + } + category = lo.MaxBy([]types.LicenseCategory{left, right}, comparison) } - for category, names := range s.categories { - if slices.Contains(names, normalizedName) { - return category, categoryToSeverity(category).String() + return category +} + +// ScanTextLicense checks license names from `categories` as glob patterns and matches licenseText against those patterns. +// If a match is found, it returns `unknown` category and severity. +func (s *Scanner) ScanTextLicense(licenseText string) (types.LicenseCategory, string) { + for cat, names := range s.categories { + for _, name := range names { + if !strings.HasPrefix(name, LicenseTextPrefix) { + continue + } + + n := strings.TrimPrefix(name, LicenseTextPrefix) + match, err := regexp.MatchString(n, licenseText) + if err != nil { + log.WithPrefix("license").Debug("Failed to match license text", log.String("license_text", licenseText), log.Err(err)) + continue + } else if match { + return cat, categoryToSeverity(cat).String() + } } } return types.CategoryUnknown, dbTypes.SeverityUnknown.String() @@ -52,3 +102,16 @@ func categoryToSeverity(category types.LicenseCategory) dbTypes.Severity { } return dbTypes.SeverityUnknown } + +func (s *Scanner) licenseToCategory(se expression.SimpleExpr) types.LicenseCategory { + normalizedNames := set.New(se.String()) // The license name with suffix (e.g. AGPL-1.0-or-later) + normalizedNames.Append(se.License) // Also accept the license name without suffix (e.g. AGPL-1.0) + + for category, names := range s.categories { + if normalizedNames.Intersection(set.New(names...)).Size() > 0 { + return category + } + } + + return types.CategoryUnknown +} diff --git a/pkg/licensing/scanner_test.go b/pkg/licensing/scanner_test.go index 2cce9d1d13e6..9bc836a064a6 100644 --- a/pkg/licensing/scanner_test.go +++ b/pkg/licensing/scanner_test.go @@ -42,6 +42,17 @@ func TestScanner_Scan(t *testing.T) { wantCategory: types.CategoryForbidden, wantSeverity: "CRITICAL", }, + { + name: "`categories` contains license with suffix", + categories: map[types.LicenseCategory][]string{ + types.CategoryNotice: { + "LGPL-2.0-only", + }, + }, + licenseName: "LGPL-2.0-only", + wantCategory: types.CategoryNotice, + wantSeverity: "LOW", + }, { name: "restricted", categories: map[types.LicenseCategory][]string{ @@ -57,6 +68,74 @@ func TestScanner_Scan(t *testing.T) { wantCategory: types.CategoryRestricted, wantSeverity: "HIGH", }, + { + name: "unnormalized license", + categories: map[types.LicenseCategory][]string{ + types.CategoryRestricted: { + expression.BSD3Clause, + expression.MIT, + }, + }, + licenseName: "MIT License", + wantCategory: types.CategoryRestricted, + wantSeverity: "HIGH", + }, + { + name: "compound OR license", + categories: map[types.LicenseCategory][]string{ + types.CategoryForbidden: { + expression.GPL30, + }, + types.CategoryRestricted: { + expression.Apache20, + }, + }, + licenseName: expression.GPL30 + " OR " + expression.Apache20, + wantCategory: types.CategoryRestricted, + wantSeverity: "HIGH", + }, + { + name: "compound AND license", + categories: map[types.LicenseCategory][]string{ + types.CategoryForbidden: { + expression.GPL30, + }, + types.CategoryRestricted: { + expression.Apache20, + }, + }, + licenseName: expression.GPL30 + " AND " + expression.Apache20, + wantCategory: types.CategoryForbidden, + wantSeverity: "CRITICAL", + }, + { + name: "compound unknown license", + categories: map[types.LicenseCategory][]string{ + types.CategoryForbidden: { + expression.GPL30, + }, + }, + licenseName: expression.GPL30 + " AND " + expression.Apache20, + wantCategory: types.CategoryUnknown, + wantSeverity: "UNKNOWN", + }, + { + name: "compound long license, recursive", + categories: map[types.LicenseCategory][]string{ + types.CategoryForbidden: { + expression.GPL30, + }, + types.CategoryRestricted: { + expression.BSD3Clause, + }, + types.CategoryNotice: { + expression.Apache20, + }, + }, + licenseName: "(" + expression.BSD3Clause + " OR " + expression.GPL30 + ")" + " AND (" + expression.GPL30 + " OR " + expression.Apache20 + ")", + wantCategory: types.CategoryRestricted, + wantSeverity: "HIGH", + }, { name: "unknown", categories: make(map[types.LicenseCategory][]string), @@ -74,3 +153,59 @@ func TestScanner_Scan(t *testing.T) { }) } } + +func TestScanner_ScanTextLicense(t *testing.T) { + tests := []struct { + name string + categories map[types.LicenseCategory][]string + licenseText string + wantCategory types.LicenseCategory + wantSeverity string + }{ + { + name: "match license text pattern", + categories: map[types.LicenseCategory][]string{ + types.CategoryForbidden: {"text://Apache.*License"}, + }, + licenseText: "Apache Software Foundation License", + wantCategory: types.CategoryForbidden, + wantSeverity: "CRITICAL", + }, + { + name: "no match returns unknown", + categories: map[types.LicenseCategory][]string{ + types.CategoryNotice: {"text://MIT.*"}, + }, + licenseText: "Some other license text", + wantCategory: types.CategoryUnknown, + wantSeverity: "UNKNOWN", + }, + { + name: "invalid regexp is ignored", + categories: map[types.LicenseCategory][]string{ + types.CategoryRestricted: {"text://("}, + }, + licenseText: "MIT License", + wantCategory: types.CategoryUnknown, + wantSeverity: "UNKNOWN", + }, + { + name: "category without text prefix is ignored", + categories: map[types.LicenseCategory][]string{ + types.CategoryNotice: {"MIT"}, + }, + licenseText: "MIT License", + wantCategory: types.CategoryUnknown, + wantSeverity: "UNKNOWN", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := licensing.NewScanner(tt.categories) + gotCategory, gotSeverity := s.ScanTextLicense(tt.licenseText) + assert.Equal(t, tt.wantCategory, gotCategory) + assert.Equal(t, tt.wantSeverity, gotSeverity) + }) + } +} diff --git a/pkg/log/handler_test.go b/pkg/log/handler_test.go index dc39aa587833..82ab5fc5e39a 100644 --- a/pkg/log/handler_test.go +++ b/pkg/log/handler_test.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "log/slog" - "os" "strings" "testing" "testing/slogtest" @@ -37,13 +36,6 @@ func TestColorHandler(t *testing.T) { compareLines(t, got, wantLines) } -func TestSlog(t *testing.T) { - logger := slog.New(log.NewHandler(os.Stdout, &log.Options{Level: slog.LevelWarn})) - logger.Info("foo") - logger.Warn("warn message", slog.Group("group2", slog.String("key5", "value5"))) - logger.Error("error", slog.Int("key3", 3), slog.Group("group3", slog.String("key4", "value4"))) -} - func TestWithAttrsAndWithGroup(t *testing.T) { t.Run("single group", func(t *testing.T) { var buf bytes.Buffer diff --git a/pkg/mapfs/fs.go b/pkg/mapfs/fs.go index 5ce894b8101c..fea95f1daf71 100644 --- a/pkg/mapfs/fs.go +++ b/pkg/mapfs/fs.go @@ -110,6 +110,42 @@ func (m *FS) FilterFunc(fn func(path string, d fs.DirEntry) (bool, error)) (*FS, return newFS, nil } +// CopyDir copies a directory from the local filesystem into the in-memory filesystem. +// This function works similarly to the Unix command "cp -r src dst", recursively copying +// the entire directory structure from the source to the destination. +// +// The function: +// 1. Walks through all files and subdirectories in the source directory +// 2. Recreates the directory structure in the in-memory filesystem +// 3. Copies all files while preserving their relative paths +// +// Parameters: +// - src: The source directory path in the local filesystem +// - dst: The destination directory path in the in-memory filesystem +// +// For example, if src is "/tmp/data" and dst is "app/", then: +// - A file at "/tmp/data/settings.json" will be copied to "app/data/settings.json" +// - A file at "/tmp/data/subdir/config.yaml" will be copied to "app/data/subdir/config.yaml" +func (m *FS) CopyDir(src, dst string) error { + base := filepath.Base(src) + return filepath.WalkDir(src, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + rel, err := filepath.Rel(src, path) + if err != nil { + return err + } + dstPath := filepath.Join(dst, base, rel) + if d.IsDir() { + return m.MkdirAll(dstPath, d.Type()) + } + return m.WriteFile(dstPath, path) + }) +} + +// TODO(knqyf263): Remove this method and replace with CopyDir func (m *FS) CopyFilesUnder(dir string) error { return filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { if err != nil { diff --git a/pkg/mapfs/fs_test.go b/pkg/mapfs/fs_test.go index d00d5626a9a9..943878654f82 100644 --- a/pkg/mapfs/fs_test.go +++ b/pkg/mapfs/fs_test.go @@ -21,7 +21,7 @@ type fileInfo struct { } var ( - filePerm = lo.Ternary(runtime.GOOS == "windows", fs.FileMode(0666), fs.FileMode(0644)) + filePerm = lo.Ternary(runtime.GOOS == "windows", fs.FileMode(0o666), fs.FileMode(0o644)) helloFileInfo = fileInfo{ name: "hello.txt", fileMode: filePerm, @@ -36,13 +36,13 @@ var ( } virtualFileInfo = fileInfo{ name: "virtual.txt", - fileMode: 0600, + fileMode: 0o600, isDir: false, size: 7, } cdirFileInfo = fileInfo{ name: "c", - fileMode: fs.FileMode(0700) | fs.ModeDir, + fileMode: fs.FileMode(0o700) | fs.ModeDir, isDir: true, size: 256, } @@ -50,13 +50,13 @@ var ( func initFS(t *testing.T) *mapfs.FS { fsys := mapfs.New() - require.NoError(t, fsys.MkdirAll("a/b/c", 0700)) - require.NoError(t, fsys.MkdirAll("a/b/empty", 0700)) + require.NoError(t, fsys.MkdirAll("a/b/c", 0o700)) + require.NoError(t, fsys.MkdirAll("a/b/empty", 0o700)) require.NoError(t, fsys.WriteFile("hello.txt", "testdata/hello.txt")) require.NoError(t, fsys.WriteFile("a/b/b.txt", "testdata/b.txt")) require.NoError(t, fsys.WriteFile("a/b/c/c.txt", "testdata/c.txt")) require.NoError(t, fsys.WriteFile("a/b/c/.dotfile", "testdata/dotfile")) - require.NoError(t, fsys.WriteVirtualFile("a/b/c/virtual.txt", []byte("virtual"), 0600)) + require.NoError(t, fsys.WriteVirtualFile("a/b/c/virtual.txt", []byte("virtual"), 0o600)) return fsys } @@ -163,12 +163,12 @@ func TestFS_ReadDir(t *testing.T) { want: []dirEntry{ { name: "a", - fileMode: fs.FileMode(0700) | fs.ModeDir, + fileMode: fs.FileMode(0o700) | fs.ModeDir, isDir: true, size: 0x100, fileInfo: fileInfo{ name: "a", - fileMode: fs.FileMode(0700) | fs.ModeDir, + fileMode: fs.FileMode(0o700) | fs.ModeDir, isDir: true, size: 0x100, }, @@ -213,7 +213,7 @@ func TestFS_ReadDir(t *testing.T) { }, { name: "virtual.txt", - fileMode: 0600, + fileMode: 0o600, isDir: false, size: 0, fileInfo: virtualFileInfo, @@ -501,3 +501,58 @@ func TestFS_WithUnderlyingRoot(t *testing.T) { require.NoError(t, err) assert.False(t, fi.IsDir()) } + +func TestFS_CopyDir(t *testing.T) { + tmpDst := "dst" + tests := []struct { + name string + src string + dst string + wantFiles map[string]string // path in dst: want content + wantErr require.ErrorAssertionFunc + }{ + { + name: "copy testdata to dst/", + src: "testdata", + dst: tmpDst, + wantFiles: map[string]string{ + tmpDst + "/testdata/hello.txt": "hello world", + tmpDst + "/testdata/b.txt": "bbb", + tmpDst + "/testdata/c.txt": "", + tmpDst + "/testdata/dotfile": "dotfile", + tmpDst + "/testdata/subdir/foo.txt": "", + tmpDst + "/testdata/subdir/..foo.txt": "", + }, + wantErr: require.NoError, + }, + { + name: "copy subdir only", + src: "testdata/subdir", + dst: tmpDst + "/subdir2", + wantFiles: map[string]string{ + tmpDst + "/subdir2/subdir/foo.txt": "", + tmpDst + "/subdir2/subdir/..foo.txt": "", + }, + wantErr: require.NoError, + }, + { + name: "non-existent src", + src: "testdata/no_such_dir", + dst: tmpDst + "/no_such", + wantErr: require.Error, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fsys := mapfs.New() + err := fsys.CopyDir(tt.src, tt.dst) + tt.wantErr(t, err) + for path, want := range tt.wantFiles { + got, err := fsys.ReadFile(path) + require.NoError(t, err, path) + assert.Equal(t, want, string(got), path) + } + }) + } +} diff --git a/pkg/misconf/scanner.go b/pkg/misconf/scanner.go index 7a866da2baf5..a153f3319f38 100644 --- a/pkg/misconf/scanner.go +++ b/pkg/misconf/scanner.go @@ -8,6 +8,7 @@ import ( "io/fs" "os" "path/filepath" + "slices" "sort" "strings" @@ -74,6 +75,7 @@ type ScannerOption struct { TerraformTFVars []string CloudFormationParamVars []string TfExcludeDownloaded bool + RawConfigScanners []types.ConfigType K8sVersion string FilePatterns []string @@ -185,7 +187,7 @@ func (s *Scanner) filterFS(fsys fs.FS) (fs.FS, error) { }) var foundRelevantFile bool - filter := func(path string, d fs.DirEntry) (bool, error) { + filter := func(path string, _ fs.DirEntry) (bool, error) { file, err := fsys.Open(path) if err != nil { return false, err @@ -302,6 +304,10 @@ func scannerOptions(t detection.FileType, opt ScannerOption) ([]options.ScannerO opts = append(opts, regoOpts...) } + opts = append(opts, options.WithScanRawConfig( + slices.Contains(opt.RawConfigScanners, enablediacTypes[t])), + ) + switch t { case detection.FileTypeHelm: return addHelmOpts(opts, opt), nil @@ -454,11 +460,11 @@ func CreateDataFS(dataPaths []string, opts ...string) (fs.FS, []string, error) { // Check if k8sVersion is provided if len(opts) > 0 { k8sVersion := opts[0] - if err := fsys.MkdirAll("system", 0700); err != nil { + if err := fsys.MkdirAll("system", 0o700); err != nil { return nil, nil, err } data := []byte(fmt.Sprintf(`{"k8s": {"version": %q}}`, k8sVersion)) - if err := fsys.WriteVirtualFile("system/k8s-version.json", data, 0600); err != nil { + if err := fsys.WriteVirtualFile("system/k8s-version.json", data, 0o600); err != nil { return nil, nil, err } } @@ -564,7 +570,7 @@ func NewCauseWithCode(underlying scan.Result, flat scan.FlatResult) types.CauseM if code, err := underlying.GetCode(); err == nil { cause.Code = types.Code{ - Lines: lo.Map(code.Lines, func(l scan.Line, i int) types.Line { + Lines: lo.Map(code.Lines, func(l scan.Line, _ int) types.Line { return types.Line{ Number: l.Number, Content: l.Content, diff --git a/pkg/misconf/scanner_test.go b/pkg/misconf/scanner_test.go index 81a6c4c0497b..45dd65528450 100644 --- a/pkg/misconf/scanner_test.go +++ b/pkg/misconf/scanner_test.go @@ -150,7 +150,7 @@ func TestScanner_Scan(t *testing.T) { // Create a virtual filesystem for testing fsys := mapfs.New() for _, f := range tt.files { - err := fsys.WriteVirtualFile(f.path, f.content, 0666) + err := fsys.WriteVirtualFile(f.path, f.content, 0o666) require.NoError(t, err) } @@ -172,7 +172,7 @@ func TestScanner_Scan(t *testing.T) { func Test_createPolicyFS(t *testing.T) { t.Run("outside pwd", func(t *testing.T) { tmpDir := t.TempDir() - require.NoError(t, os.MkdirAll(filepath.Join(tmpDir, "subdir", "testdir"), 0750)) + require.NoError(t, os.MkdirAll(filepath.Join(tmpDir, "subdir", "testdir"), 0o750)) f, got, err := CreatePolicyFS([]string{filepath.Join(tmpDir, "subdir", "testdir")}) assertFS(t, tmpDir, f, got, err) }) @@ -181,7 +181,7 @@ func Test_createPolicyFS(t *testing.T) { func Test_CreateDataFS(t *testing.T) { t.Run("outside pwd", func(t *testing.T) { tmpDir := t.TempDir() - require.NoError(t, os.MkdirAll(filepath.Join(tmpDir, "subdir", "testdir"), 0750)) + require.NoError(t, os.MkdirAll(filepath.Join(tmpDir, "subdir", "testdir"), 0o750)) f, got, err := CreateDataFS([]string{filepath.Join(tmpDir, "subdir", "testdir")}) assertFS(t, tmpDir, f, got, err) }) diff --git a/pkg/module/module.go b/pkg/module/module.go index 3a74f2c06246..d5191a4bde68 100644 --- a/pkg/module/module.go +++ b/pkg/module/module.go @@ -483,7 +483,7 @@ func (m *wasmModule) Analyze(ctx context.Context, input analyzer.AnalysisInput) return &result, nil } -func (m *wasmModule) PreScan(ctx context.Context, target *types.ScanTarget, options types.ScanOptions) error { +func (m *wasmModule) PreScan(_ context.Context, _ *types.ScanTarget, _ types.ScanOptions) error { // TODO: Implement return nil } diff --git a/pkg/module/module_test.go b/pkg/module/module_test.go index 802dec8b10ec..4912786a8b91 100644 --- a/pkg/module/module_test.go +++ b/pkg/module/module_test.go @@ -94,7 +94,7 @@ func TestManager_Register(t *testing.T) { // Confirm that wasm modules are generated beforehand var count int - err := filepath.WalkDir("testdata", func(path string, d fs.DirEntry, err error) error { + err := filepath.WalkDir("testdata", func(path string, _ fs.DirEntry, _ error) error { if filepath.Ext(path) == ".wasm" { count++ } diff --git a/pkg/oci/artifact.go b/pkg/oci/artifact.go index acef07923ed8..1abb2aa99e48 100644 --- a/pkg/oci/artifact.go +++ b/pkg/oci/artifact.go @@ -131,11 +131,11 @@ func (a *Artifact) Download(ctx context.Context, dir string, opt DownloadOption) // Take the file name of the first layer if not specified fileName := opt.Filename if fileName == "" { - if v, ok := manifest.Layers[0].Annotations[titleAnnotation]; !ok { + v, ok := manifest.Layers[0].Annotations[titleAnnotation] + if !ok { return xerrors.Errorf("annotation %s is missing", titleAnnotation) - } else { - fileName = v } + fileName = v } layerMediaType, err := layer.MediaType() diff --git a/pkg/parallel/pipeline.go b/pkg/parallel/pipeline.go index 7dbc70f3e17c..75530c9e7e15 100644 --- a/pkg/parallel/pipeline.go +++ b/pkg/parallel/pipeline.go @@ -108,8 +108,5 @@ func (p *Pipeline[T, U]) Do(ctx context.Context) error { // Check whether any of the goroutines failed. Since g is accumulating the // errors, we don't need to send them (or check for them) in the individual // results sent on the channel. - if err := g.Wait(); err != nil { - return err - } - return nil + return g.Wait() } diff --git a/pkg/parallel/walk.go b/pkg/parallel/walk.go index be569e5bc0e8..3fe65c40ad7a 100644 --- a/pkg/parallel/walk.go +++ b/pkg/parallel/walk.go @@ -81,10 +81,7 @@ func WalkDir[T any](ctx context.Context, fsys fs.FS, root string, parallel int, // Check whether any of the goroutines failed. Since g is accumulating the // errors, we don't need to send them (or check for them) in the individual // results sent on the channel. - if err := g.Wait(); err != nil { - return err - } - return nil + return g.Wait() } func walk[T any](ctx context.Context, fsys fs.FS, path string, c chan T, onFile onFile[T]) error { diff --git a/pkg/plugin/index_test.go b/pkg/plugin/index_test.go index 6af82960f023..1c5c6745a277 100644 --- a/pkg/plugin/index_test.go +++ b/pkg/plugin/index_test.go @@ -18,7 +18,7 @@ func TestManager_Update(t *testing.T) { tempDir := t.TempDir() t.Setenv("XDG_DATA_HOME", tempDir) - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { _, err := w.Write([]byte(`this is index`)) assert.NoError(t, err) })) diff --git a/pkg/plugin/manager_unix_test.go b/pkg/plugin/manager_unix_test.go index d37073dcb659..2299407249f8 100644 --- a/pkg/plugin/manager_unix_test.go +++ b/pkg/plugin/manager_unix_test.go @@ -57,7 +57,7 @@ func modifyManifest(t *testing.T, worktree, version string) { require.NoError(t, err) b = bytes.ReplaceAll(b, []byte("0.2.0"), []byte(version)) - err = os.WriteFile(manifestPath, b, 0644) + err = os.WriteFile(manifestPath, b, 0o644) require.NoError(t, err) } @@ -200,7 +200,7 @@ func TestManager_Install(t *testing.T) { // For plugin index pluginDir := filepath.Join(dst, ".trivy", "plugins") - err := os.MkdirAll(pluginDir, 0755) + err := os.MkdirAll(pluginDir, 0o755) require.NoError(t, err) _, err = fsutils.CopyFile("testdata/.trivy/plugins/index.yaml", filepath.Join(pluginDir, "index.yaml")) require.NoError(t, err) diff --git a/pkg/policy/policy_test.go b/pkg/policy/policy_test.go index bea9d3aa5ce1..420d652c4a8d 100644 --- a/pkg/policy/policy_test.go +++ b/pkg/policy/policy_test.go @@ -360,7 +360,7 @@ func TestClient_DownloadBuiltinChecks(t *testing.T) { func TestClient_Clear(t *testing.T) { cacheDir := t.TempDir() - err := os.MkdirAll(filepath.Join(cacheDir, "policy"), 0755) + err := os.MkdirAll(filepath.Join(cacheDir, "policy"), 0o755) require.NoError(t, err) c, err := policy.NewClient(cacheDir, true, "") diff --git a/pkg/purl/purl.go b/pkg/purl/purl.go index 16f636713089..390e7f5286cd 100644 --- a/pkg/purl/purl.go +++ b/pkg/purl/purl.go @@ -7,7 +7,7 @@ import ( cn "github.com/google/go-containerregistry/pkg/name" version "github.com/knqyf263/go-rpm-version" - packageurl "github.com/package-url/packageurl-go" + "github.com/package-url/packageurl-go" "github.com/samber/lo" "golang.org/x/xerrors" @@ -42,6 +42,10 @@ const ( NamespaceOCP = "ocp" TypeUnknown = "unknown" + + // Temporary type before being added in github.com/package-url/packageurl-go + // cf. https://github.com/package-url/purl-spec/issues/454 + packageurlTypeBottlerocket = "bottlerocket" ) type PackageURL packageurl.PackageURL @@ -80,6 +84,8 @@ func New(t ftypes.TargetType, metadata types.Metadata, pkg ftypes.Package) (*Pac if metadata.OS != nil { namespace = string(metadata.OS.Family) } + case packageurlTypeBottlerocket: + qualifiers = append(qualifiers, packageurl.Qualifiers{packageurl.Qualifier{Key: "distro", Value: fmt.Sprintf("bottlerocket-%s", metadata.OS.Name)}}...) case packageurl.TypeApk: var qs packageurl.Qualifiers name, namespace, qs = parseApk(name, metadata.OS) @@ -443,6 +449,7 @@ func parseJulia(pkgName, pkgUUID string) (string, string, packageurl.Qualifiers) return namespace, name, qualifiers } +// nolint: gocyclo func purlType(t ftypes.TargetType) string { switch t { case ftypes.Jar, ftypes.Pom, ftypes.Gradle, ftypes.Sbt: @@ -482,6 +489,8 @@ func purlType(t ftypes.TargetType) string { ftypes.OpenSUSELeap, ftypes.OpenSUSETumbleweed, ftypes.SLES, ftypes.SLEMicro, ftypes.Photon, ftypes.Azure, ftypes.CBLMariner: return packageurl.TypeRPM + case ftypes.Bottlerocket: + return packageurlTypeBottlerocket case TypeOCI: return packageurl.TypeOCI case ftypes.Julia: diff --git a/pkg/purl/purl_test.go b/pkg/purl/purl_test.go index 5607551da77c..e0fc6900d4f7 100644 --- a/pkg/purl/purl_test.go +++ b/pkg/purl/purl_test.go @@ -440,6 +440,42 @@ func TestNewPackageURL(t *testing.T) { }, }, }, + { + name: "bottlerocket package", + typ: ftypes.Bottlerocket, + metadata: types.Metadata{ + OS: &ftypes.OS{ + Family: ftypes.Bottlerocket, + Name: "1.34.0", + }, + }, + pkg: ftypes.Package{ + ID: "glibc@2.40", + Name: "glibc", + Version: "2.40", + Epoch: 1, + Arch: "x86_64", + }, + want: &purl.PackageURL{ + Type: "bottlerocket", + Name: "glibc", + Version: "2.40", + Qualifiers: packageurl.Qualifiers{ + { + Key: "arch", + Value: "x86_64", + }, + { + Key: "epoch", + Value: "1", + }, + { + Key: "distro", + Value: "bottlerocket-1.34.0", + }, + }, + }, + }, } for _, tc := range testCases { @@ -710,6 +746,47 @@ func TestPackageURL_Package(t *testing.T) { }, }, }, + { + name: "bottlerocket with epoch", + pkgURL: &purl.PackageURL{ + Type: "bottlerocket", + Name: "glibc", + Version: "2.40", + Qualifiers: packageurl.Qualifiers{ + { + Key: "epoch", + Value: "1", + }, + { + Key: "distro", + Value: "bottlerocket-1.34.0", + }, + }, + }, + wantPkg: &ftypes.Package{ + ID: "glibc@2.40", + Name: "glibc", + Version: "2.40", + Epoch: 1, + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: "bottlerocket", + Name: "glibc", + Version: "2.40", + Qualifiers: packageurl.Qualifiers{ + { + Key: "epoch", + Value: "1", + }, + { + Key: "distro", + Value: "bottlerocket-1.34.0", + }, + }, + }, + }, + }, + }, { name: "wrong epoch", pkgURL: &purl.PackageURL{ diff --git a/pkg/rekortest/server.go b/pkg/rekortest/server.go index 965ca82bfa38..3c818a7a72fc 100644 --- a/pkg/rekortest/server.go +++ b/pkg/rekortest/server.go @@ -332,12 +332,12 @@ func NewServer(t *testing.T) *Server { resEntries := models.LogEntry{} for _, uuid := range params.EntryUUIDs { - if e, ok := entries[uuid]; !ok { + e, ok := entries[uuid] + if !ok { http.Error(w, "no such uuid", http.StatusNotFound) return - } else { - resEntries[uuid] = e } + resEntries[uuid] = e } w.Header().Set("Content-Type", "application/json") err = json.NewEncoder(w).Encode([]models.LogEntry{resEntries}) diff --git a/pkg/remote/remote_test.go b/pkg/remote/remote_test.go index d35051757eb4..a2efab1d72ab 100644 --- a/pkg/remote/remote_test.go +++ b/pkg/remote/remote_test.go @@ -51,7 +51,7 @@ func setupDockerConfig(t *testing.T, content string) { cd := setupConfigDir(t) p := filepath.Join(cd, "config.json") - err := os.WriteFile(p, []byte(content), 0600) + err := os.WriteFile(p, []byte(content), 0o600) require.NoError(t, err) } diff --git a/pkg/report/sarif.go b/pkg/report/sarif.go index dadec92db4cb..e3237110dfc6 100644 --- a/pkg/report/sarif.go +++ b/pkg/report/sarif.go @@ -114,16 +114,16 @@ func (sw *SarifWriter) addSarifResult(data *sarifData) { } func getRuleIndex(id string, indexes map[string]int) int { - if i, ok := indexes[id]; ok { + i, ok := indexes[id] + if ok { return i - } else { - l := len(indexes) - indexes[id] = l - return l } + l := len(indexes) + indexes[id] = l + return l } -func (sw *SarifWriter) Write(ctx context.Context, report types.Report) error { +func (sw *SarifWriter) Write(_ context.Context, report types.Report) error { sarifReport, err := sarif.New(sarif.Version210) if err != nil { return xerrors.Errorf("error creating a new sarif template: %w", err) @@ -332,7 +332,7 @@ func ToPathUri(input string, resultClass types.ResultClass) string { if resultClass != types.ClassOSPkg { return input } - var matches = pathRegex.FindStringSubmatch(input) + matches := pathRegex.FindStringSubmatch(input) if matches != nil { input = matches[pathRegex.SubexpIndex("path")] } diff --git a/pkg/report/table/licensing.go b/pkg/report/table/licensing.go index be9ca32381dc..6c6e95786877 100644 --- a/pkg/report/table/licensing.go +++ b/pkg/report/table/licensing.go @@ -52,8 +52,6 @@ func (r *pkgLicenseRenderer) Render(result types.Result) { r.printf("Total: %d (%s)\n\n", total, strings.Join(summaries, ", ")) r.tableWriter.Render() - - return } func (r *pkgLicenseRenderer) setHeaders() { @@ -136,8 +134,6 @@ func (r *fileLicenseRenderer) Render(result types.Result) { r.printf("Total: %d (%s)\n\n", total, strings.Join(summaries, ", ")) r.tableWriter.Render() - - return } func (r *fileLicenseRenderer) setHeaders() { diff --git a/pkg/report/table/misconfig.go b/pkg/report/table/misconfig.go index 025f184d4632..8ca2e88cbc51 100644 --- a/pkg/report/table/misconfig.go +++ b/pkg/report/table/misconfig.go @@ -77,7 +77,6 @@ func (r *misconfigRenderer) Render(result types.Result) { if r.trace { r.outputTrace(result.Target, result.Misconfigurations) } - return } func (r *misconfigRenderer) countSeverities(misconfigs []types.DetectedMisconfiguration) map[string]int { @@ -116,7 +115,6 @@ func (r *misconfigRenderer) renderSingle(target string, typ ftypes.TargetType, m } func (r *misconfigRenderer) renderSummary(misconf types.DetectedMisconfiguration) { - // show pass/fail/exception unless we are only showing failures if r.includeNonFailures { switch misconf.Status { diff --git a/pkg/report/table/secret.go b/pkg/report/table/secret.go index 2ef556fe70f8..bf5401c29c65 100644 --- a/pkg/report/table/secret.go +++ b/pkg/report/table/secret.go @@ -53,7 +53,6 @@ func (r *secretRenderer) Render(result types.Result) { for _, m := range result.Secrets { r.renderSingle(result.Target, m) } - return } func (r *secretRenderer) countSeverities(secrets []types.DetectedSecret) map[string]int { @@ -85,7 +84,6 @@ func (r *secretRenderer) renderSingle(target string, secret types.DetectedSecret } func (r *secretRenderer) renderSummary(secret types.DetectedSecret) { - // severity switch secret.Severity { case severityCritical: diff --git a/pkg/report/table/summary.go b/pkg/report/table/summary.go index dee4f06aee6c..e5a6fd25aca9 100644 --- a/pkg/report/table/summary.go +++ b/pkg/report/table/summary.go @@ -173,9 +173,10 @@ func (r *summaryRenderer) Render(report types.Report) { for _, result := range splitAggregatedPackages(report.Results) { resultType := string(result.Type) - if result.Class == types.ClassSecret { + switch result.Class { + case types.ClassSecret: resultType = "text" - } else if result.Class == types.ClassLicense || result.Class == types.ClassLicenseFile { + case types.ClassLicense, types.ClassLicenseFile: resultType = "-" } rows := []string{ @@ -201,8 +202,6 @@ func (r *summaryRenderer) Render(report types.Report) { r.printf("Legend:\n" + "- '-': Not scanned\n" + "- '0': Clean (no security findings detected)\n\n") - - return } func (r *summaryRenderer) printf(format string, args ...any) { diff --git a/pkg/report/table/table.go b/pkg/report/table/table.go index 8f0d0902ff46..f0f81b44dce2 100644 --- a/pkg/report/table/table.go +++ b/pkg/report/table/table.go @@ -110,6 +110,7 @@ func (tw *Writer) Write(_ context.Context, report types.Report) error { func (tw *Writer) flush() { _, _ = fmt.Fprint(tw.options.Output, tw.buf.String()) + tw.buf.Reset() } func (tw *Writer) render(result types.Result) { @@ -117,21 +118,21 @@ func (tw *Writer) render(result types.Result) { return } - switch { + switch result.Class { // vulnerability - case result.Class == types.ClassOSPkg || result.Class == types.ClassLangPkg: + case types.ClassOSPkg, types.ClassLangPkg: tw.vulnerabilityRenderer.Render(result) // misconfiguration - case result.Class == types.ClassConfig: + case types.ClassConfig: tw.misconfigRenderer.Render(result) // secret - case result.Class == types.ClassSecret: + case types.ClassSecret: tw.secretRenderer.Render(result) // package license - case result.Class == types.ClassLicense: + case types.ClassLicense: tw.pkgLicenseRenderer.Render(result) // file license - case result.Class == types.ClassLicenseFile: + case types.ClassLicenseFile: tw.fileLicenseRenderer.Render(result) default: return diff --git a/pkg/report/table/table_test.go b/pkg/report/table/table_test.go index 42934ccdcb58..380ca802047f 100644 --- a/pkg/report/table/table_test.go +++ b/pkg/report/table/table_test.go @@ -246,7 +246,7 @@ Legend: Scanners: tc.scanners, TableModes: tc.tableModes, }) - _ = writer.Write(nil, types.Report{Results: tc.results}) + _ = writer.Write(t.Context(), types.Report{Results: tc.results}) assert.Equal(t, tc.wantOutput, tableWritten.String(), tc.name) }) } diff --git a/pkg/report/table/vulnerability.go b/pkg/report/table/vulnerability.go index 548f4871e0b5..118cccc9d524 100644 --- a/pkg/report/table/vulnerability.go +++ b/pkg/report/table/vulnerability.go @@ -85,8 +85,6 @@ func (r *vulnerabilityRenderer) Render(result types.Result) { } else if len(result.ModifiedFindings) > 0 { showSuppressedOnce() } - - return } func (r *vulnerabilityRenderer) renderDetectedVulnerabilities(result types.Result) { diff --git a/pkg/report/template.go b/pkg/report/template.go index 36891e0381c7..ed145cf3922f 100644 --- a/pkg/report/template.go +++ b/pkg/report/template.go @@ -75,7 +75,7 @@ func NewTemplateWriter(output io.Writer, outputTemplate, appVersion string) (*Te } // Write writes result -func (tw TemplateWriter) Write(ctx context.Context, report types.Report) error { +func (tw TemplateWriter) Write(_ context.Context, report types.Report) error { err := tw.Template.Execute(tw.Output, report.Results) if err != nil { return xerrors.Errorf("failed to write with template: %w", err) diff --git a/pkg/rpc/client/client.go b/pkg/rpc/client/client.go index aee72a3a6526..80a82a12d958 100644 --- a/pkg/rpc/client/client.go +++ b/pkg/rpc/client/client.go @@ -12,6 +12,7 @@ import ( ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" r "github.com/aquasecurity/trivy/pkg/rpc" "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/x/slices" xstrings "github.com/aquasecurity/trivy/pkg/x/strings" "github.com/aquasecurity/trivy/rpc/common" rpc "github.com/aquasecurity/trivy/rpc/scanner" @@ -68,7 +69,7 @@ func NewService(scannerOptions ServiceOption, opts ...Option) Service { } // Scan scans the image -func (s Service) Scan(ctx context.Context, target, artifactKey string, blobKeys []string, opts types.ScanOptions) (types.Results, ftypes.OS, error) { +func (s Service) Scan(ctx context.Context, target, artifactKey string, blobKeys []string, opts types.ScanOptions) (types.ScanResponse, error) { ctx = WithCustomHeaders(ctx, s.customHeaders) // Convert to the rpc struct @@ -106,8 +107,17 @@ func (s Service) Scan(ctx context.Context, target, artifactKey string, blobKeys return err }) if err != nil { - return nil, ftypes.OS{}, xerrors.Errorf("failed to detect vulnerabilities via RPC: %w", err) + return types.ScanResponse{}, xerrors.Errorf("failed to detect vulnerabilities via RPC: %w", err) } - return r.ConvertFromRPCResults(res.Results), r.ConvertFromRPCOS(res.Os), nil + return types.ScanResponse{ + Results: r.ConvertFromRPCResults(res.Results), + OS: r.ConvertFromRPCOS(res.Os), + Layers: slices.ZeroToNil(lo.FilterMap(res.Layers, func(layer *common.Layer, _ int) (ftypes.Layer, bool) { + if layer == nil { + return ftypes.Layer{}, false + } + return r.ConvertFromRPCLayer(layer), true + })), + }, nil } diff --git a/pkg/rpc/client/client_test.go b/pkg/rpc/client/client_test.go index 97d3202ad492..bdd10af0dae1 100644 --- a/pkg/rpc/client/client_test.go +++ b/pkg/rpc/client/client_test.go @@ -34,8 +34,7 @@ func TestScanner_Scan(t *testing.T) { customHeaders http.Header args args expectation *rpc.ScanResponse - wantResults types.Results - wantOS ftypes.OS + want types.ScanResponse wantEosl bool wantErr string }{ @@ -105,54 +104,56 @@ func TestScanner_Scan(t *testing.T) { }, }, }, - wantResults: types.Results{ - { - Target: "alpine:3.11", - Vulnerabilities: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2020-0001", - PkgName: "musl", - InstalledVersion: "1.2.3", - FixedVersion: "1.2.4", - Vulnerability: dbTypes.Vulnerability{ - Title: "DoS", - Description: "Denial os Service", - Severity: "CRITICAL", - References: []string{"http://example.com"}, - VendorSeverity: dbTypes.VendorSeverity{ - vulnerability.NVD: dbTypes.SeverityMedium, - vulnerability.RedHat: dbTypes.SeverityMedium, - }, - CVSS: dbTypes.VendorCVSS{ - "nvd": { - V2Vector: "AV:L/AC:L/Au:N/C:C/I:C/A:C", - V3Vector: "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", - V2Score: 7.2, - V3Score: 7.8, + want: types.ScanResponse{ + Results: types.Results{ + { + Target: "alpine:3.11", + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2020-0001", + PkgName: "musl", + InstalledVersion: "1.2.3", + FixedVersion: "1.2.4", + Vulnerability: dbTypes.Vulnerability{ + Title: "DoS", + Description: "Denial os Service", + Severity: "CRITICAL", + References: []string{"http://example.com"}, + VendorSeverity: dbTypes.VendorSeverity{ + vulnerability.NVD: dbTypes.SeverityMedium, + vulnerability.RedHat: dbTypes.SeverityMedium, }, - "redhat": { - V2Vector: "AV:H/AC:L/Au:N/C:C/I:C/A:C", - V3Vector: "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", - V2Score: 4.2, - V3Score: 2.8, + CVSS: dbTypes.VendorCVSS{ + "nvd": { + V2Vector: "AV:L/AC:L/Au:N/C:C/I:C/A:C", + V3Vector: "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + V2Score: 7.2, + V3Score: 7.8, + }, + "redhat": { + V2Vector: "AV:H/AC:L/Au:N/C:C/I:C/A:C", + V3Vector: "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + V2Score: 4.2, + V3Score: 2.8, + }, }, + CweIDs: []string{"CWE-78"}, + LastModifiedDate: utils.MustTimeParse("2020-01-01T01:01:00Z"), + PublishedDate: utils.MustTimeParse("2001-01-01T01:01:00Z"), + }, + SeveritySource: "nvd", + Layer: ftypes.Layer{ + DiffID: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", }, - CweIDs: []string{"CWE-78"}, - LastModifiedDate: utils.MustTimeParse("2020-01-01T01:01:00Z"), - PublishedDate: utils.MustTimeParse("2001-01-01T01:01:00Z"), - }, - SeveritySource: "nvd", - Layer: ftypes.Layer{ - DiffID: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", }, }, }, }, - }, - wantOS: ftypes.OS{ - Family: "alpine", - Name: "3.11", - Eosl: true, + OS: ftypes.OS{ + Family: "alpine", + Name: "3.11", + Eosl: true, + }, }, }, { @@ -173,7 +174,7 @@ func TestScanner_Scan(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { if tt.expectation == nil { e := map[string]any{ "code": "not_found", @@ -197,7 +198,7 @@ func TestScanner_Scan(t *testing.T) { s := NewService(ServiceOption{CustomHeaders: tt.customHeaders}, WithRPCClient(client)) - gotResults, gotOS, err := s.Scan(t.Context(), tt.args.target, tt.args.imageID, tt.args.layerIDs, tt.args.options) + gotResponse, err := s.Scan(t.Context(), tt.args.target, tt.args.imageID, tt.args.layerIDs, tt.args.options) if tt.wantErr != "" { require.Error(t, err, tt.name) @@ -206,14 +207,13 @@ func TestScanner_Scan(t *testing.T) { } require.NoError(t, err, tt.name) - assert.Equal(t, tt.wantResults, gotResults) - assert.Equal(t, tt.wantOS, gotOS) + assert.Equal(t, tt.want, gotResponse) }) } } func TestScanner_ScanServerInsecure(t *testing.T) { - ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) + ts := httptest.NewTLSServer(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {})) defer ts.Close() tests := []struct { @@ -241,7 +241,7 @@ func TestScanner_ScanServerInsecure(t *testing.T) { }, }) s := NewService(ServiceOption{Insecure: tt.insecure}, WithRPCClient(c)) - _, _, err := s.Scan(t.Context(), "dummy", "", nil, types.ScanOptions{}) + _, err := s.Scan(t.Context(), "dummy", "", nil, types.ScanOptions{}) if tt.wantErr != "" { require.Error(t, err) diff --git a/pkg/rpc/convert.go b/pkg/rpc/convert.go index 2fc66bce44f3..41caee1eb208 100644 --- a/pkg/rpc/convert.go +++ b/pkg/rpc/convert.go @@ -52,26 +52,27 @@ func ConvertToRPCPkgs(pkgs []ftypes.Package) []*common.Package { var rpcPkgs []*common.Package for _, pkg := range pkgs { rpcPkgs = append(rpcPkgs, &common.Package{ - Id: pkg.ID, - Name: pkg.Name, - Version: pkg.Version, - Release: pkg.Release, - Epoch: int32(pkg.Epoch), - Arch: pkg.Arch, - Identifier: ConvertToRPCPkgIdentifier(pkg.Identifier), - Dev: pkg.Dev, - SrcName: pkg.SrcName, - SrcVersion: pkg.SrcVersion, - SrcRelease: pkg.SrcRelease, - SrcEpoch: int32(pkg.SrcEpoch), - Licenses: pkg.Licenses, - Locations: ConvertToRPCLocations(pkg.Locations), - Layer: ConvertToRPCLayer(pkg.Layer), - FilePath: pkg.FilePath, - DependsOn: pkg.DependsOn, - Digest: pkg.Digest.String(), - Indirect: pkg.Indirect, - Maintainer: pkg.Maintainer, + Id: pkg.ID, + Name: pkg.Name, + Version: pkg.Version, + Release: pkg.Release, + Epoch: int32(pkg.Epoch), + Arch: pkg.Arch, + Identifier: ConvertToRPCPkgIdentifier(pkg.Identifier), + Dev: pkg.Dev, + SrcName: pkg.SrcName, + SrcVersion: pkg.SrcVersion, + SrcRelease: pkg.SrcRelease, + SrcEpoch: int32(pkg.SrcEpoch), + Licenses: pkg.Licenses, + Locations: ConvertToRPCLocations(pkg.Locations), + Layer: ConvertToRPCLayer(pkg.Layer), + FilePath: pkg.FilePath, + DependsOn: pkg.DependsOn, + Digest: pkg.Digest.String(), + Relationship: int32(pkg.Relationship), + Indirect: pkg.Indirect, + Maintainer: pkg.Maintainer, }) } return rpcPkgs @@ -205,26 +206,27 @@ func ConvertFromRPCPkgs(rpcPkgs []*common.Package) []ftypes.Package { var pkgs []ftypes.Package for _, pkg := range rpcPkgs { pkgs = append(pkgs, ftypes.Package{ - ID: pkg.Id, - Name: pkg.Name, - Version: pkg.Version, - Release: pkg.Release, - Epoch: int(pkg.Epoch), - Arch: pkg.Arch, - Identifier: ConvertFromRPCPkgIdentifier(pkg.Identifier), - Dev: pkg.Dev, - SrcName: pkg.SrcName, - SrcVersion: pkg.SrcVersion, - SrcRelease: pkg.SrcRelease, - SrcEpoch: int(pkg.SrcEpoch), - Licenses: pkg.Licenses, - Locations: ConvertFromRPCLocation(pkg.Locations), - Layer: ConvertFromRPCLayer(pkg.Layer), - FilePath: pkg.FilePath, - DependsOn: pkg.DependsOn, - Digest: digest.Digest(pkg.Digest), - Indirect: pkg.Indirect, - Maintainer: pkg.Maintainer, + ID: pkg.Id, + Name: pkg.Name, + Version: pkg.Version, + Release: pkg.Release, + Epoch: int(pkg.Epoch), + Arch: pkg.Arch, + Identifier: ConvertFromRPCPkgIdentifier(pkg.Identifier), + Dev: pkg.Dev, + SrcName: pkg.SrcName, + SrcVersion: pkg.SrcVersion, + SrcRelease: pkg.SrcRelease, + SrcEpoch: int(pkg.SrcEpoch), + Licenses: pkg.Licenses, + Locations: ConvertFromRPCLocation(pkg.Locations), + Layer: ConvertFromRPCLayer(pkg.Layer), + FilePath: pkg.FilePath, + DependsOn: pkg.DependsOn, + Digest: digest.Digest(pkg.Digest), + Relationship: ftypes.Relationship(pkg.Relationship), + Indirect: pkg.Indirect, + Maintainer: pkg.Maintainer, }) } return pkgs @@ -366,6 +368,7 @@ func ConvertToRPCMisconfs(misconfs []types.DetectedMisconfiguration) []*common.D // ConvertToRPCLayer returns common.Layer func ConvertToRPCLayer(layer ftypes.Layer) *common.Layer { return &common.Layer{ + Size: layer.Size, Digest: layer.Digest, DiffId: layer.DiffID, CreatedBy: layer.CreatedBy, @@ -649,12 +652,13 @@ func ConvertFromRPCMisconfs(rpcMisconfs []*common.DetectedMisconfiguration) []ty return misconfs } -// ConvertFromRPCLayer converts *common.Layer to fanal.Layer +// ConvertFromRPCLayer converts *common.Layer to ftypes.Layer func ConvertFromRPCLayer(rpcLayer *common.Layer) ftypes.Layer { if rpcLayer == nil { return ftypes.Layer{} } return ftypes.Layer{ + Size: rpcLayer.Size, Digest: rpcLayer.Digest, DiffID: rpcLayer.DiffId, CreatedBy: rpcLayer.CreatedBy, @@ -811,6 +815,7 @@ func ConvertFromRPCPutArtifactRequest(req *cache.PutArtifactRequest) ftypes.Arti func ConvertFromRPCPutBlobRequest(req *cache.PutBlobRequest) ftypes.BlobInfo { return ftypes.BlobInfo{ SchemaVersion: int(req.BlobInfo.SchemaVersion), + Size: req.BlobInfo.Size, Digest: req.BlobInfo.Digest, DiffID: req.BlobInfo.DiffId, OS: ConvertFromRPCOS(req.BlobInfo.Os), @@ -818,11 +823,12 @@ func ConvertFromRPCPutBlobRequest(req *cache.PutBlobRequest) ftypes.BlobInfo { PackageInfos: ConvertFromRPCPackageInfos(req.BlobInfo.PackageInfos), Applications: ConvertFromRPCApplications(req.BlobInfo.Applications), Misconfigurations: ConvertFromRPCMisconfigurations(req.BlobInfo.Misconfigurations), - OpaqueDirs: req.BlobInfo.OpaqueDirs, - WhiteoutFiles: req.BlobInfo.WhiteoutFiles, CustomResources: ConvertFromRPCCustomResources(req.BlobInfo.CustomResources), Secrets: ConvertFromRPCSecrets(req.BlobInfo.Secrets), Licenses: ConvertFromRPCLicenseFiles(req.BlobInfo.Licenses), + CreatedBy: req.BlobInfo.CreatedBy, + OpaqueDirs: req.BlobInfo.OpaqueDirs, + WhiteoutFiles: req.BlobInfo.WhiteoutFiles, } } @@ -922,6 +928,7 @@ func ConvertToRPCPutBlobRequest(diffID string, blobInfo ftypes.BlobInfo) *cache. DiffId: diffID, BlobInfo: &cache.BlobInfo{ SchemaVersion: ftypes.BlobJSONSchemaVersion, + Size: blobInfo.Size, Digest: blobInfo.Digest, DiffId: blobInfo.DiffID, Os: ConvertToRPCOS(blobInfo.OS), @@ -929,11 +936,12 @@ func ConvertToRPCPutBlobRequest(diffID string, blobInfo ftypes.BlobInfo) *cache. PackageInfos: packageInfos, Applications: applications, Misconfigurations: misconfigurations, - OpaqueDirs: blobInfo.OpaqueDirs, - WhiteoutFiles: blobInfo.WhiteoutFiles, CustomResources: customResources, Secrets: ConvertToRPCSecrets(blobInfo.Secrets), Licenses: ConvertToRPCLicenseFiles(blobInfo.Licenses), + CreatedBy: blobInfo.CreatedBy, + OpaqueDirs: blobInfo.OpaqueDirs, + WhiteoutFiles: blobInfo.WhiteoutFiles, }, } } @@ -961,9 +969,9 @@ func ConvertToMissingBlobsRequest(imageID string, layerIDs []string) *cache.Miss } // ConvertToRPCScanResponse converts types.Result to ScanResponse -func ConvertToRPCScanResponse(results types.Results, fos ftypes.OS) *scanner.ScanResponse { +func ConvertToRPCScanResponse(response types.ScanResponse) *scanner.ScanResponse { var rpcResults []*scanner.Result - for _, result := range results { + for _, result := range response.Results { secretFindings := lo.Map(result.Secrets, func(s types.DetectedSecret, _ int) ftypes.SecretFinding { return ftypes.SecretFinding(s) }) @@ -981,8 +989,11 @@ func ConvertToRPCScanResponse(results types.Results, fos ftypes.OS) *scanner.Sca } return &scanner.ScanResponse{ - Os: ConvertToRPCOS(fos), + Os: ConvertToRPCOS(response.OS), Results: rpcResults, + Layers: lo.Map(response.Layers, func(layer ftypes.Layer, _ int) *common.Layer { + return ConvertToRPCLayer(layer) + }), } } diff --git a/pkg/rpc/convert_test.go b/pkg/rpc/convert_test.go index 0a8699583983..65cd36d429ad 100644 --- a/pkg/rpc/convert_test.go +++ b/pkg/rpc/convert_test.go @@ -53,8 +53,9 @@ func TestConvertToRpcPkgs(t *testing.T) { Digest: "sha256:6a428f9f83b0a29f1fdd2ccccca19a9bab805a925b8eddf432a5a3d3da04afbc", DiffID: "sha256:39982b2a789afc156fff00c707d0ff1c6ab4af8f1666a8df4787714059ce24e7", }, - Digest: "SHA1:901a7b55410321c4d35543506cff2a8613ef5aa2", - Indirect: true, + Digest: "SHA1:901a7b55410321c4d35543506cff2a8613ef5aa2", + Indirect: true, + Relationship: ftypes.RelationshipIndirect, Identifier: ftypes.PkgIdentifier{ UID: "01", }, @@ -87,8 +88,9 @@ func TestConvertToRpcPkgs(t *testing.T) { Digest: "sha256:6a428f9f83b0a29f1fdd2ccccca19a9bab805a925b8eddf432a5a3d3da04afbc", DiffId: "sha256:39982b2a789afc156fff00c707d0ff1c6ab4af8f1666a8df4787714059ce24e7", }, - Digest: "SHA1:901a7b55410321c4d35543506cff2a8613ef5aa2", - Indirect: true, + Digest: "SHA1:901a7b55410321c4d35543506cff2a8613ef5aa2", + Indirect: true, + Relationship: 4, Identifier: &common.PkgIdentifier{ Uid: "01", }, @@ -141,8 +143,9 @@ func TestConvertFromRpcPkgs(t *testing.T) { Digest: "sha256:6a428f9f83b0a29f1fdd2ccccca19a9bab805a925b8eddf432a5a3d3da04afbc", DiffId: "sha256:39982b2a789afc156fff00c707d0ff1c6ab4af8f1666a8df4787714059ce24e7", }, - Digest: "SHA1:901a7b55410321c4d35543506cff2a8613ef5aa2", - Indirect: true, + Digest: "SHA1:901a7b55410321c4d35543506cff2a8613ef5aa2", + Relationship: 4, + Indirect: true, Identifier: &common.PkgIdentifier{ Uid: "01", }, @@ -175,8 +178,9 @@ func TestConvertFromRpcPkgs(t *testing.T) { Digest: "sha256:6a428f9f83b0a29f1fdd2ccccca19a9bab805a925b8eddf432a5a3d3da04afbc", DiffID: "sha256:39982b2a789afc156fff00c707d0ff1c6ab4af8f1666a8df4787714059ce24e7", }, - Digest: "SHA1:901a7b55410321c4d35543506cff2a8613ef5aa2", - Indirect: true, + Digest: "SHA1:901a7b55410321c4d35543506cff2a8613ef5aa2", + Relationship: ftypes.RelationshipIndirect, + Indirect: true, Identifier: ftypes.PkgIdentifier{ UID: "01", }, @@ -211,8 +215,9 @@ func TestConvertFromRpcPkgs(t *testing.T) { Digest: "sha256:8d42b73fc1ddc2e9e66c954966f144665825e69f4ed10c66342ae7c26b38d4e4", DiffId: "sha256:745d171eb8c3d69f788da3a1b053056231ad140b80be71d6869229846a1f3a77", }, - Digest: "SHA1:901a7b55410321c4d35543506cff2a8613ef5aa2", - Indirect: false, + Digest: "SHA1:901a7b55410321c4d35543506cff2a8613ef5aa2", + Indirect: false, + Relationship: 3, Identifier: &common.PkgIdentifier{ Uid: "63f8bef824b960e3", }, @@ -246,8 +251,9 @@ func TestConvertFromRpcPkgs(t *testing.T) { Digest: "sha256:8d42b73fc1ddc2e9e66c954966f144665825e69f4ed10c66342ae7c26b38d4e4", DiffID: "sha256:745d171eb8c3d69f788da3a1b053056231ad140b80be71d6869229846a1f3a77", }, - Digest: "SHA1:901a7b55410321c4d35543506cff2a8613ef5aa2", - Indirect: false, + Digest: "SHA1:901a7b55410321c4d35543506cff2a8613ef5aa2", + Relationship: ftypes.RelationshipDirect, + Indirect: false, Identifier: ftypes.PkgIdentifier{ UID: "63f8bef824b960e3", }, diff --git a/pkg/rpc/server/listen.go b/pkg/rpc/server/listen.go index c1bc5033530c..37ac101018c4 100644 --- a/pkg/rpc/server/listen.go +++ b/pkg/rpc/server/listen.go @@ -109,13 +109,13 @@ func (s Server) NewServeMux(ctx context.Context, serverCache cache.Cache, dbUpda layerHandler := withToken(withWaitGroup(cacheServer), s.token, s.tokenHeader) mux.Handle(cacheServer.PathPrefix(), gziphandler.GzipHandler(layerHandler)) - mux.HandleFunc("/healthz", func(rw http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/healthz", func(rw http.ResponseWriter, _ *http.Request) { if _, err := rw.Write([]byte("ok")); err != nil { log.Error("Health check error", log.Err(err)) } }) - mux.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/version", func(w http.ResponseWriter, _ *http.Request) { w.Header().Add("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(version.NewVersionInfo(s.cacheDir)); err != nil { diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index 765f375f95d7..bf51f89bab64 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -48,16 +48,16 @@ func teeError(err error) error { // Scan scans and return response func (s *ScanServer) Scan(ctx context.Context, in *rpcScanner.ScanRequest) (*rpcScanner.ScanResponse, error) { options := s.ToOptions(in.Options) - results, os, err := s.local.Scan(ctx, in.Target, in.ArtifactId, in.BlobIds, options) + scanResponse, err := s.local.Scan(ctx, in.Target, in.ArtifactId, in.BlobIds, options) if err != nil { return nil, teeError(xerrors.Errorf("failed scan, %s: %w", in.Target, err)) } - return rpc.ConvertToRPCScanResponse(results, os), nil + return rpc.ConvertToRPCScanResponse(scanResponse), nil } func (s *ScanServer) ToOptions(in *rpcScanner.ScanOptions) types.ScanOptions { - pkgRelationships := lo.FilterMap(in.PkgRelationships, func(r string, index int) (ftypes.Relationship, bool) { + pkgRelationships := lo.FilterMap(in.PkgRelationships, func(r string, _ int) (ftypes.Relationship, bool) { rel, err := ftypes.NewRelationship(r) if err != nil { log.Warnf("Invalid relationship: %s", r) @@ -69,7 +69,7 @@ func (s *ScanServer) ToOptions(in *rpcScanner.ScanOptions) types.ScanOptions { pkgRelationships = ftypes.Relationships // For backward compatibility } - scanners := lo.Map(in.Scanners, func(s string, index int) types.Scanner { + scanners := lo.Map(in.Scanners, func(s string, _ int) types.Scanner { return types.Scanner(s) }) diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 1c73c9c099a9..6713f72a2e15 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -62,6 +62,7 @@ func TestScanServer_Scan(t *testing.T) { require.NoError(t, c.PutBlob("sha256:beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203", ftypes.BlobInfo{ SchemaVersion: 1, + Size: 1000, DiffID: "sha256:beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203", OS: ftypes.OS{ Family: "alpine", @@ -134,6 +135,12 @@ func TestScanServer_Scan(t *testing.T) { }, }, }, + Layers: []*common.Layer{ + { + Size: 1000, + DiffId: "sha256:beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203", + }, + }, }, }, { @@ -268,7 +275,7 @@ func TestCacheServer_PutArtifact(t *testing.T) { }, }, }, - setUpCache: func(t *testing.T) cache.Cache { + setUpCache: func(_ *testing.T) cache.Cache { return cachetest.NewErrorCache(cachetest.ErrorCacheOptions{ PutArtifact: true, }) @@ -322,6 +329,8 @@ func TestCacheServer_PutBlob(t *testing.T) { SchemaVersion: 1, Digest: "sha256:154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812", DiffId: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", + OpaqueDirs: []string{"etc/"}, + WhiteoutFiles: []string{"etc/hostname"}, Os: &common.OS{ Family: "alpine", Name: "3.11", @@ -391,8 +400,6 @@ func TestCacheServer_PutBlob(t *testing.T) { }, }, }, - OpaqueDirs: []string{"etc/"}, - WhiteoutFiles: []string{"etc/hostname"}, }, }, }, @@ -403,6 +410,8 @@ func TestCacheServer_PutBlob(t *testing.T) { SchemaVersion: 1, Digest: "sha256:154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812", DiffID: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", + OpaqueDirs: []string{"etc/"}, + WhiteoutFiles: []string{"etc/hostname"}, OS: ftypes.OS{ Family: "alpine", Name: "3.11", @@ -472,8 +481,6 @@ func TestCacheServer_PutBlob(t *testing.T) { }, }, }, - OpaqueDirs: []string{"etc/"}, - WhiteoutFiles: []string{"etc/hostname"}, }, }, }, @@ -488,7 +495,7 @@ func TestCacheServer_PutBlob(t *testing.T) { }, }, }, - setUpCache: func(t *testing.T) cache.Cache { + setUpCache: func(_ *testing.T) cache.Cache { return cachetest.NewErrorCache(cachetest.ErrorCacheOptions{ PutBlob: true, }) diff --git a/pkg/sbom/cyclonedx/marshal.go b/pkg/sbom/cyclonedx/marshal.go index 9f5437f2d292..0e2f152164a8 100644 --- a/pkg/sbom/cyclonedx/marshal.go +++ b/pkg/sbom/cyclonedx/marshal.go @@ -285,7 +285,7 @@ func (*Marshaler) Licenses(licenses []string) *cdx.Licenses { if len(licenses) == 0 { return nil } - choices := lo.Map(licenses, func(license string, i int) cdx.LicenseChoice { + choices := lo.Map(licenses, func(license string, _ int) cdx.LicenseChoice { return cdx.LicenseChoice{ License: &cdx.License{ Name: license, diff --git a/pkg/sbom/cyclonedx/marshal_test.go b/pkg/sbom/cyclonedx/marshal_test.go index e3c7afdd1239..c606d3248c9e 100644 --- a/pkg/sbom/cyclonedx/marshal_test.go +++ b/pkg/sbom/cyclonedx/marshal_test.go @@ -1510,7 +1510,7 @@ func TestMarshaler_MarshalReport(t *testing.T) { References: []string{ "https://access.redhat.com/security/cve/CVE-2022-42003", }, - PublishedDate: lo.ToPtr(time.Date(2022, 10, 02, 05, 15, 0, 0, time.UTC)), + PublishedDate: lo.ToPtr(time.Date(2022, 10, 2, 5, 15, 0, 0, time.UTC)), LastModifiedDate: lo.ToPtr(time.Date(2022, 12, 20, 10, 15, 0, 0, time.UTC)), }, }, @@ -1717,7 +1717,7 @@ func TestMarshaler_MarshalReport(t *testing.T) { "https://github.com/advisories/GHSA-xm2m-2q6h-22jw", }, PublishedDate: lo.ToPtr(time.Date(2023, 6, 12, 16, 15, 0, 0, time.UTC)), - LastModifiedDate: lo.ToPtr(time.Date(2023, 6, 21, 02, 20, 0, 0, time.UTC)), + LastModifiedDate: lo.ToPtr(time.Date(2023, 6, 21, 2, 20, 0, 0, time.UTC)), }, }, { @@ -1768,7 +1768,7 @@ func TestMarshaler_MarshalReport(t *testing.T) { "https://github.com/advisories/GHSA-xm2m-2q6h-22jw", }, PublishedDate: lo.ToPtr(time.Date(2023, 6, 12, 16, 15, 0, 0, time.UTC)), - LastModifiedDate: lo.ToPtr(time.Date(2023, 6, 21, 02, 20, 0, 0, time.UTC)), + LastModifiedDate: lo.ToPtr(time.Date(2023, 6, 21, 2, 20, 0, 0, time.UTC)), }, }, }, diff --git a/pkg/sbom/io/encode.go b/pkg/sbom/io/encode.go index bbfc1ca162b7..9e2f5f8ac769 100644 --- a/pkg/sbom/io/encode.go +++ b/pkg/sbom/io/encode.go @@ -435,6 +435,6 @@ func (*Encoder) belongToParent(pkg ftypes.Package, parents map[string]ftypes.Pac func filterProperties(props []core.Property) []core.Property { return lo.Filter(props, func(property core.Property, _ int) bool { - return !(property.Value == "" || (property.Name == core.PropertySrcEpoch && property.Value == "0")) + return property.Value != "" && (property.Name != core.PropertySrcEpoch || property.Value != "0") }) } diff --git a/pkg/sbom/spdx/marshal.go b/pkg/sbom/spdx/marshal.go index 3d21f3657e3f..8d210778fe0c 100644 --- a/pkg/sbom/spdx/marshal.go +++ b/pkg/sbom/spdx/marshal.go @@ -415,7 +415,7 @@ func (m *Marshaler) spdxLicense(c *core.Component) (string, []*spdx.OtherLicense func (m *Marshaler) normalizeLicenses(licenses []string) (string, []*spdx.OtherLicense) { var otherLicenses = make(map[string]*spdx.OtherLicense) // licenseID -> OtherLicense - license := strings.Join(lo.Map(licenses, func(license string, index int) string { + license := strings.Join(lo.Map(licenses, func(license string, _ int) string { // We need to save text licenses before normalization, // because it is impossible to handle all cases possible in the text. // as an example, parse a license with 2 consecutive tokens (see https://github.com/aquasecurity/trivy/issues/8465) @@ -473,7 +473,7 @@ func (m *Marshaler) normalizeLicenses(licenses []string) (string, []*spdx.OtherL return "", nil } - return normalizedLicense, lo.Ternary(len(otherLicenses) > 0, lo.Values(otherLicenses), nil) + return normalizedLicense.String(), lo.Ternary(len(otherLicenses) > 0, lo.Values(otherLicenses), nil) } // newOtherLicense create new OtherLicense for license not included in the SPDX license list diff --git a/pkg/sbom/spdx/marshal_test.go b/pkg/sbom/spdx/marshal_test.go index 3a7a6dd8ce04..770ad170d5dd 100644 --- a/pkg/sbom/spdx/marshal_test.go +++ b/pkg/sbom/spdx/marshal_test.go @@ -1454,7 +1454,7 @@ func TestMarshaler_Marshal(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // Fake function calculating the hash value h := fnv.New64() - hasher := func(v any, format hashstructure.Format, opts *hashstructure.HashOptions) (uint64, error) { + hasher := func(v any, _ hashstructure.Format, _ *hashstructure.HashOptions) (uint64, error) { h.Reset() var str string diff --git a/pkg/scan/local/service.go b/pkg/scan/local/service.go index 8226a59a6736..37ed2ae09e85 100644 --- a/pkg/scan/local/service.go +++ b/pkg/scan/local/service.go @@ -62,7 +62,7 @@ func NewService(a applier.Applier, osPkgScanner ospkg.Scanner, langPkgScanner la // Scan scans the artifact and return results. func (s Service) Scan(ctx context.Context, targetName, artifactKey string, blobKeys []string, options types.ScanOptions) ( - types.Results, ftypes.OS, error) { + types.ScanResponse, error) { detail, err := s.applier.ApplyLayers(artifactKey, blobKeys) switch { case errors.Is(err, analyzer.ErrUnknownOS): @@ -88,7 +88,7 @@ func (s Service) Scan(ctx context.Context, targetName, artifactKey string, blobK log.Warn("No OS package is detected. Make sure you haven't deleted any files that contain information about the installed packages.") log.Warn(`e.g. files under "/lib/apk/db/", "/var/lib/dpkg/" and "/var/lib/rpm"`) case err != nil: - return nil, ftypes.OS{}, xerrors.Errorf("failed to apply layers: %w", err) + return types.ScanResponse{}, xerrors.Errorf("failed to apply layers: %w", err) } if !lo.IsEmpty(options.Distro) && !lo.IsEmpty(detail.OS) { @@ -109,7 +109,15 @@ func (s Service) Scan(ctx context.Context, targetName, artifactKey string, blobK CustomResources: detail.CustomResources, } - return s.ScanTarget(ctx, target, options) + results, os, err := s.ScanTarget(ctx, target, options) + if err != nil { + return types.ScanResponse{}, err + } + return types.ScanResponse{ + Results: results, + OS: os, + Layers: detail.Layers, + }, nil } func (s Service) ScanTarget(ctx context.Context, target types.ScanTarget, options types.ScanOptions) (types.Results, ftypes.OS, error) { @@ -249,7 +257,7 @@ func (s Service) secretsToResults(secrets []ftypes.Secret, options types.ScanOpt results = append(results, types.Result{ Target: secret.FilePath, Class: types.ClassSecret, - Secrets: lo.Map(secret.Findings, func(secret ftypes.SecretFinding, index int) types.DetectedSecret { + Secrets: lo.Map(secret.Findings, func(secret ftypes.SecretFinding, _ int) types.DetectedSecret { return types.DetectedSecret(secret) }), }) @@ -327,7 +335,6 @@ func (s Service) scanApplicationLicenses(apps []ftypes.Application, scanner lice Licenses: langLicenses, }) } - } return results @@ -422,9 +429,8 @@ func toDetectedLicense(scanner licensing.Scanner, license, pkgName, filePath str var severity, licenseText string if strings.HasPrefix(license, licensing.LicenseTextPrefix) { // License text licenseText = strings.TrimPrefix(license, licensing.LicenseTextPrefix) - category = ftypes.CategoryUnknown - severity = dbTypes.SeverityUnknown.String() - license = licensing.CustomLicensePrefix + ": " + licensing.TrimLicenseText(licenseText) + category, severity = scanner.ScanTextLicense(licenseText) + license = licensing.CustomLicensePrefix + ": " + licensing.TrimLicenseText(licenseText) // Use `CUSTOM LICENSE: *...` format for text licenses } else { // License name category, severity = scanner.Scan(license) } @@ -459,7 +465,7 @@ func filterPkgByRelationship(target *types.ScanTarget, options types.ScanOptions } filter := func(pkgs []ftypes.Package) []ftypes.Package { - return lo.Filter(pkgs, func(pkg ftypes.Package, index int) bool { + return lo.Filter(pkgs, func(pkg ftypes.Package, _ int) bool { return slices.Contains(options.PkgRelationships, pkg.Relationship) }) } @@ -479,13 +485,27 @@ func excludeDevDeps(apps []ftypes.Application, include bool) { onceInfo := sync.OnceFunc(func() { log.Info("Suppressing dependencies for development and testing. To display them, try the '--include-dev-deps' flag.") }) + for i := range apps { - apps[i].Packages = lo.Filter(apps[i].Packages, func(lib ftypes.Package, index int) bool { - if lib.Dev { + devDeps := set.New[string]() + apps[i].Packages = lo.Filter(apps[i].Packages, func(pkg ftypes.Package, _ int) bool { + if pkg.Dev { onceInfo() + devDeps.Append(pkg.ID) } - return !lib.Dev + return !pkg.Dev }) + + // Remove development dependencies from dependencies of root and workspace packages + for j, pkg := range apps[i].Packages { + if pkg.Relationship != ftypes.RelationshipRoot && pkg.Relationship != ftypes.RelationshipWorkspace { + continue + } + apps[i].Packages[j].DependsOn = lo.Filter(apps[i].Packages[j].DependsOn, func(dep string, _ int) bool { + return !devDeps.Contains(dep) + }) + + } } } diff --git a/pkg/scan/local/service_test.go b/pkg/scan/local/service_test.go index a8486533aaac..d4b96f19c54c 100644 --- a/pkg/scan/local/service_test.go +++ b/pkg/scan/local/service_test.go @@ -30,7 +30,7 @@ var ( SrcVersion: "1.2.3", Licenses: []string{"MIT"}, Layer: ftypes.Layer{ - DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", + DiffID: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", }, Identifier: ftypes.PkgIdentifier{ UID: "d9a73c7459d27809", @@ -94,7 +94,7 @@ var ( Layer: ftypes.Layer{ DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", }, - Licenses: []string{"MIT"}, + Licenses: []string{"text://(c) 2015 Continuum Analytics, Inc."}, } menuinstPkg = ftypes.Package{ Name: "menuinst", @@ -149,14 +149,13 @@ func TestScanner_Scan(t *testing.T) { options types.ScanOptions } tests := []struct { - name string - args args - fixtures []string - setUpHook bool - setupCache func(t *testing.T) cache.Cache - wantResults types.Results - wantOS ftypes.OS - wantErr string + name string + args args + fixtures []string + setUpHook bool + setupCache func(t *testing.T) cache.Cache + want types.ScanResponse + wantErr string }{ { name: "happy path", @@ -178,6 +177,8 @@ func TestScanner_Scan(t *testing.T) { c := cache.NewMemoryCache() require.NoError(t, c.PutBlob("sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", ftypes.BlobInfo{ SchemaVersion: ftypes.BlobJSONSchemaVersion, + Size: 1000, + DiffID: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", OS: ftypes.OS{ Family: ftypes.Alpine, Name: "3.11", @@ -198,71 +199,79 @@ func TestScanner_Scan(t *testing.T) { })) return c }, - wantResults: types.Results{ - { - Target: "alpine:latest (alpine 3.11)", - Class: types.ClassOSPkg, - Type: ftypes.Alpine, - Packages: ftypes.Packages{ - muslPkg, - }, - Vulnerabilities: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2020-9999", - PkgName: muslPkg.Name, - PkgIdentifier: muslPkg.Identifier, - InstalledVersion: muslPkg.Version, - FixedVersion: "1.2.4", - Status: dbTypes.StatusFixed, - Layer: ftypes.Layer{ - DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", - }, - PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-9999", - Vulnerability: dbTypes.Vulnerability{ - Title: "dos", - Description: "dos vulnerability", - Severity: "HIGH", + want: types.ScanResponse{ + Results: types.Results{ + { + Target: "alpine:latest (alpine 3.11)", + Class: types.ClassOSPkg, + Type: ftypes.Alpine, + Packages: ftypes.Packages{ + muslPkg, + }, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2020-9999", + PkgName: muslPkg.Name, + PkgIdentifier: muslPkg.Identifier, + InstalledVersion: muslPkg.Version, + FixedVersion: "1.2.4", + Status: dbTypes.StatusFixed, + Layer: ftypes.Layer{ + DiffID: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", + }, + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-9999", + Vulnerability: dbTypes.Vulnerability{ + Title: "dos", + Description: "dos vulnerability", + Severity: "HIGH", + }, }, }, }, - }, - { - Target: "/app/Gemfile.lock", - Class: types.ClassLangPkg, - Type: ftypes.Bundler, - Packages: ftypes.Packages{ - railsPkg, - }, - Vulnerabilities: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2014-0081", - PkgName: railsPkg.Name, - PkgIdentifier: railsPkg.Identifier, - InstalledVersion: railsPkg.Version, - FixedVersion: "4.0.3, 3.2.17", - Status: dbTypes.StatusFixed, - Layer: ftypes.Layer{ - DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", - }, - PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081", - Vulnerability: dbTypes.Vulnerability{ - Title: "xss", - Description: "xss vulnerability", - Severity: "MEDIUM", - References: []string{ - "http://example.com", + { + Target: "/app/Gemfile.lock", + Class: types.ClassLangPkg, + Type: ftypes.Bundler, + Packages: ftypes.Packages{ + railsPkg, + }, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2014-0081", + PkgName: railsPkg.Name, + PkgIdentifier: railsPkg.Identifier, + InstalledVersion: railsPkg.Version, + FixedVersion: "4.0.3, 3.2.17", + Status: dbTypes.StatusFixed, + Layer: ftypes.Layer{ + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081", + Vulnerability: dbTypes.Vulnerability{ + Title: "xss", + Description: "xss vulnerability", + Severity: "MEDIUM", + References: []string{ + "http://example.com", + }, + LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)), + PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)), }, - LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)), - PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)), }, }, }, }, - }, - wantOS: ftypes.OS{ - Family: "alpine", - Name: "3.11", - Eosl: true, + OS: ftypes.OS{ + Family: "alpine", + Name: "3.11", + Eosl: true, + }, + Layers: ftypes.Layers{ + { + Size: 1000, + DiffID: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", + }, + }, }, }, { @@ -289,6 +298,8 @@ func TestScanner_Scan(t *testing.T) { c := cache.NewMemoryCache() require.NoError(t, c.PutBlob("sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", ftypes.BlobInfo{ SchemaVersion: ftypes.BlobJSONSchemaVersion, + Size: 1000, + DiffID: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", OS: ftypes.OS{ Family: ftypes.Alpine, Name: "3.11", @@ -302,39 +313,47 @@ func TestScanner_Scan(t *testing.T) { })) return c }, - wantResults: types.Results{ - { - Target: "alpine:latest (alpine 3.11)", - Class: types.ClassOSPkg, - Type: ftypes.Alpine, - Packages: ftypes.Packages{ - muslPkg, - }, - Vulnerabilities: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2020-9999", - PkgName: muslPkg.Name, - PkgIdentifier: muslPkg.Identifier, - InstalledVersion: muslPkg.Version, - FixedVersion: "1.2.4", - Status: dbTypes.StatusFixed, - Layer: ftypes.Layer{ - DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", - }, - PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-9999", - Vulnerability: dbTypes.Vulnerability{ - Title: "dos", - Description: "dos vulnerability", - Severity: "HIGH", + want: types.ScanResponse{ + Results: types.Results{ + { + Target: "alpine:latest (alpine 3.11)", + Class: types.ClassOSPkg, + Type: ftypes.Alpine, + Packages: ftypes.Packages{ + muslPkg, + }, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2020-9999", + PkgName: muslPkg.Name, + PkgIdentifier: muslPkg.Identifier, + InstalledVersion: muslPkg.Version, + FixedVersion: "1.2.4", + Status: dbTypes.StatusFixed, + Layer: ftypes.Layer{ + DiffID: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", + }, + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-9999", + Vulnerability: dbTypes.Vulnerability{ + Title: "dos", + Description: "dos vulnerability", + Severity: "HIGH", + }, }, }, }, }, - }, - wantOS: ftypes.OS{ - Family: "alpine", - Name: "3.11", - Eosl: true, + OS: ftypes.OS{ + Family: "alpine", + Name: "3.11", + Eosl: true, + }, + Layers: ftypes.Layers{ + { + Size: 1000, + DiffID: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", + }, + }, }, }, { @@ -346,6 +365,13 @@ func TestScanner_Scan(t *testing.T) { PkgRelationships: ftypes.Relationships, Scanners: types.Scanners{types.LicenseScanner}, LicenseFull: true, + LicenseCategories: map[ftypes.LicenseCategory][]string{ + ftypes.CategoryNotice: { + "MIT", + "text://\\(c\\) 2015.*", + "text://.* 2016 Continuum.*", + }, + }, }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, @@ -353,6 +379,8 @@ func TestScanner_Scan(t *testing.T) { c := cache.NewMemoryCache() require.NoError(t, c.PutBlob("sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", ftypes.BlobInfo{ SchemaVersion: ftypes.BlobJSONSchemaVersion, + Size: 1000, + DiffID: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", OS: ftypes.OS{ Family: ftypes.Alpine, Name: "3.11", @@ -385,67 +413,76 @@ func TestScanner_Scan(t *testing.T) { })) return c }, - wantResults: types.Results{ - { - Target: "OS Packages", - Class: types.ClassLicense, - Licenses: []types.DetectedLicense{ - { - Severity: "UNKNOWN", - Category: "unknown", - PkgName: muslPkg.Name, - Name: "MIT", - Confidence: 1, + want: types.ScanResponse{ + Results: types.Results{ + { + Target: "OS Packages", + Class: types.ClassLicense, + Licenses: []types.DetectedLicense{ + { + Severity: "LOW", + Category: "notice", + PkgName: muslPkg.Name, + Name: "MIT", + Confidence: 1, + }, }, }, - }, - { - Target: "/app/go.mod", - Class: types.ClassLicense, - Licenses: []types.DetectedLicense{ - { - Severity: "UNKNOWN", - Category: "unknown", - PkgName: uuidPkg.Name, - FilePath: "/app/go.mod", - Name: "LGPL", - Confidence: 1, - Link: "", + { + Target: "/app/go.mod", + Class: types.ClassLicense, + Licenses: []types.DetectedLicense{ + { + Severity: "UNKNOWN", + Category: "unknown", + PkgName: uuidPkg.Name, + FilePath: "/app/go.mod", + Name: "LGPL", + Confidence: 1, + Link: "", + }, }, }, - }, - { - Target: "Python", - Class: types.ClassLicense, - Licenses: []types.DetectedLicense{ - { - Severity: "UNKNOWN", - Category: "unknown", - PkgName: urllib3Pkg.Name, - FilePath: "/usr/lib/python/site-packages/urllib3-3.2.1/METADATA", - Name: "MIT", - Confidence: 1, - }, - { - Severity: "UNKNOWN", - Category: "unknown", - PkgName: menuinstPkg.Name, - FilePath: "opt/conda/lib/python3.11/site-packages/menuinst-2.0.2.dist-info/METADATA", - Name: "CUSTOM License: (c) 2016 Continuum...", - Text: "(c) 2016 Continuum Analytics, Inc. / http://continuum.io All Rights Reserved", - Confidence: 1, + { + Target: "Python", + Class: types.ClassLicense, + Licenses: []types.DetectedLicense{ + { + Severity: "LOW", + Category: "notice", + PkgName: urllib3Pkg.Name, + FilePath: "/usr/lib/python/site-packages/urllib3-3.2.1/METADATA", + Name: "CUSTOM License: (c) 2015 Continuum...", + Text: "(c) 2015 Continuum Analytics, Inc.", + Confidence: 1, + }, + { + Severity: "LOW", + Category: "notice", + PkgName: menuinstPkg.Name, + FilePath: "opt/conda/lib/python3.11/site-packages/menuinst-2.0.2.dist-info/METADATA", + Name: "CUSTOM License: (c) 2016 Continuum...", + Text: "(c) 2016 Continuum Analytics, Inc. / http://continuum.io All Rights Reserved", + Confidence: 1, + }, }, }, + { + Target: "Loose File License(s)", + Class: types.ClassLicenseFile, + }, }, - { - Target: "Loose File License(s)", - Class: types.ClassLicenseFile, + OS: ftypes.OS{ + Family: "alpine", + Name: "3.11", + Eosl: false, + }, + Layers: ftypes.Layers{ + { + Size: 1000, + DiffID: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", + }, }, - }, - wantOS: ftypes.OS{ - Family: "alpine", - Name: "3.11", - Eosl: false, }, }, { @@ -468,6 +505,8 @@ func TestScanner_Scan(t *testing.T) { c := cache.NewMemoryCache() require.NoError(t, c.PutBlob("sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", ftypes.BlobInfo{ SchemaVersion: ftypes.BlobJSONSchemaVersion, + Size: 1000, + DiffID: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", Applications: []ftypes.Application{ { Type: ftypes.Bundler, @@ -487,49 +526,57 @@ func TestScanner_Scan(t *testing.T) { })) return c }, - wantResults: types.Results{ - { - Target: "/app1/Gemfile.lock", - Class: types.ClassLangPkg, - Type: ftypes.Bundler, - Packages: ftypes.Packages{ - innocentPkg, + want: types.ScanResponse{ + Results: types.Results{ + { + Target: "/app1/Gemfile.lock", + Class: types.ClassLangPkg, + Type: ftypes.Bundler, + Packages: ftypes.Packages{ + innocentPkg, + }, }, - }, - { - Target: "/app2/Gemfile.lock", - Class: types.ClassLangPkg, - Type: ftypes.Bundler, - Packages: ftypes.Packages{ - railsPkg, - }, - Vulnerabilities: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2014-0081", - PkgName: railsPkg.Name, - PkgIdentifier: railsPkg.Identifier, - InstalledVersion: railsPkg.Version, - FixedVersion: "4.0.3, 3.2.17", - Status: dbTypes.StatusFixed, - Layer: ftypes.Layer{ - DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", - }, - PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081", - Vulnerability: dbTypes.Vulnerability{ - Title: "xss", - Description: "xss vulnerability", - Severity: "MEDIUM", - References: []string{ - "http://example.com", + { + Target: "/app2/Gemfile.lock", + Class: types.ClassLangPkg, + Type: ftypes.Bundler, + Packages: ftypes.Packages{ + railsPkg, + }, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2014-0081", + PkgName: railsPkg.Name, + PkgIdentifier: railsPkg.Identifier, + InstalledVersion: railsPkg.Version, + FixedVersion: "4.0.3, 3.2.17", + Status: dbTypes.StatusFixed, + Layer: ftypes.Layer{ + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081", + Vulnerability: dbTypes.Vulnerability{ + Title: "xss", + Description: "xss vulnerability", + Severity: "MEDIUM", + References: []string{ + "http://example.com", + }, + LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)), + PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)), }, - LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)), - PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)), }, }, }, }, + OS: ftypes.OS{}, + Layers: ftypes.Layers{ + { + Size: 1000, + DiffID: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", + }, + }, }, - wantOS: ftypes.OS{}, }, { name: "happy path, empty file paths (e.g. Scanned SBOM)", @@ -567,71 +614,73 @@ func TestScanner_Scan(t *testing.T) { })) return c }, - wantResults: types.Results{ - { - Target: "", - Class: types.ClassLangPkg, - Type: ftypes.Bundler, - Packages: []ftypes.Package{ - { - Name: railsPkg.Name, - Version: railsPkg.Version, - Identifier: railsPkg.Identifier, - Layer: ftypes.Layer{ - DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + want: types.ScanResponse{ + Results: types.Results{ + { + Target: "", + Class: types.ClassLangPkg, + Type: ftypes.Bundler, + Packages: []ftypes.Package{ + { + Name: railsPkg.Name, + Version: railsPkg.Version, + Identifier: railsPkg.Identifier, + Layer: ftypes.Layer{ + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, }, }, - }, - Vulnerabilities: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2014-0081", - PkgName: railsPkg.Name, - PkgIdentifier: railsPkg.Identifier, - InstalledVersion: railsPkg.Version, - FixedVersion: "4.0.3, 3.2.17", - Status: dbTypes.StatusFixed, - Layer: ftypes.Layer{ - DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", - }, - PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081", - Vulnerability: dbTypes.Vulnerability{ - Title: "xss", - Description: "xss vulnerability", - Severity: "MEDIUM", - References: []string{ - "http://example.com", + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2014-0081", + PkgName: railsPkg.Name, + PkgIdentifier: railsPkg.Identifier, + InstalledVersion: railsPkg.Version, + FixedVersion: "4.0.3, 3.2.17", + Status: dbTypes.StatusFixed, + Layer: ftypes.Layer{ + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081", + Vulnerability: dbTypes.Vulnerability{ + Title: "xss", + Description: "xss vulnerability", + Severity: "MEDIUM", + References: []string{ + "http://example.com", + }, + LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)), + PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)), }, - LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)), - PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)), }, }, }, - }, - { - Target: "", - Class: types.ClassLangPkg, - Type: ftypes.Composer, - Packages: []ftypes.Package{ - { - Name: laravelPkg.Name, - Version: laravelPkg.Version, - Identifier: laravelPkg.Identifier, - Relationship: ftypes.RelationshipDirect, - Layer: ftypes.Layer{ - DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + { + Target: "", + Class: types.ClassLangPkg, + Type: ftypes.Composer, + Packages: []ftypes.Package{ + { + Name: laravelPkg.Name, + Version: laravelPkg.Version, + Identifier: laravelPkg.Identifier, + Relationship: ftypes.RelationshipDirect, + Layer: ftypes.Layer{ + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, }, }, - }, - Vulnerabilities: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2021-21263", - PkgName: laravelPkg.Name, - PkgIdentifier: laravelPkg.Identifier, - InstalledVersion: laravelPkg.Version, - FixedVersion: "8.22.1, 7.30.3, 6.20.12", - Status: dbTypes.StatusFixed, - Layer: ftypes.Layer{ - DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2021-21263", + PkgName: laravelPkg.Name, + PkgIdentifier: laravelPkg.Identifier, + InstalledVersion: laravelPkg.Version, + FixedVersion: "8.22.1, 7.30.3, 6.20.12", + Status: dbTypes.StatusFixed, + Layer: ftypes.Layer{ + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, }, }, }, @@ -658,6 +707,8 @@ func TestScanner_Scan(t *testing.T) { c := cache.NewMemoryCache() require.NoError(t, c.PutBlob("sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", ftypes.BlobInfo{ SchemaVersion: ftypes.BlobJSONSchemaVersion, + Size: 1000, + DiffID: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", OS: ftypes.OS{ Family: "alpine", Name: "3.11", @@ -674,49 +725,57 @@ func TestScanner_Scan(t *testing.T) { })) return c }, - wantResults: types.Results{ - { - Target: "alpine:latest (alpine 3.11)", - Class: types.ClassOSPkg, - Type: ftypes.Alpine, - }, - { - Target: "/app/Gemfile.lock", - Class: types.ClassLangPkg, - Type: ftypes.Bundler, - Packages: ftypes.Packages{ - railsPkg, - }, - Vulnerabilities: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2014-0081", - PkgName: "rails", - PkgIdentifier: railsPkg.Identifier, - InstalledVersion: railsPkg.Version, - FixedVersion: "4.0.3, 3.2.17", - Status: dbTypes.StatusFixed, - Layer: ftypes.Layer{ - DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", - }, - PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081", - Vulnerability: dbTypes.Vulnerability{ - Title: "xss", - Description: "xss vulnerability", - Severity: "MEDIUM", - References: []string{ - "http://example.com", + want: types.ScanResponse{ + Results: types.Results{ + { + Target: "alpine:latest (alpine 3.11)", + Class: types.ClassOSPkg, + Type: ftypes.Alpine, + }, + { + Target: "/app/Gemfile.lock", + Class: types.ClassLangPkg, + Type: ftypes.Bundler, + Packages: ftypes.Packages{ + railsPkg, + }, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2014-0081", + PkgName: "rails", + PkgIdentifier: railsPkg.Identifier, + InstalledVersion: railsPkg.Version, + FixedVersion: "4.0.3, 3.2.17", + Status: dbTypes.StatusFixed, + Layer: ftypes.Layer{ + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081", + Vulnerability: dbTypes.Vulnerability{ + Title: "xss", + Description: "xss vulnerability", + Severity: "MEDIUM", + References: []string{ + "http://example.com", + }, + LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)), + PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)), }, - LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)), - PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)), }, }, }, }, - }, - wantOS: ftypes.OS{ - Family: "alpine", - Name: "3.11", - Eosl: true, + OS: ftypes.OS{ + Family: "alpine", + Name: "3.11", + Eosl: true, + }, + Layers: ftypes.Layers{ + { + Size: 1000, + DiffID: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", + }, + }, }, }, { @@ -739,6 +798,8 @@ func TestScanner_Scan(t *testing.T) { c := cache.NewMemoryCache() require.NoError(t, c.PutBlob("sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", ftypes.BlobInfo{ SchemaVersion: ftypes.BlobJSONSchemaVersion, + Size: 1000, + DiffID: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", OS: ftypes.OS{ Family: "fedora", Name: "27", @@ -755,41 +816,49 @@ func TestScanner_Scan(t *testing.T) { })) return c }, - wantResults: types.Results{ - { - Target: "/app/Gemfile.lock", - Class: types.ClassLangPkg, - Type: ftypes.Bundler, - Packages: ftypes.Packages{railsPkg}, - Vulnerabilities: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2014-0081", - PkgName: railsPkg.Name, - PkgIdentifier: railsPkg.Identifier, - InstalledVersion: railsPkg.Version, - FixedVersion: "4.0.3, 3.2.17", - Status: dbTypes.StatusFixed, - Layer: ftypes.Layer{ - DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", - }, - PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081", - Vulnerability: dbTypes.Vulnerability{ - Title: "xss", - Description: "xss vulnerability", - Severity: "MEDIUM", - References: []string{ - "http://example.com", + want: types.ScanResponse{ + Results: types.Results{ + { + Target: "/app/Gemfile.lock", + Class: types.ClassLangPkg, + Type: ftypes.Bundler, + Packages: ftypes.Packages{railsPkg}, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2014-0081", + PkgName: railsPkg.Name, + PkgIdentifier: railsPkg.Identifier, + InstalledVersion: railsPkg.Version, + FixedVersion: "4.0.3, 3.2.17", + Status: dbTypes.StatusFixed, + Layer: ftypes.Layer{ + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081", + Vulnerability: dbTypes.Vulnerability{ + Title: "xss", + Description: "xss vulnerability", + Severity: "MEDIUM", + References: []string{ + "http://example.com", + }, + LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)), + PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)), }, - LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)), - PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)), }, }, }, }, - }, - wantOS: ftypes.OS{ - Family: "fedora", - Name: "27", + OS: ftypes.OS{ + Family: "fedora", + Name: "27", + }, + Layers: ftypes.Layers{ + { + Size: 1000, + DiffID: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", + }, + }, }, }, { @@ -815,7 +884,9 @@ func TestScanner_Scan(t *testing.T) { })) return c }, - wantResults: nil, + want: types.ScanResponse{ + Results: nil, + }, }, { name: "happy path with only language-specific package detection, excluding direct packages", @@ -838,6 +909,8 @@ func TestScanner_Scan(t *testing.T) { c := cache.NewMemoryCache() require.NoError(t, c.PutBlob("sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", ftypes.BlobInfo{ SchemaVersion: ftypes.BlobJSONSchemaVersion, + Size: 1000, + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", OS: ftypes.OS{ Family: "alpine", Name: "3.11", @@ -868,47 +941,55 @@ func TestScanner_Scan(t *testing.T) { })) return c }, - wantResults: types.Results{ - { - Target: "/app/Gemfile.lock", - Class: types.ClassLangPkg, - Type: ftypes.Bundler, - Packages: ftypes.Packages{railsPkg}, - Vulnerabilities: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2014-0081", - PkgName: railsPkg.Name, - PkgIdentifier: railsPkg.Identifier, - InstalledVersion: railsPkg.Version, - FixedVersion: "4.0.3, 3.2.17", - Status: dbTypes.StatusFixed, - Layer: ftypes.Layer{ - DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", - }, - PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081", - Vulnerability: dbTypes.Vulnerability{ - Title: "xss", - Description: "xss vulnerability", - Severity: "MEDIUM", - References: []string{ - "http://example.com", + want: types.ScanResponse{ + Results: types.Results{ + { + Target: "/app/Gemfile.lock", + Class: types.ClassLangPkg, + Type: ftypes.Bundler, + Packages: ftypes.Packages{railsPkg}, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2014-0081", + PkgName: railsPkg.Name, + PkgIdentifier: railsPkg.Identifier, + InstalledVersion: railsPkg.Version, + FixedVersion: "4.0.3, 3.2.17", + Status: dbTypes.StatusFixed, + Layer: ftypes.Layer{ + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081", + Vulnerability: dbTypes.Vulnerability{ + Title: "xss", + Description: "xss vulnerability", + Severity: "MEDIUM", + References: []string{ + "http://example.com", + }, + LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)), + PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)), }, - LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)), - PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)), }, }, }, + { + Target: "/app/composer-lock.json", + Class: types.ClassLangPkg, + Type: ftypes.Composer, + Packages: ftypes.Packages{guzzlePkg}, + }, }, - { - Target: "/app/composer-lock.json", - Class: types.ClassLangPkg, - Type: ftypes.Composer, - Packages: ftypes.Packages{guzzlePkg}, + OS: ftypes.OS{ + Family: "alpine", + Name: "3.11", + }, + Layers: ftypes.Layers{ + { + Size: 1000, + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, }, - }, - wantOS: ftypes.OS{ - Family: "alpine", - Name: "3.11", }, }, { @@ -929,6 +1010,8 @@ func TestScanner_Scan(t *testing.T) { c := cache.NewMemoryCache() require.NoError(t, c.PutBlob("sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", ftypes.BlobInfo{ SchemaVersion: ftypes.BlobJSONSchemaVersion, + Size: 1000, + DiffID: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", OS: ftypes.OS{ Family: ftypes.Alpine, Name: "3.11", @@ -942,42 +1025,50 @@ func TestScanner_Scan(t *testing.T) { })) return c }, - wantResults: types.Results{ - { - Target: "alpine:latest (pre-scan) (alpine 3.11)", - Class: types.ClassOSPkg, - Type: ftypes.Alpine, - Packages: ftypes.Packages{ - muslPkg, - }, - Vulnerabilities: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2020-9999", - PkgName: muslPkg.Name, - PkgIdentifier: muslPkg.Identifier, - InstalledVersion: muslPkg.Version, - FixedVersion: "1.2.4", - Status: dbTypes.StatusFixed, - Layer: ftypes.Layer{ - DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", - }, - PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-9999", - Vulnerability: dbTypes.Vulnerability{ - Title: "dos", - Description: "dos vulnerability", - Severity: "HIGH", - References: []string{ - "https://example.com/post-scan", // modified by post-scan hook + want: types.ScanResponse{ + Results: types.Results{ + { + Target: "alpine:latest (pre-scan) (alpine 3.11)", + Class: types.ClassOSPkg, + Type: ftypes.Alpine, + Packages: ftypes.Packages{ + muslPkg, + }, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2020-9999", + PkgName: muslPkg.Name, + PkgIdentifier: muslPkg.Identifier, + InstalledVersion: muslPkg.Version, + FixedVersion: "1.2.4", + Status: dbTypes.StatusFixed, + Layer: ftypes.Layer{ + DiffID: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", + }, + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-9999", + Vulnerability: dbTypes.Vulnerability{ + Title: "dos", + Description: "dos vulnerability", + Severity: "HIGH", + References: []string{ + "https://example.com/post-scan", // modified by post-scan hook + }, }, }, }, }, }, - }, - wantOS: ftypes.OS{ - Family: "alpine", - Name: "3.11", - Eosl: true, + OS: ftypes.OS{ + Family: "alpine", + Name: "3.11", + Eosl: true, + }, + Layers: ftypes.Layers{ + { + Size: 1000, + DiffID: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", + }, + }, }, }, { @@ -994,6 +1085,7 @@ func TestScanner_Scan(t *testing.T) { c := cache.NewMemoryCache() require.NoError(t, c.PutBlob("sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", ftypes.BlobInfo{ SchemaVersion: ftypes.BlobJSONSchemaVersion, + Size: 1000, DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", OS: ftypes.OS{ Family: ftypes.Alpine, @@ -1046,77 +1138,85 @@ func TestScanner_Scan(t *testing.T) { })) return c }, - wantResults: types.Results{ - { - Target: "/app/configs/deployment.yaml", - Class: types.ClassConfig, - Type: ftypes.Kubernetes, - Misconfigurations: []types.DetectedMisconfiguration{ - { - Type: "Kubernetes Security Check", - ID: "ID100", - Title: "Bad Deployment", - Message: "something bad", - Namespace: "main.kubernetes.id100", - Severity: "HIGH", - Status: types.MisconfStatusFailure, - Layer: ftypes.Layer{ - DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", + want: types.ScanResponse{ + Results: types.Results{ + { + Target: "/app/configs/deployment.yaml", + Class: types.ClassConfig, + Type: ftypes.Kubernetes, + Misconfigurations: []types.DetectedMisconfiguration{ + { + Type: "Kubernetes Security Check", + ID: "ID100", + Title: "Bad Deployment", + Message: "something bad", + Namespace: "main.kubernetes.id100", + Severity: "HIGH", + Status: types.MisconfStatusFailure, + Layer: ftypes.Layer{ + DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", + }, + CauseMetadata: ftypes.CauseMetadata{ + Provider: "", + Service: "", + Code: ftypes.Code{}, + }, }, - CauseMetadata: ftypes.CauseMetadata{ - Provider: "", - Service: "", - Code: ftypes.Code{}, + { + Type: "Kubernetes Security Check", + ID: "ID200", + Title: "Bad Deployment", + Message: "No issues found", + Namespace: "builtin.kubernetes.id200", + Severity: "MEDIUM", + PrimaryURL: "https://avd.aquasec.com/misconfig/id200", + References: []string{ + "https://avd.aquasec.com/misconfig/id200", + }, + Status: types.MisconfStatusPassed, + Layer: ftypes.Layer{ + DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", + }, + CauseMetadata: ftypes.CauseMetadata{ + Provider: "", + Service: "", + Code: ftypes.Code{}, + }, }, }, - { - Type: "Kubernetes Security Check", - ID: "ID200", - Title: "Bad Deployment", - Message: "No issues found", - Namespace: "builtin.kubernetes.id200", - Severity: "MEDIUM", - PrimaryURL: "https://avd.aquasec.com/misconfig/id200", - References: []string{ - "https://avd.aquasec.com/misconfig/id200", - }, - Status: types.MisconfStatusPassed, - Layer: ftypes.Layer{ - DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", - }, - CauseMetadata: ftypes.CauseMetadata{ - Provider: "", - Service: "", - Code: ftypes.Code{}, + }, + { + Target: "/app/configs/pod.yaml", + Class: types.ClassConfig, + Type: ftypes.Kubernetes, + Misconfigurations: []types.DetectedMisconfiguration{ + { + Type: "Kubernetes Security Check", + ID: "ID300", + Title: "Bad Deployment", + Message: "No issues found", + Namespace: "main.kubernetes.id300", + Severity: "MEDIUM", + Status: types.MisconfStatusFailure, + Layer: ftypes.Layer{ + DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", + }, }, }, }, }, - { - Target: "/app/configs/pod.yaml", - Class: types.ClassConfig, - Type: ftypes.Kubernetes, - Misconfigurations: []types.DetectedMisconfiguration{ - { - Type: "Kubernetes Security Check", - ID: "ID300", - Title: "Bad Deployment", - Message: "No issues found", - Namespace: "main.kubernetes.id300", - Severity: "MEDIUM", - Status: types.MisconfStatusFailure, - Layer: ftypes.Layer{ - DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", - }, - }, + OS: ftypes.OS{ + Family: "alpine", + Name: "3.11", + Eosl: false, + }, + Layers: ftypes.Layers{ + { + Size: 1000, + DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", }, }, }, - wantOS: ftypes.OS{ - Family: "alpine", - Name: "3.11", - Eosl: false, - }, }, { name: "sad path: ApplyLayers returns an error", @@ -1158,6 +1258,8 @@ func TestScanner_Scan(t *testing.T) { c := cache.NewMemoryCache() require.NoError(t, c.PutBlob("sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", ftypes.BlobInfo{ SchemaVersion: ftypes.BlobJSONSchemaVersion, + Size: 1000, + DiffID: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", OS: ftypes.OS{ Family: "alpine", Name: "3.11", @@ -1196,6 +1298,8 @@ func TestScanner_Scan(t *testing.T) { c := cache.NewMemoryCache() require.NoError(t, c.PutBlob("sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", ftypes.BlobInfo{ SchemaVersion: ftypes.BlobJSONSchemaVersion, + Size: 1000, + DiffID: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", OS: ftypes.OS{ Family: ftypes.Alpine, Name: "3.11", @@ -1251,59 +1355,73 @@ func TestScanner_Scan(t *testing.T) { })) return c }, - wantResults: types.Results{ - { - Target: "Dockerfile", - Class: types.ClassConfig, - Type: ftypes.Dockerfile, - Misconfigurations: []types.DetectedMisconfiguration{ - { - Namespace: "builtin.dockerfile.DS002", - Query: "data.builtin.dockerfile.DS002.deny", - Message: "Specify at least 1 USER command in Dockerfile with non-root user as argument", - Type: "Dockerfile Security Check", - ID: "DS002", - AVDID: "AVD-DS-0002", - Title: "Image user should not be 'root'", - Description: "Running containers with 'root' user can lead to a container escape situation. It is a best practice to run containers as non-root users, which can be done by adding a 'USER' statement to the Dockerfile.", - Severity: "HIGH", - Resolution: "Add 'USER ' line to the Dockerfile", - Status: types.MisconfStatusFailure, - PrimaryURL: "https://avd.aquasec.com/misconfig/ds002", - References: []string{"https://avd.aquasec.com/misconfig/ds002"}, - CauseMetadata: ftypes.CauseMetadata{ - Provider: "Dockerfile", - Service: "general", - Code: ftypes.Code{}, + want: types.ScanResponse{ + Results: types.Results{ + { + Target: "Dockerfile", + Class: types.ClassConfig, + Type: ftypes.Dockerfile, + Misconfigurations: []types.DetectedMisconfiguration{ + { + Namespace: "builtin.dockerfile.DS002", + Query: "data.builtin.dockerfile.DS002.deny", + Message: "Specify at least 1 USER command in Dockerfile with non-root user as argument", + Type: "Dockerfile Security Check", + ID: "DS002", + AVDID: "AVD-DS-0002", + Title: "Image user should not be 'root'", + Description: "Running containers with 'root' user can lead to a container escape situation. It is a best practice to run containers as non-root users, which can be done by adding a 'USER' statement to the Dockerfile.", + Severity: "HIGH", + Resolution: "Add 'USER ' line to the Dockerfile", + Status: types.MisconfStatusFailure, + PrimaryURL: "https://avd.aquasec.com/misconfig/ds002", + References: []string{"https://avd.aquasec.com/misconfig/ds002"}, + CauseMetadata: ftypes.CauseMetadata{ + Provider: "Dockerfile", + Service: "general", + Code: ftypes.Code{}, + }, + Layer: ftypes.Layer{ + DiffID: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", + }, }, - }, - { - Namespace: "builtin.dockerfile.DS001", - Query: "data.builtin.dockerfile.DS001.deny", - Message: "No issues found", - Type: "Dockerfile Security Check", - ID: "DS001", - AVDID: "AVD-DS-0001", - Title: "':latest' tag used", - Description: "When using a 'FROM' statement you should use a specific tag to avoid uncontrolled behavior when the image is updated.", - Severity: "MEDIUM", - Resolution: "Add a tag to the image in the 'FROM' statement", - Status: types.MisconfStatusPassed, - CauseMetadata: ftypes.CauseMetadata{ - Provider: "Dockerfile", - Service: "general", - Code: ftypes.Code{}, + { + Namespace: "builtin.dockerfile.DS001", + Query: "data.builtin.dockerfile.DS001.deny", + Message: "No issues found", + Type: "Dockerfile Security Check", + ID: "DS001", + AVDID: "AVD-DS-0001", + Title: "':latest' tag used", + Description: "When using a 'FROM' statement you should use a specific tag to avoid uncontrolled behavior when the image is updated.", + Severity: "MEDIUM", + Resolution: "Add a tag to the image in the 'FROM' statement", + Status: types.MisconfStatusPassed, + CauseMetadata: ftypes.CauseMetadata{ + Provider: "Dockerfile", + Service: "general", + Code: ftypes.Code{}, + }, + PrimaryURL: "https://avd.aquasec.com/misconfig/ds001", + References: []string{"https://avd.aquasec.com/misconfig/ds001"}, + Layer: ftypes.Layer{ + DiffID: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", + }, }, - PrimaryURL: "https://avd.aquasec.com/misconfig/ds001", - References: []string{"https://avd.aquasec.com/misconfig/ds001"}, }, }, }, - }, - wantOS: ftypes.OS{ - Family: "alpine", - Name: "3.11", - Eosl: false, + OS: ftypes.OS{ + Family: "alpine", + Name: "3.11", + Eosl: false, + }, + Layers: ftypes.Layers{ + { + Size: 1000, + DiffID: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", + }, + }, }, }, } @@ -1321,15 +1439,14 @@ func TestScanner_Scan(t *testing.T) { a := applier.NewApplier(c) s := NewService(a, ospkg.NewScanner(), langpkg.NewScanner(), vulnerability.NewClient(db.Config{})) - gotResults, gotOS, err := s.Scan(t.Context(), tt.args.target, "", tt.args.layerIDs, tt.args.options) + gotResponse, err := s.Scan(t.Context(), tt.args.target, "", tt.args.layerIDs, tt.args.options) if tt.wantErr != "" { require.ErrorContains(t, err, tt.wantErr, tt.name) return } require.NoError(t, err, tt.name) - assert.Equal(t, tt.wantResults, gotResults) - assert.Equal(t, tt.wantOS, gotOS) + assert.Equal(t, tt.want, gotResponse) }) } } diff --git a/pkg/scan/service.go b/pkg/scan/service.go index 151ba7423180..47daf2fdf0cd 100644 --- a/pkg/scan/service.go +++ b/pkg/scan/service.go @@ -4,6 +4,7 @@ import ( "context" "github.com/google/wire" + "github.com/samber/lo" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/cache" @@ -15,7 +16,6 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/artifact/sbom" "github.com/aquasecurity/trivy/pkg/fanal/artifact/vm" "github.com/aquasecurity/trivy/pkg/fanal/image" - ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/report" "github.com/aquasecurity/trivy/pkg/rpc/client" @@ -145,7 +145,7 @@ type Service struct { // and license scanning. type Backend interface { Scan(ctx context.Context, target, artifactKey string, blobKeys []string, options types.ScanOptions) ( - results types.Results, osFound ftypes.OS, err error) + response types.ScanResponse, err error) } // NewService creates a new Service instance with the specified backend implementation @@ -172,20 +172,25 @@ func (s Service) ScanArtifact(ctx context.Context, options types.ScanOptions) (t } }() - results, osFound, err := s.backend.Scan(ctx, artifactInfo.Name, artifactInfo.ID, artifactInfo.BlobIDs, options) + scanResponse, err := s.backend.Scan(ctx, artifactInfo.Name, artifactInfo.ID, artifactInfo.BlobIDs, options) if err != nil { return types.Report{}, xerrors.Errorf("scan failed: %w", err) } - ptros := &osFound - if osFound.Detected() && osFound.Eosl { + ptros := &scanResponse.OS + if scanResponse.OS.Detected() && scanResponse.OS.Eosl { log.Warn("This OS version is no longer supported by the distribution", - log.String("family", string(osFound.Family)), log.String("version", osFound.Name)) + log.String("family", string(scanResponse.OS.Family)), log.String("version", scanResponse.OS.Name)) log.Warn("The vulnerability detection may be insufficient because security updates are not provided") - } else if !osFound.Detected() { + } else if !scanResponse.OS.Detected() { ptros = nil } + // We don't need to include CreatedBy into Report + for i := range scanResponse.Layers { + scanResponse.Layers[i].CreatedBy = "" + } + return types.Report{ SchemaVersion: report.SchemaVersion, CreatedAt: clock.Now(ctx), @@ -200,8 +205,10 @@ func (s Service) ScanArtifact(ctx context.Context, options types.ScanOptions) (t RepoTags: artifactInfo.ImageMetadata.RepoTags, RepoDigests: artifactInfo.ImageMetadata.RepoDigests, ImageConfig: artifactInfo.ImageMetadata.ConfigFile, + Size: scanResponse.Layers.TotalSize(), + Layers: lo.Ternary(len(scanResponse.Layers) > 0, scanResponse.Layers, nil), }, - Results: results, + Results: scanResponse.Results, BOM: artifactInfo.BOM, }, nil } diff --git a/pkg/scan/service_test.go b/pkg/scan/service_test.go index 54f1e105cff6..91e24c293666 100644 --- a/pkg/scan/service_test.go +++ b/pkg/scan/service_test.go @@ -56,6 +56,7 @@ func TestScanner_ScanArtifact(t *testing.T) { ArtifactName: "../fanal/test/testdata/alpine-311.tar.gz", ArtifactType: ftypes.TypeContainerImage, Metadata: tTypes.Metadata{ + Size: 5861888, OS: &ftypes.OS{ Family: "alpine", Name: "3.11.5", @@ -66,6 +67,12 @@ func TestScanner_ScanArtifact(t *testing.T) { DiffIDs: []string{ "sha256:beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203", }, + Layers: ftypes.Layers{ + { + Size: 5861888, + DiffID: "sha256:beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203", + }, + }, ImageConfig: v1.ConfigFile{ Architecture: "amd64", Container: "fb71ddde5f6411a82eb056a9190f0cc1c80d7f77a8509ee90a2054428edb0024", diff --git a/pkg/types/report.go b/pkg/types/report.go index 588279096701..ddff8e292224 100644 --- a/pkg/types/report.go +++ b/pkg/types/report.go @@ -33,6 +33,7 @@ type Metadata struct { RepoTags []string `json:",omitempty"` RepoDigests []string `json:",omitempty"` ImageConfig v1.ConfigFile `json:",omitempty"` + Layers ftypes.Layers `json:",omitzero"` } // Results to hold list of Result @@ -73,6 +74,15 @@ const ( FormatCosignVuln Format = "cosign-vuln" ) +var BuiltInK8sCompiances = []string{ + ComplianceK8sNsa10, + ComplianceK8sCIS123, + ComplianceEksCIS14, + ComplianceRke2CIS124, + ComplianceK8sPSSBaseline01, + ComplianceK8sPSSRestricted01, +} + var ( SupportedFormats = []Format{ FormatTable, diff --git a/pkg/types/scan.go b/pkg/types/scan.go index 6dec84d74e4e..4db3e5e8efa1 100644 --- a/pkg/types/scan.go +++ b/pkg/types/scan.go @@ -126,3 +126,10 @@ type ScanOptions struct { Distro types.OS // Forced OS VulnSeveritySources []dbTypes.SourceID } + +// ScanResponse represents the response from the scan service +type ScanResponse struct { + Results Results + OS types.OS + Layers types.Layers +} diff --git a/pkg/vex/cyclonedx.go b/pkg/vex/cyclonedx.go index 771cc71be253..ae2689329561 100644 --- a/pkg/vex/cyclonedx.go +++ b/pkg/vex/cyclonedx.go @@ -24,7 +24,7 @@ type Statement struct { func newCycloneDX(sbom *core.BOM, vex *cdx.BOM) *CycloneDX { statements := make(map[string]Statement) for _, vuln := range lo.FromPtr(vex.Vulnerabilities) { - affects := lo.Map(lo.FromPtr(vuln.Affects), func(item cdx.Affects, index int) string { + affects := lo.Map(lo.FromPtr(vuln.Affects), func(item cdx.Affects, _ int) string { return item.Ref }) diff --git a/pkg/vex/document.go b/pkg/vex/document.go index 13cf37ab3a7c..4019ed7eb274 100644 --- a/pkg/vex/document.go +++ b/pkg/vex/document.go @@ -30,13 +30,11 @@ func NewDocument(filePath string, report *types.Report) (VEX, error) { v, errs := decodeVEX(f, filePath, report) if errs != nil { return nil, xerrors.Errorf("unable to load VEX from file: %w", errs) - } else { - return v, nil } + return v, nil } func decodeVEX(r io.ReadSeeker, source string, report *types.Report) (VEX, error) { - var errs error // Try CycloneDX JSON if ok, err := sbom.IsCycloneDXJSON(r); err != nil { diff --git a/pkg/vex/repo/manager.go b/pkg/vex/repo/manager.go index b157156bdf74..8fe2d9019022 100644 --- a/pkg/vex/repo/manager.go +++ b/pkg/vex/repo/manager.go @@ -66,7 +66,7 @@ func NewManager(cacheRoot string, opts ...ManagerOption) *Manager { } func (m *Manager) writeConfig(conf Config) error { - if err := os.MkdirAll(filepath.Dir(m.configFile), 0700); err != nil { + if err := os.MkdirAll(filepath.Dir(m.configFile), 0o700); err != nil { return xerrors.Errorf("failed to mkdir: %w", err) } f, err := os.Create(m.configFile) diff --git a/pkg/vex/repo/manager_test.go b/pkg/vex/repo/manager_test.go index f870a9549c67..74aa7a05e78c 100644 --- a/pkg/vex/repo/manager_test.go +++ b/pkg/vex/repo/manager_test.go @@ -48,7 +48,7 @@ func TestManager_Config(t *testing.T) { }, { name: "config file does not exist", - setup: func(t *testing.T, dir string) {}, + setup: func(_ *testing.T, _ string) {}, want: repo.Config{ Repositories: []repo.Repository{ { @@ -89,7 +89,7 @@ func TestManager_Init(t *testing.T) { }{ { name: "successful init", - setup: func(t *testing.T, dir string) {}, + setup: func(_ *testing.T, _ string) {}, want: repo.Config{ Repositories: []repo.Repository{ { @@ -321,9 +321,9 @@ func TestManager_Clear(t *testing.T) { // Create some dummy files cacheDir := filepath.Join(tempDir, "vex") - require.NoError(t, os.MkdirAll(cacheDir, 0755)) + require.NoError(t, os.MkdirAll(cacheDir, 0o755)) dummyFile := filepath.Join(cacheDir, "dummy.txt") - require.NoError(t, os.WriteFile(dummyFile, []byte("dummy"), 0644)) + require.NoError(t, os.WriteFile(dummyFile, []byte("dummy"), 0o644)) err := m.Clear() require.NoError(t, err) diff --git a/pkg/vex/repo/repo.go b/pkg/vex/repo/repo.go index b9637c229ee9..9749d1aef884 100644 --- a/pkg/vex/repo/repo.go +++ b/pkg/vex/repo/repo.go @@ -147,7 +147,7 @@ func (r *Repository) Index(ctx context.Context) (Index, error) { } func (r *Repository) downloadManifest(ctx context.Context, opts Options) error { - if err := os.MkdirAll(r.dir, 0700); err != nil { + if err := os.MkdirAll(r.dir, 0o700); err != nil { return xerrors.Errorf("failed to mkdir: %w", err) } @@ -217,17 +217,14 @@ func (r *Repository) needUpdate(ctx context.Context, ver Version, versionDir str now := clock.Clock(ctx).Now() log.DebugContext(ctx, "Checking if the repository needs to be updated...", log.String("repo", r.Name), log.Time("last_update", m.UpdatedAt), log.Duration("update_interval", ver.UpdateInterval.Duration)) - if now.After(m.UpdatedAt.Add(ver.UpdateInterval.Duration)) { - return true - } - return false + return now.After(m.UpdatedAt.Add(ver.UpdateInterval.Duration)) } func (r *Repository) download(ctx context.Context, ver Version, dst string, opts Options) error { if len(ver.Locations) == 0 { return xerrors.Errorf("no locations found for version %s", ver.SpecVersion) } - if err := os.MkdirAll(dst, 0700); err != nil { + if err := os.MkdirAll(dst, 0o700); err != nil { return xerrors.Errorf("failed to mkdir: %w", err) } diff --git a/pkg/vex/repo/repo_test.go b/pkg/vex/repo/repo_test.go index a7cc1dc6cb55..2d190554dfc4 100644 --- a/pkg/vex/repo/repo_test.go +++ b/pkg/vex/repo/repo_test.go @@ -60,14 +60,14 @@ func TestRepository_Manifest(t *testing.T) { }, { name: "fetch from remote", - setup: func(t *testing.T, dir string, r *repo.Repository) { + setup: func(_ *testing.T, _ string, r *repo.Repository) { r.URL = ts.URL }, want: manifest, }, { name: "http error", - setup: func(t *testing.T, dir string, r *repo.Repository) { + setup: func(_ *testing.T, _ string, r *repo.Repository) { r.URL = ts.URL + "/error" }, wantErr: "failed to download the repository metadata", @@ -188,7 +188,7 @@ func TestRepository_Update(t *testing.T) { }{ { name: "successful update", - setup: func(t *testing.T, cacheDir string, r *repo.Repository) { + setup: func(t *testing.T, cacheDir string, _ *repo.Repository) { setUpManifest(t, cacheDir, ts.URL+"/archive.zip") }, clockTime: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), diff --git a/pkg/vex/repo_test.go b/pkg/vex/repo_test.go index 2495584ea661..5316b718eb68 100644 --- a/pkg/vex/repo_test.go +++ b/pkg/vex/repo_test.go @@ -90,12 +90,12 @@ repositories: // Create the vex directory in the temporary directory vexDir := filepath.Join(tmpDir, ".trivy", "vex") - err := os.MkdirAll(vexDir, 0755) + err := os.MkdirAll(vexDir, 0o755) require.NoError(t, err) // Write the config file configPath := filepath.Join(vexDir, "repository.yaml") - err = os.WriteFile(configPath, []byte(tt.configContent), 0644) + err = os.WriteFile(configPath, []byte(tt.configContent), 0o644) require.NoError(t, err) ctx := t.Context() diff --git a/pkg/vex/vex.go b/pkg/vex/vex.go index 77ae68e50939..231ace3d32b3 100644 --- a/pkg/vex/vex.go +++ b/pkg/vex/vex.go @@ -111,7 +111,7 @@ func New(ctx context.Context, report *types.Report, opts Options) (*Client, erro v, err = NewOCI(report) if err != nil { return nil, xerrors.Errorf("VEX OCI error: %w", err) - } else if v == nil { + } else if lo.IsNil(v) { continue } case TypeSBOMReference: @@ -145,7 +145,7 @@ func (c *Client) NotAffected(vuln types.DetectedVulnerability, product, subCompo } func filterVulnerabilities(result *types.Result, bom *core.BOM, fn NotAffected) { - components := lo.MapEntries(bom.Components(), func(id uuid.UUID, component *core.Component) (string, *core.Component) { + components := lo.MapEntries(bom.Components(), func(_ uuid.UUID, component *core.Component) (string, *core.Component) { return component.PkgIdentifier.UID, component }) @@ -175,8 +175,8 @@ func filterVulnerabilities(result *types.Result, bom *core.BOM, fn NotAffected) // reachRoot traverses the component tree from the leaf to the root and returns true if the leaf reaches the root. func reachRoot(leaf *core.Component, components map[uuid.UUID]*core.Component, parents map[uuid.UUID][]uuid.UUID, - notAffected func(c, leaf *core.Component) bool) bool { - + notAffected func(c, leaf *core.Component) bool, +) bool { if notAffected(leaf, nil) { return false } diff --git a/pkg/vex/vex_test.go b/pkg/vex/vex_test.go index e85c270f8635..6d9f25e92cd0 100644 --- a/pkg/vex/vex_test.go +++ b/pkg/vex/vex_test.go @@ -481,7 +481,7 @@ func TestFilter(t *testing.T) { setup: func(t *testing.T, tmpDir string) { // Create repository.yaml vexDir := filepath.Join(tmpDir, ".trivy", "vex") - require.NoError(t, os.MkdirAll(vexDir, 0755)) + require.NoError(t, os.MkdirAll(vexDir, 0o755)) configPath := filepath.Join(vexDir, "repository.yaml") configContent := ` @@ -489,7 +489,7 @@ repositories: - name: default url: https://example.com/vex/default enabled: true` - require.NoError(t, os.WriteFile(configPath, []byte(configContent), 0644)) + require.NoError(t, os.WriteFile(configPath, []byte(configContent), 0o644)) }, args: args{ report: imageReport([]types.Result{ diff --git a/pkg/x/io/counting_reader.go b/pkg/x/io/counting_reader.go new file mode 100644 index 000000000000..1b140d28bbce --- /dev/null +++ b/pkg/x/io/counting_reader.go @@ -0,0 +1,32 @@ +package io + +import ( + "io" +) + +// CountingReader wraps an io.Reader and counts the number of bytes read. +// Note: This implementation is NOT thread-safe. It should not be used +// concurrently from multiple goroutines. +type CountingReader struct { + r io.Reader + bytesRead int64 +} + +// NewCountingReader creates a new CountingReader that wraps the provided io.Reader. +func NewCountingReader(r io.Reader) *CountingReader { + return &CountingReader{r: r} +} + +// Read reads data from the underlying reader and counts the number of bytes read. +func (c *CountingReader) Read(p []byte) (int, error) { + n, err := c.r.Read(p) + if n > 0 { + c.bytesRead += int64(n) + } + return n, err +} + +// BytesRead returns the number of bytes read. +func (c *CountingReader) BytesRead() int64 { + return c.bytesRead +} diff --git a/pkg/x/io/counting_reader_test.go b/pkg/x/io/counting_reader_test.go new file mode 100644 index 000000000000..6a8584346365 --- /dev/null +++ b/pkg/x/io/counting_reader_test.go @@ -0,0 +1,64 @@ +package io + +import ( + "bytes" + "io" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCountingReader(t *testing.T) { + testCases := []struct { + name string + data []byte + readSize int + want int64 + }{ + { + name: "empty data", + data: []byte{}, + readSize: 10, + want: 0, + }, + { + name: "small data single read", + data: []byte("hello"), + readSize: 10, + want: 5, + }, + { + name: "multiple reads", + data: []byte("hello world"), + readSize: 5, + want: 11, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Create a reader with test data + reader := bytes.NewReader(tc.data) + + // Wrap with counter + counter := NewCountingReader(reader) + + // Read all data + buf := make([]byte, tc.readSize) + for { + n, err := counter.Read(buf) + if err == io.EOF { + break + } + require.NoError(t, err, "unexpected error during read") + if n == 0 { + break + } + } + + // Verify bytes read + assert.Equal(t, tc.want, counter.BytesRead(), "BytesRead() should return correct count") + }) + } +} diff --git a/pkg/x/json/jsonc.go b/pkg/x/json/jsonc.go new file mode 100644 index 000000000000..764b546538ca --- /dev/null +++ b/pkg/x/json/jsonc.go @@ -0,0 +1,248 @@ +package json + +import ( + "bytes" + "errors" + "io" +) + +// TokenType represents the type of token being processed +type TokenType int + +const ( + TokenNormal TokenType = iota + TokenString + TokenSingleLineComment + TokenMultiLineComment +) + +// jsoncParser manages the state and processing of JSONC content +type jsoncParser struct { + reader *bytes.Reader // Source reader + dst []byte // Destination buffer + pos int // Current position in destination + tokenType TokenType // Current token type being processed + escaped bool // Whether the previous character was an escape character + lastChar byte // Last processed character +} + +// ToRFC8259 converts JSONC (JSON with Comments) to valid JSON following RFC8259. +// It strips out comments and trailing commas while maintaining the exact character +// offsets as the input. This ensures that any JSON parser locations will map +// directly back to the original source file positions. +// +// Both line numbers and character positions are preserved in the output. +// Comments and trailing commas are replaced with spaces without changing line counts. +// +// Comments can be either: +// - Single-line: starting with // and continuing to the end of the line +// - Multi-line: starting with /* and ending with */ +// +// Trailing commas are allowed in JSONC but not in standard JSON, so they are replaced +// with spaces to maintain character offsets. +func ToRFC8259(src []byte) []byte { + dst := make([]byte, len(src)) + copy(dst, src) // Copy input to maintain same length and offsets + + parser := newJSONCParser(src, dst) + parser.process() + + return dst +} + +// UnmarshalJSONC parses JSONC (JSON with Comments) data into the specified value. +// It first converts JSONC to standard JSON following RFC8259 and then unmarshals it. +// This is a convenience function that combines ToRFC8259 and Unmarshal. +// +// The parser preserves line number information, which is essential for reporting +// errors at their correct locations in the original file. +// +// Usage example: +// +// type Config struct { +// Name string `json:"name"` +// Version string `json:"version"` +// xjson.Location // Embed Location to get line number info +// } +// +// var config Config +// if err := xjson.UnmarshalJSONC(data, &config); err != nil { +// return err +// } +func UnmarshalJSONC(data []byte, v any) error { + jsonData := ToRFC8259(data) + return Unmarshal(jsonData, v) +} + +// newJSONCParser creates a new JSONC parser +func newJSONCParser(src, dst []byte) *jsoncParser { + return &jsoncParser{ + reader: bytes.NewReader(src), + dst: dst, + pos: 0, + tokenType: TokenNormal, + } +} + +// process processes the input JSONC content +func (p *jsoncParser) process() { + for { + b, err := p.reader.ReadByte() + if errors.Is(err, io.EOF) { + break + } else if err != nil { + // Ignore other errors (not expected to occur) + break + } + p.processChar(b) + } +} + +// processChar processes a single character based on current state +func (p *jsoncParser) processChar(b byte) { + switch p.tokenType { + case TokenString: + p.processStringToken(b) + case TokenSingleLineComment: + p.processSingleLineComment(b) + case TokenMultiLineComment: + p.processMultiLineComment(b) + default: + p.processNormalToken(b) + } +} + +// processStringToken processes a character within a string literal +func (p *jsoncParser) processStringToken(b byte) { + switch { + case p.escaped: + p.escaped = false + case b == '\\': + p.escaped = true + case b == '"': + p.tokenType = TokenNormal + } + + p.lastChar = b + p.pos++ +} + +// processSingleLineComment processes a character within a single-line comment +func (p *jsoncParser) processSingleLineComment(b byte) { + if b == '\n' { + // End of single-line comment at newline + p.tokenType = TokenNormal + } else if !isPreservedWhitespace(b) { + // Replace non-whitespace characters with spaces + if p.pos < len(p.dst) { + p.dst[p.pos] = ' ' + } + } + + p.lastChar = b + p.pos++ +} + +// processMultiLineComment processes a character within a multi-line comment +func (p *jsoncParser) processMultiLineComment(b byte) { + if p.lastChar == '*' && b == '/' { + // End of multi-line comment + p.tokenType = TokenNormal + if p.pos < len(p.dst) { + p.dst[p.pos] = ' ' // Replace '/' with space + } + } else if !isPreservedWhitespace(b) { + // Replace non-whitespace with space + if p.pos < len(p.dst) { + p.dst[p.pos] = ' ' + } + } + + p.lastChar = b + p.pos++ +} + +// processNormalToken processes a character outside of string literals and comments +func (p *jsoncParser) processNormalToken(b byte) { + switch b { + case '"': + // Start of string literal + p.tokenType = TokenString + case '/': + // Potential start of comment - look ahead + nextByte, err := p.reader.ReadByte() + if err != nil { + // End of file after '/' character + return + } + + switch nextByte { + case '/': + // Start of single-line comment + p.tokenType = TokenSingleLineComment + if p.pos < len(p.dst) { + p.dst[p.pos] = ' ' // Replace '/' with space + } + if p.pos+1 < len(p.dst) { + p.dst[p.pos+1] = ' ' // Replace second '/' with space + } + p.lastChar = nextByte + p.pos += 2 + return + case '*': + // Start of multi-line comment + p.tokenType = TokenMultiLineComment + if p.pos < len(p.dst) { + p.dst[p.pos] = ' ' // Replace '/' with space + } + if p.pos+1 < len(p.dst) { + p.dst[p.pos+1] = ' ' // Replace '*' with space + } + p.lastChar = nextByte + p.pos += 2 + return + } + + // Not a comment, put the byte back + p.reader.UnreadByte() + case ']', '}': + // Handle trailing comma - look backward + p.handleTrailingComma() + } + p.lastChar = b + p.pos++ +} + +// handleTrailingComma handles the trailing comma by looking backward from the current position +func (p *jsoncParser) handleTrailingComma() { + // Start from one position before the current bracket + startPos := p.pos - 1 + if startPos < 0 { + return + } + + // Find the previous significant (non-whitespace) character + for i := startPos; i >= 0; i-- { + if i >= len(p.dst) { + continue + } + + c := p.dst[i] + switch c { + case ' ', '\t', '\n', '\r': + // Skip whitespace + continue + case ',': + // If it's a comma, replace it with a space + p.dst[i] = ' ' + default: + // Stop after finding the first non-whitespace character + return + } + } +} + +// isPreservedWhitespace returns true for whitespace that should be preserved +func isPreservedWhitespace(c byte) bool { + return c == '\n' || c == '\t' || c == '\r' +} diff --git a/pkg/x/json/jsonc_test.go b/pkg/x/json/jsonc_test.go new file mode 100644 index 000000000000..3a1422ca614d --- /dev/null +++ b/pkg/x/json/jsonc_test.go @@ -0,0 +1,190 @@ +package json_test + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + xjson "github.com/aquasecurity/trivy/pkg/x/json" +) + +func TestToRFC8259(t *testing.T) { + tests := []struct { + name string + input string + want string + }{ + { + name: "no comments", + input: `{"a": 1, "b": 2}`, + want: `{"a": 1, "b": 2}`, + }, + { + name: "single-line comment", + input: "{\n \"a\": 1, // This is a comment\n \"b\": 2\n}", + want: "{\n \"a\": 1, \n \"b\": 2\n}", + }, + { + name: "multi-line comment", + input: "{\n \"a\": 1, /* This is\n a multi-line\n comment */ \"b\": 2\n}", + want: "{\n \"a\": 1, \n \n \"b\": 2\n}", + }, + { + name: "comment with forward slash in string", + input: "{\n \"url\": \"http://example.com\", // Comment\n \"value\": 123\n}", + want: "{\n \"url\": \"http://example.com\", \n \"value\": 123\n}", + }, + { + name: "trailing comma in object", + input: `{"a": 1, "b": 2,}`, + want: `{"a": 1, "b": 2 }`, + }, + { + name: "trailing comma in array", + input: `[1, 2, 3,]`, + want: `[1, 2, 3 ]`, + }, + { + name: "nested trailing commas", + input: `{"a": [1, 2,], "b": {"x": 1, "y": 2,},}`, + want: `{"a": [1, 2 ], "b": {"x": 1, "y": 2 } }`, + }, + { + name: "single-line comment at end of file without newline", + input: `{"a": 1} // Comment`, + want: `{"a": 1} `, + }, + { + name: "multi-line comment at end of file", + input: `{"a": 1} /* Comment */`, + want: `{"a": 1} `, + }, + { + name: "comment within string", + input: `{"text": "This string has // comment syntax"}`, + want: `{"text": "This string has // comment syntax"}`, + }, + { + name: "quoted comment markers", + input: `{"a": "//", "b": "/*", "c": "*/"}`, + want: `{"a": "//", "b": "/*", "c": "*/"}`, + }, + { + name: "escaped quotes in string", + input: `{"text": "String with \"escaped quotes\" // not a comment"}`, + want: `{"text": "String with \"escaped quotes\" // not a comment"}`, + }, + { + name: "complex escaped quotes", + input: `{"text": "String with \\\"double escaped\\\" quotes"}`, + want: `{"text": "String with \\\"double escaped\\\" quotes"}`, + }, + { + name: "real world example", + input: `{ + "name": "my-package", // Package name + "version": "1.0.0", /* Version number */ + "dependencies": { + "lodash": "^4.17.21", + "express": "^4.17.1", // Latest express + }, + "scripts": { + "start": "node index.js", + "test": "jest", + } +}`, + want: `{ + "name": "my-package", + "version": "1.0.0", + "dependencies": { + "lodash": "^4.17.21", + "express": "^4.17.1" + }, + "scripts": { + "start": "node index.js", + "test": "jest" + } +}`, + }, + { + name: "preserves newlines in multiline comments", + input: `{ + "name": "test", // Comment + /* + * Multi-line + * comment + */ + "value": 42 +}`, + want: `{ + "name": "test", + + + + + "value": 42 +}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Test ToRFC8259 (allocates new buffer) + got := xjson.ToRFC8259([]byte(tt.input)) + + // Check length preservation + require.Len(t, got, len(tt.input), "output length should match input length") + + // Check content + assert.Equal(t, tt.want, string(got)) + + // Verify newline count is preserved + inputNewlines := bytes.Count([]byte(tt.input), []byte{'\n'}) + outputNewlines := bytes.Count(got, []byte{'\n'}) + assert.Equal(t, inputNewlines, outputNewlines, "number of newlines should be preserved") + + // Make sure the output is valid JSON + var jsonMap any + err := xjson.Unmarshal(got, &jsonMap) + require.NoError(t, err, "result should be valid JSON") + }) + } +} + +func TestUnmarshalJSONC(t *testing.T) { + jsonc := `{ + "name": "test", // This is a comment + "dependencies": { + "lodash": "^4.17.21", /* Another comment */ + "express": "^4.17.1", // Comment + }, // Trailing comment + /* Multi-line + comment */ + "version": "1.0.0" +}` + + type Config struct { + Name string `json:"name"` + Dependencies map[string]string `json:"dependencies"` + Version string `json:"version"` + xjson.Location + } + + var config Config + err := xjson.UnmarshalJSONC([]byte(jsonc), &config) + require.NoError(t, err) + + // Verify the parsed content + assert.Equal(t, "test", config.Name) + assert.Equal(t, "1.0.0", config.Version) + assert.Equal(t, map[string]string{ + "lodash": "^4.17.21", + "express": "^4.17.1", + }, config.Dependencies) + + // Verify location information + assert.Equal(t, 1, config.StartLine) + assert.Equal(t, 10, config.EndLine) +} diff --git a/pkg/x/sync/sync.go b/pkg/x/sync/sync.go index ad2b7e9844be..ad833fe062cf 100644 --- a/pkg/x/sync/sync.go +++ b/pkg/x/sync/sync.go @@ -38,7 +38,7 @@ func (m *Map[K, V]) Store(key K, value V) { m.m.Store(key, value) } // Len returns the length of the map func (m *Map[K, V]) Len() int { var i int - m.m.Range(func(k, v any) bool { + m.m.Range(func(_, _ any) bool { i++ return true }) diff --git a/rpc/cache/service.pb.go b/rpc/cache/service.pb.go index 29bbc5a68d95..c98cd177c9b9 100644 --- a/rpc/cache/service.pb.go +++ b/rpc/cache/service.pb.go @@ -191,6 +191,8 @@ type BlobInfo struct { CustomResources []*common.CustomResource `protobuf:"bytes,10,rep,name=custom_resources,json=customResources,proto3" json:"custom_resources,omitempty"` Secrets []*common.Secret `protobuf:"bytes,12,rep,name=secrets,proto3" json:"secrets,omitempty"` Licenses []*common.LicenseFile `protobuf:"bytes,13,rep,name=licenses,proto3" json:"licenses,omitempty"` + Size int64 `protobuf:"varint,14,opt,name=size,proto3" json:"size,omitempty"` + CreatedBy string `protobuf:"bytes,15,opt,name=created_by,json=createdBy,proto3" json:"created_by,omitempty"` } func (x *BlobInfo) Reset() { @@ -316,6 +318,20 @@ func (x *BlobInfo) GetLicenses() []*common.LicenseFile { return nil } +func (x *BlobInfo) GetSize() int64 { + if x != nil { + return x.Size + } + return 0 +} + +func (x *BlobInfo) GetCreatedBy() string { + if x != nil { + return x.CreatedBy + } + return "" +} + type PutBlobRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -621,7 +637,7 @@ var file_rpc_cache_service_proto_rawDesc = []byte{ 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0c, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, - 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x83, 0x05, 0x0a, 0x08, 0x42, 0x6c, 0x6f, 0x62, 0x49, 0x6e, 0x66, + 0x49, 0x6e, 0x66, 0x6f, 0x22, 0xb6, 0x05, 0x0a, 0x08, 0x42, 0x6c, 0x6f, 0x62, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x02, 0x6f, 0x73, 0x18, 0x02, @@ -661,56 +677,59 @@ var file_rpc_cache_service_proto_rawDesc = []byte{ 0x65, 0x74, 0x73, 0x12, 0x35, 0x0a, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x46, 0x69, 0x6c, 0x65, - 0x52, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x22, 0x60, 0x0a, 0x0e, 0x50, 0x75, - 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, - 0x64, 0x69, 0x66, 0x66, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, - 0x69, 0x66, 0x66, 0x49, 0x64, 0x12, 0x35, 0x0a, 0x09, 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x69, 0x6e, - 0x66, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, - 0x2e, 0x63, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x62, 0x49, 0x6e, - 0x66, 0x6f, 0x52, 0x08, 0x62, 0x6c, 0x6f, 0x62, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x43, 0x0a, 0x0b, - 0x50, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x02, 0x6f, - 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, - 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4f, 0x53, 0x52, 0x02, 0x6f, 0x73, 0x12, 0x12, 0x0a, - 0x04, 0x65, 0x6f, 0x73, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x65, 0x6f, 0x73, - 0x6c, 0x22, 0x51, 0x0a, 0x13, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x42, 0x6c, 0x6f, 0x62, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x72, 0x74, 0x69, - 0x66, 0x61, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, - 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x6c, 0x6f, - 0x62, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x62, 0x6c, 0x6f, - 0x62, 0x49, 0x64, 0x73, 0x22, 0x6b, 0x0a, 0x14, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x42, - 0x6c, 0x6f, 0x62, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x29, 0x0a, 0x10, - 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x41, - 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x69, 0x73, 0x73, 0x69, - 0x6e, 0x67, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x0e, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x42, 0x6c, 0x6f, 0x62, 0x49, 0x64, - 0x73, 0x22, 0x2f, 0x0a, 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x6c, 0x6f, 0x62, 0x5f, - 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x62, 0x6c, 0x6f, 0x62, 0x49, - 0x64, 0x73, 0x32, 0xbb, 0x02, 0x0a, 0x05, 0x43, 0x61, 0x63, 0x68, 0x65, 0x12, 0x49, 0x0a, 0x0b, - 0x50, 0x75, 0x74, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x12, 0x22, 0x2e, 0x74, 0x72, - 0x69, 0x76, 0x79, 0x2e, 0x63, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x74, - 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x41, 0x0a, 0x07, 0x50, 0x75, 0x74, 0x42, 0x6c, - 0x6f, 0x62, 0x12, 0x1e, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x61, 0x63, 0x68, 0x65, - 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x52, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, + 0x7a, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x1d, + 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x18, 0x0f, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x22, 0x60, 0x0a, + 0x0e, 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x17, 0x0a, 0x07, 0x64, 0x69, 0x66, 0x66, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x64, 0x69, 0x66, 0x66, 0x49, 0x64, 0x12, 0x35, 0x0a, 0x09, 0x62, 0x6c, 0x6f, 0x62, + 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x74, 0x72, + 0x69, 0x76, 0x79, 0x2e, 0x63, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, + 0x62, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x62, 0x6c, 0x6f, 0x62, 0x49, 0x6e, 0x66, 0x6f, 0x22, + 0x43, 0x0a, 0x0b, 0x50, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, + 0x0a, 0x02, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x72, 0x69, + 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4f, 0x53, 0x52, 0x02, 0x6f, 0x73, + 0x12, 0x12, 0x0a, 0x04, 0x65, 0x6f, 0x73, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, + 0x65, 0x6f, 0x73, 0x6c, 0x22, 0x51, 0x0a, 0x13, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x42, + 0x6c, 0x6f, 0x62, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x61, + 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, + 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, + 0x62, 0x6c, 0x6f, 0x62, 0x49, 0x64, 0x73, 0x22, 0x6b, 0x0a, 0x14, 0x4d, 0x69, 0x73, 0x73, 0x69, + 0x6e, 0x67, 0x42, 0x6c, 0x6f, 0x62, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x29, 0x0a, 0x10, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, + 0x61, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6e, 0x67, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x42, 0x6c, 0x6f, + 0x62, 0x49, 0x64, 0x73, 0x22, 0x2f, 0x0a, 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x6c, + 0x6f, 0x62, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x6c, + 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x62, 0x6c, + 0x6f, 0x62, 0x49, 0x64, 0x73, 0x32, 0xbb, 0x02, 0x0a, 0x05, 0x43, 0x61, 0x63, 0x68, 0x65, 0x12, + 0x49, 0x0a, 0x0b, 0x50, 0x75, 0x74, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x12, 0x22, + 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x76, 0x31, 0x2e, + 0x50, 0x75, 0x74, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x59, 0x0a, 0x0c, 0x4d, 0x69, - 0x73, 0x73, 0x69, 0x6e, 0x67, 0x42, 0x6c, 0x6f, 0x62, 0x73, 0x12, 0x23, 0x2e, 0x74, 0x72, 0x69, - 0x76, 0x79, 0x2e, 0x63, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x69, 0x73, 0x73, - 0x69, 0x6e, 0x67, 0x42, 0x6c, 0x6f, 0x62, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x24, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x76, 0x31, - 0x2e, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x42, 0x6c, 0x6f, 0x62, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, - 0x6c, 0x6f, 0x62, 0x73, 0x12, 0x22, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x61, 0x63, - 0x68, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x6c, 0x6f, 0x62, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, - 0x71, 0x75, 0x61, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, 0x74, 0x72, 0x69, 0x76, - 0x79, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x3b, 0x63, 0x61, 0x63, 0x68, - 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x41, 0x0a, 0x07, 0x50, 0x75, + 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x12, 0x1e, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x61, + 0x63, 0x68, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x59, 0x0a, + 0x0c, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x42, 0x6c, 0x6f, 0x62, 0x73, 0x12, 0x23, 0x2e, + 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, + 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x42, 0x6c, 0x6f, 0x62, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x61, 0x63, 0x68, 0x65, + 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x42, 0x6c, 0x6f, 0x62, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0b, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x73, 0x12, 0x22, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, + 0x63, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, + 0x6c, 0x6f, 0x62, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x61, 0x71, 0x75, 0x61, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, 0x74, + 0x72, 0x69, 0x76, 0x79, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x3b, 0x63, + 0x61, 0x63, 0x68, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/rpc/cache/service.proto b/rpc/cache/service.proto index b9f7f999f7d7..f276027a5392 100644 --- a/rpc/cache/service.proto +++ b/rpc/cache/service.proto @@ -43,6 +43,8 @@ message BlobInfo { repeated common.CustomResource custom_resources = 10; repeated common.Secret secrets = 12; repeated common.LicenseFile licenses = 13; + int64 size = 14; + string created_by = 15; } message PutBlobRequest { diff --git a/rpc/cache/service.twirp.go b/rpc/cache/service.twirp.go index 369293c0c6aa..dc3bd09e378c 100644 --- a/rpc/cache/service.twirp.go +++ b/rpc/cache/service.twirp.go @@ -1933,58 +1933,60 @@ func callClientError(ctx context.Context, h *twirp.ClientHooks, err twirp.Error) } var twirpFileDescriptor0 = []byte{ - // 843 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x55, 0xdf, 0x8f, 0xdb, 0x44, - 0x10, 0x56, 0x92, 0xbb, 0x4b, 0x32, 0xf9, 0x71, 0xc7, 0x52, 0x5a, 0x37, 0x54, 0x6d, 0x64, 0x40, - 0x0a, 0x12, 0xd8, 0xe2, 0xa0, 0x12, 0x12, 0x02, 0x71, 0xbd, 0x02, 0x8a, 0xd4, 0x8a, 0x63, 0x8b, - 0x90, 0xe0, 0x25, 0x38, 0xeb, 0x75, 0xb2, 0xba, 0xd8, 0xeb, 0xdb, 0x59, 0x1f, 0xdc, 0x33, 0xff, - 0x13, 0x2f, 0xfc, 0x73, 0x68, 0xd7, 0xeb, 0x24, 0x4e, 0xd2, 0x13, 0xbc, 0x44, 0xd9, 0x99, 0x6f, - 0xbe, 0x9d, 0xf9, 0xe6, 0xb3, 0x0d, 0x8f, 0x54, 0xce, 0x42, 0x16, 0xb1, 0x25, 0x0f, 0x91, 0xab, - 0x5b, 0xc1, 0x78, 0x90, 0x2b, 0xa9, 0x25, 0x19, 0x6a, 0x25, 0x6e, 0xef, 0x02, 0x9b, 0x0a, 0x6e, - 0x3f, 0x1b, 0x3d, 0x5b, 0x48, 0xb9, 0x58, 0xf1, 0xd0, 0x66, 0xe7, 0x45, 0x12, 0x6a, 0x91, 0x72, - 0xd4, 0x51, 0x9a, 0x97, 0x05, 0x23, 0xcf, 0x32, 0xc9, 0x34, 0x95, 0x59, 0x9d, 0x6a, 0xf4, 0xfe, - 0x6e, 0x29, 0x4f, 0x73, 0x7d, 0x57, 0x26, 0xfd, 0xbf, 0x9b, 0xd0, 0xbf, 0x50, 0x5a, 0x24, 0x11, - 0xd3, 0xd3, 0x2c, 0x91, 0xe4, 0x23, 0x18, 0x22, 0x5b, 0xf2, 0x34, 0x9a, 0xdd, 0x72, 0x85, 0x42, - 0x66, 0x5e, 0x63, 0xdc, 0x98, 0x1c, 0xd3, 0x41, 0x19, 0xfd, 0xa5, 0x0c, 0x12, 0x1f, 0xfa, 0x91, - 0x62, 0x4b, 0xa1, 0x39, 0xd3, 0x85, 0xe2, 0x5e, 0x73, 0xdc, 0x98, 0x74, 0x69, 0x2d, 0x46, 0xbe, - 0x80, 0x36, 0x53, 0x3c, 0xd2, 0x3c, 0xf6, 0x5a, 0xe3, 0xc6, 0xa4, 0x77, 0x3e, 0x0a, 0xca, 0x56, - 0x82, 0xaa, 0x95, 0xe0, 0xe7, 0x6a, 0x0a, 0x5a, 0x41, 0x4d, 0x03, 0xb1, 0x64, 0xd7, 0x5c, 0xad, - 0x1b, 0x38, 0xb2, 0xdc, 0x83, 0x32, 0x5a, 0x35, 0x30, 0x84, 0xa6, 0x44, 0xef, 0xd8, 0xa6, 0x9a, - 0x12, 0xc9, 0xb7, 0x70, 0xb6, 0x14, 0xa8, 0xa5, 0xba, 0x9b, 0xe5, 0x11, 0xbb, 0x8e, 0x16, 0x1c, - 0xbd, 0x93, 0x71, 0x6b, 0xd2, 0x3b, 0x7f, 0x2f, 0x70, 0x5a, 0x5a, 0x71, 0x82, 0xab, 0x32, 0x4b, - 0x4f, 0x1d, 0xdc, 0x9d, 0x91, 0x7c, 0x02, 0x27, 0xc8, 0x99, 0xe2, 0xda, 0x6b, 0xdb, 0x6e, 0x1f, - 0xd4, 0xeb, 0xde, 0xd8, 0x1c, 0x75, 0x18, 0xff, 0x4f, 0x20, 0x57, 0x85, 0xae, 0xa4, 0xa3, 0xfc, - 0xa6, 0xe0, 0xa8, 0xc9, 0x33, 0xe8, 0x45, 0x2e, 0x34, 0x13, 0xb1, 0x95, 0xae, 0x4b, 0xa1, 0x0a, - 0x4d, 0x63, 0x72, 0x01, 0x83, 0x0d, 0x20, 0x4b, 0xa4, 0x15, 0xae, 0x77, 0xfe, 0x24, 0xa8, 0xef, - 0x3b, 0xd8, 0xde, 0x89, 0x91, 0x75, 0x73, 0xf2, 0xff, 0x3a, 0x86, 0xce, 0x8b, 0x95, 0x9c, 0xff, - 0x9f, 0x75, 0x8d, 0xad, 0x5a, 0xe5, 0x5d, 0x67, 0xf5, 0xb9, 0x7e, 0x7c, 0x63, 0xf5, 0xfb, 0x12, - 0x40, 0xf1, 0x5c, 0xa2, 0x30, 0x9a, 0x78, 0x3d, 0x8b, 0xf4, 0xea, 0x48, 0xba, 0xce, 0xd3, 0x2d, - 0x2c, 0xf9, 0x06, 0x06, 0x4e, 0x71, 0x3b, 0x11, 0x7a, 0x2d, 0x2b, 0xfb, 0xe3, 0x83, 0xb2, 0x97, - 0xf3, 0xe4, 0x9b, 0x03, 0x92, 0xaf, 0xa1, 0x1f, 0xe5, 0xf9, 0x4a, 0xb0, 0x48, 0x0b, 0x99, 0xa1, - 0x77, 0x74, 0xa8, 0xfc, 0x62, 0x83, 0xa0, 0x35, 0x38, 0x79, 0x05, 0xef, 0xa4, 0x02, 0x99, 0xcc, - 0x12, 0xb1, 0x28, 0x94, 0xe3, 0xe8, 0x5a, 0x8e, 0xa7, 0x75, 0x8e, 0xd7, 0x3b, 0x30, 0xba, 0x5f, - 0x68, 0x16, 0x28, 0xf3, 0xe8, 0xa6, 0xe0, 0xb3, 0x58, 0x28, 0xe3, 0xaf, 0x96, 0x59, 0x60, 0x19, - 0x7a, 0x29, 0x14, 0x1a, 0xc1, 0xff, 0x30, 0x16, 0x97, 0x85, 0x9e, 0x25, 0x62, 0xe5, 0x5c, 0xd6, - 0xa5, 0x83, 0x2a, 0xfa, 0xbd, 0x09, 0x92, 0x87, 0x70, 0x12, 0x8b, 0x05, 0xc7, 0xd2, 0x4c, 0x5d, - 0xea, 0x4e, 0xe4, 0x11, 0xb4, 0x63, 0x91, 0x24, 0xc6, 0x1c, 0x9d, 0x2a, 0x91, 0x24, 0xd3, 0x98, - 0xfc, 0x00, 0x67, 0xac, 0x40, 0x2d, 0xd3, 0x99, 0xe2, 0x28, 0x0b, 0xc5, 0x38, 0x7a, 0x60, 0xa7, - 0x78, 0x52, 0x9f, 0xe2, 0xd2, 0xa2, 0xa8, 0x03, 0xd1, 0x53, 0x56, 0x3b, 0x23, 0x09, 0xa0, 0x5d, - 0x5a, 0x14, 0xbd, 0xbe, 0xad, 0x3f, 0xec, 0xe3, 0x0a, 0x44, 0x9e, 0x43, 0x67, 0x25, 0x18, 0xcf, - 0x90, 0xa3, 0x37, 0x38, 0x24, 0xfd, 0xab, 0x32, 0x6b, 0xe6, 0xa2, 0x6b, 0xa8, 0xff, 0x3b, 0x0c, - 0xaf, 0x0a, 0x6d, 0x7c, 0x58, 0x79, 0x7f, 0x6b, 0xb4, 0x46, 0x6d, 0xb4, 0xe7, 0xd0, 0x9d, 0xaf, - 0xe4, 0xbc, 0xf4, 0x7b, 0xab, 0xee, 0xac, 0xca, 0xef, 0x95, 0xa1, 0x69, 0x67, 0xee, 0xfe, 0xf9, - 0x97, 0xd0, 0xbb, 0x2a, 0x34, 0xe5, 0x98, 0xcb, 0x0c, 0xb9, 0xb3, 0x70, 0xe3, 0x1e, 0x0b, 0x13, - 0x38, 0xe2, 0x12, 0x57, 0xd6, 0xe6, 0x1d, 0x6a, 0xff, 0xfb, 0x3f, 0xc1, 0xbb, 0xaf, 0x05, 0xa2, - 0xc8, 0x16, 0xe6, 0x06, 0xfc, 0xcf, 0xcf, 0xe9, 0x63, 0xe8, 0x94, 0x3d, 0xc7, 0xe6, 0xb1, 0x31, - 0x0b, 0x6e, 0xdb, 0xc6, 0x62, 0xf4, 0xaf, 0xe1, 0x41, 0x9d, 0xd2, 0x35, 0xf8, 0x31, 0x9c, 0xa5, - 0x65, 0x7c, 0x56, 0x11, 0x59, 0xe2, 0x0e, 0x3d, 0x75, 0xf1, 0xea, 0xa1, 0x26, 0x93, 0x0d, 0x74, - 0xe7, 0x96, 0x61, 0xba, 0xa1, 0x36, 0x97, 0x85, 0x40, 0x5e, 0xf2, 0x15, 0xd7, 0xbc, 0xd6, 0xfe, - 0x76, 0x77, 0x8d, 0x5a, 0x77, 0xe7, 0xff, 0x34, 0xe1, 0xf8, 0xd2, 0xa8, 0x4a, 0xa6, 0x56, 0xbf, - 0xf5, 0x9d, 0xfe, 0xae, 0xe4, 0xfb, 0xaf, 0xaf, 0xd1, 0xc3, 0xbd, 0x17, 0xf4, 0x77, 0xe6, 0x5b, - 0x41, 0x2e, 0xa0, 0xed, 0x96, 0x4d, 0x9e, 0x1e, 0xa0, 0xd9, 0x72, 0xc1, 0x5b, 0x29, 0x7e, 0x85, - 0xfe, 0xb6, 0x6a, 0xe4, 0x83, 0x5d, 0x9e, 0x03, 0x6b, 0x1a, 0x7d, 0x78, 0x3f, 0xc8, 0x09, 0x3f, - 0x85, 0xde, 0x96, 0x46, 0xfb, 0x83, 0xee, 0x0b, 0xf8, 0xb6, 0x2e, 0x5f, 0x84, 0xbf, 0x7d, 0xba, - 0x10, 0x7a, 0x59, 0xcc, 0x8d, 0xb5, 0xc2, 0xe8, 0xa6, 0x88, 0x90, 0xb3, 0x42, 0x09, 0x7d, 0x17, - 0x5a, 0xd2, 0x70, 0xfd, 0xbd, 0xfe, 0xca, 0xfe, 0xce, 0x4f, 0x2c, 0xc1, 0xe7, 0xff, 0x06, 0x00, - 0x00, 0xff, 0xff, 0x60, 0x74, 0xd3, 0x39, 0xc9, 0x07, 0x00, 0x00, + // 872 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x55, 0xef, 0x8f, 0x1b, 0x35, + 0x10, 0x55, 0x92, 0xbb, 0x4b, 0x32, 0xf9, 0x71, 0x87, 0x29, 0xed, 0x36, 0x94, 0x36, 0x0a, 0x20, + 0x05, 0x09, 0x76, 0xc5, 0x41, 0x25, 0x24, 0x04, 0xe2, 0xee, 0x0a, 0x28, 0x52, 0x2b, 0x0e, 0x17, + 0x21, 0xc1, 0x97, 0xb0, 0xf1, 0x7a, 0x13, 0xeb, 0xb2, 0xeb, 0x3d, 0x8f, 0xf7, 0x20, 0xfc, 0x61, + 0xfd, 0xc2, 0x3f, 0x87, 0xec, 0xf5, 0x26, 0xd9, 0x24, 0x3d, 0xc1, 0x97, 0x68, 0x3d, 0xf3, 0xfc, + 0x3c, 0xf3, 0xfc, 0xc6, 0x81, 0x47, 0x2a, 0x63, 0x01, 0x0b, 0xd9, 0x82, 0x07, 0xc8, 0xd5, 0x9d, + 0x60, 0xdc, 0xcf, 0x94, 0xd4, 0x92, 0xf4, 0xb5, 0x12, 0x77, 0x2b, 0xdf, 0xa6, 0xfc, 0xbb, 0xcf, + 0x07, 0xcf, 0xe6, 0x52, 0xce, 0x97, 0x3c, 0xb0, 0xd9, 0x59, 0x1e, 0x07, 0x5a, 0x24, 0x1c, 0x75, + 0x98, 0x64, 0xc5, 0x86, 0x81, 0x67, 0x99, 0x64, 0x92, 0xc8, 0xb4, 0x4a, 0x35, 0x78, 0x7f, 0x77, + 0x2b, 0x4f, 0x32, 0xbd, 0x2a, 0x92, 0xa3, 0x37, 0x75, 0xe8, 0x5e, 0x28, 0x2d, 0xe2, 0x90, 0xe9, + 0x49, 0x1a, 0x4b, 0xf2, 0x31, 0xf4, 0x91, 0x2d, 0x78, 0x12, 0x4e, 0xef, 0xb8, 0x42, 0x21, 0x53, + 0xaf, 0x36, 0xac, 0x8d, 0x8f, 0x69, 0xaf, 0x88, 0xfe, 0x5a, 0x04, 0xc9, 0x08, 0xba, 0xa1, 0x62, + 0x0b, 0xa1, 0x39, 0xd3, 0xb9, 0xe2, 0x5e, 0x7d, 0x58, 0x1b, 0xb7, 0x69, 0x25, 0x46, 0xbe, 0x84, + 0x26, 0x53, 0x3c, 0xd4, 0x3c, 0xf2, 0x1a, 0xc3, 0xda, 0xb8, 0x73, 0x3e, 0xf0, 0x8b, 0x52, 0xfc, + 0xb2, 0x14, 0xff, 0x97, 0xb2, 0x0b, 0x5a, 0x42, 0x4d, 0x01, 0x91, 0x64, 0x37, 0x5c, 0xad, 0x0b, + 0x38, 0xb2, 0xdc, 0xbd, 0x22, 0x5a, 0x16, 0xd0, 0x87, 0xba, 0x44, 0xef, 0xd8, 0xa6, 0xea, 0x12, + 0xc9, 0x77, 0x70, 0xb6, 0x10, 0xa8, 0xa5, 0x5a, 0x4d, 0xb3, 0x90, 0xdd, 0x84, 0x73, 0x8e, 0xde, + 0xc9, 0xb0, 0x31, 0xee, 0x9c, 0xbf, 0xe7, 0x3b, 0x2d, 0xad, 0x38, 0xfe, 0x75, 0x91, 0xa5, 0xa7, + 0x0e, 0xee, 0xd6, 0x48, 0x3e, 0x85, 0x13, 0xe4, 0x4c, 0x71, 0xed, 0x35, 0x6d, 0xb5, 0x0f, 0xaa, + 0xfb, 0x5e, 0xdb, 0x1c, 0x75, 0x98, 0xd1, 0x5f, 0x40, 0xae, 0x73, 0x5d, 0x4a, 0x47, 0xf9, 0x6d, + 0xce, 0x51, 0x93, 0x67, 0xd0, 0x09, 0x5d, 0x68, 0x2a, 0x22, 0x2b, 0x5d, 0x9b, 0x42, 0x19, 0x9a, + 0x44, 0xe4, 0x02, 0x7a, 0x1b, 0x40, 0x1a, 0x4b, 0x2b, 0x5c, 0xe7, 0xfc, 0x89, 0x5f, 0xbd, 0x6f, + 0x7f, 0xfb, 0x4e, 0x8c, 0xac, 0x9b, 0xd5, 0xe8, 0xcd, 0x31, 0xb4, 0x2e, 0x97, 0x72, 0xf6, 0x7f, + 0xae, 0x6b, 0x68, 0xd5, 0x2a, 0xce, 0x3a, 0xab, 0xf6, 0xf5, 0xd3, 0x6b, 0xab, 0xdf, 0x57, 0x00, + 0x8a, 0x67, 0x12, 0x85, 0xd1, 0xc4, 0xeb, 0x58, 0xa4, 0x57, 0x45, 0xd2, 0x75, 0x9e, 0x6e, 0x61, + 0xc9, 0xb7, 0xd0, 0x73, 0x8a, 0xdb, 0x8e, 0xd0, 0x6b, 0x58, 0xd9, 0x1f, 0x1f, 0x94, 0xbd, 0xe8, + 0x27, 0xdb, 0x2c, 0x90, 0x7c, 0x03, 0xdd, 0x30, 0xcb, 0x96, 0x82, 0x85, 0x5a, 0xc8, 0x14, 0xbd, + 0xa3, 0x43, 0xdb, 0x2f, 0x36, 0x08, 0x5a, 0x81, 0x93, 0x97, 0xf0, 0x4e, 0x22, 0x90, 0xc9, 0x34, + 0x16, 0xf3, 0x5c, 0x39, 0x8e, 0xb6, 0xe5, 0x78, 0x5a, 0xe5, 0x78, 0xb5, 0x03, 0xa3, 0xfb, 0x1b, + 0xcd, 0x05, 0xca, 0x2c, 0xbc, 0xcd, 0xf9, 0x34, 0x12, 0xca, 0xf8, 0xab, 0x61, 0x2e, 0xb0, 0x08, + 0xbd, 0x10, 0x0a, 0x8d, 0xe0, 0x7f, 0x1a, 0x8b, 0xcb, 0x5c, 0x4f, 0x63, 0xb1, 0x74, 0x2e, 0x6b, + 0xd3, 0x5e, 0x19, 0xfd, 0xc1, 0x04, 0xc9, 0x43, 0x38, 0x89, 0xc4, 0x9c, 0x63, 0x61, 0xa6, 0x36, + 0x75, 0x2b, 0xf2, 0x08, 0x9a, 0x91, 0x88, 0x63, 0x63, 0x8e, 0x56, 0x99, 0x88, 0xe3, 0x49, 0x44, + 0x7e, 0x84, 0x33, 0x96, 0xa3, 0x96, 0xc9, 0x54, 0x71, 0x94, 0xb9, 0x62, 0x1c, 0x3d, 0xb0, 0x5d, + 0x3c, 0xa9, 0x76, 0x71, 0x65, 0x51, 0xd4, 0x81, 0xe8, 0x29, 0xab, 0xac, 0x91, 0xf8, 0xd0, 0x2c, + 0x2c, 0x8a, 0x5e, 0xd7, 0xee, 0x3f, 0xec, 0xe3, 0x12, 0x44, 0x9e, 0x43, 0x6b, 0x29, 0x18, 0x4f, + 0x91, 0xa3, 0xd7, 0x3b, 0x24, 0xfd, 0xcb, 0x22, 0x6b, 0xfa, 0xa2, 0x6b, 0x28, 0x21, 0x70, 0x84, + 0xe2, 0x6f, 0xee, 0xf5, 0x87, 0xb5, 0x71, 0x83, 0xda, 0x6f, 0xf2, 0x01, 0x80, 0x9b, 0xe2, 0xe9, + 0x6c, 0xe5, 0x9d, 0xda, 0xfe, 0xda, 0x2e, 0x72, 0xb9, 0x1a, 0xfd, 0x01, 0xfd, 0xeb, 0x5c, 0x1b, + 0xeb, 0x96, 0xe3, 0xb2, 0xa5, 0x46, 0xad, 0xa2, 0xc6, 0x73, 0x68, 0xcf, 0x96, 0x72, 0x56, 0x8c, + 0x48, 0xa3, 0x6a, 0xc6, 0x72, 0x44, 0xca, 0x19, 0xa0, 0xad, 0x99, 0xfb, 0x1a, 0x5d, 0x41, 0xe7, + 0x3a, 0xd7, 0x94, 0x63, 0x26, 0x53, 0xe4, 0xce, 0xf5, 0xb5, 0x7b, 0x5c, 0x4f, 0xe0, 0x88, 0x4b, + 0x5c, 0xda, 0xc9, 0x68, 0x51, 0xfb, 0x3d, 0xfa, 0x19, 0xde, 0x7d, 0x25, 0x10, 0x45, 0x3a, 0x37, + 0x27, 0xe0, 0x7f, 0x1e, 0xed, 0xc7, 0xd0, 0x2a, 0x6a, 0x8e, 0xcc, 0xa4, 0x19, 0x4f, 0x34, 0x6d, + 0x61, 0x11, 0x8e, 0x6e, 0xe0, 0x41, 0x95, 0xd2, 0x15, 0xf8, 0x09, 0x9c, 0x25, 0x45, 0x7c, 0x5a, + 0x12, 0x59, 0xe2, 0x16, 0x3d, 0x75, 0xf1, 0xf2, 0x1d, 0x20, 0xe3, 0x0d, 0x74, 0xe7, 0x94, 0x7e, + 0xb2, 0xa1, 0x36, 0x87, 0x05, 0x40, 0x5e, 0xf0, 0x25, 0xd7, 0xbc, 0x52, 0xfe, 0x76, 0x75, 0xb5, + 0x4a, 0x75, 0xe7, 0xff, 0xd4, 0xe1, 0xf8, 0xca, 0xa8, 0x4a, 0x26, 0x56, 0xbf, 0xf5, 0x99, 0xa3, + 0x5d, 0xc9, 0xf7, 0x5f, 0xbc, 0xc1, 0xc3, 0xbd, 0x37, 0xfd, 0x7b, 0xf3, 0xf7, 0x42, 0x2e, 0xa0, + 0xe9, 0x2e, 0x9b, 0x3c, 0x3d, 0x40, 0xb3, 0xe5, 0x82, 0xb7, 0x52, 0xfc, 0x06, 0xdd, 0x6d, 0xd5, + 0xc8, 0x87, 0xbb, 0x3c, 0x07, 0xae, 0x69, 0xf0, 0xd1, 0xfd, 0x20, 0x27, 0xfc, 0x04, 0x3a, 0x5b, + 0x1a, 0xed, 0x37, 0xba, 0x2f, 0xe0, 0xdb, 0xaa, 0xbc, 0x0c, 0x7e, 0xff, 0x6c, 0x2e, 0xf4, 0x22, + 0x9f, 0x19, 0x6b, 0x05, 0xe1, 0x6d, 0x1e, 0x22, 0x67, 0xb9, 0x12, 0x7a, 0x15, 0x58, 0xd2, 0x60, + 0xfd, 0x17, 0xff, 0xb5, 0xfd, 0x9d, 0x9d, 0x58, 0x82, 0x2f, 0xfe, 0x0d, 0x00, 0x00, 0xff, 0xff, + 0xf7, 0x8e, 0x1a, 0x5d, 0xfc, 0x07, 0x00, 0x00, } diff --git a/rpc/common/service.pb.go b/rpc/common/service.pb.go index eab2cd2d2fc0..99ce8a026ee9 100644 --- a/rpc/common/service.pb.go +++ b/rpc/common/service.pb.go @@ -453,19 +453,20 @@ type Package struct { Arch string `protobuf:"bytes,5,opt,name=arch,proto3" json:"arch,omitempty"` // src package containing some binary packages // e.g. bind - SrcName string `protobuf:"bytes,6,opt,name=src_name,json=srcName,proto3" json:"src_name,omitempty"` - SrcVersion string `protobuf:"bytes,7,opt,name=src_version,json=srcVersion,proto3" json:"src_version,omitempty"` - SrcRelease string `protobuf:"bytes,8,opt,name=src_release,json=srcRelease,proto3" json:"src_release,omitempty"` - SrcEpoch int32 `protobuf:"varint,9,opt,name=src_epoch,json=srcEpoch,proto3" json:"src_epoch,omitempty"` - Licenses []string `protobuf:"bytes,15,rep,name=licenses,proto3" json:"licenses,omitempty"` - Locations []*Location `protobuf:"bytes,20,rep,name=locations,proto3" json:"locations,omitempty"` - Layer *Layer `protobuf:"bytes,11,opt,name=layer,proto3" json:"layer,omitempty"` - FilePath string `protobuf:"bytes,12,opt,name=file_path,json=filePath,proto3" json:"file_path,omitempty"` - DependsOn []string `protobuf:"bytes,14,rep,name=depends_on,json=dependsOn,proto3" json:"depends_on,omitempty"` - Digest string `protobuf:"bytes,16,opt,name=digest,proto3" json:"digest,omitempty"` - Dev bool `protobuf:"varint,17,opt,name=dev,proto3" json:"dev,omitempty"` - Indirect bool `protobuf:"varint,18,opt,name=indirect,proto3" json:"indirect,omitempty"` - Maintainer string `protobuf:"bytes,21,opt,name=maintainer,proto3" json:"maintainer,omitempty"` + SrcName string `protobuf:"bytes,6,opt,name=src_name,json=srcName,proto3" json:"src_name,omitempty"` + SrcVersion string `protobuf:"bytes,7,opt,name=src_version,json=srcVersion,proto3" json:"src_version,omitempty"` + SrcRelease string `protobuf:"bytes,8,opt,name=src_release,json=srcRelease,proto3" json:"src_release,omitempty"` + SrcEpoch int32 `protobuf:"varint,9,opt,name=src_epoch,json=srcEpoch,proto3" json:"src_epoch,omitempty"` + Licenses []string `protobuf:"bytes,15,rep,name=licenses,proto3" json:"licenses,omitempty"` + Locations []*Location `protobuf:"bytes,20,rep,name=locations,proto3" json:"locations,omitempty"` + Layer *Layer `protobuf:"bytes,11,opt,name=layer,proto3" json:"layer,omitempty"` + FilePath string `protobuf:"bytes,12,opt,name=file_path,json=filePath,proto3" json:"file_path,omitempty"` + DependsOn []string `protobuf:"bytes,14,rep,name=depends_on,json=dependsOn,proto3" json:"depends_on,omitempty"` + Digest string `protobuf:"bytes,16,opt,name=digest,proto3" json:"digest,omitempty"` + Dev bool `protobuf:"varint,17,opt,name=dev,proto3" json:"dev,omitempty"` + Indirect bool `protobuf:"varint,18,opt,name=indirect,proto3" json:"indirect,omitempty"` + Maintainer string `protobuf:"bytes,21,opt,name=maintainer,proto3" json:"maintainer,omitempty"` + Relationship int32 `protobuf:"varint,22,opt,name=relationship,proto3" json:"relationship,omitempty"` } func (x *Package) Reset() { @@ -640,6 +641,13 @@ func (x *Package) GetMaintainer() string { return "" } +func (x *Package) GetRelationship() int32 { + if x != nil { + return x.Relationship + } + return 0 +} + type PkgIdentifier struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1472,6 +1480,7 @@ type Layer struct { Digest string `protobuf:"bytes,1,opt,name=digest,proto3" json:"digest,omitempty"` DiffId string `protobuf:"bytes,2,opt,name=diff_id,json=diffId,proto3" json:"diff_id,omitempty"` CreatedBy string `protobuf:"bytes,3,opt,name=created_by,json=createdBy,proto3" json:"created_by,omitempty"` + Size int64 `protobuf:"varint,4,opt,name=size,proto3" json:"size,omitempty"` } func (x *Layer) Reset() { @@ -1527,6 +1536,13 @@ func (x *Layer) GetCreatedBy() string { return "" } +func (x *Layer) GetSize() int64 { + if x != nil { + return x.Size + } + return 0 +} + type CauseMetadata struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2515,7 +2531,7 @@ var file_rpc_common_service_proto_rawDesc = []byte{ 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x31, 0x0a, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, - 0x65, 0x52, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x22, 0xe1, 0x04, 0x0a, 0x07, + 0x65, 0x52, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x22, 0x85, 0x05, 0x0a, 0x07, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, @@ -2553,335 +2569,338 @@ var file_rpc_common_service_proto_rawDesc = []byte{ 0x64, 0x65, 0x76, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x6e, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x6d, 0x61, 0x69, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x15, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x61, 0x69, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x22, - 0x4e, 0x0a, 0x0d, 0x50, 0x6b, 0x67, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, - 0x12, 0x12, 0x0a, 0x04, 0x70, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x70, 0x75, 0x72, 0x6c, 0x12, 0x17, 0x0a, 0x07, 0x62, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x66, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x62, 0x6f, 0x6d, 0x52, 0x65, 0x66, 0x12, 0x10, 0x0a, - 0x03, 0x75, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x69, 0x64, 0x22, - 0x44, 0x0a, 0x08, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x73, - 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, - 0x64, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x65, 0x6e, - 0x64, 0x4c, 0x69, 0x6e, 0x65, 0x22, 0xff, 0x01, 0x0a, 0x10, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, - 0x6c, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, - 0x69, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x61, 0x69, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, + 0x22, 0x0a, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x18, + 0x16, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x68, 0x69, 0x70, 0x22, 0x4e, 0x0a, 0x0d, 0x50, 0x6b, 0x67, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x66, 0x69, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x70, 0x75, 0x72, 0x6c, 0x12, 0x17, 0x0a, 0x07, 0x62, 0x6f, 0x6d, 0x5f, + 0x72, 0x65, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x62, 0x6f, 0x6d, 0x52, 0x65, + 0x66, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x75, 0x69, 0x64, 0x22, 0x44, 0x0a, 0x08, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x19, + 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x07, 0x65, 0x6e, 0x64, 0x4c, 0x69, 0x6e, 0x65, 0x22, 0xff, 0x01, 0x0a, 0x10, 0x4d, 0x69, + 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, + 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, + 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x39, 0x0a, 0x09, 0x73, 0x75, 0x63, 0x63, + 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, + 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x69, 0x73, 0x63, 0x6f, + 0x6e, 0x66, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x09, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, + 0x73, 0x65, 0x73, 0x12, 0x37, 0x0a, 0x08, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x18, + 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x52, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x52, 0x08, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x37, 0x0a, 0x08, + 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, + 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x69, + 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x08, 0x66, 0x61, 0x69, + 0x6c, 0x75, 0x72, 0x65, 0x73, 0x4a, 0x04, 0x08, 0x06, 0x10, 0x07, 0x22, 0xf3, 0x01, 0x0a, 0x0d, + 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x1c, 0x0a, + 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x45, 0x0a, 0x0f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, + 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x6f, + 0x6c, 0x69, 0x63, 0x79, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x0e, 0x70, 0x6f, + 0x6c, 0x69, 0x63, 0x79, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x42, 0x0a, 0x0e, + 0x63, 0x61, 0x75, 0x73, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, + 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x52, 0x0d, 0x63, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x4a, 0x04, 0x08, 0x03, 0x10, 0x07, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x52, 0x02, 0x69, 0x64, + 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x52, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, + 0x79, 0x22, 0xf0, 0x01, 0x0a, 0x0e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x02, 0x69, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x61, 0x64, 0x76, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x64, 0x76, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, + 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, + 0x69, 0x74, 0x79, 0x12, 0x2f, 0x0a, 0x13, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, + 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x12, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x41, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, + 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, + 0x6e, 0x63, 0x65, 0x73, 0x22, 0xf7, 0x03, 0x0a, 0x18, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, + 0x64, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, + 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x6c, + 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x0a, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, + 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x52, + 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x69, + 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x55, 0x72, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, + 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, + 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x0c, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x12, 0x42, 0x0a, + 0x0e, 0x63, 0x61, 0x75, 0x73, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, + 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x52, 0x0d, 0x63, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x12, 0x15, 0x0a, 0x06, 0x61, 0x76, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x61, 0x76, 0x64, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, + 0x79, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0xff, + 0x09, 0x0a, 0x0d, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, + 0x12, 0x29, 0x0a, 0x10, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, + 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x76, 0x75, 0x6c, 0x6e, + 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x70, + 0x6b, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, + 0x6b, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, + 0x6c, 0x65, 0x64, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x10, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x69, 0x78, 0x65, 0x64, 0x5f, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x69, 0x78, 0x65, + 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, + 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x20, + 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x32, 0x0a, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x2e, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x52, 0x08, 0x73, 0x65, 0x76, 0x65, + 0x72, 0x69, 0x74, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, + 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, + 0x6e, 0x63, 0x65, 0x73, 0x12, 0x42, 0x0a, 0x0e, 0x70, 0x6b, 0x67, 0x5f, 0x69, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, + 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x6b, 0x67, 0x49, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x0d, 0x70, 0x6b, 0x67, 0x49, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, + 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x6c, 0x61, + 0x79, 0x65, 0x72, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x5f, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x65, + 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x39, 0x0a, 0x04, + 0x63, 0x76, 0x73, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x74, 0x72, 0x69, + 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, + 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x2e, 0x43, 0x76, 0x73, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x04, 0x63, 0x76, 0x73, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x63, 0x77, 0x65, 0x5f, 0x69, + 0x64, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x63, 0x77, 0x65, 0x49, 0x64, 0x73, + 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x75, 0x72, 0x6c, 0x18, + 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x55, 0x72, + 0x6c, 0x12, 0x41, 0x0a, 0x0e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, 0x64, 0x5f, 0x64, + 0x61, 0x74, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0d, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, 0x64, + 0x44, 0x61, 0x74, 0x65, 0x12, 0x48, 0x0a, 0x12, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x6d, 0x6f, 0x64, + 0x69, 0x66, 0x69, 0x65, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x6c, 0x61, + 0x73, 0x74, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x44, 0x61, 0x74, 0x65, 0x12, 0x48, + 0x0a, 0x14, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x61, 0x64, 0x76, 0x69, 0x73, 0x6f, 0x72, + 0x79, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x52, 0x12, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x41, 0x64, 0x76, 0x69, + 0x73, 0x6f, 0x72, 0x79, 0x44, 0x61, 0x74, 0x61, 0x12, 0x40, 0x0a, 0x10, 0x63, 0x75, 0x73, 0x74, + 0x6f, 0x6d, 0x5f, 0x76, 0x75, 0x6c, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x12, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x63, 0x75, 0x73, 0x74, + 0x6f, 0x6d, 0x56, 0x75, 0x6c, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x76, 0x65, + 0x6e, 0x64, 0x6f, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x13, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, + 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x49, 0x64, 0x73, 0x12, 0x39, 0x0a, 0x0b, 0x64, 0x61, 0x74, + 0x61, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, + 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x61, + 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x53, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x12, 0x58, 0x0a, 0x0f, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x5f, 0x73, + 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x18, 0x15, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, + 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x56, 0x75, 0x6c, + 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x2e, 0x56, 0x65, 0x6e, 0x64, 0x6f, + 0x72, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, + 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x19, + 0x0a, 0x08, 0x70, 0x6b, 0x67, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x16, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x70, 0x6b, 0x67, 0x50, 0x61, 0x74, 0x68, 0x12, 0x15, 0x0a, 0x06, 0x70, 0x6b, 0x67, + 0x5f, 0x69, 0x64, 0x18, 0x17, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x6b, 0x67, 0x49, 0x64, + 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x18, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x1a, 0x4b, 0x0a, 0x09, 0x43, 0x76, 0x73, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, + 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x56, 0x53, 0x53, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x59, 0x0a, 0x13, 0x56, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x53, + 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2c, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, + 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x76, + 0x65, 0x72, 0x69, 0x74, 0x79, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x22, 0x42, 0x0a, 0x0a, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x75, 0x72, 0x6c, 0x22, 0x6b, 0x0a, 0x05, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x12, 0x16, 0x0a, + 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, + 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x69, 0x66, 0x66, 0x5f, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x69, 0x66, 0x66, 0x49, 0x64, 0x12, 0x1d, + 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x12, 0x12, 0x0a, + 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, + 0x65, 0x22, 0x87, 0x02, 0x0a, 0x0d, 0x43, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, + 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6c, + 0x69, 0x6e, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, + 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x6c, 0x69, 0x6e, 0x65, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x4c, 0x69, 0x6e, 0x65, 0x12, + 0x26, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, + 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x64, + 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x42, 0x0a, 0x0e, 0x72, 0x65, 0x6e, 0x64, 0x65, + 0x72, 0x65, 0x64, 0x5f, 0x63, 0x61, 0x75, 0x73, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x52, + 0x65, 0x6e, 0x64, 0x65, 0x72, 0x65, 0x64, 0x43, 0x61, 0x75, 0x73, 0x65, 0x52, 0x0d, 0x72, 0x65, + 0x6e, 0x64, 0x65, 0x72, 0x65, 0x64, 0x43, 0x61, 0x75, 0x73, 0x65, 0x22, 0xb2, 0x01, 0x0a, 0x04, + 0x43, 0x56, 0x53, 0x53, 0x12, 0x1b, 0x0a, 0x09, 0x76, 0x32, 0x5f, 0x76, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x76, 0x32, 0x56, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x76, 0x33, 0x5f, 0x76, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x76, 0x33, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x19, + 0x0a, 0x08, 0x76, 0x32, 0x5f, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, + 0x52, 0x07, 0x76, 0x32, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x76, 0x33, 0x5f, + 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x07, 0x76, 0x33, 0x53, + 0x63, 0x6f, 0x72, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x76, 0x34, 0x30, 0x5f, 0x76, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x76, 0x34, 0x30, 0x56, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x76, 0x34, 0x30, 0x5f, 0x73, 0x63, 0x6f, 0x72, 0x65, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x76, 0x34, 0x30, 0x53, 0x63, 0x6f, 0x72, 0x65, + 0x22, 0x98, 0x01, 0x0a, 0x0e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, - 0x50, 0x61, 0x74, 0x68, 0x12, 0x39, 0x0a, 0x09, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x65, - 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, - 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x52, 0x65, - 0x73, 0x75, 0x6c, 0x74, 0x52, 0x09, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, - 0x37, 0x0a, 0x08, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, - 0x2e, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x08, - 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x37, 0x0a, 0x08, 0x66, 0x61, 0x69, 0x6c, - 0x75, 0x72, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, - 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, - 0x66, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x08, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, - 0x73, 0x4a, 0x04, 0x08, 0x06, 0x10, 0x07, 0x22, 0xf3, 0x01, 0x0a, 0x0d, 0x4d, 0x69, 0x73, 0x63, - 0x6f, 0x6e, 0x66, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, - 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, - 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x12, 0x45, 0x0a, 0x0f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x6d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x72, 0x69, - 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x0e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x42, 0x0a, 0x0e, 0x63, 0x61, 0x75, 0x73, - 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, - 0x43, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x0d, 0x63, - 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x4a, 0x04, 0x08, 0x03, - 0x10, 0x07, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x52, 0x02, 0x69, 0x64, 0x52, 0x05, 0x74, 0x69, - 0x74, 0x6c, 0x65, 0x52, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x22, 0xf0, 0x01, - 0x0a, 0x0e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, - 0x12, 0x15, 0x0a, 0x06, 0x61, 0x64, 0x76, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x61, 0x64, 0x76, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, - 0x69, 0x74, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, - 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, - 0x2f, 0x0a, 0x13, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x61, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x72, 0x65, - 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x08, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, - 0x22, 0xf7, 0x03, 0x0a, 0x18, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4d, 0x69, 0x73, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, - 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, - 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, - 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x32, 0x0a, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, - 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x52, 0x08, 0x73, 0x65, 0x76, - 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, - 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x6d, - 0x61, 0x72, 0x79, 0x55, 0x72, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, - 0x6e, 0x63, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x66, 0x65, - 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x29, - 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, - 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x61, 0x79, - 0x65, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x12, 0x42, 0x0a, 0x0e, 0x63, 0x61, 0x75, - 0x73, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0d, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, - 0x2e, 0x43, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x0d, - 0x63, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x15, 0x0a, - 0x06, 0x61, 0x76, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, - 0x76, 0x64, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x0f, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0xff, 0x09, 0x0a, 0x0d, 0x56, - 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x29, 0x0a, 0x10, - 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, - 0x69, 0x6c, 0x69, 0x74, 0x79, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x6b, 0x67, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x6b, 0x67, 0x4e, 0x61, - 0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x5f, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x69, - 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, - 0x23, 0x0a, 0x0d, 0x66, 0x69, 0x78, 0x65, 0x64, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x69, 0x78, 0x65, 0x64, 0x56, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x0a, 0x08, - 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, - 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, - 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x52, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, - 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x08, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, - 0x12, 0x42, 0x0a, 0x0e, 0x70, 0x6b, 0x67, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, - 0x65, 0x72, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, - 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x6b, 0x67, 0x49, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x0d, 0x70, 0x6b, 0x67, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x66, 0x69, 0x65, 0x72, 0x12, 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x0a, 0x20, + 0x50, 0x61, 0x74, 0x68, 0x12, 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x12, - 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, - 0x74, 0x79, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x39, 0x0a, 0x04, 0x63, 0x76, 0x73, 0x73, - 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, - 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, - 0x69, 0x74, 0x79, 0x2e, 0x43, 0x76, 0x73, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x63, - 0x76, 0x73, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x63, 0x77, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0d, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x63, 0x77, 0x65, 0x49, 0x64, 0x73, 0x12, 0x1f, 0x0a, 0x0b, - 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0e, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x55, 0x72, 0x6c, 0x12, 0x41, 0x0a, - 0x0e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, - 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x52, 0x0d, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, 0x64, 0x44, 0x61, 0x74, 0x65, - 0x12, 0x48, 0x0a, 0x12, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, - 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x6c, 0x61, 0x73, 0x74, 0x4d, 0x6f, - 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x44, 0x61, 0x74, 0x65, 0x12, 0x48, 0x0a, 0x14, 0x63, 0x75, - 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x61, 0x64, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x79, 0x5f, 0x64, 0x61, - 0x74, 0x61, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x52, 0x12, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x41, 0x64, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x79, - 0x44, 0x61, 0x74, 0x61, 0x12, 0x40, 0x0a, 0x10, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x76, - 0x75, 0x6c, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x56, 0x75, - 0x6c, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, - 0x5f, 0x69, 0x64, 0x73, 0x18, 0x13, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x76, 0x65, 0x6e, 0x64, - 0x6f, 0x72, 0x49, 0x64, 0x73, 0x12, 0x39, 0x0a, 0x0b, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x74, 0x72, 0x69, - 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x52, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x12, 0x58, 0x0a, 0x0f, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x5f, 0x73, 0x65, 0x76, 0x65, 0x72, - 0x69, 0x74, 0x79, 0x18, 0x15, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x74, 0x72, 0x69, 0x76, - 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, - 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x2e, 0x56, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x53, 0x65, 0x76, - 0x65, 0x72, 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x76, 0x65, 0x6e, 0x64, - 0x6f, 0x72, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x6b, - 0x67, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x16, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x6b, - 0x67, 0x50, 0x61, 0x74, 0x68, 0x12, 0x15, 0x0a, 0x06, 0x70, 0x6b, 0x67, 0x5f, 0x69, 0x64, 0x18, - 0x17, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x6b, 0x67, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x18, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x1a, 0x4b, 0x0a, 0x09, 0x43, 0x76, 0x73, 0x73, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x6b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, - 0x6e, 0x2e, 0x43, 0x56, 0x53, 0x53, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x1a, 0x59, 0x0a, 0x13, 0x56, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x53, 0x65, 0x76, 0x65, 0x72, - 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x74, 0x72, 0x69, 0x76, - 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, - 0x79, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x42, 0x0a, 0x0a, - 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, - 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, - 0x22, 0x57, 0x0a, 0x05, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x67, - 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, - 0x74, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x69, 0x66, 0x66, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x64, 0x69, 0x66, 0x66, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x22, 0x87, 0x02, 0x0a, 0x0d, 0x43, 0x61, - 0x75, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1a, 0x0a, 0x08, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x64, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x1d, 0x0a, - 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x19, 0x0a, 0x08, - 0x65, 0x6e, 0x64, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, - 0x65, 0x6e, 0x64, 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x26, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, - 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, - 0x42, 0x0a, 0x0e, 0x72, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x63, 0x61, 0x75, 0x73, - 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, - 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x65, 0x64, 0x43, - 0x61, 0x75, 0x73, 0x65, 0x52, 0x0d, 0x72, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x65, 0x64, 0x43, 0x61, - 0x75, 0x73, 0x65, 0x22, 0xb2, 0x01, 0x0a, 0x04, 0x43, 0x56, 0x53, 0x53, 0x12, 0x1b, 0x0a, 0x09, - 0x76, 0x32, 0x5f, 0x76, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x76, 0x32, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x76, 0x33, 0x5f, - 0x76, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x76, 0x33, - 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x76, 0x32, 0x5f, 0x73, 0x63, 0x6f, - 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x07, 0x76, 0x32, 0x53, 0x63, 0x6f, 0x72, - 0x65, 0x12, 0x19, 0x0a, 0x08, 0x76, 0x33, 0x5f, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x01, 0x52, 0x07, 0x76, 0x33, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x1d, 0x0a, 0x0a, - 0x76, 0x34, 0x30, 0x5f, 0x76, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x76, 0x34, 0x30, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x76, - 0x34, 0x30, 0x5f, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, - 0x76, 0x34, 0x30, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x22, 0x98, 0x01, 0x0a, 0x0e, 0x43, 0x75, 0x73, - 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, - 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x29, 0x0a, 0x05, - 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, - 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, - 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x12, 0x2a, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x04, 0x64, - 0x61, 0x74, 0x61, 0x22, 0xf3, 0x01, 0x0a, 0x04, 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x16, 0x0a, 0x06, - 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x6e, 0x75, - 0x6d, 0x62, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x19, - 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x63, 0x61, 0x75, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x07, 0x69, 0x73, 0x43, 0x61, 0x75, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x6e, 0x6e, - 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, - 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x72, 0x75, - 0x6e, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x74, 0x72, - 0x75, 0x6e, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x68, 0x69, 0x67, 0x68, 0x6c, - 0x69, 0x67, 0x68, 0x74, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x68, 0x69, - 0x67, 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x65, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x69, 0x72, - 0x73, 0x74, 0x5f, 0x63, 0x61, 0x75, 0x73, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, - 0x66, 0x69, 0x72, 0x73, 0x74, 0x43, 0x61, 0x75, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x61, - 0x73, 0x74, 0x5f, 0x63, 0x61, 0x75, 0x73, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, - 0x6c, 0x61, 0x73, 0x74, 0x43, 0x61, 0x75, 0x73, 0x65, 0x22, 0x30, 0x0a, 0x04, 0x43, 0x6f, 0x64, - 0x65, 0x12, 0x28, 0x0a, 0x05, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x12, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, - 0x4c, 0x69, 0x6e, 0x65, 0x52, 0x05, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x22, 0x43, 0x0a, 0x0d, 0x52, - 0x65, 0x6e, 0x64, 0x65, 0x72, 0x65, 0x64, 0x43, 0x61, 0x75, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, - 0x72, 0x61, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x61, 0x77, 0x12, 0x20, - 0x0a, 0x0b, 0x68, 0x69, 0x67, 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0b, 0x68, 0x69, 0x67, 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x65, 0x64, - 0x22, 0x9f, 0x02, 0x0a, 0x0d, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x46, 0x69, 0x6e, 0x64, 0x69, - 0x6e, 0x67, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x75, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x63, - 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, - 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, - 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, - 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, - 0x72, 0x74, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, - 0x74, 0x61, 0x72, 0x74, 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, - 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x4c, - 0x69, 0x6e, 0x65, 0x12, 0x26, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, - 0x2e, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6d, - 0x61, 0x74, 0x63, 0x68, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x61, 0x74, 0x63, - 0x68, 0x12, 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x13, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, - 0x4c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4a, 0x04, 0x08, 0x09, - 0x10, 0x0a, 0x22, 0x5d, 0x0a, 0x06, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1a, 0x0a, 0x08, - 0x66, 0x69, 0x6c, 0x65, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x66, 0x69, 0x6c, 0x65, 0x70, 0x61, 0x74, 0x68, 0x12, 0x37, 0x0a, 0x08, 0x66, 0x69, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, - 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, - 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x08, 0x66, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x73, 0x22, 0x99, 0x02, 0x0a, 0x0f, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4c, 0x69, - 0x63, 0x65, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, - 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x52, - 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x3e, 0x0a, 0x08, 0x63, 0x61, 0x74, - 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x74, 0x72, + 0x2a, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xf3, 0x01, 0x0a, 0x04, + 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x63, 0x61, 0x75, + 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x43, 0x61, 0x75, 0x73, + 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x72, 0x75, 0x6e, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x74, 0x72, 0x75, 0x6e, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, + 0x20, 0x0a, 0x0b, 0x68, 0x69, 0x67, 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x65, 0x64, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x68, 0x69, 0x67, 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x65, + 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x75, 0x73, 0x65, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x66, 0x69, 0x72, 0x73, 0x74, 0x43, 0x61, 0x75, + 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x75, 0x73, 0x65, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x43, 0x61, 0x75, 0x73, + 0x65, 0x22, 0x30, 0x0a, 0x04, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x05, 0x6c, 0x69, 0x6e, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, + 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x6e, 0x65, 0x52, 0x05, 0x6c, 0x69, + 0x6e, 0x65, 0x73, 0x22, 0x43, 0x0a, 0x0d, 0x52, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x65, 0x64, 0x43, + 0x61, 0x75, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x61, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x72, 0x61, 0x77, 0x12, 0x20, 0x0a, 0x0b, 0x68, 0x69, 0x67, 0x68, 0x6c, 0x69, + 0x67, 0x68, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x68, 0x69, 0x67, + 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x65, 0x64, 0x22, 0x9f, 0x02, 0x0a, 0x0d, 0x53, 0x65, 0x63, + 0x72, 0x65, 0x74, 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x75, + 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x75, 0x6c, + 0x65, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, + 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x74, + 0x69, 0x74, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, + 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, 0x69, 0x6e, 0x65, + 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x26, 0x0a, 0x04, 0x63, + 0x6f, 0x64, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x72, 0x69, 0x76, + 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, + 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, + 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, + 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x6c, + 0x61, 0x79, 0x65, 0x72, 0x4a, 0x04, 0x08, 0x09, 0x10, 0x0a, 0x22, 0x5d, 0x0a, 0x06, 0x53, 0x65, + 0x63, 0x72, 0x65, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x70, 0x61, 0x74, 0x68, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x70, 0x61, 0x74, 0x68, + 0x12, 0x37, 0x0a, 0x08, 0x66, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, + 0x08, 0x66, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x99, 0x02, 0x0a, 0x0f, 0x44, 0x65, + 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, + 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x16, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, + 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x52, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, + 0x79, 0x12, 0x3e, 0x0a, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, + 0x72, 0x79, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, + 0x79, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x6b, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x6b, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, + 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, + 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x02, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, + 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, + 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x74, 0x65, 0x78, 0x74, 0x22, 0xed, 0x01, 0x0a, 0x0b, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, + 0x65, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x41, 0x0a, 0x0c, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e, - 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x52, - 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x6b, 0x67, - 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x6b, 0x67, - 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, - 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, - 0x68, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x64, 0x65, - 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x64, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, - 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 0x22, 0xed, 0x01, - 0x0a, 0x0b, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x41, 0x0a, - 0x0c, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, - 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x2e, 0x45, - 0x6e, 0x75, 0x6d, 0x52, 0x0b, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x19, 0x0a, - 0x08, 0x70, 0x6b, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x70, 0x6b, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x38, 0x0a, 0x08, 0x66, 0x69, 0x6e, 0x67, - 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x72, 0x69, + 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x0b, 0x6c, 0x69, 0x63, + 0x65, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, + 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, + 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x6b, 0x67, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x6b, 0x67, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x38, 0x0a, 0x08, 0x66, 0x69, 0x6e, 0x67, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x52, 0x08, 0x66, 0x69, 0x6e, 0x67, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x29, 0x0a, 0x05, 0x6c, 0x61, + 0x79, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, 0x69, 0x76, + 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, + 0x6c, 0x61, 0x79, 0x65, 0x72, 0x22, 0x98, 0x01, 0x0a, 0x0e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, + 0x65, 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x3e, 0x0a, 0x08, 0x63, 0x61, 0x74, 0x65, + 0x67, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, - 0x65, 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x08, 0x66, 0x69, 0x6e, 0x67, 0x69, 0x6e, - 0x67, 0x73, 0x12, 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, - 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x22, 0x98, 0x01, - 0x0a, 0x0e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x12, 0x3e, 0x0a, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, - 0x6e, 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, - 0x79, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, - 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x64, 0x65, 0x6e, - 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x64, - 0x65, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x22, 0x95, 0x01, 0x0a, 0x0f, 0x4c, 0x69, 0x63, - 0x65, 0x6e, 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x22, 0x81, 0x01, 0x0a, - 0x04, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, - 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x4f, 0x52, 0x42, 0x49, 0x44, - 0x44, 0x45, 0x4e, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x52, 0x45, 0x53, 0x54, 0x52, 0x49, 0x43, - 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x52, 0x45, 0x43, 0x49, 0x50, 0x52, 0x4f, - 0x43, 0x41, 0x4c, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x4e, 0x4f, 0x54, 0x49, 0x43, 0x45, 0x10, - 0x04, 0x12, 0x0e, 0x0a, 0x0a, 0x50, 0x45, 0x52, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x56, 0x45, 0x10, - 0x05, 0x12, 0x10, 0x0a, 0x0c, 0x55, 0x4e, 0x45, 0x4e, 0x43, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x45, - 0x44, 0x10, 0x06, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x07, - 0x22, 0x4e, 0x0a, 0x0b, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, - 0x3f, 0x0a, 0x04, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45, - 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x50, 0x4b, 0x47, - 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x10, 0x02, 0x12, 0x10, - 0x0a, 0x0c, 0x4c, 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x03, - 0x2a, 0x44, 0x0a, 0x08, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x0b, 0x0a, 0x07, - 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4c, 0x4f, 0x57, - 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x4d, 0x45, 0x44, 0x49, 0x55, 0x4d, 0x10, 0x02, 0x12, 0x08, - 0x0a, 0x04, 0x48, 0x49, 0x47, 0x48, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x52, 0x49, 0x54, - 0x49, 0x43, 0x41, 0x4c, 0x10, 0x04, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x71, 0x75, 0x61, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, - 0x79, 0x2f, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, - 0x6f, 0x6e, 0x3b, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x08, + 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, + 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, + 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, + 0x22, 0x95, 0x01, 0x0a, 0x0f, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, + 0x67, 0x6f, 0x72, 0x79, 0x22, 0x81, 0x01, 0x0a, 0x04, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x0f, 0x0a, + 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0d, + 0x0a, 0x09, 0x46, 0x4f, 0x52, 0x42, 0x49, 0x44, 0x44, 0x45, 0x4e, 0x10, 0x01, 0x12, 0x0e, 0x0a, + 0x0a, 0x52, 0x45, 0x53, 0x54, 0x52, 0x49, 0x43, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0e, 0x0a, + 0x0a, 0x52, 0x45, 0x43, 0x49, 0x50, 0x52, 0x4f, 0x43, 0x41, 0x4c, 0x10, 0x03, 0x12, 0x0a, 0x0a, + 0x06, 0x4e, 0x4f, 0x54, 0x49, 0x43, 0x45, 0x10, 0x04, 0x12, 0x0e, 0x0a, 0x0a, 0x50, 0x45, 0x52, + 0x4d, 0x49, 0x53, 0x53, 0x49, 0x56, 0x45, 0x10, 0x05, 0x12, 0x10, 0x0a, 0x0c, 0x55, 0x4e, 0x45, + 0x4e, 0x43, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x45, 0x44, 0x10, 0x06, 0x12, 0x0b, 0x0a, 0x07, 0x55, + 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x07, 0x22, 0x4e, 0x0a, 0x0b, 0x4c, 0x69, 0x63, 0x65, + 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, 0x3f, 0x0a, 0x04, 0x45, 0x6e, 0x75, 0x6d, 0x12, + 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, + 0x12, 0x08, 0x0a, 0x04, 0x44, 0x50, 0x4b, 0x47, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x48, 0x45, + 0x41, 0x44, 0x45, 0x52, 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c, 0x4c, 0x49, 0x43, 0x45, 0x4e, 0x53, + 0x45, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x03, 0x2a, 0x44, 0x0a, 0x08, 0x53, 0x65, 0x76, 0x65, + 0x72, 0x69, 0x74, 0x79, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, + 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4c, 0x4f, 0x57, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x4d, 0x45, + 0x44, 0x49, 0x55, 0x4d, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x49, 0x47, 0x48, 0x10, 0x03, + 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x52, 0x49, 0x54, 0x49, 0x43, 0x41, 0x4c, 0x10, 0x04, 0x42, 0x31, + 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x71, 0x75, + 0x61, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2f, + 0x72, 0x70, 0x63, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x3b, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/rpc/common/service.proto b/rpc/common/service.proto index b5798d3cb68f..f31b698de88a 100644 --- a/rpc/common/service.proto +++ b/rpc/common/service.proto @@ -42,19 +42,20 @@ message Package { string arch = 5; // src package containing some binary packages // e.g. bind - string src_name = 6; - string src_version = 7; - string src_release = 8; - int32 src_epoch = 9; - repeated string licenses = 15; - repeated Location locations = 20; - Layer layer = 11; - string file_path = 12; - repeated string depends_on = 14; - string digest = 16; - bool dev = 17; - bool indirect = 18; - string maintainer = 21; + string src_name = 6; + string src_version = 7; + string src_release = 8; + int32 src_epoch = 9; + repeated string licenses = 15; + repeated Location locations = 20; + Layer layer = 11; + string file_path = 12; + repeated string depends_on = 14; + string digest = 16; + bool dev = 17; + bool indirect = 18; + string maintainer = 21; + int32 relationship = 22; } message PkgIdentifier { @@ -153,6 +154,7 @@ message Layer { string digest = 1; string diff_id = 2; string created_by = 3; + int64 size = 4; } message CauseMetadata { diff --git a/rpc/scanner/service.pb.go b/rpc/scanner/service.pb.go index 89464829ec7b..61b1931d7523 100644 --- a/rpc/scanner/service.pb.go +++ b/rpc/scanner/service.pb.go @@ -249,8 +249,9 @@ type ScanResponse struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Os *common.OS `protobuf:"bytes,1,opt,name=os,proto3" json:"os,omitempty"` - Results []*Result `protobuf:"bytes,3,rep,name=results,proto3" json:"results,omitempty"` + Os *common.OS `protobuf:"bytes,1,opt,name=os,proto3" json:"os,omitempty"` + Results []*Result `protobuf:"bytes,3,rep,name=results,proto3" json:"results,omitempty"` + Layers []*common.Layer `protobuf:"bytes,4,rep,name=layers,proto3" json:"layers,omitempty"` } func (x *ScanResponse) Reset() { @@ -299,6 +300,13 @@ func (x *ScanResponse) GetResults() []*Result { return nil } +func (x *ScanResponse) GetLayers() []*common.Layer { + if x != nil { + return x.Layers + } + return nil +} + // Result is the same as github.com/aquasecurity/trivy/pkg/report.Result type Result struct { state protoimpl.MessageState @@ -461,52 +469,54 @@ var file_rpc_scanner_service_proto_rawDesc = []byte{ 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x4a, 0x04, - 0x08, 0x03, 0x10, 0x04, 0x22, 0x64, 0x0a, 0x0c, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x02, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x10, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, - 0x4f, 0x53, 0x52, 0x02, 0x6f, 0x73, 0x12, 0x32, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, - 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, - 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0xd5, 0x03, 0x0a, 0x06, 0x52, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x45, 0x0a, - 0x0f, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, - 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, - 0x69, 0x74, 0x79, 0x52, 0x0f, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, - 0x74, 0x69, 0x65, 0x73, 0x12, 0x54, 0x0a, 0x11, 0x6d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x26, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, - 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x11, 0x6d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6c, - 0x61, 0x73, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6c, 0x61, 0x73, 0x73, - 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x74, 0x79, 0x70, 0x65, 0x12, 0x31, 0x0a, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, - 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, - 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x52, 0x08, 0x70, - 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x12, 0x47, 0x0a, 0x10, 0x63, 0x75, 0x73, 0x74, 0x6f, - 0x6d, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, - 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, - 0x0f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, - 0x12, 0x35, 0x0a, 0x07, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, - 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x07, - 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x39, 0x0a, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, - 0x73, 0x65, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x74, 0x72, 0x69, 0x76, - 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, - 0x64, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x52, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, - 0x65, 0x73, 0x32, 0x50, 0x0a, 0x07, 0x53, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x45, 0x0a, - 0x04, 0x53, 0x63, 0x61, 0x6e, 0x12, 0x1d, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, 0x63, - 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, 0x63, 0x61, - 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x61, 0x71, 0x75, 0x61, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, - 0x74, 0x72, 0x69, 0x76, 0x79, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, - 0x72, 0x3b, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x08, 0x03, 0x10, 0x04, 0x22, 0x91, 0x01, 0x0a, 0x0c, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x02, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x2e, 0x4f, 0x53, 0x52, 0x02, 0x6f, 0x73, 0x12, 0x32, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, + 0x2e, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x2b, 0x0a, 0x06, 0x6c, + 0x61, 0x79, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, + 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, + 0x52, 0x06, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x22, 0xd5, 0x03, 0x0a, 0x06, 0x52, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x45, 0x0a, 0x0f, 0x76, + 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, + 0x6d, 0x6f, 0x6e, 0x2e, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, + 0x79, 0x52, 0x0f, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, + 0x65, 0x73, 0x12, 0x54, 0x0a, 0x11, 0x6d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, + 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x74, + 0x65, 0x63, 0x74, 0x65, 0x64, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x11, 0x6d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6c, 0x61, 0x73, + 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x12, 0x12, + 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x12, 0x31, 0x0a, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x18, 0x05, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, + 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x52, 0x08, 0x70, 0x61, 0x63, + 0x6b, 0x61, 0x67, 0x65, 0x73, 0x12, 0x47, 0x0a, 0x10, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x1c, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0f, 0x63, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x35, + 0x0a, 0x07, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, + 0x65, 0x63, 0x72, 0x65, 0x74, 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x73, 0x65, + 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x39, 0x0a, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, + 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4c, + 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x52, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, + 0x32, 0x50, 0x0a, 0x07, 0x53, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x45, 0x0a, 0x04, 0x53, + 0x63, 0x61, 0x6e, 0x12, 0x1d, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, 0x63, 0x61, 0x6e, + 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, 0x63, 0x61, 0x6e, 0x6e, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x61, 0x71, 0x75, 0x61, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, 0x74, 0x72, + 0x69, 0x76, 0x79, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x3b, + 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -530,12 +540,13 @@ var file_rpc_scanner_service_proto_goTypes = []interface{}{ (*Result)(nil), // 4: trivy.scanner.v1.Result nil, // 5: trivy.scanner.v1.ScanOptions.LicenseCategoriesEntry (*common.OS)(nil), // 6: trivy.common.OS - (*common.Vulnerability)(nil), // 7: trivy.common.Vulnerability - (*common.DetectedMisconfiguration)(nil), // 8: trivy.common.DetectedMisconfiguration - (*common.Package)(nil), // 9: trivy.common.Package - (*common.CustomResource)(nil), // 10: trivy.common.CustomResource - (*common.SecretFinding)(nil), // 11: trivy.common.SecretFinding - (*common.DetectedLicense)(nil), // 12: trivy.common.DetectedLicense + (*common.Layer)(nil), // 7: trivy.common.Layer + (*common.Vulnerability)(nil), // 8: trivy.common.Vulnerability + (*common.DetectedMisconfiguration)(nil), // 9: trivy.common.DetectedMisconfiguration + (*common.Package)(nil), // 10: trivy.common.Package + (*common.CustomResource)(nil), // 11: trivy.common.CustomResource + (*common.SecretFinding)(nil), // 12: trivy.common.SecretFinding + (*common.DetectedLicense)(nil), // 13: trivy.common.DetectedLicense } var file_rpc_scanner_service_proto_depIdxs = []int32{ 2, // 0: trivy.scanner.v1.ScanRequest.options:type_name -> trivy.scanner.v1.ScanOptions @@ -543,20 +554,21 @@ var file_rpc_scanner_service_proto_depIdxs = []int32{ 6, // 2: trivy.scanner.v1.ScanOptions.distro:type_name -> trivy.common.OS 6, // 3: trivy.scanner.v1.ScanResponse.os:type_name -> trivy.common.OS 4, // 4: trivy.scanner.v1.ScanResponse.results:type_name -> trivy.scanner.v1.Result - 7, // 5: trivy.scanner.v1.Result.vulnerabilities:type_name -> trivy.common.Vulnerability - 8, // 6: trivy.scanner.v1.Result.misconfigurations:type_name -> trivy.common.DetectedMisconfiguration - 9, // 7: trivy.scanner.v1.Result.packages:type_name -> trivy.common.Package - 10, // 8: trivy.scanner.v1.Result.custom_resources:type_name -> trivy.common.CustomResource - 11, // 9: trivy.scanner.v1.Result.secrets:type_name -> trivy.common.SecretFinding - 12, // 10: trivy.scanner.v1.Result.licenses:type_name -> trivy.common.DetectedLicense - 1, // 11: trivy.scanner.v1.ScanOptions.LicenseCategoriesEntry.value:type_name -> trivy.scanner.v1.Licenses - 0, // 12: trivy.scanner.v1.Scanner.Scan:input_type -> trivy.scanner.v1.ScanRequest - 3, // 13: trivy.scanner.v1.Scanner.Scan:output_type -> trivy.scanner.v1.ScanResponse - 13, // [13:14] is the sub-list for method output_type - 12, // [12:13] is the sub-list for method input_type - 12, // [12:12] is the sub-list for extension type_name - 12, // [12:12] is the sub-list for extension extendee - 0, // [0:12] is the sub-list for field type_name + 7, // 5: trivy.scanner.v1.ScanResponse.layers:type_name -> trivy.common.Layer + 8, // 6: trivy.scanner.v1.Result.vulnerabilities:type_name -> trivy.common.Vulnerability + 9, // 7: trivy.scanner.v1.Result.misconfigurations:type_name -> trivy.common.DetectedMisconfiguration + 10, // 8: trivy.scanner.v1.Result.packages:type_name -> trivy.common.Package + 11, // 9: trivy.scanner.v1.Result.custom_resources:type_name -> trivy.common.CustomResource + 12, // 10: trivy.scanner.v1.Result.secrets:type_name -> trivy.common.SecretFinding + 13, // 11: trivy.scanner.v1.Result.licenses:type_name -> trivy.common.DetectedLicense + 1, // 12: trivy.scanner.v1.ScanOptions.LicenseCategoriesEntry.value:type_name -> trivy.scanner.v1.Licenses + 0, // 13: trivy.scanner.v1.Scanner.Scan:input_type -> trivy.scanner.v1.ScanRequest + 3, // 14: trivy.scanner.v1.Scanner.Scan:output_type -> trivy.scanner.v1.ScanResponse + 14, // [14:15] is the sub-list for method output_type + 13, // [13:14] is the sub-list for method input_type + 13, // [13:13] is the sub-list for extension type_name + 13, // [13:13] is the sub-list for extension extendee + 0, // [0:13] is the sub-list for field type_name } func init() { file_rpc_scanner_service_proto_init() } diff --git a/rpc/scanner/service.proto b/rpc/scanner/service.proto index 49dfe497cd46..0acbf5d9dc5a 100644 --- a/rpc/scanner/service.proto +++ b/rpc/scanner/service.proto @@ -36,8 +36,9 @@ message ScanOptions { } message ScanResponse { - common.OS os = 1; - repeated Result results = 3; + common.OS os = 1; + repeated Result results = 3; + repeated common.Layer layers = 4; } // Result is the same as github.com/aquasecurity/trivy/pkg/report.Result diff --git a/rpc/scanner/service.twirp.go b/rpc/scanner/service.twirp.go index d49372d0dd88..b4dc64aa2b5a 100644 --- a/rpc/scanner/service.twirp.go +++ b/rpc/scanner/service.twirp.go @@ -1094,51 +1094,52 @@ func callClientError(ctx context.Context, h *twirp.ClientHooks, err twirp.Error) } var twirpFileDescriptor0 = []byte{ - // 729 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x54, 0x5f, 0x6f, 0xe3, 0x44, - 0x10, 0x57, 0xe2, 0x34, 0x71, 0x26, 0x27, 0x2e, 0x5d, 0xb8, 0x93, 0x2f, 0xc7, 0x41, 0xc8, 0x03, - 0x8a, 0x84, 0x94, 0xd0, 0x1c, 0x88, 0x7f, 0x6f, 0x5c, 0x5b, 0x54, 0x04, 0x6a, 0xb5, 0xa9, 0x78, - 0xe0, 0xc5, 0x6c, 0xd6, 0x53, 0x77, 0x15, 0xc7, 0x76, 0x77, 0xd7, 0x96, 0xf2, 0x55, 0xf8, 0x5e, - 0x7c, 0x0a, 0xbe, 0x04, 0xda, 0x3f, 0x8e, 0x9a, 0xa4, 0xe5, 0xc9, 0x3b, 0x33, 0xbf, 0x99, 0xf9, - 0x79, 0xf6, 0x37, 0x0b, 0x6f, 0x64, 0xc9, 0xe7, 0x8a, 0xb3, 0x3c, 0x47, 0x39, 0x57, 0x28, 0x6b, - 0xc1, 0x71, 0x56, 0xca, 0x42, 0x17, 0x64, 0xa8, 0xa5, 0xa8, 0xb7, 0x33, 0x1f, 0x9c, 0xd5, 0x67, - 0xa3, 0xc8, 0x80, 0x79, 0xb1, 0xd9, 0x14, 0xf9, 0x3e, 0x76, 0xf2, 0x77, 0x0b, 0x06, 0x4b, 0xce, - 0x72, 0x8a, 0x0f, 0x15, 0x2a, 0x4d, 0x5e, 0x43, 0x57, 0x33, 0x99, 0xa2, 0x8e, 0x5a, 0xe3, 0xd6, - 0xb4, 0x4f, 0xbd, 0x45, 0x3e, 0x87, 0x01, 0x93, 0x5a, 0xdc, 0x31, 0xae, 0x63, 0x91, 0x44, 0x6d, - 0x1b, 0x84, 0xc6, 0x75, 0x95, 0x90, 0x37, 0x10, 0xae, 0xb2, 0x62, 0x15, 0x8b, 0x44, 0x45, 0xc1, - 0x38, 0x98, 0xf6, 0x69, 0xcf, 0xd8, 0x57, 0x89, 0x22, 0xdf, 0x41, 0xaf, 0x28, 0xb5, 0x28, 0x72, - 0x15, 0x75, 0xc6, 0xad, 0xe9, 0x60, 0xf1, 0x6e, 0x76, 0xc8, 0x70, 0x66, 0x38, 0x5c, 0x3b, 0x10, - 0x6d, 0xd0, 0x93, 0x31, 0x84, 0xbf, 0x09, 0x8e, 0xb9, 0x42, 0x45, 0x3e, 0x81, 0x93, 0x9c, 0x6d, - 0x50, 0x45, 0x2d, 0x5b, 0xdc, 0x19, 0x93, 0x7f, 0x03, 0x47, 0xdf, 0xa7, 0x92, 0xb7, 0xd0, 0x2f, - 0xd7, 0x69, 0xac, 0xb7, 0xe5, 0x0e, 0x19, 0x96, 0xeb, 0xf4, 0xd6, 0xd8, 0x64, 0x04, 0xa1, 0xef, - 0xa8, 0xa2, 0xb6, 0x8b, 0x35, 0x36, 0xe1, 0x40, 0x32, 0xd7, 0x2a, 0xe6, 0x4c, 0x63, 0x5a, 0x48, - 0x81, 0x86, 0x6e, 0x30, 0x1d, 0x2c, 0xbe, 0xf9, 0x5f, 0xba, 0x33, 0x4f, 0xf1, 0xc3, 0x2e, 0xed, - 0x22, 0xd7, 0x72, 0x4b, 0x4f, 0xb3, 0x43, 0x3f, 0x99, 0xc2, 0x50, 0xe4, 0x3c, 0xab, 0x12, 0x8c, - 0x13, 0xac, 0xe3, 0x04, 0x4b, 0x15, 0x9d, 0x8c, 0x5b, 0xd3, 0x90, 0x7e, 0xe4, 0xfd, 0xe7, 0x58, - 0x9f, 0x63, 0xa9, 0xc8, 0x57, 0x70, 0x6a, 0xfe, 0x43, 0x62, 0xc6, 0x6c, 0x93, 0x7b, 0x51, 0xaa, - 0xa8, 0x6b, 0x39, 0x0f, 0xcb, 0x75, 0x4a, 0x1f, 0xfb, 0xc9, 0x14, 0xba, 0x89, 0x50, 0x5a, 0x16, - 0x51, 0xcf, 0x8e, 0x77, 0xe8, 0xf9, 0xba, 0x0b, 0x9f, 0x5d, 0x2f, 0xa9, 0x8f, 0x93, 0x05, 0xbc, - 0xaa, 0xab, 0x2c, 0x8f, 0x15, 0xd6, 0x28, 0x85, 0xde, 0xc6, 0xaa, 0xa8, 0x24, 0x47, 0x15, 0x85, - 0xb6, 0xf4, 0xc7, 0x26, 0xb8, 0xf4, 0xb1, 0xa5, 0x0b, 0x91, 0x2f, 0xe0, 0x45, 0x33, 0x99, 0xbb, - 0x2a, 0xcb, 0xa2, 0xbe, 0x25, 0x3c, 0xf0, 0xbe, 0xcb, 0x2a, 0xcb, 0x46, 0x7f, 0xc1, 0xeb, 0xa7, - 0x87, 0x40, 0x86, 0x10, 0xac, 0x71, 0xeb, 0xb5, 0x64, 0x8e, 0xe4, 0x6b, 0x38, 0xa9, 0x59, 0x56, - 0xa1, 0x95, 0xd0, 0x60, 0x31, 0x3a, 0x9e, 0x6d, 0x73, 0xe5, 0xd4, 0x01, 0x7f, 0x6c, 0x7f, 0xdf, - 0xfa, 0xb5, 0x13, 0x06, 0xc3, 0xce, 0x24, 0x81, 0x17, 0x4e, 0xab, 0xaa, 0x2c, 0x72, 0x85, 0x64, - 0x0c, 0xed, 0x42, 0xd9, 0xe2, 0x4f, 0xfd, 0x74, 0xbb, 0x50, 0x64, 0x01, 0x3d, 0x89, 0xaa, 0xca, - 0xb4, 0x13, 0xe5, 0x60, 0x11, 0x1d, 0xf7, 0xa3, 0x16, 0x40, 0x1b, 0xe0, 0xe4, 0x9f, 0x00, 0xba, - 0xce, 0xf7, 0xec, 0x36, 0x5c, 0xc0, 0x4b, 0x33, 0x2a, 0x94, 0x6c, 0x25, 0x32, 0xa1, 0x8d, 0x54, - 0xda, 0xb6, 0xfc, 0xdb, 0x7d, 0x16, 0x7f, 0x3c, 0x02, 0x6d, 0xe9, 0x61, 0x0e, 0xb9, 0x85, 0xd3, - 0x8d, 0x50, 0xbc, 0xc8, 0xef, 0x44, 0x5a, 0x49, 0xd6, 0xac, 0x88, 0x29, 0xf4, 0xe5, 0x7e, 0xa1, - 0x73, 0xd4, 0xc8, 0x35, 0x26, 0xbf, 0x1f, 0xc0, 0xe9, 0x71, 0x01, 0xb3, 0x29, 0x3c, 0x63, 0xca, - 0xe8, 0xc5, 0x70, 0x76, 0x06, 0x21, 0xd0, 0x31, 0x5b, 0x11, 0x05, 0xd6, 0x69, 0xcf, 0xe4, 0x0c, - 0xc2, 0x92, 0xf1, 0x35, 0x4b, 0xd1, 0xe8, 0xd0, 0xb4, 0x7d, 0xb5, 0xdf, 0xf6, 0xc6, 0x45, 0xe9, - 0x0e, 0x46, 0x7e, 0x81, 0x21, 0xaf, 0x94, 0x2e, 0x36, 0xb1, 0xc4, 0x46, 0x3c, 0x3d, 0x9b, 0xfa, - 0xe9, 0x7e, 0xea, 0x07, 0x8b, 0xa2, 0x1e, 0x44, 0x5f, 0xf2, 0x3d, 0x5b, 0x91, 0x6f, 0xa1, 0xa7, - 0x90, 0x4b, 0xd4, 0x4e, 0x7c, 0x47, 0xa3, 0x5b, 0xda, 0xe0, 0xa5, 0xc8, 0x13, 0x91, 0xa7, 0xb4, - 0xc1, 0x92, 0x1f, 0x20, 0xf4, 0xca, 0x53, 0x51, 0xdf, 0xe6, 0xbd, 0x7b, 0x7a, 0x52, 0x5e, 0x45, - 0x74, 0x07, 0x5f, 0xdc, 0x40, 0x6f, 0xe9, 0x6e, 0x9d, 0x5c, 0x40, 0xc7, 0x1c, 0xc9, 0x33, 0x0f, - 0x91, 0x7f, 0x0c, 0x47, 0x9f, 0x3d, 0x17, 0x76, 0xfa, 0xfb, 0xf9, 0xfd, 0x9f, 0x67, 0xa9, 0xd0, - 0xf7, 0xd5, 0xca, 0x34, 0x9f, 0xb3, 0x87, 0x8a, 0x29, 0xe4, 0x95, 0x59, 0x9e, 0xb9, 0x4d, 0x9c, - 0x3f, 0x7a, 0xa3, 0x7f, 0xf2, 0xdf, 0x55, 0xd7, 0x3e, 0xbc, 0xef, 0xff, 0x0b, 0x00, 0x00, 0xff, - 0xff, 0xe0, 0xa9, 0x3c, 0x91, 0xc1, 0x05, 0x00, 0x00, + // 749 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x54, 0xcd, 0x8e, 0xdb, 0x36, + 0x10, 0x86, 0x2c, 0xaf, 0x2d, 0x8f, 0x83, 0xc6, 0xcb, 0x34, 0x81, 0xe2, 0x34, 0xad, 0xeb, 0x43, + 0x61, 0x20, 0x80, 0xdd, 0x75, 0x5a, 0xf4, 0xef, 0xd6, 0xec, 0xa6, 0x48, 0x91, 0x22, 0x01, 0x1d, + 0xf4, 0xd0, 0x8b, 0x4a, 0x53, 0xb3, 0x0a, 0x61, 0x59, 0xd2, 0x92, 0x94, 0x00, 0x3f, 0x46, 0xaf, + 0x7d, 0xaf, 0x3e, 0x45, 0x5f, 0xa2, 0xe0, 0x8f, 0x8c, 0xb5, 0xbd, 0xdb, 0x93, 0x38, 0xf3, 0x7d, + 0xf3, 0xa3, 0xe1, 0x37, 0x84, 0xa7, 0xb2, 0xe2, 0x0b, 0xc5, 0x59, 0x51, 0xa0, 0x5c, 0x28, 0x94, + 0x8d, 0xe0, 0x38, 0xaf, 0x64, 0xa9, 0x4b, 0x32, 0xd2, 0x52, 0x34, 0xbb, 0xb9, 0x07, 0xe7, 0xcd, + 0xc5, 0x38, 0x36, 0x64, 0x5e, 0x6e, 0xb7, 0x65, 0x71, 0xc8, 0x9d, 0xfe, 0x1d, 0xc0, 0x70, 0xc5, + 0x59, 0x41, 0xf1, 0xa6, 0x46, 0xa5, 0xc9, 0x13, 0xe8, 0x69, 0x26, 0x33, 0xd4, 0x71, 0x30, 0x09, + 0x66, 0x03, 0xea, 0x2d, 0xf2, 0x05, 0x0c, 0x99, 0xd4, 0xe2, 0x9a, 0x71, 0x9d, 0x88, 0x34, 0xee, + 0x58, 0x10, 0x5a, 0xd7, 0x9b, 0x94, 0x3c, 0x85, 0x68, 0x9d, 0x97, 0xeb, 0x44, 0xa4, 0x2a, 0x0e, + 0x27, 0xe1, 0x6c, 0x40, 0xfb, 0xc6, 0x7e, 0x93, 0x2a, 0xf2, 0x1d, 0xf4, 0xcb, 0x4a, 0x8b, 0xb2, + 0x50, 0x71, 0x77, 0x12, 0xcc, 0x86, 0xcb, 0xe7, 0xf3, 0xe3, 0x0e, 0xe7, 0xa6, 0x87, 0x77, 0x8e, + 0x44, 0x5b, 0xf6, 0x74, 0x02, 0xd1, 0x5b, 0xc1, 0xb1, 0x50, 0xa8, 0xc8, 0xa7, 0x70, 0x56, 0xb0, + 0x2d, 0xaa, 0x38, 0xb0, 0xc9, 0x9d, 0x31, 0xfd, 0x37, 0x74, 0xed, 0xfb, 0x50, 0xf2, 0x0c, 0x06, + 0xd5, 0x26, 0x4b, 0xf4, 0xae, 0xda, 0x33, 0xa3, 0x6a, 0x93, 0x7d, 0x30, 0x36, 0x19, 0x43, 0xe4, + 0x2b, 0xaa, 0xb8, 0xe3, 0xb0, 0xd6, 0x26, 0x1c, 0x48, 0xee, 0x4a, 0x25, 0x9c, 0x69, 0xcc, 0x4a, + 0x29, 0xd0, 0xb4, 0x1b, 0xce, 0x86, 0xcb, 0x6f, 0xfe, 0xb7, 0xdd, 0xb9, 0x6f, 0xf1, 0xd5, 0x3e, + 0xec, 0xaa, 0xd0, 0x72, 0x47, 0xcf, 0xf3, 0x63, 0x3f, 0x99, 0xc1, 0x48, 0x14, 0x3c, 0xaf, 0x53, + 0x4c, 0x52, 0x6c, 0x92, 0x14, 0x2b, 0x15, 0x9f, 0x4d, 0x82, 0x59, 0x44, 0x3f, 0xf1, 0xfe, 0x4b, + 0x6c, 0x2e, 0xb1, 0x52, 0xe4, 0x05, 0x9c, 0x9b, 0xff, 0x90, 0x98, 0x33, 0x5b, 0xe4, 0xa3, 0xa8, + 0x54, 0xdc, 0xb3, 0x3d, 0x8f, 0xaa, 0x4d, 0x46, 0x6f, 0xfb, 0xc9, 0x0c, 0x7a, 0xa9, 0x50, 0x5a, + 0x96, 0x71, 0xdf, 0x8e, 0x77, 0xe4, 0xfb, 0x75, 0x17, 0x3e, 0x7f, 0xb7, 0xa2, 0x1e, 0x27, 0x4b, + 0x78, 0xdc, 0xd4, 0x79, 0x91, 0x28, 0x6c, 0x50, 0x0a, 0xbd, 0x4b, 0x54, 0x59, 0x4b, 0x8e, 0x2a, + 0x8e, 0x6c, 0xea, 0x47, 0x06, 0x5c, 0x79, 0x6c, 0xe5, 0x20, 0xf2, 0x25, 0x3c, 0x68, 0x27, 0x73, + 0x5d, 0xe7, 0x79, 0x3c, 0xb0, 0x0d, 0x0f, 0xbd, 0xef, 0x75, 0x9d, 0xe7, 0xe3, 0x3f, 0xe1, 0xc9, + 0xdd, 0x43, 0x20, 0x23, 0x08, 0x37, 0xb8, 0xf3, 0x5a, 0x32, 0x47, 0xf2, 0x35, 0x9c, 0x35, 0x2c, + 0xaf, 0xd1, 0x4a, 0x68, 0xb8, 0x1c, 0x9f, 0xce, 0xb6, 0xbd, 0x72, 0xea, 0x88, 0x3f, 0x76, 0xbe, + 0x0f, 0x7e, 0xed, 0x46, 0xe1, 0xa8, 0x3b, 0xfd, 0x2b, 0x80, 0x07, 0x4e, 0xac, 0xaa, 0x2a, 0x0b, + 0x85, 0x64, 0x02, 0x9d, 0x52, 0xd9, 0xec, 0x77, 0xfd, 0x75, 0xa7, 0x54, 0x64, 0x09, 0x7d, 0x89, + 0xaa, 0xce, 0xb5, 0x53, 0xe5, 0x70, 0x19, 0x9f, 0x16, 0xa4, 0x96, 0x40, 0x5b, 0x22, 0x79, 0x01, + 0xbd, 0x9c, 0xed, 0x8c, 0x4a, 0xdc, 0xfd, 0x3f, 0x3a, 0xcc, 0xfc, 0xd6, 0x60, 0xd4, 0x53, 0xa6, + 0xff, 0x84, 0xd0, 0x73, 0x09, 0xee, 0xdd, 0x9d, 0x2b, 0x78, 0x68, 0x06, 0x8b, 0x92, 0xad, 0x45, + 0x2e, 0xb4, 0x11, 0x56, 0xc7, 0x26, 0x7e, 0x76, 0x98, 0xf8, 0xf7, 0x5b, 0xa4, 0x1d, 0x3d, 0x8e, + 0x21, 0x1f, 0xe0, 0x7c, 0x2b, 0x14, 0x2f, 0x8b, 0x6b, 0x91, 0xd5, 0x92, 0xb5, 0x0b, 0x65, 0x12, + 0x7d, 0x75, 0x98, 0xe8, 0x12, 0x35, 0x72, 0x8d, 0xe9, 0x6f, 0x47, 0x74, 0x7a, 0x9a, 0xc0, 0xec, + 0x15, 0xcf, 0x99, 0x32, 0xea, 0x32, 0x3d, 0x3b, 0x83, 0x10, 0xe8, 0x9a, 0x1d, 0x8a, 0x43, 0xeb, + 0xb4, 0x67, 0x72, 0x01, 0x51, 0xc5, 0xf8, 0x86, 0x65, 0x68, 0x54, 0x6b, 0xca, 0x3e, 0x3e, 0x2c, + 0xfb, 0xde, 0xa1, 0x74, 0x4f, 0x23, 0xbf, 0xc0, 0x88, 0xd7, 0x4a, 0x97, 0xdb, 0x44, 0x62, 0x2b, + 0xb5, 0xbe, 0x0d, 0xfd, 0xec, 0x30, 0xf4, 0x95, 0x65, 0x51, 0x4f, 0xa2, 0x0f, 0xf9, 0x81, 0xad, + 0xc8, 0xb7, 0xd0, 0x57, 0xc8, 0x25, 0x6a, 0x27, 0xd5, 0x93, 0xd1, 0xad, 0x2c, 0xf8, 0x5a, 0x14, + 0xa9, 0x28, 0x32, 0xda, 0x72, 0xc9, 0x0f, 0x10, 0x79, 0x9d, 0xaa, 0x78, 0x60, 0xe3, 0x9e, 0xdf, + 0x3d, 0x29, 0xaf, 0x39, 0xba, 0xa7, 0x2f, 0xdf, 0x43, 0x7f, 0xe5, 0x24, 0x42, 0xae, 0xa0, 0x6b, + 0x8e, 0xe4, 0x9e, 0x67, 0xcb, 0x3f, 0x9d, 0xe3, 0xcf, 0xef, 0x83, 0x9d, 0x58, 0x7f, 0x7e, 0xf9, + 0xc7, 0x45, 0x26, 0xf4, 0xc7, 0x7a, 0x6d, 0x8a, 0x2f, 0xd8, 0x4d, 0xcd, 0x14, 0xf2, 0xda, 0xac, + 0xda, 0xc2, 0x06, 0x2e, 0x6e, 0xbd, 0xe8, 0x3f, 0xf9, 0xef, 0xba, 0x67, 0x9f, 0xe9, 0x97, 0xff, + 0x05, 0x00, 0x00, 0xff, 0xff, 0x68, 0x51, 0xa0, 0x7d, 0xef, 0x05, 0x00, 0x00, }