Skip to content

Commit

Permalink
Merge cd232b3 into b533027
Browse files Browse the repository at this point in the history
  • Loading branch information
theburningmonk committed May 26, 2019
2 parents b533027 + cd232b3 commit a05b991
Show file tree
Hide file tree
Showing 2 changed files with 186 additions and 0 deletions.
34 changes: 34 additions & 0 deletions lib/deploy/stepFunctions/compileIamRole.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,34 @@ function getDynamoDBPermissions(action, state) {
}];
}

function getLambdaPermissions(state) {
// function name can be name-only, name-only with alias, full arn or partial arn
// https://docs.aws.amazon.com/lambda/latest/dg/API_Invoke.html#API_Invoke_RequestParameters
const functionName = state.Parameters.FunctionName;
const segments = functionName.split(':');

let functionArn;
if (functionName.startsWith('arn:aws:lambda')) {
// full ARN
functionArn = functionName;
} else if (segments.length === 3 && segments[0].match(/^\d+$/)) {
// partial ARN
functionArn = {
'Fn::Sub': `arn:aws:lambda:\${AWS::Region}:${functionName}`,
};
} else {
// name-only (with or without alias)
functionArn = {
'Fn::Sub': `arn:aws:lambda:\${AWS::Region}:\${AWS::AccountId}:function:${functionName}`,
};
}

return [{
action: 'lambda:InvokeFunction',
resource: functionArn,
}];
}

