簡単Webサイトホスティング①ワンクリックで配信基盤を構築するCloudFormationテンプレート

# はじめに

Amazon Web Services（AWS）が提供する、``Amazon CloudFront`` や ``Amazon S3`` と呼ばれるサービスを組み合わせることで、 **HTMLやJavaScript、画像、ビデオなどで構成される静的Webサイトの配信基盤** を[**安価に構築**](https://aws.amazon.com/jp/websites/)することができます。本記事では、リソースのセットアップを自動で行うことのできる、``AWS CloudFormation`` を用いることで、これらの配信基盤を **ミスなく迅速に構築** する手順をご説明します。なお、今回使用する CloudFormation テンプレートは、以下の GitHub リポジトリで公開しています。

+ [AWSCloudFormationTemplates/static-website-hosting-with-ssl](https://github.com/eijikominami/aws-cloudformation-templates/blob/master/static-website-hosting-with-ssl/README_JP.md)

# TL;DR

以下の ``CloudFormation`` テンプレートを実行することで、 **静的Webサイトのホスティング基盤** を **迅速かつお手軽に実現** します。下にあるボタンをクリックすると、自身のAWSアカウント（Asia Pacific Tokyo - ap-northeast-1）で、この ``CloudFormation`` テンプレートを実行することが可能となります。

[![cloudformation-launch-stack](https://raw.githubusercontent.com/eijikominami/aws-cloudformation-templates/master/images/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?region=ap-northeast-1#/stacks/create/review?stackName=StaticWebsiteHosting&templateURL=https://eijikominami.s3-ap-northeast-1.amazonaws.com/aws-cloudformation-templates/static-website-hosting-with-ssl/template.yaml) 

作成されるAWSリソースとそのアーキテクチャ図はこちら。

![](https://raw.githubusercontent.com/eijikominami/aws-cloudformation-templates/master/images/architecture-static-website-hosting.png)

この ``CloudFormation`` テンプレートは、 ``Nested Stack`` の構成となっており、下記のAWSサービスを単体で作成することも可能です。

| 作成されるAWSサービス | 個別のCloudFormationテンプレート | 作成されるサービス |
| --- | --- | --- |
| Synthetics | [![cloudformation-launch-stack](https://raw.githubusercontent.com/eijikominami/aws-cloudformation-templates/master/images/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?region=ap-northeast-1#/stacks/create/review?stackName=Synthetics&templateURL=https://eijikominami.s3-ap-northeast-1.amazonaws.com/aws-cloudformation-templates/synthetics/heartbeat.yaml) | 作成した運用基盤の **外形監視** を行います |
| Real-time Dashboard | [![cloudformation-launch-stack](https://raw.githubusercontent.com/eijikominami/aws-cloudformation-templates/master/images/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?region=ap-northeast-1#/stacks/create/review?stackName=RealtimeLogs&templateURL=https://eijikominami.s3-ap-northeast-1.amazonaws.com/aws-cloudformation-templates/static-website-hosting-with-ssl/realtime-dashboard.yaml) | CloudFront の **アクセスログ** を **リアルタイムに解析** します |
| WAF | [![cloudformation-launch-stack](https://raw.githubusercontent.com/eijikominami/aws-cloudformation-templates/master/images/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?region=ap-northeast-1#/stacks/create/review?stackName=WAF&templateURL=https://s3.amazonaws.com/eijikominami/aws-cloudformation-templates/network/waf.yaml) | 作成した配信基盤に **WAF を導入** します |

なお、 **上記の Nested Stack のリソースの作成は必須ではなく** 、これらが無くても正常に動作します。 **静的Webサイトのホスティングに必要となるのは、 Amazon CloudFront と Amazon S3 のみ** であり、本記事ではこの部分に焦点を当ててご説明します。

![architecture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51599/009ed23c-e2bf-e4c6-799a-8cf796d18024.png)

# アーキテクチャ

このテンプレートの設定内容は以下の通りです。なお、以下のYAMLコードは、[AWSCloudFormationTemplates/static-website-hosting-with-ssl](https://github.com/eijikominami/aws-cloudformation-templates/blob/master/static-website-hosting-with-ssl/) で公開しているCloudFormationテンプレートから、本記事用に一部抜粋したものです。本記事作成用に一部改変を行なっていること、またリポジトリの最新のコードを常に反映している訳ではないことをご了承ください。詳細は、GitHubの当該リポジトリをご覧ください。

+ [README(EN) - aws-cloudformation-templates/static-website-hosting-with-ssl](https://github.com/eijikominami/aws-cloudformation-templates/blob/master/static-website-hosting-with-ssl/README.md)
+ [README(JP) - aws-cloudformation-templates/static-website-hosting-with-ssl](https://github.com/eijikominami/aws-cloudformation-templates/blob/master/static-website-hosting-with-ssl/README_JP.md)

## Amazon S3

Amazon S3は、ユーザがデータを安全にかつ容量無制限に保存可能な **オブジェクトストレージ** で、本アーキテクチャでは、 **オリジンWebサーバ** としての役割を担います。このS3の中に、HTMLやJavaScript、画像、ビデオなどの **静的ファイルが保存** されており、ユーザからのHTTPSリクエストに従って、これらのコンテンツが配信されます。

なお、S3自体に備わっている ``静的Webサイトホスティング機能`` を有効化することで、S3のみでWebサイトのホスティングを実現することも可能です。しかし、 **S3に以下のような制限がある** ことから、本テンプレートでは **CloudFront と組み合わせて静的Webサイトのホスティングを実現** しています。

+ [S3単独では秒間数千リクエストを超える **大規模なトラフィックに耐えられない**](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/optimizing-performance.html)
+ [S3単独では **カスタムドメインを用いたHTTPS通信が実現できない**](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/using-https-alternate-domain-names.html)
+ [S3の権限設定ミスを悪用した **Ghostwriter攻撃** を受ける可能性がある](https://www.mcafee.com/blogs/enterprise/cloud-security/mcafee-discovers-ghostwriter-a-pervasive-aws-s3-man-in-the-middle-exposure/)

S3バケットに関するCloudFormationテンプレートは以下の通りです。

```yaml
Resource:
  S3ForWebHosting:
    Type: 'AWS::S3::Bucket'
    UpdateReplacePolicy: Retain
    DeletionPolicy: Retain
    Properties:
      AccessControl: Private
      BucketEncryption:
        ServerSideEncryptionConfiguration: 
          - ServerSideEncryptionByDefault: 
              SSEAlgorithm: AES256
      BucketName: example.com
      LifecycleConfiguration:
        Rules:
          - Id: NonCurrentVersionExpiration
            NoncurrentVersionExpirationInDays: 90
            Status: Enabled
      LoggingConfiguration:
        DestinationBucketName: !Ref S3ForAccessLog
        LogFilePrefix: S3-example.com/
      PublicAccessBlockConfiguration: 
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      VersioningConfiguration:
        Status: Enabled
```

バケット内が空である場合のみS3バケットの削除が可能となるため、S3バケットには、 ``UpdateReplacePolicy `` および ``DeletionPolicy`` を指定することで、 **リソース置換時およびリソース削除時は、リソースを保持する** 設定にしています。また、上述のように CloudFront と組み合わせて静的Webサイトホスティングを実現するため、 [**規定ACL**](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/acl-overview.html#canned-acl)を ``private`` にした上で、 **パブリックアクセスを全て禁止** にしています。これにより、 **外部から直接S3バケットに対してアクセスされることはなく、ファイルの取得は全てCloudFrontを経由** します。

なお、のちほどご説明する ``Origin Access Identity`` が、 **AES256によるバケット暗号化のみをサポート** していること、[AWS の基本的なセキュリティのベストプラクティス標準](https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp.html) にて [**S3バケットのサーバサイド暗号化を求められている**](https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-s3-4) ことから、 **AES256によるサーバサイド暗号化を有効化** しています。

```yaml
Parameters:
  DomainName:
    Type: String
    AllowedPattern: .+
    Description: The CNAME attached to CloudFront [required]

Resources:
  S3BucketPolicy:
    Type: 'AWS::S3::BucketPolicy'
    Properties: 
      Bucket: !Ref S3ForWebHosting
      PolicyDocument:
        Version: 2012-10-17
        Id: !Ref S3ForWebHosting
        Statement:
          - Effect: Allow
            Principal:
              AWS: !Sub arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${CloudFrontOriginAccessIdentity}
            Action:
              - 's3:GetObject'
            Resource: !Sub arn:aws:s3:::${S3ForWebHosting}/*
          - Effect: Deny
            Principal: '*'
            Action: 's3:*'
            Resource: 
              - !GetAtt S3ForWebHosting.Arn
              - !Join
                - ''
                - - !GetAtt S3ForWebHosting.Arn
                  - /*
            Condition:
              Bool: 
                aws:SecureTransport: false
  CloudFrontOriginAccessIdentity:
    Type: 'AWS::CloudFront::CloudFrontOriginAccessIdentity'
    Properties:
      CloudFrontOriginAccessIdentityConfig: 
        Comment: !Sub access-identity-${DomainName}
```

S3のバケットポリシーでは、 **Origin Access Identity** を用いて **CloudFrontユーザからの GetObject 操作のみを許可** しています。また、[AWS の基本的なセキュリティのベストプラクティス標準](https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp.html) にて [**Secure Socket Layer を使用するためのリクエストが必要**](https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-s3-5) とされているため、 **SSLを用いないS3バケットへのアクセスを明示的に禁止** しています。

また、上述のS3バケットとは別に、 **ログを蓄積するためのS3バケットを作成** します。このバケットの[**規定ACL**](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/acl-overview.html#canned-acl)には、 ``LogDeliveryWrite`` を指定しています。

```yaml
Resources:
  S3ForAccessLog:
    Type: 'AWS::S3::Bucket'
    UpdateReplacePolicy: Retain
    DeletionPolicy: Retain
    Properties:
      AccessControl: LogDeliveryWrite
      BucketEncryption:
        ServerSideEncryptionConfiguration: 
          - ServerSideEncryptionByDefault: 
              SSEAlgorithm: AES256
      BucketName: !Sub logs-${AWS::Region}-${AWS::AccountId}
      LifecycleConfiguration:
        Rules:
          - Id: ExpirationInDays
            ExpirationInDays: 60
            Status: Enabled
      PublicAccessBlockConfiguration: 
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
  S3BucketPolicyForAccessLog:
    Condition: CustomLogBucketName
    Type: 'AWS::S3::BucketPolicy'
    Properties: 
      Bucket: !Ref S3ForAccessLog
      PolicyDocument:
        Version: 2012-10-17
        Id: !Ref S3ForAccessLog
        Statement:
          - Effect: Deny
            Principal: '*'
            Action: 's3:*'
            Resource: 
              - !GetAtt S3ForAccessLog.Arn
              - !Join
                - ''
                - - !GetAtt S3ForAccessLog.Arn
                  - /*
            Condition:
              Bool: 
                aws:SecureTransport: false
```

本テンプレートで作成するS3バケットは、[AWS の基本的なセキュリティのベストプラクティス標準](https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp.html) の以下の項目に準拠しています。

| No. | ルール | 実行内容 |
| --- | --- | --- |
| S3.1 | [S3 バケットでは、パブリックアクセスブロック設定を有効にする必要があります](https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-s3-1) | パブリックアクセスを禁止します |
| S3.2 | [S3 バケットでは、パブリック読み取りアクセスを禁止する必要があります](https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-s3-2) | パブリックアクセスを禁止します |
| S3.3 | [S3 バケットでは、パブリック書き込みアクセスを禁止する必要があります](https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-s3-3) | パブリックアクセスを禁止します |
| S3.4 | [S3 バケットでは、サーバー側の暗号化を有効にする必要があります](https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-s3-4) | AES256によるサーバ側暗号化を行います |
| S3.5 | [S3 バケットでは、Secure Socket Layer を使用するためのリクエストが必要です](https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-s3-5) | バケットボリシーを用いて、SSL通信以外のリクエストを拒否します |

## Amazon CloudFront

CloudFrontは、コンテンツを低遅延、高速かつ安全に配信することが可能な **コンテンツ配信ネットワーク（CDN）**で、AWS Certificate Manager（ACM)で作成/インポートされた **SSL/TLS証明書を簡単に紐付ける** ことができます。

ユーザからのHTTPリクエストはまず最初にこのCloudFrontが受信し、 CloudFrontがキャッシュしていないコンテンツのみをオリジンWebサーバの役割を担うS3バケットに問い合わせるため、 **S3バケットへのトラフィックを大幅に低減することが可能** です。

また、 **GET/HEAD リクエストのみ許可** され、 かつS3バケットへは [**Origin Access Identity (OAI)と呼ばれる、特別なCloudFrontユーザからのリクエストのみが許可**](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html) されるため、 **AWSリソースおよびS3バケット上のコンテンツは安全に保護** されます。

```yaml
Parameters:
  CertificateManagerARN:
    Default: ''
    Type: String
    Description: The ARN of an SSL Certifiation attached to CloudFront
  DomainName:
    Type: String
    AllowedPattern: .+
    Description: The CNAME attached to CloudFront [required]
  CloudFrontDefaultTTL:
    Default: 86400
    MinValue: 0
    Type: Number
    Description: CloudFront Default TTL [required]
  CloudFrontMinimumTTL:
    Default: 0
    MinValue: 0
    Type: Number
    Description: CloudFront Minimum TTL [required]
  CloudFrontMaximumTTL:
    Default: 31536000
    MinValue: 0
    Type: Number
    Description: CloudFront Maximum TTL [required]

Resources:
  CloudFront:
    Type: 'AWS::CloudFront::Distribution'
    Properties:
      DistributionConfig:
        Aliases:
         - !Ref DomainName
        Comment: !Sub CDN for ${DomainName}
        DefaultCacheBehavior:
          AllowedMethods:
            - GET
            - HEAD
          DefaultTTL: !Ref CloudFrontDefaultTTL
          ForwardedValues:
            QueryString: false
          MaxTTL: !Ref CloudFrontMaximumTTL
          MinTTL: !Ref CloudFrontMinimumTTL
          SmoothStreaming: false
          TargetOriginId: !Ref S3ForWebHosting
          ViewerProtocolPolicy: redirect-to-https
        DefaultRootObject : index.html
        Enabled: true
        HttpVersion: http2
        IPV6Enabled: false
        Logging:
          Bucket: !GetAtt S3ForAccessLog.DomainName
          Prefix: !Sub CloudFront-${DomainName}/
        Origins:
          - DomainName: !GetAtt S3ForWebHosting.DomainName
            Id: !Ref S3ForWebHosting
            S3OriginConfig:
              OriginAccessIdentity: !Join
               - ''
               - - origin-access-identity/cloudfront/
                 - !Ref CloudFrontOriginAccessIdentity
        ViewerCertificate:
          AcmCertificateArn: !Ref CertificateManagerARN
          MinimumProtocolVersion: TLSv1.1_2016
          SslSupportMethod: sni-only
```

本テンプレートでは、 **任意のカスタムドメイン** とこれに **関連するSSL/TLS証明書** をCloudFrontディストリビューションに紐づけます。また、ディストリビューションが **コンテンツをキャッシュする時間（TTL）を自由に指定** することが可能です。

以上で、Amazon S3 バケットと Amazon CloudFront ディストリビューションを用いた、 **静的Webサイトの配信基盤を構築** することができました。 **トラフィックの大部分はCloudFrontで処理** され、またCloudFrontとS3との間の通信に ``Origin Access Identity`` が使用されるために、 **オリジンWebサーバの役割を担うS3バケットと、その中に保存されている静的コンテンツは安全に保護** されます。また、任意のカスタムドメインを紐付けて、このドメインに対して **HTTPS通信** を行うこともできます。これらのトラフィックの **ログは、指定したS3バケットに一定期間保存** されます。

# 関連リンク
1. **ワンクリックで配信基盤を構築** - CloudFormation を用いて簡単Webサイトホスティング
2. [**CloudFrontにWAFをアタッチ** - CloudFormation を用いて簡単Webサイトホスティング](https://qiita.com/eijikominami/items/dffa11d456be3d6a88d2)
3. [**特定のURLを定期的にモニタリングする** - CloudFormation を用いて簡単Webサイトホスティング](https://qiita.com/eijikominami/items/45162d01edeec2855141)
4. [**CloudFrontのリアルタイムログをKibanaで可視化する** - CloudFormation を用いて簡単Webサイトホスティング](https://qiita.com/eijikominami/items/1bcde04897b5136e4122)