--- 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 にアクセスしてログインボタンをクリック image Vault UIのログイン画面にリダレクトされ、ログイン済みならそのまま次の画面にリダイレクトされます。 image デコードされた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`が含まれました。 image --- LDAP Authだと`userattr`で指定した項目(e.g. `cn` or `mail`)1つしかJWTのclaimに含められなさそうなのが少し使い勝手が悪いですが、項目1つで済むレベルの使い方であれば新規でDexを立てるよりは楽で良いですね。