Distribute S3 content through CloudFront
- private S3 bucket
- cloudfront: readonly
- cross account role: read-write
- local role: read-write
- ref : https://dev.classmethod.jp/cloud/aws/basic-auth-s3-cloudfront-lambda/
Parameters:
Environment:
Type: String
Description: Application Environment
SecurityLambdaArn:
Type: String
Description: ARN for Lambda @ Edge for cloudfront
Resources:
CloudFrontOriginAccessIdentity:
Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
Properties:
CloudFrontOriginAccessIdentityConfig:
Comment: !Ref 'AWS::StackName'
S3LogBucket:
Type: AWS::S3::Bucket
Properties:
AccessControl: LogDeliveryWrite
BucketName: !Sub '${AWS::StackName}-${AWS::Region}-${AWS::AccountId}-${Environment}-log'
LifecycleConfiguration:
Rules:
- Status: Enabled
ExpirationInDays: 7 # days
S3Bucket:
Type: AWS::S3::Bucket
DeletionPolicy: Retain
Properties:
VersioningConfiguration:
Status: Enabled
AccessControl: Private # Restricted access
BucketName: !Sub '${AWS::StackName}-${AWS::Region}-${AWS::AccountId}-${Environment}'
LoggingConfiguration:
DestinationBucketName: !Ref 'S3LogBucket'
LogFilePrefix: !Sub: '${AWS::StackName}-${AWS::Region}-${AWS::AccountId}-${Environment}/' # logical folder to keep log per env
# Restrict Access to S3 bucket
S3BucketAccessPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref 'S3Bucket'
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: CloudFrontAccess
Effect: Allow
Action: s3:GetObject
Resource: !Sub: 'arn:aws:s3:::${S3Bucket}/*'
Principal:
AWS: !Sub 'arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${CloudFrontOriginAccessIdentity}'
- Sid: LocalCICDAdminAccess
Effect: Allow
Action:
- s3:ListBucket
- s3:GetObject
- s3:PutObject
- s3:PutObjectAcl
Resource:
- "Fn::Sub": arn:aws:s3:::${S3Bucket}
- "Fn::Sub": arn:aws:s3:::${S3Bucket}/*
Principal:
AWS:
Fn::Sub: arn:aws:iam::${AWS::AccountId}:role/cicdAdmin # role "cicdAdmin" under current account
- Sid: RemoteCICDAccess
Effect: Allow
Action:
- s3:ListBucket
- s3:GetObject
Resource:
- "Fn::Sub": arn:aws:s3:::${S3Bucket}
- "Fn::Sub": arn:aws:s3:::${S3Bucket}/*
Principal:
AWS:
Fn::Sub: arn:aws:iam::666666666666:role/cicd # role "cicd" under another account
# Publish S3 content in CDN
S3BucketCloudFrontLog:
Type: AWS::S3::Bucket
DeletionPolicy: Retain
Properties:
BucketName: !Sub 'cloudfrontlog-${AWS::StackName}-${AWS::Region}-${AWS::AccountId}-${Environment}'
LifecycleConfiguration:
Rules:
- Id: AutoDelete
Status: Enabled
ExpirationInDays: 15
CloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Comment: !Sub '${AWS::StackName} distribution'
Aliases: my.domain.com # custom domain name
ViewerCertificate:
SslSupportMethod: sni-only
MinimumProtocolVersion: TLSv1.1_2016
AcmCertificateArn: arn:aws:acm:us-east-1:5555555555:certificate/key-id-from-KMS-AWS-managed-key # Certs used by SSL
CustomErrorResponses:
- ErrorCode: 403 # S3 bucket's "no such object"
ResponseCode: 200
ResponsePagePath: /index.html
DefaultCacheBehavior:
ForwardedValues:
Cookies:
Forward: none
QueryString: false
TargetOriginId: latest
ViewerProtocolPolicy: https-only
LambdaFunctionAssociations:
- EventType: origin-response # process HTTP response from S3 before returning back to client, see https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_LambdaFunctionAssociation.html
LambdaFunctionARN: !Sub '${SecurityLambdaArn}' # Edge lambda, e.g. used to modify response header
DefaultRootObject: /index.html
Enabled: true
HttpVersion: http2
IPV6Enabled: true
Logging:
Bucket: !GetAtt 'S3BucketCloudFrontLog.DomainName'
Prefix: !Sub '${AWS::StackName}-CloudFront-${Environment}'
Origins:
- Id: latest
DomainName:
Fn::GetAtt: [S3Bucket, DomainName]
OriginPath:
Fn::Sub: /blog # http:/s3_bucket/blog
S3OriginConfig:
# cloudFrontOriginAccessIdentity binds cloud and S3, so to ensure security
OriginAccessIdentity: !Sub 'origin-access-identity/cloudfront/${CloudFrontOriginAccessIdentity}'
WebACLId:
Ref: WebACL
Tags: # tags are not assigned automatically to this resource
- Key: a-tag-name
Value: a-tag-value
WebACL:
Type: AWS::WAF::WebACL
Properties:
Name:
Fn::Sub: WAF ACL for ${Environment} CloudFront Distribution
DefaultAction:
Type: ALLOW
MetricName:
Fn::Sub: ${Environment}CloudFrontWafAcl
Rules:
- Priority: 10
Action:
Type: BLOCK
RuleId:
Ref: WAFRule
WAFRule:
Type: AWS::WAF::Rule
Properties:
Name:
Fn::Sub: JS Injection WAF Rule for ${Environment} CloudFront Distribution
MetricName:
Fn::Sub: ${Environment}CloudFrontWafXss
Predicates:
- Type: XssMatch
Negated: false
DataId:
Ref: WAFXssMatchSet
WAFXssMatchSet:
Type: AWS::WAF::XssMatchSet
Properties:
Name:
Fn::Sub: XssMatchSet for ${Environment} CloudFront Distribution
XssMatchTuples:
- FieldToMatch:
Type: QUERY_STRING
TextTransformation: URL_DECODE
💵