Create a CloudFront Distribution with ALB and S3 as origins using CDK
awsAmazon CloudFront is a CDN service, and it can improve latency and reduce the load on the origin by caching responses on edge servers. The main use case is distributing static files from origins such as S3, but it is also possible to return dynamic responses by placing it in front of ALB etc. In that case, the cache needs to be disabled, so the number of requests to the origin won’t be reduced, but the load will be reduced somewhat as connections can be reused. Besides, the traffic between CloudFront and the origin passes through the AWS network, so you can expect improved latency.
Prices are basically charged based on the number of requests and transfer out to the Internet. For DELETE, OPTIONS, PATCH, POST, and PUT requests, it also applies to the amount transferred to the origin.
If S3 is the origin, you can prevent direct request to the origin by allowing CloudFront to access using OriginAccessIndentity. I’m going to migrate to Origin Access Control (OAC) when L2 construct is implemented.
import * as s3 from 'aws-cdk-lib/aws-s3'
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'
const bucket = new s3.Bucket(this, 'OriginBucket', {
bucketName: '<bucket_name>',
})
const oai = new cloudfront.OriginAccessIdentity(this, 'OriginAccessIdentity');
bucket.grantRead(oai);
If ALB is the origin, you can prevent direct request by specifying CloudFront’s managed prefix list to inbound, and setting Listener’s open field to false so that a rule that opens the port isn’t automatically added. Note that internetFacing must be true to access from CloudFront. “The maximum number of rules per security group has been reached” error may occur due to the large number of CIDRs included in the managed prefix list, in which case you should request increasing the quota.
import * as ec2 from 'aws-cdk-lib/aws-ec2'
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'
import * as elbtargets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'
import * as lambda from 'aws-cdk-lib/aws-lambda'
const originALBSecurityGroup = new ec2.SecurityGroup(this, 'OriginALBSecurityGroup', { vpc });
originALBSecurityGroup.addIngressRule(
// com.amazonaws.global.cloudfront.origin-facing in us-east-1
ec2.Peer.prefixList('pl-3b927c52'),
ec2.Port.tcp(80)
)
const originALB = new elbv2.ApplicationLoadBalancer(this, 'OriginALB', {
vpc,
internetFacing: true,
securityGroup: originALBSecurityGroup,
})
const albLambda = new lambda.Function(this, 'ALBLambda', {
runtime: lambda.Runtime.NODEJS_16_X,
handler: 'index.handler',
code: lambda.Code.fromInline(`
exports.handler = async (event) => {
const response = {
statusCode: 200,
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({
currentTime: new Date().toISOString(),
requestHeaders: event.headers,
}),
}
return response
};
`),
})
originALB.addListener('Listener', {
port: 80,
open: false,
defaultTargetGroups: [
new elbv2.ApplicationTargetGroup(this, 'DefaultTargetGroup', {
targets: [new elbtargets.LambdaTarget(albLambda)],
})
]
})
Distrubtion receives a request of viewerProtocolPolicy protocol from users and makes a request of protocolPolicy protocol to the origin. It can be used as an SSL termination by setting viewerProtocolPolicy to REDIRECT_TO_HTTPS or HTTPS_ONLY and protocolPolicy to HTTP_ONLY, but in that case, the distribution can’t communicate with the origin using HTTP/2.
originRequestPolicy filters headers and query parameters for requests to the origin. If you set ALL_VIEWER_EXCEPT_HOST_HEADER, the Host header will be replaced with the origin’s one. In this case, note that the URL generated from the value of the Host header won’t pass through CloudFront and will be inaccessible if direct requests are prevented.
import * as certificatemanager from 'aws-cdk-lib/aws-certificatemanager'
import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'
const certificate = certificatemanager.Certificate.fromCertificateArn(this, 'Certificate', certificateArn);
const distribution = new cloudfront.Distribution(this, 'Distribution', {
certificate,
domainNames: [domainName],
enableIpv6: true,
defaultBehavior: {
origin: new origins.LoadBalancerV2Origin(lb, { protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY }),
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
cachePolicy: cloudfront.CachePolicy.CACHING_DISABLED,
// All headers in the viewer request except for the Host header
originRequestPolicy: cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER,
},
additionalBehaviors: {
'/static/*': {
origin: new origins.S3Origin(bucket, { originAccessIdentity: oai }),
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
// doesn't include any query strings or cookies in the cache key, and only includes the normalized Accept-Encoding header.
// https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-cache-policies.html#managed-cache-caching-optimized
cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
// Headers included in origin requests: Origin, Access-Control-Request-Headers, Access-Control-Request-Method
// https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-origin-request-policies.html#managed-origin-request-policy-cors-s3
originRequestPolicy: cloudfront.OriginRequestPolicy.CORS_S3_ORIGIN,
},
},
})
When specifying domainNames, the certificate is required, and DNS settings are also needed.
import * as route53 from 'aws-cdk-lib/aws-route53'
const hostedZone = route53.HostedZone.fromLookup(this, 'HostedZone', {
domainName: domainName.split('.').slice(1).join('.'),
})
new route53.ARecord(this, 'AliasRecord', {
zone: hostedZone,
target: route53.RecordTarget.fromAlias(new route53Targets.CloudFrontTarget(distribution)),
recordName: domainName,
})
The same domainName can’t be specified in multiple Distributions, and a non-wildcard Distribution like aaa.sambaiz.net takes precedence over a wildcard like *.sambaiz.net, so it isn’t possible to send some requests for testing at the DNS level. Instead, continuous deployment allows up to 15% of requests to send to a staging distribution.