Vaultのsecret backendとしてのConsul
先の記事ではVaultのstorage backendにConsulを利用する方法を記述した。
こちらの記事ではConsulをsecret backendとする方法を記述する。
なお、storage backendとsecret backendとには特に何の連携もないので、secret backendとしてConsulを使ったからといってstorage backendもConsulにしないといけないとか、またはその逆にstorage backendにConsulを使っているからといってsecret backendにもConsulを使わなければならないとか、なんてことはまったくない。
この記事はVaultのバージョンは0.1.2、Consulのバージョンは0.5.1で書き始めた。
Consul側の準備
Consulをsecret backendとした場合、VaultがConsul ACLの一時トークンの発行と無効化を行うことが可能になる。
ただしConsul側で設定しないとACL機能は有効化されていないため、まずこれを有効化する必要がある。
Consul側でACL機能を有効化するためには、Consulサーバの設定ファイルでacl_datacenter項にデータセンタ名を設定する。
データセンタ名には接続されているデータセンタのうちどれかを指定するが、自分以外のどことも接続されていなければ自分のデータセンタ名を指定する。
自分のデータセンタ名はdatacenter項に設定した値であり、省略時は"dc1"である。
ちなみに、実際にACL機能が有効になるのはConsulサーバのうち、acl_datacenter項が設定されたノードがleaderになった時、のようだ。
Consulの設定ファイルはここでは /etc/consul.d/agent.json とする。
{
(前略)
"acl_datacenter": "dc1",
"acl_default_policy": "deny",
"acl_master_token": "root",
(後略)
}
今回acl_default_policy項とacl_master_token項も設定している。
acl_default_policy項は許可も禁止もされていない操作が行われた場合に許可するか禁止するかを設定する項目であり、"deny"だと禁止、"allow"だと許可する。
デフォルト値は"allow"であるが、ここはACL機能をより理解するためのチャレンジとして"deny"にしてみた。
acl_master_token項はこのConsulサーバのマスタトークン、つまり何でもできるACLトークンの名前を指定する。
このACLトークンは存在しなければConsulサーバの起動時に作成されるので、あらかじめ手動で作成するなどの操作は必要ない。
Consulクライアント側ではACL機能を有効化するために設定する項目はないが、トークンの発行を行うノードではacl_datacenter項にConsulサーバと同じ設定値を指定する必要がある。
今回の目的はVaultによるConsul ACLの一時トークンの発行と無効化なので、Vaultのsecret backendの接続先Consulノード(説明は後述)がConsulサーバではなくConsulクライアントである場合、その接続先Consulクライアントの設定ファイルにもacl_datacenter項を追加してやる必要がある。
{
(前略)
"acl_datacenter": "dc1",
(後略)
}
ACLはキーバリューストアの読み書きとサービスの登録に影響する。
先の記事ではVaultサーバでvaultという名前のサービスと、自分のホスト名のサービスを登録していた。
acl_default_policy項が"deny"だとこれらのサービス登録も阻害されるので、これらサービスの登録を許可するACLトークンを作らなければならない。
(これは先の記事のHTTPS接続でHA構成とするためのセットアップ手順に従った場合のものである。そんなことしていないならサービスの登録など特に必要ない)
Vaultサーバのうち、cc6というホスト名のノードのためのACLトークンは次のコマンドで発行する。
token=root
の箇所は、token=(Consulサーバのacl_master_token項で設定した文字列)
である。
別のホスト用にはホスト名(2箇所)を変更して実行する。
# curl -X PUT http://localhost:8500/v1/acl/create?token=root -d '{ "Name": "cc6", "Rules": "key \"_rexec/\" { policy = \"write\" } service \"vault\" { policy = \"write\" } service \"cc6\" { policy = \"write\" }"}'
上記、Rulesの中が新しく作るACLトークンのポリシーであり、HCLで記述する。
上記コマンドのままでは読みづらいので読みやすいようにすると次のようなものを与えている。
key "_rexec/" { policy = "write" }
service "vault" { policy = "write" }
service "cc6" { policy = "write" }
このHCLを使った指定の仕方はこのドキュメントに説明がある。
ここではvaultというサービスとcc6(ホスト名)というサービスの登録を許可している。
もうひとつの「key "_rexec/"」は、本記事とは特に関係はないがconsul exec
が使用するキーバリューストアのキーへの読み書きを許可するものである。これがないとconsul exec
が使用できない。
さて、上記のcurlコマンドを実行すると{"ID":"47006405-4248-de71-9874-8588fc2cc176"}
のようにIDが返ってくる。
この値がACLトークンとなる。
ここで得られたACLトークンはVaultサーバがあるノードのConsul設定ファイルのacl_token項に設定する。
acl_token項はそのノードのConsulがConsulクライアントとして振舞う場合に、どのACLトークンを使用するかを設定する項目である。
{
(前略)
"acl_token": "47006405-4248-de71-9874-8588fc2cc176",
(後略)
}
ここまで、Consulの設定ファイルを変更した際はConsulの再起動を行うこと。
ドキュメントによるとconsul reload
ではこれらの設定は反映されないようだ。
しかしConsulの再起動を行った後にさらにconsul reload
してやっとサービス設定が反映されたこともあるのでその辺り良く分からない。
Vault側の準備
Vaultサーバはstorage backendにConsulを使用している場合、Consulのキーバリューストアにvaultというディレクトリ(あるいは、backend "consul"のpath項に設定したディレクトリ)を作って利用している。
Consulサーバのacl_default_policy項が"deny"だとVaultからこのvaultディレクトリが読み書きできなくなるので、これを許可するACLトークンを作る必要がある。
# curl -X PUT http://localhost:8500/v1/acl/create?token=root -d '{ "Name": "vault", "Rules": "key \"vault/\" { policy = \"write\" }"}'
発行されたACLトークンはVault設定ファイルのbackend "consul"のtoken項に設定する。
このACLトークンはVaultサーバ共通で同じものが使用できる(どのVaultサーバでも同じポリシーを適用すれば良いから)。
backend "consul" {
advertise_addr = "https://cc6.service.consul:8200"
token = "bfd965ef-1568-7790-7ebe-8e1d60b3ba4e"
}
listener "tcp" {
address = "0.0.0.0:8200"
tls_cert_file = "/etc/pki/tls/self/service.crt"
tls_key_file = "/etc/pki/tls/self/service.key"
}
設定したらVaultサーバを再起動する。
現在Vaultにマウントされているsecret backendはvault mounts
で一覧を見ることができる。
# vault mounts
Path Type Description
secret/ generic generic secret storage
sys/ system system endpoints used for control, policy and debugging
secret/とsys/は初期からマウントされているsecret backendである。
Consulをsecret backendとしてマウントするためには次を実行する。
# vault mount consul
もう一度vault mounts
を実行するとconsul/が増えている。
# vault mounts
Path Type Description
consul/ consul
secret/ generic generic secret storage
sys/ system system endpoints used for control, policy and debugging
ACL一時トークンを発行するConsulのアドレスと発行に使用するACLトークンは consul/config/access にvault write
することにより指定できる。
今のところACLトークンを発行できるACLトークンはroot(Consulサーバのacl_master_token項で設定したマスタトークン)しか見つけていないので、tokenにはrootを指定している。
# vault write consul/config/access address=127.0.0.1:8500 token=root
ACL一時トークンの発行
ここまででVault側の準備もできた。
ここからは実際に発行するための操作になる。
まず発行する一時トークンのポリシーを定義する。
これはHCLで書いたACLポリシーをbase64エンコードして、vault write consul/roles/(ロール名)
のpolicyに指定してやれば良い。
ロール名は任意である。ここではキーバリューストアのすべてのキーの読み書きができるロールrwと、すべてのキーの読みだけできるロールroを作ってみる。
# echo 'key "" { policy = "write" }' | base64 | vault write consul/roles/rw policy=-
# echo 'key "" { policy = "read" }' | base64 | vault write consul/roles/ro policy=-
一時トークンの発行はvault read consul/creds/(ロール名)
で行う。vault read consul/roles/(ロール名)
ではない。
まずrwロールの方から一時トークンを発行してみる。
# vault read consul/creds/rw
Key Value
lease_id consul/creds/rw/3f5f5cd1-9e70-8dc8-c286-bba7dff1ace8
lease_duration 3600
lease_renewable true
token d7f04ee2-1e03-9e1e-74cc-b13660699f81
この表示されたtoken、"d7f04ee2-1e03-9e1e-74cc-b13660699f81"が一時トークンである。
vault read consul/creds/rw
を実行するたびに違うトークンが発行される。
Consulサーバのacl_default_policy項が"deny"なので、トークンなしでConsulのキーバリューストアに書き込もうとしてもエラーになる。
# curl -X PUT -d 'aaa' http://127.0.0.1:8500/v1/kv/test1
rpc error: Permission denied
しかし先程発行されたトークンを使用すれば書き込みを行うことができる。
# curl -X PUT -d 'aaa' http://127.0.0.1:8500/v1/kv/test1?token=d7f04ee2-1e03-9e1e-74cc-b13660699f81
true
読み込みもトークンなしでは何も返ってこない。
# curl -s http://127.0.0.1:8500/v1/kv/test1
一時トークンを使用すれば読み込みできる。
jqはCentOS 6や7だとEPELにある。
# curl -s http://127.0.0.1:8500/v1/kv/test1?token=d7f04ee2-1e03-9e1e-74cc-b13660699f81 | jq -r .[0].Value | base64 -d
aaa
roロールの方からも一時トークンを発行してみる。
# vault read consul/creds/ro
Key Value
lease_id consul/creds/ro/b366c317-bb86-7454-14ad-e2cccae52cd7
lease_duration 3600
lease_renewable true
token 27bb968d-1cc3-bccd-1a6c-654e0be02371
このトークンでは書き込みはできないが、読み込みは可能である。
# curl -X PUT -d 'bbb' http://127.0.0.1:8500/v1/kv/test1?token=27bb968d-1cc3-bccd-1a6c-654e0be02371
rpc error: Permission denied
# curl -s http://127.0.0.1:8500/v1/kv/test1?token=27bb968d-1cc3-bccd-1a6c-654e0be02371 | jq -r .[0].Value | base64 -d
aaa
これら一時トークンの寿命はlease_durationに書かれている秒数、つまり3600秒(1時間)である。
本来ならvault renew (lease_id) (秒数)
で指定した秒数だけ寿命を延ばすことができるはずだがどういうわけか今のところinternalエラーになる(これまで1回だけエラーにならなかったことがあるが、通るとは思ってなくて適当に実行したので本当に成功したのかはよくわからなかった)。
また、今はlease_durationの値を変更することもできない。
寿命が来る前に一時トークンを無効化するには次のようにする。
revokeの後に指定するのは発行時に表示されたlease_idである。
# vault revoke consul/creds/ro/b366c317-bb86-7454-14ad-e2cccae52cd7
無効化した後に同じトークンを使って読み込みを行おうとしてももはや今度はエラーになる。
# curl -s http://127.0.0.1:8500/v1/kv/test1?token=27bb968d-1cc3-bccd-1a6c-654e0be02371
rpc error: ACL not found
あるいは-prefix=trueオプションで指定した値から始まるlease_idを持つ一時トークンをすべて無効化できる。
例えばrwロールで発行した一時トークンをすべて無効化する場合は次のようにする。
# vault revoke -prefix=true consul/creds/rw/
read,write,renew,revokeについての考察
以上で本来書くべき内容は終了だが、書いてみてvault read/write/renew/revokeについて考えたことをメモ程度に残しておく。
まずvault read
とvault write
は一般的に想像されるread/writeとは違う。
オブジェクト指向(というかクラスベース)から言葉を借りれば、vault write
がクラスの定義、vault read
はそのクラスのインスタンスを作成するようなものである。
vault renew
はvault read
で作ったものの寿命を延ばすコマンドであり、vault revoke
はvault read
で作ったものを消し去るコマンドである。どちらもvault write
で作ったものを操作するわけではない。
ゆえにConsulをsecret backendにする場合のように、vault read
によって何かが動的に作られ、かつそれが何かと連携していない限りvault renew
やvault revoke
は意味をなさない。
従ってVaultに初期から用意されているgenericなsecret backendであるsecret/のキーに対してvault renew
やvault revoke
が効果がないように見えるのはこのためである。
generic secret backendは単純に文字列を暗号化して保持しておくために使用されるもの、と理解できる。
しかしgeneric secret backendのドキュメントではlease_durationを設定する例が書かれているんだよな。ここまでの理解が正しければ、lease_durationなど意味がないはずなのだが。