api

package
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Aug 4, 2026 License: Apache-2.0 Imports: 24 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// Contract types are emitted by the control plane per the OpenAPI events
	// document. Their payloads are treated as opaque: the reconciler pull is
	// authoritative, so each of these events triggers a reconcile.
	EventNodeStateUpdated    = "node_state_updated"
	EventPolicyUpdated       = "policy_updated"
	EventBridgeConfigUpdated = "bridge_config_updated"

	// EventActionRequest exists as a push-latency optimisation: action
	// dispatches are delivered in the executions block of the state pull, so the
	// event's payload is opaque and it triggers a reconcile like the rest of the
	// tier — the resulting pull carries the dispatch.
	EventActionRequest = "action_request"

	// EventSessionSetup is the same push-latency optimisation for mediated
	// access: sessions are delivered in the sessions block of the state pull, so
	// the event's payload is opaque and it triggers a reconcile like the rest of
	// the tier — the resulting pull carries the session.
	EventSessionSetup = "session_setup"

	// Documented-coming family: types that land with the platform's 14-type
	// taxonomy. They are already named so the agent can subscribe to them once
	// the control plane starts emitting them; every one triggers a reconcile.
	EventPeerRegistered      = "peer_registered"
	EventPeerPSKAssigned     = "peer_psk_assigned"
	EventPeerDeregistered    = "peer_deregistered"
	EventPeerEndpointChanged = "peer_endpoint_changed"
	EventRotateKeys          = "rotate_keys"
	EventPeerKeyRotated      = "peer_key_rotated"
	EventSigningKeyRotated   = "signing_key_rotated"

	// EventSessionRevoked carries no teardown of its own: a session leaving the
	// pull's sessions block is what closes it, so this event only pulls the
	// observing reconcile forward.
	EventSessionRevoked = "session_revoked"
)
View Source
const (
	// ReachabilityHealthy marks a node whose heartbeats arrive on schedule.
	ReachabilityHealthy = "healthy"
	// ReachabilityStale marks a node whose last admitted heartbeat is older than
	// the control plane's freshness window.
	ReachabilityStale = "stale"
	// ReachabilityUnreachable marks a node the control plane has given up on.
	ReachabilityUnreachable = "unreachable"
	// ReachabilityNeverReported marks a node whose first heartbeat was never
	// admitted. It is the verdict a freshly enrolled node is born with.
	ReachabilityNeverReported = "never_reported"
)

Reachability verdicts the platform contract documents today. The set is open: these are the values plexd expects to see, not the values it accepts.

View Source
const (
	// ActionKindBuiltin dispatches an action built into plexd.
	ActionKindBuiltin = "builtin"
	// ActionKindHook dispatches a hook registered by the node.
	ActionKindHook = "hook"
)

The two legal values of a NodeStateExecution.Type.

View Source
const (
	// ExecutionStatusAck acknowledges receipt of the action request.
	ExecutionStatusAck = "ack"
	// ExecutionStatusStarted reports that the action has begun running.
	ExecutionStatusStarted = "started"
	// ExecutionStatusSucceeded is the terminal callback for a successful run.
	ExecutionStatusSucceeded = "succeeded"
	// ExecutionStatusFailed is the terminal callback for a failed run.
	ExecutionStatusFailed = "failed"
	// ExecutionStatusCancelled is the terminal callback for a cancelled run.
	ExecutionStatusCancelled = "cancelled"
)

Execution callback statuses a node reports on the v1 execution callback (POST /v1/nodes/{node_id}/executions/{execution_id}).

View Source
const (
	// CodeNSKNodeMismatch (403) means the callback's node id does not match the
	// node identified by the NSK bearer credential.
	CodeNSKNodeMismatch = "nsk_node_mismatch"
	// CodeInvalidStateTransition (409) means the callback would advance the
	// invocation along an illegal edge of the execution state machine.
	CodeInvalidStateTransition = "invalid_state_transition"
	// CodeExecutionAlreadyTerminal (409) means the invocation has already
	// settled and accepts no further callbacks.
	CodeExecutionAlreadyTerminal = "execution_already_terminal"
)

RFC 9457 problem codes with which the control plane refuses an execution callback. A refusal carrying one of these is deliberate and permanent: the node must stop driving the execution instead of retrying or double-reporting.

View Source
const (
	// SessionKindSSH mediates an SSH session; Target.SSH is set.
	SessionKindSSH = "ssh"
	// SessionKindK8s mediates a Kubernetes API session; Target.K8s is set.
	SessionKindK8s = "k8s"
	// SessionKindTCP mediates a plain TCP forward; Target.TCP is set.
	SessionKindTCP = "tcp"
)

The three legal values of a NodeStateSession.Kind.

View Source
const (
	MetricGroupNodeResources = "node_resources"
	MetricGroupTunnelHealth  = "tunnel_health"
	MetricGroupPeerLatency   = "peer_latency"
	MetricGroupAgentStats    = "agent_stats"
)

Wire metric groups (closed set; out-of-set → 400 ingest_batch_malformed).

View Source
const (
	// TCPPhaseSessionStarted marks the opening of a TCP session.
	TCPPhaseSessionStarted = "session_started"
	// TCPPhaseSessionEnded marks the close of a TCP session.
	TCPPhaseSessionEnded = "session_ended"
)

TCP session lifecycle phases reported on a TCPActivity.

View Source
const (
	// TerminatedByTTLExpired means the session reached its time-to-live.
	TerminatedByTTLExpired = "ttl_expired"
	// TerminatedByIdleTimeout means the session was idle past its timeout.
	TerminatedByIdleTimeout = "idle_timeout"
	// TerminatedByPlexdClose means plexd closed the session locally.
	TerminatedByPlexdClose = "plexd_close"
	// TerminatedByOperatorRevoke means the operator's access was revoked.
	TerminatedByOperatorRevoke = "operator_revoke"
)

Reasons a TCP session was terminated, reported on a session_ended TCPActivity.

View Source
const DefaultConnectTimeout = 10 * time.Second

DefaultConnectTimeout is the default TCP connect timeout.

View Source
const DefaultRequestTimeout = 30 * time.Second

DefaultRequestTimeout is the default HTTP request timeout.

View Source
const DefaultSSEIdleTimeout = 90 * time.Second

DefaultSSEIdleTimeout is the default SSE idle timeout.

View Source
const DefaultSSEReprobeInterval = 10 * time.Minute

DefaultSSEReprobeInterval is how often pull-only delivery mode re-probes the descoped SSE endpoint to detect that it has come back.

View Source
const DefaultStalenessWindow = 5 * time.Minute

DefaultStalenessWindow is the maximum age of an event before it is considered stale.

View Source
const (
	// ExecutionStatusPending marks an execution the control plane has dispatched
	// and the node has not yet acknowledged. It appears in the executions block
	// of the node state snapshot and is never reported by a node on the
	// execution callback.
	ExecutionStatusPending = "pending"
)

Execution status observed only on the pull block.

View Source
const MaxIntegrityViolationsPerBatch = 128

MaxIntegrityViolationsPerBatch is the contract's per-request ceiling on the violations array. A larger batch is refused whole with 400 integrity_violations_too_many, and an empty one with integrity_violations_empty.

Variables

View Source
var (
	ErrBadRequest      = &APIError{StatusCode: 400, Message: "bad request"}
	ErrUnauthorized    = &APIError{StatusCode: 401, Message: "unauthorized"}
	ErrForbidden       = &APIError{StatusCode: 403, Message: "forbidden"}
	ErrNotFound        = &APIError{StatusCode: 404, Message: "not found"}
	ErrConflict        = &APIError{StatusCode: 409, Message: "conflict"}
	ErrPayloadTooLarge = &APIError{StatusCode: 413, Message: "payload too large"}
	ErrUnprocessable   = &APIError{StatusCode: 422, Message: "unprocessable entity"}
	ErrRateLimit       = &APIError{StatusCode: 429, Message: "rate limit exceeded"}
	ErrServer          = &APIError{StatusCode: 500, Message: "server error"}
)

Sentinel errors for common HTTP error status codes.

View Source
var (
	ErrIntegrityViolationsEmpty   = errors.New("integrity violation batch is empty")
	ErrIntegrityViolationsTooMany = fmt.Errorf("integrity violation batch exceeds %d entries", MaxIntegrityViolationsPerBatch)
)

ErrIntegrityViolationsEmpty and ErrIntegrityViolationsTooMany are returned by ReportIntegrityViolations before any HTTP request when the batch is outside the contract's 1..MaxIntegrityViolationsPerBatch bounds.

View Source
var ErrSSEIdleTimeout = errors.New("api: SSE idle timeout")

