Automate the AppArmor fix with a narrowly-scoped sudoers rule
Adds bootstrap-host.sh (one-time, run as root on a fresh Proxmox host) and fix-lxc-apparmor.sh, the fixed-content script it installs. The sudoers rule it wires up only ever invokes that one root-owned script with a VMID argument - deliberately not a broader rule like `tee -a <conf>` or `sh -c '...'`, since those only restrict the command's own argv, not stdin/heredoc content, letting the caller write arbitrary lines to any 200-299 container's config instead of just this one fixed line. create-graylog-lxc.sh now tries `sudo -n fix-lxc-apparmor.sh` first and falls back to the existing manual instructions if that sudoers rule isn't present yet - fully backward compatible with hosts that haven't run bootstrap-host.sh. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
458aada5a5
commit
d98a63c0f1
5 changed files with 125 additions and 8 deletions
26
README.md
26
README.md
|
|
@ -290,14 +290,36 @@ alert templates already do it.
|
|||
- **Docker-in-unprivileged-LXC AppArmor block**: containers fail with
|
||||
`open sysctl net.ipv4.ip_unprivileged_port_start: permission denied`
|
||||
unless the Proxmox host admin adds a raw LXC config line. `pct set`
|
||||
does not expose this option, so it cannot be automated within the
|
||||
`claude-deploy` sudo scope. If you hit this, run as root on the host:
|
||||
does not expose this option (checked: `--features` covers
|
||||
`nesting`/`keyctl`/`mount`/`fuse`/`mknod`/`force_rw_sys`, nothing for
|
||||
AppArmor), so it can't be automated within the plain `claude-deploy`
|
||||
sudo scope of `pct create/set/start/stop/exec/status/list`. If you hit
|
||||
this, run as root on the host:
|
||||
```bash
|
||||
echo "lxc.apparmor.profile: unconfined" >> /etc/pve/lxc/<VMID>.conf
|
||||
pct reboot <VMID>
|
||||
```
|
||||
then re-run `create-graylog-lxc.sh` (idempotent, will continue from there).
|
||||
|
||||
**Automating this fix (optional, one-time per Proxmox host):** run
|
||||
`bootstrap-host.sh` once as root. It installs `fix-lxc-apparmor.sh` to
|
||||
`/usr/local/sbin/` (root-owned, `chmod 700` - not writable by
|
||||
`claude-deploy`) plus a narrow sudoers rule scoped to exactly that script
|
||||
and the VMID 200-299 range:
|
||||
```
|
||||
claude-deploy ALL=(root) NOPASSWD: /usr/local/sbin/fix-lxc-apparmor.sh 2[0-9][0-9]
|
||||
```
|
||||
Deliberately *not* a broader rule like `tee -a /etc/pve/lxc/2[0-9][0-9].conf`
|
||||
or `sh -c '...'`: sudoers only restricts a command's own argv, not
|
||||
stdin/heredoc content, so either of those would let the caller append
|
||||
*arbitrary* lines to any 200-299 container's config (e.g.
|
||||
`lxc.mount.entry` to bind-mount host paths in) - a much wider grant than
|
||||
intended. Pinning the exact line inside a fixed, root-owned script is
|
||||
what keeps the sudoers rule's effect as narrow as its pattern suggests.
|
||||
Once bootstrapped, `create-graylog-lxc.sh` calls
|
||||
`sudo -n fix-lxc-apparmor.sh` automatically and only falls back to the
|
||||
manual instructions above if that sudoers rule isn't present yet.
|
||||
|
||||
- **`vm.max_map_count`**: OpenSearch requires >= 262144. This is a
|
||||
host-wide kernel parameter, not namespaced per LXC, so it can't be set
|
||||
from inside the container either. `install-graylog.sh` only verifies it
|
||||
|
|
|
|||
27
README.uk.md
27
README.uk.md
|
|
@ -293,9 +293,11 @@ Graylog відхиляє його з помилкою Jackson-поліморфі
|
|||
- **Блокування Docker-в-unprivileged-LXC через AppArmor**: контейнери
|
||||
падають з помилкою `open sysctl net.ipv4.ip_unprivileged_port_start:
|
||||
permission denied`, якщо адміністратор хоста не додасть сирий рядок
|
||||
конфігурації LXC. `pct set` не підтримує цю опцію, тож автоматизувати
|
||||
це в межах прав `claude-deploy` неможливо. Якщо зіткнетесь із цим,
|
||||
виконайте під root на хості:
|
||||
конфігурації LXC. `pct set` не підтримує цю опцію (перевірено:
|
||||
`--features` покриває `nesting`/`keyctl`/`mount`/`fuse`/`mknod`/
|
||||
`force_rw_sys`, нічого для AppArmor), тож автоматизувати це в межах
|
||||
звичайних прав `claude-deploy` (`pct create/set/start/stop/exec/status/list`)
|
||||
неможливо. Якщо зіткнетесь із цим, виконайте під root на хості:
|
||||
```bash
|
||||
echo "lxc.apparmor.profile: unconfined" >> /etc/pve/lxc/<VMID>.conf
|
||||
pct reboot <VMID>
|
||||
|
|
@ -303,6 +305,25 @@ Graylog відхиляє його з помилкою Jackson-поліморфі
|
|||
а потім перезапустіть `create-graylog-lxc.sh` (ідемпотентний, продовжить
|
||||
з цього місця).
|
||||
|
||||
**Автоматизація цього фіксу (опційно, одноразово на кожен Proxmox-хост):**
|
||||
запустіть `bootstrap-host.sh` один раз від root. Він встановлює
|
||||
`fix-lxc-apparmor.sh` у `/usr/local/sbin/` (власник root, `chmod 700` —
|
||||
недоступний для запису `claude-deploy`) плюс вузьке sudoers-правило,
|
||||
прив'язане саме до цього скрипта й діапазону VMID 200-299:
|
||||
```
|
||||
claude-deploy ALL=(root) NOPASSWD: /usr/local/sbin/fix-lxc-apparmor.sh 2[0-9][0-9]
|
||||
```
|
||||
Навмисно *не* ширше правило типу `tee -a /etc/pve/lxc/2[0-9][0-9].conf`
|
||||
чи `sh -c '...'`: sudoers обмежує лише власні аргументи команди, а не
|
||||
вміст stdin/heredoc, тож будь-який з цих варіантів дозволив би дописати
|
||||
*довільні* рядки в конфіг будь-якого контейнера 200-299 (наприклад,
|
||||
`lxc.mount.entry` для монтування хостових шляхів усередину) — набагато
|
||||
ширший доступ, ніж потрібно. Саме фіксація точного рядка всередині
|
||||
незмінного, root-owned скрипта тримає ефект sudoers-правила таким же
|
||||
вузьким, як і виглядає його патерн. Після bootstrap `create-graylog-lxc.sh`
|
||||
сам викликає `sudo -n fix-lxc-apparmor.sh` і повертається до ручної
|
||||
інструкції вище лише якщо цього sudoers-правила ще немає.
|
||||
|
||||
- **`vm.max_map_count`**: OpenSearch вимагає >= 262144. Це
|
||||
загальносистемний параметр ядра хоста, не прив'язаний до конкретного
|
||||
LXC, тож встановити його зсередини контейнера теж неможливо.
|
||||
|
|
|
|||
44
bootstrap-host.sh
Normal file
44
bootstrap-host.sh
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#!/bin/bash
|
||||
# Run ONCE, as root, on a fresh Proxmox host - not part of the claude-deploy
|
||||
# workflow, since claude-deploy is exactly the account this script is
|
||||
# widening permissions FOR. Idempotent: safe to re-run.
|
||||
#
|
||||
# Installs fix-lxc-apparmor.sh (root-owned, not writable by claude-deploy)
|
||||
# and a narrow sudoers rule that lets claude-deploy invoke it - and only
|
||||
# it, only with a VMID argument in the 200-299 range - so create-graylog-lxc.sh
|
||||
# can clear the "Docker-in-unprivileged-LXC AppArmor block" quirk (see
|
||||
# README.md) without a human editing /etc/pve/lxc/*.conf by hand every time.
|
||||
#
|
||||
# Deliberately does NOT grant broader access (e.g. `tee -a` or `sh -c` on
|
||||
# the conf file): those let the caller write arbitrary content to any
|
||||
# 200-299 container's config, not just this one fixed line. See the
|
||||
# "Automating the AppArmor fix" note in README.md for why.
|
||||
set -euo pipefail
|
||||
|
||||
[ "$(id -u)" -eq 0 ] || { echo "Must run as root." >&2; exit 1; }
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TARGET="/usr/local/sbin/fix-lxc-apparmor.sh"
|
||||
SUDOERS_FILE="/etc/sudoers.d/claude-deploy-apparmor"
|
||||
SUDOERS_LINE="claude-deploy ALL=(root) NOPASSWD: ${TARGET} 2[0-9][0-9]"
|
||||
|
||||
if [ -f "$TARGET" ] && cmp -s "$SCRIPT_DIR/fix-lxc-apparmor.sh" "$TARGET"; then
|
||||
echo "⏭ $TARGET already up to date"
|
||||
else
|
||||
install -o root -g root -m 700 "$SCRIPT_DIR/fix-lxc-apparmor.sh" "$TARGET"
|
||||
echo "✓ installed $TARGET (root-owned, 700)"
|
||||
fi
|
||||
|
||||
if [ -f "$SUDOERS_FILE" ] && grep -qxF "$SUDOERS_LINE" "$SUDOERS_FILE"; then
|
||||
echo "⏭ sudoers rule already present in $SUDOERS_FILE"
|
||||
else
|
||||
tmp="$(mktemp)"
|
||||
echo "$SUDOERS_LINE" > "$tmp"
|
||||
chmod 440 "$tmp"
|
||||
visudo -c -f "$tmp" || { echo "generated sudoers snippet failed validation" >&2; rm -f "$tmp"; exit 1; }
|
||||
mv "$tmp" "$SUDOERS_FILE"
|
||||
chown root:root "$SUDOERS_FILE"
|
||||
echo "✓ installed sudoers rule at $SUDOERS_FILE"
|
||||
fi
|
||||
|
||||
echo "Done. claude-deploy can now run: sudo $TARGET <vmid>"
|
||||
|
|
@ -181,12 +181,18 @@ start_container_and_wait_net() {
|
|||
}
|
||||
|
||||
check_apparmor_hint() {
|
||||
# We cannot fix this ourselves (no permission to edit /etc/pve/lxc/*.conf),
|
||||
# so just remind the operator up front; install-graylog.sh will hard-stop
|
||||
# with the same instructions if it's actually needed.
|
||||
# If bootstrap-host.sh has been run on this host, the narrow sudoers rule
|
||||
# for fix-lxc-apparmor.sh exists and we can just fix this ourselves.
|
||||
# Otherwise fall back to the same manual instructions as before - this
|
||||
# must never hard-fail the script over an optional convenience path.
|
||||
if sudo -n /usr/local/sbin/fix-lxc-apparmor.sh "$VMID" 2>/dev/null; then
|
||||
ok "AppArmor profile fixed automatically (bootstrap-host.sh was run on this host)"
|
||||
return
|
||||
fi
|
||||
log "NOTE: if Docker fails inside the container with a sysctl/AppArmor permission error,"
|
||||
log " the host admin must run:"
|
||||
log " echo 'lxc.apparmor.profile: unconfined' >> /etc/pve/lxc/${VMID}.conf && pct reboot ${VMID}"
|
||||
log " (or run bootstrap-host.sh once as root to automate this for good)"
|
||||
}
|
||||
|
||||
copy_install_payload() {
|
||||
|
|
|
|||
24
fix-lxc-apparmor.sh
Normal file
24
fix-lxc-apparmor.sh
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#!/bin/bash
|
||||
# Installed by bootstrap-host.sh to /usr/local/sbin/fix-lxc-apparmor.sh,
|
||||
# owned by root, NOT writable by claude-deploy - only invocable via the
|
||||
# narrow sudoers rule bootstrap-host.sh also installs.
|
||||
#
|
||||
# Idempotently enables lxc.apparmor.profile=unconfined for one LXC container
|
||||
# (needed for Docker to run inside an unprivileged container - see the
|
||||
# "AppArmor" quirk in README.md) and reboots it if a change was actually
|
||||
# made. The VMID range is enforced by the sudoers rule that invokes this
|
||||
# script, not by this script itself.
|
||||
set -euo pipefail
|
||||
VMID="${1:?Usage: fix-lxc-apparmor.sh <vmid>}"
|
||||
CONF="/etc/pve/lxc/${VMID}.conf"
|
||||
|
||||
[ -f "$CONF" ] || { echo "No such container config: $CONF" >&2; exit 1; }
|
||||
|
||||
if grep -qx "lxc.apparmor.profile: unconfined" "$CONF"; then
|
||||
echo "already set, nothing to do"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "lxc.apparmor.profile: unconfined" >> "$CONF"
|
||||
pct reboot "$VMID"
|
||||
echo "applied and rebooted $VMID"
|
||||
Loading…
Add table
Reference in a new issue