こんにちは。X(クロス)イノベーション本部 ソフトウェアデザインセンター セキュリティグループの耿です。
CDKでAmazon Auroraデータベースクラスタを作成し、Secrets Managerで管理しているパスワードをローテーションしてみました。
ローテーションはSecrets Managerのマネジメントコンソールからでも設定できますが、CDKでも非常に簡単に書けました。やり方は公式ドキュメントには書かれているものの、日本語の情報があまり見当たらなかったため書き残しておきます。
※この記事のサンプルコードではAurora Serverlessを作成していますが、プロビジョンド版でも同じ方法でパスワードローテーションを実現できます。
- 公式ドキュメント
- マスターユーザーのローテーション(シングルユーザーローテーション)
- シングルユーザーローテーションで作成されたリソースを見てみる
- シングルユーザーローテーションのオプション
- マスターユーザー以外のユーザーのパスワードローテーション(マルチユーザーローテーション/交代ユーザーローテーション)
- マルチユーザーローテーションで作成されたリソース
- マルチユーザーローテーションのオプション
- まとめ
公式ドキュメント
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で執筆されました)