Files
mesh-catalog/modules/mesh-files/cmd/mesh-files/sudo.go
T
jschoubben 5d98bcc0db
mesh/delivery-group group feat/900-node-files checking: composed check out of date: novox/mesh-controller@210f7cf5e64b was judged against trunk 611e2275cf7c, t…
mesh/merge-gate out of date: judged against trunk 3db85a0dde83, the trunk is now 8b53a3663244; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk 3db85a0dde83, the trunk is now 8b53a3663244; judged again on a recheck or when it is merged
mesh/delivery superseded: a newer head of the same pull request
mesh-files: the holder of node-files, reads and writes with trash, owned paths and an act log, so file work on a node needs no shell (issue 900, ADR 0394)
2026-10-16 14:50:03 +02:00

75 lines
2.1 KiB
Go

package main
import (
"bytes"
"context"
"fmt"
"io"
"os/exec"
"sort"
"strings"
"time"
)
// Root repeats an act with more rights than the account this runs as has (novox/hq ADR 0394 §2). It is an
// interface so a unit test uses a fake: no test runs sudo.
type Root interface {
// Run runs one fixed binary with the arguments given, never through a shell, and answers what it wrote.
Run(bin string, args ...string) ([]byte, error)
}
// rootBins are the only binaries that run as root. The argument list is the caller's, but each caller in this
// module builds it from fixed words and one path, and ends its options with `--`.
var rootBins = map[string]bool{
"/usr/bin/cat": true, "/usr/bin/cp": true, "/usr/bin/mv": true, "/usr/bin/stat": true, "/usr/bin/chmod": true,
"/usr/bin/chown": true, "/usr/bin/mkdir": true, "/usr/bin/rm": true, "/usr/bin/readlink": true, "/usr/bin/find": true,
}
func rootBinList() string {
var l []string
for b := range rootBins {
l = append(l, b)
}
sort.Strings(l)
return strings.Join(l, ", ")
}
// sudoRoot is the real runner: `sudo -n` (never a password prompt) with a binary from rootBins.
type sudoRoot struct{}
// capped keeps at most n bytes and drops the rest.
type capped struct {
buf bytes.Buffer
n int
}
func (c *capped) Write(p []byte) (int, error) {
if room := c.n - c.buf.Len(); room > 0 {
if len(p) < room {
room = len(p)
}
c.buf.Write(p[:room])
}
return len(p), nil
}
func (sudoRoot) Run(bin string, args ...string) ([]byte, error) {
if !rootBins[bin] {
return nil, fmt.Errorf("%s is not one of the binaries that run as root here (%s)", bin, rootBinList())
}
ctx, cancel := context.WithTimeout(context.Background(), SearchTime+10*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "/usr/bin/sudo", append([]string{"-n", "--", bin}, args...)...)
out, errs := &capped{n: MaxScan}, &capped{n: 4096}
cmd.Stdout, cmd.Stderr = out, errs
cmd.Stdin = io.LimitReader(strings.NewReader(""), 0)
if err := cmd.Run(); err != nil {
msg := strings.TrimSpace(errs.buf.String())
if msg == "" {
msg = err.Error()
}
return nil, fmt.Errorf("sudo -n %s: %s", bin, msg)
}
return out.buf.Bytes(), nil
}