Documentation
¶
Overview ¶
Package handshake implements the manual trust-handshake protocol on port 444 (the Pilot Protocol PortHandshake well-known port).
This is the higher-level "establish trust between two nodes" RPC, NOT the L5 wire-level key-exchange (which lives in pkg/daemon/tunnel.go). Two daemons that have not previously met exchange a JSON handshake message over a normal stream connection on port 444; both sides validate signatures, optionally consult an allowlist, and either auto-accept or stash the request for manual approval via pilotctl.
Lifecycle: this plugin sits at L11. The composition root (plugins/runtime or test harness) constructs a Service via NewService(rt Runtime), registers it on the plugin runtime, and the daemon's Start() spawns the listener on PortHandshake via rt.PortListener.
Index ¶
- Constants
- type HandshakeMsg
- type Manager
- func (hm *Manager) ApproveHandshake(peerNodeID uint32) error
- func (hm *Manager) IsTrusted(nodeID uint32) bool
- func (hm *Manager) PendingCount() int
- func (hm *Manager) PendingRequests() []PendingHandshake
- func (hm *Manager) ProcessRelayedApproval(fromNodeID uint32)
- func (hm *Manager) ProcessRelayedRejection(fromNodeID uint32)
- func (hm *Manager) ProcessRelayedRequest(fromNodeID uint32, justification string)
- func (hm *Manager) RejectHandshake(peerNodeID uint32, reason string) error
- func (hm *Manager) RevokeTrust(peerNodeID uint32) error
- func (hm *Manager) SendRequest(peerNodeID uint32, justification string) error
- func (hm *Manager) Start() error
- func (hm *Manager) Stop()
- func (hm *Manager) TrustedPeers() []TrustRecord
- func (hm *Manager) WaitForTrust(nodeID uint32, timeout time.Duration) bool
- type PendingHandshake
- type RegistryClient
- type Runtime
- type Service
- type TrustRecord
Constants ¶
const ( HandshakeRequest = "handshake_request" HandshakeAccept = "handshake_accept" HandshakeReject = "handshake_reject" HandshakeRevoke = "handshake_revoke" )
Handshake message types.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type HandshakeMsg ¶
type HandshakeMsg struct {
Type string `json:"type"`
NodeID uint32 `json:"node_id"`
PublicKey string `json:"public_key"` // base64 Ed25519 public key
Justification string `json:"justification"` // why the sender wants to connect
Signature string `json:"signature"` // Ed25519 sig over "handshake:<node_id>:<peer_id>"
Reason string `json:"reason"` // rejection reason
Timestamp int64 `json:"timestamp"`
}
HandshakeMsg is the wire format for handshake protocol messages on port 444.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager handles the trust handshake protocol on port 444.
func NewManager ¶
NewManager constructs a Manager bound to the given Runtime. Loads persisted trust state from disk if the runtime exposes an identity path; otherwise the manager is in-memory only.
func (*Manager) ApproveHandshake ¶
ApproveHandshake approves a pending handshake request.
func (*Manager) PendingCount ¶
PendingCount returns the number of pending handshake requests.
func (*Manager) PendingRequests ¶
func (hm *Manager) PendingRequests() []PendingHandshake
PendingRequests returns all pending handshake requests.
func (*Manager) ProcessRelayedApproval ¶
ProcessRelayedApproval handles a handshake approval received via registry relay. Exported for daemon-side polling glue.
func (*Manager) ProcessRelayedRejection ¶
ProcessRelayedRejection handles a handshake rejection received via registry relay. Exported for daemon-side polling glue.
func (*Manager) ProcessRelayedRequest ¶
ProcessRelayedRequest handles a handshake request received via registry relay. Exported for daemon-side polling glue (Daemon.pollRelayedHandshakes).
func (*Manager) RejectHandshake ¶
RejectHandshake rejects a pending handshake request.
func (*Manager) RevokeTrust ¶
RevokeTrust removes a peer from the trusted set and notifies both the registry and the peer itself. Either party can revoke unilaterally.
func (*Manager) SendRequest ¶
SendRequest sends a handshake request to a remote node. First tries direct connection (port 444). If that fails (e.g. private node), falls back to relaying through the registry.
func (*Manager) Stop ¶
func (hm *Manager) Stop()
Stop waits for all background RPCs to finish and stops the replay reaper.
func (*Manager) TrustedPeers ¶
func (hm *Manager) TrustedPeers() []TrustRecord
TrustedPeers returns all trusted peers.
func (*Manager) WaitForTrust ¶
WaitForTrust blocks until the given peer transitions to trusted, or the timeout elapses. Returns true if trust was granted within the window, false on timeout. The fast path (already trusted) is a single map lookup under hm.mu — no goroutine, no channel allocation.
Self is always trusted: a daemon trivially trusts itself, and gating loopback operations behind a non-existent trust record would block pilotctl self-pings and any plugin operating against the local node.
type PendingHandshake ¶
type PendingHandshake struct {
NodeID uint32
PublicKey string
Justification string
ReceivedAt time.Time
}
PendingHandshake is an unapproved incoming request.
type RegistryClient ¶
type RegistryClient interface {
Lookup(nodeID uint32) (map[string]interface{}, error)
ReportTrust(nodeID, peerID uint32) (map[string]interface{}, error)
RevokeTrust(nodeID, peerID uint32) (map[string]interface{}, error)
RequestHandshake(fromNodeID, toNodeID uint32, justification, signatureB64 string) (map[string]interface{}, error)
RespondHandshake(nodeID, peerID uint32, accept bool, signatureB64 string) (map[string]interface{}, error)
PollHandshakes(nodeID uint32) (map[string]interface{}, error)
}
RegistryClient is the subset of pkg/registry/client.Client the handshake manager uses. Defined here as an interface so the manager stays free of import cycles and tests can supply a fake.
Method signatures match *registry/client.Client exactly, so the daemon adapter can return the underlying client directly.
type Runtime ¶
type Runtime interface {
// NodeID returns this daemon's stable node ID. Returns 0 if
// identity has not been initialized yet.
NodeID() uint32
// HasIdentity reports whether the daemon has loaded an Ed25519
// identity. SendRequest / signing paths early-return when false.
HasIdentity() bool
// PublicKey returns the local Ed25519 public key (for inclusion in
// outbound HandshakeMsg.PublicKey). Returns nil when HasIdentity is
// false.
PublicKey() ed25519.PublicKey
// Sign signs the given payload with the local Ed25519 private key.
// Returns nil when HasIdentity is false; callers handle that as
// "no signature attached".
Sign(msg []byte) []byte
// IdentityPath returns the configured on-disk identity path; the
// trust.json store sits in the same directory. Empty when the
// daemon runs in-memory only (no persistence).
IdentityPath() string
// TrustAutoApprove reports whether the daemon was configured to
// auto-approve all incoming handshake requests (trust-on-first-use
// stance for friendly fleets).
TrustAutoApprove() bool
// IsTrusted consults the trustedagents allowlist. Returns the
// agent's display name and ok=true on a match. Returns "", false
// when no checker has been registered (treated as "not trusted").
IsTrusted(nodeID uint32) (name string, ok bool)
// PublishEvent emits a topic+payload event onto the daemon's event
// bus. Non-blocking; events may be dropped under bus pressure.
PublishEvent(topic string, payload map[string]any)
// PortListener binds the given well-known port and returns a
// coreapi.Listener. The handshake plugin uses this for PortHandshake.
// Closing the returned listener stops the accept loop.
PortListener(port uint16) (coreapi.Listener, error)
// DialAndSend establishes a stream to (peerNodeID, port), writes
// the JSON-encoded message, and closes after a brief grace period
// to let the data flush.
DialAndSend(peerNodeID uint32, port uint16, data []byte) error
// RemoveTunnelPeer tears down the encrypted tunnel for the given
// peer. Called from RevokeTrust so a revoked peer's tunnel state
// (session keys, retransmit queues) is purged immediately.
RemoveTunnelPeer(nodeID uint32)
// Registry returns the L8 registry-side-channel client used for
// peer lookups, trust-pair reporting, and relay-based handshake
// when direct dial fails. Returns nil when the daemon is offline
// or running without a registry connection — callers must nil-check.
Registry() RegistryClient
}
Runtime is the primitives-only contract the handshake manager needs from the surrounding daemon. Every dependency that crossed the old `hm.daemon.X` boundary now flows through one of these methods.
The concrete adapter (plugins/runtime.HandshakeRuntime) wraps a *daemon.Daemon to satisfy this interface. Tests can substitute a fake runtime to exercise the manager in isolation.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service is the L11 plugin adapter for the manual trust-handshake runtime (port 444). Wraps a Manager and bridges between the coreapi.Service lifecycle (Start/Stop with deps) and the Runtime-interface accept loop.
func NewService ¶
NewService constructs a handshake plugin Service over the given Runtime. The composition root (plugins/runtime or test harness) builds the Runtime and registers the resulting Service.