※当サイトの記事には、広告・プロモーションが含まれます。

HashiCorp Vault環境をLinux上に構築して、REST APIでSecretを取得してみる

www.itmedia.co.jp

 「そんなことより、うちの猫ちゃんはね」──詐欺電話にゆったりした優しい声で延々と応対し、相手の時間を浪費させるAIおばあちゃん「デイジー」(Daisy)。英通信キャリア大手のO2は11月14日(現地時間)、電話詐欺対策の一環として、この生成AIボットを発表した。詐欺師から時間を奪うことが目的だ。

詐欺電話に延々と応対し時間を浪費させるAIおばあちゃん、O2が開発 - ITmedia NEWS

 どうやって詐欺師にデイジーに電話をかけさせているのかについての説明はないが、O2は顧客に対し、怪しい電話やテキストメッセージをO2の専用番号7726に転送するよう呼びかけている。

詐欺電話に延々と応対し時間を浪費させるAIおばあちゃん、O2が開発 - ITmedia NEWS

⇧ う~む...

  1. 電話が詐欺を目的としたもの
    → 顧客に受け取って欲しくない
  2. 電話が通常の業務を目的としたもの
    → 顧客に受け取って欲しい

⇧ かどうかを「顧客」側で判断ができないところが問題の焦点だと思うんだが、その一番重要な部分が解決できていないんよね...

なので、

  1. 顧客
  2. サービス提供企業
  3. 捜査機関

⇧「サービス提供企業」や「捜査機関」向けの機能ってことになるんかね?

「顧客」は、AIが有効かどうかを効果検証するための人柱みたいなものかしらね...

言い方を悪くすれば、社会実験の被検体的な扱いってことですな...

社会実験(しゃかいじっけん)とは、新たな制度や技術などの施策を導入する際、場所と期間を限定して試行することで、有効性を検証したり問題を把握し、時にはその施策の本格導入を見送るかを判断する材料とするもの。実証実験(じっしょうじっけん)、パイロット事業とも呼ばれる

社会実験 - Wikipedia

地域住民との意見交換ならびに周知と合意形成も兼ねている。

社会実験 - Wikipedia

⇧「顧客」に対して、説明はしてるのか気になりますな。

HashiCorp Vault環境を構築するには

前に、

ts0818.hatenablog.com

⇧「HashiCorp Vault」の「REST API」を利用するには的な話について情報を収集してましたが、実際に環境を構築してみようと。

公式のドキュメントによると、

developer.hashicorp.com

⇧ メジャーどころのLinuxディストリビューションのパッケージ管理ツールでもインストール可能っぽいとありますな。

Dockerコンテナとかでも導入できるんだとは思われますが、今回は、普通に仮想マシンにインストールしていく感じで。

developer.hashicorp.com

⇧ 上記のタブでLinux ディストリビューションを選択し、コマンドを実行でインストールしてから、

developer.hashicorp.com

Linuxのサービスとして起動すれば良い模様。

HashiCorp Vault環境をLinux上に構築してみる

「WSL 2(Windows Subsystem for Linux 2)」の「Rocky Linux 9」環境にインストールしてみることにします。

まずは、SSHログイン。

CentOS/REHL」向けの構築手順を実施する感じになりそうです。

では、早速、インストール。

まずは、依存パッケージをインストール。

■依存パッケージをインストール

sudo yum install -y yum-utils    

「HashiCorp Vault」を含んだリポジトリを追加します。

■「HashiCorp Vault」を含んだリポジトリを追加

sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo    

そしたらば、「HashiCorp Vault」をインストール。

■「HashiCorp Vault」をインストール

sudo yum -y install vault    

インストールできたようです。

続いて、サービスとして起動していきます。

まずは、環境変数を設定する感じらしい。

環境変数「VAULT_CONFIG」を設定

VAULT_CONFIG=/etc/vault.d

whichコマンドが無かったので、「command -v vault」の結果を設定しました。

環境変数「VAULT_BINARY」を設定

command -v vault
VAULT_BINARY=<command -v vaultの実行結果>

「systemd」の「サービス」として「vault.service」を追加するために、「HashiCorp Vault」用の「ユニットファイル」を作成します。

環境変数「VAULT_BINARY」を設定

tee /lib/systemd/system/vault.service <<EOF
[Unit]
Description="HashiCorp Vault"
Documentation="https://developer.hashicorp.com/vault/docs"
ConditionFileNotEmpty="${VAULT_CONFIG}/vault.hcl"

