Skip to content

Commit

Permalink
feat(aws-sqs): improvements to IAM grants API (#1052)
Browse files Browse the repository at this point in the history
* feat(aws-sqs): improvements to IAM grants API

Moved `grantXxx` methods from `Queue` to `QueueRef`, so they can now be
performed on imported queues.

Added commonly needed permissions to `grantConsumeMessages` and
`grantSendMessages` such as `sqs:GetQueueAttributes`, `sqs:GetQueueUrl`
and the various `sqs:xxxBatch` actions.

Added support for adding arbitrary actions to each of the grant methods.

Exposed `queue.grant(...actions)` as a general purpose grant method
which allows users to customize the set of actions for this specific 
resource/principal pair.

BREAKING CHANGE: `queue.grantReceiveMessages` has been removed. It is unlikely that this would be
sufficient to interact with a queue. Alternatively you can use `queue.grantConsumeMessages` or 
`queue.grant('sqs:ReceiveMessage')` if there's a need to only grant this action.
  • Loading branch information
Elad Ben-Israel authored Nov 1, 2018
1 parent f55d052 commit 6f2475e
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 139 deletions.
12 changes: 0 additions & 12 deletions packages/@aws-cdk/aws-sqs/lib/perms.ts

This file was deleted.

82 changes: 82 additions & 0 deletions packages/@aws-cdk/aws-sqs/lib/queue-ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,88 @@ export abstract class QueueRef extends cdk.Construct implements s3n.IBucketNotif
dependencies: [ this.policy! ]
};
}

/**
* Grant permissions to consume messages from a queue
*
* This will grant the following permissions:
*
* - sqs:ChangeMessageVisibility
* - sqs:ChangeMessageVisibilityBatch
* - sqs:DeleteMessage
* - sqs:ReceiveMessage
* - sqs:DeleteMessageBatch
* - sqs:GetQueueAttributes
* - sqs:GetQueueUrl
*
* @param identity Principal to grant consume rights to
*/
public grantConsumeMessages(identity?: iam.IPrincipal) {
this.grant(identity,
'sqs:ReceiveMessage',
'sqs:ChangeMessageVisibility',
'sqs:ChangeMessageVisibilityBatch',
'sqs:GetQueueUrl',
'sqs:DeleteMessage',
'sqs:DeleteMessageBatch',
'sqs:GetQueueAttributes');
}

/**
* Grant access to send messages to a queue to the given identity.
*
* This will grant the following permissions:
*
* - sqs:SendMessage
* - sqs:SendMessageBatch
* - sqs:GetQueueAttributes
* - sqs:GetQueueUrl
*
* @param identity Principal to grant send rights to
*/
public grantSendMessages(identity?: iam.IPrincipal) {
this.grant(identity,
'sqs:SendMessage',
'sqs:SendMessageBatch',
'sqs:GetQueueAttributes',
'sqs:GetQueueUrl');
}

/**
* Grant an IAM principal permissions to purge all messages from the queue.
*
* This will grant the following permissions:
*
* - sqs:PurgeQueue
* - sqs:GetQueueAttributes
* - sqs:GetQueueUrl
*
* @param identity Principal to grant send rights to
* @param queueActions additional queue actions to allow
*/
public grantPurge(identity?: iam.IPrincipal) {
this.grant(identity,
'sqs:PurgeQueue',
'sqs:GetQueueAttributes',
'sqs:GetQueueUrl');
}

/**
* Grant the actions defined in queueActions to the identity Principal given
* on this SQS queue resource.
*
* @param identity Principal to grant right to
* @param queueActions The actions to grant
*/
public grant(identity?: iam.IPrincipal, ...queueActions: string[]) {
if (!identity) {
return;
}

identity.addToPolicy(new iam.PolicyStatement()
.addResource(this.queueArn)
.addActions(...queueActions));
}
}

