HashiCorp Vaultを機密情報データベースとして検証する

あまりまだメジャーではないみたいですが、HashiCorpが機密情報管理用のツールとして[Vault](https://www.hashicorp.com/blog/vault.html)を出しています。代替になるツールもあまり思い浮かばないし、物は試しと使ってみました。

Vaultでできること概要
====

* 機密情報等をKey,Value形式で書き込むと暗号化して保存してくれる。
* `Secret Backends`という機能で、MySQL、PostgreSQL、AWS、LDAP等と連携し、Vaultを通じてユーザー情報の追加、変更、削除等を行える。このときLease（期限）を設定し、一定期間後にアカウントを自動削除したり、パスワードをRevokeさせることができる。
* デフォルトの状態ではデータはすべて暗号化（Sealed）されており、Vault自身も復号する方法を知らない。復号には分散鍵による認証が必要になる。
* Vaultへのアクセス方法はCLIかREST API。
* Vaultへアクセスする際の認証はユーザーパスワード形式、GitHub連携、一時的なtokenの払い出しなどを扱える。
* Vaultに対して行われた操作はすべて保存され、監査に対応できる。

今回すること
====

* 試験的な運用のため、Secret Backendsはデフォルトのまま、シンプルに情報の保存と読み出しだけを試す。
  （Secret Backendsはデフォルトだと`Generic`というものがマウントされており、これは単純に情報を保存すると、それを暗号化してファイルに書き込むのみ）
* ユーザーパスワード形式で認証して使用することを想定する。
* 開発用途で起動できるdevモードが存在しているが、本番相当の設定を検証したいので今回は使わない。

初期設定
====

## インストール

zipを落としてきて`unzip`し、PATHの通っている場所に置いて完了。

## 設定

hclもしくはjsonで書いた設定ファイルを元として起動する。設定ファイルは [Server Configuration - Vault by HashiCorp](https://www.vaultproject.io/docs/config/index.html) に記述方法が書かれているが、最低限2つの項目が必要とされる。

### backend

Vaultのデータを保存するSecret Backendの設定。MySQLやPostgresqlといったRDBMS、S3やDynamoDB、プレーンのファイルなど様々選べる。`Generic`を使う場合は`file`を設定することになる。

### listener

APIリクエストを受け付けるリスナープロトコルの設定。現状は`tcp`のみ対応。`tls`を使う場合の証明書の設定等もできる。デフォルトでは`tls`が有効化されているため、使用しない場合は無効化の明示が必要となる。

#### tls無効化

* listenerで`tls_disable = 1`を指定する。
* vaultへの接続はデフォルトで`https`が使われるので、`VAULT_ADDR`環境変数で`http`のアドレスを指定する。

```hcl:vault.hcl
backend "file" {
  path = "/home/ec2-user/vault"
}

listener "tcp" {
  address = "127.0.0.1:8200"
  tls_disable = 1
}
```

起動
====

先のhclファイルを指定して起動する。起動後に`vault init`で初期化する。

## 注意点

* フロントで立ち上がり、ログを標準出力に流してくるため、リダイレクトとバックグラウンドでの起動が必要。
* vaultは`mlock`システムコールを使用するため、管理者権限で実行しなければエラーになる。configの中で`mlock`の無効化もできるが、非推奨。
* `vault init`した際に表示される`Unseal Key`、`Initial Root Token`はいずれも必要なので記録する。

```bash
$ sudo vault server -config /etc/vault.hcl &>/var/log/vault &
$ vault init
Unseal Key 1 (hex)   : XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Unseal Key 1 (base64): XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
...
Initial Root Token: XXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Vault initialized with 5 keys and a key threshold of 3. Please
securely distribute the above keys. When the Vault is re-sealed,
restarted, or stopped, you must provide at least 3 of these keys
to unseal it again.

Vault does not store the master key. Without at least 3 keys,
your Vault will remain permanently sealed.
```

## Unseal

Vaultはデフォルトだと`Sealed`（暗号化された状態）となっているため、先程出力された`Unseal Key`を使って`unseal`を行う必要がある。まずステータスを確認してみる。

```
$ vault status
Sealed: true
Key Shares: 5
Key Threshold: 3
Unseal Progress: 0
Version: Vault v0.6.1
```

`Sealed: true`となっている。`Key Shares`は`Unseal`のために共有しているキーの数であり、このうち`Key Threashold`に表示された閾値の数だけキーを入力すると`Unseal`される。

キーは複数人で分散管理し、全員が持ち寄ることで初めて復号化できる、というのがこの機構の目的。技術的には「シャミアの秘密分散法」と呼ばれるものらしい。逆に`Seal`するときは`vault seal`コマンドを使えば一人で暗号化できるので、キーが漏れてしまった場合などは`Seal`することで対処できる。

復号化する。`vault unseal`コマンドを実行し、`vault init`の際に表示されたキーから3つを順に入れていく。出力の`Unseal Progress`が徐々に進んでいき、`Threshold`に達すると`unseal`される。

```bash
$ vault unseal
Key (will be hidden):
Sealed: true
Key Shares: 5
Key Threshold: 3
Unseal Progress: 1
```

Auth
----

認証機構。デフォルトで使用できるユーザーはrootのみなので、まずはrootで認証する。`vault init`のときに出力されたrootのトークンを使う。

```
$ vault auth XXXXXXXXXXXXXXXXXXX
Successfully authenticated! You are now logged in.
token: XXXXXXXXXXXXXXXXXXXX
token_duration: 0
```

使用するAuth Backendを有効化する。今回は`userpass`。有効化後に、ユーザーを作成して認証までしてみる。

```
$ vault auth-enable userpass
Successfully enabled 'userpass' at 'userpass'!

$ vault write auth/userpass/users/chroju password=******
Success! Data written to: auth/userpass/users/chroju

$ vault auth -method=userpass username=chroju
Password (will be hidden):
Successfully authenticated! You are now logged in.
The token below is already saved in the session. You do not
need to "vault auth" again with the token.
token: XXXXXXXXXXXXXXXXXXXXX
token_duration: 2592000
token_policies: [default]
```

認証が成功するとtokenが発行されるが、APIを使うときはこのtokenで認証することができる。CLIではこの状態でもう操作が可能。なおtokenはカレントディレクトリの`.vault-token`ファイルに書き込まれており、CLI使用時はこれでセッションを維持しているっぽい。

ACL
----

単にユーザーを作っただけでは、すべてのリソースに対する操作権限は`Deny`に設定されているため、ユーザーがどのリソースに対してアクセス可能かはACLで定義する。Vaultにおける機密情報の保存場所は、いわゆるファイルパスのようなスラッシュで区切った形式で指定する。従って適切にフォルダを分けて、アクセス権を分散させることができる。

* ACLはHCLもしくはjsonにより記述する。
* フォルダ配下へのアクセスは自動で付与されない。例えば`path "secret"`に対する許可を書くと、`secret`にはアクセスできても`secret/foo`にはアクセスできない。
* ACLを付与しない限り、デフォルトで全アクセスは`Deny`される。
* userpassバックエンドでの認証は`auth/token/lookup-self`への`read`権限がなければ通らなかった。少し謎。

```hcl
path "auth/token/lookup-self" {
  policy = "read"
}

path "secret/*" {
  policy = "write"
}
```

* ACLファイルを作成したら、Vaultに登録してからユーザーへ結びつける。
* ユーザーに対する`write`は全属性書き換えになるらしいので、常にパスワードも含めた全属性を設定して`write`する必要がある。
    * [Add policy to existing userpass account using CLI - Google グループ](https://groups.google.com/forum/#!msg/vault-tool/Qi7xvAkNyX4/yBpuA-IQCgAJ)

```bash
$ vault policy-write allow-secret /etc/vault_acl.hcl
$ vault write auth/userpass/users/chroju password=****** policies=allow-secret
```

利用
====

データの読み書き。

```bash
$ vault write secret/foo value=bar
Success! Data written to: secret/foo

$ vault read secret/foo
```

普通に入力するとコマンドラインの履歴に残って気持ち悪いので、標準入力やファイルからの入力を駆使する。

```bash
# 標準入力を使う
$ echo -n '{ "username":"foo", "password":"bar" }' | vault write secret/server01
Success! Data written to: secret/server01
  
# jsonファイルを使う 
$ cat server02.json
{
  "username" : "foo",
  "password" : "bar"
}

$ vault write secret/server02 @server02.json
Success! Data written to: secret/server02
```

APIからアクセスする。tokenを`X-Vault-Token`ヘッダーに入力することで認証する。

```bash
$ curl http://localhost:8200/v1/secret/foo -H "X-Vault-Token: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
{"request_id":"b745ef53-ff32-9ffe-b8b2-a7dc03072f7e","lease_id":"","renewable":false,"lease_duration":2592000,"data":{"value":"bar"},"wrap_info":null,"warnings":null,"auth":null}
```

tokenは`token-create`コマンドを使って、一時的な払い出しができる。オプション`-ttl`で時間制約を持たせることができ、`-use-limit`で使用回数を制限できる。

```bash
$ vault token-create -policy=allow-secret -ttl=1h
Key             Value
---             -----
token           bcae0b31-55af-8349-dd9a-f0200fbe6b53
token_accessor  39be2d81-4860-d271-365d-42db4db41f0e
token_duration  1h0m0s
token_renewable true
token_policies  [allow-secret default]
```

その他留意点など
====

## パスワードの変更

`userpass` backendを使う場合、当然各ユーザーがパスワードの変更をしたくなるが、これもまた`vault write`コマンドによる書き込みになる。パスワード情報は`auth/userpass/users/${username}/password`というパスに保存されているため、このパスに対してACLで許可を与えなくては、パスワード変更はできない。

```bash
$ vault write auth/userpass/users/chroju/password username=chroju password=hogefuga
Success! Data written to: auth/userpass/users/chroju/password
```

しかし、そうなると各ユーザーに個別にACLを設定して、自分のパスワードパスだけに対するアクセス権を与えてやることになるので、ACLの種類がユーザーの数だけ増えて管理が大変になりそう。この点、もう少しいいソリューションはないのかなと思う。

## 雑感

* APIアクセスが主であり、また認証（Auth Backends）も`userpass`ではなくGitHub等の他サービスと連携させることが基本なのかなと思う。先に書いたパスワード変更の件など、`userpass`でCLIを介して常時使うような設計にはなっていない気がした。
* とはいえHashiCorpのツールらしく、各機能がシンプルに独立していて、プラガブルに使える点はとてもいい。「こうしなければ使用できない」となってしまうような変な制限がなく、使い方に幅がある。それだけに運用方法は練る必要がある。
* スクリプト等でパスワードをハードコーディングするバッドプラクティスへの対策として、Vaultからパスワードを引っ張ってきたりできると思うので試したい。が、基本的にtokenにTTLがある状態で、どう設計すればいいのかよくわからない。軽くググってもいまいち事例が出てこないし、出てきたと思ったら唯一TTLの存在しないrootアカウントのtokenを使ってたりしてセキュリティ的によろしくない感じであった。