// Portfolio technique — Maxime Ramaugé

Routage & commutation, sans prise de tête.

Le strict nécessaire pour configurer un routage statique, du RIP, de l'OSPF, des VLAN et des liaisons trunk sur des équipements Cisco (ou compatibles). Chaque bloc de commandes est prêt à copier en TP, avec juste ce qu'il faut d'explication autour.

Les comptes rendus complets de mes réalisations (VPS, déploiement GitHub...) contiennent des informations que je ne peux pas rendre publiques. Pour toute question à leur sujet, n'hésitez pas à me contacter sur LinkedIn.
R1 R2 liaison WAN — .0/30 SW1 SW2 VLAN10 VLAN20 VLAN10 VLAN20
01

Routage statique

Le routeur ne connaît que les routes qu'on lui écrit à la main. Simple à comprendre, à réserver aux petits réseaux ou aux routes par défaut.

Accéder au mode de configuration

R1> enable
R1# configure terminal
R1(config)#
#   "enable" passe en mode privilégié, "configure terminal" en mode configuration globale

Syntaxe de base

Sur R1, pour joindre le réseau distant 192.168.20.0/24 situé derrière R2 :

R1(config)# ip route 192.168.20.0 255.255.255.0 10.0.0.2
#            réseau destiné      masque         passerelle (next-hop)

Route par défaut

Utile quand le routeur doit envoyer vers Internet (ou vers un routeur qui sait, lui, comment y aller) tout ce qu'il ne connaît pas :

R1(config)# ip route 0.0.0.0 0.0.0.0 10.0.0.2
À retenir : une route statique doit être configurée dans les deux sens (aller ET retour) sinon un des deux routeurs répondra dans le vide.
02

Routage dynamique — RIP

RIP annonce automatiquement les réseaux directement connectés aux voisins. Facile à mettre en place, mais limité (métrique = nombre de sauts, max 15).

Accéder au mode de configuration

R1> enable
R1# configure terminal
R1(config)#

Configuration RIPv2

R1(config)# router rip
R1(config-router)# version 2
R1(config-router)# network 10.0.0.0
R1(config-router)# network 192.168.10.0
R1(config-router)# no auto-summary
#   "network" = tous les réseaux DIRECTEMENT connectés à R1 à annoncer
À retenir : no auto-summary évite que RIP ne "résume" bêtement les réseaux en classes A/B/C — presque toujours nécessaire en TP avec du sous-réseautage (VLSM).
03

Routage dynamique — OSPF

OSPF fonctionne par zones (areas) et calcule le meilleur chemin selon la bande passante des liens, pas le nombre de sauts. En BTS, on reste généralement en zone unique (area 0).

Accéder au mode de configuration

R1> enable
R1# configure terminal
R1(config)#

Configuration OSPF simple (area 0)

R1(config)# router ospf 1
R1(config-router)# network 10.0.0.0 0.0.0.3 area 0
R1(config-router)# network 192.168.10.0 0.0.0.255 area 0
#   "1" = simple identifiant de processus, local au routeur
#   le 2e paramètre est un WILDCARD MASK, pas un masque classique

Comprendre le wildcard mask

C'est l'inverse d'un masque de sous-réseau : on met des 0 sur les bits fixes du réseau, des 1 sur les bits qui peuvent varier.

Masque classiqueWildcard équivalent
255.255.255.00.0.0.255
255.255.255.2520.0.0.3
255.255.0.00.0.255.255
À retenir : RIP et OSPF font le même travail (échanger les routes automatiquement) mais OSPF est celui qu'on retrouve en entreprise — RIP reste surtout pédagogique.
04

VLAN sur un switch

Un VLAN segmente logiquement le réseau (il sépare le réseau en plusieurs parties indépendantes et invisibles les unes des autres) sur un même switch physique : les machines d'un VLAN ne se voient pas avec celles d'un autre VLAN sans passer par un routeur.

Accéder au mode de configuration

SW1> enable
SW1# configure terminal
SW1(config)#

1. Créer les VLAN

