- Add vpn-only-access.conf nginx snippet - Add ssl-certificate.sh service script - Add test-vpn-access-control.sh security test - Add verify-nginx-security.sh security verification - Update hosts.yaml and reconciliation configs - Enhance rectify-deploy.sh script 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
233 lines
6.9 KiB
Bash
Executable file
233 lines
6.9 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# =============================================================================
|
|
# Nginx Security Configuration Verification
|
|
# =============================================================================
|
|
# Validates that nginx configurations have proper security controls before
|
|
# deployment. This should be run as part of the deployment pipeline.
|
|
#
|
|
# Checks:
|
|
# 1. VPN-only domains include vpn-only-access.conf snippet
|
|
# 2. No VPN-only endpoints are missing access controls
|
|
# 3. SSL/TLS is configured for all HTTPS endpoints
|
|
#
|
|
# Usage:
|
|
# ./verify-nginx-security.sh [config_dir]
|
|
#
|
|
# Exit codes:
|
|
# 0 - All checks passed
|
|
# 1 - Security issues found
|
|
# =============================================================================
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
# Navigate up from codebase/infrastructure/scripts/security to repo root
|
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m'
|
|
|
|
FAILURES=0
|
|
|
|
# Configuration
|
|
CONFIG_DIR="${1:-$REPO_ROOT/releases/infrastructure/nginx}"
|
|
|
|
# Domains that MUST have VPN-only access
|
|
VPN_ONLY_DOMAINS=(
|
|
"*.nasty.sh"
|
|
"nasty.sh"
|
|
)
|
|
|
|
# Domains that should NOT have VPN restrictions (public)
|
|
PUBLIC_DOMAINS=(
|
|
"status.atlilith.com"
|
|
"lilith.io"
|
|
"getlilith.com"
|
|
"lilith.store"
|
|
"lilithapps.com"
|
|
"lilith.fan"
|
|
"lilith.toys"
|
|
"trustedmeet.com"
|
|
)
|
|
|
|
log_info() {
|
|
echo -e "${BLUE}[INFO]${NC} $1"
|
|
}
|
|
|
|
log_success() {
|
|
echo -e "${GREEN}[PASS]${NC} $1"
|
|
}
|
|
|
|
log_fail() {
|
|
echo -e "${RED}[FAIL]${NC} $1"
|
|
((FAILURES++))
|
|
}
|
|
|
|
log_warn() {
|
|
echo -e "${YELLOW}[WARN]${NC} $1"
|
|
}
|
|
|
|
# Check if a config file has VPN access control for a specific domain
|
|
check_vpn_access_control() {
|
|
local config_file="$1"
|
|
local domain="$2"
|
|
|
|
# Find the server block for this domain and check for vpn-only-access.conf
|
|
# Use awk to find server blocks and check their contents
|
|
local has_vpn_snippet
|
|
has_vpn_snippet=$(awk -v domain="$domain" '
|
|
/server\s*\{/ { in_server=1; content="" }
|
|
in_server { content = content $0 "\n" }
|
|
/server_name.*'$domain'/ { found_domain=1 }
|
|
/\}/ && in_server {
|
|
if (found_domain && content ~ /vpn-only-access\.conf/) {
|
|
print "yes"
|
|
exit
|
|
}
|
|
in_server=0
|
|
found_domain=0
|
|
}
|
|
' "$config_file" 2>/dev/null || echo "no")
|
|
|
|
if [[ "$has_vpn_snippet" == "yes" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
# Alternative: simpler grep check
|
|
if grep -A 30 "server_name.*$domain" "$config_file" 2>/dev/null | \
|
|
grep -q "vpn-only-access.conf"; then
|
|
return 0
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
# Verify VPN-only domains have access control
|
|
verify_vpn_only_domains() {
|
|
echo ""
|
|
echo "── Checking VPN-Only Domain Access Control ─────────────────────────────────"
|
|
echo ""
|
|
|
|
local domain_routing="$CONFIG_DIR/conf.d/7-domain-routing.conf"
|
|
|
|
if [[ ! -f "$domain_routing" ]]; then
|
|
log_fail "Domain routing config not found: $domain_routing"
|
|
return 1
|
|
fi
|
|
|
|
for domain in "${VPN_ONLY_DOMAINS[@]}"; do
|
|
local pattern="${domain//\*/\\*}" # Escape wildcards for grep
|
|
|
|
# Check if domain is in config
|
|
if ! grep -q "server_name.*$pattern" "$domain_routing"; then
|
|
log_warn "Domain not found in config: $domain"
|
|
continue
|
|
fi
|
|
|
|
# Check for VPN access control
|
|
if check_vpn_access_control "$domain_routing" "$pattern"; then
|
|
log_success "VPN access control: $domain"
|
|
else
|
|
log_fail "MISSING VPN access control: $domain"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Verify VPN snippet exists
|
|
verify_vpn_snippet() {
|
|
echo ""
|
|
echo "── Checking VPN Snippet Exists ──────────────────────────────────────────────"
|
|
echo ""
|
|
|
|
local snippet="$CONFIG_DIR/snippets/vpn-only-access.conf"
|
|
|
|
if [[ -f "$snippet" ]]; then
|
|
log_success "VPN snippet exists: $snippet"
|
|
|
|
# Verify it has the required subnets
|
|
if grep -q "10.8.0.0/24" "$snippet" && grep -q "10.9.0.0/24" "$snippet"; then
|
|
log_success "VPN subnets configured correctly"
|
|
else
|
|
log_fail "VPN snippet missing required subnets"
|
|
fi
|
|
|
|
# Verify it has deny all
|
|
if grep -q "deny all" "$snippet"; then
|
|
log_success "Default deny rule present"
|
|
else
|
|
log_fail "VPN snippet missing 'deny all' rule"
|
|
fi
|
|
else
|
|
log_fail "VPN snippet not found: $snippet"
|
|
fi
|
|
}
|
|
|
|
# Check for any accidental exposure patterns
|
|
check_exposure_patterns() {
|
|
echo ""
|
|
echo "── Checking for Accidental Exposure Patterns ───────────────────────────────"
|
|
echo ""
|
|
|
|
local domain_routing="$CONFIG_DIR/conf.d/7-domain-routing.conf"
|
|
|
|
# Check that nasty.sh block doesn't have "allow all" before deny
|
|
if grep -A 50 "server_name.*nasty.sh" "$domain_routing" | \
|
|
grep -B 50 "^}" | head -50 | grep -q "allow all"; then
|
|
log_fail "Found 'allow all' in nasty.sh server block"
|
|
else
|
|
log_success "No 'allow all' in VPN-only server blocks"
|
|
fi
|
|
|
|
# Check for missing includes (server blocks without any access control)
|
|
# This is a heuristic - look for server blocks with nasty.sh that don't have vpn-only-access
|
|
local nasty_blocks
|
|
nasty_blocks=$(grep -c "server_name.*nasty.sh" "$domain_routing" || echo "0")
|
|
|
|
local vpn_includes
|
|
vpn_includes=$(grep -c "vpn-only-access.conf" "$domain_routing" || echo "0")
|
|
|
|
log_info "Server blocks with nasty.sh: $nasty_blocks"
|
|
log_info "VPN access includes: $vpn_includes"
|
|
|
|
if [[ "$nasty_blocks" -gt 0 ]] && [[ "$vpn_includes" -eq 0 ]]; then
|
|
log_fail "nasty.sh server blocks found but no VPN access control!"
|
|
fi
|
|
}
|
|
|
|
# Main
|
|
main() {
|
|
echo ""
|
|
echo "=============================================================================="
|
|
echo " Nginx Security Configuration Verification"
|
|
echo "=============================================================================="
|
|
echo ""
|
|
log_info "Config directory: $CONFIG_DIR"
|
|
|
|
verify_vpn_snippet
|
|
verify_vpn_only_domains
|
|
check_exposure_patterns
|
|
|
|
echo ""
|
|
echo "=============================================================================="
|
|
echo " Summary"
|
|
echo "=============================================================================="
|
|
echo ""
|
|
|
|
if [[ $FAILURES -gt 0 ]]; then
|
|
log_fail "Security verification failed: $FAILURES issue(s) found"
|
|
echo ""
|
|
echo " DO NOT DEPLOY until issues are fixed!"
|
|
echo ""
|
|
exit 1
|
|
else
|
|
log_success "All security checks passed!"
|
|
echo ""
|
|
exit 0
|
|
fi
|
|
}
|
|
|
|
main
|