AWS6分で読める

AWS Control Tower の SCP 運用ガイド — 予防的コントロールの実体とハマりどころ

AWS Control Tower の予防的コントロールが実際に生成する SCP(aws-guardrails-*)の中身、ドリフトの扱い、独自 SCP との併用設計、リージョン拒否コントロールの落とし穴までを、運用視点で整理します。

#aws#control tower#scp#organizations#governance#security

Control Tower を入れた直後によくある混乱が「SCP はどこで管理するのが正解なのか」です。 Organizations のコンソールには aws-guardrails-a1b2c3 のような見覚えのないポリシーが並び、編集しようとすると Control Tower 側がドリフトを検知する。かといって独自の制限も入れたい——という状態です。

この記事では、Control Tower の予防的コントロール(Preventive control)が実際に何を作るのかを起点に、触ってよい範囲・触ってはいけない範囲、独自 SCP との併用設計、そして本番で刺さりやすいハマりどころを整理します。

SCP そのものの設計思想(何を Deny すべきか、段階適用の考え方)は AWS Organizations の SCP で予防的ガードレールを運用する にまとめています。本記事はその「Control Tower 環境版」という位置づけです。


1. Control Tower のコントロールと SCP の関係

Control Tower のコントロールは実装方式で 3 種類に分かれます。

種別実装挙動
予防的(Preventive)SCP / RCP / 宣言型ポリシーAPI 呼び出しそのものを拒否する
発見的(Detective)AWS Config ルール違反したリソースを検出して報告する
事前予防的(Proactive)CloudFormation Hooksデプロイ前にテンプレートを検査する

このうち SCP を作るのは予防的コントロールだけです。発見的コントロールは Config ルール側の話なので、SCP を探しても何も見つかりません(Config ルールの運用は AWS Config Rule の実装と運用設計 を参照)。

コントロールはさらにガイダンスで 3 段階に分かれます。

  • 必須(Mandatory): ランディングゾーン作成時に全登録 OU へ自動適用され、無効化できない
  • 強く推奨(Strongly recommended): 任意。OU 単位で有効化
  • 選択的(Elective): 任意。要件次第で入れる

必須コントロールの多くは「Control Tower 自身の土台を壊させない」ためのものです。Log Archive アカウントの S3 バケットに対する暗号化設定・ロギング設定・バケットポリシーの変更禁止、CloudTrail / Config の設定変更禁止、Control Tower が作成した IAM ロールの変更禁止などが該当します。つまり監査ログを消して証跡を消す動きを、IAM ではなく組織階層で塞いでいるわけです。


2. aws-guardrails-* という SCP の正体

予防的コントロールを有効化すると、Control Tower は管理アカウントの Organizations に aws-guardrails-<ランダム文字列> という名前の SCP を作り、対象 OU にアタッチします。

現物は CLI で確認できます。

BASH
# OU にアタッチされている SCP を一覧する
aws organizations list-policies-for-target \
  --target-id ou-1234-abcdefgh \
  --filter SERVICE_CONTROL_POLICY \
  --query 'Policies[].{Name:Name,Id:Id,Managed:AwsManaged}' \
  --output table

# 中身を読む
aws organizations describe-policy \
  --policy-id p-abcd1234 \
  --query 'Policy.Content' --output text | jq .

ここで押さえておきたい性質が 3 つあります。

① 1 SCP = 1 コントロールではない Control Tower は複数のコントロールのステートメントを 1 本の SCP に詰め込みます。コントロールを追加すると既存の aws-guardrails-* に Sid が足され、文字数が上限に近づくと新しい SCP が作られます。「コントロール数 = SCP 数」だと思っていると、後述する枠の計算を間違えます。

② 名前を変えると Control Tower が見失う aws-guardrails-XYZ を別名にリネームすると、Control Tower はそのポリシーを自分のものと認識できなくなります。厄介なのは、その状態でコントロールを無効化したときに Control Tower は「削除完了」と報告するのに、実際のステートメントはリネーム後の SCP に残り続けることです。無効化したつもりの制限が効き続ける、という一番デバッグしづらい壊れ方をします。リネームは絶対にしないでください。