SW1(config)# vlan 10
SW1(config-vlan)# name UTILISATEURS
SW1(config-vlan)# exit
SW1(config)# vlan 20
SW1(config-vlan)# name SERVEURS
SW1(config-vlan)# exit

2. Affecter un port (mode access)

SW1(config)# interface fa0/1
SW1(config-if)# switchport mode access
SW1(config-if)# switchport access vlan 10

Affecter une plage de ports d'un coup

SW1(config)# interface range fa0/1 - 10
SW1(config-if-range)# switchport mode access
SW1(config-if-range)# switchport access vlan 10
À retenir : un port en mode access n'appartient qu'à UN SEUL VLAN. C'est le mode par défaut pour brancher un PC.
05

Ports trunk (agrégation de VLAN)

Un trunk fait passer plusieurs VLAN sur un seul câble — typiquement entre deux switches, ou entre un switch et un routeur (routage inter-VLAN).

Accéder au mode de configuration

SW1> enable
SW1# configure terminal
SW1(config)#
#   même principe côté routeur pour les sous-interfaces (R1) plus bas

Configurer un port en trunk

SW1(config)# interface gi0/1
SW1(config-if)# switchport trunk encapsulation dot1q
SW1(config-if)# switchport mode trunk
#   "encapsulation dot1q" n'est requise que sur certains anciens switches
#   (qui supportent aussi ISL) — souvent facultative sur du matériel récent

Limiter les VLAN autorisés sur le trunk (facultatif)

SW1(config-if)# switchport trunk allowed vlan 10,20

Routage inter-VLAN — sous-interfaces sur le routeur

Le routeur relié au trunk a besoin d'une sous-interface par VLAN (technique du "router on a stick") :

R1(config)# interface gi0/0.10
R1(config-subif)# encapsulation dot1Q 10
R1(config-subif)# ip address 192.168.10.1 255.255.255.0

R1(config)# interface gi0/0.20
R1(config-subif)# encapsulation dot1Q 20
R1(config-subif)# ip address 192.168.20.1 255.255.255.0
À retenir : le VLAN natif (par défaut VLAN 1) circule sans étiquette 802.1Q sur le trunk. Pensez à le changer en TP si le sujet le demande (switchport trunk native vlan 99).
06

Pare-feu — Listes de contrôle d'accès (ACL)

Sur un routeur Cisco, le "pare-feu" de base, ce sont les ACL (Access Control Lists) : une suite de règles permit / deny évaluées dans l'ordre, avec un deny any implicite à la fin.

Accéder au mode de configuration

R1> enable
R1# configure terminal
R1(config)#

