mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-22 07:10:41 -08:00
test: replace Go checks with Rego (#7867)
Signed-off-by: nikpivkin <nikita.pivkin@smartforce.io>
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/liamg/memoryfs"
|
||||
"github.com/samber/lo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"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...)...)
|
||||
}
|
||||
|
||||
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 {
|
||||
for _, res := range results {
|
||||
if res.Rule().LongID() == ruleID {
|
||||
|
||||
@@ -1,57 +1,54 @@
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/aquasecurity/trivy/internal/testutil"
|
||||
"github.com/aquasecurity/trivy/pkg/iac/providers"
|
||||
"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"
|
||||
"github.com/aquasecurity/trivy/pkg/iac/rego"
|
||||
)
|
||||
|
||||
func Test_ResourcesWithCount(t *testing.T) {
|
||||
var tests = []struct {
|
||||
name string
|
||||
source string
|
||||
expectedResults int
|
||||
name string
|
||||
source string
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
name: "unspecified count defaults to 1",
|
||||
source: `
|
||||
resource "bad" "this" {}
|
||||
resource "aws_s3_bucket" "test" {}
|
||||
`,
|
||||
expectedResults: 1,
|
||||
expected: 1,
|
||||
},
|
||||
{
|
||||
name: "count is literal 1",
|
||||
source: `
|
||||
resource "bad" "this" {
|
||||
resource "aws_s3_bucket" "test" {
|
||||
count = 1
|
||||
}
|
||||
`,
|
||||
expectedResults: 1,
|
||||
expected: 1,
|
||||
},
|
||||
{
|
||||
name: "count is literal 99",
|
||||
source: `
|
||||
resource "bad" "this" {
|
||||
resource "aws_s3_bucket" "test" {
|
||||
count = 99
|
||||
}
|
||||
`,
|
||||
expectedResults: 99,
|
||||
expected: 99,
|
||||
},
|
||||
{
|
||||
name: "count is literal 0",
|
||||
source: `
|
||||
resource "bad" "this" {
|
||||
resource "aws_s3_bucket" "test" {
|
||||
count = 0
|
||||
}
|
||||
`,
|
||||
expectedResults: 0,
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
name: "count is 0 from variable",
|
||||
@@ -59,11 +56,11 @@ func Test_ResourcesWithCount(t *testing.T) {
|
||||
variable "count" {
|
||||
default = 0
|
||||
}
|
||||
resource "bad" "this" {
|
||||
resource "aws_s3_bucket" "test" {
|
||||
count = var.count
|
||||
}
|
||||
`,
|
||||
expectedResults: 0,
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
name: "count is 1 from variable",
|
||||
@@ -71,22 +68,22 @@ func Test_ResourcesWithCount(t *testing.T) {
|
||||
variable "count" {
|
||||
default = 1
|
||||
}
|
||||
resource "bad" "this" {
|
||||
resource "aws_s3_bucket" "test" {
|
||||
count = var.count
|
||||
}
|
||||
`,
|
||||
expectedResults: 1,
|
||||
expected: 1,
|
||||
},
|
||||
{
|
||||
name: "count is 1 from variable without default",
|
||||
source: `
|
||||
variable "count" {
|
||||
}
|
||||
resource "bad" "this" {
|
||||
resource "aws_s3_bucket" "test" {
|
||||
count = var.count
|
||||
}
|
||||
`,
|
||||
expectedResults: 1,
|
||||
expected: 1,
|
||||
},
|
||||
{
|
||||
name: "count is 0 from conditional",
|
||||
@@ -94,11 +91,11 @@ func Test_ResourcesWithCount(t *testing.T) {
|
||||
variable "enabled" {
|
||||
default = false
|
||||
}
|
||||
resource "bad" "this" {
|
||||
resource "aws_s3_bucket" "test" {
|
||||
count = var.enabled ? 1 : 0
|
||||
}
|
||||
`,
|
||||
expectedResults: 0,
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
name: "count is 1 from conditional",
|
||||
@@ -106,11 +103,11 @@ func Test_ResourcesWithCount(t *testing.T) {
|
||||
variable "enabled" {
|
||||
default = true
|
||||
}
|
||||
resource "bad" "this" {
|
||||
resource "aws_s3_bucket" "test" {
|
||||
count = var.enabled ? 1 : 0
|
||||
}
|
||||
`,
|
||||
expectedResults: 1,
|
||||
expected: 1,
|
||||
},
|
||||
{
|
||||
name: "issue 962",
|
||||
@@ -120,18 +117,18 @@ func Test_ResourcesWithCount(t *testing.T) {
|
||||
ok = true
|
||||
}
|
||||
|
||||
resource "bad" "bad" {
|
||||
secure = something.else[0].ok
|
||||
resource "aws_s3_bucket" "test" {
|
||||
bucket = something.else[0].ok ? "test" : ""
|
||||
}
|
||||
`,
|
||||
expectedResults: 0,
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
name: "Test use of count.index",
|
||||
source: `
|
||||
resource "bad" "thing" {
|
||||
resource "aws_s3_bucket" "test" {
|
||||
count = 1
|
||||
secure = var.things[count.index]["ok"]
|
||||
bucket = var.things[count.index]["ok"] ? "test" : ""
|
||||
}
|
||||
|
||||
variable "things" {
|
||||
@@ -145,49 +142,23 @@ variable "things" {
|
||||
]
|
||||
}
|
||||
`,
|
||||
expectedResults: 0,
|
||||
expected: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
r1 := scan.Rule{
|
||||
Provider: providers.AWSProvider,
|
||||
Service: "service",
|
||||
ShortCode: "abc123",
|
||||
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)
|
||||
defer rules.Deregister(reg)
|
||||
results := scanHCL(t, test.source)
|
||||
var include string
|
||||
var exclude string
|
||||
if test.expectedResults > 0 {
|
||||
include = r1.LongID()
|
||||
results := scanHCL(t, test.source,
|
||||
rego.WithPolicyReader(strings.NewReader(emptyBucketCheck)),
|
||||
rego.WithPolicyNamespaces("user"),
|
||||
)
|
||||
|
||||
assert.Len(t, results.GetFailed(), test.expected)
|
||||
|
||||
if test.expected > 0 {
|
||||
testutil.AssertRuleFound(t, "aws-s3-non-empty-bucket", results, "false negative found")
|
||||
} else {
|
||||
exclude = r1.LongID()
|
||||
}
|
||||
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")
|
||||
testutil.AssertRuleNotFound(t, "aws-s3-non-empty-bucket", results, "false positive found")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,27 +1,20 @@
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/aquasecurity/trivy/internal/testutil"
|
||||
"github.com/aquasecurity/trivy/pkg/iac/rules"
|
||||
"github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/executor"
|
||||
"github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser"
|
||||
"github.com/aquasecurity/trivy/pkg/iac/rego"
|
||||
)
|
||||
|
||||
func Test_DeterministicResults(t *testing.T) {
|
||||
|
||||
reg := rules.Register(badRule)
|
||||
defer rules.Deregister(reg)
|
||||
|
||||
fs := testutil.CreateFS(t, map[string]string{
|
||||
fsys := testutil.CreateFS(t, map[string]string{
|
||||
"first.tf": `
|
||||
resource "problem" "uhoh" {
|
||||
bad = true
|
||||
for_each = other.thing
|
||||
resource "aws_s3_bucket" "test" {
|
||||
for_each = other.thing
|
||||
}
|
||||
`,
|
||||
"second.tf": `
|
||||
@@ -40,12 +33,11 @@ locals {
|
||||
})
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
p := parser.New(fs, "", parser.OptionStopOnHCLError(true))
|
||||
err := p.ParseFS(context.TODO(), ".")
|
||||
results, err := scanFS(fsys, ".",
|
||||
rego.WithPolicyReader(strings.NewReader(emptyBucketCheck)),
|
||||
rego.WithPolicyNamespaces("user"),
|
||||
)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,22 +1,19 @@
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/aquasecurity/trivy/internal/testutil"
|
||||
"github.com/aquasecurity/trivy/pkg/iac/providers"
|
||||
"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"
|
||||
"github.com/aquasecurity/trivy/pkg/iac/rego"
|
||||
)
|
||||
|
||||
func TestScanningJSON(t *testing.T) {
|
||||
|
||||
var tests = []struct {
|
||||
name string
|
||||
source string
|
||||
shouldFail bool
|
||||
name string
|
||||
source string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "check results are picked up in tf json configs",
|
||||
@@ -29,16 +26,14 @@ func TestScanningJSON(t *testing.T) {
|
||||
}
|
||||
},
|
||||
"resource": {
|
||||
"bad": {
|
||||
"thing": {
|
||||
"type": "ingress",
|
||||
"cidr_blocks": ["0.0.0.0/0"],
|
||||
"description": "testing"
|
||||
"aws_s3_bucket": {
|
||||
"test": {
|
||||
"bucket": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
shouldFail: true,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "check attributes are checked in tf json configs",
|
||||
@@ -51,52 +46,27 @@ func TestScanningJSON(t *testing.T) {
|
||||
}
|
||||
},
|
||||
"resource": {
|
||||
"bad": {
|
||||
"or_not": {
|
||||
"secure": true
|
||||
"aws_s3_bucket": {
|
||||
"test": {
|
||||
"bucket": "test"
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
shouldFail: false,
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
r1 := scan.Rule{
|
||||
Provider: providers.AWSProvider,
|
||||
Service: "service",
|
||||
ShortCode: "abc123",
|
||||
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("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()
|
||||
results := scanJSON(t, test.source,
|
||||
rego.WithPolicyReader(strings.NewReader(emptyBucketCheck)),
|
||||
rego.WithPolicyNamespaces("user"),
|
||||
)
|
||||
if test.expected {
|
||||
testutil.AssertRuleFound(t, "aws-s3-non-empty-bucket", results, "false negative found")
|
||||
} else {
|
||||
exclude = r1.LongID()
|
||||
}
|
||||
if include != "" {
|
||||
testutil.AssertRuleFound(t, include, results, "false negative found")
|
||||
}
|
||||
if exclude != "" {
|
||||
testutil.AssertRuleNotFound(t, exclude, results, "false positive found")
|
||||
testutil.AssertRuleNotFound(t, "aws-s3-non-empty-bucket", results, "false positive found")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,52 +1,26 @@
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/aquasecurity/trivy-checks/checks/cloud/aws/iam"
|
||||
"github.com/aquasecurity/trivy/internal/testutil"
|
||||
"github.com/aquasecurity/trivy/pkg/iac/providers"
|
||||
"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"
|
||||
"github.com/aquasecurity/trivy/pkg/iac/rego"
|
||||
)
|
||||
|
||||
var badRule = scan.Rule{
|
||||
Provider: providers.AWSProvider,
|
||||
Service: "service",
|
||||
ShortCode: "abc",
|
||||
Summary: "A stupid example check for a test.",
|
||||
Impact: "You will look stupid",
|
||||
Resolution: "Don't do stupid stuff",
|
||||
Explanation: "Bad should not be set.",
|
||||
Severity: severity.High,
|
||||
CustomChecks: scan.CustomChecks{
|
||||
Terraform: &scan.TerraformCustomCheck{
|
||||
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": `
|
||||
func Test_Modules(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
files map[string]string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
// 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.
|
||||
name: "go-cty compatibility issue",
|
||||
files: map[string]string{
|
||||
"/project/main.tf": `
|
||||
data "aws_vpc" "default" {
|
||||
default = true
|
||||
}
|
||||
@@ -54,10 +28,8 @@ data "aws_vpc" "default" {
|
||||
module "test" {
|
||||
source = "../modules/problem/"
|
||||
cidr_block = data.aws_vpc.default.cidr_block
|
||||
}
|
||||
`,
|
||||
"/modules/problem/main.tf": `
|
||||
variable "cidr_block" {}
|
||||
}`,
|
||||
"/modules/problem/main.tf": `variable "cidr_block" {}
|
||||
|
||||
variable "open" {
|
||||
default = false
|
||||
@@ -76,589 +48,333 @@ resource "aws_security_group" "this" {
|
||||
}
|
||||
}
|
||||
|
||||
resource "problem" "uhoh" {
|
||||
bad = true
|
||||
}
|
||||
`,
|
||||
})
|
||||
|
||||
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, 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": `
|
||||
resource "aws_s3_bucket" "test" {}`,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "misconfig in sibling directory module",
|
||||
files: map[string]string{
|
||||
"/project/main.tf": `
|
||||
module "something" {
|
||||
source = "../modules/problem"
|
||||
}
|
||||
`,
|
||||
"modules/problem/main.tf": `
|
||||
resource "problem" "uhoh" {
|
||||
bad = true
|
||||
}
|
||||
`},
|
||||
)
|
||||
|
||||
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, 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
|
||||
"modules/problem/main.tf": `
|
||||
resource "aws_s3_bucket" "test" {}`,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "ignore misconfig in module",
|
||||
files: map[string]string{
|
||||
"/project/main.tf": `
|
||||
#tfsec:ignore:aws-s3-non-empty-bucket
|
||||
module "something" {
|
||||
source = "../modules/problem"
|
||||
}
|
||||
`,
|
||||
"modules/problem/main.tf": `
|
||||
resource "problem" "uhoh" {
|
||||
bad = true
|
||||
}
|
||||
`},
|
||||
)
|
||||
|
||||
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, 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": `
|
||||
"modules/problem/main.tf": `
|
||||
resource "aws_s3_bucket" "test" {}
|
||||
`,
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "misconfig in subdirectory module",
|
||||
files: map[string]string{
|
||||
"project/main.tf": `
|
||||
module "something" {
|
||||
source = "./modules/problem"
|
||||
}
|
||||
`,
|
||||
"project/modules/problem/main.tf": `
|
||||
resource "problem" "uhoh" {
|
||||
bad = true
|
||||
}
|
||||
`})
|
||||
|
||||
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, 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": `
|
||||
"project/modules/problem/main.tf": `
|
||||
resource "aws_s3_bucket" "test" {}
|
||||
`,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "misconfig in parent directory module",
|
||||
files: map[string]string{
|
||||
"project/main.tf": `
|
||||
module "something" {
|
||||
source = "../problem"
|
||||
}
|
||||
`,
|
||||
"problem/main.tf": `
|
||||
resource "problem" "uhoh" {
|
||||
bad = true
|
||||
}
|
||||
`})
|
||||
|
||||
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, 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": `
|
||||
"problem/main.tf": `
|
||||
resource "aws_s3_bucket" "test" {}
|
||||
`},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "misconfig in reused module",
|
||||
files: map[string]string{
|
||||
"project/main.tf": `
|
||||
module "something_good" {
|
||||
source = "../modules/problem"
|
||||
bad = false
|
||||
bucket = "test"
|
||||
}
|
||||
|
||||
module "something_bad" {
|
||||
source = "../modules/problem"
|
||||
bad = true
|
||||
bucket = ""
|
||||
}
|
||||
`,
|
||||
"modules/problem/main.tf": `
|
||||
variable "bad" {
|
||||
default = false
|
||||
"modules/problem/main.tf": `
|
||||
variable "bucket" {}
|
||||
|
||||
resource "aws_s3_bucket" "test" {
|
||||
bucket = var.bucket
|
||||
}
|
||||
resource "problem" "uhoh" {
|
||||
bad = var.bad
|
||||
}
|
||||
`})
|
||||
|
||||
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, 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": `
|
||||
`},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "misconfig in nested module",
|
||||
files: map[string]string{
|
||||
"project/main.tf": `
|
||||
module "something" {
|
||||
source = "../modules/a"
|
||||
}
|
||||
`,
|
||||
"modules/a/main.tf": `
|
||||
"modules/a/main.tf": `
|
||||
module "something" {
|
||||
source = "../../modules/b"
|
||||
}
|
||||
`,
|
||||
"modules/b/main.tf": `
|
||||
"modules/b/main.tf": `
|
||||
module "something" {
|
||||
source = "../c"
|
||||
}
|
||||
`,
|
||||
"modules/c/main.tf": `
|
||||
resource "problem" "uhoh" {
|
||||
bad = true
|
||||
}
|
||||
`,
|
||||
})
|
||||
|
||||
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, 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": `
|
||||
"modules/c/main.tf": `resource "aws_s3_bucket" "test" {}`,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "misconfig in reused nested module",
|
||||
files: map[string]string{
|
||||
"project/main.tf": `
|
||||
module "something" {
|
||||
source = "../modules/a"
|
||||
bad = false
|
||||
bucket = "test"
|
||||
}
|
||||
|
||||
module "something-bad" {
|
||||
source = "../modules/a"
|
||||
bad = true
|
||||
bucket = ""
|
||||
}
|
||||
`,
|
||||
"modules/a/main.tf": `
|
||||
variable "bad" {
|
||||
default = false
|
||||
}
|
||||
"modules/a/main.tf": `
|
||||
variable "bucket" {}
|
||||
|
||||
module "something" {
|
||||
source = "../../modules/b"
|
||||
bad = var.bad
|
||||
bucket = var.bucket
|
||||
}
|
||||
`,
|
||||
"modules/b/main.tf": `
|
||||
variable "bad" {
|
||||
default = false
|
||||
}
|
||||
"modules/b/main.tf": `
|
||||
variable "bucket" {}
|
||||
|
||||
module "something" {
|
||||
source = "../c"
|
||||
bad = var.bad
|
||||
bucket = var.bad
|
||||
}
|
||||
`,
|
||||
"modules/c/main.tf": `
|
||||
variable "bad" {
|
||||
default = false
|
||||
}
|
||||
resource "problem" "uhoh" {
|
||||
bad = var.bad
|
||||
"modules/c/main.tf": `
|
||||
variable "bucket" {}
|
||||
|
||||
resource "aws_s3_bucket" "test" {
|
||||
bucket = var.bucket
|
||||
}
|
||||
`,
|
||||
})
|
||||
|
||||
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, 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": `
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "misconfig in terraform cached module",
|
||||
files: map[string]string{
|
||||
"project/main.tf": `
|
||||
module "something" {
|
||||
source = "../modules/somewhere"
|
||||
bad = false
|
||||
bucket = "test"
|
||||
}
|
||||
`,
|
||||
"modules/somewhere/main.tf": `
|
||||
"modules/somewhere/main.tf": `
|
||||
module "something_nested" {
|
||||
count = 1
|
||||
source = "github.com/some/module.git"
|
||||
bad = true
|
||||
bucket = ""
|
||||
}
|
||||
|
||||
variable "bad" {
|
||||
default = false
|
||||
}
|
||||
variable "bucket" {
|
||||
default = ""
|
||||
}`,
|
||||
"project/.terraform/modules/something.something_nested/main.tf": `
|
||||
variable "bucket" {}
|
||||
|
||||
`,
|
||||
"project/.terraform/modules/something.something_nested/main.tf": `
|
||||
variable "bad" {
|
||||
default = false
|
||||
}
|
||||
resource "problem" "uhoh" {
|
||||
bad = var.bad
|
||||
resource "aws_s3_bucket" "test" {
|
||||
bucket = var.bucket
|
||||
}
|
||||
`,
|
||||
"project/.terraform/modules/modules.json": `
|
||||
"project/.terraform/modules/modules.json": `
|
||||
{"Modules":[
|
||||
{"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"}
|
||||
]}
|
||||
`,
|
||||
})
|
||||
|
||||
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, 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": `
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "misconfig in reused terraform cached module",
|
||||
files: map[string]string{
|
||||
"project/main.tf": `
|
||||
module "something" {
|
||||
source = "/nowhere"
|
||||
bad = false
|
||||
bucket = ""
|
||||
}
|
||||
|
||||
module "something2" {
|
||||
source = "/nowhere"
|
||||
bad = true
|
||||
bucket = ""
|
||||
}
|
||||
`,
|
||||
"project/.terraform/modules/a/main.tf": `
|
||||
variable "bad" {
|
||||
default = false
|
||||
}
|
||||
resource "problem" "uhoh" {
|
||||
bad = var.bad
|
||||
"project/.terraform/modules/a/main.tf": `
|
||||
variable "bucket" {}
|
||||
|
||||
resource "aws_s3_bucket" "test" {
|
||||
bucket = var.bucket
|
||||
}
|
||||
`,
|
||||
"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"}]}
|
||||
`,
|
||||
})
|
||||
|
||||
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, 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": `
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "misconfig in nested modules with duplicate module names and paths",
|
||||
files: map[string]string{
|
||||
"project/main.tf": `
|
||||
module "something" {
|
||||
source = "../modules/a"
|
||||
bad = 0
|
||||
s3_bucket_count = 0
|
||||
}
|
||||
|
||||
module "something-bad" {
|
||||
source = "../modules/a"
|
||||
bad = 1
|
||||
s3_bucket_count = 1
|
||||
}
|
||||
`,
|
||||
"modules/a/main.tf": `
|
||||
variable "bad" {
|
||||
"modules/a/main.tf": `
|
||||
variable "s3_bucket_count" {
|
||||
default = 0
|
||||
}
|
||||
module "something" {
|
||||
source = "../b"
|
||||
bad = var.bad
|
||||
s3_bucket_count = var.s3_bucket_count
|
||||
}
|
||||
`,
|
||||
"modules/b/main.tf": `
|
||||
variable "bad" {
|
||||
"modules/b/main.tf": `
|
||||
variable "s3_bucket_count" {
|
||||
default = 0
|
||||
}
|
||||
module "something" {
|
||||
source = "../c"
|
||||
bad = var.bad
|
||||
s3_bucket_count = var.s3_bucket_count
|
||||
}
|
||||
`,
|
||||
"modules/c/main.tf": `
|
||||
variable "bad" {
|
||||
"modules/c/main.tf": `
|
||||
variable "s3_bucket_count" {
|
||||
default = 0
|
||||
}
|
||||
resource "problem" "uhoh" {
|
||||
count = var.bad
|
||||
bad = true
|
||||
|
||||
resource "aws_s3_bucket" "test" {
|
||||
count = var.s3_bucket_count
|
||||
}
|
||||
`,
|
||||
})
|
||||
|
||||
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, badRule.LongID(), results, "")
|
||||
|
||||
}
|
||||
|
||||
func Test_Dynamic_Variables(t *testing.T) {
|
||||
fs := testutil.CreateFS(t, map[string]string{
|
||||
"project/main.tf": `
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "misconfigured attribute referencing to dynamic variable",
|
||||
files: map[string]string{
|
||||
"project/main.tf": `
|
||||
resource "something" "this" {
|
||||
|
||||
dynamic "blah" {
|
||||
for_each = ["a"]
|
||||
|
||||
content {
|
||||
ok = true
|
||||
bucket = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "bad" "thing" {
|
||||
secure = something.this.blah[0].ok
|
||||
}
|
||||
`})
|
||||
|
||||
r1 := scan.Rule{
|
||||
Provider: providers.AWSProvider,
|
||||
Service: "service",
|
||||
ShortCode: "abc123",
|
||||
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
|
||||
},
|
||||
},
|
||||
resource "aws_s3_bucket" "test" {
|
||||
secure = something.this.blah[0].bucket
|
||||
}
|
||||
`},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
reg := rules.Register(r1)
|
||||
defer rules.Deregister(reg)
|
||||
|
||||
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": `
|
||||
{
|
||||
name: "attribute referencing to dynamic variable without index",
|
||||
files: map[string]string{
|
||||
"project/main.tf": `
|
||||
resource "something" "else" {
|
||||
x = 1
|
||||
dynamic "blah" {
|
||||
for_each = toset(["true"])
|
||||
|
||||
for_each = toset(["test"])
|
||||
content {
|
||||
ok = blah.value
|
||||
bucket = blah.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "bad" "thing" {
|
||||
secure = something.else.blah.ok
|
||||
}
|
||||
`})
|
||||
|
||||
r1 := scan.Rule{
|
||||
Provider: providers.AWSProvider,
|
||||
Service: "service",
|
||||
ShortCode: "abc123",
|
||||
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
|
||||
},
|
||||
},
|
||||
resource "aws_s3_bucket" "test" {
|
||||
bucket = something.else.blah.bucket
|
||||
}`},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
reg := rules.Register(r1)
|
||||
defer rules.Deregister(reg)
|
||||
{
|
||||
name: "references passed to nested module",
|
||||
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.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"
|
||||
resource "some_resource" "this" {
|
||||
name = "test"
|
||||
}
|
||||
|
||||
module "something" {
|
||||
source = "../modules/a"
|
||||
group = aws_iam_group.developers.name
|
||||
bucket = some_resource.this.name
|
||||
}
|
||||
`,
|
||||
"modules/a/main.tf": `
|
||||
variable "group" {
|
||||
"modules/a/main.tf": `
|
||||
variable "bucket" {
|
||||
type = string
|
||||
}
|
||||
|
||||
resource "aws_iam_group_policy" "mfa" {
|
||||
group = var.group
|
||||
policy = data.aws_iam_policy_document.policy.json
|
||||
resource "aws_s3_bucket" "test" {
|
||||
bucket = var.bucket
|
||||
}
|
||||
`},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
data "aws_iam_policy_document" "policy" {
|
||||
statement {
|
||||
sid = "main"
|
||||
effect = "Allow"
|
||||
|
||||
actions = ["s3:*"]
|
||||
resources = ["*"]
|
||||
condition {
|
||||
test = "Bool"
|
||||
variable = "aws:MultiFactorAuthPresent"
|
||||
values = ["true"]
|
||||
}
|
||||
}
|
||||
}
|
||||
`})
|
||||
|
||||
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, "")
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fsys := testutil.CreateFS(t, tt.files)
|
||||
results, err := scanFS(fsys, "project",
|
||||
rego.WithPolicyReader(strings.NewReader(emptyBucketCheck)),
|
||||
rego.WithPolicyNamespaces("user"),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
if tt.expected {
|
||||
testutil.AssertRuleFound(t, "aws-s3-non-empty-bucket", results, "")
|
||||
} else {
|
||||
testutil.AssertRuleNotFailed(t, "aws-s3-non-empty-bucket", results, "")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package terraform
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -24,29 +25,11 @@ module "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(
|
||||
rego.WithPolicyFilesystem(fs),
|
||||
rego.WithPolicyDirs("rules"),
|
||||
rego.WithPolicyReader(strings.NewReader(emptyBucketCheck)),
|
||||
rego.WithPolicyNamespaces("user"),
|
||||
rego.WithEmbeddedPolicies(false),
|
||||
rego.WithEmbeddedLibraries(false),
|
||||
options.ScannerWithRegoOnly(true),
|
||||
@@ -81,29 +64,11 @@ module "s3_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(
|
||||
rego.WithPolicyFilesystem(fs),
|
||||
rego.WithPolicyDirs("rules"),
|
||||
rego.WithPolicyReader(strings.NewReader(emptyBucketCheck)),
|
||||
rego.WithPolicyNamespaces("user"),
|
||||
rego.WithEmbeddedPolicies(false),
|
||||
rego.WithEmbeddedLibraries(false),
|
||||
options.ScannerWithRegoOnly(true),
|
||||
|
||||
@@ -20,76 +20,25 @@ import (
|
||||
"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) {
|
||||
|
||||
fs := testutil.CreateFS(t, map[string]string{
|
||||
"/code/main.tf": `
|
||||
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
|
||||
}
|
||||
`,
|
||||
fsys := testutil.CreateFS(t, map[string]string{
|
||||
"/code/main.tf": `resource "aws_s3_bucket" "my-bucket" {}`,
|
||||
"/rules/test.rego": emptyBucketCheck,
|
||||
})
|
||||
|
||||
scanner := New(
|
||||
rego.WithPolicyFilesystem(fs),
|
||||
results, err := scanFS(fsys, "code",
|
||||
rego.WithPolicyFilesystem(fsys),
|
||||
rego.WithPolicyDirs("rules"),
|
||||
options.ScannerWithRegoOnly(true),
|
||||
rego.WithPolicyNamespaces("user"),
|
||||
)
|
||||
|
||||
results, err := scanner.ScanFS(context.TODO(), fs, "code")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, results.GetFailed(), 1)
|
||||
|
||||
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()
|
||||
require.NoError(t, err)
|
||||
@@ -98,28 +47,11 @@ deny[cause] {
|
||||
}
|
||||
assert.Equal(t, []scan.Line{
|
||||
{
|
||||
Number: 2,
|
||||
Content: "resource \"aws_s3_bucket\" \"my-bucket\" {",
|
||||
IsCause: false,
|
||||
FirstCause: false,
|
||||
LastCause: false,
|
||||
Annotation: "",
|
||||
},
|
||||
{
|
||||
Number: 3,
|
||||
Content: "\tbucket = \"evil\"",
|
||||
Number: 1,
|
||||
Content: "resource \"aws_s3_bucket\" \"my-bucket\" {}",
|
||||
IsCause: true,
|
||||
FirstCause: true,
|
||||
LastCause: true,
|
||||
Annotation: "",
|
||||
},
|
||||
{
|
||||
Number: 4,
|
||||
Content: "}",
|
||||
IsCause: false,
|
||||
FirstCause: false,
|
||||
LastCause: false,
|
||||
Annotation: "",
|
||||
},
|
||||
}, actualCode.Lines)
|
||||
|
||||
@@ -236,99 +168,41 @@ cause := bucket.name
|
||||
|
||||
func Test_OptionWithRegoOnly(t *testing.T) {
|
||||
|
||||
fs := testutil.CreateFS(t, map[string]string{
|
||||
fsys := testutil.CreateFS(t, map[string]string{
|
||||
"/code/main.tf": `
|
||||
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
|
||||
}
|
||||
resource "aws_s3_bucket" "my-bucket" {}
|
||||
`,
|
||||
"/rules/test.rego": emptyBucketCheck,
|
||||
})
|
||||
|
||||
scanner := New(
|
||||
results, err := scanFS(fsys, "code",
|
||||
rego.WithPolicyDirs("rules"),
|
||||
options.ScannerWithRegoOnly(true),
|
||||
rego.WithPolicyNamespaces("user"),
|
||||
)
|
||||
|
||||
results, err := scanner.ScanFS(context.TODO(), fs, "code")
|
||||
require.NoError(t, err)
|
||||
|
||||
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) {
|
||||
|
||||
fs := testutil.CreateFS(t, map[string]string{
|
||||
fsys := testutil.CreateFS(t, map[string]string{
|
||||
"/code/main.tf": `
|
||||
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)
|
||||
}
|
||||
resource "aws_s3_bucket" "my-bucket" {}
|
||||
`,
|
||||
"/rules/test.rego": emptyBucketCheck,
|
||||
})
|
||||
|
||||
scanner := New(
|
||||
results, err := scanFS(fsys, "code",
|
||||
rego.WithPolicyDirs("rules"),
|
||||
options.ScannerWithRegoOnly(true),
|
||||
rego.WithEmbeddedLibraries(true),
|
||||
rego.WithPolicyNamespaces("user"),
|
||||
)
|
||||
|
||||
results, err := scanner.ScanFS(context.TODO(), fs, "code")
|
||||
require.NoError(t, err)
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
@@ -709,7 +583,7 @@ resource "aws_s3_bucket" "main" {
|
||||
bucket = var.bucket_name
|
||||
}
|
||||
`,
|
||||
"rules/bucket_name.rego": emptyBucketRule,
|
||||
"rules/bucket_name.rego": emptyBucketCheck,
|
||||
})
|
||||
|
||||
configsFS := testutil.CreateFS(t, map[string]string{
|
||||
@@ -719,6 +593,7 @@ bucket_name = "test"
|
||||
})
|
||||
|
||||
scanner := New(
|
||||
rego.WithPolicyNamespaces("user"),
|
||||
rego.WithPolicyDirs("rules"),
|
||||
rego.WithPolicyFilesystem(fs),
|
||||
options.ScannerWithRegoOnly(true),
|
||||
@@ -746,13 +621,14 @@ resource "aws_s3_bucket" "main" {
|
||||
bucket = var.bucket_name
|
||||
}
|
||||
`,
|
||||
"rules/bucket_name.rego": emptyBucketRule,
|
||||
"rules/bucket_name.rego": emptyBucketCheck,
|
||||
"main.tfvars": `
|
||||
bucket_name = "test"
|
||||
`,
|
||||
})
|
||||
|
||||
scanner := New(
|
||||
rego.WithPolicyNamespaces("user"),
|
||||
rego.WithPolicyDirs("rules"),
|
||||
rego.WithPolicyFilesystem(fs),
|
||||
options.ScannerWithRegoOnly(true),
|
||||
@@ -805,25 +681,7 @@ resource "aws_security_group" "main" {
|
||||
description = var.security_group_description
|
||||
}
|
||||
`,
|
||||
"/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)
|
||||
}
|
||||
`,
|
||||
"/rules/bucket_name.rego": emptyBucketCheck,
|
||||
"/rules/sec_group_description.rego": `
|
||||
# METADATA
|
||||
# schemas:
|
||||
@@ -846,6 +704,7 @@ deny[res] {
|
||||
})
|
||||
|
||||
scanner := New(
|
||||
rego.WithPolicyNamespaces("user"),
|
||||
rego.WithPolicyFilesystem(fs),
|
||||
rego.WithPolicyDirs("rules"),
|
||||
rego.WithEmbeddedPolicies(false),
|
||||
|
||||
@@ -2,6 +2,7 @@ package terraform
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/fs"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -14,6 +15,68 @@ import (
|
||||
"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 {
|
||||
fs := testutil.CreateFS(t, map[string]string{
|
||||
"source" + ext: source,
|
||||
@@ -30,30 +93,40 @@ func createModulesFromSource(t *testing.T, source, ext string) terraform.Modules
|
||||
return modules
|
||||
}
|
||||
|
||||
func scanHCLWithWorkspace(t *testing.T, source, workspace string) scan.Results {
|
||||
return scanHCL(t, source, ScannerWithWorkspaceName(workspace))
|
||||
func scanFS(fsys fs.FS, target string, opts ...options.ScannerOption) (scan.Results, error) {
|
||||
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 {
|
||||
|
||||
fs := testutil.CreateFS(t, map[string]string{
|
||||
fsys := testutil.CreateFS(t, map[string]string{
|
||||
"main.tf": source,
|
||||
})
|
||||
|
||||
localScanner := New(append(opts, rego.WithEmbeddedPolicies(false))...)
|
||||
results, err := localScanner.ScanFS(context.TODO(), fs, ".")
|
||||
results, err := scanFS(fsys, ".", opts...)
|
||||
require.NoError(t, err)
|
||||
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,
|
||||
})
|
||||
|
||||
s := New(rego.WithEmbeddedPolicies(true), rego.WithEmbeddedLibraries(true))
|
||||
results, err := s.ScanFS(context.TODO(), fs, ".")
|
||||
results, err := scanFS(fsys, ".", opts...)
|
||||
require.NoError(t, err)
|
||||
return results
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package terraform
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/aquasecurity/trivy/internal/testutil"
|
||||
"github.com/aquasecurity/trivy/pkg/iac/rules"
|
||||
"github.com/aquasecurity/trivy/pkg/iac/scan"
|
||||
@@ -49,7 +52,7 @@ func Test_WildcardMatchingOnRequiredLabels(t *testing.T) {
|
||||
|
||||
code := fmt.Sprintf("wild%d", i)
|
||||
|
||||
t.Run(code, func(t *testing.T) {
|
||||
t.Run(test.pattern, func(t *testing.T) {
|
||||
|
||||
rule := scan.Rule{
|
||||
Service: "service",
|
||||
@@ -71,7 +74,12 @@ func Test_WildcardMatchingOnRequiredLabels(t *testing.T) {
|
||||
reg := rules.Register(rule)
|
||||
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 {
|
||||
testutil.AssertRuleFound(t, fmt.Sprintf("custom-service-%s", code), results, "")
|
||||
|
||||
Reference in New Issue
Block a user