Repository: aquasecurity/tfsec (go)

Issue:
AWS-098 dosen't recognize public_access_block when count is used
**Describe the bug**
When using a count to conditionally create a s3 bucket, AWS-098 is triggered even when a public_access_block is associated with the bucket

**To Reproduce**
```
resource "aws_s3_bucket" "access-logs-bucket" {
  count = var.enable_cloudtrail ? 1 : 0
  bucket = "cloudtrail-access-logs"
  acl    = "private"
  force_destroy = true

  versioning {
    enabled = true
  }

  server_side_encryption_configuration {
    rule {
      apply_server_side_encryption_by_default {
        sse_algorithm = "AES256"
      }
    }
  }
}

resource "aws_s3_bucket_public_access_block" "access-logs" {
  count = var.enable_cloudtrail ? 1 : 0
  bucket = aws_s3_bucket.access-logs-bucket[0].id
  block_public_acls   = true
  block_public_policy = true
  ignore_public_acls  = true
  restrict_public_buckets = true
}
```

**Expected behavior**
No warnings, since the bucket has a public_access_block associated with it.


Pull request description:
handle traverseIndex when checking reference
add supporting test

Resolves #962


Patch:
diff --git a/example/counts/main.tf b/example/counts/main.tf
index 9a861278c..31c131daa 100644
--- a/example/counts/main.tf
+++ b/example/counts/main.tf
@@ -31,4 +31,34 @@ variable "trust-sg-rules" {
 			type = "egress"
 		}
 	]
+}
+
+resource "aws_s3_bucket" "access-logs-bucket" {
+  count = var.enable_cloudtrail ? 1 : 0
+  bucket = "cloudtrail-access-logs"
+  acl    = "private"
+  force_destroy = true
+
+  versioning {
+    enabled = true
+  }
+
+  server_side_encryption_configuration {
+    rule {
+      apply_server_side_encryption_by_default {
+        sse_algorithm = "AES256"
+      }
+    }
+  }
+}
+
+resource "aws_s3_bucket_public_access_block" "access-logs" {
+  count = var.enable_cloudtrail ? 1 : 0
+
+  bucket = aws_s3_bucket.access-logs-bucket[0].id
+  
+  block_public_acls   = true
+  block_public_policy = true
+  ignore_public_acls  = true
+  restrict_public_buckets = true
 }
\ No newline at end of file
diff --git a/internal/app/tfsec/block/hclattribute.go b/internal/app/tfsec/block/hclattribute.go
index a20342430..17f1e0511 100644
--- a/internal/app/tfsec/block/hclattribute.go
+++ b/internal/app/tfsec/block/hclattribute.go
@@ -558,12 +558,30 @@ func createDotReferenceFromTraversal(traversals ...hcl.Traversal) (*Reference, e
 				refParts = append(refParts, part.Name)
 			case hcl.TraverseAttr:
 				refParts = append(refParts, part.Name)
+			case hcl.TraverseIndex:
+				refParts[len(refParts)-1] = fmt.Sprintf("%s[%s]", refParts[len(refParts)-1], getIndexValue(part))
 			}
 		}
 	}
 	return newReference(refParts)
 }
 
+func getIndexValue(part hcl.TraverseIndex) string {
+	switch part.Key.Type() {
+	case cty.String:
+		return fmt.Sprintf("%q", part.Key.AsString())
+	case cty.Number:
+		var intVal int
+		if err := gocty.FromCtyValue(part.Key, &intVal); err != nil {
+			debug.Log("could not unpack the int, returning 0")
+			return "0"
+		}
+		return fmt.Sprintf("%d", intVal)
+	default:
+		return "0"
+	}
+}
+
 func (attr *HCLAttribute) Reference() (*Reference, error) {
 	if attr == nil {
 		return nil, fmt.Errorf("attribute is nil")


Test patch:
diff --git a/internal/app/tfsec/test/count_test.go b/internal/app/tfsec/test/count_test.go
index 61d5d9af0..eb4b52280 100644
--- a/internal/app/tfsec/test/count_test.go
+++ b/internal/app/tfsec/test/count_test.go
@@ -95,16 +95,51 @@ func Test_ResourcesWithCount(t *testing.T) {
 			mustExcludeResultCode: "aws-vpc-no-default-vpc",
 		},
 		{
-			name: "count is 1 from conditional",
+			name: "count is 0 from conditional",
 			source: `
 			variable "enabled" {
-				default = true
+				default = false
 			}
 			resource "aws_default_vpc" "this" {
-				count =  var.enabled ? 1 : 0
+				count = var.enabled ? 1 : 0
 			}
 `,
-			mustIncludeResultCode: "aws-vpc-no-default-vpc",
+			mustExcludeResultCode: "aws-vpc-no-default-vpc",
+		},
+		{
+			name: "issue 962",
+			source: `
+			resource "aws_s3_bucket" "access-logs-bucket" {
+			count = var.enable_cloudtrail ? 1 : 0
+			bucket = "cloudtrail-access-logs"
+			acl    = "private"
+			force_destroy = true
+
+			versioning {
+				enabled = true
+			}
+
+			server_side_encryption_configuration {
+				rule {
+				apply_server_side_encryption_by_default {
+					sse_algorithm = "AES256"
+				}
+				}
+			}
+			}
+
+			resource "aws_s3_bucket_public_access_block" "access-logs" {
+			count = var.enable_cloudtrail ? 1 : 0
+
+			bucket = aws_s3_bucket.access-logs-bucket[0].id
+			
+			block_public_acls   = true
+			block_public_policy = true
+			ignore_public_acls  = true
+			restrict_public_buckets = true
+			}	
+`,
+			mustExcludeResultCode: "aws-s3-specify-public-access-block",
 		},
 		{
 			name: "Test use of count.index",
@@ -138,7 +173,7 @@ variable "trust-sg-rules" {
 	]
 }
 			`,
-			mustExcludeResultCode: "AWS018",
+			mustExcludeResultCode: "aws-vpc-add-decription-to-security-group",
 		},
 	}
 
diff --git a/internal/app/tfsec/testutil/util.go b/internal/app/tfsec/testutil/util.go
index 53f576dba..1c89309f0 100644
--- a/internal/app/tfsec/testutil/util.go
+++ b/internal/app/tfsec/testutil/util.go
@@ -59,6 +59,11 @@ func AssertCheckCode(t *testing.T, includeCode string, excludeCode string, resul
 
 	var excludeText string
 
+	if !validateCodes(includeCode, excludeCode) {
+		t.Logf("Either includeCode (%s) or excludeCode (%s) was invalid ", includeCode, excludeCode)
+		t.FailNow()
+	}
+
 	for _, res := range results {
 		if res.RuleID == excludeCode {
 			foundExclude = true
@@ -106,3 +111,18 @@ func CreateTestFileWithModule(contents string, moduleContents string) string {
 
 	return rootPath
 }
+
+func validateCodes(includeCode, excludeCode string) bool {
+	if includeCode != "" {
+		if _, err := scanner.GetRuleById(includeCode); err != nil {
+			return false
+		}
+	}
+
+	if excludeCode != "" {
+		if _, err := scanner.GetRuleById(excludeCode); err != nil {
+			return false
+		}
+	}
+	return true
+}