ErrSSEIdleTimeout is returned when the SSE stream receives no data within the configured idle timeout period.

View Source
var ErrSecretNameInvalid = fmt.Errorf("secret name is outside the grammar %s", secretNamePattern)

ErrSecretNameInvalid is returned by FetchSecret before any HTTP request when the requested secret name is outside the contract's name grammar.

Functions

func CanonicalBytes added in v0.2.0

func CanonicalBytes(env Envelope) ([]byte, error)

CanonicalBytes returns the canonical signing bytes for an envelope: a JSON object with exactly the fields id, type, scope, key_id, issued_at, payload in that order and no signature member. A nil Payload serializes as "payload":null. It is the single canonical-form seam shared between the agent and the mock control plane; the control plane's own helper is unpublished, so this shape is an author-approved assumption until the platform documents it.

func IsEventBusNotProvisioned added in v0.2.0

func IsEventBusNotProvisioned(err error) bool

IsEventBusNotProvisioned reports whether err is the control plane's long-term descope of the signed event stream for the node: a 501 carrying the signed_event_bus_not_provisioned problem code. Unlike a transient 5xx, this is a durable verdict — the endpoint stays unavailable until the node is re-provisioned — so it is answered by switching to pull-only delivery rather than by retrying against a channel that is not there.

func IsIngestNotProvisioned added in v0.2.0

func IsIngestNotProvisioned(err error) bool

IsIngestNotProvisioned reports whether err is the control plane's refusal to accept observability ingest because it is not provisioned for the node: a 501 carrying the observability_ingest_not_provisioned problem code.

func IsIngestPermanentlyRefused added in v0.2.0

func IsIngestPermanentlyRefused(err error) bool

IsIngestPermanentlyRefused reports whether err is a refusal of an observability ingest batch that no retry can fix: a 400 carrying the ingest_batch_malformed problem code, which is a verdict on the batch bytes themselves. Re-sending them would draw the identical status forever, so a PlatformReporter drops such a batch rather than returning it for re-buffering.

The sibling refusals are deliberately not permanent. A 400 ingest_sent_at_invalid faults the X-Plexsphere-Sent-At header, which is re-stamped from the wall clock on every attempt, so it clears once the node's clock converges. A 415 faults the Content-Encoding, a transport property of the deployment rather than of the batch, so it clears once the gateway that rejects gzip is fixed. Both are returned to the caller for re-buffering. A 413 is classified by IsIngestTooLarge and answered by splitting the batch, and a 501 not-provisioned refusal by IsIngestNotProvisioned.

func IsIngestTooLarge added in v0.2.0

func IsIngestTooLarge(err error) bool

IsIngestTooLarge reports whether err is the control plane's refusal of an observability ingest batch for exceeding its size limit: a 413. The batch content is acceptable, only its size is not, so the caller answers by splitting the batch and re-sending the halves rather than dropping it.

func RedactURLError added in v0.2.0

func RedactURLError(err error) error

RedactURLError strips the request URL from a *url.Error, keeping only the operation and the underlying cause. url.Error.Error() renders the full URL including its query string, and a presigned upload URL carries its credential there — logging such an error verbatim would publish a live write capability against the object it points at. Errors of any other type pass through.

Types

type APIError

type APIError struct {
	StatusCode int
	Message    string
	// Code carries the machine-readable code member of an RFC 9457
	// problem+json response. It is empty when the response carries no
	// code (or is not a problem+json body).
	Code string
	// CorrelationID carries the correlation_id member of an RFC 9457
	// problem+json response, falling back to the X-Correlation-Id header
	// of that same response. Both sources are read only from a response
	// that presents itself as a problem document, and that is the whole
	// of what the gate buys: a response an intermediary substituted for
	// the control plane's contributes no id. It does not authenticate the
	// header, so a tracing proxy that passes a genuine problem document
	// through and stamps its own X-Correlation-Id on it is read by the
	// fallback. It is empty when neither source carries a usable id.
	CorrelationID string
	RetryAfter    time.Duration // only set for 429
}

APIError is the base error type for HTTP API errors. It supports errors.Is matching by status code and errors.As extraction.

func (*APIError) Error

func (e *APIError) Error() string

Error returns the formatted error string. A non-empty CorrelationID is appended as a trailing " (correlation_id=...)" segment.

func (*APIError) Is

func (e *APIError) Is(target error) bool

Is supports errors.Is matching by status code. ErrServer (500) matches any 5xx status code. All other sentinels require an exact status code match.

type ActionInfo

type ActionInfo struct {
	Name        string        `json:"name"`
	Description string        `json:"description"`
	Parameters  []ActionParam `json:"parameters"`
}

type ActionParam

type ActionParam struct {
	Name        string `json:"name"`
	Type        string `json:"type"`
	Required    bool   `json:"required"`
	Default     string `json:"default,omitempty"`
	Description string `json:"description"`
}

type AuditBatch

type AuditBatch = []AuditEntry

AuditBatch is the internal pipeline and local-endpoint payload for a batch of audit entries. The control-plane leg of POST /v1/nodes/{node_id}/audit sends AuditEvent instead.

type AuditEntry

type AuditEntry struct {
	Timestamp time.Time       `json:"timestamp"`
	Source    string          `json:"source"`
	EventType string          `json:"event_type"`
	Subject   json.RawMessage `json:"subject"`
	Object    json.RawMessage `json:"object"`
	Action    string          `json:"action"`
	Result    string          `json:"result"`
	Hostname  string          `json:"hostname"`
	Raw       string          `json:"raw"`
}

AuditEntry is the internal pipeline and local-endpoint format for a single audit record. The control-plane leg sends AuditEvent instead.

type AuditEvent added in v0.2.0

type AuditEvent struct {
	Source    string    `json:"source"`
	Action    string    `json:"action"`
	Outcome   string    `json:"outcome"`
	Timestamp time.Time `json:"timestamp"`
}

AuditEvent is the control-plane wire format for a single audit record in the body of POST /v1/nodes/{node_id}/audit.

type BinaryInfo

type BinaryInfo struct {
	Version  string `json:"version"`
	Checksum string `json:"checksum"`
}

BinaryInfo describes the running agent binary. It is reported through the node API's local surface; the control plane receives the same two values as the flat binary_version / binary_checksum fields of a capability manifest.

type BridgeInfo

type BridgeInfo struct {
	Enabled                 bool   `json:"enabled"`
	AccessInterface         string `json:"access_interface"`
	ActiveRoutes            int    `json:"active_routes"`
	RelayEnabled            bool   `json:"relay_enabled"`
	ActiveRelaySessions     int    `json:"active_relay_sessions"`
	IngressEnabled          bool   `json:"ingress_enabled"`
	ActiveIngressRules      int    `json:"active_ingress_rules"`
	SiteToSiteEnabled       bool   `json:"site_to_site_enabled"`
	ActiveSiteToSiteTunnels int    `json:"active_site_to_site_tunnels"`
}

BridgeInfo is the bridge status reported by the node in heartbeats.

type BridgeSnapshot added in v0.2.0

type BridgeSnapshot struct {
	Relay      *RelayConfig      `json:"relay"`
	UserAccess *UserAccessConfig `json:"user_access"`
	Ingress    *IngressConfig    `json:"ingress"`
	SiteToSite *SiteToSiteConfig `json:"site_to_site"`
}

BridgeSnapshot carries the four bridge subtrees. Each child is present-but-nullable; there are no base fields on the wire.

type CapabilityManifestRequest added in v0.4.0

type CapabilityManifestRequest struct {
	// BinaryVersion is the agent version string, non-empty after trimming.
	BinaryVersion string `json:"binary_version"`
	// BinaryChecksum is the running binary's SHA-256 as 32 raw bytes in
	// standard-padded base64 (see integrity.WireChecksum). Anything that does
	// not decode to exactly 32 bytes is refused with 400
	// binary_checksum_invalid.
	BinaryChecksum string `json:"binary_checksum"`
	// SSHHostKeyFingerprint is the optional OpenSSH host-key fingerprint in the
	// canonical `SHA256:<base64>` form. Omitted when the agent has none.
	SSHHostKeyFingerprint string `json:"ssh_host_key_fingerprint,omitempty"`
	// DeclaredHooks are the hooks the agent advertises, at most 128, with
	// unique names. Omitted when empty rather than sent as null.
	DeclaredHooks []DeclaredHook `json:"declared_hooks,omitempty"`
}

CapabilityManifestRequest is the body of PUT /v1/nodes/{node_id}/capabilities: the agent's binary version and digest, plus the hooks it advertises.

