S3バケットにあるのに取得できない|クロスアカウント構成で見落とした「オブジェクト所有者」の罠

トラブル

この記事では、クロスアカウント構成で「権限があるはずなのにS3オブジェクトを取得できない」原因と、それを防ぐために設計・テスト工程で何を見るべきかを整理します。


「設定は合っているはずなのに、取れない」

権限設定もした。
バケットも見える。
テストも通った。

それなのに、本番で特定のファイルだけ取得できない。

こういう問題は、AWSの仕様を知らないというより、誰が作ったオブジェクトなのかまで設計・テストしていなかったことで起きます。

今回は、S3のクロスアカウント構成で実際に起きた事象をもとに、単なる技術論ではなく、設計とテスト観点の話として整理します。

AWSでS3を使っていると、ついこう考えてしまいます。

「S3バケットへの権限を付ければ、その中のオブジェクトも操作できるはず」

しかし、クロスアカウント構成では、この前提が崩れることがあります。

バケットは見える。
オブジェクトも存在する。
ユーザーがアップロードしたファイルは取得できる。

それなのに、AWSサービスが出力したオブジェクトだけ取得できない。

一見すると、IAMポリシーの設定ミスに見えます。
しかし、原因は単純な権限不足ではありませんでした。

今回の本質は、S3の操作権限だけではなく、オブジェクトの所有者まで設計・テストしていたかという話です。

バケットにはあるのに、なぜか取得できなかった

構成としては、アカウントAから、アカウントBのS3バケットにあるオブジェクトを取得する必要がありました。

アカウントBにS3バケットがあり、そこに複数のオブジェクトが存在していました。

その中には、ユーザーが手動でアップロードしたオブジェクトもあれば、AWSサービスが出力したオブジェクトもありました。

たとえば、S3アクセスログやCloudTrailログのように、AWSサービス側の処理によってS3へ出力されるファイルです。

また、AWSサービスではなくても、別アカウントのEC2やバッチ処理がS3へ書き込む構成では、同じように「誰が作成したオブジェクトなのか」が問題になることがあります。

現象としては、次のような状態でした。

ユーザーがアップロードしたオブジェクトは取得できる。
しかし、AWSサービスが出力したオブジェクトは取得できない。

CLIであれば、AccessDeniedや403 Forbiddenのようなエラーとして見えることがあります。

ただし、このエラーだけを見ても、IAMポリシー不足なのか、バケットポリシーなのか、オブジェクト所有者なのかはすぐには分かりません。

同じS3バケット内にあるにもかかわらず、取得できるものと取得できないものが混在していたのです。

この時点で、単純な「バケットに対する権限不足」とは言い切れません。

もしバケットそのものへのアクセス権限が不足しているのであれば、ユーザーがアップロードしたオブジェクトも取得できないはずです。

しかし実際には、一部のオブジェクトは取得できていました。

つまり、問題はバケット単位ではなく、オブジェクト単位の条件差にありました。

画像
図1:同じS3バケット内で取得できるオブジェクトと取得できないオブジェクトが混在する

見落としていたのは、バケットではなくオブジェクトの所有者だった

原因は、オブジェクトの所有者が異なっていたことです。

S3では、バケットの所有者と、オブジェクトの所有者が常に同じとは限りません。

特にクロスアカウント構成や、AWSサービスがS3に出力する構成では、この点を意識する必要があります。

昔のS3では、ACLによってバケットやオブジェクト単位の権限を細かく制御する考え方が使われていました。その名残がある既存環境では、現在の新規バケットとは異なる前提で動いていることがあります。

今回の場合、ユーザーがアップロードしたオブジェクトは、想定していた権限設計の範囲で操作できました。

一方、AWSサービスが出力したオブジェクトは、所有者や権限の扱いが異なっていました。

AWSサービスがS3にオブジェクトを出力する場合、利用者が手動でアップロードしたファイルと同じ所有者・同じ権限になるとは限りません。

バケットのObject Ownership設定、ACLの扱い、サービス側の出力仕様によって、バケット所有者がそのまま自由に操作できるとは限らない状態が発生します。

そのため、アカウントAからアカウントBのS3バケットに対する操作を許可していても、対象オブジェクトによっては取得できない状態になっていました。

ここで重要なのは、次の点です。

「S3バケットにある」ことと「そのオブジェクトを操作できる」ことは同じではない。

