Edited at

Adminがいないクリスマス・イブでも安心してインフラ構築するためのTerraform, Vault, AWS

この記事はエイチームブライズ/エイチームコネクト/エイチーム引越し侍 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の部分を以下の用に変更してください。


demo.hcl

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ポリシーのトークンを発行するのはよろしくないことですので、

作業者に必要なポリシーを作成しました。


aws.hcl

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


variables.tf

variable "aws_access_key" {}

variable "aws_secret_key" {}
variable "vault_addr" {}
variable "vault_token" {}


terraform.tfvars

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のプロバイダーを使う設定を行います。


01_vault.tf

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


variables.tf

variable "vault_addr" {}

variable "vault_token" {}
variable "aws_region" {}


terraform.tfvars

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を使う設定以下のように

記述します。


01_vault.tf

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を利用します。


admin/vault_generic_secret.tf

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の末尾に以下の記述を追加して、再度ポリシーをアップロードします。


aws.hcl

...

path "secret/*" {
capabilities = ["read"]
}

作業者側ではTerraformのtfファイルを以下のように記述すれば、管理者側で設定した

パスワードを呼び出すことができます。


operator/02_rds.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エンジニア詳細ページ)よりお問い合わせ下さい。