示例#1
0
    def __init__(self, statement, resource, resources):

        self.__statement = copy.deepcopy(statement)
        self.__resources = resources

        self.__str__ = lambda: str(statement)

        assert isinstance(self.__statement, dict)

        keys = [k for k in self.__statement.keys()]

        if not ("Effect" in keys
                and any([k in keys for k in ["Action", "NotAction"]])):

            console.critical(f"Statement: {self.__statement} "
                             "is missing required key")

        if "NotPrincipal" in keys:
            console.warn("'NotPrincipal' support hasn't been implemented."
                         f"Statement: {self.__statement} will be ignored.")

        elif "Principal" not in keys:
            self.__statement["Principal"] = {"AWS": [str(resource)]}
            self._principals = Elements([resource])

        if (not any([k in keys for k in ["Resource", "NotResource"]])
                and resource is not None):
            self.__statement["Resource"] = [str(resource)]
            self._resources = Elements([resource])
示例#2
0
    def principals(self):

        principals = Elements()
        for _, policy in self.documents.items():
            results = policy.principals()
            principals.update(results)
        return principals
示例#3
0
 def resolve(self):
     actions = Elements()
     for i in range(len(self.statements)):
         results = self.statements[i].resolve()
         # [r.set("Statement", i) for r in results]
         actions.extend(r for r in results if r not in actions)
     return actions
示例#4
0
    def __init__(self, session, resources=None, db="default.db"):

        super().__init__(session=session, default=False)

        self._db = db

        if resources is None:

            self.client = self.session.client("iam")

            print(
                "[+] Ingesting AWS::Iam::Users, AWS::Iam::Roles, ",
                "AWS::Iam::Groups, AWS::Iam::Policies, "
                "AWS::Iam::InstanceProfiles from IAM")

            self += self.get_account_authorization_details()
            self.get_login_profile()
            self.list_access_keys()

        elif len(resources) > 0:
            self += resources

        # Set IAM entities
        self.entities = Elements(
            self.get('AWS::Iam::User') +
            self.get('AWS::Iam::Role')).get("Resource")

        # Set Account
        for a in set([e.account() for e in self.entities.get("Resource")]):
            self.root = Resource(properties={
                "Name": a,
                "Arn": "arn:aws:iam::%s:root" % a
            },
                                 labels=["Resource", "AWS::Iam::Root"])
            break
示例#5
0
    def principals(self):

        principals = Elements()

        for statement in self.statements:
            principals.update(statement.principals())

        return principals
示例#6
0
    def actions(self):

        actions = Elements()

        for statement in self.statements:
            actions.update(statement.actions())

        return actions
示例#7
0
    def resolve(self):

        actions = Elements()
        for _, policy in self.documents.items():
            results = policy.resolve()
            # [r.set("Policy", name) for r in results]
            actions.extend([r for r in results if r not in actions])
        return actions
示例#8
0
 def principals(self):
     principals = Elements()
     for i in range(len(self.statements)):
         principals.extend([
             p for p in self.statements[i].principals()
             if p not in principals
         ])
     return principals
示例#9
0
    def principals(self):

        principals = Elements()

        for policy in self.documents.values():
            principals.update(policy.principals())

        return principals
示例#10
0
    def __init__(self, statement: dict, resource: Element,
                 resources: Elements):

        # TODO: policy statements do not appear to strictly adhere to the JSON
        # format and may include duplicate keys. Duplicate keys will be ignored.

        self._resources = resources
        self._statement = statement
        self._resource = resource

        self._explicit_principals = None
        self._explicit_actions = None
        self._explicit_resources = None
        self._explicit_conditions = None
        self._explicit_resource_conditions = {}
        self._Actions = None

        try:

            if not isinstance(statement, dict):

                raise ValueError

            self._statement = statement

            keys = [k.replace("Not", "") for k in self._statement.keys()]

            if not all([k in keys for k in ["Effect", "Action"]]):
                raise ValueError("Malformed statement: Missing 'Effect'")

            if "Resource" not in keys and resource is not None:
                self._statement["Resource"] = str(self._resource)
                self._explicit_resources = Elements([self._resource])
                self._explicit_resource_conditions = {
                    self._resource.id(): [{}]
                }

            elif "Resource" is None:
                raise ValueError("Malformed statement: Missing 'Resource'")

            if "Condition" not in keys:
                self._explicit_conditions = {}
            else:
                self._explicit_conditions = self._statement["Condition"]

            # TODO: Not implemented
            if "NotPrincipal" in keys:
                print("[!] 'NotPrincipal' support has not yet been added. "
                      "\n\tThis entire statement will be ingnored (%s)." %
                      self._resource.id())
                self._explicit_principals = []

            elif "Principal" not in keys:
                self._explicit_principals = [self._resource]

        except:
            raise ValueError("Malformed statement: %s" % self._statement)
示例#11
0
    def __init__(self,
                 session,
                 resources=None,
                 db="default.db",
                 verbose=False,
                 only_types=[],
                 except_types=[],
                 only_arns=[],
                 except_arns=[]):

        self._db = db
        self._verbose = verbose
        self.account_id = "000000000000"

        if resources is not None:
            self += resources
            return

        super().__init__(session=session, default=False, verbose=verbose)

        self.client = self.session.client("iam")

        self.run = [
            r for r in self.run if (len(only_types) == 0 or r in only_types)
            and r not in except_types
        ]

        print("[*] Commencing {resources} ingestion\n".format(
            resources=', '.join([
                r if (i == 0 or i % 3 > 0) else f'\n{" " * 15}{r}'
                for i, r in enumerate(self.run)
            ])))

        self._print(
            "[*] Awaiting response to iam:GetAccountAuthorizationDetails "
            "(this can take a while)")
        self += self.get_account_authorization_details([], [])

        if "AWS::Iam::User" in self.run:

            self.get_login_profile()
            self.list_access_keys()

        # Set IAM entities
        self.entities = Elements(
            self.get('AWS::Iam::User') +
            self.get('AWS::Iam::Role')).get("Resource")

        # Set Account
        for a in set([e.account() for e in self.entities.get("Resource")]):
            self.account_id = a
            break

        self._print_stats()
示例#12
0
    def actions(self):

        actions = Elements()

        for document in self.documents.values():
            actions.update(document.actions())

        console.info(f"{self.__class__.__name__} {self.__resource} "
                     f"resolved to {len(actions)} Action(s)")

        return actions
示例#13
0
    def resolve(self):

        if self._Actions is not None:
            return self._Actions

        if self._explicit_actions is None:
            self._resolve_action_statement()
        if self._explicit_resources is None:
            self._resolve_resource_statement()
        if self._explicit_principals is None:
            self._resolve_principal_statement()

        actions = Elements()

        for action in self.actions():

            # Rewrite
            resources = Elements()
            for affected in ACTIONS[action]["Affects"]:
                resources.update(
                    Elements(self._explicit_resources.get(affected)))

            for resource in resources:

                # Action conditions comprise of resource level permission conditions
                # variants AND statement conditions

                condition = self._explicit_resource_conditions[resource.id()]

                condition = [{
                    **condition[i],
                    **self._explicit_conditions
                } for i in range(len(condition))]

                condition = json.dumps(condition) \
                    if len(condition[0]) > 0 else "[]"

                for principal in self._explicit_principals:
                    actions.add(
                        Action(properties={
                            "Name": action,
                            "Description": ACTIONS[action]["Description"],
                            "Effect": self._statement["Effect"],
                            "Access": ACTIONS[action]["Access"],
                            "Reference": ACTIONS[action]["Reference"],
                            "Condition": condition
                        },
                               source=principal,
                               target=resource))

        # Unset resource level permission conditions
        for resource in self._explicit_resources:
            resource.condition = []

        self._Actions = actions

        return self._Actions
示例#14
0
    def list_functions(self):

        if 'AWS::Lambda::Function' not in self.types:
            return

        functions = Elements()

        for function in [
                f for r in self.console.tasklist(
                    "Adding Functions",
                    iterables=self.client.get_paginator(
                        "list_functions").paginate(),
                    wait="Awaiting response to lambda:ListFunctions",
                    done="Added Functions") for f in r["Functions"]
        ]:

            function["Name"] = function["FunctionName"]
            function["Arn"] = function["FunctionArn"]
            del function["FunctionName"]
            del function["FunctionArn"]

            function = Resource(properties=function,
                                labels=["AWS::Lambda::Function"])

            self.add(function)
