//go:build !windows // Uses setupBox / writePairingState / signTerms / grantTerms / driveOp / // tAnchor / tMember from anchor_test.go, which are unix-only. buildtags_test.go // enforces the tag. package main import ( "encoding/json" "io" "net" "net/http" "net/http/httptest" "os" "path/filepath" "strconv" "strings" "sync" "testing" "time" "github.com/gorilla/websocket" ) // fakeRuntime replaces the container runtime for one test. Every call is // recorded; nothing touches a real docker or podman. type fakeRuntime struct { mu sync.Mutex calls [][]string running map[string]bool netMode map[string]string // container -> reported network mode (default "none") failOn string // a verb ("pull", "run") that returns an error } func stubServiceRuntime(t *testing.T, rt string) *fakeRuntime { t.Helper() f := &fakeRuntime{running: map[string]bool{}, netMode: map[string]string{}} oldRun, oldRT := serviceRun, serviceRuntime serviceRuntime = func() string { return rt } serviceRun = func(_ time.Duration, name string, args ...string) (string, error) { f.mu.Lock() defer f.mu.Unlock() f.calls = append(f.calls, append([]string{name}, args...)) if len(args) == 0 { return "", nil } if args[0] == f.failOn { return "boom", os.ErrInvalid } switch args[0] { case "run": for i, a := range args { if a == "--name" && i+1 < len(args) { f.running[args[i+1]] = true } } return "0123456789abcdef0123\n", nil case "rm": delete(f.running, args[len(args)-1]) case "inspect": c := args[len(args)-1] if f.failOn == "inspect" { return "Cannot connect to the Docker daemon", os.ErrDeadlineExceeded } if !f.running[c] { return "Error: No such container: " + c, os.ErrNotExist } if strings.Contains(strings.Join(args, " "), "State.Running") { return "true\n", nil } if strings.Contains(strings.Join(args, " "), "NetworkMode") { if m, ok := f.netMode[c]; ok { return m + "\n", nil } return "none\n", nil } return "172.17.0.9\n", nil } return "", nil } t.Cleanup(func() { serviceRun, serviceRuntime = oldRun, oldRT }) return f } func (f *fakeRuntime) runArgs(t *testing.T) []string { t.Helper() f.mu.Lock() defer f.mu.Unlock() for i := len(f.calls) - 1; i >= 0; i-- { if len(f.calls[i]) > 1 && f.calls[i][1] == "run" { return f.calls[i] } } t.Fatal("no `run` call was made") return nil } func forgeSpec(name string) harnessSpec { return harnessSpec{ Mode: "service", ServiceOp: "up", ServiceName: name, Image: "gitea/gitea:1.26.2-rootless", Socket: "gitea.sock", DataPath: "/var/lib/gitea", DataUID: 1000, Envs: map[string]string{"GITEA__server__DISABLE_SSH": "true"}, } } func decodeRes(t *testing.T, b []byte) map[string]any { t.Helper() var m map[string]any if err := json.Unmarshal(b, &m); err != nil { t.Fatalf("result is not JSON: %s", b) } return m } // The box refuses, before touching any runtime, every spec shape that could // escape the slot: a name that is a path or a flag, an image that is a flag, // a data path that is relative, the root, or a docker volume option. func TestServiceSpecValidation(t *testing.T) { good := forgeSpec("forge") if why := validateServiceSpec(good); why != "" { t.Fatalf("a normal forge spec must pass: %s", why) } for _, img := range []string{ "gitea/gitea:1.26.2-rootless", "codeberg.org/forgejo/forgejo:9", "localhost:5000/team/forge", "ghcr.io/owner/app@sha256:" + strings.Repeat("a", 64), "redis", } { sp := forgeSpec("forge") sp.Image = img if why := validateServiceSpec(sp); why != "" { t.Errorf("registry reference %q must pass: %s", img, why) } } bad := map[string]func(s *harnessSpec){ "name with slash": func(s *harnessSpec) { s.ServiceName = "../etc" }, "name with dot": func(s *harnessSpec) { s.ServiceName = "a.b" }, "name leading dash": func(s *harnessSpec) { s.ServiceName = "-x" }, "name upper": func(s *harnessSpec) { s.ServiceName = "Forge" }, "name empty": func(s *harnessSpec) { s.ServiceName = "" }, "image as flag": func(s *harnessSpec) { s.Image = "--privileged" }, "image archive": func(s *harnessSpec) { s.Image = "docker-archive:/home/u/x.tar" }, "image oci dir": func(s *harnessSpec) { s.Image = "oci:/srv/layout" }, "image dir transport": func(s *harnessSpec) { s.Image = "dir:/tmp/img" }, "image local storage": func(s *harnessSpec) { s.Image = "containers-storage:foo" }, "image daemon": func(s *harnessSpec) { s.Image = "docker-daemon:foo:latest" }, "image url": func(s *harnessSpec) { s.Image = "docker://gitea/gitea" }, "image uppercase": func(s *harnessSpec) { s.Image = "Gitea/Gitea" }, "image with space": func(s *harnessSpec) { s.Image = "a b" }, "image empty": func(s *harnessSpec) { s.Image = "" }, "socket missing": func(s *harnessSpec) { s.Socket = "" }, "socket path": func(s *harnessSpec) { s.Socket = "../../etc/x.sock" }, "socket subdir": func(s *harnessSpec) { s.Socket = "run/gitea.sock" }, "socket dotdot": func(s *harnessSpec) { s.Socket = ".." }, "data path relative": func(s *harnessSpec) { s.DataPath = "var/lib" }, "data path root": func(s *harnessSpec) { s.DataPath = "/" }, "data path unclean": func(s *harnessSpec) { s.DataPath = "/var/../etc" }, "data path volume opt": func(s *harnessSpec) { s.DataPath = "/data:rw" }, "env key flag": func(s *harnessSpec) { s.Envs = map[string]string{"-e": "x"} }, "env key equals": func(s *harnessSpec) { s.Envs = map[string]string{"A=B": "x"} }, "env value newline": func(s *harnessSpec) { s.Envs = map[string]string{"A": "x\ny"} }, "data uid negative": func(s *harnessSpec) { s.DataUID = -1 }, "data uid root": func(s *harnessSpec) { s.DataUID = 0 }, } for label, mut := range bad { s := forgeSpec("forge") mut(&s) if why := validateServiceSpec(s); why == "" { t.Errorf("%s: must be refused", label) } } } // `up` starts a container that STAYS: restart policy, the slot's own data dir // as the only mount, no new privileges, all caps dropped, and never -p. func TestServiceUpRunsAStayingContainerWithNoPublishedPort(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", t.TempDir()) f := stubServiceRuntime(t, "docker") spec := forgeSpec("forge") spec.owner = "anchor.host.privatae.ai" b, code := runService(spec) if code != 0 { t.Fatalf("up failed: %s", b) } res := decodeRes(t, b) if res["tunnel_port"] != float64(servicePortBase) { t.Fatalf("the first slot takes the first port, got %v", res["tunnel_port"]) } args := f.runArgs(t) joined := strings.Join(args, " ") for _, want := range []string{ "--restart unless-stopped", "--security-opt no-new-privileges", "--cap-drop ALL", "--network none", "--name privatae-svc-forge", "-e GITEA__server__DISABLE_SSH=true", } { if !strings.Contains(joined, want) { t.Errorf("run args lack %q: %s", want, joined) } } for _, a := range args { if a == "-p" || a == "--publish" || strings.HasPrefix(a, "--publish=") || a == "--privileged" || a == "--network=host" || a == "bridge" || a == "host" { t.Fatalf("run must never carry %q (it mutates the host network or widens the container): %s", a, joined) } } data, _ := serviceDataDir("forge") if !strings.Contains(joined, "-v "+data+":/var/lib/gitea") { t.Fatalf("the one mount must be the slot's data dir: %s", joined) } if args[len(args)-1] != spec.Image { t.Fatalf("the image must be the last argument, got %q", args[len(args)-1]) } rec, err := loadServiceRecord("forge") if err != nil { t.Fatalf("no record written: %v", err) } if rec.Owner != spec.owner || rec.Socket != "gitea.sock" || rec.TunnelPort != servicePortBase { t.Fatalf("record wrong: %+v", rec) } if st, _ := os.Stat(data); st == nil || !st.IsDir() { t.Fatal("the data dir must exist") } } // A slot keeps its tunnel port across a re-up; a second slot takes the next. func TestServicePortsAreStableAndDistinct(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", t.TempDir()) stubServiceRuntime(t, "docker") for _, n := range []string{"forge", "mirror-a"} { if b, code := runService(forgeSpec(n)); code != 0 { t.Fatalf("up %s: %s", n, b) } } b, code := runService(forgeSpec("forge")) if code != 0 { t.Fatalf("re-up: %s", b) } if got := decodeRes(t, b)["tunnel_port"]; got != float64(servicePortBase) { t.Fatalf("a re-up must keep the slot's port, got %v", got) } r2, _ := loadServiceRecord("mirror-a") if r2.TunnelPort != servicePortBase+1 { t.Fatalf("the second slot takes the next port, got %d", r2.TunnelPort) } recs := []serviceRecord{} for p := servicePortBase; p <= servicePortMax; p++ { recs = append(recs, serviceRecord{Name: "s" + strconv.Itoa(p), TunnelPort: p}) } if allocServicePort("new", recs) != 0 { t.Fatal("a full range must report 0, never reuse a port") } } // `down` removes the container and the record and KEEPS the data — it is the // user's repos — unless purge is asked for. func TestServiceDownKeepsDataUnlessPurged(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", t.TempDir()) f := stubServiceRuntime(t, "docker") if b, code := runService(forgeSpec("forge")); code != 0 { t.Fatalf("up: %s", b) } data, _ := serviceDataDir("forge") _ = os.WriteFile(filepath.Join(data, "repo.git"), []byte("x"), 0o600) if b, code := runService(harnessSpec{Mode: "service", ServiceOp: "down", ServiceName: "forge"}); code != 0 { t.Fatalf("down: %s", b) } if f.running["privatae-svc-forge"] { t.Fatal("down must remove the container") } if _, err := loadServiceRecord("forge"); err == nil { t.Fatal("down must remove the record") } if _, err := os.Stat(filepath.Join(data, "repo.git")); err != nil { t.Fatal("down without purge must keep the data") } if b, code := runService(forgeSpec("forge")); code != 0 { t.Fatalf("up again: %s", b) } if b, code := runService(harnessSpec{Mode: "service", ServiceOp: "down", ServiceName: "forge", Purge: true}); code != 0 { t.Fatalf("purge: %s", b) } if _, err := os.Stat(data); !os.IsNotExist(err) { t.Fatal("purge must delete the slot's data") } if b, code := runService(harnessSpec{Mode: "service", ServiceOp: "down", ServiceName: "forge"}); code == 0 { t.Fatalf("down of a slot that does not exist must fail: %s", b) } sp := forgeSpec("forge") sp.Image = "docker:27" if why := validateServiceSpec(sp); why != "" { t.Fatalf("the official docker image by its short name must pass: %s", why) } } // A failed run leaves no record behind, so the forward never listens for a // server that is not there; and no runtime means a clear refusal. func TestServiceUpFailuresLeaveNothing(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", t.TempDir()) f := stubServiceRuntime(t, "docker") f.failOn = "run" if _, code := runService(forgeSpec("forge")); code == 0 { t.Fatal("a failed run must fail the job") } if _, err := loadServiceRecord("forge"); err == nil { t.Fatal("a failed run must not leave a record") } stubServiceRuntime(t, "") b, code := runService(forgeSpec("forge")) if code == 0 || !strings.Contains(string(b), "no container runtime") { t.Fatalf("no runtime must refuse plainly: %s", b) } if b, code := runService(harnessSpec{Mode: "service", ServiceOp: "restart"}); code == 0 { t.Fatalf("an unknown verb must be refused: %s", b) } } // Status reports each slot's port and live state from the runtime. func TestServiceStatusReportsSlots(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", t.TempDir()) f := stubServiceRuntime(t, "docker") if b, code := runService(forgeSpec("forge")); code != 0 { t.Fatalf("up: %s", b) } reps := serviceReports() if len(reps) != 1 || reps[0].State != "running" || reps[0].TunnelPort != servicePortBase { t.Fatalf("status wrong: %+v", reps) } delete(f.running, "privatae-svc-forge") if reps := serviceReports(); reps[0].State != "missing" { t.Fatalf("a vanished container must read missing, got %+v", reps) } up, ok := serviceUpstream("forge") if ok { t.Fatalf("no container, no upstream: %s", up) } f.running["privatae-svc-forge"] = true data, _ := serviceDataDir("forge") if up, ok := serviceUpstream("forge"); ok { t.Fatalf("no socket file yet: nothing to serve, got %q", up) } ln, err := net.Listen("unix", filepath.Join(data, "gitea.sock")) if err != nil { t.Skip("no unix sockets here: " + err.Error()) } defer ln.Close() if up, ok := serviceUpstream("forge"); !ok || up != filepath.Join(data, "gitea.sock") { t.Fatalf("upstream must be the socket in the slot's own data dir, got %q", up) } // A slot container that has ANY network is not served: the forward must never // be the door into a server that can reach the user's machines. for _, mode := range []string{"bridge", "host", "privatae-net", ""} { f.netMode["privatae-svc-forge"] = mode if up, ok := serviceUpstream("forge"); ok { t.Fatalf("network mode %q must not be served, got %q", mode, up) } } } // The family: every service verb and the forward are "services", never // "harness" — a grant for jobs is not a grant for a server that stays. func TestServiceOpsAreTheirOwnFamily(t *testing.T) { for _, op := range []string{"service:up", "service:down", "service:status", "service:anything", serviceCapability} { if fam := opFamily(op); fam != "services" { t.Errorf("%s must be family services, got %q", op, fam) } } if opFamily("session:open") != "session" { t.Fatal("service: must not swallow session:") } } // driveHarness sends one H frame through the real harness lane as pairing // `self` and returns the J body and X code. func driveHarness(t *testing.T, self string, spec harnessSpec) (map[string]any, int) { t.Helper() up := websocket.Upgrader{} done := make(chan struct{}) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ws, err := up.Upgrade(w, r, nil) if err != nil { return } defer ws.Close() handleHarnessConn(ws, self, "") <-done })) defer srv.Close() defer close(done) ws, _, err := websocket.DefaultDialer.Dial("ws"+strings.TrimPrefix(srv.URL, "http"), nil) if err != nil { t.Fatal(err) } defer ws.Close() b, _ := json.Marshal(spec) if err := ws.WriteMessage(websocket.BinaryMessage, append([]byte{'H'}, b...)); err != nil { t.Fatal(err) } var body map[string]any code := -1 _ = ws.SetReadDeadline(time.Now().Add(10 * time.Second)) for code < 0 { _, msg, err := ws.ReadMessage() if err != nil { t.Fatalf("lane closed before X: %v", err) } switch msg[0] { case 'J': _ = json.Unmarshal(msg[1:], &body) case 'X': code, _ = strconv.Atoi(string(msg[1:])) } } return body, code } // On an anchored box, a member pairing granted "harness" is NOT granted a // server: the lane gates a service job on "services". Granted "services", // the same job runs and the record names the member as owner. And a second // member-built slot cannot be stopped by the anchor-less member path of // another host. func TestHarnessLaneGatesServiceJobsOnTheServicesFamily(t *testing.T) { priv := setupBox(t) stubServiceRuntime(t, "docker") pub, _ := deviceInit() _ = pinDeviceKey(pub) approved := func(sp harnessSpec) harnessSpec { sp.Approval = signService(t, sp) return sp } writePairingState(t, tMember, pairingPending) future := time.Now().Add(time.Hour).UTC().Format(time.RFC3339) grant := func(caps []string) { terms, sig := signTerms(t, priv, grantTerms(tMember, caps, future)) if _, errOut, code := driveOp(t, tAnchor, "pairing_grant_sync", []string{terms, sig}); code != 0 { t.Fatalf("grant: %d %s", code, errOut) } } grant([]string{"harness"}) body, code := driveHarness(t, tMember, forgeSpec("forge")) if code != 126 || !strings.Contains(body["error"].(string), "services") { t.Fatalf("harness alone must not build a server: %d %v", code, body) } if _, err := loadServiceRecord("forge"); err == nil { t.Fatal("a refused job must leave no slot") } grant([]string{"services"}) // Holding the family is the ceiling; the write itself still needs the // person's approval on an anchored box. body, code = driveHarness(t, tMember, forgeSpec("forge")) if code != 126 || !strings.Contains(body["error"].(string), "changes this machine") { t.Fatalf("an unapproved up must be refused: %d %v", code, body) } body, code = driveHarness(t, tMember, approved(forgeSpec("forge"))) if code != 0 { t.Fatalf("a services grant must build the server: %d %v", code, body) } rec, _ := loadServiceRecord("forge") if rec == nil || rec.Owner != tMember { t.Fatalf("the owner must be the pairing the lane saw, not anything in the JSON: %+v", rec) } // A host cannot claim a slot by naming an owner: the field is unexported, // so this JSON key is ignored. raw := map[string]any{"mode": "service", "service_op": "down", "service_name": "forge", "owner": tMember} b, _ := json.Marshal(raw) var spec harnessSpec _ = json.Unmarshal(b, &spec) if spec.owner != "" { t.Fatal("owner must not be settable from the host's JSON") } // The ANCHOR may stop a member's slot (its user is the machine's admin). // status is a read: no approval. body, code = driveHarness(t, tMember, harnessSpec{Mode: "service", ServiceOp: "status"}) if code != 0 { t.Fatalf("status needs no approval: %d %v", code, body) } body, code = driveHarness(t, tAnchor, approved(harnessSpec{HarnessID: "d1", Mode: "service", ServiceOp: "down", ServiceName: "forge"})) if code != 0 { t.Fatalf("the anchor must be able to stop a member's server: %d %v", code, body) } } // A member cannot replace or stop a slot another host built. func TestServiceOwnerRuleOnAnAnchoredBox(t *testing.T) { setupBox(t) r := &serviceRecord{Name: "forge", Owner: tAnchor} if why := serviceOwnerRefusal(tMember, r); why == "" { t.Fatal("a member must not change the anchor's server") } if why := serviceOwnerRefusal(tAnchor, &serviceRecord{Name: "forge", Owner: tMember}); why != "" { t.Fatalf("the anchor may change any server: %s", why) } if why := serviceOwnerRefusal(tMember, &serviceRecord{Name: "forge", Owner: tMember}); why != "" { t.Fatalf("the builder may change its own server: %s", why) } // Local box: one host, no owner rule to apply. t.Setenv("XDG_CONFIG_HOME", t.TempDir()) if why := serviceOwnerRefusal(tMember, r); why != "" { t.Fatalf("a local bastion serves one host: %s", why) } } // signService signs the approval a service verb spends, the way the person's // device would: this verb, on this machine, bound to serviceArgs. func signService(t *testing.T, sp harnessSpec) string { t.Helper() hn, _ := os.Hostname() a, err := deviceSign(mgmtOpPurpose(serviceGateOp(sp.ServiceOp), hn, serviceArgs(sp)), time.Minute) if err != nil { t.Fatal(err) } return a } // The approval binds the whole server: an approval for one image, port, data // path or env cannot start another, and purge is part of what was approved. func TestServiceApprovalBindsTheWholeServer(t *testing.T) { setupBox(t) pub, _ := deviceInit() _ = pinDeviceKey(pub) spec := forgeSpec("forge") spec.HarnessID = "s1" spec.Approval = signService(t, spec) if why := harnessGate(tAnchor, "", spec); why != "" { t.Fatalf("a matching up passes: %s", why) } if why := harnessGate(tAnchor, "", spec); why == "" { t.Fatal("an approval is spent once") } for label, mut := range map[string]func(s *harnessSpec){ "image": func(s *harnessSpec) { s.Image = "evil/image:latest" }, "socket": func(s *harnessSpec) { s.Socket = "other.sock" }, "data path": func(s *harnessSpec) { s.DataPath = "/etc" }, "data uid": func(s *harnessSpec) { s.DataUID = 0 }, "env": func(s *harnessSpec) { s.Envs = map[string]string{"X": "y"} }, "name": func(s *harnessSpec) { s.ServiceName = "other" }, "verb": func(s *harnessSpec) { s.ServiceOp = "down" }, } { sp := forgeSpec("forge") sp.HarnessID = "s2" sp.Approval = signService(t, sp) mut(&sp) if why := harnessGate(tAnchor, "", sp); why == "" { t.Errorf("a changed %s is a different server and must be refused", label) } } down := harnessSpec{HarnessID: "d1", Mode: "service", ServiceOp: "down", ServiceName: "forge"} down.Approval = signService(t, down) down.Purge = true if why := harnessGate(tAnchor, "", down); why == "" { t.Fatal("an approval to stop a server is not an approval to delete its data") } status := harnessSpec{Mode: "service", ServiceOp: "status"} if why := harnessGate(tAnchor, "", status); why != "" { t.Fatalf("status is a read: it spends nothing and answers even a pairing whose writes are quarantined: %s", why) } // Every other mode's approval is exactly what it was: harnessArgs does not // grow for this feature. if n := len(harnessArgs(harnessSpec{Mode: "git"})); n != 16 { t.Fatalf("harnessArgs must stay 16 fields for the existing modes, got %d", n) } } // A stop that cannot remove the container fails and keeps the record: a // record dropped while its container runs is a server nobody can see. func TestServiceDownThatCannotStopKeepsTheRecord(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", t.TempDir()) f := stubServiceRuntime(t, "docker") if b, code := runService(forgeSpec("forge")); code != 0 { t.Fatalf("up: %s", b) } f.failOn = "rm" b, code := runService(harnessSpec{Mode: "service", ServiceOp: "down", ServiceName: "forge"}) if code == 0 || !strings.Contains(string(b), "may still be there") { t.Fatalf("a failed stop must say the server is still there: %s", b) } if _, err := loadServiceRecord("forge"); err != nil { t.Fatal("a failed stop must keep the record") } stubServiceRuntime(t, "") b, code = runService(harnessSpec{Mode: "service", ServiceOp: "down", ServiceName: "forge"}) if code == 0 || !strings.Contains(string(b), "nothing was changed") { t.Fatalf("no runtime: the stop cannot be confirmed and must change nothing: %s", b) } if _, err := loadServiceRecord("forge"); err != nil { t.Fatal("no runtime must keep the record") } } // A purge that cannot delete everything says so instead of reporting purged. func TestServicePurgeFailureIsReported(t *testing.T) { if os.Geteuid() == 0 { // Root (including the test namespace's mapped root) can remove a // read-only directory's entries, so the failure cannot be staged. t.Skip("needs a non-root uid to make a directory undeletable") } t.Setenv("XDG_CONFIG_HOME", t.TempDir()) stubServiceRuntime(t, "docker") if b, code := runService(forgeSpec("forge")); code != 0 { t.Fatalf("up: %s", b) } data, _ := serviceDataDir("forge") sub := filepath.Join(data, "locked") _ = os.MkdirAll(sub, 0o700) _ = os.WriteFile(filepath.Join(sub, "f"), []byte("x"), 0o600) _ = os.Chmod(sub, 0o500) t.Cleanup(func() { _ = os.Chmod(sub, 0o700) }) b, code := runService(harnessSpec{Mode: "service", ServiceOp: "down", ServiceName: "forge", Purge: true}) if code == 0 || decodeRes(t, b)["purged"] != false { t.Fatalf("a partial purge must not report purged: %s", b) } } // Parallel ups never share a tunnel port. func TestConcurrentServiceUpsTakeDistinctPorts(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", t.TempDir()) stubServiceRuntime(t, "docker") var wg sync.WaitGroup for i := 0; i < 8; i++ { wg.Add(1) go func(i int) { defer wg.Done() if b, code := runService(forgeSpec("s" + strconv.Itoa(i))); code != 0 { t.Errorf("up %d: %s", i, b) } }(i) } wg.Wait() seen := map[int]string{} for _, r := range loadServiceRecords() { if other, dup := seen[r.TunnelPort]; dup { t.Fatalf("%s and %s share port %d", r.Name, other, r.TunnelPort) } seen[r.TunnelPort] = r.Name } if len(seen) != 8 { t.Fatalf("want 8 slots, got %d", len(seen)) } } // A frame that does not parse, from a pairing outside every family, gets the // caps refusal — never "bad harness spec" — so it learns nothing about the lane. func TestMalformedServiceFrameIsGatedAsHarnessFirst(t *testing.T) { priv := setupBox(t) writePairingState(t, tMember, pairingPending) future := time.Now().Add(time.Hour).UTC().Format(time.RFC3339) terms, sig := signTerms(t, priv, grantTerms(tMember, []string{"observe"}, future)) if _, errOut, code := driveOp(t, tAnchor, "pairing_grant_sync", []string{terms, sig}); code != 0 { t.Fatalf("grant: %d %s", code, errOut) } up := websocket.Upgrader{} srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ws, err := up.Upgrade(w, r, nil) if err != nil { return } defer ws.Close() handleHarnessConn(ws, tMember, "") })) defer srv.Close() ws, _, err := websocket.DefaultDialer.Dial("ws"+strings.TrimPrefix(srv.URL, "http"), nil) if err != nil { t.Fatal(err) } defer ws.Close() _ = ws.WriteMessage(websocket.BinaryMessage, []byte(`H{"mode":"service","service_op":`)) _ = ws.SetReadDeadline(time.Now().Add(5 * time.Second)) _, msg, err := ws.ReadMessage() if err != nil || msg[0] != 'J' { t.Fatalf("want a J frame: %v %q", err, msg) } if strings.Contains(string(msg), "bad harness spec") || !strings.Contains(string(msg), "caps") { t.Fatalf("an unauthorized malformed frame must get the caps refusal: %s", msg) } } // The pod and the box must derive the SAME purpose for a service verb, or // every approval is refused. This vector is pinned on both sides: the pod's // scripts/test_bastion_service_slot.py computes the same digest from // nac_harness.service_approval_args. func TestServiceArgsMatchThePodVector(t *testing.T) { sp := harnessSpec{HarnessID: "s1", Mode: "service", ServiceOp: "up", ServiceName: "forge", Image: "gitea/gitea:1.26.2-rootless", Socket: "gitea.sock", DataPath: "/var/lib/gitea", DataUID: 1000, Envs: map[string]string{"GITEA__server__DISABLE_SSH": "true", "B": "2"}} const want = "mgmt_op|service:up|box1|5cdcf7d1a0ff93ec85ed757e96c56aa80cf45f05018a56d52b07b6915d2318a3" if got := mgmtOpPurpose("service:up", "box1", serviceArgs(sp)); got != want { t.Fatalf("purpose drifted from the pod's:\n got %s\n want %s", got, want) } } // A stop whose rm fails while the daemon cannot say whether the container // exists keeps the record: an unknown is not an absence. func TestServiceDownWithAnUnansweringDaemonKeepsTheRecord(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", t.TempDir()) f := stubServiceRuntime(t, "docker") if b, code := runService(forgeSpec("forge")); code != 0 { t.Fatalf("up: %s", b) } f.failOn = "rm" f.mu.Lock() f.running["privatae-svc-forge"] = true f.mu.Unlock() // rm fails AND inspect cannot reach the daemon. serviceRunInner := serviceRun serviceRun = func(d time.Duration, name string, args ...string) (string, error) { if len(args) > 0 && args[0] == "inspect" { return "Cannot connect to the Docker daemon", os.ErrDeadlineExceeded } return serviceRunInner(d, name, args...) } b, code := runService(harnessSpec{Mode: "service", ServiceOp: "down", ServiceName: "forge"}) if code == 0 { t.Fatalf("an unanswering daemon must not read as a stopped server: %s", b) } if _, err := loadServiceRecord("forge"); err != nil { t.Fatal("the record must survive an unknown") } // And rm failing on a container the runtime says is gone lets it go. serviceRun = serviceRunInner f.mu.Lock() delete(f.running, "privatae-svc-forge") f.mu.Unlock() if b, code := runService(harnessSpec{Mode: "service", ServiceOp: "down", ServiceName: "forge"}); code != 0 { t.Fatalf("a container the runtime says is gone may be forgotten: %s", b) } } // An invalid service request is refused before the lane spends an approval. func TestInvalidServiceRequestSpendsNoApproval(t *testing.T) { priv := setupBox(t) stubServiceRuntime(t, "docker") pub, _ := deviceInit() _ = pinDeviceKey(pub) writePairingState(t, tMember, pairingPending) future := time.Now().Add(time.Hour).UTC().Format(time.RFC3339) terms, sig := signTerms(t, priv, grantTerms(tMember, []string{"services"}, future)) if _, errOut, code := driveOp(t, tAnchor, "pairing_grant_sync", []string{terms, sig}); code != 0 { t.Fatalf("grant: %d %s", code, errOut) } bad := forgeSpec("forge") bad.HarnessID = "b1" bad.DataPath = "relative" bad.Approval = signService(t, bad) body, code := driveHarness(t, tMember, bad) if code != 1 || !strings.Contains(body["error"].(string), "data_path") { t.Fatalf("an invalid up must be refused on its own terms: %d %v", code, body) } // The same approval was NOT spent: fixing nothing about the approval, the // gate still accepts it for the spec it was signed over. if why := harnessGate(tMember, "", bad); why != "" { t.Fatalf("the refused request must not have spent its approval: %s", why) } } // The container owns its data dir: a link at the socket's name, pointing at // some other socket on the machine, is never followed. func TestServiceUpstreamRefusesALinkInPlaceOfTheSocket(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", t.TempDir()) f := stubServiceRuntime(t, "docker") if b, code := runService(forgeSpec("forge")); code != 0 { t.Fatalf("up: %s", b) } f.running["privatae-svc-forge"] = true other := filepath.Join(t.TempDir(), "runtime.sock") ln, err := net.Listen("unix", other) if err != nil { t.Skip("no unix sockets here: " + err.Error()) } defer ln.Close() data, _ := serviceDataDir("forge") if err := os.Symlink(other, filepath.Join(data, "gitea.sock")); err != nil { t.Fatal(err) } if up, ok := serviceUpstream("forge"); ok { t.Fatalf("a symlink at the socket name must not be served: %q", up) } _ = os.Remove(filepath.Join(data, "gitea.sock")) _ = os.WriteFile(filepath.Join(data, "gitea.sock"), []byte("x"), 0o600) if up, ok := serviceUpstream("forge"); ok { t.Fatalf("a plain file at the socket name must not be served: %q", up) } } // The far end must be the server's own uid: a socket answered by anyone else // (root included) is closed without carrying a byte. func TestSpliceUnixRefusesAPeerThatIsNotTheServer(t *testing.T) { sock := filepath.Join(t.TempDir(), "s.sock") ln, err := net.Listen("unix", sock) if err != nil { t.Skip("no unix sockets here: " + err.Error()) } defer ln.Close() go func() { for { c, err := ln.Accept() if err != nil { return } _, _ = c.Write([]byte("SECRET")) _ = c.Close() } }() for _, want := range []int{0, os.Getuid() + 1} { a, b := net.Pipe() go spliceUnix(b, sock, want) _ = a.SetDeadline(time.Now().Add(3 * time.Second)) buf := make([]byte, 6) n, _ := io.ReadFull(a, buf) if n > 0 { t.Fatalf("wantUID %d: a peer that is not the server must get nothing through, got %q", want, buf[:n]) } _ = a.Close() } } // The forward's last hop: a tunnel connection spliced to the slot's unix socket, // both ways, when the peer IS the server's uid. Needs a non-root uid: root is 0 // and 0 is always refused, so under the test namespace's mapped root this skips // and the positive case is run as an unprivileged user instead. func TestSpliceUnixCarriesBothWays(t *testing.T) { if os.Getuid() == 0 { t.Skip("the server uid must be non-root; run as an unprivileged user") } sock := filepath.Join(t.TempDir(), "s.sock") ln, err := net.Listen("unix", sock) if err != nil { t.Skip("no unix sockets here: " + err.Error()) } defer ln.Close() go func() { c, err := ln.Accept() if err != nil { return } defer c.Close() buf := make([]byte, 5) if _, err := io.ReadFull(c, buf); err == nil { _, _ = c.Write(append([]byte("echo:"), buf...)) } }() a, b := net.Pipe() go spliceUnix(b, sock, os.Getuid()) _ = a.SetDeadline(time.Now().Add(5 * time.Second)) if _, err := a.Write([]byte("hello")); err != nil { t.Fatal(err) } got := make([]byte, 10) if _, err := io.ReadFull(a, got); err != nil || string(got) != "echo:hello" { t.Fatalf("splice: %q %v", got, err) } _ = a.Close() }