def test_get_actions_from_statement(self):
        statement = {
            "Action": "ec2:thispermissiondoesntexist",
            "NotAction": list(all_permissions),
            "Resource": "*",
            "Effect": "Allow",
        }
        expected_result = {"ec2:thispermissiondoesntexist"}
        result = get_actions_from_statement(statement)
        self.assertEqual(result, expected_result)
        result = get_actions_from_statement(dict(NotAction="abc"))
        self.assertSetEqual(result, set(all_permissions))

        statement = {
            "Action": (
                "ec2:updatesecuritygroupruledescriptionsegress",
                "ec2:cancelcapacityreservation",
            ),
            "NotAction": tuple(),
            "Resource": "*",
            "Effect": "Allow",
        }
        result = get_actions_from_statement(statement)
        self.assertSetEqual(
            result,
            {
                "ec2:updatesecuritygroupruledescriptionsegress",
                "ec2:cancelcapacityreservation",
            },
        )
 def test_get_actions_from_statement(self):
     statement = {
         "Action": "ec2:thispermissiondoesntexist",
         "NotAction": list(all_permissions),
         "Resource": "*",
         "Effect": "Allow"
     }
     expected_result = {"ec2:thispermissiondoesntexist"}
     result = get_actions_from_statement(statement)
     self.assertEqual(result, expected_result)
     get_actions_from_statement(dict(NotAction="abc"))
Ejemplo n.º 3
0
def get_permissions_in_policy(
        policy_dict: Dict[str, Any],
        warn_unknown_perms: bool = False) -> Tuple[Set[str], Set[str]]:
    """
    Given a set of policies for a role, return a set of all allowed permissions

    Args:
        policy_dict
        warn_unknown_perms

    Returns
        tuple
        set - all permissions allowed by the policies
        set - all permisisons allowed by the policies not marked with STATEMENT_SKIP_SID
    """
    total_permissions: Set[str] = set()
    eligible_permissions: Set[str] = set()

    for policy_name, policy in list(policy_dict.items()):
        policy = expand_policy(policy=policy,
                               expand_deny=False) if policy else {}
        for statement in policy.get("Statement"):
            if statement["Effect"].lower() == "allow":
                statement_actions = get_actions_from_statement(statement)
                total_permissions = total_permissions.union(statement_actions)
                if not ("Sid" in statement
                        and statement["Sid"].startswith(STATEMENT_SKIP_SID)):
                    # No Sid
                    # Sid exists, but doesn't start with STATEMENT_SKIP_SID
                    eligible_permissions = eligible_permissions.union(
                        statement_actions)

    weird_permissions = total_permissions.difference(all_permissions)
    if weird_permissions and warn_unknown_perms:
        logger.warning(
            "Unknown permissions found: {}".format(weird_permissions))

    return total_permissions, eligible_permissions
Ejemplo n.º 4
0
 def actions_expanded(self):
     return set(get_actions_from_statement(self.statement))
