def test_nodes(self): with self.assertRaises(ValueError): Node(arn='arn:aws:iam::000000000000:group/notauser', id_value='AIDA00000000000000000', attached_policies=[], group_memberships=[], trust_policy=None, instance_profile=None, num_access_keys=0, active_password=False, is_admin=False, permissions_boundary=None, has_mfa=False, tags={}) try: Node(arn='arn:aws:iam::000000000000:user/auser', id_value='AIDA00000000000000001', attached_policies=[], group_memberships=[], trust_policy=None, instance_profile=None, num_access_keys=0, active_password=False, is_admin=False, permissions_boundary=None, has_mfa=False, tags={}) except Exception as ex: self.fail('Unexpected error: ' + str(ex))
def build_playground_graph() -> Graph: """Constructs and returns a Graph objects with many nodes, edges, groups, and policies""" common_iam_prefix = 'arn:aws:iam::000000000000:' # policies to use and add admin_policy = Policy('arn:aws:iam::aws:policy/AdministratorAccess', 'AdministratorAccess', _get_admin_policy()) ec2_for_ssm_policy = Policy('arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM', 'AmazonEC2RoleforSSM', _get_ec2_for_ssm_policy()) s3_full_access_policy = Policy('arn:aws:iam::aws:policy/AmazonS3FullAccess', 'AmazonS3FullAccess', _get_s3_full_access_policy()) jump_policy = Policy('arn:aws:iam::000000000000:policy/JumpPolicy', 'JumpPolicy', _get_jump_policy()) policies = [admin_policy, ec2_for_ssm_policy, s3_full_access_policy, jump_policy] # IAM role trust docs to be used ec2_trusted_policy_doc = _make_trust_document({'Service': 'ec2.amazonaws.com'}) root_trusted_policy_doc = _make_trust_document({'AWS': 'arn:aws:iam::000000000000:root'}) alt_root_trusted_policy_doc = _make_trust_document({'AWS': '000000000000'}) other_acct_trusted_policy_doc = _make_trust_document({'AWS': '999999999999'}) # nodes to add nodes = [] # Regular admin user nodes.append(Node(common_iam_prefix + 'user/admin', 'AIDA00000000000000000', [admin_policy], [], None, None, 1, True, True)) # Regular ec2 role nodes.append(Node(common_iam_prefix + 'role/ec2_ssm_role', 'AIDA00000000000000001', [ec2_for_ssm_policy], [], ec2_trusted_policy_doc, common_iam_prefix + 'instance-profile:/ec2_ssm_role', 0, False, False)) # ec2 role with admin nodes.append(Node(common_iam_prefix + 'role/ec2_admin_role', 'AIDA00000000000000002', [ec2_for_ssm_policy], [], ec2_trusted_policy_doc, common_iam_prefix + 'instance-profile/ec2_admin_role', 0, False, True)) # assumable role with s3 access nodes.append(Node(common_iam_prefix + 'role/s3_access_role', 'AIDA00000000000000003', [s3_full_access_policy], [], root_trusted_policy_doc, None, 0, False, False)) # second assumable role with s3 access with alternative trust policy nodes.append(Node(common_iam_prefix + 'role/s3_access_role_alt', 'AIDA00000000000000004', [s3_full_access_policy], [], alt_root_trusted_policy_doc, None, 0, False, False)) # externally assumable role with s3 access nodes.append(Node(common_iam_prefix + 'role/external_s3_access_role', 'AIDA00000000000000005', [s3_full_access_policy], [], other_acct_trusted_policy_doc, None, 0, False, False)) # jump user with access to sts:AssumeRole nodes.append(Node(common_iam_prefix + 'user/jumpuser', 'AIDA00000000000000006', [jump_policy], [], None, None, 1, True, False)) # edges to add edges = obtain_edges(None, checker_map.keys(), nodes, sys.stdout, True) return Graph(nodes, edges, policies, [], _get_default_metadata())
def _build_user_with_policy(policy_dict, policy_name='single_user_policy', user_name='asdf', number='0') -> Node: """Helper function: builds an IAM User with a given input policy.""" policy = Policy('arn:aws:iam::000000000000:policy/{}'.format(policy_name), policy_name, policy_dict) result = Node( 'arn:aws:iam::000000000000:user/{}'.format(user_name), 'AIDA0000000000000000{}'.format(number), [policy], [], None, None, 1, True, False ) return result
def test_local_mfa_handling(self): mfa_policy = Policy( arn='arn:aws:iam::000000000000:policy/mfa_policy', name='mfa_policy', policy_doc={ "Version": "2012-10-17", "Statement": [{ "Sid": "AllowManageOwnUserMFA", "Effect": "Allow", "Action": [ "iam:DeactivateMFADevice", "iam:EnableMFADevice", "iam:GetUser", "iam:ListMFADevices", "iam:ResyncMFADevice" ], "Resource": "arn:aws:iam::*:user/${aws:username}" }, { "Sid": "DenyAllExceptListedIfNoMFA", "Effect": "Deny", "NotAction": [ "iam:CreateVirtualMFADevice", "iam:EnableMFADevice", "iam:GetUser", "iam:ListMFADevices", "iam:ListVirtualMFADevices", "iam:ResyncMFADevice", "sts:GetSessionToken" ], "Resource": "*", "Condition": { "BoolIfExists": { "aws:MultiFactorAuthPresent": "false" } } }] }) s3_policy = Policy( 'arn:aws:iam::000000000000:policy/s3access', 's3access', { "Version": "2012-10-17", "Statement": [{ "Sid": "AllowViewAccountInfo", "Effect": "Allow", "Action": "s3:ListAllMyBuckets", "Resource": "*" }] }) test_node = Node('arn:aws:iam::000000000000:user/uses_mfa', 'AIDA00000000000000000', [mfa_policy, s3_policy], [], None, None, 1, False, False, None, True, None) print(mfa_policy.to_dictionary()) print(s3_policy.to_dictionary()) print(test_node.to_dictionary()) # Test that lack of MFA conditions is not allowed (note the function call diff) # This matches policy sim feedback auth_result = local_check_authorization(test_node, 's3:ListAllMyBuckets', '*', {}) self.assertFalse(auth_result) # Test that MFA set to false is disallowed auth_result, mfa_result = local_check_authorization_handling_mfa( test_node, 's3:ListAllMyBuckets', '*', {'aws:MultiFactorAuthPresent': 'false'}) self.assertFalse(auth_result) self.assertFalse(mfa_result) # Test that testing both with and without MFA yields correct results auth_result, mfa_result = local_check_authorization_handling_mfa( test_node, 's3:ListAllMyBuckets', '*', {}) self.assertTrue(auth_result) self.assertTrue(mfa_result) # Test that iam:EnableMFADevice is allowed despite lack of MFA auth_result, mfa_result = local_check_authorization_handling_mfa( test_node, 'iam:EnableMFADevice', 'arn:aws:iam::000000000000:user/uses_mfa', {}) self.assertFalse(mfa_result) self.assertTrue(auth_result)
def create_graph_from_local_disk(cls, root_directory: str): """Generates a Graph object by pulling data from disk at root_directory. Structure: | <root_directory parameter> |---- metadata.json |---- graph/ |-------- nodes.json |-------- edges.json |-------- policies.json |-------- groups.json Loads metadata, then policies, then groups, then nodes, then edges. Specific ordering is for handling different dependencies when generating the objects. Validates, using metadata, that the version of Principal Mapper that created the graph is the same major/minor version of the current version of Principal Mapper. Raises a ValueError otherwise. """ rootpath = root_directory if not os.path.exists(rootpath): raise ValueError('Did not find file at: {}'.format(rootpath)) graphdir = os.path.join(rootpath, 'graph') metadatafilepath = os.path.join(rootpath, 'metadata.json') nodesfilepath = os.path.join(graphdir, 'nodes.json') edgesfilepath = os.path.join(graphdir, 'edges.json') policiesfilepath = os.path.join(graphdir, 'policies.json') groupsfilepath = os.path.join(graphdir, 'groups.json') with open(metadatafilepath) as f: metadata = json.load(f) current_pmapper_version = packaging.version.parse(principalmapper.__version__) loaded_graph_version = packaging.version.parse(metadata['pmapper_version']) if current_pmapper_version.release[0] != loaded_graph_version.release[0] or \ current_pmapper_version.release[1] != loaded_graph_version.release[1]: raise ValueError('Loaded Graph data was from a different version of Principal Mapper ({}), but the current ' 'version of Principal Mapper ({}) may not support it. Either update the stored Graph data ' 'and its metadata, or regraph the account.'.format(loaded_graph_version, current_pmapper_version)) policies = [] with open(policiesfilepath) as f: policies_file_contents = json.load(f) for policy in policies_file_contents: policies.append(Policy(arn=policy['arn'], name=policy['name'], policy_doc=policy['policy_doc'])) with open(groupsfilepath) as f: unresolved_groups = json.load(f) groups = [] for group in unresolved_groups: # dig through string list of attached policies to match up with policy objects with matching ARNs group_policies = [] for policy_ref in group['attached_policies']: for policy in policies: if policy_ref['arn'] == policy.arn and policy_ref['name'] == policy.name: group_policies.append(policy) break groups.append(Group(arn=group['arn'], attached_policies=group_policies)) with open(nodesfilepath) as f: unresolved_nodes = json.load(f) nodes = [] for node in unresolved_nodes: # dig through string list of groups and policies to match up with group and policy objects node_policies = [] group_memberships = [] for policy_ref in node['attached_policies']: for policy in policies: if policy_ref['arn'] == policy.arn and policy_ref['name'] == policy.name: node_policies.append(policy) break for group in groups: if group.arn in node['group_memberships']: group_memberships.append(group) break nodes.append(Node(arn=node['arn'], id_value=node['id_value'], attached_policies=node_policies, group_memberships=group_memberships, trust_policy=node['trust_policy'], instance_profile=node['instance_profile'], num_access_keys=node['access_keys'], active_password=node['active_password'], is_admin=node['is_admin'])) with open(edgesfilepath) as f: unresolved_edges = json.load(f) edges = [] for edge in unresolved_edges: # dig through nodes to find matching ARNs source = None destination = None for node in nodes: if source is None and node.arn == edge['source']: source = node if destination is None and node.arn == edge['destination']: destination = node if source is not None and destination is not None: break edges.append(Edge(source=source, destination=destination, reason=edge['reason'])) return Graph(nodes=nodes, edges=edges, policies=policies, groups=groups, metadata=metadata)
def test_service_linked_role_avoids_scp_restriction(self): principal = Node( 'arn:aws:iam::000000000000:role/AWSServiceRoleForSupport', 'AROAASDF', [ Policy( 'arn:aws:iam::000000000000:role/AWSServiceRoleForS3Support', 'inline-1', { 'Version': '2012-10-17', 'Statement': [{ 'Effect': 'Allow', 'Action': 's3:*', 'Resource': '*' }] }) ], None, { 'Version': '2012-10-17', 'Statement': [{ 'Effect': 'Allow', 'Action': 'sts:AssumeRole', 'Principal': { 'Service': 's3support.amazonaws.com' } }] }, None, 0, False, False, None, False, None) # SCP list of lists, this would be akin to an account in the root OU with the S3 service denied scp_collection = [[{ "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": "*", "Resource": "*" }] }], [{ "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": "*", "Resource": "*" }] }, { "Version": "2012-10-17", "Statement": [{ "Effect": "Deny", "Action": ["s3:*"], "Resource": "*", "Sid": "Statement1" }] }]] self.assertTrue( local_check_authorization_full(principal, 's3:CreateBucket', 'arn:aws:s3:::fakebucket', {}, None, None, scp_collection, None), 'AWSServiceRoleFor... check failed, this role should have access DESPITE the SCPs' ) self.assertFalse( local_check_authorization_full(principal, 'ec2:RunInstances', '*', {}, None, None, scp_collection, None))
def build_graph_with_one_admin() -> Graph: """Constructs and returns a Graph object with one node that is an admin""" admin_user_arn = 'arn:aws:iam::000000000000:user/admin' policy = Policy(admin_user_arn, 'InlineAdminPolicy', _get_admin_policy()) node = Node(admin_user_arn, 'AIDA00000000000000000', [policy], [], None, None, 1, True, True) return Graph([node], [], [policy], [], _get_default_metadata())