bastion 0.60.0
ci / personal-data (push) Successful in 4s
ci / test (push) Successful in 3m36s
ci / fuzz (push) Successful in 3m35s

This commit is contained in:
Privatae
2026-11-02 00:10:58 +00:00
parent 4a5c310482
commit 4f8285ef18
100 changed files with 11537 additions and 840 deletions
+151
View File
@@ -0,0 +1,151 @@
package main
import (
"encoding/json"
"errors"
"net"
"os"
"path/filepath"
"strings"
"testing"
)
// laneSock stands the machine's bastion up on a real unix socket and points
// this process's reads at it, as an agent beside the system service sees it.
func laneSock(t *testing.T) {
t.Helper()
sock := filepath.Join(t.TempDir(), "ask.sock")
ln, err := net.Listen("unix", sock)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = ln.Close() })
go func() {
for {
c, err := ln.Accept()
if err != nil {
return
}
go askSockServe(c)
}
}()
prevPath, prevLane, prevID := readSockPath, readsGoThroughLane, askPeerIdentityFn
t.Cleanup(func() { readSockPath, readsGoThroughLane, askPeerIdentityFn = prevPath, prevLane, prevID })
readSockPath = func() string { return sock }
readsGoThroughLane = func() bool { return true }
// The kernel's answer, with this process's own uid read as laneTestUID:
// the suite runs as root, which is never a machine's owner account.
askPeerIdentityFn = func(c net.Conn) (uint32, string, error) {
uid, who, err := contextPeerIdentity(c)
if err == nil && uid == uint32(os.Getuid()) {
uid = laneTestUID
}
return uid, who, err
}
}
const laneTestUID = 1000
// The owner's agent, with no sudo and no browser, reads the machine's status,
// agents and activity from its bastion; the kernel's account decides it.
func TestTheOwnersAgentReadsTheMachineThroughTheLane(t *testing.T) {
askFlowWorld(t)
laneOwner(t, laneTestUID)
laneSock(t)
out := captureStdout(t)
if err := cmdStatus([]string{"--json"}); err != nil {
t.Fatalf("status: %v", err)
}
var s statusRead
if err := json.Unmarshal([]byte(printedSince(out, 0, "}\n")), &s); err != nil || s.Version != clientVersion {
t.Fatalf("status --json printed %q (%v)", out(), err)
}
before := len(out())
if err := cmdAgents([]string{"--json"}); err != nil {
t.Fatalf("agents: %v", err)
}
got := printedSince(out, before, "untrusted text")
if !strings.Contains(got, `"signature": "aider"`) || !strings.Contains(got, `"signature": "claude-code"`) {
t.Errorf("agents through the lane printed %q", got)
}
before = len(out())
if err := cmdActivity(nil); err != nil {
t.Fatalf("activity: %v", err)
}
if got := printedSince(out, before, "\n"); got == "" {
t.Error("activity through the lane printed nothing")
}
}
// Another account is told why and how the administrator reads it; the reads
// that mean something with its own permissions fall back to those.
func TestAnotherAccountIsRefusedTheMachinesReads(t *testing.T) {
askFlowWorld(t)
laneOwner(t, laneTestUID+1)
laneSock(t)
err := cmdStatus(nil)
if err == nil || !strings.Contains(err.Error(), "answers reads only for its owner's account") ||
!strings.Contains(err.Error(), "sudo -H privatae-bastion status") {
t.Errorf("status from another account: %v", err)
}
if err := cmdReport([]string{"--out", "-"}); err == nil || !strings.Contains(err.Error(), "sudo -H privatae-bastion report") {
t.Errorf("report from another account: %v", err)
}
out := captureStdout(t)
if err := cmdSchedules(nil); err != nil {
t.Errorf("schedules from another account did not fall back to its own: %v", err)
}
if printedSince(out, 0, "\n") == "" {
t.Error("schedules printed nothing")
}
}
// Installed for the whole machine, with nothing answering on its socket:
// status says so instead of failing.
func TestStatusSaysTheServiceIsNotAnswering(t *testing.T) {
uiTestConfigDir(t)
prevPath, prevLane := readSockPath, readsGoThroughLane
t.Cleanup(func() { readSockPath, readsGoThroughLane = prevPath, prevLane })
readSockPath = func() string { return filepath.Join(t.TempDir(), "nothing.sock") }
readsGoThroughLane = func() bool { return true }
out := captureStdout(t)
if err := cmdStatus(nil); err != nil {
t.Fatalf("status: %v", err)
}
if got := printedSince(out, 0, "status privatae-bastion"); !strings.Contains(got, "NOT ANSWERING") ||
!strings.Contains(got, "systemctl status privatae-bastion") {
t.Errorf("status printed %q", got)
}
}
// report --out - writes the report to standard output.
func TestReportToStandardOutput(t *testing.T) {
homeTestWorld(t, true)
out := captureStdout(t)
if err := cmdReport([]string{"--out", "-"}); err != nil {
t.Fatal(err)
}
if got := printedSince(out, 0, "No AI model wrote any of this report."); !strings.Contains(got, "# AI agents on ") {
t.Errorf("report --out - printed %q", got)
}
}
// An answer larger than the asking side reads is refused by the daemon with
// the reason, not cut off mid-answer, which the asker read as a bastion that
// does not answer at all (and status as NOT ANSWERING).
func TestAnAnswerTooLargeToReadIsRefusedWithTheReason(t *testing.T) {
askFlowWorld(t)
laneOwner(t, laneTestUID)
laneSock(t)
machineReads["test-huge"] = machineRead{build: func(readOpts) (any, error) {
return strings.Repeat("x", readMaxBytes), nil
}}
t.Cleanup(func() { delete(machineReads, "test-huge") })
var v string
err := readViaSock("test-huge", readOpts{}, &v)
var le *laneError
if !errors.As(err, &le) || le.down || !strings.Contains(le.why, "--since") {
t.Errorf("a read too large for the asker: %#v", err)
}
}