Documentation
¶
Overview ¶
Package serverauth holds the request-admission seams shared by mast's network server surfaces (A2A — pkg/a2a; AG-UI — pkg/agui): pluggable bearer authentication (TokenValidator → Principal, with per-surface scope checks) and pluggable rate limiting (RateLimiter, in ratelimit.go). Both were first built for the A2A server (#78) and hoisted here so a single validator or limiter instance authenticates and admits across every surface (#84).
Like the surfaces that consume it, this package never imports the runtime: the daemon builds a validator/limiter from configuration and hands it to each server's New. It depends only on the standard library and golang.org/x/time, so it stays slim-embed-safe.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrInvalidToken = errors.New("serverauth: invalid or unknown bearer token")
ErrInvalidToken marks an unrecognized bearer token; server surfaces map it to HTTP 401.
Functions ¶
func IsLoopbackAddr ¶
IsLoopbackAddr reports whether a TCP listen address binds only a loopback interface. Conservative by design: an empty host (":7780"), the wildcards "0.0.0.0"/"::", and any hostname other than "localhost" all count as NON-loopback — when in doubt, treat the bind as network-reachable so a bind-policy check errs toward refusing. Server surfaces use it to refuse an unauthenticated bind beyond loopback. (pkg/attach keeps its own copy, predating this package.)
Types ¶
type Principal ¶
Principal is the authenticated caller a TokenValidator resolves a bearer token to. Scopes gate per-skill / per-workload access. Tenant, when set, is the caller identity the rate limiter buckets on (RateLimitRequest.Tenant).
Tenant does NOT yet drive session isolation: ADK v2.1.0's IsolationScope is an event/task-level field (the workflow finish_task machinery), not a session-create or tenant seam (session.CreateRequest carries no scope). Multi-tenant session isolation is deferred pending an ADK session-scope seam or a mast-side user-namespacing design (docs/a2a-design.md "Multi-tenancy").
type RateLimitRequest ¶
type RateLimitRequest struct {
// Subject is the authenticated caller (Principal.Subject); empty when
// the endpoint is unauthenticated (all such callers share one bucket).
Subject string
// Tenant is the caller's tenant claim (Principal.Tenant), when set.
// When present it is the caller identity the limiter buckets on, so a
// multi-token tenant is limited as one caller.
Tenant string
// Workload is the target workload (skill) the call routes to.
Workload string
// Method is the protocol method being admitted (e.g. "message/send" for
// A2A, "agui/run" for AG-UI).
Method string
}
RateLimitRequest identifies one inbound call for an admission decision. A server fills it from the authenticated principal and the resolved target workload before dispatching the turn-driving verb.
type RateLimiter ¶
type RateLimiter interface {
// Allow reports whether the request may proceed. When ok is false,
// retryAfter is an advisory backoff hint (zero if unknown).
Allow(ctx context.Context, req RateLimitRequest) (ok bool, retryAfter time.Duration)
}
RateLimiter admits or refuses an inbound call before the server drives the turn. A server calls Allow once per turn-driving request; cheap control-plane verbs are not gated. A false return maps to a retryable refusal (A2A -32000 / AG-UI HTTP 429) with an advisory Retry-After. Nil disables rate limiting. Implementations must be safe for concurrent use.
type StaticBearerValidator ¶
type StaticBearerValidator struct {
// contains filtered or unexported fields
}
StaticBearerValidator validates against a fixed token→Principal map — the "static bearer tokens (for simple deployments)" validator from docs/a2a-design.md. It compares in constant time across every configured token so neither a miss nor a value mismatch leaks timing.
func NewStaticBearerValidator ¶
func NewStaticBearerValidator(tokens map[string]*Principal) (*StaticBearerValidator, error)
NewStaticBearerValidator builds a validator from a token→Principal map. At least one non-empty token with a non-nil principal is required.
type TokenBucketLimiter ¶
type TokenBucketLimiter struct {
// contains filtered or unexported fields
}
TokenBucketLimiter is the built-in RateLimiter: an independent token bucket per (caller, workload), where the caller is the request's Tenant if set else its Subject. Every bucket shares the same rate and burst. It admits a request only when a token is available immediately; a refused request reports the wait until the next token as retryAfter and does NOT consume future capacity.
The bucket map grows one entry per distinct (caller, workload) seen and is not evicted — consistent with the daemon's per-session pools at v0.2 single-instance scale (bounded eviction is a follow-on).
func NewTokenBucketLimiter ¶
func NewTokenBucketLimiter(perSecond float64, burst int) (*TokenBucketLimiter, error)
NewTokenBucketLimiter builds a limiter admitting perSecond requests per (caller, workload) with the given burst. perSecond must be a finite value > 0 and burst must be >= 1.
func (*TokenBucketLimiter) Allow ¶
func (l *TokenBucketLimiter) Allow(_ context.Context, req RateLimitRequest) (bool, time.Duration)
Allow implements RateLimiter.
type TokenValidator ¶
TokenValidator resolves a bearer token to a Principal. It returns ErrInvalidToken for a token it does not recognize (→ HTTP 401); any other error is treated as a validator fault (→ HTTP 500). Built-in: StaticBearerValidator. JWT/JWKS, Google IAM, and OAuth2 introspection validators are v0.3 (docs/a2a-design.md "Auth model") — the interface ships now as the extension point.