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
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
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" )
class IAM(Ingestor): 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 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 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) 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) except self.client.exceptions.NoSuchEntityException: pass def post(self): 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") # Resolve actions for resource in self.get("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: print(f"[+] Identity based Policy for `{resource}` " f"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 for `{resource}` " f"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.root.account()) ]) # TODO: This code should be moved to 'ResourceBasedPolicy' and override resolve(). # For Roles, actions imply a TRUSTS relationship. For both (ACTION and TRUSTS, only actions 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.root.account(): 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: 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 for `{resource}` " f"resolved to {diff} action(s)") self.extend([p for p in principals if p not in self]) self.extend([a for a in actions if a not in self]) self.extend(trusts)