ACL standard (filtre sur l'IP source uniquement)

Numérotées de 1 à 99. À placer le plus près possible de la destination, car elles ne regardent que l'origine du paquet.

R1(config)# access-list 10 deny 192.168.20.0 0.0.0.255
R1(config)# access-list 10 permit any
R1(config)# interface gi0/1
R1(config-if)# ip access-group 10 in
#   bloque tout le VLAN 192.168.20.0/24, autorise le reste, sur le trafic ENTRANT

ACL étendue (filtre sur IP source, destination, protocole, port)

Numérotées de 100 à 199. Bien plus précises : on peut bloquer un seul service sans toucher au reste du trafic. À placer le plus près possible de la source.

R1(config)# access-list 100 deny tcp any host 192.168.10.10 eq 80
R1(config)# access-list 100 permit ip any any
R1(config)# interface gi0/0
R1(config-if)# ip access-group 100 out
#   interdit le HTTP (port 80) vers ce serveur précis, autorise tout le reste
À retenir : une ACL sans permit any (ou équivalent) à la fin bloque TOUT — même le trafic qu'on voulait juste laisser passer. Et une ACL non appliquée à une interface (ip access-group) ne fait rien du tout.
07

NAT / PAT

Le NAT traduit des adresses privées en adresses publiques (et inversement) pour sortir sur Internet. Trois variantes courantes, de la plus simple à la plus utilisée.

Accéder au mode de configuration

R1> enable
R1# configure terminal
R1(config)#

1. Étape commune : désigner inside / outside

Quelle que soit la variante, le routeur doit savoir quelle interface regarde le réseau privé et laquelle regarde le réseau public :

R1(config)# interface gi0/0
R1(config-if)# ip nat inside
R1(config)# interface gi0/1
R1(config-if)# ip nat outside

2. NAT statique — une IP privée ↔ une IP publique fixe

Utile pour un serveur qui doit toujours avoir la même adresse publique :

R1(config)# ip nat inside source static 192.168.10.10 203.0.113.10

3. NAT dynamique — un pool d'IP publiques partagé

R1(config)# ip nat pool POOL_PUB 203.0.113.10 203.0.113.20 netmask 255.255.255.0
R1(config)# access-list 1 permit 192.168.10.0 0.0.0.255
R1(config)# ip nat inside source list 1 pool POOL_PUB
#   l'ACL définit QUI a le droit d'être traduit, pas un filtrage de sécurité ici

4. PAT / NAT overload — toute la LAN derrière une seule IP publique

Le cas le plus courant (box internet, petite entreprise) : chaque connexion privée est distinguée grâce à un numéro de port différent.

R1(config)# access-list 1 permit 192.168.10.0 0.0.0.255
R1(config)# ip nat inside source list 1 interface gi0/1 overload
#   "overload" = PAT : réutilise l'IP publique de gi0/1 pour tout le monde
À retenir : sans ip nat inside / ip nat outside sur les bonnes interfaces, aucune règle NAT ne s'applique — c'est l'erreur la plus fréquente en TP.
08

SSH — connexion et durcissement

Le fichier de configuration du serveur SSH est /etc/ssh/sshd_config. On modifie, puis on relance le service.

1. Connexion par clé (au lieu du mot de passe)

user@poste:~$ ssh-keygen -t ed25519
user@poste:~$ ssh-copy-id user@serveur
#   copie la clé publique dans ~/.ssh/authorized_keys du serveur

2. Durcir /etc/ssh/sshd_config

root@serveur:~# nano /etc/ssh/sshd_config

Port 2222
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
PermitEmptyPasswords no
MaxAuthTries 3
AllowUsers admin sisr
X11Forwarding no
Protocol 2

Puis on applique :

root@serveur:~# systemctl restart sshd
DirectiveEffet
Port 2222Change le port par défaut (22) — réduit le bruit des scans automatiques
PermitRootLogin noInterdit la connexion directe en root
PasswordAuthentication noOblige l'authentification par clé
AllowUsers ...Liste blanche explicite des comptes autorisés en SSH
À retenir : toujours garder une session SSH déjà ouverte pendant qu'on teste une nouvelle config sshd_config — une erreur peut vous enfermer dehors si vous fermez la session avant de vérifier.
09

Fail2ban — bannir les tentatives d'intrusion

Fail2ban surveille les logs (SSH, web...) et bannit temporairement une IP après trop d'échecs d'authentification, via des règles pare-feu automatiques.

1. Installation

root@serveur:~# apt update && apt install fail2ban -y

2. Configuration locale (ne jamais modifier jail.conf directement)

root@serveur:~# cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
root@serveur:~# nano /etc/fail2ban/jail.local

[sshd]
enabled  = true
port     = 2222
filter   = sshd
maxretry = 3
findtime = 10m
bantime  = 1h
root@serveur:~# systemctl restart fail2ban
ParamètreSens
maxretryNombre d'échecs tolérés avant bannissement
findtimeFenêtre de temps sur laquelle on compte les échecs
bantimeDurée du bannissement de l'IP

3. Suivi

root@serveur:~# fail2ban-client status sshd
root@serveur:~# fail2ban-client unban 203.0.113.45
À retenir : pensez à mettre à jour le port de la jail sshd si vous avez changé le port SSH par défaut, sinon Fail2ban surveille le mauvais port.
10

UFW — pare-feu simplifié (Ubuntu/Debian)

UFW (Uncomplicated Firewall) est une surcouche à iptables, pensée pour rester lisible. Bonne politique par défaut : tout bloquer en entrée, tout autoriser en sortie.

Politique de base

root@serveur:~# ufw default deny incoming
root@serveur:~# ufw default allow outgoing

Autoriser les services nécessaires

root@serveur:~# ufw allow 2222/tcp
#   toujours autoriser SSH AVANT d'activer ufw, sinon coupure immédiate
root@serveur:~# ufw allow 80/tcp
root@serveur:~# ufw allow 443/tcp
root@serveur:~# ufw limit 2222/tcp
#   "limit" ralentit les connexions répétées trop rapides sur ce port (anti brute-force)

Activer et vérifier

root@serveur:~# ufw enable
root@serveur:~# ufw status verbose
root@serveur:~# ufw delete allow 80/tcp
#   pour retirer une règle qu'on ne veut plus
À retenir : l'ordre compte — ufw allow 2222/tcp doit être fait avant ufw enable si vous êtes connecté à distance en SSH.
11

ACL sur les dossiers (droits Linux avancés)

Les droits classiques (rwx propriétaire / groupe / autres) ne suffisent pas toujours. Les ACL POSIX permettent d'ajouter des droits précis pour un utilisateur ou un groupe supplémentaire sur un dossier.

1. Vérifier si les ACL sont activées

root@serveur:~# mount | grep acl
#   si absent, ajouter l'option "acl" dans /etc/fstab puis remonter la partition

2. Donner des droits précis avec setfacl

root@serveur:~# setfacl -m u:sisr:rwx /partage/projet
#   donne à l'utilisateur "sisr" les droits rwx sur /partage/projet
root@serveur:~# setfacl -m g:tp-sio:rx /partage/projet
#   donne au groupe "tp-sio" les droits lecture + exécution

3. ACL par défaut (héritée par les nouveaux fichiers/dossiers)

root@serveur:~# setfacl -d -m u:sisr:rwx /partage/projet
#   -d : cette règle s'appliquera automatiquement à tout ce qui sera créé dans le dossier

4. Lire, retirer

root@serveur:~# getfacl /partage/projet
root@serveur:~# setfacl -x u:sisr /partage/projet
#   retire l'entrée ACL de l'utilisateur "sisr"
root@serveur:~# setfacl -b /partage/projet
#   supprime TOUTES les ACL du dossier (retour aux droits classiques uniquement)
À retenir : un dossier avec des ACL affiche un + à la fin des droits dans ls -l (ex : drwxr-x---+) — c'est le signal qu'il faut penser à getfacl pour voir le détail.
12

Commandes de vérification

Toujours vérifier après avoir configuré — ces commandes suffisent pour 90% des TP.

CommandeÀ quoi ça sert
show ip routeVoir la table de routage (statique + dynamique)
show ip protocolsVérifier les réseaux annoncés en RIP/OSPF
show ip ospf neighborVoir si les voisins OSPF sont bien établis
show vlan briefLister les VLAN et les ports qui leur sont assignés
show interfaces trunkVérifier quels ports sont en trunk et les VLAN autorisés
show running-configRelire toute la config active de l'équipement
show access-listsVoir les ACL configurées et le compteur de paquets filtrés
show ip nat translationsVoir les traductions NAT/PAT actives
show ip nat statisticsVoir le résumé du NAT (pool, hits, interfaces inside/outside)
ping / tracerouteTester la connectivité de bout en bout
systemctl status sshdVérifier que le service SSH tourne et sur quel port
fail2ban-client statusLister les jails actives et les IP bannies
ufw status verboseLister les règles UFW actives et la politique par défaut
getfacl <dossier>Afficher le détail des ACL appliquées à un dossier
nginx -tVérifier la syntaxe de la config Nginx avant de redémarrer le service
certbot certificatesVoir les certificats HTTPS installés et leur date d'expiration
// Section Réalisations

Mes projets concrets

Ici commencent mes réalisations — des projets menés de bout en bout, avec les vrais blocages rencontrés en chemin. D'autres viendront s'ajouter au fil des projets.

13

Mise en place du VPS — retour d'expérience

Déploiement complet de ce portfolio sur un VPS OVH : clé SSH, durcissement, pare-feu, fail2ban, Nginx et HTTPS. Certaines valeurs ci-dessous sont volontairement masquées (IP, clé) — seule la démarche compte.

Ce résumé s'appuie sur mon compte rendu complet (23 pages), qui contient des informations que je ne peux pas rendre publiques (IP, clé SSH...). Pour plus de détails, n'hésitez pas à me contacter sur LinkedIn.

1. Clé SSH et création du VPS

Génération d'une clé ed25519 sur mon poste, puis ajout de la clé publique dans l'interface OVH avant même la création du VPS, pour éviter de dépendre du mot de passe root à la première connexion.

user@poste:~$ ssh-keygen -t ed25519
#   clé publique copiée dans OVH → Mes services → Clé SSH
user@poste:~$ ssh-ed25519 AAAAC3NzaC1lZDI1NTE5[...] portfolio-vps

2. Incident : conflit de host key après réinstallation

Après une réinstallation du VPS, OVH régénère une nouvelle "host key" côté serveur. Mon PC gardait l'ancienne dans known_hosts : même IP, clé différente → SSH bloque la connexion par sécurité (protection anti Man-in-the-Middle).

user@poste:~$ ssh-keygen -R 203.0.113.10
#   supprime l'ancienne entrée pour ce serveur dans known_hosts

Insuffisant dans mon cas : il fallait en plus réassocier la clé SSH au VPS dans l'interface OVH (« Mes services → Clé SSH ») avant la réinstallation suivante.

3. Première connexion et mise à jour

user@poste:~$ ssh user@203.0.113.10
user@vps:~$ sudo su -
#   "su -" seul refusé : pas de mot de passe root défini, il faut passer par sudo
root@vps:~# apt update && apt upgrade -y
#   garder la version de sshd déjà installée pour ne pas écraser une config en cours

4. Durcissement SSH, UFW, fail2ban

Application des principes détaillés plus haut dans cette page : port SSH changé pour 2222, connexion root directe interdite, authentification par mot de passe désactivée.

root@vps:~# sshd -t
#   aucune sortie = fichier de config valide, sinon la commande décrit l'erreur
root@vps:~# systemctl restart ssh

Pare-feu UFW : tout bloqué en entrée sauf 2222 (SSH), 80 et 443 (web).

Piège rencontré : après avoir écrit la règle ufw default deny incoming, il ne faut surtout pas oublier ufw enable — le pare-feu reste inactif tant que cette commande n'a pas été passée.

5. Incident fail2ban : jail.local écrasé et faux positif

Deux problèmes rencontrés en configurant la jail sshd :

root@vps:~# cp /etc/fail2ban/jail.local /etc/fail2ban/jail.local.bak
#   sauvegarde avant de réécrire le fichier avec uniquement les valeurs nécessaires

Un premier test de recherche dans les logs remontait un faux positif : le grep utilisé pour vérifier qu'aucune connexion n'avait réussi n'était pas assez précis et matchait des lignes non pertinentes — corrigé en affinant le motif de recherche.

root@vps:~# fail2ban-client status sshd
#   résultat réel du TP : ~130 tentatives échouées, 2 IP bannies

Face au volume de tentatives en quelques minutes, décision de revérifier que l'authentification par mot de passe était bien désactivée (elle l'était — les tentatives échouaient donc systématiquement, comme attendu).

