📝 BLOG.IK.AM

@making's memo
(🗃 Categories 🏷 Tags)

Cloud Foundry上にVaultをデプロイ

🗃 {Dev/SecretManagement/Vault}

🏷 Cloud Foundry 🏷 Vault

🗓 Updated at 2017-06-09T03:32:23+09:00 by Toshiaki Maki  🗓 Created at 2017-06-09T03:32:23+09:00 by Toshiaki Maki  {✒️️ Edit  ⏰ History}


⚠️ Caution: This content is a bit old. Please be careful to read.

目次

やりたいこと

機密情報管理に便利なHashicorp Vault
Spring 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/0.7.3/vault_0.7.3_linux_amd64.zip
unzip vault_0.7.3_linux_amd64.zip
rm -f vault_0.7.3_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
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で次のようなログが出力される。

2017-06-09T11:38:40.51+0900 [API/4]      OUT Updated app with guid de8d652c-4d84-4745-b593-4fd2ad36e837 ({"route"=>"556aa161-cc27-48d2-a2f5-918c0518e8d5", :verb=>"add", :relation=>"routes", :related_guid=>"556aa161-cc27-48d2-a2f5-918c0518e8d5"})
2017-06-09T11:39:12.76+0900 [API/2]      OUT Creating build for app with guid de8d652c-4d84-4745-b593-4fd2ad36e837
2017-06-09T11:39:13.48+0900 [API/2]      OUT Updated app with guid de8d652c-4d84-4745-b593-4fd2ad36e837 ({"state"=>"STARTED"})
2017-06-09T11:39:13.92+0900 [STG/0]      OUT Downloading binary_buildpack...
2017-06-09T11:39:14.09+0900 [STG/0]      OUT Downloaded binary_buildpack
2017-06-09T11:39:14.09+0900 [STG/0]      OUT Creating container
2017-06-09T11:39:15.27+0900 [STG/0]      OUT Successfully created container
2017-06-09T11:39:15.27+0900 [STG/0]      OUT Downloading app package...
2017-06-09T11:39:16.65+0900 [STG/0]      OUT Downloaded app package (13.6M)
2017-06-09T11:39:16.91+0900 [STG/0]      OUT -------> Buildpack version 1.0.13
2017-06-09T11:39:20.47+0900 [STG/0]      OUT Exit status 0
2017-06-09T11:39:20.47+0900 [STG/0]      OUT Uploading droplet, build artifacts cache...
2017-06-09T11:39:20.47+0900 [STG/0]      OUT Uploading build artifacts cache...
2017-06-09T11:39:20.47+0900 [STG/0]      OUT Uploading droplet...
2017-06-09T11:39:20.66+0900 [STG/0]      OUT Uploaded build artifacts cache (202B)
2017-06-09T11:39:20.92+0900 [API/6]      OUT Creating droplet for app with guid de8d652c-4d84-4745-b593-4fd2ad36e837
2017-06-09T11:39:22.99+0900 [STG/0]      OUT Uploaded droplet (13.6M)
2017-06-09T11:39:23.00+0900 [STG/0]      OUT Uploading complete
2017-06-09T11:39:23.07+0900 [STG/0]      OUT Destroying container
2017-06-09T11:39:23.81+0900 [CELL/0]     OUT Creating container
2017-06-09T11:39:24.46+0900 [STG/0]      OUT Successfully destroyed container
2017-06-09T11:39:24.66+0900 [CELL/0]     OUT Successfully created container
2017-06-09T11:39:26.70+0900 [CELL/0]     OUT Starting health monitoring of container
2017-06-09T11:39:27.35+0900 [APP/PROC/WEB/0]OUT detected cleardb
2017-06-09T11:39:27.53+0900 [APP/PROC/WEB/0]OUT #### Starting Vault...
2017-06-09T11:39:27.73+0900 [APP/PROC/WEB/0]OUT ==> Vault server configuration:
2017-06-09T11:39:27.73+0900 [APP/PROC/WEB/0]OUT                      Cgo: disabled
2017-06-09T11:39:27.73+0900 [APP/PROC/WEB/0]OUT               Listener 1: tcp (addr: "0.0.0.0:8080", cluster address: "0.0.0.0:8081", tls: "disabled")
2017-06-09T11:39:27.73+0900 [APP/PROC/WEB/0]OUT                Log Level: info
2017-06-09T11:39:27.73+0900 [APP/PROC/WEB/0]OUT                    Mlock: supported: true, enabled: false
2017-06-09T11:39:27.73+0900 [APP/PROC/WEB/0]OUT                  Storage: mysql
2017-06-09T11:39:27.73+0900 [APP/PROC/WEB/0]OUT                  Version: Vault v0.7.3
2017-06-09T11:39:27.73+0900 [APP/PROC/WEB/0]OUT              Version Sha: 0b20ae0b9b7a748d607082b1add3663a28e31b68
2017-06-09T11:39:27.73+0900 [APP/PROC/WEB/0]OUT ==> Vault server started! Log data will stream in below:
2017-06-09T11:39:29.18+0900 [CELL/0]     OUT Container became healthy

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 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 unseal USDincNpoViKoTGZJ4pX0uVi5iG+E9Avdp7Gcpv4IcUe
vault unseal f1DoeJsOaUgm4wScFaegeVmrXDYQURUaJr8bmP3AtvkQ
vault unseal xnBMnxlWucQwDa1IYYtUSwYaXudzvatITtMTnMtKNT+s

三回目の結果にSealed: falseが含まれるはず。

ログイン

Root Tokenを使ってログインする。

vault auth 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 write secret/hello vaule=world
Success! Data written to: secret/hello

$ vault read secret/hello
Key                 Value
---                 -----
refresh_interval    768h0m0s
vaule               world

再起動時の自動Unseal

残る問題は再起動した際にVaultがまたSealされるので、再度vault 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 unseal $VAULT_UNSEAL_KEY1
    fi
    if [ "$VAULT_UNSEAL_KEY2" != "" ];then
        ./vault unseal $VAULT_UNSEAL_KEY2
    fi
    if [ "$VAULT_UNSEAL_KEY3" != "" ];then
        ./vault 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 read secret/hello
Key                 Value
---                 -----
refresh_interval    768h0m0s
vaule               world

このやり方はおすすめされていないので注意。Consulを使ったHAモードならうまくUnsealされる?

まとめ

Cloud Foundryを使って、自動復旧可能な自分専用のVaultサーバーを持つことができた。
Githubにもサンプルを置いた。

32MBメモリしか使わないので、Pivotal Web Servicesだと$0.675/month(だいたい月75円くらい)で運用できる。

$86分の無料ライセンスに含まれて気軽に試せるので、開発用のVaultが欲しい人はどうぞ。

次はこのVaultサーバーを使って、

を試したい。