ConfigをCloudFormationにより有効化して色々やってみた

# はじめに

　1リージョンのみの`Config`の有効化は簡単・迅速に行えますが、マルチアカウント・マルチリージョンに対して有効化する場合、とても面倒な作業になります。
　そこで、`CloudFormation`により`Config`を有効化することで、簡単にマルチアカウント環境に`Config`を有効化できるようにします。
　ただ、この記事ではスタックセットは作成しません。
　小さく始めたいので、まずはスタックから作成します。スタックセットは今後の記事で作成しようと思います。

　この記事で行うことは

+ Aアカウントの一つのリージョンで`Config`を有効化する

+ Bアカウントの一つのリージョンで`Config`を有効化し、配信先`S3`をAアカウントの`S3`にする

ことであり、上記作業を通して

+ `Config`の仕組み・挙動

+ `Config`の配信で`SNS`を設定した場合の通知量

+ `Config`配信を受信した`S3`の階層構造

+ `PutObject`の無料利用枠が他と比べて簡単に超える理由（超えない方もいると思いますが……）

+ 既存リソースからスタックを作成する方法・注意点

+ `CloudFomation`のクロススタック参照

などのことを学べました。

　では、早速はじめていきます。　

# Aアカウントの一つのリージョンで`Config`を有効化する

　`Config`を有効化するテンプレートは公式が掲載しているものを流用します。

　https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacksets-sampletemplates.html

　リソース部分だけピックアップします。

```yaml:
Resources:

  ConfigBucket:
    DeletionPolicy: Retain
    Type: AWS::S3::Bucket

  ConfigBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref ConfigBucket
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Sid: AWSConfigBucketPermissionsCheck
            Effect: Allow
            Principal:
              Service:
                - config.amazonaws.com
            Action: s3:GetBucketAcl
            Resource:
              - !Sub "arn:${AWS::Partition}:s3:::${ConfigBucket}"
          - Sid: AWSConfigBucketDelivery
            Effect: Allow
            Principal:
              Service:
                - config.amazonaws.com
            Action: s3:PutObject
            Resource:
              - !Sub "arn:${AWS::Partition}:s3:::${ConfigBucket}/AWSLogs/${AWS::AccountId}/*"

  ConfigTopic:
    Condition: CreateTopic
    Type: AWS::SNS::Topic
    Properties:
      TopicName: !Sub "config-topic-${AWS::AccountId}"
      DisplayName: AWS Config Notification Topic

  ConfigTopicPolicy:
    Condition: CreateTopic
    Type: AWS::SNS::TopicPolicy
    Properties:
      Topics:
        - !Ref ConfigTopic
      PolicyDocument:
        Statement:
          - Sid: AWSConfigSNSPolicy
            Action:
              - sns:Publish
            Effect: Allow
            Resource: !Ref ConfigTopic
            Principal:
              Service:
                - config.amazonaws.com

  EmailNotification:
    Condition: CreateSubscription
    Type: AWS::SNS::Subscription
    Properties:
      Endpoint: !Ref NotificationEmail
      Protocol: email
      TopicArn: !Ref ConfigTopic

  ConfigRecorderRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - config.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - !Sub "arn:${AWS::Partition}:iam::aws:policy/service-role/AWS_ConfigRole"

  ConfigRecorder:
    Type: AWS::Config::ConfigurationRecorder
    DependsOn:
      - ConfigBucketPolicy
    Properties:
      RoleARN: !GetAtt ConfigRecorderRole.Arn
      RecordingGroup:
        AllSupported: !Ref AllSupported
        IncludeGlobalResourceTypes: !Ref IncludeGlobalResourceTypes
        ResourceTypes: !If
          - IsAllSupported
          - !Ref AWS::NoValue
          - !Ref ResourceTypes

  ConfigDeliveryChannel:
    Type: AWS::Config::DeliveryChannel
    DependsOn:
      - ConfigBucketPolicy
    Properties:
      Name: !If
        - IsGeneratedDeliveryChannelName
        - !Ref AWS::NoValue
        - !Ref DeliveryChannelName
      ConfigSnapshotDeliveryProperties:
        DeliveryFrequency: !FindInMap
          - Settings
          - FrequencyMap
          - !Ref Frequency
      S3BucketName: !Ref ConfigBucket
      SnsTopicARN: !If
        - CreateTopic
        - !Ref ConfigTopic
        - !Ref TopicArn
```

