2013年2月12日火曜日

S3ってなんじゃ?(CloudFrontでアクセス制御:Origin Access Identity × 署名付きURL)

CloudFrontでアプリを通してのアクセス以外にはコンテンツを配信させたくないという場合があります。
ここでは、CDPデザインパターンに以下のようなパターンがありました。

CDP:Private Cache Distributionパターン


このパターンによると、CloudFrontに対して署名付きURLを送信することで、アクセス条件を制限することができるようです。今回はこれを使ってみたいと思います。



S3バケットの作成


まず、S3バケットをひとつ用意して適当にファイルをアップします。




ちなみにindex.htmlは以下の様な内容です。
<html>
  <head>
    <title>private</title>
  </head>
  <body>
    <h1>This is Private Contents</h1>
  </body>
</html>



CloudFrontディストリビューションの作成


次にCloudFrontでディストリビューションを追加します。「Create new Distribution」をクリックします。
すると、以下のようにディストリビューションの設定画面が表示されるので、以下のように入力します。



Origin Settings


  • Origin Domain Name:先ほど追加したS3バケットを選択
  • Origin ID:自動で設定されるので今回はこのまま
  • Restrict Bucket Access:Yes(YesにするとCloudFrontからしかS3にアクセスできないようにできます)
  • Origin Access Identity:Create a New Identity(S3にアクセスするこのCloudFrontの接続子です)
  • Comment:コメントです
  • Grant Read Permissions on Bucket:Yes(S3に対してCloudFrontからのBucketPolicyを設定します。)

Default Cache Behavior Settings


  • PathPattern:Default
  • View Protocol Policy:HTTP and HTTPS
  • Object Caching:Use Origin Cache Headers(Customizeにすると次の3項目が設定出来ます。)
  • Minimum TTL:0(最短TTL、デフォルト24時間)
  • Forward Cookies:None(キャッシュURLにCookieを含めることができます)
  • Whitelist Cookies:Forward Cookiesを有効にすると、Whitelist内のCookieだけをオリジンに転送することができます。
  • Forward Query Strings:No(YesにするとQueryParameter付きURLでキャッシュできます)
  • Restrict View Access:Yes(署名付きURLでしかアクセスできないようにします)
  • Trusted Signers:Self (Specify Accoutsにチェックを入れると別アカウントの署名も有効になります。)




Distribution Settings


ここの項目はひとまずデフォルトのままにしておきます。



ここまで設定できたら「Create Distribution」をクリックします。
すると以下のように、CloudFrontをprivateアクセスするために必要な次の手順が表示されます。


ざっくり訳すと以下のような内容です。

Step1:S3バケットへのアクセス制限

  •  ディストリビューションを新規作成したままS3の設定をいじっていないのであれば、S3バケットはCloudFrontとバケットオーナーからしかアクセスを受け付けないように正しく設定されています。

Step2:署名付きURL

  •  信頼された署名者用にCloudFrontのキーペアを作成します。
  •  署名付きURLを作成するには、コーディングするかサードパーティツールを使用する必要があります。
  •  ディストリビューションへ信頼された署名者を追加します。

どうやら自動でS3のパーミッションが変更されたようです。
S3のパーミッションを見てみます。
S3のバケットのPermissionsからEdit Bucket Policyをクリックします。
すると以下のように設定されています。

{
 "Version": "2008-10-17",
 "Id": "PolicyForCloudFrontPrivateContent",
 "Statement": [
  {
   "Sid": "1",
   "Effect": "Allow",
   "Principal": {
    "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity E366VCXL4Q6CB9"
   },
   "Action": "s3:GetObject",
   "Resource": "arn:aws:s3:::memorycraft-private/*"
  }
 ]
}

Cloud Front Origin Access Identityとして設定されています。


また、作成されたディストリビューションを確認すると、以下のように無事登録されているのがわかります。
ディストリビューションのホスト名は
dapubd7a26puj.cloudfront.net
となっています。



この段階でブラウザでアクセスすると以下のようになります。


これは、すでに署名付きURLしか受け付けないため、通常のURLではエラーになるためです。



署名付きURLの作成


次に署名付きURLを作成してみたいと思います。

まず、署名をするためのCloudFront用のキーペアを取得します。
AWSの証明書のページで「一対の鍵」のタブをクリックします。


CloudFrontの一対の鍵という項目があるので、「新しい一対の鍵を作成する」をクリックします。


作成完了のモーダルウィンドウが表示されるので、確認して閉じます。


すると、「一対の鍵」にCloudFront用のキーペアが追加されているのがわかります。
「一対の鍵ID」というキーペアIDが表示され、下にダウンロードリンクが現れます。


「(公開鍵をダウンロード)」をクリックすると、rsa-キーペアID.pem, pk-キーペアID.pemというファイルがダウンロードされます。


どこか適当な環境にSDKをダウンロードします。
ここではPHPのSDKを利用します。
また、keysというディレクトリを作りpk-キーペアID.pemを配置します。
$tree -L 2 .

.
├── app
│   ├── key.php
│   ├── sdk -> sdk-1.6.0
│   ├── sdk-1.6.0
│   └── sdk-latest.zip
└── keys
    └── pk-キーペアID.pem


そして、以下のようにAmazonCloudFrontクラスを使って署名付きURLを生成します。
cat key.php

<?php
require_once('sdk/sdk.class.php');
date_default_timezone_set('Asia/Tokyo');

//CloudFrontクラスを初期化
$cf = new AmazonCloudFront(array('key'=>'通常のAWSアクセスキー', 'secret'=>'通常のAWSシークレットキー'));

//キーペアIDをセット
$cf->set_keypair_id('CloudFrontのキーペアID');

//配置した鍵ファイルの中身をセット
$cf->set_private_key(file_get_contents(dirname(__FILE__).'/../keys/pk-キーペアID.pem'));

//index.htmlのURLを期限付きで取得します(expireはstrtotimeが解釈できる文字列かUnixTimeならOK)
$url = $cf->get_private_object_url('dapubd7a26puj.cloudfront.net', 'index.html', strtotime('+5 minutes'));
echo $url;


確認


これを実行します。
# php app/key.php

http://dapubd7a26puj.cloudfront.net/index.html?Expires=1360671363&Key-Pair-Id=APKAIGNAXKEEXGUYEZ5A&Signature=UQantZ1gIp5-mm6d6Lb-HKQr1jxKDRW2NfFyC-E2dDx33ekBbFjLKmO6vHCOhv7liBrfcaTFc7~yKZCd4P1tfZE4TltU6TilhnAUFT6mCC-Db-dmnrU7XbcWSjt29~yOVVhLTv5RoPtIjoW~iPFR2BTyoM~4vlV40y9ypbO5M1U_


URLが出力されました。
これをつかってアクセスしてみます。




表示されました!

先ほどのコードでは5分後がアクセス期限でした。
5分以上経過してからアクセスすると、、、



ちゃんとアクセス拒否になりました。


おまけ


ユーザーがアクセスするごとにURLの期限を設定したい場合があります。
その場合は、以下のように元のURLにクエリーパラメータを付与してあげるといいかもしれません。
$url = $cf->get_private_object_url('dapubd7a26puj.cloudfront.net', 'index.html?u='.session_id(), strtotime('+5 minutes'));


以上の手順の多くはAWSのAPI経由でも設定可能です。必要に応じてプログラムで設定てもよいかもしれません。
また、署名の仕方や署名ポリシーにはいくつかパターンがあるため、機会があればもっといろいろな方法を試してみたいと思います。