マルチアカウント環境の整備に合わせて、SCP (サービスコントロールポリシー) を自己流で作成しました。
組織からの離脱や CloudTrail の停止など、「されては困ること」を Deny する構成で、それなりに守れているつもりでいました。
その後 AWS 公式サンプル を発見して読み直したところ、自分の SCP に抜けが複数あることに気付きました。
自己流では観点が抜け漏れやすいという反省と、公式サンプルという "標準ライブラリ" の参照を推奨する意味を込めて書き残しておきます。
本記事は、IAM Identity Center と AWS Organizations を整理していくシリーズの一つです。
1. SCP とは?
SCP (Service Control Policy) は、AWS Organizations で利用できるポリシーの一つです。
IAM ユーザーやロールに対するアクセス許可を一元的に制御する「ガードレール」として機能します。
Organizations のポリシーには、SCP のほかに RCP (リソースコントロールポリシー) や Tag Policy などがあります。
そのなかで SCP は、アクセス許可の上限を定めるガードレール として基本的な役割を担います。
Allow list と Deny list
SCP の運用方針には、大きく 2 つのアプローチがあります。
-
Allow list 方式:
FullAWSAccessを外し、許可する操作だけを明示的に列挙する -
Deny list 方式:
FullAWSAccessを残したまま、禁止したい操作を Deny で追加する
Allow list は厳密ですが、許可する操作を全て記載する必要があるため、運用負荷は高くなります。
Deny list は既存の FullAWSAccess をベースに「これだけはやらせない」を追加していく形で、今回はこちらで進めます。
ポリシー数とサイズの制限
SCP には見落としがちな制限があります。
- 1 エンティティ (Root / OU / Account) にアタッチできる SCP は 最大 5 件 (AWS 管理の
FullAWSAccessも 1 枠を使う) - 1 つの SCP のサイズは 5,120 文字 まで
この制限は、後の章でポリシーを統合するかどうかの判断材料にもなります。
2. 自己流で考えていた SCP
マルチアカウント環境の整備に合わせて、組織全体と Workloads OU の 2 レベルで SCP を書きました。
そして「停止・削除」系の操作を防ぎました。
- 組織からの離脱 (
organizations:LeaveOrganization) - CloudTrail, GuardDuty, AWS Config の無効化 (
DeleteTrail,DeleteDetector,StopConfigurationRecorderなど) - IAM Identity Center アカウントインスタンスの作成 (
sso:CreateInstance) - IAM ユーザーの作成 (
iam:CreateUser/iam:CreateAccessKey)
IAM Identity Center のインスタンスについてはこちらを参照ください。
「されては困ること」を列挙して Deny したので、これで守れているつもりでいました。
しかし実際は、これではまだまだ守れないのでした。
停止・削除を塞いでも、設定変更で実質無効化できる
公式ドキュメントと AWS 公式サンプル を読み直したところ、自分の SCP には 設定変更系の抜け道 が複数残っていることに気が付きました。
以下のように回避すると、「されては困ること」を実現できてしまいます。
CloudTrail
-
UpdateTrailで対象リージョンを絞り込む、S3 バケットを差し替える → ログが実質止まる -
PutEventSelectorsで Management / Data イベントを空にする → 証跡が記録されない
GuardDuty
-
UpdateDetectorでEnable: falseにする → 削除せずに検出を停止できる
Config
-
DeleteDeliveryChannelで配信先を消す → 記録は動いても外部に出ない
IAM ユーザー禁止 (IAM Identity Center でのログインに寄せるケース)
-
CreateLoginProfileで既存 IAM ユーザーにパスワードを付与する → コンソールでログイン可能になる
「停止・削除を塞いだだけ」では守れていません。
自分の SCP を "ガードレール" と呼んでいいのか、怪しくなってきました。
3. 公式サンプルの 6 カテゴリで点検する
抜けに気付いた後、aws-samples/service-control-policy-examples を真面目に読み直しました。
このリポジトリは 8 カテゴリで SCP のサンプルを整理しており、冒頭には "Recommended Starting Points" として 6 つの基礎 SCP が示されています。
- メンバーアカウントの離脱防止
- 承認済みリージョンの強制
- ルート認証情報のブロック
- ルートユーザーのアクション制限
- セキュリティサービスの無効化防止
- 機微な S3 バケットの保護
自分の SCP をカテゴリで見直してみると、実装済みはカテゴリ 1 と 5 の一部だけでした。
6 カテゴリ中 2 つ しか埋まっていなかったのです。
そこで、抜けを埋めた強化版に加え、リージョン強制 (カテゴリ 2) とルート認証情報ブロック (カテゴリ 3) を追加で実装することにしました。
実装と見送りの整理
最終的な実装状況と判断理由をまとめます。
| # | カテゴリ | 実装状況 | 理由 |
|---|---|---|---|
| 1 | メンバーアカウントの離脱防止 | 実装 | Sid DenyLeaveOrganization
|
| 2 | 承認済みリージョンの強制 | 実装 | ap-northeast-1 単一運用を明示的に強制 |
| 3 | ルート認証情報のブロック | 実装 | ルートは一元管理済みだが保険として追加 |
| 4 | ルートユーザーのアクション制限 | 不要 | カテゴリ 3 で一括 Deny するため重複 |
| 5 | セキュリティサービスの無効化防止 | 実装 (強化版) | 次章で詳細 |
| 6 | 機微な S3 バケットの保護 | 不要 | 現時点で該当するバケットなし |
6 カテゴリを全部入れる必要はなく、環境に応じて「なぜ入れる / 入れないか」を言語化できる状態が大事だと感じました。
4. SCP を 1 本に統合した設計
実装にあたって悩んだのが、新しく追加する SCP を 別ファイルに分けるか、既存に束ねるか でした。
1 本に統合する判断
結論として、Root に「組織全体のガードレール」を 1 本 (OrganizationGuardrailsSCP) に集約し、Workloads OU 固有のものは別 SCP (WorkloadsSCP) に分ける構成にしました。
- SCP は 1 エンティティあたり 5 件までの制限があり、分けすぎると枠を圧迫する
- 「組織全体のガードレール」という 1 コンセプトで整理でき、コンソール上の見通しも良くなる
- Sid で目的を区切れば可読性は保てる
SCP 一覧を見ると、FullAWSAccess (AWS 管理ポリシー) に加えて、OrganizationGuardrailsSCP と WorkloadsSCP の 2 本が並んでいます。
OrganizationGuardrailsSCP (Root)
Root にアタッチする統合版の SCP です。
公式サンプルからの差分
自分の環境に合わせて変えた部分を抜粋します。JSON 全文は 公式サンプル を参照してください。
CloudTrail / GuardDuty / Config に設定変更系を追加
{
"Sid": "DenyDisableCloudTrail",
"Effect": "Deny",
"Action": [
"cloudtrail:StopLogging",
"cloudtrail:DeleteTrail",
"cloudtrail:UpdateTrail",
"cloudtrail:PutEventSelectors"
],
"Resource": "*"
}
同様に GuardDuty には UpdateDetector / DisassociateFromMasterAccount / StopMonitoringMembers / DeleteMembers を、
Config には DeleteDeliveryChannel / PutConfigurationRecorder / PutDeliveryChannel を追加しています。
Region: ap-northeast-1 のみに絞る
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": ["ap-northeast-1"]
}
}
Root ブロック: ArnLike + AssumedRoot 条件
"Condition": {
"ArnLike": {
"aws:PrincipalArn": "arn:aws:iam::*:root"
},
"Null": {
"aws:AssumedRoot": "true"
}
}
コンテンツタブで JSON を確認できます。
適用の過程で気付いたこと
適用時の気付き
- 公式サンプルのなかでも
StringLikeとArnLikeが混在しており、AWS のポリシーエディタではArnLike推奨の警告が出ました。エディタの検証は必ず通すのが実務上の正解と言えます - ルート認証情報ブロックで
Null: aws:AssumedRoot = trueが欠けていると、Organizations のルート一元管理機能自体が SCP で止まってしまいます - リージョン SCP の盲点: CloudFront / IAM 等のグローバル系は
NotActionで救済されています。Lambda@Edge (us-east-1 の Lambda が必須)、DynamoDB Global Tables、S3 CRR など「他リージョンを伴う機能」を使う場合は、該当サービスをNotActionに追加してください (ap-northeast-1 単一運用なら現状のサンプルで十分)
Config の PutConfigurationRecorder / PutDeliveryChannel を Deny すると、Config の初期セットアップも塞がれます。
Control Tower などで組織管理している前提の構成です。メンバーアカウントで手動セットアップする運用では、Condition で管理者ロールを除外するか、該当 Action を外してください。
WorkloadsSCP (Workloads OU)
Workloads OU にのみアタッチする SCP です。
"Action": [
"iam:CreateUser",
"iam:CreateAccessKey",
"iam:CreateLoginProfile"
]
元は CreateUser と CreateAccessKey だけでしたが、CreateLoginProfile を追加しました。
これがないと、IAM ユーザーの作成は塞げてもコンソールログイン用パスワードの付与で抜けられてしまいます。
まとめ
SCP を自己流で書くと「停止・削除」系だけに目が行きがちです。
Update* や Put* といった設定変更系まで塞いで、初めて "無効化防止" と呼べるガードレールになります。
AWS 公式サンプルは、SCP の "標準ライブラリ" として最初に目を通すのがおすすめです。
ただし鵜呑みにせず、ポリシーエディタの検証を通すことで、サンプル自体の微妙な齟齬にも気付けます。
そして、6 カテゴリを全て入れる必要はありません。
「なぜ入れる / 入れないか」を言語化できる状態こそが、ガードレールを自分の環境に合わせて育てる第一歩であると感じました。
今日も小さな学びを。
IAM Identity Center (& AWS Organizations) 関連記事