AWS Config 設定レコーダーと配信チャネル をCloudFormation で削除する

2022.06.06

既存のアカウントをAWS Control Tower に登録する場合、事前にいくつかの作業が必要になります。
その中で、AWS Config 設定レコーダーまたは配信チャネルを削除する事が推奨されています。

登録する既存のアカウントにログイン可能な場合は該当のアカウントのCloudShellなどから CLIで削除する方法などもあり実施等が本ブログでも紹介されています。

コンソールから消せないConfigを全リージョンCLIから無効化する

コンソールから消せないConfigを全リージョンCLIから無効化する

今回、手動の処理を挟まずAWS Config 設定レコーダーと配信チャネルを削除する必要があったため CloudFormationで実施できるようにCloudFromationのカスタムリソースで作成いたしましたので共有します。

また、既存のアカウントで、AWS Configを止める事ができず、移行する必要がある場合は、サポートケースを起票する等の作業が必要となります。詳細は公式のブログでご紹介されておりますのでConfigを移行する必要がある際は、こちらをご参考ください。
https://aws.amazon.com/jp/blogs/mt/automate-enrollment-of-accounts-with-existing-aws-config-resources-into-aws-control-tower/

AWS Control Tower 登録の前提条件

https://docs.aws.amazon.com/ja_jp/controltower/latest/userguide/enrollment-prerequisites.html

1.アカウントが AWS Config 設定レコーダーまたは配信チャネルを持たないようにすることをお勧めします。
2.登録するアカウントは、AWS Control Tower 管理アカウントと同じ AWS Organizations 組織に存在する必要があります。
3.既存のアカウントを AWS Control Tower に登録する前にAWSControlTowerExecution のRoleが必要です。

となります。削除以外にも実際に登録する場合は事前に、AWSControlTowerExecution Roleの作成や、SCPでの違反がないかなど、の確認が必要となりますので、下記エントリーをご参考にしてください。

既存アカウントをControl Towerへ登録するときに確認したことをまとめてみる

CloudFromationのカスタムリソースについて

CloudFormationで作成できない外部リソースやCloudFromationでサポートされていないリソースを作成する際に使用する独自実装処理となりますので、本エントリーで共有している処理をご使用される場合は、検証環境等でご検証のうえ、ご使用ください。
参考

CloudFormationで提供されていない処理をカスタムリソースで作ってみた。

CloudFormation テンプレート説明

テンプレート全体は本ブログの最後に記載します。

Config削除Lambda用のロール

  • リージョン一覧取得
  • Config の操作の全権限

を定義しています。

  DeleteConfigFuncRole:
     省略
          Policies:
            - PolicyName: ConfigPolicy
              PolicyDocument:
                Version: 2012-10-17
                Statement:
                  - Effect: Allow
                    Action: 'config:*'
                    Resource: '*'
                  - Effect: Allow
                    Action: 'ec2:DescribeRegions'
                    Resource: '*'

Config削除処理を行うLamba関数

  • 対象となるリージョン一覧情報の取得
ec2 = boto3.client('ec2')
regions = list(map(lambda x: x['RegionName'], ec2.describe_regions()['Regions']))
  • 指定されたリージョンのConfig 設定レコーダーまたは配信チャネルの削除を実施
def delete_configuration_recorder(region):

    config = boto3.client('config', region_name=region)

    recorders = config.describe_configuration_recorders()
    for recorder in recorders["ConfigurationRecorders"]:
        # print (recorder)
        response = config.delete_configuration_recorder(ConfigurationRecorderName=recorder["name"])
        print('%s delete_configuration_recorder ' % region)

    channels = config.describe_delivery_channels()
    for channel in channels["DeliveryChannels"]:
        # print (channel)
        response = config.delete_delivery_channel(DeliveryChannelName=channel["name"])
        print('%s delete_delivery_channel' % region)

を行っています。特に難しい処理は実施していませんが、問答無用で消してますのでご注意ください

やってみる

Config が有効なアカウントにログインを実施します。

※Configが消えても問題のない、検証用のアカウントでご実施ください

CloudShell で現在の状況を確認します。

aws ec2 describe-regions --region ap-northeast-1 --query "Regions[].[RegionName]" --output text \
| while read r; do
  recorder=$(aws configservice describe-configuration-recorders --region $r \
             --query "ConfigurationRecorders[].name" --output text)
  echo "${r} ${recorder}"
done

各リージョンで有効場合、下記のような表示となります

eu-north-1 default
ap-south-1 default
eu-west-3 default
eu-west-2 default
eu-west-1 default
ap-northeast-3 default
ap-northeast-2 default
ap-northeast-1 default
sa-east-1 default
ca-central-1 default
ap-east-1 default
ap-southeast-1 default
ap-southeast-2 default
eu-central-1 default
us-east-1 default
us-east-2 default
us-west-1 default
us-west-2 default

S3上にテンプレートを共有しておりますので、下記URLをクリックしてスタックを作成ください。