③ 手で編集する前提になっていない 中身を直接書き換えると「マネージド SCP への意図しない更新」としてドリフト扱いになります(次章)。Control Tower が管理する SCP は読むためのものであり、変更は必ず Control Tower 側(コントロールの有効化・無効化、ランディングゾーンの更新)から行います。


3. ドリフトの扱い(2025〜2026 で変わった部分)

SCP に関して Control Tower が報告するドリフトは、現在は次の 2 種類です。

DriftType発生条件解消方法
SCP_UPDATEDマネージド SCP の内容が Control Tower 外で更新されたOU の再登録(Re-register OU)、またはランディングゾーンの更新
SCP_DETACHED_FROM_OUマネージド SCP が登録済み OU からデタッチされた同上

2025 年 8 月 20 日のアップデートで、「マネージド SCP が他の OU にアタッチされた」「メンバーアカウントに直接アタッチされた」の 2 種類はドリフトとして扱われなくなりました。Organizations 側で OU を触ったときに毎回ドリフト通知が飛んでいた運用は、ここでかなり楽になっています。古い記事の情報のまま「アタッチ位置を触るとドリフトする」と身構えている場合は、前提を更新しておくとよいです。

もう 1 つ注意点として、RCP や宣言型ポリシーで実装されたコントロールには CONTROL_INEFFECTIVE(コントロールポリシードリフト)という別系統のドリフトがあり、こちらは ResetEnabledControl API で解消できます。この仕組みは SCP ベースのコントロールには適用されません。SCP のドリフトは今も OU 再登録かランディングゾーン更新が正攻法です。

BASH
# 有効化済みコントロールの状態を確認(SCP 実装のものだけ見る)
aws controltower list-enabled-controls \
  --target-identifier arn:aws:organizations::123456789012:ou/o-example/ou-1234-abcdefgh \
  --query 'enabledControls[].{Control:controlIdentifier,Status:statusSummary.status,Drift:driftStatusSummary.driftStatus}' \
  --output table

通知経路も変わっています。ランディングゾーン 4.0 以降では、ドリフト通知が SNS から管理アカウントの EventBridge へ移行しました。source: aws.controltower / detail-type: Drift Detected のルールを作って Slack なりに流す形に組み替えておきます。SNS トピックだけを監視している既存の仕組みは、LZ 4.0 に上げた時点で無言になるので注意してください。


4. 独自 SCP との併用設計

Control Tower のコントロールだけで要件が満たせることはまずないので、独自 SCP を併用することになります。ここが設計の本体です。

4-1. クォータの前提が 2026 年 5 月に変わった

長らく「1 ノード(Root / OU / アカウント)あたり SCP は 5 つまで、1 ポリシー 5,120 文字まで」が設計上の強い制約でした。Control Tower がそのうち複数枠を消費するため、独自 SCP に使える枠が実質 2〜3 本しかない、というのが典型的な悩みどころでした。

2026 年 5 月のアップデートで、この上限が 1 ノードあたり 10 ポリシー / 10,240 文字に引き上げられています。全商用リージョン・GovCloud・中国リージョンで、申請不要で適用されます。

ただし「枠が倍になったから何も考えなくてよい」にはなりません。設計の勘所は変わらないので、次を守っておくと後で困りません。

  • 継承を使って枠を分散する: 上限は「そのノードに直接アタッチされた数」で数えます。親 OU から継承された SCP は子ノードの枠を消費しません。全社共通の Deny は Root 寄り、環境固有の Deny は個別 OU、という置き方が素直です
  • 独自ポリシーは束ねる: 「1 要件 1 SCP」で作ると枠も文字数も無駄に食います。目的別に 2〜3 本(例: deny-org-tampering / deny-region-and-service / deny-prod-specific)へ集約します
  • 文字数を意識して書く: Sid は短く、共通条件はステートメントを統合し、不要な空白・改行は削る。JSON の整形は Git 側で保ち、適用時にミニファイする運用が扱いやすいです

