CloudFrontの署名付きCookieでプライベートコンテンツの配信

2021.01.17

署名付きCookieはCloudFrontで配信するコンテンツをプライベートなコンテンツとして配信するための機能です。よく似た機能で署名付きURLもあります。今回は署名付きCookieでコンテンツの制限とアクセス検証します。

  • 信頼されたキーグループのキーペアの作成
  • CloudFront + S3でコンテンツの配信準備
  • コンテンツを署名付きCookieで制限しプライベートコンテンツとして配信

CloudFrontの署名付きCookieを使ってプライベートコンテンツの配信を試します。以前はrootユーザでCloudFrontのキーペアを作成していました。現在は非推奨であり、通常のIAMユーザでCloudFrontのキーペアを作成する方法が推奨されています。ということで、推奨方法でキーペアを作成するところからはじめます。

信頼されたキーグループのキーペアの作成

署名者のキーペア作成

下記ドキュメントを参考にすすめます。

署名付き URL と署名付き Cookie を作成できる署名者の指定 - Amazon CloudFront

ローカル端末でキーペア作成。

$ openssl genrsa -out private_key.pem 2048
$ openssl rsa -pubout -in private_key.pem -out public_key.pem

公開鍵と秘密鍵のキーペアが作成されました。

$ ll
total 16
-rw-r--r--  1 ohmura.yasutaka  staff   1.6K  1 15 22:04 private_key.pem
-rw-r--r--  1 ohmura.yasutaka  staff   451B  1 15 22:04 public_key.pem

公開鍵のアップロード

CloudFrontを開きます。

Add public keyをクリック

任意のKey nameを設定。Key valuepublic_key.pemの内容をコピペ。

登録されました。ここのID後半の署名付きCookie作成時必要になります。後で使うためIDを控えておくかPublic keys画面で確認できることを覚えておいてください。

CloudFront + S3でコンテンツの配信準備

S3バケット作成

配信したいファイルは網走市のPR素材をアップロード。4.4MBのPDFファイルです。

網走PR素材|網走市組織一覧|網走市

CloudFrontで配信までの流れは下記を参考。S3バケット直アクセスからオブジェクト参照のテストはしないため、S3バケットはパブリックアクセスをすべてブロックにしています。

独自ドメインのCloudFrontでPDFを配信できるようになりました。

署名付きCookieで制限

下記記事を参考にすすめていきます。

CloudFrontから対象のDestributionを選択し、BehaviorsタブからCreate Behaviorをクリック。

Path Patternは.pdfのファイルを対象に設定してみます。

Path Patternの書式は下記サイトを参考にしてください。

ディストリビューションを作成または更新する場合に指定する値 - Amazon CloudFront

Restrict Viewer AccessYesに変更。推奨のTrusted Key Groupsを使いますがキーグループ未作成なのでここから作成します。

Key group nameを入力。Public keysに準備作業で作成した公開鍵を選択しAddをクリックすると下の欄に表示されます。

CloudFrontの設定画面に戻ります。キーグループを作成したので更新ボタンを押し、キーグループ名を確認。Addをクリックすると下の欄に表示されます。

独自ドメインのCloudFrontで配信していたPDFファイルがMissingKeyのエラーで見れなくなりました。

比較のためS3バケットに.jpgの画像をアップロードしました。こちらは.pdfファイルではないので表示されます。

署名付きCookieでアクセス

ポリシーを複数のオブジェクトへの使用するためカスタムポリシーを作成

カスタムポリシーを使用する署名付き Cookie の設定 - Amazon CloudFront

DateLessThanは有効期限の設定です。EpocTimeは下記サイトで変換し24時間後の値を設定しました。

Unixtime相互変換ツール | konisimple tool

{
    "Statement":
    [
        {
            "Resource":"https://test.[your.domain]/*",
            "Condition":
            {
                "DateLessThan":
                {
                    "AWS:EpochTime":1610808495
                }
            }
        }
    ]
}

インデント、改行を削除する必要があります。下記サイトを利用してサクッと整形しpolicy.jsonとして保存しました。

改行・空白・タブ削除ツール|ちょっと便利なツール・ジェネレーター置き場

policy.json

{"Statement":[{"Resource":"https://test.[your.domain]/*","Condition":{"DateLessThan":{"AWS:EpochTime":1610808495}}}]}

これからBase64 エンコードした文字列署名された Base64 エンコードした文字列を生成します。この生成した文字列をCookieにセットして利用します。

必要なファイル

  • カスタムポリシーの保存したpolicy.jsonファイル
  • 署名者のキーペアで作成した秘密鍵private_key.pemファイル
  1. Base64 エンコードしたポリシー
    $ cat policy.json | openssl base64 | tr '+=/' '-_~'
    eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiaHR0cHM6Ly90ZXN0LmFiYXNoaXJp
    LmNpdHkvKiIsIkNvbmRpdGlvbiI6eyJEYXRlTGVzc1RoYW4iOnsiQVdTOkVwb2No
    VGltZSI6MTYxMDgwODQ5NX19fV19Cg__

  2. 署名された Base64 エンコードしたポリシー

    $ cat policy.json | openssl sha1 -sign private_key.pem | openssl base64 | tr '+=/' '-_~'
    4s0aPb9BgVH91kDlJPeEnmepd4fIv5zJ45qWis9ng5joUcn4gf4H~NQL9-btqpq1
    OxHqSTV4gVAa6E9mjSpeZRc12YJtPM~HoHZL4Xwee6uG7qEeYF3bWoUoyyV-Ng2K
    kSh8SAKmni8OHJt1Tpj-KEyfLrKxXDCTLcmDvkw~h9vrQL5MBxpDirXQYSjNJ6nE
    pr2EyweNQe89t4vFmBP3b6wOdfYrG1N3RE1o3MgozslXyoa2aqkr5j2xGWti8ZEC
    goljz-ytem5Kb45mncZC10ugRT7mJuRujt~FDnKZC~12hTKZv-haMGfZUVZZLceo
    VSP2I2mz6euO5HT6o9awlA__

