PostgreSQL
Dans un terminal, laissez le tunnel tourner :
ssh -N -o ExitOnForwardFailure=yes -L 127.0.0.1:5433:127.0.0.1:5432 user@server
Dans un autre :
psql -h 127.0.0.1 -p 5433 -U app appdb
Le port 5433 est un port libre quelconque sur votre machine. Utiliser autre chose que 5432 évite un conflit avec un PostgreSQL local. Une chaîne de connexion fonctionne de la même façon, dans n'importe quel outil ou bibliothèque :
postgresql://app@127.0.0.1:5433/appdb
Dans un outil graphique de base de données, indiquez l'hôte 127.0.0.1 et le port 5433 avec l'identifiant de la base, et laissez désactivée l'option SSH de l'outil, puisque le tunnel s'en charge déjà.
Côté serveur, la connexion semble venir du serveur lui-même. Dans notre test, inet_client_addr() a renvoyé 127.0.0.1. Il faut donc une ligne pour cela dans pg_hba.conf, que la plupart des fichiers par défaut ont déjà :
host appdb app 127.0.0.1/32 scram-sha-256
MySQL
ssh -N -o ExitOnForwardFailure=yes -L 127.0.0.1:3307:127.0.0.1:3306 user@server
mysql -h 127.0.0.1 -P 3307 -u app -p appdb
Écrivez 127.0.0.1 et non localhost. Le client MySQL interprète localhost comme une demande du socket Unix local, ignore le port et échoue :
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)
--protocol=TCP règle aussi le problème, si vous tenez vraiment à saisir localhost.
Le compte doit être autorisé depuis l'adresse que voit le serveur. Un utilisateur créé comme 'app'@'localhost' peut échouer en TCP. Sur l'image MySQL officielle, qui a skip_name_resolve activé, cela a échoué avec Access denied for user 'app'@'127.0.0.1'. Créez le compte pour cette adresse :
CREATE USER 'app'@'127.0.0.1' IDENTIFIED BY '...';
GRANT ALL ON appdb.* TO 'app'@'127.0.0.1';
Quelle adresse va où
-L 127.0.0.1:5433:127.0.0.1:5432
| | | |
| | | +- port of the database
| | +- database address, as seen by the SSH server
| +- port on your machine
+- address to listen on, on your machine
- Gardez la première adresse à
127.0.0.1, pour que personne d'autre sur votre réseau ne puisse utiliser votre tunnel. - La base peut écouter uniquement sur le bouclage :
listen_addresses = 'localhost'dans PostgreSQL,bind-address = 127.0.0.1dans MySQL. Les deux ont fonctionné dans notre test, et le tunnel n'a pas besoin qu'on les ouvre davantage. - Préférez aussi
127.0.0.1àlocalhostcomme destination. Le serveur SSH résout le nom, et il peut essayer l'IPv6 d'abord.
Les redirections distantes et dynamiques suivent le même schéma, voir tunnels SSH locaux, distants et dynamiques.
Si la base est sur une autre machine, comme un service managé, nommez-la comme destination. La base voit alors l'adresse privée du serveur SSH, c'est donc celle dont sa liste d'autorisation a besoin :
ssh -N -L 127.0.0.1:5433:db.internal.example.com:5432 user@server
TLS reste important
SSH chiffre depuis votre machine jusqu'au serveur SSH. Si la base est sur une autre machine, le dernier tronçon est hors de ce chiffrement : utilisez donc aussi TLS sur la connexion à la base.
La vérification des certificats demande une attention particulière, car votre client croit se connecter à 127.0.0.1, qui ne figure pas sur le certificat :
psql "host=127.0.0.1 port=5433 user=app dbname=appdb sslmode=verify-full sslrootcert=ca.crt"
... server certificate for "db.example.com" does not match host name "127.0.0.1"
Dans PostgreSQL, mettez dans host le nom du certificat et dans hostaddr le tunnel. Cela a passé notre test avec vérification complète :
psql "host=db.example.com hostaddr=127.0.0.1 port=5433 user=app dbname=appdb sslmode=verify-full sslrootcert=ca.crt"
MySQL n'a pas de réglage équivalent. --ssl-mode=VERIFY_IDENTITY vérifie le nom que vous avez saisi : il vous faudrait donc un certificat qui liste 127.0.0.1, ou un nom dans votre fichier hosts qui y pointe. Le simple REQUIRED chiffre sans vérifier qui a répondu.
Un utilisateur qui ne peut ouvrir que ce tunnel
Une personne ou une application qui n'a besoin que de la base ne devrait pas obtenir de shell sur le serveur. Créez un utilisateur sans shell :
sudo useradd -m -s /usr/sbin/nologin dbtunnel
Installez sa clé publique dans /home/dbtunnel/.ssh/authorized_keys. Ajoutez ensuite un fichier drop-in, par exemple /etc/ssh/sshd_config.d/10-dbtunnel.conf. Match s'arrête à la fin de ce fichier : il n'affecte donc pas les réglages qui suivent :
Match User dbtunnel
AllowTcpForwarding local
PermitOpen 127.0.0.1:5432
X11Forwarding no
AllowAgentForwarding no
PermitTTY no
ForceCommand /usr/sbin/nologin
Exécutez sudo sshd -t, rechargez, puis connectez-vous :
ssh -N -L 127.0.0.1:5433:127.0.0.1:5432 dbtunnel@server
Ce que nous avons observé avec cette configuration :
- Le tunnel ci-dessus fonctionne, et
psqls'y connecte. - Le port 3306 ou toute autre destination est refusé.
-Réchoue avecremote port forwarding failed for listen port.- Une connexion normale affiche
This account is currently not available. -L 5433:localhost:5432est refusé aussi.PermitOpencompare du texte, sans résolution de noms : le client doit donc demander exactement127.0.0.1.
Pour limiter une seule clé à la place, vous pouvez utiliser des options dans authorized_keys, sur une seule ligne :
restrict,port-forwarding,permitopen="127.0.0.1:5432" ssh-ed25519 AAAA... dbtunnel
Cela bloque aussi le shell et les autres destinations. Dans notre test, -R restait autorisé : ouvrir un port sur le serveur reste donc possible. Le bloc Match avec AllowTcpForwarding local le ferme, et c'est pour cette raison que nous le préférons.
Cet utilisateur ne peut atteindre que la connexion propre à la base. Le compte de la base doit quand même avoir son propre mot de passe ou certificat, et seulement les droits dont il a besoin.
Questions
Pourquoi utiliser le port 5433 de mon côté plutôt que 5432 ?
N'importe quel port libre convient. Un port différent évite un conflit avec un PostgreSQL qui tourne déjà sur votre machine.
Pourquoi mysql ignore-t-il le port quand j'écris localhost ?
Le client MySQL traite localhost comme le socket Unix local. Utilisez 127.0.0.1, ou ajoutez --protocol=TCP.
Le trafic de la base est-il chiffré dans un tunnel SSH ?
De votre machine jusqu'au serveur SSH, oui. Si la base tourne sur une autre machine, le dernier tronçon est hors du tunnel : utilisez aussi TLS.
Sources
- ssh(1), manuel OpenBSD
- sshd_config(5), manuel OpenBSD
- sshd(8), format du fichier authorized_keys, manuel OpenBSD
- PostgreSQL, connexions TCP/IP sécurisées par tunnels SSH
- PostgreSQL, paramètres de connexion libpq
- MySQL 8.0, connexion au serveur