Fix MTR failing under systemd: grant CAP_NET_RAW via ambient capabilities
mtr ran fine as the mtr-backend user in a shell but failed under the systemd service with: mtr-packet: Failure to open IPv4 sockets: Permission denied Cause: the unit sets NoNewPrivileges=yes, which strips file capabilities, and AmbientCapabilities/CapabilityBoundingSet were empty — so mtr-packet (the helper that opens the raw ICMP socket) had no CAP_NET_RAW. Interactive runs worked because they had no NoNewPrivileges. - Grant CAP_NET_RAW the systemd-native way (AmbientCapabilities + CapabilityBoundingSet), which survives NoNewPrivileges. Fixed in both the full installer and the patch service unit. - setcap now also targets mtr-packet, not just mtr. - Add proxy_intercept_errors off to the diag /api/mtr location: the server-level "proxy_intercept_errors on" was rewriting a backend 500 into an HTML 404, which broke the frontend's response.json() and left the client IP stuck on "detecting…". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
fb68047c02
commit
906822b42b
+13
-3
@@ -575,6 +575,10 @@ server {
|
|||||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||||
proxy_read_timeout 120s;
|
proxy_read_timeout 120s;
|
||||||
proxy_send_timeout 120s;
|
proxy_send_timeout 120s;
|
||||||
|
# Let the backend's JSON error bodies through; the server-level
|
||||||
|
# "proxy_intercept_errors on" would otherwise rewrite a 500 into an HTML
|
||||||
|
# 404 and break the frontend's response.json() parse.
|
||||||
|
proxy_intercept_errors off;
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── LibreSpeed upload sink ───────────────────────────────────────────────
|
# ── LibreSpeed upload sink ───────────────────────────────────────────────
|
||||||
@@ -1068,7 +1072,9 @@ install_diagnostics() {
|
|||||||
chmod 755 "${backend_script}"
|
chmod 755 "${backend_script}"
|
||||||
|
|
||||||
# Grant mtr raw socket capability (runs as restricted user, no root needed)
|
# Grant mtr raw socket capability (runs as restricted user, no root needed)
|
||||||
command -v setcap &>/dev/null && setcap cap_net_raw+ep "$(command -v mtr)" 2>/dev/null || true
|
# mtr-packet is the helper that actually opens the raw socket
|
||||||
|
command -v setcap &>/dev/null && setcap cap_net_raw+ep "$(command -v mtr)" 2>/dev/null || true
|
||||||
|
command -v setcap &>/dev/null && setcap cap_net_raw+ep "$(command -v mtr-packet)" 2>/dev/null || true
|
||||||
|
|
||||||
# Dedicated system user for mtr-backend
|
# Dedicated system user for mtr-backend
|
||||||
id mtr-backend &>/dev/null || \
|
id mtr-backend &>/dev/null || \
|
||||||
@@ -1101,8 +1107,12 @@ MemoryDenyWriteExecute=yes
|
|||||||
RestrictRealtime=yes
|
RestrictRealtime=yes
|
||||||
RestrictSUIDSGID=yes
|
RestrictSUIDSGID=yes
|
||||||
RemoveIPC=yes
|
RemoveIPC=yes
|
||||||
AmbientCapabilities=
|
# mtr-packet opens raw ICMP sockets. NoNewPrivileges=yes strips the file
|
||||||
CapabilityBoundingSet=
|
# capability off the mtr binary, so grant CAP_NET_RAW the systemd-native way
|
||||||
|
# (ambient caps survive NoNewPrivileges). Empty here = mtr fails with
|
||||||
|
# "Failure to open IPv4 sockets: Permission denied".
|
||||||
|
AmbientCapabilities=CAP_NET_RAW
|
||||||
|
CapabilityBoundingSet=CAP_NET_RAW
|
||||||
StandardOutput=journal
|
StandardOutput=journal
|
||||||
StandardError=journal
|
StandardError=journal
|
||||||
SyslogIdentifier=mtr-backend
|
SyslogIdentifier=mtr-backend
|
||||||
|
|||||||
+13
-1
@@ -488,6 +488,10 @@ server {
|
|||||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||||
proxy_read_timeout 120s;
|
proxy_read_timeout 120s;
|
||||||
proxy_send_timeout 120s;
|
proxy_send_timeout 120s;
|
||||||
|
# Let the backend's JSON error bodies through; the server-level
|
||||||
|
# "proxy_intercept_errors on" would otherwise rewrite a 500 into an HTML
|
||||||
|
# 404 and break the frontend's response.json() parse.
|
||||||
|
proxy_intercept_errors off;
|
||||||
}
|
}
|
||||||
location ^~ ${diag_path}api/st/up {
|
location ^~ ${diag_path}api/st/up {
|
||||||
if (\$diag_auth = 0) { return 404; }
|
if (\$diag_auth = 0) { return 404; }
|
||||||
@@ -614,7 +618,9 @@ id mtr-backend &>/dev/null || \
|
|||||||
useradd --system --no-create-home --shell /usr/sbin/nologin mtr-backend
|
useradd --system --no-create-home --shell /usr/sbin/nologin mtr-backend
|
||||||
|
|
||||||
if command -v setcap &>/dev/null; then
|
if command -v setcap &>/dev/null; then
|
||||||
setcap cap_net_raw+ep "$(command -v mtr)" 2>/dev/null || true
|
# mtr-packet is the helper that actually opens the raw socket
|
||||||
|
setcap cap_net_raw+ep "$(command -v mtr)" 2>/dev/null || true
|
||||||
|
setcap cap_net_raw+ep "$(command -v mtr-packet)" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cat > /etc/systemd/system/mtr-backend.service <<EOF
|
cat > /etc/systemd/system/mtr-backend.service <<EOF
|
||||||
@@ -633,6 +639,12 @@ NoNewPrivileges=yes
|
|||||||
PrivateTmp=yes
|
PrivateTmp=yes
|
||||||
ProtectSystem=strict
|
ProtectSystem=strict
|
||||||
ReadWritePaths=/tmp
|
ReadWritePaths=/tmp
|
||||||
|
# mtr-packet opens raw ICMP sockets. NoNewPrivileges=yes strips the file
|
||||||
|
# capability off the mtr binary, so grant CAP_NET_RAW the systemd-native way
|
||||||
|
# (ambient caps survive NoNewPrivileges). Without this mtr fails with
|
||||||
|
# "Failure to open IPv4 sockets: Permission denied".
|
||||||
|
AmbientCapabilities=CAP_NET_RAW
|
||||||
|
CapabilityBoundingSet=CAP_NET_RAW
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|||||||
Reference in New Issue
Block a user