Skip to content

Commit f007869

Browse files
authored
feat(terraform): Add new check for overly permissive SQS policy (#7125)
Add new check
1 parent 679eb1b commit f007869

File tree

3 files changed

+188
-0
lines changed

3 files changed

+188
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from checkov.common.models.enums import CheckResult, CheckCategories
2+
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
3+
from typing import List
4+
5+
6+
class SQSOverlyPermissive(BaseResourceCheck):
7+
def __init__(self):
8+
name = "Ensure SQS policy does not allow public access through wildcards"
9+
id = "CKV_AWS_387"
10+
supported_resources = ['aws_sqs_queue_policy']
11+
categories = [CheckCategories.GENERAL_SECURITY]
12+
super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
13+
14+
def scan_resource_conf(self, conf):
15+
if "policy" in conf.keys():
16+
policy = conf["policy"][0]
17+
if isinstance(policy, dict):
18+
for statement in policy.get('Statement', []):
19+
if isinstance(statement, dict):
20+
# Check Effect is Allow
21+
if statement.get('Effect') != 'Allow':
22+
continue
23+
24+
# Check Action starts with sqs: or SQS:
25+
action = statement.get('Action', '')
26+
if isinstance(action, str):
27+
actions = [action]
28+
else:
29+
actions = action
30+
31+
has_sqs_action = any(
32+
isinstance(a, str) and (a == '*' or a.startswith('sqs:') or a.startswith('SQS:')) for a in actions)
33+
if not has_sqs_action:
34+
continue
35+
36+
# Check Principal
37+
principal = statement.get('Principal', {})
38+
if isinstance(principal, str) and principal == '*':
39+
if 'Condition' not in statement:
40+
return CheckResult.FAILED
41+
elif isinstance(principal, dict) and 'AWS' in principal:
42+
aws_principal = principal['AWS']
43+
if isinstance(aws_principal, str) and aws_principal == '*':
44+
if 'Condition' not in statement:
45+
return CheckResult.FAILED
46+
elif isinstance(aws_principal, list) and '*' in aws_principal:
47+
if 'Condition' not in statement:
48+
return CheckResult.FAILED
49+
50+
return CheckResult.PASSED
51+
52+
def get_evaluated_keys(self) -> List[str]:
53+
return ['policy']
54+
55+
56+
check = SQSOverlyPermissive()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# fail
2+
resource "aws_sqs_queue" "fail" {
3+
name = "fail"
4+
}
5+
6+
resource "aws_sqs_queue_policy" "fail" {
7+
queue_url = aws_sqs_queue.fail.id
8+
9+
policy = jsonencode({
10+
Version = "2012-10-17",
11+
Id = "AllowAllSendMessage",
12+
Statement = [
13+
{
14+
Effect = "Allow",
15+
Principal = "*",
16+
Action = "sqs:SendMessage",
17+
Resource = aws_sqs_queue.fail.arn
18+
}
19+
]
20+
})
21+
}
22+
23+
resource "aws_sqs_queue" "fail2" {
24+
name = "fail2"
25+
}
26+
27+
resource "aws_sqs_queue_policy" "fail2" {
28+
queue_url = aws_sqs_queue.fail2.id
29+
30+
policy = jsonencode({
31+
Version = "2012-10-17",
32+
Id = "AllowAllSendMessage",
33+
Statement = [
34+
{
35+
Effect = "Allow",
36+
Principal = "*",
37+
Action = "*",
38+
Resource = aws_sqs_queue.fail.arn
39+
}
40+
]
41+
})
42+
}
43+
44+
# pass
45+
resource "aws_sqs_queue" "pass" {
46+
name = "pass"
47+
}
48+
49+
resource "aws_sqs_queue_policy" "pass" {
50+
queue_url = aws_sqs_queue.pass.id
51+
52+
policy = jsonencode({
53+
Version = "2012-10-17",
54+
Id = "RestrictedSendMessage",
55+
Statement = [
56+
{
57+
Effect = "Allow",
58+
Principal = {
59+
AWS = "arn:aws:iam::123456789012:role/specific-role"
60+
},
61+
Action = "sqs:SendMessage",
62+
Resource = aws_sqs_queue.pass.arn
63+
}
64+
]
65+
})
66+
}
67+
68+
resource "aws_sqs_queue" "pass_w_condition" {
69+
name = "pass_w_condition"
70+
}
71+
72+
resource "aws_sqs_queue_policy" "pass_w_condition" {
73+
queue_url = aws_sqs_queue.pass_w_condition.id
74+
75+
policy = jsonencode({
76+
Version = "2012-10-17",
77+
Id = "ConditionalAllowSendMessage",
78+
Statement = [
79+
{
80+
Effect = "Allow",
81+
Principal = "*",
82+
Action = "sqs:SendMessage",
83+
Resource = aws_sqs_queue.pass_w_condition.arn,
84+
Condition = {
85+
StringEquals = {
86+
"aws:SourceVpc": "vpc-12345678"
87+
}
88+
}
89+
}
90+
]
91+
})
92+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import os
2+
import unittest
3+
4+
from checkov.runner_filter import RunnerFilter
5+
from checkov.terraform.checks.resource.aws.SQSOverlyPermissive import check
6+
from checkov.terraform.runner import Runner
7+
8+
9+
class TestSQSOverlyPermissive(unittest.TestCase):
10+
def test(self):
11+
runner = Runner()
12+
current_dir = os.path.dirname(os.path.realpath(__file__))
13+
14+
test_files_dir = current_dir + "/example_SQSOverlyPermissive"
15+
report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
16+
summary = report.get_summary()
17+
18+
passing_resources = {
19+
"aws_sqs_queue_policy.pass",
20+
"aws_sqs_queue_policy.pass_w_condition",
21+
}
22+
failing_resources = {
23+
"aws_sqs_queue_policy.fail",
24+
"aws_sqs_queue_policy.fail2",
25+
}
26+
27+
passed_check_resources = set([c.resource for c in report.passed_checks])
28+
failed_check_resources = set([c.resource for c in report.failed_checks])
29+
30+
self.assertEqual(summary["passed"], 2)
31+
self.assertEqual(summary["failed"], 2)
32+
self.assertEqual(summary["skipped"], 0)
33+
self.assertEqual(summary["parsing_errors"], 0)
34+
35+
self.assertEqual(passing_resources, passed_check_resources)
36+
self.assertEqual(failing_resources, failed_check_resources)
37+
38+
39+
if __name__ == "__main__":
40+
unittest.main()

0 commit comments

Comments
 (0)