Skip to content

feat: generate iam role for dynamodb:Scan #586

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 50 additions & 5 deletions lib/deploy/stepFunctions/compileIamRole.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,22 +70,40 @@ function sqsQueueUrlToArn(serverless, queueUrl) {
return [];
}

function getSqsPermissions(serverless, state) {
function getSqsPermissions(serverless, state, action = 'sqs:SendMessage') {
if (_.has(state, 'Parameters.QueueUrl')
|| _.has(state, ['Parameters', 'QueueUrl.$'])) {
|| _.has(state, ['Parameters', 'QueueUrl.$'])) {
// if queue URL is provided by input, then need pervasive permissions (i.e. '*')
const queueArn = state.Parameters['QueueUrl.$']
? '*'
: sqsQueueUrlToArn(serverless, state.Parameters.QueueUrl);
return [{ action: 'sqs:SendMessage', resource: queueArn }];
return [{ action, resource: queueArn }];
}
logger.log('SQS task missing Parameters.QueueUrl or Parameters.QueueUrl.$');
return [];
}

function getApiGatewayPermissions() {
return [{
action: 'execute-api:Invoke',
resource: [
{
'Fn::Join': [
':',
[
'arn:aws:execute-api',
{ Ref: 'AWS::Region' },
{ Ref: 'AWS::AccountId' },
'*',
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know how to get the actual api ID. I'm not sure if we need permissions that restrictive.

],
],
}],
}];
}

function getSnsPermissions(serverless, state) {
if (_.has(state, 'Parameters.TopicArn')
|| _.has(state, ['Parameters', 'TopicArn.$'])) {
|| _.has(state, ['Parameters', 'TopicArn.$'])) {
// if topic ARN is provided by input, then need pervasive permissions
const topicArn = state.Parameters['TopicArn.$'] ? '*' : state.Parameters.TopicArn;
return [{ action: 'sns:Publish', resource: topicArn }];
Expand Down Expand Up @@ -364,7 +382,7 @@ function getStepFunctionsPermissions(state) {
}

return [{
action: 'states:StartExecution',
action: 'states:StartExecution,states:StartSyncExecution',
resource: stateMachineArn,
}, {
action: 'states:DescribeExecution,states:StopExecution',
Expand Down Expand Up @@ -520,6 +538,30 @@ function getIamPermissions(taskStates) {
case 'arn:aws:states:::sqs:sendMessage':
case 'arn:aws:states:::sqs:sendMessage.waitForTaskToken':
return getSqsPermissions(this.serverless, state);
case 'arn:aws:states:::aws-sdk:sqs:receiveMessage':
return getSqsPermissions(this.serverless, state, 'sqs:ReceiveMessage');
case 'arn:aws:states:::aws-sdk:sqs:deleteMessage':
return getSqsPermissions(this.serverless, state, 'sqs:DeleteMessage');

case 'arn:aws:states:::apigateway:invoke':
return getApiGatewayPermissions('Invoke', state, this.serverless);

case 'arn:aws:states:::aws-sdk:ssm:getParameters':
return {
action: 'ssm:GetParameters*',
resource: [
{
'Fn::Join': [
':',
[
'arn:aws:ssm',
{ Ref: 'AWS::Region' },
{ Ref: 'AWS::AccountId' },
'parameter/*',
],
],
}],
};

case 'arn:aws:states:::sns:publish':
case 'arn:aws:states:::sns:publish.waitForTaskToken':
Expand All @@ -541,6 +583,8 @@ function getIamPermissions(taskStates) {
return getDynamoDBPermissions('dynamodb:UpdateTable', state);
case 'arn:aws:states:::aws-sdk:dynamodb:query':
return getDynamoDBPermissions('dynamodb:Query', state);
case 'arn:aws:states:::aws-sdk:dynamodb:scan':
return getDynamoDBPermissions('dynamodb:Scan', state);

case 'arn:aws:states:::aws-sdk:dynamodb:batchGetItem':
return getBatchDynamoDBPermissions('dynamodb:BatchGetItem', state);
Expand Down Expand Up @@ -581,6 +625,7 @@ function getIamPermissions(taskStates) {
case 'arn:aws:states:::states:startExecution.sync':
case 'arn:aws:states:::states:startExecution.sync:2':
case 'arn:aws:states:::states:startExecution.waitForTaskToken':
case 'arn:aws:states:::aws-sdk:sfn:startSyncExecution':
return getStepFunctionsPermissions(state);

case 'arn:aws:states:::codebuild:startBuild':
Expand Down
39 changes: 39 additions & 0 deletions lib/deploy/stepFunctions/compileIamRole.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,45 @@ describe('#compileIamRole', () => {
});
});

it('should give dynamodb scan permission', () => {
const helloTable = 'hello';

const genStateMachine = (id, tableName) => ({
id,
definition: {
StartAt: 'A',
States: {
A: {
Type: 'Task',
Resource: 'arn:aws:states:::aws-sdk:dynamodb:scan',
Parameters: {
TableName: tableName,
},
Next: 'B',
},
},
},
});

serverless.service.stepFunctions = {
stateMachines: {
myStateMachine1: genStateMachine('StateMachine1', helloTable),
},
};

serverlessStepFunctions.compileIamRole();
const policy = serverlessStepFunctions.serverless.service
.provider.compiledCloudFormationTemplate.Resources.StateMachine1Role
.Properties.Policies[0];

expect(policy.PolicyDocument.Statement[0].Action)
.to.be.deep.equal(['dynamodb:Scan']);

expect(policy.PolicyDocument.Statement[0].Resource[0]).to.be.deep.equal({
'Fn::Join': [':', ['arn', { Ref: 'AWS::Partition' }, 'dynamodb', { Ref: 'AWS::Region' }, { Ref: 'AWS::AccountId' }, 'table/hello']],
});
});

it('should give dynamodb permission to * whenever TableName.$ is seen', () => {
const helloTable = 'hello';

Expand Down