#!/usr/bin/env bash
# harden-nginx.sh — Hardened LN Stack lab (ch.5)
# ---------------------------------------------------------------------------
# Stands up a hardened Linux + Nginx web server on Debian 13: tightened nginx
# core, a full security-headers snippet, TLS-ready ssl params, a default-deny
# catch-all for scanner noise, a UFW firewall, and fail2ban on ssh + nginx.
# Idempotent. Run inside the lab as root:  ./harden-nginx.sh [domain]
# ---------------------------------------------------------------------------
set -uo pipefail
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
print_header(){ echo -e "\n${GREEN}=== $1 ===${NC}"; }
die(){ echo -e "${RED}[x] $1${NC}"; exit 1; }
[[ ${EUID:-$(id -u)} -eq 0 ]] || die "Run as root:  sudo ./harden-nginx.sh [domain]"
export DEBIAN_FRONTEND=noninteractive

DOMAIN="${1:-localhost}"
WEBROOT="/var/www/${DOMAIN}"

print_header "Installing packages"
apt-get update -qq
apt-get install -y nginx fail2ban ufw curl openssl || die "package install failed"

print_header "Hardening the Nginx core"
cat > /etc/nginx/conf.d/00-hardening.conf <<'EOF'
server_tokens off;                 # don't leak the version
server_names_hash_bucket_size 128;
client_max_body_size 16m;          # cap uploads
client_body_timeout 10s;
client_header_timeout 10s;
send_timeout 10s;
keepalive_timeout 30s;
types_hash_max_size 2048;
# TLS defaults for any server block that enables ssl
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
# rate-limit zone (apply with limit_req in a location)
limit_req_zone $binary_remote_addr zone=req_per_ip:10m rate=10r/s;
EOF

print_header "Writing the security-headers snippet"
install -d /etc/nginx/snippets
cat > /etc/nginx/snippets/security-headers.conf <<'EOF'
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=()" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Content-Security-Policy "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'" always;
# Enable HSTS only once TLS is live (it is sticky — hard to undo):
# add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
EOF

print_header "Creating the ${DOMAIN} site (default-deny)"
install -d "$WEBROOT"
[ -f "$WEBROOT/index.html" ] || echo "<!doctype html><title>${DOMAIN}</title><h1>${DOMAIN} is up.</h1>" > "$WEBROOT/index.html"

# Catch-all that drops requests for hosts we don't serve (kills a lot of scanner noise).
cat > /etc/nginx/sites-available/000-default-deny <<'EOF'
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    return 444;                      # close the connection, send nothing
}
EOF

cat > /etc/nginx/sites-available/"$DOMAIN" <<EOF
server {
    listen 80;
    listen [::]:80;
    server_name ${DOMAIN};
    root ${WEBROOT};
    index index.html;
    include snippets/security-headers.conf;

    location / {
        limit_req zone=req_per_ip burst=20 nodelay;
        try_files \$uri \$uri/ =404;
    }
    location ~ /\.                  { deny all; }   # .env / .git / dotfiles
    location ~* \.(bak|old|sql|swp)\$ { deny all; }   # editor/backup leftovers
}
EOF
ln -sf /etc/nginx/sites-available/"$DOMAIN" /etc/nginx/sites-enabled/"$DOMAIN"
ln -sf /etc/nginx/sites-available/000-default-deny /etc/nginx/sites-enabled/000-default-deny
rm -f /etc/nginx/sites-enabled/default

print_header "Firewall (UFW)"
ufw allow OpenSSH >/dev/null 2>&1 || ufw allow 22/tcp
ufw allow 80/tcp; ufw allow 443/tcp
ufw --force enable

print_header "fail2ban (ssh + nginx)"
cat > /etc/fail2ban/jail.local <<'EOF'
[DEFAULT]
bantime  = 1h
findtime = 10m
maxretry = 5
[sshd]
enabled = true
[nginx-http-auth]
enabled = true
[nginx-botsearch]
enabled = true
EOF
systemctl enable --now fail2ban >/dev/null 2>&1 || true

print_header "Validating + reloading"
nginx -t || die "nginx config test failed"
systemctl enable --now nginx
systemctl reload nginx

print_header "Done"
echo -e "${GREEN}Hardened Nginx is serving ${DOMAIN}.${NC}"
echo    "Check headers:  curl -sI http://${DOMAIN}/ | grep -iE 'content-security|x-frame|referrer|permissions|content-type'"
echo -e "${YELLOW}Add TLS:  apt-get install -y certbot python3-certbot-nginx && certbot --nginx -d ${DOMAIN}${NC}"
echo    "Then uncomment the HSTS line in /etc/nginx/snippets/security-headers.conf."
