bastion 0.53.2
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
package main
|
||||
|
||||
// Peer announcement — how a machine introduces the hosts it serves.
|
||||
//
|
||||
// One daemon serves many hosts (config.go: one file per pairing under
|
||||
// pairings/<host_name>.json). This box is therefore the ONLY party that knows
|
||||
// the set: the platform cannot derive it, deliberately. Every pairing mints a
|
||||
// fresh WireGuard keypair, and the "hardware fingerprint" is fresh random bytes
|
||||
// per pairing (pair.go), so two pairings of one machine are unlinkable from the
|
||||
// outside. That is the privacy property, and it is why discovery has to be the
|
||||
// machine ANNOUNCING rather than the platform correlating.
|
||||
//
|
||||
// The rule: announcement is automatic and symmetric — pairing IS the
|
||||
// consent, no approval step. Pair a bastion to a second host and both hosts
|
||||
// learn of each other on the next heartbeat, which is what makes an IT team's
|
||||
// hosts assemble themselves with zero steps. It is also how you find out that
|
||||
// someone paired a host to a machine you own, so it only works if it is loud.
|
||||
//
|
||||
// WHAT TRAVELS: host name, handle, and when this box started serving it.
|
||||
// NOTHING ELSE. Not keys, not grants, not context, not fingerprints. That line
|
||||
// is load-bearing: multi-homing gives each pairing its own DCN dir precisely so
|
||||
// that one entity's pinned key can never verify another entity's grants
|
||||
// (config.go). Sharing a NAME is clear of that; carrying anything a peer could
|
||||
// verify a grant with would be exactly the cross-host bleed that isolation
|
||||
// exists to prevent.
|
||||
|
||||
// peerAnnounce is one OTHER host this machine serves, as told to a host on its
|
||||
// own heartbeat. The receiving host treats it as a CLAIM by this bastion, not
|
||||
// as proof, and it grants nothing.
|
||||
type peerAnnounce struct {
|
||||
HostName string `json:"host_name"`
|
||||
Handle string `json:"handle"`
|
||||
// State (anchor.go): active | pending | revoked. Without it a
|
||||
// host that shares the box would show a PENDING host — one the anchor has
|
||||
// not approved — as an established peer. Absent from bastions before it;
|
||||
// the host reads absence as active, which is all those could have been.
|
||||
State string `json:"state,omitempty"`
|
||||
Since string `json:"since,omitempty"`
|
||||
}
|
||||
|
||||
// peersFor returns every pairing on this box EXCEPT the one identified by
|
||||
// selfHostName, newest-serving first by name order (loadPairings sorts).
|
||||
//
|
||||
// Read fresh on each beat rather than cached at start: `privatae-bastion pair`
|
||||
// writes a new file into the same directory, and a peer the user just added
|
||||
// must show up on the next heartbeat without restarting the daemon. That IS
|
||||
// the popup — a cached list would make the feature arrive whenever the box
|
||||
// next rebooted.
|
||||
//
|
||||
// Best-effort by design: any failure to read the directory returns nil, and the
|
||||
// heartbeat simply carries no peers. A machine that cannot enumerate its own
|
||||
// pairings must not fail its liveness beat over it.
|
||||
func peersFor(selfHostName string) []peerAnnounce {
|
||||
cfgs, err := loadPairings()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
out := make([]peerAnnounce, 0, len(cfgs))
|
||||
for _, c := range cfgs {
|
||||
if c.HostName == "" || c.HostName == selfHostName {
|
||||
continue
|
||||
}
|
||||
// "Since" is first_seen when the pairing carries it (0.16.0+), else the
|
||||
// file's mtime — the honest answer already on disk for older pairings,
|
||||
// never faked with time.Now(). Omitted when neither can be read.
|
||||
out = append(out, peerAnnounce{HostName: c.HostName, Handle: handleOf(c.HostName),
|
||||
State: c.stateName(), Since: sinceOf(c)})
|
||||
}
|
||||
// EMPTY IS A REAL ANSWER, and it is NOT the same as nil.
|
||||
//
|
||||
// nil is returned above only when the pairing directory cannot be read: we
|
||||
// do not know what this box serves, so we say nothing and the host keeps
|
||||
// whatever it already has. An empty (non-nil) slice is this box saying "I
|
||||
// enumerated my pairings and I serve only this host" — a genuine state
|
||||
// change, and the one that happens when the user unpairs the last other
|
||||
// host.
|
||||
//
|
||||
// Collapsing that into nil would make the change unsendable. The host keeps
|
||||
// stored peers on an ABSENT key (its heartbeat upsert falls back to what it
|
||||
// already holds when the key is missing) so that a bastion too old to
|
||||
// report peers cannot retract them — so "none" arriving as "absent" would
|
||||
// leave the Network app listing peers the machine no longer serves, with
|
||||
// nothing that could ever clear them.
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user