The handler decodes it with DisallowUnknownFields, so this struct carries the contract's fields and nothing else. In particular there is no field for the agent's builtin action list — the control plane has no column for it, and an earlier payload that sent one under `builtin_actions` (inside a nested `binary` object that the contract also does not have) was refused whole. The action list stays available locally through the node API's /v1/actions.

type Clock

type Clock interface {
	Now() time.Time
	After(d time.Duration) <-chan time.Time
}

Clock abstracts time operations for testing.

type Config

type Config struct {
	// BaseURL is the control plane API base URL (required).
	// Example: "https://api.plexsphere.com"
	BaseURL string `yaml:"base_url"`

	// TLSInsecureSkipVerify disables TLS certificate verification.
	// WARNING: Only use for development/testing.
	TLSInsecureSkipVerify bool `yaml:"tls_insecure_skip_verify"`

	// ConnectTimeout is the maximum time to wait for a TCP connection.
	// Default: 10s
	ConnectTimeout time.Duration `yaml:"connect_timeout"`

	// RequestTimeout is the maximum time for a complete HTTP request/response cycle.
	// Default: 30s
	RequestTimeout time.Duration `yaml:"request_timeout"`

	// SSEIdleTimeout is the maximum time to wait for any data on the SSE stream
	// before considering the connection stale and reconnecting.
	// Default: 90s
	SSEIdleTimeout time.Duration `yaml:"sse_idle_timeout"`

	// SSEReprobeInterval is how often pull-only delivery re-probes the SSE
	// endpoint after the control plane descoped it.
	// Default: 10m
	SSEReprobeInterval time.Duration `yaml:"sse_reprobe_interval"`
}

Config holds the configuration for the ControlPlane client. Config is passed as a constructor argument — no file I/O in this package.

func (*Config) ApplyDefaults

func (c *Config) ApplyDefaults()

ApplyDefaults sets default values for zero-valued fields.

func (*Config) Validate

func (c *Config) Validate() error

Validate checks that required fields are set.

type ConnectFunc

type ConnectFunc func(ctx context.Context) error

ConnectFunc is called to establish an SSE connection. It should block while the connection is active and return when it drops.

type ControlPlane

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

ControlPlane is the client for the Plexsphere control plane API.

func NewControlPlane

func NewControlPlane(cfg Config, version string, logger *slog.Logger) (*ControlPlane, error)

NewControlPlane creates a new ControlPlane client with the given configuration.

func (*ControlPlane) ConnectSSE

func (c *ControlPlane) ConnectSSE(ctx context.Context, nodeID, lastEventID string) (*http.Response, error)

ConnectSSE opens an SSE connection to the node event stream. The caller is responsible for closing the response body. GET /v1/nodes/{node_id}/events

func (*ControlPlane) DeleteStateReport added in v0.2.0

func (c *ControlPlane) DeleteStateReport(ctx context.Context, nodeID, key string) error

DeleteStateReport removes a single per-key node state report. A 204 No Content returns nil; a 404 report_not_found (and any other non-2xx status) surfaces as an *APIError through errorFromResponse. DELETE /v1/nodes/{node_id}/state/reports/{key}

func (*ControlPlane) ExecutionCallback added in v0.2.0

func (c *ControlPlane) ExecutionCallback(ctx context.Context, nodeID, executionID string, req ExecutionCallbackRequest) (*ExecutionCallbackResponse, error)

ExecutionCallback posts a single execution lifecycle callback and returns the server's new invocation status, plus a presigned output upload URL when the callback declares an over-ceiling output. POST /v1/nodes/{node_id}/executions/{execution_id}

func (*ControlPlane) FetchSecret

func (c *ControlPlane) FetchSecret(ctx context.Context, nodeID, name string, version int) (*SecretEnvelope, error)

FetchSecret retrieves a specific secret for the node as the raw AES-256-GCM envelope served by the control plane: the octet-stream body carries <12-byte nonce> || <ciphertext + 16-byte GCM tag>, and the version and KID ride in the X-Plexsphere-Secret-Version and X-Plexsphere-Secret-KID headers. A version > 0 selects an older version via ?version=N; version == 0 is the current version. GET /v1/nodes/{node_id}/secrets/{name}

func (*ControlPlane) FetchState

func (c *ControlPlane) FetchState(ctx context.Context, nodeID string) (*NodeStateSnapshot, error)

FetchState retrieves the desired-state snapshot for a node. GET /v1/nodes/{node_id}/state

func (*ControlPlane) GetJSON

func (c *ControlPlane) GetJSON(ctx context.Context, path string, result any) error

GetJSON sends a GET request and decodes the JSON response.

func (*ControlPlane) Heartbeat

func (c *ControlPlane) Heartbeat(ctx context.Context, nodeID string, req HeartbeatRequest) (*HeartbeatResponse, error)

Heartbeat sends a heartbeat to the control plane. POST /v1/nodes/{node_id}/heartbeat

func (*ControlPlane) PostJSON

func (c *ControlPlane) PostJSON(ctx context.Context, path string, body any, result any) error

PostJSON sends a POST request with a JSON body and decodes the JSON response.

func (*ControlPlane) PutStateReport added in v0.2.0

func (c *ControlPlane) PutStateReport(ctx context.Context, nodeID, key string, req NodeStateReportRequest) (*NodeStateReportResponse, error)

PutStateReport publishes a single per-key node state report and returns the server's acknowledgement. PUT /v1/nodes/{node_id}/state/reports/{key}

func (*ControlPlane) Register

Register sends a registration request to the control plane. POST /v1/register is security: [] — the bootstrap token travels in the body, so the request never carries the shared bearer token even if one is set.

func (*ControlPlane) ReportAudit

func (c *ControlPlane) ReportAudit(ctx context.Context, nodeID string, events []AuditEvent) (*IngestReceipt, error)

ReportAudit posts a batch of audit events to the platform ingest endpoint as NDJSON (one JSON object per line) and returns the 202 ingest receipt. POST /v1/nodes/{node_id}/audit

func (*ControlPlane) ReportEndpoint

func (c *ControlPlane) ReportEndpoint(ctx context.Context, nodeID string, req EndpointRequest) (*EndpointResponse, error)

ReportEndpoint reports the node's NAT endpoint information. PUT /v1/nodes/{node_id}/endpoint

func (*ControlPlane) ReportIntegrityViolations added in v0.4.0

func (c *ControlPlane) ReportIntegrityViolations(ctx context.Context, nodeID string, req IntegrityViolationsRequest) error

ReportIntegrityViolations reports a batch of integrity violations to the control plane. The 200 response carries a commit timestamp and an echo of the batch size; the agent keeps no replay queue to reconcile them against, so the body is not decoded and a non-2xx surfaces as an *APIError.

The batch bounds are checked here rather than left to the server: the two codes it answers with (integrity_violations_empty, integrity_violations_too_many) describe a caller mistake, and a refused request would take every violation in it down. POST /v1/nodes/{node_id}/integrity-violations

func (*ControlPlane) ReportLogs

func (c *ControlPlane) ReportLogs(ctx context.Context, nodeID string, lines []LogLine) (*IngestReceipt, error)

ReportLogs posts a batch of log lines to the platform ingest endpoint as NDJSON (one JSON object per line) and returns the 202 ingest receipt. POST /v1/nodes/{node_id}/logs

func (*ControlPlane) ReportMetrics

func (c *ControlPlane) ReportMetrics(ctx context.Context, nodeID string, samples []MetricSample) (*IngestReceipt, error)

ReportMetrics posts a batch of metric samples to the platform ingest endpoint as a JSON array and returns the 202 ingest receipt. POST /v1/nodes/{node_id}/metrics

func (*ControlPlane) ReportSessionActivity added in v0.2.0

func (c *ControlPlane) ReportSessionActivity(ctx context.Context, nodeID, sessionID string, req SessionActivityRequest) error

ReportSessionActivity posts a one-of session activity record (ssh, k8s, or tcp). Success is 204 No Content. POST /v1/nodes/{node_id}/sessions/{session_id}

func (*ControlPlane) RotateKeys

RotateKeys completes a pending mesh-key rotation; the server identifies the node from the NSK bearer credential. POST /v1/keys/rotate

func (*ControlPlane) SetAuthToken

func (c *ControlPlane) SetAuthToken(token string)

SetAuthToken sets the bearer token used for API authentication.

func (*ControlPlane) UpdateCapabilities

func (c *ControlPlane) UpdateCapabilities(ctx context.Context, nodeID string, caps CapabilityManifestRequest) error

UpdateCapabilities publishes the node's capability manifest. PUT /v1/nodes/{node_id}/capabilities

func (*ControlPlane) UploadExecutionOutput added in v0.2.0

func (c *ControlPlane) UploadExecutionOutput(ctx context.Context, uploadURL string, output []byte) error

