5分でできるS3とCloudFrontを利用したセキュアな静的Webサイトの作り方

5分強で設置できるセキュアなCloudFrontとS3の静的ウェブサイトの設定内容について紹介します。
2020.03.28

AWSチームのすずきです。

CloudFrontとS3の静的ウェブサイトを採用する利点について、諏訪より紹介させて頂きました。

今回、実際の設置に利用しているCloudFormationテンプレートの紹介と、 主に非機能要件のため実施している設定内容について、紹介させていただきます。

設定

S3(静的コンテンツ)

静的ウェブサイト(WebsiteConfiguration)を有効としたS3バケットです。

S3Bucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Retain
    Properties:
      BucketName: !Sub '${AWS::StackName}'
      LifecycleConfiguration:
        Rules:
          - Id: NoncurrentVersionExpiration
            Status: Enabled
            NoncurrentVersionExpirationInDays: 45
      LoggingConfiguration:
        DestinationBucketName: !Ref 'S3BucketAccesslogs'
        LogFilePrefix: !Sub 's3/${AWS::StackName}'
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: false
        IgnorePublicAcls: true
        RestrictPublicBuckets: false
      VersioningConfiguration:
        Status: Enabled
      WebsiteConfiguration:
        IndexDocument: index.html
        ErrorDocument: 404.html
      Tags:
        - Key: CloudFormationArn
          Value: !Sub '${AWS::StackName}'
  • 誤操作などに備え、バージョニング(VersioningConfiguration)を有効とします。
  • S3のストレージ課金の抑制のため、ライフサイクル設定で過去バージョン保持期間を抑制します。
  • 意図せぬACL設定に起因するWebコンテンツの改竄を回避するため、ブロックパブリックアクセスはアクセスコントロールリスト (ACL)に対して設定します。
  • S3アクセスログを設定し(LoggingConfiguration)、万一不正な利用があった場合でも追跡を可能にします。
  • 任意のFQDNで S3のWebホスティング用のエンドポイントをHTTPで公開しない場合、S3のバケット名は公開FQDNに揃える必要はありません。今回、CloudFormation のスタック名を利用する設定としましたが、S3バケットの命名規則の範囲内で利用しやすいものをご利用ください。

S3(アクセスログ)

S3、CloudFront のアクセスログ用のS3バケットです。

S3BucketAccesslogs:
    Type: AWS::S3::Bucket
    DeletionPolicy: Retain
    Properties:
      AccessControl: LogDeliveryWrite
      BucketName: !Sub '${AWS::StackName}-accesslogs-${AWS::Region}-${AWS::AccountId}'
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      LifecycleConfiguration:
        Rules:
          - Id: AutoDelete
            Status: Enabled
            ExpirationInDays: 15
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      Tags:
        - Key: CloudFormationArn
          Value: !Sub '${AWS::StackName}'
  • S3のアクセスログ書込のために必要なACL(LogDeliveryWrite)を設定します。
  • 保管費用の抑制のため、ライフサイクルでアクセスログの保持期間を限定します。
  • アクセスログに含まれる接続元などの情報の保護のため、追加コストがかからず、S3のログ機能に影響しない範囲で暗号化(SSE)を実施します。
  • 意図せぬ公開を防ぐため、パブリックアクセスは全て禁止(RestrictPublicBuckets)とします。

バケットポリシー

静的コンテンツ用S3の公開設定はバケットポリシーを利用します。

S3BucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref 'S3Bucket'
      PolicyDocument:
        Id: !Sub '${AWS::StackName}-BucketPolicy'
        Statement:
          - Sid: AddPerm
            Effect: Allow
            Principal: '*'
            Action:
              - s3:GetObject
            Resource:
              - !Sub 'arn:aws:s3:::${S3Bucket}/*'
            Condition:
              StringEquals:
                aws:UserAgent: Amazon CloudFront
          - Sid: AddPerm
            Effect: Allow
            Principal: '*'
            Action:
              - s3:GetObject
            Resource:
              - !Sub 'arn:aws:s3:::${S3Bucket}/*'
            Condition:
              IpAddress:
                aws:SourceIp:
                  - 127.0.0.1/32
                  #- 0.0.0.0/0
                  #- ::/0
  • UserAgentが「Amazon CloudFront」のアクセスのみを許可し、通常のWebブラウザやBotなどからのアクセスを抑止します。
  • 公開用のS3には 秘匿情報は無いものとして、UserAgentのなりすましや、第三者が設置したCloudFrontからの参照させる可能性については容認します。
  • 万一S3のAPI課金が嵩むなどの実害が生じた場合には、アクセスログの情報を元に対策を行うものとします。
  • CloudFrontのキャッシュ影響がない、コンテンツ確認用の環境が求められる場合に備え、特定拠点のIPについてはS3の直接参照を許可するものとします。

