0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

S3バケットをユーザーごとに"自分のフォルダだけ"許可する IAM ポリシー設計

0
Posted at

概要

S3バケットを複数ユーザーで共有しつつ、各ユーザーが自分のフォルダだけを操作できるよう制限したい——そんなケースに対応するIAMポリシーの設計パターンを、基本のユーザー名ベースからABACタグ活用、バケットポリシーによるガードレール強化まで段階的に解説します。


目次

  1. なぜ "プレフィクス分離" が必要か
  2. 基本パターン:ユーザー名ベースのIAMポリシー
  3. 発展パターン:ABACタグを使った柔軟な制御
  4. ガードレール強化:バケットポリシーで明示Deny
  5. 動作確認の方法
  6. 料金への影響
  7. 運用のヒントと注意点
  8. 終わりに
  9. 参考文献・参考サイト

なぜ "プレフィクス分離" が必要か

例えば写真プリントサービスのバックエンドを考えてみましょう。ユーザー alice と bob が同じ S3 バケット photo-prints-storage を利用するとき、適切な制御がないと alice が bob のファイルを覗いたり上書きしたりできてしまいます。

S3 にはディレクトリという概念は存在しませんが、オブジェクトキーの プレフィクス("alice/"、"bob/" など) を使って論理的なフォルダ構造を表現できます。IAMポリシーのリソース ARN やコンディションにこのプレフィクスを埋め込むことで、「alice は alice/* だけを操作できる」という制御を実現できます。

image.png

ポリシー設計で最初に押さえておくべきポイントは、s3:ListBuckets3:GetObject/s3:PutObject/s3:DeleteObject でリソースの指定方法が異なる点です。

アクション リソース種別 正しい ARN の指定
s3:ListBucket バケットリソース arn:aws:s3:::バケット名
s3:GetObject など オブジェクトリソース arn:aws:s3:::バケット名/プレフィクス/*

この違いを理解していないと、片方だけ許可したつもりが一覧取得できない・逆にオブジェクト操作ができないといった問題に直面します。


基本パターン:ユーザー名ベースの IAM ポリシー

IAM では ${aws:username} というポリシー変数を使うことができます。ポリシーの評価時に呼び出し元の IAM ユーザー名に動的に置換されるため、ユーザーごとに個別のポリシーを用意しなくても「自分のフォルダだけ」に制限できます。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowListOwnPrefixForConsole",
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::photo-prints-storage",
      "Condition": {
        "StringEquals": {
          "s3:delimiter": "/"
        },
        "StringLike": {
          "s3:prefix": [
            "",
            "${aws:username}/",
            "${aws:username}/*"
          ]
        }
      }
    },
    {
      "Sid": "AllowObjectOpsOnlyUnderOwnFolder",
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject"
      ],
      "Resource": "arn:aws:s3:::photo-prints-storage/${aws:username}/*"
    }
  ]
}

ポイント解説

ListBucket は Condition でプレフィクスを縛る

s3:ListBucket はバケット ARN に対して許可するアクションですが、Condition の s3:prefix を使うことで「どのプレフィクス配下の一覧だけ許可するか」を絞れます。"" (空文字列)を含めているのは、コンソールからフォルダをナビゲートする際に最初にルート一覧を取得するリクエストが飛ぶためです。ここを省くとコンソール操作が失敗することがあります。

オブジェクト操作は ARN でプレフィクスを限定

s3:GetObject などのオブジェクト操作は、リソース ARN 末尾に ${aws:username}/* を指定するだけで完結します。Condition は不要です。

このポリシーを IAM グループにアタッチすれば、グループに追加したユーザー全員に同じルールを一括適用できます。


発展パターン:ABAC タグを使った柔軟な制御

${aws:username} は IAM ユーザーには有効ですが、AWS IAM Identity Center(旧 SSO)や SAML フェデレーション経由のユーザーには利用できません。また、ユーザー名が変わるとフォルダ名とのマッピングが崩れるリスクもあります。

そこで活用するのが ABAC(Attribute-Based Access Control) です。IAM ユーザーやロールに folder=alice のようなタグを付与し、ポリシー変数 ${aws:PrincipalTag/folder} でその値を参照します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ListUnderTaggedPrefix",
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::photo-prints-storage",
      "Condition": {
        "StringEquals": { "s3:delimiter": "/" },
        "StringLike": {
          "s3:prefix": [
            "",
            "${aws:PrincipalTag/folder}/",
            "${aws:PrincipalTag/folder}/*"
          ]
        }
      }
    },
    {
      "Sid": "OpsUnderTaggedPrefix",
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
      "Resource": "arn:aws:s3:::photo-prints-storage/${aws:PrincipalTag/folder}/*"
    }
  ]
}
比較観点 ユーザー名ベース ABAC タグベース
利用できるプリンシパル IAM ユーザーのみ IAM ユーザー・ロール・フェデレーション全般
ユーザー名変更への耐性 弱い(フォルダ名ズレ) 強い(タグ値で管理)
大規模組織での運用 ポリシー変数依存で複雑 タグ管理で一元化しやすい
初期設定の手間 少ない タグ付与の仕組みが必要

大規模組織や Identity Center を利用している環境では、ABAC パターンの採用を強く推奨します。


ガードレール強化:バケットポリシーで明示 Deny

IAM ポリシーだけでも上記の設計は機能しますが、「将来誰かが広い権限の IAM ポリシーをうっかりアタッチしても他人のフォルダにアクセスできない」というガードレールをバケット側に置くと、より堅牢になります。

IAM の評価ロジックでは、明示 Deny は必ず Allow に勝ちます。バケットポリシーでの Deny はこの原則を活用した二重の安全網です。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyObjectsOutsideOwnFolder",
      "Effect": "Deny",
      "Principal": {"AWS": "arn:aws:iam::123456789012:root"},
      "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
      "NotResource": "arn:aws:s3:::photo-prints-storage/${aws:PrincipalTag/folder}/*",
      "Condition": {
        "Null": { "aws:PrincipalTag/folder": "false" }
      }
    },
    {
      "Sid": "DenyListWithoutOwnPrefix",
      "Effect": "Deny",
      "Principal": {"AWS": "arn:aws:iam::123456789012:root"},
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::photo-prints-storage",
      "Condition": {
        "ForAllValues:StringNotLike": {
          "s3:prefix": [
            "",
            "${aws:PrincipalTag/folder}/",
            "${aws:PrincipalTag/folder}/*"
          ]
        }
      }
    }
  ]
}

Condition"Null": { "aws:PrincipalTag/folder": "false" } は「folder タグが存在するプリンシパルのみこの Deny を適用する」という意味です。タグのないプリンシパル(管理者ロールなど)を誤ってブロックしないための配慮です。arn:aws:iam::123456789012:root123456789012 は実際の AWS アカウント ID に置き換えてください。

image.png


動作確認の方法

AWS CLI v2 を使った確認例です(ap-northeast-1 リージョン、バケット名 photo-prints-storage)。

# alice として自分のフォルダへのアップロード(成功するはず)
aws s3 cp ./photo.jpg s3://photo-prints-storage/alice/photo.jpg \
  --region ap-northeast-1

# alice として自分のフォルダ一覧取得(成功するはず)
aws s3 ls s3://photo-prints-storage/alice/ \
  --region ap-northeast-1

# alice として他人のフォルダへアクセス(AccessDenied になるはず)
aws s3 ls s3://photo-prints-storage/bob/ \
  --region ap-northeast-1

# alice として他人のオブジェクトを取得(AccessDenied になるはず)
aws s3 cp s3://photo-prints-storage/bob/photo.jpg ./bob_photo.jpg \
  --region ap-northeast-1

テスト時は IAM ポリシーシミュレーター ( https://policysim.aws.amazon.com/ ) も活用すると、実際にリクエストを発行しなくても Allow/Deny の評価結果を事前確認できます。


料金への影響

今回紹介したポリシーの変更自体に追加料金は発生しません。ただし、S3 の利用料金として以下を把握しておきましょう(2025年2月時点、東京リージョン ap-northeast-1)。

課金項目 単価(概算)
ストレージ(S3 Standard、最初の50TB/月) 約 $0.025 / GB・月
PUT/COPY リクエスト 約 $0.0047 / 1,000 リクエスト
GET リクエスト 約 $0.00037 / 1,000 リクエスト
インターネットへのデータ転送(最初の10KB/月) 無料
インターネットへのデータ転送(10KB超) 約 $0.114 / GB

料金は変動する可能性があるため、最新情報は公式料金ページ ( https://aws.amazon.com/jp/s3/pricing/ ) で必ずご確認ください。


運用のヒントと注意点

フォルダ名の末尾スラッシュを忘れずに

alicealice/ は別物です。ListBucket の Condition では "alice/""alice/*" の両方を指定してください。片方を省くとコンソールからのナビゲーションが壊れることがあります。

S3 Object Ownership の設定

現代の S3 運用では、バケットの Object Ownership を Bucket owner enforced(ACL 無効化)に設定することが推奨されています。これにより ACL 経由でオブジェクトの所有権が別ユーザーに移るリスクをなくせます。AWS ドキュメント ( https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/about-object-ownership.html ) では新規バケットでこの設定をデフォルトにすることを推奨しています。

フェデレーテッドユーザーでは ${aws:username} を使わない

AWS IAM Identity Center 経由のユーザーは aws:username が空になるケースがあります。フェデレーション環境では必ず ABAC(PrincipalTag)パターンを採用してください。

既存の広いポリシーに注意

s3:* on arn:aws:s3:::photo-prints-storage/* のような広い Allow が既存ポリシーに残っていると、今回の設計は無意味になります。IAM Access Analyzer ( https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/what-is-access-analyzer.html ) で未使用の広い権限を棚卸しし、削除またはバケットポリシーの Deny でガードしましょう。


終わりに

今回の設計のポイントを整理すると、次の3点に集約されます。

まず、ListBucket はバケット ARN に対してコンディションでプレフィクスを縛り、オブジェクト操作はリソース ARN でプレフィクスを限定する、という2つの制御を分けて考えることが基本です。

次に、フェデレーション環境や大規模組織では ${aws:username} ではなく ${aws:PrincipalTag/xxx} を使う ABAC パターンが運用の安定性につながります。

最後に、IAM ポリシーによる Allow だけで設計し、必要に応じてバケットポリシーの明示 Deny でガードレールを重ねる、この二段構えが実務で安定します。

次のステップとして、IAM Access Analyzer を使った継続的な権限監査の仕組みや、AWS Organizations の SCP(Service Control Policy)との組み合わせによるアカウント横断ガードレールの設計にも挑戦してみてください。


参考文献・参考サイト

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?