Files
privatae-bastion/client-go/hostdoor_followups_linux_test.go
Privatae 48899b41a9
ci / personal-data (push) Successful in 4s
ci / test (push) Successful in 4m58s
ci / fuzz (push) Successful in 3m33s
bastion 0.63.1
2026-11-03 22:56:18 +00:00

310 lines
12 KiB
Go

//go:build linux
package main
import (
"crypto/ed25519"
"encoding/base64"
"net"
"os"
"strings"
"testing"
"time"
)
// windowBox is a pairing box whose `pair --open` runs with machine authority
// (held) and no organisation's policy, in the given mode.
func windowBox(t *testing.T, networked, held bool) {
t.Helper()
pairingBox(t)
prev := machineAuthorityHeld
machineAuthorityHeld = func() bool { return true }
t.Cleanup(func() { machineAuthorityHeld = prev })
withPolicy(t, nil)
if networked {
if err := setBoxMode(modeNetworked); err != nil {
t.Fatal(err)
}
}
machineAuthorityHeld = func() bool { return held }
}
// pairThroughWindow opens a window on a free port, pairs h's Host through it,
// and waits for `pair --open` to finish. h is made for that port.
func pairThroughWindow(t *testing.T, ca *testCA, handle, name string) (windowDoneReq, string) {
t.Helper()
addr := freeDoorAddr(t)
code, out, res := openWindowFor(t, addr)
h := newStandinHost(t, ca, handle, name, addr)
done, err := h.pair(formatWindowCode(code))
if err != nil {
t.Fatalf("pair: %v\n%s", err, pairingAuditText(t))
}
select {
case err := <-res:
if err != nil {
t.Fatalf("pair --open: %v\n%s", err, out.String())
}
case <-time.After(15 * time.Second):
t.Fatal("pair --open never ended")
}
return done, out.String()
}
// 1. A server paired through `pair --open`, by someone with machine
// authority, on a machine nobody is in charge of, has that Host in charge:
// the anchor, the existing mechanism. A local machine is made networked for
// it (an anchor is a networked machine's). The rules before it still win: a
// Host on this machine, an organisation's policy naming another Host. Nobody
// without machine authority puts anyone in charge, and a local machine that
// already serves a Host the person said yes to keeps serving it.
//
// MUTATION: windowCommit calls anchorAfterPair instead of anchorAfterWindow.
// RESULT: red (local, and networked beside another pairing: no anchor).
// MUTATION: drop the sameMachineHost check in anchorAfterWindow. RESULT: red.
// MUTATION: drop the local-mode active check. RESULT: red.
// MUTATION: anchorAfterWindow ignores openerAuthority. RESULT: red.
func TestAServerPairedThroughTheWindowHasThatHostInCharge(t *testing.T) {
const name = "home201721e4.cseries.localhost"
t.Run("local machine, nobody in charge: networked, anchored, active", func(t *testing.T) {
windowBox(t, false, true)
done, out := pairThroughWindow(t, newTestCA(t, "home CA"), "home", name)
if done.State != pairingActive {
t.Fatalf("done: %+v", done)
}
if a := currentAnchor(); a == nil || a.AnchorHost != name {
t.Fatalf("anchor = %+v, want %s", a, name)
}
if boxMode() != modeNetworked {
t.Fatalf("mode %s: a machine with an anchor is networked", boxMode())
}
if !strings.Contains(out, name+" is now the Host in charge of this machine") || !strings.Contains(out, "pair --open") {
t.Fatalf("the person is not told who is in charge:\n%s", out)
}
if a := pairingAuditText(t); !strings.Contains(a, `"op":"anchor_take"`) {
t.Fatalf("no anchor_take in the audit:\n%s", a)
}
})
t.Run("networked, unanchored, another pairing already there: anchored, the other waits", func(t *testing.T) {
windowBox(t, true, true)
writePairing(t, tRemote)
_, _ = pairThroughWindow(t, newTestCA(t, "home CA"), "home", name)
if a := currentAnchor(); a == nil || a.AnchorHost != name {
t.Fatalf("anchor = %+v, want %s", a, name)
}
if got := loadState(t, name); got != pairingActive {
t.Fatalf("the anchor never waits on itself: %q", got)
}
if got := loadState(t, tRemote); got != pairingPending {
t.Fatalf("the other Host is %q, want pending (waiting for %s)", got, name)
}
})
t.Run("a Host on this machine is paired: it stays the one in charge", func(t *testing.T) {
windowBox(t, true, true)
writeTCPPairing(t, tLocal2, "127.0.0.1:8811", pairingPending)
_, _ = pairThroughWindow(t, newTestCA(t, "home CA"), "home", name)
if a := currentAnchor(); a != nil {
t.Fatalf("a Host on this machine lost the machine to the window's Host: anchor %+v", a)
}
})
t.Run("an organisation's policy names another Host: it waits for that one", func(t *testing.T) {
windowBox(t, true, true)
withPolicy(t, &anchorPolicy{Host: "corp.host.privatae.ai", Source: "test policy"})
done, _ := pairThroughWindow(t, newTestCA(t, "home CA"), "home", name)
if a := currentAnchor(); a != nil {
t.Fatalf("anchor %+v against the policy", a)
}
if done.State != pairingPending || done.AdmittedBy != "corp.host.privatae.ai" {
t.Fatalf("done: %+v, want pending for the Host the policy names", done)
}
})
t.Run("an organisation's policy names this Host: in charge, on a local machine too", func(t *testing.T) {
windowBox(t, false, true)
withPolicy(t, &anchorPolicy{Host: name, Source: "test policy"})
writeTCPPairing(t, tLocal2, "127.0.0.1:8811", pairingRevoked)
_, _ = pairThroughWindow(t, newTestCA(t, "home CA"), "home", name)
if a := currentAnchor(); a == nil || a.AnchorHost != name {
t.Fatalf("anchor = %+v, want %s (the policy names it)", a, name)
}
})
t.Run("no machine authority: nobody is put in charge, the mode is left", func(t *testing.T) {
windowBox(t, false, false)
_, _ = pairThroughWindow(t, newTestCA(t, "home CA"), "home", name)
if a := currentAnchor(); a != nil || boxMode() != modeLocal {
t.Fatalf("without authority: anchor %+v, mode %s", a, boxMode())
}
})
t.Run("opened without machine authority, committed by a door that has it: nobody in charge", func(t *testing.T) {
windowBox(t, false, true)
cfg := Config{HostName: name, State: ""}
if took, err := anchorAfterWindow(&cfg, []*Config{&cfg}, false); took.Taken || err != nil || currentAnchor() != nil {
t.Fatalf("a window opened without authority put %s in charge: %+v %v", name, took, err)
}
})
t.Run("a local machine already serving another Host: it keeps serving it", func(t *testing.T) {
windowBox(t, false, true)
writePairing(t, tRemote)
_, _ = pairThroughWindow(t, newTestCA(t, "home CA"), "home", name)
if a := currentAnchor(); a != nil || boxMode() != modeLocal {
t.Fatalf("anchor %+v, mode %s: the Host already served here would wait", a, boxMode())
}
if got := loadState(t, tRemote); got == pairingPending {
t.Fatal("the Host already served here was left waiting")
}
})
}
// 2. The same Host pairing the same server again, through a fresh window,
// replaces its pairing: no `unpair` first. The same CA is the same Host,
// whatever handle it gives (a C-Series Host's handle is "home" while its name
// carries an id). Its state, the anchor and its grants are kept, and the old
// credentials are gone.
//
// MUTATION: drop the doorPairingWithCA lookup in windowCommit. RESULT: red,
// "this Host is already paired with this server as ...".
func TestTheSameHostPairingAgainReplacesItsPairing(t *testing.T) {
const name = "home201721e4.cseries.localhost"
windowBox(t, true, true)
ca := newTestCA(t, "home CA")
_, _ = pairThroughWindow(t, ca, "home", name)
if a := currentAnchor(); a == nil || a.AnchorHost != name {
t.Fatalf("first pairing: anchor %+v", a)
}
before, _ := pairingNamed(t, name)
gp, err := pairingGrantPath(name)
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(gp, []byte(`{"kept":true}`), 0o600); err != nil {
t.Fatal(err)
}
done, out := pairThroughWindow(t, ca, "home", name)
if done.State != pairingActive || done.HostName != name {
t.Fatalf("pairing again: %+v\n%s", done, out)
}
after, all := pairingNamed(t, name)
if len(all) != 1 {
t.Fatalf("%d pairings after pairing again, want the one", len(all))
}
if after.OperatorCert == before.OperatorCert || after.OperatorKey == before.OperatorKey || after.WGPrivateKey == before.WGPrivateKey {
t.Fatal("the old credentials were kept")
}
if after.ListenAddr != before.ListenAddr || after.FirstSeen != before.FirstSeen || after.State != before.State {
t.Fatalf("the pairing was not kept in place: %+v -> %+v", before, after)
}
if a := currentAnchor(); a == nil || a.AnchorHost != name {
t.Fatalf("the anchor was not kept: %+v", a)
}
if b, err := os.ReadFile(gp); err != nil || string(b) != `{"kept":true}` {
t.Fatalf("the grant was not kept: %q %v", b, err)
}
// Another Host (another CA) with the same handle is never this one.
done, err = func() (windowDoneReq, error) {
addr := freeDoorAddr(t)
code, _, res := openWindowFor(t, addr)
h := newStandinHost(t, newTestCA(t, "another CA"), "home", name, addr)
d, err := h.pair(formatWindowCode(code))
<-res
return d, err
}()
if err != nil || done.State != "refused" || !strings.Contains(done.Error, "different Host") {
t.Fatalf("another Host of the same name: %+v %v", done, err)
}
if c, _ := pairingNamed(t, name); c.Bearer != after.Bearer {
t.Fatal("another Host's pairing replaced this one's")
}
}
// The same Host paired again under a new name (same CA) stays in charge under
// that name: the anchor follows it, its pinned key kept.
//
// MUTATION: drop carryAnchorToNewName from windowCommit. RESULT: red.
func TestTheAnchorFollowsItsHostToANewName(t *testing.T) {
windowBox(t, true, true)
ca := newTestCA(t, "home CA")
_, _ = pairThroughWindow(t, ca, "home", "home1.cseries.localhost")
pub, _, _ := ed25519.GenerateKey(nil)
if err := pinAnchorVerifyKey(base64.StdEncoding.EncodeToString(pub)); err != nil {
t.Fatal(err)
}
f, _ := loadAnchorFile()
pin := f.VerifyPubkey
_, _ = pairThroughWindow(t, ca, "home", "home2.cseries.localhost")
all, _ := loadPairings()
if len(all) != 1 || all[0].HostName != "home2.cseries.localhost" {
t.Fatalf("pairings: %v", hostNames(all))
}
f, _ = loadAnchorFile()
if a := currentAnchor(); a == nil || a.AnchorHost != "home2.cseries.localhost" || f.VerifyPubkey != pin || pin == "" {
t.Fatalf("anchor %+v (pin kept: %v)", a, f != nil && f.VerifyPubkey == pin)
}
}
// The anchor moves only with machine authority, as every other anchor write
// (claim-anchor --repin's wall): a process without it leaves anchor.json as
// it is.
//
// MUTATION: drop the machineAuthorityHeld check in carryAnchorToNewName.
// RESULT: red, the anchor names the new name.
func TestTheAnchorMovesToANewNameOnlyWithMachineAuthority(t *testing.T) {
windowBox(t, true, true)
ca := newTestCA(t, "home CA")
_, _ = pairThroughWindow(t, ca, "home", "home1.cseries.localhost")
if a := currentAnchor(); a == nil || a.AnchorHost != "home1.cseries.localhost" {
t.Fatalf("first pairing: anchor %+v", a)
}
machineAuthorityHeld = func() bool { return false }
_, _ = pairThroughWindow(t, ca, "home", "home2.cseries.localhost")
if a := currentAnchor(); a == nil || a.AnchorHost != "home1.cseries.localhost" {
t.Fatalf("without machine authority the anchor was rewritten: %+v", a)
}
}
// 3. Right after `unpair` the running bastion still holds the door's port
// until it sees the pairing gone (a supervision period). `pair --open` waits
// for it to let go instead of failing "address already in use".
//
// MUTATION: drop the awaitDoorReleased call in cmdPairOpen. RESULT: red.
func TestPairOpenWaitsForTheRunningBastionToLetGoOfThePort(t *testing.T) {
pairingBox(t)
prevRunning, prevWait := pairDaemonRunning, doorReleaseWait
pairDaemonRunning = func() bool { return true }
doorReleaseWait = 10 * time.Second
t.Cleanup(func() { pairDaemonRunning, doorReleaseWait = prevRunning, prevWait })
addr := freeDoorAddr(t)
held, err := listenDoor(addr) // the running bastion's door, its pairing just removed
if err != nil {
t.Fatal(err)
}
time.AfterFunc(1500*time.Millisecond, func() { _ = held.Close() })
code, out, res := openWindowFor(t, addr)
if !strings.Contains(out.String(), "waiting for it to let go") {
t.Fatalf("the person is not told why it waits:\n%s", out.String())
}
h := newStandinHost(t, newTestCA(t, "home CA"), "home", "home.cseries.localhost", addr)
if done, err := h.pair(formatWindowCode(code)); err != nil || done.State != pairingActive {
t.Fatalf("pair after the port was let go: %+v %v", done, err)
}
if err := <-res; err != nil {
t.Fatalf("pair --open: %v\n%s", err, out.String())
}
// Another program that never lets go: it still fails, after the wait, and
// says what to do.
doorReleaseWait = 300 * time.Millisecond
addr2 := freeDoorAddr(t)
other, err := net.Listen("tcp", addr2)
if err != nil {
t.Fatal(err)
}
defer other.Close()
err = cmdPairOpen(addr2, &syncBuf{})
if err == nil || !strings.Contains(err.Error(), "choose another with --port") {
t.Fatalf("a port another program holds: %v", err)
}
if !strings.Contains(err.Error(), "address already in use") {
t.Fatalf("not refused for the address in use: %v", err)
}
}