AWS Config Rulesをアカウント間で共有しつつ、たくさんのアカウントに自動で展開する

Config Rulesを複数アカウントにどっかーんと自動で展開してみます。どれだけたくさんアカウントがあっても平静を保てます。
2018.12.12

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

コンニチハ、千葉です。

はじめに

複数チームで利用していくと、たくさんAWSアカウントが増えて、セキュリティやガバナンス、コンプライアンスを守るために中央管理で頑張り、ボトルネックになっていくことがあります。しかし、AWSでは自動化ができるので、チームに権限を委譲したうえでセキュリティ、ガバナンス、コンプライアンスを構築する土台があります。ブロッカーではなく、ガードレールが作れると幸せな世界が築けそうです。

ってことで、AWSにはConfig Rulesというサービスがあり、こちらを導入すると意図しない設定が入っているリソースを可視化したり、通知したりすることができます。 今回は、マルチアカウント環境でConfig Rulesを共有し、かつ自動で複数アカウントに展開してみました。これで、どれだけアカウントが増えようともへっちゃらです。

やってみた

全体イメージサマリ

Config Rulesを管理するアカウントを管理アカウント、ルールを利用するアカウントを管理対象アカウントとします。 動作イメージはこちら。

設定イメージはこちら。

管理アカウントでやること

  • Lambda実行用のIAMロール作成(STS、CWLogs,config:PutEvaluations)
  • Lambda関数の作成
  • STSで管理対象の一時クレデンシャルを取得
  • 任意の処理を実行
  • 評価結果をputする
  • Lambda関数にクロスアカウント Configからの実行許可を付与(add permission)

管理対象アカウントでやること

  • AWS Config Rules用のIAMロールを作成
  • 付与する権限:SecurityAudit
  • 信頼関係:AWS Config, 管理アカウントのIAMロール
  • AWS Config Rulesの作成
  • 管理アカウントのLambda ARNを指定

やってみた

いやー、やること整理するだけでかなり大変でした。すごく複雑です。そのため簡単に実装できるようにCFn化しました。

前提

前提として管理対象アカウントでConfigを有効にしておきます。

管理アカウントでやること

CloudFormationを貼っておきます。事前にLambdaのコードはzipでS3へアップロードしてください。 Config Rules用のコードはawslabs/aws-config-rulesに色々いあるので参考にしたり、コピーしてそのまま使いましょう。コード利用時の注意事項としては、ASSUME_ROLE_MODE という項目があるのでTrueにします。そうすると、STSを利用して一時クレデンシャルを取得するようになるので、クロスアカウントで利用可能になります。

管理アカウント用のCFn

AWSTemplateFormatVersion: '2010-09-09'
Description: This CloudFormation template to create Cross Account Config Rules for Admin Account.

Parameters:
  Environment:
    Description: Type of this environment.
    Type: String
    Default: stg
    AllowedValues:
      - prd
      - stg
      - dev
  SystemName:
    Description: Name of this system.
    Type: String
    Default: web
  LambdaFunctionS3Bucket:
    Description: S3 bucket name
    Type: String
    Default: s3-bucket-name-your-lambda-function
  LambdaFunctionS3Key:
    Description: S3 key
    Type: String
    Default: xx/xx.zip
  LambdaInvokeCrossAccount:
    Description: Cross AWS Account ID
    Type: String
    Default: 111111111111

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Environment Configuration
        Parameters:
          - SystemName
          - Environment
      - Label:
          default: Lambda Function
        Parameters:
          - LambdaFunctionS3Bucket
          - LambdaFunctionS3Key

Resources:
  # Lambda Function
  LambdaFunctionIAMCheck:
    Type: "AWS::Lambda::Function"
    Properties:
      Handler: "lambda_function.lambda_handler"
      Role:
        Fn::GetAtt:
          - "LambdaExecRole"
          - "Arn"
      Code:
        S3Bucket: !Sub ${LambdaFunctionS3Bucket}
        S3Key: !Sub ${LambdaFunctionS3Key}
      Runtime: "python3.6"
      Timeout: "60"

  # Lambda Function add execute permission from config rules of managed account
  LambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: "lambda:InvokeFunction"
      FunctionName: !GetAtt
        - LambdaFunctionIAMCheck
        - Arn
      Principal: "config.amazonaws.com"
      SourceAccount: !Sub ${LambdaInvokeCrossAccount}

  # IAM Role
  LambdaExecRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub role-${Environment}-${SystemName}-lambda
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSConfigRole
        - arn:aws:iam::aws:policy/SecurityAudit
      Path: /
      AssumeRolePolicyDocument: !Sub |
        {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": "lambda.amazonaws.com"
              },
              "Action": "sts:AssumeRole"
            }
          ]
        }

  # IAM Policy
  LambdaExecPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: !Sub policy-${Environment}-${SystemName}-lambda
      Description: !Sub policy-${Environment}-${SystemName}-lambda
      PolicyDocument: !Sub |
        {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "config:PutEvaluations"
              ],
              "Resource": [
                "*"
              ]
            },
            {
              "Effect": "Allow",
              "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
              ],
              "Resource": "arn:aws:logs:*:*:*"
            },
            {
              "Effect": "Allow",
              "Action": [
                "sts:AssumeRole"
              ],
              "Resource": "*"
            }
          ]
        }
      Roles:
        - !Ref LambdaExecRole