　テンプレートを見ると、意外と多くのリソースが作成されていることがわかります。
　一見して分かりにくいので、図にしてみます。

![Config.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/96a1a065-a8fb-1b81-78a0-ade297b17eb4.png)


　`SNSトピック`を設定すると大量に通知が来てしまいそうですが、今回は配信先に`SNS`も設定することにします。

　スタックの作成は、権限に問題がなければエラーは起きないはずです。
　私の場合は`Control Tower`で作成された子アカウントで行ったため、権限エラーが発生しました（ガードレールにより`Config`設定が拒否されているため。）。
　気を取り直して、`Control Tower`管理外のアカウントでスタックを作成します。

　無事に`Config`有効化の確認ができました。

　それでは、各リソースが正常に作成されているか確認します。
　
　S3の階層状況は以下のようになっていました。

```
バケット
└── AWSLogs
    └──アカウントID
　　　　　└──Config
　　　　　　　├──ConfigWritabilityCheckFile
　　　　　　　└──リージョン名
　　　　　　　　　└──配信年
　　　　　　　　　　　└──配信月
　　　　　　　　　　　　　└──配信日
　　　　　　　　　　　　　　　├──ConfigHistory
　　　　　　　　　　　　　　　|　├──アカウントID_Config_リージョン名_ConfigHistory_AWS::Config::ResourceCompliance_20210111T131148Z_20210111T131148Z_1.json.gz
　　　　　　　　　　　　　　　|　├──（変更したリソースの設定履歴ファイルが、変更したリソースの数だけ続く（6h間隔））
                         └──ConfigSnapshot
　　　　　　　　　　　　　　　　　└──アカウントID_Config_リージョン名_ConfigSnapshot_20210111T134218Z_3478f352-ce65-47a2-8d32-b4fe3f0827a9.json.gz

```

　`ConfigWritabilityCheckFile`は、`Config`が`S3バケット`に正常に書き込めることを確認するためのテストファイルです。
　アカウント名が最初から入っているのは、別アカウントから集約する際に区別できるようにするためでしょうか。
　`Config`有効化直後の時点では何も配信されていませんでしたが、1時間経たないうちに設定履歴ファイルとスナップショットが配信されていました。
　おそらくその配信時刻を基準として、定められた配信間隔(※)により設定履歴ファイルとスナップショットが配信されると思われます。
　なお、`Config`有効化後に変更していないリソースの設定履歴ファイルもありましたので、リソースを新規作成後に変更していなくても、そのリソースの設定履歴ファイルが配信されていなければ、設定履歴ファイルを配信するようです。

　※設定履歴ファイル：6時間、設定スナップショット：1,3,6,12,24時間のうちユーザーが指定

　実際にS3の配信状況を確認すると、多くのリソースを作成・変更するほど`PutObject`の使用が多くなることが分かりました。
　私は、よく`PutObject`の無料枠が切れるよというメールを受信していたのですが、Configの配信も原因の一部であることがわかりました（おそらく原因の大部分は`CloudTrail`を全リージョン有効化して`S3`にもログを配信していたことです。）。

　次は`SNS`を確認します。
　正常に確認できていますが、気になるのがメールの通知量です。
　設定履歴ファイルとスナップショットの配信が終わった後にメールを確認すると、デフォルトVPCくらいしかリソースがないリージョンなのに、合計16件も受信していました。
　予想通り大量の通知がきそうなので、配信先で`SNS`を設定することはやめておきます（設定する場合、`SNS`側でフィルタする処置が必須ですね。）。

　今度は、このスタックを削除したらどうなるか確認します（`S3`だけ`DeletionPolicy`設定によりリソース自体は残ります）。
　気になっているのは、`Config`無効化→リソース変更→`Config`有効化した場合、設定履歴ファイルの配信や`S3バケット`がどのような状態になるかです。

　スタックを削除して`S3`だけが残った状態を確認します。
　適当にリソースを作成したいので、空の`SNSトピック`を二つ作成しました。

　それでは、再度スタックを作成しますが、`Config`の配信先は残している`S3バケット`にしたいです。
　本来なら、スタック管理外のリソースをスタックにインポートする場合、テンプレートに`DeletionPolicy`を追加する必要があります。
　しかし、今回は元々記載していたのでテンプレートを変更する必要はなく、最初のスタック作成と同じテンプレートを使用します。
　最初と異なるのは、既存のリソースを使用する方法でスタックを作成するだけです。

