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 }