/**
Expand Down
60 changes: 0 additions & 60 deletions packages/@aws-cdk/aws-sqs/lib/queue.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import iam = require('@aws-cdk/aws-iam');
import kms = require('@aws-cdk/aws-kms');
import cdk = require('@aws-cdk/cdk');
import perms = require('./perms');
import { QueueRef } from './queue-ref';
import { cloudformation } from './sqs.generated';
import { validateProps } from './validate-props';
Expand Down Expand Up @@ -277,64 +275,6 @@ export class Queue extends QueueRef {
}
}

/**
* Grant permissions to consume messages from a queue
*
* This will grant the following permissions:
*
* - sqs:ChangeMessageVisibility
* - sqs:DeleteMessage
* - sqs:ReceiveMessage
*
* @param identity Principal to grant consume rights to
*/
public grantConsumeMessages(identity?: iam.IPrincipal) {
this.grant(identity, perms.QUEUE_GET_ACTIONS.concat(perms.QUEUE_CONSUME_ACTIONS));
}

/**
* Grant access to receive messages from a queue to
* the given identity.
*
* This will grant sqs:ReceiveMessage
*
* @param identity Principal to grant receive rights to
*/
public grantReceiveMessages(identity?: iam.IPrincipal) {
this.grant(identity, perms.QUEUE_GET_ACTIONS);
}

/**
* Grant access to send messages to a queue to the
* given identity.
*
* This will grant sqs:SendMessage
*
* @param identity Principal to grant send rights to
*/
public grantSendMessages(identity?: iam.IPrincipal) {
this.grant(identity, perms.QUEUE_PUT_ACTIONS);
}

/**
* Grant the actions defined in queueActions
* to the identity Principal given.
*
* @param identity Principal to grant right to
* @param queueActions The actions to grant
*/
private grant(identity: iam.IPrincipal | undefined,
queueActions: string[]) {

if (!identity) {
return;
}

identity.addToPolicy(new iam.PolicyStatement()
.addResource(this.queueArn)
.addActions(...queueActions));
}

/**
* Look at the props, see if the FIFO props agree, and return the correct subset of props
*/
Expand Down
146 changes: 79 additions & 67 deletions packages/@aws-cdk/aws-sqs/test/test.sqs.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { expect, haveResource } from '@aws-cdk/assert';
import { ArnPrincipal, PolicyStatement, Role, ServicePrincipal } from '@aws-cdk/aws-iam';
import iam = require('@aws-cdk/aws-iam');
import kms = require('@aws-cdk/aws-kms');
import s3 = require('@aws-cdk/aws-s3');
import { resolve, Stack } from '@aws-cdk/cdk';
import { Test } from 'nodeunit';
import sqs = require('../lib');
import { Queue } from '../lib';

// tslint:disable:object-literal-key-quotes

