Repository: aquasecurity/tfsec
Pull request #1001 (2021-08-05 13:59:48)

Add more gke node pool rules


Patch:
diff --git a/internal/app/tfsec/rules/google/gke/enable_auto_repair_rule.go b/internal/app/tfsec/rules/google/gke/enable_auto_repair_rule.go
new file mode 100644
index 000000000..f7b8f6f07
--- /dev/null
+++ b/internal/app/tfsec/rules/google/gke/enable_auto_repair_rule.go
@@ -0,0 +1,123 @@
+package gke
+
+// ATTENTION!
+// This rule was autogenerated!
+// Before making changes, consider updating the generator.
+
+import (
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/block"
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/hclcontext"
+	"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{
+		Provider:  provider.GoogleProvider,
+		Service:   "gke",
+		ShortCode: "enable-auto-repair",
+		Documentation: rule.RuleDocumentation{
+			Summary:     "Kubernetes should have 'Automatic repair' enabled",
+			Explanation: `Automatic repair will monitor nodes and attempt repair when a node fails multiple subsequent health checks`,
+			Impact:      "Failing nodes will require manual repair.",
+			Resolution:  "Enable automatic repair",
+			BadExample: []string{`
+resource "google_service_account" "default" {
+  account_id   = "service-account-id"
+  display_name = "Service Account"
+}
+
+resource "google_container_cluster" "primary" {
+  name     = "my-gke-cluster"
+  location = "us-central1"
+
+  # We can't create a cluster with no node pool defined, but we want to only use
+  # separately managed node pools. So we create the smallest possible default
+  # node pool and immediately delete it.
+  remove_default_node_pool = true
+  initial_node_count       = 1
+}
+
+resource "google_container_node_pool" "bad_example" {
+  name       = "my-node-pool"
+  cluster    = google_container_cluster.primary.id
+  node_count = 1
+
+  node_config {
+    preemptible  = true
+    machine_type = "e2-medium"
+
+    # Google recommends custom service accounts that have cloud-platform scope and permissions granted via IAM Roles.
+    service_account = google_service_account.default.email
+    oauth_scopes = [
+      "https://www.googleapis.com/auth/cloud-platform"
+    ]
+  }
+  management {
+    auto_repair = false
+  }
+}
+`},
+			GoodExample: []string{`
+resource "google_service_account" "default" {
+  account_id   = "service-account-id"
+  display_name = "Service Account"
+}
+
+resource "google_container_cluster" "primary" {
+  name     = "my-gke-cluster"
+  location = "us-central1"
+
+  # We can't create a cluster with no node pool defined, but we want to only use
+  # separately managed node pools. So we create the smallest possible default
+  # node pool and immediately delete it.
+  remove_default_node_pool = true
+  initial_node_count       = 1
+}
+
+resource "google_container_node_pool" "good_example" {
+  name       = "my-node-pool"
+  cluster    = google_container_cluster.primary.id
+  node_count = 1
+
+  node_config {
+    preemptible  = true
+    machine_type = "e2-medium"
+
+    # Google recommends custom service accounts that have cloud-platform scope and permissions granted via IAM Roles.
+    service_account = google_service_account.default.email
+    oauth_scopes = [
+      "https://www.googleapis.com/auth/cloud-platform"
+    ]
+  }
+  management {
+    auto_repair = true
+  }
+}
+`},
+			Links: []string{
+				"https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_node_pool#auto_repair",
+			},
+		},
+		RequiredTypes: []string{
+			"resource",
+		},
+		RequiredLabels: []string{
+			"google_container_node_pool",
+		},
+		DefaultSeverity: severity.Low,
+		CheckFunc: func(set result.Set, resourceBlock block.Block, _ *hclcontext.Context) {
+			if autoRepairAttr := resourceBlock.GetBlock("management").GetAttribute("auto_repair"); autoRepairAttr.IsNil() { // alert on use of default value
+				set.AddResult().
+					WithDescription("Resource '%s' uses default value for management.auto_repair", resourceBlock.FullName())
+			} else if autoRepairAttr.IsFalse() {
+				set.AddResult().
+					WithDescription("Resource '%s' does not have management.auto_repair set to true", resourceBlock.FullName()).
+					WithAttribute(autoRepairAttr)
+			}
+		},
+	})
+}
diff --git a/internal/app/tfsec/rules/google/gke/enable_auto_upgrade_rule.go b/internal/app/tfsec/rules/google/gke/enable_auto_upgrade_rule.go
new file mode 100644
index 000000000..d5df1a15a
--- /dev/null
+++ b/internal/app/tfsec/rules/google/gke/enable_auto_upgrade_rule.go
@@ -0,0 +1,123 @@
+package gke
+
+// ATTENTION!
+// This rule was autogenerated!
+// Before making changes, consider updating the generator.
+
+import (
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/block"
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/hclcontext"
+	"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{
+		Provider:  provider.GoogleProvider,
+		Service:   "gke",
+		ShortCode: "enable-auto-upgrade",
+		Documentation: rule.RuleDocumentation{
+			Summary:     "Kubernetes should have 'Automatic upgrade' enabled",
+			Explanation: `Automatic updates keep nodes updated with the latest cluster master version.`,
+			Impact:      "Nodes will need the cluster master version manually updating",
+			Resolution:  "Enable automatic upgrades",
+			BadExample: []string{`
+resource "google_service_account" "default" {
+  account_id   = "service-account-id"
+  display_name = "Service Account"
+}
+
+resource "google_container_cluster" "primary" {
+  name     = "my-gke-cluster"
+  location = "us-central1"
+
+  # We can't create a cluster with no node pool defined, but we want to only use
+  # separately managed node pools. So we create the smallest possible default
+  # node pool and immediately delete it.
+  remove_default_node_pool = true
+  initial_node_count       = 1
+}
+
+resource "google_container_node_pool" "bad_example" {
+  name       = "my-node-pool"
+  cluster    = google_container_cluster.primary.id
+  node_count = 1
+
+  node_config {
+    preemptible  = true
+    machine_type = "e2-medium"
+
+    # Google recommends custom service accounts that have cloud-platform scope and permissions granted via IAM Roles.
+    service_account = google_service_account.default.email
+    oauth_scopes = [
+      "https://www.googleapis.com/auth/cloud-platform"
+    ]
+  }
+  management {
+    auto_upgrade = false
+  }
+}
+`},
+			GoodExample: []string{`
+resource "google_service_account" "default" {
+  account_id   = "service-account-id"
+  display_name = "Service Account"
+}
+
+resource "google_container_cluster" "primary" {
+  name     = "my-gke-cluster"
+  location = "us-central1"
+
+  # We can't create a cluster with no node pool defined, but we want to only use
+  # separately managed node pools. So we create the smallest possible default
+  # node pool and immediately delete it.
+  remove_default_node_pool = true
+  initial_node_count       = 1
+}
+
+resource "google_container_node_pool" "good_example" {
+  name       = "my-node-pool"
+  cluster    = google_container_cluster.primary.id
+  node_count = 1
+
+  node_config {
+    preemptible  = true
+    machine_type = "e2-medium"
+
+    # Google recommends custom service accounts that have cloud-platform scope and permissions granted via IAM Roles.
+    service_account = google_service_account.default.email
+    oauth_scopes = [
+      "https://www.googleapis.com/auth/cloud-platform"
+    ]
+  }
+  management {
+    auto_upgrade = true
+  }
+}
+`},
+			Links: []string{
+				"https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_node_pool#auto_upgrade",
+			},
+		},
+		RequiredTypes: []string{
+			"resource",
+		},
+		RequiredLabels: []string{
+			"google_container_node_pool",
+		},
+		DefaultSeverity: severity.Low,
+		CheckFunc: func(set result.Set, resourceBlock block.Block, _ *hclcontext.Context) {
+			if autoUpgradeAttr := resourceBlock.GetBlock("management").GetAttribute("auto_upgrade"); autoUpgradeAttr.IsNil() { // alert on use of default value
+				set.AddResult().
+					WithDescription("Resource '%s' uses default value for management.auto_upgrade", resourceBlock.FullName())
+			} else if autoUpgradeAttr.IsFalse() {
+				set.AddResult().
+					WithDescription("Resource '%s' does not have management.auto_upgrade set to true", resourceBlock.FullName()).
+					WithAttribute(autoUpgradeAttr)
+			}
+		},
+	})
+}
diff --git a/internal/app/tfsec/rules/google/gke/node_pool_uses_cos_rule.go b/internal/app/tfsec/rules/google/gke/node_pool_uses_cos_rule.go
new file mode 100644
index 000000000..dfdf70052
--- /dev/null
+++ b/internal/app/tfsec/rules/google/gke/node_pool_uses_cos_rule.go
@@ -0,0 +1,119 @@
+package gke
+
+// ATTENTION!
+// This rule was autogenerated!
+// Before making changes, consider updating the generator.
+
+import (
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/block"
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/hclcontext"
+	"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{
+		Provider:  provider.GoogleProvider,
+		Service:   "gke",
+		ShortCode: "node-pool-uses-cos",
+		Documentation: rule.RuleDocumentation{
+			Summary:     "Ensure Container-Optimized OS (cos) is used for Kubernetes Engine Clusters Node image",
+			Explanation: `GKE supports several OS image types but COS is the recommended OS image to use on cluster nodes for enhanced security`,
+			Impact:      "COS is the recommended OS image to use on cluster nodes",
+			Resolution:  "Use the COS image type",
+			BadExample: []string{`
+resource "google_service_account" "default" {
+  account_id   = "service-account-id"
+  display_name = "Service Account"
+}
+
+resource "google_container_cluster" "primary" {
+  name     = "my-gke-cluster"
+  location = "us-central1"
+
+  # We can't create a cluster with no node pool defined, but we want to only use
+  # separately managed node pools. So we create the smallest possible default
+  # node pool and immediately delete it.
+  remove_default_node_pool = true
+  initial_node_count       = 1
+}
+
+resource "google_container_node_pool" "bad_example" {
+  name       = "my-node-pool"
+  cluster    = google_container_cluster.primary.id
+  node_count = 1
+
+  node_config {
+    preemptible  = true
+    machine_type = "e2-medium"
+
+    # Google recommends custom service accounts that have cloud-platform scope and permissions granted via IAM Roles.
+    service_account = google_service_account.default.email
+    oauth_scopes = [
+      "https://www.googleapis.com/auth/cloud-platform"
+    ]
+    image_type = "something"
+  }
+}
+`},
+			GoodExample: []string{`
+resource "google_service_account" "default" {
+  account_id   = "service-account-id"
+  display_name = "Service Account"
+}
+
+resource "google_container_cluster" "primary" {
+  name     = "my-gke-cluster"
+  location = "us-central1"
+
+  # We can't create a cluster with no node pool defined, but we want to only use
+  # separately managed node pools. So we create the smallest possible default
+  # node pool and immediately delete it.
+  remove_default_node_pool = true
+  initial_node_count       = 1
+}
+
+resource "google_container_node_pool" "good_example" {
+  name       = "my-node-pool"
+  cluster    = google_container_cluster.primary.id
+  node_count = 1
+
+  node_config {
+    preemptible  = true
+    machine_type = "e2-medium"
+
+    # Google recommends custom service accounts that have cloud-platform scope and permissions granted via IAM Roles.
+    service_account = google_service_account.default.email
+    oauth_scopes = [
+      "https://www.googleapis.com/auth/cloud-platform"
+    ]
+    image_type = "COS"
+  }
+}
+`},
+			Links: []string{
+				"https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_node_pool#image_type",
+			},
+		},
+		RequiredTypes: []string{
+			"resource",
+		},
+		RequiredLabels: []string{
+			"google_container_node_pool",
+		},
+		DefaultSeverity: severity.Low,
+		CheckFunc: func(set result.Set, resourceBlock block.Block, _ *hclcontext.Context) {
+			if imageTypeAttr := resourceBlock.GetBlock("node_config").GetAttribute("image_type"); imageTypeAttr.IsNil() { // alert on use of default value
+				set.AddResult().
+					WithDescription("Resource '%s' uses default value for node_config.image_type", resourceBlock.FullName())
+			} else if imageTypeAttr.NotEqual("COS") {
+				set.AddResult().
+					WithDescription("Resource '%s' does not have node_config.image_type set to COS", resourceBlock.FullName()).
+					WithAttribute(imageTypeAttr)
+			}
+		},
+	})
+}


