kernelnodeops

package
v4.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 4, 2026 License: MIT Imports: 41 Imported by: 0

Documentation

Overview

Package kernelnodeops serves the KernelNodeOps contract (sdk/api/kernelnodeops/v1, docs/architecture/node-ops-service.md) to official packages: typed, idempotent operations on nodes that the kernel records in its ledger (v4_kernel_node_operation), carries out with the executor of each kind and answers by polling and a watch stream.

Every call is authorized against the calling host's current generation: a submission for its operation family's capability, the reads, watches and cancellations for any one of the five, and they answer only the operations the caller's package owns. A kind without an executor is not served: SubmitOperation answers UNIMPLEMENTED and records nothing.

Index

Constants

View Source
const (
	// DefaultAgentAckTimeout bounds the wait for an agent's acknowledgement
	// when the operation names no timeout.
	DefaultAgentAckTimeout = 10 * time.Second
	// MaxAgentAckTimeout caps the acknowledgement wait an operation asks for.
	MaxAgentAckTimeout = 60 * time.Second
)

Timeouts of the stream dispatches, as the legacy routes bound them.

View Source
const (
	// AgentDiagnosticTaskType is the type of every task an administrator
	// sends to an agent: a whitelisted diagnostic action.
	AgentDiagnosticTaskType = "diagnostic"
	// AgentDiagnosticOperation is the Agent Control operation that carries
	// a diagnostic task on the stream; its payload is {"task": task}, the
	// WebSocket's task.assign payload. An agent that runs it advertises
	// the capability of the same name.
	AgentDiagnosticOperation = "agent.diagnostic"
)
View Source
const (
	DefaultPollInterval = time.Second
	DefaultTimeout      = 10 * time.Minute
	DefaultMaxRunning   = 16
	DefaultPackageQuota = 256
	DefaultNodeQuota    = 32
	OperationRetention  = 90 * 24 * time.Hour
	EventRetention      = 7 * 24 * time.Hour
)

Engine defaults (node-ops-service.md sections 3.4, 3.5 and 3.9).

View Source
const (
	KindForwardApply       = "forward.apply"
	KindForwardTunnel      = "forward.tunnel"
	KindForwardSyncBackend = "forward.sync_backend"
	KindForwardLegacyRule  = "forward.legacy_rule"
	KindNodeSync           = "node.sync"
	KindNodeRetire         = "node.retire"
	KindProtocolRetire     = "protocol.retire"
	KindSecretsPut         = "secrets.put"
	KindDiagnoseEndpoints  = "diagnose.endpoints"
	KindDiagnoseNodeStats  = "diagnose.node_stats"
	KindDiagnoseForward    = "diagnose.forward"
	KindDiagnoseTunnel     = "diagnose.tunnel"
	// KindDiagnoseForwardBackend tests a gost API with a token the
	// administrator typed (POST /admin/forward/test-connection); the
	// kernel dials, the package never sees the token.
	KindDiagnoseForwardBackend = "diagnose.forward_backend"
	KindAgentDiagnostic        = "agent.diagnostic"
	KindAgentOperation         = "agent.operation"
	KindCredentialIssue        = "credential.issue"  // #nosec G101 -- an operation kind name, not a credential.
	KindCredentialRevoke       = "credential.revoke" // #nosec G101 -- an operation kind name, not a credential.
	KindRegKeyIssue            = "regkey.issue"
	KindRegKeyRevoke           = "regkey.revoke"
	KindCleanAgentIssue        = "cleanagent.issue"
)

The operation kinds (node-ops-service.md section 3.3): each is recorded with its operation and answered in Operation.kind.

View Source
const (
	RevealAPIKey     = "api_key"
	RevealSecret     = "secret"
	RevealAPIToken   = "api_token"
	RevealKey        = "key"
	RevealToken      = "token"
	RevealCredential = "credential"
)

The names a credential operation reveals a generated secret under (SecretHandle.field): the answer fields config/node-secret-fields.json lists for the routes that show them.

View Source
const (
	DiagnosisAgentCapability        = "diag"
	DiagnosisAgentCapabilityVersion = "v1"
)

The agent capability a node vantage needs (decision D10): diag.v1.

View Source
const AgentEnrollmentActor = "kernelnodeops"

AgentEnrollmentActor names the kernel's KernelNodeOps in the audit entry of an enrollment credential a package had issued.

View Source
const DefaultJobPoll = 250 * time.Millisecond

DefaultJobPoll is how often a forward.apply on a job backend reads its runtime job while it waits for the executor on the node, or the local Ansible runner, to end it.

View Source
const DesiredConfigFormat = "anixops.nodeconfig/v1"

DesiredConfigFormat names the desired configuration document's schema (node-ops-service.md section 5.5): a proxy node's protocols as the kernel's builder renders them for the node, with its raw configuration and the UniProxy answers (legacy_pull); a forward node's legacy rules and tunnels.

