ユーザとポリシーでアクセスを制御する
目標
サービスユーザーを作り、最小権限のポリシーを自分で書いてアタッチしながら、権限の範囲をプレフィックス単位に絞る習慣を身につけます。
なぜ重要なのか
ストレージの流出事故の大半は、複雑な攻撃ではなく、広く開けておいた権限から来ます。「画像しかないから」とバケットを匿名読み取りで開けたのに、数か月後にそのバケットの下に、バックアップやユーザーのアップロードが混ざり込む、という具合です。ポリシーのResourceをバケット全体(arn:aws:s3:::bucket/*)にすることも、同じ問題です。サービス1つの資格情報が漏れると、バケット全体が露出します。プレフィックスまで絞っておけば、被害がそのプレフィックスの中に閉じ込められます。そして、これは実装のバグに対する防御でもあります。MinIOのCVE-2025-62506は、セッションポリシーのバイパスで権限が昇格する脆弱性でした。ポリシーを狭く書く習慣は、そうした瞬間に残る最後の防衛線です。
このラボは、AWSと同じIAM API(aws iam ...)で行います。サーバーはSeaweedFSのS3ゲートウェイで、ユーザー・アクセスキー・マネージドポリシー・アタッチを、AWSと同じ呼び出しで受け付けます。そのため、ここで使ったコマンドとポリシー文書は、AWSアカウントにそのまま持っていけます。
ステップ
- 管理者プロファイル
localでaws iam create-user --user-name appuserを実行し、aws iam create-access-key --user-name appuserで受け取ったキーを、プロファイルappに入れてください(endpoint_urlとregionも一緒に)。 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つです。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を書いてください。- バケットポリシーで、
lab-mediaのpublic/プレフィックスだけを、匿名ダウンロード用に開けてください。匿名GETが、public/の下のオブジェクトには200、private/の下のオブジェクトには403である必要があります。/root/pol/anon.txtにpublic=200 private=403を書いてください。
参考
- ユーザー・キー:
aws iam list-users・aws iam list-access-keys --user-name appuser - ポリシー一覧とアタッチの確認:
aws iam list-policies・aws iam list-attached-user-policies --user-name appuser - ポリシーのARNは、手で書かず、
list-policiesで探します。AWSはアカウント番号が入り(arn:aws:iam::123456789012:policy/…)、このサーバーは空なので(arn:aws:iam:::policy/…)、形式が違います。 - 同じ名前で
create-policyをもう一度行うと、EntityAlreadyExistsです。文書を直すときは、aws iam create-policy-version --set-as-defaultを使います。 - バケットポリシー:
aws s3api put-bucket-policy --bucket lab-media --policy file://…・確認はget-bucket-policy - IAMの変更は、すぐには反映されないことがあります。AWSも数秒かかるので、アタッチ直後の確認は、何回かリトライするほうが安全です。
- このサーバーの限界: ユーザーに直接入れるインラインポリシー(
put-user-policy)は、Allowだけを受け付けます(DenyはMalformedPolicyDocument)。マネージドポリシーとバケットポリシーは、Denyも受け付けるので、このラボは、AWSが勧めるマネージドポリシーで行います。 - よくあるミス1:
ListBucketを書き漏らしてしまうこと。オブジェクトは読めるのに、一覧が出ません。2つは別のActionで、Resourceの形式も違います。 - よくあるミス2:
Resourceをバケット全体にしてしまうこと。資格情報1つが漏れると、すべてが露出します。
サービスユーザーを作る
管理者プロファイル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をするたびに、まるごと置き換わります。範囲を変えるときは、文書全体をもう一度入れてください。