示例#15
0
    def list_functions(self):

        functions = Elements()
        self._print("[*] Listing functions (this can take a while)")
        for function in [
                f for r in self.client.get_paginator(
                    "list_functions").paginate() for f in r["Functions"]
        ]:

            function["Name"] = function["FunctionName"]
            function["Arn"] = function["FunctionArn"]
            del function["FunctionName"]
            del function["FunctionArn"]

            f = Resource(properties=function, labels=["AWS::Lambda::Function"])

            if f not in functions:
                self._print(f"[*] Adding {f}")
                functions.add(f)

        self.update(functions)
示例#16
0
    def _load_associations(self):

        if len(self.associates) == 0:
            return

        self._print(
            f"[*] Adding {self.__class__.__name__} associative relationships")

        edges = Elements()

        for resource in self.get("Resource"):

            references = {}
            label = [l for l in resource.labels() if l != "Resource"][0]

            # Find references to other resources in the form of a dictionary (refs)

            self._references(resource.properties(), references)

            # Create an edge, for all known associations (as defined by self.rels).

            for rel in [
                    r for r in self.associates
                    if r[0] == label or r[1] == label
            ]:

                i = 1 if label == rel[0] else 0

                # Get a list of foreign keys that we must be capable of referencing
                # in order to create an association

                fk = [
                    a for a in re.compile("{([A-Za-z]+)}").findall(
                        RESOURCES.definition(rel[i]))
                    if a not in ["Account", "Region"]
                ]

                if not all([k in references.keys() for k in fk]):
                    continue

                # TODO: Handle Types that make use of more than one
                # variable identifier

                if len(fk) != 1:
                    raise NotImplementedError

                fk = fk[0]

                for v in list(references[fk]):

                    # Find the first resource matching the reference

                    r = next((r
                              for r in self if re.compile(
                                  RESOURCES.definition(rel[i]).
                                  format(Account=self.account_id,
                                         Region=self.session.region_name,
                                         **{
                                             **{
                                                 x: list(y)[0]
                                                 for x, y in references.items(
                                                 ) if len(y) == 1
                                             },
                                             **{
                                                 fk: v
                                             }
                                         })).match(r.id()) is not None), None)

                    if r is None:
                        # print("Failed to match (%s: %s) against any resources" % (k, v))
                        # print("Its likely that the resource was missed during ingestion")
                        continue

                    # Delete the properties that are responsible for the edge's existence.

                    properties = self._extract_property_value(
                        resource.properties(), fk)

                    # Even though direction is irrelavent when dealing with Associative
                    # edges, neo4j is directed. We need to ensure the direction is kept
                    # in order to eliminate duplicate edges.

                    (source, target) = (resource, r) if i == 1 else (r,
                                                                     resource)

                    edge = Associative(properties={"Name": "Attached"},
                                       source=source,
                                       target=target)
                    opposite_edge = Associative(
                        properties={"Name": "Attached"},
                        source=target,
                        target=source)

                    if (edge not in self and opposite_edge
                            not in self) and edge not in edges:
                        edges.append(edge)

        self.extend(edges)
示例#17
0
class IAM(Ingestor):

    run = [
        "AWS::Iam::User", "AWS::Iam::Role", "AWS::Iam::Group",
        "AWS::Iam::Policy", "AWS::Iam::InstanceProfile"
    ]

    def __init__(self,
                 session,
                 resources=None,
                 db="default.db",
                 verbose=False,
                 only_types=[],
                 except_types=[],
                 only_arns=[],
                 except_arns=[]):

        self._db = db
        self._verbose = verbose
        self.account_id = "000000000000"

        if resources is not None:
            self += resources
            return

        super().__init__(session=session, default=False, verbose=verbose)

        self.client = self.session.client("iam")

        self.run = [
            r for r in self.run if (len(only_types) == 0 or r in only_types)
            and r not in except_types
        ]

        print("[*] Commencing {resources} ingestion\n".format(
            resources=', '.join([
                r if (i == 0 or i % 3 > 0) else f'\n{" " * 15}{r}'
                for i, r in enumerate(self.run)
            ])))

        self._print(
            "[*] Awaiting response to iam:GetAccountAuthorizationDetails "
            "(this can take a while)")
        self += self.get_account_authorization_details([], [])

        if "AWS::Iam::User" in self.run:

            self.get_login_profile()
            self.list_access_keys()

        # Set IAM entities
        self.entities = Elements(
            self.get('AWS::Iam::User') +
            self.get('AWS::Iam::Role')).get("Resource")

        # Set Account
        for a in set([e.account() for e in self.entities.get("Resource")]):
            self.account_id = a
            break

        self._print_stats()

    def get_account_authorization_details(self, only_arns, except_arns):

        elements = Elements()
        edges = {"Groups": [], "Policies": [], "InstanceProfiles": []}

        def get_aad_element(label, entry):

            properties = dict()
            for pk, pv in sorted(entry.items()):

                if pk.endswith("PolicyList"):
                    properties["Documents"] = [{
                        p["PolicyName"]: p["PolicyDocument"]
                        for p in pv
                    }]

                elif pk == "AssumeRolePolicyDocument":
                    properties["Trusts"] = pv

                elif pk in [
                        "GroupList", "InstanceProfileList",
                        "AttachedManagedPolicies"
                ]:
                    continue

                elif pk == "PolicyVersionList":

                    properties["Document"] = [{
                        "DefaultVersion":
                        [p for p in pv if p["IsDefaultVersion"]][0]["Document"]
                    }]

                else:
                    properties[pk.replace(label, "")] = pv

            element = Resource(properties=properties,
                               labels=["Resource", f"AWS::Iam::{label}"])

            if f"AWS::Iam::Group" in self.run and "GroupList" in entry.keys():

                edges["Groups"].extend([(element, g)
                                        for g in entry["GroupList"]])

            if f"AWS::Iam::InstanceProfile" in self.run \
                    and "InstanceProfileList" in entry.keys():

                edges["InstanceProfiles"].extend([
                    (get_aad_element("InstanceProfile", ip), element)
                    for ip in entry["InstanceProfileList"]
                ])

            if f"AWS::Iam::Policy" in self.run \
                    and "AttachedManagedPolicies" in entry.keys():
                edges["Policies"].extend([
                    (element, p["PolicyArn"])
                    for p in entry["AttachedManagedPolicies"]
                ])

            if (str(f"AWS::Iam::{label}") in self.run and
                (len(except_arns) == 0 or properties["Arn"] not in except_arns)
                    and (len(only_arns) == 0 or properties["Arn"] in only_arns)
                    and element not in elements):
                self._print(f"[*] Adding {element}")
                elements.append(element)

            return element

        account_authorization_details = [
            aad for aad in self.client.get_paginator(
                "get_account_authorization_details").paginate()
        ]

        account_authorization_details = [
            (label.replace("DetailList", "").replace("Policies",
                                                     "Policy"), entry)
            for aad in account_authorization_details
            for (label, v) in aad.items() if isinstance(v, list) for entry in v
        ]

        for label, entry in account_authorization_details:
            get_aad_element(label, entry)

        # Ensure edge nodes exist
        for k, v in edges.items():
            edges[k] = list(
                filter(lambda e: e[0] is not None and e[1] is not None, [
                    e if type(e[1]) == Resource else
                    (e[0],
                     next((t for t in elements
                           if (k == "Groups" and str(t).endswith(str(e[1])))
                           or str(t) == str(e[1])), None)) for e in v
                ]))

        # (:User|Group|Role)-[:TRANSITIVE{Attached}]->(:Policy)
        for (s, t) in edges["Policies"]:
            elements.append(
                Transitive(properties={"Name": "Attached"}, source=s,
                           target=t))

        # # (:User)-[:TRANSITIVE{MemberOf}]->(:Group)
        for (s, t) in edges["Groups"]:
            elements.append(
                Transitive(properties={"Name": "MemberOf"}, source=s,
                           target=t))

        # (:InstanceProfile)-[:TRANSITIVE{Attached}]->(:Role)
        for (s, t) in edges["InstanceProfiles"]:
            del s.properties()["Roles"]
            elements.append(
                Transitive(properties={"Name": "Attached"}, source=s,
                           target=t))

        return elements

    def get_login_profile(self):

        for user in self.get("AWS::Iam::User").get("Resource"):

            try:
                login_profile = self.client.get_login_profile(
                    UserName=user.get("Name"))["LoginProfile"]
                del login_profile["UserName"]
                user.set("LoginProfile", login_profile)
                self._print(
                    f"[+] Updated login profile information for {user}")

            except self.client.exceptions.NoSuchEntityException:
                pass

    def list_access_keys(self):

        for user in self.get("AWS::Iam::User").get("Resource"):

            try:
                access_keys = self.client.list_access_keys(
                    UserName=user.get("Name"))["AccessKeyMetadata"]

                for i in range(len(access_keys)):
                    k = access_keys[i]["AccessKeyId"]
                    del access_keys[i]["AccessKeyId"]
                    del access_keys[i]["UserName"]
                    access_keys[i] = {k: access_keys[i]}

                user.set("AccessKeys", access_keys)
                self._print(f"[+] Updated access key information for {user}")

            except self.client.exceptions.NoSuchEntityException:
                pass

    def post(self, skip_actions=False):
        if not skip_actions:
            self.resolve()
        self.transitive()
        return self.save(self._db)

    def transitive(self):

        instances = self.get("AWS::Ec2::Instance").get("Resource")

        functions = self.get("AWS::Lambda::Function").get("Resource")

        roles = self.get("AWS::Iam::Role").get("Resource")

        instance_profiles = self.get("AWS::Iam::InstanceProfile").get(
            "Resource")

        # Instance - [TRANSITIVE] -> Iam Instance Profile

        for instance in instances:

            if "IamInstanceProfile" not in instance.properties():
                continue

            target = next((ip for ip in instance_profiles if ip.id() ==
                           instance.properties()["IamInstanceProfile"]["Arn"]),
                          None)

            del instance.properties()["IamInstanceProfile"]

            self.append(
                Transitive({"Name": "Attached"},
                           source=instance,
                           target=target))

        # Lambda - [TRANSITIVE] -> Role

        for function in functions:

            if "Role" not in function.properties():
                continue

            role = next(
                (r for r in roles if r.id() == function.properties()["Role"]),
                None)

            del function.properties()["Role"]

            self.append(
                Transitive({"Name": "Attached"}, source=function, target=role))

    def resolve(self):

        IDP = [
            "AWS::Iam::User", "AWS::Iam::Role", "AWS::Iam::Group",
            "AWS::Iam::Policy"
        ]

        RBP = {"AWS::S3::Bucket": "Policy", "AWS::Iam::Role": "Trusts"}

        (principals, actions, trusts) = (Elements(), Elements(), Elements())
        resources = self.get("Resource") + self.get("Generic")

        print("[*] Resolving actions and resources\n")

        # Resolve actions
        for resource in self.get("Resource"):

            self._print(f"[*] Processing {resource}")

            # Identity Based Policies (Inline and Managed)

            if resource.labels()[0] in IDP:

                count = len(actions)
                actions.extend(
                    IdentityBasedPolicy(resource, resources).resolve())

                diff = len(actions) - count
                if diff > 0:
                    self._print(f"[+] Identity based Policy ({resource}) "
                                "resolved to {diff} action(s)")

            if resource.labels()[0] in RBP.keys():

                # Bucket ACLs

                if resource.type("AWS::S3::Bucket"):

                    count = len(actions)
                    acl = BucketACL(resource, resources)

                    principals.extend(
                        [p for p in acl.principals() if p not in principals])
                    actions.extend(
                        [a for a in acl.resolve() if a not in actions])

                    diff = len(actions) - count
                    if diff > 0:
                        print(f"[+] Bucket ACL ({resource}) "
                              "resolved to {diff} action(s)")

                # Resource Based Policies

                rbp = ResourceBasedPolicy(resource,
                                          resources,
                                          keys=[RBP[resource.labels()[0]]])

                if len(rbp.principals()) > 0:

                    count = len(actions)
                    resolved = rbp.resolve()

                    principals.extend([
                        p for p in rbp.principals()
                        if p not in principals and str(p) != RESOURCES.
                        types["AWS::Account"].format(Account=self.account_id)
                    ])

                    # TODO: This code should be moved to 'ResourceBasedPolicy' and override resolve().

                    # For Roles, actions imply a TRUSTS relationship. Only those beginning
                    # with sts:Assume are considered valid.

                    for action in [
                            a for a in resolved
                            if "AWS::Iam::Role" not in resource.labels()
                            or str(a).startswith("sts:AssumeRole")
                    ]:

                        if action.source().type("AWS::Account") \
                                and action.source().properties()["Arn"].split(':')[4] == self.account_id:

                            if "AWS::Iam::Role" in resource.labels():

                                trusts.extend([
                                    Trusts(properties=action.properties(),
                                           source=action.target(),
                                           target=e) for e in self.entities
                                ])

                            # This case appears redundant for Buckets

                        else:
                            if not action.source().type("AWS::Domain"):
                                actions.append(action)

                            if "AWS::Iam::Role" in resource.labels():
                                trusts.append(
                                    Trusts(properties=action.properties(),
                                           source=action.target(),
                                           target=action.source()))

                    diff = len(actions) - count

                    if diff > 0:
                        print(f"[+] Resource based policy ({resource}) "
                              "resolved to {diff} action(s)")

        principals = [p for p in principals if p not in self]

        self.extend(principals)
        self.extend(actions)
        self.extend(trusts)

        sys.stdout.write("\033[F\033[K")
        print(
            f"[+] Produced {len(principals)} new principals and {len(actions)} actions\n"
        )
