+++ title = "VaultのOIDCプロバイダー機能を試してみた" date = 2021-11-21T22:00:43+09:00 description = "Vault 1.9.0で新しく追加されたOIDC Identity Provider機能を試してみました" tags = ["Vault"] categories = ["インフラ", "HashiCorp"] author = "kazu634" +++ [Vault as an OIDC Identity Provider \| Vault \- HashiCorp Learn](https://learn.hashicorp.com/tutorials/vault/oidc-identity-provider)を参考にして、`Vault`をOIDC Providerとして利用するようにセットアップしてみました。 ## 前提 `Vault`は`root`トークンで使う前提だよ。環境はこちら。`Vault`は事前にセットアップ済み、`Boundary`は新規にインストールする想定です。ただし、`Boundary`のインストール方法などは説明しません([ここ](https://learn.hashicorp.com/tutorials/boundary/getting-started-install)を見てください)。また、`Boundary`サーバには`Docker`を事前に導入しておかないと、手順通りにはいきません。 vault-oidc-provider.drawio ## 事前作業 `v.1.9.0`以前のバージョンから`Vault`をアップグレードして利用する場合には、以下のポリシーを`default`に追加する必要があります: ``` # Allow a token to make requests to the Authorization Endpoint for OIDC providers. path "identity/oidc/provider/+/authorize" { capabilities = ["read", "update"] } ``` `default`ポリシー更新の操作例はこちら: ```bash kazu634@boundary% vault policy read default > default.policy kazu634@boundary% cp -P default.policy default.policy.new kazu634@boundary% vi default.policy.new kazu634@boundary% diff -u default.policy default.policy.new --- default.policy 2021-11-23 14:54:30.108548827 +0900 +++ default.policy.new 2021-11-23 14:54:10.384179054 +0900 @@ -85,3 +85,8 @@ path "sys/control-group/request" { capabilities = ["update"] } + +# Allow a token to make requests to the Authorization Endpoint for OIDC providers. +path "identity/oidc/provider/+/authorize" { + capabilities = ["read", "update"] +} kazu634@boundary% vault policy write default default.policy.new Success! Uploaded policy: default kazu634@boundary% vault policy read default # Allow tokens to look up their own properties path "auth/token/lookup-self" { capabilities = ["read"] } # Allow tokens to renew themselves path "auth/token/renew-self" { capabilities = ["update"] } # Allow tokens to revoke themselves path "auth/token/revoke-self" { capabilities = ["update"] } # Allow a token to look up its own capabilities on a path path "sys/capabilities-self" { capabilities = ["update"] } # Allow a token to look up its own entity by id or name path "identity/entity/id/{{identity.entity.id}}" { capabilities = ["read"] } path "identity/entity/name/{{identity.entity.name}}" { capabilities = ["read"] } # Allow a token to look up its resultant ACL from all policies. This is useful # for UIs. It is an internal path because the format may change at any time # based on how the internal ACL features and capabilities change. path "sys/internal/ui/resultant-acl" { capabilities = ["read"] } # Allow a token to renew a lease via lease_id in the request body; old path for # old clients, new path for newer path "sys/renew" { capabilities = ["update"] } path "sys/leases/renew" { capabilities = ["update"] } # Allow looking up lease properties. This requires knowing the lease ID ahead # of time and does not divulge any sensitive information. path "sys/leases/lookup" { capabilities = ["update"] } # Allow a token to manage its own cubbyhole path "cubbyhole/*" { capabilities = ["create", "read", "update", "delete", "list"] } # Allow a token to wrap arbitrary values in a response-wrapping token path "sys/wrapping/wrap" { capabilities = ["update"] } # Allow a token to look up the creation time and TTL of a given # response-wrapping token path "sys/wrapping/lookup" { capabilities = ["update"] } # Allow a token to unwrap a response-wrapping token. This is a convenience to # avoid client token swapping since this is also part of the response wrapping # policy. path "sys/wrapping/unwrap" { capabilities = ["update"] } # Allow general purpose tools path "sys/tools/hash" { capabilities = ["update"] } path "sys/tools/hash/*" { capabilities = ["update"] } # Allow checking the status of a Control Group request if the user has the # accessor path "sys/control-group/request" { capabilities = ["update"] } # Allow a token to make requests to the Authorization Endpoint for OIDC providers. path "identity/oidc/provider/+/authorize" { capabilities = ["read", "update"] } ``` ## Vaultのユーザー、エンティティ、グループの設定 Vaultのユーザー、エンティティ、グループの設定をします。 ### Vaultのuserpass認証を有効に ここでは`userpass`認証を有効にし、`end-user`という名称のユーザーを、パスワードとして`password`で作成しています: ```sh kazu634@bastion2004% vault auth enable userpass Success! Enabled userpass auth method at: userpass/ kazu634@bastion2004% vault write auth/userpass/users/end-user \ password="password" \ token_ttl="1h" Success! Data written to: auth/userpass/users/end-user ``` ### アイデンティティ・エンティティの作成 以下のようにしてアイデンティティ・エンティティを作成します。`end-user`という名前のアイデンティティ・エンティティを作成します。 ```sh kazu634@bastion2004% vault write identity/entity \ name="end-user" \ metadata="email=vault@hashicorp.com" \ metadata="phone_number=123-456-7890" \ disabled=false Key Value --- ----- aliases id ea7a76f5-dc85-2d7e-f094-3011a73ee4da name end-user ``` この`end-user`アイデンティティ・エンティティはメタデータとして、メールアドレスと電話番号を持ちます。 ### アイデンティティ・グループの作成 以下のようにしてアイデンティティ・グループを作成します。今回は`engineering`という名前のアイデンティティ・グループを作成します: ```sh kazu634@bastion2004% ENTITY_ID=$(vault read -field=id identity/entity/name/end-user) kazu634@bastion2004% vault write identity/group \ name="engineering" \ member_entity_ids="$ENTITY_ID" Key Value --- ----- id 6e134b6c-8adc-05e8-d1a1-015d5500c7c6 name engineering ``` ### アイデンティティ・エイリアスの作成 `end-user`エンティティと`end-user` userpass ユーザーを同一とみなせるように、アイデンティティ・エイリアスを作成します: ```sh kazu634@bastion2004% GROUP_ID=$(vault read -field=id identity/group/name/engineering) kazu634@bastion2004% USERPASS_ACCESSOR=$(vault auth list -detailed -format json | jq -r '.["userpass/"].accessor') kazu634@bastion2004% vault write identity/entity-alias \ name="end-user" \ canonical_id="$ENTITY_ID" \ mount_accessor="$USERPASS_ACCESSOR" Key Value --- ----- canonical_id ea7a76f5-dc85-2d7e-f094-3011a73ee4da id 7f50040a-be65-e984-7d88-bb40df05b9d2 ``` ## VaultのOIDCクライアント用設定を作成する VaultのOIDCクライアントは、以下のリソースにアクセスします: - `OIDC assignment` - 暗号化キー - クライアント・コールバックURL - TTL ### OIDC assignmentの作成 以下のようにして`OIDC assignment`を作成します: ```sh kazu634@bastion2004% vault write identity/oidc/assignment/my-assignment \ entity_ids="{$ENTITY_ID}" \ group_ids="${GROUP_ID}" Success! Data written to: identity/oidc/assignment/my-assignment ``` `Vault`のOIDC認証プロセスは暗号化キーを必要とし、認証フローで作成されたJWTトークンを暗号化、検証します。 ### 暗号化キーの作成 以下のようにして暗号化キーを作成します: ```sh kazu634@bastion2004% vault write identity/oidc/key/my-key \ allowed_client_ids="*" \ verification_ttl="2h" \ rotation_period="1h" \ algorithm="RS256" Success! Data written to: identity/oidc/key/my-key ``` `allowed_client_ids`に`*`が指定されているため、この暗号化キーはすべての`Vault` OIDCクライアントで利用できます。 ### OIDCクライアントの作成 `boundary`という名前のOIDCクライアントを作成します: ```sh kazu634@bastion2004% vault write identity/oidc/client/boundary \ redirect_uris="http://192.168.10.175:9200/v1/auth-methods/oidc:authenticate:callback" \ assignments="my-assignment" \ key="my-key" \ id_token_ttl="30m" \ access_token_ttl="1h" Success! Data written to: identity/oidc/client/boundary ``` `redirect_uris`はクライアントのコールバックURLを指定します。`http://192.168.10.175:9200/v1/auth-methods/oidc:authenticate:callback`は、デフォルトポートを利用して稼働する`Boundary`のアドレスです。`assignments`は、`my-assignment`で定義されたエンティティとグループにアクセスを制限します。`id_token_ttl`は`ID`トークンの期限切れ時間を30分に指定しています。`access_token_ttl`は、アクセストークンの有効期限を1時間に指定します。 この辺は通常プロバイダー側で実施するOIDCの設定な気がします。 最後にクライアントIDを変数に格納します: ```sh kazu634@bastion2004% CLIENT_ID=$(vault read -field=client_id identity/oidc/client/boundary) ``` ## OIDCプロバイダーの作成 VaultのOIDCプロバイダーは一つ以上のVault OIDCスコープをサポートします。これらのスコープはテンプレートで指定されたメタデータクレームを定義します。メタデータクレームは、ユーザーとOIDCサービスに関する情報を含んだキー・バリュー・ペアーです。 ### ユーザースコープの作成 `USER_SCOPE_TEMPLATE`という変数を作成し、ユーザー・スコープ・テンプレートを格納します: ```sh kazu634@bastion2004% USER_SCOPE_TEMPLATE=$(cat << EOF { "username": {{identity.entity.name}}, "contact": { "email": {{identity.entity.metadata.email}}, "phone_number": {{identity.entity.metadata.phone_number}} } } EOF ) ``` このテンプレートはエンティティのメタデータからメールアドレスと電話番号を取得します。次のコマンドを実行して、このユーザースコープテンプレートを利用して、`user`という名前のVault OIDCスコープを作成します: ```sh kazu634@bastion2004% vault write identity/oidc/scope/user \ description="The user scope provides claims using Vault identity entity metadata" \ template="$(echo ${USER_SCOPE_TEMPLATE} | base64 -)" Success! Data written to: identity/oidc/scope/user ``` ### グループスコープの作成 `GROUPS_SCOPE_TEMPLATE`という変数にグループスコープテンプレートを格納します: ```sh kazu634@bastion2004% GROUPS_SCOPE_TEMPLATE=$(cat << EOF { "groups": {{identity.entity.groups.names}} } EOF ) ``` このテンプレートは定義されたすべてのグループ名を取得します。このグループスコープテンプレートを用いて、`group`という名称のVaultのOIDCスコープを作成します: ```sh kazu634@bastion2004% vault write identity/oidc/scope/groups \ description="The groups scope provides the groups claim using Vault group membership" \ template="$(echo ${GROUPS_SCOPE_TEMPLATE} | base64 -)" Success! Data written to: identity/oidc/scope/groups ``` VautのOIDCプロバイダーには、クライアントIDとスコープのリストが必要です。 ### OIDCプロバイダーの作成 `my-provider`という名前のVault OIDCプロバイダーを作成します: ```sh kazu634@bastion2004% vault write identity/oidc/provider/my-provider \ allowed_client_ids="${CLIENT_ID}" \ scopes_supported="groups,user" Success! Data written to: identity/oidc/provider/my-provider ``` このプロバイダーは`boundary`のクライアントアクセスを許可します。 ### 各種情報の確認 VaultのOIDC設定のためのエンドポイント情報を表示します: ```sh kazu634@bastion2004% curl -s $VAULT_ADDR/v1/identity/oidc/provider/my-provider/.well-known/openid-configuration | jq { "issuer": "http://192.168.10.142:8200/v1/identity/oidc/provider/my-provider", "jwks_uri": "http://192.168.10.142:8200/v1/identity/oidc/provider/my-provider/.well-known/keys", "authorization_endpoint": "http://192.168.10.142:8200/ui/vault/identity/oidc/provider/my-provider/authorize", "token_endpoint": "http://192.168.10.142:8200/v1/identity/oidc/provider/my-provider/token", "userinfo_endpoint": "http://192.168.10.142:8200/v1/identity/oidc/provider/my-provider/userinfo", "request_uri_parameter_supported": false, "id_token_signing_alg_values_supported": [ "RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "EdDSA" ], "response_types_supported": [ "code" ], "scopes_supported": [ "groups", "user", "openid" ], "subject_types_supported": [ "public" ], "grant_types_supported": [ "authorization_code" ], "token_endpoint_auth_methods_supported": [ "client_secret_basic" ] } ``` VaultのOIDCパブリックキーの情報を表示します: ```sh kazu634@bastion2004% curl -s $VAULT_ADDR/v1/identity/oidc/provider/my-provider/.well-known/keys | jq { "keys": [ { "use": "sig", "kty": "RSA", "kid": "c9ca478d-a2e2-fbf2-d514-2b8eda568f19", "alg": "RS256", "n": "42ko31pO37ARuFcadCOvi6z4S4andET-DgR306sGftasp6aeg1AOiditfmPDe_Gn953FBqvqmUEK9Dvg1dgypIcSWUevJmN9z4_fLA1hC9DmUjKGivNlqwRqFwSg2O6pljQzLH8ZXw16fxrWDUUCoke2IrPEsgYR40AoEhGxqWUwZTlimK_M-utr76DvnmY1XbZpPQLn88zq33noNVWmoCF41Fmf2ZWxz5LGXYGX3ZudeO5SQMzES_pU7JJ-xxHpKTqAoTJqU1g6pnz5tO6N6aZ9AwIAoVTzLq5h9ybrFeSEvkcdScaU9JHchabhjqdAFO1d8zicZh60wIfFbMHFTw", "e": "AQAB" }, { "use": "sig", "kty": "RSA", "kid": "11e6dcf7-25bf-bf92-23b2-216827915508", "alg": "RS256", "n": "3PVhLrhzMG-FkqgyOaCppgkEL-hjcwW_aHoL54RvFV4Erh2USZXQSnZXNZtXjSB4RMq8LJZA7bfeEg9FwUEIauOcYJEK4eXSDU7hiYk2tKtyrAuESebdMWz73c9EZ_Uf5Y8FMc1aMQ4YNi7DKo92BWfhTI6Am9_si_59kLUuOo4XqMgA_lUCypuKjT2k54a3yNIHsyA2Rj_xr3C7bDCUUe1VEMMd27SuG0w3uMxBXHbs7PS4CTYawrWGCw79gRhj7c2uKj1jyQiRhD16_bmQMdyNBgo48jj8WXA7c7Oq1X_fY7DelTvvtCtQwiT5awjX9SabOxvJds0z_zhaw-Ysww", "e": "AQAB" } ] } ``` ## Boundaryの起動・セットアップ `Boundary`の起動・セットアップについて説明します。 ### Boundaryの起動 別なターミナルで`Boundary`を起動します。ここでは`localhost`ではなく、`-host-address`と`-api-listen-address`オプションを指定して、`192.168.10.175`にバインドさせています: ```sh kazu634@boundary% sudo boundary dev -host-address=192.168.10.175 -api-listen-address=192.168.10.175 [sudo] password for kazu634: ==> Boundary server configuration: [Controller] AEAD Key Bytes: MRxx7K8uM05eT3TZ+lB+SOH1nlcITx7+lVAdfOgVypY= [Recovery] AEAD Key Bytes: bc3rdR3DurJKo8j+d4I8JO0VzKWueE7LOdSiLK/1RRo= [Worker-Auth] AEAD Key Bytes: dnJYhTV0gQg67Sg4slxuL+D+/ctMxGIqlpmDFDWqDeo= [Recovery] AEAD Type: aes-gcm [Root] AEAD Type: aes-gcm [Worker-Auth] AEAD Type: aes-gcm Cgo: disabled Controller Public Cluster Addr: 127.0.0.1:9201 Dev Database Container: gracious_jemison Dev Database Url: postgres://postgres:password@localhost:49156/boundary?sslmode=disable Generated Admin Login Name: admin Generated Admin Password: password Generated Host Catalog Id: hcst_1234567890 Generated Host Id: hst_1234567890 Generated Host Set Id: hsst_1234567890 Generated Oidc Auth Method Id: amoidc_1234567890 Generated Org Scope Id: o_1234567890 Generated Password Auth Method Id: ampw_1234567890 Generated Project Scope Id: p_1234567890 Generated Target Id: ttcp_1234567890 Generated Unprivileged Login Name: user Generated Unprivileged Password: password Listener 1: tcp (addr: "192.168.10.175:9200", cors_allowed_headers: "[]", cors_allowed_origins: "[*]", cors_enabled: "true", max_request_duration: "1m30s", purpose: "api") Listener 2: tcp (addr: "127.0.0.1:9201", max_request_duration: "1m30s", purpose: "cluster") Listener 3: tcp (addr: "127.0.0.1:9202", max_request_duration: "1m30s", purpose: "proxy") Log Level: info Mlock: supported: true, enabled: false Version: Boundary v0.7.1 Version Sha: abd695e5bc42d01e4412bf6c76211c3fc93a93d7 Worker Public Proxy Addr: 127.0.0.1:9202 ==> Boundary server started! Log data will stream in below: ``` ### Boundaryのセットアップ 元のターミナルに戻って、`Boundary`の設定を行います。`192.168.10.175`にバインドしているため、`BOUNDARY_ADDR`環境変数を指定します: ```sh kazu634@boundary% export BOUNDARY_ADDR=http://192.168.10.175:9200 ``` その後、`admin`ユーザーのパスワードを指定します: ```sh kazu634@boundary% boundary authenticate password \ -auth-method-id ampw_1234567890 \ -login-name admin \ -password password Error opening keyring: Specified keyring backend not available Token must be provided via BOUNDARY_TOKEN env var or -token flag. Reading the token can also be disabled via -keyring-type=none. Authentication information: Account ID: acctpw_1234567890 Auth Method ID: ampw_1234567890 Expiration Time: Sat, 27 Nov 2021 10:10:39 JST User ID: u_1234567890 Error opening "pass" keyring: Specified keyring backend not available The token was not successfully saved to a system keyring. The token is: at_CP320WuH7f_s12TyNg8Rivoqi5fzP1TSoUrfthCuGpbwwm21f5phbKQUj1EYhbTXQHfZGoTSWm6qUYeAjfgzpbaQwDrWGfi2KdrypBLSEuYL1vpgNd1xocE3q It must be manually passed in via the BOUNDARY_TOKEN env var or -token flag. Storing the token can also be disabled via -keyring-type=none. ``` ここで表示されたトークンを環境変数`BOUNDARY_TOKEN`に格納します: ```bash kazu634@boundary% export BOUNDARY_TOKEN=at_CP320WuH7f_s12TyNg8Rivoqi5fzP1TSoUrfthCuGpbwwm21f5phbKQUj1EYhbTXQHfZGoTSWm6qUYeAjfgzpbaQwDrWGfi2KdrypBLSEuYL1vpgNd1xocE3q ``` ### BoundaryのOIDC認証の設定 次のようにしてOIDC認証の設定を行います。`ISSUER`という環境変数を定義して、そこに`my-provider`の`issuer`フィールドの値を格納します: ```bash kazu634@boundary% ISSUER=$(curl -s $VAULT_ADDR/v1/identity/oidc/provider/my-provider/.well-known/openid-configuration | jq -r .issuer) kazu634@boundary% echo ${ISSUER} http://192.168.10.142:8200/v1/identity/oidc/provider/my-provider ``` 環境変数`CLIENT_SECRET`に、Vault OIDCクライアント`boundary`の`client_secret`フィールドに格納された値を格納します: ```bash kazu634@boundary% CLIENT_SECRET=$(vault read -field=client_secret identity/oidc/client/boundary) kazu634@boundary% echo ${CLIENT_SECRET} hvo_secret_ZmxtbxiyWtjo9dVpU9BJZhR1bQVbczMRjTuZZHlf1HEfHaKZFOwGyDCoOKuaQAw9 ``` VaultのOIDCプロバイダーを利用するように設定した、新しい認証方法を定義します: ```bash kazu634@boundary% boundary auth-methods create oidc \ -issuer "${ISSUER}" \ -client-id "${CLIENT_ID}" \ -client-secret "${CLIENT_SECRET}" \ -signing-algorithm RS256 \ -api-url-prefix "http://192.168.10.175:9200" \ -claims-scopes "groups" \ -claims-scopes "user" \ -max-age 20 \ -name "Vault OIDC Provider" Auth Method information: Created Time: Sat, 20 Nov 2021 10:14:51 JST ID: amoidc_TUXAIBhxIF Name: Vault OIDC Provider Type: oidc Updated Time: Sat, 20 Nov 2021 10:14:51 JST Version: 1 Scope: ID: global Name: global Type: global Authorized Actions: no-op read update delete change-state authenticate Authorized Actions on Auth Method's Collections: accounts: create list managed-groups: create list Attributes: api_url_prefix: http://192.168.10.175:9200 callback_url: http://192.168.10.175:9200/v1/auth-methods/oidc:authenticate:callback claims_scopes: [groups user] client_id: VploN0lIIZEVpqAhN7xJS3eDnLYRMGR4 client_secret_hmac: qCpF9Qb5XxlnBOKn0m8iRG7Ns00U5naYxD-dTYuwlc8 issuer: http://192.168.10.142:8200/v1/identity/oidc/provider/my-provider max_age: 20 signing_algorithms: [RS256] state: inactive ``` `AUTH_METHOD_ID`という環境変数に認証方式IDを格納します: ```bash kazu634@boundary% AUTH_METHOD_ID="$(boundary auth-methods list -format json | jq -r '.items[] | select(.name == "Vault OIDC Provider") | .id')" kazu634@boundary% echo ${AUTH_METHOD_ID} amoidc_TUXAIBhxIF ``` 認証方式の状態を`active-public`に変更します: ```bash kazu634@boundary% boundary auth-methods change-state oidc \ -id "${AUTH_METHOD_ID}" \ -state active-public Auth Method information: Created Time: Sat, 20 Nov 2021 10:14:51 JST ID: amoidc_TUXAIBhxIF Name: Vault OIDC Provider Type: oidc Updated Time: Sat, 20 Nov 2021 10:16:06 JST Version: 2 Scope: ID: global Name: global Type: global Authorized Actions: no-op read update delete change-state authenticate Authorized Actions on Auth Method's Collections: accounts: create list managed-groups: create list Attributes: api_url_prefix: http://192.168.10.175:9200 callback_url: http://192.168.10.175:9200/v1/auth-methods/oidc:authenticate:callback claims_scopes: [groups user] client_id: VploN0lIIZEVpqAhN7xJS3eDnLYRMGR4 client_secret_hmac: qCpF9Qb5XxlnBOKn0m8iRG7Ns00U5naYxD-dTYuwlc8 issuer: http://192.168.10.142:8200/v1/identity/oidc/provider/my-provider max_age: 20 signing_algorithms: [RS256] state: active-public ``` `global`スコープでOIDC認証方式を利用できるようにします: ```bash kazu634@boundary% boundary scopes update \ -primary-auth-method-id "${AUTH_METHOD_ID}" \ -id global Scope information: Created Time: Sat, 20 Nov 2021 10:10:04 JST Description: Global Scope ID: global Name: global Primary Auth Method ID: amoidc_TUXAIBhxIF Updated Time: Sat, 20 Nov 2021 10:16:26 JST Version: 3 Scope (parent): ID: global Name: global Type: global Authorized Actions: no-op read update delete Authorized Actions on Scope's Collections: auth-methods: create list auth-tokens: list groups: create list roles: create list scopes: create list users: create list ``` ## 動作確認 ブラウザーで`http://192.168.10.175:9200`にアクセスすると、以下のように表示されるはずです: Authenticate _ B `Vault OIDC Provider`を選択して、`Authenticate`ボタンをクリックします。すると新しいタブが開いて、`Vault`のログイン画面が表示されます: Vault `Username`を選択し、`end-user`と`password`を入力し、`Sign in`ボタンをクリックします。すると、`Boundary`にログインできるはずです。 ## 参考リンク - [Vault as an OIDC Identity Provider \| Vault \- HashiCorp Learn](https://learn.hashicorp.com/tutorials/vault/oidc-identity-provider) - [Install Boundary \| Boundary \- HashiCorp Learn](https://learn.hashicorp.com/tutorials/boundary/getting-started-install)