![[AWS CDK] AWS Secrets ManagerでAmazon Auroraのパスワードをローテーションしてみた](https://devio2023-media.developers.io/wp-content/uploads/2019/05/aws-secrets-manager.png)
[AWS CDK] AWS Secrets ManagerでAmazon Auroraのパスワードをローテーションしてみた
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
AWS Secrets Managerのパスワードローテーション設定もAWS CDKでやりたいな
こんにちは、のんピ(@non____97)です。
AWS Secrets Managerのパスワードローテーション設定もAWS CDKでやりたいなと思ったことはありますか? 私はあります。
「AWS CloudFormation × Amazon RDS」の組み合わせは以下記事で紹介されているので、「AWS CloudFormation × Amazon Aurora」もできるだろうと思いました。
なんでも本気を出せばAWS CDKで出来ると思っているので、チャレンジします。
こちらのコードのリポジトリは以下になります。
作成されるリソースの構成
作成されるリソースの構成は以下の通りです。

AWS CDKのコードの確認
AWS CDKのコードのディレクトリ構成は以下の通りです。「既にVPCは作成されている状態でDBを作る」というパターンが多いと思うので、VPCとDB周りとでスタックを分割しました。
> tree . ├── .gitignore ├── .npmignore ├── README.md ├── bin │ └── database.ts ├── cdk.context.json ├── cdk.json ├── jest.config.js ├── lib │ ├── database-stack.ts │ └── vpc-stack.ts ├── package-lock.json ├── package.json ├── src │ └── ec2 │ └── user_data_amazon_linux2.sh ├── test │ └── database.test.ts └── tsconfig.json 5 directories, 14 files
DBのマスターユーザー名とパスワードの設定は以下のように行います。パスワードに含めない文字列はEXCLUDE_CHARACTERSとして定義しています。なお、PostgreSQLの場合adminというユーザーはシステム側で予約されているので使用できません。
// DB Admin User Secret
const dbAdminSecret = new secretsmanager.Secret(this, "DbAdminSecret", {
secretName: "prd-db-cluster/AdminLoginInfo",
generateSecretString: {
excludeCharacters: EXCLUDE_CHARACTERS, // EXCLUDE_CHARACTERS = :@/\" '
generateStringKey: "password",
passwordLength: 32,
requireEachIncludedType: true,
secretStringTemplate: '{"username": "postgresAdmin"}',
},
});
DBクラスター作成時に上述のシークレットを指定してあげます。
// DB Cluster
const dbCluster = new rds.DatabaseCluster(this, "DbCluster", {
engine: rds.DatabaseClusterEngine.auroraPostgres({
version: rds.AuroraPostgresEngineVersion.VER_13_4,
}),
instanceProps: {
vpc: props.vpc,
allowMajorVersionUpgrade: false,
autoMinorVersionUpgrade: true,
deleteAutomatedBackups: false,
enablePerformanceInsights: true,
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.BURSTABLE3,
ec2.InstanceSize.MEDIUM
),
parameterGroup: dbParameterGroup,
performanceInsightRetention: rds.PerformanceInsightRetention.DEFAULT,
publiclyAccessible: false,
securityGroups: [dbSg],
},
backup: {
retention: Duration.days(7),
preferredWindow: "16:00-16:30",
},
cloudwatchLogsExports: ["postgresql"],
cloudwatchLogsRetention: logs.RetentionDays.ONE_YEAR,
clusterIdentifier: "prd-db-cluster",
copyTagsToSnapshot: true,
credentials: rds.Credentials.fromSecret(dbAdminSecret),
defaultDatabaseName: "testDB",
deletionProtection: true,
iamAuthentication: false,
instanceIdentifierBase: "prd-db-instance",
instances: 1,
monitoringInterval: Duration.minutes(1),
parameterGroup: dbClusterParameterGroup,
preferredMaintenanceWindow: "Sat:17:00-Sat:17:30",
storageEncrypted: true,
subnetGroup,
});
パスワードのローテーションの設定は以下のように行います。今回はシングルユーザーローテーションとしました。
// Rotate DB Admin user secret
new secretsmanager.SecretRotation(this, "DbAdminSecretRotation", {
application:
secretsmanager.SecretRotationApplication.POSTGRES_ROTATION_SINGLE_USER,
secret: dbAdminSecret,
target: dbCluster,
vpc: props.vpc,
automaticallyAfter: Duration.days(3),
excludeCharacters: EXCLUDE_CHARACTERS,
securityGroup: rotateSecretsLambdaFunctionSg,
vpcSubnets: props.vpc.selectSubnets({
subnetType: ec2.SubnetType.PRIVATE_WITH_NAT,
}),
});
パスワードをローテーションさせるLambda関数の作成も自動でしてくれます。
ポイントはVPCとサブネットの設定です。
今回作成するDBクラスターではパブリックアクセスを有効化していないため、Lambda関数が動作するVPCとサブネットを指定する必要があります。正常にパスワードをローテーションさせるためには、Lambda関数からAWS Secrets Managerのエンドポイントにアクセスできる必要があります。そのためには以下のどちらかの対応が必要です。
- NAT Gatewayを介してインターネットにルーティングがある
- AWS Secrets ManagerのVPCエンドポイントがある
正しく設定がされていなければ、パスワードのローテーションができません。手動でローテーションさせようとした時も、An error occurred (InvalidRequestException) when calling the RotateSecret operation: A previous rotation isn’t complete. That rotation will be reattempted.と表示され、失敗します。
ちなみに、シングルユーザー版のPostgreSQLのローテーション用Lambda関数のソースコードは以下の通りです。
動作確認
シークレットの確認
npx cdk deploy --allでAWS CDKで定義した各種リソースをデプロイします。
デプロイ後、AWS Secrets Managerから作成されたシークレットを確認します。

