test: replace Go checks with Rego (#7867)

Signed-off-by: nikpivkin <nikita.pivkin@smartforce.io>
This commit is contained in:
Nikita Pivkin
2024-11-26 04:04:53 +06:00
committed by GitHub
parent e9a899a3cf
commit 5a93a7736b
11 changed files with 761 additions and 1538 deletions

View File

@@ -8,6 +8,7 @@ import (
"testing" "testing"
"github.com/liamg/memoryfs" "github.com/liamg/memoryfs"
"github.com/samber/lo"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -36,6 +37,16 @@ func AssertRuleNotFound(t *testing.T, ruleID string, results scan.Results, messa
assert.False(t, found, append([]any{message}, args...)...) assert.False(t, found, append([]any{message}, args...)...)
} }
func AssertRuleNotFailed(t *testing.T, ruleID string, results scan.Results, message string, args ...any) {
failedExists := ruleIDInResults(ruleID, results.GetFailed())
assert.False(t, failedExists, append([]any{message}, args...)...)
passedResults := lo.Filter(results, func(res scan.Result, _ int) bool {
return res.Status() == scan.StatusPassed || res.Status() == scan.StatusIgnored
})
passedExists := ruleIDInResults(ruleID, passedResults)
assert.True(t, passedExists, append([]any{message}, args...)...)
}
func ruleIDInResults(ruleID string, results scan.Results) bool { func ruleIDInResults(ruleID string, results scan.Results) bool {
for _, res := range results { for _, res := range results {
if res.Rule().LongID() == ruleID { if res.Rule().LongID() == ruleID {

View File

@@ -1,57 +1,54 @@
package terraform package terraform
import ( import (
"strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/internal/testutil"
"github.com/aquasecurity/trivy/pkg/iac/providers" "github.com/aquasecurity/trivy/pkg/iac/rego"
"github.com/aquasecurity/trivy/pkg/iac/rules"
"github.com/aquasecurity/trivy/pkg/iac/scan"
"github.com/aquasecurity/trivy/pkg/iac/severity"
"github.com/aquasecurity/trivy/pkg/iac/terraform"
) )
func Test_ResourcesWithCount(t *testing.T) { func Test_ResourcesWithCount(t *testing.T) {
var tests = []struct { var tests = []struct {
name string name string
source string source string
expectedResults int expected int
}{ }{
{ {
name: "unspecified count defaults to 1", name: "unspecified count defaults to 1",
source: ` source: `
resource "bad" "this" {} resource "aws_s3_bucket" "test" {}
`, `,
expectedResults: 1, expected: 1,
}, },
{ {
name: "count is literal 1", name: "count is literal 1",
source: ` source: `
resource "bad" "this" { resource "aws_s3_bucket" "test" {
count = 1 count = 1
} }
`, `,
expectedResults: 1, expected: 1,
}, },
{ {
name: "count is literal 99", name: "count is literal 99",
source: ` source: `
resource "bad" "this" { resource "aws_s3_bucket" "test" {
count = 99 count = 99
} }
`, `,
expectedResults: 99, expected: 99,
}, },
{ {
name: "count is literal 0", name: "count is literal 0",
source: ` source: `
resource "bad" "this" { resource "aws_s3_bucket" "test" {
count = 0 count = 0
} }
`, `,
expectedResults: 0, expected: 0,
}, },
{ {
name: "count is 0 from variable", name: "count is 0 from variable",
@@ -59,11 +56,11 @@ func Test_ResourcesWithCount(t *testing.T) {
variable "count" { variable "count" {
default = 0 default = 0
} }
resource "bad" "this" { resource "aws_s3_bucket" "test" {
count = var.count count = var.count
} }
`, `,
expectedResults: 0, expected: 0,
}, },
{ {
name: "count is 1 from variable", name: "count is 1 from variable",
@@ -71,22 +68,22 @@ func Test_ResourcesWithCount(t *testing.T) {
variable "count" { variable "count" {
default = 1 default = 1
} }
resource "bad" "this" { resource "aws_s3_bucket" "test" {
count = var.count count = var.count
} }
`, `,
expectedResults: 1, expected: 1,
}, },
{ {
name: "count is 1 from variable without default", name: "count is 1 from variable without default",
source: ` source: `
variable "count" { variable "count" {
} }
resource "bad" "this" { resource "aws_s3_bucket" "test" {
count = var.count count = var.count
} }
`, `,
expectedResults: 1, expected: 1,
}, },
{ {
name: "count is 0 from conditional", name: "count is 0 from conditional",
@@ -94,11 +91,11 @@ func Test_ResourcesWithCount(t *testing.T) {
variable "enabled" { variable "enabled" {
default = false default = false
} }
resource "bad" "this" { resource "aws_s3_bucket" "test" {
count = var.enabled ? 1 : 0 count = var.enabled ? 1 : 0
} }
`, `,
expectedResults: 0, expected: 0,
}, },
{ {
name: "count is 1 from conditional", name: "count is 1 from conditional",
@@ -106,11 +103,11 @@ func Test_ResourcesWithCount(t *testing.T) {
variable "enabled" { variable "enabled" {
default = true default = true
} }
resource "bad" "this" { resource "aws_s3_bucket" "test" {
count = var.enabled ? 1 : 0 count = var.enabled ? 1 : 0
} }
`, `,
expectedResults: 1, expected: 1,
}, },
{ {
name: "issue 962", name: "issue 962",
@@ -120,18 +117,18 @@ func Test_ResourcesWithCount(t *testing.T) {
ok = true ok = true
} }
resource "bad" "bad" { resource "aws_s3_bucket" "test" {
secure = something.else[0].ok bucket = something.else[0].ok ? "test" : ""
} }
`, `,
expectedResults: 0, expected: 0,
}, },
{ {
name: "Test use of count.index", name: "Test use of count.index",
source: ` source: `
resource "bad" "thing" { resource "aws_s3_bucket" "test" {
count = 1 count = 1
secure = var.things[count.index]["ok"] bucket = var.things[count.index]["ok"] ? "test" : ""
} }
variable "things" { variable "things" {
@@ -145,49 +142,23 @@ variable "things" {
] ]
} }
`, `,
expectedResults: 0, expected: 0,
}, },
} }
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
r1 := scan.Rule{ results := scanHCL(t, test.source,
Provider: providers.AWSProvider, rego.WithPolicyReader(strings.NewReader(emptyBucketCheck)),
Service: "service", rego.WithPolicyNamespaces("user"),
ShortCode: "abc123", )
Severity: severity.High,
CustomChecks: scan.CustomChecks{ assert.Len(t, results.GetFailed(), test.expected)
Terraform: &scan.TerraformCustomCheck{
RequiredLabels: []string{"bad"}, if test.expected > 0 {
Check: func(resourceBlock *terraform.Block, _ *terraform.Module) (results scan.Results) { testutil.AssertRuleFound(t, "aws-s3-non-empty-bucket", results, "false negative found")
if resourceBlock.GetAttribute("secure").IsTrue() {
return
}
results.Add(
"example problem",
resourceBlock,
)
return
},
},
},
}
reg := rules.Register(r1)
defer rules.Deregister(reg)
results := scanHCL(t, test.source)
var include string
var exclude string
if test.expectedResults > 0 {
include = r1.LongID()
} else { } else {
exclude = r1.LongID() testutil.AssertRuleNotFound(t, "aws-s3-non-empty-bucket", results, "false positive found")
}
assert.Len(t, results.GetFailed(), test.expectedResults)
if include != "" {
testutil.AssertRuleFound(t, include, results, "false negative found")
}
if exclude != "" {
testutil.AssertRuleNotFound(t, exclude, results, "false positive found")
} }
}) })
} }

View File

@@ -1,27 +1,20 @@
package terraform package terraform
import ( import (
"context" "strings"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/internal/testutil"
"github.com/aquasecurity/trivy/pkg/iac/rules" "github.com/aquasecurity/trivy/pkg/iac/rego"
"github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/executor"
"github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser"
) )
func Test_DeterministicResults(t *testing.T) { func Test_DeterministicResults(t *testing.T) {
fsys := testutil.CreateFS(t, map[string]string{
reg := rules.Register(badRule)
defer rules.Deregister(reg)
fs := testutil.CreateFS(t, map[string]string{
"first.tf": ` "first.tf": `
resource "problem" "uhoh" { resource "aws_s3_bucket" "test" {
bad = true for_each = other.thing
for_each = other.thing
} }
`, `,
"second.tf": ` "second.tf": `
@@ -40,12 +33,11 @@ locals {
}) })
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) results, err := scanFS(fsys, ".",
err := p.ParseFS(context.TODO(), ".") rego.WithPolicyReader(strings.NewReader(emptyBucketCheck)),
rego.WithPolicyNamespaces("user"),
)
require.NoError(t, err) require.NoError(t, err)
modules, _, err := p.EvaluateAll(context.TODO())
require.NoError(t, err)
results, _ := executor.New().Execute(modules)
require.Len(t, results.GetFailed(), 2) require.Len(t, results.GetFailed(), 2)
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,22 +1,19 @@
package terraform package terraform
import ( import (
"strings"
"testing" "testing"
"github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/internal/testutil"
"github.com/aquasecurity/trivy/pkg/iac/providers" "github.com/aquasecurity/trivy/pkg/iac/rego"
"github.com/aquasecurity/trivy/pkg/iac/rules"
"github.com/aquasecurity/trivy/pkg/iac/scan"
"github.com/aquasecurity/trivy/pkg/iac/severity"
"github.com/aquasecurity/trivy/pkg/iac/terraform"
) )
func TestScanningJSON(t *testing.T) { func TestScanningJSON(t *testing.T) {
var tests = []struct { var tests = []struct {
name string name string
source string source string
shouldFail bool expected bool
}{ }{
{ {
name: "check results are picked up in tf json configs", name: "check results are picked up in tf json configs",
@@ -29,16 +26,14 @@ func TestScanningJSON(t *testing.T) {
} }
}, },
"resource": { "resource": {
"bad": { "aws_s3_bucket": {
"thing": { "test": {
"type": "ingress", "bucket": ""
"cidr_blocks": ["0.0.0.0/0"],
"description": "testing"
} }
} }
} }
}`, }`,
shouldFail: true, expected: true,
}, },
{ {
name: "check attributes are checked in tf json configs", name: "check attributes are checked in tf json configs",
@@ -51,52 +46,27 @@ func TestScanningJSON(t *testing.T) {
} }
}, },
"resource": { "resource": {
"bad": { "aws_s3_bucket": {
"or_not": { "test": {
"secure": true "bucket": "test"
} }
} }
} }
}`, }`,
shouldFail: false, expected: false,
}, },
} }
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
r1 := scan.Rule{ results := scanJSON(t, test.source,
Provider: providers.AWSProvider, rego.WithPolicyReader(strings.NewReader(emptyBucketCheck)),
Service: "service", rego.WithPolicyNamespaces("user"),
ShortCode: "abc123", )
Severity: severity.High, if test.expected {
CustomChecks: scan.CustomChecks{ testutil.AssertRuleFound(t, "aws-s3-non-empty-bucket", results, "false negative found")
Terraform: &scan.TerraformCustomCheck{
RequiredLabels: []string{"bad"},
Check: func(resourceBlock *terraform.Block, _ *terraform.Module) (results scan.Results) {
if resourceBlock.GetAttribute("secure").IsTrue() {
return
}
results.Add("something", resourceBlock)
return
},
},
},
}
reg := rules.Register(r1)
defer rules.Deregister(reg)
results := scanJSON(t, test.source)
var include, exclude string
if test.shouldFail {
include = r1.LongID()
} else { } else {
exclude = r1.LongID() testutil.AssertRuleNotFound(t, "aws-s3-non-empty-bucket", results, "false positive found")
}
if include != "" {
testutil.AssertRuleFound(t, include, results, "false negative found")
}
if exclude != "" {
testutil.AssertRuleNotFound(t, exclude, results, "false positive found")
} }
}) })
} }

