feat: implémentation initiale du reverse proxy de maintenance
Reverse proxy Nginx (image stable-alpine) qui sert l'app upstream ou une page de maintenance 503 selon l'IP du client. - Modes whitelist/blacklist commutables via MAINTENANCE_MODE - Liste IPv4 via MAINTENANCE_IP_LIST (séparée par virgules, validée au boot) - Logique en directives Nginx natives (geo + map), zéro Lua/njs - Page statique sobre HTML+CSS inline, zéro JS, zéro réseau sortant - Log dédié /var/log/nginx/maintenance.log pour les requêtes bloquées - Validation des env vars dans /docker-entrypoint.d/10-init.sh - Stack Docker + docker-compose pour dev local et tests - 6 cas de tests d'intégration (whitelist/blacklist x autorisée/bloquée + log + nginx -t) - Lint shellcheck propre sur tous les scripts shell
This commit is contained in:
Executable
+60
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Transforme MAINTENANCE_IP_LIST="1.2.3.4, 5.6.7.8" en un snippet Nginx :
|
||||
#
|
||||
# geo $ip_in_list {
|
||||
# default 0;
|
||||
# 1.2.3.4 1;
|
||||
# 5.6.7.8 1;
|
||||
# }
|
||||
#
|
||||
# Toute IP malformée (non IPv4 valide) provoque une sortie en erreur.
|
||||
# Usage : build-ip-list.sh <fichier_de_sortie>
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# -ne 1 ]]; then
|
||||
echo "usage: $(basename "$0") <output_file>" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
output_file="$1"
|
||||
|
||||
if [[ -z "${MAINTENANCE_IP_LIST:-}" ]]; then
|
||||
echo "ERROR: MAINTENANCE_IP_LIST est vide ou non défini." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Regex IPv4 stricte : chaque octet entre 0 et 255.
|
||||
ipv4_regex='^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$'
|
||||
|
||||
declare -a ips=()
|
||||
|
||||
# Découpe sur la virgule, trim les espaces, ignore les entrées vides.
|
||||
IFS=',' read -ra raw_entries <<< "$MAINTENANCE_IP_LIST"
|
||||
for raw in "${raw_entries[@]}"; do
|
||||
ip="${raw// /}"
|
||||
[[ -z "$ip" ]] && continue
|
||||
if ! [[ "$ip" =~ $ipv4_regex ]]; then
|
||||
echo "ERROR: IP invalide dans MAINTENANCE_IP_LIST : '$ip'" >&2
|
||||
exit 1
|
||||
fi
|
||||
ips+=("$ip")
|
||||
done
|
||||
|
||||
if [[ ${#ips[@]} -eq 0 ]]; then
|
||||
echo "ERROR: MAINTENANCE_IP_LIST ne contient aucune IP exploitable." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
{
|
||||
echo "# Généré automatiquement par scripts/build-ip-list.sh — ne pas éditer à la main."
|
||||
echo "geo \$ip_in_list {"
|
||||
echo " default 0;"
|
||||
for ip in "${ips[@]}"; do
|
||||
printf ' %-15s 1;\n' "$ip"
|
||||
done
|
||||
echo "}"
|
||||
} > "$output_file"
|
||||
|
||||
echo "build-ip-list: ${#ips[@]} IP écrites dans $output_file" >&2
|
||||
Executable
+66
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Hook d'initialisation exécuté par l'entrypoint officiel de l'image
|
||||
# nginx:stable-alpine, AVANT le script de templating envsubst (20-envsubst-on-templates.sh).
|
||||
#
|
||||
# Responsabilités :
|
||||
# 1. Valider les variables d'environnement obligatoires (MAINTENANCE_MODE,
|
||||
# MAINTENANCE_IP_LIST, UPSTREAM_HOST). Sortie 1 si invalide.
|
||||
# 2. Appliquer les valeurs par défaut documentées (LISTEN_PORT=8080, SERVER_NAME=_).
|
||||
# 3. Générer le snippet geo $ip_in_list via build-ip-list.sh.
|
||||
#
|
||||
# Le templating envsubst et le `nginx -t` final sont gérés par les scripts
|
||||
# officiels qui s'exécutent ensuite.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
log() { echo "[init] $*" >&2; }
|
||||
fail() { echo "[init] ERROR: $*" >&2; exit 1; }
|
||||
|
||||
# --- 1. Validation des variables obligatoires ---------------------------------
|
||||
|
||||
: "${MAINTENANCE_MODE:=}"
|
||||
: "${MAINTENANCE_IP_LIST:=}"
|
||||
: "${UPSTREAM_HOST:=}"
|
||||
|
||||
case "$MAINTENANCE_MODE" in
|
||||
whitelist|blacklist) ;;
|
||||
"") fail "MAINTENANCE_MODE est requis (valeurs : 'whitelist' ou 'blacklist')." ;;
|
||||
*) fail "MAINTENANCE_MODE='$MAINTENANCE_MODE' invalide (attendu : 'whitelist' ou 'blacklist')." ;;
|
||||
esac
|
||||
|
||||
if [[ -z "$MAINTENANCE_IP_LIST" ]]; then
|
||||
fail "MAINTENANCE_IP_LIST est requis (liste d'IPv4 séparées par des virgules)."
|
||||
fi
|
||||
|
||||
if [[ -z "$UPSTREAM_HOST" ]]; then
|
||||
fail "UPSTREAM_HOST est requis (ex: '127.0.0.1:3000')."
|
||||
fi
|
||||
|
||||
# Sanity check léger sur UPSTREAM_HOST : doit contenir un ':' (host:port).
|
||||
if [[ "$UPSTREAM_HOST" != *:* ]]; then
|
||||
fail "UPSTREAM_HOST='$UPSTREAM_HOST' doit être au format 'host:port'."
|
||||
fi
|
||||
|
||||
# --- 2. Valeurs par défaut ---------------------------------------------------
|
||||
|
||||
export LISTEN_PORT="${LISTEN_PORT:-8080}"
|
||||
export SERVER_NAME="${SERVER_NAME:-_}"
|
||||
export MAINTENANCE_MODE
|
||||
export UPSTREAM_HOST
|
||||
|
||||
# --- 3. Génération du snippet geo --------------------------------------------
|
||||
|
||||
snippets_dir="/etc/nginx/snippets"
|
||||
mkdir -p "$snippets_dir"
|
||||
|
||||
# Chemin fixé par le Dockerfile (COPY ... /usr/local/bin/build-ip-list.sh).
|
||||
# Surchargable via $BUILD_IP_LIST_SCRIPT pour le développement local hors conteneur.
|
||||
build_script="${BUILD_IP_LIST_SCRIPT:-/usr/local/bin/build-ip-list.sh}"
|
||||
if [[ ! -x "$build_script" ]]; then
|
||||
fail "Script attendu introuvable ou non exécutable : $build_script"
|
||||
fi
|
||||
|
||||
"$build_script" "$snippets_dir/_generated_geo.conf"
|
||||
|
||||
log "Initialisation OK (mode=$MAINTENANCE_MODE, listen=$LISTEN_PORT, upstream=$UPSTREAM_HOST)."
|
||||
Executable
+47
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Vérifie la qualité du projet :
|
||||
# 1. shellcheck sur tous les scripts shell.
|
||||
# 2. nginx -t sur la conf templatée (via un conteneur jetable).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
shell_files=(
|
||||
scripts/build-ip-list.sh
|
||||
scripts/entrypoint.sh
|
||||
scripts/lint.sh
|
||||
tests/run.sh
|
||||
tests/lib.sh
|
||||
tests/cases/whitelist_authorized_ip.sh
|
||||
tests/cases/whitelist_unauthorized_ip.sh
|
||||
tests/cases/blacklist_blocked_ip.sh
|
||||
tests/cases/blacklist_normal_ip.sh
|
||||
tests/cases/log_dedicated.sh
|
||||
tests/cases/nginx_syntax.sh
|
||||
)
|
||||
|
||||
echo "==> shellcheck"
|
||||
if command -v shellcheck >/dev/null 2>&1; then
|
||||
shellcheck -x "${shell_files[@]}"
|
||||
else
|
||||
# Fallback : shellcheck via Docker si l'outil n'est pas installé localement.
|
||||
docker run --rm -v "$PROJECT_ROOT:/mnt" -w /mnt koalaman/shellcheck:stable -x "${shell_files[@]}"
|
||||
fi
|
||||
echo "shellcheck OK"
|
||||
|
||||
echo "==> nginx -t (build d'image + check)"
|
||||
docker build --quiet -t maintenance-proxy:lint . >/dev/null
|
||||
|
||||
docker run --rm \
|
||||
-e MAINTENANCE_MODE=whitelist \
|
||||
-e MAINTENANCE_IP_LIST="1.2.3.4,5.6.7.8" \
|
||||
-e UPSTREAM_HOST="upstream:80" \
|
||||
-e LISTEN_PORT=8080 \
|
||||
-e SERVER_NAME=_ \
|
||||
--entrypoint /bin/sh \
|
||||
maintenance-proxy:lint \
|
||||
-c '/docker-entrypoint.sh nginx -t'
|
||||
echo "nginx -t OK"
|
||||
Reference in New Issue
Block a user