bastion 0.53.2
ci / personal-data (push) Successful in 5s
ci / test (push) Successful in 3m4s
ci / fuzz (push) Successful in 3m45s

This commit is contained in:
Privatae
2026-10-20 04:42:14 +00:00
commit e4c9b69b46
471 changed files with 159794 additions and 0 deletions
+134
View File
@@ -0,0 +1,134 @@
package main
// Box mode — local or networked. One binary, two shapes.
//
// The bastion that existed before this file is the LOCAL bastion: one person,
// one host, and on a home machine it runs as that person, not root. The
// NETWORKED bastion is the same binary on a machine several people share: it
// runs as root (the ops it will grow need root), it serves many hosts, and
// every host after the first is held pending until the anchor host approves
// it (anchor.go).
//
// Why a mode at all, instead of inferring the shape from the pairing count:
// the two differ in what `pair` is ALLOWED to do. A local box refuses a second
// pairing outright — that is the whole protection for a single-user machine,
// where a second host appearing means someone else got a token onto your
// box. A networked box accepts it and parks it pending. The box has to know
// which rule it is enforcing before the second token arrives, so the choice
// is made at install and written down, never guessed.
//
// The systemd unit the installer writes (deploy/install.sh) has no User= and
// HOME=/root, so every install made through it already runs as root. Existing
// installs default to LOCAL here regardless, because a mode that silently
// widened what `pair` accepts on every machine already installed would be the
// takeover hole this work exists to close. Root + local is fine;
// non-root + networked is refused (set-mode needs root).
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
)
const (
modeLocal = "local"
modeNetworked = "networked"
)
// modePath is <configDir>/mode.json — beside owner.json and anchor.json, NOT
// inside pairings/: the mode belongs to the box, not to any host it serves.
func modePath() (string, error) {
d, err := configDir()
if err != nil {
return "", err
}
return filepath.Join(d, "mode.json"), nil
}
type modeFile struct {
Mode string `json:"mode"`
}
// boxMode returns the box's mode. Absent, unreadable or unrecognised all read
// as LOCAL: the narrower rule is the safe default, and it is exactly what
// every install that predates the mode file was doing.
func boxMode() string {
p, err := modePath()
if err != nil {
return modeLocal
}
raw, err := os.ReadFile(p)
if err != nil {
return modeLocal
}
var f modeFile
if json.Unmarshal(raw, &f) != nil {
return modeLocal
}
if f.Mode == modeNetworked {
return modeNetworked
}
return modeLocal
}
// setBoxMode writes the mode. Networked needs root because the networked shape
// is root-owned by definition — a non-root user flipping a shared
// machine to "accepts more pairings" is the thing the mode prevents.
func setBoxMode(mode string) error {
mode = strings.TrimSpace(mode)
if mode != modeLocal && mode != modeNetworked {
return fmt.Errorf("mode must be %q or %q", modeLocal, modeNetworked)
}
if mode == modeNetworked {
if err := requireMachineAuthority("set-mode networked"); err != nil {
return err
}
}
if mode == modeLocal {
// Going back to local must not strand hosts: a local box serves ONE host,
// so with several pairings the box would be lying about its own rule.
cfgs, err := loadPairings()
if err == nil && len(cfgs) > 1 {
return fmt.Errorf("cannot switch to local mode: %d hosts are paired and a local bastion serves one — remove the others first", len(cfgs))
}
if currentAnchor() != nil {
return errors.New("cannot switch to local mode: this box is anchored (anchor.json) — anchoring is a networked-mode concept")
}
}
p, err := modePath()
if err != nil {
return err
}
b, err := json.MarshalIndent(modeFile{Mode: mode}, "", " ")
if err != nil {
return err
}
if err := atomicWrite(p, b); err != nil {
return err
}
// The marker carries the mode, so it moves with it (singleton.go). Also the
// point at which a networked box first claims the machine: `set-mode
// networked` is root by definition, and install.sh runs it before anything is
// paired.
_ = maybeWriteMarker()
return nil
}
// cmdSetMode is `privatae-bastion set-mode <local|networked>`; install.sh
// --networked calls it as root at install time.
func cmdSetMode(mode string) error {
if err := setBoxMode(mode); err != nil {
return err
}
fmt.Printf("mode set to %s\n", mode)
if mode == modeNetworked {
// Platform-correct, for the same reason the refusal text is: telling a
// Windows user to run `sudo` is how a real instruction becomes noise.
fmt.Printf(" Next: anchor this machine to your host — %s\n", anchorCommandHint())
fmt.Println(" Every host paired after the anchor waits, pending, until the anchor host approves it.")
}
return nil
}