Organization内のAWS ConfigをCloudFormation StackSetsで一気に設定する

記事タイトルとURLをコピーする

最近は1つの法人で複数AWSアカウント利用するケースが増えてきました。
AWS Configのようなサービスを有効化する場合、「リージョン数 × アカウント数」の設定が必要になります。
CloudFormationでコード化したとしても、「リージョン数 × アカウント数」を実行するのは一苦労です。

そこでCloudFormation StackSetsを利用し、一気に設定することにしました。

要件

  • 利用しているアカウント・リージョンが多くても手間をかけずに設定したい
  • AWS Configのログ(設定スナップショット、履歴)を1つのS3バケットに集約したい
  • OrganizationにAWSアカウントを追加した場合も自動的に設定されるようにしたい

前提

  • AWS Organizationsで「すべての機能」が有効化されている

全体的な方針

  • AWS Organizationsに統合されたCloudFormation StackSetsを使う

余談:アグリゲータについて

AWS Configにはアグリゲータという機能があります。 マルチアカウントマルチリージョンのデータ集約と説明されているので、「これを使えば解決?」と思うのですが、実際は上記要件のいずれも解決してくれませんでした。
単一のAWS Configのコンソール画面で、全環境のリソースを検索したりできるので、これはこれで有用なものです。
しかし、AWS Configの有効化、ログファイルのS3バケットへのエクスポート、などはアグリゲータでは設定できません。

手順

1. S3バケットの作成

ログ保存用のS3バケットを作成するために、任意のアカウントの任意のリージョンでCloudFormationを実行します。
これはStackSetsではなく、普通のCloudFormation実行です。

AWSTemplateFormatVersion: 2010-09-09
Description: Create S3 bucket for AWS Config

Parameters:
  ConfigBucket:
    Type: String
  OrgID:
    Type: String

Resources:
  S3Bucket:
      Type: "AWS::S3::Bucket"
      Properties:
        BucketName: !Ref ConfigBucket
        PublicAccessBlockConfiguration:
          BlockPublicAcls: True
          BlockPublicPolicy: True
          IgnorePublicAcls: True
          RestrictPublicBuckets: True

  S3Policy:
      Type: "AWS::S3::BucketPolicy"
      Properties:
        Bucket:
          Ref: "S3Bucket"
        PolicyDocument:
          Version: 2012-10-17
          Statement:
            - Sid: "AWSConfigPrincipalCheck"
              Action: ['*']
              Effect: "Deny"
              Resource:
              - !Join ['', ['arn:aws:s3:::', !Ref 'S3Bucket']]
              - !Join ['', ['arn:aws:s3:::', !Ref 'S3Bucket','/*']]
              NotPrincipal:
                Service: [config.amazonaws.com]
            - Sid: "AWSConfigBucketPermissionsCheck"
              Action: ['s3:GetBucketAcl']
              Effect: "Allow"
              Resource:
              - !Join ['', ['arn:aws:s3:::', !Ref 'S3Bucket']]
              Principal: "*"
              Condition:
                StringEquals:
                  aws:PrincipalOrgID: [ !Ref 'OrgID' ]
            - Sid: "AWSConfigBucketExistenceCheck"
              Action: ['s3:ListBucket']
              Effect: "Allow"
              Resource:
              - !Join ['', ['arn:aws:s3:::', !Ref 'S3Bucket']]
              Principal: "*"
              Condition:
                StringEquals:
                  aws:PrincipalOrgID: [ !Ref 'OrgID' ]
            - Sid: "AWSConfigBucketDelivery"
              Action: ['s3:PutObject']
              Effect: "Allow"
              Resource:
              - !Join ['', ['arn:aws:s3:::', !Ref 'S3Bucket','/AWSLogs/*']]
              Principal: "*"
              Condition:
                StringEquals:
                  s3:x-amz-acl: "bucket-owner-full-control"
                  aws:PrincipalOrgID: [ !Ref 'OrgID' ]