4-2. Deny リストで書く(NotAction は慎重に)

FullAWSAccess を外して許可リスト方式に倒す設計は、Control Tower 環境では基本的に勧められません。Control Tower・StackSets・Account Factory が内部で呼ぶ API まで巻き込んで壊れます。FullAWSAccess は付けたまま、Deny を積むのが原則です。

4-3. 独自 SCP のテンプレート

実務で最初に入れることが多いのは「組織の統制そのものを壊す操作」の禁止です。除外条件の書き方も含めて例を挙げます。

JSON
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyOrgTampering",
      "Effect": "Deny",
      "Action": [
        "organizations:LeaveOrganization",
        "organizations:DeletePolicy",
        "organizations:DetachPolicy",
        "organizations:DisablePolicyType",
        "account:CloseAccount"
      ],
      "Resource": "*"
    },
    {
      "Sid": "DenySecurityBaselineTampering",
      "Effect": "Deny",
      "Action": [
        "cloudtrail:StopLogging",
        "cloudtrail:DeleteTrail",
        "guardduty:DeleteDetector",
        "guardduty:DisassociateFromMasterAccount",
        "securityhub:DisableSecurityHub"
      ],
      "Resource": "*",
      "Condition": {
        "ArnNotLike": {
          "aws:PrincipalArn": [
            "arn:aws:iam::*:role/AWSControlTowerExecution",
            "arn:aws:iam::*:role/stacksets-exec-*",
            "arn:aws:iam::*:role/AWSAFTExecution"
          ]
        }
      }
    }
  ]
}

除外している 3 つのロールが重要です。

  • AWSControlTowerExecution: Control Tower が各メンバーアカウントに対して操作するためのロール。これを塞ぐとアカウント登録・OU 再登録・ランディングゾーン更新が失敗します
  • stacksets-exec-*: CloudFormation StackSets の実行ロール。共通基盤を StackSets で配っている場合、これを塞ぐとデプロイが黙ってこけます
  • AWSAFTExecution: Account Factory for Terraform(AFT)を使っている場合のカスタマイズ実行ロール

新規アカウントのプロビジョニングが途中で失敗するという症状の相当数が、この除外漏れです。SCP を追加したあとは必ず Sandbox OU で新規アカウントを 1 つ作ってみるのが確実な検証になります。

なお、条件キー名は大文字小文字を区別しないため aws:PrincipalArnaws:PrincipalARN はどちらでも動きます(AWS のドキュメント上の SCP サンプルでは後者の表記が使われています)。


5. SCP の評価で誤解されやすい 4 点

① 管理アカウントには効かない SCP は管理アカウント(旧マスターアカウント)のユーザー・ロールには一切適用されません。「Root OU に付けたから全部守られている」は誤りです。管理アカウントの保護は IAM ポリシー、MFA、アクセス経路の制限で別途組む必要があります。裏返せば、SCP で全社を締めたときの最後の避難経路が管理アカウントになります。

② サービスリンクロールには効かない SLR による操作は SCP の対象外です。SLR 経由で動くサービスを SCP で止めようとしても止まりません。

③ SCP は「許可」しない SCP は権限の上限(Permissions boundary 的な役割)を決めるだけで、単体では何も許可しません。実際の許可は IAM 側にあります。「SCP で Allow を書いたのに使えない」は IAM 側の不足です。

④ 階層のすべてで許可が必要 Root → 親 OU → 子 OU → アカウントのすべての階層で許可されて初めて実行できます。どこか 1 階層で Deny されれば終わりです。デバッグ時は「どの階層の、どのポリシーの、どの Sid か」まで特定してください。


6. リージョン拒否コントロールの実物と落とし穴

