SSH avancé#

Hide code cell source

import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import matplotlib.gridspec as gridspec
import numpy as np
import pandas as pd
import seaborn as sns
import hashlib
import secrets
import base64

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

Rappels SSH — handshake et échange de clés#

SSH (Secure Shell) est un protocole cryptographique qui sécurise les communications sur un réseau non fiable. Il opère sur TCP/22 et se décompose en trois couches :

  • Transport : établit un canal chiffré et authentifie le serveur

  • Authentification : vérifie l’identité du client

  • Connexion : multiplexe plusieurs canaux logiques (shell, tunnels, SFTP) sur une seule connexion TCP

Déroulement du handshake#

  1. TCP connect : le client ouvre une connexion TCP vers le serveur sur le port 22

  2. Identification : échange des bannières de version (SSH-2.0-OpenSSH_9.x)

  3. Négociation d’algorithmes (SSH_MSG_KEXINIT) : les deux parties annoncent les algorithmes supportés pour l’échange de clés, le chiffrement symétrique, le MAC et la compression

  4. Échange de clés (DH / ECDH / curve25519) : génération d’un secret partagé sans que la clé symétrique ne transite sur le réseau

  5. Authentification du serveur : le serveur signe un message avec sa clé d’hôte privée ; le client vérifie contre ~/.ssh/known_hosts

  6. Dérivation des clés de session : à partir du secret partagé, dérivation de clés pour le chiffrement (AES-GCM, ChaCha20-Poly1305) et l’intégrité

  7. Authentification du client : par clé publique, mot de passe, GSSAPI…

  8. Ouverture des canaux : le client demande un canal shell, un tunnel, etc.

Forward Secrecy

Les échanges de clés modernes (curve25519-sha256, ecdh-sha2-nistp256) garantissent la confidentialité persistante (Perfect Forward Secrecy) : même si la clé privée du serveur est compromise a posteriori, les sessions passées restent confidentielles car les clés de session sont éphémères.

Authentification par clés publiques#

Génération de clés#

# Ed25519 — recommandé (courbe Edwards, 256 bits, très rapide)
ssh-keygen -t ed25519 -C "user@machine-$(date +%Y%m%d)" -f ~/.ssh/id_ed25519

# RSA 4096 bits — compatibilité maximale avec les anciens systèmes
ssh-keygen -t rsa -b 4096 -C "user@machine" -f ~/.ssh/id_rsa

# ECDSA 521 bits
ssh-keygen -t ecdsa -b 521 -f ~/.ssh/id_ecdsa

# Modifier la passphrase d'une clé existante
ssh-keygen -p -f ~/.ssh/id_ed25519

# Afficher la clé publique
cat ~/.ssh/id_ed25519.pub
# ssh-ed25519 AAAA...base64... user@machine-20240101

# Empreinte de la clé
ssh-keygen -l -f ~/.ssh/id_ed25519
# 256 SHA256:aBcDeFg... user@machine-20240101 (ED25519)

Déploiement sur un serveur#

# Méthode recommandée
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@serveur

# Manuellement
cat ~/.ssh/id_ed25519.pub | ssh user@serveur \
    "mkdir -p ~/.ssh && chmod 700 ~/.ssh && \
     cat >> ~/.ssh/authorized_keys && \
     chmod 600 ~/.ssh/authorized_keys"

Format authorized_keys#

# Commentaire
ssh-ed25519 AAAA...clé... user@machine

# Options restreintes
no-port-forwarding,no-X11-forwarding,from="192.168.1.0/24" ssh-ed25519 AAAA... bastion

# Clé pour une commande forcée
command="/usr/local/bin/backup.sh",no-pty ssh-rsa AAAA... backup-robot

Les options disponibles incluent from= (restriction IP), command= (forcer une commande), no-pty, no-port-forwarding, permitopen= (restreindre les tunnels), expiry-time=.

Configuration client — ~/.ssh/config#

Le fichier de configuration client évite de retaper des options longues et permet de gérer plusieurs identités.

# ~/.ssh/config
# Paramètres globaux
Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3
    AddKeysToAgent yes
    IdentitiesOnly yes

