Vault Cluster の構築

最近、HashiCorp 社が OSS として提供する Vault を触る機会があったので、忘れない内に、記事としてまとめることにしました。この記事は 'Vault 1.18.3' を使用しました。

## Vault とは
たくさんの記事が投稿されていて、改めてまとまるまでも無い感じですが、簡単に何をするソフトなのかを要約します。

Vaultは、シークレットと暗号化の管理システムです。シークレットには、「トークン、APIキー、パスワード、暗号化キー、証明書」など、大切な情報や守り、IT資源へのアクセスをコントロールするための「文字列」です。これらのシークレットは、第三者に流出して悪用されないために、厳重に管理しなければなりません。この管理は、関わる人が多くなると難しくなっていきます。この問題を解決するのが Vault です。

厳重な管理の方法として、Vaultのワークフローには、４つの段階があります。

- 認証：クライアントが本人であることを認証すると、トークンが生成され、ポリシーに関連づける。認証方法は、パスフレーズなどから選択可能
- 検証：クライアントを検証するために、Github,LDAPなどと連携してクライアントを検証
- 承認：Vault内の特定のPathと操作(作成、読み取り、更新など）を許可または禁止
- アクセス：クライアントのIDと紐も付いたトークンを発行、これを用いて、シークレット、キー、暗号化機能へのアクセスを許可

Vaultは、安全なシークレットストレージ、動的なシークレット、データの暗号化、有効期限と更新、失効などの機能を提供します。

この様な役割を担うVaultは、複数のシステムから認証／認可のために使用されます。そのため、Vaultが、停止すると、システム全体が使用不能に陥る事が懸念されます。そのことから、Vaultはクラスタ化して可用性を高める必要があります。

## Vaultのクラスタ構成
Vaultクラスタの構成は、「自動アンシールのためのVaultサーバー」、「Vaultクラスタのメンバーサーバたち」からなります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42829/56de272c-650b-a379-e29e-59662af6080a.png)

「自動アンシールのためのVaultサーバー」は、どうして必要なのか？、についての説明から始めます。Vaultサーバーの起動するとシール（封印）された状態で起動します。利用者はシール（封印）を解かなければ、Vaultの機能や保存データにアクセスできません。シールを解くためのキーは、Vaultのサーバープロセスのメモリ上だけに保存されています。そのため、Vaultのサーバープロセスを再起動するだけで、封印されてしまい、利用できなくなってしまいます。

封印を解くキーを外部から与える構造は、セキュリティを高めるために役立つのですが、セキュリティが確保されたエリアで運用するには、手間がかかります。Vaultクラスタのメンバーの一つが再起動すると、人手で封印解除のキーをインプットするまでは、クラスタに復帰できません。その問題を解決するために、提供されているのが、自動アンシール(Auto Unseal)の機能です。このVaultサーバーは、予めトークンを持ったサーバーだけに、アンシールのキーを提供します。

Vaultクラスタのメンバーの間では、Raftアルゴリズムによってリーダーが決められます。データの更新は、リーダーだけが担当して、他のメンバーは更新をフォローして同期します。これによって３重化を実現して、可用性を確保します。VaultクラスタのメンバーのVaultサーバーは、起動過程で自動アンシールのサーバーから封印解除のキーを受け取り、自身で封印を解除して、クラスタのメンバーに参加する仕組みです。

Vaultクラスタのメンバーが、自動アンシールのサーバーから封印解除のキーを受け取るためには、起動時に自動アンシールのVaultサーバーが発行したトークンを持っていなければなりません。また、自動アンシールの役割のVaultサーバーは、起動時の自身の封印解除のキーが必要です。

## Vaultのビルド済み実行形式のインストール

HashiCorpのWebサイトから、ビルド済みの実行形式をダウンロードします。 

```
curl -OL https://releases.hashicorp.com/vault/1.18.3/vault_1.18.3_linux_amd64.zip
```

圧縮を解凍して、`/usr/local/bin/vault`として配置します。このコマンド一つで、サーバーとクライアントを兼ねていますので、自動アンシールのサーバー、クラスタメンバーのサーバー、操作用ワークステーションに、それぞれ一つづ配置すればインストールは完了です。


