bastion 0.53.2
ci / personal-data (push) Successful in 5s
ci / test (push) Successful in 3m4s
ci / fuzz (push) Successful in 3m45s

This commit is contained in:
Privatae
2026-10-20 04:42:14 +00:00
commit e4c9b69b46
471 changed files with 159794 additions and 0 deletions
+362
View File
@@ -0,0 +1,362 @@
package main
// The parts of the op executor that exist only on Linux, where the sandbox
// runs: the upper-dir reader, the host's mount table, and the sandboxed dry
// run. The rest of the op tests are in ops_test.go and run on every unix.
import (
"encoding/base64"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
// ── the upper-dir reader (no sandbox needed) ────────────────────────────────
func TestUpperChangesReadCreatedModifiedAndLinks(t *testing.T) {
lower := t.TempDir()
upper := t.TempDir()
_ = os.MkdirAll(filepath.Join(lower, "etc/app"), 0o755)
_ = os.WriteFile(filepath.Join(lower, "etc/app/a.conf"), []byte("one\ntwo\n"), 0o644)
_ = os.WriteFile(filepath.Join(lower, "etc/app/same"), []byte("same\n"), 0o644)
_ = os.MkdirAll(filepath.Join(upper, "etc/app"), 0o755)
_ = os.WriteFile(filepath.Join(upper, "etc/app/a.conf"), []byte("one\nthree\n"), 0o644)
_ = os.WriteFile(filepath.Join(upper, "etc/app/new.txt"), []byte("hi\n"), 0o600)
_ = os.WriteFile(filepath.Join(upper, "etc/app/same"), []byte("same\n"), 0o644)
_ = os.Symlink("/etc/passwd", filepath.Join(upper, "etc/app/link"))
_ = os.MkdirAll(filepath.Join(upper, "var/new-dir"), 0o750)
// The walker names every change as the path it lands on under the
// overlay's host root (a fixture root here; "/" or a mount point live).
changes, err := opUpperChanges(upper, lower)
if err != nil {
t.Fatal(err)
}
got := map[string]opChange{}
for _, c := range changes {
got[strings.TrimPrefix(c.Path, lower)] = c
}
if got["/etc/app/a.conf"].Kind != "modified" || !strings.Contains(got["/etc/app/a.conf"].Diff, "- two") || !strings.Contains(got["/etc/app/a.conf"].Diff, "+ three") {
t.Fatalf("modified with a diff: %+v", got["/etc/app/a.conf"])
}
if got["/etc/app/new.txt"].Kind != "created" || got["/etc/app/new.txt"].Mode != "0600" || got["/etc/app/new.txt"].src != filepath.Join(upper, "etc/app/new.txt") {
t.Fatalf("created, with where it sits in the layer: %+v", got["/etc/app/new.txt"])
}
if _, ok := got["/etc/app/same"]; ok {
t.Fatal("a file copied up and written back unchanged is not a change")
}
if got["/etc/app/link"].Kind != "symlink" {
t.Fatalf("a symlink is named as one: %+v", got["/etc/app/link"])
}
if got["/var/new-dir"].Kind != "created_dir" {
t.Fatalf("a new directory: %+v", got["/var/new-dir"])
}
m := &opManifest{Effects: opEffects{Files: []string{lower + "/etc/app/"}}}
rep := &opReport{Changes: changes}
if why := opEffectsViolation(m, rep); !strings.Contains(why, "symlink") {
t.Fatalf("the link is refused first: %s", why)
}
rep.Changes = []opChange{got["/var/new-dir"]}
if why := opEffectsViolation(m, rep); !strings.Contains(why, "outside") {
t.Fatalf("a change outside the effects is refused: %s", why)
}
rep.Changes = []opChange{got["/etc/app/a.conf"], got["/etc/app/new.txt"]}
if why := opEffectsViolation(m, rep); why != "" {
t.Fatalf("changes inside the effects pass: %s", why)
}
// Whatever the effects say, the box's own record is never a change: the
// pinned key, ops.json, the nonce ledger.
own, _ := configDirPath()
m2 := &opManifest{Effects: opEffects{Files: []string{own + "/"}}}
rep.Changes = []opChange{{Path: own + "/dcn/anchor/dcn_grant.json", Kind: "modified"}}
if why := opEffectsViolation(m2, rep); !strings.Contains(why, "own record") {
t.Fatalf("the machine's own record is refused even inside matching effects: %s", why)
}
// A folder ABOVE the record, removed: takes the record with it — refused.
m3 := &opManifest{Effects: opEffects{Files: []string{filepath.Dir(own) + "/"}}}
rep.Changes = []opChange{{Path: filepath.Dir(own), Kind: "removed_dir"}}
if why := opEffectsViolation(m3, rep); !strings.Contains(why, "own record") {
t.Fatalf("a folder holding the record is refused: %s", why)
}
// A folder removed that holds a mount the sandbox never looked into.
if mounts, err := opHostMounts(); err == nil {
for _, mt := range mounts {
if mt.Point == "/" || filepath.Dir(mt.Point) == "/" && strings.Count(mt.Point, "/") == 1 && filepath.Dir(mt.Point) == "/" {
// a mount directly under /: its parent is /, which effects never name
continue
}
parent := filepath.Dir(mt.Point)
if parent == "/" {
continue
}
m4 := &opManifest{Effects: opEffects{Files: []string{parent + "/"}}}
rep.Changes = []opChange{{Path: parent, Kind: "removed_dir"}}
if why := opEffectsViolation(m4, rep); !strings.Contains(why, "holds a mount") {
t.Fatalf("removing %s, which holds the mount %s, is refused: %s", parent, mt.Point, why)
}
break
}
}
}
func TestHostMountsAndEffectPrefix(t *testing.T) {
mounts := []opHostMount{{"/", "ext4"}, {"/srv", "xfs"}, {"/srv/share", "nfs4"}, {"/proc", "proc"}, {"/home", "ext4"}}
for p, want := range map[string]string{
"/etc/x": "/",
"/srv/a/b": "/srv",
"/srv/share/x": "/srv/share",
"/srv/shared/x": "/srv",
"/home": "/home",
"/homely/x": "/",
} {
if got := opMountOf(mounts, p).Point; got != want {
t.Errorf("%s: on %s, want %s", p, got, want)
}
}
for e, want := range map[string]string{
"/etc/app/": "/etc/app",
"/var/log/app/*.log": "/var/log/app",
"/etc/one.conf": "/etc/one.conf",
"/srv/share/[ab]*/x": "/srv/share",
} {
if got := opEffectPrefix(e); got != want {
t.Errorf("%s: prefix %s, want %s", e, got, want)
}
}
live, err := opHostMounts()
if err != nil {
t.Skip("no mountinfo here")
}
if opMountOf(live, "/").Point != "/" {
t.Fatal("the root is always a mount")
}
}
func TestOpDryRunShowsTheChangeAndApplyCommitsExactlyThat(t *testing.T) {
requireSandbox(t)
dcn, priv := opTestBox(t)
root := filepath.Join(opTestRoot, fmt.Sprintf("r%d", time.Now().UnixNano()))
t.Cleanup(func() { _ = os.RemoveAll(root) })
files := opFixture(root, "")
sha, _ := opDirDigest(files)
params := `{"name":"x","count":1,"kind":"a"}`
run := opTerm(opKindRun, sha, params, "", 1, time.Hour, "root")
pair := signTermsPair(t, priv, run)
if _, errOut, code := driveJob(t, tAnchor, mgmtJob{Op: "op_sync", Args: append(pair, bundleB64(files))}, "", dcn); code != 0 {
t.Fatalf("sync: %s", errOut)
}
out, errOut, code := driveJob(t, tAnchor, mgmtJob{Op: "op_dry_run", Args: pair}, "", dcn)
if code != 0 {
t.Fatalf("dry run: %d %s", code, errOut)
}
var rep opReport
_ = json.Unmarshal([]byte(out), &rep)
if len(rep.Changes) != 2 || rep.Changes[0].Kind != "created_dir" || rep.Changes[1].Path != root+"/hello.txt" || rep.Changes[1].Kind != "created" {
t.Fatalf("the dry run names the folder and the file it would create: %+v", rep.Changes)
}
if !strings.Contains(rep.Changes[1].Diff, "+ hello "+params) {
t.Fatalf("with the file's text: %q", rep.Changes[1].Diff)
}
if _, err := os.Stat(root); err == nil {
t.Fatal("a dry run commits nothing")
}
if len(rep.ChangesSHA256) != 64 || rep.ChangesSHA256 != opChangesDigest(rep.Changes) {
t.Fatalf("the report names its change list: %q", rep.ChangesSHA256)
}
// An apply binds the change list the dry run showed: terms without it
// are refused before any approval is looked at.
if _, errOut, code := driveJob(t, tAnchor, mgmtJob{Op: "op_apply", Args: pair}, "", dcn); code != 126 || !strings.Contains(errOut, "changes_sha256") {
t.Fatalf("apply binds the dry run: %d %q", code, errOut)
}
run["changes_sha256"] = rep.ChangesSHA256
pair = signTermsPair(t, priv, run)
// Apply without the approval: refused; with it: the same change lands.
if _, errOut, code := driveJob(t, tAnchor, mgmtJob{Op: "op_apply", Args: pair}, "", dcn); code != 126 || !strings.Contains(errOut, "approval") {
t.Fatalf("apply spends an approval: %d %q", code, errOut)
}
pub, _ := deviceInit()
_ = pinDeviceKey(pub)
changes := rep.ChangesSHA256
// An approval the device signed for another op, other params, or another
// machine is not this one's: the purpose binds all of them.
for name, other := range map[string]*opTerms{
"another op": {Server: run["server"].(string), Op: "write-hello", SHA256: strings.Repeat("1", 64), Params: params, Expires: run["expires"].(string), Changes: changes},
"other params": {Server: run["server"].(string), Op: "write-hello", SHA256: sha, Params: `{"name":"y","count":1,"kind":"a"}`, Expires: run["expires"].(string), Changes: changes},
"another machine": {Server: run["server"].(string) + "-2", Op: "write-hello", SHA256: sha, Params: params, Expires: run["expires"].(string), Changes: changes},
"other changes": {Server: run["server"].(string), Op: "write-hello", SHA256: sha, Params: params, Expires: run["expires"].(string), Changes: strings.Repeat("2", 64)},
} {
p, _ := purposeForOpRun(other)
a, _ := deviceSign(p, time.Minute)
run["approval"] = a
if _, errOut, code := driveJob(t, tAnchor, mgmtJob{Op: "op_apply", Args: signTermsPair(t, priv, run)}, "", dcn); code != 126 || !strings.Contains(errOut, "approval") {
t.Fatalf("%s: an approval for something else is refused: %d %q", name, code, errOut)
}
}
// Five refused writes just happened: the box has closed this host's write
// lanes (the quarantine, tested on its own). The person lifts it here.
quarantineClear(tAnchor)
// Terms whose change list is not what this machine's run produces land
// nothing, approval or not — the approval was for the list on the card.
{
otherRun := opTerm(opKindRun, sha, params, "", 1, time.Hour, "root")
otherRun["nonce"] = "fedcba9876543210fedcba9876543210"
otherRun["changes_sha256"] = strings.Repeat("2", 64)
p, _ := purposeForOpRun(&opTerms{Server: run["server"].(string), Op: "write-hello", SHA256: sha, Params: params,
Expires: otherRun["expires"].(string), Changes: strings.Repeat("2", 64)})
a, _ := deviceSign(p, time.Minute)
otherRun["approval"] = a
if _, errOut, code := driveJob(t, tAnchor, mgmtJob{Op: "op_apply", Args: signTermsPair(t, priv, otherRun)}, "", dcn); code != 126 || !strings.Contains(errOut, "other than what the dry run showed") {
t.Fatalf("a different change list lands nothing: %d %q", code, errOut)
}
if _, err := os.Stat(root); err == nil {
t.Fatal("refused before commit means nothing landed")
}
// Its nonce is burned (one attempt per approval) and its status says
// refused, not applied — what a host that lost the answer must read.
st, _, _ := driveJob(t, tAnchor, mgmtJob{Op: "op_run_status", Args: []string{otherRun["nonce"].(string)}}, "", dcn)
var status map[string]any
_ = json.Unmarshal([]byte(st), &status)
if status["found"] != true || status["applied"] != false || status["nonce_burned"] != true || !strings.Contains(fmt.Sprint(status["error"]), "other than") {
t.Fatalf("a refused apply reads as refused, never applied: %s", st)
}
}
purpose, _ := purposeForOpRun(&opTerms{Server: run["server"].(string), Op: "write-hello", SHA256: sha, Params: params, Expires: run["expires"].(string), Changes: changes})
if p2, err := purposeFromDetails(map[string]any{"kind": opKindRun, "op": "write-hello", "version": "1.0.0", "server": run["server"],
"privilege": "root", "params": params, "expires": run["expires"], "sha256": sha, "changes_sha256": changes, "uses": float64(1)}); err != nil || p2 != purpose {
t.Fatalf("the device derives the apply's purpose from the details, change list included: %v %s", err, p2)
}
approval, _ := deviceSign(purpose, time.Minute)
run["approval"] = approval
pair = signTermsPair(t, priv, run)
quarantineClear(tAnchor)
out, errOut, code = driveJob(t, tAnchor, mgmtJob{Op: "op_apply", Args: pair}, "", dcn)
if code != 0 {
t.Fatalf("apply: %d %s", code, errOut)
}
_ = json.Unmarshal([]byte(out), &rep)
got, err := os.ReadFile(root + "/hello.txt")
if err != nil || string(got) != "hello "+params+"\n" {
t.Fatalf("the diff the person saw is the diff that landed: %q %v", got, err)
}
if len(rep.Committed) != 2 || rep.VerifyError != "" {
t.Fatalf("committed and verified: %+v", rep)
}
// The same signed op_run cannot be applied twice.
if _, errOut, code := driveJob(t, tAnchor, mgmtJob{Op: "op_apply", Args: pair}, "", dcn); code != 126 || !strings.Contains(errOut, "applied already") {
t.Fatalf("one use: %d %q", code, errOut)
}
// A host that lost the answer asks what became of the run — a read.
st, _, code := driveJob(t, tAnchor, mgmtJob{Op: "op_run_status", Args: []string{run["nonce"].(string)}}, "", dcn)
var status map[string]any
_ = json.Unmarshal([]byte(st), &status)
if code != 0 || status["found"] != true || status["applied"] != true || status["nonce_burned"] != true || status["changes_sha256"] != rep.ChangesSHA256 {
t.Fatalf("the run's status names it applied: %s", st)
}
st, _, _ = driveJob(t, tAnchor, mgmtJob{Op: "op_run_status", Args: []string{"never-sent-nonce-0000"}}, "", dcn)
_ = json.Unmarshal([]byte(st), &status)
if status["found"] != false || status["nonce_burned"] != false {
t.Fatalf("a run that never arrived: not found, nonce unburned: %s", st)
}
// The record: the dry run, the refused apply with the other list, the apply.
entries, _ := os.ReadDir(opsRunsDir(dcn))
if len(entries) != 3 {
t.Fatalf("every run is recorded: %d", len(entries))
}
}
// The closed view, run for real: an op reads what it declared and the system
// base, and every other file — a sibling of its own folder, a folder beside
// it, /etc/shadow — reads as empty. A declared path that has come to resolve
// onto a credential store since it was accepted is refused at run time.
func TestOpSandboxShowsOnlyTheBaseAndWhatTheOpDeclared(t *testing.T) {
requireSandbox(t)
setupBox(t)
n := time.Now().UnixNano()
root := filepath.Join(opTestRoot, fmt.Sprintf("v%d", n))
sibling := filepath.Join(opTestRoot, fmt.Sprintf("s%d.txt", n))
readme := filepath.Join(opTestRoot, fmt.Sprintf("readme%d.txt", n))
hiddenDir := filepath.Join(opTestRoot, fmt.Sprintf("hd%d", n))
link := filepath.Join(opTestRoot, fmt.Sprintf("ln%d", n))
opDir := filepath.Join(opTestRoot, fmt.Sprintf("op%d", n))
for _, p := range []string{root, sibling, readme, hiddenDir, link, opDir} {
p := p
t.Cleanup(func() { _ = os.RemoveAll(p) })
}
_ = os.MkdirAll(hiddenDir, 0o755)
_ = os.MkdirAll(opDir, 0o755)
_ = os.WriteFile(sibling, []byte("HIDDEN-SIBLING\n"), 0o644)
_ = os.WriteFile(readme, []byte("DECLARED-READ\n"), 0o644)
_ = os.WriteFile(filepath.Join(hiddenDir, "secret"), []byte("HIDDEN-DIR\n"), 0o644)
extra := fmt.Sprintf(`cat %s %s %s/secret 2>/dev/null; echo "SHADOW=$(grep -c '^root:' /etc/shadow 2>/dev/null)"; echo "PY=$(/usr/bin/python3 -c 'print(6*7)' 2>&1)"; true`,
readme, sibling, hiddenDir)
for _, f := range opFixture(root, extra) {
raw, _ := base64.StdEncoding.DecodeString(f.B64)
_ = os.WriteFile(filepath.Join(opDir, f.Path), raw, os.FileMode(f.Mode))
}
man := strings.Replace(fmt.Sprintf(opTestManifest, root), "modes:", fmt.Sprintf("reads: [%q, %q]\nmodes:", readme, link), 1)
m, err := parseOpManifest([]byte(man))
if err != nil {
t.Fatal(err)
}
rep, err := opRunSandboxed(opDir, m, opModeDryRun, `{"name":"x","count":1,"kind":"a"}`)
if rep != nil {
defer rep.cleanup()
}
if err != nil {
t.Fatalf("the op runs: %v", err)
}
for _, want := range []string{"DECLARED-READ", "SHADOW=0", "PY=42"} {
if !strings.Contains(rep.Output, want) {
t.Fatalf("want %q in the op's output: %q", want, rep.Output)
}
}
for _, never := range []string{"HIDDEN-SIBLING", "HIDDEN-DIR"} {
if strings.Contains(rep.Output, never) {
t.Fatalf("%q is outside what the op declared and must read as empty: %q", never, rep.Output)
}
}
if len(rep.Changes) != 2 || rep.Changes[1].Path != root+"/hello.txt" {
t.Fatalf("the op still writes what it declared: %+v", rep.Changes)
}
// The declared read becomes a link onto a credential store after the op
// was accepted: the run is refused, as what it now names.
if _, err := os.Stat("/etc/ssh"); err == nil {
if err := os.Symlink("/etc/ssh", link); err != nil {
t.Fatal(err)
}
rep2, err := opRunSandboxed(opDir, m, opModeDryRun, `{"name":"x","count":1,"kind":"a"}`)
if rep2 != nil {
rep2.cleanup()
}
if err == nil || !strings.Contains(err.Error(), "sensitive") {
t.Fatalf("a declared path that now resolves onto /etc/ssh is refused: %v", err)
}
}
}
// A read on a mount the sandbox does not carry (a network share, a tmpfs)
// would show an empty folder and a dry run that looks right: refused, like an
// effect there. Against a mount table, so it runs everywhere.
func TestOpEffectOrReadOnAMountTheSandboxDoesNotCarryIsRefused(t *testing.T) {
mounts := []opHostMount{{Point: "/", FSType: "ext4"}, {Point: "/srv", FSType: "xfs"}, {Point: "/mnt/share", FSType: "nfs4"}}
for _, c := range []struct {
declared string
forms []string
want string
}{
{"/etc/nginx/site.conf", []string{"/etc/nginx/site.conf"}, ""},
{"/srv/app/", []string{"/srv/app"}, ""},
{"/mnt/share/cfg", []string{"/mnt/share/cfg"}, "/mnt/share/cfg is on a nfs4 mount"},
{"/mnt/share/*.conf", []string{"/mnt/share/*.conf"}, "/mnt/share/*.conf is on a nfs4 mount"},
// A link on a carried disk that resolves onto the share: judged as it resolves.
{"/srv/app/cfg", []string{"/srv/app/cfg", "/mnt/share/cfg"}, "/srv/app/cfg is on a nfs4 mount"},
} {
err := opUncarriedDeclared(mounts, c.declared, c.forms)
if (c.want == "") != (err == nil) || (err != nil && !strings.Contains(err.Error(), c.want)) {
t.Errorf("%s: got %v, want %q", c.declared, err, c.want)
}
}
}