CloudFront で カスタムポリシーを使用した署名付き Cookie を試してみる

## はじめに
CloudFront の 署名付き Cookie 機能に詳しくなる必要があり、ちょっとした動作環境を ruby でつくったのでシェアします。
やりたいことは、CloudFront の Origin として S3 を指定、S3上に index.htmlファイルを配置し、アクセス制限として署名付きの Cookie を利用するという構成。

## 前提
以下については、ここでは説明しません。CloudFront Distribution の作成、Origin Access Identity 等は設定済みとします。まずはアクセス制限無しの状態でCloudFront経由でS3上のhtmlファイルが閲覧できることを確認してから、アクセス制限の設定をすると問題のあったときの切り分けがラクかとおもいます。

* CloudFrontとは
* Origin Access Identityの設定
    * S3上のObjectを非公開の状態のまま、特定のCloudFront Distributionからのみアクセス可能にする設定。詳しくは[ここ](http://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html)。
* CloudFront キーペアの作成
    * ここ[参照](http://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html#private-content-rotating-key-pairs)。

全体的にクラスメソッドさんの[記事](http://dev.classmethod.jp/cloud/aws/cf-s3-private-content-signed-cookies/)が参考になります。

## 最低限押さえておくべきポイント
詳細の説明は公式ドキュメントに譲るとして、ここでは最低限押さえておくべきポイントだけ説明します。
### **Policy**
一言でいうと、制限のかけ方に関するルール。JSON形式で記述。制限対象コンテンツ(Resource)、制限期間(DateLessThan)等を定義します。既定ポリシーとカスタムポリシーの２つがあります。それぞれの違いは、[ドキュメント](http://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/private-content-signed-cookies.html)を参照ください。ここでは、カスタムポリシーのやり方を説明します。こちらのほうが自由度が高いので、カスタムポリシーを理解すれば既定ポリシーについては簡単に理解可能でしょう。余裕があれば試してください。

```lang:sample
RESOURCE = "http*://xxxx.cloudfront.net/index.html"
 
start_time = (Time.now - 60).to_i
expire_time = (Time.now + 60*60*24*10).to_i
 
condition = { "DateLessThan" => {"AWS:EpochTime" => expire_time }, "DateGreaterThan" => {"AWS:EpochTime" => start_time } }
 
policy = { "Statement" => ["Resource" => RESOURCE, "Condition" => condition] }
```
### **Signature**
これについては、まずはそのまま[ドキュメント](http://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/private-content-linux-openssl.html)を抜粋します。
> 次の Linux コマンドラインのコマンドおよび OpenSSL を使用して、ポリシーステートメントをハッシュ化して署名します。次に、署名を base64 エンコードし、URL クエリ文字列パラメータでの無効な文字を有効な文字置き換えます。
>
> ```
> cat policy | openssl sha1 -sign private-key.pem | openssl base64 | tr '+=/' '-_~'
> ```

要は、作成した Policy を SHA1 でハッシュ化して CloudFrontの Key Pair の Private Key で署名をし、さらに Base64 で Encode して、最後に一部文字列(URL クエリ文字列パラメータでの無効な文字)を置換したものです。CloudFront側は request 処理時にこれを受け取って、Pair となっている Public Key で署名を Verify してるはず。

```lang:sample
signature = `printf %s '#{policy}' | openssl sha1 -sign #{PRIVATE_KEY} | openssl base64 | tr '+=/' '-_~'`
```

### **Cookie**
以上で署名付きCookieを設定するための最低限の準備ができました。Cookieに設定できる項目については[ドキュメント](http://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/private-content-setting-signed-cookie-custom-policy.html)を参照ください。

```lang:sample
header = "Cookie:CloudFront-Expires=#{expire_time}; CloudFront-Policy=#{encoded_policy}; CloudFront-Signature=#{signature}; CloudFront-Key-Pair-Id=#{KEY_PAIR_ID}"
```

## コード
最後に、動作確認するためのコードを示します。内容は、

- Policy作成
- Signature作成
- Cookie情報設定
- GET Requestを発行して response/request headerをコンソールに表示する

です。CloudFrontのKey Pair情報(KEY_PAIR_ID, PRIVATE_KEY)、コンテンツのURL(DST_URL)、Policyで定義するResource(RESOURCE)を適宜、修正すれば、動くはずです。Status Code が 403 となる場合は、Key Pair情報、DST_URLとRESOURCEの関係を見直しましょう。

Amazon Linux, Mac OSX (ruby 2.2,2.0) で動作確認済みです。2つありますが、Base64 Encoding, Signatureのところの書き方がちがうだけで、基本的には一緒です。Systemのコマンドをベタで呼ぶか、Rubyのライブラリ経由で呼ぶかの違いくらいです（たぶん、裏では同じAPIコール、処理をしてるはず）。
https://gist.github.com/t11a/06965f6aa87569f78b3a
https://gist.github.com/t11a/1bb0dcef2bcf71ab470d