def test_admin_verified_for_group_member(self):
        admin_policy = Policy(
            'arn:aws:iam::000000000000:group/admins', 'inline_admin', {
                'Version': '2012-10-17',
                'Statement': [{
                    'Effect': 'Allow',
                    'Action': '*',
                    'Resource': '*'
                }]
            })

        admin_group = Group('arn:aws:iam::000000000000:group/admins',
                            [admin_policy])
        not_admin_group = Group('arn:aws:iam::000000000000:group/losers', [])

        new_node_1 = Node('arn:aws:iam::000000000000:user/node_1', 'id1', [],
                          [admin_group], None, None, 1, False, False, None,
                          False, None)
        new_node_2 = Node('arn:aws:iam::000000000000:user/node_2', 'id2', [],
                          [not_admin_group], None, None, 1, False, False, None,
                          False, None)

        update_admin_status([new_node_1, new_node_2])

        self.assertTrue(new_node_1.is_admin,
                        'Member of admin group should be marked as an admin')
        self.assertFalse(
            new_node_2.is_admin,
            'Member of non-admin group should not be marked as an admin')
示例#2
0
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))

    # user with S3 access, path in user's ARN
    nodes.append(Node(common_iam_prefix + 'user/somepath/some_other_jumpuser', 'AIDA00000000000000007', [jump_policy],
                      [], None, None, 1, True, False))

    # role with S3 access, path in role's ARN
    nodes.append(Node(common_iam_prefix + 'role/somepath/somerole', 'AIDA00000000000000008', [s3_full_access_policy],
                      [], alt_root_trusted_policy_doc, None, 0, False, False))

    # edges to add
    edges = obtain_edges(None, checker_map.keys(), nodes, sys.stdout, True)

    return Graph(nodes, edges, policies, [], _get_default_metadata())
示例#3
0
def _get_admin_reason(node: Node) -> List[str]:
    """Return a list of reasons why this given node is an admin."""

    result = []
    logger.debug("Checking if {} is an admin".format(node.searchable_name()))
    node_type = arns.get_resource(node.arn).split('/')[0]

    # check if node can modify its own inline policies
    if node_type == 'user':
        action = 'iam:PutUserPolicy'
    else:  # node_type == 'role'
        action = 'iam:PutRolePolicy'
    if query_interface.local_check_authorization_handling_mfa(node, action, node.arn, {})[0]:
        result.append('Can call {} to add/update their own inline policies'.format(action))

    # check if node can attach the AdministratorAccess policy to itself
    if node_type == 'user':
        action = 'iam:AttachUserPolicy'
    else:
        action = 'iam:AttachRolePolicy'
    condition_keys = {'iam:PolicyARN': 'arn:aws:iam::aws:policy/AdministratorAccess'}
    if query_interface.local_check_authorization_handling_mfa(node, action, node.arn, condition_keys)[0]:
        result.append('Can call {} to attach the AdministratorAccess policy to itself'.format(action))

    # check if node can create a role and attach the AdministratorAccess policy or an inline policy
    if query_interface.local_check_authorization_handling_mfa(node, 'iam:CreateRole', '*', {})[0]:
        if query_interface.local_check_authorization_handling_mfa(node, 'iam:AttachRolePolicy', '*',
                                                                  condition_keys)[0]:
            result.append('Can create an IAM Role (iam:CreateRole) and attach the AdministratorAccess policy to it (iam:AttachRolePolicy)'.format(action))
        if query_interface.local_check_authorization_handling_mfa(node, 'iam:PutRolePolicy', '*', condition_keys)[0]:
            result.append('Can create an IAM Role (iam:CreateRole) and create an inline policy for it (iam:PutRolePolicy)'.format(action))

    # check if node can update an attached customer-managed policy (assumes SetAsDefault is set to True)
    for attached_policy in node.attached_policies:
        if attached_policy.arn != node.arn and ':aws:policy/' not in attached_policy.arn:
            if query_interface.local_check_authorization_handling_mfa(node, 'iam:CreatePolicyVersion',
                                                                      attached_policy.arn, {})[0]:
                result.append('Can modify the attached managed policy {} (iam:CreatePolicyVersion)'.format(attached_policy.arn))
                break  # reduce output

    # check if node is a user, and if it can attach or modify any of its groups's policies
    if node_type == 'user':
        for group in node.group_memberships:
            group_name = group.arn.split('/')[-1]

            if query_interface.local_check_authorization_handling_mfa(node, 'iam:PutGroupPolicy', group.arn, {})[0]:
                result.append('Can add/update an inline policy for the group {} (iam:PutGroupPolicy)'.format(group_name))

            if query_interface.local_check_authorization_handling_mfa(node, 'iam:AttachGroupPolicy', group.arn,
                                                                      condition_keys)[0]:
                result.append('Can attach the AdministratorAccess policy to the group {} (iam:AttachGroupPolicy)'.format(group_name))

            for attached_policy in group.attached_policies:
                if attached_policy.arn != group.arn and ':aws:policy/' not in attached_policy.arn:
                    if query_interface.local_check_authorization_handling_mfa(node, 'iam:CreatePolicyVersion',
                                                                              attached_policy.arn, {})[0]:
                        result.append('Can update the managed policy {} that is attached to the group {} (iam:CreatePolicyVersion)'.format(attached_policy.arn, group_name))
                        break  # reduce output

    return result
