From 5a6871813fe56ad6487ce8d7bdb007144f80be66 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Sat, 11 Jul 2026 18:15:22 +0100 Subject: [PATCH] Grant CAP_NET_RAW to the installed systemd unit for ICMP probes The installer's unit hardens with NoNewPrivileges=true, which strips the ping binary's setuid/file capabilities, so ICMP availability probes could never work on a systemd install. Grant the capability ambiently instead, document the systemctl edit override for existing units, and pin the hardening block with an install test and contract clause. Addresses #1554 (discussion) --- docs/CONFIGURATION.md | 29 +++++++++++++++++++ .../subsystems/deployment-installability.md | 11 +++++++ install.sh | 4 +++ scripts/installtests/root_install_sh_test.go | 29 +++++++++++++++++++ 4 files changed, 73 insertions(+) diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 02ba50d0c..a7b4d822c 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -455,6 +455,35 @@ Example API payload for simple ping monitoring: Pulse stores and returns that target as `protocol: "icmp"` so dashboards, alerts, and resource projections keep one canonical protocol value. +### ICMP probe privileges + +ICMP probes run the system `ping` binary, which needs the `CAP_NET_RAW` +capability. The systemd unit written by the installer hardens the service +with `NoNewPrivileges=true`, which strips ping's setuid bit and file +capabilities, so the unit also grants the capability directly with +`AmbientCapabilities=CAP_NET_RAW`. Units written by older versions of the +installer lack that line, and ICMP probes fail with +`icmp probe failed: ping: socktype: SOCK_RAW ... missing cap_net_raw+p capability`. + +To fix an existing install, either re-run the install script (it rewrites +the unit) or add the capability as an override: + +```bash +systemctl edit pulse # pulse-backend on ProxmoxVE community-script installs +``` + +```ini +[Service] +AmbientCapabilities=CAP_NET_RAW +``` + +Then `systemctl daemon-reload && systemctl restart pulse`. + +Docker installs are unaffected: Docker's default capability set includes +`NET_RAW`. If you run the container with `--cap-drop=ALL`, add +`--cap-add=NET_RAW` to keep ICMP probes working. TCP and HTTP/HTTPS probes +need no special privileges. + --- ## 🔒 HTTPS / TLS diff --git a/docs/release-control/v6/internal/subsystems/deployment-installability.md b/docs/release-control/v6/internal/subsystems/deployment-installability.md index 87183ca41..34a9e45fd 100644 --- a/docs/release-control/v6/internal/subsystems/deployment-installability.md +++ b/docs/release-control/v6/internal/subsystems/deployment-installability.md @@ -407,6 +407,17 @@ TLS floor in the dynamic config. signature verification depends on `ssh-keygen` from `openssh-client` and must not fail on a minimal supported host solely because that package was absent before installation started. + The server systemd unit that root `install.sh` writes + (`install_systemd_service`) hardens with `NoNewPrivileges=true`, which + strips setuid and file capabilities from every child the server executes. + ICMP availability probes exec the system `ping` binary, so the same + hardening block must also grant `AmbientCapabilities=CAP_NET_RAW` with a + matching `CapabilityBoundingSet=CAP_NET_RAW`; dropping either regresses + ICMP availability checks to permanent failure on every systemd install + (discussion #1554). `scripts/installtests/root_install_sh_test.go` + (`TestRootInstallServiceGrantsIcmpProbeCapability`) pins the pairing, and + `docs/CONFIGURATION.md` documents the `systemctl edit` override for units + written before the grant existed. The top-level `install.sh` asset published on GitHub Releases must be the root Pulse SERVER installer (the LXC / systemd / Proxmox VE installer that accepts `--version vX.Y.Z`, `--rc`, `--stable`, and friends). The rendered diff --git a/install.sh b/install.sh index 607840c4c..6c8dbe932 100755 --- a/install.sh +++ b/install.sh @@ -4217,6 +4217,10 @@ PrivateTmp=true ProtectSystem=strict ProtectHome=true ReadWritePaths=$INSTALL_DIR $CONFIG_DIR +# ICMP availability probes exec the system ping binary; NoNewPrivileges strips +# its setuid/file capabilities, so grant CAP_NET_RAW ambiently instead (#1554). +AmbientCapabilities=CAP_NET_RAW +CapabilityBoundingSet=CAP_NET_RAW [Install] WantedBy=multi-user.target diff --git a/scripts/installtests/root_install_sh_test.go b/scripts/installtests/root_install_sh_test.go index 83fb199b3..299401085 100644 --- a/scripts/installtests/root_install_sh_test.go +++ b/scripts/installtests/root_install_sh_test.go @@ -1279,3 +1279,32 @@ func TestRootInstallScriptSourceGuardSurvivesPipedExecution(t *testing.T) { t.Fatalf("source guard did not fall through to the installer body when piped:\n%s", got) } } + +func TestRootInstallServiceGrantsIcmpProbeCapability(t *testing.T) { + content, err := os.ReadFile(filepath.Join("..", "..", "install.sh")) + if err != nil { + t.Fatalf("read root install.sh: %v", err) + } + + script := string(content) + required := []string{ + `NoNewPrivileges=true`, + `AmbientCapabilities=CAP_NET_RAW`, + `CapabilityBoundingSet=CAP_NET_RAW`, + } + for _, needle := range required { + if !strings.Contains(script, needle) { + t.Fatalf("install.sh missing systemd ICMP capability grant: %s", needle) + } + } + + // The capability grant must live in the same hardening block that sets + // NoNewPrivileges, so every unit the installer writes gets both. + hardening := script[strings.Index(script, "# Security hardening"):] + if end := strings.Index(hardening, "[Install]"); end >= 0 { + hardening = hardening[:end] + } + if !strings.Contains(hardening, "AmbientCapabilities=CAP_NET_RAW") { + t.Fatal("AmbientCapabilities=CAP_NET_RAW is not in the unit's security hardening block") + } +}