// if there are multiple permissions with the same action, then collapsed them into one
// permission instead, and collect the resources into an array
function consolidatePermissionsByAction(permissions) {
Expand Down Expand Up @@ -185,9 +213,11 @@ function getIamPermissions(serverless, taskStates) {
return _.flatMap(taskStates, state => {
switch (state.Resource) {
case 'arn:aws:states:::sqs:sendMessage':
case 'arn:aws:states:::sqs:sendMessage.waitForTaskToken':
return getSqsPermissions(serverless, state);

case 'arn:aws:states:::sns:publish':
case 'arn:aws:states:::sns:publish.waitForTaskToken':
return getSnsPermissions(serverless, state);

case 'arn:aws:states:::dynamodb:updateItem':
Expand All @@ -208,9 +238,13 @@ function getIamPermissions(serverless, taskStates) {
return getGluePermissions();

case 'arn:aws:states:::ecs:runTask.sync':
case 'arn:aws:states:::ecs:runTask.waitForTaskToken':
case 'arn:aws:states:::ecs:runTask':
return getEcsPermissions();

case 'arn:aws:states:::lambda:invoke.waitForTaskToken':
return getLambdaPermissions(state);

default:
if (isIntrinsic(state.Resource) || state.Resource.startsWith('arn:aws:lambda')) {
return [{
Expand Down
152 changes: 152 additions & 0 deletions lib/deploy/stepFunctions/compileIamRole.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -999,4 +999,156 @@ describe('#compileIamRole', () => {
'Fn::GetAtt': ['MyTable', 'Arn'],
}]);
});

it('should support callbacks', () => {
const getStateMachine = (name, function1, function2, snsTopicArn, sqsQueueUrl) => ({
name,
definition: {
StartAt: 'A',
States: {
A: {
Type: 'Task',
Resource: 'arn:aws:states:::sns:publish.waitForTaskToken',
Parameters: {
Message: {
'Input.$': '$',
'TaskToken.$': '$$.Task.Token',
},
MessageStructure: 'json',
TopicArn: snsTopicArn,
},
Next: 'B1',
},
B1: {
Type: 'Task',
Resource: 'arn:aws:states:::lambda:invoke.waitForTaskToken',
Parameters: {
FunctionName: function1,
Payload: {
'model.$': '$',
'token.$': '$$.Task.Token',
},
},
Next: 'B2',
},
B2: {
Type: 'Task',
Resource: 'arn:aws:states:::lambda:invoke.waitForTaskToken',
Parameters: {
FunctionName: function2,
Payload: {
'model.$': '$',
'token.$': '$$.Task.Token',
},
},
Next: 'C',
},
C: {
Type: 'Task',
Resource: 'arn:aws:states:::sqs:sendMessage.waitForTaskToken',
Parameters: {
QueueUrl: sqsQueueUrl,
MessageBody: {
'Input.$': '$',
'TaskToken.$': '$$.Task.Token',
},
},
Next: 'D',
},
D: {
Type: 'Task',
Resource: 'arn:aws:states:::ecs:runTask.waitForTaskToken',
Parameters: {
LaunchType: 'FARGATE',
Cluster: 'cluster-arn',
TaskDefinition: 'job-id',
Overrides: {
ContainerOverrides: [
{
Name: 'cluster-name',
Environment: [
{
Name: 'TASK_TOKEN_ENV_VARIABLE',
'Value.$': '$$.Task.Token',
},
],
},
],
},
},
End: true,
},
},
},
});

// function name can be...
const lambda1 = 'a'; // name-only
const lambda2 = 'b:v1'; // name-only with alias
const lambda3 = 'arn:aws:lambda:us-west-2:1234567890:function:c'; // full arn
const lambda4 = '1234567890:function:d'; // partial arn

const sns1 = 'arn:aws:sns:us-east-1:1234567890:foo';
const sns2 = 'arn:aws:sns:us-east-2:#{AWS::AccountId}:bar';

const sqs1 = 'https://sqs.us-east-1.amazonaws.com/1234567890/foo';
const sqs2 = 'https://sqs.us-east-2.amazonaws.com/#{AWS::AccountId}/bar';

const sqsArn1 = 'arn:aws:sqs:us-east-1:1234567890:foo';
const sqsArn2 = 'arn:aws:sqs:us-east-2:#{AWS::AccountId}:bar';

serverless.service.stepFunctions = {
stateMachines: {
myStateMachine1: getStateMachine('sm1', lambda1, lambda2, sns1, sqs1),
myStateMachine2: getStateMachine('sm2', lambda3, lambda4, sns2, sqs2),
},
};

serverlessStepFunctions.compileIamRole();
const statements = serverlessStepFunctions.serverless.service
.provider.compiledCloudFormationTemplate.Resources.IamRoleStateMachineExecution
.Properties.Policies[0].PolicyDocument.Statement;

const ecsPermissions = statements.filter(s =>
_.isEqual(s.Action, ['ecs:RunTask', 'ecs:StopTask', 'ecs:DescribeTasks', 'iam:PassRole'])
);
expect(ecsPermissions).to.have.lengthOf(1);
expect(ecsPermissions[0].Resource).to.equal('*');

const eventPermissions = statements.filter(s =>
_.isEqual(s.Action, ['events:PutTargets', 'events:PutRule', 'events:DescribeRule'])
);
expect(eventPermissions).to.has.lengthOf(1);
expect(eventPermissions[0].Resource).to.deep.eq([{
'Fn::Join': [
':',
[
'arn:aws:events',
{ Ref: 'AWS::Region' },
{ Ref: 'AWS::AccountId' },
'rule/StepFunctionsGetEventsForECSTaskRule',
],
],
}]);

const snsPermissions = statements.filter(s => _.isEqual(s.Action, ['sns:Publish']));
expect(snsPermissions).to.have.lengthOf(1);
expect(snsPermissions[0].Resource).to.deep.eq([sns1, sns2]);

const sqsPermissions = statements.filter(s => _.isEqual(s.Action, ['sqs:SendMessage']));
expect(sqsPermissions).to.have.lengthOf(1);
expect(sqsPermissions[0].Resource).to.deep.eq([sqsArn1, sqsArn2]);

const lambdaPermissions = statements.filter(s =>
_.isEqual(s.Action, ['lambda:InvokeFunction']));
expect(lambdaPermissions).to.have.lengthOf(1);

const lambdaArns = [
{ 'Fn::Sub': 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:a' },
{ 'Fn::Sub': 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:b:v1' },
'arn:aws:lambda:us-west-2:1234567890:function:c',
{ 'Fn::Sub': 'arn:aws:lambda:${AWS::Region}:1234567890:function:d' },
];
expect(lambdaPermissions[0].Resource).to.deep.eq(lambdaArns);
});
});

0 comments on commit a05b991

Please sign in to comment.