署名付きCookieでアクセス

  • CloudFront-Policy : 1.Base64 エンコードしたポリシーの文字列
  • CloudFront-Signature : 2.署名された Base64 エンコードしたポリシーの文字列
  • CloudFront-Key-Pair-Id : 署名者のキーペアで作成した公開鍵をマネージドコンソールから登録したときのID

対象がPDFファイルなので--outputオプションで保存します。

curl -H 'Cookie:CloudFront-Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiaHR0cHM6Ly90ZXN0LmFiYXNoaXJpLmNpdHkvKiIsIkNvbmRpdGlvbiI6eyJEYXRlTGVzc1RoYW4iOnsiQVdTOkVwb2NoVGltZSI6MTYxMDgwODQ5NX19fV19Cg__;CloudFront-Signature=4s0aPb9BgVH91kDlJPeEnmepd4fIv5zJ45qWis9ng5joUcn4gf4H~NQL9-btqpq1OxHqSTV4gVAa6E9mjSpeZRc12YJtPM~HoHZL4Xwee6uG7qEeYF3bWoUoyyV-Ng2KkSh8SAKmni8OHJt1Tpj-KEyfLrKxXDCTLcmDvkw~h9vrQL5MBxpDirXQYSjNJ6nEpr2EyweNQe89t4vFmBP3b6wOdfYrG1N3RE1o3MgozslXyoa2aqkr5j2xGWti8ZECgoljz-ytem5Kb45mncZC10ugRT7mJuRujt~FDnKZC~12hTKZv-haMGfZUVZZLceoVSP2I2mz6euO5HT6o9awlA__; CloudFront-Key-Pair-Id=K123W2KZ8W70KB' https://test.[your.domain]/R2-ki.pdf --output abashiri.pdf

出力結果

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 4466k  100 4466k    0     0  5151k      0 --:--:-- --:--:-- --:--:-- 5151k

S3アップロードした4.4MBのPDFファイルをCloudFrontの署名付きCookieの制限付きでダウンロードできたことを確認できました。

$ ls -lh
total 10392
-rw-r--r--@ 1 ohmura.yasutaka  staff   4.4M  1 16 22:35 abashiri.pdf
-rw-r--r--  1 ohmura.yasutaka  staff   122B  1 16 12:21 policy.json
-rw-r--r--  1 ohmura.yasutaka  staff   1.6K  1 15 22:04 private_key.pem
-rw-r--r--  1 ohmura.yasutaka  staff   451B  1 15 22:04 public_key.pem

Cookieなしでアクセスした場合はWEBブラウザでアクセスしたとき同様にMissingKeyのエラーでPDFファイルのダウンロードできませんでした。プライベートコンテンツとして正しく機能しています。

$ curl https://test.[your.domain]/R2-ki.pdf --output no-cookie-abashiri.pdf
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   146  100   146    0     0    784      0 --:--:-- --:--:-- --:--:--   784
$ cat no-cookie-abashiri.pdf
<?xml version="1.0" encoding="UTF-8"?><Error><Code>MissingKey</Code><Message>Missing Key-Pair-Id query parameter or cookie value</Message></Error>bash-

補足

カスタムポリシーの仕様

署名付きCookieでS3バケットの配下のa-folderと、b-folderアクセス可能で、c-folderにアクセスはさせたくないといった場合を例に説明します。

private-bucket  # S3バケット名
├── a-folder # Aフォルダ
│   ├── a1.pdf
│   └── a2.jpg
├── b-folder # Bフォルダ
│   └── b1.pdf
└── c-folder # Cフォルダ
     ├── c1.pdf
     └── c2.pdf

カスタムポリシーのResourceは複数パス指定をサポートされていません。

以下、公式ドキュメントより

You can specify only one value for Resource.

Setting signed cookies using a custom policy - Amazon CloudFront

a-folderと、b-folderを署名付きCookieで許可したいからといって下記のようにResourceを複数書けないということです。

NG.json

{
    "Statement":
    [
        {
            "Resource":"https://test.[your.domain]/a-folder/*",
            "Condition":
            {
                "DateLessThan":
                {
                    "AWS:EpochTime":1610949489
                }
            }
        },
        {
            "Resource":"https://test.[your.domain]/b-folder/*",
            "Condition":
            {
                "DateLessThan":
                {
                    "AWS:EpochTime":1610949489
                }
            }
        }
    ]
}

CookieのPath指定で個々のフォルダに対する署名付きQookieを発行し使い分けするか、S3バケットのフォルダ構造を見直した方がよろしいかもしれません。カスタムポリシーの仕様を確認しご検討いただければと思います。

amazon web services - Multiple Policy Statements for CloudFront Custom Policy (Signed Cookies) - Stack Overflow

おわりに

アプリ書く人間ではないので具体的にどう実装するのかはイメージできないのですが、署名付きCookieでプライベートコンテンツを配信する手法については理解が深まりました。curlの引数が余りにも見づらいので変数でスッキリさせたかったです。 変数に入れるまでは良かったのですが引数内でうまく展開できず断念しました。

$ POLICY=$(cat policy.json | openssl base64 | tr '+=/' '-_~')
$ SIGNED_POLICY=$(cat policy.json | openssl sha1 -sign private_key.pem | openssl base64 | tr '+=/' '-_~')
$ PUBKEY_ID='K123W2KZ8W70KB'

参考

CloudFront の署名付き Cookie によるプライベートコンテンツ配信でワイルドカードを使用する | Developers.IO