Skip to content
GitHub

Security model and its limits

This page is the honest version. It lists what Kuben protects against, how, and what it does not.

Kuben’s service account can manage namespaces, secrets, deployments, cron jobs, PVCs and similar objects cluster-wide, and can patch its Gateway (the exact list is in the chart’s templates/rbac.yaml). It needs this because it creates one namespace per environment.

Every environment namespace is created with:

  • Pod Security Admission: baseline enforced, restricted warned.
  • No LoadBalancer or NodePort services. Apps reach the internet only through the Gateway.
  • A NetworkPolicy that closes ingress from other environments. This needs a CNI that enforces policies (Calico, Cilium); plain flannel does not.
  • A ResourceQuota, configurable per environment.
  • Every request is checked against the caller’s role: owner > admin > developer > viewer.
  • Objects of other organisations answer 404, not 403, so their existence is not revealed.
  • Deleting a production environment requires the separate env-delete-protected permission (owners).
  • Environment variable values are hidden from users without secret-read (viewers).
  • Logins are throttled by three counters shared by all replicas; the stored buckets are hashes, not emails or addresses. See Sign-in and throttling.
  • Passwords are hashed with Argon2id. Invited users must replace their temporary password before anything else.
  • A generated admin password goes into a Kubernetes Secret (the binary prints it once to its terminal, or writes an owner-only file when it has none), never into a log.
  • Sessions are opaque ids in a __Host-, HttpOnly, Secure cookie. No JWT reaches the browser, so an XSS cannot exfiltrate a bearer token, and a session can be revoked.
  • The console ships a strict Content Security Policy with no inline scripts.
  • Only a SHA-256 hash is stored, and it is compared in constant time.
  • A token is capped at a role (viewer, developer, admin) and optionally scoped to a project or environment.
  • Tokens cannot manage tokens, members or passwords.
  • Lifetime is 90 days by default and 365 at most.

Every mutation, denial, throttled attempt and login is recorded with actor, action, target, outcome, client address and request id, and shown under Audit. Request bodies are never recorded. The log is append-only through the API. See The audit log.

  • The API only writes secrets. It never returns their values.
  • Kuben touches only Secrets it created itself, in the environment namespaces.
  • Template credentials are generated and referenced, never embedded in a spec.
  • Releases record secret names and versions, not values.
  • Distroless image, non-root user (UID 65532), read-only root filesystem, all capabilities dropped, RuntimeDefault seccomp.
  • Static binary (Go with cgo disabled; 1.x: musl with mimalloc); no shell, no package manager in the image.
  • Every binary embeds its dependency list (Go’s module list, go version -m; 1.x: cargo auditable), so trivy, grype, osv-scanner or govulncheck -mode=binary can audit a deployed binary in place.
  • Every GitHub Action is pinned to a commit SHA; workflows are audited with zizmor on every pull request.
  • Releases are signed keyless with Sigstore, so there is no signing key to lose: checksums.txt (as the bundle checksums.txt.sigstore.json), the image and the Helm chart. The certificate names the release workflow and the tag, and install.sh verifies the signature whenever cosign is installed (--require-signature makes it mandatory). See Download, verify, then run.
  • checksums.txt lists every archive, a CycloneDX SBOM of each archive (kuben-<target>.cdx.json), install.sh and bundle.lock.json, so the signature covers them all.
  • The archives and the image carry GitHub build provenance attestations (gh attestation verify); the image also carries BuildKit’s SBOM and provenance attestations.
  • The image is built from the very binaries that were checksummed and attested, and is scanned by Trivy for fixable HIGH and CRITICAL vulnerabilities before it is pushed.
  • What setup installs from others (k3s, Gateway API CRDs, cert-manager and its images, PostgreSQL) is pinned in the release’s bundle lock (kuben version --bundle) and checked before use.
  • Releases build without a cache, so a poisoned cache can never reach an artifact.
  • govulncheck checks the Go modules on every change; the modules of main and the published image are re-checked daily.
  • Dependabot updates run with a 7-day cooldown against freshly published malicious packages.
  • Kuben is not a sandbox for hostile tenants. Environments isolate teams that trust each other from each other’s mistakes. Untrusted code needs a stronger boundary (separate clusters, gVisor, Kata).
  • TLS terminates at your Gateway controller. Kuben configures it and never sees app traffic.
  • The Gateway holds at most 64 listeners; Kuben uses at most 60.
  • Local passwords stay possible. With single sign-on (OpenID Connect), accounts linked to the provider can still sign in with a password unless sso.disable_password_for_linked is set.
  • A formal threat model is pending. The decisions above are recorded as ADRs; the threat model document will join them.

Please use private vulnerability reporting on GitHub rather than a public issue.