😸

【CloudFormation】S3をデプロイしてみる

2022/06/27に公開

1. はじめに

 はじめまして、Mitsuoです。
Zennでアカウントを作成し、初めて技術ブログを投稿します。

お手柔らかにお願いします。

2. 今回のお題

 CloudFormationを用いてS3バケットをデプロイしてみました。
S3バケットの作成自体は簡単ですが、何回も利用するサービスですし、他サービスと比べても機能が豊富なので、手動で作成するのは中々面倒だと思います。

そこで、ある程度標準化する為に雛形のテンプレートを作ったいう訳です。
ありふれた物かもしれませんが、自分用でもあるので、一石二鳥ということで。

3. 作成したリソース

 テンプレートで作成するリソースは以下の通りです。

  • S3バケット4つ
  • バケットポリシー4つ

S3バケットを2つ、それらのアクセスログ用のバケットが2つで計4つ作成します。
加えて、バケット用にバケットポリシーを設定します。

4. テンプレート

 作成したテンプレートになります。

AWSTemplateFormatVersion: '2010-09-09'
Description: Deploy S3 Buckets with Bucket policies.
# ------------------------------------------------------------#
#  Prerequisite:
#  This template is for deploying four Buckets(S3) with Bucket policies.
#  Two of them are for collecting log datas from 2 buckets.
#  Additionally, this template is intended to simplify the creation of versatile buckets.
#  If you use this template in an actual project and encounter any problems, we take no responsibility in regards to that.
#  But you notice the problems about this template, please let me know what needs to correct.I will modify them you point out.
# ------------------------------------------------------------#
#  Metadata:
#  This can provide details about the template.
#  For more information, see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/metadata-section-structure.html.
#  As Metadata Keys, AWS::CloudFormation::Interface can define the grouping and ordering of input parameters
#  when they are displayed in the AWS CloudFormation console.
#  For more information, see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudformation-interface.html.
# ------------------------------------------------------------# 

Metadata: 
  "AWS::CloudFormation::Interface": 
    ParameterGroups:
      -  
        Parameters:
          - ProjectName
          - EnvType 
          - Tagprefix
          - Owner

    ParameterLabels: 
      ProjectName:
        default: "Please type your project Name you would like to name"
      EnvType:
        default: "Please select which environment you would like to deploy buckets in from among dev, stg and prod."
      Tagprefix:
        default: "Please type the prefix you would like to add as logical unit name about each resource(e.g. webserver,monitoring,logging)"
      Owner:
        default: "Please type who owns these resources (e.g. project name, worker or for cost management)"            

# ------------------------------------------------------------#
# Parameters
# This Can enable templates to input custom values each time you create or update a stack.
# For more information, see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html.
# ------------------------------------------------------------# 

Parameters:
  ProjectName:
    Type: String
  EnvType:
    Type: String
    Default: "dev"
    AllowedValues:
      - dev
      - stg
      - prod   
  Tagprefix:
    Type: String      
  Owner:
    Type: String         

# ------------------------------------------------------------#
# Resources
# This can declare the AWS resources that you want to include in the stack.
# For more information, see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resources-section-structure.html.
# ------------------------------------------------------------# 

Resources:
# ------------------------------------------------------------#
# Create S3 Bucket 1
# ------------------------------------------------------------#

  S3Bucket1:
    Type: 'AWS::S3::Bucket'
    DeletionPolicy: Retain
    Properties:
      BucketName: !Sub ${ProjectName}-${EnvType}-${Tagprefix}-s3bucket1-${AWS::AccountId}
      PublicAccessBlockConfiguration: 
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      VersioningConfiguration:
        Status: Suspended          
      LoggingConfiguration:
        DestinationBucketName: !Ref S3Bucket3
        LogFilePrefix: AccessLog/
      BucketEncryption:
        ServerSideEncryptionConfiguration: 
          - ServerSideEncryptionByDefault: 
              SSEAlgorithm: AES256                  
      LifecycleConfiguration:
        Rules:
          - Id: !Sub ${ProjectName}-${EnvType}-${Tagprefix}-Lifecycle-s3bucket1
            Status: Enabled
            ExpirationInDays: 30
      OwnershipControls:
        Rules:
          - ObjectOwnership: BucketOwnerEnforced
      NotificationConfiguration:
        EventBridgeConfiguration:
          EventBridgeEnabled: true           
      Tags:
        - Key: Name
          Value: !Sub ${ProjectName}-${EnvType}-${Tagprefix}-s3bucket1-${AWS::AccountId}   
        - Key: Owner
          Value: !Sub ${Owner}     
    DependsOn: S3Bucket3      

