Skip to content

Instantly share code, notes, and snippets.

@vicly
Created December 19, 2018 22:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vicly/48b21943586585c1fc76096727294b74 to your computer and use it in GitHub Desktop.
Save vicly/48b21943586585c1fc76096727294b74 to your computer and use it in GitHub Desktop.
[CloudFront+S3+EdgeLambda] #AWS

Distribute S3 content through CloudFront

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


@BinaryShrub
Copy link

💵

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment