VaultとConsulを連携する (1) Consulをstorage backendとして使う

# Vaultって何？

[Vault](https://www.vaultproject.io/)はHashicorpが発表した機密情報管理ツールである。
詳細はいくらか日本語情報があるのでそちらを参照のこと。

* http://pocketstudio.jp/log3/2015/04/29/vault/
* http://kiririmode.hatenablog.jp/entry/20150429/1430279218

# 今回やること

VaultはConsulをstorage backendにすることによりHA構成を取ることができる、と[ドキュメント](http://vaultproject.io/docs/config/index.html)に書いてあったので試してみることにした。

今回、VaultサーバとなるコンピュータはConsulクライアントでもあることとする。
これは設定ファイルでConsulのアドレスを指定する箇所だけに影響する。

Vaultサーバは2台立てることにする。
10.0.2.167(hostname: cc6)はCentOS 6、10.0.2.170(hostname: cc7)はCentOS 7である。
またこちらの環境で今立てられる台数の都合でサーバになったコンピュータにはクライアントも兼ねてもらうことにしたが、別々にしても問題ない（はず）。

storage backendは[このドキュメント](http://vaultproject.io/docs/internals/architecture.html)に書かれている用語だが、同じものを指す言葉として[physical backendという表現も見たことがある](https://twitter.com/mitchellh/status/597833528173142016)。

またConsulはstorage backend以外にもsecret backendにもなる。
secret backendとする方法はいい加減長くなるので[別記事](http://qiita.com/yunano/items/931448a590c7f346ed01)にした。

この記事はVaultのバージョンは0.1.0、Consulのバージョンは0.5.0の時点で書き始めたが、Vault 0.1.2に対応するよう書き直している。

# Vaultサーバを起動する

Vaultのインストール。

```
# wget https://dl.bintray.com/mitchellh/vault/vault_0.1.2_linux_amd64.zip
# unzip vault_0.1.2_linux_amd64.zip -d /usr/local/sbin
```

Vaultが使う8200/tcpのファイアウォールを開けておく。
CentOS 6でiptablesを使っている場合だと、

```
# iptables -I INPUT 5 -m state --state NEW -p tcp --dport 8200 -j ACCEPT
# service iptables save
```

CentOS 7でfirewalldを使っている場合だと、

```
# firewall-cmd --add-port=8200/tcp
# firewall-cmd --add-port=8200/tcp --permanent
```

設定ファイルを置くディレクトリを作り、設定ファイル /etc/vault.d/server.hcl を作成する。
次はHTTP接続とする場合である。HTTPS接続とする場合については後述する。

```
# mkdir /etc/vault.d
```

```/etc/vault.d/server.hcl
backend "consul" {
  advertise_addr = "http://10.0.2.167:8200"
}

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

設定ファイルの中身を軽く説明すると、backend "consul" {}の中については、

* advertise_addrは他のVaultからアクセスする際の自分のアドレス。
Consulをバックエンドにする場合（つまり、HA構成にする場合）には必須。v0.1.0ではこの項目にはホスト名かIPアドレスだけを記述していたが、v0.1.1からVaultクライアントが自動で接続先を切り替える際のアドレスとして参照されるため、`http://10.0.2.167:8200`のような記述をする必要がある。
* addressでConsulのアドレスとポートを指定できるのだが、省略時はlocalhost:8500である。
つまり自分がConsulのサーバかクライアントになっており、わざわざポート番号を変えていなければ省略できる。

listener "tcp" {}の中については、

* addressはVaultの待ち受けアドレスとポート。
* tls_disableは値が空でない場合、HTTP接続にする。

この前[ConsulのCentOS 6で動作確認したinitスクリプトとCentOS 7で動作確認したsystemdユニットファイルを作成した](http://qiita.com/yunano/items/7ef5fa5670721de55627)ので、似たような感じでVaultのものも作ってみた。

[CentOS 6用のinitスクリプト](https://gist.github.com/yunano/c4e641bad3fb9f78d6cf)は /etc/init.d/vault に配置して、

```
# chmod 755 /etc/init.d/vault
# chkconfig --add vault
# service vault start
```

[CentOS 7用のsystemdユニットファイル](https://gist.github.com/yunano/66caf8c5ed993bb2f4e3)は /etc/systemd/system/vault.service に配置して、

```
# systemctl daemon-reload
# systemctl enable vault.service
# systemctl start vault.service
```

これでVaultサーバが起動した。
ただし後述するが、2台目以降を起動するのは`vault init`を実行した後でなければならない。

## HTTPS接続にする場合

Vault 0.1.0だとHTTPS接続が上手く行かなかったが、0.1.1ではほぼ問題なくできるようになった。
（CentOS 6で`vault read`が上手く行かないことがあった。何がトリガーになって解決したのかは実際にはわからないが、私の環境ではOS再起動後に問題が解消した）
なおHTTPS接続にすると、ログにTLS handshake errorなんてエラーが5秒ごとに(サーバ台数+1)だけ出力されるのは気になる。実ファイルに書き出しているならログローテートが必要になる程度にはなる。

HTTPS接続にする場合、追加作業がいくらか必要になる。
Vaultサーバ、VaultクライアントともにConsulサーバかクライアントであるものとする。

まず証明書のコモンネームにホスト名が使えるよう、Vaultサーバ、VaultクライアントがConsulが提供するDNSサーバに問い合わせできるようにする。

Consulは8600/udpでDNSサーバを提供しているので、localhostの53/udpへの接続を8600/udpにリダイレクトするようにする。

CentOS 6だと次の通り。

```
# iptables -t nat -A OUTPUT -p udp -o lo --dport 53 -j REDIRECT --to-ports 8600
# service iptables save
```

CentOS 7だと次の通り。

```
# firewall-cmd --direct --add-rule ipv4 nat OUTPUT 1 -p udp -o lo --dport 53 -j REDIRECT --to-ports 8600
# firewall-cmd --permanent --direct --add-rule ipv4 nat OUTPUT 1 -p udp -o lo --dport 53 -j REDIRECT --to-ports 8600
```

CentOS 6だと /etc/sysconfig/network-scripts/ifcfg-eth0 に次を追加してから /etc/resolv.conf の既存のnameserverの上に`nameserver 127.0.0.1`を追加する。

```
DNS1=127.0.0.1
DNS2=<既存のDNSサーバ>
PEERDNS=no
```

CentOS 7だと /etc/resolv.conf を直接触らずに次を実行するのが良いかな。

```
# nmcli connection modify enp0s3 ipv4.ignore-auto-dns yes ipv4.dns "127.0.0.1 <既存のDNSサーバ>"
# systemctl restart NetworkManager.service
```

ifcfg-eth0(CentOS 6)やenp0s3(CentOS 7)は各自の環境に合わせる必要がある。

証明書なんてない、という場合は自己署名証明書を作る。
私が自己署名証明書を作る場合、次のような手順で作っている。

```
# mkdir /etc/pki/tls/self
# cd /etc/pki/tls/self
# cp /etc/pki/tls/openssl.cnf ./openssl.node.cnf
```

```/etc/pki/tls/self/openssl.node.cnf
# エディタなどで以下を修正する

[ CA_default ]
# 証明書の寿命を365日から3650日に変更
default_days = 3650

[ req ]
# attributes行をコメントアウトしてprompt = noを追加
#attributes = req_attributes
prompt = no

[ req_distinguished_name ]
# req_distinguished_nameの中は全部消してから以下を追加
C = JP
ST = Tokyo
L = Tokyo
O = consul
OU = admin
CN = *.node.consul
```

```
# sha512sum /proc/vmstat | awk '{ print $1 }' > sha512.dat
# openssl genrsa -rand sha512.dat -out node.key -passout pass:$(cat sha512.dat) -aes256 2048
# openssl rsa -in node.key -passin pass:$(cat sha512.dat) -out node.key
# openssl req -new -config openssl.node.cnf -key node.key -out node.csr
# openssl x509 -in node.csr -days 3650 -req -signkey node.key -out node.crt
# rm -f sha512.dat node.csr
```

作成された秘密鍵node.keyとサーバ証明書node.crtはすべてのVaultサーバに配置する。
node.crtはすべてのVaultクライアントにも配置する。

Vaultの設定ファイル /etc/vault.d/server.hcl は次のようになる。

```/etc/vault.d/server.hcl
backend "consul" {
  advertise_addr = "https://cc6.node.consul:8200"
}

listener "tcp" {
  address = "0.0.0.0:8200"
  tls_cert_file = "/etc/pki/tls/self/node.crt"
  tls_key_file = "/etc/pki/tls/self/node.key"
}
```

ConsulをDNSサーバとして参照することにより、`<ホスト名>.node.consul`で自分にアクセスできるようになっているので、advertise_addrにはこの名前を使って設定する。証明書のコモンネーム(CN)に`*.node.consul`を設定したのはこのためである。
tls_cert_fileにはサーバ証明書へのパス、tls_key_fileには秘密鍵へのパスを設定する。

他の作業はHTTP接続の場合と変わらない。

# Vaultクライアントからの操作

今回Vaultはサーバとクライアントを兼ねているので、HTTP接続の場合は2台とも環境変数VAULT_ADDRにhttpでループバックアドレスを指定しておく。
サーバとクライアントが違うコンピュータならばVaultサーバのアドレスを指定すれば良い。

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

HTTPS接続の場合は`https://<自分のマシン名>.node.consul:8200`を指定する。

```
# export VAULT_ADDR='https://cc6.node.consul:8200'
```

またHTTPS接続の場合、以降のvaultコマンドではサーバ証明書へのパスを指定するオプション`-ca-cert /etc/pki/tls/self/node.crt`を指定する（例：`vault read -ca-cert /etc/pki/tls/self/node.crt secret/test`）。

あるいは作成したサーバ証明書を信頼する証明書に加える。

```
# update-ca-trust enable # CentOS 7では初期状態でenableになっているので実行不要
# ln -s /etc/pki/tls/self/node.crt /etc/pki/ca-trust/source/anchors/
# update-ca-trust extract
```

まずはVaultの初期化。
これを実行すると5つのKey、およびInitial Root Tokenが表示される。
これらをなくすとアクセスできなくなるので注意。
発行されるKeyの数は-key-sharesオプションで変更できる。
この作業はVaultのHA構成クラスタの中で1回だけ行えば良い。

```
# vault init
```

なお、VaultをHA構成にする場合は「1台目の起動→vault init→2台目以降の起動」の順で行う。
共有データは`vault init`の際に作成されるので、その前に2台目以降を起動してもHA構成にならないからである。

Vaultサーバを受け付け可能な状態にするにはunsealという作業を行う。
次の通り3回実行し、vault initで得たKeyのうち3つを入力すると受け付け可能状態になる。
この回数はvault init時の-key-thresholdオプションで変更できる。
この作業はVaultサーバ1台ごとに実行する必要があり、またVaultサーバが一旦停止して再起動した場合にも実行する必要がある。

```
# vault unseal
# vault unseal
# vault unseal
```

vault unsealは引数なしで実行するとKeyを対話式で入力することになる。
Keyを引数にして実行することもできるのでスクリプト内で実行する場合などはそうすると良い。

Vaultクライアントの認証はvault initで得たInitial Root Tokenを使って行う。
これはクライアントごとに行う。
期限とかありそうだけどまだ調べていない。

```
# vault auth 8c535adc-f7c7-32d7-09ae-14c32da1e6b6
```

vault authの実行時にホームディレクトリの .vault-token というファイルにTokenが書き込まれて、以降のvaultクライアント実行時にこのファイルを参照することで毎回のTokenの入力を不要にする仕組みのようだ。

（※ ここからv0.1.0の時の記述である。v0.1.1では接続先のvaultサーバがunsealされていればvaultクライアントが自動でactiveの方へ繋ぎに行くようになったので以下に記述するようなことは起こらない。もしstandby側に繋げられれば同じことが起こると推測されるので参考までに残しておく。要約すればHA構成の時は1台だけactiveで他はstandbyになる、ということである）

しかし2台目(10.0.2.170)でvault authを実行すると`Error validating token: Get https:///v1/auth/token/lookup-self: http: no Host in request URL`というエラーになる。

2台目でvault statusを実行してみると以下のように表示される。

```
# vault status
Sealed: false
Key Shares: 5
Key Threshold: 3
Unseal Progress: 0

High-Availability Enabled: true
        Mode: standby
        Leader: 10.0.2.167
```

ああ、つまり2台目はstandbyということか。
[ドキュメント](http://vaultproject.io/docs/internals/high-availability.html)にもactiveなのは1台だけ、と書いてあるのは後で気付いた。
1台目ではvault statusで次のように表示される。

```
# vault status
Sealed: false
Key Shares: 5
Key Threshold: 3
Unseal Progress: 0

High-Availability Enabled: true
        Mode: active
        Leader: 10.0.2.167
```

というわけで1台目でVaultに書き込んでから落としてみる。

```
# vault write secret/test aaa=bbb
# service vault stop
```

40秒程度経ってから2台目に切り替わっていることが確認できた。

```
# vault status
Sealed: false
Key Shares: 5
Key Threshold: 3
Unseal Progress: 0

High-Availability Enabled: true
        Mode: active
        Leader: 10.0.2.170
```

2台目でvault authも通るようになっているし、先程Vaultに保存した情報も読めるようになっている。

```
# vault auth 8c535adc-f7c7-32d7-09ae-14c32da1e6b6
Successfully authenticated! The policies that are associated
with this token are listed below:

root
```

```
# vault read secret/test
Key             Value
lease_id        secret/test/71574acd-0ccf-238f-5c64-f3ad8d39becb
lease_duration  2592000
aaa             bbb
```

（※ v0.1.0の時の記述ここまで）

VaultはConsulに切り替えのための問い合わせをしているようで、8200/tcpについてファイアウォールが2台間で開いてなくても切り替えは可能であった。

# データはConsulのどこに保存されているのか

Consulのキーバリューストアのvaultというディレクトリに保存されている。
このvaultという名前は設定で変更可能である（backend "consul"のpathオプション）。

v0.1.1まではcore/lockというキーもできていたがバグだったようで、v0.1.2からvaultディレクトリ内に書かれるようになった(vault/core/lock)。

# Vaultサーバのactive側への自動切り替え

以下ではHAProxyを使う方法とConsulを使う方法を記述する。
現在は（後から気付いた）Consulを使う方法を使用している。

## HAProxyによる自動切り替え

（※ この章の記述はHTTPS接続の箇所を除きv0.1.0当時のものそのままなので、現在とは一部出力内容が異なる箇所がある）

VaultがHA構成を取るとactive/standbyになることがわかった。
そのため、Vaultサーバにアクセスするためには、常にactive側にアクセスさせる仕組みが別途必要になる。
（v0.1.1では接続先のVaultサーバがstandbyでも自動でactive側に接続し直してくれるが、起動していなかったりsealされていたりするとそうはいかない）
そこでHAProxyを使うことにする。

### HAProxyの準備

HAProxyをVaultサーバ側に入れるとHAProxyの冗長化とか考えなければならないので、ここではそれを省略すべくVaultクライアント側に入れることとする。
（まあ実際には、この資料ではVaultサーバとVaultクライアントは同じコンピュータになってるけど、別々にしているとみなすものとして）

HAProxyとsocatをインストール。
CentOS 6だとsocatはEPELにあるので、次より先に`yum install epel-release`を実行する。

```
# yum install haproxy socat
```

CentOS 7でSELinuxが有効だとHAProxyがポートをbindできない（`systemctl status haproxy.service -l`で`cannot bind socket`というメッセージが出力されているのがわかる）のでHAProxy起動前に次を実行する。

```
# setsebool -P haproxy_connect_any on
```

HAProxyの設定ファイル /etc/haproxy/haproxy.cfg を次のような内容で作る。
次はHTTP接続とする場合である。HTTPS接続とする場合については後述する。
なお、global内とdefaults内は初期の /etc/haproxy/haproxy.cfg と同じ設定内容であり、新しく追加したのはlistenの箇所だけである。

```txt:/etc/haproxy/haproxy.cfg
global
    log         127.0.0.1 local2
    chroot      /var/lib/haproxy
    pidfile     /var/run/haproxy.pid
    maxconn     4000
    user        haproxy
    group       haproxy
    daemon
    stats socket /var/lib/haproxy/stats

defaults
    mode                    http
    log                     global
    option                  httplog
    option                  dontlognull
    option http-server-close
    option forwardfor       except 127.0.0.0/8
    option                  redispatch
    retries                 3
    timeout http-request    10s
    timeout queue           1m
    timeout connect         10s
    timeout client          1m
    timeout server          1m
    timeout http-keep-alive 10s
    timeout check           10s
    maxconn                 3000

listen vault
    bind 0.0.0.0:8210
    option httpchk GET /v1/sys/health HTTP/1.1
    server cc6 10.0.2.167:8200 check inter 5s fall 2 rise 1
    server cc7 10.0.2.170:8200 check inter 5s fall 2 rise 1
```

bindするポートは、この環境ではVaultサーバと同居しているので8200が使えないため8210としてみた。使える番号なら何でも良いだろう。
ヘルスチェック対象のURLは[/v1/sys/health](http://vaultproject.io/docs/http/sys-health.html)が良さそうだったのでこれを選んだ。
これはactiveならステータスコード200を、standbyなら429を、sealなら500を返す。
（seal状態のVaultサーバはactiveにもstandbyにもなれない）
なんでstandbyは429(Too Many Requests)なんだろう？ というのはあるが。

CentOS 6だと次でHAProxyを起動。

```
# chkconfig haproxy on
# service haproxy start
```

CentOS 7だと次でHAProxyを起動。

```
# systemctl enable haproxy.service
# systemctl start haproxy.service
```

環境変数VAULT_ADDRを変更して接続先をHAProxyが開けているポートに変える。

```
# export VAULT_ADDR='http://127.0.0.1:8210'
```

vault writeとvault readをやってみる。
問題ない。

```
# vault write secret/test2 k1=v1
Success! Data written to: secret/test2

# vault read secret/test2
Key             Value
lease_id        secret/test2/cd7fd737-2d2c-1ca6-739f-4c54c8e7649b
lease_duration  2592000
k1              v1
```

socatでHAProxyのstatを確認する。
（1つ目の#はプロンプトだけど、2つ目の#はただの出力。socatのstatについて以下同じ）

```
# echo "show stat" | socat stdio /var/lib/haproxy/stats
# pxname,svname,qcur,qmax,scur,smax,slim,stot,bin,bout,dreq,dresp,ereq,econ,eresp,wretr,wredis,status,weight,act,bck,chkfail,chkdown,lastchg,downtime,qlimit,pid,iid,sid,throttle,lbtot,tracked,type,rate,rate_lim,rate_max,check_status,check_code,check_duration,hrsp_1xx,hrsp_2xx,hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,req_rate,req_rate_max,req_tot,cli_abrt,srv_abrt,comp_in,comp_out,comp_byp,comp_rsp,lastsess,last_chk,last_agt,qtime,ctime,rtime,ttime,
vault,FRONTEND,,,0,1,3000,3,520,650,0,0,0,,,,,OPEN,,,,,,,,,1,2,0,,,,0,0,0,1,,,,0,3,0,0,0,0,,0,1,3,,,0,0,0,0,,,,,,,,
vault,cc6,0,0,0,0,,0,0,0,,0,,0,0,0,0,DOWN,1,1,0,2,1,447,447,,1,2,1,,0,,2,0,,0,L7STS,429,0,0,0,0,0,0,0,0,,,,0,0,,,,,-1,Too Many Requests,,0,0,0,0,
vault,cc7,0,0,0,1,,3,520,650,,0,,0,0,0,0,UP,1,1,0,1,1,409,1311,,1,2,2,,3,,2,0,,1,L7OK,200,0,0,3,0,0,0,0,0,,,,0,0,,,,,134,OK,,0,0,1,1,
vault,BACKEND,0,0,0,1,300,3,520,650,0,0,,0,0,0,0,UP,1,1,0,,1,409,38,,1,2,0,,3,,1,0,,1,,,,0,3,0,0,0,0,,,,,0,0,0,0,0,0,134,,,0,0,1,1,
```

1台目(cc6)のステータスコードが429、2台目(cc7)のステータスコードが200になっているので1台目がstandby、2台目がactiveである。

vault statusでも2台目にアクセスしていることがわかる。

```
# vault status
Sealed: false
Key Shares: 5
Key Threshold: 3
Unseal Progress: 0

High-Availability Enabled: true
        Mode: active
        Leader: 10.0.2.170
```

### 切り替えテスト

それでは2台目のVaultサーバを落としてみる。

```
# systemctl stop vault.service
```

先にVaultサーバの切り替えには40秒ほど掛かると書いた。
すぐにvault readしてみてもアクセスできない。

```
# vault read secret/test2
Error reading secret/test: Error making API request.

URL: GET http://127.0.0.1:8210/v1/secret/test2
Code: 503. Raw Message:

<html><body><h1>503 Service Unavailable</h1>
No server is available to handle this request.
</body></html>
```

この時点でsocatでHAProxyのステータスを見ると1台目、2台目ともにDOWNになっている。

```
# echo "show stat" | socat stdio /var/lib/haproxy/stats
# pxname,svname,qcur,qmax,scur,smax,slim,stot,bin,bout,dreq,dresp,ereq,econ,eresp,wretr,wredis,status,weight,act,bck,chkfail,chkdown,lastchg,downtime,qlimit,pid,iid,sid,throttle,lbtot,tracked,type,rate,rate_lim,rate_max,check_status,check_code,check_duration,hrsp_1xx,hrsp_2xx,hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,req_rate,req_rate_max,req_tot,cli_abrt,srv_abrt,comp_in,comp_out,comp_byp,comp_rsp,lastsess,last_chk,last_agt,qtime,ctime,rtime,ttime,
vault,FRONTEND,,,0,1,3000,2,324,480,0,0,0,,,,,OPEN,,,,,,,,,1,2,0,,,,0,0,0,1,,,,0,1,0,0,1,0,,0,1,2,,,0,0,0,0,,,,,,,,
vault,cc6,0,0,0,0,,0,0,0,,0,,0,0,0,0,DOWN,1,1,0,2,1,665,665,,1,2,1,,0,,2,0,,0,L7STS,429,0,0,0,0,0,0,0,0,,,,0,0,,,,,-1,Too Many Requests,,0,0,0,0,
vault,cc7,0,0,0,1,,5,324,480,,0,,1,0,3,0,DOWN,1,1,0,3,2,2,808,,1,2,2,,2,,2,0,,1,L4CON,,0,0,1,0,0,0,0,0,,,,0,0,,,,,4,Connection refused,,0,0,1,1,
vault,BACKEND,0,0,0,1,300,2,324,480,0,0,,1,0,3,0,DOWN,0,0,0,,2,2,40,,1,2,0,,2,,1,0,,1,,,,0,1,0,0,1,0,,,,,0,0,0,0,0,0,4,,,0,0,1,1,
```

しばらくしてからもう一度socatでHAProxyのステータスを見ると1台目の方がUPになっている。
Vaultサーバ側の切り替えが終わったようだ。

```
# echo "show stat" | socat stdio /var/lib/haproxy/stats
# pxname,svname,qcur,qmax,scur,smax,slim,stot,bin,bout,dreq,dresp,ereq,econ,eresp,wretr,wredis,status,weight,act,bck,chkfail,chkdown,lastchg,downtime,qlimit,pid,iid,sid,throttle,lbtot,tracked,type,rate,rate_lim,rate_max,check_status,check_code,check_duration,hrsp_1xx,hrsp_2xx,hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,req_rate,req_rate_max,req_tot,cli_abrt,srv_abrt,comp_in,comp_out,comp_byp,comp_rsp,lastsess,last_chk,last_agt,qtime,ctime,rtime,ttime,
vault,FRONTEND,,,0,1,3000,3,487,747,0,0,0,,,,,OPEN,,,,,,,,,1,2,0,,,,0,0,0,1,,,,0,2,0,0,1,0,,0,1,3,,,0,0,0,0,,,,,,,,
vault,cc6,0,0,0,1,,1,163,267,,0,,0,0,0,0,UP,1,1,0,2,1,147,695,,1,2,1,,1,,2,0,,1,L7OK,200,0,0,1,0,0,0,0,0,,,,0,0,,,,,107,OK,,0,0,1,1,
vault,cc7,0,0,0,1,,5,324,480,,0,,1,0,3,0,DOWN,1,1,0,3,2,179,985,,1,2,2,,2,,2,0,,1,L4CON,,0,0,1,0,0,0,0,0,,,,0,0,,,,,181,Connection refused,,0,0,1,1,
vault,BACKEND,0,0,0,1,300,3,487,747,0,0,,1,0,3,0,UP,1,1,0,,2,147,70,,1,2,0,,3,,1,0,,1,,,,0,2,0,0,1,0,,,,,0,0,0,0,0,0,107,,,0,0,1,1,
```

こうなればvault readが通る。

```
# vault read secret/test2
Key             Value
lease_id        secret/test2/75a4687d-3fe0-e8f2-2108-d34b28bd67a7
lease_duration  2592000
k1              v1
```

2台目の方のVaultサーバをもう一度起動する。

```
# systemctl start vault.service
```

2台目のステータスコードが500なのでsealされていることがわかる。

```
# echo "show stat" | socat stdio /var/lib/haproxy/stats
# pxname,svname,qcur,qmax,scur,smax,slim,stot,bin,bout,dreq,dresp,ereq,econ,eresp,wretr,wredis,status,weight,act,bck,chkfail,chkdown,lastchg,downtime,qlimit,pid,iid,sid,throttle,lbtot,tracked,type,rate,rate_lim,rate_max,check_status,check_code,check_duration,hrsp_1xx,hrsp_2xx,hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,req_rate,req_rate_max,req_tot,cli_abrt,srv_abrt,comp_in,comp_out,comp_byp,comp_rsp,lastsess,last_chk,last_agt,qtime,ctime,rtime,ttime,
vault,FRONTEND,,,0,1,3000,4,650,1014,0,0,0,,,,,OPEN,,,,,,,,,1,2,0,,,,0,0,0,1,,,,0,3,0,0,1,0,,0,1,4,,,0,0,0,0,,,,,,,,
vault,cc6,0,0,0,1,,2,326,534,,0,,0,0,0,0,UP,1,1,0,2,1,355,695,,1,2,1,,2,,2,0,,1,L7OK,200,0,0,2,0,0,0,0,0,,,,0,0,,,,,126,OK,,0,0,1,1,
vault,cc7,0,0,0,1,,5,324,480,,0,,1,0,3,0,DOWN,1,1,0,3,2,387,1193,,1,2,2,,2,,2,0,,1,L7STS,500,0,0,1,0,0,0,0,0,,,,0,0,,,,,389,Internal Server Error,,0,0,1,1,
vault,BACKEND,0,0,0,1,300,4,650,1014,0,0,,1,0,3,0,UP,1,1,0,,2,355,70,,1,2,0,,4,,1,0,,1,,,,0,3,0,0,1,0,,,,,0,0,0,0,0,0,126,,,0,0,1,1,
```

2台目はvault unsealすると再度使用可能な状態になる。

```
# vault unseal -address http://10.0.2.170:8200
# vault unseal -address http://10.0.2.170:8200
# vault unseal -address http://10.0.2.170:8200
```

2台目のスタータスコードが429に変化したのでstandbyになっていることがわかる。

```
# echo "show stat" | socat stdio /var/lib/haproxy/stats
# pxname,svname,qcur,qmax,scur,smax,slim,stot,bin,bout,dreq,dresp,ereq,econ,eresp,wretr,wredis,status,weight,act,bck,chkfail,chkdown,lastchg,downtime,qlimit,pid,iid,sid,throttle,lbtot,tracked,type,rate,rate_lim,rate_max,check_status,check_code,check_duration,hrsp_1xx,hrsp_2xx,hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,req_rate,req_rate_max,req_tot,cli_abrt,srv_abrt,comp_in,comp_out,comp_byp,comp_rsp,lastsess,last_chk,last_agt,qtime,ctime,rtime,ttime,
vault,FRONTEND,,,0,1,3000,3,520,650,0,0,0,,,,,OPEN,,,,,,,,,1,2,0,,,,0,0,0,1,,,,0,3,0,0,0,0,,0,1,3,,,0,0,0,0,,,,,,,,
vault,cc6,0,0,0,0,,0,0,0,,0,,0,0,0,0,UP,1,1,0,2,1,477,696,,1,2,1,,0,,2,0,,0,L7OK,200,0,0,0,0,0,0,0,0,,,,0,0,,,,,-1,OK,,0,0,0,0,
vault,cc7,0,0,0,1,,3,520,650,,0,,0,0,0,0,DOWN,1,1,0,3,2,515,1826,,1,2,2,,3,,2,0,,1,L7STS,429,0,0,3,0,0,0,0,0,,,,0,0,,,,,860,Too Many Requests,,0,0,1,1,
vault,BACKEND,0,0,0,1,300,3,520,650,0,0,,0,0,0,0,UP,1,1,0,,2,477,76,,1,2,0,,3,,1,0,,1,,,,0,3,0,0,0,0,,,,,0,0,0,0,0,0,860,,,0,0,1,1,
```

vault statusで1台目にアクセスしていることを確認した。

```
# vault status
Sealed: false
Key Shares: 5
Key Threshold: 3
Unseal Progress: 0

High-Availability Enabled: true
        Mode: active
        Leader: 10.0.2.167
```

### HTTPS接続にする場合

HTTPS接続だとHAProxyの設定のうち`mode http`を`mode tcp`に変更しなければならず、それに伴い`option httpchk`が使用できないので特定のURLに対してヘルスチェックを掛けることができない（と、思う。代わりになるものを見つけられていないだけかもしれない）。
従ってVaultサーバが起動しているかしていないかのチェックはできても、active/standby/sealのうちどの状態になっているかのチェックができない。
とはいえactiveかstandbyなら自動接続切り替えが働くので、これらを区別する必要は特にない。問題はsealの場合にどうすれば良いかである。

とりあえずサービス起動時にunsealまで行うように[initスクリプト](https://gist.github.com/yunano/c4e641bad3fb9f78d6cf)/[systemdユニットファイル](https://gist.github.com/yunano/66caf8c5ed993bb2f4e3)を改良して、seal状態の期間をなくす方向で回避してみる。

initスクリプトを使う場合は /etc/sysconfig/vault に次のように記述する。
VAULT_ADDRはHAProxyが開けているポートではなく、Vaultサーバ自体が開けているポートにする。
KEYSはスペース区切りで。

```/etc/sysconfig/vault
export VAULT_ADDR='https://cc6.node.consul:8200'
export KEYS="52d78d93109a47356a169e8cf2ee4868dc40de7fdd9b1c1d0c68255a8f691c0501 514f788ab44d9805577a074ce7f457891d59880a6a05b38b0acadaacdc330dbb02 b26ceda206536f317e375ae55fff849b0d3c9758f74b920d3d512122211f518b03"
export CERT='-ca-cert /etc/pki/tls/self/node.crt'
```

systemdユニットファイルを使用する場合はexportは書かない。

```/etc/sysconfig/vault
VAULT_ADDR='https://cc7.node.consul:8200'
KEYS="52d78d93109a47356a169e8cf2ee4868dc40de7fdd9b1c1d0c68255a8f691c0501 514f788ab44d9805577a074ce7f457891d59880a6a05b38b0acadaacdc330dbb02 b26ceda206536f317e375ae55fff849b0d3c9758f74b920d3d512122211f518b03"
CERT='-ca-cert /etc/pki/tls/self/node.crt'
```

HAProxyの設定ファイル /etc/haproxy/haproxy.cfg は以下のようにする。

```txt:/etc/haproxy/haproxy.cfg
global
    log         127.0.0.1 local2
    chroot      /var/lib/haproxy
    pidfile     /var/run/haproxy.pid
    maxconn     4000
    user        haproxy
    group       haproxy
    daemon
    stats socket /var/lib/haproxy/stats

defaults
    mode                    http
    log                     global
    option                  httplog
    option                  dontlognull
    option http-server-close
    option forwardfor       except 127.0.0.0/8
    option                  redispatch
    retries                 3
    timeout http-request    10s
    timeout queue           1m
    timeout connect         10s
    timeout client          1m
    timeout server          1m
    timeout http-keep-alive 10s
    timeout check           10s
    maxconn                 3000

listen vault
    mode tcp
    bind 0.0.0.0:8210
    option ssl-hello-chk
    server cc6 10.0.2.167:8200 check inter 5s fall 2 rise 1
    server cc7 10.0.2.170:8200 check inter 5s fall 2 rise 1
```

環境変数VAULT_ADDRを変更して接続先をHAProxyが開けているポートに変える。

```
# export VAULT_ADDR='https://cc6.node.consul:8210'
```

他の作業はHTTP接続の場合と変わらない。

## Consulによる自動切り替え

よくよく調べてみればHAProxyを使わなくてもConsulで自動切り替えが可能である。

HTTPS接続で自己署名証明書が必要ならコモンネームが「*.service.consul」の証明書を作成する。

```
# mkdir /etc/pki/tls/self
# cd /etc/pki/tls/self
# cp /etc/pki/tls/openssl.cnf ./openssl.service.cnf
```

```/etc/pki/tls/self/openssl.service.cnf
# エディタなどで以下を修正する

[ CA_default ]
# 証明書の寿命を365日から3650日に変更
default_days = 3650

[ req ]
# attributes行をコメントアウトしてprompt = noを追加
#attributes = req_attributes
prompt = no

[ req_distinguished_name ]
# req_distinguished_nameの中は全部消してから以下を追加
C = JP
ST = Tokyo
L = Tokyo
O = consul
OU = admin
CN = *.service.consul
```

```
# sha512sum /proc/vmstat | awk '{ print $1 }' > sha512.dat
# openssl genrsa -rand sha512.dat -out service.key -passout pass:$(cat sha512.dat) -aes256 2048
# openssl rsa -in service.key -passin pass:$(cat sha512.dat) -out service.key
# openssl req -new -config openssl.service.cnf -key service.key -out service.csr
# openssl x509 -in service.csr -days 3650 -req -signkey service.key -out service.crt
# rm -f sha512.dat service.csr
```

作成された秘密鍵service.keyとサーバ証明書service.crtはすべてのVaultサーバに配置する。
service.crtはすべてのVaultクライアントにも配置する。

すべてのVaultクライアントで作成したサーバ証明書を信頼する証明書に加える。

```
# update-ca-trust enable # CentOS 7では初期状態でenableになっているので実行不要
# ln -s /etc/pki/tls/self/service.crt /etc/pki/ca-trust/source/anchors/
# update-ca-trust extract
```

Consul側で自分自身しか指さない適当なサービスと、Vaultというサービスを定義する設定ファイル /etc/consul.d/services.json を作って、Consulをこれを読み込むようにして起動し直す。

「自分自身しか指さない適当なサービス」はHTTPS接続時に証明書のコモンネーム「*.service.consul」に一致する、自分自身だけを指すホスト名を作るために必要である。次の例だと「cc6.service.consul」で自分自身を指すことになる。各Vaultサーバでnameをそれぞれ違う値にする必要がある。

「Vaultというサービス」ではchecksのhttpを`https://<ホスト名>:8200/v1/sys/health`とするが、ホスト名はこの自分自身だけを指すホスト名にする。HTTP接続ならhttps://でなくhttp://にする。

```/etc/consul.d/services.json
{
  "services": [
    {
      "name": "cc6"
    },
    {
      "name": "vault",
      "port": 8200,
      "checks": [
        {
          "http": "https://cc6.service.consul:8200/v1/sys/health",
          "interval": "5s"
        }
      ]
    }
  ]
}
```

Vaultサーバの設定ファイル /etc/vault.d/server.hcl は次のようになる。
advertise_addrも同じようにその自分自身だけを指すホスト名を使って指定する。
HTTP接続ならadvertise_addrはhttps://でなくhttp://にし、tls_cert_fileやtls_key_fileは指定せず`tls_disable = 1`とする。

```/etc/vault.d/server.hcl
backend "consul" {
  advertise_addr = "https://cc6.service.consul:8200"
}

listener "tcp" {
  address = "0.0.0.0:8200"
  tls_cert_file = "/etc/pki/tls/self/service.crt"
  tls_key_file = "/etc/pki/tls/self/service.key"
}
```

この方法だとacitveかstandbyかsealかの区別がつくためサービス起動時に自動的にunsealする必要はないが、手動でunsealするのが面倒なら /etc/sysconfig/vault を次のように作成し、[initスクリプト](https://gist.github.com/yunano/c4e641bad3fb9f78d6cf)や[systemdユニットファイル](https://gist.github.com/yunano/66caf8c5ed993bb2f4e3)を利用してVaultサーバを起動する。

initスクリプトを使う場合。

```/etc/sysconfig/vault
export VAULT_ADDR='https://cc6.service.consul:8200'
export KEYS="52d78d93109a47356a169e8cf2ee4868dc40de7fdd9b1c1d0c68255a8f691c0501 514f788ab44d9805577a074ce7f457891d59880a6a05b38b0acadaacdc330dbb02 b26ceda206536f317e375ae55fff849b0d3c9758f74b920d3d512122211f518b03"
export CERT='-ca-cert /etc/pki/tls/self/service.crt'
```

systemdユニットファイルを使う場合。exportが不要。

```/etc/sysconfig/vault
VAULT_ADDR='https://cc7.service.consul:8200'
KEYS="52d78d93109a47356a169e8cf2ee4868dc40de7fdd9b1c1d0c68255a8f691c0501 514f788ab44d9805577a074ce7f457891d59880a6a05b38b0acadaacdc330dbb02 b26ceda206536f317e375ae55fff849b0d3c9758f74b920d3d512122211f518b03"
CERT='-ca-cert /etc/pki/tls/self/service.crt'
```

Vaultクライアントでは環境変数VAULT_ADDRに`http(s)://vault.service.consul:8200`を指定する。
vault.service.consulはactiveかstandbyのVaultサーバだけでのラウンドロビンとなり、sealや起動していない状態だとラウンドロビンから自動的に外れるので、完全に期待通りの動きをしてくれる。

```
# export VAULT_ADDR='https://vault.service.consul:8200'
```

# 監査ログの出力

vault audit-enableで監査ログを書き出すようにすることができる。
今のところ出力先はファイルかsyslogになる。

VaultがHA構成になっている場合、vault audit-enableはクラスタの中で1回だけ行えばactiveになっているノードの出力先に書き出される。

## ファイルへの出力

Vault 0.1.1まではVault側では監査ログファイルを作成する能力がなく、先にそのファイルを作ってからvault audit-enableしなければならなかった。
Vault 0.1.2から、書き込み時にファイルがなければ作ってくれるようになった。

```
# vault audit-enable file path=/var/log/vault_audit.log
```

`vault write secret/test a=b`と`vault read secret/test`を実行したところ、以下のような監査ログが出力された（v0.1.1時のもの）。

```/var/log/vault_audit.log
{"type":"request","auth":{"policies":["root"],"metadata":null},"request":{"operation":"write","path":"secret/test","data":{"a":"sha1:e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98"}}}
{"type":"response","error":"","auth":{"policies":["root"],"metadata":null},"request":{"operation":"write","path":"secret/test","data":{"a":"sha1:e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98"}},"response":{"auth":{"policies":null,"metadata":null},"secret":{"lease_id":""},"data":null,"redirect":""}}
{"type":"request","auth":{"policies":["root"],"metadata":null},"request":{"operation":"read","path":"secret/test","data":null}}
{"type":"response","error":"","auth":{"policies":["root"],"metadata":null},"request":{"operation":"read","path":"secret/test","data":null},"response":{"auth":{"policies":null,"metadata":null},"secret":{"lease_id":"secret/test/b0f8048a-336c-d326-5b19-d7f5997d500f"},"data":{"a":"sha1:e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98"},"redirect":""}}
```

ファイルへの書き込みを終了するには次を実行する。

```
# vault audit-disable file
```

`file`は書き込み先がファイルで、vault audit-enableのidオプションでIDを指定しなかった場合のデフォルトIDである（`/var/log/vault_audit.log`に置き換えるとかそういうのではない）。

## syslogへの出力

syslogを出力先に使用する場合は、Vaultはオプションの指定がなければファシリティAUTH、タグvaultに書き出すので /etc/rsyslog.d/vault.conf を次のように作成し、rsyslogを再起動する。
/var/log/vault_audit.log はrsyslogを再起動した時点で作成されるが、その後に削除されるとrsyslogをもう一度再起動するまで出力は行われない。

```/etc/rsyslog.d/vault.conf
:syslogtag, contains, "vault" /var/log/vault_audit.log
```

```
# service rsyslog restart
```

Vaultからsyslogへの監査ログ書き出しを有効化する。

```
# vault audit-enable syslog
```

同じように`vault write secret/test a=b`と`vault read secret/test`を実行したところ、以下のような監査ログが出力された（v0.1.1時のもの）。

```/var/log/vault_audit.log
May  3 13:48:26 cc7 vault[9090]: {"type":"request","auth":{"policies":["root"],"metadata":null},"request":{"operation":"write","path":"secret/test","data":{"a":"sha1:e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98"}}}
May  3 13:48:26 cc7 vault[9090]: {"type":"response","error":"","auth":{"policies":["root"],"metadata":null},"request":{"operation":"write","path":"secret/test","data":{"a":"sha1:e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98"}},"response":{"auth":{"policies":null,"metadata":null},"secret":{"lease_id":""},"data":null,"redirect":""}}
May  3 13:48:35 cc7 vault[9090]: {"type":"request","auth":{"policies":["root"],"metadata":null},"request":{"operation":"read","path":"secret/test","data":null}}
May  3 13:48:35 cc7 vault[9090]: {"type":"response","error":"","auth":{"policies":["root"],"metadata":null},"request":{"operation":"read","path":"secret/test","data":null},"response":{"auth":{"policies":null,"metadata":null},"secret":{"lease_id":"secret/test/421e8b4d-11da-2e11-d15e-85cb1bd30cab"},"data":{"a":"sha1:e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98"},"redirect":""}}
```

syslogへの書き込みを終了するには次を実行する。

```
# vault audit-disable syslog
```

`syslog`は書き込み先がsyslogで、vault audit-enableのidオプションでIDを指定しなかった場合のデフォルトIDである。

# あやしい動作？

* vault revokeで接続先の自動切り替えが起こらない。vault mountsでも同様の現象が発生した。(-v0.1.2)

# 参考：CentOS 6でlibcurl.soを差し替える

上記のCentOS 6で`vault read`が上手く行かないことがあった際にlibcurl.soの差し替えで解決したことがあった。

**しかしlibcurl.soを差し替えずともOS再起動で解決してしまったので、現在この方法は採っていない。**
ただし、まだ1度しか解決は行われていないので、この後も同じようにOS再起動、あるいは別のもっと簡便な方法で解決するかは私の環境でも不明である。

CentOS 6でHTTPS接続時にvault readがエラーになったためソースからコンパイルした。
**ただし、この方法では今度はyumが使えなくなったのでコンパイルオプションの変更で対応できるか調査する必要がある。**

```
# mkdir /tmp/curl
# cd /tmp/curl
# wget http://curl.haxx.se/download/curl-7.42.1.tar.gz
# tar xvf curl-7.42.1.tar.gz
# cd curl-7.42.1/
# yum install gcc
# ./configure
# make
# make install
# cp /usr/local/lib/libcurl.so.4.3.0 /usr/lib64
# ln -sf /usr/lib64/libcurl.so.4.3.0 /usr/lib64/libcurl.so.4
```