🐕

Terraform利用時にAurora(RDS)のパスワードをセキュアに設定する

2022/01/12に公開

こんにちは。

私は最近業務で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ファイルにパスワードの情報が残ってしまいます。

参考記事

Terraformで秘密情報を扱う方法を検索すると 「もばらぶエンジニアブログ」様の記事にいくつか方法を記載してくれています。

https://engineering.mobalab.net/2021/03/25/handling-secrets-with-terraform/

その中でも結局一番セキュアなのが、 方法4: 秘密情報を Terraform で扱わない という方法であると思い、今回実践してみました。

Terraform でリソース作成するときにパスワード等の秘密情報を指定すると、それが環境変数なり Secrets Manager なりの外部から取得したものであっても、state ファイルに書き込まれることは避けられません。remote state を使って安全に管理しておけば基本的には問題無いと思いますが、セキュリティ要件によっては方法1〜3だとダメな場合もあるかもしれません。

方法4は、基本的には以下の内容です。

秘密情報は Secrets Manager などに入れておく

  • Terraform でのリソースの作成時に秘密情報は適当に指定しておく
  • リソース作成後に、他の方法で秘密情報を変更する
    少し手間がかかりますが、Terraform の null_resource や local-exec を使う事により、リソース作成完了後に別のスクリプトを起動して Secrets Manager から値を取得してセットする事が出来ます。

実装内容

https://github.com/bun913/aws_network_practice/tree/main/aurora

以下で示すコードの全文は↑リポジトリの該当ディレクトリ以下にあります。

今回やったことは

事前準備

  • IAMユーザーに十分な権限を持たせておく
    • ネットワーク,EC2,SSM,Aurora作成、Auroraのパスワード変更など
  • 事前にSSMパラメーターストアにRDSのマスターパスワードに利用する情報を格納しておく
    • ↑はTerraformで作らず手動で行う
    • なお、今回はパラメーターストアを利用しましたが、パスワードの定期変更の要件がある場合はシークレットマネージャーのの方が良いと思います
    • 参考: https://qiita.com/tomoya_oka/items/a3dd44879eea0d1e3ef5

Terraformで構築したもの

  • Auroraへ接続確認するための 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

詳細はこちらのディレクトリ以下を参照ください。

https://github.com/bun913/aws_network_practice/tree/main/aurora/modules/bastion

なお、今回はEC2インスタンスはプライベートサブネットに配置しており、NATゲートウェイも活用しておりませんん。
利用するのがAmazonLinux2であるため、S3へのVPCエンドポイントを作成して、ssm-agentを最新にできるようにしています。

参考: https://aws.amazon.com/jp/premiumsupport/knowledge-center/ec2-al1-al2-update-yum-without-internet/

その他 SSMを利用するにはいくつか VPCエンドポイントを作成しておく必要がありますので、合わせて作成しております。

参考

余談ですがクラスメソッドさんの記事は本当に神的にわかりやすいですね。

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