proxy

package
v0.2.49 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 6, 2026 License: Apache-2.0 Imports: 28 Imported by: 0

Documentation

Overview

Package proxy is the hub's transparent front door to kcp, plus the identity helpers and static-token login that share its auth dispatch.

Index

Constants

View Source
const DefaultMaxLimiterEntries = 10000

DefaultMaxLimiterEntries bounds how many distinct client addresses a per-address rate limiter tracks at once. Past it the least recently seen address is dropped; an address that comes back simply starts a fresh bucket. The bound exists so a flood of distinct peers cannot grow the hub's memory without limit — it is not a security boundary in itself, since every key is a real connection peer once ClientIP has done its job.

Variables

View Source
var ErrIdentifyNoBearer = errors.New("no Authorization: Bearer token")

ErrIdentifyNoBearer is returned by IdentifyUser when the request carries no Authorization: Bearer header. Callers (e.g. the tenant middleware) translate this into a 401.

View Source
var ErrUserRecordUnavailable = errors.New("caller authenticated but user record unavailable")

ErrUserRecordUnavailable wraps failures that happen AFTER the caller's credential has been accepted — the static token matched, or the OIDC token verified — but the backing User record could not be read or created.

It exists so callers can tell "this caller is not authenticated" from "this caller is authenticated and the hub is having trouble". Both surface as an error from IdentifyUser, and conflating them forces a choice between serving unauthenticated callers and 500-ing authenticated ones during a hiccup. Endpoints that only need to know the caller is legitimate (not who they are) can proceed on this; anything that acts as the user must still fail.

Functions

func ClientIP

func ClientIP(r *http.Request, trusted []netip.Prefix) string

ClientIP derives the address the hub keys its pre-authentication rate limits on. The rule is the one every reverse proxy follows when it forwards: the proxy APPENDS the peer it saw to X-Forwarded-For, so the only hop the hub can vouch for is the right-most one that a trusted proxy wrote. Every hop to the left of that is whatever the client chose to send.

  • The connection peer (RemoteAddr) is always the starting point.
  • When trusted is empty, X-Forwarded-For and X-Real-IP are ignored entirely and the peer address is the answer. A hub deployed behind a proxy without --trusted-proxy-cidrs therefore sees every client as the proxy's own address and throttles them as one; that is the safe way to fail (too much throttling, never too little).
  • When the peer is inside a trusted prefix, X-Forwarded-For is walked from the right, skipping hops that are themselves trusted proxies, and the first untrusted hop is the client. X-Real-IP is consulted only when that walk yields nothing.
  • A hop that is not a valid IP ends the walk and the peer address is used, so a malformed header can never mint a fresh bucket.

The result is a canonical IP string (IPv4-mapped IPv6 unmapped, zone dropped); when RemoteAddr itself is not an IP it is returned as-is, which only happens for non-TCP listeners.

func ParseTrustedProxyCIDRs

func ParseTrustedProxyCIDRs(values []string) ([]netip.Prefix, error)

ParseTrustedProxyCIDRs parses the --trusted-proxy-cidrs values. Each entry may be a CIDR ("10.0.0.0/8", "fd00::/8") or a bare address, which is taken as a single-host prefix. Entries may themselves be comma-separated; blanks are ignored.

Types

type BoundedKeys

type BoundedKeys[V any] struct {
	// contains filtered or unexported fields
}

BoundedKeys is a fixed-capacity, least-recently-used map from a client key to a limiter state, with idle eviction. It is not safe for concurrent use; the limiter that owns it holds the lock.

Two things keep it bounded: Put evicts the least recently used entry when the map is at capacity, and any Put that lands more than idle after the previous sweep first drops every entry that has not been touched within idle. The sweep walks from the least recent end and stops at the first live entry, so it costs no more than the number of entries it removes.

func NewBoundedKeys

func NewBoundedKeys[V any](maxEntries int, idle time.Duration) *BoundedKeys[V]

NewBoundedKeys returns an empty store holding at most max entries and forgetting entries not touched for idle. Non-positive max falls back to DefaultMaxLimiterEntries; non-positive idle disables idle eviction.

func (*BoundedKeys[V]) Get

func (b *BoundedKeys[V]) Get(key string, now time.Time) (V, bool)

Get returns the value for key, marking it as used at now.

func (*BoundedKeys[V]) Len

func (b *BoundedKeys[V]) Len() int

Len is the number of tracked keys.

func (*BoundedKeys[V]) Put

func (b *BoundedKeys[V]) Put(key string, val V, now time.Time)

Put stores val under key as the most recently used entry, evicting idle entries and then the least recently used one as needed to stay in bounds.

type IPRateLimiter

type IPRateLimiter struct {
	// contains filtered or unexported fields
}

IPRateLimiter is the per-client-address token bucket the hub uses on its pre-authentication endpoints (token-login, the aggregate MCP bearer check). It is the same limiter HandleTokenLoginRateLimited applies, exposed so other packages can share one implementation without importing its internals.