示例#18
0
    def actions(self):

        if self._actions is not None:
            return self._actions

        (principals, actions, resources,
         conditions) = (self.principals(), Elements(), self.resources(),
                        self.conditions())

        for action in self._get_actions():

            action_resources = Elements()

            # Actions that do not affect specific resource types.
            if ACTIONS[action]["Affects"] == {}:
                action_resources.update(
                    Elements(self.__resources.get("CatchAll")))

            for affected_type in ACTIONS[action]["Affects"].keys():
                # Ignore mutable actions affecting built in policies
                if (affected_type == "AWS::Iam::Policy"
                        and ACTIONS[action]["Access"]
                        in ["Permissions Management", "Write"]):
                    action_resources.update([
                        a for a in resources.get(affected_type)
                        if str(a).split(':')[4] != "aws"
                    ])
                else:
                    action_resources.update(resources.get(affected_type))

            for resource in action_resources:
                # Action conditions comprise of resource-level conditions and statement conditions
                resource_conditions = list(conditions[str(resource)] if str(
                    resource) in conditions else [{}])

                statement_conditions = dict(
                    self.__statement["Condition"] if "Condition" in
                    self.__statement.keys() else {})
                # Add the two together
                condition = json.dumps([
                    {
                        **resource_conditions[i],
                        **statement_conditions
                    } for i in range(len(resource_conditions))
                ]) if (len(resource_conditions[0]) + len(statement_conditions)) > 0  \
                    else "[]"

                # Incorporate all items from ACTIONS.py
                supplementary = next((ACTIONS[action]["Affects"][r]
                                      for r in resource.labels()
                                      if r in ACTIONS[action]["Affects"]), {})

                for principal in self._principals:

                    actions.add(
                        Action(properties={
                            "Name": action,
                            "Description": ACTIONS[action]["Description"],
                            "Effect": self.__statement["Effect"],
                            "Access": ACTIONS[action]["Access"],
                            "Reference": ACTIONS[action]["Reference"],
                            "Condition": condition,
                            **supplementary
                        },
                               source=principal,
                               target=resource))

        # Unset resource level permission conditions
        for resource in self._resources:
            resource.condition = []

        self._actions = actions

        return self._actions
