control

package
v0.0.9 Latest Latest
Warning

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

Go to latest
Published: Aug 23, 2026 License: AGPL-3.0 Imports: 22 Imported by: 0

Documentation

Overview

Package control is the enrolled agent's side of the controller protocol (SPEC §6; proto/shieldlist/v1/agent.proto): enrolment, the sync round trip, the long-lived stream, and the loop that keeps the two sides agreeing — what this machine decided goes up, what it must now hold comes down.

The agent dials out and never listens. Every cursor it presents is its own, persisted in its store after the answer has been acted on, so a lost message is asked for again and nothing is applied twice.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Enrol

func Enrol(ctx context.Context, base, token, hostname, agentVersion, minController string) (credential, fingerprint, controllerVersion string, err error)

Enrol presents a join token and returns the credential, the controller's version and the fingerprint the connection was pinned to (empty when the token carried none). The token is `secret` or `secret@fingerprint`.

func Run

func Run(ctx context.Context, opts Options) error

Run keeps the agent in step with its controller until the context ends. It never returns an error for the controller being away: that is the normal state of a network, and the agent keeps enforcing meanwhile (SPEC §5.4). Only a missing store or handler is fatal.

Types

type Client

type Client struct {
	URL           string // base, e.g. https://controller.example.net:17453
	Hostname      string
	Credential    string
	Fingerprint   string // pinned; empty verifies against the system roots
	AgentVersion  string
	MinController string
	// contains filtered or unexported fields
}

Client talks to one controller as one agent.

func NewClient

func NewClient(base, hostname, credential, fingerprint, agentVersion, minController string) *Client

NewClient builds a client. A pinned fingerprint replaces chain verification: the join token carried it, and it names one controller.

func (*Client) Stream

func (c *Client) Stream(ctx context.Context, revision string, fleetCursor, liftCursor, orderCursor uint64, handle func(*pb.SyncResponse) error) error

Stream holds the long-lived connection and hands every pushed answer to handle, until the context ends, the connection drops, or handle returns an error. Cursors and the held revision are what the stream starts from.

func (*Client) Sync

func (c *Client) Sync(ctx context.Context, req *pb.SyncRequest) (*pb.SyncResponse, error)

Sync is one round trip.

type HTTPError

type HTTPError struct {
	Status int
	Body   string
}

HTTPError is a status the controller answered with, and what it said.

func (*HTTPError) Error

func (e *HTTPError) Error() string

type Handler

type Handler interface {
	// ApplyConfig makes the bundle under dir the running configuration.
	// An error keeps the previous revision on record, so a fixed bundle
	// is fetched again; the same broken revision is not retried.
	ApplyConfig(revision, dir string) error
	ApplySettings(s Settings)
	// ApplyFleet enforces sanctions other machines decided (SPEC §5.4
	// Distribution). Origin says which machine and rule.
	ApplyFleet(sanctions []store.Sanction) error
	// ApplyLift lifts a sanction on the controller's word; withdraw asks
	// that what this machine reported about the address be withdrawn too.
	ApplyLift(value, by, reason string, withdraw bool) error
	// ApplyLifts is ApplyLift for a sync's worth at once.
	ApplyLifts(lifts []Lift) error
	// ApplyOrder bans on the operator's word — through the same checks as
	// any sanction — and answers what happened in a sentence.
	ApplyOrder(o Order) (string, error)
	// ApplyOrders is ApplyOrder for a sync's worth of orders at once —
	// one pass, one transaction, one batch — answering one result each.
	ApplyOrders(os []Order) ([]string, error)
	// GeoHash is the content hash of the geo database this machine holds
	// of one kind (country, asn), "" for none; ApplyGeo installs a new
	// edition, hash and all, and turns on what waited for it.
	GeoHash(kind string) string
	ApplyGeo(kind, hash string, data []byte) error
}

Handler is the agent's side of what comes down. Each call blocks until the agent has acted, because the cursor is persisted only after.

type HitCount

type HitCount struct {
	Value string
	Count int // hits under rules in prod
	Test  int // hits under rules in test (a detection in the making, never a ban)
	At    time.Time
}

HitCount is one offender's positive hits since the last sync (a flow); At names the minute when the hits are a backfill, zero otherwise.

type Lift

type Lift struct {
	ID             uint64
	Value, By      string
	Reason         string
	WithdrawReport bool
}

Lift is one lift as the sync carries it.

type Loop

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

Loop is one running conversation with the controller.

type Options

type Options struct {
	// Store is the loop's own handle on the agent's state, for READING:
	// the round transaction belongs to the main loop, and so does every
	// write — a second writer beside a long-lived read-then-write round
	// is how that round finds its snapshot stale and loses its writes.
	Store *store.Store
	// Write runs fn with the main loop's store, on the main loop, inside
	// its round — where every write to the agent's state belongs.
	Write         func(fn func(*store.Store) error) error
	URL           string
	Hostname      string
	AgentVersion  string
	MinController string
	// ReceivedDir is where bundles land: a full agent tree the agent's
	// loader reads beside its own files.
	ReceivedDir string
	// Watch, when set, snapshots the offenders being counted but not yet
	// convicted (eval.Watch): sent on every sync, bounded, never stored.
	Watch func(limit int, linesSince time.Time) []Watched
	// Flow, when set, hands over the positive hits since the last sync by
	// offender (eval.Flow) — the flow the controller draws over time —
	// and Unflow puts them back when the sync failed.
	Flow   func() []HitCount
	Unflow func([]HitCount)
	// Backfill, when set, reads the hits still in the windows that came
	// before the given moment, by minute — sent once, when the controller
	// says from when it holds this machine's flow.
	Backfill func(before time.Time) []HitCount
	// Interval is the sync cadence — the upload of what this machine
	// decided, and the poll fallback when the stream cannot be held.
	Interval time.Duration
	// Wake, when set, is nudged by the machine's own loop the moment a
	// round has banned someone or scored a hit: the sync leaves on their
	// heels (a second at most, never more often than nudgeFloor) instead
	// of waiting out the interval — what the console shows is then this
	// second's bans and hits, not this half-minute's. A quiet machine
	// syncs on the interval alone.
	Wake    <-chan struct{}
	Handler Handler
	Logger  *slog.Logger
}

Options configure the loop.

type Order

type Order struct {
	ID       uint64
	Value    string
	Seconds  int64 // 0: permanent
	By       string
	Reason   string
	ReportAs string
	Ports    []uint16
	// Lines: the log lines the operator banned from (a detection's), the
	// manual sanction's evidence here.
	Lines []store.EvidenceLine
}

Order is a ban the operator decided by hand, as the sync carries it.

type Progress

type Progress struct {
	Threshold   string
	Count, Need int
}

Progress mirrors eval.Progress: one threshold's count.

type Settings

type Settings struct {
	State        string // on | detect-only
	EnforceFleet bool
	OfferFleet   bool
	Reporting    string // agent | controller | off
	Level        string
}

Settings are the controller's per-machine decisions that are not files (SPEC §5.4, §5.5), as the agent receives them on every answer.

type Watched

type Watched struct {
	Rule, Threshold, Value string
	Count, Need            int
	First, Last            time.Time
	Lines                  []WatchedLine
	Progress               []Progress
	Exempt                 string // why the offender is exempt here, "" when it is not
}

Watched mirrors eval.Watched without importing it: what a sync carries.

type WatchedLine

type WatchedLine struct {
	At     time.Time
	Raw    string
	Fields map[string]string
}

WatchedLine is one hit's log line.

Jump to

Keyboard shortcuts

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