From 1b2f8ee4bacc516aef6b11619266d0a27e0e032c Mon Sep 17 00:00:00 2001 From: Tibor Kircsi Date: Wed, 6 May 2026 15:09:04 +0200 Subject: [PATCH 1/3] docs(dir/oidc-gateway): update docs Signed-off-by: Tibor Kircsi --- docs/dir/directory-cli.md | 23 +- docs/dir/directory-oidc-authentication.md | 276 +++++++++++++--------- 2 files changed, 183 insertions(+), 116 deletions(-) diff --git a/docs/dir/directory-cli.md b/docs/dir/directory-cli.md index e0db9bb..0092af3 100644 --- a/docs/dir/directory-cli.md +++ b/docs/dir/directory-cli.md @@ -373,7 +373,7 @@ dirctl routing list -o json | jq -r '.[].cid' | xargs -I {} dirctl pull {} Authentication is required when accessing remote Directory servers. This section focuses on how `dirctl` authenticates and how to use the relevant CLI commands. -For the broader model, including `Envoy` and `ext-authz`, IdP-agnostic OIDC support, Dex as an optional broker, and how external OIDC access differs from internal SPIFFE/SPIRE trust, see [OIDC Authentication for Directory](directory-oidc-authentication.md). +For the broader model, including `Envoy` and `ext_authz`, IdP-agnostic OIDC support, Dex as an optional broker, and how external OIDC access differs from internal SPIFFE/SPIRE trust, see [OIDC Authentication for Directory](directory-oidc-authentication.md). | Command | Description | |---------|-------------| @@ -497,6 +497,27 @@ dirctl pull baeareihdr6t7s6sr2q4zo456sza66eewqc7huzatyfgvoupaqyjw23ilvi dirctl --auth-mode=oidc push my-agent.json ``` +#### Choosing the Gateway Endpoint + +With `oidc-gateway` v1.1.0, operators may expose two hostnames from one gateway deployment: + +- an OIDC/JWT hostname (`ingress.oidc`) for `--auth-mode=oidc`, `--auth-mode=jwt`, cached OIDC login, pre-issued OIDC tokens, and GitHub Actions OIDC tokens +- an mTLS hostname (`ingress.mtls`) for `--auth-mode=x509` or `--auth-mode=tls`, where the gateway must see the client certificate + +Use the hostname that matches the credential you send. Bearer JWT traffic should target the OIDC/JWT endpoint; X.509-SVID mTLS traffic should target the mTLS endpoint. + +```bash +# OIDC/JWT endpoint: Envoy validates the bearer token with jwt_authn +dirctl --server-addr "prod.gateway.ads.outshift.io:443" \ + --auth-mode=oidc \ + search --skill "natural_language_processing" + +# mTLS endpoint: Envoy validates the client certificate and ext_authz authorizes the SPIFFE principal +dirctl --server-addr "prod.gateway-mtls.ads.outshift.io:443" \ + --auth-mode=x509 \ + search --skill "natural_language_processing" +``` + #### Pre-issued Tokens (CI and Service Users) For CI/CD pipelines and automation, pass a pre-issued JWT token directly: diff --git a/docs/dir/directory-oidc-authentication.md b/docs/dir/directory-oidc-authentication.md index 2f8436a..a891616 100644 --- a/docs/dir/directory-oidc-authentication.md +++ b/docs/dir/directory-oidc-authentication.md @@ -1,13 +1,14 @@ # OIDC Authentication for Directory -Directory supports an optional OIDC-based authentication layer for users and automation that access the API from outside the cluster. This keeps external access standards-based while preserving SPIFFE/SPIRE as the primary trust model for in-cluster workloads and service-to-service communication. +Directory supports an optional `oidc-gateway` authentication layer for users, automation, and workloads that access the API from outside the cluster. This keeps external access standards-based while preserving SPIFFE/SPIRE as the primary trust model for in-cluster workloads and service-to-service communication. At a high level: - Directory is OIDC IdP agnostic for external access. -- `Envoy` and `ext-authz` form the authentication and authorization layer at the edge. +- `Envoy` and `ext_authz` form the authentication and authorization layer at the edge. - `Dex` is one useful deployment pattern, not a requirement. -- Internal backend trust can remain SPIFFE-based even when external callers use OIDC. +- `oidc-gateway` v1.1.0 accepts OIDC JWT, SPIFFE JWT-SVID, and SPIFFE X.509-SVID identities, and can expose OIDC/JWT and X.509-SVID mTLS traffic on separate hostnames from one gateway deployment. +- Internal backend trust can remain SPIFFE-based even when external callers use OIDC bearer tokens. ## Why Use OIDC @@ -18,7 +19,7 @@ The default Directory deployment model is a strong fit for in-cluster workloads - CI workflows such as GitHub Actions. - Enterprise users authenticating through an existing IdP. -This OIDC layer is optional. If you only need in-cluster, SPIFFE-native access, you do not need to enable it. +This gateway layer is optional. If you only need in-cluster, SPIFFE-native access, you do not need to enable it. ### What This Is Not @@ -38,7 +39,7 @@ These two layers solve different problems and should be described separately: Directory does not need to choose one or the other globally. A common deployment pattern is: - Remote callers authenticate with OIDC -- Envoy validates tokens and calls `ext-authz` +- Envoy validates tokens and calls `ext_authz` - Authorized requests are forwarded to the Directory API - Backend services continue using SPIFFE-aware trust and identity @@ -46,24 +47,32 @@ Directory does not need to choose one or the other globally. A common deployment ```mermaid flowchart LR - humanUser["Human user with dirctl"] -->|"OIDC token"| envoyGateway["Envoy gateway"] - serviceUser["Service user or automation"] -->|"OIDC token"| envoyGateway - githubActions["GitHub Actions workload"] -->|"OIDC token"| envoyGateway + humanUser["Human user with dirctl"] -->|"OIDC JWT"| oidcEndpoint["OIDC/JWT hostname"] + serviceUser["Service user or automation"] -->|"OIDC JWT or SPIFFE JWT-SVID"| oidcEndpoint + githubActions["GitHub Actions workload"] -->|"OIDC JWT"| oidcEndpoint + spiffeWorkload["SPIFFE workload"] -->|"X.509-SVID mTLS"| mtlsEndpoint["mTLS hostname"] - oidcIdp["OIDC provider or broker"] -->|"JWKS and issuer metadata"| envoyGateway + oidcIdp["OIDC provider or broker"] -->|"JWKS and issuer metadata"| envoyGateway["Envoy gateway"] - envoyGateway -->|"Verified token context"| extAuthz["ext-authz policy service"] + oidcEndpoint --> envoyGateway + mtlsEndpoint --> envoyGateway + envoyGateway -->|"Verified identity context"| extAuthz["ext_authz policy service"] extAuthz -->|"Allow or deny"| envoyGateway - envoyGateway -->|"Authorized request"| directoryApi["Directory API"] + envoyGateway -->|"Authorized request + x-auth-principal"| directoryApi["Directory API"] ``` At the edge: -1. A client gets an OIDC token from a trusted issuer. -2. Envoy `jwt_authn` validates issuer, signature, and audience. -3. `ext-authz` maps trusted claims to a canonical principal and role policy. +1. A client presents an OIDC JWT, SPIFFE JWT-SVID, or SPIFFE X.509-SVID. +2. Envoy validates bearer JWTs with `jwt_authn` on the OIDC/JWT listener, or validates downstream SPIFFE mTLS on the mTLS listener. +3. `ext_authz` maps trusted identity data to a canonical principal and role policy. 4. Only authorized requests reach the Directory API. +With `oidc-gateway` v1.1.0, the recommended production shape is a single gateway deployment with two optional downstream endpoints: + +- `envoy.endpoints.oidc` plus `ingress.oidc` for human users, CI, and automation that present bearer JWTs. +- `envoy.endpoints.mtls` plus `ingress.mtls` for SPIFFE X.509-SVID clients. This listener typically requires a client certificate and does not run Envoy `jwt_authn`; `ext_authz` authorizes the SPIFFE principal derived from the TLS session. + This keeps token handling and policy enforcement at the edge rather than spreading it across clients and backend services. ## Supported Identity Patterns @@ -136,122 +145,131 @@ Use Dex when you want: - GitHub federation for human login. - A simpler lab or reference deployment pattern that is easy to explain and reproduce. -In both cases, the Directory-facing model is the same: the edge validates OIDC tokens, `ext-authz` evaluates policy, and the Directory API receives only authorized requests. +In both cases, the Directory-facing model is the same: the edge validates tokens, `ext_authz` evaluates policy, and the Directory API receives only authorized requests. ## Relationship to `dirctl` and Deployment Configuration The two main operator touchpoints are: - [`directory-cli.md`](directory-cli.md) for user-facing commands such as `dirctl auth login`, `dirctl auth status`, `--auth-mode=oidc`, and pre-issued token usage -- deployment configuration for the Envoy and `ext-authz` layer that trusts one or more OIDC issuers and maps claims to principals and roles +- deployment configuration for Envoy and the `ext_authz` layer that trusts one or more issuers and maps verified identities to principals and roles When documenting or configuring this feature, it helps to think in three layers: 1. Client behavior: how `dirctl` or automation gets and sends tokens. 2. Edge trust: how Envoy validates JWTs from trusted issuers. -3. Authorization policy: how `ext-authz` maps claims and enforces access. +3. Authorization policy: how `ext_authz` maps identities and enforces access. -## `envoy-authz` Configuration Walkthrough +## `oidc-gateway` Configuration Walkthrough -The staging example in [`dir-staging/applications/dir/dev/values.yaml`](https://github.com/agntcy/dir-staging/blob/main/applications/dir/dev/values.yaml) is a good reference for how this model is wired in practice. +The staging example in [`dir-staging/applications/oidc-gateway/dev/values.yaml`](https://github.com/agntcy/dir-staging/blob/main/applications/oidc-gateway/dev/values.yaml) is a good reference for how this model is wired in practice. + +The public `dir` chart no longer embeds an `envoy-authz` add-on. Starting with the standalone `oidc-gateway` model, deploy the gateway as its own Helm application in front of the internal Directory API service. The configuration is split into four main areas: -- Feature enablement - Envoy edge behavior -- `ext-authz` claim and role mapping +- `ext_authz` principal extraction and RBAC +- SPIFFE and backend trust - External ingress exposure -To configure `envoy-authz`: +To configure `oidc-gateway`: -1. Enable the Add-On +1. Configure Envoy as the Edge - The top-level switch is: + The `envoy` block defines how Envoy fronts the internal Directory API: ```yaml - apiserver: - envoyAuthz: - enabled: true - ``` - - Keep this disabled when you only need in-cluster SPIFFE-based access. Enable it when external users or automation need to reach Directory through the OIDC-aware gateway. - -2. Configure Envoy as the OIDC-Aware Edge + envoy: + endpoints: + oidc: + enabled: true + port: 8080 + servicePort: 8080 + downstreamTls: + enabled: false + requireClientCertificate: false + mtls: + enabled: true + port: 8443 + servicePort: 8443 + downstreamTls: + enabled: true + requireClientCertificate: true - The `apiserver.envoy-authz.envoy` block defines how Envoy fronts the internal Directory API: + backend: + address: "dir-apiserver.dir.svc.cluster.local" + port: 8888 + streamingPath: "/agntcy.dir.events.v1.EventService/Listen" + listenRouteTimeout: 0s - ```yaml - apiserver: - envoy-authz: - envoy: - backend: - address: "dir-apiserver.dir.svc.cluster.local" - port: 8888 - oidc: - dex: - enabled: true - issuer: "https://dex.example.com" - jwksUri: "https://dex.example.com/keys" - github: - enabled: true - issuer: "https://token.actions.githubusercontent.com" - jwksUri: "https://token.actions.githubusercontent.com/.well-known/jwks" - spiffe: + oidc: + issuers: + - name: dex enabled: true + issuer: "https://dex.example.com" + jwksUri: "https://dex.example.com/keys" + jwksHost: "dex.example.com" + github: + enabled: true + issuer: "https://token.actions.githubusercontent.com" + jwksUri: "https://token.actions.githubusercontent.com/.well-known/jwks" + jwksHost: "token.actions.githubusercontent.com" + audiences: + - "dir" + + spiffe: + enabled: true + trustDomain: example.org + className: dir-spire ``` - This block does four important things: + This block does these important things: - - `backend.*` points Envoy at the internal Kubernetes Service for the Directory API, not at the public ingress hostname. - - `oidc.dex.*` enables JWT validation for human or device-flow tokens issued by Dex. + - `endpoints.oidc` exposes the listener that accepts OIDC JWT, GitHub OIDC, and SPIFFE JWT-SVID bearer tokens. + - `endpoints.mtls` exposes the listener that accepts SPIFFE X.509-SVID client certificates over downstream mTLS. + - `backend.*` points Envoy at the internal Kubernetes Service for the Directory API, not at either public ingress hostname. + - `oidc.issuers[]` creates Envoy `jwt_authn` providers and JWKS clusters for bearer JWT validation. - `oidc.github.*` enables JWT validation for GitHub Actions workload identity tokens. - `spiffe.*` keeps Envoy-to-Directory traffic anchored in SPIFFE/SPIRE-based service trust. - If you use a different IdP such as Zitadel, Keycloak, Auth0, Okta, or Entra ID, the Dex-specific issuer and JWKS values would be replaced with the corresponding issuer metadata for that provider. + If you use a different IdP such as Zitadel, Keycloak, Auth0, Okta, or Entra ID, replace the Dex issuer and JWKS values with the corresponding issuer metadata for that provider. -3. Map Claims to Principals in `ext-authz` +2. Configure `ext_authz` Principal Extraction - After Envoy validates the token, `apiserver.envoy-authz.authServer.oidc` controls how `ext-authz` interprets claims and turns them into principals: + After Envoy validates a bearer token, it forwards the verified JWT payload to the authorization server. The `authServer.oidc` block controls how `ext_authz` interprets that payload and turns it into a canonical principal: ```yaml - apiserver: - envoy-authz: - authServer: - oidc: - claims: - userID: "sub" - emailPath: "email" - issuers: [] - principalType: - mode: "auto" - machineIdentityClaim: "client_id" + authServer: + oidc: + claims: + principalClaim: "sub" + emailClaimPath: "email" + headers: + authPrincipal: "x-auth-principal" + issuers: + - providerKey: "dex" + provider: "https://dex.example.com" + authFamily: "oidc" + - providerKey: "github" + provider: "https://token.actions.githubusercontent.com" + authFamily: "oidc" + denyList: [] ``` This is where you define the trust boundary for authorization: - - `claims.userID` selects which claim identifies a human user. `sub` is the safest generic default. - - `claims.emailPath` tells `ext-authz` where to read email-like identity metadata when present. - - `issuers` maps trusted issuers to principal types such as `user`, `client`, or `github`. - - `principalType.mode` controls whether principal classification is inferred automatically or forced to a specific type. - - `machineIdentityClaim` tells `ext-authz` which claim to use for service-client identities. - - A typical issuer mapping looks like this: + - `claims.principalClaim` selects which claim becomes the default OIDC principal value. `sub` is the safest generic default. + - `claims.emailClaimPath` tells `ext_authz` where to read email-like identity metadata when present. + - `headers.authPrincipal` sets the upstream header that carries the canonical principal to Directory. The default is `x-auth-principal`. + - `issuers[].provider` must match the token `iss` value. + - `issuers[].providerKey` is the stable short name used in policy strings such as `oidc:dex:alice`. + - `issuers[].authFamily` is usually `oidc`. Use `spiffe` for SPIFFE JWT-SVID issuers. + - `denyList` blocks specific canonical principals or email claim values before RBAC evaluation. - ```yaml - issuers: - - provider: "https://dex.example.com" - principalType: "user" - - provider: "https://token.actions.githubusercontent.com" - principalType: "github" - ``` - - This is the bridge between a validated token and the principal form used by policy, such as: - - - `user::` - - `client::` - - `ghwf:repo:/:workflow::ref:` + Envoy strips client-supplied values for the configured principal header before forwarding requests, so clients cannot spoof `x-auth-principal`. -4. Map Principals to Roles and Allowed Methods +3. Use Canonical Principal Strings in Roles The `roles` section is where the high-level access model becomes concrete: @@ -259,63 +277,91 @@ To configure `envoy-authz`: roles: admin: allowedMethods: ["*"] - users: [] - clients: [] - githubWorkflows: [] + principals: + - "oidc:dex:alice" viewer: allowedMethods: - "/agntcy.dir.store.v1.StoreService/Pull" - "/agntcy.dir.search.v1.SearchService/SearchRecords" + principals: + - "oidc:dex:reader-service" + + ci-writer: + allowedMethods: + - "/agntcy.dir.store.v1.StoreService/Push" + - "/agntcy.dir.search.v1.SearchService/SearchRecords" + principals: + - "oidc:github:repo:your-org/your-repo:workflow:import-records.yaml:ref:refs/heads/main" ``` - This section answers two questions: + Supported principal forms include: - - which principals belong to a role - - which gRPC methods that role may call + - `oidc::` + - `oidc:github:repo:/:workflow::ref:` + - `spiffe:spiffe:///` - In the staging example, roles are separated for broad admin access, read-oriented access, and CI-oriented write access. That is the part you tune most often for real deployments. + GitHub workflow wildcard matching is intentionally strict. A wildcard may contain exactly one `*`, it must be the final character, and it is only supported in the branch ref segment, for example: + + ```text + oidc:github:repo:your-org/your-repo:workflow:deploy.yaml:ref:refs/heads/release-* + ``` Practical guidance: - keep roles least-privilege - grant write methods only where needed - list specific GitHub workflow principals rather than trusting all workflows - - prefer explicit users or clients over broad catch-all mappings + - prefer explicit principals over broad catch-all mappings -5. Expose the Gateway Externally +4. Expose the Gateway Externally - The `apiserver.envoy-authz.ingress` block controls whether the OIDC-aware gateway is reachable from outside the cluster: + The `ingress` block controls whether each gateway endpoint is reachable from outside the cluster: ```yaml - apiserver: - envoy-authz: - ingress: + ingress: + oidc: + enabled: true + className: nginx + host: "gateway.example.com" + annotations: + nginx.ingress.kubernetes.io/backend-protocol: "GRPC" + nginx.ingress.kubernetes.io/grpc-backend: "true" + tls: enabled: true - className: nginx - host: "gateway.example.com" - annotations: - nginx.ingress.kubernetes.io/backend-protocol: "GRPC" - tls: - enabled: true + secretName: "" + + mtls: + enabled: true + className: nginx-internal + host: "gateway-mtls.example.com" + annotations: + nginx.ingress.kubernetes.io/ssl-passthrough: "true" + nginx.ingress.kubernetes.io/backend-protocol: "GRPCS" + tls: + enabled: true + secretName: "" ``` - This is the hostname that remote `dirctl` users, SDK clients, or CI workflows target. When ingress is disabled, the OIDC layer may still exist in-cluster, but it is not exposed for external callers. + `ingress.oidc.host` is the hostname that remote `dirctl` users, SDK clients, or CI workflows target when they present bearer JWTs. `ingress.mtls.host` is the separate hostname for SPIFFE X.509-SVID callers; use an ingress controller/class that supports TLS passthrough so Envoy, not the ingress controller, terminates mTLS and sees the client certificate. + + In TLS-passthrough mode, `tls.secretName` is intentionally empty. The ingress object still advertises the TLS host for SNI routing, but the certificate is provided by Envoy via SPIFFE/SPIRE instead of by an ingress TLS secret. ### Recommended Mental Model for the YAML When reading or editing the staging values, use this sequence: -1. `envoyAuthz.enabled`: should the add-on exist at all? -2. `envoy-authz.envoy.backend`: where should authorized requests go? -3. `envoy-authz.envoy.oidc.*`: which issuers can Envoy validate? -4. `envoy-authz.authServer.oidc.issuers`: how does `ext-authz` classify those issuers? -5. `envoy-authz.authServer.oidc.roles`: which principals can call which methods? -6. `envoy-authz.ingress.*`: how do external clients reach the gateway? +1. `envoy.backend`: where should authorized requests go? +2. `envoy.oidc.*`: which bearer-token issuers can Envoy validate? +3. `envoy.endpoints.*`: which downstream listener(s) and Service ports are exposed? +4. `envoy.spiffe.*`: how does Envoy authenticate to Directory and, optionally, accept SPIFFE X.509-SVID clients? +5. `authServer.oidc.issuers`: how does `ext_authz` map verified issuers to canonical principal prefixes? +6. `authServer.oidc.roles`: which canonical principals can call which methods? +7. `ingress.oidc` / `ingress.mtls`: how do external clients reach the correct gateway listener? That sequence mirrors the actual request path: -Client token -> Envoy validation -> `ext-authz` principal mapping -> role check -> Directory API. +Client identity -> Envoy validation -> `ext_authz` canonical principal mapping -> role check -> Directory API. ## Further Reading From 996874a38e92c2b90849dfc56f658ac2f6910033 Mon Sep 17 00:00:00 2001 From: Tibor Kircsi Date: Wed, 13 May 2026 10:30:27 +0200 Subject: [PATCH 2/3] chore(dir-docs): update oidc gateway related docs Signed-off-by: Tibor Kircsi --- docs/dir/directory-cli.md | 4 ++-- docs/dir/directory-oidc-authentication.md | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/dir/directory-cli.md b/docs/dir/directory-cli.md index 0092af3..5ea9225 100644 --- a/docs/dir/directory-cli.md +++ b/docs/dir/directory-cli.md @@ -499,12 +499,12 @@ dirctl --auth-mode=oidc push my-agent.json #### Choosing the Gateway Endpoint -With `oidc-gateway` v1.1.0, operators may expose two hostnames from one gateway deployment: +With `oidc-gateway` v1.1.1, operators may expose two hostnames from one gateway deployment: - an OIDC/JWT hostname (`ingress.oidc`) for `--auth-mode=oidc`, `--auth-mode=jwt`, cached OIDC login, pre-issued OIDC tokens, and GitHub Actions OIDC tokens - an mTLS hostname (`ingress.mtls`) for `--auth-mode=x509` or `--auth-mode=tls`, where the gateway must see the client certificate -Use the hostname that matches the credential you send. Bearer JWT traffic should target the OIDC/JWT endpoint; X.509-SVID mTLS traffic should target the mTLS endpoint. +Use the hostname that matches the credential you send. Bearer JWT traffic should target the OIDC/JWT endpoint; X.509-SVID mTLS traffic should target the mTLS endpoint. In v1.1.1, the downstream mTLS listener advertises HTTP/2 (`h2`) with ALPN so modern gRPC clients can complete the TLS handshake. ```bash # OIDC/JWT endpoint: Envoy validates the bearer token with jwt_authn diff --git a/docs/dir/directory-oidc-authentication.md b/docs/dir/directory-oidc-authentication.md index a891616..9079ba2 100644 --- a/docs/dir/directory-oidc-authentication.md +++ b/docs/dir/directory-oidc-authentication.md @@ -7,7 +7,7 @@ At a high level: - Directory is OIDC IdP agnostic for external access. - `Envoy` and `ext_authz` form the authentication and authorization layer at the edge. - `Dex` is one useful deployment pattern, not a requirement. -- `oidc-gateway` v1.1.0 accepts OIDC JWT, SPIFFE JWT-SVID, and SPIFFE X.509-SVID identities, and can expose OIDC/JWT and X.509-SVID mTLS traffic on separate hostnames from one gateway deployment. +- `oidc-gateway` v1.1.1 accepts OIDC JWT, SPIFFE JWT-SVID, and SPIFFE X.509-SVID identities, and can expose OIDC/JWT and X.509-SVID mTLS traffic on separate hostnames from one gateway deployment. - Internal backend trust can remain SPIFFE-based even when external callers use OIDC bearer tokens. ## Why Use OIDC @@ -68,10 +68,10 @@ At the edge: 3. `ext_authz` maps trusted identity data to a canonical principal and role policy. 4. Only authorized requests reach the Directory API. -With `oidc-gateway` v1.1.0, the recommended production shape is a single gateway deployment with two optional downstream endpoints: +With `oidc-gateway` v1.1.1, the recommended production shape is a single gateway deployment with two optional downstream endpoints: - `envoy.endpoints.oidc` plus `ingress.oidc` for human users, CI, and automation that present bearer JWTs. -- `envoy.endpoints.mtls` plus `ingress.mtls` for SPIFFE X.509-SVID clients. This listener typically requires a client certificate and does not run Envoy `jwt_authn`; `ext_authz` authorizes the SPIFFE principal derived from the TLS session. +- `envoy.endpoints.mtls` plus `ingress.mtls` for SPIFFE X.509-SVID clients. This listener typically requires a client certificate and does not run Envoy `jwt_authn`; `ext_authz` authorizes the SPIFFE principal derived from the TLS session. The downstream TLS listener advertises HTTP/2 (`h2`) with ALPN for gRPC client compatibility. This keeps token handling and policy enforcement at the edge rather than spreading it across clients and backend services. From e6f0989cbaabed229de797ccccb85103d24bc2b6 Mon Sep 17 00:00:00 2001 From: Tibor Kircsi Date: Wed, 13 May 2026 11:41:06 +0200 Subject: [PATCH 3/3] docs(dir): update cli reference with the context command Signed-off-by: Tibor Kircsi --- docs/dir/directory-cli-reference.md | 150 ++++++++++++++++++++++ docs/dir/directory-cli.md | 53 ++++++++ docs/dir/directory-oidc-authentication.md | 6 +- 3 files changed, 208 insertions(+), 1 deletion(-) diff --git a/docs/dir/directory-cli-reference.md b/docs/dir/directory-cli-reference.md index 594463f..6ccc6f7 100644 --- a/docs/dir/directory-cli-reference.md +++ b/docs/dir/directory-cli-reference.md @@ -146,6 +146,156 @@ Checks whether the daemon is currently running by inspecting the PID file. Daemon is not running ``` +## Context Operations + +Starting with Directory v1.4.0, the context commands manage reusable `dirctl` client contexts. Contexts describe Directory endpoints and their client-side authentication, TLS, and SPIFFE settings. + +The default config file is `~/.config/dirctl/config.yaml`, or `$XDG_CONFIG_HOME/dirctl/config.yaml` when `XDG_CONFIG_HOME` is set. + +```yaml +current_context: prod +contexts: + prod: + server_address: prod.gateway.example.com:443 + auth_mode: oidc + oidc_issuer: https://prod.idp.example.com + oidc_client_id: dirctl + staging: + server_address: staging.gateway.example.com:443 + auth_mode: oidc + oidc_issuer: https://staging.idp.example.com + oidc_client_id: dirctl +``` + +Regular `dirctl` commands select a context in this order: + +1. `--context ` +2. `DIRECTORY_CLIENT_CONTEXT` +3. `current_context` from the config file + +After context selection, environment variables and explicit root flags such as `--server-addr`, `--auth-mode`, `--oidc-issuer`, and `--auth-token` override the selected context for that invocation. + +!!! note "Sensitive values" + + `dirctl context show` redacts `auth_token` and `spiffe_token` values. Prefer environment variables or a secret manager for bearer tokens instead of storing long-lived tokens in `config.yaml`. + +### `dirctl context list` + +Lists configured contexts in sorted order and marks the persisted `current_context` with `*`. This command reads local config only; it does not contact a Directory server. + +??? example + + ```bash + dirctl context list + ``` + + Example output: + + ```text + * prod + staging + ``` + +### `dirctl context current` + +Prints the persisted `current_context` from the config file. It intentionally ignores one-off overrides such as `--context` and `DIRECTORY_CLIENT_CONTEXT`, so it matches the marker shown by `dirctl context list`. + +| Flag | Description | Default | +|------|-------------|---------| +| `--quiet` | Print only the context name; print nothing when no context is set | `false` | +| `--json` | Print current context details as JSON | `false` | + +??? example + + ```bash + # Print the current context with a trailing newline + dirctl context current + + # Prompt-friendly output + dirctl context current --quiet + + # Machine-readable output + dirctl context current --json + ``` + + Example JSON output: + + ```json + { + "name": "prod", + "source": "current_context", + "path": "/home/user/.config/dirctl/config.yaml" + } + ``` + +### `dirctl context set ` + +Sets the persisted active context by updating `current_context` in the client config file. The target context must already exist. + +??? example + + ```bash + # Persist prod as the active context + dirctl context set prod + + # Run one command against staging without changing current_context + dirctl --context staging info + ``` + +### `dirctl context show [name]` + +Shows the effective client configuration for a context with sensitive values redacted. If `[name]` is omitted, the command uses the same context selection rules as other `dirctl` commands. Environment variables and explicitly changed root flags are included in the effective output. + +??? example + + ```bash + # Show the active effective context + dirctl context show + + # Show a specific context + dirctl context show prod + + # Preview a context with a one-off server override + dirctl --server-addr localhost:8888 context show prod + ``` + + Example output: + + ```yaml + name: prod + source: current_context + path: /home/user/.config/dirctl/config.yaml + config: + auth_mode: oidc + oidc_client_id: dirctl + oidc_issuer: https://prod.idp.example.com + server_address: prod.gateway.example.com:443 + tls_skip_verify: false + ``` + +### `dirctl context validate [name]` + +Validates stored context definitions without applying environment variable overrides. It catches missing required fields, unsupported auth modes, and auth-mode-specific configuration mistakes before a context is used. + +If `[name]` is provided, only that context is validated. If omitted, all configured contexts are validated in sorted order. + +??? example + + ```bash + # Validate all contexts + dirctl context validate + + # Validate one context + dirctl context validate prod + ``` + + Example output: + + ```text + prod: ok + staging: ok + ``` + ## Storage Operations ### `dirctl push ` diff --git a/docs/dir/directory-cli.md b/docs/dir/directory-cli.md index 5ea9225..78d7a5a 100644 --- a/docs/dir/directory-cli.md +++ b/docs/dir/directory-cli.md @@ -313,6 +313,55 @@ dirctl events listen --labels /skills/AI --output jsonl | \ dirctl events listen --output raw | tee event-cids.txt ``` +### Context Workflow + +Starting with Directory v1.4.0, `dirctl` can store reusable client contexts for multiple Directory endpoints. A context bundles the server address, authentication mode, OIDC settings, and TLS/SPIFFE settings that would otherwise be repeated with flags or environment variables. + +Contexts live in `~/.config/dirctl/config.yaml` by default, or under `$XDG_CONFIG_HOME/dirctl/config.yaml` when `XDG_CONFIG_HOME` is set. + +```yaml +current_context: dev +contexts: + dev: + server_address: dev.gateway.example.com:443 + auth_mode: oidc + oidc_issuer: https://dev.idp.example.com + oidc_client_id: dirctl + prod: + server_address: prod.gateway.example.com:443 + auth_mode: oidc + oidc_issuer: https://prod.idp.example.com + oidc_client_id: dirctl +``` + +Use `dirctl context` to inspect and switch the persisted active context: + +```bash +# List configured contexts; the active persisted context is marked with '*' +dirctl context list + +# Print the persisted active context +dirctl context current + +# Switch the persisted active context +dirctl context set prod + +# Show the effective context with sensitive values redacted +dirctl context show + +# Validate one or all configured contexts +dirctl context validate +dirctl context validate prod +``` + +For one command, use `--context` instead of changing `current_context`: + +```bash +dirctl --context prod search --skill "natural_language_processing" +``` + +Context values can still be overridden by environment variables or explicit root flags such as `--server-addr`, `--auth-mode`, or `--oidc-issuer`. Prefer environment variables or a secret manager for bearer tokens instead of storing long-lived `auth_token` values in the config file. + ## Output Formats All `dirctl` commands support multiple output formats via the `--output` (or `-o`) flag, making it easy to switch between human-readable output and machine-processable formats. @@ -559,6 +608,9 @@ dirctl --server-addr localhost:8888 routing list # Use environment variable export DIRECTORY_CLIENT_SERVER_ADDRESS=localhost:8888 dirctl routing list + +# Use a reusable client context +dirctl --context dev routing list ``` ### Authentication @@ -583,6 +635,7 @@ The CLI follows a clear service-based organization: - **Daemon**: Local directory server (`daemon start`, `daemon stop`, `daemon status`). - **Auth**: OIDC authentication (`auth login`, `auth logout`, `auth status`). +- **Context**: Reusable client contexts (`context list`, `context current`, `context set`, `context show`, `context validate`). - **Storage**: Direct record management (`push`, `pull`, `delete`, `info`). - **Import**: Batch imports from external registries (`import`). - **Export**: Export records to external formats (`export`). diff --git a/docs/dir/directory-oidc-authentication.md b/docs/dir/directory-oidc-authentication.md index 9079ba2..bc0cc9a 100644 --- a/docs/dir/directory-oidc-authentication.md +++ b/docs/dir/directory-oidc-authentication.md @@ -75,6 +75,10 @@ With `oidc-gateway` v1.1.1, the recommended production shape is a single gateway This keeps token handling and policy enforcement at the edge rather than spreading it across clients and backend services. +!!! note "v1.1.1 mTLS gRPC compatibility" + + `oidc-gateway` v1.1.1 fixes downstream gRPC interoperability for the SPIFFE X.509-SVID mTLS listener by advertising HTTP/2 (`h2`) with ALPN in Envoy's downstream TLS configuration. If mTLS clients fail during the TLS handshake with an error similar to `credentials: cannot check peer: missing selected ALPN property`, upgrade the gateway to v1.1.1 or verify that the rendered Envoy listener includes `alpn_protocols: ["h2"]`. + ## Supported Identity Patterns ### Human Interactive Login @@ -227,7 +231,7 @@ To configure `oidc-gateway`: This block does these important things: - `endpoints.oidc` exposes the listener that accepts OIDC JWT, GitHub OIDC, and SPIFFE JWT-SVID bearer tokens. - - `endpoints.mtls` exposes the listener that accepts SPIFFE X.509-SVID client certificates over downstream mTLS. + - `endpoints.mtls` exposes the listener that accepts SPIFFE X.509-SVID client certificates over downstream mTLS. In v1.1.1, downstream TLS listeners advertise HTTP/2 (`h2`) with ALPN for gRPC clients. - `backend.*` points Envoy at the internal Kubernetes Service for the Directory API, not at either public ingress hostname. - `oidc.issuers[]` creates Envoy `jwt_authn` providers and JWKS clusters for bearer JWT validation. - `oidc.github.*` enables JWT validation for GitHub Actions workload identity tokens.