middleware

package
v0.0.0-...-cb1ba11 Latest Latest
Warning

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

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

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func APIKeyAuth

func APIKeyAuth(config AuthConfig) gin.HandlerFunc

APIKeyAuth enforces API key authentication via header or bearer token.

func APIKeyUnaryInterceptor

func APIKeyUnaryInterceptor(apiKey string) grpc.UnaryServerInterceptor

APIKeyUnaryInterceptor enforces API key authentication on gRPC calls.

func AdminTokenAuth

func AdminTokenAuth(adminToken string) gin.HandlerFunc

AdminTokenAuth enforces a separate admin token for admin routes. If adminToken is empty, the middleware is a no-op (falls back to global API key auth). Admin tokens must be sent via the X-Admin-Token header only (not Bearer) to avoid collision with the API key Bearer token namespace.

func ConnectorCapabilityCheck

func ConnectorCapabilityCheck(capName string, capabilities map[string]config.ConnectorCapability) gin.HandlerFunc

ConnectorCapabilityCheck enforces that a specific capability is enabled for the connector token and respects read_only mode by rejecting write HTTP methods. This is the CP-side security boundary — even if the connector is compromised, requests for disabled or read-only capabilities are rejected here.

func ConnectorTokenAuth

func ConnectorTokenAuth(connectorToken string) gin.HandlerFunc

ConnectorTokenAuth enforces the connector token for connector routes. It validates the X-Connector-Token header and injects audit correlation metadata (X-Command-ID, X-Command-Source) into the gin context.

func DIDAuthMiddleware

func DIDAuthMiddleware(didService DIDWebServiceInterface, config DIDAuthConfig) gin.HandlerFunc

DIDAuthMiddleware creates a gin middleware that verifies DID-based authentication.

The middleware extracts X-Caller-DID, X-DID-Signature, and X-DID-Timestamp headers from incoming requests. If a caller DID is present, it verifies the signature against the caller's DID document public key.

Authentication flow:

  1. If no X-Caller-DID header is present, the request proceeds without DID auth
  2. If X-Caller-DID is present, X-DID-Signature and X-DID-Timestamp are required
  3. The timestamp must be within the configured time window (default: 5 minutes)
  4. The signature is verified against: timestamp + ":" + SHA256(body)
  5. Replay protection rejects signatures seen within the timestamp window
  6. On successful verification, the verified DID is stored in the gin context

This middleware should be applied AFTER API key authentication and BEFORE routes that need to know the caller's identity.

func GetCallerAgentID

func GetCallerAgentID(c *gin.Context) string

GetCallerAgentID extracts the caller agent ID from the gin context.

func GetTargetAgent

func GetTargetAgent(c *gin.Context) *types.AgentNode

GetTargetAgent extracts the resolved target agent from the gin context.

func GetTargetDID

func GetTargetDID(c *gin.Context) string

GetTargetDID extracts the target DID from the gin context.

func GetVerifiedCallerDID

func GetVerifiedCallerDID(c *gin.Context) string

GetVerifiedCallerDID extracts the verified caller DID from the gin context. Returns empty string if no verified DID is present.

func GinLogger

func GinLogger() gin.HandlerFunc

GinLogger replaces gin's default stdout logger. It emits one structured log line per request through the control-plane's zerolog logger, at a severity derived from the response status (see requestLogEvent). This keeps the default output free of the duplicated, overly verbose [GIN] lines that gin.Default() writes to stdout, without hiding failing requests.

func IsDIDAuthSkipped

func IsDIDAuthSkipped(c *gin.Context) bool

IsDIDAuthSkipped returns true if DID authentication was skipped for this request.

func MemoryEventFilter

func MemoryEventFilter(c *gin.Context) func(context.Context, *types.MemoryChangeEvent) bool

MemoryEventFilter limits wildcard streams to owned or participated scopes. Only positive participation decisions are cached, bounded per connection. A nil filter means authorization is not installed and all events are allowed.

func MemoryEventScopeFilter

func MemoryEventScopeFilter(c *gin.Context) func(context.Context, *types.MemoryChangeEvent) bool

