TerraformでRDSのパスワードローテーションのコードを作成する

# はじめに
検索してもあまり良い記事を見つけることができなかったため、自分で色々試して成功したことを記載します。

## VPC、サブネット、RDS
VPC、サブネット、RDSは検索すればたくさん出てくるので、既に作成済みを前提として記載します。
VPCのCIDRは**"10.0.0.0/16"**としております。
今回RDSは**Aurora PostgreSQL**を使用し、**プライベートサブネット**に配置します。
RDSのセキュリティグループは**PostgreSQLのポート（5432）に同VPC内(10.0.0.0/16)からのアクセスのみ許可**しております。

## パスワードローテーションの仕組み
AWS Secrets Managerに認証情報を保持し、Lambdaを使って定期的にパスワードを更新します。
https://docs.aws.amazon.com/ja_jp/secretsmanager/latest/userguide/enable-rotation-rds.html

コンソールだと上記ドキュメント記載通りにすれば簡単に設定可能なのですが、これをTerraformでコーディングするのはどうすれば良いかを試行錯誤しました。

## ローテーションのタイプ
ローテーションのタイプは2つある。

1. **シングルユーザー**  
パスワード変更可能なユーザーの認証情報をローテーションする場合。
1. **マルチユーザー**  
パスワード変更ができないユーザーの認証情報をローテーションする場合。別途、パスワード変更可能なユーザーの認証情報を保持しているシークレットが必要になる。

今回は**シングルユーザー**を前提とします

## ローテーションのプロセス
ローテーションのプロセスは簡単に書くと以下のようです。

1. Secrets Managerからローテーションを行うLambda関数を呼び出す
1. Lambda関数がSecrets Managerにある認証情報をコピーして新しい認証情報を生成し、RDSのパスワード更新を行う
1. Lambda関数がSecrets Managerの認証情報を新しい認証情報に差し替え

詳しくは以下に記載されています。
https://docs.aws.amazon.com/ja_jp/secretsmanager/latest/userguide/rotating-secrets-lambda-function-overview.html


# コーディング方法
それでは本題のコーディング方法を記載します。

:::note warn
VPCやサブネットは作成済みを前提としているため、コードに出てくる`aws_vpc.main.id`や`aws_subnet.rds_subnet.*.id`は既に作成済み前提のVPCやサブネットのIDとなります。
実際にコーディングする場合はCIDRの値（"10.0.0.0/16"）も含めてご自身の環境に合った値に変更してお試しください。
こちら記載するために実際には変数になっている部分を改変などしているため、変なところがあればご指摘していただけますと嬉しいです。
:::


## セキュリティグループ
まず、セキュリティグループを作成します。
Secrets Manager用エンドポイントのセキュリティグループとローテーション用Lambdaのセキュリティグループが必要になります。
LambdaはSecrets Manager間の相互の通信が必要なのと、RDSへのアクセスが必要になりますので、以下のように設定します。

```tf
# エンドポイント用セキュリティグループ
resource "aws_security_group" "endpoint_security_group" {
  name   = "endpoint_security_group"
  vpc_id = aws_vpc.main.id

  egress {
    cidr_blocks = ["0.0.0.0/0"]
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    self        = false
  }
  ingress {
    cidr_blocks = ["0.0.0.0/0"]
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    self        = false
  }
  tags = {
    Name = "endpoint_security_group"
  }
}

# ローテーションLambda用セキュリティグループ
resource "aws_security_group" "lambda_security_group" {
  name   = "rds-rotate-lambda"
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "rds-rotate-lambda"
  }
}

# Secrets Manager用アウトバウンド
resource "aws_security_group_rule" "lambda_security_group_rule1" {
  type              = "egress"
  from_port         = 443
  to_port           = 443
  protocol          = "tcp"
  cidr_blocks       = ["10.0.0.0/16"]
  security_group_id = aws_security_group.lambda_security_group.id
}
# RDS用アウトバウンド
resource "aws_security_group_rule" "lambda_security_group_rule2" {
  type              = "egress"
  from_port         = 5432
  to_port           = 5432
  protocol          = "tcp"
  cidr_blocks       = ["10.0.0.0/16"]
  security_group_id = aws_security_group.lambda_security_group.id
}
# Secrets Manager用インバウンド
resource "aws_security_group_rule" "lambda_security_group_rule3" {
  type              = "ingress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["10.0.0.0/16"]
  security_group_id = aws_security_group.lambda_security_group.id
}
```

