Skip to content

Commit eaaf927

Browse files
authored
Feat: SQS policy config (#334)
* Adding policy config types * Generating policy from config * SNS ignoring new prop * release prepare * Adding tests * Lint fixes * Adding doc * Export fixes
1 parent 33caa6f commit eaaf927

14 files changed

+642
-31
lines changed

README.md

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ They implement the following public methods:
3232
* `messageTypeField` - which field in the message describes the type of a message. This field needs to be defined as `z.literal` in the schema and is used for resolving the correct schema for validation
3333
* `locatorConfig` - configuration for resolving existing queue and/or topic. Should not be specified together with the `creationConfig`.
3434
* `creationConfig` - configuration for queue and/or topic to create, if one does not exist. Should not be specified together with the `locatorConfig`;
35+
* `policyConfig` - SQS only - configuration for queue access policies (see [SQS Policy Configuration](#sqs-policy-configuration) for more information);
3536
* `deletionConfig` - automatic cleanup of resources;
3637
* `handlerSpy` - allow awaiting certain messages to be published (see [Handler Spies](#handler-spies) for more information);
3738
* `logMessages` - add logs for processed messages.
@@ -99,6 +100,7 @@ Multi-schema consumers support multiple message types via handler configs. They
99100
* `locatorConfig` - configuration for resolving existing queue and/or topic. Should not be specified together with the `creationConfig`.
100101
* `creationConfig` - configuration for queue and/or topic to create, if one does not exist. Should not be specified together with the `locatorConfig`.
101102
* `subscriptionConfig` - SNS SQS consumer only - configuration for SNS -> SQS subscription to create, if one doesn't exist.
103+
* `policyConfig` - SQS only - configuration for queue access policies (see [SQS Policy Configuration](#sqs-policy-configuration) for more information);
102104
* `deletionConfig` - automatic cleanup of resources;
103105
* `consumerOverrides` – available only for SQS consumers;
104106
* `deadLetterQueue` - available only for SQS and SNS consumers (see [Dead Letter Queue](#dead-letter-queue) for more information);
@@ -237,6 +239,143 @@ Both publishers and consumers accept a queue name and configuration as parameter
237239

238240
If you do not want to create a new queue/topic, you can set `queueLocator` field for `queueConfiguration`. In that case `message-queue-toolkit` will not attempt to create a new queue or topic, and instead throw an error if they don't already exist.
239241

242+
## SQS Policy Configuration
243+
244+
SQS queues can be configured with access policies to control who can send messages to and receive messages from the queue. The `policyConfig` parameter allows you to define these policies when creating or updating SQS queues.
245+
246+
### Policy Configuration Options
247+
248+
The `policyConfig` parameter accepts the following structure:
249+
250+
```typescript
251+
type SQSPolicyConfig = {
252+
resource: string | typeof SQS_RESOURCE_ANY | typeof SQS_RESOURCE_CURRENT_QUEUE
253+
statements?: SQSPolicyStatement | SQSPolicyStatement[]
254+
}
255+
256+
type SQSPolicyStatement = {
257+
Effect?: string // "Allow" or "Deny" (default: "Allow")
258+
Principal?: string // AWS principal ARN (default: "*")
259+
Action?: string[] // SQS actions (default: ["sqs:SendMessage", "sqs:GetQueueAttributes", "sqs:GetQueueUrl"])
260+
}
261+
```
262+
263+
### Resource Types
264+
265+
The `resource` field supports three types of values:
266+
267+
- **`SQS_RESOURCE_CURRENT_QUEUE`**: Uses the ARN of the current queue being created
268+
- **`SQS_RESOURCE_ANY`**: Uses the wildcard ARN `arn:aws:sqs:*:*:*`
269+
- **Custom ARN**: Any specific SQS queue ARN (e.g., `arn:aws:sqs:us-east-1:123456789012:my-queue`)
270+
271+
### Usage Examples
272+
273+
#### Basic Policy Configuration
274+
275+
```typescript
276+
import { SQS_RESOURCE_ANY } from "@message-queue-toolkit/sqs"
277+
278+
const publisher = new SqsPermissionPublisher(dependencies, {
279+
creationConfig: {
280+
queue: {
281+
QueueName: "my-queue",
282+
Attributes: {
283+
VisibilityTimeout: "30",
284+
},
285+
},
286+
policyConfig: {
287+
resource: SQS_RESOURCE_ANY,
288+
statements: {
289+
Effect: "Allow",
290+
Principal: "arn:aws:iam::123456789012:user/my-user",
291+
Action: ["sqs:SendMessage", "sqs:ReceiveMessage"],
292+
},
293+
},
294+
},
295+
})
296+
```
297+
298+
#### Multiple Policy Statements
299+
300+
```typescript
301+
const consumer = new SqsPermissionConsumer(dependencies, {
302+
creationConfig: {
303+
queue: {
304+
QueueName: "my-queue",
305+
},
306+
policyConfig: {
307+
resource: SQS_RESOURCE_CURRENT_QUEUE,
308+
statements: [
309+
{
310+
Effect: "Allow",
311+
Principal: "arn:aws:iam::123456789012:role/my-role",
312+
Action: ["sqs:SendMessage", "sqs:ReceiveMessage"],
313+
},
314+
{
315+
Effect: "Deny",
316+
Principal: "arn:aws:iam::123456789012:user/blocked-user",
317+
Action: ["sqs:SendMessage"],
318+
},
319+
],
320+
},
321+
},
322+
})
323+
```
324+
325+
#### Policy Updates
326+
327+
To update an existing queue's policy, use `updateAttributesIfExists: true`:
328+
329+
```typescript
330+
const updatedPublisher = new SqsPermissionPublisher(dependencies, {
331+
creationConfig: {
332+
queue: {
333+
QueueName: "existing-queue",
334+
},
335+
policyConfig: {
336+
resource: SQS_RESOURCE_ANY,
337+
statements: {
338+
Effect: "Allow",
339+
Principal: "arn:aws:iam::123456789012:user/new-user",
340+
Action: ["sqs:SendMessage"],
341+
},
342+
},
343+
updateAttributesIfExists: true,
344+
},
345+
deletionConfig: {
346+
deleteIfExists: false,
347+
},
348+
})
349+
```
350+
351+
### Default Behavior
352+
353+
- If `policyConfig` is not provided or set to `undefined`, no policy is applied to the queue
354+
- If `statements` is not provided, default values are used:
355+
- `Effect`: "Allow"
356+
- `Principal`: "*" (allows all AWS principals)
357+
- `Action`: ["sqs:SendMessage", "sqs:GetQueueAttributes", "sqs:GetQueueUrl"]
358+
359+
### Policy Structure
360+
361+
The generated policy follows the AWS IAM policy format:
362+
363+
```json
364+
{
365+
"Version": "2012-10-17",
366+
"Resource": "arn:aws:sqs:*:*:*",
367+
"Statement": [
368+
{
369+
"Effect": "Allow",
370+
"Principal": {"AWS": "arn:aws:iam::123456789012:user/my-user"},
371+
"Action": ["sqs:SendMessage", "sqs:ReceiveMessage"]
372+
}
373+
]
374+
}
375+
```
376+
377+
> **_NOTE:_** See [SqsPermissionConsumer.spec.ts](./packages/sqs/test/consumers/SqsPermissionConsumer.spec.ts) and [SqsPermissionPublisher.spec.ts](./packages/sqs/test/publishers/SqsPermissionPublisher.spec.ts) for practical examples of policy configuration tests.
378+
240379
## Handler Spies
241380

242381
In certain cases you want to await until certain publisher publishes a message, or a certain handler consumes a message. For that you can use handler spy functionality, built into `message-queue-toolkit` directly.

packages/sns/lib/sns/AbstractSnsSqsConsumer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export type SNSSQSConsumerDependencies = SQSConsumerDependencies & {
1717
snsClient: SNSClient
1818
stsClient: STSClient
1919
}
20-
export type SNSSQSCreationConfig = SQSCreationConfig & SNSCreationConfig
20+
export type SNSSQSCreationConfig = Omit<SQSCreationConfig, 'policyConfig'> & SNSCreationConfig
2121

2222
export type SNSSQSQueueLocatorType = Partial<SQSQueueLocatorType> &
2323
SNSTopicLocatorType & {

packages/sns/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@message-queue-toolkit/sns",
3-
"version": "23.1.0",
3+
"version": "23.1.2",
44
"private": false,
55
"license": "MIT",
66
"description": "SNS adapter for message-queue-toolkit",

packages/sqs/lib/index.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@ export {
88
AbstractSqsPublisher,
99
OFFLOADED_PAYLOAD_SIZE_ATTRIBUTE,
1010
} from './sqs/AbstractSqsPublisher.ts'
11-
export type {
12-
ExtraSQSCreationParams,
13-
SQSCreationConfig,
14-
SQSDependencies,
15-
SQSQueueLocatorType,
11+
export {
12+
type ExtraSQSCreationParams,
13+
SQS_MESSAGE_MAX_SIZE,
14+
SQS_RESOURCE_ANY,
15+
SQS_RESOURCE_CURRENT_QUEUE,
16+
type SQSCreationConfig,
17+
type SQSDependencies,
18+
type SQSPolicyConfig,
19+
type SQSQueueLocatorType,
1620
} from './sqs/AbstractSqsService.ts'
17-
export { SQS_MESSAGE_MAX_SIZE } from './sqs/AbstractSqsService.ts'
1821
export type { CommonMessage, SQSMessage } from './types/MessageTypes.ts'
1922
export { resolveOutgoingMessageAttributes } from './utils/messageUtils.ts'
2023
export {

packages/sqs/lib/sqs/AbstractSqsConsumer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,6 @@ export abstract class AbstractSqsConsumer<
590590
}
591591

592592
// parseInt is safe because if the value is not a number process should have failed on init
593-
return visibilityTimeoutString ? Number.parseInt(visibilityTimeoutString) : undefined
593+
return visibilityTimeoutString ? Number.parseInt(visibilityTimeoutString, 10) : undefined
594594
}
595595
}

packages/sqs/lib/sqs/AbstractSqsPublisher.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ export abstract class AbstractSqsPublisher<MessagePayloadType extends object>
7777
this.logMessage(resolvedLogMessage)
7878
}
7979

80-
// biome-ignore lint/style/noParameterAssign: This is expected
8180
message = this.updateInternalProperties(message)
8281
const maybeOffloadedPayloadMessage = await this.offloadMessagePayloadIfNeeded(message, () =>
8382
calculateOutgoingMessageSize(message),

packages/sqs/lib/sqs/AbstractSqsService.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { deleteSqs, initSqs } from '../utils/sqsInitter.ts'
66

77
// https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/quotas-messages.html
88
export const SQS_MESSAGE_MAX_SIZE = 256 * 1024 // 256KB
9+
export const SQS_RESOURCE_ANY = Symbol('any')
10+
export const SQS_RESOURCE_CURRENT_QUEUE = Symbol('current_queue')
911

1012
export type SQSDependencies = QueueDependencies & {
1113
sqsClient: SQSClient
@@ -15,12 +17,25 @@ export type ExtraSQSCreationParams = {
1517
topicArnsWithPublishPermissionsPrefix?: string
1618
updateAttributesIfExists?: boolean
1719
forceTagUpdate?: boolean
20+
policyConfig?: SQSPolicyConfig
21+
}
22+
23+
type SQSPolicyStatement = {
24+
Effect?: string
25+
Principal?: string
26+
Action?: string[]
27+
}
28+
29+
export type SQSPolicyConfig = {
30+
resource: string | typeof SQS_RESOURCE_ANY | typeof SQS_RESOURCE_CURRENT_QUEUE
31+
statements?: SQSPolicyStatement | SQSPolicyStatement[]
1832
}
1933

2034
export type SQSCreationConfig = {
2135
queue: CreateQueueRequest
2236
updateAttributesIfExists?: boolean
2337
forceTagUpdate?: boolean
38+
policyConfig?: SQSPolicyConfig
2439
} & ExtraSQSCreationParams
2540

2641
export type SQSQueueLocatorType =

0 commit comments

Comments
 (0)