# Serveur de production
Host prod
    HostName 203.0.113.10
    User deploy
    Port 2222
    IdentityFile ~/.ssh/id_ed25519_prod
    ForwardAgent no

# Serveur via bastion
Host srv-interne
    HostName 10.0.0.5
    User admin
    ProxyJump bastion.example.com
    IdentityFile ~/.ssh/id_ed25519

# Bastion
Host bastion.example.com
    User jump
    IdentityFile ~/.ssh/id_ed25519_bastion
    ControlMaster auto
    ControlPath ~/.ssh/cm-%r@%h:%p
    ControlPersist 4h

# Wildcard pour le réseau 10.x
Host 10.0.*
    User ubuntu
    IdentityFile ~/.ssh/id_ed25519
    StrictHostKeyChecking accept-new
# Utilisation simplifiée
ssh prod          # équivalent à : ssh -p 2222 -i ~/.ssh/id_ed25519_prod deploy@203.0.113.10
ssh srv-interne   # passe automatiquement par le bastion

Options clés de ~/.ssh/config :

Directive

Rôle

HostName

Nom DNS ou IP réelle

User

Nom d’utilisateur distant

Port

Port SSH (défaut : 22)

IdentityFile

Clé privée à utiliser

ProxyJump

Bastion(s) intermédiaire(s)

ControlMaster

Multiplexage (auto/yes/no)

ControlPath

Chemin du socket de contrôle

ControlPersist

Durée de vie du maître

ForwardAgent

Transfert d’agent (prudence)

ServerAliveInterval

Keep-alive (secondes)

StrictHostKeyChecking

Vérification fingerprint

Configuration serveur — sshd_config#

# /etc/ssh/sshd_config — directives essentielles
# Port et interface d'écoute
Port 22
ListenAddress 0.0.0.0
ListenAddress ::

# Authentification
PermitRootLogin no                  # jamais root en direct
PasswordAuthentication no           # clés uniquement
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys

# Contrôle d'accès
AllowUsers alice bob deploy         # whitelist d'utilisateurs
AllowGroups sshusers                # ou par groupe
MaxAuthTries 3
LoginGraceTime 30

# Fonctionnalités dangereuses
X11Forwarding no
AllowTcpForwarding yes              # nécessaire pour les tunnels
AllowAgentForwarding no             # désactiver si non requis
PermitEmptyPasswords no

# Sessions
ClientAliveInterval 300
ClientAliveCountMax 2
MaxSessions 10
MaxStartups 10:30:100               # anti-DoS : 10 connexions non-auth max

# Bannière de connexion
Banner /etc/ssh/banner.txt

# Restrictions SFTP
Subsystem sftp internal-sftp

Match Group sftp-only
    ChrootDirectory /srv/sftp/%u
    ForceCommand internal-sftp
    AllowTcpForwarding no
    X11Forwarding no
# Vérifier la syntaxe avant de recharger
sshd -t

# Recharger sans couper les sessions
systemctl reload sshd

Ne jamais se déconnecter avant de tester

Après modification de sshd_config, gardez votre session active et ouvrez une nouvelle connexion dans un autre terminal pour valider que la configuration fonctionne. Une erreur de syntaxe ou une mauvaise directive peuvent vous bloquer dehors définitivement si le serveur n’a pas de console KVM ou IPMI.

Tunnels SSH#

SSH peut créer des tunnels TCP chiffrés pour sécuriser des connexions non sécurisées ou contourner des restrictions réseau.

Forwarding local (-L)#

Redirige un port local vers une destination distante, en passant par le serveur SSH.

# Accéder à une base de données PostgreSQL distante en local
ssh -L 5433:localhost:5432 user@serveur-db
# Puis : psql -h localhost -p 5433 -U postgres

# Accéder à un service interne via bastion
ssh -L 8080:srv-interne.lan:80 user@bastion
# Puis : curl http://localhost:8080

# -N : ne pas ouvrir de shell (tunnel seul)
# -f : passer en arrière-plan
ssh -NfL 5433:db.local:5432 user@bastion

Forwarding distant (-R)#

