You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
APL users cannot log in to Dex. Only the platform administrator can, because that one account is written into Dex's configuration as a single known entry.
Users are created through the API, which stores each one as a SealedSecret in the apl-users namespace. Under Keycloak they reach the issuer at runtime: the Keycloak operator watches those Secrets, decodes each record, derives group membership from the administrator flags and team list, and pushes the user into the realm. Dex has no equivalent path.
The obvious approach — rendering users into Dex's configuration from values — does not work today. The file map that would load them (AplUser, into $.users[*]) still exists and is still marked to load into the spec, but the files it globs were removed when users moved to SealedSecrets. .Values.users therefore renders empty, and the records that replaced those files are ciphertext in Git, so templates cannot read them.
Until this is solved, enabling Dex on a cluster locks out every user except the administrator.
Solution
Load users from the cluster during apply, and render them into Dex's configuration.
The capability already exists in apl-core: src/cmd/commit.ts lists Secrets from the apl-users namespace and decodes them, though only to recover the platform administrator. Generalising that to populate values.users during apply gives templates the list they need, and the file map that consumes it is already in place. The Dex configuration template then renders one static password per user, deriving groups exactly as the Keycloak operator does today.
One thing must change outside apl-core. The stored record holds a plaintext initial password, not a hash, and Dex needs bcrypt. The hash cannot be computed at render time, because bcrypt is salted: the value would differ on every render, the configuration checksum would change on every apply, and Dex would restart continuously. It must be computed once and stored.
The API is the right owner, because it holds the plaintext exactly once — when it creates the user or changes the password. apl-core needs a backfill for records created before the field existed.
Acceptance Criteria
GIVEN a user is created through the API, WHEN the record is stored, THEN it carries a bcrypt password hash in the variant Dex accepts, and the plaintext is not derivable from it.
GIVEN a user's password is changed through the API, WHEN the record is updated, THEN the stored hash matches the new password.
GIVEN a stored user record without a hash, WHEN it is next processed, THEN a hash is derived from the stored initial password and persisted, so the record is upgraded once rather than on every pass.
GIVEN a cluster with users, WHEN values are prepared during apply, THENvalues.users contains one entry per user with email, name, group membership and password hash.
GIVEN Dex is the issuer, WHEN its configuration is rendered, THEN it contains one static password per user, and each user's identifier is stable across renders so their token subject does not change.
GIVEN a user who is a platform administrator, a team administrator, or a member of teams, WHEN their configuration entry is rendered, THEN their groups match what the Keycloak operator produces for the same record today.
GIVEN an unchanged set of users, WHEN the platform is rendered repeatedly, THEN the configuration is byte-identical, so no restart is triggered without cause.
GIVEN a user is added, removed, or has their teams changed, WHEN the platform is applied, THEN the configuration changes, Dex restarts, and the change takes effect.
GIVEN a user, WHEN they authenticate against Dex, THEN the token carries their group membership and the platform grants the roles that membership implies.
GIVEN a removed user, WHEN the platform is applied, THEN they no longer appear in the configuration and can no longer authenticate.
GIVEN the issuer is Keycloak, WHEN the platform is rendered, THEN nothing about user provisioning changes.
Testing
Unit tests for hash creation and update in the API, and for the backfill, including that an existing hash is not recomputed.
Unit tests for loading users into values, covering an empty namespace, one user and several.
Template tests for the rendered configuration: group derivation for each combination of administrator flags and team membership, and stability across repeated renders.
Criteria 9 and 10 need a cluster and a real login, since they concern token claims rather than rendered output.
Criterion 7 deserves particular attention. It is the property that prevents a restart loop, and the salted-hash trap is easy to reintroduce.
Out of Scope
Self-service password change. Dex static passwords are configuration and cannot be changed by the user who owns them. This remains administrative until the issuer offers an interface that can express group membership.
Managing users through Dex's gRPC API or its custom resources. Neither can carry group membership or is supported for this purpose; see the decision record.
Removing the restart that a user change causes.
Federated clusters, where users come from an external identity provider and are never stored by APL.
Migrating users from an existing Keycloak cluster.
Further Notes
This work spans two repositories. The hash is produced in the API; loading and rendering happen in apl-core. They can be reviewed separately but only deliver value together, and the API side must land first so the field exists.
The group derivation to match is in the Keycloak operator, which builds platform-admin, team-admin and team-<id> entries from the stored flags. Reproducing it exactly is what keeps authorization identical across a change of issuer.
The user identifier drives the token subject, so it must be the stable record identifier rather than anything editable such as an email address. Changing it later re-orphans accounts in applications that key on the subject.
If the upstream request to accept group membership over Dex's gRPC API is accepted (gRPC API set groups on passwords dexidp/dex#4972), this design could move from rendered configuration to API calls, which would remove the restart. The stored hash and the group derivation would carry over unchanged.
Dependencies
Depends on #3536 — there is no Dex configuration to render users into until the chart exists.
Problem Statement
APL users cannot log in to Dex. Only the platform administrator can, because that one account is written into Dex's configuration as a single known entry.
Users are created through the API, which stores each one as a SealedSecret in the
apl-usersnamespace. Under Keycloak they reach the issuer at runtime: the Keycloak operator watches those Secrets, decodes each record, derives group membership from the administrator flags and team list, and pushes the user into the realm. Dex has no equivalent path.The obvious approach — rendering users into Dex's configuration from values — does not work today. The file map that would load them (
AplUser, into$.users[*]) still exists and is still marked to load into the spec, but the files it globs were removed when users moved to SealedSecrets..Values.userstherefore renders empty, and the records that replaced those files are ciphertext in Git, so templates cannot read them.Until this is solved, enabling Dex on a cluster locks out every user except the administrator.
Solution
Load users from the cluster during apply, and render them into Dex's configuration.
The capability already exists in
apl-core:src/cmd/commit.tslists Secrets from theapl-usersnamespace and decodes them, though only to recover the platform administrator. Generalising that to populatevalues.usersduring apply gives templates the list they need, and the file map that consumes it is already in place. The Dex configuration template then renders one static password per user, deriving groups exactly as the Keycloak operator does today.One thing must change outside
apl-core. The stored record holds a plaintext initial password, not a hash, and Dex needs bcrypt. The hash cannot be computed at render time, because bcrypt is salted: the value would differ on every render, the configuration checksum would change on every apply, and Dex would restart continuously. It must be computed once and stored.The API is the right owner, because it holds the plaintext exactly once — when it creates the user or changes the password.
apl-coreneeds a backfill for records created before the field existed.Acceptance Criteria
values.userscontains one entry per user with email, name, group membership and password hash.Testing
Out of Scope
Further Notes
apl-core. They can be reviewed separately but only deliver value together, and the API side must land first so the field exists.platform-admin,team-adminandteam-<id>entries from the stored flags. Reproducing it exactly is what keeps authorization identical across a change of issuer.Dependencies
Depends on #3536 — there is no Dex configuration to render users into until the chart exists.
Blocks #3537.