この記事は さくらインターネット Advent Calendar 2023 1日目の記事です。
さくらインターネットの大久保です。ご無沙汰しております。
実は、昨日ちょっとした新機能のリリースを予定していて、今日はそれについての記事を書こうと思ってたんですが、 リリースが延期になってしまったので
代わりに、最近 HashiCorpさんのVault を触る機会があったので、今回はさくらのクラウド上で動かす方法を紹介したいと思います。(といいつつ、ほぼ自分向けメモです)
HashiCorp Vaultとは?
HashiCorpさんが開発されているオープンソースソフトウェアです。バックエンドシステムにてやりとりされるパスワードやクレデンシャルなど、シークレット情報を保管するためのシステムです。Webサイトはこちら。
様々なサイトで分かりやすい説明がなされているので、こちらではVault自体の詳細説明は割愛します。「HashiCorp Vaultとは」でググっていただくと、いろいろと情報が出てくるかと思います
「オープンソース版」に加え、有償ライセンスにて利用できる「エンタープライズ版」が存在します。エンタープライズ版については、オープンソース版に無い機能、例えば遠隔地でDRを行うための冗長化機能やHSMとの連携機能などが追加されていたり、サポートを受けることができたりします。
なお、Vaultのソースコードは、Business Source License(BSL)に基づいて配布されています。商用利用には制限が発生する場合があるため、特にクラウド/ホスティング事業者における導入については、十分に確認が必要です。昨今、BSLについては話題になりがちなので、ライセンスに違反しないように注意しましょう。
BSLに変更された経緯は、以下Publickeyさんの記事にて分かりやすく解説されています。
今回説明する構成例
さて、今回構築するサーバ構成は以下のようになります。
- 3台のサーバでVaultクラスタを組む(Raftでの冗長化)
- サーバはプライベートネットワーク(スイッチ)に接続する
- VPCルータを設置し、外部からサーバへのアクセスはVPN(WireGuard)を経由して行う
VPCルータとスイッチの作成
サーバを作成する前に、まずVPCルータとスイッチをさくらのクラウド上に作成します。
コントロールパネルから、以下のように設定をしていきます。
参考までに、WireGuardクライアントの設定はこんな感じになります。
[Interface]
PrivateKey = sPDA2ysL...
Address = 10.0.0.2/24
[Peer]
PublicKey = Bgs856f8... <= VPCルータに表示される公開鍵を入力
AllowedIPs = 192.168.1.0/24
Endpoint = 133.242.xxx.xxx:51820 <= VPCルータのグローバル側IPアドレスを入力
WireGuard安定して接続でき、シンプルで非常にいいですよね。今までのIPsecの苦労はなんだったんだろうと思います。
サーバの作成
Vaultを動作させるサーバを作成します。今回は以下の仕様で3台作成します。
- Rocky Linux 8.8
- CPU 2コア
- Memory 2GB
- SSD 20G
Raftクラスタを組む場合の推奨スペックについては、こちらにドキュメントがあります。商用環境で動作させる場合はこちらを参考にすると良いでしょう。
https://developer.hashicorp.com/vault/tutorials/day-one-raft/raft-reference-architecture
3台のホスト名とIPアドレスは以下を割り当てます。
ホスト名 | IPアドレス | |
---|---|---|
1台目 | test-vault1 | 192.168.1.11/24 |
2台目 | test-vault2 | 192.168.1.12/24 |
3台目 | test-vault3 | 192.168.1.13/24 |
さくらのクラウドのコントロールパネルから以下のようにサーバを作成します。
同じ要領で2台目、3台目も作成します。
サーバの作成が完了したら、手元の端末からWireGuard越しに各サーバにSSHログインし、以下セットアップを進めます。
Vaultのインストール
3台のサーバにてVaultをインストールします
$ sudo yum install -y yum-utils
$ sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
$ sudo yum -y install vault
インストール結果、以下のようになりました。
$ yum info vault
インストール済みパッケージ
名前 : vault
バージョン : 1.15.2
リリース : 1
Arch : x86_64
サイズ : 369 M
ソース : vault-1.15.2-1.src.rpm
リポジトリー : @System
repo から : hashicorp
概要 : Vault is a tool for secrets management, encryption as a service, and privileged access management.
URL : https://github.com/hashicorp/vault
ライセンス : BUSL-1.1
説明 : Vault is a tool for secrets management, encryption as a service, and privileged access management.
$ vault -v
Vault v1.15.2 (cf1b5cafa047bc8e4a3f93444fcb4011593b92cb), built 2023-11-06T11:33:28Z
Vaultの初期設定と起動
設定ファイルを以下の内容で作成します。こちらは1台目の例です。
/etc/vault.d/vault.hcl
storage "raft" {
path = "/opt/vault/data"
node_id = "test-vault1" <= 2台目、3台目は書き換える
}
listener "tcp" {
address = "0.0.0.0:8200"
cluster_address = "0.0.0.0:8201"
tls_disable = true
}
api_addr = "http://192.168.1.11:8200" <= 2台目、3台目は書き換える
cluster_addr = "http://192.168.1.11:8201" <= 2台目、3台目は書き換える
2台目、3台目も同様に設定ファイルを作成します。node_idとIPアドレス部分を書き換えてください。
手順簡略化のために、一旦TLSは無効化しています。TLSは、後ほどクラスタを組んだ後に有効化するとよいでしょう。
3台全てでVaultサービスを起動します。
$ systemctl start vault
$ systemctl status vault
● vault.service - "HashiCorp Vault - A tool for managing secrets"
Loaded: loaded (/usr/lib/systemd/system/vault.service; disabled; vendor preset: disabled)
Active: active (running) since Thu 2023-11-30 19:55:57 JST; 9s ago
Docs: https://developer.hashicorp.com/vault/docs
Main PID: 34035 (vault)
Tasks: 7 (limit: 12382)
Memory: 126.5M
CGroup: /system.slice/vault.service
└─34035 /usr/bin/vault server -config=/etc/vault.d/vault.hcl
11月 30 19:55:57 test-vault1 vault[34035]: Recovery Mode: false
11月 30 19:55:57 test-vault1 vault[34035]: Storage: raft (HA available)
11月 30 19:55:57 test-vault1 vault[34035]: Version: Vault v1.15.2, built 2023-11-06T11:33:28Z
11月 30 19:55:57 test-vault1 vault[34035]: Version Sha: cf1b5cafa047bc8e4a3f93444fcb4011593b92cb
11月 30 19:55:57 test-vault1 vault[34035]: ==> Vault server started! Log data will stream in below:
11月 30 19:55:57 test-vault1 vault[34035]: 2023-11-30T19:55:57.026+0900 [INFO] proxy environment: http_proxy="" https_proxy="" no_proxy=""
11月 30 19:55:57 test-vault1 vault[34035]: 2023-11-30T19:55:57.059+0900 [INFO] incrementing seal generation: generation=1
11月 30 19:55:57 test-vault1 vault[34035]: 2023-11-30T19:55:57.287+0900 [INFO] core: Initializing version history cache for core
11月 30 19:55:57 test-vault1 vault[34035]: 2023-11-30T19:55:57.287+0900 [INFO] events: Starting event system
11月 30 19:55:57 test-vault1 systemd[1]: Started "HashiCorp Vault - A tool for managing secrets".
なお、サーバ間でVaultが通信できるようにするため、一旦firewalldは無効化しておきます。
$ systemctl stop firewalld
$ systemctl disable firewalld
データベースの初期化と認証情報の生成
続いて、1台目でデータベースの初期化と認証情報の生成を行います(2台目、3台目では実行不要です)。
! vaultコマンドは、デフォルトでhttpsでvaultデーモンにリクエストを投げるようになっているため、環境変数で "http" を指定する
$ export VAULT_ADDR='http://127.0.0.1:8200'
$ vault operator init -key-shares=1 -key-threshold=1
Unseal Key 1: uMXru9Reu9...
Initial Root Token: hvs.GmzOc2Q9...
Vault initialized with 1 key shares and a key threshold of 1. Please securely
distribute the key shares printed above. When the Vault is re-sealed,
restarted, or stopped, you must supply at least 1 of these keys to unseal it
before it can start servicing requests.
Vault does not store the generated root key. Without at least 1 keys to
reconstruct the root 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
と Initial Root Token
は、後で必要になります。漏洩や紛失しないよう、厳重に保管しておきましょう。
VaultにはSeal/Unsealという仕組みが備わっています。Vaultが預かるシークレット情報などは、Unseal Keyに紐づく暗号鍵で暗号化された状態でディスクに保存されます。
このUnseal Keyは、Vault外で管理する必要があり、Vaultデーモンを起動する際に入力します。これをUnseal操作と呼びます。Unsealが成功すると、ディスクに暗号化して保存されているシークレット情報などが読み取れる状態となり、Vaultの動作が開始します。
詳しくは以下を参照ください。
https://developer.hashicorp.com/vault/docs/concepts/seal
Unseal Keyは、デフォルトではシャミアの秘密分散法により5つ生成されます。5つのうち、いずれか3つを入力することでUnsealを行えます。
今回は手順を簡略化するため、-key-shares=1
-key-threshold=1
オプションを指定し、秘密分散せずに1つのUnseal Keyを生成するようにしています。実際の運用では、Unseal Keyをどのように管理するかに応じて調整するとよいでしょう。
初期化が完了したら、一度Unseal操作を行う必要があります。
$ vault operator unseal
Unseal Key (will be hidden): <= ここに先ほど表示されたUnseal Keyを入力
Key Value
--- -----
Seal Type shamir
Initialized true
Sealed false
Total Shares 1
Threshold 1
Version 1.15.2
Build Date 2023-11-06T11:33:28Z
Storage Type raft
Cluster Name vault-cluster-1df2da86
Cluster ID dec208b8-ccfb-b043-b164-c38f6641ca53
HA Enabled true
HA Cluster n/a
HA Mode standby
Active Node Address <none>
Raft Committed Index 31
Raft Applied Index 31
上記出力中の Sealed が false になっていれば、Unseal成功です。
続いて、 vault login
コマンドを用いて、Vaultにアクセスするための認証トークンを保存しておきます。これで、vaultコマンドによる操作が可能な状態になります。
$ vault login
Token (will be hidden): <= ここに先ほど表示されたInitial Root Tokenを入力
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 hvs.GmzOc...
token_accessor D9LvgmIdJ...
token_duration ∞
token_renewable false
token_policies ["root"]
identity_policies []
policies ["root"]
ここで入力したトークンは /root/.vault-token
ファイルに記録されます。
Raftクラスタをセットアップする
1台目の準備ができたので、2台目、3台目をクラスタに参加させていきます。なお、Raftクラスタについては、以下にドキュメントがありますので、適宜参考にしてみてください。
2台目、3台目のサーバにて、以下のように1台目のIPアドレスを指定する形で、クラスタに参加します。
$ vault operator raft join http://192.168.1.11:8200
Key Value
--- -----
Joined true
ポート番号は8201ではなく、8200を指定します。設定中にcluster_addrで記載したポート番号8201は、クラスタの構築後、ノード間でraftの通信を行うために用いられます。
クラスタに参加できたらUnsealを行います。Unseal Keyは1台目で生成されたものを入力します。
$ vault operator unseal
Unseal Key (will be hidden): <= ここに1台目で生成されたUnseal Keyを入力
Key Value
--- -----
Seal Type shamir
Initialized true
Sealed true
Total Shares 1
Threshold 1
Unseal Progress 0/1
Unseal Nonce n/a
Version 1.15.2
Build Date 2023-11-06T11:33:28Z
Storage Type raft
HA Enabled true
! しばらくしてstatusを確認すると、Sealedがfalseになる
$ vault status
Key Value
--- -----
Seal Type shamir
Initialized true
Sealed false
Total Shares 1
Threshold 1
Version 1.15.2
Build Date 2023-11-06T11:33:28Z
Storage Type raft
Cluster Name vault-cluster-1df2da86
Cluster ID dec208b8-ccfb-b043-b164-c38f6641ca53
HA Enabled true
HA Cluster https://192.168.1.11:8201
HA Mode standby
Active Node Address http://192.168.1.11:8200
Raft Committed Index 187
Raft Applied Index 187
2台目、3台目のサーバにておいても、1台目と同様にloginしておきます。
$ vault login
Token (will be hidden): <= ここに1台目で生成されたInitial Root Tokenを入力
Raftクラスタに参加できたか確認します。
$ vault operator raft list-peers
Node Address State Voter
---- ------- ----- -----
test-vault1 192.168.1.11:8201 leader true
test-vault2 192.168.1.12:8201 follower true
test-vault3 192.168.1.13:8201 follower true
3台見えるようになったら、クラスタは完成です!
クラスタを組んだ場合、1台がリーダとして選出され、残りの2台はフォロワーとなります。フォロワーであるVaultが受け取ったリクエストは、自動的に内部でリーダに転送され、リーダにて処理されます。したがって、Vaultへのリクエストは、どのノードに対して送っても(フォロワーに送っても)問題ありません。
Key Value Storeにシークレット情報を格納してみる
実際にVaultにシークレット情報を格納してみましょう。
ここでは、VaultのKey Valueシークレットエンジンを用いて、パスワードっぽい文字列を保存してみます。Key Valueシークレットエンジンのドキュメントは以下にあります。
まずは、Key Valueシークレットエンジンを有効化します。以下のコマンドはどのノードで実行しても構いません。
$ vault secrets enable -path=secret kv-v2
Success! Enabled the kv-v2 secrets engine at: secret/
$ vault secrets list
Path Type Accessor Description
---- ---- -------- -----------
cubbyhole/ cubbyhole cubbyhole_13dd3f07 per-token private secret storage
identity/ identity identity_b82486a0 identity store
secret/ kv kv_08ce0a6e n/a
sys/ system system_b301d513 system endpoints used for control, policy and debugging
これで準備ができました。
値の保存、取得ができるか確認します。
$ vault kv put -mount=secret micho passwd1=himitsu passwd2=naisho
== Secret Path ==
secret/data/micho
======= Metadata =======
Key Value
--- -----
created_time 2023-11-30T13:25:32.803582885Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 1
$ vault kv get -mount=secret micho
== Secret Path ==
secret/data/micho
======= Metadata =======
Key Value
--- -----
created_time 2023-11-30T13:25:32.803582885Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 1
===== Data =====
Key Value
--- -----
passwd1 himitsu
passwd2 naisho
トランジットシークレットエンジンを使ってみる
Vaultの面白い機能としてトランジットシークレットエンジンがあります。これは、暗号鍵がVault内部に生成され、与えられたデータの暗号化や復号が行える機能です(※)。値の保存は行われません。
※ 正確には、共通鍵暗号を使用する場合は暗号化と復号、公開鍵暗号を使用する場合は署名と検証、の動作になります。
トランジットシークレットエンジンのドキュメントは以下にあります。
トランジットシークレットエンジンで内部に生成された暗号鍵は、Vaultの外に取り出すことができないようになっています。したがって、これをRoot of Trust(信頼の基点)として利用することができます。ちょうど、HSM(Hardware Security Module)のような動作をイメージすると分かりやすいでしょうか。
まず、トランジットシークレットエンジンを有効化します。
$ vault secrets enable transit
Success! Enabled the transit secrets engine at: transit/
続いてエンドポイントを作成します。これにより、暗号鍵が内部的に生成され、該当エンドポイントを通じて暗号化や復号が行えるようになります。
$ vault write -f transit/keys/micho
Key Value
--- -----
allow_plaintext_backup false
auto_rotate_period 0s
deletion_allowed false
derived false
exportable false
imported_key false
keys map[1:1701353983]
latest_version 1
min_available_version 0
min_decryption_version 1
min_encryption_version 0
name micho
supports_decryption true
supports_derivation true
supports_encryption true
supports_signing false
type aes256-gcm96
上記の通り、暗号アルゴリズムは、デフォルトでAES256-GCM96が設定されます。
では、実際にトランジットシークレットエンジンを使い、とある文字列を暗号化してみます。入力はbase64でエンコードされている必要があります。
$ vault write transit/encrypt/micho plaintext=$(echo "hello new world!" | base64)
Key Value
--- -----
ciphertext vault:v1:JDLegPK2XvGkpn2rojksRVOi0vLz7mAbVbuf2t5h2Qx8OWx9MUsYgfk0UGKH
key_version 1
今度は、これを復号してみましょう。
$ vault write transit/decrypt/micho ciphertext=vault:v1:JDLegPK2XvGkpn2rojksRVOi0vLz7mAbVbuf2t5h2Qx8OWx9MUsYgfk0UGKH
Key Value
--- -----
plaintext aGVsbG8gbmV3IHdvcmxkIQo=
$ echo aGVsbG8gbmV3IHdvcmxkIQo= | base64 -d
hello new world!
無事に復号できました。
トランジットシークレットエンジンを暗号鍵生成機として利用する
トランジットシークレットエンジンには、内部で(暗号論的安全な)乱数を生成し、それを暗号化したものを一発で出力する機能があります。
なにかしらのデータを、共通鍵暗号(AESなど)を用いて暗号化する際の暗号鍵(DEK=Data Encryption Key)を生成するような場合にこの機能が使えるでしょう。
この場合、トランジットシークレットエンジンは、DEKを暗号化する暗号鍵(KEK=Key Encryption Key)として振る舞うことになります。
例えば、AES256に使用する暗号鍵を生成したい場合は、以下のようなコマンドになります。
$ vault write -f transit/datakey/wrapped/micho bits=256
Key Value
--- -----
ciphertext vault:v1:LHadAmu31hqZ/3UcB9kisjJ+7sKiVB4bNtgJ+ZW0CSEJeKBiGuhq7kt6W8sfy44mivL74Y2JUb+2OXZS
key_version 1
この暗号化された文字列をデータベースなどに保存しておき、実際に使用する時に、復号して生の暗号鍵を取り出します。復号はさっきと同じです。
$ vault write transit/decrypt/micho ciphertext=vault:v1:LHadAmu31hqZ/3UcB9kisjJ+7sKiVB4bNtgJ+ZW0CSEJeKBiGuhq7kt6W8sfy44mivL74Y2JUb+2OXZS
Key Value
--- -----
plaintext ZnA/ewaQAQiKH+B7BDIOhtFYLv8SrcH11IXnH/OytFs=
$ echo 'ZnA/ewaQAQiKH+B7BDIOhtFYLv8SrcH11IXnH/OytFs=' | base64 -d | hexdump -C
00000000 66 70 3f 7b 06 90 01 08 8a 1f e0 7b 04 32 0e 86 |fp?{.......{.2..|
00000010 d1 58 2e ff 12 ad c1 f5 d4 85 e7 1f f3 b2 b4 5b |.X.............[|
この32バイト(256ビット)のバイナリ列をDEKとして利用することになります。
まとめ
Vaultは非常に多機能で、ここで全てを紹介しきれませんが、さくらのクラウド上でVaultクラスタを組み、簡単な利用方法を説明しました。
ここまでお読みいただきありがとうございました。
社会を支えるパブリッククラウドを一緒に作りませんか?
ところで先日、「さくらのクラウド」はデジタル庁よりガバメントクラウドとして条件付き認定をいただきました。ひとえに皆様のご支援のおかげです。改めて感謝申し上げます。
ここに記載のあるとおり、2025年度末までに全ての技術要件を満たすことが条件となっています。これから数年がかりで、機能を充足するため開発を推進し、サービス運営体制を整備していくことになります。
この大きなチャレンジを是非一緒にやってみませんか!?
ご興味ありましたら、こちらに求人情報掲載しておりますのでご覧ください。
カジュアル面談も実施していますので、気軽にお声がけいただけるとうれしいです