6. Nginx et envoi des fichiers

Nginx choisi plutôt qu'Apache : plus léger au repos, ce qui laisse davantage de ressources à fail2ban sur un VPS d'entrée de gamme (4 Go de RAM), et possibilité de s'en servir plus tard comme reverse proxy.

root@vps:~# apt install nginx -y
root@vps:~# mkdir -p /var/www/portfolio
root@vps:~# chown -R www-data:www-data /var/www/portfolio

Depuis le poste local, envoi des fichiers en SCP sur le port SSH personnalisé :

user@poste:~$ scp -P 2222 -r ./SitePortfolio user@203.0.113.10:/var/www/portfolio/

7. Virtual host, nom de domaine, HTTPS

root@vps:~# nano /etc/nginx/sites-available/portfolio
root@vps:~# ln -s /etc/nginx/sites-available/portfolio /etc/nginx/sites-enabled/
root@vps:~# rm /etc/nginx/sites-enabled/default
root@vps:~# nginx -t && systemctl restart nginx

Nom de domaine acheté, enregistrement DNS de type A pointé vers l'IP du VPS, puis passage en HTTPS avec Let's Encrypt :

root@vps:~# apt install certbot python3-certbot-nginx -y
root@vps:~# certbot --nginx -d votre-domaine.fr -d www.votre-domaine.fr
#   renouvellement automatique du certificat géré par certbot (tâche planifiée)