示例#19
0
    def get_account_authorization_details(self, only_arns, except_arns):

        elements = Elements()
        edges = {"Groups": [], "Policies": [], "InstanceProfiles": []}

        def get_aad_element(label, entry):

            properties = dict()
            for pk, pv in sorted(entry.items()):

                if pk.endswith("PolicyList"):
                    properties["Documents"] = [{
                        p["PolicyName"]: p["PolicyDocument"]
                        for p in pv
                    }]

                elif pk == "AssumeRolePolicyDocument":
                    properties["Trusts"] = pv

                elif pk in [
                        "GroupList", "InstanceProfileList",
                        "AttachedManagedPolicies"
                ]:
                    continue

                elif pk == "PolicyVersionList":

                    properties["Document"] = [{
                        "DefaultVersion":
                        [p for p in pv if p["IsDefaultVersion"]][0]["Document"]
                    }]

                else:
                    properties[pk.replace(label, "")] = pv

            element = Resource(properties=properties,
                               labels=["Resource", f"AWS::Iam::{label}"])

            if f"AWS::Iam::Group" in self.run and "GroupList" in entry.keys():

                edges["Groups"].extend([(element, g)
                                        for g in entry["GroupList"]])

            if f"AWS::Iam::InstanceProfile" in self.run \
                    and "InstanceProfileList" in entry.keys():

                edges["InstanceProfiles"].extend([
                    (get_aad_element("InstanceProfile", ip), element)
                    for ip in entry["InstanceProfileList"]
                ])

            if f"AWS::Iam::Policy" in self.run \
                    and "AttachedManagedPolicies" in entry.keys():
                edges["Policies"].extend([
                    (element, p["PolicyArn"])
                    for p in entry["AttachedManagedPolicies"]
                ])

            if (str(f"AWS::Iam::{label}") in self.run and
                (len(except_arns) == 0 or properties["Arn"] not in except_arns)
                    and (len(only_arns) == 0 or properties["Arn"] in only_arns)
                    and element not in elements):
                self._print(f"[*] Adding {element}")
                elements.append(element)

            return element

        account_authorization_details = [
            aad for aad in self.client.get_paginator(
                "get_account_authorization_details").paginate()
        ]

        account_authorization_details = [
            (label.replace("DetailList", "").replace("Policies",
                                                     "Policy"), entry)
            for aad in account_authorization_details
            for (label, v) in aad.items() if isinstance(v, list) for entry in v
        ]

        for label, entry in account_authorization_details:
            get_aad_element(label, entry)

        # Ensure edge nodes exist
        for k, v in edges.items():
            edges[k] = list(
                filter(lambda e: e[0] is not None and e[1] is not None, [
                    e if type(e[1]) == Resource else
                    (e[0],
                     next((t for t in elements
                           if (k == "Groups" and str(t).endswith(str(e[1])))
                           or str(t) == str(e[1])), None)) for e in v
                ]))

        # (:User|Group|Role)-[:TRANSITIVE{Attached}]->(:Policy)
        for (s, t) in edges["Policies"]:
            elements.append(
                Transitive(properties={"Name": "Attached"}, source=s,
                           target=t))

        # # (:User)-[:TRANSITIVE{MemberOf}]->(:Group)
        for (s, t) in edges["Groups"]:
            elements.append(
                Transitive(properties={"Name": "MemberOf"}, source=s,
                           target=t))

        # (:InstanceProfile)-[:TRANSITIVE{Attached}]->(:Role)
        for (s, t) in edges["InstanceProfiles"]:
            del s.properties()["Roles"]
            elements.append(
                Transitive(properties={"Name": "Attached"}, source=s,
                           target=t))

        return elements
示例#20
0
    def _get_resources_and_conditions(self):

        resources = Elements()
        conditions = {}

        all_resources = self.__resources

        key = list(filter(lambda x: "Resource" in x,
                          self.__statement.keys()))[0]

        statement = self.__statement[key] \
            if isinstance(self.__statement[key], list) \
            else [self.__statement[key]]

        for rlp in set([
                r.replace('*', "(.*)") + "$" for r in statement
                if '*' not in statement and len(all_resources) > 0
        ]):

            # Identify variable resource-level permissions
            variables = list(re.findall("\$\{[0-9a-zA-Z:]+\}", rlp))
            regex = re.compile(
                reduce(lambda x, y: x.replace(y, "(.*)"), variables, rlp))

            # Match resource-level permissions against resource arns
            results = Elements(
                filter(lambda r: regex.match(r.id()), all_resources))

            # Standard case: add results to result set
            if len(variables) == 0:
                for r in results:
                    conditions[r.id()] = [{}]
                    resources.add(r)

            offset = len([x for x in rlp if x == '(']) + 1

            # Handle resource-level permissions
            for result in [
                    r for r in results
                    if r.id() not in conditions or conditions[r.id()] != [{}]
            ]:

                # TODO: skip resources that incorporate contradictory conditions
                condition = {
                    "StringEquals": {
                        variables[i]:
                        regex.match(result.id()).group(offset + i)
                        for i in range(len(variables))
                    }
                }

                if result.id() not in conditions:
                    conditions[result.id()] = []

                if condition not in conditions[result.id()]:
                    conditions[result.id()].append(condition)

                if result not in resources:
                    resources.add(result)

        if '*' in statement:

            resources = all_resources

        elif key == "NotResource":
            resources = [r for r in all_resources if r not in resources]

        resources = Elements(resources)
        conditions = {
            str(r): conditions[r.id()] if r.id() in conditions else [{}]
            for r in resources
        }

        return (resources, conditions)