Test patch:
diff --git a/internal/app/tfsec/rules/google/gke/enable_auto_repair_rule_test.go b/internal/app/tfsec/rules/google/gke/enable_auto_repair_rule_test.go
new file mode 100644
index 000000000..fe14acf68
--- /dev/null
+++ b/internal/app/tfsec/rules/google/gke/enable_auto_repair_rule_test.go
@@ -0,0 +1,53 @@
+package gke
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/scanner"
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/testutil"
+)
+
+func Test_GoogleEnableAutoRepair_FailureExamples(t *testing.T) {
+	expectedCode := "google-gke-enable-auto-repair"
+
+	rule, err := scanner.GetRuleById(expectedCode)
+	if err != nil {
+		t.Fatalf("Rule not found: %s", expectedCode)
+	}
+	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_GoogleEnableAutoRepair_SuccessExamples(t *testing.T) {
+	expectedCode := "google-gke-enable-auto-repair"
+
+	rule, err := scanner.GetRuleById(expectedCode)
+	if err != nil {
+		t.Fatalf("Rule not found: %s", expectedCode)
+	}
+	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)
+	}
+}
diff --git a/internal/app/tfsec/rules/google/gke/enable_auto_upgrade_rule_test.go b/internal/app/tfsec/rules/google/gke/enable_auto_upgrade_rule_test.go
new file mode 100644
index 000000000..cf0ecbe26
--- /dev/null
+++ b/internal/app/tfsec/rules/google/gke/enable_auto_upgrade_rule_test.go
@@ -0,0 +1,53 @@
+package gke
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/scanner"
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/testutil"
+)
+
+func Test_GoogleEnableAutoUpgrade_FailureExamples(t *testing.T) {
+	expectedCode := "google-gke-enable-auto-upgrade"
+
+	rule, err := scanner.GetRuleById(expectedCode)
+	if err != nil {
+		t.Fatalf("Rule not found: %s", expectedCode)
+	}
+	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_GoogleEnableAutoUpgrade_SuccessExamples(t *testing.T) {
+	expectedCode := "google-gke-enable-auto-upgrade"
+
+	rule, err := scanner.GetRuleById(expectedCode)
+	if err != nil {
+		t.Fatalf("Rule not found: %s", expectedCode)
+	}
+	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)
+	}
+}
diff --git a/internal/app/tfsec/rules/google/gke/node_pool_uses_cos_rule_test.go b/internal/app/tfsec/rules/google/gke/node_pool_uses_cos_rule_test.go
new file mode 100644
index 000000000..04390a886
--- /dev/null
+++ b/internal/app/tfsec/rules/google/gke/node_pool_uses_cos_rule_test.go
@@ -0,0 +1,53 @@
+package gke
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/scanner"
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/testutil"
+)
+
+func Test_GoogleNodePoolUsesCos_FailureExamples(t *testing.T) {
+	expectedCode := "google-gke-node-pool-uses-cos"
+
+	rule, err := scanner.GetRuleById(expectedCode)
+	if err != nil {
+		t.Fatalf("Rule not found: %s", expectedCode)
+	}
+	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_GoogleNodePoolUsesCos_SuccessExamples(t *testing.T) {
+	expectedCode := "google-gke-node-pool-uses-cos"
+
+	rule, err := scanner.GetRuleById(expectedCode)
+	if err != nil {
+		t.Fatalf("Rule not found: %s", expectedCode)
+	}
+	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)
+	}
+}