My Note

自己理解のためのブログ

Terraform ( v0.11.14 ) でSSMパラメータと連携してAurora ( MySQL ) を構築する

やったこと

yhidetoshi.hatenablog.com

の以前に書いたブログ ( Terraformのコードの改善 )

  • TerraformでRDSを構築するときにSSMパラメータに登録したパスワードを参照してインスタンスを構築する
  • VPC-IDをSSMパラメータから取得する
  • SecurityGroupIDをterraformの機能を使って取得する
  • サブネットグループのサブネットIDをterraformの機能を使って取得する
    • ※ Terraformのバージョンは 0.11.14 を利用した

■ 構築する構成図 (workspace=prod)

f:id:yhidetoshi:20190621225901p:plain

以前のブログでTerrfaromをRDSを作成したときは、DBのパスワードを平文でかつterraformのコード内に定義していた。 これは個人開発で環境を構築するのはいいが、チームやGitでコード管理する場合にはセキュリティ的によくない。 この課題を解決するために、今回はAWSのSSMパラメータにKMSで暗号化した状態でパスワードを管理し、Terraformでこの値を参照するように変更する。

TerraformのSSMパラメータについて

  • SSMパラメータの機能 ( 登録 / 参照 / 上書き )

www.terraform.io

vpc-id / subnet-id / db-passowd / securit-group-id を参照してサブネットIDを取得する

AWSのSSMパラメータの画面 ( vpc_id )

f:id:yhidetoshi:20190621230352p:plain

※ Stringで保存 ( 本グログには記載していないですが、VPC関連のリソースを作るときにSSMパラメータに自動登録するようにしています )

AWSのSSMパラメータの画面 ( db_password )

f:id:yhidetoshi:20190621230903p:plain

※ SecureStringで保存

■ subnet_idの画面

f:id:yhidetoshi:20190621230548p:plain

f:id:yhidetoshi:20190621230627p:plain

■ SecurityGroup_idの画面

f:id:yhidetoshi:20190621230627p:plain

■ ssmパラメータのkeyを指定して値を取得する処理 ( vpc_id )

data "aws_ssm_parameter" "vpc_id"vpc_id が keyとして指定する。

data "aws_ssm_parameter" "vpc_id" {
  name = "vpc_id"
}

■ RDSで使うsubnet-idを取得する処理 ( NameTagで検索して取得 )

  • vpc_id を指定してNameTagを指定して取得する。今回はRDSを構築するサブネット名を db-c / db-d としている。
    • "${data.aws_ssm_parameter.vpc_id.value}" で ssmパラメータの値を取得できる
data "aws_subnet_ids" "db-c" {
  vpc_id = "${data.aws_ssm_parameter.vpc_id.value}"

  tags {
    Name = "db-c"
  }
}

data "aws_subnet_ids" "db-d" {
  vpc_id = "${data.aws_ssm_parameter.vpc_id.value}"

  tags {
    Name = "db-d"
  }
}

■ ssmパラメータのkeyを指定して値を取得する処理 ( DBパスワード )

data "aws_ssm_parameter" "db_password" {
  name = "db_password"
}
  • DBのパスワード値をssmパラメータから取得は以下のようにした
    • master_password_enyenv = "${data.aws_ssm_parameter.db_password.value}"

■ RDSで使うsecurity-group-idを取得する処理 ( NameTagで検索して取得 )

data "aws_security_group" "db" {
  vpc_id = "${data.aws_ssm_parameter.vpc_id.value}"

  tags {
    Name = "db"
  }
}
  • SecurityGroup-IDをssmパラメータから取得は以下のようにした
    • vpc_security_group_ids_enyenv = ["${data.aws_security_group.db.id}"]

Terraformのコード全体

ディレクトリ構造(一部抜粋)

├── modules
│   ├── rds
│   │   └── aurora-mysql
│   │       └── main.tf ( resource側 )
├── rds.tf         ( module側 )
├── s3-backend.tf

■ Module側 ( rds.tf )

variable "availability_zone_prod" {
  default = ["ap-northeast-1c", "ap-northeast-1d"]
}

variable "availability_zone_stg" {
  default = ["ap-northeast-1c"]
}

variable "availability_zone_dev" {
  default = ["ap-northeast-1c"]
}

// vpc_idを取得する
data "aws_ssm_parameter" "vpc_id" {
  name = "vpc_id"
}

// dbサブネットのidを取得する
data "aws_subnet_ids" "db-c" {
  vpc_id = "${data.aws_ssm_parameter.vpc_id.value}"

  tags {
    Name = "db-c"
  }
}

// dbサブネットのidを取得する
data "aws_subnet_ids" "db-d" {
  vpc_id = "${data.aws_ssm_parameter.vpc_id.value}"

  tags {
    Name = "db-d"
  }
}

// dbのSecurityGroupのidを取得する
data "aws_security_group" "db" {
  vpc_id = "${data.aws_ssm_parameter.vpc_id.value}"

  tags {
    Name = "db"
  }
}

// dbパスワードをssmパラメータストアから取得
data "aws_ssm_parameter" "db_password" {
  name = "db_password"
}

locals {
  master_password_anyenv = "${data.aws_ssm_parameter.db_password.value}"

  vpc_security_group_ids_anyenv = ["${data.aws_security_group.db.id}"]
  db_subnet_ids_anyenv          = ["${data.aws_subnet_ids.db-c.ids}", "${data.aws_subnet_ids.db-d.ids}"]

  instance_class_prod = "db.r4.large"
  instance_class_stg  = "db.t2.medium"
  instance_class_dev  = "db.t2.medium"

  vpc_security_group_ids = "${split(",",join(",",local.vpc_security_group_ids_anyenv))}"
  db_subnet_ids          = "${split(",",join(",",local.db_subnet_ids_anyenv))}"
  availability_zone      = "${split(",",(terraform.workspace == "prod" && terraform.workspace != "stg" && terraform.workspace != "dev") ? join(",", var.availability_zone_prod): join(",",var.availability_zone_dev))}"
}

