bastion 0.60.0
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// stubStopWorld replaces the scan, the process tree, the accounting and the
|
||||
// kill with fixed answers and returns the list the kill appends to, so a test
|
||||
// decides who would die without signalling anything real.
|
||||
func stubStopWorld(t *testing.T, agents []agentObs, tree map[int]int) *[]int {
|
||||
t.Helper()
|
||||
prevObs, prevTree, prevKill, prevFind := observedAgents, processTree, killProcess, processFindings
|
||||
prevCwd, prevStart, prevArgs := procCwdFn, procStartedFn, procCmdArgsFn
|
||||
t.Cleanup(func() {
|
||||
observedAgents, processTree, killProcess, processFindings = prevObs, prevTree, prevKill, prevFind
|
||||
procCwdFn, procStartedFn, procCmdArgsFn = prevCwd, prevStart, prevArgs
|
||||
})
|
||||
// Nothing about the real machine's processes leaks into the decision.
|
||||
procCwdFn = func(int) string { return "" }
|
||||
procStartedFn = func(int) (time.Time, bool) { return time.Time{}, false }
|
||||
procCmdArgsFn = func(int) []string { return nil }
|
||||
observedAgents = func() ([]agentObs, error) { return agents, nil }
|
||||
processTree = func() map[int]int { return tree }
|
||||
processFindings = func() (findingView, string) { return findingView{}, "" }
|
||||
killed := &[]int{}
|
||||
killProcess = func(pid int) error { *killed = append(*killed, pid); return nil }
|
||||
return killed
|
||||
}
|
||||
|
||||
var stopTestAgents = []agentObs{
|
||||
{PID: 101, Kind: kindAgent, Signature: "claude-code"},
|
||||
{PID: 4532, Kind: kindApp, Signature: "claude-desktop"},
|
||||
}
|
||||
|
||||
func TestLocalStopPurposeBindsThePidAndTheSignature(t *testing.T) {
|
||||
p, err := purposeForLocalStop(101, "claude-code")
|
||||
if err != nil || p != "local_stop|101|claude-code" {
|
||||
t.Fatalf("purpose %q %v", p, err)
|
||||
}
|
||||
got, err := purposeFromDetails(map[string]any{"kind": localStopKind, "pid": "101", "signature": "claude-code"})
|
||||
if err != nil || got != p {
|
||||
t.Errorf("the request's details give %q (%v), the stop it approves is %q", got, err, p)
|
||||
}
|
||||
if pid, sig, ok := parseLocalStopPurpose(p); !ok || pid != 101 || sig != "claude-code" {
|
||||
t.Errorf("parse(%q) = %d %q %v", p, pid, sig, ok)
|
||||
}
|
||||
if _, err := purposeForLocalStop(101, "a|b"); err == nil {
|
||||
t.Error("a signature holding the field separator was accepted")
|
||||
}
|
||||
if _, err := purposeForLocalStop(1, "init"); err == nil {
|
||||
t.Error("a stop of pid 1 got a purpose")
|
||||
}
|
||||
}
|
||||
|
||||
// The owner's stop holds the same lines as a Host's order: never an app the
|
||||
// person is using, never something the scan did not see as an agent, never
|
||||
// init or this bastion.
|
||||
func TestLocalStopRefusesWhatAHostsOrderRefuses(t *testing.T) {
|
||||
killed := stubStopWorld(t, stopTestAgents, map[int]int{4533: 4532})
|
||||
cases := map[int]string{
|
||||
4532: "an app you are running",
|
||||
303: "not an agent this bastion sees",
|
||||
1: "not an agent this machine may stop",
|
||||
os.Getpid(): "not an agent this machine may stop",
|
||||
}
|
||||
for pid, want := range cases {
|
||||
sig := "claude-desktop"
|
||||
if got := carryOutLocalStop(pid, sig); !strings.Contains(got, want) {
|
||||
t.Errorf("stop %d: %q, want it refused as %q", pid, got, want)
|
||||
}
|
||||
}
|
||||
if len(*killed) != 0 {
|
||||
t.Errorf("refused stops signalled %v", *killed)
|
||||
}
|
||||
}
|
||||
|
||||
// Approved for one agent, carried out only on that agent: a pid that now
|
||||
// wears another signature is somebody else.
|
||||
func TestLocalStopNeedsTheSignatureThatWasApproved(t *testing.T) {
|
||||
killed := stubStopWorld(t, []agentObs{{PID: 101, Kind: kindAgent, Signature: "aider"}}, nil)
|
||||
if got := carryOutLocalStop(101, "claude-code"); !strings.Contains(got, "not the claude-code that was approved") {
|
||||
t.Errorf("a pid that changed hands: %q", got)
|
||||
}
|
||||
if len(*killed) != 0 {
|
||||
t.Errorf("signalled %v", *killed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalStopTakesTheTreeAndSaysYouDidIt(t *testing.T) {
|
||||
killed := stubStopWorld(t, stopTestAgents, map[int]int{102: 101, 103: 102, 4533: 4532})
|
||||
before := len(localEventList)
|
||||
got := carryOutLocalStop(101, "claude-code")
|
||||
sort.Ints(*killed)
|
||||
if want := []int{101, 102, 103}; len(*killed) != 3 || (*killed)[0] != 101 || (*killed)[2] != 103 {
|
||||
t.Fatalf("killed %v, want %v", *killed, want)
|
||||
}
|
||||
if !strings.HasPrefix(got, "You stopped claude-code (pid 101)") {
|
||||
t.Errorf("outcome %q", got)
|
||||
}
|
||||
if len(localEventList) != before+1 || !strings.HasPrefix(localEventList[len(localEventList)-1].Text, "You stopped") {
|
||||
t.Error("the stop was not recorded on the machine")
|
||||
}
|
||||
}
|
||||
|
||||
// The daemon carries out an approval once, and never one older than the
|
||||
// window: a stop runs when the person asked for it, not when the daemon
|
||||
// happens to start.
|
||||
func TestLocalStopLoopActsOnceAndNeverLate(t *testing.T) {
|
||||
uiTestConfigDir(t)
|
||||
killed := stubStopWorld(t, stopTestAgents, map[int]int{102: 101})
|
||||
dir, err := machineFactorDir()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
enrolFactorAt(t, dir, machineTestSecret)
|
||||
purpose, _ := purposeForLocalStop(101, "claude-code")
|
||||
approval, err := stepupIssueApproval(dir, codeFor(t, machineTestSecret), purpose)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if existingMachineFactorDir() != dir {
|
||||
t.Fatalf("the loop would read %q, the factor is at %q", existingMachineFactorDir(), dir)
|
||||
}
|
||||
now := time.Now()
|
||||
localStopOnce(dir, now)
|
||||
if len(*killed) != 2 {
|
||||
t.Fatalf("first pass killed %v, want the agent and its child", *killed)
|
||||
}
|
||||
localStopOnce(dir, now.Add(time.Second))
|
||||
if len(*killed) != 2 {
|
||||
t.Fatalf("the same approval acted twice: %v", *killed)
|
||||
}
|
||||
if r := localStopResultsLoad(dir)[approval]; !strings.HasPrefix(r.Outcome, "You stopped") {
|
||||
t.Errorf("the waiting command would read %q", r.Outcome)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalStopLoopBurnsALateApprovalWithoutActing(t *testing.T) {
|
||||
uiTestConfigDir(t)
|
||||
killed := stubStopWorld(t, stopTestAgents, nil)
|
||||
dir, err := machineFactorDir()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
enrolFactorAt(t, dir, machineTestSecret)
|
||||
purpose, _ := purposeForLocalStop(101, "claude-code")
|
||||
approval, err := stepupIssueApproval(dir, codeFor(t, machineTestSecret), purpose)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
localStopOnce(dir, time.Now().Add(localStopWindow+time.Minute))
|
||||
if len(*killed) != 0 {
|
||||
t.Fatalf("a late approval acted: %v", *killed)
|
||||
}
|
||||
if r := localStopResultsLoad(dir)[approval]; !strings.Contains(r.Outcome, "ask again") {
|
||||
t.Errorf("the late approval's outcome: %q", r.Outcome)
|
||||
}
|
||||
if a := approvalsLoad(dir)[approval]; !a.Used {
|
||||
t.Error("the late approval was left usable")
|
||||
}
|
||||
}
|
||||
|
||||
// A stop asked from the command line is an ordinary request in the owner's
|
||||
// factor: `approvals --machine` lists it in plain words.
|
||||
func TestALocalStopIsAnOrdinaryRequestOfTheMachine(t *testing.T) {
|
||||
uiTestConfigDir(t)
|
||||
dir, err := machineFactorDir()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
purpose, _ := purposeForLocalStop(101, "claude-code")
|
||||
details := map[string]any{"kind": localStopKind, "pid": "101", "signature": "claude-code"}
|
||||
id, err := stepupNewRequest(dir, purpose, details)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r := requestsLoad(dir)[id]
|
||||
if r.State != "pending" || r.Purpose != purpose {
|
||||
t.Fatalf("request %+v", r)
|
||||
}
|
||||
if got := describeRequest(r); got != "Stop claude-code (pid 101) on this machine, and every process it started." {
|
||||
t.Errorf("the owner reads %q", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user