![スクリーンショット 2021-01-23 20.59.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/f7519214-1885-392d-c07a-a6ef7694b63b.png)

　リソースをインポートから作成します。

![スクリーンショット 2021-01-23 21.01.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/df8bd48b-fbe2-d235-4007-52169752acdf.png)

![スクリーンショット 2021-01-23 21.09.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/04dbb9a0-8a3d-9146-1145-52cf4740ec2e.png)

　なんとエラーが起きました。
　どうやらリソースのインポートで作成する際は、テンプレートのリソースは全て、実際にインポートしないリソースでも、リソースのインポートでサポートされている必要があるようです。

　そのため、スタックを分割して対処します。
　今回は`Config`の検証なので無理にスタックの管理下におく必要はないのですが、クロススタック参照の学習を兼ねてやってみます。
　まず、既存のS3リソースだけをインポートするスタックを作成します。
　テンプレートは以下の通りです。

```yaml:
AWSTemplateFormatVersion: 2010-09-09
Description: S3 Config

Resources:

  ConfigBucket:
    DeletionPolicy: Retain
    Type: AWS::S3::Bucket

Outputs:
  ConfigBucketOutput:
    Description: S3
    Value: !Ref ConfigBucket
    Export:
      Name: !Sub ${AWS::StackName}-S3BucketName
```

![スクリーンショット 2021-01-23 21.28.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/ea1615ad-665a-8a80-24c7-0233e9f6398d.png)

　先ほどと異なり、エラーが発生せずにテンプレートを指定できました。

![スクリーンショット 2021-01-23 21.30.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/f6fb73ec-2312-ceaf-5784-bfbfe0a6ff07.png)

　しかし、次の画面でインポート操作では出力を追加することができないとのこと。
　仕方がないので、出力はインポート操作後に行います。

![スクリーンショット 2021-01-23 21.33.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/965a1b16-92ca-7e11-0e53-eb5a4bb37004.png)

　出力のセクションを削除したら成功しました。
　今度はStackを更新し、出力を追加します。

![スクリーンショット 2021-01-23 21.39.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/2a9d59b2-d723-064e-4178-76a29c24a4a8.png)

　成功です。

　今度は、`S3`のスタックで出力した情報を、`Config`有効化用のスタックにインポートできれば、再度`Config`有効化の設定が完了します。
　テンプレートは、リソースセクションから`S3バケット`を削除し、`S3バケット`を参照していた箇所をインポート形式に変更します。
　以下のようにインポート形式に変更しています。

```yaml:
Resources:
  ConfigBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket:
        Fn::ImportValue: !Sub ${S3StackName}-S3BucketName
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Sid: AWSConfigBucketPermissionsCheck
            Effect: Allow
            Principal:
              Service:
                - config.amazonaws.com
            Action: s3:GetBucketAcl
            Resource:
              - !Join
                  - ""
                  - - !Sub "arn:${AWS::Partition}:s3:::"
                    - Fn::ImportValue: !Sub ${S3StackName}-S3BucketName
          - Sid: AWSConfigBucketDelivery
            Effect: Allow
            Principal:
              Service:
                - config.amazonaws.com
            Action: s3:PutObject
            Resource:
              - !Join
                  - ""
                  - - !Sub "arn:${AWS::Partition}:s3:::"
                    - Fn::ImportValue: !Sub ${S3StackName}-S3BucketName
                    - !Sub "/AWSLogs/${AWS::AccountId}/*"
```

　スタックの作成は成功しました。

　ただ、現在のStackでは`S3バケット`とバケットポリシーが異なるスタックに存在しているため、不自然です。
　そのため、`S3バケット`とバケットポリシーを一緒のスタックにするため、`S3バケット`のスタックの管理下に置こうとしましたが、これは失敗しました。
　少し前でもエラーになっていましたが、バケットポリシーはリソースのインポートではサポートされていないからです。
　スタックで既存リソースのインポートを使用する際は、サポートされているかが重要であることがわかりました。
　諦めて、次の作業に進みます。

　`Config`の有効化を確認できました。
　`S3`を覗いてみると、`ConfigWritabilityCheckFile`が更新されていたので、過去に`Config`配信先にしたことがある`S3`であっても、配信確認のテストは行うようです。

