前記事でConfig Treeを使ったConfig Data APIを紹介しましたが、今回は
Spring Cloud Vaultを使ったConfig Data APIの使い方を紹介します。
Config Data APIは抽象化されているので、実質的にはSpring Cloud Vault自体の使い方の説明です。
目次
Vaultのセットアップ
VaultはDockerで起動します。
Docker Composeで使用するファイルを準備してあるので、gitで取得してください。
git clone https://github.com/making/demo-configdata-vault
cd demo-configdata-vault
Docker Composeで起動します。
docker-compose -f vault/docker-compose.yml up
次のコマンドで初期化します。vault.local.maki.lolは127.0.0.1に解決されます。
export VAULT_ADDR=https://vault.local.maki.lol:8200
export VAULT_CACERT=$PWD/vault/certs/ca.pem
vault operator init
次のようなログが出力されます。
Unseal Key 1: R6vUEJXNrYviuOrRWLbXzQ25hKlNJ5jCzmDcb3Qdosmh
Unseal Key 2: ET9qNWZvo7tTFrQbKDBUcQU0a29TrMFukIem60O9GaSA
Unseal Key 3: pz8RkNphIe6444c5B6KB6RSMIy4LJlR7h42uuMEs19B7
Unseal Key 4: ZqOBy3gqc0e+xvTaO8xNeqxrmP7hk+QOShTmzSmAx+OG
Unseal Key 5: tSiqej576SFAwwCTlODe8aYs4C/qzq1YL3UYLY2+yjGK
Initial Root Token: s.6WAxZ2zq8eZnDqi1RZfBgOct
Vault initialized with 5 key shares and a key threshold of 3. Please securely
distribute the key shares printed above. When the Vault is re-sealed,
restarted, or stopped, you must supply at least 3 of these keys to unseal it
before it can start servicing requests.
Vault does not store the generated master key. Without at least 3 key to
reconstruct the master key, Vault will remain permanently sealed!
It is possible to generate new unseal keys, provided you have a quorum of
existing unseal keys shares. See "vault operator rekey" for more information.
ログ中のUnseal Keyを3つ選んで、次のコマンドを実行し、Vaultをunsealします。
vault operator unseal R6vUEJXNrYviuOrRWLbXzQ25hKlNJ5jCzmDcb3Qdosmh
vault operator unseal ET9qNWZvo7tTFrQbKDBUcQU0a29TrMFukIem60O9GaSA
vault operator unseal pz8RkNphIe6444c5B6KB6RSMIy4LJlR7h42uuMEs19B7
上記ログ中のInitial Root Tokenを使って、次のコマンドを実行し、Vaultにログインします。
vault login s.6WAxZ2zq8eZnDqi1RZfBgOct
Secret Backendの作成
Spring Cloud Vaultではデフォルトで次の名前のSecret Backendを使用します(ドキュメント)。
/secret/{application}/{profile}/secret/{application}/secret/{default-context}/{profile}/secret/{default-context}
{application}の値はspring.cloud.vault.application-nameで指定します。{default-context}の値はspring.cloud.vault.kv.default-contextで指定でき、デフォルトはapplicationです。{profile}はspring.cloud.vault.kv.profilesまたはspring.profiles.activeで指定できます。
まずはsecretという名前のKey-Value Backendを作成します。
$ vault secrets enable -path=secret -version=2 kv
Success! Enabled the kv secrets engine at: secret/
$ vault secrets list
Path Type Accessor Description
---- ---- -------- -----------
cubbyhole/ cubbyhole cubbyhole_2e13c260 per-token private secret storage
identity/ identity identity_2f4a734c identity store
secret/ kv kv_cb7e2889 n/a
sys/ system system_cd942d8d system endpoints used for control, policy and debugging
後ほど、アプリケーション側で{application}の値をdemoに設定するとして、次の値をsecret/demoに登録します。
vault kv put secret/demo \
info.vault.name=demo \
info.vault.message='Hello World!'
PolicyとAppRoleの作成
secret以下の読み取り権限だけを持つspring policyを作成します。
$ vault policy write spring vault/config/spring.hcl
Success! Uploaded policy: spring
アプリからVaultへの認証はAppRoleで行います。
次のようにspring roleを作成し、spring policyを適用します。
$ vault auth enable approle
Success! Enabled approle auth method at: approle/
$ vault write -f auth/approle/role/spring policies=spring period=1h
Success! Data written to: auth/approle/role/spring
$ vault read auth/approle/role/spring
Key Value
--- -----
bind_secret_id true
local_secret_ids false
period 1h
policies [spring]
secret_id_bound_cidrs <nil>
secret_id_num_uses 0
secret_id_ttl 0s
token_bound_cidrs []
token_explicit_max_ttl 0s
token_max_ttl 0s
token_no_default_policy false
token_num_uses 0
token_period 1h
token_policies [spring]
token_ttl 0s
token_type default
次のコマンドでrole-idとsecret-idを取得します。
$ vault read auth/approle/role/spring/role-id
Key Value
--- -----
role_id 70814761-f442-1203-9d25-13ed54e2d440
$ vault write -f auth/approle/role/spring/secret-id
Key Value
--- -----
secret_id e3c3e858-ab01-c374-f2b4-5f954ed5e69e
secret_id_accessor 4ee83f23-2534-356b-2193-a53884f8fbc5
試しに
$ vault write auth/approle/login \
role_id="70814761-f442-1203-9d25-13ed54e2d440" \
secret_id="e3c3e858-ab01-c374-f2b4-5f954ed5e69e"
Key Value
--- -----
token s.Oqio0P3eUEz8hIkrZ2nx6FM8
token_accessor SWJkW5DC6j7jdAg0lXUkBiM9
token_duration 1h
token_renewable true
token_policies ["default" "spring"]
identity_policies []
policies ["default" "spring"]
token_meta_role_name spring
$ VAULT_TOKEN=s.Oqio0P3eUEz8hIkrZ2nx6FM8 vault kv get secret/demo
====== Metadata ======
Key Value
--- -----
created_time 2020-11-17T04:44:57.5322663Z
deletion_time n/a
destroyed false
version 1
=========== Data ===========
Key Value
--- -----
info.vault.message Hello World!
info.vault.name demo
$ VAULT_TOKEN=s.Oqio0P3eUEz8hIkrZ2nx6FM8 vault kv put secret/demo a=100
Error writing data to secret/data/demo: Error making API request.
URL: PUT https://vault.local.maki.lol:8200/v1/secret/data/demo
Code: 403. Errors:
* 1 error occurred:
* permission denied
アプリケーションの作成
Spring Initializrで雛形プロジェクトを作成します。Spring Boot 2.4及びSpring Cloud 2020.0が必要です。
執筆時点ではSpring Boot 2.4.0とSpring Cloud 2020.0.0-SNAPSHOT (Spring Cloud Vault 3.0.0-SNAPSHOT)を使用します。
次のコマンドでプロジェクトを生成します。
curl https://start.spring.io/starter.tgz \
-s \
-d javaVersion=11 \
-d artifactId=demo-configdata-vault \
-d baseDir=demo-configdata-vault \
-d dependencies=cloud-starter-vault-config,actuator,web,configuration-processor \
-d packageName=com.example \
-d applicationName=DemoConfigdataVaultApplication | tar -xzvf -
執筆時点(Spring Cloud 2020.0.0-M4)では、古いSpring Vault 2.2.0が使用されてしまうという問題があるため、<dependencyManagement>に次のWorkaroundを適用します。
<dependencyManagement>
<dependencies>
<!-- Workaround for https://github.com/spring-cloud/spring-cloud-config/issues/1751 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-vault-dependencies</artifactId>
<version>3.0.0-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
application.propertiesに次の内容を設定します。VaultにTLSで接続するために、Trust Storeの設定をしています。
Vaultに自己署名証明書を使用している場合に必要です。(HTTPは使用しないで!)
management.endpoints.web.exposure.include=info,health,env,refresh
spring.cloud.vault.application-name=demo
spring.cloud.vault.uri=https://vault.local.maki.lol:8200
spring.cloud.vault.authentication=approle
spring.cloud.vault.app-role.role-id=<the role id retrieved above>
spring.cloud.vault.app-role.secret-id=<the secret id retrieved above>
spring.cloud.vault.ssl.trust-store=file:<path to demo-configdata-vault>/vault/certs/ca.pem
spring.cloud.vault.ssl.trust-store-type=PEM
spring.cloud.vault.fail-fast=true
spring.config.import=vault://
spring.config.import=vault://が今回のポイントです。spring.config.import=vault://<path>のようにVaultから値を読み込むパスを変更することもできます。
これでDemoConfigdataVaultApplicationクラスを実行します。
/actuator/envエンドポイントにアクセスして、Vaultのsecret/demoからプロパティを取得できていることを確認してください。
$ curl -s localhost:8080/actuator/env | jq .
{
"activeProfiles": [],
"propertySources": [
// (omited)
{
"name": "secret/application",
"properties": {}
},
{
"name": "secret/demo",
"properties": {
"info.vault.name": {
"value": "demo"
},
"info.vault.message": {
"value": "Hello World!"
}
}
},
// (omited)
]
}
info.から始まるプロパティは/actuator/infoエンドポイントで取得できます。
$ curl -s localhost:8080/actuator/info | jq .
{
"vault": {
"name": "demo",
"message": "Hello World!"
}
}
Refreshの確認
Spring Cloud VaultはRefreshに対応しています。
アプリケーションを起動したまま、Vault上の値を変更します。
vault kv put secret/demo \
info.vault.name=demo \
info.vault.message='Hello Vault!'
直後に/actuator/infoを確認してもmessageの値は変わらないです。
$ curl -s localhost:8080/actuator/info | jq .
{
"vault": {
"name": "demo",
"message": "Hello World!"
}
}
/actuator/refreshエンドポイントにPOSTのリクエストを送ると、変更されたプロパティがリフレッシュされます。
$ curl -XPOST http://localhost:8080/actuator/refresh
["info.vault.message"]
$ curl -s localhost:8080/actuator/info | jq .
{
"vault": {
"name": "demo",
"message": "Hello Vault!"
}
}
明示的にRefreshしなくても、デフォルトで10秒インターバルでRefreshされます。
このインターバルはspring.cloud.vault.config.lifecycle.min-renewalで変更できます。
Config Treeとの併用
Vaultにアクセスするためのrole-idとsecret-idをアプリケーションに渡すのは最も気をつけないといけないポイントです。
ここでは前記事で説明したように、Config Treeを使用して、ファイルマウントによってプロパティを設定するように変更します。
こちらの方法が環境変数で設定するよりもセキュアです。
application.propertiesを次のように変更します。
# Remove role-id and secret-id from application.properties
#spring.cloud.vault.app-role.role-id=
#spring.cloud.vault.app-role.secret-id=
spring.config.import=configtree:/tmp/config/,vault://
role-idとsecret-idをファイルに書き込みます。
mkdir -p /tmp/config/spring/cloud/vault/app-role
echo 70814761-f442-1203-9d25-13ed54e2d440 > /tmp/config/spring/cloud/vault/app-role/role-id
echo e3c3e858-ab01-c374-f2b4-5f954ed5e69e > /tmp/config/spring/cloud/vault/app-role/secret-id
Treeは次のようになります。
$ tree /tmp/config/spring
/tmp/config/spring
`-- cloud
`-- vault
`-- app-role
|-- role-id
`-- secret-id
3 directories, 2 files
アプリケーションを再起動すれば、Vaultからプロパティが取得できます。
Spring Cloud Vaultを使ったConfig Data APIの使い方、というよりSpring Cloud Vault自体の使い方の説明しました。
Spring Cloud 2020.0正式リリース後に利用できます。
Config Treeを使えばVaultにアクセスするための情報(金庫の鍵)をよりセキュアに扱えるので良いです。
日本語のVaultの資料は↓がわかりやすいです。
https://github.com/hashicorp-japan/vault-workshop-jp