Expose un port local sur le serveur distant — utile pour donner accès à un service derrière un NAT.

# Exposer le port 80 local sur le port 8080 du serveur distant
ssh -R 8080:localhost:80 user@serveur-distant
# Sur le serveur : curl http://localhost:8080 atteint la machine locale

# Exposer sur toutes les interfaces du serveur (nécessite GatewayPorts yes dans sshd_config)
ssh -R 0.0.0.0:8080:localhost:80 user@serveur-distant

Proxy SOCKS dynamique (-D)#

Crée un proxy SOCKS5 local qui route tout le trafic via le serveur SSH.

# Lancer le proxy SOCKS5 sur le port local 1080
ssh -NfD 1080 user@serveur

# Utiliser avec curl
curl --socks5 localhost:1080 http://target.example.com

# Utiliser avec un navigateur (configurer proxy SOCKS5 : localhost:1080)

ProxyJump et bastion hosts#

Un bastion host (ou jump host) est un serveur SSH publiquement accessible qui sert de relais vers des machines internes non accessibles directement depuis Internet. C’est la pratique de référence pour les accès sécurisés aux infrastructures cloud/on-premise.

Architecture#

Internet → [Bastion 203.0.113.1:22] → [Serveurs internes 10.0.0.x:22]

Le bastion est le seul serveur avec un port 22 ouvert vers l’extérieur. Les serveurs internes n’acceptent que des connexions depuis l’IP du bastion.

Utilisation de ProxyJump#

# En ligne de commande
ssh -J user@bastion.example.com admin@10.0.0.5

# Plusieurs sauts
ssh -J user@bastion1,user@bastion2 admin@10.0.0.5

# Dans ~/.ssh/config (recommandé)
# Host srv-interne
#     ProxyJump bastion.example.com

ProxyCommand — alternative plus flexible#

# Dans ~/.ssh/config
Host srv-interne
    HostName 10.0.0.5
    ProxyCommand ssh -W %h:%p bastion.example.com

# Ou avec netcat pour plus de compatibilité
Host srv-interne
    HostName 10.0.0.5
    ProxyCommand ssh bastion.example.com nc %h %p 2>/dev/null

Sécurisation du bastion

Le bastion doit avoir un durcissement maximal : AllowAgentForwarding no, PermitRootLogin no, AllowTcpForwarding no (sauf si nécessaire), authentification par clé uniquement, fail2ban activé, accès restreint par IP source dans le pare-feu. Il ne doit exécuter aucun autre service.

Multiplexage SSH#

Le multiplexage SSH (ControlMaster) permet à plusieurs connexions vers le même serveur de partager une seule connexion TCP déjà authentifiée. Les connexions suivantes s’établissent en millisecondes.

# ~/.ssh/config
Host *
    ControlMaster auto
    ControlPath ~/.ssh/cm-%r@%h:%p
    ControlPersist 2h
# Vérifier si une connexion maître est active
ssh -O check user@serveur

# Envoyer un signal (stop, exit)
ssh -O stop user@serveur

# Lister les sockets de contrôle actifs
ls ~/.ssh/cm-*

ControlPersist 2h maintient la connexion maître active 2 heures après la fermeture du dernier canal. Utile dans les scripts Ansible ou les déploiements répétitifs.

SFTP et SCP#

SCP — copie simple#

# Copier un fichier local vers un serveur
scp fichier.txt user@serveur:/home/user/

# Copier récursivement un répertoire
scp -r ./mon-app/ user@serveur:/var/www/

# Copier depuis un serveur
scp user@serveur:/var/log/nginx/access.log ./

# Copier entre deux serveurs distants
scp user@srv1:/tmp/fichier user@srv2:/tmp/

# Via bastion (utilise la config ~/.ssh/config)
scp -J bastion user@srv-interne:/etc/nginx/nginx.conf ./

SFTP — client interactif#

# Connexion interactive
sftp user@serveur

# Commandes SFTP interactives
sftp> ls -la
sftp> cd /var/www
sftp> get nginx.conf ./nginx.conf.bak
sftp> put ./index.html /var/www/html/
sftp> mkdir /var/www/html/assets
sftp> chmod 644 /var/www/html/index.html
sftp> bye

