Repository: aquasecurity/tfsec (go)

Issue:
Check for descriptions in ingress and egress blocks within aws_security_group resources
**Is your feature request related to a problem? Please describe.**
We would like to ensure `ingress` and (optionally) `egress` blocks within `aws_security_group` resources have a `description` field. Right now, AWS018 seems to only inspect `aws_security_group_rule` resources, or descriptions at the top level of `aws_security_group`.

**Describe alternatives you've considered**
We're using a custom check to accomplish this, but it would be nice to have the functionality in a core rule.

**Additional context**

In this example, we'd like to detect the missing description from the second `ingress` block:

```
resource "aws_security_group" "example" {
  name        = "example"
  description = "Some SG" # we don't care about this
  vpc_id      = aws_vpc.main.id

  ingress {
    description     = "access from xyz" # this description is the important one
    from_port       = 8080
    to_port         = 8080
    protocol        = "tcp"
    security_groups = [aws_security_group.foo.id]
  }

  ingress {
    from_port       = 443
    to_port         = 443
    protocol        = "tcp"
    security_groups = [aws_security_group.foo.id]
  }
}
```


Pull request:
Add check for description in blocks
Resolves #1087

- ensure there is a description in ingress and egress blocks
- add tests to support
- add example

Doesn't support block as attribute format, they seem to be broken at a provider level at the moment

```hcl
ingress = [
   {
           // ingress bits
   }
]
```



Patch:
diff --git a/example/1087/main.tf b/example/1087/main.tf
new file mode 100644
index 000000000..2bf5c31b1
--- /dev/null
+++ b/example/1087/main.tf
@@ -0,0 +1,38 @@
+resource "aws_vpc" "main" {
+  cidr_block       = "10.0.0.0/16"
+  instance_tenancy = "default"
+
+  tags = {
+    Name = "main"
+  }
+}
+
+
+resource "aws_security_group" "foo" {
+  name        = "example"
+  description = "Some SG" # we don't care about this
+  vpc_id      = aws_vpc.main.id
+}
+
+
+
+resource "aws_security_group" "example" {
+  name        = "example"
+  description = "Some SG" # we don't care about this
+  vpc_id      = aws_vpc.main.id
+
+  ingress {
+    description     = "access from xyz" # this description is the important one
+    from_port       = 8080
+    to_port         = 8080
+    protocol        = "tcp"
+    security_groups = [aws_security_group.foo.id]
+  }
+
+  ingress {
+    from_port       = 443
+    to_port         = 443
+    protocol        = "tcp"
+    security_groups = [aws_security_group.foo.id]
+  }
+}
diff --git a/internal/app/tfsec/rules/aws/vpc/add_description_to_security_group_rule.go b/internal/app/tfsec/rules/aws/vpc/add_description_to_security_group_rule.go
index e25714133..1006a0f3e 100644
--- a/internal/app/tfsec/rules/aws/vpc/add_description_to_security_group_rule.go
+++ b/internal/app/tfsec/rules/aws/vpc/add_description_to_security_group_rule.go
@@ -33,12 +33,11 @@ resource "aws_security_group" "bad_example" {
   name        = "http"
 
   ingress {
-    description = "HTTP from VPC"
-    from_port   = 80
-    to_port     = 80
-    protocol    = "tcp"
-    cidr_blocks = [aws_vpc.main.cidr_block]
-  }
+		from_port   = 80
+		to_port     = 80
+		protocol    = "tcp"
+		cidr_blocks = [aws_vpc.main.cidr_block]
+	  }
 }
 `},
 			GoodExample: []string{`
