Skip to content

Commit 830d8d0

Browse files
authored
Merge pull request #2560 from kothsidh/kothsidh-feature-waf-cloudfront-websocket-apikey-serverless
New Serverless Land Pattern waf-cloudfront-websocketapi-serverless
2 parents 5c58adc + a819704 commit 830d8d0

File tree

14 files changed

+669
-0
lines changed

14 files changed

+669
-0
lines changed
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# Protecting WebSocket API with CloudFront and WAF Integration
2+
3+
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.
4+
5+
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.
6+
7+
![Alt text](images/architecturediagram.png?raw=true "Architecture Diagram for WebSocket API with CloudFront and WAF Integration")
8+
9+
10+
Learn more about this pattern at [Serverless Land Patterns](https://serverlessland.com/patterns/waf-cloudfront-websocket-apigw-cdk-python).
11+
12+
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.
13+
14+
### Prerequisites
15+
16+
- Python 3.9 or later
17+
- AWS CDK CLI
18+
- AWS CLI configured with appropriate credentials
19+
20+
***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)***
21+
22+
### Installation
23+
24+
1. Clone the repository and change directory to pattern directory:
25+
```
26+
git clone https://github.com/aws-samples/serverless-patterns
27+
cd serverless-patterns/waf-cloudfront-websocket-apigw-cdk-python
28+
```
29+
30+
2. Create and activate a virtual environment:
31+
```
32+
python -m venv .venv
33+
source .venv/bin/activate
34+
```
35+
36+
3. Install dependencies:
37+
```
38+
python -m pip install -r requirements.txt
39+
```
40+
41+
42+
4. Deploy the stack:
43+
```
44+
cdk bootstrap
45+
cdk deploy
46+
```
47+
48+
49+
### Configuration
50+
51+
The main configuration for the WebSocket API and related services is defined in `my_websocket_api/my_websocket_api_stack.py`. Key configurations include:
52+
53+
- WebSocket API settings
54+
- Lambda function for handling WebSocket events
55+
- CloudFront distribution settings
56+
- WAF Web ACL rules
57+
58+
59+
60+
### Troubleshooting
61+
62+
1. Connection Issues:
63+
- Ensure you're using the correct CloudFront URL with the "wss://" protocol.
64+
- Verify that the API key is correctly set in the CloudFront distribution's custom headers.
65+
66+
2. Lambda Function Errors:
67+
- Check CloudWatch Logs for the Lambda function to see detailed error messages.
68+
- Ensure the Lambda function has the necessary permissions to execute and access required resources.
69+
70+
3. WAF Blocking Requests:
71+
- Review the WAF rules in the AWS Console to ensure they're not unintentionally blocking legitimate traffic.
72+
- Check the WAF logs in CloudWatch for details on blocked requests.
73+
74+
75+
76+
## Data Flow
77+
78+
The WebSocket API handles data flow as follows:
79+
80+
1. Client initiates a WebSocket connection to the CloudFront distribution URL.
81+
2. WAF validates the request against the configured rules
82+
3. CloudFront forwards the request to the API Gateway WebSocket API with the "x-api-key" as custom header.
83+
4. Websocket API validates the API key and routes the request based on the route selection expression.
84+
85+
![Alt text](images/RequestFlow.png?raw=true "Request Flow for WebSocket API with CloudFront and WAF Integration")
86+
87+
## Testing
88+
89+
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.
90+
```
91+
wscat -c <DistributionURL>
92+
```
93+
94+
Alternatively, you may also use Postman's Websocket Client to test the connection:
95+
96+
![Alt text](images/PostmanScreenshot.png?raw=true "Postman Screenshot for Websocket Connection via CloudFront URL")
97+
98+
After the connection is successful, you may verify the AWS Lambda execution logs to validate the messages that were sent.
99+
100+
101+
## Cleanup
102+
103+
1. Delete the stack
104+
```bash
105+
cdk destroy STACK-NAME
106+
```
107+
1. Confirm the stack has been deleted
108+
```bash
109+
cdk list
110+
```
111+
----
112+
Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
113+
114+
SPDX-License-Identifier: MIT-0
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env python3
2+
import os
3+
4+
import aws_cdk as cdk
5+
6+
from my_websocket_api.my_websocket_api_stack import MyWebsocketApiStack
7+
8+
9+
app = cdk.App()
10+
MyWebsocketApiStack(app, "MyWebsocketApiStack",
11+
# If you don't specify 'env', this stack will be environment-agnostic.
12+
# Account/Region-dependent features and context lookups will not work,
13+
# but a single synthesized template can be deployed anywhere.
14+
15+
# Uncomment the next line to specialize this stack for the AWS Account
16+
# and Region that are implied by the current CLI configuration.
17+
18+
#env=cdk.Environment(account=os.getenv('CDK_DEFAULT_ACCOUNT'), region=os.getenv('CDK_DEFAULT_REGION')),
19+
20+
# Uncomment the next line if you know exactly what Account and Region you
21+
# want to deploy the stack to. */
22+
23+
#env=cdk.Environment(account='123456789012', region='us-east-1'),
24+
25+
# For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html
26+
)
27+
28+
app.synth()
29+
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
{
2+
"app": "python3 app.py",
3+
"watch": {
4+
"include": [
5+
"**"
6+
],
7+
"exclude": [
8+
"README.md",
9+
"cdk*.json",
10+
"requirements*.txt",
11+
"source.bat",
12+
"**/__init__.py",
13+
"**/__pycache__"
14+
]
15+
},
16+
"context": {
17+
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
18+
"@aws-cdk/core:checkSecretUsage": true,
19+
"@aws-cdk/core:target-partitions": [
20+
"aws",
21+
"aws-cn"
22+
],
23+
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
24+
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
25+
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
26+
"@aws-cdk/aws-iam:minimizePolicies": true,
27+
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
28+
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
29+
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
30+
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
31+
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
32+
"@aws-cdk/core:enablePartitionLiterals": true,
33+
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
34+
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
35+
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
36+
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
37+
"@aws-cdk/aws-route53-patters:useCertificate": true,
38+
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
39+
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
40+
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
41+
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
42+
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
43+
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
44+
"@aws-cdk/aws-redshift:columnId": true,
45+
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
46+
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
47+
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
48+
"@aws-cdk/aws-kms:aliasNameRef": true,
49+
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
50+
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
51+
"@aws-cdk/aws-efs:denyAnonymousAccess": true,
52+
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
53+
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
54+
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
55+
"@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
56+
"@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
57+
"@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
58+
"@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
59+
"@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
60+
"@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true,
61+
"@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true,
62+
"@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true,
63+
"@aws-cdk/aws-eks:nodegroupNameAttribute": true,
64+
"@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true,
65+
"@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true,
66+
"@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false,
67+
"@aws-cdk/aws-s3:keepNotificationInImportedBucket": false,
68+
"@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true,
69+
"@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": true,
70+
"@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true,
71+
"@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true,
72+
"@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true,
73+
"@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true,
74+
"@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true,
75+
"@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true,
76+
"@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": true
77+
}
78+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
{
2+
"title": "Protecting WebSocket API with CloudFront and WAF Integration",
3+
"description": "Create a WebSocket API integration with CloudFront and WAF using API Keys",
4+
"language": "Python",
5+
"level": "200",
6+
"framework": "CDK",
7+
"introBox": {
8+
"headline": "How it works",
9+
"text": [
10+
"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."
11+
]
12+
},
13+
"gitHub": {
14+
"template": {
15+
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/waf-cloudfront-websocket-apigw-cdk-python",
16+
"templateURL": "serverless-patterns/waf-cloudfront-websocket-apigw-cdk-python",
17+
"projectFolder": "waf-cloudfront-websocket-apigw-cdk-python",
18+
"templateFile": "my_websocket_api/my_websocket_api_stack.py"
19+
}
20+
},
21+
"resources": {
22+
"bullets": [
23+
{
24+
"text": "Protecting your API using Amazon API Gateway and AWS WAF",
25+
"link": "https://aws.amazon.com/blogs/compute/protecting-your-api-using-amazon-api-gateway-and-aws-waf-part-2/"
26+
},
27+
{
28+
"text": "Route Configuration for a WebSocket API",
29+
"link": "https://docs.aws.amazon.com/apigateway/latest/developerguide/websocket-api-develop-routes.html#apigateway-websocket-api-routes"
30+
}
31+
]
32+
},
33+
"deploy": {
34+
"text": [
35+
"cdk deploy"
36+
]
37+
},
38+
"testing": {
39+
"text": [
40+
"See the GitHub repo for detailed testing instructions."
41+
]
42+
},
43+
"cleanup": {
44+
"text": [
45+
"cdk destroy"
46+
]
47+
},
48+
"authors": [
49+
{
50+
"name": "Sidharth Kothari",
51+
"image": "https://www.linkedin.com/in/sidharthkothari/",
52+
"bio": "Cloud Engineer @AWS",
53+
"linkedin": "sidharthkothari"
54+
}
55+
]
56+
}
152 KB
Loading
94.2 KB
Loading
157 KB
Loading
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import json
2+
3+
4+
def handler(event, context):
5+
print(event)
6+
connection_id = event['requestContext']['connectionId']
7+
route_key = event['requestContext']['routeKey']
8+
9+
# Handle different route types
10+
if route_key == "$connect":
11+
print("Connect Route Triggered, Connection ID:", connection_id)
12+
return handle_connect(connection_id)
13+
elif route_key == "$disconnect":
14+
print("Disconnect Route Triggered, Connection ID:", connection_id)
15+
return handle_disconnect(connection_id)
16+
else:
17+
# Handle other route types or invalid routes
18+
print("Default Route Triggered, Connection ID:", connection_id)
19+
return handle_default(event, connection_id)
20+
21+
def handle_connect(connection_id):
22+
return {
23+
'statusCode': 200,
24+
'body': f'Connected with ID: {connection_id}'
25+
}
26+
27+
def handle_disconnect(connection_id):
28+
return {
29+
'statusCode': 200,
30+
'body': f'Disconnected ID: {connection_id}'
31+
}
32+
33+
def handle_default(event, connection_id):
34+
return {
35+
'statusCode': 200,
36+
'body': json.dumps({
37+
'message': 'Message received',
38+
'connectionId': connection_id,
39+
'event': event
40+
})
41+
}

waf-cloudfront-websocket-apigw-cdk-python/my_websocket_api/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)