Skip to content

Commit dad4197

Browse files
authored
Merge pull request #222 from duo-labs/update
Update
2 parents 6908bb3 + c129a2c commit dad4197

30 files changed

+57269
-28144
lines changed

.coveragerc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[run]
2+
source = parliament
3+
omit = parliament/cli.py
4+
5+
[report]
6+
fail_under = 75

README.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -217,13 +217,9 @@ ignore_locations:
217217
To create unit tests for our new private auditor, create the directory `./parliament/private_auditors/tests/` and create the file `test_sensitive_bucket_access.py` there with the contents:
218218

219219
```
220-
import unittest
221-
from nose.tools import raises, assert_equal
222-
223-
# import parliament
224220
from parliament import analyze_policy_string
225221
226-
class TestCustom(unittest.TestCase):
222+
class TestCustom():
227223
"""Test class for custom auditor"""
228224
229225
def test_my_auditor(self):

parliament/__init__.py

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""
22
This library is a linter for AWS IAM policies.
33
"""
4-
__version__ = "1.5.2"
4+
__version__ = "1.6.0"
55

66
import fnmatch
77
import functools
@@ -62,7 +62,11 @@ def analyze_policy_string(
6262
policy_json = jsoncfg.loads_config(policy_str)
6363
except jsoncfg.parser.JSONConfigParserException as e:
6464
policy = Policy(None)
65-
policy.add_finding("MALFORMED_JSON", detail="json parsing error: {}".format(e), location={'line': e.line, 'column': e.column})
65+
policy.add_finding(
66+
"MALFORMED_JSON",
67+
detail="json parsing error: {}".format(e),
68+
location={"line": e.line, "column": e.column},
69+
)
6670
return policy
6771

6872
policy = Policy(policy_json, filepath, config)
@@ -97,7 +101,7 @@ def is_arn_match(resource_type, arn_format, resource):
97101
- arn_format: ARN regex from the docs
98102
- resource: ARN regex from IAM policy
99103
100-
104+
101105
We can cheat some because after the first sections of the arn match, meaning until the 5th colon (with some
102106
rules there to allow empty or asterisk sections), we only need to match the ID part.
103107
So the above is simplified to "*/*" and "*personalize*".
@@ -147,6 +151,7 @@ def is_arn_match(resource_type, arn_format, resource):
147151
resource_id = re.sub(r"\[.+?\]", "*", resource_id)
148152
return is_glob_match(arn_id, resource_id)
149153

154+
150155
def is_arn_strictly_valid(resource_type, arn_format, resource):
151156
"""
152157
Strictly validate the arn_format specified in the docs, with the resource
@@ -179,7 +184,7 @@ def is_arn_strictly_valid(resource_type, arn_format, resource):
179184
arn_id_resource_type = re.match(r"(^[^\*][\w-]+)[\/\:].+", arn_id)
180185

181186
if arn_id_resource_type != None and resource_id != "*":
182-
187+
183188
# https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html#genref-aws-service-namesspaces
184189
# The following is not allowed: arn:aws:iam::123456789012:u*
185190
if not (resource_id.startswith(arn_id_resource_type[1])):
@@ -193,9 +198,11 @@ def is_arn_strictly_valid(resource_type, arn_format, resource):
193198
return True
194199
return False
195200

201+
196202
def strip_var_from_arn(arn, replace_with=""):
197203
return re.sub(r"\$\{aws.[\w\/]+\}", replace_with, arn)
198204

205+
199206
def is_glob_match(s1, s2):
200207
# This comes from https://github.com/duo-labs/parliament/issues/36#issuecomment-574001764
201208

@@ -275,20 +282,20 @@ def expand_action(action, raise_exceptions=True):
275282

276283

277284
def get_resource_type_matches_from_arn(arn):
278-
""" Given an ARN such as "arn:aws:s3:::mybucket", find resource types that match it.
279-
This would return:
280-
[
281-
"resource": {
282-
"arn": "arn:${Partition}:s3:::${BucketName}",
283-
"condition_keys": [],
284-
"resource": "bucket"
285-
},
286-
"service": {
287-
"service_name": "Amazon S3",
288-
"privileges": [...]
289-
...
290-
}
291-
]
285+
"""Given an ARN such as "arn:aws:s3:::mybucket", find resource types that match it.
286+
This would return:
287+
[
288+
"resource": {
289+
"arn": "arn:${Partition}:s3:::${BucketName}",
290+
"condition_keys": [],
291+
"resource": "bucket"
292+
},
293+
"service": {
294+
"service_name": "Amazon S3",
295+
"privileges": [...]
296+
...
297+
}
298+
]
292299
"""
293300
matches = []
294301
for service in iam_definition:
@@ -300,8 +307,7 @@ def get_resource_type_matches_from_arn(arn):
300307

301308

302309
def get_privilege_matches_for_resource_type(resource_type_matches):
303-
""" Given the response from get_resource_type_matches_from_arn(...), this will identify the relevant privileges.
304-
"""
310+
"""Given the response from get_resource_type_matches_from_arn(...), this will identify the relevant privileges."""
305311
privilege_matches = []
306312
for match in resource_type_matches:
307313
for privilege in match["service"]["privileges"]:

parliament/cli.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,12 @@ def main():
137137
help='Provide a string such as \'{"Version": "2012-10-17","Statement": {"Effect": "Allow","Action": ["s3:GetObject", "s3:PutBucketPolicy"],"Resource": ["arn:aws:s3:::bucket1", "arn:aws:s3:::bucket2/*"]}}\'',
138138
type=str,
139139
)
140-
parser.add_argument('--file',
141-
help="Provide a policy via stdin (e.g. through piping) or --file",
142-
type=argparse.FileType('r'),
143-
default=sys.stdin)
140+
parser.add_argument(
141+
"--file",
142+
help="Provide a policy via stdin (e.g. through piping) or --file",
143+
type=argparse.FileType("r"),
144+
default=sys.stdin,
145+
)
144146
parser.add_argument(
145147
"--directory", help="Provide a path to directory with policy files", type=str
146148
)
@@ -236,9 +238,7 @@ def main():
236238
with open(file_path) as f:
237239
contents = f.read()
238240
policy_file_json = jsoncfg.loads_config(contents)
239-
policy_string = json.dumps(
240-
policy_file_json.PolicyVersion.Document()
241-
)
241+
policy_string = json.dumps(policy_file_json.PolicyVersion.Document())
242242
policy = analyze_policy_string(
243243
policy_string,
244244
file_path,
@@ -271,15 +271,16 @@ def main():
271271
if not version.IsDefaultVersion():
272272
continue
273273
policy = analyze_policy_string(
274-
json.dumps(version.Document()), policy.Arn(),
274+
json.dumps(version.Document()),
275+
policy.Arn(),
275276
)
276277
findings.extend(policy.findings)
277278

278279
# Review the inline policies on Users, Roles, and Groups
279280
for user in auth_details_json.UserDetailList:
280281
for policy in user.UserPolicyList([]):
281282
policy = analyze_policy_string(
282-
json.dumps(policy['PolicyDocument']),
283+
json.dumps(policy["PolicyDocument"]),
283284
user.Arn(),
284285
private_auditors_custom_path=args.private_auditors,
285286
include_community_auditors=args.include_community_auditors,
@@ -289,7 +290,7 @@ def main():
289290
for role in auth_details_json.RoleDetailList:
290291
for policy in role.RolePolicyList([]):
291292
policy = analyze_policy_string(
292-
json.dumps(policy['PolicyDocument']),
293+
json.dumps(policy["PolicyDocument"]),
293294
role.Arn(),
294295
private_auditors_custom_path=args.private_auditors,
295296
include_community_auditors=args.include_community_auditors,
@@ -299,7 +300,7 @@ def main():
299300
for group in auth_details_json.GroupDetailList:
300301
for policy in group.GroupPolicyList([]):
301302
policy = analyze_policy_string(
302-
json.dumps(policy['PolicyDocument']),
303+
json.dumps(policy["PolicyDocument"]),
303304
group.Arn(),
304305
private_auditors_custom_path=args.private_auditors,
305306
include_community_auditors=args.include_community_auditors,

parliament/community_auditors/single_value_condition_too_permissive.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from parliament import Policy
88
from parliament.misc import make_list
99

10+
1011
def audit(policy: Policy) -> None:
1112
global_single_valued_condition_keys = [
1213
"aws:CalledViaFirst",
@@ -52,9 +53,12 @@ def audit(policy: Policy) -> None:
5253
operator = condition[0]
5354
condition_block = condition[1]
5455
if re.match(r"^For(All|Any)Values:", operator):
55-
keys = list(k for k,_v in condition_block)
56-
if any(any(re.match(k, key) for k in global_single_valued_condition_keys) for key in keys):
56+
keys = list(k for k, _v in condition_block)
57+
if any(
58+
any(re.match(k, key) for k in global_single_valued_condition_keys)
59+
for key in keys
60+
):
5761
policy.add_finding(
5862
"SINGLE_VALUE_CONDITION_TOO_PERMISSIVE",
59-
detail='Checking a single value conditional key against a set of values results in overly permissive policies.',
63+
detail="Checking a single value conditional key against a set of values results in overly permissive policies.",
6064
)

parliament/community_auditors/tests/test_advanced_policy_elements.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
1-
import unittest
2-
3-
from nose.tools import assert_equal
4-
51
from parliament import analyze_policy_string
62

73
S3_STAR_FINDINGS = {"PERMISSIONS_MANAGEMENT_ACTIONS", "RESOURCE_MISMATCH"}
84

95

10-
class TestAdvancedPolicyElements(unittest.TestCase):
6+
class TestAdvancedPolicyElements:
117
def test_notresource_allow(self):
128
# NotResource is OK with Effect: Deny. This denies access to
139
# all S3 buckets except Payroll buckets. This example is taken from

parliament/community_auditors/tests/test_credentials_exposure.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
1-
import unittest
2-
3-
from nose.tools import assert_equal
4-
5-
# import parliament
61
from parliament import analyze_policy_string
72

83

9-
class TestCredentialsManagement(unittest.TestCase):
4+
class TestCredentialsManagement:
105
"""Test class for Credentials Management auditor"""
116

127
def test_credentials_management(self):

parliament/community_auditors/tests/test_permissions_management.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
1-
import unittest
2-
3-
from nose.tools import assert_equal
4-
5-
# import parliament
61
from parliament import analyze_policy_string
72

83

9-
class TestPermissionsManagement(unittest.TestCase):
4+
class TestPermissionsManagement:
105
"""Test class for Permissions Management auditor"""
116

127
def test_permissions_management(self):

parliament/community_auditors/tests/test_privilege_escalation.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
1-
import unittest
2-
3-
from nose.tools import assert_equal
4-
5-
# import parliament
61
from parliament import analyze_policy_string
72

83

9-
class TestPrivilegeEscalation(unittest.TestCase):
4+
class TestPrivilegeEscalation:
105
"""Test class for Privilege Escalation auditor"""
116

127
def test_privilege_escalation(self):

parliament/community_auditors/tests/test_sensitive_access.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
1-
import unittest
2-
3-
from nose.tools import assert_equal
4-
51
from parliament import analyze_policy_string
62

73

8-
class TestSensitiveAccess(unittest.TestCase):
4+
class TestSensitiveAccess:
95
"""Test class for Sensitive access auditor"""
106

117
def test_sensitive_access(self):

0 commit comments

Comments
 (0)