示例#21
0
class Statement:

    _principals = None
    _actions = None
    _resources = None
    _conditions = None

    __statement = {}
    __resources = Elements()

    def __init__(self, statement, resource, resources):

        self.__statement = copy.deepcopy(statement)
        self.__resources = resources

        self.__str__ = lambda: str(statement)

        assert isinstance(self.__statement, dict)

        keys = [k for k in self.__statement.keys()]

        if not ("Effect" in keys
                and any([k in keys for k in ["Action", "NotAction"]])):

            console.critical(f"Statement: {self.__statement} "
                             "is missing required key")

        if "NotPrincipal" in keys:
            console.warn("'NotPrincipal' support hasn't been implemented."
                         f"Statement: {self.__statement} will be ignored.")

        elif "Principal" not in keys:
            self.__statement["Principal"] = {"AWS": [str(resource)]}
            self._principals = Elements([resource])

        if (not any([k in keys for k in ["Resource", "NotResource"]])
                and resource is not None):
            self.__statement["Resource"] = [str(resource)]
            self._resources = Elements([resource])

    def _get_principals(self):
        '''https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_principal.html'''

        principals = Elements()

        key = list(filter(lambda x: "Principal" in x,
                          self.__statement.keys()))[0]

        statement = self.__statement[key]

        if isinstance(statement, str) and statement == "*":
            statement = {"AWS": "*"}

        assert isinstance(statement, dict)

        if "AWS" in statement:

            if not isinstance(statement["AWS"], list):
                statement["AWS"] = [statement["AWS"]]

            if '*' in statement["AWS"]:

                external = External(
                    key="Arn",
                    labels=["AWS::Account"],
                    properties={
                        "Name": "All AWS Accounts",
                        "Description":
                        "Pseudo-Account representing anyone who possesses an AWS account",
                        "Arn": "arn:aws:iam::{Account}:root"
                    })

                principals = Elements([
                    *self.__resources.get('AWS::Iam::User').get("Resource"),
                    *self.__resources.get('AWS::Iam::Role').get("Resource"),
                    external
                ])

            for principal in [
                    p for p in statement["AWS"] if '*' not in statement["AWS"]
            ]:

                if '*' in principal:
                    continue

                node = next((a for a in self.__resources.get("Resource")
                             if a.id() == principal), None)

                # We haven't seen this node before. It may belong to another account,
                # or it belongs to a service that was not loaded.
                if node is None:

                    name = principal
                    labels = ["AWS::Account"]

                    if re.compile(f"^{RESOURCES.regex['Account']}$").match(
                            principal) is not None:

                        principal = f"arn:aws:iam::{principal}:root"
                        labels += ["AWS::Account"]

                    elif re.compile(
                            f"^arn:aws:iam::{RESOURCES.regex['Account']}:root$"
                    ).match(principal) is not None:

                        name = str(principal.split(":")[4])
                        labels += ["AWS::Account"]

                    else:

                        for k, v in RESOURCES.items():

                            if re.compile(v).match(principal):
                                name = principal.replace('/',
                                                         ':').split(':')[-1]
                                labels = [k]
                                break

                    node = External(
                        key=str("Arn" if principal.
                                startswith("arn") else "CanonicalUser"),
                        labels=labels,
                        properties={
                            "Name":
                            str(name),
                            str("Arn" if principal.startswith("arn") else "CanonicalUser"):
                            principal,
                        })

                principals.add(node)

        elif "Service" in statement:

            services = statement["Service"] if isinstance(
                statement["Service"], list) else [statement["Service"]]

            for service in services:

                if service.lower().endswith("amazonaws.com"):
                    labels = ["AWS::Domain"]
                else:
                    labels = ["Internet::Domain"]

                principals.add(
                    External(key="Name",
                             labels=labels,
                             properties={"Name": service}))

        elif "Federated" in statement:

            node = None
            labels = []

            statements = statement["Federated"] \
                if isinstance(statement["Federated"], list) \
                else [statement["Federated"]]

            for federated in statements:

                if re.compile(RESOURCES["AWS::Iam::SamlProvider"]).match(
                        federated) is not None:

                    base = Resource if (next(
                        (a for a in self.__resources.get("Resoure")
                         if a.account() == federated.split(':')[4]),
                        False)) else External
                    node = base(key="Arn",
                                labels=["AWS::Iam::SamlProvider"],
                                properties={
                                    "Name": federated.split('/')[-1],
                                    "Arn": federated
                                })

                elif re.compile(
                        "^(?=.{1,253}\.?$)(?:(?!-|[^.]+_)[A-Za-z0-9-_]{1,63}(?<!-)(?:\.|$)){2,}$"
                ).match(federated):
                    node = External(key="Name",
                                    labels=["Internet::Domain"],
                                    properties={"Name": federated})

                else:
                    node = External(key="Name",
                                    properties={
                                        "Name": federated,
                                    })
                principals.add(node)

        # TODO:
        elif "CanonicalUser" in statement:

            principals.add(
                External(key="CanonicalUser",
                         labels=["AWS::Account"],
                         properties={
                             "Name": statement["CanonicalUser"],
                             "CanonicalUser": statement["CanonicalUser"],
                         }))

        else:
            console.warn("Unknown principal: ", statement)

        return principals

    def _get_actions(self):

        actions = set()
        key = list(filter(lambda x: "Action" in x, self.__statement.keys()))[0]
        statement = self.__statement[key] if isinstance(
            self.__statement[key], list) else [self.__statement[key]]

        for action in [a for a in statement if '*' not in statement]:

            if '*' in action:
                actions.update(
                    set(
                        filter(
                            re.compile(action.replace("*", "(.*)")).match,
                            ACTIONS.keys())))
            else:
                actions.update([action] if action in ACTIONS.keys() else [])

        if '*' in statement and key != 'NotAction':
            actions = set(ACTIONS.keys())

        elif '*' in statement:
            actions = set()

        elif key == "NotAction":

            actions = [action for action in ACTIONS if action not in actions]

        return sorted(list(actions))

    def _get_resources_and_conditions(self):

        resources = Elements()
        conditions = {}

        all_resources = self.__resources

        key = list(filter(lambda x: "Resource" in x,
                          self.__statement.keys()))[0]

        statement = self.__statement[key] \
            if isinstance(self.__statement[key], list) \
            else [self.__statement[key]]

        for rlp in set([
                r.replace('*', "(.*)") + "$" for r in statement
                if '*' not in statement and len(all_resources) > 0
        ]):

            # Identify variable resource-level permissions
            variables = list(re.findall("\$\{[0-9a-zA-Z:]+\}", rlp))
            regex = re.compile(
                reduce(lambda x, y: x.replace(y, "(.*)"), variables, rlp))

            # Match resource-level permissions against resource arns
            results = Elements(
                filter(lambda r: regex.match(r.id()), all_resources))

            # Standard case: add results to result set
            if len(variables) == 0:
                for r in results:
                    conditions[r.id()] = [{}]
                    resources.add(r)

            offset = len([x for x in rlp if x == '(']) + 1

            # Handle resource-level permissions
            for result in [
                    r for r in results
                    if r.id() not in conditions or conditions[r.id()] != [{}]
            ]:

                # TODO: skip resources that incorporate contradictory conditions
                condition = {
                    "StringEquals": {
                        variables[i]:
                        regex.match(result.id()).group(offset + i)
                        for i in range(len(variables))
                    }
                }

                if result.id() not in conditions:
                    conditions[result.id()] = []

                if condition not in conditions[result.id()]:
                    conditions[result.id()].append(condition)

                if result not in resources:
                    resources.add(result)

        if '*' in statement:

            resources = all_resources

        elif key == "NotResource":
            resources = [r for r in all_resources if r not in resources]

        resources = Elements(resources)
        conditions = {
            str(r): conditions[r.id()] if r.id() in conditions else [{}]
            for r in resources
        }

        return (resources, conditions)

    def principals(self):

        if self._principals is None:
            self._principals = self._get_principals()

        return self._principals

    def actions(self):

        if self._actions is not None:
            return self._actions

        (principals, actions, resources,
         conditions) = (self.principals(), Elements(), self.resources(),
                        self.conditions())

        for action in self._get_actions():

            action_resources = Elements()

            # Actions that do not affect specific resource types.
            if ACTIONS[action]["Affects"] == {}:
                action_resources.update(
                    Elements(self.__resources.get("CatchAll")))

            for affected_type in ACTIONS[action]["Affects"].keys():
                # Ignore mutable actions affecting built in policies
                if (affected_type == "AWS::Iam::Policy"
                        and ACTIONS[action]["Access"]
                        in ["Permissions Management", "Write"]):
                    action_resources.update([
                        a for a in resources.get(affected_type)
                        if str(a).split(':')[4] != "aws"
                    ])
                else:
                    action_resources.update(resources.get(affected_type))

            for resource in action_resources:
                # Action conditions comprise of resource-level conditions and statement conditions
                resource_conditions = list(conditions[str(resource)] if str(
                    resource) in conditions else [{}])

                statement_conditions = dict(
                    self.__statement["Condition"] if "Condition" in
                    self.__statement.keys() else {})
                # Add the two together
                condition = json.dumps([
                    {
                        **resource_conditions[i],
                        **statement_conditions
                    } for i in range(len(resource_conditions))
                ]) if (len(resource_conditions[0]) + len(statement_conditions)) > 0  \
                    else "[]"

                # Incorporate all items from ACTIONS.py
                supplementary = next((ACTIONS[action]["Affects"][r]
                                      for r in resource.labels()
                                      if r in ACTIONS[action]["Affects"]), {})

                for principal in self._principals:

                    actions.add(
                        Action(properties={
                            "Name": action,
                            "Description": ACTIONS[action]["Description"],
                            "Effect": self.__statement["Effect"],
                            "Access": ACTIONS[action]["Access"],
                            "Reference": ACTIONS[action]["Reference"],
                            "Condition": condition,
                            **supplementary
                        },
                               source=principal,
                               target=resource))

        # Unset resource level permission conditions
        for resource in self._resources:
            resource.condition = []

        self._actions = actions

        return self._actions

    def resources(self):

        if self._resources is None:
            (self._resources,
             self._conditions) = self._get_resources_and_conditions()

        return self._resources

    def conditions(self):

        if self._conditions is None:
            (self._resources,
             self._conditions) = self._get_resources_and_conditions()

        return self._conditions
示例#22
0
    def _resolve_principal_statement(self):

        principals = Elements()

        key = list(filter(lambda x: "Principal" in x,
                          self._statement.keys()))[0]

        statement = self._statement[key]

        if isinstance(statement, str) and statement == "*":
            statement = {"AWS": "*"}

        if not isinstance(statement, dict):
            raise ValueError

        if "AWS" in statement:

            if not isinstance(statement["AWS"], list):
                statement["AWS"] = [statement["AWS"]]

            if '*' in statement["AWS"]:

                principals = self._resources.get('AWS::Iam::User').get(
                    "Resource") + self._resources.get('AWS::Iam::Role').get(
                        "Resource") + [
                            External(key="Arn",
                                     labels=["AWS::Account"],
                                     properties={
                                         "Name": "All AWS Accounts",
                                         "Arn": "arn:aws:iam::{Account}:root"
                                     })
                        ]

            for principal in [
                    p for p in statement["AWS"] if '*' not in statement["AWS"]
            ]:

                if '*' in principal:
                    continue

                node = next(
                    (a for a in self._resources if a.id() == principal), None)

                # We haven't seen this node before. It may belong to another account,
                # or it belongs to a service that was not loaded.

                if node is None:

                    name = principal
                    labels = []

                    if re.compile("^%s$" % RESOURCES.regex["Account"]).match(
                            principal) is not None:

                        labels += ["AWS::Account"]
                        principal = "arn:aws:iam::{Account}:root".format(
                            Account=principal)

                    elif re.compile(
                            "^%s$" % "arn:aws:iam::{Account}:root".format(
                                Account=RESOURCES.regex["Account"])).match(
                                    principal) is not None:

                        name = str(principal.split(":")[4])
                        labels += ["AWS::Account"]

                    else:

                        for k, v in RESOURCES.items():
                            if re.compile(v).match(principal):
                                name = principal.replace('/',
                                                         ':').split(':')[-1]
                                labels = [k]
                                break

                    node = External(key="Arn",
                                    labels=labels,
                                    properties={
                                        "Name": str(name),
                                        "Arn": principal
                                    })

                principals.add(node)

        elif "Service" in statement:

            services = statement["Service"] if isinstance(
                statement["Service"], list) else [statement["Service"]]

            for service in services:

                if service.endswith("amazonaws.com"):
                    labels = ["AWS::Domain"]
                else:
                    labels = ["Internet::Domain"]

                principals.add(
                    External(labels=labels, properties={"Name": service}))

        elif "Federated" in statement:

            node = None
            labels = []

            if re.compile(RESOURCES["AWS::Iam::SamlProvider"]).match(
                    statement["Federated"]) is not None:

                base = Resource if (next(
                    (a for a in self._resources
                     if a.id().split(':')[4] == statement["Federated"].split(
                         ':')[4]), False)) else External

                node = base(key="Arn",
                            labels=["AWS::Iam::SamlProvider"],
                            properties={
                                "Name": statement["Federated"].split('/')[-1],
                                "Arn": statement["Federated"]
                            })

            elif re.compile(
                    "^(?=.{1,253}\.?$)(?:(?!-|[^.]+_)[A-Za-z0-9-_]{1,63}(?<!-)(?:\.|$)){2,}$"
            ).match(statement["Federated"]):

                node = External(labels=["Internet::Domain"],
                                properties={"Name": statement["Federated"]})

            else:
                node = External(properties={
                    "Name": statement["Federated"],
                })

            principals.add(node)

        # TODO:
        elif "CanonicalUser" in statement:

            principals.add(
                External(labels=["AWS::Account"],
                         properties={
                             "Name": statement["CanonicalUser"],
                             "CanonicalUser": statement["CanonicalUser"],
                             "Arn": ""
                         }))

        else:
            print("Unknown pricipal: ", statement)

        self._explicit_principals = principals
