Repository: aquasecurity/tfsec
Pull request #990 (2021-08-02 08:43:45)

test: Test rule block type, labels, ensure test state is clean for registrations


Patch:
diff --git a/internal/app/tfsec/scanner/registration.go b/internal/app/tfsec/scanner/registration.go
index 9bbe5ae1c..7f7b15a5c 100644
--- a/internal/app/tfsec/scanner/registration.go
+++ b/internal/app/tfsec/scanner/registration.go
@@ -34,6 +34,18 @@ func RegisterCheckRule(rule rule.Rule) {
 	registeredRules = append(registeredRules, rule)
 }
 
+func DeregisterCheckRule(r rule.Rule) {
+	rulesLock.Lock()
+	defer rulesLock.Unlock()
+	var filtered []rule.Rule
+	for _, existing := range registeredRules {
+		if existing.ID() != r.ID() {
+			filtered = append(filtered, existing)
+		}
+	}
+	registeredRules = filtered
+}
+
 // GetRegisteredRules provides all Checks which have been registered with this package
 func GetRegisteredRules() []rule.Rule {
 	sort.Slice(registeredRules, func(i, j int) bool {


Test patch:
diff --git a/internal/app/tfsec/test/example_code_test.go b/internal/app/tfsec/test/example_code_test.go
deleted file mode 100644
index d09648582..000000000
--- a/internal/app/tfsec/test/example_code_test.go
+++ /dev/null
@@ -1,44 +0,0 @@
-package test
-
-import (
-	"fmt"
-	"strings"
-	"testing"
-
-	"github.com/aquasecurity/tfsec/internal/app/tfsec/scanner"
-)
-
-func TestExampleCode(t *testing.T) {
-	for _, check := range scanner.GetRegisteredRules() {
-
-		t.Run(fmt.Sprintf("Rule explanation for %s", check.ID()), func(t *testing.T) {
-			if strings.TrimSpace(check.Documentation.Explanation) == "" {
-				t.Fatalf("No explanation found for %s", check.ID())
-			}
-		})
-
-		t.Run(fmt.Sprintf("Rule impact for %s", check.ID()), func(t *testing.T) {
-			if strings.TrimSpace(check.Documentation.Impact) == "" {
-				t.Fatalf("No impact found for %s", check.ID())
-			}
-		})
-
-		t.Run(fmt.Sprintf("Rule resolution for %s", check.ID()), func(t *testing.T) {
-			if strings.TrimSpace(check.Documentation.Resolution) == "" {
-				t.Fatalf("No resolution found for %s", check.ID())
-			}
-		})
-
-		t.Run(fmt.Sprintf("Rule bad example(s) for %s", check.ID()), func(t *testing.T) {
-			if len(check.Documentation.BadExample) == 0 {
-				t.Fatalf("No resolution found for %s", check.ID())
-			}
-		})
-
-		t.Run(fmt.Sprintf("Rule good example(s) for %s", check.ID()), func(t *testing.T) {
-			if len(check.Documentation.GoodExample) == 0 {
-				t.Fatalf("No resolution found for %s", check.ID())
-			}
-		})
-	}
-}
diff --git a/internal/app/tfsec/test/ignore_test.go b/internal/app/tfsec/test/ignore_test.go
index 3817d0ca6..68e4c5e11 100644
--- a/internal/app/tfsec/test/ignore_test.go
+++ b/internal/app/tfsec/test/ignore_test.go
@@ -85,7 +85,7 @@ resource "aws_security_group_rule" "my-rule" {
 
 func Test_IgnoreSpecific(t *testing.T) {
 
-	scanner.RegisterCheckRule(rule.Rule{
+	r1 := rule.Rule{
 		LegacyID:        "ABC123",
 		Provider:        provider.AWSProvider,
 		Service:         "service",
@@ -96,9 +96,11 @@ func Test_IgnoreSpecific(t *testing.T) {
 			set.AddResult().
 				WithDescription("example problem")
 		},
-	})
+	}
+	scanner.RegisterCheckRule(r1)
+	defer scanner.DeregisterCheckRule(r1)
 
-	scanner.RegisterCheckRule(rule.Rule{
+	r2 := rule.Rule{
 		LegacyID:        "DEF456",
 		Provider:        provider.AWSProvider,
 		Service:         "service",
@@ -109,7 +111,9 @@ func Test_IgnoreSpecific(t *testing.T) {
 			set.AddResult().
 				WithDescription("example problem")
 		},
-	})
+	}
+	scanner.RegisterCheckRule(r2)
+	defer scanner.DeregisterCheckRule(r2)
 
 	results := testutil.ScanHCL(`
 	//tfsec:ignore:ABC123
diff --git a/internal/app/tfsec/test/module_scan_test.go b/internal/app/tfsec/test/module_scan_test.go
index 0f6cd4ac4..4ee388ec5 100644
--- a/internal/app/tfsec/test/module_scan_test.go
+++ b/internal/app/tfsec/test/module_scan_test.go
@@ -17,7 +17,7 @@ import (
 
 func Test_ProblemInModule(t *testing.T) {
 
-	scanner.RegisterCheckRule(rule.Rule{
+	rule := rule.Rule{
 		LegacyID:  "EXA001",
 		Provider:  provider.AWSProvider,
 		Service:   "service",
@@ -48,7 +48,9 @@ resource "problem" "x" {
 					WithDescription("example problem")
 			}
 		},
-	})
+	}
+	scanner.RegisterCheckRule(rule)
+	defer scanner.DeregisterCheckRule(rule)
 
 	var tests = []struct {
 		name                  string
diff --git a/internal/app/tfsec/test/rules_test.go b/internal/app/tfsec/test/rules_test.go
new file mode 100644
index 000000000..f920fb9fc
--- /dev/null
+++ b/internal/app/tfsec/test/rules_test.go
@@ -0,0 +1,68 @@
+package test
+
+import (
+	"fmt"
+	"regexp"
+	"strings"
+	"testing"
+
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/scanner"
+)
+
+func TestExampleCode(t *testing.T) {
+	for _, rule := range scanner.GetRegisteredRules() {
+
+		t.Run(fmt.Sprintf("Rule explanation for %s", rule.ID()), func(t *testing.T) {
+			if strings.TrimSpace(rule.Documentation.Explanation) == "" {
+				t.Fatalf("No explanation found for %s", rule.ID())
+			}
+		})
+
+		t.Run(fmt.Sprintf("Rule impact for %s", rule.ID()), func(t *testing.T) {
+			if strings.TrimSpace(rule.Documentation.Impact) == "" {
+				t.Fatalf("No impact found for %s", rule.ID())
+			}
+		})
+
+		t.Run(fmt.Sprintf("Rule resolution for %s", rule.ID()), func(t *testing.T) {
+			if strings.TrimSpace(rule.Documentation.Resolution) == "" {
+				t.Fatalf("No resolution found for %s", rule.ID())
+			}
+		})
+
+		t.Run(fmt.Sprintf("Rule bad example(s) for %s", rule.ID()), func(t *testing.T) {
+			if len(rule.Documentation.BadExample) == 0 {
+				t.Fatalf("No bad example found for %s", rule.ID())
+			}
+		})
+
+		t.Run(fmt.Sprintf("Rule good example(s) for %s", rule.ID()), func(t *testing.T) {
+			if len(rule.Documentation.GoodExample) == 0 {
+				t.Fatalf("No good example found for %s", rule.ID())
+			}
+		})
+	}
+}
+
+func TestBlockTypes(t *testing.T) {
+	for _, rule := range scanner.GetRegisteredRules() {
+		for _, blockType := range rule.RequiredTypes {
+			switch blockType {
+			case "resource", "data", "provider", "variable", "module", "locals", "output":
+			default:
+				t.Errorf("Invalid required block type for rule %s: '%s'", rule.ID(), blockType)
+			}
+		}
+	}
+}
+
+func TestBlockLabels(t *testing.T) {
+	identifierRegex := regexp.MustCompile(`^[a-zA-Z\-_][a-zA-Z0-9\-_]*$`)
+	for _, rule := range scanner.GetRegisteredRules() {
+		for _, label := range rule.RequiredLabels {
+			if !identifierRegex.MatchString(label) {
+				t.Errorf("Invalid required label for rule %s: '%s'", rule.ID(), label)
+			}
+		}
+	}
+}
diff --git a/internal/app/tfsec/test/wildcard_test.go b/internal/app/tfsec/test/wildcard_test.go
index 22efb22c4..68800d0b8 100644
--- a/internal/app/tfsec/test/wildcard_test.go
+++ b/internal/app/tfsec/test/wildcard_test.go
@@ -56,7 +56,7 @@ func Test_WildcardMatchingOnRequiredLabels(t *testing.T) {
 
 		code := fmt.Sprintf("wild%d", i)
 
-		scanner.RegisterCheckRule(rule.Rule{
+		rule := rule.Rule{
 			Service:   "service",
 			ShortCode: code,
 			Documentation: rule.RuleDocumentation{
@@ -70,7 +70,9 @@ func Test_WildcardMatchingOnRequiredLabels(t *testing.T) {
 				set.AddResult().
 					WithDescription("Custom check failed for resource %s.", rootBlock.FullName())
 			},
-		})
+		}
+		scanner.RegisterCheckRule(rule)
+		defer scanner.DeregisterCheckRule(rule)
 
 		results := testutil.ScanHCL(test.input, t)