やりたかったこと
Vault の userpass 認証では、IP 制限(token_bound_cidrs)はユーザー単位でしか設定できません。ユーザーが増えるたびに個別設定するのは運用上厳しいため、「Sentinel の EGP(Enterprise Governance Policy)を使えば、ユーザー個別設定に依存せず、ログインパス自体に一括でIP制限を掛けられるのでは?」という点を実機で検証しました。
結論:できました。ただし root tokenはバイパスされる/リバースプロキシ配下では別途設定が必要 といった制約がありますのでご注ください(詳細は末尾「運用上の注意点」)。
検証環境
- Vault Enterprise 2.0.2+ent(Docker Compose、
hashicorp/vault-enterpriseイメージ) - ホストOS: Windows/Vaultは WSL2 Ubuntu上のDockerコンテナで稼働
1. ローカル環境(Ubuntu/WSL)からVaultへ接続
$ vault status
Key Value
--- -----
Seal Type shamir
Initialized true
Sealed false
Total Shares 1
Threshold 1
Version 2.0.2+ent
Build Date 2026-06-04T13:15:06Z
Storage Type file
Cluster Name vault-cluster-********
Cluster ID ********-****-****-****-************
HA Enabled false
2. ライセンス確認
features に Sentinel が含まれているので、EGP/RGPが使える構成であることを確認できます。
$ vault license get
Key Value
--- -----
~~
features ~~ Sentinel ~~]
~~
3. userpass認証を有効化してサンプルユーザーを作成
$ vault auth enable userpass
$ vault write auth/userpass/users/taro password="<検証用パスワード>"
$ vault login -method=userpass username=taro password="<検証用パスワード>"
Key Value
--- -----
token hvs.********
token_accessor ********
token_duration 768h
token_renewable true
token_policies ["default"]
identity_policies []
policies ["default"]
token_meta_username taro
ここではまだSentinelの制限は何もかけていないので、当然ログインできます。
4. Windowsから接続確認(EGP適用前)
ブラウザ
Web UI(https://127.0.0.1:8200/ui)から taro でログインし、成功することを確認しました。
CLI
Windowsのコマンドプロンプトでは export ではなく set を使います。
>set VAULT_ADDR=https://127.0.0.1:8200
>set VAULT_SKIP_VERIFY=true
>vault login -method=userpass username=taro password="<検証用パスワード>"
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.********
token_accessor ********
token_duration 768h
token_renewable true
token_policies ["default"]
identity_policies []
policies ["default"]
token_meta_username taro
5. Sentinel EGPポリシーを作成
request.connection.remote_addr(Sentinelが参照できる送信元IP)をsockaddr.is_containedでCIDR判定し、許可外なら拒否するポリシーです。
$ cat egp.sentinel
import "sockaddr"
allowed_cidr = "127.0.0.1/32" #許可するIPアドレスを設定
ip_is_allowed = rule {
sockaddr.is_contained(allowed_cidr, request.connection.remote_addr)
}
main = rule {
ip_is_allowed
}
6. EGPをVaultに登録
$ vault write sys/policies/egp/userpass-login-cidr \
policy=@egp.sentinel \
paths="auth/userpass/login/*" \
enforcement_level="hard-mandatory"
Success! Data written to: sys/policies/egp/userpass-login-cidr
$ vault read sys/policies/egp/userpass-login-cidr
Key Value
--- -----
enforcement_level hard-mandatory
name userpass-login-cidr
paths [auth/userpass/login/*]
policy import "sockaddr"
allowed_cidr = "127.0.0.1/32" #許可するIPアドレスを設定
ip_is_allowed = rule {
sockaddr.is_contained(allowed_cidr, request.connection.remote_addr)
}
main = rule {
ip_is_allowed
}
paths に auth/userpass/login/* を指定することで、未認証のログインリクエスト自体にこのポリシーが適用されます。ここが重要で、「ログイン成功後のトークンに制限を付与する」のではなく「ログイン試行そのものを拒否できる」ため、ブルートフォース攻撃の入口を塞ぐ効果があります。
不要になったら以下で削除できます。
$ vault delete sys/policies/egp/userpass-login-cidr
Success! Data deleted (if it existed) at: sys/policies/egp/userpass-login-cidr
7. EGP適用後のアクセス確認
Windows ブラウザ
EGP適用後、Windowsのブラウザから taro でログインを試すと、ログイン画面のまま失敗メッセージが表示され、認証できないことを確認しました。
Windows CLI
>vault login -method=userpass username=taro password="<検証用パスワード>"
Error authenticating: Error making API request.
URL: PUT https://127.0.0.1:8200/v1/auth/userpass/login/taro
Code: 400. Errors:
* 2 errors occurred:
* egp standard policy "root/userpass-login-cidr" evaluation resulted in denial.
The specific error was:
<nil>
* permission denied
taro ユーザー自身には token_bound_cidrs を何も設定していません。それでもEGPだけでブロックされているので、ユーザー個別のIP制限設定に依存せず、ログインパスへの一元的なIP制限として機能することが確認できました。
許可したIPからのアクセス(Docker内部からのアクセス)
docker compose exec でコンテナの中に入って実行したリクエストは 127.0.0.1 として見えるため、このケースだけ許可されます。
$ docker compose exec -T vault vault write -format=json auth/userpass/login/taro password="<検証用パスワード>"
{
"request_id": "********",
"lease_id": "",
"lease_duration": 0,
"renewable": false,
"data": null,
"warnings": null,
"auth": {
"client_token": "hvs.********",
"accessor": "********)",
"policies": [
"default"
],
"token_policies": [
"default"
],
"identity_policies": null,
"metadata": {
"username": "taro"
},
"orphan": true,
"entity_id": "********)",
"lease_duration": 2764800,
"renewable": true,
"mfa_requirement": null
}
}
想定通り成功し、「EGPが送信元IPで判定して拒否/許可している」ことが裏付けられました。
まとめ
| 検証項目 | 結果 |
|---|---|
userpassのIP制限(token_bound_cidrs) |
ユーザー単位のみ。マウント全体への一括設定フィールドはない |
Sentinel EGP(paths=auth/userpass/login/*) |
未認証のログインパス自体にIP制限を適用できる。ユーザー個別設定に依存しない一元的な制御が可能 |
| Windows ブラウザ/CLI | EGP適用後は両方とも拒否される(同一のAPIパスを叩くため) |
| コンテナ内部からのアクセス | 送信元IPが127.0.0.1になるため、127.0.0.1/32許可設定では通る |
Sentinel EGPを使うことで、ユーザーが増えても個別にtoken_bound_cidrsを設定する必要がなく、ログインパスへの一括IP制限が実現できることを確認できました。
運用上の注意点
- root tokenはSentinelの評価対象外です。
- リバースプロキシ配下で運用する場合、そのプロキシがL7(HTTPモード)か L4/TLSパススルーかでVault側の対処が異なります。何も設定しないと
remote_addrがプロキシ自身のIPになり、EGPのCIDR判定が意味を持たなくなる点は共通の注意点です。-
ケース1: プロキシがHTTPを終端している(例: HAProxy
mode http、ALB等) — プロキシがX-Forwarded-Forヘッダーを付与できるので、Vault側のlistener設定でx_forwarded_for_authorized_addrs(信頼するプロキシのCIDR)とx_forwarded_for_hop_skips等を設定し、VaultにX-Forwarded-Forの値をremote_addrとして採用させます。 -
ケース2: プロキシがTCP/TLSパススルー(例: HAProxy
mode tcp) — L7のヘッダー挿入ができない(HTTPとして中身を見ていないため)ので、代わりに PROXY protocol を使います。HAProxy側ではserver行にsend-proxy(v1)またはsend-proxy-v2(v2)を付け、Vault側ではlistener "tcp"ブロックにproxy_protocol_behavior = "use_always"(またはallow_authorized)とproxy_protocol_authorized_addrs(PROXY protocolヘッダーの送信元として信頼するCIDR)を設定します。X-Forwarded-Forはここでは使えません。 - どちらの方式でも、EGPの
sockaddr.is_contained(...)が参照するrequest.connection.remote_addrに正しいクライアントIPが入っているかは、上記設定込みで実機確認することを推奨します(本記事では未検証)
-
ケース1: プロキシがHTTPを終端している(例: HAProxy
- EGPによる拒否時は、エラーレスポンス(および画面)にポリシー名がそのまま表示されます(今回の例では
root/userpass-login-cidr)。攻撃者に制御の存在や意図を推測されるリスクがあるため、命名は内容が読み取りづらいものにすることを推奨します。

