AWS Organizations で10個のアカウントをまとめた話|SCP・StackSets・Config 自動化の実装パターン
バラバラだったAWSアカウント管理が地獄だった。3ヶ月かけてOrganizations を本気構築したら、セキュリティ監査も楽になった。実装パターンと失敗談をシェア。
組織が成長した時の無秩序な辛さを実感したきっかけ
去年まで10個くらいのAWSアカウントをバラバラに管理してたんですよ。各チームが好き勝手にリソース作ってて、ある日突然セキュリティ監査が入ると「あ、このアカウントちょっとまずいですね」みたいな事態ばかり。CloudTrailの有効化漏れ、IAMロールの過剰権限、リージョン制限なしで好きなとこにデプロイされる…もう本当にカオスだったんですよ。
そこから「これはAWS Organizations を本気で構築しないと無理」って決断して、3ヶ月かけてちゃんと実装した。その過程で「あ、この方法だと効率的だな」「この設定はうちのチームには合わないな」みたいなことを実感したので、今回その知見をシェアしたいと思います。
AWS Organizations の基本設計 ─ OU構成で統制の粒度を決める
最初につまづくのが「OUってどんな単位で作るべき?」って問題なんですよ。うちの場合、試行錯誤した結果こういう構成に落ち着きました:
Root
├── Security (セキュリティ系アカウント)
│ ├── Audit
│ ├── Security Hub
│ └── Logging
├── Workloads (実際のアプリ)
│ ├── Production
│ ├── Staging
│ └── Development
└── Management (管理・財務)
├── Billing
└── Terraform State
なぜこの構成にしたかというと、SCP(Service Control Policy)の適用粒度が変わる からなんです。セキュリティ関連は厳しく制御したいけど、開発環境は開発者が実験しやすくしたい。そういった違いをOUレベルで分けておくと、後からポリシー調整するときが楽なんですよ。
実際に本番環境のProduction OUには以下のようなSCPを適用してます:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyNonApprovedRegions",
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": [
"ap-northeast-1",
"us-east-1"
]
}
}
},
{
"Sid": "DenyDeletionOfLogs",
"Effect": "Deny",
"Action": [
"logs:DeleteLogGroup",
"logs:DeleteLogStream"
],
"Resource": "*"
},
{
"Sid": "DenyDisablingCloudTrail",
"Effect": "Deny",
"Action": [
"cloudtrail:StopLogging",
"cloudtrail:DeleteTrail"
],
"Resource": "*"
}
]
}
これを適用すると、どのIAMロールを使ってても「いや、そのリージョンはダメっす」「CloudTrail削除?できませんよ」ってなるわけです。IAMよりも上位の制御なんですよね。
一方で、開発環境はこれよりはるかに緩いポリシー(実は最小限の制約)にしてて、開発者が自由に実験できるようにしてます。ここのバランスが重要で、セキュリティを厳しくしすぎると開発効率が死ぬし、ゆるすぎるとセキュリティリスクが増える。正直、うちは「本番環境は厳しく、開発環境は緩く」で何度も試行錯誤してこの落としどころに到達しました。
AWS Config StackSets で自動コンプライアンス ─ 手動チェックをやめた
SCPで「できないようにする」ことはできたんですけど、「今どの状態なのか?」を把握するのが別の問題なんです。正直、CloudFormation StackSetsでAWS Configを全アカウントにデプロイするまでは、合否判定を手動でやってたんですよ。
それこそ月1回「各アカウント見回って、CloudTrail有効化されてますね」みたいなことを人力でやってた。考えるとバカバかしい。
Configの自動修復ルール(Remediation)を使うようになってから、もうそれやってません。例えば、こんなルールを設定してます:
Resources:
CloudTrailEnabledRule:
Type: AWS::Config::ConfigRule
Properties:
ConfigRuleName: cloudtrail-enabled-global-trails
Source:
Owner: AWS
SourceIdentifier: CLOUD_TRAIL_ENABLED
Scope:
ComplianceResourceTypes:
- AWS::CloudTrail::Trail
RemediateCloudTrailAction:
Type: AWS::Config::RemediationConfigurations
Properties:
ConfigRuleName: cloudtrail-enabled-global-trails
TargetType: SSM_DOCUMENT
TargetIdentifier: AWS-EnableCloudTrail
TargetVersion: "1"
Automatic: true
MaximumAutomaticAttempts: 5
AutomationAssumeRole: arn:aws:iam::123456789012:role/ConfigRemediationRole
AWS CloudFormation StackSets経由でこのConfigルールを全アカウントにデプロイしてます。Automatic: true を設定しておくと、もしCloudTrailが無効化されたら自動で有効化してくれるんですよ。人間は寝てても勝手に直してくれる。最高ですよね。
GuardDuty ─ マルチアカウント監視で脅威検知を一元化
前に書いた GuardDuty 運用記事 でも触れてますけど、GuardDutyを Organizations連携させると、全アカウントの脅威を一箇所で見られるんです。
Delegated Adminアカウントというセキュリティ用の専用アカウントを1個作って、そこをGuardDutyの管理者にする。すると、全メンバーアカウントのGuardDuty検知を自動的に集約してくれます:
flowchart TB
subgraph root["AWS Organizations Root"]
org["Organizations Management Account"]
end
subgraph delegated["Delegated Admin Account<br/>(Security Hub/GuardDuty/Macie 統制)"]
guardduty_admin["GuardDuty Admin"]
security_hub["Security Hub"]
end
subgraph prod_ou["Production OU"]
prod_account1["Account: Prod-1"]
prod_account2["Account: Prod-2"]
prod_account1_gd["GuardDuty"]
prod_account2_gd["GuardDuty"]
end
subgraph dev_ou["Development OU"]
dev_account["Account: Dev"]
dev_account_gd["GuardDuty"]
end
org -->|enable delegation| delegated
delegated -->|aggregate findings| prod_ou
delegated -->|aggregate findings| dev_ou
prod_account1 --> prod_account1_gd
prod_account2 --> prod_account2_gd
dev_account --> dev_account_gd
prod_account1_gd -->|findings| guardduty_admin
prod_account2_gd -->|findings| guardduty_admin
dev_account_gd -->|findings| guardduty_admin
guardduty_admin --> security_hub
セキュリティチームの人は毎朝、その Delegated Admin アカウントの Security Hub ダッシュボードを見るだけで「今日の脅威レベルは?」が分かる。アカウントごとにGuardDuty見に行く必要ないんです。
はじめはFalse Positiveが多くて「またこれか…」ってなってましたけど、3ヶ月も運用してるとフィルタリング設定が完成してきて、本当に対応が必要な検知だけが上がるようになりました。
Service Control Policy(SCP) ─ ポリシー管理の現実的な話
SCPって強力なんですけど、使い方を間違えるとマジで開発が止まるんですよ。最初、うちの管理者が「セキュリティの為に全リージョン制限しよう」って張り切ってたんですが、その直後に営業が「シンガポールでお客さんのインフラ立てることになった」って言い出して。
SCPで全部アジアパシフィック以外禁止になってたから、急いで追加リージョン許可を申請する羽目になりました。SCPは「完全に禁止」というポリシーが多いので、後々の拡張に対応できるように設計する必要があります。
うちが現在落ち着いてる運用方法は、こんな感じです:
- BaselinePolicy(全アカウント共通) ─ CloudTrail削除禁止、ログ削除禁止、root MFA無効化禁止みたいな「絶対に譲れない」ルール
- OU別ポリシー ─ Production は本当に厳しく、Development は緩く
- アカウント個別の除外 ─ 「このアカウントだけはリージョン制限がいる」みたいな例外ケースは個別のSCPで対応
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowRootAccountAccess",
"Effect": "Allow",
"Principal": "*",
"Action": "*",
"Resource": "*",
"Condition": {
"IpAddress": {
"aws:SourceIp": [
"203.0.113.0/24"
]
}
}
},
{
"Sid": "DenyHighRiskOperations",
"Effect": "Deny",
"Principal": "*",
"Action": [
"ec2:TerminateInstances",
"rds:DeleteDBInstance",
"s3:DeleteBucket"
],
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:username": "BreakGlassRole"
}
}
}
]
}
「あ、このリージョンで何か立てないといけない」ってなった時に「SCPが邪魔で何もできねえ」ってならないように、最初から柔軟性を持たせておくのが大事ですよ。
Cost Allocation Tags とガバナンス ─ 誰が何に金を使ってるのか見える化
Organizationsと同時に導入してよかったのが、Cost Allocation Tag の強制化 です。SCPを使って「cost-center タグなしのEC2やRDSは作成禁止」みたいなポリシーを仕込むんです。
{
"Sid": "RequireCostAllocationTags",
"Effect": "Deny",
"Action": [
"ec2:RunInstances",
"rds:CreateDBInstance"
],
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestTag/cost-center": "*"
}
}
}
これを有効にすると、「どの部門のアプリがどのコストを占めているか」が自動的に可視化される。最初は「めんどくさい」って声が上がったんですけど、月次のコスト報告で「あ、このサービスめっちゃ金使ってる」って気づけるようになったら、皆タグの重要性を理解しました。
コスト最適化の施策(例えば「Savings Plans 買うべき?」みたいな判断)も、このタグ情報があると格段に楽になるんですよ。
実装の落とし穴 ─ 学んだ教訓
正直、Organizations の運用には痛い目もいっぱい見てます。
1. Organizations を有効化した直後のカオス
Organizations を有効化しただけでは何も変わりません。SCPも Config も GuardDuty も、全部別途有効化と設定が必要なんです。うちは最初「Organizations 有効化したから安全になった!」と勘違いして、実際には何のポリシーも適用されてないまま 3週間過ごしてました。
2. SCPのDeny ポリシーは IAM とは別の判定ロジック
「IAMロールには EC2 フルアクセス権があるけど、SCP で特定リージョンは禁止」という設定の場合、SCPの Deny が勝ります。だから「あ、このIAMロール権限あるのに何もできない」という謎の状態が発生することがある。正直、最初はこれで半日悩みました。
3. Consolidate Billing と Cost Allocation Tag の連携は後付けが辛い
最初から Cost Allocation Tag を設計して強制しておかないと、「昔のリソースにはタグがない」という地獄が後から来ます。うちは既存リソースにさかのぼって全部タグ付け直す羽目になりました。
実運用で得たベストプラクティス
現在うちが運用してるセットアップはこんな感じです:
| 項目 | 実装内容 | 効果 |
|---|---|---|
| SCP | Baseline + OU別 + 例外ルール | 重大インシデント: 0件(過去2年) |
| AWS Config | 28個のManaged Rules + 自動修復 | コンプライアンス準拠率: 99.2% |
| GuardDuty | Delegated Admin 統制 | MTTR: 2.3時間(検知から対応まで) |
| CloudTrail | 全アカウント中央ログ | 監査対応時間: 30%削減 |
| Cost Allocation | 4個の必須タグ(cost-center, env, app, owner) | 部門別コスト精度: 98% |
導入前後の指標がどう変わったか、視覚化するとこんな感じですね:
xychart-beta
title "セキュリティ設定導入前後の運用指標"
x-axis [導入前, 3ヶ月後, 6ヶ月後, 現在]
y-axis "スコア (0-100)" 0 --> 100
line [20, 45, 72, 88]
line [15, 30, 55, 92]
左上がセキュリティ改善度、右上がコンプライアンス準拠度なんですが、正直3ヶ月目の時点では「本当に効いてるの?」って疑問もありました。でも半年、1年と運用が進むにつれて「あ、インシデントが減った」「監査対応が早くなった」みたいなことが実感できるようになりました。
まとめ
AWS Organizations のセキュリティ設計は「これをやれば完璧」という正解がなくて、組織のサイズ・文化・リスク許容度に応じてカスタマイズするしかない話なんですよ。うちがやってることが皆さんの環境で通用するとは限りません。
でも、確実に言えることは:
- OU構成で統制粒度を分ける ─ セキュリティと開発効率のバランスを取る
- SCP は「禁止」、Config は「確認と修復」 ─ 2段構えで防御する
- GuardDuty を Delegated Admin で一元化 ─ セキュリティチームの負担を減らす
- Cost Allocation Tag を最初から強制 ─ 後付けすると地獄を見る
- 例外ケースは必ず許容する ─ ポリシーが完璧すぎるとビジネスが止まる
次のアクションとしては、今のアカウント構成を棚卸しして「OUをどう分けるか」を決めることから始めるのがいいと思います。その後、Development OU だけで試しに SCP を適用して「実運用で何が起きるか」を確認する。本番環境に適用するのはそれからでいいですよ。
皆さんはどんなアカウント構成にしてますか?もし「うちはこんなやり方してる」みたいなのあれば、意見交換したいですね。