func NewIPRateLimiter

func NewIPRateLimiter(interval time.Duration, burstSize int) *IPRateLimiter

NewIPRateLimiter returns a limiter that admits burstSize requests per client address immediately and refills one token per interval up to burstSize.

func (*IPRateLimiter) Allow

func (l *IPRateLimiter) Allow(clientIP string) bool

Allow reports whether a request from clientIP may proceed, consuming one token when it may.

type KCPProxy

type KCPProxy struct {
	// contains filtered or unexported fields
}

KCPProxy is the hub's front door to kcp. It forwards every kube path (/clusters/{id}/…, /services/apiexport/…, bare /api and /apis) to kcp byte-for-byte: path, query, Authorization and the WebSocket bearer subprotocol all arrive as the caller sent them, and the hub adds no credential of its own. kcp authenticates the bearer (static token, OIDC, ServiceAccount) and authorizes the request with its own RBAC — the workspace `access` gate, the bound tenant-admin role and the organization boundary guard — so the hub makes no access decision on this path. The type also hosts the identity helpers (BrowserIdentity, IdentifyUser) and the static-token login the rest of the hub shares with that front door.

func NewKCPProxy

func NewKCPProxy(kcpConfig *rest.Config, verifier *oidc.IDTokenVerifier, railgridClient *railgridclient.Client, staticAuthTokens []string, hubExternalURL string, devMode bool) (*KCPProxy, error)

NewKCPProxy creates the transparent front door to kcp. verifier is used only by BrowserIdentity and may be nil when only static token auth is used.

func (*KCPProxy) BrowserIdentity

func (p *KCPProxy) BrowserIdentity(r *http.Request) (browsersession.Identity, error)

BrowserIdentity validates a portal bearer and resolves it to the stable platform user identity used by the shared browser-session store. The returned value contains no bearer, kubeconfig, or OIDC token.

func (*KCPProxy) HandleTokenLogin

func (p *KCPProxy) HandleTokenLogin(w http.ResponseWriter, r *http.Request)

HandleTokenLogin handles static token login requests. POST /auth/token-login with Authorization: Bearer <token> Returns a LoginResponse with kubeconfig pointing to the user's workspace. Note: This handler should be wrapped with rate limiting using HandleTokenLoginRateLimited when registering routes to prevent brute force attacks.

func (*KCPProxy) HandleTokenLoginRateLimited

func (p *KCPProxy) HandleTokenLoginRateLimited(w http.ResponseWriter, r *http.Request)

HandleTokenLoginRateLimited wraps HandleTokenLogin with rate limiting. This should be used when registering the route to protect against brute force attacks. In devMode the limiter is bypassed: dev clusters share a single client IP (localhost) across many test/CLI invocations, which trivially exhausts the per-IP bucket and is not a realistic threat model for dev.

func (*KCPProxy) IdentifyUser

func (p *KCPProxy) IdentifyUser(r *http.Request) (string, error)

IdentifyUser extracts the caller's User CR name from r's bearer token (static token or OIDC). Used by hub REST endpoints behind the tenant middleware so they can identify the caller with one auth dispatch.

Returns ErrIdentifyNoBearer for missing/unparseable Authorization headers, and other errors for verification failures. kcp ServiceAccount tokens are intentionally not accepted here — REST endpoints are addressed by humans (or by their portal session) and not by edge-side bots.

func (*KCPProxy) ScrubStaticTokenUsers

func (p *KCPProxy) ScrubStaticTokenUsers(ctx context.Context) error

ScrubStaticTokenUsers removes the token text from the Users (and their personal Organizations) of every configured static token; see scrubStaticTokenUser. Login does the same, but a token nobody uses after an upgrade would otherwise stay readable in member lists indefinitely. It only updates Users that exist; it never creates one.

func (*KCPProxy) ServeHTTP

func (p *KCPProxy) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP forwards the request to kcp unchanged. There is no bearer check, cluster check or path rewrite here: a request without a credential gets kcp's 401, a workspace the caller holds no RBAC in gets kcp's 403, and a path kcp does not serve gets kcp's 404.

func (*KCPProxy) SetBrowserSessionStore

func (p *KCPProxy) SetBrowserSessionStore(store *browsersession.Store)

SetBrowserSessionStore wires the hub-wide opaque browser-session store used by static-token portal login. The proxy never places the static bearer in that store or in the cookie; it records only the resolved User identity.

func (*KCPProxy) SetTrustedProxies

func (p *KCPProxy) SetTrustedProxies(prefixes []netip.Prefix)

SetTrustedProxies tells the token-login rate limiter which connection peers are reverse proxies whose X-Forwarded-For may be believed (see ClientIP). Without it every request is keyed on the connection peer, so a hub behind a proxy throttles all of its clients as one address.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL