Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,42 @@
"MyTopic86869434": {
"Type": "AWS::SNS::Topic"
},
"MyTopicPolicy12A5EC17": {
"Type": "AWS::SNS::TopicPolicy",
"MyTopicEventsRole0D2D3332": {
"Type": "AWS::IAM::Role",
"Properties": {
"PolicyDocument": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sns:Publish",
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "events.amazonaws.com"
},
}
}
],
"Version": "2012-10-17"
}
}
},
"MyTopicEventsRoleDefaultPolicyB79AC002": {
"Type": "AWS::IAM::Policy",
"Properties": {
"PolicyDocument": {
"Statement": [
{
"Action": "sns:Publish",
"Effect": "Allow",
"Resource": {
"Ref": "MyTopic86869434"
},
"Sid": "0"
}
}
],
"Version": "2012-10-17"
},
"Topics": [
"PolicyName": "MyTopicEventsRoleDefaultPolicyB79AC002",
"Roles": [
{
"Ref": "MyTopic86869434"
"Ref": "MyTopicEventsRole0D2D3332"
}
]
}
Expand All @@ -47,7 +61,13 @@
]
}
},
"Id": "Target0"
"Id": "Target0",
"RoleArn": {
"Fn::GetAtt": [
"MyTopicEventsRole0D2D3332",
"Arn"
]
}
}
]
}
Expand Down Expand Up @@ -94,19 +114,21 @@
},
"MyQueueawscdksnseventtargetMyTopicB7575CD87304D383": {
"Type": "AWS::SNS::Subscription",
"DependsOn": "MyQueuePolicy6BBEDDAC",
"Properties": {
"Protocol": "sqs",
"TopicArn": {
"Ref": "MyTopic86869434"
},
"Endpoint": {
"Fn::GetAtt": [
"MyQueueE6CA6235",
"Arn"
]
},
"Protocol": "sqs",
"TopicArn": {
"Ref": "MyTopic86869434"
}
}
},
"DependsOn": [
"MyQueuePolicy6BBEDDAC"
]
},
"MyDeadLetterQueueD997968A": {
"Type": "AWS::SQS::Queue",
Expand Down Expand Up @@ -187,4 +209,4 @@
]
}
}
}
}
18 changes: 15 additions & 3 deletions packages/aws-cdk-lib/aws-events-targets/lib/sns.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { addToDeadLetterQueueResourcePolicy, TargetBaseProps, bindBaseTargetConfig } from './util';
import { addToDeadLetterQueueResourcePolicy, TargetBaseProps, bindBaseTargetConfig, singletonEventRole } from './util';
import * as events from '../../aws-events';
import * as iam from '../../aws-iam';
import * as sns from '../../aws-sns';
Expand All @@ -13,6 +13,13 @@ export interface SnsTopicProps extends TargetBaseProps {
* @default the entire EventBridge event
*/
readonly message?: events.RuleTargetInput;

/**
* Role to be used to publish the event
*
* @default a new role is created.
*/
readonly role?: iam.IRole;
}