View Source
const (

	// MaxWait bounds a SubmitOperation wait (wait_timeout_ms).
	MaxWait = time.Minute
)
View Source
const NodeReloadOperation = "node.reload"

NodeReloadOperation is the Agent Control operation a node sync sends to an agent without config.v1: the agent re-reads its configuration over the legacy transport. An agent that negotiated config.v1 is pushed a ConfigSnapshot instead (A2-3).

View Source
const SealedPrefix = "anix-sealed:v1:"

SealedPrefix starts a sealed secret handle (section 3.7).

Variables

View Source
var DefaultExecutors = NewRegistry()

DefaultExecutors is the registry the kernel's engines use. NO-1 serves no kind: NO-5 to NO-8 register the executors.

View Source
var ErrNoRequestBinding = errors.New("the operation has no live request binding")

ErrNoRequestBinding is Run.Reveal without a live request binding: the submission carried none, its request ended, or the kernel restarted.

View Source
var ErrNodeGone = errors.New("node does not exist")

ErrNodeGone reports a node whose row no longer exists.

View Source
var ErrOperationEnded = errors.New("node operation has ended")

ErrOperationEnded is returned by Run.Accept for an operation that ended meanwhile (cancelled, timed out): its executor should stop.

Functions

func ConfigLaggingNodes

func ConfigLaggingNodes(ctx context.Context, db *gorm.DB) (int64, error)

ConfigLaggingNodes counts the nodes whose agent reported a configuration status and whose applied revision is behind the desired one.

func ConfigSnapshotOf

ConfigSnapshotOf returns the snapshot of a stored desired configuration: its revision, hash, format and document, as stored.

func ConfigVerdict

func ConfigVerdict(desired model.KernelNodeDesiredConfig, found bool, status *agentv1pb.ConfigStatus) string

ConfigVerdict judges an agent's ConfigStatus against the node's desired configuration (model.ConfigVerdict*): applied or failed when it names the desired revision and hash, stale when it names an older revision, mismatch otherwise (the desired revision with another hash, a revision the kernel never stored, or no desired configuration).

func DialTarget

func DialTarget() sealedsecrets.Target

DialTarget is the sealed secret target of a route whose secret the kernel presents once to the address the request names (target kind dial): no resource.

func FamilyOfKind

func FamilyOfKind(name string) kernelnodeopsv1.OperationFamily

FamilyOfKind returns a kind's family, or UNSPECIFIED for an unknown kind.

func KnownKinds

func KnownKinds() []string

KnownKinds returns every operation kind of the contract, sorted.

func LoadConfigStatus

func LoadConfigStatus(ctx context.Context, db *gorm.DB, node agentcontrol.AgentNode) (model.KernelNodeConfigStatus, bool, error)

LoadConfigStatus returns what the node's agent last reported about its configuration.

func LoadDesiredConfig

func LoadDesiredConfig(ctx context.Context, db *gorm.DB, node agentcontrol.AgentNode) (model.KernelNodeDesiredConfig, bool, error)

LoadDesiredConfig returns the stored desired configuration of node.

func NewTaskID

func NewTaskID() string

NewTaskID returns a task or operation id as the legacy routes generate them.

func NodeTarget

NodeTarget is the sealed secret target of a node reference.

func RecordConfigStatus

func RecordConfigStatus(ctx context.Context, db *gorm.DB, node agentcontrol.AgentNode, sessionID string, status *agentv1pb.ConfigStatus, now time.Time) (string, error)

RecordConfigStatus records a ConfigStatus the node's agent sent on session sessionID and returns the verdict on it (ConfigVerdict). Every status is recorded as the node's last report; only an applied one that names the desired revision and hash moves the node's applied revision.

func RefreshDesiredConfig

func RefreshDesiredConfig(ctx context.Context, db *gorm.DB, node agentcontrol.AgentNode, now time.Time) (model.KernelNodeDesiredConfig, bool, error)

RefreshDesiredConfig rebuilds node's desired configuration from its rows and stores it when it changed or none was stored, through BuildDesiredConfig and StoreDesiredConfig, the functions SyncNode uses. An unchanged configuration is not written. changed reports whether the stored revision grew (or the row was created). ErrNodeGone when the node does not exist.

func RegisterNodeOperationExecutors

func RegisterNodeOperationExecutors(registry *Registry, sources SourcesFunc) error

RegisterNodeOperationExecutors serves the node configuration and agent operations of NO-6 (node-ops-service.md section 3.11): node.sync of the nodeconfig family, agent.operation of the agents family and agent.diagnostic of the diagnose family, all dispatched on the agent sessions sources returns. With a nil sources the defaults registered with UseAgentStreams and UseWebSocketAgents are read at each operation.

func SplitPhasesFrom

func SplitPhasesFrom(db *gorm.DB) func(ctx context.Context) ([]*kernelnodeopsv1.TableSplitState, error)