示例#23
0
    def resolve(self):

        IDP = [
            "AWS::Iam::User", "AWS::Iam::Role", "AWS::Iam::Group",
            "AWS::Iam::Policy"
        ]

        RBP = {"AWS::S3::Bucket": "Policy", "AWS::Iam::Role": "Trusts"}

        (principals, actions, trusts) = (Elements(), Elements(), Elements())
        resources = self.get("Resource") + self.get("Generic")

        print("[*] Resolving actions and resources\n")

        # Resolve actions
        for resource in self.get("Resource"):

            self._print(f"[*] Processing {resource}")

            # Identity Based Policies (Inline and Managed)

            if resource.labels()[0] in IDP:

                count = len(actions)
                actions.extend(
                    IdentityBasedPolicy(resource, resources).resolve())

                diff = len(actions) - count
                if diff > 0:
                    self._print(f"[+] Identity based Policy ({resource}) "
                                "resolved to {diff} action(s)")

            if resource.labels()[0] in RBP.keys():

                # Bucket ACLs

                if resource.type("AWS::S3::Bucket"):

                    count = len(actions)
                    acl = BucketACL(resource, resources)

                    principals.extend(
                        [p for p in acl.principals() if p not in principals])
                    actions.extend(
                        [a for a in acl.resolve() if a not in actions])

                    diff = len(actions) - count
                    if diff > 0:
                        print(f"[+] Bucket ACL ({resource}) "
                              "resolved to {diff} action(s)")

                # Resource Based Policies

                rbp = ResourceBasedPolicy(resource,
                                          resources,
                                          keys=[RBP[resource.labels()[0]]])

                if len(rbp.principals()) > 0:

                    count = len(actions)
                    resolved = rbp.resolve()

                    principals.extend([
                        p for p in rbp.principals()
                        if p not in principals and str(p) != RESOURCES.
                        types["AWS::Account"].format(Account=self.account_id)
                    ])

                    # TODO: This code should be moved to 'ResourceBasedPolicy' and override resolve().

                    # For Roles, actions imply a TRUSTS relationship. Only those beginning
                    # with sts:Assume are considered valid.

                    for action in [
                            a for a in resolved
                            if "AWS::Iam::Role" not in resource.labels()
                            or str(a).startswith("sts:AssumeRole")
                    ]:

                        if action.source().type("AWS::Account") \
                                and action.source().properties()["Arn"].split(':')[4] == self.account_id:

                            if "AWS::Iam::Role" in resource.labels():

                                trusts.extend([
                                    Trusts(properties=action.properties(),
                                           source=action.target(),
                                           target=e) for e in self.entities
                                ])

                            # This case appears redundant for Buckets

                        else:
                            if not action.source().type("AWS::Domain"):
                                actions.append(action)

                            if "AWS::Iam::Role" in resource.labels():
                                trusts.append(
                                    Trusts(properties=action.properties(),
                                           source=action.target(),
                                           target=action.source()))

                    diff = len(actions) - count

                    if diff > 0:
                        print(f"[+] Resource based policy ({resource}) "
                              "resolved to {diff} action(s)")

        principals = [p for p in principals if p not in self]

        self.extend(principals)
        self.extend(actions)
        self.extend(trusts)

        sys.stdout.write("\033[F\033[K")
        print(
            f"[+] Produced {len(principals)} new principals and {len(actions)} actions\n"
        )
示例#24
0
    def resolve(self):

        actions = Elements()
        for _, policy in self.documents.items():
            actions.update(policy.resolve())
        return actions
示例#25
0
 def resolve(self):
     actions = Elements()
     for i in range(len(self.statements)):
         actions.update(self.statements[i].resolve())
     return actions
示例#26
0
    def _get_principals(self):
        '''https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_principal.html'''

        principals = Elements()

        key = list(filter(lambda x: "Principal" in x,
                          self.__statement.keys()))[0]

        statement = self.__statement[key]

        if isinstance(statement, str) and statement == "*":
            statement = {"AWS": "*"}

        assert isinstance(statement, dict)

        if "AWS" in statement:

            if not isinstance(statement["AWS"], list):
                statement["AWS"] = [statement["AWS"]]

            if '*' in statement["AWS"]:

                external = External(
                    key="Arn",
                    labels=["AWS::Account"],
                    properties={
                        "Name": "All AWS Accounts",
                        "Description":
                        "Pseudo-Account representing anyone who possesses an AWS account",
                        "Arn": "arn:aws:iam::{Account}:root"
                    })

                principals = Elements([
                    *self.__resources.get('AWS::Iam::User').get("Resource"),
                    *self.__resources.get('AWS::Iam::Role').get("Resource"),
                    external
                ])

            for principal in [
                    p for p in statement["AWS"] if '*' not in statement["AWS"]
            ]:

                if '*' in principal:
                    continue

                node = next((a for a in self.__resources.get("Resource")
                             if a.id() == principal), None)

                # We haven't seen this node before. It may belong to another account,
                # or it belongs to a service that was not loaded.
                if node is None:

                    name = principal
                    labels = ["AWS::Account"]

                    if re.compile(f"^{RESOURCES.regex['Account']}$").match(
                            principal) is not None:

                        principal = f"arn:aws:iam::{principal}:root"
                        labels += ["AWS::Account"]

                    elif re.compile(
                            f"^arn:aws:iam::{RESOURCES.regex['Account']}:root$"
                    ).match(principal) is not None:

                        name = str(principal.split(":")[4])
                        labels += ["AWS::Account"]

                    else:

                        for k, v in RESOURCES.items():

                            if re.compile(v).match(principal):
                                name = principal.replace('/',
                                                         ':').split(':')[-1]
                                labels = [k]
                                break

                    node = External(
                        key=str("Arn" if principal.
                                startswith("arn") else "CanonicalUser"),
                        labels=labels,
                        properties={
                            "Name":
                            str(name),
                            str("Arn" if principal.startswith("arn") else "CanonicalUser"):
                            principal,
                        })

                principals.add(node)

        elif "Service" in statement:

            services = statement["Service"] if isinstance(
                statement["Service"], list) else [statement["Service"]]

            for service in services:

                if service.lower().endswith("amazonaws.com"):
                    labels = ["AWS::Domain"]
                else:
                    labels = ["Internet::Domain"]

                principals.add(
                    External(key="Name",
                             labels=labels,
                             properties={"Name": service}))

        elif "Federated" in statement:

            node = None
            labels = []

            statements = statement["Federated"] \
                if isinstance(statement["Federated"], list) \
                else [statement["Federated"]]

            for federated in statements:

                if re.compile(RESOURCES["AWS::Iam::SamlProvider"]).match(
                        federated) is not None:

                    base = Resource if (next(
                        (a for a in self.__resources.get("Resoure")
                         if a.account() == federated.split(':')[4]),
                        False)) else External
                    node = base(key="Arn",
                                labels=["AWS::Iam::SamlProvider"],
                                properties={
                                    "Name": federated.split('/')[-1],
                                    "Arn": federated
                                })

                elif re.compile(
                        "^(?=.{1,253}\.?$)(?:(?!-|[^.]+_)[A-Za-z0-9-_]{1,63}(?<!-)(?:\.|$)){2,}$"
                ).match(federated):
                    node = External(key="Name",
                                    labels=["Internet::Domain"],
                                    properties={"Name": federated})

                else:
                    node = External(key="Name",
                                    properties={
                                        "Name": federated,
                                    })
                principals.add(node)

        # TODO:
        elif "CanonicalUser" in statement:

            principals.add(
                External(key="CanonicalUser",
                         labels=["AWS::Account"],
                         properties={
                             "Name": statement["CanonicalUser"],
                             "CanonicalUser": statement["CanonicalUser"],
                         }))

        else:
            console.warn("Unknown principal: ", statement)

        return principals
