AWS CloudFront を使って Webサービスにおける画像のセキュア環境を構築した

この記事は RECRUIT MARKETING PARTNERS Advent Calendar 2018 の投稿記事です。

はじめに

画像権限、管理していますか?

複数の組織をユーザーとして抱えるようなWebサービスにおいて、組織内のプライベートな情報として参照できる環境上で画像をアップロードをしたとしても、一度一意の画像URLを取得してしまえばその後ログインせずともパブリックに画像参照できる、という状況の方が多いと思います。

今回はCloudFrontの機能を使って一定の条件でセキュアな環境を開発環境用に構築する方法を紹介します。

想定要件

具体的には下記の要件を満たします。

  • 組織Aに所属するユーザーがアップロードした画像は、同組織に所属するユーザーしか参照できない
  • ユーザーは複数の組織に所属できる
  • 複数組織に所属するユーザーは横断して画像の参照できる
  • 画像のキャッシュは使いたい
  • 画像のURLがバレても正当なアクセス権を持つ人間しか画像参照ができない

方針

第一に『画像を得るためにリクエストパラメータに従い規定の画像レスポンスを返す』『認証有り画像アプリケーションサービスを作る』という方針があるかと思います。

しかしながら本件ではこれを見送りました。いくつか理由がありますが、現時点の想定環境下において管理コンポーネントを増やしたくない強い理由があったためです。

別のアプリケーションとして実装する判断はMicroservicesの観点では特別異常なことでもないように思いますが、今回は管理コンポーネントを増やしたくないという気持ちを優先してAWS, 具体的にはCloudFrontの機能でできないかを検討しました。

ちなみに本節の内容は 署名付きURLと署名付きCookieの選択に詳しく記載があります。

Signed URLs ( ※ボツ案 )

セキュア環境を実現するためのCloudFrontの機能としてまず署名付きURLが挙げられます。

これは個別の画像に対して都度個別に有効期限のあるパラメータ付きのURLを発行するという機能で、画像URLが一意に固定できないことがキャッシュの観点で不効率であること、画像のURLがわかってしまえば誰でもアクセスできてしまうことなどから要件を満たさないので使いませんでした。

2018/12/05 17:00 追記

オリジンのオブジェクトに対し、

  • クエリパラメータに対して
  • 付与されるcookieに対して

それぞれに対応する形で個別でcacheされるというベースの挙動に加え、Signed URLに関してはValidationをエッジで行う都合上キャッシュは効率的に使えるというフィードバックをいただきました!ありがとうございます!!

Signed Cookies

次に署名付きCookieが挙げられます。基本的な機能は同じですが、複数の画像に対しアクセス権のあるcookieを発行しこのcookieを用いて画像を参照することができるようになるもので、画像URLが一意に固定されること、画像のURLがわかっても画像にアクセスするにはcookieが必要=正当なアクセス権のあるユーザーしかアクセスできないという点でSigned URLよりも要件を満たしそうです。

Signed Cookiesを用いた場合の要件可能可否

さて、署名付きCookieについてもう少し深掘りをしてみます。
署名付きCookieは、有効期間などのパラメータを指定する方法として

  • 規定ポリシー
  • カスタムポリシー

の2通りあります。有効期間以外のパラメータも変更する場合、カスタムポリシーを使います

{
   "Statement": [
      {
         "Resource":"URL of the object",
         "Condition":{
            "DateLessThan":{"AWS:EpochTime":required ending date and time in Unix time format and UTC},
            "DateGreaterThan":{"AWS:EpochTime":optional beginning date and time in Unix time format and UTC},
            "IpAddress":{"AWS:SourceIp":"optional IP address"}
         }
      }
   ]
}

ここで重要なことは、Statement キーに対してハッシュ(連想配列)の配列を指定できるように見えますが、実際には1つのステートメントのみを含めることができる という点です。また Resource にも『0 個以上の文字に一致するワイルドカード文字 (*)、または 1 つの文字に一致するワイルドカード文字 (?)』という制限があります。カスタムポリシーに関してはこちらに記載があります

ではこれによってどのように「ユーザーは複数の組織に所属でき、横断して画像の参照を行うことができる」という要件を実現するのでしょうか。どういう意味かというと、複数組織に所属するユーザー1が複数組織の画像を一括で要求した場合、Statementが1つしか指定できずにResourceで指定できるパスも正規表現が使えない以上、CloudFront単体で解決する手段はなさそうだ、ということです1)Resourceに正規表現使えるようにするとかStatementが複数指定できるようになってほしい...

解決案

cookieのPath属性を用いてこれを解決することができます。cookieは同名キーのものを複数保持できます。

※ 同名キーのSet-Cookieの扱いは実装に依ることに加え、

Even though the Set-Cookie header supports the Path attribute, the Path attribute does not provide any integrity protection because the user agent will accept an arbitrary Path attribute in a Set-Cookie header. For example, an HTTP response to a request for http://example.com/foo/bar can set a cookie with a Path attribute of "/qux". Consequently, servers SHOULD NOT both run mutually distrusting services on different paths of the same host and use cookies to store security-sensitive information.

RFC6265に記載があるため、十分に取扱いに注意したほうがよさそうですね。本件においてはCloudFront側でcookieの正当性を担保するのでpath依存の攻撃は成功しないと判断しました。更にいい方法があったら知りたいです。

最終的なシーケンス図

A, Bという組織に所属するユーザーの場合

まとめ

複数の組織を取り扱うWebサービス上で扱う画像について、セキュア(=正しい権限を持つユーザーにのみ正しく画像参照できる状態になっている)な環境にするためにCloudFrontの署名付きCookieを使用しました。
署名付きCookieの挙動である複数リソースの制御に不都合があったので、cookieのpathの機能を使用して解決しました。

感想

セキュリティ周りに関して、普段から意識するのは難しいと感じました。
またCookieはpath依存ではなくLamda@Edgeなどを利用した代替案もないか検討できそうなので引き続きより良い方法を考えてみたいと思います。

脚注

脚注
1 Resourceに正規表現使えるようにするとかStatementが複数指定できるようになってほしい...