@@ -47,12 +46,12 @@ resource "aws_security_group" "good_example" {
   description = "Allow inbound HTTP traffic"
 
   ingress {
-    description = "HTTP from VPC"
-    from_port   = 80
-    to_port     = 80
-    protocol    = "tcp"
-    cidr_blocks = [aws_vpc.main.cidr_block]
-  }
+		description = "HTTP from VPC"
+		from_port   = 80
+		to_port     = 80
+		protocol    = "tcp"
+		cidr_blocks = [aws_vpc.main.cidr_block]
+  	}
 }
 `},
 			Links: []string{
@@ -78,6 +77,22 @@ resource "aws_security_group" "good_example" {
 					WithDescription("Resource '%s' should include a non-empty description for auditing purposes.", resourceBlock.FullName()).
 					WithAttribute(descriptionAttr)
 			}
+
+			checkBlockForDescription("ingress", set, resourceBlock)
+			checkBlockForDescription("egress", set, resourceBlock)
+
 		},
 	})
 }
+
+func checkBlockForDescription(direction string, set result.Set, resourceBlock block.Block) {
+	blocks := resourceBlock.GetBlocks(direction)
+	for _, b := range blocks {
+		descriptionBlock := b.GetAttribute("description")
+		if descriptionBlock.IsNil() || descriptionBlock.IsEmpty() {
+			set.AddResult().
+				WithDescription("Resource '%s' has %s without description.", resourceBlock.FullName(), direction).
+				WithBlock(b)
+		}
+	}
+}


Test patch:
diff --git a/internal/app/tfsec/rules/aws/vpc/add_description_to_security_group_rule_test.go b/internal/app/tfsec/rules/aws/vpc/add_description_to_security_group_rule_test.go
index 0511b7529..0e0a239b0 100644
--- a/internal/app/tfsec/rules/aws/vpc/add_description_to_security_group_rule_test.go
+++ b/internal/app/tfsec/rules/aws/vpc/add_description_to_security_group_rule_test.go
@@ -48,6 +48,92 @@ resource "aws_security_group_rule" "my-rule" {
 }`,
 			mustExcludeResultCode: expectedCode,
 		},
+		{
+			name: "ingress block entry with no description has error",
+			source: `
+			resource "aws_security_group" "bad_example" {
+				name        = "http"
+				description = "some description"
+			  
+				ingress  {
+					description = "ingress block description"
+					from_port   = 80
+					to_port     = 80
+					protocol    = "tcp"
+					cidr_blocks = [aws_vpc.main.cidr_block]
+				  }
+
+				ingress  {
+					  from_port   = 80
+					  to_port     = 80
+					  protocol    = "tcp"
+					  cidr_blocks = [aws_vpc.main.cidr_block]
+					}
+			  }
+			  `,
+			mustIncludeResultCode: expectedCode,
+		},
+		{
+			name: "ingress block entry with description has no error",
+			source: `
+			resource "aws_security_group" "bad_example" {
+				name        = "http"
+				description = "some description"
+			  
+				ingress  {
+					  description = "ingress block description"
+					  from_port   = 80
+					  to_port     = 80
+					  protocol    = "tcp"
+					  cidr_blocks = [aws_vpc.main.cidr_block]
+					}
+			  }
+			  `,
+			mustExcludeResultCode: expectedCode,
+		},
+		{
+			name: "egress block entry with no description has error",
+			source: `
+			resource "aws_security_group" "bad_example" {
+				name        = "http"
+				description = "some description"
+			  
+				egress  {
+					description = "ingress block description"
+					from_port   = 80
+					to_port     = 80
+					protocol    = "tcp"
+					cidr_blocks = [aws_vpc.main.cidr_block]
+				  }
+
+				  egress  {
+					  from_port   = 80
+					  to_port     = 80
+					  protocol    = "tcp"
+					  cidr_blocks = [aws_vpc.main.cidr_block]
+					}
+			  }
+			  `,
+			mustIncludeResultCode: expectedCode,
+		},
+		{
+			name: "egress block entry with description has no error",
+			source: `
+			resource "aws_security_group" "bad_example" {
+				name        = "http"
+				description = "some description"
+			  
+				egress  {
+					  description = "ingress block description"
+					  from_port   = 80
+					  to_port     = 80
+					  protocol    = "tcp"
+					  cidr_blocks = [aws_vpc.main.cidr_block]
+					}
+			  }
+			  `,
+			mustExcludeResultCode: expectedCode,
+		},
 	}
 
 	for _, test := range tests {