# Transfert en batch (non-interactif)
sftp -b - user@serveur << 'EOF'
put backup.tar.gz /srv/backups/
EOF

Restriction SFTP avec chroot#

# /etc/ssh/sshd_config
Subsystem sftp internal-sftp

Match Group sftp-only
    ChrootDirectory /srv/sftp/%u
    ForceCommand internal-sftp -l INFO
    AllowTcpForwarding no
    X11Forwarding no
    PasswordAuthentication yes
# Créer l'environnement chroot (propriétaire root obligatoire)
mkdir -p /srv/sftp/alice
chown root:root /srv/sftp/alice
chmod 755 /srv/sftp/alice

# Sous-répertoire accessible à l'utilisateur
mkdir /srv/sftp/alice/uploads
chown alice:sftp-only /srv/sftp/alice/uploads

# Ajouter alice au groupe
usermod -aG sftp-only alice

Sécurisation SSH#

Hardening de sshd_config#

Au-delà des directives déjà citées, les algorithmes cryptographiques doivent être durcis pour exclure les algorithmes obsolètes :

# Algorithmes recommandés (OpenSSH 9.x)
# /etc/ssh/sshd_config

KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp521
HostKeyAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com

Certificats SSH (SSH CA)#

Les certificats SSH permettent à une autorité de certification interne de signer des clés publiques, évitant la gestion de authorized_keys sur chaque serveur.

# Créer une CA SSH
ssh-keygen -t ed25519 -f /etc/ssh/ssh_ca -C "CA-infrastructure"

# Signer la clé publique d'un utilisateur
ssh-keygen -s /etc/ssh/ssh_ca \
    -I "alice@corp" \
    -n alice,admin \
    -V +52w \
    alice_id_ed25519.pub
# Génère : alice_id_ed25519-cert.pub

# Sur le serveur : faire confiance à la CA
echo "TrustedUserCAKeys /etc/ssh/ssh_ca.pub" >> /etc/ssh/sshd_config

# L'utilisateur présente son certificat automatiquement
ssh -i ~/.ssh/alice_id_ed25519 user@serveur

Port knocking#

Le port knocking masque le port SSH en le fermant par défaut ; une séquence de connexions sur des ports spécifiques déclenche l’ouverture temporaire.

# Installation (knockd)
apt install knockd

# /etc/knockd.conf
[options]
    logfile = /var/log/knockd.log

[openSSH]
    sequence    = 7000,8000,9000
    seq_timeout = 5
    command     = /sbin/iptables -A INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
    tcpflags    = syn

[closeSSH]
    sequence    = 9000,8000,7000
    seq_timeout = 5
    command     = /sbin/iptables -D INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
# Côté client
knock serveur 7000 8000 9000
ssh user@serveur
knock serveur 9000 8000 7000

Agent SSH#

L’agent SSH (ssh-agent) stocke les clés privées déchiffrées en mémoire pour éviter de retaper la passphrase à chaque connexion.

# Démarrer l'agent (généralement lancé par la session graphique)
eval "$(ssh-agent -s)"
# Agent pid 12345

# Ajouter une clé (demande la passphrase une seule fois)
ssh-add ~/.ssh/id_ed25519

# Ajouter avec expiration automatique (4 heures)
ssh-add -t 14400 ~/.ssh/id_ed25519

# Lister les clés chargées
ssh-add -l

# Supprimer une clé de l'agent
ssh-add -d ~/.ssh/id_ed25519

# Vider l'agent
ssh-add -D

Forwarding d’agent#

Le forwarding d’agent (-A ou ForwardAgent yes) permet à des serveurs intermédiaires d’utiliser les clés de l’agent local pour d’autres connexions SSH.

# Connexion avec forwarding
ssh -A user@bastion

# Sur le bastion, l'agent local est accessible
ssh user@srv-interne   # utilise la clé de l'agent distant

Risque du forwarding d’agent

