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

コンニチハ、千葉です。

はじめに

複数チームで利用していくと、たくさん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 Crross 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を利用して複数のアカウントに展開しましょう。

[新機能] CloudFormation StackSetsを試してみた

管理対象のCFn

AWSTemplateFormatVersion: '2010-09-09'
Description: This CloudFormation template to create Crross 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(Crros 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 Setting
#  ConfigRecorder: 
#    Type: AWS::Config::ConfigurationRecorder
#    Properties: 
#      Name: default
#      RecordingGroup: 
#        ResourceTypes: 
#          - "AWS::IAM::User"
#      RoleARN: 
#        Fn::GetAtt: 
#          - ConfigRole
#          - Arn 

  # 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というアカウント間でリソースを共有できるサービスがでたので、こちらのアップデートに期待です。

参考