diff --git a/services/asterisk-do.sh b/services/asterisk-do.sh index 006cdb0..7675dca 100755 --- a/services/asterisk-do.sh +++ b/services/asterisk-do.sh @@ -214,10 +214,30 @@ install_asterisk-do() { echo "[DRY-RUN] Would open 51820/udp (not 51821) if wg-easy was selected" echo "[DRY-RUN] Would offer to create a DigitalOcean Cloud Firewall via doctl" echo "[DRY-RUN] Would reverse-proxy the web admin on the SAME FQDN used for SIP (needed for cert sync)" + echo "[DRY-RUN] Would offer local OR remote Authelia to protect the web admin" echo "[DRY-RUN] Would offer to install CrowdSec if not already present (full repo only)" + echo "[DRY-RUN] Would offer to run base setup first if not already done (full repo only)" return 0 fi + # ── Bring in base first, if this is a genuinely fresh box ───────────────── + # Naming a service directly (sudo ./setup.sh asterisk-do) skips setup.sh's + # own first-run base step — essential packages, SSH key import, disabling + # password auth. That's a real gap on a fresh droplet: everything below + # still works without it, but the SSH-hardening part of this setup's + # security story wouldn't actually have happened. Same marker setup.sh + # itself uses to detect base (command -v ncdu). + if ! command -v ncdu &>/dev/null; then + if declare -F install_base &>/dev/null; then + local WANT_BASE="" + prompt_yn "Base setup not detected (essential packages, SSH hardening) — run it first? (y/n):" "y" WANT_BASE + [[ "$WANT_BASE" =~ ^[Yy]$ ]] && install_base + else + log_warning "Base setup not detected, and this looks like a standalone copy of asterisk-do.sh." + log_warning "Run services/base.sh yourself first, or grab the full repo." + fi + fi + # ── Swap file (insurance for low-RAM droplets, e.g. the $4/mo 512MB plan) ── # DigitalOcean doesn't provision swap by default. Docker + Asterisk + coturn # fit in 512MB-1GB at idle with little headroom; a swapfile absorbs spikes @@ -580,6 +600,31 @@ ENV # Disable built-in auth since Authelia handles it sed -i "s/^WEB_ADMIN_AUTH_DISABLED=.*/WEB_ADMIN_AUTH_DISABLED=true/" .env fi + else + # No local Authelia — offer one running elsewhere (e.g. a homelab). + # There's no shared "(authelia)" Caddy snippet to import in that + # case (authelia.sh only writes one when installing locally), so + # this builds the same forward_auth block inline, targeting the + # remote instance directly instead of the local "authelia:9091" + # container reference. + local _use_remote_auth="" + prompt_yn "Protect the web admin with a remote Authelia instance (e.g. on a homelab)? (y/n):" "n" _use_remote_auth + if [[ "$_use_remote_auth" =~ ^[Yy]$ ]]; then + local _remote_authelia="" + prompt_text " Remote Authelia address — a bare host:port over a private network (e.g. a NetBird mesh IP:9091), or a full https:// URL if it's on its own public domain+TLS:" "" _remote_authelia + if [[ -n "$_remote_authelia" ]]; then + EXTRA_BLOCK=" forward_auth ${_remote_authelia} { + uri /api/authz/forward-auth + copy_headers Remote-User Remote-Groups Remote-Name Remote-Email + }" + sed -i "s/^WEB_ADMIN_AUTH_DISABLED=.*/WEB_ADMIN_AUTH_DISABLED=true/" .env + log_info "Using remote Authelia at ${_remote_authelia}." + log_info "Verify it's reachable from this droplet before relying on it — e.g.:" + log_info " curl -I ${_remote_authelia}" + else + log_info "No address entered — skipping Authelia protection." + fi + fi fi # Reconstruct the subdomain-only fragment so configure_caddy_for_service's @@ -740,9 +785,16 @@ plan for the admin panel. Offered during install (space-separated at the "Install:" prompt); can also be added later by running \`sudo ./setup.sh \` from the repo. -- **authelia** — SSO/2FA in front of the web admin. Needs Caddy. Once - installed, re-running \`asterisk-do\` will offer to protect the web admin - with it. +- **authelia** — SSO/2FA in front of the web admin. Needs Caddy locally to + install here. If it's already installed (locally or picked up by + re-running \`asterisk-do\`), that instance protects the web admin + automatically. **No local Authelia?** The web-admin step separately offers + a **remote Authelia** option instead — point it at an instance already + running elsewhere (e.g. a homelab) via a bare \`host:port\` over a private + network (a NetBird mesh IP works well here) or a full \`https://\` URL if + it has its own public domain+TLS. Every web-admin page load then does a + round trip to that address, so if it's unreachable, the panel fails closed + — SIP/calling on this droplet is unaffected either way, only the admin UI. - **ntfy** — self-hosted push notifications. Useful as a destination for CrowdSec ban alerts (\`services/crowdsec.sh\` prompts for an ntfy URL — point it at this instance instead of the public ntfy.sh if you'd rather diff --git a/services/crowdsec.sh b/services/crowdsec.sh index f51ee3b..ac99283 100644 --- a/services/crowdsec.sh +++ b/services/crowdsec.sh @@ -104,6 +104,7 @@ install_crowdsec() { echo "[DRY-RUN] Would write Caddy acquisition /etc/crowdsec/acquis.d/caddy.yaml" echo "[DRY-RUN] Would install crowdsecurity/asterisk + write an acquisition if asterisk-do is installed" echo "[DRY-RUN] Would optionally wire ntfy ban alerts into the default profile" + echo "[DRY-RUN] Would optionally register with a remote/central LAPI and disable the local one" echo "[DRY-RUN] Would enable + restart crowdsec and crowdsec-firewall-bouncer" echo "[DRY-RUN] Would write $DOCS_DIR/README.md (docs-only folder)" return 0 @@ -235,20 +236,79 @@ headers: fi fi - # ── 8. Restart services to apply ───────────────────────────────────────── - local RESTART_CS="" - prompt_yn "Restart CrowdSec to apply changes? (y/n):" "y" RESTART_CS - if [ "$RESTART_CS" = "y" ] || [ "$RESTART_CS" = "Y" ]; then - sudo systemctl enable crowdsec 2>/dev/null || true - if sudo systemctl restart crowdsec; then - echo " ✓ CrowdSec restarted successfully" - sudo systemctl enable crowdsec-firewall-bouncer 2>/dev/null || true - sudo systemctl restart crowdsec-firewall-bouncer 2>/dev/null || true - sleep 2 - sudo cscli metrics 2>/dev/null | head -20 || true + # ── 7b. Optional: point this agent at a remote/central LAPI ────────────── + # CrowdSec's real multi-server support: parsers/scenarios/bouncer still + # run locally (banning only works where traffic actually arrives), but + # the decision database (LAPI) can live on one central machine instead + # of every box running its own. Useful if you already have CrowdSec on + # a homelab and don't want a second LAPI+SQLite DB on this droplet. + echo "" + local USE_REMOTE_LAPI="" _REMOTE_LAPI_PENDING="" + prompt_yn "Point this agent at a remote/central LAPI instead of running its own (e.g. one already on a homelab)? (y/n):" "n" USE_REMOTE_LAPI + if [ "$USE_REMOTE_LAPI" = "y" ] || [ "$USE_REMOTE_LAPI" = "Y" ]; then + echo "" + echo " This registers this machine and disables its local API server." + echo " The registration is PENDING until approved on the central LAPI" + echo " machine — that approval step can't be automated from here." + echo "" + local LAPI_URL="" LAPI_MACHINE="" + prompt_text " Central LAPI URL (e.g. http://homelab-ip:8080):" "" LAPI_URL + prompt_text " Machine name to register as:" "$(hostname)" LAPI_MACHINE + if [ -n "$LAPI_URL" ]; then + if sudo cscli lapi register -u "$LAPI_URL" --machine "$LAPI_MACHINE"; then + echo " ✓ Registered with $LAPI_URL as '$LAPI_MACHINE'" + + # Disable the local API server (remove the 'api.server:' block + # from config.yaml) now that this agent forwards to the + # central one instead. Backed up first — this is a direct + # edit to CrowdSec's core config. + local CS_CONFIG="/etc/crowdsec/config.yaml" + local CS_BACKUP="$CS_CONFIG.backup.$(date +%Y%m%d-%H%M%S)" + sudo cp "$CS_CONFIG" "$CS_BACKUP" + sudo awk ' + /^ server:/ { skip=1; next } + skip && /^([a-zA-Z]| [a-zA-Z])/ { skip=0 } + !skip { print } + ' "$CS_CONFIG" | sudo tee "$CS_CONFIG.new" > /dev/null \ + && sudo mv "$CS_CONFIG.new" "$CS_CONFIG" + echo " ✓ Local API server disabled in config.yaml (backup: $(basename "$CS_BACKUP"))" + echo "" + echo " ⚠ Not usable yet — on the CENTRAL LAPI machine, run:" + echo " sudo cscli machines validate $LAPI_MACHINE" + echo " Then restart this agent: sudo systemctl restart crowdsec" + echo " If it fails to start afterward, restore the backup and check logs:" + echo " sudo cp $CS_BACKUP $CS_CONFIG && sudo systemctl restart crowdsec" + _REMOTE_LAPI_PENDING="y" + else + echo " ⚠ cscli lapi register failed — keeping the local LAPI. See:" + echo " sudo cscli lapi register -u $LAPI_URL --machine $LAPI_MACHINE" + fi else - echo " ⚠ Failed to restart CrowdSec" - echo " Check logs: sudo journalctl -u crowdsec -n 50" + echo " No URL entered — keeping the local LAPI." + fi + fi + + # ── 8. Restart services to apply ───────────────────────────────────────── + if [ "$_REMOTE_LAPI_PENDING" = "y" ]; then + echo "" + echo " Skipping the restart below — it would fail until the machine is" + echo " validated on the central LAPI (see above). Restart manually after:" + echo " sudo systemctl restart crowdsec" + else + local RESTART_CS="" + prompt_yn "Restart CrowdSec to apply changes? (y/n):" "y" RESTART_CS + if [ "$RESTART_CS" = "y" ] || [ "$RESTART_CS" = "Y" ]; then + sudo systemctl enable crowdsec 2>/dev/null || true + if sudo systemctl restart crowdsec; then + echo " ✓ CrowdSec restarted successfully" + sudo systemctl enable crowdsec-firewall-bouncer 2>/dev/null || true + sudo systemctl restart crowdsec-firewall-bouncer 2>/dev/null || true + sleep 2 + sudo cscli metrics 2>/dev/null | head -20 || true + else + echo " ⚠ Failed to restart CrowdSec" + echo " Check logs: sudo journalctl -u crowdsec -n 50" + fi fi fi @@ -292,6 +352,21 @@ sudo cscli collections list # installed detection collections - ntfy ban alerts (if enabled): `/etc/crowdsec/notifications/ntfy.yaml`, wired into `/etc/crowdsec/profiles.yaml` - Bouncer config: `/etc/crowdsec/bouncers/` +- Remote/central LAPI (if enabled): `/etc/crowdsec/local_api_credentials.yaml` + points at the remote URL; the local API server block is removed from + `/etc/crowdsec/config.yaml` (backed up as `config.yaml.backup.` + next to it before editing). Parsers, scenarios, and the firewall bouncer + still run locally regardless — only the decision database is centralized. + +## Multi-server (remote LAPI) notes + +- On THIS machine: `sudo cscli lapi register -u --machine ` + registers and disables the local API server. +- On the CENTRAL machine: `sudo cscli machines validate ` approves it — + not automated, since that's a different box. +- Check registration status here: `sudo cscli lapi status` +- Revert: restore the `config.yaml` backup and + `sudo systemctl restart crowdsec`. ## Geo + reputation notes