ForwardAgent yes est dangereux sur des serveurs non maîtrisés : un administrateur root du bastion peut utiliser votre agent pour se connecter à n’importe quel serveur où votre clé est autorisée. Utilisez-le uniquement sur des bastions de confiance, idéalement avec l’option confirm : ssh-add -c ~/.ssh/id_ed25519.


Démonstrations Python#

Diagramme de séquence du handshake SSH#

sns.set_theme(style="white", palette="muted", font_scale=1.0)

fig, ax = plt.subplots(figsize=(13, 9))
ax.set_xlim(0, 13)
ax.set_ylim(0, 10)
ax.axis("off")

# Colonnes Client et Serveur
for x, label, color in [(2.5, "Client SSH", "#4e79a7"), (10.5, "Serveur SSH", "#59a14f")]:
    ax.add_patch(plt.Rectangle((x-1.2, 8.8), 2.4, 0.7,
                                facecolor=color+"44", edgecolor=color, linewidth=2))
    ax.text(x, 9.15, label, ha="center", va="center",
            fontsize=11, fontweight="bold")
    ax.plot([x, x], [0.3, 8.8], color=color, linewidth=1.5, linestyle="--", alpha=0.5)

def message(ax, de, vers, y, txt, color="#555555"):
    x1, x2 = (2.5, 10.5) if de == "client" else (10.5, 2.5)
    ax.annotate("", xy=(x2, y), xytext=(x1, y),
                arrowprops=dict(arrowstyle="->", color=color, lw=1.5))
    mx = (x1 + x2) / 2
    ax.text(mx, y + 0.18, txt, ha="center", va="bottom",
            fontsize=8.5, color=color)

def etape(ax, y, num, txt, color="#333333"):
    ax.text(6.5, y, f"{num}. {txt}", ha="center", va="center",
            fontsize=8, color=color,
            bbox=dict(boxstyle="round,pad=0.3", facecolor="#f5f5f5",
                      edgecolor=color, linewidth=0.8))

message(ax, "client", "serveur", 8.1, "TCP SYN → port 22", "#888888")
message(ax, "serveur", "client", 7.55, "← TCP SYN-ACK", "#888888")
message(ax, "client", "serveur", 7.0, "SSH-2.0-OpenSSH_9.x (bannière)", "#4e79a7")
message(ax, "serveur", "client", 6.45, "← SSH-2.0-OpenSSH_9.x + host key algos", "#59a14f")
message(ax, "client", "serveur", 5.9, "SSH_MSG_KEXINIT (algos supportés)", "#4e79a7")
message(ax, "serveur", "client", 5.35, "← SSH_MSG_KEXINIT", "#59a14f")
message(ax, "client", "serveur", 4.8, "ECDH ephemeral key (curve25519)", "#f28e2b")
message(ax, "serveur", "client", 4.25, "← ECDH key + signature host key", "#f28e2b")

ax.text(6.5, 3.75, "★ Dérivation clés de session (HKDF)", ha="center",
        fontsize=9, color="#e15759", fontweight="bold",
        bbox=dict(boxstyle="round,pad=0.4", facecolor="#ffe0e0",
                  edgecolor="#e15759", linewidth=1.5))

message(ax, "client", "serveur", 3.2, "SSH_MSG_NEWKEYS (chiffrement actif)", "#76b7b2")
message(ax, "serveur", "client", 2.7, "← SSH_MSG_NEWKEYS", "#76b7b2")
message(ax, "client", "serveur", 2.2, "Authentification (clé publique / mot de passe)", "#4e79a7")
message(ax, "serveur", "client", 1.7, "← SSH_MSG_USERAUTH_SUCCESS", "#59a14f")
message(ax, "client", "serveur", 1.2, "Ouverture canal (session / tunnel / sftp)", "#4e79a7")
message(ax, "serveur", "client", 0.7, "← Canal accepté", "#59a14f")

ax.set_title("Handshake SSH — diagramme de séquence",
             fontsize=13, fontweight="bold", pad=14)
plt.show()
_images/db0743e2f5606fd412e19a25e3e20fff5d0c2d909f065c8a54f05cff5bfc1902.png

Simulation de génération de clés (concepts cryptographiques)#

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

# Simulation pédagogique du concept de paire de clés
# (sans lib cryptography — utilisation de hashlib + secrets + base64)

