diff --git a/docs/docs/coverage/iac/terraform.md b/docs/docs/coverage/iac/terraform.md index dff17725524f..843126f54d3a 100644 --- a/docs/docs/coverage/iac/terraform.md +++ b/docs/docs/coverage/iac/terraform.md @@ -8,18 +8,23 @@ Trivy supports the scanners listed in the table below. It supports the following formats: -| Format | Supported | -|:---------:|:---------:| -| JSON | ✓ | -| HCL | ✓ | -| Plan JSON | ✓ | - -Trivy can scan the results of `terraform plan`. -You can scan by passing the file generated as shown below to Trivy: - +| Format | Supported | +|:-------------:|:---------:| +| JSON | ✓ | +| HCL | ✓ | +| Plan Snapshot | ✓ | +| Plan JSON | ✓ | + +Trivy can scan Terraform Plan files (snapshots) or their JSON representations. To create a Terraform Plan and scan it, run the following command: +```bash +terraform plan --out tfplan +trivy conf tfplan ``` -$ terraform plan --out tfplan.binary -$ terraform show -json tfplan.binary > tfplan.json + +To scan a Terraform Plan representation in JSON format, run the following command: +```bash +terraform show -json tfplan > tfplan.json +trivy conf tfplan.json ``` ## Misconfiguration diff --git a/docs/docs/references/configuration/cli/trivy_aws.md b/docs/docs/references/configuration/cli/trivy_aws.md index 0218ccb1e987..af1ebc44a834 100644 --- a/docs/docs/references/configuration/cli/trivy_aws.md +++ b/docs/docs/references/configuration/cli/trivy_aws.md @@ -86,7 +86,7 @@ trivy aws [flags] --include-non-failures include successes and exceptions, available with '--scanners misconfig' --list-all-pkgs enabling the option will output all packages regardless of vulnerability --max-cache-age duration The maximum age of the cloud cache. Cached data will be requeried from the cloud provider if it is older than this. (default 24h0m0s) - --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan]) + --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) -o, --output string output file name --output-plugin-arg string [EXPERIMENTAL] output plugin arguments --policy-bundle-repository string OCI registry URL to retrieve policy bundle from (default "ghcr.io/aquasecurity/trivy-policies:0") diff --git a/docs/docs/references/configuration/cli/trivy_config.md b/docs/docs/references/configuration/cli/trivy_config.md index 79d99cad7331..865ecb6ba605 100644 --- a/docs/docs/references/configuration/cli/trivy_config.md +++ b/docs/docs/references/configuration/cli/trivy_config.md @@ -29,7 +29,7 @@ trivy config [flags] DIR --ignorefile string specify .trivyignore file (default ".trivyignore") --include-non-failures include successes and exceptions, available with '--scanners misconfig' --k8s-version string specify k8s version to validate outdated api by it (example: 1.21.0) - --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan]) + --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) --module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules") -o, --output string output file name --output-plugin-arg string [EXPERIMENTAL] output plugin arguments diff --git a/docs/docs/references/configuration/cli/trivy_filesystem.md b/docs/docs/references/configuration/cli/trivy_filesystem.md index 217518e18203..9a7a8fe24bb2 100644 --- a/docs/docs/references/configuration/cli/trivy_filesystem.md +++ b/docs/docs/references/configuration/cli/trivy_filesystem.md @@ -51,7 +51,7 @@ trivy filesystem [flags] PATH --license-confidence-level float specify license classifier's confidence level (default 0.9) --license-full eagerly look for licenses in source code headers and license files --list-all-pkgs enabling the option will output all packages regardless of vulnerability - --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan]) + --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) --module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules") --no-progress suppress progress bar --offline-scan do not issue API requests to identify dependencies diff --git a/docs/docs/references/configuration/cli/trivy_image.md b/docs/docs/references/configuration/cli/trivy_image.md index bc27d91213b3..e5484111bc86 100644 --- a/docs/docs/references/configuration/cli/trivy_image.md +++ b/docs/docs/references/configuration/cli/trivy_image.md @@ -69,7 +69,7 @@ trivy image [flags] IMAGE_NAME --license-confidence-level float specify license classifier's confidence level (default 0.9) --license-full eagerly look for licenses in source code headers and license files --list-all-pkgs enabling the option will output all packages regardless of vulnerability - --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan]) + --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) --module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules") --no-progress suppress progress bar --offline-scan do not issue API requests to identify dependencies diff --git a/docs/docs/references/configuration/cli/trivy_kubernetes.md b/docs/docs/references/configuration/cli/trivy_kubernetes.md index 8ac7b84e08c6..0b4ff3cf02d0 100644 --- a/docs/docs/references/configuration/cli/trivy_kubernetes.md +++ b/docs/docs/references/configuration/cli/trivy_kubernetes.md @@ -61,7 +61,7 @@ trivy kubernetes [flags] { cluster | all | specific resources like kubectl. eg: --k8s-version string specify k8s version to validate outdated api by it (example: 1.21.0) --kubeconfig string specify the kubeconfig file path to use --list-all-pkgs enabling the option will output all packages regardless of vulnerability - --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan]) + --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) -n, --namespace string specify a namespace to scan --no-progress suppress progress bar --node-collector-imageref string indicate the image reference for the node-collector scan job (default "ghcr.io/aquasecurity/node-collector:0.0.9") diff --git a/docs/docs/references/configuration/cli/trivy_repository.md b/docs/docs/references/configuration/cli/trivy_repository.md index a497a389ab68..de211fca43a2 100644 --- a/docs/docs/references/configuration/cli/trivy_repository.md +++ b/docs/docs/references/configuration/cli/trivy_repository.md @@ -51,7 +51,7 @@ trivy repository [flags] (REPO_PATH | REPO_URL) --license-confidence-level float specify license classifier's confidence level (default 0.9) --license-full eagerly look for licenses in source code headers and license files --list-all-pkgs enabling the option will output all packages regardless of vulnerability - --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan]) + --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) --module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules") --no-progress suppress progress bar --offline-scan do not issue API requests to identify dependencies diff --git a/docs/docs/references/configuration/cli/trivy_rootfs.md b/docs/docs/references/configuration/cli/trivy_rootfs.md index 79deabae2a42..fc5b6d9240ca 100644 --- a/docs/docs/references/configuration/cli/trivy_rootfs.md +++ b/docs/docs/references/configuration/cli/trivy_rootfs.md @@ -53,7 +53,7 @@ trivy rootfs [flags] ROOTDIR --license-confidence-level float specify license classifier's confidence level (default 0.9) --license-full eagerly look for licenses in source code headers and license files --list-all-pkgs enabling the option will output all packages regardless of vulnerability - --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan]) + --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) --module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules") --no-progress suppress progress bar --offline-scan do not issue API requests to identify dependencies diff --git a/docs/docs/references/configuration/cli/trivy_vm.md b/docs/docs/references/configuration/cli/trivy_vm.md index 2ac579ca53bc..282c96ea3b11 100644 --- a/docs/docs/references/configuration/cli/trivy_vm.md +++ b/docs/docs/references/configuration/cli/trivy_vm.md @@ -47,7 +47,7 @@ trivy vm [flags] VM_IMAGE --include-non-failures include successes and exceptions, available with '--scanners misconfig' --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db") --list-all-pkgs enabling the option will output all packages regardless of vulnerability - --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan]) + --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) --module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules") --no-progress suppress progress bar --offline-scan do not issue API requests to identify dependencies diff --git a/docs/docs/scanner/misconfiguration/custom/index.md b/docs/docs/scanner/misconfiguration/custom/index.md index ef34d0f20414..b1c16219e8f8 100644 --- a/docs/docs/scanner/misconfiguration/custom/index.md +++ b/docs/docs/scanner/misconfiguration/custom/index.md @@ -27,7 +27,7 @@ In the above general file formats, Trivy automatically identifies the following - CloudFormation (JSON/YAML) - Kubernetes (JSON/YAML) - Helm (YAML) -- Terraform Plan (JSON) +- Terraform Plan (JSON/Snapshot) This is useful for filtering inputs, as described below. diff --git a/docs/tutorials/misconfiguration/terraform.md b/docs/tutorials/misconfiguration/terraform.md index 53ad9ec9e755..8240e1ba53b2 100644 --- a/docs/tutorials/misconfiguration/terraform.md +++ b/docs/tutorials/misconfiguration/terraform.md @@ -104,22 +104,7 @@ The `trivy config` command is a sub-command of the `trivy fs` command. You can l ## Scanning Terraform Plan files -Instead of scanning your different Terraform resources individually, you could also scan your terraform plan output before it is deployed for misconfiguration. This will give you insights into any misconfiguration of your resources as they would become deployed. [Here](https://aquasecurity.github.io/trivy/latest/docs/scanner/misconfiguration/custom/examples/#terraform-plan) is the link to the documentation. - -First, create a terraform plan and save it to a file: -``` -terraform plan --out tfplan.binary -``` - -Next, convert the file into json format: -``` -terraform show -json tfplan.binary > tfplan.json -``` - -Lastly, scan the file with the `trivy config` command: -``` -trivy config ./tfplan.json -``` +Instead of scanning your different Terraform resources individually, you could also scan your Terraform Plan file before it is deployed for misconfiguration. This will give you insights into any misconfiguration of your resources as they would become deployed. [Here](https://aquasecurity.github.io/trivy/latest/docs/coverage/iac/terraform/#terraform) is the link to the documentation. Note that you need to be able to create a terraform init and plan without any errors. diff --git a/go.mod b/go.mod index 5bada118dd48..539c723bddf5 100644 --- a/go.mod +++ b/go.mod @@ -42,7 +42,7 @@ require ( github.com/csaf-poc/csaf_distribution/v3 v3.0.0 github.com/docker/docker v25.0.3+incompatible github.com/docker/go-connections v0.5.0 - github.com/fatih/color v1.15.0 + github.com/fatih/color v1.16.0 github.com/go-git/go-git/v5 v5.11.0 github.com/go-openapi/runtime v0.27.1 github.com/go-openapi/strfmt v0.22.0 @@ -125,7 +125,10 @@ require ( github.com/apparentlymart/go-cidr v1.1.0 github.com/aws/smithy-go v1.20.1 github.com/hashicorp/go-uuid v1.0.3 + github.com/hashicorp/go-version v1.6.0 + github.com/hashicorp/hc-install v0.6.3 github.com/hashicorp/hcl/v2 v2.19.1 + github.com/hashicorp/terraform-exec v0.20.0 github.com/liamg/iamgo v0.0.9 github.com/liamg/memoryfs v1.6.0 github.com/mitchellh/go-homedir v1.1.0 @@ -294,9 +297,9 @@ require ( github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect - github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/golang-lru v0.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/hashicorp/terraform-json v0.19.0 // indirect github.com/huandu/xstrings v1.4.0 // indirect github.com/imdario/mergo v0.3.15 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -315,7 +318,7 @@ require ( github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect diff --git a/go.sum b/go.sum index 2f2e06853135..ba486cb50204 100644 --- a/go.sum +++ b/go.sum @@ -772,8 +772,8 @@ github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZM github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= @@ -1112,6 +1112,8 @@ github.com/hashicorp/golang-lru v0.6.0 h1:uL2shRDx7RTrOrTCUZEGP/wJUFiUI8QT6E7z5o github.com/hashicorp/golang-lru v0.6.0/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru/v2 v2.0.6 h1:3xi/Cafd1NaoEnS/yDssIiuVeDVywU0QdFGl3aQaQHM= github.com/hashicorp/golang-lru/v2 v2.0.6/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/hc-install v0.6.3 h1:yE/r1yJvWbtrJ0STwScgEnCanb0U9v7zp0Gbkmcoxqs= +github.com/hashicorp/hc-install v0.6.3/go.mod h1:KamGdbodYzlufbWh4r9NRo8y6GLHWZP2GBtdnms1Ln0= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl/v2 v2.19.1 h1://i05Jqznmb2EXqa39Nsvyan2o5XyMowW5fnCKW5RPI= @@ -1120,6 +1122,10 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/terraform-exec v0.20.0 h1:DIZnPsqzPGuUnq6cH8jWcPunBfY+C+M8JyYF3vpnuEo= +github.com/hashicorp/terraform-exec v0.20.0/go.mod h1:ckKGkJWbsNqFKV1itgMnE0hY9IYf1HoiekpuN0eWoDw= +github.com/hashicorp/terraform-json v0.19.0 h1:e9DBKC5sxDfiJT7Zoi+yRIwqLVtFur/fwK/FuE6AWsA= +github.com/hashicorp/terraform-json v0.19.0/go.mod h1:qdeBs11ovMzo5puhrRibdD6d2Dq6TyE/28JiU4tIQxk= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= @@ -1269,8 +1275,8 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= diff --git a/integration/aws_cloud_test.go b/integration/aws_cloud_test.go index 3b1ca9568fab..9a8cb8781474 100644 --- a/integration/aws_cloud_test.go +++ b/integration/aws_cloud_test.go @@ -4,16 +4,13 @@ package integration import ( "context" - "fmt" "testing" "time" - dockercontainer "github.com/docker/docker/api/types/container" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/testcontainers/testcontainers-go" - "github.com/testcontainers/testcontainers-go/modules/localstack" + "github.com/aquasecurity/trivy/internal/testutil" awscommands "github.com/aquasecurity/trivy/pkg/cloud/aws/commands" "github.com/aquasecurity/trivy/pkg/flag" ) @@ -53,7 +50,8 @@ func TestAwsCommandRun(t *testing.T) { ctx := context.Background() - localstackC, addr := setupLocalStack(t, ctx) + localstackC, addr, err := testutil.SetupLocalStack(ctx, "2.2.0") + require.NoError(t, err) defer localstackC.Terminate(ctx) for _, tt := range tests { @@ -78,32 +76,3 @@ func TestAwsCommandRun(t *testing.T) { } } - -func setupLocalStack(t *testing.T, ctx context.Context) (*localstack.LocalStackContainer, string) { - t.Helper() - t.Setenv("TESTCONTAINERS_RYUK_DISABLED", "true") - container, err := localstack.RunContainer(ctx, testcontainers.CustomizeRequest( - testcontainers.GenericContainerRequest{ - ContainerRequest: testcontainers.ContainerRequest{ - Image: "localstack/localstack:2.2.0", - HostConfigModifier: func(hostConfig *dockercontainer.HostConfig) { - hostConfig.AutoRemove = true - }, - }, - }, - )) - require.NoError(t, err) - - p, err := container.MappedPort(ctx, "4566/tcp") - require.NoError(t, err) - - provider, err := testcontainers.NewDockerProvider() - require.NoError(t, err) - defer provider.Close() - - host, err := provider.DaemonHost(ctx) - require.NoError(t, err) - - return container, fmt.Sprintf("http://%s:%d", host, p.Int()) - -} diff --git a/internal/testutil/localstack.go b/internal/testutil/localstack.go new file mode 100644 index 000000000000..71eaf5a3fcf9 --- /dev/null +++ b/internal/testutil/localstack.go @@ -0,0 +1,51 @@ +package testutil + +import ( + "context" + "fmt" + "os" + + dockercontainer "github.com/docker/docker/api/types/container" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/localstack" +) + +func SetupLocalStack(ctx context.Context, version string) (*localstack.LocalStackContainer, string, error) { + + if err := os.Setenv("TESTCONTAINERS_RYUK_DISABLED", "true"); err != nil { + return nil, "", err + } + + container, err := localstack.RunContainer(ctx, testcontainers.CustomizeRequest( + testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ + Image: "localstack/localstack:" + version, + HostConfigModifier: func(hostConfig *dockercontainer.HostConfig) { + hostConfig.AutoRemove = true + }, + }, + }, + )) + if err != nil { + return nil, "", err + } + + p, err := container.MappedPort(ctx, "4566/tcp") + if err != nil { + return nil, "", err + } + + provider, err := testcontainers.NewDockerProvider() + if err != nil { + return nil, "", err + } + defer provider.Close() + + host, err := provider.DaemonHost(ctx) + if err != nil { + return nil, "", err + } + + return container, fmt.Sprintf("http://%s:%d", host, p.Int()), nil + +} diff --git a/magefiles/magefile.go b/magefiles/magefile.go index cafd8a045c47..f3024c99d7c4 100644 --- a/magefiles/magefile.go +++ b/magefiles/magefile.go @@ -1,6 +1,7 @@ package main import ( + "context" "errors" "fmt" "io/fs" @@ -181,6 +182,11 @@ func (Test) FixtureVMImages() error { return fixtureVMImages() } +// FixtureTerraformPlanSnapshots generates Terraform Plan files in test folders +func (Test) FixtureTerraformPlanSnapshots() error { + return fixtureTerraformPlanSnapshots(context.TODO()) +} + // GenerateModules compiles WASM modules for unit tests func (Test) GenerateModules() error { pattern := filepath.Join("pkg", "module", "testdata", "*", "*.go") diff --git a/magefiles/terraformplan.go b/magefiles/terraformplan.go new file mode 100644 index 000000000000..de3d9016b6ed --- /dev/null +++ b/magefiles/terraformplan.go @@ -0,0 +1,140 @@ +package main + +import ( + "context" + "errors" + "fmt" + "log" + "os" + "path/filepath" + "strings" + + hversion "github.com/hashicorp/go-version" //nolint:gomodguard // hc-install uses hashicorp/go-version + "github.com/hashicorp/hc-install/product" + "github.com/hashicorp/hc-install/releases" + "github.com/hashicorp/terraform-exec/tfexec" + "golang.org/x/sync/errgroup" + + "github.com/aquasecurity/trivy/internal/testutil" +) + +const ( + terraformVersion = "1.7.3" + terraformParallelLimit = 5 + + tfplanFile = "tfplan" +) + +func fixtureTerraformPlanSnapshots(ctx context.Context) error { + localstackC, addr, err := testutil.SetupLocalStack(ctx, "3.1.0") + if err != nil { + return err + } + defer localstackC.Terminate(ctx) + + envs := []struct { + key string + val string + }{ + {"AWS_DEFAULT_REGION", "us-east-1"}, + {"AWS_ACCESS_KEY_ID", "test"}, + {"AWS_SECRET_ACCESS_KEY", "test"}, + {"AWS_ENDPOINT_URL", addr}, + } + + for _, env := range envs { + if err := os.Setenv(env.key, env.val); err != nil { + return err + } + } + + dirs := []string{ + "pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots", + "pkg/iac/scanners/terraformplan/snapshot/testdata", + } + + var workingDirs []string + + for _, dir := range dirs { + entries, err := os.ReadDir(filepath.FromSlash(dir)) + if err != nil { + return err + } + + for _, entry := range entries { + workingDirs = append(workingDirs, filepath.Join(dir, entry.Name())) + } + } + + installer := &releases.ExactVersion{ + Product: product.Terraform, + Version: hversion.Must(hversion.NewVersion(terraformVersion)), + } + + execPath, err := installer.Install(ctx) + if err != nil { + return fmt.Errorf("failed to install Terraform: %w", err) + } + + g, ctx := errgroup.WithContext(ctx) + g.SetLimit(terraformParallelLimit) + + for _, workingDir := range workingDirs { + workingDir := workingDir + g.Go(func() error { + if err := os.Remove(tfplanFile); err != nil && !errors.Is(err, os.ErrNotExist) { + return err + } + + if err := generatePlan(ctx, execPath, workingDir); err != nil { + return fmt.Errorf("failed to generate Terraform Plan: %w", err) + } + + return nil + }) + } + + return g.Wait() +} + +func generatePlan(ctx context.Context, execPath, workingDir string) error { + if err := cleanup(workingDir); err != nil { + return err + } + defer cleanup(workingDir) + + tf, err := tfexec.NewTerraform(workingDir, execPath) + if err != nil { + return fmt.Errorf("failed to run Terraform: %w", err) + } + + prefix := fmt.Sprintf("tfplan:%s:", filepath.Base(workingDir)) + tf.SetLogger(log.New(os.Stdout, prefix, log.LstdFlags)) + + if err = tf.Init(ctx, tfexec.Upgrade(true)); err != nil { + return fmt.Errorf("failed to run Init cmd: %w", err) + } + + if _, err := tf.Plan(ctx, tfexec.Out(tfplanFile)); err != nil { + return fmt.Errorf("failed to run Plan cmd: %w", err) + } + + return nil +} + +func cleanup(workingDir string) error { + entries, err := os.ReadDir(workingDir) + if err != nil { + return err + } + + for _, entry := range entries { + if entry.Name() == "terraform.tfstate" || strings.HasPrefix(entry.Name(), ".terraform") { + path := filepath.Join(workingDir, entry.Name()) + if err := os.RemoveAll(path); err != nil && !errors.Is(err, os.ErrNotExist) { + return err + } + } + } + return nil +} diff --git a/pkg/fanal/analyzer/config/all/import.go b/pkg/fanal/analyzer/config/all/import.go index c66de2fa6d89..b171ab5e8a7f 100644 --- a/pkg/fanal/analyzer/config/all/import.go +++ b/pkg/fanal/analyzer/config/all/import.go @@ -7,5 +7,6 @@ import ( _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/helm" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/k8s" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/terraform" - _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/terraformplan" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/terraformplan/json" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/terraformplan/snapshot" ) diff --git a/pkg/fanal/analyzer/config/terraformplan/terraformplan.go b/pkg/fanal/analyzer/config/terraformplan/json/json.go similarity index 70% rename from pkg/fanal/analyzer/config/terraformplan/terraformplan.go rename to pkg/fanal/analyzer/config/terraformplan/json/json.go index a9d32dd6f627..5272f0f990f9 100644 --- a/pkg/fanal/analyzer/config/terraformplan/terraformplan.go +++ b/pkg/fanal/analyzer/config/terraformplan/json/json.go @@ -12,7 +12,7 @@ import ( ) const ( - analyzerType = analyzer.TypeTerraformPlan + analyzerType = analyzer.TypeTerraformPlanJSON version = 1 ) @@ -21,24 +21,24 @@ var requiredExts = []string{ } func init() { - analyzer.RegisterPostAnalyzer(analyzerType, newTerraformPlanConfigAnalyzer) + analyzer.RegisterPostAnalyzer(analyzerType, newTerraformPlanJSONConfigAnalyzer) } -// terraformPlanConfigAnalyzer is an analyzer for detecting misconfigurations in Terraform files. +// terraformPlanConfigAnalyzer is an analyzer for detecting misconfigurations in Terraform Plan files in JSON format. // It embeds config.Analyzer so it can implement analyzer.PostAnalyzer. type terraformPlanConfigAnalyzer struct { *config.Analyzer } -func newTerraformPlanConfigAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { - a, err := config.NewAnalyzer(analyzerType, version, misconf.NewTerraformPlanScanner, opts) +func newTerraformPlanJSONConfigAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + a, err := config.NewAnalyzer(analyzerType, version, misconf.NewTerraformPlanJSONScanner, opts) if err != nil { return nil, err } return &terraformPlanConfigAnalyzer{Analyzer: a}, nil } -// Required overrides config.Analyzer.Required() and checks if the given file is a Terraform file. +// Required overrides config.Analyzer.Required() and checks if the given file is a Terraform Plan file in JSON format. func (*terraformPlanConfigAnalyzer) Required(filePath string, _ os.FileInfo) bool { return slices.Contains(requiredExts, filepath.Ext(filePath)) } diff --git a/pkg/fanal/analyzer/config/terraformplan/terraformplan_test.go b/pkg/fanal/analyzer/config/terraformplan/json/json_test.go similarity index 100% rename from pkg/fanal/analyzer/config/terraformplan/terraformplan_test.go rename to pkg/fanal/analyzer/config/terraformplan/json/json_test.go diff --git a/pkg/fanal/analyzer/config/terraformplan/snapshot/snapshot.go b/pkg/fanal/analyzer/config/terraformplan/snapshot/snapshot.go new file mode 100644 index 000000000000..0597c137d96c --- /dev/null +++ b/pkg/fanal/analyzer/config/terraformplan/snapshot/snapshot.go @@ -0,0 +1,37 @@ +package terraformplan + +import ( + "os" + "path/filepath" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" + "github.com/aquasecurity/trivy/pkg/misconf" +) + +const ( + analyzerType = analyzer.TypeTerraformPlanSnapshot + version = 1 +) + +func init() { + analyzer.RegisterPostAnalyzer(analyzerType, newTerraformPlanSnapshotConfigAnalyzer) +} + +// terraformPlanConfigAnalyzer is an analyzer for detecting misconfigurations in Terraform Plan files in snapshot format. +// It embeds config.Analyzer so it can implement analyzer.PostAnalyzer. +type terraformPlanConfigAnalyzer struct { + *config.Analyzer +} + +func newTerraformPlanSnapshotConfigAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + a, err := config.NewAnalyzer(analyzerType, version, misconf.NewTerraformPlanSnapshotScanner, opts) + if err != nil { + return nil, err + } + return &terraformPlanConfigAnalyzer{Analyzer: a}, nil +} + +func (*terraformPlanConfigAnalyzer) Required(filePath string, fi os.FileInfo) bool { + return filepath.Ext(filePath) == ".tfplan" || filepath.Base(filePath) == "tfplan" +} diff --git a/pkg/fanal/analyzer/config/terraformplan/snapshot/snapshot_test.go b/pkg/fanal/analyzer/config/terraformplan/snapshot/snapshot_test.go new file mode 100644 index 000000000000..22bd69e5f357 --- /dev/null +++ b/pkg/fanal/analyzer/config/terraformplan/snapshot/snapshot_test.go @@ -0,0 +1,38 @@ +package terraformplan + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestConfigAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "tfplan as extension", + filePath: "/path/to/test.tfplan", + want: true, + }, + { + name: "without extension", + filePath: "/path/to/tfplan", + want: true, + }, + { + name: "bad path", + filePath: "/path/to/mytfplan.txt", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := terraformPlanConfigAnalyzer{} + got := a.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/const.go b/pkg/fanal/analyzer/const.go index 93ad337ec83d..29ed8027118f 100644 --- a/pkg/fanal/analyzer/const.go +++ b/pkg/fanal/analyzer/const.go @@ -109,13 +109,14 @@ const ( // ================= // Structured Config // ================= - TypeAzureARM Type = Type(detection.FileTypeAzureARM) - TypeCloudFormation Type = Type(detection.FileTypeCloudFormation) - TypeDockerfile Type = Type(detection.FileTypeDockerfile) - TypeHelm Type = Type(detection.FileTypeHelm) - TypeKubernetes Type = Type(detection.FileTypeKubernetes) - TypeTerraform Type = Type(detection.FileTypeTerraform) - TypeTerraformPlan Type = Type(detection.FileTypeTerraformPlan) + TypeAzureARM Type = Type(detection.FileTypeAzureARM) + TypeCloudFormation Type = Type(detection.FileTypeCloudFormation) + TypeDockerfile Type = Type(detection.FileTypeDockerfile) + TypeHelm Type = Type(detection.FileTypeHelm) + TypeKubernetes Type = Type(detection.FileTypeKubernetes) + TypeTerraform Type = Type(detection.FileTypeTerraform) + TypeTerraformPlanJSON Type = Type(detection.FileTypeTerraformPlanJSON) + TypeTerraformPlanSnapshot Type = Type(detection.FileTypeTerraformPlanSnapshot) // ======== // License @@ -228,6 +229,7 @@ var ( TypeHelm, TypeKubernetes, TypeTerraform, - TypeTerraformPlan, + TypeTerraformPlanJSON, + TypeTerraformPlanSnapshot, } ) diff --git a/pkg/fanal/artifact/image/image_test.go b/pkg/fanal/artifact/image/image_test.go index d65df55f2fc5..a99823dde2c0 100644 --- a/pkg/fanal/artifact/image/image_test.go +++ b/pkg/fanal/artifact/image/image_test.go @@ -352,17 +352,17 @@ func TestArtifact_Inspect(t *testing.T) { missingBlobsExpectation: cache.ArtifactCacheMissingBlobsExpectation{ Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", - BlobIDs: []string{"sha256:0366f81c3cbeac047536f9ca133b5632544c8342544922c3ed8a3e51c0250897"}, + BlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ MissingArtifact: true, - MissingBlobIDs: []string{"sha256:0366f81c3cbeac047536f9ca133b5632544c8342544922c3ed8a3e51c0250897"}, + MissingBlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, }, }, putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:0366f81c3cbeac047536f9ca133b5632544c8342544922c3ed8a3e51c0250897", + BlobID: "sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -429,7 +429,7 @@ func TestArtifact_Inspect(t *testing.T) { Name: "../../test/testdata/alpine-311.tar.gz", Type: types.ArtifactContainerImage, ID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", - BlobIDs: []string{"sha256:0366f81c3cbeac047536f9ca133b5632544c8342544922c3ed8a3e51c0250897"}, + BlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, ImageMetadata: types.ImageMetadata{ ID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", DiffIDs: []string{ @@ -488,25 +488,25 @@ func TestArtifact_Inspect(t *testing.T) { Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", BlobIDs: []string{ - "sha256:db2b5b2f26f320b80ee549495888f111151fdb44fecb2c432dfd928e96978e74", - "sha256:718d6a3d0a24966f2d9f64e0c86695d1bb896fe81df634b85300d34142c80174", - "sha256:9f6bc14198fa983fd47079bcd82df1391bee8e529a8a131a0305874eb0a51ca0", - "sha256:44264715b7132cd14cd956bf385d5f875603461f5adeafde7fdecabe2e266291", + "sha256:dd0a4f4754bf4590327be34f4266f63c92184352afadb72e4c9b162f76224000", + "sha256:f9e6a3065bb47f810916e90249076950a4b70785a27d3bcb90406d0ab342fa67", + "sha256:b6be0de11c6090f71dea119f43dd360335643420058e317baffb089f0dff4001", + "sha256:37c561c19b169f5f9832f4b0060bf74ebc8d1c9e01662ad4fa21c394da159440", }, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ MissingBlobIDs: []string{ - "sha256:db2b5b2f26f320b80ee549495888f111151fdb44fecb2c432dfd928e96978e74", - "sha256:718d6a3d0a24966f2d9f64e0c86695d1bb896fe81df634b85300d34142c80174", - "sha256:9f6bc14198fa983fd47079bcd82df1391bee8e529a8a131a0305874eb0a51ca0", - "sha256:44264715b7132cd14cd956bf385d5f875603461f5adeafde7fdecabe2e266291", + "sha256:dd0a4f4754bf4590327be34f4266f63c92184352afadb72e4c9b162f76224000", + "sha256:f9e6a3065bb47f810916e90249076950a4b70785a27d3bcb90406d0ab342fa67", + "sha256:b6be0de11c6090f71dea119f43dd360335643420058e317baffb089f0dff4001", + "sha256:37c561c19b169f5f9832f4b0060bf74ebc8d1c9e01662ad4fa21c394da159440", }, }, }, putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:db2b5b2f26f320b80ee549495888f111151fdb44fecb2c432dfd928e96978e74", + BlobID: "sha256:dd0a4f4754bf4590327be34f4266f63c92184352afadb72e4c9b162f76224000", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -594,7 +594,7 @@ func TestArtifact_Inspect(t *testing.T) { }, { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:718d6a3d0a24966f2d9f64e0c86695d1bb896fe81df634b85300d34142c80174", + BlobID: "sha256:f9e6a3065bb47f810916e90249076950a4b70785a27d3bcb90406d0ab342fa67", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -690,7 +690,7 @@ func TestArtifact_Inspect(t *testing.T) { }, { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:9f6bc14198fa983fd47079bcd82df1391bee8e529a8a131a0305874eb0a51ca0", + BlobID: "sha256:b6be0de11c6090f71dea119f43dd360335643420058e317baffb089f0dff4001", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -898,7 +898,7 @@ func TestArtifact_Inspect(t *testing.T) { }, { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:44264715b7132cd14cd956bf385d5f875603461f5adeafde7fdecabe2e266291", + BlobID: "sha256:37c561c19b169f5f9832f4b0060bf74ebc8d1c9e01662ad4fa21c394da159440", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -1707,10 +1707,10 @@ func TestArtifact_Inspect(t *testing.T) { Type: types.ArtifactContainerImage, ID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", BlobIDs: []string{ - "sha256:db2b5b2f26f320b80ee549495888f111151fdb44fecb2c432dfd928e96978e74", - "sha256:718d6a3d0a24966f2d9f64e0c86695d1bb896fe81df634b85300d34142c80174", - "sha256:9f6bc14198fa983fd47079bcd82df1391bee8e529a8a131a0305874eb0a51ca0", - "sha256:44264715b7132cd14cd956bf385d5f875603461f5adeafde7fdecabe2e266291", + "sha256:dd0a4f4754bf4590327be34f4266f63c92184352afadb72e4c9b162f76224000", + "sha256:f9e6a3065bb47f810916e90249076950a4b70785a27d3bcb90406d0ab342fa67", + "sha256:b6be0de11c6090f71dea119f43dd360335643420058e317baffb089f0dff4001", + "sha256:37c561c19b169f5f9832f4b0060bf74ebc8d1c9e01662ad4fa21c394da159440", }, ImageMetadata: types.ImageMetadata{ ID: "sha256:58701fd185bda36cab0557bb6438661831267aa4a9e0b54211c4d5317a48aff4", @@ -1804,25 +1804,25 @@ func TestArtifact_Inspect(t *testing.T) { Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", BlobIDs: []string{ - "sha256:dc2c102de2a4dc82bc017564a2c1a46646f071367bd51d3ddd80171dc5a78ffe", - "sha256:e6bc45e7459162f5a27dd7bd310a420f27378f2ea24c6ed5f0a083f2377bde24", - "sha256:8184bee7b1985e0abd72288946c246b90e1847b36fdcd50ca3d588b9371ec920", - "sha256:f1bb0ccf476528c6165a2f1a74d0e5a0cb06c9b254a0def6b1183db662e83dd4", + "sha256:e1187118cdbe8893fc2fd4b345f813d195ee6aaeb4820d4576694199f8c10350", + "sha256:12c266a627dc4014c3ee96936058ba98209056f4ffe0081bb5fca7ff91592cdb", + "sha256:47adac0e28b12338e99dedbd7e8b0ef1f7aaa28e646f637ab2db8908b80704c8", + "sha256:dd1082b33b17401fdc31bcbf60eaaecb9ce29e23956c50db6f34b2cc6cfa13c8", }, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ MissingBlobIDs: []string{ - "sha256:dc2c102de2a4dc82bc017564a2c1a46646f071367bd51d3ddd80171dc5a78ffe", - "sha256:e6bc45e7459162f5a27dd7bd310a420f27378f2ea24c6ed5f0a083f2377bde24", - "sha256:8184bee7b1985e0abd72288946c246b90e1847b36fdcd50ca3d588b9371ec920", - "sha256:f1bb0ccf476528c6165a2f1a74d0e5a0cb06c9b254a0def6b1183db662e83dd4", + "sha256:e1187118cdbe8893fc2fd4b345f813d195ee6aaeb4820d4576694199f8c10350", + "sha256:12c266a627dc4014c3ee96936058ba98209056f4ffe0081bb5fca7ff91592cdb", + "sha256:47adac0e28b12338e99dedbd7e8b0ef1f7aaa28e646f637ab2db8908b80704c8", + "sha256:dd1082b33b17401fdc31bcbf60eaaecb9ce29e23956c50db6f34b2cc6cfa13c8", }, }, }, putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:dc2c102de2a4dc82bc017564a2c1a46646f071367bd51d3ddd80171dc5a78ffe", + BlobID: "sha256:e1187118cdbe8893fc2fd4b345f813d195ee6aaeb4820d4576694199f8c10350", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -1833,7 +1833,7 @@ func TestArtifact_Inspect(t *testing.T) { }, { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:e6bc45e7459162f5a27dd7bd310a420f27378f2ea24c6ed5f0a083f2377bde24", + BlobID: "sha256:12c266a627dc4014c3ee96936058ba98209056f4ffe0081bb5fca7ff91592cdb", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -1844,7 +1844,7 @@ func TestArtifact_Inspect(t *testing.T) { }, { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:8184bee7b1985e0abd72288946c246b90e1847b36fdcd50ca3d588b9371ec920", + BlobID: "sha256:47adac0e28b12338e99dedbd7e8b0ef1f7aaa28e646f637ab2db8908b80704c8", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -1856,7 +1856,7 @@ func TestArtifact_Inspect(t *testing.T) { }, { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:f1bb0ccf476528c6165a2f1a74d0e5a0cb06c9b254a0def6b1183db662e83dd4", + BlobID: "sha256:dd1082b33b17401fdc31bcbf60eaaecb9ce29e23956c50db6f34b2cc6cfa13c8", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -1872,10 +1872,10 @@ func TestArtifact_Inspect(t *testing.T) { Type: types.ArtifactContainerImage, ID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", BlobIDs: []string{ - "sha256:dc2c102de2a4dc82bc017564a2c1a46646f071367bd51d3ddd80171dc5a78ffe", - "sha256:e6bc45e7459162f5a27dd7bd310a420f27378f2ea24c6ed5f0a083f2377bde24", - "sha256:8184bee7b1985e0abd72288946c246b90e1847b36fdcd50ca3d588b9371ec920", - "sha256:f1bb0ccf476528c6165a2f1a74d0e5a0cb06c9b254a0def6b1183db662e83dd4", + "sha256:e1187118cdbe8893fc2fd4b345f813d195ee6aaeb4820d4576694199f8c10350", + "sha256:12c266a627dc4014c3ee96936058ba98209056f4ffe0081bb5fca7ff91592cdb", + "sha256:47adac0e28b12338e99dedbd7e8b0ef1f7aaa28e646f637ab2db8908b80704c8", + "sha256:dd1082b33b17401fdc31bcbf60eaaecb9ce29e23956c50db6f34b2cc6cfa13c8", }, ImageMetadata: types.ImageMetadata{ ID: "sha256:58701fd185bda36cab0557bb6438661831267aa4a9e0b54211c4d5317a48aff4", @@ -1958,7 +1958,7 @@ func TestArtifact_Inspect(t *testing.T) { missingBlobsExpectation: cache.ArtifactCacheMissingBlobsExpectation{ Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", - BlobIDs: []string{"sha256:0366f81c3cbeac047536f9ca133b5632544c8342544922c3ed8a3e51c0250897"}, + BlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ Err: xerrors.New("MissingBlobs failed"), @@ -1972,16 +1972,16 @@ func TestArtifact_Inspect(t *testing.T) { missingBlobsExpectation: cache.ArtifactCacheMissingBlobsExpectation{ Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", - BlobIDs: []string{"sha256:0366f81c3cbeac047536f9ca133b5632544c8342544922c3ed8a3e51c0250897"}, + BlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ - MissingBlobIDs: []string{"sha256:0366f81c3cbeac047536f9ca133b5632544c8342544922c3ed8a3e51c0250897"}, + MissingBlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, }, }, putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:0366f81c3cbeac047536f9ca133b5632544c8342544922c3ed8a3e51c0250897", + BlobID: "sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -2041,18 +2041,18 @@ func TestArtifact_Inspect(t *testing.T) { Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", BlobIDs: []string{ - "sha256:db2b5b2f26f320b80ee549495888f111151fdb44fecb2c432dfd928e96978e74", - "sha256:718d6a3d0a24966f2d9f64e0c86695d1bb896fe81df634b85300d34142c80174", - "sha256:9f6bc14198fa983fd47079bcd82df1391bee8e529a8a131a0305874eb0a51ca0", - "sha256:44264715b7132cd14cd956bf385d5f875603461f5adeafde7fdecabe2e266291", + "sha256:dd0a4f4754bf4590327be34f4266f63c92184352afadb72e4c9b162f76224000", + "sha256:f9e6a3065bb47f810916e90249076950a4b70785a27d3bcb90406d0ab342fa67", + "sha256:b6be0de11c6090f71dea119f43dd360335643420058e317baffb089f0dff4001", + "sha256:37c561c19b169f5f9832f4b0060bf74ebc8d1c9e01662ad4fa21c394da159440", }, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ MissingBlobIDs: []string{ - "sha256:db2b5b2f26f320b80ee549495888f111151fdb44fecb2c432dfd928e96978e74", - "sha256:718d6a3d0a24966f2d9f64e0c86695d1bb896fe81df634b85300d34142c80174", - "sha256:9f6bc14198fa983fd47079bcd82df1391bee8e529a8a131a0305874eb0a51ca0", - "sha256:44264715b7132cd14cd956bf385d5f875603461f5adeafde7fdecabe2e266291", + "sha256:dd0a4f4754bf4590327be34f4266f63c92184352afadb72e4c9b162f76224000", + "sha256:f9e6a3065bb47f810916e90249076950a4b70785a27d3bcb90406d0ab342fa67", + "sha256:b6be0de11c6090f71dea119f43dd360335643420058e317baffb089f0dff4001", + "sha256:37c561c19b169f5f9832f4b0060bf74ebc8d1c9e01662ad4fa21c394da159440", }, }, }, @@ -2060,7 +2060,7 @@ func TestArtifact_Inspect(t *testing.T) { { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:db2b5b2f26f320b80ee549495888f111151fdb44fecb2c432dfd928e96978e74", + BlobID: "sha256:dd0a4f4754bf4590327be34f4266f63c92184352afadb72e4c9b162f76224000", BlobInfoAnything: true, }, @@ -2071,7 +2071,7 @@ func TestArtifact_Inspect(t *testing.T) { { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:718d6a3d0a24966f2d9f64e0c86695d1bb896fe81df634b85300d34142c80174", + BlobID: "sha256:f9e6a3065bb47f810916e90249076950a4b70785a27d3bcb90406d0ab342fa67", BlobInfoAnything: true, }, @@ -2082,7 +2082,7 @@ func TestArtifact_Inspect(t *testing.T) { { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:9f6bc14198fa983fd47079bcd82df1391bee8e529a8a131a0305874eb0a51ca0", + BlobID: "sha256:b6be0de11c6090f71dea119f43dd360335643420058e317baffb089f0dff4001", BlobInfoAnything: true, }, @@ -2093,7 +2093,7 @@ func TestArtifact_Inspect(t *testing.T) { { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:44264715b7132cd14cd956bf385d5f875603461f5adeafde7fdecabe2e266291", + BlobID: "sha256:37c561c19b169f5f9832f4b0060bf74ebc8d1c9e01662ad4fa21c394da159440", BlobInfoAnything: true, }, @@ -2110,17 +2110,17 @@ func TestArtifact_Inspect(t *testing.T) { missingBlobsExpectation: cache.ArtifactCacheMissingBlobsExpectation{ Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", - BlobIDs: []string{"sha256:0366f81c3cbeac047536f9ca133b5632544c8342544922c3ed8a3e51c0250897"}, + BlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ MissingArtifact: true, - MissingBlobIDs: []string{"sha256:0366f81c3cbeac047536f9ca133b5632544c8342544922c3ed8a3e51c0250897"}, + MissingBlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, }, }, putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:0366f81c3cbeac047536f9ca133b5632544c8342544922c3ed8a3e51c0250897", + BlobID: "sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", diff --git a/pkg/fanal/artifact/local/fs_test.go b/pkg/fanal/artifact/local/fs_test.go index 200aa970bfb9..bbaf684552ae 100644 --- a/pkg/fanal/artifact/local/fs_test.go +++ b/pkg/fanal/artifact/local/fs_test.go @@ -3,6 +3,7 @@ package local import ( "context" "errors" + "os" "path/filepath" "runtime" "testing" @@ -47,7 +48,7 @@ func TestArtifact_Inspect(t *testing.T) { }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:ff28bff7756fb32d0a060b3b474b31a781a2d365dcd2789f47b4ae556a34947e", + BlobID: "sha256:afc2bc421aac8c61d89d4dd1c1865efb5441e3877c8a4c919232729d7c574dab", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, OS: types.OS{ @@ -82,9 +83,9 @@ func TestArtifact_Inspect(t *testing.T) { want: types.ArtifactReference{ Name: "host", Type: types.ArtifactFilesystem, - ID: "sha256:ff28bff7756fb32d0a060b3b474b31a781a2d365dcd2789f47b4ae556a34947e", + ID: "sha256:afc2bc421aac8c61d89d4dd1c1865efb5441e3877c8a4c919232729d7c574dab", BlobIDs: []string{ - "sha256:ff28bff7756fb32d0a060b3b474b31a781a2d365dcd2789f47b4ae556a34947e", + "sha256:afc2bc421aac8c61d89d4dd1c1865efb5441e3877c8a4c919232729d7c574dab", }, }, }, @@ -102,7 +103,7 @@ func TestArtifact_Inspect(t *testing.T) { }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:d5fa75cdac006582a8f6bc4e3fcc8bfb70bd9d0403c24d8c2e3230d3f38a7ff5", + BlobID: "sha256:7db98974b2231d3e25f4890008c4d42f6f26a7da5a8aba99e954dec97f050bd6", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, }, @@ -112,9 +113,9 @@ func TestArtifact_Inspect(t *testing.T) { want: types.ArtifactReference{ Name: "host", Type: types.ArtifactFilesystem, - ID: "sha256:d5fa75cdac006582a8f6bc4e3fcc8bfb70bd9d0403c24d8c2e3230d3f38a7ff5", + ID: "sha256:7db98974b2231d3e25f4890008c4d42f6f26a7da5a8aba99e954dec97f050bd6", BlobIDs: []string{ - "sha256:d5fa75cdac006582a8f6bc4e3fcc8bfb70bd9d0403c24d8c2e3230d3f38a7ff5", + "sha256:7db98974b2231d3e25f4890008c4d42f6f26a7da5a8aba99e954dec97f050bd6", }, }, }, @@ -125,7 +126,7 @@ func TestArtifact_Inspect(t *testing.T) { }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:ff28bff7756fb32d0a060b3b474b31a781a2d365dcd2789f47b4ae556a34947e", + BlobID: "sha256:afc2bc421aac8c61d89d4dd1c1865efb5441e3877c8a4c919232729d7c574dab", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, OS: types.OS{ @@ -175,7 +176,7 @@ func TestArtifact_Inspect(t *testing.T) { }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:09aa251b64e824d0ec71a8c469619e57c9bd91d885f26e4a840de94209acbe4f", + BlobID: "sha256:f1101f37560adf2f5d9c8fef2ac66beff236be3be94b3ea48c0fb6f86867775f", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Applications: []types.Application{ @@ -197,9 +198,9 @@ func TestArtifact_Inspect(t *testing.T) { want: types.ArtifactReference{ Name: "testdata/requirements.txt", Type: types.ArtifactFilesystem, - ID: "sha256:09aa251b64e824d0ec71a8c469619e57c9bd91d885f26e4a840de94209acbe4f", + ID: "sha256:f1101f37560adf2f5d9c8fef2ac66beff236be3be94b3ea48c0fb6f86867775f", BlobIDs: []string{ - "sha256:09aa251b64e824d0ec71a8c469619e57c9bd91d885f26e4a840de94209acbe4f", + "sha256:f1101f37560adf2f5d9c8fef2ac66beff236be3be94b3ea48c0fb6f86867775f", }, }, }, @@ -210,7 +211,7 @@ func TestArtifact_Inspect(t *testing.T) { }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:09aa251b64e824d0ec71a8c469619e57c9bd91d885f26e4a840de94209acbe4f", + BlobID: "sha256:f1101f37560adf2f5d9c8fef2ac66beff236be3be94b3ea48c0fb6f86867775f", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Applications: []types.Application{ @@ -232,9 +233,9 @@ func TestArtifact_Inspect(t *testing.T) { want: types.ArtifactReference{ Name: "testdata/requirements.txt", Type: types.ArtifactFilesystem, - ID: "sha256:09aa251b64e824d0ec71a8c469619e57c9bd91d885f26e4a840de94209acbe4f", + ID: "sha256:f1101f37560adf2f5d9c8fef2ac66beff236be3be94b3ea48c0fb6f86867775f", BlobIDs: []string{ - "sha256:09aa251b64e824d0ec71a8c469619e57c9bd91d885f26e4a840de94209acbe4f", + "sha256:f1101f37560adf2f5d9c8fef2ac66beff236be3be94b3ea48c0fb6f86867775f", }, }, }, @@ -361,7 +362,7 @@ func TestBuildPathsToSkip(t *testing.T) { } } -var policyMetadata = types.PolicyMetadata{ +var terraformPolicyMetadata = types.PolicyMetadata{ ID: "TEST001", AVDID: "AVD-TEST-0001", Type: "Terraform Security Check", @@ -411,7 +412,7 @@ func TestTerraformMisconfigurationScan(t *testing.T) { Namespace: "user.something", Query: "data.user.something.deny", Message: "Empty bucket name!", - PolicyMetadata: policyMetadata, + PolicyMetadata: terraformPolicyMetadata, CauseMetadata: types.CauseMetadata{ Resource: "aws_s3_bucket.asd", Provider: "Generic", @@ -430,9 +431,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { want: types.ArtifactReference{ Name: "testdata/misconfig/terraform/single-failure", Type: types.ArtifactFilesystem, - ID: "sha256:51123b27efc62be0db21fad4ccaf0839850f9f9162d225c6bd9d0e94089b2d8b", + ID: "sha256:1a6ce0acc3b57eb6c830c96fcd868fec1eb4d3b57ad51e481c76d85f22870a65", BlobIDs: []string{ - "sha256:51123b27efc62be0db21fad4ccaf0839850f9f9162d225c6bd9d0e94089b2d8b", + "sha256:1a6ce0acc3b57eb6c830c96fcd868fec1eb4d3b57ad51e481c76d85f22870a65", }, }, }, @@ -464,7 +465,7 @@ func TestTerraformMisconfigurationScan(t *testing.T) { Namespace: "user.something", Query: "data.user.something.deny", Message: "Empty bucket name!", - PolicyMetadata: policyMetadata, + PolicyMetadata: terraformPolicyMetadata, CauseMetadata: types.CauseMetadata{ Resource: "aws_s3_bucket.one", Provider: "Generic", @@ -477,7 +478,7 @@ func TestTerraformMisconfigurationScan(t *testing.T) { Namespace: "user.something", Query: "data.user.something.deny", Message: "Empty bucket name!", - PolicyMetadata: policyMetadata, + PolicyMetadata: terraformPolicyMetadata, CauseMetadata: types.CauseMetadata{ Resource: "aws_s3_bucket.two", Provider: "Generic", @@ -496,7 +497,7 @@ func TestTerraformMisconfigurationScan(t *testing.T) { Namespace: "user.something", Query: "data.user.something.deny", Message: "Empty bucket name!", - PolicyMetadata: policyMetadata, + PolicyMetadata: terraformPolicyMetadata, CauseMetadata: types.CauseMetadata{ Resource: "aws_s3_bucket.three", Provider: "Generic", @@ -515,9 +516,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { want: types.ArtifactReference{ Name: "testdata/misconfig/terraform/multiple-failures", Type: types.ArtifactFilesystem, - ID: "sha256:e5159ce9589ca0fd714cbbb757628fffff31229a52310ea151ae1410be5f1f1b", + ID: "sha256:afc20cf0fc99c62bbc79b00cb9fbc70ba7ee76c946a6d560639ba9279344787d", BlobIDs: []string{ - "sha256:e5159ce9589ca0fd714cbbb757628fffff31229a52310ea151ae1410be5f1f1b", + "sha256:afc20cf0fc99c62bbc79b00cb9fbc70ba7ee76c946a6d560639ba9279344787d", }, }, }, @@ -545,9 +546,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { want: types.ArtifactReference{ Name: "testdata/misconfig/terraform/no-results", Type: types.ArtifactFilesystem, - ID: "sha256:0c31a344fe889e279aecf743d801ae5d40ee2841a45ed7820114c1094c41a966", + ID: "sha256:1827b6a0b0a17e0d623a2045e9d9c331ef613390eda2fed823969ee0dd730257", BlobIDs: []string{ - "sha256:0c31a344fe889e279aecf743d801ae5d40ee2841a45ed7820114c1094c41a966", + "sha256:1827b6a0b0a17e0d623a2045e9d9c331ef613390eda2fed823969ee0dd730257", }, }, }, @@ -578,7 +579,7 @@ func TestTerraformMisconfigurationScan(t *testing.T) { { Namespace: "user.something", Query: "data.user.something.deny", - PolicyMetadata: policyMetadata, + PolicyMetadata: terraformPolicyMetadata, CauseMetadata: types.CauseMetadata{ Provider: "Generic", Service: "general", @@ -594,9 +595,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { want: types.ArtifactReference{ Name: "testdata/misconfig/terraform/passed", Type: types.ArtifactFilesystem, - ID: "sha256:4e2b9cba04625f1d9cc57f74640d039779b0ee176e958aaea37883e03842056d", + ID: "sha256:eec58ef10d1b04df4af76b2472e615f8c27e253a16f90d7542670a7001d88915", BlobIDs: []string{ - "sha256:4e2b9cba04625f1d9cc57f74640d039779b0ee176e958aaea37883e03842056d", + "sha256:eec58ef10d1b04df4af76b2472e615f8c27e253a16f90d7542670a7001d88915", }, }, }, @@ -628,7 +629,7 @@ func TestTerraformMisconfigurationScan(t *testing.T) { Namespace: "user.something", Query: "data.user.something.deny", Message: "Empty bucket name!", - PolicyMetadata: policyMetadata, + PolicyMetadata: terraformPolicyMetadata, CauseMetadata: types.CauseMetadata{ Resource: "aws_s3_bucket.one", Provider: "Generic", @@ -641,7 +642,7 @@ func TestTerraformMisconfigurationScan(t *testing.T) { Namespace: "user.something", Query: "data.user.something.deny", Message: "Empty bucket name!", - PolicyMetadata: policyMetadata, + PolicyMetadata: terraformPolicyMetadata, CauseMetadata: types.CauseMetadata{ Resource: "aws_s3_bucket.two", Provider: "Generic", @@ -660,9 +661,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { want: types.ArtifactReference{ Name: "testdata/misconfig/terraform/busted-relative-paths/child/main.tf", Type: types.ArtifactFilesystem, - ID: "sha256:aacaabaaef04916bc31b5200617a07ca5c92a4eab1b94783cde06cc4b24412d2", + ID: "sha256:8db34d644bfb98077180616caeab0b41a26d9029a47a23d4b36e1d6e45584919", BlobIDs: []string{ - "sha256:aacaabaaef04916bc31b5200617a07ca5c92a4eab1b94783cde06cc4b24412d2", + "sha256:8db34d644bfb98077180616caeab0b41a26d9029a47a23d4b36e1d6e45584919", }, }, }, @@ -694,7 +695,7 @@ func TestTerraformMisconfigurationScan(t *testing.T) { { Namespace: "user.something", Query: "data.user.something.deny", - PolicyMetadata: policyMetadata, + PolicyMetadata: terraformPolicyMetadata, CauseMetadata: types.CauseMetadata{ Provider: "Generic", Service: "general", @@ -710,9 +711,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { want: types.ArtifactReference{ Name: "testdata/misconfig/terraform/tfvar-outside/tf", Type: types.ArtifactFilesystem, - ID: "sha256:4e2b9cba04625f1d9cc57f74640d039779b0ee176e958aaea37883e03842056d", + ID: "sha256:eec58ef10d1b04df4af76b2472e615f8c27e253a16f90d7542670a7001d88915", BlobIDs: []string{ - "sha256:4e2b9cba04625f1d9cc57f74640d039779b0ee176e958aaea37883e03842056d", + "sha256:eec58ef10d1b04df4af76b2472e615f8c27e253a16f90d7542670a7001d88915", }, }, }, @@ -743,7 +744,7 @@ func TestTerraformMisconfigurationScan(t *testing.T) { Namespace: "user.something", Query: "data.user.something.deny", Message: "Empty bucket name!", - PolicyMetadata: policyMetadata, + PolicyMetadata: terraformPolicyMetadata, CauseMetadata: types.CauseMetadata{ Resource: "aws_s3_bucket.three", Provider: "Generic", @@ -762,7 +763,7 @@ func TestTerraformMisconfigurationScan(t *testing.T) { Namespace: "user.something", Query: "data.user.something.deny", Message: "Empty bucket name!", - PolicyMetadata: policyMetadata, + PolicyMetadata: terraformPolicyMetadata, CauseMetadata: types.CauseMetadata{ Resource: "aws_s3_bucket.one", Provider: "Generic", @@ -781,7 +782,7 @@ func TestTerraformMisconfigurationScan(t *testing.T) { Namespace: "user.something", Query: "data.user.something.deny", Message: "Empty bucket name!", - PolicyMetadata: policyMetadata, + PolicyMetadata: terraformPolicyMetadata, CauseMetadata: types.CauseMetadata{ Resource: "aws_s3_bucket.two", Provider: "Generic", @@ -800,9 +801,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { want: types.ArtifactReference{ Name: "testdata/misconfig/terraform/relative-paths/child", Type: types.ArtifactFilesystem, - ID: "sha256:9c5c0038bf41e03f878ed27c569b93198a16b0d975e7fca4e90aa2a4eaf87402", + ID: "sha256:f13b89447db61be1c1e4099ef18aec7272091f8f2d3581643a9d1fabc74eda83", BlobIDs: []string{ - "sha256:9c5c0038bf41e03f878ed27c569b93198a16b0d975e7fca4e90aa2a4eaf87402", + "sha256:f13b89447db61be1c1e4099ef18aec7272091f8f2d3581643a9d1fabc74eda83", }, }, }, @@ -825,6 +826,261 @@ func TestTerraformMisconfigurationScan(t *testing.T) { } } +const emptyBucketCheck = `package user.something + +__rego_metadata__ := { + "id": "TEST001", + "avd_id": "AVD-TEST-0001", + "title": "Test policy", + "short_code": "empty-bucket-name", + "severity": "LOW", + "description": "This is a test policy.", + "recommended_actions": "Have a cup of tea.", + "url": "https://trivy.dev/", +} + +# taken from defsec rego lib to mimic behaviour +result(msg, cause) = result { + metadata := object.get(cause, "__defsec_metadata", cause) + result := { + "msg": msg, + "startline": object.get(metadata, "startline", 0), + "endline": object.get(metadata, "endline", 0), + "filepath": object.get(metadata, "filepath", ""), + "explicit": object.get(metadata, "explicit", false), + "managed": object.get(metadata, "managed", true), + "fskey": object.get(metadata, "fskey", ""), + "resource": object.get(metadata, "resource", ""), + } +} + +deny[res] { + bucket := input.aws.s3.buckets[_] + bucket.name.value == "" + res := result("Empty bucket name!", bucket) +}` + +var terraformPlanPolicyMetadata = types.PolicyMetadata{ + ID: "TEST001", + AVDID: "AVD-TEST-0001", + Type: "Terraform Plan Snapshot Security Check", + Title: "Test policy", + Description: "This is a test policy.", + Severity: "LOW", + RecommendedActions: "Have a cup of tea.", + References: []string{"https://trivy.dev/"}, +} + +func TestTerraformPlanSnapshotMisconfScan(t *testing.T) { + type fields struct { + dir string + } + tests := []struct { + name string + fields fields + putBlobExpectation cache.ArtifactCachePutBlobExpectation + want types.ArtifactReference + }{ + { + name: "single failure", + fields: fields{ + + dir: "./testdata/misconfig/terraformplan/snapshots/single-failure", + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: 2, + Misconfigurations: []types.Misconfiguration{ + { + FileType: types.TerraformPlanSnapshot, + FilePath: "main.tf", + Failures: types.MisconfResults{ + { + Namespace: "user.something", + Query: "data.user.something.deny", + Message: "Empty bucket name!", + PolicyMetadata: terraformPlanPolicyMetadata, + CauseMetadata: types.CauseMetadata{ + Resource: "aws_s3_bucket.this", + Provider: "Generic", + Service: "general", + StartLine: 10, + EndLine: 12, + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/misconfig/terraformplan/snapshots/single-failure", + Type: types.ArtifactFilesystem, + ID: "sha256:732c38451bde877a94d1ff5b6f2019655bed04fd24b1169de195eeee1199045e", + BlobIDs: []string{ + "sha256:732c38451bde877a94d1ff5b6f2019655bed04fd24b1169de195eeee1199045e", + }, + }, + }, + { + name: "multiple failures", + fields: fields{ + dir: "./testdata/misconfig/terraformplan/snapshots/multiple-failures", + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: 2, + Misconfigurations: []types.Misconfiguration{ + { + FileType: types.TerraformPlanSnapshot, + FilePath: "main.tf", + Failures: types.MisconfResults{ + { + Namespace: "user.something", + Query: "data.user.something.deny", + Message: "Empty bucket name!", + PolicyMetadata: terraformPlanPolicyMetadata, + CauseMetadata: types.CauseMetadata{ + Resource: "aws_s3_bucket.one", + Provider: "Generic", + Service: "general", + StartLine: 10, + EndLine: 12, + }, + }, + { + Namespace: "user.something", + Query: "data.user.something.deny", + Message: "Empty bucket name!", + PolicyMetadata: terraformPlanPolicyMetadata, + CauseMetadata: types.CauseMetadata{ + Resource: "aws_s3_bucket.two", + Provider: "Generic", + Service: "general", + StartLine: 14, + EndLine: 16, + }, + }, + }, + }, + { + FileType: types.TerraformPlanSnapshot, + FilePath: "more.tf", + Failures: types.MisconfResults{ + { + Namespace: "user.something", + Query: "data.user.something.deny", + Message: "Empty bucket name!", + PolicyMetadata: terraformPlanPolicyMetadata, + CauseMetadata: types.CauseMetadata{ + Resource: "aws_s3_bucket.three", + Provider: "Generic", + Service: "general", + StartLine: 1, + EndLine: 3, + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/misconfig/terraformplan/snapshots/multiple-failures", + Type: types.ArtifactFilesystem, + ID: "sha256:752c0b470adfcfe7e21892cf4c6fc3bc28dba873c9c1696f40b71b7a51ad7231", + BlobIDs: []string{ + "sha256:752c0b470adfcfe7e21892cf4c6fc3bc28dba873c9c1696f40b71b7a51ad7231", + }, + }, + }, + { + name: "passed", + fields: fields{ + dir: "./testdata/misconfig/terraformplan/snapshots/passed", + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: 2, + Misconfigurations: []types.Misconfiguration{ + { + FileType: types.TerraformPlanSnapshot, + FilePath: ".", + Successes: types.MisconfResults{ + { + Namespace: "user.something", + Query: "data.user.something.deny", + PolicyMetadata: terraformPlanPolicyMetadata, + CauseMetadata: types.CauseMetadata{ + Provider: "Generic", + Service: "general", + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: types.ArtifactReference{ + Name: "testdata/misconfig/terraformplan/snapshots/passed", + Type: types.ArtifactFilesystem, + ID: "sha256:8947704a08f54ab1df32cd905d6bca72edf1785a42702968bafa331172da7176", + BlobIDs: []string{ + "sha256:8947704a08f54ab1df32cd905d6bca72edf1785a42702968bafa331172da7176", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + tmpDir := t.TempDir() + f, err := os.Create(filepath.Join(tmpDir, "policy.rego")) + require.NoError(t, err) + defer f.Close() + + f.WriteString(emptyBucketCheck) + + c := new(cache.MockArtifactCache) + c.ApplyPutBlobExpectation(tt.putBlobExpectation) + + opt := artifact.Option{ + DisabledHandlers: []types.HandlerType{ + types.SystemFileFilteringPostHandler, + }, + SkipFiles: []string{"*.tf"}, + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + DisableEmbeddedPolicies: true, + + DisableEmbeddedLibraries: false, + Namespaces: []string{"user"}, + PolicyPaths: []string{tmpDir}, + }, + } + + a, err := NewArtifact(tt.fields.dir, c, opt) + require.NoError(t, err) + + got, err := a.Inspect(context.Background()) + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + func TestCloudFormationMisconfigurationScan(t *testing.T) { type fields struct { dir string @@ -892,9 +1148,9 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { want: types.ArtifactReference{ Name: "testdata/misconfig/cloudformation/single-failure/src", Type: types.ArtifactFilesystem, - ID: "sha256:b2ae3759e901c7ba8b0aa690e551e3eec01b6e450533d5444a63969ffbb97adf", + ID: "sha256:889a94522970c6e55f1f7543914b2f0131f79f9c4526445fb95309f64a9947d7", BlobIDs: []string{ - "sha256:b2ae3759e901c7ba8b0aa690e551e3eec01b6e450533d5444a63969ffbb97adf", + "sha256:889a94522970c6e55f1f7543914b2f0131f79f9c4526445fb95309f64a9947d7", }, }, }, @@ -976,9 +1232,9 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { want: types.ArtifactReference{ Name: "testdata/misconfig/cloudformation/multiple-failures/src", Type: types.ArtifactFilesystem, - ID: "sha256:410c31b72c1da31a4b2974fe4405820ffe81c65880017491ca63ec6be2cd9424", + ID: "sha256:17c9c72a759856445e6d3847b2d5ed90c3bad3e4ee50cea0c812ef53c179f8ca", BlobIDs: []string{ - "sha256:410c31b72c1da31a4b2974fe4405820ffe81c65880017491ca63ec6be2cd9424", + "sha256:17c9c72a759856445e6d3847b2d5ed90c3bad3e4ee50cea0c812ef53c179f8ca", }, }, }, @@ -1008,9 +1264,9 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { want: types.ArtifactReference{ Name: "testdata/misconfig/cloudformation/no-results/src", Type: types.ArtifactFilesystem, - ID: "sha256:5cafcecda4322751d7b281d9546f5789a46d82d19cc2adab614122d2ce3420b9", + ID: "sha256:26c76a2cb55cb0ef2c3a2dd79e237bddb508ca2c4cefdb103698a1972c8a9c2d", BlobIDs: []string{ - "sha256:5cafcecda4322751d7b281d9546f5789a46d82d19cc2adab614122d2ce3420b9", + "sha256:26c76a2cb55cb0ef2c3a2dd79e237bddb508ca2c4cefdb103698a1972c8a9c2d", }, }, }, @@ -1066,9 +1322,9 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { want: types.ArtifactReference{ Name: "testdata/misconfig/cloudformation/params/code/src", Type: types.ArtifactFilesystem, - ID: "sha256:0c66c19a4df3ecc11db9f90fbc921f1050325c05c480847369e07ee309e8a897", + ID: "sha256:267b572211115db6a2a4484a02317fbb6d4f050da0e95b1db4243d49889483de", BlobIDs: []string{ - "sha256:0c66c19a4df3ecc11db9f90fbc921f1050325c05c480847369e07ee309e8a897", + "sha256:267b572211115db6a2a4484a02317fbb6d4f050da0e95b1db4243d49889483de", }, }, }, @@ -1124,9 +1380,9 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { want: types.ArtifactReference{ Name: "testdata/misconfig/cloudformation/passed/src", Type: types.ArtifactFilesystem, - ID: "sha256:9f9b0773ca1ec4b257aae410798a635076eaed12afefac839b62efdc65d417e1", + ID: "sha256:8ca92725ce2f47b7ffb1b0a9e0359d59ac2b3b3f517ba42f66a859436057e54a", BlobIDs: []string{ - "sha256:9f9b0773ca1ec4b257aae410798a635076eaed12afefac839b62efdc65d417e1", + "sha256:8ca92725ce2f47b7ffb1b0a9e0359d59ac2b3b3f517ba42f66a859436057e54a", }, }, }, @@ -1212,9 +1468,9 @@ func TestDockerfileMisconfigurationScan(t *testing.T) { want: types.ArtifactReference{ Name: "testdata/misconfig/dockerfile/single-failure/src", Type: types.ArtifactFilesystem, - ID: "sha256:a22d246cba3476acf2a3d6cfe88b5a895ab78cb328b76fe070fce9f1c77f80c7", + ID: "sha256:627cbf451ec7929dfe5151dfe0e2305ed855906bf79136f198528a0cc3f6e4f9", BlobIDs: []string{ - "sha256:a22d246cba3476acf2a3d6cfe88b5a895ab78cb328b76fe070fce9f1c77f80c7", + "sha256:627cbf451ec7929dfe5151dfe0e2305ed855906bf79136f198528a0cc3f6e4f9", }, }, }, @@ -1270,9 +1526,9 @@ func TestDockerfileMisconfigurationScan(t *testing.T) { want: types.ArtifactReference{ Name: "testdata/misconfig/dockerfile/multiple-failures/src", Type: types.ArtifactFilesystem, - ID: "sha256:a22d246cba3476acf2a3d6cfe88b5a895ab78cb328b76fe070fce9f1c77f80c7", + ID: "sha256:627cbf451ec7929dfe5151dfe0e2305ed855906bf79136f198528a0cc3f6e4f9", BlobIDs: []string{ - "sha256:a22d246cba3476acf2a3d6cfe88b5a895ab78cb328b76fe070fce9f1c77f80c7", + "sha256:627cbf451ec7929dfe5151dfe0e2305ed855906bf79136f198528a0cc3f6e4f9", }, }, }, @@ -1300,9 +1556,9 @@ func TestDockerfileMisconfigurationScan(t *testing.T) { want: types.ArtifactReference{ Name: "testdata/misconfig/dockerfile/no-results/src", Type: types.ArtifactFilesystem, - ID: "sha256:5cafcecda4322751d7b281d9546f5789a46d82d19cc2adab614122d2ce3420b9", + ID: "sha256:26c76a2cb55cb0ef2c3a2dd79e237bddb508ca2c4cefdb103698a1972c8a9c2d", BlobIDs: []string{ - "sha256:5cafcecda4322751d7b281d9546f5789a46d82d19cc2adab614122d2ce3420b9", + "sha256:26c76a2cb55cb0ef2c3a2dd79e237bddb508ca2c4cefdb103698a1972c8a9c2d", }, }, }, @@ -1360,9 +1616,9 @@ func TestDockerfileMisconfigurationScan(t *testing.T) { want: types.ArtifactReference{ Name: "testdata/misconfig/dockerfile/passed/src", Type: types.ArtifactFilesystem, - ID: "sha256:a9541fe2309a78505f147a688ebf8a2e107bad8351bb22e280f627d5ecf91b16", + ID: "sha256:4cc7f6bba417cc65c5391bc9c07fd1e205e21bdec87b271889433af18be1e454", BlobIDs: []string{ - "sha256:a9541fe2309a78505f147a688ebf8a2e107bad8351bb22e280f627d5ecf91b16", + "sha256:4cc7f6bba417cc65c5391bc9c07fd1e205e21bdec87b271889433af18be1e454", }, }, }, @@ -1452,9 +1708,9 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { want: types.ArtifactReference{ Name: "testdata/misconfig/kubernetes/single-failure/src", Type: types.ArtifactFilesystem, - ID: "sha256:ee77eca0b592b90536a467b20629c017b03e627c95427a3e7f4be2a9eb55c710", + ID: "sha256:d5ca0b4e96aaaeafa424a2250db6297a5182cb6ca5db49bc1ff11790f6cdbee9", BlobIDs: []string{ - "sha256:ee77eca0b592b90536a467b20629c017b03e627c95427a3e7f4be2a9eb55c710", + "sha256:d5ca0b4e96aaaeafa424a2250db6297a5182cb6ca5db49bc1ff11790f6cdbee9", }, }, }, @@ -1538,9 +1794,9 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { want: types.ArtifactReference{ Name: "testdata/misconfig/kubernetes/multiple-failures/src", Type: types.ArtifactFilesystem, - ID: "sha256:1892ab8d28210fe199dbea24de4f2073ddf5d4bf5b33aa32436d2e1f1facb588", + ID: "sha256:eef9fff2fe8f5c4a123c018b4f91db25d9676e7d171a3a683c2fbfbbbe82fa54", BlobIDs: []string{ - "sha256:1892ab8d28210fe199dbea24de4f2073ddf5d4bf5b33aa32436d2e1f1facb588", + "sha256:eef9fff2fe8f5c4a123c018b4f91db25d9676e7d171a3a683c2fbfbbbe82fa54", }, }, }, @@ -1568,9 +1824,9 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { want: types.ArtifactReference{ Name: "testdata/misconfig/kubernetes/no-results/src", Type: types.ArtifactFilesystem, - ID: "sha256:70f3be8d859a21841ac42de298e9e805aa058593cf3e315e8ee0fa1f30ef5107", + ID: "sha256:2b54cf33feaa1fe1f5bf223f873ca6c3f7c3693b0bb3b0ce9e2e7fd79cd37b5a", BlobIDs: []string{ - "sha256:70f3be8d859a21841ac42de298e9e805aa058593cf3e315e8ee0fa1f30ef5107", + "sha256:2b54cf33feaa1fe1f5bf223f873ca6c3f7c3693b0bb3b0ce9e2e7fd79cd37b5a", }, }, }, @@ -1628,9 +1884,9 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { want: types.ArtifactReference{ Name: "testdata/misconfig/kubernetes/passed/src", Type: types.ArtifactFilesystem, - ID: "sha256:f0eb13ac1479d37da2bcfc7964de5f3a4d3f06982ec27f110c97e9c8cf1cde38", + ID: "sha256:dc7a0fd3ea2f13b0ea05f4e517a16e602b0fc17fbd72aa5e34107ef12a91a30b", BlobIDs: []string{ - "sha256:f0eb13ac1479d37da2bcfc7964de5f3a4d3f06982ec27f110c97e9c8cf1cde38", + "sha256:dc7a0fd3ea2f13b0ea05f4e517a16e602b0fc17fbd72aa5e34107ef12a91a30b", }, }, }, @@ -1717,9 +1973,9 @@ func TestAzureARMMisconfigurationScan(t *testing.T) { want: types.ArtifactReference{ Name: "testdata/misconfig/azurearm/single-failure/src", Type: types.ArtifactFilesystem, - ID: "sha256:ca17bc98d3b2dc8e7b001877324c51d08ba1b2a6764514a9a81adb3fb11f3c08", + ID: "sha256:c1a8bfd544b9041ad194382cc42b54289f70966d061ef501b267aec8fd07c5df", BlobIDs: []string{ - "sha256:ca17bc98d3b2dc8e7b001877324c51d08ba1b2a6764514a9a81adb3fb11f3c08", + "sha256:c1a8bfd544b9041ad194382cc42b54289f70966d061ef501b267aec8fd07c5df", }, }, }, @@ -1799,9 +2055,9 @@ func TestAzureARMMisconfigurationScan(t *testing.T) { want: types.ArtifactReference{ Name: "testdata/misconfig/azurearm/multiple-failures/src", Type: types.ArtifactFilesystem, - ID: "sha256:2e29f26823faf3042ff25c32734fa96bc24ebb09a4d92d5ea3ea5c60d7204114", + ID: "sha256:75bf0e88f8d2857be90fb8d10a350c04c1532ba7f510e1eb404a8bae30ce97d8", BlobIDs: []string{ - "sha256:2e29f26823faf3042ff25c32734fa96bc24ebb09a4d92d5ea3ea5c60d7204114", + "sha256:75bf0e88f8d2857be90fb8d10a350c04c1532ba7f510e1eb404a8bae30ce97d8", }, }, }, @@ -1829,9 +2085,9 @@ func TestAzureARMMisconfigurationScan(t *testing.T) { want: types.ArtifactReference{ Name: "testdata/misconfig/azurearm/no-results/src", Type: types.ArtifactFilesystem, - ID: "sha256:5cafcecda4322751d7b281d9546f5789a46d82d19cc2adab614122d2ce3420b9", + ID: "sha256:26c76a2cb55cb0ef2c3a2dd79e237bddb508ca2c4cefdb103698a1972c8a9c2d", BlobIDs: []string{ - "sha256:5cafcecda4322751d7b281d9546f5789a46d82d19cc2adab614122d2ce3420b9", + "sha256:26c76a2cb55cb0ef2c3a2dd79e237bddb508ca2c4cefdb103698a1972c8a9c2d", }, }, }, @@ -1885,9 +2141,9 @@ func TestAzureARMMisconfigurationScan(t *testing.T) { want: types.ArtifactReference{ Name: "testdata/misconfig/azurearm/passed/src", Type: types.ArtifactFilesystem, - ID: "sha256:a2a781172a5d3a7c70251322696b892cf16da89c937a85f5caf7d2e8c44eede4", + ID: "sha256:b9ba7c4eafec405c8b6998dbb98ee1c7f7830caf8487fd1461433ff82d8779e9", BlobIDs: []string{ - "sha256:a2a781172a5d3a7c70251322696b892cf16da89c937a85f5caf7d2e8c44eede4", + "sha256:b9ba7c4eafec405c8b6998dbb98ee1c7f7830caf8487fd1461433ff82d8779e9", }, }, }, diff --git a/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/multiple-failures/main.tf b/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/multiple-failures/main.tf new file mode 100644 index 000000000000..9eb5363beea5 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/multiple-failures/main.tf @@ -0,0 +1,16 @@ +terraform { + required_providers { + aws = { + source = "aws" + version = "5.35.0" + } + } +} + +resource "aws_s3_bucket" "one" { + +} + +resource "aws_s3_bucket" "two" { + +} diff --git a/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/multiple-failures/more.tf b/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/multiple-failures/more.tf new file mode 100644 index 000000000000..e5cc0c1ea195 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/multiple-failures/more.tf @@ -0,0 +1,3 @@ +resource "aws_s3_bucket" "three" { + +} diff --git a/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/multiple-failures/tfplan b/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/multiple-failures/tfplan new file mode 100644 index 000000000000..80b80a786b3d Binary files /dev/null and b/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/multiple-failures/tfplan differ diff --git a/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/passed/main.tf b/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/passed/main.tf new file mode 100644 index 000000000000..64e9f0d2b965 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/passed/main.tf @@ -0,0 +1,12 @@ +terraform { + required_providers { + aws = { + source = "aws" + version = "5.35.0" + } + } +} + +resource "aws_s3_bucket" "this" { + bucket = "test-bucket" +} diff --git a/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/passed/tfplan b/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/passed/tfplan new file mode 100644 index 000000000000..ee00c44a36b3 Binary files /dev/null and b/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/passed/tfplan differ diff --git a/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/single-failure/main.tf b/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/single-failure/main.tf new file mode 100644 index 000000000000..156495110856 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/single-failure/main.tf @@ -0,0 +1,12 @@ +terraform { + required_providers { + aws = { + source = "aws" + version = "5.35.0" + } + } +} + +resource "aws_s3_bucket" "this" { + +} diff --git a/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/single-failure/tfplan b/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/single-failure/tfplan new file mode 100644 index 000000000000..c07b8d1757f5 Binary files /dev/null and b/pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots/single-failure/tfplan differ diff --git a/pkg/fanal/artifact/repo/git_test.go b/pkg/fanal/artifact/repo/git_test.go index ca29941a597d..ab8221c2f9e8 100644 --- a/pkg/fanal/artifact/repo/git_test.go +++ b/pkg/fanal/artifact/repo/git_test.go @@ -194,9 +194,9 @@ func TestArtifact_Inspect(t *testing.T) { want: types.ArtifactReference{ Name: ts.URL + "/test.git", Type: types.ArtifactRepository, - ID: "sha256:1fa928c33b16a335015ce96e1384127f8463c4f27ed0786806a6d4584b63d091", + ID: "sha256:6a89d4fcd50f840a79da64523c255da80171acd3d286df2acc60056c778d9304", BlobIDs: []string{ - "sha256:1fa928c33b16a335015ce96e1384127f8463c4f27ed0786806a6d4584b63d091", + "sha256:6a89d4fcd50f840a79da64523c255da80171acd3d286df2acc60056c778d9304", }, }, }, diff --git a/pkg/fanal/types/const.go b/pkg/fanal/types/const.go index fca6a6b5d00a..115850f43978 100644 --- a/pkg/fanal/types/const.go +++ b/pkg/fanal/types/const.go @@ -83,15 +83,16 @@ const ( // Config files const ( - JSON ConfigType = "json" - Dockerfile ConfigType = "dockerfile" - Terraform ConfigType = "terraform" - TerraformPlan ConfigType = "terraformplan" - CloudFormation ConfigType = "cloudformation" - Kubernetes ConfigType = "kubernetes" - Helm ConfigType = "helm" - Cloud ConfigType = "cloud" - AzureARM ConfigType = "azure-arm" + JSON ConfigType = "json" + Dockerfile ConfigType = "dockerfile" + Terraform ConfigType = "terraform" + TerraformPlanJSON ConfigType = "terraformplan" + TerraformPlanSnapshot ConfigType = "terraformplan-snapshot" + CloudFormation ConfigType = "cloudformation" + Kubernetes ConfigType = "kubernetes" + Helm ConfigType = "helm" + Cloud ConfigType = "cloud" + AzureARM ConfigType = "azure-arm" ) // Language-specific file names diff --git a/pkg/iac/detection/detect.go b/pkg/iac/detection/detect.go index 40cd4ce98ee2..6d1e627d11dc 100644 --- a/pkg/iac/detection/detect.go +++ b/pkg/iac/detection/detect.go @@ -10,23 +10,25 @@ import ( "gopkg.in/yaml.v3" "github.com/aquasecurity/trivy/pkg/iac/scanners/azure/arm/parser/armjson" + "github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan/snapshot" "github.com/aquasecurity/trivy/pkg/iac/types" ) type FileType string const ( - FileTypeCloudFormation FileType = "cloudformation" - FileTypeTerraform FileType = "terraform" - FileTypeTerraformPlan FileType = "terraformplan" - FileTypeDockerfile FileType = "dockerfile" - FileTypeKubernetes FileType = "kubernetes" - FileTypeRbac FileType = "rbac" - FileTypeYAML FileType = "yaml" - FileTypeTOML FileType = "toml" - FileTypeJSON FileType = "json" - FileTypeHelm FileType = "helm" - FileTypeAzureARM FileType = "azure-arm" + FileTypeCloudFormation FileType = "cloudformation" + FileTypeTerraform FileType = "terraform" + FileTypeTerraformPlanJSON FileType = "terraformplan-json" + FileTypeTerraformPlanSnapshot FileType = "terraformplan-snapshot" + FileTypeDockerfile FileType = "dockerfile" + FileTypeKubernetes FileType = "kubernetes" + FileTypeRbac FileType = "rbac" + FileTypeYAML FileType = "yaml" + FileTypeTOML FileType = "toml" + FileTypeJSON FileType = "json" + FileTypeHelm FileType = "helm" + FileTypeAzureARM FileType = "azure-arm" ) var matchers = make(map[FileType]func(name string, r io.ReadSeeker) bool) @@ -77,7 +79,7 @@ func init() { return IsTerraformFile(name) } - matchers[FileTypeTerraformPlan] = func(name string, r io.ReadSeeker) bool { + matchers[FileTypeTerraformPlanJSON] = func(name string, r io.ReadSeeker) bool { if IsType(name, r, FileTypeJSON) { if resetReader(r) == nil { return false @@ -95,6 +97,10 @@ func init() { return false } + matchers[FileTypeTerraformPlanSnapshot] = func(name string, r io.ReadSeeker) bool { + return snapshot.IsPlanSnapshot(r) + } + matchers[FileTypeCloudFormation] = func(name string, r io.ReadSeeker) bool { sniff := struct { Resources map[string]map[string]interface{} `json:"Resources" yaml:"Resources"` diff --git a/pkg/iac/detection/detect_test.go b/pkg/iac/detection/detect_test.go index 6a38dc014731..e5f875071e16 100644 --- a/pkg/iac/detection/detect_test.go +++ b/pkg/iac/detection/detect_test.go @@ -109,7 +109,7 @@ func Test_Detection(t *testing.T) { "configuration": {} }`), expected: []FileType{ - FileTypeTerraformPlan, + FileTypeTerraformPlanJSON, FileTypeJSON, }, }, diff --git a/pkg/iac/scanners/terraformplan/snapshot/scanner.go b/pkg/iac/scanners/terraformplan/snapshot/scanner.go new file mode 100644 index 000000000000..e2a8d2807fa1 --- /dev/null +++ b/pkg/iac/scanners/terraformplan/snapshot/scanner.go @@ -0,0 +1,75 @@ +package snapshot + +import ( + "context" + "errors" + "fmt" + "io" + "io/fs" + + "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + terraformScanner "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform" +) + +type Scanner struct { + inner *terraformScanner.Scanner +} + +func (s *Scanner) Name() string { + return "Terraform Plan Snapshot" +} + +func New(opts ...options.ScannerOption) *Scanner { + scanner := &Scanner{ + inner: terraformScanner.New(opts...), + } + return scanner +} + +func (s *Scanner) ScanFS(ctx context.Context, fsys fs.FS, dir string) (scan.Results, error) { + var results scan.Results + walkFn := func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if d.IsDir() { + return nil + } + + res, err := s.ScanFile(ctx, fsys, path) + if errors.Is(err, errNoTerraformPlan) { + return nil + } else if err != nil { + return err + } + results = append(results, res...) + return nil + } + if err := fs.WalkDir(fsys, dir, walkFn); err != nil { + return nil, err + } + return results, nil +} + +func (s *Scanner) ScanFile(ctx context.Context, fsys fs.FS, filepath string) (scan.Results, error) { + file, err := fsys.Open(filepath) + if err != nil { + return nil, err + } + defer file.Close() + return s.Scan(ctx, file) +} + +func (s *Scanner) Scan(ctx context.Context, reader io.Reader) (scan.Results, error) { + snap, err := parseSnapshot(reader) + if err != nil { + return nil, err + } + fsys, err := snap.toFS() + if err != nil { + return nil, fmt.Errorf("failed to convert snapshot to FS: %w", err) + } + return s.inner.ScanFS(ctx, fsys, ".") +} diff --git a/pkg/iac/scanners/terraformplan/snapshot/scanner_test.go b/pkg/iac/scanners/terraformplan/snapshot/scanner_test.go new file mode 100644 index 000000000000..92e467db6741 --- /dev/null +++ b/pkg/iac/scanners/terraformplan/snapshot/scanner_test.go @@ -0,0 +1,132 @@ +package snapshot + +import ( + "bytes" + "context" + "os" + "path" + "path/filepath" + "sort" + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/samber/lo" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func initScanner(opts ...options.ScannerOption) *Scanner { + defaultOpts := []options.ScannerOption{ + options.ScannerWithEmbeddedPolicies(false), + options.ScannerWithEmbeddedLibraries(true), + options.ScannerWithPolicyNamespaces("user"), + options.ScannerWithPolicyDirs("."), + options.ScannerWithRegoOnly(true), + } + + opts = append(opts, defaultOpts...) + return New(opts...) +} + +func TestScanner_Scan(t *testing.T) { + tests := []struct { + dir string + expectedIDs []string + }{ + { + dir: "just-resource", + expectedIDs: []string{"ID001"}, + }, + { + dir: "with-local-module", + expectedIDs: []string{"ID001"}, + }, + { + dir: "with-remote-module", + expectedIDs: []string{"ID001"}, + }, + } + + for _, tt := range tests { + t.Run(tt.dir, func(t *testing.T) { + f, err := os.Open(filepath.Join("testdata", tt.dir, "tfplan")) + require.NoError(t, err) + defer f.Close() + + policyFS := os.DirFS(filepath.Join("testdata", tt.dir, "checks")) + + s := initScanner(options.ScannerWithPolicyFilesystem(policyFS)) + result, err := s.Scan(context.TODO(), f) + require.NoError(t, err) + + failed := result.GetFailed() + + assert.Len(t, failed, len(tt.expectedIDs)) + + ids := lo.Map(failed, func(res scan.Result, _ int) string { + return res.Rule().AVDID + }) + sort.Strings(ids) + + assert.Equal(t, tt.expectedIDs, ids) + }) + } +} + +func Test_ScanFS(t *testing.T) { + t.Parallel() + + tests := []struct { + dir string + expectedDir string + expectedIDs []string + }{ + { + dir: "just-resource", + expectedIDs: []string{"ID001"}, + }, + { + dir: "with-local-module", + expectedIDs: []string{"ID001"}, + }, + { + dir: "with-remote-module", + expectedIDs: []string{"ID001"}, + }, + } + + for _, tc := range tests { + t.Run(tc.dir, func(t *testing.T) { + fs := os.DirFS("testdata") + + debugLog := bytes.NewBuffer([]byte{}) + scanner := New( + options.ScannerWithDebug(debugLog), + options.ScannerWithPolicyDirs(path.Join(tc.dir, "checks")), + options.ScannerWithPolicyFilesystem(fs), + options.ScannerWithRegoOnly(true), + options.ScannerWithPolicyNamespaces("user"), + options.ScannerWithEmbeddedLibraries(false), + options.ScannerWithEmbeddedPolicies(false), + options.ScannerWithRegoErrorLimits(0), + ) + + results, err := scanner.ScanFS(context.TODO(), fs, path.Join(tc.dir, "tfplan")) + require.NoError(t, err) + require.Len(t, results, 1) + + failed := results.GetFailed() + + assert.Len(t, failed, len(tc.expectedIDs)) + + ids := lo.Map(failed, func(res scan.Result, _ int) string { + return res.Rule().AVDID + }) + sort.Strings(ids) + + assert.Equal(t, tc.expectedIDs, ids) + }) + } + +} diff --git a/pkg/iac/scanners/terraformplan/snapshot/snapshot.go b/pkg/iac/scanners/terraformplan/snapshot/snapshot.go new file mode 100644 index 000000000000..e32e47790c32 --- /dev/null +++ b/pkg/iac/scanners/terraformplan/snapshot/snapshot.go @@ -0,0 +1,200 @@ +package snapshot + +import ( + "archive/zip" + "encoding/json" + "errors" + "fmt" + "io" + "io/fs" + "os" + "path" + "slices" + "strings" + + "github.com/liamg/memoryfs" + + iox "github.com/aquasecurity/trivy/pkg/x/io" +) + +const ( + configSnapshotPrefix = "tfconfig/" + configSnapshotManifestFile = configSnapshotPrefix + "modules.json" + configSnapshotModulePrefix = configSnapshotPrefix + "m-" + + tfplanFilename = "tfplan" +) + +type ( + configSnapshotModuleRecord struct { + Key string `json:"Key"` + SourceAddr string `json:"Source,omitempty"` + Dir string `json:"Dir"` + } + + configSnapshotModuleManifest []configSnapshotModuleRecord +) + +var errNoTerraformPlan = errors.New("no terraform plan file") + +func IsPlanSnapshot(r io.Reader) bool { + zr, err := readSnapshot(r) + if err != nil { + return false + } + return containsTfplanFile(zr) +} + +func readSnapshot(r io.Reader) (*zip.Reader, error) { + if r == nil { + return nil, errors.New("reader is nil") + } + + rsa, size, err := iox.NewReadSeekerAtWithSize(r) + if err != nil { + return nil, err + } + + zr, err := zip.NewReader(rsa, size) + if err != nil { + return nil, err + } + + return zr, nil +} + +func parseSnapshot(r io.Reader) (*snapshot, error) { + zr, err := readSnapshot(r) + if err != nil { + return nil, err + } + + if !containsTfplanFile(zr) { + return nil, errNoTerraformPlan + } + + snap := &snapshot{ + modules: make(map[string]*snapshotModule), + } + + var moduleManifest configSnapshotModuleManifest + + for _, file := range zr.File { + switch { + case file.Name == configSnapshotManifestFile: + var err error + moduleManifest, err = readModuleManifest(file) + if err != nil { + return nil, err + } + case strings.HasPrefix(file.Name, configSnapshotModulePrefix): + if err := snap.addFile(file); err != nil { + return nil, err + } + } + } + + for _, record := range moduleManifest { + // skip non-local modules + if record.Dir != "." && !strings.HasPrefix(record.SourceAddr, ".") { + delete(snap.modules, record.Key) + continue + } + modSnap := snap.getOrCreateModuleSnapshot(record.Key) + modSnap.dir = record.Dir + } + + return snap, nil +} + +func containsTfplanFile(zr *zip.Reader) bool { + return slices.ContainsFunc(zr.File, func(f *zip.File) bool { + return f.Name == tfplanFilename + }) +} + +func readModuleManifest(f *zip.File) (configSnapshotModuleManifest, error) { + r, err := f.Open() + if err != nil { + return nil, fmt.Errorf("failed to open module manifest: %s", r) + } + defer r.Close() + + var manifest configSnapshotModuleManifest + if err := json.NewDecoder(r).Decode(&manifest); err != nil { + return nil, fmt.Errorf("failed to read module manifest: %s", f.Name) + } + return manifest, nil +} + +type ( + snapshotModule struct { + // dir is the path, relative to the root directory given when the + // snapshot was created, where the module appears in the snapshot's + // virtual filesystem. + dir string + + // files is a map from each configuration file filename for the + // module to a raw byte representation of the source file contents. + files map[string][]byte + } + + snapshot struct { + modules map[string]*snapshotModule + } +) + +func (s *snapshot) addFile(file *zip.File) error { + relName := file.Name[len(configSnapshotModulePrefix):] + moduleKey, fileName := path.Split(relName) + if moduleKey == "" { + return nil + } + moduleKey = moduleKey[:len(moduleKey)-1] + + r, err := file.Open() + if err != nil { + return fmt.Errorf("failed to open snapshot of %s from module %q: %s", fileName, moduleKey, err) + } + defer r.Close() + + fileSrc, err := io.ReadAll(r) + if err != nil { + return fmt.Errorf("failed to read snapshot of %s from module %q: %s", fileName, moduleKey, err) + } + + modSnap := s.getOrCreateModuleSnapshot(moduleKey) + modSnap.files[fileName] = fileSrc + return nil +} + +func (s *snapshot) getOrCreateModuleSnapshot(key string) *snapshotModule { + modSnap, exists := s.modules[key] + if !exists { + modSnap = &snapshotModule{ + files: make(map[string][]byte), + } + s.modules[key] = modSnap + } + return modSnap +} + +func (s *snapshot) toFS() (fs.FS, error) { + fsys := memoryfs.New() + + for _, module := range s.modules { + if err := fsys.MkdirAll(module.dir, fs.ModePerm); err != nil && !errors.Is(err, os.ErrExist) { + return nil, err + } + for filename, file := range module.files { + filePath := filename + if module.dir != "" { + filePath = path.Join(module.dir, filename) + } + if err := fsys.WriteFile(filePath, file, fs.ModePerm); err != nil { + return nil, fmt.Errorf("failed to add file: %w", err) + } + } + } + return fsys, nil +} diff --git a/pkg/iac/scanners/terraformplan/snapshot/snapshot_test.go b/pkg/iac/scanners/terraformplan/snapshot/snapshot_test.go new file mode 100644 index 000000000000..22f26e5e7b6b --- /dev/null +++ b/pkg/iac/scanners/terraformplan/snapshot/snapshot_test.go @@ -0,0 +1,110 @@ +package snapshot + +import ( + "archive/zip" + "bytes" + "io/fs" + "os" + "path/filepath" + "sort" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestReadSnapshot(t *testing.T) { + tests := []struct { + name string + dir string + expectedFiles []string + }{ + { + name: "just resource", + dir: "just-resource", + expectedFiles: []string{"main.tf", "terraform.tf"}, + }, + { + name: "with local module", + dir: "with-local-module", + expectedFiles: []string{"main.tf", "modules/ec2/main.tf", "terraform.tf"}, + }, + { + name: "with nested modules", + dir: "nested-modules", + expectedFiles: []string{ + "main.tf", + "modules/s3/main.tf", + "modules/s3/modules/logging/main.tf", + "terraform.tf", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(filepath.Join("testdata", tt.dir, "tfplan")) + require.NoError(t, err) + defer f.Close() + + snapshot, err := parseSnapshot(f) + require.NoError(t, err) + require.NotNil(t, snapshot) + + fsys, err := snapshot.toFS() + require.NoError(t, err) + + files, err := getAllfiles(fsys) + require.NoError(t, err) + + assert.Equal(t, tt.expectedFiles, files) + }) + } +} + +func getAllfiles(fsys fs.FS) ([]string, error) { + var files []string + walkFn := func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if d.IsDir() { + return nil + } + + files = append(files, filepath.ToSlash(path)) + return nil + } + if err := fs.WalkDir(fsys, ".", walkFn); err != nil { + return nil, err + } + + sort.Strings(files) + return files, nil +} + +func TestIsPlanSnapshot(t *testing.T) { + t.Run("TF plan", func(t *testing.T) { + f, err := os.Open(filepath.Join("testdata", "just-resource", "tfplan")) + require.NoError(t, err) + defer f.Close() + + got := IsPlanSnapshot(f) + assert.True(t, got) + }) + + t.Run("just a zip file", func(t *testing.T) { + var b bytes.Buffer + zw := zip.NewWriter(&b) + defer zw.Close() + + w, err := zw.Create("test.txt") + require.NoError(t, err) + + w.Write([]byte("test")) + + got := IsPlanSnapshot(&b) + assert.False(t, got) + }) +} diff --git a/pkg/iac/scanners/terraformplan/snapshot/testdata/just-resource/checks/s3-bucket-name.rego b/pkg/iac/scanners/terraformplan/snapshot/testdata/just-resource/checks/s3-bucket-name.rego new file mode 100644 index 000000000000..798b1b705a49 --- /dev/null +++ b/pkg/iac/scanners/terraformplan/snapshot/testdata/just-resource/checks/s3-bucket-name.rego @@ -0,0 +1,21 @@ +# METADATA +# title: Test rego +# description: A bucket named "test-bucket" is not allowed +# schemas: +# - input: schema["cloud"] +# custom: +# avd_id: ID001 +# severity: LOW +# input: +# selector: +# - type: cloud +# subtypes: +# - service: s3 +# provider: aws +package user.aws.ID001 + +deny[res] { + bucket := input.aws.s3.buckets[_] + bucket.name.value == "test-bucket" + res := result.new("Bucket not allowed", bucket.name) +} \ No newline at end of file diff --git a/pkg/iac/scanners/terraformplan/snapshot/testdata/just-resource/main.tf b/pkg/iac/scanners/terraformplan/snapshot/testdata/just-resource/main.tf new file mode 100644 index 000000000000..76b7c4788d76 --- /dev/null +++ b/pkg/iac/scanners/terraformplan/snapshot/testdata/just-resource/main.tf @@ -0,0 +1,3 @@ +resource "aws_s3_bucket" "this" { + bucket = "test-bucket" +} \ No newline at end of file diff --git a/pkg/iac/scanners/terraformplan/snapshot/testdata/just-resource/terraform.tf b/pkg/iac/scanners/terraformplan/snapshot/testdata/just-resource/terraform.tf new file mode 100644 index 000000000000..ac2d39c811e5 --- /dev/null +++ b/pkg/iac/scanners/terraformplan/snapshot/testdata/just-resource/terraform.tf @@ -0,0 +1,8 @@ +terraform { + required_providers { + aws = { + source = "aws" + version = "5.35.0" + } + } +} diff --git a/pkg/iac/scanners/terraformplan/snapshot/testdata/just-resource/tfplan b/pkg/iac/scanners/terraformplan/snapshot/testdata/just-resource/tfplan new file mode 100644 index 000000000000..7556492a0dff Binary files /dev/null and b/pkg/iac/scanners/terraformplan/snapshot/testdata/just-resource/tfplan differ diff --git a/pkg/iac/scanners/terraformplan/snapshot/testdata/nested-modules/main.tf b/pkg/iac/scanners/terraformplan/snapshot/testdata/nested-modules/main.tf new file mode 100644 index 000000000000..a474edf3ea6e --- /dev/null +++ b/pkg/iac/scanners/terraformplan/snapshot/testdata/nested-modules/main.tf @@ -0,0 +1,3 @@ +module "s3_bucket" { + source = "./modules/s3" +} \ No newline at end of file diff --git a/pkg/iac/scanners/terraformplan/snapshot/testdata/nested-modules/modules/s3/main.tf b/pkg/iac/scanners/terraformplan/snapshot/testdata/nested-modules/modules/s3/main.tf new file mode 100644 index 000000000000..d847578a1a00 --- /dev/null +++ b/pkg/iac/scanners/terraformplan/snapshot/testdata/nested-modules/modules/s3/main.tf @@ -0,0 +1,8 @@ +resource "aws_s3_bucket" "this" { + +} + +module "s3_log" { + source = "./modules/logging" + bucket = aws_s3_bucket.this.id +} \ No newline at end of file diff --git a/pkg/iac/scanners/terraformplan/snapshot/testdata/nested-modules/modules/s3/modules/logging/main.tf b/pkg/iac/scanners/terraformplan/snapshot/testdata/nested-modules/modules/s3/modules/logging/main.tf new file mode 100644 index 000000000000..b9cb54f04a5b --- /dev/null +++ b/pkg/iac/scanners/terraformplan/snapshot/testdata/nested-modules/modules/s3/modules/logging/main.tf @@ -0,0 +1,10 @@ +resource "aws_s3_bucket_versioning" "this" { + bucket = var.bucket + versioning_configuration { + status = "Enabled" + } +} + +variable "bucket" { + type = string +} diff --git a/pkg/iac/scanners/terraformplan/snapshot/testdata/nested-modules/terraform.tf b/pkg/iac/scanners/terraformplan/snapshot/testdata/nested-modules/terraform.tf new file mode 100644 index 000000000000..ac2d39c811e5 --- /dev/null +++ b/pkg/iac/scanners/terraformplan/snapshot/testdata/nested-modules/terraform.tf @@ -0,0 +1,8 @@ +terraform { + required_providers { + aws = { + source = "aws" + version = "5.35.0" + } + } +} diff --git a/pkg/iac/scanners/terraformplan/snapshot/testdata/nested-modules/tfplan b/pkg/iac/scanners/terraformplan/snapshot/testdata/nested-modules/tfplan new file mode 100644 index 000000000000..148507541588 Binary files /dev/null and b/pkg/iac/scanners/terraformplan/snapshot/testdata/nested-modules/tfplan differ diff --git a/pkg/iac/scanners/terraformplan/snapshot/testdata/with-local-module/checks/ec2-userdata.rego b/pkg/iac/scanners/terraformplan/snapshot/testdata/with-local-module/checks/ec2-userdata.rego new file mode 100644 index 000000000000..7ce95b9f9a54 --- /dev/null +++ b/pkg/iac/scanners/terraformplan/snapshot/testdata/with-local-module/checks/ec2-userdata.rego @@ -0,0 +1,21 @@ +# METADATA +# title: Test rego 2 +# description: Instances with userdata are not allowed +# schemas: +# - input: schema["cloud"] +# custom: +# avd_id: ID001 +# severity: MEDIUM +# input: +# selector: +# - type: cloud +# subtypes: +# - service: ec2 +# provider: aws +package user.aws.ID001 + +deny[res] { + instance := input.aws.ec2.instances[_] + instance.userdata.value != "" + res := result.new("Instances with userdata are not allowed", instance.userdata) +} \ No newline at end of file diff --git a/pkg/iac/scanners/terraformplan/snapshot/testdata/with-local-module/main.tf b/pkg/iac/scanners/terraformplan/snapshot/testdata/with-local-module/main.tf new file mode 100644 index 000000000000..9755bb5807a5 --- /dev/null +++ b/pkg/iac/scanners/terraformplan/snapshot/testdata/with-local-module/main.tf @@ -0,0 +1,5 @@ +module "ec2_instance" { + source = "./modules/ec2" + instance_type = "t3.micro" + user_data = "test" +} \ No newline at end of file diff --git a/pkg/iac/scanners/terraformplan/snapshot/testdata/with-local-module/modules/ec2/main.tf b/pkg/iac/scanners/terraformplan/snapshot/testdata/with-local-module/modules/ec2/main.tf new file mode 100644 index 000000000000..488bb6200e29 --- /dev/null +++ b/pkg/iac/scanners/terraformplan/snapshot/testdata/with-local-module/modules/ec2/main.tf @@ -0,0 +1,34 @@ +data "aws_ami" "this" { + most_recent = true + owners = ["amazon"] + filter { + name = "architecture" + values = ["arm64"] + } + filter { + name = "name" + values = ["al2023-ami-2023*"] + } +} + +resource "aws_instance" "this" { + ami = data.aws_ami.this.id + instance_market_options { + spot_options { + max_price = 0.0031 + } + } + instance_type = var.instance_type + tags = { + Name = "test-spot" + } + user_data = var.user_data +} + +variable "instance_type" { + type = string +} + +variable "user_data" { + type = string +} \ No newline at end of file diff --git a/pkg/iac/scanners/terraformplan/snapshot/testdata/with-local-module/terraform.tf b/pkg/iac/scanners/terraformplan/snapshot/testdata/with-local-module/terraform.tf new file mode 100644 index 000000000000..ac2d39c811e5 --- /dev/null +++ b/pkg/iac/scanners/terraformplan/snapshot/testdata/with-local-module/terraform.tf @@ -0,0 +1,8 @@ +terraform { + required_providers { + aws = { + source = "aws" + version = "5.35.0" + } + } +} diff --git a/pkg/iac/scanners/terraformplan/snapshot/testdata/with-local-module/tfplan b/pkg/iac/scanners/terraformplan/snapshot/testdata/with-local-module/tfplan new file mode 100644 index 000000000000..ecd1456588fb Binary files /dev/null and b/pkg/iac/scanners/terraformplan/snapshot/testdata/with-local-module/tfplan differ diff --git a/pkg/iac/scanners/terraformplan/snapshot/testdata/with-remote-module/checks/s3-bucket-name.rego b/pkg/iac/scanners/terraformplan/snapshot/testdata/with-remote-module/checks/s3-bucket-name.rego new file mode 100644 index 000000000000..798b1b705a49 --- /dev/null +++ b/pkg/iac/scanners/terraformplan/snapshot/testdata/with-remote-module/checks/s3-bucket-name.rego @@ -0,0 +1,21 @@ +# METADATA +# title: Test rego +# description: A bucket named "test-bucket" is not allowed +# schemas: +# - input: schema["cloud"] +# custom: +# avd_id: ID001 +# severity: LOW +# input: +# selector: +# - type: cloud +# subtypes: +# - service: s3 +# provider: aws +package user.aws.ID001 + +deny[res] { + bucket := input.aws.s3.buckets[_] + bucket.name.value == "test-bucket" + res := result.new("Bucket not allowed", bucket.name) +} \ No newline at end of file diff --git a/pkg/iac/scanners/terraformplan/snapshot/testdata/with-remote-module/main.tf b/pkg/iac/scanners/terraformplan/snapshot/testdata/with-remote-module/main.tf new file mode 100644 index 000000000000..de5a5fb6f7dc --- /dev/null +++ b/pkg/iac/scanners/terraformplan/snapshot/testdata/with-remote-module/main.tf @@ -0,0 +1,6 @@ +module "s3_bucket" { + source = "terraform-aws-modules/s3-bucket/aws" + version = "4.1.0" + + bucket = "test-bucket" +} \ No newline at end of file diff --git a/pkg/iac/scanners/terraformplan/snapshot/testdata/with-remote-module/terraform.tf b/pkg/iac/scanners/terraformplan/snapshot/testdata/with-remote-module/terraform.tf new file mode 100644 index 000000000000..ac2d39c811e5 --- /dev/null +++ b/pkg/iac/scanners/terraformplan/snapshot/testdata/with-remote-module/terraform.tf @@ -0,0 +1,8 @@ +terraform { + required_providers { + aws = { + source = "aws" + version = "5.35.0" + } + } +} diff --git a/pkg/iac/scanners/terraformplan/snapshot/testdata/with-remote-module/tfplan b/pkg/iac/scanners/terraformplan/snapshot/testdata/with-remote-module/tfplan new file mode 100644 index 000000000000..872ad20646cb Binary files /dev/null and b/pkg/iac/scanners/terraformplan/snapshot/testdata/with-remote-module/tfplan differ diff --git a/pkg/iac/scanners/terraformplan/parser/option.go b/pkg/iac/scanners/terraformplan/tfjson/parser/option.go similarity index 100% rename from pkg/iac/scanners/terraformplan/parser/option.go rename to pkg/iac/scanners/terraformplan/tfjson/parser/option.go diff --git a/pkg/iac/scanners/terraformplan/parser/parser.go b/pkg/iac/scanners/terraformplan/tfjson/parser/parser.go similarity index 100% rename from pkg/iac/scanners/terraformplan/parser/parser.go rename to pkg/iac/scanners/terraformplan/tfjson/parser/parser.go diff --git a/pkg/iac/scanners/terraformplan/parser/plan_file.go b/pkg/iac/scanners/terraformplan/tfjson/parser/plan_file.go similarity index 100% rename from pkg/iac/scanners/terraformplan/parser/plan_file.go rename to pkg/iac/scanners/terraformplan/tfjson/parser/plan_file.go diff --git a/pkg/iac/scanners/terraformplan/scanner.go b/pkg/iac/scanners/terraformplan/tfjson/scanner.go similarity index 96% rename from pkg/iac/scanners/terraformplan/scanner.go rename to pkg/iac/scanners/terraformplan/tfjson/scanner.go index 2a22c4b24690..1a73bd6af0cd 100644 --- a/pkg/iac/scanners/terraformplan/scanner.go +++ b/pkg/iac/scanners/terraformplan/tfjson/scanner.go @@ -1,4 +1,4 @@ -package terraformplan +package tfjson import ( "context" @@ -14,7 +14,7 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/scanners/options" terraformScanner "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform" "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/executor" - "github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan/parser" + "github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan/tfjson/parser" ) var tfPlanExts = []string{ @@ -99,7 +99,7 @@ func (s *Scanner) SetDataFilesystem(_ fs.FS) { func (s *Scanner) SetRegoErrorLimit(_ int) {} func (s *Scanner) Name() string { - return "Terraform Plan" + return "Terraform Plan JSON" } func (s *Scanner) ScanFS(ctx context.Context, inputFS fs.FS, dir string) (scan.Results, error) { diff --git a/pkg/iac/scanners/terraformplan/scanner_test.go b/pkg/iac/scanners/terraformplan/tfjson/scanner_test.go similarity index 99% rename from pkg/iac/scanners/terraformplan/scanner_test.go rename to pkg/iac/scanners/terraformplan/tfjson/scanner_test.go index 8f3dfc0f8a29..fe37184ebb20 100644 --- a/pkg/iac/scanners/terraformplan/scanner_test.go +++ b/pkg/iac/scanners/terraformplan/tfjson/scanner_test.go @@ -1,4 +1,4 @@ -package terraformplan +package tfjson import ( "bytes" diff --git a/pkg/iac/scanners/terraformplan/test/parser_test.go b/pkg/iac/scanners/terraformplan/tfjson/test/parser_test.go similarity index 78% rename from pkg/iac/scanners/terraformplan/test/parser_test.go rename to pkg/iac/scanners/terraformplan/tfjson/test/parser_test.go index bcc04fd0affb..4f81dec89751 100644 --- a/pkg/iac/scanners/terraformplan/test/parser_test.go +++ b/pkg/iac/scanners/terraformplan/tfjson/test/parser_test.go @@ -1,9 +1,9 @@ -package terraformplan +package json import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan/parser" + "github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan/tfjson/parser" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/iac/scanners/terraformplan/test/scanner_test.go b/pkg/iac/scanners/terraformplan/tfjson/test/scanner_test.go similarity index 87% rename from pkg/iac/scanners/terraformplan/test/scanner_test.go rename to pkg/iac/scanners/terraformplan/tfjson/test/scanner_test.go index 9e275ac6082a..2feb3d2fae9b 100644 --- a/pkg/iac/scanners/terraformplan/test/scanner_test.go +++ b/pkg/iac/scanners/terraformplan/tfjson/test/scanner_test.go @@ -1,4 +1,4 @@ -package terraformplan +package json import ( "os" @@ -6,7 +6,7 @@ import ( "testing/fstest" "github.com/aquasecurity/trivy/pkg/iac/scan" - "github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan" + "github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan/tfjson" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -14,7 +14,7 @@ import ( ) func Test_Scanning_Plan(t *testing.T) { - scanner := terraformplan.New( + scanner := tfjson.New( options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(true), ) diff --git a/pkg/iac/scanners/terraformplan/test/testdata/plan.json b/pkg/iac/scanners/terraformplan/tfjson/test/testdata/plan.json similarity index 100% rename from pkg/iac/scanners/terraformplan/test/testdata/plan.json rename to pkg/iac/scanners/terraformplan/tfjson/test/testdata/plan.json diff --git a/pkg/iac/types/range.go b/pkg/iac/types/range.go index 05aa535797d7..938ba929b682 100755 --- a/pkg/iac/types/range.go +++ b/pkg/iac/types/range.go @@ -4,7 +4,7 @@ import ( "encoding/json" "fmt" "io/fs" - "path/filepath" + "path" ) func NewRange(filename string, startLine, endLine int, sourcePrefix string, srcFS fs.FS) Range { @@ -110,7 +110,7 @@ func (r Range) GetFilename() string { if r.isLogicalSource { return fmt.Sprintf("%s:%s", r.sourcePrefix, r.filename) } - return filepath.Join(r.sourcePrefix, r.filename) + return path.Join(r.sourcePrefix, r.filename) } func (r Range) GetLocalFilename() string { diff --git a/pkg/misconf/scanner.go b/pkg/misconf/scanner.go index 3a4bb5311758..4c353f22c26e 100644 --- a/pkg/misconf/scanner.go +++ b/pkg/misconf/scanner.go @@ -26,7 +26,8 @@ import ( k8sscanner "github.com/aquasecurity/trivy/pkg/iac/scanners/kubernetes" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform" - tfpscanner "github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan" + tfprawscanner "github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan/snapshot" + tfpjsonscanner "github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan/tfjson" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/mapfs" @@ -34,13 +35,14 @@ import ( ) var enablediacTypes = map[detection.FileType]types.ConfigType{ - detection.FileTypeAzureARM: types.AzureARM, - detection.FileTypeCloudFormation: types.CloudFormation, - detection.FileTypeTerraform: types.Terraform, - detection.FileTypeDockerfile: types.Dockerfile, - detection.FileTypeKubernetes: types.Kubernetes, - detection.FileTypeHelm: types.Helm, - detection.FileTypeTerraformPlan: types.TerraformPlan, + detection.FileTypeAzureARM: types.AzureARM, + detection.FileTypeCloudFormation: types.CloudFormation, + detection.FileTypeTerraform: types.Terraform, + detection.FileTypeDockerfile: types.Dockerfile, + detection.FileTypeKubernetes: types.Kubernetes, + detection.FileTypeHelm: types.Helm, + detection.FileTypeTerraformPlanJSON: types.TerraformPlanJSON, + detection.FileTypeTerraformPlanSnapshot: types.TerraformPlanSnapshot, } type ScannerOption struct { @@ -99,8 +101,12 @@ func NewTerraformScanner(filePatterns []string, opt ScannerOption) (*Scanner, er return newScanner(detection.FileTypeTerraform, filePatterns, opt) } -func NewTerraformPlanScanner(filePatterns []string, opt ScannerOption) (*Scanner, error) { - return newScanner(detection.FileTypeTerraformPlan, filePatterns, opt) +func NewTerraformPlanJSONScanner(filePatterns []string, opt ScannerOption) (*Scanner, error) { + return newScanner(detection.FileTypeTerraformPlanJSON, filePatterns, opt) +} + +func NewTerraformPlanSnapshotScanner(filePatterns []string, opt ScannerOption) (*Scanner, error) { + return newScanner(detection.FileTypeTerraformPlanSnapshot, filePatterns, opt) } func newScanner(t detection.FileType, filePatterns []string, opt ScannerOption) (*Scanner, error) { @@ -123,8 +129,10 @@ func newScanner(t detection.FileType, filePatterns []string, opt ScannerOption) scanner = k8sscanner.NewScanner(opts...) case detection.FileTypeTerraform: scanner = terraform.New(opts...) - case detection.FileTypeTerraformPlan: - scanner = tfpscanner.New(opts...) + case detection.FileTypeTerraformPlanJSON: + scanner = tfpjsonscanner.New(opts...) + case detection.FileTypeTerraformPlanSnapshot: + scanner = tfprawscanner.New(opts...) } return &Scanner{ @@ -253,7 +261,7 @@ func scannerOptions(t detection.FileType, opt ScannerOption) ([]options.ScannerO switch t { case detection.FileTypeHelm: return addHelmOpts(opts, opt), nil - case detection.FileTypeTerraform: + case detection.FileTypeTerraform, detection.FileTypeTerraformPlanSnapshot: return addTFOpts(opts, opt) case detection.FileTypeCloudFormation: return addCFOpts(opts, opt) diff --git a/pkg/misconf/scanner_test.go b/pkg/misconf/scanner_test.go index 3d139e5c6c69..03c76ebba2c7 100644 --- a/pkg/misconf/scanner_test.go +++ b/pkg/misconf/scanner_test.go @@ -133,7 +133,7 @@ func TestScanner_Scan(t *testing.T) { }, { name: "happy path. terraform plan file", - scannerFunc: NewTerraformPlanScanner, + scannerFunc: NewTerraformPlanJSONScanner, files: []file{ { path: "main.tfplan.json", @@ -141,7 +141,7 @@ func TestScanner_Scan(t *testing.T) { }, }, wantFilePath: "main.tf", - wantFileType: types.TerraformPlan, + wantFileType: types.TerraformPlanJSON, misconfsExpected: 2, }, } diff --git a/pkg/x/io/io.go b/pkg/x/io/io.go index c4ba4bfe4c59..1fe3d6c0c2fc 100644 --- a/pkg/x/io/io.go +++ b/pkg/x/io/io.go @@ -30,6 +30,36 @@ func NewReadSeekerAt(r io.Reader) (ReadSeekerAt, error) { return bytes.NewReader(buff.Bytes()), nil } +func NewReadSeekerAtWithSize(r io.Reader) (ReadSeekerAt, int64, error) { + rsa, err := NewReadSeekerAt(r) + if err != nil { + return nil, 0, err + } + + br, ok := rsa.(*bytes.Reader) + if ok { + return rsa, br.Size(), nil + } + + size, err := getSeekerSize(rsa) + if err != nil { + return nil, 0, xerrors.Errorf("get size error: %w", err) + } + return rsa, size, nil +} + +func getSeekerSize(s io.Seeker) (int64, error) { + size, err := s.Seek(0, io.SeekEnd) + if err != nil { + return 0, xerrors.Errorf("seek error: %w", err) + } + + if _, err = s.Seek(0, io.SeekStart); err != nil { + return 0, xerrors.Errorf("seek error: %w", err) + } + return size, nil +} + // NopCloser returns a ReadSeekCloserAt with a no-op Close method wrapping // the provided Reader r. func NopCloser(r ReadSeekerAt) ReadSeekCloserAt {