Linux環境にHashiCorp Vaultを導入:シークレットの一元管理と認証情報の自動ローテーション

Security tutorial - IT technology blog
Security tutorial - IT technology blog

課題:シークレットスプロール(認証情報の散在)の危険性

システム管理者のキャリアを歩み始めた頃、設定管理は単純な作業に見えました。プロジェクトのディレクトリに .env ファイルを配置し、データベースのパスワードやStripeのAPIキーを貼り付けるだけで完了としていたのです。しかし、インフラがステージング環境と本番環境を合わせて18台の仮想マシンに拡大するにつれて、そのワークフローは急速に破綻しました。認証情報はbashの履歴ファイル、Dockerのビルドキャッシュ、Gitのコミットログ、プライベートなSlackチャンネルなど、あらゆる場所に漏洩していきました。

ステージングクラスタが自動化されたクレデンシャルスタッフィング攻撃を受けたことで、状況は一変しました。このインシデントにより、ファイアウォールルールやSSH鍵は境界防御にすぎないことが証明されました。攻撃者が単一のWebアプリケーションコンテナへのシェルアクセスを取得すると、平文の設定ファイルによってバックエンド全体が危険にさらされます。運用保守も同様に困難でした。漏洩したPostgreSQLのパスワードを1つ変更するだけでも、24個の個別設定ファイルを更新し、6つのマイクロサービスを再起動し、メンテナンスウィンドウを設定する必要があったのです。

シークレットスプロールを解決するには、強力な暗号化によるストレージの一元化、厳格なアクセス制御の適用、そして自動的に失効する動的認証情報の生成という3つのコア機能が必要になります。

基本アーキテクチャ:Vaultの動作原理

HashiCorp Vaultは、IDベースのアクセス制御を通じてシークレットを管理し、機密データを保護します。Linuxホスト上で扱う運用モデルは以下のとおりです。

  • ストレージバックエンド(Storage Backend): 暗号化された永続レイヤーです。Vaultはローカルファイルシステム、Consul、統合Raftストレージ、データベースなどをサポートしています。ディスクに1バイトでも書き込む前に、すべてのペイロードをメモリ上で暗号化します。
  • アンシールとシャミアの秘密分散法(Unsealing and Shamir’s Secret Sharing): Vaultは起動時にシール(封印)状態になります。管理者が分割されたマスターキー(通常は5つのキーシャードのうち3つ)または自動化されたCloud KMSキーを使用してアンシールするまで、保存されたシークレットを復号できません。
  • シークレットエンジン(Secrets Engines): さまざまなデータタイプに対応するモジュール式プラグインです。kv-v2 エンジンはバージョン管理された静的シークレットを管理し、動的エンジンはPostgreSQL、MySQL、AWSなどの一時的な認証情報をオンデマンドで生成します。
  • ポリシー(Policies): HCL(HashiCorp Configuration Language)で記述されたパスベースのアクセスルールです。read、create、update などの明示的な権限を付与します。
  • リースとTTL(Leases and TTLs): すべての動的シークレットとアクセストークンにはTTL(Time-to-Live)が設定されています。リースが期限切れになると、Vaultはアップストリームのデータベースやサービスから認証情報を自動的に削除します。

実践セットアップ:Ubuntu LinuxへのVaultのデプロイ

Ubuntu 24.04/22.04 LTSへのVaultのインストール、systemdデーモンのセットアップ、ストレージの初期化、そして動的なPostgreSQL認証情報の発行手順を順を追って解説します。

ステップ1:HashiCorpリポジトリからVaultをインストールする

システムパッケージのインデックスを更新し、必要な依存関係をインストールして、HashiCorpの公式GPG署名キーを登録します。

# 依存パッケージのインストール
sudo apt-get update -y
sudo apt-get install -y gpg coreutils curl jq

# HashiCorpのGPGキーをインポート
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg

# 公式リポジトリを追加
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list

# Vaultバイナリのインストール
sudo apt-get update -y
sudo apt-get install -y vault

ステップ2:Vaultデーモンを設定する

Vaultは主に /etc/vault.d/vault.hcl から設定を読み込みます。専用のデータディレクトリを作成し、所有権を vault システムユーザーに割り当てて、ローカルリスナーを設定します。

# 権限を制限したデータディレクトリを準備
sudo mkdir -p /opt/vault/data
sudo chown -R vault:vault /opt/vault
sudo chmod 750 /opt/vault/data

デーモン設定を /etc/vault.d/vault.hcl に書き込みます。

sudo tee /etc/vault.d/vault.hcl << 'EOF'
storage "file" {
  path = "/opt/vault/data"
}

listener "tcp" {
  address       = "127.0.0.1:8200"
  tls_disable   = 1
}

api_addr = "http://127.0.0.1:8200"
cluster_addr = "http://127.0.0.1:8201"
ui = true
disable_mlock = true
EOF

systemdユニットを有効化して起動し、デーモンがアクティブであることを確認します。

sudo systemctl enable vault
sudo systemctl start vault
sudo systemctl status vault --no-pager

ステップ3:Vaultインスタンスの初期化とアンシール

CLIがローカルリスナーをターゲットにするよう、クライアント環境変数を設定します。

export VAULT_ADDR='http://127.0.0.1:8200'
echo "export VAULT_ADDR='http://127.0.0.1:8200'" >> ~/.bashrc

