CrowdSec's host-side install (the agent + nftables bouncer the LibrePortal
Traefik plugin talks to) had stayed on blanket sudo throughout the rootless +
de-sudo hardening: `sudo apt-get install crowdsec`, `curl | sudo bash`,
`sudo sed -i /etc/crowdsec/config.yaml`, `sudo touch + sudo chmod /var/log/
crowdsec*.log`, `echo $key | sudo tee /etc/crowdsec/traefik_bouncer.key`,
plus `sudo cscli capi register / console enroll / bouncers add`. None of
those are in the scoped LP_HELPERS / LP_SYSTEM sudoers grant the manager
now holds, so any user who enabled crowdsec would have hit hard sudo
failures on every privileged step.
Follow the libreportal-appcfg / libreportal-bininstall pattern: one new
root-owned helper at /usr/local/lib/libreportal/libreportal-crowdsec
that does every privileged op behind a fixed action vocabulary with strict
argument validation. The manager calls in via runCrowdsec — the scoped
sudoers grants exactly one binary, the same trust boundary the other
helpers rely on.
Actions:
install apt repo + agent + firewall-bouncer + enable +
crowdsecurity/{linux,sshd} collections + reload
(idempotent — skips parts already in place)
services <verb> enable | disable | restart
capi <verb> register | unregister | status
console <verb> enroll <token> | disenroll | status
token format strictly validated
bouncer-traefik-init cscli register + write the manager-owned key file
atomically (returns EXISTS or GENERATED:<key>)
bouncer-priority bouncer yaml nftables priority → -100
(moved from libreportal-appcfg; one helper for
every crowdsec root op)
bind-lapi flip listen_uri to 0.0.0.0:8080 in config.yaml
prometheus <on…|off> flip the prometheus block (validated addr/port)
touch-host-logs create + chmod 0644 /var/log/crowdsec*.log so the
libreportal container can tail them
Wired in via:
- new sudoers Cmnd_Alias entry for the helper in LP_HELPERS
- new helper baked alongside the others by initRootHelpers
(replaces __SYSTEM_DIR__ / __CONTAINERS_DIR__ / __MANAGER__ at
install, with safe runtime fallbacks if unbaked)
- new runCrowdsec dispatch in scripts/docker/command/run_privileged.sh
containers/crowdsec/scripts/crowdsec_install_host.sh now drives the whole
flow through runCrowdsec — every `sudo …` is gone, the compose-toggle sed
uses runFileOp, and the security_crowdsec CFG mirror uses runInstallOp
(configs/ is manager-owned). Net: install script shrinks ~80 lines while
gaining a single auditable trust boundary. crowdsec_fix_priority.sh swung
over to runCrowdsec bouncer-priority too — the appcfg crowdsec_priority
action drops out cleanly.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
134 lines
5.5 KiB
Bash
134 lines
5.5 KiB
Bash
#!/bin/bash
|
|
# LibrePortal app-config helper — root-privileged edits of specific app config
|
|
# files owned by in-container UIDs (AdGuard yaml, ownCloud config.php) or host
|
|
# /etc (CrowdSec bouncer). Installed root:root 0755 to /usr/local/sbin by
|
|
# init.sh. Self-contained; each action edits a FIXED path with strictly-validated
|
|
# arguments, so the scoped sudoers needn't grant blanket sudo awk/sed/tee/cp/mv
|
|
# on those trees. Faithful ports of the original transforms.
|
|
|
|
set -u
|
|
|
|
[[ $EUID -eq 0 ]] || { echo "libreportal-appcfg: must run as root" >&2; exit 1; }
|
|
|
|
# Baked at install; unbaked copies keep the "__" sentinel.
|
|
SYSTEM_DIR="__SYSTEM_DIR__"
|
|
CONTAINERS_DIR="__CONTAINERS_DIR__"
|
|
[[ "$SYSTEM_DIR" == *"__"* || -z "$SYSTEM_DIR" ]] && SYSTEM_DIR="/libreportal-system"
|
|
[[ "$CONTAINERS_DIR" == *"__"* || -z "$CONTAINERS_DIR" ]] && CONTAINERS_DIR="/libreportal-containers"
|
|
DB_CFG="$SYSTEM_DIR/configs/general/general_docker_install"
|
|
|
|
_install_user() {
|
|
local u
|
|
u=$(grep -h '^CFG_DOCKER_INSTALL_USER=' "$DB_CFG" 2>/dev/null | head -1 | cut -d= -f2 | awk '{print $1}')
|
|
[[ -n "$u" ]] && id -u "$u" >/dev/null 2>&1 && { echo "$u"; return; }
|
|
echo "dockerinstall"
|
|
}
|
|
|
|
# --- AdGuard: set users[0].name + password (bcrypt) in AdGuardHome.yaml --------
|
|
adguard_auth() {
|
|
local user="$1" bcrypt="$2"
|
|
[[ "$user" =~ ^[A-Za-z0-9._@-]+$ ]] || { echo "libreportal-appcfg: invalid adguard user" >&2; return 1; }
|
|
[[ "$bcrypt" =~ ^[A-Za-z0-9./\$]+$ ]] || { echo "libreportal-appcfg: invalid bcrypt" >&2; return 1; }
|
|
local yaml="$CONTAINERS_DIR/adguard/conf/AdGuardHome.yaml"
|
|
[[ -f "$yaml" ]] || { echo "libreportal-appcfg: $yaml not found" >&2; return 1; }
|
|
local tmp; tmp=$(mktemp)
|
|
if ! awk -v u="$user" -v pw="$bcrypt" '
|
|
/^users:/ { in_users=1; print; next }
|
|
in_users && /^[^[:space:]-]/ { in_users=0 }
|
|
in_users && /^[[:space:]]+name:/ && !done_user {
|
|
match($0, /^[[:space:]]+/); print substr($0, RSTART, RLENGTH) "name: " u; done_user=1; next
|
|
}
|
|
in_users && /^[[:space:]]+password:/ && !done_pw {
|
|
match($0, /^[[:space:]]+/); print substr($0, RSTART, RLENGTH) "password: " pw; done_pw=1; next
|
|
}
|
|
{ print }
|
|
END { exit (done_pw ? 0 : 1) }
|
|
' "$yaml" > "$tmp"; then
|
|
rm -f "$tmp"
|
|
echo "libreportal-appcfg: AdGuardHome.yaml has no users password line" >&2
|
|
return 1
|
|
fi
|
|
cp "$tmp" "$yaml" # overwrite content, preserve the file's existing owner
|
|
rm -f "$tmp"
|
|
}
|
|
|
|
# --- WireGuard: enable IPv4 ip_forward via a sysctl drop-in --------------------
|
|
# The container needs the host kernel to forward packets between WG and the LAN.
|
|
# Lays down a conventional /etc/sysctl.d drop-in (idempotent overwrite) and asks
|
|
# the kernel to reload — avoids the legacy `/etc/sysctl/99-custom.conf` path
|
|
# (non-standard, may not exist) the old wireguard.sh edited via blanket sudo.
|
|
wireguard_ip_forward() {
|
|
local dropin="/etc/sysctl.d/99-libreportal-wireguard.conf"
|
|
cat > "$dropin" <<'EOF'
|
|
# Enable IPv4 forwarding for the LibrePortal WireGuard container.
|
|
# Managed by libreportal-appcfg wireguard-ip-forward.
|
|
net.ipv4.ip_forward=1
|
|
EOF
|
|
chmod 0644 "$dropin"
|
|
sysctl --system >/dev/null 2>&1 || sysctl -p "$dropin" >/dev/null 2>&1 || true
|
|
}
|
|
|
|
# --- ownCloud: normalise trusted_domains + overwrite.cli.url in config.php ------
|
|
owncloud_config() {
|
|
local public="$1" host_setup="$2" ip_setup="$3" public_ip="$4"
|
|
[[ "$public" =~ ^(true|false)$ ]] || { echo "libreportal-appcfg: public must be true|false" >&2; return 1; }
|
|
local v
|
|
for v in "$host_setup" "$ip_setup" "$public_ip"; do
|
|
[[ -z "$v" || "$v" =~ ^[A-Za-z0-9._:-]+$ ]] || { echo "libreportal-appcfg: invalid host/ip arg" >&2; return 1; }
|
|
done
|
|
local cfg_folder="$CONTAINERS_DIR/owncloud/files/config"
|
|
local cfg="$cfg_folder/config.php"
|
|
[[ -d "$cfg_folder" ]] || { echo "libreportal-appcfg: $cfg_folder not found" >&2; return 1; }
|
|
|
|
# ownCloud writes its real config.php after first boot. Drop the default and
|
|
# wait (as root — these files are owned by the in-container uid) for the
|
|
# objectstore + a non-empty config.php to appear before transforming.
|
|
rm -f "$cfg"
|
|
local i
|
|
for i in $(seq 1 60); do [[ -f "$cfg_folder/objectstore.config.php" ]] && break; sleep 5; done
|
|
for i in $(seq 1 60); do [[ -f "$cfg" && -s "$cfg" ]] && break; sleep 5; done
|
|
[[ -f "$cfg" && -s "$cfg" ]] || { echo "libreportal-appcfg: config.php never appeared" >&2; return 1; }
|
|
|
|
local tmp; tmp=$(mktemp -d)
|
|
local out="$tmp/config.php.new"
|
|
cp "$cfg" "$cfg_folder/config.php.backup"
|
|
awk '/'"'"'trusted_domains'"'"'/,/\),/{next} {print}' "$cfg" > "$out"
|
|
sed -i '/overwrite\.cli\.url/d' "$out"
|
|
sed -i '$s/);//' "$out"
|
|
sed -i '/^ *$/d' "$out"
|
|
if [[ "$public" == "true" ]]; then
|
|
cat >> "$out" <<EOL
|
|
'overwrite.cli.url' => 'https://$host_setup/',
|
|
'Overwriteprotocol' => 'https',
|
|
'trusted_domains' =>
|
|
array(
|
|
0 => '$host_setup',
|
|
1 => '$ip_setup',
|
|
2 => '$public_ip',
|
|
),
|
|
);
|
|
|
|
EOL
|
|
else
|
|
cat >> "$out" <<EOL
|
|
'trusted_domains' =>
|
|
array(
|
|
0 => '$ip_setup',
|
|
),
|
|
);
|
|
|
|
EOL
|
|
fi
|
|
mv "$out" "$cfg"
|
|
chown "165568:$(_install_user)" "$cfg"
|
|
rm -rf "$tmp"
|
|
}
|
|
|
|
action="${1:-}"; shift 2>/dev/null || true
|
|
case "$action" in
|
|
adguard-auth) adguard_auth "${1:-}" "${2:-}" ;;
|
|
owncloud-config) owncloud_config "${1:-}" "${2:-}" "${3:-}" "${4:-}" ;;
|
|
wireguard-ip-forward) wireguard_ip_forward ;;
|
|
*) echo "usage: libreportal-appcfg {adguard-auth <user> <bcrypt>|owncloud-config <public> <host> <ip> <public_ip>|wireguard-ip-forward}" >&2; exit 2 ;;
|
|
esac
|