The browser's way in to Kubernetes: login, API access and live krm-stream resources.
krm-foyer is a backend for frontend (BFF) for browser applications built on Kubernetes APIs. It owns OIDC login and sessions, proxies Kubernetes API requests with the user's own credential, and hosts krm-stream resource streams on the same origin as your frontend. The browser holds the session in a cookie sealed with keys only krm-foyer has: page scripts never see a token, and sessions survive a restart.
KRM is the Kubernetes Resource Model: the idea that everything is a declarative resource with a spec and a status. krm-foyer is for applications whose domain is modelled that way.
Status: a working prototype, not yet for a cluster that matters. Sign-in through OIDC, sessions and the API proxy work, and e2e specs against a real API server and Dex try to get past each security boundary.
task demoruns an example application against them in your browser. Request and response bounds, live streams and opt-in shared watches are implemented and tested, and a Helm chart installs it. Sessions survive a restart, but last no longer than their ID token, and logout does not revoke a copied cookie. Run one replica. Certificate reload, token refresh and more replicas remain to be built. The roadmap gives the next priorities and their required evidence.
In the devcontainer:
task demoThis starts a disposable k3d cluster with Dex, krm-foyer and the
hello example in it, and port-forwards the example and Dex into the
devcontainer, where VS Code forwards them to your machine, also when Docker runs elsewhere. Open https://foyer.localhost:8443 and
sign in as alice@example.com, who may edit the notes, or bob@example.com, who may only
read them. The password is password. The certificates come from the fixture's own CA, so
import .e2e/ca.crt into your browser or accept the warnings. task e2e-down removes it
all. A fixture made by an older version of the scripts is refused with a message saying
so; run task e2e-down once, then task demo again.
The example is one HTML file and one script with no backend of its own: it follows the
notes live through /stream, one shared watch for every user that
RBAC allows, and changes them through /k8s as the signed-in user; the 403 and 409 it
shows are Kubernetes' own answers. Open it in two tabs to see a
change in one appear in the other, and a conflict when both edit the same note.
- The issuer decides who you are; Kubernetes decides what you may do. Proxied API requests and per-user watches use the user's own OIDC token; RBAC and admission answer them. No impersonation, and no fallback to krm-foyer's service account. A shared watch, where configured, is opened once with an identity of its own, and the API server is asked about every user who reads it.
- Frontend code never receives a token. The browser holds the session sealed (AES-GCM) in a Secure, HttpOnly cookie that only krm-foyer's keys open. The cookie is itself a bearer credential until it expires, logout or not, and is guarded like one.
- One domain is one trust boundary. The application, krm-foyer and any domain backend share an origin, routed by path. Everything on that domain can act as the signed-in user, so host only what you would trust with that access. More
- No access rules of our own. krm-foyer has no allowlist: what RBAC allows the user is reachable, and what it refuses is refused by the API server. So a session carries the user's full Kubernetes access; give users grants that match the application. Why, and what a scope would add
- Kubernetes semantics, exactly. Status codes, errors, patch types and conflicts pass through unchanged. A mutation that reached Kubernetes is never automatically replayed. Why
| Document | What it answers |
|---|---|
| docs/vision.md | Why krm-foyer exists, who it helps, what it expects from your domain, and what it will not become |
| docs/design.md | The contract: routes, access, sessions, upstream responses, streams and release criteria |
| docs/application-scope.md | Why a browser application might be limited beyond RBAC, what that would block, and how to give the browser its own narrow identity in the cluster |
| docs/roadmap.md | What exists, what is next, and in what order |
| docs/bounds.md | Limits, defaults, metrics and the scope of the capacity measurements |
| docs/watches.md | Choosing per-user streams, shared streams or native watches, and configuring sharing |
| docs/bff-choice.md | Whether your application should use a universal BFF like this one, a domain backend, or both |
| docs/ingress.md | Terminating TLS itself or behind an ingress, sharing one domain with other services, /auth/check for a login gate and a domain backend, and recipes for Gateway API, Traefik, nginx and Vite |
| docs/room-pass.md | Signing an audience in with Room Pass's QR join through krm-foyer |
| docs/frontend.md | Which pages krm-foyer serves itself, and why it ships no single-page application |
| docs/testing.md | How the tests prove krm-foyer invents neither authentication nor authorization, and how to run the e2e fixture |
| docs/alternatives.md | Nearby tools and the goals each meets: other ways to reach the Kubernetes API, and JavaScript clients in place of the browser helper |
| docs/investigations/choosing-an-issuer.md | Which OIDC issuers fit krm-foyer (Dex, Pinniped, Keycloak, authentik, Authelia, OpenUnison), and why the tests use Dex |
| docs/heritage.md | Where it comes from: the Voter demo, what broke on stage, and its sibling projects |
| docs/name.md | Why it is called krm-foyer |
task run # http://localhost:8080
task verify # everything CI checks, including e2e against k3d and DexWithout flags, krm-foyer serves its start page and probes only. Sign-in and the API proxy and streams come together, and need:
| Flag | |
|---|---|
-public-url |
krm-foyer's origin as browsers reach it, such as https://app.example.com |
-oidc-issuer, -oidc-client-id, -oidc-client-secret-file |
The issuer the API server trusts, and krm-foyer's client there |
-session-keys-file |
The keys session cookies are sealed with: one to four lines, each head -c 32 /dev/urandom | base64, the first sealing. Keep them across restarts (sessions) |
-kubernetes-server |
The API server, such as https://kubernetes.default.svc |
Optional: -oidc-ca-file and -kubernetes-ca-file (CAs to trust), -oidc-scopes,
-login-config-file (login parameters and
session claims),
-session-absolute-timeout (8h), -tls-cert-file and
-tls-key-file to serve TLS, -listen (:8080) and -metrics-listen (:9090). The
bounds each have a flag and a documented default: the request rate and
concurrent requests per session, concurrent requests per replica, how long a response
may stay open, its size, and how often an open response checks its session. Run one
replica: logins in progress and logout's reach are each process's own. Shared watches are opt-in; see their
configuration and identity requirements.
The OIDC redirect URI to register is <public-url>/auth/callback. The public URL,
issuer and Kubernetes server must use HTTPS. Mounted TLS certificates, CA bundles,
the session keys and the OIDC client secret are read at startup; restart after changing them. The explicitly
configured shared-watch token file is reread by client-go for rotation.
Or as a container:
task image
docker run --rm -p 8080:8080 ghcr.io/configbutler/krm-foyer:$(git describe --tags --always)Or in a cluster, with the Helm chart, which the e2e suite installs
too. Each release publishes it beside the image (from a checkout, charts/krm-foyer
works in its place). The client secret and the TLS certificate are Secrets you create
first:
kubectl create namespace krm-foyer
kubectl -n krm-foyer create secret generic krm-foyer-oidc --from-literal=client-secret=...
kubectl -n krm-foyer create secret tls krm-foyer-tls --cert tls.crt --key tls.key
helm install krm-foyer oci://ghcr.io/configbutler/charts/krm-foyer --version <release> \
--namespace krm-foyer \
--set publicURL=https://app.example.com \
--set oidc.issuer=https://issuer.example.com,oidc.clientID=krm-foyer \
--set oidc.clientSecret.secretName=krm-foyer-oidc,tls.secretName=krm-foyer-tlskrm-foyer: Browser login, Kubernetes API access and live resources on one origin, with every access decision left to the API server
— Open in Artifact Hub
The repository comes with a devcontainer that has Go, Task, the linters CI runs, and k3d/kubectl for testing against a real API server. See CONTRIBUTING.md.