Skip to content

Commit

Permalink
feat(misconf): add support for wildcard ignores (#6414)
Browse files Browse the repository at this point in the history
  • Loading branch information
nikpivkin authored Apr 3, 2024
1 parent 74e4c6e commit 8dd0fcd
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 65 deletions.
13 changes: 12 additions & 1 deletion docs/docs/scanner/misconfiguration/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -547,4 +547,15 @@ module "s3_bucket" {
bucket = each.value
}
```
[custom]: custom/index.md

#### Support for Wildcards

You can use wildcards in the `ws` (workspace) and `ignore` sections of the ignore rules.

```tf
# trivy:ignore:aws-s3-*:ws:dev-*
```

This example ignores all checks starting with `aws-s3-` for workspaces matching the pattern `dev-*`.

[custom]: custom/index.md
37 changes: 35 additions & 2 deletions pkg/iac/ignore/rule.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package ignore

import (
"regexp"
"slices"
"strings"
"time"

"github.com/samber/lo"
Expand All @@ -10,7 +12,7 @@ import (
)

// Ignorer represents a function that checks if the rule should be ignored.
type Ignorer func(resultMeta types.Metadata, param any) bool
type Ignorer func(resultMeta types.Metadata, ignoredParam any) bool

type Rules []Rule

Expand Down Expand Up @@ -88,11 +90,42 @@ func defaultIgnorers(ids []string) map[string]Ignorer {
return map[string]Ignorer{
"id": func(_ types.Metadata, param any) bool {
id, ok := param.(string)
return ok && (id == "*" || len(ids) == 0 || slices.Contains(ids, id))
if !ok {
return false
}
if id == "*" || len(ids) == 0 {
return true
}

return slices.ContainsFunc(ids, func(s string) bool {
return MatchPattern(s, id)
})
},
"exp": func(_ types.Metadata, param any) bool {
expiry, ok := param.(time.Time)
return ok && time.Now().Before(expiry)
},
}
}

// MatchPattern checks if the pattern string matches the input pattern.
// The wildcard '*' in the pattern matches any sequence of characters.
func MatchPattern(input, pattern string) bool {
matched, err := regexp.MatchString(regexpFromPattern(pattern), input)
return err == nil && matched
}

func regexpFromPattern(pattern string) string {
parts := strings.Split(pattern, "*")
if len(parts) == 1 {
return "^" + pattern + "$"
}
var sb strings.Builder
for i, literal := range parts {
if i > 0 {
sb.WriteString(".*")
}
sb.WriteString(regexp.QuoteMeta(literal))
}
return "^" + sb.String() + "$"
}
61 changes: 61 additions & 0 deletions pkg/iac/ignore/rule_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,24 @@ func TestRules_Ignore(t *testing.T) {
},
shouldIgnore: false,
},
{
name: "with valid wildcard",
src: `#trivy:ignore:rule-*`,
args: args{
metadata: metadataWithLine(filename, 2),
ids: []string{"rule-1"},
},
shouldIgnore: true,
},
{
name: "with non-valid wildcard",
src: `#trivy:ignore:rule-1-*d`,
args: args{
metadata: metadataWithLine(filename, 2),
ids: []string{"rule-1-abc"},
},
shouldIgnore: false,
},
}

for _, tt := range tests {
Expand Down Expand Up @@ -220,6 +238,27 @@ func TestRules_IgnoreWithCustomIgnorer(t *testing.T) {
},
shouldIgnore: true,
},
{
name: "with wildcard",
src: `#trivy:ignore:rule-1:ws:dev-*`,
parser: &ignore.StringMatchParser{
SectionKey: "ws",
},
args: args{
metadata: metadataWithLine(filename, 2),
ids: []string{"rule-1"},
ignorers: map[string]ignore.Ignorer{
"ws": func(_ types.Metadata, param any) bool {
ws, ok := param.(string)
if !ok {
return false
}
return ignore.MatchPattern("dev-stage1", ws)
},
},
},
shouldIgnore: true,
},
{
name: "bad",
src: `#trivy:ignore:rule-1:ws:prod`,
Expand Down Expand Up @@ -251,3 +290,25 @@ func TestRules_IgnoreWithCustomIgnorer(t *testing.T) {
})
}
}

func TestMatchPattern(t *testing.T) {
tests := []struct {
input string
pattern string
expected bool
}{
{"foo-test-bar", "*-test-*", true},
{"foo-test-bar", "*-example-*", false},
{"test", "*test", true},
{"example", "test", false},
{"example-test", "*-test*", true},
{"example-test", "*example-*", true},
}

for _, tc := range tests {
t.Run(tc.input+":"+tc.pattern, func(t *testing.T) {
got := ignore.MatchPattern(tc.input, tc.pattern)
assert.Equal(t, tc.expected, got)
})
}
}
38 changes: 22 additions & 16 deletions pkg/iac/scanners/terraform/executor/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,22 +99,8 @@ func (e *Executor) Execute(modules terraform.Modules) (scan.Results, error) {
}

ignorers := map[string]ignore.Ignorer{
"ws": func(_ types.Metadata, param any) bool {
ws, ok := param.(string)
if !ok {
return false
}

return ws == e.workspaceName
},
"ignore": func(resultMeta types.Metadata, param any) bool {
params, ok := param.(map[string]string)
if !ok {
return false
}

return ignoreByParams(params, modules, &resultMeta)
},
"ws": workspaceIgnorer(e.workspaceName),
"ignore": attributeIgnorer(modules),
}

