☁️

S3クロスアカウントアクセスと所有権を理解する

2023/10/06に公開

今回取り上げる問題

こんにちは。Team DELTA代表の丹です。
今回は弊社で行っている技術支援の中で発生した問題について、改めてS3について学んだことがあったのでまとめます。

今回の事例では、あるアカウントから他のアカウントへのアプリケーションの移行を支援していました。
それにあたり、アプリケーション本体の移行は行うものの、諸事情によりユーザーがアセットをアップロードするS3バケットは元のアカウントに残すことになりました。

その際、要するにクロスアカウントでのS3バケットへのアクセスが必要になるわけですが、今回は所謂クロスアカウントアクセスを利用するという選択をしました。

広義のクロスアカウントアクセスの種類

一般的に、他のアカウントのリソースにアクセスする方法は二通りあります。

スイッチロール

スイッチロールは、あるクレデンシャルに対して別のロールを引き受ける(スイッチする)方法です。
スイッチロールはアカウントを跨いで行うことができるため、これを利用することで以下のような挙動を実現できます。

  • アカウントAは、アカウントBに対して、あるロールへのスイッチロールを許可する
    • このロールはアカウントAの持ち物で、普通にアカウントAのリソースに関する許可を持つ。
    • そのうえで、このロールはアカウントBからスイッチロールできるということも許可する。
  • アカウントBは、そのクレデンシャルに対してアカウントBアクセス用のロールへのスイッチロールを許可する
    • アカウントB内で、IAMポリシー内で「アカウントAの、クロスアカウント用のロールにスイッチロールしていいよ」という旨を許可する。
  • アカウントBのクレデンシャルがスイッチロールを行うことで、アカウントAのロールとして新たなクレデンシャルを得る

この方法は、あたかもアカウントAであるかのように振る舞えるクレデンシャルを得ることができるので、思わぬ挙動が起こらない、
また通常のIAMポリシーが設定できる範囲においてはどのリソースに関してもクロスアカウントでのアクセスができるというメリットがある一方で、

明示的にスイッチロールというAPIをコールする必要があるため、アプリケーションの改修の必要があり、
それによりアプリケーションレイヤーが「今アクセスしようとしているリソースは別のロールを引き受けないとアクセスできない」という知識を持つ必要があるため、
アプリケーションの可搬性やインフラからの独立性が損なわれるというデメリットがあります。

クロスアカウントアクセス

クロスアカウントアクセスは、一部のサービスにおいて利用できる方法で、
リソース側が単体で他のアカウントからのアクセスを自ら許可することで、
クレデンシャルは他のアカウントのものであるままアクセスできるようにする方法です。

この方法では、他のアカウントのクレデンシャルを利用したまま、別のアカウントのリソースにアクセスできるため、
アプリケーションの改修が不要というメリットがある一方で、

一部のリソース(具体的にはS3、SQS、SNS Topicなど)でしか対応していない、またあくまで別のアカウントからのアクセスになるというデメリットがあります。

今回は、移行期間中の動作を透過的に保証したかったため、
アプリケーション(ECS)に割り当てられたロールのクレデンシャルのまま、
S3バケットがどのアカウントにあるかに無関係にアクセスできるようにしたいと考えました。

発生した問題

つまり、アカウントAに配置されているバケットに対して、アカウントBのクレデンシャルでオブジェクトをアップロードすることになったわけですが、
この際、 本来ならオブジェクトURLでアクセスできるはずのオブジェクトが見れなくなる という事象が発生しました。

S3におけるアクセス権限の種類

問題の原因を説明する前に、S3におけるアクセス制御の種類について整理しておきます。
S3のオブジェクトURLからデータ自体にアクセスできる・できないは様々なアクセス制御機能の掛け合わせによって制御されます。
それぞれのアクセス制御機能の特徴についてまとめます。

ACL

  • オブジェクト・バケット単位のアクセス許可
  • どのアカウント(パブリック含め)が読み取り・書き込みのアクションをできるかというシンプルな制御をできる
  • ACLには、プリセットの「事前定義されたACL」というものが存在する。APIでのアップロード時には、ACLの詳細ではなくこのプリセット名を指定することもできる。
    • メジャーなのはpublicprivate かと思います
    • ここでpublic を明示的に付与されている場合、後述のバケットポリシーより優先して公開が行われる

バケットポリシー

  • バケット単位で、その中のプレフィックス単位などの柔軟な範囲でアクセス権(IAMポリシーベース)を指定できる
  • イメージとしては、IAMポリシーをバケット内で指定できるイメージ
  • ConditionsやResourceといった条件式によって、オブジェクトの有無や、オブジェクトアップロード時の具体的なリクエストに無関係に予めアクセス権を指定できる。
  • 特にクロスアカウントアクセスなどに用いると思います
    • バケット全体または既に存在するオブジェクト単位でしか指定できないACLと比較して、オブジェクトが存在する前から動的に指定できるため、書き込みに対する指定に強い
  • というか、AWSとしてはACLの廃止を推奨しているらしいので基本はバケットポリシーが推奨のようですね。

IAM

  • IAMでは、ロールまたはユーザーにポリシードキュメントベースでアクセス権を指定できる
  • もちろんこれは必要
  • S3側のアクセス制御機能(ACL、バケットポリシー)はIAMユーザー以外などもスコープにしたリソースサイドのアクセス制御を提供する一方で、IAMポリシーはIAMユーザーにしか効果がない