## 自動アンシールのVaultサーバーの構築
このサーバーは、スタンドアロンのサーバーとして構築します。そして、初期化後、封印を解いた後、rootでログインして、自動アンシールの設定を実施します。

### Configファイル

Vaultを起動するためのコンフィグファイルです。このファイルの中で、シークレットストレージを'/var/vault-data'に置いています。起動前に、予めディレクトリを作成する必要があります。

このサーバーのIPアドレスは172.16.0.9なので、api_addr にアドレスをセットします。また、listenerには、ローカルホスト(127.0.0.1)のアクセスを受けられる様に、`0.0.0.0`をセットします。
TLSの暗号通信のため、認証局証明書(ca.pem)、サーバー証明書(vault-node1.pem)、証明書鍵(vault-node1-key.pem)をセットします。ここでは、証明書の作り方は扱いません。

UIを使用すると、メニュー構造からシステムのアーキテクチャなども伺い知れるので便利ですから、`ui = true`にします。

```file:/etc/vault/config.hcl
storage "file" {
   path    = "/var/vault-data"
}

listener "tcp" {
  address               = "0.0.0.0:8200"
  cluster_address       = "0.0.0.0:8201"
  tls_client_ca_file    = "/etc/vault/ca.pem"
  tls_cert_file         = "/etc/vault/vault-node1.pem"
  tls_key_file          = "/etc/vault/vault-node1-key.pem"
}

ui = true
disable_mlock = true
cluster_name  = "vault-cluster"
api_addr      = "https://172.16.0.9:8200"
```

### Systemdのユニットとして起動するためのファイル

次の`vault.servie`を`/lib/systemd/system`の下に配置することで、`systemctl start vault`で起動できるようになります。ただし、初回起動時は、未初期化、封印（シール）された状態ですから、クライアントからコマンドを叩いて、利用可能な状態にする必要があります。

```file:/lib/systemd/system/vault.service
[Unit]
Description=vault - Manage Secrets & Protect Sensitive Data
Documentation=https://developer.hashicorp.com/vault
After=network.target
Wants=network-online.target

[Service]
Type=notify
User=root
PermissionsStartOnly=true
ExecStart=/usr/local/bin/vault server -config /etc/vault/config.hcl
Restart=on-abnormal
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
```

### 封印解除

`systemctl start vault`を実行して、起動できるか確認してみます。無事に起動できたら、次へ進みます。もし失敗するようであれば、ターミナルのコマンドラインから `vault server -config /etc/vault/config.hcl` を実行することで、エラーメッセージをコマンドの応答として確認できます。

### Vaultサーバーの初期化と封印解除 

Vaultサーバーの起動が完了したら、Vaultクライアントからサーバーを初期化します。'vault server'とすることでサーバーとして、単に`vault`としてコマンドを実行すると、クライアンで起動します。

クライアントが、サーバーに接続するためには、TLS暗号通信、サーバーのアドレスなど、必要な環境変数をセット視します。
```
export VAULT_SKIP_VERIFY=1
export VAULT_CA_CERT=/etc/vault/ca.pem
export VAULT_CLIENT_CERT=/etc/vault/vault-node1.pem
export VAULT_CLIENT_KEY=/etc/vault/vault-node1-key.pem
export VAULT_ADDR=https://127.0.0.1:8200
```

環境変数が効いていれば、サーバーの状態を次のコマンドで確認できます。

```
vault status
```

Vaultサーバーを初期化します。
実行結果として、封印解除のキー、root トークンなどが表示されますので、大切に保管しておきます。

```
vault operator init -format=json -key-shares 1 -key-threshold 1
```

Vaultは封印された状態で起動します。その封印を解除するために、封印解除のキーが必要です。以下のコマンドで、封印を解除します。
```
vault operator unseal <UNSEAL KEY> 
```

### Auto unsealサーバーの設定 

rootでログインすることで、Vaultサーバーのコマンドを受け付ける様になります。
```
vault login <ROOT TOKEN>
```