示例#4
0
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, None, False, None)
    return Graph([node], [], [policy], [], _get_default_metadata())
示例#5
0
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, None, False, None)
    return result
    def test_admin_verified_by_inline_policies(self):
        admin_policy = Policy(
            'arn:aws:iam::000000000000:user/user_1', 'inline_admin', {
                'Version': '2012-10-17',
                'Statement': [{
                    'Effect': 'Allow',
                    'Action': '*',
                    'Resource': '*'
                }]
            })

        not_admin_policy = Policy(
            'arn:aws:iam::000000000000:user/user_2', 'inline_not_admin', {
                'Version':
                '2012-10-17',
                'Statement': [{
                    'Effect': 'Allow',
                    'Action': '*',
                    'Resource': '*'
                }, {
                    'Effect': 'Deny',
                    'Action': '*',
                    'Resource': '*'
                }]
            })

        new_node_1 = Node('arn:aws:iam::000000000000:user/user_1', 'id1',
                          [admin_policy], [], None, None, 1, False, False,
                          None, False, None)
        new_node_2 = Node('arn:aws:iam::000000000000:user/user_2', 'id2',
                          [not_admin_policy], [], None, None, 1, False, False,
                          None, False, None)

        update_admin_status([new_node_1, new_node_2])

        self.assertTrue(new_node_1.is_admin,
                        'User with admin policy should be marked as an admin')
        self.assertFalse(
            new_node_2.is_admin,
            'User with non-admin policy should not be marked as an admin')
示例#7
0
def get_edges_interaccount(source_graph: Graph, inter_account_edges: List[Edge], node: Node, ignored_nodes: List[Node]) -> List[Edge]:
    """Given a Node, the Graph it belongs to, a list of inter-account Edges, and a list of Nodes to skip, this returns
    any Edges where the Node is the source element as long as the destination element isn't included in the skipped Nodes.

    If the given node is an admin, those Edge objects get generated and returned.
    """

    result = []

    for outbound_edge in node.get_outbound_edges(source_graph):
        if outbound_edge.destination not in ignored_nodes:
            result.append(outbound_edge)

    for inter_account_edge in inter_account_edges:
        if inter_account_edge.source == node and inter_account_edge.destination not in ignored_nodes:
            result.append(inter_account_edge)

    return result