　スナップショットは1時間弱で`S3`に配信されていましたが、設定履歴ファイルは4,5時間後に配信されていました。
　`Config`無効化後に作成した`SNSトピック`はスナップショットの中に入っていました。
　設定履歴ファイルは、`Config`無効化後、`SNSトピック`など変更したリソースの分だけ配信されました。
　最初に有効化した時に配信されたデフォルトVPCなどのリソースは配信されませんでした。

　ということは、`Config`は無効化すると設定情報が全てリセットされるわけではなく、あくまでも記録を止める仕組みのようです。
　有効化になって記録が再開されると、無効化前の状態と比較して変更されているリソースの設定履歴ファイルが配信されます。
　`S3`の保存場所は、有効化前後で変更はありませんでした。
　無効化→有効化で日にちが空いていれば、その分だけ設定履歴ファイルとスナップショットが存在しない状態になるか、それとも変更した日にちに合わせたフォルダ（正確にはフォルダではありませんが）に配信されるかのどちらかだと思いますが、検証していないのでわかりません。
　個人的な予想だと後者だと思います。

　これで一つのリージョンで`Config`を有効化する作業が終わりました。
　次は、別のアカウントで有効化した`Config`の配信先を今回作成した`S3`に設定してみます。

# Bアカウントの一つのリージョンで`Config`を有効化し、配信先`S3`をAアカウントの`S3`にする

　最初の`Config`有効化と異なる点は、配信先が異なるアカウントである点です。
　なので、配信先のAアカウントの`S3バケット`で、異なるアカウントからのアクションを受け入れる設定を行います。
　`Config`のロールはアカウントでアクションを制限していないので、権限は変更する必要はありません。

　では、Bアカウントで`Config`を有効化し、配信先の`S3`に前回作成したAアカウントの`S3`を指定する作業を行います。

　まず、Aアカウントの`S3`のバケットポリシーを編集します。
　編集後はこちら。

```json:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AWSConfigBucketPermissionsCheck",
            "Effect": "Allow",
            "Principal": {
                "Service": "config.amazonaws.com"
            },
            "Action": "s3:GetBucketAcl",
            "Resource": "arn:aws:s3:::enableconfig-configbucket-qnpsr1uetx0v"
        },
        {
            "Sid": "AWSConfigBucketExistenceCheck",
            "Effect": "Allow",
            "Principal": {
                "Service": "config.amazonaws.com"
            },
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::enableconfig-configbucket-qnpsr1uetx0v"
        },
        {
            "Sid": "AWSConfigBucketDelivery",
            "Effect": "Allow",
            "Principal": {
                "Service": "config.amazonaws.com"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::enableconfig-configbucket-qnpsr1uetx0v/AWSLogs/アカウントID/*"
        },
        {
            "Sid": "AWSConfigBucketDeliveryCrossAccount",
            "Effect": "Allow",
            "Principal": {
                "Service": "config.amazonaws.com"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::enableconfig-configbucket-qnpsr1uetx0v/AWSLogs/別アカウントID/Config/*",
            "Condition": {
                "StringEquals": {
                    "s3:x-amz-acl": "bucket-owner-full-control"
                }
            }
        }
    ]
}
```

　このバケットポリシーにより`Config`配信を受け入れる準備ができたので、Bアカウントで`Config`を有効化し、`S3`配信先をAアカウントの`S3`に指定します。
　今回はコンソールで有効化します。

　`Config`の有効化が無事完了したので、Aアカウントの`S3`を確認します。
　新たにBアカウントのフォルダが作成されていました。

```
バケット
└── AWSLogs
　　　├──AアカウントID
　　　|　└──Config
　　　|　　　├──ConfigWritabilityCheckFile
　　　|　　　└──リージョン名
     └──BアカウントID
　　　　　└──Config
　　　　　　　└──ConfigWritabilityCheckFile

```

　`ConfigWritabilityCheckFile`が置かれているので、バケットポリシーが機能していることがわかりました。
　これで`Config`の配信先を別アカウントの`S3`にする設定が完了しました。

# 終わりに
　単にコンソールで有効化するより、`CloudFormation`で有効化することで`Config`の仕組みが少し分かった気がします。
　`S3`に`Config`を集約した場合、どのような階層で保存されるのか気になっていたので、今回階層状況を実際に目にすることができてよかったです。
　今度は`Control Tower`管理下のアカウントに対し、スタックセットで`Config`を一気に有効化する方法について記事にしたいです。