[Service]
User=vault
Group=vault
SecureBits=keep-caps
AmbientCapabilities=CAP_IPC_LOCK
CapabilityBoundingSet=CAP_SYSLOG CAP_IPC_LOCK
NoNewPrivileges=yes
ExecStart=${VAULT_BINARY} server -config=${VAULT_CONFIG}/vault.hcl
ExecReload=/bin/kill --signal HUP
KillMode=process
KillSignal=SIGINT

[Install]
WantedBy=multi-user.target
EOF

「HashiCorp Vault」の「ユニットファイル」の権限を644に設定する。

■「/lib/systemd/system/vault.service」ファイルの権限を644に設定

chmod 644 /lib/systemd/system/vault.service

■「systemd」の設定を再読み込み

systemctl daemon-reload

■「vault.serviceY」を自動起動できるように設定

systemctl enable vault.service

■「vault.serviceY」を起動

systemctl start vault.service

⇧ サービス自体は起動できた模様。

ブラウザで「http://localhost:8200」にアクセス。

⇧ これが正しい状態なのか分からんのだが...

UI画面にアクセスできるようにしてみる

「HashiCorp Vault」では、「UI」画面も用意されているようなのだけど、

developer.hashicorp.com

⇧ 説明が分かり辛いのよね...

そもそも、設定ファイルはどれを編集すれば良いのか?

developer.hashicorp.com

Vault configuration

Outside of development mode, Vault servers are configured using a file. The format of this file is HCL or JSON.

https://developer.hashicorp.com/vault/install

⇧ どのファイルを編集すれば良いかは記載してくれないと...

とりあえず、「/etc/vault.d/vault.hcl」を編集することして、バックアップしたらば、編集します。

何やら、デフォルトで「ui = true」になってました。何も変更せず、viエデイタを終了します。

https://localhost:8200/ui」でアクセスすると、以下の画面が出て「詳細設定」をクリックすると、

localhost にアクセスする(安全ではありません)」のリンクが表示されるで、クリックすると、

「UI」画面が表示されました。

本当は「key」を複数作って管理するのが良いとは思いますが、今回は、1つで。

一応、「initial root token」「key1」の内容を適当なテキストにコピペしておいて、「Download keys」を押下した後で、「Contine Unseal」を押下。(「initial root token」の値は、ダウンロードするファイルに記載があったが、「key1」の値の用途は分からず...)

ダウンロードしたファイルの「keys」の値を入力し、「Unseal」ボタンを押下。(「key share」に指定した数だけ繰り返すことになるっぽい。)

「Method」は「Token」で、「Token」に「initial root token(root_token)」の値を設定して「Sign in」を押下。

ログインできたっぽい。

ユーザーっぽいアイコンをクリックすると「Copy token」とあるので、コピペし適当なテキストに貼り付けして確認してみる。

「root token」と同じ値だったのだけど、本来は、「root token」は「Server」起動時にしか発行されないらしいので、「一般ユーザー」は「root token」とは異なる値になると思われる。

公式のドキュメントによると、

developer.hashicorp.com

Token types

There are three types of tokens. On this page service tokens and batch tokens are outlined, while recovery tokens are covered separately in their own page. A section near the bottom of this page contains detailed information about their differences, but it is useful to understand other token concepts first. The features in the following sections all apply to service tokens, and their applicability to batch tokens is discussed later.

https://developer.hashicorp.com/vault/docs/concepts/tokens

Token prefixs

Tokens have a specific prefix that indicates their type. As of Vault 1.10, this token format was updated. The following table lists the prefix differences. This format pattern and its change also apply for recovery tokens. After the prefix, a string of 24 or more randomly-generated characters is appended.

https://developer.hashicorp.com/vault/docs/concepts/tokens

Root tokens

Root tokens are tokens that have the root policy attached to them. Root tokens can do anything in Vault. Anything. In addition, they are the only type of token within Vault that can be set to never expire without any renewal needed. As a result, it is purposefully hard to create root tokens; in fact there are only three ways to create root tokens:

https://developer.hashicorp.com/vault/docs/concepts/tokens

⇧ とあり、「root token」は3つの条件の内のいずれかを満たす場合に作成されるとあるので。

「Secrets Engines」を確認してみたところ、デフォルトで「cubbyhole」という「Secret Engine」が用意されている。

公式のドキュメントによると、

developer.hashicorp.com

Cubbyhole secrets engine

The cubbyhole secrets engine is used to store arbitrary secrets within the configured physical storage for Vault namespaced to a token. In cubbyhole, paths are scoped per token. No token can access another token's cubbyhole. When the token expires, its cubbyhole is destroyed.

