HashiCorp VaultとdirenvでAWSのcredentialsを管理してみた

CodeanywhereでAWSにアクセスする場合にクレデンシャルの管理をどうしようか思案中。
aws configure で.aws配下に平文で置いておくのはなんだか気持ち悪いし、exportで環境変数都度設定するとミスしそうだし、履歴に残るから都度消したり手間かかるしなんか嫌だなーと思ってしまったのでHashiCorp社のVaultを試してみました。

## インストール対象
今回は、CodeanywhereのUbuntu 14.04のBlankスタックにインストール

##インストール
```
$ wget https://releases.hashicorp.com/vault/0.9.6/vault_0.9.6_linux_amd64.zip
$ unzip vault_0.9.6_linux_amd64.zip
$ sudo mv vault /usr/local/bin/
```

## 設定
設定ファイルを作成する。場所はお好みで。

```
$ sudo mkdir -p /etc/vault/
$ sudo vi /etc/vault/config.hcl
```

内容はこんな感じ。こちらもお好みで修正を。
公式ドキュメントはこちら。
https://www.vaultproject.io/docs/configuration/index.html

```/etc/vault/config.hcl
backend "file" {
  path = "/mnt/vault/data"
}

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

valutへの接続はhttpsになるけれど、tlsを無効にしているのでhttpでつながないといけない。
接続アドレスを変更するために環境変数を書き換える。

```
export VAULT_ADDR=http://127.0.0.1:8200
```

毎回打つのがめんどくさかったら、.bashrcとか.envrcとかに書いておく。

## 起動

```
$ sudo vault server -config /etc/vault/config.hcl
```

これだとフォアグラウンドのプロセスで動く。
バックグランドで動かしたい場合はこんな感じ。
標準出力をログファイルに出力するようにしてみた。

```
$ sudo vault server -config /etc/vault/config.hcl >> /var/log/vault &
```

## 初期化

まずはSeal/Unsealの基本的な考え方について
https://www.vaultproject.io/docs/internals/architecture.html

Encryption KeyはMaster Keyによって暗号化されていてMaster keyはkey Sharesに分割されている？

valutが起動した時は、情報はSealされており、Unsealすることでアクセスができるようになる。
この時にMaster keyを何分割してそのうち、何個のKey Shareを使えばUnseal出るかを初期化する時に決める必要がある。

細かく分割すればするほどセキュリティは上がりそうだけど、個人利用且つ利用感のお試しをしたいのでひとまず、1にした。

```
$ vault operator init -key-shares=1 -key-threshold=1
```


-key-shares -key-threshold は省略可能で、指定しない場合の扱いはこんな感じ。

```
$ vault operator init # 下のパラメータを与えたのと同義 
$ vault operator init -key-shares=5 -key-threshold=3
```

initを実行したらこんな感じの内容が出力される。

```
vault operator init
Unseal Key 1: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Initial Root Token: aaaaaaaa-bbbb-cccc-eeee-ffffffffffff


Vault initialized with 5 key shares and a key threshold of 3. Please securely
distribute the key shares printed above. When the Vault is re-sealed,
restarted, or stopped, you must supply at least 3 of these keys to unseal it
before it can start servicing requests.

Vault does not store the generated master key. Without at least 3 key to
reconstruct the master key, Vault will remain permanently sealed!

It is possible to generate new unseal keys, provided you have a quorum of
existing unseal keys shares. See "vault rekey" for more information.
```

Unseal KeyとInitial Root Tokenを忘れないようにメモっとく。

これで利用準備が整ったので、Unsealしてアクセスできるようにする。

```
$ vault operator unseal
```


init した時に教えてもらったUnseal Key 1を入力するとこんな内容が出力される。
SealedがfalseになっているのでUnsealできた。

```
Key             Value
---             -----
Seal Type       shamir
Sealed          false
Total Shares    1
Threshold       1
Version         0.9.6
Cluster Name    vault-cluster-54c90050
Cluster ID      27ca01b0-4994-3997-e9cd-4e0838866593
HA Enabled      false
```

Unsealまで終わるとログインができるようになるのでRoot Tokenを使ってログインする。

```
$ vault login <Root Token> 
```

こんな感じでログがでる。

```
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.

Key                Value
---                -----
token              <Root Token>
token_accessor     ffffffff-ffff-ffff-ffff-ffffffffffff
token_duration     ∞
token_renewable    false
token_policies     [root]
```

コマンドが無事通ればログイン完了。

```
$ vault secrets list
Path          Type         Description
----          ----         -----------
cubbyhole/    cubbyhole    per-token private secret storage
identity/     identity     identity store
secret/       kv           key/value secret storage
sys/          system       system endpoints used for control, policy and debugging

