Basculer le menu
Changer de menu des préférences
Basculer le menu personnel
Non connecté(e)
Votre adresse IP sera visible au public si vous faites des modifications.

« Proxmox fail2ban » : différence entre les versions

De The Linux Craftsman
Aucun résumé des modifications
 
(22 versions intermédiaires par le même utilisateur non affichées)
Ligne 66 : Ligne 66 :
filter = proxmox
filter = proxmox
backend = systemd
backend = systemd
maxretry = 3
maxretry = 5
findtime = 2d
findtime = 1d
bantime = 1h
bantime = 1w
</pre>
</pre>


Ligne 75 : Ligne 75 :
# journalctl -fu pvedaemon
# journalctl -fu pvedaemon
</pre>
</pre>
== Filtre pour PVEPROXY ==
== Filtre pour PVEPROXY ==
Il ne nous reste plus qu'à spécifier le filtre ''proxmox'' dans le fichier ''/etc/fail2ban/filter.d/proxmox.conf'' :
Il ne nous reste plus qu'à spécifier le filtre ''proxmox'' dans le fichier ''/etc/fail2ban/filter.d/proxmox.conf'' :
Ligne 113 : Ligne 114 :
Lorsque votre serveur Proxmox se trouve derrière un reverse-proxy (Apache, Nginx, Traefik, ...), au niveau 3 OSI, l'adresse IP du client est remplacée par celle du proxy et le serveur utilise l'entêtes ''X-Forwarded-For'' ou ''X-Real-IP'' pour transporter l'adresse du client  
Lorsque votre serveur Proxmox se trouve derrière un reverse-proxy (Apache, Nginx, Traefik, ...), au niveau 3 OSI, l'adresse IP du client est remplacée par celle du proxy et le serveur utilise l'entêtes ''X-Forwarded-For'' ou ''X-Real-IP'' pour transporter l'adresse du client  
[[Fichier:Reverse proxy proxmox.png|700px|centré]]
[[Fichier:Reverse proxy proxmox.png|700px|centré]]
Dans cette exemple:
* le nom de domaine utilisé est '''pve.tala-informatique.fr'''
* l'adresse du proxy est '''192.168.4.100'''
* l'adresse du serveur Proxmox est '''192.168.4.1'''
== Configuration du reverse-proxy ==
Sur votre reverse-proxy Apache, vous devriez avoir une configuration qui ressemble à ça:
<source lang=apache>
MDomain pve.tala-informatique.fr
<VirtualHost *:80>
    ServerName pve.tala-informatique.fr
    Redirect / https://pve.tala-informatique.fr/
</VirtualHost>
<VirtualHost *:443>
    ServerName pve.tala-informatique.fr
   
    SSLEngine on
    SSLProxyEngine on
    SSLProxyVerify none
    SSLProxyCheckPeerCN off
    SSLProxyCheckPeerName off
   
    ProxyRequests off
    ProxyPreserveHost On
   
    RewriteEngine on
    RewriteCond %{HTTP:Connection} Upgrade [NC]
    RewriteCond %{HTTP:Upgrade} websocket [NC]
    RewriteRule ^/?(.*) wss://192.168.4.1:8006/$1 [P,L]
    ProxyPass / https://192.168.4.1:8006/ flushpackets=On connectiontimeout=300 timeout=300
    ProxyPassReverse / https://192.168.4.1:8006/
    ProxyTimeout    600
</VirtualHost>
</source>
La première ligne renvoie à la configuration de mod_md abordée dans cette [[Httpd_certificate_security#Let's_Encrypt |article]] et permet l'obtention du certificat SSL auprès de [https://letsencrypt.org Let's encrypt]
<source lang=apache>
MDomain pve.tala-informatique.fr
</source>
Le bloc suivant permet de [[Httpd_redirect_reverse-proxy_mod-rewrite#http_→_https | renvoyer les utilisateurs]] se présentant sur le port 80 vers le port 443
<source lang=apache>
<VirtualHost *:80>
    ServerName pve.tala-informatique.fr
    Redirect / https://pve.tala-informatique.fr/
</VirtualHost>
</source>
Dans le bloc ''443'', on est obligé de désactiver les vérifications SSL parce que le certificat du serveur Proxmox est autosigné (donc pas de cadenas vert), ce qui pousse Apache à bloquer la connexion si on ne désactive pas ces vérifications
<source lang=apache>
SSLProxyVerify none
SSLProxyCheckPeerCN off
SSLProxyCheckPeerName off
</source>
La réécriture d'url doit être configuré pour prendre en compte les ''websockets sécurisées'' (wss) utilisées pour l'affichage de l'écran des machine ou conteneurs avec ''xterm.js''
<source lang=apache>
RewriteEngine on
RewriteCond %{HTTP:Connection} Upgrade [NC]
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteRule ^/?(.*) wss://192.168.4.1:8006/$1 [P,L]
</source>
Enfin on redirige les requêtes vers le serveur Proxmox en n'oubliant pas de préciser le port 8006
<source lang=apache>
ProxyPass / https://192.168.4.1:8006/ flushpackets=On connectiontimeout=300 timeout=300
ProxyPassReverse / https://192.168.4.1:8006/
</source>
== Problème lié à la proxyfication ==
Le problème est que la seule adresse que fail2ban va voir apparaître dans les logs est celle du reverse-proxy (''192.168.4.100'') :
Le problème est que la seule adresse que fail2ban va voir apparaître dans les logs est celle du reverse-proxy (''192.168.4.100'') :
[[Fichier:Reverse proxy ip logs.png|600px|centré]]
[[Fichier:Reverse proxy ip logs.png|600px|centré]]
Ligne 130 : Ligne 201 :
Dec 14 10:43:01 labo unix_chkpwd[1326506]: password check failed for user (root)
Dec 14 10:43:01 labo unix_chkpwd[1326506]: password check failed for user (root)
Dec 14 10:43:01 labo IPCC.xs[1320783]: pam_unix(proxmox-ve-auth:auth): authentication failure; logname= uid=0 euid=0 tty= ruser= rhost=37.167.23.159  user=root
Dec 14 10:43:01 labo IPCC.xs[1320783]: pam_unix(proxmox-ve-auth:auth): authentication failure; logname= uid=0 euid=0 tty= ruser= rhost=37.167.23.159  user=root
Dec 14 10:43:03 labo pvedaemon[1320783]: authentication failure; rhost=37.167.23.159 user=root@pam msg=Authentication failure
Dec 14 10:43:03 labo pvedaemon[1320783]: authentication failure; rhost=192.168.4.100 user=root@pam msg=Authentication failure
</pre>
</pre>
== Configuration de pveproxy ==
== Configuration de pveproxy ==
Si on veut voir l'adresse du client, il faut dire au [https://fr.wikipedia.org/wiki/Daemon_(informatique)| démon] ''pveproxy'' d'utiliser l'une des deux entêtes précédentes ainsi que préciser l'adresse du proxy.  
Si on veut voir l'adresse du client, il faut dire au [https://fr.wikipedia.org/wiki/Daemon_(informatique)| démon] ''pveproxy'' d'utiliser l'une des deux entêtes précédentes ainsi que préciser l'adresse du proxy.  
Ligne 158 : Ligne 230 :


Il faut repérer la fonction suivante:
Il faut repérer la fonction suivante:
<source lang=perl>
<syntaxhighlight lang=perl>
sub authenticate_and_handle_request {
sub authenticate_and_handle_request {
     my ($self, $reqstate) = @_;
     my ($self, $reqstate) = @_;
Ligne 166 : Ligne 238 :


...
...
</source>
</syntaxhighlight>
C'est un peu plus loin qu'il faut insérer le code :
C'est un peu plus loin qu'il faut insérer le code :
<source lang=perl>
<syntaxhighlight lang=perl>
         }
         }
     }
     }
Ligne 177 : Ligne 249 :
         my $connect_str = $request->header('Host');
         my $connect_str = $request->header('Host');


</source>
</syntaxhighlight>
Il faut modifier l'attribut ''peer_host'' de l'objet ''reqstate'' si l'une des deux entêtes est présente :
Il faut modifier l'attribut ''peer_host'' de l'objet ''reqstate'' si l'une des deux entêtes est présente :
<source lang=perl>
<syntaxhighlight lang=perl>
if ($request->header('X-Forwarded-For')) {
if ($request->header('X-Forwarded-For')) {
   $reqstate->{peer_host} = $request->header('X-Forwarded-For');
   $reqstate->{peer_host} = $request->header('X-Forwarded-For');
Ligne 185 : Ligne 257 :
   $reqstate->{peer_host} = $request->header('X-Real-IP');
   $reqstate->{peer_host} = $request->header('X-Real-IP');
}
}
</source>
</syntaxhighlight>
=== Double IPv4 ou préfixe IPv6 ===
=== Double IPv4 ou préfixe IPv6 ===
Après redémarrage, cela permet de voir l'adresse du client dans les logs :
Après redémarrage, cela permet de voir l'adresse du client dans les logs :
Ligne 210 : Ligne 282 :
On a deux possibilités pour faire le nettoyage :
On a deux possibilités pour faire le nettoyage :
*Soit on ajoute les lignes suivantes dans ''AnyEvent.pm'':
*Soit on ajoute les lignes suivantes dans ''AnyEvent.pm'':
<source lang=perl>
<syntaxhighlight lang=perl>
if (index($reqstate->{peer_host}, ',') != -1) {
if (index($reqstate->{peer_host}, ',') != -1) {
   # Remove trailing ip  
   # Remove trailing ip  
Ligne 220 : Ligne 292 :
   $reqstate->{peer_host} = $1;
   $reqstate->{peer_host} = $1;
}
}
</source>
</syntaxhighlight>
*Soit on modifie le filtre /etc/fail2ban/filter.d/proxmox.conf :
*Soit on modifie le filtre /etc/fail2ban/filter.d/proxmox.conf :
<pre>
<pre>
Ligne 231 : Ligne 303 :
On peut automatiser le processus précédent avec le script suivant, que l'on peut créer dans /root/remoteip_fix.sh :
On peut automatiser le processus précédent avec le script suivant, que l'on peut créer dans /root/remoteip_fix.sh :


<source lang=bash>
<syntaxhighlight lang=bash>
#!/bin/bash
#!/bin/bash


Ligne 285 : Ligne 357 :
trap 0
trap 0
echo "done."
echo "done."
</source>
</syntaxhighlight>
Il ne reste plus qu'à le rendre exécutable et le lancer :
Il ne reste plus qu'à le rendre exécutable et le lancer :
<pre>
<pre>
Ligne 292 : Ligne 364 :
</pre>
</pre>
Il ne faudra pas oublier de redémarrer ''pveproxy'' pour appliquer les changements !
Il ne faudra pas oublier de redémarrer ''pveproxy'' pour appliquer les changements !
== Comment bannir derrière un reverse-proxy ? ==
Maintenant que votre Proxmox est derrière un reverse-proxy, inutile de bannir les IP publiques avec iptables sur le serveur Proxmox, la seule IP visible est celle du reverse-proxy (dans cet exemple 192.168.4.100).
La seule machine capable de bannir est le reverse-proxy, il faut donc lui transmettre les IP et nous allons faire ça en trois étapes:
* la première est de créer une "prison" proxmox sur le fail2ban présent sur le reverse-proxy
* la deuxième est l'invention d'une API pour fail2ban sur le reverse-proxy
* la troisième est la modification du comportement de fail2ban pour appeler cette API au lieu de modifier iptables
=== Création d'une prison sur le reverse-proxy ===
Pour bannir nous allons créer une prison spécifique pour le / les serveurs Proxmox que nous allons appeler ''proxmox'' dans le fichier /etc/fail2ban/jail.d/proxmox.conf :
<pre>
[proxmox]
enabled = true
port = https,http
filter = do-nothing
logpath = /dev/null
maxretry = 3
findtime = 1d
bantime = 1w
</pre>
J'attire votre attention sur les paramètres ''maxretry'', ''findtime'' et ''bantime'' qui doivent concorder avec ceux du fail2ban de votre Proxmox. S'il sont plus ''court'' sur le reverse-proxy, les IP seront libérées plus tôt à l'inverse, plus long n'est pas gênant, le fail2ban de votre Proxmox les libéreras avant celui du proxy.
Vous avez remarquez le filtre ''do-nothing'' qu'il nous reste à créer dans le fichier ''/etc/fail2ban/filter.d/do-nothing.conf'' :
<pre>
[Definition]
failregex = ^<HOST>$
</pre>
Après redémarrage de fail2ban :
<source lang=bash>
# systemctl restart fail2ban
</source>
Vous devriez voir la prison
<source lang=bash>
# fail2ban-client status
Status
|- Number of jail:      2
`- Jail list:  proxmox, sshd
[root@proxy proxymox]#
</source>
Nous pouvons maintenant passer à l'API !
=== Invention d'une API pour fail2ban ===
Toujours sur le reverse-proxy nous allons installer PHP pour créer un mini serveur qui répondra au urls suivantes:
* /ban/<ip> pour bannir
* /unban/<ip> pour lever le banissement
Commençons par installer PHP:
<source lang=bash>
# dnf -y install php-cli
</source>
Nous allons créer un répertoire "f2b-api" dans le dossier "opt" :
<source lang=bash>
# mkdir /opt/f2b-api
</source>
Puis créer un fichier ''f2b-api.php'' dans ce dossier:
<source lang=php>
<?php
main();
function main() {
        # On clean l'uri du / de début
        $uri = ltrim($_SERVER['REQUEST_URI'], '/');
        # On éclate l'uri en parties (action + ip)
        $parts = explode('/', $uri);
        # Récupération de l'action dans une variable ou false si vide
        $action = empty($parts[0]) ? false : $parts[0];
        # Récupération de l'ip dans une variable ou false si vide
        $ip = empty($parts[1]) ? false : $parts[1];
        if(!$action || !$ip){
                # Si action vide => 400 Bad request
                http_response_code(400);
                die();
        }
        if(!in_array($action, ['ban', 'unban'])){
                # Si action pas dans la liste => 400 Bad request
                http_response_code(400);
                die();
        }
        $cmd = '';
        if($action == 'ban'){
                $cmd = 'fail2ban-client set proxmox banip ' . $ip;
        } else if($action == 'unban'){
                $cmd = 'fail2ban-client unban proxmox ' . $ip;
        }
        shell_exec($cmd);
}
</source>
[[Fichier:Warning-icon.png|40px]]  Gardez en tête que ce code simpliste n'est en aucun cas sécurisé et ne doit pas être accessible depuis Internet.
Pour démarrer se code en mode serveur il suffit de taper la commande suivante:
<source lang=bash>
# php -S 0.0.0.0:8080 -t /opt/f2b-api /opt/f2b-api/f2b-api.php
</source>
Vous pouvez tester directement depuis un autre terminal en tapant les commandes:
* pour bannir :
<source lang=bash>
# curl -X GET http://127.0.0.1:8080/ban/1.2.3.4
</source>
* pour dé-bannir :
<source lang=bash>
# curl -X GET http://127.0.0.1:8080/unban/1.2.3.4
</source>
Et en vérifiant dans fail2ban:
<source lang=bash>
# fail2ban-client status proxmox
Status for the jail: proxmox
|- Filter
|  |- Currently failed: 0
|  |- Total failed:    0
|  `- File list:        /dev/null
`- Actions
  |- Currently banned: 1
  |- Total banned:    10
  `- Banned IP list:  1.2.3.4
</source>
Nous n'allons pas lancer nous même le serveur PHP, nous allons demander à SystemD de le faire et pour cela nous allons créer le fichier ''/opt/f2b-api/f2b-api.service'':
<pre>
[Unit]
Description=fail2ban API Server
After=network-online.target
[Service]
Type=simple
UMask=007
ExecStart=php -S 0.0.0.0:8080 -t /opt/f2b-api /opt/f2b-api/f2b-api.php
Restart=on-failure
TimeoutStopSec=300
[Install]
WantedBy=multi-user.target
</pre>
Il faut maintenant demander à SystemD de s'occuper de notre service:
<source lang=bash>
# systemctl link /opt/f2b-api/f2b-api.service
</source>
Et de le lancer au démarrage du reverse-proxy:
<source lang=bash>
# systemctl enable f2b-api --now
</source>
Nous pouvons maintenant nous déplacer sur le serveur Proxmox pour modifier fail2ban !
=== Modification des actions fail2ban ===
Sur le serveur Proxmox nous allons débrayer le comportement de base de fail2ban qui est de modifier iptables. pour cela nous alons créer le fichier ''/etc/fail2ban/action.d/f2b-api.conf :
<pre>
[Definition]
actionstart =
actionstop =
actioncheck =
actionban = curl -X GET 192.168.4.100:8080/ban/<ip>
actionunban = curl -X GET 192.168.4.100:8080/unban/<ip>
</pre>
Veuillez noter que:
* même si elles sont vide, les lignes ''actionstart'', ''actionstop'' et ''actioncheck'' sont obligatoires !
* l'adresse ''192.168.4.100'' doit être remplcée par celle de votre reverse-proxy
* <ip> est l'expression utilisée par fail2ban et elle sera remplacée par l'IP à bannir
Pour ce fichier d'action soit pris en compte, il faut modifier le fichier ''/etc/fail2ban/jail.d/proxmox.conf'' pour y ajouter:
<pre>
banaction = f2b-api
</pre>
Il ne vous reste plus qu'a redémarrer le service fai2ban sur le serveur Proxmox:
<source lang=bash>
# systemctl restart fail2ban
</source>
Et vous pouvez tester les commandes suivantes:
* pour bannir
<source lang=bash>
# fail2ban-client set proxmox banip 1.2.3.4
</source>
* pour dé-bannir:
<source lang=bash>
# fail2ban-client unban proxmox 1.2.3.4
</source>
Et vérifier sur le reverse-proxy que les modification sont bien prises en compte:
<source lang=bash>
# fail2ban-client status proxmox
Status for the jail: proxmox
|- Filter
|  |- Currently failed: 0
|  |- Total failed:    0
|  `- File list:        /dev/null
`- Actions
  |- Currently banned: 1
  |- Total banned:    10
  `- Banned IP list:  1.2.3.4
</source>

Dernière version du 13 juillet 2026 à 11:43

Introduction

Proxmox utilise une interface web d'administration qui utilise le port TCP 8006 et c'est un vecteur possible d'attaque par force brut. Nous allons donc dresser fail2ban pour agir en cas d'attaque !

Proxmox utilise Debian, nous allons principalement reproduire les étapes décrites dans le tutoriel de Fail2ban pour Rocky et adapter à Debian.

Avant d'aller plus loin, assurez-vous d'avoir correctement installé et configuré iptables sur Proxmox

Installation

# apt -y install fail2ban

Configuration

Tout d'abord il faut copier le fichier /etc/fail2ban/jail.conf en /etc/fail2ban/jail.local

cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

Dans ce fichier, on va s'intéresser aux variables suivantes:

  • ignoreip : correspond à la suite d'adresses IP qui ne se feront jamais bannir;
  • maxretry : correspond au nombre d'essais;
  • findtime : correspond à la période pendant laquelle les essais vont incrémenter maxretry;
  • bantime  : correspond au temps où l'adresse IP ne peut pas se connecter.

Configuration de base

Choix de la punition

Il faut choisir quelque chose de cohérent (une punition suffisante) pour ne pas permettre de se faire cracker son mot de passe:

  • maxretry = 3
  • findtime = 86400 (correspond à 1 journée)
  • bantime = 604800 (correspond à 1 semaine)

Cela signifie que si une adresse IP se trompe 3 fois en 1 journée (86400s) elle se fait bannir pendant 1 semaine (604800s).

Un rapide calcul permet de trouver le nombre maximal de tentatives durant une année:

365 jours / 7 jours par semaine * 3 tentatives = 156 essais, ce qui reste raisonnable.

Si le pirate possède un botnet, il faut bien sûr multiplier ce nombre par le nombre de machines dans le botnet...

Attention : fail2ban parcours les logs de connexion pour connaître le numéro de la tentative, ce qui a pour conséquence, si le findtime est grand, de prendre un certain temps...

Jail pour SSH

Il suffit d'ajouter la ligne enabled=true dans la section à activer !

[sshd]

# To use more aggressive sshd modes set filter parameter "mode" in jail.local:
# normal (default), ddos, extra or aggressive (combines all).
# See "tests/files/logs/sshd" or "filter.d/sshd.conf" for usage example and details.
#mode   = normal
enabled = true
port    = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s

Configuration spécifique

Jail pour PVEPROXY

Pveproxy écoute sur le port 8006 et ne fonctionne pas exactement comme un serveur Apache httpd qui mettrait ces logs dans le fichier error_log.

Nous allons mettre cette configuration spécifique dans le fichier /etc/fail2ban/jail.d/proxmox.conf :

[proxmox]
enabled = true
port = https,http,8006
filter = proxmox
backend = systemd
maxretry = 5
findtime = 1d
bantime = 1w

Le backend utilisé est systemd, exactement comme si vous utilisiez la commande :

# journalctl -fu pvedaemon

Filtre pour PVEPROXY

Il ne nous reste plus qu'à spécifier le filtre proxmox dans le fichier /etc/fail2ban/filter.d/proxmox.conf :

[Definition]
failregex = pvedaemon\[.*authentication failure; rhost=<HOST> user=.* msg=.*
ignoreregex =
journalmatch = _SYSTEMD_UNIT=pvedaemon.service

Tests

Il faut maintenant tester le filtre et pour ça on peut utiliser un partage de connexion et réaliser le nombre d'échecs spécifié par la directive "maxretry" (ici 3) :

# fail2ban-client status proxmox
Status for the jail: proxmox
|- Filter
|  |- Currently failed:	0
|  |- Total failed:	3
|  `- Journal matches:	_SYSTEMD_UNIT=pvedaemon.service
`- Actions
   |- Currently banned:	1
   |- Total banned:	1
   `- Banned IP list:	37.167.23.159

On voit bien l'IP 37.167.23.159 qui est bannie ! N'oubliez pas de l'ajouter à ignoreip dans le fichier jails.local ou de faire

# fail2ban-client unban 37.167.23.159

Démarrage et enregistrement dans le chargeur de démarrage

# systemctl start fail2ban.service
# systemctl enable fail2ban.service

Vous pouvez maintenant démarrer fail2ban

Proxification

Lorsque votre serveur Proxmox se trouve derrière un reverse-proxy (Apache, Nginx, Traefik, ...), au niveau 3 OSI, l'adresse IP du client est remplacée par celle du proxy et le serveur utilise l'entêtes X-Forwarded-For ou X-Real-IP pour transporter l'adresse du client

Dans cette exemple:

  • le nom de domaine utilisé est pve.tala-informatique.fr
  • l'adresse du proxy est 192.168.4.100
  • l'adresse du serveur Proxmox est 192.168.4.1

Configuration du reverse-proxy

Sur votre reverse-proxy Apache, vous devriez avoir une configuration qui ressemble à ça:

MDomain pve.tala-informatique.fr

<VirtualHost *:80>
    ServerName pve.tala-informatique.fr
    Redirect / https://pve.tala-informatique.fr/
</VirtualHost>

<VirtualHost *:443>
    ServerName pve.tala-informatique.fr
    
    SSLEngine on
    SSLProxyEngine on
    SSLProxyVerify none 
    SSLProxyCheckPeerCN off
    SSLProxyCheckPeerName off
    
    ProxyRequests off
    ProxyPreserveHost On
    
    RewriteEngine on
    RewriteCond %{HTTP:Connection} Upgrade [NC]
    RewriteCond %{HTTP:Upgrade} websocket [NC]
    RewriteRule ^/?(.*) wss://192.168.4.1:8006/$1 [P,L]

    ProxyPass / https://192.168.4.1:8006/ flushpackets=On connectiontimeout=300 timeout=300
    ProxyPassReverse / https://192.168.4.1:8006/
    ProxyTimeout     600

</VirtualHost>

La première ligne renvoie à la configuration de mod_md abordée dans cette article et permet l'obtention du certificat SSL auprès de Let's encrypt

MDomain pve.tala-informatique.fr

Le bloc suivant permet de renvoyer les utilisateurs se présentant sur le port 80 vers le port 443

<VirtualHost *:80>
    ServerName pve.tala-informatique.fr
    Redirect / https://pve.tala-informatique.fr/
</VirtualHost>

Dans le bloc 443, on est obligé de désactiver les vérifications SSL parce que le certificat du serveur Proxmox est autosigné (donc pas de cadenas vert), ce qui pousse Apache à bloquer la connexion si on ne désactive pas ces vérifications

SSLProxyVerify none 
SSLProxyCheckPeerCN off
SSLProxyCheckPeerName off

La réécriture d'url doit être configuré pour prendre en compte les websockets sécurisées (wss) utilisées pour l'affichage de l'écran des machine ou conteneurs avec xterm.js

RewriteEngine on
RewriteCond %{HTTP:Connection} Upgrade [NC]
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteRule ^/?(.*) wss://192.168.4.1:8006/$1 [P,L]

Enfin on redirige les requêtes vers le serveur Proxmox en n'oubliant pas de préciser le port 8006

ProxyPass / https://192.168.4.1:8006/ flushpackets=On connectiontimeout=300 timeout=300
ProxyPassReverse / https://192.168.4.1:8006/

Problème lié à la proxyfication

Le problème est que la seule adresse que fail2ban va voir apparaître dans les logs est celle du reverse-proxy (192.168.4.100) :

Même si le filtre fail2ban est configuré pour utiliser la commande journactl sur le démon pvedaemon:

...
journalmatch = _SYSTEMD_UNIT=pvedaemon.service

les adresses viennent de pveproxy qui journalise tout dans /var/log/pveproxy/access.log et pvedaemon ne journalise que les échecs d'authentification. Il est donc plus simple de faire un tail ou un cat:

# tail -f /var/log/pveproxy/access.log

Que d'utiliser journalctl pour voir les IPs :

# journalctl -fu pvedaemon
Dec 14 10:43:01 labo unix_chkpwd[1326506]: password check failed for user (root)
Dec 14 10:43:01 labo IPCC.xs[1320783]: pam_unix(proxmox-ve-auth:auth): authentication failure; logname= uid=0 euid=0 tty= ruser= rhost=37.167.23.159  user=root
Dec 14 10:43:03 labo pvedaemon[1320783]: authentication failure; rhost=192.168.4.100 user=root@pam msg=Authentication failure

Configuration de pveproxy

Si on veut voir l'adresse du client, il faut dire au démon pveproxy d'utiliser l'une des deux entêtes précédentes ainsi que préciser l'adresse du proxy.

Préciser l'adresse du proxy est essentiel pour que pveproxy n'accepte de lire l'adresse du client présente dans l'entête uniquement si cela provient du proxy, qui est une machine de confiance. Le cas contraire, tout le monde pourrait ajouter une adresse IP dans l'entête pour la faire bannir...

Nous allons créer le fichier /etc/default/pveproxy avec les lignes suivantes:

PROXY_REAL_IP_HEADER="X-Forwarded-For"
PROXY_REAL_IP_HEADER="X-Real-IP"
PROXY_REAL_IP_ALLOW_FROM="192.168.4.100"

Il faut maintenant redémarrer pveproxy pour que les changements s'appliquent. Si vous êtes connecté via l'interface web, cela va vous déconnecter, le temps que le démon redémarre, il faudra ensuite certainement rafraîchir la page web.

# systemctl restart pveproxy

Il ne reste plus qu'à vérifier dans les logs si les bonnes adresses s'affichent:

# tail -f /var/log/pveproxy/access.log

Si jamais ce n'est pas le cas, poursuivez avec la modification du fichier AnyEvent.pm

Modification de AnyEvent.pm

Application de la modification

Au jour d'aujourd'hui (13/12/25) la proxification ne fonctionne plus et il faut apporter une modification dans le fichier /usr/share/perl5/PVE/APIServer/AnyEvent.pm pour que les bonnes adresses apparaissent ! En fonction des versions cette modification peut avoir lieu soit ligne 1504, soit ligne 1554.

Il faut repérer la fonction suivante:

sub authenticate_and_handle_request {
    my ($self, $reqstate) = @_;

    my $request = $reqstate->{request};
    my $method = $request->method();

...

C'est un peu plus loin qu'il faut insérer le code :

        }
    }

 ### INSERTION ICI ###

    if ($self->{spiceproxy}) {
        my $connect_str = $request->header('Host');

Il faut modifier l'attribut peer_host de l'objet reqstate si l'une des deux entêtes est présente :

if ($request->header('X-Forwarded-For')) {
   $reqstate->{peer_host} = $request->header('X-Forwarded-For');
} elsif ($request->header('X-Real-IP')) {
   $reqstate->{peer_host} = $request->header('X-Real-IP');
}

Double IPv4 ou préfixe IPv6

Après redémarrage, cela permet de voir l'adresse du client dans les logs :

# tail -f /var/log/pveproxy/access.log
37.167.182.207, 37.167.182.207 - - [13/12/2025:18:48:18 +0100] "GET /pve2/ext6/ext-all.js?ver=7.0.0 HTTP/1.1" 200 683505
37.167.182.207, 37.167.182.207 - - [13/12/2025:18:48:18 +0100] "GET /pve2/js/u2f-api.js HTTP/1.1" 200 4898
::ffff:192.168.20.22 - - [14/12/2025:09:34:42 +0100] "GET /api2/json/cluster/resources HTTP/1.1" 200 1226
::ffff:192.168.20.22 - - [14/12/2025:09:34:42 +0100] "GET /api2/json/cluster/tasks HTTP/1.1" 200 1090
37.167.182.207, 37.167.182.207 - - [13/12/2025:18:48:18 +0100] "GET /proxmoxlib.js?ver=v5.0.4-t1754316706 HTTP/1.1" 200 157086
37.167.182.207, 37.167.182.207 - - [13/12/2025:18:48:18 +0100] "GET /pve2/ext6/theme-crisp/resources/theme-crisp-all_1.css HTTP/1.1" 200 32919
::ffff:192.168.20.22 - root@pam [14/12/2025:09:34:42 +0100] "GET /api2/json/nodes/labo/lxc/100/status/current HTTP/1.1" 200 523

Il nous reste un peu de nettoyage à faire, dans certains cas :

  • l'adresse apparaît non pas une fois mais deux, séparées par une virgule
37.167.182.207, 37.167.182.207
  • l'adresse IPv4 (32 bits) est affichée dans sa représentation IPv6 (128 bits), avec le préfixe ::ffff:
::ffff:192.168.20.22

On a deux possibilités pour faire le nettoyage :

  • Soit on ajoute les lignes suivantes dans AnyEvent.pm:
if (index($reqstate->{peer_host}, ',') != -1) {
   # Remove trailing ip 
   my @fields = split /,/, $reqstate->{peer_host};
   $reqstate->{peer_host} = $fields[0];
}
if($reqstate->{peer_host} =~ /.*\:([\d]*\..*)/) {
   # Remove IPv6 subnet for IPv4
   $reqstate->{peer_host} = $1;
}
  • Soit on modifie le filtre /etc/fail2ban/filter.d/proxmox.conf :
...
failregex = pvedaemon\[.*authentication failure; rhost=.*:?<HOST>,? user=.* msg=.*
...

Automatisation

On peut automatiser le processus précédent avec le script suivant, que l'on peut créer dans /root/remoteip_fix.sh :

#!/bin/bash

ANYEVENT_PATH="/usr/share/perl5/PVE/APIServer/AnyEvent.pm"
ANYEVENT_REGEX='$self->{spiceproxy}'
TMP_FILE="/tmp/AnyEvent.pm.tmp"

if [ ! -f $ANYEVENT_PATH ]; then
  echo "$ANYEVENT_PATH does not exists !"
  exit 1
fi

if [ $(grep '### BUGFIX REMOTE_IP FOR FAIL2BAN ###' $ANYEVENT_PATH | wc -l) -eq 1 ]; then
  echo "AnyEvent.pm already patched !"
  exit 0
fi

LINE_NUMBER_TOP=$(nl -ba $ANYEVENT_PATH | grep $ANYEVENT_REGEX | head -n 1 | awk -F ' ' '{print $1}')
let LINE_NUMBER_TOP-=1

if [ $LINE_NUMBER_TOP -lt 0 ]; then
  echo "Did not find the $ANYEVENT_REGEX expr in $ANYEVENT_PATH"
  exit 1
fi

echo -n "Applying bugfix: "
trap "rm -f $TMP_FILE" 0 1 2 5 13 15
head -n $LINE_NUMBER_TOP $ANYEVENT_PATH >$TMP_FILE
cat >>$TMP_FILE <<EOF
### BUGFIX REMOTE_IP FOR FAIL2BAN ###
    if (\$request->header('X-Forwarded-For')) {
        \$reqstate->{peer_host} = \$request->header('X-Forwarded-For');
    } elsif (\$request->header('X-Real-IP')) {
        \$reqstate->{peer_host} = \$request->header('X-Real-IP');
    }
    if (index(\$reqstate->{peer_host}, ',') != -1) {
        my @fields = split /,/, \$reqstate->{peer_host};
        \$reqstate->{peer_host} = \$fields[0];
    }
    if(\$reqstate->{peer_host} =~ /.*\:([\d]*\..*)/) {
        # Remove IPv6 subnet for IPv4
        \$reqstate->{peer_host} = \$1;
    }
#####################################
EOF
echo "$ANYEVENT_MODIF" >>$TMP_FILE
cp $ANYEVENT_PATH $ANYEVENT_PATH.ori
LINE_NUMBER_BOTTOM=$(cat $ANYEVENT_PATH | wc -l)
let LINE_NUMBER_BOTTOM-=LINE_NUMBER_TOP
tail -n $LINE_NUMBER_BOTTOM $ANYEVENT_PATH >>$TMP_FILE
cat $TMP_FILE >$ANYEVENT_PATH
rm -f $TMP_FILE
trap 0
echo "done."

Il ne reste plus qu'à le rendre exécutable et le lancer :

# chmod +x /root/remoteip_fix.sh
# /root/remoteip_fix.sh

Il ne faudra pas oublier de redémarrer pveproxy pour appliquer les changements !

Comment bannir derrière un reverse-proxy ?

Maintenant que votre Proxmox est derrière un reverse-proxy, inutile de bannir les IP publiques avec iptables sur le serveur Proxmox, la seule IP visible est celle du reverse-proxy (dans cet exemple 192.168.4.100).

La seule machine capable de bannir est le reverse-proxy, il faut donc lui transmettre les IP et nous allons faire ça en trois étapes:

  • la première est de créer une "prison" proxmox sur le fail2ban présent sur le reverse-proxy
  • la deuxième est l'invention d'une API pour fail2ban sur le reverse-proxy
  • la troisième est la modification du comportement de fail2ban pour appeler cette API au lieu de modifier iptables

Création d'une prison sur le reverse-proxy

Pour bannir nous allons créer une prison spécifique pour le / les serveurs Proxmox que nous allons appeler proxmox dans le fichier /etc/fail2ban/jail.d/proxmox.conf :

[proxmox]
enabled = true
port = https,http
filter = do-nothing
logpath = /dev/null
maxretry = 3
findtime = 1d
bantime = 1w

J'attire votre attention sur les paramètres maxretry, findtime et bantime qui doivent concorder avec ceux du fail2ban de votre Proxmox. S'il sont plus court sur le reverse-proxy, les IP seront libérées plus tôt à l'inverse, plus long n'est pas gênant, le fail2ban de votre Proxmox les libéreras avant celui du proxy.

Vous avez remarquez le filtre do-nothing qu'il nous reste à créer dans le fichier /etc/fail2ban/filter.d/do-nothing.conf :

[Definition]

failregex = ^<HOST>$

Après redémarrage de fail2ban :

# systemctl restart fail2ban

Vous devriez voir la prison

# fail2ban-client status
Status
|- Number of jail:      2
`- Jail list:   proxmox, sshd
[root@proxy proxymox]#

Nous pouvons maintenant passer à l'API !

Invention d'une API pour fail2ban

Toujours sur le reverse-proxy nous allons installer PHP pour créer un mini serveur qui répondra au urls suivantes:

  • /ban/<ip> pour bannir
  • /unban/<ip> pour lever le banissement

Commençons par installer PHP:

# dnf -y install php-cli

Nous allons créer un répertoire "f2b-api" dans le dossier "opt" :

# mkdir /opt/f2b-api

Puis créer un fichier f2b-api.php dans ce dossier:

<?php

main();

function main() {
        # On clean l'uri du / de début
        $uri = ltrim($_SERVER['REQUEST_URI'], '/');
        # On éclate l'uri en parties (action + ip)
        $parts = explode('/', $uri);
        # Récupération de l'action dans une variable ou false si vide
        $action = empty($parts[0]) ? false : $parts[0];
        # Récupération de l'ip dans une variable ou false si vide
        $ip = empty($parts[1]) ? false : $parts[1];
        if(!$action || !$ip){
                # Si action vide => 400 Bad request
                http_response_code(400);
                die();
        }
        if(!in_array($action, ['ban', 'unban'])){
                # Si action pas dans la liste => 400 Bad request
                http_response_code(400);
                die();
        }
        $cmd = '';
        if($action == 'ban'){
                $cmd = 'fail2ban-client set proxmox banip ' . $ip;
        } else if($action == 'unban'){
                $cmd = 'fail2ban-client unban proxmox ' . $ip;
        }
        shell_exec($cmd);
}

Gardez en tête que ce code simpliste n'est en aucun cas sécurisé et ne doit pas être accessible depuis Internet.

Pour démarrer se code en mode serveur il suffit de taper la commande suivante:

# php -S 0.0.0.0:8080 -t /opt/f2b-api /opt/f2b-api/f2b-api.php

Vous pouvez tester directement depuis un autre terminal en tapant les commandes:

  • pour bannir :
# curl -X GET http://127.0.0.1:8080/ban/1.2.3.4
  • pour dé-bannir :
# curl -X GET http://127.0.0.1:8080/unban/1.2.3.4

Et en vérifiant dans fail2ban:

# fail2ban-client status proxmox
Status for the jail: proxmox
|- Filter
|  |- Currently failed: 0
|  |- Total failed:     0
|  `- File list:        /dev/null
`- Actions
   |- Currently banned: 1
   |- Total banned:     10
   `- Banned IP list:   1.2.3.4

Nous n'allons pas lancer nous même le serveur PHP, nous allons demander à SystemD de le faire et pour cela nous allons créer le fichier /opt/f2b-api/f2b-api.service:

[Unit]
Description=fail2ban API Server
After=network-online.target

[Service]
Type=simple
UMask=007
ExecStart=php -S 0.0.0.0:8080 -t /opt/f2b-api /opt/f2b-api/f2b-api.php
Restart=on-failure
TimeoutStopSec=300

[Install]
WantedBy=multi-user.target

Il faut maintenant demander à SystemD de s'occuper de notre service:

# systemctl link /opt/f2b-api/f2b-api.service

Et de le lancer au démarrage du reverse-proxy:

# systemctl enable f2b-api --now

Nous pouvons maintenant nous déplacer sur le serveur Proxmox pour modifier fail2ban !

Modification des actions fail2ban

Sur le serveur Proxmox nous allons débrayer le comportement de base de fail2ban qui est de modifier iptables. pour cela nous alons créer le fichier /etc/fail2ban/action.d/f2b-api.conf :

[Definition]
actionstart =
actionstop =
actioncheck =
actionban = curl -X GET 192.168.4.100:8080/ban/<ip>
actionunban = curl -X GET 192.168.4.100:8080/unban/<ip>

Veuillez noter que:

  • même si elles sont vide, les lignes actionstart, actionstop et actioncheck sont obligatoires !
  • l'adresse 192.168.4.100 doit être remplcée par celle de votre reverse-proxy
  • <ip> est l'expression utilisée par fail2ban et elle sera remplacée par l'IP à bannir

Pour ce fichier d'action soit pris en compte, il faut modifier le fichier /etc/fail2ban/jail.d/proxmox.conf pour y ajouter:

banaction = f2b-api

Il ne vous reste plus qu'a redémarrer le service fai2ban sur le serveur Proxmox:

# systemctl restart fail2ban

Et vous pouvez tester les commandes suivantes:

  • pour bannir
# fail2ban-client set proxmox banip 1.2.3.4
  • pour dé-bannir:
# fail2ban-client unban proxmox 1.2.3.4

Et vérifier sur le reverse-proxy que les modification sont bien prises en compte:

# fail2ban-client status proxmox
Status for the jail: proxmox
|- Filter
|  |- Currently failed: 0
|  |- Total failed:     0
|  `- File list:        /dev/null
`- Actions
   |- Currently banned: 1
   |- Total banned:     10
   `- Banned IP list:   1.2.3.4