Tags

arm64 bhyve bit blacklistd bluepill bluetooth cloudinit contabo cu dns dovecot elf encrypt envsubst esp8266 filter freebsd iot ipfw lets lowdown meross openocd opensmtpd openwrt orke perl prises python shell ssh ssl stm32 template tls unbound vps wifi yubikey

2026-08-12

[ ssh ]

alias, tag et match

Imaginons le scénario suivant:

Un extrait des connexions possibles:

$ ssh vm-http.dev.foo.fr
$ ssh pouet@vm-cache.prod.bar.org
$ ssh root@vm-db.dev.baz.com

La complétion

On peut se simplifier la vie en créant des fichiers correspondant aux connexions:

$ mkdir .ssh/completion/ && cd .ssh/completion
$ touch vm-http.dev.foo.fr pouet@vm-cache.prod.bar.org root@vm-db.dev.baz.com ...

et compléter la commande ssh avec la liste de ces fichiers. Inconvénient: la touche Tab va passer un sale quart d’heure.

Mon idéal

Il faudrait avoir une “commande” par domaine: foo pour se connecter à foo.fr, bar pour bar.org et baz pour baz.com . En rajoutant une lettre (minuscule et/ou majuscule ?) pour le sous-domaine, on aurait Pfoo pour se connecter à .prod.foo.fr et dbaz pour .dev.baz.com .

Pour les logins et les machines, il ne faudrait préciser que le service et utiliser une lettre pour le login: Rdb pour root@vm-db, pcache pour pouet@vm-cache, http pour vm-http .

En assemblant le tout, l’extrait des connexions possibles deviendrait:

$ dfoo http
$ Pbar pcache
$ dbaz Rdb

Le mixte minuscule/majuscule n’est là que pour illustrer le propos et pourrait ici se traduire par “si c’est important/risqué/dangereux alors c’est en majuscule”.

Match

Une de mes utilisations de cette directive de mon .ssh/config est de définir des “raccourcis”:

Match originalhost owrt
    HostName openwrt.mon.domaine.que.j.ai

mais si je veux pouvoir préciser un éventuel login, je dois être un peu plus générique:

Match originalhost *db
    HostName vm-db

qu’on traduira par “si le nom de machine de la commande ssh se termine par db alors le nom de machine à utiliser est vm-db”. J’utilise la même chose pour les logins:

Match originalhost p*
    User pouet

Match originalhost R*
    User root

À noter que le “p” passerait en majuscule (pour bien le distinguer du nom de machine) dans le cas où un service commencerait par “p” (proxy, pgsql …).

Tag

C’est avec cette fonctionnalité que je vais résoudre en partie la simplification domaine / sous-domaine. Il est possible de passer un “tag” à une connexion à l’aide du paramètre -P tag et de définir des valeurs par “tag”:

Match tagged old
    KexAlgorithms diffie-hellman-group1-sha1

Pour vérifier si la directive est prise en compte:

$ ssh -P old -G pouet | grep ^kexalgorithms
kexalgorithms diffie-hellman-group1-sha1

Chaque domaine / sous-domaine va correspondre à un “tag” qui va définir le nom de domaine:

Match tagged devfoo
    CanonicalDomains .dev.foo.fr
...
Match tagged prodbaz
    CanonicalDomains .prod.baz.com

Je dois aussi forcer les machines commençant par “vm-” à utiliser un nom de domaine:

Match host vm-*
    CanonicalizeHostname yes

Je vérifie mes combinaisons “tag” / “host”:

$ ssh -G -P devfoo Rdb | grep -e '^user ' -e '^hostname '
user root
hostname vm-db.dev.foo.fr

Attention, une connexion ne peut avoir qu’un seul “tag” et l’ordre des directives est important !

Alias

Je n’ai plus qu’à définir les alias:

alias dfoo ssh -P devfoo
alias Pfoo ssh -P prodfoo
...
alias Pbaz ssh -P prodbaz

et attendre que mes doigts mémorisent tout ça.

Un petit pour la route

Parce que la vie n’est qu’une longue suite d’exceptions, imaginons qu’une machine d’un sous-domaine ne respecte pas la convention de nommage:

# putain de vs-database de mes couilles
Match tagged devbar originalhost *db
    HostName vs-database

