/samtranslator/model/resource_policies.py

https://github.com/awslabs/serverless-application-model · Python · 200 lines · 81 code · 28 blank · 91 comment · 16 complexity · c53dfa714de45e1400d490a3193efa40 MD5 · raw file

  1. from enum import Enum
  2. from collections import namedtuple
  3. from six import string_types
  4. from samtranslator.model.intrinsics import is_intrinsic, is_intrinsic_if, is_intrinsic_no_value
  5. from samtranslator.model.exceptions import InvalidTemplateException
  6. PolicyEntry = namedtuple("PolicyEntry", "data type")
  7. class ResourcePolicies(object):
  8. """
  9. Class encapsulating the policies property of SAM resources. This class strictly encapsulates the data
  10. and does not take opinions on how to handle them.
  11. There are three types of policies:
  12. - Policy Statements
  13. - AWS or Custom Managed Policy names/arns
  14. - Policy Templates
  15. This class is capable of parsing and detecting the type of the policy. Optionally, if policy template information
  16. is provided to this class, it will detect Policy Templates too.
  17. """
  18. POLICIES_PROPERTY_NAME = "Policies"
  19. def __init__(self, resource_properties, policy_template_processor=None):
  20. """
  21. Initialize with policies data from resource's properties
  22. :param dict resource_properties: Dictionary containing properties of this resource
  23. :param policy_template_processor: Optional Instance of PolicyTemplateProcessor that can conclusively detect
  24. if a given policy is a template or not. If not provided, then this class will not detect policy templates.
  25. """
  26. # This variable is required to get policies
  27. self._policy_template_processor = policy_template_processor
  28. # Build the list of policies upon construction.
  29. self.policies = self._get_policies(resource_properties)
  30. def get(self):
  31. """
  32. Iterator method that "yields" the next policy entry on subsequent calls to this method.
  33. :yields namedtuple("data", "type"): Yields a named tuple containing the policy data and its type
  34. """
  35. for policy_tuple in self.policies:
  36. yield policy_tuple
  37. def __len__(self):
  38. return len(self.policies)
  39. def _get_policies(self, resource_properties):
  40. """
  41. Returns a list of policies from the resource properties. This method knows how to interpret and handle
  42. polymorphic nature of the policies property.
  43. Policies can be one of the following:
  44. * Managed policy name: string
  45. * List of managed policy names: list of strings
  46. * IAM Policy document: dict containing Statement key
  47. * List of IAM Policy documents: list of IAM Policy Document
  48. * Policy Template: dict with only one key where key is in list of supported policy template names
  49. * List of Policy Templates: list of Policy Template
  50. :param dict resource_properties: Dictionary of resource properties containing the policies property.
  51. It is assumed that this is already a dictionary and contains policies key.
  52. :return list of PolicyEntry: List of policies, where each item is an instance of named tuple `PolicyEntry`
  53. """
  54. policies = None
  55. if self._contains_policies(resource_properties):
  56. policies = resource_properties[self.POLICIES_PROPERTY_NAME]
  57. if not policies:
  58. # Policies is None or empty
  59. return []
  60. if not isinstance(policies, list):
  61. # Just a single entry. Make it into a list of convenience
  62. policies = [policies]
  63. result = []
  64. for policy in policies:
  65. policy_type = self._get_type(policy)
  66. entry = PolicyEntry(data=policy, type=policy_type)
  67. result.append(entry)
  68. return result
  69. def _contains_policies(self, resource_properties):
  70. """
  71. Is there policies data in this resource?
  72. :param dict resource_properties: Properties of the resource
  73. :return: True if we can process this resource. False, otherwise
  74. """
  75. return (
  76. resource_properties is not None
  77. and isinstance(resource_properties, dict)
  78. and self.POLICIES_PROPERTY_NAME in resource_properties
  79. )
  80. def _get_type(self, policy):
  81. """
  82. Returns the type of the given policy
  83. :param string or dict policy: Policy data
  84. :return PolicyTypes: Type of the given policy. None, if type could not be inferred
  85. """
  86. # Must handle intrinsic functions. Policy could be a primitive type or an intrinsic function
  87. # Managed policies are of type string
  88. if isinstance(policy, string_types):
  89. return PolicyTypes.MANAGED_POLICY
  90. # Handle the special case for 'if' intrinsic function
  91. if is_intrinsic_if(policy):
  92. return self._get_type_from_intrinsic_if(policy)
  93. # Intrinsic functions are treated as managed policies by default
  94. if is_intrinsic(policy):
  95. return PolicyTypes.MANAGED_POLICY
  96. # Policy statement is a dictionary with the key "Statement" in it
  97. if isinstance(policy, dict) and "Statement" in policy:
  98. return PolicyTypes.POLICY_STATEMENT
  99. # This could be a policy template then.
  100. if self._is_policy_template(policy):
  101. return PolicyTypes.POLICY_TEMPLATE
  102. # Nothing matches. Don't take opinions on how to handle it. Instead just set the appropriate type.
  103. return PolicyTypes.UNKNOWN
  104. def _is_policy_template(self, policy):
  105. """
  106. Is the given policy data a policy template? Policy templates is a dictionary with one key which is the name
  107. of the template.
  108. :param dict policy: Policy data
  109. :return: True, if this is a policy template. False if it is not
  110. """
  111. return (
  112. self._policy_template_processor is not None
  113. and isinstance(policy, dict)
  114. and len(policy) == 1
  115. and self._policy_template_processor.has(list(policy.keys())[0]) is True
  116. )
  117. def _get_type_from_intrinsic_if(self, policy):
  118. """
  119. Returns the type of the given policy assuming that it is an intrinsic if function
  120. :param policy: Input value to get type from
  121. :return: PolicyTypes: Type of the given policy. PolicyTypes.UNKNOWN, if type could not be inferred
  122. """
  123. intrinsic_if_value = policy["Fn::If"]
  124. if not len(intrinsic_if_value) == 3:
  125. raise InvalidTemplateException("Fn::If requires 3 arguments")
  126. if_data = intrinsic_if_value[1]
  127. else_data = intrinsic_if_value[2]
  128. if_data_type = self._get_type(if_data)
  129. else_data_type = self._get_type(else_data)
  130. if if_data_type == else_data_type:
  131. return if_data_type
  132. if is_intrinsic_no_value(if_data):
  133. return else_data_type
  134. if is_intrinsic_no_value(else_data):
  135. return if_data_type
  136. raise InvalidTemplateException(
  137. "Different policy types within the same Fn::If statement is unsupported. "
  138. "Separate different policy types into different Fn::If statements"
  139. )
  140. class PolicyTypes(Enum):
  141. """
  142. Enum of different policy types supported by SAM & this plugin
  143. """
  144. MANAGED_POLICY = "managed_policy"
  145. POLICY_STATEMENT = "policy_statement"
  146. POLICY_TEMPLATE = "policy_template"
  147. UNKNOWN = "unknown"