8. Dernier blocage : permissions de fichiers

Le site restait inaccessible après le premier déploiement : problème de permissions sur le dossier. Correction en donnant l'accès en lecture à www-data (le compte sous lequel Nginx s'exécute), sans droit d'exécution.

root@vps:~# chown -R www-data:www-data /var/www/portfolio
root@vps:~# find /var/www/portfolio -type f -exec chmod 644 {} \;
root@vps:~# find /var/www/portfolio -type d -exec chmod 755 {} \;
Ce que je retiens : la plupart des blocages ne venaient pas d'une commande fausse mais d'un état non synchronisé entre deux systèmes (host key locale vs serveur réinstallé, fichier de conf dupliqué deux fois en SSH, permissions attendues par Nginx). Réflexe à garder : vérifier l'état réel avant de re-taper une commande qui "devrait marcher".
14

Déploiement via GitHub

Deuxième itération du déploiement : plutôt que d'envoyer les fichiers à la main en SCP à chaque changement, le VPS récupère désormais le site directement depuis un dépôt GitHub privé, via une clé de déploiement dédiée.

Comme pour la section précédente, certaines valeurs sont masquées (nom du dépôt, clé). Le compte rendu complet reste disponible sur demande via LinkedIn.

1. Dépôt privé et clé de déploiement