SplitPhasesFrom answers GetCapabilities.tables from the node credential split's state in db (v4_kernel_node_secret_split), read uncached.

  • A table answers its phase. One being finalized (finalized, without finalized_at yet) answers DUAL_READ: FINALIZED is the promise that the legacy columns hold no secret and the table may be adopted.
  • v2_forward_runtime_job is not a split table: its payloads carry no token since NO-7. It answers LEGACY, as before.
  • A database without the split tables answers LEGACY for every table.

func StoreDesiredConfig

func StoreDesiredConfig(ctx context.Context, db *gorm.DB, cfg *DesiredConfig, now time.Time) (model.KernelNodeDesiredConfig, bool, error)

StoreDesiredConfig records cfg as its node's desired configuration. The revision grows by one when the hash changed and stays when it did not, so it is monotonic per node and the same configuration keeps its revision. changed reports whether the hash moved. Two writers of one node are serialized by the row's revision: a lost race is retried.

func Terminal

func Terminal(state string) bool

Terminal reports whether a stored state has ended; it never changes again.

func UseAgentStreams

func UseAgentStreams(streams agentstreams.Streams)

UseAgentStreams makes streams the kernel's Agent Control streams for the default sources; cmd/server calls it once the listener exists.

func UseWebSocketAgents

func UseWebSocketAgents(agents agentstreams.WebSockets)

UseWebSocketAgents makes agents the kernel's WebSocket agents for the default sources; the router calls it with the agent handler.

Types

type Acceptance

type Acceptance struct {
	Channel             kernelnodeopsv1.Channel
	NodeRevision        uint64
	KernelOperationID   string
	ForwardRuntimeJobID uint64
}

Acceptance is what the channel reported when it took the operation.

type AdminError

type AdminError struct {
	Code      string `json:"code"`
	Message   string `json:"message"`
	Retryable bool   `json:"retryable"`
}

AdminError is how an operation failed.

type AdminFanOut

type AdminFanOut struct {
	Total     uint32 `json:"total"`
	Succeeded uint32 `json:"succeeded"`
	Failed    uint32 `json:"failed"`
	Pending   uint32 `json:"pending"`
}

AdminFanOut counts a fan-out's operations.

type AdminOperation

type AdminOperation struct {
	Cursor              uint64          `json:"cursor"`
	OperationID         string          `json:"operation_id"`
	RequestID           string          `json:"request_id"`
	Digest              string          `json:"digest"`
	PackageID           string          `json:"package_id"`
	PackageGeneration   uint64          `json:"package_generation"`
	SubmittedBy         string          `json:"submitted_by"`
	Family              string          `json:"family"`
	Kind                string          `json:"kind"`
	State               string          `json:"state"`
	Terminal            bool            `json:"terminal"`
	Channel             string          `json:"channel"`
	Attempt             uint32          `json:"attempt"`
	NodeRevision        uint64          `json:"node_revision"`
	Resource            string          `json:"resource"`
	Targets             []AdminTarget   `json:"targets"`
	ParentOperationID   string          `json:"parent_operation_id"`
	FanOut              *AdminFanOut    `json:"fan_out,omitempty"`
	Reason              string          `json:"reason"`
	Operation           json.RawMessage `json:"operation"`
	Result              json.RawMessage `json:"result,omitempty"`
	Error               *AdminError     `json:"error,omitempty"`
	Evidence            json.RawMessage `json:"evidence,omitempty"`
	KernelOperationID   string          `json:"kernel_operation_id"`
	ForwardRuntimeJobID uint64          `json:"forward_runtime_job_id"`
	CancelRequestedAt   *time.Time      `json:"cancel_requested_at"`
	CancelReason        string          `json:"cancel_reason"`
	CreatedAt           time.Time       `json:"created_at"`
	AcceptedAt          *time.Time      `json:"accepted_at"`
	FinishedAt          *time.Time      `json:"finished_at"`
	DeadlineAt          time.Time       `json:"deadline_at"`
	UpdatedAt           time.Time       `json:"updated_at"`
}

AdminOperation is one ledger row as administrators audit it: who asked for what, on which nodes, and how it went. It holds no secret: the operation is canonical, results and errors were scrubbed when stored.

type AdminPage

type AdminPage struct {
	Operations []AdminOperation `json:"operations"`
	NextBefore uint64           `json:"next_before"`
}

AdminPage is one page of the listing, newest first. NextBefore pages on; zero on the last page.

func AdminList

func AdminList(ctx context.Context, db *gorm.DB, query AdminQuery) (AdminPage, error)

AdminList lists every package's node operations, newest first.

type AdminQuery

type AdminQuery struct {
	PackageID   string
	Family      string
	Kind        string
	State       string
	RequestID   string
	OperationID string
	Parent      string
	TargetKind  string
	TargetID    uint64
	// Before pages: operations listed before the one with this cursor.
	Before uint64
	Limit  int
}