MemoryEventScopeFilter rechecks explicit stream selectors before each event. Explicit selectors retain read/bearer semantics, without participation checks.

func MemoryEventSnapshotFilter

func MemoryEventSnapshotFilter(c *gin.Context) func(context.Context, *types.MemoryChangeEvent) bool

MemoryEventSnapshotFilter caches participation decisions, including denials, only for this history request. Its lookups are bounded by distinct scopes in the snapshot, so it needs neither a token bucket nor cache expiration.

func MemoryPermissionMiddleware

func MemoryPermissionMiddleware(
	policyService AccessPolicyServiceInterface,
	agentResolver AgentResolverInterface,
	didResolver DIDResolverInterface,
	config MemoryPermissionConfig,
) gin.HandlerFunc

MemoryPermissionMiddleware resolves identity and evaluates memory tag policies. Handlers authorize the actual resolved scope before accessing storage. This middleware should be applied AFTER DIDAuthMiddleware.

func PermissionCheckMiddleware

func PermissionCheckMiddleware(
	policyService AccessPolicyServiceInterface,
	tagVCVerifier TagVCVerifierInterface,
	agentResolver AgentResolverInterface,
	didResolver DIDResolverInterface,
	config PermissionConfig,
) gin.HandlerFunc

PermissionCheckMiddleware creates a middleware that checks permissions before allowing requests to protected agents.

This middleware should be applied AFTER DIDAuthMiddleware so that the verified caller DID is available in the context.

The middleware:

  1. Extracts the verified caller DID from context (set by DIDAuthMiddleware)
  2. Resolves the target agent from the request path
  3. Evaluates access policies based on caller/target tags
  4. If a policy denies access, returns 403 Forbidden
  5. If no policy matches and DefaultDeny is false, allows the request (backward compat for untagged agents). When DefaultDeny is true, returns HTTP 403 with an opaque error. The unmatched (caller_tags, target_tags, function) tuple is logged at DEBUG in both modes; it is not included in the response body to avoid leaking tag taxonomy to denied callers.

func PrivilegedAccess

func PrivilegedAccess(config AuthConfig) gin.HandlerFunc

PrivilegedAccess guards the control-plane endpoints that can execute arbitrary code (package install/update/uninstall) or read and write credentials (the encrypted secret store and agent .env files).

These routes would otherwise inherit the global posture, where an empty API key disables authentication entirely. Combined with the wildcard bind in Start(), that leaves anything on the same network able to make the control plane clone and build an arbitrary repository, or overwrite the credentials an installed agent runs with.

The rule:

API key configured -> the key is required, like any other protected route.
No API key         -> only loopback callers may use these endpoints, so a
                      local CLI or desktop app keeps working untouched
                      while remote callers are told to configure a key.

The peer address is read from Request.RemoteAddr rather than c.ClientIP(): gin trusts every proxy by default, so ClientIP() honours a caller-supplied X-Forwarded-For header and a remote host could simply claim to be 127.0.0.1.

The corollary is that a reverse proxy sharing a host with the control plane makes every forwarded request look local, and the loopback rule then protects nothing. Such a deployment has to configure an API key; the docs say so.

func RateLimit

func RateLimit(store *RateLimiterStore) gin.HandlerFunc

RateLimit returns a Gin middleware that enforces per-key rate limiting. The key is derived from the API key (if authenticated) or the client IP. When the limit is exceeded, the middleware responds with HTTP 429 and a Retry-After header.

func RequireMemoryScope

func RequireMemoryScope(c *gin.Context, scope, scopeID string) bool

RequireMemoryScope writes a 403 and aborts on denial; handlers must return when false.

func WriteMemoryAccessDenial

func WriteMemoryAccessDenial(c *gin.Context, denial *MemoryAccessDenial)

WriteMemoryAccessDenial returns a denial before any storage or stream access.

Types

type AccessPolicyServiceInterface

type AccessPolicyServiceInterface interface {
	EvaluateAccess(callerTags, targetTags []string, functionName string, inputParams map[string]any) *types.PolicyEvaluationResult
}

