Skip to content

Commit 58afe93

Browse files
simar7nikpivkin
andauthored
feat(checks): Restrict s3 from wild card access (#373)
* feat(checks): Add a check to disallow wildcard S3 bucket access * add a todo for AVD-AWS-0057 * make fmt-rego * refactor and check for iam.Roles * add examples * remove todo * make docs * add json.marshal to prettify * chore: format and simplify test data Signed-off-by: Nikita Pivkin <[email protected]> --------- Signed-off-by: Nikita Pivkin <[email protected]> Co-authored-by: Nikita Pivkin <[email protected]>
1 parent 0797a5c commit 58afe93

File tree

6 files changed

+365
-0
lines changed

6 files changed

+365
-0
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
2+
Create more restrictive S3 policies instead of using s3:*
3+
4+
```yaml
5+
AWSTemplateFormatVersion: "2010-09-09"
6+
7+
Resources:
8+
GoodPolicy:
9+
Type: AWS::IAM::Policy
10+
Properties:
11+
PolicyName: good_policy
12+
PolicyDocument:
13+
Version: "2012-10-17"
14+
Statement:
15+
- Effect: Allow
16+
Action:
17+
- s3:GetObject
18+
- s3:PutObject
19+
Resource: arn:aws:s3:::examplebucket/*
20+
Roles:
21+
- !Ref GoodRole
22+
23+
GoodRole:
24+
Type: AWS::IAM::Role
25+
Properties:
26+
RoleName: good_role
27+
AssumeRolePolicyDocument:
28+
Version: "2012-10-17"
29+
Statement:
30+
- Effect: Allow
31+
Principal:
32+
Service: ec2.amazonaws.com
33+
Action: sts:AssumeRole
34+
```
35+
36+
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
2+
Create more restrictive S3 policies instead of using s3:*
3+
4+
```hcl
5+
resource "aws_iam_policy" "good_policy" {
6+
name = "good_policy"
7+
policy = jsonencode({
8+
Version = "2012-10-17"
9+
Statement = [
10+
{
11+
Effect = "Allow"
12+
Action = [
13+
"s3:GetObject",
14+
"s3:PutObject"
15+
]
16+
Resource = "arn:aws:s3:::examplebucket/*"
17+
}
18+
]
19+
})
20+
}
21+
22+
resource "aws_iam_role" "good_role" {
23+
name = "good_role"
24+
assume_role_policy = jsonencode({
25+
Version = "2012-10-17"
26+
Statement = [
27+
{
28+
Effect = "Allow"
29+
Principal = {
30+
Service = "ec2.amazonaws.com"
31+
}
32+
Action = "sts:AssumeRole"
33+
}
34+
]
35+
})
36+
}
37+
38+
resource "aws_iam_role_policy_attachment" "good_role_policy_attachment" {
39+
role = aws_iam_role.good_role.name
40+
policy_arn = aws_iam_policy.good_policy.arn
41+
}
42+
```
43+
44+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
Ensure that the creation of the IAM policy 's3:*' is disallowed.
3+
4+
### Impact
5+
<!-- Add Impact here -->
6+
7+
<!-- DO NOT CHANGE -->
8+
{{ remediationActions }}
9+
10+
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# METADATA
2+
# title: "Disallow unrestricted s3:* IAM Policies"
3+
# description: "Ensure that the creation of the IAM policy 's3:*' is disallowed."
4+
# scope: package
5+
# schemas:
6+
# - input: schema["cloud"]
7+
# custom:
8+
# id: AVD-AWS-0345
9+
# avd_id: AVD-AWS-0345
10+
# provider: aws
11+
# service: iam
12+
# severity: HIGH
13+
# short_code: no-s3-full-access
14+
# recommended_action: "Create more restrictive S3 policies instead of using s3:*"
15+
# input:
16+
# selector:
17+
# - type: cloud
18+
# subtypes:
19+
# - service: iam
20+
# provider: aws
21+
# examples: checks/cloud/aws/iam/limit_s3_full_access.yaml
22+
package builtin.aws.iam.aws0345
23+
24+
import rego.v1
25+
26+
allows_permission(statements, permission, effect) if {
27+
statement := statements[_]
28+
statement.Effect == effect
29+
action = statement.Action[_]
30+
action == permission
31+
}
32+
33+
is_s3_full_access_allowed(document) if {
34+
value := json.unmarshal(document)
35+
statements := value.Statement
36+
not allows_permission(statements, "s3:*", "Deny")
37+
allows_permission(statements, "s3:*", "Allow")
38+
}
39+
40+
deny contains res if {
41+
policy := input.aws.iam.policies[_]
42+
value = is_s3_full_access_allowed(policy.document.value)
43+
res = result.new("IAM policy allows 's3:*' action", policy.document)
44+
}
45+
46+
deny contains res if {
47+
role := input.aws.iam.roles[_]
48+
policy := role.policies[_]
49+
value = is_s3_full_access_allowed(policy.document.value)
50+
res = result.new("IAM role uses a policy that allows 's3:*' action", role)
51+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
cloudformation:
2+
good:
3+
- |-
4+
AWSTemplateFormatVersion: "2010-09-09"
5+
6+
Resources:
7+
GoodPolicy:
8+
Type: AWS::IAM::Policy
9+
Properties:
10+
PolicyName: good_policy
11+
PolicyDocument:
12+
Version: "2012-10-17"
13+
Statement:
14+
- Effect: Allow
15+
Action:
16+
- s3:GetObject
17+
- s3:PutObject
18+
Resource: arn:aws:s3:::examplebucket/*
19+
Roles:
20+
- !Ref GoodRole
21+
22+
GoodRole:
23+
Type: AWS::IAM::Role
24+
Properties:
25+
RoleName: good_role
26+
AssumeRolePolicyDocument:
27+
Version: "2012-10-17"
28+
Statement:
29+
- Effect: Allow
30+
Principal:
31+
Service: ec2.amazonaws.com
32+
Action: sts:AssumeRole
33+
bad:
34+
- |-
35+
AWSTemplateFormatVersion: "2010-09-09"
36+
37+
Resources:
38+
BadPolicy:
39+
Type: AWS::IAM::Policy
40+
Properties:
41+
PolicyName: bad_policy
42+
PolicyDocument:
43+
Version: "2012-10-17"
44+
Statement:
45+
- Effect: Allow
46+
Action: s3:*
47+
Resource: '*'
48+
Roles:
49+
- !Ref BadRole
50+
51+
BadRole:
52+
Type: AWS::IAM::Role
53+
Properties:
54+
RoleName: bad_role
55+
AssumeRolePolicyDocument:
56+
Version: "2012-10-17"
57+
Statement:
58+
- Effect: Allow
59+
Principal:
60+
Service: ec2.amazonaws.com
61+
Action: sts:AssumeRole
62+
terraform:
63+
good:
64+
- |-
65+
resource "aws_iam_policy" "good_policy" {
66+
name = "good_policy"
67+
policy = jsonencode({
68+
Version = "2012-10-17"
69+
Statement = [
70+
{
71+
Effect = "Allow"
72+
Action = [
73+
"s3:GetObject",
74+
"s3:PutObject"
75+
]
76+
Resource = "arn:aws:s3:::examplebucket/*"
77+
}
78+
]
79+
})
80+
}
81+
82+
resource "aws_iam_role" "good_role" {
83+
name = "good_role"
84+
assume_role_policy = jsonencode({
85+
Version = "2012-10-17"
86+
Statement = [
87+
{
88+
Effect = "Allow"
89+
Principal = {
90+
Service = "ec2.amazonaws.com"
91+
}
92+
Action = "sts:AssumeRole"
93+
}
94+
]
95+
})
96+
}
97+
98+
resource "aws_iam_role_policy_attachment" "good_role_policy_attachment" {
99+
role = aws_iam_role.good_role.name
100+
policy_arn = aws_iam_policy.good_policy.arn
101+
}
102+
bad:
103+
- |-
104+
resource "aws_iam_policy" "bad_policy" {
105+
name = "bad_policy"
106+
policy = jsonencode({
107+
Version = "2012-10-17"
108+
Statement = [
109+
{
110+
Effect = "Allow"
111+
Action = "s3:*"
112+
Resource = "*"
113+
}
114+
]
115+
})
116+
}
117+
118+
resource "aws_iam_role" "bad_role" {
119+
name = "bad_role"
120+
assume_role_policy = jsonencode({
121+
Version = "2012-10-17"
122+
Statement = [
123+
{
124+
Effect = "Allow"
125+
Principal = {
126+
Service = "ec2.amazonaws.com"
127+
}
128+
Action = "sts:AssumeRole"
129+
}
130+
]
131+
})
132+
}
133+
134+
resource "aws_iam_role_policy_attachment" "bad_role_policy_attachment" {
135+
role = aws_iam_role.bad_role.name
136+
policy_arn = aws_iam_policy.bad_policy.arn
137+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package builtin.aws.iam.aws0345
2+
3+
import rego.v1
4+
5+
test_with_allow_s3_full_access if {
6+
policies := [{
7+
"name": "policy_with_s3_full_access",
8+
"document": {"value": json.marshal({
9+
"Version": "2012-10-17",
10+
"Statement": [{
11+
"Effect": "Allow",
12+
"Action": ["s3:*"],
13+
"Resource": ["*"],
14+
}],
15+
})},
16+
}]
17+
18+
r := deny with input as {"aws": {"iam": {"policies": policies}}}
19+
count(r) == 1
20+
}
21+
22+
test_with_deny_s3_full_access if {
23+
policies := [{
24+
"name": "policy_with_s3_full_access",
25+
"document": {"value": json.marshal({
26+
"Version": "2012-10-17",
27+
"Statement": [{
28+
"Effect": "Deny",
29+
"Action": ["s3:*"],
30+
}],
31+
})},
32+
}]
33+
34+
r := deny with input as {"aws": {"iam": {"policies": policies}}}
35+
count(r) == 0
36+
}
37+
38+
test_with_no_s3_full_access if {
39+
policies := [{
40+
"name": "policy_without_s3_full_access",
41+
"document": {"value": json.marshal({
42+
"Version": "2012-10-17",
43+
"Statement": [{
44+
"Effect": "Allow",
45+
"Action": ["s3:GetObject"],
46+
"Resource": ["arn:aws:s3:::examplebucket/*"],
47+
}],
48+
})},
49+
}]
50+
51+
r := deny with input as {"aws": {"iam": {"policies": policies}}}
52+
count(r) == 0
53+
}
54+
55+
test_with_role_using_amazon_s3_full_access_policy if {
56+
roles := [{
57+
"name": "role_with_amazon_s3_full_access",
58+
"policies": [{"document": {"value": json.marshal({
59+
"Version": "2012-10-17",
60+
"Statement": [{
61+
"Effect": "Allow",
62+
"Action": ["s3:*"],
63+
"Resource": ["*"],
64+
}],
65+
})}}],
66+
}]
67+
68+
r := deny with input as {"aws": {"iam": {"roles": roles}}}
69+
count(r) == 1
70+
}
71+
72+
test_with_role_not_using_amazon_s3_full_access_policy if {
73+
roles := [{
74+
"name": "role_without_amazon_s3_full_access",
75+
"policies": [{"document": {"value": json.marshal({
76+
"Version": "2012-10-17",
77+
"Statement": [{
78+
"Effect": "Allow",
79+
"Action": ["s3:GetObject"],
80+
"Resource": ["arn:aws:s3:::examplebucket"],
81+
}],
82+
})}}],
83+
}]
84+
85+
r := deny with input as {"aws": {"iam": {"roles": roles}}}
86+
count(r) == 0
87+
}

0 commit comments

Comments
 (0)