AdminQuery filters the administrator's listing of node operations (GET /api/v4/kernel/node-operations).

func ParseAdminQuery

func ParseAdminQuery(get func(string) string) (AdminQuery, error)

ParseAdminQuery reads the listing's filters from query parameters.

type AdminTarget

type AdminTarget struct {
	Kind string `json:"kind"`
	ID   uint64 `json:"id"`
}

AdminTarget is one node an operation acts on.

type AgentDiagnostic

type AgentDiagnostic struct {
	Task agentstreams.DiagnosticTask
	// Row is the task's row as the legacy routes answer it: reloaded after
	// the dispatch, or as created when the task could not be sent.
	Row      *model.AgentDiagnosticTask
	Dispatch agentstreams.DiagnosticDispatch
}

AgentDiagnostic is what RunAgentDiagnostic did.

func RunAgentDiagnostic

func RunAgentDiagnostic(ctx context.Context, db *gorm.DB, request AgentDiagnosticRequest, transport agentstreams.DiagnosticTransport) (*AgentDiagnostic, error)

RunAgentDiagnostic validates a diagnostic task against the kernel's whitelist (its parameters normalized), records it in v2_agent_diagnostic_task, sends it over transport and records whether it went out, as the legacy task and execute routes do. A refused task is a DiagnosticValidationError and records nothing.

type AgentDiagnosticRequest

type AgentDiagnosticRequest struct {
	NodeID uint
	Action string
	Params map[string]any
	// Timeout is the task's timeout as the agent receives it.
	Timeout int
}

AgentDiagnosticRequest is one diagnostic task for a proxy node's agent, as POST /admin/agent/tasks and /admin/agent/execute take it.

type AgentDirectory

type AgentDirectory interface {
	// Advertises reports whether node's agent holds a session whose Hello
	// lists the capability name at version.
	Advertises(node *kernelnodeopsv1.NodeRef, name, version string) bool
}

AgentDirectory answers what nodes' agents advertise on their live Agent Control sessions.

type AgentOperation

type AgentOperation struct {
	Desired *agentv1pb.DesiredOperation
	Ack     *agentv1pb.OperationAck
	// contains filtered or unexported fields
}

AgentOperation is a dispatched Agent Control operation.

func DispatchAgentOperation

func DispatchAgentOperation(ctx context.Context, streams agentstreams.Streams, node agentcontrol.AgentNode, request AgentOperationRequest) (*AgentOperation, error)

DispatchAgentOperation sends one bounded, whitelisted Agent Control operation on node's stream and waits for the agent's acknowledgement, as the legacy route does. The desired operation is returned with the error of a failed dispatch, so a caller can show what was sent; the revision the agent acknowledged is written into it.

func (*AgentOperation) Release

func (o *AgentOperation) Release()

Release stops following the operation's observed states.

type AgentOperationRequest

type AgentOperationRequest struct {
	// OperationID is the stream's operation id; one is generated when
	// empty.
	OperationID string
	// Kind is agent.ping, node.reload or users.reload.
	Kind        string
	PayloadJSON []byte
	// Timeout bounds the wait for the acknowledgement:
	// DefaultAgentAckTimeout when zero, at most MaxAgentAckTimeout.
	Timeout time.Duration
}

AgentOperationRequest is one Agent Control operation to send on a node's stream, as POST /admin/nodes/:id/agent-control/operations takes it.

type AgentSources

type AgentSources struct {
	Streams    agentstreams.Streams
	WebSockets agentstreams.WebSockets
}

AgentSources are the live agent sessions the executors dispatch on and the agent session RPCs read: the Agent Control streams of every node kind, and the legacy WebSocket. Either may be nil in a kernel that does not hold it.

func DefaultAgentSources

func DefaultAgentSources() AgentSources

DefaultAgentSources returns the sources registered with UseAgentStreams and UseWebSocketAgents, as they are now.

type Authorizer

type Authorizer interface {
	AuthorizeCapability(ctx context.Context, host packagebridge.HostIdentity, capability string) error
}

Authorizer admits a calling host to a capability.

type BoundRequest

type BoundRequest struct {
	RequestID string
	RouteID   string
	// ActorID and Admin are the request's kernel-authenticated principal.
	ActorID  uint64
	Admin    bool
	Deadline time.Time
	// contains filtered or unexported fields
}

BoundRequest is a verified request binding: the live v2 request the calling package is serving, as the kernel's gateway dispatched it. It is never stored.

type ConnectionTest

type ConnectionTest struct {
	// Probes resolves the host for the public-address check; the zero
	// value uses the system resolver.
	Probes service.DiagnosisProbes
	// Now purges the held tokens; time.Now when nil.
	Now func() time.Time
	// contains filtered or unexported fields
}