Vaultのストレージバックエンドを初期化します。この初回のコマンドにより、マスターキーが5つのシャードに分割され、再構築のしきい値が3シャードに設定されます。

vault operator init -key-shares=5 -key-threshold=3 > ~/vault-init-keys.txt
chmod 600 ~/vault-init-keys.txt

~/vault-init-keys.txt を開き、これら5つのアンシールキーとルートトークンをすぐに暗号化されたパスワードマネージャーへバックアップしてください。

Vaultを稼働状態にするために、3つの異なるアンシールキーを入力します。

vault operator unseal <UNSEAL_KEY_1>
vault operator unseal <UNSEAL_KEY_2>
vault operator unseal <UNSEAL_KEY_3>

シールのステータスを確認します。CLI出力で Sealed: false となっていることを確認してください。

vault status

ルートトークンを使用してログインし、シークレットエンジンの設定を開始します。

vault login <INITIAL_ROOT_TOKEN>

ステップ4:静的シークレットの保存と取得(KV-v2)

バージョン2のKey-Valueシークレットエンジンを secret/ パスにマウントします。

vault secrets enable -path=secret kv-v2

決済マイクロサービス用の本番設定を保存します。

vault kv put secret/myapp/config \
    api_key="sk_live_9948571029384756" \
    payment_gateway_url="https://api.payment.internal" \
    max_retries="5"

複雑なペイロードやポリシーファイルをAPIに書き込む前に検証することで、実行時の構文解析エラーを防止できます。生のJSON出力をフォーマットする際は、ToolCraftのJSON Formatter & Validatorが便利です。ブラウザ上で完全に実行されるため、トークンや設定情報が外部サーバーに送信される心配がありません。

CLIを使用して保存されたデータを取得し、jq でフィールドをフィルタリングします。

vault kv get -format=json secret/myapp/config | jq '.data.data'

ステップ5:データベース認証情報の動的自動ローテーション

静的シークレットは、漏洩した場合に手動での無効化作業が依然として必要になります。動的シークレットは、必要に応じて一意の一時データベースロールを作成することでこの問題を解決します。

データベースシークレットエンジンを有効化します。

vault secrets enable database

127.0.0.1:5432 で稼働しているPostgreSQLインスタンスにVaultを接続します。

vault write database/config/my-postgres-db \
    plugin_name=postgresql-database-plugin \
    allowed_roles="app-readwrite" \
    connection_url="postgresql://{{username}}:{{password}}@127.0.0.1:5432/appdb?sslmode=disable" \
    username="vault_admin" \
    password="VaultAdminSecurePassword123!"

デフォルトTTLを1時間、最大リース期間を24時間に設定してアプリケーションロールを定義します。

vault write database/roles/app-readwrite \
    db_name=my-postgres-db \
    creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
        GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
    default_ttl="1h" \
    max_ttl="24h"

ロールエンドポイントにリクエストを送信して、新しい認証情報のペアを生成します。

vault read database/creds/app-readwrite

Vaultは専用のユーザー(例:v-token-app-read-0a1b2c3d)をプロビジョニングし、ランダムなパスワードを割り当てて、3600秒のリースを登録します。そのリースが期限切れになると、Vaultはデータベースロールを自動的に削除します。

ステップ6:Pythonアプリケーションとの統合

アプリケーションは、公式の hvac Pythonパッケージを使用することで、起動時に独自の短命な認証情報をリクエストして管理できます。

import hvac
import psycopg2

# Vaultクライアントの初期化
client = hvac.Client(url='http://127.0.0.1:8200', token='s.yourAppAuthTokenHere')

if not client.is_authenticated():
    raise PermissionError("Vaultデーモンとの認証に失敗しました。")

# 短期間有効なPostgreSQL認証情報をリクエスト(デフォルトは1時間のリース)
creds = client.secrets.database.generate_credentials(name='app-readwrite')
db_user = creds['data']['username']
db_pass = creds['data']['password']
lease_id = creds['lease_id']

print(f"[+] 動的ユーザーを生成しました: {db_user}")
print(f"[+] アクティブなリースID: {lease_id} ({creds['lease_duration']}秒)")

# 一時的な認証情報を使用してPostgreSQLに接続
conn = psycopg2.connect(
    host="127.0.0.1",
    port=5432,
    dbname="appdb",
    user=db_user,
    password=db_pass
)

with conn.cursor() as cur:
    cur.execute("SELECT current_user, session_user, now();")
    user_info = cur.fetchone()
    print(f"[+] アクティブなセッション: {user_info}")

conn.close()

# リースの更新パターン: バックグラウンドワーカーはTTL失効前にリースを延長可能
# client.sys.renew_lease(lease_id=lease_id, increment=3600)

次のステップ

平文の .env ファイルからHashiCorp Vaultへ移行することで、長期間有効な認証情報を、監査可能な短命トークンに置き換えることができます。ソースコードやサーバーのディスクから静的パスワードが排除され、動的シークレットにより認証情報のローテーションが完全に自動化されます。

まずは、データベース接続情報やサードパーティのAPIキーをVaultに移行することから始めましょう。サービス側でトークンの更新処理を適切にハンドリングできるようになったら、CI/CDパイプライン、Kubernetesクラスタ、クラウドIDプロバイダーとの統合へと拡張していくのがおすすめです。

Share: