CloudFront(OAC) + S3 構築ハンズオン


この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので十分ご注意ください。

はじめに

半年前にCloudFrontでOrigin Access Control (OAC)を利用したアクセス制御のハンズオンを検証しましたが、検証とは名ばかりの簡単な確認だけだったので、改めてS3と組み合わせたCFn構築をしてみようと思います。

ざっくりOACとは

CloudFrontのオリジンにS3を指定する際、S3へのアクセスをCloudFrontだけに制限するための設定。
もともとのS3へアクセス制限を行う OAIという機能よりも、OACでは以下にも対応しているため より細かい設定ができる。
 ・AWS KMS による Amazon S3 サーバー側の暗号化 (SSE-KMS)
 ・Amazon S3 に対する動的なリクエスト (PUT と DELETE)

Amazon S3 オリジンへのアクセスの制限


構成図


ハンズオン

構築のながれ

1.Amazon S3作成    :index.htmlの保存先を構築

2.Amazon CloudFront作成:OAC + CloudFrontの構築

3.AWS KMS作成     :S3オブジェクト暗号用鍵構築

4.Amazon S3更新    :KMSを利用した暗号化 + バケットポリシー更新


1.Amazon S3作成:index.htmlの保存先を構築

・コメントアウト部分の2箇所は、CloudFrontKMSが構築された後にアンコメントして更新

AWSTemplateFormatVersion: '2010-09-09'
Description: S3 Stack
# ------------------------------------------------------------#
#  Metadata
# ------------------------------------------------------------#
Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "S3 Configuration"
        Parameters:
        - S3BucketName
        - AccessControl
        - BlockPublicAcls
        - BlockPublicPolicy
        - IgnorePublicAcls
        - RestrictPublicBuckets
        - ExpirationInDays
        - EventBridgeConfiguration
        - TagsName

# ------------------------------------------------------------#
#  InputParameters
# ------------------------------------------------------------#
Parameters:
  S3BucketName:
    Type: String
    Default: "【ユニーク バケット名】"
    Description: Type of this BacketName.
  VersioningConfiguration:
    Type: String
    Default: "Enabled"
    Description: VersioningConfiguration.
  AccessControl:
    Type: String
    Description: AccessControl.
    Default: "Private"
    AllowedValues: [ "Private", "PublicRead", "PublicReadWrite", "AuthenticatedRead", "LogDeliveryWrite", "BucketOwnerRead", "BucketOwnerFullControl", "AwsExecRead" ]
  BlockPublicAcls: 
    Type: String
    Description: BlockPublicAcls.
    Default: "True"
    AllowedValues: [ "True", "False" ]
  BlockPublicPolicy:
    Type: String
    Description: BlockPublicPolicy.
    Default: "True"
    AllowedValues: [ "True", "False" ]
  IgnorePublicAcls:
    Type: String
    Description: IgnorePublicAcls.
    Default: "True"
    AllowedValues: [ "True", "False" ]
  RestrictPublicBuckets:
    Type: String
    Description: RestrictPublicBuckets.
    Default: "True"
    AllowedValues: [ "True", "False" ]
  ExpirationInDays:
    Type: String
    Description: Lifecycle Days.
    Default: "7"
  TagsName:
    Type: String
    Description: UserName
    Default: "【タグ名】"

# ------------------------------------------------------------#
#  Resources
# ------------------------------------------------------------#
Resources:
# ------------------------------------------------------------#
#  S3
# ------------------------------------------------------------#
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      # BucketEncryption: 
      #   ServerSideEncryptionConfiguration: 
      #     - BucketKeyEnabled: true
      #       ServerSideEncryptionByDefault:
      #         KMSMasterKeyID: !ImportValue KMSKeyId
      #         SSEAlgorithm: aws:kms
      BucketName: !Ref S3BucketName      
      VersioningConfiguration:
        Status: !Ref VersioningConfiguration
      AccessControl: !Ref AccessControl
      PublicAccessBlockConfiguration:
        BlockPublicAcls: !Ref BlockPublicAcls
        BlockPublicPolicy: !Ref BlockPublicPolicy
        IgnorePublicAcls: !Ref IgnorePublicAcls
        RestrictPublicBuckets: !Ref RestrictPublicBuckets
      LifecycleConfiguration:
        Rules:
          - Id: LifeCycleRule
            Status: Enabled
            ExpirationInDays: !Ref ExpirationInDays
      VersioningConfiguration:
        Status: Enabled
      WebsiteConfiguration:
        IndexDocument: index.html
        ErrorDocument: 404.html
      Tags:
        - Key: "User"
          Value: !Ref TagsName

  # S3BucketPolicy:
  #   Type: AWS::S3::BucketPolicy
  #   Properties: 
  #     Bucket: !Ref S3Bucket
  #     PolicyDocument: 
  #       Version: "2008-10-17"
  #       Statement: 
  #         - Sid: "AllowCloudFrontServicePrincipal"
  #           Effect: "Allow"
  #           Principal: 
  #             Service: 
  #               - "cloudfront.amazonaws.com"
  #           Action: 
  #             - "s3:GetObject"
  #           Resource: 
  #             - !Sub ${S3Bucket.Arn}/*
  #           Condition: 
  #             StringEquals:
  #               AWS:SourceArn: 
  #                 - !Join 
  #                   - ''
  #                   - - !Sub 'arn:aws:cloudfront::${AWS::AccountId}:distribution/'
  #                     - !ImportValue CloudFrontID

# ------------------------------------------------------------#
#  Outputs
# ------------------------------------------------------------#
Outputs:
  S3BucketName:
    Value: !Ref S3Bucket
    Export:
      Name: cfn-s3-BucketName
  S3DomainName:
    Value: !GetAtt S3Bucket.DomainName
    Export:
      Name: cfn-s3-DomainName

2.Amazon CloudFront作成:OAC + CloudFrontの構築

・S3のDNSをOriginにして設定(静的Webサイトホスティングではない)
・「S3OriginConfig」の「OriginAccessIdentity」は空で指定しOAIを利用しないように設定(記載がないとエラー表示)

AWSTemplateFormatVersion: "2010-09-09"
Description: CloudFront Stack
# ------------------------------------------------------------#
#  Metadata
# ------------------------------------------------------------#
Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "OriginAccessControl Configuration"
        Parameters:
        - OACName
      - Label:
          default: "CloudFront Configuration"
        Parameters:
        - TagsName
# ------------------------------------------------------------#
#  InputParameters
# ------------------------------------------------------------#
Parameters:
  OACName:
    Type: String
    Description: OAC Name
    Default: "【OAC Name】"

  TagsName:
    Type: String
    Description: UserName
    Default: "【タグ名】"

Resources:
# ------------------------------------------------------------#
# CloudFront
# ------------------------------------------------------------# 
  OAC: 
    Type: AWS::CloudFront::OriginAccessControl
    Properties: 
      OriginAccessControlConfig:
        Description: Access Control
        Name: !Ref OACName
        OriginAccessControlOriginType: s3
        SigningBehavior: always
        SigningProtocol: sigv4

  CloudFront:
    Type: AWS::CloudFront::Distribution
    Properties: 
      DistributionConfig:
        Comment: "CloudFront-S3 URL"
        DefaultRootObject: index.html
        DefaultCacheBehavior: 
          AllowedMethods:
            - GET
            - HEAD
          CachedMethods:
            - GET
            - HEAD
          CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6
          TargetOriginId: S3
          ViewerProtocolPolicy: allow-all
        Enabled: true
        Origins: 
          - DomainName: !ImportValue cfn-s3-DomainName
            Id: S3
            OriginAccessControlId: !GetAtt OAC.Id
            S3OriginConfig: 
              OriginAccessIdentity: ''
        PriceClass: PriceClass_200
      Tags:
        - Key: UserName
          Value: !Ref TagsName

Outputs:
# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------# 
  CloudFrontID:
    Value: !Ref CloudFront
    Export: 
      Name: CloudFrontID

3.AWS KMS作成:S3オブジェクト暗号用鍵構築

・PrincipalとしてCloudFrontが利用できるようにします。

AWSTemplateFormatVersion: "2010-09-09"
Description: KMS Stack
# ------------------------------------------------------------#
#  Metadata
# ------------------------------------------------------------#
Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "KMS Configuration"
        Parameters:
        - KMSId
        - UserName
# ------------------------------------------------------------#
#  InputParameters
# ------------------------------------------------------------#
Parameters:
  KMSId:
    Type: String
    Description: KMSId
    Default: "DEV"

  UserName:
    Type: String
    Description: UserName
    Default: "【IAM ユーザー名】"

