-
Notifications
You must be signed in to change notification settings - Fork 947
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 #2468 from samudurand/cloudfront-lambda-url-iam-cd…
…k-ts New serverless pattern - cloudfront-lambda-url-iam-cdk-ts
- Loading branch information
Showing
13 changed files
with
532 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,64 @@ | ||
# CloudFront to Lambda URL | ||
|
||
This pattern demonstrates how to setup Amazon CloudFront to proxy and cache traffic to AWS Lambda Function URLs, secured via IAM and Lambda@Edge. | ||
|
||
A function URL is a dedicated endpoint for your Lambda function. When you create a function URL, Lambda automatically generates a unique URL endpoint for you. Unlike API Gateway endpoints, using this URL does not incur any additional charges, beyond the usual data transfer costs. | ||
|
||
By configuring CloudFront in front of the Lambda Function URL endpoint you can use custom domain names, Cognito authentication via Lambda@Edge, AWS Web Application Firewall (WAF) and AWS Shield Advanced to protect your endpoint from attacks. | ||
|
||
Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/cloudfront-lambda-url-iam-cdk-ts. | ||
|
||
Note: the Lambda@Edge uses pure Javascript instead of Typescript due to the limitations of the [Lambda@Edge CDK construct](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_cloudfront.experimental.EdgeFunction.html), which does not offer a native Typescript packaging option. | ||
|
||
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 with your account's credentials | ||
* [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) installed | ||
* [NodeJS](https://nodejs.org/en/download) installed | ||
|
||
## Deployment Instructions | ||
|
||
1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository: | ||
```bash | ||
git clone https://github.com/aws-samples/serverless-patterns | ||
``` | ||
1. Modify the `cdk/bin/cdk.ts` and `cdk/lib/lambda-edge/authEdge.js` with your prefered region (the default is `eu-central-1`) | ||
1. Change directory to the pattern directory: | ||
```bash | ||
cd cloudfront-lambda-url-iam-cdk-ts/cdk | ||
``` | ||
1. From the command line, install the Node.js dependencies, bootstrap CDK in your AWS account, and finally deploy the pattern: | ||
```bash | ||
npm i | ||
npx cdk bootstrap | ||
npx cdk deploy CdkStack | ||
``` | ||
1. Note the outputs from the CDK deployment. The `CdkStack.CloudFrontDistributionURL` contains the URL of the cloudfront distribution that you can use to test the deployment. | ||
|
||
## How it works | ||
|
||
An Amazon CloudFront distribution is created that forwards requests to the domain name of the deployed AWS Lambda function URL. Amazon CloudFront also caches responses from the Lambda function. The Lambda URL is protected via IAM and can only be called via the CloudFront distribution which includes a Lambda@Edge adding the necessary IAM credentials. | ||
|
||
## Testing | ||
|
||
Copy the url of the CloudFront distribution that you can find in the `cdk deploy` command output, called `CdkStack.CloudFrontDistributionURL`. Paste this URL in a browser and you will get a JSON response. | ||
|
||
```bash | ||
{"message":"Hello, world!"} | ||
``` | ||
|
||
## Cleanup | ||
|
||
Delete all deployed resources | ||
|
||
```bash | ||
npx cdk destroy | ||
``` | ||
|
||
---- | ||
Copyright 2023 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,9 @@ | ||
*.js | ||
!lambda-edge/*.js | ||
!jest.config.js | ||
*.d.ts | ||
node_modules | ||
|
||
# CDK asset staging directory | ||
.cdk.staging | ||
cdk.out |
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,6 @@ | ||
*.ts | ||
!*.d.ts | ||
|
||
# CDK asset staging directory | ||
.cdk.staging | ||
cdk.out |
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,11 @@ | ||
#!/usr/bin/env node | ||
import 'source-map-support/register'; | ||
import * as cdk from 'aws-cdk-lib'; | ||
import { CdkStack } from '../lib/cdk-stack'; | ||
|
||
const app = new cdk.App(); | ||
new CdkStack(app, 'CdkStack', { | ||
env: { | ||
region: 'eu-central-1' // Modify to fit your own region | ||
} | ||
}); |
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,60 @@ | ||
{ | ||
"app": "npx ts-node --prefer-ts-exts bin/cdk.ts", | ||
"watch": { | ||
"include": [ | ||
"**" | ||
], | ||
"exclude": [ | ||
"README.md", | ||
"cdk*.json", | ||
"**/*.d.ts", | ||
"**/*.js", | ||
"tsconfig.json", | ||
"package*.json", | ||
"yarn.lock", | ||
"node_modules", | ||
"test" | ||
] | ||
}, | ||
"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-iam:standardizedServicePrincipals": 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 | ||
} | ||
} |
65 changes: 65 additions & 0 deletions
65
cloudfront-lambda-url-iam-cdk-ts/cdk/lambda-edge/authEdge.js
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,65 @@ | ||
const axios = require('axios'); | ||
const { SignatureV4 } = require('@aws-sdk/signature-v4'); | ||
const { Sha256 } = require('@aws-crypto/sha256-js'); | ||
|
||
const { | ||
AWS_ACCESS_KEY_ID, | ||
AWS_SECRET_ACCESS_KEY, | ||
AWS_SESSION_TOKEN, | ||
} = process.env; | ||
|
||
const sigv4 = new SignatureV4({ | ||
service: 'lambda', | ||
region: 'eu-central-1', // Modify to fit your own region | ||
credentials: { | ||
accessKeyId: AWS_ACCESS_KEY_ID, | ||
secretAccessKey: AWS_SECRET_ACCESS_KEY, | ||
sessionToken: AWS_SESSION_TOKEN, | ||
}, | ||
sha256: Sha256, | ||
}); | ||
|
||
module.exports.handler = async (event) => { | ||
const cfRequest = event.Records[0].cf.request; | ||
const headers = cfRequest.headers; | ||
|
||
const apiUrl = new URL(`https://${cfRequest.origin.custom.domainName}${cfRequest.uri}`); | ||
|
||
const signV4Options = { | ||
method: cfRequest.method, | ||
hostname: apiUrl.host, | ||
path: apiUrl.pathname + (cfRequest.querystring ? `?${cfRequest.querystring}` : ''), | ||
protocol: apiUrl.protocol, | ||
query: cfRequest.querystring, | ||
headers: { | ||
host: apiUrl.hostname | ||
}, | ||
}; | ||
|
||
try { | ||
return await signAndForwardRequest(signV4Options, apiUrl); | ||
} catch (error) { | ||
console.error('An error occurred', error); | ||
return { | ||
status: '500', | ||
statusDescription: 'Internal Server Error', | ||
body: 'Internal Server Error', | ||
}; | ||
} | ||
}; | ||
|
||
async function signAndForwardRequest(signV4Options, apiUrl) { | ||
const signed = await sigv4.sign(signV4Options); | ||
const result = await axios({ | ||
...signed, | ||
url: apiUrl.href, | ||
timeout: 5000 | ||
}); | ||
|
||
return { | ||
status: '200', | ||
statusDescription: 'OK', | ||
body: JSON.stringify(result.data), | ||
}; | ||
} | ||
|
15 changes: 15 additions & 0 deletions
15
cloudfront-lambda-url-iam-cdk-ts/cdk/lambda-edge/package.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,15 @@ | ||
{ | ||
"name": "auth-lambda-edge", | ||
"version": "0.1.0", | ||
"devDependencies": { | ||
"@types/aws-lambda": "^8.10.119", | ||
"aws-sdk": "^2.1452.0" | ||
}, | ||
"dependencies": { | ||
"@aws-crypto/sha256-js": "^5.2.0", | ||
"@aws-sdk/credential-providers": "^3.624.0", | ||
"@aws-sdk/protocol-http": "^3.374.0", | ||
"@aws-sdk/signature-v4": "^3.374.0", | ||
"axios": "^1.7.3" | ||
} | ||
} |
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,6 @@ | ||
export const handler = async () => { | ||
return { | ||
statusCode: 200, | ||
body: JSON.stringify({ message: 'Hello, world!' }), | ||
}; | ||
}; |
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,96 @@ | ||
import * as cdk from 'aws-cdk-lib'; | ||
import { Construct } from 'constructs'; | ||
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; | ||
import * as lambda from 'aws-cdk-lib/aws-lambda'; | ||
import * as lambdaNode from 'aws-cdk-lib/aws-lambda-nodejs'; | ||
import path = require('path'); | ||
import { Effect, PolicyStatement } from 'aws-cdk-lib/aws-iam'; | ||
|
||
export class CdkStack extends cdk.Stack { | ||
constructor(scope: Construct, id: string, props?: cdk.StackProps) { | ||
super(scope, id, props); | ||
|
||
// Create the Lambda | ||
const simpleLambda = new lambdaNode.NodejsFunction(this, 'simpleLambda', { | ||
entry: 'lambda/handler.ts', | ||
handler: 'handler', | ||
runtime: lambda.Runtime.NODEJS_18_X, | ||
functionName: 'simpleLambda' | ||
}); | ||
|
||
const authFunction = this.createAuthEdgeFunction(simpleLambda.functionArn); | ||
|
||
// Configure the Lambda URL | ||
const lambdaUrl = simpleLambda.addFunctionUrl({ | ||
authType: lambda.FunctionUrlAuthType.AWS_IAM, | ||
}); | ||
|
||
// Create the CloudFront distribution redirecting calls to the Lambda URL | ||
const cfDistribution = new cloudfront.CloudFrontWebDistribution(this, 'CFDistribution', { | ||
originConfigs: [ | ||
{ | ||
customOriginSource: { | ||
domainName: this.getURLDomain(lambdaUrl), | ||
originProtocolPolicy: cloudfront.OriginProtocolPolicy.HTTPS_ONLY, | ||
}, | ||
behaviors: [{ | ||
isDefaultBehavior: true, | ||
allowedMethods: cloudfront.CloudFrontAllowedMethods.ALL, | ||
lambdaFunctionAssociations: [{ | ||
eventType: cloudfront.LambdaEdgeEventType.ORIGIN_REQUEST, | ||
lambdaFunction: authFunction.currentVersion, | ||
includeBody: true | ||
}], | ||
}], | ||
} | ||
], | ||
}); | ||
|
||
new cdk.CfnOutput(this, 'CloudFrontDistributionURL', { | ||
value: cfDistribution.distributionDomainName, | ||
}); | ||
|
||
} | ||
|
||
/** | ||
* Extracts the domain from a Lambda URL | ||
* | ||
* Example: https://my-lambda.execute-api.us-east-1.amazonaws.com/ -> my-lambda.execute-api.us-east-1.amazonaws.com | ||
*/ | ||
getURLDomain(lambdaUrl: lambda.FunctionUrl) { | ||
return cdk.Fn.select(2, cdk.Fn.split('/', lambdaUrl.url)); | ||
} | ||
|
||
private createAuthEdgeFunction(functionArn: string) { | ||
const authFunction = new cloudfront.experimental.EdgeFunction(this, 'AuthLambdaEdge', { | ||
handler: 'authEdge.handler', | ||
runtime: lambda.Runtime.NODEJS_16_X, | ||
code: lambda.Code.fromAsset(path.join(__dirname, '../lambda-edge'), { | ||
bundling: { | ||
command: [ | ||
"bash", | ||
"-c", | ||
"npm install && cp -rT /asset-input/ /asset-output/", | ||
], | ||
image: lambda.Runtime.NODEJS_16_X.bundlingImage, | ||
user: "root", | ||
}, | ||
}), | ||
currentVersionOptions: { | ||
removalPolicy: cdk.RemovalPolicy.DESTROY | ||
}, | ||
timeout: cdk.Duration.seconds(7), | ||
}); | ||
|
||
authFunction.addToRolePolicy(new PolicyStatement({ | ||
sid: 'AllowInvokeFunctionUrl', | ||
effect: Effect.ALLOW, | ||
actions: ['lambda:InvokeFunctionUrl'], | ||
resources: [functionArn], | ||
conditions: { | ||
"StringEquals": { "lambda:FunctionUrlAuthType": "AWS_IAM" } | ||
} | ||
})); | ||
return authFunction; | ||
} | ||
} |
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,26 @@ | ||
{ | ||
"name": "cdk", | ||
"version": "0.1.0", | ||
"bin": { | ||
"cdk": "bin/cdk.js" | ||
}, | ||
"scripts": { | ||
"build": "tsc", | ||
"watch": "tsc -w", | ||
"cdk": "cdk", | ||
"postinstall": "cd lambda-edge && npm i" | ||
}, | ||
"devDependencies": { | ||
"@types/jest": "^29.5.4", | ||
"@types/node": "20.5.9", | ||
"jest": "^29.6.4", | ||
"aws-cdk": "2.96.2", | ||
"ts-node": "^10.9.1", | ||
"typescript": "~5.2.2" | ||
}, | ||
"dependencies": { | ||
"aws-cdk-lib": "2.96.2", | ||
"constructs": "^10.0.0", | ||
"source-map-support": "^0.5.21" | ||
} | ||
} |
Oops, something went wrong.