/**
Expand All @@ -37,8 +44,12 @@ export class SnsTopic implements events.IRuleTarget {
* @see https://docs.aws.amazon.com/eventbridge/latest/userguide/resource-based-policies-eventbridge.html#sns-permissions
*/
public bind(_rule: events.IRule, _id?: string): events.RuleTargetConfig {
// deduplicated automatically
this.topic.grantPublish(new iam.ServicePrincipal('events.amazonaws.com'));
const role = this.props.role || singletonEventRole(this.topic);

role.addToPrincipalPolicy(new iam.PolicyStatement({
actions: ['sns:Publish'],
resources: [this.topic.topicArn],
}));

if (this.props.deadLetterQueue) {
addToDeadLetterQueueResourcePolicy(_rule, this.props.deadLetterQueue);
Expand All @@ -49,6 +60,7 @@ export class SnsTopic implements events.IRuleTarget {
arn: this.topic.topicArn,
input: this.props.message,
targetResource: this.topic,
role,
};
}
}
87 changes: 60 additions & 27 deletions packages/aws-cdk-lib/aws-events-targets/test/sns/sns.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Template } from '../../../assertions';
import * as events from '../../../aws-events';
import * as iam from '../../../aws-iam';
import * as sns from '../../../aws-sns';
import * as sqs from '../../../aws-sqs';
import { Duration, Stack } from '../../../core';
Expand All @@ -17,62 +18,93 @@ test('sns topic as an event rule target', () => {
rule.addTarget(new targets.SnsTopic(topic));

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::SNS::TopicPolicy', {
Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', {
ScheduleExpression: 'rate(1 hour)',
State: 'ENABLED',
Targets: [
{
Arn: { Ref: 'MyTopic86869434' },
Id: 'Target0',
RoleArn: { 'Fn::GetAtt': ['MyTopicEventsRole0D2D3332', 'Arn'] },
},
],
});

Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', {
AssumeRolePolicyDocument: {
Statement: [
{
Action: 'sts:AssumeRole',
Effect: 'Allow',
Principal: { Service: 'events.amazonaws.com' },
},
],
Version: '2012-10-17',
},
});

Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
PolicyDocument: {
Statement: [
{
Sid: '0',
Action: 'sns:Publish',
Effect: 'Allow',
Principal: { Service: 'events.amazonaws.com' },
Resource: { Ref: 'MyTopic86869434' },
},
],
Version: '2012-10-17',
},
Topics: [{ Ref: 'MyTopic86869434' }],
});
});

test('multiple uses of a topic as a target results in a single policy statement', () => {
// GIVEN
const stack = new Stack();
const topic = new sns.Topic(stack, 'MyTopic');

// WHEN
for (let i = 0; i < 5; ++i) {
const rule = new events.Rule(stack, `Rule${i}`, {
schedule: events.Schedule.rate(Duration.hours(1)),
});
rule.addTarget(new targets.SnsTopic(topic));
}

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', {
ScheduleExpression: 'rate(1 hour)',
State: 'ENABLED',
Targets: [
{
Arn: { Ref: 'MyTopic86869434' },
Id: 'Target0',
RoleArn: { 'Fn::GetAtt': ['MyTopicEventsRole0D2D3332', 'Arn'] },
},
],
});
});

test('multiple uses of a topic as a target results in a single policy statement', () => {
test('specifying custom role for sns project target', () => {
// GIVEN
const stack = new Stack();
const topic = new sns.Topic(stack, 'MyTopic');
const rule = new events.Rule(stack, 'MyRule', {
schedule: events.Schedule.rate(Duration.hours(1)),
});
const role = new iam.Role(stack, 'MyRole', {
assumedBy: new iam.ServicePrincipal('events.amazonaws.com'),
});

// WHEN
for (let i = 0; i < 5; ++i) {
const rule = new events.Rule(stack, `Rule${i}`, {
schedule: events.Schedule.rate(Duration.hours(1)),
});
rule.addTarget(new targets.SnsTopic(topic));
}
rule.addTarget(new targets.SnsTopic(topic, { role }));

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::SNS::TopicPolicy', {
PolicyDocument: {
Statement: [
{
Action: 'sns:Publish',
Effect: 'Allow',
Principal: { Service: 'events.amazonaws.com' },
Resource: { Ref: 'MyTopic86869434' },
Sid: '0',
},
],
Version: '2012-10-17',
},
Topics: [{ Ref: 'MyTopic86869434' }],
Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', {
Targets: [
{
Arn: { Ref: 'MyTopic86869434' },
Id: 'Target0',
RoleArn: { 'Fn::GetAtt': ['MyRoleF48FFE04', 'Arn'] },
},
],
});
});

Expand Down Expand Up @@ -108,3 +140,4 @@ test('dead letter queue is configured correctly', () => {
],
});
});

Loading