import json from collections import defaultdict from typing import Any, Dict, List from .exceptions import AccessDeniedException from .models import Key ALTERNATIVE_ACTIONS = defaultdict(list) ALTERNATIVE_ACTIONS["kms:DescribeKey"] = ["kms:*", "kms:Describe*", "kms:DescribeKey"] def validate_policy(key: Key, action: str) -> None: """ Relevant docs: - https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-default.html - https://docs.aws.amazon.com/kms/latest/developerguide/policy-evaluation.html This method currently denies action based on whether: - There is a applicable DENY-statement in the policy """ try: policy = json.loads(key.policy) except (ValueError, AttributeError): return for statement in policy.get("Statement", []): statement_applies = check_statement(statement, key.arn, action) if statement_applies and statement.get("Effect", "").lower() == "deny": raise AccessDeniedException( message=f"Action {action} is now allowed by the given key policy" ) def check_statement(statement: Dict[str, Any], resource: str, action: str) -> bool: return action_matches(statement.get("Action", []), action) and resource_matches( statement.get("Resource", ""), resource ) def action_matches(applicable_actions: List[str], action: str) -> bool: alternatives = ALTERNATIVE_ACTIONS[action] if any(alt in applicable_actions for alt in alternatives): return True return False def resource_matches( applicable_resources: str, resource: str # pylint: disable=unused-argument ) -> bool: if applicable_resources == "*": return True return False