Skip to content

Commit

Permalink
Merge pull request #1602 from awslabs/develop
Browse files Browse the repository at this point in the history
Chore: Release 1.24.0
  • Loading branch information
jfuss authored May 27, 2020
2 parents 0a3419e + 153e06b commit d17bc09
Show file tree
Hide file tree
Showing 143 changed files with 19,503 additions and 180 deletions.
44 changes: 2 additions & 42 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,54 +1,17 @@
NAME=samtranslator27
PYTHON_VERSION=2.7.14
CODE_COVERAGE=95
PYENV := $(shell command -v pyenv 2> /dev/null)

target:
$(info ${HELP_MESSAGE})
@exit 0

# Make sure that pyenv is configured properly and that we can use it in our setup target.
validation:
ifndef PYENV
$(error "make sure pyenv is accessible in your path, (usually by adding to PATH variable in bash_profile, zshrc, or other locations based on your platform) See: https://github.com/pyenv/pyenv#installation for the installation insructions.")
endif
ifndef PYENV_SHELL
$(error "Add 'pyenv init' to your shell to enable shims and autocompletion, (usually by adding to your bash_profile, zshrc, or other locations based on your platform)")
endif
ifndef PYENV_VIRTUALENV_INIT
$(error "Add 'pyenv virtualenv-init' to your shell to enable shims and autocompletion, (usually by adding to your bash_profile, zshrc, or other locations based on your platform)")
endif

install:
$(info [*] Install pyenv using https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer...)
@curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer 2> /dev/null | bash

setup: validation
$(info [*] Download and install python $(PYTHON_VERSION)...)
@pyenv install $(PYTHON_VERSION)
@pyenv local $(PYTHON_VERSION)
$(info [*] Create virtualenv $(NAME) using python $(PYTHON_VERSION)...)
@pyenv virtualenv $(PYTHON_VERSION) $(NAME)
@$(MAKE) activate

activate:
$(info [*] Activate virtualenv $(NAME)...)
$(shell eval "$$(pyenv init -)" && eval "$$(pyenv virtualenv-init -)" && pyenv activate $(NAME) && pyenv local $(NAME))

init:
$(info [*] Install requirements...)
@pip install -r requirements/dev.txt -r requirements/base.txt
pip install -e '.[dev]'

test:
$(info [*] Run the unit test with minimum code coverage of $(CODE_COVERAGE)%...)
@pytest --cov samtranslator --cov-report term-missing --cov-fail-under $(CODE_COVERAGE) tests
pytest --cov samtranslator --cov-report term-missing --cov-fail-under 95 tests

