IAMポリシーで本番を2回止めた話|最小権限を「実装」として向き合うまで
「とりあえずAdministratorAccess」で本番インシデントを起こした経験、ありませんか?Permission BoundaryやIAM Access Analyzerを使って「権限の根拠を言語化できる」設計にたどり着くまでを書きました。
IAMポリシー設計で2回本番を止めた話
正直に言う。IAMの設計は2回本番インシデントを起こしてから、ようやく本気で向き合った。
最初のやらかしは3年前。開発者に「とりあえず動かすため」と言い訳して AdministratorAccess を渡したまま本番環境に残してしまっていた。次のやらかしは別の案件で、サービスロールに s3:* を付与したところ、誰かが別バケットのオブジェクトを誤って削除してしまった。どちらも防げたはずのインシデントだった。
それ以来、IAMポリシーの設計は「動けばいい」じゃなく「権限の根拠を言語化できるか」を基準にするようにした。2026年時点でAWSのIAMには新しい機能も増えているし、以前と比べてかなり設計しやすくなっている。そういう経験を踏まえて、「本当に現場で効いた設計パターン」を書いていく。
教科書的な IAM完全解説 みたいな内容じゃなく、実際に困ったことと、それをどう乗り越えたかに集中したい。セキュリティ全般のベストプラクティスについては インシデント対応の最新ベストプラクティス2026 も参考になるかも。
最小権限の原則は「宗教」じゃなく「実装技術」だ
「最小権限の原則を守りましょう」という話はどこでも聞く。でも実際のチームで「どうやって実装するか」まで語られることは少ない気がする。
2026年現在、IAA(IAM Access Analyzer)の機能が充実してきていて、実際の使用状況から「使っていない権限を削除する」という作業をかなりサポートしてくれるようになった。具体的にはポリシー生成機能を使うと、CloudTrailのログから実際に呼び出されたAPIだけをもとにポリシーを自動生成してくれる。
# IAM Access Analyzerでポリシーを生成
aws accessanalyzer start-policy-generation \
--policy-generation-details \
'principalArn=arn:aws:iam::123456789012:role/MyAppRole' \
--cloud-trail-details \
'{
"accessRole": "arn:aws:iam::123456789012:role/AccessAnalyzerRole",
"trails": [{"cloudTrailArn": "arn:aws:cloudtrail:ap-northeast-1:123456789012:trail/MyTrail", "allRegions": true}],
"startTime": "2026-07-01T00:00:00Z",
"endTime": "2026-07-31T23:59:59Z"
}'
これで生成されたポリシーを「たたき台」にして、手動で調整するアプローチがうちのチームでは定番になっている。最初から手で書くより遥かに現実的な出発点になる。
ただし注意点がある。CloudTrailのログに残るのはあくまで「実行された」アクションだけ。バッチ処理が週1回しか走らないとか、DR系の処理が一度も実行されていないとか、そういうケースで権限が削りすぎになることがある。自動生成したポリシーを鵜呑みにせず、設計意図と照らし合わせる工程は絶対省けない。
条件キーで権限をさらに絞る
権限を絞る手段として、アクション単位だけじゃなく条件キーを使うのが2026年では当たり前になってきた。特に便利なのが aws:RequestedRegion や s3:prefix を使った制限で、「権限はあるけど使えるのはここだけ」という設計が作れる。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowS3OnlyTokyo",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::my-app-bucket/*",
"Condition": {
"StringEquals": {
"aws:RequestedRegion": "ap-northeast-1"
},
"StringLike": {
"s3:prefix": ["uploads/${aws:PrincipalTag/team}/*"]
}
}
}
]
}
この aws:PrincipalTag を使ったABAC(属性ベースアクセス制御)は、チームが大きくなるほど効いてくる。ロールとポリシーの組み合わせ爆発を防げるし、チームのタグさえ正しく付いていれば新しいリソースを追加してもポリシー変更が不要になる。うちのチームでは昨年から全面的に採用していて、ポリシーの管理コストが体感で半分以下になった。個人的にはこれが2026年時点でいちばん「早く知りたかった」機能だと思っている。
Permission Boundaryで「暴走しない権限委任」を実現する
チームが大きくなると「開発者自身がIAMロールを作れるようにしたい」というニーズが出てくる。でも何の制限もなく任せると、開発者が AdministratorAccess 付きのロールを自分で作って使い始める、というホラーが起きる。実際に見たことがある。
これを防ぐのがPermission Boundary。Permission Boundaryは「ロールが持てる最大権限の上限」を定義する仕組みで、これを使うと開発者への権限委任が現実的になる。
flowchart TB
subgraph Admin["管理者(Platform Team)"]
PB[Permission Boundary Policy]
DelegatePolicy[権限委任ポリシー]
end
subgraph Developer["開発者(App Team)"]
DevRole[作成したロール]
AppPolicy[アタッチしたポリシー]
end
subgraph Resource["AWSリソース"]
S3[(S3 Bucket)]
DDB[(DynamoDB)]
SQS[(SQS)]
SSM[(SSM)]
end
PB -->|上限として設定| DevRole
DevRole -->|アタッチ| AppPolicy
AppPolicy -->|有効な権限 = PB ∩ AppPolicy| S3
AppPolicy -->|有効な権限 = PB ∩ AppPolicy| DDB
AppPolicy -->|拒否(PBにない)| SQS
DelegatePolicy -->|iam:CreateRole条件付きで付与| Developer
実際の設定例はこんな感じ。Permission Boundaryポリシー自体はこれが鉄則:「アプリケーションに必要なサービス群は許可するが、IAM操作とOrganizations操作は絶対拒否」
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowAppServices",
"Effect": "Allow",
"Action": [
"s3:*",
"dynamodb:*",
"lambda:*",
"logs:*",
"xray:*",
"ssm:GetParameter*"
],
"Resource": "*"
},
{
"Sid": "DenyIAMEscalation",
"Effect": "Deny",
"Action": [
"iam:CreateUser",
"iam:AttachRolePolicy",
"iam:PutRolePolicy",
"iam:DeleteRolePolicy",
"organizations:*"
],
"Resource": "*"
}
]
}
委任ポリシー側では iam:CreateRole に条件を付けて「このPermission Boundaryが付いてないと作れない」という縛りを入れる。
{
"Sid": "AllowCreateRoleWithBoundary",
"Effect": "Allow",
"Action": "iam:CreateRole",
"Resource": "*",
"Condition": {
"StringEquals": {
"iam:PermissionsBoundary": "arn:aws:iam::123456789012:policy/AppTeamBoundary"
}
}
}
この設計を導入したのが1年前で、それ以来「開発者が勝手に広い権限のロールを作った」というインシデントはゼロになった。地味に便利なのが、Permission Boundary変更は管理者だけができるので「一箇所変えれば全ロールに反映」という管理の一元性も保てる点。正直、もっと早く入れておけばよかったと今でも思っている。
SCPとIAMの組み合わせ設計——どこに何を書くか問題
マルチアカウント構成を運用しているなら、SCPとIAMポリシーをどう使い分けるかで頭を抱えたことがあるんじゃないかと思う。僕は最初、全部SCPに書こうとして死んだ。AWS Organizationsの設計については別の記事でも書いたけど、ここではIAMポリシーとの役割分担に絞って整理する。
xychart-beta
title "SCP vs IAMポリシー 適用レイヤー別の役割"
x-axis ["ガードレール設置", "権限の付与", "監査・ログ", "コスト制御", "リソースタグ強制"]
y-axis "適合度 (1-10)" 0 --> 10
bar [9, 2, 4, 7, 9]
line [3, 10, 8, 3, 5]
棒グラフがSCP、折れ線がIAMポリシーの適合度のイメージ(完全に主観だけど)。
それぞれどの用途に向いているか整理するとこうなる。
| 用途 | SCP | IAMポリシー | 備考 |
|---|---|---|---|
| 特定リージョン外を禁止 | ✅ | △ | SCPが正解。全アカウント強制できる |
| 本番アカウントでのRootログイン禁止 | ✅ | ❌ | SCPでないと防げない |
| 特定S3バケットへのアクセス付与 | ❌ | ✅ | IAMで具体的に設定 |
| タグなしリソース作成禁止 | ✅ | △ | SCP + IAMの条件キー両方使う |
| サービスロールの作成委任 | ❌ | ✅ | Permission Boundaryとセット |
| MFA必須化 | ✅ | ✅ | 両方使うのが堅牢 |
SCPは「やってはいけないこと」のガードレール、IAMは「やっていいこと」の明示的な許可、と分けると混乱が減る。SCPの落とし穴は「Deny」と「未記載による暗黙のDeny」の違いで、SCPに明示的なDenyを書きすぎると後で身動きが取れなくなる。うちのチームはSCPにDenyを書くのは最小限にして、基本的に NotAction で許可リスト方式を採用している。
RCP(Resource Control Policy)も忘れずに
2025年にGAになったRCP(Resource Control Policy)も2026年の設計では外せない。SCPは「IAMプリンシパルへの制限」だけど、RCPは「リソース側への制限」という棲み分けになる。クロスアカウントアクセスを制御するのに使うのが典型的なユースケースで、例えば「うちの組織外からのS3アクセスを全部拒否する」という設定はRCPが一番スッキリ書ける。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyExternalS3Access",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:PrincipalOrgID": "o-xxxxxxxxxx"
},
"BoolIfExists": {
"aws:PrincipalIsAWSService": "false"
}
}
}
]
}
これを組織のルートに適用すると、外部アカウントからのS3アクセスを組織全体でブロックできる。従来はS3バケットポリシーを一個ずつ設定する必要があったのが一元管理できるようになって、これはマジで助かった。SOC2対応でCloudTrail・Config設計に苦労した話でも似たような課題があって、RCP導入後は監査対応もかなり楽になった。
本番で踏んだIAM地雷トップ3
経験則で言うと、IAMのインシデントは「設定が複雑すぎる」より「シンプルすぎる設定が積み重なった」ケースのほうが多い。自分の失敗と周囲で見た事例を整理すると、よく踏まれる地雷はだいたいこの3パターンになる。
地雷1: ワイルドカードリソースの放置
API Gatewayのサービスロールに execute-api:Invoke を付ける際に Resource: "*" で書いてしまい、別のAPIにも呼び出せる状態になっていた、というのは僕が実際にやらかした話。特定のAPI IDとステージを指定するのが正しい。
// 悪い例
"Resource": "arn:aws:execute-api:ap-northeast-1:123456789012:*/*/*"
// 良い例
"Resource": "arn:aws:execute-api:ap-northeast-1:123456789012:abc123def/prod/GET/my-resource"
地雷2: PassRoleの管理不足
iam:PassRole は特別に危険なアクションで、これを誰かに渡すと「自分より強い権限のロールを別のサービスに渡せる」状態になる。必ず渡せるロールを限定するべきで、条件キーで縛るのが必須。
{
"Sid": "AllowPassRoleToLambdaOnly",
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::123456789012:role/MyLambdaExecutionRole-*",
"Condition": {
"StringEquals": {
"iam:PassedToService": "lambda.amazonaws.com"
}
}
}
地雷3: AssumeRoleの信頼ポリシー放置
信頼ポリシー(Trust Policy)のチェックを忘れて「誰でもこのロールを引き受けられる」状態になっているケースを見たことがある。特にCI/CDのためのロールは aws:SourceAccount や sts:ExternalId を使って、想定外のAssumeRoleを防ぐ設計が2026年では標準的になっている。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:myorg/myrepo:ref:refs/heads/main"
}
}
}
]
}
GitHub ActionsからのOIDC AssumeRoleは今や定番だけど、sub の条件を repo:myorg/myrepo:* で広げすぎると、featureブランチからも本番環境にデプロイできてしまう。ブランチを絞るのは地味だけど重要な制限で、これを知らずに広めに設定していたケースは正直かなり見かける。
IAMポリシー設計のチェックリストと全体構成
実際の設計をどう整理しているか、チームで使っている全体構成図と確認リストをまとめておく。
graph TB
subgraph Org["AWS Organizations"]
Root["Root"]
subgraph OU_Prod["Production OU"]
subgraph Account_Prod["本番アカウント"]
subgraph IAM_Layer["IAM設計レイヤー"]
PB["Permission Boundary
AppTeamBoundary"]
IdentityPolicy["Identity-Based Policy
(ロール・グループ)"]
ResourcePolicy["Resource-Based Policy
(S3・KMS・SQS等)"]
end
subgraph Services["AWSサービス"]
Lambda["Lambda"]
S3["S3"]
RDS["RDS"]
SecretsManager["Secrets Manager"]
end
end
end
subgraph OU_Dev["Development OU"]
Account_Dev["開発アカウント"]
end
end
subgraph Policy_Layer["ポリシー適用順序"]
SCP["SCP\n(組織ガードレール)"]
RCP["RCP\n(リソース側制限)"]
SessionPolicy["Session Policy\n(一時的な制限)"]
end
Root -->|SCP適用| OU_Prod
Root -->|RCP適用| OU_Prod
PB -->|上限設定| IdentityPolicy
IdentityPolicy -->|付与| Lambda
ResourcePolicy -->|アクセス制御| S3
ResourcePolicy -->|アクセス制御| SecretsManager
SCP -.->|制限| IAM_Layer
RCP -.->|制限| Services
IAMポリシーの「有効な権限」は、この全レイヤーの論理積(AND)になる。どこか一つに拒否があれば通らない。この構造を頭に入れておくと、「なんでこの操作が通らないんだ」となったときのトラブルシュートが格段に速くなる。
レビューチェックリスト
以下は、うちのチームがPR時に使っているチェックリストだ。ここに書いてある内容は全部「一回やらかした」経験から生まれたもので、形式的なものじゃなく本当に機能している。
-
*リソースに対してWrite系アクションを付与していないか -
iam:PassRoleに渡せるサービスと対象ロールの制限はあるか - 信頼ポリシーに不要なプリンシパルが含まれていないか
- Permission Boundaryなしでロールを作成できる開発者がいないか
- SCPとIAMの組み合わせで意図した権限になっているか(Access Analyzerで確認)
- ABAC(PrincipalTag)を使う場合、タグの付け方のルールが定義されているか
- GitHub ActionsのOIDCは特定ブランチ・リポジトリに絞られているか
- 90日以上未使用の権限がAccess Analyzerのレポートに残っていないか
OWASP Top 10の文脈だとこちらの記事でも触れているけど、不適切なアクセス制御はWebアプリ側のIAM設計ミスと繋がることが多いので、アプリチームともレビューを共有するのがおすすめ。
まとめ
2026年時点でIAMポリシー設計について整理すると、要点はこの5つに収まると思っている。
| # | ポイント | 一言まとめ |
|---|---|---|
| 1 | IAM Access Analyzerのポリシー生成を使う | 手書きより現実的な出発点。ただし鵜呑みにしない |
| 2 | Permission Boundary + 条件キーで権限委任 | 開発者へのIAM委任は必ずPermission Boundaryとセット |
| 3 | SCP・IAM・RCPの役割分担を明確にする | 全部SCPに書くと管理不能になる |
| 4 | 地雷3点セットをレビューチェックに入れる | ワイルドカード・PassRole・信頼ポリシーの3つ |
| 5 | ABACSは大きいチームほど効く | ポリシー爆発を防ぐために早めに移行を検討する |
次のアクションとしては、まず手元のAWSアカウントでIAM Access Analyzerを有効化して「未使用の権限レポート」を見てみることをおすすめする。いきなり設計を変えるんじゃなく、現状把握から始めるのが一番安全。
皆さんのチームではIAMポリシーのレビュープロセスってどう整えてますか?特にPermission Boundaryの導入タイミングは悩みどころで、正直まだベストプラクティスが固まりきっていない部分もあると思うので、知見があればぜひ聞いてみたい。