https://developer.hashicorp.com/vault/docs/concepts/tokens

Also unlike the kv secrets engine, because the cubbyhole's lifetime is linked to that of an authentication token, there is no concept of a TTL or refresh interval for values contained in the token's cubbyhole.

https://developer.hashicorp.com/vault/docs/concepts/tokens

Writing to a key in the cubbyhole secrets engine will completely replace the old value.

https://developer.hashicorp.com/vault/docs/concepts/tokens

⇧「kv secrets engine」とは異なる挙動になるそうな。

が、そもそもとして、「Python」の「HAVC」というライブラリで対応してると思われる「Secrets Engines」のリストに「Cubbyhole」というものが存在しないのだが...

となると、「REST API」の動作を確認するには、まずは、「Secrets Engine」を追加するところから始めないといけないわけですか...

追加してみますか。

スタンダードなのがどれか分からんのだけど、「KV」を選択してみる。

とりあえず、「Maximum number of versions」だけ変更して、「Method Options」とかも一切変更せずに「Enable engine」を押下。

「Configuration」タブによると、「Path」が「kv/」となっている。

公式のドキュメントによると、

developer.hashicorp.com

⇧とあり、「<key_path>」に該当するのが「Path」ってことになるんかね?

と思ったら、

作成する「secret」によって変わってくるみたいね...

試しに作成してみる。

API path」と「CLI path」の2つ生成されているんだが、「REST API」を利用する時は「API path」の方を利用すれば良いんかね?

というか、「Secrets Engine」自体は「version 2」で作成しているんだが、「API path」で「v1」となっているのが謎過ぎるんだが...

ただ、分かったのは、管理する情報が多い場合、GUI上での登録は無理ということですな...
1,000,000件、手動で登録とか無理ゲー過ぎて憤死する...

CSVファイルとかに登録するデータを用意しておいて、シェル、乃至は、Pythonなどで登録する用のスクリプトを組んで、「REST API」を利用して登録するしか無さそうね...

GUI上から、CSVファイルとかで「secret」の登録をできるようにしてくれたら良かったんですけどね...

REST API」のリクエストの制限とかは自分たちで設定する必要があるってことかね?

developer.hashicorp.com

過剰なリクエストを許容すると、サーバーに負荷がかかって、システムダウンしたり、クラウド上に構築してる場合、コストが怖いですしな...

とりあえず、適当なプログラミング言語向けの「HTTP Client」を利用してREST APIを実行しても受け付けてくれる情報は用意できたということですかね。

次回は、Python向けの「HTTP Client」のライブラリである「HAVC」で「HashiCorp Vault」の「REST API」を実行できるか試していく感じですかね。

Policyの設定が必要なのか?

何やら、

developer.hashicorp.com

developer.hashicorp.com

⇧「Policy」を追加する話が出てくる。

で、どこに作成すれば良いかの説明を端折るのは伝統芸みたいな感じなのかね?

とりあえず、「/etc/vault.d/」配下に作成することにしました。

何やら、「Vault」に対して何か行いたい場合は、ログインが必要と。

ログイン後、「Policy」を追加。

で、残念ながら、「Vault」は「systemd」のサービスとして起動しており、「ユニットファイル」で参照しているのが「/etc/vault.d/vault.hcl」だけになっているんよね...

「ユニットファイル」の「ExecStart」のコマンドに、configを追加して作成した「/etc/vault.d/admin-policy.hcl」を指定。

で、サービス再起動。

ただ、「Policy」の追加をしなくても、問題なかったような気がするんですが、「HashiCorp Vault」を動作させる上で必要な情報がハッキリしないので分からない...

HashiCorp Vault環境をLinux上に構築して、REST APIでSecretを取得してみる

「HashiCorp Vault」の「REST API」を、「Python」の「HTTP Client」ライブラリである「HVAC」で試してみる。

「havc」は、事前に、pip installしておく必要がある。

で、Pythonソースコードを実行。「適切なトークン」の部分はご自身の環境のものに置き換えてください。(自分の環境では、「Token Type」が「Service tokens」の形式のもので動きました。ただし、「Service tokens」且つ「Root tokens」でもある状態での動作確認になってます。)

■E:\soft_work\python\vault\app\src\main\py\main.py

import hvac

################################################
#
# Authenticate for using Vault REST API
#
################################################
vault_token="hvs.適切なトークン"
vault_base_endpoint_url='https://localhost:8200'