示例#8
0
def get_unfilled_nodes(iamclient,
                       output: io.StringIO = os.devnull,
                       debug=False) -> List[Node]:
    """Using an IAM.Client object, return a list of Node object for each IAM user and role in an account.

    Does not set Group or Policy objects. Those have to be filled in later.

    Writes high-level information on progress to the output file
    """
    result = []
    # Get users, paginating results, still need to handle policies + group memberships + is_admin
    output.write("Obtaining IAM users in account\n")
    user_paginator = iamclient.get_paginator('list_users')
    for page in user_paginator.paginate(PaginationConfig={'PageSize': 25}):
        dprint(debug, 'list_users page: {}'.format(page))
        for user in page['Users']:
            result.append(
                Node(arn=user['Arn'],
                     id_value=user['UserId'],
                     attached_policies=[],
                     group_memberships=[],
                     trust_policy=None,
                     instance_profile=None,
                     num_access_keys=0,
                     active_password='******' in user,
                     is_admin=False))
            dprint(debug, 'Adding Node for user ' + user['Arn'])

    # Get roles, paginating results, still need to handle policies + is_admin
    output.write("Obtaining IAM roles in account\n")
    role_paginator = iamclient.get_paginator('list_roles')
    for page in role_paginator.paginate(PaginationConfig={'PageSize': 25}):
        dprint(debug, 'list_roles page: {}'.format(page))
        for role in page['Roles']:
            result.append(
                Node(arn=role['Arn'],
                     id_value=role['RoleId'],
                     attached_policies=[],
                     group_memberships=[],
                     trust_policy=role['AssumeRolePolicyDocument'],
                     instance_profile=None,
                     num_access_keys=0,
                     active_password=False,
                     is_admin=False))

    # Get instance profiles, paginating results, and attach to roles as appropriate
    output.write("Obtaining EC2 instance profiles in account\n")
    ip_paginator = iamclient.get_paginator('list_instance_profiles')
    for page in ip_paginator.paginate(PaginationConfig={'PageSize': 25}):
        dprint(debug, 'list_instance_profiles page: {}'.format(page))
        for iprofile in page['InstanceProfiles']:
            iprofile_arn = iprofile['Arn']
            role_arns = []
            for role in iprofile['Roles']:
                role_arns.append(role['Arn'])
            for node in result:
                if ':role/' in node.arn and node.arn in role_arns:
                    node.instance_profile = iprofile_arn

    # Handle access keys
    output.write("Obtaining Access Keys data for IAM users\n")
    for node in result:
        if arns.get_resource(node.arn).startswith('user/'):
            # Grab access-key count and update node
            user_name = arns.get_resource(node.arn)[5:]
            if '/' in user_name:
                user_name = user_name.split('/')[-1]
                dprint(debug,
                       'removed path from username {}'.format(user_name))
            access_keys_data = iamclient.list_access_keys(UserName=user_name)
            num_access_keys = 0
            for access_key in access_keys_data['AccessKeyMetadata']:
                if access_key['Status'] == 'Active':
                    num_access_keys += 1
            node.access_keys = num_access_keys
            dprint(
                debug, 'Access Key Count for {}: {} {}'.format(
                    user_name, len(access_keys_data['AccessKeyMetadata']),
                    num_access_keys))

    return result
示例#9
0
def get_edges_with_node_source(graph: Graph, node: Node, ignored_nodes: List[Node]) -> List[Edge]:
    """Returns a list of nodes that are the destination of edges from the given graph where source of the edge is the
    passed node.
    """
    return [x for x in node.get_outbound_edges(graph) if x.source not in ignored_nodes]
