Skip to content

Commit d31d658

Browse files
committed
feat: add support dynamoDB batch tasks using AWS SDK service integration
1 parent baf6764 commit d31d658

File tree

2 files changed

+143
-0
lines changed

2 files changed

+143
-0
lines changed

lib/deploy/stepFunctions/compileIamRole.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,28 @@ function getDynamoDBPermissions(action, state) {
219219
resource,
220220
}];
221221
}
222+
223+
function getBatchDynamoDBPermissions(action, state) {
224+
if (state.Parameters['RequestItems.$']) {
225+
// When the RequestItems object is only known at runtime,
226+
// we have to provide * permissions during deployment.
227+
return [{
228+
action,
229+
resource: '*',
230+
}];
231+
}
232+
// If RequestItems is specified it must contain the target
233+
// table names as keys. We can use these to generate roles
234+
// whether the array of requests for that table is known
235+
// at deploy time or not
236+
const tableNames = Object.keys(state.Parameters.RequestItems);
237+
238+
return tableNames.map(tableName => ({
239+
action,
240+
resource: getDynamoDBArn(tableName.replace('.$', '')),
241+
}));
242+
}
243+
222244
function getRedshiftDataPermissions(action, state) {
223245
if (['redshift-data:ExecuteStatement', 'redshift-data:BatchExecuteStatement'].includes(action)) {
224246
const clusterName = _.has(state, 'Parameters.ClusterIdentifier') ? state.Parameters.ClusterIdentifier : '*';
@@ -515,6 +537,11 @@ function getIamPermissions(taskStates) {
515537
case 'arn:aws:states:::aws-sdk:dynamodb:query':
516538
return getDynamoDBPermissions('dynamodb:Query', state);
517539

540+
case 'arn:aws:states:::aws-sdk:dynamodb:batchGetItem':
541+
return getBatchDynamoDBPermissions('dynamodb:BatchGetItem', state);
542+
case 'arn:aws:states:::aws-sdk:dynamodb:batchWriteItem':
543+
return getBatchDynamoDBPermissions('dynamodb:BatchWriteItem', state);
544+
518545
case 'arn:aws:states:::aws-sdk:redshiftdata:executeStatement':
519546
return getRedshiftDataPermissions('redshift-data:ExecuteStatement', state);
520547
case 'arn:aws:states:::aws-sdk:redshiftdata:batchExecuteStatement':

lib/deploy/stepFunctions/compileIamRole.test.js

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -908,6 +908,122 @@ describe('#compileIamRole', () => {
908908
expect(policy.PolicyDocument.Statement[0].Resource[0]).to.equal('*');
909909
});
910910

911+
it('should give batch dynamodb permission for only tables referenced by state machine', () => {
912+
const helloTable = 'hello';
913+
const helloTableArn = {
914+
'Fn::Join': [
915+
':', ['arn', { Ref: 'AWS::Partition' }, 'dynamodb', { Ref: 'AWS::Region' }, { Ref: 'AWS::AccountId' }, 'table/hello'],
916+
],
917+
};
918+
const worldTable = 'world';
919+
const worldTableArn = {
920+
'Fn::Join': [
921+
':', ['arn', { Ref: 'AWS::Partition' }, 'dynamodb', { Ref: 'AWS::Region' }, { Ref: 'AWS::AccountId' }, 'table/world'],
922+
],
923+
};
924+
925+
const genStateMachine = (id, tableName) => ({
926+
id,
927+
definition: {
928+
StartAt: 'A',
929+
States: {
930+
A: {
931+
Type: 'Task',
932+
Resource: 'arn:aws:states:::aws-sdk:dynamodb:batchWriteItem',
933+
Parameters: {
934+
RequestItems: {
935+
[tableName]: [],
936+
},
937+
},
938+
Next: 'B',
939+
},
940+
B: {
941+
Type: 'Task',
942+
Resource: 'arn:aws:states:::aws-sdk:dynamodb:batchGetItem',
943+
Parameters: {
944+
RequestItems: {
945+
[tableName]: {},
946+
},
947+
},
948+
End: true,
949+
},
950+
},
951+
},
952+
});
953+
serverless.service.stepFunctions = {
954+
stateMachines: {
955+
myStateMachine1: genStateMachine('StateMachine1', helloTable),
956+
myStateMachine2: genStateMachine('StateMachine2', worldTable),
957+
},
958+
};
959+
960+
serverlessStepFunctions.compileIamRole();
961+
const resources = serverlessStepFunctions.serverless.service
962+
.provider.compiledCloudFormationTemplate.Resources;
963+
const policy1 = resources.StateMachine1Role.Properties.Policies[0];
964+
const policy2 = resources.StateMachine2Role.Properties.Policies[0];
965+
966+
[policy1, policy2].forEach((policy) => {
967+
expect(policy.PolicyDocument.Statement[0].Action)
968+
.to.be.deep.equal([
969+
'dynamodb:BatchWriteItem',
970+
'dynamodb:BatchGetItem',
971+
]);
972+
});
973+
974+
expect(policy1.PolicyDocument.Statement[0].Resource)
975+
.to.be.deep.equal([helloTableArn]);
976+
expect(policy2.PolicyDocument.Statement[0].Resource)
977+
.to.be.deep.equal([worldTableArn]);
978+
});
979+
980+
it('should give batch dynamodb permission to * whenever RequestItems.$ is seen', () => {
981+
const genStateMachine = id => ({
982+
id,
983+
definition: {
984+
StartAt: 'A',
985+
States: {
986+
A: {
987+
Type: 'Task',
988+
Resource: 'arn:aws:states:::aws-sdk:dynamodb:batchWriteItem',
989+
Parameters: {
990+
RequestItems: {
991+
tableName: [],
992+
},
993+
},
994+
Next: 'B',
995+
},
996+
B: {
997+
Type: 'Task',
998+
Resource: 'arn:aws:states:::aws-sdk:dynamodb:batchWriteItem',
999+
Parameters: {
1000+
'RequestItems.$': '$.requestItems',
1001+
},
1002+
End: true,
1003+
},
1004+
},
1005+
},
1006+
});
1007+
1008+
serverless.service.stepFunctions = {
1009+
stateMachines: {
1010+
myStateMachine1: genStateMachine('StateMachine1'),
1011+
},
1012+
};
1013+
1014+
serverlessStepFunctions.compileIamRole();
1015+
const policy = serverlessStepFunctions.serverless.service
1016+
.provider.compiledCloudFormationTemplate.Resources.StateMachine1Role
1017+
.Properties.Policies[0];
1018+
expect(policy.PolicyDocument.Statement[0].Action)
1019+
.to.be.deep.equal(['dynamodb:BatchWriteItem']);
1020+
1021+
// even though some tasks target specific tables, because RequestItems.$ is used we
1022+
// have to give broad permissions to allow execution to talk to whatever table
1023+
// the input specifies
1024+
expect(policy.PolicyDocument.Statement[0].Resource).to.equal('*');
1025+
});
1026+
9111027
it('should give Redshift Data permissions to * for safe actions', () => {
9121028
serverless.service.stepFunctions = {
9131029
stateMachines: {

0 commit comments

Comments
 (0)