コードで定義した通り3日でローテーションするように設定されています。
シークレットの値を取得する場合は、シークレットの値を取得するをクリックします。

プレーンテキストタブをクリックすれば以下のようにJSON形式で確認することもできます。
{
"dbClusterIdentifier": "prd-db-cluster",
"password": "pi81PTAPj&&aE=YIjRbZ+k5a=Ji_JyAZ",
"dbname": "testDB",
"engine": "postgres",
"port": 5432,
"host": "prd-db-cluster.cluster-cicjym7lykmq.us-east-1.rds.amazonaws.com",
"username": "postgresAdmin"
}
DBクライアントからの接続確認
次に、こちらのシークレットを使ってDBクライアントから接続できるか確認してみます。
まず、AWS CLIでシークレットを取得します。
$ aws secretsmanager get-secret-value \
--secret-id prd-db-cluster/AdminLoginInfo \
--region us-east-1
{
"Name": "prd-db-cluster/AdminLoginInfo",
"VersionId": "bf764736-f06c-49ca-9932-b1474812f28a",
"SecretString": "{\"dbClusterIdentifier\":\"prd-db-cluster\",\"password\":\"pi81PTAPj&&aE=YIjRbZ+k5a=Ji_JyAZ\",\"dbname\":\"testDB\",\"engine\":\"postgres\",\"port\":5432,\"host\":\"prd-db-cluster.cluster-cicjym7lykmq.us-east-1.rds.amazonaws.com\",\"username\":\"postgresAdmin\"}",
"VersionStages": [
"AWSCURRENT"
],
"CreatedDate": 1646951946.291,
"ARN": "arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:prd-db-cluster/AdminLoginInfo-zDAmV7"
}
マネージメントコンソールと同じパスワードを確認できました。
それでは、DBにアクセスします。シークレットの情報を環境変数に入れて、psqlコマンドを叩くと、正常にDBにアクセスできました。
$ get_secrets_value=$(aws secretsmanager get-secret-value \
--secret-id prd-db-cluster/AdminLoginInfo \
--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)
$ psql
psql (13.6, server 13.4)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.
testDB=>
testDB=>
環境変数の参考:
試しにタイムゾーンを確認したところ、DBクラスターのパラメーターグループで設定した通り、Asia/Tokyoになっていました。
testDB=> show timezone; TimeZone ------------ Asia/Tokyo (1 row)
パスワードのローテーション
それでは、パスワードのローテーションを試します。
パスワードのローテーションをする際は、シークレットのすぐにシークレットをローテーションさせるをクリックします。

注意書きの内容を確認してローテーションさせるをクリックします。

正常にローテーションのスケジューリングができれば、ローテーションのスケジュールが設定されたシークレットと表示されます。