UploadExecutionOutput PUTs an over-ceiling execution output to a presigned URL. Presigned URLs carry their own authentication, so this request sends neither the bearer token nor gzip encoding; it uploads the raw bytes with a Content-Type of application/octet-stream.

The upload URL comes from the control plane, and captured action output routinely contains configuration and credentials, so the transport is pinned twice over: the URL may not be less secure than the configured control-plane base URL (an https control plane can never downgrade an upload to http), and redirects are not followed — a 3xx is surfaced as an error rather than re-sending the body to whatever host the redirect names.

type DataEntry

type DataEntry struct {
	Key         string          `json:"key"`
	ContentType string          `json:"content_type"`
	Payload     json.RawMessage `json:"payload"`
	Version     int             `json:"version"`
	UpdatedAt   time.Time       `json:"updated_at"`
}

type DeclaredHook added in v0.4.0

type DeclaredHook struct {
	Name string `json:"name"`
	// Checksum is the hook payload's SHA-256 as 32 raw bytes in
	// standard-padded base64, same encoding as BinaryChecksum.
	Checksum string `json:"checksum"`
}

DeclaredHook pairs a hook name with the digest of its payload, so the integrity correlator can see a hook change without re-fetching it.

type DeliveryMode added in v0.2.0

type DeliveryMode string

DeliveryMode identifies which channel currently delivers control-plane state.

const (
	// DeliveryModeStreaming means the SSE event stream is the live channel.
	DeliveryModeStreaming DeliveryMode = "streaming"
	// DeliveryModePullOnly means the event stream is descoped and the
	// reconciler's own loop is the only delivery channel; SSE is re-probed at
	// a low cadence to detect its return.
	DeliveryModePullOnly DeliveryMode = "pull_only"
	// DeliveryModeDegradedPolling means SSE has been failing transiently long
	// enough that the legacy polling fallback is driving state.
	DeliveryModeDegradedPolling DeliveryMode = "degraded_polling"
)

type Ed25519Verifier

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

Ed25519Verifier verifies Envelope signatures using Ed25519 keys selected by key id. It supports rotation by holding a current key and an optional previous key that stays accepted until a transition deadline.

func NewEd25519Verifier

func NewEd25519Verifier(keyID string, key ed25519.PublicKey) *Ed25519Verifier

NewEd25519Verifier returns a new verifier that accepts envelopes signed with the given key and carrying the given key id.

func (*Ed25519Verifier) Rotate added in v0.2.0

func (v *Ed25519Verifier) Rotate(rot SigningKeyRotation) error

Rotate installs a new current signing key from a signing_key_rotated event. The previous key is retained as a grace entry only when the rotation names a previous key id the verifier currently holds and a non-zero transition deadline; otherwise no grace entry is kept. On any error the installed keys are left unchanged.

func (*Ed25519Verifier) Verify

func (v *Ed25519Verifier) Verify(_ context.Context, env Envelope) error

Verify checks the freshness and Ed25519 signature of an envelope. It selects the verifying key by the envelope's key id, honouring the rotation grace window for the previous key.

type EndpointRequest added in v0.2.0

type EndpointRequest struct {
	Endpoint   string    `json:"endpoint"`
	NATType    string    `json:"nat_type"`
	ReportedAt time.Time `json:"reported_at"`
}

type EndpointResponse

type EndpointResponse struct {
	AcceptedAt time.Time `json:"accepted_at"`
	StaleAfter time.Time `json:"stale_after"`
}

type Envelope added in v0.2.0

type Envelope struct {
	ID        string          `json:"id"`
	Type      string          `json:"type"`
	Scope     string          `json:"scope"`
	KeyID     string          `json:"key_id"`
	IssuedAt  time.Time       `json:"issued_at"`
	Payload   json.RawMessage `json:"payload"`
	Signature string          `json:"signature"`
}

Envelope is the wire format for signed events the control plane streams over SSE. It matches the control plane's OpenAPI v1 events contract.

func ParseEnvelope

func ParseEnvelope(data []byte) (Envelope, error)

ParseEnvelope unmarshals data into an Envelope and validates required fields.

type EventDispatcher

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

EventDispatcher routes verified events to registered handlers by event type.

func NewEventDispatcher

func NewEventDispatcher(logger *slog.Logger) *EventDispatcher

NewEventDispatcher creates a new EventDispatcher.

func (*EventDispatcher) Dispatch

func (d *EventDispatcher) Dispatch(ctx context.Context, envelope Envelope)

Dispatch invokes all handlers registered for the event's type. Handler errors are logged but do not stop processing of subsequent handlers. Events with no registered handler are logged at debug level and discarded.

func (*EventDispatcher) Register

func (d *EventDispatcher) Register(eventType string, handler EventHandler)

Register adds a handler for the given event type. Multiple handlers can be registered for the same event type.

type EventHandler

type EventHandler func(ctx context.Context, envelope Envelope) error

EventHandler is a function that handles a verified SSE event.

type EventVerifier

type EventVerifier interface {
	Verify(ctx context.Context, envelope Envelope) error
}

EventVerifier verifies the signature of an Envelope.

type ExecutionCallbackRequest added in v0.2.0

type ExecutionCallbackRequest struct {
	Status              string           `json:"status"`
	ExitCode            *int             `json:"exit_code,omitempty"`
	Error               string           `json:"error,omitempty"`
	DeclaredOutputBytes int64            `json:"declared_output_bytes,omitempty"`
	Output              *ExecutionOutput `json:"output,omitempty"`
}

ExecutionCallbackRequest is the single callback a node posts to POST /v1/nodes/{node_id}/executions/{execution_id} to advance an execution through its lifecycle. Status is one of the ExecutionStatus* values. ExitCode is a pointer so a terminal callback can report an explicit zero. Error is set on failed terminals. DeclaredOutputBytes is the byte length of the captured output; a declaration over the 16 KiB inline ceiling drives the presign mint.

type ExecutionCallbackResponse added in v0.2.0

type ExecutionCallbackResponse struct {
	Status          string `json:"status"`
	OutputUploadURL string `json:"output_upload_url,omitempty"`
}

ExecutionCallbackResponse is the 200 response to an execution callback. Status is the new invocation status. OutputUploadURL is a presigned PUT URL present only on the first callback that declares an over-ceiling output; the node derives the object key from the URL path and uploads the bytes there.

type ExecutionOutput added in v0.2.0

type ExecutionOutput struct {
	Inline    string `json:"inline,omitempty"`
	ObjectKey string `json:"object_key,omitempty"`
	SHA256    string `json:"sha256,omitempty"`
}

ExecutionOutput carries an execution's captured output on a terminal callback. Inline is the base64-encoded output body and is used only when it is at most 16 KiB. ObjectKey and SHA256 describe an already-uploaded over-ceiling output: ObjectKey is the object-store key and SHA256 is the lowercase-hex SHA-256 of the uploaded bytes.

type FailureAction

type FailureAction int

FailureAction indicates how the reconnect engine should handle a failure.

const (
	// RetryTransient means use exponential backoff (network errors, 5xx).
	RetryTransient FailureAction = iota
	// RetryAuth means invoke OnAuthFailure callback and pause (401).
	RetryAuth
	// RespectServer means use the server-provided Retry-After delay (429).
	RespectServer
	// PermanentFailure means stop reconnection entirely (403, 404).
	PermanentFailure
	// RetryDescoped means the endpoint is descoped long-term (a 501
	// signed_event_bus_not_provisioned) — switch to pull-only delivery
	// immediately, with no backoff, rather than retrying a channel that is
	// not there.
	RetryDescoped
)

func ClassifyError

func ClassifyError(err error) FailureAction

ClassifyError determines the appropriate reconnection action for an error.

type HeartbeatRequest

type HeartbeatRequest struct {
	ClientNow      time.Time      `json:"client_now"`
	BinaryChecksum string         `json:"binary_checksum"`
	BinaryVersion  string         `json:"binary_version"`
	NATSummary     map[string]any `json:"nat_summary"`
}

type HeartbeatResponse

type HeartbeatResponse struct {
	AcceptedAt time.Time `json:"accepted_at"`
	Reconcile  bool      `json:"reconcile"`
	RotateKeys bool      `json:"rotate_keys"`
}

type HookInfo

type HookInfo struct {
	Name        string        `json:"name"`
	Description string        `json:"description"`
	Source      string        `json:"source"`
	Checksum    string        `json:"checksum"`
	Parameters  []ActionParam `json:"parameters"`
	Timeout     string        `json:"timeout"`
	Sandbox     string        `json:"sandbox"`
}

type IngestReceipt added in v0.2.0

type IngestReceipt struct {
	AcceptedAt time.Time `json:"accepted_at"`
	Records    int       `json:"records"`
}

