bastion 0.58.0
ci / personal-data (push) Successful in 4s
ci / test (push) Successful in 4m5s
ci / fuzz (push) Successful in 4m10s

This commit is contained in:
Privatae
2026-10-29 15:54:51 +00:00
parent 081763a675
commit 01889e2a48
37 changed files with 5629 additions and 90 deletions
+177
View File
@@ -0,0 +1,177 @@
package main
// ops_verbs_only_linux_test.go — an op with no program (interpreter: none)
// runs on a machine with no overlay (the edges), through op_dry_run and an
// approved op_apply, with only the verbs' own tools. Stand-ins for systemd
// and the probe; nothing here needs root.
import (
"context"
"encoding/json"
"fmt"
"strings"
"testing"
"time"
)
func TestVerbsOnlyOpRunsWithNoOverlay(t *testing.T) {
noOverlay(t)
prevProbe := opVerbProbe
opVerbProbe = func([]opVerbLine) string { return "" }
t.Cleanup(func() { opVerbProbe = prevProbe })
var calls []string
state := map[string]string{"LoadState": "loaded", "ActiveState": "failed", "SubState": "failed", "MainPID": "0"}
prevCtl, prevJournal := opSystemctl, opJournalctl
opSystemctl = func(_ context.Context, args ...string) (string, error) {
calls = append(calls, strings.Join(args, " "))
if args[0] == "show" {
var b strings.Builder
for k, v := range state {
fmt.Fprintf(&b, "%s=%s\n", k, v)
}
return b.String(), nil
}
if args[0] == "restart" {
state["ActiveState"], state["SubState"], state["MainPID"] = "active", "running", "42"
}
return "", nil
}
opJournalctl = func(_ context.Context, _ ...string) (string, error) { return "started\n", nil }
t.Cleanup(func() { opSystemctl, opJournalctl = prevCtl, prevJournal })
dcn, priv := opTestBox(t)
enrollFactor(t, dcn)
files := verbsOnlyFiles(false)
sha, _ := opDirDigest(files)
terms := opTerm(opKindRun, sha, `{}`, "", 1, time.Hour, "root")
terms["op"] = "restart-web"
pair := signTermsPair(t, priv, terms)
if _, errOut, code := driveJob(t, tAnchor, mgmtJob{Op: "op_sync", Args: append(pair, bundleB64(files))}, "", dcn); code != 0 {
t.Fatalf("held: %s", errOut)
}
out, errOut, code := driveJob(t, tAnchor, mgmtJob{Op: "op_dry_run", Args: pair}, "", dcn)
if code != 0 {
t.Fatalf("the dry run on a machine with no overlay: %d %s", code, errOut)
}
var rep opReport
if err := json.Unmarshal([]byte(out), &rep); err != nil || len(rep.Changes) != 2 || rep.ChangesSHA256 == "" ||
!strings.Contains(rep.Changes[0].Note, "failed/failed → restarted") {
t.Fatalf("the dry run is the verbs' own read: %v %s", err, out)
}
for _, c := range calls {
if strings.HasPrefix(c, "restart") {
t.Fatal("a verb never runs in a dry run")
}
}
// The apply, approved for exactly that change list.
terms["changes_sha256"] = rep.ChangesSHA256
purpose, err := purposeForOpRun(&opTerms{Server: terms["server"].(string), Op: "restart-web", SHA256: sha,
Params: `{}`, Expires: terms["expires"].(string), Changes: rep.ChangesSHA256})
if err != nil {
t.Fatal(err)
}
raw, _ := b32decode(tSecret)
approval, err := stepupIssueApproval(dcn, hotp(raw, time.Now().Unix()/stepupPeriod), purpose)
if err != nil {
t.Fatal(err)
}
terms["approval"] = approval
out, errOut, code = driveJob(t, tAnchor, mgmtJob{Op: "op_apply", Args: signTermsPair(t, priv, terms)}, "", dcn)
if code != 0 {
t.Fatalf("the apply: %d %s", code, errOut)
}
rep = opReport{}
_ = json.Unmarshal([]byte(out), &rep)
if len(rep.Verbs) != 1 || !rep.Verbs[0].OK || len(rep.Checks) != 1 || !rep.Checks[0].OK || rep.VerifyError != "" {
t.Fatalf("the unit restarted and its log checked: %s", out)
}
if n := countPrefix(calls, "restart web.service"); n != 1 {
t.Fatalf("restarted once: %v", calls)
}
// A machine without a verb's tool refuses the run, and that is the
// machine's state, never the host's strike.
opVerbProbe = func(lines []opVerbLine) string {
return "unit.restart web.service runs systemctl, which is not installed here"
}
pair2 := signTermsPair(t, priv, terms)
_, errOut, code = driveJob(t, tAnchor, mgmtJob{Op: "op_dry_run", Args: pair2}, "", dcn)
if code != 126 || !strings.Contains(errOut, noVerbToolHere) || refusalCounts("op_dry_run", errOut) {
t.Fatalf("a missing tool: %d %q", code, errOut)
}
}
func countPrefix(xs []string, p string) int {
n := 0
for _, x := range xs {
if strings.HasPrefix(x, p) {
n++
}
}
return n
}
func TestVerbsReportNamesEachVerbsTool(t *testing.T) {
prev := opLookPath
opLookPath = func(file string) (string, error) {
if file == "systemctl" || file == "journalctl" {
return "/usr/bin/" + file, nil
}
return "", fmt.Errorf("not found")
}
t.Cleanup(func() { opLookPath = prev })
rep := opVerbsReport()
for _, verb := range opVerbNames() {
if _, ok := rep[verb]; !ok {
t.Fatalf("every verb is on the beat: %v", rep)
}
}
if !processIsRoot() {
if v := rep["unit.restart"].(map[string]any); v["ok"] != false {
t.Fatalf("a bastion that is not root runs no verb: %v", v)
}
return
}
if v := rep["unit.restart"].(map[string]any); v["ok"] != true {
t.Fatalf("systemctl here: %v", v)
}
if v := rep["k8s.set_image"].(map[string]any); v["ok"] != false || !strings.Contains(v["why"].(string), "kubectl") {
t.Fatalf("no kubectl here: %v", v)
}
if v := rep["logs.check"].(map[string]any); v["ok"] != true {
t.Fatalf("the journal is enough for a unit's log: %v", v)
}
}
// k8s.set_image runs git as well as kubectl (its ancestry check fetches the
// source), so a machine with kubectl and no git is told it cannot run the
// verb, before any run, never by a failed apply.
//
// RESULT: red before — k8s.set_image named kubectl alone.
func TestSetImageNeedsGitForItsAncestryCheck(t *testing.T) {
tools := opVerbTools("k8s.set_image", nil)
if strings.Join(tools, ",") != "kubectl,git" {
t.Fatalf("k8s.set_image runs kubectl and git: %v", tools)
}
if got := opVerbTools("tree.checkout", nil); strings.Join(got, ",") != "git" {
t.Fatalf("tree.checkout runs git: %v", got)
}
prev := opLookPath
opLookPath = func(file string) (string, error) {
if file == "kubectl" {
return "/usr/bin/kubectl", nil
}
return "", fmt.Errorf("not found")
}
t.Cleanup(func() { opLookPath = prev })
if !processIsRoot() {
return // a bastion that is not root runs no verb at all
}
line := setImageLine("sha256:" + strings.Repeat("2", 64))
if why := opVerbProbe([]opVerbLine{line}); !strings.Contains(why, "git") {
t.Fatalf("a machine with kubectl and no git cannot run k8s.set_image: %q", why)
}
if v := opVerbsReport()["k8s.set_image"].(map[string]any); v["ok"] != false || !strings.Contains(v["why"].(string), "git") {
t.Fatalf("the beat says so: %v", v)
}
}