【AWS】CDKでRDSのパスワードを自動生成＆SecretManagerで管理する方法

# 概要
RDSのパスワードはSecretManagerから取得して登録することは可能ですが、
そもそもそのSecretManagerに登録するパスワードをCDKで作成して、
CDKでRDSを作成する際にそのパスワードを取得するようにしたい！と思いました。
実現できたので、その方法を紹介します。

## 前提
RDS作成（L1コンストラクト）の際に`master_username`や`master_user_password`を指定することは可能ですが、これだとハードコーディングで指定する必要があります。

```py
    rds_instance = rds.CfnDBInstance(
        self, "sample-RDS",
        db_instance_identifier="sample-RDS",
        db_instance_class="db.t3.micro",
        db_name="sampledb",
        engine="mysql",
        engine_version="8.0.39",
        allocated_storage="20",
        max_allocated_storage=100,
        db_subnet_group_name=rds_subnet_group.ref,
        vpc_security_groups=[rds_security_group.ref],
        master_username="admin",
        master_user_password="password",
        publicly_accessible=False,
        tags=[{
            "key": "Name",
            "value": "sample-RDS"
        }]
    )
```

直接コードで指定する方法だとセキュリティや手間の問題があるので、AWS側に発行＆管理してもらおう！というのが今回の趣旨です。

## サンプルコード

下記のコードで、`secretsmanager`で40文字のパスワードを自動生成できます。

```py
        rds_secret = secretsmanager.Secret(
            self, "sample-RDSSecret",
            secret_name="sample-db-variables",
            generate_secret_string=secretsmanager.SecretStringGenerator(
                secret_string_template='{"username":"admin"}',
                generate_string_key="password",
                exclude_characters='@/\\\'" ',
                password_length=40
            )
        )
```

`generate_secret_string`を使用して、ユーザー名とパスワードを自動生成することができます。
`secret_string_template`ではJSON形式でユーザー名を指定し、generate_string_key でパスワードを生成します。

参考：

https://docs.aws.amazon.com/cdk/api/v2/python/aws_cdk.aws_secretsmanager/SecretStringGenerator.html

ここで作成したパスワードをRDSに紐づける場合は、以下のように`secret_value_from_json`を使って取得することができます。

```py
        db_name = "sampledb"

        rds_instance = rds.CfnDBInstance(
            self, "sample-RDS",
            db_instance_identifier="sample-mysql8",
            db_instance_class="db.t3.micro",
            db_name=db_name,
            engine="mysql",
            engine_version="8.0.41",
            allocated_storage="20",
            max_allocated_storage=100,
            db_subnet_group_name=rds_subnet_group.ref,
            vpc_security_groups=[rds_security_group.security_group_id],
            master_username=rds_secret.secret_value_from_json("username").unsafe_unwrap(),
            master_user_password=rds_secret.secret_value_from_json("password").unsafe_unwrap(),
            publicly_accessible=False,
        )
```

> secret_value_from_json(json_field)
Interpret the secret as a JSON object and return a field’s value from it as a SecretValue.
https://docs.aws.amazon.com/cdk/api/v2/python/aws_cdk.aws_secretsmanager/Secret.html

`unsafe_unwrap()`を使用して、シークレットの値を文字列として取得しています。

## Lambdaの環境変数にも設定できる

これは、たとえばLambdaの環境変数に当てたい場合も同様に可能です。
`rds_secret.secret_value_from_json("password").unsafe_unwrap()`を使えば、上記で作成したSecretManagerのパスワードを取得することができます。


```py
        lambda_sample_function = _lambda.Function(
            self, "Sample-Lambda",
            function_name="sample-function",
            runtime=_lambda.Runtime.PYTHON_3_12,
            handler="sample.lambda_handler",
            code=_lambda.Code.from_asset("lambda/sample"),
            role=lambda_role,
            vpc=private_subnets_vpc,
            security_groups=[lambda_security_group],
            environment={
                "DB_HOST": rds_instance.attr_endpoint_address,
                "DB_PORT": rds_instance.attr_endpoint_port,
                "DB_NAME": db_name,
                "DB_USER": rds_secret.secret_value_from_json("username").unsafe_unwrap(),
                "DB_PASSWORD": rds_secret.secret_value_from_json("password").unsafe_unwrap(),
            }
        )
```

このように記載すればパスワードをハードコーディングせずともCDKを実行できて良いですね。

## 参考記事

https://docs.aws.amazon.com/cdk/api/v2/python/aws_cdk.aws_secretsmanager/SecretStringGenerator.html


https://cloud5.jp/k-aws-cdk-rds/