client = hvac.Client(
    url=vault_base_endpoint_url
    , token=vault_token
    , verify=False
)

auth_result = client.is_authenticated()
print(auth_result)

################################################
#
# using Vault REST API
#
################################################

# HashiCorp VaultのUI画面の「API path」のパス
#path="/v1/kv/data/machine/company/music/maneki-cat/01/oxidized/config/setting/machine.config.db"
# 「HVAC」で受け入られる形に加工
path="machine/company/music/maneki-cat/01/oxidized/config/setting/machine.config.db"
# 「Secret」の値のバージョン、指定しない方が良いかも
version=1
# HashiCorp VaultのUI画面の「Secret」の「Configuration」タブで確認できる「Path」の値の末尾の「/」を除いた値
mount_point="kv"

# HashiCorp Vaultへ「Secret」の取得リクエストを実行する
secret_version_response = client.secrets.kv.v2.read_secret_version(
    path=path
    ,version=version
    ,mount_point=mount_point
)

# 取得結果
print(secret_version_response)    

■実行結果

PS E:\soft_work\python\vault> & e:/soft_work/python/vault/.venv/Scripts/python.exe e:/soft_work/python/vault/app/src/main/py/main.py
E:\soft_work\python\vault\.venv\Lib\site-packages\urllib3\connectionpool.py:1099: InsecureRequestWarning: Unverified HTTPS request is being made to host 'localhost'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
  warnings.warn(
True
e:\soft_work\python\vault\app\src\main\py\main.py:36: DeprecationWarning: The raise_on_deleted_version parameter will change its default value to False in hvac v3.0.0. The current default of True will presere previous behavior. To use the old behavior with no warning, explicitly set this value to True. See https://github.com/hvac/hvac/pull/907
  secret_version_response = client.secrets.kv.v2.read_secret_version(
E:\soft_work\python\vault\.venv\Lib\site-packages\urllib3\connectionpool.py:1099: InsecureRequestWarning: Unverified HTTPS request is being made to host 'localhost'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
  warnings.warn(
{'request_id': '0b46b3c8-0bd0-7d2d-f1f5-e4650138fb58', 'lease_id': '', 'renewable': False, 'lease_duration': 0, 'data': {'data': {'enable_password': 'enable_pass', 'host_name': 'localhost', 'ip_address': '192.168.56.103', 'model_map_key': 'Custom_Oxidized_Model', 'password': 'oxidized_pass', 'user_name': 'oxidized_user'}, 'metadata': {'created_time': '2024-11-16T09:09:04.317201048Z', 'custom_metadata': None, 'deletion_time': '', 'destroyed': False, 'version': 1}}, 'wrap_info': None, 'warnings': None, 'auth': None, 'mount_type': 'kv'}    

⇧ という感じで、レスポンス部分だけを抜粋すると、

■実行結果: HashiCorp Vaultへ「Secret」の取得リクエストを実行した結果のレスポンス部分

{
  'request_id': '0b46b3c8-0bd0-7d2d-f1f5-e4650138fb58',
  'lease_id': '',
  'renewable': False,
  'lease_duration': 0,
  'data': {
    'data': {
      'enable_password': 'enable_pass',
      'host_name': 'localhost',
      'ip_address': '192.168.56.103',
      'model_map_key': 'Custom_Oxidized_Model',
      'password': 'oxidized_pass',
      'user_name': 'oxidized_user'
    },
    'metadata': {
      'created_time': '2024-11-16T09:09:04.317201048Z',
      'custom_metadata': None,
      'deletion_time': '',
      'destroyed': False,
      'version': 1
    }
  },
  'wrap_info': None,
  'warnings': None,
  'auth': None,
  'mount_type': 'kv'
}

⇧ のようになると。

戻り値のデータ型は、「dict」ということだったが、なかなかに入れ子になっていらっしゃる...

「Secret」部分を抽出する必要があると。「dict」から「dict」を取り出す感じになると。

■レスポンスからSecret部分を抽出する

secret = secret_version_response['data']['data']    

⇧ で、問題は、「Secret」の値は「dict」型であるからして、要素が「key: value」という構成になっているのだけど、「key」の並び順が、登録した時の並び順になっていなくて、「HashiCorp Vault」側の振る舞いだとは思うのだけど強制的にアルファベットの昇順になってしまっているというね...

う~む、並び順の制御ができない以上、「key」名をフィックスして、「key」名で並び順を整理するしか無さそうね...

というのも、「Secret」で管理したいのが、

github.com

⇧「Oxidized」の「router.db」なのだが、

No router.dbの並び VaultのSecretでの並び
1 host_name enable_password
2 ip_address host_name
3 model_map_key ip_address
4 user_name model_map_key
5 password password
6 enable_password user_name

⇧ のように並び順が合わないのである...

Python側で、「HashiCorp Vault」から取得したレスポンスから「Secret」の部分を取得して、並び替えが必要になるのだが、「key」名で並び順を変更するしか無さそうなんよね...

勝手に並び順を変える「HashiCorp Vault」が完全に悪いんだけどね...

「Secret」の「key」の並び順が保証されないことについて、

developer.hashicorp.com

developer.hashicorp.com

⇧ 公式のドキュメントには記載が無いのよね...

結構、致命的な気がするんだが...

stackoverflowによると、

stackoverflow.com

⇧ テキストファイルで「Secret」に登録できるという情報があるんだが、

developer.hashicorp.com

⇧ 公式のドキュメントには、記載が無いんよね...

「HashiCorp Vault」側の意図する利用方法が分からず、なかなかに辛い...

「Oxidized」というライブラリの時は、中の人に、「Oxidized」側が期待している利用のされ方になっていない、って怒られたけど、ドキュメントに明示的に記載しといてくれないと分からんのよね...

追記

いろいろ試してみたのだけど、予め「Secret」の「Path」が分かっていないと厳しそう...

まさかの、「Secret」のlistsの取得にも「Secret」までの完全な「Path」が必要というね...

中途半端な「Path」だと、その1つ下の階層までしか取得できないという...

事前に、取得したい「Secret」の一覧をアプリケーションが稼働しているサーバー側に持たせておくしか無さそうね...

「HashiCorp Vault」側から、アプリケーションが稼働しているサーバー側に通知を送れるのであれば良いのだが、そんな機能があるのか分からんのよね...

■E:\soft_work\python\vault\app\src\main\py\main.py

import hvac

################################################
#
# Authenticate for using Vault REST API
#
################################################
vault_token="hvs.適切なトークン"
vault_base_endpoint_url='https://localhost:8200'

client = hvac.Client(
    url=vault_base_endpoint_url
    , token=vault_token
    , verify=False
)

auth_result = client.is_authenticated()
print(auth_result)

################################################
#
# using Vault REST API
#
################################################

# HashiCorp VaultのUI画面の「API path」のパス
#path="/v1/kv/data/machine/company/music/maneki-cat/01/oxidized/config/setting/machine.config.db"
# 「HVAC」で受け入られる形に加工
path="machine/company/music/maneki-cat/01/oxidized/config/setting/machine.config.db"
# 「Secret」の値のバージョン、指定しない方が良いかも
version=1
# HashiCorp VaultのUI画面の「Secret」の「Configuration」タブで確認できる「Path」の値の末尾の「/」を除いた値
mount_point="kv"

# HashiCorp Vaultへ「Secret」の取得リクエストを実行する
secret_version_response = client.secrets.kv.v2.read_secret_version(
    path=path
    ,version=version
    ,mount_point=mount_point
)

# 取得結果
print(secret_version_response)    

secrets_paths = [
  "machine/company/music/maneki-cat/01/oxidized/config/setting/machine.config.db"
  ,"machine/company/music/maneki-cat/02/oxidized/config/setting/machine.config.db"
]

# シークレットを格納する辞書
secrets = {}

for secret_path in secrets_paths:
    try:
        response = client.secrets.kv.v2.read_secret_version(
            path=secret_path 
            ,mount_point=mount_point
        )
        secrets[secret_path] = response['data']['data']  # シークレットのデータ部分を保存
    except Exception as e:
        print(f"Error fetching secret from {secret_path}: {e}")

# 取得したシークレットを表示
for path, secret in secrets.items():
    print(f"Secret at {path}: {secret}")


kv_configuration = client.secrets.kv.v2.read_configuration(
    mount_point=mount_point
)

print(f"Configuration {kv_configuration}")

path = "machine/company/music/maneki-cat"
list_response = client.secrets.kv.v2.list_secrets(
    path=path
    ,mount_point=mount_point
)

print(f"Secrets list {list_response}")

path = "machine/company/music/maneki-cat/01/oxidized/config/setting/machine.config.db"
hvac_path_metadata = client.secrets.kv.v2.read_secret_metadata(
    path=path
    ,mount_point=mount_point
)

print(f"Secret MetaData {hvac_path_metadata}")
    

毎度モヤモヤ感が半端ない…

今回はこのへんで。