Goalist Developers Blog

CloudFront の署名付き URL で S3 にアクセスしてみる

こんにちは、ゴーリストのエンジニアのJPです。
最近 SaaS サービスの開発プロジェクトでインフラ構成の調査を行っているため、その中で話題に上がった AWS のサービスを利用したファイル配信方法を紹介したいと思います。

目次

ターゲット

以下のような疑問をお持ちの方

  • S3 へのアクセスを制限したい
    ( S3 の URL 知ってれば誰でもアクセスできちゃうよね?)
  • Lambda のレスポンスサイズの上限に引っかかる
  • 署名付き URL と Cookie どっちがいいの?

  ※具体的な設定方法は次回以降の記事で記載しますので、ご注意ください。

調査の背景

  • 新規サービスにはファイルをアップロード・ダウンロードする機能がある
  • アプリケーションのユーザーによりダウンロードできるファイルを制限したい
  • バックエンドは AWS の Lambda を使用
  • Lambda にはレスポンスのサイズに 6MB という制限がある → 足りないから DB じゃなくて AWS S3 にファイルを置こう
  • S3 からファイルをダウンロードする際に、ユーザーの権限判定ロジックを埋め込みたい
    どうする?

解決策

下図のように AWS の CloudFront の署名付き URL を利用して S3 のアクセスを制限し、取得者の権限チェックロジックも埋め込もう。

f:id:j-itoh:20170511124644j:plain

署名付き URL についての概要
Amazon の公式ドキュメント:CloudFront を使用してプライベートコンテンツを供給する

インターネットを通じてコンテンツを配信する多くの企業が、選ばれたユーザー(料金を支払っているユーザーなど)のドキュメント、ビジネスデータ、メディアストリーム、またはコンテンツに対して、アクセスを制限する必要があると考えています。CloudFront を使用してこのプライベートコンテンツを安全に供給するには、以下の方法を使用できます。 特別な CloudFront 署名付き URL または署名付き Cookie を使用してプライベートコンテンツにアクセスするようユーザーに要求します。 Amazon S3 コンテンツへのアクセスにユーザーが Amazon S3 URL ではなく CloudFront URL を使用するよう要求します。CloudFront URL を要求することは必須ではありませんが、ユーザーが署名付き URL や署名付き Cookie で指定された制限をバイパスすることを防ぐため、この方法をお勧めします。

CloudFront でできること

S3URL による S3 へのアクセスを無効にする
CloudFront の Distribution のドメイン名を含む URL でしか S3 のファイルにアクセスできない
例)https://sample.cloudfront.net/usagi.png

署名付き URL でのアクセスしか受け付けないようにできる
署名付き URL:公開鍵認証方式により暗号化された文字列を URL のクエリ文字列に含んだもの
例)

https://sample.cloudfront.net/usagi.png?Expires=1493274265&Signature=P-Mz9vHOGzL5HFwUpwoEwaPFDJIqd3T5rCoenGd3JFu50FtmTC9BM~o22rUf3gBn6wLECFgt6b-9bxJKUBc32uzGzy4M0KLBhW9W3CF4G79~sOHGJUKl0HmuLsFn1ZFy606~z5bBcdhp6hI-fJXd2dS4VEP1S~Z~q2rzTR75l3V1WnHnCTxLYWl5smhFavm57WdIan6jnvCRXnVWzWv7pbSz5VAmzx1N5MgQ0fflI1GaYMNel7sErr40BGeTSSoJpdFClWHfuOhclXFYUbq-dTVD4XRp0o4rQHM7uk-jFLTyi0v~jYFGfrYp1p825xwkiM1UYw~kasH5VHJM5Njs7g__&Key-Pair-Id=APKAJRIMZTJQZZQ42AVA

署名付き URL は以下のセキュリティーポリシーを追加することができる

  • 有効期限(開始日時、終了日時)
  • アクセスを許可する IP アドレス

