CloudFrontの特定UserAgentをチェックするS3の静的ウェブサイトホスティング

静的ウェブサイトホスティング用に設定したAmazon S3、OAI(Origin Access Identity) の代りに UserAgentを利用した CloudFrontのアクセス制限を試してみました。
2021.02.28

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

AWSチームのすずきです。

静的ウェブサイトホスティング用に設定したAmazon S3、 バケットポリシーでUserAgent判定を行い、特定のCloudFront以外からのアクセスを制限する機会がありましたので、紹介させていただきます。

設定

CloudFront

オリジン設定で、CloudFront標準のUserAgentに任意の文字列を追加する指定を行いました。

  • HeadersHeader : User-Agent
  • NameValue : Amazon CloudFront <任意文字列>

オリジンリクエストへのカスタムヘッダーの追加

S3

S3バケットポリシーとして、任意の文字列が付与された CloudFrontのみアクセス(GetObject)を許可する指定を行いました。

{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::<S3バケット>/*",
            "Condition": {
                "StringEquals": {
                    "aws:UserAgent": "Amazon CloudFront <任意文字列>"
                }
            }
        }
    ]
}

動作確認

参照可能

  • UserAgentに特定文字を設定したCloudFrontは参照可能です。

参照不可

  • S3の直接参照は403 Forbiddenとなりました

  • UserAgentがデフォルトのCloudFront経由のアクセスは 403 Forbiddenとなりました。

まとめ

S3のウェブサイトホスティングを採用したシンプルなWebサイトだが、 開発中のコンテンツが第三者に参照される事は望ましくないため、CloudFrontへのアクセスは、AWS WAFや、Lambda@Edge などで 実施する場合、 オリジンのS3のアクセス制限する手段として、今回の設定をお試しください。

尚、S3の静的ウェブサイトホスティングをCloudFrontのカスタムオリジンで利用する場合、通信プロトコルはHTTPとなります。 CloudFront、S3間の通信はAWS内で完結する事が期待できますが、全てのグローバルIPを利用した通信で暗号化通信が必要な場合、S3オリジン+OAIでの利用をお勧めします。

CloudFormation

再現用のCloudFormationテンプレートです。

AWSTemplateFormatVersion: '2010-09-09'
Description: S3 web hosting with access settings limited to CloudFront
Parameters:
  UseragentToken:
    Description: Token to add to user agent
    Type: String
    Default: hogehoge
Resources:
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub '${AWS::StackName}-${AWS::Region}-${AWS::AccountId}'
      WebsiteConfiguration:
        IndexDocument: index.html
        ErrorDocument: error.html
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: false
        IgnorePublicAcls: true
        RestrictPublicBuckets: false
  S3BucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref 'S3Bucket'
      PolicyDocument:
        Statement:
          - Action: s3:GetObject
            Effect: Allow
            Resource:
              - !Sub 'arn:aws:s3:::${S3Bucket}/*'
            Principal: '*'
            Condition:
              StringEquals:
                aws:UserAgent: !Sub 'Amazon CloudFront ${UseragentToken}'
  CloudFrontDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Origins:
          - Id: CustumOrigin
            DomainName: !Sub '${AWS::StackName}-${AWS::Region}-${AWS::AccountId}.s3-website-${AWS::Region}.amazonaws.com'
            CustomOriginConfig:
              HTTPPort: 80
              OriginProtocolPolicy: http-only
            OriginCustomHeaders: 
              - HeaderName: User-Agent
                HeaderValue: !Sub 'Amazon CloudFront ${UseragentToken}'
        Enabled: 'true'
        DefaultRootObject: index.html
        DefaultCacheBehavior:
          TargetOriginId: CustumOrigin
          ForwardedValues:
            QueryString: false
          ViewerProtocolPolicy: redirect-to-https
          DefaultTTL: 300
          MaxTTL: 300
          MinTTL: 300