示例#27
0
def handle_ingest(args):
    """
    awspx ingest
    """
    resources = Elements()
    account = "000000000000"
    session = None
    graph = None

    # Check to see if environment variables are being used for credentials.
    if args.env:
        session = boto3.session.Session(region_name=args.region)
    # Use existing profile
    elif args.profile in CREDENTIALS.sections():
        session = boto3.session.Session(region_name=args.region,
                                        profile_name=args.profile)
    # Use instance profile
    elif args.profile == "default":
        try:
            provider = InstanceMetadataProvider(
                iam_role_fetcher=InstanceMetadataFetcher())
            creds = provider.load()

            session = boto3.session.Session(region_name=args.region,
                                            aws_access_key_id=creds.access_key,
                                            aws_secret_access_key=creds.secret_key,
                                            aws_session_token=creds.token)
        except:
            pass

    # Create new profile
    if not session:
        if input(f"[-] Would you like to create the profile '{args.profile}'? (y/n) ").upper() == "Y":
            args.create_profile = args.profile
            handle_profile(args)
            session = boto3.session.Session(region_name=args.region,
                                            profile_name=args.profile)
        else:
            sys.exit(1)

    try:
        identity = session.client('sts').get_caller_identity()
        account = identity["Account"]

        print(f"[+] Profile:   {args.profile} (identity: {identity['Arn']})")

    except:
        print("[-] Request to establish identity (sts:GetCallerIdentity) failed.")
        sys.exit(1)

    print(f"[+] Services:  {', '.join([s.__name__ for s in args.services])}")
    print(f"[+] Database:  {args.database}")
    print(f"[+] Region:    {args.region}")

    if args.role_to_assume:
        try:
            response = session.client('sts').assume_role(
                RoleArn=args.role_to_assume,
                RoleSessionName=f"awspx",
                DurationSeconds=args.role_to_assume_duration)

        except ClientError as e:
            print("\n" + str(e))
            if "MaxSessionDuration" in e.response["Error"]["Message"]:
                print("\nTry reducing the session duration using "
                      "'--assume-role-duration'.")

            sys.exit(1)

        if response:
            print(f"[+] Assumed role: {args.role_to_assume}")
            session = boto3.session.Session(
                aws_access_key_id=response["Credentials"]["AccessKeyId"],
                aws_secret_access_key=response["Credentials"]["SecretAccessKey"],
                aws_session_token=response["Credentials"]["SessionToken"],
                region_name=args.region)
        try:
            identity = session.client('sts').get_caller_identity()
            account = identity["Account"]
            print(f"[+] Running as {identity['Arn']}.")
            print(f"[+] Region set to {args.region}.")
        except:
            print("[-] Request to establish identity (sts:GetCallerIdentity) failed.")

    print()

    if session is None:
        sys.exit(1)

    # Run IAM first to try acquire an account number
    if IAM in args.services:
        graph = IAM(session, db=args.database, verbose=args.verbose, quick=args.quick,
                    only_types=args.only_types, skip_types=args.skip_types,
                    only_arns=args.only_arns, skip_arns=args.skip_arns)
        account = graph.account_id

    for service in [s for s in args.services if s != IAM]:
        resources += service(session, account=account, verbose=args.verbose, quick=args.quick,
                             only_types=args.only_types, skip_types=args.skip_types,
                             only_arns=args.only_arns, skip_arns=args.skip_arns)

    if graph is None:
        graph = IAM(session, verbose=args.verbose, quick=args.quick,
                    db=args.database,
                    resources=resources)
    else:
        graph.update(resources)

    args.load_zip = graph.post(skip_all_actions=args.skip_all_actions)
    handle_db(args)

    if not (args.skip_all_attacks or args.skip_all_actions):
        handle_attacks(args)