```

## ユーザーIDとパスワードで認証する

ユーザーIDとパスワードで認証できるようにする。
Githubとかでも認証できるようだが、まずはお手軽そうなユーザーIDとパスワードの認証を試す。

### ユーザー追加

まずはユーザーに割り当てる権限を用意するため、policy.hclというファイルを用意する。

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

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

```
$ vault policy write secret-all ~/policy.hcl
```

secret配下全てに書き込み権限を与える設定
secretは階層構造になっているので、secret/a配下だけアクセスできるユーザーや
secret/b配下にしかアクセスできないユーザーを作ったりできる。

今回はお勉強用なので細かい制御はスキップ。

```
$ vault auth enable userpass
$ vault write auth/userpass/users/ore password=foo policies=secret-all
```

これで `ore` という名前のユーザーが追加された。

## 追加したユーザーでログイン。

vaultのコマンドを見ているとログアウトに関するコマンドがパッと見つからなかった。
ひとまずログインをし直すことでユーザーを切り替えれたのでひとまずログアウトのことは忘れる。

```
$ vault login -method=userpass username=ore
Password (will be hidden):
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.

Key                    Value
---                    -----
token                  2f137625-a0ce-790f-9180-658101969563
token_accessor         4f621a45-f65f-dbde-4335-368341c49b82
token_duration         768h
token_renewable        true
token_policies         [admins default]
token_meta_username    ore
```

パスワードを聞かれるので入力し、認証が通ると上記のようにトークンが発行される。

## シークレットを追加

### 追加
```
$ vault write secret/a1 value=11
```

### 参照
```
$ vault read secret/a1
Key                 Value
---                 -----
refresh_interval    768h
value               11
```

$値だけ取得したい場合はこちら

```
$ vault read -field "value" secret/a1
11
```

## direnvと組み合わせてディレクトリごとに自動的にAWSのクレデンシャルを切り替える

やってみたけど、イケてない・・・もっとクールな方法があるはず。
要検討。

### ディレクトリの作成

direnvで環境を切り替えたいディレクトリを作成。

```
$ mkdir -p /PATH/TO/TARGET_DIR
```

### VaultにAWSのクレデンシャルの登録

今回はsecret配下にディレクトリ名毎のパスで階層を区切って登録する。
.envrcの内容をディレクトリ毎に都度書き換える対応をしていると事故しそうなのでディレクトリとsecretをマッピングするような形にした。
この辺はもっと詰めていけば良い方法があるはず。

まずは、クレデンシャルを登録。

```
vault write secret/PATH/TO/TARGET_DIR/AWS_ACCESS_KEY_ID value=AKI*****************
vault write secret/PATH/TO/TARGET_DIR/AWS_SECRET_ACCESS_KEY value=****************************************
```

次に対象のディレクトリで.envrcを作成。

```
$ cd /PATH/TO/TARGET_DIR
$ direnv edit .
```

以下の内容を追加。

```.envrc
DIR=`pwd`
export AWS_ACCESS_KEY_ID=$(vault read -field "value" secret$DIR/AWS_ACCESS_KEY_ID)
export AWS_SECRET_ACCESS_KEY=$(vault read -field "value" secret$DIR/AWS_SECRET_ACCESS_KEY)
export AWS_DEFAULT_REGION=ap-northeast-1
```

これでこのディレクトリに入ったらvault経由でディレクトリ単位でクレデンシャルの環境変数が書き換わる。

vault経由でクレデンシャルを取得するためには、

1. Unsealしてアクセス可能な状態にする。
1. vaultにログインしてデータを参照可能にする。

という2段階の作業を行わないとクレデンシャルが利用できなくなり、平文でのクレデンシャルを保管するaws configureよりもセキュアになった気がする。

このままだとhistoryにクレデンシャルが残ったままなので対象となりそうなやつだけ抜き取って消す方が綺麗だけどめんどくさいので今回は全部削除。

```
$ history -c && history -w
```

## まとめ
- クレデンシャルを平文で保管しないのでセキュアになった気がする。

## 今後しないといけないこと
- vaultにログインするためのベストな方法の検証。Githubとかで認証できるようにした方が良い？
- Vault Serverを都度立ち上げるのも面倒なので、スクリプト組むかcronの@rebootで起動するとかして自動起動させる。
- direnvとsecretの関連付けの最適な方法の検討。今回使った関連付けだとディレクトリとAWSのアカウントが1対1の関係で固定化されてしまう。