Il est fréquent d’avoir des applications web auxquelles il faut restreindre l’accès à certaines adresses IP seulement. Cela peut normalement se faire au niveau du serveur web :
- Apache avec mod_authz_host, comme expliqué ici.
- nginx avec ngx_http_access_module.
- lighttpd avec mod_access.
Cependant, l’utilisation de Cloudflare comme proxy inverse est de plus en plus répandue. Un proxy inverse est un service qui reçoit les requêtes des clients et les transmet à un ou plusieurs serveurs d’origine situés derrière lui. Les clients ne communiquent pas directement avec ces serveurs, mais avec le proxy inverse, qui agit comme intermédiaire entre les deux extrémités, comme le montre la figure suivante :
Dans notre scénario, le serveur d’origine doit accepter les connexions uniquement depuis les plages IP de Cloudflare (qui peuvent changer au fil du temps) et maintenir la restriction en utilisant la vraie adresse IP du client.
Table des matières
Table des matières
Cloudflare
Dans la console Cloudflare
La première partie de la solution se traite dans Cloudflare.
Dans le tableau de bord Cloudflare, allez dans Security → WAF → Custom rules et créez une règle :
- Saisissez un nom facile à identifier :
Nom : Bureau uniquement
- Créez une « expression » ou règle indiquant au WAF de Cloudflare quoi faire. C’est souvent la partie la plus complexe.
Expression (Edit expression) :
Une règle simple pourrait être
(ip.src ne a.b.c.d)
qui signifie « l’IP source n’est pas a.b.c.d ». Cela bloque tout le trafic
qui ne provient pas de votre IP de bureau avant qu’il n’atteigne votre serveur.
Si vous avez besoin d’ajouter une autre IP ultérieurement, l’expression ressemblerait à :
(ip.src ne a.b.c.d and ip.src ne e.f.g.h).
Pour appliquer une règle à des hôtes spécifiques, vous pouvez faire quelque chose comme :
(ip.src ne a.b.c.d and http.host in {"app.example.com" "admin.example.com"})
Ainsi, www.example.com ou d’autres sous-domaines pointant vers des serveurs
sans restriction restent libres.
- Sélectionnez l’action souhaitée, dans ce cas Block :
Action : Block
Cela amène Cloudflare à bloquer les requêtes provenant d’adresses IP qui ne correspondent pas à la règle.
Sur le serveur web
Comme mentionné ci-dessus, les adresses IP des proxies Cloudflare peuvent changer périodiquement. Le script suivant met à jour un fichier avec ces adresses :
#!/usr/bin/env bashset -euo pipefail
RAW="/var/lib/cloudflare/cloudflare-trusted-proxies.lst"TMP="$(mktemp)"
cleanup() { rm -f "$TMP"; }trap cleanup EXIT
mkdir -p "$(dirname "$RAW")"
{ echo "# Cloudflare trusted proxies" echo "# Generated on $(date -u '+%Y-%m-%d %H:%M:%S UTC')" echo curl -fsSL https://www.cloudflare.com/ips-v4 echo curl -fsSL https://www.cloudflare.com/ips-v6} > "$TMP"
LINES=$(grep -cE '^[0-9a-f:.\/]+$' "$TMP" || true)if [ "$LINES" -lt 5 ]; then echo "ERROR: seulement $LINES plages obtenues. Abandon." >&2 exit 1fi
if cmp -s "$TMP" "$RAW" 2>/dev/null; then echo "Aucun changement." exit 0fi
mv "$TMP" "$RAW"chmod 644 "$RAW"echo "Liste mise à jour ($LINES plages)."Ce script peut être exécuté avec cron ou systemd. N’étant pas fan de systemd, voici la procédure pour cron :
sudo chmod +x /usr/local/sbin/update-cloudflare-proxies.shsudo crontab -e# Mettre à jour les proxies Cloudflare — lundi 4h000 4 * * 1 /usr/local/sbin/update-cloudflare-proxies.sh >> /var/log/cloudflare-proxies.log 2>&1Première exécution du script :
sudo /usr/local/sbin/update-cloudflare-proxies.shIl faut maintenant indiquer au serveur web d’origine les éléments suivants :
- La liste des adresses IP des proxies Cloudflare.
- L’adresse IP autorisée.
- La restriction d’accès pour toutes les adresses IP sauf celle autorisée.
Apache
Le module remoteip est nécessaire pour obtenir la vraie adresse IP distante.
sudo a2enmod remoteipsudo systemctl restart apache2Apache lit la liste directement, sans conversion :
<IfModule mod_remoteip.c> RemoteIPHeader CF-Connecting-IP RemoteIPTrustedProxyList /var/lib/cloudflare/cloudflare-trusted-proxies.lst</IfModule>L’utilisation de RemoteIPHeader CF-Connecting-IP est essentielle, car elle
indique à Apache que la vraie adresse du client provient de Cloudflare dans
cet en-tête.
Activez cette configuration, soit en créant le lien symbolique dans
conf-enabled, soit avec :
sudo a2enmod remoteipsudo systemctl restart apache2Ensuite, appliquez la restriction pour l’IP source :
<VirtualHost *:443> ServerName www.example.com
# ... votre configuration SSL, DocumentRoot, etc. ...
# Autoriser uniquement votre IP de bureau <Location "/"> Require ip a.b.c.d 2001:db8::1 </Location></VirtualHost>sudo apache2ctl configtestsudo systemctl reload apache2nginx
nginx n’a pas d’équivalent à TrustedProxyList, il faut donc générer un snippet à partir de la liste :
#!/usr/bin/env bashset -euo pipefail
SRC="/var/lib/cloudflare/cloudflare-trusted-proxies.lst"OUT="/etc/nginx/snippets/cloudflare-trusted-proxies.conf"
{ echo "# Généré depuis $SRC — ne pas modifier" grep -E '^[0-9a-f:.\/]+$' "$SRC" | while IFS= read -r cidr; do echo "set_real_ip_from $cidr;" done echo "real_ip_header CF-Connecting-IP;"} > "$OUT"
nginx -t 2>/dev/null && systemctl reload nginxserver { listen 443 ssl; server_name app.example.com;
include snippets/cloudflare-trusted-proxies.conf;
allow a.b.c.d; allow 2001:db8::1; deny all;
# ...}lighttpd
Même situation, nécessite un snippet généré :
#!/usr/bin/env bashset -euo pipefail
SRC="/var/lib/cloudflare/cloudflare-trusted-proxies.lst"OUT="/etc/lighttpd/conf-available/90-cloudflare-trusted-proxies.conf"
{ echo "# Généré depuis $SRC — ne pas modifier" echo 'server.modules += ("mod_extforward")' echo 'extforward.headers = ("CF-Connecting-IP")' printf 'extforward.forwarder = (' FIRST=1 grep -E '^[0-9a-f:.\/]+$' "$SRC" | while IFS= read -r cidr; do [ "$FIRST" -eq 1 ] && FIRST=0 || printf ',' printf '\n "%s" => "trust"' "$cidr" done echo echo ')'} > "$OUT"
lighttpd -t -f /etc/lighttpd/lighttpd.conf 2>/dev/null && systemctl reload lighttpdinclude "conf-available/90-cloudflare-trusted-proxies.conf"
# Restriction par IP sur le vhost$HTTP["host"] == "app.example.com" { $HTTP["remoteip"] !~ "^(a\.b\.c\.d|2001:db8::1)$" { url.access-deny = ("") }}Dans le pare-feu
En guise de défense supplémentaire, configurez ufw pour que le port 443 (et 80) n’accepte que le trafic provenant des plages Cloudflare, en rejetant les connexions directes de toute autre IP :
#!/usr/bin/env bashset -euo pipefail
SRC="/var/lib/cloudflare/cloudflare-trusted-proxies.lst"
if [ ! -f "$SRC" ]; then echo "ERROR: $SRC n'existe pas. Exécutez update-cloudflare-proxies.sh d'abord." >&2 exit 1fi
CIDRS=$(grep -E '^[0-9a-f:.\/]+$' "$SRC")
if [ -z "$CIDRS" ]; then echo "ERROR: aucun CIDR trouvé dans $SRC." >&2 exit 1fi
# Supprimer les règles Cloudflare précédentes sur les ports 80,443ufw status numbered | grep -E '80,443' | grep -oP '^\[\s*\K[0-9]+' | sort -rn | while read -r num; do yes | ufw delete "$num"done
# Ajouter les règles mises à jourwhile IFS= read -r cidr; do ufw allow from "$cidr" to any port 80,443 proto tcpdone <<< "$CIDRS"
ufw reloadecho "ufw mis à jour avec $(echo "$CIDRS" | wc -l) plages."Cela empêche quiconque découvrant l’IP de votre serveur de s’y connecter directement en contournant Cloudflare.
Vérification
Depuis votre IP de bureau, accédez à https://www.example.com — cela devrait fonctionner normalement. Depuis un autre réseau (par exemple votre téléphone en données mobiles), essayez d’y accéder : vous devriez recevoir un blocage Cloudflare (erreur 1020).
Vérifiez les logs d’Apache pour confirmer que les vraies IP sont enregistrées correctement. Dans le cas d’Apache :
tail -f /var/log/apache2/access.logVous devriez voir a.b.c.d et non une IP Cloudflare.
Résumé du modèle de défense
| Couche | Ce qu’elle fait | Ce qu’elle bloque |
|---|---|---|
| Cloudflare WAF | Filtre par IP du visiteur | Trafic d’IP non autorisées avant d’atteindre le serveur |
| Apache + mod_remoteip | Filtre par vraie IP restaurée | Trafic non autorisé qui passe quand même par Cloudflare |
| ufw | Filtre par IP source au niveau réseau | Connexions directes au serveur qui contournent Cloudflare |
Les trois couches ensemble vous donnent une défense en profondeur : si l’une échoue, les autres maintiennent la restriction.
Bonus
Comment obtenir mon adresse IP de sortie ?
Vous pouvez utiliser un site comme whatismyip pour obtenir l’adresse IP publique de votre sortie.
Scripts
Vous pouvez trouver les scripts sur mon GitHub, sous licence GPL 3.0.
