Repository: aquasecurity/tfsec
Pull request #1178 (2021-12-08 13:45:08)

Check: Add MFA enforcement for aws_iam_group
- update the go-sarif version
- Add support for MFA enforcement



Patch:
diff --git a/example/mfa/group.tf b/example/mfa/group.tf
new file mode 100644
index 000000000..c0049bc50
--- /dev/null
+++ b/example/mfa/group.tf
@@ -0,0 +1,21 @@
+data aws_caller_identity current {}
+
+resource aws_iam_group support {
+  name =  "support"
+}
+
+resource aws_iam_group developers {
+  name =  "developers"
+}
+
+module enforce_mfa {
+  source  = "terraform-module/enforce-mfa/aws"
+  version = "0.12.0"
+
+  policy_name                     = "managed-mfa-enforce"
+  account_id                      = data.aws_caller_identity.current.id
+  groups                          = [aws_iam_group.support.name]
+  manage_own_signing_certificates  = true
+  manage_own_ssh_public_keys      = true
+  manage_own_git_credentials      = true
+}
diff --git a/go.mod b/go.mod
index 885eaaec3..4e628a0b8 100644
--- a/go.mod
+++ b/go.mod
@@ -16,7 +16,7 @@ require (
 	github.com/liamg/tml v0.4.0
 	github.com/mitchellh/go-homedir v1.1.0
 	github.com/olekukonko/tablewriter v0.0.5
-	github.com/owenrumney/go-sarif v1.0.14
+	github.com/owenrumney/go-sarif v1.1.1
 	github.com/owenrumney/squealer v0.3.1
 	github.com/spf13/cobra v1.2.1
 	github.com/stretchr/testify v1.7.0
diff --git a/go.sum b/go.sum
index 72f93f225..a7542d1ee 100644
--- a/go.sum
+++ b/go.sum
@@ -314,8 +314,9 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
 github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
 github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
-github.com/owenrumney/go-sarif v1.0.14 h1:uvPPxZbmyR1S59iOHePrlrjbM5Ysp9tEc5K3MSwIHD0=
 github.com/owenrumney/go-sarif v1.0.14/go.mod h1:dNDiPlF04ESR/6fHlPyq7gHKmrM0sHUvAGjsoh8ZH0U=
+github.com/owenrumney/go-sarif v1.1.1 h1:QNObu6YX1igyFKhdzd7vgzmw7XsWN3/6NMGuDzBgXmE=
+github.com/owenrumney/go-sarif v1.1.1/go.mod h1:dNDiPlF04ESR/6fHlPyq7gHKmrM0sHUvAGjsoh8ZH0U=
 github.com/owenrumney/squealer v0.3.1 h1:iBo817khHVpcP65om+iXuSYg+G6iaVvjsY0amcy1UAA=
 github.com/owenrumney/squealer v0.3.1/go.mod h1:mRMvbRyrnE2mYghLpn4M2jATJhIddp4XOAKKY60X7nY=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
diff --git a/internal/app/tfsec/block/hclattribute.go b/internal/app/tfsec/block/hclattribute.go
index 22f14ebda..e415c4dfa 100644
--- a/internal/app/tfsec/block/hclattribute.go
+++ b/internal/app/tfsec/block/hclattribute.go
@@ -678,6 +678,11 @@ func (attr *HCLAttribute) referencesInTemplate() []*Reference {
 			}
 			refs = append(refs, ref)
 		}
+	case *hclsyntax.TupleConsExpr:
+		ref, err := createDotReferenceFromTraversal(t.Variables()...)
+		if err == nil {
+			refs = append(refs, ref)
+		}
 	}
 	return refs
 }
diff --git a/internal/app/tfsec/block/hclmodule.go b/internal/app/tfsec/block/hclmodule.go
index fb80633cf..ea608a510 100644
--- a/internal/app/tfsec/block/hclmodule.go
+++ b/internal/app/tfsec/block/hclmodule.go
@@ -33,6 +33,16 @@ func (c *HCLModule) getBlocksByType(blockType string, label string) Blocks {
 	return results
 }
 
+func (c *HCLModule) getModuleBlocks() Blocks {
+	var results Blocks
+	for _, block := range c.blocks {
+		if block.Type() == "module" {
+			results = append(results, block)
+		}
+	}
+	return results
+}
+
 func (c *HCLModule) GetResourcesByType(label string) Blocks {
 	return c.getBlocksByType("resource", label)
 }
