Skip to content

Commit 0544b85

Browse files
authored
Merge pull request #522 from taschmidt/master
feat: allow additive IAM role statements
2 parents 20263c3 + f985240 commit 0544b85

File tree

4 files changed

+73
-5
lines changed

4 files changed

+73
-5
lines changed

lib/deploy/stepFunctions/compileIamRole.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -500,23 +500,29 @@ function getIamPermissions(taskStates) {
500500
});
501501
}
502502

503-
function getIamStatements(iamPermissions) {
503+
function getIamStatements(iamPermissions, stateMachineObj) {
504504
// when the state machine doesn't define any Task states, and therefore doesn't need ANY
505505
// permission, then we should follow the behaviour of the AWS console and return a policy
506506
// that denies access to EVERYTHING
507-
if (_.isEmpty(iamPermissions)) {
507+
if (_.isEmpty(iamPermissions) && _.isEmpty(stateMachineObj.iamRoleStatements)) {
508508
return [{
509509
Effect: 'Deny',
510510
Action: '*',
511511
Resource: '*',
512512
}];
513513
}
514514

515-
return iamPermissions.map(p => ({
515+
const iamStatements = iamPermissions.map(p => ({
516516
Effect: 'Allow',
517517
Action: p.action.split(','),
518518
Resource: p.resource,
519519
}));
520+
521+
if (!_.isEmpty(stateMachineObj.iamRoleStatements)) {
522+
iamStatements.push(...stateMachineObj.iamRoleStatements);
523+
}
524+
525+
return iamStatements;
520526
}
521527

522528
module.exports = {
@@ -551,7 +557,7 @@ module.exports = {
551557

552558
iamPermissions = consolidatePermissionsByAction(iamPermissions);
553559
iamPermissions = consolidatePermissionsByResource(iamPermissions);
554-
const iamStatements = getIamStatements(iamPermissions);
560+
const iamStatements = getIamStatements(iamPermissions, stateMachineObj);
555561

556562
const iamRoleStateMachineExecutionTemplate = this.serverless.utils.readFileSync(
557563
path.join(__dirname,

lib/deploy/stepFunctions/compileIamRole.test.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,44 @@ describe('#compileIamRole', () => {
172172
expectation(policy2, [fooLambda, barLambda]);
173173
});
174174

175+
it('should add discrete iam role permissions', () => {
176+
const iamRoleStatement = {
177+
Effect: 'Allow',
178+
Action: ['service:Action'],
179+
Resource: ['arn:aws:item'],
180+
};
181+
const lambdaArn = 'arn:aws:lambda:123:*:function:hello';
182+
serverless.service.stepFunctions = {
183+
stateMachines: {
184+
myStateMachine1: {
185+
id: 'StateMachine1',
186+
name: 'stateMachine1',
187+
iamRoleStatements: [iamRoleStatement],
188+
definition: {
189+
StartAt: 'A',
190+
States: {
191+
A: {
192+
Type: 'Task',
193+
Resource: lambdaArn,
194+
End: true,
195+
},
196+
},
197+
},
198+
},
199+
},
200+
};
201+
serverlessStepFunctions.compileIamRole();
202+
const policies = serverlessStepFunctions.serverless
203+
.service.provider.compiledCloudFormationTemplate.Resources
204+
.StateMachine1Role.Properties.Policies;
205+
expect(policies).to.have.length(1);
206+
const statements = policies[0].PolicyDocument.Statement;
207+
expect(statements).to.have.length(2);
208+
const [statement1, statement2] = statements;
209+
expect(statement1.Action[0]).to.equal('lambda:InvokeFunction');
210+
expect(statement2).to.be.deep.equal(iamRoleStatement);
211+
});
212+
175213
it('should give sns:Publish permission for only SNS topics referenced by state machine', () => {
176214
const helloTopic = 'arn:aws:sns:#{AWS::Region}:#{AWS::AccountId}:hello';
177215
const worldTopic = 'arn:aws:sns:us-east-1:#{AWS::AccountId}:world';

lib/deploy/stepFunctions/compileStateMachines.schema.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ const tracingConfig = Joi.object().keys({
4949
enabled: Joi.boolean().default(false),
5050
});
5151

52+
const iamRoleStatements = Joi.array().items(
53+
Joi.object({
54+
Effect: Joi.string().valid('Allow', 'Deny'),
55+
Action: [Joi.string(), Joi.array().items(Joi.string())],
56+
Resource: [Joi.string(), Joi.array()],
57+
}),
58+
);
59+
5260
const id = Joi.string();
5361
const tags = Joi.object();
5462
const name = Joi.string();
@@ -75,6 +83,7 @@ const schema = Joi.object().keys({
7583
loggingConfig,
7684
tracingConfig,
7785
inheritGlobalTags,
78-
});
86+
iamRoleStatements,
87+
}).oxor('role', 'iamRoleStatements');
7988

8089
module.exports = schema;

lib/deploy/stepFunctions/compileStateMachines.test.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1632,4 +1632,19 @@ describe('#compileStateMachines', () => {
16321632
.provider.compiledCloudFormationTemplate.Resources
16331633
.StateMachineBeta1.DeletionPolicy).to.equal('Retain');
16341634
});
1635+
1636+
it('should not allow both role and iamRoleStatements', () => {
1637+
serverless.service.stepFunctions = {
1638+
stateMachines: {
1639+
myStateMachine1: {
1640+
role: 'arn:aws:role',
1641+
iamRoleStatements: [],
1642+
definition: 'definition1',
1643+
},
1644+
},
1645+
validate: true,
1646+
};
1647+
// Definition is invalid and validate=true, should throw
1648+
expect(() => serverlessStepFunctions.compileStateMachines()).to.throw(Error);
1649+
});
16351650
});

0 commit comments

Comments
 (0)