fleetclient

package
v0.1.8 Latest Latest
Warning

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

Go to latest
Published: Aug 15, 2026 License: Apache-2.0 Imports: 21 Imported by: 0

Documentation

Overview

Package fleetclient is the agent-side HTTP client for the fleet transport (#410): it enrols, heartbeats, claims work and reports results against the control plane's /api/v1/fleet API. It is used by the synapse-agent binary. All requests carry the protocol version header and a bearer credential; the agent's certificate/token is supplied by the caller and never logged here.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func GenerateKeyAndCSR

func GenerateKeyAndCSR(commonName string) (csrPEM, keyPEM []byte, err error)

GenerateKeyAndCSR creates a fresh P-256 key pair and a PKCS#10 certificate-signing request for commonName, returning both PEM-encoded. The private key never leaves the agent; only the CSR is sent to the control plane, which signs it with the fleet CA (see internal/infrastructure/fleetca). P-256 satisfies the CA's minimum key-strength check (ECDSA >= 256 bits).

func ReadEnrolTokenFile

func ReadEnrolTokenFile(path string) (string, error)

ReadEnrolTokenFile reads a one-time enrolment token from path, treating an ABSENT file as "no token supplied" rather than as an error.

The distinction is the whole point. An enrolment token is consumed on first use, after which the agent holds a long-lived credential and the token is dead weight — so an operator deleting the consumed secret is doing the right thing. If a missing file were fatal, that correct hygiene would mean the agent could never restart, which is how a Kubernetes deployment ends up unable to come back after its Secret is cleaned up. EnsureEnrolled already decides correctly from here: a stored credential wins, and only "no credential AND no token" is an error.

Every OTHER read failure stays an error. A file that exists but cannot be read — wrong mode, a directory, a broken mount — is a misconfiguration, and silently treating it as "no token" would convert it into a confusing enrolment failure somewhere further away.

func SecretModeEnforced

func SecretModeEnforced() bool

SecretModeEnforced reports whether this platform enforces Unix permission bits on a file.

It is false on Windows, where os.Chmod only toggles the read-only attribute. Callers use it to state the guarantee they actually have rather than the one they wrote down.

func ValidateControlPlaneURL

func ValidateControlPlaneURL(raw string) error

ValidateControlPlaneURL refuses a cleartext control-plane URL so the bearer credential cannot traverse plaintext HTTP. http is allowed only for a loopback host (local development/testing). Shared by every agent binary so the transport-security rule is enforced identically.

func WriteSecret

func WriteSecret(path string, data []byte, mode os.FileMode) error

WriteSecret writes secret material and enforces the mode even if the file pre-existed with looser permissions (os.WriteFile applies the mode only on create). The explicit Chmod closes the window where a pre-seeded, world-readable file would keep its old mode after a rewrite. Exported so agent binaries reuse it for their own on-disk secrets (e.g. a buffered inventory) rather than duplicating it.

Types

type Client

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

Client talks to the control plane fleet API.

func New

func New(baseURL string, timeout time.Duration) *Client

New builds a client for baseURL (e.g. https://control-plane). timeout bounds each request.

func (*Client) ClaimWork

func (c *Client) ClaimWork(ctx context.Context, token string, max int) ([]Order, error)

ClaimWork claims up to max orders addressed to this agent.

func (*Client) Enrol

func (c *Client) Enrol(ctx context.Context, enrolToken string, req EnrolRequest) (EnrolResponse, error)

Enrol exchanges an enrolment token for an agent credential.

func (*Client) Heartbeat

func (c *Client) Heartbeat(ctx context.Context, token string, req EnrolRequest) (HeartbeatResponse, error)

Heartbeat reports liveness and current attributes and returns the control plane's version-skew signals.

func (*Client) Progress

func (c *Client) Progress(ctx context.Context, token, orderID string) error

Progress moves an order into the running state.

func (*Client) SendClusterInventory

func (c *Client) SendClusterInventory(ctx context.Context, token string, snap any) error

SendClusterInventory posts a collected Kubernetes cluster snapshot to the control plane, which maps and persists it into the asset model (#446). snap must be a JSON-tagged clusterinventory.Snapshot; the caller passes it as the marshalable value so this package keeps no domain dependency.

func (*Client) SendHostInventory

func (c *Client) SendHostInventory(ctx context.Context, token string, inv any) error

SendHostInventory posts a collected VM host inventory to the control plane, which persists the host into the asset model (#446). inv must be a JSON-tagged hostinventory.HostInventory; the caller passes it as the marshalable value so this package keeps no domain dependency.

func (*Client) SubmitResult

func (c *Client) SubmitResult(ctx context.Context, token, orderID, status, reason string) error

SubmitResult reports the terminal outcome of an order.

type Credential

type Credential struct {
	AgentID        string `json:"agent_id"`
	Token          string `json:"token"`
	CertificatePEM string `json:"certificate_pem,omitempty"`
}

Credential is a persisted agent identity. Token is a secret: the file is written 0600 and its contents are never logged.

func EnsureEnrolled

func EnsureEnrolled(ctx context.Context, e Enroller, store *CredentialStore, enrolToken string, req EnrolRequest) (Credential, error)

EnsureEnrolled returns a stored credential, or on first run generates a P-256 key + CSR, enrols via e using enrolToken, and persists the result. req carries the agent's name/platform/version/ capabilities; its CSRPEM is filled in here (the private key never leaves the host — only the CSR is sent). It errors when there is neither a stored credential nor an enrolment token.

type CredentialStore

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

CredentialStore persists an agent credential + private key under a state directory. It is shared by every agent binary so the security-sensitive persistence (0600, chmod on rewrite) lives in one place.

func NewCredentialStore

func NewCredentialStore(dir string) *CredentialStore

NewCredentialStore returns a store rooted at dir.

func (*CredentialStore) Load

func (s *CredentialStore) Load() (Credential, bool)

Load returns a stored credential, or ok=false when none is present/usable.

func (*CredentialStore) Persist

func (s *CredentialStore) Persist(cred Credential, keyPEM []byte) error

Persist writes the credential (and the private key, when supplied) with 0600 permissions.

type EnrolRequest

type EnrolRequest struct {
	Name         string   `json:"name"`
	Platform     string   `json:"platform"`
	OSVersion    string   `json:"os_version"`
	AgentVersion string   `json:"agent_version"`
	Capabilities []string `json:"capabilities"`
	CSRPEM       string   `json:"csr_pem,omitempty"`
}

EnrolRequest is the agent's enrolment payload; CSRPEM is optional (certificate identity).

type EnrolResponse

type EnrolResponse struct {
	AgentID        string `json:"agent_id"`
	Token          string `json:"token"`
	CertificatePEM string `json:"certificate_pem,omitempty"`
}

EnrolResponse carries the once-only credential material.

type Enroller

type Enroller interface {
	Enrol(ctx context.Context, enrolToken string, req EnrolRequest) (EnrolResponse, error)
}

Enroller is the subset of the client EnsureEnrolled needs; *Client satisfies it, and an agent's test fake can too.

type HeartbeatResponse

type HeartbeatResponse struct {
	Proto                    string `json:"proto"`
	ControlPlaneVersion      string `json:"control_plane_version"`
	MinSupportedAgentVersion string `json:"min_supported_agent_version"`
}

HeartbeatResponse carries the control plane's version-skew signals (#412): its own version and the minimum agent version it will serve. An agent uses these to update itself or to refuse running against a control plane older than it requires.

type Order

type Order struct {
	ID         string `json:"ID"`
	Capability string `json:"Capability"`
	AssetID    string `json:"AssetID"`
}

Order is the subset of a work order the agent needs to act. The tags are PascalCase deliberately: the control plane serialises domain/workorder.WorkOrder with NO json tags, so encoding/json emits the exact Go field names (ID, Capability, AssetID). Matching that here is what lets these decode; snake_case tags would silently zero these fields. Verified against the server's claim handler.

Jump to

Keyboard shortcuts

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