Serve a module from a runtime on its own account instead of switching users, keep bus words from bundles, and never give up a channel's work (hq ADR 0259 revision)
mesh/merge-gate pass: builds mesh-tools, node-tools → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of …
mesh/repo-check pass: THE CHANGE ALTERS ITS OWN CHECK (merge-check.sh): main's version judged it; the change's judges the pull requests after it merges; it…
mesh/delivery delivered
mesh/delivery-group group feat/asks-answered-on-any-channel stopped: a member was stopped

This commit is contained in:
jochen
2026-10-09 10:12:39 +02:00
committed by jschoubben
parent c6087129ce
commit 127047edd6
8 changed files with 364 additions and 159 deletions
+15 -7
View File
@@ -181,12 +181,6 @@ func Start(module, entry string, env []string, mesh Bus, logf func(string, ...an
start := func() (*child, error) {
cmd := exec.Command(entry)
cmd.Env = env
// **A module that names an account of its own runs as it** (novox/hq ADR 0259 §8): its secrets and
// state are that account's, and the operator's account — which every agent runs as — reaches
// neither. Never root, never the operator's.
if err := runAs(cmd, env); err != nil {
return nil, err
}
stdin, err := cmd.StdinPipe()
if err != nil {
return nil, err
@@ -310,7 +304,7 @@ func Start(module, entry string, env []string, mesh Bus, logf func(string, ...an
}
if err != nil {
_ = c.write(map[string]any{"jsonrpc": "2.0", "id": m.ID,
"error": map[string]any{"code": -32000, "message": err.Error()}})
"error": map[string]any{"code": errorCode(err), "message": err.Error()}})
return
}
_ = c.write(map[string]any{"jsonrpc": "2.0", "id": m.ID, "result": result})
@@ -599,3 +593,17 @@ func (c *child) deathReason() error {
}
return errors.New("the bundle exited")
}
// CodeChanged is the error code a bundle is answered a lost compare-and-set with (mesh-sdk's
// stdio.CodeChanged): told by its code, never by its words (novox/hq ADR 0259).
const CodeChanged = -32010
// errorCode is the code an error is answered to a bundle with: a lost compare-and-set's own, else the
// general one.
func errorCode(err error) int {
var coded interface{ ErrorCode() int }
if errors.As(err, &coded) {
return coded.ErrorCode()
}
return -32000
}
-68
View File
@@ -1,68 +0,0 @@
package launch
import (
"fmt"
"os/exec"
"os/user"
"strconv"
"strings"
"syscall"
)
// RunAs is the word in a bundle's environment naming the account it runs as (novox/hq ADR 0259 §8), and
// OperatorAccount the word naming the operator's account on this machine, which the runtime is given.
const (
RunAs = "MESH_RUN_AS"
OperatorAccount = "MESH_OPERATOR_ACCOUNT"
)
// lookupUser is user.Lookup, a variable so a test can name accounts this machine does not have.
var lookupUser = user.Lookup
func word(env []string, name string) string {
for _, kv := range env {
if k, v, ok := strings.Cut(kv, "="); ok && k == name {
return v
}
}
return ""
}
// runAs starts cmd as the account its environment names, with that account's home, or leaves it as the
// runtime's own when none is named. It refuses root and the operator's account: a module asks for an
// account of its own to keep what it holds from the agents, and every agent runs as the operator.
func runAs(cmd *exec.Cmd, env []string) error {
name := word(env, RunAs)
if name == "" {
return nil
}
if operator := word(env, OperatorAccount); operator != "" && name == operator {
return fmt.Errorf("%s names the operator's account %s; a module runs as an account of its own, never "+
"the one every agent runs as (novox/hq ADR 0259)", RunAs, name)
}
u, err := lookupUser(name)
if err != nil {
return fmt.Errorf("%s names the account %s, which this machine does not have: the module makes it with "+
"a user resource (novox/hq ADR 0259): %w", RunAs, name, err)
}
uid, err := strconv.ParseUint(u.Uid, 10, 32)
if err != nil {
return fmt.Errorf("the account %s has no usable uid %q", name, u.Uid)
}
gid, err := strconv.ParseUint(u.Gid, 10, 32)
if err != nil {
return fmt.Errorf("the account %s has no usable gid %q", name, u.Gid)
}
if uid == 0 || name == "root" {
return fmt.Errorf("%s names root; a module that asks for an account of its own is not given root", RunAs)
}
cmd.SysProcAttr = &syscall.SysProcAttr{Credential: &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}}
var kept []string
for _, kv := range cmd.Env {
if !strings.HasPrefix(kv, "HOME=") && !strings.HasPrefix(kv, "USER=") && !strings.HasPrefix(kv, "LOGNAME=") {
kept = append(kept, kv)
}
}
cmd.Env = append(kept, "HOME="+u.HomeDir, "USER="+name, "LOGNAME="+name)
return nil
}
-48
View File
@@ -1,48 +0,0 @@
package launch
import (
"errors"
"os/exec"
"os/user"
"strings"
"testing"
)
// novox/hq ADR 0259 §8: a module naming an account of its own runs as it, never as root or the operator.
func TestABundleRunsAsTheAccountItNamesAndNeverRootOrTheOperator(t *testing.T) {
was := lookupUser
t.Cleanup(func() { lookupUser = was })
lookupUser = func(name string) (*user.User, error) {
switch name {
case "telegram":
return &user.User{Username: "telegram", Uid: "961", Gid: "961", HomeDir: "/var/lib/telegram"}, nil
case "root":
return &user.User{Username: "root", Uid: "0", Gid: "0", HomeDir: "/root"}, nil
case "toor":
return &user.User{Username: "toor", Uid: "0", Gid: "0", HomeDir: "/root"}, nil
}
return nil, errors.New("unknown user")
}
cmd := exec.Command("/bin/true")
cmd.Env = []string{"HOME=/root", "PATH=/usr/bin"}
if err := runAs(cmd, []string{RunAs + "=telegram", OperatorAccount + "=jo"}); err != nil {
t.Fatal(err)
}
if c := cmd.SysProcAttr.Credential; c == nil || c.Uid != 961 || c.Gid != 961 {
t.Fatalf("not started as telegram: %+v", cmd.SysProcAttr)
}
if env := strings.Join(cmd.Env, " "); !strings.Contains(env, "HOME=/var/lib/telegram") || strings.Contains(env, "HOME=/root") {
t.Fatalf("the account's home is not its own: %s", env)
}
for name, want := range map[string]string{"jo": "operator's account", "root": "names root", "toor": "names root",
"nobody-here": "does not have"} {
err := runAs(exec.Command("/bin/true"), []string{RunAs + "=" + name, OperatorAccount + "=jo"})
if err == nil || !strings.Contains(err.Error(), want) {
t.Errorf("%s: %v, want a refusal saying %q", name, err, want)
}
}
plain := exec.Command("/bin/true")
if err := runAs(plain, nil); err != nil || plain.SysProcAttr != nil {
t.Error("a bundle naming no account was changed")
}
}