示例#10
0
def get_nodes_groups_and_policies(iamclient) -> dict:
    """Using an IAM.Client object, return a dictionary containing nodes, groups, and policies to be
    added to a Graph object. Admin status for the nodes are not updated.

    Writes high-level information on progress to the output stream.
    """
    logger.info('Obtaining IAM Users/Roles/Groups/Policies in the account.')
    result_paginator = iamclient.get_paginator(
        'get_account_authorization_details')
    user_results = []
    group_results = []
    role_results = []
    policy_results = []
    for page in result_paginator.paginate():
        user_results += page['UserDetailList']
        group_results += page['GroupDetailList']
        role_results += page['RoleDetailList']
        policy_results += page['Policies']

    logger.info(
        'Sorting users, roles, groups, policies, and their relationships.')

    # this is the result we return: dictionary with nodes/groups/users all filled out
    result = {'nodes': [], 'groups': [], 'policies': []}

    for p in policy_results:
        # go through each policy and update policy_results
        doc = [
            x['Document'] for x in p['PolicyVersionList']
            if x['IsDefaultVersion']
        ][0]
        result['policies'].append(Policy(p['Arn'], p['PolicyName'], doc))

    for g in group_results:
        # go through all inline policies and update policy_results
        group_policies = []
        if 'GroupPolicyList' in g:  # have to key-check these
            for p in g['GroupPolicyList']:
                group_policies.append(
                    Policy(
                        g['Arn'],  # inline policies get the same Arn as their principal
                        p['PolicyName'],
                        p['PolicyDocument']))
            result[
                'policies'] += group_policies  # this is just adding the inline policies for the group

        for p in g['AttachedManagedPolicies']:
            group_policies.append(
                _get_policy_by_arn_or_raise(p['PolicyArn'],
                                            result['policies']))

        result['groups'].append(Group(g['Arn'], group_policies))

    for u in user_results:
        # go through all inline policies and update policy_results
        user_policies = []
        if 'UserPolicyList' in u:  # have to key-check these
            for p in u['UserPolicyList']:
                user_policies.append(
                    Policy(
                        u['Arn'],  # inline policies inherit the Arn of their principal for the purposes of tracking
                        p['PolicyName'],
                        p['PolicyDocument']))
            result['policies'] += user_policies

        for p in u['AttachedManagedPolicies']:
            user_policies.append(
                _get_policy_by_arn_or_raise(p['PolicyArn'],
                                            result['policies']))

        if 'PermissionsBoundary' in u:
            boundary_policy = _get_policy_by_arn_or_raise(
                u['PermissionsBoundary']['PermissionsBoundaryArn'],
                result['policies'])
        else:
            boundary_policy = None

        group_list = []
        for group_name in u['GroupList']:
            for group in result['groups']:
                if arns.get_resource(group.arn).split('/')[-1] == group_name:
                    group_list.append(group)
                    break

        _tags = {}
        if 'Tags' in u:
            for tag in u['Tags']:
                _tags[tag['Key']] = tag['Value']

        # still need to figure out access keys
        result['nodes'].append(
            Node(u['Arn'], u['UserId'], user_policies, group_list, None, None,
                 0, 'PasswordLastUsed' in u, False, boundary_policy, False,
                 _tags))

    for r in role_results:
        # go through all inline policies and update policy_results
        role_policies = []
        for p in r['RolePolicyList']:
            role_policies.append(
                Policy(
                    r['Arn'],  # inline policies inherit the Arn of their principal for the purposes of tracking
                    p['PolicyName'],
                    p['PolicyDocument']))
        result['policies'] += role_policies

        for p in r['AttachedManagedPolicies']:
            role_policies.append(
                _get_policy_by_arn_or_raise(p['PolicyArn'],
                                            result['policies']))

        _tags = {}
        if 'Tags' in r:
            for tag in r['Tags']:
                _tags[tag['Key']] = tag['Value']

        result['nodes'].append(
            Node(r['Arn'], r['RoleId'], role_policies, None,
                 r['AssumeRolePolicyDocument'],
                 [x['Arn'] for x in r['InstanceProfileList']], 0, False, False,
                 None, False, _tags))

    logger.info("Obtaining Access Keys data for IAM users")
    for node in result['nodes']:
        if arns.get_resource(node.arn).startswith('user/'):
            # Grab access-key count and update node
            user_name = arns.get_resource(node.arn)[5:]
            if '/' in user_name:
                user_name = user_name.split('/')[-1]
            access_keys_data = iamclient.list_access_keys(UserName=user_name)
            node.access_keys = len(access_keys_data['AccessKeyMetadata'])
            # logger.debug('Access Key Count for {}: {}'.format(user_name, len(access_keys_data['AccessKeyMetadata'])))

    logger.info('Gathering MFA virtual device information')
    mfa_paginator = iamclient.get_paginator('list_virtual_mfa_devices')
    for page in mfa_paginator.paginate(AssignmentStatus='Assigned'):
        for device in page['VirtualMFADevices']:
            user_arn = device['User']['Arn']
            logger.debug('Found virtual MFA device for {}'.format(user_arn))
            for node in result['nodes']:
                if node.arn == user_arn:
                    node.has_mfa = True
                    break

    logger.info('Gathering MFA physical device information')
    for node in result['nodes']:
        node_resource_name = arns.get_resource(node.arn)
        if node_resource_name.startswith('user/'):
            user_name = node_resource_name.split('/')[-1]
            mfa_devices_response = iamclient.list_mfa_devices(
                UserName=user_name)
            if len(mfa_devices_response['MFADevices']) > 0:
                node.has_mfa = True

    return result
