diff --git a/sqs-lambda-tenant-isolation-sam-py/README.md b/sqs-lambda-tenant-isolation-sam-py/README.md new file mode 100644 index 000000000..196251b30 --- /dev/null +++ b/sqs-lambda-tenant-isolation-sam-py/README.md @@ -0,0 +1,61 @@ +# Lambda Tenant Isolation with SQS + +This pattern demonstrate AWS Lambda's tenant isolation feature in Multi-tenant application. It uses single SQS for multi-tenant applucation and isolating messages using messagegroupid and invoking isolated lambda enviornments. + +## Key Features + +- Tenant isolation at infrastructure level (no custom routing logic) +- Execution environments never shared between tenants +- Asynchronous invocation pattern +- Automatic tenant context propagation + +Learn more about this pattern at [Serverless Land Patterns](https://serverlessland.com/patterns/sqs-lambda-tenant-isolation) + +Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example. + + +## Architecture + +``` +SQS Queue → SQS Processor Lambda → Tenant-Isolated Lambda + (reads customer-id) (processes with tenant isolation) +``` + +## Components + +### 1. SQS Processor (`sqs-processor/`) +- Triggered by SQS queue messages +- Extracts `customer-id` from message payload +- Invokes tenant-isolated Lambda asynchronously with `TenantId` parameter + +### 2. Tenant-Isolated Processor (`tenant-isolated-processor/`) +- Configured with tenant isolation mode enabled +- Processes requests in isolated execution environments per tenant +- Accesses tenant ID via `context.identity.tenant_id` + +## Message Format + +```json +{ + "data": "your payload here" +} +``` + +## Deployment + +```bash +sam build +sam deploy --guided +``` + +## Testing + +Send a message to the SQS queue: + +```bash +aws sqs send-message \ + --queue-url \ + --message-body '{"data": "test payload"}' +``` + + diff --git a/sqs-lambda-tenant-isolation-sam-py/example-pattern.json b/sqs-lambda-tenant-isolation-sam-py/example-pattern.json new file mode 100644 index 000000000..680761977 --- /dev/null +++ b/sqs-lambda-tenant-isolation-sam-py/example-pattern.json @@ -0,0 +1,49 @@ +{ + "title": "AWS Lambda Tenant Isolation with SQS", + "description": "Lambda Isolation feature", + "language": "", + "level": "200", + "framework": "AWS SAM", + "introBox": { + "headline": "How it works", + "text": [ + "This pattern demonstrate AWS Lambda's tenant isolation feature in Multi-tenant application." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/sqs-lambda-tenant-isolation-sam-py", + "templateURL": "serverless-patterns/sqs-lambda-tenant-isolation-sam-py", + "projectFolder": "sqs-lambda-tenant-isolation-sam-py" + } + }, + "resources": { + "bullets": [{ + "text": "AWS Lambda tenant isolation", + "link": "https://docs.aws.amazon.com/lambda/latest/dg/tenant-isolation.html" + } + ] + }, + "deploy": { + "text": ["sam build", "sam deploy --guided"] + }, + "testing": { + "text": ["See the GitHub repo for detailed testing instructions."] + }, + "cleanup": { + "text": ["Delete the stack: sam delete."] + }, + "authors": [{ + "name": "Mitesh Purohit", + "image": "", + "bio": "Sr Solution Architect, AWS", + "linkedin": "https://www.linkedin.com/in/mitup/" + }, + { + "name": "Ricardo Marques", + "image": "", + "bio": "Sr Serverless Specialist, AWS", + "linkedin": "https://www.linkedin.com/in/ricardo-marques-aws/" + } +] +} \ No newline at end of file diff --git a/sqs-lambda-tenant-isolation-sam-py/sqs-processor/index.py b/sqs-lambda-tenant-isolation-sam-py/sqs-processor/index.py new file mode 100644 index 000000000..fca8a902d --- /dev/null +++ b/sqs-lambda-tenant-isolation-sam-py/sqs-processor/index.py @@ -0,0 +1,29 @@ +import json +import boto3 +import os + +lambda_client = boto3.client('lambda') +TENANT_ISOLATED_FUNCTION = os.environ['TENANT_ISOLATED_FUNCTION_NAME'] + +def handler(event, context): + for record in event['Records']: + body = json.loads(record['body']) + + # Get message group ID from SQS attributes + attributes = record.get('attributes') or {} + message_group_id = attributes.get('MessageGroupId') + + if not message_group_id: + print(f"Missing MessageGroupId in SQS record: {record}") + message_group_id = "default" + + lambda_client.invoke( + FunctionName=TENANT_ISOLATED_FUNCTION, + InvocationType='Event', + Payload=json.dumps(body), + TenantId=message_group_id + ) + + print(f"Invoked tenant-isolated function for messagegroup: {message_group_id}") + + return {'statusCode': 200} diff --git a/sqs-lambda-tenant-isolation-sam-py/sqs-processor/requirements.txt b/sqs-lambda-tenant-isolation-sam-py/sqs-processor/requirements.txt new file mode 100644 index 000000000..f6315cd5c --- /dev/null +++ b/sqs-lambda-tenant-isolation-sam-py/sqs-processor/requirements.txt @@ -0,0 +1 @@ +boto3>=1.26.0 diff --git a/sqs-lambda-tenant-isolation-sam-py/template.yml b/sqs-lambda-tenant-isolation-sam-py/template.yml new file mode 100644 index 000000000..080cfa065 --- /dev/null +++ b/sqs-lambda-tenant-isolation-sam-py/template.yml @@ -0,0 +1,69 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: Lambda Tenant Isolation Demo + +Resources: + ProcessingQueue: + Type: AWS::SQS::Queue + Properties: + QueueName: tenant-isolation-queue + VisibilityTimeout: 300 + + TenantIsolatedFunctionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: lambda.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + + TenantIsolatedFunction: + Type: AWS::Serverless::Function + Properties: + FunctionName: tenant-isolated-processor + CodeUri: tenant-isolated-processor/ + Handler: index.handler + Runtime: python3.12 + Timeout: 120 + Role: !GetAtt TenantIsolatedFunctionRole.Arn + TenancyConfig: + TenantIsolationMode: PER_TENANT + + SQSProcessorFunction: + Type: AWS::Serverless::Function + Properties: + FunctionName: sqs-processor + CodeUri: sqs-processor/ + Handler: index.handler + Runtime: python3.12 + Timeout: 60 + Environment: + Variables: + TENANT_ISOLATED_FUNCTION_NAME: !Ref TenantIsolatedFunction + Policies: + - Statement: + - Effect: Allow + Action: + - lambda:InvokeFunction + Resource: !GetAtt TenantIsolatedFunction.Arn + Events: + SQSEvent: + Type: SQS + Properties: + Queue: !GetAtt ProcessingQueue.Arn + BatchSize: 10 + +Outputs: + QueueUrl: + Value: !Ref ProcessingQueue + QueueArn: + Value: !GetAtt ProcessingQueue.Arn + TenantIsolatedFunctionArn: + Value: !GetAtt TenantIsolatedFunction.Arn + SQSProcessorFunctionArn: + Value: !GetAtt SQSProcessorFunction.Arn diff --git a/sqs-lambda-tenant-isolation-sam-py/tenant-isolated-processor/index.py b/sqs-lambda-tenant-isolation-sam-py/tenant-isolated-processor/index.py new file mode 100644 index 000000000..d5432f88e --- /dev/null +++ b/sqs-lambda-tenant-isolation-sam-py/tenant-isolated-processor/index.py @@ -0,0 +1,16 @@ +import json + +def handler(event, context): + tenant_id = context.tenant_id + + print(f"Processing request for tenant: {tenant_id}") + print(f"Event data: {json.dumps(event)}") + + # Process tenant-specific logic here + result = { + 'tenant_id': tenant_id, + 'message': 'Request processed successfully', + 'data': event + } + + return result diff --git a/sqs-lambda-tenant-isolation-sam-py/tenant-isolated-processor/requirements.txt b/sqs-lambda-tenant-isolation-sam-py/tenant-isolated-processor/requirements.txt new file mode 100644 index 000000000..d2ce485de --- /dev/null +++ b/sqs-lambda-tenant-isolation-sam-py/tenant-isolated-processor/requirements.txt @@ -0,0 +1 @@ +# No external dependencies required