diff --git a/docs/DEVELOPER-GUIDE.md b/docs/DEVELOPER-GUIDE.md index 0e9f5aa..0dfb810 100644 --- a/docs/DEVELOPER-GUIDE.md +++ b/docs/DEVELOPER-GUIDE.md @@ -1469,6 +1469,12 @@ ArgoCD will sync the Keycloak config, and the registrar CronJob will pick up the | `k8s.secret.client-id-key` | No | `client-id` | Field name for the client ID in the K8s Secret | | `k8s.secret.client-secret-key` | No | `client-secret` | Field name for the client secret in the K8s Secret | +#### Public CLI Client (Device-Code Login) + +`forte-cli` is a shared **public** client (no secret) with the RFC 8628 device-authorization grant enabled (`oauth2.device.authorization.grant.enabled: "true"`, `standardFlowEnabled: false`, `directAccessGrantsEnabled: false`). Downloaded skills and CLI tools that log in through the Auth Sidecar (forte-drop first) use it with `_CLIENT_ID=forte-cli`; nothing per-tool needs to be registered in Keycloak. + +It must be defined in `forte-realm.json` (this legacy path): the self-service registrar hardcodes `publicClient: false` / `standardFlowEnabled: true` and drops `attributes`, so a `client-config` Secret cannot produce a public device-code client. It carries no `k8s.secret.sync` attribute (the registrar's secret sync skips it) and is listed in the cleanup CronJob's protected clients. + ### Retrieving Secrets for External Deployments The registrar always writes a **central copy** of every synced secret to the `secrets` namespace, in addition to the target namespace. This allows operators to retrieve client credentials for applications deployed outside this cluster: diff --git a/infra/values/base/keycloak-values.yaml b/infra/values/base/keycloak-values.yaml index 2e838a6..59a94ea 100644 --- a/infra/values/base/keycloak-values.yaml +++ b/infra/values/base/keycloak-values.yaml @@ -186,6 +186,21 @@ keycloakConfigCli: } } ] + }, + { + "clientId": "forte-cli", + "name": "Forte CLI", + "description": "Shared public client for RFC 8628 device-code login from downloaded skills/CLI tools (forte-drop first) against services behind Auth Sidecar. No client secret.", + "enabled": true, + "protocol": "openid-connect", + "standardFlowEnabled": false, + "directAccessGrantsEnabled": false, + "publicClient": true, + "redirectUris": [], + "webOrigins": [], + "attributes": { + "oauth2.device.authorization.grant.enabled": "true" + } } ], "browserFlow": "browser-auto-idp", @@ -671,7 +686,7 @@ extraDeploy: MIN_AGE_SEC=$((MIN_AGE_DAYS * 86400)) # Hardcoded protected clients (never delete these) - PROTECTED_JSON='["gitea","grafana","argocd","vaultwarden","account","account-console","admin-cli","broker","realm-management","security-admin-console"]' + PROTECTED_JSON='["gitea","grafana","argocd","forte-cli","vaultwarden","account","account-console","admin-cli","broker","realm-management","security-admin-console"]' echo "Fetching clients from realm '${REALM}'..." CLIENTS=$(curl -sf -H "Authorization: Bearer ${TOKEN}" \