Skip to content

Commit ab9c4f9

Browse files
Merge pull request #255 from agiledigital/master
Fix permissions generation for Lambda ARNs with aliases
2 parents 6ecbe42 + ee94d8b commit ab9c4f9

File tree

5 files changed

+1956
-1876
lines changed

5 files changed

+1956
-1876
lines changed

lib/deploy/stepFunctions/compileIamRole.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
const _ = require('lodash');
44
const BbPromise = require('bluebird');
55
const path = require('path');
6-
const { isIntrinsic, translateLocalFunctionNames } = require('../../utils/aws');
6+
const { isIntrinsic, translateLocalFunctionNames, trimAliasFromLambdaArn } = require('../../utils/aws');
7+
78

89
function getTaskStates(states) {
910
return _.flatMap(states, (state) => {
@@ -328,7 +329,8 @@ function getIamPermissions(taskStates) {
328329

329330
default:
330331
if (isIntrinsic(state.Resource) || state.Resource.startsWith('arn:aws:lambda')) {
331-
const functionArn = translateLocalFunctionNames.bind(this)(state.Resource);
332+
const trimmedArn = trimAliasFromLambdaArn(state.Resource);
333+
const functionArn = translateLocalFunctionNames.bind(this)(trimmedArn);
332334
return [{
333335
action: 'lambda:InvokeFunction',
334336
resource: [

lib/deploy/stepFunctions/compileIamRole.test.js

+63
Original file line numberDiff line numberDiff line change
@@ -1383,6 +1383,69 @@ describe('#compileIamRole', () => {
13831383
expect(lambdaPermissions[0].Resource).to.deep.include.members(lambdaArns);
13841384
});
13851385

1386+
it('should support lambda ARNs as task resource with and without aliases', () => {
1387+
const getStateMachine = name => ({
1388+
name,
1389+
definition: {
1390+
StartAt: 'A',
1391+
States: {
1392+
A: {
1393+
Type: 'Task',
1394+
Resource: 'arn:aws:lambda:region:accountId:function:with-alias:some-alias',
1395+
Next: 'B',
1396+
},
1397+
B: {
1398+
Type: 'Task',
1399+
Resource: 'arn:aws:lambda:region:accountId:function:no-alias',
1400+
End: true,
1401+
},
1402+
},
1403+
},
1404+
});
1405+
1406+
serverless.service.functions = {
1407+
'with-alias': {
1408+
handler: 'with-alias.handler',
1409+
},
1410+
'no-alias': {
1411+
handler: 'with-alias.handler',
1412+
},
1413+
};
1414+
1415+
serverless.service.stepFunctions = {
1416+
stateMachines: {
1417+
myStateMachine1: getStateMachine('sm1'),
1418+
myStateMachine2: getStateMachine('sm2'),
1419+
},
1420+
};
1421+
1422+
serverlessStepFunctions.compileIamRole();
1423+
const statements = serverlessStepFunctions.serverless.service
1424+
.provider.compiledCloudFormationTemplate.Resources.IamRoleStateMachineExecution
1425+
.Properties.Policies[0].PolicyDocument.Statement;
1426+
1427+
const lambdaPermissions = statements.filter(s => _.isEqual(s.Action, ['lambda:InvokeFunction']));
1428+
expect(lambdaPermissions).to.have.lengthOf(1);
1429+
1430+
const expectedResources = [
1431+
'arn:aws:lambda:region:accountId:function:with-alias',
1432+
{
1433+
'Fn::Sub': [
1434+
'${functionArn}:*',
1435+
{ functionArn: 'arn:aws:lambda:region:accountId:function:with-alias' },
1436+
],
1437+
},
1438+
'arn:aws:lambda:region:accountId:function:no-alias',
1439+
{
1440+
'Fn::Sub': [
1441+
'${functionArn}:*',
1442+
{ functionArn: 'arn:aws:lambda:region:accountId:function:no-alias' },
1443+
],
1444+
},
1445+
];
1446+
expect(lambdaPermissions[0].Resource).to.deep.include.members(expectedResources);
1447+
});
1448+
13861449
it('should give step functions permissions (too permissive, but mirrors console behaviour)', () => {
13871450
const stateMachineArn = 'arn:aws:states:us-east-1:123456789:stateMachine:HelloStateMachine';
13881451
const genStateMachine = name => ({

lib/utils/aws.js

+15
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,23 @@ function convertToFunctionVersion(value) {
7575
return value;
7676
}
7777

78+
// If the resource is a lambda ARN string, trim off the alias
79+
function trimAliasFromLambdaArn(resource) {
80+
if (typeof resource === 'string' && resource.startsWith('arn:aws:lambda')) {
81+
const components = resource.split(':');
82+
// Lambda ARNs with an alias have 8 components
83+
// E.g. arn:aws:lambda:region:accountId:function:name:alias
84+
const numberOfComponentsWhenAliasExists = 8;
85+
const hasAlias = components.length === numberOfComponentsWhenAliasExists;
86+
// Slice off the last component and join it back into an ARN string
87+
return hasAlias ? components.slice(0, numberOfComponentsWhenAliasExists - 1).join(':') : resource;
88+
}
89+
return resource;
90+
}
91+
7892
module.exports = {
7993
isIntrinsic,
8094
translateLocalFunctionNames,
8195
convertToFunctionVersion,
96+
trimAliasFromLambdaArn,
8297
};

0 commit comments

Comments
 (0)