Compare commits

..

No commits in common. "d2a506a4910ee3187459b185c296968bac0c546f" and "10dc7d0bc0e571af26bcc9b7b91fb60d8006ce6d" have entirely different histories.

7 changed files with 145 additions and 289 deletions

View File

@ -8,8 +8,8 @@ appCrowdSecFixPriority() {
fi fi
# The bouncer yaml is root-owned under /etc/crowdsec; the backup + nftables # The bouncer yaml is root-owned under /etc/crowdsec; the backup + nftables
# ipv4/ipv6 priority rewrite (to -100) runs in the root-owned crowdsec helper. # ipv4/ipv6 priority rewrite (to -100) runs in the root-owned appcfg helper.
runCrowdsec bouncer-priority runAppCfg crowdsec-priority
checkSuccess "Patched nftables priority to -100 in $cfg" checkSuccess "Patched nftables priority to -100 in $cfg"
runSystem systemctl restart crowdsec-firewall-bouncer runSystem systemctl restart crowdsec-firewall-bouncer

View File

@ -10,14 +10,12 @@ crowdsecToggleLibrePortalLogMounts() {
case "$mode" in case "$mode" in
on) on)
# Lines inside the marker block that look like `#-/var/log/crowdsec...:` sudo sed -i '/# >>> crowdsec-host-logs >>>/,/# <<< crowdsec-host-logs <<</ {
# become `-/var/log/...:` so docker compose picks up the bind-mounts.
runFileOp sed -i '/# >>> crowdsec-host-logs >>>/,/# <<< crowdsec-host-logs <<</ {
/crowdsec.*\.log:/ s/^\([[:space:]]*\)#-/\1-/ /crowdsec.*\.log:/ s/^\([[:space:]]*\)#-/\1-/
}' "$compose" }' "$compose"
;; ;;
off) off)
runFileOp sed -i '/# >>> crowdsec-host-logs >>>/,/# <<< crowdsec-host-logs <<</ { sudo sed -i '/# >>> crowdsec-host-logs >>>/,/# <<< crowdsec-host-logs <<</ {
/crowdsec.*\.log:/ s/^\([[:space:]]*\)-/\1#-/ /crowdsec.*\.log:/ s/^\([[:space:]]*\)-/\1#-/
}' "$compose" }' "$compose"
;; ;;
@ -32,9 +30,12 @@ crowdsecToggleLibrePortalLogMounts() {
installCrowdsecHost() installCrowdsecHost()
{ {
# Make /var/log/crowdsec*.log world-readable so the libreportal container # Touch + chmod 0644 the host log files so the libreportal container
# (UID 1001) can tail them via the bind-mount we're about to enable. # (UID 1001) can read them. Then enable the bind-mounts.
runCrowdsec touch-host-logs for _l in /var/log/crowdsec.log /var/log/crowdsec-firewall-bouncer.log; do
sudo touch "$_l" 2>/dev/null || true
sudo chmod 0644 "$_l" 2>/dev/null || true
done
crowdsecToggleLibrePortalLogMounts on crowdsecToggleLibrePortalLogMounts on
local desired_state="${CFG_CROWDSEC_ENABLED:-true}" local desired_state="${CFG_CROWDSEC_ENABLED:-true}"
@ -46,16 +47,53 @@ installCrowdsecHost()
((menu_number++)) ((menu_number++))
echo "" echo ""
echo "---- $menu_number. Installing the CrowdSec agent + firewall bouncer." echo "---- $menu_number. Adding the CrowdSec apt repository."
echo ""
local result=$(curl -fsSL https://install.crowdsec.net | sudo bash)
checkSuccess "Adding CrowdSec repository"
((menu_number++))
echo ""
echo "---- $menu_number. Installing the CrowdSec agent."
echo "" echo ""
isNotice "First-time install ~30-70 MB GeoLite2 DB + parser hub, 1-3 mins." isNotice "First-time install ~30-70 MB GeoLite2 DB + parser hub, 1-3 mins."
# One-shot: adds the apt repo, installs both packages, enables both local result=$(sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -q crowdsec </dev/null 2>&1)
# services, installs the crowdsecurity/linux + /sshd collections, then checkSuccess "Installing CrowdSec package"
# reloads the agent. All of it lives in libreportal-crowdsec so the
# manager never needs `sudo apt-get` / `sudo bash`. ((menu_number++))
local result=$(runCrowdsec install) echo ""
checkSuccess "Installing CrowdSec agent + firewall bouncer + baseline collections" echo "---- $menu_number. Installing the CrowdSec firewall bouncer."
echo ""
local result=$(sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -q crowdsec-firewall-bouncer-nftables </dev/null 2>&1)
checkSuccess "Installing CrowdSec firewall bouncer (nftables)"
((menu_number++))
echo ""
echo "---- $menu_number. Enabling CrowdSec services."
echo ""
local result=$(runSystem systemctl enable --now crowdsec)
checkSuccess "Enabling CrowdSec agent"
local result=$(runSystem systemctl enable --now crowdsec-firewall-bouncer)
checkSuccess "Enabling CrowdSec firewall bouncer"
((menu_number++))
echo ""
echo "---- $menu_number. Installing baseline collections."
echo ""
local result=$(runSystem cscli collections install crowdsecurity/linux)
checkSuccess "Installing crowdsecurity/linux collection"
local result=$(runSystem cscli collections install crowdsecurity/sshd)
checkSuccess "Installing crowdsecurity/sshd collection"
local result=$(runSystem systemctl reload crowdsec)
checkSuccess "Reloading CrowdSec to pick up collections"
((menu_number++)) ((menu_number++))
echo "" echo ""
@ -68,14 +106,14 @@ installCrowdsecHost()
# unregistering. Idempotent on either branch. # unregistering. Idempotent on either branch.
local community_blocklist="${CFG_CROWDSEC_COMMUNITY_BLOCKLIST:-true}" local community_blocklist="${CFG_CROWDSEC_COMMUNITY_BLOCKLIST:-true}"
if [[ "$community_blocklist" == "true" ]]; then if [[ "$community_blocklist" == "true" ]]; then
if runCrowdsec capi status 2>&1 | grep -qi 'You can successfully'; then if runSystem cscli capi status 2>&1 | grep -qi 'You can successfully'; then
isNotice "Community blocklist already registered." isNotice "Community blocklist already registered."
else else
local result=$(runCrowdsec capi register 2>&1) local result=$(runSystem cscli capi register 2>&1)
checkSuccess "Registered with CrowdSec Central API (community blocklist)" checkSuccess "Registered with CrowdSec Central API (community blocklist)"
fi fi
else else
local result=$(runCrowdsec capi unregister 2>&1) local result=$(runSystem cscli capi unregister 2>&1)
checkSuccess "Unregistered from CrowdSec Central API (community blocklist disabled)" checkSuccess "Unregistered from CrowdSec Central API (community blocklist disabled)"
fi fi
@ -84,26 +122,26 @@ installCrowdsecHost()
echo "---- $menu_number. SaaS Console enrollment toggle." echo "---- $menu_number. SaaS Console enrollment toggle."
echo "" echo ""
# `console enroll <token>` registers this agent with the hosted # cscli console enroll <token> registers this agent with the hosted
# dashboard at app.crowdsec.net. Idempotent: if already enrolled, # dashboard at app.crowdsec.net. Idempotent: if already enrolled,
# skip. If toggled off, disenroll. Quietly skipped when the flag # skip. If toggled off, disenroll. Quietly skipped when the flag
# is on but the token field is empty (user hasn't pasted one yet). # is on but the token field is empty (user hasn't pasted one yet).
local console_enroll="${CFG_CROWDSEC_CONSOLE_ENROLL:-false}" local console_enroll="${CFG_CROWDSEC_CONSOLE_ENROLL:-false}"
local console_token="${CFG_CROWDSEC_CONSOLE_TOKEN:-}" local console_token="${CFG_CROWDSEC_CONSOLE_TOKEN:-}"
local enrolled=false local enrolled=false
runCrowdsec console status 2>&1 | grep -qi 'enrolled' && enrolled=true runSystem cscli console status 2>&1 | grep -qi 'enrolled' && enrolled=true
if [[ "$console_enroll" == "true" ]]; then if [[ "$console_enroll" == "true" ]]; then
if [[ -z "$console_token" ]]; then if [[ -z "$console_token" ]]; then
isNotice "Console enrollment ON but CFG_CROWDSEC_CONSOLE_TOKEN is empty — paste your token from app.crowdsec.net to complete." isNotice "Console enrollment ON but CFG_CROWDSEC_CONSOLE_TOKEN is empty — paste your token from app.crowdsec.net to complete."
elif [[ "$enrolled" == true ]]; then elif [[ "$enrolled" == true ]]; then
isNotice "Already enrolled with the SaaS console — skipping." isNotice "Already enrolled with the SaaS console — skipping."
else else
local result=$(runCrowdsec console enroll "$console_token" 2>&1) local result=$(runSystem cscli console enroll "$console_token" 2>&1)
checkSuccess "Enrolled with app.crowdsec.net SaaS console" checkSuccess "Enrolled with app.crowdsec.net SaaS console"
fi fi
else else
if [[ "$enrolled" == true ]]; then if [[ "$enrolled" == true ]]; then
local result=$(runCrowdsec console disenroll 2>&1) local result=$(runSystem cscli console disenroll 2>&1)
checkSuccess "Disenrolled from app.crowdsec.net SaaS console" checkSuccess "Disenrolled from app.crowdsec.net SaaS console"
else else
isNotice "SaaS console enrollment disabled — skipping." isNotice "SaaS console enrollment disabled — skipping."
@ -119,14 +157,14 @@ installCrowdsecHost()
# via host.docker.internal:host-gateway. The bouncer API key is # via host.docker.internal:host-gateway. The bouncer API key is
# required (HTTP 401 without it), so internet exposure is gated. # required (HTTP 401 without it), so internet exposure is gated.
# External access on 8080 should still be blocked at UFW. # External access on 8080 should still be blocked at UFW.
local bind_result local lapi_cfg="/etc/crowdsec/config.yaml"
bind_result=$(runCrowdsec bind-lapi 2>&1) if [[ -f "$lapi_cfg" ]] && ! sudo grep -qE 'listen_uri:[[:space:]]*0\.0\.0\.0:8080' "$lapi_cfg"; then
if [[ "$bind_result" == "ALREADY_BOUND" ]]; then sudo sed -i 's|listen_uri:.*|listen_uri: 0.0.0.0:8080|' "$lapi_cfg"
isNotice "LAPI already bound to 0.0.0.0:8080 — skipping."
else
checkSuccess "LAPI bound to 0.0.0.0:8080" checkSuccess "LAPI bound to 0.0.0.0:8080"
runCrowdsec services restart runSystem systemctl restart crowdsec
checkSuccess "CrowdSec restarted" checkSuccess "CrowdSec restarted"
else
isNotice "LAPI already bound to 0.0.0.0:8080 — skipping."
fi fi
((menu_number++)) ((menu_number++))
@ -136,62 +174,72 @@ installCrowdsecHost()
# When monitoring is on, bind CrowdSec's Prometheus metrics endpoint to # When monitoring is on, bind CrowdSec's Prometheus metrics endpoint to
# a docker-reachable address (the 127.0.0.1 default can't be scraped # a docker-reachable address (the 127.0.0.1 default can't be scraped
# from the Prometheus container). When off, rebind to localhost if a # from the Prometheus container). The sed is scoped to the prometheus:
# prior run opened it. The helper does the scoped edit in the # block. When off, rebind to localhost if a prior run opened it.
# prometheus: block. local cs_cfg="/etc/crowdsec/config.yaml"
local mon_enabled="${CFG_CROWDSEC_MONITORING:-false}" local mon_enabled="${CFG_CROWDSEC_MONITORING:-false}"
local prom_listen="${CFG_CROWDSEC_PROMETHEUS_LISTEN:-0.0.0.0:6060}" local prom_listen="${CFG_CROWDSEC_PROMETHEUS_LISTEN:-0.0.0.0:6060}"
local prom_addr="${prom_listen%%:*}" local prom_addr="${prom_listen%%:*}"
local prom_port="${prom_listen##*:}" local prom_port="${prom_listen##*:}"
if [[ "$mon_enabled" == "true" ]]; then if [[ "$mon_enabled" == "true" && -f "$cs_cfg" ]]; then
local result=$(runCrowdsec prometheus on "$prom_addr" "$prom_port") if ! sudo grep -qE "listen_addr:[[:space:]]*${prom_addr}" "$cs_cfg"; then
sudo sed -i "/^prometheus:/,/^[^[:space:]#]/ {
s|enabled:.*|enabled: true|
s|listen_addr:.*|listen_addr: ${prom_addr}|
s|listen_port:.*|listen_port: ${prom_port}|
}" "$cs_cfg"
checkSuccess "CrowdSec metrics endpoint bound to ${prom_listen}" checkSuccess "CrowdSec metrics endpoint bound to ${prom_listen}"
runCrowdsec services restart runSystem systemctl restart crowdsec
checkSuccess "CrowdSec restarted" checkSuccess "CrowdSec restarted"
else else
local result=$(runCrowdsec prometheus off) isNotice "CrowdSec metrics already bound to ${prom_addr} — skipping."
fi
elif [[ -f "$cs_cfg" ]] && sudo grep -qE 'listen_addr:[[:space:]]*0\.0\.0\.0' "$cs_cfg"; then
sudo sed -i "/^prometheus:/,/^[^[:space:]#]/ s|listen_addr:.*|listen_addr: 127.0.0.1|" "$cs_cfg"
checkSuccess "CrowdSec metrics endpoint rebound to 127.0.0.1 (monitoring off)" checkSuccess "CrowdSec metrics endpoint rebound to 127.0.0.1 (monitoring off)"
runCrowdsec services restart runSystem systemctl restart crowdsec
checkSuccess "CrowdSec restarted" checkSuccess "CrowdSec restarted"
else
isNotice "Monitoring off — CrowdSec metrics endpoint left at its default."
fi fi
((menu_number++)) # Generate a dedicated bouncer key for Traefik (idempotent: skip if
echo "" # already registered). Two sinks for the value:
echo "---- $menu_number. Traefik bouncer API key."
echo ""
# Generate a dedicated bouncer key for Traefik. Two sinks for the value:
# 1) /etc/crowdsec/traefik_bouncer.key — raw key, bind-mounted into # 1) /etc/crowdsec/traefik_bouncer.key — raw key, bind-mounted into
# the Traefik container read-only; the plugin reads it via # the Traefik container read-only; the plugin reads it via
# crowdsecLapiKeyFile. /etc/crowdsec/ is outside the framework's # crowdsecLapiKeyFile. /etc/crowdsec/ is outside the framework's
# sourceScanFiles sweep so a bare key file is safe here. # sourceScanFiles sweep so a bare key file is safe here.
# 2) ${configs_dir}security/security_crowdsec — CFG_CROWDSEC_TRAEFIK_LAPI_KEY # 2) /docker/configs/network/network_crowdsec — CFG_CROWDSEC_TRAEFIK_LAPI_KEY
# line, sourced by the framework and visible on the config page. # line, sourced by the framework and visible on the config page.
# Editing the CFG var manually does not re-register the bouncer # Editing the CFG var manually does not re-register the bouncer
# (use the rotate Tools action for that); this is a visibility # (use the rotate Tools action for that); this is a visibility
# surface, not the auth source of truth. # surface, not the auth source of truth.
# The helper handles cscli + tee + chown + chmod atomically. local key_file="/etc/crowdsec/traefik_bouncer.key"
local init_result local cfg_file="${configs_dir}security/security_crowdsec"
init_result=$(runCrowdsec bouncer-traefik-init 2>&1)
if [[ "$init_result" == "EXISTS" ]]; then if ! runSystem cscli bouncers list -o raw 2>/dev/null | grep -q '^traefik-bouncer'; then
isNotice "Bouncer 'traefik-bouncer' already registered — leaving existing key file untouched at /etc/crowdsec/traefik_bouncer.key." local bouncer_key
elif [[ "$init_result" == GENERATED:* ]]; then bouncer_key=$(runSystem cscli bouncers add traefik-bouncer -o raw 2>&1 | tail -1)
local bouncer_key="${init_result#GENERATED:}" if [[ -n "$bouncer_key" && "$bouncer_key" != *"error"* ]]; then
echo "$bouncer_key" | sudo tee "$key_file" >/dev/null
sudo chown "$sudo_user_name:$sudo_user_name" "$key_file"
sudo chmod 0600 "$key_file"
checkSuccess "Traefik bouncer API key generated" checkSuccess "Traefik bouncer API key generated"
# Mirror the key into the live config file so it's visible / # Write the key into the live config file so it's visible /
# editable via the framework's config page like any other CFG_* # editable via the framework's config page like any other
# setting. configs/ is manager-owned, so runInstallOp suffices. # CFG_* setting.
local cfg_file="${configs_dir}security/security_crowdsec"
if [[ -f "$cfg_file" ]]; then if [[ -f "$cfg_file" ]]; then
runInstallOp sed -i "s|^CFG_CROWDSEC_TRAEFIK_LAPI_KEY=.*|CFG_CROWDSEC_TRAEFIK_LAPI_KEY=${bouncer_key}|" "$cfg_file" sudo sed -i "s|^CFG_CROWDSEC_TRAEFIK_LAPI_KEY=.*|CFG_CROWDSEC_TRAEFIK_LAPI_KEY=${bouncer_key}|" "$cfg_file"
checkSuccess "Key mirrored to CFG_CROWDSEC_TRAEFIK_LAPI_KEY" checkSuccess "Key mirrored to CFG_CROWDSEC_TRAEFIK_LAPI_KEY"
else else
isNotice "Live config not present yet — key applied on next install." isNotice "Live config not present yet — key applied on next install."
fi fi
else else
isNotice "Failed to generate bouncer key: $init_result" isNotice "Failed to generate bouncer key — Traefik integration won't authenticate. Re-run installCrowdsecHost to retry."
isNotice "Traefik integration won't authenticate. Re-run installCrowdsecHost to retry." fi
else
isNotice "Bouncer 'traefik-bouncer' already registered — leaving existing key file untouched at $key_file."
fi fi
((menu_number++)) ((menu_number++))
@ -243,8 +291,11 @@ installCrowdsecHost()
echo "---- $menu_number. Re-enabling CrowdSec services." echo "---- $menu_number. Re-enabling CrowdSec services."
echo "" echo ""
local result=$(runCrowdsec services enable) local result=$(runSystem systemctl enable --now crowdsec)
checkSuccess "Enabling CrowdSec agent + firewall bouncer" checkSuccess "Enabling CrowdSec agent"
local result=$(runSystem systemctl enable --now crowdsec-firewall-bouncer)
checkSuccess "Enabling CrowdSec firewall bouncer"
isSuccessful "CrowdSec services re-enabled." isSuccessful "CrowdSec services re-enabled."
menu_number=0 menu_number=0
@ -261,8 +312,11 @@ installCrowdsecHost()
echo "---- $menu_number. Stopping and disabling CrowdSec services." echo "---- $menu_number. Stopping and disabling CrowdSec services."
echo "" echo ""
local result=$(runCrowdsec services disable) local result=$(runSystem systemctl disable --now crowdsec-firewall-bouncer 2>&1)
checkSuccess "Disabling CrowdSec agent + firewall bouncer" checkSuccess "Disabling CrowdSec firewall bouncer"
local result=$(runSystem systemctl disable --now crowdsec 2>&1)
checkSuccess "Disabling CrowdSec agent"
isSuccessful "CrowdSec disabled. Package remains installed — set CFG_CROWDSEC_ENABLED=true to re-enable, or uninstall via the Tools tab." isSuccessful "CrowdSec disabled. Package remains installed — set CFG_CROWDSEC_ENABLED=true to re-enable, or uninstall via the Tools tab."
menu_number=0 menu_number=0

View File

@ -892,8 +892,7 @@ Cmnd_Alias LP_HELPERS = ${lp_lib_dir}/libreportal-ownership, \\
${lp_lib_dir}/libreportal-socket, \\ ${lp_lib_dir}/libreportal-socket, \\
${lp_lib_dir}/libreportal-svc, \\ ${lp_lib_dir}/libreportal-svc, \\
${lp_lib_dir}/libreportal-bininstall, \\ ${lp_lib_dir}/libreportal-bininstall, \\
${lp_lib_dir}/libreportal-appcfg, \\ ${lp_lib_dir}/libreportal-appcfg
${lp_lib_dir}/libreportal-crowdsec
Cmnd_Alias LP_SYSTEM = /usr/bin/systemctl, /usr/sbin/ufw, /usr/local/bin/ufw-docker, \\ Cmnd_Alias LP_SYSTEM = /usr/bin/systemctl, /usr/sbin/ufw, /usr/local/bin/ufw-docker, \\
/usr/sbin/nft, /usr/sbin/sysctl, /sbin/sysctl, \\ /usr/sbin/nft, /usr/sbin/sysctl, /sbin/sysctl, \\
/usr/bin/loginctl, /usr/sbin/service /usr/bin/loginctl, /usr/sbin/service
@ -933,7 +932,7 @@ initRootHelpers()
# sudo's (the trust boundary the scoped sudoers relies on). # sudo's (the trust boundary the scoped sudoers relies on).
sudo install -d -m 0755 -o root -g root "$lp_lib_dir" sudo install -d -m 0755 -o root -g root "$lp_lib_dir"
local helper helper_src helper_dst helper_tmp local helper helper_src helper_dst helper_tmp
for helper in libreportal-ownership libreportal-dns libreportal-ssh-access libreportal-socket libreportal-svc libreportal-bininstall libreportal-appcfg libreportal-crowdsec; do for helper in libreportal-ownership libreportal-dns libreportal-ssh-access libreportal-socket libreportal-svc libreportal-bininstall libreportal-appcfg; do
helper_src="$script_dir/scripts/system/$helper" helper_src="$script_dir/scripts/system/$helper"
helper_dst="$lp_lib_dir/$helper" helper_dst="$lp_lib_dir/$helper"
if [[ ! -f "$helper_src" ]]; then if [[ ! -f "$helper_src" ]]; then

View File

@ -136,19 +136,10 @@ runSvc() { _runRootHelper libreportal-svc "$@"; }
runBinInstall() { _runRootHelper libreportal-bininstall "$@"; } runBinInstall() { _runRootHelper libreportal-bininstall "$@"; }
# App config-file rewrites owned by in-container uids / root /etc: # App config-file rewrites owned by in-container uids / root /etc:
# {adguard-auth <user> <bcrypt>|owncloud-config <public> <host> <ip> <public_ip>| # {adguard-auth <user> <bcrypt>|crowdsec-priority|
# wireguard-ip-forward} # owncloud-config <public> <host> <ip> <public_ip>}
runAppCfg() { _runRootHelper libreportal-appcfg "$@"; } runAppCfg() { _runRootHelper libreportal-appcfg "$@"; }
# CrowdSec host-side privileged ops — apt install of the agent + firewall
# bouncer, cscli register/enroll, /etc/crowdsec/* edits, /var/log/crowdsec*.log
# touch+chmod, /etc/crowdsec/traefik_bouncer.key write. One audit funnel for
# every operation the host-side CrowdSec install needs the manager can't drop:
# {install|services <enable|disable|restart>|capi <register|unregister|status>
# |console <enroll <token>|disenroll|status>|bouncer-traefik-init|
# bouncer-priority|bind-lapi|prometheus <on <addr> <port>|off>|touch-host-logs}
runCrowdsec() { _runRootHelper libreportal-crowdsec "$@"; }
# Genuine system-administration command (ufw/systemctl/apt/sysctl/useradd, /etc # Genuine system-administration command (ufw/systemctl/apt/sysctl/useradd, /etc
# edits). Needs real root in both modes; funnelled through one place so it can # edits). Needs real root in both modes; funnelled through one place so it can
# later be confined to a scoped sudoers allowlist. # later be confined to a scoped sudoers allowlist.

View File

@ -720,7 +720,6 @@ declare -gA LP_FN_MAP=(
[runAsManager]="docker/command/run_privileged.sh" [runAsManager]="docker/command/run_privileged.sh"
[runBackupOp]="docker/command/run_privileged.sh" [runBackupOp]="docker/command/run_privileged.sh"
[runBinInstall]="docker/command/run_privileged.sh" [runBinInstall]="docker/command/run_privileged.sh"
[runCrowdsec]="docker/command/run_privileged.sh"
[runFileOp]="docker/command/run_privileged.sh" [runFileOp]="docker/command/run_privileged.sh"
[runFileWrite]="docker/command/run_privileged.sh" [runFileWrite]="docker/command/run_privileged.sh"
[runInstallOp]="docker/command/run_privileged.sh" [runInstallOp]="docker/command/run_privileged.sh"
@ -1583,7 +1582,6 @@ declare -gA LP_FN_ROOT=(
[runAsManager]="scripts" [runAsManager]="scripts"
[runBackupOp]="scripts" [runBackupOp]="scripts"
[runBinInstall]="scripts" [runBinInstall]="scripts"
[runCrowdsec]="scripts"
[runFileOp]="scripts" [runFileOp]="scripts"
[runFileWrite]="scripts" [runFileWrite]="scripts"
[runInstallOp]="scripts" [runInstallOp]="scripts"
@ -2464,7 +2462,6 @@ runAppCfg() { source "${install_scripts_dir}docker/command/run_privileged.sh"; r
runAsManager() { source "${install_scripts_dir}docker/command/run_privileged.sh"; runAsManager "$@"; } runAsManager() { source "${install_scripts_dir}docker/command/run_privileged.sh"; runAsManager "$@"; }
runBackupOp() { source "${install_scripts_dir}docker/command/run_privileged.sh"; runBackupOp "$@"; } runBackupOp() { source "${install_scripts_dir}docker/command/run_privileged.sh"; runBackupOp "$@"; }
runBinInstall() { source "${install_scripts_dir}docker/command/run_privileged.sh"; runBinInstall "$@"; } runBinInstall() { source "${install_scripts_dir}docker/command/run_privileged.sh"; runBinInstall "$@"; }
runCrowdsec() { source "${install_scripts_dir}docker/command/run_privileged.sh"; runCrowdsec "$@"; }
runFileOp() { source "${install_scripts_dir}docker/command/run_privileged.sh"; runFileOp "$@"; } runFileOp() { source "${install_scripts_dir}docker/command/run_privileged.sh"; runFileOp "$@"; }
runFileWrite() { source "${install_scripts_dir}docker/command/run_privileged.sh"; runFileWrite "$@"; } runFileWrite() { source "${install_scripts_dir}docker/command/run_privileged.sh"; runFileWrite "$@"; }
runInstallOp() { source "${install_scripts_dir}docker/command/run_privileged.sh"; runInstallOp "$@"; } runInstallOp() { source "${install_scripts_dir}docker/command/run_privileged.sh"; runInstallOp "$@"; }

View File

@ -68,6 +68,22 @@ EOF
sysctl --system >/dev/null 2>&1 || sysctl -p "$dropin" >/dev/null 2>&1 || true sysctl --system >/dev/null 2>&1 || sysctl -p "$dropin" >/dev/null 2>&1 || true
} }
# --- CrowdSec: set nftables ipv4/ipv6 priority to -100 in the bouncer yaml ------
crowdsec_priority() {
local cfg="/etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml"
[[ -f "$cfg" ]] || { echo "libreportal-appcfg: $cfg not found" >&2; return 1; }
cp "$cfg" "${cfg}.bak.$(date +%Y%m%d-%H%M%S)"
awk -v p="-100" '
BEGIN { in_v4=0; in_v6=0 }
/^[[:space:]]*ipv4:/ { in_v4=1; in_v6=0; print; next }
/^[[:space:]]*ipv6:/ { in_v6=1; in_v4=0; print; next }
/^[a-zA-Z]/ { in_v4=0; in_v6=0 }
in_v4 && /^[[:space:]]+priority:/ { sub(/priority:.*/, "priority: " p) }
in_v6 && /^[[:space:]]+priority:/ { sub(/priority:.*/, "priority: " p) }
{ print }
' "$cfg" > "${cfg}.new" && mv "${cfg}.new" "$cfg"
}
# --- ownCloud: normalise trusted_domains + overwrite.cli.url in config.php ------ # --- ownCloud: normalise trusted_domains + overwrite.cli.url in config.php ------
owncloud_config() { owncloud_config() {
local public="$1" host_setup="$2" ip_setup="$3" public_ip="$4" local public="$1" host_setup="$2" ip_setup="$3" public_ip="$4"
@ -127,7 +143,8 @@ EOL
action="${1:-}"; shift 2>/dev/null || true action="${1:-}"; shift 2>/dev/null || true
case "$action" in case "$action" in
adguard-auth) adguard_auth "${1:-}" "${2:-}" ;; adguard-auth) adguard_auth "${1:-}" "${2:-}" ;;
crowdsec-priority) crowdsec_priority ;;
owncloud-config) owncloud_config "${1:-}" "${2:-}" "${3:-}" "${4:-}" ;; owncloud-config) owncloud_config "${1:-}" "${2:-}" "${3:-}" "${4:-}" ;;
wireguard-ip-forward) wireguard_ip_forward ;; 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 ;; *) echo "usage: libreportal-appcfg {adguard-auth <user> <bcrypt>|crowdsec-priority|owncloud-config <public> <host> <ip> <public_ip>|wireguard-ip-forward}" >&2; exit 2 ;;
esac esac

