From 74a7d793b570dd7246fe55d5b3072b010700b9cb Mon Sep 17 00:00:00 2001 From: Gloria Ciavarrini Date: Tue, 15 Sep 2026 18:54:21 +0200 Subject: [PATCH 1/7] docs: document authentication for CLI, API, and UI User-facing auth setup, login, tokens, and troubleshooting aligned with the control-plane Keycloak/OIDC behavior. Assisted-By: Claude (Anthropic) Signed-off-by: Gloria Ciavarrini --- content/docs/getting-started/_index.md | 2 + .../docs/getting-started/authentication.md | 209 ++++++++++++++++++ content/docs/getting-started/local-setup.md | 8 +- .../docs/getting-started/troubleshooting.md | 20 ++ content/docs/user-guide/_index.md | 2 + content/docs/user-guide/cli-configuration.md | 49 ++++ content/docs/user-guide/ui.md | 12 + cspell.yaml | 4 + 8 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 content/docs/getting-started/authentication.md diff --git a/content/docs/getting-started/_index.md b/content/docs/getting-started/_index.md index 290cb06..d0ec069 100644 --- a/content/docs/getting-started/_index.md +++ b/content/docs/getting-started/_index.md @@ -20,5 +20,7 @@ Get up and running with DCM. Deploy a VM by creating an instance of the catalog item. - **[Register Another Provider](register-another-provider/)** — Register a second service provider and create a random selection policy. +- **[Authentication](authentication/)** — Enable auth, log in with the CLI or + UI, and call the API with bearer tokens. - **[Troubleshooting](troubleshooting/)** — Diagnose issues using container logs and status checks. diff --git a/content/docs/getting-started/authentication.md b/content/docs/getting-started/authentication.md new file mode 100644 index 0000000..2375b8b --- /dev/null +++ b/content/docs/getting-started/authentication.md @@ -0,0 +1,209 @@ +--- +title: Authentication +type: docs +weight: 7 +--- + +DCM can require authentication for control-plane API access. When authentication +is enabled, clients must present a valid JWT bearer token (or use the supported +proxy-header path). This page describes how to configure and use authenticated +environments. For compose and Helm operator steps, see the +[control-plane deploy guide](https://github.com/dcm-project/control-plane/blob/main/deploy/RUN.md#authentication). + +## Overview + +By default, local deployments run with authentication **disabled** +(`AUTH_DISABLED=true`). Any client that can reach the API can call protected +endpoints without credentials. This keeps getting-started tutorials simple. + +For shared or production environments, operators enable authentication so every +API request is tied to an identity. The control plane validates JSON Web Tokens +(JWTs) issued by an OpenID Connect (OIDC) identity provider. The reference local +stack uses [Keycloak](https://www.keycloak.org/) with a pre-imported `dcm` +realm. + +The `/api/v1alpha1/health` endpoint stays **unauthenticated** whether or not +auth is enabled. + +> **Service providers:** Service providers do not send authentication headers to +> the control plane yet. Enabling auth on the control plane can break +> service-provider registration and instance workflows until service-provider +> authentication is available. Use auth for CLI, UI, and direct API access +> first, or keep auth disabled while exercising full SP flows locally. + +## How authentication works + +1. A user or automation obtains an access token from the identity provider + (Keycloak in the reference stack). +2. The client sends `Authorization: Bearer ` on each API request. +3. The control plane validates the token (signature, expiry, issuer, audience) + using the provider's JWKS keys, then resolves the caller to a DCM **actor**. +4. On first login, unknown identities are provisioned automatically + (just-in-time provisioning). No manual database setup is required for new + Keycloak users in the `dcm` realm. + +Tokens must include the audience expected by the control plane (default +`dcm-api` via `AUTH_JWT_AUDIENCE`). + +An optional **proxy-header** path accepts `X-Forwarded-User` and +`X-Forwarded-Preferred-Username` when `X-Auth-Proxy-Secret` matches +`AUTH_PROXY_SECRET`. Most users rely on JWT bearer tokens instead. + +## Prerequisites for authenticated deployments + +Before users can log in, the platform operator must: + +1. Run an OIDC identity provider reachable from both the control plane and + clients (Keycloak with the `auth` compose profile in local stacks). +2. Set control-plane authentication environment variables (see below). +3. Ensure JWT clients exist for your callers (reference realm includes `dcm-cli` + for the CLI device flow and `dcm-proxy` for programmatic access). +4. Set `DCM_ADMIN_SUBJECT` to the Keycloak `sub` of the bootstrap admin user + when auth is first enabled. + +To start the reference compose stack with authentication: + +```bash +cd control-plane +cp deploy/.env.example deploy/.env +# Uncomment the "Enable authentication" block in deploy/.env +make compose-up AUTH=true +``` + +Keycloak is published at `http://localhost:8180` when the auth profile is +active. Lab usernames and passwords are documented in +[deploy/RUN.md](https://github.com/dcm-project/control-plane/blob/main/deploy/RUN.md#authentication). + +For Kubernetes installs, create the auth secret and set `auth.enabled=true` as +described in the +[Helm chart README](https://github.com/dcm-project/control-plane/blob/main/deploy/helm/dcm/README.md#authentication). + +### Control-plane configuration + +| Variable | Purpose | +| ------------------- | ----------------------------------------------------------------------------------- | +| `AUTH_DISABLED` | When `true`, auth middleware is bypassed (default for local dev). | +| `AUTH_ISSUER_URL` | OIDC issuer URL for JWT validation (for example `http://keycloak:8080/realms/dcm`). | +| `AUTH_JWT_AUDIENCE` | Expected `aud` claim in access tokens (default `dcm-api`). | +| `AUTH_PROXY_SECRET` | Shared secret for the proxy-header auth path (optional). | +| `AUTH_CACHE_TTL` | How long resolved actors are cached (default `60s`). | +| `DCM_ADMIN_SUBJECT` | Keycloak subject UUID for the seed admin actor (required when auth is enabled). | + +Copy secrets and toggles from `deploy/.env.example` into `deploy/.env`. Do not +commit real production secrets to documentation or source control. + +## Authenticated user workflows + +### CLI (interactive) + +The DCM CLI authenticates with the OIDC **device authorization** flow: + +1. Configure the OIDC issuer (once per environment). +2. Run `dcm login`. +3. Open the URL shown in the terminal and approve the request in the browser. +4. Run normal `dcm` commands; the CLI attaches a bearer token and refreshes it + before expiry. + +Example for the reference Keycloak realm (issuer must match OIDC discovery): + +```bash +export DCM_ISSUER_URL=http://keycloak:8080/realms/dcm +dcm login --control-plane-url http://localhost:8080 +dcm sp provider list +``` + +`dcm login` saves `issuer-url` in `~/.dcm/config.yaml`. Tokens are stored in the +OS keyring when available, otherwise in `~/.dcm/tokens.json` (mode `0600`). + +To sign out and remove stored tokens: + +```bash +dcm logout +``` + +See [CLI Configuration](../user-guide/cli-configuration/#authentication) for +flags and environment variables. + +> **Local issuer hostname:** The reference stack advertises issuer +> `http://keycloak:8080/realms/dcm`. The control plane resolves `keycloak` on +> the compose network. On your host, map `keycloak` to the Keycloak container IP +> (or use the same issuer URL your operator documents) so `dcm login` can reach +> OIDC discovery and the device flow. + +### Web UI (Backstage) + +The [DCM UI](../user-guide/ui/) runs as a Backstage plugin. In authenticated +deployments, sign in through whatever identity provider your Backstage (or Red +Hat Developer Hub) instance uses. The plugin forwards OAuth2 bearer tokens to +the control plane, which validates them the same way as CLI and API tokens. + +Configure Backstage SSO to use the same Keycloak realm (or corporate IdP) your +control plane trusts. The local compose `dcm-ui` image does not configure +Backstage SSO by itself; treat UI login as an operator concern tied to your +Backstage deployment. + +### Adding users (Keycloak) + +In the reference stack, create users in the Keycloak admin console +(`http://localhost:8180`, realm `dcm`). New users receive DCM actors on first +authenticated API call. See +[Adding users](https://github.com/dcm-project/control-plane/blob/main/deploy/RUN.md#adding-users) +in the deploy guide. + +## Obtaining and using tokens + +### CLI session tokens + +After `dcm login`, you do not pass tokens manually. The CLI reads stored +credentials using `issuer-url` from config. + +### Static bearer token (CI and scripts) + +For non-interactive use, set a bearer access token directly: + +```bash +export DCM_TOKEN="" +export DCM_CONTROL_PLANE_URL=http://localhost:8080 +dcm sp provider list +``` + +Or pass `--token` on a single invocation. Static tokens skip the device flow and +do not use the token store. Obtain access tokens from your identity provider +(for example Keycloak token endpoint with a client your operator provisioned). + +### Authenticating API requests + +Send the access token on every request except health checks: + +```bash +curl -s \ + -H "Authorization: Bearer ${DCM_TOKEN}" \ + http://localhost:8080/api/v1alpha1/providers +``` + +Without a valid token when auth is enabled, the API returns `401 Unauthorized`. +If the identity is valid but the DCM actor is suspended or deactivated, the API +returns `403 Forbidden`. + +## Troubleshooting + +| Symptom | Things to check | +| ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| `401 Unauthorized` on API or CLI | Auth enabled on control plane; token present and not expired; `aud` includes `dcm-api`; issuer matches `AUTH_ISSUER_URL`. | +| `dcm login` cannot reach issuer | Issuer URL matches `.well-known/openid-configuration`; host can resolve and reach Keycloak; TLS settings if using HTTPS. | +| Issuer / JWKS errors in control-plane logs | `AUTH_ISSUER_URL` reachable from the control-plane container; Keycloak healthy (`auth` profile running). | +| CLI works but UI fails (or reverse) | Backstage SSO and control plane must trust the same IdP and audience; plugin backend URL points at the control plane. | +| `403 Forbidden` with valid token | Actor suspended or deactivated; wait up to `AUTH_CACHE_TTL` after status changes. | +| Service provider errors after enabling auth | Expected until SP auth exists; disable auth for SP-heavy dev or set `AUTH_DISABLED=true` on control plane for SP testing only (not for production). | +| Device flow times out | Complete browser approval within the time shown; retry `dcm login`. | + +Verify Keycloak readiness when using compose: + +```bash +curl -sf http://localhost:8180/realms/dcm/.well-known/openid-configuration | jq .issuer +``` + +The issuer string must match `AUTH_ISSUER_URL` and the issuer you pass to +`dcm login`. + +For container logs and restarts, see [Troubleshooting](troubleshooting/). diff --git a/content/docs/getting-started/local-setup.md b/content/docs/getting-started/local-setup.md index 274311f..ebb922e 100644 --- a/content/docs/getting-started/local-setup.md +++ b/content/docs/getting-started/local-setup.md @@ -28,6 +28,9 @@ podman-compose up -d The control-plane API is available at `http://localhost:8080`. The DCM UI is at `http://localhost:7007`. +Authentication is **disabled by default** on this stack. To require login and +JWT bearer tokens, see [Authentication](authentication/). + ## Running with the KubeVirt Service Provider The `kubevirt-service-provider` is behind a compose profile and does not start @@ -58,12 +61,15 @@ curl http://localhost:8080/api/v1alpha1/health ``` If you deployed with the KubeVirt provider, you can also list the registered -providers: +providers (when authentication is disabled): ```bash curl http://localhost:8080/api/v1alpha1/providers ``` +When authentication is enabled, add `Authorization: Bearer ` to API +calls. See [Authentication](authentication/#authenticating-api-requests). + ## Setting Up the CLI The DCM CLI (`dcm`) lets you interact with the DCM control plane from the diff --git a/content/docs/getting-started/troubleshooting.md b/content/docs/getting-started/troubleshooting.md index c54f973..6b54d2d 100644 --- a/content/docs/getting-started/troubleshooting.md +++ b/content/docs/getting-started/troubleshooting.md @@ -54,3 +54,23 @@ If a service is unhealthy or not responding: ```bash podman-compose restart ``` + +## Authentication + +If API calls or the CLI return `401 Unauthorized`, see +[Authentication](authentication/) for enabling auth, obtaining tokens, and +issuer configuration. + +Common checks: + +- Control plane has `AUTH_DISABLED=false` and `AUTH_ISSUER_URL` set. +- CLI: run `dcm login` or set `DCM_TOKEN` / `--token`. +- Token audience includes `dcm-api` (or your configured `AUTH_JWT_AUDIENCE`). +- Keycloak (or your IdP) is running and reachable from the control plane. + +When auth is enabled, unauthenticated `curl` calls to protected endpoints fail +by design. The health endpoint remains open: + +```bash +curl http://localhost:8080/api/v1alpha1/health +``` diff --git a/content/docs/user-guide/_index.md b/content/docs/user-guide/_index.md index 8add9d9..b20b438 100644 --- a/content/docs/user-guide/_index.md +++ b/content/docs/user-guide/_index.md @@ -12,6 +12,8 @@ Learn how to manage DCM resources using the CLI or the web UI. - **[CLI Configuration](cli-configuration/)** — Configure the CLI, global flags, output formats, and shell completion. +- **[Authentication](../getting-started/authentication/)** — Log in, use bearer + tokens, and work with auth-enabled control planes. - **[Providers](providers/)** — View registered service providers and their health status. - **[Service Types](service-types/)** — Browse available service type diff --git a/content/docs/user-guide/cli-configuration.md b/content/docs/user-guide/cli-configuration.md index f2b6868..aef9d6d 100644 --- a/content/docs/user-guide/cli-configuration.md +++ b/content/docs/user-guide/cli-configuration.md @@ -24,8 +24,12 @@ tls-ca-cert: "" tls-client-cert: "" tls-client-key: "" tls-skip-verify: false +issuer-url: "" ``` +When the control plane requires authentication, set `issuer-url` or use +`dcm login` (see [Authentication](../getting-started/authentication/)). + ## Configuration Priority Settings are resolved in the following order (highest priority first): @@ -45,6 +49,51 @@ The following flags are available on all commands: | `--output` | `-o` | `table` | Output format (`table`, `json`, `yaml`) | | `--timeout` | | `30` | Request timeout in seconds | | `--config` | | `~/.dcm/config.yaml` | Path to configuration file | +| `--issuer-url` | | _(empty)_ | OIDC issuer URL for authentication | +| `--token` | | _(empty)_ | Bearer token (skips interactive login) | + +## Authentication + +When the control plane has authentication enabled, the CLI must send a JWT +bearer token on each request. Use one of the following approaches: + +### Interactive login + +Run `dcm login` after setting the issuer URL. The command runs the OIDC device +authorization flow in your browser and stores tokens locally. + +```bash +dcm login --issuer-url http://keycloak:8080/realms/dcm \ + --control-plane-url http://localhost:8080 +``` + +On success, `issuer-url` is saved in the config file. Use `dcm logout` to revoke +stored refresh tokens and clear credentials. + +### Static token + +For scripts and CI, pass a bearer access token without using the device flow: + +| Flag / variable | Description | +| --------------- | ------------------------------------------- | +| `--token` | Bearer access token for this invocation | +| `DCM_TOKEN` | Same as `--token`, via environment variable | + +When `--token` or `DCM_TOKEN` is set, the CLI does not read the token store. + +### Auth-related settings + +| Flag / variable | Config key | Description | +| ---------------- | ------------ | --------------------------------------------------------- | +| `--issuer-url` | `issuer-url` | OIDC issuer URL (required for `dcm login` / `dcm logout`) | +| `DCM_ISSUER_URL` | `issuer-url` | Environment override for issuer URL | + +If `issuer-url` is set (and no static token is configured), the CLI loads tokens +from the OS keyring or `~/.dcm/tokens.json` and refreshes access tokens before +they expire. + +Full workflows, control-plane settings, and troubleshooting are in +[Authentication](../getting-started/authentication/). ## TLS Configuration diff --git a/content/docs/user-guide/ui.md b/content/docs/user-guide/ui.md index eeeb08f..ae3393a 100644 --- a/content/docs/user-guide/ui.md +++ b/content/docs/user-guide/ui.md @@ -35,6 +35,18 @@ Center** in the Backstage sidebar, or go directly to `/dcm`. The Data Center page is organized into six tabs, one for each core DCM resource type. +## Authentication + +When the control plane requires JWT authentication, sign in to Backstage (or Red +Hat Developer Hub) using the identity provider your platform operator +configured. The DCM plugin sends bearer tokens to the control plane on your +behalf. + +The default local compose UI does not configure Backstage SSO. For auth-enabled +local stacks, use the CLI (`dcm login`) or API tokens for control-plane access, +and configure Backstage authentication separately for browser login. See +[Authentication](../getting-started/authentication/#web-ui-backstage). + ## Common patterns All tabs share the following interaction patterns: diff --git a/cspell.yaml b/cspell.yaml index c714d19..f3c3bd0 100644 --- a/cspell.yaml +++ b/cspell.yaml @@ -15,6 +15,10 @@ words: - kubevirt - hextra - hyperscaler + - JWKS + - Keycloak + - keyring + - OIDC - rgba - vcpu - omachace From a8827daac4015d6c3b2f4cde5b133d9196864aaf Mon Sep 17 00:00:00 2001 From: Gloria Ciavarrini Date: Tue, 15 Sep 2026 19:00:19 +0200 Subject: [PATCH 2/7] docs: put Keycloak host setup before CLI login example Document /etc/hosts and issuer discovery before dcm login so host-side OIDC matches stock compose; link from CLI config. Assisted-By: Claude (Anthropic) Signed-off-by: Gloria Ciavarrini --- .../docs/getting-started/authentication.md | 41 +++++++++++++++---- content/docs/user-guide/cli-configuration.md | 4 ++ cspell.yaml | 1 + 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/content/docs/getting-started/authentication.md b/content/docs/getting-started/authentication.md index 2375b8b..88de3b4 100644 --- a/content/docs/getting-started/authentication.md +++ b/content/docs/getting-started/authentication.md @@ -104,7 +104,38 @@ The DCM CLI authenticates with the OIDC **device authorization** flow: 4. Run normal `dcm` commands; the CLI attaches a bearer token and refreshes it before expiry. -Example for the reference Keycloak realm (issuer must match OIDC discovery): +#### Local compose: host access to Keycloak + +The reference stack sets the Keycloak hostname so the issuer is +`http://keycloak:8080/realms/dcm`. That name resolves on the compose network, +not on your laptop by default. Before you run `dcm login` on the host, map +`keycloak` to the Keycloak container IP (repeat after recreating the container +if the IP changes): + +```bash +KC_IP=$(podman inspect "$(podman ps -q --filter name=keycloak)" \ + --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}') +grep -qE '[[:space:]]keycloak$' /etc/hosts \ + && sudo sed -i -E "s/^[0-9.]+[[:space:]]+keycloak$/${KC_IP} keycloak/" /etc/hosts \ + || echo "${KC_IP} keycloak" | sudo tee -a /etc/hosts +getent hosts keycloak +``` + +Confirm the issuer string matches what the control plane expects: + +```bash +curl -sf http://keycloak:8080/realms/dcm/.well-known/openid-configuration \ + | jq -r .issuer +``` + +You should see `http://keycloak:8080/realms/dcm`. The admin UI is also at +`http://localhost:8180`, but do not use `localhost:8180` as the issuer URL on +stock compose: discovery still advertises `keycloak:8080`, and the control plane +must use the same issuer as `AUTH_ISSUER_URL`. + +#### Example login + +Example for the reference Keycloak realm after host resolution is in place: ```bash export DCM_ISSUER_URL=http://keycloak:8080/realms/dcm @@ -124,12 +155,6 @@ dcm logout See [CLI Configuration](../user-guide/cli-configuration/#authentication) for flags and environment variables. -> **Local issuer hostname:** The reference stack advertises issuer -> `http://keycloak:8080/realms/dcm`. The control plane resolves `keycloak` on -> the compose network. On your host, map `keycloak` to the Keycloak container IP -> (or use the same issuer URL your operator documents) so `dcm login` can reach -> OIDC discovery and the device flow. - ### Web UI (Backstage) The [DCM UI](../user-guide/ui/) runs as a Backstage plugin. In authenticated @@ -190,7 +215,7 @@ returns `403 Forbidden`. | Symptom | Things to check | | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | `401 Unauthorized` on API or CLI | Auth enabled on control plane; token present and not expired; `aud` includes `dcm-api`; issuer matches `AUTH_ISSUER_URL`. | -| `dcm login` cannot reach issuer | Issuer URL matches `.well-known/openid-configuration`; host can resolve and reach Keycloak; TLS settings if using HTTPS. | +| `dcm login` cannot reach issuer | Host maps `keycloak` (see [host access](#local-compose-host-access-to-keycloak)); issuer matches discovery; TLS if using HTTPS. | | Issuer / JWKS errors in control-plane logs | `AUTH_ISSUER_URL` reachable from the control-plane container; Keycloak healthy (`auth` profile running). | | CLI works but UI fails (or reverse) | Backstage SSO and control plane must trust the same IdP and audience; plugin backend URL points at the control plane. | | `403 Forbidden` with valid token | Actor suspended or deactivated; wait up to `AUTH_CACHE_TTL` after status changes. | diff --git a/content/docs/user-guide/cli-configuration.md b/content/docs/user-guide/cli-configuration.md index aef9d6d..eb0877b 100644 --- a/content/docs/user-guide/cli-configuration.md +++ b/content/docs/user-guide/cli-configuration.md @@ -62,6 +62,10 @@ bearer token on each request. Use one of the following approaches: Run `dcm login` after setting the issuer URL. The command runs the OIDC device authorization flow in your browser and stores tokens locally. +For the reference compose stack, map the hostname `keycloak` on your host before +login (see +[Local compose: host access to Keycloak](../getting-started/authentication/#local-compose-host-access-to-keycloak)). + ```bash dcm login --issuer-url http://keycloak:8080/realms/dcm \ --control-plane-url http://localhost:8080 diff --git a/cspell.yaml b/cspell.yaml index f3c3bd0..93cea11 100644 --- a/cspell.yaml +++ b/cspell.yaml @@ -8,6 +8,7 @@ words: - flocati - pkliczewski - gabriel-farache + - getent - rego - opa - dcm From b1c378bdb5e24f9f417837ee44b7e030b48c7409 Mon Sep 17 00:00:00 2001 From: Gloria Ciavarrini Date: Wed, 16 Sep 2026 12:08:09 +0200 Subject: [PATCH 3/7] docs: tighten authentication guide from review Agent path, audience, proxy reference, warning callout, flag-based CLI examples. Assisted-By: Claude (Anthropic) Signed-off-by: Gloria Ciavarrini --- .../docs/getting-started/authentication.md | 79 +++++++++++-------- 1 file changed, 45 insertions(+), 34 deletions(-) diff --git a/content/docs/getting-started/authentication.md b/content/docs/getting-started/authentication.md index 88de3b4..f518b3d 100644 --- a/content/docs/getting-started/authentication.md +++ b/content/docs/getting-started/authentication.md @@ -16,20 +16,25 @@ By default, local deployments run with authentication **disabled** (`AUTH_DISABLED=true`). Any client that can reach the API can call protected endpoints without credentials. This keeps getting-started tutorials simple. -For shared or production environments, operators enable authentication so every -API request is tied to an identity. The control plane validates JSON Web Tokens -(JWTs) issued by an OpenID Connect (OIDC) identity provider. The reference local -stack uses [Keycloak](https://www.keycloak.org/) with a pre-imported `dcm` -realm. +For shared or production environments, operators **should** enable +authentication so every API request is tied to an identity. The control plane +validates JSON Web Tokens (JWTs) issued by an OpenID Connect (OIDC) identity +provider. The reference local stack uses [Keycloak](https://www.keycloak.org/) +with a pre-imported `dcm` realm. The `/api/v1alpha1/health` endpoint stays **unauthenticated** whether or not auth is enabled. -> **Service providers:** Service providers do not send authentication headers to -> the control plane yet. Enabling auth on the control plane can break -> service-provider registration and instance workflows until service-provider -> authentication is available. Use auth for CLI, UI, and direct API access -> first, or keep auth disabled while exercising full SP flows locally. +> **Service providers:** Service providers reach the control plane through the +> environment agent, not direct HTTP calls. End-to-end authentication for the +> agent and service providers is still in progress (for example +> [environment-agent#38](https://github.com/dcm-project/environment-agent/pull/38) +> and +> [environment-agent#35](https://github.com/dcm-project/environment-agent/pull/35)). +> Enabling auth on the control plane can still affect SP registration and +> instance workflows until that chain is complete. Use auth for CLI, UI, and +> direct API access first, or keep auth disabled while exercising full SP flows +> locally. ## How authentication works @@ -42,12 +47,14 @@ auth is enabled. (just-in-time provisioning). No manual database setup is required for new Keycloak users in the `dcm` realm. -Tokens must include the audience expected by the control plane (default -`dcm-api` via `AUTH_JWT_AUDIENCE`). +Tokens must include the `aud` claim the control plane expects. The default +audience is `dcm-api`; operators can change it with `AUTH_JWT_AUDIENCE`. An optional **proxy-header** path accepts `X-Forwarded-User` and `X-Forwarded-Preferred-Username` when `X-Auth-Proxy-Secret` matches -`AUTH_PROXY_SECRET`. Most users rely on JWT bearer tokens instead. +`AUTH_PROXY_SECRET`. See +[Authentication middleware](https://github.com/dcm-project/enhancements/blob/main/enhancements/authentication/authentication.md#2-authentication-middleware) +in the authentication enhancement. Most users rely on JWT bearer tokens instead. ## Prerequisites for authenticated deployments @@ -89,8 +96,10 @@ described in the | `AUTH_CACHE_TTL` | How long resolved actors are cached (default `60s`). | | `DCM_ADMIN_SUBJECT` | Keycloak subject UUID for the seed admin actor (required when auth is enabled). | -Copy secrets and toggles from `deploy/.env.example` into `deploy/.env`. Do not -commit real production secrets to documentation or source control. +Copy secrets and toggles from `deploy/.env.example` into `deploy/.env`. + +> **Warning:** Do not commit real production secrets to documentation or source +> control. ## Authenticated user workflows @@ -138,8 +147,8 @@ must use the same issuer as `AUTH_ISSUER_URL`. Example for the reference Keycloak realm after host resolution is in place: ```bash -export DCM_ISSUER_URL=http://keycloak:8080/realms/dcm -dcm login --control-plane-url http://localhost:8080 +dcm login --issuer-url http://keycloak:8080/realms/dcm \ + --control-plane-url http://localhost:8080 dcm sp provider list ``` @@ -184,21 +193,23 @@ credentials using `issuer-url` from config. ### Static bearer token (CI and scripts) -For non-interactive use, set a bearer access token directly: +For non-interactive use, pass a bearer access token with `--token` (or set +`DCM_TOKEN`). Point the CLI at the control plane with `--control-plane-url` when +it is not the default `http://localhost:8080`: ```bash -export DCM_TOKEN="" -export DCM_CONTROL_PLANE_URL=http://localhost:8080 -dcm sp provider list +dcm sp provider list \ + --token "" \ + --control-plane-url http://localhost:8080 ``` -Or pass `--token` on a single invocation. Static tokens skip the device flow and -do not use the token store. Obtain access tokens from your identity provider -(for example Keycloak token endpoint with a client your operator provisioned). +Static tokens skip the device flow and do not use the token store. Obtain access +tokens from your identity provider (for example Keycloak token endpoint with a +client your operator provisioned). ### Authenticating API requests -Send the access token on every request except health checks: +Send the access token on protected API requests: ```bash curl -s \ @@ -212,15 +223,15 @@ returns `403 Forbidden`. ## Troubleshooting -| Symptom | Things to check | -| ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | -| `401 Unauthorized` on API or CLI | Auth enabled on control plane; token present and not expired; `aud` includes `dcm-api`; issuer matches `AUTH_ISSUER_URL`. | -| `dcm login` cannot reach issuer | Host maps `keycloak` (see [host access](#local-compose-host-access-to-keycloak)); issuer matches discovery; TLS if using HTTPS. | -| Issuer / JWKS errors in control-plane logs | `AUTH_ISSUER_URL` reachable from the control-plane container; Keycloak healthy (`auth` profile running). | -| CLI works but UI fails (or reverse) | Backstage SSO and control plane must trust the same IdP and audience; plugin backend URL points at the control plane. | -| `403 Forbidden` with valid token | Actor suspended or deactivated; wait up to `AUTH_CACHE_TTL` after status changes. | -| Service provider errors after enabling auth | Expected until SP auth exists; disable auth for SP-heavy dev or set `AUTH_DISABLED=true` on control plane for SP testing only (not for production). | -| Device flow times out | Complete browser approval within the time shown; retry `dcm login`. | +| Symptom | Things to check | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| `401 Unauthorized` on API or CLI | Auth enabled on control plane; token present and not expired; `aud` includes `dcm-api`; issuer matches `AUTH_ISSUER_URL`. | +| `dcm login` cannot reach issuer | Host maps `keycloak` (see [host access](#local-compose-host-access-to-keycloak)); issuer matches discovery; TLS if using HTTPS. | +| Issuer / JWKS errors in control-plane logs | `AUTH_ISSUER_URL` reachable from the control-plane container; Keycloak healthy (`auth` profile running). | +| CLI works but UI fails (or reverse) | Backstage SSO and control plane must trust the same IdP and audience; plugin backend URL points at the control plane. | +| `403 Forbidden` with valid token | Actor suspended or deactivated; wait up to `AUTH_CACHE_TTL` after status changes. | +| Service provider errors after enabling auth | SP traffic uses the environment agent; auth for agent and SP paths is still landing. See SP callout in [Overview](#overview). | +| Device flow times out | Complete browser approval within the time shown; retry `dcm login`. | Verify Keycloak readiness when using compose: From e3904ae82f837bfd4d06075cd05db6d02386c896 Mon Sep 17 00:00:00 2001 From: Gloria Ciavarrini Date: Wed, 16 Sep 2026 12:08:14 +0200 Subject: [PATCH 4/7] docs: note static token precedence in CLI config When --token is set it wins over stored login for API calls. Assisted-By: Claude (Anthropic) Signed-off-by: Gloria Ciavarrini --- content/docs/user-guide/cli-configuration.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/content/docs/user-guide/cli-configuration.md b/content/docs/user-guide/cli-configuration.md index eb0877b..5e115bb 100644 --- a/content/docs/user-guide/cli-configuration.md +++ b/content/docs/user-guide/cli-configuration.md @@ -83,7 +83,10 @@ For scripts and CI, pass a bearer access token without using the device flow: | `--token` | Bearer access token for this invocation | | `DCM_TOKEN` | Same as `--token`, via environment variable | -When `--token` or `DCM_TOKEN` is set, the CLI does not read the token store. +When `--token` or `DCM_TOKEN` is set, the CLI does not read the token store. If +both a static token and `issuer-url` are configured, the static token is used +for API requests (`--token` and `DCM_TOKEN` follow the usual flag-over-env +precedence). Stored login sessions and `dcm logout` still use `issuer-url`. ### Auth-related settings From fe9bea6c58b3ffe200908daf955fc386b0f46089 Mon Sep 17 00:00:00 2001 From: Gloria Ciavarrini Date: Wed, 16 Sep 2026 15:31:19 +0200 Subject: [PATCH 5/7] docs: fix API examples after providers route removal Use catalog-items for auth curl and agents in local setup. Assisted-By: Claude (Anthropic) Signed-off-by: Gloria Ciavarrini --- content/docs/getting-started/authentication.md | 2 +- content/docs/getting-started/local-setup.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/content/docs/getting-started/authentication.md b/content/docs/getting-started/authentication.md index f518b3d..69e47f8 100644 --- a/content/docs/getting-started/authentication.md +++ b/content/docs/getting-started/authentication.md @@ -214,7 +214,7 @@ Send the access token on protected API requests: ```bash curl -s \ -H "Authorization: Bearer ${DCM_TOKEN}" \ - http://localhost:8080/api/v1alpha1/providers + http://localhost:8080/api/v1alpha1/catalog-items ``` Without a valid token when auth is enabled, the API returns `401 Unauthorized`. diff --git a/content/docs/getting-started/local-setup.md b/content/docs/getting-started/local-setup.md index ebb922e..532a412 100644 --- a/content/docs/getting-started/local-setup.md +++ b/content/docs/getting-started/local-setup.md @@ -60,11 +60,11 @@ Check the health endpoint: curl http://localhost:8080/api/v1alpha1/health ``` -If you deployed with the KubeVirt provider, you can also list the registered -providers (when authentication is disabled): +If you deployed with the KubeVirt provider, you can also list registered +environment agents (when authentication is disabled): ```bash -curl http://localhost:8080/api/v1alpha1/providers +curl http://localhost:8080/api/v1alpha1/agents ``` When authentication is enabled, add `Authorization: Bearer ` to API From 835d65e8c441b75632dd9af50ca58923dbad2d45 Mon Sep 17 00:00:00 2001 From: Gloria Ciavarrini Date: Thu, 17 Sep 2026 09:23:15 +0200 Subject: [PATCH 6/7] docs: fix AUTH_JWT_AUDIENCE empty-default behavior Empty means no audience check; call out explicit dcm-api for reference auth stacks. Assisted-By: Claude (Anthropic) Signed-off-by: Gloria Ciavarrini --- .../docs/getting-started/authentication.md | 27 ++++++++++--------- .../docs/getting-started/troubleshooting.md | 3 ++- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/content/docs/getting-started/authentication.md b/content/docs/getting-started/authentication.md index 69e47f8..98b3dd8 100644 --- a/content/docs/getting-started/authentication.md +++ b/content/docs/getting-started/authentication.md @@ -47,8 +47,11 @@ auth is enabled. (just-in-time provisioning). No manual database setup is required for new Keycloak users in the `dcm` realm. -Tokens must include the `aud` claim the control plane expects. The default -audience is `dcm-api`; operators can change it with `AUTH_JWT_AUDIENCE`. +Tokens may need an `aud` claim that matches `AUTH_JWT_AUDIENCE`. The control +plane does **not** default this variable: if it is empty, audience validation is +**disabled** (any client token from the issuer realm can be accepted). For +production-like setups, set `AUTH_JWT_AUDIENCE` explicitly (the reference +Keycloak realm uses `dcm-api` in `deploy/.env.example` when auth is enabled). An optional **proxy-header** path accepts `X-Forwarded-User` and `X-Forwarded-Preferred-Username` when `X-Auth-Proxy-Secret` matches @@ -91,7 +94,7 @@ described in the | ------------------- | ----------------------------------------------------------------------------------- | | `AUTH_DISABLED` | When `true`, auth middleware is bypassed (default for local dev). | | `AUTH_ISSUER_URL` | OIDC issuer URL for JWT validation (for example `http://keycloak:8080/realms/dcm`). | -| `AUTH_JWT_AUDIENCE` | Expected `aud` claim in access tokens (default `dcm-api`). | +| `AUTH_JWT_AUDIENCE` | Expected `aud` claim when set; empty disables audience checks (see above). | | `AUTH_PROXY_SECRET` | Shared secret for the proxy-header auth path (optional). | | `AUTH_CACHE_TTL` | How long resolved actors are cached (default `60s`). | | `DCM_ADMIN_SUBJECT` | Keycloak subject UUID for the seed admin actor (required when auth is enabled). | @@ -223,15 +226,15 @@ returns `403 Forbidden`. ## Troubleshooting -| Symptom | Things to check | -| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | -| `401 Unauthorized` on API or CLI | Auth enabled on control plane; token present and not expired; `aud` includes `dcm-api`; issuer matches `AUTH_ISSUER_URL`. | -| `dcm login` cannot reach issuer | Host maps `keycloak` (see [host access](#local-compose-host-access-to-keycloak)); issuer matches discovery; TLS if using HTTPS. | -| Issuer / JWKS errors in control-plane logs | `AUTH_ISSUER_URL` reachable from the control-plane container; Keycloak healthy (`auth` profile running). | -| CLI works but UI fails (or reverse) | Backstage SSO and control plane must trust the same IdP and audience; plugin backend URL points at the control plane. | -| `403 Forbidden` with valid token | Actor suspended or deactivated; wait up to `AUTH_CACHE_TTL` after status changes. | -| Service provider errors after enabling auth | SP traffic uses the environment agent; auth for agent and SP paths is still landing. See SP callout in [Overview](#overview). | -| Device flow times out | Complete browser approval within the time shown; retry `dcm login`. | +| Symptom | Things to check | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| `401 Unauthorized` on API or CLI | Auth enabled; token present and not expired; issuer matches `AUTH_ISSUER_URL`; if `AUTH_JWT_AUDIENCE` is set, token `aud` must match. | +| `dcm login` cannot reach issuer | Host maps `keycloak` (see [host access](#local-compose-host-access-to-keycloak)); issuer matches discovery; TLS if using HTTPS. | +| Issuer / JWKS errors in control-plane logs | `AUTH_ISSUER_URL` reachable from the control-plane container; Keycloak healthy (`auth` profile running). | +| CLI works but UI fails (or reverse) | Backstage SSO and control plane must trust the same IdP and audience; plugin backend URL points at the control plane. | +| `403 Forbidden` with valid token | Actor suspended or deactivated; wait up to `AUTH_CACHE_TTL` after status changes. | +| Service provider errors after enabling auth | SP traffic uses the environment agent; auth for agent and SP paths is still landing. See SP callout in [Overview](#overview). | +| Device flow times out | Complete browser approval within the time shown; retry `dcm login`. | Verify Keycloak readiness when using compose: diff --git a/content/docs/getting-started/troubleshooting.md b/content/docs/getting-started/troubleshooting.md index 6b54d2d..cbd8310 100644 --- a/content/docs/getting-started/troubleshooting.md +++ b/content/docs/getting-started/troubleshooting.md @@ -65,7 +65,8 @@ Common checks: - Control plane has `AUTH_DISABLED=false` and `AUTH_ISSUER_URL` set. - CLI: run `dcm login` or set `DCM_TOKEN` / `--token`. -- Token audience includes `dcm-api` (or your configured `AUTH_JWT_AUDIENCE`). +- If `AUTH_JWT_AUDIENCE` is set, the token `aud` claim must match; when unset, + audience validation is disabled on the control plane. - Keycloak (or your IdP) is running and reachable from the control plane. When auth is enabled, unauthenticated `curl` calls to protected endpoints fail From 6ff72850cbc7e3855690ceee182b6a091f6a2d84 Mon Sep 17 00:00:00 2001 From: Gloria Ciavarrini Date: Fri, 18 Sep 2026 15:34:48 +0200 Subject: [PATCH 7/7] docs: drop removed providers CLI from auth examples Use catalog item list for smoke checks after /providers removal. Assisted-By: Claude (Anthropic) Signed-off-by: Gloria Ciavarrini --- content/docs/getting-started/authentication.md | 4 ++-- content/docs/user-guide/cli-configuration.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/content/docs/getting-started/authentication.md b/content/docs/getting-started/authentication.md index 98b3dd8..919ddd5 100644 --- a/content/docs/getting-started/authentication.md +++ b/content/docs/getting-started/authentication.md @@ -152,7 +152,7 @@ Example for the reference Keycloak realm after host resolution is in place: ```bash dcm login --issuer-url http://keycloak:8080/realms/dcm \ --control-plane-url http://localhost:8080 -dcm sp provider list +dcm catalog item list ``` `dcm login` saves `issuer-url` in `~/.dcm/config.yaml`. Tokens are stored in the @@ -201,7 +201,7 @@ For non-interactive use, pass a bearer access token with `--token` (or set it is not the default `http://localhost:8080`: ```bash -dcm sp provider list \ +dcm catalog item list \ --token "" \ --control-plane-url http://localhost:8080 ``` diff --git a/content/docs/user-guide/cli-configuration.md b/content/docs/user-guide/cli-configuration.md index 5e115bb..c23113e 100644 --- a/content/docs/user-guide/cli-configuration.md +++ b/content/docs/user-guide/cli-configuration.md @@ -121,10 +121,10 @@ All commands support three output formats via the `-o` flag: - **`json`** — Structured JSON output, useful for scripting and automation. - **`yaml`** — YAML output. -For example, to list providers as JSON: +For example, to list catalog items as JSON: ```bash -dcm sp provider list -o json +dcm catalog item list -o json ``` ## Shell Completion