パスワードをローテーションさせるLambda関数のCloudWatch Logsを確認します。
パスワードのローテーションのログが正常に出力されていました。パスワードの作成から検証、入れ替えと一瞬でローテーションの処理が完了されています。
START RequestId: de40ce14-8fa0-4799-b95c-713bcdaeca7b Version: $LATEST [INFO] 2022-03-11T00:06:14.209Z de40ce14-8fa0-4799-b95c-713bcdaeca7b Found credentials in environment variables. [INFO] 2022-03-11T00:06:15.466Z de40ce14-8fa0-4799-b95c-713bcdaeca7b createSecret: Successfully put secret for ARN arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:prd-db-cluster/AdminLoginInfo-zDAmV7 and version 59ab42e5-77bb-483b-9422-9b85b8c9159c. END RequestId: de40ce14-8fa0-4799-b95c-713bcdaeca7b REPORT RequestId: de40ce14-8fa0-4799-b95c-713bcdaeca7b Duration: 1523.71 ms Billed Duration: 1524 ms Memory Size: 128 MB Max Memory Used: 72 MB Init Duration: 390.79 ms START RequestId: 11da92c7-a9fe-4390-92bd-53a647bdd7d2 Version: $LATEST [INFO] 2022-03-11T00:06:16.081Z 11da92c7-a9fe-4390-92bd-53a647bdd7d2 setSecret: Successfully set password for user postgresAdmin in PostgreSQL DB for secret arn arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:prd-db-cluster/AdminLoginInfo-zDAmV7. END RequestId: 11da92c7-a9fe-4390-92bd-53a647bdd7d2 REPORT RequestId: 11da92c7-a9fe-4390-92bd-53a647bdd7d2 Duration: 598.02 ms Billed Duration: 599 ms Memory Size: 128 MB Max Memory Used: 74 MB START RequestId: cd6c405f-8895-4d17-bc34-d4560d8cc11e Version: $LATEST [INFO] 2022-03-11T00:06:16.610Z cd6c405f-8895-4d17-bc34-d4560d8cc11e testSecret: Successfully signed into PostgreSQL DB with AWSPENDING secret in arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:prd-db-cluster/AdminLoginInfo-zDAmV7. END RequestId: cd6c405f-8895-4d17-bc34-d4560d8cc11e REPORT RequestId: cd6c405f-8895-4d17-bc34-d4560d8cc11e Duration: 504.39 ms Billed Duration: 505 ms Memory Size: 128 MB Max Memory Used: 74 MB START RequestId: 048458a8-3fbe-454b-8753-3e4e44615039 Version: $LATEST [INFO] 2022-03-11T00:06:16.886Z 048458a8-3fbe-454b-8753-3e4e44615039 finishSecret: Successfully set AWSCURRENT stage to version 59ab42e5-77bb-483b-9422-9b85b8c9159c for secret arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:prd-db-cluster/AdminLoginInfo-zDAmV7. END RequestId: 048458a8-3fbe-454b-8753-3e4e44615039 REPORT RequestId: 048458a8-3fbe-454b-8753-3e4e44615039 Duration: 242.43 ms Billed Duration: 243 ms Memory Size: 128 MB Max Memory Used: 74 MB
ローテーションの仕組みは以下ブログやAWS公式ドキュメントをご覧ください。
マネージメントコンソールから確認したところ、確かにパスワードが変更されていました。

ちなみに、パスワードローテーションの裏でDBクライアントからDBへのセッションは張りっぱなしでしたが、途中で切れることはありませんでした。
それでは、DBクライアントから再度接続してみます。
まずは、古いパスワードで接続を試してみます。psqlを実行したところ、認証に失敗しました。DB側で確かに新しいパスワードが反映されていそうです。
$ psql psql: error: FATAL: password authentication failed for user "postgresAdmin" FATAL: password authentication failed for user "postgresAdmin"
次にローテーション後の新しいパスワードで接続してみます。こちらは正しく接続することができました。
$ get_secrets_value=$(aws secretsmanager get-secret-value \
--secret-id prd-db-cluster/AdminLoginInfo \
--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)
$ psql
psql (13.6, server 13.4)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.
testDB=>
testDB=>
AWS CDKで一撃
AWS CDKを使ってAWS Secrets ManagerでAmazon Auroraのパスワードをローテーションさせる設定をしてみました。
AWS CDKで一撃で設定できるので非常に楽ですね。
ただし、現時点の最新のAWS CDK v2.15.0ではローテーションのスケジュールをCron式で指定することはできないので注意が必要です。
また、AWS CDKで作成したDBを実際に運用をされる際は、誤ってDBが削除されないように、スタックの削除保護の有効化や、DeletionPolicyをやRetainやSnapshotとすることをオススメします。
この記事が誰かの助けになれば幸いです。
以上、AWS事業本部 コンサルティング部の のんピ(@non____97)でした!






