Documentation
¶
Overview ¶
Package proxy reverse-proxies authenticated requests to kcp.
Index ¶
- Constants
- Variables
- func ClientIP(r *http.Request, trusted []netip.Prefix) string
- func ParseTrustedProxyCIDRs(values []string) ([]netip.Prefix, error)
- type BoundedKeys
- type IPRateLimiter
- type KCPProxy
- func (p *KCPProxy) AuthorizeCluster(ctx context.Context, userName, clusterID string) bool
- func (p *KCPProxy) BrowserIdentity(r *http.Request) (browsersession.Identity, error)
- func (p *KCPProxy) HandleTokenLogin(w http.ResponseWriter, r *http.Request)
- func (p *KCPProxy) HandleTokenLoginRateLimited(w http.ResponseWriter, r *http.Request)
- func (p *KCPProxy) IdentifyUser(r *http.Request) (string, error)
- func (p *KCPProxy) ScrubStaticTokenUsers(ctx context.Context) error
- func (p *KCPProxy) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (p *KCPProxy) SetBrowserSessionStore(store *browsersession.Store)
- func (p *KCPProxy) SetTrustedProxies(prefixes []netip.Prefix)
Constants ¶
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 ¶
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.
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 ¶
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 ¶
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.
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 a reverse proxy that authenticates requests via OIDC and forwards them to the user's dedicated kcp tenant workspace.
func NewKCPProxy ¶
func NewKCPProxy(kcpConfig *rest.Config, verifier *oidc.IDTokenVerifier, railgridClient *railgridclient.Client, bootstrapper *kcp.Bootstrapper, staticAuthTokens []string, hubExternalURL string, devMode bool) (*KCPProxy, error)
NewKCPProxy creates a reverse proxy to kcp. It validates bearer tokens as OIDC id_tokens before proxying. verifier may be nil when only static token auth is used.
func (*KCPProxy) AuthorizeCluster ¶ added in v0.2.7
AuthorizeCluster reports whether userName may reach clusterID — the same membership question ServeHTTP settles for /clusters/{id}, exported so the one answer serves every front door.
The provider backend proxy is the second such door: a data-plane route names its workspace in its path (/{root}/clusters/{id}/…) and the hub has to authorize that cluster before it hands the ID to a provider as X-Railgrid-Cluster. It asks here rather than growing a second membership check, so the set of workspaces a caller can reach with kubectl and the set they can reach through a provider stay the same set.
It adds no policy of its own: the decision, the caching and the fail-closed behaviour are all clusterAuthorizer.authorize below, unchanged.
func (*KCPProxy) BrowserIdentity ¶
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 ¶
IdentifyUser extracts the caller's User CR name from r's bearer token, using the same auth schemes as ServeHTTP (static token, OIDC). Used by hub REST endpoints behind the tenant middleware so they can identify the caller without duplicating the proxy's 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 ¶
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 validates the bearer token and proxies the request to kcp. Two token types are supported:
- OIDC id_tokens (from Dex): resolved to a tenant workspace via User CRD lookup, forwarded with admin credentials.
- kcp ServiceAccount tokens: the clusterName claim identifies the workspace, forwarded with the original SA token so kcp handles authn/authz natively.
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 ¶
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.