Skip to content

Commit 182e15c

Browse files
author
Michael Bahr
committed
chore: add tests and extend ddb permission function
1 parent 0467b3c commit 182e15c

File tree

2 files changed

+197
-6
lines changed

2 files changed

+197
-6
lines changed

lib/deploy/stepFunctions/compileIamRole.js

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,67 @@ function getDynamoDBArn(tableName) {
130130
};
131131
}
132132

133+
function getDynamoDBIndexArn(tableName, indexName) {
134+
if (isIntrinsic(tableName)) {
135+
// most likely we'll see a { Ref: LogicalId }, which we need to map to
136+
// { Fn::GetAtt: [ LogicalId, Arn ] } to get the ARN
137+
if (tableName.Ref) {
138+
return {
139+
'Fn::Join': [
140+
'/',
141+
[
142+
{ 'Fn::GetAtt': [tableName.Ref, 'Arn'] },
143+
'index',
144+
indexName,
145+
],
146+
],
147+
};
148+
}
149+
// but also support importing the table name from an external stack that exports it
150+
// as we still want to support direct state machine actions interacting with those tables
151+
if (tableName['Fn::ImportValue']) {
152+
return {
153+
'Fn::Join': [
154+
':',
155+
[
156+
'arn',
157+
{ Ref: 'AWS::Partition' },
158+
'dynamodb',
159+
{ Ref: 'AWS::Region' },
160+
{ Ref: 'AWS::AccountId' },
161+
{
162+
'Fn::Join': [
163+
'/',
164+
[
165+
'table',
166+
tableName,
167+
'index',
168+
indexName,
169+
],
170+
],
171+
},
172+
],
173+
],
174+
};
175+
}
176+
}
177+
178+
return {
179+
'Fn::Join': [
180+
':',
181+
[
182+
'arn',
183+
{ Ref: 'AWS::Partition' },
184+
'dynamodb',
185+
{ Ref: 'AWS::Region' },
186+
{ Ref: 'AWS::AccountId' },
187+
`table/${tableName}/index/${indexName}`,
188+
],
189+
],
190+
};
191+
}
192+
193+
133194
function getBatchPermissions() {
134195
return [{
135196
action: 'batch:SubmitJob,batch:DescribeJobs,batch:TerminateJob',
@@ -182,19 +243,19 @@ function getEcsPermissions() {
182243
}
183244

184245
function getDynamoDBPermissions(action, state) {
185-
const tableArn = state.Parameters['TableName.$']
186-
? '*'
187-
: getDynamoDBArn(state.Parameters.TableName);
188-
189246
const indexName = state.Parameters['IndexName.$']
190247
? '*'
191248
: state.Parameters.IndexName;
192249

193250
let resource;
194251
if (indexName) {
195-
resource = `${tableArn}/index/${indexName}`;
252+
resource = state.Parameters['TableName.$']
253+
? '*'
254+
: getDynamoDBIndexArn(state.Parameters.TableName, indexName);
196255
} else {
197-
resource = tableArn;
256+
resource = state.Parameters['TableName.$']
257+
? '*'
258+
: getDynamoDBArn(state.Parameters.TableName);
198259
}
199260
return [{
200261
action,

lib/deploy/stepFunctions/compileIamRole.test.js

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,58 @@ describe('#compileIamRole', () => {
731731
.to.be.deep.equal([worldTableArn]);
732732
});
733733

734+
it('should give dynamodb permission to index table whenever IndexName is provided', () => {
735+
const helloTable = 'hello';
736+
737+
const genStateMachine = (id, tableName) => ({
738+
id,
739+
definition: {
740+
StartAt: 'A',
741+
States: {
742+
A: {
743+
Type: 'Task',
744+
Resource: 'arn:aws:states:::aws-sdk:dynamodb:query',
745+
Parameters: {
746+
TableName: tableName,
747+
},
748+
Next: 'B',
749+
},
750+
B: {
751+
Type: 'Task',
752+
Resource: 'arn:aws:states:::aws-sdk:dynamodb:query',
753+
Parameters: {
754+
TableName: tableName,
755+
IndexName: 'GSI1',
756+
},
757+
End: true,
758+
},
759+
},
760+
},
761+
});
762+
763+
serverless.service.stepFunctions = {
764+
stateMachines: {
765+
myStateMachine1: genStateMachine('StateMachine1', helloTable),
766+
},
767+
};
768+
769+
serverlessStepFunctions.compileIamRole();
770+
const policy = serverlessStepFunctions.serverless.service
771+
.provider.compiledCloudFormationTemplate.Resources.StateMachine1Role
772+
.Properties.Policies[0];
773+
774+
expect(policy.PolicyDocument.Statement[0].Action)
775+
.to.be.deep.equal(['dynamodb:Query']);
776+
777+
expect(policy.PolicyDocument.Statement[0].Resource[0]).to.be.deep.equal({
778+
'Fn::Join': [':', ['arn', { Ref: 'AWS::Partition' }, 'dynamodb', { Ref: 'AWS::Region' }, { Ref: 'AWS::AccountId' }, 'table/hello']],
779+
});
780+
781+
expect(policy.PolicyDocument.Statement[0].Resource[1]).to.be.deep.equal({
782+
'Fn::Join': [':', ['arn', { Ref: 'AWS::Partition' }, 'dynamodb', { Ref: 'AWS::Region' }, { Ref: 'AWS::AccountId' }, 'table/hello/index/GSI1']],
783+
});
784+
});
785+
734786
it('should give dynamodb permission to * whenever TableName.$ is seen', () => {
735787
const helloTable = 'hello';
736788

@@ -778,6 +830,84 @@ describe('#compileIamRole', () => {
778830
expect(policy.PolicyDocument.Statement[0].Resource).to.equal('*');
779831
});
780832

833+
it('should give dynamodb permission to table/TableName/index/* when IndexName.$ is seen', () => {
834+
const helloTable = 'hello';
835+
836+
const genStateMachine = (id, tableName) => ({
837+
id,
838+
definition: {
839+
StartAt: 'A',
840+
States: {
841+
A: {
842+
Type: 'Task',
843+
Resource: 'arn:aws:states:::aws-sdk:dynamodb:query',
844+
Parameters: {
845+
TableName: tableName,
846+
'IndexName.$': '$.myDynamicIndexName',
847+
},
848+
End: true,
849+
},
850+
},
851+
},
852+
});
853+
854+
serverless.service.stepFunctions = {
855+
stateMachines: {
856+
myStateMachine1: genStateMachine('StateMachine1', helloTable),
857+
},
858+
};
859+
860+
serverlessStepFunctions.compileIamRole();
861+
const policy = serverlessStepFunctions.serverless.service
862+
.provider.compiledCloudFormationTemplate.Resources.StateMachine1Role
863+
.Properties.Policies[0];
864+
expect(policy.PolicyDocument.Statement[0].Action)
865+
.to.be.deep.equal(['dynamodb:Query']);
866+
867+
// even though some tasks target specific indices, because IndexName.$ is used we
868+
// have to give broad permissions to allow execution to talk to whatever index
869+
// the input specifies
870+
expect(policy.PolicyDocument.Statement[0].Resource[0]['Fn::Join'][1][5]).to.equal('table/hello/index/*');
871+
});
872+
873+
it('should give dynamodb permission to table/* whenever TableName.$ and IndexName.$ are seen', () => {
874+
const genStateMachine = id => ({
875+
id,
876+
definition: {
877+
StartAt: 'A',
878+
States: {
879+
A: {
880+
Type: 'Task',
881+
Resource: 'arn:aws:states:::aws-sdk:dynamodb:query',
882+
Parameters: {
883+
'TableName.$': '$.myDynamicTableName',
884+
'IndexName.$': '$.myDynamicIndexName',
885+
},
886+
End: true,
887+
},
888+
},
889+
},
890+
});
891+
892+
serverless.service.stepFunctions = {
893+
stateMachines: {
894+
myStateMachine1: genStateMachine('StateMachine1'),
895+
},
896+
};
897+
898+
serverlessStepFunctions.compileIamRole();
899+
const policy = serverlessStepFunctions.serverless.service
900+
.provider.compiledCloudFormationTemplate.Resources.StateMachine1Role
901+
.Properties.Policies[0];
902+
expect(policy.PolicyDocument.Statement[0].Action)
903+
.to.be.deep.equal(['dynamodb:Query']);
904+
905+
// even though some tasks target specific tables, because TableName.$ is used we
906+
// have to give broad permissions to allow execution to talk to whatever table
907+
// the input specifies
908+
expect(policy.PolicyDocument.Statement[0].Resource[0]).to.equal('*');
909+
});
910+
781911
it('should give Redshift Data permissions to * for safe actions', () => {
782912
serverless.service.stepFunctions = {
783913
stateMachines: {

0 commit comments

Comments
 (0)