Skip to main content

Kubernetes Access — Security Model

pc kube is an identity-aware access proxy: it never hands out cluster credentials, and it authorizes every request per-user. This page documents the threat model and the hardening guarantees. The design follows Teleport's Kubernetes-access model and closes the pitfalls its advisories surfaced.

Two-layer authorization

LayerEnforcesWhere
Proxima (tenant gate)Can this user reach this cluster at all?Backend re-checks live ac.Can("kubernetes:read", cluster.ClientID) on every request — fail-closed, against the target cluster's owning client.
Kubernetes (capability gate)What can the user do once inside?The agent impersonates Proxima-derived k8s groups; k8s RBAC decides.

The two layers are independent: Proxima gates which cluster; k8s RBAC gates what you may do in it.

Impersonation, not shared credentials

The agent authenticates to the apiserver with its own ServiceAccount and asserts your identity via Impersonate-User / Impersonate-Group. Consequences:

  • The cluster's audit log shows your email, not the agent's ServiceAccount.
  • k8s RBAC evaluates the impersonated groups, so least-privilege is enforced by the cluster itself.

The impersonation ServiceAccount is not god-mode

Teleport's documented impersonation ClusterRole grants blanket impersonate on users/groups — a compromised proxy could then impersonate almost any privileged group. Proxima's impersonate ClusterRole is resourceNames-restricted to exactly the groups it uses:

rules:
- apiGroups: [""]
resources: ["groups"]
verbs: ["impersonate"]
resourceNames: ["proxima:kube-readonly", "proxima:kube-exec", "system:authenticated"]
- apiGroups: [""]
resources: ["users"]
verbs: ["impersonate"] # users carry no privilege — audit identity only

Even a fully compromised collector can only ever impersonate those three non-privileged groups — never system:masters or cluster-admin.

Identity cannot be smuggled

The trusted identity travels out-of-band, never in a request header:

  • The backend resolves your identity + groups server-side and passes them in the authenticated NATS kube_open command.
  • The agent injects them into each proxied request via the request context (a Go value a caller cannot set), not a header.
  • Both the backend edge and the agent strip every client-supplied Authorization, Impersonate-*, and X-Proxima-* header before anything is forwarded.

So kubectl --as=system:masters, a raw Impersonate-Group header, or a CRLF-smuggled header value are all stripped and ignored. Impersonated values are additionally validated to reject control characters (CR/LF/NUL), closing the header-injection class.

The read/execute split is enforced by k8s

kubernetes:read and kubernetes:execute map to separate k8s groups (proxima:kube-readonly, proxima:kube-exec). The backend only asserts the exec group for a caller who actually holds kubernetes:execute; the read-only ClusterRole has no exec verb. A read-only user's kubectl exec therefore fails at the apiserver (403) — the split is enforced by real k8s RBAC, not by the proxy parsing request paths.

Exec is not "read-only"

pods/exec runs arbitrary commands inside a pod; exec into a pod with a privileged ServiceAccount, hostPath, or hostPID is a path to node/cluster compromise. That is why exec is a distinct, higher-privilege grant rather than part of read. Grant kubernetes:execute accordingly.

Secrets are excluded

The proxima:kube-readonly ClusterRole grants view-style read across common resources but never secrets.

Short-lived, cluster-bound, revocable credentials

  • The credential kubectl sends is a Proxima token, not a k8s token or cert.
  • It is bound to one cluster and audience (proxima-kube): a token minted for cluster A is rejected on cluster B, and it is not usable on any other API surface. The backend compares it against the canonicalized path cluster id.
  • TTL is short (15 minutes); the exec-credential plugin refreshes transparently.
  • The proxy treats the token as authentication only — it re-resolves your live permissions on every request. If your access is revoked or your account disabled, the next request is denied (and the in-flight window is bounded by the TTL). Revoking your Proxima session kills kube access — a stronger revocation story than long-lived client certs.

Transport & streaming

  • The agent verifies the apiserver serving certificate against the in-cluster CA (no InsecureSkipVerify outside an explicit, loudly-warned dev mode).
  • Long-lived streams (kubectl logs -f, --watch, exec) are not cut by the server's absolute write timeout, and each user is capped to a bounded number of concurrent streams to prevent resource exhaustion.

Audit & recording

  • Every proxied request is recorded as a kube_request audit event (cluster_id, client_id, user, method, path, status). Token issuance is audited as kube_token_issued.
  • Interactive kubectl exec/attach sessions are recorded (encrypted, in R2), like terminal sessions.
  • The cluster's own k8s audit log is the authoritative record — because the request runs as your impersonated identity, it attributes actions to you even for paths the proxy doesn't parse (exec, ephemeral/debug containers).

One cluster, one client

v1 assumes a cluster is dedicated to a single Proxima client. The read-only ClusterRole is cluster-wide, so on a cluster shared by multiple clients a user of one client could read another client's namespaces. Until namespace-scoped roles land, only enable kube access on single-tenant clusters.

Requirement traceability

The implementation maps to these hardening requirements:

IDRequirement
S1resourceNames-restricted impersonate ClusterRole (never blanket)
S2Strip all client Authorization / Impersonate-* / X-Proxima-* at both edges
S3Validate impersonated identity (reject CR/LF/NUL/control chars)
S4Fail-closed, per-request token + live RBAC on the target cluster's client
S5Token bound to cluster + audience; cross-cluster/cross-surface rejected
S6Agent verifies apiserver TLS via the in-cluster CA
S7Read vs exec enforced by two distinct k8s groups derived server-side
S8Long streams survive the write timeout; per-user stream limit
S9Exec recorded + encrypted; k8s audit log authoritative
S10One-cluster-one-client (documented limit)