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
| Layer | Enforces | Where |
|---|---|---|
| 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_opencommand. - 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-*, andX-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
InsecureSkipVerifyoutside 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_requestaudit event (cluster_id,client_id, user, method, path, status). Token issuance is audited askube_token_issued. - Interactive
kubectl exec/attachsessions 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:
| ID | Requirement |
|---|---|
| S1 | resourceNames-restricted impersonate ClusterRole (never blanket) |
| S2 | Strip all client Authorization / Impersonate-* / X-Proxima-* at both edges |
| S3 | Validate impersonated identity (reject CR/LF/NUL/control chars) |
| S4 | Fail-closed, per-request token + live RBAC on the target cluster's client |
| S5 | Token bound to cluster + audience; cross-cluster/cross-surface rejected |
| S6 | Agent verifies apiserver TLS via the in-cluster CA |
| S7 | Read vs exec enforced by two distinct k8s groups derived server-side |
| S8 | Long streams survive the write timeout; per-user stream limit |
| S9 | Exec recorded + encrypted; k8s audit log authoritative |
| S10 | One-cluster-one-client (documented limit) |