diff --git a/modules/50-hardening.sh b/modules/50-hardening.sh index f8d4aec..841648c 100755 --- a/modules/50-hardening.sh +++ b/modules/50-hardening.sh @@ -2,7 +2,7 @@ # 50-hardening — system defaults. Each sub-step y/N, all idempotent. step_name="hardening" -step_desc="ssh(ufw-safe), ufw, updates, unattended, tz, hostname, swap, locale, fail2ban" +step_desc="ssh, ufw, updates, unattended, tz, hostname, swap, locale, fail2ban, angie config" step_run="run_hardening" run_hardening() { @@ -14,6 +14,7 @@ run_hardening() { _h_swap _h_locale _h_fail2ban + _h_angie_config ok "hardening pass complete" } @@ -185,4 +186,185 @@ EOF sudo_ systemctl enable --now fail2ban 2>/dev/null || warn "could not enable fail2ban" fi ok "fail2ban installed + sshd jail enabled" +} + +# ── angie config: _on/ targets/ modules/ + sane root config ──────────────── +_h_angie_config() { + if ! command -v angie >/dev/null 2>&1; then + echo " skip angie config (angie not installed — run the angie step first)"; return + fi + if ! yn "Set up Angie config layout (/etc/angie/_on targets modules) + sane root config?" y; then + echo " skip angie config"; return + fi + + local d=/etc/angie + sudo_ install -d -m 0755 "$d/_on" "$d/targets" "$d/modules" "$d/modules/http" + + # ── extract packaging-specific values from the stock angie.conf ── + # Angie packages set user/pid/error_log differently per distro; preserve them. + local oldcfg="$d/angie.conf" angie_user angie_pid angie_err angie_modules="" + if [[ -f "$oldcfg" ]]; then + angie_user=$(grep -E '^\s*user\s+' "$oldcfg" | head -1 | awk '{print $2}' | tr -d ';') + angie_pid=$(grep -E '^\s*pid\s+' "$oldcfg" | head -1 | awk '{print $2}' | tr -d ';') + angie_err=$(grep -E '^\s*error_log\s+' "$oldcfg" | head -1 | awk '{print $2}' | tr -d ';') + # collect any load_module lines (dynamic modules) + angie_modules=$(grep -E '^\s*load_module\s+' "$oldcfg" || true) + fi + # sane defaults if not found + [[ -z "$angie_user" ]] && angie_user=$({ [[ "$DISTRO" == "arch" ]] && echo http || echo angie; }) + [[ -z "$angie_pid" ]] && angie_pid=/run/angie.pid + [[ -z "$angie_err" ]] && angie_err=/var/log/angie/error.log + + # backup the original once + if [[ -f "$oldcfg" && ! -f "$oldcfg.orig.bootstrap" ]]; then + sudo_ cp -a "$oldcfg" "$oldcfg.orig.bootstrap" + fi + + # ── write our root angie.conf (once — guarded, won't clobber later edits) ── + if grep -q '# managed by bootstrap' "$oldcfg" 2>/dev/null; then + ok "$oldcfg already bootstrap-managed — leaving it (keeps your edits)" + else + sudo_ tee "$oldcfg" >/dev/null </dev/null <<'EOF' +# real_ip — trust private ranges + (commented Cloudflare). Uncomment/edit for your upstream. +set_real_ip_from 10.0.0.0/8; +set_real_ip_from 172.16.0.0/12; +set_real_ip_from 192.168.0.0/16; +set_real_ip_from 169.254.0.0/16; +# set_real_ip_from 173.245.48.0/20; # Cloudflare, see https://www.cloudflare.com/ips/ +real_ip_header X-Forwarded-For; +real_ip_recursive on; + +# proxy defaults (inherited by every server/location unless overridden) +proxy_http_version 1.1; +proxy_set_header Host $host; +proxy_set_header X-Real-IP $remote_addr; +proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +proxy_set_header X-Forwarded-Proto $scheme; +EOF + fi + + # ── default catch-all server: unmatched Host -> drop ── + if [[ ! -f "$d/_on/00-default.conf" ]]; then + sudo_ tee "$d/_on/00-default.conf" >/dev/null <<'EOF' +# default_server: drop requests that match no enabled host (drive-by scanners, bare-IP) +server { + listen 80 default_server; + listen [::]:80 default_server; + listen 443 default_server; + listen [::]:443 default_server; + server_name _; + return 444; +} +EOF + fi + + # ── angie-enable / angie-disable helpers ── + _angie_helpers + + # validate + (best-effort) reload + if sudo_ angie -t 2>&1; then + ok "angie config valid" + if command -v systemctl >/dev/null 2>&1 && systemctl is-active angie >/dev/null 2>&1; then + sudo_ systemctl reload angie 2>/dev/null && ok "angie reloaded" + else + echo " start angie when ready: sudo systemctl enable --now angie" + fi + else + err "angie -t failed — review $d/angie.conf (backup at $oldcfg.orig.bootstrap)" + fi + + ok "angie layout ready: $d/{angie.conf,_on/,targets/,modules/,modules/http/}" + echo " enable a site: angie-enable (after writing $d/targets/.conf)" + echo " disable a site: angie-disable " + + # ufw already opened 80/443 in _h_ufw +} + +_angie_helpers() { + sudo_ tee /usr/local/bin/angie-enable >/dev/null <<'EOF' +#!/usr/bin/env bash +# angie-enable — symlink targets/.conf into _on/ and validate. +set -euo pipefail +name="${1:-}" +[ -n "$name" ] || { echo "usage: angie-enable " >&2; exit 2; } +target="/etc/angie/targets/${name}.conf" +link="/etc/angie/_on/${name}.conf" +[ -f "$target" ] || { echo "no such target: $target" >&2; exit 1; } +ln -sfn "../targets/${name}.conf" "$link" +echo "enabled: $name -> $link" +if command -v angie >/dev/null 2>&1; then + sudo angie -t || { echo "config invalid — review before reload" >&2; exit 1; } + sudo systemctl reload angie && echo "reloaded" +fi +EOF + sudo_ tee /usr/local/bin/angie-disable >/dev/null <<'EOF' +#!/usr/bin/env bash +# angie-disable — remove the _on/.conf symlink and reload. +set -euo pipefail +name="${1:-}" +[ -n "$name" ] || { echo "usage: angie-disable " >&2; exit 2; } +link="/etc/angie/_on/${name}.conf" +[ -L "$link" ] || [ -e "$link" ] || { echo "$name not enabled ($link missing)" >&2; exit 1; } +rm -f "$link" +echo "disabled: $name" +if command -v angie >/dev/null 2>&1; then + sudo angie -t || true + sudo systemctl reload angie && echo "reloaded" +fi +EOF + sudo_ chmod +x /usr/local/bin/angie-enable /usr/local/bin/angie-disable } \ No newline at end of file