Configルールを使ってパブリックアクセスが有効になっているRDSのDBインスタンスを自動修復する

パブリックにアクセスできるRDSを使わせないように、Configルールを使って自動修復してみました
2022.04.21

はじめに

AWS Configはリソース構成を記録・管理するサービスで、Config ルールでは管理しているリソースの評価ができます。その条件に違反しているリソースは非準拠としてフラグをつけられます。

Config ルールにはこの違反した非準拠のリソースに対して「修復」のアクションを定義でき、想定したリソースの状態へと自動修復する機能があります。

今回は パブリックアクセス可能なRDSインスタンスを自動で修復する(パブリックアクセスを無効にする) Configルールを作成してみました。

CloudFormationテンプレート

StackSetsでマルチリージョン・マルチアカウントに展開できるよう、CloudFormationテンプレートで作成しました。

作成するリソース

  • IAMロール
    • 自動修復実行用
  • Configルール
  • 修復アクション
  • SSMドキュメント
    • AWS提供のドキュメントではなく新規に作成(理由は後ほど)
AWSTemplateFormatVersion: "2010-09-09"

# ------------------------------------------------------------#
# Resources
# ------------------------------------------------------------#
Resources:
  IamRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ssm-automation-role-for-rds-disable-public-access-${AWS::Region}
      Description: "role for ssm automation: s3 encryption"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service: "ssm.amazonaws.com"
            Action: "sts:AssumeRole"
      Policies:
        - PolicyName: !Sub Ssm-Automation-Policy-DisablePublicAccess-${AWS::Region}
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              Effect: "Allow"
              Action:
                - ssm:StartAutomationExecution
                - ssm:GetAutomationExecution
                - rds:DescribeDBInstances
                - rds:ModifyDBInstance
              Resource: "*"
  ConfigRule:
    Type: AWS::Config::ConfigRule
    Properties:
      ConfigRuleName: rds-instance-public-access-check
      Description: "Checks whether the Amazon Relational Database Service (RDS) instances are not publicly accessible. The rule is non-compliant if the publiclyAccessible field is true in the instance configuration item."
      Source:
        Owner: AWS
        SourceIdentifier: RDS_INSTANCE_PUBLIC_ACCESS_CHECK
      Scope:
        ComplianceResourceTypes:
          - "AWS::RDS::DBInstance"
  RemediationConfiguration:
    Type: AWS::Config::RemediationConfiguration
    Properties:
      Automatic: true
      ConfigRuleName: !Ref ConfigRule
      MaximumAutomaticAttempts: 5
      RetryAttemptSeconds: 1200
      TargetId: !Ref ConfigRemediationDisablePublicAccessToRDSInstance
      TargetType: "SSM_DOCUMENT"
      TargetVersion: "1"
      Parameters:
        DbiResourceId:
          ResourceValue:
            Value: RESOURCE_ID
        AutomationAssumeRole:
          StaticValue:
            Values:
              - !GetAtt [IamRole, "Arn"]
  ############################################################################################################
  # RDSインスタンスのパブリックアクセス修正用SMSドキュメント
  #  当初以下AWS管理のドキュメントを使用予定だったが、新規作成のRDSインスタンスへの実行がエラーとなったため一部AWS管理のドキュメントを修正して利用
  # https://docs.aws.amazon.com/systems-manager-automation-runbooks/latest/userguide/automation-aws-disable-rds-instance-public-access.html
  ############################################################################################################
  ConfigRemediationDisablePublicAccessToRDSInstance:
    Type: AWS::SSM::Document
    Properties:
      DocumentType: Automation
      Name: ConfigRemediation-DisablePublicAccessToRDSInstance
      Content:
        schemaVersion: "0.3"
        description: |
          ### Document name - ConfigRemediation-DisablePublicAccessToRDSInstance

          ## What does this document do?
          The runbook disables public accessibility for the Amazon RDS database instance you specify using 
          the [ModifyDBInstance](https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_ModifyDBInstance.html) API.

          ## Input Parameters
          * AutomationAssumeRole: (Required) The Amazon Resource Name (ARN) of the AWS Identity and Access Management (IAM) role that allows Systems Manager Automation to perform the actions on your behalf.
          * DbiResourceId: (Required) The resource identifier for the DB instance you want to disable public accessibility.

          ## Output Parameters
          * DisablePubliclyAccessibleOnRDS.Response: The standard HTTP response from the ModifyDBInstance API.

        assumeRole: "{{ AutomationAssumeRole }}"
        parameters:
          AutomationAssumeRole:
            type: String
            description: (Required) The Amazon Resource Name (ARN) of the AWS Identity and Access Management (IAM) role that allows Systems Manager Automation to perform the actions on your behalf.
            allowedPattern: ^arn:(aws[a-zA-Z-]*)?:iam::\d{12}:role/[\w+=,.@-]+
          DbiResourceId:
            type: String
            description: (Required) The resource identifier for the DB instance you want to disable public accessibility.
            allowedPattern: "db-[A-Z0-9]{26}"
        outputs:
          - DisablePubliclyAccessibleOnRDS.Response
        mainSteps:
          - name: GetRDSInstanceIdentifier
            action: "aws:executeAwsApi"
            description: |
              ## GetRDSInstanceIdentifier
              Gathers the DB instance identifier from the DB instance resource identifier.
              ## Outputs
              * DbInstanceIdentifier: The Amazon RDS DB instance identifier.
            timeoutSeconds: 600
            isEnd: false
            inputs:
              Service: rds
              Api: DescribeDBInstances
              Filters:
                - Name: "dbi-resource-id"
                  Values:
                    - "{{ DbiResourceId }}"
            outputs:
              - Name: DbInstanceIdentifier
                Selector: $.DBInstances[0].DBInstanceIdentifier
                Type: String
          - name: WaitForDBInstanceStatusToAvailable
            action: "aws:waitForAwsResourceProperty"
            timeoutSeconds: 1200
            isEnd: false
            description: |
              ## WaitForDBInstanceStatusToAvailable
              Waits for the DB instance to change to a AVAILABLE state.
            inputs:
              Service: rds
              Api: DescribeDBInstances
              DBInstanceIdentifier: "{{ GetRDSInstanceIdentifier.DbInstanceIdentifier }}"
              PropertySelector: "$.DBInstances[0].DBInstanceStatus"
              DesiredValues:
                - "available"
          - name: VerifyDBInstanceStatus
            action: "aws:assertAwsResourceProperty"
            timeoutSeconds: 600
            isEnd: false
            description: |
              ## VerifyDBInstanceStatus
              Verifies the DB instances is in an AVAILABLE state.
            inputs:
              Service: rds
              Api: DescribeDBInstances
              DBInstanceIdentifier: "{{ GetRDSInstanceIdentifier.DbInstanceIdentifier }}"
              PropertySelector: "$.DBInstances[0].DBInstanceStatus"
              DesiredValues:
                - "available"
          - name: DisablePubliclyAccessibleOnRDS
            action: "aws:executeAwsApi"
            description: |
              ## DisablePubliclyAccessibleOnRDS
              Disables public accessibility on your DB instance.
              ## Outputs
              * Response: The standard HTTP response from the ModifyDBInstance API.
            timeoutSeconds: 600
            isEnd: false
            inputs:
              Service: rds
              Api: ModifyDBInstance
              DBInstanceIdentifier: "{{ GetRDSInstanceIdentifier.DbInstanceIdentifier }}"
              PubliclyAccessible: false
            outputs:
              - Name: Response
                Selector: $
                Type: StringMap
          - name: WaitForDBInstanceStatusToModify
            action: "aws:waitForAwsResourceProperty"
            timeoutSeconds: 600
            isEnd: false
            description: |
              ## WaitForDBInstanceStatusToModify
              Waits for the DB instance to change to a MODIFYING state.
            inputs:
              Service: rds
              Api: DescribeDBInstances
              DBInstanceIdentifier: "{{ GetRDSInstanceIdentifier.DbInstanceIdentifier }}"
              PropertySelector: "$.DBInstances[0].DBInstanceStatus"
              DesiredValues:
                - "modifying"
          - name: WaitForDBInstanceStatusToAvailableAfterModify
            action: "aws:waitForAwsResourceProperty"
            timeoutSeconds: 600
            isEnd: false
            description: |
              ## WaitForDBInstanceStatusToAvailableAfterModify
              Waits for the DB instance to change to an AVAILABLE state
            inputs:
              Service: rds
              Api: DescribeDBInstances
              DBInstanceIdentifier: "{{ GetRDSInstanceIdentifier.DbInstanceIdentifier }}"
              PropertySelector: "$.DBInstances[0].DBInstanceStatus"
              DesiredValues:
                - "available"
          - name: VerifyDBInstancePubliclyAccess
            action: "aws:assertAwsResourceProperty"
            timeoutSeconds: 600
            isEnd: true
            description: |
              ## VerifyDBInstancePubliclyAccess
              Confirms public accessibility is disabled on the DB instance.
            inputs:
              Service: rds
              Api: DescribeDBInstances
              DBInstanceIdentifier: "{{ GetRDSInstanceIdentifier.DbInstanceIdentifier }}"
              PropertySelector: "$.DBInstances[0].PubliclyAccessible"
              DesiredValues:
                - "False"