IngestReceipt is the 202 body of the three ingest operations (POST /v1/nodes/{node_id}/metrics|logs|audit). Records is the number of records the control plane accepted from the batch.

type IngressConfig

type IngressConfig struct {
	Enabled bool          `json:"enabled"`
	Rules   []IngressRule `json:"rules"`
}

IngressConfig is the ingress configuration pushed from the control plane.

type IngressInfo

type IngressInfo struct {
	Enabled         bool `json:"enabled"`
	RuleCount       int  `json:"rule_count"`
	ConnectionCount int  `json:"connection_count"`
	ACMEEnabled     bool `json:"acme_enabled"`
}

IngressInfo is the ingress status reported by the node in heartbeats.

type IngressRule

type IngressRule struct {
	RuleID     string `json:"rule_id"`
	ListenPort int    `json:"listen_port"`
	TargetAddr string `json:"target_addr"`
	// Mode is the TLS handling mode: "tcp" (passthrough), "terminate" (static cert),
	// or "acme" (automatic certificate via ACME).
	Mode     string `json:"mode"`
	CertPEM  string `json:"cert_pem,omitempty"`
	KeyPEM   string `json:"key_pem,omitempty"`
	Hostname string `json:"hostname,omitempty"`
}

IngressRule represents a single public ingress rule.

type IntegrityDetector added in v0.4.0

type IntegrityDetector string

IntegrityDetector is the on-node detector that surfaced a violation. Like IntegrityViolationKind the set is closed; a value outside it is refused with 400 integrity_violation_detected_by_invalid.

const (
	// IntegrityDetectorStartupScan is the agent's own sweep of the artifacts it
	// holds baselines for. The contract has no value for a periodic re-scan, so
	// the interval-driven sweep reports this one too.
	IntegrityDetectorStartupScan IntegrityDetector = "startup_scan"
	// IntegrityDetectorInotify is the hooks-directory watcher reacting to a
	// filesystem event.
	IntegrityDetectorInotify IntegrityDetector = "inotify"
	// IntegrityDetectorPreDispatch is the check that runs immediately before a
	// hook executes.
	IntegrityDetectorPreDispatch IntegrityDetector = "pre_dispatch"
)

type IntegrityViolationKind added in v0.4.0

type IntegrityViolationKind string

IntegrityViolationKind is the artifact class a violation applies to. The control plane canonicalises every entry through a value-object constructor that refuses anything outside this set with 400 integrity_violation_kind_invalid, so the type exists to keep an unchecked string from reaching the wire in the first place.

const (
	// IntegrityKindBinaryChecksum reports the agent's own binary.
	IntegrityKindBinaryChecksum IntegrityViolationKind = "binary_checksum"
	// IntegrityKindHookChecksum reports a hook script.
	IntegrityKindHookChecksum IntegrityViolationKind = "hook_checksum"
	// IntegrityKindSSHHostKey reports the mesh SSH host key.
	IntegrityKindSSHHostKey IntegrityViolationKind = "ssh_host_key"
)

type IntegrityViolationReport

type IntegrityViolationReport struct {
	Kind       IntegrityViolationKind `json:"kind"`
	DetectedBy IntegrityDetector      `json:"detected_by"`
	// ArtifactID identifies the affected artifact — a hook path, the binary
	// path, or the host-key file. Non-empty after trimming, at most 4096 bytes.
	ArtifactID string `json:"artifact_id"`
	// ObservedChecksum is the SHA-256 the agent computed, as 32 raw bytes in
	// standard-padded base64 (see integrity.WireChecksum). Required for the
	// checksum kinds; anything that does not decode to exactly 32 bytes is
	// refused with 400 integrity_violation_checksum_invalid.
	ObservedChecksum string `json:"observed_checksum,omitempty"`
	// ExpectedChecksum is the baseline digest, same encoding as
	// ObservedChecksum.
	ExpectedChecksum string `json:"expected_checksum,omitempty"`
	// ObservedFingerprint is the OpenSSH host-key fingerprint the agent
	// observed, in the canonical `SHA256:<base64>` form. Required for the
	// ssh_host_key kind.
	ObservedFingerprint string `json:"observed_fingerprint,omitempty"`
	// ExpectedFingerprint is the baseline fingerprint, same form as
	// ObservedFingerprint.
	ExpectedFingerprint string `json:"expected_fingerprint,omitempty"`
}

IntegrityViolationReport is one entry of an IntegrityViolationsRequest.

The handler decodes the envelope with DisallowUnknownFields, so this struct carries the contract's fields and nothing else — in particular there is no timestamp (the control plane stamps its own) and no free-text detail field.

Kind decides which digest pair is legal. A binary_checksum or hook_checksum entry carries the checksums and no fingerprint; an ssh_host_key entry carries the fingerprints and no checksum. Crossing them is refused with 400 integrity_violation_kind_mismatch, which is why the four digest fields are omitempty: an unset one must be absent from the JSON, not present and empty.

type IntegrityViolationsRequest added in v0.4.0

type IntegrityViolationsRequest struct {
	// Violations carries between 1 and MaxIntegrityViolationsPerBatch entries.
	Violations []IntegrityViolationReport `json:"violations"`
}

IntegrityViolationsRequest is the body of POST /v1/nodes/{node_id}/integrity-violations: a batch of the tamper-evidence divergences the agent detected locally. The endpoint takes a batch even when the agent has a single violation to report, so a lone finding travels as a one-entry array rather than as a bare object.

type IntegrityViolationsResponse added in v0.4.0

type IntegrityViolationsResponse struct {
	// AcceptedAt is the server-side commit timestamp.
	AcceptedAt time.Time `json:"accepted_at"`
	// ViolationCount echoes the number of rows persisted, matching the length
	// of the violations array on input.
	ViolationCount int `json:"violation_count"`
}

IntegrityViolationsResponse is the 200 body of POST /v1/nodes/{node_id}/integrity-violations. The work is synchronous — every row and the integrity_alert outbox event are committed before the response is written — so the status is 200 rather than 202.

type K8sActivity added in v0.2.0

type K8sActivity struct {
	Verb         string `json:"verb"`
	ResourceKind string `json:"resource_kind,omitempty"`
	Namespace    string `json:"namespace,omitempty"`
	Name         string `json:"name,omitempty"`
	StatusCode   int    `json:"status_code,omitempty"`
	DurationMS   int64  `json:"duration_ms,omitempty"`
}

K8sActivity records a single Kubernetes API action proxied through the session. Verb is the API verb; the remaining fields describe the target object and the outcome.

type KeyRotateRequest

type KeyRotateRequest struct {
	NewPublicKey string `json:"new_public_key"`
}

type KeyRotateResponse

type KeyRotateResponse struct {
	RotationID     string `json:"rotation_id"`
	KID            string `json:"kid"`
	WrapKeyVersion int    `json:"wrap_key_version"`
}

type LocalEndpointConfig

type LocalEndpointConfig struct {
	// URL is the HTTPS endpoint URL. Must use the https:// scheme when set.
	URL string `yaml:"url"`

	// SecretKey is the authentication credential for the local endpoint.
	// Required when URL is non-empty.
	SecretKey string `yaml:"secret_key"`

	// TLSInsecureSkipVerify disables TLS certificate verification.
	TLSInsecureSkipVerify bool `yaml:"tls_insecure_skip_verify"`
}

LocalEndpointConfig holds the configuration for a local data-plane endpoint that a pipeline can send data to in addition to the platform. A zero-valued LocalEndpointConfig means "not configured" and passes validation.

func (*LocalEndpointConfig) Validate

func (c *LocalEndpointConfig) Validate(prefix string) error

Validate checks that the local endpoint configuration is well-formed. The prefix is prepended to error messages for context (e.g. "metrics").

type LogBatch

type LogBatch = []LogEntry

LogBatch is the internal pipeline and local-endpoint payload for a batch of log entries. The control-plane leg of POST /v1/nodes/{node_id}/logs sends LogLine instead.

type LogEntry

type LogEntry struct {
	Timestamp time.Time `json:"timestamp"`
	Source    string    `json:"source"`
	Unit      string    `json:"unit"`
	Message   string    `json:"message"`
	Severity  string    `json:"severity"`
	Hostname  string    `json:"hostname"`
}

LogEntry is the internal pipeline and local-endpoint format for a single log record. The control-plane leg sends LogLine instead.

type LogLine added in v0.2.0

type LogLine struct {
	Severity  string    `json:"severity"`
	Unit      string    `json:"unit,omitempty"`
	Hostname  string    `json:"hostname,omitempty"`
	Message   string    `json:"message"`
	Timestamp time.Time `json:"timestamp"`
}

