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 ¶
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
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 ¶
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 ¶
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) 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 ¶
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 ¶
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.
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.
type StatusError ¶
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).