# ------------------------------------------------------------#
# Create S3 Bucket 2
# ------------------------------------------------------------#

  S3Bucket2:
    Type: 'AWS::S3::Bucket'
    DeletionPolicy: Retain
    Properties:
      BucketName: !Sub ${ProjectName}-${EnvType}-${Tagprefix}-s3bucket2-${AWS::AccountId}
      PublicAccessBlockConfiguration: 
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      VersioningConfiguration:
        Status: Enabled                  
      LoggingConfiguration:
        DestinationBucketName: !Ref S3Bucket4
        LogFilePrefix: AccessLog/
      BucketEncryption:
        ServerSideEncryptionConfiguration: 
          - ServerSideEncryptionByDefault: 
              SSEAlgorithm: AES256    
      LifecycleConfiguration:
        Rules:
          - Id: !Sub ${ProjectName}-${EnvType}-${Tagprefix}-Lifecycle-s3bucket2
            Status: Enabled
            ExpirationInDays: 365
            Transitions:
              - StorageClass: GLACIER
                TransitionInDays: 30
      OwnershipControls:
        Rules:
          - ObjectOwnership: BucketOwnerEnforced
      NotificationConfiguration:
        EventBridgeConfiguration:
          EventBridgeEnabled: true                         
      Tags:
        - Key: Name
          Value: !Sub ${ProjectName}-${EnvType}-${Tagprefix}-s3bucket2-${AWS::AccountId}
        - Key: Owner
          Value: !Sub ${Owner}        
    DependsOn: S3Bucket4 

# ------------------------------------------------------------#
# Create S3 Bucket 3 for collecting access log from S3 Bucket 1
# ------------------------------------------------------------#

  S3Bucket3:
    Type: 'AWS::S3::Bucket'
    DeletionPolicy: Retain
    Properties:
      BucketName: !Sub ${ProjectName}-${EnvType}-${Tagprefix}-s3bucket3-${AWS::AccountId}
      PublicAccessBlockConfiguration: 
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      VersioningConfiguration:
        Status: Suspended          
      BucketEncryption:
        ServerSideEncryptionConfiguration: 
          - ServerSideEncryptionByDefault: 
              SSEAlgorithm: AES256  
      OwnershipControls:
        Rules:
          - ObjectOwnership: BucketOwnerEnforced
      NotificationConfiguration:
        EventBridgeConfiguration:
          EventBridgeEnabled: true                                
      LifecycleConfiguration:
        Rules:
          - Id: !Sub ${ProjectName}-${EnvType}-${Tagprefix}-Lifecycle-s3bucket3
            Status: Enabled
            ExpirationInDays: 30
      Tags:
        - Key: Name
          Value: !Sub ${ProjectName}-${EnvType}-${Tagprefix}-s3bucket3-${AWS::AccountId}
        - Key: Owner
          Value: !Sub ${Owner}            

# ------------------------------------------------------------#
# Create S3 Bucket 4 for collecting access log from S3 Bucket 2
# ------------------------------------------------------------#

  S3Bucket4:
    Type: 'AWS::S3::Bucket'
    DeletionPolicy: Retain
    Properties:
      BucketName: !Sub ${ProjectName}-${EnvType}-${Tagprefix}-s3bucket4-${AWS::AccountId}
      PublicAccessBlockConfiguration: 
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      VersioningConfiguration:
        Status: Suspended                  
      BucketEncryption:
        ServerSideEncryptionConfiguration: 
          - ServerSideEncryptionByDefault: 
              SSEAlgorithm: AES256  
      OwnershipControls:
        Rules:
          - ObjectOwnership: BucketOwnerEnforced
      NotificationConfiguration:
        EventBridgeConfiguration:
          EventBridgeEnabled: true                 
      LifecycleConfiguration:
        Rules:
          - Id: !Sub ${ProjectName}-${EnvType}-${Tagprefix}-Lifecycle-s3bucket4
            Status: Enabled
            ExpirationInDays: 30
      Tags:
        - Key: Name
          Value: !Sub ${ProjectName}-${EnvType}-${Tagprefix}-s3bucket4-${AWS::AccountId}    
        - Key: Owner
          Value: !Sub ${Owner}            
        