Création d'un repository privé sur GitHub, puis génération d'une clé SSH dédiée directement sur le VPS — distincte de la clé personnelle utilisée pour se connecter en SSH au serveur.

root@vps:~# ssh-keygen -t ed25519 -f ~/.ssh/github_deploy -C "vps-deploy"
#   clé dédiée : si elle fuite, elle ne donne accès qu'à CE dépôt, pas au compte GitHub entier

La clé publique est ensuite ajoutée côté GitHub, dans Dépôt → Settings → Deploy keys → Add deploy key (accès en lecture seule, suffisant pour un simple déploiement).

2. Configuration SSH côté VPS

Pour que Git utilise la bonne clé quand il contacte GitHub, on l'indique dans la config SSH du VPS :

root@vps:~# nano ~/.ssh/config

Host github.com
    HostName github.com
    User git
    IdentityFile ~/.ssh/github_deploy

3. Installation de Git et récupération du dépôt

root@vps:~# apt install git -y
root@vps:~# rm -rf /var/www/portfolio/*
#   suppression de la première version envoyée manuellement en SCP
root@vps:~# git clone git@github.com:mon-compte/mon-depot.git /var/www/portfolio

4. Incident : permissions et emplacement de la clé SSH

Le clone échouait initialement à cause des droits sur la clé privée — SSH refuse d'utiliser une clé trop permissive, par sécurité.

root@vps:~# chmod 600 ~/.ssh/github_deploy
#   la clé privée ne doit être lisible/modifiable que par son propriétaire

Une fois corrigé, restait un second problème : après le git clone, les fichiers appartenaient à root et non plus à www-data — Nginx ne pouvait donc plus les servir. Mêmes commandes de permissions que pour le premier déploiement :

root@vps:~# chown -R www-data:www-data /var/www/portfolio
root@vps:~# find /var/www/portfolio -type f -exec chmod 644 {} \;
root@vps:~# find /var/www/portfolio -type d -exec chmod 755 {} \;

5. Piège : un fichier sans extension .html

Le dépôt contenait un fichier nommé portfolio (sans extension), dont le contenu était pourtant bien du HTML valide. Nginx, configuré pour chercher index.html par défaut, ne le trouvait donc pas.

