diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 46f4b5c85c25..9866e518d80f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -15,8 +15,8 @@ pkg/cloud/ @simar7 @nikpivkin pkg/iac/ @simar7 @nikpivkin # Helm chart -helm/trivy/ @chen-keinan +helm/trivy/ @afdesk # Kubernetes scanning -pkg/k8s/ @chen-keinan -docs/docs/target/kubernetes.md @chen-keinan +pkg/k8s/ @afdesk +docs/docs/target/kubernetes.md @afdesk diff --git a/.github/workflows/auto-update-labels.yaml b/.github/workflows/auto-update-labels.yaml index 941e7b5db9df..0de6e41438b9 100644 --- a/.github/workflows/auto-update-labels.yaml +++ b/.github/workflows/auto-update-labels.yaml @@ -20,6 +20,7 @@ jobs: with: # cf. https://github.com/aquasecurity/trivy/pull/6711 go-version: ${{ env.GO_VERSION }} + cache: false - name: Install aqua tools uses: aquaproj/aqua-installer@v3.0.1 diff --git a/.github/workflows/cache-test-images.yaml b/.github/workflows/cache-test-images.yaml new file mode 100644 index 000000000000..a03dc683e3ae --- /dev/null +++ b/.github/workflows/cache-test-images.yaml @@ -0,0 +1,86 @@ +name: Cache test images +on: + schedule: + - cron: "0 0 * * *" # Run this workflow every day at 00:00 to avoid cache deletion. + workflow_dispatch: + +jobs: + test-images: + name: Cache test images + runs-on: ubuntu-latest + steps: + - name: Check out code into the Go module directory + uses: actions/checkout@v4.1.6 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: false + + - name: Install tools + uses: aquaproj/aqua-installer@v3.0.1 + with: + aqua_version: v1.25.0 + + - name: Generate image list digest + if: github.ref_name == 'main' + id: image-digest + run: | + IMAGE_LIST=$(skopeo list-tags docker://ghcr.io/aquasecurity/trivy-test-images) + DIGEST=$(echo "$IMAGE_LIST" | sha256sum | cut -d' ' -f1) + echo "digest=$DIGEST" >> $GITHUB_OUTPUT + + ## We need to work with test image cache only for main branch + - name: Restore and save test images cache + if: github.ref_name == 'main' + uses: actions/cache@v4 + with: + path: integration/testdata/fixtures/images + key: cache-test-images-${{ steps.image-digest.outputs.digest }} + restore-keys: + cache-test-images- + + - name: Download test images + if: github.ref_name == 'main' + run: mage test:fixtureContainerImages + + test-vm-images: + name: Cache test VM images + runs-on: ubuntu-latest + steps: + - name: Check out code into the Go module directory + uses: actions/checkout@v4.1.6 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: false + + - name: Install tools + uses: aquaproj/aqua-installer@v3.0.1 + with: + aqua_version: v1.25.0 + + - name: Generate image list digest + if: github.ref_name == 'main' + id: image-digest + run: | + IMAGE_LIST=$(skopeo list-tags docker://ghcr.io/aquasecurity/trivy-test-vm-images) + DIGEST=$(echo "$IMAGE_LIST" | sha256sum | cut -d' ' -f1) + echo "digest=$DIGEST" >> $GITHUB_OUTPUT + + ## We need to work with test VM image cache only for main branch + - name: Restore and save test VM images cache + if: github.ref_name == 'main' + uses: actions/cache@v4 + with: + path: integration/testdata/fixtures/vm-images + key: cache-test-vm-images-${{ steps.image-digest.outputs.digest }} + restore-keys: + cache-test-vm-images- + + - name: Download test VM images + if: github.ref_name == 'main' + run: mage test:fixtureVMImages \ No newline at end of file diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 13f279b519b7..74b0933c6ef6 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -8,6 +8,8 @@ on: - 'LICENSE' - '.release-please-manifest.json' ## don't run tests for release-please PRs merge_group: + workflow_dispatch: + env: GO_VERSION: '1.22' jobs: @@ -24,6 +26,8 @@ jobs: uses: actions/setup-go@v5 with: go-version: ${{ env.GO_VERSION }} + cache: false + - name: go mod tidy run: | go mod tidy @@ -76,12 +80,28 @@ jobs: uses: actions/setup-go@v5 with: go-version: ${{ env.GO_VERSION }} + cache: false - name: Install tools uses: aquaproj/aqua-installer@v3.0.1 with: aqua_version: v1.25.0 + - name: Generate image list digest + id: image-digest + run: | + IMAGE_LIST=$(skopeo list-tags docker://ghcr.io/aquasecurity/trivy-test-images) + DIGEST=$(echo "$IMAGE_LIST" | sha256sum | cut -d' ' -f1) + echo "digest=$DIGEST" >> $GITHUB_OUTPUT + + - name: Restore test images from cache + uses: actions/cache/restore@v4 + with: + path: integration/testdata/fixtures/images + key: cache-test-images-${{ steps.image-digest.outputs.digest }} + restore-keys: + cache-test-images- + - name: Run integration tests run: mage test:integration @@ -96,6 +116,7 @@ jobs: uses: actions/setup-go@v5 with: go-version: ${{ env.GO_VERSION }} + cache: false - name: Install tools uses: aquaproj/aqua-installer@v3.0.1 @@ -116,12 +137,28 @@ jobs: uses: actions/setup-go@v5 with: go-version: ${{ env.GO_VERSION }} + cache: false - name: Install tools uses: aquaproj/aqua-installer@v3.0.1 with: aqua_version: v1.25.0 + - name: Generate image list digest + id: image-digest + run: | + IMAGE_LIST=$(skopeo list-tags docker://ghcr.io/aquasecurity/trivy-test-images) + DIGEST=$(echo "$IMAGE_LIST" | sha256sum | cut -d' ' -f1) + echo "digest=$DIGEST" >> $GITHUB_OUTPUT + + - name: Restore test images from cache + uses: actions/cache/restore@v4 + with: + path: integration/testdata/fixtures/images + key: cache-test-images-${{ steps.image-digest.outputs.digest }} + restore-keys: + cache-test-images- + - name: Run module integration tests shell: bash run: | @@ -138,10 +175,28 @@ jobs: uses: actions/setup-go@v5 with: go-version: ${{ env.GO_VERSION }} + cache: false + - name: Install tools uses: aquaproj/aqua-installer@v3.0.1 with: aqua_version: v1.25.0 + + - name: Generate image list digest + id: image-digest + run: | + IMAGE_LIST=$(skopeo list-tags docker://ghcr.io/aquasecurity/trivy-test-vm-images) + DIGEST=$(echo "$IMAGE_LIST" | sha256sum | cut -d' ' -f1) + echo "digest=$DIGEST" >> $GITHUB_OUTPUT + + - name: Restore test VM images from cache + uses: actions/cache/restore@v4 + with: + path: integration/testdata/fixtures/vm-images + key: cache-test-vm-images-${{ steps.image-digest.outputs.digest }} + restore-keys: + cache-test-vm-images- + - name: Run vm integration tests run: | mage test:vm @@ -162,6 +217,7 @@ jobs: uses: actions/setup-go@v5 with: go-version: ${{ env.GO_VERSION }} + cache: false - name: Determine GoReleaser ID id: goreleaser_id diff --git a/.gitignore b/.gitignore index 94254d6b0319..88070261a647 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,6 @@ dist # Signing gpg.key cmd/trivy/trivy + +# RPM +*.rpm diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 8f1dfd40939e..4991af476c6a 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1 +1 @@ -{".":"0.54.0"} +{".":"0.56.2"} diff --git a/.vex/oci.openvex.json b/.vex/oci.openvex.json index b689d43afac1..f1ec8a32df48 100644 --- a/.vex/oci.openvex.json +++ b/.vex/oci.openvex.json @@ -140,6 +140,105 @@ "status": "not_affected", "justification": "vulnerable_code_cannot_be_controlled_by_adversary", "impact_statement": "awk is not used" + }, + { + "vulnerability": { + "name": "CVE-2024-4741" + }, + "products": [ + { + "@id": "pkg:oci/trivy?repository_url=index.docker.io%2Faquasec%2Ftrivy", + "subcomponents": [ + {"@id": "pkg:apk/alpine/libcrypto3"}, + {"@id": "pkg:apk/alpine/libssl3"}, + {"@id": "pkg:apk/alpine/ssl_client"} + ] + }, + { + "@id": "pkg:oci/trivy?repository_url=public.ecr.aws%2Faquasecurity%2Ftrivy", + "subcomponents": [ + {"@id": "pkg:apk/alpine/libcrypto3"}, + {"@id": "pkg:apk/alpine/libssl3"}, + {"@id": "pkg:apk/alpine/ssl_client"} + ] + }, + { + "@id": "pkg:oci/trivy?repository_url=ghcr.io/aquasecurity/trivy", + "subcomponents": [ + {"@id": "pkg:apk/alpine/libcrypto3"}, + {"@id": "pkg:apk/alpine/libssl3"}, + {"@id": "pkg:apk/alpine/ssl_client"} + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_cannot_be_controlled_by_adversary", + "impact_statement": "openssl is not used" + }, + { + "vulnerability": { + "name": "CVE-2024-5535" + }, + "products": [ + { + "@id": "pkg:oci/trivy?repository_url=index.docker.io%2Faquasec%2Ftrivy", + "subcomponents": [ + {"@id": "pkg:apk/alpine/libcrypto3"}, + {"@id": "pkg:apk/alpine/libssl3"}, + {"@id": "pkg:apk/alpine/ssl_client"} + ] + }, + { + "@id": "pkg:oci/trivy?repository_url=public.ecr.aws%2Faquasecurity%2Ftrivy", + "subcomponents": [ + {"@id": "pkg:apk/alpine/libcrypto3"}, + {"@id": "pkg:apk/alpine/libssl3"}, + {"@id": "pkg:apk/alpine/ssl_client"} + ] + }, + { + "@id": "pkg:oci/trivy?repository_url=ghcr.io/aquasecurity/trivy", + "subcomponents": [ + {"@id": "pkg:apk/alpine/libcrypto3"}, + {"@id": "pkg:apk/alpine/libssl3"}, + {"@id": "pkg:apk/alpine/ssl_client"} + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_cannot_be_controlled_by_adversary", + "impact_statement": "openssl is not used" + }, + { + "vulnerability": { + "name": "CVE-2024-6119" + }, + "products": [ + { + "@id": "pkg:oci/trivy?repository_url=index.docker.io%2Faquasec%2Ftrivy", + "subcomponents": [ + {"@id": "pkg:apk/alpine/libcrypto3"}, + {"@id": "pkg:apk/alpine/libssl3"} + ] + }, + { + "@id": "pkg:oci/trivy?repository_url=public.ecr.aws%2Faquasecurity%2Ftrivy", + "subcomponents": [ + {"@id": "pkg:apk/alpine/libcrypto3"}, + {"@id": "pkg:apk/alpine/libssl3"} + ] + }, + { + "@id": "pkg:oci/trivy?repository_url=ghcr.io/aquasecurity/trivy", + "subcomponents": [ + {"@id": "pkg:apk/alpine/libcrypto3"}, + {"@id": "pkg:apk/alpine/libssl3"} + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_cannot_be_controlled_by_adversary", + "impact_statement": "openssl is not used" } ] } diff --git a/.vex/trivy.openvex.json b/.vex/trivy.openvex.json index 21af61db7d76..2dd1629ecc89 100644 --- a/.vex/trivy.openvex.json +++ b/.vex/trivy.openvex.json @@ -453,6 +453,93 @@ "status": "not_affected", "justification": "vulnerable_code_not_in_execute_path", "impact_statement": "Govulncheck determined that the vulnerable code isn't called" + }, + { + "vulnerability": { + "@id": "https://pkg.go.dev/vuln/GO-2024-3105", + "name": "GO-2024-3105", + "description": "Stack exhaustion in all Parse functions in go/parser", + "aliases": [ + "CVE-2024-34155" + ] + }, + "products": [ + { + "@id": "pkg:golang/github.com/aquasecurity/trivy", + "identifiers": { + "purl": "pkg:golang/github.com/aquasecurity/trivy" + }, + "subcomponents": [ + { + "@id": "pkg:golang/stdlib", + "identifiers": { + "purl": "pkg:golang/stdlib" + } + } + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_in_execute_path", + "impact_statement": "Govulncheck determined that the vulnerable code isn't called" + }, + { + "vulnerability": { + "@id": "https://pkg.go.dev/vuln/GO-2024-3106", + "name": "GO-2024-3106", + "description": "Stack exhaustion in Decoder.Decode in encoding/gob", + "aliases": [ + "CVE-2024-34156" + ] + }, + "products": [ + { + "@id": "pkg:golang/github.com/aquasecurity/trivy", + "identifiers": { + "purl": "pkg:golang/github.com/aquasecurity/trivy" + }, + "subcomponents": [ + { + "@id": "pkg:golang/stdlib", + "identifiers": { + "purl": "pkg:golang/stdlib" + } + } + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_in_execute_path", + "impact_statement": "Govulncheck incorrectly marks this vulnerability as affected. The vulnerable code isn't called. See https://github.com/aquasecurity/trivy/issues/7478" + }, + { + "vulnerability": { + "@id": "https://pkg.go.dev/vuln/GO-2024-3107", + "name": "GO-2024-3107", + "description": "Stack exhaustion in Parse in go/build/constraint", + "aliases": [ + "CVE-2024-34158" + ] + }, + "products": [ + { + "@id": "pkg:golang/github.com/aquasecurity/trivy", + "identifiers": { + "purl": "pkg:golang/github.com/aquasecurity/trivy" + }, + "subcomponents": [ + { + "@id": "pkg:golang/stdlib", + "identifiers": { + "purl": "pkg:golang/stdlib" + } + } + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_in_execute_path", + "impact_statement": "Govulncheck determined that the vulnerable code isn't called" } ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 04a57a25147e..6d6fb0c912be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,130 @@ # Changelog +## [0.56.2](https://github.com/aquasecurity/trivy/compare/v0.56.1...v0.56.2) (2024-10-10) + + +### Bug Fixes + +* **redhat:** include arch in PURL qualifiers [backport: release/v0.56] ([#7702](https://github.com/aquasecurity/trivy/issues/7702)) ([f6700ec](https://github.com/aquasecurity/trivy/commit/f6700ec10e819fb2fc0573782e87d2d31d2c50f1)) +* **sbom:** add options for DBs in private registries [backport: release/v0.56] ([#7691](https://github.com/aquasecurity/trivy/issues/7691)) ([25d2540](https://github.com/aquasecurity/trivy/commit/25d2540f12272603bf27eb67f4b3fba52b1ddab8)) + +## [0.56.1](https://github.com/aquasecurity/trivy/compare/v0.56.0...v0.56.1) (2024-10-03) + + +### Bug Fixes + +* **db:** fix javadb downloading error handling [backport: release/v0.56] ([#7646](https://github.com/aquasecurity/trivy/issues/7646)) ([5dbdadf](https://github.com/aquasecurity/trivy/commit/5dbdadfe4578288d5c3f2a5b625fff4a3580f8c5)) + +## [0.56.0](https://github.com/aquasecurity/trivy/compare/v0.55.0...v0.56.0) (2024-10-03) + + +### Features + +* **java:** add empty versions if `pom.xml` dependency versions can't be detected ([#7520](https://github.com/aquasecurity/trivy/issues/7520)) ([b836232](https://github.com/aquasecurity/trivy/commit/b8362321adb2af220830c5de31c29978423d47da)) +* **license:** improve license normalization ([#7131](https://github.com/aquasecurity/trivy/issues/7131)) ([6472e3c](https://github.com/aquasecurity/trivy/commit/6472e3c9da2a8e7ba41598a45c80df8f18e57d4c)) +* **misconf:** add ability to disable checks by ID ([#7536](https://github.com/aquasecurity/trivy/issues/7536)) ([ef0a27d](https://github.com/aquasecurity/trivy/commit/ef0a27d515ff80762bf1959d44a8bde017ae06ec)) +* **misconf:** Register checks only when needed ([#7435](https://github.com/aquasecurity/trivy/issues/7435)) ([f768d3a](https://github.com/aquasecurity/trivy/commit/f768d3a767a99a86b0372f19d9f49a2de35dbe59)) +* **misconf:** Support `--skip-*` for all included modules ([#7579](https://github.com/aquasecurity/trivy/issues/7579)) ([c0e8da3](https://github.com/aquasecurity/trivy/commit/c0e8da3828e9d3a0b30d1f6568037db8dc827765)) +* **secret:** enhance secret scanning for python binary files ([#7223](https://github.com/aquasecurity/trivy/issues/7223)) ([60725f8](https://github.com/aquasecurity/trivy/commit/60725f879ba014c5c57583db6afc290b78facae8)) +* support multiple DB repositories for vulnerability and Java DB ([#7605](https://github.com/aquasecurity/trivy/issues/7605)) ([3562529](https://github.com/aquasecurity/trivy/commit/3562529ddfb26d301311ed450c192e17011353df)) +* support RPM archives ([#7628](https://github.com/aquasecurity/trivy/issues/7628)) ([69bf7e0](https://github.com/aquasecurity/trivy/commit/69bf7e00ea5ab483692db830fdded26a31f03183)) +* **suse:** added SUSE Linux Enterprise Micro support ([#7294](https://github.com/aquasecurity/trivy/issues/7294)) ([efdb68d](https://github.com/aquasecurity/trivy/commit/efdb68d3b9ddf9dfaf45ea5855b31c43a4366bab)) + + +### Bug Fixes + +* allow access to '..' in mapfs ([#7575](https://github.com/aquasecurity/trivy/issues/7575)) ([a8fbe46](https://github.com/aquasecurity/trivy/commit/a8fbe46119adbd89f827a75c75b9e97d392f1842)) +* **db:** check `DownloadedAt` for `trivy-java-db` ([#7592](https://github.com/aquasecurity/trivy/issues/7592)) ([13ef3e7](https://github.com/aquasecurity/trivy/commit/13ef3e7d62ba2bcb3a04d7b44f79b1299674b480)) +* **java:** use `dependencyManagement` from root/child pom's for dependencies from parents ([#7497](https://github.com/aquasecurity/trivy/issues/7497)) ([5442949](https://github.com/aquasecurity/trivy/commit/54429497e7d6a87eac236771d4efb8a5a7faaac5)) +* **license:** stop spliting a long license text ([#7336](https://github.com/aquasecurity/trivy/issues/7336)) ([4926da7](https://github.com/aquasecurity/trivy/commit/4926da79de901fba73819d71845ec0355b68ae0f)) +* **misconf:** Disable deprecated checks by default ([#7632](https://github.com/aquasecurity/trivy/issues/7632)) ([82e2adc](https://github.com/aquasecurity/trivy/commit/82e2adc6f8e68d0cc0021031170c2adb60d213ba)) +* **misconf:** disable DS016 check for image history analyzer ([#7540](https://github.com/aquasecurity/trivy/issues/7540)) ([de40df9](https://github.com/aquasecurity/trivy/commit/de40df9408d6d856a3ad384ec9f086edce3aa382)) +* **misconf:** escape all special sequences ([#7558](https://github.com/aquasecurity/trivy/issues/7558)) ([ea0cf03](https://github.com/aquasecurity/trivy/commit/ea0cf0379aff0348fde87356dab37947800fc1b6)) +* **misconf:** Fix logging typo ([#7473](https://github.com/aquasecurity/trivy/issues/7473)) ([56db43c](https://github.com/aquasecurity/trivy/commit/56db43c24f4f6be92891be85faaf9492cad516ac)) +* **misconf:** Fixed scope for China Cloud ([#7560](https://github.com/aquasecurity/trivy/issues/7560)) ([37d549e](https://github.com/aquasecurity/trivy/commit/37d549e5b86a1c5dce6710fbfd2310aec9abe949)) +* **misconf:** not to warn about missing selectors of libraries ([#7638](https://github.com/aquasecurity/trivy/issues/7638)) ([fcaea74](https://github.com/aquasecurity/trivy/commit/fcaea740808d5784c120e5c5d65f5f94e1d931d4)) +* **oracle:** Update EOL date for Oracle 7 ([#7480](https://github.com/aquasecurity/trivy/issues/7480)) ([dd0a64a](https://github.com/aquasecurity/trivy/commit/dd0a64a1cf0cd76e6f81e3ff55fa6ccb95ce3c3d)) +* **report:** change a receiver of MarshalJSON ([#7483](https://github.com/aquasecurity/trivy/issues/7483)) ([927c6e0](https://github.com/aquasecurity/trivy/commit/927c6e0c9d4d4a3f1be00f0f661c1d18325d9440)) +* **report:** fix error with unmarshal of `ExperimentalModifiedFindings` ([#7463](https://github.com/aquasecurity/trivy/issues/7463)) ([7ff9aff](https://github.com/aquasecurity/trivy/commit/7ff9aff2739b2eee4a98175b98914795e4077060)) +* **sbom:** export bom-ref when converting a package to a component ([#7340](https://github.com/aquasecurity/trivy/issues/7340)) ([5dd94eb](https://github.com/aquasecurity/trivy/commit/5dd94ebc1ffe3f1df511dee6381f92a5daefadf2)) +* **sbom:** parse type `framework` as `library` when unmarshalling `CycloneDX` files ([#7527](https://github.com/aquasecurity/trivy/issues/7527)) ([aeb7039](https://github.com/aquasecurity/trivy/commit/aeb7039d7ce090e243d29f0bf16c9e4e24252a01)) +* **secret:** change grafana token regex to find them without unquoted ([#7627](https://github.com/aquasecurity/trivy/issues/7627)) ([3e1fa21](https://github.com/aquasecurity/trivy/commit/3e1fa2100074e840bacdd65947425b08750b7d9a)) + + +### Performance Improvements + +* **misconf:** use port ranges instead of enumeration ([#7549](https://github.com/aquasecurity/trivy/issues/7549)) ([1f9fc13](https://github.com/aquasecurity/trivy/commit/1f9fc13da4a1e7c76c978e4f8e119bfd61a0480e)) + + +### Reverts + +* **java:** stop supporting of `test` scope for `pom.xml` files ([#7488](https://github.com/aquasecurity/trivy/issues/7488)) ([b0222fe](https://github.com/aquasecurity/trivy/commit/b0222feeb586ec59904bb321fda8f3f22496d07b)) + +## [0.55.0](https://github.com/aquasecurity/trivy/compare/v0.54.0...v0.55.0) (2024-09-03) + + +### ⚠ BREAKING CHANGES + +* **cli:** delete deprecated SBOM flags ([#7266](https://github.com/aquasecurity/trivy/issues/7266)) + +### Features + +* **cli:** delete deprecated SBOM flags ([#7266](https://github.com/aquasecurity/trivy/issues/7266)) ([7024572](https://github.com/aquasecurity/trivy/commit/70245721372720027b7089bd61c693df48add865)) +* **go:** use `toolchain` as `stdlib` version for `go.mod` files ([#7163](https://github.com/aquasecurity/trivy/issues/7163)) ([2d80769](https://github.com/aquasecurity/trivy/commit/2d80769c34b118851640411fff9dac0b3e353e82)) +* **java:** add `test` scope support for `pom.xml` files ([#7414](https://github.com/aquasecurity/trivy/issues/7414)) ([2d97700](https://github.com/aquasecurity/trivy/commit/2d97700d10665142d2f66d7910202bec82116209)) +* **misconf:** Add support for using spec from on-disk bundle ([#7179](https://github.com/aquasecurity/trivy/issues/7179)) ([be86126](https://github.com/aquasecurity/trivy/commit/be861265cafc89787fda09c59b2ef175e3d04204)) +* **misconf:** ignore duplicate checks ([#7317](https://github.com/aquasecurity/trivy/issues/7317)) ([9ef05fc](https://github.com/aquasecurity/trivy/commit/9ef05fc6b171a264516a025b0b0bcbbc8cff10bc)) +* **misconf:** iterator argument support for dynamic blocks ([#7236](https://github.com/aquasecurity/trivy/issues/7236)) ([fe92072](https://github.com/aquasecurity/trivy/commit/fe9207255a4f7f984ec1447f8a9219ae60e560c4)) +* **misconf:** port and protocol support for EC2 networks ([#7146](https://github.com/aquasecurity/trivy/issues/7146)) ([98e136e](https://github.com/aquasecurity/trivy/commit/98e136eb7baa2b66f4233d96875c1490144e1594)) +* **misconf:** scanning support for YAML and JSON ([#7311](https://github.com/aquasecurity/trivy/issues/7311)) ([efdbd8f](https://github.com/aquasecurity/trivy/commit/efdbd8f19ab0ab0c3b48293d43e51c81b7b03b89)) +* **misconf:** support for ignore by nested attributes ([#7205](https://github.com/aquasecurity/trivy/issues/7205)) ([44e4686](https://github.com/aquasecurity/trivy/commit/44e468603d44b077cc4606327fb3e7d7ca435e05)) +* **misconf:** support for policy and bucket grants ([#7284](https://github.com/aquasecurity/trivy/issues/7284)) ([a817fae](https://github.com/aquasecurity/trivy/commit/a817fae85b7272b391b737ec86673a7cab722bae)) +* **misconf:** variable support for Terraform Plan ([#7228](https://github.com/aquasecurity/trivy/issues/7228)) ([db2c955](https://github.com/aquasecurity/trivy/commit/db2c95598da098ca610825089eb4ab63b789b215)) +* **python:** use minimum version for pip packages ([#7348](https://github.com/aquasecurity/trivy/issues/7348)) ([e9b43f8](https://github.com/aquasecurity/trivy/commit/e9b43f81e67789b067352fcb6aa55bc9478bc518)) +* **report:** export modified findings in JSON ([#7383](https://github.com/aquasecurity/trivy/issues/7383)) ([7aea79d](https://github.com/aquasecurity/trivy/commit/7aea79dd93cfb61453766dbbb2e3fc0fbd317852)) +* **sbom:** set User-Agent header on requests to Rekor ([#7396](https://github.com/aquasecurity/trivy/issues/7396)) ([af1d257](https://github.com/aquasecurity/trivy/commit/af1d257730422d238871beb674767f8f83c5d06a)) +* **server:** add internal `--path-prefix` flag for client/server mode ([#7321](https://github.com/aquasecurity/trivy/issues/7321)) ([24a4563](https://github.com/aquasecurity/trivy/commit/24a45636867b893ff54c5ce07197f3b5c6db1d9b)) +* **server:** Make Trivy Server Multiplexer Exported ([#7389](https://github.com/aquasecurity/trivy/issues/7389)) ([4c6e8ca](https://github.com/aquasecurity/trivy/commit/4c6e8ca9cc9591799907cc73075f2d740e303b8f)) +* **vm:** Support direct filesystem ([#7058](https://github.com/aquasecurity/trivy/issues/7058)) ([45b3f34](https://github.com/aquasecurity/trivy/commit/45b3f344042bcd90ca63ab696b69bff0e9ab4e36)) +* **vm:** support the Ext2/Ext3 filesystems ([#6983](https://github.com/aquasecurity/trivy/issues/6983)) ([35c60f0](https://github.com/aquasecurity/trivy/commit/35c60f030fa48de8d8e57958e5ba379814126831)) +* **vuln:** Add `--detection-priority` flag for accuracy tuning ([#7288](https://github.com/aquasecurity/trivy/issues/7288)) ([fd8348d](https://github.com/aquasecurity/trivy/commit/fd8348d610f20c6c33da81cd7b0e7d5504ce26be)) + + +### Bug Fixes + +* **aws:** handle ECR repositories in different regions ([#6217](https://github.com/aquasecurity/trivy/issues/6217)) ([feaef96](https://github.com/aquasecurity/trivy/commit/feaef9699df5d8ca399770e701a59d7c0ff979a3)) +* **flag:** incorrect behavior for deprected flag `--clear-cache` ([#7281](https://github.com/aquasecurity/trivy/issues/7281)) ([2a0e529](https://github.com/aquasecurity/trivy/commit/2a0e529c36057b572119815af59c28e4790034ca)) +* **helm:** explicitly define `kind` and `apiVersion` of `volumeClaimTemplate` element ([#7362](https://github.com/aquasecurity/trivy/issues/7362)) ([da4ebfa](https://github.com/aquasecurity/trivy/commit/da4ebfa1a741f3f8b0b43289b4028afe763f7d43)) +* **java:** Return error when trying to find a remote pom to avoid segfault ([#7275](https://github.com/aquasecurity/trivy/issues/7275)) ([49d5270](https://github.com/aquasecurity/trivy/commit/49d5270163e305f88fedcf50412973736e69dc69)) +* **license:** add license handling to JUnit template ([#7409](https://github.com/aquasecurity/trivy/issues/7409)) ([f80183c](https://github.com/aquasecurity/trivy/commit/f80183c1139b21bb95bc64e216358f4a76001a65)) +* logger initialization before flags parsing ([#7372](https://github.com/aquasecurity/trivy/issues/7372)) ([c929290](https://github.com/aquasecurity/trivy/commit/c929290c3c0e4e91337264d69e75ccb60522bc65)) +* **misconf:** change default TLS values for the Azure storage account ([#7345](https://github.com/aquasecurity/trivy/issues/7345)) ([aadb090](https://github.com/aquasecurity/trivy/commit/aadb09078843250c66087f46db9a2aa48094a118)) +* **misconf:** do not filter Terraform plan JSON by name ([#7406](https://github.com/aquasecurity/trivy/issues/7406)) ([9d7264a](https://github.com/aquasecurity/trivy/commit/9d7264af8e85bcc0dba600b8366d0470d455251c)) +* **misconf:** do not recreate filesystem map ([#7416](https://github.com/aquasecurity/trivy/issues/7416)) ([3a5d091](https://github.com/aquasecurity/trivy/commit/3a5d091759564496992a83fb2015a21c84a22213)) +* **misconf:** do not register Rego libs in checks registry ([#7420](https://github.com/aquasecurity/trivy/issues/7420)) ([a5aa63e](https://github.com/aquasecurity/trivy/commit/a5aa63eff7e229744090f9ad300c1bec3259397e)) +* **misconf:** do not set default value for default_cache_behavior ([#7234](https://github.com/aquasecurity/trivy/issues/7234)) ([f0ed5e4](https://github.com/aquasecurity/trivy/commit/f0ed5e4ced7e60af35c88d5d084aa4b7237f4973)) +* **misconf:** fix infer type for null value ([#7424](https://github.com/aquasecurity/trivy/issues/7424)) ([0cac3ac](https://github.com/aquasecurity/trivy/commit/0cac3ac7075017628a21a7990941df04cbc16dbe)) +* **misconf:** init frameworks before updating them ([#7376](https://github.com/aquasecurity/trivy/issues/7376)) ([b65b32d](https://github.com/aquasecurity/trivy/commit/b65b32ddfa6fc62ac81ad9fa580e1f5a327864f5)) +* **misconf:** load only submodule if it is specified in source ([#7112](https://github.com/aquasecurity/trivy/issues/7112)) ([a4180bd](https://github.com/aquasecurity/trivy/commit/a4180bddd43d86e479edf0afe0c362021d071482)) +* **misconf:** support deprecating for Go checks ([#7377](https://github.com/aquasecurity/trivy/issues/7377)) ([2a6c7ab](https://github.com/aquasecurity/trivy/commit/2a6c7ab3b338ce4a8f99d6ac3508c2531dcbe812)) +* **misconf:** use module to log when metadata retrieval fails ([#7405](https://github.com/aquasecurity/trivy/issues/7405)) ([0799770](https://github.com/aquasecurity/trivy/commit/0799770b8827a8276ad0d6d9ac7e0381c286757c)) +* **misconf:** wrap Azure PortRange in iac types ([#7357](https://github.com/aquasecurity/trivy/issues/7357)) ([c5c62d5](https://github.com/aquasecurity/trivy/commit/c5c62d5ff05420321f9cdbfb93e2591e0866a342)) +* **nodejs:** check all `importers` to detect dev deps from pnpm-lock.yaml file ([#7387](https://github.com/aquasecurity/trivy/issues/7387)) ([fd9ed3a](https://github.com/aquasecurity/trivy/commit/fd9ed3a330bc66e229bcbdc262dc296a3bf01f54)) +* **plugin:** do not call GitHub content API for releases and tags ([#7274](https://github.com/aquasecurity/trivy/issues/7274)) ([b3ee6da](https://github.com/aquasecurity/trivy/commit/b3ee6dac269bd7847674f3ce985a5ff7f8f0ba38)) +* **report:** escape `Message` field in `asff.tpl` template ([#7401](https://github.com/aquasecurity/trivy/issues/7401)) ([dd9733e](https://github.com/aquasecurity/trivy/commit/dd9733e950d3127aa2ac90c45ec7e2b88a2b47ca)) +* safely check if the directory exists ([#7353](https://github.com/aquasecurity/trivy/issues/7353)) ([05a8297](https://github.com/aquasecurity/trivy/commit/05a829715f99cd90b122c64cd2f40157854e467b)) +* **sbom:** use `NOASSERTION` for licenses fields in SPDX formats ([#7403](https://github.com/aquasecurity/trivy/issues/7403)) ([c96dcdd](https://github.com/aquasecurity/trivy/commit/c96dcdd440a14cdd1b01ac473b2c15e4698e387b)) +* **secret:** use `.eyJ` keyword for JWT secret ([#7410](https://github.com/aquasecurity/trivy/issues/7410)) ([bf64003](https://github.com/aquasecurity/trivy/commit/bf64003ac8b209f34b88f228918a96d4f9dac5e0)) +* **secret:** use only line with secret for long secret lines ([#7412](https://github.com/aquasecurity/trivy/issues/7412)) ([391448a](https://github.com/aquasecurity/trivy/commit/391448aba9fcb0a4138225e5ab305e4e6707c603)) +* **terraform:** add aws_region name to presets ([#7184](https://github.com/aquasecurity/trivy/issues/7184)) ([bb2e26a](https://github.com/aquasecurity/trivy/commit/bb2e26a0ab707b718f6a890cbc87e2492298b6e5)) + + +### Performance Improvements + +* **misconf:** do not convert contents of a YAML file to string ([#7292](https://github.com/aquasecurity/trivy/issues/7292)) ([85dadf5](https://github.com/aquasecurity/trivy/commit/85dadf56265647c000191561db10b08a4948c140)) +* **misconf:** optimize work with context ([#6968](https://github.com/aquasecurity/trivy/issues/6968)) ([2b6d8d9](https://github.com/aquasecurity/trivy/commit/2b6d8d9227fb6ecc9386a14333964c23c0370a52)) +* **misconf:** use json.Valid to check validity of JSON ([#7308](https://github.com/aquasecurity/trivy/issues/7308)) ([c766831](https://github.com/aquasecurity/trivy/commit/c766831069e188226efafeec184e41498685ed85)) + ## [0.54.0](https://github.com/aquasecurity/trivy/compare/v0.53.0...v0.54.0) (2024-07-30) diff --git a/Dockerfile b/Dockerfile index 9e4dfe3b1dbe..1113c80aa9d5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.20.0 +FROM alpine:3.20.3 RUN apk --no-cache add ca-certificates git COPY trivy /usr/local/bin/trivy COPY contrib/*.tpl contrib/ diff --git a/contrib/asff.tpl b/contrib/asff.tpl index d6833a1d7ff1..3cadbb03ce79 100644 --- a/contrib/asff.tpl +++ b/contrib/asff.tpl @@ -108,7 +108,7 @@ "Region": "{{ env "AWS_REGION" }}", "Details": { "Other": { - "Message": "{{ .Message }}", + "Message": "{{ escapeString .Message }}", "Filename": "{{ $target }}", "StartLine": "{{ .CauseMetadata.StartLine }}", "EndLine": "{{ .CauseMetadata.EndLine }}" diff --git a/contrib/junit.tpl b/contrib/junit.tpl index 08e649b9eb00..b01bb9fd22dd 100644 --- a/contrib/junit.tpl +++ b/contrib/junit.tpl @@ -33,5 +33,16 @@ {{- end }} + +{{- if .Licenses }} + {{- $licenses := len .Licenses }} + {{ range .Licenses }} + + + + {{- end }} + +{{- end }} + {{- end }} diff --git a/docs/docs/advanced/air-gap.md b/docs/docs/advanced/air-gap.md index 171b80249eac..cf1df06f1bfe 100644 --- a/docs/docs/advanced/air-gap.md +++ b/docs/docs/advanced/air-gap.md @@ -1,142 +1,162 @@ -# Air-Gapped Environment +# Advanced Network Scenarios -Trivy can be used in air-gapped environments. Note that an allowlist is [here][allowlist]. +Trivy needs to connect to the internet occasionally in order to download relevant content. This document explains the network connectivity requirements of Trivy and setting up Trivy in particular scenarios. -## Air-Gapped Environment for vulnerabilities +## Network requirements -### Download the vulnerability database -At first, you need to download the vulnerability database for use in air-gapped environments. +Trivy's databases are distributed as OCI images via GitHub Container registry (GHCR): -=== "Trivy" +- +- +- - ``` - TRIVY_TEMP_DIR=$(mktemp -d) - trivy --cache-dir $TRIVY_TEMP_DIR image --download-db-only - tar -cf ./db.tar.gz -C $TRIVY_TEMP_DIR/db metadata.json trivy.db - rm -rf $TRIVY_TEMP_DIR - ``` +The following hosts are required in order to fetch them: -=== "oras >= v0.13.0" - Please follow [oras installation instruction][oras]. +- `ghcr.io` +- `pkg-containers.githubusercontent.com` - Download `db.tar.gz`: +The databases are pulled by Trivy using the [OCI Distribution](https://github.com/opencontainers/distribution-spec) specification, which is a simple HTTPS-based protocol. - ``` - $ oras pull ghcr.io/aquasecurity/trivy-db:2 - ``` +[VEX Hub](https://github.com/aquasecurity/vexhub) is distributed from GitHub over HTTPS. +The following hosts are required in order to fetch it: -=== "oras < v0.13.0" - Please follow [oras installation instruction][oras]. +- `api.github.com` +- `codeload.github.com` - Download `db.tar.gz`: +## Running Trivy in air-gapped environment - ``` - $ oras pull -a ghcr.io/aquasecurity/trivy-db:2 - ``` +An air-gapped environment refers to situations where the network connectivity from the machine Trivy runs on is blocked or restricted. -### Download the Java index database[^1] -Java users also need to download the Java index database for use in air-gapped environments. +In an air-gapped environment it is your responsibility to update the Trivy databases on a regular basis. -!!! note - You container image may contain JAR files even though you don't use Java directly. - In that case, you also need to download the Java index database. +## Offline Mode -=== "Trivy" +By default, Trivy will attempt to download latest databases. If it fails, the scan might fail. To avoid this behavior, you can tell Trivy to not attempt to download database files: - ``` - TRIVY_TEMP_DIR=$(mktemp -d) - trivy --cache-dir $TRIVY_TEMP_DIR image --download-java-db-only - tar -cf ./javadb.tar.gz -C $TRIVY_TEMP_DIR/java-db metadata.json trivy-java.db - rm -rf $TRIVY_TEMP_DIR - ``` -=== "oras >= v0.13.0" - Please follow [oras installation instruction][oras]. +- `--skip-db-update` to skip updating the main vulnerability database. +- `--skip-java-db-update` to skip updating the Java vulnerability database. +- `--skip-check-update` to skip updating the misconfiguration database. - Download `javadb.tar.gz`: +```shell +trivy image --skip-db-update --skip-java-db-update --offline-scan --skip-check-update myimage +``` + +## Self-Hosting - ``` - $ oras pull ghcr.io/aquasecurity/trivy-java-db:1 - ``` +### OCI Databases -=== "oras < v0.13.0" - Please follow [oras installation instruction][oras]. +You can host the databases on your own local OCI registry. - Download `javadb.tar.gz`: +First, make a copy of the databases in a container registry that is accessible to Trivy. The databases are in: - ``` - $ oras pull -a ghcr.io/aquasecurity/trivy-java-db:1 - ``` +- `ghcr.io/aquasecurity/trivy-db:2` +- `ghcr.io/aquasecurity/trivy-java-db:1` +- `ghcr.io/aquasecurity/trivy-checks:0` +Then, tell Trivy to use the local registry: -### Transfer the DB files into the air-gapped environment -The way of transfer depends on the environment. +```shell +trivy image \ + --db-repository myregistry.local/trivy-db \ + --java-db-repository myregistry.local/trivy-java-db \ + --checks-bundle-repository myregistry.local/trivy-checks \ + myimage +``` -=== "Vulnerability db" - ``` - $ rsync -av -e ssh /path/to/db.tar.gz [user]@[host]:dst - ``` +#### Authentication -=== "Java index db[^1]" - ``` - $ rsync -av -e ssh /path/to/javadb.tar.gz [user]@[host]:dst - ``` +If the registry requires authentication, you can configure it as described in the [private registry authentication document](../advanced/private-registries/index.md). -### Put the DB files in Trivy's cache directory -You have to know where to put the DB files. The following command shows the default cache directory. +### VEX Hub +You can host a copy of VEX Hub on your own internal server. + +First, make a copy of VEX Hub in a location that is accessible to Trivy. + +1. Download the [VEX Hub](https://github.com/aquasecurity/vexhub) archive from: . +1. Download the [VEX Hub Repository Manifest](https://github.com/aquasecurity/vex-repo-spec#2-repository-manifest) file from: . +1. Create or identify an internal HTTP server that can serve the VEX Hub repository in your environment (e.g `https://server.local`). +1. Make the downloaded archive file available for serving from your server (e.g `https://server.local/main.zip`). +1. Modify the downloaded manifest file's [Location URL](https://github.com/aquasecurity/vex-repo-spec?tab=readme-ov-file#locations-subfields) field to the URL of the archive file on your server (e.g `url: https://server.local/main.zip`). +1. Make the manifest file available for serving from your server under the `/.well-known` path (e.g `https://server.local/.well-known/vex-repository.json`). + +Then, tell Trivy to use the local VEX Repository: + +1. Locate your [Trivy VEX configuration file](../supply-chain/vex/repo/#configuration-file) by running `trivy vex repo init`. Make the following changes to the file. +1. Disable the default VEX Hub repo (`enabled: false`) +1. Add your internal VEX Hub repository as a [custom repository](../supply-chain/vex/repo/#custom-repositories) with the URL pointing to your local server (e.g `url: https://server.local`). + +#### Authentication + +If your server requires authentication, you can configure it as described in the [VEX Repository Authentication document](../supply-chain/vex/repo/#authentication). + +## Manual cache population + +You can also download the databases files manually and surgically populate the Trivy cache directory with them. + +### Downloading the DB files + +On a machine with internet access, pull the database container archive from the public registry into your local workspace: + +Note that these examples operate in the current working directory. + +=== "Using ORAS" +This example uses [ORAS](https://oras.land), but you can use any other container registry manipulation tool. + +```shell +oras pull ghcr.io/aquasecurity/trivy-db:2 ``` -$ ssh user@host -$ trivy -h | grep cache - --cache-dir value cache directory (default: "/home/myuser/.cache/trivy") [$TRIVY_CACHE_DIR] -``` -=== "Vulnerability db" - Put the DB file in the cache directory + `/db`. - - ``` - $ mkdir -p /home/myuser/.cache/trivy/db - $ cd /home/myuser/.cache/trivy/db - $ tar xvf /path/to/db.tar.gz -C /home/myuser/.cache/trivy/db - x trivy.db - x metadata.json - $ rm /path/to/db.tar.gz - ``` - -=== "Java index db[^1]" - Put the DB file in the cache directory + `/java-db`. - - ``` - $ mkdir -p /home/myuser/.cache/trivy/java-db - $ cd /home/myuser/.cache/trivy/java-db - $ tar xvf /path/to/javadb.tar.gz -C /home/myuser/.cache/trivy/java-db - x trivy-java.db - x metadata.json - $ rm /path/to/javadb.tar.gz - ``` - - - -In an air-gapped environment it is your responsibility to update the Trivy databases on a regular basis, so that the scanner can detect recently-identified vulnerabilities. - -### Run Trivy with the specific flags. -In an air-gapped environment, you have to specify `--skip-db-update` and `--skip-java-db-update`[^1] so that Trivy doesn't attempt to download the latest database files. -In addition, if you want to scan `pom.xml` dependencies, you need to specify `--offline-scan` since Trivy tries to issue API requests for scanning Java applications by default. +You should now have a file called `db.tar.gz`. Next, extract it to reveal the db files: + +```shell +tar -xzf db.tar.gz ``` -$ trivy image --skip-db-update --skip-java-db-update --offline-scan alpine:3.12 + +You should now have 2 new files, `metadata.json` and `trivy.db`. These are the Trivy DB files. + +=== "Using Trivy" +This example uses Trivy to pull the database container archive. The `--cache-dir` flag makes Trivy download the database files into our current working directory. The `--download-db-only` flag tells Trivy to only download the database files, not to scan any images. + +```shell +trivy image --cache-dir . --download-db-only ``` -## Air-Gapped Environment for misconfigurations +You should now have 2 new files, `metadata.json` and `trivy.db`. These are the Trivy DB files, copy them over to the air-gapped environment. -No special measures are required to detect misconfigurations in an air-gapped environment. +### Populating the Trivy Cache -### Run Trivy with `--skip-check-update` option -In an air-gapped environment, specify `--skip-check-update` so that Trivy doesn't attempt to download the latest misconfiguration checks. +In order to populate the cache, you need to identify the location of the cache directory. If it is under the default location, you can run the following command to find it: +```shell +trivy -h | grep cache ``` -$ trivy conf --skip-policy-update /path/to/conf + +For the example, we will assume the `TRIVY_CACHE_DIR` variable holds the cache location: + +```shell +TRIVY_CACHE_DIR=/home/user/.cache/trivy ``` -[allowlist]: ../references/troubleshooting.md -[oras]: https://oras.land/docs/installation +Put the Trivy DB files in the Trivy cache directory under a `db` subdirectory: + +```shell +# ensure cache db directory exists +mkdir -p ${TRIVY_CACHE_DIR}/db +# copy the db files +cp /path/to/trivy.db /path/to/metadata.json ${TRIVY_CACHE_DIR}/db/ +``` + +### Java DB + +For Java DB the process is the same, except for the following: + +1. Image location is `ghcr.io/aquasecurity/trivy-java-db:1` +2. Archive file name is `javadb.tar.gz` +3. DB file name is `trivy-java.db` + +## Misconfigurations scanning + +Note that the misconfigurations checks bundle is also embedded in the Trivy binary (at build time), and will be used as a fallback if the external database is not available. This means that you can still scan for misconfigurations in an air-gapped environment using the Checks from the time of the Trivy release you are using. -[^1]: This is only required to scan `jar` files. More information about `Java index db` [here](../coverage/language/java.md) +The misconfiguration scanner can be configured to load checks from a local directory, using the `--config-check` flag. In an air-gapped scenario you can copy the checks library from [Trivy checks repository](https://github.com/aquasecurity/trivy-checks) into a local directory, and load it with this flag. See more in the [Misconfiguration scanner documentation](../scanner/misconfiguration/index.md). diff --git a/docs/docs/compliance/contrib-compliance.md b/docs/docs/compliance/contrib-compliance.md index a848b1bf4367..0b83b688b664 100644 --- a/docs/docs/compliance/contrib-compliance.md +++ b/docs/docs/compliance/contrib-compliance.md @@ -35,7 +35,7 @@ Additional information is provided below. #### 1. Referencing a check that is already part of Trivy -Trivy has a comprehensive list of checks as part of its misconfiguration scanning. These can be found in the `trivy-policies/checks` directory ([Link](https://github.com/aquasecurity/trivy-checks/tree/main/checks)). If the check is present, the `AVD_ID` and other information from the check has to be used. +Trivy has a comprehensive list of checks as part of its misconfiguration scanning. These can be found in the `trivy-checks/checks` directory ([Link](https://github.com/aquasecurity/trivy-checks/tree/main/checks)). If the check is present, the `AVD_ID` and other information from the check has to be used. Note: Take a look at the more generic compliance specs that are already available in Trivy. If you are adding new compliance spec to Kubernetes e.g. AWS EKS CIS Benchmarks, chances are high that the check you would like to add to the new spec has already been defined in the general `k8s-ci-v.000.yaml` compliance spec. The same applies for creating specific Cloud Provider Compliance Specs and the [generic compliance specs](https://github.com/aquasecurity/trivy-checks/tree/main/specs/compliance) available. diff --git a/docs/docs/configuration/db.md b/docs/docs/configuration/db.md index f6525fb61568..ccffae1e5302 100644 --- a/docs/docs/configuration/db.md +++ b/docs/docs/configuration/db.md @@ -54,11 +54,44 @@ $ trivy image --download-db-only $ trivy image --db-repository registry.gitlab.com/gitlab-org/security-products/dependencies/trivy-db ``` +The media type of the OCI layer must be `application/vnd.aquasec.trivy.db.layer.v1.tar+gzip`. +You can reference the OCI manifest of [trivy-db]. + +
+Manifest + +```shell +{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": { + "mediaType": "application/vnd.aquasec.trivy.config.v1+json", + "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a", + "size": 2 + }, + "layers": [ + { + "mediaType": "application/vnd.aquasec.trivy.db.layer.v1.tar+gzip", + "digest": "sha256:29ad6505b8957c7cd4c367e7c705c641a9020d2be256812c5f4cc2fc099f4f02", + "size": 55474933, + "annotations": { + "org.opencontainers.image.title": "db.tar.gz" + } + } + ], + "annotations": { + "org.opencontainers.image.created": "2024-09-11T06:14:51Z" + } +} +``` +
+ !!!note Trivy automatically adds the `trivy-db` schema version as a tag if the tag is not used: `trivy-db-registry:latest` => `trivy-db-registry:latest`, but `trivy-db-registry` => `trivy-db-registry:2`. + ## Java Index Database The same options are also available for the Java index DB, which is used for scanning Java applications. Skipping an update can be done by using the `--skip-java-db-update` option, while `--download-java-db-only` can be used to only download the Java index DB. @@ -72,6 +105,9 @@ Downloading the Java index DB from an external OCI registry can be done by using $ trivy image --java-db-repository registry.gitlab.com/gitlab-org/security-products/dependencies/trivy-java-db --download-java-db-only ``` +The media type of the OCI layer must be `application/vnd.aquasec.trivy.javadb.layer.v1.tar+gzip`. +You can reference the OCI manifest of [trivy-java-db]. + !!!note Trivy automatically adds the `trivy-java-db` schema version as a tag if the tag is not used: @@ -84,4 +120,7 @@ $ trivy image --java-db-repository registry.gitlab.com/gitlab-org/security-produ $ trivy clean --vuln-db --java-db 2024-06-24T11:42:31+06:00 INFO Removing vulnerability database... 2024-06-24T11:42:31+06:00 INFO Removing Java database... -``` \ No newline at end of file +``` + +[trivy-db]: https://github.com/aquasecurity/trivy-db/pkgs/container/trivy-db +[trivy-java-db]: https://github.com/aquasecurity/trivy-java-db/pkgs/container/trivy-java-db \ No newline at end of file diff --git a/docs/docs/configuration/filtering.md b/docs/docs/configuration/filtering.md index abe8e84ff7e3..befcc54180ac 100644 --- a/docs/docs/configuration/filtering.md +++ b/docs/docs/configuration/filtering.md @@ -101,7 +101,7 @@ Total: 1785 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 1680, CRITICAL: 105) ```bash -trivy conf --severity HIGH,CRITICAL examples/misconf/mixed +trivy config --severity HIGH,CRITICAL examples/misconf/mixed ```
@@ -238,7 +238,7 @@ You can filter the results by To show the suppressed results, use the `--show-suppressed` flag. !!! note - This flag is currently available only in the table format. + It's exported as `ExperimentalModifiedFindings` in the JSON output. ```bash $ trivy image --vex debian11.csaf.vex --ignorefile .trivyignore.yaml --show-suppressed debian:11 diff --git a/docs/docs/coverage/iac/cloudformation.md b/docs/docs/coverage/iac/cloudformation.md index 5665e1e77acd..7faea9769d7d 100644 --- a/docs/docs/coverage/iac/cloudformation.md +++ b/docs/docs/coverage/iac/cloudformation.md @@ -21,7 +21,7 @@ It evaluates properties, functions, and other elements within CloudFormation fil You can provide `cf-params` with path to [CloudFormation Parameters] file to Trivy to scan your CloudFormation code with parameters. ```bash -trivy conf --cf-params params.json ./infrastructure/cf +trivy config --cf-params params.json ./infrastructure/cf ``` You can check a [CloudFormation Parameters Example] diff --git a/docs/docs/coverage/iac/helm.md b/docs/docs/coverage/iac/helm.md index 8d0352fc42f1..0c213e1fd901 100644 --- a/docs/docs/coverage/iac/helm.md +++ b/docs/docs/coverage/iac/helm.md @@ -21,7 +21,7 @@ When override values are passed to the Helm scanner, the values will be used dur Overrides can be set inline on the command line ```bash -trivy conf --helm-set securityContext.runAsUser=0 ./charts/mySql +trivy config --helm-set securityContext.runAsUser=0 ./charts/mySql ``` #### Setting value file overrides @@ -35,7 +35,7 @@ securityContext: ``` ```bash -trivy conf --helm-values overrides.yaml ./charts/mySql +trivy config --helm-values overrides.yaml ./charts/mySql ``` #### Setting value as explicit string @@ -49,7 +49,7 @@ trivy config --helm-set-string name=false ./infrastructure/tf Specific override values can come from specific files ```bash -trivy conf --helm-set-file environment=dev.values.yaml ./charts/mySql +trivy config --helm-set-file environment=dev.values.yaml ./charts/mySql ``` ## Secret diff --git a/docs/docs/coverage/iac/index.md b/docs/docs/coverage/iac/index.md index 168c3dd650fa..40f0a88023dd 100644 --- a/docs/docs/coverage/iac/index.md +++ b/docs/docs/coverage/iac/index.md @@ -8,15 +8,18 @@ Trivy scans Infrastructure as Code (IaC) files for ## Supported configurations -| Config type | File patterns | -|-------------------------------------|-----------------------------------------------| -| [Kubernetes](kubernetes.md) | \*.yml, \*.yaml, \*.json | -| [Docker](docker.md) | Dockerfile, Containerfile | -| [Terraform](terraform.md) | \*.tf, \*.tf.json, \*.tfvars | -| [Terraform Plan](terraform.md) | tfplan, \*.tfplan, \*.tfplan.json, \*.tf.json | -| [CloudFormation](cloudformation.md) | \*.yml, \*.yaml, \*.json | -| [Azure ARM Template](azure-arm.md) | \*.json | -| [Helm](helm.md) | \*.yaml, \*.tpl, \*.tar.gz, etc. | +| Config type | File patterns | +|-------------------------------------|----------------------------------| +| [Kubernetes](kubernetes.md) | \*.yml, \*.yaml, \*.json | +| [Docker](docker.md) | Dockerfile, Containerfile | +| [Terraform](terraform.md) | \*.tf, \*.tf.json, \*.tfvars | +| [Terraform Plan](terraform.md) | tfplan, \*.tfplan, \*.json | +| [CloudFormation](cloudformation.md) | \*.yml, \*.yaml, \*.json | +| [Azure ARM Template](azure-arm.md) | \*.json | +| [Helm](helm.md) | \*.yaml, \*.tpl, \*.tar.gz, etc. | +| [YAML][json-and-yaml] | \*.yaml, \*.yml | +| [JSON][json-and-yaml] | \*.json | [misconf]: ../../scanner/misconfiguration/index.md [secret]: ../../scanner/secret.md +[json-and-yaml]: ../../scanner/misconfiguration/index.md#scan-arbitrary-json-and-yaml-configurations diff --git a/docs/docs/coverage/iac/terraform.md b/docs/docs/coverage/iac/terraform.md index e190c901cf05..55f1936cb040 100644 --- a/docs/docs/coverage/iac/terraform.md +++ b/docs/docs/coverage/iac/terraform.md @@ -18,13 +18,13 @@ It supports the following formats: Trivy can scan Terraform Plan files (snapshots) or their JSON representations. To create a Terraform Plan and scan it, run the following command: ```bash terraform plan --out tfplan -trivy conf tfplan +trivy config tfplan ``` To scan a Terraform Plan representation in JSON format, run the following command: ```bash terraform show -json tfplan > tfplan.json -trivy conf tfplan.json +trivy config tfplan.json ``` ## Misconfiguration @@ -35,7 +35,7 @@ It also evaluates variables, imports, and other elements within Terraform files You can provide `tf-vars` files to Trivy to override default values specified in the Terraform HCL code. ```bash -trivy conf --tf-vars dev.terraform.tfvars ./infrastructure/tf +trivy config --tf-vars dev.terraform.tfvars ./infrastructure/tf ``` ### Exclude Downloaded Terraform Modules @@ -43,7 +43,7 @@ By default, downloaded modules are also scanned. If you don't want to scan them, you can use the `--tf-exclude-downloaded-modules` flag. ```bash -trivy conf --tf-exclude-downloaded-modules ./configs +trivy config --tf-exclude-downloaded-modules ./configs ``` ## Secret diff --git a/docs/docs/coverage/language/dart.md b/docs/docs/coverage/language/dart.md index a64a19eb83c2..5e86b6c399f4 100644 --- a/docs/docs/coverage/language/dart.md +++ b/docs/docs/coverage/language/dart.md @@ -11,9 +11,9 @@ The following scanners are supported. The following table provides an outline of the features Trivy offers. -| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | -|-------------------------|--------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:| -| [Dart][dart-repository] | pubspec.lock | ✓ | Included | ✓ | - | +| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | [Detection Priority][detection-priority] | +|-------------------------|--------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:|:----------------------------------------:| +| [Dart][dart-repository] | pubspec.lock | ✓ | Included | ✓ | - | ✓ | ## Dart In order to detect dependencies, Trivy searches for `pubspec.lock`. @@ -22,11 +22,13 @@ Trivy marks indirect dependencies, but `pubspec.lock` file doesn't have options So Trivy includes all dependencies in report. ### SDK dependencies -Dart uses version `0.0.0` for SDK dependencies (e.g. Flutter). It is not possible to accurately determine the versions of these dependencies. +Dart uses version `0.0.0` for SDK dependencies (e.g. Flutter). +It is not possible to accurately determine the versions of these dependencies. +Trivy just treats them as `0.0.0`. -Therefore, we use the first version of the constraint for the SDK. +If [--detection-priority comprehensive][detection-priority] is passed, Trivy uses the minimum version of the constraint for the SDK. +For example, in the following case, the version of `flutter` would be `3.3.0`: -For example in this case the version of `flutter` should be `3.3.0`: ```yaml flutter: dependency: "direct main" @@ -40,6 +42,7 @@ sdks: ### Dependency tree To build `dependency tree` Trivy parses [cache directory][cache-directory]. Currently supported default directories and `PUB_CACHE` environment (absolute path only). + !!! note Make sure the cache directory contains all the dependencies installed in your application. To download missing dependencies, use `dart pub get` command. @@ -47,3 +50,4 @@ To build `dependency tree` Trivy parses [cache directory][cache-directory]. Curr [dart-repository]: https://pub.dev/ [dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies [cache-directory]: https://dart.dev/tools/pub/glossary#system-cache +[detection-priority]: ../../scanner/vulnerability.md#detection-priority \ No newline at end of file diff --git a/docs/docs/coverage/language/golang.md b/docs/docs/coverage/language/golang.md index 6b3646329318..cd1a30c53e9c 100644 --- a/docs/docs/coverage/language/golang.md +++ b/docs/docs/coverage/language/golang.md @@ -1,32 +1,31 @@ # Go -## Data Sources -The data sources are listed [here](../../scanner/vulnerability.md#data-sources-1). -Trivy uses Go Vulnerability Database for standard packages, such as `net/http`, and uses GitHub Advisory Database for third-party packages. - -## Features +## Overview Trivy supports two types of Go scanning, Go Modules and binaries built by Go. The following scanners are supported. -| Artifact | SBOM | Vulnerability | License | -| -------- | :---: | :-----------: | :-----: | -| Modules | ✓ | ✓ | ✓[^2] | -| Binaries | ✓ | ✓ | - | +| Artifact | SBOM | Vulnerability | License | +|----------|:----:|:-------------:|:-------------:| +| Modules | ✓ | ✓ | [✓](#license) | +| Binaries | ✓ | ✓ | - | The table below provides an outline of the features Trivy offers. -| Artifact | Offline[^1] | Dev dependencies | [Dependency graph][dependency-graph] | Stdlib | -|----------|:-----------:|:-----------------|:------------------------------------:|:------:| -| Modules | ✅ | Include | ✅[^2] | - | -| Binaries | ✅ | Exclude | - | ✅[^4] | +| Artifact | Offline[^1] | Dev dependencies | [Dependency graph][dependency-graph] | Stdlib | [Detection Priority][detection-priority] | +|----------|:-----------:|:-----------------|:------------------------------------:|:------------------------:|:----------------------------------------:| +| Modules | ✅ | Include | [✅](#dependency-graph) | [✅](#standard-library) | [✅](#standard-library) | +| Binaries | ✅ | Exclude | - | [✅](#standard-library-1) | Not needed | !!! note - Trivy scans only dependencies of the Go project. - Let's say you scan the Docker binary, Trivy doesn't detect vulnerabilities of Docker itself. - Also, when you scan go.mod in Kubernetes, the Kubernetes vulnerabilities will not be found. + When scanning Go projects (go.mod or binaries built with Go), Trivy scans only dependencies of the project, and does not detect vulnerabilities of application itself. + For example, when scanning the Docker project (Docker's source code with go.mod or the Docker binary), Trivy might find vulnerabilities in Go modules that Docker depends on, but won't find vulnerabilities of Docker itself. Moreover, when scanning the Trivy project, which happens to use Docker, Docker's vulnerabilities might be detected as dependencies of Trivy. + +## Data Sources +The data sources are listed [here](../../scanner/vulnerability.md#data-sources-1). +Trivy uses Go Vulnerability Database for [standard library](https://pkg.go.dev/std) and uses GitHub Advisory Database for other Go modules. -### Go Modules +## Go Module Depending on Go versions, the required files are different. | Version | Required files | Offline | @@ -42,7 +41,7 @@ Go 1.17+ holds actually needed indirect dependencies in `go.mod`, and it reduces If you want to have better detection, please consider updating the Go version in your project. !!! note - The Go version doesn't mean your CLI version, but the Go version in your go.mod. + The Go version doesn't mean your Go tool version, but the Go version in your go.mod. ``` module github.com/aquasecurity/trivy @@ -61,15 +60,37 @@ If you want to have better detection, please consider updating the Go version in $ go mod tidy -go=1.18 ``` -To identify licenses and dependency relationships, you need to download modules to local cache beforehand, -such as `go mod download`, `go mod tidy`, etc. +### Main Module +Trivy scans only dependencies of the project, and does not detect vulnerabilities of the main module. +For example, when scanning the Docker project (Docker's source code with go.mod), Trivy might find vulnerabilities in Go modules that Docker depends on, but won't find vulnerabilities of Docker itself. +Moreover, when scanning the Trivy project, which happens to use Docker, Docker's vulnerabilities might be detected as dependencies of Trivy. + +### Standard Library +Detecting the version of Go used in the project can be tricky. +The go.mod file include hints that allows Trivy to guess the Go version but it eventually depends on the Go tool version in the build environment. +Since this strategy is not fully deterministic and accurate, it is enabled only in [--detection-priority comprehensive][detection-priority] mode. +When enabled, Trivy detects stdlib version as the minimum between the `go` and the `toolchain` directives in the `go.mod` file. +To obtain reproducible scan results Trivy doesn't check the locally installed version of `Go`. + +!!! note + Trivy detects `stdlib` only for `Go` 1.21 or higher. + + The version from the `go` line (for `Go` 1.20 or early) is not a minimum required version. + For details, see [this](https://go.googlesource.com/proposal/+/master/design/57001-gotoolchain.md). + +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. -### Go binaries -Trivy scans binaries built by Go, which include [module information](https://tip.golang.org/doc/go1.18#go-version). -If there is a Go binary in your container image, Trivy automatically finds and scans it. +### Dependency Graph +Same as licenses, you need to download modules to local cache beforehand. -Also, you can scan your local binaries. +## Go Binary +Trivy scans Go binaries when it encounters them during scans such as container images or file systems. +When scanning binaries built by Go, Trivy finds dependencies and Go version information as [embedded in the binary by Go tool at build time](https://tip.golang.org/doc/go1.18#go-version). ``` $ trivy rootfs ./your_binary @@ -78,20 +99,34 @@ $ trivy rootfs ./your_binary !!! note It doesn't work with UPX-compressed binaries. -#### Empty versions -There are times when Go uses the `(devel)` version for modules/dependencies. +### Main Module +Go binaries installed using the `go install` command contains correct (semver) version for the main module and therefor are detected by Trivy. +In other cases, Go uses the `(devel)` version[^2]. +In this case, Trivy will attempt to parse any `-ldflags` as it's a common practice to pass versions this way. +If unsuccessful, the version will be empty[^3]. + +### Standard Library +Trivy detects the Go version used to compile the binary and detects its vulnerabilities in the standard libraries. +It possibly produces false positives. +See [the caveat](#stdlib-vulnerabilities) for details. + +## Caveats + +### Stdlib Vulnerabilities +Trivy does not know if or how you use stdlib functions, therefore it is possible that stdlib vulnerabilities are not applicable to your use case. +There are a few ways to mitigate this: -- Only Go binaries installed using the `go install` command contain correct (semver) version for the main module. - In other cases, Go uses the `(devel)` version[^3]. -- Dependencies replaced with local ones use the `(devel)` versions. +1. Analyze vulnerability reachability using a tool such as [govulncheck](https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck). This will ensure that reported vulnerabilities are applicable to your project. +2. Suppress non-applicable vulnerabilities using either [ignore file](../../configuration/filtering.md) for self-use or [VEX Hub](../../supply-chain/vex/repo.md) for public use. -In the first case, Trivy will attempt to parse any `-ldflags` as a secondary source, and will leave the version -empty if it cannot do so[^5]. For the second case, the version of such packages is empty. +### Empty Version +As described in the [Main Module](#main-module-1) section, the main module of Go binaries might have an empty version. +Also, dependencies replaced with local ones will have an empty version. [^1]: It doesn't require the Internet access. -[^2]: Need to download modules to local cache beforehand -[^3]: See https://github.com/aquasecurity/trivy/issues/1837#issuecomment-1832523477 -[^4]: Identify the Go version used to compile the binary and detect its vulnerabilities -[^5]: See https://github.com/golang/go/issues/63432#issuecomment-1751610604 +[^2]: See https://github.com/aquasecurity/trivy/issues/1837#issuecomment-1832523477 +[^3]: See https://github.com/golang/go/issues/63432#issuecomment-1751610604 [dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies +[toolchain]: https://go.dev/doc/toolchain +[detection-priority]: ../../scanner/vulnerability.md#detection-priority diff --git a/docs/docs/coverage/language/java.md b/docs/docs/coverage/language/java.md index bb90366c1772..0ee39bbd3155 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 | -|------------------|:---------------------:|:----------------:|:------------------------------------:|:--------:| -| JAR/WAR/PAR/EAR | Trivy Java DB | Include | - | - | -| pom.xml | Maven repository [^1] | Exclude | ✓ | ✓[^7] | -| *gradle.lockfile | - | Exclude | ✓ | ✓ | -| *.sbt.lock | - | Exclude | - | ✓ | +| 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 | These may be enabled or disabled depending on the target. See [here](./index.md) for the detail. @@ -69,6 +69,16 @@ The vulnerability database will be downloaded anyway. !!! Warning Trivy may skip some dependencies (that were not found on your local machine) when the `--offline-scan` flag is passed. +### empty dependency version +There are cases when Trivy cannot determine the version of dependencies: + +- Unable to determine the version from the parent because the parent is not reachable; +- The dependency uses a [hard requirement][version-requirement] with more than one version. + +In these cases, Trivy uses an empty version for the dependency. + +!!! Warning + Trivy doesn't detect child dependencies for dependencies without a version. ### maven-invoker-plugin Typically, the integration tests directory (`**/[src|target]/it/*/pom.xml`) of [maven-invoker-plugin][maven-invoker-plugin] doesn't contain actual `pom.xml` files and should be skipped to avoid noise. @@ -119,3 +129,5 @@ Make sure that you have cache[^8] directory to find licenses from `*.pom` depend [maven-central]: https://repo.maven.apache.org/maven2/ [maven-pom-repos]: https://maven.apache.org/settings.html#repositories [sbt-dependency-lock]: https://stringbean.github.io/sbt-dependency-lock +[detection-priority]: ../../scanner/vulnerability.md#detection-priority +[version-requirement]: https://maven.apache.org/pom.html#dependency-version-requirement-specification diff --git a/docs/docs/coverage/language/python.md b/docs/docs/coverage/language/python.md index c4f6b6d83e86..27b776ec2d75 100644 --- a/docs/docs/coverage/language/python.md +++ b/docs/docs/coverage/language/python.md @@ -21,11 +21,11 @@ The following scanners are supported for Python packages. The following table provides an outline of the features Trivy offers. -| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | -|-----------------|------------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:| -| pip | requirements.txt | - | Include | - | ✓ | -| Pipenv | Pipfile.lock | ✓ | Include | - | ✓ | -| Poetry | poetry.lock | ✓ | Exclude | ✓ | - | +| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | [Detection Priority][detection-priority] | +|-----------------|------------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:|:----------------------------------------:| +| pip | requirements.txt | - | Include | - | ✓ | ✓ | +| Pipenv | Pipfile.lock | ✓ | Include | - | ✓ | Not needed | +| Poetry | poetry.lock | ✓ | Exclude | ✓ | - | Not needed | | Packaging | Dependency graph | @@ -42,8 +42,17 @@ Trivy parses your files generated by package managers in filesystem/repository s ### pip #### Dependency detection -Trivy only parses [version specifiers](https://packaging.python.org/en/latest/specifications/version-specifiers/#id4) with `==` comparison operator and without `.*`. -To convert unsupported version specifiers - use the `pip freeze` command. +By default, Trivy only parses [version specifiers](https://packaging.python.org/en/latest/specifications/version-specifiers/#id5) with `==` comparison operator and without `.*`. + +Using the [--detection-priority comprehensive](#detection-priority) option ensures that the tool establishes a minimum version, which is particularly useful in scenarios where identifying the exact version is challenging. +In such case Trivy parses specifiers `>=`,`~=` and a trailing `.*`. + +``` +keyring >= 4.1.1 # Minimum version 4.1.1 +Mopidy-Dirble ~= 1.1 # Minimum version 1.1 +python-gitlab==2.0.* # Minimum version 2.0.0 +``` +Also, there is a way to convert unsupported version specifiers - use the `pip freeze` command. ```bash $ cat requirements.txt @@ -119,7 +128,7 @@ License detection is not supported for `Poetry`. ## Packaging Trivy parses the manifest files of installed packages in container image scanning and so on. -See [here](https://packaging.python.org/en/latest/discussions/wheel-vs-egg/) for the detail. +See [here](https://packaging.python.org/en/latest/discussions/package-formats/) for the detail. ### Egg Trivy looks for `*.egg-info`, `*.egg-info/PKG-INFO`, `*.egg` and `EGG-INFO/PKG-INFO` to identify Python packages. @@ -130,3 +139,4 @@ Trivy looks for `.dist-info/META-DATA` to identify Python packages. [^1]: Trivy checks `python`, `python3`, `python2` and `python.exe` file names. [dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies +[detection-priority]: ../../scanner/vulnerability.md#detection-priority diff --git a/docs/docs/coverage/os/index.md b/docs/docs/coverage/os/index.md index a28e113f07c9..8756ddc8aa95 100644 --- a/docs/docs/coverage/os/index.md +++ b/docs/docs/coverage/os/index.md @@ -23,18 +23,19 @@ Trivy supports operating systems for | [Amazon Linux](amazon.md) | 1, 2, 2023 | dnf/yum/rpm | | [openSUSE Leap](suse.md) | 42, 15 | zypper/rpm | | [openSUSE Tumbleweed](suse.md) | (n/a) | zypper/rpm | -| [SUSE Enterprise Linux](suse.md) | 11, 12, 15 | zypper/rpm | +| [SUSE Linux Enterprise](suse.md) | 11, 12, 15 | zypper/rpm | +| [SUSE Linux Enterprise Micro](suse.md)| 5, 6 | zypper/rpm | | [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 | -| [OSs with installed Conda](conda.md) | - | conda | +| [OSs with installed Conda](../others/conda.md) | - | conda | ## Supported container images | Container image | Supported Versions | Package Managers | |-----------------------------------------------|-------------------------------------|------------------| | [Google Distroless](google-distroless.md)[^2] | Any | apt/dpkg | -| [Bitnami](bitnami.md) | Any | - | +| [Bitnami](../others/bitnami.md) | Any | - | Each page gives more details. diff --git a/docs/docs/coverage/os/suse.md b/docs/docs/coverage/os/suse.md index 15cfb1e9379a..b28fed13425b 100644 --- a/docs/docs/coverage/os/suse.md +++ b/docs/docs/coverage/os/suse.md @@ -3,7 +3,8 @@ Trivy supports the following distributions: - openSUSE Leap - openSUSE Tumbleweed -- SUSE Enterprise Linux (SLE) +- SUSE Linux Enterprise (SLE) +- SUSE Linux Enterprise Micro Please see [here](index.md#supported-os) for supported versions. diff --git a/docs/docs/coverage/os/bitnami.md b/docs/docs/coverage/others/bitnami.md similarity index 95% rename from docs/docs/coverage/os/bitnami.md rename to docs/docs/coverage/others/bitnami.md index 56cfb97a4601..4773fd9ab20c 100644 --- a/docs/docs/coverage/os/bitnami.md +++ b/docs/docs/coverage/others/bitnami.md @@ -4,8 +4,8 @@ Scanning results may be inaccurate. While it is not an OS, this page describes the details of the [container images provided by Bitnami](https://github.com/bitnami/containers). -Bitnami images are based on [Debian](debian.md). -Please see [the Debian page](debian.md) for OS packages. +Bitnami images are based on [Debian](../os/debian.md). +Please see [the Debian page](../os/debian.md) for OS packages. Trivy supports the following scanners for Bitnami packages. diff --git a/docs/docs/coverage/os/conda.md b/docs/docs/coverage/others/conda.md similarity index 72% rename from docs/docs/coverage/os/conda.md rename to docs/docs/coverage/others/conda.md index 10c1e93c1b8c..773bd8097b6d 100644 --- a/docs/docs/coverage/os/conda.md +++ b/docs/docs/coverage/others/conda.md @@ -8,6 +8,9 @@ Trivy supports the following scanners for Conda packages. | Vulnerability | - | | License | ✓ | +| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | [Detection Priority][detection-priority] | +|-----------------|-----------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:|:----------------------------------------:| +| Conda | environment.yml | - | Include | - | ✓ | - | ## `.json` @@ -41,3 +44,5 @@ To correctly define licenses, make sure your `environment.yml`[^1] contains `pre [environment.yml]: https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#sharing-an-environment [env-version-range]: https://docs.conda.io/projects/conda-build/en/latest/resources/package-spec.html#examples-of-package-specs [prefix]: https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#specifying-a-location-for-an-environment +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies +[detection-priority]: ../../scanner/vulnerability.md#detection-priority diff --git a/docs/docs/coverage/others/rpm.md b/docs/docs/coverage/others/rpm.md new file mode 100644 index 000000000000..092cd4396b07 --- /dev/null +++ b/docs/docs/coverage/others/rpm.md @@ -0,0 +1,42 @@ +# RPM Archives + +!!! warning "EXPERIMENTAL" + This feature might change without preserving backwards compatibility. + +Trivy supports the following scanners for RPM archives. + +| Scanner | Supported | +|:-------------:|:---------:| +| SBOM | ✓ | +| Vulnerability | ✓[^1] | +| License | ✓ | + +The table below outlines the features offered by Trivy. + +## SBOM +Trivy analyzes RPM archives matching `*.rpm`. +This feature is currently disabled by default but can be enabled with an environment variable, `TRIVY_EXPERIMENTAL_RPM_ARCHIVE`. + +```shell +TRIVY_EXPERIMENTAL_RPM_ARCHIVE=true trivy fs ./rpms --format cyclonedx --output rpms.cdx.json +``` + +!!! note + Currently, it works with `--format cyclonedx`, `--format spdx` or `--format spdx-json`. + + +## Vulnerability +Since RPM files don't have OS information, you need to generate SBOM, fill in the OS information manually and then scan the SBOM for vulnerabilities. + +For example: + +```shell +$ TRIVY_EXPERIMENTAL_RPM_ARCHIVE=true trivy fs ./rpms -f cyclonedx -o rpms.cdx.json +$ jq '(.components[] | select(.type == "operating-system")) |= (.name = "redhat" | .version = "7.9")' rpms.cdx.json > rpms-res.cdx.json +$ trivy sbom ./rpms-res.cdx.json +``` + +## License +If licenses are included in the RPM archive, Trivy extracts it. + +[^1]: Need to generate SBOM first and add OS information to that SBOM diff --git a/docs/docs/references/configuration/cli/trivy_config.md b/docs/docs/references/configuration/cli/trivy_config.md index 0176c09ea58f..be7854548496 100644 --- a/docs/docs/references/configuration/cli/trivy_config.md +++ b/docs/docs/references/configuration/cli/trivy_config.md @@ -13,10 +13,11 @@ trivy config [flags] DIR --cache-ttl duration cache TTL when using redis as cache backend --cf-params strings specify paths to override the CloudFormation parameters files --check-namespaces strings Rego namespaces - --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:0") + --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:1") --compliance string compliance report to generate --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 --enable-modules strings [EXPERIMENTAL] module names to enable --exit-code int specify exit code when any security issues are found --file-patterns strings specify config file patterns diff --git a/docs/docs/references/configuration/cli/trivy_filesystem.md b/docs/docs/references/configuration/cli/trivy_filesystem.md index 99f1df23c069..0abec4990173 100644 --- a/docs/docs/references/configuration/cli/trivy_filesystem.md +++ b/docs/docs/references/configuration/cli/trivy_filesystem.md @@ -23,13 +23,18 @@ trivy filesystem [flags] PATH --cache-ttl duration cache TTL when using redis as cache backend --cf-params strings specify paths to override the CloudFormation parameters files --check-namespaces strings Rego namespaces - --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:0") + --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:1") --compliance string compliance report to generate --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 --custom-headers strings custom headers in client mode - --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") + --db-repository strings OCI repository(ies) to retrieve trivy-db in order of priority (default [ghcr.io/aquasecurity/trivy-db:2]) --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages + --detection-priority string specify the detection priority: + - "precise": Prioritizes precise by minimizing false positives. + - "comprehensive": Aims to detect more security findings at the cost of potential false positives. + (precise,comprehensive) (default "precise") --download-db-only download/update vulnerability database but don't run a scan --download-java-db-only download/update Java index database but don't run a scan --enable-modules strings [EXPERIMENTAL] module names to enable @@ -51,7 +56,7 @@ trivy filesystem [flags] PATH --include-deprecated-checks include deprecated checks --include-dev-deps include development dependencies in the report (supported: npm, yarn) --include-non-failures include successes and exceptions, available with '--scanners misconfig' - --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db:1") + --java-db-repository strings OCI repository(ies) to retrieve trivy-java-db in order of priority (default [ghcr.io/aquasecurity/trivy-java-db:1]) --license-confidence-level float specify license classifier's confidence level (default 0.9) --license-full eagerly look for licenses in source code headers and license files --list-all-pkgs output all packages in the JSON report regardless of vulnerability diff --git a/docs/docs/references/configuration/cli/trivy_image.md b/docs/docs/references/configuration/cli/trivy_image.md index e7f2e3c70ea1..f9095f041572 100644 --- a/docs/docs/references/configuration/cli/trivy_image.md +++ b/docs/docs/references/configuration/cli/trivy_image.md @@ -37,13 +37,18 @@ trivy image [flags] IMAGE_NAME --cache-backend string [EXPERIMENTAL] cache backend (e.g. redis://localhost:6379) (default "fs") --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 "ghcr.io/aquasecurity/trivy-checks:0") + --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:1") --compliance string compliance report to generate (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 --custom-headers strings custom headers in client mode - --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") + --db-repository strings OCI repository(ies) to retrieve trivy-db in order of priority (default [ghcr.io/aquasecurity/trivy-db:2]) --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages + --detection-priority string specify the detection priority: + - "precise": Prioritizes precise by minimizing false positives. + - "comprehensive": Aims to detect more security findings at the cost of potential false positives. + (precise,comprehensive) (default "precise") --docker-host string unix domain socket path to use for docker scanning --download-db-only download/update vulnerability database but don't run a scan --download-java-db-only download/update Java index database but don't run a scan @@ -69,7 +74,7 @@ trivy image [flags] IMAGE_NAME --include-deprecated-checks include deprecated checks --include-non-failures include successes and exceptions, available with '--scanners misconfig' --input string input file path instead of image name - --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db:1") + --java-db-repository strings OCI repository(ies) to retrieve trivy-java-db in order of priority (default [ghcr.io/aquasecurity/trivy-java-db:1]) --license-confidence-level float specify license classifier's confidence level (default 0.9) --license-full eagerly look for licenses in source code headers and license files --list-all-pkgs output all packages in the JSON report regardless of vulnerability diff --git a/docs/docs/references/configuration/cli/trivy_kubernetes.md b/docs/docs/references/configuration/cli/trivy_kubernetes.md index 6c98a35d9020..99fbc1badaa2 100644 --- a/docs/docs/references/configuration/cli/trivy_kubernetes.md +++ b/docs/docs/references/configuration/cli/trivy_kubernetes.md @@ -33,12 +33,17 @@ trivy kubernetes [flags] [CONTEXT] --cache-backend string [EXPERIMENTAL] cache backend (e.g. redis://localhost:6379) (default "fs") --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 "ghcr.io/aquasecurity/trivy-checks:0") + --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:1") --compliance string compliance report to generate (k8s-nsa-1.0,k8s-cis-1.23,eks-cis-1.4,rke2-cis-1.24,k8s-pss-baseline-0.1,k8s-pss-restricted-0.1) --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 - --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") + --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 + --db-repository strings OCI repository(ies) to retrieve trivy-db in order of priority (default [ghcr.io/aquasecurity/trivy-db:2]) --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages + --detection-priority string specify the detection priority: + - "precise": Prioritizes precise by minimizing false positives. + - "comprehensive": Aims to detect more security findings at the cost of potential false positives. + (precise,comprehensive) (default "precise") --disable-node-collector When the flag is activated, the node-collector job will not be executed, thus skipping misconfiguration findings on the node. --download-db-only download/update vulnerability database but don't run a scan --download-java-db-only download/update Java index database but don't run a scan @@ -65,7 +70,7 @@ trivy kubernetes [flags] [CONTEXT] --include-kinds strings indicate the kinds included in scanning (example: node) --include-namespaces strings indicate the namespaces included in scanning (example: kube-system) --include-non-failures include successes and exceptions, available with '--scanners misconfig' - --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db:1") + --java-db-repository strings OCI repository(ies) to retrieve trivy-java-db in order of priority (default [ghcr.io/aquasecurity/trivy-java-db:1]) --k8s-version string specify k8s version to validate outdated api by it (example: 1.21.0) --kubeconfig string specify the kubeconfig file path to use --list-all-pkgs output all packages in the JSON report regardless of vulnerability diff --git a/docs/docs/references/configuration/cli/trivy_repository.md b/docs/docs/references/configuration/cli/trivy_repository.md index ba9aa4983d22..821923262dee 100644 --- a/docs/docs/references/configuration/cli/trivy_repository.md +++ b/docs/docs/references/configuration/cli/trivy_repository.md @@ -23,13 +23,18 @@ trivy repository [flags] (REPO_PATH | REPO_URL) --cache-ttl duration cache TTL when using redis as cache backend --cf-params strings specify paths to override the CloudFormation parameters files --check-namespaces strings Rego namespaces - --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:0") + --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:1") --commit string pass the commit hash to be scanned --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 --custom-headers strings custom headers in client mode - --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") + --db-repository strings OCI repository(ies) to retrieve trivy-db in order of priority (default [ghcr.io/aquasecurity/trivy-db:2]) --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages + --detection-priority string specify the detection priority: + - "precise": Prioritizes precise by minimizing false positives. + - "comprehensive": Aims to detect more security findings at the cost of potential false positives. + (precise,comprehensive) (default "precise") --download-db-only download/update vulnerability database but don't run a scan --download-java-db-only download/update Java index database but don't run a scan --enable-modules strings [EXPERIMENTAL] module names to enable @@ -51,7 +56,7 @@ trivy repository [flags] (REPO_PATH | REPO_URL) --include-deprecated-checks include deprecated checks --include-dev-deps include development dependencies in the report (supported: npm, yarn) --include-non-failures include successes and exceptions, available with '--scanners misconfig' - --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db:1") + --java-db-repository strings OCI repository(ies) to retrieve trivy-java-db in order of priority (default [ghcr.io/aquasecurity/trivy-java-db:1]) --license-confidence-level float specify license classifier's confidence level (default 0.9) --license-full eagerly look for licenses in source code headers and license files --list-all-pkgs output all packages in the JSON report regardless of vulnerability diff --git a/docs/docs/references/configuration/cli/trivy_rootfs.md b/docs/docs/references/configuration/cli/trivy_rootfs.md index 39ccef2dd07a..bf2575af23b9 100644 --- a/docs/docs/references/configuration/cli/trivy_rootfs.md +++ b/docs/docs/references/configuration/cli/trivy_rootfs.md @@ -26,12 +26,17 @@ trivy rootfs [flags] ROOTDIR --cache-ttl duration cache TTL when using redis as cache backend --cf-params strings specify paths to override the CloudFormation parameters files --check-namespaces strings Rego namespaces - --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:0") + --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:1") --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 --custom-headers strings custom headers in client mode - --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") + --db-repository strings OCI repository(ies) to retrieve trivy-db in order of priority (default [ghcr.io/aquasecurity/trivy-db:2]) --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages + --detection-priority string specify the detection priority: + - "precise": Prioritizes precise by minimizing false positives. + - "comprehensive": Aims to detect more security findings at the cost of potential false positives. + (precise,comprehensive) (default "precise") --download-db-only download/update vulnerability database but don't run a scan --download-java-db-only download/update Java index database but don't run a scan --enable-modules strings [EXPERIMENTAL] module names to enable @@ -53,7 +58,7 @@ trivy rootfs [flags] ROOTDIR --ignorefile string specify .trivyignore file (default ".trivyignore") --include-deprecated-checks include deprecated checks --include-non-failures include successes and exceptions, available with '--scanners misconfig' - --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db:1") + --java-db-repository strings OCI repository(ies) to retrieve trivy-java-db in order of priority (default [ghcr.io/aquasecurity/trivy-java-db:1]) --license-confidence-level float specify license classifier's confidence level (default 0.9) --license-full eagerly look for licenses in source code headers and license files --list-all-pkgs output all packages in the JSON report regardless of vulnerability diff --git a/docs/docs/references/configuration/cli/trivy_sbom.md b/docs/docs/references/configuration/cli/trivy_sbom.md index d6d1bf07e1c6..4eaedcf17aa5 100644 --- a/docs/docs/references/configuration/cli/trivy_sbom.md +++ b/docs/docs/references/configuration/cli/trivy_sbom.md @@ -20,50 +20,57 @@ trivy sbom [flags] SBOM_PATH ### Options ``` - --cache-backend string [EXPERIMENTAL] cache backend (e.g. redis://localhost:6379) (default "memory") - --cache-ttl duration cache TTL when using redis as cache backend - --compliance string compliance report to generate - --custom-headers strings custom headers in client mode - --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") - --download-db-only download/update vulnerability database but don't run a scan - --download-java-db-only download/update Java index database but don't run a scan - --exit-code int specify exit code when any security issues are found - --exit-on-eol int exit with the specified code when the OS reaches end of service/life - --file-patterns strings specify config file patterns - -f, --format string format (table,json,template,sarif,cyclonedx,spdx,spdx-json,github,cosign-vuln) (default "table") - -h, --help help for sbom - --ignore-policy string specify the Rego file path to evaluate each vulnerability - --ignore-status strings comma-separated list of vulnerability status to ignore (unknown,not_affected,affected,fixed,under_investigation,will_not_fix,fix_deferred,end_of_life) - --ignore-unfixed display only fixed vulnerabilities - --ignored-licenses strings specify a list of license to ignore - --ignorefile string specify .trivyignore file (default ".trivyignore") - --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db:1") - --list-all-pkgs output all packages in the JSON report regardless of vulnerability - --no-progress suppress progress bar - --offline-scan do not issue API requests to identify dependencies - -o, --output string output file name - --output-plugin-arg string [EXPERIMENTAL] output plugin arguments - --pkg-relationships strings list of package relationships (unknown,root,direct,indirect) (default [unknown,root,direct,indirect]) - --pkg-types strings list of package types (os,library) (default [os,library]) - --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 - --redis-tls enable redis TLS with public certificates, if using redis as cache backend - --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") - --sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) - --scanners strings comma-separated list of what security issues to detect (vuln,license) (default [vuln]) - --server string server address in client mode - -s, --severity strings severities of security issues to be displayed (UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL) (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 - -t, --template string output template - --token string for authentication in client/server mode - --token-header string specify a header name for token in client/server mode (default "Trivy-Token") - --vex strings [EXPERIMENTAL] VEX sources ("repo", "oci" or file path) + --cache-backend string [EXPERIMENTAL] cache backend (e.g. redis://localhost:6379) (default "memory") + --cache-ttl duration cache TTL when using redis as cache backend + --compliance string compliance report to generate + --custom-headers strings custom headers in client mode + --db-repository strings OCI repository(ies) to retrieve trivy-db in order of priority (default [ghcr.io/aquasecurity/trivy-db:2]) + --detection-priority string specify the detection priority: + - "precise": Prioritizes precise by minimizing false positives. + - "comprehensive": Aims to detect more security findings at the cost of potential false positives. + (precise,comprehensive) (default "precise") + --download-db-only download/update vulnerability database but don't run a scan + --download-java-db-only download/update Java index database but don't run a scan + --exit-code int specify exit code when any security issues are found + --exit-on-eol int exit with the specified code when the OS reaches end of service/life + --file-patterns strings specify config file patterns + -f, --format string format (table,json,template,sarif,cyclonedx,spdx,spdx-json,github,cosign-vuln) (default "table") + -h, --help help for sbom + --ignore-policy string specify the Rego file path to evaluate each vulnerability + --ignore-status strings comma-separated list of vulnerability status to ignore (unknown,not_affected,affected,fixed,under_investigation,will_not_fix,fix_deferred,end_of_life) + --ignore-unfixed display only fixed vulnerabilities + --ignored-licenses strings specify a list of license to ignore + --ignorefile string specify .trivyignore file (default ".trivyignore") + --java-db-repository strings OCI repository(ies) to retrieve trivy-java-db in order of priority (default [ghcr.io/aquasecurity/trivy-java-db:1]) + --list-all-pkgs output all packages in the JSON report regardless of vulnerability + --no-progress suppress progress bar + --offline-scan do not issue API requests to identify dependencies + -o, --output string output file name + --output-plugin-arg string [EXPERIMENTAL] output plugin arguments + --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. + --pkg-relationships strings list of package relationships (unknown,root,direct,indirect) (default [unknown,root,direct,indirect]) + --pkg-types strings list of package types (os,library) (default [os,library]) + --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 + --redis-tls enable redis TLS with public certificates, if using redis as cache backend + --registry-token string registry token + --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") + --sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) + --scanners strings comma-separated list of what security issues to detect (vuln,license) (default [vuln]) + --server string server address in client mode + -s, --severity strings severities of security issues to be displayed (UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL) (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 + -t, --template string output template + --token string for authentication in client/server mode + --token-header string specify a header name for token in client/server mode (default "Trivy-Token") + --username strings username. Comma-separated usernames allowed. + --vex strings [EXPERIMENTAL] VEX sources ("repo", "oci" or file path) ``` ### Options inherited from parent commands diff --git a/docs/docs/references/configuration/cli/trivy_server.md b/docs/docs/references/configuration/cli/trivy_server.md index 4291496e34f1..b7ada8f2c19b 100644 --- a/docs/docs/references/configuration/cli/trivy_server.md +++ b/docs/docs/references/configuration/cli/trivy_server.md @@ -22,7 +22,7 @@ trivy server [flags] ``` --cache-backend string [EXPERIMENTAL] cache backend (e.g. redis://localhost:6379) (default "fs") --cache-ttl duration cache TTL when using redis as cache backend - --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") + --db-repository strings OCI repository(ies) to retrieve trivy-db in order of priority (default [ghcr.io/aquasecurity/trivy-db:2]) --download-db-only download/update vulnerability database but don't run a scan --enable-modules strings [EXPERIMENTAL] module names to enable -h, --help help for server diff --git a/docs/docs/references/configuration/cli/trivy_vm.md b/docs/docs/references/configuration/cli/trivy_vm.md index 110cc0a3ecb7..14ce8b78c21a 100644 --- a/docs/docs/references/configuration/cli/trivy_vm.md +++ b/docs/docs/references/configuration/cli/trivy_vm.md @@ -23,11 +23,16 @@ trivy vm [flags] VM_IMAGE --aws-region string AWS region to scan --cache-backend string [EXPERIMENTAL] cache backend (e.g. redis://localhost:6379) (default "fs") --cache-ttl duration cache TTL when using redis as cache backend - --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:0") + --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:1") --compliance string compliance report to generate + --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 --custom-headers strings custom headers in client mode - --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") + --db-repository strings OCI repository(ies) to retrieve trivy-db in order of priority (default [ghcr.io/aquasecurity/trivy-db:2]) --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages + --detection-priority string specify the detection priority: + - "precise": Prioritizes precise by minimizing false positives. + - "comprehensive": Aims to detect more security findings at the cost of potential false positives. + (precise,comprehensive) (default "precise") --download-db-only download/update vulnerability database but don't run a scan --download-java-db-only download/update Java index database but don't run a scan --enable-modules strings [EXPERIMENTAL] module names to enable @@ -47,7 +52,7 @@ trivy vm [flags] VM_IMAGE --ignore-unfixed display only fixed vulnerabilities --ignorefile string specify .trivyignore file (default ".trivyignore") --include-non-failures include successes and exceptions, available with '--scanners misconfig' - --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db:1") + --java-db-repository strings OCI repository(ies) to retrieve trivy-java-db in order of priority (default [ghcr.io/aquasecurity/trivy-java-db:1]) --list-all-pkgs output all packages in the JSON report regardless of vulnerability --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) --module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules") diff --git a/docs/docs/references/configuration/config-file.md b/docs/docs/references/configuration/config-file.md index 535a1b9e657e..9bc9d4351360 100644 --- a/docs/docs/references/configuration/config-file.md +++ b/docs/docs/references/configuration/config-file.md @@ -5,615 +5,615 @@ The config path can be overridden by the `--config` flag. An example is [here][example]. -## Global Options +These samples contain default values for flags. +## Global options ```yaml -# Same as '--quiet' -# Default is false -quiet: false +cache: + # Same as '--cache-dir' + dir: "/path/to/cache" # Same as '--debug' -# Default is false debug: false # Same as '--insecure' -# Default is false insecure: false +# Same as '--quiet' +quiet: false + # Same as '--timeout' -# Default is '5m' -timeout: 10m +timeout: 5m0s -# Same as '--cache-dir' -# Default is your system cache dir -cache: - dir: $HOME/.cache/trivy ``` - -## Report Options +## Cache options ```yaml -# Same as '--format' -# Default is 'table' -format: table +cache: + # Same as '--cache-backend' + backend: "fs" -# Same as '--report' (available with 'trivy k8s') -# Default is all -report: all + redis: + # Same as '--redis-ca' + ca: "" -# Same as '--template' -# Default is empty -template: + # Same as '--redis-cert' + cert: "" -# Same as '--dependency-tree' -# Default is false -dependency-tree: false + # Same as '--redis-key' + key: "" -# Same as '--list-all-pkgs' -# Default is false -list-all-pkgs: false + # Same as '--redis-tls' + tls: false -# Same as '--ignorefile' -# Default is '.trivyignore' -ignorefile: .trivyignore + # Same as '--cache-ttl' + ttl: 0s -# Same as '--ignore-policy' -# Default is empty -ignore-policy: +``` +## Clean options -# Same as '--exit-code' -# Default is 0 -exit-code: 0 +```yaml +clean: + # Same as '--all' + all: false -# Same as '--exit-on-eol' -# Default is 0 -exit-on-eol: 0 + # Same as '--checks-bundle' + checks-bundle: false -# Same as '--output' -# Default is empty (stdout) -output: + # Same as '--java-db' + java-db: false -# Same as '--severity' -# Default is all severities -severity: - - UNKNOWN - - LOW - - MEDIUM - - HIGH - - CRITICAL - -# Same as '--pkg-types' -# Default is 'os,library' -pkg-types: - - os - - library - + # Same as '--scan-cache' + scan-cache: false -scan: - # Same as '--compliance' - # Default is empty - compliance: + # Same as '--vex-repo' + vex-repo: false - # Same as '--show-suppressed' - # Default is false - show-suppressed: false -``` + # Same as '--vuln-db' + vuln-db: false -## Scan Options -Available in client/server mode +``` +## Client/Server options ```yaml -scan: - # Same as '--file-patterns' - # Default is empty - file-patterns: - - - - # Same as '--skip-dirs' - # Default is empty - skip-dirs: - - usr/local/ - - etc/ - - # Same as '--skip-files' - # Default is empty - skip-files: - - package-dev.json +server: + # Same as '--server' + addr: "" - # Same as '--offline-scan' - # Default is false - offline: false + # Same as '--custom-headers' + custom-headers: [] - # Same as '--scanners' - # Default depends on subcommand - scanners: - - vuln - - misconfig - - secret - - license - - - # Same as '--parallel' - # Default is 5 - parallel: 1 + # Same as '--listen' + listen: "localhost:4954" - # Same as '--sbom-sources' - # Default is empty - sbom-sources: - - oci - - rekor + # Same as '--token' + token: "" - # Same as '--rekor-url' - # Default is 'https://rekor.sigstore.dev' - rekor-url: https://rekor.sigstore.dev + # Same as '--token-header' + token-header: "Trivy-Token" - # Same as '--include-dev-deps' - # Default is false - include-dev-deps: false ``` - -## Cache Options +## DB options ```yaml -cache: - # Same as '--cache-backend' - # Default is 'fs' - backend: 'fs' - - # Same as '--cache-ttl' - # Default is 0 (no ttl) - ttl: 0 - - # Redis options - redis: - # Same as '--redis-tls' - # Default is false - tls: - # Same as '--redis-ca' - # Default is empty - ca: +db: + # Same as '--download-java-db-only' + download-java-only: false - # Same as '--redis-cert' - # Default is empty - cert: + # Same as '--download-db-only' + download-only: false - # Same as '--redis-key' - # Default is empty - key: -``` + # Same as '--java-db-repository' + java-repository: + - ghcr.io/aquasecurity/trivy-java-db:1 -## DB Options + # Same as '--skip-java-db-update' + java-skip-update: false -```yaml -db: # Same as '--no-progress' - # Default is false no-progress: false - - # Same as '--skip-db-update' - # Default is false - skip-update: false # Same as '--db-repository' - # Default is 'ghcr.io/aquasecurity/trivy-db:2' - repository: ghcr.io/aquasecurity/trivy-db:2 + repository: + - ghcr.io/aquasecurity/trivy-db:2 - # Same as '--skip-java-db-update' - # Default is false - java-skip-update: false + # Same as '--skip-db-update' + skip-update: false - # Same as '--java-db-repository' - # Default is 'ghcr.io/aquasecurity/trivy-java-db:1' - java-repository: ghcr.io/aquasecurity/trivy-java-db:1 ``` - -## Registry Options +## Image options ```yaml -registry: - # Same as '--username' - # Default is empty - username: +image: + docker: + # Same as '--docker-host' + host: "" - # Same as '--password' - # Default is empty - password: - - # Same as '--registry-token' - # Default is empty - registry-token: -``` + # Same as '--image-config-scanners' + image-config-scanners: [] -## Image Options -Available with container image scanning + # Same as '--input' + input: "" -```yaml -image: - # Same as '--input' (available with 'trivy image') - # Default is empty - input: + # Same as '--platform' + platform: "" + + podman: + # Same as '--podman-host' + host: "" # Same as '--removed-pkgs' - # Default is false removed-pkgs: false - - # Same as '--platform' - # Default is empty - platform: # Same as '--image-src' - # Default is 'docker,containerd,podman,remote' source: - - podman - - docker - - # Same as '--image-config-scanners' - # Default is empty - image-config-scanners: - - misconfig - - secret - - docker: - # Same as '--docker-host' - # Default is empty - host: - - podman: - # Same as '--podman-host' - # Default is empty - host: -``` + - docker + - containerd + - podman + - remote -## Vulnerability Options -Available with vulnerability scanning +``` +## Kubernetes options ```yaml -vulnerability: - # Same as '--ignore-unfixed' - # Default is false - ignore-unfixed: false +kubernetes: + # Same as '--burst' + burst: 10 - # Same as '--ignore-unfixed' - # Default is empty - ignore-status: - - end_of_life + # Same as '--disable-node-collector' + disableNodeCollector: false - # Same as '--vex' - # Default is empty - vex: - - path/to/vex/file - - repo + exclude: + # Same as '--exclude-nodes' + nodes: [] - # Same as '--skip-vex-repo-update' - # Default is false - skip-vex-repo-update: true -``` + # Same as '--exclude-owned' + owned: false -## License Options -Available with license scanning + # Same as '--exclude-kinds' + excludeKinds: [] -```yaml -license: - # Same as '--license-full' - # Default is false - full: false + # Same as '--exclude-namespaces' + excludeNamespaces: [] - # Same as '--ignored-licenses' - # Default is empty - ignored: - - MPL-2.0 - - MIT + # Same as '--include-kinds' + includeKinds: [] + + # Same as '--include-namespaces' + includeNamespaces: [] + + # Same as '--k8s-version' + k8s-version: "" + + # Same as '--kubeconfig' + kubeconfig: "" + + node-collector: + # Same as '--node-collector-imageref' + imageref: "ghcr.io/aquasecurity/node-collector:0.3.1" + + # Same as '--node-collector-namespace' + namespace: "trivy-temp" + + # Same as '--qps' + qps: 5 + + # Same as '--skip-images' + skipImages: false + + # Same as '--tolerations' + tolerations: [] +``` +## License options + +```yaml +license: # Same as '--license-confidence-level' - # Default is 0.9 confidenceLevel: 0.9 - # Set list of forbidden licenses - # Default is https://github.com/aquasecurity/trivy/blob/164b025413c5fb9c6759491e9a306b46b869be93/pkg/licensing/category.go#L171 forbidden: - - AGPL-1.0 - - AGPL-3.0 + - AGPL-1.0 + - AGPL-3.0 + - CC-BY-NC-1.0 + - CC-BY-NC-2.0 + - CC-BY-NC-2.5 + - CC-BY-NC-3.0 + - CC-BY-NC-4.0 + - CC-BY-NC-ND-1.0 + - CC-BY-NC-ND-2.0 + - CC-BY-NC-ND-2.5 + - CC-BY-NC-ND-3.0 + - CC-BY-NC-ND-4.0 + - CC-BY-NC-SA-1.0 + - CC-BY-NC-SA-2.0 + - CC-BY-NC-SA-2.5 + - CC-BY-NC-SA-3.0 + - CC-BY-NC-SA-4.0 + - Commons-Clause + - Facebook-2-Clause + - Facebook-3-Clause + - Facebook-Examples + - WTFPL - # Set list of restricted licenses - # Default is https://github.com/aquasecurity/trivy/blob/164b025413c5fb9c6759491e9a306b46b869be93/pkg/licensing/category.go#L199 - restricted: - - AGPL-1.0 - - AGPL-3.0 + # Same as '--license-full' + full: false - # Set list of reciprocal licenses - # Default is https://github.com/aquasecurity/trivy/blob/164b025413c5fb9c6759491e9a306b46b869be93/pkg/licensing/category.go#L238 - reciprocal: - - AGPL-1.0 - - AGPL-3.0 + # Same as '--ignored-licenses' + ignored: [] - # Set list of notice licenses - # Default is https://github.com/aquasecurity/trivy/blob/164b025413c5fb9c6759491e9a306b46b869be93/pkg/licensing/category.go#L260 notice: - - AGPL-1.0 - - AGPL-3.0 + - AFL-1.1 + - AFL-1.2 + - AFL-2.0 + - AFL-2.1 + - AFL-3.0 + - Apache-1.0 + - Apache-1.1 + - Apache-2.0 + - Artistic-1.0-cl8 + - Artistic-1.0-Perl + - Artistic-1.0 + - Artistic-2.0 + - BSL-1.0 + - BSD-2-Clause-FreeBSD + - BSD-2-Clause-NetBSD + - BSD-2-Clause + - BSD-3-Clause-Attribution + - BSD-3-Clause-Clear + - BSD-3-Clause-LBNL + - BSD-3-Clause + - BSD-4-Clause + - BSD-4-Clause-UC + - BSD-Protection + - CC-BY-1.0 + - CC-BY-2.0 + - CC-BY-2.5 + - CC-BY-3.0 + - CC-BY-4.0 + - FTL + - ISC + - ImageMagick + - Libpng + - Lil-1.0 + - Linux-OpenIB + - LPL-1.02 + - LPL-1.0 + - MS-PL + - MIT + - NCSA + - OpenSSL + - PHP-3.01 + - PHP-3.0 + - PIL + - Python-2.0 + - Python-2.0-complete + - PostgreSQL + - SGI-B-1.0 + - SGI-B-1.1 + - SGI-B-2.0 + - Unicode-DFS-2015 + - Unicode-DFS-2016 + - Unicode-TOU + - UPL-1.0 + - W3C-19980720 + - W3C-20150513 + - W3C + - X11 + - Xnet + - Zend-2.0 + - zlib-acknowledgement + - Zlib + - ZPL-1.1 + - ZPL-2.0 + - ZPL-2.1 + + permissive: [] - # Set list of permissive licenses - # Default is empty - permissive: - - AGPL-1.0 - - AGPL-3.0 + reciprocal: + - APSL-1.0 + - APSL-1.1 + - APSL-1.2 + - APSL-2.0 + - CDDL-1.0 + - CDDL-1.1 + - CPL-1.0 + - EPL-1.0 + - EPL-2.0 + - FreeImage + - IPL-1.0 + - MPL-1.0 + - MPL-1.1 + - MPL-2.0 + - Ruby + + restricted: + - BCL + - CC-BY-ND-1.0 + - CC-BY-ND-2.0 + - CC-BY-ND-2.5 + - CC-BY-ND-3.0 + - CC-BY-ND-4.0 + - CC-BY-SA-1.0 + - CC-BY-SA-2.0 + - CC-BY-SA-2.5 + - CC-BY-SA-3.0 + - CC-BY-SA-4.0 + - GPL-1.0 + - GPL-2.0 + - GPL-2.0-with-autoconf-exception + - GPL-2.0-with-bison-exception + - GPL-2.0-with-classpath-exception + - GPL-2.0-with-font-exception + - GPL-2.0-with-GCC-exception + - GPL-3.0 + - GPL-3.0-with-autoconf-exception + - GPL-3.0-with-GCC-exception + - LGPL-2.0 + - LGPL-2.1 + - LGPL-3.0 + - NPL-1.0 + - NPL-1.1 + - OSL-1.0 + - OSL-1.1 + - OSL-2.0 + - OSL-2.1 + - OSL-3.0 + - QPL-1.0 + - Sleepycat - # Set list of unencumbered licenses - # Default is https://github.com/aquasecurity/trivy/blob/164b025413c5fb9c6759491e9a306b46b869be93/pkg/licensing/category.go#L334 unencumbered: - - AGPL-1.0 - - AGPL-3.0 -``` + - CC0-1.0 + - Unlicense + - 0BSD -## Secret Options -Available with secret scanning +``` +## Misconfiguration options ```yaml -secret: - # Same as '--secret-config' - # Default is 'trivy-secret.yaml' - config: config/trivy/secret.yaml -``` +misconfiguration: + # Same as '--checks-bundle-repository' + checks-bundle-repository: "ghcr.io/aquasecurity/trivy-checks:1" -## Rego Options + cloudformation: + # Same as '--cf-params' + params: [] -```yaml -rego: - # Same as '--trace' - # Default is false - trace: false + # Same as '--config-file-schemas' + config-file-schemas: [] - # Same as '--skip-check-update' - # Default is false - skip-check-update: false + helm: + # Same as '--helm-api-versions' + api-versions: [] - # Same as '--config-policy' - # Default is empty - policy: - - policy/repository - - policy/custom - - policy/some-policy.rego + # Same as '--helm-kube-version' + kube-version: "" - # Same as '--config-data' - # Default is empty - data: - - data/ - - # Same as '--policy-namespaces' - # Default is empty - namespaces: - - opa.examples - - users -``` + # Same as '--helm-set' + set: [] -## Misconfiguration Options -Available with misconfiguration scanning + # Same as '--helm-set-file' + set-file: [] + + # Same as '--helm-set-string' + set-string: [] + + # Same as '--helm-values' + values: [] -```yaml -misconfiguration: # Same as '--include-non-failures' - # Default is false include-non-failures: false - - # Same as '--include-deprecated-checks' - # Default is false - include-deprecated-checks: false - # Same as '--check-bundle-repository' and '--policy-bundle-repository' - # Default is 'ghcr.io/aquasecurity/trivy-checks:0' - check-bundle-repository: ghcr.io/aquasecurity/trivy-checks:0 - - # Same as '--miconfig-scanners' - # Default is all scanners + # Same as '--misconfig-scanners' scanners: - - dockerfile - - terraform + - azure-arm + - cloudformation + - dockerfile + - helm + - kubernetes + - terraform + - terraformplan-json + - terraformplan-snapshot - # helm value override configurations - helm: - # set individual values - set: - - securityContext.runAsUser=10001 + terraform: + # Same as '--tf-exclude-downloaded-modules' + exclude-downloaded-modules: false - # set values with file - values: - - overrides.yaml + # Same as '--tf-vars' + vars: [] - # set specific values from specific files - set-file: - - image=dev-overrides.yaml +``` +## Module options - # set as string and preserve type - set-string: - - name=true +```yaml +module: + # Same as '--module-dir' + dir: "$HOME/.trivy/modules" - # Available API versions used for Capabilities.APIVersions. This flag is the same as the api-versions flag of the helm template command. - api-versions: - - policy/v1/PodDisruptionBudget - - apps/v1/Deployment + # Same as '--enable-modules' + enable-modules: [] - # Kubernetes version used for Capabilities.KubeVersion. This flag is the same as the kube-version flag of the helm template command. - kube-version: "v1.21.0" +``` +## Package options - # terraform tfvars overrrides - terraform: - vars: - - dev-terraform.tfvars - - common-terraform.tfvars - - # Same as '--tf-exclude-downloaded-modules' - # Default is false - exclude-downloaded-modules: false +```yaml +pkg: + # Same as '--include-dev-deps' + include-dev-deps: false + + # Same as '--pkg-relationships' + relationships: + - unknown + - root + - direct + - indirect + + # Same as '--pkg-types' + types: + - os + - library - # Same as '--cf-params' - # Default is false - cloudformation: - params: - - params.json ``` +## Registry options -## Kubernetes Options -Available with Kubernetes scanning +```yaml +registry: + # Same as '--password' + password: [] + + # Same as '--registry-token' + token: "" + + # Same as '--username' + username: [] + +``` +## Rego options ```yaml -kubernetes: - # Same as '--context' - # Default is empty - context: +rego: + # Same as '--config-check' + check: [] - # Same as '--namespace' - # Default is empty - namespace: + # Same as '--config-data' + data: [] - # Same as '--kubeconfig' - # Default is empty - kubeconfig: ~/.kube/config2 + # Same as '--include-deprecated-checks' + include-deprecated-checks: false - # Same as '--components' - # Default is 'workload,infra' - components: - - workload - - infra + # Same as '--check-namespaces' + namespaces: [] - # Same as '--k8s-version' - # Default is empty - k8s-version: 1.21.0 + # Same as '--skip-check-update' + skip-check-update: false - # Same as '--tolerations' - # Default is empty - tolerations: - - key1=value1:NoExecute - - key2=value2:NoSchedule + # Same as '--trace' + trace: false - # Same as '--all-namespaces' - # Default is false - all-namespaces: false +``` +## Report options - node-collector: - # Same as '--node-collector-namespace' - # Default is 'trivy-temp' - namespace: ~/.kube/config2 +```yaml +# Same as '--dependency-tree' +dependency-tree: false - # Same as '--node-collector-imageref' - # Default is 'ghcr.io/aquasecurity/node-collector:0.0.9' - imageref: ghcr.io/aquasecurity/node-collector:0.0.9 +# Same as '--exit-code' +exit-code: 0 - exclude: - # Same as '--exclude-owned' - # Default is false - owned: true +# Same as '--exit-on-eol' +exit-on-eol: 0 - # Same as '--exclude-nodes' - # Default is empty - nodes: - - kubernetes.io/arch:arm64 - - team:dev +# Same as '--format' +format: "table" - # Same as '--qps' - # Default is 5.0 - qps: 5.0 +# Same as '--ignore-policy' +ignore-policy: "" - # Same as '--burst' - # Default is 10 - burst: 10 -``` +# Same as '--ignorefile' +ignorefile: ".trivyignore" -## Repository Options -Available with git repository scanning (`trivy repo`) +# Same as '--list-all-pkgs' +list-all-pkgs: false + +# Same as '--output' +output: "" + +# Same as '--output-plugin-arg' +output-plugin-arg: "" + +# Same as '--report' +report: "all" + +scan: + # Same as '--compliance' + compliance: "" + + # Same as '--show-suppressed' + show-suppressed: false + +# Same as '--severity' +severity: + - UNKNOWN + - LOW + - MEDIUM + - HIGH + - CRITICAL + +# Same as '--template' +template: "" + +``` +## Repository options ```yaml repository: # Same as '--branch' - # Default is empty - branch: + branch: "" # Same as '--commit' - # Default is empty - commit: + commit: "" # Same as '--tag' - # Default is empty - tag: -``` + tag: "" -## Client/Server Options -Available in client/server mode +``` +## Scan options ```yaml -server: - # Same as '--server' (available in client mode) - # Default is empty - addr: http://localhost:4954 +scan: + # Same as '--detection-priority' + detection-priority: "precise" - # Same as '--token' - # Default is empty - token: "something-secret" + # Same as '--file-patterns' + file-patterns: [] - # Same as '--token-header' - # Default is 'Trivy-Token' - token-header: 'My-Token-Header' + # Same as '--offline-scan' + offline: false - # Same as '--custom-headers' - # Default is empty - custom-headers: - - scanner: trivy - - x-api-token: xxx - - # Same as '--listen' (available in server mode) - # Default is 'localhost:4954' - listen: 0.0.0.0:10000 -``` + # Same as '--parallel' + parallel: 5 -## Cloud Options + # Same as '--rekor-url' + rekor-url: "https://rekor.sigstore.dev" -Available for cloud scanning (currently only `trivy aws`) + # Same as '--sbom-sources' + sbom-sources: [] -```yaml -cloud: - # whether to force a cache update for every scan - update-cache: false - - # how old cached results can be before being invalidated - max-cache-age: 24h - - # aws-specific cloud settings - aws: - # the aws region to use - region: us-east-1 - - # the aws endpoint to use (not required for general use) - endpoint: https://my.custom.aws.endpoint - - # the aws account to use (this will be determined from your environment when not set) - account: 123456789012 - - # the aws specific services - service: - - s3 - - ec2 - - # the aws specific arn - arn: arn:aws:s3:::example-bucket - - # skip the aws specific services - skip-service: - - s3 - - ec2 -``` + # Same as '--scanners' + scanners: + - vuln + - secret + + # Same as '--skip-dirs' + skip-dirs: [] + + # Same as '--skip-files' + skip-files: [] -## Module Options -Available for modules +``` +## Secret options ```yaml -module: - # Same as '--module-dir' - # Default is '$HOME/.trivy/modules' - dir: $HOME/.trivy/modules +secret: + # Same as '--secret-config' + config: "trivy-secret.yaml" - # Same as '--enable-modules' - # Default is empty - enable-modules: - - trivy-module-spring4shell - - trivy-module-wordpress ``` +## Vulnerability options + +```yaml +vulnerability: + # Same as '--ignore-status' + ignore-status: [] -[example]: https://github.com/aquasecurity/trivy/tree/{{ git.tag }}/examples/trivy-conf/trivy.yaml + # Same as '--ignore-unfixed' + ignore-unfixed: false + + # Same as '--skip-vex-repo-update' + skip-vex-repo-update: false + + # Same as '--vex' + vex: [] + +``` +[example]: https://github.com/aquasecurity/trivy/tree/{{ git.tag }}/examples/trivy-conf/trivy.yaml \ No newline at end of file diff --git a/docs/docs/references/modes/client-server.md b/docs/docs/references/modes/client-server.md index 518fe45efc8a..7aa90abf0b22 100644 --- a/docs/docs/references/modes/client-server.md +++ b/docs/docs/references/modes/client-server.md @@ -2,9 +2,22 @@ Trivy has client/server mode. Trivy server has vulnerability database and Trivy client doesn't have to download vulnerability database. It is useful if you want to scan images or files at multiple locations and do not want to download the database at every location. -| Client/Server Mode | Image | Rootfs | Filesystem | Repository | Config | AWS | K8s | -|:---------------------:|:-----:|:------:|:----------:|:----------:|:------:|:---:|:---:| -| Supported | ✅ | ✅ | ✅ | ✅ | ✅ | X | X | +| Client/Server Mode | Image | Rootfs | Filesystem | Repository | Config | K8s | +|:------------------:|:-----:|:------:|:----------:|:----------:|:------:|:---:| +| Supported | ✅ | ✅ | ✅ | ✅ | - | - | + +Some scanners run on the client side, even in client/server mode. + +| Scanner | Run on Client or Server | +|:----------------:|:-----------------------:| +| Vulnerability | Server | +| Misconfiguration | Client[^1] | +| Secret | Client[^2] | +| License | Server | + +!!! note + Scanning of misconfigurations and licenses is performed on the client side (as in standalone mode). + Otherwise, the client would need to send files to the server that may contain sensitive information. ## Server At first, you need to launch Trivy server. It downloads vulnerability database automatically and continue to fetch the latest DB in the background. @@ -338,3 +351,5 @@ Returns the `200 OK` status if the request was successful. ![architecture](../../../imgs/client-server.png) +[^1]: The checks bundle is also downloaded on the client side. +[^2]: The scan result with masked secrets is sent to the server \ No newline at end of file diff --git a/docs/docs/references/troubleshooting.md b/docs/docs/references/troubleshooting.md index d271882c5ecb..2c9a74a0e89a 100644 --- a/docs/docs/references/troubleshooting.md +++ b/docs/docs/references/troubleshooting.md @@ -203,10 +203,7 @@ Trivy v0.23.0 or later requires Trivy DB v2. Please update your local database o !!! error FATAL failed to download vulnerability DB -If trivy is running behind corporate firewall, you have to add the following urls to your allowlist. - -- ghcr.io -- pkg-containers.githubusercontent.com +If Trivy is running behind corporate firewall, refer to the necessary connectivity requirements as described [here][network]. ### Denied @@ -271,4 +268,5 @@ $ trivy clean --all ``` [air-gapped]: ../advanced/air-gap.md +[network]: ../advanced/air-gap.md#network-requirements [redis-cache]: ../../vulnerability/examples/cache/#cache-backend diff --git a/docs/docs/scanner/misconfiguration/check/builtin.md b/docs/docs/scanner/misconfiguration/check/builtin.md index 8b513f47607a..c4ca18e79006 100644 --- a/docs/docs/scanner/misconfiguration/check/builtin.md +++ b/docs/docs/scanner/misconfiguration/check/builtin.md @@ -1,21 +1,20 @@ # Built-in Checks -## Check Sources -Built-in checks are mainly written in [Rego][rego] and Go. -Those checks are managed under [trivy-checks repository][trivy-checks]. +## Checks Sources +Trivy has an extensive library of misconfiguration checks that is maintained at . +Trivy checks are mainly written in [Rego][rego], while some checks are written in Go. See [here](../../../coverage/iac/index.md) for the list of supported config types. -For suggestions or issues regarding policy content, please open an issue under the [trivy-checks][trivy-checks] repository. +## Checks Bundle +When performing a misconfiguration scan, Trivy will automatically download the relevant Checks bundle. The bundle is cached locally and Trivy will reuse it for subsequent scans on the same machine. Trivy takes care of updating the cache automatically, so normally users can be oblivious to it. -## Check Distribution -Trivy checks are distributed as an OPA bundle on [GitHub Container Registry][ghcr] (GHCR). -When misconfiguration detection is enabled, Trivy pulls the OPA bundle from GHCR as an OCI artifact and stores it in the cache. -Those checks are then loaded into Trivy OPA engine and used for detecting misconfigurations. -If Trivy is unable to pull down newer checks, it will use the embedded set of checks as a fallback. This is also the case in air-gap environments where `--skip-policy-update` might be passed. - -## Update Interval +## Checks Distribution +Trivy checks are distributed as an [OPA bundle](opa-bundle) hosted in the following GitHub Container Registry: . Trivy checks for updates to OPA bundle on GHCR every 24 hours and pulls it if there are any updates. +### External connectivity +Trivy needs to connect to the internet to download the bundle. If you are running Trivy in an air-gapped environment, or an tightly controlled network, please refer to the [Advanced Network Scenarios document](../../../advanced/air-gap.md). +The Checks bundle is also embedded in the Trivy binary (at build time), and will be used as a fallback if Trivy is unable to download the bundle. This means that you can still scan for misconfigurations in an air-gapped environment using the Checks from the time of the Trivy release you are using. + [rego]: https://www.openpolicyagent.org/docs/latest/policy-language/ -[trivy-checks]: https://github.com/aquasecurity/trivy-checks -[ghcr]: https://github.com/aquasecurity/trivy-checks/pkgs/container/trivy-checks \ No newline at end of file +[opa-bundle]: https://www.openpolicyagent.org/docs/latest/management-bundles/ diff --git a/docs/docs/scanner/misconfiguration/custom/data.md b/docs/docs/scanner/misconfiguration/custom/data.md index 51af206b4c63..2ccd9f1a2bfd 100644 --- a/docs/docs/scanner/misconfiguration/custom/data.md +++ b/docs/docs/scanner/misconfiguration/custom/data.md @@ -1,15 +1,14 @@ # Custom Data -Custom checks may require additional data in order to determine an answer. +Custom checks may require additional data in order to make a resolution. You can pass arbitrary data files to Trivy to be used when evaluating rego checks using the `--data` flag. +Trivy recursively searches the specified data paths for JSON (`*.json`) and YAML (`*.yaml`) files. -For example, an allowed list of resources that can be created. -Instead of hardcoding this information inside your policy, Trivy allows passing paths to data files with the `--data` flag. +For example, consider an allowed list of resources that can be created. +Instead of hardcoding this information inside your policy, you can maintain the list in a separate file. -Given the following yaml file: +Example data file: -```bash -$ cd examples/misconf/custom-data -$ cat data/ports.yaml [~/src/github.com/aquasecurity/trivy/examples/misconf/custom-data] +```yaml services: ports: - "20" @@ -19,7 +18,7 @@ services: - "23/tcp" ``` -This can be imported into your policy: +Example usage in a Rego check: ```rego import data.services @@ -27,9 +26,8 @@ import data.services ports := services.ports ``` -Then, you need to pass data paths through `--data` option. -Trivy recursively searches the specified paths for JSON (`*.json`) and YAML (`*.yaml`) files. +Example loading the data file: ```bash -$ trivy conf --policy ./policy --data data --namespaces user ./configs -``` \ No newline at end of file +trivy config --config-check ./checks --data ./data --namespaces user ./configs +``` diff --git a/docs/docs/scanner/misconfiguration/custom/debug.md b/docs/docs/scanner/misconfiguration/custom/debug.md index 751e43633efc..54ec9fd65273 100644 --- a/docs/docs/scanner/misconfiguration/custom/debug.md +++ b/docs/docs/scanner/misconfiguration/custom/debug.md @@ -7,7 +7,7 @@ This will output a large trace from Open Policy Agent like the following: Only failed checks show traces. If you want to debug a passed check, you need to make it fail on purpose. ```shell -$ trivy conf --trace configs/ +$ trivy config --trace configs/ 2022-05-16T13:47:58.853+0100 INFO Detected config files: 1 Dockerfile (dockerfile) diff --git a/docs/docs/scanner/misconfiguration/custom/index.md b/docs/docs/scanner/misconfiguration/custom/index.md index 925b72cedf09..9598089b8562 100644 --- a/docs/docs/scanner/misconfiguration/custom/index.md +++ b/docs/docs/scanner/misconfiguration/custom/index.md @@ -2,10 +2,10 @@ ## Overview You can write custom checks in [Rego][rego]. -Once you finish writing custom checks, you can pass the policy files or the directory where those policies are stored with `--policy` option. +Once you finish writing custom checks, you can pass the check files or the directory where those checks are stored with --config-check` option. ``` bash -trivy conf --policy /path/to/policy.rego --policy /path/to/custom_policies --namespaces user /path/to/config_dir +trivy config --config-check /path/to/policy.rego --config-check /path/to/custom_checks --namespaces user /path/to/config_dir ``` As for `--namespaces` option, the detail is described as below. @@ -93,7 +93,7 @@ By default, only `builtin.*` packages will be evaluated. If you define custom packages, you have to specify the package prefix via `--namespaces` option. By default, Trivy only runs in its own namespace, unless specified by the user. Note that the custom namespace does not have to be `user` as in this example. It could be anything user-defined. ``` bash -trivy conf --policy /path/to/custom_policies --namespaces user /path/to/config_dir +trivy config --config-check /path/to/custom_checks --namespaces user /path/to/config_dir ``` In this case, `user.*` will be evaluated. @@ -135,7 +135,7 @@ correct and do not reference incorrect properties/values. #### custom.avd_id and custom.id -The AVD_ID can be used to link the check to the Aqua Vulnerability Database (AVD) entry. For example, the `avd_id` `AVD-AWS-0176` is the ID of the check in the [AWS Vulnerability Database](https://avd.aquasec.com/). If you are [contributing your check to trivy-policies](../../../../community/contribute/checks/overview.md), you need to generate an ID using `make id` in the [trivy-checks](https://github.com/aquasecurity/trivy-checks) repository. The output of the command will provide you the next free IDs for the different providers in Trivy. +The AVD_ID can be used to link the check to the Aqua Vulnerability Database (AVD) entry. For example, the `avd_id` `AVD-AWS-0176` is the ID of the check in the [AWS Vulnerability Database](https://avd.aquasec.com/). If you are [contributing your check to trivy-checks](../../../../community/contribute/checks/overview.md), you need to generate an ID using `make id` in the [trivy-checks](https://github.com/aquasecurity/trivy-checks) repository. The output of the command will provide you the next free IDs for the different providers in Trivy. The ID is based on the AVD_ID. For instance if the `avd_id` is `AVD-AWS-0176`, the ID is `ID0176`. diff --git a/docs/docs/scanner/misconfiguration/custom/schema.md b/docs/docs/scanner/misconfiguration/custom/schema.md index 612025d38866..7b305cc10dbf 100644 --- a/docs/docs/scanner/misconfiguration/custom/schema.md +++ b/docs/docs/scanner/misconfiguration/custom/schema.md @@ -1,7 +1,7 @@ # Input Schema ## Overview -Policies can be defined with custom schemas that allow inputs to be verified against them. Adding a policy schema +Checks can be defined with custom schemas that allow inputs to be verified against them. Adding a policy 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) diff --git a/docs/docs/scanner/misconfiguration/index.md b/docs/docs/scanner/misconfiguration/index.md index 701d469d658f..368b4522b433 100644 --- a/docs/docs/scanner/misconfiguration/index.md +++ b/docs/docs/scanner/misconfiguration/index.md @@ -101,13 +101,13 @@ For example, the following example holds IaC files for Terraform, CloudFormation ``` bash $ ls iac/ Dockerfile deployment.yaml main.tf mysql-8.8.26.tar -$ trivy conf --severity HIGH,CRITICAL ./iac +$ trivy config --severity HIGH,CRITICAL ./iac ```
Result -``` +```bash 2022-06-06T11:01:21.142+0100 INFO Detected config files: 8 Dockerfile (dockerfile) @@ -315,6 +315,9 @@ Failures: 2 (MEDIUM: 2, HIGH: 0, CRITICAL: 0) This section describes misconfiguration-specific configuration. Other common options are documented [here](../../configuration/index.md). +### External connectivity +Trivy needs to connect to the internet to download the checks bundle. If you are running Trivy in an air-gapped environment, or an tightly controlled network, please refer to the [Advanced Network Scenarios document](../../advanced/air-gap.md). + ### 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. @@ -326,19 +329,86 @@ trivy config --misconfig-scanners=terraform,dockerfile . Will only scan for misconfigurations that pertain to Terraform and Dockerfiles. -### Passing custom checks -You can pass policy files or directories including your custom checks through `--policy` option. +### 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 -cd examplex/misconf/ -trivy conf --policy custom-policy/policy --policy combine/policy --policy policy.rego --namespaces user misconf/mixed +trivy config --config-check custom-policy/policy --config-check combine/policy --config-check policy.rego --namespaces user myapp ``` -For more details, see [Custom Checks](./custom/index.md). +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 +``` + + +### 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`. See [Enabling a subset of misconfiguration scanners](#enabling-a-subset-of-misconfiguration-scanners) for more information. 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, EXCEPTIONS: 0) +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 -You also need to specify `--namespaces` option. + 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. + ### Passing custom data You can pass directories including your custom data through `--data` option. @@ -346,7 +416,7 @@ This can be repeated for specifying multiple directories. ```bash cd examples/misconf/custom-data -trivy conf --policy ./policy --data ./data --namespaces user ./configs +trivy config --config-check ./my-check --data ./data --namespaces user ./configs ``` For more details, see [Custom Data](./custom/data.md). @@ -357,15 +427,15 @@ If you want to evaluate custom checks in other packages, you have to specify pac This can be repeated for specifying multiple packages. ``` bash -trivy conf --policy ./policy --namespaces main --namespaces user ./configs +trivy config --config-check ./my-check --namespaces main --namespaces user ./configs ``` -### Private terraform registries -Trivy can download terraform code from private registries. +### 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): +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`. @@ -476,7 +546,7 @@ If you want to ignore multiple resources on different attributes, you can specif #trivy:ignore:aws-ec2-no-public-ingress-sgr[from_port=5432] ``` -You can also ignore a resource on multiple attributes: +You can also ignore a resource on multiple attributes in the same rule: ```tf locals { rules = { @@ -505,10 +575,7 @@ resource "aws_security_group_rule" "example" { } ``` -Checks can also be ignored by nested attributes, but certain restrictions apply: - -- You cannot access an individual block using indexes, for example when working with dynamic blocks. -- Special variables like [each](https://developer.hashicorp.com/terraform/language/meta-arguments/for_each#the-each-object) and [count](https://developer.hashicorp.com/terraform/language/meta-arguments/count#the-count-object) cannot be accessed. +Checks can also be ignored by nested attributes: ```tf #trivy:ignore:*[logging_config.prefix=myprefix] diff --git a/docs/docs/scanner/secret.md b/docs/docs/scanner/secret.md index 49d8c8488ece..ea74ae64c930 100644 --- a/docs/docs/scanner/secret.md +++ b/docs/docs/scanner/secret.md @@ -3,7 +3,9 @@ Trivy scans any container image, filesystem and git repository to detect exposed secrets like passwords, api keys, and tokens. Secret scanning is enabled by default. -Trivy will scan every plaintext file, according to builtin rules or configuration. There are plenty of builtin rules: +Trivy will scan every plaintext file, according to builtin rules or configuration. Also, Trivy can detect secrets in compiled Python files (`.pyc`). + +There are plenty of builtin rules: - AWS access key - GCP service account diff --git a/docs/docs/scanner/vulnerability.md b/docs/docs/scanner/vulnerability.md index 9df2b43d8b80..ba5d0014d6bf 100644 --- a/docs/docs/scanner/vulnerability.md +++ b/docs/docs/scanner/vulnerability.md @@ -158,45 +158,70 @@ Trivy can detect vulnerabilities in Kubernetes clusters and components by scanni [^1]: Some manual triage and correction has been made. -## Database -Trivy downloads [the vulnerability database](https://github.com/aquasecurity/trivy-db) every 6 hours. -Trivy uses two types of databases for vulnerability detection: +## Databases +Trivy utilizes several databases containing information relevant for vulnerability scanning. +When performing a vulnerability scan, Trivy will automatically downloads the relevant databases. The databases are cached locally and Trivy will reuse them for subsequent scans on the same machine. Trivy takes care of updating the databases cache automatically, so normally users can be oblivious to it. -- Vulnerability Database -- Java Index Database - -This page provides detailed information about these databases. +For CLI flags related to the database, please refer to [this page](../configuration/db.md). ### Vulnerability Database -Trivy utilizes a database containing vulnerability information. -This database is built every six hours on [GitHub](https://github.com/aquasecurity/trivy-db) and is distributed via [GitHub Container registry (GHCR)](https://ghcr.io/aquasecurity/trivy-db). -The database is cached and updated as needed. -As Trivy updates the database automatically during execution, users don't need to be concerned about it. +This is Trivy's main database which contains vulnerability information, as collected from the datasources mentioned above. +It is built every six hours on [GitHub](https://github.com/aquasecurity/trivy-db). -For CLI flags related to the database, please refer to [this page](../configuration/db.md). +### Java Index Database +When scanning JAR files, Trivy relies on a dedicated database for identifying the groupId, artifactId, and version of the scanned JAR files. This database is only used when scanning JAR files, however your scanned artifacts might contain JAR files that you're not aware of. +This database is built once a day on [GitHub](https://github.com/aquasecurity/trivy-java-db). -#### Private Hosting -If you host the database on your own OCI registry, you can specify a different repository with the `--db-repository` flag. -The default is `ghcr.io/aquasecurity/trivy-db`. +### External connectivity +Trivy needs to connect to the internet to download the databases. If you are running Trivy in an air-gapped environment, or an tightly controlled network, please refer to the [Advanced Network Scenarios document](../advanced/air-gap.md). -```shell -$ trivy image --db-repository YOUR_REPO YOUR_IMAGE +## Detection Behavior +Trivy prioritizes precision in vulnerability detection, aiming to minimize false positives while potentially accepting some false negatives. +This approach is particularly relevant in two key areas: + +- Handling Software Installed via OS Packages +- Handling Packages with Unspecified Versions + +### Handling Software Installed via OS Packages +For files installed by OS package managers, such as `apt`, Trivy exclusively uses advisories from the OS vendor. +This means that even if a JAR file is present in a container image, if it was installed via an OS package manager (e.g., `apt`), Trivy will not analyze the JAR file itself and use upstream security advisories. + +For example, consider the Python `requests` package in Red Hat Universal Base Image 8: + +```bash +[root@987ee49dc93d /]# head -n 3 /usr/lib/python3.6/site-packages/requests-2.20.0-py3.6.egg-info/PKG-INFO +Metadata-Version: 2.1 +Name: requests +Version: 2.20.0 ``` -If authentication is required, it can be configured in the same way as for private images. -Please refer to [the documentation](../advanced/private-registries/index.md) for more details. +Version 2.20.0 is installed, and this package is installed by `dnf`. -### Java Index Database -This database is only downloaded when scanning JAR files so that Trivy can identify the groupId, artifactId, and version of JAR files. -It is built once a day on [GitHub](https://github.com/aquasecurity/trivy-java-db) and distributed via [GitHub Container registry (GHCR)](https://ghcr.io/aquasecurity/trivy-java-db). -Like the vulnerability database, it is automatically downloaded and updated when needed, so users don't need to worry about it. +```bash +[root@987ee49dc93d /]# rpm -ql python3-requests | grep PKG-INFO +/usr/lib/python3.6/site-packages/requests-2.20.0-py3.6.egg-info/PKG-INFO +``` -#### Private Hosting -If you host the database on your own OCI registry, you can specify a different repository with the `--java-db-repository` flag. -The default is `ghcr.io/aquasecurity/trivy-java-db`. +At first glance, this might seem vulnerable to [CVE-2023-32681], which affects versions of requests prior to v2.31.0. +However, Red Hat backported the fix to v2.20.0-3 in [RHSA-2023:4520], and the package is not vulnerable. -If authentication is required, you need to run `docker login YOUR_REGISTRY`. -Currently, specifying a username and password is not supported. +- Upstream (PyPI [requests]): Fixed in v2.31.0 +- Red Hat (`python-requests`): Backported fix applied in v2.20.0-3 (RHSA-2023:4520) + +If Trivy were to detect CVE-2023-32681 in this case, it would be a false positive. +This illustrates why using the correct security advisory is crucial to avoid false detections. +To minimize false positives, Trivy trusts the OS vendor's advisory for software installed via OS package managers and does not use upstream advisories for these packages. + +However, this approach may lead to false negatives if the OS vendor's advisories are delayed or missing. +In such cases, using [--detection-priority comprehensive](#detection-priority) allows Trivy to consider upstream advisories (e.g., [GitHub Advisory Database][ghsa]), potentially increasing false positives but reducing false negatives. + +### Handling Packages with Unspecified Versions +When a package version cannot be uniquely determined (e.g., `package-a: ">=3.0"`), Trivy typically skips vulnerability detection for that package to avoid false positives. +If a lock file is present with fixed versions, Trivy will use those for detection. + +To detect potential vulnerabilities even with unspecified versions, use [--detection-priority comprehensive](#detection-priority). +This option makes Trivy use the minimum version in the specified range for vulnerability detection. +While this may increase false positives if the actual version used is not the minimum, it helps reduce false negatives. ## Configuration This section describes vulnerability-specific configuration. @@ -307,6 +332,25 @@ By default, all relationships are included in the scan. !!! warning As it may not provide a complete package list, `--pkg-relationships` cannot be used with `--dependency-tree`, `--vex` or SBOM generation. +### Detection Priority + +Trivy provides a `--detection-priority` flag to control the balance between false positives and false negatives in vulnerability detection. +This concept is similar to the relationship between [precision and recall][precision-recall] in machine learning evaluation. + +```bash +$ trivy image --detection-priority {precise|comprehensive} alpine:3.15 +``` + +- `precise`: This mode prioritizes reducing false positives. It results in less noisy vulnerability reports but may miss some potential vulnerabilities. +- `comprehensive`: This mode aims to detect more vulnerabilities, potentially including some that might be false positives. + It provides broader coverage but may increase the noise in the results. + +The default value is `precise`. Also refer to the [detection behavior](#detection-behavior) section for more information. + +Regardless of the chosen mode, user review of detected vulnerabilities is crucial: + +- `precise`: Review thoroughly, considering potential missed vulnerabilities. +- `comprehensive`: Carefully investigate each reported vulnerability due to increased false positive possibility. [^1]: https://github.com/GoogleContainerTools/distroless @@ -353,3 +397,9 @@ By default, all relationships are included in the scan. [nvd]: https://nvd.nist.gov/vuln [k8s-cve]: https://kubernetes.io/docs/reference/issues-security/official-cve-feed/ + +[CVE-2023-32681]: https://nvd.nist.gov/vuln/detail/CVE-2023-32681 +[RHSA-2023:4520]: https://access.redhat.com/errata/RHSA-2023:4520 +[ghsa]: https://github.com/advisories +[requests]: https://pypi.org/project/requests/ +[precision-recall]: https://developers.google.com/machine-learning/crash-course/classification/precision-and-recall \ No newline at end of file diff --git a/docs/docs/supply-chain/sbom.md b/docs/docs/supply-chain/sbom.md index ed57195b3550..f2f2d55c79a5 100644 --- a/docs/docs/supply-chain/sbom.md +++ b/docs/docs/supply-chain/sbom.md @@ -743,7 +743,7 @@ Trivy searches for SBOM files in container images with the following extensions: - `.cdx` - `.cdx.json` -In addition, Trivy automatically detects SBOM files in [Bitnami images](https://github.com/bitnami/containers), [see here](../coverage/os/bitnami.md) for more details. +In addition, Trivy automatically detects SBOM files in [Bitnami images](https://github.com/bitnami/containers), [see here](../coverage/others/bitnami.md) for more details. It is enabled in the following targets. diff --git a/docs/docs/target/vm.md b/docs/docs/target/vm.md index b0dc23e9c507..e2c2cac74467 100644 --- a/docs/docs/target/vm.md +++ b/docs/docs/target/vm.md @@ -230,7 +230,7 @@ Reference: [VMware Virtual Disk Format 1.1.pdf][vmdk] |-------------------|:-------:| | XFS | ✔ | | EXT4 | ✔ | -| EXT2/3 | | +| EXT2/3 | ✔ | | ZFS | | diff --git a/docs/tutorials/misconfiguration/custom-checks.md b/docs/tutorials/misconfiguration/custom-checks.md index 97ab67a7f649..0058595c0dde 100644 --- a/docs/tutorials/misconfiguration/custom-checks.md +++ b/docs/tutorials/misconfiguration/custom-checks.md @@ -93,7 +93,7 @@ Note that Rego Ensure that you have Trivy installed and run the following command: ```bash -trivy fs --scanners misconf --policy ./docker-check.rego --namespaces custom ./Dockerfile +trivy fs --scanners misconf --config-check ./docker-check.rego --namespaces custom ./Dockerfile ``` Please replace: diff --git a/docs/tutorials/misconfiguration/terraform.md b/docs/tutorials/misconfiguration/terraform.md index 24b8eebfa69a..c51f2edfe181 100644 --- a/docs/tutorials/misconfiguration/terraform.md +++ b/docs/tutorials/misconfiguration/terraform.md @@ -86,7 +86,7 @@ trivy config --severity CRITICAL, MEDIUM terraform-infra You can pass terraform values to Trivy to override default values found in the Terraform HCL code. More information are provided [in the documentation.](https://aquasecurity.github.io/trivy/latest/docs/coverage/iac/terraform/#value-overrides) ``` -trivy conf --tf-vars terraform.tfvars ./ +trivy config --tf-vars terraform.tfvars ./ ``` ### Custom Checks diff --git a/go.mod b/go.mod index 58453c942f7d..d01ce2a7d4c7 100644 --- a/go.mod +++ b/go.mod @@ -6,12 +6,12 @@ toolchain go1.22.4 require ( github.com/Azure/azure-sdk-for-go v68.0.0+incompatible - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 github.com/BurntSushi/toml v1.4.0 - github.com/CycloneDX/cyclonedx-go v0.9.0 + github.com/CycloneDX/cyclonedx-go v0.9.1 github.com/GoogleCloudPlatform/docker-credential-gcr v2.0.5+incompatible - github.com/Masterminds/sprig/v3 v3.2.3 + github.com/Masterminds/sprig/v3 v3.3.0 github.com/NYTimes/gziphandler v1.1.1 github.com/alecthomas/chroma v0.10.0 github.com/alicebob/miniredis/v2 v2.33.0 @@ -25,45 +25,45 @@ require ( github.com/aquasecurity/table v1.8.0 github.com/aquasecurity/testdocker v0.0.0-20240730042311-4642e94c7fc8 github.com/aquasecurity/tml v0.6.1 - github.com/aquasecurity/trivy-checks v0.13.0 - github.com/aquasecurity/trivy-db v0.0.0-20240718084044-d23a6ca8ba04 + github.com/aquasecurity/trivy-checks v1.1.0 + github.com/aquasecurity/trivy-db v0.0.0-20240910133327-7e0f4d2ed4c1 github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240707095038-0300bc49b68b - github.com/aws/aws-sdk-go-v2 v1.30.3 - github.com/aws/aws-sdk-go-v2/config v1.27.27 - github.com/aws/aws-sdk-go-v2/credentials v1.17.27 - github.com/aws/aws-sdk-go-v2/service/ec2 v1.172.0 - github.com/aws/aws-sdk-go-v2/service/ecr v1.30.3 - github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2 - github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect - github.com/aws/smithy-go v1.20.3 + github.com/aws/aws-sdk-go-v2 v1.31.0 + github.com/aws/aws-sdk-go-v2/config v1.27.38 + github.com/aws/aws-sdk-go-v2/credentials v1.17.36 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.179.1 + github.com/aws/aws-sdk-go-v2/service/ecr v1.35.2 + github.com/aws/aws-sdk-go-v2/service/s3 v1.63.2 + github.com/aws/aws-sdk-go-v2/service/sts v1.31.2 // indirect + github.com/aws/smithy-go v1.21.0 github.com/bitnami/go-version v0.0.0-20231130084017-bb00604d650c github.com/bmatcuk/doublestar/v4 v4.6.1 github.com/cenkalti/backoff/v4 v4.3.0 github.com/cheggaaa/pb/v3 v3.1.5 - github.com/containerd/containerd v1.7.20 + github.com/containerd/containerd v1.7.22 github.com/csaf-poc/csaf_distribution/v3 v3.0.0 - github.com/docker/docker v27.1.1+incompatible + github.com/docker/docker v27.3.1+incompatible github.com/docker/go-connections v0.5.0 github.com/fatih/color v1.17.0 github.com/go-git/go-git/v5 v5.12.0 - github.com/go-openapi/runtime v0.28.0 - github.com/go-openapi/strfmt v0.23.0 + 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/golang-jwt/jwt/v5 v5.2.1 - github.com/google/go-containerregistry v0.20.1 + github.com/google/go-containerregistry v0.20.2 github.com/google/go-github/v62 v62.0.0 github.com/google/licenseclassifier/v2 v2.0.0 github.com/google/uuid v1.6.0 github.com/google/wire v0.6.0 - github.com/hashicorp/go-getter v1.7.5 + github.com/hashicorp/go-getter v1.7.6 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-retryablehttp v0.7.7 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.7.0 - github.com/hashicorp/hcl/v2 v2.21.0 + github.com/hashicorp/hc-install v0.9.0 + github.com/hashicorp/hcl/v2 v2.22.0 github.com/hashicorp/terraform-exec v0.21.0 github.com/in-toto/in-toto-golang v0.9.0 github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f @@ -76,62 +76,63 @@ require ( github.com/liamg/jfather v0.0.7 github.com/liamg/memoryfs v1.6.0 github.com/magefile/mage v1.15.0 - github.com/masahiro331/go-disk v0.0.0-20220919035250-c8da316f91ac - github.com/masahiro331/go-ebs-file v0.0.0-20240112135404-d5fbb1d46323 - github.com/masahiro331/go-ext4-filesystem v0.0.0-20231208112839-4339555a0cd4 + github.com/masahiro331/go-disk v0.0.0-20240625071113-56c933208fee + github.com/masahiro331/go-ebs-file v0.0.0-20240917043618-e6d2bea5c32e + github.com/masahiro331/go-ext4-filesystem v0.0.0-20240620024024-ca14e6327bbd github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08 github.com/masahiro331/go-vmdk-parser v0.0.0-20221225061455-612096e4bbbd - github.com/masahiro331/go-xfs-filesystem v0.0.0-20230608043311-a335f4599b70 + github.com/masahiro331/go-xfs-filesystem v0.0.0-20231205045356-1b22259a6c44 github.com/mattn/go-shellwords v1.0.12 github.com/microsoft/go-rustaudit v0.0.0-20220808201409-204dfee52032 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.15.1 - github.com/open-policy-agent/opa v0.66.0 + github.com/moby/buildkit v0.16.0 + github.com/open-policy-agent/opa v0.68.1-0.20240903211041-76f7038ea2d1 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0 - github.com/openvex/discovery v0.1.0 + 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.3 + github.com/owenrumney/squealer v1.2.4 github.com/package-url/packageurl-go v0.1.3 github.com/quasilyte/go-ruleguard/dsl v0.3.22 - github.com/samber/lo v1.46.0 + github.com/samber/lo v1.47.0 + github.com/sassoftware/go-rpmutils v0.4.0 github.com/secure-systems-lab/go-securesystemslib v0.8.0 github.com/sigstore/rekor v1.3.6 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.6.0 + github.com/spf13/cast v1.7.0 github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 - github.com/testcontainers/testcontainers-go v0.32.0 - github.com/testcontainers/testcontainers-go/modules/localstack v0.32.0 - github.com/tetratelabs/wazero v1.7.3 + github.com/testcontainers/testcontainers-go v0.33.0 + github.com/testcontainers/testcontainers-go/modules/localstack v0.33.0 + github.com/tetratelabs/wazero v1.8.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.15.0 github.com/zclconf/go-cty-yaml v1.0.3 - go.etcd.io/bbolt v1.3.10 - golang.org/x/crypto v0.25.0 + go.etcd.io/bbolt v1.3.11 + golang.org/x/crypto v0.27.0 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect - golang.org/x/mod v0.19.0 - golang.org/x/net v0.27.0 - golang.org/x/sync v0.7.0 - golang.org/x/term v0.22.0 - golang.org/x/text v0.16.0 + golang.org/x/mod v0.21.0 + golang.org/x/net v0.29.0 + golang.org/x/sync v0.8.0 + golang.org/x/term v0.24.0 + golang.org/x/text v0.18.0 golang.org/x/vuln v1.1.3 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 google.golang.org/protobuf v1.34.2 gopkg.in/yaml.v3 v3.0.1 - helm.sh/helm/v3 v3.15.3 - k8s.io/api v0.30.3 - k8s.io/utils v0.0.0-20231127182322-b307cd553661 - modernc.org/sqlite v1.31.1 + helm.sh/helm/v3 v3.16.1 + k8s.io/api v0.31.0 + k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 + modernc.org/sqlite v1.33.1 sigs.k8s.io/yaml v1.4.0 ) @@ -140,7 +141,7 @@ require ( cloud.google.com/go/compute/metadata v0.3.0 // indirect cloud.google.com/go/iam v1.1.6 // indirect cloud.google.com/go/storage v1.39.1 // indirect - dario.cat/mergo v1.0.0 // indirect + dario.cat/mergo v1.0.1 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect @@ -152,11 +153,12 @@ require ( 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.2.2 // indirect + github.com/DataDog/zstd v1.5.5 // 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 github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.2.1 // indirect + 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.0 // indirect @@ -171,22 +173,23 @@ require ( github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/aws/aws-sdk-go v1.54.6 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect - github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // 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.11.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.23.2 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.2 // 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/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect - github.com/cloudflare/circl v1.3.7 // indirect + github.com/cloudflare/circl v1.3.8 // indirect github.com/containerd/cgroups/v3 v3.0.2 // indirect github.com/containerd/containerd/api v1.7.19 // indirect github.com/containerd/continuity v0.4.3 // indirect @@ -196,18 +199,18 @@ require ( github.com/containerd/platforms v0.2.1 // indirect github.com/containerd/stargz-snapshotter/estargz v0.15.1 // indirect github.com/containerd/ttrpc v1.2.5 // indirect - github.com/containerd/typeurl/v2 v2.1.1 // indirect + github.com/containerd/typeurl/v2 v2.2.0 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46 // indirect - github.com/cyphar/filepath-securejoin v0.2.4 // indirect + github.com/cyphar/filepath-securejoin v0.3.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 // indirect github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect - github.com/docker/cli v27.0.3+incompatible // indirect + github.com/docker/cli v27.2.1+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/docker-credential-helpers v0.8.2 // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect @@ -218,10 +221,11 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect - github.com/evanphx/json-patch v5.7.0+incompatible // indirect + github.com/evanphx/json-patch v5.9.0+incompatible // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.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 github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect @@ -261,20 +265,19 @@ require ( github.com/gorilla/websocket v1.5.0 // indirect github.com/gosuri/uitable v0.0.4 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect github.com/hashicorp/golang-lru v0.6.0 // indirect github.com/hashicorp/hcl v1.0.1-vault-5 // indirect github.com/hashicorp/terraform-json v0.22.1 // indirect - github.com/huandu/xstrings v1.4.0 // indirect + github.com/huandu/xstrings v1.5.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/jmoiron/sqlx v1.3.5 // indirect + github.com/jmoiron/sqlx v1.4.0 // indirect 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 @@ -298,11 +301,12 @@ require ( github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/locker v1.0.1 // indirect github.com/moby/patternmatcher v0.6.0 // indirect - github.com/moby/spdystream v0.2.0 // indirect - github.com/moby/sys/mountinfo v0.7.1 // indirect + github.com/moby/spdystream v0.4.0 // indirect + github.com/moby/sys/mountinfo v0.7.2 // indirect github.com/moby/sys/sequential v0.5.0 // indirect - github.com/moby/sys/signal v0.7.0 // indirect - github.com/moby/sys/user v0.1.0 // indirect + github.com/moby/sys/signal v0.7.1 // indirect + github.com/moby/sys/user v0.3.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 github.com/modern-go/reflect2 v1.0.2 // indirect @@ -323,14 +327,14 @@ require ( github.com/pkg/errors v0.9.1 // 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.19.1 // indirect + github.com/prometheus/client_golang v1.20.2 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.51.1 // indirect + github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.4 // indirect - github.com/rubenv/sql-migrate v1.5.2 // indirect + github.com/rubenv/sql-migrate v1.7.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect @@ -340,7 +344,7 @@ require ( github.com/shibumi/go-pathspec v1.3.0 // indirect github.com/shirou/gopsutil/v3 v3.24.2 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect - github.com/shopspring/decimal v1.3.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.3 // indirect github.com/sigstore/timestamp-authority v1.2.2 // indirect @@ -357,51 +361,55 @@ require ( github.com/tklauser/numcpus v0.7.0 // 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.11 // indirect + github.com/ulikunitz/xz v0.5.12 // indirect github.com/vbatts/tar-split v0.11.5 // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/x448/float16 v0.8.4 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect 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 go.mongodb.org/mongo-driver v1.14.0 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect - go.opentelemetry.io/otel v1.27.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 // indirect - go.opentelemetry.io/otel/metric v1.27.0 // indirect - go.opentelemetry.io/otel/sdk v1.27.0 // indirect - go.opentelemetry.io/otel/trace v1.27.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect + go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/otel/sdk v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/oauth2 v0.20.0 // indirect - golang.org/x/sys v0.22.0 // indirect + golang.org/x/oauth2 v0.21.0 // indirect + golang.org/x/sys v0.25.0 // indirect golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7 // indirect - golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.23.0 // indirect + golang.org/x/time v0.6.0 // indirect + golang.org/x/tools v0.24.0 // indirect google.golang.org/api v0.172.0 // indirect google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect - google.golang.org/grpc v1.64.1 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect + google.golang.org/grpc v1.66.0 // indirect gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect + gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/apiextensions-apiserver v0.30.0 // indirect - k8s.io/apimachinery v0.30.3 // indirect - k8s.io/apiserver v0.30.0 // indirect - k8s.io/cli-runtime v0.30.2 // indirect - k8s.io/client-go v0.30.2 // indirect - k8s.io/component-base v0.30.1 // indirect - k8s.io/klog/v2 v2.120.1 // indirect + k8s.io/apiextensions-apiserver v0.31.0 // indirect + k8s.io/apimachinery v0.31.0 // indirect + k8s.io/apiserver v0.31.0 // indirect + k8s.io/cli-runtime v0.31.0 // indirect + k8s.io/client-go v0.31.0 // indirect + k8s.io/component-base v0.31.0 // indirect + k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect - k8s.io/kubectl v0.30.1 // indirect + k8s.io/kubectl v0.31.0 // indirect modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect modernc.org/libc v1.55.3 // indirect modernc.org/mathutil v1.6.0 // indirect @@ -411,10 +419,7 @@ require ( mvdan.cc/sh/v3 v3.8.0 // indirect oras.land/oras-go v1.2.5 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect - sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // indirect + sigs.k8s.io/kustomize/api v0.17.2 // indirect + sigs.k8s.io/kustomize/kyaml v0.17.1 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect ) - -// cf. https://github.com/openvex/discovery/pull/40 -replace github.com/openvex/discovery => github.com/knqyf263/discovery v0.1.1-0.20240726113521-97873005fd03 diff --git a/go.sum b/go.sum index c5c4bc01e657..51d43bf95aa1 100644 --- a/go.sum +++ b/go.sum @@ -188,8 +188,8 @@ cuelabs.dev/go/oci/ociregistry v0.0.0-20240314152124-224736b49f2e h1:GwCVItFUPxw cuelabs.dev/go/oci/ociregistry v0.0.0-20240314152124-224736b49f2e/go.mod h1:ApHceQLLwcOkCEXM1+DyCXTHEJhNGDpJ2kmV6axsx24= cuelang.org/go v0.8.1 h1:VFYsxIFSPY5KgSaH1jQ2GxHOrbu6Ga3kEI70yCZwnOg= cuelang.org/go v0.8.1/go.mod h1:CoDbYolfMms4BhWUlhD+t5ORnihR7wvjcfgyO9lL5FI= -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= @@ -203,8 +203,8 @@ github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo 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.13.0 h1:GJHeeA2N7xrG3q30L2UXDyuWRzDM900/65j70wcM4Ww= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= @@ -241,10 +241,12 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 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/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/CycloneDX/cyclonedx-go v0.9.0 h1:inaif7qD8bivyxp7XLgxUYtOXWtDez7+j72qKTMQTb8= -github.com/CycloneDX/cyclonedx-go v0.9.0/go.mod h1:NE/EWvzELOFlG6+ljX/QeMlVt9VKcTwu8u0ccsACEsw= +github.com/CycloneDX/cyclonedx-go v0.9.1 h1:yffaWOZsv77oTJa/SdVZYdgAgFioCeycBUKkqS2qzQM= +github.com/CycloneDX/cyclonedx-go v0.9.1/go.mod h1:NE/EWvzELOFlG6+ljX/QeMlVt9VKcTwu8u0ccsACEsw= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= +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/Intevation/gval v1.3.0 h1:+Ze5sft5MmGbZrHj06NVUbcxCb67l9RaPTLMNr37mjw= @@ -255,11 +257,10 @@ github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= -github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= -github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= -github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= -github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= @@ -348,10 +349,10 @@ github.com/aquasecurity/testdocker v0.0.0-20240730042311-4642e94c7fc8 h1:b43UVqY 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 v0.13.0 h1:na6PTdY4U0uK/fjz3HNRYBxvYSJ8vgTb57a5T8Y5t9w= -github.com/aquasecurity/trivy-checks v0.13.0/go.mod h1:Xec/SMVGV66I7RgUqOX9MEr+YxBqHXDVLTYmpspPi3E= -github.com/aquasecurity/trivy-db v0.0.0-20240718084044-d23a6ca8ba04 h1:6/T8sFdNVG/AwOGoK6X55h7hF7LYqK8bsuPz8iEz8jM= -github.com/aquasecurity/trivy-db v0.0.0-20240718084044-d23a6ca8ba04/go.mod h1:0T6oy2t1Iedt+yi3Ml5cpOYp5FZT4MI1/mx+3p+PIs8= +github.com/aquasecurity/trivy-checks v1.1.0 h1:I0tVOK8dG/KHrWsqfGNYp2uD/i0f+yS7Je31F+LIUqQ= +github.com/aquasecurity/trivy-checks v1.1.0/go.mod h1:tVzhU0gajD3GmxKPLn/BHR8ZeUquc5ajQTmAsi0kCCU= +github.com/aquasecurity/trivy-db v0.0.0-20240910133327-7e0f4d2ed4c1 h1:G0gnacAORRUqz2Tm5MqivSpldY2GZ74ijhJcMsae+sA= +github.com/aquasecurity/trivy-db v0.0.0-20240910133327-7e0f4d2ed4c1/go.mod h1:PYkSRx4dlgFATEt+okGwibvbxVEtqsOdH+vX/saACYE= 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.6.7-0.20240707095038-0300bc49b68b h1:h7gsIzHyrxpQnayOuQI0kX7+8rVcqhV6G5bM3KVFyJU= @@ -365,44 +366,44 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:W github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go v1.54.6 h1:HEYUib3yTt8E6vxjMWM3yAq5b+qjj/6aKA62mkgux9g= github.com/aws/aws-sdk-go v1.54.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= -github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY= -github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= -github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90= -github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg= -github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI= -github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= -github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7 h1:CRzzXjmgx9p362yO39D6hbZULdMI23gaKqSxijJCXHM= -github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7/go.mod h1:wnsHqpi3RgDwklS5SPHUgjcUUpontGPKJ+GJYOdV7pY= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.172.0 h1:lJjLKG92RyKIIYujVvulR3JpVjr3yxaU34nwXCq8K2o= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.172.0/go.mod h1:o6QDjdVKpP5EF0dp/VlvqckzuSDATr1rLdHt3A5m0YY= -github.com/aws/aws-sdk-go-v2/service/ecr v1.30.3 h1:+v2hv29pWaVDASIScHuUhDC93nqJGVlGf6cujrJMHZE= -github.com/aws/aws-sdk-go-v2/service/ecr v1.30.3/go.mod h1:RhaP7Wil0+uuuhiE4FzOOEFZwkmFAk1ZflXzK+O3ptU= +github.com/aws/aws-sdk-go-v2 v1.31.0 h1:3V05LbxTSItI5kUqNwhJrrrY1BAXxXt0sN0l72QmG5U= +github.com/aws/aws-sdk-go-v2 v1.31.0/go.mod h1:ztolYtaEUtdpf9Wftr31CJfLVjOnD/CVRkKOOYgF8hA= +github.com/aws/aws-sdk-go-v2/config v1.27.38 h1:mMVyJJuSUdbD4zKXoxDgWrgM60QwlFEg+JhihCq6wCw= +github.com/aws/aws-sdk-go-v2/config v1.27.38/go.mod h1:6xOiNEn58bj/64MPKx89r6G/el9JZn8pvVbquSqTKK4= +github.com/aws/aws-sdk-go-v2/credentials v1.17.36 h1:zwI5WrT+oWWfzSKoTNmSyeBKQhsFRJRv+PGW/UZW+Yk= +github.com/aws/aws-sdk-go-v2/credentials v1.17.36/go.mod h1:3AG/sY1rc9NJrNWcN/3KPU4SIDPGTrd/qegKB0TnFdE= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 h1:C/d03NAmh8C4BZXhuRNboF/DqhBkBCeDiJDcaqIT5pA= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14/go.mod h1:7I0Ju7p9mCIdlrfS+JCgqcYD0VXz/N4yozsox+0o078= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 h1:kYQ3H1u0ANr9KEKlGs/jTLrBFPo8P8NaH/w7A01NeeM= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18/go.mod h1:r506HmK5JDUh9+Mw4CfGJGSSoqIiLCndAuqXuhbv67Y= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 h1:Z7IdFUONvTcvS7YuhtVxN99v2cCoHRXOS4mTr0B/pUc= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18/go.mod h1:DkKMmksZVVyat+Y+r1dEOgJEfUeA7UngIHWeKsi0yNc= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= +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.179.1 h1:TwFjSwRn1kR1i1qeq5cQBRwRaZ80JQS8BHsJTb6QBk8= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.179.1/go.mod h1:W6sNzs5T4VpZn1Vy+FMKw8s24vt5k6zPJXcNOK0asBo= +github.com/aws/aws-sdk-go-v2/service/ecr v1.35.2 h1:bVNvja4oEB7v+VL1yP46hWthCPp+KYpZBLS2AifM5PY= +github.com/aws/aws-sdk-go-v2/service/ecr v1.35.2/go.mod h1:oRaGEExKI6Pqcow+Tt7wpJf73/Srcj/CUJv5Eb9QFhg= 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.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 h1:QFASJGfT8wMXtuP3D5CRmMjARHv9ZmzFUMJznHDOY3w= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5/go.mod h1:QdZ3OmoIjSX+8D1OPAzPxDfjXASbBMDsz9qvtyIhtik= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 h1:Xbwbmk44URTiHNx6PNo0ujDE6ERlsCKJD3u1zfnzAPg= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20/go.mod h1:oAfOFzUB14ltPZj1rWwRc3d/6OgD76R8KlvU3EqM9Fg= github.com/aws/aws-sdk-go-v2/service/kms v1.30.0 h1:yS0JkEdV6h9JOo8sy2JSpjX+i7vsKifU8SIeHrqiDhU= github.com/aws/aws-sdk-go-v2/service/kms v1.30.0/go.mod h1:+I8VUUSVD4p5ISQtzpgSva4I8cJ4SQ4b1dcBcof7O+g= -github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2 h1:sZXIzO38GZOU+O0C+INqbH7C2yALwfMWpd64tONS/NE= -github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2/go.mod h1:Lcxzg5rojyVPU/0eFwLtcyTaek/6Mtic5B1gJo7e/zE= -github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM= -github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw= -github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE= -github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ= -github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE= -github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/aws/aws-sdk-go-v2/service/s3 v1.63.2 h1:1iXmXy8SJzQVMGvo40TSzBYS9ig6BSyXfRIMzLfmBfE= +github.com/aws/aws-sdk-go-v2/service/s3 v1.63.2/go.mod h1:NLTqRLe3pUNu3nTEHI6XlHLKYmc8fbHUdMxAB6+s41Q= +github.com/aws/aws-sdk-go-v2/service/sso v1.23.2 h1:yzi/y/vKlLyzOfG7pSu5ONNGRxHIgLeDrV4w2AMRCo0= +github.com/aws/aws-sdk-go-v2/service/sso v1.23.2/go.mod h1:XRlMvmad0ZNL+75C5FYdMvbbLkd6qiqz6foR1nA1PXY= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.2 h1:3gb6pYhYLjo8rB1h2Tqs61wpjRd3rQymYcVq/pp0yxI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.2/go.mod h1:FnvDM4sfa+isJ3kDXIzAB9GAwVSzFzSy97uZ3IsHo4E= +github.com/aws/aws-sdk-go-v2/service/sts v1.31.2 h1:O6tyji8mXmBGsHvTCB0VIhrDw19lGTUSbKIyjnw79s8= +github.com/aws/aws-sdk-go-v2/service/sts v1.31.2/go.mod h1:yMWe0F+XG0DkRZK5ODZhG7BEFYhLXi2dqGsv6tX0cgI= +github.com/aws/smithy-go v1.21.0 h1:H7L8dtDRk0P1Qm6y0ji7MCYMQObJ5R9CRpyPhRUkLYA= +github.com/aws/smithy-go v1.21.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8 h1:SoFYaT9UyGkR0+nogNyD/Lj+bsixB+SNuAS4ABlEs6M= github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8/go.mod h1:2JF49jcDOrLStIXN/j/K1EKRq8a8R2qRnlZA6/o/c7c= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -415,6 +416,8 @@ github.com/bitnami/go-version v0.0.0-20231130084017-bb00604d650c h1:C4UZIaS+HAw+ github.com/bitnami/go-version v0.0.0-20231130084017-bb00604d650c/go.mod h1:9iglf1GG4oNRJ39bZ5AZrjgAFD2RwQbXw6Qf7Cs47wo= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= @@ -445,8 +448,8 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= @@ -460,8 +463,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.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= -github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/cloudflare/circl v1.3.8 h1:j+V8jJt09PoeMFIu2uh5JUyEaIHTXVOHslFoLNAKqwI= +github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU= 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= @@ -479,8 +482,8 @@ github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL 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.2 h1:f5WFqIVSgo5IZmtTT3qVBo6TzI1ON6sycSBKkymb9L0= github.com/containerd/cgroups/v3 v3.0.2/go.mod h1:JUgITrzdFqp42uI2ryGA+ge0ap/nxzYgkGmIcetmErE= -github.com/containerd/containerd v1.7.20 h1:Sl6jQYk3TRavaU83h66QMbI2Nqg9Jm6qzwX57Vsn1SQ= -github.com/containerd/containerd v1.7.20/go.mod h1:52GsS5CwquuqPuLncsXwG0t2CiUce+KsNHJZQJvAgR0= +github.com/containerd/containerd v1.7.22 h1:nZuNnNRA6T6jB975rx2RRNqqH2k6ELYKDZfqTHqwyy0= +github.com/containerd/containerd v1.7.22/go.mod h1:e3Jz1rYRUZ2Lt51YrH9Rz0zPyJBOlSvB3ghr2jbVD8g= github.com/containerd/containerd/api v1.7.19 h1:VWbJL+8Ap4Ju2mx9c9qS1uFSB1OVYr5JJrW2yT5vFoA= github.com/containerd/containerd/api v1.7.19/go.mod h1:fwGavl3LNwAV5ilJ0sbrABL44AQxmNjDRcwheXDb6Ig= github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= @@ -497,8 +500,8 @@ github.com/containerd/stargz-snapshotter/estargz v0.15.1 h1:eXJjw9RbkLFgioVaTG+G github.com/containerd/stargz-snapshotter/estargz v0.15.1/go.mod h1:gr2RNwukQ/S9Nv33Lt6UC7xEx58C+LHRdoqbEKjz1Kk= github.com/containerd/ttrpc v1.2.5 h1:IFckT1EFQoFBMG4c3sMdT8EP3/aKfumK1msY+Ze4oLU= github.com/containerd/ttrpc v1.2.5/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= -github.com/containerd/typeurl/v2 v2.1.1 h1:3Q4Pt7i8nYwy2KmQWIw2+1hTvwTE/6w9FqcttATPO/4= -github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0= +github.com/containerd/typeurl/v2 v2.2.0 h1:6NBDbQzr7I5LHgp34xAXYF5DOTQDn05X58lsPEmzLso= +github.com/containerd/typeurl/v2 v2.2.0/go.mod h1:8XOOxnyatxSWuG8OfsZXVnAF4iZfedjS/8UHSPJnX4g= github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk= github.com/coreos/go-oidc/v3 v3.10.0 h1:tDnXHnLyiTVyT/2zLDGj09pFPkhND8Gl8lnTRhoEaJU= github.com/coreos/go-oidc/v3 v3.10.0/go.mod h1:5j11xcw0D3+SGxn6Z/WFADsgcWVMyNAlSQupk0KK3ac= @@ -513,8 +516,8 @@ github.com/csaf-poc/csaf_distribution/v3 v3.0.0 h1:ob9+Fmpff0YWgTP3dYaw7G2hKQ9ce github.com/csaf-poc/csaf_distribution/v3 v3.0.0/go.mod h1:uilCTiNKivq+6zrDvjtZaUeLk70oe21iwKivo6ILwlQ= github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46 h1:2Dx4IHfC1yHWI12AxQDJM1QbRCDfk6M+blLzlZCXdrc= github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw= -github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= -github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/cyphar/filepath-securejoin v0.3.1 h1:1V7cHiaW+C+39wEfpH6XlLBQo3j/PciWFrgfCLS8XrE= +github.com/cyphar/filepath-securejoin v0.3.1/go.mod h1:F7i41x/9cBF7lzCrVsYs9fuzwRZm4NQsGTBdpp6mETc= github.com/danieljoos/wincred v1.2.1 h1:dl9cBrupW8+r5250DYkYxocLeZ1Y4vB1kxgtjxw8GQs= github.com/danieljoos/wincred v1.2.1/go.mod h1:uGaFL9fDn3OLTvzCGulzE+SzjEe5NGlh5FdCcyfPwps= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -542,12 +545,12 @@ 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 v27.0.3+incompatible h1:usGs0/BoBW8MWxGeEtqPMkzOY56jZ6kYlSN5BLDioCQ= -github.com/docker/cli v27.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v27.2.1+incompatible h1:U5BPtiD0viUzjGAjV1p0MGB8eVA3L3cbIrnyWmSJI70= +github.com/docker/cli v27.2.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 v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= -github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI= +github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= @@ -583,8 +586,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= -github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +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/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -605,6 +608,8 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -680,21 +685,15 @@ github.com/go-playground/validator/v10 v10.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtP github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= 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.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 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-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= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU= -github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs= -github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0= -github.com/gobuffalo/packd v1.0.1/go.mod h1:PP2POP3p3RXGz7Jh6eYEf93S7vA2za6xM7QT85L4+VY= -github.com/gobuffalo/packr/v2 v2.8.3 h1:xE1yzvnO56cUC0sTpKR3DIbxZgB54AftTFMhB2XEWlY= -github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXsOdiU5KwbKc= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/goccy/go-yaml v1.8.1/go.mod h1:wS4gNoLalDSJxo/SpngzPQ2BN4uuZVLCmbM4S3vd4+Y= @@ -714,8 +713,8 @@ github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= -github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4= +github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -782,8 +781,8 @@ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-containerregistry v0.20.1 h1:eTgx9QNYugV4DN5mz4U8hiAGTi1ybXn0TPi4Smd8du0= -github.com/google/go-containerregistry v0.20.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= +github.com/google/go-containerregistry v0.20.2 h1:B1wPJ1SN/S7pB+ZAimcciVD+r+yV/l/DSArMxlbwseo= +github.com/google/go-containerregistry v0.20.2/go.mod h1:z38EKdKh4h7IP2gSfUUqEvalZBqs6AoLeWfUy34nQC8= github.com/google/go-github/v55 v55.0.0 h1:4pp/1tNMB9X/LuAhs5i0KQAE40NmiR/y6prLNb9x9cg= github.com/google/go-github/v55 v55.0.0/go.mod h1:JLahOTA1DnXzhxEymmFF5PP2tSS9JVNj68mSZNDwskA= github.com/google/go-github/v62 v62.0.0 h1:/6mGCaRywZz9MuHyw9gD1CwsbmBX8GWsbFkwMmHdhl4= @@ -817,8 +816,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-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= -github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= @@ -829,7 +828,6 @@ github.com/google/tink/go v1.7.0 h1:6Eox8zONGebBFcCBqkVmt60LaWZa6xg1cl/DwAh/J1w= github.com/google/tink/go v1.7.0/go.mod h1:GAUOd+QE3pgj9q8VKIGTCP33c/B7eb4NhxLcgTJZStM= github.com/google/trillian v1.6.0 h1:jMBeDBIkINFvS2n6oV5maDqfRlxREAc6CW9QYWQ0qT4= github.com/google/trillian v1.6.0/go.mod h1:Yu3nIMITzNhhMJEHjAtp6xKiu+H/iHu2Oq5FjV2mCWI= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -857,13 +855,13 @@ 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.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 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/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= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= @@ -872,8 +870,8 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-getter v1.7.5 h1:dT58k9hQ/vbxNMwoI5+xFYAJuv6152UNvdHokfI5wE4= -github.com/hashicorp/go-getter v1.7.5/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= +github.com/hashicorp/go-getter v1.7.6 h1:5jHuM+aH373XNtXl9TNTUH5Qd69Trve11tHIrB+6yj4= +github.com/hashicorp/go-getter v1.7.6/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= @@ -901,12 +899,12 @@ 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.7.0 h1:Uu9edVqjKQxxuD28mR5TikkKDd/p55S8vzPC1659aBk= -github.com/hashicorp/hc-install v0.7.0/go.mod h1:ELmmzZlGnEcqoUMKUuykHaPCIR1sYLYX+KSggWSKZuA= +github.com/hashicorp/hc-install v0.9.0 h1:2dIk8LcvANwtv3QZLckxcjyF5w8KVtiMxu6G6eLhghE= +github.com/hashicorp/hc-install v0.9.0/go.mod h1:+6vOP+mf3tuGgMApVYtmsnDoKWMDcFXeTxCACYZ8SFg= 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.21.0 h1:lve4q/o/2rqwYOgUg3y3V2YPyD1/zkCLGjIV74Jit14= -github.com/hashicorp/hcl/v2 v2.21.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= +github.com/hashicorp/hcl/v2 v2.22.0 h1:hkZ3nCtqeJsDhPRFz5EA9iwcG1hNWGePOTw6oyul12M= +github.com/hashicorp/hcl/v2 v2.22.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ= github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec= @@ -916,12 +914,10 @@ github.com/hashicorp/vault/api v1.12.2/go.mod h1:LSGf1NGT1BnvFFnKVtnvcaLBM2Lz+gJ 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= -github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= -github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/in-toto/in-toto-golang v0.9.0 h1:tHny7ac4KgtsfrG6ybU8gVOZux2H8jN05AXJ9EBM1XU= @@ -940,8 +936,8 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmhodges/clock v1.2.0 h1:eq4kys+NI0PLngzaHEe7AmPT90XMGIEySD1JfV1PDIs= github.com/jmhodges/clock v1.2.0/go.mod h1:qKjhA7x7u/lQpPB1XAqX1b1lCI/w3/fNuYpI/ZjLynI= -github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= -github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -951,8 +947,6 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= -github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -962,8 +956,6 @@ github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrD github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/knqyf263/discovery v0.1.1-0.20240726113521-97873005fd03 h1:fsWNAqGAbq2sz7q0agtKCq/esMjvReNd26bgWN8Lk6w= -github.com/knqyf263/discovery v0.1.1-0.20240726113521-97873005fd03/go.mod h1:z4b//Qi7p7zcM/c41ogeTy+/nqfMbbeYnfZ+EMCTCD0= github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f h1:GvCU5GXhHq+7LeOzx/haG7HSIZokl3/0GkoUFzsRJjg= github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f/go.mod h1:q59u9px8b7UTj0nIjEjvmTWekazka6xIt6Uogz5Dm+8= github.com/knqyf263/go-deb-version v0.0.0-20230223133812-3ed183d23422 h1:PPPlUUqPP6fLudIK4n0l0VU4KT2cQGnheW9x8pNiCHI= @@ -1000,7 +992,6 @@ github.com/liamg/jfather v0.0.7 h1:Xf78zS263yfT+xr2VSo6+kyAy4ROlCacRqJG7s5jt4k= github.com/liamg/jfather v0.0.7/go.mod h1:xXBGiBoiZ6tmHhfy5Jzw8sugzajwYdi6VosIpB3/cPM= github.com/liamg/memoryfs v1.6.0 h1:jAFec2HI1PgMTem5gR7UT8zi9u4BfG5jorCRlLH06W8= github.com/liamg/memoryfs v1.6.0/go.mod h1:z7mfqXFQS8eSeBBsFjYLlxYRMRyiPktytvYCYTb3BSk= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= @@ -1016,24 +1007,18 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V github.com/magiconair/properties v1.8.7/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/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= -github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= -github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY= -github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= -github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= -github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= -github.com/masahiro331/go-disk v0.0.0-20220919035250-c8da316f91ac h1:QyRucnGOLHJag1eB9CtuZwZk+/LpvTSYr5mnFLLFlgA= -github.com/masahiro331/go-disk v0.0.0-20220919035250-c8da316f91ac/go.mod h1:J7Vb0sf0JzOhT0uHTeCqO6dqP/ELVcQvQ6yQ/56ZRGw= -github.com/masahiro331/go-ebs-file v0.0.0-20240112135404-d5fbb1d46323 h1:uQubA711SeYStvStohMLrdvRTTohdPHrEPFzerLcY9I= -github.com/masahiro331/go-ebs-file v0.0.0-20240112135404-d5fbb1d46323/go.mod h1:OdtzwqTtu49Gh5RFkNEU1SbcihIuVTtUipwHflqxckE= -github.com/masahiro331/go-ext4-filesystem v0.0.0-20231208112839-4339555a0cd4 h1:uHO44vOunB0oEtk+r8ifBbFOD0mr6+fmoyFNCgLE66k= -github.com/masahiro331/go-ext4-filesystem v0.0.0-20231208112839-4339555a0cd4/go.mod h1:3XMMY1M486mWGTD13WPItg6FsgflQR72ZMAkd+gsyoQ= +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= +github.com/masahiro331/go-ebs-file v0.0.0-20240917043618-e6d2bea5c32e/go.mod h1:XFWPTlAcEL733RUjbr0QBybdt6oK2DH7LZk8id2qtd4= +github.com/masahiro331/go-ext4-filesystem v0.0.0-20240620024024-ca14e6327bbd h1:JEIW94K3spsvBI5Xb9PGhKSIza9/jxO1lF30tPCAJlA= +github.com/masahiro331/go-ext4-filesystem v0.0.0-20240620024024-ca14e6327bbd/go.mod h1:3XMMY1M486mWGTD13WPItg6FsgflQR72ZMAkd+gsyoQ= github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08 h1:AevUBW4cc99rAF8q8vmddIP8qd/0J5s/UyltGbp66dg= github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08/go.mod h1:JOkBRrE1HvgTyjk6diFtNGgr8XJMtIfiBzkL5krqzVk= github.com/masahiro331/go-vmdk-parser v0.0.0-20221225061455-612096e4bbbd h1:Y30EzvuoVp97b0unb/GOFXzBUKRXZXUN2e0wYmvC+ic= github.com/masahiro331/go-vmdk-parser v0.0.0-20221225061455-612096e4bbbd/go.mod h1:5f7mCJGW9cJb8SDn3z8qodGxpMCOo8d/2nls/tiwRrw= -github.com/masahiro331/go-xfs-filesystem v0.0.0-20230608043311-a335f4599b70 h1:X6W6raTo07X0q4pvSI/68Pj/Ic4iIU2CfQU65OH0Zhc= -github.com/masahiro331/go-xfs-filesystem v0.0.0-20230608043311-a335f4599b70/go.mod h1:QKBZqdn6teT0LK3QhAf3K6xakItd1LonOShOEC44idQ= +github.com/masahiro331/go-xfs-filesystem v0.0.0-20231205045356-1b22259a6c44 h1:VmSjn0UCyfXUNdePDr7uM/uZTnGSp+mKD5+cYkEoLx4= +github.com/masahiro331/go-xfs-filesystem v0.0.0-20231205045356-1b22259a6c44/go.mod h1:QKBZqdn6teT0LK3QhAf3K6xakItd1LonOShOEC44idQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -1053,7 +1038,6 @@ github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZ github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= -github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -1063,7 +1047,6 @@ github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= -github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -1076,27 +1059,28 @@ github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4 github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 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.15.1 h1:J6wrew7hphKqlq1wuu6yaUb/1Ra7gEzDAovylGztAKM= -github.com/moby/buildkit v0.15.1/go.mod h1:Yis8ZMUJTHX9XhH9zVyK2igqSHV3sxi3UN0uztZocZk= +github.com/moby/buildkit v0.16.0 h1:wOVBj1o5YNVad/txPQNXUXdelm7Hs/i0PUFjzbK0VKE= +github.com/moby/buildkit v0.16.0/go.mod h1:Xqx/5GlrqE1yIRORk0NSCVDFpQAU1WjlT6KHYZdisIQ= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/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.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/moby/sys/mountinfo v0.7.1 h1:/tTvQaSJRr2FshkhXiIpux6fQ2Zvc4j7tAhMTStAG2g= -github.com/moby/sys/mountinfo v0.7.1/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= +github.com/moby/spdystream v0.4.0 h1:Vy79D6mHeJJjiPdFEL2yku1kl0chZpJfZcPpb16BRl8= +github.com/moby/spdystream v0.4.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +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.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= -github.com/moby/sys/signal v0.7.0 h1:25RW3d5TnQEoKvRbEKUGay6DCQ46IxAVTT9CUMgmsSI= -github.com/moby/sys/signal v0.7.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= -github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= -github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= +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/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -1134,16 +1118,16 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= -github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= +github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE= -github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk= -github.com/open-policy-agent/opa v0.66.0 h1:DbrvfJQja0FBRcPOB3Z/BOckocN+M4ApNWyNhSRJt0w= -github.com/open-policy-agent/opa v0.66.0/go.mod h1:EIgNnJcol7AvQR/IcWLwL13k64gHVbNAVG46b2G+/EY= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/open-policy-agent/opa v0.68.1-0.20240903211041-76f7038ea2d1 h1:GQrryTKpunLNDc2NdhNL1FzfrbuNvo45s76anGdqz9k= +github.com/open-policy-agent/opa v0.68.1-0.20240903211041-76f7038ea2d1/go.mod h1:5E5SvaPwTpwt2WM177I9Z3eT7qUpmOGjk1ZdHs+TZ4w= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= @@ -1154,13 +1138,15 @@ github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaL github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= 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= +github.com/openvex/discovery v0.1.1-0.20240802171711-7c54efc57553/go.mod h1:z4b//Qi7p7zcM/c41ogeTy+/nqfMbbeYnfZ+EMCTCD0= github.com/openvex/go-vex v0.2.5 h1:41utdp2rHgAGCsG+UbjmfMG5CWQxs15nGqir1eRgSrQ= github.com/openvex/go-vex v0.2.5/go.mod h1:j+oadBxSUELkrKh4NfNb+BPo77U3q7gdKME88IO/0Wo= github.com/owenrumney/go-sarif v1.1.1/go.mod h1:dNDiPlF04ESR/6fHlPyq7gHKmrM0sHUvAGjsoh8ZH0U= github.com/owenrumney/go-sarif/v2 v2.3.3 h1:ubWDJcF5i3L/EIOER+ZyQ03IfplbSU1BLOE26uKQIIU= github.com/owenrumney/go-sarif/v2 v2.3.3/go.mod h1:MSqMMx9WqlBSY7pXoOZWgEsVB4FDNfhcaXDA1j6Sr+w= -github.com/owenrumney/squealer v1.2.3 h1:7v2BGNReEHYGyopOpjnurbnowk5WWagpN/u9KEu0uUU= -github.com/owenrumney/squealer v1.2.3/go.mod h1:F3PF/UaTAzaexT/cvvMYCSRHLRPBCiUcPClz3SZ6618= +github.com/owenrumney/squealer v1.2.4 h1:77CEDP10mgvFLWHzUIBTfFIj9RkJ5h36YQhZ48GtjsQ= +github.com/owenrumney/squealer v1.2.4/go.mod h1:F3PF/UaTAzaexT/cvvMYCSRHLRPBCiUcPClz3SZ6618= github.com/package-url/packageurl-go v0.1.3 h1:4juMED3hHiz0set3Vq3KeQ75KD1avthoXLtmE3I0PLs= github.com/package-url/packageurl-go v0.1.3/go.mod h1:nKAWB8E6uk1MHqiS/lQb9pYBGH2+mdJ2PJc2s50dQY0= github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= @@ -1189,8 +1175,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.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg= +github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= 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= @@ -1198,8 +1184,8 @@ github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.51.1 h1:eIjN50Bwglz6a/c3hAgSMcofL3nD+nFQkV6Dd4DsQCw= -github.com/prometheus/common v0.51.1/go.mod h1:lrWtQx+iDfn2mbH5GUzlH9TSHyfZpHkSiG1W7y3sF2Q= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= @@ -1220,8 +1206,8 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/rubenv/sql-migrate v1.5.2 h1:bMDqOnrJVV/6JQgQ/MxOpU+AdO8uzYYA/TxFUBzFtS0= -github.com/rubenv/sql-migrate v1.5.2/go.mod h1:H38GW8Vqf8F0Su5XignRyaRcbXbJunSWxs+kmzlg0Is= +github.com/rubenv/sql-migrate v1.7.0 h1:HtQq1xyTN2ISmQDggnh0c9U3JlP8apWh8YO2jzlXpTI= +github.com/rubenv/sql-migrate v1.7.0/go.mod h1:S4wtDEG1CKn+0ShpTtzWhFpHHI5PvCUtiGI+C+Z2THE= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -1231,10 +1217,12 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/samber/lo v1.46.0 h1:w8G+oaCPgz1PoCJztqymCFaKwXt+5cCXn51uPxExFfQ= -github.com/samber/lo v1.46.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= +github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= +github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= +github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg= +github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI= github.com/sassoftware/relic v7.2.1+incompatible h1:Pwyh1F3I0r4clFJXkSI8bOyJINGqpgjJU3DYAZeI05A= github.com/sassoftware/relic v7.2.1+incompatible/go.mod h1:CWfAxv73/iLZ17rbyhIEq3K9hs5w6FpNMdUT//qR+zk= github.com/sassoftware/relic/v7 v7.6.2 h1:rS44Lbv9G9eXsukknS4mSjIAuuX+lMq/FnStgmZlUv4= @@ -1254,9 +1242,9 @@ github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFt github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sigstore/cosign/v2 v2.2.4 h1:iY4vtEacmu2hkNj1Fh+8EBqBwKs2DHM27/lbNWDFJro= github.com/sigstore/cosign/v2 v2.2.4/go.mod h1:JZlRD2uaEjVAvZ1XJ3QkkZJhTqSDVtLaet+C/TMR81Y= @@ -1294,9 +1282,8 @@ github.com/spdx/tools-golang v0.5.5 h1:61c0KLfAcNqAjlg6UNMdkwpMernhw3zVRwDZ2x9XO github.com/spdx/tools-golang v0.5.5/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYecciXgrw5vE= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -1333,12 +1320,12 @@ github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BG github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= github.com/terminalstatic/go-xsd-validate v0.1.5 h1:RqpJnf6HGE2CB/lZB1A8BYguk8uRtcvYAPLCF15qguo= github.com/terminalstatic/go-xsd-validate v0.1.5/go.mod h1:18lsvYFofBflqCrvo1umpABZ99+GneNTw2kEEc8UPJw= -github.com/testcontainers/testcontainers-go v0.32.0 h1:ug1aK08L3gCHdhknlTTwWjPHPS+/alvLJU/DRxTD/ME= -github.com/testcontainers/testcontainers-go v0.32.0/go.mod h1:CRHrzHLQhlXUsa5gXjTOfqIEJcrK5+xMDmBr/WMI88E= -github.com/testcontainers/testcontainers-go/modules/localstack v0.32.0 h1:FITjE+DSDD136HQho7ThA6cEtUouZzDf7FvMBL2Muog= -github.com/testcontainers/testcontainers-go/modules/localstack v0.32.0/go.mod h1:JasdXHmUT8MTDYfyJza3JjO/k+QA3m8K2GQfnFQM++g= -github.com/tetratelabs/wazero v1.7.3 h1:PBH5KVahrt3S2AHgEjKu4u+LlDbbk+nsGE3KLucy6Rw= -github.com/tetratelabs/wazero v1.7.3/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= +github.com/testcontainers/testcontainers-go v0.33.0 h1:zJS9PfXYT5O0ZFXM2xxXfk4J5UMw/kRiISng037Gxdw= +github.com/testcontainers/testcontainers-go v0.33.0/go.mod h1:W80YpTa8D5C3Yy16icheD01UTDu+LmXIA2Keo+jWtT8= +github.com/testcontainers/testcontainers-go/modules/localstack v0.33.0 h1:AhbUGUjneEnMyTV5aTsPYzDiAWrba1duPtiV+Z9CKdY= +github.com/testcontainers/testcontainers-go/modules/localstack v0.33.0/go.mod h1:J5vMq1fXXiTfwcJplMClHhn+j8+MbIMv7Lic4d9E8qU= +github.com/tetratelabs/wazero v1.8.0 h1:iEKu0d4c2Pd+QSRieYbnQC9yiFlMS9D+Jr0LsRmcF4g= +github.com/tetratelabs/wazero v1.8.0/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs= github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg= github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU= github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI= @@ -1361,13 +1348,19 @@ github.com/twitchtv/twirp v8.1.3+incompatible h1:+F4TdErPgSUbMZMwp13Q/KgDVuI7HJX github.com/twitchtv/twirp v8.1.3+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= -github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= +github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts= github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk= github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xanzy/go-gitlab v0.102.0 h1:ExHuJ1OTQ2yt25zBMMj0G96ChBirGYv8U7HyUiYkZ+4= github.com/xanzy/go-gitlab v0.102.0/go.mod h1:ETg8tcj4OhrB84UEgeE8dSuV/0h4BBL1uOV/qK0vlyI= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= @@ -1379,6 +1372,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yashtewari/glob-intersection v0.2.0 h1:8iuHdN88yYuCzCdjt0gDe+6bAhUwBeEWqThExu54RFg= @@ -1411,8 +1406,8 @@ github.com/zclconf/go-cty-yaml v1.0.3/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JApr github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs= github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0= -go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ= +go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= +go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -1424,27 +1419,27 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0= -go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= -go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 h1:R9DE4kQ4k+YtfLI2ULwX82VtNQ2J8yZmA7ZIF/D+7Mc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0/go.mod h1:OQFyQVrDlbe+R7xrEyDr/2Wr67Ol0hRUgsfA+V5A95s= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 h1:R3X6ZXmNPRR8ul6i3WgFURCHzaXjHdm0karRG/+dj3s= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0/go.mod h1:QWFXnDavXWwMx2EEcZsf3yxgEKAqsxQ+Syjp+seyInw= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0/go.mod h1:/OpE/y70qVkndM0TrxT4KBoN3RsFZP0QaofcfYrj76I= -go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= -go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= -go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI= -go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A= -go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= -go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= +go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= -go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= go.step.sm/crypto v0.44.2 h1:t3p3uQ7raP2jp2ha9P6xkQF85TJZh+87xmjSLaib+jk= @@ -1465,12 +1460,11 @@ golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1512,8 +1506,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1567,14 +1561,13 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= 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= @@ -1600,8 +1593,8 @@ golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= -golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= -golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1619,8 +1612,8 @@ golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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= @@ -1684,7 +1677,6 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1704,7 +1696,6 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1712,21 +1703,20 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= 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= @@ -1741,13 +1731,13 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -1806,8 +1796,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 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.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= -golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/vuln v1.1.3 h1:NPGnvPOTgnjBc9HTaUx+nj+EaUYxl5SJOWqaDYGaFYw= golang.org/x/vuln v1.1.3/go.mod h1:7Le6Fadm5FOqE9C926BCD0g12NWyhg7cxV4BwcPFuNY= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1980,10 +1970,10 @@ google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 h1:ImUcDPHjTrAqNhlOkSocDLfG9rrNHH7w7uoKWPaWZ8s= google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7/go.mod h1:/3XmxOjePkvmKrHuBy4zNFw7IzxJXtAgdpXi8Ll990U= -google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 h1:P8OJ/WCl/Xo4E4zoe4/bifHpSmmKwARqyqE4nW6J2GQ= -google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:RGnPtTG7r4i8sPlNyDeikXF99hMM+hN6QMm4ooG9g2g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 h1:AgADTJarZTBqgjiUzRgfaBchgYB3/WFTC80GPwsMcRI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0= +google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= 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= @@ -2019,8 +2009,8 @@ google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= -google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= +google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= +google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= 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= @@ -2049,6 +2039,8 @@ gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS gopkg.in/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk= gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs= gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI= @@ -2075,8 +2067,8 @@ 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.15.3 h1:HcZDaVFe9uHa6hpsR54mJjYyRy4uz/pc6csg27nxFOc= -helm.sh/helm/v3 v3.15.3/go.mod h1:FzSIP8jDQaa6WAVg9F+OkKz7J0ZmAga4MABtTbsb9WQ= +helm.sh/helm/v3 v3.16.1 h1:cER6tI/8PgUAsaJaQCVBUg3VI9KN4oVaZJgY60RIc0c= +helm.sh/helm/v3 v3.16.1/go.mod h1:r+xBHHP20qJeEqtvBXMf7W35QDJnzY/eiEBzt+TfHps= 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= @@ -2084,28 +2076,28 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.30.3 h1:ImHwK9DCsPA9uoU3rVh4QHAHHK5dTSv1nxJUapx8hoQ= -k8s.io/api v0.30.3/go.mod h1:GPc8jlzoe5JG3pb0KJCSLX5oAFIW3/qNJITlDj8BH04= -k8s.io/apiextensions-apiserver v0.30.0 h1:jcZFKMqnICJfRxTgnC4E+Hpcq8UEhT8B2lhBcQ+6uAs= -k8s.io/apiextensions-apiserver v0.30.0/go.mod h1:N9ogQFGcrbWqAY9p2mUAL5mGxsLqwgtUce127VtRX5Y= -k8s.io/apimachinery v0.30.3 h1:q1laaWCmrszyQuSQCfNB8cFgCuDAoPszKY4ucAjDwHc= -k8s.io/apimachinery v0.30.3/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= -k8s.io/apiserver v0.30.0 h1:QCec+U72tMQ+9tR6A0sMBB5Vh6ImCEkoKkTDRABWq6M= -k8s.io/apiserver v0.30.0/go.mod h1:smOIBq8t0MbKZi7O7SyIpjPsiKJ8qa+llcFCluKyqiY= -k8s.io/cli-runtime v0.30.2 h1:ooM40eEJusbgHNEqnHziN9ZpLN5U4WcQGsdLKVxpkKE= -k8s.io/cli-runtime v0.30.2/go.mod h1:Y4g/2XezFyTATQUbvV5WaChoUGhojv/jZAtdp5Zkm0A= -k8s.io/client-go v0.30.2 h1:sBIVJdojUNPDU/jObC+18tXWcTJVcwyqS9diGdWHk50= -k8s.io/client-go v0.30.2/go.mod h1:JglKSWULm9xlJLx4KCkfLLQ7XwtlbflV6uFFSHTMgVs= -k8s.io/component-base v0.30.1 h1:bvAtlPh1UrdaZL20D9+sWxsJljMi0QZ3Lmw+kmZAaxQ= -k8s.io/component-base v0.30.1/go.mod h1:e/X9kDiOebwlI41AvBHuWdqFriSRrX50CdwA9TFaHLI= -k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= -k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/api v0.31.0 h1:b9LiSjR2ym/SzTOlfMHm1tr7/21aD7fSkqgD/CVJBCo= +k8s.io/api v0.31.0/go.mod h1:0YiFF+JfFxMM6+1hQei8FY8M7s1Mth+z/q7eF1aJkTE= +k8s.io/apiextensions-apiserver v0.31.0 h1:fZgCVhGwsclj3qCw1buVXCV6khjRzKC5eCFt24kyLSk= +k8s.io/apiextensions-apiserver v0.31.0/go.mod h1:b9aMDEYaEe5sdK+1T0KU78ApR/5ZVp4i56VacZYEHxk= +k8s.io/apimachinery v0.31.0 h1:m9jOiSr3FoSSL5WO9bjm1n6B9KROYYgNZOb4tyZ1lBc= +k8s.io/apimachinery v0.31.0/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/apiserver v0.31.0 h1:p+2dgJjy+bk+B1Csz+mc2wl5gHwvNkC9QJV+w55LVrY= +k8s.io/apiserver v0.31.0/go.mod h1:KI9ox5Yu902iBnnyMmy7ajonhKnkeZYJhTZ/YI+WEMk= +k8s.io/cli-runtime v0.31.0 h1:V2Q1gj1u3/WfhD475HBQrIYsoryg/LrhhK4RwpN+DhA= +k8s.io/cli-runtime v0.31.0/go.mod h1:vg3H94wsubuvWfSmStDbekvbla5vFGC+zLWqcf+bGDw= +k8s.io/client-go v0.31.0 h1:QqEJzNjbN2Yv1H79SsS+SWnXkBgVu4Pj3CJQgbx0gI8= +k8s.io/client-go v0.31.0/go.mod h1:Y9wvC76g4fLjmU0BA+rV+h2cncoadjvjjkkIGoTLcGU= +k8s.io/component-base v0.31.0 h1:/KIzGM5EvPNQcYgwq5NwoQBaOlVFrghoVGr8lG6vNRs= +k8s.io/component-base v0.31.0/go.mod h1:TYVuzI1QmN4L5ItVdMSXKvH7/DtvIuas5/mm8YT3rTo= +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-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= -k8s.io/kubectl v0.30.1 h1:sHFIRI3oP0FFZmBAVEE8ErjnTyXDPkBcvO88mH9RjuY= -k8s.io/kubectl v0.30.1/go.mod h1:7j+L0Cc38RYEcx+WH3y44jRBe1Q1jxdGPKkX0h4iDq0= -k8s.io/utils v0.0.0-20231127182322-b307cd553661 h1:FepOBzJ0GXm8t0su67ln2wAZjbQ6RxQGZDnzuLcrUTI= -k8s.io/utils v0.0.0-20231127182322-b307cd553661/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/kubectl v0.31.0 h1:kANwAAPVY02r4U4jARP/C+Q1sssCcN/1p9Nk+7BQKVg= +k8s.io/kubectl v0.31.0/go.mod h1:pB47hhFypGsaHAPjlwrNbvhXgmuAr01ZBvAIIUaI8d4= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y= @@ -2126,8 +2118,8 @@ modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM= +modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= @@ -2141,10 +2133,10 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 h1:XX3Ajgzov2RKUdc5jW3t5jwY7Bo7dcRm+tFxT+NfgY0= -sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3/go.mod h1:9n16EZKMhXBNSiUC5kSdFQJkdH3zbxS/JoO619G1VAY= -sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 h1:W6cLQc5pnqM7vh3b7HvGNfXrJ/xL6BDMS0v1V/HHg5U= -sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3/go.mod h1:JWP1Fj0VWGHyw3YUPjXSQnRnrwezrZSrApfX5S0nIag= +sigs.k8s.io/kustomize/api v0.17.2 h1:E7/Fjk7V5fboiuijoZHgs4aHuexi5Y2loXlVOAVAG5g= +sigs.k8s.io/kustomize/api v0.17.2/go.mod h1:UWTz9Ct+MvoeQsHcJ5e+vziRRkwimm3HytpZgIYqye0= +sigs.k8s.io/kustomize/kyaml v0.17.1 h1:TnxYQxFXzbmNG6gOINgGWQt09GghzgTP6mIurOgrLCQ= +sigs.k8s.io/kustomize/kyaml v0.17.1/go.mod h1:9V0mCjIEYjlXuCdYsSXvyoy2BTsLESH7TlGV81S282U= sigs.k8s.io/release-utils v0.7.7 h1:JKDOvhCk6zW8ipEOkpTGDH/mW3TI+XqtPp16aaQ79FU= sigs.k8s.io/release-utils v0.7.7/go.mod h1:iU7DGVNi3umZJ8q6aHyUFzsDUIaYwNnNKGHo3YE5E3s= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= diff --git a/helm/trivy/Chart.yaml b/helm/trivy/Chart.yaml index 3fc94a4b4968..3695c704a2e8 100644 --- a/helm/trivy/Chart.yaml +++ b/helm/trivy/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v2 name: trivy -version: 0.7.0 -appVersion: 0.37.2 +version: 0.8.0 +appVersion: 0.55.0 description: Trivy helm chart keywords: - scanner diff --git a/helm/trivy/templates/statefulset.yaml b/helm/trivy/templates/statefulset.yaml index efb90274fe2c..fef41a47da3c 100644 --- a/helm/trivy/templates/statefulset.yaml +++ b/helm/trivy/templates/statefulset.yaml @@ -17,7 +17,9 @@ spec: app.kubernetes.io/instance: {{ .Release.Name }} {{- if .Values.persistence.enabled }} volumeClaimTemplates: - - metadata: + - apiVersion: v1 + kind: PersistentVolumeClaim + metadata: name: data spec: resources: @@ -103,7 +105,7 @@ spec: scheme: HTTP path: /healthz port: trivy-http - initialDelaySeconds: 5 + initialDelaySeconds: {{ default 5 .Values.livenessProbe.initialDelaySeconds }} periodSeconds: 10 successThreshold: 1 failureThreshold: 10 diff --git a/helm/trivy/values.yaml b/helm/trivy/values.yaml index a969f10b7899..73d780d6a0b1 100644 --- a/helm/trivy/values.yaml +++ b/helm/trivy/values.yaml @@ -56,6 +56,11 @@ tolerations: [] ## Ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ podAnnotations: {} +# Allow Trivy to download the database before being killed. In some cases the conection to gsoci can be slow and take longer than the default +# Maybe we can remove this once we have Zot +livenessProbe: + initialDelaySeconds: 300 + trivy: # debugMode the flag to enable Trivy debug mode debugMode: false diff --git a/integration/client_server_test.go b/integration/client_server_test.go index c6dd7fb74dc5..07a4f086200c 100644 --- a/integration/client_server_test.go +++ b/integration/client_server_test.go @@ -32,6 +32,7 @@ type csArgs struct { Input string ClientToken string ClientTokenHeader string + PathPrefix string ListAllPackages bool Target string secretConfig string @@ -219,6 +220,13 @@ func TestClientServer(t *testing.T) { }, golden: "testdata/opensuse-tumbleweed.json.golden", }, + { + name: "sle micro rancher 5.4", + args: csArgs{ + Input: "testdata/fixtures/images/sle-micro-rancher-5.4_ndb.tar.gz", + }, + golden: "testdata/sl-micro-rancher5.4.json.golden", + }, { name: "photon 3.0", args: csArgs{ @@ -287,7 +295,7 @@ func TestClientServer(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - osArgs := setupClient(t, tt.args, addr, cacheDir, tt.golden) + osArgs := setupClient(t, tt.args, addr, cacheDir) if tt.args.secretConfig != "" { osArgs = append(osArgs, "--secret-config", tt.args.secretConfig) @@ -407,7 +415,7 @@ func TestClientServerWithFormat(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Setenv("AWS_REGION", "test-region") t.Setenv("AWS_ACCOUNT_ID", "123456789012") - osArgs := setupClient(t, tt.args, addr, cacheDir, tt.golden) + osArgs := setupClient(t, tt.args, addr, cacheDir) runTest(t, osArgs, tt.golden, "", tt.args.Format, runOptions{ override: overrideUID, @@ -435,7 +443,7 @@ func TestClientServerWithCycloneDX(t *testing.T) { addr, cacheDir := setup(t, setupOptions{}) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - osArgs := setupClient(t, tt.args, addr, cacheDir, tt.golden) + osArgs := setupClient(t, tt.args, addr, cacheDir) runTest(t, osArgs, tt.golden, "", types.FormatCycloneDX, runOptions{ fakeUUID: "3ff14136-e09f-4df9-80ea-%012d", }) @@ -443,7 +451,11 @@ func TestClientServerWithCycloneDX(t *testing.T) { } } -func TestClientServerWithToken(t *testing.T) { +func TestClientServerWithCustomOptions(t *testing.T) { + token := "token" + tokenHeader := "Trivy-Token" + pathPrefix := "prefix" + tests := []struct { name string args csArgs @@ -451,11 +463,12 @@ func TestClientServerWithToken(t *testing.T) { wantErr string }{ { - name: "alpine 3.9 with token", + name: "alpine 3.9 with token and prefix", args: csArgs{ Input: "testdata/fixtures/images/alpine-39.tar.gz", - ClientToken: "token", - ClientTokenHeader: "Trivy-Token", + ClientToken: token, + ClientTokenHeader: tokenHeader, + PathPrefix: pathPrefix, }, golden: "testdata/alpine-39.json.golden", }, @@ -464,7 +477,8 @@ func TestClientServerWithToken(t *testing.T) { args: csArgs{ Input: "testdata/fixtures/images/distroless-base.tar.gz", ClientToken: "invalidtoken", - ClientTokenHeader: "Trivy-Token", + ClientTokenHeader: tokenHeader, + PathPrefix: pathPrefix, }, wantErr: "twirp error unauthenticated: invalid token", }, @@ -472,23 +486,33 @@ func TestClientServerWithToken(t *testing.T) { name: "invalid token header", args: csArgs{ Input: "testdata/fixtures/images/distroless-base.tar.gz", - ClientToken: "token", + ClientToken: token, ClientTokenHeader: "Unknown-Header", + PathPrefix: pathPrefix, }, wantErr: "twirp error unauthenticated: invalid token", }, + { + name: "wrong path prefix", + args: csArgs{ + Input: "testdata/fixtures/images/distroless-base.tar.gz", + ClientToken: token, + ClientTokenHeader: tokenHeader, + PathPrefix: "wrong", + }, + wantErr: "HTTP status code 404", + }, } - serverToken := "token" - serverTokenHeader := "Trivy-Token" addr, cacheDir := setup(t, setupOptions{ - token: serverToken, - tokenHeader: serverTokenHeader, + token: token, + tokenHeader: tokenHeader, + pathPrefix: pathPrefix, }) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - osArgs := setupClient(t, tt.args, addr, cacheDir, tt.golden) + osArgs := setupClient(t, tt.args, addr, cacheDir) runTest(t, osArgs, tt.golden, "", types.FormatJSON, runOptions{ override: overrideUID, wantErr: tt.wantErr, @@ -515,7 +539,7 @@ func TestClientServerWithRedis(t *testing.T) { golden := "testdata/alpine-39.json.golden" t.Run("alpine 3.9", func(t *testing.T) { - osArgs := setupClient(t, testArgs, addr, cacheDir, golden) + osArgs := setupClient(t, testArgs, addr, cacheDir) // Run Trivy client runTest(t, osArgs, golden, "", types.FormatJSON, runOptions{ @@ -527,7 +551,7 @@ func TestClientServerWithRedis(t *testing.T) { require.NoError(t, redisC.Terminate(ctx)) t.Run("sad path", func(t *testing.T) { - osArgs := setupClient(t, testArgs, addr, cacheDir, golden) + osArgs := setupClient(t, testArgs, addr, cacheDir) // Run Trivy client runTest(t, osArgs, "", "", types.FormatJSON, runOptions{ @@ -539,6 +563,7 @@ func TestClientServerWithRedis(t *testing.T) { type setupOptions struct { token string tokenHeader string + pathPrefix string cacheBackend string } @@ -556,7 +581,7 @@ func setup(t *testing.T, options setupOptions) (string, string) { addr := fmt.Sprintf("localhost:%d", port) go func() { - osArgs := setupServer(addr, options.token, options.tokenHeader, cacheDir, options.cacheBackend) + osArgs := setupServer(addr, options.token, options.tokenHeader, options.pathPrefix, cacheDir, options.cacheBackend) // Run Trivy server require.NoError(t, execute(osArgs)) @@ -569,22 +594,20 @@ func setup(t *testing.T, options setupOptions) (string, string) { return addr, cacheDir } -func setupServer(addr, token, tokenHeader, cacheDir, cacheBackend string) []string { +func setupServer(addr, token, tokenHeader, pathPrefix, cacheDir, cacheBackend string) []string { osArgs := []string{ "--cache-dir", cacheDir, "server", - "--skip-update", + "--skip-db-update", "--listen", addr, } if token != "" { - osArgs = append(osArgs, []string{ - "--token", - token, - "--token-header", - tokenHeader, - }...) + osArgs = append(osArgs, "--token", token, "--token-header", tokenHeader) + } + if pathPrefix != "" { + osArgs = append(osArgs, "--path-prefix", pathPrefix) } if cacheBackend != "" { osArgs = append(osArgs, "--cache-backend", cacheBackend) @@ -592,14 +615,14 @@ func setupServer(addr, token, tokenHeader, cacheDir, cacheBackend string) []stri return osArgs } -func setupClient(t *testing.T, c csArgs, addr string, cacheDir string, golden string) []string { +func setupClient(t *testing.T, c csArgs, addr string, cacheDir string) []string { + t.Helper() if c.Command == "" { c.Command = "image" } if c.RemoteAddrOption == "" { c.RemoteAddrOption = "--server" } - t.Helper() osArgs := []string{ "--cache-dir", cacheDir, @@ -639,6 +662,9 @@ func setupClient(t *testing.T, c csArgs, addr string, cacheDir string, golden st if c.ClientToken != "" { osArgs = append(osArgs, "--token", c.ClientToken, "--token-header", c.ClientTokenHeader) } + if c.PathPrefix != "" { + osArgs = append(osArgs, "--path-prefix", c.PathPrefix) + } if c.Input != "" { osArgs = append(osArgs, "--input", c.Input) } diff --git a/integration/convert_test.go b/integration/convert_test.go index 803ba538dcba..e7d2a5f5e6fe 100644 --- a/integration/convert_test.go +++ b/integration/convert_test.go @@ -11,9 +11,11 @@ import ( func TestConvert(t *testing.T) { type args struct { - input string - format string - scanners string + input string + format string + scanners string + showSuppressed bool + listAllPkgs bool } tests := []struct { name string @@ -37,6 +39,16 @@ func TestConvert(t *testing.T) { }, golden: "testdata/npm-cyclonedx.json.golden", }, + { + name: "npm with suppressed vulnerability", + args: args{ + input: "testdata/fixtures/convert/npm-with-suppressed.json.golden", + format: "json", + showSuppressed: true, + listAllPkgs: true, + }, + golden: "testdata/fixtures/convert/npm-with-suppressed.json.golden", + }, } for _, tt := range tests { @@ -50,6 +62,14 @@ func TestConvert(t *testing.T) { tt.args.format, } + if tt.args.showSuppressed { + osArgs = append(osArgs, "--show-suppressed") + } + + if tt.args.listAllPkgs { + osArgs = append(osArgs, "--list-all-pkgs") + } + // Set up the output file outputFile := filepath.Join(t.TempDir(), "output.json") if *update { diff --git a/integration/docker_engine_test.go b/integration/docker_engine_test.go index 5b62d391eead..a543d22e466f 100644 --- a/integration/docker_engine_test.go +++ b/integration/docker_engine_test.go @@ -1,19 +1,16 @@ //go:build integration -// +build integration package integration import ( "context" - "io" "os" "strings" "testing" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/types" - "github.com/docker/docker/api/types/image" - "github.com/docker/docker/client" "github.com/stretchr/testify/require" ) @@ -23,7 +20,6 @@ func TestDockerEngine(t *testing.T) { } tests := []struct { name string - imageTag string invalidImage bool ignoreUnfixed bool ignoreStatus []string @@ -34,10 +30,9 @@ func TestDockerEngine(t *testing.T) { wantErr string }{ { - name: "alpine:3.9", - imageTag: "ghcr.io/aquasecurity/trivy-test-images:alpine-39", - input: "testdata/fixtures/images/alpine-39.tar.gz", - golden: "testdata/alpine-39.json.golden", + name: "alpine:3.9", + input: "testdata/fixtures/images/alpine-39.tar.gz", + golden: "testdata/alpine-39.json.golden", }, { name: "alpine:3.9, with high and critical severity", @@ -45,13 +40,11 @@ func TestDockerEngine(t *testing.T) { "HIGH", "CRITICAL", }, - imageTag: "ghcr.io/aquasecurity/trivy-test-images:alpine-39", - input: "testdata/fixtures/images/alpine-39.tar.gz", - golden: "testdata/alpine-39-high-critical.json.golden", + input: "testdata/fixtures/images/alpine-39.tar.gz", + golden: "testdata/alpine-39-high-critical.json.golden", }, { - name: "alpine:3.9, with .trivyignore", - imageTag: "ghcr.io/aquasecurity/trivy-test-images:alpine-39", + name: "alpine:3.9, with .trivyignore", ignoreIDs: []string{ "CVE-2019-1549", "CVE-2019-14697", @@ -60,161 +53,141 @@ func TestDockerEngine(t *testing.T) { golden: "testdata/alpine-39-ignore-cveids.json.golden", }, { - name: "alpine:3.10", - imageTag: "ghcr.io/aquasecurity/trivy-test-images:alpine-310", - input: "testdata/fixtures/images/alpine-310.tar.gz", - golden: "testdata/alpine-310.json.golden", + name: "alpine:3.10", + input: "testdata/fixtures/images/alpine-310.tar.gz", + golden: "testdata/alpine-310.json.golden", }, { - name: "amazonlinux:1", - imageTag: "ghcr.io/aquasecurity/trivy-test-images:amazon-1", - input: "testdata/fixtures/images/amazon-1.tar.gz", - golden: "testdata/amazon-1.json.golden", + name: "amazonlinux:1", + input: "testdata/fixtures/images/amazon-1.tar.gz", + golden: "testdata/amazon-1.json.golden", }, { - name: "amazonlinux:2", - imageTag: "ghcr.io/aquasecurity/trivy-test-images:amazon-2", - input: "testdata/fixtures/images/amazon-2.tar.gz", - golden: "testdata/amazon-2.json.golden", + name: "amazonlinux:2", + input: "testdata/fixtures/images/amazon-2.tar.gz", + golden: "testdata/amazon-2.json.golden", }, { - name: "almalinux 8", - imageTag: "ghcr.io/aquasecurity/trivy-test-images:almalinux-8", - input: "testdata/fixtures/images/almalinux-8.tar.gz", - golden: "testdata/almalinux-8.json.golden", + name: "almalinux 8", + input: "testdata/fixtures/images/almalinux-8.tar.gz", + golden: "testdata/almalinux-8.json.golden", }, { - name: "rocky linux 8", - imageTag: "ghcr.io/aquasecurity/trivy-test-images:rockylinux-8", - input: "testdata/fixtures/images/rockylinux-8.tar.gz", - golden: "testdata/rockylinux-8.json.golden", + name: "rocky linux 8", + input: "testdata/fixtures/images/rockylinux-8.tar.gz", + golden: "testdata/rockylinux-8.json.golden", }, { - name: "centos 6", - imageTag: "ghcr.io/aquasecurity/trivy-test-images:centos-6", - input: "testdata/fixtures/images/centos-6.tar.gz", - golden: "testdata/centos-6.json.golden", + name: "centos 6", + input: "testdata/fixtures/images/centos-6.tar.gz", + golden: "testdata/centos-6.json.golden", }, { - name: "centos 7", - imageTag: "ghcr.io/aquasecurity/trivy-test-images:centos-7", - input: "testdata/fixtures/images/centos-7.tar.gz", - golden: "testdata/centos-7.json.golden", + name: "centos 7", + input: "testdata/fixtures/images/centos-7.tar.gz", + golden: "testdata/centos-7.json.golden", }, { name: "centos 7, with --ignore-unfixed option", - imageTag: "ghcr.io/aquasecurity/trivy-test-images:centos-7", ignoreUnfixed: true, input: "testdata/fixtures/images/centos-7.tar.gz", golden: "testdata/centos-7-ignore-unfixed.json.golden", }, { name: "centos 7, with --ignore-status option", - imageTag: "ghcr.io/aquasecurity/trivy-test-images:centos-7", ignoreStatus: []string{"will_not_fix"}, input: "testdata/fixtures/images/centos-7.tar.gz", golden: "testdata/centos-7-ignore-unfixed.json.golden", }, { name: "centos 7, with --ignore-unfixed option, with medium severity", - imageTag: "ghcr.io/aquasecurity/trivy-test-images:centos-7", ignoreUnfixed: true, severity: []string{"MEDIUM"}, input: "testdata/fixtures/images/centos-7.tar.gz", golden: "testdata/centos-7-medium.json.golden", }, { - name: "registry.redhat.io/ubi7", - imageTag: "ghcr.io/aquasecurity/trivy-test-images:ubi-7", - input: "testdata/fixtures/images/ubi-7.tar.gz", - golden: "testdata/ubi-7.json.golden", + name: "registry.redhat.io/ubi7", + input: "testdata/fixtures/images/ubi-7.tar.gz", + golden: "testdata/ubi-7.json.golden", }, { - name: "debian buster/10", - imageTag: "ghcr.io/aquasecurity/trivy-test-images:debian-buster", - input: "testdata/fixtures/images/debian-buster.tar.gz", - golden: "testdata/debian-buster.json.golden", + name: "debian buster/10", + input: "testdata/fixtures/images/debian-buster.tar.gz", + golden: "testdata/debian-buster.json.golden", }, { name: "debian buster/10, with --ignore-unfixed option", ignoreUnfixed: true, - imageTag: "ghcr.io/aquasecurity/trivy-test-images:debian-buster", input: "testdata/fixtures/images/debian-buster.tar.gz", golden: "testdata/debian-buster-ignore-unfixed.json.golden", }, { name: "debian buster/10, with --ignore-status option", ignoreStatus: []string{"affected"}, - imageTag: "ghcr.io/aquasecurity/trivy-test-images:debian-buster", input: "testdata/fixtures/images/debian-buster.tar.gz", golden: "testdata/debian-buster-ignore-unfixed.json.golden", }, { - name: "debian stretch/9", - imageTag: "ghcr.io/aquasecurity/trivy-test-images:debian-stretch", - input: "testdata/fixtures/images/debian-stretch.tar.gz", - golden: "testdata/debian-stretch.json.golden", + name: "debian stretch/9", + input: "testdata/fixtures/images/debian-stretch.tar.gz", + golden: "testdata/debian-stretch.json.golden", }, { - name: "distroless base", - imageTag: "ghcr.io/aquasecurity/trivy-test-images:distroless-base", - input: "testdata/fixtures/images/distroless-base.tar.gz", - golden: "testdata/distroless-base.json.golden", + name: "distroless base", + input: "testdata/fixtures/images/distroless-base.tar.gz", + golden: "testdata/distroless-base.json.golden", }, { - name: "distroless python2.7", - imageTag: "ghcr.io/aquasecurity/trivy-test-images:distroless-python27", - input: "testdata/fixtures/images/distroless-python27.tar.gz", - golden: "testdata/distroless-python27.json.golden", + name: "distroless python2.7", + input: "testdata/fixtures/images/distroless-python27.tar.gz", + golden: "testdata/distroless-python27.json.golden", }, { - name: "oracle linux 8", - imageTag: "ghcr.io/aquasecurity/trivy-test-images:oraclelinux-8", - input: "testdata/fixtures/images/oraclelinux-8.tar.gz", - golden: "testdata/oraclelinux-8.json.golden", + name: "oracle linux 8", + input: "testdata/fixtures/images/oraclelinux-8.tar.gz", + golden: "testdata/oraclelinux-8.json.golden", }, { - name: "ubuntu 18.04", - imageTag: "ghcr.io/aquasecurity/trivy-test-images:ubuntu-1804", - input: "testdata/fixtures/images/ubuntu-1804.tar.gz", - golden: "testdata/ubuntu-1804.json.golden", + name: "ubuntu 18.04", + input: "testdata/fixtures/images/ubuntu-1804.tar.gz", + golden: "testdata/ubuntu-1804.json.golden", }, { name: "ubuntu 18.04, with --ignore-unfixed option", - imageTag: "ghcr.io/aquasecurity/trivy-test-images:ubuntu-1804", ignoreUnfixed: true, input: "testdata/fixtures/images/ubuntu-1804.tar.gz", golden: "testdata/ubuntu-1804-ignore-unfixed.json.golden", }, { - name: "opensuse leap 15.1", - imageTag: "ghcr.io/aquasecurity/trivy-test-images:opensuse-leap-151", - input: "testdata/fixtures/images/opensuse-leap-151.tar.gz", - golden: "testdata/opensuse-leap-151.json.golden", + name: "opensuse leap 15.1", + input: "testdata/fixtures/images/opensuse-leap-151.tar.gz", + golden: "testdata/opensuse-leap-151.json.golden", }, { - name: "opensuse tumbleweed", - imageTag: "ghcr.io/aquasecurity/trivy-test-images:opensuse-tumbleweed", - input: "testdata/fixtures/images/opensuse-tumbleweed.tar.gz", - golden: "testdata/opensuse-tumbleweed.json.golden", + name: "opensuse tumbleweed", + input: "testdata/fixtures/images/opensuse-tumbleweed.tar.gz", + golden: "testdata/opensuse-tumbleweed.json.golden", }, { - name: "photon 3.0", - imageTag: "ghcr.io/aquasecurity/trivy-test-images:photon-30", - input: "testdata/fixtures/images/photon-30.tar.gz", - golden: "testdata/photon-30.json.golden", + name: "sle micro rancher 5.4", + input: "testdata/fixtures/images/sle-micro-rancher-5.4_ndb.tar.gz", + golden: "testdata/sl-micro-rancher5.4.json.golden", }, { - name: "CBL-Mariner 1.0", - imageTag: "ghcr.io/aquasecurity/trivy-test-images:mariner-1.0", - input: "testdata/fixtures/images/mariner-1.0.tar.gz", - golden: "testdata/mariner-1.0.json.golden", + name: "photon 3.0", + input: "testdata/fixtures/images/photon-30.tar.gz", + golden: "testdata/photon-30.json.golden", }, { - name: "busybox with Cargo.lock", - imageTag: "ghcr.io/aquasecurity/trivy-test-images:busybox-with-lockfile", - input: "testdata/fixtures/images/busybox-with-lockfile.tar.gz", - golden: "testdata/busybox-with-lockfile.json.golden", + name: "CBL-Mariner 1.0", + input: "testdata/fixtures/images/mariner-1.0.tar.gz", + golden: "testdata/mariner-1.0.json.golden", + }, + { + name: "busybox with Cargo.lock", + input: "testdata/fixtures/images/busybox-with-lockfile.tar.gz", + golden: "testdata/busybox-with-lockfile.json.golden", }, { name: "sad path, invalid image", @@ -233,44 +206,27 @@ func TestDockerEngine(t *testing.T) { ctx := context.Background() defer ctx.Done() - cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) - require.NoError(t, err) + cli := testutil.NewDockerClient(t) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if !tt.invalidImage { testfile, err := os.Open(tt.input) require.NoError(t, err, tt.name) + defer testfile.Close() - // ensure image doesnt already exists - _, _ = cli.ImageRemove(ctx, tt.input, image.RemoveOptions{ - Force: true, - PruneChildren: true, - }) + // Ensure image doesn't already exist + cli.ImageRemove(t, ctx, tt.input) - // load image into docker engine - res, err := cli.ImageLoad(ctx, testfile, true) - require.NoError(t, err, tt.name) - if _, err := io.Copy(io.Discard, res.Body); err != nil { - require.NoError(t, err, tt.name) - } - defer res.Body.Close() + // Load image into docker engine + loadedImage := cli.ImageLoad(t, ctx, tt.input) - // tag our image to something unique - err = cli.ImageTag(ctx, tt.imageTag, tt.input) + // Tag our image to something unique + err = cli.ImageTag(ctx, loadedImage, tt.input) require.NoError(t, err, tt.name) - // cleanup - t.Cleanup(func() { - _, _ = cli.ImageRemove(ctx, tt.input, image.RemoveOptions{ - Force: true, - PruneChildren: true, - }) - _, _ = cli.ImageRemove(ctx, tt.imageTag, image.RemoveOptions{ - Force: true, - PruneChildren: true, - }) - }) + // Cleanup + t.Cleanup(func() { cli.ImageRemove(t, ctx, tt.input) }) } osArgs := []string{ @@ -303,7 +259,7 @@ func TestDockerEngine(t *testing.T) { } if len(tt.ignoreIDs) != 0 { trivyIgnore := ".trivyignore" - err = os.WriteFile(trivyIgnore, []byte(strings.Join(tt.ignoreIDs, "\n")), 0444) + err := os.WriteFile(trivyIgnore, []byte(strings.Join(tt.ignoreIDs, "\n")), 0444) require.NoError(t, err, "failed to write .trivyignore") defer os.Remove(trivyIgnore) } @@ -314,7 +270,8 @@ func TestDockerEngine(t *testing.T) { wantErr: tt.wantErr, // Container field was removed in Docker Engine v26.0 // cf. https://github.com/docker/cli/blob/v26.1.3/docs/deprecated.md#container-and-containerconfig-fields-in-image-inspect - override: overrideFuncs(overrideUID, func(t *testing.T, want, _ *types.Report) { + override: overrideFuncs(overrideUID, func(t *testing.T, want, got *types.Report) { + got.Metadata.ImageConfig.Container = "" want.Metadata.ImageConfig.Container = "" }), }) diff --git a/integration/integration_test.go b/integration/integration_test.go index c263ec2fc51e..96c8f54a67e9 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -186,7 +186,6 @@ func readCycloneDX(t *testing.T, filePath string) *cdx.BOM { return (*bom.Components)[i].Name < (*bom.Components)[j].Name }) for i := range *bom.Components { - (*bom.Components)[i].BOMRef = "" sort.Slice(*(*bom.Components)[i].Properties, func(ii, jj int) bool { return (*(*bom.Components)[i].Properties)[ii].Name < (*(*bom.Components)[i].Properties)[jj].Name }) diff --git a/integration/plugin_test.go b/integration/plugin_test.go new file mode 100644 index 000000000000..e4a6bf4d0014 --- /dev/null +++ b/integration/plugin_test.go @@ -0,0 +1,111 @@ +//go:build integration + +package integration + +import ( + "io" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/utils/fsutils" +) + +func TestPlugin(t *testing.T) { + tests := []struct { + name string + plugin string + pluginArgs string + golden string + }{ + { + name: "count plugin installed from `index`", + plugin: "count@v0.2.0", + golden: "testdata/count-0.2.0-plugin.txt.golden", + }, + { + name: "count plugin installed from github archive", + plugin: "https://github.com/aquasecurity/trivy-plugin-count/archive/refs/tags/v0.1.0.zip", + pluginArgs: "--published-before=2020-01-01", + golden: "testdata/count-0.1.0-plugin-with-before-flag.txt.golden", + }, + } + + // Set up testing DB + cacheDir := initDB(t) + tempStdOut := setTempStdout(t) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // We can overwrite stdout for `_default_Manager` only once. + // So we need to clear the temporary stdout file before each test case. + clearFile(t, tempStdOut) + + t.Setenv("XDG_DATA_HOME", t.TempDir()) + + // Install plugin + err := execute([]string{ + "plugin", + "install", + tt.plugin, + }) + require.NoError(t, err) + + // Get list of plugins + err = execute([]string{ + "plugin", + "list", + }) + require.NoError(t, err) + + // Run Trivy with plugin as output + args := []string{ + "--cache-dir", + cacheDir, + "fs", + "-f", + "json", + "-o", + "plugin=count", + "testdata/fixtures/repo/pip", + } + + if tt.pluginArgs != "" { + args = append(args, "--output-plugin-arg", tt.pluginArgs) + } + + err = execute(args) + + if *update { + fsutils.CopyFile(tempStdOut.Name(), tt.golden) + } + + compareRawFiles(t, tt.golden, tempStdOut.Name()) + }) + } +} + +func setTempStdout(t *testing.T) *os.File { + tmpFile := filepath.Join(t.TempDir(), "output.txt") + f, err := os.Create(tmpFile) + require.NoError(t, err) + + // Overwrite Stdout to get output of plugin + defaultStdout := os.Stdout + os.Stdout = f + t.Cleanup(func() { + os.Stdout = defaultStdout + f.Close() + }) + return f +} + +func clearFile(t *testing.T, file *os.File) { + _, err := file.Seek(0, io.SeekStart) + require.NoError(t, err) + + _, err = file.Write([]byte{}) + require.NoError(t, err) +} diff --git a/integration/registry_test.go b/integration/registry_test.go index 9a2570062e56..3831ea9a280e 100644 --- a/integration/registry_test.go +++ b/integration/registry_test.go @@ -10,7 +10,6 @@ import ( "crypto/x509" "encoding/json" "fmt" - "github.com/aquasecurity/trivy/pkg/types" "io" "net/http" "net/url" @@ -18,6 +17,8 @@ import ( "path/filepath" "testing" + "github.com/aquasecurity/trivy/pkg/types" + dockercontainer "github.com/docker/docker/api/types/container" "github.com/docker/go-connections/nat" "github.com/google/go-containerregistry/pkg/authn" @@ -152,32 +153,59 @@ func TestRegistry(t *testing.T) { name string imageName string imageFile string + os string option registryOption golden string wantErr string }{ { - name: "happy path with username/password", + name: "authenticate with username/password", imageName: "alpine:3.10", imageFile: "testdata/fixtures/images/alpine-310.tar.gz", + os: "alpine 3.10.2", option: registryOption{ AuthURL: authURL, Username: authUsername, Password: authPassword, }, - golden: "testdata/alpine-310-registry.json.golden", + golden: "testdata/alpine-310.json.golden", }, { - name: "happy path with registry token", + name: "authenticate with registry token", imageName: "alpine:3.10", imageFile: "testdata/fixtures/images/alpine-310.tar.gz", + os: "alpine 3.10.2", option: registryOption{ AuthURL: authURL, Username: authUsername, Password: authPassword, RegistryToken: true, }, - golden: "testdata/alpine-310-registry.json.golden", + golden: "testdata/alpine-310.json.golden", + }, + { + name: "amazonlinux 2", + imageName: "amazonlinux:2", + imageFile: "testdata/fixtures/images/amazon-2.tar.gz", + os: "amazon 2 (Karoo)", + option: registryOption{ + AuthURL: authURL, + Username: authUsername, + Password: authPassword, + }, + golden: "testdata/amazon-2.json.golden", + }, + { + name: "debian buster", + imageName: "debian:buster", + imageFile: "testdata/fixtures/images/debian-buster.tar.gz", + os: "debian 10.1", + option: registryOption{ + AuthURL: authURL, + Username: authUsername, + Password: authPassword, + }, + golden: "testdata/debian-buster.json.golden", }, { name: "sad path", @@ -187,25 +215,25 @@ func TestRegistry(t *testing.T) { }, } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - s := fmt.Sprintf("%s/%s", registryURL.Host, tc.imageName) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := fmt.Sprintf("%s/%s", registryURL.Host, tt.imageName) imageRef, err := name.ParseReference(s) require.NoError(t, err) // Load a test image from the tar file, tag it and push to the test registry. - err = replicateImage(imageRef, tc.imageFile, auth) + err = replicateImage(imageRef, tt.imageFile, auth) require.NoError(t, err) - osArgs, err := scan(t, imageRef, baseDir, tc.golden, tc.option) + osArgs, err := scan(t, imageRef, baseDir, tt.option) // Run Trivy - runTest(t, osArgs, tc.golden, "", types.FormatJSON, runOptions{ - wantErr: tc.wantErr, - override: overrideFuncs(overrideUID, func(t *testing.T, _, got *types.Report) { - got.ArtifactName = tc.imageName - for i := range got.Results { - got.Results[i].Target = fmt.Sprintf("%s (alpine 3.10.2)", tc.imageName) + runTest(t, osArgs, tt.golden, "", types.FormatJSON, runOptions{ + wantErr: tt.wantErr, + override: overrideFuncs(overrideUID, func(t *testing.T, want, got *types.Report) { + want.ArtifactName = s + for i := range want.Results { + want.Results[i].Target = fmt.Sprintf("%s (%s)", s, tt.os) } }), }) @@ -213,7 +241,7 @@ func TestRegistry(t *testing.T) { } } -func scan(t *testing.T, imageRef name.Reference, baseDir, goldenFile string, opt registryOption) ([]string, error) { +func scan(t *testing.T, imageRef name.Reference, baseDir string, opt registryOption) ([]string, error) { // Set up testing DB cacheDir := initDB(t) @@ -232,6 +260,8 @@ func scan(t *testing.T, imageRef name.Reference, baseDir, goldenFile string, opt "image", "--format", "json", + "--image-src", + "remote", "--skip-update", imageRef.Name(), } diff --git a/integration/sbom_test.go b/integration/sbom_test.go index e3ee5b89cc3d..e887f1520e68 100644 --- a/integration/sbom_test.go +++ b/integration/sbom_test.go @@ -25,6 +25,7 @@ func TestSBOM(t *testing.T) { name string args args golden string + fakeUUID string override OverrideFunc }{ { @@ -57,6 +58,16 @@ func TestSBOM(t *testing.T) { }, golden: "testdata/fluentd-multiple-lockfiles.json.golden", }, + { + name: "scan SBOM into SBOM", + args: args{ + input: "testdata/fixtures/sbom/fluentd-multiple-lockfiles-cyclonedx.json", + format: "cyclonedx", + artifactType: "cyclonedx", + }, + fakeUUID: "3ff14136-e09f-4df9-80ea-%012d", + golden: "testdata/fluentd-multiple-lockfiles-short.cdx.json.golden", + }, { name: "minikube KBOM", args: args{ @@ -165,6 +176,7 @@ func TestSBOM(t *testing.T) { // Run "trivy sbom" runTest(t, osArgs, tt.golden, outputFile, types.Format(tt.args.format), runOptions{ override: overrideFuncs(overrideSBOMReport, overrideUID, tt.override), + fakeUUID: tt.fakeUUID, }) }) } diff --git a/integration/standalone_tar_test.go b/integration/standalone_tar_test.go index 2cb372b86b0a..039293cbfd56 100644 --- a/integration/standalone_tar_test.go +++ b/integration/standalone_tar_test.go @@ -8,6 +8,7 @@ import ( "strings" "testing" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/types" "github.com/stretchr/testify/require" @@ -15,13 +16,14 @@ import ( func TestTar(t *testing.T) { type args struct { - IgnoreUnfixed bool - Severity []string - IgnoreIDs []string - Format types.Format - Input string - SkipDirs []string - SkipFiles []string + IgnoreUnfixed bool + Severity []string + IgnoreIDs []string + Format types.Format + Input string + SkipDirs []string + SkipFiles []string + DetectionPriority ftypes.DetectionPriority } tests := []struct { name string @@ -240,7 +242,7 @@ func TestTar(t *testing.T) { golden: "testdata/centos-7.json.golden", }, { - name: "centos 7with --ignore-unfixed option", + name: "centos 7 with --ignore-unfixed option", args: args{ IgnoreUnfixed: true, Format: types.FormatJSON, @@ -274,6 +276,15 @@ func TestTar(t *testing.T) { }, golden: "testdata/ubi-7.json.golden", }, + { + name: "ubi 7 with comprehensive priority", + args: args{ + Format: types.FormatJSON, + Input: "testdata/fixtures/images/ubi-7.tar.gz", + DetectionPriority: ftypes.PriorityComprehensive, + }, + golden: "testdata/ubi-7-comprehensive.json.golden", + }, { name: "almalinux 8", args: args{ @@ -330,6 +341,14 @@ func TestTar(t *testing.T) { }, golden: "testdata/opensuse-tumbleweed.json.golden", }, + { + name: "sle micro rancher 5.4", + args: args{ + Format: types.FormatJSON, + Input: "testdata/fixtures/images/sle-micro-rancher-5.4_ndb.tar.gz", + }, + golden: "testdata/sl-micro-rancher5.4.json.golden", + }, { name: "photon 3.0", args: args{ @@ -380,7 +399,7 @@ func TestTar(t *testing.T) { "-q", "--format", string(tt.args.Format), - "--skip-update", + "--skip-db-update", } if tt.args.IgnoreUnfixed { @@ -411,6 +430,10 @@ func TestTar(t *testing.T) { } } + if tt.args.DetectionPriority != "" { + osArgs = append(osArgs, "--detection-priority", string(tt.args.DetectionPriority)) + } + // Run Trivy runTest(t, osArgs, tt.golden, "", tt.args.Format, runOptions{}) }) diff --git a/integration/testdata/alpine-310-registry.json.golden b/integration/testdata/alpine-310-registry.json.golden deleted file mode 100644 index 51100d633f53..000000000000 --- a/integration/testdata/alpine-310-registry.json.golden +++ /dev/null @@ -1,374 +0,0 @@ -{ - "SchemaVersion": 2, - "CreatedAt": "2021-08-25T12:20:30.000000005Z", - "ArtifactName": "alpine:3.10", - "ArtifactType": "container_image", - "Metadata": { - "OS": { - "Family": "alpine", - "Name": "3.10.2", - "EOSL": true - }, - "ImageID": "sha256:961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4", - "DiffIDs": [ - "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" - ], - "RepoTags": [ - "alpine:3.10" - ], - "RepoDigests": [ - "alpine@sha256:b1c5a500182b21d0bfa5a584a8526b56d8be316f89e87d951be04abed2446e60" - ], - "ImageConfig": { - "architecture": "amd64", - "container": "0a80155a31551fcc1a36fccbbda79fcd3f0b1c7d270653d00310e6e2217c57e6", - "created": "2019-08-20T20:19:55.211423266Z", - "docker_version": "18.06.1-ce", - "history": [ - { - "created": "2019-08-20T20:19:55.062606894Z", - "created_by": "/bin/sh -c #(nop) ADD file:fe64057fbb83dccb960efabbf1cd8777920ef279a7fa8dbca0a8801c651bdf7c in / " - }, - { - "created": "2019-08-20T20:19:55.211423266Z", - "created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", - "empty_layer": true - } - ], - "os": "linux", - "rootfs": { - "type": "layers", - "diff_ids": [ - "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" - ] - }, - "config": { - "Cmd": [ - "/bin/sh" - ], - "Env": [ - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" - ], - "Image": "sha256:06f4121dff4d0123ce11bd2e44f48da9ba9ddcd23ae376ea1f363f63ea0849b5", - "ArgsEscaped": true - } - } - }, - "Results": [ - { - "Target": "alpine:3.10 (alpine 3.10.2)", - "Class": "os-pkgs", - "Type": "alpine", - "Vulnerabilities": [ - { - "VulnerabilityID": "CVE-2019-1549", - "PkgID": "libcrypto1.1@1.1.1c-r0", - "PkgName": "libcrypto1.1", - "PkgIdentifier": { - "PURL": "pkg:apk/alpine/libcrypto1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2" - }, - "InstalledVersion": "1.1.1c-r0", - "FixedVersion": "1.1.1d-r0", - "Status": "fixed", - "Layer": { - "Digest": "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", - "DiffID": "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" - }, - "SeveritySource": "nvd", - "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-1549", - "DataSource": { - "ID": "alpine", - "Name": "Alpine Secdb", - "URL": "https://secdb.alpinelinux.org/" - }, - "Title": "openssl: information disclosure in fork()", - "Description": "OpenSSL 1.1.1 introduced a rewritten random number generator (RNG). This was intended to include protection in the event of a fork() system call in order to ensure that the parent and child processes did not share the same RNG state. However this protection was not being used in the default case. A partial mitigation for this issue is that the output from a high precision timer is mixed into the RNG state so the likelihood of a parent and child process sharing state is significantly reduced. If an application already calls OPENSSL_init_crypto() explicitly using OPENSSL_INIT_ATFORK then this problem does not occur at all. Fixed in OpenSSL 1.1.1d (Affected 1.1.1-1.1.1c).", - "Severity": "MEDIUM", - "CweIDs": [ - "CWE-330" - ], - "VendorSeverity": { - "amazon": 2, - "nvd": 2, - "oracle-oval": 2, - "photon": 2, - "redhat": 1, - "ubuntu": 1 - }, - "CVSS": { - "nvd": { - "V2Vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N", - "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", - "V2Score": 5, - "V3Score": 5.3 - }, - "redhat": { - "V3Vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N", - "V3Score": 4.8 - } - }, - "References": [ - "https://access.redhat.com/security/cve/CVE-2019-1549", - "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1549", - "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=1b0fe00e2704b5e20334a16d3c9099d1ba2ef1be", - "https://linux.oracle.com/cve/CVE-2019-1549.html", - "https://linux.oracle.com/errata/ELSA-2020-1840.html", - "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/GY6SNRJP2S7Y42GIIDO3HXPNMDYN2U3A/", - "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZN4VVQJ3JDCHGIHV4Y2YTXBYQZ6PWQ7E/", - "https://seclists.org/bugtraq/2019/Oct/1", - "https://security.netapp.com/advisory/ntap-20190919-0002/", - "https://support.f5.com/csp/article/K44070243", - "https://support.f5.com/csp/article/K44070243?utm_source=f5support\u0026amp;utm_medium=RSS", - "https://ubuntu.com/security/notices/USN-4376-1", - "https://usn.ubuntu.com/4376-1/", - "https://www.debian.org/security/2019/dsa-4539", - "https://www.openssl.org/news/secadv/20190910.txt", - "https://www.oracle.com/security-alerts/cpuapr2020.html", - "https://www.oracle.com/security-alerts/cpujan2020.html", - "https://www.oracle.com/security-alerts/cpujul2020.html", - "https://www.oracle.com/security-alerts/cpuoct2020.html", - "https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html" - ], - "PublishedDate": "2019-09-10T17:15:00Z", - "LastModifiedDate": "2020-10-20T22:15:00Z" - }, - { - "VulnerabilityID": "CVE-2019-1551", - "PkgID": "libcrypto1.1@1.1.1c-r0", - "PkgName": "libcrypto1.1", - "PkgIdentifier": { - "PURL": "pkg:apk/alpine/libcrypto1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2" - }, - "InstalledVersion": "1.1.1c-r0", - "FixedVersion": "1.1.1d-r2", - "Status": "fixed", - "Layer": { - "Digest": "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", - "DiffID": "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" - }, - "SeveritySource": "nvd", - "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-1551", - "DataSource": { - "ID": "alpine", - "Name": "Alpine Secdb", - "URL": "https://secdb.alpinelinux.org/" - }, - "Title": "openssl: Integer overflow in RSAZ modular exponentiation on x86_64", - "Description": "There is an overflow bug in the x64_64 Montgomery squaring procedure used in exponentiation with 512-bit moduli. No EC algorithms are affected. Analysis suggests that attacks against 2-prime RSA1024, 3-prime RSA1536, and DSA1024 as a result of this defect would be very difficult to perform and are not believed likely. Attacks against DH512 are considered just feasible. However, for an attack the target would have to re-use the DH512 private key, which is not recommended anyway. Also applications directly using the low level API BN_mod_exp may be affected if they use BN_FLG_CONSTTIME. Fixed in OpenSSL 1.1.1e (Affected 1.1.1-1.1.1d). Fixed in OpenSSL 1.0.2u (Affected 1.0.2-1.0.2t).", - "Severity": "MEDIUM", - "CweIDs": [ - "CWE-200" - ], - "VendorSeverity": { - "amazon": 1, - "nvd": 2, - "oracle-oval": 1, - "photon": 2, - "redhat": 1, - "ubuntu": 1 - }, - "CVSS": { - "nvd": { - "V2Vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N", - "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", - "V2Score": 5, - "V3Score": 5.3 - }, - "redhat": { - "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N", - "V3Score": 4.8 - } - }, - "References": [ - "http://lists.opensuse.org/opensuse-security-announce/2020-01/msg00030.html", - "http://packetstormsecurity.com/files/155754/Slackware-Security-Advisory-openssl-Updates.html", - "https://access.redhat.com/security/cve/CVE-2019-1551", - "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1551", - "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=419102400a2811582a7a3d4a4e317d72e5ce0a8f", - "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=f1c5eea8a817075d31e43f5876993c6710238c98", - "https://github.com/openssl/openssl/pull/10575", - "https://linux.oracle.com/cve/CVE-2019-1551.html", - "https://linux.oracle.com/errata/ELSA-2020-4514.html", - "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/DDHOAATPWJCXRNFMJ2SASDBBNU5RJONY/", - "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EXDDAOWSAIEFQNBHWYE6PPYFV4QXGMCD/", - "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/XVEP3LAK4JSPRXFO4QF4GG2IVXADV3SO/", - "https://seclists.org/bugtraq/2019/Dec/39", - "https://seclists.org/bugtraq/2019/Dec/46", - "https://security.gentoo.org/glsa/202004-10", - "https://security.netapp.com/advisory/ntap-20191210-0001/", - "https://ubuntu.com/security/notices/USN-4376-1", - "https://ubuntu.com/security/notices/USN-4504-1", - "https://usn.ubuntu.com/4376-1/", - "https://usn.ubuntu.com/4504-1/", - "https://www.debian.org/security/2019/dsa-4594", - "https://www.debian.org/security/2021/dsa-4855", - "https://www.openssl.org/news/secadv/20191206.txt", - "https://www.oracle.com/security-alerts/cpuApr2021.html", - "https://www.oracle.com/security-alerts/cpujan2021.html", - "https://www.oracle.com/security-alerts/cpujul2020.html", - "https://www.tenable.com/security/tns-2019-09", - "https://www.tenable.com/security/tns-2020-03", - "https://www.tenable.com/security/tns-2020-11", - "https://www.tenable.com/security/tns-2021-10" - ], - "PublishedDate": "2019-12-06T18:15:00Z", - "LastModifiedDate": "2021-07-21T11:39:00Z" - }, - { - "VulnerabilityID": "CVE-2019-1549", - "PkgID": "libssl1.1@1.1.1c-r0", - "PkgName": "libssl1.1", - "PkgIdentifier": { - "PURL": "pkg:apk/alpine/libssl1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2" - }, - "InstalledVersion": "1.1.1c-r0", - "FixedVersion": "1.1.1d-r0", - "Status": "fixed", - "Layer": { - "Digest": "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", - "DiffID": "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" - }, - "SeveritySource": "nvd", - "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-1549", - "DataSource": { - "ID": "alpine", - "Name": "Alpine Secdb", - "URL": "https://secdb.alpinelinux.org/" - }, - "Title": "openssl: information disclosure in fork()", - "Description": "OpenSSL 1.1.1 introduced a rewritten random number generator (RNG). This was intended to include protection in the event of a fork() system call in order to ensure that the parent and child processes did not share the same RNG state. However this protection was not being used in the default case. A partial mitigation for this issue is that the output from a high precision timer is mixed into the RNG state so the likelihood of a parent and child process sharing state is significantly reduced. If an application already calls OPENSSL_init_crypto() explicitly using OPENSSL_INIT_ATFORK then this problem does not occur at all. Fixed in OpenSSL 1.1.1d (Affected 1.1.1-1.1.1c).", - "Severity": "MEDIUM", - "CweIDs": [ - "CWE-330" - ], - "VendorSeverity": { - "amazon": 2, - "nvd": 2, - "oracle-oval": 2, - "photon": 2, - "redhat": 1, - "ubuntu": 1 - }, - "CVSS": { - "nvd": { - "V2Vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N", - "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", - "V2Score": 5, - "V3Score": 5.3 - }, - "redhat": { - "V3Vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N", - "V3Score": 4.8 - } - }, - "References": [ - "https://access.redhat.com/security/cve/CVE-2019-1549", - "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1549", - "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=1b0fe00e2704b5e20334a16d3c9099d1ba2ef1be", - "https://linux.oracle.com/cve/CVE-2019-1549.html", - "https://linux.oracle.com/errata/ELSA-2020-1840.html", - "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/GY6SNRJP2S7Y42GIIDO3HXPNMDYN2U3A/", - "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZN4VVQJ3JDCHGIHV4Y2YTXBYQZ6PWQ7E/", - "https://seclists.org/bugtraq/2019/Oct/1", - "https://security.netapp.com/advisory/ntap-20190919-0002/", - "https://support.f5.com/csp/article/K44070243", - "https://support.f5.com/csp/article/K44070243?utm_source=f5support\u0026amp;utm_medium=RSS", - "https://ubuntu.com/security/notices/USN-4376-1", - "https://usn.ubuntu.com/4376-1/", - "https://www.debian.org/security/2019/dsa-4539", - "https://www.openssl.org/news/secadv/20190910.txt", - "https://www.oracle.com/security-alerts/cpuapr2020.html", - "https://www.oracle.com/security-alerts/cpujan2020.html", - "https://www.oracle.com/security-alerts/cpujul2020.html", - "https://www.oracle.com/security-alerts/cpuoct2020.html", - "https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html" - ], - "PublishedDate": "2019-09-10T17:15:00Z", - "LastModifiedDate": "2020-10-20T22:15:00Z" - }, - { - "VulnerabilityID": "CVE-2019-1551", - "PkgID": "libssl1.1@1.1.1c-r0", - "PkgName": "libssl1.1", - "PkgIdentifier": { - "PURL": "pkg:apk/alpine/libssl1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2" - }, - "InstalledVersion": "1.1.1c-r0", - "FixedVersion": "1.1.1d-r2", - "Status": "fixed", - "Layer": { - "Digest": "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", - "DiffID": "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0" - }, - "SeveritySource": "nvd", - "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-1551", - "DataSource": { - "ID": "alpine", - "Name": "Alpine Secdb", - "URL": "https://secdb.alpinelinux.org/" - }, - "Title": "openssl: Integer overflow in RSAZ modular exponentiation on x86_64", - "Description": "There is an overflow bug in the x64_64 Montgomery squaring procedure used in exponentiation with 512-bit moduli. No EC algorithms are affected. Analysis suggests that attacks against 2-prime RSA1024, 3-prime RSA1536, and DSA1024 as a result of this defect would be very difficult to perform and are not believed likely. Attacks against DH512 are considered just feasible. However, for an attack the target would have to re-use the DH512 private key, which is not recommended anyway. Also applications directly using the low level API BN_mod_exp may be affected if they use BN_FLG_CONSTTIME. Fixed in OpenSSL 1.1.1e (Affected 1.1.1-1.1.1d). Fixed in OpenSSL 1.0.2u (Affected 1.0.2-1.0.2t).", - "Severity": "MEDIUM", - "CweIDs": [ - "CWE-200" - ], - "VendorSeverity": { - "amazon": 1, - "nvd": 2, - "oracle-oval": 1, - "photon": 2, - "redhat": 1, - "ubuntu": 1 - }, - "CVSS": { - "nvd": { - "V2Vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N", - "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", - "V2Score": 5, - "V3Score": 5.3 - }, - "redhat": { - "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N", - "V3Score": 4.8 - } - }, - "References": [ - "http://lists.opensuse.org/opensuse-security-announce/2020-01/msg00030.html", - "http://packetstormsecurity.com/files/155754/Slackware-Security-Advisory-openssl-Updates.html", - "https://access.redhat.com/security/cve/CVE-2019-1551", - "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1551", - "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=419102400a2811582a7a3d4a4e317d72e5ce0a8f", - "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=f1c5eea8a817075d31e43f5876993c6710238c98", - "https://github.com/openssl/openssl/pull/10575", - "https://linux.oracle.com/cve/CVE-2019-1551.html", - "https://linux.oracle.com/errata/ELSA-2020-4514.html", - "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/DDHOAATPWJCXRNFMJ2SASDBBNU5RJONY/", - "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EXDDAOWSAIEFQNBHWYE6PPYFV4QXGMCD/", - "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/XVEP3LAK4JSPRXFO4QF4GG2IVXADV3SO/", - "https://seclists.org/bugtraq/2019/Dec/39", - "https://seclists.org/bugtraq/2019/Dec/46", - "https://security.gentoo.org/glsa/202004-10", - "https://security.netapp.com/advisory/ntap-20191210-0001/", - "https://ubuntu.com/security/notices/USN-4376-1", - "https://ubuntu.com/security/notices/USN-4504-1", - "https://usn.ubuntu.com/4376-1/", - "https://usn.ubuntu.com/4504-1/", - "https://www.debian.org/security/2019/dsa-4594", - "https://www.debian.org/security/2021/dsa-4855", - "https://www.openssl.org/news/secadv/20191206.txt", - "https://www.oracle.com/security-alerts/cpuApr2021.html", - "https://www.oracle.com/security-alerts/cpujan2021.html", - "https://www.oracle.com/security-alerts/cpujul2020.html", - "https://www.tenable.com/security/tns-2019-09", - "https://www.tenable.com/security/tns-2020-03", - "https://www.tenable.com/security/tns-2020-11", - "https://www.tenable.com/security/tns-2021-10" - ], - "PublishedDate": "2019-12-06T18:15:00Z", - "LastModifiedDate": "2021-07-21T11:39:00Z" - } - ] - } - ] -} diff --git a/integration/testdata/alpine-39-high-critical.json.golden b/integration/testdata/alpine-39-high-critical.json.golden index 408cd11d4988..26931273fd01 100644 --- a/integration/testdata/alpine-39-high-critical.json.golden +++ b/integration/testdata/alpine-39-high-critical.json.golden @@ -106,7 +106,7 @@ "PkgName": "musl-utils", "PkgIdentifier": { "PURL": "pkg:apk/alpine/musl-utils@1.1.20-r4?arch=x86_64\u0026distro=3.9.4", - "UID": "8c341199f4077fc8" + "UID": "a35dd6cab4aabdf1" }, "InstalledVersion": "1.1.20-r4", "FixedVersion": "1.1.20-r5", diff --git a/integration/testdata/alpine-39.json.golden b/integration/testdata/alpine-39.json.golden index 3e1089f3e7cb..35d3e2a5c1b7 100644 --- a/integration/testdata/alpine-39.json.golden +++ b/integration/testdata/alpine-39.json.golden @@ -418,7 +418,7 @@ "PkgName": "musl-utils", "PkgIdentifier": { "PURL": "pkg:apk/alpine/musl-utils@1.1.20-r4?arch=x86_64\u0026distro=3.9.4", - "UID": "8c341199f4077fc8" + "UID": "a35dd6cab4aabdf1" }, "InstalledVersion": "1.1.20-r4", "FixedVersion": "1.1.20-r5", diff --git a/integration/testdata/alpine-distroless.json.golden b/integration/testdata/alpine-distroless.json.golden index 4ba010f0eea0..da03075d0cd5 100644 --- a/integration/testdata/alpine-distroless.json.golden +++ b/integration/testdata/alpine-distroless.json.golden @@ -55,7 +55,7 @@ "PkgName": "git", "PkgIdentifier": { "PURL": "pkg:apk/alpine/git@2.35.1-r2?arch=x86_64\u0026distro=3.16", - "UID": "d44ac4666246b919" + "UID": "2999d822f6cae40c" }, "InstalledVersion": "2.35.1-r2", "FixedVersion": "2.35.2-r0", diff --git a/integration/testdata/count-0.1.0-plugin-with-before-flag.txt.golden b/integration/testdata/count-0.1.0-plugin-with-before-flag.txt.golden new file mode 100644 index 000000000000..4c9d2bbaeb41 --- /dev/null +++ b/integration/testdata/count-0.1.0-plugin-with-before-flag.txt.golden @@ -0,0 +1,5 @@ +Installed Plugins: + Name: count + Version: 0.1.0 + +Number of vulnerabilities: 1 diff --git a/integration/testdata/count-0.2.0-plugin.txt.golden b/integration/testdata/count-0.2.0-plugin.txt.golden new file mode 100644 index 000000000000..9f86fc48e6dd --- /dev/null +++ b/integration/testdata/count-0.2.0-plugin.txt.golden @@ -0,0 +1,5 @@ +Installed Plugins: + Name: count + Version: 0.2.0 + +Number of vulnerabilities: 2 diff --git a/integration/testdata/fixtures/convert/npm-with-suppressed.json.golden b/integration/testdata/fixtures/convert/npm-with-suppressed.json.golden new file mode 100644 index 000000000000..0ce14e33545b --- /dev/null +++ b/integration/testdata/fixtures/convert/npm-with-suppressed.json.golden @@ -0,0 +1,195 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2024-09-09T13:21:09.230231+06:00", + "ArtifactName": "package-lock.json", + "ArtifactType": "filesystem", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "package-lock.json", + "Class": "lang-pkgs", + "Type": "npm", + "Packages": [ + { + "ID": "debug@3.0.1", + "Name": "debug", + "Identifier": { + "PURL": "pkg:npm/debug@3.0.1", + "UID": "45acc377fa09cc3" + }, + "Version": "3.0.1", + "Relationship": "direct", + "DependsOn": [ + "ms@2.0.0" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 11, + "EndLine": 19 + } + ] + }, + { + "ID": "ms@2.0.0", + "Name": "ms", + "Identifier": { + "PURL": "pkg:npm/ms@2.0.0", + "UID": "f51af0181daf2ced" + }, + "Version": "2.0.0", + "Indirect": true, + "Relationship": "indirect", + "Layer": {}, + "Locations": [ + { + "StartLine": 20, + "EndLine": 25 + } + ] + } + ], + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2017-20165", + "PkgID": "debug@3.0.1", + "PkgName": "debug", + "PkgIdentifier": { + "PURL": "pkg:npm/debug@3.0.1", + "UID": "45acc377fa09cc3" + }, + "InstalledVersion": "3.0.1", + "FixedVersion": "3.1.0, 2.6.9", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2017-20165", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory npm", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Anpm" + }, + "Title": "A vulnerability classified as problematic has been found in debug-js d ...", + "Description": "A vulnerability classified as problematic has been found in debug-js debug up to 3.0.x. This affects the function useColors of the file src/node.js. The manipulation of the argument str leads to inefficient regular expression complexity. Upgrading to version 3.1.0 is able to address this issue. The identifier of the patch is c38a0166c266a679c8de012d4eaccec3f944e685. It is recommended to upgrade the affected component. The identifier VDB-217665 was assigned to this vulnerability.", + "Severity": "HIGH", + "CweIDs": [ + "CWE-1333" + ], + "VendorSeverity": { + "ghsa": 3, + "nvd": 3 + }, + "CVSS": { + "ghsa": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "V3Score": 7.5 + }, + "nvd": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "V3Score": 7.5 + } + }, + "References": [ + "https://github.com/debug-js/debug", + "https://github.com/debug-js/debug/commit/c38a0166c266a679c8de012d4eaccec3f944e685", + "https://github.com/debug-js/debug/commit/f53962e944a87e6ca9bb622a2a12dffc22a9bb5a", + "https://github.com/debug-js/debug/pull/504", + "https://github.com/debug-js/debug/releases/tag/2.6.9", + "https://github.com/debug-js/debug/releases/tag/3.1.0", + "https://nvd.nist.gov/vuln/detail/CVE-2017-20165", + "https://vuldb.com/?ctiid.217665", + "https://vuldb.com/?id.217665" + ], + "PublishedDate": "2023-01-09T10:15:10.447Z", + "LastModifiedDate": "2024-05-17T01:17:24.28Z" + } + ], + "ExperimentalModifiedFindings": [ + { + "Type": "vulnerability", + "Status": "not_affected", + "Statement": "vulnerable_code_not_in_execute_path", + "Source": "./vex.json", + "Finding": { + "VulnerabilityID": "CVE-2017-16137", + "PkgID": "debug@3.0.1", + "PkgName": "debug", + "PkgIdentifier": { + "PURL": "pkg:npm/debug@3.0.1", + "UID": "45acc377fa09cc3" + }, + "InstalledVersion": "3.0.1", + "FixedVersion": "2.6.9, 3.1.0, 3.2.7, 4.3.1", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2017-16137", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory npm", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Anpm" + }, + "Title": "nodejs-debug: Regular expression Denial of Service", + "Description": "The debug module is vulnerable to regular expression denial of service when untrusted user input is passed into the o formatter. It takes around 50k characters to block for 2 seconds making this a low severity issue.", + "Severity": "LOW", + "CweIDs": [ + "CWE-400" + ], + "VendorSeverity": { + "ghsa": 1, + "nvd": 2, + "redhat": 2 + }, + "CVSS": { + "ghsa": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L", + "V3Score": 3.7 + }, + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:N/I:N/A:P", + "V3Vector": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L", + "V2Score": 5, + "V3Score": 5.3 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L", + "V3Score": 5.3 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2017-16137", + "https://github.com/debug-js/debug/commit/4e2150207c568adb9ead8f4c4528016081c88020", + "https://github.com/debug-js/debug/commit/71169065b5262f9858ac78cc0b688c84a438f290", + "https://github.com/debug-js/debug/commit/b6d12fdbc63b483e5c969da33ea6adc09946b5ac", + "https://github.com/debug-js/debug/commit/f53962e944a87e6ca9bb622a2a12dffc22a9bb5a", + "https://github.com/debug-js/debug/issues/797", + "https://github.com/visionmedia/debug", + "https://github.com/visionmedia/debug/issues/501", + "https://github.com/visionmedia/debug/pull/504", + "https://lists.apache.org/thread.html/r8ba4c628fba7181af58817d452119481adce4ba92e889c643e4c7dd3%40%3Ccommits.netbeans.apache.org%3E", + "https://lists.apache.org/thread.html/r8ba4c628fba7181af58817d452119481adce4ba92e889c643e4c7dd3@%3Ccommits.netbeans.apache.org%3E", + "https://lists.apache.org/thread.html/rb5ac16fad337d1f3bb7079549f97d8166d0ef3082629417c39f12d63%40%3Cnotifications.netbeans.apache.org%3E", + "https://lists.apache.org/thread.html/rb5ac16fad337d1f3bb7079549f97d8166d0ef3082629417c39f12d63@%3Cnotifications.netbeans.apache.org%3E", + "https://nodesecurity.io/advisories/534", + "https://nvd.nist.gov/vuln/detail/CVE-2017-16137", + "https://www.cve.org/CVERecord?id=CVE-2017-16137" + ], + "PublishedDate": "2018-06-07T02:29:03.817Z", + "LastModifiedDate": "2023-11-07T02:40:28.13Z" + } + } + ] + } + ] +} diff --git a/integration/testdata/fixtures/db/python.yaml b/integration/testdata/fixtures/db/python.yaml index 2d484feff17c..728f11b67e6d 100644 --- a/integration/testdata/fixtures/db/python.yaml +++ b/integration/testdata/fixtures/db/python.yaml @@ -14,3 +14,11 @@ - 0.11.6 VulnerableVersions: - < 0.11.6 + - bucket: setuptools + pairs: + - key: CVE-2022-40897 + value: + PatchedVersions: + - 65.5.1 + VulnerableVersions: + - < 65.5.1 \ No newline at end of file diff --git a/integration/testdata/fixtures/db/suse.yaml b/integration/testdata/fixtures/db/suse.yaml new file mode 100644 index 000000000000..690360c5de13 --- /dev/null +++ b/integration/testdata/fixtures/db/suse.yaml @@ -0,0 +1,19 @@ +- bucket: "SUSE Linux Enterprise 15-SP3" + pairs: + - bucket: libopenssl1_1 + pairs: + - key: "SUSE-SU-2022:2251-1" + value: + FixedVersion: 1.1.1d-150200.11.48.1 + - bucket: openssl-1_1 + pairs: + - key: "SUSE-SU-2022:2251-1" + value: + FixedVersion: 1.1.1d-150200.11.48.1 +- bucket: "SUSE Linux Enterprise Micro 5.3" + pairs: + - bucket: libopenssl1_1 + pairs: + - key: "SUSE-SU-2023:0311-1" + value: + FixedVersion: 1.1.1l-150400.7.22.1 diff --git a/integration/testdata/fixtures/db/vulnerability.yaml b/integration/testdata/fixtures/db/vulnerability.yaml index 9b16712d0dbc..e18d731a30c8 100644 --- a/integration/testdata/fixtures/db/vulnerability.yaml +++ b/integration/testdata/fixtures/db/vulnerability.yaml @@ -1349,6 +1349,15 @@ - "https://www.suse.com/security/cve/CVE-2023-2975/" - "https://www.suse.com/security/cve/CVE-2023-3446/" - "https://www.suse.com/support/security/rating/" + - key: SUSE-SU-2022:2251-1 + value: + Title: "Security update for openssl-1_1" + Description: "This update for openssl-1_1 fixes the following issues:\nCVE-2022-1292: Fixed command injection in c_rehash (bsc#1199166).\nCVE-2022-2068: Fixed more shell code injection issues in c_rehash. (bsc#1200550)" + Severity: MEDIUM + References: + - "https://www.suse.com/security/cve/CVE-2022-1292/" + - "https://www.suse.com/security/cve/CVE-2022-2068/" + - "https://www.suse.com/support/security/rating/" - key: CVE-2022-22965 value: Title: "spring-framework: RCE via Data Binding on JDK 9+" @@ -1399,4 +1408,23 @@ - "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-14155" - "https://nvd.nist.gov/vuln/detail/CVE-2020-14155" PublishedDate: "2020-06-15T17:15:00Z" - LastModifiedDate: "2022-04-28T15:06:00Z" \ No newline at end of file + LastModifiedDate: "2022-04-28T15:06:00Z" + - key: CVE-2022-40897 + value: + Title: "pypa-setuptools: Regular Expression Denial of Service (ReDoS) in package_index.py" + Description: "Python Packaging Authority (PyPA) setuptools before 65.5.1 allows remote attackers to cause a denial of service via HTML in a crafted package or custom PackageIndex page. There is a Regular Expression Denial of Service (ReDoS) in package_index.py." + Severity: MEDIUM + CweIDs: + - CWE-1333 + VendorSeverity: + ghsa: 3 + nvd: 2 + CVSS: + nvd: + V3Vector: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H" + V3Score: 5.9 + References: + - "https://access.redhat.com/errata/RHSA-2023:0952" + - "https://access.redhat.com/security/cve/CVE-2022-40897" + PublishedDate: "2022-12-23T00:15:13.987Z" + LastModifiedDate: "2024-06-21T19:15:23.877Z" diff --git a/integration/testdata/fluentd-multiple-lockfiles-short.cdx.json.golden b/integration/testdata/fluentd-multiple-lockfiles-short.cdx.json.golden new file mode 100644 index 000000000000..496ca8ae3110 --- /dev/null +++ b/integration/testdata/fluentd-multiple-lockfiles-short.cdx.json.golden @@ -0,0 +1,526 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.6", + "serialNumber": "urn:uuid:3ff14136-e09f-4df9-80ea-000000000010", + "version": 1, + "metadata": { + "timestamp": "2021-08-25T12:20:30+00:00", + "tools": { + "components": [ + { + "type": "application", + "group": "aquasecurity", + "name": "trivy", + "version": "dev" + } + ] + }, + "component": { + "bom-ref": "95de56ee-980c-413d-8f68-6c674dc3e9d1", + "type": "container", + "name": "integration/testdata/fixtures/images/fluentd-multiple-lockfiles.tar.gz", + "properties": [ + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:02874b2b269dea8dde0f7edb4c9906904dfe38a09de1a214f20c650cfb15c60e" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:25165eb51d15842f870f97873e0a58409d5e860e6108e3dd829bd10e484c0065" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:3752e1f6fd759c795c13aff2c93c081529366e27635ba6621e849b0f9cfc77f0" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:788c00e2cfc8f2a018ae4344ccf0b2c226ebd756d7effd1ce50eea1a4252cd89" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" + }, + { + "name": "aquasecurity:trivy:ImageID", + "value": "sha256:5a992077baba51b97f27591a10d54d2f2723dc9c81a3fe419e261023f2554933" + }, + { + "name": "aquasecurity:trivy:SchemaVersion", + "value": "2" + } + ] + } + }, + "components": [ + { + "bom-ref": "353f2470-9c8b-4647-9d0d-96d893838dc8", + "type": "operating-system", + "name": "debian", + "version": "10.2", + "properties": [ + { + "name": "aquasecurity:trivy:Class", + "value": "os-pkgs" + }, + { + "name": "aquasecurity:trivy:Type", + "value": "debian" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/bash@5.0-4?distro=debian-10.2", + "type": "library", + "name": "bash", + "version": "5.0-4", + "purl": "pkg:deb/debian/bash@5.0-4?distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "bash@5.0-4" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "bash" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "5.0-4" + } + ] + }, + { + "bom-ref": "pkg:deb/debian/libidn2-0@2.0.5-1?distro=debian-10.2", + "type": "library", + "name": "libidn2-0", + "version": "2.0.5-1", + "purl": "pkg:deb/debian/libidn2-0@2.0.5-1?distro=debian-10.2", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "libidn2-0@2.0.5-1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "debian" + }, + { + "name": "aquasecurity:trivy:SrcName", + "value": "libidn2" + }, + { + "name": "aquasecurity:trivy:SrcVersion", + "value": "2.0.5-1" + } + ] + }, + { + "bom-ref": "pkg:gem/activesupport@6.0.2.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Factivesupport-6.0.2.1.gemspec", + "type": "library", + "name": "activesupport", + "version": "6.0.2.1", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:gem/activesupport@6.0.2.1", + "properties": [ + { + "name": "aquasecurity:trivy:FilePath", + "value": "var/lib/gems/2.5.0/specifications/activesupport-6.0.2.1.gemspec" + }, + { + "name": "aquasecurity:trivy:PkgID", + "value": "activesupport@6.0.2.1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "gemspec" + } + ] + } + ], + "dependencies": [ + { + "ref": "353f2470-9c8b-4647-9d0d-96d893838dc8", + "dependsOn": [ + "pkg:deb/debian/bash@5.0-4?distro=debian-10.2", + "pkg:deb/debian/libidn2-0@2.0.5-1?distro=debian-10.2" + ] + }, + { + "ref": "95de56ee-980c-413d-8f68-6c674dc3e9d1", + "dependsOn": [ + "353f2470-9c8b-4647-9d0d-96d893838dc8", + "pkg:gem/activesupport@6.0.2.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Factivesupport-6.0.2.1.gemspec" + ] + }, + { + "ref": "pkg:deb/debian/bash@5.0-4?distro=debian-10.2", + "dependsOn": [] + }, + { + "ref": "pkg:deb/debian/libidn2-0@2.0.5-1?distro=debian-10.2", + "dependsOn": [] + }, + { + "ref": "pkg:gem/activesupport@6.0.2.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Factivesupport-6.0.2.1.gemspec", + "dependsOn": [] + } + ], + "vulnerabilities": [ + { + "id": "CVE-2019-18224", + "source": { + "name": "debian", + "url": "https://salsa.debian.org/security-tracker-team/security-tracker" + }, + "ratings": [ + { + "source": { + "name": "amazon" + }, + "severity": "medium" + }, + { + "source": { + "name": "nvd" + }, + "score": 7.5, + "severity": "high", + "method": "CVSSv2", + "vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P" + }, + { + "source": { + "name": "nvd" + }, + "score": 9.8, + "severity": "critical", + "method": "CVSSv31", + "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + }, + { + "source": { + "name": "redhat" + }, + "score": 5.6, + "severity": "medium", + "method": "CVSSv3", + "vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L" + }, + { + "source": { + "name": "ubuntu" + }, + "severity": "medium" + } + ], + "cwes": [ + 787 + ], + "description": "idn2_to_ascii_4i in lib/lookup.c in GNU libidn2 before 2.1.1 has a heap-based buffer overflow via a long domain string.", + "recommendation": "Upgrade libidn2-0 to version 2.0.5-1+deb10u1", + "advisories": [ + { + "url": "https://avd.aquasec.com/nvd/cve-2019-18224" + }, + { + "url": "http://lists.opensuse.org/opensuse-security-announce/2019-12/msg00008.html" + }, + { + "url": "http://lists.opensuse.org/opensuse-security-announce/2019-12/msg00009.html" + }, + { + "url": "https://access.redhat.com/security/cve/CVE-2019-18224" + }, + { + "url": "https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=12420" + }, + { + "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-18224" + }, + { + "url": "https://github.com/libidn/libidn2/commit/e4d1558aa2c1c04a05066ee8600f37603890ba8c" + }, + { + "url": "https://github.com/libidn/libidn2/compare/libidn2-2.1.0...libidn2-2.1.1" + }, + { + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/JDQVQ2XPV5BTZUFINT7AFJSKNNBVURNJ/" + }, + { + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/MINU5RKDFE6TKAFY5DRFN3WSFDS4DYVS/" + }, + { + "url": "https://seclists.org/bugtraq/2020/Feb/4" + }, + { + "url": "https://security.gentoo.org/glsa/202003-63" + }, + { + "url": "https://ubuntu.com/security/notices/USN-4168-1" + }, + { + "url": "https://usn.ubuntu.com/4168-1/" + }, + { + "url": "https://www.debian.org/security/2020/dsa-4613" + } + ], + "published": "2019-10-21T17:15:00+00:00", + "updated": "2019-10-29T19:15:00+00:00", + "affects": [ + { + "ref": "pkg:deb/debian/libidn2-0@2.0.5-1?distro=debian-10.2", + "versions": [ + { + "version": "2.0.5-1", + "status": "affected" + } + ] + } + ] + }, + { + "id": "CVE-2019-18276", + "source": { + "name": "debian", + "url": "https://salsa.debian.org/security-tracker-team/security-tracker" + }, + "ratings": [ + { + "source": { + "name": "cbl-mariner" + }, + "severity": "high" + }, + { + "source": { + "name": "debian" + }, + "severity": "low" + }, + { + "source": { + "name": "nvd" + }, + "score": 7.2, + "severity": "high", + "method": "CVSSv2", + "vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C" + }, + { + "source": { + "name": "nvd" + }, + "score": 7.8, + "severity": "high", + "method": "CVSSv31", + "vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H" + }, + { + "source": { + "name": "oracle-oval" + }, + "severity": "low" + }, + { + "source": { + "name": "photon" + }, + "severity": "high" + }, + { + "source": { + "name": "redhat" + }, + "score": 7.8, + "severity": "low", + "method": "CVSSv31", + "vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H" + }, + { + "source": { + "name": "ubuntu" + }, + "severity": "low" + } + ], + "cwes": [ + 273 + ], + "description": "An issue was discovered in disable_priv_mode in shell.c in GNU Bash through 5.0 patch 11. By default, if Bash is run with its effective UID not equal to its real UID, it will drop privileges by setting its effective UID to its real UID. However, it does so incorrectly. On Linux and other systems that support \"saved UID\" functionality, the saved UID is not dropped. An attacker with command execution in the shell can use \"enable -f\" for runtime loading of a new builtin, which can be a shared object that calls setuid() and therefore regains privileges. However, binaries running with an effective UID of 0 are unaffected.", + "advisories": [ + { + "url": "https://avd.aquasec.com/nvd/cve-2019-18276" + }, + { + "url": "http://packetstormsecurity.com/files/155498/Bash-5.0-Patch-11-Privilege-Escalation.html" + }, + { + "url": "https://access.redhat.com/security/cve/CVE-2019-18276" + }, + { + "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-18276" + }, + { + "url": "https://github.com/bminor/bash/commit/951bdaad7a18cc0dc1036bba86b18b90874d39ff" + }, + { + "url": "https://linux.oracle.com/cve/CVE-2019-18276.html" + }, + { + "url": "https://linux.oracle.com/errata/ELSA-2021-1679.html" + }, + { + "url": "https://lists.apache.org/thread.html/rf9fa47ab66495c78bb4120b0754dd9531ca2ff0430f6685ac9b07772@%3Cdev.mina.apache.org%3E" + }, + { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2019-18276" + }, + { + "url": "https://security.gentoo.org/glsa/202105-34" + }, + { + "url": "https://security.netapp.com/advisory/ntap-20200430-0003/" + }, + { + "url": "https://www.youtube.com/watch?v=-wGtxJ8opa8" + } + ], + "published": "2019-11-28T01:15:00+00:00", + "updated": "2021-05-26T12:15:00+00:00", + "affects": [ + { + "ref": "pkg:deb/debian/bash@5.0-4?distro=debian-10.2", + "versions": [ + { + "version": "5.0-4", + "status": "affected" + } + ] + } + ] + }, + { + "id": "CVE-2020-8165", + "source": { + "name": "ghsa", + "url": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Arubygems" + }, + "ratings": [ + { + "source": { + "name": "ghsa" + }, + "severity": "high" + }, + { + "source": { + "name": "nvd" + }, + "score": 7.5, + "severity": "high", + "method": "CVSSv2", + "vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P" + }, + { + "source": { + "name": "nvd" + }, + "score": 9.8, + "severity": "critical", + "method": "CVSSv31", + "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + }, + { + "source": { + "name": "redhat" + }, + "score": 9.8, + "severity": "high", + "method": "CVSSv31", + "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + } + ], + "cwes": [ + 502 + ], + "description": "A deserialization of untrusted data vulnerability exists in rails < 5.2.4.3, rails < 6.0.3.1 that can allow an attacker to unmarshal user-provided objects in MemCacheStore and RedisCacheStore potentially resulting in an RCE.", + "recommendation": "Upgrade activesupport to version 6.0.3.1, 5.2.4.3", + "advisories": [ + { + "url": "https://avd.aquasec.com/nvd/cve-2020-8165" + }, + { + "url": "http://lists.opensuse.org/opensuse-security-announce/2020-10/msg00031.html" + }, + { + "url": "http://lists.opensuse.org/opensuse-security-announce/2020-10/msg00034.html" + }, + { + "url": "https://access.redhat.com/security/cve/CVE-2020-8165" + }, + { + "url": "https://github.com/advisories/GHSA-2p68-f74v-9wc6" + }, + { + "url": "https://github.com/rubysec/ruby-advisory-db/blob/master/gems/activesupport/CVE-2020-8165.yml" + }, + { + "url": "https://groups.google.com/forum/#!msg/rubyonrails-security/bv6fW4S0Y1c/KnkEqM7AAQAJ" + }, + { + "url": "https://groups.google.com/forum/#!topic/rubyonrails-security/bv6fW4S0Y1c" + }, + { + "url": "https://groups.google.com/g/rubyonrails-security/c/bv6fW4S0Y1c" + }, + { + "url": "https://hackerone.com/reports/413388" + }, + { + "url": "https://lists.debian.org/debian-lts-announce/2020/06/msg00022.html" + }, + { + "url": "https://lists.debian.org/debian-lts-announce/2020/07/msg00013.html" + }, + { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2020-8165" + }, + { + "url": "https://weblog.rubyonrails.org/2020/5/18/Rails-5-2-4-3-and-6-0-3-1-have-been-released/" + }, + { + "url": "https://www.debian.org/security/2020/dsa-4766" + } + ], + "published": "2020-06-19T18:15:00+00:00", + "updated": "2020-10-17T12:15:00+00:00", + "affects": [ + { + "ref": "pkg:gem/activesupport@6.0.2.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Factivesupport-6.0.2.1.gemspec", + "versions": [ + { + "version": "6.0.2.1", + "status": "affected" + } + ] + } + ] + } + ] +} diff --git a/integration/testdata/fluentd-multiple-lockfiles.cdx.json.golden b/integration/testdata/fluentd-multiple-lockfiles.cdx.json.golden index cc442e7d881d..65fc78e6c66f 100644 --- a/integration/testdata/fluentd-multiple-lockfiles.cdx.json.golden +++ b/integration/testdata/fluentd-multiple-lockfiles.cdx.json.golden @@ -81,7 +81,7 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } } ], @@ -121,7 +121,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } } ], @@ -161,7 +166,7 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } } ], @@ -201,7 +206,7 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } }, { @@ -246,7 +251,7 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-only" } } ], @@ -290,7 +295,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } }, { @@ -320,22 +330,42 @@ }, { "license": { - "name": "LGPL-2.0" + "name": "LGPL-2.0-or-later" + } + }, + { + "license": { + "name": "LGPL-2.1-or-later" + } + }, + { + "license": { + "name": "GPL-3.0-or-later" + } + }, + { + "license": { + "name": "LGPL-3.0-or-later" + } + }, + { + "license": { + "name": "GPL-3.0-only" } }, { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.0-only" } }, { "license": { - "name": "GPL-3.0" + "name": "LGPL-2.1-only" } }, { "license": { - "name": "LGPL-3.0" + "name": "LGPL-3.0-only" } } ], @@ -379,7 +409,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } }, { @@ -424,7 +459,7 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-only" } } ], @@ -468,7 +503,7 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } } ], @@ -552,7 +587,7 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } } ], @@ -592,7 +627,7 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } } ], @@ -632,12 +667,12 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } }, { "license": { - "name": "GFDL" + "name": "GFDL-1.3-or-later" } } ], @@ -685,7 +720,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } }, { @@ -740,12 +780,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } }, { "license": { - "name": "LGPL-2.0" + "name": "LGPL-2.0-only" } } ], @@ -789,7 +829,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } }, { @@ -819,22 +864,42 @@ }, { "license": { - "name": "LGPL-2.0" + "name": "LGPL-2.0-or-later" } }, { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-or-later" } }, { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-or-later" } }, { "license": { - "name": "LGPL-3.0" + "name": "LGPL-3.0-or-later" + } + }, + { + "license": { + "name": "GPL-3.0-only" + } + }, + { + "license": { + "name": "LGPL-2.0-only" + } + }, + { + "license": { + "name": "LGPL-2.1-only" + } + }, + { + "license": { + "name": "LGPL-3.0-only" } } ], @@ -878,12 +943,12 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-only" } }, { "license": { - "name": "GFDL-1.3" + "name": "GFDL-1.3-only" } } ], @@ -927,27 +992,32 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-3.0-only" } }, { "license": { - "name": "GFDL-1.2" + "name": "GFDL-1.2-only" } }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } }, { "license": { - "name": "Artistic" + "name": "Artistic-2.0" } }, { "license": { - "name": "LGPL-3.0" + "name": "LGPL-2.0-or-later" } } ], @@ -991,7 +1061,7 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-or-later" } }, { @@ -1001,12 +1071,12 @@ }, { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-or-later" } }, { "license": { - "name": "Expat" + "name": "MIT" } }, { @@ -1016,7 +1086,7 @@ }, { "license": { - "name": "LGPL-3.0" + "name": "LGPL-3.0-or-later" } }, { @@ -1033,6 +1103,21 @@ "license": { "name": "CC0-1.0" } + }, + { + "license": { + "name": "GPL-3.0-only" + } + }, + { + "license": { + "name": "LGPL-3.0-only" + } + }, + { + "license": { + "name": "LGPL-2.1-only" + } } ], "purl": "pkg:deb/debian/gpgv@2.2.12-1%2Bdeb10u1?arch=amd64&distro=debian-10.2", @@ -1075,7 +1160,12 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-or-later" + } + }, + { + "license": { + "name": "GPL-3.0-only" } } ], @@ -1119,7 +1209,7 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } } ], @@ -1163,7 +1253,7 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } } ], @@ -1208,7 +1298,12 @@ }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } } ], @@ -1248,17 +1343,22 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } }, { "license": { - "name": "LGPL-2.0" + "name": "LGPL-2.0-or-later" } }, { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-only" } } ], @@ -1302,7 +1402,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } } ], @@ -1342,17 +1447,22 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" } }, { "license": { - "name": "LGPL-2.0" + "name": "GPL-2.0-only" } }, { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.0-or-later" + } + }, + { + "license": { + "name": "LGPL-2.1-only" } } ], @@ -1400,17 +1510,17 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } }, { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-only" } }, { "license": { - "name": "GPL-1.0" + "name": "GPL-1.0-only" } } ], @@ -1458,17 +1568,17 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } }, { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-only" } }, { "license": { - "name": "GPL-1.0" + "name": "GPL-1.0-only" } } ], @@ -1516,7 +1626,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } }, { @@ -1546,22 +1661,42 @@ }, { "license": { - "name": "LGPL-2.0" + "name": "LGPL-2.0-or-later" } }, { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-or-later" } }, { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-or-later" } }, { "license": { - "name": "LGPL-3.0" + "name": "LGPL-3.0-or-later" + } + }, + { + "license": { + "name": "GPL-3.0-only" + } + }, + { + "license": { + "name": "LGPL-2.0-only" + } + }, + { + "license": { + "name": "LGPL-2.1-only" + } + }, + { + "license": { + "name": "LGPL-3.0-only" } } ], @@ -1605,12 +1740,12 @@ "licenses": [ { "license": { - "name": "BSD-variant" + "name": "BSD-3-Clause" } }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } } ], @@ -1654,12 +1789,12 @@ "licenses": [ { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-only" } }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } } ], @@ -1703,12 +1838,12 @@ "licenses": [ { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-only" } }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } } ], @@ -1752,17 +1887,17 @@ "licenses": [ { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-only" } }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } }, { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-only" } } ], @@ -1913,12 +2048,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } }, { "license": { - "name": "LGPL-2.0" + "name": "LGPL-2.0-only" } } ], @@ -1962,7 +2097,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } }, { @@ -1992,22 +2132,42 @@ }, { "license": { - "name": "LGPL-2.0" + "name": "LGPL-2.0-or-later" + } + }, + { + "license": { + "name": "LGPL-2.1-or-later" + } + }, + { + "license": { + "name": "GPL-3.0-or-later" } }, { "license": { - "name": "LGPL-2.1" + "name": "LGPL-3.0-or-later" } }, { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-only" } }, { "license": { - "name": "LGPL-3.0" + "name": "LGPL-2.0-only" + } + }, + { + "license": { + "name": "LGPL-2.1-only" + } + }, + { + "license": { + "name": "LGPL-3.0-only" } } ], @@ -2051,7 +2211,7 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } } ], @@ -2132,12 +2292,12 @@ "licenses": [ { "license": { - "name": "LGPL-3.0" + "name": "LGPL-2.0-or-later" } }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } } ], @@ -2181,17 +2341,27 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-or-later" } }, { "license": { - "name": "GPL-2.0" + "name": "GFDL-1.3-no-invariants-or-later" } }, { "license": { - "name": "GFDL-NIV-1.3+" + "name": "GPL-3.0-only" + } + }, + { + "license": { + "name": "GPL-2.0-only" } } ], @@ -2235,17 +2405,27 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GFDL-1.3-no-invariants-or-later" } }, { "license": { - "name": "GPL-2.0" + "name": "GPL-3.0-only" } }, { "license": { - "name": "GFDL-NIV-1.3+" + "name": "GPL-2.0-only" } } ], @@ -2289,17 +2469,22 @@ "licenses": [ { "license": { - "name": "LGPL-3.0" + "name": "LGPL-3.0-only" + } + }, + { + "license": { + "name": "GPL-2.0-only" } }, { "license": { - "name": "GPL-2.0" + "name": "GPL-3.0-only" } }, { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } } ], @@ -2347,43 +2532,58 @@ "licenses": [ { "license": { - "name": "LGPL-3.0" + "name": "LGPL-2.1-only" } }, { "license": { - "name": "GPL-3.0" + "name": "LGPL-2.0-or-later" } }, { "license": { - "name": "GFDL-1.3" + "name": "LGPL-3.0-only" } }, { "license": { - "name": "CC0" + "name": "GPL-2.0-or-later" } }, { "license": { - "name": "The MIT License" + "name": "GPL-3.0-only" } }, { "license": { - "name": "LGPLv3+" + "name": "GFDL-1.3-only" } }, { "license": { - "name": "GPL-2.0" + "name": "CC0-1.0" + } + }, + { + "license": { + "name": "MIT" + } + }, + { + "license": { + "name": "LGPL-3.0-or-later" } }, { "license": { "name": "Apache-2.0" } + }, + { + "license": { + "name": "GPL-3.0-or-later" + } } ], "purl": "pkg:deb/debian/libgnutls30@3.6.7-4?arch=amd64&distro=debian-10.2", @@ -2426,7 +2626,7 @@ "licenses": [ { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-or-later" } }, { @@ -2441,7 +2641,17 @@ }, { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-or-later" + } + }, + { + "license": { + "name": "LGPL-2.1-only" + } + }, + { + "license": { + "name": "GPL-3.0-only" } } ], @@ -2522,23 +2732,38 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-or-later" } }, { "license": { - "name": "LGPL-3.0" + "name": "LGPL-3.0-or-later" } }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" } }, { "license": { "name": "Unicode" } + }, + { + "license": { + "name": "GPL-3.0-only" + } + }, + { + "license": { + "name": "GPL-2.0-only" + } + }, + { + "license": { + "name": "LGPL-3.0-only" + } } ], "purl": "pkg:deb/debian/libidn2-0@2.0.5-1?arch=amd64&distro=debian-10.2", @@ -2596,7 +2821,7 @@ }, { "license": { - "name": "Expat" + "name": "MIT" } }, { @@ -2655,7 +2880,12 @@ }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } } ], @@ -2709,12 +2939,12 @@ }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" } }, { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-or-later" } }, { @@ -2732,6 +2962,11 @@ "name": "permissive-nowarranty" } }, + { + "license": { + "name": "GPL-2.0-only" + } + }, { "license": { "name": "none" @@ -2744,7 +2979,12 @@ }, { "license": { - "name": "LGPL-2.0" + "name": "LGPL-2.0-only" + } + }, + { + "license": { + "name": "LGPL-2.1-only" } }, { @@ -2759,7 +2999,7 @@ }, { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-only" } } ], @@ -2803,7 +3043,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } }, { @@ -2833,22 +3078,42 @@ }, { "license": { - "name": "LGPL-2.0" + "name": "LGPL-2.0-or-later" + } + }, + { + "license": { + "name": "LGPL-2.1-or-later" + } + }, + { + "license": { + "name": "GPL-3.0-or-later" } }, { "license": { - "name": "LGPL-2.1" + "name": "LGPL-3.0-or-later" } }, { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-only" } }, { "license": { - "name": "LGPL-3.0" + "name": "LGPL-2.0-only" + } + }, + { + "license": { + "name": "LGPL-2.1-only" + } + }, + { + "license": { + "name": "LGPL-3.0-only" } } ], @@ -2966,47 +3231,47 @@ "licenses": [ { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-or-later" } }, { "license": { - "name": "LGPL-2.0" + "name": "LGPL-2.0-or-later" } }, { "license": { - "name": "other" + "name": "LGPL-2.0-only" } }, { "license": { - "name": "GPL-2.0" + "name": "other" } }, { "license": { - "name": "GPL-2.0-with-autoconf-exception" + "name": "GPL-2.0-or-later" } }, { "license": { - "name": "public-domain" + "name": "GPL-2.0-with-autoconf-exception+" } }, { "license": { - "name": "GAP" + "name": "public-domain" } }, { "license": { - "name": "LGPL-3.0" + "name": "GPL-2.0-only" } }, { "license": { - "name": "GPL-3.0" + "name": "GAP" } } ], @@ -3114,7 +3379,7 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } } ], @@ -3158,7 +3423,7 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } } ], @@ -3202,7 +3467,7 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } } ], @@ -3246,7 +3511,7 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } } ], @@ -3331,12 +3596,12 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-only" } }, { "license": { - "name": "GFDL" + "name": "GFDL-1.3-or-later" } } ], @@ -3390,12 +3655,12 @@ }, { "license": { - "name": "Expat" + "name": "MIT" } }, { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-or-later" } }, { @@ -3440,27 +3705,27 @@ }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } }, { "license": { - "name": "Artistic" + "name": "Artistic-2.0" } }, { "license": { - "name": "zlib/libpng" + "name": "zlib-acknowledgement" } }, { "license": { - "name": "GPL-1.0" + "name": "GPL-1.0-or-later" } }, { "license": { - "name": "CC0" + "name": "CC0-1.0" } }, { @@ -3472,6 +3737,16 @@ "license": { "name": "Permissive" } + }, + { + "license": { + "name": "GPL-1.0-only" + } + }, + { + "license": { + "name": "GPL-3.0-only" + } } ], "purl": "pkg:deb/debian/libruby2.5@2.5.5-3%2Bdeb10u1?arch=amd64&distro=debian-10.2", @@ -3514,7 +3789,7 @@ "licenses": [ { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-only" } } ], @@ -3558,12 +3833,12 @@ "licenses": [ { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-only" } }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } } ], @@ -3607,12 +3882,12 @@ "licenses": [ { "license": { - "name": "LGPL-3.0" + "name": "LGPL-2.0-or-later" } }, { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } } ], @@ -3656,12 +3931,12 @@ "licenses": [ { "license": { - "name": "LGPL-3.0" + "name": "LGPL-2.0-or-later" } }, { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } } ], @@ -3705,12 +3980,12 @@ "licenses": [ { "license": { - "name": "LGPL-3.0" + "name": "LGPL-2.0-or-later" } }, { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } } ], @@ -3754,7 +4029,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } }, { @@ -3784,22 +4064,42 @@ }, { "license": { - "name": "LGPL-2.0" + "name": "LGPL-2.0-or-later" + } + }, + { + "license": { + "name": "LGPL-2.1-or-later" } }, { "license": { - "name": "LGPL-2.1" + "name": "GPL-3.0-or-later" } }, { "license": { - "name": "GPL-3.0" + "name": "LGPL-3.0-or-later" } }, { "license": { - "name": "LGPL-3.0" + "name": "GPL-3.0-only" + } + }, + { + "license": { + "name": "LGPL-2.0-only" + } + }, + { + "license": { + "name": "LGPL-2.1-only" + } + }, + { + "license": { + "name": "LGPL-3.0-only" } } ], @@ -3954,7 +4254,7 @@ "licenses": [ { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-or-later" } }, { @@ -3964,18 +4264,28 @@ }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" + } + }, + { + "license": { + "name": "GPL-2.0-or-later" } }, { "license": { - "name": "Expat" + "name": "MIT" } }, { "license": { "name": "public-domain" } + }, + { + "license": { + "name": "LGPL-2.1-only" + } } ], "purl": "pkg:deb/debian/libsystemd0@241-7~deb10u2?arch=amd64&distro=debian-10.2", @@ -4018,22 +4328,22 @@ "licenses": [ { "license": { - "name": "LGPL-3.0" + "name": "LGPL-2.0-or-later" } }, { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-only" } }, { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-only" } }, { "license": { - "name": "GFDL-1.3" + "name": "GFDL-1.3-only" } } ], @@ -4114,7 +4424,7 @@ "licenses": [ { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-or-later" } }, { @@ -4124,18 +4434,28 @@ }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" + } + }, + { + "license": { + "name": "GPL-2.0-or-later" } }, { "license": { - "name": "Expat" + "name": "MIT" } }, { "license": { "name": "public-domain" } + }, + { + "license": { + "name": "LGPL-2.1-only" + } } ], "purl": "pkg:deb/debian/libudev1@241-7~deb10u2?arch=amd64&distro=debian-10.2", @@ -4178,12 +4498,12 @@ "licenses": [ { "license": { - "name": "LGPL-3.0" + "name": "LGPL-3.0-or-later" } }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" } }, { @@ -4198,12 +4518,12 @@ }, { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-or-later" } }, { "license": { - "name": "GFDL-1.2+" + "name": "GFDL-1.2-or-later" } }, { @@ -4213,7 +4533,22 @@ }, { "license": { - "name": "GFDL-1.2" + "name": "LGPL-3.0-only" + } + }, + { + "license": { + "name": "GPL-3.0-only" + } + }, + { + "license": { + "name": "GPL-2.0-only" + } + }, + { + "license": { + "name": "GFDL-1.2-only" } } ], @@ -4257,7 +4592,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } }, { @@ -4287,22 +4627,42 @@ }, { "license": { - "name": "LGPL-2.0" + "name": "LGPL-2.0-or-later" + } + }, + { + "license": { + "name": "LGPL-2.1-or-later" + } + }, + { + "license": { + "name": "GPL-3.0-or-later" + } + }, + { + "license": { + "name": "LGPL-3.0-or-later" + } + }, + { + "license": { + "name": "GPL-3.0-only" } }, { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.0-only" } }, { "license": { - "name": "GPL-3.0" + "name": "LGPL-2.1-only" } }, { "license": { - "name": "LGPL-3.0" + "name": "LGPL-3.0-only" } } ], @@ -4346,7 +4706,7 @@ "licenses": [ { "license": { - "name": "Expat" + "name": "MIT" } }, { @@ -4400,7 +4760,7 @@ }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } }, { @@ -4410,7 +4770,12 @@ }, { "license": { - "name": "Expat" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "MIT" } } ], @@ -4454,7 +4819,7 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } } ], @@ -4502,7 +4867,7 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } } ], @@ -4546,7 +4911,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } }, { @@ -4576,22 +4946,42 @@ }, { "license": { - "name": "LGPL-2.0" + "name": "LGPL-2.0-or-later" + } + }, + { + "license": { + "name": "LGPL-2.1-or-later" + } + }, + { + "license": { + "name": "GPL-3.0-or-later" + } + }, + { + "license": { + "name": "LGPL-3.0-or-later" + } + }, + { + "license": { + "name": "GPL-3.0-only" } }, { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.0-only" } }, { "license": { - "name": "GPL-3.0" + "name": "LGPL-2.1-only" } }, { "license": { - "name": "LGPL-3.0" + "name": "LGPL-3.0-only" } } ], @@ -4746,7 +5136,7 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } } ], @@ -4831,7 +5221,7 @@ "licenses": [ { "license": { - "name": "Expat" + "name": "MIT" } } ], @@ -4875,12 +5265,12 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-only" } }, { "license": { - "name": "GFDL" + "name": "GFDL-1.3-or-later" } } ], @@ -4924,7 +5314,7 @@ "licenses": [ { "license": { - "name": "Expat" + "name": "MIT" } } ], @@ -4968,7 +5358,7 @@ "licenses": [ { "license": { - "name": "Expat" + "name": "MIT" } } ], @@ -5120,7 +5510,7 @@ }, { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-only" } } ], @@ -5218,12 +5608,12 @@ }, { "license": { - "name": "Expat" + "name": "MIT" } }, { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-or-later" } }, { @@ -5268,27 +5658,27 @@ }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } }, { "license": { - "name": "Artistic" + "name": "Artistic-2.0" } }, { "license": { - "name": "zlib/libpng" + "name": "zlib-acknowledgement" } }, { "license": { - "name": "GPL-1.0" + "name": "GPL-1.0-or-later" } }, { "license": { - "name": "CC0" + "name": "CC0-1.0" } }, { @@ -5300,6 +5690,16 @@ "license": { "name": "Permissive" } + }, + { + "license": { + "name": "GPL-1.0-only" + } + }, + { + "license": { + "name": "GPL-3.0-only" + } } ], "purl": "pkg:deb/debian/ruby2.5@2.5.5-3%2Bdeb10u1?arch=amd64&distro=debian-10.2", @@ -5342,12 +5742,12 @@ "licenses": [ { "license": { - "name": "RubyLicense" + "name": "Ruby" } }, { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } } ], @@ -5391,7 +5791,7 @@ "licenses": [ { "license": { - "name": "Expat" + "name": "MIT" } } ], @@ -5431,7 +5831,7 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-only" } } ], @@ -5475,7 +5875,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } } ], @@ -5519,12 +5924,12 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-only" } }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } } ], @@ -5605,7 +6010,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } }, { @@ -5635,22 +6045,42 @@ }, { "license": { - "name": "LGPL-2.0" + "name": "LGPL-2.0-or-later" + } + }, + { + "license": { + "name": "LGPL-2.1-or-later" } }, { "license": { - "name": "LGPL-2.1" + "name": "GPL-3.0-or-later" } }, { "license": { - "name": "GPL-3.0" + "name": "LGPL-3.0-or-later" } }, { "license": { - "name": "LGPL-3.0" + "name": "GPL-3.0-only" + } + }, + { + "license": { + "name": "LGPL-2.0-only" + } + }, + { + "license": { + "name": "LGPL-2.1-only" + } + }, + { + "license": { + "name": "LGPL-3.0-only" } } ], @@ -6289,7 +6719,7 @@ "licenses": [ { "license": { - "name": "Apache License (2.0)" + "name": "Apache-2.0" } } ], @@ -7370,7 +7800,7 @@ "licenses": [ { "license": { - "name": "2-clause BSDL" + "name": "BSD-2-Clause" } } ], diff --git a/integration/testdata/helm_testchart.overridden.json.golden b/integration/testdata/helm_testchart.overridden.json.golden index 1b1ada2cd9a3..f4c984daa458 100644 --- a/integration/testdata/helm_testchart.overridden.json.golden +++ b/integration/testdata/helm_testchart.overridden.json.golden @@ -527,7 +527,7 @@ "IsCause": true, "Annotation": "", "Truncated": false, - "Highlighted": "\u001b[0m \u001b[38;5;33mrunAsUser\u001b[0m: \u001b[38;5;37m0", + "Highlighted": "\u001b[0m \u001b[38;5;33mrunAsUser\u001b[0m: \u001b[38;5;37m0\u001b[0m", "FirstCause": false, "LastCause": true } diff --git a/integration/testdata/julia-spdx.json.golden b/integration/testdata/julia-spdx.json.golden index 8ae4ead23a7d..89a4edb3f287 100644 --- a/integration/testdata/julia-spdx.json.golden +++ b/integration/testdata/julia-spdx.json.golden @@ -31,8 +31,8 @@ "downloadLocation": "NONE", "filesAnalyzed": false, "sourceInfo": "package found in: Manifest.toml", - "licenseConcluded": "NONE", - "licenseDeclared": "NONE", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", "externalRefs": [ { "referenceCategory": "PACKAGE-MANAGER", @@ -54,8 +54,8 @@ "downloadLocation": "NONE", "filesAnalyzed": false, "sourceInfo": "package found in: Manifest.toml", - "licenseConcluded": "NONE", - "licenseDeclared": "NONE", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", "externalRefs": [ { "referenceCategory": "PACKAGE-MANAGER", @@ -77,8 +77,8 @@ "downloadLocation": "NONE", "filesAnalyzed": false, "sourceInfo": "package found in: Manifest.toml", - "licenseConcluded": "NONE", - "licenseDeclared": "NONE", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", "externalRefs": [ { "referenceCategory": "PACKAGE-MANAGER", diff --git a/integration/testdata/license-cyclonedx.json.golden b/integration/testdata/license-cyclonedx.json.golden index cf69da9756ed..95f8acd896b1 100644 --- a/integration/testdata/license-cyclonedx.json.golden +++ b/integration/testdata/license-cyclonedx.json.golden @@ -47,8 +47,8 @@ "Link": "" }, { - "Severity": "UNKNOWN", - "Category": "unknown", + "Severity": "LOW", + "Category": "notice", "PkgName": "org.slf4j:slf4j-api", "FilePath": "", "Name": "MIT License", diff --git a/integration/testdata/opensuse-leap-151.json.golden b/integration/testdata/opensuse-leap-151.json.golden index 77b0148604bb..dadc7adcf600 100644 --- a/integration/testdata/opensuse-leap-151.json.golden +++ b/integration/testdata/opensuse-leap-151.json.golden @@ -66,7 +66,7 @@ "PkgID": "libopenssl1_1@1.1.0i-lp151.8.3.1.x86_64", "PkgName": "libopenssl1_1", "PkgIdentifier": { - "PURL": "pkg:rpm/opensuse.leap/libopenssl1_1@1.1.0i-lp151.8.3.1?arch=x86_64\u0026distro=opensuse.leap-15.1", + "PURL": "pkg:rpm/opensuse/libopenssl1_1@1.1.0i-lp151.8.3.1?arch=x86_64\u0026distro=opensuse.leap-15.1", "UID": "898b73ddd0412f57" }, "InstalledVersion": "1.1.0i-lp151.8.3.1", @@ -99,7 +99,7 @@ "PkgID": "openssl-1_1@1.1.0i-lp151.8.3.1.x86_64", "PkgName": "openssl-1_1", "PkgIdentifier": { - "PURL": "pkg:rpm/opensuse.leap/openssl-1_1@1.1.0i-lp151.8.3.1?arch=x86_64\u0026distro=opensuse.leap-15.1", + "PURL": "pkg:rpm/opensuse/openssl-1_1@1.1.0i-lp151.8.3.1?arch=x86_64\u0026distro=opensuse.leap-15.1", "UID": "58980d005de43f54" }, "InstalledVersion": "1.1.0i-lp151.8.3.1", diff --git a/integration/testdata/opensuse-tumbleweed.json.golden b/integration/testdata/opensuse-tumbleweed.json.golden index b3dc552bdfca..a15146616bc2 100644 --- a/integration/testdata/opensuse-tumbleweed.json.golden +++ b/integration/testdata/opensuse-tumbleweed.json.golden @@ -69,7 +69,7 @@ "PkgID": "libopenssl3@3.1.4-9.1.x86_64", "PkgName": "libopenssl3", "PkgIdentifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libopenssl3@3.1.4-9.1?arch=x86_64\u0026distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libopenssl3@3.1.4-9.1?arch=x86_64\u0026distro=opensuse.tumbleweed-20240607", "UID": "f051425f385d2b99" }, "InstalledVersion": "3.1.4-9.1", diff --git a/integration/testdata/sl-micro-rancher5.4.json.golden b/integration/testdata/sl-micro-rancher5.4.json.golden new file mode 100644 index 000000000000..99e2ad4ca599 --- /dev/null +++ b/integration/testdata/sl-micro-rancher5.4.json.golden @@ -0,0 +1,69 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/sle-micro-rancher-5.4_ndb.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "suse linux enterprise micro", + "Name": "5.4" + }, + "ImageID": "sha256:c45ec974938acac29c893b5d273d73e4ebdd7e6a97b6fa861dfbd8dd430b9016", + "DiffIDs": [ + "sha256:7cdd3aec849d122d63dc83a5e1e2fb89b341c67b03e25979131ca335a463bb57" + ], + "ImageConfig": { + "architecture": "amd64", + "author": "SUSE LLC (https://www.suse.com/)", + "created": "2024-09-03T17:54:39Z", + "history": [ + { + "author": "SUSE LLC \u003chttps://www.suse.com/\u003e", + "created": "2024-09-03T17:54:39Z", + "created_by": "KIWI 9.24.43" + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:7cdd3aec849d122d63dc83a5e1e2fb89b341c67b03e25979131ca335a463bb57" + ] + }, + "config": { + "Cmd": [ + "/bin/bash" + ], + "Labels": { + "com.suse.eula": "sle-eula", + "com.suse.image-type": "sle-micro", + "com.suse.release-stage": "released", + "com.suse.sle.micro.rancher.created": "2024-09-03T17:53:32.129328086Z", + "com.suse.sle.micro.rancher.description": "Image containing a micro environment for containers based on the SLE Micro for Rancher.", + "com.suse.sle.micro.rancher.disturl": "obs://build.suse.de/SUSE:SLE-15-SP4:Update:Products:Micro54:Update:CR/images/fcaa3a91b132f1955fa900b902aef7f2-SLE-Micro-Rancher", + "com.suse.sle.micro.rancher.reference": "registry.suse.com/suse/sle-micro-rancher/5.4:%PKG_VERSION%-%RELEASE", + "com.suse.sle.micro.rancher.title": "SLE Micro for Rancher Base Container", + "com.suse.sle.micro.rancher.url": "https://www.suse.com/products/micro/", + "com.suse.sle.micro.rancher.vendor": "SUSE LLC", + "com.suse.sle.micro.rancher.version": "5.4", + "com.suse.supportlevel": "l3", + "org.openbuildservice.disturl": "obs://build.suse.de/SUSE:SLE-15-SP4:Update:Products:Micro54:Update:CR/images/fcaa3a91b132f1955fa900b902aef7f2-SLE-Micro-Rancher", + "org.opencontainers.image.created": "2024-09-03T17:53:32.129328086Z", + "org.opencontainers.image.description": "Image containing a micro environment for containers based on the SLE Micro for Rancher.", + "org.opencontainers.image.title": "SLE Micro for Rancher Base Container", + "org.opencontainers.image.url": "https://www.suse.com/products/micro/", + "org.opencontainers.image.vendor": "SUSE LLC", + "org.opencontainers.image.version": "5.4", + "org.suse.reference": "registry.suse.com/suse/sle-micro-rancher/5.4:%PKG_VERSION%-%RELEASE" + } + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/sle-micro-rancher-5.4_ndb.tar.gz (suse linux enterprise micro 5.4)", + "Class": "os-pkgs", + "Type": "suse linux enterprise micro" + } + ] +} diff --git a/integration/testdata/ubi-7-comprehensive.json.golden b/integration/testdata/ubi-7-comprehensive.json.golden new file mode 100644 index 000000000000..b500dc5d4bc5 --- /dev/null +++ b/integration/testdata/ubi-7-comprehensive.json.golden @@ -0,0 +1,192 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/ubi-7.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "redhat", + "Name": "7.7" + }, + "ImageID": "sha256:6fecccc91c83e11ae4fede6793e9410841221d4779520c2b9e9fb7f7b3830264", + "DiffIDs": [ + "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac", + "sha256:ecb0311889b3478bc9b62660fa9391d5ebf8da4c6ae143cb33434873668f9e36" + ], + "ImageConfig": { + "architecture": "amd64", + "created": "2019-09-02T12:56:43.939095Z", + "docker_version": "1.13.1", + "history": [ + { + "created": "2019-09-02T12:56:36.440695936Z", + "comment": "Imported from -" + }, + { + "created": "2019-09-02T12:56:43.939095Z" + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac", + "sha256:ecb0311889b3478bc9b62660fa9391d5ebf8da4c6ae143cb33434873668f9e36" + ] + }, + "config": { + "Cmd": [ + "/bin/bash" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "container=oci" + ], + "Hostname": "0da2e3774382", + "Image": "2e9103a7b91a7ffe333e9162ce98ea078263747527571655e93bd4d35ee278f0", + "Labels": { + "architecture": "x86_64", + "authoritative-source-url": "registry.access.redhat.com", + "build-date": "2019-09-02T12:56:18.824770", + "com.redhat.build-host": "cpt-1005.osbs.prod.upshift.rdu2.redhat.com", + "com.redhat.component": "ubi7-container", + "com.redhat.license_terms": "https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI", + "description": "The Universal Base Image is designed and engineered to be the base layer for all of your containerized applications, middleware and utilities. This base image is freely redistributable, but Red Hat only supports Red Hat technologies through subscriptions for Red Hat products. This image is maintained by Red Hat and updated regularly.", + "distribution-scope": "public", + "io.k8s.description": "The Universal Base Image is designed and engineered to be the base layer for all of your containerized applications, middleware and utilities. This base image is freely redistributable, but Red Hat only supports Red Hat technologies through subscriptions for Red Hat products. This image is maintained by Red Hat and updated regularly.", + "io.k8s.display-name": "Red Hat Universal Base Image 7", + "io.openshift.tags": "base rhel7", + "maintainer": "Red Hat, Inc.", + "name": "ubi7", + "release": "140", + "summary": "Provides the latest release of the Red Hat Universal Base Image 7.", + "url": "https://access.redhat.com/containers/#/registry.access.redhat.com/ubi7/images/7.7-140", + "vcs-ref": "4c80c8aa26e69950ab11b87789c8fb7665b1632d", + "vcs-type": "git", + "vendor": "Red Hat, Inc.", + "version": "7.7" + }, + "ArgsEscaped": true + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/ubi-7.tar.gz (redhat 7.7)", + "Class": "os-pkgs", + "Type": "redhat", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-18276", + "PkgID": "bash@4.2.46-33.el7.x86_64", + "PkgName": "bash", + "PkgIdentifier": { + "PURL": "pkg:rpm/redhat/bash@4.2.46-33.el7?arch=x86_64\u0026distro=redhat-7.7", + "UID": "f5b786381193ad1b" + }, + "InstalledVersion": "4.2.46-33.el7", + "Status": "will_not_fix", + "Layer": { + "Digest": "sha256:7b1c937e0f6794db2535be6e4cb6d60a0b668ef78c2576611a3fb9c97a95ccdf", + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + }, + "SeveritySource": "redhat", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-18276", + "Title": "bash: when effective UID is not equal to its real UID the saved UID is not dropped", + "Description": "An issue was discovered in disable_priv_mode in shell.c in GNU Bash through 5.0 patch 11. By default, if Bash is run with its effective UID not equal to its real UID, it will drop privileges by setting its effective UID to its real UID. However, it does so incorrectly. On Linux and other systems that support \"saved UID\" functionality, the saved UID is not dropped. An attacker with command execution in the shell can use \"enable -f\" for runtime loading of a new builtin, which can be a shared object that calls setuid() and therefore regains privileges. However, binaries running with an effective UID of 0 are unaffected.", + "Severity": "LOW", + "CweIDs": [ + "CWE-273" + ], + "VendorSeverity": { + "cbl-mariner": 3, + "nvd": 3, + "oracle-oval": 1, + "photon": 3, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "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": { + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 7.8 + } + }, + "References": [ + "http://packetstormsecurity.com/files/155498/Bash-5.0-Patch-11-Privilege-Escalation.html", + "https://access.redhat.com/security/cve/CVE-2019-18276", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-18276", + "https://github.com/bminor/bash/commit/951bdaad7a18cc0dc1036bba86b18b90874d39ff", + "https://linux.oracle.com/cve/CVE-2019-18276.html", + "https://linux.oracle.com/errata/ELSA-2021-1679.html", + "https://lists.apache.org/thread.html/rf9fa47ab66495c78bb4120b0754dd9531ca2ff0430f6685ac9b07772@%3Cdev.mina.apache.org%3E", + "https://nvd.nist.gov/vuln/detail/CVE-2019-18276", + "https://security.gentoo.org/glsa/202105-34", + "https://security.netapp.com/advisory/ntap-20200430-0003/", + "https://www.youtube.com/watch?v=-wGtxJ8opa8" + ], + "PublishedDate": "2019-11-28T01:15:00Z", + "LastModifiedDate": "2021-05-26T12:15:00Z" + } + ] + }, + { + "Target": "Python", + "Class": "lang-pkgs", + "Type": "python-pkg", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2022-40897", + "PkgName": "setuptools", + "PkgPath": "usr/lib/python2.7/site-packages/setuptools-0.9.8-py2.7.egg-info/PKG-INFO", + "PkgIdentifier": { + "PURL": "pkg:pypi/setuptools@0.9.8", + "UID": "13d32ebdc7bda1b4" + }, + "InstalledVersion": "0.9.8", + "FixedVersion": "65.5.1", + "Status": "fixed", + "Layer": { + "Digest": "sha256:7b1c937e0f6794db2535be6e4cb6d60a0b668ef78c2576611a3fb9c97a95ccdf", + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + }, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2022-40897", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Pip", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Apip" + }, + "Title": "pypa-setuptools: Regular Expression Denial of Service (ReDoS) in package_index.py", + "Description": "Python Packaging Authority (PyPA) setuptools before 65.5.1 allows remote attackers to cause a denial of service via HTML in a crafted package or custom PackageIndex page. There is a Regular Expression Denial of Service (ReDoS) in package_index.py.", + "Severity": "HIGH", + "CweIDs": [ + "CWE-1333" + ], + "VendorSeverity": { + "ghsa": 3, + "nvd": 2 + }, + "CVSS": { + "nvd": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H", + "V3Score": 5.9 + } + }, + "References": [ + "https://access.redhat.com/errata/RHSA-2023:0952", + "https://access.redhat.com/security/cve/CVE-2022-40897" + ], + "PublishedDate": "2022-12-23T00:15:13.987Z", + "LastModifiedDate": "2024-06-21T19:15:23.877Z" + } + ] + } + ] +} diff --git a/internal/dbtest/fake.go b/internal/dbtest/fake.go index 9f2484bbf94a..528c549bda7f 100644 --- a/internal/dbtest/fake.go +++ b/internal/dbtest/fake.go @@ -62,10 +62,7 @@ func NewFakeDB(t *testing.T, dbPath string, opts FakeDBOptions) *oci.Artifact { opt := ftypes.RegistryOptions{ Insecure: false, } - art, err := oci.NewArtifact("dummy", true, opt, oci.WithImage(img)) - require.NoError(t, err) - - return art + return oci.NewArtifact("dummy", opt, oci.WithImage(img)) } func ArchiveDir(t *testing.T, dir string) string { diff --git a/internal/testutil/docker.go b/internal/testutil/docker.go new file mode 100644 index 000000000000..60e1bb3ab668 --- /dev/null +++ b/internal/testutil/docker.go @@ -0,0 +1,57 @@ +package testutil + +import ( + "context" + "encoding/json" + "os" + "strings" + "testing" + + "github.com/docker/docker/api/types/image" + "github.com/docker/docker/client" + "github.com/stretchr/testify/require" +) + +type DockerClient struct { + *client.Client +} + +func NewDockerClient(t *testing.T) *DockerClient { + cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + require.NoError(t, err) + return &DockerClient{Client: cli} +} + +func (c *DockerClient) ImageLoad(t *testing.T, ctx context.Context, imageFile string) string { + t.Helper() + testfile, err := os.Open(imageFile) + require.NoError(t, err) + defer testfile.Close() + + // Load image into docker engine + res, err := c.Client.ImageLoad(ctx, testfile, true) + require.NoError(t, err) + defer res.Body.Close() + + // Parse the response and extract the loaded image name + var data struct { + Stream string `json:"stream"` + } + err = json.NewDecoder(res.Body).Decode(&data) + require.NoError(t, err) + loadedImage := strings.TrimPrefix(data.Stream, "Loaded image: ") + loadedImage = strings.TrimSpace(loadedImage) + require.NotEmpty(t, loadedImage, data.Stream) + + t.Cleanup(func() { c.ImageRemove(t, ctx, loadedImage) }) + + return loadedImage +} + +func (c *DockerClient) ImageRemove(t *testing.T, ctx context.Context, imageID string) { + t.Helper() + _, _ = c.Client.ImageRemove(ctx, imageID, image.RemoveOptions{ + Force: true, + PruneChildren: true, + }) +} diff --git a/magefiles/docs.go b/magefiles/docs.go index 1a59007de229..bfdd7480d2a9 100644 --- a/magefiles/docs.go +++ b/magefiles/docs.go @@ -3,7 +3,11 @@ package main import ( + "cmp" + "fmt" "os" + "slices" + "strings" "github.com/spf13/cobra/doc" @@ -12,6 +16,15 @@ import ( "github.com/aquasecurity/trivy/pkg/log" ) +const ( + title = "Config file" + description = "Trivy can be customized by tweaking a `trivy.yaml` file.\n" + + "The config path can be overridden by the `--config` flag.\n\n" + + "An example is [here][example].\n\n" + + "These samples contain default values for flags." + footer = "[example]: https://github.com/aquasecurity/trivy/tree/{{ git.tag }}/examples/trivy-conf/trivy.yaml" +) + // Generate CLI references func main() { // Set a dummy path for the documents @@ -26,4 +39,117 @@ func main() { if err := doc.GenMarkdownTree(cmd, "./docs/docs/references/configuration/cli"); err != nil { log.Fatal("Fatal error", log.Err(err)) } + if err := generateConfigDocs("./docs/docs/references/configuration/config-file.md"); err != nil { + log.Fatal("Fatal error in config file generation", log.Err(err)) + } +} + +// generateConfigDocs creates markdown file for Trivy config. +func generateConfigDocs(filename string) error { + // remoteFlags should contain Client and Server flags. + // NewClientFlags doesn't initialize `Listen` field + remoteFlags := flag.NewClientFlags() + remoteFlags.Listen = flag.ServerListenFlag.Clone() + + // These flags don't work from config file. + // Clear configName to skip them later. + globalFlags := flag.NewGlobalFlagGroup() + globalFlags.ConfigFile.ConfigName = "" + globalFlags.ShowVersion.ConfigName = "" + globalFlags.GenerateDefaultConfig.ConfigName = "" + + var allFlagGroups = []flag.FlagGroup{ + globalFlags, + flag.NewCacheFlagGroup(), + flag.NewCleanFlagGroup(), + remoteFlags, + flag.NewDBFlagGroup(), + flag.NewImageFlagGroup(), + flag.NewK8sFlagGroup(), + flag.NewLicenseFlagGroup(), + flag.NewMisconfFlagGroup(), + flag.NewModuleFlagGroup(), + flag.NewPackageFlagGroup(), + flag.NewRegistryFlagGroup(), + flag.NewRegoFlagGroup(), + flag.NewReportFlagGroup(), + flag.NewRepoFlagGroup(), + flag.NewScanFlagGroup(), + flag.NewSecretFlagGroup(), + flag.NewVulnerabilityFlagGroup(), + } + + f, err := os.Create(filename) + if err != nil { + return err + } + defer f.Close() + f.WriteString("# " + title + "\n\n") + f.WriteString(description + "\n") + + for _, group := range allFlagGroups { + f.WriteString("## " + group.Name() + " options\n") + writeFlags(group, f) + } + + f.WriteString(footer) + return nil +} + +func writeFlags(group flag.FlagGroup, w *os.File) { + flags := group.Flags() + // Sort flags to avoid duplicates of non-last parts of config file + slices.SortFunc(flags, func(a, b flag.Flagger) int { + return cmp.Compare(a.GetConfigName(), b.GetConfigName()) + }) + w.WriteString("\n```yaml\n") + + var lastParts []string + for _, flg := range flags { + if flg.GetConfigName() == "" || flg.Hidden() { + continue + } + // We need to split the config name on `.` to make the indentations needed in yaml. + parts := strings.Split(flg.GetConfigName(), ".") + for i := range parts { + // Skip already added part + if len(lastParts) >= i+1 && parts[i] == lastParts[i] { + continue + } + ind := strings.Repeat(" ", i) + // We need to add a comment and example values only for the last part of the config name. + isLastPart := i == len(parts)-1 + if isLastPart { + // Some `Flags` don't support flag for CLI. (e.g.`LicenseForbidden`). + if flg.GetName() != "" { + fmt.Fprintf(w, "%s# Same as '--%s'\n", ind, flg.GetName()) + } + } + w.WriteString(ind + parts[i] + ":") + if isLastPart { + writeFlagValue(flg.GetDefaultValue(), ind, w) + } + w.WriteString("\n") + } + lastParts = parts + } + w.WriteString("```\n") +} + +func writeFlagValue(val any, ind string, w *os.File) { + switch v := val.(type) { + case []string: + if len(v) > 0 { + w.WriteString("\n") + for _, vv := range v { + fmt.Fprintf(w, "%s - %s\n", ind, vv) + } + } else { + w.WriteString(" []\n") + } + case string: + fmt.Fprintf(w, " %q\n", v) + default: + fmt.Fprintf(w, " %v\n", v) + } } diff --git a/magefiles/magefile.go b/magefiles/magefile.go index 7ce148d885a0..06ebd7e665e1 100644 --- a/magefiles/magefile.go +++ b/magefiles/magefile.go @@ -16,6 +16,8 @@ import ( "github.com/magefile/mage/sh" "github.com/magefile/mage/target" + //mage:import rpm + rpm "github.com/aquasecurity/trivy/pkg/fanal/analyzer/pkg/rpm/testdata" // Trivy packages should not be imported in Mage (see https://github.com/aquasecurity/trivy/pull/4242), // but this package doesn't have so many dependencies, and Mage is still fast. "github.com/aquasecurity/trivy/pkg/log" @@ -30,6 +32,10 @@ var ( } ) +var protoFiles = []string{ + "pkg/iac/scanners/terraformplan/snapshot/planproto/planfile.proto", +} + func init() { slog.SetDefault(log.New(log.NewHandler(os.Stderr, nil))) // stdout is suppressed in mage } @@ -154,11 +160,11 @@ func Mock(dir string) error { func Protoc() error { // It is called in the protoc container if _, ok := os.LookupEnv("TRIVY_PROTOC_CONTAINER"); ok { - protoFiles, err := findProtoFiles() + rpcProtoFiles, err := findRPCProtoFiles() if err != nil { return err } - for _, file := range protoFiles { + for _, file := range rpcProtoFiles { // Check if the generated Go file is up-to-date dst := strings.TrimSuffix(file, ".proto") + ".pb.go" if updated, err := target.Path(dst, file); err != nil { @@ -173,6 +179,13 @@ func Protoc() error { return err } } + + for _, file := range protoFiles { + if err := sh.RunV("protoc", ".", "paths=source_relative", "--go_out", ".", "--go_opt", + "paths=source_relative", file); err != nil { + return err + } + } return nil } @@ -257,7 +270,7 @@ func compileWasmModules(pattern string) error { // Unit runs unit tests func (t Test) Unit() error { - mg.Deps(t.GenerateModules) + mg.Deps(t.GenerateModules, rpm.Fixtures) return sh.RunWithV(ENV, "go", "test", "-v", "-short", "-coverprofile=coverage.txt", "-covermode=atomic", "./...") } @@ -331,11 +344,13 @@ func Fmt() error { } // Format proto files - protoFiles, err := findProtoFiles() + rpcProtoFiles, err := findRPCProtoFiles() if err != nil { return err } - for _, file := range protoFiles { + + allProtoFiles := append(protoFiles, rpcProtoFiles...) + for _, file := range allProtoFiles { if err = sh.Run("clang-format", "-i", file); err != nil { return err } @@ -422,7 +437,7 @@ func (Docs) Generate() error { return sh.RunWith(ENV, "go", "run", "-tags=mage_docs", "./magefiles") } -func findProtoFiles() ([]string, error) { +func findRPCProtoFiles() ([]string, error) { var files []string err := filepath.WalkDir("rpc", func(path string, d fs.DirEntry, err error) error { switch { diff --git a/mkdocs.yml b/mkdocs.yml index 9585c8341f94..abe494df9dd9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -78,7 +78,6 @@ nav: - Azure Linux (CBL-Mariner): docs/coverage/os/azure.md - CentOS: docs/coverage/os/centos.md - Chainguard: docs/coverage/os/chainguard.md - - Conda: docs/coverage/os/conda.md - Debian: docs/coverage/os/debian.md - Oracle Linux: docs/coverage/os/oracle.md - Photon OS: docs/coverage/os/photon.md @@ -88,7 +87,6 @@ nav: - Ubuntu: docs/coverage/os/ubuntu.md - Wolfi: docs/coverage/os/wolfi.md - Google Distroless (Images): docs/coverage/os/google-distroless.md - - Bitnami (Images): docs/coverage/os/bitnami.md - Language: - Overview: docs/coverage/language/index.md - C/C++: docs/coverage/language/c.md @@ -112,6 +110,10 @@ nav: - Helm: docs/coverage/iac/helm.md - Kubernetes: docs/coverage/iac/kubernetes.md - Terraform: docs/coverage/iac/terraform.md + - Others: + - Bitnami Images: docs/coverage/others/bitnami.md + - Conda: docs/coverage/others/conda.md + - RPM Archives: docs/coverage/others/rpm.md - Kubernetes: docs/coverage/kubernetes.md - Configuration: - Overview: docs/configuration/index.md @@ -141,7 +143,7 @@ nav: - Developer guide: docs/plugin/developer-guide.md - Advanced: - Modules: docs/advanced/modules.md - - Air-Gapped Environment: docs/advanced/air-gap.md + - Advanced Network Scenarios: docs/advanced/air-gap.md - Container Image: - Embed in Dockerfile: docs/advanced/container/embed-in-dockerfile.md - Unpacked container image filesystem: docs/advanced/container/unpacked-filesystem.md diff --git a/pkg/cache/key.go b/pkg/cache/key.go index 7d6720393554..0ad0fde82fe1 100644 --- a/pkg/cache/key.go +++ b/pkg/cache/key.go @@ -13,6 +13,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/artifact" + "github.com/aquasecurity/trivy/pkg/fanal/types" ) func CalcKey(id string, analyzerVersions analyzer.Versions, hookVersions map[string]int, artifactOpt artifact.Option) (string, error) { @@ -24,13 +25,22 @@ 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 - AnalyzerVersions analyzer.Versions - HookVersions map[string]int - SkipFiles []string - SkipDirs []string - FilePatterns []string `json:",omitempty"` - }{id, analyzerVersions, hookVersions, artifactOpt.WalkerOption.SkipFiles, artifactOpt.WalkerOption.SkipDirs, artifactOpt.FilePatterns} + ID string + AnalyzerVersions analyzer.Versions + HookVersions map[string]int + SkipFiles []string + SkipDirs []string + FilePatterns []string `json:",omitempty"` + DetectionPriority types.DetectionPriority `json:",omitempty"` + }{ + id, + analyzerVersions, + hookVersions, + artifactOpt.WalkerOption.SkipFiles, + artifactOpt.WalkerOption.SkipDirs, + artifactOpt.FilePatterns, + artifactOpt.DetectionPriority, + } if err := json.NewEncoder(h).Encode(keyBase); err != nil { return "", xerrors.Errorf("json encode error: %w", err) diff --git a/pkg/cache/key_test.go b/pkg/cache/key_test.go index 012e9edd407f..d80748567683 100644 --- a/pkg/cache/key_test.go +++ b/pkg/cache/key_test.go @@ -8,21 +8,23 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/artifact" + "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/walker" "github.com/aquasecurity/trivy/pkg/misconf" ) func TestCalcKey(t *testing.T) { type args struct { - key string - analyzerVersions analyzer.Versions - hookVersions map[string]int - skipFiles []string - skipDirs []string - patterns []string - policy []string - data []string - secretConfigPath string + key string + analyzerVersions analyzer.Versions + hookVersions map[string]int + skipFiles []string + skipDirs []string + patterns []string + policy []string + data []string + secretConfigPath string + detectionPriority types.DetectionPriority } tests := []struct { name string @@ -115,7 +117,10 @@ func TestCalcKey(t *testing.T) { "debian": 1, }, }, - patterns: []string{"test", ""}, + patterns: []string{ + "test", + "", + }, }, want: "sha256:71abf09bf1422531e2838db692b80f9b9f48766f56b7d3d02aecdb36b019e103", }, @@ -129,7 +134,10 @@ func TestCalcKey(t *testing.T) { "debian": 1, }, }, - patterns: []string{"", "test"}, + patterns: []string{ + "", + "test", + }, }, want: "sha256:71abf09bf1422531e2838db692b80f9b9f48766f56b7d3d02aecdb36b019e103", }, @@ -177,6 +185,23 @@ func TestCalcKey(t *testing.T) { }, want: "sha256:363f70f4ee795f250873caea11c2fc94ef12945444327e7e2f8a99e3884695e0", }, + { + name: "detection priority", + args: args{ + key: "sha256:5c534be56eca62e756ef2ef51523feda0f19cd7c15bb0c015e3d6e3ae090bf6e", + analyzerVersions: analyzer.Versions{ + Analyzers: map[string]int{ + "alpine": 1, + "debian": 1, + }, + }, + skipFiles: []string{"app/deployment.yaml"}, + skipDirs: []string{"usr/java"}, + policy: []string{"testdata/policy"}, + detectionPriority: types.PriorityComprehensive, + }, + want: "sha256:2f1c898271e84f4382cd48ae7533069cc3dc656c2d688ac108f5db1a0d9fd393", + }, { name: "secret config", @@ -231,7 +256,8 @@ func TestCalcKey(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { artifactOpt := artifact.Option{ - FilePatterns: tt.args.patterns, + FilePatterns: tt.args.patterns, + DetectionPriority: tt.args.detectionPriority, MisconfScannerOption: misconf.ScannerOption{ PolicyPaths: tt.args.policy, @@ -249,7 +275,6 @@ func TestCalcKey(t *testing.T) { } got, err := CalcKey(tt.args.key, tt.args.analyzerVersions, tt.args.hookVersions, artifactOpt) if tt.wantErr != "" { - require.Error(t, err) assert.ErrorContains(t, err, tt.wantErr) return } diff --git a/pkg/cache/remote.go b/pkg/cache/remote.go index 44c9f63c92d8..19d748bf7923 100644 --- a/pkg/cache/remote.go +++ b/pkg/cache/remote.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "net/http" + "github.com/twitchtv/twirp" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -19,6 +20,7 @@ type RemoteOptions struct { ServerAddr string CustomHeaders http.Header Insecure bool + PathPrefix string } // RemoteCache implements remote cache @@ -39,7 +41,12 @@ func NewRemoteCache(opts RemoteOptions) *RemoteCache { }, }, } - c := rpcCache.NewCacheProtobufClient(opts.ServerAddr, httpClient) + + var twirpOpts []twirp.ClientOption + if opts.PathPrefix != "" { + twirpOpts = append(twirpOpts, twirp.WithClientPathPrefix(opts.PathPrefix)) + } + c := rpcCache.NewCacheProtobufClient(opts.ServerAddr, httpClient, twirpOpts...) return &RemoteCache{ ctx: ctx, client: c, diff --git a/pkg/commands/app.go b/pkg/commands/app.go index a3bd7b78e6f1..bdba2fa12ac7 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -624,7 +624,7 @@ func NewServerCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { // 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.JavaDBRepository = nil // disable '--java-db-repository' + serverFlags.DBFlagGroup.JavaDBRepositories = nil // disable '--java-db-repository' cmd := &cobra.Command{ Use: "server [flags]", @@ -1143,10 +1143,10 @@ func NewSBOMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { CacheFlagGroup: flag.NewCacheFlagGroup(), DBFlagGroup: flag.NewDBFlagGroup(), PackageFlagGroup: flag.NewPackageFlagGroup(), - RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode + RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode + RegistryFlagGroup: flag.NewRegistryFlagGroup(), // for DBs in private registries ReportFlagGroup: reportFlagGroup, ScanFlagGroup: scanFlagGroup, - SBOMFlagGroup: flag.NewSBOMFlagGroup(), VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), LicenseFlagGroup: licenseFlagGroup, } diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index e49829e352fb..49c7d3b34296 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "os" "slices" "github.com/hashicorp/go-multierror" @@ -40,7 +41,6 @@ const ( TargetFilesystem TargetKind = "fs" TargetRootfs TargetKind = "rootfs" TargetRepository TargetKind = "repo" - TargetImageArchive TargetKind = "archive" TargetSBOM TargetKind = "sbom" TargetVM TargetKind = "vm" ) @@ -292,7 +292,7 @@ func (r *runner) initDB(ctx context.Context, opts flag.Options) error { // download the database file noProgress := opts.Quiet || opts.NoProgress - if err := operation.DownloadDB(ctx, opts.AppVersion, opts.CacheDir, opts.DBRepository, noProgress, opts.SkipDBUpdate, opts.RegistryOpts()); err != nil { + if err := operation.DownloadDB(ctx, opts.AppVersion, opts.CacheDir, opts.DBRepositories, noProgress, opts.SkipDBUpdate, opts.RegistryOpts()); err != nil { return err } @@ -322,7 +322,7 @@ func (r *runner) initJavaDB(opts flag.Options) error { // Update the Java DB noProgress := opts.Quiet || opts.NoProgress - javadb.Init(opts.CacheDir, opts.JavaDBRepository, opts.SkipJavaDBUpdate, noProgress, opts.RegistryOpts()) + javadb.Init(opts.CacheDir, opts.JavaDBRepositories, opts.SkipJavaDBUpdate, noProgress, opts.RegistryOpts()) if opts.DownloadJavaDBOnly { if err := javadb.Update(); err != nil { return xerrors.Errorf("Java DB error: %w", err) @@ -345,6 +345,15 @@ func Run(ctx context.Context, opts flag.Options, targetKind TargetKind) (err err } }() + if opts.ServerAddr != "" && opts.Scanners.AnyEnabled(types.MisconfigScanner, types.SecretScanner) { + log.WarnContext(ctx, + fmt.Sprintf( + "Trivy runs in client/server mode, but misconfiguration and license scanning will be done on the client side, see %s", + doc.URL("/docs/references/modes/client-server", ""), + ), + ) + } + if opts.GenerateDefaultConfig { log.Info("Writing the default config to trivy-default.yaml...") return viper.SafeWriteConfigAs("trivy-default.yaml") @@ -359,32 +368,23 @@ func Run(ctx context.Context, opts flag.Options, targetKind TargetKind) (err err } defer r.Close(ctx) - var report types.Report - switch targetKind { - case TargetContainerImage, TargetImageArchive: - if report, err = r.ScanImage(ctx, opts); err != nil { - return xerrors.Errorf("image scan error: %w", err) - } - case TargetFilesystem: - if report, err = r.ScanFilesystem(ctx, opts); err != nil { - return xerrors.Errorf("filesystem scan error: %w", err) - } - case TargetRootfs: - if report, err = r.ScanRootfs(ctx, opts); err != nil { - return xerrors.Errorf("rootfs scan error: %w", err) - } - case TargetRepository: - if report, err = r.ScanRepository(ctx, opts); err != nil { - return xerrors.Errorf("repository scan error: %w", err) - } - case TargetSBOM: - if report, err = r.ScanSBOM(ctx, opts); err != nil { - return xerrors.Errorf("sbom scan error: %w", err) - } - case TargetVM: - if report, err = r.ScanVM(ctx, opts); err != nil { - return xerrors.Errorf("vm scan error: %w", err) - } + scans := map[TargetKind]func(context.Context, flag.Options) (types.Report, error){ + TargetContainerImage: r.ScanImage, + TargetFilesystem: r.ScanFilesystem, + TargetRootfs: r.ScanRootfs, + TargetRepository: r.ScanRepository, + TargetSBOM: r.ScanSBOM, + TargetVM: r.ScanVM, + } + + scanFunction, exists := scans[targetKind] + if !exists { + return xerrors.Errorf("unknown target kind: %s", targetKind) + } + + report, err := scanFunction(ctx, opts) + if err != nil { + return xerrors.Errorf("%s scan error: %w", targetKind, err) } report, err = r.Filter(ctx, opts, report) @@ -458,6 +458,12 @@ func disabledAnalyzers(opts flag.Options) []analyzer.Type { analyzers = append(analyzers, analyzer.TypeExecutable) } + // Disable RPM archive analyzer unless the environment variable is set + // TODO: add '--enable-analyzers' and delete this environment variable + if os.Getenv("TRIVY_EXPERIMENTAL_RPM_ARCHIVE") == "" { + analyzers = append(analyzers, analyzer.TypeRpmArchive) + } + return analyzers } @@ -471,7 +477,7 @@ func filterMisconfigAnalyzers(included, all []analyzer.Type) ([]analyzer.Type, e return lo.Without(all, included...), nil } -func (r *runner) initScannerConfig(opts flag.Options) (ScannerConfig, types.ScanOptions, error) { +func (r *runner) initScannerConfig(ctx context.Context, opts flag.Options) (ScannerConfig, types.ScanOptions, error) { target := opts.Target if opts.Input != "" { target = opts.Input @@ -502,43 +508,13 @@ func (r *runner) initScannerConfig(opts flag.Options) (ScannerConfig, types.Scan log.WithPrefix(log.PrefixVulnerability).Info("Vulnerability scanning is enabled") } - // ScannerOption is filled only when config scanning is enabled. + // Misconfig ScannerOption is filled only when config scanning is enabled. var configScannerOptions misconf.ScannerOption if opts.Scanners.Enabled(types.MisconfigScanner) || opts.ImageConfigScanners.Enabled(types.MisconfigScanner) { - logger := log.WithPrefix(log.PrefixMisconfiguration) - logger.Info("Misconfiguration scanning is enabled") - - var downloadedPolicyPaths []string - var disableEmbedded bool - - downloadedPolicyPaths, err := operation.InitBuiltinPolicies(context.Background(), opts.CacheDir, opts.Quiet, opts.SkipCheckUpdate, opts.MisconfOptions.ChecksBundleRepository, opts.RegistryOpts()) + var err error + configScannerOptions, err = initMisconfScannerOption(ctx, opts) if err != nil { - if !opts.SkipCheckUpdate { - logger.Error("Falling back to embedded checks", log.Err(err)) - } - } else { - logger.Debug("Policies successfully loaded from disk") - disableEmbedded = true - } - configScannerOptions = misconf.ScannerOption{ - Debug: opts.Debug, - Trace: opts.Trace, - Namespaces: append(opts.CheckNamespaces, rego.BuiltinNamespaces()...), - PolicyPaths: append(opts.CheckPaths, downloadedPolicyPaths...), - DataPaths: opts.DataPaths, - HelmValues: opts.HelmValues, - HelmValueFiles: opts.HelmValueFiles, - HelmFileValues: opts.HelmFileValues, - HelmStringValues: opts.HelmStringValues, - HelmAPIVersions: opts.HelmAPIVersions, - HelmKubeVersion: opts.HelmKubeVersion, - TerraformTFVars: opts.TerraformTFVars, - CloudFormationParamVars: opts.CloudFormationParamVars, - K8sVersion: opts.K8sVersion, - DisableEmbeddedPolicies: disableEmbedded, - DisableEmbeddedLibraries: disableEmbedded, - IncludeDeprecatedChecks: opts.IncludeDeprecatedChecks, - TfExcludeDownloaded: opts.TfExcludeDownloaded, + return ScannerConfig{}, types.ScanOptions{}, err } } @@ -568,17 +544,18 @@ func (r *runner) initScannerConfig(opts flag.Options) (ScannerConfig, types.Scan fileChecksum = true } + // Disable the post handler for filtering system file when detection priority is comprehensive. + disabledHandlers := lo.Ternary(opts.DetectionPriority == ftypes.PriorityComprehensive, + []ftypes.HandlerType{ftypes.SystemFileFilteringPostHandler}, nil) + return ScannerConfig{ Target: target, CacheOptions: opts.CacheOpts(), RemoteCacheOptions: opts.RemoteCacheOpts(), - ServerOption: client.ScannerOption{ - RemoteURL: opts.ServerAddr, - CustomHeaders: opts.CustomHeaders, - Insecure: opts.Insecure, - }, + ServerOption: opts.ClientScannerOpts(), ArtifactOption: artifact.Option{ DisabledAnalyzers: disabledAnalyzers(opts), + DisabledHandlers: disabledHandlers, FilePatterns: opts.FilePatterns, Parallel: opts.Parallel, Offline: opts.OfflineScan, @@ -592,6 +569,7 @@ func (r *runner) initScannerConfig(opts flag.Options) (ScannerConfig, types.Scan AWSRegion: opts.Region, AWSEndpoint: opts.Endpoint, FileChecksum: fileChecksum, + DetectionPriority: opts.DetectionPriority, // For image scanning ImageOption: ftypes.ImageOptions{ @@ -629,7 +607,7 @@ func (r *runner) initScannerConfig(opts flag.Options) (ScannerConfig, types.Scan } func (r *runner) scan(ctx context.Context, opts flag.Options, initializeScanner InitializeScanner) (types.Report, error) { - scannerConfig, scanOptions, err := r.initScannerConfig(opts) + scannerConfig, scanOptions, err := r.initScannerConfig(ctx, opts) if err != nil { return types.Report{}, err } @@ -645,3 +623,50 @@ func (r *runner) scan(ctx context.Context, opts flag.Options, initializeScanner } return report, nil } + +func initMisconfScannerOption(ctx context.Context, opts flag.Options) (misconf.ScannerOption, error) { + ctx = log.WithContextPrefix(ctx, log.PrefixMisconfiguration) + log.InfoContext(ctx, "Misconfiguration scanning is enabled") + + var downloadedPolicyPaths []string + var disableEmbedded bool + + downloadedPolicyPaths, err := operation.InitBuiltinChecks(ctx, opts.CacheDir, opts.Quiet, opts.SkipCheckUpdate, opts.MisconfOptions.ChecksBundleRepository, opts.RegistryOpts()) + if err != nil { + if !opts.SkipCheckUpdate { + log.ErrorContext(ctx, "Falling back to embedded checks", log.Err(err)) + } + } else { + log.DebugContext(ctx, "Checks successfully loaded from disk") + disableEmbedded = true + } + + configSchemas, err := misconf.LoadConfigSchemas(opts.ConfigFileSchemas) + if err != nil { + return misconf.ScannerOption{}, xerrors.Errorf("load schemas error: %w", err) + } + + return misconf.ScannerOption{ + Trace: opts.Trace, + Namespaces: append(opts.CheckNamespaces, rego.BuiltinNamespaces()...), + PolicyPaths: append(opts.CheckPaths, downloadedPolicyPaths...), + DataPaths: opts.DataPaths, + HelmValues: opts.HelmValues, + HelmValueFiles: opts.HelmValueFiles, + HelmFileValues: opts.HelmFileValues, + HelmStringValues: opts.HelmStringValues, + HelmAPIVersions: opts.HelmAPIVersions, + HelmKubeVersion: opts.HelmKubeVersion, + TerraformTFVars: opts.TerraformTFVars, + CloudFormationParamVars: opts.CloudFormationParamVars, + K8sVersion: opts.K8sVersion, + DisableEmbeddedPolicies: disableEmbedded, + DisableEmbeddedLibraries: disableEmbedded, + IncludeDeprecatedChecks: opts.IncludeDeprecatedChecks, + TfExcludeDownloaded: opts.TfExcludeDownloaded, + FilePatterns: opts.FilePatterns, + ConfigFileSchemas: configSchemas, + SkipFiles: opts.SkipFiles, + SkipDirs: opts.SkipDirs, + }, nil +} diff --git a/pkg/commands/operation/operation.go b/pkg/commands/operation/operation.go index 16aa72085949..ac52eee7fb1e 100644 --- a/pkg/commands/operation/operation.go +++ b/pkg/commands/operation/operation.go @@ -21,14 +21,14 @@ import ( var mu sync.Mutex // DownloadDB downloads the DB -func DownloadDB(ctx context.Context, appVersion, cacheDir string, dbRepository name.Reference, quiet, skipUpdate bool, +func DownloadDB(ctx context.Context, appVersion, cacheDir string, dbRepositories []name.Reference, quiet, skipUpdate bool, opt ftypes.RegistryOptions) error { mu.Lock() defer mu.Unlock() - ctx = log.WithContextPrefix(ctx, "db") + ctx = log.WithContextPrefix(ctx, log.PrefixVulnerabilityDB) dbDir := db.Dir(cacheDir) - client := db.NewClient(dbDir, quiet, db.WithDBRepository(dbRepository)) + client := db.NewClient(dbDir, quiet, db.WithDBRepository(dbRepositories)) needsUpdate, err := client.NeedsUpdate(ctx, appVersion, skipUpdate) if err != nil { return xerrors.Errorf("database error: %w", err) @@ -36,7 +36,6 @@ func DownloadDB(ctx context.Context, appVersion, cacheDir string, dbRepository n if needsUpdate { log.InfoContext(ctx, "Need to update DB") - log.InfoContext(ctx, "Downloading DB...", log.String("repository", dbRepository.String())) if err = client.Download(ctx, dbDir, opt); err != nil { return xerrors.Errorf("failed to download vulnerability DB: %w", err) } @@ -78,8 +77,8 @@ func DownloadVEXRepositories(ctx context.Context, opts flag.Options) error { } -// InitBuiltinPolicies downloads the built-in policies and loads them -func InitBuiltinPolicies(ctx context.Context, cacheDir string, quiet, skipUpdate bool, checkBundleRepository string, registryOpts ftypes.RegistryOptions) ([]string, error) { +// InitBuiltinChecks downloads the built-in policies and loads them +func InitBuiltinChecks(ctx context.Context, cacheDir string, quiet, skipUpdate bool, checkBundleRepository string, registryOpts ftypes.RegistryOptions) ([]string, error) { mu.Lock() defer mu.Unlock() @@ -97,14 +96,14 @@ func InitBuiltinPolicies(ctx context.Context, cacheDir string, quiet, skipUpdate } if needsUpdate { - log.Info("Need to update the built-in policies") - log.Info("Downloading the built-in policies...") - if err = client.DownloadBuiltinPolicies(ctx, registryOpts); err != nil { + log.InfoContext(ctx, "Need to update the built-in checks") + log.InfoContext(ctx, "Downloading the built-in checks...") + if err = client.DownloadBuiltinChecks(ctx, registryOpts); err != nil { return nil, xerrors.Errorf("failed to download built-in policies: %w", err) } } - policyPaths, err := client.LoadBuiltinPolicies() + policyPaths, err := client.LoadBuiltinChecks() if err != nil { if skipUpdate { msg := "No downloadable policies were loaded as --skip-check-update is enabled" diff --git a/pkg/commands/server/run.go b/pkg/commands/server/run.go index 19b24f990396..e9187b3442f5 100644 --- a/pkg/commands/server/run.go +++ b/pkg/commands/server/run.go @@ -26,7 +26,7 @@ func Run(ctx context.Context, opts flag.Options) (err error) { defer cleanup() // download the database file - if err = operation.DownloadDB(ctx, opts.AppVersion, opts.CacheDir, opts.DBRepository, + if err = operation.DownloadDB(ctx, opts.AppVersion, opts.CacheDir, opts.DBRepositories, true, opts.SkipDBUpdate, opts.RegistryOpts()); err != nil { return err } @@ -50,6 +50,6 @@ func Run(ctx context.Context, opts flag.Options) (err error) { m.Register() server := rpcServer.NewServer(opts.AppVersion, opts.Listen, opts.CacheDir, opts.Token, opts.TokenHeader, - opts.DBRepository, opts.RegistryOpts()) + opts.PathPrefix, opts.DBRepositories, opts.RegistryOpts()) return server.ListenAndServe(ctx, cacheClient, opts.SkipDBUpdate) } diff --git a/pkg/compliance/spec/compliance.go b/pkg/compliance/spec/compliance.go index 7b0b4f6cffdd..70355eaa926f 100644 --- a/pkg/compliance/spec/compliance.go +++ b/pkg/compliance/spec/compliance.go @@ -3,6 +3,7 @@ package spec import ( "fmt" "os" + "path/filepath" "strings" "github.com/samber/lo" @@ -11,6 +12,7 @@ import ( sp "github.com/aquasecurity/trivy-checks/pkg/spec" iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/types" ) @@ -70,18 +72,41 @@ func scannerByCheckID(checkID string) types.Scanner { } } +func checksDir(cacheDir string) string { + return filepath.Join(cacheDir, "policy") +} + +func complianceSpecDir(cacheDir string) string { + return filepath.Join(checksDir(cacheDir), "content", "specs", "compliance") +} + // GetComplianceSpec accepct compliance flag name/path and return builtin or file system loaded spec -func GetComplianceSpec(specNameOrPath string) (ComplianceSpec, error) { +func GetComplianceSpec(specNameOrPath, cacheDir string) (ComplianceSpec, error) { + if specNameOrPath == "" { + return ComplianceSpec{}, nil + } + var b []byte var err error - if strings.HasPrefix(specNameOrPath, "@") { + if strings.HasPrefix(specNameOrPath, "@") { // load user specified spec from disk b, err = os.ReadFile(strings.TrimPrefix(specNameOrPath, "@")) if err != nil { return ComplianceSpec{}, fmt.Errorf("error retrieving compliance spec from path: %w", err) } + log.Debug("Compliance spec loaded from specified path", log.String("path", specNameOrPath)) } else { - // TODO: GetSpecByName() should return []byte - b = []byte(sp.NewSpecLoader().GetSpecByName(specNameOrPath)) + _, err := os.Stat(filepath.Join(checksDir(cacheDir), "metadata.json")) + if err != nil { // cache corrupt or bundle does not exist, load embedded version + b = []byte(sp.NewSpecLoader().GetSpecByName(specNameOrPath)) + log.Debug("Compliance spec loaded from embedded library", log.String("spec", specNameOrPath)) + } else { + // load from bundle on disk + b, err = LoadFromBundle(cacheDir, specNameOrPath) + if err != nil { + return ComplianceSpec{}, err + } + log.Debug("Compliance spec loaded from disk bundle", log.String("spec", specNameOrPath)) + } } var complianceSpec ComplianceSpec @@ -91,3 +116,11 @@ func GetComplianceSpec(specNameOrPath string) (ComplianceSpec, error) { return complianceSpec, nil } + +func LoadFromBundle(cacheDir, specNameOrPath string) ([]byte, error) { + b, err := os.ReadFile(filepath.Join(complianceSpecDir(cacheDir), specNameOrPath+".yaml")) + if err != nil { + return nil, fmt.Errorf("error retrieving compliance spec from bundle %s: %w", specNameOrPath, err) + } + return b, nil +} diff --git a/pkg/compliance/spec/compliance_test.go b/pkg/compliance/spec/compliance_test.go index a4ee4961973e..0b44702b4159 100644 --- a/pkg/compliance/spec/compliance_test.go +++ b/pkg/compliance/spec/compliance_test.go @@ -1,10 +1,12 @@ package spec_test import ( + "path/filepath" "sort" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/compliance/spec" iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" @@ -239,3 +241,53 @@ func TestComplianceSpec_CheckIDs(t *testing.T) { }) } } + +func TestComplianceSpec_LoadFromDiskBundle(t *testing.T) { + + t.Run("load user specified spec from disk", func(t *testing.T) { + cs, err := spec.GetComplianceSpec(filepath.Join("@testdata", "testcache", "policy", "content", "specs", "compliance", "testspec.yaml"), filepath.Join("testdata", "testcache")) + require.NoError(t, err) + assert.Equal(t, spec.ComplianceSpec{Spec: iacTypes.Spec{ + ID: "test-spec-1.2", + Title: "Test Spec", + Description: "This is a test spec", + RelatedResources: []string{ + "https://www.google.ca", + }, + Version: "1.2", + Controls: []iacTypes.Control{ + { + Name: "moar-testing", + Description: "Test needs foo bar baz", + ID: "1.1", + Checks: []iacTypes.SpecCheck{ + {ID: "AVD-TEST-1234"}, + }, + Severity: "LOW", + }, + }, + }}, cs) + }) + + t.Run("load user specified spec from disk fails", func(t *testing.T) { + _, err := spec.GetComplianceSpec("@doesnotexist", "does-not-matter") + assert.Contains(t, err.Error(), "error retrieving compliance spec from path") + }) + + t.Run("bundle does not exist", func(t *testing.T) { + cs, err := spec.GetComplianceSpec("aws-cis-1.2", "does-not-matter") + require.NoError(t, err) + assert.Equal(t, "aws-cis-1.2", cs.Spec.ID) + }) + + t.Run("load spec from disk", func(t *testing.T) { + cs, err := spec.GetComplianceSpec("testspec", filepath.Join("testdata", "testcache")) + require.NoError(t, err) + assert.Equal(t, "test-spec-1.2", cs.Spec.ID) + }) + + t.Run("load spec yaml unmarshal failure", func(t *testing.T) { + _, err := spec.GetComplianceSpec("invalid", filepath.Join("testdata", "testcache")) + assert.Contains(t, err.Error(), "spec yaml decode error") + }) +} diff --git a/pkg/compliance/spec/testdata/testcache/policy/content/specs/compliance/invalid.yaml b/pkg/compliance/spec/testdata/testcache/policy/content/specs/compliance/invalid.yaml new file mode 100644 index 000000000000..29dcba2e15af --- /dev/null +++ b/pkg/compliance/spec/testdata/testcache/policy/content/specs/compliance/invalid.yaml @@ -0,0 +1 @@ +this is not yaml but easier to read \ No newline at end of file diff --git a/pkg/compliance/spec/testdata/testcache/policy/content/specs/compliance/testspec.yaml b/pkg/compliance/spec/testdata/testcache/policy/content/specs/compliance/testspec.yaml new file mode 100644 index 000000000000..ec1f75f52970 --- /dev/null +++ b/pkg/compliance/spec/testdata/testcache/policy/content/specs/compliance/testspec.yaml @@ -0,0 +1,15 @@ +spec: + id: test-spec-1.2 + title: Test Spec + description: This is a test spec + version: "1.2" + relatedResources: + - https://www.google.ca + controls: + - id: "1.1" + name: moar-testing + description: |- + Test needs foo bar baz + checks: + - id: AVD-TEST-1234 + severity: LOW \ No newline at end of file diff --git a/pkg/compliance/spec/testdata/testcache/policy/metadata.json b/pkg/compliance/spec/testdata/testcache/policy/metadata.json new file mode 100644 index 000000000000..ba37beda3850 --- /dev/null +++ b/pkg/compliance/spec/testdata/testcache/policy/metadata.json @@ -0,0 +1 @@ +{"Digest":"sha256:ef2d9ad4fce0f933b20a662004d7e55bf200987c180e7f2cd531af631f408bb3","DownloadedAt":"2024-08-07T20:07:48.917915-06:00"} \ No newline at end of file diff --git a/pkg/db/db.go b/pkg/db/db.go index e4af60d092c6..70fbb93a5a91 100644 --- a/pkg/db/db.go +++ b/pkg/db/db.go @@ -2,14 +2,13 @@ package db import ( "context" - "errors" "fmt" "os" "path/filepath" "time" "github.com/google/go-containerregistry/pkg/name" - "github.com/google/go-containerregistry/pkg/v1/remote/transport" + "github.com/samber/lo" "golang.org/x/xerrors" "github.com/aquasecurity/trivy-db/pkg/db" @@ -18,7 +17,6 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/oci" - "github.com/aquasecurity/trivy/pkg/version/doc" ) const ( @@ -27,8 +25,9 @@ const ( ) var ( - DefaultRepository = fmt.Sprintf("%s:%d", "ghcr.io/aquasecurity/trivy-db", db.SchemaVersion) - defaultRepository, _ = name.NewTag(DefaultRepository) + // GitHub Container Registry + DefaultGHCRRepository = fmt.Sprintf("%s:%d", "ghcr.io/aquasecurity/trivy-db", db.SchemaVersion) + defaultGHCRRepository = lo.Must(name.NewTag(DefaultGHCRRepository)) Init = db.Init Close = db.Close @@ -36,8 +35,8 @@ var ( ) type options struct { - artifact *oci.Artifact - dbRepository name.Reference + artifact *oci.Artifact + dbRepositories []name.Reference } // Option is a functional option @@ -51,9 +50,9 @@ func WithOCIArtifact(art *oci.Artifact) Option { } // WithDBRepository takes a dbRepository -func WithDBRepository(dbRepository name.Reference) Option { +func WithDBRepository(dbRepository []name.Reference) Option { return func(opts *options) { - opts.dbRepository = dbRepository + opts.dbRepositories = dbRepository } } @@ -73,7 +72,9 @@ func Dir(cacheDir string) string { // NewClient is the factory method for DB client func NewClient(dbDir string, quiet bool, opts ...Option) *Client { o := &options{ - dbRepository: defaultRepository, + dbRepositories: []name.Reference{ + defaultGHCRRepository, + }, } for _, opt := range opts { @@ -92,22 +93,22 @@ func NewClient(dbDir string, quiet bool, opts ...Option) *Client { func (c *Client) NeedsUpdate(ctx context.Context, cliVersion string, skip bool) (bool, error) { meta, err := c.metadata.Get() if err != nil { - log.Debug("There is no valid metadata file", log.Err(err)) + log.DebugContext(ctx, "There is no valid metadata file", log.Err(err)) if skip { - log.Error("The first run cannot skip downloading DB") + log.ErrorContext(ctx, "The first run cannot skip downloading DB") return false, xerrors.New("--skip-update cannot be specified on the first run") } meta = metadata.Metadata{Version: db.SchemaVersion} } if db.SchemaVersion < meta.Version { - log.Error("The Trivy version is old. Update to the latest version.", log.String("version", cliVersion)) + log.ErrorContext(ctx, "Trivy version is old. Update to the latest version.", log.String("version", cliVersion)) return false, xerrors.Errorf("the version of DB schema doesn't match. Local DB: %d, Expected: %d", meta.Version, db.SchemaVersion) } if skip { - log.Debug("Skipping DB update...") + log.DebugContext(ctx, "Skipping DB update...") if err = c.validate(meta); err != nil { return false, xerrors.Errorf("validate error: %w", err) } @@ -115,7 +116,7 @@ func (c *Client) NeedsUpdate(ctx context.Context, cliVersion string, skip bool) } if db.SchemaVersion != meta.Version { - log.Debug("The local DB schema version does not match with supported version schema.", + log.DebugContext(ctx, "The local DB schema version does not match with supported version schema.", log.Int("local_version", meta.Version), log.Int("supported_version", db.SchemaVersion)) return true, nil } @@ -150,19 +151,14 @@ func (c *Client) isNewDB(ctx context.Context, meta metadata.Metadata) bool { 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.Debug("No metadata file") + log.DebugContext(ctx, "No metadata file") } - art, err := c.initOCIArtifact(opt) - if err != nil { + if err := c.downloadDB(ctx, opt, dst); err != nil { return xerrors.Errorf("OCI artifact error: %w", err) } - if err = art.Download(ctx, dst, oci.DownloadOption{MediaType: dbMediaType}); err != nil { - return xerrors.Errorf("database download error: %w", err) - } - - if err = c.updateDownloadedAt(ctx, dst); err != nil { + if err := c.updateDownloadedAt(ctx, dst); err != nil { return xerrors.Errorf("failed to update downloaded_at: %w", err) } return nil @@ -194,27 +190,23 @@ func (c *Client) updateDownloadedAt(ctx context.Context, dbDir string) error { return nil } -func (c *Client) initOCIArtifact(opt types.RegistryOptions) (*oci.Artifact, error) { +func (c *Client) initArtifacts(opt types.RegistryOptions) oci.Artifacts { if c.artifact != nil { - return c.artifact, nil + return oci.Artifacts{c.artifact} } + return oci.NewArtifacts(c.dbRepositories, opt) +} - art, err := oci.NewArtifact(c.dbRepository.String(), c.quiet, opt) - if err != nil { - var terr *transport.Error - if errors.As(err, &terr) { - for _, diagnostic := range terr.Errors { - // For better user experience - if diagnostic.Code == transport.DeniedErrorCode || diagnostic.Code == transport.UnauthorizedErrorCode { - // e.g. https://aquasecurity.github.io/trivy/latest/docs/references/troubleshooting/#db - log.Warnf("See %s", doc.URL("/docs/references/troubleshooting/", "db")) - break - } - } - } - return nil, xerrors.Errorf("OCI artifact error: %w", err) +func (c *Client) downloadDB(ctx context.Context, opt types.RegistryOptions, dst string) error { + log.InfoContext(ctx, "Downloading vulnerability DB...") + downloadOpt := oci.DownloadOption{ + MediaType: dbMediaType, + Quiet: c.quiet, } - return art, nil + if err := c.initArtifacts(opt).Download(ctx, dst, downloadOpt); err != nil { + return xerrors.Errorf("failed to download vulnerability DB: %w", err) + } + return nil } func (c *Client) ShowInfo() error { diff --git a/pkg/db/db_test.go b/pkg/db/db_test.go index ff5e36e2b194..f64c0dc4a5c6 100644 --- a/pkg/db/db_test.go +++ b/pkg/db/db_test.go @@ -159,7 +159,7 @@ func TestClient_Download(t *testing.T) { { name: "invalid gzip", input: "testdata/trivy.db", - wantErr: "unexpected EOF", + wantErr: "OCI artifact error: failed to download vulnerability DB", }, } diff --git a/pkg/dependency/parser/dart/pub/parse.go b/pkg/dependency/parser/dart/pub/parse.go index 1bd85a56f1d6..f17fedf0a2b0 100644 --- a/pkg/dependency/parser/dart/pub/parse.go +++ b/pkg/dependency/parser/dart/pub/parse.go @@ -19,12 +19,14 @@ const ( // Parser is a parser for pubspec.lock type Parser struct { - logger *log.Logger + logger *log.Logger + useMinVersion bool } -func NewParser() *Parser { +func NewParser(useMinVersion bool) *Parser { return &Parser{ - logger: log.WithPrefix("pub"), + logger: log.WithPrefix("pub"), + useMinVersion: useMinVersion, } } @@ -50,7 +52,7 @@ func (p Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency var pkgs []ftypes.Package for name, dep := range l.Packages { version := dep.Version - if version == "0.0.0" && dep.Source == "sdk" { + if version == "0.0.0" && dep.Source == "sdk" && p.useMinVersion { version = p.findSDKVersion(l, name, dep) } @@ -71,27 +73,27 @@ func (p Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency return pkgs, nil, nil } -// findSDKVersion detects the first version of the SDK constraint specified in the Description. +// findSDKVersion detects the minimum version of the SDK constraint specified in the Description. // If the constraint is not found, it returns the original version. func (p Parser) findSDKVersion(l *lock, name string, dep Dep) string { // Some dependencies use one of the SDK versions. // In this case dep.Version == `0.0.0`. // We can't get versions for these dependencies. - // Therefore, we use the first version of the SDK constraint specified in the Description. + // Therefore, we use the minimum version of the SDK constraint specified in the Description. // See https://github.com/aquasecurity/trivy/issues/6017 constraint, ok := l.Sdks[string(dep.Description)] if !ok { return dep.Version } - v, err := firstVersionOfConstrain(constraint) + v, err := minVersionOfConstrain(constraint) if err != nil { p.logger.Warn("Unable to get sdk version from constraint", log.Err(err)) return dep.Version } else if v == "" { return dep.Version } - p.logger.Info("Using the first version of the constraint from the sdk source", log.String("dep", name), + p.logger.Info("Using the minimum version of the constraint from the sdk source", log.String("dep", name), log.String("constraint", constraint)) return v } @@ -106,8 +108,8 @@ func (p Parser) relationship(dep string) ftypes.Relationship { return ftypes.RelationshipUnknown } -// firstVersionOfConstrain returns the first acceptable version for constraint -func firstVersionOfConstrain(constraint string) (string, error) { +// minVersionOfConstrain returns the minimum acceptable version for constraint +func minVersionOfConstrain(constraint string) (string, error) { css, err := goversion.NewConstraints(constraint) if err != nil { return "", xerrors.Errorf("unable to parse constraints: %w", err) @@ -119,7 +121,7 @@ func firstVersionOfConstrain(constraint string) (string, error) { if len(constraints) == 0 || len(constraints[0]) == 0 { return "", nil } - // We only need to get the first version from the range + // We only need to get the minimum version from the range if constraints[0][0].Operator() != ">=" && constraints[0][0].Operator() != "^" { return "", nil } diff --git a/pkg/dependency/parser/dart/pub/parse_test.go b/pkg/dependency/parser/dart/pub/parse_test.go index 5f4591201b7c..e884adf28306 100644 --- a/pkg/dependency/parser/dart/pub/parse_test.go +++ b/pkg/dependency/parser/dart/pub/parse_test.go @@ -15,14 +15,42 @@ import ( func TestParser_Parse(t *testing.T) { tests := []struct { - name string - inputFile string - want []ftypes.Package - wantErr assert.ErrorAssertionFunc + name string + useMinVersion bool + inputFile string + want []ftypes.Package + wantErr assert.ErrorAssertionFunc }{ { - name: "happy path", - inputFile: "testdata/happy.lock", + name: "not use minimum version", + useMinVersion: false, + inputFile: "testdata/happy.lock", + want: []ftypes.Package{ + { + ID: "crypto@3.0.2", + Name: "crypto", + Version: "3.0.2", + Relationship: ftypes.RelationshipDirect, + }, + { + ID: "flutter_test@0.0.0", + Name: "flutter_test", + Version: "0.0.0", + Relationship: ftypes.RelationshipDirect, + }, + { + ID: "uuid@3.0.6", + Name: "uuid", + Version: "3.0.6", + Relationship: ftypes.RelationshipIndirect, + }, + }, + wantErr: assert.NoError, + }, + { + name: "use minimum version", + useMinVersion: true, + inputFile: "testdata/happy.lock", want: []ftypes.Package{ { ID: "crypto@3.0.2", @@ -63,7 +91,7 @@ func TestParser_Parse(t *testing.T) { require.NoError(t, err) defer f.Close() - gotPkgs, _, err := pub.NewParser().Parse(f) + gotPkgs, _, err := pub.NewParser(tt.useMinVersion).Parse(f) if !tt.wantErr(t, err, fmt.Sprintf("Parse(%v)", tt.inputFile)) { return } diff --git a/pkg/dependency/parser/golang/mod/parse.go b/pkg/dependency/parser/golang/mod/parse.go index 508da6911521..bbf42926a766 100644 --- a/pkg/dependency/parser/golang/mod/parse.go +++ b/pkg/dependency/parser/golang/mod/parse.go @@ -29,12 +29,14 @@ var ( ) type Parser struct { - replace bool // 'replace' represents if the 'replace' directive should be taken into account. + replace bool // 'replace' represents if the 'replace' directive should be taken into account. + useMinVersion bool } -func NewParser(replace bool) *Parser { +func NewParser(replace, useMinVersion bool) *Parser { return &Parser{ - replace: replace, + replace: replace, + useMinVersion: useMinVersion, } } @@ -80,7 +82,20 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc skipIndirect := true if modFileParsed.Go != nil { // Old go.mod file may not include the go version. Go version for these files is less than 1.17 - skipIndirect = lessThan117(modFileParsed.Go.Version) + skipIndirect = lessThan(modFileParsed.Go.Version, 1, 17) + } + + // Use minimal required go version from `toolchain` line (or from `go` line if `toolchain` is omitted) as `stdlib`. + // Show `stdlib` only with `useMinVersion` flag. + if p.useMinVersion { + if toolchainVer := toolchainVersion(modFileParsed.Toolchain, modFileParsed.Go); toolchainVer != "" { + pkgs["stdlib"] = ftypes.Package{ + ID: packageID("stdlib", toolchainVer), + Name: "stdlib", + Version: toolchainVer, + Relationship: ftypes.RelationshipDirect, // Considered a direct dependency as the main module depends on the standard packages. + } + } } // Main module @@ -150,8 +165,12 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc return lo.Values(pkgs), nil, nil } -// Check if the Go version is less than 1.17 -func lessThan117(ver string) bool { +// lessThan checks if the Go version is less than `.` +func lessThan(ver string, majorVer, minorVer int) bool { + if ver == "" { + return false + } + ss := strings.Split(ver, ".") if len(ss) != 2 { return false @@ -165,7 +184,55 @@ func lessThan117(ver string) bool { return false } - return major <= 1 && minor < 17 + return major <= majorVer && minor < minorVer +} + +// toolchainVersion returns version from `toolchain`. +// If `toolchain` is omitted - return version from `go` line (if it is version in toolchain format) +// cf. https://go.dev/doc/toolchain +func toolchainVersion(toolchain *modfile.Toolchain, goVer *modfile.Go) string { + if toolchain != nil && toolchain.Name != "" { + // cf. https://go.dev/doc/toolchain#name + // `dropping the initial go and discarding off any suffix beginning with -` + // e.g. `go1.22.5-custom` => `1.22.5` + name, _, _ := strings.Cut(toolchain.Name, "-") + return strings.TrimPrefix(name, "go") + } + + if goVer != nil { + return toolchainVersionFromGoLine(goVer.Version) + } + return "" +} + +// toolchainVersionFromGoLine detects Go version from `go` line if `toolchain` line is omitted. +// `go` line supports the following formats: +// cf. https://go.dev/doc/toolchain#version +// - `1.N.P`. e.g. `1.22.0` +// - `1.N`. e.g. `1.22` +// - `1.NrcR`. e.g. `1.22rc1` +// - `1.NbetaR`. e.g. `1.18beta1` - only for Go 1.20 or earlier +func toolchainVersionFromGoLine(ver string) string { + var majorMinorVer string + + if ss := strings.Split(ver, "."); len(ss) > 2 { // `1.N.P` + majorMinorVer = strings.Join(ss[:2], ".") + } else if v, _, rcFound := strings.Cut(ver, "rc"); rcFound { // `1.NrcR` + majorMinorVer = v + } else { // `1.N` + majorMinorVer = ver + // Add `.0` suffix to avoid user confusing. + // See https://github.com/aquasecurity/trivy/pull/7163#discussion_r1682424315 + ver = v + ".0" + } + + // `toolchain` has been added in go 1.21. + // So we need to check that Go version is 1.21 or higher. + // cf. https://github.com/aquasecurity/trivy/pull/7163#discussion_r1682424315 + if lessThan(majorMinorVer, 1, 21) { + return "" + } + return ver } func packageID(name, version string) string { diff --git a/pkg/dependency/parser/golang/mod/parse_test.go b/pkg/dependency/parser/golang/mod/parse_test.go index 598cb3fe70f3..10bda7f01144 100644 --- a/pkg/dependency/parser/golang/mod/parse_test.go +++ b/pkg/dependency/parser/golang/mod/parse_test.go @@ -7,22 +7,31 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/mod/modfile" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestParse(t *testing.T) { tests := []struct { - name string - file string - replace bool - want []ftypes.Package + name string + file string + replace bool + useMinVersion bool + want []ftypes.Package }{ + { + name: "normal with stdlib", + file: "testdata/normal/go.mod", + replace: true, + useMinVersion: true, + want: GoModNormal, + }, { name: "normal", file: "testdata/normal/go.mod", replace: true, - want: GoModNormal, + want: GoModNormalWithoutStdlib, }, { name: "without go version", @@ -85,7 +94,7 @@ func TestParse(t *testing.T) { f, err := os.Open(tt.file) require.NoError(t, err) - got, _, err := NewParser(tt.replace).Parse(f) + got, _, err := NewParser(tt.replace, tt.useMinVersion).Parse(f) require.NoError(t, err) sort.Sort(ftypes.Packages(got)) @@ -95,3 +104,90 @@ func TestParse(t *testing.T) { }) } } + +func TestToolchainVersion(t *testing.T) { + tests := []struct { + name string + modFile modfile.File + want string + }{ + { + name: "version from toolchain line", + modFile: modfile.File{ + Toolchain: &modfile.Toolchain{ + Name: "1.21.1", + }, + }, + want: "1.21.1", + }, + { + name: "version from toolchain line with suffix", + modFile: modfile.File{ + Toolchain: &modfile.Toolchain{ + Name: "1.21.1-custom", + }, + }, + want: "1.21.1", + }, + { + name: "'1.18rc1' from go line", + modFile: modfile.File{ + Go: &modfile.Go{ + Version: "1.18rc1", + }, + }, + want: "", + }, + { + name: "'1.18.1' from go line", + modFile: modfile.File{ + Go: &modfile.Go{ + Version: "1.18.1", + }, + }, + want: "", + }, + { + name: "'1.20' from go line", + modFile: modfile.File{ + Go: &modfile.Go{ + Version: "1.20", + }, + }, + want: "", + }, + { + name: "'1.21' from go line", + modFile: modfile.File{ + Go: &modfile.Go{ + Version: "1.21", + }, + }, + want: "1.21.0", + }, + { + name: "'1.21rc1' from go line", + modFile: modfile.File{ + Go: &modfile.Go{ + Version: "1.21rc1", + }, + }, + want: "1.21rc1", + }, + { + name: "'1.21.2' from go line", + modFile: modfile.File{ + Go: &modfile.Go{ + Version: "1.21.2", + }, + }, + want: "1.21.2", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.want, toolchainVersion(tt.modFile.Toolchain, tt.modFile.Go)) + }) + } +} diff --git a/pkg/dependency/parser/golang/mod/parse_testcase.go b/pkg/dependency/parser/golang/mod/parse_testcase.go index 5a07b939c549..4671ef3e6854 100644 --- a/pkg/dependency/parser/golang/mod/parse_testcase.go +++ b/pkg/dependency/parser/golang/mod/parse_testcase.go @@ -1,6 +1,10 @@ package mod -import ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" +import ( + "slices" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" +) var ( // execute go mod tidy in normal folder @@ -17,37 +21,71 @@ var ( }, }, { - ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", - Name: "github.com/aquasecurity/go-dep-parser", - Version: "0.0.0-20211224170007-df43bca6b6ff", + ID: "stdlib@v1.22.5", + Name: "stdlib", + Version: "1.22.5", + Relationship: ftypes.RelationshipDirect, + }, + { + ID: "github.com/aquasecurity/go-version@v0.0.0-20240603093900-cf8a8d29271d", + Name: "github.com/aquasecurity/go-version", + Version: "0.0.0-20240603093900-cf8a8d29271d", Relationship: ftypes.RelationshipDirect, ExternalReferences: []ftypes.ExternalRef{ { Type: ftypes.RefVCS, - URL: "https://github.com/aquasecurity/go-dep-parser", + URL: "https://github.com/aquasecurity/go-version", }, }, }, { - ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", - Name: "golang.org/x/xerrors", - Version: "0.0.0-20200804184101-5ec99f83aff1", + ID: "github.com/davecgh/go-spew@v1.1.2-0.20180830191138-d8f796af33cc", + Name: "github.com/davecgh/go-spew", + Version: "1.1.2-0.20180830191138-d8f796af33cc", + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ + { + Type: ftypes.RefVCS, + URL: "https://github.com/davecgh/go-spew", + }, + }, + }, + { + ID: "github.com/pmezard/go-difflib@v1.0.1-0.20181226105442-5d4384ee4fb2", + Name: "github.com/pmezard/go-difflib", + Version: "1.0.1-0.20181226105442-5d4384ee4fb2", Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ + { + Type: ftypes.RefVCS, + URL: "https://github.com/pmezard/go-difflib", + }, + }, }, { - ID: "gopkg.in/yaml.v3@v3.0.0-20210107192922-496545a6307b", - Name: "gopkg.in/yaml.v3", - Version: "3.0.0-20210107192922-496545a6307b", + ID: "github.com/stretchr/testify@v1.9.0", + Name: "github.com/stretchr/testify", + Version: "1.9.0", Relationship: ftypes.RelationshipIndirect, ExternalReferences: []ftypes.ExternalRef{ { Type: ftypes.RefVCS, - URL: "https://github.com/go-yaml/yaml", + URL: "https://github.com/stretchr/testify", }, }, }, + { + ID: "golang.org/x/xerrors@v0.0.0-20231012003039-104605ab7028", + Name: "golang.org/x/xerrors", + Version: "0.0.0-20231012003039-104605ab7028", + Relationship: ftypes.RelationshipIndirect, + }, } + GoModNormalWithoutStdlib = slices.DeleteFunc(slices.Clone(GoModNormal), func(f ftypes.Package) bool { + return f.Name == "stdlib" + }) + // execute go mod tidy in replaced folder GoModReplaced = []ftypes.Package{ { diff --git a/pkg/dependency/parser/golang/mod/testdata/normal/go.mod b/pkg/dependency/parser/golang/mod/testdata/normal/go.mod index 9d48b25e0079..564414c10583 100644 --- a/pkg/dependency/parser/golang/mod/testdata/normal/go.mod +++ b/pkg/dependency/parser/golang/mod/testdata/normal/go.mod @@ -1,10 +1,14 @@ module github.com/org/repo -go 1.17 +go 1.22.0 -require github.com/aquasecurity/go-dep-parser v0.0.0-20211224170007-df43bca6b6ff +toolchain go1.22.5 + +require github.com/aquasecurity/go-version v0.0.0-20240603093900-cf8a8d29271d require ( - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/stretchr/testify v1.9.0 // indirect + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect ) diff --git a/pkg/dependency/parser/golang/mod/testdata/normal/go.sum b/pkg/dependency/parser/golang/mod/testdata/normal/go.sum index 032bb4727cfe..eefc0690f47d 100644 --- a/pkg/dependency/parser/golang/mod/testdata/normal/go.sum +++ b/pkg/dependency/parser/golang/mod/testdata/normal/go.sum @@ -1,62 +1,12 @@ -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/aquasecurity/go-dep-parser v0.0.0-20211224170007-df43bca6b6ff h1:JCKEV3TgUNh9fn+8hXyIdsF9yErA0rUbCkgt2flRKt4= -github.com/aquasecurity/go-dep-parser v0.0.0-20211224170007-df43bca6b6ff/go.mod h1:8fJ//Ob6/03lxbn4xa1F+G/giVtiVLxnZNpBp5xOxNk= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -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= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +github.com/aquasecurity/go-version v0.0.0-20240603093900-cf8a8d29271d h1:4zour5Sh9chOg+IqIinIcJ3qtr3cIf8FdFY6aArlXBw= +github.com/aquasecurity/go-version v0.0.0-20240603093900-cf8a8d29271d/go.mod h1:1cPOp4BaQZ1G2F5fnw4dFz6pkOyXJI9KTuak8ghIl3U= +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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/dependency/parser/golang/mod/testdata/normal/main.go b/pkg/dependency/parser/golang/mod/testdata/normal/main.go index 0b51fb861f32..fe31e68d5da8 100644 --- a/pkg/dependency/parser/golang/mod/testdata/normal/main.go +++ b/pkg/dependency/parser/golang/mod/testdata/normal/main.go @@ -3,11 +3,11 @@ package main import ( "log" - "github.com/aquasecurity/trivy/pkg/dependency/parser/golang/mod" + "github.com/aquasecurity/go-version/pkg/version" ) func main() { - if _, err := mod.Parse(nil); err != nil { + if _, err := version.Parse("v0.1.2"); err != nil { log.Fatal(err) } } diff --git a/pkg/dependency/parser/java/pom/artifact.go b/pkg/dependency/parser/java/pom/artifact.go index b2e97efb229b..73ebe6b21407 100644 --- a/pkg/dependency/parser/java/pom/artifact.go +++ b/pkg/dependency/parser/java/pom/artifact.go @@ -6,15 +6,22 @@ import ( "regexp" "slices" "strings" + "sync" "github.com/samber/lo" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/version/doc" ) var ( - varRegexp = regexp.MustCompile(`\${(\S+?)}`) + varRegexp = regexp.MustCompile(`\${(\S+?)}`) + emptyVersionWarn = sync.OnceFunc(func() { + log.WithPrefix("pom").Warn("Dependency version cannot be determined. Child dependencies will not be found.", + // e.g. https://aquasecurity.github.io/trivy/latest/docs/coverage/language/java/#empty-dependency-version + log.String("details", doc.URL("/docs/coverage/language/java/", "empty-dependency-version"))) + }) ) type artifact struct { @@ -42,7 +49,17 @@ func newArtifact(groupID, artifactID, version string, licenses []string, props m } func (a artifact) IsEmpty() bool { - return a.GroupID == "" || a.ArtifactID == "" || a.Version.String() == "" + if a.GroupID == "" || a.ArtifactID == "" { + return true + } + if a.Version.String() == "" { + emptyVersionWarn() + log.WithPrefix("pom").Debug("Dependency version cannot be determined.", + log.String("GroupID", a.GroupID), + log.String("ArtifactID", a.ArtifactID), + ) + } + return false } func (a artifact) Equal(o artifact) bool { diff --git a/pkg/dependency/parser/java/pom/parse.go b/pkg/dependency/parser/java/pom/parse.go index 550c5761c6ee..542abcac3c8a 100644 --- a/pkg/dependency/parser/java/pom/parse.go +++ b/pkg/dependency/parser/java/pom/parse.go @@ -13,7 +13,7 @@ import ( "sort" "strings" - multierror "github.com/hashicorp/go-multierror" + "github.com/hashicorp/go-multierror" "github.com/samber/lo" "golang.org/x/net/html/charset" "golang.org/x/xerrors" @@ -292,6 +292,14 @@ func (p *Parser) resolve(art artifact, rootDepManagement []pomDependency) (analy return *result, nil } + // We can't resolve a dependency without a version. + // So let's just keep this dependency. + if art.Version.String() == "" { + return analysisResult{ + artifact: art, + }, nil + } + p.logger.Debug("Resolving...", log.String("group_id", art.GroupID), log.String("artifact_id", art.ArtifactID), log.String("version", art.Version.String())) pomContent, err := p.tryRepository(art.GroupID, art.ArtifactID, art.Version.String()) @@ -335,8 +343,20 @@ func (p *Parser) analyze(pom *pom, opts analysisOptions) (analysisResult, error) p.releaseRemoteRepos = lo.Uniq(append(pomReleaseRemoteRepos, p.releaseRemoteRepos...)) p.snapshotRemoteRepos = lo.Uniq(append(pomSnapshotRemoteRepos, p.snapshotRemoteRepos...)) + // We need to forward dependencyManagements from current and root pom to Parent, + // to use them for dependencies in parent. + // For better understanding see the following tests: + // - `dependency from parent uses version from child pom depManagement` + // - `dependency from parent uses version from root pom depManagement` + // + // depManagements from root pom has higher priority than depManagements from current pom. + depManagementForParent := lo.UniqBy(append(opts.depManagement, pom.content.DependencyManagement.Dependencies.Dependency...), + func(dep pomDependency) string { + return dep.Name() + }) + // Parent - parent, err := p.parseParent(pom.filePath, pom.content.Parent) + parent, err := p.parseParent(pom.filePath, pom.content.Parent, depManagementForParent) if err != nil { return analysisResult{}, xerrors.Errorf("parent error: %w", err) } @@ -477,7 +497,7 @@ func excludeDep(exclusions map[string]struct{}, art artifact) bool { return false } -func (p *Parser) parseParent(currentPath string, parent pomParent) (analysisResult, error) { +func (p *Parser) parseParent(currentPath string, parent pomParent, rootDepManagement []pomDependency) (analysisResult, error) { // Pass nil properties so that variables in are not evaluated. target := newArtifact(parent.GroupId, parent.ArtifactId, parent.Version, nil, nil) // if version is property (e.g. ${revision}) - we still need to parse this pom @@ -499,7 +519,9 @@ func (p *Parser) parseParent(currentPath string, parent pomParent) (analysisResu logger.Debug("Parent POM not found", log.Err(err)) } - result, err := p.analyze(parentPOM, analysisOptions{}) + result, err := p.analyze(parentPOM, analysisOptions{ + depManagement: rootDepManagement, + }) if err != nil { return analysisResult{}, xerrors.Errorf("analyze error: %w", err) } @@ -680,18 +702,15 @@ func (p *Parser) fetchPOMFromRemoteRepositories(paths []string, snapshot bool) ( func (p *Parser) remoteRepoRequest(repo string, paths []string) (*http.Request, error) { repoURL, err := url.Parse(repo) if err != nil { - p.logger.Error("URL parse error", log.String("repo", repo)) - return nil, nil + return nil, xerrors.Errorf("unable to parse URL: %w", err) } paths = append([]string{repoURL.Path}, paths...) repoURL.Path = path.Join(paths...) - logger := p.logger.With(log.String("host", repoURL.Host), log.String("path", repoURL.Path)) req, err := http.NewRequest("GET", repoURL.String(), http.NoBody) if err != nil { - logger.Debug("HTTP request failed") - return nil, nil + return nil, xerrors.Errorf("unable to create HTTP request: %w", err) } if repoURL.User != nil { password, _ := repoURL.User.Password() @@ -709,13 +728,17 @@ func (p *Parser) fetchPomFileNameFromMavenMetadata(repo string, paths []string) req, err := p.remoteRepoRequest(repo, mavenMetadataPaths) if err != nil { - return "", xerrors.Errorf("unable to create request for maven-metadata.xml file") + p.logger.Debug("Unable to create request", log.String("repo", repo), log.Err(err)) + return "", nil } client := &http.Client{} resp, err := client.Do(req) - if err != nil || resp.StatusCode != http.StatusOK { - p.logger.Debug("Failed to fetch", log.String("url", req.URL.String())) + if err != nil { + p.logger.Debug("Failed to fetch", log.String("url", req.URL.String()), log.Err(err)) + return "", nil + } else if resp.StatusCode != http.StatusOK { + p.logger.Debug("Failed to fetch", log.String("url", req.URL.String()), log.Int("statusCode", resp.StatusCode)) return "", nil } defer resp.Body.Close() @@ -739,13 +762,17 @@ func (p *Parser) fetchPomFileNameFromMavenMetadata(repo string, paths []string) func (p *Parser) fetchPOMFromRemoteRepository(repo string, paths []string) (*pom, error) { req, err := p.remoteRepoRequest(repo, paths) if err != nil { - return nil, xerrors.Errorf("unable to create request for pom file") + p.logger.Debug("Unable to create request", log.String("repo", repo), log.Err(err)) + return nil, nil } client := &http.Client{} resp, err := client.Do(req) - if err != nil || resp.StatusCode != http.StatusOK { - p.logger.Debug("Failed to fetch", log.String("url", req.URL.String())) + if err != nil { + p.logger.Debug("Failed to fetch", log.String("url", req.URL.String()), log.Err(err)) + return nil, nil + } else if resp.StatusCode != http.StatusOK { + p.logger.Debug("Failed to fetch", log.String("url", req.URL.String()), log.Int("statusCode", resp.StatusCode)) return nil, nil } defer resp.Body.Close() diff --git a/pkg/dependency/parser/java/pom/parse_test.go b/pkg/dependency/parser/java/pom/parse_test.go index 934085d5d536..82604846a4f3 100644 --- a/pkg/dependency/parser/java/pom/parse_test.go +++ b/pkg/dependency/parser/java/pom/parse_test.go @@ -809,6 +809,17 @@ func TestPom_Parse(t *testing.T) { Licenses: []string{"Apache 2.0"}, Relationship: ftypes.RelationshipRoot, }, + { + ID: "org.example:example-api", + Name: "org.example:example-api", + Relationship: ftypes.RelationshipDirect, + Locations: []ftypes.Location{ + { + StartLine: 28, + EndLine: 32, + }, + }, + }, }, }, { @@ -1499,6 +1510,126 @@ func TestPom_Parse(t *testing.T) { }, }, }, + { + name: "dependency without version", + inputFile: filepath.Join("testdata", "dep-without-version", "pom.xml"), + local: true, + want: []ftypes.Package{ + { + ID: "com.example:dep-without-version:1.0.0", + Name: "com.example:dep-without-version", + Version: "1.0.0", + Relationship: ftypes.RelationshipRoot, + }, + { + ID: "org.example:example-api", + Name: "org.example:example-api", + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ + { + StartLine: 19, + EndLine: 22, + }, + }, + }, + }, + }, + // [INFO] com.example:root-depManagement-in-parent:jar:1.0.0 + // [INFO] \- org.example:example-dependency:jar:2.0.0:compile + // [INFO] \- org.example:example-api:jar:1.0.1:compile + { + name: "dependency from parent uses version from root pom depManagement", + inputFile: filepath.Join("testdata", "use-root-dep-management-in-parent", "pom.xml"), + local: true, + want: []ftypes.Package{ + { + ID: "com.example:root-depManagement-in-parent:1.0.0", + Name: "com.example:root-depManagement-in-parent", + Version: "1.0.0", + Relationship: ftypes.RelationshipRoot, + }, + { + ID: "org.example:example-dependency:2.0.0", + Name: "org.example:example-dependency", + Version: "2.0.0", + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ + { + StartLine: 25, + EndLine: 29, + }, + }, + }, + { + ID: "org.example:example-api:1.0.1", + Name: "org.example:example-api", + Version: "1.0.1", + Relationship: ftypes.RelationshipIndirect, + }, + }, + wantDeps: []ftypes.Dependency{ + { + ID: "com.example:root-depManagement-in-parent:1.0.0", + DependsOn: []string{ + "org.example:example-dependency:2.0.0", + }, + }, + { + ID: "org.example:example-dependency:2.0.0", + DependsOn: []string{ + "org.example:example-api:1.0.1", + }, + }, + }, + }, + // [INFO] com.example:root-depManagement-in-parent:jar:1.0.0 + // [INFO] \- org.example:example-dependency:jar:2.0.0:compile + // [INFO] \- org.example:example-api:jar:2.0.1:compile + { + name: "dependency from parent uses version from child pom depManagement", + inputFile: filepath.Join("testdata", "use-dep-management-from-child-in-parent", "pom.xml"), + local: true, + want: []ftypes.Package{ + { + ID: "com.example:root-depManagement-in-parent:1.0.0", + Name: "com.example:root-depManagement-in-parent", + Version: "1.0.0", + Relationship: ftypes.RelationshipRoot, + }, + { + ID: "org.example:example-dependency:2.0.0", + Name: "org.example:example-dependency", + Version: "2.0.0", + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ + { + StartLine: 15, + EndLine: 19, + }, + }, + }, + { + ID: "org.example:example-api:2.0.1", + Name: "org.example:example-api", + Version: "2.0.1", + Relationship: ftypes.RelationshipIndirect, + }, + }, + wantDeps: []ftypes.Dependency{ + { + ID: "com.example:root-depManagement-in-parent:1.0.0", + DependsOn: []string{ + "org.example:example-dependency:2.0.0", + }, + }, + { + ID: "org.example:example-dependency:2.0.0", + DependsOn: []string{ + "org.example:example-api:2.0.1", + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/dependency/parser/java/pom/testdata/dep-without-version/pom.xml b/pkg/dependency/parser/java/pom/testdata/dep-without-version/pom.xml new file mode 100644 index 000000000000..d7da70dd4735 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/dep-without-version/pom.xml @@ -0,0 +1,24 @@ + + 4.0.0 + + no-parent + Parent not found + + + com.example + wrong-parent + 1.0.0 + + + com.example + dep-without-version + 1.0.0 + + + + org.example + example-api + + + diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency/2.0.0/example-dependency-2.0.0.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency/2.0.0/example-dependency-2.0.0.pom new file mode 100644 index 000000000000..3f236cdf314e --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency/2.0.0/example-dependency-2.0.0.pom @@ -0,0 +1,26 @@ + + + 4.0.0 + + + org.example + example-parent + 3.0.0 + + + org.example + example-dependency + 2.0.0 + + + + + org.example + example-api + 2.0.1 + + + + + \ No newline at end of file diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-parent/3.0.0/example-parent-3.0.0.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-parent/3.0.0/example-parent-3.0.0.pom new file mode 100644 index 000000000000..917908d53d7b --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-parent/3.0.0/example-parent-3.0.0.pom @@ -0,0 +1,32 @@ + + + + 4.0.0 + + org.example + example-parent + 3.0.0 + pom + + + 3.0.1 + + + + + + org.example + example-api + ${api.version} + + + + + + + org.example + example-api + + + \ No newline at end of file diff --git a/pkg/dependency/parser/java/pom/testdata/use-dep-management-from-child-in-parent/pom.xml b/pkg/dependency/parser/java/pom/testdata/use-dep-management-from-child-in-parent/pom.xml new file mode 100644 index 000000000000..5d062242161d --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/use-dep-management-from-child-in-parent/pom.xml @@ -0,0 +1,21 @@ + + 4.0.0 + + com.example + root-depManagement-in-parent + 1.0.0 + + + + 1.0.1 + + + + + org.example + example-dependency + 2.0.0 + + + diff --git a/pkg/dependency/parser/java/pom/testdata/use-root-dep-management-in-parent/pom.xml b/pkg/dependency/parser/java/pom/testdata/use-root-dep-management-in-parent/pom.xml new file mode 100644 index 000000000000..685a7fc7caf3 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/use-root-dep-management-in-parent/pom.xml @@ -0,0 +1,31 @@ + + 4.0.0 + + com.example + root-depManagement-in-parent + 1.0.0 + + + + 1.0.1 + + + + + + org.example + example-api + ${api.version} + + + + + + + org.example + example-dependency + 2.0.0 + + + diff --git a/pkg/dependency/parser/nodejs/pnpm/parse.go b/pkg/dependency/parser/nodejs/pnpm/parse.go index 8ccf9de0ae9a..6f85411f40d0 100644 --- a/pkg/dependency/parser/nodejs/pnpm/parse.go +++ b/pkg/dependency/parser/nodejs/pnpm/parse.go @@ -37,15 +37,11 @@ type LockFile struct { Packages map[string]PackageInfo `yaml:"packages,omitempty"` // V9 - Importers Importer `yaml:"importers,omitempty"` + Importers map[string]Importer `yaml:"importers,omitempty"` Snapshots map[string]Snapshot `yaml:"snapshots,omitempty"` } type Importer struct { - Root RootImporter `yaml:".,omitempty"` -} - -type RootImporter struct { Dependencies map[string]ImporterDepVersion `yaml:"dependencies,omitempty"` DevDependencies map[string]ImporterDepVersion `yaml:"devDependencies,omitempty"` } @@ -167,6 +163,18 @@ func (p *Parser) parseV9(lockFile LockFile) ([]ftypes.Package, []ftypes.Dependen } + // Parse `Importers` to find all direct dependencies + devDeps := make(map[string]string) + deps := make(map[string]string) + for _, importer := range lockFile.Importers { + for n, v := range importer.DevDependencies { + devDeps[n] = v.Version + } + for n, v := range importer.Dependencies { + deps[n] = v.Version + } + } + for depPath, pkgInfo := range lockFile.Packages { name, ver, ref := p.parseDepPath(depPath, lockVer) parsedVer := p.parseVersion(depPath, ver, lockVer) @@ -179,10 +187,10 @@ func (p *Parser) parseV9(lockFile LockFile) ([]ftypes.Package, []ftypes.Dependen // We will update `Dev` field later. dev := true relationship := ftypes.RelationshipIndirect - if dep, ok := lockFile.Importers.Root.DevDependencies[name]; ok && dep.Version == ver { + if v, ok := devDeps[name]; ok && p.trimPeerDeps(v, lockVer) == ver { relationship = ftypes.RelationshipDirect } - if dep, ok := lockFile.Importers.Root.Dependencies[name]; ok && p.trimPeerDeps(dep.Version, lockVer) == ver { + if v, ok := deps[name]; ok && p.trimPeerDeps(v, lockVer) == ver { relationship = ftypes.RelationshipDirect dev = false // mark root direct deps to update `dev` field of their child deps. } diff --git a/pkg/dependency/parser/nodejs/pnpm/parse_test.go b/pkg/dependency/parser/nodejs/pnpm/parse_test.go index ec869d6ff492..ebdfe0e2442f 100644 --- a/pkg/dependency/parser/nodejs/pnpm/parse_test.go +++ b/pkg/dependency/parser/nodejs/pnpm/parse_test.go @@ -59,12 +59,6 @@ func TestParse(t *testing.T) { want: pnpmV9, wantDeps: pnpmV9Deps, }, - { - name: "v9", - file: "testdata/pnpm-lock_v9.yaml", - want: pnpmV9, - wantDeps: pnpmV9Deps, - }, { name: "v9 with cyclic dependencies import", file: "testdata/pnpm-lock_v9_cyclic_import.yaml", diff --git a/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go b/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go index 3c9383bd1e25..5649a9325559 100644 --- a/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go +++ b/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go @@ -752,6 +752,13 @@ var ( Version: "0.4.0", Relationship: ftypes.RelationshipIndirect, }, + { + ID: "await-sleep@0.0.1", + Name: "await-sleep", + Version: "0.0.1", + Dev: true, + Relationship: ftypes.RelationshipDirect, + }, { ID: "debug@4.3.4", Name: "debug", @@ -843,6 +850,12 @@ var ( Version: "8.1.0", Relationship: ftypes.RelationshipDirect, }, + { + ID: "sleep-utils@1.0.3", + Name: "sleep-utils", + Version: "1.0.3", + Relationship: ftypes.RelationshipDirect, + }, { ID: "statuses@1.4.0", Name: "statuses", diff --git a/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_v9.yaml b/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_v9.yaml index 12a61f02b79a..ef8a229a3c88 100644 --- a/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_v9.yaml +++ b/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_v9.yaml @@ -40,6 +40,17 @@ importers: specifier: 2.0.0 version: 2.0.0 + subdir: + dependencies: + sleep-utils: + specifier: 1.0.3 + version: 1.0.3 + + devDependencies: + await-sleep: + specifier: ^0.0.1 + version: 0.0.1 + packages: '@babel/helper-string-parser@7.24.1': @@ -52,6 +63,9 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + await-sleep@0.0.1: + resolution: {integrity: sha512-H3X3eAxwGpeNIk/yvFOs8g7500Q1YvzrxjSC9TNgLGtjrMFxPwhDdcT34QNs2iGWpZ+5WKkMJdjDoYs+Sw+TaA==} + debug@4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} @@ -117,6 +131,9 @@ packages: promise@8.1.0: resolution: {integrity: sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q==} + sleep-utils@1.0.3: + resolution: {integrity: sha512-uJW7WDHISE1zJIdvoIewcdmis3pBvJhM30rni2gH7fHhV1NkTWLKw3J6CPRFdg3h+rFChFHzAgbkCKUErd4s8Q==} + statuses@1.4.0: resolution: {integrity: sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==} engines: {node: '>= 0.6'} @@ -134,6 +151,8 @@ snapshots: asynckit@0.4.0: {} + await-sleep@0.0.1: {} + debug@4.3.4(supports-color@8.1.1): dependencies: ms: 2.0.0 @@ -186,6 +205,8 @@ snapshots: optionalDependencies: asap: 2.0.6 + sleep-utils@1.0.3: {} + statuses@1.4.0: {} unpipe@1.0.0: {} diff --git a/pkg/dependency/parser/python/packaging/parse.go b/pkg/dependency/parser/python/packaging/parse.go index fa1758308860..58ecac200db5 100644 --- a/pkg/dependency/parser/python/packaging/parse.go +++ b/pkg/dependency/parser/python/packaging/parse.go @@ -80,7 +80,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc } if license == "" && h.Get("License-File") != "" { - license = "file://" + h.Get("License-File") + license = licensing.LicenseFilePrefix + h.Get("License-File") } return []ftypes.Package{ diff --git a/pkg/dependency/parser/python/pip/parse.go b/pkg/dependency/parser/python/pip/parse.go index 0d9e040f952b..981b7def69c3 100644 --- a/pkg/dependency/parser/python/pip/parse.go +++ b/pkg/dependency/parser/python/pip/parse.go @@ -25,14 +25,29 @@ const ( ) type Parser struct { - logger *log.Logger + logger *log.Logger + useMinVersion bool } -func NewParser() *Parser { +func NewParser(useMinVersion bool) *Parser { return &Parser{ - logger: log.WithPrefix("pip"), + logger: log.WithPrefix("pip"), + useMinVersion: useMinVersion, } } +func (p *Parser) splitLine(line string) []string { + separators := []string{"~=", ">=", "=="} + // Without useMinVersion check only `==` + if !p.useMinVersion { + separators = []string{"=="} + } + for _, sep := range separators { + if result := strings.Split(line, sep); len(result) == 2 { + return result + } + } + return nil +} func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { // `requirements.txt` can use byte order marks (BOM) @@ -53,10 +68,14 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc line = rStripByKey(line, commentMarker) line = rStripByKey(line, endColon) line = rStripByKey(line, hashMarker) - s := strings.Split(line, "==") + + s := p.splitLine(line) if len(s) != 2 { continue } + if p.useMinVersion && strings.HasSuffix(s[1], ".*") { + s[1] = strings.TrimSuffix(s[1], "*") + "0" + } if !isValidName(s[0]) || !isValidVersion(s[1]) { p.logger.Debug("Invalid package name/version in requirements.txt.", log.String("line", text)) diff --git a/pkg/dependency/parser/python/pip/parse_test.go b/pkg/dependency/parser/python/pip/parse_test.go index 3a13c5272cc8..97964ea77a4a 100644 --- a/pkg/dependency/parser/python/pip/parse_test.go +++ b/pkg/dependency/parser/python/pip/parse_test.go @@ -12,9 +12,10 @@ import ( func TestParse(t *testing.T) { tests := []struct { - name string - filePath string - want []ftypes.Package + name string + filePath string + useMinVersion bool + want []ftypes.Package }{ { name: "happy path", @@ -66,6 +67,12 @@ func TestParse(t *testing.T) { filePath: "testdata/requirements_with_templating_engine.txt", want: nil, }, + { + name: "compatible versions", + filePath: "testdata/requirements_compatible.txt", + useMinVersion: true, + want: requirementsCompatibleVersions, + }, } for _, tt := range tests { @@ -73,7 +80,7 @@ func TestParse(t *testing.T) { f, err := os.Open(tt.filePath) require.NoError(t, err) - got, _, err := NewParser().Parse(f) + got, _, err := NewParser(tt.useMinVersion).Parse(f) require.NoError(t, err) assert.Equal(t, tt.want, got) diff --git a/pkg/dependency/parser/python/pip/parse_testcase.go b/pkg/dependency/parser/python/pip/parse_testcase.go index e8192ee1775d..e4a8d83d7117 100644 --- a/pkg/dependency/parser/python/pip/parse_testcase.go +++ b/pkg/dependency/parser/python/pip/parse_testcase.go @@ -3,6 +3,38 @@ package pip import ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" var ( + requirementsCompatibleVersions = []ftypes.Package{ + { + Name: "keyring", + Version: "4.1.1", + Locations: []ftypes.Location{ + { + StartLine: 1, + EndLine: 1, + }, + }, + }, + { + Name: "Mopidy-Dirble", + Version: "1.1", + Locations: []ftypes.Location{ + { + StartLine: 2, + EndLine: 2, + }, + }, + }, + { + Name: "python-gitlab", + Version: "2.0.0", + Locations: []ftypes.Location{ + { + StartLine: 3, + EndLine: 3, + }, + }, + }, + } requirementsFlask = []ftypes.Package{ { Name: "click", diff --git a/pkg/dependency/parser/python/pip/testdata/requirements_compatible.txt b/pkg/dependency/parser/python/pip/testdata/requirements_compatible.txt new file mode 100644 index 000000000000..dbcde5b7ab10 --- /dev/null +++ b/pkg/dependency/parser/python/pip/testdata/requirements_compatible.txt @@ -0,0 +1,5 @@ +keyring >= 4.1.1 # Minimum version 4.1.1 +Mopidy-Dirble ~= 1.1 # Compatible release. Same as >= 1.1, == 1.* +python-gitlab==2.0.* +django==5.*.* # this dep should be skipped +django==4.*.1 \ No newline at end of file diff --git a/pkg/detector/ospkg/detect.go b/pkg/detector/ospkg/detect.go index 0f4a1df2a9d3..fedc8d31c9c2 100644 --- a/pkg/detector/ospkg/detect.go +++ b/pkg/detector/ospkg/detect.go @@ -44,6 +44,7 @@ var ( ftypes.OpenSUSETumbleweed: suse.NewScanner(suse.OpenSUSETumbleweed), ftypes.OpenSUSELeap: suse.NewScanner(suse.OpenSUSE), ftypes.SLES: suse.NewScanner(suse.SUSEEnterpriseLinux), + ftypes.SLEMicro: suse.NewScanner(suse.SUSEEnterpriseLinuxMicro), ftypes.Photon: photon.NewScanner(), ftypes.Wolfi: wolfi.NewScanner(), ftypes.Chainguard: chainguard.NewScanner(), diff --git a/pkg/detector/ospkg/oracle/oracle.go b/pkg/detector/ospkg/oracle/oracle.go index 2b234698e38a..dd66c11a3cf1 100644 --- a/pkg/detector/ospkg/oracle/oracle.go +++ b/pkg/detector/ospkg/oracle/oracle.go @@ -25,7 +25,7 @@ var ( "4": time.Date(2013, 12, 31, 23, 59, 59, 0, time.UTC), "5": time.Date(2017, 12, 31, 23, 59, 59, 0, time.UTC), "6": time.Date(2021, 3, 21, 23, 59, 59, 0, time.UTC), - "7": time.Date(2024, 7, 23, 23, 59, 59, 0, time.UTC), + "7": time.Date(2024, 12, 31, 23, 59, 59, 0, time.UTC), "8": time.Date(2029, 7, 18, 23, 59, 59, 0, time.UTC), "9": time.Date(2032, 7, 18, 23, 59, 59, 0, time.UTC), } diff --git a/pkg/detector/ospkg/suse/suse.go b/pkg/detector/ospkg/suse/suse.go index b999e1dafb22..36a26b335a96 100644 --- a/pkg/detector/ospkg/suse/suse.go +++ b/pkg/detector/ospkg/suse/suse.go @@ -44,6 +44,18 @@ var ( // 6 months after SLES 15 SP7 release // "15.7": time.Date(2031, 7, 31, 23, 59, 59, 0, time.UTC), } + slemicroEolDates = map[string]time.Time{ + // Source: https://www.suse.com/lifecycle/ + "5.0": time.Date(2022, 3, 31, 23, 59, 59, 0, time.UTC), + "5.1": time.Date(2025, 10, 31, 23, 59, 59, 0, time.UTC), + "5.2": time.Date(2026, 4, 30, 23, 59, 59, 0, time.UTC), + "5.3": time.Date(2026, 10, 30, 23, 59, 59, 0, time.UTC), + "5.4": time.Date(2027, 4, 30, 23, 59, 59, 0, time.UTC), + "5.5": time.Date(2027, 10, 31, 23, 59, 59, 0, time.UTC), + "6.0": time.Date(2028, 6, 30, 23, 59, 59, 0, time.UTC), + // 6.1 will be released late 2024 + // "6.1": time.Date(2028, 11, 30, 23, 59, 59, 0, time.UTC), + } opensuseEolDates = map[string]time.Time{ // Source: https://en.opensuse.org/Lifetime @@ -66,6 +78,8 @@ type Type int const ( // SUSEEnterpriseLinux is Linux Enterprise version SUSEEnterpriseLinux Type = iota + // SUSE Linux Enterprise Micro is the micro series + SUSEEnterpriseLinuxMicro // OpenSUSE for open versions OpenSUSE OpenSUSETumbleweed @@ -83,6 +97,10 @@ func NewScanner(t Type) *Scanner { return &Scanner{ vs: susecvrf.NewVulnSrc(susecvrf.SUSEEnterpriseLinux), } + case SUSEEnterpriseLinuxMicro: + return &Scanner{ + vs: susecvrf.NewVulnSrc(susecvrf.SUSEEnterpriseLinuxMicro), + } case OpenSUSE: return &Scanner{ vs: susecvrf.NewVulnSrc(susecvrf.OpenSUSE), @@ -135,6 +153,9 @@ func (s *Scanner) IsSupportedVersion(ctx context.Context, osFamily ftypes.OSType if osFamily == ftypes.SLES { return osver.Supported(ctx, slesEolDates, osFamily, osVer) } + if osFamily == ftypes.SLEMicro { + return osver.Supported(ctx, slemicroEolDates, osFamily, osVer) + } // tumbleweed is a rolling release, it has no version and no eol if osFamily == ftypes.OpenSUSETumbleweed { return true diff --git a/pkg/detector/ospkg/suse/suse_test.go b/pkg/detector/ospkg/suse/suse_test.go index 4db052743e82..9d480f18b0ea 100644 --- a/pkg/detector/ospkg/suse/suse_test.go +++ b/pkg/detector/ospkg/suse/suse_test.go @@ -111,6 +111,86 @@ func TestScanner_Detect(t *testing.T) { }, }, }, + { + name: "happy path: suse sle 15sp3", + fixtures: []string{ + "testdata/fixtures/suse.yaml", + "testdata/fixtures/data-source.yaml", + }, + distribution: suse.SUSEEnterpriseLinux, + args: args{ + osVer: "15.3", + pkgs: []ftypes.Package{ + { + Name: "libopenssl1_1", + Version: "1.1.1d", + Release: "150200.11.47.1", + SrcName: "libopenssl1_1", + SrcVersion: "1.1.1d", + SrcRelease: "150200.11.47.1", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + }, + }, + }, + want: []types.DetectedVulnerability{ + { + PkgName: "libopenssl1_1", + VulnerabilityID: "SUSE-SU-2022:2251-1", + InstalledVersion: "1.1.1d-150200.11.47.1", + FixedVersion: "1.1.1d-150200.11.48.1", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + DataSource: &dbTypes.DataSource{ + ID: vulnerability.SuseCVRF, + Name: "SUSE CVRF", + URL: "https://ftp.suse.com/pub/projects/security/cvrf/", + }, + }, + }, + }, + { + name: "happy path: suse sle micro 15.3", + fixtures: []string{ + "testdata/fixtures/suse.yaml", + "testdata/fixtures/data-source.yaml", + }, + distribution: suse.SUSEEnterpriseLinuxMicro, + args: args{ + osVer: "5.3", + pkgs: []ftypes.Package{ + { + Name: "libopenssl1_1", + Version: "1.1.1l", + Release: "150400.7.21.1", + SrcName: "libopenssl1_1", + SrcVersion: "1.1.1l", + SrcRelease: "150400.7.21.1", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + }, + }, + }, + want: []types.DetectedVulnerability{ + { + PkgName: "libopenssl1_1", + VulnerabilityID: "SUSE-SU-2023:0311-1", + InstalledVersion: "1.1.1l-150400.7.21.1", + FixedVersion: "1.1.1l-150400.7.22.1", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + DataSource: &dbTypes.DataSource{ + ID: vulnerability.SuseCVRF, + Name: "SUSE CVRF", + URL: "https://ftp.suse.com/pub/projects/security/cvrf/", + }, + }, + }, + }, { name: "broken bucket", fixtures: []string{ diff --git a/pkg/detector/ospkg/suse/testdata/fixtures/data-source.yaml b/pkg/detector/ospkg/suse/testdata/fixtures/data-source.yaml index b917b7e19da1..4e9ddde802d5 100644 --- a/pkg/detector/ospkg/suse/testdata/fixtures/data-source.yaml +++ b/pkg/detector/ospkg/suse/testdata/fixtures/data-source.yaml @@ -15,3 +15,8 @@ ID: "suse-cvrf" Name: "SUSE CVRF" URL: "https://ftp.suse.com/pub/projects/security/cvrf/" + - key: SUSE Linux Enterprise Micro 5.3 + value: + ID: "suse-cvrf" + Name: "SUSE CVRF" + URL: "https://ftp.suse.com/pub/projects/security/cvrf/" diff --git a/pkg/detector/ospkg/suse/testdata/fixtures/suse.yaml b/pkg/detector/ospkg/suse/testdata/fixtures/suse.yaml index 6a17594af66c..027e56673d0e 100644 --- a/pkg/detector/ospkg/suse/testdata/fixtures/suse.yaml +++ b/pkg/detector/ospkg/suse/testdata/fixtures/suse.yaml @@ -8,3 +8,23 @@ - key: CVE-2021-0001 value: FixedVersion: "" +- bucket: SUSE Linux Enterprise 15.3 + pairs: + - bucket: libopenssl1_1 + pairs: + - key: "SUSE-SU-2022:2251-1" + value: + FixedVersion: 1.1.1d-150200.11.48.1 + - bucket: openssl-1_1 + pairs: + - key: "SUSE-SU-2022:2251-1" + value: + FixedVersion: 1.1.1d-150200.11.48.1 +- bucket: SUSE Linux Enterprise Micro 5.3 + pairs: + - bucket: libopenssl1_1 + pairs: + - key: "SUSE-SU-2023:0311-1" + value: + FixedVersion: 1.1.1l-150400.7.22.1 + diff --git a/pkg/downloader/download.go b/pkg/downloader/download.go index 63b130a667fd..96fd3ce49008 100644 --- a/pkg/downloader/download.go +++ b/pkg/downloader/download.go @@ -154,7 +154,8 @@ func (t *CustomTransport) RoundTrip(req *http.Request) (*http.Response, error) { func NewGitHubTransport(u *url.URL, insecure bool, token string) http.RoundTripper { client := newGitHubClient(insecure, token) ss := strings.SplitN(u.Path, "/", 4) - if len(ss) < 4 || strings.HasPrefix(ss[3], "archive/") { + if len(ss) < 4 || strings.HasPrefix(ss[3], "archive/") || strings.HasPrefix(ss[3], "releases/") || + strings.HasPrefix(ss[3], "tags/") { // Use the default transport from go-github for authentication return client.Client().Transport } diff --git a/pkg/fanal/analyzer/analyzer.go b/pkg/fanal/analyzer/analyzer.go index d2d07ffcc733..abedb4b90638 100644 --- a/pkg/fanal/analyzer/analyzer.go +++ b/pkg/fanal/analyzer/analyzer.go @@ -44,6 +44,7 @@ type AnalyzerOptions struct { Parallel int FilePatterns []string DisabledAnalyzers []Type + DetectionPriority types.DetectionPriority MisconfScannerOption misconf.ScannerOption SecretScannerOption SecretScannerOption LicenseScannerOption LicenseScannerOption @@ -120,10 +121,11 @@ type CustomGroup interface { type Opener func() (xio.ReadSeekCloserAt, error) type AnalyzerGroup struct { - logger *log.Logger - analyzers []analyzer - postAnalyzers []PostAnalyzer - filePatterns map[Type][]*regexp.Regexp + logger *log.Logger + analyzers []analyzer + postAnalyzers []PostAnalyzer + filePatterns map[Type][]*regexp.Regexp + detectionPriority types.DetectionPriority } /////////////////////////// @@ -212,7 +214,11 @@ func (r *AnalysisResult) Sort() { // Misconfigurations sort.Slice(r.Misconfigurations, func(i, j int) bool { - return r.Misconfigurations[i].FilePath < r.Misconfigurations[j].FilePath + 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 + } }) // Secrets @@ -312,17 +318,18 @@ func belongToGroup(groupName Group, analyzerType Type, disabledAnalyzers []Type, const separator = ":" -func NewAnalyzerGroup(opt AnalyzerOptions) (AnalyzerGroup, error) { - groupName := opt.Group +func NewAnalyzerGroup(opts AnalyzerOptions) (AnalyzerGroup, error) { + groupName := opts.Group if groupName == "" { groupName = GroupBuiltin } group := AnalyzerGroup{ - logger: log.WithPrefix("analyzer"), - filePatterns: make(map[Type][]*regexp.Regexp), + logger: log.WithPrefix("analyzer"), + filePatterns: make(map[Type][]*regexp.Regexp), + detectionPriority: opts.DetectionPriority, } - for _, p := range opt.FilePatterns { + for _, p := range opts.FilePatterns { // e.g. "dockerfile:my_dockerfile_*" s := strings.SplitN(p, separator, 2) if len(s) != 2 { @@ -343,12 +350,12 @@ func NewAnalyzerGroup(opt AnalyzerOptions) (AnalyzerGroup, error) { } for analyzerType, a := range analyzers { - if !belongToGroup(groupName, analyzerType, opt.DisabledAnalyzers, a) { + if !belongToGroup(groupName, analyzerType, opts.DisabledAnalyzers, a) { continue } // Initialize only scanners that have Init() if ini, ok := a.(Initializer); ok { - if err := ini.Init(opt); err != nil { + if err := ini.Init(opts); err != nil { return AnalyzerGroup{}, xerrors.Errorf("analyzer initialization error: %w", err) } } @@ -356,11 +363,11 @@ func NewAnalyzerGroup(opt AnalyzerOptions) (AnalyzerGroup, error) { } for analyzerType, init := range postAnalyzers { - a, err := init(opt) + a, err := init(opts) if err != nil { return AnalyzerGroup{}, xerrors.Errorf("post-analyzer init error: %w", err) } - if !belongToGroup(groupName, analyzerType, opt.DisabledAnalyzers, a) { + if !belongToGroup(groupName, analyzerType, opts.DisabledAnalyzers, a) { continue } group.postAnalyzers = append(group.postAnalyzers, a) @@ -473,6 +480,11 @@ func (ag AnalyzerGroup) PostAnalyze(ctx context.Context, compositeFS *CompositeF } skippedFiles := result.SystemInstalledFiles + if ag.detectionPriority == types.PriorityComprehensive { + // If the detection priority is comprehensive, system files installed by the OS package manager will not be skipped. + // It can lead to false positives and duplicates, but it may be necessary to detect all possible vulnerabilities. + skippedFiles = nil + } for _, app := range result.Applications { skippedFiles = append(skippedFiles, app.FilePath) for _, pkg := range app.Packages { diff --git a/pkg/fanal/analyzer/analyzer_test.go b/pkg/fanal/analyzer/analyzer_test.go index 313fccda7e94..d8f93c0aa420 100644 --- a/pkg/fanal/analyzer/analyzer_test.go +++ b/pkg/fanal/analyzer/analyzer_test.go @@ -623,9 +623,9 @@ func TestAnalyzerGroup_PostAnalyze(t *testing.T) { if tt.analyzerType == analyzer.TypeJar { // init java-trivy-db with skip update - repo, err := name.NewTag(javadb.DefaultRepository) + repo, err := name.NewTag(javadb.DefaultGHCRRepository) require.NoError(t, err) - javadb.Init("./language/java/jar/testdata", repo, true, false, types.RegistryOptions{Insecure: false}) + javadb.Init("./language/java/jar/testdata", []name.Reference{repo}, true, false, types.RegistryOptions{Insecure: false}) } ctx := context.Background() diff --git a/pkg/fanal/analyzer/config/all/import.go b/pkg/fanal/analyzer/config/all/import.go index b171ab5e8a7f..74ba00ba49a8 100644 --- a/pkg/fanal/analyzer/config/all/import.go +++ b/pkg/fanal/analyzer/config/all/import.go @@ -5,8 +5,10 @@ import ( _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/cloudformation" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/dockerfile" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/helm" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/json" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/k8s" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/terraform" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/terraformplan/json" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/terraformplan/snapshot" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/yaml" ) diff --git a/pkg/fanal/analyzer/config/azurearm/azurearm.go b/pkg/fanal/analyzer/config/azurearm/azurearm.go index 3c0c4b9828f1..ecd7826173a0 100644 --- a/pkg/fanal/analyzer/config/azurearm/azurearm.go +++ b/pkg/fanal/analyzer/config/azurearm/azurearm.go @@ -6,7 +6,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" - "github.com/aquasecurity/trivy/pkg/misconf" + "github.com/aquasecurity/trivy/pkg/iac/detection" ) const ( @@ -25,7 +25,7 @@ type azureARMConfigAnalyzer struct { } func newAzureARMConfigAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { - a, err := config.NewAnalyzer(analyzerType, version, misconf.NewAzureARMScanner, opts) + a, err := config.NewAnalyzer(analyzerType, version, detection.FileTypeAzureARM, opts) if err != nil { return nil, err } diff --git a/pkg/fanal/analyzer/config/cloudformation/cloudformation.go b/pkg/fanal/analyzer/config/cloudformation/cloudformation.go index 06f7e458a859..02f281cbbb8a 100644 --- a/pkg/fanal/analyzer/config/cloudformation/cloudformation.go +++ b/pkg/fanal/analyzer/config/cloudformation/cloudformation.go @@ -3,7 +3,7 @@ package cloudformation import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" - "github.com/aquasecurity/trivy/pkg/misconf" + "github.com/aquasecurity/trivy/pkg/iac/detection" ) const ( @@ -22,7 +22,7 @@ type cloudFormationConfigAnalyzer struct { } func newCloudFormationConfigAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { - a, err := config.NewAnalyzer(analyzerType, version, misconf.NewCloudFormationScanner, opts) + a, err := config.NewAnalyzer(analyzerType, version, detection.FileTypeCloudFormation, opts) if err != nil { return nil, err } diff --git a/pkg/fanal/analyzer/config/config.go b/pkg/fanal/analyzer/config/config.go index b5f3569d0ab4..020ae1ae3562 100644 --- a/pkg/fanal/analyzer/config/config.go +++ b/pkg/fanal/analyzer/config/config.go @@ -9,6 +9,7 @@ import ( "k8s.io/utils/strings/slices" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/iac/detection" "github.com/aquasecurity/trivy/pkg/misconf" ) @@ -26,10 +27,8 @@ type Analyzer struct { scanner *misconf.Scanner } -type NewScanner func([]string, misconf.ScannerOption) (*misconf.Scanner, error) - -func NewAnalyzer(t analyzer.Type, version int, newScanner NewScanner, opts analyzer.AnalyzerOptions) (*Analyzer, error) { - s, err := newScanner(opts.FilePatterns, opts.MisconfScannerOption) +func NewAnalyzer(t analyzer.Type, version int, fileType detection.FileType, opts analyzer.AnalyzerOptions) (*Analyzer, error) { + s, err := misconf.NewScanner(fileType, opts.MisconfScannerOption) if err != nil { return nil, xerrors.Errorf("%s scanner init error: %w", t, err) } diff --git a/pkg/fanal/analyzer/config/config_test.go b/pkg/fanal/analyzer/config/config_test.go index 147b1f4d3201..34503bde2995 100644 --- a/pkg/fanal/analyzer/config/config_test.go +++ b/pkg/fanal/analyzer/config/config_test.go @@ -12,14 +12,15 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/iac/detection" "github.com/aquasecurity/trivy/pkg/misconf" ) func TestAnalyzer_PostAnalyze(t *testing.T) { type fields struct { - typ analyzer.Type - newScanner config.NewScanner - opts analyzer.AnalyzerOptions + typ analyzer.Type + fileType detection.FileType + opts analyzer.AnalyzerOptions } tests := []struct { name string @@ -31,8 +32,8 @@ func TestAnalyzer_PostAnalyze(t *testing.T) { { name: "dockerfile", fields: fields{ - typ: analyzer.TypeDockerfile, - newScanner: misconf.NewDockerfileScanner, + typ: analyzer.TypeDockerfile, + fileType: detection.FileTypeDockerfile, opts: analyzer.AnalyzerOptions{ MisconfScannerOption: misconf.ScannerOption{ Namespaces: []string{"user"}, @@ -74,8 +75,8 @@ func TestAnalyzer_PostAnalyze(t *testing.T) { { name: "non-existent dir", fields: fields{ - typ: analyzer.TypeDockerfile, - newScanner: misconf.NewDockerfileScanner, + typ: analyzer.TypeDockerfile, + fileType: detection.FileTypeDockerfile, opts: analyzer.AnalyzerOptions{ MisconfScannerOption: misconf.ScannerOption{ Namespaces: []string{"user"}, @@ -90,7 +91,7 @@ func TestAnalyzer_PostAnalyze(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - a, err := config.NewAnalyzer(tt.fields.typ, 0, tt.fields.newScanner, tt.fields.opts) + a, err := config.NewAnalyzer(tt.fields.typ, 0, tt.fields.fileType, tt.fields.opts) require.NoError(t, err) got, err := a.PostAnalyze(context.Background(), analyzer.PostAnalysisInput{ diff --git a/pkg/fanal/analyzer/config/dockerfile/docker.go b/pkg/fanal/analyzer/config/dockerfile/docker.go index 353cef4eb62a..0c4463dd528e 100644 --- a/pkg/fanal/analyzer/config/dockerfile/docker.go +++ b/pkg/fanal/analyzer/config/dockerfile/docker.go @@ -7,7 +7,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" - "github.com/aquasecurity/trivy/pkg/misconf" + "github.com/aquasecurity/trivy/pkg/iac/detection" ) const ( @@ -28,7 +28,7 @@ type dockerConfigAnalyzer struct { } func newDockerfileConfigAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { - a, err := config.NewAnalyzer(analyzerType, version, misconf.NewDockerfileScanner, opts) + a, err := config.NewAnalyzer(analyzerType, version, detection.FileTypeDockerfile, opts) if err != nil { return nil, err } diff --git a/pkg/fanal/analyzer/config/helm/helm.go b/pkg/fanal/analyzer/config/helm/helm.go index 14ea6aff63de..42542ce6c835 100644 --- a/pkg/fanal/analyzer/config/helm/helm.go +++ b/pkg/fanal/analyzer/config/helm/helm.go @@ -7,7 +7,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" - "github.com/aquasecurity/trivy/pkg/misconf" + "github.com/aquasecurity/trivy/pkg/iac/detection" ) const ( @@ -29,7 +29,7 @@ type helmConfigAnalyzer struct { } func newHelmConfigAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { - a, err := config.NewAnalyzer(analyzerType, version, misconf.NewHelmScanner, opts) + a, err := config.NewAnalyzer(analyzerType, version, detection.FileTypeHelm, opts) if err != nil { return nil, err } diff --git a/pkg/fanal/analyzer/config/json/json.go b/pkg/fanal/analyzer/config/json/json.go new file mode 100644 index 000000000000..c2a6eaa73d63 --- /dev/null +++ b/pkg/fanal/analyzer/config/json/json.go @@ -0,0 +1,36 @@ +package json + +import ( + "os" + "path/filepath" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" + "github.com/aquasecurity/trivy/pkg/iac/detection" +) + +const ( + analyzerType = analyzer.TypeJSON + version = 1 +) + +func init() { + analyzer.RegisterPostAnalyzer(analyzerType, newJSONConfigAnalyzer) +} + +// jsonConfigAnalyzer analyzes JSON files +type jsonConfigAnalyzer struct { + *config.Analyzer +} + +func newJSONConfigAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + a, err := config.NewAnalyzer(analyzerType, version, detection.FileTypeJSON, opts) + if err != nil { + return nil, err + } + return &jsonConfigAnalyzer{Analyzer: a}, nil +} + +func (*jsonConfigAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return filepath.Ext(filePath) == ".json" +} diff --git a/pkg/fanal/analyzer/config/k8s/k8s.go b/pkg/fanal/analyzer/config/k8s/k8s.go index 6f3a58af16b5..dabdf41a990b 100644 --- a/pkg/fanal/analyzer/config/k8s/k8s.go +++ b/pkg/fanal/analyzer/config/k8s/k8s.go @@ -3,7 +3,7 @@ package k8s import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" - "github.com/aquasecurity/trivy/pkg/misconf" + "github.com/aquasecurity/trivy/pkg/iac/detection" ) const ( @@ -22,7 +22,7 @@ type kubernetesConfigAnalyzer struct { } func newKubernetesConfigAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { - a, err := config.NewAnalyzer(analyzerType, version, misconf.NewKubernetesScanner, opts) + a, err := config.NewAnalyzer(analyzerType, version, detection.FileTypeKubernetes, opts) if err != nil { return nil, err } diff --git a/pkg/fanal/analyzer/config/terraform/terraform.go b/pkg/fanal/analyzer/config/terraform/terraform.go index 363d35de87fe..14b683742413 100644 --- a/pkg/fanal/analyzer/config/terraform/terraform.go +++ b/pkg/fanal/analyzer/config/terraform/terraform.go @@ -6,7 +6,6 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" "github.com/aquasecurity/trivy/pkg/iac/detection" - "github.com/aquasecurity/trivy/pkg/misconf" ) const ( @@ -25,7 +24,7 @@ type terraformConfigAnalyzer struct { } func newTerraformConfigAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { - a, err := config.NewAnalyzer(analyzerType, version, misconf.NewTerraformScanner, opts) + a, err := config.NewAnalyzer(analyzerType, version, detection.FileTypeTerraform, opts) if err != nil { return nil, err } diff --git a/pkg/fanal/analyzer/config/terraformplan/json/json.go b/pkg/fanal/analyzer/config/terraformplan/json/json.go index 5272f0f990f9..f0cb2c518549 100644 --- a/pkg/fanal/analyzer/config/terraformplan/json/json.go +++ b/pkg/fanal/analyzer/config/terraformplan/json/json.go @@ -8,7 +8,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" - "github.com/aquasecurity/trivy/pkg/misconf" + "github.com/aquasecurity/trivy/pkg/iac/detection" ) const ( @@ -31,7 +31,7 @@ type terraformPlanConfigAnalyzer struct { } func newTerraformPlanJSONConfigAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { - a, err := config.NewAnalyzer(analyzerType, version, misconf.NewTerraformPlanJSONScanner, opts) + a, err := config.NewAnalyzer(analyzerType, version, detection.FileTypeTerraformPlanJSON, opts) if err != nil { return nil, err } diff --git a/pkg/fanal/analyzer/config/terraformplan/snapshot/snapshot.go b/pkg/fanal/analyzer/config/terraformplan/snapshot/snapshot.go index 0597c137d96c..13316914874d 100644 --- a/pkg/fanal/analyzer/config/terraformplan/snapshot/snapshot.go +++ b/pkg/fanal/analyzer/config/terraformplan/snapshot/snapshot.go @@ -6,7 +6,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" - "github.com/aquasecurity/trivy/pkg/misconf" + "github.com/aquasecurity/trivy/pkg/iac/detection" ) const ( @@ -25,7 +25,7 @@ type terraformPlanConfigAnalyzer struct { } func newTerraformPlanSnapshotConfigAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { - a, err := config.NewAnalyzer(analyzerType, version, misconf.NewTerraformPlanSnapshotScanner, opts) + a, err := config.NewAnalyzer(analyzerType, version, detection.FileTypeTerraformPlanSnapshot, opts) if err != nil { return nil, err } diff --git a/pkg/fanal/analyzer/config/yaml/yaml.go b/pkg/fanal/analyzer/config/yaml/yaml.go new file mode 100644 index 000000000000..f8b5569f5bed --- /dev/null +++ b/pkg/fanal/analyzer/config/yaml/yaml.go @@ -0,0 +1,36 @@ +package yaml + +import ( + "os" + "path/filepath" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" + "github.com/aquasecurity/trivy/pkg/iac/detection" +) + +const ( + analyzerType = analyzer.TypeYAML + version = 1 +) + +func init() { + analyzer.RegisterPostAnalyzer(analyzerType, newYAMLConfigAnalyzer) +} + +// yamlConfigAnalyzer analyzes YAML files +type yamlConfigAnalyzer struct { + *config.Analyzer +} + +func newYAMLConfigAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + a, err := config.NewAnalyzer(analyzerType, version, detection.FileTypeYAML, opts) + if err != nil { + return nil, err + } + return &yamlConfigAnalyzer{Analyzer: a}, nil +} + +func (*yamlConfigAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return filepath.Ext(filePath) == ".yaml" || filepath.Ext(filePath) == ".yml" +} diff --git a/pkg/fanal/analyzer/const.go b/pkg/fanal/analyzer/const.go index 681f8b9987cc..a9733d4b6076 100644 --- a/pkg/fanal/analyzer/const.go +++ b/pkg/fanal/analyzer/const.go @@ -32,6 +32,7 @@ const ( TypeDpkg Type = "dpkg" TypeDpkgLicense Type = "dpkg-license" // For analyzing licenses TypeRpm Type = "rpm" + TypeRpmArchive Type = "rpm-archive" TypeRpmqa Type = "rpmqa" // OS Package Repository @@ -75,10 +76,11 @@ const ( TypeCondaEnv Type = "conda-environment" // Python - TypePythonPkg Type = "python-pkg" - TypePip Type = "pip" - TypePipenv Type = "pipenv" - TypePoetry Type = "poetry" + TypePythonPkg Type = "python-pkg" + TypePythonPkgEgg Type = "python-egg" + TypePip Type = "pip" + TypePipenv Type = "pipenv" + TypePoetry Type = "poetry" // Go TypeGoBinary Type = "gobinary" @@ -124,6 +126,8 @@ const ( TypeTerraform Type = Type(detection.FileTypeTerraform) TypeTerraformPlanJSON Type = Type(detection.FileTypeTerraformPlanJSON) TypeTerraformPlanSnapshot Type = Type(detection.FileTypeTerraformPlanSnapshot) + TypeYAML Type = Type(detection.FileTypeYAML) + TypeJSON Type = Type(detection.FileTypeJSON) // ======== // License @@ -245,5 +249,7 @@ var ( TypeTerraform, TypeTerraformPlanJSON, TypeTerraformPlanSnapshot, + TypeYAML, + TypeJSON, } ) diff --git a/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile.go b/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile.go index e0c70cc34cd0..c79a81f54c61 100644 --- a/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile.go +++ b/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile.go @@ -11,10 +11,15 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/image" "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/iac/detection" "github.com/aquasecurity/trivy/pkg/mapfs" "github.com/aquasecurity/trivy/pkg/misconf" ) +var disabledChecks = []string{ + "DS016", // See https://github.com/aquasecurity/trivy/issues/7368 +} + const analyzerVersion = 1 func init() { @@ -26,7 +31,8 @@ type historyAnalyzer struct { } func newHistoryAnalyzer(opts analyzer.ConfigAnalyzerOptions) (analyzer.ConfigAnalyzer, error) { - s, err := misconf.NewDockerfileScanner(opts.FilePatterns, opts.MisconfScannerOption) + opts.MisconfScannerOption.DisabledCheckIDs = append(opts.MisconfScannerOption.DisabledCheckIDs, disabledChecks...) + s, err := misconf.NewScanner(detection.FileTypeDockerfile, opts.MisconfScannerOption) if err != nil { return nil, xerrors.Errorf("misconfiguration scanner error: %w", err) } diff --git a/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile_test.go b/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile_test.go index 1ee81016ac8c..c454de8ba3d4 100644 --- a/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile_test.go +++ b/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile_test.go @@ -284,6 +284,47 @@ func Test_historyAnalyzer_Analyze(t *testing.T) { Config: nil, }, }, + { + name: "DS016 check not detected", + input: analyzer.ConfigAnalysisInput{ + Config: &v1.ConfigFile{ + Config: v1.Config{ + Healthcheck: &v1.HealthConfig{ + Test: []string{"CMD-SHELL", "curl --fail http://localhost:3000 || exit 1"}, + Interval: time.Second * 10, + Timeout: time.Second * 3, + }, + }, + History: []v1.History{ + { + // duplicate command from another layer + CreatedBy: `/bin/sh -c #(nop) CMD [\"/bin/bash\"]`, + EmptyLayer: true, + }, + { + CreatedBy: "/bin/sh -c #(nop) ADD file:e4d600fc4c9c293efe360be7b30ee96579925d1b4634c94332e2ec73f7d8eca1 in /", + }, + { + CreatedBy: `HEALTHCHECK &{["CMD-SHELL" "curl --fail http://localhost:3000 || exit 1"] "10s" "3s" "0s" '\x00'}`, + }, + { + CreatedBy: `USER user`, + EmptyLayer: true, + }, + { + CreatedBy: `/bin/sh -c #(nop) CMD [\"/bin/sh\"]`, + EmptyLayer: true, + }, + }, + }, + }, + want: &analyzer.ConfigAnalysisResult{ + Misconfiguration: &types.Misconfiguration{ + FileType: types.Dockerfile, + FilePath: "Dockerfile", + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/fanal/analyzer/language/dart/pub/pubspec.go b/pkg/fanal/analyzer/language/dart/pub/pubspec.go index 9def336d406f..30fc2dadb18b 100644 --- a/pkg/fanal/analyzer/language/dart/pub/pubspec.go +++ b/pkg/fanal/analyzer/language/dart/pub/pubspec.go @@ -37,10 +37,10 @@ type pubSpecLockAnalyzer struct { parser language.Parser } -func newPubSpecLockAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { +func newPubSpecLockAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { return pubSpecLockAnalyzer{ logger: log.WithPrefix("pub"), - parser: pub.NewParser(), + parser: pub.NewParser(opts.DetectionPriority == types.PriorityComprehensive), }, nil } diff --git a/pkg/fanal/analyzer/language/golang/mod/mod.go b/pkg/fanal/analyzer/language/golang/mod/mod.go index 96d40ba1c954..398511fdc63c 100644 --- a/pkg/fanal/analyzer/language/golang/mod/mod.go +++ b/pkg/fanal/analyzer/language/golang/mod/mod.go @@ -56,9 +56,9 @@ type gomodAnalyzer struct { func newGoModAnalyzer(opt analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { return &gomodAnalyzer{ - modParser: mod.NewParser(true), // Only the root module should replace + modParser: mod.NewParser(true, opt.DetectionPriority == types.PriorityComprehensive), // Only the root module should replace sumParser: sum.NewParser(), - leafModParser: mod.NewParser(false), + leafModParser: mod.NewParser(false, false), // Don't detect stdlib for non-root go.mod files licenseClassifierConfidenceLevel: opt.LicenseScannerOption.ClassifierConfidenceLevel, logger: log.WithPrefix("golang"), }, nil diff --git a/pkg/fanal/analyzer/language/java/jar/jar_test.go b/pkg/fanal/analyzer/language/java/jar/jar_test.go index 225737166c81..146b60c1bbd6 100644 --- a/pkg/fanal/analyzer/language/java/jar/jar_test.go +++ b/pkg/fanal/analyzer/language/java/jar/jar_test.go @@ -132,9 +132,9 @@ func Test_javaLibraryAnalyzer_Analyze(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // init java-trivy-db with skip update - repo, err := name.NewTag(javadb.DefaultRepository) + repo, err := name.NewTag(javadb.DefaultGHCRRepository) require.NoError(t, err) - javadb.Init("testdata", repo, true, false, types.RegistryOptions{Insecure: false}) + javadb.Init("testdata", []name.Reference{repo}, true, false, types.RegistryOptions{Insecure: false}) a := javaLibraryAnalyzer{} ctx := context.Background() diff --git a/pkg/fanal/analyzer/language/java/pom/pom_test.go b/pkg/fanal/analyzer/language/java/pom/pom_test.go index b3aaf43a82cf..ea147a4d1141 100644 --- a/pkg/fanal/analyzer/language/java/pom/pom_test.go +++ b/pkg/fanal/analyzer/language/java/pom/pom_test.go @@ -147,6 +147,17 @@ func Test_pomAnalyzer_Analyze(t *testing.T) { Licenses: []string{"Apache-2.0"}, Relationship: types.RelationshipRoot, }, + { + ID: "org.example:example-api", + Name: "org.example:example-api", + Relationship: types.RelationshipDirect, + Locations: []types.Location{ + { + StartLine: 21, + EndLine: 25, + }, + }, + }, }, }, }, diff --git a/pkg/fanal/analyzer/language/python/packaging/egg.go b/pkg/fanal/analyzer/language/python/packaging/egg.go new file mode 100644 index 000000000000..1de53980260e --- /dev/null +++ b/pkg/fanal/analyzer/language/python/packaging/egg.go @@ -0,0 +1,126 @@ +package packaging + +import ( + "archive/zip" + "context" + "io" + "os" + "path" + "path/filepath" + + "github.com/samber/lo" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/python/packaging" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +func init() { + analyzer.RegisterAnalyzer(&eggAnalyzer{}) +} + +const ( + eggAnalyzerVersion = 1 + eggExt = ".egg" +) + +type eggAnalyzer struct { + logger *log.Logger + licenseClassifierConfidenceLevel float64 +} + +func (a *eggAnalyzer) Init(opt analyzer.AnalyzerOptions) error { + a.logger = log.WithPrefix("python") + a.licenseClassifierConfidenceLevel = opt.LicenseScannerOption.ClassifierConfidenceLevel + return nil +} + +// Analyze analyzes egg archive files +func (a *eggAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + // .egg file is zip format and PKG-INFO needs to be extracted from the zip file. + pkginfoInZip, err := findFileInZip(input.Content, input.Info.Size(), isEggFile) + if err != nil { + return nil, xerrors.Errorf("unable to open `.egg` archive: %w", err) + } + + // Egg archive may not contain required files, then we will get nil. Skip this archives + if pkginfoInZip == nil { + return nil, nil + } + + rsa, err := xio.NewReadSeekerAt(pkginfoInZip) + if err != nil { + return nil, xerrors.Errorf("unable to convert PKG-INFO reader: %w", err) + } + + app, err := language.ParsePackage(types.PythonPkg, input.FilePath, rsa, packaging.NewParser(), input.Options.FileChecksum) + if err != nil { + return nil, xerrors.Errorf("parse error: %w", err) + } else if app == nil { + return nil, nil + } + + opener := func(licPath string) (io.ReadCloser, error) { + required := func(filePath string) bool { + return path.Base(filePath) == licPath + } + + f, err := findFileInZip(input.Content, input.Info.Size(), required) + if err != nil { + return nil, xerrors.Errorf("unable to find license file in `*.egg` file: %w", err) + } else if f == nil { // zip doesn't contain license file + return nil, nil + } + + return f, nil + } + + if err = fillAdditionalData(opener, app, a.licenseClassifierConfidenceLevel); err != nil { + a.logger.Warn("Unable to collect additional info", log.Err(err)) + } + + return &analyzer.AnalysisResult{ + Applications: []types.Application{*app}, + }, nil +} + +func findFileInZip(r xio.ReadSeekerAt, zipSize int64, required func(filePath string) bool) (io.ReadCloser, error) { + if _, err := r.Seek(0, io.SeekStart); err != nil { + return nil, xerrors.Errorf("file seek error: %w", err) + } + + zr, err := zip.NewReader(r, zipSize) + if err != nil { + return nil, xerrors.Errorf("zip reader error: %w", err) + } + + found, ok := lo.Find(zr.File, func(f *zip.File) bool { + return required(f.Name) + }) + if !ok { + return nil, nil + } + + f, err := found.Open() + if err != nil { + return nil, xerrors.Errorf("unable to open file in zip: %w", err) + } + + return f, nil +} + +func (a *eggAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return filepath.Ext(filePath) == eggExt +} + +func (a *eggAnalyzer) Type() analyzer.Type { + return analyzer.TypePythonPkgEgg +} + +func (a *eggAnalyzer) Version() int { + return eggAnalyzerVersion +} diff --git a/pkg/fanal/analyzer/language/python/packaging/egg_test.go b/pkg/fanal/analyzer/language/python/packaging/egg_test.go new file mode 100644 index 000000000000..615dffb4b0cc --- /dev/null +++ b/pkg/fanal/analyzer/language/python/packaging/egg_test.go @@ -0,0 +1,146 @@ +package packaging + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_eggAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + inputFile string + includeChecksum bool + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "egg zip", + inputFile: "testdata/egg-zip/kitchen-1.2.6-py2.7.egg", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.PythonPkg, + FilePath: "testdata/egg-zip/kitchen-1.2.6-py2.7.egg", + Packages: types.Packages{ + { + Name: "kitchen", + Version: "1.2.6", + Licenses: []string{ + "LGPL-2.1-only", + }, + FilePath: "testdata/egg-zip/kitchen-1.2.6-py2.7.egg", + }, + }, + }, + }, + }, + }, + { + name: "egg zip with checksum", + inputFile: "testdata/egg-zip/kitchen-1.2.6-py2.7.egg", + includeChecksum: true, + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.PythonPkg, + FilePath: "testdata/egg-zip/kitchen-1.2.6-py2.7.egg", + Packages: types.Packages{ + { + Name: "kitchen", + Version: "1.2.6", + Licenses: []string{ + "LGPL-2.1-only", + }, + FilePath: "testdata/egg-zip/kitchen-1.2.6-py2.7.egg", + Digest: "sha1:4e13b6e379966771e896ee43cf8e240bf6083dca", + }, + }, + }, + }, + }, + }, + { + name: "egg zip with license file", + inputFile: "testdata/egg-zip-with-license-file/sample_package.egg", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.PythonPkg, + FilePath: "testdata/egg-zip-with-license-file/sample_package.egg", + Packages: types.Packages{ + { + Name: "sample_package", + Version: "0.1", + Licenses: []string{ + "MIT", + }, + FilePath: "testdata/egg-zip-with-license-file/sample_package.egg", + }, + }, + }, + }, + }, + }, + { + name: "egg zip doesn't contain required files", + inputFile: "testdata/no-req-files/no-required-files.egg", + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + fileInfo, err := os.Lstat(tt.inputFile) + require.NoError(t, err) + + a := &eggAnalyzer{} + got, err := a.Analyze(context.Background(), analyzer.AnalysisInput{ + Content: f, + FilePath: tt.inputFile, + Info: fileInfo, + Options: analyzer.AnalysisOptions{ + FileChecksum: tt.includeChecksum, + }, + }) + + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } + +} + +func Test_eggAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "egg zip", + filePath: "python2.7/site-packages/cssutils-1.0-py2.7.egg", + want: true, + }, + { + name: "egg-info PKG-INFO", + filePath: "python3.8/site-packages/wrapt-1.12.1.egg-info/PKG-INFO", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := eggAnalyzer{} + got := a.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/python/packaging/packaging.go b/pkg/fanal/analyzer/language/python/packaging/packaging.go index 51fd585d8a6c..944a5abde331 100644 --- a/pkg/fanal/analyzer/language/python/packaging/packaging.go +++ b/pkg/fanal/analyzer/language/python/packaging/packaging.go @@ -1,8 +1,6 @@ package packaging import ( - "archive/zip" - "bytes" "context" "errors" "io" @@ -29,7 +27,7 @@ func init() { analyzer.RegisterPostAnalyzer(analyzer.TypePythonPkg, newPackagingAnalyzer) } -const version = 1 +const version = 2 func newPackagingAnalyzer(opt analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { return &packagingAnalyzer{ @@ -43,7 +41,7 @@ var ( eggFiles = []string{ // .egg format // https://setuptools.readthedocs.io/en/latest/deprecated/python_eggs.html#eggs-and-their-formats - ".egg", // zip format + // ".egg" is zip format. We check it in `eggAnalyzer`. "EGG-INFO/PKG-INFO", // .egg-info format: .egg-info can be a file or directory @@ -68,38 +66,32 @@ func (a packagingAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAna return filepath.Base(path) == "METADATA" || isEggFile(path) } - err := fsutils.WalkDir(input.FS, ".", required, func(path string, d fs.DirEntry, r io.Reader) error { + err := fsutils.WalkDir(input.FS, ".", required, func(filePath string, d fs.DirEntry, r io.Reader) error { rsa, ok := r.(xio.ReadSeekerAt) if !ok { return xerrors.New("invalid reader") } - // .egg file is zip format and PKG-INFO needs to be extracted from the zip file. - if strings.HasSuffix(path, ".egg") { - info, err := d.Info() - if err != nil { - return xerrors.Errorf("egg file error: %w", err) - } - pkginfoInZip, err := a.analyzeEggZip(rsa, info.Size()) - if err != nil { - return xerrors.Errorf("egg analysis error: %w", err) - } - - // Egg archive may not contain required files, then we will get nil. Skip this archives - if pkginfoInZip == nil { - return nil - } - rsa = pkginfoInZip - } - - app, err := a.parse(path, rsa, input.Options.FileChecksum) + app, err := a.parse(filePath, rsa, input.Options.FileChecksum) if err != nil { return xerrors.Errorf("parse error: %w", err) } else if app == nil { return nil } - if err := a.fillAdditionalData(input.FS, app); err != nil { + opener := func(licPath string) (io.ReadCloser, error) { + // Note that fs.FS is always slashed regardless of the platform, + // and path.Join should be used rather than filepath.Join. + f, err := input.FS.Open(path.Join(path.Dir(filePath), licPath)) + if errors.Is(err, fs.ErrNotExist) { + return nil, nil + } else if err != nil { + return nil, xerrors.Errorf("file open error: %w", err) + } + return f, nil + } + + if err = fillAdditionalData(opener, app, a.licenseClassifierConfidenceLevel); err != nil { a.logger.Warn("Unable to collect additional info", log.Err(err)) } @@ -115,30 +107,25 @@ func (a packagingAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAna }, nil } -func (a packagingAnalyzer) fillAdditionalData(fsys fs.FS, app *types.Application) error { +type fileOpener func(filePath string) (io.ReadCloser, error) + +func fillAdditionalData(opener fileOpener, app *types.Application, licenseClassifierConfidenceLevel float64) error { for i, pkg := range app.Packages { var licenses []string for _, lic := range pkg.Licenses { // Parser adds `file://` prefix to filepath from `License-File` field // We need to read this file to find licenses // Otherwise, this is the name of the license - if !strings.HasPrefix(lic, "file://") { + if !strings.HasPrefix(lic, licensing.LicenseFilePrefix) { licenses = append(licenses, lic) continue } - licenseFilePath := path.Base(strings.TrimPrefix(lic, "file://")) + licensePath := path.Base(strings.TrimPrefix(lic, licensing.LicenseFilePrefix)) - findings, err := classifyLicense(app.FilePath, licenseFilePath, a.licenseClassifierConfidenceLevel, fsys) + foundLicenses, err := classifyLicenses(opener, licensePath, licenseClassifierConfidenceLevel) if err != nil { - return err - } else if len(findings) == 0 { - continue + return xerrors.Errorf("unable to classify licenses: %w", err) } - - // License found - foundLicenses := lo.Map(findings, func(finding types.LicenseFinding, _ int) string { - return finding.Name - }) licenses = append(licenses, foundLicenses...) } app.Packages[i].Licenses = licenses @@ -147,62 +134,32 @@ func (a packagingAnalyzer) fillAdditionalData(fsys fs.FS, app *types.Application return nil } -func classifyLicense(dir, licPath string, classifierConfidenceLevel float64, fsys fs.FS) (types.LicenseFindings, error) { - // Note that fs.FS is always slashed regardless of the platform, - // and path.Join should be used rather than filepath.Join. - f, err := fsys.Open(path.Join(path.Dir(dir), licPath)) - if errors.Is(err, fs.ErrNotExist) { +func classifyLicenses(opener fileOpener, licPath string, licenseClassifierConfidenceLevel float64) ([]string, error) { + f, err := opener(licPath) + if err != nil { + return nil, xerrors.Errorf("unable to open license file: %w", err) + } else if f == nil { // File doesn't exist return nil, nil - } else if err != nil { - return nil, xerrors.Errorf("file open error: %w", err) } defer f.Close() - l, err := licensing.Classify(licPath, f, classifierConfidenceLevel) + l, err := licensing.Classify("", f, licenseClassifierConfidenceLevel) if err != nil { return nil, xerrors.Errorf("license classify error: %w", err) - } else if l == nil { + } else if l == nil { // No licenses found return nil, nil } - return l.Findings, nil + // License found + return lo.Map(l.Findings, func(finding types.LicenseFinding, _ int) string { + return finding.Name + }), nil } func (a packagingAnalyzer) parse(filePath string, r xio.ReadSeekerAt, checksum bool) (*types.Application, error) { return language.ParsePackage(types.PythonPkg, filePath, r, a.pkgParser, checksum) } -func (a packagingAnalyzer) analyzeEggZip(r io.ReaderAt, size int64) (xio.ReadSeekerAt, error) { - zr, err := zip.NewReader(r, size) - if err != nil { - return nil, xerrors.Errorf("zip reader error: %w", err) - } - - found, ok := lo.Find(zr.File, func(f *zip.File) bool { - return isEggFile(f.Name) - }) - if !ok { - return nil, nil - } - return a.open(found) -} - -// open reads the file content in the zip archive to make it seekable. -func (a packagingAnalyzer) open(file *zip.File) (xio.ReadSeekerAt, error) { - f, err := file.Open() - if err != nil { - return nil, err - } - defer f.Close() - - b, err := io.ReadAll(f) - if err != nil { - return nil, xerrors.Errorf("file %s open error: %w", file.Name, err) - } - - return bytes.NewReader(b), nil -} - func (a packagingAnalyzer) Required(filePath string, _ os.FileInfo) bool { return strings.Contains(filePath, ".dist-info") || isEggFile(filePath) } diff --git a/pkg/fanal/analyzer/language/python/packaging/packaging_test.go b/pkg/fanal/analyzer/language/python/packaging/packaging_test.go index c3a89ad0cd19..47a9cca901b6 100644 --- a/pkg/fanal/analyzer/language/python/packaging/packaging_test.go +++ b/pkg/fanal/analyzer/language/python/packaging/packaging_test.go @@ -20,28 +20,6 @@ func Test_packagingAnalyzer_Analyze(t *testing.T) { want *analyzer.AnalysisResult wantErr string }{ - { - name: "egg zip", - dir: "testdata/egg-zip", - want: &analyzer.AnalysisResult{ - Applications: []types.Application{ - { - Type: types.PythonPkg, - FilePath: "kitchen-1.2.6-py2.7.egg", - Packages: types.Packages{ - { - Name: "kitchen", - Version: "1.2.6", - Licenses: []string{ - "GNU Library or Lesser General Public License (LGPL)", - }, - FilePath: "kitchen-1.2.6-py2.7.egg", - }, - }, - }, - }, - }, - }, { name: "egg-info", dir: "testdata/happy-egg", @@ -55,7 +33,7 @@ func Test_packagingAnalyzer_Analyze(t *testing.T) { { Name: "distlib", Version: "0.3.1", - Licenses: []string{"Python license"}, + Licenses: []string{"Python-2.0"}, FilePath: "distlib-0.3.1.egg-info/PKG-INFO", Digest: "sha1:d9d89d8ed3b2b683767c96814c9c5d3e57ef2e1b", }, @@ -76,7 +54,7 @@ func Test_packagingAnalyzer_Analyze(t *testing.T) { { Name: "setuptools", Version: "51.3.3", - Licenses: []string{"MIT License"}, + Licenses: []string{"MIT"}, FilePath: "setuptools-51.3.3.egg-info/PKG-INFO", }, }, @@ -96,7 +74,7 @@ func Test_packagingAnalyzer_Analyze(t *testing.T) { { Name: "setuptools", Version: "51.3.3", - Licenses: []string{"MIT License"}, + Licenses: []string{"MIT"}, FilePath: "setuptools-51.3.3.dist-info/METADATA", }, }, @@ -116,7 +94,7 @@ func Test_packagingAnalyzer_Analyze(t *testing.T) { { Name: "distlib", Version: "0.3.1", - Licenses: []string{"Python license"}, + Licenses: []string{"Python-2.0"}, FilePath: "distlib-0.3.1.dist-info/METADATA", }, }, @@ -124,11 +102,6 @@ func Test_packagingAnalyzer_Analyze(t *testing.T) { }, }, }, - { - name: "egg zip doesn't contain required files", - dir: "testdata/no-req-files", - want: &analyzer.AnalysisResult{}, - }, { name: "license file in dist.info", dir: "testdata/license-file-dist", diff --git a/pkg/fanal/analyzer/language/python/packaging/testdata/egg-zip-with-license-file/sample_package.egg b/pkg/fanal/analyzer/language/python/packaging/testdata/egg-zip-with-license-file/sample_package.egg new file mode 100644 index 000000000000..91d67dc5947b Binary files /dev/null and b/pkg/fanal/analyzer/language/python/packaging/testdata/egg-zip-with-license-file/sample_package.egg differ diff --git a/pkg/fanal/analyzer/language/python/pip/pip.go b/pkg/fanal/analyzer/language/python/pip/pip.go index e08a90e7a70b..670dd195bb50 100644 --- a/pkg/fanal/analyzer/language/python/pip/pip.go +++ b/pkg/fanal/analyzer/language/python/pip/pip.go @@ -38,14 +38,16 @@ var pythonExecNames = []string{ } type pipLibraryAnalyzer struct { - logger *log.Logger - metadataParser packaging.Parser + logger *log.Logger + metadataParser packaging.Parser + detectionPriority types.DetectionPriority } -func newPipLibraryAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { +func newPipLibraryAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { return pipLibraryAnalyzer{ - logger: log.WithPrefix("pip"), - metadataParser: *packaging.NewParser(), + logger: log.WithPrefix("pip"), + metadataParser: *packaging.NewParser(), + detectionPriority: opts.DetectionPriority, }, nil } @@ -62,8 +64,10 @@ func (a pipLibraryAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAn return true } + useMinVersion := a.detectionPriority == types.PriorityComprehensive + if err = fsutils.WalkDir(input.FS, ".", required, func(pathPath string, d fs.DirEntry, r io.Reader) error { - app, err := language.Parse(types.Pip, pathPath, r, pip.NewParser()) + 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/os/release/release.go b/pkg/fanal/analyzer/os/release/release.go index 8da24644d5f7..67a18714ac1a 100644 --- a/pkg/fanal/analyzer/os/release/release.go +++ b/pkg/fanal/analyzer/os/release/release.go @@ -55,6 +55,11 @@ func (a osReleaseAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInp family = types.OpenSUSELeap case "sles": family = types.SLES + // There are various rebrands of SLE Micro, there is also one brief (and reverted rebrand) + // for SLE Micro 6.0. which was called "SL Micro 6.0" until very short before release + // and there is a "SLE Micro for Rancher" rebrand, which is used by SUSEs K8S based offerings. + case "sle-micro", "sl-micro", "sle-micro-rancher": + family = types.SLEMicro case "photon": family = types.Photon case "wolfi": diff --git a/pkg/fanal/analyzer/os/release/release_test.go b/pkg/fanal/analyzer/os/release/release_test.go index 3b534ad7b14d..532e01d9efef 100644 --- a/pkg/fanal/analyzer/os/release/release_test.go +++ b/pkg/fanal/analyzer/os/release/release_test.go @@ -70,6 +70,36 @@ func Test_osReleaseAnalyzer_Analyze(t *testing.T) { }, }, }, + { + name: "SUSE Linux Enterprise Micro", + inputFile: "testdata/slemicro", + want: &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.SLEMicro, + Name: "5.3", + }, + }, + }, + { + name: "SUSE Linux Enterprise Micro 6.0", + inputFile: "testdata/slemicro6.0", + want: &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.SLEMicro, + Name: "6.0", + }, + }, + }, + { + name: "SUSE Linux Enterprise Micro 5.4 for Rancher", + inputFile: "testdata/slemicro-rancher", + want: &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.SLEMicro, + Name: "5.4", + }, + }, + }, { name: "Photon OS", inputFile: "testdata/photon", diff --git a/pkg/fanal/analyzer/os/release/testdata/slemicro b/pkg/fanal/analyzer/os/release/testdata/slemicro new file mode 100644 index 000000000000..3e3bdaa30de5 --- /dev/null +++ b/pkg/fanal/analyzer/os/release/testdata/slemicro @@ -0,0 +1,8 @@ +NAME="SLE Micro" +VERSION="5.3" +VERSION_ID="5.3" +PRETTY_NAME="SUSE Linux Enterprise Micro 5.3" +ID="sle-micro" +ID_LIKE="suse" +ANSI_COLOR="0;32" +CPE_NAME="cpe:/o:suse:sle-micro:5.3" diff --git a/pkg/fanal/analyzer/os/release/testdata/slemicro-rancher b/pkg/fanal/analyzer/os/release/testdata/slemicro-rancher new file mode 100644 index 000000000000..83ce34662c3d --- /dev/null +++ b/pkg/fanal/analyzer/os/release/testdata/slemicro-rancher @@ -0,0 +1,13 @@ +NAME="SLE Micro" +VERSION="5.4" +VERSION_ID="5.4" +PRETTY_NAME="SUSE Linux Enterprise Micro for Rancher 5.4" +ID="sle-micro-rancher" +ID_LIKE="suse" +ANSI_COLOR="0;32" +CPE_NAME="cpe:/o:suse:sle-micro-rancher:5.4" +IMAGE_REPO="registry.suse.com/rancher/elemental-teal/5.4" +IMAGE_TAG="1.2.3-3.2.153" +IMAGE="registry.suse.com/rancher/elemental-teal/5.4:1.2.3-3.2.153" +TIMESTAMP=20240419051540 +GRUB_ENTRY_NAME="Elemental" diff --git a/pkg/fanal/analyzer/os/release/testdata/slemicro6.0 b/pkg/fanal/analyzer/os/release/testdata/slemicro6.0 new file mode 100644 index 000000000000..a18632b128a6 --- /dev/null +++ b/pkg/fanal/analyzer/os/release/testdata/slemicro6.0 @@ -0,0 +1,10 @@ +NAME="SL-Micro" +VERSION="6.0" +VERSION_ID="6.0" +PRETTY_NAME="SUSE Linux Micro 6.0" +ID="sl-micro" +ID_LIKE="suse" +ANSI_COLOR="0;32" +CPE_NAME="cpe:/o:suse:sl-micro:6.0" +HOME_URL="https://www.suse.com/products/micro/" +DOCUMENTATION_URL="https://documentation.suse.com/sl-micro/6.0/" diff --git a/pkg/fanal/analyzer/pkg/apk/apk.go b/pkg/fanal/analyzer/pkg/apk/apk.go index 9ce13e4b013d..962398600fc5 100644 --- a/pkg/fanal/analyzer/pkg/apk/apk.go +++ b/pkg/fanal/analyzer/pkg/apk/apk.go @@ -142,26 +142,8 @@ func (a alpinePkgAnalyzer) trimRequirement(s string) string { } func (a alpinePkgAnalyzer) parseLicense(line string) []string { - line = line[2:] // Remove "L:" - if line == "" { - return nil - } - var licenses []string - // e.g. MPL 2.0 GPL2+ => {"MPL2.0", "GPL2+"} - for i, s := range strings.Fields(line) { - s = strings.Trim(s, "()") - switch { - case s == "": - continue - case s == "AND" || s == "OR": - continue - case i > 0 && (s == "1.0" || s == "2.0" || s == "3.0"): - licenses[i-1] = licensing.Normalize(licenses[i-1] + s) - default: - licenses = append(licenses, licensing.Normalize(s)) - } - } - return licenses + // Remove "L:" before split + return licensing.LaxSplitLicenses(line[2:]) } func (a alpinePkgAnalyzer) parseProvides(line, pkgID string, provides map[string]string) { diff --git a/pkg/fanal/analyzer/pkg/apk/apk_test.go b/pkg/fanal/analyzer/pkg/apk/apk_test.go index 08a5d302d324..0491ed57c0d2 100644 --- a/pkg/fanal/analyzer/pkg/apk/apk_test.go +++ b/pkg/fanal/analyzer/pkg/apk/apk_test.go @@ -33,7 +33,7 @@ var pkgs = []types.Package{ Version: "1.24.2-r9", SrcName: "busybox", SrcVersion: "1.24.2-r9", - Licenses: []string{"GPL-2.0"}, + Licenses: []string{"GPL-2.0-only"}, DependsOn: []string{"musl@1.1.14-r10"}, Arch: "x86_64", Digest: "sha1:ca124719267cd0bedc2f4cb850a286ac13f0ad44", @@ -51,7 +51,7 @@ var pkgs = []types.Package{ Version: "3.0.3-r0", SrcName: "alpine-baselayout", SrcVersion: "3.0.3-r0", - Licenses: []string{"GPL-2.0"}, + Licenses: []string{"GPL-2.0-only"}, DependsOn: []string{ "busybox@1.24.2-r9", "musl@1.1.14-r10", @@ -92,7 +92,7 @@ var pkgs = []types.Package{ Version: "1.1-r0", SrcName: "alpine-keys", SrcVersion: "1.1-r0", - Licenses: []string{"GPL-3.0"}, + Licenses: []string{"GPL-2.0-or-later"}, Arch: "x86_64", Digest: "sha1:4def7ffaee6aeba700c1d62570326f75cbb8fa25", InstalledFiles: []string{ @@ -124,7 +124,7 @@ var pkgs = []types.Package{ Version: "1.0.2h-r1", SrcName: "openssl", SrcVersion: "1.0.2h-r1", - Licenses: []string{"openssl"}, + Licenses: []string{"OpenSSL"}, DependsOn: []string{ "musl@1.1.14-r10", "zlib@1.2.8-r2", @@ -155,7 +155,7 @@ var pkgs = []types.Package{ Version: "1.0.2h-r1", SrcName: "openssl", SrcVersion: "1.0.2h-r1", - Licenses: []string{"openssl"}, + Licenses: []string{"OpenSSL"}, Digest: "sha1:7120f337e93b2b4c44e0f5f31a15b60dc678ca14", DependsOn: []string{ "libcrypto1.0@1.0.2h-r1", @@ -173,7 +173,7 @@ var pkgs = []types.Package{ Version: "2.6.7-r0", SrcName: "apk-tools", SrcVersion: "2.6.7-r0", - Licenses: []string{"GPL-2.0"}, + Licenses: []string{"GPL-2.0-only"}, Digest: "sha1:0990c0acd62b4175818c3a4cc60ed11f14e23bd8", DependsOn: []string{ "libcrypto1.0@1.0.2h-r1", @@ -192,7 +192,7 @@ var pkgs = []types.Package{ Version: "1.1.6-r0", SrcName: "pax-utils", SrcVersion: "1.1.6-r0", - Licenses: []string{"GPL-2.0"}, + Licenses: []string{"GPL-2.0-only"}, Digest: "sha1:f9bab817c5ad93e92a6218bc0f7596b657c02d90", DependsOn: []string{"musl@1.1.14-r10"}, Arch: "x86_64", @@ -209,7 +209,7 @@ var pkgs = []types.Package{ Licenses: []string{ "MIT", "BSD-3-Clause", - "GPL-2.0", + "GPL-2.0-or-later", }, Digest: "sha1:608aa1dd39eff7bc6615d3e5e33383750f8f5ecc", DependsOn: []string{ @@ -231,7 +231,7 @@ var pkgs = []types.Package{ Version: "0.7-r0", SrcName: "libc-dev", SrcVersion: "0.7-r0", - Licenses: []string{"GPL-3.0"}, + Licenses: []string{"GPL-2.0-or-later"}, Digest: "sha1:9055bc7afd76cf2672198042f72fc4a5ed4fa961", DependsOn: []string{"musl-utils@1.1.14-r10"}, Arch: "x86_64", @@ -255,7 +255,6 @@ var pkgs = []types.Package{ "usr/share/aclocal/pkg.m4", }, }, - { ID: "sqlite-libs@3.26.0-r3", Name: "sqlite-libs", @@ -271,7 +270,6 @@ var pkgs = []types.Package{ "usr/lib/libsqlite3.so.0.8.6", }, }, - { ID: "test@2.9.11_pre20061021-r2", Name: "test", @@ -292,7 +290,6 @@ var pkgs = []types.Package{ "usr/include/sqlite3.h", }, }, - { ID: "ada-libs@2.7.4-r0", Name: "ada-libs", diff --git a/pkg/fanal/analyzer/pkg/dpkg/copyright_test.go b/pkg/fanal/analyzer/pkg/dpkg/copyright_test.go index d44899e4362e..2c6c1e2c50ee 100644 --- a/pkg/fanal/analyzer/pkg/dpkg/copyright_test.go +++ b/pkg/fanal/analyzer/pkg/dpkg/copyright_test.go @@ -29,8 +29,8 @@ func Test_dpkgLicenseAnalyzer_Analyze(t *testing.T) { Type: types.LicenseTypeDpkg, FilePath: "usr/share/doc/zlib1g/copyright", Findings: []types.LicenseFinding{ - {Name: "GPL-1.0"}, - {Name: "Artistic"}, + {Name: "GPL-1.0-or-later"}, + {Name: "Artistic-2.0"}, {Name: "BSD-4-clause-POWERDOG"}, {Name: "Zlib"}, }, @@ -49,7 +49,7 @@ func Test_dpkgLicenseAnalyzer_Analyze(t *testing.T) { Type: types.LicenseTypeDpkg, FilePath: "usr/share/doc/adduser/copyright", Findings: []types.LicenseFinding{ - {Name: "GPL-2.0"}, + {Name: "GPL-2.0-only"}, }, PkgName: "adduser", }, @@ -66,7 +66,8 @@ func Test_dpkgLicenseAnalyzer_Analyze(t *testing.T) { Type: types.LicenseTypeDpkg, FilePath: "usr/share/doc/apt/copyright", Findings: []types.LicenseFinding{ - {Name: "GPL-2.0"}, + {Name: "GPL-2.0-or-later"}, + {Name: "GPL-2.0-only"}, }, PkgName: "apt", }, diff --git a/pkg/fanal/analyzer/pkg/rpm/archive.go b/pkg/fanal/analyzer/pkg/rpm/archive.go new file mode 100644 index 000000000000..2eda91e4a563 --- /dev/null +++ b/pkg/fanal/analyzer/pkg/rpm/archive.go @@ -0,0 +1,168 @@ +package rpm + +import ( + "context" + "errors" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/package-url/packageurl-go" + "github.com/sassoftware/go-rpmutils" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/scanner/utils" +) + +const archiveVersion = 1 + +func init() { + analyzer.RegisterAnalyzer(newRPMArchiveAnalyzer()) +} + +type rpmArchiveAnalyzer struct { + logger *log.Logger +} + +func newRPMArchiveAnalyzer() *rpmArchiveAnalyzer { + return &rpmArchiveAnalyzer{ + logger: log.WithPrefix("rpm-archive"), + } +} + +func (a *rpmArchiveAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + rpm, err := rpmutils.ReadRpm(input.Content) + if err != nil { + return nil, xerrors.Errorf("failed to read rpm (%s): %w", input.FilePath, err) + } + pkg, err := a.parseHeader(rpm.Header) + if err != nil { + return nil, xerrors.Errorf("failed to parse rpm header: %w", err) + } + pkg.FilePath = input.FilePath + pkg.Identifier.PURL = a.generatePURL(&pkg) + + return &analyzer.AnalysisResult{ + PackageInfos: []types.PackageInfo{ + { + FilePath: input.FilePath, + Packages: types.Packages{pkg}, + }, + }, + }, nil +} + +func (a *rpmArchiveAnalyzer) parseHeader(h *rpmutils.RpmHeader) (types.Package, error) { + if h == nil { + return types.Package{}, errors.New("rpm header is nil") + } + // Getting metadata + nevra, err := h.GetNEVRA() + if a.unexpectedError(err) != nil { + return types.Package{}, xerrors.Errorf("failed to get NEVRA: %w", err) + } + epoch, err := strconv.Atoi(nevra.Epoch) + if a.unexpectedError(err) != nil { + return types.Package{}, xerrors.Errorf("failed to convert epoch to int (%s): %w", nevra.Name, err) + } + licenses, err := h.GetStrings(rpmutils.LICENSE) + if a.unexpectedError(err) != nil { + return types.Package{}, xerrors.Errorf("failed to get licenses: %w", err) + } + srcName, srcVer, srcRel, err := a.parseSourceRPM(h) + if err != nil { + return types.Package{}, xerrors.Errorf("failed to parse source rpm: %w", err) + } + vendor, err := h.GetString(rpmutils.VENDOR) + if a.unexpectedError(err) != nil { + return types.Package{}, xerrors.Errorf("failed to get vendor: %w", err) + } + + // TODO: add the const to go-rpmutils + // cf. https://github.com/rpm-software-management/rpm/blob/rpm-4.16.0-release/lib/rpmtag.h#L375 + const modularityLabelTag = 5096 + modularityLabel, err := h.GetString(modularityLabelTag) + if a.unexpectedError(err) != nil { + return types.Package{}, xerrors.Errorf("failed to get modularitylabel: %w", err) + } + + return types.Package{ + Name: nevra.Name, + Version: nevra.Version, + Release: nevra.Release, + Epoch: epoch, + Arch: nevra.Arch, + SrcName: srcName, + SrcVersion: srcVer, + SrcRelease: srcRel, + SrcEpoch: epoch, + Licenses: licenses, + Maintainer: vendor, + Modularitylabel: modularityLabel, + }, nil +} + +func (a *rpmArchiveAnalyzer) parseSourceRPM(h *rpmutils.RpmHeader) (string, string, string, error) { + sourceRpm, err := h.GetString(rpmutils.SOURCERPM) + if a.unexpectedError(err) != nil { + return "", "", "", xerrors.Errorf("failed to get source rpm: %w", err) + } else if sourceRpm == "(none)" || sourceRpm == "" { + return "", "", "", nil + } + + srcName, srcVer, srcRel, err := splitFileName(sourceRpm) + if err != nil { + a.logger.Debug("Invalid Source RPM Found", log.String("sourcerpm", sourceRpm)) + } + return srcName, srcVer, srcRel, nil +} + +func (a *rpmArchiveAnalyzer) generatePURL(pkg *types.Package) *packageurl.PackageURL { + vendor := strings.ToLower(pkg.Maintainer) + + // TODO: Handle more vendors + var ns string + switch { + case strings.Contains(vendor, "red hat"): + ns = "redhat" + case strings.Contains(vendor, "fedora"): + ns = "fedora" + case strings.Contains(vendor, "opensuse"): + ns = "opensuse" + case strings.Contains(vendor, "suse"): + ns = "suse" + } + var qualifiers packageurl.Qualifiers + if pkg.Arch != "" { + qualifiers = append(qualifiers, packageurl.Qualifier{ + Key: "arch", + Value: pkg.Arch, + }) + } + return packageurl.NewPackageURL(packageurl.TypeRPM, ns, pkg.Name, utils.FormatVersion(*pkg), qualifiers, "") +} + +func (a *rpmArchiveAnalyzer) unexpectedError(err error) error { + var rerr rpmutils.NoSuchTagError + if errors.As(err, &rerr) { + a.logger.Debug("RPM tag not found", log.Err(rerr)) + return nil + } + return err +} + +func (a *rpmArchiveAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return filepath.Ext(filePath) == ".rpm" +} + +func (a *rpmArchiveAnalyzer) Type() analyzer.Type { + return analyzer.TypeRpmArchive +} + +func (a *rpmArchiveAnalyzer) Version() int { + return archiveVersion +} diff --git a/pkg/fanal/analyzer/pkg/rpm/archive_test.go b/pkg/fanal/analyzer/pkg/rpm/archive_test.go new file mode 100644 index 000000000000..5e8475e171f6 --- /dev/null +++ b/pkg/fanal/analyzer/pkg/rpm/archive_test.go @@ -0,0 +1,87 @@ +package rpm + +import ( + "context" + "os" + "strings" + "testing" + + "github.com/package-url/packageurl-go" + "github.com/samber/lo" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_rpmArchiveAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + input analyzer.AnalysisInput + want *analyzer.AnalysisResult + wantErr require.ErrorAssertionFunc + }{ + { + name: "valid", + input: analyzer.AnalysisInput{ + FilePath: "testdata/valid.rpm", + Content: lo.Must(os.Open("testdata/socat-1.7.3.2-2.el7.x86_64.rpm")), // Must run 'mage rpm:fixtures' before this test + }, + want: &analyzer.AnalysisResult{ + PackageInfos: []types.PackageInfo{ + { + FilePath: "testdata/valid.rpm", + Packages: types.Packages{ + { + Name: "socat", + Version: "1.7.3.2", + Release: "2.el7", + Arch: "x86_64", + SrcName: "socat", + SrcVersion: "1.7.3.2", + SrcRelease: "2.el7", + FilePath: "testdata/valid.rpm", + Licenses: []string{ + "GPLv2", + }, + Maintainer: "Red Hat, Inc.", + Identifier: types.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeRPM, + Namespace: "redhat", + Name: "socat", + Version: "1.7.3.2-2.el7", + Qualifiers: packageurl.Qualifiers{ + { + Key: "arch", + Value: "x86_64", + }, + }, + }, + }, + }, + }, + }, + }, + }, + wantErr: require.NoError, + }, + { + name: "broken", + input: analyzer.AnalysisInput{ + FilePath: "testdata/broken.rpm", + Content: strings.NewReader(`broken`), + }, + wantErr: require.Error, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := newRPMArchiveAnalyzer() + got, err := a.Analyze(context.Background(), tt.input) + tt.wantErr(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/pkg/rpm/testdata/fixture.go b/pkg/fanal/analyzer/pkg/rpm/testdata/fixture.go new file mode 100644 index 000000000000..c9b1c0779f61 --- /dev/null +++ b/pkg/fanal/analyzer/pkg/rpm/testdata/fixture.go @@ -0,0 +1,63 @@ +package rpm + +import ( + "io" + "log/slog" + "net/http" + "os" + "path" + "path/filepath" + "runtime" + + "github.com/magefile/mage/target" + "golang.org/x/xerrors" +) + +const url = "https://mirror.openshift.com/pub/openshift-v4/amd64/dependencies/rpms/4.10-beta/socat-1.7.3.2-2.el7.x86_64.rpm" + +// Fixtures downloads RPM files for unit tests +func Fixtures() error { + _, filePath, _, _ := runtime.Caller(0) + dir := filepath.Dir(filePath) + dst := filepath.Join(dir, path.Base(url)) + + // Download the file only when it is needed + if updated, err := target.Path(dst, filePath); err != nil { + return err + } else if !updated { + return nil + } + return downloadFile(url, dst) +} + +// downloadFile downloads a file from the given URL and saves it with the original filename +// TODO: move this function to a common package for Mage +func downloadFile(url, dst string) error { + slog.Info("Downloading...", slog.String("url", url)) + + // Send a GET request to the URL + resp, err := http.Get(url) + if err != nil { + return xerrors.Errorf("error sending GET request: %v", err) + } + defer resp.Body.Close() + + // Check if the response status code is OK (200) + if resp.StatusCode != http.StatusOK { + return xerrors.Errorf("unexpected status code: %d", resp.StatusCode) + } + + // Create a new file with the extracted filename + out, err := os.Create(dst) + if err != nil { + return xerrors.Errorf("error creating file: %v", err) + } + defer out.Close() + + // Copy the response body to the file + if _, err = io.Copy(out, resp.Body); err != nil { + return xerrors.Errorf("error writing to file: %v", err) + } + + return nil +} diff --git a/pkg/fanal/analyzer/secret/secret.go b/pkg/fanal/analyzer/secret/secret.go index d2627a840c1b..bdb3e96d1428 100644 --- a/pkg/fanal/analyzer/secret/secret.go +++ b/pkg/fanal/analyzer/secret/secret.go @@ -54,6 +54,9 @@ var ( ".gz", ".gzip", ".tar", + } + + allowedBinaries = []string{ ".pyc", } ) @@ -63,6 +66,10 @@ func init() { analyzer.RegisterAnalyzer(NewSecretAnalyzer(secret.Scanner{}, "")) } +func allowedBinary(filename string) bool { + return slices.Contains(allowedBinaries, filepath.Ext(filename)) +} + // SecretAnalyzer is an analyzer for secrets type SecretAnalyzer struct { scanner secret.Scanner @@ -96,7 +103,7 @@ func (a *SecretAnalyzer) Init(opt analyzer.AnalyzerOptions) error { func (a *SecretAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { // Do not scan binaries binary, err := utils.IsBinary(input.Content, input.Info.Size()) - if binary || err != nil { + if err != nil || (binary && !allowedBinary(input.FilePath)) { return nil, nil } @@ -104,12 +111,20 @@ func (a *SecretAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput log.WithPrefix("secret").Warn("The size of the scanned file is too large. It is recommended to use `--skip-files` for this file to avoid high memory consumption.", log.FilePath(input.FilePath), log.Int64("size (MB)", size/1048576)) } - content, err := io.ReadAll(input.Content) - if err != nil { - return nil, xerrors.Errorf("read error %s: %w", input.FilePath, err) - } + var content []byte - content = bytes.ReplaceAll(content, []byte("\r"), []byte("")) + if !binary { + content, err = io.ReadAll(input.Content) + if err != nil { + return nil, xerrors.Errorf("read error %s: %w", input.FilePath, err) + } + content = bytes.ReplaceAll(content, []byte("\r"), []byte("")) + } else { + content, err = utils.ExtractPrintableBytes(input.Content) + if err != nil { + return nil, xerrors.Errorf("binary read error %s: %w", input.FilePath, err) + } + } filePath := input.FilePath // Files extracted from the image have an empty input.Dir. @@ -122,6 +137,7 @@ func (a *SecretAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput result := a.scanner.Scan(secret.ScanArgs{ FilePath: filePath, Content: content, + Binary: binary, }) if len(result.Findings) == 0 { diff --git a/pkg/fanal/analyzer/secret/secret_test.go b/pkg/fanal/analyzer/secret/secret_test.go index 7cba1d137e8f..5187ebc341bf 100644 --- a/pkg/fanal/analyzer/secret/secret_test.go +++ b/pkg/fanal/analyzer/secret/secret_test.go @@ -95,6 +95,16 @@ func TestSecretAnalyzer(t *testing.T) { }, }, } + wantFindingGH_PAT := types.SecretFinding{ + RuleID: "github-fine-grained-pat", + Category: "GitHub", + Title: "GitHub Fine-grained personal access tokens", + Severity: "CRITICAL", + StartLine: 1, + EndLine: 1, + Match: "Binary file \"/testdata/secret.cpython-310.pyc\" matches a rule \"GitHub Fine-grained personal access tokens\"", + } + tests := []struct { name string configPath string @@ -153,6 +163,21 @@ func TestSecretAnalyzer(t *testing.T) { filePath: "testdata/binaryfile", want: nil, }, + { + name: "python binary file", + configPath: "testdata/skip-tests-config.yaml", + filePath: "testdata/secret.cpython-310.pyc", + want: &analyzer.AnalysisResult{ + Secrets: []types.Secret{ + { + FilePath: "/testdata/secret.cpython-310.pyc", + Findings: []types.SecretFinding{ + wantFindingGH_PAT, + }, + }, + }, + }, + }, } for _, tt := range tests { diff --git a/pkg/fanal/analyzer/secret/testdata/secret.cpython-310.pyc b/pkg/fanal/analyzer/secret/testdata/secret.cpython-310.pyc new file mode 100755 index 000000000000..2bb659e7eb21 Binary files /dev/null and b/pkg/fanal/analyzer/secret/testdata/secret.cpython-310.pyc differ diff --git a/pkg/fanal/applier/docker.go b/pkg/fanal/applier/docker.go index c1c21f236b22..6d1967497ab9 100644 --- a/pkg/fanal/applier/docker.go +++ b/pkg/fanal/applier/docker.go @@ -220,7 +220,7 @@ func ApplyLayers(layers []ftypes.BlobInfo) ftypes.ArtifactDetail { mergedLayer.Packages[i].InstalledFiles = installedFiles } - if mergedLayer.OS.Family != "" { + if mergedLayer.OS.Family != "" && pkg.Identifier.PURL == nil { mergedLayer.Packages[i].Identifier.PURL = newPURL(mergedLayer.OS.Family, types.Metadata{OS: &mergedLayer.OS}, pkg) } mergedLayer.Packages[i].Identifier.UID = dependency.UID("", pkg) diff --git a/pkg/fanal/artifact/artifact.go b/pkg/fanal/artifact/artifact.go index e431278f6fc2..b6034cb5ac63 100644 --- a/pkg/fanal/artifact/artifact.go +++ b/pkg/fanal/artifact/artifact.go @@ -28,6 +28,7 @@ type Option struct { AWSRegion string AWSEndpoint string FileChecksum bool // For SPDX + DetectionPriority types.DetectionPriority // Git repositories RepoBranch string @@ -50,6 +51,7 @@ func (o *Option) AnalyzerOptions() analyzer.AnalyzerOptions { FilePatterns: o.FilePatterns, Parallel: o.Parallel, DisabledAnalyzers: o.DisabledAnalyzers, + DetectionPriority: o.DetectionPriority, MisconfScannerOption: o.MisconfScannerOption, SecretScannerOption: o.SecretScannerOption, LicenseScannerOption: o.LicenseScannerOption, diff --git a/pkg/fanal/artifact/image/image_test.go b/pkg/fanal/artifact/image/image_test.go index cd7fea2df1e2..f7e80e3cf578 100644 --- a/pkg/fanal/artifact/image/image_test.go +++ b/pkg/fanal/artifact/image/image_test.go @@ -40,7 +40,7 @@ func TestArtifact_Inspect(t *testing.T) { Version: "3.2.0-r3", SrcName: "alpine-baselayout", SrcVersion: "3.2.0-r3", - Licenses: []string{"GPL-2.0"}, + Licenses: []string{"GPL-2.0-only"}, Digest: "sha1:8f373f5b329c3aaf136eb30c63a387661ee0f3d0", DependsOn: []string{ "busybox@1.31.1-r9", @@ -113,7 +113,7 @@ func TestArtifact_Inspect(t *testing.T) { Version: "2.10.4-r3", SrcName: "apk-tools", SrcVersion: "2.10.4-r3", - Licenses: []string{"GPL-2.0"}, + Licenses: []string{"GPL-2.0-only"}, Digest: "sha1:b15ad0c90e4493dfdc948d6b90a8e020da8936ef", DependsOn: []string{ "libcrypto1.1@1.1.1d-r3", @@ -132,7 +132,7 @@ func TestArtifact_Inspect(t *testing.T) { Version: "1.31.1-r9", SrcName: "busybox", SrcVersion: "1.31.1-r9", - Licenses: []string{"GPL-2.0"}, + Licenses: []string{"GPL-2.0-only"}, Digest: "sha1:a457703d71654811ea28d8d27a5cfc49ece27b34", DependsOn: []string{ "musl@1.1.24-r2", @@ -156,7 +156,7 @@ func TestArtifact_Inspect(t *testing.T) { SrcVersion: "20191127-r1", Licenses: []string{ "MPL-2.0", - "GPL-2.0", + "GPL-2.0-or-later", }, Arch: "x86_64", Digest: "sha1:3aeb8a90d7179d2a187782e980a964494e08c5fb", @@ -265,7 +265,7 @@ func TestArtifact_Inspect(t *testing.T) { Licenses: []string{ "MIT", "BSD-3-Clause", - "GPL-2.0", + "GPL-2.0-or-later", }, Digest: "sha1:6d3b45e79dbab444ca7cbfa59e2833203be6fb6a", DependsOn: []string{ @@ -287,7 +287,7 @@ func TestArtifact_Inspect(t *testing.T) { Version: "1.2.4-r0", SrcName: "pax-utils", SrcVersion: "1.2.4-r0", - Licenses: []string{"GPL-2.0"}, + Licenses: []string{"GPL-2.0-only"}, Digest: "sha1:d6147beb32bff803b5d9f83a3bec7ab319087185", DependsOn: []string{ "musl@1.1.24-r2", @@ -303,7 +303,7 @@ func TestArtifact_Inspect(t *testing.T) { Version: "1.31.1-r9", SrcName: "busybox", SrcVersion: "1.31.1-r9", - Licenses: []string{"GPL-2.0"}, + Licenses: []string{"GPL-2.0-only"}, Digest: "sha1:3b685152af320120ae8941c740d3376b54e43c10", DependsOn: []string{ "libtls-standalone@2.9.1-r0", @@ -352,17 +352,17 @@ func TestArtifact_Inspect(t *testing.T) { missingBlobsExpectation: cache.ArtifactCacheMissingBlobsExpectation{ Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", - BlobIDs: []string{"sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638"}, + BlobIDs: []string{"sha256:24a7af33784fabfedf01999d9e0dc456e8e1c1943f7d4421f7c05164026788a4"}, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ MissingArtifact: true, - MissingBlobIDs: []string{"sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638"}, + MissingBlobIDs: []string{"sha256:24a7af33784fabfedf01999d9e0dc456e8e1c1943f7d4421f7c05164026788a4"}, }, }, putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638", + BlobID: "sha256:24a7af33784fabfedf01999d9e0dc456e8e1c1943f7d4421f7c05164026788a4", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -429,7 +429,7 @@ func TestArtifact_Inspect(t *testing.T) { Name: "../../test/testdata/alpine-311.tar.gz", Type: artifact.TypeContainerImage, ID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", - BlobIDs: []string{"sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638"}, + BlobIDs: []string{"sha256:24a7af33784fabfedf01999d9e0dc456e8e1c1943f7d4421f7c05164026788a4"}, ImageMetadata: artifact.ImageMetadata{ ID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", DiffIDs: []string{ @@ -488,25 +488,25 @@ func TestArtifact_Inspect(t *testing.T) { Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", BlobIDs: []string{ - "sha256:a3eb0f92862bc742ea1e7ee875dd5623568ee17213ae7d29f05960eb1135fa6d", - "sha256:05b96a707dab6e1fcd9543f0df6a0e4cdf5c7e26272d7f6bc7ed2e1cf23afa9f", - "sha256:677cd3a664e4923227de2c2571c40b9956d99e4775b2e11ce8aa207842123119", - "sha256:e870ba0421bc71c046819f809c8369e98f59a2cde34961fdd429a2102da33c0c", + "sha256:4a26915356c961f038d5a7b7f73f24cd1eec53dcf6fdeecd39b310ddc066faec", + "sha256:e23e1d428a2a4ca9607cde5c556f744c7e9f3a1d3bfe835707c0fea107caf453", + "sha256:b8ae022ed4f8b8bf827c04a825c2e6998217581d44c0b28b59a4e66ca65bbaa5", + "sha256:8c51dcc708602d983f3f0507f0d26de609819c4391db92497639417e54378d11", }, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ MissingBlobIDs: []string{ - "sha256:a3eb0f92862bc742ea1e7ee875dd5623568ee17213ae7d29f05960eb1135fa6d", - "sha256:05b96a707dab6e1fcd9543f0df6a0e4cdf5c7e26272d7f6bc7ed2e1cf23afa9f", - "sha256:677cd3a664e4923227de2c2571c40b9956d99e4775b2e11ce8aa207842123119", - "sha256:e870ba0421bc71c046819f809c8369e98f59a2cde34961fdd429a2102da33c0c", + "sha256:4a26915356c961f038d5a7b7f73f24cd1eec53dcf6fdeecd39b310ddc066faec", + "sha256:e23e1d428a2a4ca9607cde5c556f744c7e9f3a1d3bfe835707c0fea107caf453", + "sha256:b8ae022ed4f8b8bf827c04a825c2e6998217581d44c0b28b59a4e66ca65bbaa5", + "sha256:8c51dcc708602d983f3f0507f0d26de609819c4391db92497639417e54378d11", }, }, }, putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:a3eb0f92862bc742ea1e7ee875dd5623568ee17213ae7d29f05960eb1135fa6d", + BlobID: "sha256:4a26915356c961f038d5a7b7f73f24cd1eec53dcf6fdeecd39b310ddc066faec", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -567,7 +567,7 @@ func TestArtifact_Inspect(t *testing.T) { Type: types.LicenseTypeDpkg, FilePath: "usr/share/doc/base-files/copyright", Findings: []types.LicenseFinding{ - {Name: "GPL-3.0"}, + {Name: "GPL-2.0-or-later"}, }, PkgName: "base-files", }, @@ -575,7 +575,8 @@ func TestArtifact_Inspect(t *testing.T) { Type: types.LicenseTypeDpkg, FilePath: "usr/share/doc/ca-certificates/copyright", Findings: []types.LicenseFinding{ - {Name: "GPL-2.0"}, + {Name: "GPL-2.0-or-later"}, + {Name: "GPL-2.0-only"}, {Name: "MPL-2.0"}, }, PkgName: "ca-certificates", @@ -584,7 +585,7 @@ func TestArtifact_Inspect(t *testing.T) { Type: types.LicenseTypeDpkg, FilePath: "usr/share/doc/netbase/copyright", Findings: []types.LicenseFinding{ - {Name: "GPL-2.0"}, + {Name: "GPL-2.0-only"}, }, PkgName: "netbase", }, @@ -594,7 +595,7 @@ func TestArtifact_Inspect(t *testing.T) { }, { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:05b96a707dab6e1fcd9543f0df6a0e4cdf5c7e26272d7f6bc7ed2e1cf23afa9f", + BlobID: "sha256:e23e1d428a2a4ca9607cde5c556f744c7e9f3a1d3bfe835707c0fea107caf453", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -655,8 +656,8 @@ func TestArtifact_Inspect(t *testing.T) { Type: types.LicenseTypeDpkg, FilePath: "usr/share/doc/libc6/copyright", Findings: []types.LicenseFinding{ - {Name: "LGPL-2.1"}, - {Name: "GPL-2.0"}, + {Name: "LGPL-2.1-only"}, + {Name: "GPL-2.0-only"}, }, PkgName: "libc6", }, @@ -690,7 +691,7 @@ func TestArtifact_Inspect(t *testing.T) { }, { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:677cd3a664e4923227de2c2571c40b9956d99e4775b2e11ce8aa207842123119", + BlobID: "sha256:b8ae022ed4f8b8bf827c04a825c2e6998217581d44c0b28b59a4e66ca65bbaa5", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -898,7 +899,7 @@ func TestArtifact_Inspect(t *testing.T) { }, { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:e870ba0421bc71c046819f809c8369e98f59a2cde34961fdd429a2102da33c0c", + BlobID: "sha256:8c51dcc708602d983f3f0507f0d26de609819c4391db92497639417e54378d11", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -1761,10 +1762,10 @@ func TestArtifact_Inspect(t *testing.T) { Type: artifact.TypeContainerImage, ID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", BlobIDs: []string{ - "sha256:a3eb0f92862bc742ea1e7ee875dd5623568ee17213ae7d29f05960eb1135fa6d", - "sha256:05b96a707dab6e1fcd9543f0df6a0e4cdf5c7e26272d7f6bc7ed2e1cf23afa9f", - "sha256:677cd3a664e4923227de2c2571c40b9956d99e4775b2e11ce8aa207842123119", - "sha256:e870ba0421bc71c046819f809c8369e98f59a2cde34961fdd429a2102da33c0c", + "sha256:4a26915356c961f038d5a7b7f73f24cd1eec53dcf6fdeecd39b310ddc066faec", + "sha256:e23e1d428a2a4ca9607cde5c556f744c7e9f3a1d3bfe835707c0fea107caf453", + "sha256:b8ae022ed4f8b8bf827c04a825c2e6998217581d44c0b28b59a4e66ca65bbaa5", + "sha256:8c51dcc708602d983f3f0507f0d26de609819c4391db92497639417e54378d11", }, ImageMetadata: artifact.ImageMetadata{ ID: "sha256:58701fd185bda36cab0557bb6438661831267aa4a9e0b54211c4d5317a48aff4", @@ -1858,25 +1859,25 @@ func TestArtifact_Inspect(t *testing.T) { Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", BlobIDs: []string{ - "sha256:f46989447d5a1357f6b2427b86ca2af827dd380dbd7fbf392d2abf9a5d457323", - "sha256:487a6fb0914825c8fb9f3a0662a608039bd5a8b6488d76b9de2eb1a684e908e1", - "sha256:a23b05a9c95939a0d30d6b4f6c25393473252bde47b2daa03258c27461367509", - "sha256:47226d3c41a3ffd99dacdbcd2b197a7394ee8948270710ee035181427f88dfab", + "sha256:139bc12e936e0c46090b9380c4a29456d3ad8d8abd50c7bdc6160018cd887462", + "sha256:c491838e70ff0fcfdd0605af1ba84e86d6958c0846b16c52a84e06bb344e8e8d", + "sha256:25e775ef81049a93eafd865447b0b79da9e9956ab74bc02b5916eaea21c87c7c", + "sha256:a8a4798a22b65739cda9ca99ddb2cd86125c1dd86df6fc3971f937a0ff5b9ec3", }, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ MissingBlobIDs: []string{ - "sha256:f46989447d5a1357f6b2427b86ca2af827dd380dbd7fbf392d2abf9a5d457323", - "sha256:487a6fb0914825c8fb9f3a0662a608039bd5a8b6488d76b9de2eb1a684e908e1", - "sha256:a23b05a9c95939a0d30d6b4f6c25393473252bde47b2daa03258c27461367509", - "sha256:47226d3c41a3ffd99dacdbcd2b197a7394ee8948270710ee035181427f88dfab", + "sha256:139bc12e936e0c46090b9380c4a29456d3ad8d8abd50c7bdc6160018cd887462", + "sha256:c491838e70ff0fcfdd0605af1ba84e86d6958c0846b16c52a84e06bb344e8e8d", + "sha256:25e775ef81049a93eafd865447b0b79da9e9956ab74bc02b5916eaea21c87c7c", + "sha256:a8a4798a22b65739cda9ca99ddb2cd86125c1dd86df6fc3971f937a0ff5b9ec3", }, }, }, putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:f46989447d5a1357f6b2427b86ca2af827dd380dbd7fbf392d2abf9a5d457323", + BlobID: "sha256:139bc12e936e0c46090b9380c4a29456d3ad8d8abd50c7bdc6160018cd887462", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -1887,7 +1888,7 @@ func TestArtifact_Inspect(t *testing.T) { }, { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:487a6fb0914825c8fb9f3a0662a608039bd5a8b6488d76b9de2eb1a684e908e1", + BlobID: "sha256:c491838e70ff0fcfdd0605af1ba84e86d6958c0846b16c52a84e06bb344e8e8d", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -1898,7 +1899,7 @@ func TestArtifact_Inspect(t *testing.T) { }, { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:a23b05a9c95939a0d30d6b4f6c25393473252bde47b2daa03258c27461367509", + BlobID: "sha256:25e775ef81049a93eafd865447b0b79da9e9956ab74bc02b5916eaea21c87c7c", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -1910,7 +1911,7 @@ func TestArtifact_Inspect(t *testing.T) { }, { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:47226d3c41a3ffd99dacdbcd2b197a7394ee8948270710ee035181427f88dfab", + BlobID: "sha256:a8a4798a22b65739cda9ca99ddb2cd86125c1dd86df6fc3971f937a0ff5b9ec3", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -1926,10 +1927,10 @@ func TestArtifact_Inspect(t *testing.T) { Type: artifact.TypeContainerImage, ID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", BlobIDs: []string{ - "sha256:f46989447d5a1357f6b2427b86ca2af827dd380dbd7fbf392d2abf9a5d457323", - "sha256:487a6fb0914825c8fb9f3a0662a608039bd5a8b6488d76b9de2eb1a684e908e1", - "sha256:a23b05a9c95939a0d30d6b4f6c25393473252bde47b2daa03258c27461367509", - "sha256:47226d3c41a3ffd99dacdbcd2b197a7394ee8948270710ee035181427f88dfab", + "sha256:139bc12e936e0c46090b9380c4a29456d3ad8d8abd50c7bdc6160018cd887462", + "sha256:c491838e70ff0fcfdd0605af1ba84e86d6958c0846b16c52a84e06bb344e8e8d", + "sha256:25e775ef81049a93eafd865447b0b79da9e9956ab74bc02b5916eaea21c87c7c", + "sha256:a8a4798a22b65739cda9ca99ddb2cd86125c1dd86df6fc3971f937a0ff5b9ec3", }, ImageMetadata: artifact.ImageMetadata{ ID: "sha256:58701fd185bda36cab0557bb6438661831267aa4a9e0b54211c4d5317a48aff4", @@ -2012,7 +2013,7 @@ func TestArtifact_Inspect(t *testing.T) { missingBlobsExpectation: cache.ArtifactCacheMissingBlobsExpectation{ Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", - BlobIDs: []string{"sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638"}, + BlobIDs: []string{"sha256:24a7af33784fabfedf01999d9e0dc456e8e1c1943f7d4421f7c05164026788a4"}, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ Err: xerrors.New("MissingBlobs failed"), @@ -2026,16 +2027,16 @@ func TestArtifact_Inspect(t *testing.T) { missingBlobsExpectation: cache.ArtifactCacheMissingBlobsExpectation{ Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", - BlobIDs: []string{"sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638"}, + BlobIDs: []string{"sha256:24a7af33784fabfedf01999d9e0dc456e8e1c1943f7d4421f7c05164026788a4"}, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ - MissingBlobIDs: []string{"sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638"}, + MissingBlobIDs: []string{"sha256:24a7af33784fabfedf01999d9e0dc456e8e1c1943f7d4421f7c05164026788a4"}, }, }, putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638", + BlobID: "sha256:24a7af33784fabfedf01999d9e0dc456e8e1c1943f7d4421f7c05164026788a4", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -2095,18 +2096,18 @@ func TestArtifact_Inspect(t *testing.T) { Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", BlobIDs: []string{ - "sha256:a3eb0f92862bc742ea1e7ee875dd5623568ee17213ae7d29f05960eb1135fa6d", - "sha256:05b96a707dab6e1fcd9543f0df6a0e4cdf5c7e26272d7f6bc7ed2e1cf23afa9f", - "sha256:677cd3a664e4923227de2c2571c40b9956d99e4775b2e11ce8aa207842123119", - "sha256:e870ba0421bc71c046819f809c8369e98f59a2cde34961fdd429a2102da33c0c", + "sha256:4a26915356c961f038d5a7b7f73f24cd1eec53dcf6fdeecd39b310ddc066faec", + "sha256:e23e1d428a2a4ca9607cde5c556f744c7e9f3a1d3bfe835707c0fea107caf453", + "sha256:b8ae022ed4f8b8bf827c04a825c2e6998217581d44c0b28b59a4e66ca65bbaa5", + "sha256:8c51dcc708602d983f3f0507f0d26de609819c4391db92497639417e54378d11", }, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ MissingBlobIDs: []string{ - "sha256:a3eb0f92862bc742ea1e7ee875dd5623568ee17213ae7d29f05960eb1135fa6d", - "sha256:05b96a707dab6e1fcd9543f0df6a0e4cdf5c7e26272d7f6bc7ed2e1cf23afa9f", - "sha256:677cd3a664e4923227de2c2571c40b9956d99e4775b2e11ce8aa207842123119", - "sha256:e870ba0421bc71c046819f809c8369e98f59a2cde34961fdd429a2102da33c0c", + "sha256:4a26915356c961f038d5a7b7f73f24cd1eec53dcf6fdeecd39b310ddc066faec", + "sha256:e23e1d428a2a4ca9607cde5c556f744c7e9f3a1d3bfe835707c0fea107caf453", + "sha256:b8ae022ed4f8b8bf827c04a825c2e6998217581d44c0b28b59a4e66ca65bbaa5", + "sha256:8c51dcc708602d983f3f0507f0d26de609819c4391db92497639417e54378d11", }, }, }, @@ -2114,7 +2115,7 @@ func TestArtifact_Inspect(t *testing.T) { { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:a3eb0f92862bc742ea1e7ee875dd5623568ee17213ae7d29f05960eb1135fa6d", + BlobID: "sha256:4a26915356c961f038d5a7b7f73f24cd1eec53dcf6fdeecd39b310ddc066faec", BlobInfoAnything: true, }, @@ -2125,7 +2126,7 @@ func TestArtifact_Inspect(t *testing.T) { { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:05b96a707dab6e1fcd9543f0df6a0e4cdf5c7e26272d7f6bc7ed2e1cf23afa9f", + BlobID: "sha256:e23e1d428a2a4ca9607cde5c556f744c7e9f3a1d3bfe835707c0fea107caf453", BlobInfoAnything: true, }, @@ -2136,7 +2137,7 @@ func TestArtifact_Inspect(t *testing.T) { { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:677cd3a664e4923227de2c2571c40b9956d99e4775b2e11ce8aa207842123119", + BlobID: "sha256:b8ae022ed4f8b8bf827c04a825c2e6998217581d44c0b28b59a4e66ca65bbaa5", BlobInfoAnything: true, }, @@ -2147,7 +2148,7 @@ func TestArtifact_Inspect(t *testing.T) { { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:e870ba0421bc71c046819f809c8369e98f59a2cde34961fdd429a2102da33c0c", + BlobID: "sha256:8c51dcc708602d983f3f0507f0d26de609819c4391db92497639417e54378d11", BlobInfoAnything: true, }, @@ -2164,17 +2165,17 @@ func TestArtifact_Inspect(t *testing.T) { missingBlobsExpectation: cache.ArtifactCacheMissingBlobsExpectation{ Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", - BlobIDs: []string{"sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638"}, + BlobIDs: []string{"sha256:24a7af33784fabfedf01999d9e0dc456e8e1c1943f7d4421f7c05164026788a4"}, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ MissingArtifact: true, - MissingBlobIDs: []string{"sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638"}, + MissingBlobIDs: []string{"sha256:24a7af33784fabfedf01999d9e0dc456e8e1c1943f7d4421f7c05164026788a4"}, }, }, putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638", + BlobID: "sha256:24a7af33784fabfedf01999d9e0dc456e8e1c1943f7d4421f7c05164026788a4", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", diff --git a/pkg/fanal/artifact/image/remote_sbom.go b/pkg/fanal/artifact/image/remote_sbom.go index 37303a9f7b05..d07d9cafe3ac 100644 --- a/pkg/fanal/artifact/image/remote_sbom.go +++ b/pkg/fanal/artifact/image/remote_sbom.go @@ -87,10 +87,6 @@ func (a Artifact) inspectOCIReferrerSBOM(ctx context.Context) (artifact.Referenc func (a Artifact) parseReferrer(ctx context.Context, repo string, desc v1.Descriptor) (artifact.Reference, error) { const fileName string = "referrer.sbom" repoName := fmt.Sprintf("%s@%s", repo, desc.Digest) - referrer, err := oci.NewArtifact(repoName, true, a.artifactOption.ImageOption.RegistryOptions) - if err != nil { - return artifact.Reference{}, xerrors.Errorf("OCI error: %w", err) - } tmpDir, err := os.MkdirTemp("", "trivy-sbom-*") if err != nil { @@ -99,9 +95,11 @@ func (a Artifact) parseReferrer(ctx context.Context, repo string, desc v1.Descri defer os.RemoveAll(tmpDir) // Download SBOM to local filesystem + referrer := oci.NewArtifact(repoName, a.artifactOption.ImageOption.RegistryOptions) if err = referrer.Download(ctx, tmpDir, oci.DownloadOption{ MediaType: desc.ArtifactType, Filename: fileName, + Quiet: true, }); err != nil { return artifact.Reference{}, xerrors.Errorf("SBOM download error: %w", err) } diff --git a/pkg/fanal/artifact/local/fs_test.go b/pkg/fanal/artifact/local/fs_test.go index d27b5ffb4366..dbef68e893bb 100644 --- a/pkg/fanal/artifact/local/fs_test.go +++ b/pkg/fanal/artifact/local/fs_test.go @@ -47,7 +47,7 @@ func TestArtifact_Inspect(t *testing.T) { }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:e480047f53bccb8a9107a424fab43452dc59df641022a300d34326639254a0cf", + BlobID: "sha256:5ba63074e071e3f0247d03dd7e544b6a75f7224ee238618482c490b36f4792dc", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, OS: types.OS{ @@ -82,9 +82,9 @@ func TestArtifact_Inspect(t *testing.T) { want: artifact.Reference{ Name: "host", Type: artifact.TypeFilesystem, - ID: "sha256:e480047f53bccb8a9107a424fab43452dc59df641022a300d34326639254a0cf", + ID: "sha256:5ba63074e071e3f0247d03dd7e544b6a75f7224ee238618482c490b36f4792dc", BlobIDs: []string{ - "sha256:e480047f53bccb8a9107a424fab43452dc59df641022a300d34326639254a0cf", + "sha256:5ba63074e071e3f0247d03dd7e544b6a75f7224ee238618482c490b36f4792dc", }, }, }, @@ -102,7 +102,7 @@ func TestArtifact_Inspect(t *testing.T) { }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:7db98974b2231d3e25f4890008c4d42f6f26a7da5a8aba99e954dec97f050bd6", + BlobID: "sha256:649ddb291d142363aafcf9e9cf8a6e32dc0a6ae5a95ab43d09b8201d86ed8f7a", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, }, @@ -112,9 +112,9 @@ func TestArtifact_Inspect(t *testing.T) { want: artifact.Reference{ Name: "host", Type: artifact.TypeFilesystem, - ID: "sha256:7db98974b2231d3e25f4890008c4d42f6f26a7da5a8aba99e954dec97f050bd6", + ID: "sha256:649ddb291d142363aafcf9e9cf8a6e32dc0a6ae5a95ab43d09b8201d86ed8f7a", BlobIDs: []string{ - "sha256:7db98974b2231d3e25f4890008c4d42f6f26a7da5a8aba99e954dec97f050bd6", + "sha256:649ddb291d142363aafcf9e9cf8a6e32dc0a6ae5a95ab43d09b8201d86ed8f7a", }, }, }, @@ -125,7 +125,7 @@ func TestArtifact_Inspect(t *testing.T) { }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:e480047f53bccb8a9107a424fab43452dc59df641022a300d34326639254a0cf", + BlobID: "sha256:5ba63074e071e3f0247d03dd7e544b6a75f7224ee238618482c490b36f4792dc", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, OS: types.OS{ @@ -175,7 +175,7 @@ func TestArtifact_Inspect(t *testing.T) { }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:c91a594202ba114ce4d067d9cac40b545c7ba2a96520c9ca8050b437020c3ab9", + BlobID: "sha256:00e49bf14e0a8c15b2d611d8e5c231276f1e10f22b3307177e513605fd18d807", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Applications: []types.Application{ @@ -203,9 +203,9 @@ func TestArtifact_Inspect(t *testing.T) { want: artifact.Reference{ Name: "testdata/requirements.txt", Type: artifact.TypeFilesystem, - ID: "sha256:c91a594202ba114ce4d067d9cac40b545c7ba2a96520c9ca8050b437020c3ab9", + ID: "sha256:00e49bf14e0a8c15b2d611d8e5c231276f1e10f22b3307177e513605fd18d807", BlobIDs: []string{ - "sha256:c91a594202ba114ce4d067d9cac40b545c7ba2a96520c9ca8050b437020c3ab9", + "sha256:00e49bf14e0a8c15b2d611d8e5c231276f1e10f22b3307177e513605fd18d807", }, }, }, @@ -216,7 +216,7 @@ func TestArtifact_Inspect(t *testing.T) { }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:c91a594202ba114ce4d067d9cac40b545c7ba2a96520c9ca8050b437020c3ab9", + BlobID: "sha256:00e49bf14e0a8c15b2d611d8e5c231276f1e10f22b3307177e513605fd18d807", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Applications: []types.Application{ @@ -244,9 +244,9 @@ func TestArtifact_Inspect(t *testing.T) { want: artifact.Reference{ Name: "testdata/requirements.txt", Type: artifact.TypeFilesystem, - ID: "sha256:c91a594202ba114ce4d067d9cac40b545c7ba2a96520c9ca8050b437020c3ab9", + ID: "sha256:00e49bf14e0a8c15b2d611d8e5c231276f1e10f22b3307177e513605fd18d807", BlobIDs: []string{ - "sha256:c91a594202ba114ce4d067d9cac40b545c7ba2a96520c9ca8050b437020c3ab9", + "sha256:00e49bf14e0a8c15b2d611d8e5c231276f1e10f22b3307177e513605fd18d807", }, }, }, @@ -299,15 +299,6 @@ func TestTerraformMisconfigurationScan(t *testing.T) { fields: fields{ dir: "./testdata/misconfig/terraform/single-failure", }, - artifactOpt: artifact.Option{ - MisconfScannerOption: misconf.ScannerOption{ - RegoOnly: true, - Namespaces: []string{"user"}, - PolicyPaths: []string{"./testdata/misconfig/terraform/rego"}, - DisableEmbeddedPolicies: true, - DisableEmbeddedLibraries: true, - }, - }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ BlobIDAnything: true, @@ -341,9 +332,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/terraform/single-failure", Type: artifact.TypeFilesystem, - ID: "sha256:5e4ca8ffaa89f49c69acfbac6d7cebb0a372b07d4c4c9876f9a58043c8ee56e9", + ID: "sha256:4f2a334086f1d175c0ee57cd4220f20b187b456dc36bbe39a63c42b5637b2179", BlobIDs: []string{ - "sha256:5e4ca8ffaa89f49c69acfbac6d7cebb0a372b07d4c4c9876f9a58043c8ee56e9", + "sha256:4f2a334086f1d175c0ee57cd4220f20b187b456dc36bbe39a63c42b5637b2179", }, }, }, @@ -352,15 +343,6 @@ func TestTerraformMisconfigurationScan(t *testing.T) { fields: fields{ dir: "./testdata/misconfig/terraform/multiple-failures", }, - artifactOpt: artifact.Option{ - MisconfScannerOption: misconf.ScannerOption{ - RegoOnly: true, - Namespaces: []string{"user"}, - PolicyPaths: []string{"./testdata/misconfig/terraform/rego"}, - DisableEmbeddedPolicies: true, - DisableEmbeddedLibraries: true, - }, - }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ BlobIDAnything: true, @@ -426,9 +408,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/terraform/multiple-failures", Type: artifact.TypeFilesystem, - ID: "sha256:1832cedd0d8baeb172c7297acb56417c0dec454c280a79ee2a8f413e1ffca192", + ID: "sha256:ff7a84de97729e169c94107a89bc9da88f5ecf94873cdbd9bf0844e1af5f5b30", BlobIDs: []string{ - "sha256:1832cedd0d8baeb172c7297acb56417c0dec454c280a79ee2a8f413e1ffca192", + "sha256:ff7a84de97729e169c94107a89bc9da88f5ecf94873cdbd9bf0844e1af5f5b30", }, }, }, @@ -437,13 +419,6 @@ func TestTerraformMisconfigurationScan(t *testing.T) { fields: fields{ dir: "./testdata/misconfig/terraform/no-results", }, - artifactOpt: artifact.Option{ - MisconfScannerOption: misconf.ScannerOption{ - RegoOnly: true, - Namespaces: []string{"user"}, - PolicyPaths: []string{"./testdata/misconfig/terraform/rego"}, - }, - }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ BlobIDAnything: true, @@ -456,9 +431,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/terraform/no-results", Type: artifact.TypeFilesystem, - ID: "sha256:00e01cf7bf052dbf8f02739d281675eff16f5ddf004256472cdddb55cf974fd6", + ID: "sha256:06406e9bb7ba09d8d24c73c0995ac3b94fc1d6ce059e5a45418d7c0ab2b6dca4", BlobIDs: []string{ - "sha256:00e01cf7bf052dbf8f02739d281675eff16f5ddf004256472cdddb55cf974fd6", + "sha256:06406e9bb7ba09d8d24c73c0995ac3b94fc1d6ce059e5a45418d7c0ab2b6dca4", }, }, }, @@ -467,15 +442,6 @@ func TestTerraformMisconfigurationScan(t *testing.T) { fields: fields{ dir: "./testdata/misconfig/terraform/passed", }, - artifactOpt: artifact.Option{ - MisconfScannerOption: misconf.ScannerOption{ - RegoOnly: true, - Namespaces: []string{"user"}, - PolicyPaths: []string{"./testdata/misconfig/terraform/rego"}, - DisableEmbeddedPolicies: true, - DisableEmbeddedLibraries: true, - }, - }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ BlobIDAnything: true, @@ -505,9 +471,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/terraform/passed", Type: artifact.TypeFilesystem, - ID: "sha256:9ee9fe14c3fcff202bc8c72f9e20a3035442ae394ec5cd201e1cb29d2111b4e1", + ID: "sha256:107251e6ee7312c8c27ff04e71dd943b92021777c575971809f57b60bf41bba4", BlobIDs: []string{ - "sha256:9ee9fe14c3fcff202bc8c72f9e20a3035442ae394ec5cd201e1cb29d2111b4e1", + "sha256:107251e6ee7312c8c27ff04e71dd943b92021777c575971809f57b60bf41bba4", }, }, }, @@ -516,15 +482,6 @@ func TestTerraformMisconfigurationScan(t *testing.T) { fields: fields{ dir: "./testdata/misconfig/terraform/busted-relative-paths/child/main.tf", }, - artifactOpt: artifact.Option{ - MisconfScannerOption: misconf.ScannerOption{ - RegoOnly: true, - Namespaces: []string{"user"}, - PolicyPaths: []string{"./testdata/misconfig/terraform/rego"}, - DisableEmbeddedPolicies: true, - DisableEmbeddedLibraries: true, - }, - }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ BlobIDAnything: true, @@ -571,9 +528,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/terraform/busted-relative-paths/child/main.tf", Type: artifact.TypeFilesystem, - ID: "sha256:c3e6c9e68cd7a9900cd8aa690e1d5af174ddcc00ddb9438a858c3e329b9ea8f4", + ID: "sha256:f2f07f41dbd6816d41ce6f28b3922fcedab611b8602d95e328571afd5c53b31d", BlobIDs: []string{ - "sha256:c3e6c9e68cd7a9900cd8aa690e1d5af174ddcc00ddb9438a858c3e329b9ea8f4", + "sha256:f2f07f41dbd6816d41ce6f28b3922fcedab611b8602d95e328571afd5c53b31d", }, }, }, @@ -584,12 +541,7 @@ func TestTerraformMisconfigurationScan(t *testing.T) { }, artifactOpt: artifact.Option{ MisconfScannerOption: misconf.ScannerOption{ - RegoOnly: true, - Namespaces: []string{"user"}, - PolicyPaths: []string{"./testdata/misconfig/terraform/rego"}, - TerraformTFVars: []string{"./testdata/misconfig/terraform/tfvar-outside/main.tfvars"}, - TfExcludeDownloaded: true, - DisableEmbeddedPolicies: true, + TerraformTFVars: []string{"./testdata/misconfig/terraform/tfvar-outside/main.tfvars"}, }, }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ @@ -621,9 +573,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/terraform/tfvar-outside/tf", Type: artifact.TypeFilesystem, - ID: "sha256:9ee9fe14c3fcff202bc8c72f9e20a3035442ae394ec5cd201e1cb29d2111b4e1", + ID: "sha256:107251e6ee7312c8c27ff04e71dd943b92021777c575971809f57b60bf41bba4", BlobIDs: []string{ - "sha256:9ee9fe14c3fcff202bc8c72f9e20a3035442ae394ec5cd201e1cb29d2111b4e1", + "sha256:107251e6ee7312c8c27ff04e71dd943b92021777c575971809f57b60bf41bba4", }, }, }, @@ -632,14 +584,6 @@ func TestTerraformMisconfigurationScan(t *testing.T) { fields: fields{ dir: "./testdata/misconfig/terraform/relative-paths/child", }, - artifactOpt: artifact.Option{ - MisconfScannerOption: misconf.ScannerOption{ - RegoOnly: true, - Namespaces: []string{"user"}, - PolicyPaths: []string{"./testdata/misconfig/terraform/rego"}, - DisableEmbeddedPolicies: true, - }, - }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ BlobIDAnything: true, @@ -711,9 +655,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/terraform/relative-paths/child", Type: artifact.TypeFilesystem, - ID: "sha256:44d28d3fc115f1f8dd3f8978c7e9432ba255dd82661c3810178276897e2d8fb7", + ID: "sha256:f04c37d8e5300ce9344c795c2d4e0bb1dbef251b15538a6e0c11d6d9a86664d1", BlobIDs: []string{ - "sha256:44d28d3fc115f1f8dd3f8978c7e9432ba255dd82661c3810178276897e2d8fb7", + "sha256:f04c37d8e5300ce9344c795c2d4e0bb1dbef251b15538a6e0c11d6d9a86664d1", }, }, }, @@ -726,6 +670,8 @@ func TestTerraformMisconfigurationScan(t *testing.T) { types.SystemFileFilteringPostHandler, } tt.artifactOpt.MisconfScannerOption.DisableEmbeddedPolicies = true + tt.artifactOpt.MisconfScannerOption.Namespaces = []string{"user"} + tt.artifactOpt.MisconfScannerOption.PolicyPaths = []string{"./testdata/misconfig/terraform/rego"} a, err := NewArtifact(tt.fields.dir, c, walker.NewFS(), tt.artifactOpt) require.NoError(t, err) @@ -830,9 +776,9 @@ func TestTerraformPlanSnapshotMisconfScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/terraformplan/snapshots/single-failure", Type: artifact.TypeFilesystem, - ID: "sha256:39babdcb854331c58dc1e20c20dc67c2b4bda23895bf2861cc72d187de2bc716", + ID: "sha256:c21e15d7d0cfe7c1ef1e1933b443f781d1411b864500431302a1e45fe0950529", BlobIDs: []string{ - "sha256:39babdcb854331c58dc1e20c20dc67c2b4bda23895bf2861cc72d187de2bc716", + "sha256:c21e15d7d0cfe7c1ef1e1933b443f781d1411b864500431302a1e45fe0950529", }, }, }, @@ -906,9 +852,9 @@ func TestTerraformPlanSnapshotMisconfScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/terraformplan/snapshots/multiple-failures", Type: artifact.TypeFilesystem, - ID: "sha256:32debc3c2857404ceeec978a938609a4b20f1c53c304603815955885377f286c", + ID: "sha256:800c9ce07be36c7f4d1a4876ecfaaa77c1d90b15f43c58eaf52ea27670afcc42", BlobIDs: []string{ - "sha256:32debc3c2857404ceeec978a938609a4b20f1c53c304603815955885377f286c", + "sha256:800c9ce07be36c7f4d1a4876ecfaaa77c1d90b15f43c58eaf52ea27670afcc42", }, }, }, @@ -946,9 +892,9 @@ func TestTerraformPlanSnapshotMisconfScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/terraformplan/snapshots/passed", Type: artifact.TypeFilesystem, - ID: "sha256:2432b64e4583676ec1e94903d31c4faf79a1e0f4ed49bf24aef2bf9b44517ca2", + ID: "sha256:3d90bb96d2dc0af277ab0ce28972670eb81968d00775d1e92edce54ae2d165c0", BlobIDs: []string{ - "sha256:2432b64e4583676ec1e94903d31c4faf79a1e0f4ed49bf24aef2bf9b44517ca2", + "sha256:3d90bb96d2dc0af277ab0ce28972670eb81968d00775d1e92edce54ae2d165c0", }, }, }, @@ -972,9 +918,8 @@ func TestTerraformPlanSnapshotMisconfScan(t *testing.T) { types.SystemFileFilteringPostHandler, }, MisconfScannerOption: misconf.ScannerOption{ - RegoOnly: true, - DisableEmbeddedPolicies: true, - + RegoOnly: true, + DisableEmbeddedPolicies: true, DisableEmbeddedLibraries: false, Namespaces: []string{"user"}, PolicyPaths: []string{tmpDir}, @@ -983,7 +928,6 @@ func TestTerraformPlanSnapshotMisconfScan(t *testing.T) { SkipFiles: []string{"*.tf"}, }, } - a, err := NewArtifact(tt.fields.dir, c, walker.NewFS(), opt) require.NoError(t, err) @@ -1015,7 +959,6 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { RegoOnly: true, Namespaces: []string{"user"}, PolicyPaths: []string{"./testdata/misconfig/cloudformation/single-failure/rego"}, - DisableEmbeddedPolicies: true, DisableEmbeddedLibraries: true, }, }, @@ -1045,7 +988,7 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { }, CauseMetadata: types.CauseMetadata{ Resource: "main.yaml:3-6", - Provider: "Generic", + Provider: "Cloud", Service: "general", StartLine: 3, EndLine: 6, @@ -1061,9 +1004,9 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/cloudformation/single-failure/src", Type: artifact.TypeFilesystem, - ID: "sha256:43bb7ce5253686cf96a68e6d4bd30c33e163019ae2893968602da9e5e86b1aab", + ID: "sha256:bd481a673eb07ed7b51e1ff2a6e7aca08b433d11288eb9f5e9aa2d2f482a0c16", BlobIDs: []string{ - "sha256:43bb7ce5253686cf96a68e6d4bd30c33e163019ae2893968602da9e5e86b1aab", + "sha256:bd481a673eb07ed7b51e1ff2a6e7aca08b433d11288eb9f5e9aa2d2f482a0c16", }, }, }, @@ -1077,7 +1020,6 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { RegoOnly: true, Namespaces: []string{"user"}, PolicyPaths: []string{"./testdata/misconfig/cloudformation/multiple-failures/rego"}, - DisableEmbeddedPolicies: true, DisableEmbeddedLibraries: true, }, }, @@ -1107,7 +1049,7 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { }, CauseMetadata: types.CauseMetadata{ Resource: "main.yaml:2-5", - Provider: "Generic", + Provider: "Cloud", Service: "general", StartLine: 2, EndLine: 5, @@ -1129,7 +1071,7 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { }, CauseMetadata: types.CauseMetadata{ Resource: "main.yaml:6-9", - Provider: "Generic", + Provider: "Cloud", Service: "general", StartLine: 6, EndLine: 9, @@ -1145,9 +1087,9 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/cloudformation/multiple-failures/src", Type: artifact.TypeFilesystem, - ID: "sha256:4609d64f57187099483e76a979d2f74862a092a5f42a9945e6f1242c372a811c", + ID: "sha256:c25676d23114b9c912067d45285cd9e662cefae5e3cc82c40f67df5fee39f92a", BlobIDs: []string{ - "sha256:4609d64f57187099483e76a979d2f74862a092a5f42a9945e6f1242c372a811c", + "sha256:c25676d23114b9c912067d45285cd9e662cefae5e3cc82c40f67df5fee39f92a", }, }, }, @@ -1161,7 +1103,6 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { RegoOnly: true, Namespaces: []string{"user"}, PolicyPaths: []string{"./testdata/misconfig/cloudformation/no-results/rego"}, - DisableEmbeddedPolicies: true, DisableEmbeddedLibraries: true, }, }, @@ -1177,9 +1118,9 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/cloudformation/no-results/src", Type: artifact.TypeFilesystem, - ID: "sha256:a3d40c3290bf45542f9c0aa364f2c16aca06cdccb4868a2e442b7e6a56c6157a", + ID: "sha256:522b19ad182f50b7b04217831c914df52c2d2eb1bdddb02eb9cd2b4e14c9a32b", BlobIDs: []string{ - "sha256:a3d40c3290bf45542f9c0aa364f2c16aca06cdccb4868a2e442b7e6a56c6157a", + "sha256:522b19ad182f50b7b04217831c914df52c2d2eb1bdddb02eb9cd2b4e14c9a32b", }, }, }, @@ -1194,7 +1135,6 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { Namespaces: []string{"user"}, PolicyPaths: []string{"./testdata/misconfig/cloudformation/params/code/rego"}, CloudFormationParamVars: []string{"./testdata/misconfig/cloudformation/params/cfparams.json"}, - DisableEmbeddedPolicies: true, DisableEmbeddedLibraries: true, }, }, @@ -1235,9 +1175,9 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/cloudformation/params/code/src", Type: artifact.TypeFilesystem, - ID: "sha256:abaeb2f443a59940afd63e015d0bb737127ebde06306840f529dd40d65390703", + ID: "sha256:40d6550292de7518fd7229f7b14803c67cbffbad3376e773ad7e6dc003846e87", BlobIDs: []string{ - "sha256:abaeb2f443a59940afd63e015d0bb737127ebde06306840f529dd40d65390703", + "sha256:40d6550292de7518fd7229f7b14803c67cbffbad3376e773ad7e6dc003846e87", }, }, }, @@ -1251,7 +1191,6 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { RegoOnly: true, Namespaces: []string{"user"}, PolicyPaths: []string{"./testdata/misconfig/cloudformation/passed/rego"}, - DisableEmbeddedPolicies: true, DisableEmbeddedLibraries: true, }, }, @@ -1279,7 +1218,7 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { References: []string{"https://trivy.dev/"}, }, CauseMetadata: types.CauseMetadata{ - Provider: "Generic", + Provider: "Cloud", Service: "general", }, }, @@ -1293,9 +1232,9 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/cloudformation/passed/src", Type: artifact.TypeFilesystem, - ID: "sha256:73bb13d7c722bfcdf6c00f51f368befda453e1acba31ba62aad31b5b04b235e8", + ID: "sha256:e2269b8ea44e29aedeaeea83368f879b3fb0cb97bfe46bcca4383a637280cace", BlobIDs: []string{ - "sha256:73bb13d7c722bfcdf6c00f51f368befda453e1acba31ba62aad31b5b04b235e8", + "sha256:e2269b8ea44e29aedeaeea83368f879b3fb0cb97bfe46bcca4383a637280cace", }, }, }, @@ -1339,7 +1278,6 @@ func TestDockerfileMisconfigurationScan(t *testing.T) { RegoOnly: true, Namespaces: []string{"user"}, PolicyPaths: []string{"./testdata/misconfig/dockerfile/single-failure/rego"}, - DisableEmbeddedPolicies: true, DisableEmbeddedLibraries: true, }, }, @@ -1381,9 +1319,9 @@ func TestDockerfileMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/dockerfile/single-failure/src", Type: artifact.TypeFilesystem, - ID: "sha256:059f081288e7ea418286bddcee78167e8ebf33b47e365c20754b6cfa180d997d", + ID: "sha256:3551bddb0f53fb9e0c32390e3ac33f841e3cc15a52ddbcbd9ea07f7e6d1d4437", BlobIDs: []string{ - "sha256:059f081288e7ea418286bddcee78167e8ebf33b47e365c20754b6cfa180d997d", + "sha256:3551bddb0f53fb9e0c32390e3ac33f841e3cc15a52ddbcbd9ea07f7e6d1d4437", }, }, }, @@ -1397,7 +1335,6 @@ func TestDockerfileMisconfigurationScan(t *testing.T) { RegoOnly: true, Namespaces: []string{"user"}, PolicyPaths: []string{"./testdata/misconfig/dockerfile/multiple-failures/rego"}, - DisableEmbeddedPolicies: true, DisableEmbeddedLibraries: true, }, }, @@ -1439,9 +1376,9 @@ func TestDockerfileMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/dockerfile/multiple-failures/src", Type: artifact.TypeFilesystem, - ID: "sha256:059f081288e7ea418286bddcee78167e8ebf33b47e365c20754b6cfa180d997d", + ID: "sha256:3551bddb0f53fb9e0c32390e3ac33f841e3cc15a52ddbcbd9ea07f7e6d1d4437", BlobIDs: []string{ - "sha256:059f081288e7ea418286bddcee78167e8ebf33b47e365c20754b6cfa180d997d", + "sha256:3551bddb0f53fb9e0c32390e3ac33f841e3cc15a52ddbcbd9ea07f7e6d1d4437", }, }, }, @@ -1469,9 +1406,9 @@ func TestDockerfileMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/dockerfile/no-results/src", Type: artifact.TypeFilesystem, - ID: "sha256:a3d40c3290bf45542f9c0aa364f2c16aca06cdccb4868a2e442b7e6a56c6157a", + ID: "sha256:e57ad1b0be7370a131e1265a25ac8790bbfec2bb5867315916cf92799e5855d3", BlobIDs: []string{ - "sha256:a3d40c3290bf45542f9c0aa364f2c16aca06cdccb4868a2e442b7e6a56c6157a", + "sha256:e57ad1b0be7370a131e1265a25ac8790bbfec2bb5867315916cf92799e5855d3", }, }, }, @@ -1485,7 +1422,6 @@ func TestDockerfileMisconfigurationScan(t *testing.T) { RegoOnly: true, Namespaces: []string{"user"}, PolicyPaths: []string{"./testdata/misconfig/dockerfile/passed/rego"}, - DisableEmbeddedPolicies: true, DisableEmbeddedLibraries: true, }, }, @@ -1529,9 +1465,9 @@ func TestDockerfileMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/dockerfile/passed/src", Type: artifact.TypeFilesystem, - ID: "sha256:5f6504b01b68ef1418d59210c0c7b3604fe63f8575a72721d1172d9d2fdd2e23", + ID: "sha256:ff4a3a7aed57bd8190277cf2cc16213eef43b7a37f26f8458525f2efd9793e8f", BlobIDs: []string{ - "sha256:5f6504b01b68ef1418d59210c0c7b3604fe63f8575a72721d1172d9d2fdd2e23", + "sha256:ff4a3a7aed57bd8190277cf2cc16213eef43b7a37f26f8458525f2efd9793e8f", }, }, }, @@ -1543,6 +1479,7 @@ func TestDockerfileMisconfigurationScan(t *testing.T) { tt.artifactOpt.DisabledHandlers = []types.HandlerType{ types.SystemFileFilteringPostHandler, } + tt.artifactOpt.MisconfScannerOption.DisableEmbeddedPolicies = true a, err := NewArtifact(tt.fields.dir, c, walker.NewFS(), tt.artifactOpt) require.NoError(t, err) @@ -1574,7 +1511,6 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { RegoOnly: true, Namespaces: []string{"user"}, PolicyPaths: []string{"./testdata/misconfig/kubernetes/single-failure/rego"}, - DisableEmbeddedPolicies: true, DisableEmbeddedLibraries: true, }, }, @@ -1605,7 +1541,7 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { }, }, CauseMetadata: types.CauseMetadata{ - Provider: "Generic", + Provider: "Kubernetes", Service: "general", StartLine: 7, EndLine: 9, @@ -1621,9 +1557,9 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/kubernetes/single-failure/src", Type: artifact.TypeFilesystem, - ID: "sha256:14768f3c82388c43e0af6579632535b5ecd6bd4ac802d063d74b636725191fe9", + ID: "sha256:63ceedb6582e29ee4184b8b776ee27efe226d07a932461639c05bfbe47bf7efa", BlobIDs: []string{ - "sha256:14768f3c82388c43e0af6579632535b5ecd6bd4ac802d063d74b636725191fe9", + "sha256:63ceedb6582e29ee4184b8b776ee27efe226d07a932461639c05bfbe47bf7efa", }, }, }, @@ -1637,7 +1573,6 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { RegoOnly: true, Namespaces: []string{"user"}, PolicyPaths: []string{"./testdata/misconfig/kubernetes/multiple-failures/rego"}, - DisableEmbeddedPolicies: true, DisableEmbeddedLibraries: true, }, }, @@ -1668,7 +1603,7 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { }, }, CauseMetadata: types.CauseMetadata{ - Provider: "Generic", + Provider: "Kubernetes", Service: "general", StartLine: 7, EndLine: 9, @@ -1691,7 +1626,7 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { }, }, CauseMetadata: types.CauseMetadata{ - Provider: "Generic", + Provider: "Kubernetes", Service: "general", StartLine: 10, EndLine: 12, @@ -1707,9 +1642,9 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/kubernetes/multiple-failures/src", Type: artifact.TypeFilesystem, - ID: "sha256:d7b5fe7fb0e8d51cb6a06b5f2027b1e976e25a2462ff40241f86e13e69ff210c", + ID: "sha256:47fcc85b182385fc6cd7ca08270efff33281ba7717c7a97c7b28a47bef24fae3", BlobIDs: []string{ - "sha256:d7b5fe7fb0e8d51cb6a06b5f2027b1e976e25a2462ff40241f86e13e69ff210c", + "sha256:47fcc85b182385fc6cd7ca08270efff33281ba7717c7a97c7b28a47bef24fae3", }, }, }, @@ -1737,9 +1672,9 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/kubernetes/no-results/src", Type: artifact.TypeFilesystem, - ID: "sha256:ddd0e363b0ebab71250f9d36de65d485ca2faa9510ab6ddea940de7af8267b67", + ID: "sha256:4aad6cb079f406935fa383e126616cee6c82e326a92c163042d6043596f18e04", BlobIDs: []string{ - "sha256:ddd0e363b0ebab71250f9d36de65d485ca2faa9510ab6ddea940de7af8267b67", + "sha256:4aad6cb079f406935fa383e126616cee6c82e326a92c163042d6043596f18e04", }, }, }, @@ -1753,7 +1688,6 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { RegoOnly: true, Namespaces: []string{"user"}, PolicyPaths: []string{"./testdata/misconfig/kubernetes/passed/rego"}, - DisableEmbeddedPolicies: true, DisableEmbeddedLibraries: true, }, }, @@ -1783,7 +1717,7 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { }, }, CauseMetadata: types.CauseMetadata{ - Provider: "Generic", + Provider: "Kubernetes", Service: "general", }, }, @@ -1797,9 +1731,9 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/kubernetes/passed/src", Type: artifact.TypeFilesystem, - ID: "sha256:af77d733739a4366b6e03e95605f5282262d45905efed501d83ec7ecc97a7574", + ID: "sha256:b781859c685b32a25e96e54b331957d696cedfc98162146819ac64d3f157660e", BlobIDs: []string{ - "sha256:af77d733739a4366b6e03e95605f5282262d45905efed501d83ec7ecc97a7574", + "sha256:b781859c685b32a25e96e54b331957d696cedfc98162146819ac64d3f157660e", }, }, }, @@ -1811,6 +1745,7 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { tt.artifactOpt.DisabledHandlers = []types.HandlerType{ types.SystemFileFilteringPostHandler, } + tt.artifactOpt.MisconfScannerOption.DisableEmbeddedPolicies = true a, err := NewArtifact(tt.fields.dir, c, walker.NewFS(), tt.artifactOpt) require.NoError(t, err) @@ -1870,7 +1805,7 @@ func TestAzureARMMisconfigurationScan(t *testing.T) { }, CauseMetadata: types.CauseMetadata{ Resource: "resources[0]", - Provider: "Generic", + Provider: "Cloud", Service: "general", StartLine: 30, EndLine: 40, @@ -1886,9 +1821,9 @@ func TestAzureARMMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/azurearm/single-failure/src", Type: artifact.TypeFilesystem, - ID: "sha256:ff25ae7d346f724faf4d2653868bc249bf221460bcf6d364cf50372c303342d2", + ID: "sha256:62a167d993f603f5552042e4b3c7ac3a65dbbe62bad28e72631c69c9a8f5e2b5", BlobIDs: []string{ - "sha256:ff25ae7d346f724faf4d2653868bc249bf221460bcf6d364cf50372c303342d2", + "sha256:62a167d993f603f5552042e4b3c7ac3a65dbbe62bad28e72631c69c9a8f5e2b5", }, }, }, @@ -1930,7 +1865,7 @@ func TestAzureARMMisconfigurationScan(t *testing.T) { }, CauseMetadata: types.CauseMetadata{ Resource: "resources[0]", - Provider: "Generic", + Provider: "Cloud", Service: "general", StartLine: 30, EndLine: 40, @@ -1952,7 +1887,7 @@ func TestAzureARMMisconfigurationScan(t *testing.T) { }, CauseMetadata: types.CauseMetadata{ Resource: "resources[1]", - Provider: "Generic", + Provider: "Cloud", Service: "general", StartLine: 41, EndLine: 51, @@ -1968,9 +1903,9 @@ func TestAzureARMMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/azurearm/multiple-failures/src", Type: artifact.TypeFilesystem, - ID: "sha256:e5c5ad752c6ff8d26f1b6764a9fb6dec345157df7c816b86d491b0af9e9f4ae6", + ID: "sha256:3cc8c966f10a75dc902589329cf202168176243ef8fdec7219452bb54d02af8e", BlobIDs: []string{ - "sha256:e5c5ad752c6ff8d26f1b6764a9fb6dec345157df7c816b86d491b0af9e9f4ae6", + "sha256:3cc8c966f10a75dc902589329cf202168176243ef8fdec7219452bb54d02af8e", }, }, }, @@ -1998,9 +1933,9 @@ func TestAzureARMMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/azurearm/no-results/src", Type: artifact.TypeFilesystem, - ID: "sha256:a3d40c3290bf45542f9c0aa364f2c16aca06cdccb4868a2e442b7e6a56c6157a", + ID: "sha256:522b19ad182f50b7b04217831c914df52c2d2eb1bdddb02eb9cd2b4e14c9a32b", BlobIDs: []string{ - "sha256:a3d40c3290bf45542f9c0aa364f2c16aca06cdccb4868a2e442b7e6a56c6157a", + "sha256:522b19ad182f50b7b04217831c914df52c2d2eb1bdddb02eb9cd2b4e14c9a32b", }, }, }, @@ -2040,7 +1975,7 @@ func TestAzureARMMisconfigurationScan(t *testing.T) { References: []string{"https://trivy.dev/"}, }, CauseMetadata: types.CauseMetadata{ - Provider: "Generic", + Provider: "Cloud", Service: "general", }, }, @@ -2054,9 +1989,9 @@ func TestAzureARMMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/azurearm/passed/src", Type: artifact.TypeFilesystem, - ID: "sha256:0702eea6f699984f98c788c737f3adf74ae00db2b24b7d44577c7eedc31b1eb1", + ID: "sha256:d6a4722cb6865cac6f55c1789d64c57479539e9198722519918764a230586b4b", BlobIDs: []string{ - "sha256:0702eea6f699984f98c788c737f3adf74ae00db2b24b7d44577c7eedc31b1eb1", + "sha256:d6a4722cb6865cac6f55c1789d64c57479539e9198722519918764a230586b4b", }, }, }, @@ -2068,6 +2003,7 @@ func TestAzureARMMisconfigurationScan(t *testing.T) { tt.artifactOpt.DisabledHandlers = []types.HandlerType{ types.SystemFileFilteringPostHandler, } + tt.artifactOpt.MisconfScannerOption.DisableEmbeddedPolicies = true a, err := NewArtifact(tt.fields.dir, c, walker.NewFS(), tt.artifactOpt) require.NoError(t, err) @@ -2099,7 +2035,6 @@ func TestMixedConfigurationScan(t *testing.T) { RegoOnly: true, Namespaces: []string{"user"}, PolicyPaths: []string{"./testdata/misconfig/mixed/rego"}, - DisableEmbeddedPolicies: true, DisableEmbeddedLibraries: true, }, }, @@ -2110,8 +2045,8 @@ func TestMixedConfigurationScan(t *testing.T) { SchemaVersion: 2, Misconfigurations: []types.Misconfiguration{ { - FileType: "terraform", - FilePath: "main.tf", + FileType: "cloudformation", + FilePath: "main.yaml", Failures: types.MisconfResults{ { Namespace: "user.something", @@ -2120,7 +2055,7 @@ func TestMixedConfigurationScan(t *testing.T) { PolicyMetadata: types.PolicyMetadata{ ID: "TEST001", AVDID: "AVD-TEST-0001", - Type: "Terraform Security Check", + Type: "CloudFormation Security Check", Title: "Test policy", Description: "This is a test policy.", Severity: "LOW", @@ -2128,18 +2063,18 @@ func TestMixedConfigurationScan(t *testing.T) { References: []string{"https://trivy.dev/"}, }, CauseMetadata: types.CauseMetadata{ - Resource: "aws_s3_bucket.asd", - Provider: "Generic", + Resource: "main.yaml:3-6", + Provider: "Cloud", Service: "general", - StartLine: 1, - EndLine: 3, + StartLine: 3, + EndLine: 6, }, }, }, }, { - FileType: "cloudformation", - FilePath: "main.yaml", + FileType: "terraform", + FilePath: "main.tf", Failures: types.MisconfResults{ { Namespace: "user.something", @@ -2148,7 +2083,7 @@ func TestMixedConfigurationScan(t *testing.T) { PolicyMetadata: types.PolicyMetadata{ ID: "TEST001", AVDID: "AVD-TEST-0001", - Type: "CloudFormation Security Check", + Type: "Terraform Security Check", Title: "Test policy", Description: "This is a test policy.", Severity: "LOW", @@ -2156,11 +2091,11 @@ func TestMixedConfigurationScan(t *testing.T) { References: []string{"https://trivy.dev/"}, }, CauseMetadata: types.CauseMetadata{ - Resource: "main.yaml:3-6", - Provider: "Generic", + Resource: "aws_s3_bucket.asd", + Provider: "Cloud", Service: "general", - StartLine: 3, - EndLine: 6, + StartLine: 1, + EndLine: 3, }, }, }, @@ -2184,6 +2119,7 @@ func TestMixedConfigurationScan(t *testing.T) { tt.artifactOpt.DisabledHandlers = []types.HandlerType{ types.SystemFileFilteringPostHandler, } + tt.artifactOpt.MisconfScannerOption.DisableEmbeddedPolicies = true a, err := NewArtifact(tt.fields.dir, c, walker.NewFS(), tt.artifactOpt) require.NoError(t, err) @@ -2195,5 +2131,330 @@ func TestMixedConfigurationScan(t *testing.T) { assert.Equal(t, tt.want.Type, got.Type) }) } +} +func TestJSONConfigScan(t *testing.T) { + type fields struct { + dir string + schemas []string + } + + tests := []struct { + name string + fields fields + artifactOpt artifact.Option + putBlobExpectation cache.ArtifactCachePutBlobExpectation + want artifact.Reference + }{ + { + name: "happy path without custom schema", + fields: fields{ + dir: "./testdata/misconfig/json/passed/src", + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/json/passed/checks"}, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Misconfigurations: []types.Misconfiguration{ + { + FileType: types.JSON, + FilePath: "test1.json", + Failures: types.MisconfResults{ + { + Namespace: "user.test_json_check", + Query: "data.user.test_json_check.deny", + Message: `Service "foo" should not be used`, + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST001", + AVDID: "TEST001", + Type: "JSON Security Check", + Title: "Test check", + Severity: "LOW", + }, + CauseMetadata: types.CauseMetadata{ + Provider: "Generic", + Service: "general", + }, + }, + }, + }, + { + FileType: types.JSON, + FilePath: "test2.json", + Failures: types.MisconfResults{ + { + Namespace: "user.test_json_check", + Query: "data.user.test_json_check.deny", + Message: `Provider "bar" should not be used`, + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST001", + AVDID: "TEST001", + Type: "JSON Security Check", + Title: "Test check", + Severity: "LOW", + }, + CauseMetadata: types.CauseMetadata{ + Provider: "Generic", + Service: "general", + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: artifact.Reference{ + Name: "testdata/misconfig/json/passed/src", + Type: artifact.TypeFilesystem, + }, + }, + { + name: "happy path with custom schema", + fields: fields{ + dir: "./testdata/misconfig/json/with-schema/src", + schemas: []string{"./testdata/misconfig/json/with-schema/schemas"}, + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/json/with-schema/checks"}, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Misconfigurations: []types.Misconfiguration{ + { + FileType: types.JSON, + FilePath: "test1.json", + Failures: types.MisconfResults{ + { + Namespace: "user.test_json_check", + Query: "data.user.test_json_check.deny", + Message: `Service "foo" should not be used`, + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST001", + AVDID: "TEST001", + Type: "JSON Security Check", + Title: "Test check", + Severity: "LOW", + }, + CauseMetadata: types.CauseMetadata{ + Provider: "Generic", + Service: "general", + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: artifact.Reference{ + Name: "testdata/misconfig/json/with-schema/src", + Type: artifact.TypeFilesystem, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := new(cache.MockArtifactCache) + c.ApplyPutBlobExpectation(tt.putBlobExpectation) + + tt.artifactOpt.MisconfScannerOption.DisableEmbeddedPolicies = true + if len(tt.fields.schemas) > 0 { + schemas, err := misconf.LoadConfigSchemas(tt.fields.schemas) + require.NoError(t, err) + tt.artifactOpt.MisconfScannerOption.ConfigFileSchemas = schemas + } + + a, err := NewArtifact(tt.fields.dir, c, walker.NewFS(), tt.artifactOpt) + require.NoError(t, err) + + got, err := a.Inspect(context.Background()) + require.NoError(t, err) + require.NotNil(t, got) + + assert.Equal(t, tt.want.Name, got.Name) + assert.Equal(t, tt.want.Type, got.Type) + }) + } +} + +func TestYAMLConfigScan(t *testing.T) { + type fields struct { + dir string + schemas []string + } + + tests := []struct { + name string + fields fields + artifactOpt artifact.Option + putBlobExpectation cache.ArtifactCachePutBlobExpectation + want artifact.Reference + }{ + { + name: "happy path without custom schema", + fields: fields{ + dir: "./testdata/misconfig/yaml/passed/src", + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/yaml/passed/checks"}, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Misconfigurations: []types.Misconfiguration{ + { + FileType: types.YAML, + FilePath: "test1.yaml", + Failures: types.MisconfResults{ + { + Namespace: "user.test_yaml_check", + Query: "data.user.test_yaml_check.deny", + Message: `Service "foo" should not be used`, + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST001", + AVDID: "TEST001", + Type: "YAML Security Check", + Title: "Test check", + Severity: "LOW", + }, + CauseMetadata: types.CauseMetadata{ + Provider: "Generic", + Service: "general", + }, + }, + }, + }, + { + FileType: types.YAML, + FilePath: "test2.yml", + Failures: types.MisconfResults{ + { + Namespace: "user.test_yaml_check", + Query: "data.user.test_yaml_check.deny", + Message: `Provider "bar" should not be used`, + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST001", + AVDID: "TEST001", + Type: "YAML Security Check", + Title: "Test check", + Severity: "LOW", + }, + CauseMetadata: types.CauseMetadata{ + Provider: "Generic", + Service: "general", + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: artifact.Reference{ + Name: "testdata/misconfig/yaml/passed/src", + Type: artifact.TypeFilesystem, + }, + }, + { + name: "happy path with custom schema", + fields: fields{ + dir: "./testdata/misconfig/yaml/with-schema/src", + schemas: []string{"./testdata/misconfig/yaml/with-schema/schemas"}, + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/yaml/with-schema/checks"}, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Misconfigurations: []types.Misconfiguration{ + { + FileType: types.YAML, + FilePath: "test1.yaml", + Failures: types.MisconfResults{ + { + Namespace: "user.test_yaml_check", + Query: "data.user.test_yaml_check.deny", + Message: `Service "foo" should not be used`, + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST001", + AVDID: "TEST001", + Type: "YAML Security Check", + Title: "Test check", + Severity: "LOW", + }, + CauseMetadata: types.CauseMetadata{ + Provider: "Generic", + Service: "general", + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: artifact.Reference{ + Name: "testdata/misconfig/yaml/with-schema/src", + Type: artifact.TypeFilesystem, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := new(cache.MockArtifactCache) + c.ApplyPutBlobExpectation(tt.putBlobExpectation) + + tt.artifactOpt.MisconfScannerOption.DisableEmbeddedPolicies = true + if len(tt.fields.schemas) > 0 { + schemas, err := misconf.LoadConfigSchemas(tt.fields.schemas) + require.NoError(t, err) + tt.artifactOpt.MisconfScannerOption.ConfigFileSchemas = schemas + } + + a, err := NewArtifact(tt.fields.dir, c, walker.NewFS(), tt.artifactOpt) + require.NoError(t, err) + + got, err := a.Inspect(context.Background()) + require.NoError(t, err) + require.NotNil(t, got) + + assert.Equal(t, tt.want.Name, got.Name) + assert.Equal(t, tt.want.Type, got.Type) + }) + } } diff --git a/pkg/fanal/artifact/local/testdata/misconfig/azurearm/multiple-failures/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/azurearm/multiple-failures/rego/policy.rego index fca807d18e9d..4b5158649568 100644 --- a/pkg/fanal/artifact/local/testdata/misconfig/azurearm/multiple-failures/rego/policy.rego +++ b/pkg/fanal/artifact/local/testdata/misconfig/azurearm/multiple-failures/rego/policy.rego @@ -1,16 +1,19 @@ +# METADATA +# title: Test policy +# description: This is a test policy. +# related_resources: +# - "https://trivy.dev/" +# custom: +# id: TEST001 +# avd_id: AVD-TEST-0001 +# severity: LOW +# short_code: no-buckets +# recommended_actions: Have a cup of tea. +# input: +# selector: +# - type: cloud package user.something -__rego_metadata__ := { - "id": "TEST001", - "avd_id": "AVD-TEST-0001", - "title": "Test policy", - "short_code": "no-buckets", - "severity": "LOW", - "description": "This is a test policy.", - "recommended_actions": "Have a cup of tea.", - "url": "https://trivy.dev/", -} - # taken from defsec rego lib to mimic behaviour result(msg, cause) = result { metadata := object.get(cause, "__defsec_metadata", cause) diff --git a/pkg/fanal/artifact/local/testdata/misconfig/azurearm/no-results/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/azurearm/no-results/rego/policy.rego index ecf4506727a3..22c65aaf287c 100644 --- a/pkg/fanal/artifact/local/testdata/misconfig/azurearm/no-results/rego/policy.rego +++ b/pkg/fanal/artifact/local/testdata/misconfig/azurearm/no-results/rego/policy.rego @@ -1,16 +1,19 @@ +# METADATA +# title: Test policy +# description: This is a test policy. +# related_resources: +# - "https://trivy.dev/" +# custom: +# id: TEST001 +# avd_id: AVD-TEST-0001 +# severity: LOW +# short_code: no-buckets +# recommended_actions: Have a cup of tea. +# input: +# selector: +# - type: cloud package user.something -__rego_metadata__ := { - "id": "TEST001", - "avd_id": "AVD-TEST-0001", - "title": "Test policy", - "short_code": "no-buckets", - "severity": "LOW", - "description": "This is a test policy.", - "recommended_actions": "Have a cup of tea.", - "url": "https://trivy.dev/", -} - # taken from defsec rego lib to mimic behaviour result(msg, cause) = result { metadata := object.get(cause, "__defsec_metadata", cause) diff --git a/pkg/fanal/artifact/local/testdata/misconfig/azurearm/passed/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/azurearm/passed/rego/policy.rego index efae52a8f62c..243eb8e0d9ec 100644 --- a/pkg/fanal/artifact/local/testdata/misconfig/azurearm/passed/rego/policy.rego +++ b/pkg/fanal/artifact/local/testdata/misconfig/azurearm/passed/rego/policy.rego @@ -1,16 +1,19 @@ +# METADATA +# title: Test policy +# description: This is a test policy. +# related_resources: +# - "https://trivy.dev/" +# custom: +# id: TEST001 +# avd_id: AVD-TEST-0001 +# severity: LOW +# short_code: no-buckets +# recommended_actions: Have a cup of tea. +# input: +# selector: +# - type: cloud package user.something -__rego_metadata__ := { - "id": "TEST001", - "avd_id": "AVD-TEST-0001", - "title": "Test policy", - "short_code": "no-buckets", - "severity": "LOW", - "description": "This is a test policy.", - "recommended_actions": "Have a cup of tea.", - "url": "https://trivy.dev/", -} - # taken from defsec rego lib to mimic behaviour result(msg, cause) = result { metadata := object.get(cause, "__defsec_metadata", cause) diff --git a/pkg/fanal/artifact/local/testdata/misconfig/azurearm/single-failure/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/azurearm/single-failure/rego/policy.rego index fca807d18e9d..4b5158649568 100644 --- a/pkg/fanal/artifact/local/testdata/misconfig/azurearm/single-failure/rego/policy.rego +++ b/pkg/fanal/artifact/local/testdata/misconfig/azurearm/single-failure/rego/policy.rego @@ -1,16 +1,19 @@ +# METADATA +# title: Test policy +# description: This is a test policy. +# related_resources: +# - "https://trivy.dev/" +# custom: +# id: TEST001 +# avd_id: AVD-TEST-0001 +# severity: LOW +# short_code: no-buckets +# recommended_actions: Have a cup of tea. +# input: +# selector: +# - type: cloud package user.something -__rego_metadata__ := { - "id": "TEST001", - "avd_id": "AVD-TEST-0001", - "title": "Test policy", - "short_code": "no-buckets", - "severity": "LOW", - "description": "This is a test policy.", - "recommended_actions": "Have a cup of tea.", - "url": "https://trivy.dev/", -} - # taken from defsec rego lib to mimic behaviour result(msg, cause) = result { metadata := object.get(cause, "__defsec_metadata", cause) diff --git a/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/multiple-failures/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/multiple-failures/rego/policy.rego index ecf4506727a3..22c65aaf287c 100644 --- a/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/multiple-failures/rego/policy.rego +++ b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/multiple-failures/rego/policy.rego @@ -1,16 +1,19 @@ +# METADATA +# title: Test policy +# description: This is a test policy. +# related_resources: +# - "https://trivy.dev/" +# custom: +# id: TEST001 +# avd_id: AVD-TEST-0001 +# severity: LOW +# short_code: no-buckets +# recommended_actions: Have a cup of tea. +# input: +# selector: +# - type: cloud package user.something -__rego_metadata__ := { - "id": "TEST001", - "avd_id": "AVD-TEST-0001", - "title": "Test policy", - "short_code": "no-buckets", - "severity": "LOW", - "description": "This is a test policy.", - "recommended_actions": "Have a cup of tea.", - "url": "https://trivy.dev/", -} - # taken from defsec rego lib to mimic behaviour result(msg, cause) = result { metadata := object.get(cause, "__defsec_metadata", cause) diff --git a/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/no-results/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/no-results/rego/policy.rego index ecf4506727a3..22c65aaf287c 100644 --- a/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/no-results/rego/policy.rego +++ b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/no-results/rego/policy.rego @@ -1,16 +1,19 @@ +# METADATA +# title: Test policy +# description: This is a test policy. +# related_resources: +# - "https://trivy.dev/" +# custom: +# id: TEST001 +# avd_id: AVD-TEST-0001 +# severity: LOW +# short_code: no-buckets +# recommended_actions: Have a cup of tea. +# input: +# selector: +# - type: cloud package user.something -__rego_metadata__ := { - "id": "TEST001", - "avd_id": "AVD-TEST-0001", - "title": "Test policy", - "short_code": "no-buckets", - "severity": "LOW", - "description": "This is a test policy.", - "recommended_actions": "Have a cup of tea.", - "url": "https://trivy.dev/", -} - # taken from defsec rego lib to mimic behaviour result(msg, cause) = result { metadata := object.get(cause, "__defsec_metadata", cause) diff --git a/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/params/code/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/params/code/rego/policy.rego index 1a94609aaa8b..1d8cd4a1463b 100644 --- a/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/params/code/rego/policy.rego +++ b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/params/code/rego/policy.rego @@ -12,7 +12,9 @@ # severity: HIGH # short_code: foo-bar-baz # recommended_action: "Remove bad stuff" - +# input: +# selector: +# - type: cloud package user.something deny[res] { diff --git a/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/passed/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/passed/rego/policy.rego index d844d6cd2e75..ce267bc752a6 100644 --- a/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/passed/rego/policy.rego +++ b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/passed/rego/policy.rego @@ -1,16 +1,19 @@ +# METADATA +# title: Test policy +# description: This is a test policy. +# related_resources: +# - "https://trivy.dev/" +# custom: +# id: TEST001 +# avd_id: AVD-TEST-0001 +# severity: LOW +# short_code: no-buckets +# recommended_actions: Have a cup of tea. +# input: +# selector: +# - type: cloud package user.something -__rego_metadata__ := { - "id": "TEST001", - "avd_id": "AVD-TEST-0001", - "title": "Test policy", - "short_code": "no-buckets", - "severity": "LOW", - "description": "This is a test policy.", - "recommended_actions": "Have a cup of tea.", - "url": "https://trivy.dev/", -} - # taken from defsec rego lib to mimic behaviour result(msg, cause) = result { metadata := object.get(cause, "__defsec_metadata", cause) diff --git a/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/single-failure/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/single-failure/rego/policy.rego index ecf4506727a3..22c65aaf287c 100644 --- a/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/single-failure/rego/policy.rego +++ b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/single-failure/rego/policy.rego @@ -1,16 +1,19 @@ +# METADATA +# title: Test policy +# description: This is a test policy. +# related_resources: +# - "https://trivy.dev/" +# custom: +# id: TEST001 +# avd_id: AVD-TEST-0001 +# severity: LOW +# short_code: no-buckets +# recommended_actions: Have a cup of tea. +# input: +# selector: +# - type: cloud package user.something -__rego_metadata__ := { - "id": "TEST001", - "avd_id": "AVD-TEST-0001", - "title": "Test policy", - "short_code": "no-buckets", - "severity": "LOW", - "description": "This is a test policy.", - "recommended_actions": "Have a cup of tea.", - "url": "https://trivy.dev/", -} - # taken from defsec rego lib to mimic behaviour result(msg, cause) = result { metadata := object.get(cause, "__defsec_metadata", cause) diff --git a/pkg/fanal/artifact/local/testdata/misconfig/json/passed/checks/test.rego b/pkg/fanal/artifact/local/testdata/misconfig/json/passed/checks/test.rego new file mode 100644 index 000000000000..b8e61860e873 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/json/passed/checks/test.rego @@ -0,0 +1,17 @@ +# METADATA +# title: Test check +# custom: +# id: TEST001 +# avd_id: TEST001 +# severity: LOW +package user.test_json_check + +deny[res] { + input.service == "foo" + res := result.new(`Service "foo" should not be used`, input.service) +} + +deny[res] { + input.provider == "bar" + res := result.new(`Provider "bar" should not be used`, input.provider) +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/json/passed/src/test1.json b/pkg/fanal/artifact/local/testdata/misconfig/json/passed/src/test1.json new file mode 100644 index 000000000000..95b9d38deb71 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/json/passed/src/test1.json @@ -0,0 +1,3 @@ +{ + "service": "foo" +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/json/passed/src/test2.json b/pkg/fanal/artifact/local/testdata/misconfig/json/passed/src/test2.json new file mode 100644 index 000000000000..22e90bf288b3 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/json/passed/src/test2.json @@ -0,0 +1,3 @@ +{ + "provider": "bar" +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/json/with-schema/checks/test.rego b/pkg/fanal/artifact/local/testdata/misconfig/json/with-schema/checks/test.rego new file mode 100644 index 000000000000..b8e61860e873 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/json/with-schema/checks/test.rego @@ -0,0 +1,17 @@ +# METADATA +# title: Test check +# custom: +# id: TEST001 +# avd_id: TEST001 +# severity: LOW +package user.test_json_check + +deny[res] { + input.service == "foo" + res := result.new(`Service "foo" should not be used`, input.service) +} + +deny[res] { + input.provider == "bar" + res := result.new(`Provider "bar" should not be used`, input.provider) +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/json/with-schema/schemas/test.json b/pkg/fanal/artifact/local/testdata/misconfig/json/with-schema/schemas/test.json new file mode 100644 index 000000000000..f72cdb0d2016 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/json/with-schema/schemas/test.json @@ -0,0 +1,9 @@ +{ + "$id": "https://example.com/test.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "service": { "type": "string" } + }, + "required": ["service"] +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/json/with-schema/src/test1.json b/pkg/fanal/artifact/local/testdata/misconfig/json/with-schema/src/test1.json new file mode 100644 index 000000000000..95b9d38deb71 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/json/with-schema/src/test1.json @@ -0,0 +1,3 @@ +{ + "service": "foo" +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/json/with-schema/src/test2.json b/pkg/fanal/artifact/local/testdata/misconfig/json/with-schema/src/test2.json new file mode 100644 index 000000000000..22e90bf288b3 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/json/with-schema/src/test2.json @@ -0,0 +1,3 @@ +{ + "provider": "bar" +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/multiple-failures/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/multiple-failures/rego/policy.rego index 6acb90f8b852..2e914843a266 100644 --- a/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/multiple-failures/rego/policy.rego +++ b/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/multiple-failures/rego/policy.rego @@ -1,16 +1,19 @@ +# METADATA +# title: Test policy +# description: This is a test policy. +# related_resources: +# - "https://trivy.dev/" +# custom: +# id: TEST001 +# avd_id: AVD-TEST-0001 +# severity: LOW +# short_code: no-buckets +# recommended_actions: Have a cup of tea. +# input: +# selector: +# - type: kubernetes package user.something -__rego_metadata__ := { - "id": "TEST001", - "avd_id": "AVD-TEST-0001", - "title": "Test policy", - "short_code": "no-buckets", - "severity": "LOW", - "description": "This is a test policy.", - "recommended_actions": "Have a cup of tea.", - "url": "https://trivy.dev/", -} - # taken from defsec rego lib to mimic behaviour result(msg, cause) = result { metadata := object.get(cause, "__defsec_metadata", cause) diff --git a/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/no-results/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/no-results/rego/policy.rego index 87c2e8f830b8..e46dea74b0a9 100644 --- a/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/no-results/rego/policy.rego +++ b/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/no-results/rego/policy.rego @@ -1,16 +1,19 @@ +# METADATA +# title: Test policy +# description: This is a test policy. +# related_resources: +# - "https://trivy.dev/" +# custom: +# id: TEST001 +# avd_id: AVD-TEST-0001 +# severity: LOW +# short_code: no-buckets +# recommended_actions: Have a cup of tea. +# input: +# selector: +# - type: kubernetes package user.something -__rego_metadata__ := { - "id": "TEST001", - "avd_id": "AVD-TEST-0001", - "title": "Test policy", - "short_code": "no-buckets", - "severity": "LOW", - "description": "This is a test policy.", - "recommended_actions": "Have a cup of tea.", - "url": "https://trivy.dev/", -} - # taken from defsec rego lib to mimic behaviour result(msg, cause) = result { metadata := object.get(cause, "__defsec_metadata", cause) diff --git a/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/passed/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/passed/rego/policy.rego index c46411777481..fd312f3d26d5 100644 --- a/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/passed/rego/policy.rego +++ b/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/passed/rego/policy.rego @@ -1,16 +1,19 @@ +# METADATA +# title: Test policy +# description: This is a test policy. +# related_resources: +# - "https://trivy.dev/" +# custom: +# id: TEST001 +# avd_id: AVD-TEST-0001 +# severity: LOW +# short_code: no-buckets +# recommended_actions: Have a cup of tea. +# input: +# selector: +# - type: kubernetes package user.something -__rego_metadata__ := { - "id": "TEST001", - "avd_id": "AVD-TEST-0001", - "title": "Test policy", - "short_code": "no-buckets", - "severity": "LOW", - "description": "This is a test policy.", - "recommended_actions": "Have a cup of tea.", - "url": "https://trivy.dev/", -} - # taken from defsec rego lib to mimic behaviour result(msg, cause) = result { metadata := object.get(cause, "__defsec_metadata", cause) diff --git a/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/single-failure/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/single-failure/rego/policy.rego index de8ae68d4d5d..d01bb39ce435 100644 --- a/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/single-failure/rego/policy.rego +++ b/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/single-failure/rego/policy.rego @@ -1,16 +1,19 @@ +# METADATA +# title: Test policy +# description: This is a test policy. +# related_resources: +# - https://trivy.dev/ +# custom: +# id: TEST001 +# avd_id: AVD-TEST-0001 +# severity: LOW +# short_code: no-evil +# recommended_actions: Have a cup of tea. +# input: +# selector: +# - type: kubernetes package user.something -__rego_metadata__ := { - "id": "TEST001", - "avd_id": "AVD-TEST-0001", - "title": "Test policy", - "short_code": "no-evil", - "severity": "LOW", - "description": "This is a test policy.", - "recommended_actions": "Have a cup of tea.", - "url": "https://trivy.dev/", -} - # taken from defsec rego lib to mimic behaviour result(msg, cause) = result { metadata := object.get(cause, "__defsec_metadata", cause) diff --git a/pkg/fanal/artifact/local/testdata/misconfig/mixed/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/mixed/rego/policy.rego index a9399362d285..410a3c8ada8d 100644 --- a/pkg/fanal/artifact/local/testdata/misconfig/mixed/rego/policy.rego +++ b/pkg/fanal/artifact/local/testdata/misconfig/mixed/rego/policy.rego @@ -1,16 +1,19 @@ +# METADATA +# title: Test policy +# description: This is a test policy. +# related_resources: +# - "https://trivy.dev/" +# custom: +# id: TEST001 +# avd_id: AVD-TEST-0001 +# severity: LOW +# short_code: no-buckets +# recommended_actions: Have a cup of tea. +# input: +# selector: +# - type: cloud package user.something - __rego_metadata__ := { - "id": "TEST001", - "avd_id": "AVD-TEST-0001", - "title": "Test policy", - "short_code": "no-buckets", - "severity": "LOW", - "description": "This is a test policy.", - "recommended_actions": "Have a cup of tea.", - "url": "https://trivy.dev/", - } - # taken from defsec rego lib to mimic behaviour result(msg, cause) = result { metadata := object.get(cause, "__defsec_metadata", cause) diff --git a/pkg/fanal/artifact/local/testdata/misconfig/yaml/passed/checks/test.rego b/pkg/fanal/artifact/local/testdata/misconfig/yaml/passed/checks/test.rego new file mode 100644 index 000000000000..ef60d7c9f702 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/yaml/passed/checks/test.rego @@ -0,0 +1,17 @@ +# METADATA +# title: Test check +# custom: +# id: TEST001 +# avd_id: TEST001 +# severity: LOW +package user.test_yaml_check + +deny[res] { + input.service == "foo" + res := result.new(`Service "foo" should not be used`, input.service) +} + +deny[res] { + input.provider == "bar" + res := result.new(`Provider "bar" should not be used`, input.provider) +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/yaml/passed/src/test1.yaml b/pkg/fanal/artifact/local/testdata/misconfig/yaml/passed/src/test1.yaml new file mode 100644 index 000000000000..18d05964b79b --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/yaml/passed/src/test1.yaml @@ -0,0 +1 @@ +service: foo \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/yaml/passed/src/test2.yml b/pkg/fanal/artifact/local/testdata/misconfig/yaml/passed/src/test2.yml new file mode 100644 index 000000000000..067ebe333ddf --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/yaml/passed/src/test2.yml @@ -0,0 +1 @@ +provider: bar \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/yaml/with-schema/checks/test.rego b/pkg/fanal/artifact/local/testdata/misconfig/yaml/with-schema/checks/test.rego new file mode 100644 index 000000000000..ef60d7c9f702 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/yaml/with-schema/checks/test.rego @@ -0,0 +1,17 @@ +# METADATA +# title: Test check +# custom: +# id: TEST001 +# avd_id: TEST001 +# severity: LOW +package user.test_yaml_check + +deny[res] { + input.service == "foo" + res := result.new(`Service "foo" should not be used`, input.service) +} + +deny[res] { + input.provider == "bar" + res := result.new(`Provider "bar" should not be used`, input.provider) +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/yaml/with-schema/schemas/test.json b/pkg/fanal/artifact/local/testdata/misconfig/yaml/with-schema/schemas/test.json new file mode 100644 index 000000000000..f72cdb0d2016 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/yaml/with-schema/schemas/test.json @@ -0,0 +1,9 @@ +{ + "$id": "https://example.com/test.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "service": { "type": "string" } + }, + "required": ["service"] +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/yaml/with-schema/src/test1.yaml b/pkg/fanal/artifact/local/testdata/misconfig/yaml/with-schema/src/test1.yaml new file mode 100644 index 000000000000..18d05964b79b --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/yaml/with-schema/src/test1.yaml @@ -0,0 +1 @@ +service: foo \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/yaml/with-schema/src/test2.yml b/pkg/fanal/artifact/local/testdata/misconfig/yaml/with-schema/src/test2.yml new file mode 100644 index 000000000000..067ebe333ddf --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/yaml/with-schema/src/test2.yml @@ -0,0 +1 @@ +provider: bar \ No newline at end of file diff --git a/pkg/fanal/artifact/repo/git_test.go b/pkg/fanal/artifact/repo/git_test.go index 8de1f3d8864b..fbfbe39ff85f 100644 --- a/pkg/fanal/artifact/repo/git_test.go +++ b/pkg/fanal/artifact/repo/git_test.go @@ -197,9 +197,9 @@ func TestArtifact_Inspect(t *testing.T) { want: artifact.Reference{ Name: ts.URL + "/test-repo.git", Type: artifact.TypeRepository, - ID: "sha256:6a89d4fcd50f840a79da64523c255da80171acd3d286df2acc60056c778d9304", + ID: "sha256:88233504639eb201433a0505956309ba0c48156f45beb786f95ccd3e8a343e9d", BlobIDs: []string{ - "sha256:6a89d4fcd50f840a79da64523c255da80171acd3d286df2acc60056c778d9304", + "sha256:88233504639eb201433a0505956309ba0c48156f45beb786f95ccd3e8a343e9d", }, }, }, diff --git a/pkg/fanal/image/registry/azure/azure.go b/pkg/fanal/image/registry/azure/azure.go index 3203829f3d3d..d19392924a10 100644 --- a/pkg/fanal/image/registry/azure/azure.go +++ b/pkg/fanal/image/registry/azure/azure.go @@ -14,15 +14,19 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/fanal/image/registry/intf" "github.com/aquasecurity/trivy/pkg/fanal/types" ) -type Registry struct { +type RegistryClient struct { domain string scope string cloud cloud.Configuration } +type Registry struct { +} + const ( azureURL = ".azurecr.io" chinaAzureURL = ".azurecr.cn" @@ -31,23 +35,25 @@ const ( scheme = "https" ) -func (r *Registry) CheckOptions(domain string, _ types.RegistryOptions) error { +func (r *Registry) CheckOptions(domain string, _ types.RegistryOptions) (intf.RegistryClient, error) { if strings.HasSuffix(domain, azureURL) { - r.domain = domain - r.scope = scope - r.cloud = cloud.AzurePublic - return nil + return &RegistryClient{ + domain: domain, + scope: scope, + cloud: cloud.AzurePublic, + }, nil } else if strings.HasSuffix(domain, chinaAzureURL) { - r.domain = domain - r.scope = chinaScope - r.cloud = cloud.AzureChina - return nil + return &RegistryClient{ + domain: domain, + scope: chinaScope, + cloud: cloud.AzureChina, + }, nil } - return xerrors.Errorf("Azure registry: %w", types.InvalidURLPattern) + return nil, xerrors.Errorf("Azure registry: %w", types.InvalidURLPattern) } -func (r *Registry) GetCredential(ctx context.Context) (string, string, error) { +func (r *RegistryClient) GetCredential(ctx context.Context) (string, string, error) { opts := azcore.ClientOptions{Cloud: r.cloud} cred, err := azidentity.NewDefaultAzureCredential(&azidentity.DefaultAzureCredentialOptions{ClientOptions: opts}) if err != nil { diff --git a/pkg/fanal/image/registry/azure/azure_test.go b/pkg/fanal/image/registry/azure/azure_test.go index 0fb4839e8fee..5be56f777a51 100644 --- a/pkg/fanal/image/registry/azure/azure_test.go +++ b/pkg/fanal/image/registry/azure/azure_test.go @@ -38,7 +38,7 @@ func TestRegistry_CheckOptions(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := azure.Registry{} - err := r.CheckOptions(tt.domain, types.RegistryOptions{}) + _, err := r.CheckOptions(tt.domain, types.RegistryOptions{}) if tt.wantErr != "" { assert.EqualError(t, err, tt.wantErr) } else { diff --git a/pkg/fanal/image/registry/ecr/ecr.go b/pkg/fanal/image/registry/ecr/ecr.go index 261030d1a584..c9bb0dea5330 100644 --- a/pkg/fanal/image/registry/ecr/ecr.go +++ b/pkg/fanal/image/registry/ecr/ecr.go @@ -3,6 +3,7 @@ package ecr import ( "context" "encoding/base64" + "regexp" "strings" "github.com/aws/aws-sdk-go-v2/aws" @@ -11,48 +12,73 @@ import ( "github.com/aws/aws-sdk-go-v2/service/ecr" "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/fanal/image/registry/intf" "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" ) -const ecrURLSuffix = ".amazonaws.com" -const ecrURLPartial = ".dkr.ecr" - type ecrAPI interface { GetAuthorizationToken(ctx context.Context, params *ecr.GetAuthorizationTokenInput, optFns ...func(*ecr.Options)) (*ecr.GetAuthorizationTokenOutput, error) } type ECR struct { +} + +type ECRClient struct { Client ecrAPI } -func getSession(option types.RegistryOptions) (aws.Config, error) { +func getSession(domain, region string, option types.RegistryOptions) (aws.Config, error) { // create custom credential information if option is valid if option.AWSSecretKey != "" && option.AWSAccessKey != "" && option.AWSRegion != "" { + if region != option.AWSRegion { + log.Warnf("The region from AWS_REGION (%s) is being overridden. The region from domain (%s) was used.", option.AWSRegion, domain) + } return config.LoadDefaultConfig( context.TODO(), - config.WithRegion(option.AWSRegion), + config.WithRegion(region), config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(option.AWSAccessKey, option.AWSSecretKey, option.AWSSessionToken)), ) } - return config.LoadDefaultConfig(context.TODO()) + return config.LoadDefaultConfig(context.TODO(), config.WithRegion(region)) } -func (e *ECR) CheckOptions(domain string, option types.RegistryOptions) error { - if !strings.HasSuffix(domain, ecrURLSuffix) && !strings.Contains(domain, ecrURLPartial) { - return xerrors.Errorf("ECR : %w", types.InvalidURLPattern) +func (e *ECR) CheckOptions(domain string, option types.RegistryOptions) (intf.RegistryClient, error) { + region := determineRegion(domain) + if region == "" { + return nil, xerrors.Errorf("ECR : %w", types.InvalidURLPattern) } - cfg, err := getSession(option) + cfg, err := getSession(domain, region, option) if err != nil { - return err + return nil, err } svc := ecr.NewFromConfig(cfg) - e.Client = svc - return nil + return &ECRClient{Client: svc}, nil +} + +// Endpoints take the form +// .dkr.ecr..amazonaws.com +// .dkr.ecr-fips..amazonaws.com +// .dkr.ecr..amazonaws.com.cn +// .dkr.ecr..sc2s.sgov.gov +// .dkr.ecr..c2s.ic.gov +// see +// - https://docs.aws.amazon.com/general/latest/gr/ecr.html +// - https://docs.amazonaws.cn/en_us/aws/latest/userguide/endpoints-arns.html +// - https://github.com/boto/botocore/blob/1.34.51/botocore/data/endpoints.json +var ecrEndpointMatch = regexp.MustCompile(`^[^.]+\.dkr\.ecr(?:-fips)?\.([^.]+)\.(?:amazonaws\.com(?:\.cn)?|sc2s\.sgov\.gov|c2s\.ic\.gov)$`) + +func determineRegion(domain string) string { + matches := ecrEndpointMatch.FindStringSubmatch(domain) + if matches != nil { + return matches[1] + } + return "" } -func (e *ECR) GetCredential(ctx context.Context) (username, password string, err error) { +func (e *ECRClient) GetCredential(ctx context.Context) (username, password string, err error) { input := &ecr.GetAuthorizationTokenInput{} result, err := e.Client.GetAuthorizationToken(ctx, input) if err != nil { diff --git a/pkg/fanal/image/registry/ecr/ecr_test.go b/pkg/fanal/image/registry/ecr/ecr_test.go index 68b1870f07da..321de7f8639d 100644 --- a/pkg/fanal/image/registry/ecr/ecr_test.go +++ b/pkg/fanal/image/registry/ecr/ecr_test.go @@ -8,14 +8,20 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/ecr" awstypes "github.com/aws/aws-sdk-go-v2/service/ecr/types" + "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/fanal/types" ) +type testECRClient interface { + Options() ecr.Options +} + func TestCheckOptions(t *testing.T) { var tests = map[string]struct { - domain string - wantErr error + domain string + expectedRegion string + wantErr error }{ "InvalidURL": { domain: "alpine:3.9", @@ -30,19 +36,71 @@ func TestCheckOptions(t *testing.T) { wantErr: types.InvalidURLPattern, }, "NoOption": { - domain: "xxx.ecr.ap-northeast-1.amazonaws.com", + domain: "xxx.dkr.ecr.ap-northeast-1.amazonaws.com", + expectedRegion: "ap-northeast-1", + }, + "region-1": { + domain: "xxx.dkr.ecr.region-1.amazonaws.com", + expectedRegion: "region-1", + }, + "region-2": { + domain: "xxx.dkr.ecr.region-2.amazonaws.com", + expectedRegion: "region-2", + }, + "fips-region-1": { + domain: "xxx.dkr.ecr-fips.fips-region.amazonaws.com", + expectedRegion: "fips-region", + }, + "cn-region-1": { + domain: "xxx.dkr.ecr.region-1.amazonaws.com.cn", + expectedRegion: "region-1", + }, + "cn-region-2": { + domain: "xxx.dkr.ecr.region-2.amazonaws.com.cn", + expectedRegion: "region-2", + }, + "sc2s-region-1": { + domain: "xxx.dkr.ecr.sc2s-region.sc2s.sgov.gov", + expectedRegion: "sc2s-region", + }, + "c2s-region-1": { + domain: "xxx.dkr.ecr.c2s-region.c2s.ic.gov", + expectedRegion: "c2s-region", + }, + "invalid-ecr": { + domain: "xxx.dkrecr.region-1.amazonaws.com", + wantErr: types.InvalidURLPattern, + }, + "invalid-fips": { + domain: "xxx.dkr.ecrfips.fips-region.amazonaws.com", + wantErr: types.InvalidURLPattern, + }, + "invalid-cn": { + domain: "xxx.dkr.ecr.region-2.amazonaws.cn", + wantErr: types.InvalidURLPattern, + }, + "invalid-sc2s": { + domain: "xxx.dkr.ecr.sc2s-region.sc2s.sgov", + wantErr: types.InvalidURLPattern, + }, + "invalid-cs2": { + domain: "xxx.dkr.ecr.c2s-region.c2s.ic", + wantErr: types.InvalidURLPattern, }, } for testname, v := range tests { a := &ECR{} - err := a.CheckOptions(v.domain, types.RegistryOptions{}) + ecrClient, err := a.CheckOptions(v.domain, types.RegistryOptions{}) if err != nil { if !errors.Is(err, v.wantErr) { t.Errorf("[%s]\nexpected error based on %v\nactual : %v", testname, v.wantErr, err) } continue } + + client := (ecrClient.(*ECRClient)).Client.(testECRClient) + require.Equal(t, v.expectedRegion, client.Options().Region) } } @@ -90,7 +148,7 @@ func TestECRGetCredential(t *testing.T) { } for i, c := range cases { - e := ECR{ + e := ECRClient{ Client: mockedECR{Resp: c.Resp}, } username, password, err := e.GetCredential(context.Background()) diff --git a/pkg/fanal/image/registry/google/google.go b/pkg/fanal/image/registry/google/google.go index fe52c85f7493..3c58f0de005f 100644 --- a/pkg/fanal/image/registry/google/google.go +++ b/pkg/fanal/image/registry/google/google.go @@ -9,14 +9,18 @@ import ( "github.com/GoogleCloudPlatform/docker-credential-gcr/store" "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/fanal/image/registry/intf" "github.com/aquasecurity/trivy/pkg/fanal/types" ) -type Registry struct { +type GoogleRegistryClient struct { Store store.GCRCredStore domain string } +type Registry struct { +} + // Google container registry const gcrURLDomain = "gcr.io" const gcrURLSuffix = ".gcr.io" @@ -24,18 +28,18 @@ const gcrURLSuffix = ".gcr.io" // Google artifact registry const garURLSuffix = "-docker.pkg.dev" -func (g *Registry) CheckOptions(domain string, option types.RegistryOptions) error { +func (g *Registry) CheckOptions(domain string, option types.RegistryOptions) (intf.RegistryClient, error) { if domain != gcrURLDomain && !strings.HasSuffix(domain, gcrURLSuffix) && !strings.HasSuffix(domain, garURLSuffix) { - return xerrors.Errorf("Google registry: %w", types.InvalidURLPattern) + return nil, xerrors.Errorf("Google registry: %w", types.InvalidURLPattern) } - g.domain = domain + client := GoogleRegistryClient{domain: domain} if option.GCPCredPath != "" { - g.Store = store.NewGCRCredStore(option.GCPCredPath) + client.Store = store.NewGCRCredStore(option.GCPCredPath) } - return nil + return &client, nil } -func (g *Registry) GetCredential(_ context.Context) (username, password string, err error) { +func (g *GoogleRegistryClient) GetCredential(_ context.Context) (username, password string, err error) { var credStore store.GCRCredStore if g.Store == nil { credStore, err = store.DefaultGCRCredStore() diff --git a/pkg/fanal/image/registry/google/google_test.go b/pkg/fanal/image/registry/google/google_test.go index 2e4ba64d663e..1bce72897d46 100644 --- a/pkg/fanal/image/registry/google/google_test.go +++ b/pkg/fanal/image/registry/google/google_test.go @@ -14,7 +14,7 @@ func TestCheckOptions(t *testing.T) { var tests = map[string]struct { domain string opt types.RegistryOptions - gcr *Registry + grc *GoogleRegistryClient wantErr error }{ "InvalidURL": { @@ -27,12 +27,12 @@ func TestCheckOptions(t *testing.T) { }, "NoOption": { domain: "gcr.io", - gcr: &Registry{domain: "gcr.io"}, + grc: &GoogleRegistryClient{domain: "gcr.io"}, }, "CredOption": { domain: "gcr.io", opt: types.RegistryOptions{GCPCredPath: "/path/to/file.json"}, - gcr: &Registry{ + grc: &GoogleRegistryClient{ domain: "gcr.io", Store: store.NewGCRCredStore("/path/to/file.json"), }, @@ -41,7 +41,7 @@ func TestCheckOptions(t *testing.T) { for testname, v := range tests { g := &Registry{} - err := g.CheckOptions(v.domain, v.opt) + grc, err := g.CheckOptions(v.domain, v.opt) if v.wantErr != nil { if err == nil { t.Errorf("%s : expected error but no error", testname) @@ -52,8 +52,8 @@ func TestCheckOptions(t *testing.T) { } continue } - if !reflect.DeepEqual(v.gcr, g) { - t.Errorf("[%s]\nexpected : %v\nactual : %v", testname, v.gcr, g) + if !reflect.DeepEqual(v.grc, grc) { + t.Errorf("[%s]\nexpected : %v\nactual : %v", testname, v.grc, grc) } } } diff --git a/pkg/fanal/image/registry/intf/registry.go b/pkg/fanal/image/registry/intf/registry.go new file mode 100644 index 000000000000..da8c5d3c1789 --- /dev/null +++ b/pkg/fanal/image/registry/intf/registry.go @@ -0,0 +1,15 @@ +package intf + +import ( + "context" + + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +type RegistryClient interface { + GetCredential(ctx context.Context) (string, string, error) +} + +type Registry interface { + CheckOptions(domain string, option types.RegistryOptions) (RegistryClient, error) +} diff --git a/pkg/fanal/image/registry/token.go b/pkg/fanal/image/registry/token.go index b959c6cc7bbc..72c569890196 100644 --- a/pkg/fanal/image/registry/token.go +++ b/pkg/fanal/image/registry/token.go @@ -8,12 +8,13 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/image/registry/azure" "github.com/aquasecurity/trivy/pkg/fanal/image/registry/ecr" "github.com/aquasecurity/trivy/pkg/fanal/image/registry/google" + "github.com/aquasecurity/trivy/pkg/fanal/image/registry/intf" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" ) var ( - registries []Registry + registries []intf.Registry ) func init() { @@ -22,23 +23,18 @@ func init() { RegisterRegistry(&azure.Registry{}) } -type Registry interface { - CheckOptions(domain string, option types.RegistryOptions) error - GetCredential(ctx context.Context) (string, string, error) -} - -func RegisterRegistry(registry Registry) { +func RegisterRegistry(registry intf.Registry) { registries = append(registries, registry) } func GetToken(ctx context.Context, domain string, opt types.RegistryOptions) (auth authn.Basic) { // check registry which particular to get credential for _, registry := range registries { - err := registry.CheckOptions(domain, opt) + client, err := registry.CheckOptions(domain, opt) if err != nil { continue } - username, password, err := registry.GetCredential(ctx) + username, password, err := client.GetCredential(ctx) if err != nil { // only skip check registry if error occurred log.Debug("Credential error", log.Err(err)) diff --git a/pkg/fanal/secret/builtin-allow-rules.go b/pkg/fanal/secret/builtin-allow-rules.go index 5e02f3241a04..634037fbceec 100644 --- a/pkg/fanal/secret/builtin-allow-rules.go +++ b/pkg/fanal/secret/builtin-allow-rules.go @@ -4,7 +4,7 @@ var builtinAllowRules = []AllowRule{ { ID: "tests", Description: "Avoid test files and paths", - Path: MustCompile(`(^test|\/test|-test|_test|\.test)`), + Path: MustCompile(`(^(?i)test|\/test|-test|_test|\.test)`), }, { ID: "examples", diff --git a/pkg/fanal/secret/builtin-rules.go b/pkg/fanal/secret/builtin-rules.go index 9cb0aa361025..09bf98fd3da8 100644 --- a/pkg/fanal/secret/builtin-rules.go +++ b/pkg/fanal/secret/builtin-rules.go @@ -553,7 +553,7 @@ var builtinRules = []Rule{ Category: CategoryGrafana, Title: "Grafana API token", Severity: "MEDIUM", - Regex: MustCompile(`['\"]eyJrIjoi(?i)[a-z0-9\-_=]{72,92}['\"]`), + Regex: MustCompile(`['\"]?eyJrIjoi(?i)[a-z0-9\-_=]{72,92}['\"]?`), Keywords: []string{"eyJrIjoi"}, }, { @@ -604,7 +604,7 @@ var builtinRules = []Rule{ Title: "JWT token", Severity: "MEDIUM", Regex: MustCompile(`ey[a-zA-Z0-9]{17,}\.ey[a-zA-Z0-9\/\\_-]{17,}\.(?:[a-zA-Z0-9\/\\_-]{10,}={0,2})?`), - Keywords: []string{"jwt"}, + Keywords: []string{".eyJ"}, }, { ID: "linear-api-token", diff --git a/pkg/fanal/secret/scanner.go b/pkg/fanal/secret/scanner.go index b8591a69228c..33a3806f41b4 100644 --- a/pkg/fanal/secret/scanner.go +++ b/pkg/fanal/secret/scanner.go @@ -366,6 +366,7 @@ func NewScanner(config *Config) Scanner { type ScanArgs struct { FilePath string Content []byte + Binary bool } type Match struct { @@ -434,9 +435,14 @@ func (s *Scanner) Scan(args ScanArgs) types.Secret { censored = censorLocation(loc, censored) } } - for _, match := range matched { - findings = append(findings, toFinding(match.Rule, match.Location, censored)) + finding := toFinding(match.Rule, match.Location, censored) + // Rewrite unreadable fields for binary files + if args.Binary { + finding.Match = fmt.Sprintf("Binary file %q matches a rule %q", args.FilePath, match.Rule.Title) + finding.Code = types.Code{} + } + findings = append(findings, finding) } if len(findings) == 0 { @@ -504,8 +510,8 @@ func findLocation(start, end int, content []byte) (int, int, types.Code, string) } if lineEnd-lineStart > 100 { - lineStart = lo.Ternary(start-30 < 0, 0, start-30) - lineEnd = lo.Ternary(end+20 > len(content), len(content), end+20) + lineStart = lo.Ternary(start-lineStart-30 < 0, lineStart, start-30) + lineEnd = lo.Ternary(end+20 > lineEnd, lineEnd, end+20) } matchLine := string(content[lineStart:lineEnd]) endLineNum := startLineNum + bytes.Count(content[start:end], lineSep) diff --git a/pkg/fanal/secret/scanner_test.go b/pkg/fanal/secret/scanner_test.go index 86d813f4785e..f17b1150dd4a 100644 --- a/pkg/fanal/secret/scanner_test.go +++ b/pkg/fanal/secret/scanner_test.go @@ -690,6 +690,71 @@ func TestSecretScanner(t *testing.T) { }, } + wantFindingGrafanaQuoted := types.SecretFinding{ + RuleID: "grafana-api-token", + Category: secret.CategoryGrafana, + Title: "Grafana API token", + Severity: "MEDIUM", + StartLine: 1, + EndLine: 1, + Match: "GRAFANA_TOKEN=**********************************************************************************************", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "GRAFANA_TOKEN=**********************************************************************************************", + Highlighted: "GRAFANA_TOKEN=**********************************************************************************************", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + { + Number: 2, + Content: "GRAFANA_TOKEN=**************************************************************************************", + Highlighted: "GRAFANA_TOKEN=**************************************************************************************", + IsCause: false, + FirstCause: false, + LastCause: false, + }, + }, + }, + } + + wantFindingGrafanaUnquoted := types.SecretFinding{ + RuleID: "grafana-api-token", + Category: secret.CategoryGrafana, + Title: "Grafana API token", + Severity: "MEDIUM", + StartLine: 2, + EndLine: 2, + Match: "GRAFANA_TOKEN=********************************************************************************************", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "GRAFANA_TOKEN=**************************************************************************************", + Highlighted: "GRAFANA_TOKEN=**************************************************************************************", + IsCause: false, + FirstCause: false, + LastCause: false, + }, + { + Number: 2, + Content: "GRAFANA_TOKEN=********************************************************************************************", + Highlighted: "GRAFANA_TOKEN=********************************************************************************************", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + { + Number: 3, + Content: "", + Highlighted: "", + }, + }, + }, + } + wantMultiLine := types.SecretFinding{ RuleID: "multi-line-secret", Category: "general", @@ -742,6 +807,42 @@ func TestSecretScanner(t *testing.T) { }, }, } + wantFindingJWT := types.SecretFinding{ + RuleID: "jwt-token", + Category: "JWT", + Title: "JWT token", + Severity: "MEDIUM", + StartLine: 3, + EndLine: 3, + Match: "jwt: ***********************************************************************************************************************************************************", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "asd", + Highlighted: "asd", + }, + { + Number: 2, + Content: "aaaa", + Highlighted: "aaaa", + }, + { + Number: 3, + Content: "jwt: ***********************************************************************************************************************************************************", + Highlighted: "jwt: ***********************************************************************************************************************************************************", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + { + Number: 4, + Content: "asda", + Highlighted: "asda", + }, + }, + }, + } tests := []struct { name string @@ -822,6 +923,24 @@ func TestSecretScanner(t *testing.T) { Findings: []types.SecretFinding{wantFindingHuggingFace}, }, }, + { + name: "find grafana secret", + configPath: filepath.Join("testdata", "config.yaml"), + inputFilePath: filepath.Join("testdata", "grafana-env.txt"), + want: types.Secret{ + FilePath: filepath.Join("testdata", "grafana-env.txt"), + Findings: []types.SecretFinding{wantFindingGrafanaUnquoted, wantFindingGrafanaQuoted}, + }, + }, + { + name: "find JWT token", + configPath: filepath.Join("testdata", "config.yaml"), + inputFilePath: filepath.Join("testdata", "jwt-secret.txt"), + want: types.Secret{ + FilePath: filepath.Join("testdata", "jwt-secret.txt"), + Findings: []types.SecretFinding{wantFindingJWT}, + }, + }, { name: "include when keyword found", configPath: filepath.Join("testdata", "config-happy-keywords.yaml"), diff --git a/pkg/fanal/secret/testdata/grafana-env.txt b/pkg/fanal/secret/testdata/grafana-env.txt new file mode 100644 index 000000000000..ede29f880a6b --- /dev/null +++ b/pkg/fanal/secret/testdata/grafana-env.txt @@ -0,0 +1,2 @@ +GRAFANA_TOKEN="eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbkT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk" +GRAFANA_TOKEN=eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbkT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk diff --git a/pkg/fanal/secret/testdata/jwt-secret.txt b/pkg/fanal/secret/testdata/jwt-secret.txt new file mode 100644 index 000000000000..b23e942b3d98 --- /dev/null +++ b/pkg/fanal/secret/testdata/jwt-secret.txt @@ -0,0 +1,4 @@ +asd +aaaa +jwt: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c +asda \ 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 cf7ed95a1679..ebe17d6e2188 100644 --- a/pkg/fanal/test/integration/library_test.go +++ b/pkg/fanal/test/integration/library_test.go @@ -7,20 +7,13 @@ import ( "encoding/json" "flag" "fmt" - "io" "os" "sort" - "strings" "testing" - dimage "github.com/docker/docker/api/types/image" - "github.com/docker/docker/client" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/aquasecurity/trivy/pkg/fanal/analyzer" - + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/cache" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/all" "github.com/aquasecurity/trivy/pkg/fanal/applier" "github.com/aquasecurity/trivy/pkg/fanal/artifact" @@ -29,6 +22,9 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/image" "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + _ "modernc.org/sqlite" ) @@ -36,7 +32,7 @@ var update = flag.Bool("update", false, "update golden files") type testCase struct { name string - remoteImageName string + imageTag string imageFile string wantOS types.OS wantPkgsFromCmds string @@ -45,63 +41,63 @@ type testCase struct { var tests = []testCase{ { - name: "happy path, alpine:3.10", - remoteImageName: "ghcr.io/aquasecurity/trivy-test-images:alpine-310", - imageFile: "../../../../integration/testdata/fixtures/images/alpine-310.tar.gz", + name: "happy path, alpine:3.10", + imageTag: "alpine-310", + imageFile: "../../../../integration/testdata/fixtures/images/alpine-310.tar.gz", wantOS: types.OS{ Name: "3.10.2", Family: "alpine", }, }, { - name: "happy path, amazonlinux:2", - remoteImageName: "ghcr.io/aquasecurity/trivy-test-images:amazon-2", - imageFile: "../../../../integration/testdata/fixtures/images/amazon-2.tar.gz", + name: "happy path, amazonlinux:2", + imageTag: "amazon-2", + imageFile: "../../../../integration/testdata/fixtures/images/amazon-2.tar.gz", wantOS: types.OS{ Name: "2 (Karoo)", Family: "amazon", }, }, { - name: "happy path, debian:buster", - remoteImageName: "ghcr.io/aquasecurity/trivy-test-images:debian-buster", - imageFile: "../../../../integration/testdata/fixtures/images/debian-buster.tar.gz", + name: "happy path, debian:buster", + imageTag: "debian-buster", + imageFile: "../../../../integration/testdata/fixtures/images/debian-buster.tar.gz", wantOS: types.OS{ Name: "10.1", Family: "debian", }, }, { - name: "happy path, photon:3.0", - remoteImageName: "ghcr.io/aquasecurity/trivy-test-images:photon-30", - imageFile: "../../../../integration/testdata/fixtures/images/photon-30.tar.gz", + name: "happy path, photon:3.0", + imageTag: "photon-30", + imageFile: "../../../../integration/testdata/fixtures/images/photon-30.tar.gz", wantOS: types.OS{ Name: "3.0", Family: "photon", }, }, { - name: "happy path, registry.redhat.io/ubi7", - remoteImageName: "ghcr.io/aquasecurity/trivy-test-images:ubi-7", - imageFile: "../../../../integration/testdata/fixtures/images/ubi-7.tar.gz", + name: "happy path, registry.redhat.io/ubi7", + imageTag: "ubi-7", + imageFile: "../../../../integration/testdata/fixtures/images/ubi-7.tar.gz", wantOS: types.OS{ Name: "7.7", Family: "redhat", }, }, { - name: "happy path, opensuse leap 15.1", - remoteImageName: "ghcr.io/aquasecurity/trivy-test-images:opensuse-leap-151", - imageFile: "../../../../integration/testdata/fixtures/images/opensuse-leap-151.tar.gz", + name: "happy path, opensuse leap 15.1", + imageTag: "opensuse-leap-151", + imageFile: "../../../../integration/testdata/fixtures/images/opensuse-leap-151.tar.gz", wantOS: types.OS{ Name: "15.1", Family: "opensuse.leap", }, }, { - name: "happy path, opensuse tumbleweed", - remoteImageName: "ghcr.io/aquasecurity/trivy-test-images:opensuse-tumbleweed", - imageFile: "../../../../integration/testdata/fixtures/images/opensuse-tumbleweed.tar.gz", + name: "happy path, opensuse tumbleweed", + imageTag: "opensuse-tumbleweed", + imageFile: "../../../../integration/testdata/fixtures/images/opensuse-tumbleweed.tar.gz", wantOS: types.OS{ Name: "20240607", Family: "opensuse.tumbleweed", @@ -109,27 +105,27 @@ var tests = []testCase{ }, { // from registry.suse.com/suse/sle15:15.3.17.8.16 - name: "happy path, suse 15.3 (NDB)", - remoteImageName: "ghcr.io/aquasecurity/trivy-test-images:suse-15.3_ndb", - imageFile: "../../../../integration/testdata/fixtures/images/suse-15.3_ndb.tar.gz", + name: "happy path, suse 15.3 (NDB)", + imageTag: "suse-15.3_ndb", + imageFile: "../../../../integration/testdata/fixtures/images/suse-15.3_ndb.tar.gz", wantOS: types.OS{ Name: "15.3", Family: "suse linux enterprise server", }, }, { - name: "happy path, Fedora 35", - remoteImageName: "ghcr.io/aquasecurity/trivy-test-images:fedora-35", - imageFile: "../../../../integration/testdata/fixtures/images/fedora-35.tar.gz", + name: "happy path, Fedora 35", + imageTag: "fedora-35", + imageFile: "../../../../integration/testdata/fixtures/images/fedora-35.tar.gz", wantOS: types.OS{ Name: "35", Family: "fedora", }, }, { - name: "happy path, vulnimage with lock files", - remoteImageName: "ghcr.io/aquasecurity/trivy-test-images:vulnimage", - imageFile: "../../../../integration/testdata/fixtures/images/vulnimage.tar.gz", + name: "happy path, vulnimage with lock files", + imageTag: "vulnimage", + imageFile: "../../../../integration/testdata/fixtures/images/vulnimage.tar.gz", wantOS: types.OS{ Name: "3.7.1", Family: "alpine", @@ -139,60 +135,14 @@ var tests = []testCase{ }, } -func TestFanal_Library_DockerLessMode(t *testing.T) { - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - ctx := context.Background() - d := t.TempDir() - - c, err := cache.NewFSCache(d) - require.NoError(t, err, tt.name) - - cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) - require.NoError(t, err) - - // remove existing Image if any - _, _ = cli.ImageRemove(ctx, tt.remoteImageName, dimage.RemoveOptions{ - Force: true, - PruneChildren: true, - }) - - // Enable only registry scanning - img, cleanup, err := image.NewContainerImage(ctx, tt.remoteImageName, types.ImageOptions{ - ImageSources: types.ImageSources{types.RemoteImageSource}, - }) - require.NoError(t, err) - defer cleanup() - - // don't scan licenses in the test - in parallel it will fail - ar, err := aimage.NewArtifact(img, c, artifact.Option{ - 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++ { - runChecks(t, ctx, ar, applier, tt) - } - - // clear Cache - require.NoError(t, c.Clear()) - }) - } -} - func TestFanal_Library_DockerMode(t *testing.T) { // Disable updating golden files because local images don't have compressed layer digests, // and updating golden files in this function results in incomplete files. if *update { t.Skipf("This test creates wrong golden file") } + + cli := testutil.NewDockerClient(t) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := context.Background() @@ -201,20 +151,10 @@ func TestFanal_Library_DockerMode(t *testing.T) { c, err := cache.NewFSCache(d) require.NoError(t, err) - cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) - require.NoError(t, err, tt.name) - - testfile, err := os.Open(tt.imageFile) - require.NoError(t, err) - - // load image into docker engine - resp, err := cli.ImageLoad(ctx, testfile, true) - require.NoError(t, err, tt.name) - _, err = io.Copy(io.Discard, resp.Body) - require.NoError(t, err, tt.name) + loadedImage := cli.ImageLoad(t, ctx, tt.imageFile) // Enable only dockerd scanning - img, cleanup, err := image.NewContainerImage(ctx, tt.remoteImageName, types.ImageOptions{ + img, cleanup, err := image.NewContainerImage(ctx, loadedImage, types.ImageOptions{ ImageSources: types.ImageSources{types.DockerImageSource}, }) require.NoError(t, err, tt.name) @@ -238,11 +178,6 @@ func TestFanal_Library_DockerMode(t *testing.T) { // clear Cache require.NoError(t, c.Clear(), tt.name) - - _, _ = cli.ImageRemove(ctx, tt.remoteImageName, dimage.RemoveOptions{ - Force: true, - PruneChildren: true, - }) }) } } @@ -297,8 +232,7 @@ func checkOSPackages(t *testing.T, detail types.ArtifactDetail, tc testCase) { // Sort OS packages for consistency sort.Sort(detail.Packages) - splitted := strings.Split(tc.remoteImageName, ":") - goldenFile := fmt.Sprintf("testdata/goldens/packages/%s.json.golden", splitted[len(splitted)-1]) + goldenFile := fmt.Sprintf("testdata/goldens/packages/%s.json.golden", tc.imageTag) if *update { b, err := json.MarshalIndent(detail.Packages, "", " ") diff --git a/pkg/fanal/test/integration/testdata/goldens/packages/alpine-310.json.golden b/pkg/fanal/test/integration/testdata/goldens/packages/alpine-310.json.golden index 32513f398b9f..17d3941a620e 100644 --- a/pkg/fanal/test/integration/testdata/goldens/packages/alpine-310.json.golden +++ b/pkg/fanal/test/integration/testdata/goldens/packages/alpine-310.json.golden @@ -4,14 +4,14 @@ "Name": "alpine-baselayout", "Identifier": { "PURL": "pkg:apk/alpine/alpine-baselayout@3.1.2-r0?arch=x86_64\u0026distro=3.10.2", - "UID": "3ca42aecf84bfa9" + "UID": "2d19d30821e01d2c" }, "Version": "3.1.2-r0", "Arch": "x86_64", "SrcName": "alpine-baselayout", "SrcVersion": "3.1.2-r0", "Licenses": [ - "GPL-2.0" + "GPL-2.0-only" ], "DependsOn": [ "busybox@1.30.1-r2", @@ -95,14 +95,14 @@ "Name": "apk-tools", "Identifier": { "PURL": "pkg:apk/alpine/apk-tools@2.10.4-r2?arch=x86_64\u0026distro=3.10.2", - "UID": "fa7ca40ce236844e" + "UID": "e967fd57e4033819" }, "Version": "2.10.4-r2", "Arch": "x86_64", "SrcName": "apk-tools", "SrcVersion": "2.10.4-r2", "Licenses": [ - "GPL-2.0" + "GPL-2.0-only" ], "DependsOn": [ "libcrypto1.1@1.1.1c-r0", @@ -124,14 +124,14 @@ "Name": "busybox", "Identifier": { "PURL": "pkg:apk/alpine/busybox@1.30.1-r2?arch=x86_64\u0026distro=3.10.2", - "UID": "1941d9acbebe7f44" + "UID": "f3002aff2b6b251d" }, "Version": "1.30.1-r2", "Arch": "x86_64", "SrcName": "busybox", "SrcVersion": "1.30.1-r2", "Licenses": [ - "GPL-2.0" + "GPL-2.0-only" ], "DependsOn": [ "musl@1.1.22-r3" @@ -155,7 +155,7 @@ "Name": "ca-certificates-cacert", "Identifier": { "PURL": "pkg:apk/alpine/ca-certificates-cacert@20190108-r0?arch=x86_64\u0026distro=3.10.2", - "UID": "9b1db91ad655d76c" + "UID": "1d3125ae903daa3c" }, "Version": "20190108-r0", "Arch": "x86_64", @@ -163,7 +163,7 @@ "SrcVersion": "20190108-r0", "Licenses": [ "MPL-2.0", - "GPL-2.0" + "GPL-2.0-or-later" ], "Layer": { "Digest": "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", @@ -321,7 +321,7 @@ "Name": "musl-utils", "Identifier": { "PURL": "pkg:apk/alpine/musl-utils@1.1.22-r3?arch=x86_64\u0026distro=3.10.2", - "UID": "40edadcb74964baa" + "UID": "112ed00987ba9c7d" }, "Version": "1.1.22-r3", "Arch": "x86_64", @@ -330,7 +330,7 @@ "Licenses": [ "MIT", "BSD-3-Clause", - "GPL-2.0" + "GPL-2.0-or-later" ], "DependsOn": [ "musl@1.1.22-r3", @@ -354,14 +354,14 @@ "Name": "scanelf", "Identifier": { "PURL": "pkg:apk/alpine/scanelf@1.2.3-r0?arch=x86_64\u0026distro=3.10.2", - "UID": "6e5cf642f47e44d0" + "UID": "3ae1856359a8e719" }, "Version": "1.2.3-r0", "Arch": "x86_64", "SrcName": "pax-utils", "SrcVersion": "1.2.3-r0", "Licenses": [ - "GPL-2.0" + "GPL-2.0-only" ], "DependsOn": [ "musl@1.1.22-r3" @@ -380,14 +380,14 @@ "Name": "ssl_client", "Identifier": { "PURL": "pkg:apk/alpine/ssl_client@1.30.1-r2?arch=x86_64\u0026distro=3.10.2", - "UID": "3338164dd993d2c8" + "UID": "92ce33a8acb582f6" }, "Version": "1.30.1-r2", "Arch": "x86_64", "SrcName": "busybox", "SrcVersion": "1.30.1-r2", "Licenses": [ - "GPL-2.0" + "GPL-2.0-only" ], "DependsOn": [ "libtls-standalone@2.9.1-r0", diff --git a/pkg/fanal/test/integration/testdata/goldens/packages/opensuse-tumbleweed.json.golden b/pkg/fanal/test/integration/testdata/goldens/packages/opensuse-tumbleweed.json.golden index a651f86e3862..fa00e70aa6e6 100644 --- a/pkg/fanal/test/integration/testdata/goldens/packages/opensuse-tumbleweed.json.golden +++ b/pkg/fanal/test/integration/testdata/goldens/packages/opensuse-tumbleweed.json.golden @@ -3,7 +3,7 @@ "ID": "aaa_base@84.87+git20240523.10a5692-1.1.x86_64", "Name": "aaa_base", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/aaa_base@84.87%2Bgit20240523.10a5692-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/aaa_base@84.87%2Bgit20240523.10a5692-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "fe755017155caefc" }, "Version": "84.87+git20240523.10a5692", @@ -34,7 +34,7 @@ "ID": "bash@5.2.26-12.1.x86_64", "Name": "bash", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/bash@5.2.26-12.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/bash@5.2.26-12.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "ce56393f87add219" }, "Version": "5.2.26", @@ -61,7 +61,7 @@ "ID": "bash-sh@5.2.26-12.1.noarch", "Name": "bash-sh", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/bash-sh@5.2.26-12.1?arch=noarch&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/bash-sh@5.2.26-12.1?arch=noarch&distro=opensuse.tumbleweed-20240607", "UID": "7363186d472571e0" }, "Version": "5.2.26", @@ -87,7 +87,7 @@ "ID": "boost-license1_85_0@1.85.0-1.2.noarch", "Name": "boost-license1_85_0", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/boost-license1_85_0@1.85.0-1.2?arch=noarch&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/boost-license1_85_0@1.85.0-1.2?arch=noarch&distro=opensuse.tumbleweed-20240607", "UID": "2d87c856df6862ee" }, "Version": "1.85.0", @@ -110,7 +110,7 @@ "ID": "branding-openSUSE@84.87.20240405-1.2.noarch", "Name": "branding-openSUSE", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/branding-openSUSE@84.87.20240405-1.2?arch=noarch&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/branding-openSUSE@84.87.20240405-1.2?arch=noarch&distro=opensuse.tumbleweed-20240607", "UID": "acda90b5f91cb463" }, "Version": "84.87.20240405", @@ -133,7 +133,7 @@ "ID": "ca-certificates@2+git20240415.3fe9324-1.1.noarch", "Name": "ca-certificates", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/ca-certificates@2%2Bgit20240415.3fe9324-1.1?arch=noarch&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/ca-certificates@2%2Bgit20240415.3fe9324-1.1?arch=noarch&distro=opensuse.tumbleweed-20240607", "UID": "7a18ce239fe8c044" }, "Version": "2+git20240415.3fe9324", @@ -162,7 +162,7 @@ "ID": "ca-certificates-mozilla@2.66-1.2.noarch", "Name": "ca-certificates-mozilla", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/ca-certificates-mozilla@2.66-1.2?arch=noarch&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/ca-certificates-mozilla@2.66-1.2?arch=noarch&distro=opensuse.tumbleweed-20240607", "UID": "362f343aa3c5d416" }, "Version": "2.66", @@ -189,7 +189,7 @@ "ID": "coreutils@9.5-1.1.x86_64", "Name": "coreutils", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/coreutils@9.5-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/coreutils@9.5-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "9483aa372c47866d" }, "Version": "9.5", @@ -221,7 +221,7 @@ "ID": "cracklib-dict-small@2.9.11-1.4.x86_64", "Name": "cracklib-dict-small", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/cracklib-dict-small@2.9.11-1.4?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/cracklib-dict-small@2.9.11-1.4?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "cc38aea883124e41" }, "Version": "2.9.11", @@ -244,7 +244,7 @@ "ID": "crypto-policies@20230920.570ea89-3.2.noarch", "Name": "crypto-policies", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/crypto-policies@20230920.570ea89-3.2?arch=noarch&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/crypto-policies@20230920.570ea89-3.2?arch=noarch&distro=opensuse.tumbleweed-20240607", "UID": "ebef887879b412aa" }, "Version": "20230920.570ea89", @@ -267,7 +267,7 @@ "ID": "curl@8.8.0-1.1.x86_64", "Name": "curl", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/curl@8.8.0-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/curl@8.8.0-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "50b6514bae052e62" }, "Version": "8.8.0", @@ -295,7 +295,7 @@ "ID": "file-magic@5.45-2.2.noarch", "Name": "file-magic", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/file-magic@5.45-2.2?arch=noarch&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/file-magic@5.45-2.2?arch=noarch&distro=opensuse.tumbleweed-20240607", "UID": "9318efe3deebc83a" }, "Version": "5.45", @@ -318,7 +318,7 @@ "ID": "filesystem@84.87-15.3.x86_64", "Name": "filesystem", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/filesystem@84.87-15.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/filesystem@84.87-15.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "379508af5bc6bae5" }, "Version": "84.87", @@ -344,7 +344,7 @@ "ID": "fillup@1.42-281.1.x86_64", "Name": "fillup", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/fillup@1.42-281.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/fillup@1.42-281.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "7d48bfb3846c8056" }, "Version": "1.42", @@ -370,7 +370,7 @@ "ID": "glibc@2.39-9.1.x86_64", "Name": "glibc", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/glibc@2.39-9.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/glibc@2.39-9.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "77433316d747193b" }, "Version": "2.39", @@ -396,7 +396,7 @@ "ID": "glibc-locale-base@2.39-9.1.x86_64", "Name": "glibc-locale-base", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/glibc-locale-base@2.39-9.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/glibc-locale-base@2.39-9.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "7f6f8a5c2e27af75" }, "Version": "2.39", @@ -422,7 +422,7 @@ "ID": "gpg-pubkey@29b700a4-62b07e22.", "Name": "gpg-pubkey", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/gpg-pubkey@29b700a4-62b07e22?arch=None&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/gpg-pubkey@29b700a4-62b07e22?arch=None&distro=opensuse.tumbleweed-20240607", "UID": "562934f3f56669a5" }, "Version": "29b700a4", @@ -440,7 +440,7 @@ "ID": "gpg-pubkey@39db7c82-510a966b.", "Name": "gpg-pubkey", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/gpg-pubkey@39db7c82-510a966b?arch=None&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/gpg-pubkey@39db7c82-510a966b?arch=None&distro=opensuse.tumbleweed-20240607", "UID": "5e72dadde79df0d4" }, "Version": "39db7c82", @@ -458,7 +458,7 @@ "ID": "gpg2@2.4.5-1.1.x86_64", "Name": "gpg2", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/gpg2@2.4.5-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/gpg2@2.4.5-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "e95cc1c58ec7e824" }, "Version": "2.4.5", @@ -496,7 +496,7 @@ "ID": "grep@3.11-3.1.x86_64", "Name": "grep", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/grep@3.11-3.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/grep@3.11-3.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "7c3b5ec5d53fa9f9" }, "Version": "3.11", @@ -524,7 +524,7 @@ "ID": "gzip@1.13-3.1.x86_64", "Name": "gzip", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/gzip@1.13-3.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/gzip@1.13-3.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "f51af60e831e41e" }, "Version": "1.13", @@ -551,7 +551,7 @@ "ID": "krb5@1.21.2-5.1.x86_64", "Name": "krb5", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/krb5@1.21.2-5.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/krb5@1.21.2-5.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "f22a7694d8a232ac" }, "Version": "1.21.2", @@ -583,7 +583,7 @@ "ID": "libabsl_lite_2401_0_0@20240116.2-2.1.x86_64", "Name": "libabsl_lite_2401_0_0", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libabsl_lite_2401_0_0@20240116.2-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libabsl_lite_2401_0_0@20240116.2-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "24307f175234d50" }, "Version": "20240116.2", @@ -611,7 +611,7 @@ "ID": "libacl1@2.3.2-2.1.x86_64", "Name": "libacl1", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libacl1@2.3.2-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libacl1@2.3.2-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "6e55e249889869ed" }, "Version": "2.3.2", @@ -637,7 +637,7 @@ "ID": "libassuan0@2.5.7-1.1.x86_64", "Name": "libassuan0", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libassuan0@2.5.7-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libassuan0@2.5.7-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "4a9f149fc3b4d802" }, "Version": "2.5.7", @@ -664,7 +664,7 @@ "ID": "libattr1@2.5.2-1.2.x86_64", "Name": "libattr1", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libattr1@2.5.2-1.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libattr1@2.5.2-1.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "bf6e596e053cc667" }, "Version": "2.5.2", @@ -690,7 +690,7 @@ "ID": "libaudit1@3.1.1-1.6.x86_64", "Name": "libaudit1", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libaudit1@3.1.1-1.6?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libaudit1@3.1.1-1.6?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "c2ab09cc3b09bf56" }, "Version": "3.1.1", @@ -716,7 +716,7 @@ "ID": "libaugeas0@1.14.1-1.3.x86_64", "Name": "libaugeas0", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libaugeas0@1.14.1-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libaugeas0@1.14.1-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "bc9b541f623eec37" }, "Version": "1.14.1", @@ -744,7 +744,7 @@ "ID": "libblkid1@2.40.1-2.1.x86_64", "Name": "libblkid1", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libblkid1@2.40.1-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libblkid1@2.40.1-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "bcf4491906d1eb4d" }, "Version": "2.40.1", @@ -771,7 +771,7 @@ "ID": "libboost_thread1_85_0@1.85.0-1.2.x86_64", "Name": "libboost_thread1_85_0", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libboost_thread1_85_0@1.85.0-1.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libboost_thread1_85_0@1.85.0-1.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "b8612fd1d8aa51a7" }, "Version": "1.85.0", @@ -800,7 +800,7 @@ "ID": "libbrotlicommon1@1.1.0-1.3.x86_64", "Name": "libbrotlicommon1", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libbrotlicommon1@1.1.0-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libbrotlicommon1@1.1.0-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "f1d7b84b18abde08" }, "Version": "1.1.0", @@ -826,7 +826,7 @@ "ID": "libbrotlidec1@1.1.0-1.3.x86_64", "Name": "libbrotlidec1", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libbrotlidec1@1.1.0-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libbrotlidec1@1.1.0-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "5c297a82e6701a0d" }, "Version": "1.1.0", @@ -853,7 +853,7 @@ "ID": "libbz2-1@1.0.8-5.10.x86_64", "Name": "libbz2-1", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libbz2-1@1.0.8-5.10?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libbz2-1@1.0.8-5.10?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "702f3dd378cba8f0" }, "Version": "1.0.8", @@ -879,7 +879,7 @@ "ID": "libcap-ng0@0.8.5-1.1.x86_64", "Name": "libcap-ng0", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libcap-ng0@0.8.5-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libcap-ng0@0.8.5-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "b40d6fdd09912405" }, "Version": "0.8.5", @@ -905,7 +905,7 @@ "ID": "libcap2@2.70-1.1.x86_64", "Name": "libcap2", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libcap2@2.70-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libcap2@2.70-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "c33018bbf8c4bdfa" }, "Version": "2.70", @@ -931,7 +931,7 @@ "ID": "libcom_err2@1.47.0-4.2.x86_64", "Name": "libcom_err2", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libcom_err2@1.47.0-4.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libcom_err2@1.47.0-4.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "58b023020895cfea" }, "Version": "1.47.0", @@ -957,7 +957,7 @@ "ID": "libcrypt1@4.4.36-1.6.x86_64", "Name": "libcrypt1", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libcrypt1@4.4.36-1.6?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libcrypt1@4.4.36-1.6?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "541be9a801034440" }, "Version": "4.4.36", @@ -983,7 +983,7 @@ "ID": "libcurl4@8.8.0-1.1.x86_64", "Name": "libcurl4", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libcurl4@8.8.0-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libcurl4@8.8.0-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "59c3c7a8962c110a" }, "Version": "8.8.0", @@ -1019,7 +1019,7 @@ "ID": "libeconf0@0.6.3-1.1.x86_64", "Name": "libeconf0", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libeconf0@0.6.3-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libeconf0@0.6.3-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "9e3e97464bc6164b" }, "Version": "0.6.3", @@ -1045,7 +1045,7 @@ "ID": "libfa1@1.14.1-1.3.x86_64", "Name": "libfa1", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libfa1@1.14.1-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libfa1@1.14.1-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "9df420b84b79a62" }, "Version": "1.14.1", @@ -1071,7 +1071,7 @@ "ID": "libfdisk1@2.40.1-2.1.x86_64", "Name": "libfdisk1", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libfdisk1@2.40.1-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libfdisk1@2.40.1-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "ab47b44e7c45eab1" }, "Version": "2.40.1", @@ -1099,7 +1099,7 @@ "ID": "libffi8@3.4.6-1.1.x86_64", "Name": "libffi8", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libffi8@3.4.6-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libffi8@3.4.6-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "a569681a5276bde6" }, "Version": "3.4.6", @@ -1125,7 +1125,7 @@ "ID": "libgcc_s1@14.1.0+git10173-1.1.x86_64", "Name": "libgcc_s1", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libgcc_s1@14.1.0%2Bgit10173-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libgcc_s1@14.1.0%2Bgit10173-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "3130b825fbc3a81e" }, "Version": "14.1.0+git10173", @@ -1151,7 +1151,7 @@ "ID": "libgcrypt20@1.10.3-3.3.x86_64", "Name": "libgcrypt20", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libgcrypt20@1.10.3-3.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libgcrypt20@1.10.3-3.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "ac38e6e75132d1c6" }, "Version": "1.10.3", @@ -1178,7 +1178,7 @@ "ID": "libglib-2_0-0@2.80.2-1.1.x86_64", "Name": "libglib-2_0-0", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libglib-2_0-0@2.80.2-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libglib-2_0-0@2.80.2-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "e3dccc27a6f44a3d" }, "Version": "2.80.2", @@ -1205,7 +1205,7 @@ "ID": "libgmp10@6.3.0-3.2.x86_64", "Name": "libgmp10", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libgmp10@6.3.0-3.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libgmp10@6.3.0-3.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "fb3994e26d59ae4f" }, "Version": "6.3.0", @@ -1231,7 +1231,7 @@ "ID": "libgpg-error0@1.49-1.1.x86_64", "Name": "libgpg-error0", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libgpg-error0@1.49-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libgpg-error0@1.49-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "a3b16ea69b05fe60" }, "Version": "1.49", @@ -1257,7 +1257,7 @@ "ID": "libgpgme11@1.23.2-4.2.x86_64", "Name": "libgpgme11", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libgpgme11@1.23.2-4.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libgpgme11@1.23.2-4.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "6d9271ab523fb009" }, "Version": "1.23.2", @@ -1286,7 +1286,7 @@ "ID": "libidn2-0@2.3.7-1.2.x86_64", "Name": "libidn2-0", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libidn2-0@2.3.7-1.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libidn2-0@2.3.7-1.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "ae81c3e9fc0d0fc3" }, "Version": "2.3.7", @@ -1313,7 +1313,7 @@ "ID": "libkeyutils1@1.6.3-7.2.x86_64", "Name": "libkeyutils1", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libkeyutils1@1.6.3-7.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libkeyutils1@1.6.3-7.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "f9f931edfe4b540c" }, "Version": "1.6.3", @@ -1339,7 +1339,7 @@ "ID": "libksba8@1.6.6-1.1.x86_64", "Name": "libksba8", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libksba8@1.6.6-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libksba8@1.6.6-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "c532eef98bb36938" }, "Version": "1.6.6", @@ -1366,7 +1366,7 @@ "ID": "libldap2@2.6.7-2.1.x86_64", "Name": "libldap2", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libldap2@2.6.7-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libldap2@2.6.7-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "55fa8e45be9ed78" }, "Version": "2.6.7", @@ -1394,7 +1394,7 @@ "ID": "liblua5_4-5@5.4.6-3.3.x86_64", "Name": "liblua5_4-5", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/liblua5_4-5@5.4.6-3.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/liblua5_4-5@5.4.6-3.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "98b4001b2f59f46" }, "Version": "5.4.6", @@ -1420,7 +1420,7 @@ "ID": "liblz4-1@1.9.4-2.8.x86_64", "Name": "liblz4-1", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/liblz4-1@1.9.4-2.8?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/liblz4-1@1.9.4-2.8?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "267a6bfb140f0d45" }, "Version": "1.9.4", @@ -1446,7 +1446,7 @@ "ID": "liblzma5@5.6.2-1.1.x86_64", "Name": "liblzma5", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/liblzma5@5.6.2-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/liblzma5@5.6.2-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "304510f1f6669e2c" }, "Version": "5.6.2", @@ -1472,7 +1472,7 @@ "ID": "libmagic1@5.45-2.2.x86_64", "Name": "libmagic1", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libmagic1@5.45-2.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libmagic1@5.45-2.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "d8fdc2934df34a83" }, "Version": "5.45", @@ -1503,7 +1503,7 @@ "ID": "libmount1@2.40.1-2.1.x86_64", "Name": "libmount1", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libmount1@2.40.1-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libmount1@2.40.1-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "8386ec24a06557ea" }, "Version": "2.40.1", @@ -1531,7 +1531,7 @@ "ID": "libncurses6@6.5.20240601-38.1.x86_64", "Name": "libncurses6", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libncurses6@6.5.20240601-38.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libncurses6@6.5.20240601-38.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "9513bf16199cee6b" }, "Version": "6.5.20240601", @@ -1560,7 +1560,7 @@ "ID": "libnghttp2-14@1.61.0-1.1.x86_64", "Name": "libnghttp2-14", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libnghttp2-14@1.61.0-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libnghttp2-14@1.61.0-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "de28696676fc1ebd" }, "Version": "1.61.0", @@ -1586,7 +1586,7 @@ "ID": "libnpth0@1.7-1.1.x86_64", "Name": "libnpth0", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libnpth0@1.7-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libnpth0@1.7-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "7bff27e583fb62b3" }, "Version": "1.7", @@ -1612,7 +1612,7 @@ "ID": "libnss_usrfiles2@2.27.1-1.2.x86_64", "Name": "libnss_usrfiles2", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libnss_usrfiles2@2.27.1-1.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libnss_usrfiles2@2.27.1-1.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "d3c8c8f840c86b12" }, "Version": "2.27.1", @@ -1638,7 +1638,7 @@ "ID": "libopenssl-3-fips-provider@3.1.4-9.1.x86_64", "Name": "libopenssl-3-fips-provider", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libopenssl-3-fips-provider@3.1.4-9.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libopenssl-3-fips-provider@3.1.4-9.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "65c56c2870042412" }, "Version": "3.1.4", @@ -1665,7 +1665,7 @@ "ID": "libopenssl3@3.1.4-9.1.x86_64", "Name": "libopenssl3", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libopenssl3@3.1.4-9.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libopenssl3@3.1.4-9.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "f051425f385d2b99" }, "Version": "3.1.4", @@ -1693,7 +1693,7 @@ "ID": "libp11-kit0@0.25.3-1.3.x86_64", "Name": "libp11-kit0", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libp11-kit0@0.25.3-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libp11-kit0@0.25.3-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "fbca9a69218ce8e7" }, "Version": "0.25.3", @@ -1720,7 +1720,7 @@ "ID": "libpcre2-8-0@10.43-3.1.x86_64", "Name": "libpcre2-8-0", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libpcre2-8-0@10.43-3.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libpcre2-8-0@10.43-3.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "dabdfbc56d214ae6" }, "Version": "10.43", @@ -1746,7 +1746,7 @@ "ID": "libpopt0@1.19-1.8.x86_64", "Name": "libpopt0", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libpopt0@1.19-1.8?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libpopt0@1.19-1.8?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "98fa32fcd9ee1e39" }, "Version": "1.19", @@ -1772,7 +1772,7 @@ "ID": "libprocps8@3.3.17-17.1.x86_64", "Name": "libprocps8", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libprocps8@3.3.17-17.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libprocps8@3.3.17-17.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "f874f4997e1438be" }, "Version": "3.3.17", @@ -1799,7 +1799,7 @@ "ID": "libprotobuf-lite25_3_0@25.3-11.2.x86_64", "Name": "libprotobuf-lite25_3_0", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libprotobuf-lite25_3_0@25.3-11.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libprotobuf-lite25_3_0@25.3-11.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "b306bfd6494e6405" }, "Version": "25.3", @@ -1828,7 +1828,7 @@ "ID": "libpsl5@0.21.5-1.2.x86_64", "Name": "libpsl5", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libpsl5@0.21.5-1.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libpsl5@0.21.5-1.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "5d2411f7ede68692" }, "Version": "0.21.5", @@ -1856,7 +1856,7 @@ "ID": "libreadline8@8.2.10-1.3.x86_64", "Name": "libreadline8", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libreadline8@8.2.10-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libreadline8@8.2.10-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "9271e2cd0119054c" }, "Version": "8.2.10", @@ -1883,7 +1883,7 @@ "ID": "libsasl2-3@2.1.28-8.1.x86_64", "Name": "libsasl2-3", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libsasl2-3@2.1.28-8.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libsasl2-3@2.1.28-8.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "fe2536ad8601f334" }, "Version": "2.1.28", @@ -1909,7 +1909,7 @@ "ID": "libselinux1@3.6-1.3.x86_64", "Name": "libselinux1", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libselinux1@3.6-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libselinux1@3.6-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "6bc8fe60a073ba96" }, "Version": "3.6", @@ -1936,7 +1936,7 @@ "ID": "libsemanage-conf@3.6-2.1.x86_64", "Name": "libsemanage-conf", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libsemanage-conf@3.6-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libsemanage-conf@3.6-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "56c91988ca2e8ce5" }, "Version": "3.6", @@ -1959,7 +1959,7 @@ "ID": "libsemanage2@3.6-2.1.x86_64", "Name": "libsemanage2", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libsemanage2@3.6-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libsemanage2@3.6-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "d945b0271ed45cf5" }, "Version": "3.6", @@ -1990,7 +1990,7 @@ "ID": "libsepol2@3.6-1.3.x86_64", "Name": "libsepol2", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libsepol2@3.6-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libsepol2@3.6-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "f2aaf81754d3169d" }, "Version": "3.6", @@ -2016,7 +2016,7 @@ "ID": "libsigc-2_0-0@2.12.1-2.3.x86_64", "Name": "libsigc-2_0-0", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libsigc-2_0-0@2.12.1-2.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libsigc-2_0-0@2.12.1-2.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "c4d52d6f33dee391" }, "Version": "2.12.1", @@ -2044,7 +2044,7 @@ "ID": "libsmartcols1@2.40.1-2.1.x86_64", "Name": "libsmartcols1", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libsmartcols1@2.40.1-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libsmartcols1@2.40.1-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "5302abe63411170d" }, "Version": "2.40.1", @@ -2070,7 +2070,7 @@ "ID": "libsolv-tools-base@0.7.29-1.1.x86_64", "Name": "libsolv-tools-base", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libsolv-tools-base@0.7.29-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libsolv-tools-base@0.7.29-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "f2adb3efc201c696" }, "Version": "0.7.29", @@ -2102,7 +2102,7 @@ "ID": "libsqlite3-0@3.46.0-1.1.x86_64", "Name": "libsqlite3-0", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libsqlite3-0@3.46.0-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libsqlite3-0@3.46.0-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "d9bf1a49d16f0c" }, "Version": "3.46.0", @@ -2128,7 +2128,7 @@ "ID": "libssh-config@0.10.6-2.1.x86_64", "Name": "libssh-config", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libssh-config@0.10.6-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libssh-config@0.10.6-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "8628d51e34c2f5b1" }, "Version": "0.10.6", @@ -2151,7 +2151,7 @@ "ID": "libssh4@0.10.6-2.1.x86_64", "Name": "libssh4", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libssh4@0.10.6-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libssh4@0.10.6-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "d07880785aee16c8" }, "Version": "0.10.6", @@ -2181,7 +2181,7 @@ "ID": "libstdc++6@14.1.0+git10173-1.1.x86_64", "Name": "libstdc++6", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libstdc%2B%2B6@14.1.0%2Bgit10173-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libstdc%2B%2B6@14.1.0%2Bgit10173-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "f3345c3d3261e7e9" }, "Version": "14.1.0+git10173", @@ -2208,7 +2208,7 @@ "ID": "libsubid4@4.15.1-1.2.x86_64", "Name": "libsubid4", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libsubid4@4.15.1-1.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libsubid4@4.15.1-1.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "e155b313aa6da812" }, "Version": "4.15.1", @@ -2236,7 +2236,7 @@ "ID": "libsystemd0@255.7-2.1.x86_64", "Name": "libsystemd0", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libsystemd0@255.7-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libsystemd0@255.7-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "4fa3c2608f054287" }, "Version": "255.7", @@ -2267,7 +2267,7 @@ "ID": "libtasn1-6@4.19.0-1.7.x86_64", "Name": "libtasn1-6", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libtasn1-6@4.19.0-1.7?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libtasn1-6@4.19.0-1.7?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "35e287fcdf033bd1" }, "Version": "4.19.0", @@ -2293,7 +2293,7 @@ "ID": "libudev1@255.7-2.1.x86_64", "Name": "libudev1", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libudev1@255.7-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libudev1@255.7-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "4ae1c62105f1f901" }, "Version": "255.7", @@ -2320,7 +2320,7 @@ "ID": "libunistring5@1.2-1.1.x86_64", "Name": "libunistring5", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libunistring5@1.2-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libunistring5@1.2-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "e8be56f8ad59a760" }, "Version": "1.2", @@ -2346,7 +2346,7 @@ "ID": "libusb-1_0-0@1.0.27-1.2.x86_64", "Name": "libusb-1_0-0", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libusb-1_0-0@1.0.27-1.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libusb-1_0-0@1.0.27-1.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "dab90c8d517b4ee4" }, "Version": "1.0.27", @@ -2373,7 +2373,7 @@ "ID": "libuuid1@2.40.1-2.1.x86_64", "Name": "libuuid1", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libuuid1@2.40.1-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libuuid1@2.40.1-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "bc5c46e1650d4a95" }, "Version": "2.40.1", @@ -2399,7 +2399,7 @@ "ID": "libverto1@0.3.2-3.3.x86_64", "Name": "libverto1", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libverto1@0.3.2-3.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libverto1@0.3.2-3.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "8c13b7ac8ed99616" }, "Version": "0.3.2", @@ -2425,7 +2425,7 @@ "ID": "libxml2-2@2.12.7-1.1.x86_64", "Name": "libxml2-2", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libxml2-2@2.12.7-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libxml2-2@2.12.7-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "1285499ab636c5d9" }, "Version": "2.12.7", @@ -2453,7 +2453,7 @@ "ID": "libyaml-cpp0_8@0.8.0-1.3.x86_64", "Name": "libyaml-cpp0_8", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libyaml-cpp0_8@0.8.0-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libyaml-cpp0_8@0.8.0-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "d743795a2d65f87b" }, "Version": "0.8.0", @@ -2481,7 +2481,7 @@ "ID": "libz1@1.3.1-1.1.x86_64", "Name": "libz1", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libz1@1.3.1-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libz1@1.3.1-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "f09857fffac622a" }, "Version": "1.3.1", @@ -2507,7 +2507,7 @@ "ID": "libzck1@1.4.0-2.1.x86_64", "Name": "libzck1", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libzck1@1.4.0-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libzck1@1.4.0-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "76b3d8e58402a974" }, "Version": "1.4.0", @@ -2535,7 +2535,7 @@ "ID": "libzstd1@1.5.6-1.1.x86_64", "Name": "libzstd1", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libzstd1@1.5.6-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libzstd1@1.5.6-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "4edc1117cd2019eb" }, "Version": "1.5.6", @@ -2561,7 +2561,7 @@ "ID": "libzypp@17.34.1-1.1.x86_64", "Name": "libzypp", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/libzypp@17.34.1-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/libzypp@17.34.1-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "3545239e91f3bd9" }, "Version": "17.34.1", @@ -2606,7 +2606,7 @@ "ID": "login_defs@4.15.1-1.2.noarch", "Name": "login_defs", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/login_defs@4.15.1-1.2?arch=noarch&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/login_defs@4.15.1-1.2?arch=noarch&distro=opensuse.tumbleweed-20240607", "UID": "1695371f9551a301" }, "Version": "4.15.1", @@ -2632,7 +2632,7 @@ "ID": "lsb-release@3.3-1.3.noarch", "Name": "lsb-release", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/lsb-release@3.3-1.3?arch=noarch&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/lsb-release@3.3-1.3?arch=noarch&distro=opensuse.tumbleweed-20240607", "UID": "8c82a3a248c52a13" }, "Version": "3.3", @@ -2659,7 +2659,7 @@ "ID": "ncurses-utils@6.5.20240601-38.1.x86_64", "Name": "ncurses-utils", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/ncurses-utils@6.5.20240601-38.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/ncurses-utils@6.5.20240601-38.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "90d23a67ceb37784" }, "Version": "6.5.20240601", @@ -2686,7 +2686,7 @@ "ID": "netcfg@11.6-13.3.noarch", "Name": "netcfg", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/netcfg@11.6-13.3?arch=noarch&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/netcfg@11.6-13.3?arch=noarch&distro=opensuse.tumbleweed-20240607", "UID": "c32526003d9c5528" }, "Version": "11.6", @@ -2712,7 +2712,7 @@ "ID": "openSUSE-build-key@1.0-53.1.x86_64", "Name": "openSUSE-build-key", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/openSUSE-build-key@1.0-53.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/openSUSE-build-key@1.0-53.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "ed8309d0e84993e4" }, "Version": "1.0", @@ -2739,7 +2739,7 @@ "ID": "openSUSE-release@20240607-2943.1.x86_64", "Name": "openSUSE-release", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/openSUSE-release@20240607-2943.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/openSUSE-release@20240607-2943.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "ad908712f8c8e5ab" }, "Version": "20240607", @@ -2766,7 +2766,7 @@ "ID": "openSUSE-release-appliance-docker@20240607-2943.1.x86_64", "Name": "openSUSE-release-appliance-docker", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/openSUSE-release-appliance-docker@20240607-2943.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/openSUSE-release-appliance-docker@20240607-2943.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "46f06026407817a0" }, "Version": "20240607", @@ -2789,7 +2789,7 @@ "ID": "openssl@3.1.4-3.2.noarch", "Name": "openssl", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/openssl@3.1.4-3.2?arch=noarch&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/openssl@3.1.4-3.2?arch=noarch&distro=opensuse.tumbleweed-20240607", "UID": "cd2ead77021cf857" }, "Version": "3.1.4", @@ -2815,7 +2815,7 @@ "ID": "openssl-3@3.1.4-9.1.x86_64", "Name": "openssl-3", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/openssl-3@3.1.4-9.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/openssl-3@3.1.4-9.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "da148866e5ba5d92" }, "Version": "3.1.4", @@ -2846,7 +2846,7 @@ "ID": "p11-kit@0.25.3-1.3.x86_64", "Name": "p11-kit", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/p11-kit@0.25.3-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/p11-kit@0.25.3-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "7da38dbf3cd84149" }, "Version": "0.25.3", @@ -2874,7 +2874,7 @@ "ID": "p11-kit-tools@0.25.3-1.3.x86_64", "Name": "p11-kit-tools", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/p11-kit-tools@0.25.3-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/p11-kit-tools@0.25.3-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "fb534863cc7b3050" }, "Version": "0.25.3", @@ -2902,7 +2902,7 @@ "ID": "pam@1.6.1-1.1.x86_64", "Name": "pam", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/pam@1.6.1-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/pam@1.6.1-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "2cc82a7c85091dc0" }, "Version": "1.6.1", @@ -2935,7 +2935,7 @@ "ID": "patterns-base-fips@20200505-51.1.x86_64", "Name": "patterns-base-fips", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/patterns-base-fips@20200505-51.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/patterns-base-fips@20200505-51.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "70a74594ade38509" }, "Version": "20200505", @@ -2958,7 +2958,7 @@ "ID": "patterns-base-minimal_base@20200505-51.1.x86_64", "Name": "patterns-base-minimal_base", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/patterns-base-minimal_base@20200505-51.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/patterns-base-minimal_base@20200505-51.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "22550c4b68de6581" }, "Version": "20200505", @@ -2987,7 +2987,7 @@ "ID": "permctl@1699_20240522-1.1.x86_64", "Name": "permctl", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/permctl@1699_20240522-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/permctl@1699_20240522-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "cfcd9931dafbea39" }, "Version": "1699_20240522", @@ -3016,7 +3016,7 @@ "ID": "permissions@1699_20240522-1.1.x86_64", "Name": "permissions", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/permissions@1699_20240522-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/permissions@1699_20240522-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "971d93fae8da6b23" }, "Version": "1699_20240522", @@ -3043,7 +3043,7 @@ "ID": "permissions-config@1699_20240522-1.1.noarch", "Name": "permissions-config", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/permissions-config@1699_20240522-1.1?arch=noarch&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/permissions-config@1699_20240522-1.1?arch=noarch&distro=opensuse.tumbleweed-20240607", "UID": "8bd3994be34b3e73" }, "Version": "1699_20240522", @@ -3072,7 +3072,7 @@ "ID": "pinentry@1.2.1-3.5.x86_64", "Name": "pinentry", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/pinentry@1.2.1-3.5?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/pinentry@1.2.1-3.5?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "90686edea2822ef8" }, "Version": "1.2.1", @@ -3102,7 +3102,7 @@ "ID": "procps@3.3.17-17.1.x86_64", "Name": "procps", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/procps@3.3.17-17.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/procps@3.3.17-17.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "41a25e357a85fe17" }, "Version": "3.3.17", @@ -3131,7 +3131,7 @@ "ID": "rpm@4.19.1.1-3.2.x86_64", "Name": "rpm", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/rpm@4.19.1.1-3.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/rpm@4.19.1.1-3.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "6385ed7e7827135a" }, "Version": "4.19.1.1", @@ -3170,7 +3170,7 @@ "ID": "rpm-config-SUSE@20240214-1.2.noarch", "Name": "rpm-config-SUSE", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/rpm-config-SUSE@20240214-1.2?arch=noarch&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/rpm-config-SUSE@20240214-1.2?arch=noarch&distro=opensuse.tumbleweed-20240607", "UID": "b0a53b3b9cd8de6e" }, "Version": "20240214", @@ -3198,7 +3198,7 @@ "ID": "sed@4.9-2.6.x86_64", "Name": "sed", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/sed@4.9-2.6?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/sed@4.9-2.6?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "465c6c9c97824acd" }, "Version": "4.9", @@ -3226,7 +3226,7 @@ "ID": "shadow@4.15.1-1.2.x86_64", "Name": "shadow", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/shadow@4.15.1-1.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/shadow@4.15.1-1.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "7fefaa914168ef4f" }, "Version": "4.15.1", @@ -3265,7 +3265,7 @@ "ID": "system-user-root@20190513-2.16.noarch", "Name": "system-user-root", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/system-user-root@20190513-2.16?arch=noarch&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/system-user-root@20190513-2.16?arch=noarch&distro=opensuse.tumbleweed-20240607", "UID": "cc450033801f0db5" }, "Version": "20190513", @@ -3288,7 +3288,7 @@ "ID": "tar@1.35-2.2.x86_64", "Name": "tar", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/tar@1.35-2.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/tar@1.35-2.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "6f7d60b91f9b815f" }, "Version": "1.35", @@ -3316,7 +3316,7 @@ "ID": "terminfo-base@6.5.20240601-38.1.x86_64", "Name": "terminfo-base", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/terminfo-base@6.5.20240601-38.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/terminfo-base@6.5.20240601-38.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "ba53240ca965e6c0" }, "Version": "6.5.20240601", @@ -3342,7 +3342,7 @@ "ID": "timezone@2024a-3.2.x86_64", "Name": "timezone", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/timezone@2024a-3.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/timezone@2024a-3.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "aa7fc225c615b895" }, "Version": "2024a", @@ -3369,7 +3369,7 @@ "ID": "util-linux@2.40.1-2.1.x86_64", "Name": "util-linux", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/util-linux@2.40.1-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/util-linux@2.40.1-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "1440e3eb3dfc6c5" }, "Version": "2.40.1", @@ -3412,7 +3412,7 @@ "ID": "xz@5.6.2-1.1.x86_64", "Name": "xz", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/xz@5.6.2-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/xz@5.6.2-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "1c46963e750a4a9" }, "Version": "5.6.2", @@ -3440,7 +3440,7 @@ "ID": "zypper@1.14.73-1.2.x86_64", "Name": "zypper", "Identifier": { - "PURL": "pkg:rpm/opensuse.tumbleweed/zypper@1.14.73-1.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "PURL": "pkg:rpm/opensuse/zypper@1.14.73-1.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", "UID": "9d7cafcab0f1fed2" }, "Version": "1.14.73", diff --git a/pkg/fanal/test/integration/testdata/goldens/packages/vulnimage.json.golden b/pkg/fanal/test/integration/testdata/goldens/packages/vulnimage.json.golden index 4bfbb16a3dff..b4a8d982d9f7 100644 --- a/pkg/fanal/test/integration/testdata/goldens/packages/vulnimage.json.golden +++ b/pkg/fanal/test/integration/testdata/goldens/packages/vulnimage.json.golden @@ -71,14 +71,14 @@ "Name": "alpine-baselayout", "Identifier": { "PURL": "pkg:apk/alpine/alpine-baselayout@3.0.5-r2?arch=x86_64\u0026distro=3.7.1", - "UID": "fb3e0109c30ad75d" + "UID": "1fd865bbd91dfad4" }, "Version": "3.0.5-r2", "Arch": "x86_64", "SrcName": "alpine-baselayout", "SrcVersion": "3.0.5-r2", "Licenses": [ - "GPL-2.0" + "GPL-2.0-only" ], "DependsOn": [ "busybox@1.27.2-r11", @@ -163,14 +163,14 @@ "Name": "apk-tools", "Identifier": { "PURL": "pkg:apk/alpine/apk-tools@2.10.1-r0?arch=x86_64\u0026distro=3.7.1", - "UID": "44e4204bb4dc2b2a" + "UID": "78262f2dc70b3ede" }, "Version": "2.10.1-r0", "Arch": "x86_64", "SrcName": "apk-tools", "SrcVersion": "2.10.1-r0", "Licenses": [ - "GPL-2.0" + "GPL-2.0-only" ], "DependsOn": [ "libressl2.6-libcrypto@2.6.5-r0", @@ -192,14 +192,14 @@ "Name": "apr", "Identifier": { "PURL": "pkg:apk/alpine/apr@1.6.3-r0?arch=x86_64\u0026distro=3.7.1", - "UID": "715dd499dcec48f9" + "UID": "64f7de29e73c4636" }, "Version": "1.6.3-r0", "Arch": "x86_64", "SrcName": "apr", "SrcVersion": "1.6.3-r0", "Licenses": [ - "ASL2.0" + "Apache-2.0" ], "DependsOn": [ "libuuid@2.31-r0", @@ -221,14 +221,14 @@ "Name": "apr-util", "Identifier": { "PURL": "pkg:apk/alpine/apr-util@1.6.1-r1?arch=x86_64\u0026distro=3.7.1", - "UID": "2aa1dd25c68a60eb" + "UID": "2d3f23b8b61a097c" }, "Version": "1.6.1-r1", "Arch": "x86_64", "SrcName": "apr-util", "SrcVersion": "1.6.1-r1", "Licenses": [ - "ASL2.0" + "Apache-2.0" ], "DependsOn": [ "apr@1.6.3-r0", @@ -253,14 +253,14 @@ "Name": "bash", "Identifier": { "PURL": "pkg:apk/alpine/bash@4.4.19-r1?arch=x86_64\u0026distro=3.7.1", - "UID": "32e82b55d6afc293" + "UID": "c7841dcc19f73c7e" }, "Version": "4.4.19-r1", "Arch": "x86_64", "SrcName": "bash", "SrcVersion": "4.4.19-r1", "Licenses": [ - "GPL-3.0" + "GPL-3.0-or-later" ], "DependsOn": [ "busybox@1.27.2-r11", @@ -368,14 +368,14 @@ "Name": "busybox", "Identifier": { "PURL": "pkg:apk/alpine/busybox@1.27.2-r11?arch=x86_64\u0026distro=3.7.1", - "UID": "e32c8aeae1daa385" + "UID": "d936440f5fd65b0a" }, "Version": "1.27.2-r11", "Arch": "x86_64", "SrcName": "busybox", "SrcVersion": "1.27.2-r11", "Licenses": [ - "GPL-2.0" + "GPL-2.0-only" ], "DependsOn": [ "musl@1.1.18-r3" @@ -399,7 +399,7 @@ "Name": "ca-certificates", "Identifier": { "PURL": "pkg:apk/alpine/ca-certificates@20171114-r0?arch=x86_64\u0026distro=3.7.1", - "UID": "fc49474e56f2cdca" + "UID": "88fab2304b2bb95" }, "Version": "20171114-r0", "Arch": "x86_64", @@ -407,7 +407,7 @@ "SrcVersion": "20171114-r0", "Licenses": [ "MPL-2.0", - "GPL-2.0" + "GPL-2.0-or-later" ], "DependsOn": [ "busybox@1.27.2-r11", @@ -666,14 +666,14 @@ "Name": "gdbm", "Identifier": { "PURL": "pkg:apk/alpine/gdbm@1.13-r1?arch=x86_64\u0026distro=3.7.1", - "UID": "eb4b3efac8d4d73f" + "UID": "1bbe6ee4fe37c0c5" }, "Version": "1.13-r1", "Arch": "x86_64", "SrcName": "gdbm", "SrcVersion": "1.13-r1", "Licenses": [ - "GPL-3.0" + "GPL-2.0-or-later" ], "DependsOn": [ "musl@1.1.18-r3" @@ -698,14 +698,14 @@ "Name": "git", "Identifier": { "PURL": "pkg:apk/alpine/git@2.15.2-r0?arch=x86_64\u0026distro=3.7.1", - "UID": "845b6214d48ea80e" + "UID": "8f0a9684f6888b5b" }, "Version": "2.15.2-r0", "Arch": "x86_64", "SrcName": "git", "SrcVersion": "2.15.2-r0", "Licenses": [ - "GPL-2.0" + "GPL-2.0-or-later" ], "DependsOn": [ "expat@2.2.5-r0", @@ -1271,16 +1271,16 @@ "Name": "libuuid", "Identifier": { "PURL": "pkg:apk/alpine/libuuid@2.31-r0?arch=x86_64\u0026distro=3.7.1", - "UID": "ec8a9d1580eb93d7" + "UID": "39de02f4a8b5e0c" }, "Version": "2.31-r0", "Arch": "x86_64", "SrcName": "util-linux", "SrcVersion": "2.31-r0", "Licenses": [ - "GPL-2.0", - "GPL-2.0", - "LGPL-2.0", + "GPL-2.0-only", + "GPL-2.0-or-later", + "LGPL-2.0-or-later", "BSD-3-Clause", "Public", "Domain" @@ -1331,14 +1331,14 @@ "Name": "mercurial", "Identifier": { "PURL": "pkg:apk/alpine/mercurial@4.5.2-r0?arch=x86_64\u0026distro=3.7.1", - "UID": "28d12c554475942a" + "UID": "6148e64298851c1b" }, "Version": "4.5.2-r0", "Arch": "x86_64", "SrcName": "mercurial", "SrcVersion": "4.5.2-r0", "Licenses": [ - "GPL-2.0" + "GPL-2.0-or-later" ], "DependsOn": [ "musl@1.1.18-r3", @@ -2100,7 +2100,7 @@ "Name": "musl-utils", "Identifier": { "PURL": "pkg:apk/alpine/musl-utils@1.1.18-r3?arch=x86_64\u0026distro=3.7.1", - "UID": "c2765ad0d8dd83f9" + "UID": "dc61f5453717063a" }, "Version": "1.1.18-r3", "Arch": "x86_64", @@ -2109,7 +2109,7 @@ "Licenses": [ "MIT", "BSD-3-Clause", - "GPL-2.0" + "GPL-2.0-or-later" ], "DependsOn": [ "musl@1.1.18-r3", @@ -5136,14 +5136,14 @@ "Name": "patch", "Identifier": { "PURL": "pkg:apk/alpine/patch@2.7.5-r2?arch=x86_64\u0026distro=3.7.1", - "UID": "c3945b28493e2842" + "UID": "be58b1df7c42f71e" }, "Version": "2.7.5-r2", "Arch": "x86_64", "SrcName": "patch", "SrcVersion": "2.7.5-r2", "Licenses": [ - "GPL-3.0" + "GPL-2.0-or-later" ], "DependsOn": [ "musl@1.1.18-r3" @@ -7667,14 +7667,14 @@ "Name": "readline", "Identifier": { "PURL": "pkg:apk/alpine/readline@7.0.003-r0?arch=x86_64\u0026distro=3.7.1", - "UID": "2cb2c7047f5911f5" + "UID": "f3dc1d91dea8744d" }, "Version": "7.0.003-r0", "Arch": "x86_64", "SrcName": "readline", "SrcVersion": "7.0.003-r0", "Licenses": [ - "GPL-3.0" + "GPL-2.0-or-later" ], "DependsOn": [ "musl@1.1.18-r3", @@ -7695,14 +7695,14 @@ "Name": "scanelf", "Identifier": { "PURL": "pkg:apk/alpine/scanelf@1.2.2-r1?arch=x86_64\u0026distro=3.7.1", - "UID": "88086fdb5a997cfd" + "UID": "8520c35436819" }, "Version": "1.2.2-r1", "Arch": "x86_64", "SrcName": "pax-utils", "SrcVersion": "1.2.2-r1", "Licenses": [ - "GPL-2.0" + "GPL-2.0-only" ], "DependsOn": [ "musl@1.1.18-r3" @@ -7721,14 +7721,14 @@ "Name": "serf", "Identifier": { "PURL": "pkg:apk/alpine/serf@1.3.9-r3?arch=x86_64\u0026distro=3.7.1", - "UID": "34d5052ef0071707" + "UID": "e052e8031c85839d" }, "Version": "1.3.9-r3", "Arch": "x86_64", "SrcName": "serf", "SrcVersion": "1.3.9-r3", "Licenses": [ - "ASL2.0" + "Apache-2.0" ], "DependsOn": [ "apr-util@1.6.1-r1", @@ -7780,14 +7780,14 @@ "Name": "ssl_client", "Identifier": { "PURL": "pkg:apk/alpine/ssl_client@1.27.2-r11?arch=x86_64\u0026distro=3.7.1", - "UID": "eab2a79208d922b" + "UID": "740ce998f526d2c2" }, "Version": "1.27.2-r11", "Arch": "x86_64", "SrcName": "busybox", "SrcVersion": "1.27.2-r11", "Licenses": [ - "GPL-2.0" + "GPL-2.0-only" ], "DependsOn": [ "libressl2.6-libtls@2.6.5-r0", @@ -7930,14 +7930,14 @@ "Name": "tar", "Identifier": { "PURL": "pkg:apk/alpine/tar@1.29-r1?arch=x86_64\u0026distro=3.7.1", - "UID": "636f4bd512a19da9" + "UID": "35fcd0737165df45" }, "Version": "1.29-r1", "Arch": "x86_64", "SrcName": "tar", "SrcVersion": "1.29-r1", "Licenses": [ - "GPL-3.0" + "GPL-2.0-or-later" ], "DependsOn": [ "musl@1.1.18-r3" diff --git a/pkg/fanal/types/const.go b/pkg/fanal/types/const.go index c257154e24ea..c304f40bac5f 100644 --- a/pkg/fanal/types/const.go +++ b/pkg/fanal/types/const.go @@ -37,6 +37,7 @@ const ( Photon OSType = "photon" RedHat OSType = "redhat" Rocky OSType = "rocky" + SLEMicro OSType = "suse linux enterprise micro" SLES OSType = "suse linux enterprise server" Ubuntu OSType = "ubuntu" Wolfi OSType = "wolfi" @@ -97,6 +98,7 @@ var AggregatingTypes = []LangType{ // Config files const ( JSON ConfigType = "json" + YAML ConfigType = "yaml" Dockerfile ConfigType = "dockerfile" Terraform ConfigType = "terraform" TerraformPlanJSON ConfigType = "terraformplan" diff --git a/pkg/fanal/types/detection.go b/pkg/fanal/types/detection.go new file mode 100644 index 000000000000..38d1634ed2a3 --- /dev/null +++ b/pkg/fanal/types/detection.go @@ -0,0 +1,10 @@ +package types + +// DetectionPriority represents the priority of detection +type DetectionPriority string + +// PriorityPrecise tries to minimize false positives +const PriorityPrecise DetectionPriority = "precise" + +// PriorityComprehensive tries to minimize false negatives +const PriorityComprehensive DetectionPriority = "comprehensive" diff --git a/pkg/fanal/types/package.go b/pkg/fanal/types/package.go index 822291e61c29..2726aa8ad8c8 100644 --- a/pkg/fanal/types/package.go +++ b/pkg/fanal/types/package.go @@ -78,7 +78,7 @@ type PkgIdentifier struct { } // MarshalJSON customizes the JSON encoding of PkgIdentifier. -func (id *PkgIdentifier) MarshalJSON() ([]byte, error) { +func (id PkgIdentifier) MarshalJSON() ([]byte, error) { var p string if id.PURL != nil { p = id.PURL.String() @@ -90,7 +90,7 @@ func (id *PkgIdentifier) MarshalJSON() ([]byte, error) { *Alias }{ PURL: p, - Alias: (*Alias)(id), + Alias: (*Alias)(&id), }) } diff --git a/pkg/fanal/utils/utils.go b/pkg/fanal/utils/utils.go index 1c5ea54b2612..463c8dd1f255 100644 --- a/pkg/fanal/utils/utils.go +++ b/pkg/fanal/utils/utils.go @@ -2,13 +2,21 @@ package utils import ( "bufio" + "bytes" "fmt" "io" "math" "os" "os/exec" "path/filepath" + "strings" + "unicode" + "github.com/bmatcuk/doublestar/v4" + "github.com/samber/lo" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -93,3 +101,60 @@ func IsBinary(content xio.ReadSeekerAt, fileSize int64) (bool, error) { return false, nil } + +func CleanSkipPaths(skipPaths []string) []string { + return lo.Map(skipPaths, func(skipPath string, index int) string { + skipPath = filepath.ToSlash(filepath.Clean(skipPath)) + return strings.TrimLeft(skipPath, "/") + }) +} + +func SkipPath(path string, skipPaths []string) bool { + path = strings.TrimLeft(path, "/") + + // skip files + for _, pattern := range skipPaths { + match, err := doublestar.Match(pattern, path) + if err != nil { + return false // return early if bad pattern + } else if match { + log.Debug("Skipping path", log.String("path", path)) + return true + } + } + return false +} + +func ExtractPrintableBytes(content xio.ReadSeekerAt) ([]byte, error) { + const minLength = 4 // Minimum length of strings to extract + var result []byte + currentPrintableLine := new(bytes.Buffer) + + current := make([]byte, 1) // buffer for 1 byte reading + + for { + if n, err := content.Read(current); err == io.EOF { + break + } else if n != 1 { + continue + } else if err != nil { + return nil, xerrors.Errorf("failed to read a byte: %w", err) + } + if unicode.IsPrint(rune(current[0])) { + _ = currentPrintableLine.WriteByte(current[0]) + continue + } + if currentPrintableLine.Len() > minLength { + // add a newline between printable lines to separate them + _ = currentPrintableLine.WriteByte('\n') + result = append(result, currentPrintableLine.Bytes()...) + } + currentPrintableLine.Reset() + } + if currentPrintableLine.Len() > minLength { + // add a newline between printable lines to separate them + _ = currentPrintableLine.WriteByte('\n') + result = append(result, currentPrintableLine.Bytes()...) + } + return result, nil +} diff --git a/pkg/fanal/walker/fs.go b/pkg/fanal/walker/fs.go index f6bec84d1fc2..3ebfb7258459 100644 --- a/pkg/fanal/walker/fs.go +++ b/pkg/fanal/walker/fs.go @@ -9,6 +9,7 @@ import ( "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/fanal/utils" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -53,13 +54,13 @@ func (w *FS) WalkDirFunc(root string, fn WalkFunc, opt Option) fs.WalkDirFunc { // Skip unnecessary files switch { case d.IsDir(): - if SkipPath(relPath, opt.SkipDirs) { + if utils.SkipPath(relPath, opt.SkipDirs) { return filepath.SkipDir } return nil case !d.Type().IsRegular(): return nil - case SkipPath(relPath, opt.SkipFiles): + case utils.SkipPath(relPath, opt.SkipFiles): return nil } @@ -148,7 +149,7 @@ func (w *FS) BuildSkipPaths(base string, paths []string) []string { relativePaths = append(relativePaths, relPath) } - relativePaths = CleanSkipPaths(relativePaths) + relativePaths = utils.CleanSkipPaths(relativePaths) return relativePaths } diff --git a/pkg/fanal/walker/tar.go b/pkg/fanal/walker/tar.go index 51f185d89c50..0c7d0bee9d5d 100644 --- a/pkg/fanal/walker/tar.go +++ b/pkg/fanal/walker/tar.go @@ -27,8 +27,8 @@ type LayerTar struct { func NewLayerTar(opt Option) LayerTar { return LayerTar{ - skipFiles: CleanSkipPaths(opt.SkipFiles), - skipDirs: CleanSkipPaths(opt.SkipDirs), + skipFiles: utils.CleanSkipPaths(opt.SkipFiles), + skipDirs: utils.CleanSkipPaths(opt.SkipDirs), } } @@ -63,12 +63,12 @@ func (w LayerTar) Walk(layer io.Reader, analyzeFn WalkFunc) ([]string, []string, switch hdr.Typeflag { case tar.TypeDir: - if SkipPath(filePath, w.skipDirs) { + if utils.SkipPath(filePath, w.skipDirs) { skippedDirs = append(skippedDirs, filePath) continue } case tar.TypeReg: - if SkipPath(filePath, w.skipFiles) { + if utils.SkipPath(filePath, w.skipFiles) { continue } // symlinks and hardlinks have no content in reader, skip them diff --git a/pkg/fanal/walker/vm.go b/pkg/fanal/walker/vm.go index 5d7336f1623c..f8ef63ce0252 100644 --- a/pkg/fanal/walker/vm.go +++ b/pkg/fanal/walker/vm.go @@ -9,11 +9,15 @@ import ( "strings" "github.com/masahiro331/go-disk" + diskFs "github.com/masahiro331/go-disk/fs" "github.com/masahiro331/go-disk/gpt" "github.com/masahiro331/go-disk/mbr" "github.com/masahiro331/go-disk/types" + "github.com/masahiro331/go-ext4-filesystem/ext4" + "github.com/masahiro331/go-xfs-filesystem/xfs" "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/fanal/utils" "github.com/aquasecurity/trivy/pkg/fanal/vm/filesystem" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" @@ -29,6 +33,11 @@ var requiredDiskName = []string{ "3", // Common image name } +var checkFsFuncs = []diskFs.CheckFsFunc{ + ext4.Check, + xfs.Check, +} + func AppendPermitDiskName(s ...string) { requiredDiskName = append(requiredDiskName, s...) } @@ -53,7 +62,7 @@ func (w *VM) Walk(vreader *io.SectionReader, root string, opt Option, fn WalkFun // This function will be called on each file. w.analyzeFn = fn - driver, err := disk.NewDriver(vreader) + driver, err := disk.NewDriver(vreader, checkFsFuncs...) if err != nil { return xerrors.Errorf("failed to new disk driver: %w", err) } @@ -123,13 +132,13 @@ func (w *VM) fsWalk(fsys fs.FS, path string, d fs.DirEntry, err error) error { pathName := strings.TrimPrefix(filepath.Clean(path), "/") switch { case fi.IsDir(): - if SkipPath(pathName, w.skipDirs) { + if utils.SkipPath(pathName, w.skipDirs) { return filepath.SkipDir } return nil case !fi.Mode().IsRegular(): return nil - case SkipPath(pathName, w.skipFiles): + case utils.SkipPath(pathName, w.skipFiles): return nil case fi.Mode()&0x1000 == 0x1000 || fi.Mode()&0x2000 == 0x2000 || diff --git a/pkg/fanal/walker/walk.go b/pkg/fanal/walker/walk.go index 52c13808d86b..0785cef41606 100644 --- a/pkg/fanal/walker/walk.go +++ b/pkg/fanal/walker/walk.go @@ -2,14 +2,8 @@ package walker import ( "os" - "path/filepath" - "strings" - - "github.com/bmatcuk/doublestar/v4" - "github.com/samber/lo" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" - "github.com/aquasecurity/trivy/pkg/log" ) const defaultSizeThreshold = int64(100) << 20 // 200MB @@ -27,26 +21,3 @@ type Option struct { } type WalkFunc func(filePath string, info os.FileInfo, opener analyzer.Opener) error - -func CleanSkipPaths(skipPaths []string) []string { - return lo.Map(skipPaths, func(skipPath string, index int) string { - skipPath = filepath.ToSlash(filepath.Clean(skipPath)) - return strings.TrimLeft(skipPath, "/") - }) -} - -func SkipPath(path string, skipPaths []string) bool { - path = strings.TrimLeft(path, "/") - - // skip files - for _, pattern := range skipPaths { - match, err := doublestar.Match(pattern, path) - if err != nil { - return false // return early if bad pattern - } else if match { - log.Debug("Skipping path", log.String("path", path)) - return true - } - } - return false -} diff --git a/pkg/fanal/walker/walk_test.go b/pkg/fanal/walker/walk_test.go index 0bac6009d04f..09d55527c0fe 100644 --- a/pkg/fanal/walker/walk_test.go +++ b/pkg/fanal/walker/walk_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/aquasecurity/trivy/pkg/fanal/walker" + "github.com/aquasecurity/trivy/pkg/fanal/utils" ) func TestSkipFile(t *testing.T) { @@ -64,7 +64,7 @@ func TestSkipFile(t *testing.T) { t.Run(tt.name, func(t *testing.T) { for file, want := range tt.wants { file = filepath.ToSlash(filepath.Clean(file)) - got := walker.SkipPath(file, walker.CleanSkipPaths(tt.skipFiles)) + got := utils.SkipPath(file, utils.CleanSkipPaths(tt.skipFiles)) assert.Equal(t, want, got, fmt.Sprintf("skipFiles: %s, file: %s", tt.skipFiles, file)) } }) @@ -138,7 +138,7 @@ func TestSkipDir(t *testing.T) { t.Run(tt.name, func(t *testing.T) { for dir, want := range tt.wants { dir = filepath.ToSlash(filepath.Clean(dir)) - got := walker.SkipPath(dir, walker.CleanSkipPaths(tt.skipDirs)) + got := utils.SkipPath(dir, utils.CleanSkipPaths(tt.skipDirs)) assert.Equal(t, want, got, fmt.Sprintf("defaultSkipDirs: %s, dir: %s", tt.skipDirs, dir)) } }) diff --git a/pkg/flag/cache_flags.go b/pkg/flag/cache_flags.go index 074953c2ea44..9cf8403a1e56 100644 --- a/pkg/flag/cache_flags.go +++ b/pkg/flag/cache_flags.go @@ -80,6 +80,7 @@ type CacheOptions struct { // NewCacheFlagGroup returns a default CacheFlagGroup func NewCacheFlagGroup() *CacheFlagGroup { return &CacheFlagGroup{ + ClearCache: ClearCacheFlag.Clone(), CacheBackend: CacheBackendFlag.Clone(), CacheTTL: CacheTTLFlag.Clone(), RedisTLS: RedisTLSFlag.Clone(), diff --git a/pkg/flag/db_flags.go b/pkg/flag/db_flags.go index 352a0094be7c..df0d6c6f5194 100644 --- a/pkg/flag/db_flags.go +++ b/pkg/flag/db_flags.go @@ -50,17 +50,17 @@ var ( ConfigName: "db.no-progress", Usage: "suppress progress bar", } - DBRepositoryFlag = Flag[string]{ + DBRepositoryFlag = Flag[[]string]{ Name: "db-repository", ConfigName: "db.repository", - Default: db.DefaultRepository, - Usage: "OCI repository to retrieve trivy-db from", + Default: []string{db.DefaultGHCRRepository}, + Usage: "OCI repository(ies) to retrieve trivy-db in order of priority", } - JavaDBRepositoryFlag = Flag[string]{ + JavaDBRepositoryFlag = Flag[[]string]{ Name: "java-db-repository", ConfigName: "db.java-repository", - Default: javadb.DefaultRepository, - Usage: "OCI repository to retrieve trivy-java-db from", + Default: []string{javadb.DefaultGHCRRepository}, + Usage: "OCI repository(ies) to retrieve trivy-java-db in order of priority", } LightFlag = Flag[bool]{ Name: "light", @@ -78,8 +78,8 @@ type DBFlagGroup struct { DownloadJavaDBOnly *Flag[bool] SkipJavaDBUpdate *Flag[bool] NoProgress *Flag[bool] - DBRepository *Flag[string] - JavaDBRepository *Flag[string] + DBRepositories *Flag[[]string] + JavaDBRepositories *Flag[[]string] Light *Flag[bool] // deprecated } @@ -90,8 +90,8 @@ type DBOptions struct { DownloadJavaDBOnly bool SkipJavaDBUpdate bool NoProgress bool - DBRepository name.Reference - JavaDBRepository name.Reference + DBRepositories []name.Reference + JavaDBRepositories []name.Reference } // NewDBFlagGroup returns a default DBFlagGroup @@ -104,8 +104,8 @@ func NewDBFlagGroup() *DBFlagGroup { SkipJavaDBUpdate: SkipJavaDBUpdateFlag.Clone(), Light: LightFlag.Clone(), NoProgress: NoProgressFlag.Clone(), - DBRepository: DBRepositoryFlag.Clone(), - JavaDBRepository: JavaDBRepositoryFlag.Clone(), + DBRepositories: DBRepositoryFlag.Clone(), + JavaDBRepositories: JavaDBRepositoryFlag.Clone(), } } @@ -121,8 +121,8 @@ func (f *DBFlagGroup) Flags() []Flagger { f.DownloadJavaDBOnly, f.SkipJavaDBUpdate, f.NoProgress, - f.DBRepository, - f.JavaDBRepository, + f.DBRepositories, + f.JavaDBRepositories, f.Light, } } @@ -147,30 +147,21 @@ func (f *DBFlagGroup) ToOptions() (DBOptions, error) { return DBOptions{}, xerrors.New("--skip-java-db-update and --download-java-db-only options can not be specified both") } - var dbRepository, javaDBRepository name.Reference - var err error - if f.DBRepository != nil { - if dbRepository, err = name.ParseReference(f.DBRepository.Value(), name.WithDefaultTag("")); err != nil { - return DBOptions{}, xerrors.Errorf("invalid db repository: %w", err) - } - // Add the schema version if the tag is not specified for backward compatibility. - if t, ok := dbRepository.(name.Tag); ok && t.TagStr() == "" { - dbRepository = t.Tag(fmt.Sprint(db.SchemaVersion)) - log.Info("Adding schema version to the DB repository for backward compatibility", - log.String("repository", dbRepository.String())) + 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) } + dbRepositories = append(dbRepositories, ref) } - if f.JavaDBRepository != nil { - if javaDBRepository, err = name.ParseReference(f.JavaDBRepository.Value(), name.WithDefaultTag("")); err != nil { + for _, repo := range f.JavaDBRepositories.Value() { + ref, err := parseRepository(repo, javadb.SchemaVersion) + if err != nil { return DBOptions{}, xerrors.Errorf("invalid javadb repository: %w", err) } - // Add the schema version if the tag is not specified for backward compatibility. - if t, ok := javaDBRepository.(name.Tag); ok && t.TagStr() == "" { - javaDBRepository = t.Tag(fmt.Sprint(javadb.SchemaVersion)) - log.Info("Adding schema version to the Java DB repository for backward compatibility", - log.String("repository", javaDBRepository.String())) - } + javaDBRepositories = append(javaDBRepositories, ref) } return DBOptions{ @@ -180,7 +171,26 @@ func (f *DBFlagGroup) ToOptions() (DBOptions, error) { DownloadJavaDBOnly: downloadJavaDBOnly, SkipJavaDBUpdate: skipJavaDBUpdate, NoProgress: f.NoProgress.Value(), - DBRepository: dbRepository, - JavaDBRepository: javaDBRepository, + DBRepositories: dbRepositories, + JavaDBRepositories: javaDBRepositories, }, nil } + +func parseRepository(repo string, dbSchemaVersion int) (name.Reference, error) { + dbRepository, err := name.ParseReference(repo, name.WithDefaultTag("")) + if err != nil { + return nil, err + } + + // Add the schema version if the tag is not specified for backward compatibility. + t, ok := dbRepository.(name.Tag) + if !ok || t.TagStr() != "" { + return dbRepository, nil + } + + dbRepository = t.Tag(fmt.Sprint(dbSchemaVersion)) + log.Info("Adding schema version to the DB repository for backward compatibility", + log.String("repository", dbRepository.String())) + + return dbRepository, nil +} diff --git a/pkg/flag/db_flags_test.go b/pkg/flag/db_flags_test.go index 2bbd5b00198c..4f742e74ed68 100644 --- a/pkg/flag/db_flags_test.go +++ b/pkg/flag/db_flags_test.go @@ -17,31 +17,34 @@ func TestDBFlagGroup_ToOptions(t *testing.T) { SkipDBUpdate bool DownloadDBOnly bool Light bool - DBRepository string - JavaDBRepository string + DBRepository []string + JavaDBRepository []string } tests := []struct { - name string - fields fields - want flag.DBOptions - wantLogs []string - assertion require.ErrorAssertionFunc + name string + fields fields + want flag.DBOptions + wantLogs []string + wantErr string }{ { name: "happy", fields: fields{ SkipDBUpdate: true, DownloadDBOnly: false, - DBRepository: "ghcr.io/aquasecurity/trivy-db", - JavaDBRepository: "ghcr.io/aquasecurity/trivy-java-db", + DBRepository: []string{"ghcr.io/aquasecurity/trivy-db"}, + JavaDBRepository: []string{"ghcr.io/aquasecurity/trivy-java-db"}, }, want: flag.DBOptions{ - SkipDBUpdate: true, - DownloadDBOnly: false, - DBRepository: name.Tag{}, // All fields are unexported - JavaDBRepository: name.Tag{}, // All fields are unexported + SkipDBUpdate: true, + DownloadDBOnly: false, + DBRepositories: []name.Reference{name.Tag{}}, // All fields are unexported + JavaDBRepositories: []name.Reference{name.Tag{}}, // All fields are unexported + }, + wantLogs: []string{ + `Adding schema version to the DB repository for backward compatibility repository="ghcr.io/aquasecurity/trivy-db:2"`, + `Adding schema version to the DB repository for backward compatibility repository="ghcr.io/aquasecurity/trivy-java-db:1"`, }, - assertion: require.NoError, }, { name: "sad", @@ -49,25 +52,36 @@ func TestDBFlagGroup_ToOptions(t *testing.T) { SkipDBUpdate: true, DownloadDBOnly: true, }, - assertion: func(t require.TestingT, err error, msgs ...any) { - require.ErrorContains(t, err, "--skip-db-update and --download-db-only options can not be specified both") - }, + wantErr: "--skip-db-update and --download-db-only options can not be specified both", }, { name: "invalid repo", fields: fields{ SkipDBUpdate: true, DownloadDBOnly: false, - DBRepository: "foo:bar:baz", + DBRepository: []string{"foo:bar:baz"}, }, - assertion: func(t require.TestingT, err error, msgs ...any) { - require.ErrorContains(t, err, "invalid db repository") + wantErr: "invalid DB repository", + }, + { + name: "multiple repos", + fields: fields{ + SkipDBUpdate: true, + DownloadDBOnly: false, + DBRepository: []string{"ghcr.io/aquasecurity/trivy-db:2", "gallery.ecr.aws/aquasecurity/trivy-db:2"}, + JavaDBRepository: []string{"ghcr.io/aquasecurity/trivy-java-db:1", "gallery.ecr.aws/aquasecurity/trivy-java-db:1"}, + }, + want: flag.DBOptions{ + SkipDBUpdate: true, + DownloadDBOnly: false, + DBRepositories: []name.Reference{name.Tag{}, name.Tag{}}, // All fields are unexported + JavaDBRepositories: []name.Reference{name.Tag{}, name.Tag{}}, // All fields are unexported }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - out := newLogger(log.LevelWarn) + out := newLogger(log.LevelInfo) viper.Set(flag.SkipDBUpdateFlag.ConfigName, tt.fields.SkipDBUpdate) viper.Set(flag.DownloadDBOnlyFlag.ConfigName, tt.fields.DownloadDBOnly) @@ -76,13 +90,19 @@ func TestDBFlagGroup_ToOptions(t *testing.T) { // Assert options f := &flag.DBFlagGroup{ - DownloadDBOnly: flag.DownloadDBOnlyFlag.Clone(), - SkipDBUpdate: flag.SkipDBUpdateFlag.Clone(), - DBRepository: flag.DBRepositoryFlag.Clone(), - JavaDBRepository: flag.JavaDBRepositoryFlag.Clone(), + DownloadDBOnly: flag.DownloadDBOnlyFlag.Clone(), + SkipDBUpdate: flag.SkipDBUpdateFlag.Clone(), + DBRepositories: flag.DBRepositoryFlag.Clone(), + JavaDBRepositories: flag.JavaDBRepositoryFlag.Clone(), } got, err := f.ToOptions() - tt.assertion(t, err) + 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 log messages diff --git a/pkg/flag/global_flags.go b/pkg/flag/global_flags.go index ebd79bd5a06c..2d20611b8b72 100644 --- a/pkg/flag/global_flags.go +++ b/pkg/flag/global_flags.go @@ -106,7 +106,7 @@ func NewGlobalFlagGroup() *GlobalFlagGroup { } func (f *GlobalFlagGroup) Name() string { - return "global" + return "Global" } func (f *GlobalFlagGroup) Flags() []Flagger { diff --git a/pkg/flag/license_flags.go b/pkg/flag/license_flags.go index 5f4e148af5b2..5ff4c912e2a9 100644 --- a/pkg/flag/license_flags.go +++ b/pkg/flag/license_flags.go @@ -2,7 +2,7 @@ package flag import ( "github.com/aquasecurity/trivy/pkg/fanal/types" - "github.com/aquasecurity/trivy/pkg/licensing" + "github.com/aquasecurity/trivy/pkg/licensing/expression" ) var ( @@ -26,37 +26,37 @@ var ( // LicenseForbidden is an option only in a config file LicenseForbidden = Flag[[]string]{ ConfigName: "license.forbidden", - Default: licensing.ForbiddenLicenses, + Default: expression.ForbiddenLicenses, Usage: "forbidden licenses", } // LicenseRestricted is an option only in a config file LicenseRestricted = Flag[[]string]{ ConfigName: "license.restricted", - Default: licensing.RestrictedLicenses, + Default: expression.RestrictedLicenses, Usage: "restricted licenses", } // LicenseReciprocal is an option only in a config file LicenseReciprocal = Flag[[]string]{ ConfigName: "license.reciprocal", - Default: licensing.ReciprocalLicenses, + Default: expression.ReciprocalLicenses, Usage: "reciprocal licenses", } // LicenseNotice is an option only in a config file LicenseNotice = Flag[[]string]{ ConfigName: "license.notice", - Default: licensing.NoticeLicenses, + Default: expression.NoticeLicenses, Usage: "notice licenses", } // LicensePermissive is an option only in a config file LicensePermissive = Flag[[]string]{ ConfigName: "license.permissive", - Default: licensing.PermissiveLicenses, + Default: expression.PermissiveLicenses, Usage: "permissive licenses", } // LicenseUnencumbered is an option only in a config file LicenseUnencumbered = Flag[[]string]{ ConfigName: "license.unencumbered", - Default: licensing.UnencumberedLicenses, + Default: expression.UnencumberedLicenses, Usage: "unencumbered licenses", } ) diff --git a/pkg/flag/misconf_flags.go b/pkg/flag/misconf_flags.go index fc7505fec393..128ecfcac42e 100644 --- a/pkg/flag/misconf_flags.go +++ b/pkg/flag/misconf_flags.go @@ -3,6 +3,8 @@ package flag import ( "fmt" + "github.com/samber/lo" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/policy" xstrings "github.com/aquasecurity/trivy/pkg/x/strings" @@ -96,8 +98,15 @@ var ( MisconfigScannersFlag = Flag[[]string]{ Name: "misconfig-scanners", ConfigName: "misconfiguration.scanners", - Default: xstrings.ToStringSlice(analyzer.TypeConfigFiles), - Usage: "comma-separated list of misconfig scanners to use for misconfiguration scanning", + Default: xstrings.ToStringSlice( + lo.Without(analyzer.TypeConfigFiles, analyzer.TypeYAML, analyzer.TypeJSON), + ), + Usage: "comma-separated list of misconfig scanners to use for misconfiguration scanning", + } + ConfigFileSchemasFlag = Flag[[]string]{ + Name: "config-file-schemas", + ConfigName: "misconfiguration.config-file-schemas", + Usage: "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", } ) @@ -118,6 +127,7 @@ type MisconfFlagGroup struct { CloudformationParamVars *Flag[[]string] TerraformExcludeDownloaded *Flag[bool] MisconfigScanners *Flag[[]string] + ConfigFileSchemas *Flag[[]string] } type MisconfOptions struct { @@ -136,6 +146,7 @@ type MisconfOptions struct { CloudFormationParamVars []string TfExcludeDownloaded bool MisconfigScanners []analyzer.Type + ConfigFileSchemas []string } func NewMisconfFlagGroup() *MisconfFlagGroup { @@ -154,6 +165,7 @@ func NewMisconfFlagGroup() *MisconfFlagGroup { CloudformationParamVars: CfParamsFlag.Clone(), TerraformExcludeDownloaded: TerraformExcludeDownloaded.Clone(), MisconfigScanners: MisconfigScannersFlag.Clone(), + ConfigFileSchemas: ConfigFileSchemasFlag.Clone(), } } @@ -176,6 +188,7 @@ func (f *MisconfFlagGroup) Flags() []Flagger { f.TerraformExcludeDownloaded, f.CloudformationParamVars, f.MisconfigScanners, + f.ConfigFileSchemas, } } @@ -198,5 +211,6 @@ func (f *MisconfFlagGroup) ToOptions() (MisconfOptions, error) { CloudFormationParamVars: f.CloudformationParamVars.Value(), TfExcludeDownloaded: f.TerraformExcludeDownloaded.Value(), MisconfigScanners: xstrings.ToTSlice[analyzer.Type](f.MisconfigScanners.Value()), + ConfigFileSchemas: f.ConfigFileSchemas.Value(), }, nil } diff --git a/pkg/flag/options.go b/pkg/flag/options.go index 3f48b75ac9b4..e00aa4cfa922 100644 --- a/pkg/flag/options.go +++ b/pkg/flag/options.go @@ -23,6 +23,7 @@ import ( "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/plugin" "github.com/aquasecurity/trivy/pkg/result" + "github.com/aquasecurity/trivy/pkg/rpc/client" "github.com/aquasecurity/trivy/pkg/types" "github.com/aquasecurity/trivy/pkg/version/app" ) @@ -56,15 +57,21 @@ type Flag[T FlagType] struct { // Usage explains how to use the flag. Usage string - // Persistent represents if the flag is persistent + // Persistent represents if the flag is persistent. Persistent bool - // Deprecated represents if the flag is deprecated + // Deprecated represents if the flag is deprecated. + // It shows a warning message when the flag is used. Deprecated string - // Removed represents if the flag is removed and no longer works + // Removed represents if the flag is removed and no longer works. + // It shows an error message when the flag is used. Removed string + // Internal represents if the flag is for internal use only. + // It is not shown in the usage message. + Internal bool + // Aliases represents aliases Aliases []Alias @@ -196,10 +203,22 @@ func (f *Flag[T]) GetName() string { return f.Name } +func (f *Flag[T]) GetConfigName() string { + return f.ConfigName +} + +func (f *Flag[T]) GetDefaultValue() any { + return f.Default +} + func (f *Flag[T]) GetAliases() []Alias { return f.Aliases } +func (f *Flag[T]) Hidden() bool { + return f.Deprecated != "" || f.Removed != "" || f.Internal +} + func (f *Flag[T]) Value() (t T) { if f == nil { return t @@ -241,7 +260,7 @@ func (f *Flag[T]) Add(cmd *cobra.Command) { flags.Float64P(f.Name, f.Shorthand, v, f.Usage) } - if f.Deprecated != "" || f.Removed != "" { + if f.Hidden() { _ = flags.MarkHidden(f.Name) } } @@ -302,7 +321,10 @@ type FlagGroup interface { type Flagger interface { GetName() string + GetConfigName() string + GetDefaultValue() any GetAliases() []Alias + Hidden() bool Parse() error Add(cmd *cobra.Command) @@ -326,7 +348,6 @@ type Flags struct { RegoFlagGroup *RegoFlagGroup RepoFlagGroup *RepoFlagGroup ReportFlagGroup *ReportFlagGroup - SBOMFlagGroup *SBOMFlagGroup ScanFlagGroup *ScanFlagGroup SecretFlagGroup *SecretFlagGroup VulnerabilityFlagGroup *VulnerabilityFlagGroup @@ -350,7 +371,6 @@ type Options struct { RemoteOptions RepoOptions ReportOptions - SBOMOptions ScanOptions SecretOptions VulnerabilityOptions @@ -472,6 +492,16 @@ func (o *Options) RemoteCacheOpts() cache.RemoteOptions { ServerAddr: o.ServerAddr, CustomHeaders: o.CustomHeaders, Insecure: o.Insecure, + PathPrefix: o.PathPrefix, + } +} + +func (o *Options) ClientScannerOpts() client.ScannerOption { + return client.ScannerOption{ + RemoteURL: o.ServerAddr, + CustomHeaders: o.CustomHeaders, + Insecure: o.Insecure, + PathPrefix: o.PathPrefix, } } @@ -549,9 +579,6 @@ func (f *Flags) groups() []FlagGroup { if f.ImageFlagGroup != nil { groups = append(groups, f.ImageFlagGroup) } - if f.SBOMFlagGroup != nil { - groups = append(groups, f.SBOMFlagGroup) - } if f.VulnerabilityFlagGroup != nil { groups = append(groups, f.VulnerabilityFlagGroup) } @@ -760,13 +787,6 @@ func (f *Flags) ToOptions(args []string) (Options, error) { } } - if f.SBOMFlagGroup != nil { - opts.SBOMOptions, err = f.SBOMFlagGroup.ToOptions() - if err != nil { - return Options{}, xerrors.Errorf("sbom flag error: %w", err) - } - } - if f.ScanFlagGroup != nil { opts.ScanOptions, err = f.ScanFlagGroup.ToOptions(args) if err != nil { diff --git a/pkg/flag/remote_flags.go b/pkg/flag/remote_flags.go index 2348ef649e66..b09e89a48104 100644 --- a/pkg/flag/remote_flags.go +++ b/pkg/flag/remote_flags.go @@ -39,6 +39,12 @@ var ( Default: "localhost:4954", Usage: "listen address in server mode", } + ServerPathPrefixFlag = Flag[string]{ + Name: "path-prefix", + ConfigName: "server.path-prefix", + Usage: "prefix for the server endpoint", + Internal: true, // Internal use + } ) // RemoteFlagGroup composes common printer flag structs @@ -47,6 +53,7 @@ type RemoteFlagGroup struct { // for client/server Token *Flag[string] TokenHeader *Flag[string] + PathPrefix *Flag[string] // for client ServerAddr *Flag[string] @@ -63,12 +70,17 @@ type RemoteOptions struct { ServerAddr string Listen string CustomHeaders http.Header + + // Server endpoint: []/./ (default prefix: /twirp) + // e.g., http://localhost:4954/twirp/trivy.scanner.v1.Scanner/Scan + PathPrefix string } func NewClientFlags() *RemoteFlagGroup { return &RemoteFlagGroup{ Token: ServerTokenFlag.Clone(), TokenHeader: ServerTokenHeaderFlag.Clone(), + PathPrefix: ServerPathPrefixFlag.Clone(), ServerAddr: ServerAddrFlag.Clone(), CustomHeaders: ServerCustomHeadersFlag.Clone(), } @@ -76,9 +88,10 @@ func NewClientFlags() *RemoteFlagGroup { func NewServerFlags() *RemoteFlagGroup { return &RemoteFlagGroup{ - Token: &ServerTokenFlag, - TokenHeader: &ServerTokenHeaderFlag, - Listen: &ServerListenFlag, + Token: ServerTokenFlag.Clone(), + TokenHeader: ServerTokenHeaderFlag.Clone(), + PathPrefix: ServerPathPrefixFlag.Clone(), + Listen: ServerListenFlag.Clone(), } } @@ -90,6 +103,7 @@ func (f *RemoteFlagGroup) Flags() []Flagger { return []Flagger{ f.Token, f.TokenHeader, + f.PathPrefix, f.ServerAddr, f.CustomHeaders, f.Listen, @@ -129,6 +143,7 @@ func (f *RemoteFlagGroup) ToOptions() (RemoteOptions, error) { return RemoteOptions{ Token: token, TokenHeader: tokenHeader, + PathPrefix: f.PathPrefix.Value(), ServerAddr: serverAddr, CustomHeaders: customHeaders, Listen: listen, diff --git a/pkg/flag/report_flags.go b/pkg/flag/report_flags.go index ce833cc1b13e..67d553b65553 100644 --- a/pkg/flag/report_flags.go +++ b/pkg/flag/report_flags.go @@ -9,6 +9,7 @@ import ( "golang.org/x/xerrors" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/compliance/spec" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/result" @@ -260,7 +261,7 @@ func loadComplianceTypes(compliance string) (spec.ComplianceSpec, error) { return spec.ComplianceSpec{}, xerrors.Errorf("unknown compliance : %v", compliance) } - cs, err := spec.GetComplianceSpec(compliance) + cs, err := spec.GetComplianceSpec(compliance, cache.DefaultDir()) if err != nil { return spec.ComplianceSpec{}, xerrors.Errorf("spec loading from file system error: %w", err) } diff --git a/pkg/flag/sbom_flags.go b/pkg/flag/sbom_flags.go deleted file mode 100644 index 9af8414ed546..000000000000 --- a/pkg/flag/sbom_flags.go +++ /dev/null @@ -1,49 +0,0 @@ -package flag - -var ( - ArtifactTypeFlag = Flag[string]{ - Name: "artifact-type", - ConfigName: "sbom.artifact-type", - Usage: "deprecated", - Removed: `Use 'trivy image' or other subcommands. See also https://github.com/aquasecurity/trivy/discussions/2407`, - } - SBOMFormatFlag = Flag[string]{ - Name: "sbom-format", - ConfigName: "sbom.format", - Usage: "deprecated", - Removed: `Use 'trivy image' or other subcommands. See also https://github.com/aquasecurity/trivy/discussions/2407`, - } -) - -type SBOMFlagGroup struct { - ArtifactType *Flag[string] // deprecated - SBOMFormat *Flag[string] // deprecated -} - -type SBOMOptions struct{} - -func NewSBOMFlagGroup() *SBOMFlagGroup { - return &SBOMFlagGroup{ - ArtifactType: ArtifactTypeFlag.Clone(), - SBOMFormat: SBOMFormatFlag.Clone(), - } -} - -func (f *SBOMFlagGroup) Name() string { - return "SBOM" -} - -func (f *SBOMFlagGroup) Flags() []Flagger { - return []Flagger{ - f.ArtifactType, - f.SBOMFormat, - } -} - -func (f *SBOMFlagGroup) ToOptions() (SBOMOptions, error) { - if err := parseFlags(f); err != nil { - return SBOMOptions{}, err - } - - return SBOMOptions{}, nil -} diff --git a/pkg/flag/scan_flags.go b/pkg/flag/scan_flags.go index b413fce01fe9..544d56691883 100644 --- a/pkg/flag/scan_flags.go +++ b/pkg/flag/scan_flags.go @@ -5,6 +5,7 @@ import ( "github.com/samber/lo" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/types" xstrings "github.com/aquasecurity/trivy/pkg/x/strings" @@ -96,43 +97,59 @@ var ( Default: "https://rekor.sigstore.dev", Usage: "[EXPERIMENTAL] address of rekor STL server", } + DetectionPriority = Flag[string]{ + Name: "detection-priority", + ConfigName: "scan.detection-priority", + Default: string(ftypes.PriorityPrecise), + Values: xstrings.ToStringSlice([]ftypes.DetectionPriority{ + ftypes.PriorityPrecise, + ftypes.PriorityComprehensive, + }), + Usage: `specify the detection priority: + - "precise": Prioritizes precise by minimizing false positives. + - "comprehensive": Aims to detect more security findings at the cost of potential false positives. +`, + } ) type ScanFlagGroup struct { - SkipDirs *Flag[[]string] - SkipFiles *Flag[[]string] - OfflineScan *Flag[bool] - Scanners *Flag[[]string] - FilePatterns *Flag[[]string] - Slow *Flag[bool] // deprecated - Parallel *Flag[int] - SBOMSources *Flag[[]string] - RekorURL *Flag[string] + SkipDirs *Flag[[]string] + SkipFiles *Flag[[]string] + OfflineScan *Flag[bool] + Scanners *Flag[[]string] + FilePatterns *Flag[[]string] + Slow *Flag[bool] // deprecated + Parallel *Flag[int] + SBOMSources *Flag[[]string] + RekorURL *Flag[string] + DetectionPriority *Flag[string] } type ScanOptions struct { - Target string - SkipDirs []string - SkipFiles []string - OfflineScan bool - Scanners types.Scanners - FilePatterns []string - Parallel int - SBOMSources []string - RekorURL string + Target string + SkipDirs []string + SkipFiles []string + OfflineScan bool + Scanners types.Scanners + FilePatterns []string + Parallel int + SBOMSources []string + RekorURL string + DetectionPriority ftypes.DetectionPriority } func NewScanFlagGroup() *ScanFlagGroup { return &ScanFlagGroup{ - SkipDirs: SkipDirsFlag.Clone(), - SkipFiles: SkipFilesFlag.Clone(), - OfflineScan: OfflineScanFlag.Clone(), - Scanners: ScannersFlag.Clone(), - FilePatterns: FilePatternsFlag.Clone(), - Parallel: ParallelFlag.Clone(), - SBOMSources: SBOMSourcesFlag.Clone(), - RekorURL: RekorURLFlag.Clone(), - Slow: SlowFlag.Clone(), + SkipDirs: SkipDirsFlag.Clone(), + SkipFiles: SkipFilesFlag.Clone(), + OfflineScan: OfflineScanFlag.Clone(), + Scanners: ScannersFlag.Clone(), + FilePatterns: FilePatternsFlag.Clone(), + Parallel: ParallelFlag.Clone(), + SBOMSources: SBOMSourcesFlag.Clone(), + RekorURL: RekorURLFlag.Clone(), + Slow: SlowFlag.Clone(), + DetectionPriority: DetectionPriority.Clone(), } } @@ -151,6 +168,7 @@ func (f *ScanFlagGroup) Flags() []Flagger { f.Parallel, f.SBOMSources, f.RekorURL, + f.DetectionPriority, } } @@ -171,14 +189,15 @@ func (f *ScanFlagGroup) ToOptions(args []string) (ScanOptions, error) { } return ScanOptions{ - Target: target, - SkipDirs: f.SkipDirs.Value(), - SkipFiles: f.SkipFiles.Value(), - OfflineScan: f.OfflineScan.Value(), - Scanners: xstrings.ToTSlice[types.Scanner](f.Scanners.Value()), - FilePatterns: f.FilePatterns.Value(), - Parallel: parallel, - SBOMSources: f.SBOMSources.Value(), - RekorURL: f.RekorURL.Value(), + Target: target, + SkipDirs: f.SkipDirs.Value(), + SkipFiles: f.SkipFiles.Value(), + OfflineScan: f.OfflineScan.Value(), + Scanners: xstrings.ToTSlice[types.Scanner](f.Scanners.Value()), + FilePatterns: f.FilePatterns.Value(), + Parallel: parallel, + SBOMSources: f.SBOMSources.Value(), + RekorURL: f.RekorURL.Value(), + DetectionPriority: ftypes.DetectionPriority(f.DetectionPriority.Value()), }, nil } diff --git a/pkg/iac/adapters/arm/network/adapt.go b/pkg/iac/adapters/arm/network/adapt.go index 5201b84761e7..214cf75c3c90 100644 --- a/pkg/iac/adapters/arm/network/adapt.go +++ b/pkg/iac/adapters/arm/network/adapt.go @@ -27,11 +27,11 @@ func adaptSecurityGroups(deployment azure.Deployment) (sgs []network.SecurityGro func adaptSecurityGroup(resource azure.Resource, deployment azure.Deployment) network.SecurityGroup { return network.SecurityGroup{ Metadata: resource.Metadata, - Rules: adaptSecurityGroupRules(resource, deployment), + Rules: adaptSecurityGroupRules(deployment), } } -func adaptSecurityGroupRules(resource azure.Resource, deployment azure.Deployment) (rules []network.SecurityGroupRule) { +func adaptSecurityGroupRules(deployment azure.Deployment) (rules []network.SecurityGroupRule) { for _, resource := range deployment.GetResourcesByType("Microsoft.Network/networkSecurityGroups/securityRules") { rules = append(rules, adaptSecurityGroupRule(resource)) } @@ -120,7 +120,7 @@ func expandRange(r string, m iacTypes.Metadata) network.PortRange { return network.PortRange{ Metadata: m, - Start: start, - End: end, + Start: iacTypes.Int(start, m), + End: iacTypes.Int(end, m), } } diff --git a/pkg/iac/adapters/arm/storage/adapt.go b/pkg/iac/adapters/arm/storage/adapt.go index 1b10ebbe9ad8..018949e24e10 100644 --- a/pkg/iac/adapters/arm/storage/adapt.go +++ b/pkg/iac/adapters/arm/storage/adapt.go @@ -59,7 +59,7 @@ func adaptAccounts(deployment azure.Deployment) []storage.Account { Metadata: resource.Properties.GetMetadata(), EnableLogging: types.BoolDefault(false, resource.Properties.GetMetadata()), }, - MinimumTLSVersion: resource.Properties.GetMapValue("minimumTlsVersion").AsStringValue("TLS1_0", resource.Properties.GetMetadata()), + MinimumTLSVersion: resource.Properties.GetMapValue("minimumTlsVersion").AsStringValue("", resource.Properties.GetMetadata()), Queues: queues, } accounts = append(accounts, account) diff --git a/pkg/iac/adapters/arm/storage/adapt_test.go b/pkg/iac/adapters/arm/storage/adapt_test.go index d1e124e2449e..f4fd81f47ad2 100644 --- a/pkg/iac/adapters/arm/storage/adapt_test.go +++ b/pkg/iac/adapters/arm/storage/adapt_test.go @@ -26,7 +26,7 @@ func Test_AdaptStorageDefaults(t *testing.T) { require.Len(t, output.Accounts, 1) account := output.Accounts[0] - assert.Equal(t, "TLS1_0", account.MinimumTLSVersion.Value()) + assert.Equal(t, "", account.MinimumTLSVersion.Value()) assert.False(t, account.EnforceHTTPS.Value()) } diff --git a/pkg/iac/adapters/cloudformation/aws/ec2/adapt_test.go b/pkg/iac/adapters/cloudformation/aws/ec2/adapt_test.go index 01fb201768e9..b218b95a4e99 100644 --- a/pkg/iac/adapters/cloudformation/aws/ec2/adapt_test.go +++ b/pkg/iac/adapters/cloudformation/aws/ec2/adapt_test.go @@ -51,15 +51,15 @@ Resources: SecurityGroupIngress: - IpProtocol: tcp Description: ingress - FromPort: 80 + FromPort: "80" ToPort: 80 CidrIp: 0.0.0.0/0 SecurityGroupEgress: - - IpProtocol: tcp + - IpProtocol: -1 Description: egress FromPort: 80 - ToPort: 80 - CidrIp: 0.0.0.0/0 + ToPort: "80" + CidrIp: "0.0.0.0/0" myNetworkAcl: Type: AWS::EC2::NetworkAcl Properties: @@ -73,6 +73,9 @@ Resources: Protocol: 6 RuleAction: allow CidrBlock: 172.16.0.0/24 + PortRange: + From: 22 + To: "23" myLaunchConfig: Type: AWS::AutoScaling::LaunchConfiguration Properties: @@ -137,6 +140,9 @@ Resources: CIDRs: []types.StringValue{ types.StringTest("0.0.0.0/0"), }, + FromPort: types.IntTest(80), + ToPort: types.IntTest(80), + Protocol: types.StringTest("tcp"), }, }, EgressRules: []ec2.SecurityGroupRule{ @@ -145,6 +151,9 @@ Resources: CIDRs: []types.StringValue{ types.StringTest("0.0.0.0/0"), }, + FromPort: types.IntTest(80), + ToPort: types.IntTest(80), + Protocol: types.StringTest("-1"), }, }, }, @@ -159,6 +168,8 @@ Resources: CIDRs: []types.StringValue{ types.StringTest("172.16.0.0/24"), }, + FromPort: types.IntTest(22), + ToPort: types.IntTest(23), }, }, }, @@ -309,6 +320,8 @@ Resources: CIDRs: []types.StringValue{ types.StringTest("0.0.0.0/0"), }, + FromPort: types.IntTest(-1), + ToPort: types.IntTest(-1), }, }, EgressRules: []ec2.SecurityGroupRule{ @@ -317,6 +330,8 @@ Resources: CIDRs: []types.StringValue{ types.StringTest("0.0.0.0/0"), }, + FromPort: types.IntTest(-1), + ToPort: types.IntTest(-1), }, }, }, diff --git a/pkg/iac/adapters/cloudformation/aws/ec2/nacl.go b/pkg/iac/adapters/cloudformation/aws/ec2/nacl.go index 0f908f78ae08..a91a12569994 100644 --- a/pkg/iac/adapters/cloudformation/aws/ec2/nacl.go +++ b/pkg/iac/adapters/cloudformation/aws/ec2/nacl.go @@ -4,6 +4,7 @@ import ( "strconv" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) @@ -29,7 +30,8 @@ func getRules(id string, ctx parser.FileContext) (rules []ec2.NetworkACLRule) { Metadata: ruleResource.Metadata(), Type: iacTypes.StringDefault(ec2.TypeIngress, ruleResource.Metadata()), Action: iacTypes.StringDefault(ec2.ActionAllow, ruleResource.Metadata()), - Protocol: iacTypes.String("-1", ruleResource.Metadata()), + FromPort: iacTypes.IntDefault(-1, ruleResource.Metadata()), + ToPort: iacTypes.IntDefault(-1, ruleResource.Metadata()), CIDRs: nil, } @@ -62,6 +64,17 @@ func getRules(id string, ctx parser.FileContext) (rules []ec2.NetworkACLRule) { rule.CIDRs = append(rule.CIDRs, ipv6Cidr.AsStringValue()) } + portRange := ruleResource.GetProperty("PortRange") + fromPort := portRange.GetProperty("From").ConvertTo(cftypes.Int) + if fromPort.IsInt() { + rule.FromPort = fromPort.AsIntValue() + } + + toPort := portRange.GetProperty("To").ConvertTo(cftypes.Int) + if toPort.IsInt() { + rule.ToPort = toPort.AsIntValue() + } + rules = append(rules, rule) } } diff --git a/pkg/iac/adapters/cloudformation/aws/ec2/security_group.go b/pkg/iac/adapters/cloudformation/aws/ec2/security_group.go index 6770062affb2..e85a91cdecb6 100644 --- a/pkg/iac/adapters/cloudformation/aws/ec2/security_group.go +++ b/pkg/iac/adapters/cloudformation/aws/ec2/security_group.go @@ -4,6 +4,7 @@ import ( "github.com/samber/lo" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" "github.com/aquasecurity/trivy/pkg/iac/types" ) @@ -75,7 +76,10 @@ func adaptRule(r interface { rule := ec2.SecurityGroupRule{ Metadata: r.Metadata(), Description: r.GetStringProperty("Description"), + FromPort: types.IntDefault(-1, r.Metadata()), + ToPort: types.IntDefault(-1, r.Metadata()), } + v4Cidr := r.GetProperty("CidrIp") if v4Cidr.IsString() && v4Cidr.AsStringValue().IsNotEmpty() { rule.CIDRs = append(rule.CIDRs, types.StringExplicit(v4Cidr.AsString(), v4Cidr.Metadata())) @@ -85,5 +89,20 @@ func adaptRule(r interface { rule.CIDRs = append(rule.CIDRs, types.StringExplicit(v6Cidr.AsString(), v6Cidr.Metadata())) } + fromPort := r.GetProperty("FromPort").ConvertTo(cftypes.Int) + if fromPort.IsInt() { + rule.FromPort = fromPort.AsIntValue() + } + + toPort := r.GetProperty("ToPort").ConvertTo(cftypes.Int) + if toPort.IsInt() { + rule.ToPort = toPort.AsIntValue() + } + + protocol := r.GetProperty("IpProtocol").ConvertTo(cftypes.String) + if protocol.IsString() { + rule.Protocol = protocol.AsStringValue() + } + return rule } diff --git a/pkg/iac/adapters/cloudformation/aws/s3/bucket.go b/pkg/iac/adapters/cloudformation/aws/s3/bucket.go index 5f5329fc6714..ec56894dbee4 100644 --- a/pkg/iac/adapters/cloudformation/aws/s3/bucket.go +++ b/pkg/iac/adapters/cloudformation/aws/s3/bucket.go @@ -7,7 +7,9 @@ import ( "strings" s3types "github.com/aws/aws-sdk-go-v2/service/s3/types" + "github.com/liamg/iamgo" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/s3" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" @@ -37,6 +39,7 @@ func getBuckets(cfFile parser.FileContext) []s3.Bucket { Website: getWebsite(r), BucketLocation: iacTypes.String("", r.Metadata()), Objects: nil, + BucketPolicies: getBucketPolicies(cfFile, r), } buckets = append(buckets, s3b) @@ -156,3 +159,35 @@ func getWebsite(r *parser.Resource) *s3.Website { } } } + +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") + if bucket.NotEqualTo(r.GetStringProperty("BucketName").Value()) && bucket.NotEqualTo(r.ID()) { + continue + } + + doc := bucketPolicy.GetProperty("PolicyDocument") + if doc.IsNil() { + continue + } + + parsed, err := iamgo.Parse(doc.GetJsonBytes()) + if err != nil { + continue + } + policies = append(policies, iam.Policy{ + Metadata: doc.Metadata(), + Name: iacTypes.StringDefault("", doc.Metadata()), + Document: iam.Document{ + Metadata: doc.Metadata(), + Parsed: *parsed, + }, + Builtin: iacTypes.Bool(false, doc.Metadata()), + }) + } + + return policies +} diff --git a/pkg/iac/adapters/cloudformation/aws/s3/s3_test.go b/pkg/iac/adapters/cloudformation/aws/s3/s3_test.go index ee8fffb39f75..707aae028f7f 100644 --- a/pkg/iac/adapters/cloudformation/aws/s3/s3_test.go +++ b/pkg/iac/adapters/cloudformation/aws/s3/s3_test.go @@ -3,7 +3,10 @@ package s3 import ( "testing" + "github.com/liamg/iamgo" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/s3" "github.com/aquasecurity/trivy/pkg/iac/types" ) @@ -57,6 +60,19 @@ Resources: Status: Enabled WebsiteConfiguration: IndexDocument: index.html + SampleBucketPolicy: + Type: AWS::S3::BucketPolicy + Properties: + Bucket: !Ref Bucket + PolicyDocument: + Version: 2012-10-17 + Statement: + - Action: + - 's3:GetObject' + Effect: Allow + Resource: !Join + - 'arn:aws:s3:::testbucket/*' + Principal: '*' `, expected: s3.S3{ Buckets: []s3.Bucket{ @@ -91,6 +107,23 @@ Resources: Enabled: types.BoolTest(true), }, Website: &s3.Website{}, + BucketPolicies: []iam.Policy{ + { + Document: iam.Document{ + Parsed: iamgo.NewPolicyBuilder(). + WithStatement( + iamgo.NewStatementBuilder(). + WithActions([]string{"s3:GetObject"}). + WithAllPrincipals(true). + WithEffect("Allow"). + WithResources([]string{"arn:aws:s3:::testbucket/*"}). + Build(), + ). + WithVersion("2012-10-17T00:00:00Z"). + Build(), + }, + }, + }, }, }, }, diff --git a/pkg/iac/adapters/terraform/aws/cloudfront/adapt.go b/pkg/iac/adapters/terraform/aws/cloudfront/adapt.go index a981241a83a4..721ecf991d55 100644 --- a/pkg/iac/adapters/terraform/aws/cloudfront/adapt.go +++ b/pkg/iac/adapters/terraform/aws/cloudfront/adapt.go @@ -33,7 +33,7 @@ func adaptDistribution(resource *terraform.Block) cloudfront.Distribution { }, DefaultCacheBehaviour: cloudfront.CacheBehaviour{ Metadata: resource.GetMetadata(), - ViewerProtocolPolicy: types.String("allow-all", resource.GetMetadata()), + ViewerProtocolPolicy: types.StringDefault("", resource.GetMetadata()), }, OrdererCacheBehaviours: nil, ViewerCertificate: cloudfront.ViewerCertificate{ @@ -53,13 +53,13 @@ func adaptDistribution(resource *terraform.Block) cloudfront.Distribution { if defaultCacheBlock := resource.GetBlock("default_cache_behavior"); defaultCacheBlock.IsNotNil() { distribution.DefaultCacheBehaviour.Metadata = defaultCacheBlock.GetMetadata() viewerProtocolPolicyAttr := defaultCacheBlock.GetAttribute("viewer_protocol_policy") - distribution.DefaultCacheBehaviour.ViewerProtocolPolicy = viewerProtocolPolicyAttr.AsStringValueOrDefault("allow-all", defaultCacheBlock) + distribution.DefaultCacheBehaviour.ViewerProtocolPolicy = viewerProtocolPolicyAttr.AsStringValueOrDefault("", defaultCacheBlock) } orderedCacheBlocks := resource.GetBlocks("ordered_cache_behavior") for _, orderedCacheBlock := range orderedCacheBlocks { viewerProtocolPolicyAttr := orderedCacheBlock.GetAttribute("viewer_protocol_policy") - viewerProtocolPolicyVal := viewerProtocolPolicyAttr.AsStringValueOrDefault("allow-all", orderedCacheBlock) + viewerProtocolPolicyVal := viewerProtocolPolicyAttr.AsStringValueOrDefault("", orderedCacheBlock) distribution.OrdererCacheBehaviours = append(distribution.OrdererCacheBehaviours, cloudfront.CacheBehaviour{ Metadata: orderedCacheBlock.GetMetadata(), ViewerProtocolPolicy: viewerProtocolPolicyVal, diff --git a/pkg/iac/adapters/terraform/aws/cloudfront/adapt_test.go b/pkg/iac/adapters/terraform/aws/cloudfront/adapt_test.go index d20520cd4651..fd1bf65e9cc5 100644 --- a/pkg/iac/adapters/terraform/aws/cloudfront/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/cloudfront/adapt_test.go @@ -83,7 +83,7 @@ func Test_adaptDistribution(t *testing.T) { }, DefaultCacheBehaviour: cloudfront.CacheBehaviour{ Metadata: iacTypes.NewTestMetadata(), - ViewerProtocolPolicy: iacTypes.String("allow-all", iacTypes.NewTestMetadata()), + ViewerProtocolPolicy: iacTypes.String("", iacTypes.NewTestMetadata()), }, ViewerCertificate: cloudfront.ViewerCertificate{ diff --git a/pkg/iac/adapters/terraform/aws/ec2/subnet.go b/pkg/iac/adapters/terraform/aws/ec2/subnet.go index 4e4e49c4f59e..41a68eb2832c 100644 --- a/pkg/iac/adapters/terraform/aws/ec2/subnet.go +++ b/pkg/iac/adapters/terraform/aws/ec2/subnet.go @@ -9,13 +9,13 @@ func adaptSubnets(modules terraform.Modules) []ec2.Subnet { var subnets []ec2.Subnet for _, module := range modules { for _, resource := range module.GetResourcesByType("aws_subnet") { - subnets = append(subnets, adaptSubnet(resource, module)) + subnets = append(subnets, adaptSubnet(resource)) } } return subnets } -func adaptSubnet(resource *terraform.Block, module *terraform.Module) ec2.Subnet { +func adaptSubnet(resource *terraform.Block) ec2.Subnet { mapPublicIpOnLaunchAttr := resource.GetAttribute("map_public_ip_on_launch") mapPublicIpOnLaunchVal := mapPublicIpOnLaunchAttr.AsBoolValueOrDefault(false, resource) diff --git a/pkg/iac/adapters/terraform/aws/ec2/subnet_test.go b/pkg/iac/adapters/terraform/aws/ec2/subnet_test.go index c371ef4f504f..33a0a642245a 100644 --- a/pkg/iac/adapters/terraform/aws/ec2/subnet_test.go +++ b/pkg/iac/adapters/terraform/aws/ec2/subnet_test.go @@ -61,7 +61,7 @@ func Test_adaptSubnet(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") - adapted := adaptSubnet(modules.GetBlocks()[0], modules[0]) + adapted := adaptSubnet(modules.GetBlocks()[0]) testutil.AssertDefsecEqual(t, test.expected, adapted) }) } diff --git a/pkg/iac/adapters/terraform/aws/ec2/vpc.go b/pkg/iac/adapters/terraform/aws/ec2/vpc.go index 92ee730e0947..6be6cbaf672d 100644 --- a/pkg/iac/adapters/terraform/aws/ec2/vpc.go +++ b/pkg/iac/adapters/terraform/aws/ec2/vpc.go @@ -72,9 +72,9 @@ func (a *sgAdapter) adaptSecurityGroups(modules terraform.Modules) []ec2.Securit } for _, sgRule := range orphanResources { if sgRule.GetAttribute("type").Equals("ingress") { - orphanage.IngressRules = append(orphanage.IngressRules, adaptSGRule(sgRule, modules)) + orphanage.IngressRules = append(orphanage.IngressRules, adaptSGRule(sgRule)) } else if sgRule.GetAttribute("type").Equals("egress") { - orphanage.EgressRules = append(orphanage.EgressRules, adaptSGRule(sgRule, modules)) + orphanage.EgressRules = append(orphanage.EgressRules, adaptSGRule(sgRule)) } } securityGroups = append(securityGroups, orphanage) @@ -116,21 +116,21 @@ func (a *sgAdapter) adaptSecurityGroup(resource *terraform.Block, module terrafo ingressBlocks := resource.GetBlocks("ingress") for _, ingressBlock := range ingressBlocks { - ingressRules = append(ingressRules, adaptSGRule(ingressBlock, module)) + ingressRules = append(ingressRules, adaptSGRule(ingressBlock)) } egressBlocks := resource.GetBlocks("egress") for _, egressBlock := range egressBlocks { - egressRules = append(egressRules, adaptSGRule(egressBlock, module)) + egressRules = append(egressRules, adaptSGRule(egressBlock)) } rulesBlocks := module.GetReferencingResources(resource, "aws_security_group_rule", "security_group_id") for _, ruleBlock := range rulesBlocks { a.sgRuleIDs.Resolve(ruleBlock.ID()) if ruleBlock.GetAttribute("type").Equals("ingress") { - ingressRules = append(ingressRules, adaptSGRule(ruleBlock, module)) + ingressRules = append(ingressRules, adaptSGRule(ruleBlock)) } else if ruleBlock.GetAttribute("type").Equals("egress") { - egressRules = append(egressRules, adaptSGRule(ruleBlock, module)) + egressRules = append(egressRules, adaptSGRule(ruleBlock)) } } @@ -154,7 +154,7 @@ func (a *sgAdapter) adaptSecurityGroup(resource *terraform.Block, module terrafo } } -func adaptSGRule(resource *terraform.Block, modules terraform.Modules) ec2.SecurityGroupRule { +func adaptSGRule(resource *terraform.Block) ec2.SecurityGroupRule { ruleDescAttr := resource.GetAttribute("description") ruleDescVal := ruleDescAttr.AsStringValueOrDefault("", resource) @@ -162,16 +162,6 @@ func adaptSGRule(resource *terraform.Block, modules terraform.Modules) ec2.Secur cidrBlocks := resource.GetAttribute("cidr_blocks") ipv6cidrBlocks := resource.GetAttribute("ipv6_cidr_blocks") - varBlocks := modules.GetBlocks().OfType("variable") - - for _, vb := range varBlocks { - if cidrBlocks.IsNotNil() && cidrBlocks.ReferencesBlock(vb) { - cidrBlocks = vb.GetAttribute("default") - } - if ipv6cidrBlocks.IsNotNil() && ipv6cidrBlocks.ReferencesBlock(vb) { - ipv6cidrBlocks = vb.GetAttribute("default") - } - } if cidrBlocks.IsNotNil() { cidrs = cidrBlocks.AsStringValues() @@ -185,6 +175,9 @@ func adaptSGRule(resource *terraform.Block, modules terraform.Modules) ec2.Secur Metadata: resource.GetMetadata(), Description: ruleDescVal, CIDRs: cidrs, + FromPort: resource.GetAttribute("from_port").AsIntValueOrDefault(-1, resource), + ToPort: resource.GetAttribute("to_port").AsIntValueOrDefault(-1, resource), + Protocol: resource.GetAttribute("protocol").AsStringValueOrDefault("", resource), } } @@ -203,6 +196,9 @@ func adaptSingleSGRule(resource *terraform.Block) ec2.SecurityGroupRule { Metadata: resource.GetMetadata(), Description: description, CIDRs: cidrs, + FromPort: resource.GetAttribute("from_port").AsIntValueOrDefault(-1, resource), + ToPort: resource.GetAttribute("to_port").AsIntValueOrDefault(-1, resource), + Protocol: resource.GetAttribute("ip_protocol").AsStringValueOrDefault("", resource), } } @@ -236,7 +232,7 @@ func adaptNetworkACLRule(resource *terraform.Block) ec2.NetworkACLRule { actionVal := actionAttr.AsStringValueOrDefault("", resource) protocolAtrr := resource.GetAttribute("protocol") - protocolVal := protocolAtrr.AsStringValueOrDefault("-1", resource) + protocolVal := protocolAtrr.AsStringValueOrDefault("", resource) cidrAttr := resource.GetAttribute("cidr_block") if cidrAttr.IsNotNil() { @@ -253,5 +249,7 @@ func adaptNetworkACLRule(resource *terraform.Block) ec2.NetworkACLRule { Action: actionVal, Protocol: protocolVal, CIDRs: cidrs, + FromPort: resource.GetAttribute("from_port").AsIntValueOrDefault(-1, resource), + ToPort: resource.GetAttribute("to_port").AsIntValueOrDefault(-1, resource), } } diff --git a/pkg/iac/adapters/terraform/aws/ec2/vpc_test.go b/pkg/iac/adapters/terraform/aws/ec2/vpc_test.go index 611f8ca8527d..1acee8146d3e 100644 --- a/pkg/iac/adapters/terraform/aws/ec2/vpc_test.go +++ b/pkg/iac/adapters/terraform/aws/ec2/vpc_test.go @@ -102,6 +102,9 @@ func Test_AdaptVPC(t *testing.T) { CIDRs: []iacTypes.StringValue{ iacTypes.String("4.5.6.7/32", iacTypes.NewTestMetadata()), }, + FromPort: iacTypes.IntTest(80), + ToPort: iacTypes.IntTest(80), + Protocol: iacTypes.StringTest("tcp"), }, { Metadata: iacTypes.NewTestMetadata(), @@ -111,6 +114,9 @@ func Test_AdaptVPC(t *testing.T) { iacTypes.String("1.2.3.4/32", iacTypes.NewTestMetadata()), iacTypes.String("4.5.6.7/32", iacTypes.NewTestMetadata()), }, + FromPort: iacTypes.IntTest(22), + ToPort: iacTypes.IntTest(22), + Protocol: iacTypes.StringTest("tcp"), }, }, @@ -121,6 +127,8 @@ func Test_AdaptVPC(t *testing.T) { CIDRs: []iacTypes.StringValue{ iacTypes.String("1.2.3.4/32", iacTypes.NewTestMetadata()), }, + FromPort: iacTypes.IntTest(-1), + ToPort: iacTypes.IntTest(-1), }, }, }, @@ -137,6 +145,8 @@ func Test_AdaptVPC(t *testing.T) { CIDRs: []iacTypes.StringValue{ iacTypes.String("10.0.0.0/16", iacTypes.NewTestMetadata()), }, + FromPort: iacTypes.IntTest(22), + ToPort: iacTypes.IntTest(22), }, }, IsDefaultRule: iacTypes.Bool(false, iacTypes.NewTestMetadata()), @@ -169,6 +179,8 @@ func Test_AdaptVPC(t *testing.T) { { Metadata: iacTypes.NewTestMetadata(), Description: iacTypes.String("", iacTypes.NewTestMetadata()), + FromPort: iacTypes.IntTest(-1), + ToPort: iacTypes.IntTest(-1), }, }, @@ -176,6 +188,8 @@ func Test_AdaptVPC(t *testing.T) { { Metadata: iacTypes.NewTestMetadata(), Description: iacTypes.String("", iacTypes.NewTestMetadata()), + FromPort: iacTypes.IntTest(-1), + ToPort: iacTypes.IntTest(-1), }, }, }, @@ -188,7 +202,9 @@ func Test_AdaptVPC(t *testing.T) { Metadata: iacTypes.NewTestMetadata(), Type: iacTypes.String("ingress", iacTypes.NewTestMetadata()), Action: iacTypes.String("", iacTypes.NewTestMetadata()), - Protocol: iacTypes.String("-1", iacTypes.NewTestMetadata()), + Protocol: iacTypes.String("", iacTypes.NewTestMetadata()), + FromPort: iacTypes.IntTest(-1), + ToPort: iacTypes.IntTest(-1), }, }, IsDefaultRule: iacTypes.Bool(false, iacTypes.NewTestMetadata()), @@ -252,6 +268,9 @@ resource "aws_vpc_security_group_ingress_rule" "test" { CIDRs: []iacTypes.StringValue{ iacTypes.StringTest("0.0.0.0/0"), }, + Protocol: iacTypes.StringTest("tcp"), + FromPort: iacTypes.IntTest(22), + ToPort: iacTypes.IntTest(22), }, }, EgressRules: []ec2.SecurityGroupRule{ @@ -259,6 +278,9 @@ resource "aws_vpc_security_group_ingress_rule" "test" { CIDRs: []iacTypes.StringValue{ iacTypes.StringTest("0.0.0.0/0"), }, + Protocol: iacTypes.StringTest("-1"), + FromPort: iacTypes.IntTest(-1), + ToPort: iacTypes.IntTest(-1), }, }, }, diff --git a/pkg/iac/adapters/terraform/aws/s3/adapt_test.go b/pkg/iac/adapters/terraform/aws/s3/adapt_test.go index 1cd1dec76270..ea12206f312b 100644 --- a/pkg/iac/adapters/terraform/aws/s3/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/s3/adapt_test.go @@ -149,6 +149,15 @@ func Test_Adapt(t *testing.T) { resource "aws_s3_bucket_acl" "example" { bucket = aws_s3_bucket.example.id acl = "private" + access_control_policy { + grant { + grantee { + type = "Group" + uri = "http://acs.amazonaws.com/groups/s3/LogDelivery" + } + permission = "READ_ACP" + } + } } resource "aws_s3_bucket_server_side_encryption_configuration" "example" { @@ -250,6 +259,51 @@ func Test_Adapt(t *testing.T) { TargetBucket: iacTypes.String("aws_s3_bucket.example", iacTypes.NewTestMetadata()), }, ACL: iacTypes.String("private", iacTypes.NewTestMetadata()), + Grants: []s3.Grant{ + { + Metadata: iacTypes.NewTestMetadata(), + Grantee: s3.Grantee{ + Type: iacTypes.StringTest("Group"), + URI: iacTypes.StringTest("http://acs.amazonaws.com/groups/s3/LogDelivery"), + }, + Permissions: iacTypes.StringValueList{ + iacTypes.StringTest("READ_ACP"), + }, + }, + }, + }, + }, + }, + }, + { + name: "bucket with grants", + terraform: ` +resource "aws_s3_bucket" "this" { + bucket = "test" + + grant { + type = "Group" + uri = "http://acs.amazonaws.com/groups/s3/LogDelivery" + permissions = ["FULL_CONTROL"] + } +} +`, + expected: s3.S3{ + Buckets: []s3.Bucket{ + { + Name: iacTypes.StringTest("test"), + ACL: iacTypes.StringTest("private"), + Grants: []s3.Grant{ + { + Grantee: s3.Grantee{ + Type: iacTypes.StringTest("Group"), + URI: iacTypes.StringTest("http://acs.amazonaws.com/groups/s3/LogDelivery"), + }, + Permissions: iacTypes.StringValueList{ + iacTypes.StringTest("FULL_CONTROL"), + }, + }, + }, }, }, }, diff --git a/pkg/iac/adapters/terraform/aws/s3/bucket.go b/pkg/iac/adapters/terraform/aws/s3/bucket.go index 5ecf7e9ba21b..206b0cbdabdf 100644 --- a/pkg/iac/adapters/terraform/aws/s3/bucket.go +++ b/pkg/iac/adapters/terraform/aws/s3/bucket.go @@ -26,6 +26,7 @@ func (a *adapter) adaptBuckets() []s3.Bucket { Versioning: getVersioning(block, a), Logging: getLogging(block, a), ACL: getBucketAcl(block, a), + Grants: getGrants(block, a), AccelerateConfigurationStatus: getAccelerateStatus(block, a), BucketLocation: block.GetAttribute("region").AsStringValueOrDefault("", block), LifecycleConfiguration: getLifecycle(block, a), @@ -193,6 +194,55 @@ func getBucketAcl(block *terraform.Block, a *adapter) iacTypes.StringValue { return iacTypes.StringDefault("private", block.GetMetadata()) } +func getGrants(block *terraform.Block, a *adapter) []s3.Grant { + if val, ok := applyForBucketRelatedResource(a, block, "aws_s3_bucket_acl", func(resource *terraform.Block) []s3.Grant { + var grants []s3.Grant + + if acessControlPolicy := resource.GetBlock("access_control_policy"); acessControlPolicy.IsNotNil() { + for _, grantBlock := range acessControlPolicy.GetBlocks("grant") { + grant := s3.Grant{ + Metadata: grantBlock.GetMetadata(), + Permissions: iacTypes.StringValueList{ + grantBlock.GetAttribute("permission").AsStringValueOrDefault("", grantBlock), + }, + } + + if granteeBlock := grantBlock.GetBlock("grantee"); granteeBlock.IsNotNil() { + grant.Grantee = s3.Grantee{ + Metadata: granteeBlock.GetMetadata(), + Type: granteeBlock.GetAttribute("type").AsStringValueOrDefault("", granteeBlock), + URI: granteeBlock.GetAttribute("uri").AsStringValueOrDefault("", granteeBlock), + } + } + + grants = append(grants, grant) + } + } + + return grants + + }); ok { + return val + } + + var grants []s3.Grant + for _, grantBlock := range block.GetBlocks("grant") { + grant := s3.Grant{ + Metadata: grantBlock.GetMetadata(), + Permissions: grantBlock.GetAttribute("permissions").AsStringValueSliceOrEmpty(), + Grantee: s3.Grantee{ + Metadata: grantBlock.GetMetadata(), + Type: grantBlock.GetAttribute("type").AsStringValueOrDefault("", grantBlock), + URI: grantBlock.GetAttribute("uri").AsStringValueOrDefault("", grantBlock), + }, + } + + grants = append(grants, grant) + } + + return grants +} + func isEncrypted(sseConfgihuration *terraform.Block) iacTypes.BoolValue { return terraform.MapNestedAttribute( sseConfgihuration, diff --git a/pkg/iac/adapters/terraform/azure/network/adapt.go b/pkg/iac/adapters/terraform/azure/network/adapt.go index b2866cd9100a..4bbcca6c5fd2 100644 --- a/pkg/iac/adapters/terraform/azure/network/adapt.go +++ b/pkg/iac/adapters/terraform/azure/network/adapt.go @@ -136,8 +136,8 @@ func (a *adapter) adaptSource(ruleBlock *terraform.Block, rule *network.Security f := sourcePortRangeAttr.AsNumber() rule.SourcePorts = append(rule.SourcePorts, network.PortRange{ Metadata: sourcePortRangeAttr.GetMetadata(), - Start: int(f), - End: int(f), + Start: iacTypes.Int(int(f), sourcePortRangeAttr.GetMetadata()), + End: iacTypes.Int(int(f), sourcePortRangeAttr.GetMetadata()), }) } } @@ -160,8 +160,8 @@ func (a *adapter) adaptDestination(ruleBlock *terraform.Block, rule *network.Sec f := destPortRangeAttr.AsNumber() rule.DestinationPorts = append(rule.DestinationPorts, network.PortRange{ Metadata: destPortRangeAttr.GetMetadata(), - Start: int(f), - End: int(f), + Start: iacTypes.Int(int(f), destPortRangeAttr.GetMetadata()), + End: iacTypes.Int(int(f), destPortRangeAttr.GetMetadata()), }) } } @@ -189,8 +189,8 @@ func expandRange(r string, m iacTypes.Metadata) network.PortRange { return network.PortRange{ Metadata: m, - Start: start, - End: end, + Start: iacTypes.Int(start, m), + End: iacTypes.Int(end, m), } } diff --git a/pkg/iac/adapters/terraform/azure/network/adapt_test.go b/pkg/iac/adapters/terraform/azure/network/adapt_test.go index 15b966b06ffc..99931b6b2d3e 100644 --- a/pkg/iac/adapters/terraform/azure/network/adapt_test.go +++ b/pkg/iac/adapters/terraform/azure/network/adapt_test.go @@ -65,15 +65,15 @@ func Test_Adapt(t *testing.T) { SourcePorts: []network.PortRange{ { Metadata: iacTypes.NewTestMetadata(), - Start: 0, - End: 65535, + Start: iacTypes.IntTest(0), + End: iacTypes.IntTest(65535), }, }, DestinationPorts: []network.PortRange{ { Metadata: iacTypes.NewTestMetadata(), - Start: 3389, - End: 3389, + Start: iacTypes.IntTest(3389), + End: iacTypes.IntTest(3389), }, }, Protocol: iacTypes.String("TCP", iacTypes.NewTestMetadata()), diff --git a/pkg/iac/adapters/terraform/azure/storage/adapt.go b/pkg/iac/adapters/terraform/azure/storage/adapt.go index edc5f0029be7..6a51cf1fca2b 100644 --- a/pkg/iac/adapters/terraform/azure/storage/adapt.go +++ b/pkg/iac/adapters/terraform/azure/storage/adapt.go @@ -6,6 +6,8 @@ import ( iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) +const minimumTlsVersionOneTwo = "TLS1_2" + func Adapt(modules terraform.Modules) storage.Storage { accounts, containers, networkRules := adaptAccounts(modules) @@ -106,7 +108,7 @@ func adaptAccount(resource *terraform.Block) storage.Account { Metadata: resource.GetMetadata(), EnableLogging: iacTypes.BoolDefault(false, resource.GetMetadata()), }, - MinimumTLSVersion: iacTypes.StringDefault("TLS1_2", resource.GetMetadata()), + MinimumTLSVersion: iacTypes.StringDefault(minimumTlsVersionOneTwo, resource.GetMetadata()), } networkRulesBlocks := resource.GetBlocks("network_rules") @@ -127,7 +129,7 @@ func adaptAccount(resource *terraform.Block) storage.Account { } minTLSVersionAttr := resource.GetAttribute("min_tls_version") - account.MinimumTLSVersion = minTLSVersionAttr.AsStringValueOrDefault("TLS1_0", resource) + account.MinimumTLSVersion = minTLSVersionAttr.AsStringValueOrDefault(minimumTlsVersionOneTwo, resource) return account } diff --git a/pkg/iac/adapters/terraform/google/compute/adapt_test.go b/pkg/iac/adapters/terraform/google/compute/adapt_test.go index a90c4315364a..d5bbe5a8f566 100644 --- a/pkg/iac/adapters/terraform/google/compute/adapt_test.go +++ b/pkg/iac/adapters/terraform/google/compute/adapt_test.go @@ -184,8 +184,8 @@ func TestLines(t *testing.T) { assert.Equal(t, 59, network.Firewall.IngressRules[0].Protocol.GetMetadata().Range().GetStartLine()) assert.Equal(t, 59, network.Firewall.IngressRules[0].Protocol.GetMetadata().Range().GetEndLine()) - assert.Equal(t, 60, network.Firewall.IngressRules[0].Ports[0].GetMetadata().Range().GetStartLine()) - assert.Equal(t, 60, network.Firewall.IngressRules[0].Ports[0].GetMetadata().Range().GetEndLine()) + assert.Equal(t, 60, network.Firewall.IngressRules[0].Ports[0].Metadata.Range().GetStartLine()) + assert.Equal(t, 60, network.Firewall.IngressRules[0].Ports[0].Metadata.Range().GetEndLine()) assert.Equal(t, 64, network.Subnetworks[0].Metadata.Range().GetStartLine()) assert.Equal(t, 72, network.Subnetworks[0].Metadata.Range().GetEndLine()) diff --git a/pkg/iac/adapters/terraform/google/compute/networks.go b/pkg/iac/adapters/terraform/google/compute/networks.go index ea3c9cb97a5f..5ea8753d1f7f 100644 --- a/pkg/iac/adapters/terraform/google/compute/networks.go +++ b/pkg/iac/adapters/terraform/google/compute/networks.go @@ -105,44 +105,51 @@ func adaptNetworks(modules terraform.Modules) (networks []compute.Network) { return networks } -func expandRange(ports string, attr *terraform.Attribute) []iacTypes.IntValue { +func expandRange(ports string, meta iacTypes.Metadata) (compute.PortRange, bool) { ports = strings.ReplaceAll(ports, " ", "") if !strings.Contains(ports, "-") { i, err := strconv.Atoi(ports) if err != nil { - return nil - } - return []iacTypes.IntValue{ - iacTypes.Int(i, attr.GetMetadata()), + return compute.PortRange{}, false } + return compute.PortRange{ + Metadata: meta, + Start: iacTypes.Int(i, meta), + End: iacTypes.Int(i, meta), + }, true } parts := strings.Split(ports, "-") if len(parts) != 2 { - return nil + return compute.PortRange{}, false } start, err := strconv.Atoi(parts[0]) if err != nil { - return nil + return compute.PortRange{}, false } end, err := strconv.Atoi(parts[1]) if err != nil { - return nil - } - var output []iacTypes.IntValue - for i := start; i <= end; i++ { - output = append(output, iacTypes.Int(i, attr.GetMetadata())) + return compute.PortRange{}, false } - return output + + return compute.PortRange{ + Metadata: meta, + Start: iacTypes.Int(start, meta), + End: iacTypes.Int(end, meta), + }, true } func adaptFirewallRule(firewall *compute.Firewall, firewallBlock, ruleBlock *terraform.Block, allow bool) { protocolAttr := ruleBlock.GetAttribute("protocol") portsAttr := ruleBlock.GetAttribute("ports") - var ports []iacTypes.IntValue + var rngs []compute.PortRange rawPorts := portsAttr.AsStringValues() for _, portStr := range rawPorts { - ports = append(ports, expandRange(portStr.Value(), portsAttr)...) + rng, ok := expandRange(portStr.Value(), portsAttr.GetMetadata()) + if !ok { + continue + } + rngs = append(rngs, rng) } // ingress by default @@ -153,7 +160,7 @@ func adaptFirewallRule(firewall *compute.Firewall, firewallBlock, ruleBlock *ter Enforced: iacTypes.BoolDefault(true, firewallBlock.GetMetadata()), IsAllow: iacTypes.Bool(allow, ruleBlock.GetMetadata()), Protocol: protocolAttr.AsStringValueOrDefault("tcp", ruleBlock), - Ports: ports, + Ports: rngs, } disabledAttr := firewallBlock.GetAttribute("disabled") diff --git a/pkg/iac/adapters/terraform/google/compute/networks_test.go b/pkg/iac/adapters/terraform/google/compute/networks_test.go index 32ad00bc5334..df1cef261e7d 100644 --- a/pkg/iac/adapters/terraform/google/compute/networks_test.go +++ b/pkg/iac/adapters/terraform/google/compute/networks_test.go @@ -39,7 +39,7 @@ func Test_adaptNetworks(t *testing.T) { source_ranges = ["1.2.3.4/32"] allow { protocol = "icmp" - ports = ["80", "8080"] + ports = ["80", "8080", "9090-9095"] } } `, @@ -57,9 +57,19 @@ func Test_adaptNetworks(t *testing.T) { IsAllow: iacTypes.Bool(true, iacTypes.NewTestMetadata()), Protocol: iacTypes.String("icmp", iacTypes.NewTestMetadata()), Enforced: iacTypes.Bool(true, iacTypes.NewTestMetadata()), - Ports: []iacTypes.IntValue{ - iacTypes.Int(80, iacTypes.NewTestMetadata()), - iacTypes.Int(8080, iacTypes.NewTestMetadata()), + Ports: []compute.PortRange{ + { + Start: iacTypes.IntTest(80), + End: iacTypes.IntTest(80), + }, + { + Start: iacTypes.IntTest(8080), + End: iacTypes.IntTest(8080), + }, + { + Start: iacTypes.IntTest(9090), + End: iacTypes.IntTest(9095), + }, }, }, SourceRanges: []iacTypes.StringValue{ diff --git a/pkg/iac/debug/debug.go b/pkg/iac/debug/debug.go deleted file mode 100644 index 489c777dd205..000000000000 --- a/pkg/iac/debug/debug.go +++ /dev/null @@ -1,38 +0,0 @@ -package debug - -import ( - "fmt" - "io" - "strings" - "time" -) - -const timeFormat = "04:05.000000000" - -type Logger struct { - writer io.Writer - prefix string -} - -func New(w io.Writer, parts ...string) Logger { - return Logger{ - writer: w, - prefix: strings.Join(parts, "."), - } -} - -func (l *Logger) Extend(parts ...string) Logger { - return Logger{ - writer: l.writer, - prefix: strings.Join(append([]string{l.prefix}, parts...), "."), - } -} - -func (l *Logger) Log(format string, args ...any) { - if l.writer == nil { - return - } - message := fmt.Sprintf(format, args...) - line := fmt.Sprintf("%s %-32s %s\n", time.Now().Format(timeFormat), l.prefix, message) - _, _ = l.writer.Write([]byte(line)) -} diff --git a/pkg/iac/detection/detect.go b/pkg/iac/detection/detect.go index 596d8da1851d..fcaad6a36179 100644 --- a/pkg/iac/detection/detect.go +++ b/pkg/iac/detection/detect.go @@ -7,11 +7,13 @@ import ( "path/filepath" "strings" + "github.com/xeipuuv/gojsonschema" "gopkg.in/yaml.v3" "github.com/aquasecurity/trivy/pkg/iac/scanners/azure/arm/parser/armjson" "github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan/snapshot" "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" ) type FileType string @@ -33,25 +35,24 @@ const ( var matchers = make(map[FileType]func(name string, r io.ReadSeeker) bool) +// TODO(nikita): refactor. If the file matches the schema, it no longer needs to be checked for other scanners. // nolint func init() { matchers[FileTypeJSON] = func(name string, r io.ReadSeeker) bool { - ext := filepath.Ext(filepath.Base(name)) - if !strings.EqualFold(ext, ".json") { + if !isJSON(name) { return false } if resetReader(r) == nil { return true } - var content any - return json.NewDecoder(r).Decode(&content) == nil + b, err := io.ReadAll(r) + return err == nil && json.Valid(b) } matchers[FileTypeYAML] = func(name string, r io.ReadSeeker) bool { - ext := filepath.Ext(filepath.Base(name)) - if !strings.EqualFold(ext, ".yaml") && !strings.EqualFold(ext, ".yml") { + if !isYAML(name) { return false } if resetReader(r) == nil { @@ -87,12 +88,17 @@ func init() { contents := make(map[string]any) err := json.NewDecoder(r).Decode(&contents) - if err == nil { - if _, ok := contents["terraform_version"]; ok { - _, stillOk := contents["format_version"] - return stillOk + if err != nil { + return false + } + + for _, k := range []string{"terraform_version", "format_version"} { + if _, ok := contents[k]; !ok { + return false } } + + return true } return false } @@ -135,17 +141,21 @@ func init() { } sniff := struct { - ContentType string `json:"contentType"` - Parameters map[string]any `json:"parameters"` - Resources []any `json:"resources"` + Schema string `json:"$schema"` + Parameters map[string]any `json:"parameters"` + Resources []any `json:"resources"` }{} metadata := types.NewUnmanagedMetadata() if err := armjson.UnmarshalFromReader(r, &sniff, &metadata); err != nil { return false } - return (sniff.Parameters != nil && len(sniff.Parameters) > 0) || - (sniff.Resources != nil && len(sniff.Resources) > 0) + // schema is required https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/syntax + if !strings.HasPrefix(sniff.Schema, "https://schema.management.azure.com/schemas") { + return false + } + + return len(sniff.Parameters) > 0 || len(sniff.Resources) > 0 } matchers[FileTypeDockerfile] = func(name string, _ io.ReadSeeker) bool { @@ -216,15 +226,15 @@ func init() { } data := buf.Bytes() - marker := "\n---\n" - altMarker := "\r\n---\r\n" - if bytes.Contains(data, []byte(altMarker)) { + marker := []byte("\n---\n") + altMarker := []byte("\r\n---\r\n") + if bytes.Contains(data, altMarker) { marker = altMarker } - for _, partial := range strings.Split(string(data), marker) { + for _, partial := range bytes.Split(data, marker) { var result map[string]any - if err := yaml.Unmarshal([]byte(partial), &result); err != nil { + if err := yaml.Unmarshal(partial, &result); err != nil { continue } match := true @@ -304,3 +314,42 @@ func resetReader(r io.Reader) io.ReadSeeker { } return ensureSeeker(r) } + +func isJSON(name string) bool { + ext := filepath.Ext(name) + return strings.EqualFold(ext, ".json") +} +func isYAML(name string) bool { + ext := filepath.Ext(name) + return strings.EqualFold(ext, ".yaml") || strings.EqualFold(ext, ".yml") +} + +func IsFileMatchesSchemas(schemas map[string]*gojsonschema.Schema, typ FileType, name string, r io.ReadSeeker) bool { + defer resetReader(r) + + var l gojsonschema.JSONLoader + switch { + case typ == FileTypeJSON && isJSON(name): + b, err := io.ReadAll(r) + if err != nil { + return false + } + l = gojsonschema.NewBytesLoader(b) + case typ == FileTypeYAML && isYAML(name): + var content any + if err := yaml.NewDecoder(r).Decode(&content); err != nil { + return false + } + l = gojsonschema.NewGoLoader(content) + default: + return false + } + + for schemaPath, schema := range schemas { + if res, err := schema.Validate(l); err == nil && res.Valid() { + log.Debug("File matched schema", log.FilePath(name), log.String("schema_path", schemaPath)) + return true + } + } + return false +} diff --git a/pkg/iac/detection/detect_test.go b/pkg/iac/detection/detect_test.go index 5a0d9f876b16..f082220f2f00 100644 --- a/pkg/iac/detection/detect_test.go +++ b/pkg/iac/detection/detect_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/xeipuuv/gojsonschema" ) func Test_Detection(t *testing.T) { @@ -355,6 +356,70 @@ rules: FileTypeYAML, }, }, + { + name: "Azure ARM template with resources", + path: "test.json", + r: strings.NewReader(` +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [ + { + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2021-09-01", + "name": "{provide-unique-name}", + "location": "eastus", + "sku": { + "name": "Standard_LRS" + }, + "kind": "StorageV2", + "properties": { + "supportsHttpsTrafficOnly": true + } + } + ] +} +`), + expected: []FileType{ + FileTypeJSON, + FileTypeAzureARM, + }, + }, + { + name: "Azure ARM template with parameters", + path: "test.json", + r: strings.NewReader(` +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "storageName": { + "type": "string", + "minLength": 3, + "maxLength": 24 + } + } +} +`), + expected: []FileType{ + FileTypeJSON, + FileTypeAzureARM, + }, + }, + { + name: "empty Azure ARM template", + path: "test.json", + r: strings.NewReader(` +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [] +} +`), + expected: []FileType{ + FileTypeJSON, + }, + }, } for _, test := range tests { @@ -409,3 +474,140 @@ func BenchmarkIsType_BigFile(b *testing.B) { _ = IsType(fmt.Sprintf("./testdata/%s", "big.file"), bytes.NewReader(data), FileTypeAzureARM) } } + +func Test_IsFileMatchesSchemas(t *testing.T) { + + schema := `{ + "$id": "https://example.com/test.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "service": { "type": "string" } + }, + "required": ["service"] +}` + + schema2 := `{ + "$id": "https://example.com/test.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "provider": { "type": "string" } + }, + "required": ["provider"] + }` + + type args struct { + schemas []string + fileType FileType + fileName string + fileContent string + } + tests := []struct { + name string + args args + matches bool + }{ + { + name: "json file matches", + args: args{ + schemas: []string{schema}, + fileType: FileTypeJSON, + fileName: "test.json", + fileContent: `{ + "service": "test" +}`, + }, + matches: true, + }, + { + name: "json file dost not matches", + args: args{ + schemas: []string{schema}, + fileType: FileTypeJSON, + fileName: "test.json", + fileContent: `{ + "somefield": "test", +}`, + }, + matches: false, + }, + { + name: "json file matches, but file type is yaml", + args: args{ + schemas: []string{schema}, + fileType: FileTypeYAML, + fileName: "test.json", + fileContent: `{ + "service": "test" +}`, + }, + matches: false, + }, + { + name: "broken json file", + args: args{ + schemas: []string{schema}, + fileType: FileTypeJSON, + fileName: "test.json", + fileContent: `{ + "service": "test",, +}`, + }, + matches: false, + }, + { + name: "yaml file matches", + args: args{ + schemas: []string{schema}, + fileType: FileTypeYAML, + fileName: "test.yml", + fileContent: `service: test`, + }, + matches: true, + }, + { + name: "yaml file does not matches", + args: args{ + schemas: []string{schema}, + fileType: FileTypeYAML, + fileName: "test.yaml", + fileContent: `somefield: test`, + }, + matches: false, + }, + { + name: "broken yaml file", + args: args{ + schemas: []string{schema}, + fileType: FileTypeYAML, + fileName: "test.yaml", + fileContent: `text foobar +number: 2`, + }, + matches: false, + }, + { + name: "multiple schemas", + args: args{ + schemas: []string{schema, schema2}, + fileType: FileTypeYAML, + fileName: "test.yaml", + fileContent: `provider: test`, + }, + matches: true, + }, + } + for _, tt := range tests { + schemas := make(map[string]*gojsonschema.Schema) + for i, content := range tt.args.schemas { + l := gojsonschema.NewStringLoader(content) + s, err := gojsonschema.NewSchema(l) + require.NoError(t, err) + schemas[fmt.Sprintf("schema-%d.json", i)] = s + } + rs := strings.NewReader(tt.args.fileContent) + got := IsFileMatchesSchemas(schemas, tt.args.fileType, tt.args.fileName, rs) + assert.Equal(t, tt.matches, got) + } +} diff --git a/pkg/iac/providers/aws/ec2/vpc.go b/pkg/iac/providers/aws/ec2/vpc.go index bce7fb4de2a8..d6bff4fe7025 100644 --- a/pkg/iac/providers/aws/ec2/vpc.go +++ b/pkg/iac/providers/aws/ec2/vpc.go @@ -23,6 +23,9 @@ type SecurityGroupRule struct { Metadata iacTypes.Metadata Description iacTypes.StringValue CIDRs []iacTypes.StringValue + Protocol iacTypes.StringValue + FromPort iacTypes.IntValue + ToPort iacTypes.IntValue } type VPC struct { @@ -49,4 +52,6 @@ type NetworkACLRule struct { Action iacTypes.StringValue Protocol iacTypes.StringValue CIDRs []iacTypes.StringValue + FromPort iacTypes.IntValue + ToPort iacTypes.IntValue } diff --git a/pkg/iac/providers/aws/s3/bucket.go b/pkg/iac/providers/aws/s3/bucket.go index e884bbd4a7ff..ccabe6974c99 100755 --- a/pkg/iac/providers/aws/s3/bucket.go +++ b/pkg/iac/providers/aws/s3/bucket.go @@ -14,6 +14,7 @@ type Bucket struct { Versioning Versioning Logging Logging ACL iacTypes.StringValue + Grants []Grant BucketLocation iacTypes.StringValue AccelerateConfigurationStatus iacTypes.StringValue LifecycleConfiguration []Rules @@ -65,3 +66,15 @@ type Contents struct { type Website struct { Metadata iacTypes.Metadata } + +type Grant struct { + Metadata iacTypes.Metadata + Grantee Grantee + Permissions iacTypes.StringValueList +} + +type Grantee struct { + Metadata iacTypes.Metadata + URI iacTypes.StringValue + Type iacTypes.StringValue +} diff --git a/pkg/iac/providers/azure/network/network.go b/pkg/iac/providers/azure/network/network.go index 71c56b62b465..4fdc56e44e86 100755 --- a/pkg/iac/providers/azure/network/network.go +++ b/pkg/iac/providers/azure/network/network.go @@ -27,12 +27,12 @@ type SecurityGroupRule struct { type PortRange struct { Metadata iacTypes.Metadata - Start int - End int + Start iacTypes.IntValue + End iacTypes.IntValue } func (r PortRange) Includes(port int) bool { - return port >= r.Start && port <= r.End + return port >= r.Start.Value() && port <= r.End.Value() } type NetworkWatcherFlowLog struct { diff --git a/pkg/iac/providers/google/compute/firewall.go b/pkg/iac/providers/google/compute/firewall.go index c122369d9954..19f4044cdb72 100755 --- a/pkg/iac/providers/google/compute/firewall.go +++ b/pkg/iac/providers/google/compute/firewall.go @@ -18,7 +18,13 @@ type FirewallRule struct { Enforced iacTypes.BoolValue IsAllow iacTypes.BoolValue Protocol iacTypes.StringValue - Ports []iacTypes.IntValue + Ports []PortRange +} + +type PortRange struct { + Metadata iacTypes.Metadata + Start iacTypes.IntValue + End iacTypes.IntValue } type IngressRule struct { diff --git a/pkg/iac/rego/build.go b/pkg/iac/rego/build.go index a56ee042fb52..6c84c52617fb 100644 --- a/pkg/iac/rego/build.go +++ b/pkg/iac/rego/build.go @@ -13,7 +13,7 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/types" ) -func BuildSchemaSetFromPolicies(policies map[string]*ast.Module, paths []string, fsys fs.FS) (*ast.SchemaSet, bool, error) { +func BuildSchemaSetFromPolicies(policies map[string]*ast.Module, paths []string, fsys fs.FS, customSchemas map[string][]byte) (*ast.SchemaSet, bool, error) { schemaSet := ast.NewSchemaSet() schemaSet.Put(ast.MustParseRef("schema.input"), make(map[string]any)) // for backwards compat only var customFound bool @@ -26,9 +26,15 @@ func BuildSchemaSetFromPolicies(policies map[string]*ast.Module, paths []string, continue } + if schemaSet.Get(ss.Schema) != nil { + continue + } + var schema []byte if s, ok := schemas.SchemaMap[types.Source(schemaName)]; ok { schema = []byte(s) + } else if s, ok := customSchemas[schemaName]; ok { + schema = s } else { b, err := findSchemaInFS(paths, fsys, schemaName) if err != nil { @@ -47,7 +53,7 @@ func BuildSchemaSetFromPolicies(policies map[string]*ast.Module, paths []string, return schemaSet, false, fmt.Errorf("could not parse schema %q: %w", schemaName, err) } customFound = true - schemaSet.Put(ast.MustParseRef(ss.Schema.String()), rawSchema) + schemaSet.Put(ss.Schema, rawSchema) } } } diff --git a/pkg/iac/rego/embed.go b/pkg/iac/rego/embed.go index 9d1ac6458c52..4679102033c2 100644 --- a/pkg/iac/rego/embed.go +++ b/pkg/iac/rego/embed.go @@ -2,18 +2,20 @@ package rego import ( "context" + "fmt" "io/fs" "path/filepath" "strings" + "sync" "github.com/open-policy-agent/opa/ast" checks "github.com/aquasecurity/trivy-checks" "github.com/aquasecurity/trivy/pkg/iac/rules" + "github.com/aquasecurity/trivy/pkg/log" ) -func init() { - +var LoadAndRegister = sync.OnceFunc(func() { modules, err := LoadEmbeddedPolicies() if err != nil { // we should panic as the policies were not embedded properly @@ -28,12 +30,12 @@ func init() { } RegisterRegoRules(modules) -} +}) func RegisterRegoRules(modules map[string]*ast.Module) { ctx := context.TODO() - schemaSet, _, _ := BuildSchemaSetFromPolicies(modules, nil, nil) + schemaSet, _, _ := BuildSchemaSetFromPolicies(modules, nil, nil, make(map[string][]byte)) compiler := ast.NewCompiler(). WithSchemas(schemaSet). @@ -47,17 +49,36 @@ func RegisterRegoRules(modules map[string]*ast.Module) { } retriever := NewMetadataRetriever(compiler) + regoCheckIDs := make(map[string]struct{}) + for _, module := range modules { metadata, err := retriever.RetrieveMetadata(ctx, module) if err != nil { + log.Warn("Failed to retrieve metadata", log.String("package", module.Package.String()), log.Err(err)) continue } + if metadata.AVDID == "" { + if !metadata.Library { + log.Warn("Check ID is empty", log.FilePath(module.Package.Location.File)) + } continue } - rules.Register( - metadata.ToRule(), - ) + + if !metadata.Deprecated { + regoCheckIDs[metadata.AVDID] = struct{}{} + } + + rules.Register(metadata.ToRule()) + } + + for _, check := range rules.GetRegistered() { + if !check.Deprecated && check.CanCheck() { + if _, exists := regoCheckIDs[check.AVDID]; exists { + log.Warn("Ignore duplicate Go check", log.String("avdid", check.AVDID)) + rules.Deregister(check) + } + } } } @@ -95,8 +116,7 @@ func LoadPoliciesFromDirs(target fs.FS, paths ...string) (map[string]*ast.Module ProcessAnnotation: true, }) if err != nil { - // s.debug.Log("Failed to load module: %s, err: %s", filepath.ToSlash(path), err.Error()) - return err + return fmt.Errorf("failed to parse Rego module: %w", err) } modules[path] = module return nil diff --git a/pkg/iac/rego/embed_test.go b/pkg/iac/rego/embed_test.go index 15001119a934..9ed0b00747ed 100644 --- a/pkg/iac/rego/embed_test.go +++ b/pkg/iac/rego/embed_test.go @@ -2,6 +2,7 @@ package rego import ( "testing" + "testing/fstest" "github.com/open-policy-agent/opa/ast" "github.com/stretchr/testify/assert" @@ -10,9 +11,11 @@ import ( checks "github.com/aquasecurity/trivy-checks" "github.com/aquasecurity/trivy/pkg/iac/rules" "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/iac/state" ) func Test_EmbeddedLoading(t *testing.T) { + LoadAndRegister() frameworkRules := rules.GetRegistered() var found bool @@ -204,3 +207,49 @@ deny[res]{ }) } } + +func Test_IgnoreDuplicateChecks(t *testing.T) { + rules.Reset() + + r := scan.Rule{ + AVDID: "TEST001", + Check: func(s *state.State) (results scan.Results) { + for _, bucket := range s.AWS.S3.Buckets { + if bucket.Name.Value() == "evil" { + results.Add("Bucket name should not be evil", bucket.Name) + } + } + return + }, + } + reg := rules.Register(r) + defer rules.Deregister(reg) + + fsys := fstest.MapFS{ + "test.rego": &fstest.MapFile{ + Data: []byte(` +# METADATA +# title: "Test rego" +# scope: package +# schemas: +# - input: schema["cloud"] +# custom: +# avd_id: TEST001 +# severity: LOW +package user.test001 + +deny[res] { + res := result.new("test", {}) +} +`), + }, + } + + modules, err := LoadPoliciesFromDirs(fsys, ".") + require.NoError(t, err) + + RegisterRegoRules(modules) + registered := rules.GetRegistered() + assert.Len(t, registered, 1) + assert.Equal(t, "TEST001", registered[0].AVDID) +} diff --git a/pkg/iac/rego/load.go b/pkg/iac/rego/load.go index f2e4c0645c4f..7356384a3327 100644 --- a/pkg/iac/rego/load.go +++ b/pkg/iac/rego/load.go @@ -10,6 +10,8 @@ import ( "github.com/open-policy-agent/opa/ast" "github.com/open-policy-agent/opa/bundle" "github.com/samber/lo" + + "github.com/aquasecurity/trivy/pkg/log" ) var builtinNamespaces = map[string]struct{}{ @@ -61,26 +63,26 @@ func (s *Scanner) loadEmbedded() error { return fmt.Errorf("failed to load embedded rego libraries: %w", err) } s.embeddedLibs = loaded - s.debug.Log("Loaded %d embedded libraries.", len(loaded)) + s.logger.Debug("Embedded libraries are loaded", log.Int("count", len(loaded))) loaded, err = LoadEmbeddedPolicies() if err != nil { - return fmt.Errorf("failed to load embedded rego policies: %w", err) + return fmt.Errorf("failed to load embedded rego checks: %w", err) } s.embeddedChecks = loaded - s.debug.Log("Loaded %d embedded policies.", len(loaded)) + s.logger.Debug("Embedded checks are loaded", log.Int("count", len(loaded))) return nil } -func (s *Scanner) LoadPolicies(enableEmbeddedLibraries, enableEmbeddedPolicies bool, srcFS fs.FS, paths []string, readers []io.Reader) error { +func (s *Scanner) LoadPolicies(srcFS fs.FS) error { if s.policies == nil { s.policies = make(map[string]*ast.Module) } if s.policyFS != nil { - s.debug.Log("Overriding filesystem for checks!") + s.logger.Debug("Overriding filesystem for checks") srcFS = s.policyFS } @@ -88,35 +90,35 @@ func (s *Scanner) LoadPolicies(enableEmbeddedLibraries, enableEmbeddedPolicies b return err } - if enableEmbeddedPolicies { + if s.includeEmbeddedPolicies { s.policies = lo.Assign(s.policies, s.embeddedChecks) } - if enableEmbeddedLibraries { + if s.includeEmbeddedLibraries { s.policies = lo.Assign(s.policies, s.embeddedLibs) } var err error - if len(paths) > 0 { - loaded, err := LoadPoliciesFromDirs(srcFS, paths...) + if len(s.policyDirs) > 0 { + loaded, err := LoadPoliciesFromDirs(srcFS, s.policyDirs...) if err != nil { - return fmt.Errorf("failed to load rego checks from %s: %w", paths, err) + return fmt.Errorf("failed to load rego checks from %s: %w", s.policyDirs, err) } for name, policy := range loaded { s.policies[name] = policy } - s.debug.Log("Loaded %d checks from disk.", len(loaded)) + s.logger.Debug("Checks from disk are loaded", log.Int("count", len(loaded))) } - if len(readers) > 0 { - loaded, err := s.loadPoliciesFromReaders(readers) + if len(s.policyReaders) > 0 { + loaded, err := s.loadPoliciesFromReaders(s.policyReaders) if err != nil { return fmt.Errorf("failed to load rego checks from reader(s): %w", err) } for name, policy := range loaded { s.policies[name] = policy } - s.debug.Log("Loaded %d checks from reader(s).", len(loaded)) + s.logger.Debug("Checks from readers are loaded", log.Int("count", len(loaded))) } // gather namespaces @@ -132,7 +134,7 @@ func (s *Scanner) LoadPolicies(enableEmbeddedLibraries, enableEmbeddedPolicies b dataFS := srcFS if s.dataFS != nil { - s.debug.Log("Overriding filesystem for data!") + s.logger.Debug("Overriding filesystem for data") dataFS = s.dataFS } store, err := initStore(dataFS, s.dataDirs, namespaces) @@ -141,7 +143,7 @@ func (s *Scanner) LoadPolicies(enableEmbeddedLibraries, enableEmbeddedPolicies b } s.store = store - return s.compilePolicies(srcFS, paths) + return s.compilePolicies(srcFS, s.policyDirs) } func (s *Scanner) fallbackChecks(compiler *ast.Compiler) { @@ -168,15 +170,19 @@ func (s *Scanner) fallbackChecks(compiler *ast.Compiler) { continue } - s.debug.Log("Error occurred while parsing: %s, %s. Trying to fallback to embedded check.", loc, e.Error()) + s.logger.Error( + "Error occurred while parsing. Trying to fallback to embedded check", + log.FilePath(loc), + log.Err(e), + ) embedded := s.findMatchedEmbeddedCheck(badPolicy) if embedded == nil { - s.debug.Log("Failed to find embedded check: %s", loc) + s.logger.Error("Failed to find embedded check, skipping", log.FilePath(loc)) continue } - s.debug.Log("Found embedded check: %s", embedded.Package.Location.File) + s.logger.Debug("Found embedded check", log.FilePath(embedded.Package.Location.File)) delete(s.policies, loc) // remove bad check s.policies[embedded.Package.Location.File] = embedded delete(s.embeddedChecks, embedded.Package.Location.File) // avoid infinite loop if embedded check contains ref error @@ -214,7 +220,7 @@ func (s *Scanner) findMatchedEmbeddedCheck(badPolicy *ast.Module) *ast.Module { func (s *Scanner) prunePoliciesWithError(compiler *ast.Compiler) error { if len(compiler.Errors) > s.regoErrorLimit { - s.debug.Log("Error(s) occurred while loading checks") + s.logger.Error("Error(s) occurred while loading checks") return compiler.Errors } @@ -222,7 +228,10 @@ func (s *Scanner) prunePoliciesWithError(compiler *ast.Compiler) error { if e.Location == nil { continue } - s.debug.Log("Error occurred while parsing: %s, %s", e.Location.File, e.Error()) + s.logger.Error( + "Error occurred while parsing", + log.FilePath(e.Location.File), log.Err(e), + ) delete(s.policies, e.Location.File) } return nil @@ -230,7 +239,7 @@ func (s *Scanner) prunePoliciesWithError(compiler *ast.Compiler) error { func (s *Scanner) compilePolicies(srcFS fs.FS, paths []string) error { - schemaSet, custom, err := BuildSchemaSetFromPolicies(s.policies, paths, srcFS) + schemaSet, custom, err := BuildSchemaSetFromPolicies(s.policies, paths, srcFS, s.customSchemas) if err != nil { return err } @@ -281,11 +290,29 @@ func (s *Scanner) filterModules(retriever *MetadataRetriever) error { if err != nil { return err } + + if !meta.hasAnyFramework(s.frameworks) { + continue + } + + if IsBuiltinNamespace(getModuleNamespace(module)) { + if _, disabled := s.disabledCheckIDs[meta.ID]; disabled { // ignore builtin disabled checks + continue + } + } + if len(meta.InputOptions.Selectors) == 0 { - s.debug.Log("WARNING: Module %s has no input selectors - it will be loaded for all inputs!", name) + if !meta.Library { + s.logger.Warn( + "Module has no input selectors - it will be loaded for all inputs!", + log.FilePath(module.Package.Location.File), + log.String("module", name), + ) + } filtered[name] = module continue } + for _, selector := range meta.InputOptions.Selectors { if selector.Type == string(s.sourceType) { filtered[name] = module diff --git a/pkg/iac/rego/load_test.go b/pkg/iac/rego/load_test.go index ede06e4610ed..174cbc732525 100644 --- a/pkg/iac/rego/load_test.go +++ b/pkg/iac/rego/load_test.go @@ -4,7 +4,7 @@ import ( "bytes" "embed" "fmt" - "io" + "log/slog" "strings" "testing" "testing/fstest" @@ -15,8 +15,8 @@ import ( checks "github.com/aquasecurity/trivy-checks" "github.com/aquasecurity/trivy/pkg/iac/rego" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" ) //go:embed all:testdata/policies @@ -28,29 +28,31 @@ var embeddedChecksFS embed.FS func Test_RegoScanning_WithSomeInvalidPolicies(t *testing.T) { t.Run("allow no errors", func(t *testing.T) { var debugBuf bytes.Buffer + slog.SetDefault(log.New(log.NewHandler(&debugBuf, nil))) scanner := rego.NewScanner( types.SourceDockerfile, - options.ScannerWithRegoErrorLimits(0), - options.ScannerWithDebug(&debugBuf), + rego.WithRegoErrorLimits(0), + rego.WithPolicyDirs("."), ) - err := scanner.LoadPolicies(false, false, testEmbedFS, []string{"."}, nil) + err := scanner.LoadPolicies(testEmbedFS) require.ErrorContains(t, err, `want (one of): ["Cmd" "EndLine" "Flags" "JSON" "Original" "Path" "Stage" "StartLine" "SubCmd" "Value"]`) assert.Contains(t, debugBuf.String(), "Error(s) occurred while loading checks") }) t.Run("allow up to max 1 error", func(t *testing.T) { var debugBuf bytes.Buffer + slog.SetDefault(log.New(log.NewHandler(&debugBuf, nil))) scanner := rego.NewScanner( types.SourceDockerfile, - options.ScannerWithRegoErrorLimits(1), - options.ScannerWithDebug(&debugBuf), + rego.WithRegoErrorLimits(1), + rego.WithPolicyDirs("."), ) - err := scanner.LoadPolicies(false, false, testEmbedFS, []string{"."}, nil) + err := scanner.LoadPolicies(testEmbedFS) require.NoError(t, err) - assert.Contains(t, debugBuf.String(), "Error occurred while parsing: testdata/policies/invalid.rego, testdata/policies/invalid.rego:7") + assert.Contains(t, debugBuf.String(), "Error occurred while parsing\tfile_path=\"testdata/policies/invalid.rego\" err=\"testdata/policies/invalid.rego:7") }) t.Run("schema does not exist", func(t *testing.T) { @@ -62,9 +64,13 @@ package mypackage deny { input.evil == "foo bar" }` - scanner := rego.NewScanner(types.SourceJSON) + scanner := rego.NewScanner( + types.SourceJSON, + rego.WithPolicyDirs("."), + rego.WithPolicyReader(strings.NewReader(check)), + ) - err := scanner.LoadPolicies(false, false, fstest.MapFS{}, []string{"."}, []io.Reader{strings.NewReader(check)}) + err := scanner.LoadPolicies(fstest.MapFS{}) assert.ErrorContains(t, err, "could not find schema \"fooschema\"") }) @@ -77,7 +83,11 @@ package mypackage deny { input.evil == "foo bar" }` - scanner := rego.NewScanner(types.SourceJSON) + scanner := rego.NewScanner( + types.SourceJSON, + rego.WithPolicyDirs("."), + rego.WithPolicyReader(strings.NewReader(check)), + ) fsys := fstest.MapFS{ "schemas/fooschema.json": &fstest.MapFile{ @@ -85,7 +95,7 @@ deny { }, } - err := scanner.LoadPolicies(false, false, fsys, []string{"."}, []io.Reader{strings.NewReader(check)}) + err := scanner.LoadPolicies(fsys) assert.ErrorContains(t, err, "could not parse schema \"fooschema\"") }) @@ -95,8 +105,12 @@ deny { deny { input.evil == "foo bar" }` - scanner := rego.NewScanner(types.SourceJSON) - err := scanner.LoadPolicies(false, false, fstest.MapFS{}, []string{"."}, []io.Reader{strings.NewReader(check)}) + scanner := rego.NewScanner( + types.SourceJSON, + rego.WithPolicyDirs("."), + rego.WithPolicyReader(strings.NewReader(check)), + ) + err := scanner.LoadPolicies(fstest.MapFS{}) require.NoError(t, err) }) @@ -182,8 +196,9 @@ deny { t.Run(tt.name, func(t *testing.T) { scanner := rego.NewScanner( types.SourceDockerfile, - options.ScannerWithRegoErrorLimits(0), - options.ScannerWithEmbeddedPolicies(false), + rego.WithRegoErrorLimits(0), + rego.WithEmbeddedPolicies(false), + rego.WithPolicyDirs("."), ) tt.files["schemas/fooschema.json"] = &fstest.MapFile{ @@ -198,9 +213,8 @@ deny { }`), } - fsys := fstest.MapFS(tt.files) checks.EmbeddedPolicyFileSystem = embeddedChecksFS - err := scanner.LoadPolicies(false, false, fsys, []string{"."}, nil) + err := scanner.LoadPolicies(fstest.MapFS(tt.files)) if tt.expectedErr != "" { assert.ErrorContains(t, err, tt.expectedErr) @@ -242,8 +256,9 @@ deny { scanner := rego.NewScanner( types.SourceDockerfile, - options.ScannerWithEmbeddedPolicies(false), + rego.WithEmbeddedPolicies(false), + rego.WithPolicyDirs("."), ) - err := scanner.LoadPolicies(false, false, fsys, []string{"."}, nil) + err := scanner.LoadPolicies(fsys) require.Error(t, err) } diff --git a/pkg/iac/rego/metadata.go b/pkg/iac/rego/metadata.go index 907b8450bdcb..cb1d38724e8a 100644 --- a/pkg/iac/rego/metadata.go +++ b/pkg/iac/rego/metadata.go @@ -49,11 +49,16 @@ func NewStaticMetadata(pkgPath string, inputOpt InputOptions) *StaticMetadata { Description: fmt.Sprintf("Rego module: %s", pkgPath), Package: pkgPath, InputOptions: inputOpt, - Frameworks: make(map[framework.Framework][]string), + Frameworks: map[framework.Framework][]string{ + framework.Default: {}, + }, } } -func (sm *StaticMetadata) Update(meta map[string]any) error { +func (sm *StaticMetadata) update(meta map[string]any) error { + if sm.Frameworks == nil { + sm.Frameworks = make(map[framework.Framework][]string) + } upd := func(field *string, key string) { if raw, ok := meta[key]; ok { @@ -122,21 +127,31 @@ func (sm *StaticMetadata) Update(meta map[string]any) error { } func (sm *StaticMetadata) updateFrameworks(meta map[string]any) error { - if raw, ok := meta["frameworks"]; ok { - frameworks, ok := raw.(map[string]any) + raw, ok := meta["frameworks"] + if !ok { + return nil + } + + frameworks, ok := raw.(map[string]any) + if !ok { + return fmt.Errorf("frameworks metadata is not an object, got %T", raw) + } + + if len(frameworks) > 0 { + sm.Frameworks = make(map[framework.Framework][]string) + } + + for fw, rawIDs := range frameworks { + ids, ok := rawIDs.([]any) if !ok { - return fmt.Errorf("frameworks metadata is not an object, got %T", raw) + return fmt.Errorf("framework ids is not an array, got %T", rawIDs) } - for fw, rawIDs := range frameworks { - ids, ok := rawIDs.([]any) - if !ok { - return fmt.Errorf("framework ids is not an array, got %T", rawIDs) - } - fr := framework.Framework(fw) - for _, id := range ids { - if str, ok := id.(string); ok { - sm.Frameworks[fr] = append(sm.Frameworks[fr], str) - } + fr := framework.Framework(fw) + for _, id := range ids { + if str, ok := id.(string); ok { + sm.Frameworks[fr] = append(sm.Frameworks[fr], str) + } else { + sm.Frameworks[fr] = []string{} } } } @@ -163,7 +178,7 @@ func (sm *StaticMetadata) FromAnnotations(annotations *ast.Annotations) error { sm.References = append(sm.References, resource.Ref.String()) } if custom := annotations.Custom; custom != nil { - if err := sm.Update(custom); err != nil { + if err := sm.update(custom); err != nil { return err } } @@ -326,7 +341,7 @@ func (m *MetadataRetriever) RetrieveMetadata(ctx context.Context, module *ast.Mo return nil, fmt.Errorf("failed to parse metadata: not an object") } - if err := metadata.Update(meta); err != nil { + if err := metadata.update(meta); err != nil { return nil, err } @@ -433,3 +448,17 @@ func metadataFromRegoModule(module *ast.Module) (*StaticMetadata, error) { } return meta, nil } + +func (m *StaticMetadata) hasAnyFramework(frameworks []framework.Framework) bool { + if len(frameworks) == 0 { + frameworks = []framework.Framework{framework.Default} + } + + for _, fr := range frameworks { + if _, exists := m.Frameworks[fr]; exists { + return true + } + } + + return false +} diff --git a/pkg/iac/rego/metadata_test.go b/pkg/iac/rego/metadata_test.go index 6b4bb9773a92..b421df69bb30 100644 --- a/pkg/iac/rego/metadata_test.go +++ b/pkg/iac/rego/metadata_test.go @@ -27,12 +27,9 @@ func Test_UpdateStaticMetadata(t *testing.T) { Provider: "pr", Service: "srvc", Library: false, - Frameworks: map[framework.Framework][]string{ - framework.Default: {"dd"}, - }, } - require.NoError(t, sm.Update( + require.NoError(t, sm.update( map[string]any{ "id": "i_n", "avd_id": "a_n", @@ -68,8 +65,7 @@ func Test_UpdateStaticMetadata(t *testing.T) { Service: "srvc_n", Library: true, Frameworks: map[framework.Framework][]string{ - framework.Default: {"dd"}, - framework.ALL: {"aa"}, + framework.ALL: {"aa"}, }, CloudFormation: &scan.EngineMetadata{}, Terraform: &scan.EngineMetadata{}, @@ -82,7 +78,7 @@ func Test_UpdateStaticMetadata(t *testing.T) { sm := StaticMetadata{ References: []string{"r"}, } - require.NoError(t, sm.Update(map[string]any{ + require.NoError(t, sm.update(map[string]any{ "related_resources": []map[string]any{ { "ref": "r1_n", @@ -97,6 +93,7 @@ func Test_UpdateStaticMetadata(t *testing.T) { References: []string{"r", "r1_n", "r2_n"}, CloudFormation: &scan.EngineMetadata{}, Terraform: &scan.EngineMetadata{}, + Frameworks: make(map[framework.Framework][]string), } assert.Equal(t, expected, sm) @@ -106,7 +103,7 @@ func Test_UpdateStaticMetadata(t *testing.T) { sm := StaticMetadata{ References: []string{"r"}, } - require.NoError(t, sm.Update(map[string]any{ + require.NoError(t, sm.update(map[string]any{ "related_resources": []string{"r1_n", "r2_n"}, })) @@ -114,6 +111,7 @@ func Test_UpdateStaticMetadata(t *testing.T) { References: []string{"r", "r1_n", "r2_n"}, CloudFormation: &scan.EngineMetadata{}, Terraform: &scan.EngineMetadata{}, + Frameworks: make(map[framework.Framework][]string), } assert.Equal(t, expected, sm) @@ -123,7 +121,7 @@ func Test_UpdateStaticMetadata(t *testing.T) { sm := StaticMetadata{ Deprecated: false, } - require.NoError(t, sm.Update(map[string]any{ + require.NoError(t, sm.update(map[string]any{ "deprecated": true, })) @@ -131,10 +129,19 @@ func Test_UpdateStaticMetadata(t *testing.T) { Deprecated: true, CloudFormation: &scan.EngineMetadata{}, Terraform: &scan.EngineMetadata{}, + Frameworks: make(map[framework.Framework][]string), } assert.Equal(t, expected, sm) }) + + t.Run("frameworks is not initialized", func(t *testing.T) { + sm := StaticMetadata{} + err := sm.update(map[string]any{ + "frameworks": map[string]any{"all": []any{"a", "b", "c"}}, + }) + require.NoError(t, err) + }) } func Test_NewEngineMetadata(t *testing.T) { diff --git a/pkg/iac/rego/options.go b/pkg/iac/rego/options.go new file mode 100644 index 000000000000..5cc60284abdb --- /dev/null +++ b/pkg/iac/rego/options.go @@ -0,0 +1,119 @@ +package rego + +import ( + "io" + "io/fs" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" +) + +func WithPolicyReader(readers ...io.Reader) options.ScannerOption { + return func(s options.ConfigurableScanner) { + if ss, ok := s.(*Scanner); ok { + ss.policyReaders = readers + } + } +} + +func WithEmbeddedPolicies(include bool) options.ScannerOption { + return func(s options.ConfigurableScanner) { + if ss, ok := s.(*Scanner); ok { + ss.includeEmbeddedPolicies = include + } + } +} + +func WithEmbeddedLibraries(include bool) options.ScannerOption { + return func(s options.ConfigurableScanner) { + if ss, ok := s.(*Scanner); ok { + ss.includeEmbeddedLibraries = include + } + } +} + +// WithTrace specifies an io.Writer for trace logs (mainly rego tracing) - if not set, they are discarded +func WithTrace(w io.Writer) options.ScannerOption { + return func(s options.ConfigurableScanner) { + if ss, ok := s.(*Scanner); ok { + ss.traceWriter = w + } + } +} + +func WithPerResultTracing(enabled bool) options.ScannerOption { + return func(s options.ConfigurableScanner) { + if ss, ok := s.(*Scanner); ok { + ss.tracePerResult = enabled + } + } +} + +func WithPolicyDirs(paths ...string) options.ScannerOption { + return func(s options.ConfigurableScanner) { + if ss, ok := s.(*Scanner); ok { + ss.policyDirs = paths + } + } +} + +func WithDataDirs(paths ...string) options.ScannerOption { + return func(s options.ConfigurableScanner) { + if ss, ok := s.(*Scanner); ok { + ss.dataDirs = paths + } + } +} + +// WithPolicyNamespaces - namespaces which indicate rego policies containing enforced rules +func WithPolicyNamespaces(namespaces ...string) options.ScannerOption { + return func(s options.ConfigurableScanner) { + if ss, ok := s.(*Scanner); ok { + for _, namespace := range namespaces { + ss.ruleNamespaces[namespace] = struct{}{} + } + } + } +} + +func WithPolicyFilesystem(fsys fs.FS) options.ScannerOption { + return func(s options.ConfigurableScanner) { + if ss, ok := s.(*Scanner); ok { + ss.policyFS = fsys + } + } +} + +func WithDataFilesystem(fsys fs.FS) options.ScannerOption { + return func(s options.ConfigurableScanner) { + if ss, ok := s.(*Scanner); ok { + ss.dataFS = fsys + } + } +} + +func WithRegoErrorLimits(limit int) options.ScannerOption { + return func(s options.ConfigurableScanner) { + if ss, ok := s.(*Scanner); ok { + ss.regoErrorLimit = limit + } + } +} + +func WithCustomSchemas(schemas map[string][]byte) options.ScannerOption { + return func(s options.ConfigurableScanner) { + if ss, ok := s.(*Scanner); ok { + ss.customSchemas = schemas + } + } +} + +// WithDisabledCheckIDs disables checks by their ID (ID field in metadata) +func WithDisabledCheckIDs(ids ...string) options.ScannerOption { + return func(s options.ConfigurableScanner) { + if ss, ok := s.(*Scanner); ok { + for _, id := range ids { + ss.disabledCheckIDs[id] = struct{}{} + } + } + } +} diff --git a/pkg/iac/rego/scanner.go b/pkg/iac/rego/scanner.go index 2e0516761a02..9cf80fc83da0 100644 --- a/pkg/iac/rego/scanner.go +++ b/pkg/iac/rego/scanner.go @@ -15,13 +15,13 @@ import ( "github.com/open-policy-agent/opa/storage" "github.com/open-policy-agent/opa/util" - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/providers" "github.com/aquasecurity/trivy/pkg/iac/rego/schemas" "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" ) var checkTypesWithSubtype = map[types.Source]struct{}{ @@ -44,51 +44,45 @@ func makeSupportedProviders() map[string]struct{} { var _ options.ConfigurableScanner = (*Scanner)(nil) type Scanner struct { - ruleNamespaces map[string]struct{} - policies map[string]*ast.Module - store storage.Store - dataDirs []string - runtimeValues *ast.Term - compiler *ast.Compiler - regoErrorLimit int - debug debug.Logger - traceWriter io.Writer - tracePerResult bool - retriever *MetadataRetriever - policyFS fs.FS - dataFS fs.FS - frameworks []framework.Framework - spec string - inputSchema any // unmarshalled into this from a json schema document - sourceType types.Source - includeDeprecatedChecks bool + ruleNamespaces map[string]struct{} + policies map[string]*ast.Module + store storage.Store + runtimeValues *ast.Term + compiler *ast.Compiler + regoErrorLimit int + logger *log.Logger + traceWriter io.Writer + tracePerResult bool + retriever *MetadataRetriever + policyFS fs.FS + policyDirs []string + policyReaders []io.Reader + dataFS fs.FS + dataDirs []string + frameworks []framework.Framework + inputSchema any // unmarshalled into this from a json schema document + sourceType types.Source + includeDeprecatedChecks bool + includeEmbeddedPolicies bool + includeEmbeddedLibraries bool embeddedLibs map[string]*ast.Module embeddedChecks map[string]*ast.Module + customSchemas map[string][]byte + + disabledCheckIDs map[string]struct{} } func (s *Scanner) SetIncludeDeprecatedChecks(b bool) { s.includeDeprecatedChecks = b } -func (s *Scanner) SetUseEmbeddedLibraries(b bool) { - // handled externally -} - -func (s *Scanner) SetSpec(spec string) { - s.spec = spec -} - func (s *Scanner) SetRegoOnly(bool) {} func (s *Scanner) SetFrameworks(frameworks []framework.Framework) { s.frameworks = frameworks } -func (s *Scanner) SetUseEmbeddedPolicies(b bool) { - // handled externally -} - func (s *Scanner) trace(heading string, input any) { if s.traceWriter == nil { return @@ -100,52 +94,6 @@ func (s *Scanner) trace(heading string, input any) { _, _ = fmt.Fprintf(s.traceWriter, "REGO %[1]s:\n%s\nEND REGO %[1]s\n\n", heading, string(data)) } -func (s *Scanner) SetPolicyFilesystem(fsys fs.FS) { - s.policyFS = fsys -} - -func (s *Scanner) SetDataFilesystem(fsys fs.FS) { - s.dataFS = fsys -} - -func (s *Scanner) SetPolicyReaders(_ []io.Reader) { - // NOTE: Policy readers option not applicable for rego, policies are loaded on-demand by other scanners. -} - -func (s *Scanner) SetDebugWriter(writer io.Writer) { - s.debug = debug.New(writer, "rego", "scanner") -} - -func (s *Scanner) SetTraceWriter(writer io.Writer) { - s.traceWriter = writer -} - -func (s *Scanner) SetPerResultTracingEnabled(b bool) { - s.tracePerResult = b -} - -func (s *Scanner) SetPolicyDirs(_ ...string) { - // NOTE: Policy dirs option not applicable for rego, policies are loaded on-demand by other scanners. -} - -func (s *Scanner) SetDataDirs(dirs ...string) { - s.dataDirs = dirs -} - -func (s *Scanner) SetPolicyNamespaces(namespaces ...string) { - for _, namespace := range namespaces { - s.ruleNamespaces[namespace] = struct{}{} - } -} - -func (s *Scanner) SetSkipRequiredCheck(_ bool) { - // NOTE: Skip required option not applicable for rego. -} - -func (s *Scanner) SetRegoErrorLimit(limit int) { - s.regoErrorLimit = limit -} - type DynamicMetadata struct { Warning bool Filepath string @@ -155,16 +103,21 @@ type DynamicMetadata struct { } func NewScanner(source types.Source, opts ...options.ScannerOption) *Scanner { + LoadAndRegister() + schema, ok := schemas.SchemaMap[source] if !ok { schema = schemas.Anything } s := &Scanner{ - regoErrorLimit: ast.CompileErrorLimitDefault, - sourceType: source, - ruleNamespaces: make(map[string]struct{}), - runtimeValues: addRuntimeValues(), + regoErrorLimit: ast.CompileErrorLimitDefault, + sourceType: source, + ruleNamespaces: make(map[string]struct{}), + runtimeValues: addRuntimeValues(), + logger: log.WithPrefix("rego"), + customSchemas: make(map[string][]byte), + disabledCheckIDs: make(map[string]struct{}), } maps.Copy(s.ruleNamespaces, builtinNamespaces) @@ -181,10 +134,6 @@ func NewScanner(source types.Source, opts ...options.ScannerOption) *Scanner { return s } -func (s *Scanner) SetParentDebugLogger(l debug.Logger) { - s.debug = l.Extend("rego") -} - func (s *Scanner) runQuery(ctx context.Context, query string, input ast.Value, disableTracing bool) (rego.ResultSet, []string, error) { trace := (s.traceWriter != nil || s.tracePerResult) && !disableTracing @@ -245,7 +194,7 @@ func GetInputsContents(inputs []Input) []any { func (s *Scanner) ScanInput(ctx context.Context, inputs ...Input) (scan.Results, error) { - s.debug.Log("Scanning %d inputs...", len(inputs)) + s.logger.Debug("Scanning inputs", "count", len(inputs)) var results scan.Results @@ -265,9 +214,11 @@ func (s *Scanner) ScanInput(ctx context.Context, inputs ...Input) (scan.Results, staticMeta, err := s.retriever.RetrieveMetadata(ctx, module, GetInputsContents(inputs)...) if err != nil { - s.debug.Log( - "Error occurred while retrieving metadata from check %q: %s", - module.Package.Location.File, err) + s.logger.Error( + "Error occurred while retrieving metadata from check", + log.FilePath(module.Package.Location.File), + log.Err(err), + ) continue } @@ -298,9 +249,12 @@ func (s *Scanner) ScanInput(ctx context.Context, inputs ...Input) (scan.Results, if isEnforcedRule(ruleName) { ruleResults, err := s.applyRule(ctx, namespace, ruleName, inputs, staticMeta.InputOptions.Combined) if err != nil { - s.debug.Log( - "Error occurred while applying rule %q from check %q: %s", - ruleName, module.Package.Location.File, err) + s.logger.Error( + "Error occurred while applying rule from check", + log.String("rule", ruleName), + log.FilePath(module.Package.Location.File), + log.Err(err), + ) continue } results = append(results, s.embellishResultsWithRuleMetadata(ruleResults, *staticMeta)...) @@ -388,7 +342,7 @@ func (s *Scanner) applyRule(ctx context.Context, namespace, rule string, inputs s.trace("INPUT", input) parsedInput, err := parseRawInput(input.Contents) if err != nil { - s.debug.Log("Error occurred while parsing input: %s", err) + s.logger.Error("Error occurred while parsing input", log.Err(err)) continue } if ignored, err := s.isIgnored(ctx, namespace, rule, parsedInput); err != nil { diff --git a/pkg/iac/rego/scanner_test.go b/pkg/iac/rego/scanner_test.go index 67a80f51c2fd..1310e56d2ec4 100644 --- a/pkg/iac/rego/scanner_test.go +++ b/pkg/iac/rego/scanner_test.go @@ -1,4 +1,4 @@ -package rego +package rego_test import ( "bytes" @@ -14,7 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/severity" "github.com/aquasecurity/trivy/pkg/iac/types" ) @@ -43,13 +43,13 @@ deny { `, }) - scanner := NewScanner(types.SourceJSON) - require.NoError( - t, - scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + scanner := rego.NewScanner( + types.SourceJSON, + rego.WithPolicyDirs("policies"), ) + require.NoError(t, scanner.LoadPolicies(srcFS)) - results, err := scanner.ScanInput(context.TODO(), Input{ + results, err := scanner.ScanInput(context.TODO(), rego.Input{ Path: "/evil.lol", Contents: map[string]any{ "evil": true, @@ -78,13 +78,13 @@ deny { srcFS := os.DirFS(tmp) - scanner := NewScanner(types.SourceJSON) - require.NoError( - t, - scanner.LoadPolicies(false, false, srcFS, []string{"/policies"}, nil), + scanner := rego.NewScanner( + types.SourceJSON, + rego.WithPolicyDirs("policies"), ) + require.NoError(t, scanner.LoadPolicies(srcFS)) - results, err := scanner.ScanInput(context.TODO(), Input{ + results, err := scanner.ScanInput(context.TODO(), rego.Input{ Path: "/evil.lol", Contents: map[string]any{ "evil": true, @@ -113,13 +113,13 @@ warn { `, }) - scanner := NewScanner(types.SourceJSON) - require.NoError( - t, - scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + scanner := rego.NewScanner( + types.SourceJSON, + rego.WithPolicyDirs("policies"), ) + require.NoError(t, scanner.LoadPolicies(srcFS)) - results, err := scanner.ScanInput(context.TODO(), Input{ + results, err := scanner.ScanInput(context.TODO(), rego.Input{ Path: "/evil.lol", Contents: map[string]any{ "evil": true, @@ -145,13 +145,13 @@ deny { `, }) - scanner := NewScanner(types.SourceJSON) - require.NoError( - t, - scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + scanner := rego.NewScanner( + types.SourceJSON, + rego.WithPolicyDirs("policies"), ) + require.NoError(t, scanner.LoadPolicies(srcFS)) - results, err := scanner.ScanInput(context.TODO(), Input{ + results, err := scanner.ScanInput(context.TODO(), rego.Input{ Path: "/evil.lol", Contents: map[string]any{ "evil": false, @@ -188,13 +188,13 @@ exception[ns] { `, }) - scanner := NewScanner(types.SourceJSON) - require.NoError( - t, - scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + scanner := rego.NewScanner( + types.SourceJSON, + rego.WithPolicyDirs("policies"), ) + require.NoError(t, scanner.LoadPolicies(srcFS)) - results, err := scanner.ScanInput(context.TODO(), Input{ + results, err := scanner.ScanInput(context.TODO(), rego.Input{ Path: "/evil.lol", Contents: map[string]any{ "evil": true, @@ -236,13 +236,13 @@ exception[ns] { `, }) - scanner := NewScanner(types.SourceJSON) - require.NoError( - t, - scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + scanner := rego.NewScanner( + types.SourceJSON, + rego.WithPolicyDirs("policies"), ) + require.NoError(t, scanner.LoadPolicies(srcFS)) - results, err := scanner.ScanInput(context.TODO(), Input{ + results, err := scanner.ScanInput(context.TODO(), rego.Input{ Path: "/evil.lol", Contents: map[string]any{ "evil": true, @@ -273,13 +273,13 @@ exception[rules] { `, }) - scanner := NewScanner(types.SourceJSON) - require.NoError( - t, - scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + scanner := rego.NewScanner( + types.SourceJSON, + rego.WithPolicyDirs("policies"), ) + require.NoError(t, scanner.LoadPolicies(srcFS)) - results, err := scanner.ScanInput(context.TODO(), Input{ + results, err := scanner.ScanInput(context.TODO(), rego.Input{ Path: "/evil.lol", Contents: map[string]any{ "evil": true, @@ -309,13 +309,13 @@ exception[rules] { `, }) - scanner := NewScanner(types.SourceJSON) - require.NoError( - t, - scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + scanner := rego.NewScanner( + types.SourceJSON, + rego.WithPolicyDirs("policies"), ) + require.NoError(t, scanner.LoadPolicies(srcFS)) - results, err := scanner.ScanInput(context.TODO(), Input{ + results, err := scanner.ScanInput(context.TODO(), rego.Input{ Path: "/evil.lol", Contents: map[string]any{ "evil": true, @@ -343,13 +343,13 @@ deny_evil { `, }) - scanner := NewScanner(types.SourceJSON) - require.NoError( - t, - scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + scanner := rego.NewScanner( + types.SourceJSON, + rego.WithPolicyDirs("policies"), ) + require.NoError(t, scanner.LoadPolicies(srcFS)) - results, err := scanner.ScanInput(context.TODO(), Input{ + results, err := scanner.ScanInput(context.TODO(), rego.Input{ Path: "/evil.lol", Contents: map[string]any{ "evil": true, @@ -374,13 +374,13 @@ deny[msg] { `, }) - scanner := NewScanner(types.SourceJSON) - require.NoError( - t, - scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + scanner := rego.NewScanner( + types.SourceJSON, + rego.WithPolicyDirs("policies"), ) + require.NoError(t, scanner.LoadPolicies(srcFS)) - results, err := scanner.ScanInput(context.TODO(), Input{ + results, err := scanner.ScanInput(context.TODO(), rego.Input{ Path: "/evil.lol", Contents: map[string]any{ "evil": true, @@ -412,13 +412,13 @@ deny[res] { `, }) - scanner := NewScanner(types.SourceJSON) - require.NoError( - t, - scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + scanner := rego.NewScanner( + types.SourceJSON, + rego.WithPolicyDirs("policies"), ) + require.NoError(t, scanner.LoadPolicies(srcFS)) - results, err := scanner.ScanInput(context.TODO(), Input{ + results, err := scanner.ScanInput(context.TODO(), rego.Input{ Path: "/evil.lol", Contents: map[string]any{ "evil": true, @@ -454,13 +454,13 @@ deny[res] { `, }) - scanner := NewScanner(types.SourceJSON) - require.NoError( - t, - scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + scanner := rego.NewScanner( + types.SourceJSON, + rego.WithPolicyDirs("policies"), ) + require.NoError(t, scanner.LoadPolicies(srcFS)) - results, err := scanner.ScanInput(context.TODO(), Input{ + results, err := scanner.ScanInput(context.TODO(), rego.Input{ Path: "/evil.lol", Contents: map[string]any{ "evil": true, @@ -508,13 +508,13 @@ deny[res] { `, }) - scanner := NewScanner(types.SourceJSON) - require.NoError( - t, - scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + scanner := rego.NewScanner( + types.SourceJSON, + rego.WithPolicyDirs("policies"), ) + require.NoError(t, scanner.LoadPolicies(srcFS)) - results, err := scanner.ScanInput(context.TODO(), Input{ + results, err := scanner.ScanInput(context.TODO(), rego.Input{ Path: "/evil.lol", Contents: map[string]any{ "evil": true, @@ -557,13 +557,13 @@ deny { `, }) - scanner := NewScanner(types.SourceJSON) - require.NoError( - t, - scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + scanner := rego.NewScanner( + types.SourceJSON, + rego.WithPolicyDirs("policies"), ) + require.NoError(t, scanner.LoadPolicies(srcFS)) - results, err := scanner.ScanInput(context.TODO(), Input{ + results, err := scanner.ScanInput(context.TODO(), rego.Input{ Path: "/evil.lol", Contents: map[string]any{ "evil": true, @@ -591,13 +591,13 @@ deny { `, }) - scanner := NewScanner(types.SourceJSON) - require.NoError( - t, - scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + scanner := rego.NewScanner( + types.SourceJSON, + rego.WithPolicyDirs("policies"), ) + require.NoError(t, scanner.LoadPolicies(srcFS)) - results, err := scanner.ScanInput(context.TODO(), Input{ + results, err := scanner.ScanInput(context.TODO(), rego.Input{ Path: "/evil.lol", Contents: map[string]any{ "evil": true, @@ -622,13 +622,13 @@ deny { `, }) - scanner := NewScanner(types.SourceJSON) - require.NoError( - t, - scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + scanner := rego.NewScanner( + types.SourceJSON, + rego.WithPolicyDirs("policies"), ) + require.NoError(t, scanner.LoadPolicies(srcFS)) - results, err := scanner.ScanInput(context.TODO(), Input{ + results, err := scanner.ScanInput(context.TODO(), rego.Input{ Path: "/evil.lol", Contents: map[string]any{ "evil": true, @@ -657,13 +657,14 @@ deny { traceBuffer := bytes.NewBuffer([]byte{}) - scanner := NewScanner(types.SourceJSON, options.ScannerWithTrace(traceBuffer)) - require.NoError( - t, - scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + scanner := rego.NewScanner( + types.SourceJSON, + rego.WithTrace(traceBuffer), + rego.WithPolicyDirs("policies"), ) + require.NoError(t, scanner.LoadPolicies(srcFS)) - results, err := scanner.ScanInput(context.TODO(), Input{ + results, err := scanner.ScanInput(context.TODO(), rego.Input{ Path: "/evil.lol", Contents: map[string]any{ "evil": true, @@ -691,13 +692,14 @@ deny { `, }) - scanner := NewScanner(types.SourceJSON, options.ScannerWithPerResultTracing(true)) - require.NoError( - t, - scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + scanner := rego.NewScanner( + types.SourceJSON, + rego.WithPerResultTracing(true), + rego.WithPolicyDirs("policies"), ) + require.NoError(t, scanner.LoadPolicies(srcFS)) - results, err := scanner.ScanInput(context.TODO(), Input{ + results, err := scanner.ScanInput(context.TODO(), rego.Input{ Path: "/evil.lol", Contents: map[string]any{ "evil": true, @@ -729,13 +731,13 @@ deny { `, }) - scanner := NewScanner(types.SourceJSON) - require.NoError( - t, - scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + scanner := rego.NewScanner( + types.SourceJSON, + rego.WithPolicyDirs("policies"), ) + require.NoError(t, scanner.LoadPolicies(srcFS)) - results, err := scanner.ScanInput(context.TODO(), Input{ + results, err := scanner.ScanInput(context.TODO(), rego.Input{ Path: "/evil.lol", Contents: map[string]any{ "text": "dynamic", @@ -762,13 +764,13 @@ deny { `, }) - scanner := NewScanner(types.SourceJSON) - require.NoError( - t, - scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + scanner := rego.NewScanner( + types.SourceJSON, + rego.WithPolicyDirs("policies"), ) + require.NoError(t, scanner.LoadPolicies(srcFS)) - results, err := scanner.ScanInput(context.TODO(), Input{ + results, err := scanner.ScanInput(context.TODO(), rego.Input{ Path: "/evil.lol", Contents: map[string]any{ "text": "test", @@ -809,13 +811,14 @@ deny { `, }) - scanner := NewScanner(types.SourceJSON) - require.NoError( - t, - scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + scanner := rego.NewScanner( + types.SourceJSON, + rego.WithPerResultTracing(true), + rego.WithPolicyDirs("policies"), ) + require.NoError(t, scanner.LoadPolicies(srcFS)) - results, err := scanner.ScanInput(context.TODO(), Input{ + results, err := scanner.ScanInput(context.TODO(), rego.Input{ Path: "/evil.lol", Contents: map[string]any{ "text": "test", @@ -847,13 +850,11 @@ deny { `, }) - scanner := NewScanner(types.SourceDockerfile) - scanner.SetRegoErrorLimit(0) // override to not allow any errors - assert.ErrorContains( - t, - scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), - "undefined ref: input.evil", + scanner := rego.NewScanner( + types.SourceDockerfile, + rego.WithPolicyDirs("policies"), ) + require.NoError(t, scanner.LoadPolicies(srcFS)) } func Test_RegoScanning_WithValidInputSchema(t *testing.T) { @@ -870,11 +871,11 @@ deny { `, }) - scanner := NewScanner(types.SourceDockerfile) - require.NoError( - t, - scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + scanner := rego.NewScanner( + types.SourceDockerfile, + rego.WithPolicyDirs("policies"), ) + require.NoError(t, scanner.LoadPolicies(srcFS)) } func Test_RegoScanning_WithFilepathToSchema(t *testing.T) { @@ -889,11 +890,16 @@ deny { } `, }) - scanner := NewScanner(types.SourceJSON) - scanner.SetRegoErrorLimit(0) // override to not allow any errors + + scanner := rego.NewScanner( + types.SourceJSON, + rego.WithRegoErrorLimits(0), + rego.WithPolicyDirs("policies"), + ) + assert.ErrorContains( t, - scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + scanner.LoadPolicies(srcFS), "undefined ref: input.evil", ) } @@ -921,16 +927,16 @@ deny { "data/junk.txt": "this file should be ignored", }) - scanner := NewScanner(types.SourceJSON) - scanner.SetDataFilesystem(dataFS) - scanner.SetDataDirs(".") - - require.NoError( - t, - scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + scanner := rego.NewScanner( + types.SourceJSON, + rego.WithDataFilesystem(dataFS), + rego.WithDataDirs("."), + rego.WithPolicyDirs("policies"), ) - results, err := scanner.ScanInput(context.TODO(), Input{}) + require.NoError(t, scanner.LoadPolicies(srcFS)) + + results, err := scanner.ScanInput(context.TODO(), rego.Input{}) require.NoError(t, err) assert.Len(t, results.GetFailed(), 1) @@ -961,16 +967,16 @@ deny { "data/junk.txt": "this file should be ignored", }) - scanner := NewScanner(types.SourceJSON) - scanner.SetDataFilesystem(dataFS) - scanner.SetDataDirs("X://") - - require.NoError( - t, - scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + scanner := rego.NewScanner( + types.SourceJSON, + rego.WithDataFilesystem(dataFS), + rego.WithDataDirs("X://"), + rego.WithPolicyDirs("policies"), ) - results, err := scanner.ScanInput(context.TODO(), Input{}) + require.NoError(t, scanner.LoadPolicies(srcFS)) + + results, err := scanner.ScanInput(context.TODO(), rego.Input{}) require.NoError(t, err) assert.Len(t, results.GetFailed(), 1) @@ -997,19 +1003,13 @@ deny { }, } - var buf bytes.Buffer - scanner := NewScanner( + scanner := rego.NewScanner( types.SourceYAML, - options.ScannerWithDebug(&buf), + rego.WithPolicyDirs("checks"), ) - require.NoError( - t, - scanner.LoadPolicies(false, false, fsys, []string{"checks"}, nil), - ) - _, err := scanner.ScanInput(context.TODO(), Input{}) + require.NoError(t, scanner.LoadPolicies(fsys)) + _, err := scanner.ScanInput(context.TODO(), rego.Input{}) require.NoError(t, err) - assert.Contains(t, buf.String(), - `Error occurred while applying rule "deny" from check "checks/bad.rego"`) } func Test_RegoScanning_WithDeprecatedCheck(t *testing.T) { @@ -1069,13 +1069,10 @@ deny { "policies/test.rego": tc.policy, }) - scanner := NewScanner(types.SourceJSON) - require.NoError( - t, - scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), - ) + scanner := rego.NewScanner(types.SourceJSON, rego.WithPolicyDirs("policies")) + require.NoError(t, scanner.LoadPolicies(srcFS)) - results, err := scanner.ScanInput(context.TODO(), Input{ + results, err := scanner.ScanInput(context.TODO(), rego.Input{ Path: "/evil.lol", Contents: map[string]any{ "text": "test", @@ -1086,3 +1083,157 @@ deny { }) } } + +func Test_RegoScanner_WithCustomSchemas(t *testing.T) { + + schema := `{ + "$id": "https://example.com/test.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "service": { "type": "string" } + }, + "required": ["service"] +}` + + tests := []struct { + name string + check string + expectedResults int + }{ + { + name: "happy path", + check: `# METADATA +# title: test check +# schemas: +# - input: schema["test"] +package user.test + +deny { + input.service == "test" +} +`, + expectedResults: 1, + }, + { + name: "sad path", + check: `# METADATA +# title: test check +# schemas: +# - input: schema["test"] +package user.test + +deny { + input.other == "test" +} +`, + expectedResults: 0, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + scanner := rego.NewScanner( + types.SourceYAML, + rego.WithCustomSchemas(map[string][]byte{ + "test": []byte(schema), + }), + rego.WithPolicyNamespaces("user"), + rego.WithPolicyReader(strings.NewReader(tc.check)), + ) + + require.NoError(t, scanner.LoadPolicies(nil)) + + results, err := scanner.ScanInput(context.TODO(), rego.Input{ + Path: "test.yaml", + Contents: map[string]any{"service": "test"}, + }) + require.NoError(t, err) + require.Len(t, results, tc.expectedResults, tc.name) + }) + } +} + +func Test_RegoScanner_WithDisabledCheckIDs(t *testing.T) { + + check := `# METADATA +# custom: +# id: TEST-001 +# avd_id: AVD-TEST-001 +# severity: LOW +# provider: aws +# service: s3 +# short_code: test +package builtin.test + +deny { + true +} +` + + tests := []struct { + name string + disabledChecks []string + inputCheck string + expected bool + }{ + { + name: "no disabled checks", + expected: true, + inputCheck: check, + }, + { + name: "disable check by ID", + disabledChecks: []string{"TEST-001"}, + inputCheck: check, + }, + { + name: "disabling a non-existent check", + disabledChecks: []string{"FOO"}, + expected: true, + inputCheck: check, + }, + { + name: "one of the identifiers does not exist", + disabledChecks: []string{"FOO", "TEST-001"}, + inputCheck: check, + }, + { + name: "do not disable user checks with builtin IDs", + inputCheck: `# METADATA +# custom: +# id: TEST-001 +# avd_id: AVD-TEST-001 +# severity: LOW +# provider: aws +# service: s3 +# short_code: test +package user.test + +deny { + true +} +`, + disabledChecks: []string{"TEST-001"}, + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + scanner := rego.NewScanner( + types.SourceYAML, + rego.WithPolicyReader(strings.NewReader(tt.inputCheck)), + rego.WithDisabledCheckIDs(tt.disabledChecks...), + rego.WithPolicyNamespaces("user"), + ) + + require.NoError(t, scanner.LoadPolicies(nil)) + results, err := scanner.ScanInput(context.TODO(), rego.Input{}) + require.NoError(t, err) + + require.Equal(t, tt.expected, len(results.GetFailed()) > 0) + }) + } +} diff --git a/pkg/iac/rego/schemas/cloud.json b/pkg/iac/rego/schemas/cloud.json index ad81b6488ae8..b034f24fa104 100644 --- a/pkg/iac/rego/schemas/cloud.json +++ b/pkg/iac/rego/schemas/cloud.json @@ -1615,10 +1615,18 @@ "$ref": "#/definitions/github.aaakk.us.kg.aquasecurity.trivy.pkg.iac.types.StringValue" } }, + "fromport": { + "type": "object", + "$ref": "#/definitions/github.aaakk.us.kg.aquasecurity.trivy.pkg.iac.types.IntValue" + }, "protocol": { "type": "object", "$ref": "#/definitions/github.aaakk.us.kg.aquasecurity.trivy.pkg.iac.types.StringValue" }, + "toport": { + "type": "object", + "$ref": "#/definitions/github.aaakk.us.kg.aquasecurity.trivy.pkg.iac.types.IntValue" + }, "type": { "type": "object", "$ref": "#/definitions/github.aaakk.us.kg.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -1677,6 +1685,18 @@ "description": { "type": "object", "$ref": "#/definitions/github.aaakk.us.kg.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "fromport": { + "type": "object", + "$ref": "#/definitions/github.aaakk.us.kg.aquasecurity.trivy.pkg.iac.types.IntValue" + }, + "protocol": { + "type": "object", + "$ref": "#/definitions/github.aaakk.us.kg.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "toport": { + "type": "object", + "$ref": "#/definitions/github.aaakk.us.kg.aquasecurity.trivy.pkg.iac.types.IntValue" } } }, @@ -3647,6 +3667,13 @@ "type": "object", "$ref": "#/definitions/github.aaakk.us.kg.aquasecurity.trivy.pkg.iac.providers.aws.s3.Encryption" }, + "grants": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.aaakk.us.kg.aquasecurity.trivy.pkg.iac.providers.aws.s3.Grant" + } + }, "lifecycleconfiguration": { "type": "array", "items": { @@ -3713,6 +3740,43 @@ } } }, + "github.aaakk.us.kg.aquasecurity.trivy.pkg.iac.providers.aws.s3.Grant": { + "type": "object", + "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.aaakk.us.kg.aquasecurity.trivy.pkg.iac.types.Metadata" + }, + "grantee": { + "type": "object", + "$ref": "#/definitions/github.aaakk.us.kg.aquasecurity.trivy.pkg.iac.providers.aws.s3.Grantee" + }, + "permissions": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.aaakk.us.kg.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + } + }, + "github.aaakk.us.kg.aquasecurity.trivy.pkg.iac.providers.aws.s3.Grantee": { + "type": "object", + "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.aaakk.us.kg.aquasecurity.trivy.pkg.iac.types.Metadata" + }, + "type": { + "type": "object", + "$ref": "#/definitions/github.aaakk.us.kg.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "uri": { + "type": "object", + "$ref": "#/definitions/github.aaakk.us.kg.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, "github.aaakk.us.kg.aquasecurity.trivy.pkg.iac.providers.aws.s3.Logging": { "type": "object", "properties": { @@ -5163,10 +5227,12 @@ "$ref": "#/definitions/github.aaakk.us.kg.aquasecurity.trivy.pkg.iac.types.Metadata" }, "end": { - "type": "integer" + "type": "object", + "$ref": "#/definitions/github.aaakk.us.kg.aquasecurity.trivy.pkg.iac.types.IntValue" }, "start": { - "type": "integer" + "type": "object", + "$ref": "#/definitions/github.aaakk.us.kg.aquasecurity.trivy.pkg.iac.types.IntValue" } } }, @@ -6040,7 +6106,7 @@ "type": "array", "items": { "type": "object", - "$ref": "#/definitions/github.aaakk.us.kg.aquasecurity.trivy.pkg.iac.types.IntValue" + "$ref": "#/definitions/github.aaakk.us.kg.aquasecurity.trivy.pkg.iac.providers.google.compute.PortRange" } }, "protocol": { @@ -6172,6 +6238,23 @@ } } }, + "github.aaakk.us.kg.aquasecurity.trivy.pkg.iac.providers.google.compute.PortRange": { + "type": "object", + "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.aaakk.us.kg.aquasecurity.trivy.pkg.iac.types.Metadata" + }, + "end": { + "type": "object", + "$ref": "#/definitions/github.aaakk.us.kg.aquasecurity.trivy.pkg.iac.types.IntValue" + }, + "start": { + "type": "object", + "$ref": "#/definitions/github.aaakk.us.kg.aquasecurity.trivy.pkg.iac.types.IntValue" + } + } + }, "github.aaakk.us.kg.aquasecurity.trivy.pkg.iac.providers.google.compute.ProjectMetadata": { "type": "object", "properties": { diff --git a/pkg/iac/scan/code.go b/pkg/iac/scan/code.go index e61b547988c6..8388dd4dbf6e 100644 --- a/pkg/iac/scan/code.go +++ b/pkg/iac/scan/code.go @@ -2,7 +2,6 @@ package scan import ( "bufio" - "bytes" "fmt" "io/fs" "path/filepath" @@ -15,6 +14,44 @@ type Code struct { Lines []Line } +func (c *Code) truncateLines(maxLines int) { + previouslyTruncated := maxLines-1 > 0 && c.Lines[maxLines-2].Truncated + if maxLines-1 > 0 && c.Lines[maxLines-1].LastCause { + c.Lines[maxLines-2].LastCause = true + } + c.Lines[maxLines-1] = Line{ + Truncated: true, + Number: c.Lines[maxLines-1].Number, + } + if previouslyTruncated { + c.Lines = c.Lines[:maxLines-1] + } else { + c.Lines = c.Lines[:maxLines] + } +} + +func (c *Code) markFirstAndLastCauses() { + var isFirst bool + var isLast bool + + for i, line := range c.Lines { + if line.IsCause && !isFirst { + c.Lines[i].FirstCause = true + isFirst = true + } + + if isFirst && !line.IsCause && i > 0 { + c.Lines[i-1].LastCause = true + isLast = true + break + } + } + + if !isLast && len(c.Lines) > 0 { + c.Lines[len(c.Lines)-1].LastCause = true + } +} + type Line struct { Number int `json:"Number"` Content string `json:"Content"` @@ -96,199 +133,187 @@ func OptionCodeWithHighlighted(include bool) CodeOption { } } -func validateRange(r iacTypes.Range) error { - if r.GetStartLine() < 0 || r.GetStartLine() > r.GetEndLine() || r.GetEndLine() < 0 { - return fmt.Errorf("invalid range: %s", r.String()) - } - return nil -} - -// nolint func (r *Result) GetCode(opts ...CodeOption) (*Code, error) { - settings := defaultCodeSettings for _, opt := range opts { opt(&settings) } - srcFS := r.Metadata().Range().GetFS() - if srcFS == nil { + fsys := r.Metadata().Range().GetFS() + if fsys == nil { return nil, fmt.Errorf("code unavailable: result was not mapped to a known filesystem") } - innerRange := r.Range() - outerRange := innerRange - metadata := r.Metadata() - for { - if parent := metadata.Parent(); parent != nil && - parent.Range().GetFilename() == metadata.Range().GetFilename() && - parent.Range().GetStartLine() > 0 { - outerRange = parent.Range() - metadata = *parent - continue - } - break + innerRange := r.metadata.Range() + if err := innerRange.Validate(); err != nil { + return nil, err } - if err := validateRange(innerRange); err != nil { - return nil, err + if innerRange.GetStartLine() == 0 { + return nil, fmt.Errorf("inner range has invalid start line: %s", innerRange.String()) } - if err := validateRange(outerRange); err != nil { + + outerRange := r.getOuterRange() + if err := outerRange.Validate(); err != nil { return nil, err } - slashed := filepath.ToSlash(r.fsPath) - slashed = strings.TrimPrefix(slashed, "/") - - content, err := fs.ReadFile(srcFS, slashed) + filePath := strings.TrimPrefix(filepath.ToSlash(r.fsPath), "/") + rawLines, err := readLinesFromFile(fsys, filePath, outerRange.GetStartLine(), outerRange.GetEndLine()) if err != nil { - return nil, fmt.Errorf("failed to read file from result filesystem (%#v): %w", srcFS, err) + return nil, err } - hasAnnotation := r.Annotation() != "" - - code := Code{ - Lines: nil, + if outerRange.GetEndLine()-outerRange.GetStartLine() > len(rawLines) { + return nil, fmt.Errorf("invalid outer range: %s", outerRange.String()) } - var rawLines []string - bs := bufio.NewScanner(bytes.NewReader(content)) - for bs.Scan() { - rawLines = append(rawLines, bs.Text()) - } - if bs.Err() != nil { - return nil, fmt.Errorf("failed to scan file : %w", err) - } + highlightedLines := r.getHighlightedLines(outerRange, innerRange, rawLines, settings) - var highlightedLines []string - if settings.includeHighlighted { - highlightedLines = highlight(iacTypes.CreateFSKey(innerRange.GetFS()), innerRange.GetLocalFilename(), content, settings.theme) - if len(highlightedLines) < len(rawLines) { - highlightedLines = rawLines - } + var code Code + + shrink := settings.allowTruncation && outerRange.LineCount() > (innerRange.LineCount()+10) + + if shrink { + code.Lines = r.getTruncatedLines(outerRange, innerRange, rawLines, highlightedLines) } else { - highlightedLines = make([]string, len(rawLines)) + code.Lines = r.getAllLines(outerRange, innerRange, rawLines, highlightedLines) } - if outerRange.GetEndLine()-1 >= len(rawLines) || innerRange.GetStartLine() == 0 { - return nil, fmt.Errorf("invalid line number") + if settings.allowTruncation && len(code.Lines) > settings.maxLines && settings.maxLines > 0 { + code.truncateLines(settings.maxLines) } - shrink := settings.allowTruncation && outerRange.LineCount() > (innerRange.LineCount()+10) + code.markFirstAndLastCauses() - if shrink { + return &code, nil +} - if outerRange.GetStartLine() < innerRange.GetStartLine() { - code.Lines = append( - code.Lines, - Line{ - Content: rawLines[outerRange.GetStartLine()-1], - Highlighted: highlightedLines[outerRange.GetStartLine()-1], - Number: outerRange.GetStartLine(), - }, - ) - if outerRange.GetStartLine()+1 < innerRange.GetStartLine() { - code.Lines = append( - code.Lines, - Line{ - Truncated: true, - Number: outerRange.GetStartLine() + 1, - }, - ) - } - } +func (r *Result) getHighlightedLines(outerRange, innerRange iacTypes.Range, rawLines []string, settings codeSettings) []string { - for lineNo := innerRange.GetStartLine(); lineNo <= innerRange.GetEndLine(); lineNo++ { + highlightedLines := make([]string, len(rawLines)) + if !settings.includeHighlighted { + return highlightedLines + } - if lineNo-1 >= len(rawLines) || lineNo-1 >= len(highlightedLines) { - break - } + content := strings.Join(rawLines, "\n") + fsKey := iacTypes.CreateFSKey(innerRange.GetFS()) + highlightedLines = highlight(fsKey, innerRange.GetLocalFilename(), + outerRange.GetStartLine(), outerRange.GetEndLine(), content, settings.theme) - line := Line{ - Number: lineNo, - Content: strings.TrimSuffix(rawLines[lineNo-1], "\r"), - Highlighted: strings.TrimSuffix(highlightedLines[lineNo-1], "\r"), - IsCause: true, - } + if len(highlightedLines) < len(rawLines) { + return rawLines + } - if hasAnnotation && lineNo == innerRange.GetStartLine() { - line.Annotation = r.Annotation() - } + return highlightedLines +} - code.Lines = append(code.Lines, line) - } +func (r *Result) getOuterRange() iacTypes.Range { + outer := r.Metadata().Range() + for parent := r.Metadata().Parent(); parent != nil && + parent.Range().GetFilename() == outer.GetFilename() && + parent.Range().GetStartLine() > 0; parent = parent.Parent() { + outer = parent.Range() + } + return outer +} - if outerRange.GetEndLine() > innerRange.GetEndLine() { - if outerRange.GetEndLine() > innerRange.GetEndLine()+1 { - code.Lines = append( - code.Lines, - Line{ - Truncated: true, - Number: outerRange.GetEndLine() - 1, - }, - ) - } - code.Lines = append( - code.Lines, - Line{ - Content: rawLines[outerRange.GetEndLine()-1], - Highlighted: highlightedLines[outerRange.GetEndLine()-1], - Number: outerRange.GetEndLine(), - }, - ) +func (r *Result) getTruncatedLines(outerRange, innerRange iacTypes.Range, rawLines, highlightedLines []string) []Line { + var lines []Line + + if outerRange.GetStartLine() < innerRange.GetStartLine() { + lines = append(lines, Line{ + Content: rawLines[0], + Highlighted: highlightedLines[0], + Number: outerRange.GetStartLine(), + }) + if outerRange.GetStartLine()+1 < innerRange.GetStartLine() { + lines = append(lines, Line{ + Truncated: true, + Number: outerRange.GetStartLine() + 1, + }) + } + } + for lineNo := innerRange.GetStartLine() - outerRange.GetStartLine(); lineNo <= innerRange.GetEndLine()-outerRange.GetStartLine(); lineNo++ { + if lineNo >= len(rawLines) || lineNo >= len(highlightedLines) { + break } - } else { - for lineNo := outerRange.GetStartLine(); lineNo <= outerRange.GetEndLine(); lineNo++ { + line := Line{ + Number: lineNo + outerRange.GetStartLine(), + Content: strings.TrimSuffix(rawLines[lineNo], "\r"), + Highlighted: strings.TrimSuffix(highlightedLines[lineNo], "\r"), + IsCause: true, + } - line := Line{ - Number: lineNo, - Content: strings.TrimSuffix(rawLines[lineNo-1], "\r"), - Highlighted: strings.TrimSuffix(highlightedLines[lineNo-1], "\r"), - IsCause: lineNo >= innerRange.GetStartLine() && lineNo <= innerRange.GetEndLine(), - } + if r.Annotation() != "" && lineNo == innerRange.GetStartLine()-outerRange.GetStartLine()-1 { + line.Annotation = r.Annotation() + } - if hasAnnotation && lineNo == innerRange.GetStartLine() { - line.Annotation = r.Annotation() - } + lines = append(lines, line) + } - code.Lines = append(code.Lines, line) + if outerRange.GetEndLine() > innerRange.GetEndLine() { + if outerRange.GetEndLine() > innerRange.GetEndLine()+1 { + lines = append(lines, Line{ + Truncated: true, + Number: outerRange.GetEndLine() - 1, + }) } + lines = append(lines, Line{ + Content: rawLines[outerRange.GetEndLine()-outerRange.GetStartLine()], + Highlighted: highlightedLines[outerRange.GetEndLine()-outerRange.GetStartLine()], + Number: outerRange.GetEndLine(), + }) } - if settings.allowTruncation && len(code.Lines) > settings.maxLines && settings.maxLines > 0 { - previouslyTruncated := settings.maxLines-1 > 0 && code.Lines[settings.maxLines-2].Truncated - if settings.maxLines-1 > 0 && code.Lines[settings.maxLines-1].LastCause { - code.Lines[settings.maxLines-2].LastCause = true - } - code.Lines[settings.maxLines-1] = Line{ - Truncated: true, - Number: code.Lines[settings.maxLines-1].Number, + return lines +} + +func (r *Result) getAllLines(outerRange, innerRange iacTypes.Range, rawLines, highlightedLines []string) []Line { + lines := make([]Line, 0, outerRange.GetEndLine()-outerRange.GetStartLine()+1) + + for lineNo := 0; lineNo <= outerRange.GetEndLine()-outerRange.GetStartLine(); lineNo++ { + line := Line{ + Number: lineNo + outerRange.GetStartLine(), + Content: strings.TrimSuffix(rawLines[lineNo], "\r"), + Highlighted: strings.TrimSuffix(highlightedLines[lineNo], "\r"), + IsCause: lineNo >= innerRange.GetStartLine()-outerRange.GetStartLine() && + lineNo <= innerRange.GetEndLine()-outerRange.GetStartLine(), } - if previouslyTruncated { - code.Lines = code.Lines[:settings.maxLines-1] - } else { - code.Lines = code.Lines[:settings.maxLines] + + if r.Annotation() != "" && lineNo == innerRange.GetStartLine()-outerRange.GetStartLine()-1 { + line.Annotation = r.Annotation() } + + lines = append(lines, line) } - var first, last bool - for i, line := range code.Lines { - if line.IsCause && !first { - code.Lines[i].FirstCause = true - first = true - continue - } - if first && !line.IsCause && i > 0 { - code.Lines[i-1].LastCause = true - last = true - break + return lines +} + +func readLinesFromFile(fsys fs.FS, path string, from, to int) ([]string, error) { + slashedPath := strings.TrimPrefix(filepath.ToSlash(path), "/") + + file, err := fsys.Open(slashedPath) + if err != nil { + return nil, fmt.Errorf("failed to read file from result filesystem: %w", err) + } + defer file.Close() + + scanner := bufio.NewScanner(file) + rawLines := make([]string, 0, to-from+1) + + for lineNum := 0; scanner.Scan() && lineNum < to; lineNum++ { + if lineNum >= from-1 { + rawLines = append(rawLines, scanner.Text()) } } - if !last && len(code.Lines) > 0 { - code.Lines[len(code.Lines)-1].LastCause = true + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("failed to scan file: %w", err) } - return &code, nil + return rawLines, nil } diff --git a/pkg/iac/scan/code_test.go b/pkg/iac/scan/code_test.go index c3ffe3725ef1..559f1d9dfe69 100644 --- a/pkg/iac/scan/code_test.go +++ b/pkg/iac/scan/code_test.go @@ -1,11 +1,10 @@ package scan import ( - "os" "strings" "testing" + "testing/fstest" - "github.com/liamg/memoryfs" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -13,19 +12,18 @@ import ( ) func TestResult_GetCode(t *testing.T) { - + const line = "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind." tests := []struct { - name string - source string - filename string - start int - end int - outerStart int - outerEnd int - expected []Line - options []CodeOption - wantErr bool - annotation string + name string + source string + filename string + startInnerLine int + endInnerLine int + startOuterLine int + endOuterLine int + expected []Line + options []CodeOption + wantErr bool }{ { name: "basic w/ defaults", @@ -33,9 +31,9 @@ func TestResult_GetCode(t *testing.T) { 2 3 4`, - filename: "test.txt", - start: 2, - end: 3, + filename: "test.txt", + startInnerLine: 2, + endInnerLine: 3, expected: []Line{ { Number: 2, @@ -60,12 +58,12 @@ func TestResult_GetCode(t *testing.T) { source: `resource "aws_s3_bucket" "something" { bucket = "something" }`, - filename: "main.tf", - start: 2, - end: 2, - outerStart: 1, - outerEnd: 3, - options: []CodeOption{OptionCodeWithHighlighted(false)}, + filename: "main.tf", + startInnerLine: 2, + endInnerLine: 2, + startOuterLine: 1, + endOuterLine: 3, + options: []CodeOption{OptionCodeWithHighlighted(false)}, expected: []Line{ { Number: 1, @@ -90,10 +88,10 @@ func TestResult_GetCode(t *testing.T) { 2 3 4`, - filename: "", - start: 2, - end: 3, - wantErr: true, + filename: "", + startInnerLine: 2, + endInnerLine: 3, + wantErr: true, }, { name: "no line numbers", @@ -101,10 +99,10 @@ func TestResult_GetCode(t *testing.T) { 2 3 4`, - filename: "test.txt", - start: 0, - end: 0, - wantErr: true, + filename: "test.txt", + startInnerLine: 0, + endInnerLine: 0, + wantErr: true, }, { name: "negative line numbers", @@ -112,10 +110,10 @@ func TestResult_GetCode(t *testing.T) { 2 3 4`, - filename: "test.txt", - start: -2, - end: -1, - wantErr: true, + filename: "test.txt", + startInnerLine: -2, + endInnerLine: -1, + wantErr: true, }, { name: "invalid line numbers", @@ -123,17 +121,17 @@ func TestResult_GetCode(t *testing.T) { 2 3 4`, - filename: "test.txt", - start: 5, - end: 6, - wantErr: true, + filename: "test.txt", + startInnerLine: 5, + endInnerLine: 6, + wantErr: true, }, { - name: "syntax highlighting", - source: `FROM ubuntu`, - filename: "Dockerfile", - start: 1, - end: 1, + name: "syntax highlighting", + source: `FROM ubuntu`, + filename: "Dockerfile", + startInnerLine: 1, + endInnerLine: 1, expected: []Line{ { Number: 1, @@ -146,81 +144,81 @@ func TestResult_GetCode(t *testing.T) { }, }, { - name: "truncation", - source: strings.Repeat("If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.\n", 100), - filename: "longfile.txt", - start: 1, - end: 100, + name: "truncation", + source: strings.Repeat(line+"\n", 100), + filename: "longfile.txt", + startInnerLine: 1, + endInnerLine: 100, expected: []Line{ { Number: 1, - Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Content: line, IsCause: true, - Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Highlighted: line, FirstCause: true, LastCause: false, }, { Number: 2, - Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Content: line, IsCause: true, - Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Highlighted: line, FirstCause: false, LastCause: false, }, { Number: 3, - Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Content: line, IsCause: true, - Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Highlighted: line, FirstCause: false, LastCause: false, }, { Number: 4, - Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Content: line, IsCause: true, - Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Highlighted: line, FirstCause: false, LastCause: false, }, { Number: 5, - Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Content: line, IsCause: true, - Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Highlighted: line, FirstCause: false, LastCause: false, }, { Number: 6, - Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Content: line, IsCause: true, - Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Highlighted: line, FirstCause: false, LastCause: false, }, { Number: 7, - Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Content: line, IsCause: true, - Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Highlighted: line, FirstCause: false, LastCause: false, }, { Number: 8, - Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Content: line, IsCause: true, - Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Highlighted: line, FirstCause: false, LastCause: false, }, { Number: 9, - Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Content: line, IsCause: true, - Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Highlighted: line, FirstCause: false, LastCause: true, }, @@ -230,26 +228,97 @@ func TestResult_GetCode(t *testing.T) { }, }, }, + { + name: "invalid inner range", + source: `Test`, + filename: "test.txt", + startInnerLine: 0, + endInnerLine: 0, + wantErr: true, + }, + { + name: "invalid outer range", + source: `Test`, + filename: "test.txt", + startInnerLine: 10, + endInnerLine: 12, + startOuterLine: 5, + endOuterLine: 3, + wantErr: true, + }, + { + name: "truncate with outer range", + source: strings.Repeat(line+"\n", 100), + filename: "longfile.txt", + startOuterLine: 1, + endOuterLine: 100, + startInnerLine: 10, + endInnerLine: 12, + options: []CodeOption{OptionCodeWithTruncation(true)}, + expected: []Line{ + { + Number: 1, + Content: line, + Highlighted: line, + }, + { + Number: 2, + Truncated: true, + }, + { + Number: 10, + Content: line, + IsCause: true, + FirstCause: true, + Highlighted: line, + }, + { + Number: 11, + Content: line, + IsCause: true, + Highlighted: line, + }, + { + Number: 12, + Content: line, + IsCause: true, + LastCause: true, + Highlighted: line, + }, + { + Number: 99, + Truncated: true, + }, + { + Number: 100, + Content: line, + Highlighted: line, + }, + }, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - system := memoryfs.New() - require.NoError(t, system.WriteFile(test.filename, []byte(test.source), os.ModePerm)) + fsys := fstest.MapFS{ + test.filename: &fstest.MapFile{ + Data: []byte(test.source), + }, + } + meta := iacTypes.NewMetadata( - iacTypes.NewRange(test.filename, test.start, test.end, "", system), + iacTypes.NewRange(test.filename, test.startInnerLine, test.endInnerLine, "", fsys), "", ) - if test.outerStart > 0 { + if test.startOuterLine > 0 { meta = meta.WithParent(iacTypes.NewMetadata( - iacTypes.NewRange(test.filename, test.outerStart, test.outerEnd, "", system), + iacTypes.NewRange(test.filename, test.startOuterLine, test.endOuterLine, "", fsys), "", )) } result := &Result{ - annotation: test.annotation, - metadata: meta, - fsPath: test.filename, + metadata: meta, + fsPath: test.filename, } code, err := result.GetCode(test.options...) if test.wantErr { @@ -262,3 +331,72 @@ func TestResult_GetCode(t *testing.T) { } } + +func TestCode_IsCauseMultiline(t *testing.T) { + + tests := []struct { + name string + code Code + expected bool + }{ + { + name: "no cause", + code: Code{ + Lines: []Line{ + { + Number: 1, + Content: "Test", + Highlighted: "Test", + }, + }, + }, + expected: false, + }, + { + name: "one cause", + code: Code{ + Lines: []Line{ + { + Number: 1, + Content: "Test", + IsCause: true, + Highlighted: "Test", + }, + }, + }, + expected: false, + }, + { + name: "multiple causes", + code: Code{ + Lines: []Line{ + { + Number: 1, + Content: "Test", + IsCause: true, + Highlighted: "Test", + }, + { + Number: 2, + Content: "Test", + IsCause: true, + Highlighted: "Test", + }, + { + Number: 3, + Content: "Test", + IsCause: true, + Highlighted: "Test", + }, + }, + }, + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, tt.code.IsCauseMultiline()) + }) + } +} diff --git a/pkg/iac/scan/highlighting.go b/pkg/iac/scan/highlighting.go index 12bd3fe086cf..df260130d599 100644 --- a/pkg/iac/scan/highlighting.go +++ b/pkg/iac/scan/highlighting.go @@ -34,9 +34,9 @@ var globalCache = &cache{ data: make(map[string][]string), } -func highlight(fsKey, filename string, input []byte, theme string) []string { +func highlight(fsKey, filename string, startLine, endLine int, input, theme string) []string { - key := fmt.Sprintf("%s|%s", fsKey, filename) + key := fmt.Sprintf("%s|%s|%d-%d", fsKey, filename, startLine, endLine) if lines, ok := globalCache.Get(key); ok { return lines } @@ -56,15 +56,13 @@ func highlight(fsKey, filename string, input []byte, theme string) []string { formatter = formatters.Fallback } - // replace windows line endings - input = bytes.ReplaceAll(input, []byte{0x0d}, []byte{}) - iterator, err := lexer.Tokenise(nil, string(input)) + iterator, err := lexer.Tokenise(nil, input) if err != nil { return nil } - buffer := bytes.NewBuffer([]byte{}) - if err := formatter.Format(buffer, style, iterator); err != nil { + var buffer bytes.Buffer + if err := formatter.Format(&buffer, style, iterator); err != nil { return nil } diff --git a/pkg/iac/scanners/azure/arm/parser/parser.go b/pkg/iac/scanners/azure/arm/parser/parser.go index e5e171914b42..53f9eb21431b 100644 --- a/pkg/iac/scanners/azure/arm/parser/parser.go +++ b/pkg/iac/scanners/azure/arm/parser/parser.go @@ -5,44 +5,29 @@ import ( "fmt" "io" "io/fs" - "path/filepath" - "strings" - "github.com/aquasecurity/trivy/pkg/iac/debug" - azure2 "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" + "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" "github.com/aquasecurity/trivy/pkg/iac/scanners/azure/arm/parser/armjson" "github.com/aquasecurity/trivy/pkg/iac/scanners/azure/resolver" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" ) type Parser struct { - targetFS fs.FS - skipRequired bool - debug debug.Logger + targetFS fs.FS + logger *log.Logger } -func (p *Parser) SetDebugWriter(writer io.Writer) { - p.debug = debug.New(writer, "azure", "arm") -} - -func (p *Parser) SetSkipRequiredCheck(b bool) { - p.skipRequired = b -} - -func New(targetFS fs.FS, opts ...options.ParserOption) *Parser { - p := &Parser{ +func New(targetFS fs.FS) *Parser { + return &Parser{ targetFS: targetFS, + logger: log.WithPrefix("arm parser"), } - for _, opt := range opts { - opt(p) - } - return p } -func (p *Parser) ParseFS(ctx context.Context, dir string) ([]azure2.Deployment, error) { +func (p *Parser) ParseFS(ctx context.Context, dir string) ([]azure.Deployment, error) { - var deployments []azure2.Deployment + var deployments []azure.Deployment if err := fs.WalkDir(p.targetFS, dir, func(path string, entry fs.DirEntry, err error) error { if err != nil { @@ -56,14 +41,13 @@ func (p *Parser) ParseFS(ctx context.Context, dir string) ([]azure2.Deployment, if entry.IsDir() { return nil } - if !p.Required(path) { - return nil - } + f, err := p.targetFS.Open(path) if err != nil { return err } defer f.Close() + deployment, err := p.parseFile(f, path) if err != nil { return err @@ -77,35 +61,7 @@ func (p *Parser) ParseFS(ctx context.Context, dir string) ([]azure2.Deployment, return deployments, nil } -func (p *Parser) Required(path string) bool { - if p.skipRequired { - return true - } - if !strings.HasSuffix(path, ".json") { - return false - } - data, err := fs.ReadFile(p.targetFS, path) - if err != nil { - return false - } - var template Template - root := types.NewMetadata( - types.NewRange(filepath.Base(path), 0, 0, "", p.targetFS), - "", - ) - if err := armjson.Unmarshal(data, &template, &root); err != nil { - p.debug.Log("Error scanning %s: %s", path, err) - return false - } - - if template.Schema.Kind != azure2.KindString { - return false - } - - return strings.HasPrefix(template.Schema.AsString(), "https://schema.management.azure.com") -} - -func (p *Parser) parseFile(r io.Reader, filename string) (*azure2.Deployment, error) { +func (p *Parser) parseFile(r io.Reader, filename string) (*azure.Deployment, error) { var template Template data, err := io.ReadAll(r) if err != nil { @@ -122,11 +78,11 @@ func (p *Parser) parseFile(r io.Reader, filename string) (*azure2.Deployment, er return p.convertTemplate(template), nil } -func (p *Parser) convertTemplate(template Template) *azure2.Deployment { +func (p *Parser) convertTemplate(template Template) *azure.Deployment { - deployment := azure2.Deployment{ + deployment := azure.Deployment{ Metadata: template.Metadata, - TargetScope: azure2.ScopeResourceGroup, // TODO: override from --resource-group? + TargetScope: azure.ScopeResourceGroup, // TODO: override from --resource-group? Parameters: nil, Variables: nil, Resources: nil, @@ -139,8 +95,8 @@ func (p *Parser) convertTemplate(template Template) *azure2.Deployment { // TODO: the references passed here should probably not be the name - maybe params.NAME.DefaultValue? for name, param := range template.Parameters { - deployment.Parameters = append(deployment.Parameters, azure2.Parameter{ - Variable: azure2.Variable{ + deployment.Parameters = append(deployment.Parameters, azure.Parameter{ + Variable: azure.Variable{ Name: name, Value: param.DefaultValue, }, @@ -150,14 +106,14 @@ func (p *Parser) convertTemplate(template Template) *azure2.Deployment { } for name, variable := range template.Variables { - deployment.Variables = append(deployment.Variables, azure2.Variable{ + deployment.Variables = append(deployment.Variables, azure.Variable{ Name: name, Value: variable, }) } for name, output := range template.Outputs { - deployment.Outputs = append(deployment.Outputs, azure2.Output{ + deployment.Outputs = append(deployment.Outputs, azure.Output{ Name: name, Value: output, }) @@ -170,15 +126,15 @@ func (p *Parser) convertTemplate(template Template) *azure2.Deployment { return &deployment } -func (p *Parser) convertResource(input Resource) azure2.Resource { +func (p *Parser) convertResource(input Resource) azure.Resource { - var children []azure2.Resource + var children []azure.Resource for _, child := range input.Resources { children = append(children, p.convertResource(child)) } - resource := azure2.Resource{ + resource := azure.Resource{ Metadata: input.Metadata, APIVersion: input.APIVersion, Type: input.Type, diff --git a/pkg/iac/scanners/azure/arm/parser/parser_test.go b/pkg/iac/scanners/azure/arm/parser/parser_test.go index 3273c0dd6596..ff1444a60655 100644 --- a/pkg/iac/scanners/azure/arm/parser/parser_test.go +++ b/pkg/iac/scanners/azure/arm/parser/parser_test.go @@ -3,7 +3,6 @@ package parser import ( "context" "io/fs" - "os" "testing" "github.com/liamg/memoryfs" @@ -12,7 +11,6 @@ import ( azure2 "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" "github.com/aquasecurity/trivy/pkg/iac/scanners/azure/resolver" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/types" ) @@ -36,11 +34,6 @@ func TestParser_Parse(t *testing.T) { want func() azure2.Deployment wantDeployment bool }{ - { - name: "invalid code", - input: `blah`, - wantDeployment: false, - }, { name: "basic param", input: `{ @@ -210,7 +203,7 @@ func TestParser_Parse(t *testing.T) { require.NoError(t, targetFS.WriteFile(filename, []byte(tt.input), 0644)) - p := New(targetFS, options.ParserWithDebug(os.Stderr)) + p := New(targetFS) got, err := p.ParseFS(context.Background(), ".") require.NoError(t, err) @@ -298,7 +291,7 @@ func Test_NestedResourceParsing(t *testing.T) { require.NoError(t, targetFS.WriteFile("nested.json", []byte(input), 0644)) - p := New(targetFS, options.ParserWithDebug(os.Stderr)) + p := New(targetFS) got, err := p.ParseFS(context.Background(), ".") require.NoError(t, err) require.Len(t, got, 1) diff --git a/pkg/iac/scanners/azure/arm/scanner.go b/pkg/iac/scanners/azure/arm/scanner.go index b4bcfc539486..410ccd6d18df 100644 --- a/pkg/iac/scanners/azure/arm/scanner.go +++ b/pkg/iac/scanners/azure/arm/scanner.go @@ -3,12 +3,10 @@ package arm import ( "context" "fmt" - "io" "io/fs" "sync" "github.com/aquasecurity/trivy/pkg/iac/adapters/arm" - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/rules" @@ -19,31 +17,24 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/state" "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" ) var _ scanners.FSScanner = (*Scanner)(nil) var _ options.ConfigurableScanner = (*Scanner)(nil) -type Scanner struct { // nolint: gocritic - scannerOptions []options.ScannerOption - parserOptions []options.ParserOption - debug debug.Logger - frameworks []framework.Framework - skipRequired bool - regoOnly bool - loadEmbeddedPolicies bool - loadEmbeddedLibraries bool - policyDirs []string - policyReaders []io.Reader - regoScanner *rego.Scanner - spec string - sync.Mutex +type Scanner struct { + mu sync.Mutex + scannerOptions []options.ScannerOption + logger *log.Logger + frameworks []framework.Framework + regoOnly bool + regoScanner *rego.Scanner + includeDeprecatedChecks bool } -func (s *Scanner) SetIncludeDeprecatedChecks(b bool) {} - -func (s *Scanner) SetSpec(spec string) { - s.spec = spec +func (s *Scanner) SetIncludeDeprecatedChecks(b bool) { + s.includeDeprecatedChecks = b } func (s *Scanner) SetRegoOnly(regoOnly bool) { @@ -53,6 +44,7 @@ func (s *Scanner) SetRegoOnly(regoOnly bool) { func New(opts ...options.ScannerOption) *Scanner { scanner := &Scanner{ scannerOptions: opts, + logger: log.WithPrefix("azure-arm"), } for _, opt := range opts { opt(scanner) @@ -64,56 +56,18 @@ func (s *Scanner) Name() string { return "Azure ARM" } -func (s *Scanner) SetDebugWriter(writer io.Writer) { - s.debug = debug.New(writer, "azure", "arm") - s.parserOptions = append(s.parserOptions, options.ParserWithDebug(writer)) -} - -func (s *Scanner) SetPolicyDirs(dirs ...string) { - s.policyDirs = dirs -} - -func (s *Scanner) SetSkipRequiredCheck(skipRequired bool) { - s.skipRequired = skipRequired -} -func (s *Scanner) SetPolicyReaders(readers []io.Reader) { - s.policyReaders = readers -} - -func (s *Scanner) SetPolicyFilesystem(_ fs.FS) { - // handled by rego when option is passed on -} -func (s *Scanner) SetDataFilesystem(_ fs.FS) { - // handled by rego when option is passed on -} - -func (s *Scanner) SetUseEmbeddedPolicies(b bool) { - s.loadEmbeddedPolicies = b -} - -func (s *Scanner) SetUseEmbeddedLibraries(b bool) { - s.loadEmbeddedLibraries = b -} - func (s *Scanner) SetFrameworks(frameworks []framework.Framework) { s.frameworks = frameworks } -func (s *Scanner) SetTraceWriter(io.Writer) {} -func (s *Scanner) SetPerResultTracingEnabled(bool) {} -func (s *Scanner) SetDataDirs(...string) {} -func (s *Scanner) SetPolicyNamespaces(...string) {} -func (s *Scanner) SetRegoErrorLimit(_ int) {} - func (s *Scanner) initRegoScanner(srcFS fs.FS) error { - s.Lock() - defer s.Unlock() + s.mu.Lock() + defer s.mu.Unlock() if s.regoScanner != nil { return nil } regoScanner := rego.NewScanner(types.SourceCloud, s.scannerOptions...) - regoScanner.SetParentDebugLogger(s.debug) - if err := regoScanner.LoadPolicies(s.loadEmbeddedLibraries, s.loadEmbeddedPolicies, srcFS, s.policyDirs, s.policyReaders); err != nil { + if err := regoScanner.LoadPolicies(srcFS); err != nil { return err } s.regoScanner = regoScanner @@ -121,7 +75,7 @@ func (s *Scanner) initRegoScanner(srcFS fs.FS) error { } func (s *Scanner) ScanFS(ctx context.Context, fsys fs.FS, dir string) (scan.Results, error) { - p := parser.New(fsys, s.parserOptions...) + p := parser.New(fsys) deployments, err := p.ParseFS(ctx, dir) if err != nil { return nil, err @@ -159,11 +113,12 @@ func (s *Scanner) scanDeployment(ctx context.Context, deployment azure.Deploymen return nil, ctx.Err() default: } - if rule.GetRule().RegoPackage != "" { - continue + + if !s.includeDeprecatedChecks && rule.Deprecated { + continue // skip deprecated checks } + ruleResults := rule.Evaluate(deploymentState) - s.debug.Log("Found %d results for %s", len(ruleResults), rule.GetRule().AVDID) if len(ruleResults) > 0 { results = append(results, ruleResults...) } diff --git a/pkg/iac/scanners/cloudformation/cftypes/types.go b/pkg/iac/scanners/cloudformation/cftypes/types.go index 21949ae099cf..6a533aae179c 100644 --- a/pkg/iac/scanners/cloudformation/cftypes/types.go +++ b/pkg/iac/scanners/cloudformation/cftypes/types.go @@ -15,6 +15,9 @@ const ( ) func TypeFromGoValue(value any) CfType { + if value == nil { + return Unknown + } switch reflect.TypeOf(value).Kind() { case reflect.String: return String diff --git a/pkg/iac/scanners/cloudformation/parser/file_context.go b/pkg/iac/scanners/cloudformation/parser/file_context.go index 78a267cabb04..949add1ca7c4 100644 --- a/pkg/iac/scanners/cloudformation/parser/file_context.go +++ b/pkg/iac/scanners/cloudformation/parser/file_context.go @@ -54,10 +54,20 @@ func (t *FileContext) Metadata() iacTypes.Metadata { return iacTypes.NewMetadata(rng, NewCFReference("Template", rng).String()) } -func (t *FileContext) OverrideParameters(params map[string]any) { +func (t *FileContext) overrideParameters(params map[string]any) { for key := range t.Parameters { if val, ok := params[key]; ok { t.Parameters[key].UpdateDefault(val) } } } + +func (t *FileContext) missingParameterValues() []string { + var missing []string + for key := range t.Parameters { + if t.Parameters[key].inner.Default == nil { + missing = append(missing, key) + } + } + return missing +} diff --git a/pkg/iac/scanners/cloudformation/parser/file_context_test.go b/pkg/iac/scanners/cloudformation/parser/file_context_test.go index bbf5db4ddc39..80bdfa72eb05 100644 --- a/pkg/iac/scanners/cloudformation/parser/file_context_test.go +++ b/pkg/iac/scanners/cloudformation/parser/file_context_test.go @@ -1,6 +1,7 @@ package parser import ( + "slices" "testing" "github.com/stretchr/testify/assert" @@ -54,8 +55,51 @@ func TestFileContext_OverrideParameters(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tt.ctx.OverrideParameters(tt.arg) + tt.ctx.overrideParameters(tt.arg) assert.Equal(t, tt.expected, tt.ctx.Parameters) }) } } + +func TestFileContext_MissingParameterValues(t *testing.T) { + + tests := []struct { + name string + ctx FileContext + expected []string + }{ + { + name: "happy", + ctx: FileContext{ + Parameters: map[string]*Parameter{ + "BucketName": { + inner: parameterInner{ + Type: "String", + Default: "test", + }, + }, + "QueueName": { + inner: parameterInner{ + Type: "String", + }, + }, + "KMSKey": { + inner: parameterInner{ + Type: "String", + Default: nil, + }, + }, + }, + }, + expected: []string{"KMSKey", "QueueName"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.ctx.missingParameterValues() + slices.Sort(got) + assert.Equal(t, tt.expected, got) + }) + } +} diff --git a/pkg/iac/scanners/cloudformation/parser/parser.go b/pkg/iac/scanners/cloudformation/parser/parser.go index 5aa760e19882..40d0035f6cd2 100644 --- a/pkg/iac/scanners/cloudformation/parser/parser.go +++ b/pkg/iac/scanners/cloudformation/parser/parser.go @@ -1,7 +1,6 @@ package parser import ( - "bytes" "context" "encoding/json" "errors" @@ -14,57 +13,42 @@ import ( "github.com/liamg/jfather" "gopkg.in/yaml.v3" - "github.com/aquasecurity/trivy/pkg/iac/debug" - "github.com/aquasecurity/trivy/pkg/iac/detection" "github.com/aquasecurity/trivy/pkg/iac/ignore" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/log" ) -var _ options.ConfigurableParser = (*Parser)(nil) - type Parser struct { - debug debug.Logger - skipRequired bool + logger *log.Logger parameterFiles []string parameters map[string]any overridedParameters Parameters configsFS fs.FS } -func WithParameters(params map[string]any) options.ParserOption { - return func(cp options.ConfigurableParser) { - if p, ok := cp.(*Parser); ok { - p.parameters = params - } - } -} +type Option func(*Parser) -func WithParameterFiles(files ...string) options.ParserOption { - return func(cp options.ConfigurableParser) { - if p, ok := cp.(*Parser); ok { - p.parameterFiles = files - } +func WithParameters(params map[string]any) Option { + return func(p *Parser) { + p.parameters = params } } -func WithConfigsFS(fsys fs.FS) options.ParserOption { - return func(cp options.ConfigurableParser) { - if p, ok := cp.(*Parser); ok { - p.configsFS = fsys - } +func WithParameterFiles(files ...string) Option { + return func(p *Parser) { + p.parameterFiles = files } } -func (p *Parser) SetDebugWriter(writer io.Writer) { - p.debug = debug.New(writer, "cloudformation", "parser") -} - -func (p *Parser) SetSkipRequiredCheck(b bool) { - p.skipRequired = b +func WithConfigsFS(fsys fs.FS) Option { + return func(p *Parser) { + p.configsFS = fsys + } } -func New(opts ...options.ParserOption) *Parser { - p := &Parser{} +func New(opts ...Option) *Parser { + p := &Parser{ + logger: log.WithPrefix("cloudformation parser"), + } for _, option := range opts { option(p) } @@ -86,14 +70,9 @@ func (p *Parser) ParseFS(ctx context.Context, fsys fs.FS, dir string) (FileConte return nil } - if !p.Required(fsys, path) { - p.debug.Log("not a CloudFormation file, skipping %s", path) - return nil - } - c, err := p.ParseFile(ctx, fsys, path) if err != nil { - p.debug.Log("Error parsing file '%s': %s", path, err) + p.logger.Error("Error parsing file", log.FilePath(path), log.Err(err)) return nil } contexts = append(contexts, c) @@ -104,23 +83,6 @@ func (p *Parser) ParseFS(ctx context.Context, fsys fs.FS, dir string) (FileConte return contexts, nil } -func (p *Parser) Required(fsys fs.FS, path string) bool { - if p.skipRequired { - return true - } - - f, err := fsys.Open(filepath.ToSlash(path)) - if err != nil { - return false - } - defer func() { _ = f.Close() }() - if data, err := io.ReadAll(f); err == nil { - return detection.IsType(path, bytes.NewReader(data), detection.FileTypeCloudFormation) - } - return false - -} - func (p *Parser) ParseFile(ctx context.Context, fsys fs.FS, path string) (fctx *FileContext, err error) { defer func() { if e := recover(); e != nil { @@ -178,13 +140,17 @@ func (p *Parser) ParseFile(ctx context.Context, fsys fs.FS, path string) (fctx * } } - fctx.OverrideParameters(p.overridedParameters) + fctx.overrideParameters(p.overridedParameters) + + if params := fctx.missingParameterValues(); len(params) > 0 { + p.logger.Warn("Missing parameter values", log.FilePath(path), log.String("parameters", strings.Join(params, ", "))) + } fctx.lines = lines fctx.SourceFormat = sourceFmt fctx.filepath = path - p.debug.Log("Context loaded from source %s", path) + p.logger.Debug("Context loaded from source", log.FilePath(path)) // the context must be set to conditions before resources for _, c := range fctx.Conditions { @@ -192,7 +158,7 @@ func (p *Parser) ParseFile(ctx context.Context, fsys fs.FS, path string) (fctx * } for name, r := range fctx.Resources { - r.ConfigureResource(name, fsys, path, fctx) + r.configureResource(name, fsys, path, fctx) } return fctx, nil diff --git a/pkg/iac/scanners/cloudformation/parser/parser_test.go b/pkg/iac/scanners/cloudformation/parser/parser_test.go index 0d37440e2e1a..aa058c4df855 100644 --- a/pkg/iac/scanners/cloudformation/parser/parser_test.go +++ b/pkg/iac/scanners/cloudformation/parser/parser_test.go @@ -419,3 +419,24 @@ func TestJsonWithNumbers(t *testing.T) { assert.Equal(t, 1, res[0].GetProperty("SomeIntProp").AsIntValue().Value()) assert.Equal(t, 0, res[0].GetProperty("SomeFloatProp").AsIntValue().Value()) } + +func TestParameterIsNull(t *testing.T) { + src := `--- +AWSTemplateFormatVersion: 2010-09-09 + +Parameters: + Email: + Type: String + +Conditions: + SubscribeEmail: !Not [!Equals [ !Ref Email, ""]] +` + + fsys := testutil.CreateFS(t, map[string]string{ + "main.yaml": src, + }) + + files, err := New().ParseFS(context.TODO(), fsys, ".") + require.NoError(t, err) + require.Len(t, files, 1) +} diff --git a/pkg/iac/scanners/cloudformation/parser/property_conversion.go b/pkg/iac/scanners/cloudformation/parser/property_conversion.go index d286fa4dd797..35847bb35c5b 100644 --- a/pkg/iac/scanners/cloudformation/parser/property_conversion.go +++ b/pkg/iac/scanners/cloudformation/parser/property_conversion.go @@ -43,6 +43,8 @@ func (p *Property) isConvertableToBool() bool { case cftypes.Int: return p.EqualTo(1) || p.EqualTo(0) + case cftypes.Bool: + return true } return false } @@ -53,7 +55,7 @@ func (p *Property) isConvertableToInt() bool { if _, err := strconv.Atoi(p.AsString()); err == nil { return true } - case cftypes.Bool: + case cftypes.Bool, cftypes.Int: return true } return false @@ -61,6 +63,10 @@ func (p *Property) isConvertableToInt() bool { func (p *Property) ConvertTo(conversionType cftypes.CfType) *Property { + if p.Type() == conversionType { + return p + } + if !p.IsConvertableTo(conversionType) { _, _ = fmt.Fprintf(os.Stderr, "property of type %s cannot be converted to %s\n", p.Type(), conversionType) return p diff --git a/pkg/iac/scanners/cloudformation/parser/resource.go b/pkg/iac/scanners/cloudformation/parser/resource.go index bd1351f234df..ed4b2834d573 100644 --- a/pkg/iac/scanners/cloudformation/parser/resource.go +++ b/pkg/iac/scanners/cloudformation/parser/resource.go @@ -23,7 +23,7 @@ type ResourceInner struct { Properties map[string]*Property `json:"Properties" yaml:"Properties"` } -func (r *Resource) ConfigureResource(id string, target fs.FS, filepath string, ctx *FileContext) { +func (r *Resource) configureResource(id string, target fs.FS, filepath string, ctx *FileContext) { r.setId(id) r.setFile(target, filepath) r.setContext(ctx) diff --git a/pkg/iac/scanners/cloudformation/scanner.go b/pkg/iac/scanners/cloudformation/scanner.go index 1bbbe39f2117..4497a807506c 100644 --- a/pkg/iac/scanners/cloudformation/scanner.go +++ b/pkg/iac/scanners/cloudformation/scanner.go @@ -3,13 +3,11 @@ package cloudformation import ( "context" "fmt" - "io" "io/fs" "sort" "sync" adapter "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation" - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/rules" @@ -18,12 +16,13 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" ) func WithParameters(params map[string]any) options.ScannerOption { return func(cs options.ConfigurableScanner) { if s, ok := cs.(*Scanner); ok { - s.addParserOptions(parser.WithParameters(params)) + s.addParserOption(parser.WithParameters(params)) } } } @@ -31,7 +30,7 @@ func WithParameters(params map[string]any) options.ScannerOption { func WithParameterFiles(files ...string) options.ScannerOption { return func(cs options.ConfigurableScanner) { if s, ok := cs.(*Scanner); ok { - s.addParserOptions(parser.WithParameterFiles(files...)) + s.addParserOption(parser.WithParameterFiles(files...)) } } } @@ -39,7 +38,7 @@ func WithParameterFiles(files ...string) options.ScannerOption { func WithConfigsFS(fsys fs.FS) options.ScannerOption { return func(cs options.ConfigurableScanner) { if s, ok := cs.(*Scanner); ok { - s.addParserOptions(parser.WithConfigsFS(fsys)) + s.addParserOption(parser.WithConfigsFS(fsys)) } } } @@ -47,26 +46,23 @@ func WithConfigsFS(fsys fs.FS) options.ScannerOption { var _ scanners.FSScanner = (*Scanner)(nil) var _ options.ConfigurableScanner = (*Scanner)(nil) -type Scanner struct { // nolint: gocritic - debug debug.Logger - policyDirs []string - policyReaders []io.Reader - parser *parser.Parser - regoScanner *rego.Scanner - skipRequired bool - regoOnly bool - loadEmbeddedPolicies bool - loadEmbeddedLibraries bool - options []options.ScannerOption - parserOptions []options.ParserOption - frameworks []framework.Framework - spec string - sync.Mutex +type Scanner struct { + mu sync.Mutex + logger *log.Logger + parser *parser.Parser + regoScanner *rego.Scanner + regoOnly bool + options []options.ScannerOption + parserOptions []parser.Option + frameworks []framework.Framework + includeDeprecatedChecks bool } -func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} +func (s *Scanner) SetIncludeDeprecatedChecks(b bool) { + s.includeDeprecatedChecks = b +} -func (s *Scanner) addParserOptions(opt options.ParserOption) { +func (s *Scanner) addParserOption(opt parser.Option) { s.parserOptions = append(s.parserOptions, opt) } @@ -74,18 +70,6 @@ func (s *Scanner) SetFrameworks(frameworks []framework.Framework) { s.frameworks = frameworks } -func (s *Scanner) SetSpec(spec string) { - s.spec = spec -} - -func (s *Scanner) SetUseEmbeddedPolicies(b bool) { - s.loadEmbeddedPolicies = b -} - -func (s *Scanner) SetUseEmbeddedLibraries(b bool) { - s.loadEmbeddedLibraries = b -} - func (s *Scanner) SetRegoOnly(regoOnly bool) { s.regoOnly = regoOnly } @@ -94,58 +78,27 @@ func (s *Scanner) Name() string { return "CloudFormation" } -func (s *Scanner) SetPolicyReaders(readers []io.Reader) { - s.policyReaders = readers -} - -func (s *Scanner) SetSkipRequiredCheck(skip bool) { - s.skipRequired = skip -} - -func (s *Scanner) SetDebugWriter(writer io.Writer) { - s.debug = debug.New(writer, "cloudformation", "scanner") -} - -func (s *Scanner) SetPolicyDirs(dirs ...string) { - s.policyDirs = dirs -} - -func (s *Scanner) SetPolicyFilesystem(_ fs.FS) { - // handled by rego when option is passed on -} - -func (s *Scanner) SetDataFilesystem(_ fs.FS) { - // handled by rego when option is passed on -} -func (s *Scanner) SetRegoErrorLimit(_ int) {} - -func (s *Scanner) SetTraceWriter(_ io.Writer) {} -func (s *Scanner) SetPerResultTracingEnabled(_ bool) {} -func (s *Scanner) SetDataDirs(_ ...string) {} -func (s *Scanner) SetPolicyNamespaces(_ ...string) {} - // New creates a new Scanner func New(opts ...options.ScannerOption) *Scanner { s := &Scanner{ options: opts, + logger: log.WithPrefix("cloudformation scanner"), } for _, opt := range opts { opt(s) } - s.addParserOptions(options.ParserWithSkipRequiredCheck(s.skipRequired)) s.parser = parser.New(s.parserOptions...) return s } func (s *Scanner) initRegoScanner(srcFS fs.FS) (*rego.Scanner, error) { - s.Lock() - defer s.Unlock() + s.mu.Lock() + defer s.mu.Unlock() if s.regoScanner != nil { return s.regoScanner, nil } regoScanner := rego.NewScanner(types.SourceCloud, s.options...) - regoScanner.SetParentDebugLogger(s.debug) - if err := regoScanner.LoadPolicies(s.loadEmbeddedLibraries, s.loadEmbeddedPolicies, srcFS, s.policyDirs, s.policyReaders); err != nil { + if err := regoScanner.LoadPolicies(srcFS); err != nil { return nil, err } s.regoScanner = regoScanner @@ -220,12 +173,13 @@ func (s *Scanner) scanFileContext(ctx context.Context, regoScanner *rego.Scanner return nil, ctx.Err() default: } - if rule.GetRule().RegoPackage != "" { - continue + + if !s.includeDeprecatedChecks && rule.Deprecated { + continue // skip deprecated checks } + evalResult := rule.Evaluate(state) if len(evalResult) > 0 { - s.debug.Log("Found %d results for %s", len(evalResult), rule.GetRule().AVDID) for _, scanResult := range evalResult { ref := scanResult.Metadata().Reference() @@ -254,7 +208,10 @@ func (s *Scanner) scanFileContext(ctx context.Context, regoScanner *rego.Scanner results.Ignore(cfCtx.Ignores, nil) for _, ignored := range results.GetIgnored() { - s.debug.Log("Ignored '%s' at '%s'.", ignored.Rule().LongID(), ignored.Range()) + s.logger.Info("Ignore finding", + log.String("rule", ignored.Rule().LongID()), + log.String("range", ignored.Range().String()), + ) } return results, nil diff --git a/pkg/iac/scanners/cloudformation/scanner_test.go b/pkg/iac/scanners/cloudformation/scanner_test.go index baa8ed81ba59..67ee92cf69c3 100644 --- a/pkg/iac/scanners/cloudformation/scanner_test.go +++ b/pkg/iac/scanners/cloudformation/scanner_test.go @@ -10,6 +10,7 @@ import ( "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/framework" + "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" ) @@ -57,7 +58,7 @@ deny[res] { `, }) - scanner := New(options.ScannerWithPolicyDirs("rules"), options.ScannerWithRegoOnly(true)) + scanner := New(rego.WithPolicyDirs("rules"), options.ScannerWithRegoOnly(true)) results, err := scanner.ScanFS(context.TODO(), fs, "code") require.NoError(t, err) @@ -82,7 +83,9 @@ deny[res] { Terraform: (*scan.TerraformCustomCheck)(nil), }, RegoPackage: "data.builtin.dockerfile.DS006", - Frameworks: make(map[framework.Framework][]string), + Frameworks: map[framework.Framework][]string{ + framework.Default: {}, + }, }, results.GetFailed()[0].Rule()) failure := results.GetFailed()[0] @@ -213,9 +216,9 @@ Resources: scanner := New( options.ScannerWithRegoOnly(true), - options.ScannerWithEmbeddedPolicies(false), - options.ScannerWithPolicyReader(strings.NewReader(bucketNameCheck)), - options.ScannerWithPolicyNamespaces("user"), + rego.WithEmbeddedPolicies(false), + rego.WithPolicyReader(strings.NewReader(bucketNameCheck)), + rego.WithPolicyNamespaces("user"), ) results, err := scanner.ScanFS(context.TODO(), fsys, "code") diff --git a/pkg/iac/scanners/cloudformation/test/cf_scanning_test.go b/pkg/iac/scanners/cloudformation/test/cf_scanning_test.go index 4ab3009d4dab..46eed857a75f 100644 --- a/pkg/iac/scanners/cloudformation/test/cf_scanning_test.go +++ b/pkg/iac/scanners/cloudformation/test/cf_scanning_test.go @@ -1,7 +1,6 @@ package test import ( - "bytes" "context" "os" "testing" @@ -9,12 +8,12 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" ) func Test_basic_cloudformation_scanning(t *testing.T) { - cfScanner := cloudformation.New(options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(true)) + cfScanner := cloudformation.New(rego.WithEmbeddedPolicies(true), rego.WithEmbeddedLibraries(true)) results, err := cfScanner.ScanFS(context.TODO(), os.DirFS("./examples/bucket"), ".") require.NoError(t, err) @@ -23,26 +22,10 @@ func Test_basic_cloudformation_scanning(t *testing.T) { } func Test_cloudformation_scanning_has_expected_errors(t *testing.T) { - cfScanner := cloudformation.New(options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(true)) + cfScanner := cloudformation.New(rego.WithEmbeddedPolicies(true), rego.WithEmbeddedLibraries(true)) results, err := cfScanner.ScanFS(context.TODO(), os.DirFS("./examples/bucket"), ".") require.NoError(t, err) assert.NotEmpty(t, results.GetFailed()) } - -func Test_cloudformation_scanning_with_debug(t *testing.T) { - - debugWriter := bytes.NewBufferString("") - - scannerOptions := []options.ScannerOption{ - options.ScannerWithDebug(debugWriter), - } - cfScanner := cloudformation.New(scannerOptions...) - - _, err := cfScanner.ScanFS(context.TODO(), os.DirFS("./examples/bucket"), ".") - require.NoError(t, err) - - // check debug is as expected - assert.NotEmpty(t, debugWriter.String()) -} diff --git a/pkg/iac/scanners/dockerfile/parser/parser.go b/pkg/iac/scanners/dockerfile/parser/parser.go index d6ff7b4df21a..e92116ae3079 100644 --- a/pkg/iac/scanners/dockerfile/parser/parser.go +++ b/pkg/iac/scanners/dockerfile/parser/parser.go @@ -11,34 +11,19 @@ import ( "github.com/moby/buildkit/frontend/dockerfile/instructions" "github.com/moby/buildkit/frontend/dockerfile/parser" - "github.com/aquasecurity/trivy/pkg/iac/debug" - "github.com/aquasecurity/trivy/pkg/iac/detection" "github.com/aquasecurity/trivy/pkg/iac/providers/dockerfile" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/log" ) -var _ options.ConfigurableParser = (*Parser)(nil) - type Parser struct { - debug debug.Logger - skipRequired bool -} - -func (p *Parser) SetDebugWriter(writer io.Writer) { - p.debug = debug.New(writer, "dockerfile", "parser") -} - -func (p *Parser) SetSkipRequiredCheck(b bool) { - p.skipRequired = b + logger *log.Logger } // New creates a new Dockerfile parser -func New(opts ...options.ParserOption) *Parser { - p := &Parser{} - for _, option := range opts { - option(p) +func New() *Parser { + return &Parser{ + logger: log.WithPrefix("dockerfile parser"), } - return p } func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[string]*dockerfile.Dockerfile, error) { @@ -56,12 +41,10 @@ func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[st if entry.IsDir() { return nil } - if !p.Required(path) { - return nil - } + df, err := p.ParseFile(ctx, target, path) if err != nil { - // TODO add debug for parse errors + p.logger.Error("Failed to parse Dockerfile", log.FilePath(path), log.Err(err)) return nil } files[path] = df @@ -82,13 +65,6 @@ func (p *Parser) ParseFile(_ context.Context, fsys fs.FS, path string) (*dockerf return p.parse(path, f) } -func (p *Parser) Required(path string) bool { - if p.skipRequired { - return true - } - return detection.IsType(path, nil, detection.FileTypeDockerfile) -} - func (p *Parser) parse(path string, r io.Reader) (*dockerfile.Dockerfile, error) { parsed, err := parser.Parse(r) if err != nil { diff --git a/pkg/iac/scanners/dockerfile/scanner.go b/pkg/iac/scanners/dockerfile/scanner.go index 29df54634d58..1694f28d814a 100644 --- a/pkg/iac/scanners/dockerfile/scanner.go +++ b/pkg/iac/scanners/dockerfile/scanner.go @@ -2,11 +2,9 @@ package dockerfile import ( "context" - "io" "io/fs" "sync" - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scan" @@ -14,103 +12,37 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/scanners/dockerfile/parser" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" ) var _ scanners.FSScanner = (*Scanner)(nil) var _ options.ConfigurableScanner = (*Scanner)(nil) -type Scanner struct { // nolint: gocritic - debug debug.Logger - policyDirs []string - policyReaders []io.Reader - parser *parser.Parser - regoScanner *rego.Scanner - skipRequired bool - options []options.ScannerOption - frameworks []framework.Framework - spec string - sync.Mutex - loadEmbeddedLibraries bool - loadEmbeddedPolicies bool +type Scanner struct { + mu sync.Mutex + logger *log.Logger + parser *parser.Parser + regoScanner *rego.Scanner + options []options.ScannerOption } -func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} - -func (s *Scanner) SetSpec(spec string) { - s.spec = spec -} - -func (s *Scanner) SetRegoOnly(bool) { -} - -func (s *Scanner) SetFrameworks(frameworks []framework.Framework) { - s.frameworks = frameworks -} - -func (s *Scanner) SetUseEmbeddedPolicies(b bool) { - s.loadEmbeddedPolicies = b -} - -func (s *Scanner) SetUseEmbeddedLibraries(b bool) { - s.loadEmbeddedLibraries = b -} +func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} +func (s *Scanner) SetRegoOnly(bool) {} +func (s *Scanner) SetFrameworks(frameworks []framework.Framework) {} func (s *Scanner) Name() string { return "Dockerfile" } -func (s *Scanner) SetPolicyReaders(readers []io.Reader) { - s.policyReaders = readers -} - -func (s *Scanner) SetSkipRequiredCheck(skip bool) { - s.skipRequired = skip -} - -func (s *Scanner) SetDebugWriter(writer io.Writer) { - s.debug = debug.New(writer, "dockerfile", "scanner") -} - -func (s *Scanner) SetTraceWriter(_ io.Writer) { - // handled by rego later - nothing to do for now... -} - -func (s *Scanner) SetPerResultTracingEnabled(_ bool) { - // handled by rego later - nothing to do for now... -} - -func (s *Scanner) SetPolicyDirs(dirs ...string) { - s.policyDirs = dirs -} - -func (s *Scanner) SetDataDirs(_ ...string) { - // handled by rego later - nothing to do for now... -} - -func (s *Scanner) SetPolicyNamespaces(_ ...string) { - // handled by rego later - nothing to do for now... -} - -func (s *Scanner) SetPolicyFilesystem(_ fs.FS) { - // handled by rego when option is passed on -} - -func (s *Scanner) SetDataFilesystem(_ fs.FS) { - // handled by rego when option is passed on -} - -func (s *Scanner) SetRegoErrorLimit(_ int) { - // handled by rego when option is passed on -} - func NewScanner(opts ...options.ScannerOption) *Scanner { s := &Scanner{ options: opts, + logger: log.WithPrefix("dockerfile scanner"), } for _, opt := range opts { opt(s) } - s.parser = parser.New(options.ParserWithSkipRequiredCheck(s.skipRequired)) + s.parser = parser.New() return s } @@ -146,7 +78,7 @@ func (s *Scanner) ScanFile(ctx context.Context, fsys fs.FS, path string) (scan.R if err != nil { return nil, err } - s.debug.Log("Scanning %s...", path) + s.logger.Debug("Scanning", log.FilePath(path)) return s.scanRego(ctx, fsys, rego.Input{ Path: path, Contents: dockerfile.ToRego(), @@ -154,15 +86,14 @@ func (s *Scanner) ScanFile(ctx context.Context, fsys fs.FS, path string) (scan.R } func (s *Scanner) initRegoScanner(srcFS fs.FS) (*rego.Scanner, error) { - s.Lock() - defer s.Unlock() + s.mu.Lock() + defer s.mu.Unlock() if s.regoScanner != nil { return s.regoScanner, nil } regoScanner := rego.NewScanner(types.SourceDockerfile, s.options...) - regoScanner.SetParentDebugLogger(s.debug) - if err := regoScanner.LoadPolicies(s.loadEmbeddedLibraries, s.loadEmbeddedPolicies, srcFS, s.policyDirs, s.policyReaders); err != nil { + if err := regoScanner.LoadPolicies(srcFS); err != nil { return nil, err } s.regoScanner = regoScanner diff --git a/pkg/iac/scanners/dockerfile/scanner_test.go b/pkg/iac/scanners/dockerfile/scanner_test.go index 72c48f6a9f5e..2872df1e4ef6 100644 --- a/pkg/iac/scanners/dockerfile/scanner_test.go +++ b/pkg/iac/scanners/dockerfile/scanner_test.go @@ -13,7 +13,6 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/rego/schemas" "github.com/aquasecurity/trivy/pkg/iac/scan" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" ) const DS006PolicyWithDockerfileSchema = `# METADATA @@ -220,7 +219,7 @@ USER root "/rules/rule.rego": DS006LegacyWithOldStyleMetadata, }) - scanner := NewScanner(options.ScannerWithPolicyDirs("rules")) + scanner := NewScanner(rego.WithPolicyDirs("rules")) results, err := scanner.ScanFS(context.TODO(), fs, "code") require.NoError(t, err) @@ -252,7 +251,9 @@ USER root CustomChecks: scan.CustomChecks{ Terraform: (*scan.TerraformCustomCheck)(nil)}, RegoPackage: "data.builtin.dockerfile.DS006", - Frameworks: make(map[framework.Framework][]string), + Frameworks: map[framework.Framework][]string{ + framework.Default: {}, + }, }, results.GetFailed()[0].Rule(), ) @@ -553,29 +554,23 @@ res := true for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - regoMap := make(map[string]string) - libs, err := rego.LoadEmbeddedLibraries() - require.NoError(t, err) - for name, library := range libs { - regoMap["/rules/"+name] = library.String() - } - regoMap["/code/Dockerfile"] = `FROM golang:1.7.3 as dep + fsysMap := make(map[string]string) + fsysMap["/code/Dockerfile"] = `FROM golang:1.7.3 as dep COPY --from=dep /binary /` - regoMap["/rules/rule.rego"] = tc.inputRegoPolicy - regoMap["/rules/schemas/myfancydockerfile.json"] = string(schemas.Dockerfile) // just use the same for testing - fs := testutil.CreateFS(t, regoMap) + fsysMap["/rules/rule.rego"] = tc.inputRegoPolicy + fsysMap["/rules/schemas/myfancydockerfile.json"] = string(schemas.Dockerfile) // just use the same for testing + fsys := testutil.CreateFS(t, fsysMap) var traceBuf bytes.Buffer - var debugBuf bytes.Buffer scanner := NewScanner( - options.ScannerWithPolicyDirs("rules"), - options.ScannerWithTrace(&traceBuf), - options.ScannerWithDebug(&debugBuf), - options.ScannerWithRegoErrorLimits(0), + rego.WithPolicyDirs("rules"), + rego.WithEmbeddedLibraries(true), + rego.WithTrace(&traceBuf), + rego.WithRegoErrorLimits(0), ) - results, err := scanner.ScanFS(context.TODO(), fs, "code") + results, err := scanner.ScanFS(context.TODO(), fsys, "code") if tc.expectedError != "" && err != nil { require.Equal(t, tc.expectedError, err.Error(), tc.name) } else { @@ -607,7 +602,9 @@ COPY --from=dep /binary /` CustomChecks: scan.CustomChecks{ Terraform: (*scan.TerraformCustomCheck)(nil)}, RegoPackage: "data.builtin.dockerfile.DS006", - Frameworks: make(map[framework.Framework][]string), + Frameworks: map[framework.Framework][]string{ + framework.Default: {}, + }, }, results.GetFailed()[0].Rule(), ) diff --git a/pkg/iac/scanners/helm/options.go b/pkg/iac/scanners/helm/options.go index 6ac412bf1e34..c4439d928845 100644 --- a/pkg/iac/scanners/helm/options.go +++ b/pkg/iac/scanners/helm/options.go @@ -5,55 +5,50 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/scanners/options" ) -type ConfigurableHelmScanner interface { - options.ConfigurableScanner - AddParserOptions(options ...options.ParserOption) -} - func ScannerWithValuesFile(paths ...string) options.ScannerOption { return func(s options.ConfigurableScanner) { - if helmScanner, ok := s.(ConfigurableHelmScanner); ok { - helmScanner.AddParserOptions(parser.OptionWithValuesFile(paths...)) + if helmScanner, ok := s.(*Scanner); ok { + helmScanner.addParserOptions(parser.OptionWithValuesFile(paths...)) } } } func ScannerWithValues(values ...string) options.ScannerOption { return func(s options.ConfigurableScanner) { - if helmScanner, ok := s.(ConfigurableHelmScanner); ok { - helmScanner.AddParserOptions(parser.OptionWithValues(values...)) + if helmScanner, ok := s.(*Scanner); ok { + helmScanner.addParserOptions(parser.OptionWithValues(values...)) } } } func ScannerWithFileValues(values ...string) options.ScannerOption { return func(s options.ConfigurableScanner) { - if helmScanner, ok := s.(ConfigurableHelmScanner); ok { - helmScanner.AddParserOptions(parser.OptionWithFileValues(values...)) + if helmScanner, ok := s.(*Scanner); ok { + helmScanner.addParserOptions(parser.OptionWithFileValues(values...)) } } } func ScannerWithStringValues(values ...string) options.ScannerOption { return func(s options.ConfigurableScanner) { - if helmScanner, ok := s.(ConfigurableHelmScanner); ok { - helmScanner.AddParserOptions(parser.OptionWithStringValues(values...)) + if helmScanner, ok := s.(*Scanner); ok { + helmScanner.addParserOptions(parser.OptionWithStringValues(values...)) } } } func ScannerWithAPIVersions(values ...string) options.ScannerOption { return func(s options.ConfigurableScanner) { - if helmScanner, ok := s.(ConfigurableHelmScanner); ok { - helmScanner.AddParserOptions(parser.OptionWithAPIVersions(values...)) + if helmScanner, ok := s.(*Scanner); ok { + helmScanner.addParserOptions(parser.OptionWithAPIVersions(values...)) } } } func ScannerWithKubeVersion(values string) options.ScannerOption { return func(s options.ConfigurableScanner) { - if helmScanner, ok := s.(ConfigurableHelmScanner); ok { - helmScanner.AddParserOptions(parser.OptionWithKubeVersion(values)) + if helmScanner, ok := s.(*Scanner); ok { + helmScanner.addParserOptions(parser.OptionWithKubeVersion(values)) } } } diff --git a/pkg/iac/scanners/helm/parser/option.go b/pkg/iac/scanners/helm/parser/option.go index 6de98d765182..0f2eae3cc291 100644 --- a/pkg/iac/scanners/helm/parser/option.go +++ b/pkg/iac/scanners/helm/parser/option.go @@ -1,9 +1,6 @@ package parser -import "github.com/aquasecurity/trivy/pkg/iac/scanners/options" - type ConfigurableHelmParser interface { - options.ConfigurableParser SetValuesFile(...string) SetValues(...string) SetFileValues(...string) @@ -12,50 +9,40 @@ type ConfigurableHelmParser interface { SetKubeVersion(string) } -func OptionWithValuesFile(paths ...string) options.ParserOption { - return func(p options.ConfigurableParser) { - if helmParser, ok := p.(ConfigurableHelmParser); ok { - helmParser.SetValuesFile(paths...) - } +type Option func(p *Parser) + +func OptionWithValuesFile(paths ...string) Option { + return func(p *Parser) { + p.valuesFiles = paths } } -func OptionWithValues(values ...string) options.ParserOption { - return func(p options.ConfigurableParser) { - if helmParser, ok := p.(ConfigurableHelmParser); ok { - helmParser.SetValues(values...) - } +func OptionWithValues(values ...string) Option { + return func(p *Parser) { + p.values = values } } -func OptionWithFileValues(values ...string) options.ParserOption { - return func(p options.ConfigurableParser) { - if helmParser, ok := p.(ConfigurableHelmParser); ok { - helmParser.SetValues(values...) - } +func OptionWithFileValues(values ...string) Option { + return func(p *Parser) { + p.fileValues = values } } -func OptionWithStringValues(values ...string) options.ParserOption { - return func(p options.ConfigurableParser) { - if helmParser, ok := p.(ConfigurableHelmParser); ok { - helmParser.SetValues(values...) - } +func OptionWithStringValues(values ...string) Option { + return func(p *Parser) { + p.stringValues = values } } -func OptionWithAPIVersions(values ...string) options.ParserOption { - return func(p options.ConfigurableParser) { - if helmParser, ok := p.(ConfigurableHelmParser); ok { - helmParser.SetAPIVersions(values...) - } +func OptionWithAPIVersions(values ...string) Option { + return func(p *Parser) { + p.apiVersions = values } } -func OptionWithKubeVersion(value string) options.ParserOption { - return func(p options.ConfigurableParser) { - if helmParser, ok := p.(ConfigurableHelmParser); ok { - helmParser.SetKubeVersion(value) - } +func OptionWithKubeVersion(value string) Option { + return func(p *Parser) { + p.kubeVersion = value } } diff --git a/pkg/iac/scanners/helm/parser/parser.go b/pkg/iac/scanners/helm/parser/parser.go index 8768ac459867..e38f032b6221 100644 --- a/pkg/iac/scanners/helm/parser/parser.go +++ b/pkg/iac/scanners/helm/parser/parser.go @@ -5,7 +5,6 @@ import ( "context" "errors" "fmt" - "io" "io/fs" "path/filepath" "regexp" @@ -21,20 +20,18 @@ import ( "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/releaseutil" - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/detection" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/log" ) var manifestNameRegex = regexp.MustCompile("# Source: [^/]+/(.+)") type Parser struct { + logger *log.Logger helmClient *action.Install rootPath string ChartSource string filepaths []string - debug debug.Logger - skipRequired bool workingFS fs.FS valuesFiles []string values []string @@ -49,39 +46,7 @@ type ChartFile struct { ManifestContent string } -func (p *Parser) SetDebugWriter(writer io.Writer) { - p.debug = debug.New(writer, "helm", "parser") -} - -func (p *Parser) SetSkipRequiredCheck(b bool) { - p.skipRequired = b -} - -func (p *Parser) SetValuesFile(s ...string) { - p.valuesFiles = s -} - -func (p *Parser) SetValues(values ...string) { - p.values = values -} - -func (p *Parser) SetFileValues(values ...string) { - p.fileValues = values -} - -func (p *Parser) SetStringValues(values ...string) { - p.stringValues = values -} - -func (p *Parser) SetAPIVersions(values ...string) { - p.apiVersions = values -} - -func (p *Parser) SetKubeVersion(value string) { - p.kubeVersion = value -} - -func New(path string, opts ...options.ParserOption) (*Parser, error) { +func New(path string, opts ...Option) (*Parser, error) { client := action.NewInstall(&action.Configuration{}) client.DryRun = true // don't do anything @@ -91,6 +56,7 @@ func New(path string, opts ...options.ParserOption) (*Parser, error) { p := &Parser{ helmClient: client, ChartSource: path, + logger: log.WithPrefix("helm parser"), } for _, option := range opts { @@ -129,10 +95,6 @@ func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) error { return nil } - if !p.required(path, p.workingFS) { - return nil - } - if detection.IsArchive(path) { tarFS, err := p.addTarToFS(path) if errors.Is(err, errSkipFS) { @@ -310,15 +272,3 @@ func getManifestPath(manifest string) string { } return manifestFilePathParts[0] } - -func (p *Parser) required(path string, workingFS fs.FS) bool { - if p.skipRequired { - return true - } - content, err := fs.ReadFile(workingFS, path) - if err != nil { - return false - } - - return detection.IsType(path, bytes.NewReader(content), detection.FileTypeHelm) -} diff --git a/pkg/iac/scanners/helm/parser/parser_tar.go b/pkg/iac/scanners/helm/parser/parser_tar.go index f8d692eaa977..8e2ba97a81d2 100644 --- a/pkg/iac/scanners/helm/parser/parser_tar.go +++ b/pkg/iac/scanners/helm/parser/parser_tar.go @@ -14,6 +14,7 @@ import ( "github.com/liamg/memoryfs" "github.com/aquasecurity/trivy/pkg/iac/detection" + "github.com/aquasecurity/trivy/pkg/log" ) var errSkipFS = errors.New("skip parse FS") @@ -75,13 +76,13 @@ func (p *Parser) addTarToFS(archivePath string) (fs.FS, error) { return nil, err } case tar.TypeReg: - p.debug.Log("Unpacking tar entry %s", targetPath) + p.logger.Debug("Unpacking tar entry", log.FilePath(targetPath)) if err := copyFile(tarFS, tr, targetPath); err != nil { return nil, err } case tar.TypeSymlink: if path.IsAbs(link) { - p.debug.Log("Symlink %s is absolute, skipping", link) + p.logger.Debug("Symlink is absolute, skipping", log.String("link", link)) continue } diff --git a/pkg/iac/scanners/helm/scanner.go b/pkg/iac/scanners/helm/scanner.go index fc54af44781f..bfa63a5d2c00 100644 --- a/pkg/iac/scanners/helm/scanner.go +++ b/pkg/iac/scanners/helm/scanner.go @@ -11,7 +11,6 @@ import ( "github.com/liamg/memoryfs" - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/detection" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/rego" @@ -21,45 +20,29 @@ import ( kparser "github.com/aquasecurity/trivy/pkg/iac/scanners/kubernetes/parser" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" ) var _ scanners.FSScanner = (*Scanner)(nil) var _ options.ConfigurableScanner = (*Scanner)(nil) type Scanner struct { - policyDirs []string - dataDirs []string - debug debug.Logger - options []options.ScannerOption - parserOptions []options.ParserOption - policyReaders []io.Reader - loadEmbeddedLibraries bool - loadEmbeddedPolicies bool - policyFS fs.FS - skipRequired bool - frameworks []framework.Framework - spec string - regoScanner *rego.Scanner - mu sync.Mutex + mu sync.Mutex + logger *log.Logger + options []options.ScannerOption + parserOptions []parser.Option + regoScanner *rego.Scanner } -func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} - -func (s *Scanner) SetSpec(spec string) { - s.spec = spec -} - -func (s *Scanner) SetRegoOnly(bool) { -} - -func (s *Scanner) SetFrameworks(frameworks []framework.Framework) { - s.frameworks = frameworks -} +func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} +func (s *Scanner) SetRegoOnly(bool) {} +func (s *Scanner) SetFrameworks(frameworks []framework.Framework) {} // New creates a new Scanner func New(opts ...options.ScannerOption) *Scanner { s := &Scanner{ options: opts, + logger: log.WithPrefix("helm scanner"), } for _, option := range opts { @@ -68,61 +51,14 @@ func New(opts ...options.ScannerOption) *Scanner { return s } -func (s *Scanner) AddParserOptions(opts ...options.ParserOption) { +func (s *Scanner) addParserOptions(opts ...parser.Option) { s.parserOptions = append(s.parserOptions, opts...) } -func (s *Scanner) SetUseEmbeddedPolicies(b bool) { - s.loadEmbeddedPolicies = b -} - -func (s *Scanner) SetUseEmbeddedLibraries(b bool) { - s.loadEmbeddedLibraries = b -} - func (s *Scanner) Name() string { return "Helm" } -func (s *Scanner) SetPolicyReaders(readers []io.Reader) { - s.policyReaders = readers -} - -func (s *Scanner) SetSkipRequiredCheck(skip bool) { - s.skipRequired = skip -} - -func (s *Scanner) SetDebugWriter(writer io.Writer) { - s.debug = debug.New(writer, "helm", "scanner") -} - -func (s *Scanner) SetTraceWriter(_ io.Writer) { - // handled by rego later - nothing to do for now... -} - -func (s *Scanner) SetPerResultTracingEnabled(_ bool) { - // handled by rego later - nothing to do for now... -} - -func (s *Scanner) SetPolicyDirs(dirs ...string) { - s.policyDirs = dirs -} - -func (s *Scanner) SetDataDirs(dirs ...string) { - s.dataDirs = dirs -} - -func (s *Scanner) SetPolicyNamespaces(namespaces ...string) { - // handled by rego later - nothing to do for now... -} - -func (s *Scanner) SetPolicyFilesystem(policyFS fs.FS) { - s.policyFS = policyFS -} - -func (s *Scanner) SetDataFilesystem(_ fs.FS) {} -func (s *Scanner) SetRegoErrorLimit(_ int) {} - func (s *Scanner) ScanFS(ctx context.Context, target fs.FS, path string) (scan.Results, error) { if err := s.initRegoScanner(target); err != nil { @@ -183,13 +119,16 @@ func (s *Scanner) getScanResults(path string, ctx context.Context, target fs.FS) chartFiles, err := helmParser.RenderedChartFiles() if err != nil { // not valid helm, maybe some other yaml etc., abort - s.debug.Log("Failed to render Chart files: %s", err) + s.logger.Error( + "Failed to render Chart files", + log.FilePath(path), log.Err(err), + ) return nil, nil } for _, file := range chartFiles { file := file - s.debug.Log("Processing rendered chart file: %s", file.TemplateFilePath) + s.logger.Debug("Processing rendered chart file", log.FilePath(file.TemplateFilePath)) manifests, err := kparser.New().Parse(strings.NewReader(file.ManifestContent), file.TemplateFilePath) if err != nil { @@ -232,8 +171,7 @@ func (s *Scanner) initRegoScanner(srcFS fs.FS) error { return nil } regoScanner := rego.NewScanner(types.SourceKubernetes, s.options...) - regoScanner.SetParentDebugLogger(s.debug) - if err := regoScanner.LoadPolicies(s.loadEmbeddedLibraries, s.loadEmbeddedPolicies, srcFS, s.policyDirs, s.policyReaders); err != nil { + if err := regoScanner.LoadPolicies(srcFS); err != nil { return err } s.regoScanner = regoScanner diff --git a/pkg/iac/scanners/helm/test/option_test.go b/pkg/iac/scanners/helm/test/option_test.go index 8efb03f16116..05bf96ee01d3 100644 --- a/pkg/iac/scanners/helm/test/option_test.go +++ b/pkg/iac/scanners/helm/test/option_test.go @@ -11,7 +11,6 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/iac/scanners/helm/parser" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" ) func Test_helm_parser_with_options_with_values_file(t *testing.T) { @@ -34,7 +33,7 @@ func Test_helm_parser_with_options_with_values_file(t *testing.T) { t.Logf("Running test: %s", test.testName) - var opts []options.ParserOption + var opts []parser.Option if test.valuesFile != "" { opts = append(opts, parser.OptionWithValuesFile(test.valuesFile)) @@ -84,7 +83,7 @@ func Test_helm_parser_with_options_with_set_value(t *testing.T) { t.Logf("Running test: %s", test.testName) - var opts []options.ParserOption + var opts []parser.Option if test.valuesFile != "" { opts = append(opts, parser.OptionWithValuesFile(test.valuesFile)) @@ -138,7 +137,7 @@ func Test_helm_parser_with_options_with_api_versions(t *testing.T) { t.Logf("Running test: %s", test.testName) - var opts []options.ParserOption + var opts []parser.Option if len(test.apiVersions) > 0 { opts = append(opts, parser.OptionWithAPIVersions(test.apiVersions...)) @@ -195,7 +194,7 @@ func Test_helm_parser_with_options_with_kube_versions(t *testing.T) { t.Logf("Running test: %s", test.testName) - var opts []options.ParserOption + var opts []parser.Option opts = append(opts, parser.OptionWithKubeVersion(test.kubeVersion)) diff --git a/pkg/iac/scanners/helm/test/scanner_test.go b/pkg/iac/scanners/helm/test/scanner_test.go index 45a426762439..ef751ac2a7b8 100644 --- a/pkg/iac/scanners/helm/test/scanner_test.go +++ b/pkg/iac/scanners/helm/test/scanner_test.go @@ -13,8 +13,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scanners/helm" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" ) func Test_helm_scanner_with_archive(t *testing.T) { @@ -40,7 +40,7 @@ func Test_helm_scanner_with_archive(t *testing.T) { for _, test := range tests { t.Logf("Running test: %s", test.testName) - helmScanner := helm.New(options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(true)) + helmScanner := helm.New(rego.WithEmbeddedPolicies(true), rego.WithEmbeddedLibraries(true)) testTemp := t.TempDir() testFileName := filepath.Join(testTemp, test.archiveName) @@ -96,7 +96,7 @@ func Test_helm_scanner_with_missing_name_can_recover(t *testing.T) { for _, test := range tests { t.Logf("Running test: %s", test.testName) - helmScanner := helm.New(options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(true)) + helmScanner := helm.New(rego.WithEmbeddedPolicies(true), rego.WithEmbeddedLibraries(true)) testTemp := t.TempDir() testFileName := filepath.Join(testTemp, test.archiveName) @@ -128,7 +128,7 @@ func Test_helm_scanner_with_dir(t *testing.T) { t.Logf("Running test: %s", test.testName) - helmScanner := helm.New(options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(true)) + helmScanner := helm.New(rego.WithEmbeddedPolicies(true), rego.WithEmbeddedLibraries(true)) testFs := os.DirFS(filepath.Join("testdata", test.chartName)) results, err := helmScanner.ScanFS(context.TODO(), testFs, ".") @@ -209,9 +209,9 @@ deny[res] { t.Run(test.testName, func(t *testing.T) { t.Logf("Running test: %s", test.testName) - helmScanner := helm.New(options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(true), - options.ScannerWithPolicyDirs("rules"), - options.ScannerWithPolicyNamespaces("user")) + helmScanner := helm.New(rego.WithEmbeddedPolicies(true), rego.WithEmbeddedLibraries(true), + rego.WithPolicyDirs("rules"), + rego.WithPolicyNamespaces("user")) testTemp := t.TempDir() testFileName := filepath.Join(testTemp, test.archiveName) @@ -274,7 +274,7 @@ func copyArchive(src, dst string) error { } func Test_helm_chart_with_templated_name(t *testing.T) { - helmScanner := helm.New(options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(true)) + helmScanner := helm.New(rego.WithEmbeddedPolicies(true), rego.WithEmbeddedLibraries(true)) testFs := os.DirFS(filepath.Join("testdata", "templated-name")) _, err := helmScanner.ScanFS(context.TODO(), testFs, ".") require.NoError(t, err) @@ -302,10 +302,10 @@ deny[res] { } ` helmScanner := helm.New( - options.ScannerWithEmbeddedPolicies(false), - options.ScannerWithEmbeddedLibraries(false), - options.ScannerWithPolicyNamespaces("user"), - options.ScannerWithPolicyReader(strings.NewReader(policy)), + rego.WithEmbeddedPolicies(false), + rego.WithEmbeddedLibraries(false), + rego.WithPolicyNamespaces("user"), + rego.WithPolicyReader(strings.NewReader(policy)), ) results, err := helmScanner.ScanFS(context.TODO(), os.DirFS("testdata/simmilar-templates"), ".") @@ -348,10 +348,10 @@ deny[res] { ` scanner := helm.New( - options.ScannerWithEmbeddedPolicies(false), - options.ScannerWithEmbeddedLibraries(true), - options.ScannerWithPolicyNamespaces("user"), - options.ScannerWithPolicyReader(strings.NewReader(check)), + rego.WithEmbeddedPolicies(false), + rego.WithEmbeddedLibraries(true), + rego.WithPolicyNamespaces("user"), + rego.WithPolicyReader(strings.NewReader(check)), ) results, err := scanner.ScanFS(context.TODO(), os.DirFS("testdata/with-subchart"), ".") diff --git a/pkg/iac/scanners/json/parser/parser.go b/pkg/iac/scanners/json/parser/parser.go index 340f23c9d11c..c97e54b40bd9 100644 --- a/pkg/iac/scanners/json/parser/parser.go +++ b/pkg/iac/scanners/json/parser/parser.go @@ -3,37 +3,21 @@ package parser import ( "context" "encoding/json" - "io" "io/fs" "path/filepath" - "github.com/aquasecurity/trivy/pkg/iac/debug" - "github.com/aquasecurity/trivy/pkg/iac/detection" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/log" ) -var _ options.ConfigurableParser = (*Parser)(nil) - type Parser struct { - debug debug.Logger - skipRequired bool -} - -func (p *Parser) SetDebugWriter(writer io.Writer) { - p.debug = debug.New(writer, "json", "parser") -} - -func (p *Parser) SetSkipRequiredCheck(b bool) { - p.skipRequired = b + logger *log.Logger } // New creates a new parser -func New(opts ...options.ParserOption) *Parser { - p := &Parser{} - for _, opt := range opts { - opt(p) +func New() *Parser { + return &Parser{ + logger: log.WithPrefix("json parser"), } - return p } func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[string]any, error) { @@ -51,14 +35,13 @@ func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[st if entry.IsDir() { return nil } - if !p.Required(path) { - return nil - } + df, err := p.ParseFile(ctx, target, path) if err != nil { - p.debug.Log("Parse error in '%s': %s", path, err) + p.logger.Error("Parse error", log.FilePath(path), log.Err(err)) return nil } + files[path] = df return nil }); err != nil { @@ -80,10 +63,3 @@ func (p *Parser) ParseFile(_ context.Context, fsys fs.FS, path string) (any, err } return target, nil } - -func (p *Parser) Required(path string) bool { - if p.skipRequired { - return true - } - return detection.IsType(path, nil, detection.FileTypeJSON) -} diff --git a/pkg/iac/scanners/json/scanner.go b/pkg/iac/scanners/json/scanner.go index 3d563c34c790..f46f9cc56bc9 100644 --- a/pkg/iac/scanners/json/scanner.go +++ b/pkg/iac/scanners/json/scanner.go @@ -2,11 +2,9 @@ package json import ( "context" - "io" "io/fs" "sync" - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scan" @@ -14,89 +12,33 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/scanners/json/parser" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" ) var _ scanners.FSScanner = (*Scanner)(nil) var _ options.ConfigurableScanner = (*Scanner)(nil) -type Scanner struct { // nolint: gocritic - debug debug.Logger - policyDirs []string - policyReaders []io.Reader - parser *parser.Parser - regoScanner *rego.Scanner - skipRequired bool - options []options.ScannerOption - sync.Mutex - frameworks []framework.Framework - spec string - loadEmbeddedPolicies bool - loadEmbeddedLibraries bool +type Scanner struct { + mu sync.Mutex + logger *log.Logger + parser *parser.Parser + regoScanner *rego.Scanner + options []options.ScannerOption } -func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} - -func (s *Scanner) SetRegoOnly(bool) { -} - -func (s *Scanner) SetFrameworks(frameworks []framework.Framework) { - s.frameworks = frameworks -} - -func (s *Scanner) SetSpec(spec string) { - s.spec = spec -} - -func (s *Scanner) SetUseEmbeddedPolicies(b bool) { - s.loadEmbeddedPolicies = b -} - -func (s *Scanner) SetUseEmbeddedLibraries(b bool) { - s.loadEmbeddedLibraries = b -} - -func (s *Scanner) SetPolicyReaders(readers []io.Reader) { - s.policyReaders = readers -} - -func (s *Scanner) SetDebugWriter(writer io.Writer) { - s.debug = debug.New(writer, "json", "scanner") -} - -func (s *Scanner) SetTraceWriter(_ io.Writer) { -} - -func (s *Scanner) SetPerResultTracingEnabled(_ bool) { -} - -func (s *Scanner) SetPolicyDirs(dirs ...string) { - s.policyDirs = dirs -} - -func (s *Scanner) SetDataDirs(_ ...string) {} -func (s *Scanner) SetPolicyNamespaces(_ ...string) {} - -func (s *Scanner) SetSkipRequiredCheck(skip bool) { - s.skipRequired = skip -} - -func (s *Scanner) SetPolicyFilesystem(_ fs.FS) { - // handled by rego when option is passed on -} - -func (s *Scanner) SetDataFilesystem(_ fs.FS) { - // handled by rego when option is passed on -} -func (s *Scanner) SetRegoErrorLimit(_ int) {} +func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} +func (s *Scanner) SetRegoOnly(bool) {} +func (s *Scanner) SetFrameworks(frameworks []framework.Framework) {} func NewScanner(opts ...options.ScannerOption) *Scanner { s := &Scanner{ options: opts, + logger: log.WithPrefix("json scanner"), + parser: parser.New(), } for _, opt := range opts { opt(s) } - s.parser = parser.New(options.ParserWithSkipRequiredCheck(s.skipRequired)) return s } @@ -136,7 +78,7 @@ func (s *Scanner) ScanFile(ctx context.Context, fsys fs.FS, path string) (scan.R if err != nil { return nil, err } - s.debug.Log("Scanning %s...", path) + s.logger.Debug("Scanning", log.FilePath(path)) return s.scanRego(ctx, fsys, rego.Input{ Path: path, Contents: parsed, @@ -144,14 +86,13 @@ func (s *Scanner) ScanFile(ctx context.Context, fsys fs.FS, path string) (scan.R } func (s *Scanner) initRegoScanner(srcFS fs.FS) (*rego.Scanner, error) { - s.Lock() - defer s.Unlock() + s.mu.Lock() + defer s.mu.Unlock() if s.regoScanner != nil { return s.regoScanner, nil } regoScanner := rego.NewScanner(types.SourceJSON, s.options...) - regoScanner.SetParentDebugLogger(s.debug) - if err := regoScanner.LoadPolicies(s.loadEmbeddedLibraries, s.loadEmbeddedPolicies, srcFS, s.policyDirs, s.policyReaders); err != nil { + if err := regoScanner.LoadPolicies(srcFS); err != nil { return nil, err } s.regoScanner = regoScanner diff --git a/pkg/iac/scanners/json/scanner_test.go b/pkg/iac/scanners/json/scanner_test.go index 7e7f1c308186..6656f70b2e25 100644 --- a/pkg/iac/scanners/json/scanner_test.go +++ b/pkg/iac/scanners/json/scanner_test.go @@ -9,8 +9,8 @@ import ( "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/framework" + "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scan" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" ) func Test_BasicScan(t *testing.T) { @@ -48,7 +48,7 @@ deny[res] { `, }) - scanner := NewScanner(options.ScannerWithPolicyDirs("rules")) + scanner := NewScanner(rego.WithPolicyDirs("rules")) results, err := scanner.ScanFS(context.TODO(), fs, "code") require.NoError(t, err) @@ -73,6 +73,8 @@ deny[res] { Terraform: (*scan.TerraformCustomCheck)(nil), }, RegoPackage: "data.builtin.json.lol", - Frameworks: make(map[framework.Framework][]string), + Frameworks: map[framework.Framework][]string{ + framework.Default: {}, + }, }, results.GetFailed()[0].Rule()) } diff --git a/pkg/iac/scanners/kubernetes/parser/parser.go b/pkg/iac/scanners/kubernetes/parser/parser.go index a1c5ecf46962..f3ca7d613562 100644 --- a/pkg/iac/scanners/kubernetes/parser/parser.go +++ b/pkg/iac/scanners/kubernetes/parser/parser.go @@ -1,7 +1,6 @@ package parser import ( - "bytes" "context" "encoding/json" "fmt" @@ -14,33 +13,18 @@ import ( "gopkg.in/yaml.v3" kyaml "sigs.k8s.io/yaml" - "github.com/aquasecurity/trivy/pkg/iac/debug" - "github.com/aquasecurity/trivy/pkg/iac/detection" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/log" ) -var _ options.ConfigurableParser = (*Parser)(nil) - type Parser struct { - debug debug.Logger - skipRequired bool -} - -func (p *Parser) SetDebugWriter(writer io.Writer) { - p.debug = debug.New(writer, "kubernetes", "parser") -} - -func (p *Parser) SetSkipRequiredCheck(b bool) { - p.skipRequired = b + logger *log.Logger } // New creates a new K8s parser -func New(opts ...options.ParserOption) *Parser { - p := &Parser{} - for _, option := range opts { - option(p) +func New() *Parser { + return &Parser{ + logger: log.WithPrefix("k8s parser"), } - return p } func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[string][]any, error) { @@ -57,14 +41,13 @@ func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[st if entry.IsDir() { return nil } - if !p.required(target, path) { - return nil - } + parsed, err := p.ParseFile(ctx, target, path) if err != nil { - p.debug.Log("Parse error in '%s': %s", path, err) + p.logger.Error("Parse error", log.FilePath(path), log.Err(err)) return nil } + files[path] = parsed return nil }); err != nil { @@ -83,21 +66,6 @@ func (p *Parser) ParseFile(_ context.Context, fsys fs.FS, path string) ([]any, e return p.Parse(f, path) } -func (p *Parser) required(fsys fs.FS, path string) bool { - if p.skipRequired { - return true - } - f, err := fsys.Open(filepath.ToSlash(path)) - if err != nil { - return false - } - defer func() { _ = f.Close() }() - if data, err := io.ReadAll(f); err == nil { - return detection.IsType(path, bytes.NewReader(data), detection.FileTypeKubernetes) - } - return false -} - func (p *Parser) Parse(r io.Reader, path string) ([]any, error) { contents, err := io.ReadAll(r) diff --git a/pkg/iac/scanners/kubernetes/scanner.go b/pkg/iac/scanners/kubernetes/scanner.go index 44f13ce5b003..dfd248cc8b1d 100644 --- a/pkg/iac/scanners/kubernetes/scanner.go +++ b/pkg/iac/scanners/kubernetes/scanner.go @@ -10,7 +10,6 @@ import ( "github.com/liamg/memoryfs" - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scan" @@ -18,89 +17,33 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/scanners/kubernetes/parser" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" ) var _ scanners.FSScanner = (*Scanner)(nil) var _ options.ConfigurableScanner = (*Scanner)(nil) -type Scanner struct { // nolint: gocritic - debug debug.Logger - options []options.ScannerOption - policyDirs []string - policyReaders []io.Reader - regoScanner *rego.Scanner - parser *parser.Parser - skipRequired bool - sync.Mutex - loadEmbeddedPolicies bool - frameworks []framework.Framework - spec string - loadEmbeddedLibraries bool +type Scanner struct { + mu sync.Mutex + logger *log.Logger + options []options.ScannerOption + regoScanner *rego.Scanner + parser *parser.Parser } -func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} - -func (s *Scanner) SetSpec(spec string) { - s.spec = spec -} - -func (s *Scanner) SetRegoOnly(bool) {} - -func (s *Scanner) SetFrameworks(frameworks []framework.Framework) { - s.frameworks = frameworks -} - -func (s *Scanner) SetUseEmbeddedPolicies(b bool) { - s.loadEmbeddedPolicies = b -} - -func (s *Scanner) SetUseEmbeddedLibraries(b bool) { - s.loadEmbeddedLibraries = b -} - -func (s *Scanner) SetPolicyReaders(readers []io.Reader) { - s.policyReaders = readers -} - -func (s *Scanner) SetSkipRequiredCheck(skip bool) { - s.skipRequired = skip -} - -func (s *Scanner) SetDebugWriter(writer io.Writer) { - s.debug = debug.New(writer, "kubernetes", "scanner") -} - -func (s *Scanner) SetTraceWriter(_ io.Writer) { -} - -func (s *Scanner) SetPerResultTracingEnabled(_ bool) { -} - -func (s *Scanner) SetPolicyDirs(dirs ...string) { - s.policyDirs = dirs -} - -func (s *Scanner) SetDataDirs(...string) {} -func (s *Scanner) SetPolicyNamespaces(_ ...string) { -} - -func (s *Scanner) SetPolicyFilesystem(_ fs.FS) { - // handled by rego when option is passed on -} - -func (s *Scanner) SetDataFilesystem(_ fs.FS) { - // handled by rego when option is passed on -} -func (s *Scanner) SetRegoErrorLimit(_ int) {} +func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} +func (s *Scanner) SetRegoOnly(bool) {} +func (s *Scanner) SetFrameworks(frameworks []framework.Framework) {} func NewScanner(opts ...options.ScannerOption) *Scanner { s := &Scanner{ options: opts, + logger: log.WithPrefix("k8s scanner"), + parser: parser.New(), } for _, opt := range opts { opt(s) } - s.parser = parser.New(options.ParserWithSkipRequiredCheck(s.skipRequired)) return s } @@ -109,14 +52,13 @@ func (s *Scanner) Name() string { } func (s *Scanner) initRegoScanner(srcFS fs.FS) (*rego.Scanner, error) { - s.Lock() - defer s.Unlock() + s.mu.Lock() + defer s.mu.Unlock() if s.regoScanner != nil { return s.regoScanner, nil } regoScanner := rego.NewScanner(types.SourceKubernetes, s.options...) - regoScanner.SetParentDebugLogger(s.debug) - if err := regoScanner.LoadPolicies(s.loadEmbeddedLibraries, s.loadEmbeddedPolicies, srcFS, s.policyDirs, s.policyReaders); err != nil { + if err := regoScanner.LoadPolicies(srcFS); err != nil { return nil, err } s.regoScanner = regoScanner @@ -165,7 +107,7 @@ func (s *Scanner) ScanFS(ctx context.Context, target fs.FS, dir string) (scan.Re return nil, err } - s.debug.Log("Scanning %d files...", len(inputs)) + s.logger.Debug("Scanning files", log.Int("count", len(inputs))) results, err := regoScanner.ScanInput(ctx, inputs...) if err != nil { return nil, err diff --git a/pkg/iac/scanners/kubernetes/scanner_test.go b/pkg/iac/scanners/kubernetes/scanner_test.go index a75173f67c0e..ef1ac38560f4 100644 --- a/pkg/iac/scanners/kubernetes/scanner_test.go +++ b/pkg/iac/scanners/kubernetes/scanner_test.go @@ -11,8 +11,8 @@ import ( "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/framework" + "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scan" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" ) func Test_BasicScan_YAML(t *testing.T) { @@ -94,8 +94,8 @@ deny[res] { }) scanner := NewScanner( - options.ScannerWithPolicyDirs("rules"), - options.ScannerWithEmbeddedLibraries(true), + rego.WithPolicyDirs("rules"), + rego.WithEmbeddedLibraries(true), ) results, err := scanner.ScanFS(context.TODO(), fs, "code") @@ -119,7 +119,9 @@ deny[res] { CloudFormation: &scan.EngineMetadata{}, CustomChecks: scan.CustomChecks{Terraform: (*scan.TerraformCustomCheck)(nil)}, RegoPackage: "data.builtin.kubernetes.KSV011", - Frameworks: make(map[framework.Framework][]string), + Frameworks: map[framework.Framework][]string{ + framework.Default: {}, + }, }, results.GetFailed()[0].Rule()) failure := results.GetFailed()[0] @@ -254,8 +256,8 @@ deny[res] { }) scanner := NewScanner( - options.ScannerWithPolicyDirs("rules"), - options.ScannerWithEmbeddedLibraries(true), + rego.WithPolicyDirs("rules"), + rego.WithEmbeddedLibraries(true), ) results, err := scanner.ScanFS(context.TODO(), fs, "code") @@ -279,7 +281,9 @@ deny[res] { CloudFormation: &scan.EngineMetadata{}, CustomChecks: scan.CustomChecks{Terraform: (*scan.TerraformCustomCheck)(nil)}, RegoPackage: "data.builtin.kubernetes.KSV011", - Frameworks: make(map[framework.Framework][]string), + Frameworks: map[framework.Framework][]string{ + framework.Default: {}, + }, }, results.GetFailed()[0].Rule()) failure := results.GetFailed()[0] @@ -326,7 +330,7 @@ deny[res] { func Test_FileScan(t *testing.T) { - results, err := NewScanner(options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(true), options.ScannerWithEmbeddedLibraries(true)).ScanReader(context.TODO(), "k8s.yaml", strings.NewReader(` + results, err := NewScanner(rego.WithEmbeddedPolicies(true), rego.WithEmbeddedLibraries(true), rego.WithEmbeddedLibraries(true)).ScanReader(context.TODO(), "k8s.yaml", strings.NewReader(` apiVersion: v1 kind: Pod metadata: @@ -344,7 +348,7 @@ spec: func Test_FileScan_WithSeparator(t *testing.T) { - results, err := NewScanner(options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(true)).ScanReader(context.TODO(), "k8s.yaml", strings.NewReader(` + results, err := NewScanner(rego.WithEmbeddedPolicies(true), rego.WithEmbeddedLibraries(true)).ScanReader(context.TODO(), "k8s.yaml", strings.NewReader(` --- --- apiVersion: v1 @@ -387,9 +391,9 @@ spec: ` results, err := NewScanner( - options.ScannerWithEmbeddedPolicies(true), - options.ScannerWithEmbeddedLibraries(true), - options.ScannerWithEmbeddedLibraries(true)).ScanReader(context.TODO(), "k8s.yaml", strings.NewReader(file)) + rego.WithEmbeddedPolicies(true), + rego.WithEmbeddedLibraries(true), + rego.WithEmbeddedLibraries(true)).ScanReader(context.TODO(), "k8s.yaml", strings.NewReader(file)) require.NoError(t, err) assert.Greater(t, len(results.GetFailed()), 1) @@ -407,7 +411,7 @@ spec: func Test_FileScanWithPolicyReader(t *testing.T) { - results, err := NewScanner(options.ScannerWithPolicyReader(strings.NewReader(`package defsec + results, err := NewScanner(rego.WithPolicyReader(strings.NewReader(`package defsec deny[msg] { msg = "fail" @@ -430,7 +434,7 @@ spec: func Test_FileScanJSON(t *testing.T) { - results, err := NewScanner(options.ScannerWithPolicyReader(strings.NewReader(`package defsec + results, err := NewScanner(rego.WithPolicyReader(strings.NewReader(`package defsec deny[msg] { input.kind == "Pod" @@ -486,9 +490,8 @@ deny[msg] { func Test_FileScanWithMetadata(t *testing.T) { results, err := NewScanner( - options.ScannerWithDebug(os.Stdout), - options.ScannerWithTrace(os.Stdout), - options.ScannerWithPolicyReader(strings.NewReader(`package defsec + rego.WithTrace(os.Stdout), + rego.WithPolicyReader(strings.NewReader(`package defsec deny[msg] { input.kind == "Pod" @@ -526,9 +529,8 @@ spec: func Test_FileScanExampleWithResultFunction(t *testing.T) { results, err := NewScanner( - options.ScannerWithDebug(os.Stdout), - options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(true), - options.ScannerWithPolicyReader(strings.NewReader(`package defsec + rego.WithEmbeddedPolicies(true), rego.WithEmbeddedLibraries(true), + rego.WithPolicyReader(strings.NewReader(`package defsec import data.lib.kubernetes @@ -705,10 +707,10 @@ spec: }) scanner := NewScanner( - // options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(true), - options.ScannerWithEmbeddedLibraries(true), - options.ScannerWithPolicyDirs("policies/"), - options.ScannerWithPolicyFilesystem(srcFS), + // rego.WithEmbeddedPolicies(true), rego.WithEmbeddedLibraries(true), + rego.WithEmbeddedLibraries(true), + rego.WithPolicyDirs("policies/"), + rego.WithPolicyFilesystem(srcFS), ) results, err := scanner.ScanFS(context.TODO(), srcFS, "test/KSV001") require.NoError(t, err) diff --git a/pkg/iac/scanners/options/parser.go b/pkg/iac/scanners/options/parser.go deleted file mode 100644 index 65ec41bb825d..000000000000 --- a/pkg/iac/scanners/options/parser.go +++ /dev/null @@ -1,23 +0,0 @@ -package options - -import "io" - -type ConfigurableParser interface { - SetDebugWriter(io.Writer) - SetSkipRequiredCheck(bool) -} - -type ParserOption func(s ConfigurableParser) - -func ParserWithSkipRequiredCheck(skip bool) ParserOption { - return func(s ConfigurableParser) { - s.SetSkipRequiredCheck(skip) - } -} - -// ParserWithDebug specifies an io.Writer for debug logs - if not set, they are discarded -func ParserWithDebug(w io.Writer) ParserOption { - return func(s ConfigurableParser) { - s.SetDebugWriter(w) - } -} diff --git a/pkg/iac/scanners/options/scanner.go b/pkg/iac/scanners/options/scanner.go index 8e79b0c4a185..a9561d95130a 100644 --- a/pkg/iac/scanners/options/scanner.go +++ b/pkg/iac/scanners/options/scanner.go @@ -1,124 +1,26 @@ package options import ( - "io" - "io/fs" - "github.com/aquasecurity/trivy/pkg/iac/framework" ) type ConfigurableScanner interface { - SetDebugWriter(io.Writer) - SetTraceWriter(io.Writer) - SetPerResultTracingEnabled(bool) - SetPolicyDirs(...string) - SetDataDirs(...string) - SetPolicyNamespaces(...string) - SetSkipRequiredCheck(bool) - SetPolicyReaders([]io.Reader) - SetPolicyFilesystem(fs.FS) - SetDataFilesystem(fs.FS) - SetUseEmbeddedPolicies(bool) SetFrameworks(frameworks []framework.Framework) - SetSpec(spec string) SetRegoOnly(regoOnly bool) - SetRegoErrorLimit(limit int) - SetUseEmbeddedLibraries(bool) SetIncludeDeprecatedChecks(bool) } -type ScannerOption func(s ConfigurableScanner) - -func ScannerWithFrameworks(frameworks ...framework.Framework) ScannerOption { - return func(s ConfigurableScanner) { - s.SetFrameworks(frameworks) - } -} - -func ScannerWithSpec(spec string) ScannerOption { - return func(s ConfigurableScanner) { - s.SetSpec(spec) - } -} - -func ScannerWithPolicyReader(readers ...io.Reader) ScannerOption { - return func(s ConfigurableScanner) { - s.SetPolicyReaders(readers) - } -} - -// ScannerWithDebug specifies an io.Writer for debug logs - if not set, they are discarded -func ScannerWithDebug(w io.Writer) ScannerOption { - return func(s ConfigurableScanner) { - s.SetDebugWriter(w) - } -} - -func ScannerWithEmbeddedPolicies(embedded bool) ScannerOption { - return func(s ConfigurableScanner) { - s.SetUseEmbeddedPolicies(embedded) - } -} - -func ScannerWithEmbeddedLibraries(enabled bool) ScannerOption { - return func(s ConfigurableScanner) { - s.SetUseEmbeddedLibraries(enabled) - } -} - func ScannerWithIncludeDeprecatedChecks(enabled bool) ScannerOption { return func(s ConfigurableScanner) { s.SetIncludeDeprecatedChecks(enabled) } } -// ScannerWithTrace specifies an io.Writer for trace logs (mainly rego tracing) - if not set, they are discarded -func ScannerWithTrace(w io.Writer) ScannerOption { - return func(s ConfigurableScanner) { - s.SetTraceWriter(w) - } -} - -func ScannerWithPerResultTracing(enabled bool) ScannerOption { - return func(s ConfigurableScanner) { - s.SetPerResultTracingEnabled(enabled) - } -} - -func ScannerWithPolicyDirs(paths ...string) ScannerOption { - return func(s ConfigurableScanner) { - s.SetPolicyDirs(paths...) - } -} - -func ScannerWithDataDirs(paths ...string) ScannerOption { - return func(s ConfigurableScanner) { - s.SetDataDirs(paths...) - } -} - -// ScannerWithPolicyNamespaces - namespaces which indicate rego policies containing enforced rules -func ScannerWithPolicyNamespaces(namespaces ...string) ScannerOption { - return func(s ConfigurableScanner) { - s.SetPolicyNamespaces(namespaces...) - } -} - -func ScannerWithSkipRequiredCheck(skip bool) ScannerOption { - return func(s ConfigurableScanner) { - s.SetSkipRequiredCheck(skip) - } -} - -func ScannerWithPolicyFilesystem(f fs.FS) ScannerOption { - return func(s ConfigurableScanner) { - s.SetPolicyFilesystem(f) - } -} +type ScannerOption func(s ConfigurableScanner) -func ScannerWithDataFilesystem(f fs.FS) ScannerOption { +func ScannerWithFrameworks(frameworks ...framework.Framework) ScannerOption { return func(s ConfigurableScanner) { - s.SetDataFilesystem(f) + s.SetFrameworks(frameworks) } } @@ -127,9 +29,3 @@ func ScannerWithRegoOnly(regoOnly bool) ScannerOption { s.SetRegoOnly(regoOnly) } } - -func ScannerWithRegoErrorLimits(limit int) ScannerOption { - return func(s ConfigurableScanner) { - s.SetRegoErrorLimit(limit) - } -} diff --git a/pkg/iac/scanners/terraform/executor/executor.go b/pkg/iac/scanners/terraform/executor/executor.go index 88dc1fa9801c..452e47dcc8de 100644 --- a/pkg/iac/scanners/terraform/executor/executor.go +++ b/pkg/iac/scanners/terraform/executor/executor.go @@ -5,10 +5,10 @@ import ( "runtime" "sort" + "github.com/samber/lo" "github.com/zclconf/go-cty/cty" adapter "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform" - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/ignore" "github.com/aquasecurity/trivy/pkg/iac/rego" @@ -16,22 +16,26 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/terraform" "github.com/aquasecurity/trivy/pkg/iac/types" + ruleTypes "github.com/aquasecurity/trivy/pkg/iac/types/rules" + "github.com/aquasecurity/trivy/pkg/log" ) // Executor scans HCL blocks by running all registered rules against them type Executor struct { - workspaceName string - debug debug.Logger - resultsFilters []func(scan.Results) scan.Results - regoScanner *rego.Scanner - regoOnly bool - frameworks []framework.Framework + workspaceName string + logger *log.Logger + resultsFilters []func(scan.Results) scan.Results + regoScanner *rego.Scanner + regoOnly bool + includeDeprecatedChecks bool + frameworks []framework.Framework } // New creates a new Executor func New(options ...Option) *Executor { s := &Executor{ regoOnly: false, + logger: log.WithPrefix("terraform executor"), } for _, option := range options { option(s) @@ -41,31 +45,36 @@ func New(options ...Option) *Executor { func (e *Executor) Execute(modules terraform.Modules) (scan.Results, error) { - e.debug.Log("Adapting modules...") + e.logger.Debug("Adapting modules...") infra := adapter.Adapt(modules) - e.debug.Log("Adapted %d module(s) into defsec state data.", len(modules)) + e.logger.Debug("Adapted module(s) into state data.", log.Int("count", len(modules))) threads := runtime.NumCPU() if threads > 1 { threads-- } - e.debug.Log("Using max routines of %d", threads) + e.logger.Debug("Using max routines", log.Int("count", threads)) - registeredRules := rules.GetRegistered(e.frameworks...) - e.debug.Log("Initialized %d rule(s).", len(registeredRules)) + registeredRules := lo.Filter(rules.GetRegistered(e.frameworks...), func(r ruleTypes.RegisteredRule, _ int) bool { + if !e.includeDeprecatedChecks && r.Deprecated { + return false // skip deprecated checks + } + + return true + }) + e.logger.Debug("Initialized Go check(s).", log.Int("count", len(registeredRules))) pool := NewPool(threads, registeredRules, modules, infra, e.regoScanner, e.regoOnly) - e.debug.Log("Created pool with %d worker(s) to apply rules.", threads) results, err := pool.Run() if err != nil { return nil, err } - e.debug.Log("Finished applying rules.") + e.logger.Debug("Finished applying rules.") - e.debug.Log("Applying ignores...") + e.logger.Debug("Applying ignores...") var ignores ignore.Rules for _, module := range modules { ignores = append(ignores, module.Ignores()...) @@ -79,7 +88,10 @@ func (e *Executor) Execute(modules terraform.Modules) (scan.Results, error) { results.Ignore(ignores, ignorers) for _, ignored := range results.GetIgnored() { - e.debug.Log("Ignored '%s' at '%s'.", ignored.Rule().LongID(), ignored.Range()) + e.logger.Info("Ignore finding", + log.String("rule", ignored.Rule().LongID()), + log.String("range", ignored.Range().String()), + ) } results = e.filterResults(results) @@ -91,11 +103,12 @@ func (e *Executor) Execute(modules terraform.Modules) (scan.Results, error) { func (e *Executor) filterResults(results scan.Results) scan.Results { if len(e.resultsFilters) > 0 && len(results) > 0 { before := len(results.GetIgnored()) - e.debug.Log("Applying %d results filters to %d results...", len(results), before) + e.logger.Debug("Applying results filters...") for _, filter := range e.resultsFilters { results = filter(results) } - e.debug.Log("Filtered out %d results.", len(results.GetIgnored())-before) + e.logger.Debug("Applied results filters.", + log.Int("count", len(results.GetIgnored())-before)) } return results @@ -122,26 +135,23 @@ func ignoreByParams(params map[string]string, modules terraform.Modules, m *type if block == nil { return true } - for key, val := range params { - attr, _ := block.GetNestedAttribute(key) - if attr.IsNil() || !attr.Value().IsKnown() { - return false - } - switch attr.Type() { + for key, param := range params { + val := block.GetValueByPath(key) + switch val.Type() { case cty.String: - if !attr.Equals(val) { + if val.AsString() != param { return false } case cty.Number: - bf := attr.Value().AsBigFloat() + bf := val.AsBigFloat() f64, _ := bf.Float64() comparableInt := fmt.Sprintf("%d", int(f64)) comparableFloat := fmt.Sprintf("%f", f64) - if val != comparableInt && val != comparableFloat { + if param != comparableInt && param != comparableFloat { return false } case cty.Bool: - if fmt.Sprintf("%t", attr.IsTrue()) != val { + if fmt.Sprintf("%t", val.True()) != param { return false } default: diff --git a/pkg/iac/scanners/terraform/executor/option.go b/pkg/iac/scanners/terraform/executor/option.go index a58d72867b54..70dd1a9520c3 100644 --- a/pkg/iac/scanners/terraform/executor/option.go +++ b/pkg/iac/scanners/terraform/executor/option.go @@ -1,9 +1,6 @@ package executor import ( - "io" - - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scan" @@ -23,12 +20,6 @@ func OptionWithResultsFilter(f func(scan.Results) scan.Results) Option { } } -func OptionWithDebugWriter(w io.Writer) Option { - return func(s *Executor) { - s.debug = debug.New(w, "terraform", "executor") - } -} - func OptionWithWorkspaceName(workspaceName string) Option { return func(s *Executor) { s.workspaceName = workspaceName @@ -46,3 +37,9 @@ func OptionWithRegoOnly(regoOnly bool) Option { e.regoOnly = regoOnly } } + +func OptionWithIncludeDeprecatedChecks(b bool) Option { + return func(e *Executor) { + e.includeDeprecatedChecks = b + } +} diff --git a/pkg/iac/scanners/terraform/fs_test.go b/pkg/iac/scanners/terraform/fs_test.go index 79b0844fe983..fb5448873607 100644 --- a/pkg/iac/scanners/terraform/fs_test.go +++ b/pkg/iac/scanners/terraform/fs_test.go @@ -8,14 +8,13 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/iac/rego" ) func Test_OS_FS(t *testing.T) { s := New( - options.ScannerWithDebug(os.Stderr), - options.ScannerWithEmbeddedPolicies(true), - options.ScannerWithEmbeddedLibraries(true), + rego.WithEmbeddedPolicies(true), + rego.WithEmbeddedLibraries(true), ) results, err := s.ScanFS(context.TODO(), os.DirFS("testdata"), "fail") require.NoError(t, err) diff --git a/pkg/iac/scanners/terraform/ignore_test.go b/pkg/iac/scanners/terraform/ignore_test.go index 6183469d5296..a42abc323d31 100644 --- a/pkg/iac/scanners/terraform/ignore_test.go +++ b/pkg/iac/scanners/terraform/ignore_test.go @@ -11,6 +11,7 @@ import ( "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/providers" + "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/rules" "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" @@ -442,6 +443,21 @@ resource "bad" "my-rule" { } } } +`, + assertLength: 0, + }, + { + name: "ignore by indexed dynamic block value", + inputOptions: ` +// trivy:ignore:*[secure_settings.0.enabled=false] +resource "bad" "my-rule" { + dynamic "secure_settings" { + for_each = ["false", "true"] + content { + enabled = secure_settings.value + } + } +} `, assertLength: 0, }, @@ -604,6 +620,154 @@ data "aws_iam_policy_document" "test_policy" { resources = ["*"] # trivy:ignore:aws-iam-enforce-mfa } } +`, + assertLength: 0, + }, + { + name: "ignore by each.value", + inputOptions: ` +// trivy:ignore:*[each.value=false] +resource "bad" "my-rule" { + for_each = toset(["false", "true", "false"]) + secure = each.value +} +`, + assertLength: 0, + }, + { + name: "ignore by nested each.value", + inputOptions: ` +locals { + vms = [ + { + ip_address = "10.0.0.1" + name = "vm-1" + }, + { + ip_address = "10.0.0.2" + name = "vm-2" + } + ] +} +// trivy:ignore:*[each.value.name=vm-2] +resource "bad" "my-rule" { + secure = false + for_each = { for vm in local.vms : vm.name => vm } + ip_address = each.value.ip_address +} +`, + assertLength: 1, + }, + { + name: "ignore resource with `count` meta-argument", + inputOptions: ` +// trivy:ignore:*[count.index=1] +resource "bad" "my-rule" { + count = 2 + secure = false +} +`, + assertLength: 1, + }, + { + name: "invalid index when accessing blocks", + inputOptions: ` +// trivy:ignore:*[ingress.99.port=9090] +// trivy:ignore:*[ingress.-10.port=9090] +resource "bad" "my-rule" { + secure = false + dynamic "ingress" { + for_each = [8080, 9090] + content { + port = ingress.value + } + } +} +`, + assertLength: 1, + }, + { + name: "ignore by list value", + inputOptions: ` +#trivy:ignore:*[someattr.1.Environment=dev] +resource "bad" "my-rule" { + secure = false + someattr = [ + { + Environment = "prod" + }, + { + Environment = "dev" + } + ] +} +`, + assertLength: 0, + }, + { + name: "ignore by list value with invalid index", + inputOptions: ` +#trivy:ignore:*[someattr.-2.Environment=dev] +resource "bad" "my-rule" { + secure = false + someattr = [ + { + Environment = "prod" + }, + { + Environment = "dev" + } + ] +} +`, + assertLength: 1, + }, + { + name: "ignore by object value", + inputOptions: ` +#trivy:ignore:*[tags.Environment=dev] +resource "bad" "my-rule" { + secure = false + tags = { + Environment = "dev" + } +} +`, + assertLength: 0, + }, + { + name: "ignore by object value in block", + inputOptions: ` +#trivy:ignore:*[someblock.tags.Environment=dev] +resource "bad" "my-rule" { + secure = false + someblock { + tags = { + Environment = "dev" + } + } +} +`, + assertLength: 0, + }, + { + name: "ignore by list value in map", + inputOptions: ` +variable "testvar" { + type = map(list(string)) + default = { + server1 = ["web", "dev"] + server2 = ["prod"] + } +} + +#trivy:ignore:*[someblock.someattr.server1.1=dev] +resource "bad" "my-rule" { + secure = false + someblock { + someattr = var.testvar + } +} `, assertLength: 0, }, @@ -793,11 +957,11 @@ deny[res] { }` localScanner := New( - options.ScannerWithEmbeddedPolicies(false), - options.ScannerWithEmbeddedLibraries(true), + rego.WithEmbeddedPolicies(false), + rego.WithEmbeddedLibraries(true), options.ScannerWithRegoOnly(true), - options.ScannerWithPolicyNamespaces("user"), - options.ScannerWithPolicyReader(strings.NewReader(check)), + rego.WithPolicyNamespaces("user"), + rego.WithPolicyReader(strings.NewReader(check)), ScannerWithDownloadsAllowed(false), ScannerWithSkipCachedModules(true), ) diff --git a/pkg/iac/scanners/terraform/module_test.go b/pkg/iac/scanners/terraform/module_test.go index 5469204e741f..d0d289a9d562 100644 --- a/pkg/iac/scanners/terraform/module_test.go +++ b/pkg/iac/scanners/terraform/module_test.go @@ -1,10 +1,7 @@ package terraform import ( - "bytes" "context" - "fmt" - "os" "testing" "github.com/stretchr/testify/require" @@ -14,7 +11,6 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/providers" "github.com/aquasecurity/trivy/pkg/iac/rules" "github.com/aquasecurity/trivy/pkg/iac/scan" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/executor" "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser" "github.com/aquasecurity/trivy/pkg/iac/severity" @@ -86,9 +82,7 @@ resource "problem" "uhoh" { `, }) - debug := bytes.NewBuffer([]byte{}) - - p := parser.New(fs, "", parser.OptionStopOnHCLError(true), options.ParserWithDebug(debug)) + p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) err := p.ParseFS(context.TODO(), "project") require.NoError(t, err) modules, _, err := p.EvaluateAll(context.TODO()) @@ -98,9 +92,6 @@ resource "problem" "uhoh" { require.NoError(t, err) testutil.AssertRuleFound(t, badRule.LongID(), results, "") - if t.Failed() { - fmt.Println(debug.String()) - } } func Test_ProblemInModuleInSiblingDir(t *testing.T) { @@ -293,7 +284,7 @@ resource "problem" "uhoh" { `, }) - p := parser.New(fs, "", parser.OptionStopOnHCLError(true), options.ParserWithDebug(os.Stderr)) + p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) err := p.ParseFS(context.TODO(), "project") require.NoError(t, err) modules, _, err := p.EvaluateAll(context.TODO()) diff --git a/pkg/iac/scanners/terraform/options.go b/pkg/iac/scanners/terraform/options.go index f5a0d2223534..5a665a05638f 100644 --- a/pkg/iac/scanners/terraform/options.go +++ b/pkg/iac/scanners/terraform/options.go @@ -14,7 +14,7 @@ type ConfigurableTerraformScanner interface { options.ConfigurableScanner SetForceAllDirs(bool) AddExecutorOptions(options ...executor.Option) - AddParserOptions(options ...options.ParserOption) + AddParserOptions(options ...parser.Option) } func ScannerWithTFVarsPaths(paths ...string) options.ScannerOption { @@ -84,3 +84,19 @@ func ScannerWithConfigsFileSystem(fsys fs.FS) options.ScannerOption { } } } + +func ScannerWithSkipFiles(files []string) options.ScannerOption { + return func(s options.ConfigurableScanner) { + if tf, ok := s.(ConfigurableTerraformScanner); ok { + tf.AddParserOptions(parser.OptionWithSkipFiles(files)) + } + } +} + +func ScannerWithSkipDirs(dirs []string) options.ScannerOption { + return func(s options.ConfigurableScanner) { + if tf, ok := s.(ConfigurableTerraformScanner); ok { + tf.AddParserOptions(parser.OptionWithSkipDirs(dirs)) + } + } +} diff --git a/pkg/iac/scanners/terraform/parser/evaluator.go b/pkg/iac/scanners/terraform/parser/evaluator.go index bf3e4f4ffc13..809498f35c63 100644 --- a/pkg/iac/scanners/terraform/parser/evaluator.go +++ b/pkg/iac/scanners/terraform/parser/evaluator.go @@ -13,11 +13,11 @@ import ( "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/convert" - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/ignore" "github.com/aquasecurity/trivy/pkg/iac/terraform" tfcontext "github.com/aquasecurity/trivy/pkg/iac/terraform/context" "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" ) const ( @@ -25,6 +25,7 @@ const ( ) type evaluator struct { + logger *log.Logger filesystem fs.FS ctx *tfcontext.Context blocks terraform.Blocks @@ -35,7 +36,6 @@ type evaluator struct { moduleName string ignores ignore.Rules parentParser *Parser - debug debug.Logger allowDownloads bool skipCachedModules bool } @@ -52,7 +52,7 @@ func newEvaluator( moduleMetadata *modulesMetadata, workspace string, ignores ignore.Rules, - logger debug.Logger, + logger *log.Logger, allowDownloads bool, skipCachedModules bool, ) *evaluator { @@ -84,7 +84,7 @@ func newEvaluator( inputVars: inputVars, moduleMetadata: moduleMetadata, ignores: ignores, - debug: logger, + logger: logger, allowDownloads: allowDownloads, skipCachedModules: skipCachedModules, } @@ -96,9 +96,8 @@ func (e *evaluator) evaluateStep() { e.ctx.Set(e.getValuesByBlockType("locals"), "local") e.ctx.Set(e.getValuesByBlockType("provider"), "provider") - resources := e.getValuesByBlockType("resource") - for key, resource := range resources.AsValueMap() { - e.ctx.Set(resource, key) + for typ, resource := range e.getResources() { + e.ctx.Set(resource, typ) } e.ctx.Set(e.getValuesByBlockType("data"), "data") @@ -115,20 +114,24 @@ func (e *evaluator) exportOutputs() cty.Value { continue } data[block.Label()] = attr.Value() - e.debug.Log("Added module output %s=%s.", block.Label(), attr.Value().GoString()) + e.logger.Debug( + "Added module output", + log.String("block", block.Label()), + log.String("value", attr.Value().GoString()), + ) } return cty.ObjectVal(data) } func (e *evaluator) EvaluateAll(ctx context.Context) (terraform.Modules, map[string]fs.FS) { - fsKey := types.CreateFSKey(e.filesystem) - e.debug.Log("Filesystem key is '%s'", fsKey) + e.logger.Debug("Starting module evaluation...", log.String("path", e.modulePath)) - fsMap := make(map[string]fs.FS) - fsMap[fsKey] = e.filesystem + fsKey := types.CreateFSKey(e.filesystem) + fsMap := map[string]fs.FS{ + fsKey: e.filesystem, + } - e.debug.Log("Starting module evaluation...") e.evaluateSteps() // expand out resources and modules via count, for-each and dynamic @@ -136,34 +139,49 @@ func (e *evaluator) EvaluateAll(ctx context.Context) (terraform.Modules, map[str e.blocks = e.expandBlocks(e.blocks) e.blocks = e.expandBlocks(e.blocks) - e.debug.Log("Starting submodule evaluation...") + submodules := e.evaluateSubmodules(ctx, fsMap) + + e.logger.Debug("Starting post-submodules evaluation...") + e.evaluateSteps() + + e.logger.Debug("Module evaluation complete.") + rootModule := terraform.NewModule(e.projectRootPath, e.modulePath, e.blocks, e.ignores) + return append(terraform.Modules{rootModule}, submodules...), fsMap +} + +func (e *evaluator) evaluateSubmodules(ctx context.Context, fsMap map[string]fs.FS) terraform.Modules { submodules := e.loadSubmodules(ctx) + if len(submodules) == 0 { + return nil + } + + e.logger.Debug("Starting submodules evaluation...") + for i := 0; i < maxContextIterations; i++ { changed := false for _, sm := range submodules { changed = changed || e.evaluateSubmodule(ctx, sm) } if !changed { - e.debug.Log("All submodules are evaluated at i=%d", i) + e.logger.Debug("All submodules are evaluated", log.Int("loop", i)) break } } - e.debug.Log("Starting post-submodule evaluation...") + e.logger.Debug("Starting post-submodule evaluation...") e.evaluateSteps() var modules terraform.Modules for _, sm := range submodules { modules = append(modules, sm.modules...) - fsMap = lo.Assign(fsMap, sm.fsMap) + for k, v := range sm.fsMap { + fsMap[k] = v + } } - e.debug.Log("Finished processing %d submodule(s).", len(modules)) - - e.debug.Log("Module evaluation complete.") - rootModule := terraform.NewModule(e.projectRootPath, e.modulePath, e.blocks, e.ignores) - return append(terraform.Modules{rootModule}, modules...), fsMap + e.logger.Debug("Finished processing submodule(s).", log.Int("count", len(modules))) + return modules } type submodule struct { @@ -182,7 +200,7 @@ func (e *evaluator) loadSubmodules(ctx context.Context) []*submodule { if errors.Is(err, ErrNoFiles) { continue } else if err != nil { - e.debug.Log("Failed to load submodule '%s': %s.", definition.Name, err) + e.logger.Error("Failed to load submodule", log.String("name", definition.Name), log.Err(err)) continue } @@ -200,12 +218,12 @@ func (e *evaluator) evaluateSubmodule(ctx context.Context, sm *submodule) bool { inputVars := sm.definition.inputVars() if len(sm.modules) > 0 { if reflect.DeepEqual(inputVars, sm.lastState) { - e.debug.Log("Submodule %s inputs unchanged", sm.definition.Name) + e.logger.Debug("Submodule inputs unchanged", log.String("name", sm.definition.Name)) return false } } - e.debug.Log("Evaluating submodule %s", sm.definition.Name) + e.logger.Debug("Evaluating submodule", log.String("name", sm.definition.Name)) sm.eval.inputVars = inputVars sm.modules, sm.fsMap = sm.eval.EvaluateAll(ctx) outputs := sm.eval.exportOutputs() @@ -224,10 +242,12 @@ func (e *evaluator) evaluateSteps() { var lastContext hcl.EvalContext for i := 0; i < maxContextIterations; i++ { + e.logger.Debug("Starting iteration", log.Int("iteration", i)) e.evaluateStep() // if ctx matches the last evaluation, we can bail, nothing left to resolve if i > 0 && reflect.DeepEqual(lastContext.Variables, e.ctx.Inner().Variables) { + e.logger.Debug("Context unchanged", log.Int("iteration", i)) break } if len(e.ctx.Inner().Variables) != len(lastContext.Variables) { @@ -282,6 +302,7 @@ func isBlockSupportsForEachMetaArgument(block *terraform.Block) bool { } func (e *evaluator) expandBlockForEaches(blocks terraform.Blocks, isDynamic bool) terraform.Blocks { + var forEachFiltered terraform.Blocks for _, block := range blocks { @@ -296,6 +317,10 @@ func (e *evaluator) expandBlockForEaches(blocks terraform.Blocks, isDynamic bool forEachVal := forEachAttr.Value() if forEachVal.IsNull() || !forEachVal.IsKnown() || !forEachAttr.IsIterable() { + e.logger.Error(`Failed to expand block. Invalid "for-each" argument. Must be known and iterable.`, + log.String("block", block.FullName()), + log.String("value", forEachVal.GoString()), + ) continue } @@ -309,9 +334,12 @@ func (e *evaluator) expandBlockForEaches(blocks terraform.Blocks, isDynamic bool // instances are identified by a map key (or set member) from the value provided to for_each idx, err := convert.Convert(key, cty.String) if err != nil { - e.debug.Log( - `Invalid "for-each" argument: map key (or set value) is not a string, but %s`, - key.Type().FriendlyName(), + e.logger.Error( + `Failed to expand block. Invalid "for-each" argument: map key (or set value) is not a string`, + log.String("block", block.FullName()), + log.String("key", key.GoString()), + log.String("value", val.GoString()), + log.Err(err), ) return } @@ -323,28 +351,46 @@ func (e *evaluator) expandBlockForEaches(blocks terraform.Blocks, isDynamic bool !forEachVal.Type().IsMapType() && !isDynamic { stringVal, err := convert.Convert(val, cty.String) if err != nil { - e.debug.Log("Failed to convert for-each arg %v to string", val) + e.logger.Error( + "Failed to expand block. Invalid 'for-each' argument: value is not a string", + log.String("block", block.FullName()), + log.String("key", idx.AsString()), + log.String("value", val.GoString()), + log.Err(err), + ) return } idx = stringVal } clone := block.Clone(idx) - ctx := clone.Context() - e.copyVariables(block, clone) - ctx.SetByDot(idx, "each.key") - ctx.SetByDot(val, "each.value") - ctx.Set(idx, block.TypeLabel(), "key") - ctx.Set(val, block.TypeLabel(), "value") + eachObj := cty.ObjectVal(map[string]cty.Value{ + "key": idx, + "value": val, + }) + + ctx.Set(eachObj, "each") + ctx.Set(eachObj, block.TypeLabel()) + + if isDynamic { + if iterAttr := block.GetAttribute("iterator"); iterAttr.IsNotNil() { + refs := iterAttr.AllReferences() + if len(refs) == 1 { + ctx.Set(idx, refs[0].TypeLabel(), "key") + ctx.Set(val, refs[0].TypeLabel(), "value") + } else { + e.logger.Debug("Ignoring iterator attribute in dynamic block, expected one reference", + log.Int("refs", len(refs))) + } + } + } forEachFiltered = append(forEachFiltered, clone) - values := clone.Values() - clones[idx.AsString()] = values - e.ctx.SetByDot(values, clone.GetMetadata().Reference()) + clones[idx.AsString()] = clone.Values() }) metadata := block.GetMetadata() @@ -355,7 +401,10 @@ func (e *evaluator) expandBlockForEaches(blocks terraform.Blocks, isDynamic bool // So we must replace the old resource with a map with the attributes of the resource. e.ctx.Replace(cty.ObjectVal(clones), metadata.Reference()) } - e.debug.Log("Expanded block '%s' into %d clones via 'for_each' attribute.", block.LocalName(), len(clones)) + e.logger.Debug("Expanded block into clones via 'for_each' attribute.", + log.String("block", block.FullName()), + log.Int("clones", len(clones)), + ) } return forEachFiltered @@ -397,7 +446,11 @@ func (e *evaluator) expandBlockCounts(blocks terraform.Blocks) terraform.Blocks } else { e.ctx.SetByDot(cty.TupleVal(clones), metadata.Reference()) } - e.debug.Log("Expanded block '%s' into %d clones via 'count' attribute.", block.LocalName(), len(clones)) + e.logger.Debug( + "Expanded block into clones via 'count' attribute.", + log.String("block", block.FullName()), + log.Int("clones", len(clones)), + ) } return countFiltered @@ -422,11 +475,12 @@ func (e *evaluator) copyVariables(from, to *terraform.Block) { return } - srcValue := e.ctx.Root().Get(fromBase, fromRel) + rootCtx := e.ctx.Root() + srcValue := rootCtx.Get(fromBase, fromRel) if srcValue == cty.NilVal { return } - e.ctx.Root().Set(srcValue, fromBase, toRel) + rootCtx.Set(srcValue, fromBase, toRel) } func (e *evaluator) evaluateVariable(b *terraform.Block) (cty.Value, error) { @@ -518,7 +572,7 @@ func (e *evaluator) getValuesByBlockType(blockType string) cty.Value { continue } values[b.Label()] = b.Values() - case "resource", "data": + case "data": if len(b.Labels()) < 2 { continue } @@ -541,3 +595,28 @@ func (e *evaluator) getValuesByBlockType(blockType string) cty.Value { return cty.ObjectVal(values) } + +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 { + continue + } + + val, exists := values[b.Labels()[0]] + if !exists { + val = make(map[string]cty.Value) + values[b.Labels()[0]] = val + } + val[b.Labels()[1]] = b.Values() + } + + return lo.MapValues(values, func(v map[string]cty.Value, _ string) cty.Value { + return cty.ObjectVal(v) + }) +} diff --git a/pkg/iac/scanners/terraform/parser/load_module.go b/pkg/iac/scanners/terraform/parser/load_module.go index 0bd6a6395936..78ebe3430b4e 100644 --- a/pkg/iac/scanners/terraform/parser/load_module.go +++ b/pkg/iac/scanners/terraform/parser/load_module.go @@ -11,6 +11,7 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser/resolvers" "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/aquasecurity/trivy/pkg/log" ) type ModuleDefinition struct { @@ -42,11 +43,11 @@ func (e *evaluator) loadModules(ctx context.Context) []*ModuleDefinition { } moduleDefinition, err := e.loadModule(ctx, moduleBlock) if err != nil { - e.debug.Log("Failed to load module %q. Maybe try 'terraform init'?", err) + e.logger.Error("Failed to load module. Maybe try 'terraform init'?", log.Err(err)) continue } - e.debug.Log("Loaded module %q from %q.", moduleDefinition.Name, moduleDefinition.Path) + e.logger.Debug("Loaded module", log.String("name", moduleDefinition.Name), log.FilePath(moduleDefinition.Path)) moduleDefinitions = append(moduleDefinitions, moduleDefinition) } @@ -77,7 +78,7 @@ func (e *evaluator) loadModule(ctx context.Context, b *terraform.Block) (*Module } if def, err := e.loadModuleFromTerraformCache(ctx, b, source); err == nil { - e.debug.Log("found module '%s' in .terraform/modules", source) + e.logger.Debug("Using module from Terraform cache .terraform/modules", log.String("source", source)) return def, nil } @@ -110,7 +111,11 @@ func (e *evaluator) loadModuleFromTerraformCache(ctx context.Context, b *terrafo } } - e.debug.Log("Module '%s' resolved to path '%s' in filesystem '%s' using modules.json", b.FullName(), modulePath, e.filesystem) + e.logger.Debug("Module resolved using modules.json", + log.String("block", b.FullName()), + log.String("source", source), + log.String("modulePath", modulePath), + ) moduleParser := e.parentParser.newModuleParser(e.filesystem, source, modulePath, b.Label(), b) if err := moduleParser.ParseFS(ctx, modulePath); err != nil { return nil, err @@ -126,7 +131,7 @@ func (e *evaluator) loadModuleFromTerraformCache(ctx context.Context, b *terrafo func (e *evaluator) loadExternalModule(ctx context.Context, b *terraform.Block, source string) (*ModuleDefinition, error) { - e.debug.Log("locating non-initialized module '%s'...", source) + e.logger.Debug("Locating non-initialized module", log.String("source", source)) version := b.GetAttribute("version").AsStringValueOrDefault("", b).Value() opt := resolvers.Options{ @@ -137,7 +142,7 @@ func (e *evaluator) loadExternalModule(ctx context.Context, b *terraform.Block, WorkingDir: e.projectRootPath, Name: b.FullName(), ModulePath: e.modulePath, - DebugLogger: e.debug.Extend("resolver"), + Logger: log.WithPrefix("module resolver"), AllowDownloads: e.allowDownloads, SkipCache: e.skipCachedModules, } @@ -147,7 +152,12 @@ func (e *evaluator) loadExternalModule(ctx context.Context, b *terraform.Block, return nil, err } prefix = path.Join(e.parentParser.moduleSource, prefix) - e.debug.Log("Module '%s' resolved to path '%s' in filesystem '%s' with prefix '%s'", b.FullName(), downloadPath, filesystem, prefix) + e.logger.Debug("Module resolved", + log.String("block", b.FullName()), + log.String("source", source), + log.String("prefix", prefix), + log.FilePath(downloadPath), + ) moduleParser := e.parentParser.newModuleParser(filesystem, prefix, downloadPath, b.Label(), b) if err := moduleParser.ParseFS(ctx, downloadPath); err != nil { return nil, err diff --git a/pkg/iac/scanners/terraform/parser/module_retrieval.go b/pkg/iac/scanners/terraform/parser/module_retrieval.go index 165f64eef1cb..cc374b68eeb7 100644 --- a/pkg/iac/scanners/terraform/parser/module_retrieval.go +++ b/pkg/iac/scanners/terraform/parser/module_retrieval.go @@ -6,6 +6,7 @@ import ( "io/fs" "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser/resolvers" + "github.com/aquasecurity/trivy/pkg/log" ) type ModuleResolver interface { @@ -20,12 +21,13 @@ var defaultResolvers = []ModuleResolver{ } func resolveModule(ctx context.Context, current fs.FS, opt resolvers.Options) (filesystem fs.FS, sourcePrefix, downloadPath string, err error) { - opt.Debug("Resolving module '%s' with source: '%s'...", opt.Name, opt.Source) + opt.Logger.Debug("Resolving module", + log.String("name", opt.Name), log.String("source", opt.Source)) for _, resolver := range defaultResolvers { if filesystem, prefix, path, applies, err := resolver.Resolve(ctx, current, opt); err != nil { return nil, "", "", err } else if applies { - opt.Debug("Module path is %s", path) + opt.Logger.Debug("Module resolved", log.FilePath(path)) return filesystem, prefix, path, nil } } diff --git a/pkg/iac/scanners/terraform/parser/option.go b/pkg/iac/scanners/terraform/parser/option.go index 887899496f1b..f9ada6545225 100644 --- a/pkg/iac/scanners/terraform/parser/option.go +++ b/pkg/iac/scanners/terraform/parser/option.go @@ -3,65 +3,61 @@ package parser import ( "io/fs" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/zclconf/go-cty/cty" ) -type ConfigurableTerraformParser interface { - options.ConfigurableParser - SetTFVarsPaths(...string) - SetStopOnHCLError(bool) - SetWorkspaceName(string) - SetAllowDownloads(bool) - SetSkipCachedModules(bool) - SetConfigsFS(fsys fs.FS) +type Option func(p *Parser) + +func OptionWithTFVarsPaths(paths ...string) Option { + return func(p *Parser) { + p.tfvarsPaths = paths + } } -type Option func(p ConfigurableTerraformParser) +func OptionStopOnHCLError(stop bool) Option { + return func(p *Parser) { + p.stopOnHCLError = stop + } +} + +func OptionsWithTfVars(vars map[string]cty.Value) Option { + return func(p *Parser) { + p.tfvars = vars + } +} -func OptionWithTFVarsPaths(paths ...string) options.ParserOption { - return func(p options.ConfigurableParser) { - if tf, ok := p.(ConfigurableTerraformParser); ok { - tf.SetTFVarsPaths(paths...) - } +func OptionWithWorkspaceName(workspaceName string) Option { + return func(p *Parser) { + p.workspaceName = workspaceName } } -func OptionStopOnHCLError(stop bool) options.ParserOption { - return func(p options.ConfigurableParser) { - if tf, ok := p.(ConfigurableTerraformParser); ok { - tf.SetStopOnHCLError(stop) - } +func OptionWithDownloads(allowed bool) Option { + return func(p *Parser) { + p.allowDownloads = allowed } } -func OptionWithWorkspaceName(workspaceName string) options.ParserOption { - return func(p options.ConfigurableParser) { - if tf, ok := p.(ConfigurableTerraformParser); ok { - tf.SetWorkspaceName(workspaceName) - } +func OptionWithSkipCachedModules(b bool) Option { + return func(p *Parser) { + p.skipCachedModules = b } } -func OptionWithDownloads(allowed bool) options.ParserOption { - return func(p options.ConfigurableParser) { - if tf, ok := p.(ConfigurableTerraformParser); ok { - tf.SetAllowDownloads(allowed) - } +func OptionWithConfigsFS(fsys fs.FS) Option { + return func(p *Parser) { + p.configsFS = fsys } } -func OptionWithSkipCachedModules(b bool) options.ParserOption { - return func(p options.ConfigurableParser) { - if tf, ok := p.(ConfigurableTerraformParser); ok { - tf.SetSkipCachedModules(b) - } +func OptionWithSkipFiles(files []string) Option { + return func(p *Parser) { + p.skipPaths = files } } -func OptionWithConfigsFS(fsys fs.FS) options.ParserOption { - return func(s options.ConfigurableParser) { - if p, ok := s.(ConfigurableTerraformParser); ok { - p.SetConfigsFS(fsys) - } +func OptionWithSkipDirs(dirs []string) Option { + return func(p *Parser) { + p.skipPaths = dirs } } diff --git a/pkg/iac/scanners/terraform/parser/parser.go b/pkg/iac/scanners/terraform/parser/parser.go index fa511fed54c2..695cbee4fb25 100644 --- a/pkg/iac/scanners/terraform/parser/parser.go +++ b/pkg/iac/scanners/terraform/parser/parser.go @@ -15,11 +15,11 @@ import ( "github.com/hashicorp/hcl/v2/hclparse" "github.com/zclconf/go-cty/cty" - "github.com/aquasecurity/trivy/pkg/iac/debug" + "github.com/aquasecurity/trivy/pkg/fanal/utils" "github.com/aquasecurity/trivy/pkg/iac/ignore" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/terraform" tfcontext "github.com/aquasecurity/trivy/pkg/iac/terraform/context" + "github.com/aquasecurity/trivy/pkg/log" ) type sourceFile struct { @@ -27,8 +27,6 @@ type sourceFile struct { path string } -var _ ConfigurableTerraformParser = (*Parser)(nil) - // Parser is a tool for parsing terraform templates at a given file system location type Parser struct { projectRoot string @@ -39,53 +37,22 @@ type Parser struct { moduleBlock *terraform.Block files []sourceFile tfvarsPaths []string + tfvars map[string]cty.Value stopOnHCLError bool workspaceName string underlying *hclparse.Parser children []*Parser - options []options.ParserOption - debug debug.Logger + options []Option + logger *log.Logger allowDownloads bool skipCachedModules bool fsMap map[string]fs.FS - skipRequired bool configsFS fs.FS -} - -func (p *Parser) SetDebugWriter(writer io.Writer) { - p.debug = debug.New(writer, "terraform", "parser", "<"+p.moduleName+">") -} - -func (p *Parser) SetTFVarsPaths(s ...string) { - p.tfvarsPaths = s -} - -func (p *Parser) SetStopOnHCLError(b bool) { - p.stopOnHCLError = b -} - -func (p *Parser) SetWorkspaceName(s string) { - p.workspaceName = s -} - -func (p *Parser) SetAllowDownloads(b bool) { - p.allowDownloads = b -} - -func (p *Parser) SetSkipCachedModules(b bool) { - p.skipCachedModules = b -} - -func (p *Parser) SetSkipRequiredCheck(b bool) { - p.skipRequired = b -} - -func (p *Parser) SetConfigsFS(fsys fs.FS) { - p.configsFS = fsys + skipPaths []string } // New creates a new Parser -func New(moduleFS fs.FS, moduleSource string, opts ...options.ParserOption) *Parser { +func New(moduleFS fs.FS, moduleSource string, opts ...Option) *Parser { p := &Parser{ workspaceName: "default", underlying: hclparse.NewParser(), @@ -95,6 +62,8 @@ func New(moduleFS fs.FS, moduleSource string, opts ...options.ParserOption) *Par moduleFS: moduleFS, moduleSource: moduleSource, configsFS: moduleFS, + logger: log.WithPrefix("terraform parser").With("module", "root"), + tfvars: make(map[string]cty.Value), } for _, option := range opts { @@ -109,7 +78,9 @@ 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.projectRoot = p.projectRoot + mp.skipPaths = p.skipPaths p.children = append(p.children, mp) for _, option := range p.options { option(mp) @@ -125,7 +96,7 @@ func (p *Parser) ParseFile(_ context.Context, fullPath string) error { return nil } - p.debug.Log("Parsing '%s'...", fullPath) + p.logger.Debug("Parsing", log.FilePath(fullPath)) f, err := p.moduleFS.Open(filepath.ToSlash(fullPath)) if err != nil { return err @@ -138,7 +109,7 @@ func (p *Parser) ParseFile(_ context.Context, fullPath string) error { } if dir := path.Dir(fullPath); p.projectRoot == "" { - p.debug.Log("Setting project/module root to '%s'", dir) + p.logger.Debug("Setting project/module root", log.FilePath(dir)) p.projectRoot = dir p.modulePath = dir } @@ -159,7 +130,7 @@ func (p *Parser) ParseFile(_ context.Context, fullPath string) error { path: fullPath, }) - p.debug.Log("Added file %s.", fullPath) + p.logger.Debug("Added file", log.FilePath(fullPath)) return nil } @@ -169,13 +140,13 @@ func (p *Parser) ParseFS(ctx context.Context, dir string) error { dir = path.Clean(dir) if p.projectRoot == "" { - p.debug.Log("Setting project/module root to '%s'", dir) + p.logger.Debug("Setting project/module root", log.FilePath(dir)) p.projectRoot = dir p.modulePath = dir } slashed := filepath.ToSlash(dir) - p.debug.Log("Parsing FS from '%s'", slashed) + p.logger.Debug("Parsing FS", log.FilePath(slashed)) fileInfos, err := fs.ReadDir(p.moduleFS, slashed) if err != nil { return err @@ -187,6 +158,10 @@ func (p *Parser) ParseFS(ctx context.Context, dir string) error { if info.IsDir() { continue } + if utils.SkipPath(realPath, utils.CleanSkipPaths(p.skipPaths)) { + p.logger.Debug("Skipping path based on input glob", log.FilePath(realPath), log.Any("glob", p.skipPaths)) + continue + } paths = append(paths, realPath) } sort.Strings(paths) @@ -195,7 +170,7 @@ func (p *Parser) ParseFS(ctx context.Context, dir string) error { if p.stopOnHCLError { return err } - p.debug.Log("error parsing '%s': %s", path, err) + p.logger.Error("Error parsing file", log.FilePath(path), log.Err(err)) continue } } @@ -206,10 +181,10 @@ func (p *Parser) ParseFS(ctx context.Context, dir string) error { var ErrNoFiles = errors.New("no files found") func (p *Parser) Load(ctx context.Context) (*evaluator, error) { - p.debug.Log("Evaluating module...") + p.logger.Debug("Loading module", log.String("module", p.moduleName)) if len(p.files) == 0 { - p.debug.Log("No files found, nothing to do.") + p.logger.Info("No files found, nothing to do.") return nil, ErrNoFiles } @@ -217,26 +192,43 @@ func (p *Parser) Load(ctx context.Context) (*evaluator, error) { if err != nil { return nil, err } - p.debug.Log("Read %d block(s) and %d ignore(s) for module '%s' (%d file[s])...", len(blocks), len(ignores), p.moduleName, len(p.files)) + p.logger.Debug("Read block(s) and ignore(s)", + log.Int("blocks", len(blocks)), log.Int("ignores", len(ignores))) var inputVars map[string]cty.Value - if p.moduleBlock != nil { + + switch { + case p.moduleBlock != nil: inputVars = p.moduleBlock.Values().AsValueMap() - p.debug.Log("Added %d input variables from module definition.", len(inputVars)) - } else { + p.logger.Debug("Added input variables from module definition", + log.Int("count", len(inputVars))) + case len(p.tfvars) > 0: + inputVars = p.tfvars + p.logger.Debug("Added input variables from tfvars.", log.Int("count", len(inputVars))) + default: inputVars, err = loadTFVars(p.configsFS, p.tfvarsPaths) if err != nil { return nil, err } - p.debug.Log("Added %d variables from tfvars.", len(inputVars)) + p.logger.Debug("Added input variables from tfvars", log.Int("count", len(inputVars))) + + if missingVars := missingVariableValues(blocks, inputVars); len(missingVars) > 0 { + p.logger.Warn( + "Variable values was not found in the environment or variable files. Evaluating may not work correctly.", + log.String("variables", strings.Join(missingVars, ", ")), + ) + } } modulesMetadata, metadataPath, err := loadModuleMetadata(p.moduleFS, p.projectRoot) if err != nil && !errors.Is(err, os.ErrNotExist) { - p.debug.Log("Error loading module metadata: %s.", err) + p.logger.Error("Error loading module metadata", log.Err(err)) } else if err == nil { - p.debug.Log("Loaded module metadata for %d module(s) from %q.", len(modulesMetadata.Modules), metadataPath) + p.logger.Debug("Loaded module metadata for modules", + log.FilePath(metadataPath), + log.Int("count", len(modulesMetadata.Modules)), + ) } workingDir, err := os.Getwd() @@ -244,7 +236,7 @@ func (p *Parser) Load(ctx context.Context) (*evaluator, error) { return nil, err } - p.debug.Log("Working directory for module evaluation is %q", workingDir) + p.logger.Debug("Working directory for module evaluation", log.FilePath(workingDir)) return newEvaluator( p.moduleFS, p, @@ -257,12 +249,25 @@ func (p *Parser) Load(ctx context.Context) (*evaluator, error) { modulesMetadata, p.workspaceName, ignores, - p.debug.Extend("evaluator"), + log.WithPrefix("terraform evaluator"), p.allowDownloads, p.skipCachedModules, ), nil } +func missingVariableValues(blocks terraform.Blocks, inputVars map[string]cty.Value) []string { + var missing []string + for _, varBlock := range blocks.OfType("variable") { + if varBlock.GetAttribute("default") == nil { + if _, ok := inputVars[varBlock.TypeLabel()]; !ok { + missing = append(missing, varBlock.TypeLabel()) + } + } + } + + return missing +} + func (p *Parser) EvaluateAll(ctx context.Context) (terraform.Modules, cty.Value, error) { e, err := p.Load(ctx) @@ -273,7 +278,7 @@ func (p *Parser) EvaluateAll(ctx context.Context) (terraform.Modules, cty.Value, } modules, fsMap := e.EvaluateAll(ctx) - p.debug.Log("Finished parsing module '%s'.", p.moduleName) + p.logger.Debug("Finished parsing module") p.fsMap = fsMap return modules, e.exportOutputs(), nil } @@ -295,7 +300,7 @@ func (p *Parser) readBlocks(files []sourceFile) (terraform.Blocks, ignore.Rules, if p.stopOnHCLError { return nil, nil, err } - p.debug.Log("Encountered HCL parse error: %s", err) + p.logger.Error("Encountered HCL parse error", log.FilePath(file.path), log.Err(err)) continue } for _, fileBlock := range fileBlocks { diff --git a/pkg/iac/scanners/terraform/parser/parser_test.go b/pkg/iac/scanners/terraform/parser/parser_test.go index 10232b007fd9..e3bd817748f6 100644 --- a/pkg/iac/scanners/terraform/parser/parser_test.go +++ b/pkg/iac/scanners/terraform/parser/parser_test.go @@ -1,7 +1,9 @@ package parser import ( + "bytes" "context" + "log/slog" "os" "path/filepath" "sort" @@ -13,8 +15,8 @@ import ( "github.com/zclconf/go-cty/cty" "github.com/aquasecurity/trivy/internal/testutil" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/aquasecurity/trivy/pkg/log" ) func Test_BasicParsing(t *testing.T) { @@ -172,7 +174,7 @@ output "mod_result" { `, }) - parser := New(fs, "", OptionStopOnHCLError(true), options.ParserWithDebug(os.Stderr)) + parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(context.TODO(), "code")) modules, _, err := parser.EvaluateAll(context.TODO()) @@ -1745,3 +1747,140 @@ func TestTFVarsFileDoesNotExist(t *testing.T) { _, _, err := parser.EvaluateAll(context.TODO()) assert.ErrorContains(t, err, "file does not exist") } + +func Test_OptionsWithTfVars(t *testing.T) { + fs := testutil.CreateFS(t, map[string]string{ + "main.tf": `resource "test" "this" { + foo = var.foo +} +variable "foo" {} +`}) + + parser := New(fs, "", OptionsWithTfVars( + map[string]cty.Value{ + "foo": cty.StringVal("bar"), + }, + )) + + require.NoError(t, parser.ParseFS(context.TODO(), ".")) + + modules, _, err := parser.EvaluateAll(context.TODO()) + require.NoError(t, err) + assert.Len(t, modules, 1) + + rootModule := modules[0] + + blocks := rootModule.GetResourcesByType("test") + assert.Len(t, blocks, 1) + assert.Equal(t, "bar", blocks[0].GetAttribute("foo").Value().AsString()) +} + +func TestDynamicWithIterator(t *testing.T) { + fsys := fstest.MapFS{ + "main.tf": &fstest.MapFile{ + Data: []byte(`resource "aws_s3_bucket" "this" { + dynamic versioning { + for_each = [true] + iterator = ver + + content { + enabled = ver.value + } + } +}`), + }, + } + + parser := New( + fsys, "", + OptionStopOnHCLError(true), + OptionWithDownloads(false), + ) + require.NoError(t, parser.ParseFS(context.TODO(), ".")) + + modules, _, err := parser.EvaluateAll(context.TODO()) + require.NoError(t, err) + + assert.Len(t, modules, 1) + + buckets := modules.GetResourcesByType("aws_s3_bucket") + assert.Len(t, buckets, 1) + + attr, _ := buckets[0].GetNestedAttribute("versioning.enabled") + + assert.True(t, attr.Value().True()) +} + +func Test_AWSRegionNameDefined(t *testing.T) { + + fs := testutil.CreateFS(t, map[string]string{ + "code/test.tf": ` +data "aws_region" "current" {} + +data "aws_region" "other" { + name = "us-east-2" +} + +resource "something" "blah" { + r1 = data.aws_region.current.name + r2 = data.aws_region.other.name +} +`, + }) + + parser := New(fs, "", OptionStopOnHCLError(true)) + require.NoError(t, parser.ParseFS(context.TODO(), "code")) + modules, _, err := parser.EvaluateAll(context.TODO()) + require.NoError(t, err) + require.Len(t, modules, 1) + rootModule := modules[0] + + blocks := rootModule.GetResourcesByType("something") + require.Len(t, blocks, 1) + block := blocks[0] + + r1 := block.GetAttribute("r1") + require.NotNil(t, r1) + assert.True(t, r1.IsResolvable()) + assert.Equal(t, "current-region", r1.Value().AsString()) + + r2 := block.GetAttribute("r2") + require.NotNil(t, r2) + assert.True(t, r2.IsResolvable()) + assert.Equal(t, "us-east-2", r2.Value().AsString()) +} + +func TestLogAboutMissingVariableValues(t *testing.T) { + var buf bytes.Buffer + slog.SetDefault(slog.New(log.NewHandler(&buf, nil))) + + fsys := fstest.MapFS{ + "main.tf": &fstest.MapFile{ + Data: []byte(` +variable "foo" {} + +variable "bar" { + default = "bar" +} + +variable "baz" {} +`), + }, + "main.tfvars": &fstest.MapFile{ + Data: []byte(`baz = "baz"`), + }, + } + + parser := New( + fsys, "", + OptionStopOnHCLError(true), + OptionWithTFVarsPaths("main.tfvars"), + ) + require.NoError(t, parser.ParseFS(context.TODO(), ".")) + + _, err := parser.Load(context.TODO()) + require.NoError(t, err) + + assert.Contains(t, buf.String(), "Variable values was not found in the environment or variable files.") + assert.Contains(t, buf.String(), "variables=\"foo\"") +} diff --git a/pkg/iac/scanners/terraform/parser/resolvers/cache.go b/pkg/iac/scanners/terraform/parser/resolvers/cache.go index e08c43ff3ba0..24f803f60139 100644 --- a/pkg/iac/scanners/terraform/parser/resolvers/cache.go +++ b/pkg/iac/scanners/terraform/parser/resolvers/cache.go @@ -8,6 +8,8 @@ import ( "io/fs" "os" "path/filepath" + + "github.com/aquasecurity/trivy/pkg/log" ) type cacheResolver struct{} @@ -42,27 +44,27 @@ func locateCacheDir(cacheDir string) (string, error) { func (r *cacheResolver) Resolve(_ context.Context, _ fs.FS, opt Options) (filesystem fs.FS, prefix, downloadPath string, applies bool, err error) { if opt.SkipCache { - opt.Debug("Cache is disabled.") + opt.Logger.Debug("Module caching is disabled") return nil, "", "", false, nil } cacheFS, err := locateCacheFS(opt.CacheDir) if err != nil { - opt.Debug("No cache filesystem is available on this machine.") + opt.Logger.Debug("No cache filesystem is available on this machine.", log.Err(err)) return nil, "", "", false, nil } - src := removeSubdirFromSource(opt.Source) + src, subdir := splitPackageSubdirRaw(opt.Source) key := cacheKey(src, opt.Version) - opt.Debug("Trying to resolve: %s", key) + opt.Logger.Debug("Trying to resolve module via cache", log.String("key", key)) if info, err := fs.Stat(cacheFS, filepath.ToSlash(key)); err == nil && info.IsDir() { - opt.Debug("Module '%s' resolving via cache...", opt.Name) + opt.Logger.Debug("Module resolved from cache", log.String("key", key)) cacheDir, err := locateCacheDir(opt.CacheDir) if err != nil { return nil, "", "", true, err } - return os.DirFS(filepath.Join(cacheDir, key)), opt.OriginalSource, ".", true, nil + return os.DirFS(filepath.Join(cacheDir, key)), opt.OriginalSource, subdir, true, nil } return nil, "", "", false, nil } 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 a7c6704b6708..43ad7f06b15b 100644 --- a/pkg/iac/scanners/terraform/parser/resolvers/cache_integration_test.go +++ b/pkg/iac/scanners/terraform/parser/resolvers/cache_integration_test.go @@ -21,9 +21,10 @@ func TestResolveModuleFromCache(t *testing.T) { } tests := []struct { - name string - opts resolvers.Options - firstResolver moduleResolver + name string + opts resolvers.Options + firstResolver moduleResolver + expectedSubdir string }{ { name: "registry", @@ -32,7 +33,8 @@ func TestResolveModuleFromCache(t *testing.T) { Source: "terraform-aws-modules/s3-bucket/aws", Version: "4.1.2", }, - firstResolver: resolvers.Registry, + firstResolver: resolvers.Registry, + expectedSubdir: ".", }, { name: "registry with subdir", @@ -41,7 +43,8 @@ func TestResolveModuleFromCache(t *testing.T) { Source: "terraform-aws-modules/s3-bucket/aws//modules/object", Version: "4.1.2", }, - firstResolver: resolvers.Registry, + firstResolver: resolvers.Registry, + expectedSubdir: "modules/object", }, { name: "remote", @@ -49,7 +52,8 @@ func TestResolveModuleFromCache(t *testing.T) { Name: "bucket", Source: "git::https://github.com/terraform-aws-modules/terraform-aws-s3-bucket.git?ref=v4.1.2", }, - firstResolver: resolvers.Remote, + firstResolver: resolvers.Remote, + expectedSubdir: ".", }, { name: "remote with subdir", @@ -57,7 +61,8 @@ func TestResolveModuleFromCache(t *testing.T) { Name: "object", Source: "git::https://github.com/terraform-aws-modules/terraform-aws-s3-bucket.git//modules/object?ref=v4.1.2", }, - firstResolver: resolvers.Remote, + firstResolver: resolvers.Remote, + expectedSubdir: "modules/object", }, } diff --git a/pkg/iac/scanners/terraform/parser/resolvers/local.go b/pkg/iac/scanners/terraform/parser/resolvers/local.go index eb053741c8ab..a11a294edcfb 100644 --- a/pkg/iac/scanners/terraform/parser/resolvers/local.go +++ b/pkg/iac/scanners/terraform/parser/resolvers/local.go @@ -5,6 +5,8 @@ import ( "io/fs" "path" "path/filepath" + + "github.com/aquasecurity/trivy/pkg/log" ) type localResolver struct{} @@ -17,11 +19,15 @@ func (r *localResolver) Resolve(_ context.Context, target fs.FS, opt Options) (f } joined := path.Clean(path.Join(opt.ModulePath, opt.Source)) if _, err := fs.Stat(target, filepath.ToSlash(joined)); err == nil { - opt.Debug("Module '%s' resolved locally to %s", opt.Name, joined) + opt.Logger.Debug("Module resolved locally", + log.String("name", opt.Name), log.FilePath(joined), + ) return target, "", joined, true, nil } clean := path.Clean(opt.Source) - opt.Debug("Module '%s' resolved locally to %s", opt.Name, clean) + opt.Logger.Debug("Module resolved locally", + log.String("name", opt.Name), log.FilePath(clean), + ) return target, "", clean, true, nil } diff --git a/pkg/iac/scanners/terraform/parser/resolvers/options.go b/pkg/iac/scanners/terraform/parser/resolvers/options.go index cdfde6b01bcc..73fd39689e84 100644 --- a/pkg/iac/scanners/terraform/parser/resolvers/options.go +++ b/pkg/iac/scanners/terraform/parser/resolvers/options.go @@ -3,12 +3,12 @@ package resolvers import ( "strings" - "github.com/aquasecurity/trivy/pkg/iac/debug" + "github.com/aquasecurity/trivy/pkg/log" ) type Options struct { Source, OriginalSource, Version, OriginalVersion, WorkingDir, Name, ModulePath string - DebugLogger debug.Logger + Logger *log.Logger AllowDownloads bool SkipCache bool RelativePath string @@ -23,7 +23,3 @@ func (o *Options) hasPrefix(prefixes ...string) bool { } return false } - -func (o *Options) Debug(format string, args ...any) { - o.DebugLogger.Log(format, args...) -} diff --git a/pkg/iac/scanners/terraform/parser/resolvers/registry.go b/pkg/iac/scanners/terraform/parser/resolvers/registry.go index bcdd4734974a..471416463cad 100644 --- a/pkg/iac/scanners/terraform/parser/resolvers/registry.go +++ b/pkg/iac/scanners/terraform/parser/resolvers/registry.go @@ -14,6 +14,7 @@ import ( "golang.org/x/net/idna" "github.com/aquasecurity/go-version/pkg/version" + "github.com/aquasecurity/trivy/pkg/log" ) type registryResolver struct { @@ -45,7 +46,7 @@ func (r *registryResolver) Resolve(ctx context.Context, target fs.FS, opt Option } inputVersion := opt.Version - source := removeSubdirFromSource(opt.Source) + source, _ := splitPackageSubdirRaw(opt.Source) parts := strings.Split(source, "/") if len(parts) < 3 || len(parts) > 4 { return @@ -59,9 +60,11 @@ func (r *registryResolver) Resolve(ctx context.Context, target fs.FS, opt Option token, err = getPrivateRegistryTokenFromEnvVars(hostname) if err == nil { - opt.Debug("Found a token for the registry at %s", hostname) + opt.Logger.Debug("Found a token for the registry", log.String("hostname", hostname)) } else { - opt.Debug(err.Error()) + opt.Logger.Error( + "Failed to find a token for the registry", + log.String("hostname", hostname), log.Err(err)) } } @@ -69,7 +72,8 @@ func (r *registryResolver) Resolve(ctx context.Context, target fs.FS, opt Option if opt.Version != "" { versionUrl := fmt.Sprintf("https://%s/v1/modules/%s/versions", hostname, moduleName) - opt.Debug("Requesting module versions from registry using '%s'...", versionUrl) + opt.Logger.Debug("Requesting module versions from registry using", + log.String("url", versionUrl)) req, err := http.NewRequestWithContext(ctx, http.MethodGet, versionUrl, nil) if err != nil { return nil, "", "", true, err @@ -94,7 +98,8 @@ func (r *registryResolver) Resolve(ctx context.Context, target fs.FS, opt Option if err != nil { return nil, "", "", true, err } - opt.Debug("Found version '%s' for constraint '%s'", opt.Version, inputVersion) + opt.Logger.Debug("Found module version", + log.String("version", opt.Version), log.String("constraint", inputVersion)) } var url string @@ -104,7 +109,7 @@ func (r *registryResolver) Resolve(ctx context.Context, target fs.FS, opt Option url = fmt.Sprintf("https://%s/v1/modules/%s/%s/download", hostname, moduleName, opt.Version) } - opt.Debug("Requesting module source from registry using '%s'...", url) + opt.Logger.Debug("Requesting module source from registry", log.String("url", url)) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { @@ -145,7 +150,8 @@ func (r *registryResolver) Resolve(ctx context.Context, target fs.FS, opt Option return nil, "", "", true, fmt.Errorf("no source was found for the registry at %s", hostname) } - opt.Debug("Module '%s' resolved via registry to new source: '%s'", opt.Name, opt.Source) + opt.Logger.Debug("Module resolved via registry to new source", + log.String("source", opt.Source), log.String("name", moduleName)) filesystem, prefix, downloadPath, _, err = Remote.Resolve(ctx, target, opt) if err != nil { diff --git a/pkg/iac/scanners/terraform/parser/resolvers/remote.go b/pkg/iac/scanners/terraform/parser/resolvers/remote.go index df94c775da78..467f2cee6970 100644 --- a/pkg/iac/scanners/terraform/parser/resolvers/remote.go +++ b/pkg/iac/scanners/terraform/parser/resolvers/remote.go @@ -9,6 +9,8 @@ import ( "sync/atomic" "github.com/hashicorp/go-getter" + + "github.com/aquasecurity/trivy/pkg/log" ) type remoteResolver struct { @@ -20,9 +22,9 @@ var Remote = &remoteResolver{ } func (r *remoteResolver) incrementCount(o Options) { - o.Debug("Incrementing the download counter") + atomic.CompareAndSwapInt32(&r.count, r.count, r.count+1) - o.Debug("Download counter is now %d", r.count) + o.Logger.Debug("Incrementing the download counter", log.Int("count", int(r.count))) } func (r *remoteResolver) GetDownloadCount() int { @@ -38,9 +40,9 @@ func (r *remoteResolver) Resolve(ctx context.Context, _ fs.FS, opt Options) (fil return nil, "", "", false, nil } - src := removeSubdirFromSource(opt.OriginalSource) + src, subdir := splitPackageSubdirRaw(opt.OriginalSource) key := cacheKey(src, opt.OriginalVersion) - opt.Debug("Storing with cache key %s", key) + opt.Logger.Debug("Caching module", log.String("key", key)) baseCacheDir, err := locateCacheDir(opt.CacheDir) if err != nil { @@ -52,9 +54,11 @@ func (r *remoteResolver) Resolve(ctx context.Context, _ fs.FS, opt Options) (fil } r.incrementCount(opt) - opt.Debug("Successfully downloaded %s from %s", opt.Name, opt.Source) - opt.Debug("Module '%s' resolved via remote download.", opt.Name) - return os.DirFS(cacheDir), opt.Source, filepath.Join(".", opt.RelativePath), true, nil + opt.Logger.Debug("Successfully resolve module via remote download", + log.String("name", opt.Name), + log.String("source", opt.Source), + ) + return os.DirFS(cacheDir), opt.Source, subdir, true, nil } func (r *remoteResolver) download(ctx context.Context, opt Options, dst string) error { @@ -68,7 +72,7 @@ func (r *remoteResolver) download(ctx context.Context, opt Options, dst string) // Overwrite the file getter so that a file will be copied getter.Getters["file"] = &getter.FileGetter{Copy: true} - opt.Debug("Downloading %s...", opt.Source) + opt.Logger.Debug("Downloading module", log.String("source", opt.Source)) // Build the client client := &getter.Client{ diff --git a/pkg/iac/scanners/terraform/parser/resolvers/source.go b/pkg/iac/scanners/terraform/parser/resolvers/source.go index d7637ffaf8a5..ee5662d6c7d0 100644 --- a/pkg/iac/scanners/terraform/parser/resolvers/source.go +++ b/pkg/iac/scanners/terraform/parser/resolvers/source.go @@ -2,7 +2,7 @@ package resolvers import "strings" -func removeSubdirFromSource(src string) string { +func splitPackageSubdirRaw(src string) (string, string) { stop := len(src) if idx := strings.Index(src, "?"); idx > -1 { stop = idx @@ -18,7 +18,7 @@ func removeSubdirFromSource(src string) string { // First see if we even have an explicit subdir idx := strings.Index(src[offset:stop], "//") if idx == -1 { - return src + return src, "." } idx += offset @@ -29,8 +29,9 @@ func removeSubdirFromSource(src string) string { // URL. if idx = strings.Index(subdir, "?"); idx > -1 { query := subdir[idx:] + subdir = subdir[:idx] src += query } - return src + return src, subdir } diff --git a/pkg/iac/scanners/terraform/parser/resolvers/source_test.go b/pkg/iac/scanners/terraform/parser/resolvers/source_test.go index 3f57ab68b6d3..46df7db700dc 100644 --- a/pkg/iac/scanners/terraform/parser/resolvers/source_test.go +++ b/pkg/iac/scanners/terraform/parser/resolvers/source_test.go @@ -6,39 +6,45 @@ import ( "github.com/stretchr/testify/assert" ) -func TestRemoveSubdirFromSource(t *testing.T) { +func TestSplitPackageSubdirRaw(t *testing.T) { tests := []struct { - name string - source string - expected string + name string + source string + expectedPkg string + expectedSubdir string }{ { - name: "address with scheme and query string", - source: "git::https://github.com/aquasecurity/terraform-modules.git//modules/ecs-service?ref=v0.1.0", - expected: "git::https://github.com/aquasecurity/terraform-modules.git?ref=v0.1.0", + name: "address with scheme and query string", + source: "git::https://github.com/aquasecurity/terraform-modules.git//modules/ecs-service?ref=v0.1.0", + expectedPkg: "git::https://github.com/aquasecurity/terraform-modules.git?ref=v0.1.0", + expectedSubdir: "modules/ecs-service", }, { - name: "address with scheme", - source: "git::https://github.com/aquasecurity/terraform-modules.git//modules/ecs-service", - expected: "git::https://github.com/aquasecurity/terraform-modules.git", + name: "address with scheme", + source: "git::https://github.com/aquasecurity/terraform-modules.git//modules/ecs-service", + expectedPkg: "git::https://github.com/aquasecurity/terraform-modules.git", + expectedSubdir: "modules/ecs-service", }, { - name: "registry address", - source: "hashicorp/consul/aws//modules/consul-cluster", - expected: "hashicorp/consul/aws", + name: "registry address", + source: "hashicorp/consul/aws//modules/consul-cluster", + expectedPkg: "hashicorp/consul/aws", + expectedSubdir: "modules/consul-cluster", }, { - name: "without subdir", - source: `hashicorp/consul/aws`, - expected: `hashicorp/consul/aws`, + name: "without subdir", + source: `hashicorp/consul/aws`, + expectedPkg: `hashicorp/consul/aws`, + expectedSubdir: ".", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - got := removeSubdirFromSource(test.source) - assert.Equal(t, test.expected, got) + pkgAddr, subdir := splitPackageSubdirRaw(test.source) + assert.Equal(t, test.expectedPkg, pkgAddr) + assert.Equal(t, test.expectedSubdir, subdir) }) } } diff --git a/pkg/iac/scanners/terraform/scanner.go b/pkg/iac/scanners/terraform/scanner.go index c999acf337f5..d574acdb2c4a 100644 --- a/pkg/iac/scanners/terraform/scanner.go +++ b/pkg/iac/scanners/terraform/scanner.go @@ -3,7 +3,6 @@ package terraform import ( "context" "fmt" - "io" "io/fs" "path" "path/filepath" @@ -11,7 +10,6 @@ import ( "strings" "sync" - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scan" @@ -21,34 +19,27 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser" "github.com/aquasecurity/trivy/pkg/iac/terraform" "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" ) var _ scanners.FSScanner = (*Scanner)(nil) var _ options.ConfigurableScanner = (*Scanner)(nil) var _ ConfigurableTerraformScanner = (*Scanner)(nil) -type Scanner struct { // nolint: gocritic - sync.Mutex - options []options.ScannerOption - parserOpt []options.ParserOption - executorOpt []executor.Option - dirs map[string]struct{} - forceAllDirs bool - policyDirs []string - policyReaders []io.Reader - regoScanner *rego.Scanner - execLock sync.RWMutex - debug debug.Logger - frameworks []framework.Framework - spec string - loadEmbeddedLibraries bool - loadEmbeddedPolicies bool +type Scanner struct { + mu sync.Mutex + logger *log.Logger + options []options.ScannerOption + parserOpt []parser.Option + executorOpt []executor.Option + dirs map[string]struct{} + forceAllDirs bool + regoScanner *rego.Scanner + execLock sync.RWMutex } -func (s *Scanner) SetIncludeDeprecatedChecks(b bool) {} - -func (s *Scanner) SetSpec(spec string) { - s.spec = spec +func (s *Scanner) SetIncludeDeprecatedChecks(b bool) { + s.executorOpt = append(s.executorOpt, executor.OptionWithIncludeDeprecatedChecks(b)) } func (s *Scanner) SetRegoOnly(regoOnly bool) { @@ -56,15 +47,7 @@ func (s *Scanner) SetRegoOnly(regoOnly bool) { } func (s *Scanner) SetFrameworks(frameworks []framework.Framework) { - s.frameworks = frameworks -} - -func (s *Scanner) SetUseEmbeddedPolicies(b bool) { - s.loadEmbeddedPolicies = b -} - -func (s *Scanner) SetUseEmbeddedLibraries(b bool) { - s.loadEmbeddedLibraries = b + s.executorOpt = append(s.executorOpt, executor.OptionWithFrameworks(frameworks...)) } func (s *Scanner) Name() string { @@ -75,7 +58,7 @@ func (s *Scanner) SetForceAllDirs(b bool) { s.forceAllDirs = b } -func (s *Scanner) AddParserOptions(opts ...options.ParserOption) { +func (s *Scanner) AddParserOptions(opts ...parser.Option) { s.parserOpt = append(s.parserOpt, opts...) } @@ -83,46 +66,11 @@ func (s *Scanner) AddExecutorOptions(opts ...executor.Option) { s.executorOpt = append(s.executorOpt, opts...) } -func (s *Scanner) SetPolicyReaders(readers []io.Reader) { - s.policyReaders = readers -} - -func (s *Scanner) SetSkipRequiredCheck(skip bool) { - s.parserOpt = append(s.parserOpt, options.ParserWithSkipRequiredCheck(skip)) -} - -func (s *Scanner) SetDebugWriter(writer io.Writer) { - s.parserOpt = append(s.parserOpt, options.ParserWithDebug(writer)) - s.executorOpt = append(s.executorOpt, executor.OptionWithDebugWriter(writer)) - s.debug = debug.New(writer, "terraform", "scanner") -} - -func (s *Scanner) SetTraceWriter(_ io.Writer) { -} - -func (s *Scanner) SetPerResultTracingEnabled(_ bool) { -} - -func (s *Scanner) SetPolicyDirs(dirs ...string) { - s.policyDirs = dirs -} - -func (s *Scanner) SetDataDirs(_ ...string) {} -func (s *Scanner) SetPolicyNamespaces(_ ...string) {} - -func (s *Scanner) SetPolicyFilesystem(_ fs.FS) { - // handled by rego when option is passed on -} - -func (s *Scanner) SetDataFilesystem(_ fs.FS) { - // handled by rego when option is passed on -} -func (s *Scanner) SetRegoErrorLimit(_ int) {} - func New(opts ...options.ScannerOption) *Scanner { s := &Scanner{ dirs: make(map[string]struct{}), options: opts, + logger: log.WithPrefix("terraform scanner"), } for _, opt := range opts { opt(s) @@ -131,15 +79,13 @@ func New(opts ...options.ScannerOption) *Scanner { } func (s *Scanner) initRegoScanner(srcFS fs.FS) (*rego.Scanner, error) { - s.Lock() - defer s.Unlock() + s.mu.Lock() + defer s.mu.Unlock() if s.regoScanner != nil { return s.regoScanner, nil } regoScanner := rego.NewScanner(types.SourceCloud, s.options...) - regoScanner.SetParentDebugLogger(s.debug) - - if err := regoScanner.LoadPolicies(s.loadEmbeddedLibraries, s.loadEmbeddedPolicies, srcFS, s.policyDirs, s.policyReaders); err != nil { + if err := regoScanner.LoadPolicies(srcFS); err != nil { return nil, err } s.regoScanner = regoScanner @@ -155,14 +101,14 @@ type terraformRootModule struct { func (s *Scanner) ScanFS(ctx context.Context, target fs.FS, dir string) (scan.Results, error) { - s.debug.Log("Scanning [%s] at '%s'...", target, dir) + s.logger.Debug("Scanning directory", log.FilePath(dir)) // find directories which directly contain tf files modulePaths := s.findModules(target, dir, dir) sort.Strings(modulePaths) if len(modulePaths) == 0 { - s.debug.Log("no modules found") + s.logger.Info("No modules found, skipping directory", log.FilePath(dir)) return nil, nil } @@ -172,7 +118,7 @@ func (s *Scanner) ScanFS(ctx context.Context, target fs.FS, dir string) (scan.Re } s.execLock.Lock() - s.executorOpt = append(s.executorOpt, executor.OptionWithRegoScanner(regoScanner), executor.OptionWithFrameworks(s.frameworks...)) + s.executorOpt = append(s.executorOpt, executor.OptionWithRegoScanner(regoScanner)) s.execLock.Unlock() var allResults scan.Results @@ -188,7 +134,7 @@ func (s *Scanner) ScanFS(ctx context.Context, target fs.FS, dir string) (scan.Re // parse all root module directories for _, dir := range rootDirs { - s.debug.Log("Scanning root module '%s'...", dir) + s.logger.Info("Scanning root module", log.FilePath(dir)) p := parser.New(target, "", s.parserOpt...) @@ -298,7 +244,7 @@ func (s *Scanner) findModules(target fs.FS, scanDir string, dirs ...string) []st func (s *Scanner) isRootModule(target fs.FS, dir string) bool { files, err := fs.ReadDir(target, filepath.ToSlash(dir)) if err != nil { - s.debug.Log("failed to read dir '%s' from filesystem [%s]: %s", dir, target, err) + s.logger.Error("Failed to read dir", log.FilePath(dir), log.Err(err)) return false } for _, file := range files { diff --git a/pkg/iac/scanners/terraform/scanner_integration_test.go b/pkg/iac/scanners/terraform/scanner_integration_test.go index 6c60dd8a9f2c..96c681e97553 100644 --- a/pkg/iac/scanners/terraform/scanner_integration_test.go +++ b/pkg/iac/scanners/terraform/scanner_integration_test.go @@ -1,15 +1,14 @@ package terraform import ( - "bytes" "context" - "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" ) @@ -45,14 +44,11 @@ deny[res] { }`, }) - debugLog := bytes.NewBuffer([]byte{}) - scanner := New( - options.ScannerWithDebug(debugLog), - options.ScannerWithPolicyFilesystem(fs), - options.ScannerWithPolicyDirs("rules"), - options.ScannerWithEmbeddedPolicies(false), - options.ScannerWithEmbeddedLibraries(false), + rego.WithPolicyFilesystem(fs), + rego.WithPolicyDirs("rules"), + rego.WithEmbeddedPolicies(false), + rego.WithEmbeddedLibraries(false), options.ScannerWithRegoOnly(true), ScannerWithAllDirectories(true), ScannerWithSkipCachedModules(true), @@ -62,10 +58,6 @@ deny[res] { require.NoError(t, err) assert.Len(t, results.GetPassed(), 1) - - if t.Failed() { - fmt.Printf("Debug logs:\n%s\n", debugLog.String()) - } } func Test_ScanChildUseRemoteModule(t *testing.T) { @@ -109,14 +101,11 @@ deny[res] { }`, }) - debugLog := bytes.NewBuffer([]byte{}) - scanner := New( - options.ScannerWithDebug(debugLog), - options.ScannerWithPolicyFilesystem(fs), - options.ScannerWithPolicyDirs("rules"), - options.ScannerWithEmbeddedPolicies(false), - options.ScannerWithEmbeddedLibraries(false), + rego.WithPolicyFilesystem(fs), + rego.WithPolicyDirs("rules"), + rego.WithEmbeddedPolicies(false), + rego.WithEmbeddedLibraries(false), options.ScannerWithRegoOnly(true), ScannerWithAllDirectories(true), ScannerWithSkipCachedModules(true), @@ -126,10 +115,6 @@ deny[res] { require.NoError(t, err) assert.Len(t, results.GetPassed(), 1) - - if t.Failed() { - fmt.Printf("Debug logs:\n%s\n", debugLog.String()) - } } func Test_OptionWithSkipDownloaded(t *testing.T) { @@ -163,10 +148,10 @@ deny[cause] { t.Run("without skip", func(t *testing.T) { scanner := New( ScannerWithSkipCachedModules(true), - options.ScannerWithPolicyDirs("rules"), + rego.WithPolicyDirs("rules"), options.ScannerWithRegoOnly(true), - options.ScannerWithEmbeddedPolicies(false), - options.ScannerWithEmbeddedLibraries(true), + rego.WithEmbeddedPolicies(false), + rego.WithEmbeddedLibraries(true), ) results, err := scanner.ScanFS(context.TODO(), fs, "test") require.NoError(t, err) @@ -179,10 +164,10 @@ deny[cause] { scanner := New( ScannerWithSkipDownloaded(true), ScannerWithSkipCachedModules(true), - options.ScannerWithPolicyDirs("rules"), + rego.WithPolicyDirs("rules"), options.ScannerWithRegoOnly(true), - options.ScannerWithEmbeddedPolicies(false), - options.ScannerWithEmbeddedLibraries(true), + rego.WithEmbeddedPolicies(false), + rego.WithEmbeddedLibraries(true), ) results, err := scanner.ScanFS(context.TODO(), fs, "test") require.NoError(t, err) @@ -233,10 +218,10 @@ deny[res] { scanner := New( ScannerWithSkipDownloaded(true), ScannerWithSkipCachedModules(true), - options.ScannerWithPolicyDirs("rules"), + rego.WithPolicyDirs("rules"), options.ScannerWithRegoOnly(true), - options.ScannerWithEmbeddedLibraries(true), - options.ScannerWithEmbeddedPolicies(false), + rego.WithEmbeddedLibraries(true), + rego.WithEmbeddedPolicies(false), ) results, err := scanner.ScanFS(context.TODO(), fs, "test") require.NoError(t, err) diff --git a/pkg/iac/scanners/terraform/scanner_test.go b/pkg/iac/scanners/terraform/scanner_test.go index 20e839ff91ab..a7020b4ddd06 100644 --- a/pkg/iac/scanners/terraform/scanner_test.go +++ b/pkg/iac/scanners/terraform/scanner_test.go @@ -1,7 +1,6 @@ package terraform import ( - "bytes" "context" "fmt" "strconv" @@ -12,30 +11,15 @@ import ( "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/providers" + "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/rules" "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/severity" - "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/aquasecurity/trivy/pkg/iac/state" + "github.com/aquasecurity/trivy/pkg/iac/types" ) -var alwaysFailRule = scan.Rule{ - Provider: providers.AWSProvider, - Service: "service", - ShortCode: "abc", - Severity: severity.High, - CustomChecks: scan.CustomChecks{ - Terraform: &scan.TerraformCustomCheck{ - RequiredTypes: []string{}, - RequiredLabels: []string{}, - Check: func(resourceBlock *terraform.Block, _ *terraform.Module) (results scan.Results) { - results.Add("oh no", resourceBlock) - return - }, - }, - }, -} - const emptyBucketRule = ` # METADATA # schemas: @@ -56,33 +40,6 @@ deny[res] { } ` -func scanWithOptions(t *testing.T, code string, opt ...options.ScannerOption) scan.Results { - - fs := testutil.CreateFS(t, map[string]string{ - "project/main.tf": code, - }) - - scanner := New(opt...) - results, err := scanner.ScanFS(context.TODO(), fs, "project") - require.NoError(t, err) - return results -} - -func Test_OptionWithDebugWriter(t *testing.T) { - reg := rules.Register(alwaysFailRule) - defer rules.Deregister(reg) - - buffer := bytes.NewBuffer([]byte{}) - - scannerOpts := []options.ScannerOption{ - options.ScannerWithDebug(buffer), - } - _ = scanWithOptions(t, ` -resource "something" "else" {} -`, scannerOpts...) - require.Positive(t, buffer.Len()) -} - func Test_OptionWithPolicyDirs(t *testing.T) { fs := testutil.CreateFS(t, map[string]string{ @@ -119,11 +76,9 @@ deny[cause] { `, }) - debugLog := bytes.NewBuffer([]byte{}) scanner := New( - options.ScannerWithDebug(debugLog), - options.ScannerWithPolicyFilesystem(fs), - options.ScannerWithPolicyDirs("rules"), + rego.WithPolicyFilesystem(fs), + rego.WithPolicyDirs("rules"), options.ScannerWithRegoOnly(true), ) @@ -168,10 +123,6 @@ deny[cause] { }, }, actualCode.Lines) - if t.Failed() { - fmt.Printf("Debug logs:\n%s\n", debugLog.String()) - } - } func Test_OptionWithPolicyNamespaces(t *testing.T) { @@ -263,8 +214,8 @@ cause := bucket.name }) scanner := New( - options.ScannerWithPolicyDirs("rules"), - options.ScannerWithPolicyNamespaces(test.includedNamespaces...), + rego.WithPolicyDirs("rules"), + rego.WithPolicyNamespaces(test.includedNamespaces...), ) results, err := scanner.ScanFS(context.TODO(), fs, "code") @@ -278,7 +229,6 @@ cause := bucket.name } } assert.Equal(t, test.wantFailure, found) - }) } @@ -320,10 +270,8 @@ deny[cause] { `, }) - debugLog := bytes.NewBuffer([]byte{}) scanner := New( - options.ScannerWithDebug(debugLog), - options.ScannerWithPolicyDirs("rules"), + rego.WithPolicyDirs("rules"), options.ScannerWithRegoOnly(true), ) @@ -332,10 +280,6 @@ deny[cause] { require.Len(t, results.GetFailed(), 1) assert.Equal(t, "AVD-TEST-0123", results[0].Rule().AVDID) - - if t.Failed() { - fmt.Printf("Debug logs:\n%s\n", debugLog.String()) - } } func Test_OptionWithRegoOnly_CodeHighlighting(t *testing.T) { @@ -374,12 +318,10 @@ deny[res] { `, }) - debugLog := bytes.NewBuffer([]byte{}) scanner := New( - options.ScannerWithDebug(debugLog), - options.ScannerWithPolicyDirs("rules"), + rego.WithPolicyDirs("rules"), options.ScannerWithRegoOnly(true), - options.ScannerWithEmbeddedLibraries(true), + rego.WithEmbeddedLibraries(true), ) results, err := scanner.ScanFS(context.TODO(), fs, "code") @@ -388,10 +330,6 @@ deny[res] { require.Len(t, results.GetFailed(), 1) assert.Equal(t, "AVD-TEST-0123", results[0].Rule().AVDID) assert.NotNil(t, results[0].Metadata().Range().GetFS()) - - if t.Failed() { - fmt.Printf("Debug logs:\n%s\n", debugLog.String()) - } } func Test_IAMPolicyRego(t *testing.T) { @@ -448,21 +386,12 @@ deny[res] { `, }) - debugLog := bytes.NewBuffer([]byte{}) scanner := New( - options.ScannerWithDebug(debugLog), - options.ScannerWithTrace(debugLog), - options.ScannerWithPolicyDirs("rules"), + rego.WithPolicyDirs("rules"), options.ScannerWithRegoOnly(true), - options.ScannerWithEmbeddedLibraries(true), + rego.WithEmbeddedLibraries(true), ) - defer func() { - if t.Failed() { - fmt.Printf("Debug logs:\n%s\n", debugLog.String()) - } - }() - results, err := scanner.ScanFS(context.TODO(), fs, "code") require.NoError(t, err) @@ -541,12 +470,10 @@ deny[res] { `, }) - debugLog := bytes.NewBuffer([]byte{}) scanner := New( - options.ScannerWithDebug(debugLog), - options.ScannerWithPolicyDirs("rules"), + rego.WithPolicyDirs("rules"), options.ScannerWithRegoOnly(true), - options.ScannerWithEmbeddedLibraries(true), + rego.WithEmbeddedLibraries(true), ) results, err := scanner.ScanFS(context.TODO(), fs, "code") @@ -556,9 +483,6 @@ deny[res] { assert.Equal(t, "AVD-TEST-0123", results[0].Rule().AVDID) assert.NotNil(t, results[0].Metadata().Range().GetFS()) - if t.Failed() { - fmt.Printf("Debug logs:\n%s\n", debugLog.String()) - } } func Test_S3_Linking(t *testing.T) { @@ -603,10 +527,7 @@ resource "aws_s3_bucket_public_access_block" "foo" { "code/main.tf": code, }) - debugLog := bytes.NewBuffer([]byte{}) - scanner := New( - options.ScannerWithDebug(debugLog), - ) + scanner := New() results, err := scanner.ScanFS(context.TODO(), fs, "code") require.NoError(t, err) @@ -622,10 +543,6 @@ resource "aws_s3_bucket_public_access_block" "foo" { // versioning assert.NotEqual(t, "AVD-AWS-0090", result.Rule().AVDID) } - - if t.Failed() { - fmt.Printf("Debug logs:\n%s\n", debugLog.String()) - } } func Test_S3_Linking_PublicAccess(t *testing.T) { @@ -673,10 +590,7 @@ resource "aws_s3_bucket_public_access_block" "testB" { "code/main.tf": code, }) - debugLog := bytes.NewBuffer([]byte{}) - scanner := New( - options.ScannerWithDebug(debugLog), - ) + scanner := New() results, err := scanner.ScanFS(context.TODO(), fs, "code") require.NoError(t, err) @@ -686,9 +600,6 @@ resource "aws_s3_bucket_public_access_block" "testB" { assert.NotEqual(t, "AVD-AWS-0094", result.Rule().AVDID) } - if t.Failed() { - fmt.Printf("Debug logs:\n%s\n", debugLog.String()) - } } // PoC for replacing Go with Rego: AVD-AWS-0001 @@ -732,11 +643,9 @@ deny[res] { `, }) - debugLog := bytes.NewBuffer([]byte{}) scanner := New( - options.ScannerWithDebug(debugLog), - options.ScannerWithPolicyFilesystem(fs), - options.ScannerWithPolicyDirs("rules"), + rego.WithPolicyFilesystem(fs), + rego.WithPolicyDirs("rules"), options.ScannerWithRegoOnly(true), ) @@ -788,11 +697,6 @@ deny[res] { Annotation: "", }, }, actualCode.Lines) - - if t.Failed() { - fmt.Printf("Debug logs:\n%s\n", debugLog.String()) - } - } func Test_OptionWithConfigsFileSystem(t *testing.T) { @@ -814,14 +718,12 @@ bucket_name = "test" `, }) - debugLog := bytes.NewBuffer([]byte{}) scanner := New( - options.ScannerWithDebug(debugLog), - options.ScannerWithPolicyDirs("rules"), - options.ScannerWithPolicyFilesystem(fs), + rego.WithPolicyDirs("rules"), + rego.WithPolicyFilesystem(fs), options.ScannerWithRegoOnly(true), - options.ScannerWithEmbeddedLibraries(false), - options.ScannerWithEmbeddedPolicies(false), + rego.WithEmbeddedLibraries(false), + rego.WithEmbeddedPolicies(false), ScannerWithAllDirectories(true), ScannerWithTFVarsPaths("main.tfvars"), ScannerWithConfigsFileSystem(configsFS), @@ -832,10 +734,6 @@ bucket_name = "test" assert.Len(t, results, 1) assert.Len(t, results.GetPassed(), 1) - - if t.Failed() { - fmt.Printf("Debug logs:\n%s\n", debugLog.String()) - } } func Test_OptionWithConfigsFileSystem_ConfigInCode(t *testing.T) { @@ -854,14 +752,12 @@ bucket_name = "test" `, }) - debugLog := bytes.NewBuffer([]byte{}) scanner := New( - options.ScannerWithDebug(debugLog), - options.ScannerWithPolicyDirs("rules"), - options.ScannerWithPolicyFilesystem(fs), + rego.WithPolicyDirs("rules"), + rego.WithPolicyFilesystem(fs), options.ScannerWithRegoOnly(true), - options.ScannerWithEmbeddedLibraries(false), - options.ScannerWithEmbeddedPolicies(false), + rego.WithEmbeddedLibraries(false), + rego.WithEmbeddedPolicies(false), ScannerWithAllDirectories(true), ScannerWithTFVarsPaths("main.tfvars"), ScannerWithConfigsFileSystem(fs), @@ -872,10 +768,6 @@ bucket_name = "test" assert.Len(t, results, 1) assert.Len(t, results.GetPassed(), 1) - - if t.Failed() { - fmt.Printf("Debug logs:\n%s\n", debugLog.String()) - } } func Test_DoNotScanNonRootModules(t *testing.T) { @@ -953,13 +845,11 @@ deny[res] { `, }) - debugLog := bytes.NewBuffer([]byte{}) scanner := New( - options.ScannerWithDebug(debugLog), - options.ScannerWithPolicyFilesystem(fs), - options.ScannerWithPolicyDirs("rules"), - options.ScannerWithEmbeddedPolicies(false), - options.ScannerWithEmbeddedLibraries(false), + rego.WithPolicyFilesystem(fs), + rego.WithPolicyDirs("rules"), + rego.WithEmbeddedPolicies(false), + rego.WithEmbeddedLibraries(false), options.ScannerWithRegoOnly(true), ScannerWithAllDirectories(true), ) @@ -970,10 +860,6 @@ deny[res] { assert.Len(t, results.GetPassed(), 2) require.Len(t, results.GetFailed(), 1) assert.Equal(t, "AVD-AWS-0002", results.GetFailed()[0].Rule().AVDID) - - if t.Failed() { - fmt.Printf("Debug logs:\n%s\n", debugLog.String()) - } } func Test_RoleRefToOutput(t *testing.T) { @@ -1029,14 +915,12 @@ deny[res] { `, }) - debugLog := bytes.NewBuffer([]byte{}) scanner := New( - options.ScannerWithDebug(debugLog), - options.ScannerWithPolicyDirs("rules"), - options.ScannerWithPolicyFilesystem(fs), + rego.WithPolicyDirs("rules"), + rego.WithPolicyFilesystem(fs), options.ScannerWithRegoOnly(true), - options.ScannerWithEmbeddedLibraries(false), - options.ScannerWithEmbeddedPolicies(false), + rego.WithEmbeddedLibraries(false), + rego.WithEmbeddedPolicies(false), ScannerWithAllDirectories(true), ) @@ -1100,14 +984,12 @@ deny[res] { }`, }) - debugLog := bytes.NewBuffer([]byte{}) scanner := New( - options.ScannerWithDebug(debugLog), - options.ScannerWithPolicyDirs("rules"), - options.ScannerWithPolicyFilesystem(fs), + rego.WithPolicyDirs("rules"), + rego.WithPolicyFilesystem(fs), options.ScannerWithRegoOnly(true), - options.ScannerWithEmbeddedLibraries(false), - options.ScannerWithEmbeddedPolicies(false), + rego.WithEmbeddedLibraries(false), + rego.WithEmbeddedPolicies(false), ScannerWithAllDirectories(true), ) @@ -1121,10 +1003,6 @@ deny[res] { require.Len(t, results.GetPassed(), 1) assert.Equal(t, "AVD-AWS-0002", results.GetPassed()[0].Rule().AVDID) - - if t.Failed() { - fmt.Printf("Debug logs:\n%s\n", debugLog.String()) - } } func TestScanModuleWithCount(t *testing.T) { @@ -1170,16 +1048,14 @@ deny[res] { `, }) - debugLog := bytes.NewBuffer([]byte{}) scanner := New( - options.ScannerWithDebug(debugLog), - options.ScannerWithPolicyDirs("rules"), - options.ScannerWithPolicyFilesystem(fs), + rego.WithPolicyDirs("rules"), + rego.WithPolicyFilesystem(fs), options.ScannerWithRegoOnly(true), - options.ScannerWithPolicyNamespaces("user"), - options.ScannerWithEmbeddedLibraries(false), - options.ScannerWithEmbeddedPolicies(false), - options.ScannerWithRegoErrorLimits(0), + rego.WithPolicyNamespaces("user"), + rego.WithEmbeddedLibraries(false), + rego.WithEmbeddedPolicies(false), + rego.WithRegoErrorLimits(0), ScannerWithAllDirectories(true), ) @@ -1195,3 +1071,149 @@ deny[res] { occurrences := failed[0].Occurrences() assert.Equal(t, "code/example/main.tf", occurrences[0].Filename) } + +func TestSkipDeprecatedGoChecks(t *testing.T) { + + check := scan.Rule{ + Provider: providers.AWSProvider, + Service: "service", + ShortCode: "abc", + Severity: severity.High, + Check: func(s *state.State) (results scan.Results) { + results.Add("Deny", types.NewTestMetadata()) + return + }, + } + + fsys := testutil.CreateFS(t, map[string]string{ + "main.tf": `resource "foo" "bar" {}`, + }) + + scanner := New( + rego.WithPolicyFilesystem(fsys), + rego.WithEmbeddedLibraries(false), + rego.WithEmbeddedPolicies(false), + ScannerWithAllDirectories(true), + ) + + t.Run("deprecated", func(t *testing.T) { + check.Deprecated = true + reg := rules.Register(check) + defer rules.Deregister(reg) + + results, err := scanner.ScanFS(context.TODO(), fsys, ".") + require.NoError(t, err) + + require.Empty(t, results) + }) + + t.Run("not deprecated", func(t *testing.T) { + check.Deprecated = false + reg := rules.Register(check) + defer rules.Deregister(reg) + + results, err := scanner.ScanFS(context.TODO(), fsys, ".") + require.NoError(t, err) + + require.Len(t, results, 1) + }) +} + +func TestSkipDir(t *testing.T) { + fs := testutil.CreateFS(t, map[string]string{ + "deployments/main.tf": ` +module "use_bad_configuration" { + source = "../modules" +} + +module "use_bad_configuration_2" { + source = "../modules/modules2" +} +`, + "modules/misconfig.tf": `data "aws_iam_policy_document" "bad" { + statement { + actions = [ + "apigateway:*", + ] + + resources = [ + "*", + ] + } +} + +resource "aws_iam_policy" "bad_configuration" { + name_prefix = local.setup_role_name + policy = data.aws_iam_policy_document.bad.json +} +`, + "modules/modules2/misconfig.tf": `data "aws_iam_policy_document" "bad" { + statement { + actions = [ + "apigateway:*", + ] + + resources = [ + "*", + ] + } +} + +resource "aws_iam_policy" "bad_configuration" { + name_prefix = local.setup_role_name + policy = data.aws_iam_policy_document.bad.json +} +`, + }) + + t.Run("use skip-dir option", func(t *testing.T) { + scanner := New( + options.ScannerWithIncludeDeprecatedChecks(true), + ScannerWithSkipDirs([]string{"**/modules/**"}), + ScannerWithAllDirectories(true), + ) + + results, err := scanner.ScanFS(context.TODO(), fs, "deployments") + require.NoError(t, err) + + assert.Empty(t, results) + }) + + t.Run("use skip-files option", func(t *testing.T) { + scanner := New( + options.ScannerWithIncludeDeprecatedChecks(true), + ScannerWithSkipFiles([]string{"**/modules/**/*.tf"}), + ScannerWithAllDirectories(true), + ) + + results, err := scanner.ScanFS(context.TODO(), fs, "deployments") + require.NoError(t, err) + + assert.Empty(t, results) + }) + + t.Run("non existing value for skip-files option", func(t *testing.T) { + scanner := New( + options.ScannerWithIncludeDeprecatedChecks(true), + ScannerWithSkipFiles([]string{"foo/bar*.tf"}), + ScannerWithAllDirectories(true), + ) + + results, err := scanner.ScanFS(context.TODO(), fs, "deployments") + require.NoError(t, err) + + assert.Len(t, results, 4) + }) + + t.Run("empty skip-files option", func(t *testing.T) { + scanner := New( + options.ScannerWithIncludeDeprecatedChecks(true), + ScannerWithAllDirectories(true), + ) + + results, err := scanner.ScanFS(context.TODO(), fs, "deployments") + require.NoError(t, err) + + assert.Len(t, results, 4) + }) +} diff --git a/pkg/iac/scanners/terraform/setup_test.go b/pkg/iac/scanners/terraform/setup_test.go index d7a8f0277a96..5b98c438f9c1 100644 --- a/pkg/iac/scanners/terraform/setup_test.go +++ b/pkg/iac/scanners/terraform/setup_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/require" "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" "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser" @@ -39,7 +40,7 @@ func scanHCL(t *testing.T, source string, opts ...options.ScannerOption) scan.Re "main.tf": source, }) - localScanner := New(append(opts, options.ScannerWithEmbeddedPolicies(false))...) + localScanner := New(append(opts, rego.WithEmbeddedPolicies(false))...) results, err := localScanner.ScanFS(context.TODO(), fs, ".") require.NoError(t, err) return results @@ -51,7 +52,7 @@ func scanJSON(t *testing.T, source string) scan.Results { "main.tf.json": source, }) - s := New(options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(true)) + s := New(rego.WithEmbeddedPolicies(true), rego.WithEmbeddedLibraries(true)) results, err := s.ScanFS(context.TODO(), fs, ".") require.NoError(t, err) return results diff --git a/pkg/iac/scanners/terraformplan/snapshot/plan.go b/pkg/iac/scanners/terraformplan/snapshot/plan.go new file mode 100644 index 000000000000..3f0c3adcd791 --- /dev/null +++ b/pkg/iac/scanners/terraformplan/snapshot/plan.go @@ -0,0 +1,64 @@ +package snapshot + +import ( + "fmt" + "io" + + "github.com/zclconf/go-cty/cty" + ctymsgpack "github.com/zclconf/go-cty/cty/msgpack" + "google.golang.org/protobuf/proto" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan/snapshot/planproto" +) + +type DynamicValue []byte + +func (v DynamicValue) Decode(ty cty.Type) (cty.Value, error) { + if v == nil { + return cty.NilVal, nil + } + + return ctymsgpack.Unmarshal([]byte(v), ty) +} + +type Plan struct { + variableValues map[string]DynamicValue +} + +func (p Plan) inputVariables() (map[string]cty.Value, error) { + vars := make(map[string]cty.Value) + for k, v := range p.variableValues { + val, err := v.Decode(cty.DynamicPseudoType) + if err != nil { + return nil, err + } + vars[k] = val + } + return vars, nil +} + +func readTfPlan(r io.Reader) (*Plan, error) { + b, err := io.ReadAll(r) + if err != nil { + return nil, fmt.Errorf("failed to read plan: %w", err) + } + + var rawPlan planproto.Plan + if err := proto.Unmarshal(b, &rawPlan); err != nil { + return nil, fmt.Errorf("failed to unmarshal plan: %w", err) + } + + plan := Plan{ + variableValues: make(map[string]DynamicValue), + } + + for k, v := range rawPlan.Variables { + if len(v.Msgpack) == 0 { // len(0) because that's the default value for a "bytes" in protobuf + return nil, fmt.Errorf("dynamic value does not have msgpack serialization") + } + + plan.variableValues[k] = DynamicValue(v.Msgpack) + } + + return &plan, nil +} diff --git a/pkg/iac/scanners/terraformplan/snapshot/planproto/planfile.pb.go b/pkg/iac/scanners/terraformplan/snapshot/planproto/planfile.pb.go new file mode 100644 index 000000000000..1aa3ae5a7ceb --- /dev/null +++ b/pkg/iac/scanners/terraformplan/snapshot/planproto/planfile.pb.go @@ -0,0 +1,222 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.32.0 +// protoc v5.27.1 +// source: pkg/iac/scanners/terraformplan/snapshot/planproto/planfile.proto + +package planproto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type DynamicValue struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Msgpack []byte `protobuf:"bytes,1,opt,name=msgpack,proto3" json:"msgpack,omitempty"` +} + +func (x *DynamicValue) Reset() { + *x = DynamicValue{} + if protoimpl.UnsafeEnabled { + mi := &file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DynamicValue) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DynamicValue) ProtoMessage() {} + +func (x *DynamicValue) ProtoReflect() protoreflect.Message { + mi := &file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DynamicValue.ProtoReflect.Descriptor instead. +func (*DynamicValue) Descriptor() ([]byte, []int) { + return file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDescGZIP(), []int{0} +} + +func (x *DynamicValue) GetMsgpack() []byte { + if x != nil { + return x.Msgpack + } + return nil +} + +type Plan struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Variables map[string]*DynamicValue `protobuf:"bytes,2,rep,name=variables,proto3" json:"variables,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *Plan) Reset() { + *x = Plan{} + if protoimpl.UnsafeEnabled { + mi := &file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Plan) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Plan) ProtoMessage() {} + +func (x *Plan) ProtoReflect() protoreflect.Message { + mi := &file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Plan.ProtoReflect.Descriptor instead. +func (*Plan) Descriptor() ([]byte, []int) { + return file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDescGZIP(), []int{1} +} + +func (x *Plan) GetVariables() map[string]*DynamicValue { + if x != nil { + return x.Variables + } + return nil +} + +var File_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto protoreflect.FileDescriptor + +var file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDesc = []byte{ + 0x0a, 0x40, 0x70, 0x6b, 0x67, 0x2f, 0x69, 0x61, 0x63, 0x2f, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, + 0x72, 0x73, 0x2f, 0x74, 0x65, 0x72, 0x72, 0x61, 0x66, 0x6f, 0x72, 0x6d, 0x70, 0x6c, 0x61, 0x6e, + 0x2f, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x2f, 0x70, 0x6c, 0x61, 0x6e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x6c, 0x61, 0x6e, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x12, 0x06, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x22, 0x28, 0x0a, 0x0c, 0x44, 0x79, + 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x73, + 0x67, 0x70, 0x61, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x73, 0x67, + 0x70, 0x61, 0x63, 0x6b, 0x22, 0x95, 0x01, 0x0a, 0x04, 0x50, 0x6c, 0x61, 0x6e, 0x12, 0x39, 0x0a, + 0x09, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1b, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x2e, 0x56, + 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x76, + 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x1a, 0x52, 0x0a, 0x0e, 0x56, 0x61, 0x72, 0x69, + 0x61, 0x62, 0x6c, 0x65, 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, 0x2a, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x66, + 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x4d, 0x5a, 0x4b, + 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, 0x69, 0x61, + 0x63, 0x2f, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x2f, 0x74, 0x65, 0x72, 0x72, 0x61, + 0x66, 0x6f, 0x72, 0x6d, 0x70, 0x6c, 0x61, 0x6e, 0x2f, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, + 0x74, 0x2f, 0x70, 0x6c, 0x61, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, +} + +var ( + file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDescOnce sync.Once + file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDescData = file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDesc +) + +func file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDescGZIP() []byte { + file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDescOnce.Do(func() { + file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDescData = protoimpl.X.CompressGZIP(file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDescData) + }) + return file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDescData +} + +var file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_goTypes = []interface{}{ + (*DynamicValue)(nil), // 0: tfplan.DynamicValue + (*Plan)(nil), // 1: tfplan.Plan + nil, // 2: tfplan.Plan.VariablesEntry +} +var file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_depIdxs = []int32{ + 2, // 0: tfplan.Plan.variables:type_name -> tfplan.Plan.VariablesEntry + 0, // 1: tfplan.Plan.VariablesEntry.value:type_name -> tfplan.DynamicValue + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_init() } +func file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_init() { + if File_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DynamicValue); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Plan); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDesc, + NumEnums: 0, + NumMessages: 3, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_goTypes, + DependencyIndexes: file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_depIdxs, + MessageInfos: file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_msgTypes, + }.Build() + File_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto = out.File + file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDesc = nil + file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_goTypes = nil + file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_depIdxs = nil +} diff --git a/pkg/iac/scanners/terraformplan/snapshot/planproto/planfile.proto b/pkg/iac/scanners/terraformplan/snapshot/planproto/planfile.proto new file mode 100644 index 000000000000..c5615b819e66 --- /dev/null +++ b/pkg/iac/scanners/terraformplan/snapshot/planproto/planfile.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; +package tfplan; + +option go_package = "github.com/aquasecurity/trivy/iac/scanners/terraformplan/snapshot/planproto"; + +message DynamicValue { + bytes msgpack = 1; +} + +message Plan { + map variables = 2; +} \ No newline at end of file diff --git a/pkg/iac/scanners/terraformplan/snapshot/scanner.go b/pkg/iac/scanners/terraformplan/snapshot/scanner.go index e2a8d2807fa1..3c8dcc8fce0b 100644 --- a/pkg/iac/scanners/terraformplan/snapshot/scanner.go +++ b/pkg/iac/scanners/terraformplan/snapshot/scanner.go @@ -10,6 +10,7 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" terraformScanner "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform" + tfparser "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser" ) type Scanner struct { @@ -71,5 +72,9 @@ func (s *Scanner) Scan(ctx context.Context, reader io.Reader) (scan.Results, err if err != nil { return nil, fmt.Errorf("failed to convert snapshot to FS: %w", err) } + + s.inner.AddParserOptions( + tfparser.OptionsWithTfVars(snap.inputVariables), + ) return s.inner.ScanFS(ctx, fsys, ".") } diff --git a/pkg/iac/scanners/terraformplan/snapshot/scanner_test.go b/pkg/iac/scanners/terraformplan/snapshot/scanner_test.go index 9eca249c1f16..992bed5af809 100644 --- a/pkg/iac/scanners/terraformplan/snapshot/scanner_test.go +++ b/pkg/iac/scanners/terraformplan/snapshot/scanner_test.go @@ -1,7 +1,6 @@ package snapshot import ( - "bytes" "context" "os" "path" @@ -13,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" tfscanner "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform" @@ -20,12 +20,12 @@ import ( func initScanner(opts ...options.ScannerOption) *Scanner { defaultOpts := []options.ScannerOption{ - options.ScannerWithEmbeddedPolicies(false), - options.ScannerWithEmbeddedLibraries(true), - options.ScannerWithPolicyNamespaces("user"), - options.ScannerWithPolicyDirs("."), + rego.WithEmbeddedPolicies(false), + rego.WithEmbeddedLibraries(true), + rego.WithPolicyNamespaces("user"), + rego.WithPolicyDirs("."), options.ScannerWithRegoOnly(true), - options.ScannerWithRegoErrorLimits(0), + rego.WithRegoErrorLimits(0), tfscanner.ScannerWithSkipCachedModules(true), } @@ -60,7 +60,7 @@ func TestScanner_Scan(t *testing.T) { policyFS := os.DirFS(filepath.Join("testdata", tt.dir, "checks")) - s := initScanner(options.ScannerWithPolicyFilesystem(policyFS)) + s := initScanner(rego.WithPolicyFilesystem(policyFS)) result, err := s.Scan(context.TODO(), f) require.NoError(t, err) @@ -98,22 +98,24 @@ func Test_ScanFS(t *testing.T) { dir: "with-remote-module", expectedIDs: []string{"ID001"}, }, + { + dir: "with-var", + expectedIDs: []string{"ID001"}, + }, } for _, tc := range tests { t.Run(tc.dir, func(t *testing.T) { fs := os.DirFS("testdata") - debugLog := bytes.NewBuffer([]byte{}) scanner := New( - options.ScannerWithDebug(debugLog), - options.ScannerWithPolicyDirs(path.Join(tc.dir, "checks")), - options.ScannerWithPolicyFilesystem(fs), + rego.WithPolicyDirs(path.Join(tc.dir, "checks")), + rego.WithPolicyFilesystem(fs), options.ScannerWithRegoOnly(true), - options.ScannerWithPolicyNamespaces("user"), - options.ScannerWithEmbeddedLibraries(false), - options.ScannerWithEmbeddedPolicies(false), - options.ScannerWithRegoErrorLimits(0), + rego.WithPolicyNamespaces("user"), + rego.WithEmbeddedLibraries(false), + rego.WithEmbeddedPolicies(false), + rego.WithRegoErrorLimits(0), tfscanner.ScannerWithSkipCachedModules(true), ) @@ -133,5 +135,4 @@ func Test_ScanFS(t *testing.T) { assert.Equal(t, tc.expectedIDs, ids) }) } - } diff --git a/pkg/iac/scanners/terraformplan/snapshot/snapshot.go b/pkg/iac/scanners/terraformplan/snapshot/snapshot.go index e32e47790c32..72556a984479 100644 --- a/pkg/iac/scanners/terraformplan/snapshot/snapshot.go +++ b/pkg/iac/scanners/terraformplan/snapshot/snapshot.go @@ -13,6 +13,7 @@ import ( "strings" "github.com/liamg/memoryfs" + "github.com/zclconf/go-cty/cty" iox "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -74,7 +75,8 @@ func parseSnapshot(r io.Reader) (*snapshot, error) { } snap := &snapshot{ - modules: make(map[string]*snapshotModule), + modules: make(map[string]*snapshotModule), + inputVariables: make(map[string]cty.Value), } var moduleManifest configSnapshotModuleManifest @@ -91,6 +93,23 @@ func parseSnapshot(r io.Reader) (*snapshot, error) { if err := snap.addFile(file); err != nil { return nil, err } + case file.Name == tfplanFilename: + r, err := file.Open() + if err != nil { + return nil, fmt.Errorf("failed to open plan: %w", err) + } + + plan, err := readTfPlan(r) + if err != nil { + _ = r.Close() + return nil, fmt.Errorf("failed to read tfplan: %w", err) + } + _ = r.Close() + + snap.inputVariables, err = plan.inputVariables() + if err != nil { + return nil, err + } } } @@ -140,7 +159,8 @@ type ( } snapshot struct { - modules map[string]*snapshotModule + modules map[string]*snapshotModule + inputVariables map[string]cty.Value } ) diff --git a/pkg/iac/scanners/terraformplan/snapshot/snapshot_test.go b/pkg/iac/scanners/terraformplan/snapshot/snapshot_test.go index 22f26e5e7b6b..c85791b2f283 100644 --- a/pkg/iac/scanners/terraformplan/snapshot/snapshot_test.go +++ b/pkg/iac/scanners/terraformplan/snapshot/snapshot_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/zclconf/go-cty/cty" ) func TestReadSnapshot(t *testing.T) { @@ -108,3 +109,18 @@ func TestIsPlanSnapshot(t *testing.T) { assert.False(t, got) }) } + +func TestPlanWithVariables(t *testing.T) { + f, err := os.Open(filepath.Join("testdata", "with-var", "tfplan")) + require.NoError(t, err) + defer f.Close() + + snapshot, err := parseSnapshot(f) + require.NoError(t, err) + require.NotNil(t, snapshot) + + expectedVars := map[string]cty.Value{ + "bucket_name": cty.StringVal("test-bucket"), + } + assert.Equal(t, expectedVars, snapshot.inputVariables) +} diff --git a/pkg/iac/scanners/terraformplan/snapshot/testdata/with-var/checks/s3-bucket-name.rego b/pkg/iac/scanners/terraformplan/snapshot/testdata/with-var/checks/s3-bucket-name.rego new file mode 100644 index 000000000000..798b1b705a49 --- /dev/null +++ b/pkg/iac/scanners/terraformplan/snapshot/testdata/with-var/checks/s3-bucket-name.rego @@ -0,0 +1,21 @@ +# METADATA +# title: Test rego +# description: A bucket named "test-bucket" is not allowed +# schemas: +# - input: schema["cloud"] +# custom: +# avd_id: ID001 +# severity: LOW +# input: +# selector: +# - type: cloud +# subtypes: +# - service: s3 +# provider: aws +package user.aws.ID001 + +deny[res] { + bucket := input.aws.s3.buckets[_] + bucket.name.value == "test-bucket" + res := result.new("Bucket not allowed", bucket.name) +} \ No newline at end of file diff --git a/pkg/iac/scanners/terraformplan/snapshot/testdata/with-var/main.tf b/pkg/iac/scanners/terraformplan/snapshot/testdata/with-var/main.tf new file mode 100644 index 000000000000..a6fc475bfb3e --- /dev/null +++ b/pkg/iac/scanners/terraformplan/snapshot/testdata/with-var/main.tf @@ -0,0 +1,5 @@ +variable "bucket_name" {} + +resource "aws_s3_bucket" "this" { + bucket = var.bucket_name +} \ No newline at end of file diff --git a/pkg/iac/scanners/terraformplan/snapshot/testdata/with-var/tfplan b/pkg/iac/scanners/terraformplan/snapshot/testdata/with-var/tfplan new file mode 100644 index 000000000000..78be033f53d4 Binary files /dev/null and b/pkg/iac/scanners/terraformplan/snapshot/testdata/with-var/tfplan differ diff --git a/pkg/iac/scanners/terraformplan/tfjson/parser/option.go b/pkg/iac/scanners/terraformplan/tfjson/parser/option.go deleted file mode 100644 index 09e09b97bed5..000000000000 --- a/pkg/iac/scanners/terraformplan/tfjson/parser/option.go +++ /dev/null @@ -1,17 +0,0 @@ -package parser - -import "io" - -type Option func(p *Parser) - -func OptionWithDebugWriter(w io.Writer) Option { - return func(p *Parser) { - p.debugWriter = w - } -} - -func OptionStopOnHCLError(stop bool) Option { - return func(p *Parser) { - p.stopOnHCLError = stop - } -} diff --git a/pkg/iac/scanners/terraformplan/tfjson/parser/parser.go b/pkg/iac/scanners/terraformplan/tfjson/parser/parser.go index 85d565bd307f..a55cfa99384f 100644 --- a/pkg/iac/scanners/terraformplan/tfjson/parser/parser.go +++ b/pkg/iac/scanners/terraformplan/tfjson/parser/parser.go @@ -11,28 +11,17 @@ import ( "github.com/liamg/memoryfs" "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/aquasecurity/trivy/pkg/log" ) type Parser struct { - debugWriter io.Writer - stopOnHCLError bool + logger *log.Logger } -func New(options ...Option) *Parser { - parser := &Parser{} - - for _, o := range options { - o(parser) +func New() *Parser { + return &Parser{ + logger: log.WithPrefix("tfjson parser"), } - return parser -} - -func (p *Parser) SetDebugWriter(writer io.Writer) { - p.debugWriter = writer -} - -func (p *Parser) SetStopOnHCLError(b bool) { - p.stopOnHCLError = b } func (p *Parser) ParseFile(filepath string) (*PlanFile, error) { diff --git a/pkg/iac/scanners/terraformplan/tfjson/scanner.go b/pkg/iac/scanners/terraformplan/tfjson/scanner.go index 6f62d822177f..b3e8725c42ae 100644 --- a/pkg/iac/scanners/terraformplan/tfjson/scanner.go +++ b/pkg/iac/scanners/terraformplan/tfjson/scanner.go @@ -6,140 +6,67 @@ import ( "io" "io/fs" - "github.com/bmatcuk/doublestar/v4" - - "github.com/aquasecurity/trivy/pkg/iac/debug" - "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" - terraformScanner "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform" - "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/executor" + "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform" "github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan/tfjson/parser" + "github.com/aquasecurity/trivy/pkg/log" ) -var tfPlanExts = []string{ - "**/*tfplan.json", - "**/*tf.json", -} - type Scanner struct { - parser parser.Parser - parserOpt []options.ParserOption - debug debug.Logger - - options []options.ScannerOption - spec string - executorOpt []executor.Option - frameworks []framework.Framework - loadEmbeddedPolicies bool - loadEmbeddedLibraries bool - enableEmbeddedLibraries bool - policyDirs []string - policyReaders []io.Reader -} - -func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} - -func (s *Scanner) SetUseEmbeddedLibraries(b bool) { - s.loadEmbeddedLibraries = b -} - -func (s *Scanner) SetSpec(spec string) { - s.spec = spec -} - -func (s *Scanner) SetRegoOnly(regoOnly bool) { - s.executorOpt = append(s.executorOpt, executor.OptionWithRegoOnly(regoOnly)) -} - -func (s *Scanner) SetFrameworks(frameworks []framework.Framework) { - s.frameworks = frameworks -} - -func (s *Scanner) SetUseEmbeddedPolicies(b bool) { - s.loadEmbeddedPolicies = b -} - -func (s *Scanner) SetEmbeddedLibrariesEnabled(enabled bool) { - s.enableEmbeddedLibraries = enabled -} - -func (s *Scanner) SetPolicyReaders(readers []io.Reader) { - s.policyReaders = readers -} - -func (s *Scanner) SetSkipRequiredCheck(skip bool) { - s.parserOpt = append(s.parserOpt, options.ParserWithSkipRequiredCheck(skip)) -} - -func (s *Scanner) SetDebugWriter(writer io.Writer) { - s.parserOpt = append(s.parserOpt, options.ParserWithDebug(writer)) - s.executorOpt = append(s.executorOpt, executor.OptionWithDebugWriter(writer)) - s.debug = debug.New(writer, "tfplan", "scanner") -} - -func (s *Scanner) SetTraceWriter(_ io.Writer) { + parser *parser.Parser + logger *log.Logger + options []options.ScannerOption + tfScanner *terraform.Scanner } -func (s *Scanner) SetPerResultTracingEnabled(_ bool) { -} - -func (s *Scanner) SetPolicyDirs(dirs ...string) { - s.policyDirs = dirs -} - -func (s *Scanner) SetDataDirs(_ ...string) {} -func (s *Scanner) SetPolicyNamespaces(_ ...string) {} - -func (s *Scanner) SetPolicyFilesystem(_ fs.FS) { - // handled by rego when option is passed on -} - -func (s *Scanner) SetDataFilesystem(_ fs.FS) { - // handled by rego when option is passed on -} -func (s *Scanner) SetRegoErrorLimit(_ int) {} - func (s *Scanner) Name() string { return "Terraform Plan JSON" } -func (s *Scanner) ScanFS(ctx context.Context, inputFS fs.FS, dir string) (scan.Results, error) { - var filesFound []string +func (s *Scanner) ScanFS(ctx context.Context, fsys fs.FS, dir string) (scan.Results, error) { - for _, ext := range tfPlanExts { - files, err := doublestar.Glob(inputFS, ext, doublestar.WithFilesOnly()) + var results scan.Results + + walkFn := func(path string, d fs.DirEntry, err error) error { if err != nil { - return nil, fmt.Errorf("unable to scan for terraform plan files: %w", err) + return err } - filesFound = append(filesFound, files...) - } - var results scan.Results - for _, f := range filesFound { - res, err := s.ScanFile(f, inputFS) + if d.IsDir() { + return nil + } + + res, err := s.ScanFile(path, fsys) if err != nil { - return nil, err + return fmt.Errorf("failed to scan %s: %w", path, err) } + results = append(results, res...) + return nil } + + if err := fs.WalkDir(fsys, dir, walkFn); err != nil { + return nil, err + } + return results, nil } func New(opts ...options.ScannerOption) *Scanner { scanner := &Scanner{ - parser: *parser.New(), - options: opts, - } - for _, o := range opts { - o(scanner) + options: opts, + logger: log.WithPrefix("tfjson scanner"), + parser: parser.New(), + tfScanner: terraform.New(opts...), } + return scanner } func (s *Scanner) ScanFile(filepath string, fsys fs.FS) (scan.Results, error) { - s.debug.Log("Scanning file %s", filepath) + s.logger.Debug("Scanning file", log.FilePath(filepath)) file, err := fsys.Open(filepath) if err != nil { return nil, err @@ -157,9 +84,8 @@ func (s *Scanner) Scan(reader io.Reader) (scan.Results, error) { planFS, err := planFile.ToFS() if err != nil { - return nil, err + return nil, fmt.Errorf("failed to convert plan to FS: %w", err) } - scanner := terraformScanner.New(s.options...) - return scanner.ScanFS(context.TODO(), planFS, ".") + return s.tfScanner.ScanFS(context.TODO(), planFS, ".") } diff --git a/pkg/iac/scanners/terraformplan/tfjson/scanner_test.go b/pkg/iac/scanners/terraformplan/tfjson/scanner_test.go index 8a878e206bb3..9173c8046bb1 100644 --- a/pkg/iac/scanners/terraformplan/tfjson/scanner_test.go +++ b/pkg/iac/scanners/terraformplan/tfjson/scanner_test.go @@ -1,9 +1,7 @@ package tfjson import ( - "bytes" "context" - "fmt" "os" "testing" @@ -11,23 +9,11 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" ) -func Test_TerraformScanner(t *testing.T) { - t.Parallel() - - testCases := []struct { - name string - inputFile string - inputRego string - options []options.ScannerOption - }{ - { - name: "old rego metadata", - inputFile: "test/testdata/plan.json", - inputRego: ` -package defsec.abcdefg +const defaultCheck = `package defsec.abcdefg __rego_metadata__ := { "id": "TEST123", @@ -50,48 +36,40 @@ deny[cause] { bucket := input.aws.s3.buckets[_] bucket.name.value == "tfsec-plan-testing" cause := bucket.name -} -`, +}` + +func Test_TerraformScanner(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + inputFile string + check string + options []options.ScannerOption + }{ + { + name: "old rego metadata", + inputFile: "test/testdata/plan.json", + check: defaultCheck, options: []options.ScannerOption{ - options.ScannerWithPolicyDirs("rules"), + rego.WithPolicyDirs("rules"), options.ScannerWithRegoOnly(true), - options.ScannerWithEmbeddedPolicies(false)}, + }, }, { name: "with user namespace", inputFile: "test/testdata/plan.json", - inputRego: ` -# METADATA -# title: Bad buckets are bad -# description: Bad buckets are bad because they are not good. -# scope: package -# schemas: -# - input: schema["input"] -# custom: -# avd_id: AVD-TEST-0123 -# severity: CRITICAL -# short_code: very-bad-misconfig -# recommended_action: "Fix the s3 bucket" - -package user.foobar.ABC001 - -deny[cause] { - bucket := input.aws.s3.buckets[_] - bucket.name.value == "tfsec-plan-testing" - cause := bucket.name -} -`, + check: defaultCheck, options: []options.ScannerOption{ - options.ScannerWithPolicyDirs("rules"), + rego.WithPolicyDirs("rules"), options.ScannerWithRegoOnly(true), - options.ScannerWithEmbeddedPolicies(false), - options.ScannerWithPolicyNamespaces("user"), + rego.WithPolicyNamespaces("user"), }, }, { name: "with templated plan json", inputFile: "test/testdata/plan_with_template.json", - inputRego: ` + check: ` # METADATA # title: Bad buckets are bad # description: Bad buckets are bad because they are not good. @@ -113,25 +91,32 @@ deny[cause] { } `, options: []options.ScannerOption{ - options.ScannerWithPolicyDirs("rules"), + rego.WithPolicyDirs("rules"), + options.ScannerWithRegoOnly(true), + rego.WithPolicyNamespaces("user"), + }, + }, + { + name: "plan with arbitrary name", + inputFile: "test/testdata/arbitrary_name.json", + check: defaultCheck, + options: []options.ScannerOption{ + rego.WithPolicyDirs("rules"), options.ScannerWithRegoOnly(true), - options.ScannerWithEmbeddedPolicies(false), - options.ScannerWithPolicyNamespaces("user"), + rego.WithPolicyNamespaces("user"), }, }, } for _, tc := range testCases { - tc := tc t.Run(tc.name, func(t *testing.T) { b, _ := os.ReadFile(tc.inputFile) fs := testutil.CreateFS(t, map[string]string{ "/code/main.tfplan.json": string(b), - "/rules/test.rego": tc.inputRego, + "/rules/test.rego": tc.check, }) - debugLog := bytes.NewBuffer([]byte{}) - so := append(tc.options, options.ScannerWithDebug(debugLog), options.ScannerWithPolicyFilesystem(fs)) + so := append(tc.options, rego.WithPolicyFilesystem(fs)) scanner := New(so...) results, err := scanner.ScanFS(context.TODO(), fs, "code") @@ -142,9 +127,6 @@ deny[cause] { failure := results.GetFailed()[0] assert.Equal(t, "AVD-TEST-0123", failure.Rule().AVDID) - if t.Failed() { - fmt.Printf("Debug logs:\n%s\n", debugLog.String()) - } }) } } diff --git a/pkg/iac/scanners/terraformplan/tfjson/test/scanner_test.go b/pkg/iac/scanners/terraformplan/tfjson/test/scanner_test.go index 5b684dc77ed0..d70280da437f 100644 --- a/pkg/iac/scanners/terraformplan/tfjson/test/scanner_test.go +++ b/pkg/iac/scanners/terraformplan/tfjson/test/scanner_test.go @@ -8,15 +8,15 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scan" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan/tfjson" ) func Test_Scanning_Plan(t *testing.T) { scanner := tfjson.New( - options.ScannerWithEmbeddedPolicies(true), - options.ScannerWithEmbeddedLibraries(true), + rego.WithEmbeddedPolicies(true), + rego.WithEmbeddedLibraries(true), ) b, _ := os.ReadFile("testdata/plan.json") testFS := fstest.MapFS{ @@ -33,7 +33,7 @@ func Test_Scanning_Plan(t *testing.T) { failedResults = append(failedResults, r) } } - assert.Len(t, results, 15) - assert.Len(t, failedResults, 9) + + assert.Len(t, failedResults, 8) } diff --git a/pkg/iac/scanners/terraformplan/tfjson/test/testdata/arbitrary_name.json b/pkg/iac/scanners/terraformplan/tfjson/test/testdata/arbitrary_name.json new file mode 100644 index 000000000000..e0109b0325a8 --- /dev/null +++ b/pkg/iac/scanners/terraformplan/tfjson/test/testdata/arbitrary_name.json @@ -0,0 +1 @@ +{"format_version":"1.2","terraform_version":"1.8.1","planned_values":{"root_module":{"resources":[{"address":"aws_s3_bucket.this","mode":"managed","type":"aws_s3_bucket","name":"this","provider_name":"registry.opentofu.org/hashicorp/aws","schema_version":0,"values":{"bucket":"tfsec-plan-testing","force_destroy":false,"tags":null,"timeouts":null},"sensitive_values":{"cors_rule":[],"grant":[],"lifecycle_rule":[],"logging":[],"object_lock_configuration":[],"replication_configuration":[],"server_side_encryption_configuration":[],"tags_all":{},"versioning":[],"website":[]}}]}},"resource_drift":[{"address":"aws_security_group.this","mode":"managed","type":"aws_security_group","name":"this","provider_name":"registry.opentofu.org/hashicorp/aws","change":{"actions":["delete"],"before":{"arn":"arn:aws:ec2:us-west-1:145167242158:security-group/sg-0f3ac859864629452","description":"Managed by Terraform","egress":[{"cidr_blocks":[],"description":"","from_port":0,"ipv6_cidr_blocks":[],"prefix_list_ids":[],"protocol":"-1","security_groups":[],"self":false,"to_port":0}],"id":"sg-0f3ac859864629452","ingress":[],"name":"test_sg","name_prefix":"","owner_id":"145167242158","revoke_rules_on_delete":false,"tags":null,"tags_all":{},"timeouts":null,"vpc_id":"vpc-06165c55f5a70fdfa"},"after":null,"after_unknown":{},"before_sensitive":{"egress":[{"cidr_blocks":[],"ipv6_cidr_blocks":[],"prefix_list_ids":[],"security_groups":[]}],"ingress":[],"tags_all":{}},"after_sensitive":false}}],"resource_changes":[{"address":"aws_s3_bucket.this","mode":"managed","type":"aws_s3_bucket","name":"this","provider_name":"registry.opentofu.org/hashicorp/aws","change":{"actions":["create"],"before":null,"after":{"bucket":"tfsec-plan-testing","force_destroy":false,"tags":null,"timeouts":null},"after_unknown":{"acceleration_status":true,"acl":true,"arn":true,"bucket_domain_name":true,"bucket_prefix":true,"bucket_regional_domain_name":true,"cors_rule":true,"grant":true,"hosted_zone_id":true,"id":true,"lifecycle_rule":true,"logging":true,"object_lock_configuration":true,"object_lock_enabled":true,"policy":true,"region":true,"replication_configuration":true,"request_payer":true,"server_side_encryption_configuration":true,"tags_all":true,"versioning":true,"website":true,"website_domain":true,"website_endpoint":true},"before_sensitive":false,"after_sensitive":{"cors_rule":[],"grant":[],"lifecycle_rule":[],"logging":[],"object_lock_configuration":[],"replication_configuration":[],"server_side_encryption_configuration":[],"tags_all":{},"versioning":[],"website":[]}}}],"configuration":{"provider_config":{"aws":{"name":"aws","full_name":"registry.opentofu.org/hashicorp/aws"}},"root_module":{"resources":[{"address":"aws_s3_bucket.this","mode":"managed","type":"aws_s3_bucket","name":"this","provider_config_key":"aws","expressions":{"bucket":{"constant_value":"tfsec-plan-testing"}},"schema_version":0}]}},"timestamp":"2024-08-28T04:27:38Z","errored":false} diff --git a/pkg/iac/scanners/toml/parser/parser.go b/pkg/iac/scanners/toml/parser/parser.go index a8cdcd730f86..95444389d74d 100644 --- a/pkg/iac/scanners/toml/parser/parser.go +++ b/pkg/iac/scanners/toml/parser/parser.go @@ -2,39 +2,23 @@ package parser import ( "context" - "io" "io/fs" "path/filepath" "github.com/BurntSushi/toml" - "github.com/aquasecurity/trivy/pkg/iac/debug" - "github.com/aquasecurity/trivy/pkg/iac/detection" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/log" ) -var _ options.ConfigurableParser = (*Parser)(nil) - type Parser struct { - debug debug.Logger - skipRequired bool -} - -func (p *Parser) SetDebugWriter(writer io.Writer) { - p.debug = debug.New(writer, "toml", "parser") -} - -func (p *Parser) SetSkipRequiredCheck(b bool) { - p.skipRequired = b + logger *log.Logger } // New creates a new parser -func New(opts ...options.ParserOption) *Parser { - p := &Parser{} - for _, opt := range opts { - opt(p) +func New() *Parser { + return &Parser{ + logger: log.WithPrefix("toml parser"), } - return p } func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[string]any, error) { @@ -52,12 +36,10 @@ func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[st if entry.IsDir() { return nil } - if !p.Required(path) { - return nil - } + df, err := p.ParseFile(ctx, target, path) if err != nil { - p.debug.Log("Parse error in '%s': %s", path, err) + p.logger.Error("Parse error", log.FilePath(path), log.Err(err)) return nil } files[path] = df @@ -81,10 +63,3 @@ func (p *Parser) ParseFile(_ context.Context, fsys fs.FS, path string) (any, err } return target, nil } - -func (p *Parser) Required(path string) bool { - if p.skipRequired { - return true - } - return detection.IsType(path, nil, detection.FileTypeTOML) -} diff --git a/pkg/iac/scanners/toml/scanner.go b/pkg/iac/scanners/toml/scanner.go index 0a05fdbac18f..216d0b1f9697 100644 --- a/pkg/iac/scanners/toml/scanner.go +++ b/pkg/iac/scanners/toml/scanner.go @@ -2,99 +2,45 @@ package toml import ( "context" - "io" "io/fs" "sync" - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/scanners/toml/parser" "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" ) var _ options.ConfigurableScanner = (*Scanner)(nil) -type Scanner struct { // nolint: gocritic - debug debug.Logger - options []options.ScannerOption - policyDirs []string - policyReaders []io.Reader - parser *parser.Parser - regoScanner *rego.Scanner - skipRequired bool - sync.Mutex - frameworks []framework.Framework - spec string - loadEmbeddedPolicies bool - loadEmbeddedLibraries bool +type Scanner struct { + mu sync.Mutex + logger *log.Logger + options []options.ScannerOption + parser *parser.Parser + regoScanner *rego.Scanner } -func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} - -func (s *Scanner) SetRegoOnly(bool) {} - -func (s *Scanner) SetFrameworks(frameworks []framework.Framework) { - s.frameworks = frameworks -} - -func (s *Scanner) SetSpec(spec string) { - s.spec = spec -} - -func (s *Scanner) SetUseEmbeddedPolicies(b bool) { - s.loadEmbeddedPolicies = b -} - -func (s *Scanner) SetUseEmbeddedLibraries(b bool) { - s.loadEmbeddedLibraries = b -} +func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} +func (s *Scanner) SetRegoOnly(bool) {} +func (s *Scanner) SetFrameworks(frameworks []framework.Framework) {} func (s *Scanner) Name() string { return "TOML" } -func (s *Scanner) SetPolicyReaders(readers []io.Reader) { - s.policyReaders = readers -} - -func (s *Scanner) SetSkipRequiredCheck(skip bool) { - s.skipRequired = skip -} - -func (s *Scanner) SetDebugWriter(writer io.Writer) { - s.debug = debug.New(writer, "toml", "scanner") -} - -func (s *Scanner) SetTraceWriter(_ io.Writer) {} -func (s *Scanner) SetPerResultTracingEnabled(_ bool) {} - -func (s *Scanner) SetPolicyDirs(dirs ...string) { - s.policyDirs = dirs -} - -func (s *Scanner) SetDataDirs(_ ...string) {} -func (s *Scanner) SetPolicyNamespaces(_ ...string) {} - -func (s *Scanner) SetPolicyFilesystem(_ fs.FS) { - // handled by rego when option is passed on -} - -func (s *Scanner) SetDataFilesystem(_ fs.FS) { - // handled by rego when option is passed on -} -func (s *Scanner) SetRegoErrorLimit(_ int) {} - func NewScanner(opts ...options.ScannerOption) *Scanner { s := &Scanner{ options: opts, + logger: log.WithPrefix("toml scanner"), } for _, opt := range opts { opt(s) } - s.parser = parser.New(options.ParserWithSkipRequiredCheck(s.skipRequired)) + s.parser = parser.New() return s } @@ -130,7 +76,7 @@ func (s *Scanner) ScanFile(ctx context.Context, fsys fs.FS, path string) (scan.R if err != nil { return nil, err } - s.debug.Log("Scanning %s...", path) + s.logger.Debug("Scanning", log.FilePath(path)) return s.scanRego(ctx, fsys, rego.Input{ Path: path, Contents: parsed, @@ -138,14 +84,13 @@ func (s *Scanner) ScanFile(ctx context.Context, fsys fs.FS, path string) (scan.R } func (s *Scanner) initRegoScanner(srcFS fs.FS) (*rego.Scanner, error) { - s.Lock() - defer s.Unlock() + s.mu.Lock() + defer s.mu.Unlock() if s.regoScanner != nil { return s.regoScanner, nil } regoScanner := rego.NewScanner(types.SourceTOML, s.options...) - regoScanner.SetParentDebugLogger(s.debug) - if err := regoScanner.LoadPolicies(s.loadEmbeddedLibraries, s.loadEmbeddedPolicies, srcFS, s.policyDirs, s.policyReaders); err != nil { + if err := regoScanner.LoadPolicies(srcFS); err != nil { return nil, err } s.regoScanner = regoScanner diff --git a/pkg/iac/scanners/toml/scanner_test.go b/pkg/iac/scanners/toml/scanner_test.go index c2fdcda26faa..ea5819448288 100644 --- a/pkg/iac/scanners/toml/scanner_test.go +++ b/pkg/iac/scanners/toml/scanner_test.go @@ -9,8 +9,8 @@ import ( "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/framework" + "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scan" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" ) func Test_BasicScan(t *testing.T) { @@ -52,7 +52,7 @@ deny[res] { `, }) - scanner := NewScanner(options.ScannerWithPolicyDirs("rules")) + scanner := NewScanner(rego.WithPolicyDirs("rules")) results, err := scanner.ScanFS(context.TODO(), fs, "code") require.NoError(t, err) @@ -76,7 +76,9 @@ deny[res] { CustomChecks: scan.CustomChecks{ Terraform: (*scan.TerraformCustomCheck)(nil)}, RegoPackage: "data.builtin.toml.lol", - Frameworks: make(map[framework.Framework][]string), + Frameworks: map[framework.Framework][]string{ + framework.Default: {}, + }, }, results.GetFailed()[0].Rule(), ) diff --git a/pkg/iac/scanners/universal/scanner.go b/pkg/iac/scanners/universal/scanner.go deleted file mode 100644 index 3687da85fc3d..000000000000 --- a/pkg/iac/scanners/universal/scanner.go +++ /dev/null @@ -1,63 +0,0 @@ -package universal - -import ( - "context" - "io/fs" - - "github.com/aquasecurity/trivy/pkg/iac/scan" - "github.com/aquasecurity/trivy/pkg/iac/scanners" - "github.com/aquasecurity/trivy/pkg/iac/scanners/azure/arm" - "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation" - "github.com/aquasecurity/trivy/pkg/iac/scanners/dockerfile" - "github.com/aquasecurity/trivy/pkg/iac/scanners/helm" - "github.com/aquasecurity/trivy/pkg/iac/scanners/json" - "github.com/aquasecurity/trivy/pkg/iac/scanners/kubernetes" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" - "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform" - "github.com/aquasecurity/trivy/pkg/iac/scanners/toml" - "github.com/aquasecurity/trivy/pkg/iac/scanners/yaml" -) - -type nestableFSScanners interface { - scanners.FSScanner - options.ConfigurableScanner -} - -var _ scanners.FSScanner = (*Scanner)(nil) - -type Scanner struct { - fsScanners []nestableFSScanners -} - -func New(opts ...options.ScannerOption) *Scanner { - s := &Scanner{ - fsScanners: []nestableFSScanners{ - terraform.New(opts...), - cloudformation.New(opts...), - dockerfile.NewScanner(opts...), - kubernetes.NewScanner(opts...), - json.NewScanner(opts...), - yaml.NewScanner(opts...), - toml.NewScanner(opts...), - helm.New(opts...), - arm.New(opts...), - }, - } - return s -} - -func (s *Scanner) Name() string { - return "Universal" -} - -func (s *Scanner) ScanFS(ctx context.Context, fsys fs.FS, dir string) (scan.Results, error) { - var results scan.Results - for _, inner := range s.fsScanners { - innerResults, err := inner.ScanFS(ctx, fsys, dir) - if err != nil { - return nil, err - } - results = append(results, innerResults...) - } - return results, nil -} diff --git a/pkg/iac/scanners/yaml/parser/parser.go b/pkg/iac/scanners/yaml/parser/parser.go index 6f113635be4a..9b1f026ae248 100644 --- a/pkg/iac/scanners/yaml/parser/parser.go +++ b/pkg/iac/scanners/yaml/parser/parser.go @@ -6,43 +6,28 @@ import ( "io" "io/fs" "path/filepath" - "strings" "gopkg.in/yaml.v3" - "github.com/aquasecurity/trivy/pkg/iac/debug" - "github.com/aquasecurity/trivy/pkg/iac/detection" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/log" ) -var _ options.ConfigurableParser = (*Parser)(nil) - type Parser struct { - debug debug.Logger - skipRequired bool -} - -func (p *Parser) SetDebugWriter(writer io.Writer) { - p.debug = debug.New(writer, "yaml", "parser") + logger *log.Logger } -func (p *Parser) SetSkipRequiredCheck(b bool) { - p.skipRequired = b -} - -// New creates a new parser -func New(opts ...options.ParserOption) *Parser { - p := &Parser{} - for _, opt := range opts { - opt(p) +// New creates a new YAML parser +func New() *Parser { + return &Parser{ + logger: log.WithPrefix("yaml parser"), } - return p } func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[string][]any, error) { files := make(map[string][]any) if err := fs.WalkDir(target, filepath.ToSlash(path), func(path string, entry fs.DirEntry, err error) error { + select { case <-ctx.Done(): return ctx.Err() @@ -54,12 +39,10 @@ func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[st if entry.IsDir() { return nil } - if !p.Required(path) { - return nil - } + df, err := p.ParseFile(ctx, target, path) if err != nil { - p.debug.Log("Parse error in '%s': %s", path, err) + p.logger.Error("Parse error", log.FilePath(path), log.Err(err)) return nil } files[path] = df @@ -85,15 +68,15 @@ func (p *Parser) ParseFile(_ context.Context, fsys fs.FS, path string) ([]any, e var results []any - marker := "\n---\n" - altMarker := "\r\n---\r\n" - if bytes.Contains(contents, []byte(altMarker)) { + marker := []byte("\n---\n") + altMarker := []byte("\r\n---\r\n") + if bytes.Contains(contents, altMarker) { marker = altMarker } - for _, partial := range strings.Split(string(contents), marker) { + for _, partial := range bytes.Split(contents, marker) { var target any - if err := yaml.Unmarshal([]byte(partial), &target); err != nil { + if err := yaml.Unmarshal(partial, &target); err != nil { return nil, err } results = append(results, target) @@ -101,10 +84,3 @@ func (p *Parser) ParseFile(_ context.Context, fsys fs.FS, path string) ([]any, e return results, nil } - -func (p *Parser) Required(path string) bool { - if p.skipRequired { - return true - } - return detection.IsType(path, nil, detection.FileTypeYAML) -} diff --git a/pkg/iac/scanners/yaml/scanner.go b/pkg/iac/scanners/yaml/scanner.go index 0adc43bbd4cf..565d10df1f83 100644 --- a/pkg/iac/scanners/yaml/scanner.go +++ b/pkg/iac/scanners/yaml/scanner.go @@ -2,98 +2,45 @@ package yaml import ( "context" - "io" "io/fs" "sync" - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/scanners/yaml/parser" "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" ) var _ options.ConfigurableScanner = (*Scanner)(nil) -type Scanner struct { // nolint: gocritic - options []options.ScannerOption - debug debug.Logger - policyDirs []string - policyReaders []io.Reader - parser *parser.Parser - regoScanner *rego.Scanner - skipRequired bool - sync.Mutex - frameworks []framework.Framework - spec string - loadEmbeddedLibraries bool - loadEmbeddedPolicies bool +type Scanner struct { + mu sync.Mutex + options []options.ScannerOption + logger *log.Logger + parser *parser.Parser + regoScanner *rego.Scanner } -func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} - -func (s *Scanner) SetRegoOnly(bool) {} - -func (s *Scanner) SetFrameworks(frameworks []framework.Framework) { - s.frameworks = frameworks -} - -func (s *Scanner) SetSpec(spec string) { - s.spec = spec -} - -func (s *Scanner) SetUseEmbeddedPolicies(b bool) { - s.loadEmbeddedPolicies = b -} - -func (s *Scanner) SetUseEmbeddedLibraries(b bool) { - s.loadEmbeddedLibraries = b -} +func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} +func (s *Scanner) SetRegoOnly(bool) {} +func (s *Scanner) SetFrameworks(frameworks []framework.Framework) {} func (s *Scanner) Name() string { return "YAML" } -func (s *Scanner) SetPolicyReaders(readers []io.Reader) { - s.policyReaders = readers -} - -func (s *Scanner) SetSkipRequiredCheck(skip bool) { - s.skipRequired = skip -} - -func (s *Scanner) SetDebugWriter(writer io.Writer) { - s.debug = debug.New(writer, "yaml", "scanner") -} - -func (s *Scanner) SetTraceWriter(_ io.Writer) {} -func (s *Scanner) SetPerResultTracingEnabled(_ bool) {} - -func (s *Scanner) SetPolicyDirs(dirs ...string) { - s.policyDirs = dirs -} - -func (s *Scanner) SetDataDirs(_ ...string) {} -func (s *Scanner) SetPolicyNamespaces(_ ...string) {} - -func (s *Scanner) SetPolicyFilesystem(_ fs.FS) { - // handled by rego when option is passed on -} -func (s *Scanner) SetDataFilesystem(_ fs.FS) { - // handled by rego when option is passed on -} -func (s *Scanner) SetRegoErrorLimit(_ int) {} - func NewScanner(opts ...options.ScannerOption) *Scanner { s := &Scanner{ options: opts, + logger: log.WithPrefix("yaml scanner"), + parser: parser.New(), } for _, opt := range opts { opt(s) } - s.parser = parser.New(options.ParserWithSkipRequiredCheck(s.skipRequired)) return s } @@ -131,7 +78,7 @@ func (s *Scanner) ScanFile(ctx context.Context, fsys fs.FS, path string) (scan.R if err != nil { return nil, err } - s.debug.Log("Scanning %s...", path) + s.logger.Debug("Scanning", log.String("path", path)) return s.scanRego(ctx, fsys, rego.Input{ Path: path, Contents: parsed, @@ -139,14 +86,13 @@ func (s *Scanner) ScanFile(ctx context.Context, fsys fs.FS, path string) (scan.R } func (s *Scanner) initRegoScanner(srcFS fs.FS) (*rego.Scanner, error) { - s.Lock() - defer s.Unlock() + s.mu.Lock() + defer s.mu.Unlock() if s.regoScanner != nil { return s.regoScanner, nil } regoScanner := rego.NewScanner(types.SourceYAML, s.options...) - regoScanner.SetParentDebugLogger(s.debug) - if err := regoScanner.LoadPolicies(s.loadEmbeddedLibraries, s.loadEmbeddedPolicies, srcFS, s.policyDirs, s.policyReaders); err != nil { + if err := regoScanner.LoadPolicies(srcFS); err != nil { return nil, err } s.regoScanner = regoScanner diff --git a/pkg/iac/scanners/yaml/scanner_test.go b/pkg/iac/scanners/yaml/scanner_test.go index 771bf45ef7d2..b7c4ec623887 100644 --- a/pkg/iac/scanners/yaml/scanner_test.go +++ b/pkg/iac/scanners/yaml/scanner_test.go @@ -9,8 +9,8 @@ import ( "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/framework" + "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scan" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" ) func Test_BasicScan(t *testing.T) { @@ -55,7 +55,7 @@ deny[res] { `, }) - scanner := NewScanner(options.ScannerWithPolicyDirs("rules")) + scanner := NewScanner(rego.WithPolicyDirs("rules")) results, err := scanner.ScanFS(context.TODO(), fs, "code") require.NoError(t, err) @@ -79,7 +79,9 @@ deny[res] { CustomChecks: scan.CustomChecks{ Terraform: (*scan.TerraformCustomCheck)(nil)}, RegoPackage: "data.builtin.yaml.lol", - Frameworks: make(map[framework.Framework][]string), + Frameworks: map[framework.Framework][]string{ + framework.Default: {}, + }, }, results.GetFailed()[0].Rule(), ) diff --git a/pkg/iac/terraform/block.go b/pkg/iac/terraform/block.go index 8b49225b6e69..dbf7352959d0 100644 --- a/pkg/iac/terraform/block.go +++ b/pkg/iac/terraform/block.go @@ -3,6 +3,7 @@ package terraform import ( "fmt" "io/fs" + "strconv" "strings" "github.com/google/uuid" @@ -85,7 +86,7 @@ func NewBlock(hclBlock *hcl.Block, ctx *context.Context, moduleBlock *Block, par } b := Block{ - id: uuid.New().String(), + id: uuid.NewString(), context: ctx, hclBlock: hclBlock, moduleBlock: moduleBlock, @@ -298,6 +299,120 @@ func (b *Block) GetAttribute(name string) *Attribute { return nil } +// GetValueByPath returns the value of the attribute located at the given path. +// 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) + } + + if restPath, ok := strings.CutPrefix(path, "each.value."); ok { + if restPath == "" { + return cty.NilVal + } + + val := b.Context().GetByDot("each.value") + res, err := getValueByPath(val, strings.Split(restPath, ".")) + if err != nil { + return cty.NilVal + } + return res + } + + attr, restPath := b.getAttributeByPath(path) + + if attr == nil { + return cty.NilVal + } + + if !attr.IsIterable() || len(restPath) == 0 { + return attr.Value() + } + + res, err := getValueByPath(attr.Value(), restPath) + if err != nil { + return cty.NilVal + } + return res +} + +func (b *Block) getAttributeByPath(path string) (*Attribute, []string) { + steps := strings.Split(path, ".") + + if len(steps) == 1 { + return b.GetAttribute(steps[0]), nil + } + + var ( + attribute *Attribute + stepIndex int + ) + + for currentBlock := b; currentBlock != nil && stepIndex < len(steps); { + blocks := currentBlock.GetBlocks(steps[stepIndex]) + var nextBlock *Block + if !hasIndex(steps, stepIndex+1) && len(blocks) > 0 { + // if index is not provided then return the first block for backwards compatibility + nextBlock = blocks[0] + } else if len(blocks) > 1 && stepIndex < len(steps)-2 { + // handling the case when there are multiple blocks with the same name, + // e.g. when using a `dynamic` block + indexVal, err := strconv.Atoi(steps[stepIndex+1]) + if err == nil && indexVal >= 0 && indexVal < len(blocks) { + nextBlock = blocks[indexVal] + stepIndex++ + } + } + + if nextBlock == nil { + attribute = currentBlock.GetAttribute(steps[stepIndex]) + } + + currentBlock = nextBlock + stepIndex++ + } + + return attribute, steps[stepIndex:] +} + +func hasIndex(steps []string, idx int) bool { + if idx < 0 || idx >= len(steps) { + return false + } + _, err := strconv.Atoi(steps[idx]) + return err == nil +} + +func getValueByPath(val cty.Value, path []string) (cty.Value, error) { + var err error + for _, step := range path { + switch valType := val.Type(); { + case valType.IsMapType(): + val, err = cty.IndexStringPath(step).Apply(val) + case valType.IsObjectType(): + val, err = cty.GetAttrPath(step).Apply(val) + case valType.IsListType() || valType.IsTupleType(): + var idx int + idx, err = strconv.Atoi(step) + if err != nil { + return cty.NilVal, fmt.Errorf("index %q is not a number", step) + } + val, err = cty.IndexIntPath(idx).Apply(val) + default: + return cty.NilVal, fmt.Errorf( + "unexpected value type %s for path step %q", + valType.FriendlyName(), step, + ) + } + if err != nil { + return cty.NilVal, err + } + } + return val, nil +} + func (b *Block) GetNestedAttribute(name string) (*Attribute, *Block) { parts := strings.Split(name, ".") @@ -446,6 +561,9 @@ func (b *Block) Attributes() map[string]*Attribute { func (b *Block) Values() cty.Value { values := createPresetValues(b) for _, attribute := range b.GetAttributes() { + if attribute.Name() == "for_each" { + continue + } values[attribute.Name()] = attribute.Value() } return cty.ObjectVal(postProcessValues(b, values)) diff --git a/pkg/iac/terraform/context/context.go b/pkg/iac/terraform/context/context.go index 0f4a58de9ac9..14e29a9a8378 100644 --- a/pkg/iac/terraform/context/context.go +++ b/pkg/iac/terraform/context/context.go @@ -46,17 +46,29 @@ func (c *Context) Get(parts ...string) cty.Value { if len(parts) == 0 { return cty.NilVal } - src := c.ctx.Variables - for i, part := range parts { - if i == len(parts)-1 { - return src[part] + + curr := c.ctx.Variables[parts[0]] + if len(parts) == 1 { + return curr + } + + for i, part := range parts[1:] { + if !curr.Type().HasAttribute(part) { + return cty.NilVal + } + + attr := curr.GetAttr(part) + + if i == len(parts)-2 { // iteration from the first element + return attr } - nextPart := src[part] - if nextPart == cty.NilVal { + + if !(attr.IsKnown() && attr.Type().IsObjectType()) { return cty.NilVal } - src = nextPart.AsValueMap() + curr = attr } + return cty.NilVal } @@ -97,13 +109,12 @@ func mergeVars(src cty.Value, parts []string, value cty.Value) cty.Value { } data := make(map[string]cty.Value) - if src.Type().IsObjectType() && !src.IsNull() && src.LengthInt() > 0 { + if isNotEmptyObject(src) { data = src.AsValueMap() - tmp, ok := src.AsValueMap()[parts[0]] - if !ok { - src = cty.ObjectVal(make(map[string]cty.Value)) + if attr, ok := data[parts[0]]; ok { + src = attr } else { - src = tmp + src = cty.EmptyObjectVal } } @@ -118,14 +129,16 @@ func mergeObjects(a, b cty.Value) cty.Value { for key, val := range a.AsValueMap() { output[key] = val } - for key, val := range b.AsValueMap() { - old, exists := output[key] - if exists && isNotEmptyObject(old) && isNotEmptyObject(val) { - output[key] = mergeObjects(old, val) + b.ForEachElement(func(key, val cty.Value) (stop bool) { + k := key.AsString() + old := output[k] + if old.IsKnown() && isNotEmptyObject(old) && isNotEmptyObject(val) { + output[k] = mergeObjects(old, val) } else { - output[key] = val + output[k] = val } - } + return false + }) return cty.ObjectVal(output) } diff --git a/pkg/iac/terraform/context/context_test.go b/pkg/iac/terraform/context/context_test.go index 8185d7b9892d..dfd8e05e5fac 100644 --- a/pkg/iac/terraform/context/context_test.go +++ b/pkg/iac/terraform/context/context_test.go @@ -52,6 +52,43 @@ func Test_ContextVariablesPreservation(t *testing.T) { } +func Test_SetWithMerge(t *testing.T) { + hctx := hcl.EvalContext{ + Variables: map[string]cty.Value{ + "my": cty.ObjectVal(map[string]cty.Value{ + "someValue": cty.ObjectVal(map[string]cty.Value{ + "foo": cty.StringVal("test"), + "bar": cty.ObjectVal(map[string]cty.Value{ + "foo": cty.StringVal("test"), + }), + }), + }), + }, + } + + ctx := NewContext(&hctx, nil) + + val := cty.ObjectVal(map[string]cty.Value{ + "foo2": cty.StringVal("test2"), + "bar": cty.ObjectVal(map[string]cty.Value{ + "foo2": cty.StringVal("test2"), + }), + }) + + ctx.Set(val, "my", "someValue") + got := ctx.Get("my", "someValue") + expected := cty.ObjectVal(map[string]cty.Value{ + "foo": cty.StringVal("test"), + "foo2": cty.StringVal("test2"), + "bar": cty.ObjectVal(map[string]cty.Value{ + "foo": cty.StringVal("test"), + "foo2": cty.StringVal("test2"), + }), + }) + + assert.Equal(t, expected, got) +} + func Test_ContextVariablesPreservationByDot(t *testing.T) { underlying := &hcl.EvalContext{} diff --git a/pkg/iac/terraform/presets.go b/pkg/iac/terraform/presets.go index d10d795a86d7..b29a5fc9c181 100644 --- a/pkg/iac/terraform/presets.go +++ b/pkg/iac/terraform/presets.go @@ -19,13 +19,16 @@ func createPresetValues(b *Block) map[string]cty.Value { presets["arn"] = cty.StringVal(b.ID()) } - // workaround for weird iam feature switch b.TypeLabel() { + // workaround for weird iam feature case "aws_iam_policy_document": presets["json"] = cty.StringVal(b.ID()) // If the user leaves the name blank, Terraform will automatically generate a unique name case "aws_launch_template": presets["name"] = cty.StringVal(uuid.New().String()) + // allow referencing the current region name + case "aws_region": + presets["name"] = cty.StringVal("current-region") } return presets diff --git a/pkg/iac/terraform/resource_block.go b/pkg/iac/terraform/resource_block.go index 339843423112..abce0fcb040e 100644 --- a/pkg/iac/terraform/resource_block.go +++ b/pkg/iac/terraform/resource_block.go @@ -3,7 +3,6 @@ package terraform import ( "bytes" "fmt" - "regexp" "strings" "text/template" ) @@ -106,19 +105,14 @@ func renderPrimitive(val any) string { func parseStringPrimitive(input string) string { // we must escape templating // ref: https://developer.hashicorp.com/terraform/language/expressions/strings#escape-sequences-1 - r := regexp.MustCompile(`((\$|\%)\{.+\})`) - ff := r.ReplaceAllStringFunc(input, func(s string) string { - s = strings.Replace(s, "$", "$$", 1) - s = strings.Replace(s, "%", "%%", 1) - return s - }) - if strings.Contains(ff, "\n") { + input = escapeSpecialSequences(input) + if strings.Contains(input, "\n") { return fmt.Sprintf(`< 0 && rune(input[i-1]) == r { + continue + } + + sb.WriteRune(r) + } else { + sb.WriteRune(r) + } + } + return sb.String() +} diff --git a/pkg/iac/terraform/resource_block_test.go b/pkg/iac/terraform/resource_block_test.go new file mode 100644 index 000000000000..37fb1d32fdcb --- /dev/null +++ b/pkg/iac/terraform/resource_block_test.go @@ -0,0 +1,62 @@ +package terraform + +import ( + "testing" + + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_EscapeSpecialSequences(t *testing.T) { + tests := []struct { + name string + inp string + expected string + }{ + { + name: "without special sequences", + inp: `"hello world\\"`, + expected: `"hello world\\"`, + }, + { + name: "interpolation", + inp: `"Hello, ${var.name}!"`, + expected: `"Hello, $${var.name}!"`, + }, + { + name: "directive", + inp: `"Hello, %{ if true }foo%{ else }bar%{ endif }!"`, + expected: `"Hello, %%{ if true }foo%%{ else }bar%%{ endif }!"`, + }, + { + name: "interpolation already escaped", + inp: `"Hello, $${var.name}!"`, + expected: `"Hello, $${var.name}!"`, + }, + { + name: "start with special character", + inp: `${var.name}!"`, + expected: `$${var.name}!"`, + }, + { + name: "grok pattern", + inp: "# Grok Pattern Template\ngrok_pattern = \"%{TIMESTAMP_ISO8601:time} \\\\[%{NUMBER:pid}\\\\] %{GREEDYDATA:message}\"", + expected: "# Grok Pattern Template\ngrok_pattern = \"%%{TIMESTAMP_ISO8601:time} \\\\[%%{NUMBER:pid}\\\\] %%{GREEDYDATA:message}\"", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := escapeSpecialSequences(tt.inp) + assert.Equal(t, tt.expected, got) + + // We make sure that the characters are properly escaped + _, diag := hclsyntax.ParseTemplate([]byte(got), "", hcl.InitialPos) + if diag.HasErrors() { + require.NoError(t, diag) + } + }) + } +} diff --git a/pkg/iac/types/range.go b/pkg/iac/types/range.go index c06a2bf6fe1a..754ee29c9675 100755 --- a/pkg/iac/types/range.go +++ b/pkg/iac/types/range.go @@ -146,3 +146,10 @@ func (r Range) GetFS() fs.FS { func (r Range) GetSourcePrefix() string { return r.sourcePrefix } + +func (r Range) Validate() error { + if r.startLine < 0 || r.endLine < 0 || r.startLine > r.endLine { + return fmt.Errorf("invalid range: %s", r.String()) + } + return nil +} diff --git a/pkg/javadb/client.go b/pkg/javadb/client.go index ca055a0c333f..835730109b02 100644 --- a/pkg/javadb/client.go +++ b/pkg/javadb/client.go @@ -26,12 +26,15 @@ const ( mediaType = "application/vnd.aquasec.trivy.javadb.layer.v1.tar+gzip" ) -var DefaultRepository = fmt.Sprintf("%s:%d", "ghcr.io/aquasecurity/trivy-java-db", SchemaVersion) +var ( + // GitHub Container Registry + DefaultGHCRRepository = fmt.Sprintf("%s:%d", "ghcr.io/aquasecurity/trivy-java-db", SchemaVersion) +) var updater *Updater type Updater struct { - repo name.Reference + repos []name.Reference dbDir string skip bool quiet bool @@ -40,31 +43,24 @@ type Updater struct { } func (u *Updater) Update() error { - dbDir := u.dbDir - metac := db.NewMetadata(dbDir) + ctx := log.WithContextPrefix(context.Background(), log.PrefixJavaDB) + metac := db.NewMetadata(u.dbDir) meta, err := metac.Get() if err != nil { if !errors.Is(err, os.ErrNotExist) { return xerrors.Errorf("Java DB metadata error: %w", err) } else if u.skip { - log.Error("The first run cannot skip downloading Java DB") + log.ErrorContext(ctx, "The first run cannot skip downloading Java DB") return xerrors.New("'--skip-java-db-update' cannot be specified on the first run") } } - if (meta.Version != SchemaVersion || meta.NextUpdate.Before(time.Now().UTC())) && !u.skip { + if (meta.Version != SchemaVersion || !u.isNewDB(ctx, meta)) && !u.skip { // Download DB - log.Info("Java DB Repository", log.Any("repository", u.repo)) - log.Info("Downloading the Java DB...") - // TODO: support remote options - var a *oci.Artifact - if a, err = oci.NewArtifact(u.repo.String(), u.quiet, u.registryOption); err != nil { - return xerrors.Errorf("oci error: %w", err) - } - if err = a.Download(context.Background(), dbDir, oci.DownloadOption{MediaType: mediaType}); err != nil { - return xerrors.Errorf("DB download error: %w", err) + if err := u.downloadDB(ctx); err != nil { + return xerrors.Errorf("OCI artifact error: %w", err) } // Parse the newly downloaded metadata.json @@ -78,16 +74,45 @@ func (u *Updater) Update() error { if err = metac.Update(meta); err != nil { return xerrors.Errorf("Java DB metadata update error: %w", err) } - log.Info("The Java DB is cached for 3 days. If you want to update the database more frequently, " + + log.InfoContext(ctx, "Java DB is cached for 3 days. If you want to update the database more frequently, "+ `"trivy clean --java-db" command clears the DB cache.`) } return nil } -func Init(cacheDir string, javaDBRepository name.Reference, skip, quiet bool, registryOption ftypes.RegistryOptions) { +func (u *Updater) isNewDB(ctx context.Context, meta db.Metadata) bool { + now := time.Now().UTC() + if now.Before(meta.NextUpdate) { + log.DebugContext(ctx, "Java DB update was skipped because the local Java DB is the latest") + return true + } + + if now.Before(meta.DownloadedAt.Add(time.Hour * 24)) { // 1 day + log.DebugContext(ctx, "Java DB update was skipped because the local Java DB was downloaded during the last day") + return true + } + return false +} + +func (u *Updater) downloadDB(ctx context.Context) error { + log.InfoContext(ctx, "Downloading Java DB...") + + artifacts := oci.NewArtifacts(u.repos, u.registryOption) + downloadOpt := oci.DownloadOption{ + MediaType: mediaType, + Quiet: u.quiet, + } + if err := artifacts.Download(ctx, u.dbDir, downloadOpt); err != nil { + return xerrors.Errorf("failed to download Java DB: %w", err) + } + + return nil +} + +func Init(cacheDir string, javaDBRepositories []name.Reference, skip, quiet bool, registryOption ftypes.RegistryOptions) { updater = &Updater{ - repo: javaDBRepository, + repos: javaDBRepositories, dbDir: dbDir(cacheDir), skip: skip, quiet: quiet, diff --git a/pkg/k8s/commands/cluster.go b/pkg/k8s/commands/cluster.go index 179a089523df..11dd5e0d3a00 100644 --- a/pkg/k8s/commands/cluster.go +++ b/pkg/k8s/commands/cluster.go @@ -37,7 +37,7 @@ func clusterRun(ctx context.Context, opts flag.Options, cluster k8s.Cluster) err trivyk8s.WithExcludeOwned(opts.ExcludeOwned), } if opts.Scanners.AnyEnabled(types.MisconfigScanner) && !opts.DisableNodeCollector { - artifacts, err = trivyk8s.New(cluster, k8sOpts...).ListArtifactAndNodeInfo(ctx, nodeCollectorOptions(opts)...) + artifacts, err = trivyk8s.New(cluster, k8sOpts...).ListArtifactAndNodeInfo(ctx, nodeCollectorOptions(ctx, opts)...) if err != nil { return xerrors.Errorf("get k8s artifacts with node info error: %w", err) } @@ -59,20 +59,17 @@ func clusterRun(ctx context.Context, opts flag.Options, cluster k8s.Cluster) err return runner.run(ctx, artifacts) } -func nodeCollectorOptions(opts flag.Options) []trivyk8s.NodeCollectorOption { +func nodeCollectorOptions(ctx context.Context, opts flag.Options) []trivyk8s.NodeCollectorOption { nodeCollectorOptions := []trivyk8s.NodeCollectorOption{ trivyk8s.WithScanJobNamespace(opts.NodeCollectorNamespace), trivyk8s.WithIgnoreLabels(opts.ExcludeNodes), trivyk8s.WithScanJobImageRef(opts.NodeCollectorImageRef), - trivyk8s.WithTolerations(opts.Tolerations)} - - contentPath, err := operation.InitBuiltinPolicies(context.Background(), - opts.CacheDir, - opts.Quiet, - opts.SkipCheckUpdate, - opts.MisconfOptions.ChecksBundleRepository, - opts.RegistryOpts()) + trivyk8s.WithTolerations(opts.Tolerations), + } + ctx = log.WithContextPrefix(ctx, log.PrefixMisconfiguration) + contentPath, err := operation.InitBuiltinChecks(ctx, opts.CacheDir, opts.Quiet, opts.SkipCheckUpdate, + opts.MisconfOptions.ChecksBundleRepository, opts.RegistryOpts()) if err != nil { log.Error("Falling back to embedded checks", log.Err(err)) nodeCollectorOptions = append(nodeCollectorOptions, diff --git a/pkg/licensing/category.go b/pkg/licensing/expression/category.go similarity index 96% rename from pkg/licensing/category.go rename to pkg/licensing/expression/category.go index f99ccdc52af4..c32f228c07d8 100644 --- a/pkg/licensing/category.go +++ b/pkg/licensing/expression/category.go @@ -1,4 +1,4 @@ -package licensing +package expression // Canonical names of the licenses. // ported from https://github.com/google/licenseclassifier/blob/7c62d6fe8d3aa2f39c4affb58c9781d9dc951a2d/license_type.go#L24-L177 @@ -165,6 +165,28 @@ const ( ZPL21 = "ZPL-2.1" ) +// GNU licenses always use either the suffix “-only“ or the suffix “-or-later“ +// https://spdx.dev/learn/handling-license-info/ +var GnuLicenses = []string{ + AGPL10, + AGPL30, + GFDL11WithInvariants, + GFDL11NoInvariants, + GFDL11, + GFDL12WithInvariants, + GFDL12NoInvariants, + GFDL12, + GFDL13WithInvariants, + GFDL13NoInvariants, + GFDL13, + GPL10, + GPL20, + GPL30, + LGPL20, + LGPL21, + LGPL30, +} + var ( // ForbiddenLicenses - Licenses that are forbidden to be used. // ported from https://github.com/google/licenseclassifier/blob/7c62d6fe8d3aa2f39c4affb58c9781d9dc951a2d/license_type.go#L340-L364 diff --git a/pkg/licensing/expression/expression.go b/pkg/licensing/expression/expression.go index 59848e57eef7..6f3c0054f929 100644 --- a/pkg/licensing/expression/expression.go +++ b/pkg/licensing/expression/expression.go @@ -11,7 +11,7 @@ var ( ErrInvalidExpression = xerrors.New("invalid expression error") ) -type NormalizeFunc func(license string) string +type NormalizeFunc func(license string) SimpleExpr func parse(license string) (Expression, error) { l := NewLexer(strings.NewReader(license)) @@ -38,7 +38,9 @@ func normalize(expr Expression, fn ...NormalizeFunc) Expression { switch e := expr.(type) { case SimpleExpr: for _, f := range fn { - e.license = f(e.license) + normalized := f(e.License) + e.License = normalized.License + e.HasPlus = e.HasPlus || normalized.HasPlus } return e case CompoundExpr: @@ -52,10 +54,10 @@ func normalize(expr Expression, fn ...NormalizeFunc) Expression { } // NormalizeForSPDX replaces ' ' to '-' in license-id. -// SPDX license MUST NOT be white space between a license-id. +// SPDX license MUST NOT have white space between a license-id. // There MUST be white space on either side of the operator "WITH". // ref: https://spdx.github.io/spdx-spec/v2.3/SPDX-license-expressions -func NormalizeForSPDX(s string) string { +func NormalizeForSPDX(s string) SimpleExpr { var b strings.Builder for _, c := range s { switch { @@ -70,7 +72,7 @@ func NormalizeForSPDX(s string) string { _, _ = b.WriteRune('-') } } - return b.String() + return SimpleExpr{License: b.String(), HasPlus: false} } func isAlphabet(r rune) bool { diff --git a/pkg/licensing/expression/expression_test.go b/pkg/licensing/expression/expression_test.go index 0e3eaa7ae7cc..12b715e7ad30 100644 --- a/pkg/licensing/expression/expression_test.go +++ b/pkg/licensing/expression/expression_test.go @@ -37,7 +37,7 @@ func TestNormalize(t *testing.T) { { name: "upper", license: "LGPL-2.1-only OR MIT", - fn: strings.ToUpper, + fn: func(license string) SimpleExpr { return SimpleExpr{strings.ToUpper(license), false} }, want: "LGPL-2.1-ONLY OR MIT", }, } diff --git a/pkg/licensing/expression/parser.go.y b/pkg/licensing/expression/parser.go.y index 82bd11028619..85a90a5a73ce 100644 --- a/pkg/licensing/expression/parser.go.y +++ b/pkg/licensing/expression/parser.go.y @@ -32,17 +32,17 @@ license simple : IDENT { - $$ = SimpleExpr{license: $1.literal} + $$ = SimpleExpr{License: $1.literal} } | simple IDENT /* e.g. Public Domain */ { - $$ = SimpleExpr{license: $1.String() + " " + $2.literal} + $$ = SimpleExpr{License: $1.String() + " " + $2.literal} } plus : simple '+' { - $$ = SimpleExpr{license: $1.String(), hasPlus: true} + $$ = SimpleExpr{License: $1.String(), HasPlus: true} } compound diff --git a/pkg/licensing/expression/parser_gen.go b/pkg/licensing/expression/parser_gen.go index 32f65276374e..57c51b320e5d 100644 --- a/pkg/licensing/expression/parser_gen.go +++ b/pkg/licensing/expression/parser_gen.go @@ -452,19 +452,19 @@ yydefault: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:34 { - yyVAL.expr = SimpleExpr{license: yyDollar[1].token.literal} + yyVAL.expr = SimpleExpr{License: yyDollar[1].token.literal} } case 3: yyDollar = yyS[yypt-2 : yypt+1] //line parser.go.y:38 { - yyVAL.expr = SimpleExpr{license: yyDollar[1].expr.String() + " " + yyDollar[2].token.literal} + yyVAL.expr = SimpleExpr{License: yyDollar[1].expr.String() + " " + yyDollar[2].token.literal} } case 4: yyDollar = yyS[yypt-2 : yypt+1] //line parser.go.y:44 { - yyVAL.expr = SimpleExpr{license: yyDollar[1].expr.String(), hasPlus: true} + yyVAL.expr = SimpleExpr{License: yyDollar[1].expr.String(), HasPlus: true} } case 5: yyDollar = yyS[yypt-1 : yypt+1] diff --git a/pkg/licensing/expression/parser_test.go b/pkg/licensing/expression/parser_test.go index 522777085b5b..acbc09a53545 100644 --- a/pkg/licensing/expression/parser_test.go +++ b/pkg/licensing/expression/parser_test.go @@ -20,7 +20,7 @@ func TestParse(t *testing.T) { name: "single license", input: "Public Domain", want: SimpleExpr{ - license: "Public Domain", + License: "Public Domain", }, wantStr: "Public Domain", }, @@ -28,7 +28,7 @@ func TestParse(t *testing.T) { name: "tag:value license", input: "DocumentRef-spdx-tool-1.2:LicenseRef-MIT-Style-2", want: SimpleExpr{ - license: "DocumentRef-spdx-tool-1.2:LicenseRef-MIT-Style-2", + License: "DocumentRef-spdx-tool-1.2:LicenseRef-MIT-Style-2", }, wantStr: "DocumentRef-spdx-tool-1.2:LicenseRef-MIT-Style-2", }, @@ -36,8 +36,8 @@ func TestParse(t *testing.T) { name: "symbols", input: "Public ._-+", want: SimpleExpr{ - license: "Public ._-", - hasPlus: true, + License: "Public ._-", + HasPlus: true, }, wantStr: "Public ._-+", }, @@ -47,7 +47,7 @@ func TestParse(t *testing.T) { want: CompoundExpr{ left: CompoundExpr{ left: SimpleExpr{ - license: "Public Domain", + License: "Public Domain", }, conjunction: Token{ token: AND, @@ -55,15 +55,15 @@ func TestParse(t *testing.T) { }, right: CompoundExpr{ left: SimpleExpr{ - license: "GPLv2", - hasPlus: true, + License: "GPLv2", + HasPlus: true, }, conjunction: Token{ token: OR, literal: "or", }, right: SimpleExpr{ - license: "AFL", + License: "AFL", }, }, }, @@ -73,15 +73,15 @@ func TestParse(t *testing.T) { }, right: CompoundExpr{ left: SimpleExpr{ - license: "LGPLv2", - hasPlus: true, + License: "LGPLv2", + HasPlus: true, }, conjunction: Token{ token: WITH, literal: "with", }, right: SimpleExpr{ - license: "distribution exceptions", + License: "distribution exceptions", }, }, }, @@ -92,7 +92,7 @@ func TestParse(t *testing.T) { input: "Public Domain AND ( GPLv2+ or AFL AND ( CC0 or LGPL1.0) )", want: CompoundExpr{ left: SimpleExpr{ - license: "Public Domain", + License: "Public Domain", }, conjunction: Token{ token: AND, @@ -100,8 +100,8 @@ func TestParse(t *testing.T) { }, right: CompoundExpr{ left: SimpleExpr{ - license: "GPLv2", - hasPlus: true, + License: "GPLv2", + HasPlus: true, }, conjunction: Token{ token: OR, @@ -109,7 +109,7 @@ func TestParse(t *testing.T) { }, right: CompoundExpr{ left: SimpleExpr{ - license: "AFL", + License: "AFL", }, conjunction: Token{ token: AND, @@ -117,14 +117,14 @@ func TestParse(t *testing.T) { }, right: CompoundExpr{ left: SimpleExpr{ - license: "CC0", + License: "CC0", }, conjunction: Token{ token: OR, literal: "or", }, right: SimpleExpr{ - license: "LGPL1.0", + License: "LGPL1.0", }, }, }, diff --git a/pkg/licensing/expression/types.go b/pkg/licensing/expression/types.go index f344c610ab4d..acf2473ed4e8 100644 --- a/pkg/licensing/expression/types.go +++ b/pkg/licensing/expression/types.go @@ -3,30 +3,8 @@ package expression import ( "fmt" "slices" - - "github.com/aquasecurity/trivy/pkg/licensing" ) -var versioned = []string{ - licensing.AGPL10, - licensing.AGPL30, - licensing.GFDL11WithInvariants, - licensing.GFDL11NoInvariants, - licensing.GFDL11, - licensing.GFDL12WithInvariants, - licensing.GFDL12NoInvariants, - licensing.GFDL12, - licensing.GFDL13WithInvariants, - licensing.GFDL13NoInvariants, - licensing.GFDL13, - licensing.GPL10, - licensing.GPL20, - licensing.GPL30, - licensing.LGPL20, - licensing.LGPL21, - licensing.LGPL30, -} - type Expression interface { String() string } @@ -37,24 +15,24 @@ type Token struct { } type SimpleExpr struct { - license string - hasPlus bool + License string + HasPlus bool } func (s SimpleExpr) String() string { - if slices.Contains(versioned, s.license) { - if s.hasPlus { + if slices.Contains(GnuLicenses, s.License) { + if s.HasPlus { // e.g. AGPL-1.0-or-later - return s.license + "-or-later" + return s.License + "-or-later" } // e.g. GPL-1.0-only - return s.license + "-only" + return s.License + "-only" } - if s.hasPlus { - return s.license + "+" + if s.HasPlus { + return s.License + "+" } - return s.license + return s.License } type CompoundExpr struct { diff --git a/pkg/licensing/normalize.go b/pkg/licensing/normalize.go index 07105e329357..4ce294275214 100644 --- a/pkg/licensing/normalize.go +++ b/pkg/licensing/normalize.go @@ -3,162 +3,577 @@ package licensing import ( "regexp" "strings" + + expr "github.com/aquasecurity/trivy/pkg/licensing/expression" ) -var mapping = map[string]string{ - // GPL - "GPL-1": GPL10, - "GPL-1+": GPL10, - "GPL 1.0": GPL10, - "GPL 1": GPL10, - "GPL2": GPL20, - "GPL 2.0": GPL20, - "GPL 2": GPL20, - "GPL-2": GPL20, - "GPL-2.0-ONLY": GPL20, - "GPL2+": GPL20, - "GPLV2": GPL20, - "GPLV2+": GPL20, - "GPL-2+": GPL20, - "GPL-2.0+": GPL20, - "GPL-2.0-OR-LATER": GPL20, - "GPL-2+ WITH AUTOCONF EXCEPTION": GPL20withautoconfexception, - "GPL-2+-with-bison-exception": GPL20withbisonexception, - "GPL3": GPL30, - "GPL 3.0": GPL30, - "GPL 3": GPL30, - "GPLV3": GPL30, - "GPLV3+": GPL30, - "GPL-3": GPL30, - "GPL-3.0-ONLY": GPL30, - "GPL3+": GPL30, - "GPL-3+": GPL30, - "GPL-3.0-OR-LATER": GPL30, - "GPL-3+ WITH AUTOCONF EXCEPTION": GPL30withautoconfexception, - "GPL-3+-WITH-BISON-EXCEPTION": GPL20withbisonexception, - "GPL": GPL30, // 2? 3? - - // LGPL - "LGPL2": LGPL20, - "LGPL 2": LGPL20, - "LGPL 2.0": LGPL20, - "LGPL-2": LGPL20, - "LGPL2+": LGPL20, - "LGPL-2+": LGPL20, - "LGPL-2.0+": LGPL20, - "LGPL-2.1": LGPL21, - "LGPL 2.1": LGPL21, - "LGPL-2.1+": LGPL21, - "LGPLV2.1+": LGPL21, - "LGPL-3": LGPL30, - "LGPL 3": LGPL30, - "LGPL-3+": LGPL30, - "LGPL": LGPL30, // 2? 3? - "GNU LESSER": LGPL30, // 2? 3? - - // MPL - "MPL1.0": MPL10, - "MPL1": MPL10, - "MPL 1.0": MPL10, - "MPL 1": MPL10, - "MPL2.0": MPL20, - "MPL 2.0": MPL20, - "MPL2": MPL20, - "MPL 2": MPL20, - - // BSD - "BSD": BSD3Clause, // 2? 3? - "BSD-2-CLAUSE": BSD2Clause, - "BSD-3-CLAUSE": BSD3Clause, - "BSD-4-CLAUSE": BSD4Clause, - "BSD 2 CLAUSE": BSD2Clause, - "BSD 2-CLAUSE": BSD2Clause, - "BSD 2-CLAUSE LICENSE": BSD2Clause, - "THE BSD 2-CLAUSE LICENSE": BSD2Clause, - "THE 2-CLAUSE BSD LICENSE": BSD2Clause, - "TWO-CLAUSE BSD-STYLE LICENSE": BSD2Clause, - "BSD 3 CLAUSE": BSD3Clause, - "BSD 3-CLAUSE": BSD3Clause, - "BSD 3-CLAUSE LICENSE": BSD3Clause, - "THE BSD 3-CLAUSE LICENSE": BSD3Clause, - "BSD 3-CLAUSE \"NEW\" OR \"REVISED\" LICENSE (BSD-3-CLAUSE)": BSD3Clause, - "ECLIPSE DISTRIBUTION LICENSE (NEW BSD LICENSE)": BSD3Clause, - "NEW BSD LICENSE": BSD3Clause, - "MODIFIED BSD LICENSE": BSD3Clause, - "REVISED BSD": BSD3Clause, - "REVISED BSD LICENSE": BSD3Clause, - "THE NEW BSD LICENSE": BSD3Clause, - "3-CLAUSE BSD LICENSE": BSD3Clause, - "BSD 3-CLAUSE NEW LICENSE": BSD3Clause, - "BSD LICENSE": BSD3Clause, - "EDL 1.0": BSD3Clause, - "ECLIPSE DISTRIBUTION LICENSE - V 1.0": BSD3Clause, - "ECLIPSE DISTRIBUTION LICENSE V. 1.0": BSD3Clause, - "ECLIPSE DISTRIBUTION LICENSE V1.0": BSD3Clause, - "THE BSD LICENSE": BSD4Clause, - - // APACHE - "APACHE LICENSE": Apache10, - "APACHE SOFTWARE LICENSES": Apache10, - "APACHE": Apache20, // 1? 2? - "APACHE 2.0": Apache20, - "APACHE 2": Apache20, - "APACHE V2": Apache20, - "APACHE 2.0 LICENSE": Apache20, - "APACHE SOFTWARE LICENSE, VERSION 2.0": Apache20, - "THE APACHE SOFTWARE LICENSE, VERSION 2.0": Apache20, - "APACHE LICENSE (V2.0)": Apache20, - "APACHE LICENSE 2.0": Apache20, - "APACHE LICENSE V2.0": Apache20, - "APACHE LICENSE VERSION 2.0": Apache20, - "APACHE LICENSE, VERSION 2.0": Apache20, - "APACHE PUBLIC LICENSE 2.0": Apache20, - "APACHE SOFTWARE LICENSE - VERSION 2.0": Apache20, - "THE APACHE LICENSE, VERSION 2.0": Apache20, - "APACHE-2.0 LICENSE": Apache20, - "APACHE 2 STYLE LICENSE": Apache20, - "ASF 2.0": Apache20, - - // CC0-1.0 - "CC0 1.0 UNIVERSAL": CC010, - "PUBLIC DOMAIN, PER CREATIVE COMMONS CC0": CC010, - - // CDDL 1.0 - "CDDL 1.0": CDDL10, - "CDDL LICENSE": CDDL10, - "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) VERSION 1.0": CDDL10, - "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) V1.0": CDDL10, - - // CDDL 1.1 - "CDDL 1.1": CDDL11, - "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) VERSION 1.1": CDDL11, - "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) V1.1": CDDL11, - - // EPL 1.0 - "ECLIPSE PUBLIC LICENSE - VERSION 1.0": EPL10, - "ECLIPSE PUBLIC LICENSE (EPL) 1.0": EPL10, - "ECLIPSE PUBLIC LICENSE V1.0": EPL10, - "ECLIPSE PUBLIC LICENSE, VERSION 1.0": EPL10, - "ECLIPSE PUBLIC LICENSE - V 1.0": EPL10, - "ECLIPSE PUBLIC LICENSE - V1.0": EPL10, - "ECLIPSE PUBLIC LICENSE (EPL), VERSION 1.0": EPL10, - - // EPL 2.0 - "ECLIPSE PUBLIC LICENSE - VERSION 2.0": EPL20, - "EPL 2.0": EPL20, - "ECLIPSE PUBLIC LICENSE - V 2.0": EPL20, - "ECLIPSE PUBLIC LICENSE V2.0": EPL20, - "ECLIPSE PUBLIC LICENSE, VERSION 2.0": EPL20, - "THE ECLIPSE PUBLIC LICENSE VERSION 2.0": EPL20, - "ECLIPSE PUBLIC LICENSE V. 2.0": EPL20, - - "RUBY": Ruby, - "ZLIB": Zlib, - - // Public Domain - "PUBLIC DOMAIN": Unlicense, +func licence(name string, hasPlus bool) expr.SimpleExpr { + return expr.SimpleExpr{License: name, HasPlus: hasPlus} +} + +var mapping = map[string]expr.SimpleExpr{ + // Simple mappings (i.e. that could be parsed by SpdxExpression.parse, at least without space) + // modified from https://github.com/oss-review-toolkit/ort/blob/fc5389c2cfd9c8b009794c8a11f5c91321b7a730/utils/spdx/src/main/resources/simple-license-mapping.yml + + // Ambiguous simple mappings (mapping reason not obvious without additional information) + "AFL": licence(expr.AFL30, false), + "AGPL": licence(expr.AGPL30, false), + "AL-2": licence(expr.Apache20, false), + "AL-2.0": licence(expr.Apache20, false), + "APACHE": licence(expr.Apache20, false), + "APACHE-STYLE": licence(expr.Apache20, false), + "ARTISTIC": licence(expr.Artistic20, false), + "ASL": licence(expr.Apache20, false), + "BSD": licence(expr.BSD3Clause, false), + "BSD*": licence(expr.BSD3Clause, false), + "BSD-LIKE": licence(expr.BSD3Clause, false), + "BSD-STYLE": licence(expr.BSD3Clause, false), + "BSD-VARIANT": licence(expr.BSD3Clause, false), + "CDDL": licence(expr.CDDL10, false), + "ECLIPSE": licence(expr.EPL10, false), + "EPL": licence(expr.EPL10, false), + "EUPL": licence(expr.EUPL10, false), + "FDL": licence(expr.GFDL13, true), + "GFDL": licence(expr.GFDL13, true), + "GPL": licence(expr.GPL20, true), + "LGPL": licence(expr.LGPL20, true), + "MPL": licence(expr.MPL20, false), + "NETSCAPE": licence(expr.NPL11, false), + "PYTHON": licence(expr.Python20, false), + "ZOPE": licence(expr.ZPL21, false), + + // Non-ambiguous simple mappings + "0BSD": licence(expr.ZeroBSD, false), + "AFL-1.1": licence(expr.AFL11, false), + "AFL-1.2": licence(expr.AFL12, false), + "AFL-2": licence(expr.AFL20, false), + "AFL-2.0": licence(expr.AFL20, false), + "AFL-2.1": licence(expr.AFL21, false), + "AFL-3.0": licence(expr.AFL30, false), + "AGPL-1.0": licence(expr.AGPL10, false), + "AGPL-3.0": licence(expr.AGPL30, false), + "APACHE-1": licence(expr.Apache10, false), + "APACHE-1.0": licence(expr.Apache10, false), + "APACHE-1.1": licence(expr.Apache11, false), + "APACHE-2": licence(expr.Apache20, false), + "APACHE-2.0": licence(expr.Apache20, false), + "APL-2": licence(expr.Apache20, false), + "APL-2.0": licence(expr.Apache20, false), + "APSL-1.0": licence(expr.APSL10, false), + "APSL-1.1": licence(expr.APSL11, false), + "APSL-1.2": licence(expr.APSL12, false), + "APSL-2.0": licence(expr.APSL20, false), + "ARTISTIC-1.0": licence(expr.Artistic10, false), + "ARTISTIC-1.0-CL-8": licence(expr.Artistic10cl8, false), + "ARTISTIC-1.0-PERL": licence(expr.Artistic10Perl, false), + "ARTISTIC-2.0": licence(expr.Artistic20, false), + "ASF-1": licence(expr.Apache10, false), + "ASF-1.0": licence(expr.Apache10, false), + "ASF-1.1": licence(expr.Apache11, false), + "ASF-2": licence(expr.Apache20, false), + "ASF-2.0": licence(expr.Apache20, false), + "ASL-1": licence(expr.Apache10, false), + "ASL-1.0": licence(expr.Apache10, false), + "ASL-1.1": licence(expr.Apache11, false), + "ASL-2": licence(expr.Apache20, false), + "ASL-2.0": licence(expr.Apache20, false), + "BCL": licence(expr.BCL, false), + "BEERWARE": licence(expr.Beerware, false), + "BOOST": licence(expr.BSL10, false), + "BOOST-1.0": licence(expr.BSL10, false), + "BOUNCY": licence(expr.MIT, false), + "BSD-2": licence(expr.BSD2Clause, false), + "BSD-2-CLAUSE": licence(expr.BSD2Clause, false), + "BSD-2-CLAUSE-FREEBSD": licence(expr.BSD2ClauseFreeBSD, false), + "BSD-2-CLAUSE-NETBSD": licence(expr.BSD2ClauseNetBSD, false), + "BSD-3": licence(expr.BSD3Clause, false), + "BSD-3-CLAUSE": licence(expr.BSD3Clause, false), + "BSD-3-CLAUSE-ATTRIBUTION": licence(expr.BSD3ClauseAttribution, false), + "BSD-3-CLAUSE-CLEAR": licence(expr.BSD3ClauseClear, false), + "BSD-3-CLAUSE-LBNL": licence(expr.BSD3ClauseLBNL, false), + "BSD-4": licence(expr.BSD4Clause, false), + "BSD-4-CLAUSE": licence(expr.BSD4Clause, false), + "BSD-4-CLAUSE-UC": licence(expr.BSD4ClauseUC, false), + "BSD-PROTECTION": licence(expr.BSDProtection, false), + "BSL": licence(expr.BSL10, false), + "BSL-1.0": licence(expr.BSL10, false), + "CC-BY-1.0": licence(expr.CCBY10, false), + "CC-BY-2.0": licence(expr.CCBY20, false), + "CC-BY-2.5": licence(expr.CCBY25, false), + "CC-BY-3.0": licence(expr.CCBY30, false), + "CC-BY-4.0": licence(expr.CCBY40, false), + "CC-BY-NC-1.0": licence(expr.CCBYNC10, false), + "CC-BY-NC-2.0": licence(expr.CCBYNC20, false), + "CC-BY-NC-2.5": licence(expr.CCBYNC25, false), + "CC-BY-NC-3.0": licence(expr.CCBYNC30, false), + "CC-BY-NC-4.0": licence(expr.CCBYNC40, false), + "CC-BY-NC-ND-1.0": licence(expr.CCBYNCND10, false), + "CC-BY-NC-ND-2.0": licence(expr.CCBYNCND20, false), + "CC-BY-NC-ND-2.5": licence(expr.CCBYNCND25, false), + "CC-BY-NC-ND-3.0": licence(expr.CCBYNCND30, false), + "CC-BY-NC-ND-4.0": licence(expr.CCBYNCND40, false), + "CC-BY-NC-SA-1.0": licence(expr.CCBYNCSA10, false), + "CC-BY-NC-SA-2.0": licence(expr.CCBYNCSA20, false), + "CC-BY-NC-SA-2.5": licence(expr.CCBYNCSA25, false), + "CC-BY-NC-SA-3.0": licence(expr.CCBYNCSA30, false), + "CC-BY-NC-SA-4.0": licence(expr.CCBYNCSA40, false), + "CC-BY-ND-1.0": licence(expr.CCBYND10, false), + "CC-BY-ND-2.0": licence(expr.CCBYND20, false), + "CC-BY-ND-2.5": licence(expr.CCBYND25, false), + "CC-BY-ND-3.0": licence(expr.CCBYND30, false), + "CC-BY-ND-4.0": licence(expr.CCBYND40, false), + "CC-BY-SA-1.0": licence(expr.CCBYSA10, false), + "CC-BY-SA-2.0": licence(expr.CCBYSA20, false), + "CC-BY-SA-2.5": licence(expr.CCBYSA25, false), + "CC-BY-SA-3.0": licence(expr.CCBYSA30, false), + "CC-BY-SA-4.0": licence(expr.CCBYSA40, false), + "CC0": licence(expr.CC010, false), + "CC0-1.0": licence(expr.CC010, false), + "CDDL-1": licence(expr.CDDL10, false), + "CDDL-1.0": licence(expr.CDDL10, false), + "CDDL-1.1": licence(expr.CDDL11, false), + "COMMONS-CLAUSE": licence(expr.CommonsClause, false), + "CPAL": licence(expr.CPAL10, false), + "CPAL-1.0": licence(expr.CPAL10, false), + "CPL": licence(expr.CPL10, false), + "CPL-1.0": licence(expr.CPL10, false), + "ECLIPSE-1.0": licence(expr.EPL10, false), + "ECLIPSE-2.0": licence(expr.EPL20, false), + "EDL-1.0": licence(expr.BSD3Clause, false), + "EGENIX": licence(expr.EGenix, false), + "EPL-1.0": licence(expr.EPL10, false), + "EPL-2.0": licence(expr.EPL20, false), + "EUPL-1.0": licence(expr.EUPL10, false), + "EUPL-1.1": licence(expr.EUPL11, false), + "EXPAT": licence(expr.MIT, false), + "FACEBOOK-2-CLAUSE": licence(expr.Facebook2Clause, false), + "FACEBOOK-3-CLAUSE": licence(expr.Facebook3Clause, false), + "FACEBOOK-EXAMPLES": licence(expr.FacebookExamples, false), + "FREEIMAGE": licence(expr.FreeImage, false), + "FTL": licence(expr.FTL, false), + "GFDL-1.1": licence(expr.GFDL11, false), + "GFDL-1.1-INVARIANTS": licence(expr.GFDL11WithInvariants, false), + "GFDL-1.1-NO-INVARIANTS": licence(expr.GFDL11NoInvariants, false), + "GFDL-1.2": licence(expr.GFDL12, false), + "GFDL-1.2-INVARIANTS": licence(expr.GFDL12WithInvariants, false), + "GFDL-1.2-NO-INVARIANTS": licence(expr.GFDL12NoInvariants, false), + "GFDL-1.3": licence(expr.GFDL13, false), + "GFDL-1.3-INVARIANTS": licence(expr.GFDL13WithInvariants, false), + "GFDL-1.3-NO-INVARIANTS": licence(expr.GFDL13NoInvariants, false), + "GFDL-NIV-1.3": licence(expr.GFDL13NoInvariants, false), + "GO": licence(expr.BSD3Clause, false), + "GPL-1": licence(expr.GPL10, false), + "GPL-1.0": licence(expr.GPL10, false), + "GPL-2": licence(expr.GPL20, false), + "GPL-2+-WITH-BISON-EXCEPTION": licence(expr.GPL20withbisonexception, true), + "GPL-2.0": licence(expr.GPL20, false), + "GPL-2.0-WITH-AUTOCONF-EXCEPTION": licence(expr.GPL20withautoconfexception, false), + "GPL-2.0-WITH-BISON-EXCEPTION": licence(expr.GPL20withbisonexception, false), + "GPL-2.0-WITH-CLASSPATH-EXCEPTION": licence(expr.GPL20withclasspathexception, false), + "GPL-2.0-WITH-FONT-EXCEPTION": licence(expr.GPL20withfontexception, false), + "GPL-2.0-WITH-GCC-EXCEPTION": licence(expr.GPL20withGCCexception, false), + "GPL-3": licence(expr.GPL30, false), + "GPL-3+-WITH-BISON-EXCEPTION": licence(expr.GPL20withbisonexception, true), + "GPL-3.0": licence(expr.GPL30, false), + "GPL-3.0-WITH-AUTOCONF-EXCEPTION": licence(expr.GPL30withautoconfexception, false), + "GPL-3.0-WITH-GCC-EXCEPTION": licence(expr.GPL30withGCCexception, false), + "GPLV2+CE": licence(expr.GPL20withclasspathexception, true), + "GUST-FONT": licence(expr.GUSTFont, false), + "HSQLDB": licence(expr.BSD3Clause, false), + "IMAGEMAGICK": licence(expr.ImageMagick, false), + "IPL-1.0": licence(expr.IPL10, false), + "ISC": licence(expr.ISC, false), + "ISCL": licence(expr.ISC, false), + "JQUERY": licence(expr.MIT, false), + "LGPL-2": licence(expr.LGPL20, false), + "LGPL-2.0": licence(expr.LGPL20, false), + "LGPL-2.1": licence(expr.LGPL21, false), + "LGPL-3": licence(expr.LGPL30, false), + "LGPL-3.0": licence(expr.LGPL30, false), + "LGPLLR": licence(expr.LGPLLR, false), + "LIBPNG": licence(expr.Libpng, false), + "LIL-1.0": licence(expr.Lil10, false), + "LINUX-OPENIB": licence(expr.LinuxOpenIB, false), + "LPL-1.0": licence(expr.LPL10, false), + "LPL-1.02": licence(expr.LPL102, false), + "LPPL-1.3C": licence(expr.LPPL13c, false), + "MIT": licence(expr.MIT, false), + // MIT No Attribution (MIT-0) is not yet supported by google/licenseclassifier + "MIT-0": licence(expr.MIT, false), + "MIT-LIKE": licence(expr.MIT, false), + "MIT-STYLE": licence(expr.MIT, false), + "MPL-1": licence(expr.MPL10, false), + "MPL-1.0": licence(expr.MPL10, false), + "MPL-1.1": licence(expr.MPL11, false), + "MPL-2": licence(expr.MPL20, false), + "MPL-2.0": licence(expr.MPL20, false), + "MS-PL": licence(expr.MSPL, false), + "NCSA": licence(expr.NCSA, false), + "NPL-1.0": licence(expr.NPL10, false), + "NPL-1.1": licence(expr.NPL11, false), + "OFL-1.1": licence(expr.OFL11, false), + "OPENSSL": licence(expr.OpenSSL, false), + "OPENVISION": licence(expr.OpenVision, false), + "OSL-1": licence(expr.OSL10, false), + "OSL-1.0": licence(expr.OSL10, false), + "OSL-1.1": licence(expr.OSL11, false), + "OSL-2": licence(expr.OSL20, false), + "OSL-2.0": licence(expr.OSL20, false), + "OSL-2.1": licence(expr.OSL21, false), + "OSL-3": licence(expr.OSL30, false), + "OSL-3.0": licence(expr.OSL30, false), + "PHP-3.0": licence(expr.PHP30, false), + "PHP-3.01": licence(expr.PHP301, false), + "PIL": licence(expr.PIL, false), + "POSTGRESQL": licence(expr.PostgreSQL, false), + "PYTHON-2": licence(expr.Python20, false), + "PYTHON-2.0": licence(expr.Python20, false), + "PYTHON-2.0-COMPLETE": licence(expr.Python20complete, false), + "QPL-1": licence(expr.QPL10, false), + "QPL-1.0": licence(expr.QPL10, false), + "RUBY": licence(expr.Ruby, false), + "SGI-B-1.0": licence(expr.SGIB10, false), + "SGI-B-1.1": licence(expr.SGIB11, false), + "SGI-B-2.0": licence(expr.SGIB20, false), + "SISSL": licence(expr.SISSL, false), + "SISSL-1.2": licence(expr.SISSL12, false), + "SLEEPYCAT": licence(expr.Sleepycat, false), + "UNICODE-DFS-2015": licence(expr.UnicodeDFS2015, false), + "UNICODE-DFS-2016": licence(expr.UnicodeDFS2016, false), + "UNICODE-TOU": licence(expr.UnicodeTOU, false), + "UNLICENSE": licence(expr.Unlicense, false), + "UNLICENSED": licence(expr.Unlicense, false), + "UPL-1": licence(expr.UPL10, false), + "UPL-1.0": licence(expr.UPL10, false), + "W3C": licence(expr.W3C, false), + "W3C-19980720": licence(expr.W3C19980720, false), + "W3C-20150513": licence(expr.W3C20150513, false), + "W3CL": licence(expr.W3C, false), + "WTF": licence(expr.WTFPL, false), + "WTFPL": licence(expr.WTFPL, false), + "X11": licence(expr.X11, false), + "XNET": licence(expr.Xnet, false), + "ZEND-2": licence(expr.Zend20, false), + "ZEND-2.0": licence(expr.Zend20, false), + "ZLIB": licence(expr.Zlib, false), + "ZLIB-ACKNOWLEDGEMENT": licence(expr.ZlibAcknowledgement, false), + "ZOPE-1.1": licence(expr.ZPL11, false), + "ZOPE-2.0": licence(expr.ZPL20, false), + "ZOPE-2.1": licence(expr.ZPL21, false), + "ZPL-1.1": licence(expr.ZPL11, false), + "ZPL-2.0": licence(expr.ZPL20, false), + "ZPL-2.1": licence(expr.ZPL21, false), + + // Non simple declared mappings + // modified from https://github.com/oss-review-toolkit/ort/blob/fc5389c2cfd9c8b009794c8a11f5c91321b7a730/utils/spdx/src/main/resources/declared-license-mapping.yml + + // Ambiguous declared mappings (mapping reason not obvious without additional information) + "ACADEMIC FREE LICENSE (AFL)": licence(expr.AFL21, false), + "APACHE SOFTWARE LICENSES": licence(expr.Apache20, false), + "APACHE SOFTWARE": licence(expr.Apache20, false), + "APPLE PUBLIC SOURCE": licence(expr.APSL10, false), + "BSD SOFTWARE": licence(expr.BSD2Clause, false), + "BSD STYLE": licence(expr.BSD3Clause, false), + "COMMON DEVELOPMENT AND DISTRIBUTION": licence(expr.CDDL10, false), + "CREATIVE COMMONS - BY": licence(expr.CCBY30, false), + "CREATIVE COMMONS ATTRIBUTION": licence(expr.CCBY30, false), + "CREATIVE COMMONS": licence(expr.CCBY30, false), + "ECLIPSE PUBLIC LICENSE (EPL)": licence(expr.EPL10, false), + "GENERAL PUBLIC LICENSE (GPL)": licence(expr.GPL20, true), + "GNU FREE DOCUMENTATION LICENSE (FDL)": licence(expr.GFDL13, true), + "GNU GENERAL PUBLIC LIBRARY": licence(expr.GPL30, true), + "GNU GENERAL PUBLIC LICENSE (GPL)": licence(expr.GPL30, true), + "GNU GPL": licence(expr.GPL20, false), + "GNU LESSER GENERAL PUBLIC LICENSE (LGPL)": licence(expr.LGPL21, false), + "GNU LESSER GENERAL PUBLIC": licence(expr.LGPL21, false), + "GNU LESSER PUBLIC": licence(expr.LGPL21, false), + "GNU LESSER": licence(expr.LGPL21, false), + "GNU LGPL": licence(expr.LGPL21, false), + "GNU LIBRARY OR LESSER GENERAL PUBLIC LICENSE (LGPL)": licence(expr.LGPL21, false), + "GNU PUBLIC": licence(expr.GPL20, true), + "GPL (WITH DUAL LICENSING OPTION)": licence(expr.GPL20, false), + "GPLV2 WITH EXCEPTIONS": licence(expr.GPL20withclasspathexception, false), + "INDIVIDUAL BSD": licence(expr.BSD3Clause, false), + "LESSER GENERAL PUBLIC LICENSE (LGPL)": licence(expr.LGPL21, true), + "LGPL WITH EXCEPTIONS": licence(expr.LGPL30, false), + "LPGL, SEE LICENSE FILE.": licence(expr.LGPL30, true), + "MOZILLA PUBLIC": licence(expr.MPL20, false), + "ZOPE PUBLIC": licence(expr.ZPL21, false), + + // Non-ambiguous declared mappings + "(NEW) BSD": licence(expr.BSD3Clause, false), + "2-CLAUSE BSD": licence(expr.BSD2Clause, false), + "2-CLAUSE BSDL": licence(expr.BSD2Clause, false), + "3-CLAUSE BDSL": licence(expr.BSD3Clause, false), + "3-CLAUSE BSD": licence(expr.BSD3Clause, false), + "ACADEMIC FREE LICENSE (AFL-2.1": licence(expr.AFL21, false), + "AFFERO GENERAL PUBLIC LICENSE (AGPL-3": licence(expr.AGPL30, false), + "APACHE 2 STYLE": licence(expr.Apache20, false), + "APACHE LICENSE, ASL-2.0": licence(expr.Apache20, false), + "APACHE LICENSE, VERSION 2.0 (HTTP://WWW.APACHE.ORG/LICENSES/LICENSE-2.0": licence(expr.Apache20, false), + "APACHE PUBLIC-1.1": licence(expr.Apache11, false), + "APACHE PUBLIC-2": licence(expr.Apache20, false), + "APACHE PUBLIC-2.0": licence(expr.Apache20, false), + "APACHE SOFTWARE LICENSE (APACHE-2": licence(expr.Apache20, false), + "APACHE SOFTWARE LICENSE (APACHE-2.0": licence(expr.Apache20, false), + "APACHE SOFTWARE-1.1": licence(expr.Apache11, false), + "APACHE SOFTWARE-2": licence(expr.Apache20, false), + "APACHE SOFTWARE-2.0": licence(expr.Apache20, false), + "APACHE VERSION 2.0, JANUARY 2004": licence(expr.Apache20, false), + "APACHE-2.0 */ ' " =END --": licence(expr.Apache20, false), + "BERKELEY SOFTWARE DISTRIBUTION (BSD)": licence(expr.BSD2Clause, false), + "BOOST SOFTWARE LICENSE 1.0 (BSL-1.0": licence(expr.BSL10, false), + "BOOST SOFTWARE": licence(expr.BSL10, false), + "BOUNCY CASTLE": licence(expr.MIT, false), + "BSD (3-CLAUSE)": licence(expr.BSD3Clause, false), + "BSD - SEE NDG/HTTPSCLIENT/LICENSE FILE FOR DETAILS": licence(expr.BSD3Clause, false), + "BSD 2 CLAUSE": licence(expr.BSD2Clause, false), + "BSD 2-CLAUSE": licence(expr.BSD2Clause, false), + "BSD 3 CLAUSE": licence(expr.BSD3Clause, false), + "BSD 3-CLAUSE NEW": licence(expr.BSD3Clause, false), + "BSD 3-CLAUSE \"NEW\" OR \"REVISED\" LICENSE (BSD-3-CLAUSE)": licence(expr.BSD3Clause, false), + "BSD 3-CLAUSE": licence(expr.BSD3Clause, false), + "BSD 4 CLAUSE": licence(expr.BSD4Clause, false), + "BSD 4-CLAUSE": licence(expr.BSD4Clause, false), + "BSD FOUR CLAUSE": licence(expr.BSD4Clause, false), + "BSD LICENSE FOR HSQL": licence(expr.BSD3Clause, false), + "BSD NEW": licence(expr.BSD3Clause, false), + "BSD THREE CLAUSE": licence(expr.BSD3Clause, false), + "BSD TWO CLAUSE": licence(expr.BSD2Clause, false), + "BSD-3 CLAUSE": licence(expr.BSD3Clause, false), + "BSD-STYLE + ATTRIBUTION": licence(expr.BSD3ClauseAttribution, false), + "CC BY-NC-SA-2.0": licence(expr.CCBYNCSA20, false), + "CC BY-NC-SA-2.5": licence(expr.CCBYNCSA25, false), + "CC BY-NC-SA-3.0": licence(expr.CCBYNCSA30, false), + "CC BY-NC-SA-4.0": licence(expr.CCBYNCSA40, false), + "CC BY-SA-2.0": licence(expr.CCBYSA20, false), + "CC BY-SA-2.5": licence(expr.CCBYSA25, false), + "CC BY-SA-3.0": licence(expr.CCBYSA30, false), + "CC BY-SA-4.0": licence(expr.CCBYSA40, false), + "CC0 1.0 UNIVERSAL (CC0 1.0) PUBLIC DOMAIN DEDICATION": licence(expr.CC010, false), + "CC0 1.0 UNIVERSAL": licence(expr.CC010, false), + "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL)-1.0": licence(expr.CDDL10, false), + "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL)-1.1": licence(expr.CDDL11, false), + "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE 1.0 (CDDL-1.0": licence(expr.CDDL10, false), + "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE 1.1 (CDDL-1.1": licence(expr.CDDL11, false), + "COMMON PUBLIC": licence(expr.CPL10, false), + "COMMON PUBLIC-1.0": licence(expr.CPL10, false), + "CREATIVE COMMONS - ATTRIBUTION 4.0 INTERNATIONAL": licence(expr.CCBY40, false), + "CREATIVE COMMONS 3.0 BY-SA": licence(expr.CCBYSA30, false), + "CREATIVE COMMONS ATTRIBUTION 3.0 UNPORTED (CC BY-3.0": licence(expr.CCBY30, false), + "CREATIVE COMMONS ATTRIBUTION 4.0 INTERNATIONAL (CC BY-4.0": licence(expr.CCBY40, false), + "CREATIVE COMMONS ATTRIBUTION 4.0 INTERNATIONAL PUBLIC": licence(expr.CCBY40, false), + "CREATIVE COMMONS ATTRIBUTION-1.0": licence(expr.CCBY10, false), + "CREATIVE COMMONS ATTRIBUTION-2.5": licence(expr.CCBY25, false), + "CREATIVE COMMONS ATTRIBUTION-3.0": licence(expr.CCBY30, false), + "CREATIVE COMMONS ATTRIBUTION-4.0": licence(expr.CCBY40, false), + "CREATIVE COMMONS ATTRIBUTION-NONCOMMERCIAL 4.0 INTERNATIONAL": licence(expr.CCBYNC40, false), + "CREATIVE COMMONS ATTRIBUTION-NONCOMMERCIAL-NODERIVATIVES 4.0 INTERNATIONAL": licence(expr.CCBYNCND40, false), + "CREATIVE COMMONS ATTRIBUTION-NONCOMMERCIAL-SHAREALIKE 3.0 UNPORTED (CC BY-NC-SA-3.0": licence(expr.CCBYNCSA30, false), + "CREATIVE COMMONS ATTRIBUTION-NONCOMMERCIAL-SHAREALIKE 4.0 INTERNATIONAL PUBLIC": licence(expr.CCBYNCSA40, false), + "CREATIVE COMMONS CC0": licence(expr.CC010, false), + "CREATIVE COMMONS GNU LGPL-2.1": licence(expr.LGPL21, false), + "CREATIVE COMMONS LICENSE ATTRIBUTION-NODERIVS 3.0 UNPORTED": licence(expr.CCBYNCND30, false), + "CREATIVE COMMONS LICENSE ATTRIBUTION-NONCOMMERCIAL-SHAREALIKE 3.0 UNPORTED": licence(expr.CCBYNCSA30, false), + "CREATIVE COMMONS ZERO": licence(expr.CC010, false), + "CREATIVE COMMONS-3.0": licence(expr.CCBY30, false), + "ECLIPSE DISTRIBUTION LICENSE (EDL)-1.0": licence(expr.BSD3Clause, false), + "ECLIPSE DISTRIBUTION LICENSE (NEW BSD LICENSE)": licence(expr.BSD3Clause, false), + "ECLIPSE DISTRIBUTION-1.0": licence(expr.BSD3Clause, false), + "ECLIPSE PUBLIC LICENSE (EPL)-1.0": licence(expr.EPL10, false), + "ECLIPSE PUBLIC LICENSE (EPL)-2.0": licence(expr.EPL20, false), + "ECLIPSE PUBLIC LICENSE 1.0 (EPL-1.0": licence(expr.EPL10, false), + "ECLIPSE PUBLIC LICENSE 2.0 (EPL-2.0": licence(expr.EPL20, false), + "ECLIPSE PUBLIC": licence(expr.EPL10, false), + "ECLIPSE PUBLIC-1.0": licence(expr.EPL10, false), + "ECLIPSE PUBLIC-2.0": licence(expr.EPL20, false), + "ECLIPSE PUBLISH-1.0": licence(expr.EPL10, false), + "EPL (ECLIPSE PUBLIC LICENSE)-1.0": licence(expr.EPL10, false), + "EU PUBLIC LICENSE 1.0 (EUPL-1.0": licence(expr.EUPL10, false), + "EU PUBLIC LICENSE 1.1 (EUPL-1.1": licence(expr.EUPL11, false), + "EUROPEAN UNION PUBLIC LICENSE (EUPL-1.0": licence(expr.EUPL10, false), + "EUROPEAN UNION PUBLIC LICENSE (EUPL-1.1": licence(expr.EUPL11, false), + "EUROPEAN UNION PUBLIC LICENSE 1.0 (EUPL-1.0": licence(expr.EUPL10, false), + "EUROPEAN UNION PUBLIC LICENSE 1.1 (EUPL-1.1": licence(expr.EUPL11, false), + "EUROPEAN UNION PUBLIC-1.0": licence(expr.EUPL10, false), + "EUROPEAN UNION PUBLIC-1.1": licence(expr.EUPL11, false), + "EXPAT (MIT/X11)": licence(expr.MIT, false), + "GENERAL PUBLIC LICENSE 2.0 (GPL)": licence(expr.GPL20, false), + "GNU AFFERO GENERAL PUBLIC LICENSE V3 (AGPL-3": licence(expr.AGPL30, false), + "GNU AFFERO GENERAL PUBLIC LICENSE V3 (AGPL-3.0": licence(expr.AGPL30, false), + "GNU AFFERO GENERAL PUBLIC LICENSE V3 OR LATER (AGPL3+)": licence(expr.AGPL30, true), + "GNU AFFERO GENERAL PUBLIC LICENSE V3 OR LATER (AGPLV3+)": licence(expr.AGPL30, true), + "GNU AFFERO GENERAL PUBLIC-3": licence(expr.AGPL30, false), + "GNU FREE DOCUMENTATION LICENSE (GFDL-1.3": licence(expr.GFDL13, false), + "GNU GENERAL LESSER PUBLIC LICENSE (LGPL)-2.1": licence(expr.LGPL21, false), + "GNU GENERAL LESSER PUBLIC LICENSE (LGPL)-3.0": licence(expr.LGPL30, false), + "GNU GENERAL PUBLIC LICENSE (GPL), VERSION 2, WITH CLASSPATH EXCEPTION": licence(expr.GPL20withclasspathexception, false), + "GNU GENERAL PUBLIC LICENSE (GPL), VERSION 2, WITH THE CLASSPATH EXCEPTION": licence(expr.GPL20withclasspathexception, false), + "GNU GENERAL PUBLIC LICENSE (GPL)-2": licence(expr.GPL20, false), + "GNU GENERAL PUBLIC LICENSE (GPL)-3": licence(expr.GPL30, false), + "GNU GENERAL PUBLIC LICENSE V2 (GPL-2": licence(expr.GPL20, false), + "GNU GENERAL PUBLIC LICENSE V2 OR LATER (GPLV2+)": licence(expr.GPL20, true), + "GNU GENERAL PUBLIC LICENSE V2.0 ONLY, WITH CLASSPATH EXCEPTION": licence(expr.GPL20withclasspathexception, false), + "GNU GENERAL PUBLIC LICENSE V3 (GPL-3": licence(expr.GPL30, false), + "GNU GENERAL PUBLIC LICENSE V3 OR LATER (GPLV3+)": licence(expr.GPL30, true), + "GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL-2": licence(expr.GPL20, false), + "GNU GENERAL PUBLIC LICENSE VERSION 2, JUNE 1991": licence(expr.GPL20, false), + "GNU GENERAL PUBLIC LICENSE VERSION 3 (GPL-3": licence(expr.GPL30, false), + "GNU GENERAL PUBLIC LICENSE, VERSION 2 (GPL2), WITH THE CLASSPATH EXCEPTION": licence(expr.GPL20withclasspathexception, false), + "GNU GENERAL PUBLIC LICENSE, VERSION 2 WITH THE CLASSPATH EXCEPTION": licence(expr.GPL20withclasspathexception, false), + "GNU GENERAL PUBLIC LICENSE, VERSION 2 WITH THE GNU CLASSPATH EXCEPTION": licence(expr.GPL20withclasspathexception, false), + "GNU GENERAL PUBLIC LICENSE, VERSION 2, WITH THE CLASSPATH EXCEPTION": licence(expr.GPL20withclasspathexception, false), + "GNU GENERAL PUBLIC-2": licence(expr.GPL20, false), + "GNU GENERAL PUBLIC-3": licence(expr.GPL30, false), + "GNU GPL-2": licence(expr.GPL20, false), + "GNU GPL-3": licence(expr.GPL30, false), + "GNU LESSER GENERAL PUBLIC LICENSE (LGPL)-2": licence(expr.LGPL20, false), + "GNU LESSER GENERAL PUBLIC LICENSE (LGPL)-2.0": licence(expr.LGPL20, false), + "GNU LESSER GENERAL PUBLIC LICENSE (LGPL)-2.1": licence(expr.LGPL21, false), + "GNU LESSER GENERAL PUBLIC LICENSE (LGPL)-3": licence(expr.LGPL30, false), + "GNU LESSER GENERAL PUBLIC LICENSE (LGPL)-3.0": licence(expr.LGPL30, false), + "GNU LESSER GENERAL PUBLIC LICENSE (LGPL-2": licence(expr.LGPL20, false), + "GNU LESSER GENERAL PUBLIC LICENSE (LGPL-2.0": licence(expr.LGPL20, false), + "GNU LESSER GENERAL PUBLIC LICENSE (LGPL-2.1": licence(expr.LGPL21, false), + "GNU LESSER GENERAL PUBLIC LICENSE (LGPL-3": licence(expr.LGPL30, false), + "GNU LESSER GENERAL PUBLIC LICENSE (LGPL-3.0": licence(expr.LGPL30, false), + "GNU LESSER GENERAL PUBLIC LICENSE V2 (LGPL-2": licence(expr.LGPL20, false), + "GNU LESSER GENERAL PUBLIC LICENSE V2 OR LATER (LGPLV2+)": licence(expr.LGPL20, true), + "GNU LESSER GENERAL PUBLIC LICENSE V3 (LGPL-3": licence(expr.LGPL30, false), + "GNU LESSER GENERAL PUBLIC LICENSE V3 OR LATER (LGPLV3+)": licence(expr.LGPL30, true), + "GNU LESSER GENERAL PUBLIC LICENSE VERSION 2.1 (LGPL-2.1": licence(expr.LGPL21, false), + "GNU LESSER GENERAL PUBLIC LICENSE VERSION 2.1, FEBRUARY 1999": licence(expr.LGPL21, false), + "GNU LESSER GENERAL PUBLIC LICENSE, VERSION 2.1, FEBRUARY 1999": licence(expr.LGPL21, false), + "GNU LESSER GENERAL PUBLIC-2": licence(expr.LGPL20, false), + "GNU LESSER GENERAL PUBLIC-2.0": licence(expr.LGPL20, false), + "GNU LESSER GENERAL PUBLIC-2.1": licence(expr.LGPL21, false), + "GNU LESSER GENERAL PUBLIC-3": licence(expr.LGPL30, false), + "GNU LESSER GENERAL PUBLIC-3.0": licence(expr.LGPL30, false), + "GNU LGP (GNU GENERAL PUBLIC LICENSE)-2": licence(expr.LGPL20, false), + "GNU LGPL (GNU LESSER GENERAL PUBLIC LICENSE)-2.1": licence(expr.LGPL21, false), + "GNU LGPL-2": licence(expr.LGPL20, false), + "GNU LGPL-2.0": licence(expr.LGPL20, false), + "GNU LGPL-2.1": licence(expr.LGPL21, false), + "GNU LGPL-3": licence(expr.LGPL30, false), + "GNU LGPL-3.0": licence(expr.LGPL30, false), + "GNU LIBRARY GENERAL PUBLIC-2.0": licence(expr.LGPL20, false), + "GNU LIBRARY GENERAL PUBLIC-2.1": licence(expr.LGPL21, false), + "GNU LIBRARY OR LESSER GENERAL PUBLIC LICENSE VERSION 2.0 (LGPL-2": licence(expr.LGPL20, false), + "GNU LIBRARY OR LESSER GENERAL PUBLIC LICENSE VERSION 3.0 (LGPL-3": licence(expr.LGPL30, false), + "GPL (≥ 3)": licence(expr.GPL30, true), + "GPL 2 WITH CLASSPATH EXCEPTION": licence(expr.GPL20withclasspathexception, false), + "GPL V2 WITH CLASSPATH EXCEPTION": licence(expr.GPL20withclasspathexception, false), + "GPL-2+ WITH AUTOCONF EXCEPTION": licence(expr.GPL20withautoconfexception, true), + "GPL-3+ WITH AUTOCONF EXCEPTION": licence(expr.GPL30withautoconfexception, true), + "GPL2 W/ CPE": licence(expr.GPL20withclasspathexception, false), + "GPLV2 LICENSE, INCLUDES THE CLASSPATH EXCEPTION": licence(expr.GPL20withclasspathexception, false), + "GPLV2 WITH CLASSPATH EXCEPTION": licence(expr.GPL20withclasspathexception, false), + "HSQLDB LICENSE, A BSD OPEN SOURCE": licence(expr.BSD3Clause, false), + "HTTP://ANT-CONTRIB.SOURCEFORGE.NET/TASKS/LICENSE.TXT": licence(expr.Apache11, false), + "HTTP://ASM.OW2.ORG/LICENSE.HTML": licence(expr.BSD3Clause, false), + "HTTP://CREATIVECOMMONS.ORG/PUBLICDOMAIN/ZERO/1.0/LEGALCODE": licence(expr.CC010, false), + "HTTP://EN.WIKIPEDIA.ORG/WIKI/ZLIB_LICENSE": licence(expr.Zlib, false), + "HTTP://JSON.CODEPLEX.COM/LICENSE": licence(expr.MIT, false), + "HTTP://POLYMER.GITHUB.IO/LICENSE.TXT": licence(expr.BSD3Clause, false), + "HTTP://WWW.APACHE.ORG/LICENSES/LICENSE-2.0": licence(expr.Apache20, false), + "HTTP://WWW.APACHE.ORG/LICENSES/LICENSE-2.0.HTML": licence(expr.Apache20, false), + "HTTP://WWW.APACHE.ORG/LICENSES/LICENSE-2.0.TXT": licence(expr.Apache20, false), + "HTTP://WWW.GNU.ORG/COPYLEFT/LESSER.HTML": licence(expr.LGPL30, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-NC-ND/1.0": licence(expr.CCBYNCND10, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-NC-ND/2.0": licence(expr.CCBYNCND20, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-NC-ND/2.5": licence(expr.CCBYNCND25, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-NC-ND/3.0": licence(expr.CCBYNCND30, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-NC-ND/4.0": licence(expr.CCBYNCND40, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-NC-SA/1.0": licence(expr.CCBYNCSA10, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-NC-SA/2.0": licence(expr.CCBYNCSA20, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-NC-SA/2.5": licence(expr.CCBYNCSA25, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-NC-SA/3.0": licence(expr.CCBYNCSA30, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-NC-SA/4.0": licence(expr.CCBYNCSA40, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-ND/1.0": licence(expr.CCBYND10, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-ND/2.0": licence(expr.CCBYND20, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-ND/2.5": licence(expr.CCBYND25, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-ND/3.0": licence(expr.CCBYND30, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-ND/4.0": licence(expr.CCBYND40, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-SA/1.0": licence(expr.CCBYSA10, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-SA/2.0": licence(expr.CCBYSA20, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-SA/2.5": licence(expr.CCBYSA25, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-SA/3.0": licence(expr.CCBYSA30, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-SA/4.0": licence(expr.CCBYSA40, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY/1.0": licence(expr.CCBY10, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY/2.0": licence(expr.CCBY20, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY/2.5": licence(expr.CCBY25, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY/3.0": licence(expr.CCBY30, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY/4.0": licence(expr.CCBY40, false), + "HTTPS://CREATIVECOMMONS.ORG/PUBLICDOMAIN/ZERO/1.0/": licence(expr.CC010, false), + "HTTPS://GITHUB.COM/DOTNET/CORE-SETUP/BLOB/MASTER/LICENSE.TXT": licence(expr.MIT, false), + "HTTPS://GITHUB.COM/DOTNET/COREFX/BLOB/MASTER/LICENSE.TXT": licence(expr.MIT, false), + "HTTPS://RAW.GITHUB.COM/RDFLIB/RDFLIB/MASTER/LICENSE": licence(expr.BSD3Clause, false), + "HTTPS://RAW.GITHUBUSERCONTENT.COM/ASPNET/ASPNETCORE/2.0.0/LICENSE.TXT": licence(expr.Apache20, false), + "HTTPS://RAW.GITHUBUSERCONTENT.COM/ASPNET/HOME/2.0.0/LICENSE.TXT": licence(expr.Apache20, false), + "HTTPS://RAW.GITHUBUSERCONTENT.COM/NUGET/NUGET.CLIENT/DEV/LICENSE.TXT": licence(expr.Apache20, false), + "HTTPS://WWW.APACHE.ORG/LICENSES/LICENSE-2.0": licence(expr.Apache20, false), + "HTTPS://WWW.ECLIPSE.ORG/LEGAL/EPL-V10.HTML": licence(expr.EPL10, false), + "HTTPS://WWW.ECLIPSE.ORG/LEGAL/EPL-V20.HTML": licence(expr.EPL20, false), + "IBM PUBLIC": licence(expr.IPL10, false), + "ISC LICENSE (ISCL)": licence(expr.ISC, false), + "JYTHON SOFTWARE": licence(expr.Python20, false), + "KIRKK.COM BSD": licence(expr.BSD3Clause, false), + "LESSER GENERAL PUBLIC LICENSE, VERSION 3 OR GREATER": licence(expr.LGPL30, true), + "LICENSE AGREEMENT FOR OPEN SOURCE COMPUTER VISION LIBRARY (3-CLAUSE BSD LICENSE)": licence(expr.BSD3Clause, false), + "MIT (HTTP://MOOTOOLS.NET/LICENSE.TXT)": licence(expr.MIT, false), + "MIT / HTTP://REM.MIT-LICENSE.ORG": licence(expr.MIT, false), + "MIT LICENSE (HTTP://OPENSOURCE.ORG/LICENSES/MIT)": licence(expr.MIT, false), + "MIT LICENSE (MIT)": licence(expr.MIT, false), + "MIT LICENSE(MIT)": licence(expr.MIT, false), + "MIT LICENSED. HTTP://WWW.OPENSOURCE.ORG/LICENSES/MIT-LICENSE.PHP": licence(expr.MIT, false), + "MIT/EXPAT": licence(expr.MIT, false), + "MOCKRUNNER LICENSE, BASED ON APACHE SOFTWARE-1.1": licence(expr.Apache11, false), + "MODIFIED BSD": licence(expr.BSD3Clause, false), + "MOZILLA PUBLIC LICENSE 1.0 (MPL)": licence(expr.MPL10, false), + "MOZILLA PUBLIC LICENSE 1.1 (MPL-1.1": licence(expr.MPL11, false), + "MOZILLA PUBLIC LICENSE 2.0 (MPL-2.0": licence(expr.MPL20, false), + "MOZILLA PUBLIC-1.0": licence(expr.MPL10, false), + "MOZILLA PUBLIC-1.1": licence(expr.MPL11, false), + "MOZILLA PUBLIC-2.0": licence(expr.MPL20, false), + "NCSA OPEN SOURCE": licence(expr.NCSA, false), + "NETSCAPE PUBLIC LICENSE (NPL)": licence(expr.NPL10, false), + "NETSCAPE PUBLIC": licence(expr.NPL10, false), + "NEW BSD": licence(expr.BSD3Clause, false), + "OPEN SOFTWARE LICENSE 3.0 (OSL-3.0": licence(expr.OSL30, false), + "OPEN SOFTWARE-3.0": licence(expr.OSL30, false), + "PERL ARTISTIC-2": licence(expr.Artistic10Perl, false), + // Note: public domain without a specific license should not be mapped + // see https://wiki.spdx.org/view/Legal_Team/Decisions/Dealing_with_Public_Domain_within_SPDX_Files + // and https://opensource.google/documentation/reference/thirdparty/licenses#unencumbered + "PUBLIC DOMAIN (CC0-1.0)": licence(expr.CC010, false), + "PUBLIC DOMAIN, PER CREATIVE COMMONS CC0": licence(expr.CC010, false), + "QT PUBLIC LICENSE (QPL)": licence(expr.QPL10, false), + "QT PUBLIC": licence(expr.QPL10, false), + "REVISED BSD": licence(expr.BSD3Clause, false), + "RUBY'S": licence(expr.Ruby, false), + "SEQUENCE LIBRARY LICENSE (BSD-LIKE)": licence(expr.BSD3Clause, false), + "SIL OPEN FONT LICENSE 1.1 (OFL-1.1": licence(expr.OFL11, false), + "SIL OPEN FONT-1.1": licence(expr.OFL11, false), + "SIMPLIFIED BSD LISCENCE": licence(expr.BSD2Clause, false), + "SIMPLIFIED BSD": licence(expr.BSD2Clause, false), + "SUN INDUSTRY STANDARDS SOURCE LICENSE (SISSL)": licence(expr.SISSL, false), + "THREE-CLAUSE BSD-STYLE": licence(expr.BSD3Clause, false), + "TWO-CLAUSE BSD-STYLE": licence(expr.BSD2Clause, false), + "UNIVERSAL PERMISSIVE LICENSE (UPL)": licence(expr.UPL10, false), + "UNIVERSAL PERMISSIVE-1.0": licence(expr.UPL10, false), + "UNLICENSE (UNLICENSE)": licence(expr.Unlicense, false), + "W3C SOFTWARE": licence(expr.W3C, false), + "ZLIB / LIBPNG": licence(expr.ZlibAcknowledgement, false), + "ZLIB/LIBPNG": licence(expr.ZlibAcknowledgement, false), + "['MIT']": licence(expr.MIT, false), } +const ( + LicenseTextPrefix = "text://" + LicenseFilePrefix = "file://" + CustomLicensePrefix = "CUSTOM License" +) + // pythonLicenseExceptions contains licenses that we cannot separate correctly using our logic. // first word after separator (or/and) => license name var pythonLicenseExceptions = map[string]string{ @@ -175,22 +590,112 @@ var pythonLicenseExceptions = map[string]string{ // 'BSD-3-CLAUSE and GPL-2' => {"BSD-3-CLAUSE", "GPL-2"} // 'GPL-1+ or Artistic, and BSD-4-clause-POWERDOG' => {"GPL-1+", "Artistic", "BSD-4-clause-POWERDOG"} // 'BSD 3-Clause License or Apache License, Version 2.0' => {"BSD 3-Clause License", "Apache License, Version 2.0"} -// var LicenseSplitRegexp = regexp.MustCompile("(,?[_ ]+or[_ ]+)|(,?[_ ]+and[_ ])|(,[ ]*)") - var licenseSplitRegexp = regexp.MustCompile("(,?[_ ]+(?:or|and)[_ ]+)|(,[ ]*)") -func Normalize(name string) string { +// Typical keywords for license texts +var licenseTextKeywords = []string{ + "http://", + "https://", + "(c)", + "as-is", + ";", + "hereby", + "permission to use", + "permission is", + "use in source", + "use, copy, modify", + "using", +} + +func isLicenseText(str string) bool { + for _, keyword := range licenseTextKeywords { + if strings.Contains(str, keyword) { + return true + } + } + return false +} + +func TrimLicenseText(text string) string { + s := strings.Split(text, " ") + n := len(s) + if n > 3 { + n = 3 + } + return strings.Join(s[:n], " ") + "..." +} + +// version number match +var versionRegexpString = "([A-UW-Z)]{2,})( LICENSE)?\\s*[,(-]?\\s*(V|V\\.|VER|VER\\.|VERSION|VERSION-|-)?\\s*([1-9](\\.\\d)*)[)]?" + +// case insensitive version match anywhere in string +var versionRegexp = regexp.MustCompile("(?i)" + versionRegexpString) + +// version suffix match +var versionSuffixRegexp = regexp.MustCompile(versionRegexpString + "$") + +// suffixes from https://spdx.dev/learn/handling-license-info/ +var onlySuffixes = [2]string{"-ONLY", " ONLY"} +var plusSuffixes = [3]string{"+", "-OR-LATER", " OR LATER"} + +func standardizeKeyAndSuffix(name string) expr.SimpleExpr { + // Standardize space, including newline + name = strings.Join(strings.Fields(name), " ") name = strings.TrimSpace(name) - if l, ok := mapping[strings.ToUpper(name)]; ok { - return l + name = strings.ToUpper(name) + // Do not perform any further normalization for URLs + if strings.HasPrefix(name, "HTTP") { + return expr.SimpleExpr{License: name, HasPlus: false} + } + name = strings.ReplaceAll(name, "LICENCE", "LICENSE") + name = strings.TrimPrefix(name, "THE ") + name = strings.TrimSuffix(name, " LICENSE") + name = strings.TrimSuffix(name, " LICENSED") + name = strings.TrimSuffix(name, "-LICENSE") + name = strings.TrimSuffix(name, "-LICENSED") + // Remove License and Licensed suffixes except for licenses already containing those suffixes such as Unlicense + if name != "UNLICENSE" { + name = strings.TrimSuffix(name, "LICENSE") + } + if name != "UNLICENSED" { + name = strings.TrimSuffix(name, "LICENSED") + } + hasPlus := false + for _, s := range plusSuffixes { + if strings.HasSuffix(name, s) { + name = strings.TrimSuffix(name, s) + hasPlus = true + } + } + for _, s := range onlySuffixes { + name = strings.TrimSuffix(name, s) + } + name = versionSuffixRegexp.ReplaceAllString(name, "$1-$4") + return expr.SimpleExpr{License: name, HasPlus: hasPlus} +} + +func Normalize(name string) string { + return NormalizeLicense(name).String() +} + +func NormalizeLicense(name string) expr.SimpleExpr { + normalized := standardizeKeyAndSuffix(name) + if found, ok := mapping[normalized.License]; ok { + return expr.SimpleExpr{License: found.License, HasPlus: found.HasPlus || normalized.HasPlus} } - return name + return expr.SimpleExpr{License: name, HasPlus: false} } func SplitLicenses(str string) []string { if str == "" { return nil } + if isLicenseText(strings.ToLower(str)) { + return []string{ + LicenseTextPrefix + str, + } + } + var licenses []string for _, maybeLic := range licenseSplitRegexp.Split(str, -1) { lower := strings.ToLower(maybeLic) @@ -216,3 +721,25 @@ func SplitLicenses(str string) []string { } return licenses } + +// Split license string considering spaces as separator +// e.g. MPL 2.0 GPL2+ => {"MPL2.0", "GPL2+"} +func LaxSplitLicenses(str string) []string { + if str == "" { + return nil + } + var licenses []string + str = versionRegexp.ReplaceAllString(str, "$1-$4") + for _, s := range strings.Fields(str) { + s = strings.Trim(s, "()") + switch { + case s == "": + continue + case s == "AND" || s == "OR": + continue + default: + licenses = append(licenses, Normalize(s)) + } + } + return licenses +} diff --git a/pkg/licensing/normalize_private_test.go b/pkg/licensing/normalize_private_test.go new file mode 100644 index 000000000000..68d62f241944 --- /dev/null +++ b/pkg/licensing/normalize_private_test.go @@ -0,0 +1,18 @@ +package licensing + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// All map keys must be standardized to be matched +// (uppercase, no common suffixes, standardized version, etc.) +func TestMap(t *testing.T) { + for key := range mapping { + t.Run(key, func(t *testing.T) { + standardized := standardizeKeyAndSuffix(key) + assert.Equal(t, standardized.License, key) + }) + } +} diff --git a/pkg/licensing/normalize_test.go b/pkg/licensing/normalize_test.go index 28934f4f2340..9b47fd923f29 100644 --- a/pkg/licensing/normalize_test.go +++ b/pkg/licensing/normalize_test.go @@ -8,6 +8,220 @@ import ( "github.com/aquasecurity/trivy/pkg/licensing" ) +func TestNormalize(t *testing.T) { + tests := []struct { + licenses []string + normalized string + normalizedKey string + }{ + { + licenses: []string{ + " the apache license ", + " the\tapache \r\nlicense \r\n ", + " apache ", + "ApacheLicence", + "ApacheLicense", + "al-2", + "al-v2", + "al2", + "alv2", + "apache - v 2.0", + "apache - v. 2.0", + "apache - ver 2.0", + "apache - version 2.0", + "apache 2", + "apache 2.0", + "apache license (2.0)", + "apache license (v. 2)", + "apache license (v. 2.0)", + "apache license (v2)", + "apache license (v2.0)", + "apache license (version 2.0)", + "apache license 2", + "apache license 2.0", + "apache license v2", + "apache license v2.0", + "apache license version 2", + "apache license version 2.0", + "apache license", + "apache license, 2.0", + "apache license, asl version 2.0", + "apache license, version 2", + "apache license, version 2.0 (http://www.apache.org/licenses/license-2.0)", + "apache license, version 2.0", + "apache license,version 2.0", + "apache license,version-2.0", + "apache license-2.0", + "apache public 2.0", + "apache public license 2.0", + "apache public license-2.0", + "apache public-2", + "apache public-2.0", + "apache software license (apache-2.0)", + "apache software license - version 2.0", + "apache software license 2.0", + "apache software license, version 2", + "apache software license, version 2.0", + "apache software-2.0", + "apache v 2.0", + "apache v. 2.0", + "apache v2", + "apache v2.0", + "apache ver 2.0", + "apache ver. 2.0", + "apache version 2.0", + "apache version 2.0, january 2004", + "apache version-2", + "apache version-2.0", + "apache", + "apache, 2", + "apache, v2.0", + "apache, version 2", + "apache, version 2.0", + "apache-2", + "apache-2.0", + "apache-licence", + "apache-license", + "apache-licensed", + "apache-licensed", + "asf 2.0", + "asl 2", + "asl, version 2", + "asl2.0", + "the apache license", + "the apache license", + }, + normalized: "Apache-2.0", + normalizedKey: "Apache-2.0", + }, + { + licenses: []string{ + "Apache+", + }, + normalized: "Apache-2.0+", + normalizedKey: "Apache-2.0", + }, + { + licenses: []string{ + "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) V1.1", + "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) VERSION 1.1", + "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL), VERSION 1.1", + "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE 1.1 (CDDL-1.1)", + }, + normalized: "CDDL-1.1", + normalizedKey: "CDDL-1.1", + }, + { + licenses: []string{ + "ECLIPSE PUBLIC LICENSE (EPL) 1.0", + "ECLIPSE PUBLIC LICENSE (EPL), VERSION 1.0", + "ECLIPSE PUBLIC LICENSE - V 1.0", + "ECLIPSE PUBLIC LICENSE - V1.0", + "ECLIPSE PUBLIC LICENSE - VERSION 1.0", + "ECLIPSE PUBLIC LICENSE 1.0 (EPL-1.0)", + "ECLIPSE PUBLIC LICENSE 1.0", + "ECLIPSE PUBLIC LICENSE V. 1.0", + "ECLIPSE PUBLIC LICENSE V1.0", + "ECLIPSE PUBLIC LICENSE VERSION 1.0", + "ECLIPSE PUBLIC LICENSE, VERSION 1.0", + "ECLIPSE PUBLIC", + }, + normalized: "EPL-1.0", + normalizedKey: "EPL-1.0", + }, + { + licenses: []string{ + "EUROPEAN UNION PUBLIC LICENSE (EUPL V.1.1)", + "EUROPEAN UNION PUBLIC LICENSE 1.1 (EUPL 1.1)", + "EUROPEAN UNION PUBLIC LICENSE 1.1", + "EUROPEAN UNION PUBLIC LICENSE, VERSION 1.1", + }, + normalized: "EUPL-1.1", + normalizedKey: "EUPL-1.1", + }, + { + licenses: []string{ + "GPL-or-later", + "GPL+", + "GPL-2.0-only+", + }, + normalized: "GPL-2.0-or-later", + normalizedKey: "GPL-2.0", + }, + { + licenses: []string{ + "GPL (≥ 3)", + "GPL3+", + "GPL3-or-later", + "GPL3 or later licence", + }, + normalized: "GPL-3.0-or-later", + normalizedKey: "GPL-3.0", + }, + { + licenses: []string{ + "GNU GENERAL PUBLIC LICENSE 3", + "GNU GENERAL PUBLIC LICENSE (GPL) V. 3", + "GNU GENERAL PUBLIC LICENSE VERSION 3 (GPL V3)", + }, + normalized: "GPL-3.0-only", + normalizedKey: "GPL-3.0", + }, + + { + licenses: []string{ + "LGPL LICENSE-3", + "GNU LESSER GENERAL PUBLIC LICENSE V3", + "GNU LESSER GENERAL PUBLIC LICENSE V3.0", + "GNU LESSER GENERAL PUBLIC LICENSE VERSION 3", + "GNU LESSER GENERAL PUBLIC LICENSE VERSION 3.0", + "GNU LESSER GENERAL PUBLIC LICENSE, VERSION 3.0", + "GNU LIBRARY OR LESSER GENERAL PUBLIC LICENSE VERSION 3.0 (LGPLV3)", + "GNU GENERAL LESSER PUBLIC LICENSE (LGPL) VERSION 3.0", + "GNU LESSER GENERAL PUBLIC LICENSE (LGPL), VERSION 3", + }, + normalized: "LGPL-3.0-only", + normalizedKey: "LGPL-3.0", + }, + { + licenses: []string{ + "The Unlicense", + "Unlicense", + "Unlicensed", + "UNLICENSE", + "UNLICENSED", + }, + normalized: "Unlicense", + normalizedKey: "Unlicense", + }, + { + licenses: []string{ + "MIT License", + "http://json.codeplex.com/license", + }, + normalized: "MIT", + normalizedKey: "MIT", + }, + { + licenses: []string{ + " The unmapped license ", + }, + normalized: " The unmapped license ", + normalizedKey: " The unmapped license ", + }, + } + for _, tt := range tests { + t.Run(tt.normalized, func(t *testing.T) { + for _, ll := range tt.licenses { + normalized := licensing.Normalize(ll) + normalizedKey := licensing.NormalizeLicense(ll).License + assert.Equal(t, tt.normalized, normalized) + assert.Equal(t, tt.normalizedKey, normalizedKey) + } + }) + } +} + func TestSplitLicenses(t *testing.T) { tests := []struct { name string @@ -97,6 +311,13 @@ func TestSplitLicenses(t *testing.T) { "Historical Permission Notice and Disclaimer (HPND)", }, }, + { + name: "License text", + license: "* Permission to use this software in any way is granted without", + licenses: []string{ + "text://* Permission to use this software in any way is granted without", + }, + }, } for _, tt := range tests { @@ -106,3 +327,28 @@ func TestSplitLicenses(t *testing.T) { }) } } + +func TestLaxSplitLicense(t *testing.T) { + var tests = []struct { + license string + wantLicenses []string + }{ + { + license: "ASL 2.0", + wantLicenses: []string{"Apache-2.0"}, + }, + { + license: "MPL 2.0 GPL2+", + wantLicenses: []string{ + "MPL-2.0", + "GPL-2.0-or-later", + }, + }, + } + for _, tt := range tests { + t.Run(tt.license, func(t *testing.T) { + parsed := licensing.LaxSplitLicenses(tt.license) + assert.Equal(t, tt.wantLicenses, parsed) + }) + } +} diff --git a/pkg/licensing/scanner.go b/pkg/licensing/scanner.go index 100358a5af6c..1a8ed8e1d9f0 100644 --- a/pkg/licensing/scanner.go +++ b/pkg/licensing/scanner.go @@ -21,8 +21,9 @@ func NewScanner(categories map[types.LicenseCategory][]string) Scanner { } func (s *Scanner) Scan(licenseName string) (types.LicenseCategory, string) { + license := NormalizeLicense(licenseName) for category, names := range s.categories { - if slices.Contains(names, licenseName) { + if slices.Contains(names, license.License) { return category, categoryToSeverity(category).String() } } diff --git a/pkg/licensing/scanner_test.go b/pkg/licensing/scanner_test.go index fce93f3c5337..992992e48f9a 100644 --- a/pkg/licensing/scanner_test.go +++ b/pkg/licensing/scanner_test.go @@ -7,6 +7,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/licensing" + "github.com/aquasecurity/trivy/pkg/licensing/expression" ) func TestScanner_Scan(t *testing.T) { @@ -21,11 +22,11 @@ func TestScanner_Scan(t *testing.T) { name: "forbidden", categories: map[types.LicenseCategory][]string{ types.CategoryForbidden: { - licensing.BSD3Clause, - licensing.Apache20, + expression.BSD3Clause, + expression.Apache20, }, }, - licenseName: licensing.Apache20, + licenseName: expression.Apache20, wantCategory: types.CategoryForbidden, wantSeverity: "CRITICAL", }, @@ -33,21 +34,21 @@ func TestScanner_Scan(t *testing.T) { name: "restricted", categories: map[types.LicenseCategory][]string{ types.CategoryForbidden: { - licensing.GPL30, + expression.GPL30, }, types.CategoryRestricted: { - licensing.BSD3Clause, - licensing.Apache20, + expression.BSD3Clause, + expression.Apache20, }, }, - licenseName: licensing.BSD3Clause, + licenseName: expression.BSD3Clause, wantCategory: types.CategoryRestricted, wantSeverity: "HIGH", }, { name: "unknown", categories: make(map[types.LicenseCategory][]string), - licenseName: licensing.BSD3Clause, + licenseName: expression.BSD3Clause, wantCategory: types.CategoryUnknown, wantSeverity: "UNKNOWN", }, diff --git a/pkg/log/deferred.go b/pkg/log/deferred.go new file mode 100644 index 000000000000..9fbf81652f4d --- /dev/null +++ b/pkg/log/deferred.go @@ -0,0 +1,62 @@ +package log + +import ( + "context" + "log/slog" + "slices" +) + +func init() { + // Set the default logger so that logs are buffered until the logger is initialized. + slog.SetDefault(New(&DeferredHandler{records: new([]deferredRecord)})) +} + +// DeferredHandler is needed to save logs and print them after calling `PrintLogs()` command. +// For example, this may be necessary when the logger is not yet initialized, but messages need to be transmitted. +// In this case, the messages are saved and printed when the logger is initialized. +type DeferredHandler struct { + attrs []slog.Attr + + // Shared with all instances of the handler. + // NOTE: non-thread safe + records *[]deferredRecord +} + +type deferredRecord struct { + ctx context.Context + slog.Record +} + +func (*DeferredHandler) Enabled(context.Context, slog.Level) bool { + return true +} + +func (d *DeferredHandler) Handle(ctx context.Context, record slog.Record) error { + record.AddAttrs(d.attrs...) + *d.records = append(*d.records, deferredRecord{ + ctx: ctx, + Record: record, + }) + return nil +} + +func (d *DeferredHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + h := *d + h.attrs = slices.Clip(d.attrs) + h.attrs = append(h.attrs, attrs...) + return &h +} + +func (*DeferredHandler) WithGroup(_ string) slog.Handler { + panic("WithGroup is not implemented") +} + +func (d *DeferredHandler) Flush(h slog.Handler) { + for _, record := range *d.records { + if !h.Enabled(record.ctx, record.Level) { + continue + } + _ = h.Handle(record.ctx, record.Record) + } + d.records = nil +} diff --git a/pkg/log/logger.go b/pkg/log/logger.go index 6eb8bde70dc2..2948886b9559 100644 --- a/pkg/log/logger.go +++ b/pkg/log/logger.go @@ -6,7 +6,6 @@ import ( "io" "log/slog" "os" - "strings" "github.com/samber/lo" ) @@ -24,6 +23,8 @@ const ( PrefixMisconfiguration = "misconfig" PrefixSecret = "secret" PrefixLicense = "license" + PrefixVulnerabilityDB = "vulndb" + PrefixJavaDB = "javadb" ) // Logger is an alias of slog.Logger @@ -34,11 +35,18 @@ func New(h slog.Handler) *Logger { return slog.New(h) } -// InitLogger initialize the logger variable +// InitLogger initializes the logger variable and flushes the buffered logs if needed. func InitLogger(debug, disable bool) { level := lo.Ternary(debug, slog.LevelDebug, slog.LevelInfo) out := lo.Ternary(disable, io.Discard, io.Writer(os.Stderr)) - slog.SetDefault(New(NewHandler(out, &Options{Level: level}))) + h := NewHandler(out, &Options{Level: level}) + + // Flush the buffered logs if needed. + if d, ok := slog.Default().Handler().(*DeferredHandler); ok { + d.Flush(h) + } + + slog.SetDefault(New(h)) } var ( @@ -83,18 +91,3 @@ func Fatal(msg string, args ...any) { slog.Default().Log(context.Background(), LevelFatal, msg, args...) os.Exit(1) } - -// WriteLogger is a wrapper around Logger to implement io.Writer -type WriteLogger struct { - logger *Logger -} - -// NewWriteLogger creates a new WriteLogger -func NewWriteLogger(logger *Logger) *WriteLogger { - return &WriteLogger{logger: logger} -} - -func (l *WriteLogger) Write(p []byte) (n int, err error) { - l.logger.Debug(strings.TrimSpace(string(p))) - return len(p), nil -} diff --git a/pkg/mapfs/fs.go b/pkg/mapfs/fs.go index 4cba59263c37..5ce894b8101c 100644 --- a/pkg/mapfs/fs.go +++ b/pkg/mapfs/fs.go @@ -123,7 +123,7 @@ func (m *FS) CopyFilesUnder(dir string) error { // Stat returns a FileInfo describing the file. func (m *FS) Stat(name string) (fs.FileInfo, error) { - if strings.HasPrefix(name, "../") && m.underlyingRoot != "" { + if m.isPathAboveRoot(name) { return os.Stat(filepath.Join(m.underlyingRoot, name)) } @@ -145,7 +145,7 @@ func (m *FS) Stat(name string) (fs.FileInfo, error) { // ReadDir reads the named directory // and returns a list of directory entries sorted by filename. func (m *FS) ReadDir(name string) ([]fs.DirEntry, error) { - if strings.HasPrefix(name, "../") && m.underlyingRoot != "" { + if m.isPathAboveRoot(name) { return os.ReadDir(filepath.Join(m.underlyingRoot, name)) } return m.root.ReadDir(cleanPath(name)) @@ -153,7 +153,7 @@ func (m *FS) ReadDir(name string) ([]fs.DirEntry, error) { // Open opens the named file for reading. func (m *FS) Open(name string) (fs.File, error) { - if strings.HasPrefix(name, "../") && m.underlyingRoot != "" { + if m.isPathAboveRoot(name) { return os.Open(filepath.Join(m.underlyingRoot, name)) } return m.root.Open(cleanPath(name)) @@ -188,7 +188,7 @@ func (m *FS) MkdirAll(path string, perm fs.FileMode) error { // The caller is permitted to modify the returned byte slice. // This method should return a copy of the underlying data. func (m *FS) ReadFile(name string) ([]byte, error) { - if strings.HasPrefix(name, "../") && m.underlyingRoot != "" { + if m.isPathAboveRoot(name) { return os.ReadFile(filepath.Join(m.underlyingRoot, name)) } @@ -245,3 +245,7 @@ func cleanPath(path string) string { path = strings.TrimLeft(path, "/") // Remove the leading slash return path } + +func (m *FS) isPathAboveRoot(name string) bool { + return (name == ".." || strings.HasPrefix(name, "../")) && m.underlyingRoot != "" +} diff --git a/pkg/mapfs/fs_test.go b/pkg/mapfs/fs_test.go index 22b659d7f387..d00d5626a9a9 100644 --- a/pkg/mapfs/fs_test.go +++ b/pkg/mapfs/fs_test.go @@ -478,3 +478,26 @@ func TestFS_RemoveAll(t *testing.T) { require.ErrorIs(t, err, fs.ErrNotExist) }) } + +func TestFS_WithUnderlyingRoot(t *testing.T) { + root := "testdata/subdir" + fsys := mapfs.New(mapfs.WithUnderlyingRoot(root)) + require.NoError(t, fsys.WriteFile("foo.txt", root+"/foo.txt")) + require.NoError(t, fsys.WriteFile("..foo.txt", root+"/..foo.txt")) + + fi, err := fsys.Stat("..") + require.NoError(t, err) + assert.True(t, fi.IsDir()) + + fi, err = fsys.Stat("../hello.txt") + require.NoError(t, err) + assert.False(t, fi.IsDir()) + + fi, err = fsys.Stat("foo.txt") + require.NoError(t, err) + assert.False(t, fi.IsDir()) + + fi, err = fsys.Stat("..foo.txt") + require.NoError(t, err) + assert.False(t, fi.IsDir()) +} diff --git a/pkg/mapfs/testdata/subdir/..foo.txt b/pkg/mapfs/testdata/subdir/..foo.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/pkg/mapfs/testdata/subdir/foo.txt b/pkg/mapfs/testdata/subdir/foo.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/pkg/misconf/config_schema.go b/pkg/misconf/config_schema.go new file mode 100644 index 000000000000..a728bfa01e3b --- /dev/null +++ b/pkg/misconf/config_schema.go @@ -0,0 +1,74 @@ +package misconf + +import ( + "bytes" + "io/fs" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/xeipuuv/gojsonschema" + "golang.org/x/xerrors" +) + +type ConfigFileSchema struct { + path string + name string + source []byte + schema *gojsonschema.Schema +} + +func LoadConfigSchemas(paths []string) ([]*ConfigFileSchema, error) { + var configSchemas []*ConfigFileSchema + for _, path := range paths { + walkFn := func(path string, info fs.DirEntry, err error) error { + if err != nil { + return err + } + if info.IsDir() || !strings.HasSuffix(info.Name(), ".json") { + return nil + } + + schema, err := newConfigFileSchema(path) + if err != nil { + return xerrors.Errorf("load config file schema: %w", err) + } + + configSchemas = append(configSchemas, schema) + return nil + } + if err := filepath.WalkDir(path, walkFn); err != nil { + return nil, xerrors.Errorf("walk error: %w", err) + } + } + + return configSchemas, nil +} + +func newConfigFileSchema(path string) (*ConfigFileSchema, error) { + b, err := os.ReadFile(path) + if err != nil { + return nil, xerrors.Errorf("read config schema error: %w", err) + } + + // Go's regular expression engine does not support \Z + b = bytes.ReplaceAll(b, []byte(`\\Z`), []byte(`$`)) + + // Go's regular expression engine does not support negative lookahead + b = regexp.MustCompile(`\(\?\!.*\)`).ReplaceAll(b, []byte{}) + schema, err := gojsonschema.NewSchema(gojsonschema.NewBytesLoader(b)) + if err != nil { + return nil, xerrors.Errorf("compile config schema %s error: %w", path, err) + } + + fileName := filepath.Base(path) + schemaName := strings.TrimSuffix(fileName, filepath.Ext(fileName)) + + return &ConfigFileSchema{ + path: path, + name: schemaName, + schema: schema, + source: b, + }, nil +} diff --git a/pkg/misconf/config_schema_test.go b/pkg/misconf/config_schema_test.go new file mode 100644 index 000000000000..8290dbdfaf7c --- /dev/null +++ b/pkg/misconf/config_schema_test.go @@ -0,0 +1,41 @@ +package misconf_test + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/misconf" +) + +func Test_LoadConfigSchemas(t *testing.T) { + tests := []struct { + name string + paths []string + want int + }{ + { + name: "load one schema", + paths: []string{ + filepath.Join("testdata", "schemas", "schema1.json"), + }, + want: 1, + }, + { + name: "load dir with schemas", + paths: []string{ + filepath.Join("testdata", "schemas"), + }, + want: 3, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := misconf.LoadConfigSchemas(tt.paths) + require.NoError(t, err) + assert.Len(t, got, tt.want) + }) + } +} diff --git a/pkg/misconf/scanner.go b/pkg/misconf/scanner.go index 90ee90cb4216..7bec3ed6fe14 100644 --- a/pkg/misconf/scanner.go +++ b/pkg/misconf/scanner.go @@ -12,22 +12,26 @@ import ( "strings" "github.com/samber/lo" + "github.com/xeipuuv/gojsonschema" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/iac/detection" + "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners" "github.com/aquasecurity/trivy/pkg/iac/scanners/azure/arm" cfscanner "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation" cfparser "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" dfscanner "github.com/aquasecurity/trivy/pkg/iac/scanners/dockerfile" - helm2 "github.com/aquasecurity/trivy/pkg/iac/scanners/helm" + "github.com/aquasecurity/trivy/pkg/iac/scanners/helm" + jsonscanner "github.com/aquasecurity/trivy/pkg/iac/scanners/json" k8sscanner "github.com/aquasecurity/trivy/pkg/iac/scanners/kubernetes" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform" tfprawscanner "github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan/snapshot" tfpjsonscanner "github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan/tfjson" + yamlscanner "github.com/aquasecurity/trivy/pkg/iac/scanners/yaml" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/mapfs" @@ -43,10 +47,11 @@ var enablediacTypes = map[detection.FileType]types.ConfigType{ detection.FileTypeHelm: types.Helm, detection.FileTypeTerraformPlanJSON: types.TerraformPlanJSON, detection.FileTypeTerraformPlanSnapshot: types.TerraformPlanSnapshot, + detection.FileTypeJSON: types.JSON, + detection.FileTypeYAML: types.YAML, } type ScannerOption struct { - Debug bool Trace bool RegoOnly bool Namespaces []string @@ -66,6 +71,13 @@ type ScannerOption struct { CloudFormationParamVars []string TfExcludeDownloaded bool K8sVersion string + + FilePatterns []string + ConfigFileSchemas []*ConfigFileSchema + + DisabledCheckIDs []string + SkipFiles []string + SkipDirs []string } func (o *ScannerOption) Sort() { @@ -75,44 +87,13 @@ func (o *ScannerOption) Sort() { } type Scanner struct { - fileType detection.FileType - scanner scanners.FSScanner - hasFilePattern bool -} - -func NewAzureARMScanner(filePatterns []string, opt ScannerOption) (*Scanner, error) { - return newScanner(detection.FileTypeAzureARM, filePatterns, opt) -} - -func NewCloudFormationScanner(filePatterns []string, opt ScannerOption) (*Scanner, error) { - return newScanner(detection.FileTypeCloudFormation, filePatterns, opt) -} - -func NewDockerfileScanner(filePatterns []string, opt ScannerOption) (*Scanner, error) { - return newScanner(detection.FileTypeDockerfile, filePatterns, opt) + fileType detection.FileType + scanner scanners.FSScanner + hasFilePattern bool + configFileSchemas []*ConfigFileSchema } -func NewHelmScanner(filePatterns []string, opt ScannerOption) (*Scanner, error) { - return newScanner(detection.FileTypeHelm, filePatterns, opt) -} - -func NewKubernetesScanner(filePatterns []string, opt ScannerOption) (*Scanner, error) { - return newScanner(detection.FileTypeKubernetes, filePatterns, opt) -} - -func NewTerraformScanner(filePatterns []string, opt ScannerOption) (*Scanner, error) { - return newScanner(detection.FileTypeTerraform, filePatterns, opt) -} - -func NewTerraformPlanJSONScanner(filePatterns []string, opt ScannerOption) (*Scanner, error) { - return newScanner(detection.FileTypeTerraformPlanJSON, filePatterns, opt) -} - -func NewTerraformPlanSnapshotScanner(filePatterns []string, opt ScannerOption) (*Scanner, error) { - return newScanner(detection.FileTypeTerraformPlanSnapshot, filePatterns, opt) -} - -func newScanner(t detection.FileType, filePatterns []string, opt ScannerOption) (*Scanner, error) { +func NewScanner(t detection.FileType, opt ScannerOption) (*Scanner, error) { opts, err := scannerOptions(t, opt) if err != nil { return nil, err @@ -127,7 +108,7 @@ func newScanner(t detection.FileType, filePatterns []string, opt ScannerOption) case detection.FileTypeDockerfile: scanner = dfscanner.NewScanner(opts...) case detection.FileTypeHelm: - scanner = helm2.New(opts...) + scanner = helm.New(opts...) case detection.FileTypeKubernetes: scanner = k8sscanner.NewScanner(opts...) case detection.FileTypeTerraform: @@ -136,12 +117,19 @@ func newScanner(t detection.FileType, filePatterns []string, opt ScannerOption) scanner = tfpjsonscanner.New(opts...) case detection.FileTypeTerraformPlanSnapshot: scanner = tfprawscanner.New(opts...) + case detection.FileTypeYAML: + scanner = yamlscanner.NewScanner(opts...) + case detection.FileTypeJSON: + scanner = jsonscanner.NewScanner(opts...) + default: + return nil, xerrors.Errorf("unknown file type: %s", t) } return &Scanner{ - fileType: t, - scanner: scanner, - hasFilePattern: hasFilePattern(t, filePatterns), + fileType: t, + scanner: scanner, + hasFilePattern: hasFilePattern(t, opt.FilePatterns), + configFileSchemas: opt.ConfigFileSchemas, }, nil } @@ -185,21 +173,31 @@ func (s *Scanner) filterFS(fsys fs.FS) (fs.FS, error) { return fsys, nil } + schemas := lo.SliceToMap(s.configFileSchemas, func(schema *ConfigFileSchema) (string, *gojsonschema.Schema) { + return schema.path, schema.schema + }) + var foundRelevantFile bool filter := func(path string, d fs.DirEntry) (bool, error) { file, err := fsys.Open(path) if err != nil { return false, err } + defer file.Close() + rs, ok := file.(io.ReadSeeker) if !ok { return false, xerrors.Errorf("type assertion error: %w", err) } - defer file.Close() - if !s.hasFilePattern && !detection.IsType(path, rs, s.fileType) { + if len(schemas) > 0 && + (s.fileType == detection.FileTypeYAML || s.fileType == detection.FileTypeJSON) && + !detection.IsFileMatchesSchemas(schemas, s.fileType, path, rs) { + return true, nil + } else if !s.hasFilePattern && !detection.IsType(path, rs, s.fileType) { return true, nil } + foundRelevantFile = true return false, nil } @@ -215,10 +213,10 @@ func (s *Scanner) filterFS(fsys fs.FS) (fs.FS, error) { func scannerOptions(t detection.FileType, opt ScannerOption) ([]options.ScannerOption, error) { opts := []options.ScannerOption{ - options.ScannerWithSkipRequiredCheck(true), - options.ScannerWithEmbeddedPolicies(!opt.DisableEmbeddedPolicies), - options.ScannerWithEmbeddedLibraries(!opt.DisableEmbeddedLibraries), + rego.WithEmbeddedPolicies(!opt.DisableEmbeddedPolicies), + rego.WithEmbeddedLibraries(!opt.DisableEmbeddedLibraries), options.ScannerWithIncludeDeprecatedChecks(opt.IncludeDeprecatedChecks), + rego.WithDisabledCheckIDs(opt.DisabledCheckIDs...), } policyFS, policyPaths, err := CreatePolicyFS(opt.PolicyPaths) @@ -226,24 +224,26 @@ func scannerOptions(t detection.FileType, opt ScannerOption) ([]options.ScannerO return nil, err } if policyFS != nil { - opts = append(opts, options.ScannerWithPolicyFilesystem(policyFS)) + opts = append(opts, rego.WithPolicyFilesystem(policyFS)) } dataFS, dataPaths, err := CreateDataFS(opt.DataPaths, opt.K8sVersion) if err != nil { return nil, err } + + schemas := lo.SliceToMap(opt.ConfigFileSchemas, func(schema *ConfigFileSchema) (string, []byte) { + return schema.name, schema.source + }) + opts = append(opts, - options.ScannerWithDataDirs(dataPaths...), - options.ScannerWithDataFilesystem(dataFS), + rego.WithDataDirs(dataPaths...), + rego.WithDataFilesystem(dataFS), + rego.WithCustomSchemas(schemas), ) - if opt.Debug { - opts = append(opts, options.ScannerWithDebug(log.NewWriteLogger(log.WithPrefix("misconf")))) - } - if opt.Trace { - opts = append(opts, options.ScannerWithPerResultTracing(true)) + opts = append(opts, rego.WithPerResultTracing(true)) } if opt.RegoOnly { @@ -251,15 +251,15 @@ func scannerOptions(t detection.FileType, opt ScannerOption) ([]options.ScannerO } if len(policyPaths) > 0 { - opts = append(opts, options.ScannerWithPolicyDirs(policyPaths...)) + opts = append(opts, rego.WithPolicyDirs(policyPaths...)) } if len(opt.DataPaths) > 0 { - opts = append(opts, options.ScannerWithDataDirs(opt.DataPaths...)) + opts = append(opts, rego.WithDataDirs(opt.DataPaths...)) } if len(opt.Namespaces) > 0 { - opts = append(opts, options.ScannerWithPolicyNamespaces(opt.Namespaces...)) + opts = append(opts, rego.WithPolicyNamespaces(opt.Namespaces...)) } switch t { @@ -299,6 +299,8 @@ func addTFOpts(opts []options.ScannerOption, scannerOption ScannerOption) ([]opt opts = append(opts, terraform.ScannerWithAllDirectories(true), terraform.ScannerWithSkipDownloaded(scannerOption.TfExcludeDownloaded), + terraform.ScannerWithSkipFiles(scannerOption.SkipFiles), + terraform.ScannerWithSkipDirs(scannerOption.SkipDirs), ) return opts, nil @@ -321,27 +323,27 @@ func addCFOpts(opts []options.ScannerOption, scannerOption ScannerOption) ([]opt func addHelmOpts(opts []options.ScannerOption, scannerOption ScannerOption) []options.ScannerOption { if len(scannerOption.HelmValueFiles) > 0 { - opts = append(opts, helm2.ScannerWithValuesFile(scannerOption.HelmValueFiles...)) + opts = append(opts, helm.ScannerWithValuesFile(scannerOption.HelmValueFiles...)) } if len(scannerOption.HelmValues) > 0 { - opts = append(opts, helm2.ScannerWithValues(scannerOption.HelmValues...)) + opts = append(opts, helm.ScannerWithValues(scannerOption.HelmValues...)) } if len(scannerOption.HelmFileValues) > 0 { - opts = append(opts, helm2.ScannerWithFileValues(scannerOption.HelmFileValues...)) + opts = append(opts, helm.ScannerWithFileValues(scannerOption.HelmFileValues...)) } if len(scannerOption.HelmStringValues) > 0 { - opts = append(opts, helm2.ScannerWithStringValues(scannerOption.HelmStringValues...)) + opts = append(opts, helm.ScannerWithStringValues(scannerOption.HelmStringValues...)) } if len(scannerOption.HelmAPIVersions) > 0 { - opts = append(opts, helm2.ScannerWithAPIVersions(scannerOption.HelmAPIVersions...)) + opts = append(opts, helm.ScannerWithAPIVersions(scannerOption.HelmAPIVersions...)) } if scannerOption.HelmKubeVersion != "" { - opts = append(opts, helm2.ScannerWithKubeVersion(scannerOption.HelmKubeVersion)) + opts = append(opts, helm.ScannerWithKubeVersion(scannerOption.HelmKubeVersion)) } return opts diff --git a/pkg/misconf/scanner_test.go b/pkg/misconf/scanner_test.go index 6270b78b65e2..e85caf651a53 100644 --- a/pkg/misconf/scanner_test.go +++ b/pkg/misconf/scanner_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/iac/detection" "github.com/aquasecurity/trivy/pkg/mapfs" ) @@ -82,8 +83,7 @@ func TestScannerOption_Sort(t *testing.T) { func TestScanner_Scan(t *testing.T) { type fields struct { - filePatterns []string - opt ScannerOption + opt ScannerOption } type file struct { path string @@ -91,7 +91,7 @@ func TestScanner_Scan(t *testing.T) { } tests := []struct { name string - scannerFunc func(filePatterns []string, opt ScannerOption) (*Scanner, error) + fileType detection.FileType fields fields files []file wantFilePath string @@ -99,8 +99,8 @@ func TestScanner_Scan(t *testing.T) { misconfsExpected int }{ { - name: "happy path. Dockerfile", - scannerFunc: NewDockerfileScanner, + name: "happy path. Dockerfile", + fileType: detection.FileTypeDockerfile, fields: fields{ opt: ScannerOption{}, }, @@ -115,11 +115,12 @@ func TestScanner_Scan(t *testing.T) { misconfsExpected: 1, }, { - name: "happy path. Dockerfile with custom file name", - scannerFunc: NewDockerfileScanner, + name: "happy path. Dockerfile with custom file name", + fileType: detection.FileTypeDockerfile, fields: fields{ - filePatterns: []string{"dockerfile:dockerf"}, - opt: ScannerOption{}, + opt: ScannerOption{ + FilePatterns: []string{"dockerfile:dockerf"}, + }, }, files: []file{ { @@ -132,8 +133,8 @@ func TestScanner_Scan(t *testing.T) { misconfsExpected: 1, }, { - name: "happy path. terraform plan file", - scannerFunc: NewTerraformPlanJSONScanner, + name: "happy path. terraform plan file", + fileType: detection.FileTypeTerraformPlanJSON, files: []file{ { path: "main.tfplan.json", @@ -154,7 +155,8 @@ func TestScanner_Scan(t *testing.T) { require.NoError(t, err) } - s, err := tt.scannerFunc(tt.fields.filePatterns, tt.fields.opt) + // s, err := tt.scannerFunc(tt.fields.filePatterns, tt.fields.opt) + s, err := NewScanner(tt.fileType, tt.fields.opt) require.NoError(t, err) misconfs, err := s.Scan(context.Background(), fsys) diff --git a/pkg/misconf/testdata/schemas/no-schema.file b/pkg/misconf/testdata/schemas/no-schema.file new file mode 100644 index 000000000000..7b4d68d70fca --- /dev/null +++ b/pkg/misconf/testdata/schemas/no-schema.file @@ -0,0 +1 @@ +empty \ No newline at end of file diff --git a/pkg/misconf/testdata/schemas/schema-with-bad-regex.json b/pkg/misconf/testdata/schemas/schema-with-bad-regex.json new file mode 100644 index 000000000000..522a1ccb46e3 --- /dev/null +++ b/pkg/misconf/testdata/schemas/schema-with-bad-regex.json @@ -0,0 +1,9 @@ +{ + "$id": "https://example.com/test.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "patternProperties": { + "^(?![\\.0-9]).": { "type": "string" }, + "test\\Z": { "type": "string" } + } +} diff --git a/pkg/misconf/testdata/schemas/schema1.json b/pkg/misconf/testdata/schemas/schema1.json new file mode 100644 index 000000000000..f72cdb0d2016 --- /dev/null +++ b/pkg/misconf/testdata/schemas/schema1.json @@ -0,0 +1,9 @@ +{ + "$id": "https://example.com/test.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "service": { "type": "string" } + }, + "required": ["service"] +} \ No newline at end of file diff --git a/pkg/misconf/testdata/schemas/schema2.json b/pkg/misconf/testdata/schemas/schema2.json new file mode 100644 index 000000000000..f72cdb0d2016 --- /dev/null +++ b/pkg/misconf/testdata/schemas/schema2.json @@ -0,0 +1,9 @@ +{ + "$id": "https://example.com/test.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "service": { "type": "string" } + }, + "required": ["service"] +} \ No newline at end of file diff --git a/pkg/module/command.go b/pkg/module/command.go index 87a87e5d6209..a74da8384c3f 100644 --- a/pkg/module/command.go +++ b/pkg/module/command.go @@ -23,15 +23,11 @@ func Install(ctx context.Context, dir, repo string, quiet bool, opt types.Regist } log.Info("Installing the module from the repository...", log.String("repo", repo)) - artifact, err := oci.NewArtifact(repo, quiet, opt) - if err != nil { - return xerrors.Errorf("module initialize error: %w", err) - } + art := oci.NewArtifact(repo, opt) dst := filepath.Join(dir, ref.Context().Name()) log.Debug("Installing the module...", log.String("dst", dst)) - - if err = artifact.Download(ctx, dst, oci.DownloadOption{MediaType: mediaType}); err != nil { + if err = art.Download(ctx, dst, oci.DownloadOption{MediaType: mediaType, Quiet: quiet}); err != nil { return xerrors.Errorf("module download error: %w", err) } diff --git a/pkg/oci/artifact.go b/pkg/oci/artifact.go index 04d62ee15d36..416952578918 100644 --- a/pkg/oci/artifact.go +++ b/pkg/oci/artifact.go @@ -2,6 +2,7 @@ package oci import ( "context" + "errors" "io" "os" "path/filepath" @@ -10,11 +11,15 @@ import ( "github.com/cheggaaa/pb/v3" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/remote/transport" + "github.com/samber/lo" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/downloader" "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/remote" + "github.com/aquasecurity/trivy/pkg/version/doc" ) const ( @@ -48,7 +53,6 @@ func WithImage(img v1.Image) Option { type Artifact struct { m sync.Mutex repository string - quiet bool // For OCI registries types.RegistryOptions @@ -57,17 +61,16 @@ type Artifact struct { } // NewArtifact returns a new artifact -func NewArtifact(repo string, quiet bool, registryOpt types.RegistryOptions, opts ...Option) (*Artifact, error) { +func NewArtifact(repo string, registryOpt types.RegistryOptions, opts ...Option) *Artifact { art := &Artifact{ repository: repo, - quiet: quiet, RegistryOptions: registryOpt, } for _, o := range opts { o(art) } - return art, nil + return art } func (a *Artifact) populate(ctx context.Context, opt types.RegistryOptions) error { @@ -98,6 +101,7 @@ func (a *Artifact) populate(ctx context.Context, opt types.RegistryOptions) erro type DownloadOption struct { MediaType string // Accept any media type if not specified Filename string // Use the annotation if not specified + Quiet bool } func (a *Artifact) Download(ctx context.Context, dir string, opt DownloadOption) error { @@ -140,14 +144,14 @@ func (a *Artifact) Download(ctx context.Context, dir string, opt DownloadOption) return xerrors.Errorf("unacceptable media type: %s", string(layerMediaType)) } - if err = a.download(ctx, layer, fileName, dir); err != nil { + if err = a.download(ctx, layer, fileName, dir, opt.Quiet); err != nil { return xerrors.Errorf("oci download error: %w", err) } return nil } -func (a *Artifact) download(ctx context.Context, layer v1.Layer, fileName, dir string) error { +func (a *Artifact) download(ctx context.Context, layer v1.Layer, fileName, dir string, quiet bool) error { size, err := layer.Size() if err != nil { return xerrors.Errorf("size error: %w", err) @@ -161,7 +165,7 @@ func (a *Artifact) download(ctx context.Context, layer v1.Layer, fileName, dir s // Show progress bar bar := pb.Full.Start64(size) - if a.quiet { + if quiet { bar.SetWriter(io.Discard) } pr := bar.NewProxyReader(rc) @@ -207,3 +211,54 @@ func (a *Artifact) Digest(ctx context.Context) (string, error) { } return digest.String(), nil } + +type Artifacts []*Artifact + +// NewArtifacts returns a slice of artifacts. +func NewArtifacts(repos []name.Reference, opt types.RegistryOptions, opts ...Option) Artifacts { + return lo.Map(repos, func(r name.Reference, _ int) *Artifact { + return NewArtifact(r.String(), opt, opts...) + }) +} + +// Download downloads artifacts until one of them succeeds. +// Attempts to download next artifact if the first one fails due to a temporary error. +func (a Artifacts) Download(ctx context.Context, dst string, opt DownloadOption) error { + for i, art := range a { + log.InfoContext(ctx, "Downloading artifact...", log.String("repo", art.repository)) + err := art.Download(ctx, dst, opt) + if err == nil { + log.InfoContext(ctx, "Artifact successfully downloaded", log.String("repo", art.repository)) + return nil + } + + if !shouldTryOtherRepo(err) { + return xerrors.Errorf("failed to download artifact from %s: %w", art.repository, err) + } + log.ErrorContext(ctx, "Failed to download artifact", log.String("repo", art.repository), log.Err(err)) + if i < len(a)-1 { + log.InfoContext(ctx, "Trying to download artifact from other repository...") + } + } + + return xerrors.New("failed to download artifact from any source") +} + +func shouldTryOtherRepo(err error) bool { + var terr *transport.Error + if !errors.As(err, &terr) { + return false + } + + for _, diagnostic := range terr.Errors { + // For better user experience + if diagnostic.Code == transport.DeniedErrorCode || diagnostic.Code == transport.UnauthorizedErrorCode { + // e.g. https://aquasecurity.github.io/trivy/latest/docs/references/troubleshooting/#db + log.Warnf("See %s", doc.URL("/docs/references/troubleshooting/", "db")) + break + } + } + + // try the following artifact only if a temporary error occurs + return terr.Temporary() +} diff --git a/pkg/oci/artifact_test.go b/pkg/oci/artifact_test.go index ddfe0304cc63..a8ce6e542641 100644 --- a/pkg/oci/artifact_test.go +++ b/pkg/oci/artifact_test.go @@ -116,11 +116,10 @@ func TestArtifact_Download(t *testing.T) { }, }, nil) - artifact, err := oci.NewArtifact("repo", true, ftypes.RegistryOptions{}, oci.WithImage(img)) - require.NoError(t, err) - + artifact := oci.NewArtifact("repo", ftypes.RegistryOptions{}, oci.WithImage(img)) err = artifact.Download(context.Background(), tempDir, oci.DownloadOption{ MediaType: tt.mediaType, + Quiet: true, }) if tt.wantErr != "" { assert.ErrorContains(t, err, tt.wantErr) diff --git a/pkg/policy/policy.go b/pkg/policy/policy.go index 88af220d3cf6..670588868dcb 100644 --- a/pkg/policy/policy.go +++ b/pkg/policy/policy.go @@ -18,7 +18,7 @@ import ( ) const ( - BundleVersion = 0 // Latest released MAJOR version for trivy-checks + BundleVersion = 1 // Latest released MAJOR version for trivy-checks BundleRepository = "ghcr.io/aquasecurity/trivy-checks" policyMediaType = "application/vnd.cncf.openpolicyagent.layer.v1.tar+gzip" updateInterval = 24 * time.Hour @@ -89,26 +89,23 @@ func NewClient(cacheDir string, quiet bool, checkBundleRepo string, opts ...Opti }, nil } -func (c *Client) populateOCIArtifact(registryOpts types.RegistryOptions) error { +func (c *Client) populateOCIArtifact(ctx context.Context, registryOpts types.RegistryOptions) { if c.artifact == nil { - log.Debug("Loading check bundle", log.String("repository", c.checkBundleRepo)) - art, err := oci.NewArtifact(c.checkBundleRepo, c.quiet, registryOpts) - if err != nil { - return xerrors.Errorf("OCI artifact error: %w", err) - } - c.artifact = art + log.DebugContext(ctx, "Loading check bundle", log.String("repository", c.checkBundleRepo)) + c.artifact = oci.NewArtifact(c.checkBundleRepo, registryOpts) } - return nil } -// DownloadBuiltinPolicies download default policies from GitHub Pages -func (c *Client) DownloadBuiltinPolicies(ctx context.Context, registryOpts types.RegistryOptions) error { - if err := c.populateOCIArtifact(registryOpts); err != nil { - return xerrors.Errorf("OPA bundle error: %w", err) - } +// DownloadBuiltinChecks download default policies from GitHub Pages +func (c *Client) DownloadBuiltinChecks(ctx context.Context, registryOpts types.RegistryOptions) error { + c.populateOCIArtifact(ctx, registryOpts) dst := c.contentDir() - if err := c.artifact.Download(ctx, dst, oci.DownloadOption{MediaType: policyMediaType}); err != nil { + if err := c.artifact.Download(ctx, dst, oci.DownloadOption{ + MediaType: policyMediaType, + Quiet: c.quiet, + }, + ); err != nil { return xerrors.Errorf("download error: %w", err) } @@ -116,7 +113,7 @@ func (c *Client) DownloadBuiltinPolicies(ctx context.Context, registryOpts types if err != nil { return xerrors.Errorf("digest error: %w", err) } - log.Debug("Digest of the built-in policies", log.String("digest", digest)) + log.DebugContext(ctx, "Digest of the built-in checks", log.String("digest", digest)) // Update metadata.json with the new digest and the current date if err = c.updateMetadata(digest, c.clock.Now()); err != nil { @@ -126,8 +123,8 @@ func (c *Client) DownloadBuiltinPolicies(ctx context.Context, registryOpts types return nil } -// LoadBuiltinPolicies loads default policies -func (c *Client) LoadBuiltinPolicies() ([]string, error) { +// LoadBuiltinChecks loads default policies +func (c *Client) LoadBuiltinChecks() ([]string, error) { f, err := os.Open(c.manifestPath()) if err != nil { return nil, xerrors.Errorf("manifest file open error (%s): %w", c.manifestPath(), err) @@ -155,7 +152,7 @@ func (c *Client) LoadBuiltinPolicies() ([]string, error) { // NeedsUpdate returns if the default check should be updated func (c *Client) NeedsUpdate(ctx context.Context, registryOpts types.RegistryOptions) (bool, error) { - meta, err := c.GetMetadata() + meta, err := c.GetMetadata(ctx) if err != nil { return true, nil } @@ -165,10 +162,7 @@ func (c *Client) NeedsUpdate(ctx context.Context, registryOpts types.RegistryOpt return false, nil } - if err = c.populateOCIArtifact(registryOpts); err != nil { - return false, xerrors.Errorf("OPA bundle error: %w", err) - } - + c.populateOCIArtifact(ctx, registryOpts) digest, err := c.artifact.Digest(ctx) if err != nil { return false, xerrors.Errorf("digest error: %w", err) @@ -219,17 +213,17 @@ func (c *Client) updateMetadata(digest string, now time.Time) error { return nil } -func (c *Client) GetMetadata() (*Metadata, error) { +func (c *Client) GetMetadata(ctx context.Context) (*Metadata, error) { f, err := os.Open(c.metadataPath()) if err != nil { - log.Debug("Failed to open the check metadata", log.Err(err)) + log.DebugContext(ctx, "Failed to open the check metadata", log.Err(err)) return nil, err } defer f.Close() var meta Metadata if err = json.NewDecoder(f).Decode(&meta); err != nil { - log.Warn("Check metadata decode error", log.Err(err)) + log.WarnContext(ctx, "Check metadata decode error", log.Err(err)) return nil, err } diff --git a/pkg/policy/policy_test.go b/pkg/policy/policy_test.go index 8e1b175da483..4752fa4ce7fc 100644 --- a/pkg/policy/policy_test.go +++ b/pkg/policy/policy_test.go @@ -116,13 +116,11 @@ func TestClient_LoadBuiltinPolicies(t *testing.T) { }, nil) // Mock OCI artifact - art, err := oci.NewArtifact("repo", true, ftypes.RegistryOptions{}, oci.WithImage(img)) - require.NoError(t, err) - + art := oci.NewArtifact("repo", ftypes.RegistryOptions{}, oci.WithImage(img)) c, err := policy.NewClient(tt.cacheDir, true, "", policy.WithOCIArtifact(art)) require.NoError(t, err) - got, err := c.LoadBuiltinPolicies() + got, err := c.LoadBuiltinChecks() if tt.wantErr != "" { require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) @@ -257,9 +255,7 @@ func TestClient_NeedsUpdate(t *testing.T) { require.NoError(t, err) } - art, err := oci.NewArtifact("repo", true, ftypes.RegistryOptions{}, oci.WithImage(img)) - require.NoError(t, err) - + art := oci.NewArtifact("repo", ftypes.RegistryOptions{}, oci.WithImage(img)) c, err := policy.NewClient(tmpDir, true, "", policy.WithOCIArtifact(art), policy.WithClock(tt.clock)) require.NoError(t, err) @@ -361,13 +357,11 @@ func TestClient_DownloadBuiltinPolicies(t *testing.T) { }, nil) // Mock OCI artifact - art, err := oci.NewArtifact("repo", true, ftypes.RegistryOptions{}, oci.WithImage(img)) - require.NoError(t, err) - + art := oci.NewArtifact("repo", ftypes.RegistryOptions{}, oci.WithImage(img)) c, err := policy.NewClient(tempDir, true, "", policy.WithClock(tt.clock), policy.WithOCIArtifact(art)) require.NoError(t, err) - err = c.DownloadBuiltinPolicies(context.Background(), ftypes.RegistryOptions{}) + err = c.DownloadBuiltinChecks(context.Background(), ftypes.RegistryOptions{}) if tt.wantErr != "" { require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) diff --git a/pkg/purl/purl.go b/pkg/purl/purl.go index ba19d40c26a9..a7b1cb9181f0 100644 --- a/pkg/purl/purl.go +++ b/pkg/purl/purl.go @@ -84,7 +84,7 @@ func New(t ftypes.TargetType, metadata types.Metadata, pkg ftypes.Package) (*Pac var qs packageurl.Qualifiers name, namespace, qs = parseApk(name, metadata.OS) qualifiers = append(qualifiers, qs...) - case packageurl.TypeMaven, string(ftypes.Gradle): // TODO: replace with packageurl.TypeGradle once they add it. + case packageurl.TypeMaven, packageurl.TypeGradle: namespace, name = parseMaven(name) case packageurl.TypePyPi: name = parsePyPI(name) @@ -358,16 +358,19 @@ func parseRPM(fos *ftypes.OS, modularityLabel string) (ftypes.OSType, packageurl return "", packageurl.Qualifiers{} } - // SLES string has whitespace family := fos.Family - if fos.Family == ftypes.SLES { - family = "sles" + // SLES string has whitespace, also highlevel family is not the same as distro + if fos.Family == ftypes.SLES || fos.Family == ftypes.SLEMicro { + family = "suse" + } + if fos.Family == ftypes.OpenSUSETumbleweed || fos.Family == ftypes.OpenSUSELeap { + family = "opensuse" } qualifiers := packageurl.Qualifiers{ { Key: "distro", - Value: fmt.Sprintf("%s-%s", family, fos.Name), + Value: fmt.Sprintf("%s-%s", fos.Family, fos.Name), }, } @@ -476,7 +479,7 @@ func purlType(t ftypes.TargetType) string { return packageurl.TypeDebian case ftypes.RedHat, ftypes.CentOS, ftypes.Rocky, ftypes.Alma, ftypes.Amazon, ftypes.Fedora, ftypes.Oracle, ftypes.OpenSUSE, - ftypes.OpenSUSELeap, ftypes.OpenSUSETumbleweed, ftypes.SLES, ftypes.Photon, + ftypes.OpenSUSELeap, ftypes.OpenSUSETumbleweed, ftypes.SLES, ftypes.SLEMicro, ftypes.Photon, ftypes.Azure, ftypes.CBLMariner: return packageurl.TypeRPM case TypeOCI: diff --git a/pkg/rekor/client.go b/pkg/rekor/client.go index d748166d6d7f..c6390f9679db 100644 --- a/pkg/rekor/client.go +++ b/pkg/rekor/client.go @@ -2,11 +2,10 @@ package rekor import ( "context" - "net/url" + "fmt" "slices" - httptransport "github.com/go-openapi/runtime/client" - "github.com/go-openapi/strfmt" + pkgclient "github.com/sigstore/rekor/pkg/client" "github.com/sigstore/rekor/pkg/generated/client" eclient "github.com/sigstore/rekor/pkg/generated/client/entries" "github.com/sigstore/rekor/pkg/generated/client/index" @@ -14,6 +13,7 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/version/app" ) const ( @@ -64,15 +64,10 @@ type Client struct { } func NewClient(rekorURL string) (*Client, error) { - u, err := url.Parse(rekorURL) + c, err := pkgclient.GetRekorClient(rekorURL, pkgclient.WithUserAgent(fmt.Sprintf("trivy/%s", app.Version()))) if err != nil { - return nil, xerrors.Errorf("failed to parse url: %w", err) + return nil, xerrors.Errorf("failed to create rekor client: %w", err) } - - c := client.New( - httptransport.New(u.Host, client.DefaultBasePath, []string{u.Scheme}), - strfmt.Default, - ) return &Client{Rekor: c}, nil } diff --git a/pkg/rekor/client_test.go b/pkg/rekor/client_test.go index 9ea48d657cc5..f9390122fbf0 100644 --- a/pkg/rekor/client_test.go +++ b/pkg/rekor/client_test.go @@ -4,6 +4,7 @@ import ( "context" "net/http" "net/http/httptest" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -56,6 +57,9 @@ func TestClient_Search(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !strings.HasPrefix(r.UserAgent(), "trivy/") { + t.Fatalf("User-Agent header was not specified") + } http.ServeFile(w, r, tt.mockResponseFile) return })) @@ -148,6 +152,9 @@ func TestClient_GetEntries(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !strings.HasPrefix(r.UserAgent(), "trivy/") { + t.Fatalf("User-Agent header was not specified") + } http.ServeFile(w, r, tt.mockResponseFile) return })) diff --git a/pkg/report/json.go b/pkg/report/json.go index 8529b5274560..e51d524d6509 100644 --- a/pkg/report/json.go +++ b/pkg/report/json.go @@ -14,8 +14,9 @@ import ( // JSONWriter implements result Writer type JSONWriter struct { - Output io.Writer - ListAllPkgs bool + Output io.Writer + ListAllPkgs bool + ShowSuppressed bool } // Write writes the results in JSON format @@ -26,6 +27,12 @@ func (jw JSONWriter) Write(_ context.Context, report types.Report) error { report.Results[i].Packages = nil } } + if !jw.ShowSuppressed { + // Delete suppressed findings + for i := range report.Results { + report.Results[i].ModifiedFindings = nil + } + } report.Results = lo.Filter(report.Results, func(r types.Result, _ int) bool { return r.Target != "" || !r.IsEmpty() }) diff --git a/pkg/report/template_test.go b/pkg/report/template_test.go index 56a2d6df7610..bf2e04b7473e 100644 --- a/pkg/report/template_test.go +++ b/pkg/report/template_test.go @@ -6,11 +6,13 @@ import ( "testing" "time" + "github.com/package-url/packageurl-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy/pkg/clock" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/report" "github.com/aquasecurity/trivy/pkg/types" ) @@ -156,6 +158,33 @@ func TestReportWriter_Template(t *testing.T) { template: `{{ $high := 0 }}{{ $critical := 0 }}{{ range . }}{{ range .Vulnerabilities}}{{ if eq .Severity "HIGH" }}{{ $high = add $high 1 }}{{ end }}{{ if eq .Severity "CRITICAL" }}{{ $critical = add $critical 1 }}{{ end }}{{ end }}Critical: {{ $critical }}, High: {{ $high }}{{ end }}`, expected: `Critical: 2, High: 1`, }, + { + name: "custom JSON marshaler", + detectedVulns: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-0000", + PkgName: "foo", + Status: dbTypes.StatusAffected, + PkgIdentifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeNPM, + Name: "foobar", + Version: "1.2.3", + }, + }, + }, + }, + template: `{{ range . }}{{ range .Vulnerabilities}}{{ toPrettyJson . }}{{ end }}{{ end }}`, + expected: `{ + "VulnerabilityID": "CVE-2019-0000", + "PkgName": "foo", + "PkgIdentifier": { + "PURL": "pkg:npm/foobar@1.2.3" + }, + "Status": "affected", + "Layer": {} +}`, + }, { name: "happy path: env var parsing", detectedVulns: []types.DetectedVulnerability{}, diff --git a/pkg/report/writer.go b/pkg/report/writer.go index 041c9acdfe8e..a5b7ae231599 100644 --- a/pkg/report/writer.go +++ b/pkg/report/writer.go @@ -56,8 +56,9 @@ func Write(ctx context.Context, report types.Report, option flag.Options) (err e } case types.FormatJSON: writer = &JSONWriter{ - Output: output, - ListAllPkgs: option.ListAllPkgs, + Output: output, + ListAllPkgs: option.ListAllPkgs, + ShowSuppressed: option.ShowSuppressed, } case types.FormatGitHub: writer = &github.Writer{ diff --git a/pkg/rpc/client/client.go b/pkg/rpc/client/client.go index 9e13db8ded48..ed7d130dcb06 100644 --- a/pkg/rpc/client/client.go +++ b/pkg/rpc/client/client.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "net/http" + "github.com/twitchtv/twirp" "golang.org/x/xerrors" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -32,6 +33,7 @@ type ScannerOption struct { RemoteURL string Insecure bool CustomHeaders http.Header + PathPrefix string } // Scanner implements the RPC scanner @@ -42,16 +44,15 @@ type Scanner struct { // NewScanner is the factory method to return RPC Scanner func NewScanner(scannerOptions ScannerOption, opts ...Option) Scanner { - httpClient := &http.Client{ - Transport: &http.Transport{ - Proxy: http.ProxyFromEnvironment, - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: scannerOptions.Insecure, - }, - }, - } + tr := http.DefaultTransport.(*http.Transport).Clone() + tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: scannerOptions.Insecure} + httpClient := &http.Client{Transport: tr} - c := rpc.NewScannerProtobufClient(scannerOptions.RemoteURL, httpClient) + var twirpOpts []twirp.ClientOption + if scannerOptions.PathPrefix != "" { + twirpOpts = append(twirpOpts, twirp.WithClientPathPrefix(scannerOptions.PathPrefix)) + } + c := rpc.NewScannerProtobufClient(scannerOptions.RemoteURL, httpClient, twirpOpts...) o := &options{rpcClient: c} for _, opt := range opts { diff --git a/pkg/rpc/convert.go b/pkg/rpc/convert.go index dcd81ce85ded..1662877bd597 100644 --- a/pkg/rpc/convert.go +++ b/pkg/rpc/convert.go @@ -436,6 +436,7 @@ func ConvertFromRPCDetectedLicenses(rpcLicenses []*common.DetectedLicense) []typ PkgName: l.PkgName, FilePath: l.FilePath, Name: l.Name, + Text: l.Text, Confidence: float64(l.Confidence), Link: l.Link, }) @@ -983,6 +984,7 @@ func ConvertToRPCLicenses(licenses []types.DetectedLicense) []*common.DetectedLi PkgName: l.PkgName, FilePath: l.FilePath, Name: l.Name, + Text: l.Text, Confidence: float32(l.Confidence), Link: l.Link, }) diff --git a/pkg/rpc/convert_test.go b/pkg/rpc/convert_test.go index f7c7b3d36d86..6f90c3b5cc8e 100644 --- a/pkg/rpc/convert_test.go +++ b/pkg/rpc/convert_test.go @@ -760,6 +760,7 @@ func TestConvertFromRPCLicenses(t *testing.T) { PkgName: "alpine-baselayout", FilePath: "some-path", Name: "GPL-2.0", + Text: "text://* Permission to use this software in any way is granted without", Confidence: 1, Link: "https://some-link", }, @@ -771,6 +772,7 @@ func TestConvertFromRPCLicenses(t *testing.T) { PkgName: "alpine-baselayout", FilePath: "some-path", Name: "GPL-2.0", + Text: "text://* Permission to use this software in any way is granted without", Confidence: 1, Link: "https://some-link", }, @@ -806,6 +808,7 @@ func TestConvertToRPCLicenses(t *testing.T) { PkgName: "alpine-baselayout", FilePath: "some-path", Name: "GPL-2.0", + Text: "text://* Permission to use this software in any way is granted without", Confidence: 1, Link: "https://some-link", }, @@ -817,6 +820,7 @@ func TestConvertToRPCLicenses(t *testing.T) { PkgName: "alpine-baselayout", FilePath: "some-path", Name: "GPL-2.0", + Text: "text://* Permission to use this software in any way is granted without", Confidence: 1, Link: "https://some-link", }, diff --git a/pkg/rpc/server/listen.go b/pkg/rpc/server/listen.go index 0c4484930773..c1bc5033530c 100644 --- a/pkg/rpc/server/listen.go +++ b/pkg/rpc/server/listen.go @@ -5,6 +5,7 @@ import ( "encoding/json" "net/http" "os" + "strings" "sync" "time" @@ -28,26 +29,30 @@ const updateInterval = 1 * time.Hour // Server represents Trivy server type Server struct { - appVersion string - addr string - dbDir string - token string - tokenHeader string - dbRepository name.Reference + appVersion string + addr string + cacheDir string + dbDir string + token string + tokenHeader string + pathPrefix string + dbRepositories []name.Reference // For OCI registries types.RegistryOptions } // NewServer returns an instance of Server -func NewServer(appVersion, addr, cacheDir, token, tokenHeader string, dbRepository name.Reference, opt types.RegistryOptions) Server { +func NewServer(appVersion, addr, cacheDir, token, tokenHeader, pathPrefix string, dbRepositories []name.Reference, opt types.RegistryOptions) Server { return Server{ appVersion: appVersion, addr: addr, + cacheDir: cacheDir, dbDir: db.Dir(cacheDir), token: token, tokenHeader: tokenHeader, - dbRepository: dbRepository, + pathPrefix: pathPrefix, + dbRepositories: dbRepositories, RegistryOptions: opt, } } @@ -58,7 +63,7 @@ func (s Server) ListenAndServe(ctx context.Context, serverCache cache.Cache, ski dbUpdateWg := &sync.WaitGroup{} go func() { - worker := newDBWorker(db.NewClient(s.dbDir, true, db.WithDBRepository(s.dbRepository))) + worker := newDBWorker(db.NewClient(s.dbDir, true, db.WithDBRepository(s.dbRepositories))) for { time.Sleep(updateInterval) if err := worker.update(ctx, s.appVersion, s.dbDir, skipDBUpdate, dbUpdateWg, requestWg, s.RegistryOptions); err != nil { @@ -67,14 +72,13 @@ func (s Server) ListenAndServe(ctx context.Context, serverCache cache.Cache, ski } }() - mux := newServeMux(ctx, serverCache, dbUpdateWg, requestWg, s.token, s.tokenHeader, s.dbDir) + mux := s.NewServeMux(ctx, serverCache, dbUpdateWg, requestWg) log.Infof("Listening %s...", s.addr) return http.ListenAndServe(s.addr, mux) } -func newServeMux(ctx context.Context, serverCache cache.Cache, dbUpdateWg, requestWg *sync.WaitGroup, - token, tokenHeader, cacheDir string) *http.ServeMux { +func (s Server) NewServeMux(ctx context.Context, serverCache cache.Cache, dbUpdateWg, requestWg *sync.WaitGroup) *http.ServeMux { withWaitGroup := func(base http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Stop processing requests during DB update @@ -91,13 +95,19 @@ func newServeMux(ctx context.Context, serverCache cache.Cache, dbUpdateWg, reque mux := http.NewServeMux() - scanServer := rpcScanner.NewScannerServer(initializeScanServer(serverCache), nil) - scanHandler := withToken(withWaitGroup(scanServer), token, tokenHeader) - mux.Handle(rpcScanner.ScannerPathPrefix, gziphandler.GzipHandler(scanHandler)) + var twirpOpts []any + if s.pathPrefix != "" { + pathPrefix := "/" + strings.TrimPrefix(s.pathPrefix, "/") // Twirp requires the leading slash + twirpOpts = append(twirpOpts, twirp.WithServerPathPrefix(pathPrefix)) + } + + scanServer := rpcScanner.NewScannerServer(initializeScanServer(serverCache), twirpOpts...) + scanHandler := withToken(withWaitGroup(scanServer), s.token, s.tokenHeader) + mux.Handle(scanServer.PathPrefix(), gziphandler.GzipHandler(scanHandler)) - layerServer := rpcCache.NewCacheServer(NewCacheServer(serverCache), nil) - layerHandler := withToken(withWaitGroup(layerServer), token, tokenHeader) - mux.Handle(rpcCache.CachePathPrefix, gziphandler.GzipHandler(layerHandler)) + cacheServer := rpcCache.NewCacheServer(NewCacheServer(serverCache), twirpOpts...) + 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) { if _, err := rw.Write([]byte("ok")); err != nil { @@ -108,7 +118,7 @@ func newServeMux(ctx context.Context, serverCache cache.Cache, dbUpdateWg, reque mux.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") - if err := json.NewEncoder(w).Encode(version.NewVersionInfo(cacheDir)); err != nil { + if err := json.NewEncoder(w).Encode(version.NewVersionInfo(s.cacheDir)); err != nil { log.Error("Version error", log.Err(err)) } }) diff --git a/pkg/rpc/server/listen_test.go b/pkg/rpc/server/listen_test.go index 8457cef042d7..7f97bdbcb31e 100644 --- a/pkg/rpc/server/listen_test.go +++ b/pkg/rpc/server/listen_test.go @@ -115,7 +115,7 @@ func Test_dbWorker_update(t *testing.T) { } } -func Test_newServeMux(t *testing.T) { +func TestServer_newServeMux(t *testing.T) { type args struct { token string tokenHeader string @@ -182,9 +182,8 @@ func Test_newServeMux(t *testing.T) { require.NoError(t, err) defer func() { _ = c.Close() }() - ts := httptest.NewServer(newServeMux(context.Background(), c, dbUpdateWg, requestWg, tt.args.token, - tt.args.tokenHeader, ""), - ) + s := NewServer("", "", "", tt.args.token, tt.args.tokenHeader, "", nil, ftypes.RegistryOptions{}) + ts := httptest.NewServer(s.NewServeMux(context.Background(), c, dbUpdateWg, requestWg)) defer ts.Close() var resp *http.Response @@ -214,9 +213,8 @@ func Test_VersionEndpoint(t *testing.T) { require.NoError(t, err) defer func() { _ = c.Close() }() - ts := httptest.NewServer(newServeMux(context.Background(), c, dbUpdateWg, requestWg, "", "", - "testdata/testcache"), - ) + s := NewServer("", "", "testdata/testcache", "", "", "", nil, ftypes.RegistryOptions{}) + ts := httptest.NewServer(s.NewServeMux(context.Background(), c, dbUpdateWg, requestWg)) defer ts.Close() resp, err := http.Get(ts.URL + "/version") diff --git a/pkg/sbom/cyclonedx/marshal_test.go b/pkg/sbom/cyclonedx/marshal_test.go index e778b803619c..8b88c4ec762a 100644 --- a/pkg/sbom/cyclonedx/marshal_test.go +++ b/pkg/sbom/cyclonedx/marshal_test.go @@ -1462,7 +1462,7 @@ func TestMarshaler_MarshalReport(t *testing.T) { Name: "com.fasterxml.jackson.core:jackson-databind", Version: "2.13.4.1", Identifier: ftypes.PkgIdentifier{ - BOMRef: "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.1?file_path=jackson-databind-2.13.4.1.jar", + BOMRef: "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.1", UID: "9A5066570222D04C", PURL: &packageurl.PackageURL{ Type: packageurl.TypeMaven, @@ -1480,7 +1480,7 @@ func TestMarshaler_MarshalReport(t *testing.T) { PkgName: "com.fasterxml.jackson.core:jackson-databind", PkgPath: "jackson-databind-2.13.4.1.jar", PkgIdentifier: ftypes.PkgIdentifier{ - BOMRef: "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.1?file_path=jackson-databind-2.13.4.1.jar", + BOMRef: "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.1", UID: "9A5066570222D04C", PURL: &packageurl.PackageURL{ Type: packageurl.TypeMaven, diff --git a/pkg/sbom/cyclonedx/testdata/happy/third-party-bom.json b/pkg/sbom/cyclonedx/testdata/happy/third-party-bom.json index d8b5068f5cc0..281d3c4fcc30 100644 --- a/pkg/sbom/cyclonedx/testdata/happy/third-party-bom.json +++ b/pkg/sbom/cyclonedx/testdata/happy/third-party-bom.json @@ -46,7 +46,7 @@ }, { "bom-ref": "pkg:composer/pear/pear_exception@v1.0.0", - "type": "library", + "type": "framework", "name": "pear/pear_exception", "version": "v1.0.0", "purl": "pkg:composer/pear/pear_exception@v1.0.0" diff --git a/pkg/sbom/cyclonedx/unmarshal.go b/pkg/sbom/cyclonedx/unmarshal.go index 71a0ee27b640..2a2456063670 100644 --- a/pkg/sbom/cyclonedx/unmarshal.go +++ b/pkg/sbom/cyclonedx/unmarshal.go @@ -128,13 +128,17 @@ func (b *BOM) parseComponent(c cdx.Component) (*core.Component, error) { return nil, xerrors.Errorf("failed to unmarshal component type: %w", err) } + identifier := ftypes.PkgIdentifier{ + BOMRef: c.BOMRef, + } + // Parse PURL - var purl packageurl.PackageURL if c.PackageURL != "" { - purl, err = packageurl.FromString(c.PackageURL) + purl, err := packageurl.FromString(c.PackageURL) if err != nil { return nil, xerrors.Errorf("failed to parse PURL: %w", err) } + identifier.PURL = &purl } component := &core.Component{ @@ -148,12 +152,9 @@ func (b *BOM) parseComponent(c cdx.Component) (*core.Component, error) { Digests: b.unmarshalHashes(c.Hashes), }, }, - PkgIdentifier: ftypes.PkgIdentifier{ - PURL: &purl, - BOMRef: c.BOMRef, - }, - Supplier: b.unmarshalSupplier(c.Supplier), - Properties: b.unmarshalProperties(c.Properties), + PkgIdentifier: identifier, + Supplier: b.unmarshalSupplier(c.Supplier), + Properties: b.unmarshalProperties(c.Properties), } return component, nil @@ -166,7 +167,10 @@ func (b *BOM) unmarshalType(t cdx.ComponentType) (core.ComponentType, error) { ctype = core.TypeContainerImage case cdx.ComponentTypeApplication: ctype = core.TypeApplication - case cdx.ComponentTypeLibrary: + // There are not many differences between a `library` and a `framework` components, and sometimes it is difficult to choose the right type. + // That is why some users choose `framework` type. + // So we should parse and scan `framework` components as libraries. + case cdx.ComponentTypeLibrary, cdx.ComponentTypeFramework: ctype = core.TypeLibrary case cdx.ComponentTypeOS: ctype = core.TypeOS diff --git a/pkg/sbom/io/encode.go b/pkg/sbom/io/encode.go index 0be0bf361280..975b98d40b10 100644 --- a/pkg/sbom/io/encode.go +++ b/pkg/sbom/io/encode.go @@ -5,6 +5,7 @@ import ( "slices" "strconv" + "github.com/google/uuid" "github.com/package-url/packageurl-go" "github.com/samber/lo" "golang.org/x/xerrors" @@ -19,8 +20,9 @@ import ( ) type Encoder struct { - bom *core.BOM - opts core.Options + bom *core.BOM + opts core.Options + components map[uuid.UUID]*core.Component } func NewEncoder(opts core.Options) *Encoder { @@ -28,6 +30,9 @@ func NewEncoder(opts core.Options) *Encoder { } func (e *Encoder) Encode(report types.Report) (*core.BOM, error) { + if report.BOM != nil { + e.components = report.BOM.Components() + } // Metadata component root, err := e.rootComponent(report) if err != nil { @@ -257,6 +262,16 @@ func (e *Encoder) encodePackages(parent *core.Component, result types.Result) { } } +// existedPkgIdentifier tries to look for package identifier (BOM-ref, PURL) by component name and component type +func (e *Encoder) existedPkgIdentifier(name string, componentType core.ComponentType) ftypes.PkgIdentifier { + for _, c := range e.components { + if c.Name == name && c.Type == componentType { + return c.PkgIdentifier + } + } + return ftypes.PkgIdentifier{} +} + func (e *Encoder) resultComponent(root *core.Component, r types.Result, osFound *ftypes.OS) *core.Component { component := &core.Component{ Name: r.Target, @@ -279,8 +294,10 @@ func (e *Encoder) resultComponent(root *core.Component, r types.Result, osFound component.Version = osFound.Name } component.Type = core.TypeOS + component.PkgIdentifier = e.existedPkgIdentifier(component.Name, component.Type) case types.ClassLangPkg: component.Type = core.TypeApplication + component.PkgIdentifier = e.existedPkgIdentifier(component.Name, component.Type) } e.bom.AddRelationship(root, component, core.RelationshipContains) @@ -377,8 +394,9 @@ func (*Encoder) component(result types.Result, pkg ftypes.Package) *core.Compone SrcVersion: utils.FormatSrcVersion(pkg), SrcFile: srcFile, PkgIdentifier: ftypes.PkgIdentifier{ - UID: pkg.Identifier.UID, - PURL: pkg.Identifier.PURL, + UID: pkg.Identifier.UID, + PURL: pkg.Identifier.PURL, + BOMRef: pkg.Identifier.BOMRef, }, Supplier: pkg.Maintainer, Licenses: pkg.Licenses, diff --git a/pkg/sbom/spdx/marshal.go b/pkg/sbom/spdx/marshal.go index 35bd0448aa5d..809ce95d7e4c 100644 --- a/pkg/sbom/spdx/marshal.go +++ b/pkg/sbom/spdx/marshal.go @@ -33,6 +33,7 @@ const ( CreatorOrganization = "aquasecurity" CreatorTool = "trivy" noneField = "NONE" + noAssertionField = "NOASSERTION" ) const ( @@ -378,7 +379,7 @@ func (m *Marshaler) spdxAttributionTexts(c *core.Component) []string { func (m *Marshaler) spdxLicense(c *core.Component) string { if len(c.Licenses) == 0 { - return noneField + return noAssertionField } return NormalizeLicense(c.Licenses) } @@ -546,7 +547,7 @@ func NormalizeLicense(licenses []string) string { return fmt.Sprintf("(%s)", license) }), " AND ") - s, err := expression.Normalize(license, licensing.Normalize, expression.NormalizeForSPDX) + s, err := expression.Normalize(license, licensing.NormalizeLicense, expression.NormalizeForSPDX) if err != nil { // Not fail on the invalid license log.Warn("Unable to marshal SPDX licenses", log.String("license", license)) diff --git a/pkg/sbom/spdx/marshal_test.go b/pkg/sbom/spdx/marshal_test.go index 3cd034803ffc..74e9bee0a45d 100644 --- a/pkg/sbom/spdx/marshal_test.go +++ b/pkg/sbom/spdx/marshal_test.go @@ -11,6 +11,7 @@ import ( "github.com/package-url/packageurl-go" "github.com/spdx/tools-golang/spdx" "github.com/spdx/tools-golang/spdx/v2/common" + "github.com/spdx/tools-golang/spdxlib" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -217,8 +218,8 @@ func TestMarshaler_Marshal(t *testing.T) { PackageDownloadLocation: "NONE", PackageName: "actioncontroller", PackageVersion: "7.0.1", - PackageLicenseConcluded: "NONE", - PackageLicenseDeclared: "NONE", + PackageLicenseConcluded: "NOASSERTION", + PackageLicenseDeclared: "NOASSERTION", PackageAttributionTexts: []string{ "PkgType: bundler", }, @@ -238,8 +239,8 @@ func TestMarshaler_Marshal(t *testing.T) { PackageDownloadLocation: "NONE", PackageName: "actionpack", PackageVersion: "7.0.1", - PackageLicenseConcluded: "NONE", - PackageLicenseDeclared: "NONE", + PackageLicenseConcluded: "NOASSERTION", + PackageLicenseDeclared: "NOASSERTION", PackageAttributionTexts: []string{ "PkgType: bundler", }, @@ -259,8 +260,8 @@ func TestMarshaler_Marshal(t *testing.T) { PackageDownloadLocation: "NONE", PackageName: "actionpack", PackageVersion: "7.0.1", - PackageLicenseConcluded: "NONE", - PackageLicenseDeclared: "NONE", + PackageLicenseConcluded: "NOASSERTION", + PackageLicenseDeclared: "NOASSERTION", PackageAttributionTexts: []string{ "PkgType: bundler", }, @@ -536,8 +537,8 @@ func TestMarshaler_Marshal(t *testing.T) { PackageDownloadLocation: "NONE", PackageName: "actionpack", PackageVersion: "7.0.1", - PackageLicenseConcluded: "NONE", - PackageLicenseDeclared: "NONE", + PackageLicenseConcluded: "NOASSERTION", + PackageLicenseDeclared: "NOASSERTION", PackageExternalReferences: []*spdx.PackageExternalReference{ { Category: tspdx.CategoryPackageManager, @@ -561,8 +562,8 @@ func TestMarshaler_Marshal(t *testing.T) { PackageDownloadLocation: "NONE", PackageName: "actionpack", PackageVersion: "7.0.1", - PackageLicenseConcluded: "NONE", - PackageLicenseDeclared: "NONE", + PackageLicenseConcluded: "NOASSERTION", + PackageLicenseDeclared: "NOASSERTION", PackageExternalReferences: []*spdx.PackageExternalReference{ { Category: tspdx.CategoryPackageManager, @@ -750,8 +751,8 @@ func TestMarshaler_Marshal(t *testing.T) { PackageDownloadLocation: "NONE", PackageName: "actioncable", PackageVersion: "6.1.4.1", - PackageLicenseConcluded: "NONE", - PackageLicenseDeclared: "NONE", + PackageLicenseConcluded: "NOASSERTION", + PackageLicenseDeclared: "NOASSERTION", PackageExternalReferences: []*spdx.PackageExternalReference{ { Category: tspdx.CategoryPackageManager, @@ -771,8 +772,8 @@ func TestMarshaler_Marshal(t *testing.T) { PackageDownloadLocation: "NONE", PackageName: "com.example:example", PackageVersion: "1.0.0", - PackageLicenseConcluded: "NONE", - PackageLicenseDeclared: "NONE", + PackageLicenseConcluded: "NOASSERTION", + PackageLicenseDeclared: "NOASSERTION", PackageExternalReferences: []*spdx.PackageExternalReference{ { Category: tspdx.CategoryPackageManager, @@ -889,8 +890,8 @@ func TestMarshaler_Marshal(t *testing.T) { PackageDownloadLocation: "NONE", PackageName: "org.apache.logging.log4j:log4j-core", PackageVersion: "2.17.0", - PackageLicenseConcluded: "NONE", - PackageLicenseDeclared: "NONE", + PackageLicenseConcluded: "NOASSERTION", + PackageLicenseDeclared: "NOASSERTION", PackageExternalReferences: []*spdx.PackageExternalReference{ { Category: tspdx.CategoryPackageManager, @@ -1229,8 +1230,8 @@ func TestMarshaler_Marshal(t *testing.T) { PackageSPDXIdentifier: spdx.ElementID("Package-b1c3b9e2363f5ff7"), PackageDownloadLocation: "NONE", PackageName: "./private_repos/cnrm.googlesource.com/cnrm/", - PackageLicenseConcluded: "NONE", - PackageLicenseDeclared: "NONE", + PackageLicenseConcluded: "NOASSERTION", + PackageLicenseDeclared: "NOASSERTION", PrimaryPackagePurpose: tspdx.PackagePurposeLibrary, PackageSupplier: &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion}, PackageSourceInfo: "package found in: /usr/local/bin/test", @@ -1243,8 +1244,8 @@ func TestMarshaler_Marshal(t *testing.T) { PackageDownloadLocation: "NONE", PackageName: "golang.org/x/crypto", PackageVersion: "v0.0.1", - PackageLicenseConcluded: "NONE", - PackageLicenseDeclared: "NONE", + PackageLicenseConcluded: "NOASSERTION", + PackageLicenseDeclared: "NOASSERTION", PackageExternalReferences: []*spdx.PackageExternalReference{ { Category: tspdx.CategoryPackageManager, @@ -1329,6 +1330,7 @@ func TestMarshaler_Marshal(t *testing.T) { spdxDoc, err := marshaler.MarshalReport(ctx, tc.inputReport) require.NoError(t, err) + assert.NoError(t, spdxlib.ValidateDocument(spdxDoc)) assert.Equal(t, tc.wantSBOM, spdxDoc) }) } @@ -1361,7 +1363,7 @@ func Test_GetLicense(t *testing.T) { "GPLv2+", "LGPL 2.0 or GNU LESSER", }, - want: "GPL-2.0-or-later AND (LGPL-2.0-only OR LGPL-3.0-only)", + want: "GPL-2.0-or-later AND (LGPL-2.0-only OR LGPL-2.1-only)", }, { name: "happy path with AND operator", @@ -1369,7 +1371,7 @@ func Test_GetLicense(t *testing.T) { "GPLv2+", "LGPL 2.0 and GNU LESSER", }, - want: "GPL-2.0-or-later AND LGPL-2.0-only AND LGPL-3.0-only", + want: "GPL-2.0-or-later AND LGPL-2.0-only AND LGPL-2.1-only", }, { name: "happy path with WITH operator", diff --git a/pkg/scanner/local/scan.go b/pkg/scanner/local/scan.go index 3b0c719721e2..f34723058e06 100644 --- a/pkg/scanner/local/scan.go +++ b/pkg/scanner/local/scan.go @@ -261,14 +261,7 @@ func (s Scanner) scanLicenses(target types.ScanTarget, options types.ScanOptions var osPkgLicenses []types.DetectedLicense for _, pkg := range target.Packages { for _, license := range pkg.Licenses { - category, severity := scanner.Scan(license) - osPkgLicenses = append(osPkgLicenses, types.DetectedLicense{ - Severity: severity, - Category: category, - PkgName: pkg.Name, - Name: license, - Confidence: 1.0, - }) + osPkgLicenses = append(osPkgLicenses, toDetectedLicense(scanner, license, pkg.Name, "")) } } results = append(results, types.Result{ @@ -282,17 +275,11 @@ func (s Scanner) scanLicenses(target types.ScanTarget, options types.ScanOptions var langLicenses []types.DetectedLicense for _, lib := range app.Packages { for _, license := range lib.Licenses { - category, severity := scanner.Scan(license) - langLicenses = append(langLicenses, types.DetectedLicense{ - Severity: severity, - Category: category, - PkgName: lib.Name, - Name: license, - // Lock files use app.FilePath - https://github.com/aquasecurity/trivy/blob/6ccc0a554b07b05fd049f882a1825a0e1e0aabe1/pkg/fanal/types/artifact.go#L245-L246 - // Applications use lib.FilePath - https://github.com/aquasecurity/trivy/blob/6ccc0a554b07b05fd049f882a1825a0e1e0aabe1/pkg/fanal/types/artifact.go#L93-L94 - FilePath: lo.Ternary(lib.FilePath != "", lib.FilePath, app.FilePath), - Confidence: 1.0, - }) + // Lock files use app.FilePath - https://github.com/aquasecurity/trivy/blob/6ccc0a554b07b05fd049f882a1825a0e1e0aabe1/pkg/fanal/types/artifact.go#L245-L246 + // Applications use lib.FilePath - https://github.com/aquasecurity/trivy/blob/6ccc0a554b07b05fd049f882a1825a0e1e0aabe1/pkg/fanal/types/artifact.go#L93-L94 + filePath := lo.Ternary(lib.FilePath != "", lib.FilePath, app.FilePath) + + langLicenses = append(langLicenses, toDetectedLicense(scanner, license, lib.Name, filePath)) } } @@ -390,6 +377,29 @@ func toDetectedMisconfiguration(res ftypes.MisconfResult, defaultSeverity dbType } } +func toDetectedLicense(scanner licensing.Scanner, license, pkgName, filePath string) types.DetectedLicense { + var category ftypes.LicenseCategory + 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) + } else { // License name + category, severity = scanner.Scan(license) + } + + return types.DetectedLicense{ + Severity: severity, + Category: category, + PkgName: pkgName, + FilePath: filePath, + Name: license, + Text: licenseText, + Confidence: 1.0, + } +} + func ShouldScanMisconfigOrRbac(scanners types.Scanners) bool { return scanners.AnyEnabled(types.MisconfigScanner, types.RBACScanner) } diff --git a/pkg/scanner/local/scan_test.go b/pkg/scanner/local/scan_test.go index 7d60697fa23a..f3e6cac5d3fa 100644 --- a/pkg/scanner/local/scan_test.go +++ b/pkg/scanner/local/scan_test.go @@ -64,6 +64,25 @@ var ( }, Licenses: []string{"MIT"}, } + python39min = ftypes.Package{ + Name: "python3.9-minimal", + Version: "3.9.1", + FilePath: "/usr/lib/python/site-packages/python3.9-minimal/METADATA", + Layer: ftypes.Layer{ + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, + Licenses: []string{"text://Redistribution and use in source and binary forms, with or without"}, + } + menuinstPkg = ftypes.Package{ + Name: "menuinst", + Version: "2.0.2", + FilePath: "opt/conda/lib/python3.11/site-packages/menuinst-2.0.2.dist-info/METADATA", + Layer: ftypes.Layer{ + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, + Licenses: []string{"text://(c) 2016 Continuum Analytics, Inc. / http://continuum.io All Rights Reserved"}, + } + laravelPkg = ftypes.Package{ Name: "laravel/framework", Version: "6.0.0", @@ -225,6 +244,7 @@ func TestScanner_Scan(t *testing.T) { }, Packages: []ftypes.Package{ muslPkg, + python39min, }, Applications: []ftypes.Application{ { @@ -239,6 +259,7 @@ func TestScanner_Scan(t *testing.T) { FilePath: "", Packages: []ftypes.Package{ urllib3Pkg, + menuinstPkg, }, }, }, @@ -257,6 +278,14 @@ func TestScanner_Scan(t *testing.T) { Name: "MIT", Confidence: 1, }, + { + Severity: "UNKNOWN", + Category: "unknown", + PkgName: python39min.Name, + Name: "CUSTOM License: Redistribution and use...", + Text: "Redistribution and use in source and binary forms, with or without", + Confidence: 1, + }, }, }, { @@ -286,6 +315,15 @@ func TestScanner_Scan(t *testing.T) { 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, + }, }, }, { diff --git a/pkg/types/finding.go b/pkg/types/finding.go index 9e194b8e6b74..65bc4b930fc9 100644 --- a/pkg/types/finding.go +++ b/pkg/types/finding.go @@ -1,5 +1,11 @@ package types +import ( + "encoding/json" + + "golang.org/x/xerrors" +) + type FindingType string type FindingStatus string @@ -45,3 +51,44 @@ func NewModifiedFinding(f finding, status FindingStatus, statement, source strin Finding: f, } } + +// UnmarshalJSON unmarshals ModifiedFinding given the type and `UnmarshalJSON` functions of struct fields +func (m *ModifiedFinding) UnmarshalJSON(data []byte) error { + type Alias ModifiedFinding + aux := &struct { + Finding json.RawMessage `json:"Finding"` + *Alias + }{ + Alias: (*Alias)(m), + } + + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + + // Select struct by m.Type to avoid errors with Unmarshal + var err error + switch m.Type { + case FindingTypeVulnerability: + m.Finding, err = unmarshalFinding[DetectedVulnerability](aux.Finding) + case FindingTypeMisconfiguration: + m.Finding, err = unmarshalFinding[DetectedMisconfiguration](aux.Finding) + case FindingTypeSecret: + m.Finding, err = unmarshalFinding[DetectedSecret](aux.Finding) + case FindingTypeLicense: + m.Finding, err = unmarshalFinding[DetectedLicense](aux.Finding) + default: + return xerrors.Errorf("invalid Finding type: %s", m.Type) + } + + if err != nil { + return xerrors.Errorf("unable to unmarshal %q type: %w", m.Type, err) + } + return nil +} + +func unmarshalFinding[T finding](data []byte) (T, error) { + var f T + err := json.Unmarshal(data, &f) + return f, err +} diff --git a/pkg/types/license.go b/pkg/types/license.go index baca0328a457..22dd6bcb2040 100644 --- a/pkg/types/license.go +++ b/pkg/types/license.go @@ -22,6 +22,9 @@ type DetectedLicense struct { // Name holds a detected license name Name string + // Text holds a long license text if Trivy detects a license name as a license text + Text string + // Confidence is level of the match. The confidence level is between 0.0 and 1.0, with 1.0 indicating an // exact match and 0.0 indicating a complete mismatch Confidence float64 diff --git a/pkg/types/report.go b/pkg/types/report.go index 6937f8ce7960..af364c95a11d 100644 --- a/pkg/types/report.go +++ b/pkg/types/report.go @@ -120,8 +120,8 @@ type Result struct { // ModifiedFindings holds a list of findings that have been modified from their original state. // This can include vulnerabilities that have been marked as ignored, not affected, or have had - // their severity adjusted. It is currently available only in the table format. - ModifiedFindings []ModifiedFinding `json:"-"` + // their severity adjusted. It's still in an experimental stage and may change in the future. + ModifiedFindings []ModifiedFinding `json:"ExperimentalModifiedFindings,omitempty"` } func (r *Result) IsEmpty() bool { diff --git a/pkg/types/scan.go b/pkg/types/scan.go index 09b90e745c58..0fef0028bef3 100644 --- a/pkg/types/scan.go +++ b/pkg/types/scan.go @@ -1,9 +1,100 @@ package types import ( + "slices" + "github.com/aquasecurity/trivy/pkg/fanal/types" ) +// PkgType represents package type +type PkgType = string + +// Scanner represents the type of security scanning +type Scanner string + +// Scanners is a slice of scanners +type Scanners []Scanner + +const ( + // PkgTypeUnknown is a package type of unknown + PkgTypeUnknown PkgType = "unknown" + + // PkgTypeOS is a package type of OS packages + PkgTypeOS PkgType = "os" + + // PkgTypeLibrary is a package type of programming language dependencies + PkgTypeLibrary PkgType = "library" + + // UnknownScanner is the scanner of unknown + UnknownScanner Scanner = "unknown" + + // NoneScanner is the scanner of none + NoneScanner Scanner = "none" + + // SBOMScanner is the virtual scanner of SBOM, which cannot be enabled by the user + SBOMScanner Scanner = "sbom" + + // VulnerabilityScanner is the scanner of vulnerabilities + VulnerabilityScanner Scanner = "vuln" + + // MisconfigScanner is the scanner of misconfigurations + MisconfigScanner Scanner = "misconfig" + + // SecretScanner is the scanner of secrets + SecretScanner Scanner = "secret" + + // RBACScanner is the scanner of rbac assessment + RBACScanner Scanner = "rbac" + + // LicenseScanner is the scanner of licenses + LicenseScanner Scanner = "license" +) + +var ( + PkgTypes = []string{ + PkgTypeOS, + PkgTypeLibrary, + } + + AllScanners = Scanners{ + VulnerabilityScanner, + MisconfigScanner, + RBACScanner, + SecretScanner, + LicenseScanner, + NoneScanner, + } + + // AllImageConfigScanners has a list of available scanners on container image config. + // The container image in container registries consists of manifest, config and layers. + // Trivy is also able to detect security issues on the image config. + AllImageConfigScanners = Scanners{ + MisconfigScanner, + SecretScanner, + NoneScanner, + } +) + +func (scanners *Scanners) Enable(s Scanner) { + if !scanners.Enabled(s) { + *scanners = append(*scanners, s) + } +} + +func (scanners *Scanners) Enabled(s Scanner) bool { + return slices.Contains(*scanners, s) +} + +// AnyEnabled returns true if any of the passed scanners is included. +func (scanners *Scanners) AnyEnabled(ss ...Scanner) bool { + for _, s := range ss { + if scanners.Enabled(s) { + return true + } + } + return false +} + // ScanTarget holds the attributes for scanning. type ScanTarget struct { Name string // container image name, file path, etc diff --git a/pkg/types/target.go b/pkg/types/target.go deleted file mode 100644 index a8cccced3de7..000000000000 --- a/pkg/types/target.go +++ /dev/null @@ -1,94 +0,0 @@ -package types - -import ( - "slices" -) - -// PkgType represents package type -type PkgType = string - -// Scanner represents the type of security scanning -type Scanner string - -// Scanners is a slice of scanners -type Scanners []Scanner - -const ( - // PkgTypeUnknown is a package type of unknown - PkgTypeUnknown = PkgType("unknown") - - // PkgTypeOS is a package type of OS packages - PkgTypeOS = PkgType("os") - - // PkgTypeLibrary is a package type of programming language dependencies - PkgTypeLibrary = PkgType("library") - - // UnknownScanner is the scanner of unknown - UnknownScanner = Scanner("unknown") - - // NoneScanner is the scanner of none - NoneScanner = Scanner("none") - - // SBOMScanner is the virtual scanner of SBOM, which cannot be enabled by the user - SBOMScanner = Scanner("sbom") - - // VulnerabilityScanner is the scanner of vulnerabilities - VulnerabilityScanner = Scanner("vuln") - - // MisconfigScanner is the scanner of misconfigurations - MisconfigScanner = Scanner("misconfig") - - // SecretScanner is the scanner of secrets - SecretScanner = Scanner("secret") - - // RBACScanner is the scanner of rbac assessment - RBACScanner = Scanner("rbac") - - // LicenseScanner is the scanner of licenses - LicenseScanner = Scanner("license") -) - -var ( - PkgTypes = []string{ - PkgTypeOS, - PkgTypeLibrary, - } - - AllScanners = Scanners{ - VulnerabilityScanner, - MisconfigScanner, - RBACScanner, - SecretScanner, - LicenseScanner, - NoneScanner, - } - - // AllImageConfigScanners has a list of available scanners on container image config. - // The container image in container registries consists of manifest, config and layers. - // Trivy is also able to detect security issues on the image config. - AllImageConfigScanners = Scanners{ - MisconfigScanner, - SecretScanner, - NoneScanner, - } -) - -func (scanners *Scanners) Enable(s Scanner) { - if !scanners.Enabled(s) { - *scanners = append(*scanners, s) - } -} - -func (scanners *Scanners) Enabled(s Scanner) bool { - return slices.Contains(*scanners, s) -} - -// AnyEnabled returns true if any of the passed scanners is included. -func (scanners *Scanners) AnyEnabled(ss ...Scanner) bool { - for _, s := range ss { - if scanners.Enabled(s) { - return true - } - } - return false -} diff --git a/pkg/utils/fsutils/fs.go b/pkg/utils/fsutils/fs.go index e0518236f9a8..b2aea51dda0a 100644 --- a/pkg/utils/fsutils/fs.go +++ b/pkg/utils/fsutils/fs.go @@ -1,7 +1,6 @@ package fsutils import ( - "errors" "fmt" "io" "io/fs" @@ -59,18 +58,13 @@ func CopyFile(src, dst string) (int64, error) { } func DirExists(path string) bool { - if f, err := os.Stat(path); os.IsNotExist(err) || !f.IsDir() { - return false - } - return true + f, err := os.Stat(path) + return err == nil && f.IsDir() } func FileExists(filename string) bool { - _, err := os.Stat(filename) - if errors.Is(err, os.ErrNotExist) { - return false - } - return err == nil + f, err := os.Stat(filename) + return err == nil && !f.IsDir() } type WalkDirRequiredFunc func(path string, d fs.DirEntry) bool diff --git a/pkg/utils/fsutils/fs_test.go b/pkg/utils/fsutils/fs_test.go index fef4b7f5a32c..42a4b113a5ef 100644 --- a/pkg/utils/fsutils/fs_test.go +++ b/pkg/utils/fsutils/fs_test.go @@ -2,29 +2,13 @@ package fsutils import ( "os" + "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func touch(t *testing.T, name string) { - f, err := os.Create(name) - if err != nil { - t.Fatal(err) - } - if err := f.Close(); err != nil { - t.Fatal(err) - } -} - -func write(t *testing.T, name, content string) { - err := os.WriteFile(name, []byte(content), 0666) - if err != nil { - t.Fatal(err) - } -} - func TestCopyFile(t *testing.T) { type args struct { src string @@ -72,3 +56,47 @@ func TestCopyFile(t *testing.T) { }) } } + +func TestDirExists(t *testing.T) { + t.Run("invalid path", func(t *testing.T) { + assert.False(t, DirExists("\000invalid:path")) + }) + + t.Run("valid path", func(t *testing.T) { + assert.True(t, DirExists(t.TempDir())) + }) + + t.Run("dir not exist", func(t *testing.T) { + assert.False(t, DirExists(filepath.Join(t.TempDir(), "tmp"))) + }) + + t.Run("file path", func(t *testing.T) { + filePath := filepath.Join(t.TempDir(), "tmp") + f, err := os.Create(filePath) + require.NoError(t, f.Close()) + require.NoError(t, err) + assert.False(t, DirExists(filePath)) + }) +} + +func TestFileExists(t *testing.T) { + t.Run("invalid path", func(t *testing.T) { + assert.False(t, FileExists("\000invalid:path")) + }) + + t.Run("valid path", func(t *testing.T) { + filePath := filepath.Join(t.TempDir(), "tmp") + f, err := os.Create(filePath) + require.NoError(t, f.Close()) + require.NoError(t, err) + assert.True(t, FileExists(filePath)) + }) + + t.Run("file not exist", func(t *testing.T) { + assert.False(t, FileExists(filepath.Join(t.TempDir(), "tmp"))) + }) + + t.Run("dir path", func(t *testing.T) { + assert.False(t, FileExists(t.TempDir())) + }) +} diff --git a/pkg/version/version.go b/pkg/version/version.go index 60ee62bd52f1..17da77a2a1bd 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -1,6 +1,7 @@ package version import ( + "context" "fmt" "path/filepath" @@ -80,7 +81,8 @@ func NewVersionInfo(cacheDir string) VersionInfo { log.Debug("Failed to instantiate policy client", log.Err(err)) } if pc != nil && err == nil { - pbMetaRaw, err := pc.GetMetadata() + ctx := log.WithContextPrefix(context.TODO(), log.PrefixMisconfiguration) + pbMetaRaw, err := pc.GetMetadata(ctx) if err != nil { log.Debug("Failed to get policy metadata", log.Err(err)) diff --git a/rpc/common/service.pb.go b/rpc/common/service.pb.go index c8d829762956..4c7f0af79464 100644 --- a/rpc/common/service.pb.go +++ b/rpc/common/service.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.34.0 -// protoc v5.26.1 +// protoc v3.19.4 // source: rpc/common/service.proto package common @@ -2084,6 +2084,7 @@ type DetectedLicense struct { Name string `protobuf:"bytes,5,opt,name=name,proto3" json:"name,omitempty"` Confidence float32 `protobuf:"fixed32,6,opt,name=confidence,proto3" json:"confidence,omitempty"` Link string `protobuf:"bytes,7,opt,name=link,proto3" json:"link,omitempty"` + Text string `protobuf:"bytes,8,opt,name=text,proto3" json:"text,omitempty"` } func (x *DetectedLicense) Reset() { @@ -2167,6 +2168,13 @@ func (x *DetectedLicense) GetLink() string { return "" } +func (x *DetectedLicense) GetText() string { + if x != nil { + return x.Text + } + return "" +} + type LicenseFile struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2718,7 +2726,7 @@ var file_rpc_common_service_proto_rawDesc = []byte{ 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, 0x85, 0x02, 0x0a, 0x0f, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4c, + 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, @@ -2734,54 +2742,56 @@ var file_rpc_common_service_proto_rawDesc = []byte{ 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, 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, 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, + 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, 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, } var ( diff --git a/rpc/common/service.proto b/rpc/common/service.proto index 91ed89dd534f..d80bf57e3b86 100644 --- a/rpc/common/service.proto +++ b/rpc/common/service.proto @@ -226,6 +226,7 @@ message DetectedLicense { string name = 5; float confidence = 6; string link = 7; + string text = 8; } message LicenseFile {