Et non, je ne suis hélas pas parti en vacances au Mexique déguster du guacamole arrosé de téquila ! Mais j'ai trouvé la solution que je cherchait depuis longtemps pour sécuriser les accès RDP. Comme chacun le sait il n'est vraiment pas conseillé de laisser ouvert sur internet le port RDP qui est plutôt vulnérable. Hélas pensant le confinement beaucoup de clients ont du mettre en place des solutions dans l'urgence et dans bien des cas il était impossible de verrouiller au minimum ce port sur une IP fixe....
Bien sur Microsoft a une solution (RDP Gateway), couteuse et complexe, donc pas adaptée à de petites entreprises dont les couts IT explosent déjà. Je restait donc en éveil à la recherche d'une solution quand je suis tombé sur le projet open-source Guacamole en HTML5. Et surprise, celui ci fonctionne vraiment bien ! Le principe est simple et reprends celui des autres projets, une machine va servir de proxy afin d'exposer le RDP (mais aussi SSH, Telnet et VNC).
Installation
S'il est possible d'installer Guacamole en natif comme c'est très bien expliqué ici, après avoir testé différentes options, par facilité je vais faire ça sous Docker en utilisant ce projet.
Je vais donc monter une VM Linux Ubuntu et y configurer Docker et Docker Compose. Pour cette partie je vous laisse chercher, d'autres expliquent mieux que moi.
Pour déployer Guacamole on a besoin de 3 services :
- GUACD : Le cœur de Guacamole qui va servir à connecter les différents services (RDP, SSH, etc...)
- PostgreSQL : Une base de donnée (qui peut être également MySQL ou encore l'utilisation d'une base externe)
- Guacamole : Le composant FrontEnd qui va gérer les connections, les services et les utilisateurs.
Docker Compose
L'auteur de l'intégration a eu la bonne idée de nous fournir un script d'installation. On va donc l'utiliser et on se servira de Docker Compose plus tard afin d'ajuster la configuration.
sudo git clone "https://github.com/boschkundendienst/guacamole-docker-compose.git"
cd guacamole-docker-compose
./prepare.sh
sudo docker-compose up -d
Si tout se passe bien on peut se connecter sur https://ip_serveur:8443/guacamole
. Le nom d'utilisateur par défaut est guacadmin
avec mot de passe guacadmin
. La première chose à faire est bien sur de le changer.
On pourra facilement configurer un serveur RDP et s'y connecter pour avoir les premières impressions. Je trouve que le rendu RDP en HTML5 est fluide, même en regardant des vidéos sur YouTube à une bonne résolution. Attention à ne pas perdre de vue que c'est le serveur Guacamole qui doit encaisser le trafic entrant et sortant. Selon l'usage et le nombre de clients il faudra donc le dimensionner correctement.
Je ne vais pas m'étendre sur les différentes options, l'interface et claire, la documentation également et d'autres en parlent très bien.
Sécurisation
Il n'est bien sur pas question d'exposer ce serveur directement sur internet et je vais tester deux solutions de reverse proxy, je commence par éditer le fichier docker-compose.yml
afin de supprimer le proxy Nginx préinstallé histoire de ne pas empiler les proxys. J'ajuste également en 8080:8080
pour une utilisation en direct et REMOTE_IP_VALVE_ENABLED: 'true'
pour activer le proxy externe et WEBAPP_CONTEXT: 'ROOT'
afin que Guacamole soit accessible en racine :
version: '2.0'
networks:
guacnetwork_compose:
driver: bridge
services:
# guacd
guacd:
container_name: guacd_compose
image: guacamole/guacd
networks:
guacnetwork_compose:
restart: always
volumes:
- ./drive:/drive:rw
- ./record:/record:rw
# postgres
postgres:
container_name: postgres_guacamole_compose
environment:
PGDATA: /var/lib/postgresql/data/guacamole
POSTGRES_DB: guacamole_db
POSTGRES_PASSWORD: 'ChooseYourOwnPasswordHere1234'
POSTGRES_USER: guacamole_user
image: postgres:15.2-alpine
networks:
guacnetwork_compose:
restart: always
volumes:
- ./init:/docker-entrypoint-initdb.d:z
- ./data:/var/lib/postgresql/data:Z
guacamole:
container_name: guacamole_compose
depends_on:
- guacd
- postgres
environment:
REMOTE_IP_VALVE_ENABLED: 'true' # On active ici l'utilisation via un proxy externe
WEBAPP_CONTEXT: 'ROOT'
GUACD_HOSTNAME: guacd
POSTGRES_DATABASE: guacamole_db
POSTGRES_HOSTNAME: postgres
POSTGRES_PASSWORD: 'ChooseYourOwnPasswordHere1234'
POSTGRES_USER: guacamole_user
image: guacamole/guacamole
volumes:
- ./custom/server.xml:/home/administrator/guacamole-docker-compose/server.xml
links:
- guacd
networks:
guacnetwork_compose:
ports:
- 8080:8080
restart: always
Je relance docker :
administrator@guacamole:~/guacamole-docker-compose$ sudo docker-compose up -d
En local je me connecte maintenant en http://ip_serveur:8080
sans SSL car le SSL sera géré par le proxy.
Option 1 : pfsense
J'ai un pfsense installé avec HAProxy et Acme pour gérer les certificats Lets'Encrypt, je vais donc me servir de ca pour publier le service. Sur HAProxy on configure le BackEnd et le FronteEnd qui lui utilisera le certificat préalablement créé avec Acme. Dans ma configuration je partage l'IP avec plusieurs sites et ce qui est important c'est d'activer l'option forwardfor
qui permettra de transférer les adresses sources à Guacamole.
Je mets ici le code de configuration qui sera utile à ceux qui n'utilisent pas HAProxy sous pfsense qui lui dispose d'une interface graphique. Comme on peut le constater le HTTP to HTTPS se fait au niveau du HAProxy et c'est lui également qui redirigera les requetés HTTTP vers HTTPS et mon serveur sera accessible sur https://guacamole.mondomaine.tld
:
global
maxconn 10000
stats socket /tmp/haproxy.socket level admin expose-fd listeners
uid 80
gid 80
nbproc 1
nbthread 1
hard-stop-after 15m
chroot /tmp/haproxy_chroot
daemon
tune.ssl.default-dh-param 1024
server-state-file /tmp/haproxy_server_state
listen HAProxyLocalStats
bind 127.0.0.1:2200 name localstats
mode http
stats enable
stats admin if TRUE
stats show-legends
stats uri /haproxy/haproxy_stats.php?haproxystats=1
timeout client 5000
timeout connect 5000
timeout server 5000
frontend Shared_WAN-merged
bind x.x.x.x:443 (IP WAN) name x.x.x.x:443 (IP WAN) ssl crt-list /var/etc/haproxy/Shared_WAN.crt_list
mode http
log global
option http-keep-alive
option forwardfor
acl https ssl_fc
http-request set-header X-Forwarded-Proto http if !https
http-request set-header X-Forwarded-Proto https if https
timeout client 30000
acl Admin var(txn.txnhost) -m str -i admin.domain.tld
acl guacamole var(txn.txnhost) -m str -i guacamole.domain.tld
http-request set-var(txn.txnhost) hdr(host)
use_backend Admin_ipvANY if Admin
use_backend Guacamole_8443_ipvANY if guacamole
frontend http-to-https
bind x.x.x.x:80 (IP WAN) name x.x.x.x:80 (IP WAN)
mode http
log global
option http-keep-alive
timeout client 30000
http-request redirect code 301 location https://%[hdr(host)]%[path]
backend Admin_ipvANY
mode http
id 100
log global
timeout connect 30000
timeout server 30000
retries 3
server admin 192.168.55.44:80 id 101
backend Guacamole_8443_ipvANY
mode http
id 102
log global
timeout connect 30000
timeout server 30000
retries 3
server guacamole 192.168.66.55:8080 id 101
Option 2 : CloudFlared
Cette option est encore plus simple pour ceux qui disposent d'un domaine chez Cloudflare. On va utiliser les possibilités offertes gratuitement par Cloudflared et ajouter une couche de sécurité supplémentaire.
On commence par créer un tunnel CloudFlared avec une instance Docker supplémentaire (le code est fournit par le site de configuration Zero Trust de Cloudflare)
docker run cloudflare/cloudflared:latest tunnel --no-autoupdate run --token eyJhIjoiZjk0YjdkZTBiMzFmYWNkNjZlshhsgfhsfghsgfhgfhssgfhsfgzRiMC00MmNlLWJjMghsfghsghsgfhsgfhsgfhsgmpaR00tyyretu(-yenebybvewWXpGaUxUazVNell0TnpRek56WXhNMlkxWXpFeiJ9
Ensuite une fois que le tunnel est monté
On va publier et pointant sur l'ip privée et le port de notre serveur, cela va automatiquement créer l'entrée DNS dans CloudFlare et gérer la problématique du certificat. Ll sera accessible en SSL. :
Ensuite on va ajouter une couche de sécurité supplémentaire en créant une application de type SelfHosted à laquelle on affecte une policie qui impose un code supplémentaire lors d'une connexion. Ce code sera envoyé sur le mail de l'utilisateur sur une adresse individuelle ou un domaine spécifique. Ce n'est pas tout à fait du MFA, mais on considère que si l'utilisateur reçoit bien le code sur son mail professionnel, il s'agit bien de lui et on peut lui permettre de saisir son login / password pour accéder au service :
C'est la méthode la plus simple et de nombreuses option sont exploitables, de même qu'il est possible d'utiliser de nombreux providers externes d'authentification :
Lorsqu'il souhaite se connecter, l'utilisateur tombe sur un portail que l'on peu personnaliser aux couleurs de l'entreprise.
Voilà, et surtout maintenant on retrouve bien les IP publiques dans le log de Guacamole :
Il n'y a plus qu'a ajuster les différentes options de Guacamole et pour ça la documentation est très bien faite. Et bien sur si on se sert de Guacamole pour exposer des serveurs RDP accessibles sur l'internet on prendra soin de restreindre leur usage à l'IP publique du serveur Guacamole, ou mieux de faire transiter ce flux par un tunnel (Wireguard, Tailscale ou Zerotier par exemple que l'on peu facilement installer sur les deux serveurs).