From 43779a992b53a39eab318ee28376c95618c8948b Mon Sep 17 00:00:00 2001 From: librelad Date: Sat, 23 May 2026 23:48:23 +0100 Subject: [PATCH] harden(desudo): backup engines (restic/kopia/borg) + crowdsec host helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - restic_install, crowdsec_update/verify_firewall/fix_priority: pure host ops (apt/cscli/nft/systemctl, /etc/crowdsec) -> runSystem. - kopia_backup/borg_restore: ignore-file/target tee+chown+mkdir -> runFileOp/ runFileWrite; kept the 'sudo -E -u dockerinstall' engine calls as-is — those already run as the unprivileged backup user (least-privilege; the scoped sudoers will permit (dockerinstall)). Co-Authored-By: Claude Opus 4.7 Signed-off-by: librelad --- .../app/containers/crowdsec/crowdsec_fix_priority.sh | 10 +++++----- scripts/app/containers/crowdsec/crowdsec_update.sh | 12 ++++++------ .../containers/crowdsec/crowdsec_verify_firewall.sh | 10 +++++----- scripts/backup/engine/borg_restore.sh | 4 ++-- scripts/backup/engine/kopia_backup.sh | 8 ++++---- scripts/backup/engine/restic_install.sh | 12 ++++++------ 6 files changed, 28 insertions(+), 28 deletions(-) diff --git a/scripts/app/containers/crowdsec/crowdsec_fix_priority.sh b/scripts/app/containers/crowdsec/crowdsec_fix_priority.sh index 416a28f..18d4591 100644 --- a/scripts/app/containers/crowdsec/crowdsec_fix_priority.sh +++ b/scripts/app/containers/crowdsec/crowdsec_fix_priority.sh @@ -9,14 +9,14 @@ appCrowdSecFixPriority() { local target_priority="-100" - sudo cp "$cfg" "${cfg}.bak.$(date +%Y%m%d-%H%M%S)" + runSystem cp "$cfg" "${cfg}.bak.$(date +%Y%m%d-%H%M%S)" checkSuccess "Backed up $cfg" # nftables section in the yaml has ipv4: and ipv6: subsections; each may # carry a priority: line. Set both to target_priority, inserting the key # if it isn't present. We hand the file to a small awk pass so the YAML # indentation is preserved. - sudo awk -v p="$target_priority" ' + runSystem awk -v p="$target_priority" ' BEGIN { in_v4=0; in_v6=0; v4_done=0; v6_done=0 } /^[[:space:]]*ipv4:/ { in_v4=1; in_v6=0; print; next } /^[[:space:]]*ipv6:/ { in_v6=1; in_v4=0; print; next } @@ -28,11 +28,11 @@ appCrowdSecFixPriority() { in_v4 && /^[[:space:]]+priority:/ { sub(/priority:.*/, "priority: " p); v4_done=1 } in_v6 && /^[[:space:]]+priority:/ { sub(/priority:.*/, "priority: " p); v6_done=1 } { print } - ' "$cfg" | sudo tee "${cfg}.new" >/dev/null - sudo mv "${cfg}.new" "$cfg" + ' "$cfg" | runSystem tee "${cfg}.new" >/dev/null + runSystem mv "${cfg}.new" "$cfg" checkSuccess "Patched nftables priority to $target_priority in $cfg" - sudo systemctl restart crowdsec-firewall-bouncer + runSystem systemctl restart crowdsec-firewall-bouncer checkSuccess "Restarted crowdsec-firewall-bouncer" isSuccessful "Priority updated. Run 'crowdsec_verify_firewall' to confirm CrowdSec now runs before UFW." diff --git a/scripts/app/containers/crowdsec/crowdsec_update.sh b/scripts/app/containers/crowdsec/crowdsec_update.sh index 5a92eb7..ee2fd4e 100644 --- a/scripts/app/containers/crowdsec/crowdsec_update.sh +++ b/scripts/app/containers/crowdsec/crowdsec_update.sh @@ -7,32 +7,32 @@ appCrowdSecUpdate() { echo "" echo "---- $menu_number. Updating apt package index." echo "" - local result=$(sudo apt-get update) + local result=$(runSystem apt-get update) checkSuccess "apt-get update" ((menu_number++)) echo "" echo "---- $menu_number. Upgrading CrowdSec packages." echo "" - local result=$(sudo apt-get install -y --only-upgrade crowdsec crowdsec-firewall-bouncer-nftables) + local result=$(runSystem apt-get install -y --only-upgrade crowdsec crowdsec-firewall-bouncer-nftables) checkSuccess "Upgraded crowdsec + crowdsec-firewall-bouncer-nftables" ((menu_number++)) echo "" echo "---- $menu_number. Refreshing hub collections." echo "" - local result=$(sudo cscli hub update) + local result=$(runSystem cscli hub update) checkSuccess "Refreshed hub index" - local result=$(sudo cscli hub upgrade) + local result=$(runSystem cscli hub upgrade) checkSuccess "Upgraded installed collections" ((menu_number++)) echo "" echo "---- $menu_number. Reloading services." echo "" - local result=$(sudo systemctl reload crowdsec) + local result=$(runSystem systemctl reload crowdsec) checkSuccess "Reloaded crowdsec agent" - local result=$(sudo systemctl restart crowdsec-firewall-bouncer) + local result=$(runSystem systemctl restart crowdsec-firewall-bouncer) checkSuccess "Restarted crowdsec-firewall-bouncer" isSuccessful "CrowdSec updated. Run 'crowdsec_verify_firewall' if you want to re-check nftables priorities." diff --git a/scripts/app/containers/crowdsec/crowdsec_verify_firewall.sh b/scripts/app/containers/crowdsec/crowdsec_verify_firewall.sh index 4a36f45..68d07e4 100644 --- a/scripts/app/containers/crowdsec/crowdsec_verify_firewall.sh +++ b/scripts/app/containers/crowdsec/crowdsec_verify_firewall.sh @@ -2,22 +2,22 @@ appCrowdSecVerifyFirewall() { echo "=== nftables tables present ===" - sudo nft list tables 2>&1 + runSystem nft list tables 2>&1 echo echo "=== chain priorities (input hook) ===" - sudo nft list ruleset 2>/dev/null | grep -E 'chain |hook input.*priority' | head -30 + runSystem nft list ruleset 2>/dev/null | grep -E 'chain |hook input.*priority' | head -30 echo echo "=== priority comparison ===" local cs_prio ufw_prio - cs_prio=$(sudo nft list ruleset 2>/dev/null | awk '/table .* crowdsec/{flag=1} flag && /priority/{match($0,/priority [-0-9]+/); print substr($0,RSTART+9,RLENGTH-9); exit}') - ufw_prio=$(sudo nft list ruleset 2>/dev/null | awk '/chain ufw[a-z0-9-]*input/{flag=1} flag && /priority/{match($0,/priority [-0-9]+/); print substr($0,RSTART+9,RLENGTH-9); exit}') + cs_prio=$(runSystem nft list ruleset 2>/dev/null | awk '/table .* crowdsec/{flag=1} flag && /priority/{match($0,/priority [-0-9]+/); print substr($0,RSTART+9,RLENGTH-9); exit}') + ufw_prio=$(runSystem nft list ruleset 2>/dev/null | awk '/chain ufw[a-z0-9-]*input/{flag=1} flag && /priority/{match($0,/priority [-0-9]+/); print substr($0,RSTART+9,RLENGTH-9); exit}') echo "CrowdSec priority: ${cs_prio:-not present}" echo "UFW priority: ${ufw_prio:-not present}" if [[ -z "$cs_prio" ]]; then isNotice "CrowdSec nftables table missing — bouncer may not be running." - sudo systemctl is-active crowdsec-firewall-bouncer + runSystem systemctl is-active crowdsec-firewall-bouncer return 1 fi if [[ -z "$ufw_prio" ]]; then diff --git a/scripts/backup/engine/borg_restore.sh b/scripts/backup/engine/borg_restore.sh index 7e8fd72..0af5b25 100644 --- a/scripts/backup/engine/borg_restore.sh +++ b/scripts/backup/engine/borg_restore.sh @@ -13,7 +13,7 @@ borgRestoreSnapshot() fi borgEnvExport "$idx" || return 1 - sudo mkdir -p "$target_dir" + runFileOp mkdir -p "$target_dir" isNotice "Restoring $snapshot_id from $(resticLocationName "$idx") → $target_dir" local rc @@ -39,7 +39,7 @@ borgDumpFile() borgEnvExport "$idx" || return 1 local stripped="${file_path#/}" if [[ -n "$target_file" ]]; then - sudo -E -u "$docker_install_user" borg extract --stdout "::$snapshot_id" "$stripped" | sudo tee "$target_file" >/dev/null + sudo -E -u "$docker_install_user" borg extract --stdout "::$snapshot_id" "$stripped" | runFileWrite "$target_file" else sudo -E -u "$docker_install_user" borg extract --stdout "::$snapshot_id" "$stripped" fi diff --git a/scripts/backup/engine/kopia_backup.sh b/scripts/backup/engine/kopia_backup.sh index 9fd46b8..b5bc23e 100644 --- a/scripts/backup/engine/kopia_backup.sh +++ b/scripts/backup/engine/kopia_backup.sh @@ -30,13 +30,13 @@ kopiaBackupAppToLocation() local wrote_ignore=false if [[ -n "${backup_exclude_paths:-}" ]]; then local rel - : | sudo tee "$ignore_file" >/dev/null + : | runFileWrite "$ignore_file" while IFS= read -r p; do [[ -z "$p" ]] && continue rel="/${p#"$source_path"/}" - echo "$rel" | sudo tee -a "$ignore_file" >/dev/null + echo "$rel" | runFileWrite -a "$ignore_file" done <<< "$backup_exclude_paths" - sudo chown "$docker_install_user":"$docker_install_user" "$ignore_file" 2>/dev/null + runFileOp chown "$docker_install_user":"$docker_install_user" "$ignore_file" 2>/dev/null wrote_ignore=true fi @@ -44,7 +44,7 @@ kopiaBackupAppToLocation() output=$(sudo -E -u "$docker_install_user" kopia snapshot create "$source_path" "${tags[@]}" --json 2>&1) local rc=$? - [[ "$wrote_ignore" == true ]] && sudo rm -f "$ignore_file" + [[ "$wrote_ignore" == true ]] && runFileOp rm -f "$ignore_file" local snapshot_id snapshot_id=$(echo "$output" | grep -oE '"id":\s*"[^"]+"' | head -1 | cut -d'"' -f4) diff --git a/scripts/backup/engine/restic_install.sh b/scripts/backup/engine/restic_install.sh index 34fc311..10e4955 100644 --- a/scripts/backup/engine/restic_install.sh +++ b/scripts/backup/engine/restic_install.sh @@ -12,16 +12,16 @@ resticInstall() isHeader "Installing restic" if command -v apt-get >/dev/null 2>&1; then - sudo apt-get update -qq >/dev/null - if sudo apt-get install -y restic >/dev/null 2>&1; then + runSystem apt-get update -qq >/dev/null + if runSystem apt-get install -y restic >/dev/null 2>&1; then checkSuccess "restic installed via apt" - sudo restic self-update >/dev/null 2>&1 || true + runSystem restic self-update >/dev/null 2>&1 || true return 0 fi elif command -v dnf >/dev/null 2>&1; then - sudo dnf install -y restic && return 0 + runSystem dnf install -y restic && return 0 elif command -v pacman >/dev/null 2>&1; then - sudo pacman -S --noconfirm restic && return 0 + runSystem pacman -S --noconfirm restic && return 0 fi isNotice "Package manager install unavailable — downloading restic binary" @@ -38,7 +38,7 @@ resticInstall() tmp=$(mktemp -d) if curl -sL "https://github.com/restic/restic/releases/latest/download/restic_linux_${arch}.bz2" -o "$tmp/restic.bz2"; then bunzip2 "$tmp/restic.bz2" - sudo install -m 0755 "$tmp/restic" /usr/local/bin/restic + runSystem install -m 0755 "$tmp/restic" /usr/local/bin/restic rm -rf "$tmp" checkSuccess "restic installed to /usr/local/bin/restic" else