LambdaはSecrets Manager用のインバウンドとアウトバウンドとRDS用のアウトバウンドルールを設定しています。

## エンドポイント
Secrets ManagerはVPC外にあるため、エンドポイントが必要になります。

```tf
data "aws_region" "current" {}
resource "aws_vpc_endpoint" "secretsmanager" {
  service_name       = "com.amazonaws.${data.aws_region.current.name}.secretsmanager"
  security_group_ids = [aws_security_group.endpoint_security_group.id]
  policy = jsonencode(
    {
      Statement = [
        {
          Action    = "*"
          Effect    = "Allow"
          Principal = "*"
          Resource  = "*"
        },
      ]
    }
  )
  private_dns_enabled = true
  route_table_ids     = []
  subnet_ids          = aws_subnet.rds_subnet.*.id
  vpc_endpoint_type   = "Interface"
  vpc_id              = aws_vpc.main.id

  tags = {
    Name        = "secrets-manager"
  }
}
```

## Lambda
LambdaはSAP（AWS Serverless Application Repository）で公開されているSAM（AWS Serverless Application Model）を使用します。
https://serverlessrepo.aws.amazon.com/applications/arn:aws:serverlessrepo:us-east-1:297356227824:applications~SecretsManagerRDSPostgreSQLRotationSingleUser

```tf
data "aws_partition" "current" {}
resource "aws_serverlessapplicationrepository_cloudformation_stack" "postgres_rotator" {
  name           = "postgres-rotator"
  application_id = "arn:aws:serverlessrepo:us-east-1:297356227824:applications/SecretsManagerRDSPostgreSQLRotationSingleUser"
  capabilities = [
    "CAPABILITY_IAM",
    "CAPABILITY_RESOURCE_POLICY",
  ]
  parameters = {
    functionName        = "postgres-rotator"
    endpoint            = "https://secretsmanager.${data.aws_region.current.name}.${data.aws_partition.current.dns_suffix}"
    vpcSubnetIds        = join(",", aws_subnet.rds_subnet.*.id)
    vpcSecurityGroupIds = "${aws_security_group.lambda_security_group.id}"
  }
}
```

`parameters`でサブネットとセキュリティグループを指定しています。
RDSはプライベートサブネットにあるため、Lambdaも同プライベートサブネットに配置します。

PostgreSQL以外のSAMも公開されています。
[MySQL シングルユーザー](https://serverlessrepo.aws.amazon.com/applications/us-east-1/297356227824/SecretsManagerRDSMySQLRotationSingleUser)
[Oracle シングルユーザー](https://serverlessrepo.aws.amazon.com/applications/us-east-1/297356227824/SecretsManagerRDSOracleRotationSingleUser)


検索欄で"rotation"と打てば他にも出てきます。

## Secrets Manager

最後にSecrets Managerに認証情報のシークレットを作成します。

```tf
resource "aws_secretsmanager_secret" "postgres_rotate_secretsmanager_secret" {
  name = "postgres-rotate-secretsmanager-secret"
}

resource "aws_secretsmanager_secret_rotation" "postgres_rotate_secretsmanager_secret_rotation" {
  secret_id           = aws_secretsmanager_secret.postgres_rotate_secretsmanager_secret.id
  rotation_lambda_arn = aws_serverlessapplicationrepository_cloudformation_stack.postgres_rotator.outputs.RotationLambdaARN
  rotation_rules {
    automatically_after_days = 30
  }
}

resource "aws_secretsmanager_secret_version" "postgres_rotate_secretsmanager_secret_version" {
  secret_id     = aws_secretsmanager_secret.postgres_rotate_secretsmanager_secret.id
  secret_string = <<-EOF
    {
      "engine": "postgres",
      "host": "{RDSのエンドポイント}",
      "username": "{DBのユーザ名}",
      "password": "{DBのパスワード}",
      "dbname": "{DBのデータベース名}",
      "port": "5432"
    }
  EOF
}
```

`aws_secretsmanager_secret_rotation`で作成したLambdaのARNを指定しています。
`automatically_after_days`は更新間隔（日）です。
`secret_string`は使用するデータベースによって内容が異なります。
その内容は[こちら](https://docs.aws.amazon.com/ja_jp/secretsmanager/latest/userguide/reference_available-rotation-templates.html)に記載されております。

# 完了
これでapplyし、Secrets Managerからローテーションを試してみて成功すればOKです。