リソースの確認

展開されるリソースの確認をしていきます。Configが有効化されているリージョンで先ほどのCloudFormationテンプレートを使ってスタックを作成してください。

Configルール

rds-instance-public-access-checkというConfigルールが作成されています。

対象のリソース変更時にチェックが実行され、非準拠となった場合には修復アクションで定義したSSMドキュメント(ConfigRemediation-DisablePublicAccessToRDSInstance)が実行されます。

ここの修復アクションで使われる権限は、新規で作成しているIAMロールを指定しています。

このConfigルールではRDSのDBインスタンスを対象にチェックしており、パブリックアクセスが有効になっているものがあると非準拠となります。

SSMドキュメント

先ほど修復アクションで指定されていたSSMドキュメントを確認します。

AWS Systems Managerのコンソールから ドキュメント>自己所有 を確認すると、ConfigRemediation-DisablePublicAccessToRDSInstanceというSSMドキュメントが作成されています。

このドキュメントで実行されるステップを確認すると、以下の7つが定義されています。

ざっと簡単に説明すると、以下のようなことを各ステップで行なっています。

  1. DBインスタンスの情報を取得
  2. ステータスがavailableになるまで待機
  3. ステータスがavailableになったことを確認
  4. DBインスタンスのpublic accessibilityを無効化
  5. ステータスがmodifyingになるまで待機
  6. ステータスがavailableになるまで待機
  7. DBインスタンスのpublic accessibilityが無効化されていることを確認