Lambdaのコード

Assume Roleで、一時クレデンシャルを取得するコードを入れておきます。GitHubにあるAWSラボのコードをみたのですが、管理対象アカウントのConfigロールに対してAssume Roleしていました。今回は、以下のようにして作成したRoleにAssume Roleするようなコードを入れました。(AWSラボのコードを利用する場合は、クレデンシャル取得する箇所を以下に変更しましょう。自分で一からカスタムルール作成するときは、このコードを埋め込みましょう。

executionRoleArn = 'arn:aws:iam::' + event["accountId"] + ':role/role-exec-config-rules'
credentials = get_assume_role_credentials(executionRoleArn)
return boto3.client(service, aws_access_key_id=credentials['AccessKeyId'],
aws_secret_access_key=credentials['SecretAccessKey'],
aws_session_token=credentials['SessionToken']
)

管理対象アカウントでやること

入力には、管理アカウントのLambdaに指定しているIAMロールARN、LambdaのARNが必要になりますので取得しておきましょう。 こちらのCFnをStacSetsを利用して複数のアカウントに展開しましょう。

https://dev.classmethod.jp/cloud/aws/introducing-cloudformation-stacksets/

管理対象のCFn

AWSTemplateFormatVersion: '2010-09-09'
Description: This CloudFormation template to create Cross Account Config Rules for Managed Account.

Parameters:
  Environment:
    Description: Type of this environment.
    Type: String
    Default: stg
    AllowedValues:
      - prd
      - stg
      - dev
  SystemName:
    Description: Name of this system.
    Type: String
    Default: web
  AllowAccessRole:
    Description: Role of Lambda on Cross Account (ARN)
    Type: String
    Default: Role on Admin Account
  ConfigRulesLambda:
    Description: Lambda for ConfigRules (Cross Account Lambda ARN)
    Type: String
    Default: Cross Account Lambda ARN

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Environment Configuration
        Parameters:
          - SystemName
          - Environment
      - Label:
          default: Config Configure
        Parameters:
          - ConfigRulesLambda
          - AllowAccessRole

Resources:
  # Config Rules
  ConfigRuleIAMNoUser:
    Type: AWS::Config::ConfigRule
    Properties:
      ConfigRuleName: "ConfigRuleIAM_NO_USER"
      Description: "IAM NO USER"
      InputParameters:
        tag1Key: test1
      Scope:
        ComplianceResourceTypes:
          - "AWS::IAM::User"
      Source:
        Owner: "CUSTOM_LAMBDA"
        SourceIdentifier: !Sub ${ConfigRulesLambda}
        SourceDetails:
          - EventSource: "aws.config"
            MessageType: "ConfigurationItemChangeNotification"

  # IAM Role
  ConfigRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub role-exec-config-rules
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSConfigRole
        - arn:aws:iam::aws:policy/SecurityAudit
      Path: /
      AssumeRolePolicyDocument: !Sub |
        {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "AWS": "${AllowAccessRole}"
              },
              "Action": "sts:AssumeRole"
            },
            {
              "Effect": "Allow",
              "Principal": {
                "Service": "config.amazonaws.com"
              },
              "Action": "sts:AssumeRole"
            }
          ]
        }

  # IAM Policy
  LambdaExecPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: !Sub policy-${Environment}-${SystemName}-lambda
      Description: !Sub policy-${Environment}-${SystemName}-lambda
      PolicyDocument: !Sub |
        {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "config:PutEvaluations"
              ],
              "Resource": [
                "*"
              ]
            }
          ]
        }
      Roles:
        - !Ref ConfigRole

管理対象アカウントから評価を実行

管理対象から評価を実施し、結果が表示されるかやってみました。

表示できました!

最後に

かなり複雑で、はまって検証時間がかかりました。クロスアカウントはやっぱりむずかしいですね。CFn化したので、ある程度簡略化できましたが。最近Resource Access Managerというアカウント間でリソースを共有できるサービスがでたので、こちらのアップデートに期待です。

参考