S3をファイルサーバーのように見ていると、この違いを見落とします。

ディレクトリの中にファイルがある。
だから、フォルダに権限を付ければ中のファイルも読める。

オンプレのファイルサーバー感覚では、こう考えがちです。

しかし、S3ではこの前提がそのまま通用しません。

今回の直接原因は「オブジェクト所有者」でしたが、そもそもS3の権限設計は単一の概念では完結しません。

S3では、バケット、オブジェクト、オブジェクト所有者、バケットポリシー、IAMロール、ACL、Object Ownershipなど、複数の要素が絡みます。

現在のAWSでは、S3 Object OwnershipのBucket owner enforcedを使い、ACLを無効化して、バケットポリシーやIAMポリシーで制御する設計が基本です。新規バケットではBucket owner enforcedがデフォルトになっています。

ただし、既存環境や移行案件では、過去に作成されたバケット、既存アプリケーション、ACL前提の処理、サービス出力、外部連携が残っていることがあります。

そのため、「今のAWSではデフォルトがこうだから、この問題は起きない」とは言い切れません。

既存環境では、今回のようにオブジェクト所有者やACLの考え方が残っていて、運用時に問題として表面化することがあります。

画像
図2:S3の権限設計に関わる要素の関係

このように、S3では「バケットに権限を付けたから終わり」ではありません。

誰がバケットを持っているのか。
誰がオブジェクトを作ったのか。
そのオブジェクトの所有者は誰なのか。
どのアカウント、どのIAMロールで操作するのか。

ここまで整理しないと、実際の運用で詰まります。

誰に権限を付けるかではなく、誰として操作するかを決めた

今回の対策を考えるうえで、最初に整理すべきだったのは、単に「誰にS3権限を付けるか」ではありません。

見るべき観点は、次のようなものでした。

  • 誰がバケットを所有しているのか。
  • 誰がオブジェクトを作成するのか。
  • 作成されたオブジェクトの所有者は誰になるのか。
  • そのオブジェクトを誰が読むのか。
  • 読む主体はユーザーなのか、IAMロールなのか、別アカウントのサービスなのか。
  • 対象はユーザーがアップロードしたファイルだけなのか、AWSサービスが出力したファイルも含むのか。

ここまで整理して初めて、対策の方向性が決まります。

現在のAWS設計として優先すべき方向は、S3 Object OwnershipをBucket owner enforcedにし、ACLを無効化して、バケット所有者に所有権を寄せることです。

新規プロジェクトであれば、基本的にはこの設計を第一候補にすべきです。

この設計にできれば、オブジェクトごとのACL差分や所有者差分による混乱を減らせます。

ただし、現場では理想形にすぐ寄せられないことがあります。

  • 既存アプリケーションがACLを前提にしている。
  • 外部サービスや既存ジョブの出力仕様が変えられない。
  • 過去に作成されたオブジェクトが大量にある。
  • 設定変更による影響範囲をすぐに確認できない。
  • 監査ログや運用ログの保管先としてすでに使われている。

こうした制約がある場合、いきなりBucket owner enforcedへ変更するのではなく、影響調査と段階的な移行が必要になります。

今回も、根本的な方向性としてはBucket owner enforcedへの移行を検討すべき構成でした。

ただし、既存環境では、すぐに設定変更できない事情がありました。

そこで今回は、まず権限の使い方を整理する現実的な対策として、アカウントB側のIAMロールにスイッチして操作する方式を採用しました。

いきなり理想形へ変更できないのであれば、まずは現在の権限設計のどこが曖昧だったのかを整理する必要があります。

今回の改善前の考え方は、こうでした。

「アカウントAに、アカウントBのS3バケットを操作する権限を与える」

一見、正しそうに見えます。

しかし、この考え方では、バケットに対する権限を中心に見ています。

改善後は、考え方を変えました。

「アカウントB側にIAMロールを作成し、アカウントAからそのロールにスイッチして、S3操作を行う」

つまり、バケットが存在するアカウント側の権限設計に寄せました。

これにより、アカウントAから直接アカウントBのS3を操作するのではなく、アカウントB側のロールとして操作する形にします。

対策としては、次のいずれかを明確に決める必要があります。

  • アカウントB側のIAMロールにスイッチして操作するのか。
  • S3 Object Ownershipを見直して、バケット所有者に所有権を寄せるのか。
  • サービス出力時の権限付与方式を見直すのか。
  • 既存オブジェクトについて、所有者やアクセス権の扱いを整理するのか。
  • 将来的にACL依存をなくす移行計画を立てるのか。