https://ap-northeast-1.console.aws.amazon.com/cloudformation/home?region=ap-northeast-1#/stacks/quickcreate?templateUrl=https%3A%2F%2Fpub-devio-blog-qrgebosd.s3.ap-northeast-1.amazonaws.com%2Ftemplate%2FDeleteConfig.yml&stackName=DeleteConfig

AWS CloudFormation によって IAM リソースが作成される場合があることを承認します。

にチェックを行いスタックを作成します。

  1. Roleの作成
  2. 関数の作成
  3. カスタムリソースの作成
    1. 実際にはこのタイミングでConfigの削除が行われます。

とすすみ、スタックが正常に作成されるとConfigの削除が実施されます。

削除状況確認

先ほどと同様のコマンドを実施してみます。一覧表示されるのみでレコード(default)がないことが確認できたかと思います。

$ aws ec2 describe-regions --region ap-northeast-1 --query "Regions[].[RegionName]" --output text | while read r; do   recorder=$(aws configservice describe-configuration-recorders --region $r \
             --query "ConfigurationRecorders[].name" --output text);   echo "${r} ${recorder}"; done
eu-north-1 
ap-south-1 
eu-west-3 
eu-west-2 
eu-west-1 
ap-northeast-3 
ap-northeast-2 
ap-northeast-1 
sa-east-1 
ca-central-1 
ap-east-1 
ap-southeast-1 
ap-southeast-2 
eu-central-1 
us-east-1 
us-east-2 
us-west-1 
us-west-2

スタックの削除

カスタムリソース自体ではConfigの復活等の処理は実装していませんので、削除する際はConfigには影響はありません。
Configの削除を行うために作成したRole、Lamba関数が削除されます。

まとめ

多くのアカウントを移行する際など、個別のアカウントに入りConfigを無効化する作業の自動化を行うためにカスタムリソース処理を用いました。
若干、リソースの削除を行うのに、スタックを作成するとはいかに?という気もしなくもないですが、 Oraganizations また、StackSetsと組み合わせて、自動でContorol Tower への移行前に各アカウントで事前の処理を行う際などに有用かと思います。

参考情報

https://dev.classmethod.jp/articles/disable-config-all-region-cli/

CloudFormation テンプレート

AWSTemplateFormatVersion: 2010-09-09
Description: Delete Configuration recorder and Delivery channel

Resources:
  DeleteConfigFuncRole:
        Type: "AWS::IAM::Role"
        Properties:
          Path: "/lambda/servicerole/"
          ManagedPolicyArns:
            - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
          AssumeRolePolicyDocument:
              Version: "2012-10-17"
              Statement:
                  - Effect: "Allow"
                    Principal:
                      Service: [lambda.amazonaws.com]
                    Action:
                      - sts:AssumeRole
          Policies:
            - PolicyName: ConfigPolicy
              PolicyDocument:
                Version: 2012-10-17
                Statement:
                  - Effect: Allow
                    Action: 'config:*'
                    Resource: '*'
                  - Effect: Allow
                    Action: 'ec2:DescribeRegions'
                    Resource: '*'

  DeleteConfigFunc:
    Type: 'AWS::Lambda::Function'
    Properties:
      Handler: index.lambda_handler
      Role: !GetAtt DeleteConfigFuncRole.Arn
      Code:
        ZipFile: |
          import json
          import boto3
          import cfnresponse

          SUCCESS = "SUCCESS"
          FAILED = "FAILED"

          print('Loading function')

          ec2 = boto3.client('ec2')
          regions = list(map(lambda x: x['RegionName'], ec2.describe_regions()['Regions']))

          def lambda_handler(event, context):
              # print("Received event: " + json.dumps(event, indent=2))
              responseData={}
              try:
                  if event['RequestType'] == 'Delete':
                      # print("Request Type:", event['RequestType'])

                      print("Sending response to custom resource after Delete")
                  elif event['RequestType'] == 'Create' or event['RequestType'] == 'Update':
                      # print("Request Type:", event['RequestType'])

                      for region in regions:
                          delete_configuration_recorder(region)
                      
                      print("Sending response to custom resource")
                  responseStatus = 'SUCCESS'
              except Exception as e:
                  print('Failed to process:', e)
                  responseStatus = 'FAILED'
                  responseData = {'Failure': 'Something bad happened.'}
              cfnresponse.send(event, context, responseStatus, responseData)

          def delete_configuration_recorder(region):

              config = boto3.client('config', region_name=region)

              recorders = config.describe_configuration_recorders()
              for recorder in recorders["ConfigurationRecorders"]:
                  # print (recorder)
                  response = config.delete_configuration_recorder(ConfigurationRecorderName=recorder["name"])
                  print('%s delete_configuration_recorder ' % region)

              channels = config.describe_delivery_channels()
              for channel in channels["DeliveryChannels"]:
                  # print (channel)
                  response = config.delete_delivery_channel(DeliveryChannelName=channel["name"])
                  print('%s delete_delivery_channel' % region)
                  

      Runtime: python3.9
      Timeout: 60
  MyCustomResource:
    Type: AWS::CloudFormation::CustomResource
    Properties:
      ServiceToken: !GetAtt DeleteConfigFunc.Arn