示例#11
0
def get_unfilled_nodes(iamclient) -> List[Node]:
    """Using an IAM.Client object, return a list of Node object for each IAM user and role in an account.

    Does not set Group or Policy objects, does not set permissions boundary attr. Those have to be filled in later.

    Writes high-level information on progress to the output file
    """
    result = []
    # Get users, paginating results, still need to handle policies + group memberships + is_admin
    logger.info("Obtaining IAM users in account")
    user_paginator = iamclient.get_paginator('list_users')
    for page in user_paginator.paginate(PaginationConfig={'PageSize': 25}):
        logger.debug('list_users page: {}'.format(page))
        for user in page['Users']:
            # grab permission boundary ARN if applicable
            # TODO: iam:ListUsers does not return boundary information. may need to wait for a fix.
            if 'PermissionsBoundary' in user:
                _pb = user['PermissionsBoundary']['PermissionsBoundaryArn']
            else:
                _pb = None
            result.append(
                Node(
                    arn=user['Arn'],
                    id_value=user['UserId'],
                    attached_policies=[],
                    group_memberships=[],
                    trust_policy=None,
                    instance_profile=None,
                    num_access_keys=0,
                    active_password='******' in user,
                    is_admin=False,
                    permissions_boundary=_pb,
                    has_mfa=False,
                    tags=None  # TODO: fix tags for old user-gathering method
                ))
            logger.debug('Adding Node for user ' + user['Arn'])

    # Get roles, paginating results, still need to handle policies + is_admin
    logger.info("Obtaining IAM roles in account")
    role_paginator = iamclient.get_paginator('list_roles')
    for page in role_paginator.paginate(PaginationConfig={'PageSize': 25}):
        logger.debug('list_roles page: {}'.format(page))
        for role in page['Roles']:
            # grab permission boundary ARN if applicable
            if 'PermissionsBoundary' in role:
                _pb = role['PermissionsBoundary']['PermissionsBoundaryArn']
            else:
                _pb = None
            result.append(
                Node(
                    arn=role['Arn'],
                    id_value=role['RoleId'],
                    attached_policies=[],
                    group_memberships=[],
                    trust_policy=role['AssumeRolePolicyDocument'],
                    instance_profile=None,
                    num_access_keys=0,
                    active_password=False,
                    is_admin=False,
                    permissions_boundary=_pb,
                    has_mfa=False,
                    tags=None  # TODO: fix tags for old role-gathering method
                ))

    # Get instance profiles, paginating results, and attach to roles as appropriate
    logger.info("Obtaining EC2 instance profiles in account")
    ip_paginator = iamclient.get_paginator('list_instance_profiles')
    for page in ip_paginator.paginate(PaginationConfig={'PageSize': 25}):
        logger.debug('list_instance_profiles page: {}'.format(page))
        for iprofile in page['InstanceProfiles']:
            iprofile_arn = iprofile['Arn']
            role_arns = []
            for role in iprofile['Roles']:
                role_arns.append(role['Arn'])
            for node in result:
                if ':role/' in node.arn and node.arn in role_arns:
                    node.instance_profile = iprofile_arn

    # Handle access keys
    logger.info("Obtaining Access Keys data for IAM users")
    for node in result:
        if arns.get_resource(node.arn).startswith('user/'):
            # Grab access-key count and update node
            user_name = arns.get_resource(node.arn)[5:]
            if '/' in user_name:
                user_name = user_name.split('/')[-1]
                logger.debug('removed path from username {}'.format(user_name))
            access_keys_data = iamclient.list_access_keys(UserName=user_name)
            node.access_keys = len(access_keys_data['AccessKeyMetadata'])
            logger.debug('Access Key Count for {}: {}'.format(
                user_name, len(access_keys_data['AccessKeyMetadata'])))

    return result