S3における所有権の概念

S3においては、オブジェクトの単位で「所有者」という概念があります。
普通、シングルアカウントで運用している場合は意識する必要はありませんが、複数アカウントを運用する場合は意識するといいかもしれません。
デフォルトの設定では、オブジェクトライター = オブジェクトを書き込んだアカウントがそのオブジェクトの所有者となります。
つまり、クロスアカウントアクセスによりオブジェクトを書き込んだ場合、バケットの所有者と異なるアカウントがオブジェクトの所有者となります。

S3バケットポリシーはバケット所有者とオブジェクト所有者が同じオブジェクトにしか作用しない

今回の問題事象を引き起こしていたのはこの仕様でした。
先程のバケットポリシーの説明画面にはちいさーく書いてあるんですが、
S3のバケットポリシーは、オブジェクト所有者 === バケット所有者の場合のみ作用します。
オブジェクトACLによりオブジェクトを公開している場合はさておき、特定のPrefix(例えば、public/)配下に配置されたオブジェクトは公開する、
という設定をバケットポリシーであてている場合は、
デフォルトのACLでクロスアカウントでアップロードしたオブジェクトにはそのポリシーがあたらずパブリックアクセスできなくなります。

ACL(詳解)

デフォルトのACLでクロスアカウントでアップロードしたオブジェクトにはそのポリシーがあたらずパブリックアクセスできなくなります。

これはなぜこうなるのでしょうか?

詳細には、バケットポリシーは、「バケット所有者がコントロールできるオブジェクトに対して適用される」という性質を持っています。
で、デフォルトのACLでアップロードされたオブジェクトは、オブジェクトライターが所有者となり、バケット所有者には何の権限も与えられない(コントロールできない)ので、バケットポリシーが適用されません。

具体的には、デフォルトのACLは private というACLになります。

private

private というACLは以下のような仕様となります。

_ オブジェクト読み込み オブジェクト書き込み
オブジェクトライター
バケット所有者 × ×
パブリック × ×

一見するとオブジェクトライターのみが権限を持つ良いACLのように見えますが、
バケット所有者がオブジェクトライターと異なる場合、バケット所有者がコントロールできないためバケットポリシーがあたりません。

バケット所有者=オブジェクトライターの場合

_ オブジェクト読み込み オブジェクト書き込み
オブジェクトライター
バケット所有者
パブリック × ×

バケット所有者!=オブジェクトライターの場合

_ オブジェクト読み込み オブジェクト書き込み
オブジェクトライター
バケット所有者 × ×
パブリック × ×

bucket-owner-full-control

今回やりたいのは、バケット所有者!=オブジェクトライターの場合に、
バケット所有者がオブジェクトに対してコントロールを持つという制御になります。

これを実現するためには、privateではなくbucket-owner-full-controlというプリセットACLを明示的に渡す必要があります。

常に

_ オブジェクト読み込み オブジェクト書き込み
オブジェクトライター
バケット所有者
パブリック × ×

オブジェクトの所有者自体を変更する

bucket-owner-full-control を適用すると、そのオブジェクトのフルコントロールをバケットの所有者が持つことになります。
ただし、この場合でも「所有者」はあくまでオブジェクトライターのままです。

この問題を解決し、名実ともにオブジェクトの所有者を完全にバケットの所有者にする場合、S3側の設定を変更する必要があります。

その設定がこれです。

この設定を、「希望するバケット所有者」にすることで、 bucket-owner-full-control が付与されたオブジェクトの所有者はバケット所有者になるようになります。
private を指定した場合は従来通りオブジェクトライターになります。

結局、クロスアカウントアクセスを透過的に行いながら、バケットポリシーを適用することはできるのか

bucket-owner-full-control ACLを明示的に渡すということは、S3にオブジェクトをアップロードするAPIのパラメタを変更する必要があるということになりますが、
これはすなわち、アプリケーションへの改修が結局必要ということにほかなりません。

その時点で、当初やりたかった、「バケット所有者のアカウントが、アプリケーションのアカウントと同じかどうかに無関係に動作するアプリケーション」は実現できなくなるのでしょうか?

実はそうではありません。 bucket-owner-full-control ACLは、「バケット所有者とオブジェクト所有者が同じ場合」においては、private ACLと全く同じ動作をします。

なので、このお客様に関しては、現在privateまたはACLなし(デフォルトではprivateになります)の箇所に対して、bucket-owner-full-controlを明示するという対応を行いました。

この対応を行ったところ、期待通りバケットポリシーが適用されるようになりました!

学び

  • S3でクロスアカウントアクセス(違うアカウントのクレデンシャルのまま、別のアカウントのS3バケットにアクセスする)を行う場合、オブジェクトの所有者は基本的にはオブジェクトライターになる
  • バケットポリシーはバケット所有者にしか適用されない
  • bucket-owner-full-control ACLは、バケット所有者がオブジェクトへのフルコントロールを持つようになるもの

We are hiring!

ここまでお読みいただきありがとうございます!

現在Team DELTAでは、一緒に働く仲間を募集中です。

この事例のように、普段は使うことがないマニアックなAWSの設定も、実践の中で触れることができるエキサイティングな環境です。

少しでもご興味のある方はぜひこちらからご連絡ください!

Discussion