mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
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)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user