A web UI for kcp: workspace navigation/management, an APIExport / APIBinding manager, and a discovery-driven object browser — with OIDC or no-OIDC auth.
This is a standalone repository (github.com/kcp-dev/contrib-dashboard); it
talks to a kcp cluster over the front-proxy and is developed and released
independently of kcp.
See DESIGN.md for the architecture and roadmap.
server/— BFF (Node + TypeScript + Express). Terminates OIDC (later) and reverse-proxies Kubernetes REST to the kcp front-proxy.web/— SPA (Vue 3 + Vite + TypeScript).
- Node.js >= 20
- A running kcp and a kubeconfig that can reach it. The dev default is
.kcp/admin.kubeconfig(created bykcp start).
npm install
# Point the BFF at your kcp admin kubeconfig (no-oidc mode is the default).
export AUTH_MODE=none
export KUBECONFIG=/path/to/kcp/.kcp/admin.kubeconfig # e.g. your kcp checkout
export KCP_INSECURE=true # dev self-signed CA
npm run dev- BFF listens on
:8080, SPA (Vite dev server) on:5173. - Open http://localhost:5173 — you should see the workspaces under
root.
The Vite dev server proxies /api, /auth and /api/kcp back to the BFF, so
the SPA runs same-origin.
The BFF terminates OIDC (auth-code + PKCE); the SPA never sees tokens. Uses a
Dex issuer — the kcp repo ships a ready one at contrib/kcp-dex.
-
Register a dashboard client in Dex. Add to the Dex config
staticClients:- id: kcp-dashboard public: true # public client, PKCE name: 'kcp dashboard' redirectURIs: - http://localhost:8080/auth/callback
-
Start kcp with matching OIDC flags (audience/client
kcp-dashboard, or reuse your existing structuredauth-config.yamland add the audience). See the kcp repo'scontrib/kcp-dex/README.md. -
Run the dashboard in oidc mode:
export AUTH_MODE=oidc export KCP_BASE_URL=https://127.0.0.1:6443 export KCP_INSECURE=true # dev self-signed kcp + Dex export OIDC_ISSUER_URL=https://127.0.0.1:5556/dex export OIDC_CLIENT_ID=kcp-dashboard export OIDC_REDIRECT_URL=http://localhost:8080/auth/callback npm run dev
Open http://localhost:5173, click Sign in, authenticate in Dex (
admin/passwordin the sample config). The dashboard then acts with your identity — you only see workspaces/resources your token is authorized for.
KCP_INSECURE=true skips TLS verification for both the upstream kcp and the
Dex issuer (dev only).
The Tiltfile is a standalone dev environment: it stands up a
static kcp using kcp's tilt library (deploy_kcp from kcp_static.Tiltfile) and
deploys the dashboard on top, so one command gives you kcp + the dashboard.
It needs a local kcp checkout for the tilt library and infra manifests. Point
KCP_DIR at it (default ../kcp, i.e. a sibling clone under .../kcp-dev/):
make tilt-up # creates the kind cluster (if needed), then tilt up
# or, if you already have a cluster:
tilt up # uses ../kcp
KCP_DIR=/path/to/kcp tilt upmake tilt-up also warns if the *.kcp.localhost names don't resolve; add them
to /etc/hosts as it instructs (127.0.0.1 kcp.localhost root.kcp.localhost theseus.kcp.localhost dex.kcp.localhost).
Options:
KCP_SINGLE_SHARD=true— skip the second (theseus) shard for a leaner cluster.KCP_OIDC_ENABLED=true— run in OIDC mode with an in-cluster Dex (see below).
Tilt brings up cert-manager, the envoy gateway, etcd, the kcp-operator, the kcp
shards + front-proxy, extracts the admin kubeconfig, then builds and deploys the
dashboard — port-forwarded to http://localhost:8080 (no-oidc). It runs in no-oidc
mode by mounting the operator-published kcp-frontproxy-kubeconfig secret and
proxying to the in-cluster front-proxy Service; it waits for the kcp-admin extract step so the secret exists first.
make tilt-up-oidc # == KCP_OIDC_ENABLED=true make tilt-upThis additionally deploys Dex in the cluster (via dex.Tiltfile),
served through the same envoy gateway at https://dex.kcp.localhost:8443/dex,
configures kcp to trust it (deploy_kcp(oidc=...), with kcp's caFileRef pointing
at Dex's cert-manager cert), and runs the dashboard from
deploy/deployment-oidc.yaml in AUTH_MODE=oidc.
Open http://localhost:8080, click Sign in, and log in with admin /
password (the sample static user). The issuer URL
https://dex.kcp.localhost:8443/dex is one identical string for the browser (via
/etc/hosts), the BFF, and kcp — the pods resolve it to the gateway through a
hostAlias, so add the dex.kcp.localhost entry to /etc/hosts above.
Admin rights: the Dex dev user is in the system:kcp:admin group, which
kcp's bootstrap policy binds to cluster-admin — so the OIDC identity gets kcp
admin straight from the token, no extra RBAC. This needs dex ≥ v2.45.0
(staticPasswords gained a groups field then; the pinned image in
dex.Tiltfile is v2.45.1). Older dex silently drops the
field and the user lands with only system:authenticated.
Dex uses in-memory storage, so restarting the dex pod rotates its signing keys. kcp refetches Dex's JWKS on its own, but if a token briefly fails with
failed to verify id token signatureright after a dex restart, give it a minute orkubectl rollout restart deploy/frontproxy-front-proxy.
Reusing Dex from kcp's own Tilt: dex.Tiltfile is self-contained (Starlark
only, no sibling-file reads), so kcp's contrib/tilt Tiltfiles can load it the
same way:
deploy_dex = load_dynamic('.../contrib-dashboard/dex.Tiltfile')['deploy_dex']
oidc = deploy_dex() # returns the dict deploy_kcp(oidc=...) expects
deploy_kcp(..., hostnames=[..., 'dex.kcp.localhost'], oidc=oidc)Dockerfileis a multi-stage build (SPA + BFF → slim runtime that servesweb/distand proxies REST). Build locally:docker build -t kcp-dashboard ...github/workflows/dashboard-image.yamlbuilds the image on every PR (which also verifies the TypeScript build) and pushes toghcr.io/<owner>/contrib-dashboardonmainand version tags.
| Var | Meaning |
|---|---|
PORT |
BFF port (default 8080) |
AUTH_MODE |
none (kubeconfig) or oidc |
KUBECONFIG |
kubeconfig path for no-oidc mode |
KCP_BASE_URL |
upstream front-proxy base URL (oidc mode, or to override) |
KCP_CA_FILE |
CA bundle to trust for the upstream |
KCP_INSECURE |
true to skip upstream TLS verification (dev only) |
OIDC_ISSUER_URL / OIDC_CLIENT_ID / OIDC_CLIENT_SECRET |
OIDC client (oidc mode) |
OIDC_REDIRECT_URL |
OIDC callback (default http://localhost:$PORT/auth/callback) |
SESSION_SECRET |
cookie signing key |
npm run build # builds web → web/dist, compiles server → server/dist
npm start # BFF serves the built SPA and the API on :8080