LogLine is the control-plane wire format for a single log record in the body of POST /v1/nodes/{node_id}/logs. Unit and Hostname are absent when unknown.

type MetricBatch

type MetricBatch = []MetricPoint

MetricBatch is the internal pipeline and local-endpoint payload for a batch of metric points. The control-plane leg of POST /v1/nodes/{node_id}/metrics sends MetricSample instead.

type MetricPoint

type MetricPoint struct {
	Timestamp time.Time       `json:"timestamp"`
	Group     string          `json:"group"`
	PeerID    string          `json:"peer_id,omitempty"`
	Data      json.RawMessage `json:"data"`
}

MetricPoint is the internal pipeline and local-endpoint format for a single metric. The control-plane leg sends MetricSample instead.

type MetricSample added in v0.2.0

type MetricSample struct {
	Group     string            `json:"group"`
	Name      string            `json:"name"`
	Value     float64           `json:"value"`
	Labels    map[string]string `json:"labels,omitempty"`
	Timestamp time.Time         `json:"timestamp"`
}

MetricSample is the control-plane wire format for a single metric in the body of POST /v1/nodes/{node_id}/metrics. Group is one of the MetricGroup* values; Labels is absent when the sample carries no dimensions.

type NoOpVerifier

type NoOpVerifier struct{}

NoOpVerifier is an EventVerifier that accepts all envelopes without verification.

func (NoOpVerifier) Verify

func (NoOpVerifier) Verify(_ context.Context, _ Envelope) error

Verify always returns nil.

type NodeStateBlock added in v0.2.0

type NodeStateBlock struct {
	Metadata []StateEntry `json:"metadata"`
	Data     []StateEntry `json:"data"`
	Reports  []StateEntry `json:"reports"`
}

NodeStateBlock is a three-bucket state block. Each bucket is a required array (never null when the block is populated), with entries ordered by key ascending.

type NodeStateExecution added in v0.3.0

type NodeStateExecution struct {
	ExecutionID string                     `json:"execution_id"`
	Action      string                     `json:"action"`
	Type        string                     `json:"type"`
	Parameters  map[string]json.RawMessage `json:"parameters"`
	Status      string                     `json:"status"`
	RequestedAt time.Time                  `json:"requested_at"`
	ExpiresAt   time.Time                  `json:"expires_at"`
}

NodeStateExecution is one pending action dispatch in the executions block of GET /v1/nodes/{node_id}/state. That block is the delivery channel for action dispatches: it is always present ([] when empty, never null) and ordered by RequestedAt, then ExecutionID. An entry keeps reappearing on every pull until its execution reaches a terminal status through the execution callback, so a consumer must tolerate re-observing the same ExecutionID.

Type is one of the ActionKind* values; Status is ExecutionStatusPending, ExecutionStatusAck, or ExecutionStatusStarted. ExpiresAt is an absolute UTC deadline, not a relative timeout. Parameters is nullable: a JSON null decodes to a nil map. The entry carries no callback URL and no hook checksum.

A parameter value is held as raw JSON, not decoded into any: the state response is decoded with a plain decoder, so a JSON number would land in an any as a float64 and every integer beyond 2^53 would reach the action rewritten. The raw form hands the value to the action exactly as the control plane sent it.

type NodeStateReportRequest added in v0.2.0

type NodeStateReportRequest struct {
	Value       string `json:"value"`
	WorkloadTag string `json:"workload_tag,omitempty"`
}

NodeStateReportRequest is the control-plane wire format for the body of PUT /v1/nodes/{id}/state/reports/{key}. Value is the opaque report payload; WorkloadTag attributes the report to a workload and is absent when the report is unattributed.

type NodeStateReportResponse added in v0.2.0

type NodeStateReportResponse struct {
	AcceptedAt time.Time `json:"accepted_at"`
	Key        string    `json:"key"`
}

NodeStateReportResponse is the control-plane wire format for the 200 body of PUT /v1/nodes/{id}/state/reports/{key} (and DELETE /v1/nodes/{id}/state/ reports/{key}). Key echoes the report key the operation addressed.

type NodeStateSession added in v0.3.0

type NodeStateSession struct {
	SessionID          string        `json:"session_id"`
	JTI                string        `json:"jti"`
	Kind               string        `json:"kind"`
	Target             SessionTarget `json:"target"`
	ExpiresAt          time.Time     `json:"expires_at"`
	IdleTimeoutSeconds int           `json:"idle_timeout_seconds,omitempty"`
}

NodeStateSession is one live mediated-access session in the sessions block of GET /v1/nodes/{node_id}/state. That block is desired state, not a queue: it is always present on the wire ([] when empty, never null). An entry appears when the control plane issues the session and disappears on revocation or hard expiry — the disappearance is the teardown signal, there is no separate teardown event. A response that violates the contract by omitting or nulling the block decodes to a nil NodeStateSnapshot.Sessions and is not read as an empty block.

Kind is one of the SessionKind* values and selects which member of Target is set. JTI equals the session id: it is carried as an opaque value and never evaluated. ExpiresAt is an absolute UTC timestamp, not a relative timeout. IdleTimeoutSeconds 0 or absent means the session has no idle window.

type NodeStateSnapshot added in v0.2.0

type NodeStateSnapshot struct {
	Peers        []SnapshotPeer       `json:"peers"`
	Reachability json.RawMessage      `json:"reachability"`
	Policy       *PolicySnapshot      `json:"policy"`
	Bridge       *BridgeSnapshot      `json:"bridge"`
	State        *NodeStateBlock      `json:"state"`
	Reports      *NodeStateBlock      `json:"reports"`
	Executions   []NodeStateExecution `json:"executions"`
	// Sessions is a pointer for the same reason the block fields above are: the
	// sessions block is desired state whose emptiness is destructive — an empty
	// block tears every live session down — so "the control plane says you have
	// no sessions" ([]) has to stay distinguishable from "the control plane did
	// not populate the block" (null, or a key a build predating the block never
	// wrote). A nil pointer is the second case and is not a teardown signal.
	Sessions *[]NodeStateSession `json:"sessions"`
}

NodeStateSnapshot is the desired-state envelope returned by GET /v1/nodes/{node_id}/state. Every block key is always present on the wire: a null value means "block not populated", never "field absent". The differ therefore distinguishes a nil pointer from a populated block, so none of the eight fields carry omitempty.

type Peer

type Peer struct {
	ID         string   `json:"id"`
	PublicKey  string   `json:"public_key"`
	MeshIP     string   `json:"mesh_ip"`
	Endpoint   string   `json:"endpoint"`
	AllowedIPs []string `json:"allowed_ips"`
	PSK        string   `json:"psk"`
}

Peer is the internal WireGuard programming shape built by peerFromSnapshot.

type PolicyRule

type PolicyRule struct {
	Action          string     `json:"action"`
	Protocol        string     `json:"protocol"`
	SourceCIDR      string     `json:"source_cidr"`
	DestinationCIDR string     `json:"destination_cidr"`
	Ports           *PortRange `json:"ports,omitempty"`
}

PolicyRule is a five-tuple firewall rule. Ports is present iff Protocol is tcp or udp and is absent for icmp/any.

type PolicySnapshot added in v0.2.0

type PolicySnapshot struct {
	RevisionID  string       `json:"revision_id"`
	Fingerprint string       `json:"fingerprint"`
	Rules       []PolicyRule `json:"rules"`
}

PolicySnapshot is the single merged policy block. Fingerprint is a 44-char base64 SHA-256 over the server's canonical rule byte stream; plexd treats it as an opaque comparison key and never re-derives it from Rules.

type PollFunc

type PollFunc func(ctx context.Context) error

PollFunc is called during polling fallback to fetch full state.

type PortRange added in v0.2.0

type PortRange struct {
	From int `json:"from"`
	To   int `json:"to"`
}

PortRange is a single inclusive destination port range (from <= to).

type ReachabilitySnapshot added in v0.7.0

type ReachabilitySnapshot struct {
	State           string     `json:"state"`
	LastHeartbeatAt *time.Time `json:"last_heartbeat_at,omitempty"`
	ChangedAt       time.Time  `json:"changed_at"`
}

ReachabilitySnapshot is the interior of the snapshot's reachability block: the control plane's own verdict about this node, derived from the heartbeats it has admitted. It is a diagnostic projection, never desired state.

State is a plain string and is never validated against the constants below. The verdict vocabulary belongs to the control plane and grows there — never_reported was added after plexd shipped — so a value this build does not know must keep flowing to the log rather than be rejected. LastHeartbeatAt is absent until the first heartbeat is accepted; ChangedAt is always present.

type ReconcileTrigger added in v0.2.0

