Documentation
¶
Overview ¶
Package trustedagents holds the build-time-embedded list of node IDs that the daemon auto-accepts handshake requests from. The data layer is utility-tier so both the daemon plugin (plugins/trustedagents) and the CLI (cmd/pilotctl) can read it without violating the strict downward layer rule.
The list is plain JSON in this directory, embedded at build time and refreshed hourly from raw.githubusercontent.com by plugins/trustedagents.Run. Authenticity comes from HTTPS to GitHub plus repo write access — there is no separate signature check.
Adding an agent: edit trusted-agents.json, commit. Daemons in the field pick it up within ~1h. Brand-new daemons get the embedded copy from the binary, so the feature works on first boot even airgapped.
Index ¶
- func EmbeddedJSON() []byte
- func IsTrusted(nodeID uint32) (string, bool)
- func IsTrustedWithKey(nodeID uint32, pubKey []byte) (string, bool)
- func Load(raw []byte) error
- func Run(ctx context.Context)
- func SetForTest(agents []Agent) (restore func())
- func VerifyAndStripSig(raw []byte) ([]byte, error)
- type Agent
- type Service
- func (s *Service) IsTrusted(nodeID uint32) (string, bool)
- func (s *Service) IsTrustedWithKey(nodeID uint32, pubKey []byte) (string, bool)
- func (s *Service) Name() string
- func (s *Service) Order() int
- func (s *Service) Start(ctx context.Context, deps coreapi.Deps) error
- func (s *Service) Stop(ctx context.Context) error
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func EmbeddedJSON ¶
func EmbeddedJSON() []byte
EmbeddedJSON returns the bytes of the embedded JSON list. Exposed for the plugin's HTTP refresher which needs to compare fetched bytes against the embedded baseline at startup.
func IsTrusted ¶
IsTrusted reports whether nodeID is in the trusted-agents list. The caller MUST verify the (node_id, public_key) binding at the registry before acting on a true result — this package only checks the list.
IsTrusted does NOT consult the optional per-entry pubkey pin: it answers the node_id-only question for callers that have no authenticated key in scope. Callers that DO have the authenticated peer key (e.g. the inbound handshake auto-accept path) MUST prefer IsTrustedWithKey so a present pin is enforced.
func IsTrustedWithKey ¶ added in v0.2.4
IsTrustedWithKey reports whether nodeID is trusted given the authenticated peer's Ed25519 public key.
- nodeID not in the list → ("", false)
- entry HAS a pinned PublicKey → trusted ONLY if pubKey equals the pin (constant-time compare). A mismatch — or an empty/short pubKey when a pin is required — is ("", false).
- entry has NO pin (every entry today) → trusted by node_id alone, preserving IsTrusted's behavior. The unpinned match is logged at debug so finding-H4 exposure is observable until pins are added.
Pass the AUTHENTICATED key (the one the peer proved possession of in the handshake), never an unverified claim — otherwise the pin adds nothing.
func Load ¶
Load parses raw JSON and atomically replaces the active list. Safe to call from any goroutine. Used by plugins/trustedagents.fetchOnce after each successful HTTP refresh.
func Run ¶
Run polls the canonical URL on a timer, replacing the active list whenever a new one is fetched. Blocks until ctx is cancelled. The first fetch is delayed 0–30s so a fleet rebooting at the same time doesn't thunder the URL.
func SetForTest ¶
func SetForTest(agents []Agent) (restore func())
SetForTest replaces the active list with agents and returns a restore function that reloads the embedded list. Test-only — never call from production code.
func VerifyAndStripSig ¶
VerifyAndStripSig checks the ed25519 signature embedded in the fetched JSON. If no "signature" field is present the raw body is returned as-is (backward-compatible with unsigned lists). If the field is present the signature is verified against embeddedPubKey; on mismatch an error is returned so the caller falls back to the embedded list.
Types ¶
type Agent ¶
type Agent struct {
Hostname string `json:"hostname"`
Address string `json:"address"`
NodeID uint32 `json:"node_id"`
PublicKey string `json:"public_key,omitempty"`
}
Agent is one entry in the trusted-agents list. Match is by NodeID; Hostname and Address are kept for logs and `pilotctl trusted list`. Other JSON fields in the source file (tier, description, ...) are silently ignored on unmarshal — we don't care about them at runtime.
PublicKey is OPTIONAL: a base64 (std encoding) Ed25519 public key pinning the node_id to a specific key. When present, the inbound auto-accept path MUST verify the authenticated peer's key equals it (see IsTrustedWithKey). When absent — as for every entry shipped today — trust falls back to node_id alone, preserving current behavior. Pinning closes audit finding H4: without it, taking over a trusted node_id (or a registry that maps a trusted node_id to an attacker key) inherits full auto-approve trust.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service is the L11 plugin adapter. Implements both coreapi.Service (lifecycle) and coreapi.TrustChecker (trust gate). Daemon stores it twice — once in the plugin registry, once as the trust checker — but it's the same struct.
func NewService ¶
func NewService() *Service
NewService returns a Service ready for daemon.RegisterPlugin and daemon.RegisterTrustChecker.
func (*Service) IsTrusted ¶
IsTrusted is the coreapi.TrustChecker side of the plugin. Delegates to the package-global allowlist that Run() maintains.
func (*Service) IsTrustedWithKey ¶ added in v0.2.4
IsTrustedWithKey is the pubkey-pinned trust gate (audit finding H4). It enforces a per-entry Ed25519 pin when one is present and otherwise falls back to node_id-only trust. Delegates to the package-global allowlist.
TODO(H4 wiring): the inbound auto-accept call site lives in protocol/plugins/handshake (handshake.go ~L639), where it calls hm.rt.IsTrusted(peerNodeID) via the coreapi.TrustChecker interface. The authenticated peer key IS in scope there as msg.PublicKey (it is already stored into the TrustRecord). To actually ENFORCE pinning, two upstream changes are needed, in this order:
- common/coreapi.TrustChecker: add IsTrustedWithKey(nodeID uint32, pubKey []byte) (string, bool).
- protocol/plugins/handshake: replace the auto-accept hm.rt.IsTrusted(peerNodeID) call with hm.rt.IsTrustedWithKey(peerNodeID, msg.PublicKey).
Until those land, this method is callable directly but the daemon's handshake path still routes through IsTrusted, so pins are stored and validated on Load but not yet enforced at auto-accept. That is safe: every shipped entry is unpinned, so behavior is unchanged.