署名付き URL は Java、Node.js、PHP 等の AWS ライブラリで作成する必要がある
具体的な作成方法は省略します。

認証の仕組み

署名付き URL の構造

https:// { Distribution } / { S3 Key } ?Expires= { 有効期限 }&Signature= { 暗号化情報 } &Key-Pair-Id= { アクセスキー ID }
パラメータ 説明
Distribution CloudFront の S3 接続用の Distribution のドメイン名
S3 Key S3 上の目的のファイルまでのパス
有効期限 URLが使えなくなる時刻(UTCで指定しなければならない)
暗号化情報 秘密鍵により暗号化かつ Base64 エンコードされた文字列(※)
アクセスキー ID AWSのルートアカウントにより作成された秘密鍵と公開鍵のペアの ID(認証に使用するキーペアを指定)

※暗号化情報には上表のパラメータが全て含まれている

認証手順

順序 実行サービス 処理内容
1 クライアント 署名付き URL により CloudFront にアクセス
2 CloudFront Distribution でアクセスが許可されている AWS ルートアカウントがアクセスキー ID を保持しているか判定
3 CloudFront 保持している場合、2の ID の公開鍵で Signature を復号化
4 CloudFront 復号化した内容とパラメータを比較( URL の改ざんチェック)
5 CloudFront 4の比較の結果、合わない場合アクセスを拒否(エラーを返す)
6 CloudFront 5が合う場合、有効期限などのポリシーをチェック
7 S3 有効期限内であればファイルを取得

URL の再利用への対策

  • 秘密鍵がなければ Signature を復号化できないため、有効期限等の暗号化情報を改ざんすることはできない
    (公開鍵で暗号化したものは3の公開鍵での復号化はできない)
  • 有効期限内であれば再利用可能(最適な有効期限を検討する必要)

どちらにするかの検討段階で、署名付き URL の以下のメリットが大きいかなと思い、正直 Cookie の方は調べていません。
Cookie のメリットはこれから勉強します。

  • 画像ファイルの URL をそのまま HTML タグのプロパティに書ける
<img src=" { 署名付き URL } "/>

Cookie だとフロント側でロジックが必要になりそう。

活用例

ファイルダウンロード機能において、アプリケーションのユーザー権限によるアクセス制限を行う
※ Lambda では 6MB という制限があるため、ファイルは一律 S3 から転送するようにする

順序 実行サービス 処理内容
1 クライアント ファイルダウンロードリクエストを送信
2 Lambda 権限チェック
3 Lambda 2で権限がある場合、署名付き URL を作成してレスポンスを送信
4 クライアント 署名付き URL にアクセス
5 CloudFront URL の改ざん、有効期限をチェック
6 CloudFront キャッシュの確認・あれば返信(7は実行しない)
7 S3 ファイルを送信

他の実現方法

他の案として Lambda@Edge でセッションをチェックするという方法もありましたが、以下の懸念があったため採用しませんでした。
※ Lambda@Edge とは CloudFront のリクエストと S3 の間に Lambda Function を処理をかませる機能です。

  • Lambda Function の実行時間の上限が0.05秒しかない
  • Node.js でしかロジックを書けない(ランタイムが Node.js にしか対応していない)

感想

署名付き URL でもアプリケーションのユーザー権限によるセキュリティを保つことができることが分かりました。
また、CloudFront を使用することで、キャッシュからの高速なファイル転送が可能となりました( SaaS だったら関係ないかも)。
Lambda のレスポンスサイズの上限を気にする必要がなく、ファイルを DB から取得する方法と、CloudFront 経由のファイル取得、どちらの方が早いのかは気になるところです。

課題としては、ベストな有効期間を検討することが残っています(動かしてみるしかない)。

機会があれば続きとして以下のようなことも書いていきたいです。

  • AWS コンソール上での設定方法
  • Java での署名付き URL の作成方法
  • CloudFront を経由して一括で複数のファイルをダウンロードする際の最も早い方法