The host owns sessions, Kubernetes credentials and authorization policy. The gateway enforces the host's scope and projection decisions. How Kubernetes checks a caller depends on the backend:
| Backend | Kubernetes identity | Host responsibility |
|---|---|---|
| Per-user | The caller's token or an impersonated user | Resolve the caller, authorize the scope and supply their client. |
| Shared | One service identity | Authorize every subscriber before serving cached objects; use kube.SubjectAccessReviewAuthorizer for Kubernetes RBAC decisions. |
Prefer a per-user backend: Kubernetes RBAC is then the boundary by construction. The wiring for both is in adopting. Watch sharing is an efficiency choice; it never grants a subscriber the service identity's access. Full/spec Secret redaction also does not replace scope authorization.
These settings describe the current gateway. The native connector reads through a host proxy behind
the same session boundary; the proxy, not the browser, holds Kubernetes credentials and authorizes
each collection. Native retains original authorized resources; gateway SSE delivers
projected views. Neither source may bypass refusal or extend session lifetime. The native connector
must stop on terminal authentication failure, with a new connection after access is restored. Native
writes go through the same proxy as the signed-in person; the proxy validates each PATCH with
ValidateNativeMergePatch and keeps its own write authorization, and Kubernetes RBAC still applies.
See native editing.
Use a same-origin session cookie. The host handles OIDC with its identity provider, keeps the tokens
server-side and issues a secure session cookie. connectResourceStream sends that cookie over fetch;
native EventSource can use the same route.
sequenceDiagram
participant B as Browser
participant S as Your Go application
participant D as OIDC provider
participant K as Kubernetes API
B->>S: Sign in
S-->>B: Redirect to identity provider
B->>D: Authenticate
D-->>B: Redirect to host callback with code
B->>S: Callback with code
S->>D: Exchange code
D-->>S: Tokens
S-->>B: Secure, HttpOnly, SameSite session cookie
B->>S: Open stream with session cookie
S->>S: Resolve principal and authorize scope
S->>K: Open watch using caller's client
K-->>S: Watch events or access refusal
S-->>B: Projected SSE stream or terminal error
A token-bearing client uses connectResourceStream with explicit headers, because native
EventSource cannot send an Authorization header. The host must then enforce trusted HTTPS
endpoints and redirect handling; see adoption.
Principalresolves the request to an opaque application identity. Return a*StreamErrorto choose the refusal (UNAUTHENTICATED,UPSTREAM_UNAVAILABLE); any other error is sent asUNAUTHENTICATEDwithout its text.Authorizerdenies unauthorized scopes before a watch opens and on every later check.Clientssupplies the backend for that identity and target. Build it withkube.NewBackendForConfig, which refuses redirects: client-go would otherwise carry the caller's token to wherever a redirect points. A dynamic client forkube.NewBackendmust be built onkube.HTTPClientFor(cfg)for the same reason.Scopesallowlists targets and resources, or withAnyResourceleaves resource admission to the API server's RBAC for a backend that acts as the caller. A browser cannot supply an API-server URL.
A per-user backend can use the user's bearer token or Kubernetes impersonation; impersonation needs host credentials with impersonation rights. A projection does not grant permission to read or write a resource: redaction restricts disclosure to a caller who is already authorized.
The gateway rechecks authorization and projection policy on every snapshot cycle, and calls Clients
again so the host can supply refreshed credentials. Checks use the principal captured when the stream
opened, so resolve current session and account validity inside the Authorizer. End the request when
the session or token expires: the shared-host example
puts the earlier of the two on the request context as a deadline.
A quiet stream may go a long time without a new cycle. To bound how long a revoked caller keeps it, recheck on a timer:
options.ReauthorizationInterval = 30 * time.Second // how often each subscriber is rechecked
options.ReauthorizationTimeout = 5 * time.Second // budget for each periodic check's callbacks
options.WriteTimeout = 10 * time.Second // required over HTTP with timed checksEach subscriber is checked on its own timer. Denial, timeout, policy failure or a changed projection
ends only that stream. A SubjectAccessReview that cannot reach the API server ends it with a
non-terminal UPSTREAM_UNAVAILABLE, so the client reconnects later. Zero interval keeps cycle-only
checks; zero timeout uses 10 seconds. Timed checks do not call Clients.
A timed check shares the subscriber's delivery gate: while it runs, that subscriber receives no
objects, and it waits for the delivery in progress. A write to a browser that has stopped reading
blocks once the buffers fill, so HTTP serving requires a positive WriteTimeout with timed checks.
Handler panics at construction without one, and ServeStream panics before writing. Test that your mounted middleware supports flushing and write deadlines with the
capability-check recipe.
Stream allows timed checks with any sink; the host bounds that sink's I/O and honors cancellation.
Each check costs the host's authorizer a call. With SubjectAccessReviewAuthorizer, 200 subscribers
on a 30-second interval add about 13 reviews per second (list and watch each), plus opening and
cycle checks. Choose the interval for both your revocation budget and API-server capacity.
The time from withdrawing access until a stream stops delivering is made of these parts:
| Part | Bounded by | What it covers |
|---|---|---|
| Decision freshness | the host | Until the authorizer can see the change: session stores, group sync, any decision cache. |
| Timer | ReauthorizationInterval |
Up to one interval until that subscriber's next check. |
| Gate wait | not bounded as a whole | Waits for current delivery; WriteTimeout bounds individual HTTP writes, not total gate waiting. |
| Check | ReauthorizationTimeout |
Starts once the check holds the gate; covers the Authorizer and projection policy. |
| Termination | WriteTimeout, then the host |
The refusal is written as the terminal frame; the request then returns and releases its subscription. |
Measure the total revocation latency for your deployment under its intended load.
Revocation ends three things at different times. New object delivery stops when a denying check holds the gate. The request returns once the terminal frame is written or fails. The browser sees the refusal only if the transport delivers it; a reader that stopped reading may never see it. Bytes already written to the socket or buffered by a proxy cannot be recalled.
ReauthorizationTimeout covers periodic checks only. Opening and snapshot-cycle checks run under the
request's context, so give host callbacks their own deadlines, but not a short deadline on the whole
healthy stream.
SharedBackend opens one upstream watch per scope as one service identity.
The host's Authorizer is then the only check between a subscriber and the cached objects, which is
why sharing is opt-in. Authorize every subscriber with kube.SubjectAccessReviewAuthorizer, so
Kubernetes still decides whether that caller may list and watch the scope.
The adapter refuses an incomplete review, and an explicit Denied wins over Allowed. The service
account needs create on subjectaccessreviews (system:auth-delegator), not impersonation rights.
These are SubjectAccessReview requests, not SelfSubjectAccessReview requests. Opening, cycle and timed
checks use the same authorizer.
For each check the adapter sends one review per verb, list then watch, and stops at the first
refusal or failure:
| Field | Value |
|---|---|
user, groups, uid, extra |
The kube.Subject returned by the host's SubjectFor, unchanged. |
resourceAttributes.verb |
list, then watch. |
resourceAttributes.group, .version, .resource |
Copied from the scope. |
resourceAttributes.namespace, .name |
Copied from the scope. |
Selectors are omitted from the authorization request: the label selector, any field selector, and the subresource are not sent, so a selector does not narrow the question. The scope's target and the stream's projection are not review attributes; the clientset given to the adapter decides which API server receives the review. This describes the adapter's request, not how every Kubernetes authorizer evaluates it.
A host that caches decisions must:
- key on the complete subject (user, groups, UID and extra) and the complete request, including the verb;
- keep decisions from different API servers or targets apart;
- never let a reused review skip its own checks of session validity or projection policy.
Changing these inputs is a compatibility change for caching hosts, and is announced in the release notes. The library does not provide a decision cache.
The shared ConfigMap host is a tested composition of participant identity resolution, service-account reviews, session expiry and bounded HTTP delivery. Its identity helper is an example, not a library API.
The host owns writes, CSRF protection, audit and write authorization. See saving.