-
Notifications
You must be signed in to change notification settings - Fork 951
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2560 from kothsidh/kothsidh-feature-waf-cloudfron…
…t-websocket-apikey-serverless New Serverless Land Pattern waf-cloudfront-websocketapi-serverless
- Loading branch information
Showing
14 changed files
with
669 additions
and
0 deletions.
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") | ||
|
||
|
||
Learn more about this pattern at [Serverless Land Patterns](https://serverlessland.com/patterns/waf-cloudfront-websocket-apigw-cdk-python). | ||
|
||
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. | ||
|
||
### 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 and change directory to pattern directory: | ||
``` | ||
git clone https://github.com/aws-samples/serverless-patterns | ||
cd serverless-patterns/waf-cloudfront-websocket-apigw-cdk-python | ||
``` | ||
|
||
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. WAF validates the request against the configured rules | ||
3. CloudFront forwards the request to the API Gateway WebSocket API with the "x-api-key" as custom header. | ||
4. Websocket API validates the API key and routes the request based on the route selection expression. | ||
|
||
![Alt text](images/RequestFlow.png?raw=true "Request Flow for WebSocket API with CloudFront and WAF Integration") | ||
|
||
## 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/waf-cloudfront-websocket-apigw-cdk-python", | ||
"templateURL": "serverless-patterns/waf-cloudfront-websocket-apigw-cdk-python", | ||
"projectFolder": "waf-cloudfront-websocket-apigw-cdk-python", | ||
"templateFile": "my_websocket_api/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.
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.