Files
mesh-catalog/modules/sudo/cmd/sudo-tools/escalation_test.go
T
jschoubben f30089c288 sudo: count any sudo rule as root, fail on what cannot be judged, and commit no binary
The review of 2026-10-09: an unknown account and sudo that could not be asked were answered not root, and
only NOPASSWD on ALL counted, though NOPASSWD on systemctl, tee, bash or pacman reaches root as surely,
and a password the person typed is kept by sudo for minutes. Any rule now counts, as the node-engine's
own judge of an agent account does (ADR 0266); what cannot be judged fails the answer. The bundle built
in the module's directory is removed and ignored.
2026-10-09 17:08:17 +02:00

71 lines
3.4 KiB
Go

package main
import (
"strings"
"testing"
)
// novox/hq ADR 0259 §8, as the review of 2026-10-09 asked: any sudo rule is root without a person — NOPASSWD on
// a named command (systemctl, tee, bash, pacman) as much as on all of them, and a rule asking a password too —
// and what cannot be judged is an error, never a no.
func TestWhoCanBecomeRootWithoutAPerson(t *testing.T) {
var calls []call
listing := func(account, rule string) Ran {
return Ran{Stdout: "User " + account + " may run the following commands on anchor:\n " + rule + "\n"}
}
run := byLine(map[string]Ran{
"id -nG ops": {Stdout: "ops wheel\n"},
"sudo -n -l -U ops": listing("ops", "(ALL : ALL) NOPASSWD: ALL"),
"id -nG agents": {Stdout: "agents\n"},
"sudo -n -l -U agents": {Stdout: "User agents is not allowed to run sudo on anchor.\n", Status: 1},
"id -nG builder": {Stdout: "builder docker\n"},
"id -nG careful": {Stdout: "careful wheel\n"},
"sudo -n -l -U careful": listing("careful", "(ALL : ALL) ALL"),
"id -nG units": {Stdout: "units\n"},
"sudo -n -l -U units": listing("units", "(root) NOPASSWD: /usr/bin/systemctl"),
"id -nG writer": {Stdout: "writer\n"},
"sudo -n -l -U writer": listing("writer", "(root) NOPASSWD: /usr/bin/tee /etc/hosts"),
"id -nG packager": {Stdout: "packager\n"},
"sudo -n -l -U packager": listing("packager", "(root) NOPASSWD: /usr/bin/pacman -Syu"),
"id -nG asother": {Stdout: "asother\n"},
"sudo -n -l -U asother": listing("asother", "(ops) NOPASSWD: /bin/bash"),
"id -nG containers": {Stdout: "containers lxd\n"},
}, &calls)
got, err := machine(run, 1000).Escalations([]string{"ops", "agents", "builder", "careful", "units", "writer",
"packager", "asother", "containers", "root"})
if err != nil {
t.Fatal(err)
}
want := map[string]bool{"ops": true, "agents": false, "builder": true, "careful": true, "units": true, "writer": true,
"packager": true, "asother": true, "containers": true, "root": true}
for _, e := range got {
if e.Root != want[e.Account] || e.Why == "" {
t.Errorf("%s: root %v (%s), want %v", e.Account, e.Root, e.Why, want[e.Account])
}
}
if _, err := machine(run, 1000).Escalations([]string{"a b"}); err == nil || !strings.Contains(err.Error(), "not an account name") {
t.Errorf("an account name that is not one was asked: %v", err)
}
}
// What cannot be judged is never a no: an unknown account, sudo that cannot be asked, a listing that cannot be
// read — each fails the whole answer.
func TestWhatCannotBeJudgedIsAnError(t *testing.T) {
var calls []call
run := byLine(map[string]Ran{
"id -nG ghost": {Status: 1, Stderr: "id: 'ghost': no such user"},
"id -nG agents": {Stdout: "agents\n"},
"sudo -n -l -U agents": {Status: 1, Stderr: "sudo: a password is required"},
"id -nG garbled": {Stdout: "garbled\n"},
"sudo -n -l -U garbled": {Stdout: "something else entirely\n"},
"id -nG fine": {Stdout: "fine\n"},
"sudo -n -l -U fine": {Stdout: "User fine is not allowed to run sudo on anchor.\n", Status: 1},
}, &calls)
for _, a := range []string{"ghost", "agents", "garbled"} {
got, err := machine(run, 1000).Escalations([]string{"fine", a})
if err == nil || !strings.Contains(err.Error(), a+" cannot be judged") {
t.Errorf("%s: answered %+v (%v), want an error", a, got, err)
}
}
}