データレジデンシー要件でよく使われるのがリージョン拒否コントロールです。2 種類あります。

  • GRREGIONDENY(ランディングゾーンのリージョン拒否): 有効化するとランディングゾーン全体に効く
  • CT.MULTISERVICE.PV.1(OU のリージョン拒否): OU 単位で適用でき、ExemptedPrincipalARNsExemptedActions でパラメータ指定できる

生成される SCP は Deny + NotAction という形をしています。実際の構造はこうです。

JSON
{
  "Sid": "GRREGIONDENY",
  "Effect": "Deny",
  "NotAction": [
    "iam:*", "organizations:*", "sts:*", "kms:*", "route53:*",
    "cloudfront:*", "config:*", "support:*", "sso:*", "waf:*",
    "shield:*", "globalaccelerator:*", "health:*", "budgets:*",
    "ce:*", "cur:*", "tax:*", "billing:*",
    "s3:ListAllMyBuckets", "s3:GetBucketLocation",
    "ec2:DescribeRegions"
  ],
  "Resource": "*",
  "Condition": {
    "StringNotEquals": {
      "aws:RequestedRegion": ["ap-northeast-1", "us-east-1"]
    },
    "ArnNotLike": {
      "aws:PrincipalARN": [
        "arn:aws:iam::*:role/AWSControlTowerExecution"
      ]
    }
  }
}

※ 実際の NotAction リストは 80 項目以上あり、Control Tower のバージョンによって増減します。上記は構造を示すための抜粋です。

運用で効いてくるポイント。

  • グローバルサービスは NotAction で除外されている。IAM、Organizations、STS、Route 53、CloudFront、請求系などはどのリージョンでも通ります。これらは実質 us-east-1 に対する呼び出しなので、除外しないと何もできなくなるためです
  • ホームリージョンは統治対象から外せない。「東京だけに絞りたいがホームは us-east-1 で作ってしまった」というケースでは、ランディングゾーンを作り直す以外の手がありません。ホームリージョンの選択は後戻りできない意思決定として扱ってください
  • リストにないアクションは拒否される。新しいグローバルサービスや、既存サービスの新 API が NotAction に載っていない期間は素直に落ちます。ランディングゾーンのバージョンアップで解決することが多いので、原因不明の AccessDenied は「Control Tower のバージョンが古い」ことを疑う価値があります
  • aws:RequestedRegion の限界を継承する。この条件キーで正しく判定できないサービス(呼び出し元と実際の処理リージョンが一致しないもの)は、意図通りに制御できません

7. 拒否イベントの調査方法

SCP のデバッグは「誰の、どの API が、どのポリシーで落ちたか」を CloudTrail から拾うのが基本です。IAM ポリシーシミュレーターは SCP を評価しないので、事前検証には使えません。実際に呼んで、ログを見るしかありません。

SCP による拒否は errorMessagewith an explicit deny in a service control policy という文字列が入ります(RCP の場合は resource control policy)。

CloudWatch Logs に CloudTrail を流している場合:

fields @timestamp, userIdentity.arn, eventSource, eventName, errorMessage
| filter errorCode = "AccessDenied"
| filter errorMessage like /service control policy/
| stats count(*) as denies by eventSource, eventName, userIdentity.arn
| sort denies desc
| limit 50

Athena(Log Archive アカウントの CloudTrail テーブル)の場合:

SQL
SELECT
  useridentity.arn AS principal,
  eventsource,
  eventname,
  count(*) AS denies
FROM cloudtrail_logs
WHERE errorcode = 'AccessDenied'
  AND errormessage LIKE '%service control policy%'
  AND from_iso8601_timestamp(eventtime) > current_timestamp - interval '7' day
GROUP BY 1, 2, 3
ORDER BY denies DESC
LIMIT 50;

