Skip to content

Commit

Permalink
Add support for sourceOwner policy (#113)
Browse files Browse the repository at this point in the history
  • Loading branch information
kibertoad authored Mar 31, 2024
1 parent 28d3102 commit a47c67c
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 14 deletions.
1 change: 1 addition & 0 deletions packages/sns/lib/sns/AbstractSnsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export type ExistingSNSOptionsMultiSchema<

export type ExtraSNSCreationParams = {
queueUrlsWithSubscribePermissionsPrefix?: string
allowedSourceOwner?: string
}

export type SNSCreationConfig = {
Expand Down
32 changes: 27 additions & 5 deletions packages/sns/lib/utils/snsAttributeUtils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,36 @@ import { generateFilterAttributes, generateTopicSubscriptionPolicy } from './sns

describe('snsAttributeUtils', () => {
describe('generateTopicSubscriptionPolicy', () => {
it('resolves policy', () => {
const resolvedPolicy = generateTopicSubscriptionPolicy(
'arn:aws:sns:eu-central-1:632374391739:test-sns-some-service',
'arn:aws:sqs:eu-central-1:632374391739:test-sqs-*',
it('resolves policy for both params', () => {
const resolvedPolicy = generateTopicSubscriptionPolicy({
topicArn: 'arn:aws:sns:eu-central-1:632374391739:test-sns-some-service',
allowedSqsQueueUrlPrefix: 'arn:aws:sqs:eu-central-1:632374391739:test-sqs-*',
allowedSourceOwner: '111111111111',
})

expect(resolvedPolicy).toBe(
`{"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-*"}}}]}`,
)
})

it('resolves policy for one param', () => {
const resolvedPolicy = generateTopicSubscriptionPolicy({
topicArn: 'arn:aws:sns:eu-central-1:632374391739:test-sns-some-service',
allowedSourceOwner: '111111111111',
})

expect(resolvedPolicy).toBe(
`{"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"}}}]}`,
)
})

it('resolves policy for zero params', () => {
const resolvedPolicy = generateTopicSubscriptionPolicy({
topicArn: 'arn:aws:sns:eu-central-1:632374391739:test-sns-some-service',
})

expect(resolvedPolicy).toBe(
`{"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-*"}}}]}`,
`{"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":{}}]}`,
)
})
})
Expand Down
22 changes: 17 additions & 5 deletions packages/sns/lib/utils/snsAttributeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,23 @@ import type { ZodSchema } from 'zod'
// See https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_version.html
const POLICY_VERSION = '2012-10-17'

export function generateTopicSubscriptionPolicy(
topicArn: string,
supportedSqsQueueUrlPrefix: string,
) {
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}"}}}]}`
export type TopicSubscriptionPolicyParams = {
topicArn: string
allowedSqsQueueUrlPrefix?: string
allowedSourceOwner?: string
}

export function generateTopicSubscriptionPolicy(params: TopicSubscriptionPolicyParams) {
const sourceOwnerFragment = params.allowedSourceOwner
? `"StringEquals":{"AWS:SourceOwner": "${params.allowedSourceOwner}"}`
: ''
const supportedSqsQueueUrlPrefixFragment = params.allowedSqsQueueUrlPrefix
? `"StringLike":{"sns:Endpoint":"${params.allowedSqsQueueUrlPrefix}"}`
: ''
const commaFragment =
sourceOwnerFragment.length > 0 && supportedSqsQueueUrlPrefixFragment.length > 0 ? ',' : ''

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}}}]}`
}

export function generateFilterAttributes(
Expand Down
2 changes: 2 additions & 0 deletions packages/sns/lib/utils/snsInitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export async function initSnsSqs(
updateAttributesIfExists: creationConfig.updateAttributesIfExists,
queueUrlsWithSubscribePermissionsPrefix:
creationConfig.queueUrlsWithSubscribePermissionsPrefix,
allowedSourceOwner: creationConfig.allowedSourceOwner,
topicArnsWithPublishPermissionsPrefix: creationConfig.topicArnsWithPublishPermissionsPrefix,
logger: extraParams?.logger,
},
Expand Down Expand Up @@ -171,6 +172,7 @@ export async function initSns(
}
const topicArn = await assertTopic(snsClient, creationConfig.topic, {
queueUrlsWithSubscribePermissionsPrefix: creationConfig.queueUrlsWithSubscribePermissionsPrefix,
allowedSourceOwner: creationConfig.allowedSourceOwner,
})
return {
topicArn,
Expand Down
1 change: 1 addition & 0 deletions packages/sns/lib/utils/snsSubscriber.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export async function subscribeToTopic(
) {
const topicArn = await assertTopic(snsClient, topicConfiguration, {
queueUrlsWithSubscribePermissionsPrefix: extraParams?.queueUrlsWithSubscribePermissionsPrefix,
allowedSourceOwner: extraParams?.allowedSourceOwner,
})
const { queueUrl, queueArn } = await assertQueue(sqsClient, queueConfiguration, {
topicArnsWithPublishPermissionsPrefix: extraParams?.topicArnsWithPublishPermissionsPrefix,
Expand Down
9 changes: 5 additions & 4 deletions packages/sns/lib/utils/snsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,15 @@ export async function assertTopic(
}
const topicArn = response.TopicArn

if (extraParams?.queueUrlsWithSubscribePermissionsPrefix) {
if (extraParams?.queueUrlsWithSubscribePermissionsPrefix || extraParams?.allowedSourceOwner) {
const setTopicAttributesCommand = new SetTopicAttributesCommand({
TopicArn: topicArn,
AttributeName: 'Policy',
AttributeValue: generateTopicSubscriptionPolicy(
AttributeValue: generateTopicSubscriptionPolicy({
topicArn,
extraParams.queueUrlsWithSubscribePermissionsPrefix,
),
allowedSqsQueueUrlPrefix: extraParams.queueUrlsWithSubscribePermissionsPrefix,
allowedSourceOwner: extraParams.allowedSourceOwner,
}),
})
await snsClient.send(setTopicAttributesCommand)
}
Expand Down
20 changes: 20 additions & 0 deletions packages/sns/test/publishers/SnsPermissionPublisher.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,26 @@ describe('SNSPermissionPublisher', () => {
)
})

it('sets correct policy when two policy fields are set', async () => {
const newPublisher = new SnsPermissionPublisherMonoSchema(diContainer.cradle, {
creationConfig: {
topic: {
Name: 'policy-topic',
},
queueUrlsWithSubscribePermissionsPrefix: 'dummy*',
allowedSourceOwner: '111111111111',
},
})

await newPublisher.init()

const topic = await getTopicAttributes(snsClient, newPublisher.topicArn)

expect(topic.result?.attributes?.Policy).toBe(
`{"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*"}}}]}`,
)
})

// FixMe https://github.com/localstack/localstack/issues/9306
it.skip('throws an error when invalid queue locator is passed', async () => {
const newPublisher = new SnsPermissionPublisherMonoSchema(diContainer.cradle, {
Expand Down

0 comments on commit a47c67c

Please sign in to comment.