1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

この記事誰得? 私しか得しないニッチな技術で記事投稿!
Qiita Engineer Festa20242024年7月17日まで開催中!

CloudFrontで配信しているS3に置かれた画像を特定サイトからしか読み込めないようにする

Posted at

はじめに

S3に置かれた画像をCloudFrontで配信するにあたって、画像を読み込めるサイトに制限をかけたいと考えました。
その特定サイトは検索にはヒットせず、しかしIP制限などはかけていないものとします。
かといってAWS lambdaなどを間に挟んでゴニョゴニョするような面倒はかけたくないため、ある程度のガバさは許容するが、特に何の工夫もなしに画像を引っ張ってこれるようにはしたくない。
そんな仕様を叶える手段の一つとしてRefererというヘッダーを見るというのがあります。
Refererヘッダーにはリクエストされているページへのリンク先を持った直前のウェブページのアドレスが含まれています。
つまり画像へのアクセス元ページのアドレスがわかるわけです。
ヘッダーなんてのは簡単に書き換えられてしまうため、ザルではありますが、それでも何の工夫もなしに画像を引っ張ってこれなくはなります。
ヘッダーを見てリクエストを弾いたりするのはWAFでできます。

よって本記事ではRefererヘッダーに特定サイトのURLが含まれている場合のみ、CloudFrontを通してS3にある画像を取ってこれるようにする仕組みをCloudFormationで作成します。

CFn

WAFとS3+CloudFrontそれぞれ分けて作成します。
理由は下記記事にある通り、CloudFront用のWebACLを作成するにはus-east-1 (バージニア北部) に作成する必要があるためです。

【小ネタ】AWS WAF v2 の WebACL (CloudFront用)を東京リージョンから CloudFormation で作成しようとしたら怒られた

WebACL

とりあえず https://example.com からのアクセスであれば通すようにしています。
複数通したければAWS::WAFv2::WebACL OrStatementで複数指定します。

AWS::WAFv2::WebACL DefaultActionでデフォルトのアクションを設定できるのですが、ここをデフォルトAllowにしてしまうと特定箇所以外からも許可されます。
私は、AWS::WAFv2::WebACL RuleActionを特定サイトからのアクセスは通すからとAllowにして、デフォルトアクションもAllowにしていたのを見逃していたため、どこからでもAllowになってしまい時間を溶かしました。

AWSTemplateFormatVersion: "2010-09-09"

Resources:
  RefererWebACL:
    Type: AWS::WAFv2::WebACL
    Properties:
      Scope: CLOUDFRONT
      Name: WafCloudFrontForImageDelivery
      DefaultAction:
        Block: {}
      VisibilityConfig:
        SampledRequestsEnabled: true
        CloudWatchMetricsEnabled: true
        MetricName: AllowSpecificReferrersFoImageDelivery

      Rules:
        - Name: AllowSpecificReferrersRule
          Priority: 0
          Action:
            Allow: {}
          Statement:
            ByteMatchStatement:
              SearchString: "https://example.com"
              FieldToMatch:
                SingleHeader:
                  Name: "Referer"
              TextTransformations:
                - Priority: 0
                  Type: NONE
              PositionalConstraint: "CONTAINS"
          VisibilityConfig:
            SampledRequestsEnabled: true
            CloudWatchMetricsEnabled: true
            MetricName: AllowSpecificReferrersRuleMetricForImageDelivery

作成します。
regionに注意。

aws cloudformation create-stack --stack-name web-acl --template-body file://web-acl.yaml --region us-east-1

S3, CloudFront

これでS3とCloudFrontがいい感じに作成され、CloudFrontには上で作成したWAFが付いてくれます。
WAFは別で作成しているので、WebAclのARNをパラメータで与えてやる必要があるので注意が必要です。

AWS::CloudFront::Distribution DefaultCacheBehavior CachePolicyIdにはマネージドキャッシュポリシーのCachingOptimizedのポリシーIDを指定しています。

AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  WebAclARN:
    Description: WAF v2 ARN
    Type: String

Resources:
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: "いい感じの一意な名前"
      AccessControl: Private
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256

  CloudFrontOriginAccessIdentity:
    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
    Properties:
      CloudFrontOriginAccessIdentityConfig:
        Comment: "Access identity for S3 bucket"

  S3BucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref S3Bucket
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Sid: AllowCloudFrontServicePrincipalReadOnly
            Effect: Allow
            Action: s3:GetObject
            Resource: !Join [ "/", [ !GetAtt S3Bucket.Arn, "*" ] ]
            Principal:
              AWS: !Sub arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${CloudFrontOriginAccessIdentity}

  CloudFrontDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Enabled: true
        Origins:
          - Id: S3Origin
            DomainName: !GetAtt S3Bucket.DomainName
            S3OriginConfig:
              OriginAccessIdentity: !Sub origin-access-identity/cloudfront/${CloudFrontOriginAccessIdentity}
        HttpVersion: http2and3
        DefaultCacheBehavior:
          TargetOriginId: S3Origin
          ViewerProtocolPolicy: allow-all
          AllowedMethods:
            - GET
            - HEAD
          Compress: true
          CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6
        WebACLId: !Ref WebAclARN

パラメータをいちいちコマンドで突っ込むのが面倒なのでparam.jsonを作ります。

[
  {
    "ParameterKey": "WebAclARN",
    "ParameterValue": "%WebAclのARN%"
  }
]

param.jsonを注入してstackを作成します。

aws cloudformation create-stack --stack-name s3-cloudfront --template-body file://s3-cloudfront.yaml --parameters=file://./params/s3-cloudfront.param.json

確認

できたと思うので確認します。

$ curl -I -L https://xxx.cloudfront.net/path/to/image

HTTP/2 403
server: CloudFront
content-type: text/html
x-cache: Error from cloudfront

$ curl -I -L -H 'Referer:https://qiita.com'  https://xxx.cloudfront.net/path/to/image
HTTP/2 403
server: CloudFront
content-type: text/html
x-cache: Error from cloudfront

ブロックはできてそう。
正常系を試します。

curl -I -L -H 'Referer:https://example.com' https://xxx.cloudfront.net/path/to/image
HTTP/2 307
content-type: application/xml
location: https://s3バケット名.s3-ap-northeast-1.amazonaws.com/path/to/image
x-amz-bucket-region: ap-northeast-1
server: AmazonS3
x-cache: Miss from cloudfront

HTTP/1.1 403 Forbidden
Server: AmazonS3

!?!?
リダイレクトされてる!?

わからんので「CloudFront」と「307」をキーワードにして検索します。
ありました。

S3+CloudFrontでS3のURLにリダイレクトされてしまう場合の対処法

S3をバージニア北部(us-east-1)以外で作成した場合、24時間以内のアクセスだとリダイレクトが発生するようですね。
待ちます。

待ちました。
もう一度叩きます。

$ curl -I -L -H 'Referer:https://example.com' https://xxx.cloudfront.net/path/to/image

HTTP/2 200
content-type: image/png
x-amz-server-side-encryption: AES256
server: AmazonS3
x-cache: Miss from cloudfront

取ってこれてる!

https://example.com で開発者ツールを使ってDOMを追加して、取れているかを確認します。

サーバルちゃん

やったぜ!

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?