Resources:
# ------------------------------------------------------------#
# KMS
# ------------------------------------------------------------# 
  KMSAlias:
    Type: AWS::KMS::Alias
    Properties: 
      AliasName: !Sub alias/cfn-${UserName}
      TargetKeyId: !Ref KMSKey

  KMSKey:
    Type: AWS::KMS::Key
    Properties: 
      Description: Encryption KMS key
      Enabled: true
      KeyPolicy: 
        Version: '2012-10-17'
        Id: !Sub cfn-${KMSId}-kms
        Statement: 
          - Sid: Enable IAM User Permissions
            Effect: Allow
            Principal: 
              AWS: !Sub arn:aws:iam::${AWS::AccountId}:root
            Action: 'kms:*'
            Resource: '*'
          - Sid: 'Allow access for Key Administrators'
            Effect: Allow
            Principal: 
              AWS: !Sub arn:aws:iam::${AWS::AccountId}:user/${UserName}
            Action: 
              - 'kms:Create*'
              - 'kms:Describe*'
              - 'kms:Enable*'
              - 'kms:List*'
              - 'kms:Put*'
              - 'kms:Update*'
              - 'kms:Revoke*'
              - 'kms:Disable*'
              - 'kms:Get*'
              - 'kms:Delete*'
              - 'kms:TagResource'
              - 'kms:UntagResource'
              - 'kms:ScheduleKeyDeletion'
              - 'kms:CancelKeyDeletion'
              - 'kms:CreateGrant'
              - 'kms:ListGrants'
              - 'kms:RevokeGrant'
            Resource: '*'
          - Sid: 'Allow use of the key'
            Effect: Allow
            Principal: 
              AWS: !Sub arn:aws:iam::${AWS::AccountId}:user/${UserName}
            Action: 
              - 'kms:Encrypt'
              - 'kms:Decrypt'
              - 'kms:ReEncrypt*'
              - 'kms:GenerateDataKey*'
              - 'kms:DescribeKey'
            Resource: '*'
          - Sid: 'Allow use of the key'
            Effect: Allow
            Principal: 
              Service: 
                - cloudfront.amazonaws.com
            Action: 
              - 'kms:Decrypt'
              - 'kms:Encrypt'
              - 'kms:GenerateDataKey*'
            Resource: '*'
            Condition: 
              StringEquals:
                Aws:SourceArn:
                  - !Join 
                    - ''
                    - - !Sub 'arn:aws:cloudfront::${AWS::AccountId}:distribution/'
                      - !ImportValue CloudFrontID
      PendingWindowInDays: 7
      Tags: 
        - Key: UserName
          Value: !Ref UserName
# ------------------------------------------------------------#
#  Outputs
# ------------------------------------------------------------#
Outputs:
  KMSKeyId:
    Value: !GetAtt KMSKey.KeyId
    Export: 
      Name: KMSKeyId
  KMSArn:
    Value: !GetAtt KMSKey.Arn
    Export: 
      Name: KMSArn

4.Amazon S3更新:KMSを利用した暗号化 + バケットポリシー更新

・手順1で構築したテンプレートのコメントアウトされていた2箇所をアンコメント

【KMSによる暗号化部分】

      BucketEncryption: 
        ServerSideEncryptionConfiguration: 
          - BucketKeyEnabled: true
            ServerSideEncryptionByDefault:
              KMSMasterKeyID: !ImportValue KMSKeyId
              SSEAlgorithm: aws:kms

【CloudFrontからのアクセスを許可するバケットポリシー】

  S3BucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties: 
      Bucket: !Ref S3Bucket
      PolicyDocument: 
        Version: "2008-10-17"
        Statement: 
          - Sid: "AllowCloudFrontServicePrincipal"
            Effect: "Allow"
            Principal: 
              Service: 
                - "cloudfront.amazonaws.com"
            Action: 
              - "s3:GetObject"
            Resource: 
              - !Sub ${S3Bucket.Arn}/*
            Condition: 
              StringEquals:
                AWS:SourceArn: 
                  - !Join 
                    - ''
                    - - !Sub 'arn:aws:cloudfront::${AWS::AccountId}:distribution/'
                      - !ImportValue CloudFrontID


挙動の確認

1.S3バケットに index.html をアップロード

1.1.index.htmlの詳細
<html>
  <head>
    <meta charset="UTF-8">
    <title>テストページ</title>
  </head>
  <body bgcolor="#10100E" text="#cccccc">Hello World!!<br/></body>
</html>

1.2.S3に index.html を保存した画面

2.CloudFront のドメイン名を取得

・CloudFront > ディストリビューション より取得

3.ブラウザで確認

4.CloudFrontでOACの設定を確認

・CloudFront > ディストリビューション > タブ:オリジンを押下 > オリジンS3を編集を押下


さいごに

CloudFrontのデフォルトルートオブジェクト名(index.html)をテンプレートに入力していないなどで、何回かAccessDeniedにもなりましたが無事に表示することが出来ました。
新しいものを億劫がらずに よりセキュアな構築の考え方を取得していきたいです。

Last modified: 2023-02-12

Author