以下を考慮し、バケットポリシーを作成しました。

  • アカウントが増えてもメンテナンスフリーにするため、アカウントIDを埋め込まない
  • config.amazonaws.com以外のアクセスはDenyする
  • aws:PrincipalOrgIDでOrganization内からのみAllowする

2. AWS Configの作成

2-1.マスターアカウントでAWS OrganizationsのStackSetsの有効化

2-2.CloudFormation StackSetsを実行

マスターアカウントの任意のリージョンで実行します。

テンプレートファイルの内容は下記です。

AWSTemplateFormatVersion: 2010-09-09
Description: Enable AWS Config

Parameters:
  ConfigBucket:
    Type: String
  IncludeGlobalResourceTypeRegion:
    Type: String
    Default: ap-northeast-1

Conditions:
  IsIncludeGlobalResourceTypeRegion: !Equals [ !Ref IncludeGlobalResourceTypeRegion, !Ref "AWS::Region" ]

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

  ConfigRecorder:
    Type: AWS::Config::ConfigurationRecorder
    DeletionPolicy: Delete
    Properties:
      Name: !Sub "configuration-recorder-${AWS::Region}"
      RoleARN: !GetAtt ConfigRecorderRole.Arn
      RecordingGroup:
        AllSupported: true
        IncludeGlobalResourceTypes: !If [IsIncludeGlobalResourceTypeRegion, true, false]
    DependsOn: ConfigRecorderRole

  ConfigDeliveryChannel:
    Type: AWS::Config::DeliveryChannel
    DeletionPolicy: Delete
    Properties:
      Name: !Sub "delivery-channel-${AWS::Region}"
      ConfigSnapshotDeliveryProperties:
        DeliveryFrequency: One_Hour
#      1hour   : One_Hour
#      3hours  : Three_Hours
#      6hours  : Six_Hours
#      12hours : Twelve_Hours
#      24hours : TwentyFour_Hours
      S3BucketName: !Ref ConfigBucket
    DependsOn: ConfigRecorderRole

パラメータにIncludeGlobalResourceTypeRegionというのを作成しました。
基本的に AWS Config はリージョンごとにリソースを記録します。
しかし、リージョン外のグローバルリソース(IAM等)というものもあります。
これを各リージョンで記録対象にすることも可能ですが、検索時などに重複してしまう可能性を避けるため、1つのリージョンを指定するようにしました。

「組織へのデプロイ」または「組織単位(OU)へのデプロイ」を選択します。
今回は「組織単位(OU)へのデプロイ」を選択したので、OU IDを追加で入力しました。

また、「自動デプロイ」もデフォルトで有効になっています。
アカウント追加時に自動的にデプロイされることになります。

今回は「すべてのリージョンを追加」を選択しました。 個別にリージョンを選択することも可能です。

実行開始すると、オペレーションステータスが RUNNIING になります。

スタックインスタンスのステータスが OUTDATED になっています。

1リージョンずつ実行され、徐々に CURRENT に変更していきます。

最終的には全てのスタックインスタンスが CURRENT になり、オペレーションステータスが SUCCEEDED になります。

余談:なぜ、IAM Roleをリージョンごとに作っているか

AWS Configに割り当てるIAM Roleは、全リージョンで共用できます。 しかし、今回はわざわざ複数作成しています。

ConfigurationRecorderを作成するには、IAM Roleを先に作成する必要があります。 CloudFormation StackSetsを実行すると、デプロイは1リージョンずつ実行されます。 したがって、最初のリージョンでIAM Role作成すればいいのですが、CloudFormationの仕様上、「IAM Roleが存在しなければ作成する」といった書き方はできません。

私が試した限りでは、デプロイ対象として全リージョンを指定すると、必ずus-east-1から実行されました。 しかし、必ずしも全リージョンにデプロイするとも限りません。

なお、IAM Roleだけ別スタックに切り出して事前に作成してもいいと思いますが、今回はスタックを増やしたくなかったのでこのような実装にしました。

感想

StackSetsが使えることで、CloudFormationの価値が上がり、学習するメリットも上がったと思いました。

渡辺 信秀(記事一覧)

2017年入社 / 地味な内容を丁寧に書きたい