def simuler_cle_ed25519():
    """Simule la structure d'une clé Ed25519 à des fins pédagogiques.
    En réalité : clé privée = 32 octets aléatoires, clé publique = point sur la courbe Ed25519."""
    # Clé privée : 32 octets aléatoires (seed)
    seed = secrets.token_bytes(32)

    # Simulation de la dérivation de la clé publique
    # (en vrai : multiplication scalaire sur Ed25519 — ici on simule avec SHA-512)
    h = hashlib.sha512(seed).digest()
    # Modifier les bits selon la spec Ed25519
    a = bytearray(h[:32])
    a[0] &= 248
    a[31] &= 127
    a[31] |= 64
    public_key_bytes = bytes(a)  # simulé

    # Encodage base64 (format OpenSSH)
    priv_b64 = base64.b64encode(seed).decode()
    pub_b64 = base64.b64encode(b"ssh-ed25519" + public_key_bytes).decode()

    return {
        "type": "ed25519",
        "seed_hex": seed.hex()[:32] + "…",
        "private_b64": priv_b64[:40] + "…",
        "public_b64": pub_b64[:60] + "…",
        "fingerprint": "SHA256:" + base64.b64encode(
            hashlib.sha256(public_key_bytes).digest()
        ).decode()[:43],
        "longueur_privee": len(seed) * 8,
        "longueur_publique": len(public_key_bytes) * 8,
    }