black:
rm -f tests/*.pyc samtranslator/*.pyc
black setup.py samtranslator/* tests/* bin/*

black-check:
rm -f tests/*.pyc samtranslator/*.pyc
black --check setup.py samtranslator/* tests/* bin/*

# Command to run everytime you make changes to verify everything works
Expand All @@ -62,9 +25,6 @@ define HELP_MESSAGE
Usage: $ make [TARGETS]

TARGETS
install Install pyenv using the pyenv-installer.
setup Download, install and activate a virtualenv for this project.
activate Activate the virtual environment for this project.
init Initialize and install the requirements and dev-requirements for this project.
test Run the Unit tests.
dev Run all development tests after a change.
Expand Down
2 changes: 1 addition & 1 deletion samtranslator/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.23.0"
__version__ = "1.24.0"
30 changes: 30 additions & 0 deletions samtranslator/model/iam.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,36 @@ class IAMRole(Resource):


class IAMRolePolicies:
@classmethod
def construct_assume_role_policy_for_service_principal(cls, service_principal):
document = {
"Version": "2012-10-17",
"Statement": [
{"Action": ["sts:AssumeRole"], "Effect": "Allow", "Principal": {"Service": [service_principal]},}
],
}
return document

@classmethod
def step_functions_start_execution_role_policy(cls, state_machine_arn, logical_id):
document = {
"PolicyName": logical_id + "StartExecutionPolicy",
"PolicyDocument": {
"Statement": [{"Action": "states:StartExecution", "Effect": "Allow", "Resource": state_machine_arn}]
},
}
return document

@classmethod
def stepfunctions_assume_role_policy(cls):
document = {
"Version": "2012-10-17",
"Statement": [
{"Action": ["sts:AssumeRole"], "Effect": "Allow", "Principal": {"Service": ["states.amazonaws.com"]},}
],
}
return document

@classmethod
def cloud_watch_log_assume_role_policy(cls):
document = {
Expand Down
200 changes: 200 additions & 0 deletions samtranslator/model/resource_policies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
from enum import Enum
from collections import namedtuple

from six import string_types

from samtranslator.model.intrinsics import is_intrinsic, is_intrinsic_if, is_intrinsic_no_value
from samtranslator.model.exceptions import InvalidTemplateException

PolicyEntry = namedtuple("PolicyEntry", "data type")


class ResourcePolicies(object):
"""
Class encapsulating the policies property of SAM resources. This class strictly encapsulates the data
and does not take opinions on how to handle them.
There are three types of policies:
- Policy Statements
- AWS or Custom Managed Policy names/arns
- Policy Templates
This class is capable of parsing and detecting the type of the policy. Optionally, if policy template information
is provided to this class, it will detect Policy Templates too.
"""

POLICIES_PROPERTY_NAME = "Policies"

def __init__(self, resource_properties, policy_template_processor=None):
"""
Initialize with policies data from resource's properties
:param dict resource_properties: Dictionary containing properties of this resource
:param policy_template_processor: Optional Instance of PolicyTemplateProcessor that can conclusively detect
if a given policy is a template or not. If not provided, then this class will not detect policy templates.
"""

# This variable is required to get policies
self._policy_template_processor = policy_template_processor

# Build the list of policies upon construction.
self.policies = self._get_policies(resource_properties)

def get(self):
"""
Iterator method that "yields" the next policy entry on subsequent calls to this method.
:yields namedtuple("data", "type"): Yields a named tuple containing the policy data and its type
"""

for policy_tuple in self.policies:
yield policy_tuple

def __len__(self):
return len(self.policies)

def _get_policies(self, resource_properties):
"""
Returns a list of policies from the resource properties. This method knows how to interpret and handle
polymorphic nature of the policies property.
Policies can be one of the following:
* Managed policy name: string
* List of managed policy names: list of strings
* IAM Policy document: dict containing Statement key
* List of IAM Policy documents: list of IAM Policy Document
* Policy Template: dict with only one key where key is in list of supported policy template names
* List of Policy Templates: list of Policy Template
:param dict resource_properties: Dictionary of resource properties containing the policies property.
It is assumed that this is already a dictionary and contains policies key.
:return list of PolicyEntry: List of policies, where each item is an instance of named tuple `PolicyEntry`
"""

policies = None

if self._contains_policies(resource_properties):
policies = resource_properties[self.POLICIES_PROPERTY_NAME]

if not policies:
# Policies is None or empty
return []

if not isinstance(policies, list):
# Just a single entry. Make it into a list of convenience
policies = [policies]

result = []
for policy in policies:
policy_type = self._get_type(policy)
entry = PolicyEntry(data=policy, type=policy_type)
result.append(entry)

return result

def _contains_policies(self, resource_properties):
"""
Is there policies data in this resource?
:param dict resource_properties: Properties of the resource
:return: True if we can process this resource. False, otherwise
"""
return (
resource_properties is not None
and isinstance(resource_properties, dict)
and self.POLICIES_PROPERTY_NAME in resource_properties
)

def _get_type(self, policy):
"""
Returns the type of the given policy
:param string or dict policy: Policy data
:return PolicyTypes: Type of the given policy. None, if type could not be inferred
"""

# Must handle intrinsic functions. Policy could be a primitive type or an intrinsic function

# Managed policies are of type string
if isinstance(policy, string_types):
return PolicyTypes.MANAGED_POLICY

# Handle the special case for 'if' intrinsic function
if is_intrinsic_if(policy):
return self._get_type_from_intrinsic_if(policy)

# Intrinsic functions are treated as managed policies by default
if is_intrinsic(policy):
return PolicyTypes.MANAGED_POLICY

# Policy statement is a dictionary with the key "Statement" in it
if isinstance(policy, dict) and "Statement" in policy:
return PolicyTypes.POLICY_STATEMENT

# This could be a policy template then.
if self._is_policy_template(policy):
return PolicyTypes.POLICY_TEMPLATE

# Nothing matches. Don't take opinions on how to handle it. Instead just set the appropriate type.
return PolicyTypes.UNKNOWN

def _is_policy_template(self, policy):
"""
Is the given policy data a policy template? Policy templates is a dictionary with one key which is the name
of the template.
:param dict policy: Policy data
:return: True, if this is a policy template. False if it is not
"""

return (
self._policy_template_processor is not None
and isinstance(policy, dict)
and len(policy) == 1
and self._policy_template_processor.has(list(policy.keys())[0]) is True
)

def _get_type_from_intrinsic_if(self, policy):
"""
Returns the type of the given policy assuming that it is an intrinsic if function
:param policy: Input value to get type from
:return: PolicyTypes: Type of the given policy. PolicyTypes.UNKNOWN, if type could not be inferred
"""
intrinsic_if_value = policy["Fn::If"]

if not len(intrinsic_if_value) == 3:
raise InvalidTemplateException("Fn::If requires 3 arguments")

if_data = intrinsic_if_value[1]
else_data = intrinsic_if_value[2]

if_data_type = self._get_type(if_data)
else_data_type = self._get_type(else_data)

if if_data_type == else_data_type:
return if_data_type

if is_intrinsic_no_value(if_data):
return else_data_type

if is_intrinsic_no_value(else_data):
return if_data_type

raise InvalidTemplateException(
"Different policy types within the same Fn::If statement is unsupported. "
"Separate different policy types into different Fn::If statements"
)


class PolicyTypes(Enum):
"""
Enum of different policy types supported by SAM & this plugin
"""

MANAGED_POLICY = "managed_policy"
POLICY_STATEMENT = "policy_statement"
POLICY_TEMPLATE = "policy_template"
UNKNOWN = "unknown"
1 change: 1 addition & 0 deletions samtranslator/model/role_utils/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .role_constructor import construct_role_for_resource
Loading

0 comments on commit d17bc09

Please sign in to comment.