跳到內容
開源

Architecture

Zeabur Identity is a passkey-first OpenID Connect issuer for self-managed Kubernetes clusters. k3s is tested in CI; RKE2 uses the same configuration but has no release-installation verification. kube-apiserver verifies Zeabur Identity's tokens itself through Structured Authentication Configuration, so people sign in with a passkey instead of carrying a long-lived admin kubeconfig that cannot be revoked. Any OpenID Connect client (Argo CD, Grafana, Headlamp, …) can use the same issuer.

  • No passwords. Passkeys (WebAuthn, user verification required) are the primary credential; GitHub and Google accounts can be linked as alternatives for ordinary access.
  • Admin rights belong to admin accounts. Only a User with accountType: Admin gets the zeabur:admins group, bound to cluster-admin. An admin account signs in with a passkey only, its tokens last five minutes by default, and its sessions can idle out sooner. With the install-wide AAL3 setting it also needs a device-bound passkey. Regular accounts get only the rights bound to their groups. Every session ends 12 hours after sign-in, and kube-apiserver's configuration bounds every token's lifetime independently.
  • No standing secrets in the issuer's hands. Tokens are short-lived and audience-bound; only hashes of codes, refresh tokens and links are stored; signing keys rotate on a schedule. The service account cannot create Secrets, bindings or users.
  • Self-service without new privileges. The account page acts only on the signed-in user's own objects, matched by UID; adding or removing a passkey, and linking or unlinking GitHub or Google, needs a fresh verified passkey. The last passkey cannot be removed, and the last way to sign in cannot be unlinked.
  • State in Kubernetes. Custom resources hold users, credentials, clients, sessions and invitations, with no database; see removing someone for revocation and deletion order.
  • Small. One static binary in an otherwise empty image (about 20 MB), a few MiB of memory at rest; sign-in pages are server-rendered with about 1 KB of script each.

conformance.md maps every specification requirement Zeabur Identity implements to the code and the test that proves it.

browser ──passkey──▶ zeabur-identity (in the cluster) ──tokens──▶ kubectl / OIDC clients
│ ▲ │
custom resources bearer token
▼ │ ▼
kube-apiserver ◀──discovery + keys (internal TLS)── verifies tokens itself

A kubectl request goes like this:

  1. kubelogin opens the browser at Zeabur Identity's sign-in page.
  2. The person signs in with a passkey. kubelogin receives a short-lived access token and a refresh token, and Zeabur Identity records the session as a Grant object.
  3. kubectl sends the token to kube-apiserver.
  4. kube-apiserver checks the token's signature against Zeabur Identity's published keys, which it fetches over an internal TLS connection. Zeabur Identity is not involved in the request itself.
  5. When the token expires, kubelogin renews it with a refresh token, without the browser.

The admin tool zeabur-identityctl runs on an administrator's machine with their own kubeconfig. It prints the install manifests, writes kube-apiserver's AuthenticationConfiguration for the distribution, and issues the one-time links people use to enrol a passkey.

deploy/crds.json and deploy/rbac.json are the committed outputs of zeabur-identityctl crds and zeabur-identityctl rbac, for review in pull requests.

The code is split into small crates in one flat crates/ directory:

  • Dependencies only point downwards, with no cycles (Kladov; Lakos and Martin, see References).
  • Each crate hides one design decision behind a narrow interface (Parnas).
  • Storage is a set of traits with an in-memory and a Kubernetes implementation (ports and adapters, Cockburn), so most tests need no cluster.
  • The server and the admin CLI are separate binaries, as in Kanidm (kanidmd/kanidm), etcd (etcd/etcdctl) and cert-manager (cmctl). Code only an administrator should run never ships in the server image.

Every crate has a README that is also its crate-level documentation (cargo doc --open).

CrateWhat it holdsDepends on
zeabur-identity-coretime, cryptographic helpers, validated config values, protocol names—
zeabur-identity-keyssigning keys, JWK publication, JWS signing and verification, rotationcore
zeabur-identity-storestorage ports (traits, records) and in-memory adapterscore, keys
zeabur-identity-tlstrust roots, certificate issuance, a reloadable TLS listenercore
zeabur-identity-uiserver-rendered pages, embedded assets, translations, security headerscore
zeabur-identity-oauththe OAuth 2.0 / OpenID Connect authorization servercore, keys, store, ui
zeabur-identity-signinpasskey enrolment and sign-in, upstream providers, page routescore, keys, store, oauth, ui
zeabur-identity-k8scustom resources, Kubernetes adapters, GC, manifests, kube-apiserver configcore, keys, store, tls
zeabur-identitybinary: the in-cluster serverall of the above
zeabur-identityctlbinary: the administrator's CLIcore, k8s
zeabur-identity-test-support, zeabur-identity-e2etest helpers; end-to-end tests on a cluster—

The sign-in pages' TypeScript and Tailwind sources live in ui/, with Zeabur's design system as a package. Their built output, and the design system's classes as askama macros (templates/ds.html), are committed to crates/zeabur-identity-ui and embedded in the binary, so building Zeabur Identity needs only Cargo.

See CONTRIBUTING.md for setup, checks, dependency policy and cluster tests.