def simuler_cle_rsa(bits=4096):
    """Simule la structure d'une clé RSA (seed aléatoire, taille réelle)."""
    seed = secrets.token_bytes(bits // 8)
    pub = hashlib.sha256(seed).digest()
    return {
        "type": f"rsa{bits}",
        "seed_hex": seed.hex()[:32] + "…",
        "private_b64": base64.b64encode(seed[:64]).decode()[:40] + "…",
        "public_b64": base64.b64encode(pub).decode()[:60] + "…",
        "fingerprint": "SHA256:" + base64.b64encode(
            hashlib.sha256(pub).digest()
        ).decode()[:43],
        "longueur_privee": bits,
        "longueur_publique": bits,
    }

cles = [simuler_cle_ed25519(), simuler_cle_rsa(4096), simuler_cle_rsa(2048)]

print("=== Simulation de génération de paires de clés SSH ===\n")
for c in cles:
    print(f"Type         : {c['type'].upper()}")
    print(f"Seed/Privée  : {c['seed_hex']}")
    print(f"Clé publique : {c['public_b64']}")
    print(f"Empreinte    : {c['fingerprint']}")
    print(f"Taille privée: {c['longueur_privee']} bits")
    print()

# Visualisation comparative
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# Comparaison des tailles de clés
types = ["Ed25519\n(256b seed)", "ECDSA\n(521b)", "RSA-2048", "RSA-4096"]
tailles_priv = [256, 521, 2048, 4096]
tailles_pub = [256, 521, 2048, 4096]
temps_gen = [0.001, 0.003, 0.8, 5.2]   # secondes simulées

ax = axes[0]
x = np.arange(len(types))
width = 0.4
ax.bar(x - width/2, tailles_priv, width, label="Taille clé (bits)", color="#4e79a7")
ax.bar(x + width/2, [t*10 for t in temps_gen], width,
       label="Temps génération (×10 ms)", color="#f28e2b")
ax.set_xticks(x)
ax.set_xticklabels(types, fontsize=9)
ax.set_ylabel("Bits / ms×10")
ax.set_title("Taille des clés", fontweight="bold")
ax.legend()

# Sécurité relative
ax2 = axes[1]
criteres = ["Sécurité\n(bits équivalents)", "Vitesse\nsignature", "Vitesse\nvérification",
            "Taille\nsignature", "Compatibilité"]
scores = {
    "Ed25519":  [128, 100, 100, 95, 90],
    "ECDSA-521":[260, 85, 80, 75, 80],
    "RSA-2048": [112, 50, 70, 30, 100],
    "RSA-4096": [140, 20, 60, 20, 100],
}
colors2 = {"Ed25519": "#59a14f", "ECDSA-521": "#4e79a7", "RSA-2048": "#f28e2b", "RSA-4096": "#e15759"}

x2 = np.arange(len(criteres))
w = 0.2
offsets2 = [-1.5, -0.5, 0.5, 1.5]
for i, (algo, vals) in enumerate(scores.items()):
    ax2.bar(x2 + offsets2[i]*w, vals, w, label=algo,
            color=colors2[algo], edgecolor="white")
ax2.set_xticks(x2)
ax2.set_xticklabels(criteres, fontsize=8.5)
ax2.set_ylabel("Score relatif (0-100)")
ax2.set_title("Comparaison des algorithmes", fontweight="bold")
ax2.legend(fontsize=8)
ax2.set_ylim(0, 120)

plt.suptitle("Algorithmes de clés SSH — analyse comparative", fontsize=13, fontweight="bold")
plt.show()
=== Simulation de génération de paires de clés SSH ===

Type         : ED25519
Seed/Privée  : 8dff949430f1a35c18beea2f4a32972b…
Clé publique : c3NoLWVkMjU1MTn40m0gIjKH1DVEAp7gr9+xSTFMo8bqSwnTrRiRPfGZZA==…
Empreinte    : SHA256:qnj+22cM9HXu/uLOvPspG7UD/QCX0io9K5S92c6akIw
Taille privée: 256 bits

Type         : RSA4096
Seed/Privée  : 3e5c6b59ff7258990fb51d417abfec76…
Clé publique : vbr0ZGHS0oWvaVC3wrI+aHnYHizGgRe8RleAme/wqJk=…
Empreinte    : SHA256:enaumoBWo3+Hr30aAd6tsHcTGvmuusUwPKXiaTn3r5o
Taille privée: 4096 bits

Type         : RSA2048
Seed/Privée  : 53ef19eff233de1834ebcf0eca928452…
Clé publique : PLuNELlW17mr+UdeZqam+8Q/JdM4wSzHaRSZu8bVOqc=…
Empreinte    : SHA256:dy0cO9naJbQsdgJM48Gd9ImZauFrinEKBH0g0qQ0y3Y
Taille privée: 2048 bits
_images/275d3ab82e68aa9df2f96d63a491fded04895cb6a200fc960ff5880bd143622a.png

Architecture bastion host#

sns.set_theme(style="white", palette="muted", font_scale=1.0)

fig, ax = plt.subplots(figsize=(14, 8))
ax.set_xlim(0, 14)
ax.set_ylim(0, 8)
ax.axis("off")

def boite(ax, x, y, w, h, titre, sous="", color="#4e79a7", fs=9):
    r = plt.Rectangle((x-w/2, y-h/2), w, h,
                       facecolor=color+"33", edgecolor=color, linewidth=2, zorder=3)
    ax.add_patch(r)
    ax.text(x, y + (0.15 if sous else 0), titre, ha="center", va="center",
            fontsize=fs, fontweight="bold", zorder=4)
    if sous:
        ax.text(x, y-0.22, sous, ha="center", va="center", fontsize=7.5,
                color="#444444", zorder=4)

def fleche_ssh(ax, x1, y1, x2, y2, label=""):
    ax.annotate("", xy=(x2, y2), xytext=(x1, y1),
                arrowprops=dict(arrowstyle="->", color="#4e79a7",
                                lw=2, connectionstyle="arc3,rad=0.0"), zorder=2)
    if label:
        ax.text((x1+x2)/2, (y1+y2)/2 + 0.22, label,
                ha="center", fontsize=8, color="#4e79a7",
                bbox=dict(boxstyle="round,pad=0.2", facecolor="white",
                          edgecolor="#4e79a7", linewidth=0.8))

def zone(ax, x, y, w, h, label, color):
    r = plt.Rectangle((x, y), w, h, facecolor=color+"11",
                       edgecolor=color, linewidth=2, linestyle="--", zorder=1)
    ax.add_patch(r)
    ax.text(x + w/2, y + h - 0.25, label, ha="center", va="top",
            fontsize=9, color=color, fontweight="bold")

# Zones
zone(ax, 0.1, 0.3, 3.8, 7.4, "Internet / Poste Développeur", "#888888")
zone(ax, 4.2, 3.8, 5.6, 3.7, "DMZ (pare-feu)", "#e15759")
zone(ax, 4.2, 0.3, 5.6, 3.3, "LAN privé", "#59a14f")
zone(ax, 10.1, 0.3, 3.8, 7.4, "Infrastructure interne", "#4e79a7")

# Développeur
boite(ax, 2.0, 6.2, 2.8, 1.0, "Développeur", "~/.ssh/config\nid_ed25519", "#888888")

# Bastion
boite(ax, 7.0, 6.2, 3.0, 1.0, "Bastion SSH", "203.0.113.1:22\nfail2ban + MFA", "#e15759")
fleche_ssh(ax, 3.4, 6.2, 5.5, 6.2, "SSH :22\n(clé + MFA)")

# Règle pare-feu
ax.text(6.0, 5.3, "Pare-feu : seul port 22\nde l'extérieur autorisé\nvers le bastion",
        ha="center", fontsize=7.5, color="#e15759",
        bbox=dict(boxstyle="round", facecolor="#ffe8e8", edgecolor="#e15759"))

# Serveurs internes
serveurs = [
    ("srv-web-01", "10.0.10.1", "#f28e2b"),
    ("srv-web-02", "10.0.10.2", "#f28e2b"),
    ("srv-db-01",  "10.0.20.1", "#4e79a7"),
    ("srv-mgmt",   "10.0.1.1",  "#76b7b2"),
]
y_positions = [5.5, 4.0, 2.5, 1.0]
for (nom, ip, color), y in zip(serveurs, y_positions):
    boite(ax, 12.0, y, 2.8, 0.85, nom, ip, color, fs=8.5)
    fleche_ssh(ax, 8.5, 5.8, 10.6, y + 0.1 if y > 3 else y + 0.4,
               "SSH :22")

# Flux ProxyJump
ax.annotate("", xy=(1.0, 4.5), xytext=(1.0, 5.7),
            arrowprops=dict(arrowstyle="->", color="#555555",
                            lw=1.5, linestyle="dashed"))
ax.text(0.3, 5.1, "ProxyJump\ntransparent\npour l'user", fontsize=7.5,
        color="#555555", va="center")

ax.set_title("Architecture bastion host — accès SSH sécurisé à l'infrastructure",
             fontsize=13, fontweight="bold", pad=14)
plt.show()
_images/80cc9404e295a333f7462ee6ee9ea803e2d93ac153c912d6dd3fbb3abe999af0.png

Résumé#

SSH est le protocole d’administration à distance de référence sous Linux. Sa richesse fonctionnelle va bien au-delà de la simple connexion distante.

Points essentiels à retenir :

  • Ed25519 est l’algorithme de clé recommandé : sécurité équivalente à RSA-3072 avec des clés 17 fois plus courtes et des opérations cryptographiques plus rapides.

  • Le fichier ~/.ssh/config est un outil de productivité majeur : définir des alias, des clés par contexte, des bastions et le multiplexage évite des erreurs et accélère le travail quotidien.

  • PermitRootLogin no et PasswordAuthentication no sont les deux directives de sécurité les plus importantes dans sshd_config.

  • Le multiplexage (ControlMaster) rend les connexions successives quasi-instantanées — essentiel pour Ansible et les déploiements.

  • Les tunnels SSH (-L, -R, -D) permettent de sécuriser des protocoles non chiffrés ou d’accéder à des ressources derrière un NAT sans VPN dédié.

  • L’architecture bastion host est le standard industriel pour l’accès aux infrastructures cloud : un seul point d’entrée audité, ProxyJump rend la traversée transparente pour l’utilisateur.

  • Les certificats SSH (SSH CA) simplifient la gestion à grande échelle : plus besoin de propager authorized_keys sur chaque serveur, la CA signe les clés avec une durée de validité.

  • L’agent SSH avec ssh-add -t limite la durée d’exposition des clés en mémoire ; éviter ForwardAgent sauf sur des bastions de confiance.