-
Notifications
You must be signed in to change notification settings - Fork 953
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New Serverless Land Pattern waf-cloudfront-websocketapi-serverless #2560
Merged
julianwood
merged 4 commits into
aws-samples:main
from
kothsidh:kothsidh-feature-waf-cloudfront-websocket-apikey-serverless
Jan 27, 2025
Merged
Changes from 1 commit
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
8c2af17
Initial Commit for Websocket API pattern
kothsidh 79fc569
Create waf-cloudfront-websocket-apigw-cdk-python.json
parikhudit 1950a6e
Updates Readme.md with Serverless Pattern reference and requestflow d…
kothsidh a819704
Updates the git clone url with actual values
kothsidh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
# Protecting WebSocket API with CloudFront and WAF Integration | ||
|
||
This pattern implements a secure WebSocket API using AWS CDK, integrating CloudFront for distribution and WAF for protection through AWS CDK with Python. It makes use of API keys to ensure that the Websocket endpoint can only be accessed via the CloudFront distribution by passing the API key as custom header from CloudFront. | ||
|
||
The WebSocket API provides real-time communication capabilities, while CloudFront ensures low-latency content delivery. The Web Application Firewall (WAF) adds an extra layer of security by protecting against common web exploits and controlling access based on configurable rules. | ||
|
||
![Alt text](images/architecturediagram.png?raw=true "Architecture Diagram for WebSocket API with CloudFront and WAF Integration") | ||
|
||
### Prerequisites | ||
|
||
- Python 3.9 or later | ||
- AWS CDK CLI | ||
- AWS CLI configured with appropriate credentials | ||
|
||
***Please note that AWS WAF is available globally for CloudFront distributions. So you must use the Region us-east-1 region while deploying the stack (N. Virginia)*** | ||
|
||
### Installation | ||
|
||
1. Clone the repository: | ||
``` | ||
git clone <repository-url> | ||
cd <repository-directory> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you please put actual URL and path? |
||
``` | ||
|
||
2. Create and activate a virtual environment: | ||
``` | ||
python -m venv .venv | ||
source .venv/bin/activate | ||
``` | ||
|
||
3. Install dependencies: | ||
``` | ||
python -m pip install -r requirements.txt | ||
``` | ||
|
||
|
||
4. Deploy the stack: | ||
``` | ||
cdk bootstrap | ||
cdk deploy | ||
``` | ||
|
||
|
||
### Configuration | ||
|
||
The main configuration for the WebSocket API and related services is defined in `my_websocket_api/my_websocket_api_stack.py`. Key configurations include: | ||
|
||
- WebSocket API settings | ||
- Lambda function for handling WebSocket events | ||
- CloudFront distribution settings | ||
- WAF Web ACL rules | ||
|
||
|
||
|
||
### Troubleshooting | ||
|
||
1. Connection Issues: | ||
- Ensure you're using the correct CloudFront URL with the "wss://" protocol. | ||
- Verify that the API key is correctly set in the CloudFront distribution's custom headers. | ||
|
||
2. Lambda Function Errors: | ||
- Check CloudWatch Logs for the Lambda function to see detailed error messages. | ||
- Ensure the Lambda function has the necessary permissions to execute and access required resources. | ||
|
||
3. WAF Blocking Requests: | ||
- Review the WAF rules in the AWS Console to ensure they're not unintentionally blocking legitimate traffic. | ||
- Check the WAF logs in CloudWatch for details on blocked requests. | ||
|
||
|
||
|
||
## Data Flow | ||
|
||
The WebSocket API handles data flow as follows: | ||
|
||
1. Client initiates a WebSocket connection to the CloudFront distribution URL. | ||
2. CloudFront forwards the request to the API Gateway WebSocket API with the "x-api-key" as custom header. | ||
3. Websocket API validates the API key and routes the request based on the route selection expression. | ||
4. The Lambda function is invoked to handle the WebSocket event. | ||
|
||
``` | ||
[Client] <-> [CloudFront] <-> [API Gateway WebSocket API] <-> [Lambda Function] | ||
^ | | ||
| | | ||
+---------------------------------------------------------------+ | ||
``` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Totally optional but if possible I'd recommend to add a flow diagram for explanation |
||
|
||
##Testing | ||
|
||
Copy the "DistributionURL" value from the Cloudformation Stack's output section. Use it to connect to your Webosocket API. When you connect to your API, API Gateway invokes the $connect route. | ||
``` | ||
wscat -c <DistributionURL> | ||
``` | ||
|
||
Alternatively, you may also use Postman's Websocket Client to test the connection: | ||
|
||
![Alt text](images/PostmanScreenshot.png?raw=true "Postman Screenshot for Websocket Connection via CloudFront URL") | ||
|
||
After the connection is successful, you may verify the AWS Lambda execution logs to validate the messages that were sent. | ||
|
||
|
||
## Cleanup | ||
|
||
1. Delete the stack | ||
```bash | ||
cdk destroy STACK-NAME | ||
``` | ||
1. Confirm the stack has been deleted | ||
```bash | ||
cdk list | ||
``` | ||
---- | ||
Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
|
||
SPDX-License-Identifier: MIT-0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
#!/usr/bin/env python3 | ||
import os | ||
|
||
import aws_cdk as cdk | ||
|
||
from my_websocket_api.my_websocket_api_stack import MyWebsocketApiStack | ||
|
||
|
||
app = cdk.App() | ||
MyWebsocketApiStack(app, "MyWebsocketApiStack", | ||
# If you don't specify 'env', this stack will be environment-agnostic. | ||
# Account/Region-dependent features and context lookups will not work, | ||
# but a single synthesized template can be deployed anywhere. | ||
|
||
# Uncomment the next line to specialize this stack for the AWS Account | ||
# and Region that are implied by the current CLI configuration. | ||
|
||
#env=cdk.Environment(account=os.getenv('CDK_DEFAULT_ACCOUNT'), region=os.getenv('CDK_DEFAULT_REGION')), | ||
|
||
# Uncomment the next line if you know exactly what Account and Region you | ||
# want to deploy the stack to. */ | ||
|
||
#env=cdk.Environment(account='123456789012', region='us-east-1'), | ||
|
||
# For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html | ||
) | ||
|
||
app.synth() | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
{ | ||
"app": "python3 app.py", | ||
"watch": { | ||
"include": [ | ||
"**" | ||
], | ||
"exclude": [ | ||
"README.md", | ||
"cdk*.json", | ||
"requirements*.txt", | ||
"source.bat", | ||
"**/__init__.py", | ||
"**/__pycache__" | ||
] | ||
}, | ||
"context": { | ||
"@aws-cdk/aws-lambda:recognizeLayerVersion": true, | ||
"@aws-cdk/core:checkSecretUsage": true, | ||
"@aws-cdk/core:target-partitions": [ | ||
"aws", | ||
"aws-cn" | ||
], | ||
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, | ||
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, | ||
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, | ||
"@aws-cdk/aws-iam:minimizePolicies": true, | ||
"@aws-cdk/core:validateSnapshotRemovalPolicy": true, | ||
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, | ||
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, | ||
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, | ||
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true, | ||
"@aws-cdk/core:enablePartitionLiterals": true, | ||
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, | ||
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, | ||
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, | ||
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, | ||
"@aws-cdk/aws-route53-patters:useCertificate": true, | ||
"@aws-cdk/customresources:installLatestAwsSdkDefault": false, | ||
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, | ||
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, | ||
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, | ||
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, | ||
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, | ||
"@aws-cdk/aws-redshift:columnId": true, | ||
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, | ||
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, | ||
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, | ||
"@aws-cdk/aws-kms:aliasNameRef": true, | ||
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, | ||
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true, | ||
"@aws-cdk/aws-efs:denyAnonymousAccess": true, | ||
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, | ||
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, | ||
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, | ||
"@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, | ||
"@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, | ||
"@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, | ||
"@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true, | ||
"@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true, | ||
"@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true, | ||
"@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true, | ||
"@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true, | ||
"@aws-cdk/aws-eks:nodegroupNameAttribute": true, | ||
"@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true, | ||
"@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true, | ||
"@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false, | ||
"@aws-cdk/aws-s3:keepNotificationInImportedBucket": false, | ||
"@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true, | ||
"@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": true, | ||
"@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true, | ||
"@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true, | ||
"@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true, | ||
"@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true, | ||
"@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true, | ||
"@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true, | ||
"@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": true | ||
} | ||
} |
56 changes: 56 additions & 0 deletions
56
waf-cloudfront-websocket-apigw-cdk-python/example-pattern.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
{ | ||
"title": "Protecting WebSocket API with CloudFront and WAF Integration", | ||
"description": "Create a WebSocket API integration with CloudFront and WAF using API Keys", | ||
"language": "Python", | ||
"level": "200", | ||
"framework": "CDK", | ||
"introBox": { | ||
"headline": "How it works", | ||
"text": [ | ||
"This pattern implements a secure WebSocket API using AWS CDK, integrating CloudFront for distribution and WAF for protection through AWS CDK with Python. It makes use of API keys to ensure that the Websocket endpoint can only be accessed via the CloudFront distribution by passing the API key as custom header from CloudFront.The WebSocket API provides real-time communication capabilities, while CloudFront ensures low-latency content delivery. The Web Application Firewall (WAF) adds an extra layer of security by protecting against common web exploits and controlling access based on configurable rules." | ||
] | ||
}, | ||
"gitHub": { | ||
"template": { | ||
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/<>", | ||
"templateURL": "serverless-patterns/<>", | ||
"projectFolder": "<>", | ||
"templateFile": "my_websocket_api_stack.py" | ||
} | ||
}, | ||
"resources": { | ||
"bullets": [ | ||
{ | ||
"text": "Protecting your API using Amazon API Gateway and AWS WAF", | ||
"link": "https://aws.amazon.com/blogs/compute/protecting-your-api-using-amazon-api-gateway-and-aws-waf-part-2/" | ||
}, | ||
{ | ||
"text": "Route Configuration for a WebSocket API", | ||
"link": "https://docs.aws.amazon.com/apigateway/latest/developerguide/websocket-api-develop-routes.html#apigateway-websocket-api-routes" | ||
} | ||
] | ||
}, | ||
"deploy": { | ||
"text": [ | ||
"cdk deploy" | ||
] | ||
}, | ||
"testing": { | ||
"text": [ | ||
"See the GitHub repo for detailed testing instructions." | ||
] | ||
}, | ||
"cleanup": { | ||
"text": [ | ||
"cdk destroy" | ||
] | ||
}, | ||
"authors": [ | ||
{ | ||
"name": "Sidharth Kothari", | ||
"image": "https://www.linkedin.com/in/sidharthkothari/", | ||
"bio": "Cloud Engineer @AWS", | ||
"linkedin": "sidharthkothari" | ||
} | ||
] | ||
} |
Binary file added
BIN
+152 KB
waf-cloudfront-websocket-apigw-cdk-python/images/PostmanScreenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+157 KB
waf-cloudfront-websocket-apigw-cdk-python/images/architecturediagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import json | ||
|
||
|
||
def handler(event, context): | ||
print(event) | ||
connection_id = event['requestContext']['connectionId'] | ||
route_key = event['requestContext']['routeKey'] | ||
|
||
# Handle different route types | ||
if route_key == "$connect": | ||
print("Connect Route Triggered, Connection ID:", connection_id) | ||
return handle_connect(connection_id) | ||
elif route_key == "$disconnect": | ||
print("Disconnect Route Triggered, Connection ID:", connection_id) | ||
return handle_disconnect(connection_id) | ||
else: | ||
# Handle other route types or invalid routes | ||
print("Default Route Triggered, Connection ID:", connection_id) | ||
return handle_default(event, connection_id) | ||
|
||
def handle_connect(connection_id): | ||
return { | ||
'statusCode': 200, | ||
'body': f'Connected with ID: {connection_id}' | ||
} | ||
|
||
def handle_disconnect(connection_id): | ||
return { | ||
'statusCode': 200, | ||
'body': f'Disconnected ID: {connection_id}' | ||
} | ||
|
||
def handle_default(event, connection_id): | ||
return { | ||
'statusCode': 200, | ||
'body': json.dumps({ | ||
'message': 'Message received', | ||
'connectionId': connection_id, | ||
'event': event | ||
}) | ||
} |
Empty file.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please refer other PRs to see template for README. It is recommended to put path for to-be serverlessland pattern in description.