--- title: Cloud Foundry上にVaultをデプロイ tags: ["Cloud Foundry", "Vault"] categories: ["Dev", "SecretManagement", "Vault"] date: 2017-06-09T03:32:23Z updated: 2020-02-23T11:04:20Z --- **目次** ### やりたいこと 機密情報管理に便利な[Hashicorp Vault](https://www.vaultproject.io/)。 [Spring Vault](http://projects.spring.io/spring-vault/)や[Spring Cloud ConfigのVault Backend](https://cloud.spring.io/spring-cloud-config/spring-cloud-config.html#_vault_backend)、[Cloud Foundry Vault Service Broker](https://www.hashicorp.com/blog/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は[いくつか選択肢](https://www.vaultproject.io/docs/configuration/storage/index.html)があるが、Cloud FoundryのサービスとしてもっともポピュラーなMySQLを使う。 [MySQL Storage](https://www.vaultproject.io/docs/configuration/storage/mysql.html)はHA非対応だし、Communityによる開発なので、Production用途ではないけれども、開発用途でCloud上に置いておくバックエンドとしては十分。 ### MySQLサービスインスタンスの作成 まずは`vault-db`というMySQLのサービスインスタンスを作成する。`cf login`している前提で説明する。 #### Pivotal Web Servicesの場合 [Pivotal Web Services](https://run.pivotal.io)を使う場合は ``` cf create-service cleardb spark vault-db ``` #### Pivotal Cloud Foundryの場合 [Pivotal Cloud Foundry](https://docs.pivotal.io/pivotalcf/)を使う場合は ``` cf create-service p-mysql 100mb-dev vault-db ``` 他のCloud Foundryサービスの場合でもOKだと思う。 ### Vaultのデプロイ ではVaultをCloud Foundryにデプロイする。 #### Vaultのダウンロード Vaultは実行可能バイナリとして配布されている。[Download Vault](https://www.vaultproject.io/downloads.html)の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形式で接続情報](https://docs.pivotal.io/pivotalcf/devguide/deploy-apps/environment-variable.html#VCAP-SERVICES)が含まれるので、これ`jq`コマンドで加工する。 スクリプトは次のようになる。 ``` 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 < 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名は重複するかもしれないので変えたほうが無難。 ``` yaml 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://.cfapps.io/v1/ {"errors":["Vault is sealed"]} ``` > `manifest.yml`を作りたくない場合は、次のコマンドでもOK > > ``` > cf 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://.cfapps.io/v1/ {"errors":[""]} ``` これで自分専用のVaultが出来上がり、この環境で[VaultのGetting Started](https://www.vaultproject.io/intro/getting-started/first-secret.html)も始められる。 ``` 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`を次のように修正。 ``` 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 < 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を設定 ``` yaml 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 ``` > このやり方は[おすすめされていない](https://www.vaultproject.io/docs/concepts/seal.html#unsealing)ので注意。Consulを使ったHAモードならうまくUnsealされる? ### まとめ Cloud Foundryを使って、自動復旧可能な自分専用のVaultサーバーを持つことができた。 [Github](https://github.com/making/cf-vault)にもサンプルを置いた。 32MBメモリしか使わないので、[Pivotal Web Services](https://run.pivotal.io/)だと$0.675/month(だいたい月75円くらい)で運用できる。 $86分の無料ライセンスに含まれて気軽に試せるので、開発用のVaultが欲しい人はどうぞ。 次はこのVaultサーバーを使って、 * [Cloud Foundry Vault Service Broker](https://www.hashicorp.com/blog/cloud-foundry-vault-service-broker/) * [Spring Cloud ServicesのVault & Multiple Backendサポート](https://content.pivotal.io/blog/spring-cloud-services-supports-vault-multiple-backends-use-the-right-config-repo-for-the-job) を試したい。