AWS CDKでAmazon Aurora DB クラスターとAWS Secrets Managerとの統合を設定してみた
AWS CDKでもRDSとSecrets Managerの統合を設定したいな
こんにちは、のんピ(@non____97)です。
皆さんはAWS CDKでもAmazon RDSとAmazon Secrets Managerの統合を設定したいなと思ったことはありますか? 私はあります。
こちらの機能を使うことで自動でRDSのマスターユーザーのシークレットが作成されます。特にアツいのが自動ローテーション用のLambda関数を作成する必要がない点ですね。
こちらの機能はCFnでも有効化することが可能です。具体的にはAWS::RDS::DBCluster
またはAWS::RDS::DBInstance
でManageMasterUserPassword
をtrue
にします。
ManageMasterUserPassword
A value that indicates whether to manage the master user password with AWS Secrets Manager.
For more information, see Password management with AWS Secrets Manager in the Amazon RDS User Guide and Password management with AWS Secrets Manager in the Amazon Aurora User Guide.
Constraints:
- Can't manage the master user password with AWS Secrets Manager if MasterUserPassword is specified.
Valid for: Aurora DB clusters and Multi-AZ DB clusters
Required: No
Type: Boolean
Update requires: No interruption
しかし、AWS CDKのL2 ConstructであるDatabaseClusterやDatabaseInstanceにそのようなプロパティはありません。
マスターユーザーの認証情報の指定はcredentials
で行います。しかし、Credentialsを確認してもRDSとSecrets Managerの統合についての言及はありませんでした。良きようにやってくれるのでしょうか。
実際に試してみたので紹介します。
いきなりまとめ
- Escape hatchで
manageMasterUserPassword
をtrue
に指定することでDBクラスターとAWS Secrets Managerとの統合が可能 manageMasterUserPassword
をtrue
にする場合はmasterUserPassword
は指定してはならないaddPropertyDeletionOverride("MasterUserPassword")
で削除する
tryRemoveChild("Secret")
としなければ、Secrets Managerとの統合されていないシークレットが作成されてしまうmasterUsername
をL1 Constructで指定しなければ、masterUsername
の参照エラーでデプロイできない- Secrets Managerとの統合によって作成されたシークレットのローテーション間隔を指定することも可能
- Secrets Managerとの統合によって作成されたシークレットのARNは
getAtt("MasterUserSecret.SecretArn")
で取得可能
- Secrets Managerとの統合によって作成されたシークレットのARNは
やってみた
credentials に username と secretName を指定した場合
まず、credentials
にusername
とsecretName
を指定した場合の挙動を確認します。
以下のようにAmazon Aurora DB クラスターを定義します。
import * as cdk from "aws-cdk-lib"; import { Construct } from "constructs"; export interface AuroraProps { vpc: cdk.aws_ec2.IVpc; securityGroup: cdk.aws_ec2.ISecurityGroup; } export class Aurora extends Construct { constructor(scope: Construct, id: string, props: AuroraProps) { super(scope, id); // DB Cluster Parameter Group const dbClusterParameterGroup = new cdk.aws_rds.ParameterGroup( this, "DbClusterParameterGroup", { engine: cdk.aws_rds.DatabaseClusterEngine.auroraPostgres({ version: cdk.aws_rds.AuroraPostgresEngineVersion.VER_15_2, }), description: "aurora-postgresql15", parameters: { log_statement: "none", "pgaudit.log": "all", "pgaudit.role": "rds_pgaudit", shared_preload_libraries: "pgaudit", }, } ); // DB Parameter Group const dbParameterGroup = new cdk.aws_rds.ParameterGroup( this, "DbParameterGroup", { engine: cdk.aws_rds.DatabaseClusterEngine.auroraPostgres({ version: cdk.aws_rds.AuroraPostgresEngineVersion.VER_15_2, }), description: "aurora-postgresql15", } ); // Subnet Group const subnetGroup = new cdk.aws_rds.SubnetGroup(this, "SubnetGroup", { description: "description", vpc: props.vpc, subnetGroupName: "SubnetGroup", vpcSubnets: props.vpc.selectSubnets({ onePerAz: true, subnetType: cdk.aws_ec2.SubnetType.PRIVATE_ISOLATED, }), }); // Monitoring Role const monitoringRole = new cdk.aws_iam.Role(this, "MonitoringRole", { assumedBy: new cdk.aws_iam.ServicePrincipal( "monitoring.rds.amazonaws.com" ), managedPolicies: [ cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName( "service-role/AmazonRDSEnhancedMonitoringRole" ), ], }); // DB Cluster const clusterIdentifier = "db-cluster"; new cdk.aws_rds.DatabaseCluster(this, "Default", { engine: cdk.aws_rds.DatabaseClusterEngine.auroraPostgres({ version: cdk.aws_rds.AuroraPostgresEngineVersion.VER_15_2, }), writer: cdk.aws_rds.ClusterInstance.provisioned("Instance1", { instanceType: cdk.aws_ec2.InstanceType.of( cdk.aws_ec2.InstanceClass.T3, cdk.aws_ec2.InstanceSize.MEDIUM ), allowMajorVersionUpgrade: false, autoMinorVersionUpgrade: true, enablePerformanceInsights: true, parameterGroup: dbParameterGroup, performanceInsightRetention: cdk.aws_rds.PerformanceInsightRetention.DEFAULT, publiclyAccessible: false, instanceIdentifier: "db-instance1", }), backup: { retention: cdk.Duration.days(7), preferredWindow: "16:00-16:30", }, cloudwatchLogsExports: ["postgresql"], cloudwatchLogsRetention: cdk.aws_logs.RetentionDays.ONE_YEAR, clusterIdentifier, copyTagsToSnapshot: true, credentials: { username: "postgresAdmin", secretName: `${clusterIdentifier}/postgresAdmin`, }, defaultDatabaseName: "testDB", deletionProtection: false, iamAuthentication: false, monitoringInterval: cdk.Duration.minutes(1), monitoringRole, parameterGroup: dbClusterParameterGroup, preferredMaintenanceWindow: "Sat:17:00-Sat:17:30", storageEncrypted: true, vpc: props.vpc, securityGroups: [props.securityGroup], subnetGroup, }); } }
cdk deploy
後、デプロイされたDBインスタンスの編集画面に遷移して、RDSとSecrets Managerとの統合が有効化されているか確認します。
AWS Secrets Manager でマスター認証情報を管理する
にチェックが入っていないので、有効化はされていなさそうですね。
作成されたシークレットも確認します。
自動ローテーションは無効になっているということから、RDSとSecrets Managerとの統合が有効化されていないことがわかります。
せっかくなので、シークレットに保存されている認証情報を使ってDBクラスターに接続します。
# PostgreSQL 15のクライアントをインストール $ sudo dnf install postgresql15 Last metadata expiration check: 0:00:28 ago on Sun Jun 4 23:50:34 2023. Dependencies resolved. ================================================================================================================================= Package Architecture Version Repository Size ================================================================================================================================= Installing: postgresql15 x86_64 15.0-1.amzn2023.0.2 amazonlinux 1.6 M Installing dependencies: postgresql15-private-libs x86_64 15.0-1.amzn2023.0.2 amazonlinux 143 k Transaction Summary ================================================================================================================================= Install 2 Packages Total download size: 1.8 M Installed size: 6.5 M Is this ok [y/N]: y Downloading Packages: (1/2): postgresql15-private-libs-15.0-1.amzn2023.0.2.x86_64.rpm 1.2 MB/s | 143 kB 00:00 (2/2): postgresql15-15.0-1.amzn2023.0.2.x86_64.rpm 9.5 MB/s | 1.6 MB 00:00 --------------------------------------------------------------------------------------------------------------------------------- Total 7.4 MB/s | 1.8 MB 00:00 Running transaction check Transaction check succeeded. Running transaction test Transaction test succeeded. Running transaction Preparing : 1/1 Installing : postgresql15-private-libs-15.0-1.amzn2023.0.2.x86_64 1/2 Installing : postgresql15-15.0-1.amzn2023.0.2.x86_64 2/2 Running scriptlet: postgresql15-15.0-1.amzn2023.0.2.x86_64 2/2 Verifying : postgresql15-15.0-1.amzn2023.0.2.x86_64 1/2 Verifying : postgresql15-private-libs-15.0-1.amzn2023.0.2.x86_64 2/2 Installed: postgresql15-15.0-1.amzn2023.0.2.x86_64 postgresql15-private-libs-15.0-1.amzn2023.0.2.x86_64 Complete! # インストールされたことを確認 $ psql --version psql (PostgreSQL) 15.0 # シークレットから認証情報を取得 $ get_secrets_value=$(aws secretsmanager get-secret-value \ --secret-id db-cluster/postgresAdmin \ --region us-east-1 \ | jq -r .SecretString) # 環境変数に埋め込み $ export PGHOST=$(echo "${get_secrets_value}" | jq -r .host) $ export PGPORT=$(echo "${get_secrets_value}" | jq -r .port) $ export PGDATABASE=$(echo "${get_secrets_value}" | jq -r .dbname) $ export PGUSER=$(echo "${get_secrets_value}" | jq -r .username) $ export PGPASSWORD=$(echo "${get_secrets_value}" | jq -r .password) # DBクラスターに接続 $ psql psql (15.0, server 15.2) SSL connection (protocol: TLSv1.2, cipher: AES128-SHA256, compression: off) Type "help" for help. testDB=>
接続できましたね。
Escape hatchで manageMasterUserPassword を true に指定
L2 ConstructでRDSとSecrets Managerとの統合を有効化することは難しそうなので、Escape hatchで無理やり指定します。
const cfnDbCluster = dbCluster.node .defaultChild as cdk.aws_rds.CfnDBCluster; cfnDbCluster.manageMasterUserPassword = true;
diffを確認します。
$ npx cdk diff Stack AuroraStack Resources [~] AWS::RDS::DBCluster Aurora/Default Aurora2CBAB212 └─ [+] ManageMasterUserPassword └─ true
ManageMasterUserPassword
がtrue
に指定されただけですね。
cdk deploy
後、デプロイされたDBインスタンスの編集画面に遷移して、RDSとSecrets Managerとの統合が有効化されているか確認します。
AWS Secrets Manager でマスター認証情報を管理する
にチェックが入っているので、有効化はされていそうです。
Secrets Managerからも確認します。
このシークレットはAmazon RDS (rds) によって作成されました
とあることからRDSとSecrets Managerとの統合によって作成されたシークレットであることが分かります。
なお、統合によって作成されたシークレットにはDBクラスターエンドポイントやDB名、ポート番号の情報はないようです。
PostgreSQLのクライアントからシークレットを再取得せずにDBクラスターに接続してみます。
$ psql psql: error: connection to server at "db-cluster.cluster-cicjym7lykmq.us-east-1.rds.amazonaws.com" (10.1.1.118), port 5432 failed: FATAL: password authentication failed for user "postgresAdmin" connection to server at "db-cluster.cluster-cicjym7lykmq.us-east-1.rds.amazonaws.com" (10.1.1.118), port 5432 failed: FATAL: password authentication failed for user "postgresAdmin"
パスワードが変わってしまったので接続できないですね。
Secrets Managerとの統合によって作成されたシークレットを取得して、再接続します。
# シークレットから認証情報を取得 $ get_secrets_value=$(aws secretsmanager get-secret-value \ --secret-id 'rds!cluster-8a84d67a-61f3-421d-8cd4-aaa0c1877f56' \ --region us-east-1 \ | jq -r .SecretString) # 環境変数に埋め込み $ export PGUSER=$(echo "${get_secrets_value}" | jq -r .username) $ export PGPASSWORD=$(echo "${get_secrets_value}" | jq -r .password) # DBクラスターに接続 $ psql psql (15.0, server 15.2) SSL connection (protocol: TLSv1.2, cipher: AES128-SHA256, compression: off) Type "help" for help. # DBクラスター内のロールの確認 testDB=> \du List of roles Role name | Attributes | Member of -----------------+------------------------------------------------------------+-------------------------------------------------- ------------------------------------------------------------ postgresAdmin | Create role, Create DB +| {rds_superuser} | Password valid until infinity | rds_ad | Cannot login | {} rds_iam | Cannot login | {} rds_password | Cannot login | {} rds_replication | Cannot login | {} rds_superuser | Cannot login | {pg_read_all_data,pg_write_all_data,pg_monitor,pg_signal_backend,pg_checkpoint,rds_replication,rds_password} rdsadmin | Superuser, Create role, Create DB, Replication, Bypass RLS+| {} | Password valid until infinity |
接続できましたね。裏で特殊なロールが作成されているのかなと思ったのですが、そのようなことはないようです。
Secrets Managerとの統合によって作成されたシークレットの自動ローテーション設定の変更
手動でSecrets Managerとの統合によって作成されたシークレットの自動ローテーション設定の変更してみます。
スケジュール式とウィンドウを変更して保存
をクリックします。シークレットを保存するとすぐにローテーションさせます。次回のローテーションは、スケジュールに基づいて開始されます。
にはチェックを入れたままにしておきます。
設定変更後、マネジメントコンソールからシークレットを再取得するとpassword
の値が変わっていることを確認できます。
CloudTrailから手動でシークレットのローテーション設定を変更した際のイベントを確認します。
SLRSession
というセッションによってパスワードのローテーションがされていますね。
{ "eventVersion": "1.08", "userIdentity": { "type": "AssumedRole", "principalId": "AROA6KUFAVPUVTZY2EZVY:SLRSession", "arn": "arn:aws:sts::<AWSアカウントID>:assumed-role/AWSServiceRoleForRDS/SLRSession", "accountId": "<AWSアカウントID>", "accessKeyId": "ASIA6KUFAVPUUJF5PYUR", "sessionContext": { "sessionIssuer": { "type": "Role", "principalId": "AROA6KUFAVPUVTZY2EZVY", "arn": "arn:aws:iam::<AWSアカウントID>:role/aws-service-role/rds.amazonaws.com/AWSServiceRoleForRDS", "accountId": "<AWSアカウントID>", "userName": "AWSServiceRoleForRDS" }, "webIdFederationData": {}, "attributes": { "creationDate": "2023-06-05T02:34:52Z", "mfaAuthenticated": "false" } }, "invokedBy": "rds.amazonaws.com" }, "eventTime": "2023-06-05T02:34:52Z", "eventSource": "secretsmanager.amazonaws.com", "eventName": "PutSecretValue", "awsRegion": "us-east-1", "sourceIPAddress": "rds.amazonaws.com", "userAgent": "rds.amazonaws.com", "requestParameters": { "secretId": "arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:rds!cluster-8a84d67a-61f3-421d-8cd4-aaa0c1877f56-psD95W", "clientRequestToken": "4f16bd74-5ddd-48f5-b24d-b2e9bacdd859", "versionStages": [ "AWSPENDING" ] }, "responseElements": { "arn": "arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:rds!cluster-8a84d67a-61f3-421d-8cd4-aaa0c1877f56-psD95W" }, "requestID": "eeef0705-e89f-4deb-91b9-c65e8191b94d", "eventID": "92adb311-c7ba-45fa-847d-6f399040b549", "readOnly": false, "eventType": "AwsApiCall", "managementEvent": true, "recipientAccountId": "<AWSアカウントID>", "eventCategory": "Management" }
なお、一度キャンセルされていますが、キャンセル理由は特に書いてありませんでした。
{ "eventVersion": "1.08", "userIdentity": { . . (中略) . . }, "eventTime": "2023-06-05T02:33:47Z", "eventSource": "secretsmanager.amazonaws.com", "eventName": "CancelRotateSecret", "awsRegion": "us-east-1", "sourceIPAddress": "<IPアドレス>", "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36", "requestParameters": { "secretId": "arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:rds!cluster-8a84d67a-61f3-421d-8cd4-aaa0c1877f56-psD95W" }, "responseElements": { "aRN": "arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:rds!cluster-8a84d67a-61f3-421d-8cd4-aaa0c1877f56-psD95W", "name": "rds!cluster-8a84d67a-61f3-421d-8cd4-aaa0c1877f56" }, "requestID": "ff70429a-dae4-482f-a8af-60b15c622daf", "eventID": "29d44b8a-42d3-448b-a669-67d52ecd3112", "readOnly": false, "eventType": "AwsApiCall", "managementEvent": true, "recipientAccountId": "<AWSアカウントID>", "eventCategory": "Management", "tlsDetails": { "tlsVersion": "TLSv1.3", "cipherSuite": "TLS_AES_128_GCM_SHA256", "clientProvidedHostHeader": "secretsmanager.us-east-1.amazonaws.com" }, "sessionCredentialFromConsole": "true" }
ローテーションされたパスワードを使ってDBクラスターに接続できることも確認できました。
統合されていないシークレットを削除する
以上でめでたしめでたし。
とはいきません。
というのも統合されていないシークレットは引き続き残っています。余計なリソースが作成されるのは避けたいところです。
どうにかして統合されていないシークレットを削除します。
まず、DBクラスターのcredentials.secretName
の指定をしないようにしてみます。
credentials: { username: "postgresAdmin", },
シークレットの名前が指定されなければ、シークレットも作成されないのでは? という淡い期待を込めています。
編集後diffを確認します。
$ npx cdk diff Stack AuroraStack Resources [~] AWS::SecretsManager::Secret Aurora/Default/Secret AuroraSecret7ACECA7F replace └─ [-] Name (requires replacement) └─ db-cluster/postgresAdmin
replace
となっているので、リソースは削除されず作り直されそうですね。
一旦気にせずcdk deploy
してみます。
$ npx cdk deploy ✨ Synthesis time: 7.61s AuroraStack: start: Building 71646dd7c5446c0e5cce7a079467281bd6f33982d50d7bb1f555cef193c4747a:<AWSアカウントID>-us-east-1 AuroraStack: success: Built 71646dd7c5446c0e5cce7a079467281bd6f33982d50d7bb1f555cef193c4747a:<AWSアカウントID>-us-east-1 AuroraStack: start: Publishing 71646dd7c5446c0e5cce7a079467281bd6f33982d50d7bb1f555cef193c4747a:<AWSアカウントID>-us-east-1 AuroraStack: success: Published 71646dd7c5446c0e5cce7a079467281bd6f33982d50d7bb1f555cef193c4747a:<AWSアカウントID>-us-east-1 AuroraStack: deploying... [1/1] AuroraStack: creating CloudFormation changeset... 11:57:29 | UPDATE_FAILED | AWS::RDS::DBCluster | Aurora2CBAB212 Resource handler returned message: "MasterUserPassword and ManageMasterUserPassword are mutually exclusive. Specify onl y one of these parameters. (Service: Rds, Status Code: 400, Request ID: 6662f5ae-61ba-43cd-96e2-c3fdec0fab99)" (Request Token: 1af22298-1f3d-31a7-5e4a-9c00feff136c, HandlerErrorCode: InvalidRequest) 11:57:34 | UPDATE_FAILED | AWS::RDS::DBCluster | Aurora2CBAB212 Resource handler returned message: "MasterUserPassword and ManageMasterUserPassword are mutually exclusive. Specify onl y one of these parameters. (Service: Rds, Status Code: 400, Request ID: d9fb353a-0fc3-4511-8b79-ba0d4c2fa9ed)" (Request Token: 88bc765c-826d-5e6b-c3f3-960150bb7edc, HandlerErrorCode: InvalidRequest) ❌ AuroraStack failed: Error: The stack named AuroraStack failed to deploy: UPDATE_ROLLBACK_FAILED (The following resource(s) failed to update: [Aurora2CBAB212]. ): Resource handler returned message: "MasterUserPassword and ManageMasterUserPassword are mutually exclusive. Specify only one of these parameters. (Service: Rds, Status Code: 400, Request ID: 6662f5ae-61ba-43cd-96e2-c3fdec0fab99)" (RequestToken: 1af22298-1f3d-31a7-5e4a-9c00feff136c, HandlerErrorCode: InvalidRequest), Resource handler returned message: "MasterUserPassword and ManageMasterUserPassword are mutually exclusive. Specify only one of these parameters. (Service: Rds, Status Code: 400, Request ID: d9fb353a-0fc3-4511-8b79-ba0d4c2fa9ed)" (RequestToken: 88bc765c-826d-5e6b-c3f3-960150bb7edc, HandlerErrorCode: InvalidRequest) at FullCloudFormationDeployment.monitorDeployment (/<ディレクトリパス>/aurora/node_modules/aws-cdk/lib/index.js:397:10236) at process.processTicksAndRejections (node:internal/process/task_queues:95:5) at async Object.deployStack2 [as deployStack] (/<ディレクトリパス>/aurora/node_modules/aws-cdk/lib/index.js:400:149977) at async /<ディレクトリパス>/aurora/node_modules/aws-cdk/lib/index.js:400:135508 ❌ Deployment failed: Error: The stack named AuroraStack failed to deploy: UPDATE_ROLLBACK_FAILED (The following resource(s) failed to update: [Aurora2CBAB212]. ): Resource handler returned message: "MasterUserPassword and ManageMasterUserPassword are mutually exclusive. Specify only one of these parameters. (Service: Rds, Status Code: 400, Request ID: 6662f5ae-61ba-43cd-96e2-c3fdec0fab99)" (RequestToken: 1af22298-1f3d-31a7-5e4a-9c00feff136c, HandlerErrorCode: InvalidRequest), Resource handler returned message: "MasterUserPassword and ManageMasterUserPassword are mutually exclusive. Specify only one of these parameters. (Service: Rds, Status Code: 400, Request ID: d9fb353a-0fc3-4511-8b79-ba0d4c2fa9ed)" (RequestToken: 88bc765c-826d-5e6b-c3f3-960150bb7edc, HandlerErrorCode: InvalidRequest) at FullCloudFormationDeployment.monitorDeployment (/<ディレクトリパス>/aurora/node_modules/aws-cdk/lib/index.js:397:10236) at process.processTicksAndRejections (node:internal/process/task_queues:95:5) at async Object.deployStack2 [as deployStack] (/<ディレクトリパス>/aurora/node_modules/aws-cdk/lib/index.js:400:149977) at async /<ディレクトリパス>/aurora/node_modules/aws-cdk/lib/index.js:400:135508 The stack named AuroraStack failed to deploy: UPDATE_ROLLBACK_FAILED (The following resource(s) failed to update: [Aurora2CBAB212]. ): Resource handler returned message: "MasterUserPassword and ManageMasterUserPassword are mutually exclusive. Specify only one of these parameters. (Service: Rds, Status Code: 400, Request ID: 6662f5ae-61ba-43cd-96e2-c3fdec0fab99)" (RequestToken: 1af22298-1f3d-31a7-5e4a-9c00feff136c, HandlerErrorCode: InvalidRequest), Resource handler returned message: "MasterUserPassword and ManageMasterUserPassword are mutually exclusive. Specify only one of these parameters. (Service: Rds, Status Code: 400, Request ID: d9fb353a-0fc3-4511-8b79-ba0d4c2fa9ed)" (RequestToken: 88bc765c-826d-5e6b-c3f3-960150bb7edc, HandlerErrorCode: InvalidRequest)
MasterUserPassword
とManageMasterUserPassword
を同時に指定することはできないと怒られました。
MasterUserPassword
を指定しないようにaddPropertyDeletionOverride
を使います。
const cfnDbCluster = dbCluster.node .defaultChild as cdk.aws_rds.CfnDBCluster; cfnDbCluster.manageMasterUserPassword = true; cfnDbCluster.addPropertyDeletionOverride("MasterUserPassword");
編集後、diffを確認します。
$ npx cdk diff Stack AuroraStack Resources [~] AWS::SecretsManager::Secret Aurora/Default/Secret AuroraSecret7ACECA7F replace └─ [-] Name (requires replacement) └─ db-cluster/postgresAdmin [~] AWS::RDS::DBCluster Aurora/Default Aurora2CBAB212 └─ [-] MasterUserPassword └─ {"Fn::Join":["",["{{resolve:secretsmanager:",{"Ref":"AuroraSecret7ACECA7F"},":SecretString:password::}}"]]}
MasterUserPassword
の参照が削除されましたね。
cdk deploy
します。
すんなりデプロイできましたが、作成されているリソースを確認するとシークレットが存在しているようでした。
Secret
というIDのリソースは不要なのでtryRemoveChild
でDBクラスターのL2 Constructから削除します。
const cfnDbCluster = dbCluster.node .defaultChild as cdk.aws_rds.CfnDBCluster; cfnDbCluster.manageMasterUserPassword = true; cfnDbCluster.addPropertyDeletionOverride("MasterUserPassword"); dbCluster.node.tryRemoveChild("Secret");
編集後、diffを確認します。
npx cdk diff Stack AuroraStack Resources [-] AWS::SecretsManager::Secret Aurora/Default/Secret AuroraSecret7ACECA7F destroy [-] AWS::SecretsManager::SecretTargetAttachment Aurora/Default/Secret/Attachment AuroraSecretAttachmentEAAB0558 destroy
シークレットがdestroy
になりましたね! これは完全勝利の予感です。
cdk deploy
します。
$ npx cdk deploy ✨ Synthesis time: 7.47s AuroraStack: deploying... [1/1] AuroraStack: creating CloudFormation changeset... ❌ AuroraStack failed: Error [ValidationError]: Template format error: Unresolved resource dependencies [AuroraSecret7ACECA7F] in the Resources block of the template at Request.extractError (/<ディレクトリパス>/aurora/node_modules/aws-cdk/lib/index.js:292:46245) at Request.callListeners (/<ディレクトリパス>/aurora/node_modules/aws-cdk/lib/index.js:292:89237) at Request.emit (/<ディレクトリパス>/aurora/node_modules/aws-cdk/lib/index.js:292:88685) at Request.emit (/<ディレクトリパス>/aurora/node_modules/aws-cdk/lib/index.js:292:195114) at Request.transition (/<ディレクトリパス>/aurora/node_modules/aws-cdk/lib/index.js:292:188666) at AcceptorStateMachine.runTo (/<ディレクトリパス>/aurora/node_modules/aws-cdk/lib/index.js:292:153538) at /<ディレクトリパス>/aurora/node_modules/aws-cdk/lib/index.js:292:153868 at Request.<anonymous> (/<ディレクトリパス>/aurora/node_modules/aws-cdk/lib/index.js:292:188958) at Request.<anonymous> (/<ディレクトリパス>/aurora/node_modules/aws-cdk/lib/index.js:292:195189) at Request.callListeners (/<ディレクトリパス>/aurora/node_modules/aws-cdk/lib/index.js:292:89405) { code: 'ValidationError', time: 2023-06-05T06:09:44.898Z, requestId: 'be6fb124-cdbf-4cf5-af1e-dcae92285640', statusCode: 400, retryable: false, retryDelay: 485.60885421936837 } ❌ Deployment failed: Error [ValidationError]: Template format error: Unresolved resource dependencies [AuroraSecret7ACECA7F] in the Resources block of the template at Request.extractError (/<ディレクトリパス>/aurora/node_modules/aws-cdk/lib/index.js:292:46245) at Request.callListeners (/<ディレクトリパス>/aurora/node_modules/aws-cdk/lib/index.js:292:89237) at Request.emit (/<ディレクトリパス>/aurora/node_modules/aws-cdk/lib/index.js:292:88685) at Request.emit (/<ディレクトリパス>/aurora/node_modules/aws-cdk/lib/index.js:292:195114) at Request.transition (/<ディレクトリパス>/aurora/node_modules/aws-cdk/lib/index.js:292:188666) at AcceptorStateMachine.runTo (/<ディレクトリパス>/aurora/node_modules/aws-cdk/lib/index.js:292:153538) at /<ディレクトリパス>/aurora/node_modules/aws-cdk/lib/index.js:292:153868 at Request.<anonymous> (/<ディレクトリパス>/aurora/node_modules/aws-cdk/lib/index.js:292:188958) at Request.<anonymous> (/<ディレクトリパス>/aurora/node_modules/aws-cdk/lib/index.js:292:195189) at Request.callListeners (/<ディレクトリパス>/aurora/node_modules/aws-cdk/lib/index.js:292:89405) { code: 'ValidationError', time: 2023-06-05T06:09:44.898Z, requestId: 'be6fb124-cdbf-4cf5-af1e-dcae92285640', statusCode: 400, retryable: false, retryDelay: 485.60885421936837 } Template format error: Unresolved resource dependencies [AuroraSecret7ACECA7F] in the Resources block of the template
削除したはずのシークレットの論理IDAuroraSecret7ACECA7F
が参照されており、エラーとなっているようです。
どのような時にシークレットが作成されるのかAWS CDKのソースコードを確認します。
cluster.tsを確認すると、renderCredentials
で作成したcredentials
をCfnDBCluster
のmasterUsername
やmasterUserPassword
に指定していますね。
export class DatabaseCluster extends DatabaseClusterNew { /** * Import an existing DatabaseCluster from properties */ public static fromDatabaseClusterAttributes(scope: Construct, id: string, attrs: DatabaseClusterAttributes): IDatabaseCluster { return new ImportedDatabaseCluster(scope, id, attrs); } public readonly clusterIdentifier: string; public readonly clusterResourceIdentifier: string; public readonly clusterEndpoint: Endpoint; public readonly clusterReadEndpoint: Endpoint; public readonly connections: ec2.Connections; public readonly instanceIdentifiers: string[]; public readonly instanceEndpoints: Endpoint[]; /** * The secret attached to this cluster */ public readonly secret?: secretsmanager.ISecret; constructor(scope: Construct, id: string, props: DatabaseClusterProps) { super(scope, id, props); const credentials = renderCredentials(this, props.engine, props.credentials); const secret = credentials.secret; const cluster = new CfnDBCluster(this, 'Resource', { ...this.newCfnProps, // Admin masterUsername: credentials.username, masterUserPassword: credentials.password?.unsafeUnwrap(), }); this.clusterIdentifier = cluster.ref; this.clusterResourceIdentifier = cluster.attrDbClusterResourceId; if (secret) { this.secret = secret.attach(this); } // create a number token that represents the port of the cluster const portAttribute = Token.asNumber(cluster.attrEndpointPort); this.clusterEndpoint = new Endpoint(cluster.attrEndpointAddress, portAttribute); this.clusterReadEndpoint = new Endpoint(cluster.attrReadEndpointAddress, portAttribute); this.connections = new ec2.Connections({ securityGroups: this.securityGroups, defaultPort: ec2.Port.tcp(this.clusterEndpoint.port), }); cluster.applyRemovalPolicy(props.removalPolicy ?? RemovalPolicy.SNAPSHOT); setLogRetention(this, props); if ((props.writer || props.readers) && (props.instances || props.instanceProps)) { throw new Error('Cannot provide writer or readers if instances or instanceProps are provided'); } if (!props.instanceProps && !props.writer) { throw new Error('writer must be provided'); } const createdInstances = props.writer ? this._createInstances(props) : legacyCreateInstances(this, props, this.subnetGroup); this.instanceIdentifiers = createdInstances.instanceIdentifiers; this.instanceEndpoints = createdInstances.instanceEndpoints; } }
masterUsername
はシークレットの認証情報から参照していることになるので、ここが怪しいですね。
Escape hatchで直接CfnDBCluster
のmasterUsername
を指定すれば解決しそうです。
util.tsからrenderCredentials
の動きも確認しておきます。
/** * Renders the credentials for an instance or cluster */ export function renderCredentials(scope: Construct, engine: IEngine, credentials?: Credentials): Credentials { let renderedCredentials = credentials ?? Credentials.fromUsername(engine.defaultUsername ?? 'admin'); // For backwards compatibilty if (!renderedCredentials.secret && !renderedCredentials.password) { renderedCredentials = Credentials.fromSecret( new DatabaseSecret(scope, 'Secret', { username: renderedCredentials.username, secretName: renderedCredentials.secretName, encryptionKey: renderedCredentials.encryptionKey, excludeCharacters: renderedCredentials.excludeCharacters, // if username must be referenced as a string we can safely replace the // secret when customization options are changed without risking a replacement replaceOnPasswordCriteriaChanges: credentials?.usernameAsString, replicaRegions: renderedCredentials.replicaRegions, }), // pass username if it must be referenced as a string credentials?.usernameAsString ? renderedCredentials.username : undefined, ); } return renderedCredentials; }
認証情報の指定がなければ、DatabaseSecret
で認証情報を生成するようですね。
解決の糸口が見えたので、Escape hatchで直接CfnDBCluster
のmasterUsername
を指定します。
const cfnDbCluster = dbCluster.node .defaultChild as cdk.aws_rds.CfnDBCluster; cfnDbCluster.manageMasterUserPassword = true; cfnDbCluster.masterUsername = "postgresAdmin"; cfnDbCluster.addPropertyDeletionOverride("MasterUserPassword"); dbCluster.node.tryRemoveChild("Secret");
編集後、diffを確認します。
$ npx cdk diff Stack AuroraStack Resources [-] AWS::SecretsManager::Secret Aurora/Default/Secret AuroraSecret7ACECA7F destroy [-] AWS::SecretsManager::SecretTargetAttachment Aurora/Default/Secret/Attachment AuroraSecretAttachmentEAAB0558 destroy [~] AWS::RDS::DBCluster Aurora/Default Aurora2CBAB212 may be replaced └─ [~] MasterUsername (may cause replacement) └─ @@ -1,12 +1,1 @@ [-] { [-] "Fn::Join": [ [-] "", [-] [ [-] "{{resolve:secretsmanager:", [-] { [-] "Ref": "AuroraSecret7ACECA7F" [-] }, [-] ":SecretString:username::}}" [-] ] [-] ] [-] } [+] "postgresAdmin"
MasterUsername
がシークレットからの参照ではなくなりましたね。
その後のcdk deploy
も問題なく通りました。
デプロイ後、スタックのリソース一覧からもSecrets
がなくなっているか確認します。
綺麗さっぱり消えていますね。
統合されているシークレットを手動でローテーションして、DBクラスターに接続できることまで確認できました。
Secrets Managerとの統合によって作成されたシークレットのローテーション間隔を指定する
最後にSecrets Managerとの統合によって作成されたシークレットのローテーション間隔を指定してみます。
これもEscape hatchでゴリ押します。おまけでOutputにシークレットのARNを出力できるか確認してみます。
const masterUserSecretArn = cfnDbCluster .getAtt("MasterUserSecret.SecretArn") .toString(); new cdk.aws_secretsmanager.CfnRotationSchedule(this, "RotationSchedule", { secretId: masterUserSecretArn, rotationRules: { scheduleExpression: "cron(0 18 ? 1/1 7#1 *)", duration: "1h", }, }); new cdk.CfnOutput(this, "OutputMasterUserSecretArn", { value: masterUserSecretArn, exportName: "OutputMasterUserSecretArn", });
編集後、diffを確認します。
$ npx cdk diff Stack AuroraStack Resources [+] AWS::SecretsManager::RotationSchedule Aurora/RotationSchedule AuroraRotationSchedule7E76A0E7 Outputs [+] Output Aurora/OutputMasterUserSecretArn AuroraOutputMasterUserSecretArn16B0094A: {"Value":{"Fn::GetAtt":["Aurora2CBAB212","MasterUserSecret.SecretArn"]},"Export":{"Name":"OutputMasterUserSecretArn"}}
ローテーション間隔の指定ができそうですね。Secrets Managerとの統合によって作成されたシークレットのARNもgetAtt("MasterUserSecret.SecretArn")
で取得できるようです。
デプロイ後、スタック内のリソースを確認するとAWS::SecretsManager::RotationScheduleのリソースがありますね。
シークレットを確認すると、ローテーション間隔がAWS CDKで指定した通りcron(0 18 ? 1/1 7#1 *)
になっていることが確認できました。
また、OutputにもシークレットのARNが正しく出力されていました。
Escape hatchの影響範囲を理解しよう
AWS CDKでAmazon Aurora DB クラスターとAWS Secrets Managerとの統合を設定してみました。
やはりEscape hatchは痒い所に手が届く非常にありがたい考え方ですね。使いこなす場合はAWS CDKのソースコードを読むなどして影響範囲を理解しながら設定すると良いでしょう。
使用したコードは以下リポジトリに保存しています。
この記事が誰かの助けになれば幸いです。
以上、AWS事業本部 コンサルティング部の のんピ(@non____97)でした!