Ejemplo n.º 5
0
def pass_condition(b, test, a):
    """
    Generic test function used by Scout
                                        .
    :param b:                           Value to be tested against
    :param test:                        Name of the test case to run
    :param a:                           Value to be tested

    :return:                            True of condition is met, False otherwise
    """

    # Return false by default
    result = False

    # Equality tests
    if test == 'equal':
        a = str(a)
        b = str(b)
        result = (a == b)
    elif test == 'notEqual':
        result = (not pass_condition(b, 'equal', a))

    # More/Less tests
    elif test == 'lessThan':
        result = (int(b) < int(a))
    elif test == 'lessOrEqual':
        result = (int(b) <= int(a))
    elif test == 'moreThan':
        result = (int(b) > int(a))
    elif test == 'moreOrEqual':
        result = (int(b) >= int(a))

    # Empty tests
    elif test == 'empty':
        result = ((type(b) == dict and b == {})
                  or (type(b) == list and b == [])
                  or (type(b) == list and b == [None]))
    elif test == 'notEmpty':
        result = (not pass_condition(b, 'empty', 'a'))
    elif test == 'null':
        result = ((b is None) or (type(b) == str and b == 'None'))
    elif test == 'notNull':
        result = (not pass_condition(b, 'null', a))

    # Boolean tests
    elif test == 'true':
        result = (str(b).lower() == 'true')
    elif test == 'notTrue' or test == 'false':
        result = (str(b).lower() == 'false')

    # Object length tests
    elif test == 'lengthLessThan':
        result = (len(b) < int(a))
    elif test == 'lengthMoreThan':
        result = (len(b) > int(a))
    elif test == 'lengthEqual':
        result = (len(b) == int(a))

    # Dictionary keys tests
    elif test == 'withKey':
        result = (a in b)
    elif test == 'withoutKey':
        result = a not in b

    # String test
    elif test == 'containString':
        if not type(b) == str:
            b = str(b)
        if not type(a) == str:
            a = str(a)
        result = a in b
    elif test == 'notContainString':
        if not type(b) == str:
            b = str(b)
        if not type(a) == str:
            a = str(a)
        result = a not in b

    # List tests
    elif test == 'containAtLeastOneOf':
        result = False
        if not type(b) == list:
            b = [b]
        if not type(a) == list:
            a = [a]
        for c in b:
            if type(c) != dict:
                c = str(c)
            if c in a:
                result = True
                break
    elif test == 'containAtLeastOneDifferentFrom':
        result = False
        if not type(b) == list:
            b = [b]
        if not type(a) == list:
            a = [a]
        for c in b:
            if c and c != '' and c not in a:
                result = True
                break
    elif test == 'containNoneOf':
        result = True
        if not type(b) == list:
            b = [b]
        if not type(a) == list:
            a = [a]
        for c in b:
            if c in a:
                result = False
                break
    elif test == 'containAtLeastOneMatching':
        result = False
        for item in b:
            if re.match(a, item):
                result = True
                break

    # Regex tests
    elif test == 'match':
        if type(a) != list:
            a = [a]
        b = str(b)
        for c in a:
            if re.match(c, b):
                result = True
                break
    elif test == 'notMatch':
        result = (not pass_condition(b, 'match', a))

    # Date tests
    elif test == 'priorToDate':
        b = dateutil.parser.parse(str(b)).replace(tzinfo=None)
        a = dateutil.parser.parse(str(a)).replace(tzinfo=None)
        result = (b < a)
    elif test == 'olderThan':
        age, threshold = __prepare_age_test(a, b)
        result = (age > threshold)
    elif test == 'newerThan':
        age, threshold = __prepare_age_test(a, b)
        result = (age < threshold)

    # CIDR tests
    elif test == 'inSubnets':
        result = False
        grant = netaddr.IPNetwork(b)
        if type(a) != list:
            a = [a]
        for c in a:
            known_subnet = netaddr.IPNetwork(c)
            if grant in known_subnet:
                result = True
                break
    elif test == 'notInSubnets':
        result = (not pass_condition(b, 'inSubnets', a))

    # Policy statement tests
    elif test == 'containAction':
        result = False
        if type(b) != dict:
            b = json.loads(b)
        statement_actions = get_actions_from_statement(b)
        rule_actions = _expand_wildcard_action(a)
        for action in rule_actions:
            if action.lower() in statement_actions:
                result = True
                break
    elif test == 'notContainAction':
        result = (not pass_condition(b, 'containAction', a))
    elif test == 'containAtLeastOneAction':
        result = False
        if type(b) != dict:
            b = json.loads(b)
        if type(a) != list:
            a = [a]
        actions = get_actions_from_statement(b)
        for c in a:
            if c.lower() in actions:
                result = True
                break

    # Policy principal tests
    elif test == 'isCrossAccount':
        result = False
        if type(b) != list:
            b = [b]
        for c in b:
            if type(c) == dict and 'AWS' in c:
                c = c['AWS']
            if c != a and not re.match(r'arn:aws:iam:.*?:%s:.*' % a, c):
                result = True
                break
    elif test == 'isSameAccount':
        result = False
        if type(b) != list:
            b = [b]
        for c in b:
            if c == a or re.match(r'arn:aws:iam:.*?:%s:.*' % a, c):
                result = True
                break

    # Unknown test case
    else:
        print_error('Error: unknown test case %s' % test)
        raise Exception

    return result
