diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index 97fdcbfc0c..a0bdc5bec0 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -1120,6 +1120,7 @@ class SamStateMachine(SamResourceMacro): "Tags": PropertyType(False, is_type(dict)), "Policies": PropertyType(False, one_of(is_str(), list_of(one_of(is_str(), is_type(dict), is_type(dict))))), "Tracing": PropertyType(False, is_type(dict)), + "PermissionsBoundary": PropertyType(False, is_str()), } event_resolver = ResourceTypeResolver( samtranslator.model.stepfunctions.events, @@ -1140,6 +1141,7 @@ def to_cloudformation(self, **kwargs): logging=self.Logging, name=self.Name, policies=self.Policies, + permissions_boundary=self.PermissionsBoundary, definition_substitutions=self.DefinitionSubstitutions, role=self.Role, state_machine_type=self.Type, diff --git a/samtranslator/model/stepfunctions/events.py b/samtranslator/model/stepfunctions/events.py index 290fd75f0e..e02332e20b 100644 --- a/samtranslator/model/stepfunctions/events.py +++ b/samtranslator/model/stepfunctions/events.py @@ -42,11 +42,12 @@ def _generate_logical_id(self, prefix, suffix, resource_type): logical_id = generator.gen() return logical_id - def _construct_role(self, resource, prefix=None, suffix=""): + def _construct_role(self, resource, permissions_boundary=None, prefix=None, suffix=""): """Constructs the IAM Role resource allowing the event service to invoke the StartExecution API of the state machine resource it is associated with. :param model.stepfunctions.StepFunctionsStateMachine resource: The state machine resource associated with the event + :param string permissions_boundary: The ARN of the policy used to set the permissions boundary for the role :param string prefix: Prefix to use for the logical ID of the IAM role :param string suffix: Suffix to add for the logical ID of the IAM role @@ -63,6 +64,9 @@ def _construct_role(self, resource, prefix=None, suffix=""): IAMRolePolicies.step_functions_start_execution_role_policy(state_machine_arn, role_logical_id) ] + if permissions_boundary: + event_role.PermissionsBoundary = permissions_boundary + return event_role @@ -88,6 +92,8 @@ def to_cloudformation(self, resource, **kwargs): """ resources = [] + permissions_boundary = kwargs.get("permissions_boundary") + events_rule = EventsRule(self.logical_id) resources.append(events_rule) @@ -99,7 +105,7 @@ def to_cloudformation(self, resource, **kwargs): if CONDITION in resource.resource_attributes: events_rule.set_resource_attribute(CONDITION, resource.resource_attributes[CONDITION]) - role = self._construct_role(resource) + role = self._construct_role(resource, permissions_boundary) resources.append(role) events_rule.Targets = [self._construct_target(resource, role)] @@ -144,6 +150,8 @@ def to_cloudformation(self, resource, **kwargs): """ resources = [] + permissions_boundary = kwargs.get("permissions_boundary") + events_rule = EventsRule(self.logical_id) events_rule.EventBusName = self.EventBusName events_rule.EventPattern = self.Pattern @@ -152,7 +160,7 @@ def to_cloudformation(self, resource, **kwargs): resources.append(events_rule) - role = self._construct_role(resource) + role = self._construct_role(resource, permissions_boundary) resources.append(role) events_rule.Targets = [self._construct_target(resource, role)] @@ -243,9 +251,9 @@ def resources_to_link(self, resources): return {"explicit_api": explicit_api, "explicit_api_stage": {"suffix": stage_suffix}} def to_cloudformation(self, resource, **kwargs): - """If the Api event source has a RestApi property, then simply return the IAM role resource - allowing API Gateway to start the state machine execution. If no RestApi is provided, then - additionally inject the path, method, and the x-amazon-apigateway-integration into the + """If the Api event source has a RestApi property, then simply return the IAM role resource + allowing API Gateway to start the state machine execution. If no RestApi is provided, then + additionally inject the path, method, and the x-amazon-apigateway-integration into the Swagger body for a provided implicit API. :param model.stepfunctions.resources.StepFunctionsStateMachine resource; the state machine \ @@ -259,12 +267,13 @@ def to_cloudformation(self, resource, **kwargs): resources = [] intrinsics_resolver = kwargs.get("intrinsics_resolver") + permissions_boundary = kwargs.get("permissions_boundary") if self.Method is not None: # Convert to lower case so that user can specify either GET or get self.Method = self.Method.lower() - role = self._construct_role(resource) + role = self._construct_role(resource, permissions_boundary) resources.append(role) explicit_api = kwargs["explicit_api"] diff --git a/samtranslator/model/stepfunctions/generators.py b/samtranslator/model/stepfunctions/generators.py index 1170cb18b1..726458086c 100644 --- a/samtranslator/model/stepfunctions/generators.py +++ b/samtranslator/model/stepfunctions/generators.py @@ -37,6 +37,7 @@ def __init__( logging, name, policies, + permissions_boundary, definition_substitutions, role, state_machine_type, @@ -60,6 +61,7 @@ def __init__( :param logging: Logging configuration for the State Machine :param name: Name of the State Machine resource :param policies: Policies attached to the execution role + :param permissions_boundary: The ARN of the policy used to set the permissions boundary for the role :param definition_substitutions: Variable-to-value mappings to be replaced in the State Machine definition :param role: Role ARN to use for the execution role :param state_machine_type: Type of the State Machine @@ -82,6 +84,7 @@ def __init__( self.name = name self.logging = logging self.policies = policies + self.permissions_boundary = permissions_boundary self.definition_substitutions = definition_substitutions self.role = role self.type = state_machine_type @@ -220,6 +223,7 @@ def _construct_role(self): assume_role_policy_document=IAMRolePolicies.stepfunctions_assume_role_policy(), resource_policies=state_machine_policies, tags=self._construct_tag_list(), + permissions_boundary=self.permissions_boundary, ) return execution_role @@ -242,7 +246,10 @@ def _generate_event_resources(self): resources = [] if self.events: for logical_id, event_dict in self.events.items(): - kwargs = {"intrinsics_resolver": self.intrinsics_resolver} + kwargs = { + "intrinsics_resolver": self.intrinsics_resolver, + "permissions_boundary": self.permissions_boundary, + } try: eventsource = self.event_resolver.resolve_resource_type(event_dict).from_dict( self.state_machine.logical_id + logical_id, event_dict, logical_id diff --git a/tests/model/stepfunctions/test_state_machine_generator.py b/tests/model/stepfunctions/test_state_machine_generator.py index 762242fa7a..39f6883674 100644 --- a/tests/model/stepfunctions/test_state_machine_generator.py +++ b/tests/model/stepfunctions/test_state_machine_generator.py @@ -19,6 +19,7 @@ def setUp(self): "logging": None, "name": None, "policies": None, + "permissions_boundary": None, "definition_substitutions": None, "role": None, "state_machine_type": None, diff --git a/tests/translator/input/state_machine_with_permissions_boundary.yaml b/tests/translator/input/state_machine_with_permissions_boundary.yaml new file mode 100644 index 0000000000..5d86d465bb --- /dev/null +++ b/tests/translator/input/state_machine_with_permissions_boundary.yaml @@ -0,0 +1,39 @@ +Resources: + MyFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Handler: hello.handler + Runtime: python2.7 + ReservedConcurrentExecutions: 100 + + StateMachine: + Type: AWS::Serverless::StateMachine + Properties: + Name: MyStateMachine + Events: + ScheduleEvent: + Type: Schedule + Properties: + Schedule: "rate(1 minute)" + Name: TestSchedule + CWEvent: + Type: CloudWatchEvent + Properties: + Pattern: + detail: + state: + - terminated + MyApiEvent: + Type: Api + Properties: + Path: /startMyExecution + Method: post + DefinitionUri: + Bucket: sam-demo-bucket + Key: my-state-machine.asl.json + Version: 3 + PermissionsBoundary: arn:aws:1234:iam:boundary/CustomerCreatedPermissionsBoundary + Policies: + - LambdaInvokePolicy: + FunctionName: !Ref MyFunction \ No newline at end of file diff --git a/tests/translator/output/aws-cn/state_machine_with_permissions_boundary.json b/tests/translator/output/aws-cn/state_machine_with_permissions_boundary.json new file mode 100644 index 0000000000..645e7d5a30 --- /dev/null +++ b/tests/translator/output/aws-cn/state_machine_with_permissions_boundary.json @@ -0,0 +1,380 @@ +{ + "Resources": { + "MyFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ], + "ReservedConcurrentExecutions": 100, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "MyFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7" + } + }, + "StateMachineMyApiEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "PermissionsBoundary": "arn:aws:1234:iam:boundary/CustomerCreatedPermissionsBoundary", + "Policies": [ + { + "PolicyName": "StateMachineMyApiEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "apigateway.amazonaws.com" + ] + } + } + ] + } + } + }, + "StateMachine": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineRole", + "Arn" + ] + }, + "StateMachineName": "MyStateMachine", + "DefinitionS3Location": { + "Version": 3, + "Bucket": "sam-demo-bucket", + "Key": "my-state-machine.asl.json" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "stateMachine:createdBy" + } + ] + } + }, + "ServerlessRestApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "ServerlessRestApiDeployment05bc9f394c" + }, + "RestApiId": { + "Ref": "ServerlessRestApi" + }, + "StageName": "Prod" + } + }, + "ServerlessRestApiDeployment05bc9f394c": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "ServerlessRestApi" + }, + "Description": "RestApi deployment id: 05bc9f394c3ca5d24b8d6dc69d133762afd1298e", + "StageName": "Stage" + } + }, + "StateMachineCWEvent": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "Targets": [ + { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineCWEventRole", + "Arn" + ] + }, + "Id": "StateMachineCWEventStepFunctionsTarget", + "Arn": { + "Ref": "StateMachine" + } + } + ] + } + }, + "MyFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ServerlessRestApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/startMyExecution": { + "post": { + "x-amazon-apigateway-integration": { + "responses": { + "200": { + "statusCode": "200" + }, + "400": { + "statusCode": "400" + } + }, + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:states:action/StartExecution" + }, + "httpMethod": "POST", + "requestTemplates": { + "application/json": { + "Fn::Sub": "{\"input\": \"$util.escapeJavaScript($input.json('$'))\", \"stateMachineArn\": \"${StateMachine}\"}" + } + }, + "credentials": { + "Fn::GetAtt": [ + "StateMachineMyApiEventRole", + "Arn" + ] + }, + "type": "aws" + }, + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + } + }, + "swagger": "2.0" + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "StateMachineRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "states.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [], + "PermissionsBoundary": "arn:aws:1234:iam:boundary/CustomerCreatedPermissionsBoundary", + "Policies": [ + { + "PolicyName": "StateMachineRolePolicy0", + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "lambda:InvokeFunction" + ], + "Resource": { + "Fn::Sub": [ + "arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:${functionName}*", + { + "functionName": { + "Ref": "MyFunction" + } + } + ] + }, + "Effect": "Allow" + } + ] + } + } + ], + "Tags": [ + { + "Value": "SAM", + "Key": "stateMachine:createdBy" + } + ] + } + }, + "StateMachineScheduleEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "PermissionsBoundary": "arn:aws:1234:iam:boundary/CustomerCreatedPermissionsBoundary", + "Policies": [ + { + "PolicyName": "StateMachineScheduleEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "events.amazonaws.com" + ] + } + } + ] + } + } + }, + "StateMachineScheduleEvent": { + "Type": "AWS::Events::Rule", + "Properties": { + "ScheduleExpression": "rate(1 minute)", + "Targets": [ + { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineScheduleEventRole", + "Arn" + ] + }, + "Id": "StateMachineScheduleEventStepFunctionsTarget", + "Arn": { + "Ref": "StateMachine" + } + } + ], + "Name": "TestSchedule" + } + }, + "StateMachineCWEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "PermissionsBoundary": "arn:aws:1234:iam:boundary/CustomerCreatedPermissionsBoundary", + "Policies": [ + { + "PolicyName": "StateMachineCWEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "events.amazonaws.com" + ] + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/state_machine_with_permissions_boundary.json b/tests/translator/output/aws-us-gov/state_machine_with_permissions_boundary.json new file mode 100644 index 0000000000..550c0ae536 --- /dev/null +++ b/tests/translator/output/aws-us-gov/state_machine_with_permissions_boundary.json @@ -0,0 +1,380 @@ +{ + "Resources": { + "MyFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ], + "ReservedConcurrentExecutions": 100, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "MyFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7" + } + }, + "StateMachineMyApiEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "PermissionsBoundary": "arn:aws:1234:iam:boundary/CustomerCreatedPermissionsBoundary", + "Policies": [ + { + "PolicyName": "StateMachineMyApiEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "apigateway.amazonaws.com" + ] + } + } + ] + } + } + }, + "StateMachine": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineRole", + "Arn" + ] + }, + "StateMachineName": "MyStateMachine", + "DefinitionS3Location": { + "Version": 3, + "Bucket": "sam-demo-bucket", + "Key": "my-state-machine.asl.json" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "stateMachine:createdBy" + } + ] + } + }, + "ServerlessRestApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "ServerlessRestApiDeployment05bc9f394c" + }, + "RestApiId": { + "Ref": "ServerlessRestApi" + }, + "StageName": "Prod" + } + }, + "ServerlessRestApiDeployment05bc9f394c": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "ServerlessRestApi" + }, + "Description": "RestApi deployment id: 05bc9f394c3ca5d24b8d6dc69d133762afd1298e", + "StageName": "Stage" + } + }, + "StateMachineCWEvent": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "Targets": [ + { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineCWEventRole", + "Arn" + ] + }, + "Id": "StateMachineCWEventStepFunctionsTarget", + "Arn": { + "Ref": "StateMachine" + } + } + ] + } + }, + "MyFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ServerlessRestApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/startMyExecution": { + "post": { + "x-amazon-apigateway-integration": { + "responses": { + "200": { + "statusCode": "200" + }, + "400": { + "statusCode": "400" + } + }, + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:states:action/StartExecution" + }, + "httpMethod": "POST", + "requestTemplates": { + "application/json": { + "Fn::Sub": "{\"input\": \"$util.escapeJavaScript($input.json('$'))\", \"stateMachineArn\": \"${StateMachine}\"}" + } + }, + "credentials": { + "Fn::GetAtt": [ + "StateMachineMyApiEventRole", + "Arn" + ] + }, + "type": "aws" + }, + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + } + }, + "swagger": "2.0" + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "StateMachineRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "states.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [], + "PermissionsBoundary": "arn:aws:1234:iam:boundary/CustomerCreatedPermissionsBoundary", + "Policies": [ + { + "PolicyName": "StateMachineRolePolicy0", + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "lambda:InvokeFunction" + ], + "Resource": { + "Fn::Sub": [ + "arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:${functionName}*", + { + "functionName": { + "Ref": "MyFunction" + } + } + ] + }, + "Effect": "Allow" + } + ] + } + } + ], + "Tags": [ + { + "Value": "SAM", + "Key": "stateMachine:createdBy" + } + ] + } + }, + "StateMachineScheduleEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "PermissionsBoundary": "arn:aws:1234:iam:boundary/CustomerCreatedPermissionsBoundary", + "Policies": [ + { + "PolicyName": "StateMachineScheduleEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "events.amazonaws.com" + ] + } + } + ] + } + } + }, + "StateMachineScheduleEvent": { + "Type": "AWS::Events::Rule", + "Properties": { + "ScheduleExpression": "rate(1 minute)", + "Targets": [ + { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineScheduleEventRole", + "Arn" + ] + }, + "Id": "StateMachineScheduleEventStepFunctionsTarget", + "Arn": { + "Ref": "StateMachine" + } + } + ], + "Name": "TestSchedule" + } + }, + "StateMachineCWEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "PermissionsBoundary": "arn:aws:1234:iam:boundary/CustomerCreatedPermissionsBoundary", + "Policies": [ + { + "PolicyName": "StateMachineCWEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "events.amazonaws.com" + ] + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/state_machine_with_permissions_boundary.json b/tests/translator/output/state_machine_with_permissions_boundary.json new file mode 100644 index 0000000000..b3823b73bd --- /dev/null +++ b/tests/translator/output/state_machine_with_permissions_boundary.json @@ -0,0 +1,372 @@ +{ + "Resources": { + "MyFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ], + "ReservedConcurrentExecutions": 100, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "MyFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7" + } + }, + "StateMachineMyApiEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "PermissionsBoundary": "arn:aws:1234:iam:boundary/CustomerCreatedPermissionsBoundary", + "Policies": [ + { + "PolicyName": "StateMachineMyApiEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "apigateway.amazonaws.com" + ] + } + } + ] + } + } + }, + "StateMachine": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineRole", + "Arn" + ] + }, + "StateMachineName": "MyStateMachine", + "DefinitionS3Location": { + "Version": 3, + "Bucket": "sam-demo-bucket", + "Key": "my-state-machine.asl.json" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "stateMachine:createdBy" + } + ] + } + }, + "ServerlessRestApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "ServerlessRestApiDeployment05bc9f394c" + }, + "RestApiId": { + "Ref": "ServerlessRestApi" + }, + "StageName": "Prod" + } + }, + "ServerlessRestApiDeployment05bc9f394c": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "ServerlessRestApi" + }, + "Description": "RestApi deployment id: 05bc9f394c3ca5d24b8d6dc69d133762afd1298e", + "StageName": "Stage" + } + }, + "StateMachineCWEvent": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "Targets": [ + { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineCWEventRole", + "Arn" + ] + }, + "Id": "StateMachineCWEventStepFunctionsTarget", + "Arn": { + "Ref": "StateMachine" + } + } + ] + } + }, + "MyFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ServerlessRestApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/startMyExecution": { + "post": { + "x-amazon-apigateway-integration": { + "responses": { + "200": { + "statusCode": "200" + }, + "400": { + "statusCode": "400" + } + }, + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:states:action/StartExecution" + }, + "httpMethod": "POST", + "requestTemplates": { + "application/json": { + "Fn::Sub": "{\"input\": \"$util.escapeJavaScript($input.json('$'))\", \"stateMachineArn\": \"${StateMachine}\"}" + } + }, + "credentials": { + "Fn::GetAtt": [ + "StateMachineMyApiEventRole", + "Arn" + ] + }, + "type": "aws" + }, + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + } + } + } + } + }, + "swagger": "2.0" + } + } + }, + "StateMachineRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "states.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [], + "PermissionsBoundary": "arn:aws:1234:iam:boundary/CustomerCreatedPermissionsBoundary", + "Policies": [ + { + "PolicyName": "StateMachineRolePolicy0", + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "lambda:InvokeFunction" + ], + "Resource": { + "Fn::Sub": [ + "arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:${functionName}*", + { + "functionName": { + "Ref": "MyFunction" + } + } + ] + }, + "Effect": "Allow" + } + ] + } + } + ], + "Tags": [ + { + "Value": "SAM", + "Key": "stateMachine:createdBy" + } + ] + } + }, + "StateMachineScheduleEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "PermissionsBoundary": "arn:aws:1234:iam:boundary/CustomerCreatedPermissionsBoundary", + "Policies": [ + { + "PolicyName": "StateMachineScheduleEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "events.amazonaws.com" + ] + } + } + ] + } + } + }, + "StateMachineScheduleEvent": { + "Type": "AWS::Events::Rule", + "Properties": { + "ScheduleExpression": "rate(1 minute)", + "Targets": [ + { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineScheduleEventRole", + "Arn" + ] + }, + "Id": "StateMachineScheduleEventStepFunctionsTarget", + "Arn": { + "Ref": "StateMachine" + } + } + ], + "Name": "TestSchedule" + } + }, + "StateMachineCWEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "PermissionsBoundary": "arn:aws:1234:iam:boundary/CustomerCreatedPermissionsBoundary", + "Policies": [ + { + "PolicyName": "StateMachineCWEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "events.amazonaws.com" + ] + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/test_translator.py b/tests/translator/test_translator.py index 76f3f291d9..231b82fc42 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -300,6 +300,7 @@ class TestTranslatorEndToEnd(TestCase): "state_machine_with_condition_and_events", "state_machine_with_xray", "function_with_file_system_config", + "state_machine_with_permissions_boundary", ], [ ("aws", "ap-southeast-1"),