Match originalhost *db
    HostName vm-db

Tiré d’un exemple @TAF (et oui, le commentaire est d’origine).

Commentaires: https://github.com/bsdsx/blog_posts/issues/24

Lien vers ce billet


2023-06-10

[ ssh ]

Une configuration ssh aux petis oignons

ssh possède une option assez pratique pour qui veut apporter des modifications à sa configuration sans tout casser:

 -G      Causes ssh to print its configuration after evaluating Host and
         Match blocks and exit.

Pour avoir une idée de ce qu’il est possible de configurer:

$ ssh -F /dev/null -G localhost | wc -l
81

On va donc plutôt filtrer la sortie:

$ ssh -F /dev/null -G localhost | grep -e port -e localhost
host localhost
hostname localhost
port 22
gatewayports no
nohostauthenticationforlocalhost no

AddressFamily

Cette option permet de définir l’usage d’ipv4, d’ipv6 ou des deux. Comme chez moi, le ssh ne fonctionne qu’en ipv6, je peux ajouter dans mon ~/.ssh/config:

AddressFamily inet6

Sauf que:

Je commence par identifier le problème:

$ echo 'AddressFamily inet6' > /tmp/ssh_config
$ ssh -F /tmp/ssh_config github.com
ssh: Could not resolve hostname github.com: Address family for hostname not supported
$ ssh -F /tmp/ssh_config 192.168.0.1
ssh: Could not resolve hostname 192.168.0.1: Name does not resolve

Je distingue ces citoyens de seconde zone avec une directive Match:

$ cat /tmp/ssh_config 
Match host github.com,10.*,192.168.*
AddressFamily inet

Match all
AddressFamily inet6

et je teste:

$ ssh -F /tmp/ssh_config -G github.com | grep address
addressfamily inet
$ ssh -F /tmp/ssh_config -G gitlab.com | grep address
addressfamily inet6
$ ssh -F /tmp/ssh_config -G 192.168.0.1 | grep address
addressfamily inet

En cas de liste, attention à ne pas utiliser d’espace, uniquement la virgule. Pour le réseau 172.16.0.0/12, c’est un peu plus long:

$ grep 172 /tmp/ssh_config
$ Match host github.com,10.*,192.168.*,172.16.*,172.17.*,172.18.*,172.19.*,172.2?.*,172.30.*,172.31.*

Pour mon usage d’ipv4, je ne souhaite pas polluer mon .ssh/known_hosts:

$ cat /tmp/ssh_config
Match host github.com,192.168.*,10.*,172.16.*,172.17.*,172.18.*,172.19.*,172.2?.*,172.30.*,172.31.*
AddressFamily inet

Match host 1*
UserKnownHostsFile none

Match all
AddressFamily inet6

IdentityFile

J’ai une clef pour chaque serveur publique sur lequel je me connecte et j’ai la bonne idée de la nommer en fonction du serveur:

$ cat /tmp/ssh_config
Match host github.com,10.*,192.168.*,172.16.*,172.17.*,172.18.*,172.19.*,172.2?.*,172.30.*,172.31.*
AddressFamily inet

Match host 1*
UserKnownHostsFile none

Match all
AddressFamily inet6

Match host github.com,gitlab.com
IdentityFile ~/.ssh/%h

Je teste:

$ ssh -F /tmp/ssh_config -G github.com | grep -e address -e identityfile
addressfamily inet
identityfile ~/.ssh/%h
$ ssh -F /tmp/ssh_config -G gitlab.com | grep -e address -e identityfile
addressfamily inet6
identityfile ~/.ssh/%h
$ ssh -F /tmp/ssh_config -G localhost | grep -e address -e identityfile
addressfamily inet6
identityfile ~/.ssh/id_rsa
identityfile ~/.ssh/id_ecdsa
identityfile ~/.ssh/id_ecdsa_sk
identityfile ~/.ssh/id_ed25519
identityfile ~/.ssh/id_ed25519_sk
identityfile ~/.ssh/id_xmss
identityfile ~/.ssh/id_dsa

Parce que l’ordre compte

J’utilise toujours une clef sauf pour le tld .ru:

