この記事はエイチームブライズ/エイチームコネクト/エイチーム引越し侍 Advent Calendar 2018 24日目の記事です。
はじめに
TerraformはIaC(Infrastructure as Code)を実現するための最も重要なツールの一つといっても
過言ではないと思います。AWS, GCPはもちろんのこと様々なクラウドサービスの機能をコード化する
ことができます。
Terraformを習得すれば、AWS上に数百台のインスタンスをあっと言う間に起動することだって
いとも簡単にできます。
当然、TerraformでAWS上のリソースを操作するためには、AWSのアクセスキーとシークレットキー、
そして、リソースを操作するためのIAMの権限が必要です。アクセスキーとシークレットキーは定期的
に交換することや、IAMの権限は定期的に見直すことが望ましいですが、特にインフラエンジニアが
増えてくるとこれらの管理は苦痛を伴うものになってきます。
そこで今回はTerraformとVaultを用いて、インフラ管理者以外にアクセスキーとシークレットキーを
配布しないでTerraformの実行環境を整えたいと思います。
Vaultの準備
まずはVaultの環境を構築します。ローカルでVaultを構築したい場合には、Hashicorp Vault 1.0で追加されるauto-unsealを試してみたを参考に
環境を構築することをおすすめします(これはお試し用の環境ですので、本番環境には適用しないで下さい)。
Terraformと連携する場合にはコンテナの外からVaultにアクセスする必要がありますので、上記の記事を参考
にする場合にdemo.hclのlistnerの部分を以下の用に変更してください。
listener "tcp" {
address = "0.0.0.0:8200"
tls_disable = 1
}
管理者、作業者用のトークンの発行とポリシーの設定
terraformからVaultに接続するためのトークンをVaultのコマンドで作成します。
管理者用のTerraformと作業者用のTerraform用に2つトークンが必要になりますが、まずは
管理者用のトークンを作成します。ポリシーはrootと同じものでいいと思いますが念の為ttlを設定して
作成しました。
# vault token create -ttl=168h
Key Value
--- -----
token s.4hD6KBn9U20TxwpUMvzoKdzm
token_accessor 4RpO0YsQVM7PHG7FS3jnPgoJ
token_duration 168h
token_renewable true
token_policies ["root"]
identity_policies []
policies ["root"]
次に作業者用のトークンを作成します。さすがにrootポリシーのトークンを発行するのはよろしくないことですので、
作業者に必要なポリシーを作成しました。
path "aws/*" {
capabilities = ["read"]
}
path "auth/token/*" {
capabilities = ["update"]
}
上記の内容をaws.hclというファイルに保存して、以下のコマンドを実行してポリシーを作成します。
# vault policy write aws aws.hcl
Success! Uploaded policy: aws
# vault policies
aws
default
root
そして作成したポリシーを使用した作業者用のトークンを作成します。
# vault token-create -policy="aws" -ttl=24h
Key Value
--- -----
token s.12jFUtLq811lJDWLFQnevcTs
token_accessor 33V4nGl9EK3aTE0WYndFLNaO
token_duration 24h
token_renewable true
token_policies ["aws" "default"]
identity_policies []
policies ["aws" "default"]
#インフラ管理者用のTerraformの作成
Vaultと連携してAWSのアクセスキーとシークレットキーを管理するTerraformプロジェクトを作成します。
簡略化のためにバックエンド等の設定はしていませんが、以下のように構成にします。
├── admin
│ ├── 01_vault.tf
│ ├── terraform.tfvars
│ └── variable.tf
variable "aws_access_key" {}
variable "aws_secret_key" {}
variable "vault_addr" {}
variable "vault_token" {}
aws_access_key = "AWS_ACCESS_KEY"
aws_secret_key = "AWS_SECRET_KEY"
vault_addr = "VAULT_ADDR"
vault_token = "s.4hD6KBn9U20TxwpUMvzoKdzm"
Vaultをローカル環境で立ち上げている場合はvault_addrはhttp://127.0.0.1:8200
にしておけばいいでしょう。
vault_tokenは先程の作業で発行した管理者用のトークンを指定します。
これらの情報を元にTerraformからVaultのプロバイダーを使う設定を行います。
provider "vault" {
address = "${var.vault_addr}"
token = "${var.vault_token}"
}
resource "vault_aws_secret_backend" "aws" {
access_key = "${var.aws_access_key}"
secret_key = "${var.aws_secret_key}"
region = "ap-northeast-1"
default_lease_ttl_seconds = "120"
max_lease_ttl_seconds = "240"
}
resource "vault_aws_secret_backend_role" "ec2-admin" {
backend = "${vault_aws_secret_backend.aws.path}"
name = "ec2-admin-role"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iam:*", "ec2:*", "rds:*"
],
"Resource": "*"
}
]
}
EOF
}
上記の状態でterraform applyを完了すると、VaultにAWSのadmin roleの情報が保存されます。
念の為Vault側で確認してみると以下の情報が見れると思います。
/ # vault read aws/roles/ec2-admin-role
Key Value
--- -----
credential_types [iam_user federation_token]
default_sts_ttl 0s
max_sts_ttl 0s
policy_arns <nil>
policy_document {"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["iam:*","ec2:*","rds:*"],"Resource":"*"}]}
role_arns <nil>
#作業者用のTerraformの作成
実際にAWSのリソースを操作してインフラ構築を行う作業者用のTerraformは以下の用に作成します。
└── operator
├── 01_vault.tf
├── terraform.tfvars
└── variable.tf
variable "vault_addr" {}
variable "vault_token" {}
variable "aws_region" {}
vault_addr = "VAULT_ADDR"
vault_token = "s.12jFUtLq811lJDWLFQnevcTs"
aws_region = "ap-northeast-1"
vault_tokenには作業者用に作成したトークンを指定します。
管理者用のTerraformプロジェクトではaws_access_keyとaws_secret_keyを定義していましたが、
作業者用のTerraformプロジェクトでは定義しません。これらの情報はVaultから取得出来るように
なるからです。このTerraformプロジェクトでVaultとAWSのproviderを使う設定以下のように
記述します。
provider "vault" {
address = "${var.vault_addr}"
token = "${var.vault_token}"
}
data "vault_aws_access_credentials" "creds" {
backend = "aws"
role = "ec2-admin-role"
}
provider "aws" {
access_key = "${data.vault_aws_access_credentials.creds.access_key}"
secret_key = "${data.vault_aws_access_credentials.creds.secret_key}"
region = "${var.aws_region}"
}
この状態でTerraform applyを行うとVault側から取得したAWSのアクセスキーとシークレットキー
を使用してAWSのリソースを操作することができます。
#AWSコンソールでのIAMの確認
これまでで、作業者向けのAWSのアクセスキーとシークレットキーをVault側から取得することが
できましたが、作業者向けのTerraformをApplyしたとき、AWS側ではどのようなIAM情報が作られているのか
AWSのコンソールから確認してみます。ユーザー名でvaultで検索すると今回のTerraformで作成された
IAMのユーザーを見ることができます。
ユーザー詳細画面でユーザーに付与されたポリシーを確認するとインラインポリシーで、管理者用のTerraform
で定義したポリシーが付与されていることが確認できます。
また、このユーザーは永久に残されるわけではなく、default_lease_ttl_secondsに従って自動的に削除されます。しばらく時間が経ってからユーザー名をvaultで検索しても何もヒットしないことが確認出来ると思います。
vault_1 | 2018-12-19T11:31:33.876Z [INFO] expiration: revoked lease: lease_id=aws/creds/ec2-admin-role/KXEYNFRvp21mgAsVtnaAzfNS
Vault側のログを確認してみると上記のようなログが残っており、Vault側からIAMのユーザーを削除してくれたことが確認できます。AWS側のリソースがどうなっているかを意識することなくVault側でユーザーの追加と削除を行ってくれるのは素晴らしいことだと思います。
#その他の秘密情報もVaultから取得する
AWSのアクセスキーとシークレットキー以外にも、Vaultから情報を取得することを考えてみます。
例えばRDSを作成する際のDBのパスワードを当然必要ですが、作業者はパスワードを知る必要も
ありませんし、それを平文でGitリポジトリにコミットする必要はないでしょう。
TerraformからVaultにこのような情報を登録する際はvault_generic_secretを利用します。
resource "random_id" "db_password" {
byte_length = 8
}
resource "vault_generic_secret" "db" {
path = "secret/db"
data_json = <<EOT
{
"password": "${random_id.db_password.b64}"
}
EOT
}
下記のようなtfファイルを作成してterraform applyするとVault側にはsecret/dbというキー名で
情報が登録されます。作業者側で呼び出す前にVaultのポリシーを更新する必要があります。
今回の例の場合ではaws.hclの末尾に以下の記述を追加して、再度ポリシーをアップロードします。
...
path "secret/*" {
capabilities = ["read"]
}
作業者側ではTerraformのtfファイルを以下のように記述すれば、管理者側で設定した
パスワードを呼び出すことができます。
data "vault_generic_secret" "db" {
path = "secret/db"
}
resource "aws_db_instance" "db" {
allocated_storage = 10
storage_type = "gp2"
engine = "mysql"
engine_version = "5.7"
password = "${data.vault_generic_secret.db.data["password"]}"
...
#まとめ
TerraformとVaultを連携させることにより、有効期限の短いAWSのシークレットキーの発行と利用
を透過的の行えるようになりました。この方法であれば、AWSのシークレットキーを平文で記述する
必要がないので、漏洩のリスクを抑えることができます。
またシークレットキーが必要な管理者側のTerraformでもIAMを作る権限さえあればいいため、
AdministratorAccessのような強いポリシーを持つ必要がないのもメリットです。
Vault側のトークンにもポリシーと有効期限
を設定することが出来るので、こちらも適切なポリシーを設定して有効期限を短くすることにより、
漏洩の影響を最低限に留めることが出来ると思います。
お知らせ
エイチームグループでは一緒に活躍してくれる優秀な人材を募集中です。
興味のある方はぜひともエイチームグループ採用ページ(Webエンジニア詳細ページ)よりお問い合わせ下さい。