こんにちは。X(クロス) イノベーション 本部 ソフトウェアデザインセンター セキュリティグループの耿です。 CDKで Amazon Aurora データベース クラスタ を作成し、Secrets Managerで管理しているパスワードをローテーションしてみました。 ローテーションはSecrets Managerのマネジメントコンソールからでも設定できますが、CDKでも非常に簡単に書けました。やり方は 公式ドキュメント には書かれているものの、日本語の情報があまり見当たらなかったため書き残しておきます。 ※この記事のサンプルコードではAurora Serverlessを作成していますが、プロビジョンド版でも同じ方法でパスワードローテーションを実現できます。 公式ドキュメント マスターユーザーのローテーション(シングルユーザーローテーション) Secrets Managerにアクセスできない場合のエラー シングルユーザーローテーションで作成されたリソースを見てみる ローテーションを実行するLambda関数 Lambda関数のセキュリティグループ Lambda関数の実行ロール シングルユーザーローテーションのオプション マスターユーザー以外のユーザーのパスワードローテーション(マルチユーザーローテーション/交代ユーザーローテーション) マルチユーザーローテーションで作成されたリソース マルチユーザーローテーションのオプション まとめ 公式ドキュメント CDKの aws_rds モジュールの Rotating credentials セクションにクレデンシャルのローテーションに関する記載があり、これを参考にしました。 https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_rds-readme.html#rotating-credentials マスターユーザーのローテーション(シングルユーザーローテーション) 以下のCDKコードでリソースを作成します。 import { Stack , StackProps } from "aws-cdk-lib" ; import * as ec2 from "aws-cdk-lib/aws-ec2" ; import * as rds from "aws-cdk-lib/aws-rds" ; import { Construct } from "constructs" ; export class MyStack extends Stack { constructor( scope: Construct , id: string , props?: StackProps ) { super( scope , id , props ); // プライベートサブネットを持つVPC const vpc = new ec2.Vpc ( this , "MyVpc" , { cidr: "10.0.0.0/16" , enableDnsHostnames: true , enableDnsSupport: true , subnetConfiguration: [ { name: "myPrivateSubnet" , subnetType: ec2.SubnetType.PRIVATE_ISOLATED , cidrMask: 20 , } , ] , } ); // VPCエンドポイント用セキュリティグループ const VpceSG = new ec2.SecurityGroup ( this , "MyVpceSg" , { vpc: vpc , allowAllOutbound: true , } ); // Secrets ManagerへのVPCエンドポイント vpc.addInterfaceEndpoint ( "SecretsManagerEndpoint" , { service: ec2.InterfaceVpcEndpointAwsService.SECRETS_MANAGER , subnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED } , securityGroups: [ VpceSG ] , privateDnsEnabled: true , } ); // Aurora Serverlessクラスタ const auroraCluster = new rds.ServerlessCluster ( this , "MyAuroraCluster" , { engine: rds.DatabaseClusterEngine.AURORA_MYSQL , vpc: vpc , vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED } , } ); // パスワードのローテーションを設定 auroraCluster.addRotationSingleUser (); } } Aurora Serverless クラスタ を以上のサンプルコードで作成すると、マスターユーザーの情報はSecrets Managerに保存されます。 マスターユーザーのパスワードをローテーション設定しているのは次の一行のみです。これだけでローテーションが有効化され、ローテーションを実行するLambda関数などのリソースが作成されます。非常に簡単ですね。 auroraCluster.addRotationSingleUser (); マネジメントコンソールでSecrets Managerのシークレットを確認すると、確かにローテーションが有効になっていることがわかります。 addRotationSingleUser() 関数で有効になるローテーションは 「シングルユーザーローテーション」 と呼ばれ、ユーザーのパスワードをそのまま更新するだけの単純なローテーションです。 Secrets Managerにアクセスできない場合のエラー デフォルトでは、ローテーションを実行するLambda関数はデータベース クラスタ と同じサブネットにデプロイされます。 Lambda関数がSecrets Managerにアクセスできるようにする必要があり、今回はそのための VPC エンドポイントを作成しています。 Lambda関数がSecrets Managerにアクセスできない場合、マネジメントコンソールから手動でローテーションを実行すると以下のエラーが表示されます。 シークレット「MyAuroraClusterSecretD92700-ozDfy6jZIGiv」をローテーションできませんでした。 A previous rotation isn't complete. That rotation will be reattempted. また、Lambda関数のCloudWatch Logsロググループには次のように タイムアウト が記録されます。 START RequestId: 3031c3a5-9624-49ed-994e-db8f0cb14d63 Version: $LATEST END RequestId: 3031c3a5-9624-49ed-994e-db8f0cb14d63 REPORT RequestId: 3031c3a5-9624-49ed-994e-db8f0cb14d63 Duration: 30035.14 ms Billed Duration: 30000 ms Memory Size: 128 MB Max Memory Used: 70 MB 2022-05-24T04:28:30.600Z 3031c3a5-9624-49ed-994e-db8f0cb14d63 Task timed out after 30.04 seconds シングルユーザーローテーションで作成されたリソースを見てみる auroraCluster.addRotationSingleUser (); この一行でどのようなリソースが作成されているのか見てみました。 ローテーションを実行するLambda関数 まず、 MyStackMyAuroraClusterRotationSingleUser~ という名前でLambda関数が作成されていました。 このLambda関数はデータベース クラスタ と同じサブネットに配置されています。 Lambda関数のセキュリティグループ Lambda関数のセキュリティグループも新規に作成されていました。インバウンドルールはなく、アウトバウンドルールは全ての通信を許可しています。 また、データベース クラスタ のセキュリティグループは、Lambda関数のセキュリティグループから3306ポートのインバウンド通信が許可されていました。 Lambda関数の実行ロール Lambda関数の実行ロールが作成され、4つのポリシーが付けられていました。 AWSLambdaBasicExecutionRole( AWS 管理) AWSLambdaVPCAccessExecutionRole( AWS 管理) SecretsManagerRDSMySQLRotationSingleUserRolePolicy0(カスタマーインライン) SecretsManagerRDSMySQLRotationSingleUserRolePolicy1(カスタマーインライン) インラインポリシー SecretsManagerRDSMySQLRotationSingleUserRolePolicy0 は次のようになっていました。(ネットワークインターフェースの操作を許可しているのですが、なぜここで必要なのか分かりません) { " Statement ": [ { " Action ": [ " ec2:CreateNetworkInterface ", " ec2:DeleteNetworkInterface ", " ec2:DescribeNetworkInterfaces ", " ec2:DetachNetworkInterface " ] , " Resource ": " * ", " Effect ": " Allow " } ] } インラインポリシー SecretsManagerRDSMySQLRotationSingleUserRolePolicy1 は次のようになっていました。Lambda関数からSecrets Managerへのアクセスを許可しています。 Resourceは該当リージョンの全てのSecrets Managerシークレットを指しており、広めの許可です。 { " Statement ": [ { " Condition ": { " StringEquals ": { " secretsmanager:resource/AllowRotationLambdaArn ": " arn:aws:lambda:ap-northeast-1:<アカウントID>:function:MyStackMyAuroraClusterRotationSingleUser4A86DF55 " } } , " Action ": [ " secretsmanager:DescribeSecret ", " secretsmanager:GetSecretValue ", " secretsmanager:PutSecretValue ", " secretsmanager:UpdateSecretVersionStage " ] , " Resource ": " arn:aws:secretsmanager:ap-northeast-1:<アカウントID>:secret:* ", " Effect ": " Allow " } , { " Action ": [ " secretsmanager:GetRandomPassword " ] , " Resource ": " * ", " Effect ": " Allow " } ] } 全体の構成は次の図のようになっています。 シングルユーザーローテーションのオプション auroraCluster.addRotationSingleUser (); シングルユーザーローテーションはこの一行で書けますが、いくつかオプションを渡すこともできます。 import { Duration } from "aws-cdk-lib" ; auroraCluster.addRotationSingleUser ( { automaticallyAfter: Duration.days ( 30 ), excludeCharacters: " %+~`#$&*()|[]{}:;<>?!'/@\"\\" , vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED } , endpoint: endpoint , securityGroup: securityGroup , // 2022/12/9追記:CDK v2.54.0〜 } ); automaticallyAfter : ローテーション間隔(デフォルトは30日) excludeCharacters : パスワードから除外する文字。デフォルトは「 %+~`#$&*()|[]{}:;<>?!'/@\"\」 vpcSubnets : ローテーション用Lambda関数を配置する VPC サブネット(デフォルトはデータベース クラスタ と同じサブネット) endpoint : ローテーション用Lambda関数がSecrets Managerにアクセスするために使う VPC エンドポイント。プライベート DNS が VPC で有効なら特に指定不要 securityGroup : (2022/12/9追記:CDK v2.54.0〜)ローテーション用Lambda関数のセキュリティグループ。指定しない場合は新規に作成される。指定することにより、Secrets Managerにアクセスするために使う VPC エンドポイントのアクセス元を、このセキュリティグループに限定しやすくなる マスターユーザー以外のユーザーのパスワードローテーション(マルチユーザーローテーション/交代ユーザーローテーション) アプリケーションからデータベースにアクセスするときはマスターユーザーではなく、権限を制限したユーザーを使うのが望ましいです。 addRotationSingleUser() 関数はマスターユーザーのローテーションのみを行うため、マスターユーザー以外のユーザーのパスワードをローテーションする場合、少し書き方が異なります。 // 「user」というユーザー名でパスワードを自動生成する const userSecret = new rds.DatabaseSecret ( this , "MyUserSecret" , { username: "user" , secretName: "MyAuroraClusterUserSecret" , masterSecret: auroraCluster.secret , } ); // データベースの接続情報を追加する const secretAttached = userSecret.attach ( auroraCluster ); // ローテーションを設定 auroraCluster.addRotationMultiUser ( "MyUserRotation" , { secret: secretAttached , } ); ローテーションには addRotationMultiUser() 関数を使います。これは 「マルチユーザーローテーション」もしくは「交代ユーザーローテーション」 と呼ばれるローテーション方法です。 ローテーション実行時はユーザーのパスワードをすぐに上書きするのではなく、元のユーザーと同じ権限を持つユーザーを新たにデータベースに作成します。同時に2つのユーザーが有効になるため、データベースにアクセスするアプリケーションがクレデンシャル情報をキャッシュしている場合でも、ローテーションによって急に接続できなくなる事態を回避できます。そして2回目以降のローテーションでは新規にユーザーは作成せず、2つ前のユーザーのパスワード情報を上書きすることで、古いパスワードを利用できなくします。 マルチユーザーローテーションを行うには、ユーザーをクローンする権限が必要であるため、マスターユーザーのシークレットを渡しています。 masterSecret: auroraCluster.secret , 以上はあくまでもシークレットの作成とローテーションの設定であり、 別途データベースに接続し、同じユーザー名でユーザーを作成する必要があります。作成するユーザーにはSecrets Managerに登録された自動生成パスワードを設定します。 1度ローテーションを実行した後のデータベースユーザー一覧を見ると、 user という名前のユーザーに加え、 user_clone という名前のユーザーも存在することがわかります。これ以降、 user と user_clone のパスワードが交互に変更されていきます。 マルチユーザーローテーションで作成されたリソース マルチユーザーローテーションを設定すると、シングルユーザーローテーションと同じく以下のリソースが作成されます ローテーション用Lambda関数 Lambda関数のセキュリティグループ Lambda関数の実行ロール Lambda関数の実行ロールは、シングルユーザーローテーションの時と若干異なり、以下のポリシーが付いていました。 AmazonRDSReadOnlyAccess ( AWS 管理) AWSLambdaBasicExecutionRole( AWS 管理) AWSLambdaVPCAccessExecutionRole( AWS 管理) SecretsManagerRDSMySQLRotationMultiUserRolePolicy1(カスタマーインライン) SecretsManagerRDSMySQLRotationMultiUserRolePolicy2(カスタマーインライン) SecretsManagerRDSMySQLRotationMultiUserRolePolicy3 (カスタマーインライン) インラインポリシー SecretsManagerRDSMySQLRotationMultiUserRolePolicy1 と SecretsManagerRDSMySQLRotationMultiUserRolePolicy2 は、シングルユーザーローテーションの時のインラインポリシーと同じ内容でした。 インラインポリシー SecretsManagerRDSMySQLRotationMultiUserRolePolicy3 はシングルユーザーローテーションにはなかったポリシーで、マスターユーザーのシークレットへのアクセスを許可しています。( ~UserRolePolicy2 で該当リージョンの全シークレットへの GetSecretValue を既に許可しているため、冗長であるように思えます) { " Statement ": [ { " Action ": [ " secretsmanager:GetSecretValue " ] , " Resource ": " arn:aws:secretsmanager:ap-northeast-1:<アカウントID>:secret:MyAuroraClusterSecretD92700-ozDfy6jZIGiv-iVeG0u ", " Effect ": " Allow " } ] } マルチユーザーローテーションのオプション マルチユーザーローテーションではパラメーター secret にローテーション対象のアタッチ済みシークレットを指定する必要があります。 auroraCluster.addRotationMultiUser ( "MyUserRotation" , { secret: secretAttached , } ); その他のオプションはシングルユーザーローテーションと同じものを指定できます。 automaticallyAfter excludeCharacters vpcSubnets endpoint securityGroup (2022/12/9追記:CDK v2.54.0〜) まとめ CDKでAurora クラスタ のパスワードをローテーションする設定を非常に簡単に書けました。マスターユーザーはシングルユーザーローテーション、それ以外のユーザーはマルチユーザーローテーションとなるのが個人的に面白かったです。 今回は試していませんが、 公式ドキュメント によるとAurora以外のRDSデータベースでも、同じような方法でパスワードのローテーションを実現できそうです。 執筆: @kou.kinyo2 、レビュー: @sato.taichi ( Shodo で執筆されました )