Checked in new pattern rabbitmq-private-lambda-python-sam - #3224
Conversation
…amoDB - Add comprehensive README documenting the RabbitMQ consumer pattern setup and deployment workflow - Add RabbitMQAndClientEC2.yaml CloudFormation template to provision Amazon MQ cluster in private subnets with EC2 client instance - Add create_rabbit_queue.sh script to configure RabbitMQ virtualhosts, exchanges, and queues - Add query_rabbit_queue.sh script to verify RabbitMQ queue configuration - Add Lambda consumer function (lambda_function.py) to process RabbitMQ messages and store in DynamoDB - Add SAM template (template_original.yaml) for Lambda function deployment - Add RabbitMQ producer script (rabbitmq_producer.py) to publish JSON messages for testing - Add example-pattern.json with pattern metadata and author information - Add test event (event.json) for local Lambda invocation testing - Add dependencies and configuration files (requirements.txt, commands.sh, us-500.csv sample data) - Provides complete serverless pattern for consuming messages from private RabbitMQ clusters using Python Lambda functions
- Rename template_original.yaml to template.yaml for standard SAM deployment - Update Lambda runtime from PYTHON3_VERSION placeholder to python3.13 - Establish template.yaml as the active configuration for RabbitMQ consumer pattern
…nfiguration - Update build command to use `sam build --use-container` for consistent environment builds - Replace hardcoded Python 3.13 runtime with `PYTHON3_VERSION` variable for better maintainability - Improves cross-platform compatibility and reduces environment-related build issues
…ontrol - Add template_original.yaml as reference copy of RabbitMQ consumer SAM configuration - Preserve baseline SAM template with Python Lambda, DynamoDB integration, and MQ event source - Include original parameter defaults and IAM policy definitions for historical tracking - Enable easy comparison between template iterations and maintained configurations
- Delete redundant template.yaml from rabbitmq_consumer_dynamo_sam subdirectory - Primary SAM configuration now consolidated in parent directory - Eliminates template duplication and reduces maintenance burden
…mand - Remove unnecessary --use-container flag from SAM build instructions - Simplify build command to use default local build process - Update documentation to reflect current build configuration
bfreiberg
left a comment
There was a problem hiding this comment.
Code review — rabbitmq-private-lambda-python-sam
Thanks for the contribution! I reviewed the pattern and validated the findings against a live end-to-end deployment. Findings below are grouped by severity, and I've marked each with how it was verified.
🔴 High — data loss
1. Consumer silently drops the entire batch on any error — rabbitmq_consumer_dynamo_sam/rabbitmq_event_consumer_function/lambda_function.py:19-90
lambda_handler wraps the whole message loop in try/except and, on any exception, logs it and return "500". Amazon MQ event source mappings ignore the handler's return value and only retry/redrive when the invocation raises. Returning normally is treated as success, so the batch is acked and removed from the queue.
Verified live: I pointed the function at a table it couldn't write, published 20 messages, and observed:
- ESM delivered 2 batches; each logged
An exception occurred - ... PutItem ... AccessDeniedException - Lambda
Errorsmetric = 0,Invocations= 2 (ESM saw success) - DynamoDB table count = 0 (nothing persisted)
- Queue depth = 0, no redelivery — the messages were gone
Because the try wraps the whole loop, the logs also showed only the first message of each batch reached put_item before the exception aborted the remaining 9 — so one bad/throttled message discards everything after it in the batch too.
Suggested fix: let exceptions propagate (remove the catch-all), or adopt partial-batch responses (ReportBatchItemFailures / bisectBatchOnFunctionError + a DLQ) so failed messages are retried instead of silently dropped.
2. Producer off-by-one skips the first record and crashes at the max count — rabbitmq_message_sender_json/rabbitmq_producer.py:76-77
for i in range(1, num_to_send + 1): ... people[i] skips people[0] and reads out of bounds when num_to_send == len(people). us-500.csv has 500 rows (0–499, no header).
Verified live: running the real producer with 500 raised IndexError: list index out of range at people[500] after publishing only 499 messages (the first CSV record was never sent). The README explicitly tells users to pass "a number between 1 and 500", so 500 is a documented input.
Suggested fix: for i in range(num_to_send): person = get_person_from_row(people[i]) (and update the header/number fields accordingly).
🟠 Medium — security posture
3. Broker security group opens all ports to the whole VPC — RabbitMQAndClientEC2.yaml:337-340
Alongside the scoped 5671/443 rules, RabbitMQSecurityGroup allows tcp 0-65535 from the VPC CIDR 10.2.0.0/16. Verified on the deployed SG. Recommend dropping this rule and keeping only 5671 (and 443 if the console/management API is needed) from the client SG.
4. Client EC2 instance role is effectively account administrator — RabbitMQAndClientEC2.yaml:681-732
The role attaches IAMFullAccess, AWSLambda_FullAccess, AmazonS3FullAccess, AmazonDynamoDBFullAccess_v2, AWSCloudFormationFullAccess, CloudWatchFullAccess, plus inline iam:* and mq:* on *. Verified on the deployed role. IAMFullAccess + iam:* also allows privilege escalation. Recommend scoping to the specific actions the setup scripts and sam deploy actually need.
5. Hardcoded default broker password — RabbitMQAndClientEC2.yaml:39-43
RabbitMQBrokerPassword defaults to rabbitmqPassword123, committed to a public repo and also written into Secrets Manager. Recommend removing the default and requiring the value at deploy time (or generating one via Secrets Manager).
🟡 Low — correctness / docs
6. Empty messageId breaks the DynamoDB write — lambda_function.py:95
MessageID (the table's partition key) defaults to '' when basicProperties.messageId is absent. Verified live: PutItem with an empty-string key returns ValidationException: The AttributeValue for a key attribute cannot contain an empty string value (and that error is then swallowed by finding #1). Recommend skipping/dead-lettering messages without a usable key.
7. MessageType stores the message ID, not the type — lambda_function.py:116
'MessageType': message_id — should likely be basic_properties.get('type', '') (the type is otherwise only stored under Type).
8. templateFile points at a non-existent directory — example-pattern.json:20
"activemq_consumer_dynamo_sam/template_original.yaml" — this pattern's directory is rabbitmq_consumer_dynamo_sam (looks like a leftover from an ActiveMQ pattern). Breaks the generated template link.
9. Python3Version parameter description contradicts its allowed values — RabbitMQAndClientEC2.yaml:10-18
The description says "between 3.9 and 3.12 … maximum allowed version is 3.12", but AllowedValues goes up to python3.14 and the default is python3.14.
- Fix silent batch drop: remove catch-all try/except so ESM retries on error - Fix producer off-by-one: use range(num_to_send) with zero-based indexing - Remove overly permissive SG rule (0-65535 all ports to VPC CIDR) - Scope down EC2 instance role from admin to SAM deploy permissions - Auto-generate broker password via Secrets Manager (GenerateSecretString) instead of hardcoded default; broker and EC2 UserData retrieve from secret - Guard against empty messageId breaking DynamoDB partition key - Fix MessageType field to store type instead of message_id - Fix templateFile path from activemq to rabbitmq in example-pattern.json - Update Python3Version parameter description to match AllowedValues - Apply README formatting: backtick script names, remove redundant text - Add copyright footer to README
- Fix silent batch drop: remove catch-all try/except so ESM retries on error - Fix producer off-by-one: use range(num_to_send) with zero-based indexing - Remove overly permissive SG rule (0-65535 all ports to VPC CIDR) - Scope down EC2 instance role from admin to SAM deploy permissions - Auto-generate broker password via Secrets Manager (GenerateSecretString) instead of hardcoded default; broker and EC2 UserData retrieve from secret - Guard against empty messageId breaking DynamoDB partition key - Fix MessageType field to store type instead of message_id - Fix templateFile path from activemq to rabbitmq in example-pattern.json - Update Python3Version parameter description to match AllowedValues - Apply README formatting: backtick script names, remove redundant text - Add copyright footer to README
- Fix silent batch drop: remove catch-all try/except so ESM retries on error - Fix producer off-by-one: use range(num_to_send) with zero-based indexing - Remove overly permissive SG rule (0-65535 all ports to VPC CIDR) - Scope down EC2 instance role from admin to SAM deploy permissions - Auto-generate broker password via Secrets Manager (GenerateSecretString) instead of hardcoded default; broker and EC2 UserData retrieve from secret - Guard against empty messageId breaking DynamoDB partition key - Fix MessageType field to store type instead of message_id - Fix templateFile path from activemq to rabbitmq in example-pattern.json - Update Python3Version parameter description to match AllowedValues - Apply README formatting: backtick script names, remove redundant text - Add copyright footer to README
Addresses all 9 findings from code review plus README formatting suggestions. HIGH - Data loss: - Remove catch-all try/except in Lambda handler so ESM retries on error - Fix producer off-by-one: use range(num_to_send) with zero-based indexing MEDIUM - Security posture: - Remove overly permissive SG rule (0-65535 all ports to VPC CIDR) - Add self-referencing SG rule for ESM ENI connectivity to broker - Scope down EC2 instance role to SAM deploy permissions only - Auto-generate broker password via Secrets Manager (GenerateSecretString) with ExcludePunctuation for Amazon MQ pattern compatibility - Shell scripts fetch credentials from Secrets Manager at runtime LOW - Correctness/docs: - Guard against empty messageId breaking DynamoDB partition key - Fix MessageType field to store type instead of message_id - Fix templateFile path from activemq to rabbitmq in example-pattern.json - Update Python3Version parameter description to match AllowedValues - Apply README formatting: backtick script names, remove redundant text - Add copyright footer to README
ec86164 to
e393372
Compare
Addresses all 9 findings from code review plus README formatting suggestions. HIGH - Data loss: - Remove catch-all try/except in Lambda handler so ESM retries on error - Fix producer off-by-one: use range(num_to_send) with zero-based indexing MEDIUM - Security posture: - Remove overly permissive SG rule (0-65535 all ports to VPC CIDR) - Add self-referencing SG rule (separate resource) for ESM ENI connectivity - Scope down EC2 instance role to SAM deploy permissions only - Auto-generate broker password via Secrets Manager (GenerateSecretString) with ExcludePunctuation for Amazon MQ pattern compatibility - Shell scripts fetch credentials from Secrets Manager at runtime LOW - Correctness/docs: - Guard against empty messageId breaking DynamoDB partition key - Fix MessageType field to store type instead of message_id - Fix templateFile path from activemq to rabbitmq in example-pattern.json - Update Python3Version parameter description to match AllowedValues - Apply README formatting: backtick script names, remove redundant text - Add copyright footer to README
Addresses all 9 findings from code review plus README formatting suggestions. HIGH - Data loss: - Remove catch-all try/except in Lambda handler so ESM retries on error - Fix producer off-by-one: use range(num_to_send) with zero-based indexing MEDIUM - Security posture: - Remove overly permissive SG rule (0-65535 all ports to VPC CIDR) - Add self-referencing SG rule (separate resource) for ESM ENI connectivity - Scope down EC2 instance role to SAM deploy permissions only - Auto-generate broker password via Secrets Manager (GenerateSecretString) with ExcludePunctuation for Amazon MQ pattern compatibility - Shell scripts fetch credentials from Secrets Manager at runtime LOW - Correctness/docs: - Guard against empty messageId breaking DynamoDB partition key - Fix MessageType field to store type instead of message_id - Fix templateFile path from activemq to rabbitmq in example-pattern.json - Update Python3Version parameter description to match AllowedValues - Apply README formatting: backtick script names, remove redundant text - Add copyright footer to README
|
🔴 High — Data Loss 1: Consumer silently drops the entire batch on any errorWhat we changed: Removed the catch-all try/except block entirely from lambda_handler. Exceptions now propagate naturally, causing the Lambda invocation to fail. The ESM sees the failure and retries the batch (or sends to DLQ if configured). Why: The ESM contract for Amazon MQ is that a successful invocation (no exception raised) means the batch was processed. The only way to signal failure is to let the exception propagate. This is different from SQS-based event sources where you can use batchItemFailures in the response. 2: Producer off-by-one skips the first record and crashes at the max countWhat we changed: Changed to for i in range(num_to_send): person = get_person_from_row(people[i]). Updated MessageNumberInBatch, correlation_id, message_id, and the print statement to use i + 1 for 1-based display numbers. 🟠 Medium — Security Posture 3: Broker security group opens all ports to the whole VPCWhat we changed: Removed the tcp 0-65535 rule entirely. The broker SG now only allows: Port 5671 from the client SG (AMQPS connections) 4: Client EC2 instance role is effectively account administratorWhat we changed: Removed all overly broad managed policies and the
Kept only 5: Hardcoded default broker passwordWhat we changed: Removed the GenerateSecretString:
SecretStringTemplate: !Sub '{"username": "${RabbitMQBrokerAdminUser}"}'
GenerateStringKey: 'password'
PasswordLength: 24
ExcludePunctuation: trueThe broker retrieves the password via a CloudFormation dynamic reference: Password: !Sub '{{resolve:secretsmanager:${RabbitMQSecret}:SecretString:password}}'Shell scripts ( 🟡 Low — Correctness / Docs6: Empty
|
Issue #, if available:
Description of changes: - We created a new pattern rabbitmq-private-lambda-java-sam a few months ago. Now checking in the Python equivalent of the pattern rabbitmq-private-lambda-python-sam
When running the CloudFormation template, please remember to change the input parameter ServerlessLandGithubLocation from https://github.com/aws-samples/serverless-patterns.git to https://github.com/gmedard/serverless-patterns.git, else the sample will not work. This is only for testing till the PR is merged. Customers won't have to undertake this step.
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.