シークレットの転送機能を有効化して、転送キーをセットいます。
```
vault secrets enable transit
vault write -f transit/keys/autounseal
```

転送キー対して、ポリシーを設定します。ポリシーには、可能な操作を記載します。

```
vault policy write autounseal -<<EOF
path "transit/encrypt/autounseal" {
   capabilities = [ "update" ]
}

path "transit/decrypt/autounseal" {
   capabilities = [ "update" ]
}
EOF
```

自動封印解除対象のサーバーのために、トークンを生成します。
ここで作成したトークンは、厳重に保存します。
```
vault token create -orphan -policy="autounseal" \
   -wrap-ttl=120 -period=24h \
   -field=wrapping_token > wrapping-token.txt
vault unwrap -field=token $(cat wrapping-token.txt)
rm wrapping-token.txt
```

以上で、オートアンシールのためのサーバーの設定は完了です。

## Vaultクラスタメンバーのサーバー設定

Vaultクラスタを構成するサーバーを、最初に１つだけ起動して、初期化します。

### Systemdから起動するためのファイル

systemd から起動するためのファイルを /lib/systemd/systemの下に配置します。
ポイントは、`Environment=VAULT_TOKEN='から始まる行で、前述で取得してた`hvs.CAESIB*****`から始まるトークンをセットします。

```
# /lib/systemd/system/vault.service
[Unit]
Description=vault - Manage Secrets & Protect Sensitive Data
Documentation=https://developer.hashicorp.com/vault
After=network.target
Wants=network-online.target

[Service]
Environment=VAULT_TOKEN=hvs.CAESIB******************************************
Type=notify
User=root
PermissionsStartOnly=true
ExecStart=/usr/local/bin/vault server -config /etc/vault/config.hcl
Restart=on-abnormal
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
```

### VaultのConfigファイル

`{{ ansible_facts.all_ipv4_addresses[0] }}`の部分は、サーバーのIPアドレスで置き換えます。また、`{{ ansible_facts.hostname }}`はホスト名で置き換えます。

`leader_api_addr`から始まる行には、それぞれ、VaultクラスタのメンバーとなるIPアドレスをセットします。

起動前に、Vaultが保存するデータのために、`/var/vault-data`のディレクトリを作成しておきます。また、`/etc/vault`の下に認証局証明書、サーバー証明書、鍵を配置しておきます。

```
api_addr      = "https://{{ ansible_facts.all_ipv4_addresses[0] }}:8200"
cluster_addr  = "https://{{ ansible_facts.all_ipv4_addresses[0] }}:8201"
cluster_name  = "vault-cluster"
disable_mlock = true
ui            = true

storage "raft" {
   path       = "/var/vault-data"
   node_id    = "{{ ansible_facts.hostname }}"
   retry_join {
      leader_tls_servername   = "vault-cluster"
      leader_api_addr         = "https://172.16.31.11:8200"
      leader_ca_cert_file     = "/etc/vault/ca.pem"
      leader_client_cert_file = "/etc/vault/vault-node1.pem"
      leader_client_key_file  = "/etc/vault/vault-node1-key.pem"
   }
   retry_join {
      leader_tls_servername   = "vault-cluster"
      leader_api_addr         = "https://172.16.31.12:8200"
      leader_ca_cert_file     = "/etc/vault/ca.pem"
      leader_client_cert_file = "/etc/vault/vault-node1.pem"
      leader_client_key_file  = "/etc/vault/vault-node1-key.pem"
   }
   retry_join {
      leader_tls_servername   = "vault-cluster"
      leader_api_addr         = "https://172.16.31.13:8200"
      leader_ca_cert_file     = "/etc/vault/ca.pem"
      leader_client_cert_file = "/etc/vault/vault-node1.pem"
      leader_client_key_file  = "/etc/vault/vault-node1-key.pem"
   }
}

listener "tcp" {
  address               = "0.0.0.0:8200"
  cluster_address       = "0.0.0.0:8201"
  tls_client_ca_file    = "/etc/vault/ca.pem"
  tls_cert_file         = "/etc/vault/vault-node1.pem"
  tls_key_file          = "/etc/vault/vault-node1-key.pem"
}

