Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(misconf): add support for wildcard ignores #6414

Merged
merged 3 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -122,22 +122,8 @@ func (e *Executor) Execute(modules terraform.Modules) (scan.Results, Metrics, er
}

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 @@ -268,3 +254,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