Kubernetes Controller RBAC

Every permission the Kubernetes Gateway/Ingress controller uses, and why. This is the source the Helm chart’s Role and ClusterRole are generated from; a verb that does not appear here is one the controller does not use.

The controller is entirely absent unless the top-level kubernetes configuration section is present, so a Trickster deployment that is not a gateway needs none of this.

Watched resources

The controller reads these to build its routing model. It never writes them themselves; the get verbs on the four statused kinds exist only to re-read an object whose status write was refused as a conflict, and a read-only instance does not need them.

API groupResourceVerbsScopeWhy
gateway.networking.k8s.iogatewayclassesget, list, watchClusterDeciding which classes name this controller; get re-reads one whose status write conflicted
gateway.networking.k8s.iogatewaysget, list, watchNamespacedListeners, hostnames, TLS references; get as above
gateway.networking.k8s.iohttproutesget, list, watchNamespacedRoute matches and backend references; get as above
gateway.networking.k8s.iogrpcroutesget, list, watchNamespacedgRPC method matches and backend references; only in a cluster whose Gateway API serves the kind, and get as above
gateway.networking.k8s.iotcproutes, tlsroutes, udproutesget, list, watchNamespacedStream route backend references and, for TLSRoute, server names; only in a cluster whose experimental Gateway API channel (v1alpha2) serves the kind, and get as above
gateway.networking.k8s.ioreferencegrantslist, watchNamespacedPermitting cross-namespace Service and Secret references
gateway.networking.k8s.iobackendtlspolicieslist, watchNamespacedTLS to backends; only in a cluster whose Gateway API serves the kind
networking.k8s.ioingressclasseslist, watchClusterDeciding which classes name this controller, and which is the cluster default
networking.k8s.ioingressesget, list, watchNamespacedHosts, paths, backends, TLS references; get as above
(core)serviceslist, watchNamespacedResolving backend references to an address and port; also in published_service’s namespace, for its addresses
(core)secretslist, watchNamespacedTLS certificates for HTTPS listeners
(core)configmapslist, watchNamespacedA GatewayClass’s parametersRef and a BackendTLSPolicy’s CA bundle; only in a cluster serving the Gateway API
(core)namespaceslist, watchClusterWhen namespace_selector is configured, or the cluster serves the Gateway API
discovery.k8s.ioendpointsliceslist, watchNamespacedOnly with routing_mode: endpoint: the generated discoverer reads the endpoints of every referenced Service over the controller’s connection
trickstercache.orgtrickstercachepoliciesget, list, watchNamespacedCaching policy on Gateways, routes and Services; only in a cluster that serves the resource, and get as above

The gateway.networking.k8s.io grants are needed only in a cluster that serves the Gateway API; its CRDs are an add-on. The controller probes for the group at startup and, where it is absent, watches none of its kinds and serves Ingress objects alone. Granting the verbs for resources the cluster does not define is harmless, so one Role covers both cases; a cluster that will never install the CRDs can leave them out.

secrets access is narrowed server-side to type=kubernetes.io/tls, so the controller never holds an application’s credentials in memory. The grant itself cannot express that narrowing, which is the strongest reason to scope the controller to named namespaces where that is possible.

namespaces is requested when namespace_selector is configured, and in any cluster that serves the Gateway API, because a Gateway listener may admit routes by the labels of their namespace (allowedRoutes.namespaces.from: Selector) and only the Namespace object carries those labels. An Ingress-only controller using watch_namespaces avoids both the cluster-wide namespace read and the cluster-scoped Role binding for it.

configmaps is requested only in a cluster that serves the Gateway API, for the ConfigMap a GatewayClass’s parametersRef may name and the CA bundle a BackendTLSPolicy’s caCertificateRefs may name. It is watched in the same namespaces as Services, so a reference into a namespace outside watch_namespaces is reported as not found.

backendtlspolicies graduated to the Gateway API’s v1 later than the core kinds, so a cluster may serve the group without it. The controller reads the served resources at startup and builds the informer only where the kind is served; a policy on a cluster without it is simply never seen.

trickstercachepolicies is this project’s own custom resource (kubernetes-cache-policy.md), installed from deploy/kube/crds. The controller probes for it at startup exactly as it probes for the Gateway API and watches it only where the cluster serves it, so the grant is harmless on a cluster without the definition.

EndpointSlices are not watched by the controller itself: endpoint churn is the autodiscovery provider’s job and reaches the data plane through pool mutation rather than a configuration reload. In the endpoint routing mode the controller generates a discoverer over its own connection, so the service account then also needs the endpointslices grant above in every namespace whose Services routes reference, exactly as a hand-configured discoverer would; see the RBAC section of alb-autodiscovery.md. In the service routing mode it is not needed.

Cluster writes

Status and Events are the only writes the controller makes; everything above is read. Setting read_only: true gives up all of them, and the grant below along with them, for an instance whose service account cannot or should not write to the cluster. It still watches, translates and serves traffic — read-only describes the relationship with the cluster, not the data plane.

API groupResourceVerbsScopeWhy
gateway.networking.k8s.iogatewayclasses/statusupdateClusterAccepted condition on claimed classes
gateway.networking.k8s.iogateways/statusupdateNamespacedAccepted and Programmed conditions, listener conditions, and published addresses
gateway.networking.k8s.iohttproutes/statusupdateNamespacedPer-parent Accepted and ResolvedRefs conditions
gateway.networking.k8s.iogrpcroutes/statusupdateNamespacedThe same, for GRPCRoutes
gateway.networking.k8s.iotcproutes/status, tlsroutes/status, udproutes/statusupdateNamespacedThe same, for the stream route kinds where the cluster serves them
networking.k8s.ioingresses/statusupdateNamespacedstatus.loadBalancer addresses
trickstercache.orgtrickstercachepolicies/statusupdateNamespacedPer-target Accepted conditions
(core)eventscreate, patchNamespacedTranslation errors, rejected annotations, certificate failures, class acceptance; patch is how client-go counts a repeated Event

Leader election

Required only when leader_election.enabled is true (the default) and the instance is not read-only: the election decides which replica writes status and Events, so an instance that writes neither contends for nothing. Every replica programs its own data plane regardless, so a replica that loses an election still serves traffic.

API groupResourceVerbsScopeWhy
coordination.k8s.ioleasesget, create, updateNamespacedThe election itself, in leader_election.namespace

Address publishing

Required only when published_service is configured: the controller watches that one Service (list, watch on services, narrowed by a field selector to its name) in the Service’s own namespace, which need not be one of the watched namespaces, and publishes its assigned addresses into Gateway and Ingress status.

Last modified September 14, 2026: update docs (ff7e425)