AWS_CloudFormation　S3とCloudFrontを構築してみる

# はじめに
１年ほど前からAWSに関わる業務に携わっておりました。
そこでCloudFormationを用いてAWS環境を構築してきたため、
今回はS3とCloudFrontで静的webサイトを表示させる環境をCloudFormationで構築する手順について記載します。

また、S3やCloudFront、CloudFormationについての説明は割愛させていただきます。

# 目次
・CloudFormationのスタック構成
・CloudFormation作成
・最後に
・参考

# CloudFormationのスタック構成
CloudFormationで使用するスタックセットは下記に分けます
・S3バケット
・CloudFront（OAC設定含む)
・S3バケットポリシー

S3バケットとバケットポリシーを分けている理由としては、
バケットポリシーにCloudFrontの情報(ディストリビューション)が必要なためです。
そのため、S3バケット⇒CloudFront⇒バケットポリシーの順に構築していくこととなります。

# CloudFormation作成
### S3バケット
S3バケットを作成するCloudFormationを下記に記載します。
OutputsでS3バケットの情報をエクスポートしておくことで後に作成するCloudFrontやバケットポリシーに紐づけられるようにしております。

```yaml:S3Bucket.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

#------------------------------------------------------------#
#Parameters
#------------------------------------------------------------#
Parameters:
  PJPrefix:
    Description: Project Name
    Type: String
    Default: pj-ogataro
    AllowedPattern: ^[a-zA-Z0-9\-]*$
  Environment:
    Description: Environment Name
    Type: String
    Default: dev
    AllowedPattern: ^[a-zA-Z0-9\-]*$

#------------------------------------------------------------#
#Resources
#------------------------------------------------------------#
Resources:
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub '${PJPrefix}-${Environment}-storage'
      VersioningConfiguration:
        Status: Enabled
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      Tags:
        - Key: Name
          Value: !Sub '${PJPrefix}-${Environment}-storage'
#------------------------------------------------------------#
#Outputs
#------------------------------------------------------------#
Outputs:
  cloudfront:
    Value: !Ref S3Bucket
    Export: 
      Name: !Sub "${PJPrefix}-${Environment}-storage-Output"
```
### CloudFront
次にCloudFrontを作成するCloudFormationを下記に記載します
AWSでは現在OAIではなく、OACを推奨していますのでOACで構築していきます。
https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html

また、今回はhttpとhttps両方のアクセス許可で設定しています。


```yaml:CloudFront.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

#------------------------------------------------------------#
#Parameters
#------------------------------------------------------------#
Parameters:
  PJPrefix:
    Description: Project Name
    Type: String
    Default: pj-ogataro
    AllowedPattern: ^[a-zA-Z0-9\-]*$
  Environment:
    Description: Environment Name
    Type: String
    Default: dev
    AllowedPattern: ^[a-zA-Z0-9\-]*$

#------------------------------------------------------------#
#Resources
#------------------------------------------------------------#
Resources:
  OAC:
    Type: AWS::CloudFront::OriginAccessControl
    Properties:
      OriginAccessControlConfig:
        Description: Access Control
        Name: !Sub '${PJPrefix}-${Environment}-OAC'
        OriginAccessControlOriginType: s3
        SigningBehavior: always
        SigningProtocol: sigv4

  CloudFrontDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Enabled: true
        Origins:
          - Id: S3Origin
            DomainName: !Sub '${PJPrefix}-${Environment}-storage.s3.amazonaws.com'
            OriginAccessControlId: !GetAtt OAC.Id
            S3OriginConfig:
              OriginAccessIdentity: '' #OAIを使用しない場合、空で設定しないとエラーになるため
        DefaultCacheBehavior:
          TargetOriginId: S3Origin
          ViewerProtocolPolicy: allow-all
          AllowedMethods:
            - GET
            - HEAD
          ForwardedValues:
            QueryString: false
            Cookies:
              Forward: none
        PriceClass: PriceClass_200
        DefaultRootObject: index.html
        HttpVersion: 'http2'

#------------------------------------------------------------#
#Outputs
#------------------------------------------------------------#
Outputs:
  cloudfront:
    Value: !Ref CloudFrontDistribution
    Export: 
      Name: !Sub '${PJPrefix}-${Environment}-Distribution-Output'
```
### S3バケットポリシー
最後にS3バケットポリシーを作成するCloudFormationを下記に記載します。
CloudFront OAC への読み取り専用アクセスを許可する内容となっています。

```yaml:S3BucketPolicy
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

#------------------------------------------------------------#
#Parameters
#------------------------------------------------------------#
Parameters:
  PJPrefix:
    Description: Project Name
    Type: String
    Default: pj-ogataro
    AllowedPattern: ^[a-zA-Z0-9\-]*$
  Environment:
    Description: Environment Name
    Type: String
    Default: dev
    AllowedPattern: ^[a-zA-Z0-9\-]*$

#------------------------------------------------------------#
#Resources
#------------------------------------------------------------#
Resources:
  S3BucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !ImportValue
        Fn::Sub:  "${PJPrefix}-${Environment}-storage-Output"
      PolicyDocument:
        Version: "2012-10-17"
        Id: "PolicyForCloudFrontPrivateContent"
        Statement:
          - Sid: "AllowCloudFrontService"
            Effect: "Allow"
            Principal:
              Service: "cloudfront.amazonaws.com"
            Action: "s3:GetObject"
            Resource: !Sub "arn:aws:s3:::${PJPrefix}-${Environment}-storage/*"
            Condition:
              StringEquals:
                AWS:SourceArn:
                  - !Join
                    - ''
                    - - !Sub "arn:aws:cloudfront::${AWS::AccountId}:distribution/"
                      - !ImportValue
                        Fn::Sub: "${PJPrefix}-${Environment}-Distribution-Output"

```
# 最後に
上記設定を完了したうえで、S3にindex.htmlファイルをアップロードします。
index.htmlの中身は下記となります。

```html:index.html
<!DOCTYPE html> 
<html lang="ja">
<head> 
<meta charset="utf-8">
<title>test</title>
</head> 
<body> 
Hello,World!!
</body> 
</html>
```

反映までに時間がかかりますので、アップロード後CloudFrontのキャッシュ削除を実施してください。

CloudFrontにアクセスして下記のようになれば完了です。
※Access Deniedなどでアクセスできない場合、ブラウザのcookieもしくは履歴を削除すると改善する場合があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513082/9eea5286-4ef2-1b2f-f02f-297fd6b98e99.png)

参考になれば、幸いです。

# 参考サイト
### S3バケット
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3-bucket.html

### CloudFront
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-originaccesscontrol.html

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-distribution.html

### S3バケットポリシー
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3-bucketpolicy.html