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
+375
View File
@@ -0,0 +1,375 @@
//go:build !windows
// Uses setupBox / tAnchor / tMember from anchor_test.go (unix-only).
package main
import (
"crypto/ed25519"
"encoding/base64"
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"time"
"github.com/gorilla/websocket"
)
// driveDirOp runs one management job through the real lane as pairing `self`,
// with a real grant directory (driveOp passes none, so dcn-dir ops refuse there).
func driveDirOp(t *testing.T, self, dir, op string, args []string) (string, string, int) {
t.Helper()
up := websocket.Upgrader{}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ws, err := up.Upgrade(w, r, nil)
if err != nil {
return
}
defer ws.Close()
handleMgmtConnFor(ws, self, "", dir, "")
}))
defer srv.Close()
c, _, err := websocket.DefaultDialer.Dial("ws"+strings.TrimPrefix(srv.URL, "http"), nil)
if err != nil {
t.Fatalf("dial: %v", err)
}
defer c.Close()
job, _ := json.Marshal(mgmtJob{Op: op, Args: args})
if err := c.WriteMessage(websocket.BinaryMessage, append([]byte("C"), job...)); err != nil {
t.Fatalf("write: %v", err)
}
var out, errOut string
for {
_, data, err := c.ReadMessage()
if err != nil {
t.Fatalf("read: %v", err)
}
switch data[0] {
case 'O':
out += string(data[1:])
case 'R':
errOut += string(data[1:])
case 'X':
code := 0
for _, ch := range string(data[1:]) {
code = code*10 + int(ch-'0')
}
return out, errOut, code
}
}
}
func signedTerms(t *testing.T, priv ed25519.PrivateKey, terms map[string]any) (string, string) {
t.Helper()
raw, err := json.Marshal(terms) // sorted keys, no whitespace — the host's canonical form
if err != nil {
t.Fatal(err)
}
return base64.StdEncoding.EncodeToString(raw), base64.StdEncoding.EncodeToString(ed25519.Sign(priv, raw))
}
func reachPayload(t *testing.T, priv ed25519.PrivateKey, id int64, terms map[string]any) string {
t.Helper()
tc, sig := signedTerms(t, priv, terms)
pub := base64.StdEncoding.EncodeToString(priv.Public().(ed25519.PublicKey))
b, _ := json.Marshal(map[string]any{"grant_id": id, "terms_canonical": tc, "signature": sig, "verify_pubkey": pub})
return base64.StdEncoding.EncodeToString(b)
}
func reachTermsFor(subject, slot string, days int) map[string]any {
hn, _ := os.Hostname()
return map[string]any{"kind": reachKind, "server": hn, "slot": slot, "subject": subject,
"ttl_days": days, "expires": time.Now().Add(time.Duration(days-1) * 24 * time.Hour).UTC().Format(time.RFC3339)}
}
// The approval the person's device gives for exactly these terms.
func approveReach(t *testing.T, terms map[string]any) {
t.Helper()
p, err := purposeForReach(&reachTerms{Server: terms["server"].(string), Slot: terms["slot"].(string),
Subject: terms["subject"].(string), TTLDays: terms["ttl_days"].(int)})
if err != nil {
t.Fatal(err)
}
a, err := deviceSign(p, time.Minute)
if err != nil {
t.Fatal(err)
}
terms["approval"] = a
}
// A store grant and a reach grant are both records the entity key signs; neither
// may be offered as the other. (Before this, dcnVerifyRec parsed `kind` and
// never compared it.)
func TestSignedKindsCannotStandInForEachOther(t *testing.T) {
_, priv, _ := ed25519.GenerateKey(nil)
pub := base64.StdEncoding.EncodeToString(priv.Public().(ed25519.PublicKey))
exp := time.Now().Add(time.Hour).UTC().Format(time.RFC3339)
store := map[string]any{"kind": "dcn_store_grant", "class": "machine", "server": "b", "root": "/d",
"mask": []string{"read"}, "expires": exp, "slot": "forge", "subject": "h"}
reach := reachTermsFor("h.example", "forge", 30)
tcS, sigS := signedTerms(t, priv, store)
tcR, sigR := signedTerms(t, priv, reach)
if _, err := reachVerifyRec(pub, dcnGrantRec{TermsCanonical: tcS, Signature: sigS}); err == nil {
t.Fatal("a store grant must not verify as a reach grant")
}
if _, err := dcnVerifyRec(pub, dcnGrantRec{TermsCanonical: tcR, Signature: sigR}); err == nil {
t.Fatal("a reach grant must not verify as a store grant")
}
if _, err := reachVerifyRec(pub, dcnGrantRec{TermsCanonical: tcR, Signature: sigR}); err != nil {
t.Fatalf("a reach grant verifies as itself: %v", err)
}
if _, err := dcnVerifyRec(pub, dcnGrantRec{TermsCanonical: tcS, Signature: sigS}); err != nil {
t.Fatalf("a store grant verifies as itself: %v", err)
}
}
// The purpose the host builds for the person's approval must be this string
// (nac_stepup.purpose_for_reach_terms pins the same one).
func TestReachPurposeMatchesTheHost(t *testing.T) {
got, err := purposeForReach(&reachTerms{Server: "box1", Slot: "forge", Subject: "h.example", TTLDays: 30})
if err != nil || got != "service_reach_grant|box1|forge|h.example|ttl:30" {
t.Fatalf("purpose drifted: %q %v", got, err)
}
if _, err := purposeForReach(&reachTerms{Server: "b", Slot: "f", Subject: "a|b", TTLDays: 1}); err == nil {
t.Fatal("a reserved character in a purpose field must be refused")
}
if p, _ := purposeFromDetails(map[string]any{"kind": reachKind, "server": "box1", "slot": "forge",
"subject": "h.example", "ttl_days": float64(30)}); p != got {
t.Fatalf("the details the person reads must build the same purpose: %q", p)
}
if stepupKindFamily(reachKind) != "services" {
t.Fatal("a reach approval is gated on the services family")
}
}
// On an anchored box: a reach grant installs only with the person's approval,
// only for the host that sends it, only for this machine; it then lets exactly
// that host reach exactly that server; revoke and expiry take it away.
func TestReachGrantSyncAndTheForwardWall(t *testing.T) {
priv := setupBox(t)
pub, _ := deviceInit()
_ = pinDeviceKey(pub)
dir := t.TempDir()
// No approval: refused on an anchored box.
bare := reachTermsFor(tAnchor, "forge", 30)
if _, errOut, code := driveDirOp(t, tAnchor, dir, "service_grant_sync",
[]string{reachPayload(t, priv, 1, bare)}); code != 126 {
t.Fatalf("an unapproved reach grant must be refused: %d %s", code, errOut)
}
// For ANOTHER host, sent by this one: refused even when approved.
other := reachTermsFor(tMember, "forge", 30)
approveReach(t, other)
if _, errOut, code := driveDirOp(t, tAnchor, dir, "service_grant_sync",
[]string{reachPayload(t, priv, 2, other)}); code != 126 || !strings.Contains(errOut, "not the host that sent it") {
t.Fatalf("a host must not install reach for another host: %d %s", code, errOut)
}
// For another machine: refused.
elsewhere := reachTermsFor(tAnchor, "forge", 30)
elsewhere["server"] = "some-other-box"
approveReach(t, elsewhere)
if _, errOut, code := driveDirOp(t, tAnchor, dir, "service_grant_sync",
[]string{reachPayload(t, priv, 3, elsewhere)}); code != 126 {
t.Fatalf("a grant for another machine must be refused: %d %s", code, errOut)
}
// The real one.
good := reachTermsFor(tAnchor, "forge", 30)
approveReach(t, good)
if out, errOut, code := driveDirOp(t, tAnchor, dir, "service_grant_sync",
[]string{reachPayload(t, priv, 4, good)}); code != 0 {
t.Fatalf("an approved reach grant for this host installs: %d %s %s", code, out, errOut)
}
if why := serviceReachAllowed(dir, tAnchor, "forge"); why != "" {
t.Fatalf("the granted host reaches the granted server: %s", why)
}
if why := serviceReachAllowed(dir, tAnchor, "other-server"); why == "" {
t.Fatal("a grant for one server does not open another")
}
if why := serviceReachAllowed(dir, tMember, "forge"); why == "" {
t.Fatal("a grant for one host does not let another in")
}
// The same approval cannot install a second, different grant.
again := reachTermsFor(tAnchor, "forge", 29)
again["approval"] = good["approval"]
if _, _, code := driveDirOp(t, tAnchor, dir, "service_grant_sync",
[]string{reachPayload(t, priv, 5, again)}); code == 0 {
t.Fatal("an approval installs one grant once")
}
// Revoke.
if _, errOut, code := driveDirOp(t, tAnchor, dir, "service_grant_drop", []string{"4"}); code != 0 {
t.Fatalf("drop: %d %s", code, errOut)
}
if why := serviceReachAllowed(dir, tAnchor, "forge"); why == "" {
t.Fatal("a revoked grant reaches nothing")
}
}
// A grant that expired, or whose bytes were changed on disk, lets nobody in.
func TestReachGrantExpiryAndTamper(t *testing.T) {
_, priv, _ := ed25519.GenerateKey(nil)
dir := t.TempDir()
pub := base64.StdEncoding.EncodeToString(priv.Public().(ed25519.PublicKey))
hn, _ := os.Hostname()
put := func(terms map[string]any, tamper bool) {
tc, sig := signedTerms(t, priv, terms)
if tamper {
raw, _ := base64.StdEncoding.DecodeString(tc)
raw = []byte(strings.Replace(string(raw), `"forge"`, `"forgf"`, 1))
tc = base64.StdEncoding.EncodeToString(raw)
}
f := &dcnGrantFile{VerifyPubkey: pub, Stores: map[string]dcnGrantRec{},
Reach: map[string]dcnGrantRec{"1": {TermsCanonical: tc, Signature: sig}}}
if err := dcnSaveFile(dir, f); err != nil {
t.Fatal(err)
}
}
live := map[string]any{"kind": reachKind, "server": hn, "slot": "forge", "subject": "h",
"ttl_days": 30, "expires": time.Now().Add(time.Hour).UTC().Format(time.RFC3339)}
put(live, false)
if why := serviceReachAllowed(dir, "h", "forge"); why != "" {
t.Fatalf("a live grant lets its host in: %s", why)
}
expired := map[string]any{"kind": reachKind, "server": hn, "slot": "forge", "subject": "h",
"ttl_days": 30, "expires": time.Now().Add(-time.Minute).UTC().Format(time.RFC3339)}
put(expired, false)
if why := serviceReachAllowed(dir, "h", "forge"); why == "" {
t.Fatal("an expired grant lets nobody in")
}
put(live, true)
if why := serviceReachAllowed(dir, "h", "forgf"); why == "" {
t.Fatal("a record whose signed bytes were changed lets nobody in")
}
delete(live, "expires")
put(live, false)
if why := serviceReachAllowed(dir, "h", "forge"); why == "" {
t.Fatal("an open-ended grant is refused")
}
}
// The heartbeat reconcile: a held grant absent from TWO consecutive replies is a
// revoke this box missed and is dropped; an absent field means no instruction.
func TestReachReconcile(t *testing.T) {
_, priv, _ := ed25519.GenerateKey(nil)
dir := t.TempDir()
pub := base64.StdEncoding.EncodeToString(priv.Public().(ed25519.PublicKey))
tc, sig := signedTerms(t, priv, reachTermsFor("h", "forge", 30))
f := &dcnGrantFile{VerifyPubkey: pub, Stores: map[string]dcnGrantRec{},
Reach: map[string]dcnGrantRec{"7": {TermsCanonical: tc, Signature: sig}}}
if err := dcnSaveFile(dir, f); err != nil {
t.Fatal(err)
}
if d := maybeReconcileReach([]byte(`{"ok":true}`), dir); len(d) != 0 {
t.Fatal("no service_grants field is no instruction")
}
listed := []byte(`{"service_grants":{"grants":[{"grant_id":7,"signature":"x"}]}}`)
empty := []byte(`{"service_grants":{"grants":[]}}`)
if d := maybeReconcileReach(empty, dir); len(d) != 0 {
t.Fatal("one missed reply could be timing: kept")
}
if d := maybeReconcileReach(listed, dir); len(d) != 0 {
t.Fatal("listed again: kept, and the count resets")
}
maybeReconcileReach(empty, dir)
if d := maybeReconcileReach(empty, dir); len(d) != 1 || d[0] != 7 {
t.Fatalf("two missed replies in a row: dropped, got %v", d)
}
if got := heldReachGrants(dir); len(got) != 0 {
t.Fatalf("nothing held after the drop: %v", got)
}
}
// The enforcing report names the pairing, so the host signs the subject the box enforces.
func TestEnforcingNamesThePairingHost(t *testing.T) {
setupBox(t)
if got, _ := pairingEnforcing(tAnchor)["pairing_host"].(string); got != tAnchor {
t.Fatalf("pairing_host must be the pairing's own host name, got %q", got)
}
}
// Stopping a server drops every reach grant for it, in every pairing's file, so
// the next server built under that name inherits no one's reach.
func TestStoppingAServerDropsItsReachGrants(t *testing.T) {
setupBox(t)
stubServiceRuntime(t, "docker")
_, priv, _ := ed25519.GenerateKey(nil)
pub := base64.StdEncoding.EncodeToString(priv.Public().(ed25519.PublicKey))
cfgs, _ := loadPairings()
var dir string
for _, c := range cfgs {
if c.HostName == tAnchor {
dir = c.dcnDir()
}
}
if dir == "" {
t.Fatal("anchor pairing has no dir")
}
tcF, sigF := signedTerms(t, priv, reachTermsFor(tAnchor, "forge", 30))
tcW, sigW := signedTerms(t, priv, reachTermsFor(tAnchor, "wiki", 30))
f := &dcnGrantFile{VerifyPubkey: pub, Stores: map[string]dcnGrantRec{}, Reach: map[string]dcnGrantRec{
"1": {TermsCanonical: tcF, Signature: sigF}, "2": {TermsCanonical: tcW, Signature: sigW}}}
if err := dcnSaveFile(dir, f); err != nil {
t.Fatal(err)
}
if b, code := runService(forgeSpec("forge")); code != 0 {
t.Fatalf("up: %s", b)
}
b, code := runService(harnessSpec{Mode: "service", ServiceOp: "down", ServiceName: "forge"})
if code != 0 || decodeRes(t, b)["reach_grants_dropped"] != float64(1) {
t.Fatalf("down must drop the one grant for forge: %s", b)
}
if why := serviceReachAllowed(dir, tAnchor, "forge"); why == "" {
t.Fatal("the stopped server's grant is gone")
}
if why := serviceReachAllowed(dir, tAnchor, "wiki"); why != "" {
t.Fatalf("another server's grant stays: %s", why)
}
if got := droppedReachGrants(dir); len(got) != 1 || got[0] != 1 {
t.Fatalf("the dropped grant is reported so its host revokes it, got %v", got)
}
}
// The approval device files a reach grant ask (its purpose names the machine).
func TestTheDeviceTakesAReachAsk(t *testing.T) {
setupBox(t)
pub, _ := deviceInit()
_ = pinDeviceKey(pub)
hn, _ := os.Hostname()
id, err := deviceNewRequest(map[string]any{"kind": reachKind, "server": hn, "slot": "forge",
"subject": tAnchor, "ttl_days": float64(30)})
if err != nil || id == "" {
t.Fatalf("a reach ask must be filed on the device: %q %v", id, err)
}
}
// A record of another kind that happens to list a session op never opens a
// terminal: a management grant carries no kind, every other signed record does.
func TestAKindedRecordNeverOpensATerminal(t *testing.T) {
_, priv, _ := ed25519.GenerateKey(nil)
pub := base64.StdEncoding.EncodeToString(priv.Public().(ed25519.PublicKey))
dir := t.TempDir()
if err := dcnSaveFile(dir, &dcnGrantFile{VerifyPubkey: pub, Stores: map[string]dcnGrantRec{}}); err != nil {
t.Fatal(err)
}
hn, _ := os.Hostname()
exp := time.Now().Add(time.Hour).UTC().Format(time.RFC3339)
mgmt := map[string]any{"server": hn, "ops": []string{sessionOpOpen}, "expires": exp}
tc, sig := signedTerms(t, priv, mgmt)
if _, err := sessionVerifyGrant(dir, "", hn, sessionOpOpen, tc, sig, ""); err != nil {
t.Fatalf("a management grant listing the op opens it: %v", err)
}
for _, kind := range []string{reachKind, "dcn_store_grant", "dcn_move_plan"} {
rec := map[string]any{"kind": kind, "server": hn, "ops": []string{sessionOpOpen}, "expires": exp}
tc, sig := signedTerms(t, priv, rec)
if _, err := sessionVerifyGrant(dir, "", hn, sessionOpOpen, tc, sig, ""); err == nil {
t.Fatalf("a %s must never open a terminal", kind)
}
}
}