RDP & Guacamole

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).

Windows Terminal & SSH

Après nous avoir vanté pendant des décennies l'avantage d'un O/S Windows ou tout se passe en mode clic clic, force est de constater ces dernières années que le CLI revient en force, et ce notamment avec PowerShell. Et puis ces dernières années Microsoft nous a pondu Windows Terminal (ou mieux celle-ci) qui supporte bien sur PowerShell, Dos, mais également le client SSH maintenant intégré (timidement) à Windows.

Et c'est celui ci qui m'a intéressé pour remplacer mes anciens clients SSH (j'utilisait Putty ou Bitvise SSH) avec des mots de passe. Sauf que ce client SSH calqué sur ceux que l'on trouve sur Linux (OpenSSH) ne permet bien sur pas de stocker les mots de passe. Et c'est normal. Il va donc falloir apprendre à générer et utiliser des paires de clés SSH, ce qui avec ma culture Windows n'a pas été une mince affaire.

Si ce n'est pas fait on installe le client SSH après avoir installé Windows Terminal (le client SSH peut aussi fonctionner seul, et tout ce qui se rapporte ici à SSH n'est bien sur pas lié à Windows...).

PS C:\> Add-WindowsCapability -Online -Name OpenSSH.Client*

On va donc commencer par générer une paire de clés. Idéalement on se place dans le répertoire .ssh qui se trouve dans le répertoire utilisateur. Cela nous évitera d'avoir à saisir un chemin, car au final les clés devront se trouver dans se répertoire pour simplifier la suite. Il est possible de protéger les clés par une phrase de passe, ou pas.

PS C:\Users\Lionel> cd .ssh
PS C:\Users\Lionel\.ssh> ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (C:\Users\Lionel/.ssh/id_rsa): test
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in test.
Your public key has been saved in test.pub.
The key fingerprint is:
SHA256:vAwnkX2iNyxyqFBVnNVHpzPbdvy6g1VoE/RU8o7+hJ0 lionel@canaletto
The key's randomart image is:
+---[RSA 2048]----+
|    .o.o.. ...+ +|
|   .  oo  . .o.= |
|  .   o o ..+  oo|
| .   . = o   =++.|
|.   o * S   ..+o+|
| . . o B o   o.+o|
|  .     o    ooE+|
|            . .+ |
|              oo.|
+----[SHA256]-----+

On dispose maintenant de notre paire de clés, test qui est la clé privée et test.pub qui est la clé publique. Si je ne l'avais pas nommée ainsi on se serait retrouvé avec id_rsa et id_rsa.pub qui est le nom de la clé par défaut. Peu importe le nommage, ce qui compte c'est de bien identifier ces deux fichiers.

A partir de là il va falloir installer la clé publique sur le serveur de destination.

A ce stade il est donc nécessaire que le service SSH soit activé sur le serveur distant avec la possibilité de se connecter avec un mot de passe, voire en root (je sais c'est pas bien). Pour cela on édite le fichier qui va bien avec sudo nano /etc/ssh/sshd_config et on redémare le service :

On remplace PermitRootLogin prohibit-password par PermitRootLogin yes 
On remplace PasswordAuthentication no par PasswordAuthentication yes

A noter que quand on a installé une surcouche comme aaPanel, celui ci se charge du SSH au dessus de l'O/S, et on peut télécharger la clé privée à utiliser coté client directement depuis l'interface, et le cas échéant désactiver l'authentification par mot de passe. Inconvénient, pour l'instant aaPanel ne gère que le root qu'il convient donc de sécuriser.

Et c'est là que ça se complique, car si sous Linux il existe le petit outil idoine pour faire ça :

ssh-copy-id username@remote_host

Rien de tel sous Windows et il va falloir faire avec ce sont on dispose, donc le client SSH et voici la méthode que j'ai trouvée pour installer la clé publique sur le serveur :

PS C:\> cat ~/.ssh/id_rsa.pub | ssh [email protected] "mkdir -p ~/.ssh && touch ~/.ssh/authorized_keys && chmod -R go= ~/.ssh && cat >> ~/.ssh/authorized_keys"

Une confirmation sera demandée (pour ajouter ce nouveau host à la liste locale) ainsi que le mot de passe SSH du serveur distant. 

A ce stade il est possible de se connecter avec notre clé et il sera possible de désactiver (sans se précipiter) l'authentification par mot de passe en éditant le fichier sshd_config comme vu précédemment.

PS C:\> ssh [email protected]

Et si j'ai plusieurs clés destinées à plusieurs serveurs ?

C'est possible, de deux façons, la première en cli :

PS C:\> ssh -i ~/.ssh/ma_cle_perso [email protected]

Ou plus simplement :

PS C:\> ssh MonServeur

A condition d'avoir créé un fichier config dans /.ssh

Host MonServeur
  User root
  HostName 192.168.20.20
    Port 22
  IdentityFile ~/.ssh/ma_cle_perso

Alternativement je préfère cette méthode (ici)

c:\cat ~/.ssh/id_rsa.pub | ssh username@remote_host "mkdir -p ~/.ssh && touch ~/.ssh/authorized_keys && chmod -R go= ~/.ssh && cat >> ~/.ssh/authorized_keys"

Intégration au menu Windows Terminal

Windows Terminal dispose d'un menu facilement paramétrable auquel on va pouvoir ajouter des raccourcis vers nos serveurs favoris. En cliquant sur paramètres on va directement ouvrir le fichier de paramètres et ajouter une entrée correspondant à un nouveau serveur SSH distant (attention le GUID doit être unique et on peut en générer sur plusieurs sites comme ici).

   {
     "guid":  "{232b7eac-3f44-4560-9250-eee6e97ca4e3}", // Random GUID
       "hidden":  false,
     "name":  "MonServeur",
  "icon":  "c:/users/lionel/pictures/terminal/ps.png", // facultatif
     "commandline":  "ssh 192.168.20.20"
   },

Ainsi il sera très facile d'accéder à un serveur. Dans ce fichier il est également possible de modifier tous les paramètres, mais pour ça je vous laisse lire les mes sources ci-dessous. Et comme il y a du Windows dans l'air, certains n'ont pas pu résister à ajouter des icones dans le terminal...

C'est bien sur anecdotique, mais je suis sur que ça va en amuser certains, voici la marche à suivre :

  1. On télécharge ces polices et on installe la police Caskaydia*
  2. Dans PowerShell on exécute Install-Module Terminal-Icons -Scope CurrentUser
  3. Toujours dans PS : code $profile, et on ajoute cette ligne Import-Module Terminal-Icons
  4. Avec RegEdit : Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Console\TrueTypeFont: 000=CaskaydiaCove Nerd Font
  5. Dans les paramètres de Windows Terminal on ajoute "fontFace": "CaskaydiaCove Nerd Font" dans profiles > defaults.

Bonus

Si vous êtes vraiment allergiques à Microsoft (que faites vous sous Windows), il y a une très bonne alternative, Fluent Terminal.

Voilà ! Je n'ai rien inventé et cet article a surtout pour but de me permettre de mémoriser tout ça. Et si ça peut vous aider.... Et si d'aucune ont quelque chose à y ajouter, n'hésitez pas !

Enjoy ;-)

Sources