mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
Keep Docker-in-LXC working when commands are enabled after install
lxc-attach into an unprivileged guest writes /proc/<pid>/uid_map, which needs CAP_SETUID in the parent user namespace. NoNewPrivileges drops CAP_SETUID from the effective set and also stops lxc-attach falling back to the setuid newuidmap/newgidmap helpers, so the socket probe dies with "write_id_mapping: 61 Operation not permitted". install.sh already relaxed NoNewPrivileges for this, but only when the agent was installed with --enable-commands. Command execution is also togglable from the server afterwards: applyRemoteConfig starts the command client without rewriting the unit. An agent installed without the flag and switched on later therefore ends up able to run commands and unable to attach to unprivileged guests, so Docker inside every unprivileged LXC disappears from the Proxmox page. The probe failure is logged at debug level and retried on every poll, so the surface looks empty rather than broken while the agent re-probes the whole guest list. Grant CAP_SETUID/CAP_SETGID to any PVE agent rather than gating on the install-time flag, so the later toggle lands on a unit that can attach. Ambient capabilities restore exactly the privilege lxc-attach needs and leave the rest of the sandbox intact; the existing install-time relaxation is unchanged. Verified on a live PVE node. With the hardened unit the probe succeeded only on the three privileged guests and failed on every unprivileged one. After the ambient grant CapEff regained CAP_SETUID and both unprivileged Docker guests were discovered, taking that node from one Docker LXC to three.
This commit is contained in:
@@ -1983,6 +1983,18 @@ the intentionally sparse public response.
|
||||
`NoNewPrivileges=false` and `RestrictSUIDSGID=false`. That exception is
|
||||
limited to the PVE command-agent path; it must not become the default for
|
||||
Docker / Podman, host-only, PBS-only, or ordinary non-command agents.
|
||||
The narrower `AmbientCapabilities=CAP_SETUID CAP_SETGID` grant is not bound
|
||||
to the install-time flag and must be written for every `--enable-proxmox`
|
||||
agent of type `pve` or `all`, with or without `--enable-commands`. Command
|
||||
execution is also enabled from the server at runtime, where
|
||||
`applyRemoteConfig` starts the command client without regenerating the unit,
|
||||
so an agent provisioned only for the install-time case would accept commands
|
||||
it cannot use: `lxc-attach` needs `CAP_SETUID` to write `/proc/<pid>/uid_map`
|
||||
for unprivileged guests, and without it the Docker socket probe fails with
|
||||
`write_id_mapping: 61 Operation not permitted` for every unprivileged LXC.
|
||||
Because that probe failure is debug-level and retried each poll, the
|
||||
resulting surface is silently incomplete rather than visibly broken, so the
|
||||
capability must be provisioned up front rather than diagnosed later.
|
||||
Persistence-sensitive NAS targets must keep one canonical continuity model here: installer-owned bootstraps may use flash-backed or immutable-root launch hooks only as thin trampolines, while the durable wrapper, state, and reboot-surviving binary copy stay in the governed persistent state directory that updater continuity also refreshes.
|
||||
Unix `--update` re-entry must also preserve lifecycle identity for legacy
|
||||
v5.1.x agents that do not yet have v6 `connection.env` state. When a
|
||||
|
||||
@@ -467,6 +467,20 @@ upgrade, update, release, or artifact-selection behavior.
|
||||
`RestrictSUIDSGID=false`. That exception is deployment-owned operator truth
|
||||
for the Proxmox LXC Docker inventory path and must not leak into non-PVE or
|
||||
non-command agent installs.
|
||||
Independently of the install-time command flag, the unit for **any** PVE
|
||||
agent must grant `AmbientCapabilities=CAP_SETUID CAP_SETGID`. `lxc-attach`
|
||||
into an unprivileged guest writes `/proc/<pid>/uid_map`, which requires
|
||||
`CAP_SETUID` in the parent user namespace; `NoNewPrivileges` removes it from
|
||||
the effective set and simultaneously blocks the setuid
|
||||
`newuidmap`/`newgidmap` fallback, so the socket probe fails with
|
||||
`write_id_mapping: 61 Operation not permitted`. Gating this grant on the
|
||||
install-time flag is not sufficient, because command execution is also
|
||||
enabled from the server at runtime (`applyRemoteConfig`) without rewriting
|
||||
the unit; an agent switched on that way could run commands but never attach
|
||||
to unprivileged guests, so Docker inside them silently vanished from the
|
||||
Proxmox page. The grant is deliberately narrower than the
|
||||
`NoNewPrivileges=false` exception above: it restores only the privilege
|
||||
`lxc-attach` needs and leaves the rest of the sandbox intact.
|
||||
|
||||
## Extension Points
|
||||
|
||||
|
||||
+30
-2
@@ -1012,10 +1012,27 @@ render_systemd_agent_unit() {
|
||||
if [[ -n "$log_target" ]]; then
|
||||
log_lines=$'\n'"StandardOutput=append:${log_target}"$'\n'"StandardError=append:${log_target}"
|
||||
fi
|
||||
local ambient_line=""
|
||||
if systemd_agent_requires_lxc_attach; then
|
||||
no_new_privileges="false"
|
||||
restrict_suidsgid="false"
|
||||
fi
|
||||
if systemd_agent_may_attach_lxc; then
|
||||
# lxc-attach into an unprivileged guest writes /proc/<pid>/uid_map,
|
||||
# which needs CAP_SETUID in the parent user namespace. NoNewPrivileges
|
||||
# drops CAP_SETUID from the effective set and also stops lxc-attach
|
||||
# falling back to the setuid newuidmap/newgidmap helpers, so the probe
|
||||
# dies with "write_id_mapping: Operation not permitted" and Docker in
|
||||
# every unprivileged LXC stays invisible.
|
||||
#
|
||||
# This is granted for any PVE agent, not only one installed with
|
||||
# --enable-commands, because command execution can be turned on later
|
||||
# from the server (applyRemoteConfig) without rewriting this unit. That
|
||||
# path used to leave the agent able to run commands but unable to
|
||||
# attach to unprivileged guests, and the failure is only visible at
|
||||
# debug level.
|
||||
ambient_line=$'\n'"AmbientCapabilities=CAP_SETUID CAP_SETGID"
|
||||
fi
|
||||
|
||||
local hardening_lines
|
||||
hardening_lines="NoNewPrivileges=${no_new_privileges}
|
||||
@@ -1025,7 +1042,7 @@ ProtectKernelModules=true
|
||||
ProtectControlGroups=true
|
||||
LockPersonality=true
|
||||
RestrictSUIDSGID=${restrict_suidsgid}
|
||||
SystemCallArchitectures=native"
|
||||
SystemCallArchitectures=native${ambient_line}"
|
||||
if [[ -d /usr/syno ]]; then
|
||||
# Synology DSM ships a heavily patched, old systemd whose kernels
|
||||
# cannot apply these sandbox directives; NoNewPrivileges alone kills
|
||||
@@ -1053,7 +1070,18 @@ EOF
|
||||
}
|
||||
|
||||
systemd_agent_requires_lxc_attach() {
|
||||
if [[ "$ENABLE_COMMANDS" != "true" || "$ENABLE_PROXMOX" != "true" ]]; then
|
||||
if [[ "$ENABLE_COMMANDS" != "true" ]]; then
|
||||
return 1
|
||||
fi
|
||||
systemd_agent_may_attach_lxc
|
||||
}
|
||||
|
||||
# Whether this host could ever need lxc-attach, independent of whether command
|
||||
# execution was enabled at install time. Command execution is also togglable
|
||||
# from the server after the fact, so the unit has to be provisioned for it up
|
||||
# front or the later toggle produces a half-working agent.
|
||||
systemd_agent_may_attach_lxc() {
|
||||
if [[ "$ENABLE_PROXMOX" != "true" ]]; then
|
||||
return 1
|
||||
fi
|
||||
case "${PROXMOX_TYPE:-}" in
|
||||
|
||||
@@ -266,7 +266,7 @@ func TestInstallSHAllowsProxmoxCommandAgentLXCAttach(t *testing.T) {
|
||||
script := string(content)
|
||||
required := []string{
|
||||
`systemd_agent_requires_lxc_attach() {`,
|
||||
`if [[ "$ENABLE_COMMANDS" != "true" || "$ENABLE_PROXMOX" != "true" ]]; then`,
|
||||
`if [[ "$ENABLE_COMMANDS" != "true" ]]; then`,
|
||||
`""|pve|all)`,
|
||||
`no_new_privileges="false"`,
|
||||
`restrict_suidsgid="false"`,
|
||||
@@ -278,6 +278,37 @@ func TestInstallSHAllowsProxmoxCommandAgentLXCAttach(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// A PVE agent installed without --enable-commands must still be provisioned for
|
||||
// lxc-attach, because command execution can be enabled later from the server
|
||||
// without rewriting the unit. Without CAP_SETUID, lxc-attach cannot write
|
||||
// /proc/<pid>/uid_map for unprivileged guests, so Docker inside every
|
||||
// unprivileged LXC silently disappears from the Proxmox page.
|
||||
func TestInstallSHGrantsLXCAttachCapabilitiesForAnyPVEAgent(t *testing.T) {
|
||||
content, err := os.ReadFile(repoFile("scripts", "install.sh"))
|
||||
if err != nil {
|
||||
t.Fatalf("read install.sh: %v", err)
|
||||
}
|
||||
|
||||
script := string(content)
|
||||
required := []string{
|
||||
`systemd_agent_may_attach_lxc() {`,
|
||||
`if [[ "$ENABLE_PROXMOX" != "true" ]]; then`,
|
||||
`if systemd_agent_may_attach_lxc; then`,
|
||||
`AmbientCapabilities=CAP_SETUID CAP_SETGID`,
|
||||
`SystemCallArchitectures=native${ambient_line}`,
|
||||
}
|
||||
for _, needle := range required {
|
||||
if !strings.Contains(script, needle) {
|
||||
t.Fatalf("install.sh missing unprivileged-LXC attach capability grant: %s", needle)
|
||||
}
|
||||
}
|
||||
|
||||
if strings.Contains(script, `systemd_agent_may_attach_lxc`) &&
|
||||
!strings.Contains(script, "command execution can be turned on later") {
|
||||
t.Fatal("install.sh must document why the capability grant is not gated on ENABLE_COMMANDS")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstallSHPreflightChecksAgentDownloadArtifact(t *testing.T) {
|
||||
var requestedDownloadPath string
|
||||
var requestedDownloadArch string
|
||||
|
||||
Reference in New Issue
Block a user