type ReconcileTrigger interface {
	TriggerReconcile()
}

ReconcileTrigger requests a full state reconcile. *reconcile.Reconciler satisfies it.

type ReconnectEngine

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

ReconnectEngine manages SSE reconnection with backoff and polling fallback.

func NewReconnectEngine

func NewReconnectEngine(logger *slog.Logger) *ReconnectEngine

NewReconnectEngine creates a new ReconnectEngine with default settings.

func (*ReconnectEngine) Mode added in v0.2.0

func (r *ReconnectEngine) Mode() DeliveryMode

Mode returns the delivery mode currently reported by the engine.

func (*ReconnectEngine) Run

func (r *ReconnectEngine) Run(ctx context.Context, connectFn ConnectFunc, pollFn PollFunc) error

Run is the main state machine loop that manages SSE reconnection.

States: Connecting -> Connected | Backoff

Backoff -> Connecting | Polling
Polling -> Connecting (periodic SSE retry)

Context cancellation exits from any state.

func (*ReconnectEngine) SetBaseInterval

func (r *ReconnectEngine) SetBaseInterval(d time.Duration)

SetBaseInterval updates the base backoff interval. This is called when the SSE retry: field is received from the server.

func (*ReconnectEngine) SetClock

func (r *ReconnectEngine) SetClock(c Clock)

SetClock sets a custom clock implementation for testing.

func (*ReconnectEngine) SetIntervals

func (r *ReconnectEngine) SetIntervals(base, max time.Duration)

SetIntervals configures the base and max backoff intervals and resets the current interval to the new base. Useful for testing with fast intervals.

func (*ReconnectEngine) SetOnAuthFailure

func (r *ReconnectEngine) SetOnAuthFailure(fn func())

SetOnAuthFailure sets the callback invoked on authentication failures.

func (*ReconnectEngine) SetOnModeChange added in v0.2.0

func (r *ReconnectEngine) SetOnModeChange(fn func(DeliveryMode))

SetOnModeChange sets the callback invoked on every delivery-mode transition. The callback is fired outside the engine's lock and may be nil.

func (*ReconnectEngine) SetPollInterval

func (r *ReconnectEngine) SetPollInterval(d time.Duration)

SetPollInterval sets how often to poll during polling fallback mode.

func (*ReconnectEngine) SetPollingFallbackConfig

func (r *ReconnectEngine) SetPollingFallbackConfig(fallbackAfter, pollInterval time.Duration)

SetPollingFallbackConfig configures when to enter polling mode and how often to poll.

func (*ReconnectEngine) SetReprobeInterval added in v0.2.0

func (r *ReconnectEngine) SetReprobeInterval(d time.Duration)

SetReprobeInterval sets how often pull-only mode re-probes the SSE endpoint. A non-positive duration is ignored.

type RegisterPeer added in v0.2.0

type RegisterPeer struct {
	NodeID           string `json:"node_id"`
	MeshIP           string `json:"mesh_ip"`
	PublicKey        string `json:"public_key"`
	FallbackEndpoint string `json:"fallback_endpoint,omitempty"`
}

RegisterPeer is the initial peer snapshot entry returned by POST /v1/register. It is deliberately narrow: it carries NO psk, allowed_ips, or endpoint. The reconciliation peer shape is SnapshotPeer.

type RegisterRequest

type RegisterRequest struct {
	ProjectID           string `json:"project_id"`
	ResourceHandle      string `json:"resource_handle"`
	BootstrapToken      string `json:"bootstrap_token"`
	Nonce               string `json:"nonce"`
	PublicKey           string `json:"public_key"`
	RequestedResourceID string `json:"requested_resource_id,omitempty"`
}

type RegisterResponse

type RegisterResponse struct {
	NodeID           string         `json:"node_id"`
	MeshIP           string         `json:"mesh_ip"`
	SigningPublicKey string         `json:"signing_public_key"`
	SigningKeyID     string         `json:"signing_key_id"`
	NSK              string         `json:"nsk"`
	PeerSnapshot     []RegisterPeer `json:"peer_snapshot"`
	DomainMeshCIDR   string         `json:"domain_mesh_cidr"`
}

type RelayConfig

type RelayConfig struct {
	Sessions []RelaySessionAssignment `json:"sessions"`
}

RelayConfig is the relay configuration pushed from the control plane. It contains the list of relay session assignments for this bridge node.

type RelaySessionAssignment

type RelaySessionAssignment struct {
	SessionID     string    `json:"session_id"`
	PeerAID       string    `json:"peer_a_id"`
	PeerAEndpoint string    `json:"peer_a_endpoint"`
	PeerBID       string    `json:"peer_b_id"`
	PeerBEndpoint string    `json:"peer_b_endpoint"`
	ExpiresAt     time.Time `json:"expires_at"`
}

RelaySessionAssignment represents a relay session assigned by the control plane.

type RetryCallback

type RetryCallback func(interval time.Duration)

RetryCallback is called when the SSE server sends a retry: field.

type SSEEvent

type SSEEvent struct {
	Type string // from "event:" field, defaults to "message"
	Data string // concatenated data fields
	ID   string // from "id:" field
}

SSEEvent represents a single parsed SSE event.

type SSEManager

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

SSEManager is the top-level orchestrator that wires SSEStream, ReconnectEngine, EventVerifier, and EventDispatcher together.

func NewSSEManager

func NewSSEManager(client *ControlPlane, verifier EventVerifier, logger *slog.Logger) *SSEManager

NewSSEManager creates a new SSEManager. If verifier is nil, NoOpVerifier is used.

func (*SSEManager) Mode added in v0.2.0

func (m *SSEManager) Mode() DeliveryMode

Mode returns the delivery mode currently reported by the reconnect engine.

func (*SSEManager) RegisterHandler

func (m *SSEManager) RegisterHandler(eventType string, handler EventHandler)

RegisterHandler adds a handler for the given event type. Must be called before Start.

func (*SSEManager) SetIdleTimeout added in v0.2.0

func (m *SSEManager) SetIdleTimeout(d time.Duration)

SetIdleTimeout sets the SSE idle timeout used for connections opened by Start. A zero duration falls back to DefaultSSEIdleTimeout.

func (*SSEManager) SetOnModeChange added in v0.2.0

func (m *SSEManager) SetOnModeChange(fn func(DeliveryMode))

SetOnModeChange registers a callback invoked on every delivery-mode transition. The callback may be nil.

func (*SSEManager) SetPollFunc

func (m *SSEManager) SetPollFunc(fn PollFunc)

SetPollFunc sets the function called during polling fallback to fetch full state.

func (*SSEManager) SetPollingFallback

func (m *SSEManager) SetPollingFallback(fallbackAfter, pollInterval time.Duration)

SetPollingFallback configures when to enter polling mode and how often to poll.

func (*SSEManager) SetReconcileTrigger added in v0.2.0

func (m *SSEManager) SetReconcileTrigger(t ReconcileTrigger)

SetReconcileTrigger sets the trigger fired once after every successful SSE connect so the client covers replay gaps with a full reconcile pull. A nil trigger disables the pull.

func (*SSEManager) SetReconnectIntervals

func (m *SSEManager) SetReconnectIntervals(base, max time.Duration)

SetReconnectIntervals configures the base and max backoff intervals. Useful for testing with fast intervals.

func (*SSEManager) SetReprobeInterval added in v0.2.0

func (m *SSEManager) SetReprobeInterval(d time.Duration)

SetReprobeInterval sets how often pull-only mode re-probes the SSE endpoint. It delegates to the reconnect engine.

func (*SSEManager) Shutdown

func (m *SSEManager) Shutdown()

Shutdown gracefully stops the manager by cancelling its context.

func (*SSEManager) Start

func (m *SSEManager) Start(ctx context.Context, nodeID string) error

Start begins the SSE connection loop with automatic reconnection. It blocks until the context is cancelled, Shutdown is called, or a permanent error occurs.

type SSEParser

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

SSEParser reads from an io.Reader and emits parsed SSE events.

func NewSSEParser

func NewSSEParser(r io.Reader) *SSEParser

NewSSEParser creates a parser reading from the given reader.

func (*SSEParser) LastEventID

func (p *SSEParser) LastEventID() string

LastEventID returns the most recently received event ID.

func (*SSEParser) Next

func (p *SSEParser) Next() (SSEEvent, bool)

Next reads lines until a complete event is found. Returns the event and true, or a zero event and false when the reader is exhausted.

func (*SSEParser) SetRetryCallback

func (p *SSEParser) SetRetryCallback(cb RetryCallback)

SetRetryCallback sets the function called when a retry: field is received.

type SSEStream

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

SSEStream connects to the SSE endpoint, parses events, verifies envelopes, and dispatches them to registered handlers.

