Skip to content

Commit f0ce09b

Browse files
authored
Merge pull request #386 from knasty51/feature/assumeRoleSection
Feature/assume role section
2 parents 1f44ac1 + b51e81c commit f0ce09b

File tree

11 files changed

+219
-3
lines changed

11 files changed

+219
-3
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,14 @@ skip-resource-constraints:
177177
# Exclude actions from the output by specifying them here. Accepts wildcards, like kms:Delete*
178178
exclude-actions:
179179
- ''
180+
# If this policy needs to include an AssumeRole action
181+
sts:
182+
assume-role:
183+
- ''
184+
assume-role-with-saml:
185+
- ''
186+
assume-role-with-web-identity:
187+
- ''
180188
```
181189
182190
### Step 2: Copy/paste ARNs
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
Assume Role Actions
2+
============
3+
4+
Using CRUD mode, you can add STS assume role actions to your larger CRUD policy by using the `sts` section.
5+
6+
**Input**:
7+
8+
```yaml
9+
mode: crud
10+
sts:
11+
assume-role:
12+
- arn:aws:iam::123456789012:role/demo
13+
- arn:aws:iam::123456789012:role/demo2
14+
assume-role-with-saml:
15+
- arn:aws:iam::123456789012:role/demo3
16+
assume-role-with-web-identity:
17+
- 'arn:aws:iam::123456789012:role/demo'
18+
```
19+
20+
**Output**:
21+
22+
```json
23+
{
24+
"Sid": "AssumeRole",
25+
"Effect": "Allow",
26+
"Action": [
27+
"sts:AssumeRole"
28+
],
29+
"Resource": [
30+
"arn:aws:iam::123456789012:role/demo",
31+
"arn:aws:iam::123456789012:role/demo2"
32+
]
33+
},
34+
{
35+
"Sid": "AssumeRoleWithSAML",
36+
"Effect": "Allow",
37+
"Action": [
38+
"sts:AssumeRoleWithSAML"
39+
],
40+
"Resource": [
41+
"arn:aws:iam::123456789012:role/demo3"
42+
]
43+
},
44+
{
45+
"Sid": "AssumeRoleWithWebIdentity",
46+
"Effect": "Allow",
47+
"Action": [
48+
"sts:AssumeRoleWithWebIdentity"
49+
],
50+
"Resource": [
51+
"arn:aws:iam::123456789012:role/demo"
52+
]
53+
}
54+
```

docs/writing-policies/crud-mode.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ wildcard-only:
6161
- ''
6262
service-permissions-management:
6363
- ''
64+
sts:
65+
assume-role:
66+
- ''
67+
assume-role-with-saml:
68+
- ''
69+
assume-role-with-web-identity:
70+
- ''
6471
```
6572
6673
- Then just fill it out:
@@ -92,6 +99,13 @@ wildcard-only:
9299
- ''
93100
service-permissions-management:
94101
- ''
102+
sts:
103+
assume-role:
104+
- ''
105+
assume-role-with-saml:
106+
- ''
107+
assume-role-with-web-identity:
108+
- ''
95109
```
96110
97111
- Run the command:

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ nav:
3434
- "<i>Example</i>: Single actions": 'writing-policies/wildcard-only/example-single-actions.md'
3535
- "<i>Example</i>: Service-wide": 'writing-policies/wildcard-only/example-service-wide.md'
3636
- "<i>Example</i>: Combining approaches": 'writing-policies/wildcard-only/combined-approaches.md'
37+
- "<i>Example</i>: AssumeRole Actions": 'writing-policies/assume-role-actions.md'
3738
- "<b>Wildcard-only</b>":
3839
- Introduction: 'writing-policies/wildcard-only/introduction.md'
3940
- Single actions: 'writing-policies/wildcard-only/single-actions.md'

policy_sentry/writing/sid_group.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,56 @@ def add_skip_resource_constraints(self, skip_resource_constraints_actions):
108108
else:
109109
raise Exception("Please provide 'skip_resource_constraints' as a list of IAM actions.")
110110

111+
def add_sts_actions(self, sts_actions):
112+
"""
113+
To add STS actions to the output from special YAML section
114+
"""
115+
if sts_actions:
116+
# Hard coded for this special case
117+
service_prefix = "sts"
118+
access_level = "Write"
119+
120+
for action, arns in sts_actions.items():
121+
clean_action = action.replace('-','') # Convention to follow adding dashes instead of CamelCase
122+
service_action_data = get_action_data(service_prefix, clean_action)
123+
124+
# Schema validation takes care of this, but just in case. No data returned for the action
125+
if not service_action_data:
126+
raise Exception(f"Could not find service action data for {service_prefix} - {clean_action}")
127+
128+
for row in service_action_data[service_prefix]:
129+
for arn in arns:
130+
if not arn: # skip the - '' situation
131+
continue
132+
if (
133+
does_arn_match(arn, row["resource_arn_format"])
134+
and row["access_level"] == access_level
135+
):
136+
raw_arn_format = row["resource_arn_format"]
137+
138+
# Each action will get its own namespace sts:AssumeRole -> AssumeRole
139+
# -1 index is a neat trick if the colon ever goes away we won't get an index error.
140+
sid_namespace = row["action"].split(':')[-1]
141+
142+
temp_sid_dict = {
143+
"arn": [arn],
144+
"service": service_prefix,
145+
"access_level": access_level,
146+
"arn_format": raw_arn_format,
147+
"actions": [row["action"]],
148+
"conditions": [], # TODO: Add conditions
149+
}
150+
151+
# Using a custom namespace and not gathering actions so no need to find
152+
# dependent actions either, though we could do it here
153+
154+
if sid_namespace in self.sids.keys():
155+
# If the ARN already exists there, skip it.
156+
if arn not in self.sids[sid_namespace]["arn"]:
157+
self.sids[sid_namespace]["arn"].append(arn)
158+
else:
159+
self.sids[sid_namespace] = temp_sid_dict
160+
111161
def add_requested_service_wide(self, service_prefixes, access_level):
112162
"""
113163
When a user requests all wildcard-only actions available under a service at a specific access level
@@ -486,6 +536,14 @@ def process_template(self, cfg, minimize=None):
486536
self.add_action_without_resource_constraint(
487537
skip_resource_constraints_action, "SkipResourceConstraints"
488538
)
539+
540+
# STS Section
541+
if cfg.get("sts"):
542+
logger.debug(
543+
f"STS section detected. Building assume role policy statement"
544+
)
545+
self.add_sts_actions(cfg['sts'])
546+
489547
elif cfg.get("mode") == "actions":
490548
check_actions_schema(cfg)
491549
if "actions" in cfg.keys():

policy_sentry/writing/template.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@
4242
# Exclude actions from the output by specifying them here. Accepts wildcards, like kms:Delete*
4343
exclude-actions:
4444
- ''
45+
# If this policy needs to include an AssumeRole action
46+
sts:
47+
assume-role:
48+
- ''
49+
assume-role-with-saml:
50+
- ''
51+
assume-role-with-web-identity:
52+
- ''
4553
"""
4654

4755
CRUD_TEMPLATE_DICT = {
@@ -61,7 +69,12 @@
6169
"service-permissions-management": [],
6270
},
6371
"skip-resource-constraints": [],
64-
"exclude-actions": []
72+
"exclude-actions": [],
73+
"sts": {
74+
"assume-role": [],
75+
"assume-role-with-saml": [],
76+
"assume-role-with-web-identity": []
77+
}
6578
}
6679

6780
ACTIONS_TEMPLATE_DICT = {"mode": "actions", "name": "", "actions": []}

policy_sentry/writing/validate.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
"""
22
Validation for the Policy Sentry YML Templates.
33
"""
4+
from ast import Or
45
import logging
5-
from schema import Optional, Schema, And, Use, SchemaError
6+
from schema import Optional, Schema, And, Use, Regex, SchemaError
67

78
logger = logging.getLogger(__name__)
89

@@ -51,6 +52,7 @@ def check(conf_schema, conf):
5152
},
5253
Optional("skip-resource-constraints"): [str],
5354
Optional("exclude-actions"): [str],
55+
Optional("sts"): dict({And(Use(str), Regex(r'^assume-role(-with-)*(saml|web-identity)*$')): [str]}),
5456
}
5557
)
5658

test/writing/test_sid_group_crud.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,3 +608,34 @@ def test_exclude_actions_empty_sid_from_crud_output(self):
608608
]
609609
}
610610
self.assertDictEqual(result, expected_result)
611+
612+
613+
def test_write_template_with_sts_actions(self):
614+
cfg = {
615+
"mode": "crud",
616+
"name": "RoleNameWithCRUD",
617+
"sts": {"assume-role-with-web-identity": ["arn:aws:iam::123456789012:role/demo"]},
618+
"list": [
619+
"arn:aws:secretsmanager:us-east-1:123456789012:secret:anothersecret"
620+
],
621+
}
622+
sid_group = SidGroup()
623+
rendered_policy = sid_group.process_template(cfg)
624+
desired_output = {
625+
"Version": "2012-10-17",
626+
"Statement": [
627+
{
628+
"Sid": "AssumeRoleWithWebIdentity",
629+
"Effect": "Allow",
630+
"Action": [
631+
"sts:AssumeRoleWithWebIdentity"
632+
],
633+
"Resource": [
634+
"arn:aws:iam::123456789012:role/demo"
635+
]
636+
}
637+
]
638+
}
639+
640+
# print(json.dumps(rendered_policy, indent=4))
641+
self.assertEqual(rendered_policy, desired_output)

test/writing/test_template.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ def test_crud_template(self):
4747
# Exclude actions from the output by specifying them here. Accepts wildcards, like kms:Delete*
4848
exclude-actions:
4949
- ''
50+
# If this policy needs to include an AssumeRole action
51+
sts:
52+
assume-role:
53+
- ''
54+
assume-role-with-saml:
55+
- ''
56+
assume-role-with-web-identity:
57+
- ''
5058
"""
5159
crud_template = create_crud_template()
5260
self.maxDiff = None

test/writing/test_validate.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,18 @@
4242
"wrist": ["arn:aws:s3:::example-org-sbx-vmimport",],
4343
}
4444

45+
valid_crud_with_sts_actions = {
46+
"mode": "crud",
47+
"name": "RoleNameWithCRUD",
48+
"sts": {"assume-role": ["arn:aws:iam::123456789012:role/demo",]}
49+
}
50+
51+
invalid_crud_with_invalid_sts_action = {
52+
"mode": "crud",
53+
"name": "RoleNameWithCRUD",
54+
"sts": {"assume-role-fake-something": ["arn:aws:iam::123456789012:role/demo",]}
55+
}
56+
4557

4658
class YMLSchemaTestCase(unittest.TestCase):
4759
def test_actions_schema(self):
@@ -54,5 +66,8 @@ def test_crud_schema(self):
5466
result = check_crud_schema(valid_cfg_for_crud)
5567
self.assertTrue(result)
5668
self.assertTrue(check_crud_schema(valid_crud_with_one_item_only))
69+
self.assertTrue(check_crud_schema(valid_crud_with_sts_actions))
5770
with self.assertRaises(Exception):
5871
check_crud_schema(invalid_crud_with_mispelled_category)
72+
with self.assertRaises(Exception):
73+
check_crud_schema(invalid_crud_with_invalid_sts_action)

test/writing/test_write_policy_library_usage.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,16 @@
8686
"Resource": [
8787
"arn:aws:kms:us-east-1:123456789012:key/123456"
8888
]
89+
},
90+
{
91+
"Sid": "AssumeRole",
92+
"Effect": "Allow",
93+
"Action": [
94+
"sts:AssumeRole"
95+
],
96+
"Resource": [
97+
"arn:aws:iam::123456789012:role/demo"
98+
]
8999
}
90100
]
91101
}
@@ -181,6 +191,7 @@ def test_write_crud_policy_with_library_only(self):
181191
"arn:aws:ssm:us-east-1:123456789012:parameter/test"
182192
)
183193
another_crud_template["wildcard-only"]["single-actions"].extend(wildcard_actions_to_add)
194+
another_crud_template["sts"]["assume-role"].append("arn:aws:iam::123456789012:role/demo")
184195
# Modify it
185196
sid_group = SidGroup()
186197
# minimize = None
@@ -193,7 +204,8 @@ def test_write_crud_policy_with_library_only(self):
193204
"SecretsmanagerWriteSecret",
194205
"S3ListObject",
195206
"SsmTaggingParameter",
196-
"KmsPermissionsmanagementKey"
207+
"KmsPermissionsmanagementKey",
208+
"AssumeRole"
197209
]
198210
self.maxDiff = None
199211
print(json.dumps(result, indent=4))

0 commit comments

Comments
 (0)