対策は、技術的な設定値だけで決めるものではありません。

  • 運用で誰が取得するのか。
  • 障害時に誰が調査するのか。
  • 監査ログとして誰が参照するのか。
  • 保管期間中に別アカウントから読む必要があるのか。
  • 削除やライフサイクル管理は誰が行うのか。

ここまで含めて、対策を選ぶ必要があります。

直接取りに行くのをやめ、バケット側のロールを使った

今回の対策では、アカウントB側にIAMロールを作成し、アカウントAからスイッチロールして操作する方式に変更しました。

アカウントBのIAMロールとして操作することで、アカウントB側のアイデンティティでS3にアクセスする形になります。

これにより、別アカウントから直接バケット内のオブジェクトを操作するのではなく、バケットを持つアカウント側の権限に寄せて操作できるようになります。

改善前は、アカウントA側のIAMに対して、アカウントBのS3バケット操作を許可していました。

しかし、これでは対象オブジェクトの所有者やサービス出力の扱いによって、取得できるものとできないものが発生しました。

改善後は、バケットが存在するアカウントB側にS3操作用のIAMロールを用意しました。

アカウントAのユーザーは、そのロールにスイッチします。

そして、アカウントB側のロールとしてS3バケット内のオブジェクトを操作します。

この形にすることで、少なくとも「アカウントAから直接、別アカウントのバケット内オブジェクトを操作する」という曖昧な構成を避けることができます。

また、権限設計の説明もしやすくなります。

「アカウントBのS3バケットを操作する場合は、アカウントBのIAMロールを使う」

このように整理できるからです。

画像
図3:改善前と改善後の構成比較

運用手順としても、どのロールで作業するのかを明確にできます。

これは地味ですが、現場ではかなり重要です。

障害調査のときに、担当者ごとに違うIAMユーザーや違う権限で確認していると、現象が再現したりしなかったりします。

  • ある人は見える。
  • 別の人は見えない。
  • CLIでは取れる。
  • マネジメントコンソールでは取れない。
  • アプリからは失敗する。
  • 運用端末からは成功する。

こうなると、原因調査が一気に複雑になります。

権限設計は、セキュリティのためだけにあるのではありません。

障害時に、同じ条件で確認できるようにするためにも必要です。

テストで手動ファイルだけ見ていたら、この問題は見逃される

今回の話で一番重要なのは、対策そのものではありません。

本来、テスト工程で何を確認すべきだったのか、という点です。

もしテストで、ユーザーが手動アップロードしたファイルだけを使っていたら、この問題は見逃されます。

なぜなら、そのファイルは取得できてしまうからです。

「S3からファイル取得できました」
「クロスアカウントアクセスできました」
「権限設定は問題ありません」

このようなテスト結果になってしまいます。

しかし、本番運用で実際に取得したいのは、ユーザーが手動アップロードしたファイルとは限りません。

  • AWSサービスが出力したログファイルです。
  • 運用バッチが出力したファイルです。
  • 監査用に自動生成されたファイルです。
  • 障害調査で必要になるファイルです。

つまり、テストデータの作り方が間違っていると、設計ミスを検知できません。

今回であれば、少なくとも次の観点でテストする必要がありました。

  • ユーザーがアップロードしたオブジェクトを取得できるか。
  • AWSサービスが出力したオブジェクトを取得できるか。
  • 別アカウントから取得できるか。
  • 想定するIAMロールで取得できるか。
  • 運用担当者の作業手順で取得できるか。
  • CLI、アプリ、運用ジョブなど、実際の利用経路で取得できるか。
  • 取得できない場合に、どのログやエラーで原因を追えるか。

ここまで確認して、初めて「運用で使える」と言えます。

単体テストでS3の設定値を確認するだけでは足りません。

結合テストでアカウント間のアクセスを確認するだけでも足りません。

システムテストや運用テストでは、本番と同じようにAWSサービスが出力したオブジェクトを使い、実際の運用手順で取得できるかを確認する必要があります。

ここを省略すると、テストでは成功したのに、本番運用で失敗します。

画像
図4:設計とテスト観点の対応

「同じバケットなら同じ扱い」という思い込みが危ない

