Repository: tfsec/tfsec
Pull request #579 (2021-02-07 19:47:07)

Add check to ensure source_arn present


Patch:
diff --git a/internal/app/tfsec/checks/aws058.go b/internal/app/tfsec/checks/aws058.go
new file mode 100644
index 000000000..81cab24a3
--- /dev/null
+++ b/internal/app/tfsec/checks/aws058.go
@@ -0,0 +1,69 @@
+package checks
+
+import (
+	"fmt"
+	"github.com/tfsec/tfsec/internal/app/tfsec/parser"
+	"github.com/tfsec/tfsec/internal/app/tfsec/scanner"
+)
+
+const AWSCheckLambdaFunctionForSourceARN scanner.RuleCode = "AWS058"
+const AWSCheckLambdaFunctionForSourceARNDescription scanner.RuleSummary = "Ensure that lambda function permission has a source arn specified"
+const AWSCheckLambdaFunctionForSourceARNExplanation = `When the principal is an AWS service, the ARN of the specific resource within that service to grant permission to. 
+
+Without this, any resource from principal will be granted permission – even if that resource is from another account. 
+
+For S3, this should be the ARN of the S3 Bucket. For CloudWatch Events, this should be the ARN of the CloudWatch Events Rule. For API Gateway, this should be the ARN of the API`
+const AWSCheckLambdaFunctionForSourceARNBadExample = `
+resource "aws_lambda_permission" "bad_example" {
+  statement_id  = "AllowExecutionFromSNS"
+  action        = "lambda:InvokeFunction"
+  function_name = aws_lambda_function.func.function_name
+  principal     = "sns.amazonaws.com"
+}
+`
+const AWSCheckLambdaFunctionForSourceARNGoodExample = `
+resource "aws_lambda_permission" "good_example" {
+  statement_id  = "AllowExecutionFromSNS"
+  action        = "lambda:InvokeFunction"
+  function_name = aws_lambda_function.func.function_name
+  principal     = "sns.amazonaws.com"
+  source_arn    = aws_sns_topic.default.arn
+}
+`
+
+func init() {
+	scanner.RegisterCheck(scanner.Check{
+		Code: AWSCheckLambdaFunctionForSourceARN,
+		Documentation: scanner.CheckDocumentation{
+			Summary:     AWSCheckLambdaFunctionForSourceARNDescription,
+			Explanation: AWSCheckLambdaFunctionForSourceARNExplanation,
+			BadExample:  AWSCheckLambdaFunctionForSourceARNBadExample,
+			GoodExample: AWSCheckLambdaFunctionForSourceARNGoodExample,
+			Links: []string{
+				"https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission",
+				"https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-permission.html",
+			},
+		},
+		Provider:       scanner.AWSProvider,
+		RequiredTypes:  []string{"resource"},
+		RequiredLabels: []string{"aws_lambda_permission"},
+		CheckFunc: func(check *scanner.Check, block *parser.Block, _ *scanner.Context) []scanner.Result {
+
+			if block.HasChild("principal") {
+				if block.GetAttribute("principal").EndsWith("amazonaws.com") {
+					if block.MissingChild("source_arn") {
+						return []scanner.Result{
+							check.NewResult(
+								fmt.Sprintf("Resource '%s' .", block.FullName()),
+								block.Range(),
+								scanner.SeverityError,
+							),
+						}
+					}
+				}
+			}
+
+			return nil
+		},
+	})
+}


Test patch:
diff --git a/internal/app/tfsec/test/aws058_test.go b/internal/app/tfsec/test/aws058_test.go
new file mode 100644
index 000000000..2db3e2c3b
--- /dev/null
+++ b/internal/app/tfsec/test/aws058_test.go
@@ -0,0 +1,63 @@
+package test
+
+import (
+	"testing"
+
+	"github.com/tfsec/tfsec/internal/app/tfsec/checks"
+	"github.com/tfsec/tfsec/internal/app/tfsec/scanner"
+)
+
+func Test_AWSCheckLambdaFunctionForSourceARN(t *testing.T) {
+
+	var tests = []struct {
+		name                  string
+		source                string
+		mustIncludeResultCode scanner.RuleCode
+		mustExcludeResultCode scanner.RuleCode
+	}{
+		{
+			name: "Principal present with no source_arn",
+			source: `
+resource "aws_lambda_permission" "with_sns" {
+  statement_id  = "AllowExecutionFromSNS"
+  action        = "lambda:InvokeFunction"
+  function_name = aws_lambda_function.func.function_name
+  principal     = "sns.amazonaws.com"
+}
+`,
+			mustIncludeResultCode: checks.AWSCheckLambdaFunctionForSourceARN,
+		},
+		{
+			name: "Principal present and source_arn present",
+			source: `
+resource "aws_lambda_permission" "with_sns" {
+  statement_id  = "AllowExecutionFromSNS"
+  action        = "lambda:InvokeFunction"
+  function_name = aws_lambda_function.func.function_name
+  principal     = "sns.amazonaws.com"
+  source_arn    = aws_sns_topic.default.arn
+}
+`,
+			mustExcludeResultCode: checks.AWSCheckLambdaFunctionForSourceARN,
+		},
+		{
+			name: "No principal specified",
+			source: `
+resource "aws_lambda_permission" "with_sns" {
+  statement_id  = "AllowExecutionFromSNS"
+  action        = "lambda:InvokeFunction"
+  function_name = aws_lambda_function.func.function_name
+}
+`,
+			mustExcludeResultCode: checks.AWSCheckLambdaFunctionForSourceARN,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			results := scanSource(test.source)
+			assertCheckCode(t, test.mustIncludeResultCode, test.mustExcludeResultCode, results)
+		})
+	}
+
+}