Ejemplo n.º 6
0
def get_repoed_policy(
        policies: Dict[str, Any],
        repoable_permissions: Set[str]) -> Tuple[Dict[str, Any], List[str]]:
    """
    This function contains the logic to rewrite the policy to remove any repoable permissions. To do so we:
      - Iterate over role policies
      - Iterate over policy statements
      - Skip Deny statements
      - Remove any actions that are in repoable_permissions
      - Remove any statements that now have zero actions
      - Remove any policies that now have zero statements

    Args:
        policies (dict): All of the inline policies as a dict with name and policy contents
        repoable_permissions (set): A set of all of the repoable permissions for policies

    Returns:
        dict: The rewritten set of all inline policies
        list: Any policies that are now empty as a result of the rewrites
    """
    # work with our own copy; don't mess with the CACHE copy.
    role_policies = copy.deepcopy(policies)

    empty_policies = []
    for policy_name, policy in list(role_policies.items()):
        # list of indexes in the policy that are empty
        empty_statements = []

        if type(policy["Statement"]) is dict:
            policy["Statement"] = [policy["Statement"]]

        for idx, statement in enumerate(policy["Statement"]):
            if statement["Effect"].lower() == "allow":
                if "Sid" in statement and statement["Sid"].startswith(
                        STATEMENT_SKIP_SID):
                    continue

                statement_actions = get_actions_from_statement(statement)
                new_actions = {
                    action
                    for action in statement_actions
                    if action not in repoable_permissions
                    and action.split(":")[0] not in repoable_permissions
                }

                if statement_actions == new_actions:
                    # No permissions are being taken away; let's not modify this statement at all.
                    continue

                # get_actions_from_statement has already inverted this so our new statement should be 'Action'
                if "NotAction" in statement:
                    del statement["NotAction"]

                # by putting this into a set, we lose order, which may be confusing to someone.
                statement["Action"] = sorted(list(new_actions))

                # mark empty statements to be removed
                if len(statement["Action"]) == 0:
                    empty_statements.append(idx)

        # do the actual removal of empty statements
        for idx in sorted(empty_statements, reverse=True):
            del policy["Statement"][idx]

        # mark empty policies to be removed
        if len(policy["Statement"]) == 0:
            empty_policies.append(policy_name)

    # do the actual removal of empty policies.
    for policy_name in empty_policies:
        del role_policies[policy_name]

    return role_policies, empty_policies
