From ba9feb66bf8f8292e0f9f936b0f01e244859c06e Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Fri, 30 Jan 2026 14:02:58 +0600 Subject: [PATCH] chore: bump trivy-checks to v2 (#9875) Signed-off-by: nikpivkin --- .../configuration/cli/trivy_config.md | 2 +- .../configuration/cli/trivy_filesystem.md | 2 +- .../configuration/cli/trivy_image.md | 2 +- .../configuration/cli/trivy_kubernetes.md | 2 +- .../configuration/cli/trivy_repository.md | 2 +- .../configuration/cli/trivy_rootfs.md | 2 +- .../references/configuration/cli/trivy_vm.md | 2 +- .../references/configuration/config-file.md | 2 +- go.mod | 6 +- go.sum | 12 +- integration/testdata/dockerfile.json.golden | 7 +- .../dockerfile_file_pattern.json.golden | 7 +- integration/testdata/helm.json.golden | 128 +++++------ .../testdata/helm_testchart.json.golden | 48 ++--- .../helm_testchart.overridden.json.golden | 62 +++--- ...exclude-misconfs-remote-module.json.golden | 2 +- .../terraform-opentofu-registry.json.golden | 161 +------------- ...rraform-remote-module-in-child.json.golden | 169 +-------------- .../terraform-remote-module.json.golden | 161 +------------- .../terraform-remote-submodule.json.golden | 16 +- .../terraform-terraform-registry.json.golden | 161 +------------- pkg/commands/operation/operation.go | 16 +- pkg/commands/operation/operation_test.go | 9 +- pkg/compliance/spec/compliance.go | 22 +- .../analyzer/imgconf/dockerfile/dockerfile.go | 6 +- .../imgconf/dockerfile/dockerfile_test.go | 12 +- pkg/iac/adapters/terraform/aws/s3/bucket.go | 1 + pkg/iac/rego/embed.go | 2 +- pkg/iac/scanners/helm/test/scanner_test.go | 52 ++--- .../terraformplan/tfjson/scanner_test.go | 16 +- pkg/misconf/scanner.go | 2 +- pkg/oci/artifact.go | 13 ++ pkg/policy/policy.go | 134 ++++++++++-- pkg/policy/policy_test.go | 204 +++++++++++++++--- 34 files changed, 568 insertions(+), 877 deletions(-) diff --git a/docs/guide/references/configuration/cli/trivy_config.md b/docs/guide/references/configuration/cli/trivy_config.md index 7534e90f9f..738d3df68e 100644 --- a/docs/guide/references/configuration/cli/trivy_config.md +++ b/docs/guide/references/configuration/cli/trivy_config.md @@ -16,7 +16,7 @@ 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 "mirror.gcr.io/aquasec/trivy-checks:1") + --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:2") --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 diff --git a/docs/guide/references/configuration/cli/trivy_filesystem.md b/docs/guide/references/configuration/cli/trivy_filesystem.md index 0d75fbf8a3..579157a73c 100644 --- a/docs/guide/references/configuration/cli/trivy_filesystem.md +++ b/docs/guide/references/configuration/cli/trivy_filesystem.md @@ -26,7 +26,7 @@ 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 "mirror.gcr.io/aquasec/trivy-checks:1") + --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:2") --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 diff --git a/docs/guide/references/configuration/cli/trivy_image.md b/docs/guide/references/configuration/cli/trivy_image.md index f76f4980a5..579a6d9711 100644 --- a/docs/guide/references/configuration/cli/trivy_image.md +++ b/docs/guide/references/configuration/cli/trivy_image.md @@ -40,7 +40,7 @@ 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 "mirror.gcr.io/aquasec/trivy-checks:1") + --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:2") --compliance string compliance report to generate (built-in compliance's: docker-cis-1.6.0) --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded diff --git a/docs/guide/references/configuration/cli/trivy_kubernetes.md b/docs/guide/references/configuration/cli/trivy_kubernetes.md index 51064cd3d1..b200e336c6 100644 --- a/docs/guide/references/configuration/cli/trivy_kubernetes.md +++ b/docs/guide/references/configuration/cli/trivy_kubernetes.md @@ -36,7 +36,7 @@ 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 "mirror.gcr.io/aquasec/trivy-checks:1") + --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:2") --compliance string compliance report to generate Built-in compliance's: - k8s-nsa-1.0 diff --git a/docs/guide/references/configuration/cli/trivy_repository.md b/docs/guide/references/configuration/cli/trivy_repository.md index ebd99012e6..d7a58cf7bf 100644 --- a/docs/guide/references/configuration/cli/trivy_repository.md +++ b/docs/guide/references/configuration/cli/trivy_repository.md @@ -26,7 +26,7 @@ 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 "mirror.gcr.io/aquasec/trivy-checks:1") + --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:2") --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 diff --git a/docs/guide/references/configuration/cli/trivy_rootfs.md b/docs/guide/references/configuration/cli/trivy_rootfs.md index 1221d5e134..f560f272b0 100644 --- a/docs/guide/references/configuration/cli/trivy_rootfs.md +++ b/docs/guide/references/configuration/cli/trivy_rootfs.md @@ -29,7 +29,7 @@ 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 "mirror.gcr.io/aquasec/trivy-checks:1") + --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:2") --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 diff --git a/docs/guide/references/configuration/cli/trivy_vm.md b/docs/guide/references/configuration/cli/trivy_vm.md index ab07822a46..828c043f02 100644 --- a/docs/guide/references/configuration/cli/trivy_vm.md +++ b/docs/guide/references/configuration/cli/trivy_vm.md @@ -26,7 +26,7 @@ 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 "mirror.gcr.io/aquasec/trivy-checks:1") + --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:2") --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 diff --git a/docs/guide/references/configuration/config-file.md b/docs/guide/references/configuration/config-file.md index 8dc9b8f9cf..363ab839d9 100644 --- a/docs/guide/references/configuration/config-file.md +++ b/docs/guide/references/configuration/config-file.md @@ -392,7 +392,7 @@ ansible: misconfiguration: # Same as '--checks-bundle-repository' - checks-bundle-repository: "mirror.gcr.io/aquasec/trivy-checks:1" + checks-bundle-repository: "mirror.gcr.io/aquasec/trivy-checks:2" cloudformation: # Same as '--cf-params' diff --git a/go.mod b/go.mod index 7b6ad00673..28ef6a62a7 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/aquasecurity/table v1.11.0 github.com/aquasecurity/testdocker v0.0.0-20250616060700-ba6845ac6d17 github.com/aquasecurity/tml v0.6.1 - github.com/aquasecurity/trivy-checks v1.11.3-0.20250604022615-9a7efa7c9169 + github.com/aquasecurity/trivy-checks v1.12.2-0.20251219190323-79d27547baf5 github.com/aquasecurity/trivy-db v0.0.0-20251222105351-a833f47f8f0d github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 github.com/aquasecurity/trivy-kubernetes v0.9.1 @@ -378,7 +378,7 @@ require ( github.com/oklog/ulid/v2 v2.1.1 // indirect github.com/opencontainers/runtime-spec v1.2.1 // indirect github.com/opencontainers/selinux v1.13.0 // indirect - github.com/owenrumney/squealer v1.2.11 // indirect + github.com/owenrumney/squealer v1.2.12 // indirect github.com/pandatix/go-cvss v0.6.2 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect @@ -502,7 +502,7 @@ require ( modernc.org/libc v1.66.10 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect - mvdan.cc/sh/v3 v3.11.0 // indirect + mvdan.cc/sh/v3 v3.12.0 // indirect oras.land/oras-go/v2 v2.6.0 // indirect pluginrpc.com/pluginrpc v0.5.0 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect diff --git a/go.sum b/go.sum index 66b7a608f7..3c390ea7bf 100644 --- a/go.sum +++ b/go.sum @@ -173,8 +173,8 @@ github.com/aquasecurity/testdocker v0.0.0-20250616060700-ba6845ac6d17 h1:/xWTD1Y github.com/aquasecurity/testdocker v0.0.0-20250616060700-ba6845ac6d17/go.mod h1:6kYuX29QyBWHJejvbKkA4yzz8EUX/Fn+GmQ09JAZ5lY= github.com/aquasecurity/tml v0.6.1 h1:y2ZlGSfrhnn7t4ZJ/0rotuH+v5Jgv6BDDO5jB6A9gwo= github.com/aquasecurity/tml v0.6.1/go.mod h1:OnYMWY5lvI9ejU7yH9LCberWaaTBW7hBFsITiIMY2yY= -github.com/aquasecurity/trivy-checks v1.11.3-0.20250604022615-9a7efa7c9169 h1:TckzIxUX7lZaU9f2lNxCN0noYYP8fzmSQf6a4JdV83w= -github.com/aquasecurity/trivy-checks v1.11.3-0.20250604022615-9a7efa7c9169/go.mod h1:nT69xgRcBD4NlHwTBpWMYirpK5/Zpl8M+XDOgmjMn2k= +github.com/aquasecurity/trivy-checks v1.12.2-0.20251219190323-79d27547baf5 h1:8HnXyjgCiJwVX1mTKeqdyizd7ZBmXMPL+BMQ5UZd0Nk= +github.com/aquasecurity/trivy-checks v1.12.2-0.20251219190323-79d27547baf5/go.mod h1:hBSA3ziBFwGENK6/PYNIKm6N24SFg0wsv1VXeqPG/3M= github.com/aquasecurity/trivy-db v0.0.0-20251222105351-a833f47f8f0d h1:mwCxwhDRnW5UkSQdZfekTCjaLyWp1rqfIa6KKRdMDAo= github.com/aquasecurity/trivy-db v0.0.0-20251222105351-a833f47f8f0d/go.mod h1:B0cbg/BEHbJg2RcS7PLdlbGCzz2TkChcZAiI4oSs0VI= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 h1:JVgBIuIYbwG+ekC5lUHUpGJboPYiCcxiz06RCtz8neI= @@ -936,8 +936,8 @@ github.com/openvex/go-vex v0.2.7/go.mod h1:ZyQC3NXl9jjS53JOpBG3LAUXySkW8IlJ/GIhs 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.11 h1:vMudrj70VeOzY+t7Phz9Yo0wAgm4kXes9DcTLBVDqGY= -github.com/owenrumney/squealer v1.2.11/go.mod h1:8KOuitfOfmS/OtzgxQbxnnrbngAGopfgKB/BiGGpqGA= +github.com/owenrumney/squealer v1.2.12 h1:6VQxQ323q7q0eKNP5p7MX4nbTW1z8wK44YvyHLwDcx0= +github.com/owenrumney/squealer v1.2.12/go.mod h1:yBdddxW+31mPHXgjOAYiqCtK1/f1S3o5Jq6S6vZGtxc= github.com/package-url/packageurl-go v0.1.3 h1:4juMED3hHiz0set3Vq3KeQ75KD1avthoXLtmE3I0PLs= github.com/package-url/packageurl-go v0.1.3/go.mod h1:nKAWB8E6uk1MHqiS/lQb9pYBGH2+mdJ2PJc2s50dQY0= github.com/pandatix/go-cvss v0.6.2 h1:TFiHlzUkT67s6UkelHmK6s1INKVUG7nlKYiWWDTITGI= @@ -1545,8 +1545,8 @@ modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -mvdan.cc/sh/v3 v3.11.0 h1:q5h+XMDRfUGUedCqFFsjoFjrhwf2Mvtt1rkMvVz0blw= -mvdan.cc/sh/v3 v3.11.0/go.mod h1:LRM+1NjoYCzuq/WZ6y44x14YNAI0NK7FLPeQSaFagGg= +mvdan.cc/sh/v3 v3.12.0 h1:ejKUR7ONP5bb+UGHGEG/k9V5+pRVIyD+LsZz7o8KHrI= +mvdan.cc/sh/v3 v3.12.0/go.mod h1:Se6Cj17eYSn+sNooLZiEUnNNmNxg0imoYlTu4CyaGyg= oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= diff --git a/integration/testdata/dockerfile.json.golden b/integration/testdata/dockerfile.json.golden index a6e4c7dbba..6cfd9d95a4 100644 --- a/integration/testdata/dockerfile.json.golden +++ b/integration/testdata/dockerfile.json.golden @@ -19,8 +19,7 @@ "Misconfigurations": [ { "Type": "Dockerfile Security Check", - "ID": "DS002", - "AVDID": "AVD-DS-0002", + "ID": "DS-0002", "Title": "Image user should not be 'root'", "Description": "Running containers with 'root' user can lead to a container escape situation. It is a best practice to run containers as non-root users, which can be done by adding a 'USER' statement to the Dockerfile.", "Message": "Specify at least 1 USER command in Dockerfile with non-root user as argument", @@ -28,10 +27,10 @@ "Query": "data.builtin.dockerfile.DS002.deny", "Resolution": "Add 'USER \u003cnon root user name\u003e' line to the Dockerfile", "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ds002", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ds-0002", "References": [ "https://docs.docker.com/develop/develop-images/dockerfile_best-practices/", - "https://avd.aquasec.com/misconfig/ds002" + "https://avd.aquasec.com/misconfig/ds-0002" ], "Status": "FAIL", "CauseMetadata": { diff --git a/integration/testdata/dockerfile_file_pattern.json.golden b/integration/testdata/dockerfile_file_pattern.json.golden index ad6804e24c..d6d47e605b 100644 --- a/integration/testdata/dockerfile_file_pattern.json.golden +++ b/integration/testdata/dockerfile_file_pattern.json.golden @@ -19,8 +19,7 @@ "Misconfigurations": [ { "Type": "Dockerfile Security Check", - "ID": "DS002", - "AVDID": "AVD-DS-0002", + "ID": "DS-0002", "Title": "Image user should not be 'root'", "Description": "Running containers with 'root' user can lead to a container escape situation. It is a best practice to run containers as non-root users, which can be done by adding a 'USER' statement to the Dockerfile.", "Message": "Specify at least 1 USER command in Dockerfile with non-root user as argument", @@ -28,10 +27,10 @@ "Query": "data.builtin.dockerfile.DS002.deny", "Resolution": "Add 'USER \u003cnon root user name\u003e' line to the Dockerfile", "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ds002", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ds-0002", "References": [ "https://docs.docker.com/develop/develop-images/dockerfile_best-practices/", - "https://avd.aquasec.com/misconfig/ds002" + "https://avd.aquasec.com/misconfig/ds-0002" ], "Status": "FAIL", "CauseMetadata": { diff --git a/integration/testdata/helm.json.golden b/integration/testdata/helm.json.golden index 112213f8ea..a225b9aae9 100644 --- a/integration/testdata/helm.json.golden +++ b/integration/testdata/helm.json.golden @@ -13,14 +13,13 @@ "Class": "config", "Type": "helm", "MisconfSummary": { - "Successes": 78, + "Successes": 81, "Failures": 18 }, "Misconfigurations": [ { "Type": "Helm Security Check", - "ID": "KSV001", - "AVDID": "AVD-KSV-0001", + "ID": "KSV-0001", "Title": "Can elevate its own privileges", "Description": "A program inside the container can elevate its own privileges and run as root, which might give the program control over the container and node.", "Message": "Container 'nginx' of Deployment 'nginx-deployment' should set 'securityContext.allowPrivilegeEscalation' to false", @@ -28,10 +27,10 @@ "Query": "data.builtin.kubernetes.KSV001.deny", "Resolution": "Set 'set containers[].securityContext.allowPrivilegeEscalation' to 'false'.", "Severity": "MEDIUM", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv001", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0001", "References": [ "https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted", - "https://avd.aquasec.com/misconfig/ksv001" + "https://avd.aquasec.com/misconfig/ksv-0001" ], "Status": "FAIL", "CauseMetadata": { @@ -87,8 +86,7 @@ }, { "Type": "Helm Security Check", - "ID": "KSV003", - "AVDID": "AVD-KSV-0003", + "ID": "KSV-0003", "Title": "Default capabilities: some containers do not drop all", "Description": "The container should drop all default capabilities and add only those that are needed for its execution.", "Message": "Container 'nginx' of Deployment 'nginx-deployment' should add 'ALL' to 'securityContext.capabilities.drop'", @@ -96,10 +94,10 @@ "Query": "data.builtin.kubernetes.KSV003.deny", "Resolution": "Add 'ALL' to containers[].securityContext.capabilities.drop.", "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv003", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0003", "References": [ "https://kubesec.io/basics/containers-securitycontext-capabilities-drop-index-all/", - "https://avd.aquasec.com/misconfig/ksv003" + "https://avd.aquasec.com/misconfig/ksv-0003" ], "Status": "FAIL", "CauseMetadata": { @@ -155,8 +153,7 @@ }, { "Type": "Helm Security Check", - "ID": "KSV004", - "AVDID": "AVD-KSV-0004", + "ID": "KSV-0004", "Title": "Default capabilities: some containers do not drop any", "Description": "Security best practices require containers to run with minimal required capabilities.", "Message": "Container 'nginx' of 'deployment' 'nginx-deployment' in 'default' namespace should set securityContext.capabilities.drop", @@ -164,10 +161,10 @@ "Query": "data.builtin.kubernetes.KSV004.deny", "Resolution": "Specify at least one unneeded capability in 'containers[].securityContext.capabilities.drop'", "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv004", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0004", "References": [ "https://kubesec.io/basics/containers-securitycontext-capabilities-drop-index-all/", - "https://avd.aquasec.com/misconfig/ksv004" + "https://avd.aquasec.com/misconfig/ksv-0004" ], "Status": "FAIL", "CauseMetadata": { @@ -223,8 +220,7 @@ }, { "Type": "Helm Security Check", - "ID": "KSV011", - "AVDID": "AVD-KSV-0011", + "ID": "KSV-0011", "Title": "CPU not limited", "Description": "Enforcing CPU limits prevents DoS via resource exhaustion.", "Message": "Container 'nginx' of Deployment 'nginx-deployment' should set 'resources.limits.cpu'", @@ -232,10 +228,10 @@ "Query": "data.builtin.kubernetes.KSV011.deny", "Resolution": "Set a limit value under 'containers[].resources.limits.cpu'.", "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv011", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0011", "References": [ "https://cloud.google.com/blog/products/containers-kubernetes/kubernetes-best-practices-resource-requests-and-limits", - "https://avd.aquasec.com/misconfig/ksv011" + "https://avd.aquasec.com/misconfig/ksv-0011" ], "Status": "FAIL", "CauseMetadata": { @@ -291,8 +287,7 @@ }, { "Type": "Helm Security Check", - "ID": "KSV012", - "AVDID": "AVD-KSV-0012", + "ID": "KSV-0012", "Title": "Runs as root user", "Description": "Force the running image to run as a non-root user to ensure least privileges.", "Message": "Container 'nginx' of Deployment 'nginx-deployment' should set 'securityContext.runAsNonRoot' to true", @@ -300,10 +295,10 @@ "Query": "data.builtin.kubernetes.KSV012.deny", "Resolution": "Set 'containers[].securityContext.runAsNonRoot' to true.", "Severity": "MEDIUM", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv012", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0012", "References": [ "https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted", - "https://avd.aquasec.com/misconfig/ksv012" + "https://avd.aquasec.com/misconfig/ksv-0012" ], "Status": "FAIL", "CauseMetadata": { @@ -359,8 +354,7 @@ }, { "Type": "Helm Security Check", - "ID": "KSV014", - "AVDID": "AVD-KSV-0014", + "ID": "KSV-0014", "Title": "Root file system is not read-only", "Description": "An immutable root file system prevents applications from writing to their local disk. This can limit intrusions, as attackers will not be able to tamper with the file system or write foreign executables to disk.", "Message": "Container 'nginx' of Deployment 'nginx-deployment' should set 'securityContext.readOnlyRootFilesystem' to true", @@ -368,10 +362,10 @@ "Query": "data.builtin.kubernetes.KSV014.deny", "Resolution": "Change 'containers[].securityContext.readOnlyRootFilesystem' to 'true'.", "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv014", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0014", "References": [ "https://kubesec.io/basics/containers-securitycontext-readonlyrootfilesystem-true/", - "https://avd.aquasec.com/misconfig/ksv014" + "https://avd.aquasec.com/misconfig/ksv-0014" ], "Status": "FAIL", "CauseMetadata": { @@ -427,8 +421,7 @@ }, { "Type": "Helm Security Check", - "ID": "KSV015", - "AVDID": "AVD-KSV-0015", + "ID": "KSV-0015", "Title": "CPU requests not specified", "Description": "When containers have resource requests specified, the scheduler can make better decisions about which nodes to place pods on, and how to deal with resource contention.", "Message": "Container 'nginx' of Deployment 'nginx-deployment' should set 'resources.requests.cpu'", @@ -436,10 +429,10 @@ "Query": "data.builtin.kubernetes.KSV015.deny", "Resolution": "Set 'containers[].resources.requests.cpu'.", "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv015", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0015", "References": [ "https://cloud.google.com/blog/products/containers-kubernetes/kubernetes-best-practices-resource-requests-and-limits", - "https://avd.aquasec.com/misconfig/ksv015" + "https://avd.aquasec.com/misconfig/ksv-0015" ], "Status": "FAIL", "CauseMetadata": { @@ -495,8 +488,7 @@ }, { "Type": "Helm Security Check", - "ID": "KSV016", - "AVDID": "AVD-KSV-0016", + "ID": "KSV-0016", "Title": "Memory requests not specified", "Description": "When containers have memory requests specified, the scheduler can make better decisions about which nodes to place pods on, and how to deal with resource contention.", "Message": "Container 'nginx' of Deployment 'nginx-deployment' should set 'resources.requests.memory'", @@ -504,10 +496,10 @@ "Query": "data.builtin.kubernetes.KSV016.deny", "Resolution": "Set 'containers[].resources.requests.memory'.", "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv016", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0016", "References": [ "https://kubesec.io/basics/containers-resources-limits-memory/", - "https://avd.aquasec.com/misconfig/ksv016" + "https://avd.aquasec.com/misconfig/ksv-0016" ], "Status": "FAIL", "CauseMetadata": { @@ -563,8 +555,7 @@ }, { "Type": "Helm Security Check", - "ID": "KSV018", - "AVDID": "AVD-KSV-0018", + "ID": "KSV-0018", "Title": "Memory not limited", "Description": "Enforcing memory limits prevents DoS via resource exhaustion.", "Message": "Container 'nginx' of Deployment 'nginx-deployment' should set 'resources.limits.memory'", @@ -572,10 +563,10 @@ "Query": "data.builtin.kubernetes.KSV018.deny", "Resolution": "Set a limit value under 'containers[].resources.limits.memory'.", "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv018", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0018", "References": [ "https://kubesec.io/basics/containers-resources-limits-memory/", - "https://avd.aquasec.com/misconfig/ksv018" + "https://avd.aquasec.com/misconfig/ksv-0018" ], "Status": "FAIL", "CauseMetadata": { @@ -631,8 +622,7 @@ }, { "Type": "Helm Security Check", - "ID": "KSV020", - "AVDID": "AVD-KSV-0020", + "ID": "KSV-0020", "Title": "Runs with UID \u003c= 10000", "Description": "Force the container to run with user ID \u003e 10000 to avoid conflicts with the host’s user table.", "Message": "Container 'nginx' of Deployment 'nginx-deployment' should set 'securityContext.runAsUser' \u003e 10000", @@ -640,10 +630,10 @@ "Query": "data.builtin.kubernetes.KSV020.deny", "Resolution": "Set 'containers[].securityContext.runAsUser' to an integer \u003e 10000.", "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv020", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0020", "References": [ "https://kubesec.io/basics/containers-securitycontext-runasuser/", - "https://avd.aquasec.com/misconfig/ksv020" + "https://avd.aquasec.com/misconfig/ksv-0020" ], "Status": "FAIL", "CauseMetadata": { @@ -699,8 +689,7 @@ }, { "Type": "Helm Security Check", - "ID": "KSV021", - "AVDID": "AVD-KSV-0021", + "ID": "KSV-0021", "Title": "Runs with GID \u003c= 10000", "Description": "Force the container to run with group ID \u003e 10000 to avoid conflicts with the host’s user table.", "Message": "Container 'nginx' of Deployment 'nginx-deployment' should set 'securityContext.runAsGroup' \u003e 10000", @@ -708,10 +697,10 @@ "Query": "data.builtin.kubernetes.KSV021.deny", "Resolution": "Set 'containers[].securityContext.runAsGroup' to an integer \u003e 10000.", "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv021", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0021", "References": [ "https://kubesec.io/basics/containers-securitycontext-runasuser/", - "https://avd.aquasec.com/misconfig/ksv021" + "https://avd.aquasec.com/misconfig/ksv-0021" ], "Status": "FAIL", "CauseMetadata": { @@ -767,8 +756,7 @@ }, { "Type": "Helm Security Check", - "ID": "KSV030", - "AVDID": "AVD-KSV-0030", + "ID": "KSV-0030", "Title": "Runtime/Default Seccomp profile not set", "Description": "According to pod security standard 'Seccomp', the RuntimeDefault seccomp profile must be required, or allow specific additional profiles.", "Message": "Either Pod or Container should set 'securityContext.seccompProfile.type' to 'RuntimeDefault'", @@ -776,10 +764,10 @@ "Query": "data.builtin.kubernetes.KSV030.deny", "Resolution": "Set 'spec.securityContext.seccompProfile.type', 'spec.containers[*].securityContext.seccompProfile' and 'spec.initContainers[*].securityContext.seccompProfile' to 'RuntimeDefault' or undefined.", "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv030", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0030", "References": [ "https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted", - "https://avd.aquasec.com/misconfig/ksv030" + "https://avd.aquasec.com/misconfig/ksv-0030" ], "Status": "FAIL", "CauseMetadata": { @@ -835,8 +823,7 @@ }, { "Type": "Helm Security Check", - "ID": "KSV104", - "AVDID": "AVD-KSV-0104", + "ID": "KSV-0104", "Title": "Seccomp policies disabled", "Description": "A program inside the container can bypass Seccomp protection policies.", "Message": "container \"nginx\" of deployment \"nginx-deployment\" in \"default\" namespace should specify a seccomp profile", @@ -844,10 +831,10 @@ "Query": "data.builtin.kubernetes.KSV104.deny", "Resolution": "Specify seccomp either by annotation or by seccomp profile type having allowed values as per pod security standards", "Severity": "MEDIUM", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv104", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0104", "References": [ "https://kubernetes.io/docs/concepts/security/pod-security-standards/#baseline", - "https://avd.aquasec.com/misconfig/ksv104" + "https://avd.aquasec.com/misconfig/ksv-0104" ], "Status": "FAIL", "CauseMetadata": { @@ -903,8 +890,7 @@ }, { "Type": "Helm Security Check", - "ID": "KSV106", - "AVDID": "AVD-KSV-0106", + "ID": "KSV-0106", "Title": "Container capabilities must only include NET_BIND_SERVICE", "Description": "Containers must drop ALL capabilities, and are only permitted to add back the NET_BIND_SERVICE capability.", "Message": "container should drop all", @@ -912,10 +898,10 @@ "Query": "data.builtin.kubernetes.KSV106.deny", "Resolution": "Set 'spec.containers[*].securityContext.capabilities.drop' to 'ALL' and only add 'NET_BIND_SERVICE' to 'spec.containers[*].securityContext.capabilities.add'.", "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv106", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0106", "References": [ "https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted", - "https://avd.aquasec.com/misconfig/ksv106" + "https://avd.aquasec.com/misconfig/ksv-0106" ], "Status": "FAIL", "CauseMetadata": { @@ -971,8 +957,7 @@ }, { "Type": "Helm Security Check", - "ID": "KSV110", - "AVDID": "AVD-KSV-0110", + "ID": "KSV-0110", "Title": "Workloads in the default namespace", "Description": "Checks whether a workload is running in the default namespace.", "Message": "deployment nginx-deployment in default namespace should set metadata.namespace to a non-default namespace", @@ -980,10 +965,10 @@ "Query": "data.builtin.kubernetes.KSV110.deny", "Resolution": "Set 'metadata.namespace' to a non-default namespace.", "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv110", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0110", "References": [ "https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/", - "https://avd.aquasec.com/misconfig/ksv110" + "https://avd.aquasec.com/misconfig/ksv-0110" ], "Status": "FAIL", "CauseMetadata": { @@ -1039,8 +1024,7 @@ }, { "Type": "Helm Security Check", - "ID": "KSV117", - "AVDID": "AVD-KSV-0117", + "ID": "KSV-0117", "Title": "Prevent binding to privileged ports", "Description": "The ports which are lower than 1024 receive and transmit various sensitive and privileged data. Allowing containers to use them can bring serious implications.", "Message": "deployment nginx-deployment in default namespace should not set spec.template.spec.containers.ports.containerPort to less than 1024", @@ -1048,11 +1032,11 @@ "Query": "data.builtin.kubernetes.KSV117.deny", "Resolution": "Do not map the container ports to privileged host ports when starting a container.", "Severity": "MEDIUM", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv117", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0117", "References": [ "https://kubernetes.io/docs/concepts/security/pod-security-standards/", "https://www.stigviewer.com/stig/kubernetes/2022-12-02/finding/V-242414", - "https://avd.aquasec.com/misconfig/ksv117" + "https://avd.aquasec.com/misconfig/ksv-0117" ], "Status": "FAIL", "CauseMetadata": { @@ -1062,8 +1046,7 @@ }, { "Type": "Helm Security Check", - "ID": "KSV118", - "AVDID": "AVD-KSV-0118", + "ID": "KSV-0118", "Title": "Default security context configured", "Description": "Security context controls the allocation of security parameters for the pod/container/volume, ensuring the appropriate level of protection. Relying on default security context may expose vulnerabilities to potential attacks that rely on privileged access.", "Message": "container nginx-deployment in default namespace is using the default security context", @@ -1071,10 +1054,10 @@ "Query": "data.builtin.kubernetes.KSV118.deny", "Resolution": "To enhance security, it is strongly recommended not to rely on the default security context. Instead, it is advisable to explicitly define the required security parameters (such as runAsNonRoot, capabilities, readOnlyRootFilesystem, etc.) within the security context.", "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv118", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0118", "References": [ "https://kubernetes.io/docs/tasks/configure-pod-container/security-context/", - "https://avd.aquasec.com/misconfig/ksv118" + "https://avd.aquasec.com/misconfig/ksv-0118" ], "Status": "FAIL", "CauseMetadata": { @@ -1130,8 +1113,7 @@ }, { "Type": "Helm Security Check", - "ID": "KSV118", - "AVDID": "AVD-KSV-0118", + "ID": "KSV-0118", "Title": "Default security context configured", "Description": "Security context controls the allocation of security parameters for the pod/container/volume, ensuring the appropriate level of protection. Relying on default security context may expose vulnerabilities to potential attacks that rely on privileged access.", "Message": "deployment nginx-deployment in default namespace is using the default security context, which allows root privileges", @@ -1139,10 +1121,10 @@ "Query": "data.builtin.kubernetes.KSV118.deny", "Resolution": "To enhance security, it is strongly recommended not to rely on the default security context. Instead, it is advisable to explicitly define the required security parameters (such as runAsNonRoot, capabilities, readOnlyRootFilesystem, etc.) within the security context.", "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv118", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0118", "References": [ "https://kubernetes.io/docs/tasks/configure-pod-container/security-context/", - "https://avd.aquasec.com/misconfig/ksv118" + "https://avd.aquasec.com/misconfig/ksv-0118" ], "Status": "FAIL", "CauseMetadata": { diff --git a/integration/testdata/helm_testchart.json.golden b/integration/testdata/helm_testchart.json.golden index 40f8386439..dd138e51d3 100644 --- a/integration/testdata/helm_testchart.json.golden +++ b/integration/testdata/helm_testchart.json.golden @@ -13,14 +13,13 @@ "Class": "config", "Type": "helm", "MisconfSummary": { - "Successes": 89, + "Successes": 92, "Failures": 6 }, "Misconfigurations": [ { "Type": "Helm Security Check", - "ID": "KSV001", - "AVDID": "AVD-KSV-0001", + "ID": "KSV-0001", "Title": "Can elevate its own privileges", "Description": "A program inside the container can elevate its own privileges and run as root, which might give the program control over the container and node.", "Message": "Container 'testchart' of Deployment 'testchart' should set 'securityContext.allowPrivilegeEscalation' to false", @@ -28,10 +27,10 @@ "Query": "data.builtin.kubernetes.KSV001.deny", "Resolution": "Set 'set containers[].securityContext.allowPrivilegeEscalation' to 'false'.", "Severity": "MEDIUM", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv001", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0001", "References": [ "https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted", - "https://avd.aquasec.com/misconfig/ksv001" + "https://avd.aquasec.com/misconfig/ksv-0001" ], "Status": "FAIL", "CauseMetadata": { @@ -146,8 +145,7 @@ }, { "Type": "Helm Security Check", - "ID": "KSV030", - "AVDID": "AVD-KSV-0030", + "ID": "KSV-0030", "Title": "Runtime/Default Seccomp profile not set", "Description": "According to pod security standard 'Seccomp', the RuntimeDefault seccomp profile must be required, or allow specific additional profiles.", "Message": "Either Pod or Container should set 'securityContext.seccompProfile.type' to 'RuntimeDefault'", @@ -155,10 +153,10 @@ "Query": "data.builtin.kubernetes.KSV030.deny", "Resolution": "Set 'spec.securityContext.seccompProfile.type', 'spec.containers[*].securityContext.seccompProfile' and 'spec.initContainers[*].securityContext.seccompProfile' to 'RuntimeDefault' or undefined.", "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv030", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0030", "References": [ "https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted", - "https://avd.aquasec.com/misconfig/ksv030" + "https://avd.aquasec.com/misconfig/ksv-0030" ], "Status": "FAIL", "CauseMetadata": { @@ -273,8 +271,7 @@ }, { "Type": "Helm Security Check", - "ID": "KSV104", - "AVDID": "AVD-KSV-0104", + "ID": "KSV-0104", "Title": "Seccomp policies disabled", "Description": "A program inside the container can bypass Seccomp protection policies.", "Message": "container \"testchart\" of deployment \"testchart\" in \"default\" namespace should specify a seccomp profile", @@ -282,10 +279,10 @@ "Query": "data.builtin.kubernetes.KSV104.deny", "Resolution": "Specify seccomp either by annotation or by seccomp profile type having allowed values as per pod security standards", "Severity": "MEDIUM", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv104", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0104", "References": [ "https://kubernetes.io/docs/concepts/security/pod-security-standards/#baseline", - "https://avd.aquasec.com/misconfig/ksv104" + "https://avd.aquasec.com/misconfig/ksv-0104" ], "Status": "FAIL", "CauseMetadata": { @@ -400,8 +397,7 @@ }, { "Type": "Helm Security Check", - "ID": "KSV110", - "AVDID": "AVD-KSV-0110", + "ID": "KSV-0110", "Title": "Workloads in the default namespace", "Description": "Checks whether a workload is running in the default namespace.", "Message": "deployment testchart in default namespace should set metadata.namespace to a non-default namespace", @@ -409,10 +405,10 @@ "Query": "data.builtin.kubernetes.KSV110.deny", "Resolution": "Set 'metadata.namespace' to a non-default namespace.", "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv110", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0110", "References": [ "https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/", - "https://avd.aquasec.com/misconfig/ksv110" + "https://avd.aquasec.com/misconfig/ksv-0110" ], "Status": "FAIL", "CauseMetadata": { @@ -508,8 +504,7 @@ }, { "Type": "Helm Security Check", - "ID": "KSV117", - "AVDID": "AVD-KSV-0117", + "ID": "KSV-0117", "Title": "Prevent binding to privileged ports", "Description": "The ports which are lower than 1024 receive and transmit various sensitive and privileged data. Allowing containers to use them can bring serious implications.", "Message": "deployment testchart in default namespace should not set spec.template.spec.containers.ports.containerPort to less than 1024", @@ -517,11 +512,11 @@ "Query": "data.builtin.kubernetes.KSV117.deny", "Resolution": "Do not map the container ports to privileged host ports when starting a container.", "Severity": "MEDIUM", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv117", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0117", "References": [ "https://kubernetes.io/docs/concepts/security/pod-security-standards/", "https://www.stigviewer.com/stig/kubernetes/2022-12-02/finding/V-242414", - "https://avd.aquasec.com/misconfig/ksv117" + "https://avd.aquasec.com/misconfig/ksv-0117" ], "Status": "FAIL", "CauseMetadata": { @@ -531,8 +526,7 @@ }, { "Type": "Helm Security Check", - "ID": "KSV118", - "AVDID": "AVD-KSV-0118", + "ID": "KSV-0118", "Title": "Default security context configured", "Description": "Security context controls the allocation of security parameters for the pod/container/volume, ensuring the appropriate level of protection. Relying on default security context may expose vulnerabilities to potential attacks that rely on privileged access.", "Message": "deployment testchart in default namespace is using the default security context, which allows root privileges", @@ -540,10 +534,10 @@ "Query": "data.builtin.kubernetes.KSV118.deny", "Resolution": "To enhance security, it is strongly recommended not to rely on the default security context. Instead, it is advisable to explicitly define the required security parameters (such as runAsNonRoot, capabilities, readOnlyRootFilesystem, etc.) within the security context.", "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv118", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0118", "References": [ "https://kubernetes.io/docs/tasks/configure-pod-container/security-context/", - "https://avd.aquasec.com/misconfig/ksv118" + "https://avd.aquasec.com/misconfig/ksv-0118" ], "Status": "FAIL", "CauseMetadata": { @@ -663,7 +657,7 @@ "Class": "config", "Type": "helm", "MisconfSummary": { - "Successes": 59, + "Successes": 62, "Failures": 0 } }, @@ -672,7 +666,7 @@ "Class": "config", "Type": "helm", "MisconfSummary": { - "Successes": 58, + "Successes": 61, "Failures": 0 } } diff --git a/integration/testdata/helm_testchart.overridden.json.golden b/integration/testdata/helm_testchart.overridden.json.golden index 2c36951950..b2d46a8b74 100644 --- a/integration/testdata/helm_testchart.overridden.json.golden +++ b/integration/testdata/helm_testchart.overridden.json.golden @@ -13,14 +13,13 @@ "Class": "config", "Type": "helm", "MisconfSummary": { - "Successes": 87, + "Successes": 90, "Failures": 8 }, "Misconfigurations": [ { "Type": "Helm Security Check", - "ID": "KSV001", - "AVDID": "AVD-KSV-0001", + "ID": "KSV-0001", "Title": "Can elevate its own privileges", "Description": "A program inside the container can elevate its own privileges and run as root, which might give the program control over the container and node.", "Message": "Container 'testchart' of Deployment 'testchart' should set 'securityContext.allowPrivilegeEscalation' to false", @@ -28,10 +27,10 @@ "Query": "data.builtin.kubernetes.KSV001.deny", "Resolution": "Set 'set containers[].securityContext.allowPrivilegeEscalation' to 'false'.", "Severity": "MEDIUM", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv001", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0001", "References": [ "https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted", - "https://avd.aquasec.com/misconfig/ksv001" + "https://avd.aquasec.com/misconfig/ksv-0001" ], "Status": "FAIL", "CauseMetadata": { @@ -146,8 +145,7 @@ }, { "Type": "Helm Security Check", - "ID": "KSV020", - "AVDID": "AVD-KSV-0020", + "ID": "KSV-0020", "Title": "Runs with UID \u003c= 10000", "Description": "Force the container to run with user ID \u003e 10000 to avoid conflicts with the host’s user table.", "Message": "Container 'testchart' of Deployment 'testchart' should set 'securityContext.runAsUser' \u003e 10000", @@ -155,10 +153,10 @@ "Query": "data.builtin.kubernetes.KSV020.deny", "Resolution": "Set 'containers[].securityContext.runAsUser' to an integer \u003e 10000.", "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv020", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0020", "References": [ "https://kubesec.io/basics/containers-securitycontext-runasuser/", - "https://avd.aquasec.com/misconfig/ksv020" + "https://avd.aquasec.com/misconfig/ksv-0020" ], "Status": "FAIL", "CauseMetadata": { @@ -273,8 +271,7 @@ }, { "Type": "Helm Security Check", - "ID": "KSV030", - "AVDID": "AVD-KSV-0030", + "ID": "KSV-0030", "Title": "Runtime/Default Seccomp profile not set", "Description": "According to pod security standard 'Seccomp', the RuntimeDefault seccomp profile must be required, or allow specific additional profiles.", "Message": "Either Pod or Container should set 'securityContext.seccompProfile.type' to 'RuntimeDefault'", @@ -282,10 +279,10 @@ "Query": "data.builtin.kubernetes.KSV030.deny", "Resolution": "Set 'spec.securityContext.seccompProfile.type', 'spec.containers[*].securityContext.seccompProfile' and 'spec.initContainers[*].securityContext.seccompProfile' to 'RuntimeDefault' or undefined.", "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv030", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0030", "References": [ "https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted", - "https://avd.aquasec.com/misconfig/ksv030" + "https://avd.aquasec.com/misconfig/ksv-0030" ], "Status": "FAIL", "CauseMetadata": { @@ -400,8 +397,7 @@ }, { "Type": "Helm Security Check", - "ID": "KSV104", - "AVDID": "AVD-KSV-0104", + "ID": "KSV-0104", "Title": "Seccomp policies disabled", "Description": "A program inside the container can bypass Seccomp protection policies.", "Message": "container \"testchart\" of deployment \"testchart\" in \"default\" namespace should specify a seccomp profile", @@ -409,10 +405,10 @@ "Query": "data.builtin.kubernetes.KSV104.deny", "Resolution": "Specify seccomp either by annotation or by seccomp profile type having allowed values as per pod security standards", "Severity": "MEDIUM", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv104", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0104", "References": [ "https://kubernetes.io/docs/concepts/security/pod-security-standards/#baseline", - "https://avd.aquasec.com/misconfig/ksv104" + "https://avd.aquasec.com/misconfig/ksv-0104" ], "Status": "FAIL", "CauseMetadata": { @@ -527,8 +523,7 @@ }, { "Type": "Helm Security Check", - "ID": "KSV105", - "AVDID": "AVD-KSV-0105", + "ID": "KSV-0105", "Title": "Containers must not set runAsUser to 0", "Description": "Containers should be forbidden from running with a root UID.", "Message": "securityContext.runAsUser should be set to a value greater than 0", @@ -536,10 +531,10 @@ "Query": "data.builtin.kubernetes.KSV105.deny", "Resolution": "Set 'securityContext.runAsUser' to a non-zero integer or leave undefined.", "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv105", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0105", "References": [ "https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted", - "https://avd.aquasec.com/misconfig/ksv105" + "https://avd.aquasec.com/misconfig/ksv-0105" ], "Status": "FAIL", "CauseMetadata": { @@ -635,8 +630,7 @@ }, { "Type": "Helm Security Check", - "ID": "KSV110", - "AVDID": "AVD-KSV-0110", + "ID": "KSV-0110", "Title": "Workloads in the default namespace", "Description": "Checks whether a workload is running in the default namespace.", "Message": "deployment testchart in default namespace should set metadata.namespace to a non-default namespace", @@ -644,10 +638,10 @@ "Query": "data.builtin.kubernetes.KSV110.deny", "Resolution": "Set 'metadata.namespace' to a non-default namespace.", "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv110", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0110", "References": [ "https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/", - "https://avd.aquasec.com/misconfig/ksv110" + "https://avd.aquasec.com/misconfig/ksv-0110" ], "Status": "FAIL", "CauseMetadata": { @@ -743,8 +737,7 @@ }, { "Type": "Helm Security Check", - "ID": "KSV117", - "AVDID": "AVD-KSV-0117", + "ID": "KSV-0117", "Title": "Prevent binding to privileged ports", "Description": "The ports which are lower than 1024 receive and transmit various sensitive and privileged data. Allowing containers to use them can bring serious implications.", "Message": "deployment testchart in default namespace should not set spec.template.spec.containers.ports.containerPort to less than 1024", @@ -752,11 +745,11 @@ "Query": "data.builtin.kubernetes.KSV117.deny", "Resolution": "Do not map the container ports to privileged host ports when starting a container.", "Severity": "MEDIUM", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv117", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0117", "References": [ "https://kubernetes.io/docs/concepts/security/pod-security-standards/", "https://www.stigviewer.com/stig/kubernetes/2022-12-02/finding/V-242414", - "https://avd.aquasec.com/misconfig/ksv117" + "https://avd.aquasec.com/misconfig/ksv-0117" ], "Status": "FAIL", "CauseMetadata": { @@ -766,8 +759,7 @@ }, { "Type": "Helm Security Check", - "ID": "KSV118", - "AVDID": "AVD-KSV-0118", + "ID": "KSV-0118", "Title": "Default security context configured", "Description": "Security context controls the allocation of security parameters for the pod/container/volume, ensuring the appropriate level of protection. Relying on default security context may expose vulnerabilities to potential attacks that rely on privileged access.", "Message": "deployment testchart in default namespace is using the default security context, which allows root privileges", @@ -775,10 +767,10 @@ "Query": "data.builtin.kubernetes.KSV118.deny", "Resolution": "To enhance security, it is strongly recommended not to rely on the default security context. Instead, it is advisable to explicitly define the required security parameters (such as runAsNonRoot, capabilities, readOnlyRootFilesystem, etc.) within the security context.", "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv118", + "PrimaryURL": "https://avd.aquasec.com/misconfig/ksv-0118", "References": [ "https://kubernetes.io/docs/tasks/configure-pod-container/security-context/", - "https://avd.aquasec.com/misconfig/ksv118" + "https://avd.aquasec.com/misconfig/ksv-0118" ], "Status": "FAIL", "CauseMetadata": { @@ -898,7 +890,7 @@ "Class": "config", "Type": "helm", "MisconfSummary": { - "Successes": 59, + "Successes": 62, "Failures": 0 } }, @@ -907,7 +899,7 @@ "Class": "config", "Type": "helm", "MisconfSummary": { - "Successes": 58, + "Successes": 61, "Failures": 0 } } diff --git a/integration/testdata/terraform-exclude-misconfs-remote-module.json.golden b/integration/testdata/terraform-exclude-misconfs-remote-module.json.golden index 55d24c31d5..c90a266c56 100644 --- a/integration/testdata/terraform-exclude-misconfs-remote-module.json.golden +++ b/integration/testdata/terraform-exclude-misconfs-remote-module.json.golden @@ -13,7 +13,7 @@ "Class": "config", "Type": "terraform", "MisconfSummary": { - "Successes": 45, + "Successes": 58, "Failures": 0 } }, diff --git a/integration/testdata/terraform-opentofu-registry.json.golden b/integration/testdata/terraform-opentofu-registry.json.golden index d6b9e3ffbe..527a1732cc 100644 --- a/integration/testdata/terraform-opentofu-registry.json.golden +++ b/integration/testdata/terraform-opentofu-registry.json.golden @@ -13,7 +13,7 @@ "Class": "config", "Type": "terraform", "MisconfSummary": { - "Successes": 45, + "Successes": 58, "Failures": 0 } }, @@ -23,149 +23,12 @@ "Type": "terraform", "MisconfSummary": { "Successes": 0, - "Failures": 4 + "Failures": 3 }, "Misconfigurations": [ { "Type": "Terraform Security Check", - "ID": "AVD-AWS-0088", - "AVDID": "AVD-AWS-0088", - "Title": "Unencrypted S3 bucket.", - "Description": "S3 Buckets should be encrypted to protect the data that is stored within them if access is compromised.\n", - "Message": "Bucket does not have encryption enabled", - "Namespace": "builtin.aws.s3.aws0088", - "Query": "data.builtin.aws.s3.aws0088.deny", - "Resolution": "Configure bucket encryption", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0088", - "References": [ - "https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-encryption.html", - "https://avd.aquasec.com/misconfig/avd-aws-0088" - ], - "Status": "FAIL", - "CauseMetadata": { - "Resource": "module.bucket", - "Provider": "AWS", - "Service": "s3", - "StartLine": 25, - "EndLine": 36, - "Code": { - "Lines": [ - { - "Number": 25, - "Content": "resource \"aws_s3_bucket\" \"this\" {", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": "\u001b[38;5;33mresource\u001b[0m \u001b[38;5;37m\"aws_s3_bucket\"\u001b[0m \u001b[38;5;37m\"this\"\u001b[0m {", - "FirstCause": true, - "LastCause": false - }, - { - "Number": 26, - "Content": " count = local.create_bucket \u0026\u0026 !var.is_directory_bucket ? 1 : 0", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;245mcount\u001b[0m = local.create_bucket \u001b[38;5;245m\u0026\u0026\u001b[0m \u001b[38;5;245m!\u001b[0m\u001b[38;5;33mvar\u001b[0m.is_directory_bucket \u001b[38;5;245m?\u001b[0m \u001b[38;5;37m1\u001b[0m \u001b[38;5;245m:\u001b[0m \u001b[38;5;37m0", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 27, - "Content": "", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": "\u001b[0m", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 28, - "Content": " region = var.region", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;245mregion\u001b[0m = \u001b[38;5;33mvar\u001b[0m.region", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 29, - "Content": "", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "FirstCause": false, - "LastCause": false - }, - { - "Number": 30, - "Content": " bucket = var.bucket", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;245mbucket\u001b[0m = \u001b[38;5;33mvar\u001b[0m.bucket", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 31, - "Content": " bucket_prefix = var.bucket_prefix", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;245mbucket_prefix\u001b[0m = \u001b[38;5;33mvar\u001b[0m.bucket_prefix", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 32, - "Content": "", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "FirstCause": false, - "LastCause": false - }, - { - "Number": 33, - "Content": " force_destroy = var.force_destroy", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;245mforce_destroy\u001b[0m = \u001b[38;5;33mvar\u001b[0m.force_destroy", - "FirstCause": false, - "LastCause": true - }, - { - "Number": 34, - "Content": "", - "IsCause": false, - "Annotation": "", - "Truncated": true, - "FirstCause": false, - "LastCause": false - } - ] - }, - "Occurrences": [ - { - "Resource": "module.bucket", - "Filename": "main.tf", - "Location": { - "StartLine": 1, - "EndLine": 5 - } - } - ] - } - }, - { - "Type": "Terraform Security Check", - "ID": "AVD-AWS-0089", - "AVDID": "AVD-AWS-0089", + "ID": "AWS-0089", "Title": "S3 Bucket Logging", "Description": "Ensures S3 bucket logging is enabled for S3 buckets", "Message": "Bucket has logging disabled", @@ -173,11 +36,11 @@ "Query": "data.builtin.aws.s3.aws0089.deny", "Resolution": "Add a logging block to the resource to enable access logging", "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0089", + "PrimaryURL": "https://avd.aquasec.com/misconfig/aws-0089", "References": [ "https://docs.aws.amazon.com/AmazonS3/latest/userguide/ServerLogs.html", "https://docs.aws.amazon.com/AmazonS3/latest/userguide/enable-server-access-logging.html", - "https://avd.aquasec.com/misconfig/avd-aws-0089" + "https://avd.aquasec.com/misconfig/aws-0089" ], "Status": "FAIL", "CauseMetadata": { @@ -301,8 +164,7 @@ }, { "Type": "Terraform Security Check", - "ID": "AVD-AWS-0090", - "AVDID": "AVD-AWS-0090", + "ID": "AWS-0090", "Title": "S3 Data should be versioned", "Description": "Versioning in Amazon S3 is a means of keeping multiple variants of an object in the same bucket.\n\nYou can use the S3 Versioning feature to preserve, retrieve, and restore every version of every object stored in your buckets.\n\nWith versioning you can recover more easily from both unintended user actions and application failures.\n\nWhen you enable versioning, also keep in mind the potential costs of storing noncurrent versions of objects. To help manage those costs, consider setting up an S3 Lifecycle configuration.\n", "Message": "Bucket does not have versioning enabled", @@ -310,11 +172,11 @@ "Query": "data.builtin.aws.s3.aws0090.deny", "Resolution": "Enable versioning to protect against accidental/malicious removal or modification", "Severity": "MEDIUM", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0090", + "PrimaryURL": "https://avd.aquasec.com/misconfig/aws-0090", "References": [ "https://docs.aws.amazon.com/AmazonS3/latest/userguide/Versioning.html", "https://aws.amazon.com/blogs/storage/reduce-storage-costs-with-fewer-noncurrent-versions-using-amazon-s3-lifecycle/", - "https://avd.aquasec.com/misconfig/avd-aws-0090" + "https://avd.aquasec.com/misconfig/aws-0090" ], "Status": "FAIL", "CauseMetadata": { @@ -438,8 +300,7 @@ }, { "Type": "Terraform Security Check", - "ID": "AVD-AWS-0132", - "AVDID": "AVD-AWS-0132", + "ID": "AWS-0132", "Title": "S3 encryption should use Customer Managed Keys", "Description": "Encryption using AWS keys provides protection for your S3 buckets. To gain greater control over encryption, such as key rotation, access policies, and auditability, use customer managed keys (CMKs) with SSE-KMS.\nNote that SSE-KMS is not supported for S3 server access logging destination buckets; in such cases, use SSE-S3 instead.\n", "Message": "Bucket does not encrypt data with a customer managed key.", @@ -447,10 +308,10 @@ "Query": "data.builtin.aws.s3.aws0132.deny", "Resolution": "Use SSE-KMS with a customer managed key (CMK)", "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0132", + "PrimaryURL": "https://avd.aquasec.com/misconfig/aws-0132", "References": [ "https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-encryption.html", - "https://avd.aquasec.com/misconfig/avd-aws-0132" + "https://avd.aquasec.com/misconfig/aws-0132" ], "Status": "FAIL", "CauseMetadata": { diff --git a/integration/testdata/terraform-remote-module-in-child.json.golden b/integration/testdata/terraform-remote-module-in-child.json.golden index da98a11f3a..273178a088 100644 --- a/integration/testdata/terraform-remote-module-in-child.json.golden +++ b/integration/testdata/terraform-remote-module-in-child.json.golden @@ -13,7 +13,7 @@ "Class": "config", "Type": "terraform", "MisconfSummary": { - "Successes": 45, + "Successes": 58, "Failures": 0 } }, @@ -23,157 +23,12 @@ "Type": "terraform", "MisconfSummary": { "Successes": 0, - "Failures": 4 + "Failures": 3 }, "Misconfigurations": [ { "Type": "Terraform Security Check", - "ID": "AVD-AWS-0088", - "AVDID": "AVD-AWS-0088", - "Title": "Unencrypted S3 bucket.", - "Description": "S3 Buckets should be encrypted to protect the data that is stored within them if access is compromised.\n", - "Message": "Bucket does not have encryption enabled", - "Namespace": "builtin.aws.s3.aws0088", - "Query": "data.builtin.aws.s3.aws0088.deny", - "Resolution": "Configure bucket encryption", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0088", - "References": [ - "https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-encryption.html", - "https://avd.aquasec.com/misconfig/avd-aws-0088" - ], - "Status": "FAIL", - "CauseMetadata": { - "Resource": "module.this", - "Provider": "AWS", - "Service": "s3", - "StartLine": 25, - "EndLine": 36, - "Code": { - "Lines": [ - { - "Number": 25, - "Content": "resource \"aws_s3_bucket\" \"this\" {", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": "\u001b[38;5;33mresource\u001b[0m \u001b[38;5;37m\"aws_s3_bucket\"\u001b[0m \u001b[38;5;37m\"this\"\u001b[0m {", - "FirstCause": true, - "LastCause": false - }, - { - "Number": 26, - "Content": " count = local.create_bucket \u0026\u0026 !var.is_directory_bucket ? 1 : 0", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;245mcount\u001b[0m = local.create_bucket \u001b[38;5;245m\u0026\u0026\u001b[0m \u001b[38;5;245m!\u001b[0m\u001b[38;5;33mvar\u001b[0m.is_directory_bucket \u001b[38;5;245m?\u001b[0m \u001b[38;5;37m1\u001b[0m \u001b[38;5;245m:\u001b[0m \u001b[38;5;37m0", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 27, - "Content": "", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": "\u001b[0m", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 28, - "Content": " region = var.region", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;245mregion\u001b[0m = \u001b[38;5;33mvar\u001b[0m.region", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 29, - "Content": "", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "FirstCause": false, - "LastCause": false - }, - { - "Number": 30, - "Content": " bucket = var.bucket", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;245mbucket\u001b[0m = \u001b[38;5;33mvar\u001b[0m.bucket", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 31, - "Content": " bucket_prefix = var.bucket_prefix", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;245mbucket_prefix\u001b[0m = \u001b[38;5;33mvar\u001b[0m.bucket_prefix", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 32, - "Content": "", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "FirstCause": false, - "LastCause": false - }, - { - "Number": 33, - "Content": " force_destroy = var.force_destroy", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;245mforce_destroy\u001b[0m = \u001b[38;5;33mvar\u001b[0m.force_destroy", - "FirstCause": false, - "LastCause": true - }, - { - "Number": 34, - "Content": "", - "IsCause": false, - "Annotation": "", - "Truncated": true, - "FirstCause": false, - "LastCause": false - } - ] - }, - "Occurrences": [ - { - "Resource": "module.bucket", - "Filename": "modules/s3/main.tf", - "Location": { - "StartLine": 5, - "EndLine": 8 - } - }, - { - "Resource": "module.this", - "Filename": "main.tf", - "Location": { - "StartLine": 1, - "EndLine": 4 - } - } - ] - } - }, - { - "Type": "Terraform Security Check", - "ID": "AVD-AWS-0089", - "AVDID": "AVD-AWS-0089", + "ID": "AWS-0089", "Title": "S3 Bucket Logging", "Description": "Ensures S3 bucket logging is enabled for S3 buckets", "Message": "Bucket has logging disabled", @@ -181,11 +36,11 @@ "Query": "data.builtin.aws.s3.aws0089.deny", "Resolution": "Add a logging block to the resource to enable access logging", "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0089", + "PrimaryURL": "https://avd.aquasec.com/misconfig/aws-0089", "References": [ "https://docs.aws.amazon.com/AmazonS3/latest/userguide/ServerLogs.html", "https://docs.aws.amazon.com/AmazonS3/latest/userguide/enable-server-access-logging.html", - "https://avd.aquasec.com/misconfig/avd-aws-0089" + "https://avd.aquasec.com/misconfig/aws-0089" ], "Status": "FAIL", "CauseMetadata": { @@ -317,8 +172,7 @@ }, { "Type": "Terraform Security Check", - "ID": "AVD-AWS-0090", - "AVDID": "AVD-AWS-0090", + "ID": "AWS-0090", "Title": "S3 Data should be versioned", "Description": "Versioning in Amazon S3 is a means of keeping multiple variants of an object in the same bucket.\n\nYou can use the S3 Versioning feature to preserve, retrieve, and restore every version of every object stored in your buckets.\n\nWith versioning you can recover more easily from both unintended user actions and application failures.\n\nWhen you enable versioning, also keep in mind the potential costs of storing noncurrent versions of objects. To help manage those costs, consider setting up an S3 Lifecycle configuration.\n", "Message": "Bucket does not have versioning enabled", @@ -326,11 +180,11 @@ "Query": "data.builtin.aws.s3.aws0090.deny", "Resolution": "Enable versioning to protect against accidental/malicious removal or modification", "Severity": "MEDIUM", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0090", + "PrimaryURL": "https://avd.aquasec.com/misconfig/aws-0090", "References": [ "https://docs.aws.amazon.com/AmazonS3/latest/userguide/Versioning.html", "https://aws.amazon.com/blogs/storage/reduce-storage-costs-with-fewer-noncurrent-versions-using-amazon-s3-lifecycle/", - "https://avd.aquasec.com/misconfig/avd-aws-0090" + "https://avd.aquasec.com/misconfig/aws-0090" ], "Status": "FAIL", "CauseMetadata": { @@ -462,8 +316,7 @@ }, { "Type": "Terraform Security Check", - "ID": "AVD-AWS-0132", - "AVDID": "AVD-AWS-0132", + "ID": "AWS-0132", "Title": "S3 encryption should use Customer Managed Keys", "Description": "Encryption using AWS keys provides protection for your S3 buckets. To gain greater control over encryption, such as key rotation, access policies, and auditability, use customer managed keys (CMKs) with SSE-KMS.\nNote that SSE-KMS is not supported for S3 server access logging destination buckets; in such cases, use SSE-S3 instead.\n", "Message": "Bucket does not encrypt data with a customer managed key.", @@ -471,10 +324,10 @@ "Query": "data.builtin.aws.s3.aws0132.deny", "Resolution": "Use SSE-KMS with a customer managed key (CMK)", "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0132", + "PrimaryURL": "https://avd.aquasec.com/misconfig/aws-0132", "References": [ "https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-encryption.html", - "https://avd.aquasec.com/misconfig/avd-aws-0132" + "https://avd.aquasec.com/misconfig/aws-0132" ], "Status": "FAIL", "CauseMetadata": { diff --git a/integration/testdata/terraform-remote-module.json.golden b/integration/testdata/terraform-remote-module.json.golden index 4af02abe89..a9ef2a211e 100644 --- a/integration/testdata/terraform-remote-module.json.golden +++ b/integration/testdata/terraform-remote-module.json.golden @@ -13,7 +13,7 @@ "Class": "config", "Type": "terraform", "MisconfSummary": { - "Successes": 45, + "Successes": 58, "Failures": 0 } }, @@ -23,149 +23,12 @@ "Type": "terraform", "MisconfSummary": { "Successes": 0, - "Failures": 4 + "Failures": 3 }, "Misconfigurations": [ { "Type": "Terraform Security Check", - "ID": "AVD-AWS-0088", - "AVDID": "AVD-AWS-0088", - "Title": "Unencrypted S3 bucket.", - "Description": "S3 Buckets should be encrypted to protect the data that is stored within them if access is compromised.\n", - "Message": "Bucket does not have encryption enabled", - "Namespace": "builtin.aws.s3.aws0088", - "Query": "data.builtin.aws.s3.aws0088.deny", - "Resolution": "Configure bucket encryption", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0088", - "References": [ - "https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-encryption.html", - "https://avd.aquasec.com/misconfig/avd-aws-0088" - ], - "Status": "FAIL", - "CauseMetadata": { - "Resource": "module.bucket", - "Provider": "AWS", - "Service": "s3", - "StartLine": 25, - "EndLine": 36, - "Code": { - "Lines": [ - { - "Number": 25, - "Content": "resource \"aws_s3_bucket\" \"this\" {", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": "\u001b[38;5;33mresource\u001b[0m \u001b[38;5;37m\"aws_s3_bucket\"\u001b[0m \u001b[38;5;37m\"this\"\u001b[0m {", - "FirstCause": true, - "LastCause": false - }, - { - "Number": 26, - "Content": " count = local.create_bucket \u0026\u0026 !var.is_directory_bucket ? 1 : 0", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;245mcount\u001b[0m = local.create_bucket \u001b[38;5;245m\u0026\u0026\u001b[0m \u001b[38;5;245m!\u001b[0m\u001b[38;5;33mvar\u001b[0m.is_directory_bucket \u001b[38;5;245m?\u001b[0m \u001b[38;5;37m1\u001b[0m \u001b[38;5;245m:\u001b[0m \u001b[38;5;37m0", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 27, - "Content": "", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": "\u001b[0m", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 28, - "Content": " region = var.region", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;245mregion\u001b[0m = \u001b[38;5;33mvar\u001b[0m.region", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 29, - "Content": "", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "FirstCause": false, - "LastCause": false - }, - { - "Number": 30, - "Content": " bucket = var.bucket", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;245mbucket\u001b[0m = \u001b[38;5;33mvar\u001b[0m.bucket", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 31, - "Content": " bucket_prefix = var.bucket_prefix", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;245mbucket_prefix\u001b[0m = \u001b[38;5;33mvar\u001b[0m.bucket_prefix", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 32, - "Content": "", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "FirstCause": false, - "LastCause": false - }, - { - "Number": 33, - "Content": " force_destroy = var.force_destroy", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;245mforce_destroy\u001b[0m = \u001b[38;5;33mvar\u001b[0m.force_destroy", - "FirstCause": false, - "LastCause": true - }, - { - "Number": 34, - "Content": "", - "IsCause": false, - "Annotation": "", - "Truncated": true, - "FirstCause": false, - "LastCause": false - } - ] - }, - "Occurrences": [ - { - "Resource": "module.bucket", - "Filename": "main.tf", - "Location": { - "StartLine": 1, - "EndLine": 5 - } - } - ] - } - }, - { - "Type": "Terraform Security Check", - "ID": "AVD-AWS-0089", - "AVDID": "AVD-AWS-0089", + "ID": "AWS-0089", "Title": "S3 Bucket Logging", "Description": "Ensures S3 bucket logging is enabled for S3 buckets", "Message": "Bucket has logging disabled", @@ -173,11 +36,11 @@ "Query": "data.builtin.aws.s3.aws0089.deny", "Resolution": "Add a logging block to the resource to enable access logging", "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0089", + "PrimaryURL": "https://avd.aquasec.com/misconfig/aws-0089", "References": [ "https://docs.aws.amazon.com/AmazonS3/latest/userguide/ServerLogs.html", "https://docs.aws.amazon.com/AmazonS3/latest/userguide/enable-server-access-logging.html", - "https://avd.aquasec.com/misconfig/avd-aws-0089" + "https://avd.aquasec.com/misconfig/aws-0089" ], "Status": "FAIL", "CauseMetadata": { @@ -301,8 +164,7 @@ }, { "Type": "Terraform Security Check", - "ID": "AVD-AWS-0090", - "AVDID": "AVD-AWS-0090", + "ID": "AWS-0090", "Title": "S3 Data should be versioned", "Description": "Versioning in Amazon S3 is a means of keeping multiple variants of an object in the same bucket.\n\nYou can use the S3 Versioning feature to preserve, retrieve, and restore every version of every object stored in your buckets.\n\nWith versioning you can recover more easily from both unintended user actions and application failures.\n\nWhen you enable versioning, also keep in mind the potential costs of storing noncurrent versions of objects. To help manage those costs, consider setting up an S3 Lifecycle configuration.\n", "Message": "Bucket does not have versioning enabled", @@ -310,11 +172,11 @@ "Query": "data.builtin.aws.s3.aws0090.deny", "Resolution": "Enable versioning to protect against accidental/malicious removal or modification", "Severity": "MEDIUM", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0090", + "PrimaryURL": "https://avd.aquasec.com/misconfig/aws-0090", "References": [ "https://docs.aws.amazon.com/AmazonS3/latest/userguide/Versioning.html", "https://aws.amazon.com/blogs/storage/reduce-storage-costs-with-fewer-noncurrent-versions-using-amazon-s3-lifecycle/", - "https://avd.aquasec.com/misconfig/avd-aws-0090" + "https://avd.aquasec.com/misconfig/aws-0090" ], "Status": "FAIL", "CauseMetadata": { @@ -438,8 +300,7 @@ }, { "Type": "Terraform Security Check", - "ID": "AVD-AWS-0132", - "AVDID": "AVD-AWS-0132", + "ID": "AWS-0132", "Title": "S3 encryption should use Customer Managed Keys", "Description": "Encryption using AWS keys provides protection for your S3 buckets. To gain greater control over encryption, such as key rotation, access policies, and auditability, use customer managed keys (CMKs) with SSE-KMS.\nNote that SSE-KMS is not supported for S3 server access logging destination buckets; in such cases, use SSE-S3 instead.\n", "Message": "Bucket does not encrypt data with a customer managed key.", @@ -447,10 +308,10 @@ "Query": "data.builtin.aws.s3.aws0132.deny", "Resolution": "Use SSE-KMS with a customer managed key (CMK)", "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0132", + "PrimaryURL": "https://avd.aquasec.com/misconfig/aws-0132", "References": [ "https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-encryption.html", - "https://avd.aquasec.com/misconfig/avd-aws-0132" + "https://avd.aquasec.com/misconfig/aws-0132" ], "Status": "FAIL", "CauseMetadata": { diff --git a/integration/testdata/terraform-remote-submodule.json.golden b/integration/testdata/terraform-remote-submodule.json.golden index 95acd8e8b0..ba94aea4d3 100644 --- a/integration/testdata/terraform-remote-submodule.json.golden +++ b/integration/testdata/terraform-remote-submodule.json.golden @@ -13,7 +13,7 @@ "Class": "config", "Type": "terraform", "MisconfSummary": { - "Successes": 56, + "Successes": 69, "Failures": 0 } }, @@ -28,8 +28,7 @@ "Misconfigurations": [ { "Type": "Terraform Security Check", - "ID": "AVD-AWS-0104", - "AVDID": "AVD-AWS-0104", + "ID": "AWS-0104", "Title": "A security group rule should not allow unrestricted egress to any IP address.", "Description": "Opening up ports to connect out to the public internet is generally to be avoided. You should restrict access to IP addresses or ranges that are explicitly required where possible.\n", "Message": "Security group rule allows unrestricted egress to any IP address.", @@ -37,10 +36,10 @@ "Query": "data.builtin.aws.ec2.aws0104.deny", "Resolution": "Set a more restrictive cidr range", "Severity": "CRITICAL", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0104", + "PrimaryURL": "https://avd.aquasec.com/misconfig/aws-0104", "References": [ "https://docs.aws.amazon.com/whitepapers/latest/building-scalable-secure-multi-vpc-network-infrastructure/centralized-egress-to-internet.html", - "https://avd.aquasec.com/misconfig/avd-aws-0104" + "https://avd.aquasec.com/misconfig/aws-0104" ], "Status": "FAIL", "CauseMetadata": { @@ -127,8 +126,7 @@ }, { "Type": "Terraform Security Check", - "ID": "AVD-AWS-0124", - "AVDID": "AVD-AWS-0124", + "ID": "AWS-0124", "Title": "Missing description for security group rule.", "Description": "Security group rules should include a description for auditing purposes.\n\nSimplifies auditing, debugging, and managing security groups.\n", "Message": "Security group rule does not have a description.", @@ -136,10 +134,10 @@ "Query": "data.builtin.aws.ec2.aws0124.deny", "Resolution": "Add descriptions for all security groups rules", "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0124", + "PrimaryURL": "https://avd.aquasec.com/misconfig/aws-0124", "References": [ "https://www.cloudconformity.com/knowledge-base/aws/EC2/security-group-rules-description.html", - "https://avd.aquasec.com/misconfig/avd-aws-0124" + "https://avd.aquasec.com/misconfig/aws-0124" ], "Status": "FAIL", "CauseMetadata": { diff --git a/integration/testdata/terraform-terraform-registry.json.golden b/integration/testdata/terraform-terraform-registry.json.golden index d6b9e3ffbe..527a1732cc 100644 --- a/integration/testdata/terraform-terraform-registry.json.golden +++ b/integration/testdata/terraform-terraform-registry.json.golden @@ -13,7 +13,7 @@ "Class": "config", "Type": "terraform", "MisconfSummary": { - "Successes": 45, + "Successes": 58, "Failures": 0 } }, @@ -23,149 +23,12 @@ "Type": "terraform", "MisconfSummary": { "Successes": 0, - "Failures": 4 + "Failures": 3 }, "Misconfigurations": [ { "Type": "Terraform Security Check", - "ID": "AVD-AWS-0088", - "AVDID": "AVD-AWS-0088", - "Title": "Unencrypted S3 bucket.", - "Description": "S3 Buckets should be encrypted to protect the data that is stored within them if access is compromised.\n", - "Message": "Bucket does not have encryption enabled", - "Namespace": "builtin.aws.s3.aws0088", - "Query": "data.builtin.aws.s3.aws0088.deny", - "Resolution": "Configure bucket encryption", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0088", - "References": [ - "https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-encryption.html", - "https://avd.aquasec.com/misconfig/avd-aws-0088" - ], - "Status": "FAIL", - "CauseMetadata": { - "Resource": "module.bucket", - "Provider": "AWS", - "Service": "s3", - "StartLine": 25, - "EndLine": 36, - "Code": { - "Lines": [ - { - "Number": 25, - "Content": "resource \"aws_s3_bucket\" \"this\" {", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": "\u001b[38;5;33mresource\u001b[0m \u001b[38;5;37m\"aws_s3_bucket\"\u001b[0m \u001b[38;5;37m\"this\"\u001b[0m {", - "FirstCause": true, - "LastCause": false - }, - { - "Number": 26, - "Content": " count = local.create_bucket \u0026\u0026 !var.is_directory_bucket ? 1 : 0", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;245mcount\u001b[0m = local.create_bucket \u001b[38;5;245m\u0026\u0026\u001b[0m \u001b[38;5;245m!\u001b[0m\u001b[38;5;33mvar\u001b[0m.is_directory_bucket \u001b[38;5;245m?\u001b[0m \u001b[38;5;37m1\u001b[0m \u001b[38;5;245m:\u001b[0m \u001b[38;5;37m0", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 27, - "Content": "", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": "\u001b[0m", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 28, - "Content": " region = var.region", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;245mregion\u001b[0m = \u001b[38;5;33mvar\u001b[0m.region", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 29, - "Content": "", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "FirstCause": false, - "LastCause": false - }, - { - "Number": 30, - "Content": " bucket = var.bucket", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;245mbucket\u001b[0m = \u001b[38;5;33mvar\u001b[0m.bucket", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 31, - "Content": " bucket_prefix = var.bucket_prefix", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;245mbucket_prefix\u001b[0m = \u001b[38;5;33mvar\u001b[0m.bucket_prefix", - "FirstCause": false, - "LastCause": false - }, - { - "Number": 32, - "Content": "", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "FirstCause": false, - "LastCause": false - }, - { - "Number": 33, - "Content": " force_destroy = var.force_destroy", - "IsCause": true, - "Annotation": "", - "Truncated": false, - "Highlighted": " \u001b[38;5;245mforce_destroy\u001b[0m = \u001b[38;5;33mvar\u001b[0m.force_destroy", - "FirstCause": false, - "LastCause": true - }, - { - "Number": 34, - "Content": "", - "IsCause": false, - "Annotation": "", - "Truncated": true, - "FirstCause": false, - "LastCause": false - } - ] - }, - "Occurrences": [ - { - "Resource": "module.bucket", - "Filename": "main.tf", - "Location": { - "StartLine": 1, - "EndLine": 5 - } - } - ] - } - }, - { - "Type": "Terraform Security Check", - "ID": "AVD-AWS-0089", - "AVDID": "AVD-AWS-0089", + "ID": "AWS-0089", "Title": "S3 Bucket Logging", "Description": "Ensures S3 bucket logging is enabled for S3 buckets", "Message": "Bucket has logging disabled", @@ -173,11 +36,11 @@ "Query": "data.builtin.aws.s3.aws0089.deny", "Resolution": "Add a logging block to the resource to enable access logging", "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0089", + "PrimaryURL": "https://avd.aquasec.com/misconfig/aws-0089", "References": [ "https://docs.aws.amazon.com/AmazonS3/latest/userguide/ServerLogs.html", "https://docs.aws.amazon.com/AmazonS3/latest/userguide/enable-server-access-logging.html", - "https://avd.aquasec.com/misconfig/avd-aws-0089" + "https://avd.aquasec.com/misconfig/aws-0089" ], "Status": "FAIL", "CauseMetadata": { @@ -301,8 +164,7 @@ }, { "Type": "Terraform Security Check", - "ID": "AVD-AWS-0090", - "AVDID": "AVD-AWS-0090", + "ID": "AWS-0090", "Title": "S3 Data should be versioned", "Description": "Versioning in Amazon S3 is a means of keeping multiple variants of an object in the same bucket.\n\nYou can use the S3 Versioning feature to preserve, retrieve, and restore every version of every object stored in your buckets.\n\nWith versioning you can recover more easily from both unintended user actions and application failures.\n\nWhen you enable versioning, also keep in mind the potential costs of storing noncurrent versions of objects. To help manage those costs, consider setting up an S3 Lifecycle configuration.\n", "Message": "Bucket does not have versioning enabled", @@ -310,11 +172,11 @@ "Query": "data.builtin.aws.s3.aws0090.deny", "Resolution": "Enable versioning to protect against accidental/malicious removal or modification", "Severity": "MEDIUM", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0090", + "PrimaryURL": "https://avd.aquasec.com/misconfig/aws-0090", "References": [ "https://docs.aws.amazon.com/AmazonS3/latest/userguide/Versioning.html", "https://aws.amazon.com/blogs/storage/reduce-storage-costs-with-fewer-noncurrent-versions-using-amazon-s3-lifecycle/", - "https://avd.aquasec.com/misconfig/avd-aws-0090" + "https://avd.aquasec.com/misconfig/aws-0090" ], "Status": "FAIL", "CauseMetadata": { @@ -438,8 +300,7 @@ }, { "Type": "Terraform Security Check", - "ID": "AVD-AWS-0132", - "AVDID": "AVD-AWS-0132", + "ID": "AWS-0132", "Title": "S3 encryption should use Customer Managed Keys", "Description": "Encryption using AWS keys provides protection for your S3 buckets. To gain greater control over encryption, such as key rotation, access policies, and auditability, use customer managed keys (CMKs) with SSE-KMS.\nNote that SSE-KMS is not supported for S3 server access logging destination buckets; in such cases, use SSE-S3 instead.\n", "Message": "Bucket does not encrypt data with a customer managed key.", @@ -447,10 +308,10 @@ "Query": "data.builtin.aws.s3.aws0132.deny", "Resolution": "Use SSE-KMS with a customer managed key (CMK)", "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0132", + "PrimaryURL": "https://avd.aquasec.com/misconfig/aws-0132", "References": [ "https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-encryption.html", - "https://avd.aquasec.com/misconfig/avd-aws-0132" + "https://avd.aquasec.com/misconfig/aws-0132" ], "Status": "FAIL", "CauseMetadata": { diff --git a/pkg/commands/operation/operation.go b/pkg/commands/operation/operation.go index 4393a544ca..850368df42 100644 --- a/pkg/commands/operation/operation.go +++ b/pkg/commands/operation/operation.go @@ -82,15 +82,18 @@ func DownloadVEXRepositories(ctx context.Context, opts flag.Options) error { func InitBuiltinChecks(ctx context.Context, client *policy.Client, skipUpdate bool, registryOpts ftypes.RegistryOptions) (string, error) { mu.Lock() defer mu.Unlock() + + ctx = log.WithContextPrefix(ctx, "checks-client") + var err error if skipUpdate { - log.Info("No downloadable checks were loaded as --skip-check-update is enabled, loading from existing cache...") + log.InfoContext(ctx, "No downloadable checks were loaded as --skip-check-update is enabled, loading from existing cache...") - path := client.LoadBuiltinChecks() + path := client.BuiltinChecksPath() _, _, err := misconf.CheckPathExists(path) if err != nil { - return "", xerrors.Errorf("Failed to load existing cache, err: %s", err.Error()) + return "", xerrors.Errorf("failed to check cache: %w", err) } return path, nil } @@ -106,9 +109,14 @@ func InitBuiltinChecks(ctx context.Context, client *policy.Client, skipUpdate bo if err = client.DownloadBuiltinChecks(ctx, registryOpts); err != nil { return "", xerrors.Errorf("failed to download checks bundle: %w", err) } + } else { + log.InfoContext(ctx, + "Using existing checks from cache", + log.String("path", client.BuiltinChecksPath()), + ) } - return client.LoadBuiltinChecks(), nil + return client.BuiltinChecksPath(), nil } func Exit(opts flag.Options, failedResults bool, m types.Metadata) error { diff --git a/pkg/commands/operation/operation_test.go b/pkg/commands/operation/operation_test.go index 39f16dbcda..79609226c1 100644 --- a/pkg/commands/operation/operation_test.go +++ b/pkg/commands/operation/operation_test.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "errors" + "fmt" "io" "os" "path/filepath" @@ -14,6 +15,7 @@ import ( fakei "github.com/google/go-containerregistry/pkg/v1/fake" "github.com/google/go-containerregistry/pkg/v1/tarball" "github.com/google/go-containerregistry/pkg/v1/types" + "github.com/samber/lo" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "k8s.io/utils/clock" @@ -74,6 +76,7 @@ func TestInitBuiltinChecks(t *testing.T) { metadata: policy.Metadata{ Digest: `sha256:922e50f14ab484f11ae65540c3d2d76009020213f1027d4331d31141575e5414`, DownloadedAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + MajorVersion: lo.ToPtr(policy.BundleVersion), }, skipUpdate: false, }, @@ -81,7 +84,7 @@ func TestInitBuiltinChecks(t *testing.T) { name: "skip update flag set with no existing cache to fallback to", skipUpdate: true, checkDir: "policy", - wantErr: "Failed to load existing cache", + wantErr: "cache does not exist at", }, { name: "skip update flag set with existing cache to fallback to", @@ -101,6 +104,7 @@ func TestInitBuiltinChecks(t *testing.T) { metadata: policy.Metadata{ Digest: `sha256:922e50f14ab484f11ae65540c3d2d76009020213f1027d4331d31141575e5414`, DownloadedAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + MajorVersion: lo.ToPtr(policy.BundleVersion), }, checkDir: "policy", clock: fake.NewFakeClock(time.Date(3000, 1, 1, 1, 0, 0, 0, time.UTC)), @@ -162,6 +166,9 @@ func TestInitBuiltinChecks(t *testing.T) { }, }, }, + Annotations: map[string]string{ + policy.VersionAnnotationKey: fmt.Sprintf("%d.0.0", policy.BundleVersion), + }, }, nil) // Mock OCI artifact diff --git a/pkg/compliance/spec/compliance.go b/pkg/compliance/spec/compliance.go index 66ac81e66b..aa67fda0dc 100644 --- a/pkg/compliance/spec/compliance.go +++ b/pkg/compliance/spec/compliance.go @@ -9,7 +9,7 @@ import ( "golang.org/x/xerrors" "gopkg.in/yaml.v3" - "github.com/aquasecurity/trivy-checks/pkg/specs" + "github.com/aquasecurity/trivy-checks/pkg/compliance" iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/set" @@ -61,17 +61,31 @@ func scannerByCheckID(checkID string) types.Scanner { switch { case strings.HasPrefix(checkID, "cve-") || strings.HasPrefix(checkID, "dla-"): return types.VulnerabilityScanner - case strings.HasPrefix(checkID, "avd-"): - return types.MisconfigScanner case strings.HasPrefix(checkID, "vuln-"): // custom id for filtering vulnerabilities by severity return types.VulnerabilityScanner case strings.HasPrefix(checkID, "secret-"): // custom id for filtering secrets by severity return types.SecretScanner + // check the "avd-" prefix for backward compatibility + case strings.HasPrefix(checkID, "avd-") || isMisconfCheck(checkID): + return types.MisconfigScanner default: return types.UnknownScanner } } +var misconfPrefixes = set.New( + "aws", "azu", "nif", "dig", "oci", "cldstk", "git", + "gcp", "ksv", "kcv", "ds", "kube", "opnstk", +) + +func isMisconfCheck(checkID string) bool { + prefix, _, ok := strings.Cut(checkID, "-") + if !ok { + return false + } + return misconfPrefixes.Contains(prefix) +} + func checksDir(cacheDir string) string { return filepath.Join(cacheDir, "policy") } @@ -97,7 +111,7 @@ func GetComplianceSpec(specNameOrPath, cacheDir string) (ComplianceSpec, error) } else { _, err := os.Stat(filepath.Join(checksDir(cacheDir), "metadata.json")) if err != nil { // cache corrupt or bundle does not exist, load embedded version - b = []byte(specs.GetSpec(specNameOrPath)) + b = []byte(compliance.GetSpec(specNameOrPath)) log.Debug("Compliance spec loaded from embedded library", log.String("spec", specNameOrPath)) } else { // load from bundle on disk diff --git a/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile.go b/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile.go index e23e5371c2..43f9d497bf 100644 --- a/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile.go +++ b/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile.go @@ -22,7 +22,7 @@ import ( ) var ( - disabledChecks = set.New("AVD-DS-0007", "AVD-DS-0016") + disabledChecks = set.New("DS-0007", "DS-0016") reason = "See " + doc.URL("guide/target/container_image", "disabled-checks") ) @@ -202,9 +202,9 @@ func (a *historyAnalyzer) Version() int { func filterDisabledChecks(results types.MisconfResults) types.MisconfResults { var filtered types.MisconfResults for _, r := range results { - if disabledChecks.Contains(r.AVDID) { + if disabledChecks.Contains(r.ID) { log.WithPrefix("image history analyzer").Info("Skip disabled check", - log.String("ID", r.AVDID), log.String("reason", reason)) + log.String("ID", r.ID), log.String("reason", reason)) continue } filtered = append(filtered, r) diff --git a/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile_test.go b/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile_test.go index 866f5b29f8..f6397ef0c7 100644 --- a/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile_test.go +++ b/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile_test.go @@ -101,8 +101,8 @@ func Test_historyAnalyzer_Analyze(t *testing.T) { Query: "data.builtin.dockerfile.DS005.deny", Message: "Consider using 'COPY foo.txt /' command instead of 'ADD foo.txt /'", PolicyMetadata: types.PolicyMetadata{ - ID: "DS005", - AVDID: "AVD-DS-0005", + ID: "DS-0005", + AVDID: "", Type: "Dockerfile Security Check", Title: "ADD instead of COPY", Description: "You should use COPY instead of ADD unless you want to extract a tar file. Note that an ADD command will extract a tar file, which adds the risk of Zip-based vulnerabilities. Accordingly, it is advised to use a COPY command, which does not extract tar files.", @@ -188,8 +188,8 @@ func Test_historyAnalyzer_Analyze(t *testing.T) { Query: "data.builtin.dockerfile.DS005.deny", Message: "Consider using 'COPY ./foo.txt /foo.txt' command instead of 'ADD ./foo.txt /foo.txt'", PolicyMetadata: types.PolicyMetadata{ - ID: "DS005", - AVDID: "AVD-DS-0005", + ID: "DS-0005", + AVDID: "", Type: "Dockerfile Security Check", Title: "ADD instead of COPY", Description: "You should use COPY instead of ADD unless you want to extract a tar file. Note that an ADD command will extract a tar file, which adds the risk of Zip-based vulnerabilities. Accordingly, it is advised to use a COPY command, which does not extract tar files.", @@ -262,8 +262,8 @@ func Test_historyAnalyzer_Analyze(t *testing.T) { Query: "data.builtin.dockerfile.DS002.deny", Message: "Specify at least 1 USER command in Dockerfile with non-root user as argument", PolicyMetadata: types.PolicyMetadata{ - ID: "DS002", - AVDID: "AVD-DS-0002", + ID: "DS-0002", + AVDID: "", Type: "Dockerfile Security Check", Title: "Image user should not be 'root'", Description: "Running containers with 'root' user can lead to a container escape situation. It is a best practice to run containers as non-root users, which can be done by adding a 'USER' statement to the Dockerfile.", diff --git a/pkg/iac/adapters/terraform/aws/s3/bucket.go b/pkg/iac/adapters/terraform/aws/s3/bucket.go index d420673969..05a837a2b8 100644 --- a/pkg/iac/adapters/terraform/aws/s3/bucket.go +++ b/pkg/iac/adapters/terraform/aws/s3/bucket.go @@ -102,6 +102,7 @@ func getVersioning(block *terraform.Block, a *adapter) s3.Versioning { } if enabled, ok := applyForBucketRelatedResource(a, block, "aws_s3_bucket_object_lock_configuration", func(resource *terraform.Block) *iacTypes.BoolValue { + // TODO: object_lock_enabled is a string if block.GetAttribute("object_lock_enabled").IsTrue() { return isObjeckLockEnabled(resource) } diff --git a/pkg/iac/rego/embed.go b/pkg/iac/rego/embed.go index dc1a0803cb..bd05be94ce 100644 --- a/pkg/iac/rego/embed.go +++ b/pkg/iac/rego/embed.go @@ -57,7 +57,7 @@ func RegisterRegoRules(modules map[string]*ast.Module) { continue } - if metadata.AVDID == "" { + if metadata.ID == "" { if !metadata.Library { log.Warn("Check ID is empty", log.FilePath(module.Package.Location.File)) } diff --git a/pkg/iac/scanners/helm/test/scanner_test.go b/pkg/iac/scanners/helm/test/scanner_test.go index e83dd04a59..fe4f769bd8 100644 --- a/pkg/iac/scanners/helm/test/scanner_test.go +++ b/pkg/iac/scanners/helm/test/scanner_test.go @@ -28,12 +28,12 @@ func TestScanner_ScanFS(t *testing.T) { name: "archived chart", target: filepath.Join("testdata", "mysql-8.8.26.tar"), assert: assertIds([]string{ - "KSV001", "KSV003", - "KSV011", "KSV012", "KSV014", - "KSV015", "KSV016", "KSV018", - "KSV020", "KSV021", "KSV030", - "KSV104", "KSV106", "KSV0125", - "KSV004", + "KSV-0001", "KSV-0003", + "KSV-0011", "KSV-0012", "KSV-0014", + "KSV-0015", "KSV-0016", "KSV-0018", + "KSV-0020", "KSV-0021", "KSV-0030", + "KSV-0104", "KSV-0106", "KSV-0125", + "KSV-0004", }), }, { @@ -41,19 +41,19 @@ func TestScanner_ScanFS(t *testing.T) { target: filepath.Join("testdata", "testchart"), assert: func(t *testing.T, results scan.Results) { assertIds([]string{ - "KSV001", "KSV003", - "KSV011", "KSV012", "KSV014", - "KSV015", "KSV016", - "KSV020", "KSV021", "KSV030", - "KSV104", "KSV106", - "KSV117", "KSV110", "KSV118", - "KSV004", + "KSV-0001", "KSV-0003", + "KSV-0011", "KSV-0012", "KSV-0014", + "KSV-0015", "KSV-0016", + "KSV-0020", "KSV-0021", "KSV-0030", + "KSV-0104", "KSV-0106", + "KSV-0117", "KSV-0110", "KSV-0118", + "KSV-0004", })(t, results) ignored := results.GetIgnored() assert.Len(t, ignored, 1) - assert.Equal(t, "KSV018", ignored[0].Rule().ID) + assert.Equal(t, "KSV-0018", ignored[0].Rule().ID) assert.Equal(t, "testchart/templates/deployment.yaml", ignored[0].Metadata().Range().GetFilename()) }, }, @@ -62,12 +62,12 @@ func TestScanner_ScanFS(t *testing.T) { name: "scanner with missing chart name can recover", target: filepath.Join("testdata", "aws-cluster-autoscaler-bad.tar.gz"), assert: assertIds([]string{ - "KSV014", "KSV023", "KSV030", - "KSV104", "KSV003", "KSV018", - "KSV118", "KSV012", "KSV106", - "KSV016", "KSV001", "KSV011", - "KSV015", "KSV021", "KSV110", "KSV020", - "KSV004", + "KSV-0014", "KSV-0023", "KSV-0030", + "KSV-0104", "KSV-0003", "KSV-0018", + "KSV-0118", "KSV-0012", "KSV-0106", + "KSV-0016", "KSV-0001", "KSV-0011", + "KSV-0015", "KSV-0021", "KSV-0110", "KSV-0020", + "KSV-0004", }), }, { @@ -96,12 +96,12 @@ deny[res] { }`)), }, assert: assertIds([]string{ - "KSV001", "KSV003", - "KSV011", "KSV012", "KSV014", - "KSV015", "KSV016", "KSV018", - "KSV020", "KSV021", "KSV030", - "KSV104", "KSV106", "USR-ID001", - "KSV004", "KSV0125", + "KSV-0001", "KSV-0003", + "KSV-0011", "KSV-0012", "KSV-0014", + "KSV-0015", "KSV-0016", "KSV-0018", + "KSV-0020", "KSV-0021", "KSV-0030", + "KSV-0104", "KSV-0106", "USR-ID001", + "KSV-0004", "KSV-0125", }), }, { diff --git a/pkg/iac/scanners/terraformplan/tfjson/scanner_test.go b/pkg/iac/scanners/terraformplan/tfjson/scanner_test.go index 58e5ebf685..9aa6ce2fe7 100644 --- a/pkg/iac/scanners/terraformplan/tfjson/scanner_test.go +++ b/pkg/iac/scanners/terraformplan/tfjson/scanner_test.go @@ -55,14 +55,14 @@ func TestScanner_ScanFS(t *testing.T) { rego.WithEmbeddedLibraries(true), }, expected: []string{ - "AVD-AWS-0093", - "AVD-AWS-0086", - "AVD-AWS-0132", - "AVD-AWS-0094", - "AVD-AWS-0087", - "AVD-AWS-0091", - "AVD-AWS-0099", - "AVD-AWS-0124", + "AWS-0093", + "AWS-0086", + "AWS-0132", + "AWS-0094", + "AWS-0087", + "AWS-0091", + "AWS-0099", + "AWS-0124", }, }, { diff --git a/pkg/misconf/scanner.go b/pkg/misconf/scanner.go index d8e7113e27..9d3c5ceb91 100644 --- a/pkg/misconf/scanner.go +++ b/pkg/misconf/scanner.go @@ -427,7 +427,7 @@ func CheckPathExists(path string) (fs.FileInfo, string, error) { } fi, err := os.Stat(abs) if errors.Is(err, os.ErrNotExist) { - return nil, "", xerrors.Errorf("check file %q not found", abs) + return nil, "", xerrors.Errorf("cache does not exist at %q", abs) } else if err != nil { return nil, "", xerrors.Errorf("file %q stat error: %w", abs, err) } diff --git a/pkg/oci/artifact.go b/pkg/oci/artifact.go index 0a2eea94a7..f021f9be22 100644 --- a/pkg/oci/artifact.go +++ b/pkg/oci/artifact.go @@ -219,6 +219,19 @@ func (a *Artifact) Digest(ctx context.Context) (string, error) { return digest.String(), nil } +func (a *Artifact) Manifest(ctx context.Context) (*v1.Manifest, error) { + if err := a.populate(ctx, a.RegistryOptions); err != nil { + return nil, err + } + + manifest, err := a.image.Manifest() + if err != nil { + return nil, xerrors.Errorf("get manifest: %w", err) + } + + return manifest, nil +} + type Artifacts []*Artifact // NewArtifacts returns a slice of artifacts. diff --git a/pkg/policy/policy.go b/pkg/policy/policy.go index 99f70b48e5..f5e54988d4 100644 --- a/pkg/policy/policy.go +++ b/pkg/policy/policy.go @@ -3,7 +3,9 @@ package policy import ( "context" "encoding/json" + "errors" "fmt" + "io/fs" "os" "path/filepath" "time" @@ -11,16 +13,20 @@ import ( "golang.org/x/xerrors" "k8s.io/utils/clock" + "github.com/aquasecurity/go-version/pkg/part" + "github.com/aquasecurity/go-version/pkg/semver" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/oci" ) const ( - BundleVersion = 1 // Latest released MAJOR version for trivy-checks + BundleVersion = 2 // Latest released MAJOR version for trivy-checks BundleRepository = "mirror.gcr.io/aquasec/trivy-checks" policyMediaType = "application/vnd.cncf.openpolicyagent.layer.v1.tar+gzip" updateInterval = 24 * time.Hour + + VersionAnnotationKey = "org.opencontainers.image.version" ) type options struct { @@ -57,6 +63,16 @@ type Client struct { type Metadata struct { Digest string DownloadedAt time.Time + + // MajorVersion indicates the major version of the bundle. + // Used to invalidate cache when the major version increases. + // Nil for old cache entries. Set to 0 for custom builds. + MajorVersion *int `json:",omitempty"` + + // CustomBuild is true if the bundle was built manually and did not go + // through the official build process that enriches the manifest with additional data. + // For custom builds, MajorVersion is not used for cache invalidation. + CustomBuild bool `json:",omitempty"` } func (m Metadata) String() string { @@ -88,16 +104,17 @@ func NewClient(cacheDir string, quiet bool, checkBundleRepo string, opts ...Opti }, nil } -func (c *Client) populateOCIArtifact(ctx context.Context, registryOpts types.RegistryOptions) { +func (c *Client) initOCIArtifact(ctx context.Context, registryOpts types.RegistryOptions) { if c.artifact == nil { - log.DebugContext(ctx, "Loading check bundle", log.String("repository", c.checkBundleRepo)) + log.DebugContext(ctx, "Initializing OCI checks bundle artifact", + log.String("repository", c.checkBundleRepo)) c.artifact = oci.NewArtifact(c.checkBundleRepo, registryOpts) } } // DownloadBuiltinChecks download default policies from GitHub Pages func (c *Client) DownloadBuiltinChecks(ctx context.Context, registryOpts types.RegistryOptions) error { - c.populateOCIArtifact(ctx, registryOpts) + c.initOCIArtifact(ctx, registryOpts) dst := c.contentDir() if err := c.artifact.Download(ctx, dst, oci.DownloadOption{ @@ -112,47 +129,137 @@ func (c *Client) DownloadBuiltinChecks(ctx context.Context, registryOpts types.R if err != nil { return xerrors.Errorf("digest error: %w", err) } - log.DebugContext(ctx, "Digest of the built-in checks", log.String("digest", digest)) + + ver, err := c.getBundleMajorVersion(ctx) + if err != nil { + return xerrors.Errorf("get bundle version: %w", err) + } + + isCustomBundle := ver == 0 + if isCustomBundle { + log.DebugContext(ctx, "Built-in checks (custom build)", + log.String("digest", digest)) + } else { + log.DebugContext(ctx, "Built-in checks", + log.String("digest", digest), log.Int("major_version", ver)) + } // Update metadata.json with the new digest and the current date - if err = c.updateMetadata(digest, c.clock.Now()); err != nil { + if err = c.updateMetadata(Metadata{ + Digest: digest, + DownloadedAt: c.clock.Now(), + MajorVersion: &ver, + CustomBuild: isCustomBundle, + }); err != nil { return xerrors.Errorf("unable to update the check metadata: %w", err) } return nil } -// LoadBuiltinChecks loads default policies -func (c *Client) LoadBuiltinChecks() string { +// BuiltinChecksPath returns default policies +func (c *Client) BuiltinChecksPath() string { return c.contentDir() } +func (c *Client) getBundleMajorVersion(ctx context.Context) (ver int, err error) { + manifest, err := c.artifact.Manifest(ctx) + if err != nil { + return 0, err + } + + // No annotations → treat as custom build + if manifest.Annotations == nil { + return 0, nil + } + + v, ok := manifest.Annotations[VersionAnnotationKey] + if !ok || v == "" { + return 0, nil + } + + version, err := semver.Parse(v) + if err != nil { + // Invalid version → treat as custom build + return 0, nil + } + + majorPart, ok := version.Major().(part.Uint64) + if !ok { + // Could not extract major part → treat as custom build + return 0, nil + } + + return int(majorPart), nil +} + // 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(ctx) if err != nil { + if errors.Is(err, fs.ErrNotExist) { + log.DebugContext(ctx, "Cache does not exist, will be created") + } else { + log.DebugContext(ctx, + "Invalidating cache: failed to get metadata", log.Err(err), + ) + } return true, nil } + // For official builds, check the major version for cache invalidation. + // Custom builds may not have a version annotation, so MajorVersion is ignored. + // Old cache entries without a version will be invalidated once to enrich metadata. + if !meta.CustomBuild { + + // Invalidate the old cache if it does not store the version. + if meta.MajorVersion == nil { + log.DebugContext(ctx, + "Invalidating cache: missing major version", + log.String("digest", meta.Digest), + log.Time("downloaded_at", meta.DownloadedAt), + ) + return true, nil + } + + // Invalidate the old cache if its version does not match. + if *meta.MajorVersion != BundleVersion { + log.DebugContext(ctx, "Invalidating cache: version mismatch", + log.Int("cached_major_version", *meta.MajorVersion), + log.Int("current_major_version", BundleVersion), + ) + return true, nil + } + } + // No need to update if it's been within a day since the last update. if c.clock.Now().Before(meta.DownloadedAt.Add(updateInterval)) { return false, nil } - c.populateOCIArtifact(ctx, registryOpts) + c.initOCIArtifact(ctx, registryOpts) digest, err := c.artifact.Digest(ctx) if err != nil { return false, xerrors.Errorf("digest error: %w", err) } if meta.Digest != digest { + log.DebugContext(ctx, "Invalidating cache: digest mismatch", + log.String("cached_digest", meta.Digest), + log.String("current_digest", digest), + ) return true, nil } // Update DownloadedAt with the current time. // Otherwise, if there are no updates in the remote registry, // the digest will be fetched every time even after this. - if err = c.updateMetadata(meta.Digest, time.Now()); err != nil { + if err = c.updateMetadata(Metadata{ + Digest: meta.Digest, + DownloadedAt: c.clock.Now(), + MajorVersion: meta.MajorVersion, + CustomBuild: meta.CustomBuild, + }); err != nil { return false, xerrors.Errorf("unable to update the check metadata: %w", err) } @@ -167,18 +274,13 @@ func (c *Client) metadataPath() string { return filepath.Join(c.policyDir, "metadata.json") } -func (c *Client) updateMetadata(digest string, now time.Time) error { +func (c *Client) updateMetadata(meta Metadata) error { f, err := os.Create(c.metadataPath()) if err != nil { return xerrors.Errorf("failed to open checks bundle metadata: %w", err) } defer f.Close() - meta := Metadata{ - Digest: digest, - DownloadedAt: now, - } - if err = json.NewEncoder(f).Encode(meta); err != nil { return xerrors.Errorf("json encode error: %w", err) } diff --git a/pkg/policy/policy_test.go b/pkg/policy/policy_test.go index 420d652c4a..c54c4a4619 100644 --- a/pkg/policy/policy_test.go +++ b/pkg/policy/policy_test.go @@ -3,7 +3,9 @@ package policy_test import ( "encoding/json" "errors" + "fmt" "io" + "maps" "os" "path/filepath" "testing" @@ -13,6 +15,7 @@ import ( fakei "github.com/google/go-containerregistry/pkg/v1/fake" "github.com/google/go-containerregistry/pkg/v1/tarball" "github.com/google/go-containerregistry/pkg/v1/types" + "github.com/samber/lo" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "k8s.io/utils/clock" @@ -97,7 +100,7 @@ func TestClient_LoadBuiltinChecks(t *testing.T) { c, err := policy.NewClient(tt.cacheDir, true, "", policy.WithOCIArtifact(art)) require.NoError(t, err) - got := c.LoadBuiltinChecks() + got := c.BuiltinChecksPath() if tt.wantErr != "" { require.ErrorContains(t, err, tt.wantErr) return @@ -108,61 +111,67 @@ func TestClient_LoadBuiltinChecks(t *testing.T) { } } +type annotations = map[string]string + func TestClient_NeedsUpdate(t *testing.T) { type digestReturns struct { h v1.Hash err error } + + imageDigest := digestReturns{ + h: v1.Hash{ + Algorithm: "sha256", + Hex: "01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d", + }, + } + + usedBundleVersion := fmt.Sprintf("%d.0.0", policy.BundleVersion) + tests := []struct { name string clock clock.Clock digestReturns digestReturns metadata any want bool + wantMetadata *policy.Metadata wantErr bool }{ { - name: "recent download", - clock: fake.NewFakeClock(time.Date(2021, 1, 1, 1, 0, 0, 0, time.UTC)), - digestReturns: digestReturns{ - h: v1.Hash{ - Algorithm: "sha256", - Hex: "01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d", - }, - }, + name: "recent download", + clock: fake.NewFakeClock(time.Date(2021, 1, 1, 1, 0, 0, 0, time.UTC)), + digestReturns: imageDigest, metadata: policy.Metadata{ Digest: `sha256:922e50f14ab484f11ae65540c3d2d76009020213f1027d4331d31141575e5414`, DownloadedAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + MajorVersion: lo.ToPtr(policy.BundleVersion), }, want: false, }, { - name: "same digest", - clock: fake.NewFakeClock(time.Date(2021, 1, 2, 1, 0, 0, 0, time.UTC)), - digestReturns: digestReturns{ - h: v1.Hash{ - Algorithm: "sha256", - Hex: "01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d", - }, - }, + name: "same digest", + clock: fake.NewFakeClock(time.Date(2021, 1, 2, 1, 0, 0, 0, time.UTC)), + digestReturns: imageDigest, metadata: policy.Metadata{ Digest: `sha256:01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d`, DownloadedAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + MajorVersion: lo.ToPtr(policy.BundleVersion), }, want: false, + wantMetadata: &policy.Metadata{ + Digest: `sha256:01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d`, + DownloadedAt: time.Date(2021, 1, 2, 1, 0, 0, 0, time.UTC), + MajorVersion: lo.ToPtr(policy.BundleVersion), + }, }, { - name: "different digest", - clock: fake.NewFakeClock(time.Date(2021, 1, 2, 1, 0, 0, 0, time.UTC)), - digestReturns: digestReturns{ - h: v1.Hash{ - Algorithm: "sha256", - Hex: "01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d", - }, - }, + name: "different digest", + clock: fake.NewFakeClock(time.Date(2021, 1, 2, 1, 0, 0, 0, time.UTC)), + digestReturns: imageDigest, metadata: policy.Metadata{ Digest: `sha256:922e50f14ab484f11ae65540c3d2d76009020213f1027d4331d31141575e5414`, DownloadedAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + MajorVersion: lo.ToPtr(policy.BundleVersion), }, want: true, }, @@ -175,6 +184,7 @@ func TestClient_NeedsUpdate(t *testing.T) { metadata: policy.Metadata{ Digest: `sha256:922e50f14ab484f11ae65540c3d2d76009020213f1027d4331d31141575e5414`, DownloadedAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + MajorVersion: lo.ToPtr(policy.BundleVersion), }, want: false, wantErr: true, @@ -190,6 +200,45 @@ func TestClient_NeedsUpdate(t *testing.T) { metadata: `"foo"`, want: true, }, + { + name: "old metadata without version", + clock: fake.NewFakeClock(time.Date(2021, 1, 2, 1, 0, 0, 0, time.UTC)), + digestReturns: imageDigest, + metadata: policy.Metadata{ + Digest: `sha256:01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d`, + DownloadedAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + }, + want: true, + }, + { + name: "version mismatched", + clock: fake.NewFakeClock(time.Date(2021, 1, 2, 1, 0, 0, 0, time.UTC)), + digestReturns: imageDigest, + metadata: policy.Metadata{ + Digest: `sha256:01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d`, + DownloadedAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + MajorVersion: lo.ToPtr(1), + }, + want: true, + }, + { + name: "version mismatched but custom build", + clock: fake.NewFakeClock(time.Date(2021, 1, 2, 1, 0, 0, 0, time.UTC)), + digestReturns: imageDigest, + metadata: policy.Metadata{ + Digest: `sha256:01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d`, + DownloadedAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), + CustomBuild: true, + MajorVersion: lo.ToPtr(1), + }, + want: false, + wantMetadata: &policy.Metadata{ + Digest: `sha256:01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d`, + DownloadedAt: time.Date(2021, 1, 2, 1, 0, 0, 0, time.UTC), + CustomBuild: true, + MajorVersion: lo.ToPtr(1), + }, + }, } for _, tt := range tests { @@ -215,6 +264,9 @@ func TestClient_NeedsUpdate(t *testing.T) { }, }, }, + Annotations: annotations{ + policy.VersionAnnotationKey: usedBundleVersion, + }, }, nil) // Create a check directory @@ -237,8 +289,29 @@ func TestClient_NeedsUpdate(t *testing.T) { // Assert results got, err := c.NeedsUpdate(t.Context(), ftypes.RegistryOptions{}) - assert.Equal(t, tt.wantErr, err != nil) + if tt.wantErr { + assert.Error(t, err) + return + } assert.Equal(t, tt.want, got) + + // Verify that metadata has been updated correctly + if metadata, ok := tt.metadata.(policy.Metadata); ok { + metadataPath := filepath.Join(tmpDir, "policy", "metadata.json") + b, err := os.ReadFile(metadataPath) + require.NoError(t, err) + + var want policy.Metadata + err = json.Unmarshal(b, &want) + require.NoError(t, err) + + if tt.wantMetadata == nil { + // Metadata has not been changed + tt.wantMetadata = lo.ToPtr(metadata) + } + + assert.Equal(t, tt.wantMetadata, &want) + } }) } } @@ -257,6 +330,7 @@ func TestClient_DownloadBuiltinChecks(t *testing.T) { clock clock.Clock layersReturns layersReturns digestReturns digestReturns + annotations annotations want *policy.Metadata wantErr string }{ @@ -275,6 +349,7 @@ func TestClient_DownloadBuiltinChecks(t *testing.T) { want: &policy.Metadata{ Digest: "sha256:01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d", DownloadedAt: time.Date(2021, 1, 1, 1, 0, 0, 0, time.UTC), + MajorVersion: lo.ToPtr(policy.BundleVersion), }, }, { @@ -300,11 +375,71 @@ func TestClient_DownloadBuiltinChecks(t *testing.T) { digestReturns: digestReturns{ err: errors.New("error"), }, + wantErr: "digest error", + }, + { + name: "custom bundle", + clock: fake.NewFakeClock(time.Date(2021, 1, 1, 1, 0, 0, 0, time.UTC)), + layersReturns: layersReturns{ + layers: []v1.Layer{newFakeLayer(t)}, + }, + digestReturns: digestReturns{ + h: v1.Hash{ + Algorithm: "sha256", + Hex: "01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d", + }, + }, + annotations: annotations{}, want: &policy.Metadata{ Digest: "sha256:01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d", DownloadedAt: time.Date(2021, 1, 1, 1, 0, 0, 0, time.UTC), + MajorVersion: lo.ToPtr(0), + CustomBuild: true, + }, + }, + { + name: "invalid version is treated as a custom build", + clock: fake.NewFakeClock(time.Date(2021, 1, 1, 1, 0, 0, 0, time.UTC)), + layersReturns: layersReturns{ + layers: []v1.Layer{newFakeLayer(t)}, + }, + digestReturns: digestReturns{ + h: v1.Hash{ + Algorithm: "sha256", + Hex: "01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d", + }, + }, + annotations: annotations{ + policy.VersionAnnotationKey: "dev", + }, + want: &policy.Metadata{ + Digest: "sha256:01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d", + DownloadedAt: time.Date(2021, 1, 1, 1, 0, 0, 0, time.UTC), + MajorVersion: lo.ToPtr(0), + CustomBuild: true, + }, + }, + { + name: "nightly build", + clock: fake.NewFakeClock(time.Date(2021, 1, 1, 1, 0, 0, 0, time.UTC)), + layersReturns: layersReturns{ + layers: []v1.Layer{newFakeLayer(t)}, + }, + digestReturns: digestReturns{ + h: v1.Hash{ + Algorithm: "sha256", + Hex: "01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d", + }, + }, + annotations: annotations{ + policy.VersionAnnotationKey: "2.0.1-nightly.20260129", + }, + want: &policy.Metadata{ + Digest: "sha256:01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d", + DownloadedAt: time.Date(2021, 1, 1, 1, 0, 0, 0, time.UTC), + MajorVersion: lo.ToPtr(2), + CustomBuild: false, }, - wantErr: "digest error", }, } @@ -316,7 +451,8 @@ func TestClient_DownloadBuiltinChecks(t *testing.T) { img := new(fakei.FakeImage) img.DigestReturns(tt.digestReturns.h, tt.digestReturns.err) img.LayersReturns(tt.layersReturns.layers, tt.layersReturns.err) - img.ManifestReturns(&v1.Manifest{ + + manifest := &v1.Manifest{ Layers: []v1.Descriptor{ { MediaType: "application/vnd.cncf.openpolicyagent.layer.v1.tar+gzip", @@ -330,7 +466,17 @@ func TestClient_DownloadBuiltinChecks(t *testing.T) { }, }, }, - }, nil) + Annotations: make(map[string]string), + } + + if tt.annotations != nil { + maps.Copy(manifest.Annotations, tt.annotations) + } else { + // Set default version + manifest.Annotations[policy.VersionAnnotationKey] = fmt.Sprintf("%d.0.0", policy.BundleVersion) + } + + img.ManifestReturns(manifest, nil) // Mock OCI artifact art := oci.NewArtifact("repo", ftypes.RegistryOptions{}, oci.WithImage(img))