Repository: tfsec/tfsec
Pull request #803 (2021-06-28 20:17:30)

traverse the load balancer to check type
- add attribute functions to walk the scope and find the related
  resource to a reference so further checks can be performed
- rename `ReferencesDataBlock` to `IsDataBlockReference` to make the intent clearer


Patch:
diff --git a/internal/app/tfsec/block/attribute.go b/internal/app/tfsec/block/attribute.go
index 202dc63be..2804fbe04 100644
--- a/internal/app/tfsec/block/attribute.go
+++ b/internal/app/tfsec/block/attribute.go
@@ -359,7 +359,7 @@ func (attr *Attribute) GreaterThanOrEqualTo(checkValue interface{}) bool {
 	return false
 }
 
-func (attr *Attribute) ReferencesDataBlock() bool {
+func (attr *Attribute) IsDataBlockReference() bool {
 	switch t := attr.hclAttribute.Expr.(type) {
 	case *hclsyntax.ScopeTraversalExpr:
 		split := t.Traversal.SimpleSplit()
@@ -386,6 +386,39 @@ func (attr *Attribute) ReferenceAsString() string {
 	return ""
 }
 
+func (attr *Attribute) IsResourceBlockReference(resourceType string) bool {
+	switch t := attr.hclAttribute.Expr.(type) {
+	case *hclsyntax.ScopeTraversalExpr:
+		split := t.Traversal.SimpleSplit()
+		return split.Abs.RootName() == resourceType
+	}
+	return false
+}
+
+func (attr *Attribute) GetReferencedResourceBlockType() (string, error) {
+	switch t := attr.hclAttribute.Expr.(type) {
+	case *hclsyntax.ScopeTraversalExpr:
+		split := t.Traversal.SimpleSplit()
+		return split.Abs.RootName(), nil
+	}
+	return "", fmt.Errorf("not referenced block found on the attribute")
+}
+
+func (attr *Attribute) GetReferencedResourceBlocksName() (string, error) {
+	switch t := attr.hclAttribute.Expr.(type) {
+	case *hclsyntax.ScopeTraversalExpr:
+		parts := t.Traversal.SimpleSplit()
+		for _, p := range parts.Rel {
+			switch part := p.(type) {
+			case hcl.TraverseAttr:
+				// the first name on the Rel is the label for the attribute, so return that
+				return part.Name, nil
+			}
+		}
+	}
+	return "", fmt.Errorf("no referenced block found on the atteibute")
+}
+
 func getRawValue(value cty.Value) interface{} {
 	typeName := value.Type().FriendlyName()
 
diff --git a/internal/app/tfsec/hclcontext/context.go b/internal/app/tfsec/hclcontext/context.go
index 3613812f1..514412b1d 100644
--- a/internal/app/tfsec/hclcontext/context.go
+++ b/internal/app/tfsec/hclcontext/context.go
@@ -53,3 +53,22 @@ func (c *Context) GetProviderBlocksByProvider(providerName string, alias string)
 	}
 	return results
 }
+
+func (c *Context) GetReferencedBlock(referringAttr *block.Attribute) (*block.Block, error) {
+	resType, err := referringAttr.GetReferencedResourceBlockType()
+	if err != nil {
+		return nil, err
+	}
+	resName, err := referringAttr.GetReferencedResourceBlocksName()
+	if err != nil {
+		return nil, err
+	}
+
+	for _, resource := range c.GetResourcesByType(resType) {
+		if resource.NameLabel() == resName {
+			return resource, nil
+		}
+	}
+
+	return nil, fmt.Errorf("did not find a suitable block to reference")
+}
diff --git a/internal/app/tfsec/rules/aws004.go b/internal/app/tfsec/rules/aws004.go
index 720c53f06..f0111f8d3 100644
--- a/internal/app/tfsec/rules/aws004.go
+++ b/internal/app/tfsec/rules/aws004.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/tfsec/tfsec/pkg/provider"
 
+	"github.com/tfsec/tfsec/internal/app/tfsec/debug"
 	"github.com/tfsec/tfsec/internal/app/tfsec/hclcontext"
 
 	"github.com/tfsec/tfsec/internal/app/tfsec/block"
@@ -57,7 +58,21 @@ func init() {
 		Provider:       provider.AWSProvider,
 		RequiredTypes:  []string{"resource"},
 		RequiredLabels: []string{"aws_lb_listener", "aws_alb_listener"},
-		CheckFunc: func(set result.Set, resourceBlock *block.Block, _ *hclcontext.Context) {
+		CheckFunc: func(set result.Set, resourceBlock *block.Block, ctx *hclcontext.Context) {
+			if resourceBlock.HasChild("load_balancer_arn") {
+				lbaAttr := resourceBlock.GetAttribute("load_balancer_arn")
+				if lbaAttr.IsResourceBlockReference("aws_lb") {
+					referencedBlock, err := ctx.GetReferencedBlock(lbaAttr)
+					if err == nil {
+						if referencedBlock.HasChild("load_balancer_type") && referencedBlock.GetAttribute("load_balancer_type").Equals("gateway") {
+							return
+						}
+					} else {
+						// didn't find the referenced block, log and move on
+						debug.Log(err.Error())
+					}
+				}
+			}
 
 			if protocolAttr := resourceBlock.GetAttribute("protocol"); protocolAttr == nil || (protocolAttr.Type() == cty.String && protocolAttr.Value().AsString() == "HTTP") {
 
diff --git a/internal/app/tfsec/rules/aws016.go b/internal/app/tfsec/rules/aws016.go
index 45b67fa24..b025c760e 100644
--- a/internal/app/tfsec/rules/aws016.go
+++ b/internal/app/tfsec/rules/aws016.go
@@ -78,7 +78,7 @@ func init() {
 				return
 			}
 
-			if kmsKeyIDAttr.ReferencesDataBlock() {
+			if kmsKeyIDAttr.IsDataBlockReference() {
 				ref := kmsKeyIDAttr.ReferenceAsString()
 				dataReferenceParts := strings.Split(ref, ".")
 				if len(dataReferenceParts) < 3 {
diff --git a/internal/app/tfsec/rules/aws095.go b/internal/app/tfsec/rules/aws095.go
index aa3c6921b..efaac4cb8 100644
--- a/internal/app/tfsec/rules/aws095.go
+++ b/internal/app/tfsec/rules/aws095.go
@@ -72,7 +72,7 @@ func init() {
 			}
 
 			kmsKeyAttr := resourceBlock.GetAttribute("kms_key_id")
-			if kmsKeyAttr.ReferencesDataBlock() {
+			if kmsKeyAttr.IsDataBlockReference() {
 				ref := kmsKeyAttr.ReferenceAsString()
 				dataReferenceParts := strings.Split(ref, ".")
 				if len(dataReferenceParts) < 3 {


Test patch:
diff --git a/internal/app/tfsec/test/aws004_test.go b/internal/app/tfsec/test/aws004_test.go
index 4c9e674eb..ef1f7181b 100644
--- a/internal/app/tfsec/test/aws004_test.go
+++ b/internal/app/tfsec/test/aws004_test.go
@@ -37,6 +37,26 @@ resource "aws_alb_listener" "my-listener" {
 }`,
 			mustIncludeResultCode: rules.AWSPlainHTTP,
 		},
+		{
+			name: "check aws_alb_listeneer should continue checks if the referenced if a load balancer is not gateway",
+			source: `
+resource "aws_lb" "gwlb" {
+
+	load_balancer_type = "application"
+
+}
+
+resource "aws_lb_listener" "gwlb_listener" {
+	load_balancer_arn = aws_lb.gwlb.id
+	  
+	default_action {
+		target_group_arn = aws_lb_target_group.gwlb_target_group.id
+		  type             = "forward"
+		}
+}
+	`,
+			mustIncludeResultCode: rules.AWSPlainHTTP,
+		},
 		{
 			name: "check aws_alb_listener using HTTPS",
 			source: `
@@ -62,6 +82,26 @@ resource "aws_alb_listener" "my-listener" {
 }`,
 			mustExcludeResultCode: rules.AWSPlainHTTP,
 		},
+		{
+			name: "check aws_alb_listeneer should pass if a type is gateway",
+			source: `
+resource "aws_lb" "gwlb" {
+
+	load_balancer_type = "gateway"
+
+}
+
+resource "aws_lb_listener" "gwlb_listener" {
+	load_balancer_arn = aws_lb.gwlb.id
+	  
+	default_action {
+		target_group_arn = aws_lb_target_group.gwlb_target_group.id
+		  type             = "forward"
+		}
+}
+	`,
+			mustExcludeResultCode: rules.AWSPlainHTTP,
+		},
 	}
 
 	for _, test := range tests {