func NewSSEStream

func NewSSEStream(client *ControlPlane, verifier EventVerifier, dispatcher *EventDispatcher, idleTimeout time.Duration, logger *slog.Logger) *SSEStream

NewSSEStream creates a new SSEStream.

func (*SSEStream) Connect

func (s *SSEStream) Connect(ctx context.Context, nodeID string) error

Connect establishes the SSE connection and processes events until the connection drops or context is cancelled. Returns nil when the connection closes cleanly, or an error.

func (*SSEStream) LastEventID

func (s *SSEStream) LastEventID() string

LastEventID returns the last received event ID (for reconnection).

func (*SSEStream) SetOnConnected added in v0.2.0

func (s *SSEStream) SetOnConnected(fn func())

SetOnConnected registers a hook invoked once after every ConnectSSE that returns HTTP 200, before the parse loop begins.

type SSHActivity added in v0.2.0

type SSHActivity struct {
	Command     string     `json:"command"`
	ExitCode    *int       `json:"exit_code,omitempty"`
	StartedAt   *time.Time `json:"started_at,omitempty"`
	CompletedAt *time.Time `json:"completed_at,omitempty"`
}

SSHActivity records a completed SSH session command. Command is the executed command line and is capped at 1 KiB. StartedAt and CompletedAt are RFC 3339 timestamps.

type SecretEnvelope added in v0.2.0

type SecretEnvelope struct {
	Data    []byte
	Version int
	KID     string
}

SecretEnvelope is the raw AES-256-GCM envelope served by GET /v1/nodes/{id}/secrets/{name}: Data is <12-byte nonce> || <ciphertext + 16-byte GCM tag>, Version and KID come from the X-Plexsphere-Secret-Version and X-Plexsphere-Secret-KID headers. It is parsed from headers and body, never from JSON.

type SecretRef

type SecretRef struct {
	Key     string `json:"key"`
	Version int    `json:"version"`
}

type SessionActivityRequest added in v0.2.0

type SessionActivityRequest struct {
	SSH *SSHActivity `json:"ssh,omitempty"`
	K8s *K8sActivity `json:"k8s,omitempty"`
	TCP *TCPActivity `json:"tcp,omitempty"`
}

SessionActivityRequest is the one-of activity record a node posts to POST /v1/nodes/{node_id}/sessions/{session_id}. Exactly one member is set, selecting the session kind: SSH, K8s, or TCP.

type SessionTarget added in v0.3.0

type SessionTarget struct {
	SSH *SessionTargetSSH `json:"ssh,omitempty"`
	K8s *SessionTargetK8s `json:"k8s,omitempty"`
	TCP *SessionTargetTCP `json:"tcp,omitempty"`
}

SessionTarget is the target of a NodeStateSession. Exactly one member is set, selecting the session kind: SSH, K8s, or TCP.

type SessionTargetK8s added in v0.3.0

type SessionTargetK8s struct {
	User              string   `json:"user"`
	ImpersonateGroups []string `json:"impersonate_groups,omitempty"`
}

SessionTargetK8s is the target of a k8s session. User is the impersonated Kubernetes user; ImpersonateGroups are the groups impersonated alongside it.

type SessionTargetSSH added in v0.3.0

type SessionTargetSSH struct {
	User            string   `json:"user"`
	AllowedCommands []string `json:"allowed_commands,omitempty"`
}

SessionTargetSSH is the target of an ssh session. User is the local account the session logs in as; AllowedCommands, when set, is the closed set of command lines the session may run.

type SessionTargetTCP added in v0.3.0

type SessionTargetTCP struct {
	Host string `json:"host"`
	Port int    `json:"port"`
}

SessionTargetTCP is the target of a tcp session: the host and port the node forwards the session's connections to.

type SigningKeyRotation added in v0.2.0

type SigningKeyRotation struct {
	KeyID             string    `json:"key_id"`
	PublicKey         string    `json:"public_key"`
	PreviousKeyID     string    `json:"previous_key_id"`
	TransitionExpires time.Time `json:"transition_expires"`
}

SigningKeyRotation is the payload of the signing_key_rotated event: the new current signing key and, optionally, the previous key id kept valid until a transition deadline. Its shape is an author-approved assumption until the platform taxonomy documents it.

type SiteToSiteConfig

type SiteToSiteConfig struct {
	Enabled bool               `json:"enabled"`
	Tunnels []SiteToSiteTunnel `json:"tunnels"`
}

SiteToSiteConfig is the site-to-site VPN configuration pushed from the control plane.

type SiteToSiteInfo

type SiteToSiteInfo struct {
	Enabled             bool     `json:"enabled"`
	TunnelCount         int      `json:"tunnel_count"`
	TunnelProviderNames []string `json:"tunnel_provider_names,omitempty"`
}

SiteToSiteInfo is the site-to-site VPN status reported by the node in heartbeats.

type SiteToSiteTunnel

type SiteToSiteTunnel struct {
	TunnelID        string   `json:"tunnel_id"`
	RemoteEndpoint  string   `json:"remote_endpoint"`
	RemotePublicKey string   `json:"remote_public_key"`
	LocalSubnets    []string `json:"local_subnets"`
	RemoteSubnets   []string `json:"remote_subnets"`
	PSK             string   `json:"psk,omitempty"`
	InterfaceName   string   `json:"interface_name"`
	ListenPort      int      `json:"listen_port"`
	// ProviderType specifies which tunnel provider to use for this tunnel.
	// Empty or "wireguard" means the default WireGuard-based approach.
	// Other values (e.g. "ipsec", "openvpn") delegate to the corresponding TunnelProvider.
	ProviderType string `json:"provider_type,omitempty"`
}

SiteToSiteTunnel represents a single site-to-site VPN tunnel definition.

type SnapshotPeer added in v0.2.0

type SnapshotPeer struct {
	NodeID           string `json:"node_id"`
	MeshIP           string `json:"mesh_ip"`
	PublicKey        string `json:"public_key"`
	FallbackEndpoint string `json:"fallback_endpoint,omitempty"`
}

SnapshotPeer is a reconciliation peer entry in NodeStateSnapshot. It is a separate type from RegisterPeer (one Go type per contract schema) and carries NO psk, allowed_ips, or endpoint: AllowedIPs are derived locally as mesh_ip/32, fallback_endpoint is the relay target programmed as the WireGuard endpoint, and no preshared key is fabricated.

type StateEntry added in v0.2.0

type StateEntry struct {
	Key         string `json:"key"`
	Value       string `json:"value"`
	WorkloadTag string `json:"workload_tag,omitempty"`
}

StateEntry is a single state entry. Value is an opaque string; WorkloadTag is absent/empty when the entry is unattributed.

type TCPActivity added in v0.2.0

type TCPActivity struct {
	Phase            string `json:"phase"`
	TargetHost       string `json:"target_host,omitempty"`
	TargetPort       int    `json:"target_port,omitempty"`
	ListenerEndpoint string `json:"listener_endpoint,omitempty"`
	BytesIn          *int64 `json:"bytes_in,omitempty"`
	BytesOut         *int64 `json:"bytes_out,omitempty"`
	TerminatedBy     string `json:"terminated_by,omitempty"`
}

TCPActivity records a TCP session lifecycle event. Phase is one of the TCPPhase* values. ListenerEndpoint is the node's bound listener address and is set only on session_started rows. BytesIn (operator to target) and BytesOut (target to operator) are pointers so a session_ended row carries explicit zeros while a session_started row omits the byte counters. TerminatedBy, when set, is one of the TerminatedBy* values.

type UserAccessConfig

type UserAccessConfig struct {
	Enabled       bool             `json:"enabled"`
	InterfaceName string           `json:"interface_name"`
	ListenPort    int              `json:"listen_port"`
	Peers         []UserAccessPeer `json:"peers"`
}

UserAccessConfig is the user access configuration pushed from the control plane.

type UserAccessInfo

type UserAccessInfo struct {
	Enabled        bool   `json:"enabled"`
	InterfaceName  string `json:"interface_name"`
	PeerCount      int    `json:"peer_count"`
	ListenPort     int    `json:"listen_port"`
	ProviderName   string `json:"provider_name,omitempty"`
	ProviderStatus string `json:"provider_status,omitempty"`
}

UserAccessInfo is the user access status reported by the node in heartbeats.

type UserAccessPeer

type UserAccessPeer struct {
	PublicKey  string   `json:"public_key"`
	AllowedIPs []string `json:"allowed_ips"`
	PSK        string   `json:"psk,omitempty"`
	Label      string   `json:"label"`
}

UserAccessPeer represents a user access peer (external VPN client).

Jump to

Keyboard shortcuts

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