TT Lab
はじめる
学ぶ 学習パス コース

オブジェクトストレージとS3

ユーザとポリシーでアクセスを制御する

TT Labで続きを見る

目標

サービスユーザーを作り、最小権限のポリシーを自分で書いてアタッチしながら、権限の範囲をプレフィックス単位に絞る習慣を身につけます。

なぜ重要なのか

ストレージの流出事故の大半は、複雑な攻撃ではなく、広く開けておいた権限から来ます。「画像しかないから」とバケットを匿名読み取りで開けたのに、数か月後にそのバケットの下に、バックアップやユーザーのアップロードが混ざり込む、という具合です。ポリシーのResourceをバケット全体(arn:aws:s3:::bucket/*)にすることも、同じ問題です。サービス1つの資格情報が漏れると、バケット全体が露出します。プレフィックスまで絞っておけば、被害がそのプレフィックスの中に閉じ込められます。そして、これは実装のバグに対する防御でもあります。MinIOのCVE-2025-62506は、セッションポリシーのバイパスで権限が昇格する脆弱性でした。ポリシーを狭く書く習慣は、そうした瞬間に残る最後の防衛線です。

このラボは、AWSと同じIAM API(aws iam ...)で行います。サーバーはSeaweedFSのS3ゲートウェイで、ユーザー・アクセスキー・マネージドポリシー・アタッチを、AWSと同じ呼び出しで受け付けます。そのため、ここで使ったコマンドとポリシー文書は、AWSアカウントにそのまま持っていけます。

ステップ

  1. 管理者プロファイルlocalでaws iam create-user --user-name appuserを実行し、aws iam create-access-key --user-name appuserで受け取ったキーを、プロファイルappに入れてください(endpoint_urlとregionも一緒に)。
  2. aws --profile app s3 ls s3://lab-mediaを試し、結果を/root/pol/denied.txtに保存してください。ファイルにAccessDeniedが含まれている必要があります。
  3. /root/pol/readonly.jsonにポリシーを書いてください。s3:GetObjectとs3:ListBucketを許可し、Resourceはarn:aws:s3:::lab-mediaとarn:aws:s3:::lab-media/*の2つです。
  4. aws iam create-policy --policy-name labreadonly --policy-document file:///root/pol/readonly.jsonでマネージドポリシーを作り、aws iam attach-user-policyでappuserにアタッチしてください。aws --profile app s3 ls s3://lab-mediaが成功する必要があります。
  5. aws --profile app s3 cp /opt/fixtures/s3/readme.txt s3://lab-media/doc/nope.txtが失敗する必要があります。/root/pol/write.txtにwrite_denied=trueを書いてください。
  6. /root/pol/imgonly.jsonに、Resourceをarn:aws:s3:::lab-media/img/*に絞ったポリシーを書いて、labimgonlyとして作り、アタッチしてください(広いlabreadonlyは外します)。appuserで、img/logo.pngの取得はでき、doc/sales.csvの取得は失敗する必要があります。/root/pol/prefix.txtにimg=ok doc=deniedを書いてください。
  7. バケットポリシーで、lab-mediaのpublic/プレフィックスだけを、匿名ダウンロード用に開けてください。匿名GETが、public/の下のオブジェクトには200、private/の下のオブジェクトには403である必要があります。/root/pol/anon.txtにpublic=200 private=403を書いてください。

参考

サービスユーザーを作る

管理者プロファイルlocalでaws iam create-user --user-name appuserを実行し、aws iam create-access-key --user-name appuserで受け取ったキーを、プロファイルappに入れてください(endpoint_urlとregionも一緒に)。

管理者プロファイルでユーザーを作り、アクセスキーを発行します。シークレットは、発行のレスポンスで1度しか見えないので、すぐにプロファイルに入れてください。

ポリシーのないユーザーが拒否されることを確認する

aws --profile app s3 ls s3://lab-mediaを試し、結果を/root/pol/denied.txtに保存してください。ファイルにAccessDeniedが含まれている必要があります。

新しいプロファイルで一覧を試すと、拒否されます。既定では、何の権限もありません。

読み取り専用ポリシーを書く

/root/pol/readonly.jsonにポリシーを書いてください。s3:GetObjectとs3:ListBucketを許可し、Resourceはarn:aws:s3:::lab-mediaとarn:aws:s3:::lab-media/*の2つです。

JSONでEffect、Action、Resourceを書きます。一覧の取得とオブジェクトの読み取りは、別々のActionです。

マネージドポリシーを作ってアタッチし、読み取りを成功させる

aws iam create-policy --policy-name labreadonly --policy-document file:///root/pol/readonly.jsonでマネージドポリシーを作り、aws iam attach-user-policyでappuserにアタッチしてください。aws --profile app s3 ls s3://lab-mediaが成功する必要があります。

ポリシーを作ることと、ユーザーに付けることは、別のステップです。アタッチ直後は、反映に数秒かかることがあります。

書き込みは、まだ拒否されることを確認する

aws --profile app s3 cp /opt/fixtures/s3/readme.txt s3://lab-media/doc/nope.txtが失敗する必要があります。/root/pol/write.txtにwrite_denied=trueを書いてください。

読み取り専用ポリシーなら、アップロードが失敗する必要があります。失敗するのが正常です。

プレフィックス限定のポリシーを作る

/root/pol/imgonly.jsonに、Resourceをarn:aws:s3:::lab-media/img/*に絞ったポリシーを書いて、labimgonlyとして作り、アタッチしてください(広いlabreadonlyは外します)。appuserで、img/logo.pngの取得はでき、doc/sales.csvの取得は失敗する必要があります。/root/pol/prefix.txtにimg=ok doc=deniedを書いてください。

Resourceに、ワイルドカードをプレフィックスまで付けます。img/は通り、doc/は通らないようにしてください。

特定のプレフィックスだけを匿名公開する

バケットポリシーで、lab-mediaのpublic/プレフィックスだけを、匿名ダウンロード用に開けてください。匿名GETが、public/の下のオブジェクトには200、private/の下のオブジェクトには403である必要があります。/root/pol/anon.txtにpublic=200 private=403を書いてください。

バケット全体を開けることと、プレフィックス1つだけを開けることは、事故の規模が違います。

Resourceの末尾のスラッシュ1文字に注意してください。このラボの要点が、そこにすべて入っています。

"Resource": "arn:aws:s3:::lab-media/public*"    → public 으로 시작하는 모든 키
"Resource": "arn:aws:s3:::lab-media/public/*"   → public/ 아래 키만

S3にフォルダーはありません。ポリシーが見るのは文字列のプレフィックスだけなので、スラッシュを抜かすと、public-backup.tarのように、publicで始まるだけのルートのオブジェクトまで、一緒に開きます。aws s3api get-bucket-policy --bucket lab-mediaで、サーバーに実際にかかっているResourceを確認してください。

バケットポリシーは、バケットに1つで、put-bucket-policyをするたびに、まるごと置き換わります。範囲を変えるときは、文書全体をもう一度入れてください。