View File

@@ -1,52 +1,26 @@
package terraform package terraform
import ( import (
"context" "strings"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy-checks/checks/cloud/aws/iam"
"github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/internal/testutil"
"github.com/aquasecurity/trivy/pkg/iac/providers" "github.com/aquasecurity/trivy/pkg/iac/rego"
"github.com/aquasecurity/trivy/pkg/iac/rules"
"github.com/aquasecurity/trivy/pkg/iac/scan"
"github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/executor"
"github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser"
"github.com/aquasecurity/trivy/pkg/iac/severity"
"github.com/aquasecurity/trivy/pkg/iac/terraform"
) )
var badRule = scan.Rule{ func Test_Modules(t *testing.T) {
Provider: providers.AWSProvider, tests := []struct {
Service: "service", name string
ShortCode: "abc", files map[string]string
Summary: "A stupid example check for a test.", expected bool
Impact: "You will look stupid", }{
Resolution: "Don't do stupid stuff", {
Explanation: "Bad should not be set.", // IMPORTANT: if this test is failing, you probably need to set the version of go-cty in go.mod to the same version that hcl uses.
Severity: severity.High, name: "go-cty compatibility issue",
CustomChecks: scan.CustomChecks{ files: map[string]string{
Terraform: &scan.TerraformCustomCheck{ "/project/main.tf": `
RequiredTypes: []string{"resource"},
RequiredLabels: []string{"problem"},
Check: func(resourceBlock *terraform.Block, _ *terraform.Module) (results scan.Results) {
if attr := resourceBlock.GetAttribute("bad"); attr.IsTrue() {
results.Add("bad", attr)
}
return
},
},
},
}
// IMPORTANT: if this test is failing, you probably need to set the version of go-cty in go.mod to the same version that hcl uses.
func Test_GoCtyCompatibilityIssue(t *testing.T) {
registered := rules.Register(badRule)
defer rules.Deregister(registered)
fs := testutil.CreateFS(t, map[string]string{
"/project/main.tf": `
data "aws_vpc" "default" { data "aws_vpc" "default" {
default = true default = true
} }
@@ -54,10 +28,8 @@ data "aws_vpc" "default" {
module "test" { module "test" {
source = "../modules/problem/" source = "../modules/problem/"
cidr_block = data.aws_vpc.default.cidr_block cidr_block = data.aws_vpc.default.cidr_block
} }`,
`, "/modules/problem/main.tf": `variable "cidr_block" {}
"/modules/problem/main.tf": `
variable "cidr_block" {}
variable "open" { variable "open" {
default = false default = false
@@ -76,589 +48,333 @@ resource "aws_security_group" "this" {
} }
} }
resource "problem" "uhoh" { resource "aws_s3_bucket" "test" {}`,
bad = true },
} expected: true,
`, },
}) {
name: "misconfig in sibling directory module",
p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) files: map[string]string{
err := p.ParseFS(context.TODO(), "project") "/project/main.tf": `
require.NoError(t, err)
modules, _, err := p.EvaluateAll(context.TODO())
require.NoError(t, err)
results, err := executor.New().Execute(modules)
require.NoError(t, err)
testutil.AssertRuleFound(t, badRule.LongID(), results, "")
}
func Test_ProblemInModuleInSiblingDir(t *testing.T) {
registered := rules.Register(badRule)
defer rules.Deregister(registered)
fs := testutil.CreateFS(t, map[string]string{
"/project/main.tf": `
module "something" { module "something" {
source = "../modules/problem" source = "../modules/problem"
} }
`, `,
"modules/problem/main.tf": ` "modules/problem/main.tf": `
resource "problem" "uhoh" { resource "aws_s3_bucket" "test" {}`,
bad = true },
} expected: true,
`}, },
) {
name: "ignore misconfig in module",
p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) files: map[string]string{
err := p.ParseFS(context.TODO(), "project") "/project/main.tf": `
require.NoError(t, err) #tfsec:ignore:aws-s3-non-empty-bucket
modules, _, err := p.EvaluateAll(context.TODO())
require.NoError(t, err)
results, err := executor.New().Execute(modules)
require.NoError(t, err)
testutil.AssertRuleFound(t, badRule.LongID(), results, "")
}
func Test_ProblemInModuleIgnored(t *testing.T) {
registered := rules.Register(badRule)
defer rules.Deregister(registered)
fs := testutil.CreateFS(t, map[string]string{
"/project/main.tf": `
#tfsec:ignore:aws-service-abc
module "something" { module "something" {
source = "../modules/problem" source = "../modules/problem"
} }
`, `,
"modules/problem/main.tf": ` "modules/problem/main.tf": `
resource "problem" "uhoh" { resource "aws_s3_bucket" "test" {}
bad = true `,
} },
`}, expected: false,
) },
{
p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) name: "misconfig in subdirectory module",
err := p.ParseFS(context.TODO(), "project") files: map[string]string{
require.NoError(t, err) "project/main.tf": `
modules, _, err := p.EvaluateAll(context.TODO())
require.NoError(t, err)
results, err := executor.New().Execute(modules)
require.NoError(t, err)
testutil.AssertRuleNotFound(t, badRule.LongID(), results, "")
}
func Test_ProblemInModuleInSubdirectory(t *testing.T) {
registered := rules.Register(badRule)
defer rules.Deregister(registered)
fs := testutil.CreateFS(t, map[string]string{
"project/main.tf": `
module "something" { module "something" {
source = "./modules/problem" source = "./modules/problem"
} }
`, `,
"project/modules/problem/main.tf": ` "project/modules/problem/main.tf": `
resource "problem" "uhoh" { resource "aws_s3_bucket" "test" {}
bad = true `,
} },
`}) expected: true,
},
p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) {
err := p.ParseFS(context.TODO(), "project") name: "misconfig in parent directory module",
require.NoError(t, err) files: map[string]string{
modules, _, err := p.EvaluateAll(context.TODO()) "project/main.tf": `
require.NoError(t, err)
results, err := executor.New().Execute(modules)
require.NoError(t, err)
testutil.AssertRuleFound(t, badRule.LongID(), results, "")
}
func Test_ProblemInModuleInParentDir(t *testing.T) {
registered := rules.Register(badRule)
defer rules.Deregister(registered)
fs := testutil.CreateFS(t, map[string]string{
"project/main.tf": `
module "something" { module "something" {
source = "../problem" source = "../problem"
} }
`, `,
"problem/main.tf": ` "problem/main.tf": `
resource "problem" "uhoh" { resource "aws_s3_bucket" "test" {}
bad = true `},
} expected: true,
`}) },
{
p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) name: "misconfig in reused module",
err := p.ParseFS(context.TODO(), "project") files: map[string]string{
require.NoError(t, err) "project/main.tf": `
modules, _, err := p.EvaluateAll(context.TODO())
require.NoError(t, err)
results, err := executor.New().Execute(modules)
require.NoError(t, err)
testutil.AssertRuleFound(t, badRule.LongID(), results, "")
}
func Test_ProblemInModuleReuse(t *testing.T) {
registered := rules.Register(badRule)
defer rules.Deregister(registered)
fs := testutil.CreateFS(t, map[string]string{
"project/main.tf": `
module "something_good" { module "something_good" {
source = "../modules/problem" source = "../modules/problem"
bad = false bucket = "test"
} }
module "something_bad" { module "something_bad" {
source = "../modules/problem" source = "../modules/problem"
bad = true bucket = ""
} }
`, `,
"modules/problem/main.tf": ` "modules/problem/main.tf": `
variable "bad" { variable "bucket" {}
default = false
resource "aws_s3_bucket" "test" {
bucket = var.bucket
} }
resource "problem" "uhoh" { `},
bad = var.bad expected: true,
} },
`}) {
name: "misconfig in nested module",
p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) files: map[string]string{
err := p.ParseFS(context.TODO(), "project") "project/main.tf": `
require.NoError(t, err)
modules, _, err := p.EvaluateAll(context.TODO())
require.NoError(t, err)
results, err := executor.New().Execute(modules)
require.NoError(t, err)
testutil.AssertRuleFound(t, badRule.LongID(), results, "")
}
func Test_ProblemInNestedModule(t *testing.T) {
registered := rules.Register(badRule)
defer rules.Deregister(registered)
fs := testutil.CreateFS(t, map[string]string{
"project/main.tf": `
module "something" { module "something" {
source = "../modules/a" source = "../modules/a"
} }
`, `,
"modules/a/main.tf": ` "modules/a/main.tf": `
module "something" { module "something" {
source = "../../modules/b" source = "../../modules/b"
} }
`, `,
"modules/b/main.tf": ` "modules/b/main.tf": `
module "something" { module "something" {
source = "../c" source = "../c"
} }
`, `,
"modules/c/main.tf": ` "modules/c/main.tf": `resource "aws_s3_bucket" "test" {}`,
resource "problem" "uhoh" { },
bad = true expected: true,
} },
`, {
}) name: "misconfig in reused nested module",
files: map[string]string{
p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) "project/main.tf": `
err := p.ParseFS(context.TODO(), "project")
require.NoError(t, err)
modules, _, err := p.EvaluateAll(context.TODO())
require.NoError(t, err)
results, err := executor.New().Execute(modules)
require.NoError(t, err)
testutil.AssertRuleFound(t, badRule.LongID(), results, "")
}
func Test_ProblemInReusedNestedModule(t *testing.T) {
registered := rules.Register(badRule)
defer rules.Deregister(registered)
fs := testutil.CreateFS(t, map[string]string{
"project/main.tf": `
module "something" { module "something" {
source = "../modules/a" source = "../modules/a"
bad = false bucket = "test"
} }
module "something-bad" { module "something-bad" {
source = "../modules/a" source = "../modules/a"
bad = true bucket = ""
} }
`, `,
"modules/a/main.tf": ` "modules/a/main.tf": `
variable "bad" { variable "bucket" {}
default = false
}
module "something" { module "something" {
source = "../../modules/b" source = "../../modules/b"
bad = var.bad bucket = var.bucket
} }
`, `,
"modules/b/main.tf": ` "modules/b/main.tf": `
variable "bad" { variable "bucket" {}
default = false
}
module "something" { module "something" {
source = "../c" source = "../c"
bad = var.bad bucket = var.bad
} }
`, `,
"modules/c/main.tf": ` "modules/c/main.tf": `
variable "bad" { variable "bucket" {}
default = false
} resource "aws_s3_bucket" "test" {
resource "problem" "uhoh" { bucket = var.bucket
bad = var.bad
} }
`, `,
}) },
expected: true,
p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) },
err := p.ParseFS(context.TODO(), "project") {
require.NoError(t, err) name: "misconfig in terraform cached module",
modules, _, err := p.EvaluateAll(context.TODO()) files: map[string]string{
require.NoError(t, err) "project/main.tf": `
results, err := executor.New().Execute(modules)
require.NoError(t, err)
testutil.AssertRuleFound(t, badRule.LongID(), results, "")
}
func Test_ProblemInInitialisedModule(t *testing.T) {
registered := rules.Register(badRule)
defer rules.Deregister(registered)
fs := testutil.CreateFS(t, map[string]string{
"project/main.tf": `
module "something" { module "something" {
source = "../modules/somewhere" source = "../modules/somewhere"
bad = false bucket = "test"
} }
`, `,
"modules/somewhere/main.tf": ` "modules/somewhere/main.tf": `
module "something_nested" { module "something_nested" {
count = 1 count = 1
source = "github.com/some/module.git" source = "github.com/some/module.git"
bad = true bucket = ""
} }
variable "bad" { variable "bucket" {
default = false default = ""
} }`,
"project/.terraform/modules/something.something_nested/main.tf": `
variable "bucket" {}
`, resource "aws_s3_bucket" "test" {
"project/.terraform/modules/something.something_nested/main.tf": ` bucket = var.bucket
variable "bad" {
default = false
}
resource "problem" "uhoh" {
bad = var.bad
} }
`, `,
"project/.terraform/modules/modules.json": ` "project/.terraform/modules/modules.json": `
{"Modules":[ {"Modules":[
{"Key":"something","Source":"../modules/somewhere","Version":"2.35.0","Dir":"../modules/somewhere"}, {"Key":"something","Source":"../modules/somewhere","Version":"2.35.0","Dir":"../modules/somewhere"},
{"Key":"something.something_nested","Source":"git::https://github.com/some/module.git","Version":"2.35.0","Dir":".terraform/modules/something.something_nested"} {"Key":"something.something_nested","Source":"git::https://github.com/some/module.git","Version":"2.35.0","Dir":".terraform/modules/something.something_nested"}
]} ]}
`, `,
}) },
expected: true,
p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) },
err := p.ParseFS(context.TODO(), "project") {
require.NoError(t, err) name: "misconfig in reused terraform cached module",
modules, _, err := p.EvaluateAll(context.TODO()) files: map[string]string{
require.NoError(t, err) "project/main.tf": `
results, err := executor.New().Execute(modules)
require.NoError(t, err)
testutil.AssertRuleFound(t, badRule.LongID(), results, "")
}
func Test_ProblemInReusedInitialisedModule(t *testing.T) {
registered := rules.Register(badRule)
defer rules.Deregister(registered)
fs := testutil.CreateFS(t, map[string]string{
"project/main.tf": `
module "something" { module "something" {
source = "/nowhere" source = "/nowhere"
bad = false bucket = ""
} }
module "something2" { module "something2" {
source = "/nowhere" source = "/nowhere"
bad = true bucket = ""
} }
`, `,
"project/.terraform/modules/a/main.tf": ` "project/.terraform/modules/a/main.tf": `
variable "bad" { variable "bucket" {}
default = false
} resource "aws_s3_bucket" "test" {
resource "problem" "uhoh" { bucket = var.bucket
bad = var.bad
} }
`, `,
"project/.terraform/modules/modules.json": ` "project/.terraform/modules/modules.json": `
{"Modules":[{"Key":"something","Source":"/nowhere","Version":"2.35.0","Dir":".terraform/modules/a"},{"Key":"something2","Source":"/nowhere","Version":"2.35.0","Dir":".terraform/modules/a"}]} {"Modules":[{"Key":"something","Source":"/nowhere","Version":"2.35.0","Dir":".terraform/modules/a"},{"Key":"something2","Source":"/nowhere","Version":"2.35.0","Dir":".terraform/modules/a"}]}
`, `,
}) },
expected: true,
p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) },
err := p.ParseFS(context.TODO(), "project") {
require.NoError(t, err) name: "misconfig in nested modules with duplicate module names and paths",
modules, _, err := p.EvaluateAll(context.TODO()) files: map[string]string{
require.NoError(t, err) "project/main.tf": `
results, err := executor.New().Execute(modules)
require.NoError(t, err)
testutil.AssertRuleFound(t, badRule.LongID(), results, "")
}
func Test_ProblemInDuplicateModuleNameAndPath(t *testing.T) {
registered := rules.Register(badRule)
defer rules.Deregister(registered)
fs := testutil.CreateFS(t, map[string]string{
"project/main.tf": `
module "something" { module "something" {
source = "../modules/a" source = "../modules/a"
bad = 0 s3_bucket_count = 0
} }
module "something-bad" { module "something-bad" {
source = "../modules/a" source = "../modules/a"
bad = 1 s3_bucket_count = 1
} }
`, `,
"modules/a/main.tf": ` "modules/a/main.tf": `
variable "bad" { variable "s3_bucket_count" {
default = 0 default = 0
} }
module "something" { module "something" {
source = "../b" source = "../b"
bad = var.bad s3_bucket_count = var.s3_bucket_count
} }
`, `,
"modules/b/main.tf": ` "modules/b/main.tf": `
variable "bad" { variable "s3_bucket_count" {
default = 0 default = 0
} }
module "something" { module "something" {
source = "../c" source = "../c"
bad = var.bad s3_bucket_count = var.s3_bucket_count
} }
`, `,
"modules/c/main.tf": ` "modules/c/main.tf": `
variable "bad" { variable "s3_bucket_count" {
default = 0 default = 0
} }
resource "problem" "uhoh" {
count = var.bad resource "aws_s3_bucket" "test" {
bad = true count = var.s3_bucket_count
} }
`, `,
}) },
expected: true,
p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) },
err := p.ParseFS(context.TODO(), "project") {
require.NoError(t, err) name: "misconfigured attribute referencing to dynamic variable",
modules, _, err := p.EvaluateAll(context.TODO()) files: map[string]string{
require.NoError(t, err) "project/main.tf": `
results, err := executor.New().Execute(modules)
require.NoError(t, err)
testutil.AssertRuleFound(t, badRule.LongID(), results, "")
}
func Test_Dynamic_Variables(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"project/main.tf": `
resource "something" "this" { resource "something" "this" {
dynamic "blah" { dynamic "blah" {
for_each = ["a"] for_each = ["a"]
content { content {
ok = true bucket = ""
} }
} }
} }
resource "bad" "thing" {
secure = something.this.blah[0].ok
}
`})
r1 := scan.Rule{ resource "aws_s3_bucket" "test" {
Provider: providers.AWSProvider, secure = something.this.blah[0].bucket
Service: "service", }
ShortCode: "abc123", `},
Severity: severity.High, expected: true,
CustomChecks: scan.CustomChecks{
Terraform: &scan.TerraformCustomCheck{
RequiredLabels: []string{"bad"},
Check: func(resourceBlock *terraform.Block, _ *terraform.Module) (results scan.Results) {
if resourceBlock.GetAttribute("secure").IsTrue() {
return
}
results.Add("example problem", resourceBlock)
return
},
},
}, },
} {
reg := rules.Register(r1) name: "attribute referencing to dynamic variable without index",
defer rules.Deregister(reg) files: map[string]string{
"project/main.tf": `
p := parser.New(fs, "", parser.OptionStopOnHCLError(true))
err := p.ParseFS(context.TODO(), "project")
require.NoError(t, err)
modules, _, err := p.EvaluateAll(context.TODO())
require.NoError(t, err)
results, err := executor.New().Execute(modules)
require.NoError(t, err)
testutil.AssertRuleFound(t, r1.LongID(), results, "")
}
func Test_Dynamic_Variables_FalsePositive(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"project/main.tf": `
resource "something" "else" { resource "something" "else" {
x = 1
dynamic "blah" { dynamic "blah" {
for_each = toset(["true"]) for_each = toset(["test"])
content { content {
ok = blah.value bucket = blah.value
} }
} }
} }
resource "bad" "thing" {
secure = something.else.blah.ok
}
`})
r1 := scan.Rule{ resource "aws_s3_bucket" "test" {
Provider: providers.AWSProvider, bucket = something.else.blah.bucket
Service: "service", }`},
ShortCode: "abc123", expected: false,
Severity: severity.High,
CustomChecks: scan.CustomChecks{
Terraform: &scan.TerraformCustomCheck{
RequiredLabels: []string{"bad"},
Check: func(resourceBlock *terraform.Block, _ *terraform.Module) (results scan.Results) {
if resourceBlock.GetAttribute("secure").IsTrue() {
return
}
results.Add("example problem", resourceBlock)
return
},
},
}, },
} {
reg := rules.Register(r1) name: "references passed to nested module",
defer rules.Deregister(reg) files: map[string]string{
"project/main.tf": `
p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) resource "some_resource" "this" {
err := p.ParseFS(context.TODO(), "project") name = "test"
require.NoError(t, err)
modules, _, err := p.EvaluateAll(context.TODO())
require.NoError(t, err)
results, err := executor.New().Execute(modules)
require.NoError(t, err)
testutil.AssertRuleNotFound(t, r1.LongID(), results, "")
}
func Test_ReferencesPassedToNestedModule(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"project/main.tf": `
resource "aws_iam_group" "developers" {
name = "developers"
} }
module "something" { module "something" {
source = "../modules/a" source = "../modules/a"
group = aws_iam_group.developers.name bucket = some_resource.this.name
} }
`, `,
"modules/a/main.tf": ` "modules/a/main.tf": `
variable "group" { variable "bucket" {
type = string type = string
} }
resource "aws_iam_group_policy" "mfa" { resource "aws_s3_bucket" "test" {
group = var.group bucket = var.bucket
policy = data.aws_iam_policy_document.policy.json
} }
`},
expected: false,
},
}
data "aws_iam_policy_document" "policy" { for _, tt := range tests {
statement { t.Run(tt.name, func(t *testing.T) {
sid = "main" fsys := testutil.CreateFS(t, tt.files)
effect = "Allow" results, err := scanFS(fsys, "project",
rego.WithPolicyReader(strings.NewReader(emptyBucketCheck)),
actions = ["s3:*"] rego.WithPolicyNamespaces("user"),
resources = ["*"] )
condition { require.NoError(t, err)
test = "Bool" if tt.expected {
variable = "aws:MultiFactorAuthPresent" testutil.AssertRuleFound(t, "aws-s3-non-empty-bucket", results, "")
values = ["true"] } else {
} testutil.AssertRuleNotFailed(t, "aws-s3-non-empty-bucket", results, "")
} }
} })
`}) }
p := parser.New(fs, "", parser.OptionStopOnHCLError(true))
err := p.ParseFS(context.TODO(), "project")
require.NoError(t, err)
modules, _, err := p.EvaluateAll(context.TODO())
require.NoError(t, err)
results, err := executor.New().Execute(modules)
require.NoError(t, err)
testutil.AssertRuleNotFound(t, iam.CheckEnforceGroupMFA.LongID(), results, "")
} }

