enrollment

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Oct 3, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package enrollment provides the controller-side enrollment system for accepting or rejecting agent enrollment requests via NATS.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrAgentKeyNotFound means no key is stored for the machine ID. During
	// a rollout this is expected: no agent accepted before the store
	// existed has one.
	ErrAgentKeyNotFound = errors.New("no stored key for agent")

	// ErrNoPendingAgent means no enrollment request is waiting under the name
	// the caller gave. The API answers 404: there is nothing there to accept or
	// reject.
	ErrNoPendingAgent = errors.New("no pending agent")

	// ErrAgentKeyStoreUnavailable means the record could not be read or
	// decoded. It never resembles "no stored key", and is never treated as
	// verified.
	ErrAgentKeyStoreUnavailable = errors.New("agent key store unavailable")

	// ErrAgentKeySignatureMismatch means a key that is not this agent's
	// signed the message.
	ErrAgentKeySignatureMismatch = errors.New("agent signature mismatch")
)

The three causes of a failed lookup stay distinct so an operator can tell "this agent has not re-enrolled yet" from "something is forging messages" from "the store could not be read". Collapsing them hides the difference at exactly the moment it matters.

Functions

This section is empty.

Types

type AcceptedAgent

type AcceptedAgent struct {
	MachineID   string            `json:"machine_id"`
	Hostname    string            `json:"hostname"`
	PublicKey   ed25519.PublicKey `json:"public_key"`
	Fingerprint string            `json:"fingerprint"`
	AcceptedAt  time.Time         `json:"accepted_at"`

	// SupersededKey is the key replaced by the most recent rotation. It is
	// absent unless a rotation is inside its grace period.
	SupersededKey ed25519.PublicKey `json:"superseded_key,omitempty"`

	// SupersededUntil is the instant the superseded key stops being
	// accepted. Storing the instant rather than a duration means a restart
	// cannot silently extend the window.
	SupersededUntil time.Time `json:"superseded_until,omitempty"`
}

AcceptedAgent is the stored record of an accepted agent's public key. It outlives the pending record, which is deleted on acceptance, so every later message from that agent can be verified against the key the agent enrolled with.

Only enrollment acceptance creates or replaces a record: nothing an agent sends may introduce or change one.

type AgentKeyStore

type AgentKeyStore interface {
	LookupAgentKey(
		ctx context.Context,
		machineID string,
	) (*AcceptedAgent, error)
}

AgentKeyStore is the narrow lookup the verification callers depend on, so the job client and target resolution need not import this package wholesale. Satisfied by *Watcher.

type KVStore

type KVStore = jetstream.KeyValue

KVStore wraps the jetstream.KeyValue interface for testability.

type KeyRotationMessage

type KeyRotationMessage struct {
	NewPublicKey []byte `json:"new_public_key"`
}

KeyRotationMessage is published to agents when the controller rotates its keypair.

type NATSSubscriber

type NATSSubscriber interface {
	Subscribe(
		subj string,
		cb nats.MsgHandler,
	) (*nats.Subscription, error)
	PublishCore(
		subj string,
		data []byte,
	) error
}

NATSSubscriber defines the NATS operations needed by the enrollment watcher for subscribing to enrollment requests and publishing responses. Satisfied by the nats-client's *Client type (Subscribe + PublishCore).

type PKIProvider

type PKIProvider interface {
	PublicKey() ed25519.PublicKey
}

PKIProvider defines the PKI operations needed by the enrollment watcher to provide the controller's public key in acceptance responses.

type PendingAgent

type PendingAgent struct {
	MachineID   string    `json:"machine_id"`
	Hostname    string    `json:"hostname"`
	PublicKey   []byte    `json:"public_key"`
	Fingerprint string    `json:"fingerprint"`
	RequestedAt time.Time `json:"requested_at"`
}

PendingAgent represents an agent awaiting enrollment acceptance.

type Watcher

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

Watcher monitors NATS for agent enrollment requests and manages pending agents in a JetStream KV bucket.

func NewWatcher

func NewWatcher(
	logger *slog.Logger,
	nc NATSSubscriber,
	enrollmentKV KVStore,
	pkiProvider PKIProvider,
	autoAccept bool,
	namespace string,
) *Watcher

NewWatcher creates a new enrollment Watcher.

func (*Watcher) AcceptAgent

func (w *Watcher) AcceptAgent(
	ctx context.Context,
	machineID string,
) error

AcceptAgent accepts a pending agent by machine ID. It publishes an acceptance response containing the controller's public key to the agent's response subject and deletes the pending entry from KV.

func (*Watcher) AcceptByFingerprint

func (w *Watcher) AcceptByFingerprint(
	ctx context.Context,
	fingerprint string,
) error

AcceptByFingerprint scans the pending KV for an agent matching the given fingerprint and accepts it. Returns an error if no matching agent is found.

func (*Watcher) AcceptByHostname

func (w *Watcher) AcceptByHostname(
	ctx context.Context,
	hostname string,
) error

AcceptByHostname scans the pending KV for an agent matching the given hostname and accepts it. Returns an error if no matching agent is found.

func (*Watcher) KeyStore

func (w *Watcher) KeyStore() client.AgentKeyStore

KeyStore returns the watcher as the job client's agent key store, so response verification reads the same records acceptance writes and shares their cache.

func (*Watcher) ListPending

func (w *Watcher) ListPending(
	ctx context.Context,
) ([]PendingAgent, error)

ListPending returns all pending agents from the enrollment KV bucket.

func (*Watcher) LookupAgentKey

func (w *Watcher) LookupAgentKey(
	ctx context.Context,
	machineID string,
) (*AcceptedAgent, error)

LookupAgentKey returns the stored record for a machine ID. A missing record and an unreadable store are different answers: the first is ErrAgentKeyNotFound, the second ErrAgentKeyStoreUnavailable.

func (*Watcher) RejectAgent

func (w *Watcher) RejectAgent(
	ctx context.Context,
	machineID string,
	reason string,
) error

RejectAgent rejects a pending agent by machine ID. It publishes a rejection response to the agent's response subject and deletes the pending entry from KV.

func (*Watcher) RejectByHostname

func (w *Watcher) RejectByHostname(
	ctx context.Context,
	hostname string,
	reason string,
) error

RejectByHostname scans the pending KV for an agent matching the given hostname and rejects it. Returns an error if no matching agent is found.

func (*Watcher) RemoveAgentKey

func (w *Watcher) RemoveAgentKey(
	ctx context.Context,
	machineID string,
) error

RemoveAgentKey deletes an agent's stored record. After it returns, nothing signed by the removed key verifies.

func (*Watcher) RotateControllerKey

func (w *Watcher) RotateControllerKey() error

RotateControllerKey publishes the controller's current public key to all agents via the pki.rotate subject. Agents receiving this message move their current controller key to previous and store the new key, enabling a grace period where both keys are accepted.

func (*Watcher) SetRotationGracePeriod

func (w *Watcher) SetRotationGracePeriod(
	d time.Duration,
)

SetRotationGracePeriod sets how long a replaced key keeps verifying after a rotation. Called once at startup from the controller's PKI configuration.

Without it a rotation rejects everything signed in the moments before the new key was accepted, which is the reason operators turn verification off.

func (*Watcher) Start

func (w *Watcher) Start(
	ctx context.Context,
) error

Start subscribes to the enrollment request subject and blocks until the context is cancelled. Returns nil on clean shutdown.

Directories

Path Synopsis
Package mocks is a generated GoMock package.
Package mocks is a generated GoMock package.

Jump to

Keyboard shortcuts

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