예제 #1
0
파일: policy.py 프로젝트: shenkaiqi/awspx
    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.extend(Elements(
                    [r for r in self._explicit_resources.get(affected)
                     if r not in resources]))

            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.append(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
예제 #2
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
예제 #3
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)
예제 #4
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.append(node)

        elif "Service" in statement:

            pass
            # 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.append(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.append(node)

        # TODO:
        elif "CanonicalUser" in statement:

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

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

        self._explicit_principals = principals
예제 #5
0
    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()] = [{}]
                    if r not in resources:
                        resources.append(r)
                continue

            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.append(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(sorted(resources))
예제 #6
0
    def get_account_authorization_details(self):

        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",
                                       "AWS::Iam::%s" % label])

            if "GroupList" in entry.keys():

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

            if "InstanceProfileList" in entry.keys():

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

            if "AttachedManagedPolicies" in entry.keys():
                edges["Policies"].extend([
                    (element, p["PolicyArn"])
                    for p in entry["AttachedManagedPolicies"]
                ])

            if element not in elements:
                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)

        # User|Group|Role - Attached -> Policy

        for (s, t) in edges["Policies"]:
            t = next(entry for entry in elements if entry.id() == t)
            elements.append(
                Transitive(properties={"Name": "Attached"}, source=s,
                           target=t))

        # User - [MemberOf] -> Group

        for (s, t) in edges["Groups"]:
            t = next(entry for entry in elements.get("AWS::Iam::Group")
                     if str(entry).endswith(t))
            elements.append(
                Transitive(properties={"Name": "MemberOf"}, source=s,
                           target=t))

        # InstanceProfile - [Attached] -> Role

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

        return elements