View File

@@ -100,3 +100,11 @@ func ScannerWithSkipDirs(dirs []string) options.ScannerOption {
} }
} }
} }
func ScannerWithStopOnHCLError(stop bool) options.ScannerOption {
return func(s options.ConfigurableScanner) {
if tf, ok := s.(ConfigurableTerraformScanner); ok {
tf.AddParserOptions(parser.OptionStopOnHCLError(stop))
}
}
}

View File

@@ -2,6 +2,7 @@ package terraform
import ( import (
"context" "context"
"strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -24,29 +25,11 @@ module "s3_bucket" {
bucket = "my-s3-bucket" bucket = "my-s3-bucket"
} }
`, `,
"/rules/bucket_name.rego": `
# METADATA
# schemas:
# - input: schema.input
# custom:
# avd_id: AVD-AWS-0001
# input:
# selector:
# - type: cloud
# subtypes:
# - service: s3
# provider: aws
package defsec.test.aws1
deny[res] {
bucket := input.aws.s3.buckets[_]
bucket.name.value == ""
res := result.new("The name of the bucket must not be empty", bucket)
}`,
}) })
scanner := New( scanner := New(
rego.WithPolicyFilesystem(fs), rego.WithPolicyReader(strings.NewReader(emptyBucketCheck)),
rego.WithPolicyDirs("rules"), rego.WithPolicyNamespaces("user"),
rego.WithEmbeddedPolicies(false), rego.WithEmbeddedPolicies(false),
rego.WithEmbeddedLibraries(false), rego.WithEmbeddedLibraries(false),
options.ScannerWithRegoOnly(true), options.ScannerWithRegoOnly(true),
@@ -81,29 +64,11 @@ module "s3_bucket" {
bucket = var.bucket bucket = var.bucket
} }
`, `,
"rules/bucket_name.rego": `
# METADATA
# schemas:
# - input: schema.input
# custom:
# avd_id: AVD-AWS-0001
# input:
# selector:
# - type: cloud
# subtypes:
# - service: s3
# provider: aws
package defsec.test.aws1
deny[res] {
bucket := input.aws.s3.buckets[_]
bucket.name.value == ""
res := result.new("The name of the bucket must not be empty", bucket)
}`,
}) })
scanner := New( scanner := New(
rego.WithPolicyFilesystem(fs), rego.WithPolicyReader(strings.NewReader(emptyBucketCheck)),
rego.WithPolicyDirs("rules"), rego.WithPolicyNamespaces("user"),
rego.WithEmbeddedPolicies(false), rego.WithEmbeddedPolicies(false),
rego.WithEmbeddedLibraries(false), rego.WithEmbeddedLibraries(false),
options.ScannerWithRegoOnly(true), options.ScannerWithRegoOnly(true),

View File

@@ -20,76 +20,25 @@ import (
"github.com/aquasecurity/trivy/pkg/iac/types" "github.com/aquasecurity/trivy/pkg/iac/types"
) )
const emptyBucketRule = `
# METADATA
# schemas:
# - input: schema.input
# custom:
# avd_id: AVD-AWS-0001
# input:
# selector:
# - type: cloud
# subtypes:
# - service: s3
# provider: aws
package defsec.test.aws1
deny[res] {
bucket := input.aws.s3.buckets[_]
bucket.name.value == ""
res := result.new("The name of the bucket must not be empty", bucket)
}
`
func Test_OptionWithPolicyDirs(t *testing.T) { func Test_OptionWithPolicyDirs(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{ fsys := testutil.CreateFS(t, map[string]string{
"/code/main.tf": ` "/code/main.tf": `resource "aws_s3_bucket" "my-bucket" {}`,
resource "aws_s3_bucket" "my-bucket" { "/rules/test.rego": emptyBucketCheck,
bucket = "evil"
}
`,
"/rules/test.rego": `
package defsec.abcdefg
__rego_metadata__ := {
"id": "TEST123",
"avd_id": "AVD-TEST-0123",
"title": "Buckets should not be evil",
"short_code": "no-evil-buckets",
"severity": "CRITICAL",
"type": "DefSec Security Check",
"description": "You should not allow buckets to be evil",
"recommended_actions": "Use a good bucket instead",
"url": "https://google.com/search?q=is+my+bucket+evil",
}
__rego_input__ := {
"combine": false,
"selector": [{"type": "defsec", "subtypes": [{"service": "s3", "provider": "aws"}]}],
}
deny[cause] {
bucket := input.aws.s3.buckets[_]
bucket.name.value == "evil"
cause := bucket.name
}
`,
}) })
scanner := New( results, err := scanFS(fsys, "code",
rego.WithPolicyFilesystem(fs), rego.WithPolicyFilesystem(fsys),
rego.WithPolicyDirs("rules"), rego.WithPolicyDirs("rules"),
options.ScannerWithRegoOnly(true), rego.WithPolicyNamespaces("user"),
) )
results, err := scanner.ScanFS(context.TODO(), fs, "code")
require.NoError(t, err) require.NoError(t, err)
require.Len(t, results.GetFailed(), 1) require.Len(t, results.GetFailed(), 1)
failure := results.GetFailed()[0] failure := results.GetFailed()[0]
assert.Equal(t, "AVD-TEST-0123", failure.Rule().AVDID) assert.Equal(t, "USER-TEST-0123", failure.Rule().AVDID)
actualCode, err := failure.GetCode() actualCode, err := failure.GetCode()
require.NoError(t, err) require.NoError(t, err)
@@ -98,28 +47,11 @@ deny[cause] {
} }
assert.Equal(t, []scan.Line{ assert.Equal(t, []scan.Line{
{ {
Number: 2, Number: 1,
Content: "resource \"aws_s3_bucket\" \"my-bucket\" {", Content: "resource \"aws_s3_bucket\" \"my-bucket\" {}",
IsCause: false,
FirstCause: false,
LastCause: false,
Annotation: "",
},
{
Number: 3,
Content: "\tbucket = \"evil\"",
IsCause: true, IsCause: true,
FirstCause: true, FirstCause: true,
LastCause: true, LastCause: true,
Annotation: "",
},
{
Number: 4,
Content: "}",
IsCause: false,
FirstCause: false,
LastCause: false,
Annotation: "",
}, },
}, actualCode.Lines) }, actualCode.Lines)
@@ -236,99 +168,41 @@ cause := bucket.name
func Test_OptionWithRegoOnly(t *testing.T) { func Test_OptionWithRegoOnly(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{ fsys := testutil.CreateFS(t, map[string]string{
"/code/main.tf": ` "/code/main.tf": `
resource "aws_s3_bucket" "my-bucket" { resource "aws_s3_bucket" "my-bucket" {}
bucket = "evil"
}
`,
"/rules/test.rego": `
package defsec.abcdefg
__rego_metadata__ := {
"id": "TEST123",
"avd_id": "AVD-TEST-0123",
"title": "Buckets should not be evil",
"short_code": "no-evil-buckets",
"severity": "CRITICAL",
"type": "DefSec Security Check",
"description": "You should not allow buckets to be evil",
"recommended_actions": "Use a good bucket instead",
"url": "https://google.com/search?q=is+my+bucket+evil",
}
__rego_input__ := {
"combine": false,
"selector": [{"type": "defsec", "subtypes": [{"service": "s3", "provider": "aws"}]}],
}
deny[cause] {
bucket := input.aws.s3.buckets[_]
bucket.name.value == "evil"
cause := bucket.name
}
`, `,
"/rules/test.rego": emptyBucketCheck,
}) })
scanner := New( results, err := scanFS(fsys, "code",
rego.WithPolicyDirs("rules"), rego.WithPolicyDirs("rules"),
options.ScannerWithRegoOnly(true), rego.WithPolicyNamespaces("user"),
) )
results, err := scanner.ScanFS(context.TODO(), fs, "code")
require.NoError(t, err) require.NoError(t, err)
require.Len(t, results.GetFailed(), 1) require.Len(t, results.GetFailed(), 1)
assert.Equal(t, "AVD-TEST-0123", results[0].Rule().AVDID) assert.Equal(t, "USER-TEST-0123", results[0].Rule().AVDID)
} }
func Test_OptionWithRegoOnly_CodeHighlighting(t *testing.T) { func Test_OptionWithRegoOnly_CodeHighlighting(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{ fsys := testutil.CreateFS(t, map[string]string{
"/code/main.tf": ` "/code/main.tf": `
resource "aws_s3_bucket" "my-bucket" { resource "aws_s3_bucket" "my-bucket" {}
bucket = "evil"
}
`,
"/rules/test.rego": `
package defsec.abcdefg
__rego_metadata__ := {
"id": "TEST123",
"avd_id": "AVD-TEST-0123",
"title": "Buckets should not be evil",
"short_code": "no-evil-buckets",
"severity": "CRITICAL",
"type": "DefSec Security Check",
"description": "You should not allow buckets to be evil",
"recommended_actions": "Use a good bucket instead",
"url": "https://google.com/search?q=is+my+bucket+evil",
}
__rego_input__ := {
"combine": false,
"selector": [{"type": "defsec", "subtypes": [{"service": "s3", "provider": "aws"}]}],
}
deny[res] {
bucket := input.aws.s3.buckets[_]
bucket.name.value == "evil"
res := result.new("oh no", bucket.name)
}
`, `,
"/rules/test.rego": emptyBucketCheck,
}) })
scanner := New( results, err := scanFS(fsys, "code",
rego.WithPolicyDirs("rules"), rego.WithPolicyDirs("rules"),
options.ScannerWithRegoOnly(true), rego.WithPolicyNamespaces("user"),
rego.WithEmbeddedLibraries(true),
) )
results, err := scanner.ScanFS(context.TODO(), fs, "code")
require.NoError(t, err) require.NoError(t, err)
require.Len(t, results.GetFailed(), 1) require.Len(t, results.GetFailed(), 1)
assert.Equal(t, "AVD-TEST-0123", results[0].Rule().AVDID) assert.Equal(t, "USER-TEST-0123", results[0].Rule().AVDID)
assert.NotNil(t, results[0].Metadata().Range().GetFS()) assert.NotNil(t, results[0].Metadata().Range().GetFS())
} }
@@ -709,7 +583,7 @@ resource "aws_s3_bucket" "main" {
bucket = var.bucket_name bucket = var.bucket_name
} }
`, `,
"rules/bucket_name.rego": emptyBucketRule, "rules/bucket_name.rego": emptyBucketCheck,
}) })
configsFS := testutil.CreateFS(t, map[string]string{ configsFS := testutil.CreateFS(t, map[string]string{
@@ -719,6 +593,7 @@ bucket_name = "test"
}) })
scanner := New( scanner := New(
rego.WithPolicyNamespaces("user"),
rego.WithPolicyDirs("rules"), rego.WithPolicyDirs("rules"),
rego.WithPolicyFilesystem(fs), rego.WithPolicyFilesystem(fs),
options.ScannerWithRegoOnly(true), options.ScannerWithRegoOnly(true),
@@ -746,13 +621,14 @@ resource "aws_s3_bucket" "main" {
bucket = var.bucket_name bucket = var.bucket_name
} }
`, `,
"rules/bucket_name.rego": emptyBucketRule, "rules/bucket_name.rego": emptyBucketCheck,
"main.tfvars": ` "main.tfvars": `
bucket_name = "test" bucket_name = "test"
`, `,
}) })
scanner := New( scanner := New(
rego.WithPolicyNamespaces("user"),
rego.WithPolicyDirs("rules"), rego.WithPolicyDirs("rules"),
rego.WithPolicyFilesystem(fs), rego.WithPolicyFilesystem(fs),
options.ScannerWithRegoOnly(true), options.ScannerWithRegoOnly(true),
@@ -805,25 +681,7 @@ resource "aws_security_group" "main" {
description = var.security_group_description description = var.security_group_description
} }
`, `,
"/rules/bucket_name.rego": ` "/rules/bucket_name.rego": emptyBucketCheck,
# METADATA
# schemas:
# - input: schema.input
# custom:
# avd_id: AVD-AWS-0001
# input:
# selector:
# - type: cloud
# subtypes:
# - service: s3
# provider: aws
package defsec.test.aws1
deny[res] {
bucket := input.aws.s3.buckets[_]
bucket.name.value == ""
res := result.new("The name of the bucket must not be empty", bucket)
}
`,
"/rules/sec_group_description.rego": ` "/rules/sec_group_description.rego": `
# METADATA # METADATA
# schemas: # schemas:
@@ -846,6 +704,7 @@ deny[res] {
}) })
scanner := New( scanner := New(
rego.WithPolicyNamespaces("user"),
rego.WithPolicyFilesystem(fs), rego.WithPolicyFilesystem(fs),
rego.WithPolicyDirs("rules"), rego.WithPolicyDirs("rules"),
rego.WithEmbeddedPolicies(false), rego.WithEmbeddedPolicies(false),

View File

@@ -2,6 +2,7 @@ package terraform
import ( import (
"context" "context"
"io/fs"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -14,6 +15,68 @@ import (
"github.com/aquasecurity/trivy/pkg/iac/terraform" "github.com/aquasecurity/trivy/pkg/iac/terraform"
) )
var emptyBucketCheck = `# METADATA
# schemas:
# - input: schema.cloud
# custom:
# avd_id: USER-TEST-0123
# short_code: non-empty-bucket
# provider: aws
# service: s3
# aliases:
# - my-alias
# input:
# selector:
# - type: cloud
# subtypes:
# - service: s3
# provider: aws
package user.test123
import rego.v1
deny contains res if {
some bucket in input.aws.s3.buckets
bucket.name.value == ""
res := result.new("The bucket name cannot be empty.", bucket.name)
}
`
var enforceGroupMfaCheck = `# METADATA
# schemas:
# - input: schema["cloud"]
# custom:
# id: USER-TEST-0124
# aliases:
# - aws-iam-enforce-mfa
# provider: aws
# service: iam
# short_code: enforce-group-mfa
# input:
# selector:
# - type: cloud
# subtypes:
# - service: iam
# provider: aws
package user.test124
import rego.v1
deny contains res if {
some group in input.aws.iam.groups
not is_group_mfa_enforced(group)
res := result.new("Multi-Factor authentication is not enforced for group", group)
}
is_group_mfa_enforced(group) if {
some policy in group.policies
value := json.unmarshal(policy.document.value)
some condition in value.Statement[_].Condition
some key, _ in condition
key == "aws:MultiFactorAuthPresent"
}
`
func createModulesFromSource(t *testing.T, source, ext string) terraform.Modules { func createModulesFromSource(t *testing.T, source, ext string) terraform.Modules {
fs := testutil.CreateFS(t, map[string]string{ fs := testutil.CreateFS(t, map[string]string{
"source" + ext: source, "source" + ext: source,
@@ -30,30 +93,40 @@ func createModulesFromSource(t *testing.T, source, ext string) terraform.Modules
return modules return modules
} }
func scanHCLWithWorkspace(t *testing.T, source, workspace string) scan.Results { func scanFS(fsys fs.FS, target string, opts ...options.ScannerOption) (scan.Results, error) {
return scanHCL(t, source, ScannerWithWorkspaceName(workspace)) s := New(append(
[]options.ScannerOption{
rego.WithEmbeddedLibraries(true),
rego.WithRegoErrorLimits(0),
options.ScannerWithRegoOnly(true),
ScannerWithAllDirectories(true),
ScannerWithSkipCachedModules(true),
ScannerWithStopOnHCLError(true),
},
opts...,
)...,
)
return s.ScanFS(context.TODO(), fsys, target)
} }
func scanHCL(t *testing.T, source string, opts ...options.ScannerOption) scan.Results { func scanHCL(t *testing.T, source string, opts ...options.ScannerOption) scan.Results {
fs := testutil.CreateFS(t, map[string]string{ fsys := testutil.CreateFS(t, map[string]string{
"main.tf": source, "main.tf": source,
}) })
results, err := scanFS(fsys, ".", opts...)
localScanner := New(append(opts, rego.WithEmbeddedPolicies(false))...)
results, err := localScanner.ScanFS(context.TODO(), fs, ".")
require.NoError(t, err) require.NoError(t, err)
return results return results
} }
func scanJSON(t *testing.T, source string) scan.Results { func scanJSON(t *testing.T, source string, opts ...options.ScannerOption) scan.Results {
fs := testutil.CreateFS(t, map[string]string{ fsys := testutil.CreateFS(t, map[string]string{
"main.tf.json": source, "main.tf.json": source,
}) })
s := New(rego.WithEmbeddedPolicies(true), rego.WithEmbeddedLibraries(true)) results, err := scanFS(fsys, ".", opts...)
results, err := s.ScanFS(context.TODO(), fs, ".")
require.NoError(t, err) require.NoError(t, err)
return results return results
} }

View File

@@ -1,9 +1,12 @@
package terraform package terraform
import ( import (
"context"
"fmt" "fmt"
"testing" "testing"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/internal/testutil"
"github.com/aquasecurity/trivy/pkg/iac/rules" "github.com/aquasecurity/trivy/pkg/iac/rules"
"github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scan"
@@ -49,7 +52,7 @@ func Test_WildcardMatchingOnRequiredLabels(t *testing.T) {
code := fmt.Sprintf("wild%d", i) code := fmt.Sprintf("wild%d", i)
t.Run(code, func(t *testing.T) { t.Run(test.pattern, func(t *testing.T) {
rule := scan.Rule{ rule := scan.Rule{
Service: "service", Service: "service",
@@ -71,7 +74,12 @@ func Test_WildcardMatchingOnRequiredLabels(t *testing.T) {
reg := rules.Register(rule) reg := rules.Register(rule)
defer rules.Deregister(reg) defer rules.Deregister(reg)
results := scanHCL(t, test.input) fsys := testutil.CreateFS(t, map[string]string{
"main.tf": test.input,
})
s := New()
results, err := s.ScanFS(context.TODO(), fsys, ".")
require.NoError(t, err)
if test.expectedFailure { if test.expectedFailure {
testutil.AssertRuleFound(t, fmt.Sprintf("custom-service-%s", code), results, "") testutil.AssertRuleFound(t, fmt.Sprintf("custom-service-%s", code), results, "")