# ------------------------------------------------------------#
# Create S3 Bucket Policy
# ------------------------------------------------------------#

  BucketPolicy1:
    Type: 'AWS::S3::BucketPolicy'
    Properties:
      Bucket: !Ref S3Bucket1
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action:
              - 's3:*'
            Sid: BucketPolicy1              
            Effect: Allow
            Resource: 
              - !Sub 'arn:aws:s3:::${S3Bucket1}'
              - !Sub 'arn:aws:s3:::${S3Bucket1}/*'
            Principal: 
              AWS: 
                - !Sub 'arn:aws:iam::${AWS::AccountId}:root'
    DependsOn: S3Bucket1 

  BucketPolicy2:
    Type: 'AWS::S3::BucketPolicy'
    Properties:
      Bucket: !Ref S3Bucket2
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action:
              - 's3:*'
            Sid: BucketPolicy2
            Effect: Allow
            Resource: 
              - !Sub 'arn:aws:s3:::${S3Bucket2}'
              - !Sub 'arn:aws:s3:::${S3Bucket2}/*'
            Principal: 
              AWS: 
                - !Sub 'arn:aws:iam::${AWS::AccountId}:root'
    DependsOn: S3Bucket2 

  BucketPolicy3:
    Type: 'AWS::S3::BucketPolicy'
    Properties:
      Bucket: !Ref S3Bucket3
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action:
              - 's3:PutObject'
            Sid: BucketPolicy3               
            Effect: Allow
            Resource: 
              - !Sub 'arn:aws:s3:::${S3Bucket3}'
              - !Sub 'arn:aws:s3:::${S3Bucket3}/*'
            Principal:
              Service: logging.s3.amazonaws.com
    DependsOn: S3Bucket3 

  BucketPolicy4:
    Type: 'AWS::S3::BucketPolicy'
    Properties:
      Bucket: !Ref S3Bucket4
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action:
              - 's3:PutObject'
            Sid: BucketPolicy4               
            Effect: Allow
            Resource: 
              - !Sub 'arn:aws:s3:::${S3Bucket4}'
              - !Sub 'arn:aws:s3:::${S3Bucket4}/*'
            Principal:
              Service: logging.s3.amazonaws.com
    DependsOn: S3Bucket4             

# ------------------------------------------------------------#
# Outputs
# This can output each value specified as output section.
# For more information, see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resources-section-structure.html.
# ------------------------------------------------------------# 

Outputs:
  S3Bucket1:
    Description: S3Bucket1
    Value: !Ref S3Bucket1
  S3Bucket2:
    Description: S3Bucket2
    Value: !Ref S3Bucket2 
  S3Bucket3:
    Description: S3Bucket3
    Value: !Ref S3Bucket3
  S3Bucket4:
    Description: S3Bucket4
    Value: !Ref S3Bucket4      

5. テンプレートの補足

テンプレートに関して、以下の通り補足します。

5.1 Metadata/Parameters

 メタデータおよびパラメターセクションで、4つの値を定義します。

項目 備考
ProjectName プロジェクト名や利用目的等を入力する
EnvType デプロイする環境を選択する
検証、ステージング、本番の3点
Tagprefix 任意で入力するPrefix値
Owner 作業者、所有者、コスト管理用に入力する

バケット名およびバケットのNameタグの値に、ProjectNameEnvTypeTagprefixの3点が含みます。
OwnerはOwnerタグの値に利用します。

5.2 Resources (Type: 'AWS::S3::Bucket')

 リソースセクションで以下の用に定義しています。

具体的なプロパティに解説は公式ドキュメントを参照ください。

5.2.1 S3Bucket1

  • パブリックアクセスの無効化
      PublicAccessBlockConfiguration: 
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
  • バージョニング設定の無効化
      VersioningConfiguration:
        Status: Suspended   
  • S3アクセスログ設定

バケット直下にAccessLogフォルダを切ってその下にログを格納します。


      LoggingConfiguration:
        DestinationBucketName: !Ref S3Bucket3
        LogFilePrefix: AccessLog/
  • 暗号化設定

S3のサーバサイド暗号化を設定します。KMSにする事も可能です。

      BucketEncryption:
        ServerSideEncryptionConfiguration: 
          - ServerSideEncryptionByDefault: 
              SSEAlgorithm: AES256
  • ライフサイクル設定

