From 50b2a59dee2721f8c85a28f973e82a8338baad76 Mon Sep 17 00:00:00 2001 From: Owen Cummings Date: Wed, 9 Sep 2020 17:07:15 -0400 Subject: [PATCH 1/2] Add GCP011 iam user grant check --- README.md | 1 + .../app/tfsec/checks/google_user_iam_grant.go | 73 ++++++++++++++ .../app/tfsec/google_user_iam_grant_test.go | 95 +++++++++++++++++++ 3 files changed, 169 insertions(+) create mode 100644 internal/app/tfsec/checks/google_user_iam_grant.go create mode 100644 internal/app/tfsec/google_user_iam_grant_test.go diff --git a/README.md b/README.md index ce76f03299..a810f34cdf 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,7 @@ there are also checks which are provider agnostic. | GCP008 | google | Legacy client authentication methods utilized. | GCP009 | google | Pod security policy enforcement not defined. | GCP010 | google | Shielded GKE nodes not enabled. +| GCP010 | google | IAM granted directly to user. ## Running in CI diff --git a/internal/app/tfsec/checks/google_user_iam_grant.go b/internal/app/tfsec/checks/google_user_iam_grant.go new file mode 100644 index 0000000000..bbdee9c1d3 --- /dev/null +++ b/internal/app/tfsec/checks/google_user_iam_grant.go @@ -0,0 +1,73 @@ +package checks + +import ( + "fmt" + "strings" + "github.com/liamg/tfsec/internal/app/tfsec/parser" + "github.com/liamg/tfsec/internal/app/tfsec/scanner" + "github.com/zclconf/go-cty/cty" +) + +// GoogleUserIAMGrant See https://github.com/liamg/tfsec#included-checks for check info +const GoogleUserIAMGrant scanner.RuleID = "GCP011" + +func init() { + scanner.RegisterCheck(scanner.Check{ + Code: GoogleUserIAMGrant, + RequiredTypes: []string{"resource", "data"}, + RequiredLabels: []string{ + "google_cloud_run_service_iam_binding", + "google_cloud_run_service_iam_member", + "google_compute_instance_iam_binding", + "google_compute_instance_iam_member", + "google_compute_subnetwork_iam_binding", + "google_compute_subnetwork_iam_member", + "google_data_catalog_entry_group_iam_binding", + "google_data_catalog_entry_group_iam_member", + "google_folder_iam_member", + "google_folder_iam_binding", + "google_project_iam_member", + "google_project_iam_binding", + "google_pubsub_subscription_iam_binding", + "google_pubsub_subscription_iam_member", + "google_pubsub_topic_iam_binding", + "google_pubsub_topic_iam_member", + "google_sourcerepo_repository_iam_binding", + "google_sourcerepo_repository_iam_member", + "google_spanner_database_iam_binding", + "google_spanner_database_iam_member", + "google_spanner_instance_iam_binding", + "google_spanner_instance_iam_member", + "google_storage_bucket_iam_binding", + "google_storage_bucket_iam_member", + "google_iam_policy", + }, + CheckFunc: func(check *scanner.Check, block *parser.Block, _ *scanner.Context) []scanner.Result { + + var members []cty.Value + var attributes *parser.Attribute + + if attributes = block.GetAttribute("member"); attributes != nil { + members = append(members, attributes.Value()) + } else if attributes = block.GetAttribute("members"); attributes != nil { + members = attributes.Value().AsValueSlice() + } else if attributes = block.GetBlock("binding").GetAttribute("members"); attributes != nil { + members = attributes.Value().AsValueSlice() + } + + for _, identities := range members { + if identities.Type() == cty.String && strings.HasPrefix(identities.AsString(), "user:") { + return []scanner.Result{ + check.NewResult( + fmt.Sprintf("'%s' grants IAM to a user object. It is recommended to manage user permissions with groups.", block.Name()), + attributes.Range(), + scanner.SeverityWarning, + ), + } + } + } + + return nil + }, + }) +} diff --git a/internal/app/tfsec/google_user_iam_grant_test.go b/internal/app/tfsec/google_user_iam_grant_test.go new file mode 100644 index 0000000000..b6d4b223d4 --- /dev/null +++ b/internal/app/tfsec/google_user_iam_grant_test.go @@ -0,0 +1,95 @@ +package tfsec + +import ( + "testing" + + "github.com/liamg/tfsec/internal/app/tfsec/scanner" + + "github.com/liamg/tfsec/internal/app/tfsec/checks" +) + +func Test_GoogleUserIAMGrant(t *testing.T) { + + var tests = []struct { + name string + source string + mustIncludeResultCode scanner.RuleID + mustExcludeResultCode scanner.RuleID + }{ + { + name: "check google_project_iam_binding with grant to user identity", + source: ` +resource "google_project_iam_binding" "project-binding" { + members = [ + "user:test@example.com", + ] +}`, + mustIncludeResultCode: checks.GoogleUserIAMGrant, + }, + { + name: "check google_project_iam_binding with grant to user identity", + source: ` +resource "google_project_iam_binding" "project-binding" { + members = [ + "group:test@example.com", + ] +}`, + mustExcludeResultCode: checks.GoogleUserIAMGrant, + }, + { + name: "check google_project_iam_member with grant to user identity", + source: ` +resource "google_project_iam_member" "project-member" { + member = "user:test@example.com" +}`, + mustIncludeResultCode: checks.GoogleUserIAMGrant, + }, + { + name: "check google_storage_bucket_iam_binding with grant to user identity", + source: ` +resource "google_storage_bucket_iam_binding" "bucket-binding" { + members = [ + "user:test@example.com", + ] +}`, + mustIncludeResultCode: checks.GoogleUserIAMGrant, + }, + { + name: "check google_storage_bucket_iam_member with grant to user identity", + source: ` +resource "google_storage_bucket_iam_member" "bucket-member" { + member = "user:test@example.com" +}`, + mustIncludeResultCode: checks.GoogleUserIAMGrant, + }, + { + name: "check google_storage_bucket_iam_member with grant to service account identity", + source: ` +resource "google_storage_bucket_iam_member" "bucket-member" { + member = "serviceAccount:test@example.com" +}`, + mustExcludeResultCode: checks.GoogleUserIAMGrant, + }, + { + name: "check data.google_iam_policy with grant to user", + source: ` +data "google_iam_policy" "test-policy" { + binding { + members = [ + "group:test@example.com", + "user:test@example.com", + ] + } +}`, + mustIncludeResultCode: checks.GoogleUserIAMGrant, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + results := scanSource(test.source) + assertCheckCode(t, test.mustIncludeResultCode, test.mustExcludeResultCode, results) + }) + } + +} From b281805e1994b3f1229897aab441387b8cd62a36 Mon Sep 17 00:00:00 2001 From: Owen Cummings Date: Wed, 9 Sep 2020 17:08:33 -0400 Subject: [PATCH 2/2] Update check ID in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a810f34cdf..91fceb37e2 100644 --- a/README.md +++ b/README.md @@ -172,7 +172,7 @@ there are also checks which are provider agnostic. | GCP008 | google | Legacy client authentication methods utilized. | GCP009 | google | Pod security policy enforcement not defined. | GCP010 | google | Shielded GKE nodes not enabled. -| GCP010 | google | IAM granted directly to user. +| GCP011 | google | IAM granted directly to user. ## Running in CI