a1a17e81a1
Includes updated JS challenge scripts with Claude-User whitelist, same-site referer bypass, Blackbox-Exporter allowed bot, and all new exporters, cheat sheets, and automation scripts.
487 lines
16 KiB
Bash
Executable File
487 lines
16 KiB
Bash
Executable File
#!/bin/bash
|
|
################################################################################
|
|
# Script Name: nginx-geoip-setup.sh
|
|
# Version: 1.0
|
|
# Description: Automate nginx GeoIP2 enrichment for IP intelligence.
|
|
# Installs GeoIP2 module, downloads GeoIP databases (MaxMind or
|
|
# DB-IP free), creates nginx geoip2 variable mappings and an
|
|
# enriched log format.
|
|
#
|
|
# Author: Phil Connor
|
|
# Contact: contact@mylinux.work
|
|
# Website: https://mylinux.work
|
|
# License: MIT
|
|
#
|
|
# Prerequisites:
|
|
# - nginx installed and running
|
|
# - Root access
|
|
# - MaxMind account OR use --db-ip for no-signup alternative
|
|
#
|
|
# Usage:
|
|
# sudo ./nginx-geoip-setup.sh --db-ip
|
|
# sudo ./nginx-geoip-setup.sh --account-id 123456 --license-key ABCDEF
|
|
# sudo ./nginx-geoip-setup.sh --dry-run
|
|
# sudo ./nginx-geoip-setup.sh --remove
|
|
# sudo ./nginx-geoip-setup.sh --skip-packages
|
|
#
|
|
################################################################################
|
|
|
|
set -euo pipefail
|
|
|
|
# --- Configuration ---
|
|
GEOIP_CONF="/etc/GeoIP.conf"
|
|
GEOIP_DB_DIR="/usr/share/GeoIP"
|
|
NGINX_GEOIP_CONF="/etc/nginx/conf.d/geoip2.conf"
|
|
NGINX_LOG_CONF="/etc/nginx/enriched-log-format.conf"
|
|
CRON_WEEKLY="/etc/cron.weekly/geoip-db-update"
|
|
CRON_MONTHLY="/etc/cron.monthly/geoip-db-update"
|
|
MARKER_START="# nginx-geoip-managed-start"
|
|
MARKER_END="# nginx-geoip-managed-end"
|
|
TIMESTAMP=$(date +%s)
|
|
|
|
DRY_RUN=false
|
|
REMOVE=false
|
|
SKIP_PACKAGES=false
|
|
USE_DBIP=false
|
|
ACCOUNT_ID="${MAXMIND_ACCOUNT_ID:-}"
|
|
LICENSE_KEY="${MAXMIND_LICENSE_KEY:-}"
|
|
|
|
# --- Colors ---
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[0;33m'
|
|
CYAN='\033[0;36m'
|
|
BOLD='\033[1m'
|
|
NC='\033[0m'
|
|
|
|
info() { echo -e "${GREEN}[OK]${NC} $*"; }
|
|
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
|
step() { echo -e "${CYAN}[STEP]${NC} $*"; }
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: sudo $(basename "$0") [OPTIONS]
|
|
|
|
Sets up nginx GeoIP2 enrichment using MaxMind GeoLite2 or DB-IP free databases.
|
|
Creates geoip2 variable mappings and an enriched log format for nginx.
|
|
|
|
Options:
|
|
--db-ip Use DB-IP free databases (no signup required)
|
|
--account-id ID MaxMind account ID (not needed with --db-ip)
|
|
--license-key KEY MaxMind license key (not needed with --db-ip)
|
|
--dry-run Show what would be done without making changes
|
|
--remove Remove managed configs and update script (leaves packages/databases)
|
|
--skip-packages Skip package installation
|
|
-h, --help Show this help
|
|
|
|
Environment variables:
|
|
MAXMIND_ACCOUNT_ID MaxMind account ID (overridden by --account-id)
|
|
MAXMIND_LICENSE_KEY MaxMind license key (overridden by --license-key)
|
|
|
|
Database providers:
|
|
MaxMind GeoLite2 Free, requires account signup at maxmind.com
|
|
DB-IP Lite Free, no signup, direct download (--db-ip)
|
|
|
|
Examples:
|
|
sudo $(basename "$0") --db-ip
|
|
sudo $(basename "$0") --account-id 123456 --license-key ABCDEF
|
|
sudo $(basename "$0") --dry-run
|
|
sudo $(basename "$0") --remove
|
|
EOF
|
|
exit 0
|
|
}
|
|
|
|
# --- Argument parsing ---
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--db-ip) USE_DBIP=true; shift ;;
|
|
--account-id) ACCOUNT_ID="$2"; shift 2 ;;
|
|
--license-key) LICENSE_KEY="$2"; shift 2 ;;
|
|
--dry-run) DRY_RUN=true; shift ;;
|
|
--remove) REMOVE=true; shift ;;
|
|
--skip-packages) SKIP_PACKAGES=true; shift ;;
|
|
-h|--help) usage ;;
|
|
*) echo "Unknown option: $1"; usage ;;
|
|
esac
|
|
done
|
|
|
|
# --- Checks ---
|
|
if [[ $EUID -ne 0 ]]; then
|
|
echo -e "${RED}Error: Run as root (sudo)${NC}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v nginx &>/dev/null; then
|
|
echo -e "${RED}Error: nginx not found${NC}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# --- Distro detection ---
|
|
detect_distro() {
|
|
if [[ -f /etc/os-release ]]; then
|
|
# shellcheck source=/dev/null
|
|
. /etc/os-release
|
|
case "$ID" in
|
|
debian|ubuntu|linuxmint) echo "debian" ;;
|
|
rhel|centos|rocky|alma|fedora|ol) echo "rhel" ;;
|
|
*) echo "unknown" ;;
|
|
esac
|
|
else
|
|
echo "unknown"
|
|
fi
|
|
}
|
|
|
|
DISTRO=$(detect_distro)
|
|
|
|
backup_file() {
|
|
local file="$1"
|
|
if [[ -f "$file" ]]; then
|
|
cp "$file" "${file}.bak.${TIMESTAMP}"
|
|
warn "Backed up: ${file} → ${file}.bak.${TIMESTAMP}"
|
|
fi
|
|
}
|
|
|
|
# =====================================================
|
|
# REMOVE MODE
|
|
# =====================================================
|
|
if [[ "$REMOVE" == "true" ]]; then
|
|
step "Removing GeoIP2 configuration"
|
|
|
|
for file in "$NGINX_GEOIP_CONF" "$NGINX_LOG_CONF" "$CRON_WEEKLY" "$CRON_MONTHLY"; do
|
|
if [[ -f "$file" ]]; then
|
|
if [[ "$DRY_RUN" == "true" ]]; then
|
|
echo " Would remove: ${file}"
|
|
else
|
|
rm -f "$file"
|
|
info "Removed: ${file}"
|
|
fi
|
|
else
|
|
warn "Not found: ${file} (already removed?)"
|
|
fi
|
|
done
|
|
|
|
if [[ "$DRY_RUN" == "true" ]]; then
|
|
echo " Would run: nginx -t"
|
|
echo " Would run: systemctl reload nginx"
|
|
else
|
|
step "Testing nginx configuration"
|
|
if nginx -t 2>&1; then
|
|
info "nginx config valid"
|
|
else
|
|
echo -e "${RED}[ERROR] nginx config test failed${NC}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
step "Reloading nginx"
|
|
systemctl reload nginx
|
|
info "nginx reloaded"
|
|
fi
|
|
|
|
echo ""
|
|
echo -e "${BOLD}GeoIP2 configuration removed.${NC}"
|
|
echo " Packages and databases left in place."
|
|
echo " To remove databases: rm -rf ${GEOIP_DB_DIR}/*.mmdb"
|
|
exit 0
|
|
fi
|
|
|
|
# =====================================================
|
|
# INSTALL MODE
|
|
# =====================================================
|
|
|
|
# --- Prompt for credentials if not using DB-IP ---
|
|
if [[ "$USE_DBIP" != "true" ]]; then
|
|
if [[ -z "$ACCOUNT_ID" ]]; then
|
|
read -rp "MaxMind Account ID (or Ctrl+C and rerun with --db-ip): " ACCOUNT_ID
|
|
fi
|
|
if [[ -z "$LICENSE_KEY" ]]; then
|
|
read -rp "MaxMind License Key: " LICENSE_KEY
|
|
fi
|
|
|
|
if [[ -z "$ACCOUNT_ID" || -z "$LICENSE_KEY" ]]; then
|
|
echo -e "${RED}Error: MaxMind credentials required (or use --db-ip for no-signup alternative)${NC}" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# =====================================================
|
|
# Step 1: Install packages
|
|
# =====================================================
|
|
if [[ "$SKIP_PACKAGES" == "true" ]]; then
|
|
warn "Skipping package installation (--skip-packages)"
|
|
else
|
|
step "Installing GeoIP2 packages"
|
|
|
|
if [[ "$DISTRO" == "debian" ]]; then
|
|
if [[ "$USE_DBIP" == "true" ]]; then
|
|
PACKAGES="libnginx-mod-http-geoip2 mmdb-bin curl"
|
|
else
|
|
PACKAGES="libnginx-mod-http-geoip2 geoipupdate mmdb-bin"
|
|
fi
|
|
if [[ "$DRY_RUN" == "true" ]]; then
|
|
echo " Would run: apt-get update && apt-get install -y ${PACKAGES}"
|
|
else
|
|
apt-get update -qq
|
|
# shellcheck disable=SC2086
|
|
apt-get install -y $PACKAGES
|
|
info "Packages installed: ${PACKAGES}"
|
|
fi
|
|
elif [[ "$DISTRO" == "rhel" ]]; then
|
|
if [[ "$USE_DBIP" == "true" ]]; then
|
|
PACKAGES="nginx-mod-http-geoip2 mmdb-bin curl"
|
|
else
|
|
PACKAGES="nginx-mod-http-geoip2 geoipupdate mmdb-bin"
|
|
fi
|
|
if [[ "$DRY_RUN" == "true" ]]; then
|
|
echo " Would run: dnf install -y ${PACKAGES}"
|
|
else
|
|
# shellcheck disable=SC2086
|
|
dnf install -y $PACKAGES
|
|
info "Packages installed: ${PACKAGES}"
|
|
fi
|
|
else
|
|
echo -e "${RED}Error: Unsupported distro. Install packages manually and rerun with --skip-packages${NC}" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# =====================================================
|
|
# Step 2: Write GeoIP.conf (MaxMind only)
|
|
# =====================================================
|
|
if [[ "$USE_DBIP" != "true" ]]; then
|
|
step "Writing MaxMind configuration to ${GEOIP_CONF}"
|
|
|
|
GEOIP_CONF_CONTENT="${MARKER_START}
|
|
AccountID ${ACCOUNT_ID}
|
|
LicenseKey ${LICENSE_KEY}
|
|
EditionIDs GeoLite2-City GeoLite2-ASN GeoLite2-Country
|
|
DatabaseDirectory ${GEOIP_DB_DIR}
|
|
${MARKER_END}"
|
|
|
|
if [[ "$DRY_RUN" == "true" ]]; then
|
|
echo " Would create: ${GEOIP_CONF}"
|
|
else
|
|
backup_file "$GEOIP_CONF"
|
|
echo "$GEOIP_CONF_CONTENT" > "$GEOIP_CONF"
|
|
chmod 600 "$GEOIP_CONF"
|
|
info "Created: ${GEOIP_CONF}"
|
|
fi
|
|
fi
|
|
|
|
# =====================================================
|
|
# Step 3: Download GeoIP databases
|
|
# =====================================================
|
|
if [[ "$USE_DBIP" == "true" ]]; then
|
|
step "Downloading DB-IP free databases to ${GEOIP_DB_DIR}"
|
|
|
|
DBIP_MONTH=$(date +%Y-%m)
|
|
|
|
if [[ "$DRY_RUN" == "true" ]]; then
|
|
echo " Would download: dbip-country-lite-${DBIP_MONTH}.mmdb.gz"
|
|
echo " Would download: dbip-asn-lite-${DBIP_MONTH}.mmdb.gz"
|
|
else
|
|
mkdir -p "$GEOIP_DB_DIR"
|
|
TMPDIR=$(mktemp -d)
|
|
trap 'rm -rf "$TMPDIR"' EXIT
|
|
|
|
curl -fsSL "https://download.db-ip.com/free/dbip-country-lite-${DBIP_MONTH}.mmdb.gz" -o "${TMPDIR}/country.mmdb.gz"
|
|
curl -fsSL "https://download.db-ip.com/free/dbip-asn-lite-${DBIP_MONTH}.mmdb.gz" -o "${TMPDIR}/asn.mmdb.gz"
|
|
gunzip -f "${TMPDIR}/country.mmdb.gz"
|
|
gunzip -f "${TMPDIR}/asn.mmdb.gz"
|
|
mv "${TMPDIR}/country.mmdb" "${GEOIP_DB_DIR}/GeoLite2-City.mmdb"
|
|
mv "${TMPDIR}/asn.mmdb" "${GEOIP_DB_DIR}/GeoLite2-ASN.mmdb"
|
|
info "Downloaded: DB-IP Country → GeoLite2-City.mmdb"
|
|
info "Downloaded: DB-IP ASN → GeoLite2-ASN.mmdb"
|
|
fi
|
|
else
|
|
step "Downloading MaxMind GeoIP2 databases to ${GEOIP_DB_DIR}"
|
|
|
|
if [[ "$DRY_RUN" == "true" ]]; then
|
|
echo " Would run: mkdir -p ${GEOIP_DB_DIR}"
|
|
echo " Would run: geoipupdate"
|
|
else
|
|
mkdir -p "$GEOIP_DB_DIR"
|
|
geoipupdate -v
|
|
info "GeoIP2 databases downloaded"
|
|
fi
|
|
fi
|
|
|
|
# =====================================================
|
|
# Step 4: Create nginx geoip2 config
|
|
# =====================================================
|
|
step "Creating nginx GeoIP2 config at ${NGINX_GEOIP_CONF}"
|
|
|
|
NGINX_GEOIP_CONTENT="${MARKER_START}
|
|
# GeoIP2 variable mappings — generated by nginx-geoip-setup.sh
|
|
|
|
geoip2 ${GEOIP_DB_DIR}/GeoLite2-City.mmdb {
|
|
auto_reload 60m;
|
|
\$geoip2_country_code country iso_code;
|
|
\$geoip2_country_name country names en;
|
|
\$geoip2_city_name city names en;
|
|
}
|
|
|
|
geoip2 ${GEOIP_DB_DIR}/GeoLite2-ASN.mmdb {
|
|
auto_reload 60m;
|
|
\$geoip2_asn autonomous_system_number;
|
|
\$geoip2_asn_org autonomous_system_organization;
|
|
}
|
|
${MARKER_END}"
|
|
|
|
if [[ "$DRY_RUN" == "true" ]]; then
|
|
echo " Would create: ${NGINX_GEOIP_CONF}"
|
|
else
|
|
backup_file "$NGINX_GEOIP_CONF"
|
|
echo "$NGINX_GEOIP_CONTENT" > "$NGINX_GEOIP_CONF"
|
|
info "Created: ${NGINX_GEOIP_CONF}"
|
|
fi
|
|
|
|
# =====================================================
|
|
# Step 5: Create enriched log format
|
|
# =====================================================
|
|
step "Creating enriched log format at ${NGINX_LOG_CONF}"
|
|
|
|
NGINX_LOG_CONTENT="${MARKER_START}
|
|
# Enriched log format with GeoIP2 data — generated by nginx-geoip-setup.sh
|
|
|
|
log_format enriched '\$remote_addr - \$remote_user [\$time_local] '
|
|
'\"\$request\" \$status \$body_bytes_sent '
|
|
'\"\$http_referer\" \"\$http_user_agent\" '
|
|
'\$geoip2_country_code \"\$geoip2_asn_org\"';
|
|
${MARKER_END}"
|
|
|
|
if [[ "$DRY_RUN" == "true" ]]; then
|
|
echo " Would create: ${NGINX_LOG_CONF}"
|
|
else
|
|
backup_file "$NGINX_LOG_CONF"
|
|
echo "$NGINX_LOG_CONTENT" > "$NGINX_LOG_CONF"
|
|
info "Created: ${NGINX_LOG_CONF}"
|
|
fi
|
|
|
|
# =====================================================
|
|
# Step 6: Patch nginx.conf to use enriched log format
|
|
# =====================================================
|
|
NGINX_CONF="/etc/nginx/nginx.conf"
|
|
|
|
if grep -q "enriched-log-format.conf" "$NGINX_CONF" 2>/dev/null; then
|
|
warn "nginx.conf already includes enriched log format — skipping"
|
|
elif grep -q "access_log.*enriched" "$NGINX_CONF" 2>/dev/null; then
|
|
warn "nginx.conf already uses enriched format — skipping"
|
|
else
|
|
step "Patching ${NGINX_CONF} to use enriched log format"
|
|
|
|
if [[ "$DRY_RUN" == "true" ]]; then
|
|
echo " Would add: include ${NGINX_LOG_CONF}; before access_log"
|
|
echo " Would change: access_log line to use enriched format"
|
|
else
|
|
backup_file "$NGINX_CONF"
|
|
|
|
# Insert include before the access_log line, and append 'enriched' to it
|
|
sed -i "/^[[:space:]]*access_log[[:space:]]/ {
|
|
i\\\\tinclude ${NGINX_LOG_CONF};
|
|
s|access_log \(.*\);|access_log \1 enriched;|
|
|
}" "$NGINX_CONF"
|
|
|
|
# Only patch if access_log doesn't already have a format name (avoid double-appending)
|
|
# Check if the sed worked
|
|
if grep -q "enriched-log-format.conf" "$NGINX_CONF"; then
|
|
info "Patched: ${NGINX_CONF}"
|
|
else
|
|
warn "Could not find access_log line in ${NGINX_CONF} — add manually:"
|
|
warn " include ${NGINX_LOG_CONF};"
|
|
warn " access_log /var/log/nginx/access.log enriched;"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# =====================================================
|
|
# Step 7: Create cron job for database updates
|
|
# =====================================================
|
|
if [[ "$USE_DBIP" == "true" ]]; then
|
|
CRON_FILE="$CRON_MONTHLY"
|
|
step "Creating monthly DB-IP update script at ${CRON_FILE}"
|
|
|
|
CRON_CONTENT="#!/bin/sh
|
|
# Monthly DB-IP database update — generated by nginx-geoip-setup.sh
|
|
MONTH=\$(date +%Y-%m)
|
|
TMPDIR=\$(mktemp -d)
|
|
trap 'rm -rf \"\$TMPDIR\"' EXIT
|
|
if curl -fsSL \"https://download.db-ip.com/free/dbip-country-lite-\${MONTH}.mmdb.gz\" -o \"\${TMPDIR}/country.mmdb.gz\" && \\
|
|
curl -fsSL \"https://download.db-ip.com/free/dbip-asn-lite-\${MONTH}.mmdb.gz\" -o \"\${TMPDIR}/asn.mmdb.gz\" && \\
|
|
gunzip \"\${TMPDIR}/country.mmdb.gz\" && \\
|
|
gunzip \"\${TMPDIR}/asn.mmdb.gz\"; then
|
|
mv \"\${TMPDIR}/country.mmdb\" \"${GEOIP_DB_DIR}/GeoLite2-City.mmdb\"
|
|
mv \"\${TMPDIR}/asn.mmdb\" \"${GEOIP_DB_DIR}/GeoLite2-ASN.mmdb\"
|
|
logger -t dbip-update \"DB-IP databases updated\"
|
|
else
|
|
logger -t dbip-update -p user.err \"DB-IP download failed — kept existing databases\"
|
|
fi"
|
|
else
|
|
CRON_FILE="$CRON_WEEKLY"
|
|
step "Creating weekly geoipupdate script at ${CRON_FILE}"
|
|
|
|
CRON_CONTENT="#!/bin/sh
|
|
# Weekly GeoIP2 database update — generated by nginx-geoip-setup.sh
|
|
/usr/bin/geoipupdate -s 2>&1 | logger -t geoipupdate"
|
|
fi
|
|
|
|
if [[ "$DRY_RUN" == "true" ]]; then
|
|
echo " Would create: ${CRON_FILE}"
|
|
else
|
|
backup_file "$CRON_FILE"
|
|
echo "$CRON_CONTENT" > "$CRON_FILE"
|
|
chmod 755 "$CRON_FILE"
|
|
info "Created: ${CRON_FILE}"
|
|
fi
|
|
|
|
# =====================================================
|
|
# Step 8: Validate nginx config
|
|
# =====================================================
|
|
step "Testing nginx configuration"
|
|
|
|
if [[ "$DRY_RUN" == "true" ]]; then
|
|
echo " Would run: nginx -t"
|
|
else
|
|
if nginx -t 2>&1; then
|
|
info "nginx config valid"
|
|
else
|
|
echo -e "${RED}[ERROR] nginx config test failed${NC}" >&2
|
|
echo " Restore backups (.bak.${TIMESTAMP}) and check configuration" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# =====================================================
|
|
# Step 9: Reload nginx
|
|
# =====================================================
|
|
step "Reloading nginx"
|
|
|
|
if [[ "$DRY_RUN" == "true" ]]; then
|
|
echo " Would run: systemctl reload nginx"
|
|
else
|
|
systemctl reload nginx
|
|
info "nginx reloaded"
|
|
fi
|
|
|
|
# =====================================================
|
|
# Summary
|
|
# =====================================================
|
|
echo ""
|
|
echo -e "${BOLD}Done.${NC}"
|
|
echo ""
|
|
if [[ "$USE_DBIP" == "true" ]]; then
|
|
echo " Provider: DB-IP Lite (no signup)"
|
|
else
|
|
echo " Provider: MaxMind GeoLite2"
|
|
echo " GeoIP config: ${GEOIP_CONF}"
|
|
fi
|
|
echo " Database dir: ${GEOIP_DB_DIR}"
|
|
echo " nginx geoip2: ${NGINX_GEOIP_CONF}"
|
|
echo " Log format: ${NGINX_LOG_CONF}"
|
|
echo " Cron job: ${CRON_FILE}"
|
|
echo ""
|
|
echo -e "${BOLD}Verify:${NC}"
|
|
echo " tail -5 /var/log/nginx/access.log"
|
|
echo " (should show country code and ASN org at end of each line)"
|
|
echo ""
|
|
echo " To remove: sudo $(basename "$0") --remove"
|