diff --git a/waf-cloudfront-ai-monetization-cdk/.gitignore b/waf-cloudfront-ai-monetization-cdk/.gitignore new file mode 100644 index 0000000000..46052f7ff4 --- /dev/null +++ b/waf-cloudfront-ai-monetization-cdk/.gitignore @@ -0,0 +1,5 @@ +node_modules +build +cdk.out +cdk.context.json +package-lock.json diff --git a/waf-cloudfront-ai-monetization-cdk/README.md b/waf-cloudfront-ai-monetization-cdk/README.md new file mode 100644 index 0000000000..c595c63d58 --- /dev/null +++ b/waf-cloudfront-ai-monetization-cdk/README.md @@ -0,0 +1,120 @@ +# AWS WAF AI Traffic Monetization with Amazon CloudFront (CDK) + +This pattern deploys an Amazon CloudFront distribution protected by AWS WAF with AI traffic monetization enabled. AI bots that access your content are automatically charged via the x402 payment protocol using USDC stablecoins. + +Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/waf-cloudfront-ai-monetization-cdk + +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. + +## Requirements + +* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources. +* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured +* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) +* [Node.js 20+](https://nodejs.org/en/download/) installed +* [AWS CDK v2](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html) installed and bootstrapped + +## Deployment Instructions + +1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository: + ``` + git clone https://github.com/aws-samples/serverless-patterns + ``` +2. Change directory to the pattern directory: + ``` + cd serverless-patterns/waf-cloudfront-ai-monetization-cdk + ``` +3. Install dependencies: + ``` + npm install + ``` +4. Deploy the stack (must deploy to us-east-1 for Amazon CloudFront): + ``` + cdk deploy + ``` + +## How it works + +This pattern creates: + +1. **Amazon S3 bucket** — stores your content (HTML, APIs, data) that AI bots want to access. +2. **Amazon CloudFront distribution** — serves content globally with HTTPS. +3. **AWS WAF WebACL** (CLOUDFRONT scope) with: + - **Bot Control managed rule group** — uses machine learning to classify bots into categories (AI, scraper, crawler, etc.) + - **MonetizeAction rule** — matches bots labeled `awswaf:managed:aws:bot-control:bot:category:ai` and returns HTTP 402 with an x402 payment manifest + - **MonetizationConfig** — configures the x402 payment network (Base Sepolia testnet by default), wallet address, and USDC pricing + +### Flow + +``` +AI Bot Request + │ + ▼ +CloudFront Distribution + │ + ▼ +AWS WAF Bot Control (classifies bot category) + │ + ├── Human browser → Allow (200 OK) + │ + └── AI Bot (GPTBot, ClaudeBot, etc.) + │ + ▼ + MonetizeAction → HTTP 402 Payment Required + │ (x402 price manifest) + │ + ├── Bot pays USDC → Request replayed → 200 OK + │ + └── Bot doesn't pay → Blocked +``` + +### Test Mode + +The pattern deploys in **TEST mode** using Base Sepolia testnet. No real money is involved. To switch to production: + +1. Replace the wallet address with your production USDC wallet (Base or Solana) +2. Change `CurrencyMode` from `TEST` to `LIVE` +3. Change `Chain` from `BASE_SEPOLIA` to `BASE` (or `SOLANA`) + +## Testing + +1. Upload sample content: + ```bash + echo '
cdk bootstrap",
+ "npm install",
+ "cdk deploy"
+ ]
+ },
+ "testing": {
+ "text": [
+ "Upload sample content to the Amazon S3 bucket:",
+ "aws s3 cp index.html s3://$(aws cloudformation describe-stacks --stack-name WafCloudfrontAiMonetizationStack --query 'Stacks[0].Outputs[?OutputKey==`ContentBucketName`].OutputValue' --output text)/index.html",
+ "Test with a normal browser request (should succeed):",
+ "curl -s https://$(aws cloudformation describe-stacks --stack-name WafCloudfrontAiMonetizationStack --query 'Stacks[0].Outputs[?OutputKey==`DistributionDomainName`].OutputValue' --output text)/index.html",
+ "Test with an AI bot User-Agent (should return HTTP 402 with x402 payment manifest):",
+ "curl -s -D - -H 'User-Agent: GPTBot/1.0' https://$(aws cloudformation describe-stacks --stack-name WafCloudfrontAiMonetizationStack --query 'Stacks[0].Outputs[?OutputKey==`DistributionDomainName`].OutputValue' --output text)/index.html"
+ ]
+ },
+ "cleanup": {
+ "text": [
+ "cdk destroy",
+ "Note: The Amazon S3 bucket has a DESTROY removal policy and will be deleted automatically with all objects."
+ ]
+ },
+ "authors": [
+ {
+ "name": "Nithin Chandran R",
+ "bio": "Technical Account Manager at AWS",
+ "linkedin": "nithin-chandran-r"
+ }
+ ],
+ "patternArch": {
+ "icon1": {
+ "x": 20,
+ "y": 50,
+ "service": "waf",
+ "label": "AWS WAF"
+ },
+ "icon2": {
+ "x": 50,
+ "y": 50,
+ "service": "cloudfront",
+ "label": "Amazon CloudFront"
+ },
+ "icon3": {
+ "x": 80,
+ "y": 50,
+ "service": "s3",
+ "label": "Amazon S3"
+ },
+ "line1": {
+ "from": "icon1",
+ "to": "icon2"
+ },
+ "line2": {
+ "from": "icon2",
+ "to": "icon3"
+ }
+ }
+}
diff --git a/waf-cloudfront-ai-monetization-cdk/lib/waf-cloudfront-ai-monetization-stack.ts b/waf-cloudfront-ai-monetization-cdk/lib/waf-cloudfront-ai-monetization-stack.ts
new file mode 100644
index 0000000000..adb93910dc
--- /dev/null
+++ b/waf-cloudfront-ai-monetization-cdk/lib/waf-cloudfront-ai-monetization-stack.ts
@@ -0,0 +1,122 @@
+import * as cdk from 'aws-cdk-lib';
+import * as s3 from 'aws-cdk-lib/aws-s3';
+import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
+import * as origins from 'aws-cdk-lib/aws-cloudfront-origins';
+import * as wafv2 from 'aws-cdk-lib/aws-wafv2';
+import { Construct } from 'constructs';
+
+export class WafCloudfrontAiMonetizationStack extends cdk.Stack {
+ constructor(scope: Construct, id: string, props?: cdk.StackProps) {
+ super(scope, id, props);
+
+ // Amazon S3 bucket for sample content
+ const contentBucket = new s3.Bucket(this, 'ContentBucket', {
+ removalPolicy: cdk.RemovalPolicy.DESTROY,
+ autoDeleteObjects: true,
+ });
+
+ // WAF WebACL with Bot Control and AI Traffic Monetization
+ const webAcl = new wafv2.CfnWebACL(this, 'AiMonetizationWebAcl', {
+ defaultAction: { allow: {} },
+ scope: 'CLOUDFRONT',
+ visibilityConfig: {
+ cloudWatchMetricsEnabled: true,
+ metricName: 'AiMonetizationWebAcl',
+ sampledRequestsEnabled: true,
+ },
+ rules: [
+ {
+ name: 'AWSManagedRulesBotControlRuleSet',
+ priority: 0,
+ overrideAction: { count: {} },
+ statement: {
+ managedRuleGroupStatement: {
+ vendorName: 'AWS',
+ name: 'AWSManagedRulesBotControlRuleSet',
+ managedRuleGroupConfigs: [
+ {
+ awsManagedRulesBotControlRuleSet: {
+ inspectionLevel: 'TARGETED',
+ enableMachineLearning: true,
+ },
+ },
+ ],
+ },
+ },
+ visibilityConfig: {
+ cloudWatchMetricsEnabled: true,
+ metricName: 'BotControlRuleSet',
+ sampledRequestsEnabled: true,
+ },
+ },
+ {
+ name: 'MonetizeAiBots',
+ priority: 1,
+ action: { count: {} }, // Placeholder — overridden below
+ statement: {
+ labelMatchStatement: {
+ scope: 'LABEL',
+ key: 'awswaf:managed:aws:bot-control:bot:category:ai',
+ },
+ },
+ visibilityConfig: {
+ cloudWatchMetricsEnabled: true,
+ metricName: 'MonetizeAiBots',
+ sampledRequestsEnabled: true,
+ },
+ },
+ ],
+ });
+
+ // Override the MonetizeAiBots rule action with Monetize (not yet in CDK types)
+ webAcl.addPropertyDeletionOverride('Rules.1.Action.Count');
+ webAcl.addPropertyOverride('Rules.1.Action.Monetize', {
+ PriceMultiplier: 10,
+ });
+
+ // Add MonetizationConfig to the WebACL (not yet in CDK types)
+ // Using TEST mode with Base Sepolia testnet — no real money involved
+ webAcl.addPropertyOverride('MonetizationConfig', {
+ CurrencyMode: 'TEST',
+ CryptoConfig: {
+ PaymentNetworks: [
+ {
+ Chain: 'BASE_SEPOLIA',
+ WalletAddress: '0x0000000000000000000000000000000000000000', // Replace with your testnet wallet
+ Prices: [
+ {
+ Amount: '0.001',
+ Currency: 'USDC',
+ },
+ ],
+ },
+ ],
+ },
+ });
+
+ // CloudFront distribution with WAF protection
+ const distribution = new cloudfront.Distribution(this, 'Distribution', {
+ defaultBehavior: {
+ origin: origins.S3BucketOrigin.withOriginAccessControl(contentBucket),
+ viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
+ },
+ webAclId: webAcl.attrArn,
+ });
+
+ // Outputs
+ new cdk.CfnOutput(this, 'DistributionDomainName', {
+ value: distribution.distributionDomainName,
+ description: 'Amazon CloudFront distribution domain name',
+ });
+
+ new cdk.CfnOutput(this, 'ContentBucketName', {
+ value: contentBucket.bucketName,
+ description: 'Amazon S3 bucket for sample content',
+ });
+
+ new cdk.CfnOutput(this, 'WebAclArn', {
+ value: webAcl.attrArn,
+ description: 'AWS WAF WebACL ARN',
+ });
+ }
+}
diff --git a/waf-cloudfront-ai-monetization-cdk/package.json b/waf-cloudfront-ai-monetization-cdk/package.json
new file mode 100644
index 0000000000..3b748c76b1
--- /dev/null
+++ b/waf-cloudfront-ai-monetization-cdk/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "waf-cloudfront-ai-monetization-cdk",
+ "version": "1.0.0",
+ "bin": {
+ "app": "bin/app.ts"
+ },
+ "scripts": {
+ "build": "tsc",
+ "cdk": "cdk"
+ },
+ "dependencies": {
+ "aws-cdk-lib": "^2.260.0",
+ "constructs": "^10.0.0",
+ "source-map-support": "^0.5.21"
+ },
+ "devDependencies": {
+ "@types/node": "^20.0.0",
+ "typescript": "~5.4.0",
+ "aws-cdk": "^2.260.0",
+ "ts-node": "^10.9.0"
+ }
+}
diff --git a/waf-cloudfront-ai-monetization-cdk/tsconfig.json b/waf-cloudfront-ai-monetization-cdk/tsconfig.json
new file mode 100644
index 0000000000..ae328a5827
--- /dev/null
+++ b/waf-cloudfront-ai-monetization-cdk/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "module": "commonjs",
+ "lib": ["es2020"],
+ "declaration": true,
+ "strict": true,
+ "noImplicitAny": true,
+ "strictNullChecks": true,
+ "noImplicitThis": true,
+ "alwaysStrict": true,
+ "outDir": "build",
+ "rootDir": ".",
+ "typeRoots": ["./node_modules/@types"]
+ },
+ "exclude": ["node_modules", "build"]
+}