示例#28
0
class Statement:
    def __init__(self, statement: dict, resource: Element,
                 resources: Elements):

        # TODO: policy statements do not appear to strictly adhere to the JSON
        # format and may include duplicate keys. Duplicate keys will be ignored.

        self._resources = resources
        self._statement = statement
        self._resource = resource

        self._explicit_principals = None
        self._explicit_actions = None
        self._explicit_resources = None
        self._explicit_conditions = None
        self._explicit_resource_conditions = {}
        self._Actions = None

        try:

            if not isinstance(statement, dict):

                raise ValueError

            self._statement = statement

            keys = [k.replace("Not", "") for k in self._statement.keys()]

            if not all([k in keys for k in ["Effect", "Action"]]):
                raise ValueError("Malformed statement: Missing 'Effect'")

            if "Resource" not in keys and resource is not None:
                self._statement["Resource"] = str(self._resource)
                self._explicit_resources = Elements([self._resource])
                self._explicit_resource_conditions = {
                    self._resource.id(): [{}]
                }

            elif "Resource" is None:
                raise ValueError("Malformed statement: Missing 'Resource'")

            if "Condition" not in keys:
                self._explicit_conditions = {}
            else:
                self._explicit_conditions = self._statement["Condition"]

            # TODO: Not implemented
            if "NotPrincipal" in keys:
                print("[!] 'NotPrincipal' support has not yet been added. "
                      "\n\tThis entire statement will be ingnored (%s)." %
                      self._resource.id())
                self._explicit_principals = []

            elif "Principal" not in keys:
                self._explicit_principals = [self._resource]

        except:
            raise ValueError("Malformed statement: %s" % self._statement)

    '''https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_principal.html'''

    def _resolve_principal_statement(self):

        principals = Elements()

        key = list(filter(lambda x: "Principal" in x,
                          self._statement.keys()))[0]

        statement = self._statement[key]

        if isinstance(statement, str) and statement == "*":
            statement = {"AWS": "*"}

        if not isinstance(statement, dict):
            raise ValueError

        if "AWS" in statement:

            if not isinstance(statement["AWS"], list):
                statement["AWS"] = [statement["AWS"]]

            if '*' in statement["AWS"]:

                principals = self._resources.get('AWS::Iam::User').get(
                    "Resource") + self._resources.get('AWS::Iam::Role').get(
                        "Resource") + [
                            External(key="Arn",
                                     labels=["AWS::Account"],
                                     properties={
                                         "Name": "All AWS Accounts",
                                         "Arn": "arn:aws:iam::{Account}:root"
                                     })
                        ]

            for principal in [
                    p for p in statement["AWS"] if '*' not in statement["AWS"]
            ]:

                if '*' in principal:
                    continue

                node = next(
                    (a for a in self._resources if a.id() == principal), None)

                # We haven't seen this node before. It may belong to another account,
                # or it belongs to a service that was not loaded.

                if node is None:

                    name = principal
                    labels = []

                    if re.compile("^%s$" % RESOURCES.regex["Account"]).match(
                            principal) is not None:

                        labels += ["AWS::Account"]
                        principal = "arn:aws:iam::{Account}:root".format(
                            Account=principal)

                    elif re.compile(
                            "^%s$" % "arn:aws:iam::{Account}:root".format(
                                Account=RESOURCES.regex["Account"])).match(
                                    principal) is not None:

                        name = str(principal.split(":")[4])
                        labels += ["AWS::Account"]

                    else:

                        for k, v in RESOURCES.items():
                            if re.compile(v).match(principal):
                                name = principal.replace('/',
                                                         ':').split(':')[-1]
                                labels = [k]
                                break

                    node = External(key="Arn",
                                    labels=labels,
                                    properties={
                                        "Name": str(name),
                                        "Arn": principal
                                    })

                principals.add(node)

        elif "Service" in statement:

            services = statement["Service"] if isinstance(
                statement["Service"], list) else [statement["Service"]]

            for service in services:

                if service.endswith("amazonaws.com"):
                    labels = ["AWS::Domain"]
                else:
                    labels = ["Internet::Domain"]

                principals.add(
                    External(labels=labels, properties={"Name": service}))

        elif "Federated" in statement:

            node = None
            labels = []

            if re.compile(RESOURCES["AWS::Iam::SamlProvider"]).match(
                    statement["Federated"]) is not None:

                base = Resource if (next(
                    (a for a in self._resources
                     if a.id().split(':')[4] == statement["Federated"].split(
                         ':')[4]), False)) else External

                node = base(key="Arn",
                            labels=["AWS::Iam::SamlProvider"],
                            properties={
                                "Name": statement["Federated"].split('/')[-1],
                                "Arn": statement["Federated"]
                            })

            elif re.compile(
                    "^(?=.{1,253}\.?$)(?:(?!-|[^.]+_)[A-Za-z0-9-_]{1,63}(?<!-)(?:\.|$)){2,}$"
            ).match(statement["Federated"]):

                node = External(labels=["Internet::Domain"],
                                properties={"Name": statement["Federated"]})

            else:
                node = External(properties={
                    "Name": statement["Federated"],
                })

            principals.add(node)

        # TODO:
        elif "CanonicalUser" in statement:

            principals.add(
                External(labels=["AWS::Account"],
                         properties={
                             "Name": statement["CanonicalUser"],
                             "CanonicalUser": statement["CanonicalUser"],
                             "Arn": ""
                         }))

        else:
            print("Unknown pricipal: ", statement)

        self._explicit_principals = principals

    def _resolve_action_statement(self):

        actions = set()
        key = list(filter(lambda x: "Action" in x, self._statement.keys()))[0]
        statement = self._statement[key] \
            if isinstance(self._statement[key], list) \
            else [self._statement[key]]

        for action in [a for a in statement if '*' not in statement]:

            if '*' in action:

                actions.update(
                    set(
                        filter(
                            re.compile(action.replace("*", "(.*)")).match,
                            ACTIONS.keys())))
            else:
                actions.update([action] if action in ACTIONS.keys() else [])

        if '*' in statement and key != 'NotAction':
            actions = set(ACTIONS.keys())

        elif '*' in statement:
            actions = set()

        elif key == "NotAction":

            actions = [action for action in ACTIONS if action not in actions]

        self._explicit_actions = sorted(list(actions))

    def _resolve_resource_statement(self):

        resources = Elements()
        conditions = {}

        key = list(filter(lambda x: "Resource" in x,
                          self._statement.keys()))[0]
        statement = self._statement[key] \
            if isinstance(self._statement[key], list) \
            else [self._statement[key]]

        for resource in set([
                r.replace('*', "(.*)") + "$" for r in statement
                if '*' not in statement and len(self._resources) > 0
        ]):

            # Identify variable resource-level permissions
            variables = list(re.findall("\$\{[0-9a-zA-Z:]+\}", resource))
            regex = re.compile(
                reduce(lambda x, y: x.replace(y, "(.*)"), variables, resource))

            # Match resource-level permissions against resource arns
            results = Elements(
                filter(lambda r: regex.match(r.id()), self._resources))

            # Standard case: add results to result set
            if len(variables) == 0:
                for r in results:
                    conditions[r.id()] = [{}]
                    resources.add(r)

            offset = len([x for x in resource if x == '(']) + 1

            # Handle resource-level permissions
            for result in [
                    r for r in results
                    if r.id() not in conditions or conditions[r.id()] != [{}]
            ]:

                # TODO: skip resources that incorporate contradictory conditions
                condition = {
                    "StringEquals": {
                        variables[i]:
                        regex.match(result.id()).group(offset + i)
                        for i in range(len(variables))
                    }
                }

                if result.id() not in conditions:
                    conditions[result.id()] = []

                if condition not in conditions[result.id()]:
                    conditions[result.id()].append(condition)

                if result not in resources:
                    resources.add(result)

        if '*' in statement:

            resources = self._resources

        elif key == "NotResource":
            resources = [r for r in self._resources if r not in resources]

        self._explicit_resource_conditions = {
            r.id(): conditions[r.id()] if r.id() in conditions else [{}]
            for r in resources
        }

        self._explicit_resources = Elements(resources)

    def __str__(self):
        return str(self._statement)

    def principals(self):

        if self._explicit_principals is None:
            self._resolve_principal_statement()
        return self._explicit_principals

    def actions(self):

        if self._explicit_actions is None:
            self._resolve_action_statement()

        return self._explicit_actions

    def resources(self):

        if self._explicit_resources is None:
            self._resolve_resource_statement()

        return [str(r) for r in self._explicit_resources]

    def resolve(self):

        if self._Actions is not None:
            return self._Actions

        if self._explicit_actions is None:
            self._resolve_action_statement()
        if self._explicit_resources is None:
            self._resolve_resource_statement()
        if self._explicit_principals is None:
            self._resolve_principal_statement()

        actions = Elements()

        for action in self.actions():

            # Rewrite
            resources = Elements()
            for affected in ACTIONS[action]["Affects"]:
                resources.update(
                    Elements(self._explicit_resources.get(affected)))

            for resource in resources:

                # Action conditions comprise of resource level permission conditions
                # variants AND statement conditions

                condition = self._explicit_resource_conditions[resource.id()]

                condition = [{
                    **condition[i],
                    **self._explicit_conditions
                } for i in range(len(condition))]

                condition = json.dumps(condition) \
                    if len(condition[0]) > 0 else "[]"

                for principal in self._explicit_principals:
                    actions.add(
                        Action(properties={
                            "Name": action,
                            "Description": ACTIONS[action]["Description"],
                            "Effect": self._statement["Effect"],
                            "Access": ACTIONS[action]["Access"],
                            "Reference": ACTIONS[action]["Reference"],
                            "Condition": condition
                        },
                               source=principal,
                               target=resource))

        # Unset resource level permission conditions
        for resource in self._explicit_resources:
            resource.condition = []

        self._Actions = actions

        return self._Actions
示例#29
0
文件: cli.py 项目: sasqwatch/awspx
def handle_ingest(args):
    """
    awspx ingest
    """
    resources = Elements()
    account = "000000000000"
    session = None
    graph = None

    if args.profile not in CREDENTIALS.sections():
        if input(f"[-] The profile '{args.profile}' was not found. "
                 "Would you like to create it? (y/n) ").upper() == "Y":
            args.create_profile = args.profile
            handle_profile(args)
        else:
            return

    try:
        session = boto3.session.Session(profile_name=args.profile,
                                        region_name=args.region)
        identity = session.client('sts').get_caller_identity()
        account = identity["Account"]

        print(f"[+] User set to: {identity['Arn']}")

    except:
        print(
            "[-] Request to establish identity (sts:GetCallerIdentity) failed."
        )

    print(f"[+] Region set to: {args.region}")
    print(f"[+] Database set to: {args.database}")

    if args.role_to_assume:
        response = session.client('sts').assume_role(
            RoleArn=args.role_to_assume,
            RoleSessionName=f"awspx",
            DurationSeconds=7200)
        if response:
            print(f"[+] Assumed role: {args.role_to_assume}")
            session = boto3.session.Session(
                aws_access_key_id=response["Credentials"]["AccessKeyId"],
                aws_secret_access_key=response["Credentials"]
                ["SecretAccessKey"],
                aws_session_token=response["Credentials"]["SessionToken"],
                region_name=args.region)
        try:
            identity = session.client('sts').get_caller_identity()
            account = identity["Account"]
            print(f"[+] Running as {identity['Arn']}.")
            print(f"[+] Region set to {args.region}.")
        except:
            print(
                "[-] Request to establish identity (sts:GetCallerIdentity) failed."
            )

    print()

    if session is None:
        sys.exit(1)

    # Run IAM first to try acquire an account number
    if IAM in args.services:
        graph = IAM(session,
                    db=args.database,
                    verbose=args.verbose,
                    only_types=args.only_types,
                    except_types=args.except_types,
                    only_arns=args.only_arns,
                    except_arns=args.except_arns)
        account = graph.account_id

    for service in [s for s in args.services if s != IAM]:
        resources += service(session,
                             account=account,
                             verbose=args.verbose,
                             only_types=args.only_types,
                             except_types=args.except_types,
                             only_arns=args.only_arns,
                             except_arns=args.except_arns)

    if graph is None:
        graph = IAM(session,
                    verbose=args.verbose,
                    db=args.database,
                    resources=resources)
    else:
        graph.update(resources)

    args.load_zip = graph.post(skip_actions=args.skip_actions)
    handle_db(args)

    if not (args.skip_attacks or args.skip_actions):
        handle_attacks(args)
示例#30
0
 def principals(self):
     principals = Elements()
     for i in range(len(self.statements)):
         principals.update(self.statements[i].principals())
     return principals