Documentation
¶
Index ¶
- Constants
- func SetNodeBan(ctx context.Context, store storage.Store, node *storage.EnrolledNode, ...) error
- func ValidatePolicyConfig(req *api.PolicyConfigUpdateRequest) error
- type MeshAdapter
- type NodeStatus
- type NopMeshAdapter
- func (n *NopMeshAdapter) Close() error
- func (n *NopMeshAdapter) DiscoverServices(ctx context.Context, serviceType string) ([]*ServiceAnnouncement, error)
- func (n *NopMeshAdapter) GetNodeStatus(ctx context.Context, peerID string) (*NodeStatus, error)
- func (n *NopMeshAdapter) PublishEvent(ctx context.Context, eventType api.MeshEvent_Type, peerID string, ...) error
- type Options
- type P2PMeshAdapter
- func (p *P2PMeshAdapter) Close() error
- func (p *P2PMeshAdapter) DiscoverServices(ctx context.Context, serviceType string) ([]*ServiceAnnouncement, error)
- func (p *P2PMeshAdapter) GetNodeStatus(ctx context.Context, peerID string) (*NodeStatus, error)
- func (p *P2PMeshAdapter) PublishEvent(ctx context.Context, eventType api.MeshEvent_Type, peerID string, ...) error
- type Server
- func (s *Server) Addr() string
- func (s *Server) Close() error
- func (s *Server) HandleAdminBootstrapTokenAction(w http.ResponseWriter, r *http.Request)
- func (s *Server) HandleAdminBootstrapTokens(w http.ResponseWriter, r *http.Request)
- func (s *Server) HandleAdminEnrollmentAction(w http.ResponseWriter, r *http.Request)
- func (s *Server) HandleAdminEnrollments(w http.ResponseWriter, r *http.Request)
- func (s *Server) HandleAdminNodeAction(w http.ResponseWriter, r *http.Request)
- func (s *Server) HandleAdminRevoke(w http.ResponseWriter, r *http.Request)
- func (s *Server) HandleAdminStatus(w http.ResponseWriter, r *http.Request)
- func (s *Server) HandleEnroll(w http.ResponseWriter, r *http.Request)
- func (s *Server) HandleEnrollStatus(w http.ResponseWriter, r *http.Request)
- func (s *Server) HandleHealthz(w http.ResponseWriter, r *http.Request)
- func (s *Server) HandleInfo(w http.ResponseWriter, r *http.Request)
- func (s *Server) HandleKeys(w http.ResponseWriter, r *http.Request)
- func (s *Server) HandleNodeCatalog(w http.ResponseWriter, r *http.Request)
- func (s *Server) HandlePolicies(w http.ResponseWriter, r *http.Request)
- func (s *Server) HandleReadyz(w http.ResponseWriter, r *http.Request)
- func (s *Server) HandleRefresh(w http.ResponseWriter, r *http.Request)
- func (s *Server) HandleRegister(w http.ResponseWriter, r *http.Request)
- func (s *Server) HandleRouterLease(w http.ResponseWriter, r *http.Request)
- func (s *Server) HandleUserBootstrapTokens(w http.ResponseWriter, r *http.Request)
- func (s *Server) HandleUserRevoke(w http.ResponseWriter, r *http.Request)
- func (s *Server) HandleUserStatus(w http.ResponseWriter, r *http.Request)
- func (s *Server) Init() error
- func (s *Server) RegisterRoutes(mux *http.ServeMux)
- func (s *Server) SetMeshAdapter(m MeshAdapter)
- func (s *Server) Start() error
- type ServiceAnnouncement
Constants ¶
const ( EnrollRateLimit = 10 EnrollBurst = 20 JWTVerificationTimeout = 10 * time.Second )
const DefaultMeshReconnectInterval = 30 * time.Second
DefaultMeshReconnectInterval is how often the publisher re-reads the lease table; a router that just enrolled waits at most this long for events. It is one query and at most a few dials per tick, so it is kept short.
const ( // DefaultNodeRetention keeps a lapsed enrollment for a month past its // session, long enough to be looked up when investigating an incident, // and is what --node-retention defaults to. DefaultNodeRetention = 30 * 24 * time.Hour )
const RouterDialTimeout = 10 * time.Second
RouterDialTimeout bounds each attempt to reach a leased router.
Variables ¶
This section is empty.
Functions ¶
func SetNodeBan ¶
func SetNodeBan(ctx context.Context, store storage.Store, node *storage.EnrolledNode, banned bool) error
SetNodeBan bans or unbans a node and, when its record carries OIDC claims, the identity behind it. The one place both halves are toggled together, for the HTTP handlers and the CLI alike: a ban that names only the peer id is shed with a new keypair, and an unban that lifts only the peer id leaves the human locked out of /register.
func ValidatePolicyConfig ¶
func ValidatePolicyConfig(req *api.PolicyConfigUpdateRequest) error
ValidatePolicyConfig checks a mesh policy the way POST /policies does; any other writer of the policy (e.g. a seed file) must run it too.
Types ¶
type MeshAdapter ¶
type MeshAdapter interface {
// PublishEvent constructs, signs, and broadcasts a Control Plane MeshEvent (POLICY_UPDATE, BANNED, KEY_ROTATION).
PublishEvent(ctx context.Context, eventType api.MeshEvent_Type, peerID string, payload []byte) error
// DiscoverServices queries active mesh nodes/DHT for services matching a type or pattern.
DiscoverServices(ctx context.Context, serviceType string) ([]*ServiceAnnouncement, error)
// GetNodeStatus retrieves node reachability and status from the mesh.
GetNodeStatus(ctx context.Context, peerID string) (*NodeStatus, error)
// Close gracefully releases any P2P host resources, streams, and PubSub topics.
Close() error
}
MeshAdapter defines the generic interface for Control Plane operations interacting with the Sovereign Agent Mesh.
type NodeStatus ¶
NodeStatus represents mesh status information for a peer.
type NopMeshAdapter ¶
type NopMeshAdapter struct{}
NopMeshAdapter provides a no-op implementation used when P2P mesh integration is disabled or in unit tests.
func NewNopMeshAdapter ¶
func NewNopMeshAdapter() *NopMeshAdapter
func (*NopMeshAdapter) Close ¶
func (n *NopMeshAdapter) Close() error
func (*NopMeshAdapter) DiscoverServices ¶
func (n *NopMeshAdapter) DiscoverServices(ctx context.Context, serviceType string) ([]*ServiceAnnouncement, error)
func (*NopMeshAdapter) GetNodeStatus ¶
func (n *NopMeshAdapter) GetNodeStatus(ctx context.Context, peerID string) (*NodeStatus, error)
func (*NopMeshAdapter) PublishEvent ¶
func (n *NopMeshAdapter) PublishEvent(ctx context.Context, eventType api.MeshEvent_Type, peerID string, payload []byte) error
type Options ¶
type Options struct {
ListenAddr string
DriverName string
DataSourceName string
OIDCIssuer string
OIDCClientID string // OAuth client id advertised via /info; defaults to the first allowed audience
AllowedAudiences []string
LeaseDuration time.Duration
KeyRotationInterval time.Duration
KeyGracePeriod time.Duration
InsecureSkipTLSVerify bool
BiscuitTimeout time.Duration
BiscuitTTL time.Duration // Lifespan minted into every issued Biscuit's expiration() fact; defaults to api.BiscuitTokenTTL
OIDCSessionTTL time.Duration // How long an OIDC enrollment stays refreshable before the identity must re-authenticate interactively; defaults to api.OIDCSessionTTL
// NodeRetention is how long an enrolled node's row is kept after its
// session expired before being deleted; 0 keeps rows forever. Every
// pod restart without a persistent data dir enrolls a fresh identity,
// so without this the nodes table only ever grows.
NodeRetention time.Duration
AdminToken string // Optional: administrative bearer token for protecting policy and enrollment queue REST APIs
AutoApproveEnrollment bool // If true, valid bootstrap token enrollment requests are immediately approved without administrative manual gate
}
Options holds configuration for the control plane.
type P2PMeshAdapter ¶
type P2PMeshAdapter struct {
// contains filtered or unexported fields
}
P2PMeshAdapter publishes control plane events on the mesh's gossip topic.
It is the control plane's whole presence on the mesh, and it is one-way: events go out so a ban, a key rotation or a policy change reaches routers and nodes the moment it happens, and nothing is read back. Every consumer still pulls /keys, /info and /policies on its own schedule, so a missed event is a delay, never a divergence. Where the adapter runs on a host of its own (NewMeshPublisher) that host has no listen address, no DHT, no relay and no stream handlers: it can dial routers and nothing can dial it.
func NewMeshPublisher ¶
func NewMeshPublisher(ctx context.Context, store storage.Store, reconnect time.Duration) (*P2PMeshAdapter, error)
NewMeshPublisher builds the control plane's own publish-only peer and keeps it connected to every router holding a lease, re-checking the lease table every reconnect. The lease table is all the control plane needs to know about the mesh's shape, and it already has it. Close stops the loop and the host.
func NewP2PMeshAdapter ¶
func NewP2PMeshAdapter(h host.Host, topic *pubsub.Topic, store storage.Store) (*P2PMeshAdapter, error)
NewP2PMeshAdapter publishes on an existing host's topic. The caller owns both and closes them; Close on the adapter is a no-op.
func (*P2PMeshAdapter) Close ¶
func (p *P2PMeshAdapter) Close() error
func (*P2PMeshAdapter) DiscoverServices ¶
func (p *P2PMeshAdapter) DiscoverServices(ctx context.Context, serviceType string) ([]*ServiceAnnouncement, error)
func (*P2PMeshAdapter) GetNodeStatus ¶
func (p *P2PMeshAdapter) GetNodeStatus(ctx context.Context, peerID string) (*NodeStatus, error)
func (*P2PMeshAdapter) PublishEvent ¶
func (p *P2PMeshAdapter) PublishEvent(ctx context.Context, eventType api.MeshEvent_Type, peerID string, payload []byte) error
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server implements the SAM Control Plane web app.
func (*Server) HandleAdminBootstrapTokenAction ¶
func (s *Server) HandleAdminBootstrapTokenAction(w http.ResponseWriter, r *http.Request)
HandleAdminBootstrapTokenAction HTTP DELETE `/admin/bootstrap-tokens/{id}` soft-revokes a token (see storage.BootstrapToken.RevokedAt): idempotent, and 404 only when the id names no token at all, per #368.
func (*Server) HandleAdminBootstrapTokens ¶
func (s *Server) HandleAdminBootstrapTokens(w http.ResponseWriter, r *http.Request)
HandleAdminBootstrapTokens HTTP POST/GET `/admin/bootstrap-tokens`
func (*Server) HandleAdminEnrollmentAction ¶
func (s *Server) HandleAdminEnrollmentAction(w http.ResponseWriter, r *http.Request)
HandleAdminEnrollmentAction HTTP POST `/admin/enrollments/{id}/approve` or `/admin/enrollments/{id}/reject`
func (*Server) HandleAdminEnrollments ¶
func (s *Server) HandleAdminEnrollments(w http.ResponseWriter, r *http.Request)
HandleAdminEnrollments HTTP GET `/admin/enrollments`
func (*Server) HandleAdminNodeAction ¶
func (s *Server) HandleAdminNodeAction(w http.ResponseWriter, r *http.Request)
HandleAdminNodeAction HTTP POST `/admin/nodes/{peer_id}/autonomous-recovery` with body {"enabled": bool} toggles storage.EnrolledNode.AutonomousRecovery for one enrolled node. This is the per-node counterpart of the flag on a bootstrap token, for a node that is already enrolled.
func (*Server) HandleAdminRevoke ¶
func (s *Server) HandleAdminRevoke(w http.ResponseWriter, r *http.Request)
HandleAdminRevoke HTTP POST `/admin/revoke`
func (*Server) HandleAdminStatus ¶
func (s *Server) HandleAdminStatus(w http.ResponseWriter, r *http.Request)
HandleAdminStatus returns a consolidated JSON state of the control plane.
func (*Server) HandleEnroll ¶
func (s *Server) HandleEnroll(w http.ResponseWriter, r *http.Request)
HandleEnroll HTTP POST `/enroll`
func (*Server) HandleEnrollStatus ¶
func (s *Server) HandleEnrollStatus(w http.ResponseWriter, r *http.Request)
HandleEnrollStatus HTTP GET `/enroll/status`
The approved response carries the enrollee's Biscuit, so polling requires proof of possession of the key submitted at /enroll: the api.HeaderChallengeTimestamp header (unix milliseconds) and the api.HeaderChallengeSignature header (unpadded base64url signature over api.EnrollStatusChallenge) must accompany `peer_id`. Every failure mode after the header parse answers a uniform 401 so the endpoint is not a peer-ID existence oracle for anonymous callers.
func (*Server) HandleHealthz ¶
func (s *Server) HandleHealthz(w http.ResponseWriter, r *http.Request)
HandleHealthz HTTP GET `/healthz`
func (*Server) HandleInfo ¶
func (s *Server) HandleInfo(w http.ResponseWriter, r *http.Request)
HandleInfo HTTP GET `/info`
func (*Server) HandleKeys ¶
func (s *Server) HandleKeys(w http.ResponseWriter, r *http.Request)
HandleKeys HTTP GET `/keys`
func (*Server) HandleNodeCatalog ¶
func (s *Server) HandleNodeCatalog(w http.ResponseWriter, r *http.Request)
HandleNodeCatalog HTTP POST /nodes/catalog - a node self-reports the services it currently has registered locally (the same data list_local_services already answers on the node itself), so the control plane can show mesh-wide service topology without needing to be a DHT participant or open a P2P connection to every enrolled node itself.
The body is an api.NodeCatalogReport. The reporting peer is the one bound in the presented Biscuit, so a node can only ever describe itself.
This is a live-status cache, not authoritative state: a node that goes offline without ever reporting an empty catalog just leaves its last report in place until ReportedAt visibly goes stale or its enrollment ends. It is admin-facing display data only and never feeds authorization, which is also why a bare bearer Biscuit (no signed challenge, unlike /refresh) is accepted here: a replayed token can only repaint a table.
func (*Server) HandlePolicies ¶
func (s *Server) HandlePolicies(w http.ResponseWriter, r *http.Request)
HandlePolicies HTTP GET/POST/PUT `/policies`
func (*Server) HandleReadyz ¶
func (s *Server) HandleReadyz(w http.ResponseWriter, r *http.Request)
HandleReadyz HTTP GET `/readyz`
func (*Server) HandleRefresh ¶
func (s *Server) HandleRefresh(w http.ResponseWriter, r *http.Request)
HandleRefresh HTTP POST `/refresh`
func (*Server) HandleRegister ¶
func (s *Server) HandleRegister(w http.ResponseWriter, r *http.Request)
HandleRegister HTTP POST `/register`
func (*Server) HandleRouterLease ¶
func (s *Server) HandleRouterLease(w http.ResponseWriter, r *http.Request)
HandleRouterLease HTTP POST `/routers/lease`
func (*Server) HandleUserBootstrapTokens ¶
func (s *Server) HandleUserBootstrapTokens(w http.ResponseWriter, r *http.Request)
func (*Server) HandleUserRevoke ¶
func (s *Server) HandleUserRevoke(w http.ResponseWriter, r *http.Request)
func (*Server) HandleUserStatus ¶
func (s *Server) HandleUserStatus(w http.ResponseWriter, r *http.Request)
func (*Server) Init ¶
Init prepares the control plane without binding a listener: it bootstraps the signing keyring, discovers OIDC providers and starts the key-rotation loop. Embedders that own their own listener call Init + RegisterRoutes instead of Start.
func (*Server) RegisterRoutes ¶
RegisterRoutes registers every control-plane HTTP handler on mux. Probes and /metrics are left uncounted so scrapers do not dominate the figures.
func (*Server) SetMeshAdapter ¶
func (s *Server) SetMeshAdapter(m MeshAdapter)
SetMeshAdapter sets a custom MeshAdapter implementation for the control plane.