AccessPolicyServiceInterface defines the methods required for tag-based policy evaluation.

type AgentResolverInterface

type AgentResolverInterface interface {
	GetAgent(ctx context.Context, agentID string) (*types.AgentNode, error)
}

AgentResolverInterface provides methods for resolving agent information.

type AuthConfig

type AuthConfig struct {
	APIKey                  string
	SkipPaths               []string
	SkipPrefixes            []string
	QueryAPIKeyAllowedPaths []string
}

AuthConfig mirrors server configuration for HTTP authentication.

type ContextKey

type ContextKey string

ContextKey is the type for context keys used by this middleware.

const (
	// VerifiedCallerDIDKey is the context key for the verified caller DID.
	VerifiedCallerDIDKey ContextKey = "verified_caller_did"
	// DIDAuthSkippedKey is set when DID auth was skipped (no DID claimed).
	DIDAuthSkippedKey ContextKey = "did_auth_skipped"
)
const (
	// CallerAgentIDKey is the context key for storing the resolved caller agent ID.
	CallerAgentIDKey ContextKey = "caller_agent_id"
	// MemoryPermissionResultKey is the context key for storing memory permission check results.
	MemoryPermissionResultKey ContextKey = "memory_permission_result"
)
const (
	// PermissionCheckResultKey is the context key for storing permission check results.
	PermissionCheckResultKey ContextKey = "permission_check_result"
	// TargetAgentKey is the context key for storing the resolved target agent.
	TargetAgentKey ContextKey = "target_agent"
	// TargetDIDKey is the context key for storing the target agent's DID.
	TargetDIDKey ContextKey = "target_did"
)

type DIDAuthConfig

type DIDAuthConfig struct {
	// Enabled determines if DID authentication is active
	Enabled bool
	// TimestampWindowSeconds is the allowed time drift for signature timestamps (default: 300)
	TimestampWindowSeconds int64
	// SkipPaths are paths that bypass DID authentication
	SkipPaths []string
}

DIDAuthConfig holds configuration for DID authentication middleware.

type DIDResolverInterface

type DIDResolverInterface interface {
	GenerateDIDWeb(agentID string) string
	// ResolveAgentIDByDID looks up the agent ID associated with a DID.
	// Returns empty string if the DID cannot be resolved.
	ResolveAgentIDByDID(ctx context.Context, did string) string
}

DIDResolverInterface provides methods for resolving agent DIDs.

type DIDWebServiceInterface

type DIDWebServiceInterface interface {
	VerifyDIDOwnership(ctx context.Context, did string, message []byte, signature []byte) (bool, error)
}

DIDWebServiceInterface defines the methods required for DID verification. This interface allows the middleware to work with any DID service implementation.

type MemoryAccessDenial

type MemoryAccessDenial struct {
	Error   string `json:"error"`
	Message string `json:"message"`
}

MemoryAccessDenial describes an authorization failure without writing a response. Hierarchical reads and wildcard events can skip denied scopes quietly.

func AuthorizeMemoryScope

func AuthorizeMemoryScope(c *gin.Context, scope, scopeID string) *MemoryAccessDenial

AuthorizeMemoryScope checks the scope actually selected by a handler. Absence of middleware intentionally preserves the legacy behavior when auth is off.

type MemoryAgentLister

type MemoryAgentLister interface {
	ListAgents(context.Context, types.AgentFilters) ([]*types.AgentNode, error)
}

MemoryAgentLister supplies all registered nodes, including inactive/offline nodes. It is optional on AgentResolverInterface; without it actor checks fail closed.

type MemoryExecutionReader

type MemoryExecutionReader interface {
	QueryExecutionRecords(context.Context, types.ExecutionFilter) ([]*types.Execution, error)
}

MemoryExecutionReader is the minimal storage contract for event participation.

type MemoryPermissionConfig

type MemoryPermissionConfig struct {
	// Enabled determines if memory permission checking is active
	Enabled bool
	// EnforceScopeOwnership determines if scope ownership validation is enforced
	EnforceScopeOwnership bool
	// ExecutionReader supplies participation checks for wildcard memory events.
	ExecutionReader MemoryExecutionReader
}