上記を非準拠となったDBインスタンスに対して実行することで、パブリックアクセスが可能なものを自動修復する仕組みとなっています。

動作確認

それではパブリックアクセスが有効になっているDBインスタンスを作成して、どのように動作するのか確認してみましょう。

パブリックアクセスを「あり」にチェックを入れてDBインスタンスを作成してみます。

Configルールを確認すると、非準拠のリソースとして作成したDBインスタンスが表示されました。

そこから少し待つと、ステータスに「アクションが正常に実行されました」と表示されます。

改めて作成したDBインスタンスを確認してみると、パブリックアクセスが自動で修復されていることが確認できました。

なぜAWS管理のSSMドキュメントを使わないのか

AWSから提供されているSSMドキュメントにAWSConfigRemediation-DisablePublicAccessToRDSInstanceという今回作成したドキュメントに似たものがあります。(というよりこのドキュメントをもとに作成しました。)

参考:AWSConfigRemediation-DisablePublicAccessToRDSInstance - AWS Systems Manager Automation runbook reference

このドキュメントを使わなかった理由は、DBインスタンスを作成時の修復を想定されたステップになっていなかったからです。

このSSMドキュメントを使用して修復アクションを実行してみた所、既存のRDSに対しては問題なく実行できるのですが、新規作成のDBインスタンスを実行した際にはエラーとなります。

コンソールからエラー詳細が確認できないので、CLIで確認すると以下のエラーメッセージが出力されました。

"Step fails when it is Execute/Cancelling action. Property value 'creating' from the API output is not in the desired values. Desired values: ['available'].. Please refer to Automation Service Troubleshooting Guide for more diagnosis details."

これは、DBインスタンスが作成中(creating)のステータス時にSSMドキュメントが ステータスがavailableになったことを確認する のステップが実行されてしまったため、想定とステータスが違うことでエラーとなったようです。

そのため、今回はAWS管理のSSMドキュメントをもとに ステータスがavailableになるまで待機するステップを追加したものを新規で作成しています。

おわりに

Configルールの自動修復を使ってRDSのパブリックアクセスを無効にしてみました。RDSのインスタンスをパブリックに公開するケースはほとんどないかと思いますので、統制をかけたい際にぜひ利用してみてください。

参考