View File

@ -1,202 +0,0 @@
#!/bin/bash
# LibrePortal CrowdSec helper — root-privileged ops for the host-side CrowdSec
# install (the agent + firewall bouncer the LibrePortal Traefik bouncer talks
# to). Installed root:root 0755 to /usr/local/lib/libreportal/ by init.sh.
# Self-contained: each action runs a FIXED set of ops with strictly-validated
# args, so the scoped sudoers needn't grant the manager blanket apt/cscli/tee/
# sed/chown on /etc/crowdsec or /var/log. The runtime calls in via
# `runCrowdsec <action> [args…]` (run_privileged.sh).
set -u
[[ $EUID -eq 0 ]] || { echo "libreportal-crowdsec: must run as root" >&2; exit 1; }
# Baked at install; unbaked copies keep the "__" sentinel.
SYSTEM_DIR="__SYSTEM_DIR__"
CONTAINERS_DIR="__CONTAINERS_DIR__"
MANAGER_USER="__MANAGER__"
[[ "$SYSTEM_DIR" == *"__"* || -z "$SYSTEM_DIR" ]] && SYSTEM_DIR="/libreportal-system"
[[ "$CONTAINERS_DIR" == *"__"* || -z "$CONTAINERS_DIR" ]] && CONTAINERS_DIR="/libreportal-containers"
[[ "$MANAGER_USER" == *"__"* || -z "$MANAGER_USER" ]] && MANAGER_USER="libreportal"
CFG_FILE="/etc/crowdsec/config.yaml"
BOUNCER_CFG="/etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml"
TRAEFIK_KEY_FILE="/etc/crowdsec/traefik_bouncer.key"
HOST_LOGS=("/var/log/crowdsec.log" "/var/log/crowdsec-firewall-bouncer.log")
# --- install: add repo + apt install + initial config -------------------------
# Idempotent: skips repo add if /etc/apt/sources.list.d/crowdsec_*.list exists,
# skips apt install if cscli is on PATH, skips collections install if already
# present.
crowdsec_install() {
if ! command -v cscli >/dev/null 2>&1; then
if ! ls /etc/apt/sources.list.d/crowdsec_*.list >/dev/null 2>&1; then
curl -fsSL https://install.crowdsec.net | bash >/dev/null 2>&1 || {
echo "libreportal-crowdsec: failed to add CrowdSec apt repo" >&2; return 1
}
fi
DEBIAN_FRONTEND=noninteractive apt-get install -y -q crowdsec </dev/null >/dev/null 2>&1 || {
echo "libreportal-crowdsec: apt-get install crowdsec failed" >&2; return 1
}
fi
if ! dpkg -s crowdsec-firewall-bouncer-nftables >/dev/null 2>&1; then
DEBIAN_FRONTEND=noninteractive apt-get install -y -q crowdsec-firewall-bouncer-nftables </dev/null >/dev/null 2>&1 || {
echo "libreportal-crowdsec: apt-get install firewall-bouncer failed" >&2; return 1
}
fi
systemctl enable --now crowdsec >/dev/null 2>&1 || true
systemctl enable --now crowdsec-firewall-bouncer >/dev/null 2>&1 || true
# Baseline collections. cscli is idempotent ("already installed" exits 0).
cscli collections install crowdsecurity/linux >/dev/null 2>&1 || true
cscli collections install crowdsecurity/sshd >/dev/null 2>&1 || true
systemctl reload crowdsec >/dev/null 2>&1 || true
}
# --- services {enable|disable|restart} ----------------------------------------
crowdsec_services() {
case "${1:-}" in
enable)
systemctl enable --now crowdsec || return 1
systemctl enable --now crowdsec-firewall-bouncer || return 1
;;
disable)
systemctl disable --now crowdsec-firewall-bouncer >/dev/null 2>&1 || true
systemctl disable --now crowdsec >/dev/null 2>&1 || true
;;
restart)
systemctl restart crowdsec || return 1
;;
*) echo "libreportal-crowdsec: services {enable|disable|restart}" >&2; return 2 ;;
esac
}
# --- capi {register|unregister|status} ----------------------------------------
# Community blocklist (subscribe + send anonymous attack signals).
crowdsec_capi() {
case "${1:-}" in
register) cscli capi register 2>&1 ;;
unregister) cscli capi unregister 2>&1 ;;
status) cscli capi status 2>&1 ;;
*) echo "libreportal-crowdsec: capi {register|unregister|status}" >&2; return 2 ;;
esac
}
# --- console {enroll <token>|disenroll|status} --------------------------------
# SaaS dashboard at app.crowdsec.net. Token strictly validated.
crowdsec_console() {
case "${1:-}" in
enroll)
local token="${2:-}"
[[ "$token" =~ ^[A-Za-z0-9_-]{8,128}$ ]] || {
echo "libreportal-crowdsec: console enroll <token> — invalid token format" >&2
return 1
}
cscli console enroll "$token" 2>&1
;;
disenroll) cscli console disenroll 2>&1 ;;
status) cscli console status 2>&1 ;;
*) echo "libreportal-crowdsec: console {enroll <token>|disenroll|status}" >&2; return 2 ;;
esac
}
# --- bouncer-traefik-init: cscli register + write key file --------------------
# Idempotent: if the bouncer is already registered, skips and prints the existing
# key-file marker so callers can decide whether to keep or rotate. Returns the
# fresh key on stdout when newly generated, "EXISTS" when already registered.
crowdsec_bouncer_traefik_init() {
if cscli bouncers list -o raw 2>/dev/null | grep -q '^traefik-bouncer'; then
echo "EXISTS"
return 0
fi
local key
key=$(cscli bouncers add traefik-bouncer -o raw 2>&1 | tail -1)
[[ -n "$key" && "$key" != *"error"* ]] || {
echo "libreportal-crowdsec: cscli bouncers add failed: $key" >&2
return 1
}
# Write the key file: bind-mounted RO into Traefik. Manager-owned so the
# WebUI/config layer (running as the manager) can read it; mode 0600.
printf '%s\n' "$key" > "$TRAEFIK_KEY_FILE"
chown "${MANAGER_USER}:${MANAGER_USER}" "$TRAEFIK_KEY_FILE"
chmod 0600 "$TRAEFIK_KEY_FILE"
echo "GENERATED:$key"
}
# --- bind-lapi: set listen_uri to 0.0.0.0:8080 in config.yaml -----------------
# Traefik talks to LAPI via host.docker.internal:8080. Bouncer API key gates
# external access (HTTP 401 without it).
crowdsec_bind_lapi() {
[[ -f "$CFG_FILE" ]] || { echo "libreportal-crowdsec: $CFG_FILE not found" >&2; return 1; }
if grep -qE 'listen_uri:[[:space:]]*0\.0\.0\.0:8080' "$CFG_FILE"; then
echo "ALREADY_BOUND"; return 0
fi
sed -i 's|listen_uri:.*|listen_uri: 0.0.0.0:8080|' "$CFG_FILE"
}
# --- prometheus {on <addr> <port>|off} ----------------------------------------
# Flip CrowdSec's Prometheus metrics endpoint. `on` requires an addr + port the
# Prometheus container can reach; `off` rebinds to 127.0.0.1.
crowdsec_prometheus() {
[[ -f "$CFG_FILE" ]] || { echo "libreportal-crowdsec: $CFG_FILE not found" >&2; return 1; }
case "${1:-}" in
on)
local addr="${2:-}" port="${3:-}"
[[ "$addr" =~ ^[A-Za-z0-9.-]+$ ]] || { echo "libreportal-crowdsec: invalid addr" >&2; return 1; }
[[ "$port" =~ ^[0-9]+$ ]] || { echo "libreportal-crowdsec: invalid port" >&2; return 1; }
sed -i "/^prometheus:/,/^[^[:space:]#]/ {
s|enabled:.*|enabled: true|
s|listen_addr:.*|listen_addr: ${addr}|
s|listen_port:.*|listen_port: ${port}|
}" "$CFG_FILE"
;;
off)
sed -i "/^prometheus:/,/^[^[:space:]#]/ s|listen_addr:.*|listen_addr: 127.0.0.1|" "$CFG_FILE"
;;
*) echo "libreportal-crowdsec: prometheus {on <addr> <port>|off}" >&2; return 2 ;;
esac
}
# --- touch-host-logs: make crowdsec logs readable by the libreportal container --
crowdsec_touch_host_logs() {
local l
for l in "${HOST_LOGS[@]}"; do
touch "$l" 2>/dev/null || true
chmod 0644 "$l" 2>/dev/null || true
done
}
# --- bouncer-priority: set nftables ipv4/ipv6 priority to -100 -----------------
# Moved here from libreportal-appcfg.crowdsec_priority. Same transform; lives in
# the dedicated crowdsec helper so all CrowdSec-touching root ops are in one
# auditable file.
crowdsec_bouncer_priority() {
[[ -f "$BOUNCER_CFG" ]] || { echo "libreportal-crowdsec: $BOUNCER_CFG not found" >&2; return 1; }
cp "$BOUNCER_CFG" "${BOUNCER_CFG}.bak.$(date +%Y%m%d-%H%M%S)"
awk -v p="-100" '
BEGIN { in_v4=0; in_v6=0 }
/^[[:space:]]*ipv4:/ { in_v4=1; in_v6=0; print; next }
/^[[:space:]]*ipv6:/ { in_v6=1; in_v4=0; print; next }
/^[a-zA-Z]/ { in_v4=0; in_v6=0 }
in_v4 && /^[[:space:]]+priority:/ { sub(/priority:.*/, "priority: " p) }
in_v6 && /^[[:space:]]+priority:/ { sub(/priority:.*/, "priority: " p) }
{ print }
' "$BOUNCER_CFG" > "${BOUNCER_CFG}.new" && mv "${BOUNCER_CFG}.new" "$BOUNCER_CFG"
}
action="${1:-}"; shift 2>/dev/null || true
case "$action" in
install) crowdsec_install ;;
services) crowdsec_services "${1:-}" ;;
capi) crowdsec_capi "${1:-}" ;;
console) crowdsec_console "${1:-}" "${2:-}" ;;
bouncer-traefik-init) crowdsec_bouncer_traefik_init ;;
bouncer-priority) crowdsec_bouncer_priority ;;
bind-lapi) crowdsec_bind_lapi ;;
prometheus) crowdsec_prometheus "${1:-}" "${2:-}" "${3:-}" ;;
touch-host-logs) crowdsec_touch_host_logs ;;
*)
echo "usage: libreportal-crowdsec {install|services <enable|disable|restart>|capi <register|unregister|status>|console <enroll <token>|disenroll|status>|bouncer-traefik-init|bouncer-priority|bind-lapi|prometheus <on <addr> <port>|off>|touch-host-logs}" >&2
exit 2
;;
esac