Created
July 8, 2021 14:44
-
-
Save daaru00/3260e1f26750525047f574443b473f46 to your computer and use it in GitHub Desktop.
A SAM template that describe an Amazon CloudFront distribution that serve a static website from an S3 Bucket.
This file contains hidden or 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
AWSTemplateFormatVersion: 2010-09-09 | |
Transform: | |
- AWS::Serverless-2016-10-31 | |
# Template Information | |
Description: "Personal Website" | |
# Template Parameters | |
Parameters: | |
DomainName: | |
Type: String | |
Description: "The domain name of website" | |
HostedZoneId: | |
Type: String | |
Description: "The Route53 hosted zone ID used for the domain" | |
AcmCertificateArn: | |
Type: String | |
Description: "The certificate arn for the domain name provided" | |
IndexDocument: | |
Type: String | |
Description: "The index document" | |
Default: "index.html" | |
ErrorDocument: | |
Type: String | |
Description: "The error document, ignored in SPA mode" | |
Default: "404.html" | |
RewriteMode: | |
Type: String | |
Description: "The request rewrite behaviour type" | |
Default: "STATIC" | |
AllowedValues: | |
- STATIC | |
- SPA | |
CloudFrontPriceClass: | |
Type: String | |
Description: "The price class for CloudFront distribution" | |
Default: "PriceClass_100" | |
AllowedValues: | |
- PriceClass_100 | |
- PriceClass_200 | |
- PriceClass_All | |
# Resources create conditions | |
Conditions: | |
IsStaticMode: !Equals [!Ref RewriteMode, "STATIC"] | |
IsSPAMode: !Equals [!Ref RewriteMode, "SPA"] | |
# Template Resources | |
Resources: | |
DnsRecord: | |
Type: AWS::Route53::RecordSet | |
Properties: | |
HostedZoneId: !Ref HostedZoneId | |
Name: !Ref DomainName | |
Type: A | |
AliasTarget: | |
DNSName: !GetAtt Distribution.DomainName | |
HostedZoneId: "Z2FDTNDATAQYW2" # CloudFront | |
Bucket: | |
Type: AWS::S3::Bucket | |
Properties: | |
BucketName: !Ref DomainName | |
AccessControl: Private | |
PublicAccessBlockConfiguration: | |
BlockPublicAcls: true | |
BlockPublicPolicy: true | |
IgnorePublicAcls: true | |
RestrictPublicBuckets: true | |
BucketPolicy: | |
Type: AWS::S3::BucketPolicy | |
Properties: | |
Bucket: !Ref Bucket | |
PolicyDocument: | |
Statement: | |
- Effect: "Allow" | |
Action: "s3:GetObject" | |
Resource: !Sub "arn:aws:s3:::${Bucket}/*" | |
Principal: | |
AWS: !Sub 'arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${OriginAccessIdentity}' | |
OriginAccessIdentity: | |
Type: AWS::CloudFront::CloudFrontOriginAccessIdentity | |
Properties: | |
CloudFrontOriginAccessIdentityConfig: | |
Comment: !Ref AWS::StackName | |
RewriteRequestStaticFunction: | |
Condition: IsStaticMode | |
Type: AWS::CloudFront::Function | |
Properties: | |
Name: !Sub "${AWS::StackName}-req-static" | |
AutoPublish: true | |
FunctionCode: !Sub | | |
function handler(event) { | |
var request = event.request; | |
var uri = request.uri | |
if (uri.endsWith('/')) { | |
request.uri += '${IndexDocument}'; | |
} else if (!uri.includes('.')) { | |
request.uri += '/${IndexDocument}'; | |
} | |
return request; | |
} | |
FunctionConfig: | |
Comment: !Sub "rewrite all paths to /${IndexDocument}" | |
Runtime: cloudfront-js-1.0 | |
RewriteRequestSpaFunction: | |
Condition: IsSPAMode | |
Type: AWS::CloudFront::Function | |
Properties: | |
Name: !Sub "${AWS::StackName}-req-spa" | |
AutoPublish: true | |
FunctionCode: !Sub | | |
function handler(event) { | |
var request = event.request; | |
var uri = request.uri | |
if (uri.includes('.') && !uri.endsWith('.html')) { | |
return request; | |
} | |
request.uri = '/${IndexDocument}'; | |
return request; | |
} | |
FunctionConfig: | |
Comment: !Sub "rewrite sub-directory path with trailing /${IndexDocument}" | |
Runtime: cloudfront-js-1.0 | |
Distribution: | |
Type: AWS::CloudFront::Distribution | |
Properties: | |
DistributionConfig: | |
Enabled: 'true' | |
Comment: !Ref AWS::StackName | |
DefaultRootObject: !Ref IndexDocument | |
HttpVersion: http2 | |
CustomErrorResponses: | |
- ErrorCachingMinTTL: 86400 | |
ErrorCode: 403 # object not found in bucket | |
ResponseCode: !If [IsStaticMode, 404, 200] | |
ResponsePagePath: !If [IsStaticMode, !Sub "/${ErrorDocument}", !Sub "/${IndexDocument}"] | |
Origins: | |
- DomainName: !Sub "${Bucket}.s3.${AWS::Region}.amazonaws.com" | |
Id: bucketOrigin | |
S3OriginConfig: | |
OriginAccessIdentity: !Sub origin-access-identity/cloudfront/${OriginAccessIdentity} | |
DefaultCacheBehavior: | |
Compress: 'true' | |
AllowedMethods: | |
- GET | |
- HEAD | |
- OPTIONS | |
TargetOriginId: bucketOrigin | |
ForwardedValues: | |
QueryString: 'false' | |
Cookies: | |
Forward: none | |
ViewerProtocolPolicy: redirect-to-https | |
FunctionAssociations: | |
- EventType: viewer-request | |
FunctionARN: !If [IsStaticMode, !GetAtt RewriteRequestStaticFunction.FunctionMetadata.FunctionARN, !GetAtt RewriteRequestSpaFunction.FunctionMetadata.FunctionARN] | |
PriceClass: !Ref CloudFrontPriceClass | |
Aliases: | |
- !Ref DomainName | |
ViewerCertificate: | |
AcmCertificateArn: !Ref AcmCertificateArn | |
SslSupportMethod: sni-only | |
# Template Outputs | |
Outputs: | |
BucketName: | |
Description: "The S3 bucket name where HTML files need to be uploaded" | |
Value: !Ref Bucket | |
CloudFrontDistribution: | |
Description: "The CloudFront distribution in front of the S3 bucket" | |
Value: !Ref Distribution | |
WebsiteUrl: | |
Description: "The website URL" | |
Value: !Sub "https://${DomainName}/" | |
That's super nice, thanks!
Thanx! this is very usefull, I was able to adapt it and use it in a matter of half an hour. 2024 and still working perfectly
Thank you!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for that. Could not find a better example wireing the S3 bucket to a Cloudformation via OriginAccessIdentity. Helped a lot.
Cheers