Terraform利用時にAurora(RDS)のパスワードをセキュアに設定する
こんにちは。
私は最近業務でAWS Auroraを利用したWebシステムの構築にTerraformを利用しています。
Auroraを構築するにあたり、クラスターのマスターパスワードやユーザー名を指定する必要があります。
以下のような感じです
resource "aws_rds_cluster" "main" {
cluster_identifier = var.cluster_settings.cluster_name
engine = var.cluster_settings.engine
engine_version = var.cluster_settings.engine_version
availability_zones = var.cluster_settings.availability_zones
database_name = var.cluster_settings.database_name
# ユーザー名とパスワード
master_username = var.cluster_settings.master_username
master_password = var.cluster_settings.master_password
# 略
}
しかしながらここでパスワードを *.tf ファイル上で渡すとtfstateファイルにパスワードの情報が残ってしまいます。
参考記事
- https://techlife.cookpad.com/entry/2020/02/28/120000
- Terraform公式(stateファイルの目的)
Terraformで秘密情報を扱う方法を検索すると 「もばらぶエンジニアブログ」様の記事にいくつか方法を記載してくれています。
その中でも結局一番セキュアなのが、 方法4: 秘密情報を Terraform で扱わない
という方法であると思い、今回実践してみました。
Terraform でリソース作成するときにパスワード等の秘密情報を指定すると、それが環境変数なり Secrets Manager なりの外部から取得したものであっても、state ファイルに書き込まれることは避けられません。remote state を使って安全に管理しておけば基本的には問題無いと思いますが、セキュリティ要件によっては方法1〜3だとダメな場合もあるかもしれません。
方法4は、基本的には以下の内容です。
秘密情報は Secrets Manager などに入れておく
- Terraform でのリソースの作成時に秘密情報は適当に指定しておく
- リソース作成後に、他の方法で秘密情報を変更する
少し手間がかかりますが、Terraform の null_resource や local-exec を使う事により、リソース作成完了後に別のスクリプトを起動して Secrets Manager から値を取得してセットする事が出来ます。
実装内容
以下で示すコードの全文は↑リポジトリの該当ディレクトリ以下にあります。
今回やったことは
事前準備
- IAMユーザーに十分な権限を持たせておく
- ネットワーク,EC2,SSM,Aurora作成、Auroraのパスワード変更など
- 事前にSSMパラメーターストアにRDSのマスターパスワードに利用する情報を格納しておく
- ↑はTerraformで作らず手動で行う
- なお、今回はパラメーターストアを利用しましたが、パスワードの定期変更の要件がある場合はシークレットマネージャーのの方が良いと思います
- 参考: https://qiita.com/tomoya_oka/items/a3dd44879eea0d1e3ef5
Terraformで構築したもの
- Auroraへ接続確認するための EC2インスタンスおよびネットワークリソースなど
- 今回EC2インスタンスはプライベートサブネットに配置。SSMセッションマネージャーを利用してセキュアに接続する
- 参考: https://dev.classmethod.jp/articles/choosing-the-right-shell-access-solution-to-aws-ec2/
- AuroraのマルチAZ構成のためのネットワーク・Auroraインスタンスの作成
管理用EC2の作成
ネットワークリソースの作成やセキュリティグループの作成などの部分は省略いたしますが、EC2インスタンスを作成します。
resource "aws_instance" "bastion" {
ami = data.aws_ssm_parameter.amzn2_ami.value
instance_type = "t2.micro"
iam_instance_profile = aws_iam_instance_profile.bastion-profile.name
subnet_id = var.bastion_subnet
vpc_security_group_ids = [aws_security_group.allow_https_outbound.id]
user_data = file("${path.module}/user_data.sh")
tags = var.tags
}
その際 amazon-ssm-agentの最新版を取得できるように user_dataを以下の内容にしています。
#!/bin/bash
cd /tmp
sudo yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm
sudo systemctl start amazon-ssm-agent
詳細はこちらのディレクトリ以下を参照ください。
なお、今回はEC2インスタンスはプライベートサブネットに配置しており、NATゲートウェイも活用しておりませんん。
利用するのがAmazonLinux2であるため、S3へのVPCエンドポイントを作成して、ssm-agentを最新にできるようにしています。
参考: https://aws.amazon.com/jp/premiumsupport/knowledge-center/ec2-al1-al2-update-yum-without-internet/
その他 SSMを利用するにはいくつか VPCエンドポイントを作成しておく必要がありますので、合わせて作成しております。
参考
- https://aws.amazon.com/jp/premiumsupport/knowledge-center/systems-manager-ec2-instance-not-appear/
- https://dev.classmethod.jp/articles/troubleshooting-ssm-session-manager-configuration/
余談ですがクラスメソッドさんの記事は本当に神的にわかりやすいですね。
Auroraクラスターの作成
以下のように AuroraクラスターおよびDBインスタンスを作成します。
他にもパラメーターグループなども作成しております(https://github.com/bun913/aws_network_practice/tree/main/aurora/modules/db)
# rds cluster group
resource "aws_rds_cluster" "main" {
cluster_identifier = var.cluster_settings.cluster_name
engine = var.cluster_settings.engine
engine_version = var.cluster_settings.engine_version
availability_zones = var.cluster_settings.availability_zones
database_name = var.cluster_settings.database_name
master_username = var.cluster_settings.master_username
master_password = var.cluster_settings.master_password
backup_retention_period = var.cluster_settings.backup_retention_period
db_subnet_group_name = aws_db_subnet_group.main.id
vpc_security_group_ids = [aws_security_group.db.id]
db_cluster_parameter_group_name = aws_rds_cluster_parameter_group.main.id
# NOTE: お試しようなので削除保護をfalseにしている。本番では必ず設定する
deletion_protection = false
skip_final_snapshot = true
}
# clusterのパスワードを変更する
resource "null_resource" "cluster" {
# DBが作成されたことをトリガーにする
triggers = {
cluster_instance_ids = aws_rds_cluster.main.id
}
provisioner "local-exec" {
# aws cliでRDSのパスワードを変更する
command = "aws ssm get-parameter --name /${var.project}/db_password --query 'Parameter.Value' --with-decryption | xargs -IPASS aws rds modify-db-cluster --db-cluster-identifier ${aws_rds_cluster.main.id} --master-user-password PASS" --apply-immediately
on_failure = fail
}
}
# rds_instance
resource "aws_rds_cluster_instance" "example" {
count = 2
cluster_identifier = aws_rds_cluster.main.id
identifier = "${aws_rds_cluster.main.id}-${count.index}"
engine = aws_rds_cluster.main.engine
engine_version = aws_rds_cluster.main.engine_version
instance_class = var.instance_class
db_subnet_group_name = aws_db_subnet_group.main.name
db_parameter_group_name = aws_db_parameter_group.main.name
publicly_accessible = false
}
以下の箇所がポイントです。
# clusterのパスワードを変更する
resource "null_resource" "cluster" {
# DBが作成されたことをトリガーにする
triggers = {
cluster_instance_ids = aws_rds_cluster.main.id
}
provisioner "local-exec" {
# aws cliでRDSのパスワードを変更する
command = "aws ssm get-parameter --name /${var.project}/db_password --query 'Parameter.Value' --with-decryption | xargs -IPASS aws rds modify-db-cluster --db-cluster-identifier ${aws_rds_cluster.main.id} --master-user-password PASS"
on_failure = fail
}
}
null_resourceを使ってDB作成をトリガーにして、RDSのパスワードを local-exec
で変更しています。
こうすることによって あくまでパスワードの取得・更新を terraform外で行っているため コード上にも tfstate上にも本当のパスワードが残りません。
(残るのは適当に設定したダミーのパスワードのみです)
接続確認
ここまでした後に、AWS マネジメントコンソールからセッションマネージャーを立ち上げる て、EC2インスタンスから本物のパスワードで接続すると・・・
無事接続確認できました!(赤枠のところには Aurora Writerインスタンスのエンドポイントを指定)
まとめ
Terraformで直接秘密情報を扱わないのが、一番無難なのかなとやりながら思いました。
私もさぐりながらやってみたので、是非色々よい方法を教えてください!
Discussion