root@vps:~# mv /var/www/portfolio/portfolio /var/www/portfolio/index.html
#   renommage pour correspondre à ce que Nginx attend par défaut
root@vps:~# nginx -t && systemctl restart nginx

6. Mettre à jour le site au quotidien

Une fois ce déploiement en place, le workflow devient très simple : GitHub reste la source unique de vérité (édition directe des fichiers en ligne), le VPS ne fait que récupérer les changements.

# Sur GitHub : modification du fichier via l'éditeur en ligne
#   Dépôt → fichier concerné → icône crayon → modification → "Commit changes..."

# Sur le VPS : récupération de la nouvelle version
debian@vps:~$ cd /var/www/portfolio
debian@vps:~$ git pull

Le git pull se fait volontairement avec l'utilisateur debian (sans sudo), pas en root, car la clé de déploiement github_deploy et le fichier ~/.ssh/config ont été créés dans son dossier personnel — les exécuter en root échouerait faute de trouver la bonne clé.

# Si de nouveaux fichiers sont ajoutés (images, CSS séparé...), revérifier les permissions :
debian@vps:~$ sudo chmod -R 755 /var/www/portfolio
debian@vps:~$ sudo find /var/www/portfolio -type f -exec chmod 644 {} \;
Piège rencontré : un premier essai de mise à jour a échoué avec Permission denied (publickey) — la commande avait été lancée avec sudo, qui exécute Git en tant que root et ne trouve donc pas la clé de déploiement stockée dans le home de debian. Correction : ne jamais préfixer git pull par sudo une fois les permissions du dossier bien attribuées à debian (ou à un groupe partagé avec www-data).
Ce que je retiens : une clé de déploiement dédiée (plutôt que de réutiliser sa clé SSH personnelle) limite les dégâts en cas de fuite, et un git clone ne préserve jamais automatiquement les permissions attendues par le serveur web — le réflexe "chown/chmod après coup" doit devenir systématique.
15

Clé USB de secours — se connecter en SSH au VPS depuis n'importe quel poste

Objectif : ne jamais perdre l'accès au VPS en cas de panne, de perte ou de vol du poste principal. La clé SSH personnelle est dupliquée sur une clé USB dédiée, protégée par une passphrase, que je peux copier sur n'importe quel autre ordinateur pour retrouver l'accès au serveur.

Le port SSH et l'adresse de connexion utilisés ci-dessous sont volontairement masqués (remplacés par des valeurs génériques).

Principe : la clé reste utilisable ailleurs, tant qu'on connaît la passphrase

La clé privée SSH est un simple fichier, chiffré par une passphrase. La copier sur un autre poste ne retire ni ne modifie cette protection : sans la passphrase, le fichier est inutilisable même s'il est dérobé. C'est ce qui rend cette sauvegarde à la fois pratique et sûre.

# Sur un nouveau poste, copier la clé depuis la clé USB vers le profil utilisateur local
PS C:\Users\nouveau-poste> mkdir C:\Users\nouveau-poste\.ssh
PS C:\Users\nouveau-poste> copy D:\ssh-backup\id_ed25519 C:\Users\nouveau-poste\.ssh\
PS C:\Users\nouveau-poste> copy D:\ssh-backup\id_ed25519.pub C:\Users\nouveau-poste\.ssh\

