Ejemplo n.º 1
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
Ejemplo n.º 2
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)
Ejemplo n.º 3
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
Ejemplo n.º 4
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)
Ejemplo n.º 5
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
Ejemplo n.º 6
0
    def get_account_authorization_details(self, only_arns, except_arns):

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

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

        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.add(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.add(
                Transitive(properties={"Name": "Attached"}, source=s,
                           target=t))

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

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

        self.update(elements)
Ejemplo n.º 7
0
    def _load_associations(self):

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

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

        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(str(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.add(edge)

        self.update(edges)
Ejemplo n.º 8
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():

            resources = Elements()

            # Actions that do not affect specific resource types.

            if ACTIONS[action]["Affects"] == {}:

                resources.update(
                    Elements(self._explicit_resources.get("CatchAll")))

            for affected_type in ACTIONS[action]["Affects"].keys():

                affected = self._explicit_resources.get(affected_type)

                # Ignore mutable actions affecting built in policies

                if affected_type == "AWS::Iam::Policy" \
                        and ACTIONS[action]["Access"] in ["Permissions Management", "Write"]:
                    affected = [
                        a for a in affected if str(a).split(':')[4] != "aws"
                    ]

                resources.update(Elements(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 "[]"

                supplementary = next((ACTIONS[action]["Affects"][r]
                                      for r in resource.labels()
                                      if r in ACTIONS[action]["Affects"]), {})

                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,
                            **supplementary
                        },
                               source=principal,
                               target=resource))

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

        self._Actions = actions

        return self._Actions