Expand Down Expand Up @@ -56,7 +57,7 @@ export = {
'addToPolicy will automatically create a policy for this queue'(test: Test) {
const stack = new Stack();
const queue = new sqs.Queue(stack, 'MyQueue');
queue.addToResourcePolicy(new PolicyStatement().addAllResources().addActions('sqs:*').addPrincipal(new ArnPrincipal('arn')));
queue.addToResourcePolicy(new iam.PolicyStatement().addAllResources().addActions('sqs:*').addPrincipal(new iam.ArnPrincipal('arn')));
expect(stack).toMatch({
"Resources": {
"MyQueueE6CA6235": {
Expand Down Expand Up @@ -113,92 +114,77 @@ export = {
test.done();
},

'iam': {
'grants permission to consume messages'(test: Test) {
const stack = new Stack();
const role = new Role(stack, 'Role', { assumedBy: new ServicePrincipal('lambda.amazonaws.com') });
const queue = new sqs.Queue(stack, 'Queue');
queue.grantConsumeMessages(role);

expect(stack).to(haveResource('AWS::IAM::Policy', {
"PolicyDocument": {
"Statement": [
{
"Action": [
"sqs:ReceiveMessage",
"sqs:ChangeMessageVisibility",
"sqs:DeleteMessage"
],
"Effect": "Allow",
"Resource": {
"Fn::GetAtt":
[
"Queue4A7E3555",
"Arn"
]
}
}
]
}
}));

'grants': {
'grantConsumeMessages'(test: Test) {
testGrant((q, p) => q.grantConsumeMessages(p),
'sqs:ReceiveMessage',
'sqs:ChangeMessageVisibility',
'sqs:ChangeMessageVisibilityBatch',
'sqs:GetQueueUrl',
'sqs:DeleteMessage',
'sqs:DeleteMessageBatch',
'sqs:GetQueueAttributes',
);
test.done();
},

'grants permission to receive messages'(test: Test) {
const stack = new Stack();
const role = new Role(stack, 'Role', { assumedBy: new ServicePrincipal('lambda.amazonaws.com') });
const queue = new sqs.Queue(stack, 'Queue');
queue.grantReceiveMessages(role);
'grantSendMessages'(test: Test) {
testGrant((q, p) => q.grantSendMessages(p),
'sqs:SendMessage',
'sqs:SendMessageBatch',
'sqs:GetQueueAttributes',
'sqs:GetQueueUrl',
);
test.done();
},

expect(stack).to(haveResource('AWS::IAM::Policy', {
"PolicyDocument": {
"Statement": [
{
"Action": "sqs:ReceiveMessage",
"Effect": "Allow",
"Resource": {
"Fn::GetAtt":
[
"Queue4A7E3555",
"Arn"
]
}
}
]
}
}));
'grantPurge'(test: Test) {
testGrant((q, p) => q.grantPurge(p),
'sqs:PurgeQueue',
'sqs:GetQueueAttributes',
'sqs:GetQueueUrl',
);
test.done();
},

'grant() is general purpose'(test: Test) {
testGrant((q, p) => q.grant(p, 'hello', 'world'),
'hello',
'world'
);
test.done();
},

'grants permission to send messages'(test: Test) {
'grants also work on imported queues'(test: Test) {
const stack = new Stack();
const role = new Role(stack, 'Role', { assumedBy: new ServicePrincipal('lambda.amazonaws.com') });
const queue = new sqs.Queue(stack, 'Queue');
queue.grantSendMessages(role);
const queue = Queue.import(stack, 'Import', {
queueArn: 'imported-queue-arn',
queueUrl: 'https://queue-url'
});

const user = new iam.User(stack, 'User');

queue.grantPurge(user);

expect(stack).to(haveResource('AWS::IAM::Policy', {
"PolicyDocument": {
"Statement": [
{
"Action": "sqs:SendMessage",
"Action": [
"sqs:PurgeQueue",
"sqs:GetQueueAttributes",
"sqs:GetQueueUrl"
],
"Effect": "Allow",
"Resource": {
"Fn::GetAtt":
[
"Queue4A7E3555",
"Arn"
]
}
"Resource": "imported-queue-arn"
}
]
],
"Version": "2012-10-17"
}
}));

test.done();
}

},

'queue encryption': {
Expand Down Expand Up @@ -500,3 +486,29 @@ export = {

}
};

function testGrant(action: (q: Queue, principal: iam.IPrincipal) => void, ...expectedActions: string[]) {
const stack = new Stack();
const queue = new Queue(stack, 'MyQueue');
const principal = new iam.User(stack, 'User');

action(queue, principal);

expect(stack).to(haveResource('AWS::IAM::Policy', {
"PolicyDocument": {
"Statement": [
{
"Action": expectedActions,
"Effect": "Allow",
"Resource": {
"Fn::GetAtt": [
"MyQueueE6CA6235",
"Arn"
]
}
}
],
"Version": "2012-10-17"
}
}));
}

0 comments on commit 6f2475e

Please sign in to comment.