今回の気づきは、S3の権限設計は「バケットにアクセスできるか」だけで終わらないということです。

特にクロスアカウント構成では、次のような思い込みが危険です。

S3バケットに権限を付ければ、中のオブジェクトも読める。
同じバケットにあるオブジェクトなら、同じように操作できる。
テスト用に手動で置いたファイルが取れれば、本番のログファイルも取れる。
AWSサービスが出力したファイルも、ユーザーがアップロードしたファイルと同じ扱いになる。
IAMポリシーだけ見れば原因が分かる。

これらは、実務では危ない前提です。

S3はシンプルに見えます。

バケットを作る。
ファイルを置く。
権限を付ける。
取得する。

表面的にはそれだけに見えます。

しかし、複数アカウント、AWSサービス出力、ログ保管、監査、運用ジョブが絡むと、一気に設計論点が増えます。

だからこそ、詳細設計では「S3バケット名」「保存先プレフィックス」「IAMポリシー」だけを書いて終わりにしてはいけません。

誰が作るのか。
誰が所有するのか。
誰が読むのか。
誰が消すのか。
誰が障害時に調べるのか。

ここまで書かないと、運用で使える設計にはなりません。

「取れました」で終わらせないためのチェックポイント

ここからは、同じ問題を防ぐための具体的なチェックリストです。

まず、S3を使う設計では、少なくとも次の項目を確認します。

  • バケット所有アカウント。
  • オブジェクト作成主体。
  • オブジェクト所有者。
  • アクセスするアカウント。
  • アクセスに使うIAMロール。
  • AWSサービスが出力するオブジェクトの有無。
  • クロスアカウントアクセスの有無。
  • S3 Object Ownershipの設定。
  • ACLを使うのか、使わないのか。
  • 運用時に参照・取得・削除する主体。
  • 将来的にBucket owner enforcedへ移行できるか。

次に、テスト工程では、テストデータの作り方を明確にします。

  • 手動でアップロードしたファイルだけで試験しない。
  • AWSサービスが実際に出力したファイルで試験する。
  • 本番運用で使うIAMロールで試験する。
  • 別アカウントからの取得を試験する。
  • 運用手順書どおりに取得できるか確認する。
  • 失敗時のエラー確認方法も試験する。

そして、レビュー時には次の質問を入れます。

  • 「そのオブジェクトは誰が作るのか」
  • 「そのオブジェクトの所有者は誰になるのか」
  • 「本番で取得するファイルと、テストで使ったファイルは同じ条件か」
  • 「AWSサービスが出力したファイルでも確認したか」
  • 「運用担当者が使うロールで確認したか」
  • 「ACLに依存した設計が残っていないか」
  • 「Bucket owner enforcedにした場合の影響を確認したか」

この質問を入れるだけで、今回のような問題はかなり防ぎやすくなります。

まとめ

今回の事象は、S3のクロスアカウントアクセスにおけるオブジェクト所有者の問題でした。

ただし、本質はAWSの細かい仕様そのものではありません。

本質は、次の一文です。

設計で決めていないことは、テストでも確認されない。

S3バケットに権限を付ける。
IAMポリシーを書く。
ロールを作る。
テストでファイルを取得する。

これだけでは、まだ足りません。

本番で実際に扱うオブジェクトは、誰が作るのか。
そのオブジェクトは、誰の所有になるのか。
どのアカウントから、どのロールで、どの手順で取得するのか。
障害時に、誰が調査できるのか。

ここまで設計して、初めて運用に耐える構成になります。

クラウドサービスは便利です。

しかし、サービスが自動で出力するものほど、利用者が手で作ったものと同じ前提で扱ってはいけません。

「作成できた」
「保存できた」
「バケットに存在する」

そこで安心してはいけません。

本当に確認すべきなのは、その先です。

必要な人が、必要なタイミングで、必要な権限で、そのオブジェクトを扱えるか。

そこまで見て、初めて設計とテストがつながります。


関連記事

今回の記事で扱った「設定したか」ではなく、「実運用で誰が、どの権限で、何を扱えるか」まで確認する考え方は、以下の記事でも整理しています。

【前編】詳細設計で決めること|基本設計を「構築・テスト・運用できる形」に落とし込む工程

シリーズ全体は「実務で使えるシステム開発方法論」マガジンにまとめています。

コメント

タイトルとURLをコピーしました