MemoryPermissionConfig holds configuration for memory permission checking.

type MemoryPermissionResult

type MemoryPermissionResult struct {
	Allowed  bool
	Reason   string
	CallerID string
}

MemoryPermissionResult contains the result of a memory permission check.

func GetMemoryPermissionResult

func GetMemoryPermissionResult(c *gin.Context) *MemoryPermissionResult

GetMemoryPermissionResult extracts the memory permission result from the gin context.

type PermissionCheckResult

type PermissionCheckResult struct {
	Allowed            bool
	RequiresPermission bool
	Error              error
}

PermissionCheckResult contains the result of a permission check.

func GetPermissionCheckResult

func GetPermissionCheckResult(c *gin.Context) *PermissionCheckResult

GetPermissionCheckResult extracts the permission check result from the gin context.

type PermissionConfig

type PermissionConfig struct {
	// Enabled determines if permission checking is active
	Enabled bool
	// DefaultDeny returns 403 instead of allowing fall-through when no
	// access policy matches. Default false preserves backward compat.
	DefaultDeny bool
}

PermissionConfig holds configuration for permission checking.

type RateLimitConfig

type RateLimitConfig struct {
	// RequestsPerSecond is the sustained rate of allowed requests per key.
	RequestsPerSecond float64 `yaml:"requests_per_second" mapstructure:"requests_per_second"`
	// BurstSize is the maximum burst of requests allowed above the sustained rate.
	BurstSize int `yaml:"burst_size" mapstructure:"burst_size"`
	// Enabled controls whether rate limiting is active. Defaults to true when
	// any rate limit config is present.
	Enabled *bool `yaml:"enabled,omitempty" mapstructure:"enabled"`
}

RateLimitConfig defines per-endpoint rate limiting parameters.

func (RateLimitConfig) IsEnabled

func (c RateLimitConfig) IsEnabled() bool

IsEnabled returns whether this rate limit config is active.

type RateLimiterStore

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

RateLimiterStore manages per-key rate limiters with idle eviction.

func NewRateLimiterStore

func NewRateLimiterStore(rps float64, burst int, ttl time.Duration) *RateLimiterStore

NewRateLimiterStore creates a store for per-key rate limiters. Keys idle for longer than ttl are evicted by a background goroutine.

func (*RateLimiterStore) Allow

func (s *RateLimiterStore) Allow(key string) bool

Allow checks whether the given key is within its rate limit.

func (*RateLimiterStore) Len

func (s *RateLimiterStore) Len() int

Len returns the number of tracked keys (for testing/metrics).

func (*RateLimiterStore) Stop

func (s *RateLimiterStore) Stop()

Stop halts the background eviction goroutine.

type RateLimitsConfig

type RateLimitsConfig struct {
	Execute    RateLimitConfig `yaml:"execute" mapstructure:"execute"`
	Discovery  RateLimitConfig `yaml:"discovery" mapstructure:"discovery"`
	BulkStatus RateLimitConfig `yaml:"bulk_status" mapstructure:"bulk_status"`
	Global     RateLimitConfig `yaml:"global" mapstructure:"global"`
}

RateLimitsConfig holds rate limit settings for different endpoint groups.

func DefaultRateLimitConfig

func DefaultRateLimitConfig() RateLimitsConfig

DefaultRateLimitConfig returns sensible defaults for production use. Execute: 50 req/s burst 100 (per key) — generous for normal agent traffic. Discovery: 20 req/s burst 40 — read-heavy but cacheable. BulkStatus: 30 req/s burst 60 — moderate fan-out. Global: 200 req/s burst 400 — per-IP fallback for unauthenticated paths.

type TagVCVerifierInterface

type TagVCVerifierInterface interface {
	VerifyAgentTagVC(ctx context.Context, agentID string) (*types.AgentTagVCDocument, error)
}

TagVCVerifierInterface defines the methods required for verifying Agent Tag VCs.

Jump to

Keyboard shortcuts

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