Une fois copiée dans le profil Windows local, la clé bénéficie automatiquement de permissions NTFS correctes (le dossier C:\Users\... est déjà restreint à l'utilisateur du poste), puis peut être utilisée normalement :

PS C:\Users\nouveau-poste> ssh -i C:\Users\nouveau-poste\.ssh\id_ed25519 -p XXXX debian@votre-domaine.fr
#   la passphrase est ensuite demandée normalement pour déverrouiller la clé
À retenir : copier la clé directement depuis la clé USB (sans la recopier d'abord dans le profil local) fonctionne aussi sur Mac/Linux, mais pose problème sous Windows si la clé USB est formatée en FAT32/exFAT — voir l'incident ci-dessous.

Incident 1 — écriture refusée sur la clé USB

Une première tentative de création de dossier via PowerShell échouait avec Accès refusé, y compris sur une seconde clé USB testée en remplacement :

PS C:\Users\maxim> mkdir D:\ssh-backup
Accès refusé

Cause identifiée : une exécution précédente avait en réalité échoué silencieusement et créé le dossier sur C: au lieu de D:, d'où la confusion entre "le dossier existe déjà" (vu par PowerShell) et "la clé USB vide" (vu dans l'Explorateur). Contournement : copie effectuée directement à la souris depuis l'Explorateur de fichiers, qui n'était pas concerné par ce blocage.

Incident 2 — clé privée refusée par SSH (permissions trop ouvertes)

WARNING: UNPROTECTED PRIVATE KEY FILE!
Permissions for 'D:\ssh-backup\id_ed25519' are too open.

OpenSSH refuse par principe d'utiliser une clé privée si ses permissions ne garantissent pas un accès exclusif au propriétaire. Diagnostic :

PS C:\Users\maxim> fsutil fsinfo volumeinfo D:
#   confirme FAT32 → système de fichiers qui ne supporte pas les ACL Windows,
#   donc "icacls" ne peut matériellement pas restreindre les droits sur ce support

Solution : reformatage de la clé USB en NTFS (via l'Explorateur → clic droit sur le lecteur → Formater → système de fichiers NTFS), puis recopie des fichiers et application des permissions :

PS C:\Users\maxim> icacls D:\ssh-backup\id_ed25519 /inheritance:r
PS C:\Users\maxim> icacls D:\ssh-backup\id_ed25519 /grant:r "$env:USERNAME`:R"
#   restreint la clé au seul compte Windows courant, en lecture seule
Piège rencontré : la syntaxe %USERNAME% (valable en cmd.exe) ne fonctionne pas telle quelle en PowerShell et provoque une erreur de mappage de compte — il faut utiliser $env:USERNAME, avec un accent grave avant les deux-points (`:) pour éviter que PowerShell n'interprète mal la fin de la variable.

Cas particulier : la clé de déploiement GitHub (github_deploy)

Une clé distincte existe sur le VPS pour cloner le dépôt GitHub privé (github_deploy, voir section précédente). Sa sauvegarde sur clé USB n'a volontairement pas été mise en place :

# La clé github_deploy n'existe que sur le VPS et sert uniquement à CE VPS
# En cas de perte du serveur (crash, réinstallation), la régénérer est plus
# simple et plus sûr que de restaurer une ancienne sauvegarde :
#   1. ssh-keygen -t ed25519 -f ~/.ssh/github_deploy -C "vps-deploy"
#   2. ajouter la nouvelle clé publique dans GitHub → Deploy keys
#   3. git clone git@github.com:mon-compte/mon-depot.git

Contrairement à la clé SSH personnelle (qui donne accès au serveur lui-même et dont la perte serait bloquante), cette clé n'est qu'un moyen d'accès secondaire à un dépôt de code déjà sauvegardé sur GitHub — sa sauvegarde n'apporterait donc pas de réelle valeur, pour une complexité et un risque de fuite supplémentaires (cette clé n'a pas de passphrase, afin de permettre l'automatisation du git pull).

Validation finale

PS C:\Users\maxim> ssh -i D:\ssh-backup\id_ed25519 -p XXXX debian@votre-domaine.fr
#   plus d'avertissement de permissions, passphrase demandée normalement, connexion réussie
Ce que je retiens : une clé SSH protégée par passphrase reste sûre où qu'elle soit copiée — c'est la gestion des permissions du système d'exploitation qui varie selon le support (FAT32 vs NTFS, Windows vs Linux/Mac), pas la sécurité intrinsèque de la clé elle-même.
16

Mon CV

Vous pouvez consulter mon CV directement ci-dessous, ou l'ouvrir dans un nouvel onglet.

Si l'aperçu ne s'affiche pas correctement sur votre appareil (notamment sur mobile), vous pouvez ouvrir le CV dans un nouvel onglet.