bastion 0.60.0
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user