---
title: HashiCorp VaultをOpenID Connect Providerとして使用する
tags: ["Vault", "OIDC", "LDAP"]
categories: ["Dev", "SecretManagement", "Vault"]
date: 2023-01-11T03:04:05Z
updated: 2023-01-12T01:46:56Z
---

Vault 1.9からVault自身がOpenID Connect Providerとして利用できるようになっていました。
Self HostedなOIDC Providerが必要な時、Vaultを既に運用していれば、DexやKeycloakなどを新規で構築しなくても既存のVaultをOIDC Providerとして利用できます。

* https://developer.hashicorp.com/vault/docs/secrets/identity/oidc-provider
* https://developer.hashicorp.com/vault/tutorials/auth-methods/oidc-identity-provider


Vaultは`default`という名前のproviderが初めから提供されています。Issuer URLは`${VAULT_ADDR}/v1/identity/oidc/provider/default`です。


次のVault環境で検証してみます。

```
$ vault version
Vault v1.12.2 (415e1fe3118eebd5df6cb60d13defdc01aa17b03), built 2022-11-23T12:53:46Z

$ vault status
Key             Value
---             -----
Seal Type       shamir
Initialized     true
Sealed          false
Total Shares    5
Threshold       3
Version         1.12.2
Build Date      2022-11-23T12:53:46Z
Storage Type    s3
Cluster Name    vault-cluster-d5eb8945
Cluster ID      76979525-c584-407c-6b91-2671b591bf04
HA Enabled      false
```

この環境ではLDAP Auth Methodが有効になっています ([こちらの記事](https://ik.am/entries/484)で設定)。また、HTTPSで https://vault.maki.lol というURLで公開されているとします。

まずはOpenID Provider Configuration (`/.well-known/openid-configuration`)にアクセスしてみます。

```
$ curl -s https://vault.maki.lol/v1/identity/oidc/provider/default/.well-known/openid-configuration | jq .
{
  "issuer": "/v1/identity/oidc/provider/default",
  "jwks_uri": "/v1/identity/oidc/provider/default/.well-known/keys",
  "authorization_endpoint": "/ui/vault/identity/oidc/provider/default/authorize",
  "token_endpoint": "/v1/identity/oidc/provider/default/token",
  "userinfo_endpoint": "/v1/identity/oidc/provider/default/userinfo",
  "request_parameter_supported": false,
  "request_uri_parameter_supported": false,
  "id_token_signing_alg_values_supported": [
    "RS256",
    "RS384",
    "RS512",
    "ES256",
    "ES384",
    "ES512",
    "EdDSA"
  ],
  "response_types_supported": [
    "code"
  ],
  "scopes_supported": [
    "openid"
  ],
  "claims_supported": [],
  "subject_types_supported": [
    "public"
  ],
  "grant_types_supported": [
    "authorization_code"
  ],
  "token_endpoint_auth_methods_supported": [
    "none",
    "client_secret_basic",
    "client_secret_post"
  ]
}
```

各キーの値がURLではなく、パスだけになっています。

次のコマンドで`default` providerの設定を確認すると確かにissuerがURL形式になっていません。このままだとアプリがエンドポイントを検出する際に問題になります。

```
$ vault read identity/oidc/provider/default
Key                   Value
---                   -----
allowed_client_ids    [*]
issuer                /v1/identity/oidc/provider/default
scopes_supported      []
```

次のコマンドで`default` providerのissuerを変更します。

```
vault write identity/oidc/provider/default issuer=https://vault.maki.lol
```

これでOpenID Provider Configurationも次のようにURLを返すようになります。

```
$ curl -s https://vault.maki.lol/v1/identity/oidc/provider/default/.well-known/openid-configuration | jq .
{
  "issuer": "https://vault.maki.lol/v1/identity/oidc/provider/default",
  "jwks_uri": "https://vault.maki.lol/v1/identity/oidc/provider/default/.well-known/keys",
  "authorization_endpoint": "https://vault.maki.lol/ui/vault/identity/oidc/provider/default/authorize",
  "token_endpoint": "https://vault.maki.lol/v1/identity/oidc/provider/default/token",
  "userinfo_endpoint": "https://vault.maki.lol/v1/identity/oidc/provider/default/userinfo",
  "request_parameter_supported": false,
  "request_uri_parameter_supported": false,
  "id_token_signing_alg_values_supported": [
    "RS256",
    "RS384",
    "RS512",
    "ES256",
    "ES384",
    "ES512",
    "EdDSA"
  ],
  "response_types_supported": [
    "code"
  ],
  "scopes_supported": [
    "openid"
  ],
  "claims_supported": [],
  "subject_types_supported": [
    "public"
  ],
  "grant_types_supported": [
    "authorization_code"
  ],
  "token_endpoint_auth_methods_supported": [
    "none",
    "client_secret_basic",
    "client_secret_post"
  ]
}
```

動作検証用のOIDCクライアントとしてDexのexample-appを使用します。

https://github.com/dexidp/dex/tree/master/examples/example-app


次のコマンドでこのOIDCクライアントの情報をVaultに登録します。

```
vault write identity/oidc/client/example-app \
  redirect_uris="http://127.0.0.1:5555/callback" \
  assignments="allow_all"
```

CLIENT_IDとCLIENT_SECRETを次のコマンドで取得します。


```
CLIENT_ID=$(vault read -field client_id identity/oidc/client/example-app)
CLIENT_SECRET=$(vault read -field client_secret identity/oidc/client/example-app)
```

example-appを起動します。

```
docker run --rm -p 5555:5555 obitech/dex-example-app --debug --issuer https://vault.maki.lol/v1/identity/oidc/provider/default --listen http://0.0.0.0:5555 --client-id ${CLIENT_ID} --client-secret ${CLIENT_SECRET}
```

example-appのURL http://127.0.0.1:5555 にアクセスしてログインボタンをクリック

<img width="1024" alt="image" src="https://user-images.githubusercontent.com/106908/211180555-339e78d1-fe68-462f-9491-3f35b16d5744.png">

Vault UIのログイン画面にリダレクトされ、ログイン済みならそのまま次の画面にリダイレクトされます。

<img width="1024" alt="image" src="https://user-images.githubusercontent.com/106908/211180567-2e41a16d-83db-4bd4-80d4-88ed259fdbc1.png">

デコードされたJWTが表示されます。基本的にはこれでOKなのですが、JWTのペイロードをよく見ると、ユーザー名が含まれていません。

LDAP Authを使用しているからかどうかわかりませんが、ユーザー名を含めたいので次のように、OIDCのScopeを追加して、LDAP Authの`name`の項目を`username` Claimに含めるようにします。

```
MOUNT_ACCESOR=$(vault read -field accessor sys/auth/ldap)
PROFILE_SCOPE_TEMPLATE="{\"username\": {{identity.entity.aliases.$MOUNT_ACCESOR.name}}}"
vault write identity/oidc/scope/profile template="$(echo ${PROFILE_SCOPE_TEMPLATE} | base64 -)"
```

`default` prodiverに追加したScope (`profile`)をサポートさせます。

```
vault write identity/oidc/provider/default scopes_supported=profile
```

次のコマンドで`profile`スコープがサポートされたことを確認できます。

```
$ vault read identity/oidc/provider/default
Key                   Value
---                   -----
allowed_client_ids    [*]
issuer                https://vault.maki.lol/v1/identity/oidc/provider/default
scopes_supported      [email profile]
```


OpenID Provider Configurationを確認すると`profile` scopeが含まれます。


```
$ curl -s https://vault.maki.lol/v1/identity/oidc/provider/default/.well-known/openid-configuration | jq .
{
  "issuer": "https://vault.maki.lol/v1/identity/oidc/provider/default",
  "jwks_uri": "https://vault.maki.lol/v1/identity/oidc/provider/default/.well-known/keys",
  "authorization_endpoint": "https://vault.maki.lol/ui/vault/identity/oidc/provider/default/authorize",
  "token_endpoint": "https://vault.maki.lol/v1/identity/oidc/provider/default/token",
  "userinfo_endpoint": "https://vault.maki.lol/v1/identity/oidc/provider/default/userinfo",
  "request_parameter_supported": false,
  "request_uri_parameter_supported": false,
  "id_token_signing_alg_values_supported": [
    "RS256",
    "RS384",
    "RS512",
    "ES256",
    "ES384",
    "ES512",
    "EdDSA"
  ],
  "response_types_supported": [
    "code"
  ],
  "scopes_supported": [
    "profile",
    "openid"
  ],
  "claims_supported": [],
  "subject_types_supported": [
    "public"
  ],
  "grant_types_supported": [
    "authorization_code"
  ],
  "token_endpoint_auth_methods_supported": [
    "none",
    "client_secret_basic",
    "client_secret_post"
  ]
}
```

これで再度ログインするとJWTの`username` ClaimにLDAPの`cn`が含まれました。

<img width="1024" alt="image" src="https://user-images.githubusercontent.com/106908/211185048-28e0d8cc-8cd2-45e7-896b-b73097af0c3c.png">

---

LDAP Authだと`userattr`で指定した項目(e.g. `cn` or `mail`)1つしかJWTのclaimに含められなさそうなのが少し使い勝手が悪いですが、項目1つで済むレベルの使い方であれば新規でDexを立てるよりは楽で良いですね。
