AWS Config ルールを CloudFormation StackSets で展開するために、リージョン毎に実装を変える必要があって大変だけど何とかする

一つの組織の中で、複数アカウント×複数リージョンに AWS Config ルールを一括して適用したくなるケースがあるかと思います。CloudFormations StackSets で一括設定と自動デプロイが実現可能ですが、リージョン毎に設定を変えるのは慣れてないと意外と大変です。StackSets でリージョン毎の設定を柔軟に実装するために、Condition句、 DependsOn 句と WaitCondition 機能を組み合わせて CloudFormation テンプレートを定義する方法を紹介します。

_「AWS Control Tower を使えばよいじゃないか」と考えたくなりますよね。でも、自分ではそれを選択できない苦しい立場で実装を考えなければいけない人もいると信じて記事を作成してみました。_

なお、この記事では CloudFormation StackSets, AWS Config の基本的な知識を前提としています。これらの基本から学びたい方は、BlackBelt 等を参考にしていただけると良いかと思います。

* [BlackBelt: AWS CloudFormation (2020)](https://aws.amazon.com/jp/blogs/news/webinar-bb-aws-cloudformation-2020/)
* [BlackBelt: AWS Config update (2020)](https://aws.amazon.com/jp/blogs/news/webinar-bb-awsconfigupdate-2020/)

## AWS Config と Config ルールの展開におけるリージョンの問題

この構成図のように AWS Config の有効化(レコーダーの設定)と Config ルールを3つのリージョンに展開することが要件とします。

![実装イメージ_依存関係.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531180/f944d678-f804-55fc-b487-7bc85633d788.png)

AWS Config には「__グローバルリソース (AWS IAM リソースなど) を含める__」というオプションがあります。このオプションはいずれかのリージョンで有効にしておくことが望ましいですが、複数のリージョンで有効にしても同じログを複数のリージョンで取得することになるので、コストが無駄になってしまいます。

```YAML:AWS Config レコーダーのリソース定義
Resources:
  EnableConfigMainRegion:
    Type: AWS::Config::ConfigurationRecorder
    Properties:
      Name: "default"
      RecordingGroup:
        AllSupported: true
        # ここの設定は代表リージョンだけ true にしたい
        IncludeGlobalResourceTypes: <true or false>
      RoleARN: <Config Recorder 用の IAM Role ARN>

```

この問題は下記項で説明している通り、Conditions 設定で解決できます。

ただしこの AWS Config レコーダーと共に Config ルールを設定しようとすると、別の問題が発生します。Config レコーダーが有効になっていない状態で Config Rule を実装しようとするとエラーになってしまうため、依存関係の設定が必要になります。

しかし例えば以下の定義だと AWS Config レコーダーのリソース定義が東京とそれ以外で異なる場合、東京以外のリージョンでエラーが発生してしまいます。

```YAML:AWS Config ルールのリソース定義（依存先の定義が東京以外で無効なためエラーとなる）
  ConfigRule1:
    Type: AWS::Config::ConfigRule
    # ！この依関係の設定がエラーとなる！
    DependsOn: <東京リージョンのみで実装する AWS Config レコーダーを指定>
    Properties:
      ConfigRuleName: eip-attached
      Source:
        Owner: AWS
        SourceIdentifier: EIP_ATTACHED
```

これらの問題を起こさない CloudFormation テンプレートをどのように設計すればよいかを解説していきます。

## 1つのリージョンの Stack だけ AWS Config のグローバルリージョンの設定をするには

Conditions 句を使うことで、指定のリージョンだけにリソースを作成する定義ができます。

まず条件式を定義します。「代表リージョンか、それ以外か」という条件判定ができるようにします。

```YAML:リージョン毎にリソース作成の要否を変えるための条件式
Conditions:

  # 代表リージョン（東京）か否かを判定
  MainRegion: 
    !Equals [!Ref "AWS::Region", ap-northeast-1]

  # それ以外（東京）のリージョンか否かを判定
  OtherRegion: 
    !Not [ !Equals [!Ref "AWS::Region", ap-northeast-1] ]
```

以下は実際に AWS Config を設定する定義です。代表リージョンである東京だけで「グローバルリソースを含める」オプションを有効にしています。

```YAML:東京とそれ以外で実装する Config レコーダーの定義を変える
Resources:

  # 代表リージョン(東京以外)の AWS Config 設定
  ConfigRecorderMainRegion:
    Type: AWS::Config::ConfigurationRecorder
    Condition: MainRegion
    Properties:
      Name: "default"
      RecordingGroup:
        AllSupported: true
        # 代表リージョン(東京以外)はグローバルリソースを含める
        IncludeGlobalResourceTypes: true
      RoleARN: <Config Recorder 用の IAM Role ARN>

  # その他のリージョン(東京以外)の AWS Config 設定
  ConfigRecorderOtherRegion:
    Type: AWS::Config::ConfigurationRecorder
    Condition: OtherRegion
    Properties:
      Name: "default"
      RecordingGroup:
        AllSupported: true
        # その他のリージョン(東京以外)はグローバルリソースを含めない
        IncludeGlobalResourceTypes: false
      RoleARN: <Config Recorder 用の IAM Role ARN>
```

## AWS Config レコーダーと Config ルールの間で依存関係を定義する

AWS Config レコーダーを、代表リージョン(東京)とそれ以外のリージョンで別々に定義したので、それを踏まえた依存関係の設定をします。しかし Config ルールから直接リージョン毎に異なる Config レコーダーの定義と依存設定することはできません。そこで WaitCondition 機能を用います。

以下の例では、東京リージョン用と、それ以外のリージョン用の WaitConditionHandle を定義した上で、さらにディスパッチ用の WaitConditionHandle で参照先を分岐できるようにします。

```YAML:東京とそれ以外で依存関係の参照先を振り分けるための WaitConditionHandle 定義

  # 東京リージョンで有効になる WaitConditionHandle
  WaitHandleTokyo: 
    Condition: MainRegion
    # 東京だけで有効になる Config レコーダーの定義を指定
    DependsOn: ConfigRecorderMainRegion
    Type: "AWS::CloudFormation::WaitConditionHandle"

  # 東京以外のリージョンで有効になる WaitConditionHandle
  WaitHandleOtherRegions: 
    Condition: OtherRegion
    # 東京以外のリージョンで有効になる Config レコーダーの定義を指定
    DependsOn: ConfigRecorderOtherRegion
    Type: "AWS::CloudFormation::WaitConditionHandle"

  # ディスパッチ用の WaitConditionHandle
  WaitConditionDespatch: 
    Type: "AWS::CloudFormation::WaitCondition"
    Properties: 
      Handle: !If [MainRegion, !Ref WaitHandleTokyo, !Ref WaitHandleOtherRegions]
      Timeout: "1"
      Count: 0
```

上記の WaitConditionHandle を Config ルールの依存先に指定します。上記でリージョンによるディスパッチをしてくれるため、リージョンによらない Config ルールを定義することができます。

```YAML:WaitConditionHandle を参照先にしたリージョン共通の Config Rule 定義
  ConfigRule1:
    Type: AWS::Config::ConfigRule
    DependsOn: WaitConditionDespatch
    Properties:
      ConfigRuleName: eip-attached
      Source:
        Owner: AWS
        SourceIdentifier: EIP_ATTACHED
  ConfigRule2:
    Type: AWS::Config::ConfigRule
    DependsOn: WaitConditionDespatch
    Properties:
      ConfigRuleName: s3-default-encryption-kms
      Source:
        Owner: AWS
        SourceIdentifier: S3_DEFAULT_ENCRYPTION_KMS
```

WaitConditionHandle を活用しているケースは多くないかもしれませんが、上記のようなリージョンに依存したリソース定義が必要な場合には役に立つという例を紹介させていただきました。もっと望ましい実装例などありましたらコメントいただければ幸いです。