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
- Variables
- func ConfigLaggingNodes(ctx context.Context, db *gorm.DB) (int64, error)
- func ConfigSnapshotOf(row model.KernelNodeDesiredConfig) *agentv1pb.ConfigSnapshot
- func ConfigVerdict(desired model.KernelNodeDesiredConfig, found bool, ...) string
- func DialTarget() sealedsecrets.Target
- func FamilyOfKind(name string) kernelnodeopsv1.OperationFamily
- func KnownKinds() []string
- func LoadConfigStatus(ctx context.Context, db *gorm.DB, node agentcontrol.AgentNode) (model.KernelNodeConfigStatus, bool, error)
- func LoadDesiredConfig(ctx context.Context, db *gorm.DB, node agentcontrol.AgentNode) (model.KernelNodeDesiredConfig, bool, error)
- func NewTaskID() string
- func NodeTarget(ref *kernelnodeopsv1.NodeRef) sealedsecrets.Target
- func RecordConfigStatus(ctx context.Context, db *gorm.DB, node agentcontrol.AgentNode, ...) (string, error)
- func RefreshDesiredConfig(ctx context.Context, db *gorm.DB, node agentcontrol.AgentNode, now time.Time) (model.KernelNodeDesiredConfig, bool, error)
- func RegisterNodeOperationExecutors(registry *Registry, sources SourcesFunc) error
- func StoreDesiredConfig(ctx context.Context, db *gorm.DB, cfg *DesiredConfig, now time.Time) (model.KernelNodeDesiredConfig, bool, error)
- func Terminal(state string) bool
- func UseAgentStreams(streams agentstreams.Streams)
- func UseWebSocketAgents(agents agentstreams.WebSockets)
- type Acceptance
- type AdminError
- type AdminFanOut
- type AdminOperation
- type AdminPage
- type AdminQuery
- type AdminTarget
- type AgentDiagnostic
- type AgentDiagnosticRequest
- type AgentDirectory
- type AgentOperation
- type AgentOperationRequest
- type AgentSources
- type Authorizer
- type BoundRequest
- type ConnectionTest
- type Credentials
- type DesiredConfig
- type Diagnosis
- type DiagnosticValidationError
- type Engine
- type Executor
- type ExecutorFunc
- type Forward
- type NodeSync
- type Outcome
- type Preparer
- type QueryError
- type Registry
- type Retirements
- type Run
- type SecretDocuments
- type Server
- type SourcesFunc
- type Submission
- type SyncNodeOptions
Constants ¶
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.
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" )
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).
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.
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.
const ( DiagnosisAgentCapability = "diag" DiagnosisAgentCapabilityVersion = "v1" )
The agent capability a node vantage needs (decision D10): diag.v1.
const AgentEnrollmentActor = "kernelnodeops"
AgentEnrollmentActor names the kernel's KernelNodeOps in the audit entry of an enrollment credential a package had issued.
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.
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.
const ( // MaxWait bounds a SubmitOperation wait (wait_timeout_ms). MaxWait = time.Minute )
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).
const SealedPrefix = "anix-sealed:v1:"
SealedPrefix starts a sealed secret handle (section 3.7).
Variables ¶
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.
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.
var ErrNodeGone = errors.New("node does not exist")
ErrNodeGone reports a node whose row no longer exists.
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 ¶
ConfigLaggingNodes counts the nodes whose agent reported a configuration status and whose applied revision is behind the desired one.
func ConfigSnapshotOf ¶
func ConfigSnapshotOf(row model.KernelNodeDesiredConfig) *agentv1pb.ConfigSnapshot
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 ¶
func NodeTarget(ref *kernelnodeopsv1.NodeRef) sealedsecrets.Target
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 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 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.
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 ¶
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.
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 ¶
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 ¶
Prune deletes the operations that ended before the retention and the events older than theirs. A request id pruned may be used again.
type Executor ¶
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 ¶
ExecutorFunc adapts a function to Executor.
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.
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.
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 ¶
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.
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) Prepared ¶
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.
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. Nil
// answers LEGACY for every table: nothing is split before NO-2.
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 ¶
func (s *Server) For(host packagebridge.HostIdentity) kernelnodeopsv1.KernelNodeOpsServer
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.