def _update_statements(self, statements: list, is_cf=False) -> list: """ Replace partition, region and account ID placeholders in resources with specific values. """ if not statements: return [] updated = [] for statement in statements: resources = statement.get("Resource") if not resources: resources = "*" resources = make_list(resources) statement["Resource"] = [] for resource in resources: if is_cf: if "AWS::" in resource: resource = {"Fn::Sub": resource} else: resource = (resource.replace("${AWS::Partition}", self.partition).replace( "${AWS::Region}", self.region).replace( "${AWS::AccountId}", self.account_id)) statement["Resource"].append(resource) updated.append(statement) return updated
def principal_service(self) -> list: """ Return any service principals from the role """ principal = self.role_dict.get("settings", {}).get("principal_service") if not principal: return [] return make_list(principal)
def is_finding_filtered(finding, minimum_severity="LOW"): # Return True if the finding should be filtered (ie. return False if it should be displayed) minimum_severity = minimum_severity.upper() severity_choices = ["MUTE", "INFO", "LOW", "MEDIUM", "HIGH", "CRITICAL"] if severity_choices.index( finding.severity) < severity_choices.index(minimum_severity): return True if finding.ignore_locations: # The ignore_locations element looks like this: # # ignore_locations: # - filepath: "test.json" # action: "s3:GetObject" # resource: # - "a" # - "b" # - action: "s3:GetObject" # resource: # - "c.*" # # Assuming the finding has these types of values in the `location` element, # this will ignore any finding that matches the filepath to "test.json" # AND action to "s3:GetObject" # AND the resource to "a" OR "b" # It will also ignore a resource that matches "c.*". for ignore_location in finding.ignore_locations: all_match = True for location_type, locations_to_ignore in ignore_location.items(): has_array_match = False for location_to_ignore in make_list(locations_to_ignore): if re.fullmatch( location_to_ignore.lower(), str(finding.location.get(location_type, "")).lower(), ): has_array_match = True if not has_array_match: all_match = False if all_match: return True return False
def audit(policy): services = set() for stmt in policy.statements: actions = make_list(stmt.stmt["Action"]) for action in actions: prefix = action.split(":")[0] if prefix == "*": continue services.add(prefix.lower()) print(f"SERVICES: {services}") denied_services_in_policy = [] for service in services: if service in DENIED_SERVICES: denied_services_in_policy.append(service) if denied_services_in_policy: policy.add_finding( "DENIED_SERVICE", "Services are not approved for use", location={"Action": denied_services_in_policy}, )
def simulate_statement(client, account_id: str, statement: object) -> list: """ Simulate a policy statement using the SimulateCustomPolicy API """ all_actions = set() actions = make_list(statement.stmt.get("Action", [])) for action in actions: expanded_actions = expand_action(action, raise_exceptions=False) for action_struct in expanded_actions: all_actions.add(action_struct["service"] + ":" + action_struct["action"]) resources = make_list(statement.stmt.get("Resource", [])) if not resources: resources = ["*"] if len(resources) > 1 and "*" in resources: resources.remove("*") policies = [ json.dumps({ "Version": "2012-10-17", "Statement": statement.stmt }) ] response = client.simulate_custom_policy( PolicyInputList=policies, ActionNames=sorted(all_actions), ResourceArns=resources, ResourceOwner=f"arn:aws:iam::{account_id}:root", ) LOGGER.info(f"SimulateResponse = {response}") findings = [] for result in response.get("EvaluationResults", []): action = result.get("EvalActionName") is_action_denied = result.get("EvalDecision", "implicitDeny") in DENY_RESULT is_org_allowed = result.get("OrganizationDecisionDetail", {}).get("AllowedByOrganizations") is_boundary_allowed = result.get( "PermissionsBoundaryDecisionDetail", {}).get("AllowedByPermissionsBoundary") denied_resources = [ resource for resource in result.get("ResourceSpecificResults", []) if resource.get("EvalResourceDecision", "implicitDeny") in DENY_RESULT ] if denied_resources: resource_names = [ resource.get("EvalResourceName") for resource in denied_resources ] findings.append( Finding( "DENIED_POLICY", detail= f"API {action} was denied because the API is approved but not for the following resources requested:", location={"Resource": resource_names}, )) elif is_action_denied: if is_org_allowed is False: findings.append( Finding( "DENIED_POLICY", detail= f"API {action} was denied by an organizational policy", location={"Action": action}, )) elif is_boundary_allowed is False: findings.append( Finding( "DENIED_POLICY", detail= f"API {action} was denied by a permission boundary policy", location={"Action": action}, )) else: findings.append( Finding( "DENIED_POLICY", detail= f"API {action} was denied because it is not in the approved API list", location={"Action": action}, )) continue return findings