この記事は公開されてから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
というユーザーはシステム側で予約されているので使用できません。
./src/database-stack.ts
// 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クラスター作成時に上述のシークレットを指定してあげます。
./src/database-stack.ts
// 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,
});
パスワードのローテーションの設定は以下のように行います。今回はシングルユーザーローテーションとしました。
./src/database-stack.ts
// 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)でした!