seal "transit" {
  address             = "https://172.16.0.9:8200"
  disable_renewal     = "false"
  key_name            = "autounseal"
  mount_path          = "transit/"
  tls_cert_file       = "/etc/vault/vault-node1.pem"
  tls_key_file        = "/etc/vault/vault-node1-key.pem"
  tls_client_ca_file  = "/etc/vault/ca.pem"
  tls_skip_verify    = "true"
}
```

以上の設定が完了したら、`systemctl start vault` で起動できるか確認しておきます。

## クラスタメンバーの初期化

Vaultクライアントのための環境変数をセットしておきます。
```
export VAULT_SKIP_VERIFY=1
export VAULT_CA_CERT=/etc/vault/ca.pem
export VAULT_CLIENT_CERT=/etc/vault/vault-node1.pem
export VAULT_CLIENT_KEY=/etc/vault/vault-node1-key.pem
export VAULT_ADDR=https://172.16.31.11:8200
```

Vaultクライアントが、サーバーと接続できるか確認します。
```
vault status
Key                      Value
---                      -----
Seal Type                transit
Recovery Seal Type       n/a
Initialized              false
Sealed                   true
<以下省略>
```

サーバーを初期化します。
```
vault operator init
```

表示されたキーを大切に保存しておきます。
```
Recovery Key 1: vzdo7uWa/q2W2K6HZkN+LdzTWs05kRZn3QkjDImK8J6S
Recovery Key 2: 4hQTImabMwOL1+/JeysACP2UY4xqIZ6JX3F3Bghnm4cU
Recovery Key 3: 5TK3LrcsGtmPuXKzAyozwE6TmsCU7DaQ5SroasiSfdWn
Recovery Key 4: VJcHeofUZyftzdtFzkyLLRroVq59pA5zK/Y69ePSKBsb
Recovery Key 5: DMvsZvtxS3OCTCSz5uGZhD4xk7JniApfUP2x6C6Xwt5R

Initial Root Token: hvs.1x3NPGQV8BkfgIzkcCKVsvDG

Success! Vault is initialized
```

### rootでログインして、クラスタ状態を確認します。
クラスタに参加するvaultサーバーにログインするには、前述の初期化で表示された`Initial Root Token:`を使います。

```
vault login <hvs.から始まるトークン>
```

クラスタのメンバーをリストできます。
```
$ vault operator raft list-peers
Node     Address              State     Voter
----     -------              -----     -----
node1    172.16.31.11:8201    leader    true
```

### クラスタメンバーの追加

`/etc/vault/config.hcl`,`/var/vault-data` などの設定を実施して、`systemctl start vault`を実行することで、参加するメンバーが増えていきます。 

```
vault operator members
Host Name    API Address                  Cluster Address              Active Node    Version    Upgrade Version    Redundancy Zone    Last Echo
---------    -----------                  ---------------              -----------    -------    ---------------    ---------------    ---------
node1        https://172.16.31.11:8200    https://172.16.31.11:8201    true           1.18.3     1.18.3             n/a                n/a
node2        https://172.16.31.12:8200    https://172.16.31.12:8201    false          1.18.3     1.18.3             n/a                2025-02-02T12:46:13Z
node3        https://172.16.31.13:8200    https://172.16.31.13:8201    false          1.18.3     1.18.3             n/a                2025-02-02T12:46:16Z
```

Vault Clusterの構築は以上です。パブリッククラウドでは、自動アンシールのサービスを実施していますので、使用するクラウドのドキュメントを参照ください。


## 参考資料

* [Auto-unseal Vault using transit secrets engine](https://developer.hashicorp.com/vault/tutorials/day-one-raft/autounseal-transit)
* [Configure Vault cluster with Integrated Storage](https://developer.hashicorp.com/vault/tutorials/raft/raft-storage)
* [Operations quick start](https://developer.hashicorp.com/vault/docs/get-started/operations-qs)