diff --git a/lib/deploy/stepFunctions/compileIamRole.js b/lib/deploy/stepFunctions/compileIamRole.js index 30a5f470..5b3ed809 100644 --- a/lib/deploy/stepFunctions/compileIamRole.js +++ b/lib/deploy/stepFunctions/compileIamRole.js @@ -440,6 +440,39 @@ function getStepFunctionsPermissions(state) { }]; } +function getStepFunctionsSDKPermissions(state) { + let stateMachineArn = state.Mode === 'DISTRIBUTED' ? { + 'Fn::Sub': [ + `arn:aws:states:\${AWS::Region}:\${AWS::AccountId}:stateMachine:${state.StateMachineName}`, + {}, + ], + } : null; + + if (!stateMachineArn) { + stateMachineArn = state.Parameters['StateMachineArn.$'] ? '*' + : state.Parameters.StateMachineArn; + } + + return [{ + action: 'states:StartSyncExecution', + resource: stateMachineArn, + }, { + action: 'states:DescribeExecution,states:StopExecution', + // this is excessive but mirrors behaviour in the console + // also, DescribeExecution supports executions as resources but StopExecution + // doesn't support resources + resource: '*', + }, { + action: 'events:PutTargets,events:PutRule,events:DescribeRule', + resource: { + 'Fn::Sub': [ + 'arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:rule/StepFunctionsGetEventsForStepFunctionsExecutionRule', + {}, + ], + }, + }]; +} + function getCodeBuildPermissions(state) { const projectName = state.Parameters.ProjectName; @@ -640,6 +673,9 @@ function getIamPermissions(taskStates) { case 'arn:aws:states:::states:startExecution.waitForTaskToken': return getStepFunctionsPermissions(state); + case 'arn:aws:states:::aws-sdk:sfn:startSyncExecution': + return getStepFunctionsSDKPermissions(state); + case 'arn:aws:states:::codebuild:startBuild': case 'arn:aws:states:::codebuild:startBuild.sync': return getCodeBuildPermissions(state); diff --git a/lib/deploy/stepFunctions/compileIamRole.test.js b/lib/deploy/stepFunctions/compileIamRole.test.js index 21329ce2..f72480e4 100644 --- a/lib/deploy/stepFunctions/compileIamRole.test.js +++ b/lib/deploy/stepFunctions/compileIamRole.test.js @@ -2950,6 +2950,55 @@ describe('#compileIamRole', () => { }]); }); + it('should give step functions using sdk permissions (too permissive, but mirrors console behavior)', () => { + const stateMachineArn = 'arn:aws:states:us-east-1:123456789:stateMachine:HelloStateMachine'; + const genStateMachine = id => ({ + id, + definition: { + StartAt: 'A', + States: { + A: { + Type: 'Task', + Resource: 'arn:aws:states:::aws-sdk:sfn:startSyncExecution', + Parameters: { + StateMachineArn: stateMachineArn, + Input: {}, + }, + End: true, + }, + }, + }, + }); + + serverless.service.stepFunctions = { + stateMachines: { + myStateMachine1: genStateMachine('StateMachine1'), + }, + }; + + serverlessStepFunctions.compileIamRole(); + const statements = serverlessStepFunctions.serverless.service + .provider.compiledCloudFormationTemplate.Resources.StateMachine1Role + .Properties.Policies[0].PolicyDocument.Statement; + + const stateMachinePermissions = statements.filter(s => _.isEqual(s.Action, ['states:StartSyncExecution'])); + expect(stateMachinePermissions).to.have.lengthOf(1); + expect(stateMachinePermissions[0].Resource).to.deep.eq([stateMachineArn]); + + const executionPermissions = statements.filter(s => _.isEqual(s.Action, ['states:DescribeExecution', 'states:StopExecution'])); + expect(executionPermissions).to.have.lengthOf(1); + expect(executionPermissions[0].Resource).to.equal('*'); + + const eventPermissions = statements.filter(s => _.isEqual(s.Action, ['events:PutTargets', 'events:PutRule', 'events:DescribeRule'])); + expect(eventPermissions).to.have.lengthOf(1); + expect(eventPermissions[0].Resource).to.deep.eq([{ + 'Fn::Sub': [ + 'arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:rule/StepFunctionsGetEventsForStepFunctionsExecutionRule', + {}, + ], + }]); + }); + it('should give step functions permission to * whenever StateMachineArn.$ is seen', () => { const stateMachineArn = 'arn:aws:states:us-east-1:123456789:stateMachine:HelloStateMachine'; const genStateMachine = id => ({ @@ -3004,6 +3053,42 @@ describe('#compileIamRole', () => { expect(stateMachinePermissions[0].Resource).to.equal('*'); }); + it('should give step functions using sdk permission to * whenever StateMachineArn.$ is seen', () => { + const genStateMachine = id => ({ + id, + definition: { + StartAt: 'A', + States: { + A: { + Type: 'Task', + Resource: 'arn:aws:states:::aws-sdk:sfn:startSyncExecution', + Parameters: { + 'StateMachineArn.$': '$.arn', + Input: {}, + }, + End: true, + }, + }, + }, + }); + + serverless.service.stepFunctions = { + stateMachines: { + myStateMachine1: genStateMachine('StateMachine1'), + }, + }; + + serverlessStepFunctions.compileIamRole(); + const statements = serverlessStepFunctions.serverless.service + .provider.compiledCloudFormationTemplate.Resources.StateMachine1Role + .Properties.Policies[0].PolicyDocument.Statement; + + const stateMachinePermissions = statements.filter(s => _.includes(s.Action, 'states:StartSyncExecution')); + + expect(stateMachinePermissions).to.have.lengthOf(1); + expect(stateMachinePermissions[0].Resource).to.equal('*'); + }); + it('should support Map state type', () => { const getStateMachine = (id, lambdaArn) => ({ id,