Documentation
¶
Overview ¶
Package server provides OAuth 2.1 protection for the Muster Server.
This package implements ADR 005 (OAuth Protection for Muster Server), allowing the Muster Server to act as an OAuth Resource Server. When enabled, all MCP endpoints require valid access tokens from authenticated clients.
Architecture ¶
The server package integrates with the mcp-oauth library to provide:
- OAuth 2.1 server with mandatory PKCE
- Dynamic client registration (RFC 7591)
- Client ID Metadata Documents (CIMD) per MCP 2025-11-25 spec
- Token validation middleware for protecting MCP endpoints
- Multiple provider support (Dex OIDC, Google OAuth)
- Token storage backends (in-memory, Valkey/Redis)
Integration ¶
The OAuth server wraps the existing aggregator HTTP handler, adding authentication and authorization before requests reach MCP endpoints.
┌─────────────────────────────────────────────────────────────┐ │ Muster Server │ │ │ │ [ OAuth Middleware (Resource Server) ] │ │ Validates Token from Agent │ │ │ │ │ ▼ │ │ [ Aggregator / Tool Handler ] │ │ │ │ │ ▼ │ │ [ OAuth Proxy (Client) ] │ │ Injects Token for Remote MCPs │ └─────────────────────────────────────────────────────────────┘
Usage ¶
To enable OAuth server protection, configure the aggregator with:
aggregator:
oauthServer:
enabled: true
baseUrl: "https://muster.example.com"
provider: "dex"
dex:
issuerUrl: "https://dex.example.com"
clientId: "muster-server"
clientSecret: "${DEX_CLIENT_SECRET}"
Endpoints ¶
When OAuth server is enabled, the following endpoints are exposed:
- /.well-known/oauth-authorization-server - Authorization Server Metadata (RFC 8414)
- /.well-known/oauth-protected-resource - Protected Resource Metadata (RFC 9728)
- /oauth/register - Dynamic Client Registration (RFC 7591)
- /oauth/authorize - OAuth Authorization
- /oauth/token - Token Endpoint
- /oauth/callback - OAuth Callback (from IdP)
- /oauth/revoke - Token Revocation (RFC 7009)
- /mcp - Protected MCP endpoint (requires Bearer token)
Index ¶
- Constants
- func ContextWithIDToken(ctx context.Context, idToken string) context.Context
- func GetIDToken(token *oauth2.Token) string
- func GetIDTokenFromContext(ctx context.Context) (string, bool)
- type LazyOAuthHTTPServer
- func (l *LazyOAuthHTTPServer) CreateMux() http.Handler
- func (l *LazyOAuthHTTPServer) RefreshSession(ctx context.Context, familyID string) error
- func (l *LazyOAuthHTTPServer) SetOnAuthenticated(fn func(context.Context, string))
- func (l *LazyOAuthHTTPServer) Shutdown(ctx context.Context) error
- func (l *LazyOAuthHTTPServer) ValidateTokenWithSubject(next http.Handler) http.Handler
- func (l *LazyOAuthHTTPServer) WaitReady(ctx context.Context) error
- type OAuthHTTPServer
- func (s *OAuthHTTPServer) CreateMux() http.Handler
- func (s *OAuthHTTPServer) GetOAuthHandler() *oauthhandler.Handler
- func (s *OAuthHTTPServer) GetOAuthServer() *oauth.Server
- func (s *OAuthHTTPServer) GetTokenStore() storage.TokenStore
- func (s *OAuthHTTPServer) RefreshSession(ctx context.Context, familyID string) error
- func (s *OAuthHTTPServer) SetOnAuthenticated(fn func(ctx context.Context, sessionID string))
- func (s *OAuthHTTPServer) Shutdown(ctx context.Context) error
- func (s *OAuthHTTPServer) ValidateTokenWithSubject(next http.Handler) http.Handler
- type UserInfo
Constants ¶
const ( // OAuthProviderDex is the Dex OIDC provider type. OAuthProviderDex = "dex" // OAuthProviderGoogle is the Google OAuth provider type. OAuthProviderGoogle = "google" // DefaultAccessTokenTTL is the configured TTL for access tokens (30 minutes). // This is intentionally set to match the Dex idTokens expiry (30m) so that // capTokenExpiry in mcp-oauth doesn't need to cap it further. If Dex's // idTokens expiry is shorter than this value, capTokenExpiry will // automatically reduce the effective TTL to match the provider's token lifetime. DefaultAccessTokenTTL = 30 * time.Minute // DefaultRefreshTokenTTL is the server-side TTL for refresh tokens. // Derived from pkgoauth.DefaultSessionDuration to keep server and CLI in sync. // Aligned with Dex's absoluteLifetime (720h = 30 days). Note: muster uses a // rolling TTL (reset on each rotation), while Dex's absoluteLifetime is // measured from original issuance and does NOT reset. DefaultRefreshTokenTTL = pkgoauth.DefaultSessionDuration // DefaultIPRateLimit is the default rate limit for requests per IP (requests/second). // In Kubernetes deployments, traffic may arrive from multiple distinct IPs or be // NATed through an ingress, so per-IP limits should be generous enough to avoid // false positives while still protecting against abuse. DefaultIPRateLimit = 50 // DefaultIPBurst is the default burst size for IP rate limiting. DefaultIPBurst = 100 // DefaultUserRateLimit is the default rate limit for authenticated users (requests/second). DefaultUserRateLimit = 100 // DefaultUserBurst is the default burst size for authenticated user rate limiting. DefaultUserBurst = 200 // DefaultMaxClientsPerIP is the default maximum number of clients per IP address. DefaultMaxClientsPerIP = 10 // DefaultSecurityEventRate bounds security-event log emission (events/second // per keyed event), keeping a malformed-token attack from flooding the audit // pipeline. Mirrors mcp-oauth's production-example default (1, 5). DefaultSecurityEventRate = 1 // DefaultSecurityEventBurst is the burst size for security-event log emission. DefaultSecurityEventBurst = 5 // DefaultMetadataFetchRate is the per-domain rate for CIMD outbound metadata // fetches (requests/second). Prevents a misbehaving or malicious client from // triggering unbounded outbound HTTP requests to arbitrary domains. DefaultMetadataFetchRate = 1 // DefaultMetadataFetchBurst is the burst size for CIMD metadata fetch rate limiting. DefaultMetadataFetchBurst = 3 // DefaultReadHeaderTimeout is the default timeout for reading request headers. DefaultReadHeaderTimeout = 10 * time.Second // DefaultWriteTimeout is the default timeout for writing responses. DefaultWriteTimeout = 120 * time.Second // DefaultIdleTimeout is the default idle timeout for keepalive connections. DefaultIdleTimeout = 120 * time.Second )
Variables ¶
This section is empty.
Functions ¶
func ContextWithIDToken ¶ added in v0.1.26
ContextWithIDToken creates a context with the given OIDC ID token. This is used to pass the user's ID token for downstream authentication (e.g., to remote MCP servers).
func GetIDToken ¶
GetIDToken extracts the ID token from an OAuth2 token. OIDC providers include an id_token in the Extra data. Kubernetes OIDC authentication requires the ID token, not the access token.
Types ¶
type LazyOAuthHTTPServer ¶ added in v0.1.209
type LazyOAuthHTTPServer struct {
// contains filtered or unexported fields
}
LazyOAuthHTTPServer wraps OAuthHTTPServer construction behind a background retry loop. The HTTP handler surface is available immediately but returns 503 until OIDC discovery against the upstream Dex/OIDC issuer succeeds. All non-OAuth paths (MCP aggregation, reconcilers) are unaffected and start immediately.
func NewLazyOAuthHTTPServer ¶ added in v0.1.209
func NewLazyOAuthHTTPServer(ctx context.Context, cfg config.OAuthServerConfig, mcpHandler http.Handler, debug bool, opts ...oauth.ServerOption) *LazyOAuthHTTPServer
NewLazyOAuthHTTPServer creates a lazy OAuth HTTP server that starts a background goroutine to perform OIDC discovery with exponential backoff. It always returns successfully; the caller gets a handler that serves 503 until discovery succeeds.
func (*LazyOAuthHTTPServer) CreateMux ¶ added in v0.1.209
func (l *LazyOAuthHTTPServer) CreateMux() http.Handler
CreateMux returns an http.Handler that proxies to the inner mux once ready. Before OIDC discovery succeeds, /health returns a degraded-status JSON body and all other paths return 503.
func (*LazyOAuthHTTPServer) RefreshSession ¶ added in v0.5.4
func (l *LazyOAuthHTTPServer) RefreshSession(ctx context.Context, familyID string) error
RefreshSession forces an in-process upstream provider token refresh for the given token family. Returns an error if OIDC discovery has not yet completed.
func (*LazyOAuthHTTPServer) SetOnAuthenticated ¶ added in v0.1.209
func (l *LazyOAuthHTTPServer) SetOnAuthenticated(fn func(context.Context, string))
SetOnAuthenticated stores the callback and forwards it to the inner server once ready. Safe to call before or after discovery completes.
func (*LazyOAuthHTTPServer) Shutdown ¶ added in v0.1.209
func (l *LazyOAuthHTTPServer) Shutdown(ctx context.Context) error
Shutdown stops the discovery loop and shuts down the inner server if it was created. It waits for the background goroutine to exit so the caller can be sure no new inner server connections will be opened after Shutdown returns.
func (*LazyOAuthHTTPServer) ValidateTokenWithSubject ¶ added in v0.1.209
func (l *LazyOAuthHTTPServer) ValidateTokenWithSubject(next http.Handler) http.Handler
ValidateTokenWithSubject returns a middleware that delegates to the inner server once OIDC discovery has succeeded. Before that it returns 503.
type OAuthHTTPServer ¶
type OAuthHTTPServer struct {
// contains filtered or unexported fields
}
OAuthHTTPServer wraps an MCP HTTP handler with OAuth 2.1 authentication. It provides both OAuth server functionality (authorization, token issuance) and resource server protection (token validation middleware).
func NewOAuthHTTPServer ¶
func NewOAuthHTTPServer(cfg config.OAuthServerConfig, mcpHandler http.Handler, debug bool, opts ...oauth.ServerOption) (*OAuthHTTPServer, error)
NewOAuthHTTPServer creates a new OAuth-enabled HTTP server that wraps the provided MCP handler with authentication protection. Caller-provided mcp-oauth options (e.g. token-family lifecycle handlers) are forwarded to the underlying OAuth server.
func (*OAuthHTTPServer) CreateMux ¶
func (s *OAuthHTTPServer) CreateMux() http.Handler
CreateMux creates an HTTP mux that routes to both OAuth and MCP handlers. The MCP endpoints are protected by the OAuth ValidateToken middleware.
func (*OAuthHTTPServer) GetOAuthHandler ¶
func (s *OAuthHTTPServer) GetOAuthHandler() *oauthhandler.Handler
GetOAuthHandler returns the OAuth handler for testing or direct access.
func (*OAuthHTTPServer) GetOAuthServer ¶
func (s *OAuthHTTPServer) GetOAuthServer() *oauth.Server
GetOAuthServer returns the underlying OAuth server for testing or direct access.
func (*OAuthHTTPServer) GetTokenStore ¶
func (s *OAuthHTTPServer) GetTokenStore() storage.TokenStore
GetTokenStore returns the token store for downstream OAuth passthrough.
func (*OAuthHTTPServer) RefreshSession ¶ added in v0.5.4
func (s *OAuthHTTPServer) RefreshSession(ctx context.Context, familyID string) error
RefreshSession forces an in-process upstream provider token refresh for the given token family. Delegates to the underlying mcp-oauth Server.RefreshSession so that TokenRefreshHandler fires and the SSO proxy store is updated before the caller re-reads the ID token.
func (*OAuthHTTPServer) SetOnAuthenticated ¶ added in v0.1.59
func (s *OAuthHTTPServer) SetOnAuthenticated(fn func(ctx context.Context, sessionID string))
SetOnAuthenticated registers a callback that fires on every authenticated MCP request after the session ID has been extracted. The aggregator uses this to trigger on-demand SSO connections from the HTTP middleware rather than from individual MCP operations.
func (*OAuthHTTPServer) Shutdown ¶
func (s *OAuthHTTPServer) Shutdown(ctx context.Context) error
Shutdown gracefully shuts down the server.
func (*OAuthHTTPServer) ValidateTokenWithSubject ¶ added in v0.1.44
func (s *OAuthHTTPServer) ValidateTokenWithSubject(next http.Handler) http.Handler
ValidateTokenWithSubject wraps the given handler with OAuth token validation and extracts the authenticated user's subject (sub claim) into the context. This is used for API endpoints that need to identify the user but don't need the full SSO/token-injection logic of the MCP middleware chain.