30日後にオブジェクトを削除します。

      LifecycleConfiguration:
        Rules:
          - Id: !Sub ${ProjectName}-${EnvType}-${Tagprefix}-Lifecycle-s3bucket1
            Status: Enabled
            ExpirationInDays: 30
  • ACL設定

ACLは無効、バケットポリシーで制御する想定です。
全てのオブジェクトにおける所有者を「バケットを所有しているアカウント」にしています。

      OwnershipControls:
        Rules:
          - ObjectOwnership: BucketOwnerEnforced
  • 通知設定

S3からEventBridgeへの連携を有効化しています。

      NotificationConfiguration:
        EventBridgeConfiguration:
          EventBridgeEnabled: true
  • タグ設定
      Tags:
        - Key: Name
          Value: !Sub ${ProjectName}-${EnvType}-${Tagprefix}-s3bucket1-${AWS::AccountId}   
        - Key: Owner
          Value: !Sub ${Owner} 

5.2.2 S3Bucket2

 他のリソースとの差異がある箇所のみ補足します。

  • バージョニングの有効化

Enabledが有効化、Suspendedが無効化です。

      VersioningConfiguration:
        Status: Enabled 
  • ライフサイクル設定

365日後にオブジェクトを削除します。
ストレージクラスの変更もTransitionsで指定可能です。
30日後に、Glacier Flexible Retrievalにデータを移行します。

      LifecycleConfiguration:
        Rules:
          - Id: !Sub ${ProjectName}-${EnvType}-${Tagprefix}-Lifecycle-s3bucket2
            Status: Enabled
            ExpirationInDays: 365
            Transitions:
              - StorageClass: GLACIER
                TransitionInDays: 30

5.2.3 S3Bucket3/4

 アクセスログの設定は無効化

5.3 Resources (AWS::S3::BucketPolicy)

 リソースセクションで以下の用に定義しています。

具体的なプロパティに解説は公式ドキュメントを参照ください。

5.3.1 BucketPolicy1

ルートユーザによるS3Bucket1のみS3の全てのアクションを許可する設定です。
実際に利用する際には、適切な権限付与をおこなってください。
パブリックアクセスが無効化されている場合に、Principalが*の場合は、
エラーになるので注意してください。

BucketPolicy2の補足事項は特に無いです。

      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action:
              - 's3:*'
            Sid: BucketPolicy1              
            Effect: Allow
            Resource: 
              - !Sub 'arn:aws:s3:::${S3Bucket1}'
              - !Sub 'arn:aws:s3:::${S3Bucket1}/*'
            Principal: 
              AWS: 
                - !Sub 'arn:aws:iam::${AWS::AccountId}:root'                

5.3.2 BucketPolicy3

サービスにlogging.s3.amazonaws.comを指定する事で、アクセスログ機能に必要な、
S3Bucket3に対するPutObjectアクションを許可しています。
実際に利用する際には、適切な権限付与をおこなってください。

BucketPolicy4の補足事項は特に無いです。

      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action:
              - 's3:PutObject'
            Sid: BucketPolicy3               
            Effect: Allow
            Resource: 
              - !Sub 'arn:aws:s3:::${S3Bucket3}'
              - !Sub 'arn:aws:s3:::${S3Bucket3}/*'
            Principal:
              Service: logging.s3.amazonaws.com

5.4 Outputs

 各バケットの値を出力します。

Outputs:
  S3Bucket1:
    Description: S3Bucket1
    Value: !Ref S3Bucket1
  S3Bucket2:
    Description: S3Bucket2
    Value: !Ref S3Bucket2 
  S3Bucket3:
    Description: S3Bucket3
    Value: !Ref S3Bucket3
  S3Bucket4:
    Description: S3Bucket4
    Value: !Ref S3Bucket4   

5.5 その他

  • S3バケットのDeletionPolicyをRetainにしています

    • オブジェクトが存在すると削除出来ない事と、バケットが気付かない内に削除されるケースを懸念して、手動で削除する様にしています
  • DependsOnの設定をおこなっています

    • バケット1、バケット2はそれぞれアクセスログ用のバケットが作成されていないと、
      エラーが発生する為、DependsOnの設定をそれぞれ行っています
    • バケットポリシーも同様に設定しています
    • バケットを作成した後にバケットポリシーを作成しています

6. まとめ

 ありきたりなテンプレートかもしれませんが、工数削減に助力できるバケットになってくれれば嬉しいです。

また書きます。Mitsuoでした!

Discussion