目次
やりたいこと
機密情報管理に便利なHashicorp Vault。
Spring VaultやSpring Cloud ConfigのVault Backend、Cloud Foundry Vault Service Brokerなど、Vault関連のプロジェクトが増えていき、色々試したいが、実際にアプリケーションをデプロイした時にも接続できる場所にVaultを置いておきたい。
Cloud Foundryにできれば運用が楽になるので、cf push
でVaultをデプロイしてみた。
Vault自体はGoで書かれたHTTPサーバーなので基本的にはcf push
できる。ただし、そのままpushすると次の課題が出る。
- StorageがIn-Memoryなので再起動するとデータが消える
- Storageを永続化したとしても、再起動すると自動でSealされるので、Unsealしないといけない。
Storageはいくつか選択肢があるが、Cloud FoundryのサービスとしてもっともポピュラーなMySQLを使う。 MySQL StorageはHA非対応だし、Communityによる開発なので、Production用途ではないけれども、開発用途でCloud上に置いておくバックエンドとしては十分。
MySQLサービスインスタンスの作成
まずはvault-db
というMySQLのサービスインスタンスを作成する。cf login
している前提で説明する。
Pivotal Web Servicesの場合
Pivotal Web Servicesを使う場合は
cf create-service cleardb spark vault-db
Pivotal Cloud Foundryの場合
Pivotal Cloud Foundryを使う場合は
cf create-service p-mysql 100mb-dev vault-db
他のCloud Foundryサービスの場合でもOKだと思う。
Vaultのデプロイ
ではVaultをCloud Foundryにデプロイする。
Vaultのダウンロード
Vaultは実行可能バイナリとして配布されている。Download VaultのLinux 64-bitをダウンロードして展開すると実行可能なvault
ファイルが手に入る。Cloud Foundryのコンテナ上で動かすので、Linux用のバイナリにすること。
以降、空の/tmp/cf-vault
ディレクトリを作業ディレクトリとして進める。
mkdir -p /tmp/cf-vault
cd /tmp/cf-vault
wget https://releases.hashicorp.com/vault/1.3.2/vault_1.3.2_linux_amd64.zip
unzip vault_1.3.2_linux_amd64.zip
rm -f vault_1.3.2_linux_amd64.zip
起動スクリプトの作成
次にVaultの起動スクリプトrun.sh
を/tmp/cf-vault
に作成。ここで先ほど作成したMySQLサービスの接続情報を取得する。サービスインスタンスがアプリケーションにバインドされると環境変数VCAP_SERVICES
にJSON形式で接続情報が含まれるので、これjq
コマンドで加工する。
スクリプトは次のようになる。
#!/bin/sh
CLEARDB=`echo $VCAP_SERVICES | grep "cleardb"`
PMYSQL=`echo $VCAP_SERVICES | grep "p-mysql"`
if [ "$CLEARDB" != "" ];then
SERVICE="cleardb"
elif [ "$PMYSQL" != "" ]; then
SERVICE="p-mysql"
fi
echo "detected $SERVICE"
HOSTNAME=`echo $VCAP_SERVICES | jq -r '.["'$SERVICE'"][0].credentials.hostname'`
PASSWORD=`echo $VCAP_SERVICES | jq -r '.["'$SERVICE'"][0].credentials.password'`
PORT=`echo $VCAP_SERVICES | jq -r '.["'$SERVICE'"][0].credentials.port'`
USERNAME=`echo $VCAP_SERVICES | jq -r '.["'$SERVICE'"][0].credentials.username'`
DATABASE=`echo $VCAP_SERVICES | jq -r '.["'$SERVICE'"][0].credentials.name'`
cat <<EOF > cf.hcl
ui = true
disable_mlock = true
storage "mysql" {
username = "$USERNAME"
password = "$PASSWORD"
address = "$HOSTNAME:$PORT"
database = "$DATABASE"
table = "vault"
}
listener "tcp" {
address = "0.0.0.0:8080"
tls_disable = 1
}
EOF
echo "#### Starting Vault..."
./vault server -config=cf.hcl &
実行権限をつけておくこと。
chmod +x run.sh
この記事で想定していないMySQLを使いたい場合は
run.sh
中のHOSTNAME
、PASSWORD
、PORT
、USERNAME
、DATABASE
を設定している箇所を変更すれば良い。
manifest.ymlの作成
Cloud Foundryにデプロイするためのmanifest.yml
は次の通り。今回はbinary_buildpackを使用する。メモリは32MBくらいで十分。
application名は重複するかもしれないので変えたほうが無難。
applications:
- name: cf-vault
buildpack: binary_buildpack
memory: 32m
command: './run.sh'
services:
- vault-db
manifest.yml
ができてしまえば、あとは
cf push
でデプロイ完了。
cf logs
で次のようなログが出力される。
2020-02-23T19:49:13.61+0900 [CELL/0] OUT Cell 6d545859-b729-4434-8158-8d0ae0e3dd0a successfully created container for instance 2f6f4417-7bec-4292-5c1f-7252
2020-02-23T19:49:14.11+0900 [CELL/0] OUT Downloading droplet...
2020-02-23T19:49:18.60+0900 [CELL/0] OUT Downloaded droplet (45.5M)
2020-02-23T19:49:18.60+0900 [CELL/0] OUT Starting health monitoring of container
2020-02-23T19:49:29.21+0900 [APP/PROC/WEB/0] OUT detected cleardb
2020-02-23T19:49:30.04+0900 [APP/PROC/WEB/0] OUT #### Starting Vault...
2020-02-23T19:49:33.17+0900 [APP/PROC/WEB/0] OUT ==> Vault server configuration:
2020-02-23T19:49:33.20+0900 [APP/PROC/WEB/0] OUT Cgo: disabled
2020-02-23T19:49:33.31+0900 [APP/PROC/WEB/0] OUT Listener 1: tcp (addr: "0.0.0.0:8080", cluster address: "0.0.0.0:8081", max_request_duration: "1m30s", max_request_size: "33554432", tls: "disabled")
2020-02-23T19:49:33.32+0900 [APP/PROC/WEB/0] OUT Log Level: info
2020-02-23T19:49:33.35+0900 [APP/PROC/WEB/0] OUT Mlock: supported: true, enabled: false
2020-02-23T19:49:33.37+0900 [APP/PROC/WEB/0] OUT Recovery Mode: false
2020-02-23T19:49:33.39+0900 [APP/PROC/WEB/0] OUT Storage: mysql (HA disabled)
2020-02-23T19:49:33.40+0900 [APP/PROC/WEB/0] OUT Version: Vault v1.3.2
2020-02-23T19:49:33.44+0900 [APP/PROC/WEB/0] OUT ==> Vault server started! Log data will stream in below:
2020-02-23T19:49:33.44+0900 [APP/PROC/WEB/0] ERR 2020-02-23T10:49:31.994Z [INFO] proxy environment: http_proxy= https_proxy= no_proxy=
2020-02-23T19:49:33.44+0900 [APP/PROC/WEB/0] ERR 2020-02-23T10:49:33.137Z [WARN] no `api_addr` value specified in config or in VAULT_API_ADDR; falling back to detection if possible, but this value should be manually set
2020-02-23T19:49:34.78+0900 [CELL/0] OUT Container became healthy
2020-02-23T19:50:30.30+0900 [APP/PROC/WEB/0] ERR 2020-02-23T10:50:30.306Z [ERROR] core: no seal config found, can't determine if legacy or new-style shamir
2020-02-23T19:50:30.46+0900 [APP/PROC/WEB/0] ERR 2020-02-23T10:50:30.460Z [INFO] core: security barrier not initialized
2020-02-23T19:50:30.65+0900 [APP/PROC/WEB/0] ERR 2020-02-23T10:50:30.654Z [INFO] core: security barrier initialized: stored=1 shares=5 threshold=3
2020-02-23T19:50:30.83+0900 [APP/PROC/WEB/0] ERR 2020-02-23T10:50:30.830Z [INFO] core: post-unseal setup starting
2020-02-23T19:50:31.33+0900 [APP/PROC/WEB/0] ERR 2020-02-23T10:50:31.335Z [INFO] core: loaded wrapping token key
2020-02-23T19:50:31.33+0900 [APP/PROC/WEB/0] ERR 2020-02-23T10:50:31.337Z [INFO] core: successfully setup plugin catalog: plugin-directory=
2020-02-23T19:50:31.38+0900 [APP/PROC/WEB/0] ERR 2020-02-23T10:50:31.387Z [INFO] core: no mounts; adding default mount table
2020-02-23T19:50:31.45+0900 [APP/PROC/WEB/0] ERR 2020-02-23T10:50:31.450Z [INFO] core: successfully mounted backend: type=cubbyhole path=cubbyhole/
2020-02-23T19:50:31.47+0900 [APP/PROC/WEB/0] ERR 2020-02-23T10:50:31.460Z [INFO] core: successfully mounted backend: type=system path=sys/
2020-02-23T19:50:31.51+0900 [APP/PROC/WEB/0] ERR 2020-02-23T10:50:31.510Z [INFO] core: successfully mounted backend: type=identity path=identity/
2020-02-23T19:50:32.14+0900 [APP/PROC/WEB/0] ERR 2020-02-23T10:50:32.142Z [INFO] core: successfully enabled credential backend: type=token path=token/
2020-02-23T19:50:32.14+0900 [APP/PROC/WEB/0] ERR 2020-02-23T10:50:32.142Z [INFO] core: restoring leases
2020-02-23T19:50:32.14+0900 [APP/PROC/WEB/0] ERR 2020-02-23T10:50:32.143Z [INFO] rollback: starting rollback manager
2020-02-23T19:50:32.18+0900 [APP/PROC/WEB/0] ERR 2020-02-23T10:50:32.184Z [INFO] expiration: lease restore complete
2020-02-23T19:50:32.30+0900 [APP/PROC/WEB/0] ERR 2020-02-23T10:50:32.306Z [INFO] identity: entities restored
2020-02-23T19:50:32.32+0900 [APP/PROC/WEB/0] ERR 2020-02-23T10:50:32.324Z [INFO] identity: groups restored
2020-02-23T19:50:32.43+0900 [APP/PROC/WEB/0] ERR 2020-02-23T10:50:32.432Z [INFO] core: post-unseal setup complete
2020-02-23T19:50:32.51+0900 [APP/PROC/WEB/0] ERR 2020-02-23T10:50:32.514Z [INFO] core: root token generated
2020-02-23T19:50:32.51+0900 [APP/PROC/WEB/0] ERR 2020-02-23T10:50:32.514Z [INFO] core: pre-seal teardown starting
2020-02-23T19:50:32.51+0900 [APP/PROC/WEB/0] ERR 2020-02-23T10:50:32.514Z [INFO] rollback: stopping rollback manager
2020-02-23T19:50:32.51+0900 [APP/PROC/WEB/0] ERR 2020-02-23T10:50:32.519Z [INFO] core: pre-seal teardown complete
https://<アプリケーションのURL>/v1/
にアクセスして"Vault is sealed"
と返って来ればOK。
Pivotal Web Servicesの場合、
$ curl https://<your application name>.cfapps.io/v1/
{"errors":["Vault is sealed"]}
manifest.yml
を作りたくない場合は、次のコマンドでもOKcf push cf-vault -b binary_buildpack -m 32m -c './run.sh' --no-start cf bind-service cf-vault vault-db cf start cf-vault
Vaultの初期化
起動した直後のVaultは"Sealed"な状態なので、"Unseal"する必要がある。vault
コマンドで初期化する。
Macからアクセスする場合は別途
vault
コマンドをインストールする必要がある。brew install vault
初期化
export VAULT_ADDR=https://<アプリケーションのURL>
vault operator init
このコマンドを実行すると次のように出力される。
Unseal Key 1: USDincNpoViKoTGZJ4pX0uVi5iG+E9Avdp7Gcpv4IcUe
Unseal Key 2: f1DoeJsOaUgm4wScFaegeVmrXDYQURUaJr8bmP3AtvkQ
Unseal Key 3: xnBMnxlWucQwDa1IYYtUSwYaXudzvatITtMTnMtKNT+s
Unseal Key 4: gqcCx6iV1DXgeur9jU5+NfyoUMVfZRylo5VCPW3+jeT7
Unseal Key 5: PFJFSzRHhdCjkFpl6E3PwncG75qnuLNDQD4BA1MuJzLN
Initial Root Token: 06fdd16a-8941-1f78-2454-f25a13f6d55c
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 KeyとRoot Tokenが重要。
Unseal
表示された5つのUnseal Keyのうち、3つを使ってUnsealする。
vault operator unseal USDincNpoViKoTGZJ4pX0uVi5iG+E9Avdp7Gcpv4IcUe
vault operator unseal f1DoeJsOaUgm4wScFaegeVmrXDYQURUaJr8bmP3AtvkQ
vault operator unseal xnBMnxlWucQwDa1IYYtUSwYaXudzvatITtMTnMtKNT+s
三回目の結果にSealed: false
が含まれるはず。
ログイン
Root Tokenを使ってログインする。
vault login 06fdd16a-8941-1f78-2454-f25a13f6d55c
Successfully authenticated! You are now logged in.
が出力されればOK。
再度/v1/
にアクセスすると、errors
がなくなっている。
$ curl https://<your application name>.cfapps.io/v1/
{"errors":[""]}
これで自分専用のVaultが出来上がり、この環境でVaultのGetting Startedも始められる。
vault secrets enable -path=kv kv
vault secrets list
$ vault kv put kv/hello target=world
Success! Data written to: kv/hello
再起動時の自動Unseal
残る問題は再起動した際にVaultがまたSealされるので、再度vault operator unseal
しないといけないこと。
セキュリティ上重要であるが、Cloud Foundryのようにコンテナがダウンしても自動復旧が行われると不都合である。
自動で復旧してほしいので、ここでは割り切ってrun.sh
内で起動時に自動でUnsealするようにする。
run.sh
を次のように修正。
#!/bin/sh
CLEARDB=`echo $VCAP_SERVICES | grep "cleardb"`
PMYSQL=`echo $VCAP_SERVICES | grep "p-mysql"`
if [ "$CLEARDB" != "" ];then
SERVICE="cleardb"
elif [ "$PMYSQL" != "" ]; then
SERVICE="p-mysql"
fi
echo "detected $SERVICE"
HOSTNAME=`echo $VCAP_SERVICES | jq -r '.["'$SERVICE'"][0].credentials.hostname'`
PASSWORD=`echo $VCAP_SERVICES | jq -r '.["'$SERVICE'"][0].credentials.password'`
PORT=`echo $VCAP_SERVICES | jq -r '.["'$SERVICE'"][0].credentials.port'`
USERNAME=`echo $VCAP_SERVICES | jq -r '.["'$SERVICE'"][0].credentials.username'`
DATABASE=`echo $VCAP_SERVICES | jq -r '.["'$SERVICE'"][0].credentials.name'`
cat <<EOF > cf.hcl
disable_mlock = true
storage "mysql" {
username = "$USERNAME"
password = "$PASSWORD"
address = "$HOSTNAME:$PORT"
database = "$DATABASE"
table = "vault"
}
listener "tcp" {
address = "0.0.0.0:8080"
tls_disable = 1
}
EOF
echo "#### Starting Vault..."
./vault server -config=cf.hcl &
###### !!!!ここから追加!!!!
if [ "$VAULT_UNSEAL_KEY1" != "" ];then
export VAULT_ADDR='http://127.0.0.1:8080'
echo "#### Waiting..."
sleep 1
echo "#### Unsealing..."
if [ "$VAULT_UNSEAL_KEY1" != "" ];then
./vault operator unseal $VAULT_UNSEAL_KEY1
fi
if [ "$VAULT_UNSEAL_KEY2" != "" ];then
./vault operator unseal $VAULT_UNSEAL_KEY2
fi
if [ "$VAULT_UNSEAL_KEY3" != "" ];then
./vault operator unseal $VAULT_UNSEAL_KEY3
fi
fi
manifest.yml
にUnseal Keyを設定
applications:
- name: cf-vault
buildpack: binary_buildpack
memory: 32m
command: './run.sh'
services:
- vault-db
env:
VAULT_UNSEAL_KEY1: USDincNpoViKoTGZJ4pX0uVi5iG+E9Avdp7Gcpv4IcUe
VAULT_UNSEAL_KEY2: f1DoeJsOaUgm4wScFaegeVmrXDYQURUaJr8bmP3AtvkQ
VAULT_UNSEAL_KEY3: xnBMnxlWucQwDa1IYYtUSwYaXudzvatITtMTnMtKNT+s
これで再起動や再ステージング(コンテナイメージ再作成)しても、同じMySQLインスタンスを使う限り、そのままvault
コマンドでVaultにアクセスできる。
$ vault kv get kv/hello
===== Data =====
Key Value
--- -----
target world
vaule world
このやり方はおすすめされていないので注意。Consulを使ったHAモードならうまくUnsealされる?
まとめ
Cloud Foundryを使って、自動復旧可能な自分専用のVaultサーバーを持つことができた。 Githubにもサンプルを置いた。
32MBメモリしか使わないので、Pivotal Web Servicesだと$0.675/month(だいたい月75円くらい)で運用できる。
$86分の無料ライセンスに含まれて気軽に試せるので、開発用のVaultが欲しい人はどうぞ。
次はこのVaultサーバーを使って、
を試したい。