CloudFront

S3の静的ホスティングのエンドポイントをカスタムオリジンとして設定します。

CloudFrontDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Origins:
          - Id: CustomOrigin
            DomainName: !Sub '${S3Bucket}.s3-website-${AWS::Region}.amazonaws.com'
            CustomOriginConfig:
              HTTPPort: 80
              OriginProtocolPolicy: http-only
        Enabled: true
        DefaultRootObject: index.html
        Logging:
          IncludeCookies: 'false'
          Bucket: !Sub '${S3BucketAccesslogs}.s3-${AWS::Region}.amazonaws.com'
          Prefix: !Sub 'cloudfront/${AWS::StackName}'
        CustomErrorResponses:
          - ErrorCachingMinTTL: 300
            ErrorCode: 403
            ResponseCode: 200
            ResponsePagePath: /index.html
        Comment: !Sub '${AWS::StackName}-distribution'
        DefaultCacheBehavior:
          TargetOriginId: CustomOrigin
          ForwardedValues:
            QueryString: false
          DefaultTTL: 300
          MaxTTL: 300
          MinTTL: 300
          ViewerProtocolPolicy: redirect-to-https
        #Aliases:
        #- dummy.example.com
        #ViewerCertificate:
        #  SslSupportMethod: sni-only
        #  AcmCertificateArn: arn:aws:acm:us-east-1:000000000000:certificate/dummy-example-com
      Tags:
        - Key: CloudFormationArn
          Value: !Sub '${AWS::StackName}'
  • オリジンとして利用するS3、低コストで高い配信性能が期待できる事からキャッシュのTTLは長くせず(一律300秒)、コンテンツ更新後のキャッシュ消去(Invalidation)は極力実施しない設定とします。
  • HTTPを利用したアクセスは、HTTPSにリダイレクトで誘導します。(ViewerProtocolPolicy: redirect-to-https)
  • 独自ドメインで公開する場合、ACM(ViewerCertificate)、CNAME(Aliases)は別途実施するものとします。

IAM

S3を操作するWebコンテンツの管理者用、IAMロールを用意します。

S3の操作は、AWSコンソール、CyberDuckなど、多要素認証(MFA)が利用できる環境を利用するものとします。

IamRoleS3access:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub 'iam-role-${AWS::StackName}'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              AWS: !Ref 'IamUserArn'
            Action: sts:AssumeRole
            Condition:
              Bool:
                aws:MultiFactorAuthPresent: 'true'
              IpAddress:
                aws:SourceIp:
                  - 0.0.0.0/0
                  - ::/0
      Policies:
        - PolicyName: S3accessPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:ListAllMyBuckets
                  - s3:GetBucketLocation
                Resource:
                  - arn:aws:s3:::*
              - Effect: Allow
                Action:
                  - s3:*
                Resource:
                  - !Sub 'arn:aws:s3:::${S3Bucket}/*'
                  - !Sub 'arn:aws:s3:::${S3Bucket}'
              - Effect: Deny
                Action:
                  - s3:PutBucket*
                  - s3:CreateBucket
                  - s3:DeleteBucket
                  - s3:PutObjectAcl
                  - s3:PutObjectVersionAcl
                Resource:
                  - arn:aws:s3:::*
              - Effect: Allow
                Action:
                  - cloudfront:Get*
                  - cloudfront:List*
                Resource:
                  - '*'
              - Effect: Allow
                Action:
                  - cloudfront:CreateInvalidation
                Resource:
                  - !Sub 'arn:aws:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}'
  • 信頼できるログイン環境、IAMユーザを限定してロールの利用許可(sts:AssumeRole)を付与します。

  • IAMロールの利用にあたり、接続元のIPアドレス(aws:SourceIp)制限、IPv4、IPv6の範囲とも実施しない設定としました。特定拠点やVPNのIPに限定できる場合には、よりセキュアなS3利用が可能になります。

  • CloudFront のTTLは短い設定としますが、キャッシュ操作(Invalidation)の権限は付与します。

実行例

今回紹介した上記のテンプレート、CloudFormationの設置所要時間は5分42秒でした。

まとめ

S3の静的ウェブサイトを設定したS3、パブリックアクセスや、コンテンツ更新に利用するIAMを適切に管理することで、セキュアな利用を実現可能です。

CloudFront、S3の静的ウェブサイトのエンドポイント間のHTTP、暗号化されていない通信となりますが、 静的ウェブサイトのS3はユーザ情報を受取る利用はできません。

また、AWSのグローバルネットワーク内の通信が盗聴されたり、S3のエンドポイント「amazonaws.com」の名前解決結果の改竄されるなど、意図せぬオリジンが利用される可能性について起き得ないリスクとして許容できる場合、Webサーバとしての機能に優れるS3の静的ホスティングをご活用ください。