Skip to content

Commit

Permalink
fix(sns): for SSE topics, add KMS permissions in grantPublish
Browse files Browse the repository at this point in the history
  • Loading branch information
lightningboltemoji committed Jan 9, 2025
1 parent f2ade56 commit a45469a
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,32 @@ class SNSInteg extends Stack {
const topic2 = new Topic(this, 'MyTopic2', {
topicName: 'fooTopic2',
displayName: 'fooDisplayName2',
masterKey: key,
});
const importedTopic = Topic.fromTopicArn(this, 'ImportedTopic', topic2.topicArn);
const importedTopic2 = Topic.fromTopicArn(this, 'ImportedTopic', topic2.topicArn);

const publishRole = new Role(this, 'PublishRole', {
assumedBy: new ServicePrincipal('s3.amazonaws.com'),
});
importedTopic.grantPublish(publishRole);
importedTopic2.grantPublish(publishRole);

// Can import encrypted topic by attributes
const topic3 = new Topic(this, 'MyTopic3', {
topicName: 'fooTopic3',
displayName: 'fooDisplayName3',
masterKey: key,
});
const importedTopic3 = Topic.fromTopicAttributes(this, 'ImportedTopic', {
topicArn: topic3.topicArn,
keyArn: key.keyArn,
});
importedTopic3.grantPublish(publishRole);

// Can reference KMS key after creation
topic3.masterKey!.addToResourcePolicy(new PolicyStatement({
principals: [new ServicePrincipal('s3.amazonaws.com')],
actions: ['kms:GenerateDataKey*', 'kms:Decrypt'],
resources: ['*'],
}));
}
}

Expand Down
16 changes: 15 additions & 1 deletion packages/aws-cdk-lib/aws-sns/lib/topic-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ITopicSubscription } from './subscriber';
import { Subscription } from './subscription';
import * as notifications from '../../aws-codestarnotifications';
import * as iam from '../../aws-iam';
import { IKey } from '../../aws-kms';
import { IResource, Resource, ResourceProps, Token } from '../../core';

/**
Expand All @@ -25,6 +26,13 @@ export interface ITopic extends IResource, notifications.INotificationRuleTarget
*/
readonly topicName: string;

/**
* A KMS Key, either managed by this CDK app, or imported.
*
* @default None
*/
readonly masterKey?: IKey;

/**
* Enables content-based deduplication for FIFO topics.
*
Expand Down Expand Up @@ -72,6 +80,8 @@ export abstract class TopicBase extends Resource implements ITopic {

public abstract readonly topicName: string;

public abstract readonly masterKey?: IKey;

public abstract readonly fifo: boolean;

public abstract readonly contentBasedDeduplication: boolean;
Expand Down Expand Up @@ -173,12 +183,16 @@ export abstract class TopicBase extends Resource implements ITopic {
* Grant topic publishing permissions to the given identity
*/
public grantPublish(grantee: iam.IGrantable) {
return iam.Grant.addToPrincipalOrResource({
const ret = iam.Grant.addToPrincipalOrResource({
grantee,
actions: ['sns:Publish'],
resourceArns: [this.topicArn],
resource: this,
});
if (this.masterKey) {
this.masterKey.grantEncryptDecrypt(grantee);
}
return ret;
}

/**
Expand Down
14 changes: 13 additions & 1 deletion packages/aws-cdk-lib/aws-sns/lib/topic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Construct } from 'constructs';
import { CfnTopic } from './sns.generated';
import { ITopic, TopicBase } from './topic-base';
import { IRole } from '../../aws-iam';
import { IKey } from '../../aws-kms';
import { IKey, Key } from '../../aws-kms';
import { ArnFormat, Lazy, Names, Stack, Token } from '../../core';

/**
Expand Down Expand Up @@ -189,6 +189,13 @@ export interface TopicAttributes {
*/
readonly topicArn: string;

/**
* KMS encryption key, if this topic is server-side encrypted by a KMS key.
*
* @default - None
*/
readonly keyArn?: string;

/**
* Whether content-based deduplication is enabled.
* Only applicable for FIFO topics.
Expand Down Expand Up @@ -232,6 +239,9 @@ export class Topic extends TopicBase {
class Import extends TopicBase {
public readonly topicArn = attrs.topicArn;
public readonly topicName = topicName;
public readonly masterKey = attrs.keyArn
? Key.fromKeyArn(this, 'Key', attrs.keyArn)
: undefined;
public readonly fifo = fifo;
public readonly contentBasedDeduplication = attrs.contentBasedDeduplication || false;
protected autoCreatePolicy: boolean = false;
Expand All @@ -244,6 +254,7 @@ export class Topic extends TopicBase {

public readonly topicArn: string;
public readonly topicName: string;
public readonly masterKey?: IKey;
public readonly contentBasedDeduplication: boolean;
public readonly fifo: boolean;

Expand Down Expand Up @@ -322,6 +333,7 @@ export class Topic extends TopicBase {
resource: this.physicalName,
});
this.topicName = this.getResourceNameAttribute(resource.attrTopicName);
this.masterKey = props.masterKey;
this.fifo = props.fifo || false;
this.contentBasedDeduplication = props.contentBasedDeduplication || false;
}
Expand Down
46 changes: 46 additions & 0 deletions packages/aws-cdk-lib/aws-sns/test/sns.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,38 @@ describe('Topic', () => {
],
},
});
});

test('give publishing permissions with masterKey', () => {
// GIVEN
const stack = new cdk.Stack();
const key = new kms.Key(stack, 'CustomKey');
const topic = new sns.Topic(stack, 'Topic', { masterKey: key });
const user = new iam.User(stack, 'User');

// WHEN
topic.grantPublish(user);

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
'PolicyDocument': {
Version: '2012-10-17',
'Statement': [
{
'Action': 'sns:Publish',
'Effect': 'Allow',
'Resource': stack.resolve(topic.topicArn),
},
{
'Action': ['kms:Decrypt', 'kms:Encrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey*'],
'Effect': 'Allow',
'Resource': {
'Fn::GetAtt': ['CustomKey1E6D0D07', 'Arn'],
},
},
],
},
});
});

test('give subscribing permissions', () => {
Expand Down Expand Up @@ -532,6 +563,21 @@ describe('Topic', () => {
})).toThrow(/Cannot import topic; contentBasedDeduplication is only available for FIFO SNS topics./);
});

test('fromTopicAttributes keyArn', () => {
// GIVEN
const stack = new cdk.Stack();
const keyArn = 'arn:aws:kms:us-east-1:123456789012:key/1234abcd-12ab-34cd-56ef-1234567890ab';

//WHEN
const imported = sns.Topic.fromTopicAttributes(stack, 'Imported', {
topicArn: 'arn:aws:sns:*:123456789012:mytopic',
keyArn,
});

// THEN
expect(imported.masterKey?.keyArn).toEqual(keyArn);
});

test('sets account for imported topic env', () => {
// GIVEN
const stack = new cdk.Stack();
Expand Down

0 comments on commit a45469a

Please sign in to comment.