def main():
    """Body of the script."""

    # handle arguments
    parser = argparse.ArgumentParser()
    parser.add_argument('--account',
                        default='000000000000',
                        help='The account ID to assign the simulated Graph')

    file_arg_group = parser.add_mutually_exclusive_group(required=True)
    file_arg_group.add_argument(
        '--json', help='The CloudFormation JSON template file to read from')
    file_arg_group.add_argument(
        '--yaml', help='The CloudFormation YAML template file to read from')
    parsed_args = parser.parse_args()

    # Parse file
    if parsed_args.json:
        print('[+] Loading file {}'.format(parsed_args.json))
        fd = open(parsed_args.json)
        data = json.load(fd)
    else:
        print('[+] Loading file {}'.format(parsed_args.yaml))
        fd = open(parsed_args.yaml)
        data = yaml.safe_load(fd)
    fd.close()

    # Create metadata
    metadata = {
        'account_id': parsed_args.account,
        'pmapper_version': principalmapper.__version__
    }

    print('[+] Building a Graph object for an account with ID {}'.format(
        metadata['account_id']))

    if 'Resources' not in data:
        print('[!] Missing required template element "Resources"')
        return -1

    # Create space to stash all the data we generate
    groups = []
    policies = []
    nodes = []

    # Handle data from IAM
    iam_id_counter = 0
    template_resources = data['Resources']
    # TODO: Handle policies to start
    # TODO: Handle groups
    for logical_id, contents in template_resources.items():
        # Get data on IAM Users and Roles
        if contents['Type'] == 'AWS::IAM::User':
            properties = contents['Properties']
            node_path = '/' if 'Path' not in properties else properties['Path']
            node_arn = 'arn:aws:iam::{}:user{}'.format(
                metadata['account_id'], '{}{}'.format(node_path,
                                                      properties['UserName']))
            print('[+] Adding user {}'.format(node_arn))
            nodes.append(
                Node(
                    node_arn,
                    _generate_iam_id('user', iam_id_counter),
                    [],  # TODO: add policy handling
                    [],  # TODO: add group handling
                    None,
                    None,
                    0,  # TODO: fix access keys stuff
                    False,  # TODO: fix password handling
                    False,  # TODO: implement admin checks
                    None,  # TODO: handle permission boundaries
                    False,  # TODO: handle MFA stuff in CF template reading
                    {}  # TODO: add tag handling
                ))
            iam_id_counter += 1

        elif contents['Type'] == 'AWS::IAM::Role':
            properties = contents['Properties']
            # TODO: finish out roles

    # TODO: update access keys for users

    # Sort out administrative principals
    gathering.update_admin_status(nodes)

    # Create Edges
    edges = iam_edges.generate_edges_locally(
        nodes) + sts_edges.generate_edges_locally(nodes)

    # Create our graph and finish
    graph = Graph(nodes, edges, policies, groups, metadata)
    graph_actions.print_graph_data(graph)