AWS Organizations設計で10アカウント管理が崩壊した話|実装を丸ごと作り直した3ヶ月
5アカウントから10アカウントに増えた瞬間、SCP設計が完全に壊れました。削除権限の制御失敗、OU階層の複雑化、ポリシー組み合わせの地獄…実務で学んだ失敗と改善を包み隠さず公開します。
先日、10アカウント管理で設計を丸ごと壊しました
うちのチームがAWS Organizations導入して1年半。最初は5アカウントで細かく管理できてたんですけど、プロダクト増えて10アカウントになった瞬間に統制が崩壊しました。SCP(Service Control Policy)の設計が甘かった。削除権限の制御で本来消していい開発環境のリソースまで引っかかって、チーム全体で毎回「なぜロック掛かってるんだ?」って騒ぐ羽目に。
そこから3ヶ月間、実装を見直して気づいたことが結構あります。教科書通りのOrganizationsって、本番環境では思ったより複雑なんですよね。今日はそのあたりの実務的な話をシェアします。
階層設計を「運用できる粒度」で決めるべき
最初の失敗は、アカウント階層をビジネス単位で分けようとしたことです。うちは「本番」「ステージング」「開発」「セキュリティ」って分けてたんですけど、実際に運用してみたら、プロダクトごとにアカウントを分けたい要件が増えていった。
いま落ち着いたのがこんな構成:
Root (Organization管理アカウント)
├── Security OU
│ ├── Audit Account (CloudTrail、Config、GuardDuty一元化)
│ └── Log Archive (ログ集約)
├── Workload Production OU
│ ├── Product-A Account
│ └── Product-B Account
├── Workload Development OU
│ ├── Dev Account (デプロイ検証用)
│ └── Sandbox Account (個人実験環境)
└── Security Baseline OU
└── Network Account (Transit Gateway、VPC集約)
ここのポイントは「OU(Organization Unit)の数を絞ること」。以前は8個OUがあったんですけど、いまは5個。なぜかというと、各OUごとにSCPを作成・維持する手間が半端ないんです。OUが増えるとポリシー組み合わせがマトリックス爆発する。
実装当初、3個のOUに対して5個のSCPを組み合わせてたんですけど、本来の意図と違う挙動が出始めました。例えば「本番で削除禁止」と「開発で削除許可」を両立させようとしたら、どのポリシーが優先されるのかわからなくなる。そうなると運用が地獄です。
本番環境で「最小限の制御」をするなら、OU数は片手で数えられるくらいが現実的だと痛感しました。
SCPの「明示的な許可」と「暗黙的な拒否」のジレンマ
これが本当に悩ましい。SCPはデフォルト許可で、制限したい項目を拒否するスタイル(Deny-based)が一般的です。でも実務では逆行します。本番アカウントでセキュリティキーの削除を防ぎつつ、開発アカウントではキー削除を許可したい。こういう要件が増えるんですよね。
僕たちの現在の実装がこれ:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyDeleteKMSKey",
"Effect": "Deny",
"Action": [
"kms:ScheduleKeyDeletion",
"kms:DisableKey"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:RequestedRegion": "ap-northeast-1"
}
}
},
{
"Sid": "DenyTerminateProductionEC2",
"Effect": "Deny",
"Action": "ec2:TerminateInstances",
"Resource": "arn:aws:ec2:*:*:instance/*",
"Condition": {
"StringEquals": {
"ec2:ResourceTag/Environment": "production"
}
}
}
]
}
これを本番OUに適用して、開発OUには別のポリシーを適用する。ただここで落とし穴があるんです。SCPはIAMポリシーとは別なんです。SCPで許可しても、IAMで拒否されたら実行できない。逆も然り。つまり、両方確認する必要があるんです。
実装6ヶ月で気づいたんですけど、「なぜか削除できない」って障害報告は、SCPが原因じゃなくてIAMロール設定が間違ってるケースが60%ありました。正直、その時は「SCPチェック漏れだー」って思ってたから、いい勉強になりました。
Control Towerで「自動コンプライアンス」はウソ
正直に言うと、Control Towerは便利です。新しいアカウント作るとき、VPC・CloudTrail・Config・GuardDutyが自動で仕込まれます。ほんと地味に便利。でも「自動コンプライアンス」という謳い文句は、かなり誇大広告です。
実際のところ、Control Towerは「基本的なセキュリティ構成を自動化する」だけなんですよ。本番環境のコンプライアンス要件(例:SOC2、HIPAA、PCI-DSS)に対応しようと思ったら、相当カスタマイズが必要です。
うちが3ヶ月で組んだのがこの構成:
flowchart TB
subgraph ControlTower["Control Tower"]
direction TB
AccountFactory["Account Factory<br/>自動プロビジョニング"]
Guardrails["Guardrails<br/>SCP + IAM"]
end
subgraph CustomCompliance["カスタムコンプライアンス層"]
direction TB
Config["AWS Config<br/>リアルタイムコンプライアンス評価"]
Lambda["Lambda Function<br/>自動修復"]
EventBridge["EventBridge<br/>イベント駆動トリガー"]
end
subgraph Monitoring["監視・ロギング"]
direction TB
CloudTrail["CloudTrail<br/>監査ログ"]
SecurityHub["Security Hub<br/>脅威検知集約"]
Macie["Macie<br/>データ保護"]
end
AccountFactory --> Config
Guardrails --> EventBridge
Config --> Lambda
Lambda --> EventBridge
CloudTrail --> SecurityHub
Macie --> SecurityHub
EventBridge --> Monitoring
style ControlTower fill:#ff9999
style CustomCompliance fill:#99ccff
style Monitoring fill:#99ff99
Control TowerのGuardrailsは基本的な制御です。例えば「S3バケットはデフォルト暗号化」みたいなやつ。でも実務では「S3には特定のタグ付けが必須」とか「CloudFrontの前段は必須」みたいな、より細かい制御が必要になるんです。
そこで活躍するのがAWS Configですね。ConfigルールをEventBridgeと組み合わせると、非準拠のリソースが見つかったときに自動で修復できる。これで初めて「本当のコンプライアンス管理」になります。
import boto3
import json
from datetime import datetime
lambda_client = boto3.client('lambda')
s3_client = boto3.client('s3')
config_client = boto3.client('config')
def lambda_handler(event, context):
# EventBridgeから非準拠リソース情報を受け取る
detail = event.get('detail', {})
resource_id = detail.get('resourceId')
resource_type = detail.get('resourceType')
if resource_type == 'AWS::S3::Bucket':
try:
# S3バケットの暗号化を有効化
s3_client.put_bucket_encryption(
Bucket=resource_id,
ServerSideEncryptionConfiguration={
'Rules': [{
'ApplyServerSideEncryptionByDefault': {
'SSEAlgorithm': 'AES256'
},
'BucketKeyEnabled': True
}]
}
)
# Config評価を再トリガー
config_client.put_evaluations(
Evaluations=[{
'ComplianceResourceType': resource_type,
'ComplianceResourceId': resource_id,
'ComplianceType': 'COMPLIANT',
'Annotation': 'Auto-remediated by Lambda',
'OrderingTimestamp': datetime.utcnow().isoformat() + 'Z'
}],
ResultToken='remediation-token'
)
return {'statusCode': 200, 'body': 'Remediation successful'}
except Exception as e:
print(f'Remediation failed: {str(e)}')
return {'statusCode': 500, 'body': str(e)}
本番運用で気づいたのは、この自動修復もやりすぎるとダメなんですよ。ログ保持期間を自動で変更されたら、意図した監査ログが消えちゃう。だから修復対象は「明らかにセキュリティ的にアウト」な項目(暗号化なし、公開設定)に限定して、それ以外は通知だけにしています。慎重になるべきです。
マルチアカウント統制の現実的な運用
正直、AWS Organizations + Control Tower だけでは足りません。6ヶ月運用で見えた、本当に必要な層はこんな感じです:
1. アカウント作成時の自動セットアップ
Control Towerの Account Factory で新規アカウント作るんですけど、その後の細かいセットアップ(VPC作成、サブネット設計、ルートテーブル設定)は自動化されていません。手作業になってしまう。
そこで CDK + StackSets を使って自動化します:
import * as cdk from 'aws-cdk-lib';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as iam from 'aws-cdk-lib/aws-iam';
export class BaselineStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// 本番VPC作成(所有アカウントで自動適用)
const vpc = new ec2.Vpc(this, 'ProductionVPC', {
cidr: '10.0.0.0/16',
maxAzs: 3,
natGateways: 1,
subnetConfiguration: [
{
subnetType: ec2.SubnetType.PUBLIC,
name: 'Public',
cidrMask: 24,
},
{
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
name: 'Private',
cidrMask: 24,
},
],
});
// CloudTrail用のS3バケット(ログ集約アカウントで共有)
const auditBucket = new s3.Bucket(this, 'AuditLogBucket', {
encryption: s3.BucketEncryption.KMS,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
versioned: true,
lifecycleRules: [
{
noncurrentVersionExpirationInDays: 90,
},
],
});
// ログ集約アカウントへのアクセス許可
const auditAccountId = '123456789012'; // ログ集約アカウントID
auditBucket.addToResourcePolicy(
new iam.PolicyStatement({
sid: 'AllowAuditAccountAccess',
principals: [new iam.AccountPrincipal(auditAccountId)],
actions: ['s3:GetObject', 's3:ListBucket'],
resources: [auditBucket.bucketArn, auditBucket.arnForObjects('*')],
})
);
// タグの強制(Guardrail補完)
cdk.Tags.of(this).add('Compliance', 'required');
cdk.Tags.of(this).add('Environment', props?.description || 'production');
}
}
これをStackSetsで配布すると、新アカウント自動作成の1時間後には本番用VPCが出来上がっています。いい話ですよね。
2. ログ集約と一元監視
セキュリティ系OUの専用アカウント(Audit Account)で、全アカウントのCloudTrail・Config・GuardDutyログを集約します。これが本当に重要。マルチアカウント環境でセキュリティインシデント起きたときに「あのアカウントのログ見てください」なんて言ってたら地獄です。
実装は Lake Formation + Athena で、こんな感じに:
-- マルチアカウント CloudTrail ログクエリ
SELECT
eventtime,
useridentity.accountid,
eventname,
eventsource,
sourceipaddress,
useragent
FROM cloudtrail_logs
WHERE
from_iso8601_timestamp(eventtime) > current_timestamp - interval '7' day
AND eventname IN ('DeleteBucket', 'PutBucketPolicy', 'DisableLogging')
GROUP BY useridentity.accountid, eventname
ORDER BY eventtime DESC;
気づいたのは、ログ集約だけしてもダメなんですよ。アラートも一元化する必要があります。Security Hubを使って、各アカウントのGuardDuty検知を1箇所で見れるようにしました。こうすることで、インシデント検知から対応までの時間が劇的に縮まります。
3. Terraform/CDKのマルチアカウント対応
最初の失敗:各アカウントで別々のTerraformを管理してました。結果、環境間で設定がズレまくりました。もう大変なんですよ。あっちで削除されたリソースがこっちにはまだ残ってるとか、そんなカオスが発生する。
今は assume role で、管理アカウントから各アカウントリソースにアクセスする形にしました:
# 管理アカウント側
provider "aws" {
alias = "production"
assume_role {
role_arn = "arn:aws:iam::PROD_ACCOUNT_ID:role/TerraformRole"
}
}
resource "aws_s3_bucket" "production" {
provider = aws.production
bucket = "prod-bucket-${var.environment}"
}
これによって、管理アカウント側の単一の git リポジトリから全アカウントを統制できます。個人的には、この手法が一番運用しやすいと思ってます。
セキュリティ監査と定期的な見直し
本当に大切なのはここです。オンボーディング完了しても、3ヶ月ごとに設計を見直す必要があります。なぜなら、AWSの新機能・新ベストプラクティスが四半期ごとに出てくるから。放っておくと気づかないうちに陳腐化します。
うちのチームで実装した「設計レビュープロセス」がこちら:
| 頻度 | 内容 | 担当者 |
|---|---|---|
| 月1回 | 脅威検知確認(Security Hubの検知傾向分析) | セキュリティチーム |
| 月1回 | コンプライアンス監査(Configルール評価の失敗件数推移) | インフラチーム |
| 四半期ごと | 設計レビュー(新機能対応・ベストプラクティス更新) | リードエンジニア |
| 半年ごと | ペネテストシミュレーション(SCPの実効性確認) | セキュリティエンジニア |
また、セキュリティ事象の検知から対応までのフローも整備する必要があります。Organizations だけで完結しません。インシデント対応の体制、連絡先、エスカレーションパスなど、そういったものを同時に用意しておく必要があるんです。
まとめ
AWS Organizations のセキュリティ設計、本当のところはこんな感じです:
- OU は「運用できる粒度」で。5個前後が現実的 — 多すぎるとSCP管理が指数爆発する
- Control Tower は基礎。その上に自動修復層を重ねる — ConfigルールとLambdaで本当のコンプライアンス実現
- マルチアカウント IaC 統制が必須 — Terraform/CDK の assume role パターンで統一管理
- ログ集約と一元監視がないと、セキュリティインシデント対応が地獄 — Lake Formation + Athena + Security Hub で対応
- 設計は固定ではなく定期的に見直す — 四半期ごとに新機能・新ベストプラクティス対応を
10アカウント運用でボロボロになった経験があるから、次のマルチアカウント環境では(多分5年以内にきますw)、最初からこの設計で行こうと思ってます。教科書通りじゃなく、本当に運用できる粒度を最優先に。
皆さんのチームではどんなMulti-Account戦略とってますか?何か気づきがあれば、コメントで教えてもらえると嬉しいです。