federation

package
v0.8.2 Latest Latest
Warning

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

Go to latest
Published: Sep 30, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

Documentation

Overview

Package federation is the laptop half of hybrid enrolment: the client for the organisation's device routes, and the forwarder pushing this daemon's audit rows upward. It never writes into the chain on anyone else's behalf.

Index

Constants

View Source
const AuditActor = "wardyn/federation"

AuditActor names this package on the local rows it writes.

Variables

This section is empty.

Functions

func OrgURLSHA256 added in v0.8.2

func OrgURLSHA256(orgURL string) string

OrgURLSHA256 is hex(sha256(url)) of the organisation URL, the form Credential records. The URL is normalised first, so a cosmetic edit is not a change of organisation: surrounding space, the case of scheme and host, a default port (:443 for https, :80 for http) and trailing slashes do not matter. A value that does not parse as a URL with a host is hashed as trimmed.

func TokenSHA256

func TokenSHA256(token string) string

TokenSHA256 is hex(sha256(token)), the form Credential records.

Types

type Client

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

Client calls the organisation's device routes.

func NewClient

func NewClient(orgURL string) *Client

NewClient returns a client for orgURL, using the shared http.DefaultTransport (patched at boot by wardynd). Redirects are refused: the bearer is for this URL only.

func (*Client) Enrol

func (c *Client) Enrol(ctx context.Context, enrolmentToken string) (types.DeviceEnrolResponse, error)

Enrol trades a single-use enrolment token for this device's credential.

func (*Client) Heartbeat

func (c *Client) Heartbeat(ctx context.Context, cred Credential) (int64, error)

Heartbeat is the idle keepalive; it answers the same recorded cursor.

func (*Client) Push

func (c *Client) Push(ctx context.Context, cred Credential, rows []types.FederatedAuditEvent) (int64, error)

Push sends one batch of this daemon's chained rows, oldest first, and returns the organisation's recorded cursor for the device.

type Credential

type Credential struct {
	DeviceID             uuid.UUID `json:"device_id"`
	Token                string    `json:"token"`
	EnrolmentTokenSHA256 string    `json:"enrolment_token_sha256"`
	OrgURLSHA256         string    `json:"org_url_sha256,omitempty"`
	ResetPending         bool      `json:"reset_pending,omitempty"`
	Name                 string    `json:"name,omitempty"`
}

Credential is what the laptop keeps in its secret store after enrolling: device id, wdd_ bearer, and the enrolment token's SHA-256 (tells a fresh token from the spent one MDM leaves behind). ResetPending, persisted true only on a fresh credential, makes the post-enrolment reset resumable across a crash without re-spending the token or clearing a later, genuine revocation. OrgURLSHA256 binds the credential to the WARDYN_ORG_URL it was enrolled at, so the bearer is never sent to a different host; empty on a credential stored before that field existed (bootHybrid adopts the configured URL for it).

type Forwarder

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

Forwarder pushes this daemon's chained audit rows to the organisation from the durable cursor in org_federation, one batch at a time — at-least-once: the cursor moves only after acknowledgement, and the organisation recognises a re-sent row by seq + row hash (the cursor keeps both, since a local table reset can reuse the seq).

func NewForwarder

func NewForwarder(c *Client, st Store, cred Credential, rec audit.Recorder) *Forwarder

NewForwarder returns a forwarder for cred. Nothing is read until Load or Run.

func (*Forwarder) Done

func (f *Forwarder) Done() <-chan struct{}

Done closes once Run has returned — after ctx ends or the organisation revokes this device. A caller running Run on its own goroutine must join it (cancel, then <-Done()) before treating shutdown as complete. Run must be called at most once per Forwarder; a second call double-closes done and panics.

func (*Forwarder) Load

func (f *Forwarder) Load(ctx context.Context) error

Load reads the durable cursor and revoked mark, called before the server starts so a laptop revoked before a restart refuses new runs from its first request, regardless of organisation reachability.

func (*Forwarder) Run

func (f *Forwarder) Run(ctx context.Context)

Run forwards until ctx ends or the organisation revokes this device. A revoked forwarder stops calling the organisation for the rest of the process: its credential is dead, and re-enrolment is a boot-time act.

func (*Forwarder) Status

func (f *Forwarder) Status() Status

Status returns a copy of the current state.

type Status

type Status struct {
	DeviceID  uuid.UUID
	AckedSeq  int64
	HeadSeq   int64
	LastOK    time.Time
	Revoked   bool
	LastError string
}

Status is the forwarder's published state. DeviceID is for this daemon's own logs and operators; /healthz, which is anonymous, never carries it.

func (Status) Lag

func (s Status) Lag() int64

Lag is how many local rows the organisation does not yet hold.

type StatusError

type StatusError struct {
	Code            int
	RetryAfter      time.Duration
	Message         string
	WWWAuthenticate string
}

StatusError is a non-2xx answer from the organisation. RetryAfter is the parsed Retry-After header; WWWAuthenticate lets Revoked tell the organisation's own 401 from one injected elsewhere on the path.

func (*StatusError) Error

func (e *StatusError) Error() string

func (*StatusError) Revoked

func (e *StatusError) Revoked() bool

Revoked reports the definitive "this credential is dead" answers: 410, and a 401 carrying deviceAuth's own realm (revoked and unknown are deliberately one answer there). A bare 401 without that realm is not revocation.

type Store

type Store interface {
	ListAuditEventsAfterSeq(ctx context.Context, seq int64, limit int) ([]types.FederatedAuditEvent, error)
	GetFederationCursor(ctx context.Context) (seq int64, rowHash string, err error)
	SetFederationCursor(ctx context.Context, seq int64, rowHash string) error
	AuditHeadSeq(ctx context.Context) (int64, error)
	FederationRevoked(ctx context.Context) (bool, error)
	MarkFederationRevoked(ctx context.Context) error
	ResetFederation(ctx context.Context) error
}

Store is the local table the forwarder reads and the durable org_federation row it keeps: the cursor and the revoked mark (store.PG).

Jump to

Keyboard shortcuts

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