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
75 lines
2.1 KiB
Go
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
|
|
}
|