PRD: Add Dex chart, disabled by default
- Dominant language
- Go Template
- Stars
- 2.3k
- Forks
- 186
- Avg merge
- 3d 11h
- Merged PRs (30d)
- 66
Description
## Problem Statement
APL has no Dex. Before the OIDC issuer can be made selectable, Dex has to exist as an ordinary platform application: a vendored chart, a schema, defaults, a namespace, a rendered configuration and the secrets it needs to start.
Delivering this together with the issuer switch would produce one large change in which a new component and a change of authentication arrive at the same time. Splitting them means Dex can be installed, started and inspected on a real cluster while every application still authenticates against Keycloak, so any problem found is unambiguously Dex's.
## Solution
Add Dex as a platform application that is disabled by default.
When enabled it runs, serves its discovery document, and can be logged into with the platform administrator credential. Nothing else in the platform points at it, so enabling it changes no authentication behaviour.
Dex's configuration is delivered as an External Secret rather than through Helm values, because it embeds the OAuth2 client secret and secret values must not travel through Helmfile values. The configuration is rendered from a single shared snippet so that later work can hash the same text that is deployed.
Users are held in Dex's own storage and managed over its gRPC API (`CreatePassword`/`UpdatePassword`/`DeletePassword`), not rendered into configuration. Upstream Dex's gRPC `Password` message cannot express group membership; the image runs our fork, which adds a `groups` field to that message (dexidp/dex#4972, submitted upstream, not yet merged). The reasoning is recorded in ADR-2026-08-06. This PRD only stands up the chart and the admin login; the fork/patched image and the gRPC provisioning path itself are #3541.
Dex needs three secret values, and none of them is added to the values schema. Random values come from an External Secrets `Password` generator, consumed once and never refreshed. The platform administrator's password hash is derived by templating the existing administrator password through `htpasswd`, which produces the bcrypt variant Dex accepts. Nothing new is written to the values repository, and nothing depends on bootstrap having run.
## Acceptance Criteria
1. **GIVEN** default values, **WHEN** the platform is rendered, **THEN** Dex is not installed and no Dex resource appears in the output.
2. **GIVEN** Dex is enabled, **WHEN** the platform is rendered, **THEN** the Dex release, its namespace, its configuration secret and its route are produced and validate against the Kubernetes schemas.
3. **GIVEN** Dex is enabled on a cluster, **WHEN** the pod becomes ready, **THEN** its discovery document is reachable at the platform hostname and reports the issuer, authorization, token, JWKS and userinfo endpoints.
4. **GIVEN** Dex is enabled, **WHEN** the rendered templates are inspected, **THEN** no secret value appears in them; each is resolved by External Secrets at apply time.
5. **GIVEN** Dex is enabled, **WHEN** the values schema is inspected, **THEN** it declares no new secret field for Dex.
6. **GIVEN** a session cookie encryption key is required, **WHEN** it is produced, **THEN** it comes from a generator, is a length the encryption accepts, and is identical on every replica.
7. **GIVEN** a generated value exists, **WHEN** the platform reconciles repeatedly, **THEN** the value does not change, so a reconcile never rotates a credential in use.
8. **GIVEN** the platform administrator password, **WHEN** the administrator password hash is produced, **THEN** it verifies against that password and is in the bcrypt variant Dex accepts.
9. **GIVEN** Dex is enabled and running, **WHEN** the administrator logs in with the platform administrator credential, **THEN** authentication succeeds and the resulting token carries the administrator's group membership.
10. **GIVEN** a cluster installed before this change, **WHEN** it is upgraded and Dex is enabled, **THEN** every secret Dex needs is produced without bootstrap being re-run.
11. **GIVEN** Dex is enabled, **WHEN** every other application is inspected, **THEN** they still authenticate against the existing issuer and their configuration is unchanged.
12. **GIVEN** the platform is rendered with Dex disabled before and after this change, **WHEN** the outputs are compared, **THEN** they differ only by the namespace that any new application contributes.
## Testing
- Template validation with Dex both disabled and enabled, using a fixture for each.
- Values validation, which should confirm the absence of new secret fields rather than their presence.
- Criteria 3, 8 and 9 need a real cluster. A local single-node cluster is sufficient; a browser is not required, since the password grant can drive a login and the resulting token can be decoded to inspect its claims.
- Criterion 7 deserves particular attention. It is the property that stops a reconcile rotating the client secret out from under every application, and the mechanism protecting it is a single field.
## Out of Scope
- Pointing any application at Dex. Dex is installed but unused.
- Provisioning APL users. Only the platform administrator can log in; see #3541.
- Making Keycloak optional.
- High availability and configuration reload, which follow separately.
- Removing the pre-release image pin, which cannot happen until the upstream feature ships.
## Further Notes
- Secrets use an External Secrets `Password` generator rather than a schema field, following the pattern already established for team passwords in `values/team-secrets/`. The consuming External Secret sets `refreshInterval: "0"`, which is what makes a generated value stable — without it the generator produces a new value on every refresh.
- This also sidesteps a known gap: schema secrets are generated only during bootstrap, so a secret added after a cluster was installed never reaches it. Generated secrets are produced in-cluster on demand and are unaffected.
- Generated values exist only in the cluster and are not written to the values repository. A rebuilt cluster produces new ones. That is acceptable here: the client secret is read from one place by every consumer, a new session key only invalidates sessions, and the administrator hash derives from a password that is in the values repository.
- The image is pinned to a pre-release build by digest: auth sessions and logout are merged upstream but unreleased, and group membership over the gRPC `Password` message is our own unmerged patch (dexidp/dex#4972) — so the image is built from our fork, not upstream master. A digest is used rather than a branch tag so the running version cannot change underneath a cluster. **APL must not be released while this pin stands.**
- The chart's own default would install a version with neither of those, leaving every user authenticated but unauthorized (no groups) and with no logout. That is why the pin exists and should not be removed without checking both conditions.
- Dex is not yet present in the image mirror, so mirroring must be disabled on clusters running Dex until it is added.
## Dependencies
Blocks #3541, #3537 and #3539.
Contributor guide
Research direction
Start with the established team-password pattern in values/team-secrets/, then inspect the chart, rendering fixtures, values validation, and External Secret templates used by the platform. Verify disabled and enabled renders, schema validation, stable generated secrets, and the real-cluster discovery document and administrator login described in the acceptance criteria.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- helm, kubernetes
- Domain
- authentication, infrastructure, security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100