Match host github.com,10.*,192.168.*,172.16.*,172.17.*,172.18.*,172.19.*,172.2?.*,172.30.*,172.31.*
AddressFamily inet

Match host 1*
UserKnownHostsFile none

Match all
AddressFamily inet6
PreferredAuthentications publickey

Match host github.com,gitlab.com
IdentityFile ~/.ssh/%h

Match host *.ru
PreferredAuthentications password

Pas marche:

$ ssh -F /tmp/ssh_config -G fail.ru | grep -e preferredauthentications
preferredauthentications publickey

On ne peut pas redéfinir une directive, comme indiqué en début de page de manuel:

 Unless noted otherwise, for each parameter, the first obtained value will
 be used.  

Pour mon cas d’usage je peux:

Include et alias

La directive suivante définit un raccourci vers github.com

Host gh
Hostname github.com

que l’on peut placer dans un fichier dédié. Une directive Include :

$ cat /tmp/ssh_config_alias
Host gh
Hostname github.com

$ cat /tmp/ssh_config
Include /tmp/ssh_config_alias # Chemin complet car le fichier de config n'est pas un de ceux par défaut

Match host github.com,10.*,192.168.*,172.16.*,172.17.*,172.18.*,172.19.*,172.2?.*,172.30.*,172.31.*
AddressFamily inet

Match host 1*
UserKnownHostsFile none

Match all
AddressFamily inet6

Match host github.com,gitlab.com
IdentityFile ~/.ssh/%h

Match host *.ru
PreferredAuthentications password

Match all
PreferredAuthentications publickey

et le tour est joué:

$ ssh -F /tmp/ssh_config -G gh | grep -e address -e identityfile
addressfamily inet
identityfile ~/.ssh/%h

J’ai quelques machines virtuelles qui sont dans un sous-domaine dédié:

Host nbsd2?,al3?,fbsd4?
Hostname %n.ni3

Mais ce n’est pas la bonne solution: le token %n n’est pas disponible dans une directive Hostname. Je vais utiliser les directives Canonicalize :

Match host nbsd2? al3? fbsd4?
CanonicalizeHostname yes
CanonicalDomains ni3.bsdsx.fr # oui, le domaine complet

Si la configuration DNS est correcte alors:

$ ssh -F /tmp/ssh_config -G al30 | grep -e hostname
hostname al30.ni3.bsdsx.fr
canonicalizehostname true

Control

Quand on doit avoir plusieurs connections vers une machine, on comprend vite l’utilité des directives Control et ça ne coûte pas plus cher de l’activer par défaut:

$ cat /tmp/ssh_config_alias
ControlMaster auto
ControlPath ~/.ssh/.%r@%h:%p
...

Match vs Host

J’ai toujours utilisé la directive Host, c’est pour ce billet que je suis passé à Match. Ces deux directives ne diffèrent que par leur syntaxe:

Host *.ru *.ch
Match host *.ru,*.ch

mais seul Match permet ce genre de chose:

Match host *.prod.mon.domaine user backup localuser !production exec "id -Gn %u | grep -q admin"
PreferredAuthentications publickey
IdentitiesOnly yes
IdentityFile ~/.ssh/id_rsa_backup_with_force_command

Si:

alors on utilise une clef spécifique. Oui cet exemple est plutôt capillotracté (mais aucun drosophile n’a été … lors de la rédaction de ce billet).

Un dernier pour la route

C’est plus par principe qu’autre chose mais si il y a bien un truc que je ne supporte pas c’est tout ce qui ne sert à rien (oui, je ne supporte pas grand chose :) . Quand je vois tous les fichiers testés par

$ ssh -v ...

je me dis que ces quelques lignes ne peuvent pas faire de mal:

GlobalKnownHostsFile /dev/null # je n'ai jamais créé les fichiers de cette directive
UserKnownHostsFile ~/.ssh/known_hosts # known_hosts2 n'existe pas
# Pour ne pas tester des clefs qui n'existent pas
IdentityFile ~/.ssh/id_rsa
IdentityFile ~/.ssh/id_ecdsa_sk
AddKeysToAgent yes # ajouter les clefs à l'agent lors de leur première utilisation

Enjoy !

Commentaires: https://github.com/bsdsx/blog_posts/issues/18

Lien vers ce billet