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"
|
"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 {
|
||||||
|
|||||||
@@ -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")
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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")
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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, "")
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 (
|
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),
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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, "")
|
||||||
|
|||||||
Reference in New Issue
Block a user