Skip to main content
Version: 2.32

Minimize the client's cluster permissions

A Telepresence client has never needed elevated cluster permissions, but it used to need a handful of mechanical grants: resolving the traffic-manager to a pod, listing namespaces for name completion, reading pod logs for telepresence gather-logs. None of those exist because the client must be the one doing the work. The traffic-manager authenticates every caller as a real Kubernetes identity and authorizes what that identity may do (see Authentication and authorization), so it can act as the client's deputy: serve namespace discovery and log gathering itself, and enforce policy with SubjectAccessReviews against the caller's verified identity instead of relying on what the client can physically reach.

That turns the client's RBAC footprint into a dial. Each step below removes a class of grants, states what it requires, and what — if anything — it costs. The steps are independent unless noted; the end of the page has the whole ladder in one table.

Step 1: drop the discovery and diagnostic grants​

clientRbac:
legacyAccess: false

The traffic-manager runs as a single-replica StatefulSet, so its pod name is known without looking: traffic-manager-0. With legacyAccess: false the chart stops rendering the discovery rules (services get, pods get/list in the manager's namespace) and the per-namespace diagnostic grants (pods get/list, pods/log get), leaving a connect Role with a single rule: pods/portforward create, scoped by resourceNames to that one pod. Namespace discovery and telepresence gather-logs keep working — the manager serves both, and controls log access per namespace with the logs.telepresence.io grant described below.

Against a traffic-manager at v2.32 or later, an explicit --mapped-namespaces list needs no pods grant either: the client trusts the manager's own attachment review instead of probing get pods in each listed namespace.

This requires clients at the release that introduced known-name connection or later, and the default apiPort; older clients must resolve the traffic-manager Service to a pod themselves, which is exactly what the legacy grants permit. See RBAC for the rendered roles in both forms.

Step 2: enforce authentication​

security:
authentication:
mode: enforcing

Steps 3 and 4 move enforcement from the API server to the traffic-manager, which only means something when the manager rejects callers it cannot authenticate. Under the default permissive mode an unauthenticated caller is still admitted (and merely logged), so the required grant below would have nothing to bite on. Authentication and authorization covers what enforcing mode requires — most notably that every client's kubeconfig can produce a bearer token or a verifiable client certificate.

Step 3: authorize with Telepresence's own grants​

security:
authorization:
requiredGrant: telepresence

By default, the manager authorizes a connection or an attachment by asking whether the caller holds pods/portforward in the relevant namespace — the permission a client historically exercised to reach the manager or an agent, doubling as policy. Requiring the telepresence grant replaces that proxy with grants that exist purely as policy, in the telepresence.io API group:

GrantAuthorizes
connections create (manager namespace)Establishing a session.
attachments create / get (target namespace)Attaching to a workload: create for intercept, replace, and wiretap; get for ingest.
logs and logs/yaml get (target namespace)Gathering that namespace's pod logs (logs) and including pod manifests in the result (logs/yaml). The two are reviewed independently: a caller granted logs alone gets the logs, with the manifests simply omitted.

These resources are never exercised against the Kubernetes API server — the manager evaluates them with SubjectAccessReviews — so granting them confers nothing outside Telepresence. They are ordinary RBAC in every other way: bind them with Roles per namespace or a ClusterRole, and scope attachments down to individual workloads with resourceNames. The chart renders matching client Roles for whichever grant is required (clientRbac.subjects decides who they bind).

With telepresence as the required grant, the per-namespace pods/portforward grant disappears from the client Roles. Its mechanical use goes with it: the client's first direct port-forward dial to a traffic-agent in that namespace is refused, and there is no manager relay to fall back on. An attachment in that namespace then needs the QUIC transport, decided independently of this grant when an agent is first dialed; without it, the intercept, replace, or ingest fails immediately with a clear error. The intermediate any setting (the default) accepts either grant during a migration.

This is what the client's permissions look like at this step, for a developer who connects and attaches to two named workloads in the shop namespace. One Kubernetes grant remains: the port-forward to the traffic-manager's pod, which is how the client still reaches the manager. Everything else is policy in the telepresence.io group:

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: traffic-manager-connect
namespace: ambassador
rules:
# The one remaining Kubernetes grant: reaching the manager's pod.
- apiGroups: [""]
resources: ["pods/portforward"]
resourceNames: ["traffic-manager-0"]
verbs: ["create"]
# Policy: may this identity establish a session?
- apiGroups: ["telepresence.io"]
resources: ["connections"]
verbs: ["create"]
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: telepresence-shop
namespace: shop
rules:
# Policy: may this identity attach to these workloads? "create" covers
# intercept, replace, and wiretap; "get" covers ingest.
- apiGroups: ["telepresence.io"]
resources: ["attachments"]
resourceNames: ["cart", "checkout"]
verbs: ["create", "get"]
# Policy: may this identity gather the namespace's pod logs?
- apiGroups: ["telepresence.io"]
resources: ["logs", "logs/yaml"]
verbs: ["get"]

Bind both Roles to the developer with ordinary RoleBindings. Nothing here lets the identity read or change pods, services, or namespaces through the Kubernetes API.

Step 4: Direct Connect, no Kubernetes API access at all​

externalEndpoint:
enabled: true
tls:
secretName: traffic-manager-tls # an existing kubernetes.io/tls Secret

The final step removes the last grant — and with it the client's need to contact the Kubernetes API server. The manager publishes a TLS endpoint of its own; a client configured with cluster.managerAddress dials it directly instead of port-forwarding through the API server, and makes no Kubernetes API request from either of its daemons. The client still authenticates with its kubeconfig credentials; reading the kubeconfig and running its exec plugin are local operations. Attachment traffic needs the QUIC endpoint published alongside.

This mode requires enforcing authentication — without the API server vouching for whoever reaches the port, an unauthenticated caller must not be admitted — and pairs naturally with telepresence as the required grant and clientRbac.create: false, so that no Kubernetes grant, held for whatever reason, can establish a session. Publishing the endpoint also withholds the mechanical pods/portforward grants from any client Roles the chart still renders — those clients never port-forward — leaving only the telepresence.io policy grants, unless the required grant is portforward. See External control endpoint.

The same developer as in step 3, connecting through Direct Connect, needs no Kubernetes grant at all. The connect Role loses its pods/portforward rule, and what is left in both Roles is policy that the traffic-manager evaluates and the Kubernetes API server never sees:

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: traffic-manager-connect
namespace: ambassador
rules:
- apiGroups: ["telepresence.io"]
resources: ["connections"]
verbs: ["create"]
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: telepresence-shop
namespace: shop
rules:
- apiGroups: ["telepresence.io"]
resources: ["attachments"]
resourceNames: ["cart", "checkout"]
verbs: ["create", "get"]
- apiGroups: ["telepresence.io"]
resources: ["logs", "logs/yaml"]
verbs: ["get"]

The Kubernetes API server would grant this identity nothing: the telepresence.io resources do not exist there. Whether the developer may connect, attach, or read logs is decided entirely by the traffic-manager, against the identity it verified when the client authenticated. That is what Direct Connect brings: the client's cluster footprint is the policy you wrote, and nothing else.

The ladder​

Client's Kubernetes permissionsHelm valuesRequires
Discovery, diagnostics, and port-forward grantsdefaults—
One named pods/portforward in the manager namespace, pods/portforward per attached namespaceclientRbac.legacyAccess: falsecurrent clients, default apiPort
Policy-only telepresence.io grants+ security.authorization.requiredGrant: telepresencesecurity.authentication.mode: enforcing; QUIC tunnel for attachments
None+ externalEndpoint, clientRbac.create: falseenforcing mode, a persisted TLS certificate, QUIC for Direct Connect attachments

telepresence setup asks about each step above — whether to enforce authentication, which grant to require, whether to enable Direct Connect, and whether legacy clients need access — and writes the resulting values; see Guided cluster setup.