results.Ignore(ignores, ignorers)
Expand Down Expand Up @@ -229,3 +215,23 @@ func ignoreByParams(params map[string]string, modules terraform.Modules, m *type
}
return true
}

func workspaceIgnorer(ws string) ignore.Ignorer {
return func(_ types.Metadata, param any) bool {
ignoredWorkspace, ok := param.(string)
if !ok {
return false
}
return ignore.MatchPattern(ws, ignoredWorkspace)
}
}

func attributeIgnorer(modules terraform.Modules) ignore.Ignorer {
return func(resultMeta types.Metadata, param any) bool {
params, ok := param.(map[string]string)
if !ok {
return false
}
return ignoreByParams(params, modules, &resultMeta)
}
}
94 changes: 48 additions & 46 deletions pkg/iac/scanners/terraform/ignore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,10 @@ data "aws_iam_policy_document" "test_policy" {
resources = ["*"] # trivy:ignore:aws-iam-enforce-mfa
}
}
`, assertLength: 0}}
`,
assertLength: 0,
},
}

reg := rules.Register(exampleRule)
defer rules.Deregister(reg)
Expand All @@ -612,16 +615,53 @@ data "aws_iam_policy_document" "test_policy" {
}
}

func Test_IgnoreIgnoreWithExpiryAndWorkspaceAndWorkspaceSupplied(t *testing.T) {
func Test_IgnoreByWorkspace(t *testing.T) {
reg := rules.Register(exampleRule)
defer rules.Deregister(reg)

results := scanHCLWithWorkspace(t, `
# tfsec:ignore:aws-service-abc123:exp:2221-01-02:ws:testworkspace
resource "bad" "my-rule" {
}
`, "testworkspace")
assert.Len(t, results.GetFailed(), 0)
tests := []struct {
name string
src string
expectedFailed int
}{
{
name: "with expiry and workspace",
src: `# tfsec:ignore:aws-service-abc123:exp:2221-01-02:ws:testworkspace
resource "bad" "my-rule" {}`,
expectedFailed: 0,
},
{
name: "bad workspace",
src: `# tfsec:ignore:aws-service-abc123:exp:2221-01-02:ws:otherworkspace
resource "bad" "my-rule" {}`,
expectedFailed: 1,
},
{
name: "with expiry and workspace, trivy prefix",
src: `# trivy:ignore:aws-service-abc123:exp:2221-01-02:ws:testworkspace
resource "bad" "my-rule" {}`,
expectedFailed: 0,
},
{
name: "bad workspace, trivy prefix",
src: `# trivy:ignore:aws-service-abc123:exp:2221-01-02:ws:otherworkspace
resource "bad" "my-rule" {}`,
expectedFailed: 1,
},
{
name: "workspace with wildcard",
src: `# tfsec:ignore:*:ws:test*
resource "bad" "my-rule" {}`,
expectedFailed: 0,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
results := scanHCLWithWorkspace(t, tt.src, "testworkspace")
assert.Len(t, results.GetFailed(), tt.expectedFailed)
})
}
}

func Test_IgnoreInline(t *testing.T) {
Expand All @@ -636,19 +676,6 @@ func Test_IgnoreInline(t *testing.T) {
assert.Len(t, results.GetFailed(), 0)
}

func Test_IgnoreIgnoreWithExpiryAndWorkspaceButWrongWorkspaceSupplied(t *testing.T) {
reg := rules.Register(exampleRule)
defer rules.Deregister(reg)

results := scanHCLWithWorkspace(t, `
# tfsec:ignore:aws-service-abc123:exp:2221-01-02:ws:otherworkspace
resource "bad" "my-rule" {
}
`, "testworkspace")
assert.Len(t, results.GetFailed(), 1)
}

func Test_IgnoreWithAliasCodeStillIgnored(t *testing.T) {
reg := rules.Register(exampleRule)
defer rules.Deregister(reg)
Expand All @@ -662,31 +689,6 @@ resource "bad" "my-rule" {
assert.Len(t, results.GetFailed(), 0)
}

func Test_TrivyIgnoreIgnoreWithExpiryAndWorkspaceAndWorkspaceSupplied(t *testing.T) {
reg := rules.Register(exampleRule)
defer rules.Deregister(reg)

results := scanHCLWithWorkspace(t, `
# trivy:ignore:aws-service-abc123:exp:2221-01-02:ws:testworkspace
resource "bad" "my-rule" {
}
`, "testworkspace")
assert.Len(t, results.GetFailed(), 0)
}

func Test_TrivyIgnoreIgnoreWithExpiryAndWorkspaceButWrongWorkspaceSupplied(t *testing.T) {
reg := rules.Register(exampleRule)
defer rules.Deregister(reg)

results := scanHCLWithWorkspace(t, `
# trivy:ignore:aws-service-abc123:exp:2221-01-02:ws:otherworkspace
resource "bad" "my-rule" {
}
`, "testworkspace")
assert.Len(t, results.GetFailed(), 1)
}

func Test_TrivyIgnoreWithAliasCodeStillIgnored(t *testing.T) {
reg := rules.Register(exampleRule)
defer rules.Deregister(reg)
Expand Down

0 comments on commit 8dd0fcd

Please sign in to comment.