6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-12-23

この記事はエイチームブライズ/エイチームコネクト/エイチーム引越し侍 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のユーザーを見ることができます。
スクリーンショット 2018-12-19 20.33.32.png
ユーザー詳細画面でユーザーに付与されたポリシーを確認するとインラインポリシーで、管理者用のTerraform
で定義したポリシーが付与されていることが確認できます。
スクリーンショット 2018-12-19 20.34.17.png

また、このユーザーは永久に残されるわけではなく、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エンジニア詳細ページ)よりお問い合わせ下さい。

6
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?