diff --git a/lib/deploy/stepFunctions/compileIamRole.js b/lib/deploy/stepFunctions/compileIamRole.js index f0fcc361..af276336 100644 --- a/lib/deploy/stepFunctions/compileIamRole.js +++ b/lib/deploy/stepFunctions/compileIamRole.js @@ -636,13 +636,13 @@ function getHttpInvokePermissions(state) { ]; } -function getEventBridgeSchedulerPermissions(state) { - const scheduleGroupName = state.Parameters.GroupName; - const scheduleTargetRoleArn = state.Parameters.Target.RoleArn; +function getEventBridgeSchedulerPermissions(action, state) { + const scheduleGroupName = state.Parameters.GroupName || 'default'; + const scheduleTargetRoleArn = state.Parameters.Target && state.Parameters.Target.RoleArn; return [ { - action: 'scheduler:CreateSchedule', + action, resource: { 'Fn::Sub': [ 'arn:${AWS::Partition}:scheduler:${AWS::Region}:${AWS::AccountId}:schedule/${scheduleGroupName}/*', @@ -650,10 +650,11 @@ function getEventBridgeSchedulerPermissions(state) { ], }, }, - { + // createSchedule needs a target role; deleteSchedule does not + ...(scheduleTargetRoleArn ? [{ action: 'iam:PassRole', resource: scheduleTargetRoleArn, - }, + }] : []), ]; } @@ -851,7 +852,9 @@ function getIamPermissions(taskStates) { return getEventBridgePermissions(state); case 'arn:aws:states:::aws-sdk:scheduler:createSchedule': - return getEventBridgeSchedulerPermissions(state); + return getEventBridgeSchedulerPermissions('scheduler:CreateSchedule', state); + case 'arn:aws:states:::aws-sdk:scheduler:deleteSchedule': + return getEventBridgeSchedulerPermissions('scheduler:DeleteSchedule', state); case 'arn:aws:states:::http:invoke': return getHttpInvokePermissions(state); diff --git a/lib/deploy/stepFunctions/compileIamRole.test.js b/lib/deploy/stepFunctions/compileIamRole.test.js index 2f3765e4..0fd0bec9 100644 --- a/lib/deploy/stepFunctions/compileIamRole.test.js +++ b/lib/deploy/stepFunctions/compileIamRole.test.js @@ -4476,6 +4476,87 @@ describe('#compileIamRole', () => { expect(rolePermissions[0].Resource).to.deep.eq(['arn:aws:iam::${AWS::AccountId}:role/MyIAMRole']); }); + it('should give event bridge scheduler deleteSchedule permissions', () => { + const genStateMachine = id => ({ + id, + definition: { + StartAt: 'A', + States: { + A: { + Type: 'Task', + Resource: 'arn:aws:states:::aws-sdk:scheduler:deleteSchedule', + Parameters: { + 'Name.$': '$$.Execution.Name', + GroupName: 'MyScheduleGroup', + }, + 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 schedulerPermissions = statements.filter(s => _.isEqual(s.Action, ['scheduler:DeleteSchedule'])); + expect(schedulerPermissions[0].Resource).to.has.lengthOf(1); + expect(schedulerPermissions[0].Resource).to.deep.eq([{ + 'Fn::Sub': [ + 'arn:${AWS::Partition}:scheduler:${AWS::Region}:${AWS::AccountId}:schedule/${scheduleGroupName}/*', + { scheduleGroupName: 'MyScheduleGroup' }, + ], + }]); + const rolePermissions = statements.filter(s => _.isEqual(s.Action, ['iam:PassRole'])); + expect(rolePermissions).to.have.lengthOf(0); + }); + + it('should give event bridge scheduler deleteSchedule permissions with default group', () => { + const genStateMachine = id => ({ + id, + definition: { + StartAt: 'A', + States: { + A: { + Type: 'Task', + Resource: 'arn:aws:states:::aws-sdk:scheduler:deleteSchedule', + Parameters: { + 'Name.$': '$$.Execution.Name', + }, + 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 schedulerPermissions = statements.filter(s => _.isEqual(s.Action, ['scheduler:DeleteSchedule'])); + expect(schedulerPermissions[0].Resource).to.has.lengthOf(1); + expect(schedulerPermissions[0].Resource).to.deep.eq([{ + 'Fn::Sub': [ + 'arn:${AWS::Partition}:scheduler:${AWS::Region}:${AWS::AccountId}:schedule/${scheduleGroupName}/*', + { scheduleGroupName: 'default' }, + ], + }]); + }); + it('should give http:invoke permissions with static ConnectionArn', () => { serverless.service.stepFunctions = { stateMachines: {