Read the tunnel through sudo where the tool runner is not root (hq issue 522)
mesh/merge-gate pass: builds netcheck → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery delivered
mesh/merge-gate pass: builds netcheck → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery delivered
On novox the tool runner runs as the operator's account, and wg show answered 'Operation not permitted'. Root is the module's concern: the command runs as given when the process is root, else through sudo -n, which the sudo module allows the runtime's account, as nftables does.
This commit is contained in:
@@ -16,6 +16,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sort"
|
||||
"strconv"
|
||||
@@ -118,9 +119,13 @@ var names sync.Map
|
||||
|
||||
// readTunnel asks wg for the interface's state and names each peer by the mesh's names.
|
||||
func readTunnel(ctx context.Context) (Tunnel, error) {
|
||||
out, err := exec.CommandContext(ctx, "wg", "show", TunnelInterface, "dump").Output()
|
||||
program, argv := escalated("wg", []string{"show", TunnelInterface, "dump"}, os.Geteuid())
|
||||
out, err := exec.CommandContext(ctx, program, argv...).Output()
|
||||
if err != nil {
|
||||
var ee *exec.ExitError
|
||||
if program == "sudo" && errors.As(err, &ee) && strings.HasPrefix(strings.TrimSpace(string(ee.Stderr)), "sudo:") {
|
||||
return Tunnel{}, fmt.Errorf("wg needs root, and sudo refused the runtime's account: %s", strings.TrimSpace(string(ee.Stderr)))
|
||||
}
|
||||
if errors.As(err, &ee) && strings.Contains(string(ee.Stderr), "No such device") {
|
||||
return Tunnel{}, errNoTunnel
|
||||
}
|
||||
@@ -147,6 +152,16 @@ func readTunnel(ctx context.Context) (Tunnel, error) {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// escalated is the command as it is run: as given when this process is root, else through sudo without a
|
||||
// prompt, which the mesh's sudo module allows the runtime's account (ADR 0175 §4: root is the module's
|
||||
// concern; the nftables module reads its rules the same way). The tunnel answers only to root.
|
||||
func escalated(cmd string, args []string, euid int) (string, []string) {
|
||||
if euid == 0 {
|
||||
return cmd, args
|
||||
}
|
||||
return "sudo", append([]string{"-n", cmd}, args...)
|
||||
}
|
||||
|
||||
// nameOf is the mesh's name for a peer's own address (a single-address allowed ip), or "".
|
||||
func nameOf(ctx context.Context, allowed []string) string {
|
||||
for _, a := range allowed {
|
||||
|
||||
@@ -79,3 +79,12 @@ func asString(v any) string {
|
||||
s, _ := v.(string)
|
||||
return s
|
||||
}
|
||||
|
||||
func TestTheTunnelIsReadAsRootOrThroughSudo(t *testing.T) {
|
||||
if p, a := escalated("wg", []string{"show", "mesh0", "dump"}, 0); p != "wg" || strings.Join(a, " ") != "show mesh0 dump" {
|
||||
t.Errorf("as root: %s %v", p, a)
|
||||
}
|
||||
if p, a := escalated("wg", []string{"show", "mesh0", "dump"}, 1000); p != "sudo" || strings.Join(a, " ") != "-n wg show mesh0 dump" {
|
||||
t.Errorf("as an account: %s %v", p, a)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user