[AWS CDK] AWS Secrets ManagerでAmazon Auroraのパスワードをローテーションしてみた
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)でした!