share facebook facebook2 twitter menu hatena pocket slack

2015.01.19 MON

Amazon S3+CloudFrontで期限付きURL

小谷松 丈樹

WRITTEN BY小谷松 丈樹

はじめまして、cloudpack小谷松 です。

期限付きURL

今回の要件としては、URLを時限式でexpireさせる事で、URLを叩けばいくらでもアクセス出来るのを防ぐ事を目的としています。

AWS構成

事前準備

CloudFrontのOriginをS3に向けてあり、S3とCloudFrontのURLどちらを叩いても同じ内容が表示されている状態にしてあります。

テスト用に、Windows 7に標準で入ってる画像をアップロードしてあるので、アクセスしてみましょう。
Amazon S3+CloudFrontで期限付きURL: 事前準備
どちらも同じ内容が表示されます。

実装

構想

期限付きのURLをEC2から発行し、CloudFrontを経由してS3からコンテンツを取得し配信する。CDP:Private Cache Distributionパターンというやつらしいです。

CloudFront、S3の設定変更

今はただアクセスするだけで表示されてしまうので、設定を変更し、署名付きのURLでないとアクセスが出来ないように、AWSコンソールから各設定の変更を行います。

CloudFront

まずはCloudFrontの Originsタブ から、基本設定を変更します。
Amazon S3+CloudFrontで期限付きURL: CloudFrontの設定(1)

  • Restrict Bucket Access
    • YesにするとS3に対するアクセス制限をかけられる
  • Origin Access Identity
    • CloudFrontがS3にアクセスするために必要なID Yesにすると新規作成
  • Grant Read Permissions on Bucket
    • YesにするとS3の設定(Bucket Policy)をCloudFrontから自動で書き換える

次に Behaviorsタブ から、動作設定を変更します。
Amazon S3+CloudFrontで期限付きURL: CloudFrontの設定(2)

この一番下の、 Restrict Viewer Access(Use Signed URLs) が今回のキモとなる設定です。
これを「Yes」にすると、新しい設定項目が出てきます。
Amazon S3+CloudFrontで期限付きURL: CloudFrontの設定(3)

Trusted Signers
説明:Choose whether you want to use the current AWS account and/or other AWS accounts to create signed URLs.

うーん。。。Specify Accountsに変更するシーンってどんなのだろう?
大規模で他社が絡む開発とか?何かしらの政治的な理由?うーむ・・・
とりあえず、同一アカウントから署名付きURLを発行するので、Selfとして設定完了です。

反映されるまで、少し時間がかかるようです。CloudFrontの表示を更新して、署名キーが無いため表示されない事を確認。
Amazon S3+CloudFrontで期限付きURL: CloudFrontの設定の確認

CloudFrontの方はこれで設定完了です。

S3

S3の方はまだ見られる状態なので、いくらCloudFront経由のアクセスに制限がかけられても、S3に直でアクセスされたら普通に表示されてしまうためアウトです。

バケット名を右クリックして Properties を開いて、Edit bucket policy で設定を変更します。
Amazon S3+CloudFrontで期限付きURL: S3の設定(1)

すると Bucket Policy Editor が開かれて、JSON形式の設定情報が表示されます。

