IK.AM

@making's tech note


Cloud Foundry上にVaultをデプロイ

🗃 {Dev/SecretManagement/Vault}
🏷 Cloud Foundry 🏷 Vault 
🗓 Updated at 2020-02-23T11:04:20Z  🗓 Created at 2017-06-09T03:32:23Z   🌎 English Page

目次

やりたいこと

機密情報管理に便利なHashicorp VaultSpring VaultSpring Cloud ConfigのVault BackendCloud 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中のHOSTNAMEPASSWORDPORTUSERNAMEDATABASEを設定している箇所を変更すれば良い。

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を作りたくない場合は、次のコマンドでも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://<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サーバーを使って、

を試したい。


✒️️ Edit  ⏰ History  🗑 Delete