Ejemplo n.º 7
0
def getPolicyStatementDetails(statement):
    '''
    These are the different element of a Policy Statement

    i. Action / NotAction
    ii. Effect
    iii. Resource / NotResource
    iv. Sid
    v. Condition
    vi. Principal / NotPrincipal
    Link : https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html

    This function parses the policy statement and gives values for all possible elements of a policy in a standard key value format
    '''

    # Determining Actions
    try:
        statement_action = statement['Action']
        statement_action_key = "Action"
    except KeyError:
        statement_action = statement['NotAction']
        statement_action_key = "NotAction"

    # Stringifying and Replacing list and set characters helps in linearising the lists / sets
    # PolicyStatement Relationships contain actions as one of the properties
    # Hence sorting is important , as every time the order keeps changing when the string is split.
    # Sorting helps from creating duplicate relationships when the data is synced again
    statement_action = sorted(
        set(
            str(statement_action).replace("'", "").replace("{", "").replace(
                "}", "").replace("[", "").replace("]",
                                                  "").replace(" ",
                                                              "").split(",")))

    # Determining Resource
    try:
        statement_resource = statement['Resource']
        statement_resource_key = "Resource"

    except KeyError:
        try:
            statement_resource = statement['NotResource']
            statement_resource_key = "NotResource"
        # In case there is no Resource (AssumeRole Policies do not have Resource mentioned)
        except KeyError:
            statement_resource = set()
            statement_resource_key = ""
    # Stringifying and Replacing list and set characters helps in linearising the lists / sets
    # PolicyStatement Relationships contain resources as one of the properties
    # Hence sorting is important , as every time the order keeps changing when the string is split.
    # Sorting helps from creating duplicate relationships when the data is synced again

    if statement_resource != set():
        statement_resource = sorted(
            set(
                str(statement_resource).replace("'", "").replace(
                    "{", "").replace("}", "").replace("[", "").replace(
                        "]", "").replace(" ", "").split(",")))

    # Determining Effect
    statement_effect = statement['Effect']
    # Determining Principal

    # Principals are not part of every type of AWS Policy (Hence need for try and except)
    try:
        statement_principal = statement['Principal']
        statement_principal_key = "Principal"
    except KeyError:
        # In case of NotPrincipal
        try:
            statement_principal = statement['NotPrincipal']
            statement_principal_key = "NotPrincipal"

        # In case there is no principal (General AWS Policies do not have explicit mention of principals)
        except KeyError:
            statement_principal = set()
            statement_principal_key = ""

    if statement_principal:
        # In case of * as value for Principal
        if statement_principal == '*' or statement_principal == ['*']:
            # Sub Key should be AWS (Ref :https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_principal.html -> (Everyone (anonymous users))
            principal = OrderedDict()
            principal.__setitem__("AWS", ["*"])
            statement_principal = principal

        for key in statement_principal.keys():
            statement_principal[key] = sorted(
                set(
                    str(statement_principal[key]).replace("'", "").replace(
                        "{", "").replace("}", "").replace("[", "").replace(
                            "]", "").replace(" ", "").split(",")))

    try:
        if statement['Condition'] == {}:
            statement_condition = ""
        else:
            # To change it to return to non-string (In case of evaluation)
            # As of now , its been stringified as it is just used t display as
            # a property of the statement relation and not actually planned to
            # evaluate
            statement_condition = str(json.dumps(statement['Condition']))

    except KeyError:
        statement_condition = ""
    try:
        statement_sid = statement['Sid']
    except KeyError:
        statement_sid = ""

    # Policy Universe's get_actions_from_statement works only in Action and not
    # NotAction scenario. Hence temporarily converting the Action key to NotAction
    # and expanding the Action's Wild cards

    temp = OrderedDict()
    not_action_flag = 0
    for key in statement.keys():
        if key == "Action":
            temp.__setitem__(key, statement_action)
        elif key == "NotAction":
            temp.__setitem__("Action", statement_action)
            not_action_flag = 1
        else:
            temp.__setitem__(key, statement[key])

    # statement_aaia_expanded_action variable stores the expanded actions (including inverted NotAction cases).

    statement_aaia_expanded_action = ""
    if not_action_flag == 0:
        statement_aaia_expanded_action = set(
            expander_minimizer.get_actions_from_statement(temp))
    elif not_action_flag == 1:
        # In case of NotAction all the mentioned actions will be inverted and added to statement_aaia_expanded_action
        statement_aaia_expanded_action = set(
            all_permissions.difference(
                expander_minimizer.get_actions_from_statement(temp)))

    statement_aaia_expanded_action = sorted(
        str(statement_aaia_expanded_action).replace("'", "").replace(
            "{",
            "").replace("}",
                        "").replace("[",
                                    "").replace("]",
                                                "").replace(" ",
                                                            "").split(","))

    statement_aaia_expanded_action = str(
        statement_aaia_expanded_action).replace("'", "").replace(
            "{", "").replace("}", "").replace("[",
                                              "").replace("]",
                                                          "").replace(" ", "")

    # ActionKey,ResourceKey,PrincipalKey determines whether it is Action/NotAction , Resource/NotResource and Principal/NotPrincipal respectively in the policy
    # wheras the Action,Resource,Policy in the below OrderedDict() returns actions,resources,principal respectively  as values
    # Example {"NotAction": "iam:*"} will be returned as
    # { "ActionKey" : "NotAction, "Action" : "iam:*"}

    # Hence one has to consider both ActionKey/ResourceKey/PrincipalKey along with Action/Resource/Principal
    # to evaluate the policy

    policy_statement_details = OrderedDict()
    policy_statement_details.__setitem__('Action', statement_action)
    policy_statement_details.__setitem__('ActionKey', statement_action_key)
    policy_statement_details.__setitem__('Aaia_ExpandedAction',
                                         statement_aaia_expanded_action)
    policy_statement_details.__setitem__('Effect', statement_effect)
    policy_statement_details.__setitem__('Resource', statement_resource)
    policy_statement_details.__setitem__('ResourceKey', statement_resource_key)
    policy_statement_details.__setitem__('Condition', statement_condition)
    policy_statement_details.__setitem__('Principal', statement_principal)
    policy_statement_details.__setitem__('PrincipalKey',
                                         statement_principal_key)
    policy_statement_details.__setitem__('Sid', statement_sid)

    return policy_statement_details