Merge pull request 'Read the tunnel through sudo where the tool runner is not root (hq issue 522)' (#226) from fix/522-tunnel-escalates into main

This commit was merged in pull request #226.
This commit is contained in:
2026-10-11 22:07:54 +00:00
2 changed files with 25 additions and 1 deletions
+16 -1
View File
@@ -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)
}
}