{
    "Version": "2008-10-17",
    "Id": "Policy1391746695837",
    "Statement": [
        {
            "Sid": "Stmt1391746693957",
            "Effect": "Allow",
            "Principal": {
                "AWS": "*"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::s3-********.cloudpack.jp/*"
        },
        {
            "Sid": "2",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity E************8"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::s3-********.cloudpack.jp/*"
        }
    ]
}

Statement の14行目以下が、CloudFrontの設定変更により自動で付け加えられた設定でしょう。おそらく上位優先でアクセスの許可が下りてしまい、まだ表示が可能のようですね。
という事で、マークが付いてる5〜13行目の設定を消して設定をすると・・・
Amazon S3+CloudFrontで期限付きURL: S3の設定の確認

403 Forbidden。これで普通にアクセスするだけで表示する事は出来なくなりました。

CloudFront 暗号化鍵

署名付きURLを作成するために、CloudFront用のキーペアを取得する必要があります。
Amazon S3+CloudFrontで期限付きURL: CloudFront暗号化鍵の設定(1)

アカウントのプルダウンメニューから Security Credentials をクリックして、CloudFront Key Pairs のメニューから、Create New Key Pair をクリック。
Amazon S3+CloudFrontで期限付きURL: CloudFront暗号化鍵の設定(2)

秘密鍵(pk-xxxx.pem)、公開鍵(rsa-xxxx.pem)共にダウンロードしておきましょう。公開鍵は別にダウンロードしなくてもいいのかな?以後登場しません。
で、秘密鍵はEC2内のどこかに適当に配置しておきましょう。

開発

さて、では準備が完了したので SDKの説明書き を参考に開発を進めます。といっても、署名付きURLを作る、というための項目が既に用意されていますね。
Signing CloudFront URLs for Private Distributions を見て、下記の通り10行足らずで実装可能でした。

<?php
require_once('/path/to/sdk/vendor/autoload.php');
use AwsCloudFrontCloudFrontClient;

//CloudFront接続情報
$_cf_url = 'dxxxxxxxxxxxxh.cloudfront.net';
$_cf_accesskey_id = 'AxxxxxxxxxxxxxxxxxxQ';

//CloudFrontクラスを初期化
$cloudFront = CloudFrontClient::factory(array(
    'private_key' => '/path/to/keys/pk-'.$_cf_accesskey_id.'.pem',
    'key_pair_id' => $_cf_accesskey_id,
));

//期限付きURL作成
$signedUrlCannedPolicy = $cloudFront->getSignedUrl(array(
    'url'       => 'http://'.$_cf_url . '/' . 'Tulips.jpg?u='.uniqid(),
    'expires'   => strtotime('+1 minutes'),
));

echo $signedUrlCannedPolicy;

ソースのポイントとしては、

  • $_cf_accesskey_id が、ダウンロード直後の秘密鍵ファイル名とキーペアIDと一緒だから、ダウンロードしたのをそのまま任意のディレクトリに置けば良い
  • 期限付きURLにランダムな値をパラメータとして付与する事で、実行の度に別のURLを作成出来る
  • 動画をストリーミングで取得する場合は、$cloudFront->getSignedUrl() に渡すURLのプロトコルを 'rtmp://' とすれば良い

という感じでしょうか。

実行

先のソースを key.php として保存して実行。

$ php key.php
http://dxxxxxxxxxxxxh.cloudfront.net/Tulips.jpg?u=54b8b43a36d99&Expires=1421390966&Signature=Sr~Acz4PNzDrYjnys1xKsMCo-Q94jj3BkkzSyCqAR8p4P4qHfaJ9M3knlUk0l9WdnM2FFO5r2vqapLIbFqmwP8yUznXufPD15aCOdsf~aJS61YV4Y5DMVJaKfX1YBJJpohfXNYSZWGyNPDKFtEn3jWzgQvAPo283W41eh7DYxvD-s-XqBQp327ejcashvdJnAwWnSLVTAjvJViZafbJ-20yHpTq2nc-FFYxsQazkL47X7mQCIIvl8gFPBAoBma8RH~uP-GiPqBUdTFpX51AFCX1h2xOVFI5R9ZY-lISPTbw0E~EG-GlmpC~l0-fTGA677g4xTnNMiAhx51QyeD16sg__&Key-Pair-Id=AxxxxxxxxxxxxxxxxxxQ

なっがーいURLが出力されました。
上記が署名&期限付きのURLとなります。
では早速アクセスしてみましょう。
Amazon S3+CloudFrontで期限付きURL: 動作確認
表示されました!

では、先のソースでは strtotime('+1 minutes') として、1分間の期限を設けていたので、1分待ってもう一度アクセスしてみましょう。
Amazon S3+CloudFrontで期限付きURL: 動作確認(2)
うんうん、アクセス拒否のXMLが出力されました。期限付きURLはちゃんと機能していますね。

余談

CloudFrontの設定で、パラメータを無視してキャッシュを保存出来る機能ってあったような…?
あれば、期限付きの毎回異なるURLを出力しながら、データの配信にはキャッシュを利用して負荷軽減が出来たりとかするのかな。
これはこれで別で調べてみよう!

元記事はこちらです。
S3+CloudFrontで期限付きURL

小谷松 丈樹

小谷松 丈樹

アイレット第一事業部のWebアプリ開発者。PHP、JavaScriptの案件がメイン。モノづくりは常に楽しみながらがモットー。面白いものを生み出して、管理までできるようになるのが目標。