diff --git a/docs/docs/scanner/misconfiguration/index.md b/docs/docs/scanner/misconfiguration/index.md index 4cd175cf58c3..7419e7757af9 100644 --- a/docs/docs/scanner/misconfiguration/index.md +++ b/docs/docs/scanner/misconfiguration/index.md @@ -449,9 +449,9 @@ From the Terraform [docs](https://developer.hashicorp.com/terraform/cli/config/c If multiple variables evaluate to the same hostname, Trivy will choose the environment variable name where the dashes have not been encoded as double underscores. -### Skipping resources by inline comments +### Skipping detected misconfigurations by inline comments -Trivy supports ignoring misconfigured resources by inline comments for Terraform, CloudFormation and Helm configuration files only. +Trivy supports ignoring detected misconfigurations by inline comments for Terraform, CloudFormation (YAML), Helm and Dockerfile configuration files only. In cases where Trivy can detect comments of a specific format immediately adjacent to resource definitions, it is possible to ignore findings from a single source of resource definition (in contrast to `.trivyignore`, which has a directory-wide scope on all of the files scanned). The format for these comments is `trivy:ignore:` immediately following the format-specific line-comment [token](https://developer.hashicorp.com/terraform/language/syntax/configuration#comments). @@ -519,6 +519,13 @@ Example for Helm: imagePullPolicy: "Always" ``` +Example for Dockerfile: +```Dockerfile +FROM scratch +# trivy:ignore:AVD-DS-0022 +MAINTAINER moby@example.com +``` + #### Expiration Date You can specify the expiration date of the ignore rule in `yyyy-mm-dd` format. This is a useful feature when you want to make sure that an ignored issue is not forgotten and worth revisiting in the future. For example: diff --git a/pkg/iac/scanners/dockerfile/scanner_test.go b/pkg/iac/scanners/dockerfile/scanner_test.go index 3182b6d02e6f..44ab743e1778 100644 --- a/pkg/iac/scanners/dockerfile/scanner_test.go +++ b/pkg/iac/scanners/dockerfile/scanner_test.go @@ -3,7 +3,9 @@ package dockerfile_test import ( "bytes" "context" + "strings" "testing" + "testing/fstest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -630,3 +632,73 @@ COPY --from=dep /binary /` } } + +func Test_IgnoreByInlineComments(t *testing.T) { + tests := []struct { + name string + src string + expected bool + }{ + { + name: "without ignore rule", + src: `FROM scratch +MAINTAINER moby@example.com`, + expected: true, + }, + { + name: "with ignore rule", + src: `FROM scratch +# trivy:ignore:USER-TEST-0001 +MAINTAINER moby@example.com`, + expected: false, + }, + } + + check := `# METADATA +# title: test +# schemas: +# - input: schema["dockerfile"] +# custom: +# avd_id: USER-TEST-0001 +# short_code: maintainer-deprecated +# input: +# selector: +# - type: dockerfile +package user.test0001 + +import rego.v1 + +get_maintainer contains cmd if { + cmd := input.Stages[_].Commands[_] + cmd.Cmd == "maintainer" +} + +deny contains res if { + cmd := get_maintainer[_] + msg := sprintf("MAINTAINER should not be used: 'MAINTAINER %s'", [cmd.Value[0]]) + res := result.new(msg, cmd) +} +` + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fsys := fstest.MapFS{ + "Dockerfile": &fstest.MapFile{Data: []byte(tt.src)}, + } + + scanner := dockerfile.NewScanner( + rego.WithPolicyReader(strings.NewReader(check)), + rego.WithPolicyNamespaces("user"), + rego.WithEmbeddedLibraries(true), + rego.WithRegoErrorLimits(0), + ) + results, err := scanner.ScanFS(context.TODO(), fsys, ".") + require.NoError(t, err) + if tt.expected { + testutil.AssertRuleFound(t, "dockerfile-general-maintainer-deprecated", results, "") + } else { + testutil.AssertRuleNotFailed(t, "dockerfile-general-maintainer-deprecated", results, "") + } + }) + } +} diff --git a/pkg/iac/scanners/generic/scanner.go b/pkg/iac/scanners/generic/scanner.go index 90f0103f719d..a2d72d77a63a 100644 --- a/pkg/iac/scanners/generic/scanner.go +++ b/pkg/iac/scanners/generic/scanner.go @@ -12,8 +12,10 @@ import ( "sync" "github.com/BurntSushi/toml" + "github.com/samber/lo" "gopkg.in/yaml.v3" + "github.com/aquasecurity/trivy/pkg/iac/ignore" "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" @@ -122,9 +124,18 @@ func (s *GenericScanner) ScanFS(ctx context.Context, fsys fs.FS, dir string) (sc return nil, err } results.SetSourceAndFilesystem("", fsys, false) + + if err := s.applyIgnoreRules(fsys, results); err != nil { + return nil, err + } + return results, nil } +func (s *GenericScanner) supportsIgnoreRules() bool { + return s.source == types.SourceDockerfile +} + func (s *GenericScanner) parseFS(ctx context.Context, fsys fs.FS, path string) (map[string]any, error) { files := make(map[string]any) if err := fs.WalkDir(fsys, filepath.ToSlash(path), func(path string, entry fs.DirEntry, err error) error { @@ -173,6 +184,27 @@ func (s *GenericScanner) initRegoScanner(srcFS fs.FS) (*rego.Scanner, error) { return regoScanner, nil } +func (s *GenericScanner) applyIgnoreRules(fsys fs.FS, results scan.Results) error { + if !s.supportsIgnoreRules() { + return nil + } + + uniqueFiles := lo.Uniq(lo.Map(results.GetFailed(), func(res scan.Result, _ int) string { + return res.Metadata().Range().GetFilename() + })) + + for _, filename := range uniqueFiles { + content, err := fs.ReadFile(fsys, filename) + if err != nil { + return err + } + + ignoreRules := ignore.Parse(string(content), filename, "") + results.Ignore(ignoreRules, nil) + } + return nil +} + func parseJson(ctx context.Context, r io.Reader, _ string) (any, error) { var target any if err := json.NewDecoder(r).Decode(&target); err != nil {