Vaultを正しく理解して、実務で導入/運用をできるようにするワークショップです。 なお、WEB+DB PRESS Vol.104をあらかじめ買って「Hashicorp Vaultで秘密情報管理」の記事を読んでおくとバッチリでしょう。
- はじめに
- 座学
- Vaultとは
- Vaultのアーキテクチャ
- Vaultのセキュリティに対する思想
- ハンズオン
- Vaultの冗長構成
- Vaultで通信を安全にする
- VaultでDBに素の個人情報を入れないようにする
- VaultでWrite権限ユーザを発行する
- おわりに
以下の目的のもと、本ワークショップを行います。
- Vaultが何なのか説明できるようになる
- 実務においてVaultの使い所がわかる
- Vaultを運用できるようになる
Vaultを使う上で必要な基礎知識をお話しします。
- 秘密情報管理のソフトウェア
- 堅牢なKey-Value Store
- たくさんの機能とともにセキュリティに対するプラクティスが詰まっている(最小権限の原則など)
- Hashicorpソフトウェアと併用することで秘密情報管理を自動化できる
- KVS(ストレージ)という括りで、認証/秘密/監査/ポリシー という仕組み
- Vaultサービスを無効/有効にするにはSeal/Unsealという用語
- APIを通じてたくさんの機能を操作
- ポリシーはファイル管理
- 秘密情報へのアクセスポリシーに沿って簡単に権限を取得できる
- 秘密情報へのアクセス権限のスコープを小さくすること、監査ログをとること、でインシデントのインパクトを最小限にする
- これらによって、運用上の手軽さと秘密管理の堅牢さを両立している
- なので付与するアクセス権限のTTLが短く設定しrenewという延長処理を行って運用する
実際に手を動かし、Vaultの機能を体験してみましょう。今回作るシステムの全体図は以下の通りです。
- 環境変数を使う前提で進みますので、複数のターミナルを使うと環境変数がなくてエラーというのに気をつけてください
- 手元のアプリとポートが被っているとコンテナが起動しない場合があるので確認しながら進めてください
まず、dockerコンテナを使ってのハンズオンなので、以下のdockerイメージをpullしておきます。
$ imgs=(agent server node); for ((i = 1; i <= ${#imgs[@]}; i++)) { docker pull linyows/consul:1.2-${imgs[i]} }
次に、vaultとconsulをclientとして使うのでhomebrewでインストールしておきます。また、vault cliを補完出来るようにしておくと便利です。
$ brew install vault consul
$ vault -autocomplete-install
- ストレージバックエンドにConsulを使用
- HA構成のVaultはストレージを通じてコミュニケーションする
- なので、特にVIPをシェアする必要はない
- Unseal状態においてホットスタンバイ
今回作るコンテナはconsul clusterとして起動するので、まず最初にconsul serverを起動する
$ docker run -p 8500:8500 --name consul -h consul -d linyows/consul:1.2-server
$ open http://localhost:8500/ui
vaultコンテナのためのファイル群をdownload
$ mkdir vault; cd vault
$ files=(vault.Dockerfile reload_vault.sh update_ca_certs.sh vault.conf vault.consul-template.conf vault.ini)
$ gist=linyows/64be55af49031b995c3de9161c9c6713
$ for ((i = 1; i <= ${#files[@]}; i++)) { curl -O -s https://gist.githubusercontent.com/$gist/raw/${files[i]} }
$ mv vault.Dockerfile Dockerfile
イメージをビルドしてvaultを2台起動
$ docker build -t linyows/vault .
$ ip=$(docker inspect -f "{{.NetworkSettings.IPAddress}}" consul)
$ docker run -p 8200:8200 --dns=127.0.0.1 --dns=8.8.8.8 --add-host=consul:$ip --name vault-1 -h vault-1 \
-e VAULT_REDIRECT_ADDR=https://vault.service.consul:8200 -d linyows/vault
$ docker run -p 8201:8200 --dns=127.0.0.1 --dns=8.8.8.8 --add-host=consul:$ip --name vault-2 -h vault-2 \
-e VAULT_REDIRECT_ADDR=https://vault.service.consul:8200 -d linyows/vault
# この状態をconsulから確認してみる
$ export CONSUL_HTTP_ADDR=localhost:8500
$ consul members
Node Address Status Type Build Protocol DC Segment
consul 172.17.0.2:8301 alive server 1.2.0 2 dc1 <all>
vault-1 172.17.0.3:8301 alive client 1.2.0 2 dc1 <default>
vault-2 172.17.0.4:8301 alive client 1.2.0 2 dc1 <default>
vaultの初期化と設定を行う
# localhostで信頼できるのでverify skip
$ export VAULT_SKIP_VERIFY=true
$ export VAULT_ADDR=https://localhost:8200
# 初期化で大事な情報が出力されます
$ vault operator init | tee keys
Unseal Key 1: SNvELrGymFguSf9rb4L9mpF0/0Yg+sBBjwzE54Ig+xmo
Unseal Key 2: s6fDWUERp032ffcyt7/nOIl4HxC2WLRBa8YMe9rV2zuO
Unseal Key 3: X+GMJ9yUO/foxRFWXtWFN2MMfEqQ4kZpfg3pi7tP0l+u
Unseal Key 4: IX6UEDy+0ksp5GtKIGutcbcLT5os3+0jsmc5Tappcuy9
Unseal Key 5: H8uWa1oNQUgax3skFM7WxSZHNEoMXjKXWsWMHQOqqpiL
Initial Root Token: 3f20a2c3-dfbf-2abf-022d-7e1cb887781b
# 閾値までunseal
$ vault operator unseal
# 状態確認 => active
$ vault status
Key Value
--- -----
Seal Type shamir
Sealed false
Total Shares 5
Threshold 3
Version 0.10.3
Cluster Name vault-cluster-81b8b861
Cluster ID 499c5059-23d5-04d3-2f92-6977d154e212
HA Enabled true
HA Cluster https://vault.service.consul:8201
HA Mode active
# unsealされていないとクラスタにjoinしないので2台目も設定
$ export VAULT_ADDR=https://localhost:8201
# おなじく閾値までunseal
$ vault operator unseal
# 状態確認 => standby
$ vault status
Key Value
--- -----
Seal Type shamir
Sealed false
Total Shares 5
Threshold 3
Version 0.10.3
Cluster Name vault-cluster-81b8b861
Cluster ID 499c5059-23d5-04d3-2f92-6977d154e212
HA Enabled true
HA Cluster https://vault.service.consul:8201
HA Mode standby
Active Node Address https://vault.service.consul:8200
# ハンズオンでは便宜上root tokenを使います `~/.vault-token`
$ vault login
Token (will be hidden):
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.
Key Value
--- -----
token 3f20a2c3-dfbf-2abf-022d-7e1cb887781b
token_accessor be43dba5-7b41-c861-4e92-79606c3c816a
token_duration ∞
token_renewable false
token_policies ["root"]
identity_policies []
policies ["root"]
vaultコンテナを全部削除してみる
$ ids=(1 2); for ((i = 1; i <= ${#ids[@]}; i++)) { docker stop vault-${ids[i]} && docker rm vault-${ids[i]} }
再度、コンテナを作ったらどうなるか?
https://speakerdeck.com/linyows/secure-internal-communicaton-by-hashicorp-vault
- サービスの大小あるが内部通信は必ずある
- 通信先が正しいかどうかは疎かにされがち
- VaultをRoot CAまたはIntermediate CAとして機能させる
- 自動化の話
- 初期構築の話
# Root CAを有効にする(pkiは自分でdescriptionを設定しないとsecrets listでn/aと表示される)
$ vault secrets enable -description="root ca" pki
# TTLの最大値を設定しておく
$ vault secrets tune -max-lease-ttl=87600h pki/
# CAのcertificateとprivate keyを作成する
$ vault write pki/root/generate/internal common_name=service.consul ttl=8760h
# 証明証発行とCRLのパスを変更
$ vault write pki/config/urls issuing_certificates="https://vault.service.consul:8200/v1/pki/ca" \
crl_distribution_points="https://vault.service.consul:8200/v1/pki/crl"
# 証明証を発行するロールを作成する
$ vault write pki/roles/service-dot-consul allowed_domains=service.consul allow_subdomains=true max_ttl=72h
# 証明証を発行
$ vault write pki/issue/service-dot-consul common_name=vault.service.consul
セキュリティの観点で、Root CAをVaultの外で管理しVaultは中間認証局として使うのがおすすめされている
手順
# 中間認証局を有効にする
$ vault secrets enable -path=pki_int -description="Intermediate CA" pki
# Root CA同様にmaxTTLを設定(Rootより短くする)
$ vault secrets tune -max-lease-ttl=43800h pki_int
# 中間認証局のCSRを発行
$ vault write pki_int/intermediate/generate/internal common_name="service.consul Intermediate Authority" ttl=43800h > pki_int.csr
# Root CAで署名する
$ vault write pki/root/sign-intermediate csr=@pki_int.csr format=pem_bundle > signed_certificate.pem
# 証明書を設定
$ vault write pki_int/intermediate/set-signed certificate=@signed_certificate.pem
# 証明証発行とCRLのパスを変更
$ vault write pki_int/config/urls issuing_certificates="https://vault.service.consul:8200/v1/pki_int/ca" \
crl_distribution_points="https://vault.service.consul:8200/v1/pki_int/crl"
# ロールを作成
$ vault write pki_int/roles/service-dot-consul allowed_domains=service.consul allow_subdomains=true max_ttl=72h
# 証明書発行
$ vault write pki_int/issue/service-dot-consul common_name=vault.service.consul
# 名前解決しつつ、証明書のエラーが出ることを確認する
$ docker exec -it vault-1 curl https://vault.service.consul:8200/v1/
# supervisordの状態確認
$ docker exec -it vault-1 supervisorctl status
consul RUNNING pid 10, uptime 0:07:44
consul-template STOPPED Not started
vault RUNNING pid 9, uptime 0:07:44
# consul-templateが使うtokenを設定するためにlogin
$ docker exec -it vault-1 vault login
Token (will be hidden):
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.
Key Value
--- -----
token 052f8bdc-738b-5032-4074-7443e206694f
token_accessor 8528fe1a-0a04-b8c1-93b0-b4314992d9ba
token_duration ∞
token_renewable false
token_policies ["root"]
identity_policies []
policies ["root"]
# consul-templateを起動する
$ docker exec -it vault-1 supervisorctl start consul-template
# 状態確認
$ docker exec -it vault-1 supervisorctl status
consul RUNNING pid 10, uptime 0:13:17
consul-template RUNNING pid 101, uptime 0:04:00
vault RUNNING pid 9, uptime 0:13:17
# 名前解決しつつ、証明書のエラーが出ないかを確認する
$ docker exec -it vault-1 curl https://vault.service.consul:8200/v1/
{"errors":[]}
vault-2も同様の手順を行う
- databaseシークレットとは
- アーキテクチャ図
- 特定のSQLを発行できる口を作って権限者がそれを実行する
- 自動化の話
$ cd ../; mkdir mysql; cd mysql
$ files=(mysql.Dockerfile mysql.ini mysql.sh)
$ for ((i = 1; i <= ${#files[@]}; i++)) { curl -O -s https://gist.githubusercontent.com/$gist/raw/${files[i]} }
$ mv mysql.Dockerfile Dockerfile
$ docker build -t linyows/mysql .
$ docker run -e MYSQL_DATABASE=express -p 13306:3306 --dns=127.0.0.1 --dns=8.8.8.8 \
--add-host=consul:$ip --name mysql -h mysql -d linyows/mysql
# 状態確認
$ consul members
Node Address Status Type Build Protocol DC Segment
consul 172.17.0.2:8301 alive server 1.2.0 2 dc1 <all>
mysql 172.17.0.5:8301 alive client 1.2.0 2 dc1 <default>
vault-1 172.17.0.3:8301 alive client 1.2.0 2 dc1 <default>
vault-2 172.17.0.4:8301 alive client 1.2.0 2 dc1 <default>
$ vault secrets enable database
Success! Enabled the database secrets engine at: database/
# プラグインの指定とDBへの接続情報を設定する
$ vault write database/config/mysql-database plugin_name=mysql-database-plugin \
connection_url="{{username}}:{{password}}@tcp(mysql.node.consul:3306)/" allowed_roles="express" username="root" password="secret"
# ロールの作成とロールのTTLや発行するコマンドの設定
$ vault write database/roles/express db_name=mysql-database default_ttl="1h" max_ttl="24h" \
creation_statements="CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}';GRANT ALL ON *.* TO '{{name}}'@'%';"
Success! Data written to: database/roles/express
# 時限ユーザ発行
$ vault read database/creds/express
Key Value
--- -----
lease_id database/creds/express/d2f89c12-1df5-66d1-d62e-a562ed79fbef
lease_duration 1h
lease_renewable true
password A1a-2T4GrUg0sAZREKrV
username v-root-express-eAyhwKO10Ma0ceDl3
# 接続確認
$ mysql -h 127.0.0.1 -P 13306 -u v-root-express-eAyhwKO10Ma0ceDl3 -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 5.5.5-10.1.32-MariaDB MariaDB Server
Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>
- transitシークレットとは、transitキーを設定し、設定したキーに対して暗号化/復号する仕組み
- HMAC暗号
- 同じキーでもcontext(おそらく別のアプリケーション等の復号するシーンが違う)で暗号用の鍵を派生鍵として利用できる
- rails版 https://github.com/hashicorp/vault-rails
- node版 https://github.com/linyows/sequelize-vault
$ cd ../; mkdir express; cd express
$ files=(express.Dockerfile update_ca_certs.sh express.consul-template.conf express.ini)
$ for ((i = 1; i <= ${#files[@]}; i++)) { curl -O -s https://gist.githubusercontent.com/$gist/raw/${files[i]} }
$ mv express.Dockerfile Dockerfile
# CA公開鍵をイメージに含める(要修正 issuing_caのみにする)
$ vault write pki/issue/service-dot-consul common_name=vault.service.consul > ca.crt
or
curl -H "X-Vault-Token: $token" -X PUT -k -d '{"common_name":"vault.service.consul"}' https://127.0.0.1:8200/v1/pki/issue/service-consul | jq '.data.issuing_ca' | sed -e 's/"//g' | sed -e 's/\\n/\n/g' > /usr/share/ca-certificates/extra/Vault_Root_CA.crt
# ビルド
$ docker build -t linyows/express .
# トークンを環境変数に設定する
$ docker run -e VAULT_TOKEN=$(cat ~/.vault-token) -e VAULT_ADDR=https://vault.service.consul:8200 \
-e DB_HOST=mysql.node.consul -e DB_PORT=3306 \
-p 3000:3000 --dns=127.0.0.1 --dns=8.8.8.8 --add-host=consul:$ip --name express -h express -d linyows/express
# ビルド時にcaの公開鍵を配置しているので、Vaultサーバを認証できる確認
$ docker exec -it express curl https://vault.service.consul:8200/v1/
{"errors":[]}
# 状態確認
$ consul members
Node Address Status Type Build Protocol DC Segment
consul 172.17.0.2:8301 alive server 1.2.0 2 dc1 <all>
express 172.17.0.6:8301 alive client 1.2.0 2 dc1 <default>
mysql 172.17.0.5:8301 alive client 1.2.0 2 dc1 <default>
vault-1 172.17.0.3:8301 alive client 1.2.0 2 dc1 <default>
vault-2 172.17.0.4:8301 alive client 1.2.0 2 dc1 <default>
# 有効化
$ vault secrets enable transit
Success! Enabled the transit secrets engine at: transit/
# 暗号化を一意にするには以下の2つのオプションが必要になる
$ vault write -f transit/keys/express_users_email convergent_encryption=true derived=true
Success! Data written to: transit/keys/express_users_email
$ docker exec -it express supervisorctl start consul-template
consul-template: started
$ docker exec -it express supervisorctl status
consul RUNNING pid 9, uptime 0:03:27
consul-template RUNNING pid 48, uptime 0:00:21
express STOPPED Not started
# consul-templateにより作成されたDB接続情報を確認
$ docker exec -it express cat .env
DB_USER='v-root-express-yJYCFHG8lHIrQUjW1'
DB_PASS='A1a-68LCi7xme2qpijfY'
$ docker exec -it express supervisorctl start express
express: started
$ docker exec -it express supervisorctl status
consul RUNNING pid 9, uptime 0:06:11
consul-template RUNNING pid 51, uptime 0:02:01
express RUNNING pid 99, uptime 0:00:08
# webで暗号化/復号を確認してみましょう
$ open http://localhost:3000/
# 実際に暗号化したもののみが保存されているか確認しよう
# mysql -u root -h localhost -P 13306 -p express
> select * from users;
- いろんな認証についての違い
- GUIを通してpolicyをさわってみよう
# 有効化
$ vault auth enable github
# GitHubの認証に使う組織を指定
$ vault write auth/github/config organization=tech
# GitHub EnterpriseであればAPI urlを設定
$ vault write auth/github/config organization=tech base_url=https://ghe.com/api/v3/
# 既存ポリシーの確認
$ vault read sys/policy
Key Value
--- -----
keys [default root]
policies [default root]
# ポリシーを作成
cat <<EOF > dev.hcl
path "pki/*" {
capabilities = [ "create", "read", "update", "delete", "list" ]
}
path "auth/token/*" {
capabilities = [ "create", "read", "update", "delete", "list" ]
}
path "sys/policy/*" {
capabilities = [ "create", "read", "update", "delete", "list" ]
}
path "sys/auth/*" {
capabilities = [ "create", "read", "update", "delete", "sudo" ]
}
#path "transit/*" {
# capabilities = [ "create", "read", "update", "delete", "list" ]
#}
EOF
# ポリシーを登録する
vault policy write dev dev.hcl
# チームにポリシーを割り当てる
$ vault write auth/github/map/teams/dev value=dev
# githubのpersonal access tokenを発行する(admin:orgに権限付与)
$ open https://github.com/settings/tokens
# vault uiのログインでgithub tokenを使ってログインしてみる
$ open https://localhost:8200/ui/
# 一旦ログアウトしてroot tokenを使ってログインしてみる(表示される秘密情報の違いを確認する
ハンズオンなので、便宜上rootトークンを使い回しましたが、実際の導入では、各トークンをpolicyと紐づけて発行して利用することになります。また、導入するシステムに応じて、鍵やトークンの受け渡しをまず最初にどうするか、一連のルーチンをどう自動化するかを設計することが重要になります。
Vaultは秘密情報管理という特性上、比較的難しいソフトウェアだと思います。しかし、これを使いこなすことによってシステムがより堅牢になるのは間違いないので、脆弱性が高そうなところ、導入しやすいところなどから取り組んでいきましょう。
https://vimeo.com/126398526