def _add_to_result( self, result: Result, logical_id: str, policy_name: Optional[str], action: Optional[str], statement: Statement, extras: Dict, warning: bool = False, ): add_to_result = self.add_warning_to_result if warning else self.add_failure_to_result add_to_result( result=result, reason=self._build_reason(logical_id, action, policy_name), granularity=RuleGranularity.ACTION, resource_ids={logical_id}, actions=set(statement.get_action_list()), context={ "config": self._config, "extras": extras, "logical_id": logical_id, "policy_name": policy_name, "statement": statement, "action": action, }, )
def _add_to_result( self, result: Result, logical_id: str, policy_name: Optional[str], action: Optional[str], statement: Statement, extras: Dict, resource_type: str, monitor: bool = False, ): self.add_failure_to_result( result=result, reason=self._build_reason(logical_id, action, policy_name), granularity=RuleGranularity.ACTION, resource_ids={logical_id}, resource_types={resource_type}, actions=set(statement.get_action_list()), rule_mode=RuleMode.MONITOR if monitor else None, context={ "config": self._config, "extras": extras, "logical_id": logical_id, "policy_name": policy_name, "statement": statement, "action": action, }, )
def statement_principal_5(): return Statement( **{ "Principal": [ "arn:aws:iam::AWS-account-ID:user/user-name-1", "arn:aws:iam::AWS-account-ID:user/UserName2" ] })
def statement_4(): return Statement( **{ "Effect": "Allow", "Action": "action2", "Resource": "arn1", "NotResource": ["arn2"] })
def statement_1(): return Statement( **{ "Effect": "Allow", "Action": ["action1"], "NotAction": "action2", "Resource": ["arn"] })
def statement_not_principal_2(): return Statement( **{ "NotPrincipal": { "AWS": ["arn:aws:iam::AWS-account-ID:user/user-name-1", "arn:aws:iam::AWS-account-ID:user/UserName2"] } } )
def _check_statement(self, result: Result, logical_id: str, policy_name: Optional[str], statement: Statement, extras: Dict): if statement.Effect and statement.Effect == "Deny": return if statement.actions_with(REGEX_IS_STAR): if statement.Condition: self._add_to_result(result, logical_id, policy_name, None, statement, extras, warning=True) else: self._add_to_result(result, logical_id, policy_name, None, statement, extras) else: for action in statement.get_expanded_action_list(): if action in CLOUDFORMATION_ACTIONS_ONLY_ACCEPTS_WILDCARD: logger.info( f"Action {action} only accepts wildcard, ignoring...") elif action.lower().startswith("kms:"): # When KMS Key policies use * in the resource, that * will only apply this policy to the KMS Key being created # so we must not flag this # Source: https://docs.aws.amazon.com/kms/latest/developerguide/key-policies.html logger.info( f"KMS Action {action} only accepts wildcard, ignoring..." ) elif statement.Condition: self._add_to_result(result, logical_id, policy_name, action, statement, extras, warning=True) else: self._add_to_result(result, logical_id, policy_name, action, statement, extras)
def test_can_obtain_policy_documents_from_inherited_method(valid_opensearch_domain_with_access_policies): assert len(valid_opensearch_domain_with_access_policies.policy_documents) == 1 assert valid_opensearch_domain_with_access_policies.policy_documents == [ OptionallyNamedPolicyDocument( policy_document=PolicyDocument( Statement=[ Statement( Effect="Allow", Action="es:*", Resource="arn:aws:es:us-east-1:123456789012:domain/test/*", Principal=Principal(AWS="arn:aws:iam::123456789012:user/opensearch-user"), ) ] ), name=None, ), ]
def _do_statement_check(self, result: Result, logical_id: str, statement: Statement, filters_available_context: Dict, resource: Resource): if statement.Effect == "Allow": for principal in statement.get_principal_list(): account_id = get_account_id_from_principal(principal) filters_available_context["principal"] = principal filters_available_context["account_id"] = account_id if ( # checks if principal is a canonical id and is allowed principal not in self.valid_principals # if it wasn't a canonical id and contains a valid account id and account_id not in self.valid_principals # if principal is an AWS service and not principal.endswith(".amazonaws.com")): if statement.Condition and statement.Condition.dict(): # Ignoring condition checks since they will get reviewed in other rules and future improvements pass elif not self._config.aws_account_id: logger.warning( f"Not adding {type(self).__name__} failure in {logical_id} " f"because no AWS Account ID was found in the config." ) elif principal.startswith( "GETATT") or principal.startswith("UNDEFINED_"): self.add_failure_to_result( result, self.REASON.format(logical_id, principal), rule_mode=RuleMode.DEBUG, resource_ids={logical_id}, context=filters_available_context, resource_types={resource.Type}, ) else: self.add_failure_to_result( result, self.REASON.format(logical_id, principal), resource_ids={logical_id}, context=filters_available_context, resource_types={resource.Type}, )
def _do_statement_check(self, result: Result, logical_id: str, statement: Statement, filters_available_context: Dict): if statement.Effect == "Allow": for principal in statement.get_principal_list(): account_id = get_account_id_from_principal(principal) filters_available_context["principal"] = principal filters_available_context["account_id"] = account_id if ( # checks if principal is a canonical id and is whitelisted principal not in self.valid_principals # if it wasn't a canonical id and contains a valid account id and account_id not in self.valid_principals # if principal is an AWS service and not principal.endswith(".amazonaws.com")): if statement.Condition and statement.Condition.dict(): logger.warning( f"Not adding {type(self).__name__} failure in {logical_id} " f"because there are conditions: {statement.Condition}" ) elif not self._config.aws_account_id: logger.warning( f"Not adding {type(self).__name__} failure in {logical_id} " f"because no AWS Account ID was found in the config." ) elif "GETATT" in principal or "UNDEFINED_" in principal: self.add_failure_to_result( result, self.REASON.format(logical_id, principal), rule_mode=RuleMode.DEBUG, resource_ids={logical_id}, context=filters_available_context, ) else: self.add_failure_to_result( result, self.REASON.format(logical_id, principal), resource_ids={logical_id}, context=filters_available_context, )
"Statement": [{ "Effect": "Allow", "Action": ["service:GetService"], "Resource": "*", }], }, }, } }, }, [ OptionallyNamedPolicyDocument( policy_document=PolicyDocument(Statement=[ Statement( Effect="Allow", Action=["service:GetService"], Resource="*", ) ]), name=None, ) ], 1, ), ( { "AWSTemplateFormatVersion": "2010-09-09", "Description": "Test resolving a nonexistent resource to Resource class", "Resources": { "NonexistentResource": {
def statement_principal_4(): return Statement(**{"Principal": "arn:aws:iam::123456789012:root"})
def statement_principal_3(): return Statement( **{"Principal": { "Federated": "cognito-identity.amazonaws.com" }})
def statement_not_principal_1(): return Statement( **{"NotPrincipal": { "AWS": "arn:aws:iam::123456789012:root" }})
def test_capitalize_effect(): statement = Statement(**{"Effect": "allOw", "Action": ["action1"], "NotAction": "action2", "Resource": ["arn"]}) assert statement.Effect == "Allow"
"statement, expected_output", [ (statement_1(), ["action1", "action2"]), (statement_2(), ["action1", "action2"]), (statement_3(), ["action1"]), (statement_4(), ["action2"]), ], ) def test_get_action_list(statement, expected_output): assert statement.get_action_list() == expected_output @pytest.mark.parametrize( "statement, expected_output", [ (Statement(**{"Effect": "Allow", "Action": "ec2:RunInstances", "Resource": ["arn"]}), ["ec2:RunInstances"]), (Statement(**{"Effect": "Allow", "Action": "ec2:Run?nstances", "Resource": ["arn"]}), ["ec2:RunInstances"]), (Statement(**{"Effect": "Allow", "Action": "ec?:RunInstances", "Resource": ["arn"]}), ["ec2:RunInstances"]), ( Statement(**{"Effect": "Allow", "Action": "ec2:Run*", "Resource": ["arn"]}), ["ec2:RunInstances", "ec2:RunScheduledInstances"], ), ], ) def test_get_expanded_action_list(statement, expected_output): assert statement.get_expanded_action_list() == expected_output @pytest.mark.parametrize( "statement, expected_output", [