Security model and its limits
This page is the honest version. It lists what Kuben protects against, how, and what it does not.
Cluster access
Section titled “Cluster access”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.
Tenant namespace isolation
Section titled “Tenant namespace isolation”Every environment namespace is created with:
- Pod Security Admission:
baselineenforced,restrictedwarned. - No
LoadBalancerorNodePortservices. Apps reach the internet only through the Gateway. - A
NetworkPolicythat closes ingress from other environments. This needs a CNI that enforces policies (Calico, Cilium); plain flannel does not. - A
ResourceQuota, configurable per environment.
Authorisation
Section titled “Authorisation”- Every request is checked against the caller’s role:
owner>admin>developer>viewer. - Objects of other organisations answer
404, not403, so their existence is not revealed. - Deleting a production environment requires the separate
env-delete-protectedpermission (owners). - Environment variable values are hidden from users without
secret-read(viewers).
Sign-in
Section titled “Sign-in”- 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,Securecookie. 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.
API tokens
Section titled “API tokens”- 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.
Secrets
Section titled “Secrets”- 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.
The pod
Section titled “The pod”- Distroless image, non-root user (UID 65532), read-only root filesystem, all capabilities dropped,
RuntimeDefaultseccomp. - 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), sotrivy,grype,osv-scannerorgovulncheck -mode=binarycan audit a deployed binary in place.
Supply chain
Section titled “Supply chain”- 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 bundlechecksums.txt.sigstore.json), the image and the Helm chart. The certificate names the release workflow and the tag, andinstall.shverifies the signature whenever cosign is installed (--require-signaturemakes it mandatory). See Download, verify, then run. checksums.txtlists every archive, a CycloneDX SBOM of each archive (kuben-<target>.cdx.json),install.shandbundle.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
mainand the published image are re-checked daily. - Dependabot updates run with a 7-day cooldown against freshly published malicious packages.
Limits
Section titled “Limits”- 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_linkedis set. - A formal threat model is pending. The decisions above are recorded as ADRs; the threat model document will join them.
Reporting a vulnerability
Section titled “Reporting a vulnerability”Please use private vulnerability reporting on GitHub rather than a public issue.