Annexe G. PostgreSQL et Stunnel

Table des matières

G.1. Introduction
G.2. Pré-requis
G.3. Motivations: sniffer une connexion non sécurisée avec NAST, limites d'une connexion par mot de passe en md5!
G.3.1. Test sans mot de passe
G.3.2. Test avec MD5
G.4. Stunnel: sécurisation de la connexion
G.4.1. Pré-requis: OpenSSL
G.4.2. Installation de Stunnel
G.4.3. Mise en oeuvre
G.5. Installation en service de Stunnel
G.6. Pour aller plus loin

G.1. Introduction

Pour protéger ses données lors d'une connexion entre un serveur et un client avec PostgreSQL, plusieurs solutions existent: soit en utilisant PostgreSQL compilé avec OpenSSL en natif, utiliser un tunnel SSH avec redirection de Port ect...Un des moyens que j'aime bien est d'utiliser Stunnel/OpenSSL.

Stunnel est une enveloppe SSL, permettant donc d'étendre les fonctionnalités de SSL à un démon qui à l'origine n'est pas prévu pour être une couche de sécurité. On peut donc par exemple créer une connexion sécurisée entre vers une base de données, consolidant ainsi la connexion du système.

Un autre intérêt de l'installation avec Stunnel est qu'il peut-être installé en tant que service (automatiquement relancé au démarrage de la machine). Ce que ne propose pas une redirection par SSH. Je ne propose pas ici l'installation de stunnel avec xinetd, trop contrariant à mon sens.

G.2. Pré-requis

Je pars du principe ici que les configurations réseaux avec PostgreSQL sont acquises par le lecteur. Pour les test ici, nous aurons besoin de deux machines. Sur mon réseau domestique, j'ai deux machines dont les noms sont respectivement jenna et bremko:

  1. jenna dont l'IP est 192.168.0.5 fera office de serveur. Je lui ai installé un serveur PostgreSQL 8.1.5; On s'assurea aussi que le paramètre listen_adresses = '*' est activé dans le fichier postgresql.conf de configuration de PostgreSQL.

  2. bremko dont l'IP est 192.168.0.4 fera office de client. On s'assurera d'avoir un logiciel client de PostgreSQL comme pgadmin3 ou psql sur la machine cliente. Ici c'est psql que j'utiliserai.

