Skip to content

Commit a47c67c

Browse files
authored
Add support for sourceOwner policy (#113)
1 parent 28d3102 commit a47c67c

File tree

7 files changed

+73
-14
lines changed

7 files changed

+73
-14
lines changed

packages/sns/lib/sns/AbstractSnsService.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export type ExistingSNSOptionsMultiSchema<
7171

7272
export type ExtraSNSCreationParams = {
7373
queueUrlsWithSubscribePermissionsPrefix?: string
74+
allowedSourceOwner?: string
7475
}
7576

7677
export type SNSCreationConfig = {

packages/sns/lib/utils/snsAttributeUtils.spec.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,36 @@ import { generateFilterAttributes, generateTopicSubscriptionPolicy } from './sns
99

1010
describe('snsAttributeUtils', () => {
1111
describe('generateTopicSubscriptionPolicy', () => {
12-
it('resolves policy', () => {
13-
const resolvedPolicy = generateTopicSubscriptionPolicy(
14-
'arn:aws:sns:eu-central-1:632374391739:test-sns-some-service',
15-
'arn:aws:sqs:eu-central-1:632374391739:test-sqs-*',
12+
it('resolves policy for both params', () => {
13+
const resolvedPolicy = generateTopicSubscriptionPolicy({
14+
topicArn: 'arn:aws:sns:eu-central-1:632374391739:test-sns-some-service',
15+
allowedSqsQueueUrlPrefix: 'arn:aws:sqs:eu-central-1:632374391739:test-sqs-*',
16+
allowedSourceOwner: '111111111111',
17+
})
18+
19+
expect(resolvedPolicy).toBe(
20+
`{"Version":"2012-10-17","Id":"__default_policy_ID","Statement":[{"Sid":"AllowSQSSubscription","Effect":"Allow","Principal":{"AWS":"*"},"Action":["sns:Subscribe"],"Resource":"arn:aws:sns:eu-central-1:632374391739:test-sns-some-service","Condition":{"StringEquals":{"AWS:SourceOwner": "111111111111"},"StringLike":{"sns:Endpoint":"arn:aws:sqs:eu-central-1:632374391739:test-sqs-*"}}}]}`,
21+
)
22+
})
23+
24+
it('resolves policy for one param', () => {
25+
const resolvedPolicy = generateTopicSubscriptionPolicy({
26+
topicArn: 'arn:aws:sns:eu-central-1:632374391739:test-sns-some-service',
27+
allowedSourceOwner: '111111111111',
28+
})
29+
30+
expect(resolvedPolicy).toBe(
31+
`{"Version":"2012-10-17","Id":"__default_policy_ID","Statement":[{"Sid":"AllowSQSSubscription","Effect":"Allow","Principal":{"AWS":"*"},"Action":["sns:Subscribe"],"Resource":"arn:aws:sns:eu-central-1:632374391739:test-sns-some-service","Condition":{"StringEquals":{"AWS:SourceOwner": "111111111111"}}}]}`,
1632
)
33+
})
34+
35+
it('resolves policy for zero params', () => {
36+
const resolvedPolicy = generateTopicSubscriptionPolicy({
37+
topicArn: 'arn:aws:sns:eu-central-1:632374391739:test-sns-some-service',
38+
})
1739

1840
expect(resolvedPolicy).toBe(
19-
`{"Version":"2012-10-17","Id":"__default_policy_ID","Statement":[{"Sid":"AllowSQSSubscription","Effect":"Allow","Principal":{"AWS":"*"},"Action":["sns:Subscribe"],"Resource":"arn:aws:sns:eu-central-1:632374391739:test-sns-some-service","Condition":{"StringLike":{"sns:Endpoint":"arn:aws:sqs:eu-central-1:632374391739:test-sqs-*"}}}]}`,
41+
`{"Version":"2012-10-17","Id":"__default_policy_ID","Statement":[{"Sid":"AllowSQSSubscription","Effect":"Allow","Principal":{"AWS":"*"},"Action":["sns:Subscribe"],"Resource":"arn:aws:sns:eu-central-1:632374391739:test-sns-some-service","Condition":{}}]}`,
2042
)
2143
})
2244
})

packages/sns/lib/utils/snsAttributeUtils.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,23 @@ import type { ZodSchema } from 'zod'
33
// See https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_version.html
44
const POLICY_VERSION = '2012-10-17'
55

6-
export function generateTopicSubscriptionPolicy(
7-
topicArn: string,
8-
supportedSqsQueueUrlPrefix: string,
9-
) {
10-
return `{"Version":"${POLICY_VERSION}","Id":"__default_policy_ID","Statement":[{"Sid":"AllowSQSSubscription","Effect":"Allow","Principal":{"AWS":"*"},"Action":["sns:Subscribe"],"Resource":"${topicArn}","Condition":{"StringLike":{"sns:Endpoint":"${supportedSqsQueueUrlPrefix}"}}}]}`
6+
export type TopicSubscriptionPolicyParams = {
7+
topicArn: string
8+
allowedSqsQueueUrlPrefix?: string
9+
allowedSourceOwner?: string
10+
}
11+
12+
export function generateTopicSubscriptionPolicy(params: TopicSubscriptionPolicyParams) {
13+
const sourceOwnerFragment = params.allowedSourceOwner
14+
? `"StringEquals":{"AWS:SourceOwner": "${params.allowedSourceOwner}"}`
15+
: ''
16+
const supportedSqsQueueUrlPrefixFragment = params.allowedSqsQueueUrlPrefix
17+
? `"StringLike":{"sns:Endpoint":"${params.allowedSqsQueueUrlPrefix}"}`
18+
: ''
19+
const commaFragment =
20+
sourceOwnerFragment.length > 0 && supportedSqsQueueUrlPrefixFragment.length > 0 ? ',' : ''
21+
22+
return `{"Version":"${POLICY_VERSION}","Id":"__default_policy_ID","Statement":[{"Sid":"AllowSQSSubscription","Effect":"Allow","Principal":{"AWS":"*"},"Action":["sns:Subscribe"],"Resource":"${params.topicArn}","Condition":{${sourceOwnerFragment}${commaFragment}${supportedSqsQueueUrlPrefixFragment}}}]}`
1123
}
1224

1325
export function generateFilterAttributes(

packages/sns/lib/utils/snsInitter.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export async function initSnsSqs(
4747
updateAttributesIfExists: creationConfig.updateAttributesIfExists,
4848
queueUrlsWithSubscribePermissionsPrefix:
4949
creationConfig.queueUrlsWithSubscribePermissionsPrefix,
50+
allowedSourceOwner: creationConfig.allowedSourceOwner,
5051
topicArnsWithPublishPermissionsPrefix: creationConfig.topicArnsWithPublishPermissionsPrefix,
5152
logger: extraParams?.logger,
5253
},
@@ -171,6 +172,7 @@ export async function initSns(
171172
}
172173
const topicArn = await assertTopic(snsClient, creationConfig.topic, {
173174
queueUrlsWithSubscribePermissionsPrefix: creationConfig.queueUrlsWithSubscribePermissionsPrefix,
175+
allowedSourceOwner: creationConfig.allowedSourceOwner,
174176
})
175177
return {
176178
topicArn,

packages/sns/lib/utils/snsSubscriber.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export async function subscribeToTopic(
2525
) {
2626
const topicArn = await assertTopic(snsClient, topicConfiguration, {
2727
queueUrlsWithSubscribePermissionsPrefix: extraParams?.queueUrlsWithSubscribePermissionsPrefix,
28+
allowedSourceOwner: extraParams?.allowedSourceOwner,
2829
})
2930
const { queueUrl, queueArn } = await assertQueue(sqsClient, queueConfiguration, {
3031
topicArnsWithPublishPermissionsPrefix: extraParams?.topicArnsWithPublishPermissionsPrefix,

packages/sns/lib/utils/snsUtils.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,14 +85,15 @@ export async function assertTopic(
8585
}
8686
const topicArn = response.TopicArn
8787

88-
if (extraParams?.queueUrlsWithSubscribePermissionsPrefix) {
88+
if (extraParams?.queueUrlsWithSubscribePermissionsPrefix || extraParams?.allowedSourceOwner) {
8989
const setTopicAttributesCommand = new SetTopicAttributesCommand({
9090
TopicArn: topicArn,
9191
AttributeName: 'Policy',
92-
AttributeValue: generateTopicSubscriptionPolicy(
92+
AttributeValue: generateTopicSubscriptionPolicy({
9393
topicArn,
94-
extraParams.queueUrlsWithSubscribePermissionsPrefix,
95-
),
94+
allowedSqsQueueUrlPrefix: extraParams.queueUrlsWithSubscribePermissionsPrefix,
95+
allowedSourceOwner: extraParams.allowedSourceOwner,
96+
}),
9697
})
9798
await snsClient.send(setTopicAttributesCommand)
9899
}

packages/sns/test/publishers/SnsPermissionPublisher.spec.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,26 @@ describe('SNSPermissionPublisher', () => {
5050
)
5151
})
5252

53+
it('sets correct policy when two policy fields are set', async () => {
54+
const newPublisher = new SnsPermissionPublisherMonoSchema(diContainer.cradle, {
55+
creationConfig: {
56+
topic: {
57+
Name: 'policy-topic',
58+
},
59+
queueUrlsWithSubscribePermissionsPrefix: 'dummy*',
60+
allowedSourceOwner: '111111111111',
61+
},
62+
})
63+
64+
await newPublisher.init()
65+
66+
const topic = await getTopicAttributes(snsClient, newPublisher.topicArn)
67+
68+
expect(topic.result?.attributes?.Policy).toBe(
69+
`{"Version":"2012-10-17","Id":"__default_policy_ID","Statement":[{"Sid":"AllowSQSSubscription","Effect":"Allow","Principal":{"AWS":"*"},"Action":["sns:Subscribe"],"Resource":"arn:aws:sns:eu-west-1:000000000000:policy-topic","Condition":{"StringEquals":{"AWS:SourceOwner":"111111111111"},"StringLike":{"sns:Endpoint":"dummy*"}}}]}`,
70+
)
71+
})
72+
5373
// FixMe https://github.com/localstack/localstack/issues/9306
5474
it.skip('throws an error when invalid queue locator is passed', async () => {
5575
const newPublisher = new SnsPermissionPublisherMonoSchema(diContainer.cradle, {

0 commit comments

Comments
 (0)