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 ¶
- func GenerateKeyAndCSR(commonName string) (csrPEM, keyPEM []byte, err error)
- func ReadEnrolTokenFile(path string) (string, error)
- func SecretModeEnforced() bool
- func ValidateControlPlaneURL(raw string) error
- func WriteSecret(path string, data []byte, mode os.FileMode) error
- type Client
- func (c *Client) ClaimWork(ctx context.Context, token string, max int) ([]Order, error)
- func (c *Client) Enrol(ctx context.Context, enrolToken string, req EnrolRequest) (EnrolResponse, error)
- func (c *Client) Heartbeat(ctx context.Context, token string, req EnrolRequest) (HeartbeatResponse, error)
- func (c *Client) Progress(ctx context.Context, token, orderID string) error
- func (c *Client) SendClusterInventory(ctx context.Context, token string, snap any) error
- func (c *Client) SendHostInventory(ctx context.Context, token string, inv any) error
- func (c *Client) SubmitResult(ctx context.Context, token, orderID, status, reason string) error
- type Credential
- type CredentialStore
- type EnrolRequest
- type EnrolResponse
- type Enroller
- type HeartbeatResponse
- type Order
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func GenerateKeyAndCSR ¶
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 ¶
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 ¶
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 ¶
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 ¶
New builds a client for baseURL (e.g. https://control-plane). timeout bounds each request.
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) SendClusterInventory ¶
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 ¶
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.
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.