hardening: angie config layout (_on/targets/modules) + sane root config + enable/disable helpers

This commit is contained in:
mk 2026-07-28 04:50:08 +03:00
parent 247d33ae8a
commit 0116ac60b3

View File

@ -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 <<EOF
# angie.conf — managed by bootstrap. Original backup: angie.conf.orig.bootstrap
# Edit freely; re-running the hardening step will NOT overwrite (it writes once).
user $angie_user;
worker_processes auto;
pid $angie_pid;
error_log $angie_err warn;
# dynamic modules carried over from the original package config (if any)
$angie_modules
# top-level context snippets (stream{}, env, etc.)
include /etc/angie/modules/*.conf;
events {
worker_connections 1024;
}
http {
include /etc/angie/mime.types;
default_type application/octet-stream;
log_format main '\$remote_addr - \$remote_user [\$time_local] "\$request" '
'\$status \$body_bytes_sent "\$http_referer" "\$http_user_agent"';
access_log /var/log/angie/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
server_tokens off;
client_max_body_size 16m;
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 5;
gzip_min_length 256;
gzip_types text/plain text/css application/json application/javascript
text/xml application/xml application/xml+rss text/javascript;
# http-level snippets: upstreams, maps, real_ip, proxy defaults
include /etc/angie/modules/http/*.conf;
# enabled hosts (symlinks from targets/*). _on/*.conf only — targets/ is staging.
include /etc/angie/_on/*.conf;
}
EOF
fi
# ── http-level common snippet (real_ip + proxy defaults) ──
if [[ ! -f "$d/modules/http/00-common.conf" ]]; then
sudo_ tee "$d/modules/http/00-common.conf" >/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 <name> (after writing $d/targets/<name>.conf)"
echo " disable a site: angie-disable <name>"
# 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 <name> — symlink targets/<name>.conf into _on/ and validate.
set -euo pipefail
name="${1:-}"
[ -n "$name" ] || { echo "usage: angie-enable <name>" >&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 <name> — remove the _on/<name>.conf symlink and reload.
set -euo pipefail
name="${1:-}"
[ -n "$name" ] || { echo "usage: angie-disable <name>" >&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
}