Vault: CubbyholeとResponse Wrapping

[Hashicorp Vault](https://www.vaultproject.io/)のCubbyholeバックエンドとResponse Wrappingに関するメモです。トークンの受け渡し時の漏洩リスクを最小限にする方法を提供してくれる機能です。

## Cubbyhole

CubbyholeとはVaultのSecretバックエンドの一つです。`generic`バックエンドと同じくKey Value形式でデータを書くことができます。特徴はトークンごとにパスが別のスコープになっており、あるトークンで書いたデータはそのトークンからしか見ることができないことです。

```bash:example
# トークンAでデータを書き込む
$ VAULT_TOKEN=$TOKEN_A vault write cubbyhole/mysecret whoami=A
Success! Data written to: cubbyhole/mysecret

# トークンAからはデータが見れる
$ VAULT_TOKEN=$TOKEN_A vault read cubbyhole/mysecret
Key     Value
---     -----
whoami  A

# トークンBではデータは見えない
$ VAULT_TOKEN=$TOKEN_B vault read cubbyhole/mysecret
No value found at cubbyhole/mysecret

# トークンBで同じパスで書き込む
$ VAULT_TOKEN=$TOKEN_B vault write cubbyhole/mysecret whoami=B
Success! Data written to: cubbyhole/mysecret

# トークンAの同一パスはそのまま
$ VAULT_TOKEN=$TOKEN_A vault read cubbyhole/mysecret
Key     Value
---     -----
whoami  A
```

この仕組みとトークンのTTL、使用回数制限を利用することで、リスクを最小限にサーバーやコンテナ等にトークンを渡すことができます。

**後述のVault 0.6で追加されたResponse Wrappingを使えば下記は不要**ですが、仕組みの理解のために図にしてみました。

![cubbyhole1](https://qiita-image-store.s3.amazonaws.com/0/100377/0fc7ca98-8f4f-f9a5-b948-8eb9a09614e0.png)

- **永続トークン**: 対象に渡したいトークン
- **一時トークン**: 永続トークンを取得するための一時的なトークン
  - 使用回数(2回)と短いTTLを設定したトークン

一時トークンは2回の使用制限を設定し下記のよう使います。

1. デプロイを行うエンティティ(管理者など)が**永続トークン**を生成します
2. 加えて使用制限を2回に設定した**一時トークン**を生成します
3. **一時トークン**を使って`cubbyhole`に**永続トークン**を書きます (使用1回目)
4. デプロイ対象に**一時トークン**を渡します
5. デプロイ対象は**一時トークン**を使って`cubbyhole`から**永続トークン**を取得します (使用2回目)
  - この時点でこの**一時トークン**は無効になるので漏洩しても問題ない

トークン受け渡しのチャネルがサーバー生成時のuser_dataや環境変数など、セキュアではないチャネルであっても**一時トークン**を利用することで、**永続トークン**の漏洩リスクを最低限に抑えることができます。対象が永続トークンを取得される前に漏洩した一時トークンが使われる可能性もゼロではありませんが、対象が取得エラーになるため気づくことができます。また、監査ログを検査することでも検出が可能です。

Cubbyholeとトークンの受け渡しの詳細に関してはVault公式blogの[Vault: Cubbyhole Authentication Principles](https://www.hashicorp.com/blog/vault-cubbyhole-principles.html)という記事が詳しいです。


## Reponse Wrapping
Response WrappingはVault 0.6で導入された機能で、Cubbyholeをより簡単に利用できる機能です。本来のレスポンスをラップし、実際のレスポンスの代わりにそのレスポンスにアクセスするための一回限り使えるトークン(ラッピングトークン)を発行してくれます。`sys/`エンドポイントを除く全てのリソースで利用可能です。

トークン発行の場合は下記のように`-wrap-ttl`を指定してリクエストをします。

```bash
$ vault token-create -policy=default -wrap-ttl=60s
Key                             Value
---                             -----
wrapping_token:                 d2fcb185-94f8-8402-b40f-4663df34b330 # ラッピングトークン
wrapping_token_ttl:             1m0s
wrapping_token_creation_time:   2016-10-02 14:29:12.421102311 +0000 UTC
wrapped_accessor:               a7bdc8ae-fc3d-2971-9870-e3da1601c924
```

ここで返ってくるラッピングトークンを使って、本来のトークンを取得します(unwrap)。レスポンスはラッピングトークンのスコープ内の`cubbyhole/response`というパスに書かれています。unwrapにはトークンは必要ありません。

```bash
$ unset VAULT_TOKEN # トークンが不要なことを確認するために消しておく
$ vault unwrap d2fcb185-94f8-8402-b40f-4663df34b330
Key             Value
---             -----
token           20cb9352-8cfb-b130-0a11-5dad88ceaa02 # 本来渡したいトークン
token_accessor  a7bdc8ae-fc3d-2971-9870-e3da1601c924
token_duration  720h0m0s
token_renewable true
token_policies  [default]
```

ラッピングトークンを2回使おうとすると下記のようにエラーになります。

```bash
$ vault unwrap d2fcb185-94f8-8402-b40f-4663df34b330
error reading cubbyhole/response: Error making API request.

URL: GET http://127.0.0.1:8200/v1/cubbyhole/response
Code: 403. Errors:

* permission denied
```

前述のトークンの受け渡しにResponse Wrappingを使うと下記のようになります。一回のリクエストでラッピングトークンを取得できるので非常に簡単になりました。また、永続トークンを管理者が取得する必要がなくよりセキュアな仕組みになっています。

![vault-cubbyhole_002.png](https://qiita-image-store.s3.amazonaws.com/0/100377/d27d3b3a-a4bc-9ee2-806e-def1a86ca201.png)


Wrapping Responseはトークン生成以外にも使える汎用的な仕組みです。例えば`secret/mysecret`というパスの情報のラップレスポンスが欲しい場合以下のように通常のreadに`-wrap-ttl`を指定します(HTTPの場合`X-Vault-Wrap-TTL`ヘッダ)。

```bash
$ vault read -wrap-ttl=60s secret/mysecret
Key                             Value
---                             -----
wrapping_token:                 c42b4bca-fc6f-95f0-a352-bf6b5e639a9c
wrapping_token_ttl:             1m0s
wrapping_token_creation_time:   2016-10-02 14:35:50.899576812 +0000 UTC
```

下記のように`unwrap`でsecretのレスポンスを取得することができます。

```bash
$ vault unwrap c42b4bca-fc6f-95f0-a352-bf6b5e639a9c
Key                     Value
---                     -----
refresh_interval        720h0m0s
hello                   world
```

なお、レスポンスは`cubbyhole`に書かれているため、wrap/unwrap間に元データの更新があっても、wrap時点でのデータが取得されます。

## 参考

- https://www.vaultproject.io/docs/secrets/cubbyhole/
- https://www.vaultproject.io/docs/concepts/response-wrapping.html
- https://www.hashicorp.com/blog/vault-0.6.html#response-wrapping
- https://www.hashicorp.com/blog/vault-cubbyhole-principles.html