ConnectionTest executes diagnose.forward_backend (node-ops-service.md section 6.1, NO-7): the gost API connection test of POST /admin/forward/test-connection, run from the kernel with the token the administrator typed. The gost-mesh package host submits the operation with its request binding and the sealed handle of the token; the Preparer resolves the handle in the submitting call and keeps the value in kernel memory until the executor dials. The token is scrubbed from the result and never recorded.

The dial follows the kernel's legacy handler: the same gost client, the same calls, the same error texts, so the route's answer is byte for byte what it was. A refusing or unreachable API is the result, not a failed operation. The SSRF rule of the diagnoses (#84, #90) applies: a request that is not an administrator's dials public addresses only.

func (*ConnectionTest) Execute

func (c *ConnectionTest) Execute(ctx context.Context, run *Run) Outcome

Execute dials the gost API as the legacy handler does.

func (*ConnectionTest) Held

func (c *ConnectionTest) Held() int

Held reports how many tokens are held, for tests.

func (*ConnectionTest) Prepare

func (c *ConnectionTest) Prepare(_ context.Context, submission *Submission) error

Prepare resolves the token's handle for the bound request (target kind dial, field /api_token) and holds the value for the executor until the request's deadline. Without a binding the handle does not resolve (PERMISSION_DENIED) and nothing is recorded.

func (*ConnectionTest) Register

func (c *ConnectionTest) Register(registry *Registry) error

Register serves the kind on registry.

type Credentials

type Credentials struct {
	// PKI returns the agent PKI that issues enrollment credentials, on the
	// operation's transaction; nil uses the kernel's configuration
	// (agentpki.FromConfig).
	PKI func(ctx context.Context, tx *gorm.DB) (*agentpki.Service, error)
}

Credentials executes the credentials family (node-ops-service.md section 3.3, NO-5): IssueCredential, RevokeCredential, IssueRegistrationKey, RevokeRegistrationKey and IssueCleanAgent. Each runs the function of internal/service the legacy route runs, in one transaction with every nodesecrets.Sync and every agent certificate revocation, so native and legacy write identical rows.

Secrets in, secrets out. A typed secret arrives as a sealed handle: the Preparer resolves it for the bound request (Submission.Unseal) and keeps it for the executor in memory. A generated secret is minted as a handle (Run.Reveal) inside the transaction that stores it: when the answer cannot show it, nothing is stored. A credential is issued only in an administrator's request (the binding), never outside one, and results never carry a value in clear.

func (*Credentials) Register

func (c *Credentials) Register(registry *Registry) error

Register serves the credential kinds on registry.

type DesiredConfig

type DesiredConfig struct {
	Node     agentcontrol.AgentNode
	Format   string
	Document map[string]any
	// JSON is the canonical document: sorted keys, no whitespace. Hash is
	// its SHA-256 (hex). The same configuration always has the same hash.
	JSON []byte
	Hash string
	// ExcludedProtocols counts the protocol rows left out because they
	// failed validation; zero while validation is report-only (section 3.8).
	ExcludedProtocols uint32
}

DesiredConfig is a node's desired configuration as the kernel built it from the current rows. The document holds the node's runtime secrets; it is stored in the protected table and never answered.

func BuildDesiredConfig

func BuildDesiredConfig(db *gorm.DB, node agentcontrol.AgentNode) (*DesiredConfig, error)

BuildDesiredConfig builds node's desired configuration from its rows. ErrNodeGone when the node does not exist.

type Diagnosis

type Diagnosis struct {
	// Probes is how the diagnoses reach the network. The zero value dials
	// from Control, with the system resolver, at most
	// service.DefaultDiagnosisConcurrency probes at once per operation.
	Probes service.DiagnosisProbes
	// Agents answers which nodes' agents advertise diag.v1; nil answers
	// none. No forward node holds an Agent Control session before A2-1, so
	// the kernel's default has none.
	Agents AgentDirectory
	// Now stamps the results; time.Now when nil.
	Now func() time.Time
}

Diagnosis executes the diagnose family's kinds (node-ops-service.md section 3.3, NO-8): CheckEndpoints, CollectNodeStats, DiagnoseForward and DiagnoseTunnel. Each runs the kernel's implementation of the legacy route (internal/service), which the legacy handlers run too, so its result is what the route computes. Results carry no credential: the nodes' tokens are named to the engine's scrubber, and diagnoses send none.

The vantage (D10) is Control by default; it would be the node when the agent of every node a diagnosis concerns advertises diag.v1. The kernel does not run diagnoses on agents yet (A2-5), so it dials from Control and says so in the result's VantageReport. A package host is never a vantage.

func (*Diagnosis) Register

func (d *Diagnosis) Register(registry *Registry) error

Register serves the diagnose kinds on registry.

type DiagnosticValidationError

type DiagnosticValidationError struct {
	Err error
}

DiagnosticValidationError refuses a task the whitelist does not allow.

func (*DiagnosticValidationError) Error

func (e *DiagnosticValidationError) Error() string

func (*DiagnosticValidationError) Unwrap

func (e *DiagnosticValidationError) Unwrap() error

type Engine

type Engine struct {
	DB *gorm.DB
	// Executors serve the operation kinds; nil serves none.
	Executors *Registry
	// PollInterval is how often the dispatcher looks for work without a
	// wake-up; DefaultPollInterval when zero.
	PollInterval time.Duration
	// Timeout is an operation's deadline after its submission;
	// DefaultTimeout when zero.
	Timeout time.Duration
	// MaxRunning bounds the executors running at once.
	MaxRunning int
	// PackageQuota and NodeQuota bound the operations that have not ended,
	// per package and per target node.
	PackageQuota int
	NodeQuota    int
	// Now defaults to time.Now; it stamps the ledger.
	Now func() time.Time
	// Secrets holds the sealed handles of the requests in flight; nil
	// selects sealedsecrets.DefaultStore, which the v2 gateway seals into.
	Secrets *sealedsecrets.Store
	// contains filtered or unexported fields
}

Engine is the kernel's KernelNodeOps engine: the ledger, the dispatcher that hands pending operations to their executors, and the change notifications that waits and watches follow. One engine runs its dispatcher per database (Control is a single replica, section 8 R6).

func EngineFor

func EngineFor(db *gorm.DB) *Engine

EngineFor returns the process's engine for db, serving DefaultExecutors. The local package bridge and the module listener share it, and the kernel's singleton workers run its dispatcher.

func (*Engine) Prune

func (e *Engine) Prune(ctx context.Context, now time.Time) (int64, error)

Prune deletes the operations that ended before the retention and the events older than theirs. A request id pruned may be used again.

func (*Engine) Run

func (e *Engine) Run(ctx context.Context)

Run dispatches operations until ctx ends: it first recovers the operations a previous process left started, then on every tick ends the operations past their deadline, passes cancellations to their executors and starts pending operations, and prunes the ledger hourly.

func (*Engine) Wake

func (e *Engine) Wake()

Wake makes the dispatcher look for work now.

type Executor

type Executor interface {
	Execute(ctx context.Context, run *Run) Outcome
}

Executor carries out the operations of one kind (node-ops-service.md section 3.3). The engine calls Execute once per dispatch, in its own goroutine, with a context that ends at the operation's deadline or when the operation is cancelled. Execute reports the channel's acceptance through Run.Accept and returns the outcome; the engine records it.

An executor must apply all of an operation or nothing it cannot repeat: a level-triggered kind may run again after a kernel restart, every other kind ends FAILED (retryable) when it was interrupted.

type ExecutorFunc

type ExecutorFunc func(ctx context.Context, run *Run) Outcome

ExecutorFunc adapts a function to Executor.

func (ExecutorFunc) Execute

func (f ExecutorFunc) Execute(ctx context.Context, run *Run) Outcome

Execute calls f.

type Forward

type Forward struct {
	// JobPoll is how often a job backend's job is read; DefaultJobPoll when
	// zero.
	JobPoll time.Duration
	// Now stamps the results; time.Now when nil.
	Now func() time.Time
}

Forward executes the forward family's kinds (node-ops-service.md section 3.3, NO-7): ApplyForward, ApplyTunnel, SyncForwardBackend and ApplyLegacyRule. Each runs the kernel's implementation of the legacy route (internal/service), which the legacy handlers run too, over the executors the kernel already has: NodeX for the gost backend and the legacy rules, the local Ansible job executor and the clean agent job queue. The operation carries ids, never a payload: the kernel reads the rows when it executes, so a repeat converges on the latest state.

Credentials never leave the kernel. A job's stored payload carries no token; the gost executor resolves the ingress node's token when it sends the request to NodeX, and only for the node's pinned endpoint (section 3.8): an address that is not the pin ends ENDPOINT_UNCONFIRMED.

func (*Forward) Register

func (f *Forward) Register(registry *Registry) error

Register serves the forward kinds on registry.

type NodeSync

type NodeSync struct {
	Node agentcontrol.AgentNode
	// Channel is AGENT_CONTROL when the node's agent holds a stream, else
	// LEGACY_PULL: the node's next periodic pull reads the configuration.
	Channel kernelnodeopsv1.Channel
	Config  *DesiredConfig
	// Stored is the desired configuration row after the sync; Changed is
	// true when its hash moved (and its revision grew).
	Stored  model.KernelNodeDesiredConfig
	Changed bool
	// Pushed is true when a node.reload or a ConfigSnapshot went out on
	// the stream: the configuration changed, the sync was forced, or (for
	// a snapshot) the agent had not applied the stored configuration.
	Pushed bool
	// Desired and Ack describe a node.reload push; Snapshot and SessionID
	// a ConfigSnapshot push (the agent negotiated config.v1). DispatchErr
	// is why either push failed.
	Desired     *agentv1pb.DesiredOperation
	Ack         *agentv1pb.OperationAck
	Snapshot    *agentv1pb.ConfigSnapshot
	SessionID   string
	DispatchErr error
	// contains filtered or unexported fields
}

NodeSync is what a node sync did (node-ops-service.md section 5.5 and the node.sync kind of section 3.3).

func SyncNode

func SyncNode(ctx context.Context, db *gorm.DB, streams agentstreams.Streams, node agentcontrol.AgentNode, opts SyncNodeOptions) (*NodeSync, error)

SyncNode rebuilds and stores a node's desired configuration from its rows, drops what the kernel caches about the node, and, when the node's agent holds an Agent Control stream, pushes the configuration: a ConfigSnapshot when the agent negotiated config.v1 (once the configuration changed, opts.Force is set, or the agent has not applied the stored configuration), else a node.reload through the existing control-stream operation path (once the configuration changed or opts.Force is set). Nodes on the legacy transports keep their stored configuration for their next pull. The legacy sync route and the node.sync executor call it. ErrNodeGone when the node does not exist.

func (*NodeSync) Release

func (s *NodeSync) Release()

Release stops following the pushed operation's observed states, or the pushed snapshot's ConfigStatus; a caller that answers at the acknowledgement (or the send) calls it.

type Outcome

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

Outcome is an executor's report: build it with Succeeded, Failed, Cancelled or FanOut.

func Cancelled

func Cancelled(message string) Outcome

Cancelled ends the operation CANCELLED: the executor stopped it because its context was cancelled.

func Failed

func Failed(code kernelnodeopsv1.ErrorCode, message string, retryable bool) Outcome

Failed ends the operation FAILED. A code of UNSPECIFIED is recorded as BACKEND_FAILED.

func FanOut

func FanOut(children ...*kernelnodeopsv1.OperationSpec) Outcome

FanOut ends a fan-out kind's own work: the engine records one child operation per spec, and the operation ends when they all have.

func Succeeded

func Succeeded(result *kernelnodeopsv1.OperationResult) Outcome

Succeeded ends the operation SUCCEEDED with result (nil for none).

func (Outcome) WithChannel

func (o Outcome) WithChannel(channel kernelnodeopsv1.Channel) Outcome

WithChannel records the channel that carried the operation, when the executor did not call Run.Accept.

func (Outcome) WithResult

func (o Outcome) WithResult(result *kernelnodeopsv1.OperationResult) Outcome

WithResult attaches a result to a failed or cancelled outcome, for what the operation did before it stopped.

type Preparer

type Preparer interface {
	Prepare(ctx context.Context, submission *Submission) error
}

Preparer is implemented by executors that must act in the submitting call, before the operation is recorded: use the verified request binding, resolve sealed handles (Submission.Unseal), capture what a deletion removes. Prepare may replace submission.Operation; the canonical form of what it leaves is stored and executed, and must hold no sealed handle. An error refuses the submission and records nothing; a gRPC status error is answered as it is.

type QueryError

type QueryError struct{ Message string }

QueryError is a filter the listing does not accept; its text says why.

func (*QueryError) Error

func (e *QueryError) Error() string

type Registry

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

Registry maps operation kinds to their executors. A kind without one is not served: SubmitOperation answers UNIMPLEMENTED and records nothing.

func NewRegistry

func NewRegistry() *Registry

NewRegistry returns an empty registry.

func (*Registry) Kinds

func (r *Registry) Kinds() []string

Kinds returns the served kinds, sorted.

func (*Registry) Lookup

func (r *Registry) Lookup(kindName string) (Executor, bool)

Lookup returns kind's executor.

func (*Registry) Register

func (r *Registry) Register(kindName string, executor Executor) error

Register serves kind with executor. A kind the contract does not define, or one registered before, is an error.

type Retirements

type Retirements struct{}

Retirements executes RetireNode and RetireProtocol (node-ops-service.md section 3.3, NO-5; decision D11). The kernel performs the cascade of the legacy deletion in one transaction: a node's protocols with their users' WireGuard peers, their subscription group links and their secrets, the node's credentials and raw configuration secrets, and its agent certificates and enrollments; a protocol's peers, links and secrets. The package deletes its own row afterwards. The functions are the legacy deletions' (service.RetireProxyNodeTx, RetireForwardNodeTx, RetireProtocolTx), and every step is idempotent: a retirement of what is already gone succeeds with nothing counted.

func (*Retirements) Register

func (r *Retirements) Register(registry *Registry) error

Register serves the retirement kinds on registry.

type Run

type Run struct {
	OperationID       string
	RequestID         string
	PackageID         string
	Kind              string
	ParentOperationID string
	// Operation is the stored canonical operation.
	Operation *kernelnodeopsv1.OperationSpec
	Targets   []*kernelnodeopsv1.NodeRef
	Attempt   uint32
	Deadline  time.Time
	// Request is the submission's verified request binding while its
	// request is live: nil when it carried none, the request ended, or the
	// kernel restarted since. Reveal mints handles for it.
	Request *BoundRequest
	// contains filtered or unexported fields
}

Run is one dispatch of an operation, as its executor sees it.

func (*Run) Accept

func (r *Run) Accept(ctx context.Context, acceptance Acceptance) error

Accept records that the channel took the operation: it moves to RUNNING and an ACCEPTED wait answers. Calling it again updates the links.

func (*Run) DB

func (r *Run) DB() *gorm.DB

DB returns the kernel database the operation is recorded in.

func (*Run) Prepared

func (r *Run) Prepared() any

Prepared returns what the Preparer retained, nil when the request's binding is no longer live.

func (*Run) Reveal

func (r *Run) Reveal(name, value string) (*kernelnodeopsv1.SecretHandle, error)

Reveal mints the handle of a secret the operation generated, for the answer to its bound request to show once under name (a SecretHandle field the route's answer lists). The value is also scrubbed from the operation's texts. The handle is answered to the submitting call only.

func (*Run) UseSecret

func (r *Run) UseSecret(values ...string)

UseSecret names credential values the operation uses: the kernel replaces them with the placeholder in every text of the result, the error and the evidence before it stores them.

type SecretDocuments

type SecretDocuments struct{}

SecretDocuments executes PutSecretDocument (node-ops-service.md section 3.3, NO-5): the secret-keeping part of the protocol and raw configuration writes. In the document a package sends, a secret is a sealed handle (a value the administrator typed), the placeholder (keep the stored value) or empty (no value); a secret in clear is refused by the kind's check.

The Preparer resolves every handle for the bound request, at the field the gateway sealed it from (the column's pointer followed by the secret's), and keeps the values for the executor. The executor stores the document as the legacy routes store it (service.PutNodeProtocolSecretDocumentTx, PutNodeRawConfigDocumentTx): the stored values at the placeholders, the legacy column and the split table in one transaction, and answers the redacted document the package keeps in its own row.

func (*SecretDocuments) Register

func (s *SecretDocuments) Register(registry *Registry) error

Register serves secrets.put on registry.

type Server

type Server struct {
	Engine     *Engine
	Authorizer Authorizer
	// SplitPhases answers the node credential split's phase per table
	// (SplitPhasesFrom). Nil answers LEGACY for every table.
	SplitPhases func(ctx context.Context) ([]*kernelnodeopsv1.TableSplitState, error)
	// Agents are the live agent sessions the session RPCs answer; nil reads
	// the defaults registered with UseAgentStreams and UseWebSocketAgents.
	Agents *AgentSources
}

Server holds what every host's KernelNodeOps calls share.

func (*Server) For

For returns the KernelNodeOps server that host reaches; it has the shape of packagebridge.KernelNodeOpsProvider.

type SourcesFunc

type SourcesFunc func() AgentSources

SourcesFunc returns the sources at the time of a call, so that a source registered after the executors were (the WebSocket handler is built with the router) is found when an operation runs.

type Submission

type Submission struct {
	Host      packagebridge.HostIdentity
	Kind      string
	Operation *kernelnodeopsv1.OperationSpec
	// Binding is the request binding's bridge capability; it is never
	// stored.
	Binding []byte
	// Request is the verified request binding: the live v2 request the
	// package is serving. Nil when the submission carries no binding; a
	// binding that names no live request of the package is refused before
	// Prepare.
	Request *BoundRequest
	Targets []*kernelnodeopsv1.NodeRef
	// contains filtered or unexported fields
}

Submission is an operation in its submitting call.

func (*Submission) Retain

func (s *Submission) Retain(value any)

Retain keeps a value of the Preparer's for the operation's executor (Run.Prepared): the secrets it resolved from the request's handles. It lives with the request binding, in kernel memory only, and is gone with it: at the request's deadline, or after a kernel restart. Without a binding nothing is kept.

func (*Submission) Unseal

func (s *Submission) Unseal(uses ...sealedsecrets.Use) ([]sealedsecrets.Secret, error)

Unseal resolves sealed handles the bound request carried, for the target and field each is stored for, all or none, and each once (sealedsecrets.Store.Resolve). Without a binding, or for a handle sealed from another request, route, target or field, it is PERMISSION_DENIED; the error names no handle.

type SyncNodeOptions

type SyncNodeOptions struct {
	// Force pushes even when the configuration did not change, as the
	// administrator's sync button does.
	Force bool
	// AckTimeout bounds the wait for the agent's acknowledgement;
	// DefaultAgentAckTimeout when zero.
	AckTimeout time.Duration
	// Now stamps the desired configuration; time.Now when nil.
	Now func() time.Time
}

SyncNodeOptions adjust a node sync.

Jump to

Keyboard shortcuts

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