Pour vérifier que les données sont bien chiffrées où non, nous avons besoin d'installer un sniffer comme nast ou etherreal (...) entre jenna et bremko! J'opte ici pour NAST (Network Analize Sniffer Tool/http://nast.berlios.de/) que j'installe en faisant - en tant que root - sur bremko

Exemple G.1. Installation de nast

apt-get install nast

Pour les besoins de mes tests, je crée un super-utilisateur damien sur jenna dont le mot de passe sera 'morphine'

Exemple G.2. PostgreSQL sur la machine-serveur: création d'un super-utilisateur damien ayant pour mot de passe 'morphine'

root@jenna:/root$ su postgres
postgres@jenna:/root$createuser -sEPe damien
Entrez le mot de passe pour le nouvel rôle :
Entrez-le de nouveau :
CREATE ROLE damien ENCRYPTED PASSWORD 'morphine' SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN;
CREATE ROLE

G.3. Motivations: sniffer une connexion non sécurisée avec NAST, limites d'une connexion par mot de passe en md5!

Dans cette section, nous allons mettre en évidence l'intérêt de sécuriser une connexion avec PostgreSQL.

G.3.1. Test sans mot de passe

Commençons donc par vérifier que si je laisse le mot de passe par défaut sur à 'password' sur le réseau on arrive quand même à le récupérer. Pour celà, dans le pg_hba.conf de jenna, je fais la modification suivante

Exemple G.3. Modification du fichier de configuration (pg_hba.conf) sur jenna pour une connexion par mot de passe classique

 host 192.168.0.4 255.255.255.255 password 

et je redémarre ensuite mon serveur

Exemple G.4. Redémarrage du serveur

/etc/init.d/postgresql restart

Sur bremko, dans un terminal pour sniffer les connexion je fais

Exemple G.5. Nast sur bremko: sniffage des paquets envoyés à jenna sur le port 5432

 nast -i eth0 -pd -f "dst 192.168.0.5" -f "port 5432" 

Dans un autre terminal (toujous depuis bremko), j'ouvre ma connexion à jenna en faisant

Exemple G.6. Connexion à jenna depuis bremko

psql -h jenna -U damien template1

Dans le terminal que contient le processus actif de nast, je vois a un moment passé le message

Exemple G.7. Portion de paquets interceptionnés

---[ TCP Data ]------------------------------------------------------

   (    user damien database template1
---[ TCP ]-----------------------------------------------------------
192.168.0.5:5432(postgresql) -> 192.168.0.4:50884(unknown)
TTL: 64         Window: 1448    Version: 4      Lenght: 61
FLAGS: ---PA--  SEQ: 1495197601 - ACK: 923679297
Packet Number: 26

---[ TCP Data ]------------------------------------------------------

R
---[ TCP ]-----------------------------------------------------------
192.168.0.4:50884(unknown) -> 192.168.0.5:5432(postgresql)
TTL: 64         Window: 1460    Version: 4      Lenght: 66
FLAGS: ---PA--  SEQ: 923679297 - ACK: 1495197610
Packet Number: 27

---[ TCP Data ]------------------------------------------------------

morphine
---[ TCP ]-----------------------------------------------------------

preuve que ça ne suffit pas!

G.3.2. Test avec MD5

Allons donc! Changeons donc le mot-clé 'password' par 'md5' dans le fichier pg_hba.conf de jenna

Exemple G.8. Modification du fichier de configuration de jenna (pg_hba.conf): connexion par md5

host 192.168.0.4 255.255.255.255 md5

et redémarrons le serveur (/etc/init.d/postgresql restart). Relançons donc une connexion au serveur depusi bremko. Maintenant depuis nast, j'obtiens

Exemple G.9. Réception de paquets par Nast: mise en évidence de l'intérêt de md5

---[ TCP Data ]------------------------------------------------------

   (    user damien database template1
---[ TCP ]-----------------------------------------------------------
192.168.0.5:5432(postgresql) -> 192.168.0.4:45585(unknown)
TTL: 64         Window: 1448    Version: 4      Lenght: 65
FLAGS: ---PA--  SEQ: 1852264915 - ACK: 1258303094
Packet Number: 85

---[ TCP Data ]------------------------------------------------------

R         +F
---[ TCP ]-----------------------------------------------------------
192.168.0.4:45585(unknown) -> 192.168.0.5:5432(postgresql)
TTL: 64         Window: 1460    Version: 4      Lenght: 93
FLAGS: ---PA--  SEQ: 1258303094 - ACK: 1852264928
Packet Number: 86

---[ TCP Data ]------------------------------------------------------

p   (md5063971165646bb85c855953e66c01196
---[ TCP ]-----------------------------------------------------------

Note

Extrait de la documentation française de PostgreSQL concernant la méthode MD5

La méthode d'authentification MD5 crypte deux fois le mot de passe sur le client avant de l'envoyer au serveur. Il le crypte tout d'abord à partir du nom de l'utilisateur puis il le crypte à partir d'un élément du hasard envoyé par le serveur au moment de la connexion. Cette valeur, deux fois cryptée, est envoyée sur le réseau au serveur. Le double cryptage empêche non seulement la découverte du mot de passe, il empêche aussi une autre connexion en rejouant la même valeur de double cryptage dans une connexion future.

Gagné! Enfin ne nous réjouissons pas trop vite car si je tape une requête depuis le client je vois apparaître par exemple

Exemple G.10. Réception de paquets par Nast: insuffisance pour la sécurisation de la méthode

---[ TCP Data ]------------------------------------------------------

Q   %select * from geometry_columns ;ima
---[ TCP ]-----------------------------------------------------------
192.168.0.5:5432(postgresql) -> 192.168.0.4:48508(unknown)
TTL: 64         Window: 6091    Version: 4      Lenght: 52
FLAGS: ----A--  SEQ: 2056534420 - ACK: 1484467451
Packet Number: 161

---[ TCP ]-----------------------------------------------------------
192.168.0.5:5432(postgresql) -> 192.168.0.4:48508(unknown)
TTL: 64         Window: 6091    Version: 4      Lenght: 1500
FLAGS: ----A--  SEQ: 2056534420 - ACK: 1484467451
Packet Number: 162

---[ TCP Data ]------------------------------------------------------

T      f_table_catalog                   f_table_schema                   f_table_name                   f_geometry_column                  
 coord_dimension                   srid                   type                "  D   E          public    apb_lr    the_geom    2   
 -1    MULTIPOLYGOND   U          public   a_cours_d_eau_n2_v3    the_geom    2    -1    MULTILINESTRING

Oups!

En effet, depuis bremko j'ai envoyé la requête

select * from geometry_columns

à jenna. Les résultats me sont envoyés en claire comme me le confirme la ligne 192.168.0.5:5432(postgresql) 192.168.0.4:48508(unknown) ainsi que le reste des résultats.

Première conclusion: Bon md5 protège bien mon mot de passe mais pas mes requêtes ainsi que les résultats renvoyés par le serveur! Et c'est là justement qu'intervient Stunnel!

G.4. Stunnel: sécurisation de la connexion

Nous devons commencer par installer OpenSSL pour utiliser Stunnel par la suite

G.4.1. Pré-requis: OpenSSL

Stunnel a besoin de OpenSSL pour pouvoir fonctionner

Exemple G.11. Pré-requis pour l'installation de Stunnel

apt-get install openssl libssl-dev

G.4.2. Installation de Stunnel

Le site de stunnel est http://www.stunnel.org. Nous aurons besoin ici de l'installer à la fois sur le serveur et sur le client. Je fournis ici les commandes que j'ai utilisé pour l'installer sans plus de détails

Exemple G.12. Installation de Stunnel

wget http://www.stunnel.org/download/stunnel/src/stunnel-4.20.tar.gz
tar xvzf stunnel-4.20.tar.gz
cd stunnel-4.20
./configure --with-ssl=/usr --prefix=/opt/stunnel
make 
make install

L'installation aura donc lieu ici dans le répertorie /opt/stunnel mais vous pouvez l'installer où bon vous semble.

Lors de l'installation, un certificat auto-signé stunnel.pem sera généré dont voici une copie d'écran de ce que j'ai renseigné pour jenna

Exemple G.13. Génération du certificat auto-signé

Generating a 1024 bit RSA private key
.................++++++
............++++++
writing new private key to 'stunnel.pem'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [PL]:FR
State or Province Name (full name) [Some-State]:Hérault
Locality Name (eg, city) []:Castelnau-Le-Lez
Organization Name (eg, company) [Stunnel Developers Ltd]:01MAP
Organizational Unit Name (eg, section) []:01MAP
Common Name (FQDN of your server) [localhost]:jenna

Je renouvelle l'instalaltion sur bremko, en changeant la ligne Common Name (FQDN of your server) [localhost]:bremko

G.4.3. Mise en oeuvre

J'ouvre maintenant un terminal sur jenna et j'y tape

Exemple G.14. Stunnel en tant que serveur sur jenna

 root@jenna:/opt/stunnel/sbin/stunnel3 -D 7 -f -P /opt/stunnel/stunnel.pid -o /opt/stunnel/stunnel.log -p /opt/stunnel/etc/stunnel/stunnel.pem -d 9000 -r localhost:5432 

avec les options suivantes:

  • -D 7: pour spécifier le niveau de déboggage afin de collecter le maximum d'informatios;

  • -f: me permet de faire tourner le processus en arrière-plan dans le terminal. Un simple [CTRL]+[C] me suffira donc pour arrêter le processus si besoin est;

  • -P /opt/stunnel/stunnel.pid pour le PID du processus;

  • -o /opt/stunnel/stunnel.log pour spécifier un fichier de log;

  • -p /opt/stunnel/etc/stunnel/stunnel.pem pour le certificat auto-signé

Un processus démon de Stunnel par cette commande est donc lancée. Le paramètre -d 9000 est le port d'écoute du démon et lui demande d'attendres de données chiffrées sur ce port. Le paramètre -r localhost:5432 indique au démon que les données reçues sur son port d'écoute (9000), il devra les déchiffrer et les envoyer sur le port 5432 de la machine locale. Or ce dernier port n'est autre que le port d'écoute du serveur PostgreSQL sur jenna.

Sur bremko, je lance

Exemple G.15. Stunnel en tant que client sur bremko

root@bremko:/opt/stunnel# /opt/stunnel/sbin/stunnel3 -D 7 -f -P/opt/stunnel/stunnel.pid -o /opt/stunnel/log/stunnel.log -c -d localhost:5433 -c -d localhost:5433 -r 192.168.0.5:9000

Dans la commande précédente c'est la portion -c -d localhost:5433 -r 192.168.0.5:9000 qui nous intéresse. Une instance de Stunnel est ainsi lancée en mode client grâce au paramètre -c et lui demande d'écouter sur le port 5433. Le paramètre -r 192.168.0.5:9000 indique à cette instance que la machine-serveur (jenna) a pour adresse 192.168.0.5 et que son port d'écoute est le 9000.

Pour m'assurer que les connexions seront chiffrées dans un troisème terminal sur bremko, je fais

nast -pd -i eth0 -f "dst 192.168.0.5" -f "port 9000"

Dans un nouveau terminal toujours - sur bremko - j'ouvre maintenant une connexon PostgreSQL en faisant

psql -h localhost -p 5433 -U damien direnlr

Je saisis quelques requêtes et j'apprécie le travail en regardant les lignes retournées par nast depuis le troisième terminal en question. Par exemple pour la requête

direnlr=# select * from geometry_columns limit 1;
-[ RECORD 1 ]-----+-------------
f_table_catalog   |
f_table_schema    | public
f_table_name      | apb_lr
f_geometry_column | the_geom
coord_dimension   | 2
srid              | -1
type              | MULTIPOLYGON 

j'obtiens avec nast

Exemple G.16. Paquets réceptionnés: Sécurisation de la connexion

---[ TCP ]-----------------------------------------------------------
192.168.0.4:45480(unknown) -> 192.168.0.5:9000(unknown)
TTL: 64         Window: 2546    Version: 4      Lenght: 126
FLAGS: ---PA--  SEQ: 2814905551 - ACK: 2149157549
Packet Number: 1

---[ TCP Data ]------------------------------------------------------

        ' jP;o  , 9     e nH}  .
---[ TCP ]-----------------------------------------------------------
192.168.0.5:9000(unknown) -> 192.168.0.4:45480(unknown)
TTL: 64         Window: 1984    Version: 4      Lenght: 126
FLAGS: ---PA--  SEQ: 2149157549 - ACK: 2814905625
Packet Number: 2

---[ TCP Data ]------------------------------------------------------

      D n~  %   N 4; r M   { g.X= > q     v 1     -N{W   u @  A 9 *4l   _K
---[ TCP ]-----------------------------------------------------------
192.168.0.4:45480(unknown) -> 192.168.0.5:9000(unknown)
TTL: 64         Window: 2546    Version: 4      Lenght: 52
FLAGS: ----A--  SEQ: 2814905625 - ACK: 2149157623
Packet Number: 3

G.5. Installation en service de Stunnel

Pour l'installation en service sur les deux machines, il suffit de modifer le script de démarrage de Ubuntu à savoir /etc/init.d/rc.local. J'y ai par exemple effectuer les modifications suivantes sur jenna

Exemple G.17. Script de démarrage pour stunnel en tant que service sur la machine-serveur

#! /bin/sh
# Modications du PATH pour accéder à stunnel
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/opt/stunnel/sbin
[ -f /etc/default/rcS ] && . /etc/default/rcS
. /lib/lsb/init-functions

do_start() {
        if [ -x /etc/rc.local ]; then
                log_begin_msg "Running local boot scripts (/etc/rc.local)"
                /etc/rc.local
                log_end_msg $?
        fi
}
# Ma ligne pour lancer stunnel sur jenna (Serveur PostgreSQL)
# à adapter en conséquence sur bremko (voir ci-dessus dans la doc)
/opt/stunnel/sbin/stunnel3 -D 7 -f -P /opt/stunnel/stunnel.pid -p /opt/stunnel/etc/stunnel/stunnel.pem -d 9000 -r localhost:5432 -o /opt/stunnel/log/stunnel.log

case "$1" in
    start)
        do_start
        ;;
    restart|reload|force-reload)
        echo "Error: argument '$1' not supported" >&2
        exit 3
        ;;
    stop)
        ;;
    *)
        echo "Usage: $0 start|stop" >&2
        exit 3
        ;;
esac

Attention

Ne pas oublier d'adapter votre script de démarrage (/etc/init.d/rc.local (pour Ubuntu) ou /etc/rc.d/rc.local...) sur la machine-cliente pour la commande

/opt/stunnel/sbin/stunnel3 -D 7 -f -P/opt/stunnel/stunnel.pid -o /opt/stunnel/log/stunnel.log -c -d localhost:5433 -o /opt/stunnel/log/stunnel.log -c -d localhost:5433 -r 192.168.0.5:9000

G.6. Pour aller plus loin

Il est tout à fait possible avec Stunnel d'utiliser le fichier de configuration de stunnel dans /opt/stunnel/etc/stunnel ou lui proposer un fichier personnel de configuration. Je ne me suis pas attarder à le proposer dans cette article afin de ne pas trop le surcharger.