module "create-aurora-mysql" {
  source                                = "./modules/rds/aurora-mysql"
  description                           = "for app"
  cluster_identifier                    = "app-db-${terraform.workspace}-cluster"
  identifier                            = "app-db-${terraform.workspace}"
  database_name                         = "app"
  master_username                       = "root"
  engine                                = "aurora-mysql"
  engine_version                        = "5.7.12"
  db_subnet_group_name                  = "app_db"
  db_parameter_group_name               = "app-aurora57"
  db_parameter_group_family             = "aurora-mysql5.7"
  cluster_parameter_group_family        = "aurora-mysql5.7"
  cluster_parameter_group_name          = "app-cluster-aurora57"
  preferred_backup_window_cluster       = "20:06-20:36"
  preferred_maintenance_window_instance = "sun:19:00-sun:19:30"
  preferred_maintenance_window_cluster  = "sun:19:00-sun:19:30"
  final_snapshot_identifier             = true
  backup_retention_period               = 7
  auto_minor_version_upgrade            = true
  publicly_accessible                   = false
  storage_encrypted                     = "${terraform.workspace == "prod" ? true : false}"
  master_password                       = "${local.master_password_anyenv }"
  vpc_security_group_ids                = "${split(",",join(",",local.vpc_security_group_ids_anyenv))}"
  db_subnet_ids                         = "${split(",",join(",",local.db_subnet_ids))}"
  instance_class                        = "${terraform.workspace == "prod" ? local.instance_class_prod : terraform.workspace == "stg" ? local.instance_class_stg :local.instance_class_dev }"
  availability_zone                     = "${split(",",(terraform.workspace != "prod" && terraform.workspace == "stg" && terraform.workspace != "dev") ? join(",", var.availability_zone_stg): join(",",local.availability_zone))}"
}

■ Resource側 ( main.tf )

variable "instance_class" {}
variable "description" {}
variable "identifier" {}
variable "cluster_identifier" {}
variable "database_name" {}
variable "master_username" {}
variable "master_password" {}
variable "db_parameter_group_name" {}
variable "db_parameter_group_family" {}
variable "cluster_parameter_group_name" {}
variable "cluster_parameter_group_family" {}
variable "preferred_backup_window_cluster" {}
variable "preferred_maintenance_window_cluster" {}
variable "preferred_maintenance_window_instance" {}
variable "final_snapshot_identifier" {}
variable "backup_retention_period" {}
variable "auto_minor_version_upgrade" {}
variable "publicly_accessible" {}
variable "storage_encrypted" {}
variable "engine" {}
variable "engine_version" {}
variable "db_subnet_group_name" {}

variable "availability_zone" {
  default = []
}

variable "vpc_security_group_ids" {
  default = []
}

variable "db_subnet_ids" {
  default = []
}

resource "aws_rds_cluster" "aurora_cluster" {
  cluster_identifier              = "${var.cluster_identifier}"
  availability_zones              = ["${var.availability_zone}"]
  database_name                   = "${var.database_name}"
  engine                          = "${var.engine}"
  master_username                 = "${var.master_username}"
  master_password                 = "${var.master_password}"
  db_cluster_parameter_group_name = "${var.cluster_parameter_group_name}"
  db_subnet_group_name            = "${aws_db_subnet_group.default.name}"
  vpc_security_group_ids          = ["${var.vpc_security_group_ids}"]
  preferred_backup_window         = "${var.preferred_backup_window_cluster}"
  preferred_maintenance_window    = "${var.preferred_maintenance_window_cluster}"
  final_snapshot_identifier       = "${var.final_snapshot_identifier}"
  backup_retention_period         = "${var.backup_retention_period}"
  storage_encrypted               = "${var.storage_encrypted}"
}

resource "aws_rds_cluster_instance" "cluster_instances" {
  count                        = "${length(var.availability_zone)}"
  identifier                   = "${var.identifier}-${count.index+1}"
  cluster_identifier           = "${aws_rds_cluster.aurora_cluster.id}"
  instance_class               = "${var.instance_class}"
  engine                       = "${var.engine}"
  db_subnet_group_name         = "${aws_db_subnet_group.default.name}"
  db_parameter_group_name      = "${var.db_parameter_group_name}"
  auto_minor_version_upgrade   = "${var.auto_minor_version_upgrade}"
  publicly_accessible          = "${var.publicly_accessible}"
  preferred_maintenance_window = "${var.preferred_maintenance_window_instance}"
}

resource "aws_db_subnet_group" "default" {
  name        = "${var.db_subnet_group_name}"
  subnet_ids  = ["${var.db_subnet_ids}"]
  description = "${var.description}"
}

resource "aws_db_parameter_group" "default" {
  name        = "${var.db_parameter_group_name}"
  family      = "${var.db_parameter_group_family}"
  description = "${var.description}"
}

resource "aws_rds_cluster_parameter_group" "default" {
  name        = "${var.cluster_parameter_group_name}"
  family      = "${var.cluster_parameter_group_family}"
  description = "${var.description}"
}

まとめ

今回は以下のことに取り組んだ。

  • VPC-IDをSSMパラメータから取得
  • DBのパスワードをSSMパラメータから取得
  • DBのサブネットIDをTerraformの機能を使って検索して取得
  • DBのセキュリティグループIDをTerraformの機能を使って検索して取得

SSMパラメータをTerraformで利用することで機密性の高いデータを扱うことができるようになった。 また、AWSリソース情報をフィルタして取得して利用することで動的な書き方が可能になりました。