Documentation
¶
Index ¶
- Constants
- Variables
- func Capabilities() []string
- func CheckHealth(dataDir string, maxAge time.Duration, now time.Time) error
- func HashToken(token string) string
- func HealthPath(dataDir string) string
- func NewToken() (cleartext, hash, id, prefix string, err error)
- func Run(ctx context.Context, cfg AgentConfig, logger *slog.Logger) error
- func RunCollector(ctx context.Context, id *Identity, rt runtime.Runtime, label, nodeName string, ...) error
- func RunEnrollment(ctx context.Context, id *Identity, dataDir string, enrollmentToken string, ...) error
- func RunWithReconnect(ctx context.Context, c *Client, id *Identity, logger *slog.Logger, ...) error
- func StartHealthReporter(ctx context.Context, dataDir string, interval time.Duration, ...) <-chan struct{}
- func TokenIDFromHash(hash string) string
- func TokenPrefix(token string) string
- func WriteHealth(dataDir string, t time.Time) error
- type Agent
- type AgentConfig
- type Client
- func (c *Client) Close() error
- func (c *Client) DialPush(ctx context.Context, id *Identity, logger *slog.Logger, hooks StreamHooks) (*PushStream, error)
- func (c *Client) EnableCommands(rt runtime.Runtime, agentVersion string, logger *slog.Logger)
- func (c *Client) Register(ctx context.Context, req *agentpb.RegisterRequest) (*agentpb.RegisterResponse, error)
- type CommandRunner
- type EnrollmentToken
- type Identity
- type OSIdentity
- type PushStream
- type Spool
- func (s *Spool) Acked(seq uint64)
- func (s *Spool) Attach(sink eventSink)
- func (s *Spool) Close() error
- func (s *Spool) Depth() (int64, error)
- func (s *Spool) Detach()
- func (s *Spool) Discard() error
- func (s *Spool) Drain(ctx context.Context) error
- func (s *Spool) Dropped() (int64, error)
- func (s *Spool) PurgeExpired(ctx context.Context) error
- func (s *Spool) RateLimited(retryAfter time.Duration)
- func (s *Spool) ResetDropped()
- func (s *Spool) Rewind()
- func (s *Spool) Send(evt *agentpb.AgentEvent) error
- type SpoolConfig
- type StreamHooks
Constants ¶
const ( RuntimeDocker = "docker" RuntimeSwarm = "swarm" RuntimeKubernetes = "kubernetes" )
Runtime labels reported by the agent during enrollment. "docker" / "kubernetes" come from runtime.Runtime.Name(); "swarm" is derived from the swarm.Detector check applied to the docker runtime.
const ( HealthInterval = 15 * time.Second HealthMaxAge = 60 * time.Second )
HealthInterval is how often a running agent refreshes its liveness file, and HealthMaxAge how stale that file may be before the agent counts as stuck. The gap between them absorbs a slow tick without flapping.
const CapabilityLogs = "logs"
CapabilityLogs mirrors agentserver.CapabilityLogs. Declared here rather than imported: the agent must not depend on the server package.
const TokenPrefixLen = 14
TokenPrefixLen is how much of a token is kept in the clear for display. The prefix is `mnt_enr_` plus six base32 characters: enough for an operator to tell two tokens apart in a list, and 30 bits out of 256 — worthless to someone holding only the hash, which is the whole point of storing it.
Variables ¶
var ( ErrAgentNotFound = errors.New("agent not found") ErrTokenNotFound = errors.New("enrollment token not found") ErrTokenAlreadyConsumed = errors.New("enrollment token already consumed") ErrTokenExpired = errors.New("enrollment token expired") ErrLabelTooLong = errors.New("label exceeds 64 characters") ErrAgentRevoked = errors.New("agent is revoked") ErrBadSignature = errors.New("invalid Ed25519 signature") ErrClockSkew = errors.New("clock skew exceeds 300s") ErrAgentUnknown = errors.New("agent unknown") ErrHostLimitReached = errors.New("agent host limit reached") )
Sentinel errors for agent store operations.
var ErrAgentRevokedServer = errors.New("agent revoked by server")
ErrAgentRevokedServer is returned by RunWithReconnect when the server revokes the agent. The caller should exit without retrying.
var ErrNoStream = errors.New("no agent stream available")
ErrNoStream is returned by a disabled spool with no stream attached.
Functions ¶
func Capabilities ¶ added in v1.3.8
func Capabilities() []string
Capabilities lists the command families this build understands. Sent at auth so the server can reject unsupported requests immediately instead of timing out.
func CheckHealth ¶ added in v1.4.0
CheckHealth reports whether an agent refreshed its liveness file within maxAge. A missing file is returned as an fs.ErrNotExist-wrapped error so callers can tell "no agent runs here" from "the agent is stuck".
func HashToken ¶ added in v1.3.9
HashToken returns the hex-encoded SHA-256 of an enrollment token. This is the only form of the token the database ever sees.
func HealthPath ¶ added in v1.4.0
HealthPath returns the liveness file path inside an agent data directory.
func NewToken ¶ added in v1.3.9
NewToken mints an enrollment token and returns its cleartext, the hash that gets persisted, the id derived from that hash, and the display prefix.
The cleartext is returned once and never stored: it exists in the creation response and nowhere else. Everything the server needs afterwards to accept, list or revoke the token is derivable from the hash.
func Run ¶
Run is the main agent entry point (mode=agent). It detects the local runtime, loads or creates the agent identity, enrolls if needed, then enters the long-lived Push streaming loop (US2).
func RunCollector ¶
func RunCollector(ctx context.Context, id *Identity, rt runtime.Runtime, label, nodeName string, spool *Spool, logger *slog.Logger) error
RunCollector starts collecting events from the local runtime and pushing them to stream. rt is the already-connected runtime resolved by agent.Run; label is the reported runtime kind ("docker", "swarm" or "kubernetes"); nodeName is the Kubernetes node the agent runs on, empty unless the operator set it. Blocks until ctx is cancelled or a fatal push error occurs.
func RunEnrollment ¶
func RunEnrollment( ctx context.Context, id *Identity, dataDir string, enrollmentToken string, runtimeLabel string, label string, agentVersion string, client *Client, ) error
RunEnrollment sends a RegisterRequest to the server using the enrollment token. On success it marks the identity as registered and persists it to dataDir.
func RunWithReconnect ¶
func RunWithReconnect( ctx context.Context, c *Client, id *Identity, logger *slog.Logger, hooks StreamHooks, onStream func(ctx context.Context, stream *PushStream) error, ) error
RunWithReconnect runs onStream with exponential backoff reconnect. Returns nil when ctx is cancelled, ErrAgentRevokedServer when the server revokes the agent. Backoff: min(60s, 1s * 2^attempt) ±25% jitter. Attempt resets to 0 if stream was stable >30s.
func StartHealthReporter ¶ added in v1.4.0
func StartHealthReporter(ctx context.Context, dataDir string, interval time.Duration, logger *slog.Logger) <-chan struct{}
StartHealthReporter refreshes the liveness file every interval until ctx is done. The returned channel is closed once the reporter has stopped writing, so a caller that needs the directory settled can wait for it: cancelling the context only asks the goroutine to stop, it does not mean an in-flight write has landed.
Reachability of the server is deliberately not part of the signal: an agent that cannot reach the server is already reported as disconnected there, and letting an orchestrator restart the agent over it would fix nothing while hiding the actual outage.
func TokenIDFromHash ¶ added in v1.3.9
TokenIDFromHash derives the opaque id used in API paths from the token hash. Truncating is safe: the id identifies a row, it never authorises anything — enrollment matches on the full hash.
func TokenPrefix ¶ added in v1.3.9
TokenPrefix returns the leading, non-secret part of a token.
Types ¶
type Agent ¶
type Agent struct {
AgentID string
PublicKey []byte // raw 32 bytes Ed25519
Hostname string
Label string
OSArch string
AgentVersion string
DetectedRuntime string // "docker"|"swarm"|"kubernetes"
Status string // "active"|"revoked"
LastSeenAt *time.Time
CreatedAt time.Time
RevokedAt *time.Time
RevokedBy *string
OSID string
OSVersionID string
OSPrettyName string
OSSource string // ""|"host_file"|"kubernetes_node"
OSReportedAt *time.Time
}
Agent represents a remote agent registered with the server.
type AgentConfig ¶
type AgentConfig struct {
DataDir string
ServerURL string
EnrollmentToken string
RuntimeOverride string
Label string
NodeName string
AgentVersion string
InsecureSkipVerify bool
ProxyLabels bool
SpoolMaxMemoryBytes int64
SpoolMaxDiskBytes int64
SpoolMaxAgeSeconds int64
}
AgentConfig holds runtime configuration for an agent process.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client wraps the gRPC IngestClient with connection lifecycle management.
func NewClient ¶
func NewClient(ctx context.Context, serverURL string, insecureSkipVerify bool, logger *slog.Logger) (*Client, error)
NewClient dials the server at serverURL and returns a ready-to-use Client. serverURL should start with "grpcs://" (TLS) or "grpc://" (plaintext, not recommended). If insecureSkipVerify is true, TLS certificate validation is skipped (debug only).
func (*Client) DialPush ¶
func (c *Client) DialPush(ctx context.Context, id *Identity, logger *slog.Logger, hooks StreamHooks) (*PushStream, error)
DialPush opens the bidirectional Push stream, performs the Ed25519 auth handshake, and returns a PushStream ready to send events.
func (*Client) EnableCommands ¶ added in v1.3.8
EnableCommands lets the server issue commands (log reads) against rt, and makes the running build known at each connect. Must be called before DialPush; the receive loop starts inside it, so a later hook-up would race.
func (*Client) Register ¶
func (c *Client) Register(ctx context.Context, req *agentpb.RegisterRequest) (*agentpb.RegisterResponse, error)
Register calls the unary RegisterAgent RPC.
type CommandRunner ¶ added in v1.3.8
type CommandRunner struct {
// contains filtered or unexported fields
}
CommandRunner executes server-issued commands against the local runtime. One instance is shared across reconnections; in-flight work is keyed by the server-minted request id so a Cancel can reach it.
func NewCommandRunner ¶ added in v1.3.8
func NewCommandRunner(rt runtime.Runtime, logger *slog.Logger) *CommandRunner
NewCommandRunner builds a runner over rt. rt may be nil, in which case every command is answered with an error rather than silently dropped.
func (*CommandRunner) CancelAll ¶ added in v1.3.8
func (r *CommandRunner) CancelAll()
CancelAll aborts every in-flight command, used when the stream goes away.
func (*CommandRunner) Handle ¶ added in v1.3.8
func (r *CommandRunner) Handle(ctx context.Context, out resultSender, cmd *agentpb.AgentCommand)
Handle dispatches cmd, returning immediately: work runs in its own goroutine so the stream's receive loop is never blocked by a log tail.
type EnrollmentToken ¶
type EnrollmentToken struct {
TokenID string
TokenHash string
TokenPrefix string
CreatedAt time.Time
ExpiresAt time.Time
ConsumedAt *time.Time
ConsumedByAgentID *string
}
EnrollmentToken represents a one-time token for enrolling an agent. The cleartext is deliberately absent: only its SHA-256 is persisted, so a copy of the database file yields nothing replayable. TokenPrefix is the leading, non-secret slice kept so a read path can still name the token it matched.
func (*EnrollmentToken) Masked ¶ added in v1.3.9
func (t *EnrollmentToken) Masked() string
Masked renders a token for display from the stored prefix alone. The rest is unrecoverable by design, so this is all any read path can ever show.
type Identity ¶
type Identity struct {
AgentID string `json:"agent_id"`
PublicKey []byte `json:"public_key"`
PrivateKey []byte `json:"private_key"`
Registered bool `json:"registered"`
RegisteredAt *time.Time `json:"registered_at,omitempty"`
}
Identity holds the persistent agent identity stored on disk.
func LoadOrCreate ¶
LoadOrCreate loads an existing identity from dataDir/identity.json, or generates and persists a new Ed25519 keypair if no file exists. File is created with mode 0600.
type OSIdentity ¶ added in v1.7.0
OSIdentity is the operating system identity an agent reports for its host.
type PushStream ¶
type PushStream struct {
// contains filtered or unexported fields
}
PushStream wraps an authenticated bidirectional gRPC stream for pushing agent events. Multiple goroutines may call Send concurrently.
func (*PushStream) Close ¶
func (ps *PushStream) Close()
Close signals the end of the send side of the stream.
func (*PushStream) Send ¶
func (ps *PushStream) Send(evt *agentpb.AgentEvent) error
Send wraps evt in a ClientMessage and delivers it unchanged.
func (*PushStream) SendResult ¶ added in v1.3.8
func (ps *PushStream) SendResult(res *agentpb.CommandResult) error
SendResult delivers a reply to a server-issued command. Safe to call from the per-request goroutines, which share the stream with the telemetry senders.
func (*PushStream) SendStatus ¶ added in v1.6.0
func (ps *PushStream) SendStatus(st *agentpb.SpoolStatus) error
SendStatus reports the spool's state. It is not telemetry: the server neither rate-limits it nor dispatches it.
func (*PushStream) Wait ¶
func (ps *PushStream) Wait(ctx context.Context) error
Wait blocks until the receive goroutine exits and returns its error (nil on clean close). If ctx is cancelled before recvLoop exits, it returns ctx.Err() immediately so the caller can proceed with shutdown. The leaked recvLoop goroutine terminates when the underlying gRPC stream is finally closed (typically by the deferred grpcClient.Close()).
type Spool ¶ added in v1.6.0
type Spool struct {
// contains filtered or unexported fields
}
Spool is the agent's bounded outbound queue.
func NewSpool ¶ added in v1.6.0
func NewSpool(dataDir string, cfg SpoolConfig, logger *slog.Logger) *Spool
NewSpool opens the spool for dataDir, degrading to memory only if it cannot.
func (*Spool) Attach ¶ added in v1.6.0
func (s *Spool) Attach(sink eventSink)
Attach binds the spool to a live stream, rewinding to the last ack.
func (*Spool) Depth ¶ added in v1.6.0
Depth reports how many events are waiting, buffered and stored.
func (*Spool) Detach ¶ added in v1.6.0
func (s *Spool) Detach()
Detach unbinds the current stream. Queued events wait for the next one.
func (*Spool) Drain ¶ added in v1.6.0
Drain sends everything queued on the attached stream, oldest first.
func (*Spool) Dropped ¶ added in v1.6.0
Dropped reports how many events were abandoned since the last connection.
func (*Spool) PurgeExpired ¶ added in v1.6.0
PurgeExpired drops everything observed before the retention window.
func (*Spool) RateLimited ¶ added in v1.6.0
RateLimited holds the drain for the delay the server asked for and rewinds to the last ack: the server drops a refused event without failing the stream.
func (*Spool) ResetDropped ¶ added in v1.6.0
func (s *Spool) ResetDropped()
ResetDropped clears the per-connection drop counter.
type SpoolConfig ¶ added in v1.6.0
SpoolConfig bounds what the spool may hold; both budgets at zero disable it.
func (SpoolConfig) Enabled ¶ added in v1.6.0
func (c SpoolConfig) Enabled() bool
Enabled reports whether events are queued rather than passed straight through.
type StreamHooks ¶ added in v1.6.0
StreamHooks lets the spool react to what the server says about the stream. A nil field ignores that signal.