diff --git a/lib/deploy/stepFunctions/compileIamRole.js b/lib/deploy/stepFunctions/compileIamRole.js index d01a53e..d5cb857 100644 --- a/lib/deploy/stepFunctions/compileIamRole.js +++ b/lib/deploy/stepFunctions/compileIamRole.js @@ -464,7 +464,7 @@ function getStateMachineArn(state) { function getStepFunctionsPermissions(state) { let stateMachineArn = state.Mode === 'DISTRIBUTED' ? { 'Fn::Sub': [ - `arn:aws:states:\${AWS::Region}:\${AWS::AccountId}:stateMachine:${state.StateMachineName}`, + `arn:\${AWS::Partition}:states:\${AWS::Region}:\${AWS::AccountId}:stateMachine:${state.StateMachineName}`, {}, ], } : null; @@ -496,7 +496,7 @@ function getStepFunctionsPermissions(state) { function getStepFunctionsSDKPermissions(state) { let stateMachineArn = state.Mode === 'DISTRIBUTED' ? { 'Fn::Sub': [ - `arn:aws:states:\${AWS::Region}:\${AWS::AccountId}:stateMachine:${state.StateMachineName}`, + `arn:\${AWS::Partition}:states:\${AWS::Region}:\${AWS::AccountId}:stateMachine:${state.StateMachineName}`, {}, ], } : null; @@ -736,7 +736,12 @@ function consolidatePermissionsByResource(permissions) { function getIamPermissions(taskStates) { return _.flatMap(taskStates, (state) => { - const resourceName = typeof state.Resource === 'string' ? state.Resource.replace(/^arn:aws(-[a-z]+)*:/, 'arn:aws:') : state.Resource; + // Normalize resource ARN to handle different partition formats: + // - arn:aws:, arn:aws-cn:, arn:aws-us-gov: (literal partitions) + // - arn:${AWS::Partition}: (CloudFormation intrinsic) + const resourceName = typeof state.Resource === 'string' + ? state.Resource.replace(/^arn:(aws(-[a-z]+)*|\$\{AWS::Partition\}):/, 'arn:aws:') + : state.Resource; switch (resourceName) { case 'arn:aws:states:::sqs:sendMessage': case 'arn:aws:states:::sqs:sendMessage.waitForTaskToken': diff --git a/lib/deploy/stepFunctions/compileIamRole.test.js b/lib/deploy/stepFunctions/compileIamRole.test.js index ea92a91..a7d27e6 100644 --- a/lib/deploy/stepFunctions/compileIamRole.test.js +++ b/lib/deploy/stepFunctions/compileIamRole.test.js @@ -3274,6 +3274,64 @@ describe('#compileIamRole', () => { ], }]); }); + + it('should handle ${AWS::Partition} in resource ARN', () => { + 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::Partition}:states:::states:startExecution', + Parameters: { + StateMachineArn: stateMachineArn, + Input: {}, + }, + Next: 'B', + }, + B: { + Type: 'Task', + Resource: 'arn:${AWS::Partition}:states:::states:startExecution.sync', + 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:StartExecution'])); + 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', + {}, + ], + }]); + }); }); describe('should give step functions using sdk permissions (too permissive, but mirrors console behavior)', () => { @@ -3660,7 +3718,7 @@ describe('#compileIamRole', () => { expect(stepFunctionPermission).to.have.lengthOf(1); expect(stepFunctionPermission[0].Resource).to.deep.eq([{ 'Fn::Sub': [ - 'arn:aws:states:${AWS::Region}:${AWS::AccountId}:stateMachine:myStateMachine', + 'arn:${AWS::Partition}:states:${AWS::Region}:${AWS::AccountId}:stateMachine:myStateMachine', {}, ], }, @@ -3711,7 +3769,7 @@ describe('#compileIamRole', () => { expect(stepFunctionPermission).to.have.lengthOf(1); expect(stepFunctionPermission[0].Resource).to.deep.eq([{ 'Fn::Sub': [ - 'arn:aws:states:${AWS::Region}:${AWS::AccountId}:stateMachine:DistributedMapper', + 'arn:${AWS::Partition}:states:${AWS::Region}:${AWS::AccountId}:stateMachine:DistributedMapper', {}, ], },