Decision (2026-09-30): krm-foyer ships a small set of server-rendered pages, embedded in the Go binary. It does not ship a single-page application, a resource browser or a Node build. Getting started is covered by the start page and by examples that live next to the service, not inside it.
Amended (2026-10-01): krm-foyer does not host the application's own files either. The
ingress routes / to the application and krm-foyer's prefixes to krm-foyer; see
sharing one domain.
krm-foyer is a backend. Its API contract answers JSON to JavaScript: an unauthenticated API request gets a 401, not a redirect. That is right for applications, but a person who has just deployed the service and opens it in a browser needs more than a 404 to confirm it works. The login flow also has steps where the browser is on a krm-foyer URL and no application page exists yet:
- The OIDC provider sends the browser back to
/auth/callback. When that callback fails, something has to explain why. - After
POST /auth/logout, the browser needs somewhere to go. - Before an application is configured,
/has nothing to serve.
The table distinguishes implemented pages from planned ones. Planned routes currently return 404; they are requirements, not configuration switches.
| Path | Page | Why it is ours |
|---|---|---|
/ |
Start page, for requests that reach krm-foyer itself: a port-forward, or before routing is set up | Confirms the deployment works and shows what to route next. It links to the docs and to the session page. Behind an ingress, / belongs to the application and this page is never seen. |
/auth/login?return_to=/path |
No page: a redirect to the OIDC provider | The return path is validated as a local path before it is stored in the login transaction. |
/auth/callback |
A redirect to the stored return path on success. On failure, an error page with a stable reason and a "try again" link | An application cannot render this: its code is not loaded yet. |
/auth/logged-out |
Plain confirmation with a "sign in again" link, and a note that the issuer may still have the user signed in | Where logout lands when the application does not supply its own destination. POST /auth/logout answers 204, and the caller navigates here or to its own page. |
/auth/whoami |
Who Kubernetes takes you to be (username, groups and extra, from a SelfSubjectReview), with the session's issuer and expiry, as JSON. Never tokens | A way to investigate a 403, and to see the attribution extras. /auth/session shows the issuer's claims; this shows what Kubernetes made of them. |
/_foyer/access (planned) |
What you may do in a namespace, from a SelfSubjectRulesReview, and a "can I?" form answered by a SelfSubjectAccessReview | kubectl auth can-i for someone with only a browser. Code asks the same reviews natively. See what may I do. |
/k8s/..., /stream when krm-foyer interrupts |
For a browser navigation only: sign in, redirect notice, held-back content, store unavailable. Same status code as the JSON form | The proxy is explorable in a tab. These say what krm-foyer decided, as opposed to what Kubernetes answered. See interruptions. |
/healthz, /readyz |
Plain text | For probes, not people. |
/_foyer/... |
The pages' stylesheet and images | The one prefix krm-foyer reserves for its own pages and files that are not part of login, so they never collide with an application on the same origin. |
Rules for these pages:
- Go
html/templateandembed, no JavaScript build. The pages are static apart from a few values, so a Node toolchain would cost more than the pages are worth. It would also add an npm dependency tree to a security-sensitive binary. - No inline script and a strict Content-Security-Policy. The service that holds session cookies should be the easiest one to audit.
- The application chooses where to go after logout. The helper's
logout(next)accepts a destination. Callback failures currently use the built-in error page; configurable error destinations and page replacement are not implemented. - Nothing reveals configuration to anonymous visitors. The start page says which features are switched on, never issuer secrets or cluster addresses.
- A resource browser or dashboard. Raw API answers are viewable in a tab, as JSON; there is no UI on top of them. A dashboard would compete with the applications krm-foyer exists to serve, and name.md picked "foyer" partly so the name would not suggest one.
- A login page with its own form. Login happens at the OIDC provider. krm-foyer only redirects to it.
- A frontend framework or component library. Applications choose their own.
The pages above make a deployment verifiable. What makes it easy to build on is separate:
- Routing recipes, not hosting. A Gateway API
HTTPRoute, an nginx server block and a Vite dev-server proxy that put the application and krm-foyer on one origin. These are documentation and examples, not code in krm-foyer. - A tiny browser helper at
/_foyer/foyer.js, served by krm-foyer as a plain ES module (source). It has six calls:session(),login(returnTo, params),loginURL(returnTo, params),logout(next),requireSession()andk8s(path, options).paramsare login parameters for the issuer, sent asoidc.<name>;loginURLis the same link for an<a>or a QR code, which works without the helper. The last puts the CSRF header on every change and reports each answer as an outcome (ok,signed-out,refused,missing,conflict,invalid,error) with theStatusmessage, without retrying anything Kubernetes answered. Its one resend is of a change krm-foyer refused for a stale CSRF token, which never reached Kubernetes. It knows that refusal by theKrm-Foyer-Interruptionheader, never by the body, which an aggregated API could copy (see interruptions).requireSession()sends a signed-out page to login, for deployments whose ingress cannot run the login gate. Every application needs these, and they are easy to get subtly wrong. Its tests run undernode --test(foyer.test.js), with no npm packages. There is no npm package until someone needs one outside a krm-foyer origin. examples/hello/(exists): one HTML file, one script and a stylesheet, with no bundler and no backend of its own. It signs in, lists, creates and edits Notes (a small custom resource) through/k8s, follows them live through/streamwith krm-stream's browser library (one file, vendored), and shows Kubernetes' 403 and 409 answers as they are. A Gateway API front door (Traefik) routes/to a file server for it and krm-foyer's prefixes to krm-foyer, on one origin, andkubectl port-forwardbrings it to the browser.task demostarts it in a disposable cluster, and the e2e suite's browser specs drive it in Chromium.
The start page and the auth pages came first, because the OIDC work needed them anyway. The routing recipes come with the ingress work.
- Two adopters write the same page themselves. That is a sign it belongs in the service.
- The helper grows past the session and the proof every request needs. Then it belongs in its own package, or in krm-stream.
Looked at (2026-10-02): no published Kubernetes client fits as the helper's transport, so the helper stays its own, small and without dependencies. The libraries checked, and what each is good for, are in alternatives.