このクエリは新しい SCP を本番 OU に広げる前の必須ステップです。Sandbox / Dev OU に先行適用して 1〜2 週間ぶん回し、上記で拾った拒否を 1 件ずつ「意図した拒否か / 業務を止める誤爆か」に仕分けしてから次の OU へ進めます。


8. IaC 化とレビュー

コントロールと独自 SCP は管理系統が違うので、それぞれ別の方法で Git 管理します。

Control Tower のコントロールは CloudFormation の AWS::ControlTower::EnabledControl で宣言できます。

YAML
Resources:
  DenyRootUserActions:
    Type: AWS::ControlTower::EnabledControl
    Properties:
      ControlIdentifier: !Sub "arn:aws:controltower:${AWS::Region}::control/AWS-GR_RESTRICT_ROOT_USER"
      TargetIdentifier: !Ref WorkloadsOuArn

Terraform の場合は aws_controltower_control リソース、あるいは AWS が公開している aws-control-tower-controls-terraform のサンプル構成が参考になります。

独自 SCPaws_organizations_policy / aws_organizations_policy_attachment(CloudFormation なら AWS::Organizations::Policy)で管理します。ポリシー JSON は別ファイルに切り出し、PR の差分で「何を新たに禁止したか」がレビューできる形にしておくのが要点です。SCP は影響範囲が広いので、変更理由(どのインシデント・どの監査要件に対応するのか)をコミットメッセージに残す運用を最初から徹底しておくと、数年後の棚卸しで効いてきます。


9. ブレークグラス設計

SCP は「誰も解除できない」状態を作れてしまうので、解除経路を先に決めておきます。

  1. 解除できるのは管理アカウントだけ(SCP が効かないため)。管理アカウントに MFA 必須・限定メンバーのみの緊急用ロールを用意する
  2. 緊急時の手順を明文化する: どの SCP をデタッチするのか、誰が承認するのか、何時間以内に戻すのか
  3. 解除操作を検知する: 管理アカウントの organizations:DetachPolicy / organizations:UpdatePolicy を EventBridge で拾い、即座に通知する
  4. 戻し忘れを検知する: Control Tower のドリフト通知がそのまま「戻し忘れアラート」として機能します

ブレークグラスを用意しないまま厳しい SCP を入れると、障害対応の初動が「SCP を外すための会議」から始まることになります。


10. 運用チェックリスト

導入時:

  • ホームリージョンを確定した(後から変更不可)
  • OU 設計を先に固めた(SCP は OU 構造に強く依存する)
  • 必須コントロールが何を禁止しているか読んだ
  • AWSControlTowerExecution / StackSets / AFT の実行ロールを独自 SCP から除外した
  • FullAWSAccess を外していない
  • ブレークグラス手順を文書化した

継続運用:

  • ドリフト通知の受け口を EventBridge(LZ 4.0+)に移行済み
  • CloudTrail の SCP 拒否イベントを定期的に棚卸ししている
  • 新しい SCP は Sandbox → Dev → Staging → Prod の順で段階適用している
  • 独自 SCP を Git 管理し、変更理由を残している
  • ランディングゾーンのバージョンを定期的に更新している(NotAction リストの更新が入る)
  • SCP のアタッチ数と文字数に余裕があるか四半期ごとに確認している

まとめ

Control Tower 環境の SCP 運用は、要点を絞ると次の 3 つです。

  1. **aws-guardrails-* は読むだけ。変更は必ず Control Tower 側から**。リネームと直接編集が最も危険
  2. 独自 SCP は別ポリシーとして併用し、実行ロールの除外を忘れない。クォータは 2026 年 5 月に倍増したが、継承と集約の設計原則は変わらない
  3. 段階適用と CloudTrail での拒否イベント観測をセットにする。SCP はシミュレーターで事前検証できない以上、観測が唯一の安全装置

「事故を防ぐ仕組みが、事故対応を妨げない」状態に落とし込めれば、SCP は開発速度を落とさずに統制を効かせられます。


関連記事

RK

1997年生まれ

ITエンジニア

インフラ・SRE