Skip to content

Commit a0abccb

Browse files
authored
Merge pull request #315 from kmcquade/fix/GH-296-query-all-services-for-wildcard-only
Fixes #296 - query all services for wildcard-only actions
2 parents af607da + e693567 commit a0abccb

File tree

7 files changed

+118
-37
lines changed

7 files changed

+118
-37
lines changed

policy_sentry/bin/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"""
33
Policy Sentry is a tool for generating least-privilege IAM Policies.
44
"""
5-
__version__ = "0.11.2"
5+
__version__ = "0.11.3"
66
import click
77
from policy_sentry import command
88

policy_sentry/command/query.py

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
get_action_data,
2020
get_actions_matching_condition_key,
2121
get_actions_with_arn_type_and_access_level,
22-
get_actions_matching_arn_type
22+
get_actions_matching_arn_type,
23+
get_actions_that_support_wildcard_arns_only
2324
)
2425
from policy_sentry.querying.conditions import (
2526
get_condition_keys_for_service,
@@ -32,6 +33,20 @@
3233
iam_definition_path = DATASTORE_FILE_PATH
3334

3435

36+
def print_list(output, fmt="json"):
37+
"""Common method on how to print a list, depending on whether the user requests JSON or YAML output"""
38+
print(yaml.dump(output)) if fmt == "yaml" else [
39+
print(item) for item in output
40+
]
41+
42+
43+
def print_dict(output, fmt="json"):
44+
"""Common method on how to print a dict, depending on whether the user requests JSON or YAML output"""
45+
print(yaml.dump(output)) if fmt == "yaml" else [
46+
print(json.dumps(output, indent=4))
47+
]
48+
49+
3550
@click.group()
3651
def query():
3752
"""Allow users to query the IAM tables from command line"""
@@ -113,63 +128,53 @@ def query_action_table(
113128
for serv in all_services:
114129
result = get_actions_with_access_level(serv, level)
115130
output.extend(result)
116-
print(yaml.dump(output)) if fmt == "yaml" else [
117-
print(result) for result in output
118-
]
131+
print_list(output=output, fmt=fmt)
119132
# Get a list of all services in the database
133+
elif resource_type == "*":
134+
print("ALL actions that do not support resource ARN constraints")
135+
output = get_actions_that_support_wildcard_arns_only(service)
136+
print_dict(output=output, fmt=fmt)
120137
else:
121138
print("All services in the database:\n")
122139
output = all_services
123-
print(yaml.dump(output)) if fmt == "yaml" else [
124-
print(item) for item in output
125-
]
140+
print_list(output=output, fmt=fmt)
126141
elif name is None and access_level and not resource_type:
127142
print(
128143
f"All IAM actions under the {service} service that have the access level {access_level}:"
129144
)
130145
level = transform_access_level_text(access_level)
131146
output = get_actions_with_access_level(service, level)
132-
print(yaml.dump(output)) if fmt == "yaml" else [
133-
print(json.dumps(output, indent=4))
134-
]
147+
print_dict(output=output, fmt=fmt)
135148
elif name is None and access_level and resource_type:
136149
print(
137150
f"{service} {access_level.upper()} actions that have the resource type {resource_type.upper()}:"
138151
)
139152
access_level = transform_access_level_text(access_level)
140153
output = get_actions_with_arn_type_and_access_level(service, resource_type, access_level)
141-
print(yaml.dump(output)) if fmt == "yaml" else [
142-
print(json.dumps(output, indent=4))
143-
]
154+
print_dict(output=output, fmt=fmt)
144155
# Get a list of all IAM actions under the service that support the specified condition key.
145156
elif condition:
146157
print(
147158
f"IAM actions under {service} service that support the {condition} condition only:"
148159
)
149160
output = get_actions_matching_condition_key(service, condition)
150-
print(yaml.dump(output)) if fmt == "yaml" else [
151-
print(json.dumps(output, indent=4))
152-
]
161+
print_dict(output=output, fmt=fmt)
153162
# Get a list of IAM Actions under the service that only support resources = "*"
154163
# (i.e., you cannot restrict it according to ARN)
155164
elif resource_type:
156165
print(
157166
f"IAM actions under {service} service that have the resource type {resource_type}:"
158167
)
159168
output = get_actions_matching_arn_type(service, resource_type)
160-
print(yaml.dump(output)) if fmt == "yaml" else [
161-
print(json.dumps(output, indent=4))
162-
]
169+
print_dict(output=output, fmt=fmt)
163170
elif name and access_level is None:
164171
output = get_action_data(service, name)
165-
print(yaml.dump(output)) if fmt == "yaml" else [
166-
print(json.dumps(output, indent=4))
167-
]
172+
print_dict(output=output, fmt=fmt)
168173
else:
169174
# Get a list of all IAM Actions available to the service
170175
output = get_actions_for_service(service)
171176
print(f"ALL {service} actions:")
172-
print(yaml.dump(output)) if fmt == "yaml" else [print(item) for item in output]
177+
print_list(output=output, fmt=fmt)
173178
return output
174179

175180

@@ -225,20 +230,16 @@ def query_arn_table(name, service, list_arn_types, fmt):
225230
# Get a list of all RAW ARN formats available through the service.
226231
if name is None and list_arn_types is False:
227232
output = get_raw_arns_for_service(service)
228-
print(yaml.dump(output)) if fmt == "yaml" else [print(item) for item in output]
233+
print_list(output=output, fmt=fmt)
229234
# Get a list of all the ARN types per service, paired with the RAW ARNs
230235
elif name is None and list_arn_types:
231236
output = get_arn_types_for_service(service)
232-
print(yaml.dump(output)) if fmt == "yaml" else [
233-
print(json.dumps(output, indent=4))
234-
]
237+
print_dict(output=output, fmt=fmt)
235238
# Get the raw ARN format for the `cloud9` service with the short name
236239
# `environment`
237240
else:
238241
output = get_arn_type_details(service, name)
239-
print(yaml.dump(output)) if fmt == "yaml" else [
240-
print(json.dumps(output, indent=4))
241-
]
242+
print_dict(output=output, fmt=fmt)
242243
return output
243244

244245

@@ -287,11 +288,9 @@ def query_condition_table(name, service, fmt="json"):
287288
# Get a list of all condition keys available to the service
288289
if name is None:
289290
output = get_condition_keys_for_service(service)
290-
print(yaml.dump(output)) if fmt == "yaml" else [print(item) for item in output]
291+
print_list(output=output, fmt=fmt)
291292
# Get details on the specific condition key
292293
else:
293294
output = get_condition_key_details(service, name)
294-
print(yaml.dump(output)) if fmt == "yaml" else [
295-
print(json.dumps(output, indent=4))
296-
]
295+
print_dict(output=output, fmt=fmt)
297296
return output

policy_sentry/querying/actions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ def get_actions_that_support_wildcard_arns_only(service_prefix):
217217
if len(action_data["resource_types"].keys()) == 1:
218218
for resource_type in action_data["resource_types"]:
219219
if resource_type == '':
220-
results.append(f"{service_prefix}:{action_name}")
220+
results.append(f"{some_prefix}:{action_name}")
221221
else:
222222
service_prefix_data = get_service_prefix_data(service_prefix)
223223
for action_name, action_data in service_prefix_data["privileges"].items():

tasks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ def query_with_yaml(c):
182182
c.run('echo "Querying the action table"', pty=True)
183183
c.run('./policy_sentry/bin/cli.py query action-table --service ram --fmt yaml', pty=True)
184184
c.run('./policy_sentry/bin/cli.py query action-table --service ram --name tagresource --fmt yaml', pty=True)
185-
c.run('./policy_sentry/bin/cli.py query action-table ''--service ram --access-level permissions-management --fmt yaml', pty=True)
185+
c.run('./policy_sentry/bin/cli.py query action-table --service ram --access-level permissions-management --fmt yaml', pty=True)
186186
c.run('./policy_sentry/bin/cli.py query action-table --service ses --condition ses:FeedbackAddress --fmt yaml', pty=True)
187187
c.run('echo "Querying the ARN table"', pty=True)
188188
c.run('./policy_sentry/bin/cli.py query arn-table --service ssm --fmt yaml', pty=True)

test/command/__init__.py

Whitespace-only changes.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import json
2+
import unittest
3+
from click.testing import CliRunner
4+
from policy_sentry.command.query import query
5+
6+
7+
class QueryClickUnitTests(unittest.TestCase):
8+
def setUp(self):
9+
self.runner = CliRunner()
10+
11+
def test_query_action_table_basic_with_click(self):
12+
"""command.query.query_action_table: should return exit code 0"""
13+
result = self.runner.invoke(query, ["action-table", "--service", "ram"])
14+
self.assertTrue(result.exit_code == 0)
15+
16+
def test_click_query_action_table(self):
17+
"""command.query.query_action_table: click testing"""
18+
cases = [
19+
["action-table", "--service", "all", "--access-level", "permissions-management"],
20+
["action-table", "--service", "all", "--resource-type", "*"],
21+
["action-table", "--service", "all"],
22+
["action-table", "--service", "ram", "--access-level", "permissions-management"],
23+
["action-table", "--service", "ram", "--access-level", "permissions-management", "--resource-type", "resource-share"],
24+
["action-table", "--service", "ses", "--condition", "ses:FeedbackAddress"],
25+
["action-table", "--service", "ssm", "--resource-type", "*"],
26+
["action-table", "--service", "ram", "--name", "tagresource"],
27+
["action-table", "--service", "ssm", "--resource-type", "parameter"],
28+
["action-table", "--service", "ram"],
29+
]
30+
for case in cases:
31+
result = self.runner.invoke(query, case)
32+
print(result.output)
33+
self.assertTrue(result.exit_code == 0)
34+
35+
def test_click_query_arn_table(self):
36+
"""command.query.query_arn_table: click testing"""
37+
cases = [
38+
["arn-table", "--service", "ssm"],
39+
["arn-table", "--service", "cloud9", "--list-arn-types"],
40+
["arn-table", "--service", "cloud9", "--name", "environment"],
41+
]
42+
for case in cases:
43+
result = self.runner.invoke(query, case)
44+
print(result.output)
45+
self.assertTrue(result.exit_code == 0)
46+
47+
def test_click_query_condition_table(self):
48+
"""command.query.query_condition_table: click testing"""
49+
cases = [
50+
["condition-table", "--service", "cloud9"],
51+
["condition-table", "--service", "cloud9", "--name", "cloud9:Permissions"],
52+
]
53+
for case in cases:
54+
result = self.runner.invoke(query, case)
55+
print(result.output)
56+
self.assertTrue(result.exit_code == 0)

test/querying/test_all.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
get_all_service_prefixes,
55
get_all_actions
66
)
7+
from policy_sentry.command.query import query_action_table
78

89

910
class QueryActionsTestCase(unittest.TestCase):
@@ -24,3 +25,28 @@ def test_get_all_actions(self):
2425
# performance notes:
2526
# old: 0.112s (without sort)
2627
# new: 0.106s
28+
29+
def test_query_actions_with_access_level_and_wildcard_only(self):
30+
service = "all"
31+
resource_type = "*"
32+
result = query_action_table(
33+
service=service,
34+
resource_type=resource_type,
35+
name=None,
36+
access_level="permissions-management",
37+
condition=None
38+
)
39+
print(len(result))
40+
self.assertTrue(len(result) > 200)
41+
42+
def test_GH_296_query_all_actions_with_wildcard_resources(self):
43+
service = "all"
44+
resource_type = "*"
45+
result = query_action_table(
46+
service=service,
47+
resource_type=resource_type,
48+
name=None,
49+
access_level=None,
50+
condition=None
51+
)
52+
self.assertTrue(len(result) > 3000)

0 commit comments

Comments
 (0)