@@ -73,6 +83,18 @@ func (c *HCLModule) GetReferencingResources(originalBlock Block, referencingLabe
 	return c.getReferencingBlocks(originalBlock, "resource", referencingLabel, referencingAttributeName)
 }
 
+func (c *HCLModule) GetsModulesBySource(moduleSource string) (Blocks, error) {
+	var results Blocks
+
+	modules := c.getModuleBlocks()
+	for _, module := range modules {
+		if module.HasChild("source") && module.GetAttribute("source").Equals(moduleSource) {
+			results = append(results, module)
+		}
+	}
+	return results, nil
+}
+
 func (c *HCLModule) getReferencingBlocks(originalBlock Block, referencingType string, referencingLabel string, referencingAttributeName string) (Blocks, error) {
 	blocks := c.getBlocksByType(referencingType, referencingLabel)
 	var results Blocks
diff --git a/internal/app/tfsec/block/module.go b/internal/app/tfsec/block/module.go
index fbb1509f2..54caa3618 100644
--- a/internal/app/tfsec/block/module.go
+++ b/internal/app/tfsec/block/module.go
@@ -7,4 +7,5 @@ type Module interface {
 	GetProviderBlocksByProvider(providerName string, alias string) Blocks
 	GetReferencedBlock(referringAttr Attribute) (Block, error)
 	GetReferencingResources(originalBlock Block, referencingLabel string, referencingAttributeName string) (Blocks, error)
+	GetsModulesBySource(moduleSource string) (Blocks, error)
 }
diff --git a/internal/app/tfsec/formatters/sarif.go b/internal/app/tfsec/formatters/sarif.go
index 7a82f6c05..1b50855d3 100644
--- a/internal/app/tfsec/formatters/sarif.go
+++ b/internal/app/tfsec/formatters/sarif.go
@@ -5,10 +5,9 @@ import (
 	"path/filepath"
 
 	"github.com/aquasecurity/tfsec/pkg/severity"
+	"github.com/owenrumney/go-sarif/sarif"
 
 	"github.com/aquasecurity/tfsec/pkg/result"
-
-	"github.com/owenrumney/go-sarif/sarif"
 )
 
 func FormatSarif(w io.Writer, results []result.Result, baseDir string, _ ...FormatterOption) error {
@@ -32,7 +31,7 @@ func FormatSarif(w io.Writer, results []result.Result, baseDir string, _ ...Form
 		}
 		rule := run.AddRule(res.RuleID).
 			WithDescription(res.RuleSummary).
-			WithHelp(link)
+			WithHelpURI(link)
 
 		relativePath, err := filepath.Rel(baseDir, res.Range().Filename)
 		if err != nil {
diff --git a/internal/app/tfsec/rules/aws/iam/enforce_mfa_rule.go b/internal/app/tfsec/rules/aws/iam/enforce_mfa_rule.go
new file mode 100644
index 000000000..65b903d2a
--- /dev/null
+++ b/internal/app/tfsec/rules/aws/iam/enforce_mfa_rule.go
@@ -0,0 +1,105 @@
+package iam
+
+import (
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/block"
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/scanner"
+	"github.com/aquasecurity/tfsec/pkg/provider"
+	"github.com/aquasecurity/tfsec/pkg/result"
+	"github.com/aquasecurity/tfsec/pkg/rule"
+	"github.com/aquasecurity/tfsec/pkg/severity"
+)
+
+func init() {
+	scanner.RegisterCheckRule(rule.Rule{
+		LegacyID:  "",
+		Service:   "iam",
+		ShortCode: "enforce-mfa",
+		Documentation: rule.RuleDocumentation{
+			Summary:    "IAM Groups should have MFA enforcement activated.",
+			Impact:     "User accounts are more vulnerable to compromise without multi factor authentication activated",
+			Resolution: "Use terraform-module/enforce-mfa/aws to ensure that MFA is enforced",
+			Explanation: `
+IAM user accounts should be protected with multi factor authentication to add safe guards to password compromise.
+			`,
+			BadExample: []string{`
+data aws_caller_identity current {}
+
+resource aws_iam_group support {
+  name =  "support"
+}
+
+resource aws_iam_group developers {
+  name =  "developers"
+}
+`, `
+data aws_caller_identity current {}
+
+resource aws_iam_group support {
+  name =  "support"
+}
+
+resource aws_iam_group developers {
+  name =  "developers"
+}
+
+module enforce_mfa {
+  source  = "terraform-module/enforce-mfa/aws"
+  version = "0.12.0"
+
+  policy_name                     = "managed-mfa-enforce"
+  account_id                      = data.aws_caller_identity.current.id
+  groups                          = [aws_iam_group.support.name]
+  manage_own_signing_certificates  = true
+  manage_own_ssh_public_keys      = true
+  manage_own_git_credentials      = true
+}
+`},
+			GoodExample: []string{`
+data aws_caller_identity current {}
+
+resource aws_iam_group support {
+  name =  "support"
+}
+
+module enforce_mfa {
+  source  = "terraform-module/enforce-mfa/aws"
+  version = "0.12.0"
+
+  policy_name                     = "managed-mfa-enforce"
+  account_id                      = data.aws_caller_identity.current.id
+  groups                          = [aws_iam_group.support.name]
+  manage_own_signing_certificates  = true
+  manage_own_ssh_public_keys      = true
+  manage_own_git_credentials      = true
+}
+`},
+			Links: []string{
+				"https://registry.terraform.io/modules/terraform-module/enforce-mfa/aws/latest",
+				"https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_passwords_account-policy.html#password-policy-details",
+			},
+		},
+		Provider:        provider.AWSProvider,
+		RequiredTypes:   []string{"resource"},
+		RequiredLabels:  []string{"aws_iam_group"},
+		DefaultSeverity: severity.High,
+		CheckFunc: func(set result.Set, resourceBlock block.Block, module block.Module) {
+			blocks, err := module.GetsModulesBySource("terraform-module/enforce-mfa/aws")
+			if err != nil || len(blocks) == 0 {
+				set.AddResult().
+					WithDescription("Resource %s has no associated MFA enforcement block.", resourceBlock.FullName())
+			}
+
+			for _, moduleBlock := range blocks {
+				groupsAttr := moduleBlock.GetAttribute("groups")
+				if groupsAttr.IsNil() {
+					continue
+				}
+				if groupsAttr.ReferencesBlock(resourceBlock) {
+					return
+				}
+			}
+			set.AddResult().
+				WithDescription("Resource %s has no associated MFA enforcement block.", resourceBlock.FullName())
+		},
+	})
+}
diff --git a/vendor/github.com/owenrumney/go-sarif/sarif/multi_format_message_string.go b/vendor/github.com/owenrumney/go-sarif/sarif/multi_format_message_string.go
index c730df4d1..bacd67c5e 100644
--- a/vendor/github.com/owenrumney/go-sarif/sarif/multi_format_message_string.go
+++ b/vendor/github.com/owenrumney/go-sarif/sarif/multi_format_message_string.go
@@ -2,13 +2,19 @@ package sarif
 
 type MultiformatMessageString struct {
 	PropertyBag
-	Text     string  `json:"text"`
+	Text     *string `json:"text,omitempty"`
 	Markdown *string `json:"markdown,omitempty"`
 }
 
+func NewMarkdownMultiformatMessageString(markdown string) *MultiformatMessageString {
+	return &MultiformatMessageString{
+		Markdown: &markdown,
+	}
+}
+
 func NewMultiformatMessageString(text string) *MultiformatMessageString {
 	return &MultiformatMessageString{
-		Text: text,
+		Text: &text,
 	}
 }
 
diff --git a/vendor/github.com/owenrumney/go-sarif/sarif/reporting_descriptor.go b/vendor/github.com/owenrumney/go-sarif/sarif/reporting_descriptor.go
new file mode 100644
index 000000000..41b0a9761
--- /dev/null
+++ b/vendor/github.com/owenrumney/go-sarif/sarif/reporting_descriptor.go
@@ -0,0 +1,87 @@
+package sarif
+
+type ReportingConfiguration struct {
+	Enabled    bool         `json:"enabled,omitempty"`
+	Level      interface{}  `json:"level,omitempty"`
+	Parameters *PropertyBag `json:"parameters,omitempty"`
+	Properties *PropertyBag `json:"properties,omitempty"`
+	Rank       float64      `json:"rank,omitempty"`
+}
+
+// ReportingDescriptor specifies a Sarif ReportingDescriptor object
+type ReportingDescriptor struct {
+	PropertyBag
+	ID                   string                    `json:"id"`
+	Name                 *string                   `json:"name,omitempty"`
+	ShortDescription     *MultiformatMessageString `json:"shortDescription"`
+	FullDescription      *MultiformatMessageString `json:"fullDescription,omitempty"`
+	DefaultConfiguration *ReportingConfiguration   `json:"defaultConfiguration,omitempty"`
+	HelpURI              *string                   `json:"helpUri,omitempty"`
+	Help                 *MultiformatMessageString `json:"help,omitempty"`
+	Properties           Properties                `json:"properties,omitempty"`
+}
+
+func newRule(ruleID string) *ReportingDescriptor {
+	return &ReportingDescriptor{
+		ID: ruleID,
+	}
+}
+
+// WithName specifies rule name that is understandable to an end user and returns the updated rule.
+func (rule *ReportingDescriptor) WithName(name string) *ReportingDescriptor {
+	rule.Name = &name
+	return rule
+}
+
+// WithDescription specifies short description for a rule and returns the updated rule.
+// Short description should be a single sentence that is understandable when visible space is limited to a single line
+// of text.
+func (rule *ReportingDescriptor) WithDescription(description string) *ReportingDescriptor {
+	rule.ShortDescription = NewMultiformatMessageString(description)
+	return rule
+}
+
+// WithShortDescription specifies short description for a rule and returns the updated rule.
+// Short description should be a single sentence that is understandable when visible space is limited to a single line
+// of text.
+func (rule *ReportingDescriptor) WithShortDescription(description *MultiformatMessageString) *ReportingDescriptor {
+	rule.ShortDescription = description
+	return rule
+}
+
+// WithFullDescription specifies full description for a rule and returns the updated rule.
+// Full description should, as far as possible, provide details sufficient to enable resolution of any problem indicated
+// by the result.
+func (rule *ReportingDescriptor) WithFullDescription(description *MultiformatMessageString) *ReportingDescriptor {
+	rule.FullDescription = description
+	return rule
+}
+
+// WithHelpURI specifies a helpURI for a rule and returns the updated rule
+func (rule *ReportingDescriptor) WithHelpURI(helpURI string) *ReportingDescriptor {
+	rule.HelpURI = &helpURI
+	return rule
+}
+
+// WithHelp specifies a help text  for a rule and returns the updated rule
+func (rule *ReportingDescriptor) WithHelp(helpText string) *ReportingDescriptor {
+	rule.Help = NewMultiformatMessageString(helpText)
+	return rule
+}
+
+// WithMarkdownHelp specifies a help text  for a rule and returns the updated rule
+func (rule *ReportingDescriptor) WithMarkdownHelp(markdownText string) *ReportingDescriptor {
+	rule.Help = NewMarkdownMultiformatMessageString(markdownText)
+	return rule
+}
+
+// WithProperties specifies properties for a rule and returns the updated rule
+func (rule *ReportingDescriptor) WithProperties(properties Properties) *ReportingDescriptor {
+	rule.Properties = properties
+	return rule
+}
+
+// AttachPropertyBag adds a property bag to a rule
+func (rule *ReportingDescriptor) AttachPropertyBag(pb *PropertyBag) {
+	rule.Properties = pb.Properties
+}
diff --git a/vendor/github.com/owenrumney/go-sarif/sarif/rule.go b/vendor/github.com/owenrumney/go-sarif/sarif/rule.go
deleted file mode 100644
index 6970199cb..000000000
--- a/vendor/github.com/owenrumney/go-sarif/sarif/rule.go
+++ /dev/null
@@ -1,64 +0,0 @@
-package sarif
-
-// Rule specifies a Sarif Rule object
-type Rule struct {
-	PropertyBag
-	ID               string                    `json:"id"`
-	Name             *string                   `json:"name,omitempty"`
-	ShortDescription *MultiformatMessageString `json:"shortDescription"`
-	FullDescription  *MultiformatMessageString `json:"fullDescription,omitempty"`
-	HelpURI          *string                   `json:"helpUri,omitempty"`
-	Help             *MultiformatMessageString `json:"help,omitempty"`
-	Properties       Properties                `json:"properties,omitempty"`
-}
-
-func newRule(ruleID string) *Rule {
-	return &Rule{
-		ID: ruleID,
-	}
-}
-
-// WithName specifies rule name that is understandable to an end user and returns the updated rule.
-func (rule *Rule) WithName(name string) *Rule {
-	rule.Name = &name
-	return rule
-}
-
-// WithDescription specifies short description for a rule and returns the updated rule.
-// Short description should be a single sentence that is understandable when visible space is limited to a single line
-// of text.
-func (rule *Rule) WithDescription(description string) *Rule {
-	rule.ShortDescription = NewMultiformatMessageString(description)
-	return rule
-}
-
-// WithFullDescription specifies full description for a rule and returns the updated rule.
-// Full description should, as far as possible, provide details sufficient to enable resolution of any problem indicated
-// by the result.
-func (rule *Rule) WithFullDescription(description *MultiformatMessageString) *Rule {
-	rule.FullDescription = description
-	return rule
-}
-
-// WithHelpURI specifies a helpURI for a rule and returns the updated rule
-func (rule *Rule) WithHelpURI(helpURI string) *Rule {
-	rule.HelpURI = &helpURI
-	return rule
-}
-
-// WithHelp specifies a help text  for a rule and returns the updated rule
-func (rule *Rule) WithHelp(helpText string) *Rule {
-	rule.Help = NewMultiformatMessageString(helpText)
-	return rule
-}
-
-// WithProperties specifies properties for a rule and returns the updated rule
-func (rule *Rule) WithProperties(properties Properties) *Rule {
-	rule.Properties = properties
-	return rule
-}
-
-// AttachPropertyBag adds a property bag to a rule
-func (rule *Rule) AttachPropertyBag(pb *PropertyBag) {
-	rule.Properties = pb.Properties
-}
diff --git a/vendor/github.com/owenrumney/go-sarif/sarif/run.go b/vendor/github.com/owenrumney/go-sarif/sarif/run.go
index a1a4a2852..e99d3b50c 100644
--- a/vendor/github.com/owenrumney/go-sarif/sarif/run.go
+++ b/vendor/github.com/owenrumney/go-sarif/sarif/run.go
@@ -24,7 +24,7 @@ type Run struct { // https://docs.oasis-open.org/sarif/sarif/v2.1.0/csprd01/sari
 func NewRun(toolName, informationURI string) *Run {
 	run := &Run{
 		Tool: Tool{
-			Driver: &Driver{
+			Driver: &ToolComponent{
 				Name:           toolName,
 				InformationURI: &informationURI,
 			},
@@ -70,8 +70,8 @@ func (run *Run) AddDistinctArtifact(uri string) *Artifact {
 	return a
 }
 
-// AddRule returns an existing Rule for the ruleID or creates a new Rule and returns a pointer to it
-func (run *Run) AddRule(ruleID string) *Rule {
+// AddRule returns an existing ReportingDescriptor for the ruleID or creates a new ReportingDescriptor and returns a pointer to it
+func (run *Run) AddRule(ruleID string) *ReportingDescriptor {
 	for _, rule := range run.Tool.Driver.Rules {
 		if rule.ID == ruleID {
 			return rule
@@ -94,7 +94,7 @@ func (run *Run) AttachPropertyBag(pb *PropertyBag) {
 }
 
 // GetRuleById finds a rule by a given rule ID and returns a pointer to it
-func (run *Run) GetRuleById(ruleId string) (*Rule, error) {
+func (run *Run) GetRuleById(ruleId string) (*ReportingDescriptor, error) {
 	if run.Tool.Driver != nil {
 		for _, rule := range run.Tool.Driver.Rules {
 			if rule.ID == ruleId {
diff --git a/vendor/github.com/owenrumney/go-sarif/sarif/tool.go b/vendor/github.com/owenrumney/go-sarif/sarif/tool.go
index 3941d289f..44dcf076b 100644
--- a/vendor/github.com/owenrumney/go-sarif/sarif/tool.go
+++ b/vendor/github.com/owenrumney/go-sarif/sarif/tool.go
@@ -2,29 +2,6 @@ package sarif
 
 type Tool struct {
 	PropertyBag
-	Driver *Driver `json:"driver"`
+	Driver *ToolComponent `json:"driver"`
 }
 
-type Driver struct {
-	PropertyBag
-	Name           string  `json:"name"`
-	Version        *string `json:"version,omitempty"`
-	InformationURI *string `json:"informationUri"`
-	Rules          []*Rule `json:"rules,omitempty"`
-}
-
-// WithVersion specifies tool version, in whatever format it natively provides. Returns updated driver.
-func (driver *Driver) WithVersion(version string) *Driver {
-	driver.Version = &version
-	return driver
-}
-
-func (driver *Driver) getOrCreateRule(rule *Rule) uint {
-	for i, r := range driver.Rules {
-		if r.ID == rule.ID {
-			return uint(i)
-		}
-	}
-	driver.Rules = append(driver.Rules, rule)
-	return uint(len(driver.Rules) - 1)
-}
diff --git a/vendor/github.com/owenrumney/go-sarif/sarif/tool_component.go b/vendor/github.com/owenrumney/go-sarif/sarif/tool_component.go
new file mode 100644
index 000000000..5eac2b0b3
--- /dev/null
+++ b/vendor/github.com/owenrumney/go-sarif/sarif/tool_component.go
@@ -0,0 +1,27 @@
+package sarif
+
+type ToolComponent struct {
+	PropertyBag
+	Name           string                 `json:"name"`
+	Version        *string                `json:"version,omitempty"`
+	InformationURI *string                `json:"informationUri"`
+	Notifications  []*ReportingDescriptor `json:"notifications,omitempty"`
+	Rules          []*ReportingDescriptor `json:"rules,omitempty"`
+	Taxa           []*ReportingDescriptor `json:"taxa,omitempty"`
+}
+
+// WithVersion specifies tool version, in whatever format it natively provides. Returns updated driver.
+func (driver *ToolComponent) WithVersion(version string) *ToolComponent {
+	driver.Version = &version
+	return driver
+}
+
+func (driver *ToolComponent) getOrCreateRule(rule *ReportingDescriptor) uint {
+	for i, r := range driver.Rules {
+		if r.ID == rule.ID {
+			return uint(i)
+		}
+	}
+	driver.Rules = append(driver.Rules, rule)
+	return uint(len(driver.Rules) - 1)
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 624d98c2b..27364090c 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -180,7 +180,7 @@ github.com/mitchellh/go-wordwrap
 # github.com/olekukonko/tablewriter v0.0.5
 ## explicit; go 1.12
 github.com/olekukonko/tablewriter
-# github.com/owenrumney/go-sarif v1.0.14
+# github.com/owenrumney/go-sarif v1.1.1
 ## explicit; go 1.16
 github.com/owenrumney/go-sarif/sarif
 # github.com/owenrumney/squealer v0.3.1


Test patch:
diff --git a/internal/app/tfsec/rules/aws/iam/enforce_mfa_rule_test.go b/internal/app/tfsec/rules/aws/iam/enforce_mfa_rule_test.go
new file mode 100644
index 000000000..7a049f76c
--- /dev/null
+++ b/internal/app/tfsec/rules/aws/iam/enforce_mfa_rule_test.go
@@ -0,0 +1,53 @@
+package iam
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/scanner"
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/testutil"
+)
+
+func Test_AWSEnableLogExport_FailureExamples(t *testing.T) {
+	expectedCode := "aws-iam-enforce-mfa"
+
+	rule, err := scanner.GetRuleById(expectedCode)
+	if err != nil {
+		t.FailNow()
+	}
+	for i, badExample := range rule.Documentation.BadExample {
+		t.Logf("Running bad example for '%s' #%d", expectedCode, i+1)
+		if strings.TrimSpace(badExample) == "" {
+			t.Fatalf("bad example code not provided for %s", rule.ID())
+		}
+		defer func() {
+			if err := recover(); err != nil {
+				t.Fatalf("Scan (bad) failed: %s", err)
+			}
+		}()
+		results := testutil.ScanHCL(badExample, t)
+		testutil.AssertCheckCode(t, rule.ID(), "", results)
+	}
+}
+
+func Test_AWSEnableLogExport_SuccessExamples(t *testing.T) {
+	expectedCode := "aws-iam-enforce-mfa"
+
+	rule, err := scanner.GetRuleById(expectedCode)
+	if err != nil {
+		t.FailNow()
+	}
+	for i, example := range rule.Documentation.GoodExample {
+		t.Logf("Running good example for '%s' #%d", expectedCode, i+1)
+		if strings.TrimSpace(example) == "" {
+			t.Fatalf("good example code not provided for %s", rule.ID())
+		}
+		defer func() {
+			if err := recover(); err != nil {
+				t.Fatalf("Scan (good) failed: %s", err)
+			}
+		}()
+		results := testutil.ScanHCL(example, t)
+		testutil.AssertCheckCode(t, "", rule.ID(), results)
+	}
+}