Repository: aquasecurity/tfsec
Pull request #1241 (2022-01-04 13:15:55)

Add check for plaintext github action secrets


Patch:
diff --git a/internal/app/tfsec/adapter/github/adapt.go b/internal/app/tfsec/adapter/github/adapt.go
index 4507f5b4f..3aa87a1dd 100644
--- a/internal/app/tfsec/adapter/github/adapt.go
+++ b/internal/app/tfsec/adapter/github/adapt.go
@@ -3,11 +3,13 @@ package github
 import (
 	"github.com/aquasecurity/defsec/provider/github"
 	"github.com/aquasecurity/tfsec/internal/app/tfsec/adapter/github/repositories"
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/adapter/github/secrets"
 	"github.com/aquasecurity/tfsec/internal/app/tfsec/block"
 )
 
 func Adapt(modules []block.Module) github.GitHub {
 	return github.GitHub{
-		Repositories: repositories.Adapt(modules),
+		Repositories:       repositories.Adapt(modules),
+		EnvironmentSecrets: secrets.Adapt(modules),
 	}
 }
diff --git a/internal/app/tfsec/adapter/github/secrets/adapt.go b/internal/app/tfsec/adapter/github/secrets/adapt.go
new file mode 100644
index 000000000..6cef36237
--- /dev/null
+++ b/internal/app/tfsec/adapter/github/secrets/adapt.go
@@ -0,0 +1,30 @@
+package secrets
+
+import (
+	"github.com/aquasecurity/defsec/provider/github"
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/block"
+)
+
+func Adapt(modules []block.Module) []github.EnvironmentSecret {
+	return adaptSecrets(modules)
+}
+
+func adaptSecrets(modules []block.Module) []github.EnvironmentSecret {
+	var secrets []github.EnvironmentSecret
+	for _, module := range modules {
+		for _, resource := range module.GetResourcesByType("github_actions_environment_secret") {
+			secrets = append(secrets, adaptSecret(resource))
+		}
+	}
+	return secrets
+}
+
+func adaptSecret(resource block.Block) github.EnvironmentSecret {
+	var secret github.EnvironmentSecret
+	secret.SecretName = resource.GetAttribute("secret_name").AsStringValueOrDefault("", resource)
+	secret.PlainTextValue = resource.GetAttribute("plaintext_value").AsStringValueOrDefault("", resource)
+	secret.Environment = resource.GetAttribute("environment").AsStringValueOrDefault("", resource)
+	secret.Repository = resource.GetAttribute("repository").AsStringValueOrDefault("", resource)
+	secret.EncryptedValue = resource.GetAttribute("encrypted_value").AsStringValueOrDefault("", resource)
+	return secret
+}
diff --git a/internal/app/tfsec/rules/github/actions/no_plain_text_secrets_rule.go b/internal/app/tfsec/rules/github/actions/no_plain_text_secrets_rule.go
new file mode 100644
index 000000000..6f1ce139d
--- /dev/null
+++ b/internal/app/tfsec/rules/github/actions/no_plain_text_secrets_rule.go
@@ -0,0 +1,37 @@
+package actions
+
+// ATTENTION!
+// This rule was autogenerated!
+// Before making changes, consider updating the generator.
+
+import (
+	"github.com/aquasecurity/defsec/rules/github/actions"
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/scanner"
+	"github.com/aquasecurity/tfsec/pkg/rule"
+)
+
+func init() {
+	scanner.RegisterCheckRule(rule.Rule{
+		BadExample: []string{`
+resource "github_actions_environment_secret" "bad_example" {	 
+	repository       = "my repository name"
+	environment       = "my environment"
+	secret_name       = "my secret name"
+	plaintext_value   = "sensitive secret string"
+}
+`},
+		GoodExample: []string{`
+resource "github_actions_environment_secret" "good_example" {
+	repository       = "my repository name"
+	environment       = "my environment"
+	secret_name       = "my secret name"
+	encrypted_value   = var.some_encrypted_secret_string
+}
+`},
+		Links: []string{
+			"https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_environment_secret",
+			"https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions",
+		},
+		Base: actions.CheckNoPlainTextActionEnvironmentSecrets,
+	})
+}
diff --git a/internal/app/tfsec/rules/init.go b/internal/app/tfsec/rules/init.go
index 8bfdd5593..abea154fc 100644
--- a/internal/app/tfsec/rules/init.go
+++ b/internal/app/tfsec/rules/init.go
@@ -54,6 +54,7 @@ import (
 	_ "github.com/aquasecurity/tfsec/internal/app/tfsec/rules/digitalocean/compute"
 	_ "github.com/aquasecurity/tfsec/internal/app/tfsec/rules/digitalocean/spaces"
 	_ "github.com/aquasecurity/tfsec/internal/app/tfsec/rules/general/secrets"
+	_ "github.com/aquasecurity/tfsec/internal/app/tfsec/rules/github/actions"
 	_ "github.com/aquasecurity/tfsec/internal/app/tfsec/rules/github/repositories"
 	_ "github.com/aquasecurity/tfsec/internal/app/tfsec/rules/google/bigquery"
 	_ "github.com/aquasecurity/tfsec/internal/app/tfsec/rules/google/compute"
diff --git a/vendor/github.com/aquasecurity/defsec/rules/github/actions/no_plain_text_secrets.go b/vendor/github.com/aquasecurity/defsec/rules/github/actions/no_plain_text_secrets.go
new file mode 100644
index 000000000..2f5f7ecee
--- /dev/null
+++ b/vendor/github.com/aquasecurity/defsec/rules/github/actions/no_plain_text_secrets.go
@@ -0,0 +1,36 @@
+package actions
+
+import (
+	"github.com/aquasecurity/defsec/provider"
+	"github.com/aquasecurity/defsec/rules"
+	"github.com/aquasecurity/defsec/severity"
+	"github.com/aquasecurity/defsec/state"
+)
+
+var CheckNoPlainTextActionEnvironmentSecrets = rules.Register(
+	rules.Rule{
+		AVDID:      "AVD-GIT-0002",
+		Provider:   provider.GitHubProvider,
+		Service:    "actions",
+		ShortCode:  "no-plain-text-action-secrets",
+		Summary:    "Ensure plaintext value is not used for GitHub Action Environment Secret.",
+		Impact:     "Unencrypted sensitive plaintext value can be easily accessible in code.",
+		Resolution: "Do not store plaintext values in your code but rather populate the encrypted_value using fields from a resource, data source or variable.", Explanation: `For the purposes of security, the contents of the plaintext_value field have been marked as sensitive to Terraform, but this does not hide it from state files. State should be treated as sensitive always.`,
+
+		Links: []string{
+			"https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_environment_secret",
+			"https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions",
+		},
+		Severity: severity.High,
+	},
+	func(s *state.State) (results rules.Results) {
+		for _, environmentSecret := range s.GitHub.EnvironmentSecrets {
+			if environmentSecret.PlainTextValue.IsNotEmpty() {
+				results.Add("Secret has plain text value",
+					&environmentSecret,
+					environmentSecret.PlainTextValue)
+			}
+		}
+		return results
+	},
+)
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 672acecca..df6624dc3 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -155,6 +155,7 @@ github.com/aquasecurity/defsec/rules/cloudstack/compute
 github.com/aquasecurity/defsec/rules/digitalocean/compute
 github.com/aquasecurity/defsec/rules/digitalocean/spaces
 github.com/aquasecurity/defsec/rules/general/secrets
+github.com/aquasecurity/defsec/rules/github/actions
 github.com/aquasecurity/defsec/rules/github/repositories
 github.com/aquasecurity/defsec/rules/google/bigquery
 github.com/aquasecurity/defsec/rules/google/compute


Test patch:
diff --git a/internal/app/tfsec/rules/github/actions/no_plain_text_secrets_rule_test.go b/internal/app/tfsec/rules/github/actions/no_plain_text_secrets_rule_test.go
new file mode 100644
index 000000000..95d596161
--- /dev/null
+++ b/internal/app/tfsec/rules/github/actions/no_plain_text_secrets_rule_test.go
@@ -0,0 +1,53 @@
+package actions
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/scanner"
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/testutil"
+)
+
+func Test_GithubNo_plain_text_secrets_FailureExamples(t *testing.T) {
+	expectedCode := "github-actions-no-plain-text-action-secrets"
+
+	rule, err := scanner.GetRuleById(expectedCode)
+	if err != nil {
+		t.Fatalf("Rule not found: %s", expectedCode)
+	}
+	for i, badExample := range rule.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_GithubNo_plain_text_secrets_SuccessExamples(t *testing.T) {
+	expectedCode := "github-actions-no-plain-text-action-secrets"
+
+	rule, err := scanner.GetRuleById(expectedCode)
+	if err != nil {
+		t.Fatalf("Rule not found: %s", expectedCode)
+	}
+	for i, example := range rule.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)
+	}
+}