CloudFormation StackSetsで展開したConfig Ruleの自動修復を特定アカウントだけ無効化してみた

CloudFormation StackSetsで展開したConfig Ruleの自動修復を特定アカウントだけ無効化してみた

CloudFormation StackSetsの自動デプロイでConfig Ruleと自動修復を全アカウントに展開していますが、特定アカウントだけ自動修復を止めたいケースがありました。CloudFormation ConditionsとAWS::AccountId疑似パラメータを組み合わせて、アカウント単位でRemediationConfigurationの作成を制御する方法を紹介します。
2026.06.25

こんにちは!クラウド事業本部の吉田です。

前回の記事では、Control Tower環境でCloudFormation StackSetsの自動デプロイを使う際に、ベースラインStackSetsとの競合を回避する方法を紹介しました。

前回の記事: CloudFormation StackSetsの依存関係先としてControl Tower管理のStackSetsを指定したかった

SetupOUを経由してOU移動をトリガーにすることで、Config Ruleと自動修復(SSM Automation)を全アカウントに自動展開できるようになりました。

ただ、運用していく中で「特定のアカウントだけ自動修復を止めたい」ケースが出てきました。
別組織からアカウントを移行する際に、S3パブリックアクセスブロックがアカウントレベルでOFFになっていたのがきっかけです。
バケットレベルでは個別にONにしていたものの、アカウントレベルでは未設定でした。

移行先のOUにはS3パブリックアクセスブロックを強制するStackSetsが展開されているため、OU移動後に自動修復が走ってアカウントレベルで強制的にONになります。
今回は移行前にONにして対応しましたが、正当な理由でOFFにしたいアカウントがある場合、自動修復と競合してしまいます。

この記事では、CloudFormation Conditionsを使って特定アカウントだけ自動修復を無効化する方法を紹介します。

やりたいこと

求める要件は以下のとおりです。

  • Config Ruleは全アカウントにデプロイして準拠状況を可視化したい
  • Config Ruleの自動修復だけを特定アカウントで無効化したい
  • CloudFormation StackSetsの自動デプロイは維持したい(新規アカウント追加時の自動展開を止めたくない)
  • 運用負荷は最小限に抑えたい

通常のアカウントではConfig Ruleが非準拠を検出すると自動修復が実行されますが、例外アカウントでは自動修復を設定せず、非準拠の検出のみを行うイメージです。

自動デプロイのアカウント除外は非対応

まず思いつくのは「自動デプロイの対象からアカウントを除外する」方法ですが、これはAWSの仕様として対応していません。

自動デプロイはStackSetレベルの設定であり、OU・アカウント・リージョン単位で調整できません。※1

自動デプロイ機能は StackSet レベルで有効になります。OU、アカウント、リージョンを選択して自動デプロイを調整することはできません。

また、手動デプロイ時に使えるAccountFilterType: DIFFERENCE(特定アカウントを除外するフィルター)も、自動デプロイには適用されません。※1

自動デプロイでは、アカウントレベルのターゲティングフィルターは考慮しません。特定のアカウントをターゲットにして自動デプロイを有効にすると、StackSet はデプロイを継続して、デプロイされた組織内の新しく追加されたアカウントにデプロイします。

StackSets側でアカウントを除外する方法はないため、テンプレート側で制御する方針に切り替えました。

CloudFormation Conditionsで自動修復を制御する

代替案として、CloudFormationテンプレートのConditionsでRemediationConfigurationリソースの作成を制御する方法を検討しました。

テンプレートに除外アカウントIDのパラメータを追加し、AWS::AccountId疑似パラメータと比較するConditionを定義します。

Parameters:
  ExceptionAccountId1:
    Type: String
    Default: ""
    Description: Account ID to exclude from auto-remediation (leave empty if not used)
  ExceptionAccountId2:
    Type: String
    Default: ""
    Description: Account ID to exclude from auto-remediation (leave empty if not used)

Conditions:
  IsException1: !Equals [!Ref AWS::AccountId, !Ref ExceptionAccountId1]
  IsException2: !Equals [!Ref AWS::AccountId, !Ref ExceptionAccountId2]
  IsExceptionAccount: !Or
    - !Condition IsException1
    - !Condition IsException2
  AutoRemediationEnabled: !Not [!Condition IsExceptionAccount]

ポイントは、StackSetsのパラメータは全アカウント共通ですが、AWS::AccountIdはデプロイ先アカウントごとに異なる値に解決されるという点です。
そのため、同じパラメータ値でもConditionの評価結果がアカウントごとに変わります。

RemediationConfigurationリソースにCondition: AutoRemediationEnabledを付与すると、例外アカウントではこのリソースが作成されません。
Config Ruleには条件を付けないため、準拠状況の可視化は全アカウントで維持されます。

パラメータが空文字(デフォルト値)の場合、12桁のアカウントIDとは一致しないため、全アカウントで自動修復が有効になります。
例外を設定しない限り、既存の動作に影響はありません。

テンプレート例

S3パブリックアクセスブロックのConfig Rule + 自動修復テンプレートの全体を掲載します。

AWSTemplateFormatVersion: "2010-09-09"
Description: "Config Rules Auto Remediation S3 Public Access Block - Multi-region deployment supported"

Parameters:
  ControlPlaneRegion:
    Type: String
    Default: ap-northeast-1
    Description: Region where IAM resources are created
  ExceptionAccountId1:
    Type: String
    Default: ""
    Description: Account ID to exclude from auto-remediation (leave empty if not used)
  ExceptionAccountId2:
    Type: String
    Default: ""
    Description: Account ID to exclude from auto-remediation (leave empty if not used)

Conditions:
  IsControlPlaneRegion: !Equals [!Ref AWS::Region, !Ref ControlPlaneRegion]
  IsException1: !Equals [!Ref AWS::AccountId, !Ref ExceptionAccountId1]
  IsException2: !Equals [!Ref AWS::AccountId, !Ref ExceptionAccountId2]
  IsExceptionAccount: !Or
    - !Condition IsException1
    - !Condition IsException2
  AutoRemediationEnabled: !Not [!Condition IsExceptionAccount]

Resources:
  IAMRole:
    Type: "AWS::IAM::Role"
    Condition: IsControlPlaneRegion
    Properties:
      Path: "/"
      RoleName: "config-remediation-s3-public-access-block"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service: "ssm.amazonaws.com"
            Action: "sts:AssumeRole"
      MaxSessionDuration: 3600
      ManagedPolicyArns:
        - !Ref IAMManagedPolicy

  IAMManagedPolicy:
    Type: "AWS::IAM::ManagedPolicy"
    Condition: IsControlPlaneRegion
    Properties:
      ManagedPolicyName: "config-remediation-s3-public-access-block-policy"
      Path: "/"
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Action:
              - "s3:GetBucketPolicyStatus"
              - "s3:GetBucketPublicAccessBlock"
              - "s3:PutBucketPublicAccessBlock"
              - "s3:GetAccountPublicAccessBlock"
              - "s3:PutAccountPublicAccessBlock"
              - "s3:PutAccessPointPublicAccessBlock"
            Resource: "*"
            Effect: "Allow"

  S3AccountLevelPublicAccessBlocks:
    Type: AWS::Config::ConfigRule
    Properties:
      ConfigRuleName: "s3-account-level-public-access-blocks-periodic"
      Source:
        Owner: AWS
        SourceIdentifier: "S3_ACCOUNT_LEVEL_PUBLIC_ACCESS_BLOCKS_PERIODIC"
      Scope:
        ComplianceResourceTypes:
          - "AWS::S3::AccountPublicAccessBlock"
      InputParameters:
        IgnorePublicAcls: "True"
        BlockPublicPolicy: "True"
        BlockPublicAcls: "True"
        RestrictPublicBuckets: "True"
      MaximumExecutionFrequency: "TwentyFour_Hours"

  ConfigureS3PublicAccessBlock:
    Type: AWS::Config::RemediationConfiguration
    Condition: AutoRemediationEnabled
    Properties:
      Automatic: true
      ConfigRuleName: !Ref S3AccountLevelPublicAccessBlocks
      MaximumAutomaticAttempts: 1
      RetryAttemptSeconds: 1
      Parameters:
        AccountId:
          StaticValue:
            Values:
              - !Ref AWS::AccountId
        RestrictPublicBuckets:
          StaticValue:
            Values:
              - "true"
        BlockPublicAcls:
          StaticValue:
            Values:
              - "true"
        IgnorePublicAcls:
          StaticValue:
            Values:
              - "true"
        BlockPublicPolicy:
          StaticValue:
            Values:
              - "true"
        AutomationAssumeRole:
          StaticValue:
            Values:
              - !Sub "arn:aws:iam::${AWS::AccountId}:role/config-remediation-s3-public-access-block"
      TargetId: "AWSConfigRemediation-ConfigureS3PublicAccessBlock"
      TargetType: "SSM_DOCUMENT"

Conditionsでは2つの制御を行っています。

IsControlPlaneRegionはIAMリソースのリージョン制御です。
IAMはグローバルリソースのため、マルチリージョン展開時に1つのリージョンでのみ作成します。
今回の例外処理とは無関係ですが、StackSetsでマルチリージョン展開する際の一般的なパターンです。

AutoRemediationEnabledが今回の例外処理です。
ConfigureS3PublicAccessBlock(RemediationConfiguration)にCondition: AutoRemediationEnabledを付与しています。
例外アカウントではこのリソースが作成されないため、Config Ruleは動作しますが自動修復は実行されません。

やってみた

実際に先ほどのテンプレートの動作確認をします。
下記のように2つのアカウントを利用します。

  • アカウントA
    • 通常アカウント
    • このアカウントが配置されているOUをStackSetsの自動デプロイ対象OUに設定する
      • StackSetsの自動デプロイが実行され、自動修復が設定されたConfig Ruleが作成される見込み
  • アカウントB
    • 例外アカウント
    • StackSets作成後、アカウントAが配置されているOUに移動する
      • StackSetsの自動デプロイが実行されConfig Ruleは作成されるが、自動修復が設定されない見込み

まずは、アカウントAの結果を載せます。

スタックインスタンスの作成リソースのキャプチャとなります。
Config RuleのRemediationConfigurationが作成されていそうですね。

screenshot 2.png

実際のConfig Ruleを確認します。
ちゃんと修復アクションが設定されております。

screenshot 3.png

こちらが、通常アカウントの挙動となります。

次に、アカウントBの結果を載せます。

スタックインスタンスの作成リソースに、Config RuleのRemediationConfigurationがありません。

screenshot 1.png

Config Ruleを確認しても、Ruleはあるが修復アクションは設定されておりません。

screenshot.png

こちらが、例外アカウントの挙動となります。

運用について(例外アカウントの追加)

StackSetsのパラメータでExceptionAccountId1またはExceptionAccountId2に除外対象のアカウントIDを設定します。
2つ以上設定する場合は、ExceptionAccountId3といった具合で、テンプレートにパラメーターを追加します。
ただし、!Orに指定できる条件は2〜10個となる点、注意が必要となります。

さいごに

CloudFormation StackSetsの自動デプロイではアカウント単位の除外はできませんが、テンプレートのConditionsとAWS::AccountId疑似パラメータを組み合わせることで、リソースの作成をアカウントごとに制御できます。

Config Ruleによる可視化は維持しつつ、自動修復だけを無効化するこのアプローチは、デフォルトセキュリティグループのルール削除など他のConfig Rule + 自動修復のStackSetsにも、同じパターンで適用できます。

以上、クラウド事業本部の吉田でした!

参考資料

この記事をシェアする

関連記事