Catégorie : Réseau

22 juillet 2026 /

Comment faire passer un seul site (ici x.com) par un VPN, et uniquement lui, pendant que tout le reste de la machine garde sa connexion directe ? C’est ce qu’on appelle un split-tunnel par domaine. Sous Linux, ça se résout élégamment avec un network namespace isolé, un proxy SOCKS et un fichier PAC dans le navigateur. Sous Android, la logique est différente (le VPN y est par application). Voici le guide complet, avec les pièges rencontrés en vrai et une section honnête sur les limites.

Pourquoi ce n’est pas trivial

On pourrait croire qu’il suffit d’activer le VPN et de « router x.com ». En pratique, plusieurs mauvaises pistes se referment vite :

  • VPN sur toute la machine + filtrage par IP : les grands sites sont derrière des CDN aux IP tournantes et partagées. On capterait trop (d’autres sites du même CDN) et pas assez (IP qui changent), et ça toucherait toutes les applications.
  • Network namespace ou VPN par application : ça fait passer toute une application par le VPN, pas un seul domaine.

La bonne approche combine deux idées : sélection par domaine côté navigateur (fichier PAC) et proxy dont la sortie est le VPN, le tout cloisonné pour éviter toute fuite si le tunnel tombe.

Le principe

┌─ Système principal (connexion directe) ───────────────────────┐
│  Firefox ──PAC── si domaine = x.com / twitter / t.co / twimg   │
│                    → proxy SOCKS,  sinon → DIRECT              │
│                         │                                      │
│                         ▼  10.200.200.2:1080 (veth)            │
│  ┌─ netns « vpnx » ─────────────────────────────────────────┐ │
│  │  proxy SOCKS5 ──► tun0 (OpenVPN) ──► x.com (via le VPN)   │ │
│  │  défaut = tun0 ; pas de route de secours = fail-closed    │ │
│  └──────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────┘

Tout le VPN vit dans un namespace réseau dédié. Le système principal reste en direct ; seul ce qu’on envoie explicitement dans ce namespace (le trafic x.com relayé par le proxy) emprunte le VPN. La résolution DNS se fait dans le namespace, donc à travers le tunnel : pas de fuite DNS. Et si le VPN tombe, il n’y a plus de route de secours : le site échoue au lieu de fuiter (kill-switch par construction).

Prérequis

  • Une distribution type Ubuntu / Debian.
  • Un VPN fournissant une configuration OpenVPN (dans l’exemple : CyberGhost, profil « US », avec ca.crt / client.crt / client.key et un couple identifiant/mot de passe OpenVPN).
  • Les paquets openvpn et microsocks : sudo apt install openvpn microsocks

Partie 1 — Sur le PC (Linux)

Le script

Un seul script gère tout : up (monte le tunnel isolé + proxy + génère le PAC), down (démonte proprement) et status (compare l’IP directe et l’IP via le VPN). Adaptez les chemins OVPN_* et OVPN_HOST à votre fournisseur.

#!/bin/bash
set -uo pipefail
IFS=$'\n\t'

# --- Paramètres (à adapter) ---
NETNS="vpnx"
VETH_HOST="veth-x-h"; VETH_NS="veth-x-n"
HOST_IP="10.200.200.1"; NS_IP="10.200.200.2"; SUBNET="10.200.200.0/24"
SOCKS_PORT="1080"; DNS_NS="1.1.1.1"
OVPN_DIR="$HOME/cyberghost/us"            # dossier contenant openvpn.ovpn + certs
OVPN_CONF="$OVPN_DIR/openvpn.ovpn"
OVPN_HOST="us.exemple-vpn.net"            # nom du serveur (résolu en IP)
AUTH_FILE="$HOME/.config/vpn-x/cg-auth"   # 2 lignes : identifiant / mot de passe
RUN_DIR="/run/vpn-x"; OVPN_PID="$RUN_DIR/openvpn.pid"; SOCKS_PID="$RUN_DIR/microsocks.pid"
PAC_FILE="$HOME/vpn-x-proxy.pac"          # PAC hors dossier caché (cf. piège Firefox Snap)

priv() { sudo "$@"; }

generer_pac() {
  cat > "$PAC_FILE" </dev/null

  # 3. Forward + NAT (porte uniquement le trafic VPN chiffré)
  priv sysctl -q -w net.ipv4.ip_forward=1
  priv iptables -t nat -A POSTROUTING -s "$SUBNET" -o "$wan_if" -j MASQUERADE
  priv iptables -A FORWARD -s "$SUBNET" -o "$wan_if" -j ACCEPT
  priv iptables -A FORWARD -d "$SUBNET" -i "$wan_if" -m state --state RELATED,ESTABLISHED -j ACCEPT

  # 4. OpenVPN dans le namespace (remote épinglé en IP => reconnexion sans DNS)
  priv ip netns exec "$NETNS" openvpn --cd "$OVPN_DIR" --config "$OVPN_CONF" \
      --remote "$server_ip" 443 --auth-user-pass "$AUTH_FILE" --auth-nocache \
      --writepid "$OVPN_PID" --log "$RUN_DIR/openvpn.log" --daemon "openvpn-vpnx"

  # 5. Attente du tunnel
  local i
  for i in $(seq 1 30); do
    priv ip netns exec "$NETNS" ip -4 addr show tun0 2>/dev/null | grep -q "inet " && break
    sleep 1
  done

  # 6. Kill-switch : route serveur épinglée + suppression de la route de secours
  priv ip netns exec "$NETNS" ip route replace "${server_ip}/32" via "$HOST_IP"
  priv ip netns exec "$NETNS" ip route del default via "$HOST_IP" 2>/dev/null || true

  # 7. Proxy SOCKS5 dans le namespace
  priv ip netns exec "$NETNS" microsocks -i "$NS_IP" -p "$SOCKS_PORT" >"$RUN_DIR/microsocks.log" 2>&1 &
  sleep 1; pgrep -x microsocks | head -1 > "$SOCKS_PID"

  generer_pac
  echo "Prêt. PAC : file://$PAC_FILE"
}

action_down() {
  [[ -f "$SOCKS_PID" ]] && { priv kill "$(cat "$SOCKS_PID")" 2>/dev/null || true; priv rm -f "$SOCKS_PID"; }
  [[ -f "$OVPN_PID" ]]  && { priv kill "$(cat "$OVPN_PID")"  2>/dev/null || true; priv rm -f "$OVPN_PID"; }
  local wan_if; wan_if=$(ip route show default | awk '/default/ {print $5; exit}')
  priv iptables -t nat -D POSTROUTING -s "$SUBNET" -o "$wan_if" -j MASQUERADE 2>/dev/null || true
  priv iptables -D FORWARD -s "$SUBNET" -o "$wan_if" -j ACCEPT 2>/dev/null || true
  priv iptables -D FORWARD -d "$SUBNET" -i "$wan_if" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || true
  priv ip netns del "$NETNS" 2>/dev/null || true
  priv ip link del "$VETH_HOST" 2>/dev/null || true
  priv rm -rf "/etc/netns/${NETNS}"
}

action_status() {
  echo -n "IP directe : "; curl -s --max-time 8 https://api.ipify.org; echo
  echo -n "IP via VPN : "; curl -s --max-time 12 --socks5-hostname "${NS_IP}:${SOCKS_PORT}" https://api.ipify.org; echo
}

case "${1:-}" in
  up) action_up ;; down) action_down ;; status) action_status ;;
  *) echo "Usage : $0 {up|down|status}" ;;
esac

Le fichier d’identifiants

Le script lit l’identifiant et le mot de passe OpenVPN depuis un fichier à deux lignes, en chmod 600 — jamais en dur dans le script :

mkdir -p ~/.config/vpn-x
printf '%s\n%s\n' "VOTRE_IDENTIFIANT_OPENVPN" "VOTRE_MOT_DE_PASSE" > ~/.config/vpn-x/cg-auth
chmod 600 ~/.config/vpn-x/cg-auth

Configurer Firefox (fichier PAC)

  1. Paramètres → Vie privée et sécurité → Sécurité logicielle et des connexions → Paramètres de Proxy → Configurer le proxy… (chemin des versions récentes de Firefox ; sur d’anciennes versions : Général → Paramètres réseau).
  2. Cocher « Adresse de configuration automatique du proxy (.pac) » et coller : file:///home/VOTRE_USER/vpn-x-proxy.pac
  3. Dans about:config, passer network.proxy.socks_remote_dns à true (résolution DNS côté proxy = anti-fuite).

⚠️ Piège Firefox Snap (Ubuntu récent). Le Firefox installé en Snap n’a pas accès aux dossiers cachés comme ~/.config/. Un PAC placé là est silencieusement ignoré, et le site part en direct sans erreur visible. Solution : mettre le PAC dans un chemin non caché du dossier personnel (ici ~/vpn-x-proxy.pac), accessible via l’interface Snap home.

Démarrage automatique au boot (systemd)

Pour ne jamais oublier de le lancer, un service systemd le monte après le réseau et le démonte à l’extinction. Il tourne sous votre compte (il élève via sudo) :

[Unit]
Description=Split-tunnel VPN pour x.com
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes
User=VOTRE_USER
Group=VOTRE_USER
Environment=HOME=/home/VOTRE_USER
ExecStart=/home/VOTRE_USER/scripts/vpn_x_split.sh up
ExecStop=/home/VOTRE_USER/scripts/vpn_x_split.sh down

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now vpn-x.service
systemctl status vpn-x

Le mode oneshot + RemainAfterExit=yes laisse openvpn et microsocks tourner dans le cgroup du service ; systemctl stop tue tout proprement.

Vérifier que ça marche

La commande status doit montrer deux IP différentes (directe vs VPN). Pour confirmer que le navigateur route bien le bon domaine, on observe les connexions dans le namespace pendant qu’on charge le site :

# IP de sortie comparée
./vpn_x_split.sh status

# Connexions actives dans le tunnel (charger x.com dans Firefox d'abord)
sudo ip netns exec vpnx ss -tn state established

# Preuve côté proxy
tail -f /run/vpn-x/microsocks.log   # doit lister x.com / video.twimg.com …

En parallèle, une page « quelle est mon IP » ouverte dans Firefox doit afficher votre IP normale (le reste passe en direct), tandis que le trafic du site ciblé sort par l’IP du VPN.

Partie 2 — Sur Android

Sur Android, impossible de filtrer par domaine : l’API VPN est par application et exclusive (un seul VPN actif à la fois). L’équivalent réaliste, c’est de router une seule application (l’app du service, ou un navigateur dédié) dans le VPN.

Attention : le split-tunneling intégré de nombreux clients VPN (dont CyberGhost) fonctionne uniquement en mode exclusion (choisir les apps qui contournent le VPN). Pour n’envoyer que l’app voulue, il faudrait exclure toutes les autres : impraticable.

La solution : l’application OpenVPN for Android (d’Arne Schwabe — pas « OpenVPN Connect »), qui propose une liste « Allowed Apps » en mode inclusif.

Préparer un profil .ovpn mono-fichier

OpenVPN for Android importe plus facilement un .ovpn avec les certificats intégrés en ligne. On le fabrique à partir des fichiers de la configuration manuelle :

SRC=~/cyberghost/us
OUT=~/vpn-us-android.ovpn
grep -vE '^\s*(ca|cert|key)\s' "$SRC/openvpn.ovpn" > "$OUT"
{ echo "";   cat "$SRC/ca.crt";     echo ""
  echo ""; cat "$SRC/client.crt"; echo ""
  echo "";  cat "$SRC/client.key"; echo ""; } >> "$OUT"
chmod 600 "$OUT"

On n’intègre pas le mot de passe : l’app le demandera et le mémorisera. Transférez le fichier sur le téléphone par un canal privé (câble USB, partage réseau local…) — il contient votre clé privée.

Sur le téléphone

  1. Installer OpenVPN for Android (Play Store / F-Droid).
  2. Importer le .ovpn.
  3. Saisir identifiant / mot de passe OpenVPN (cocher « enregistrer »).
  4. Éditer le profil → onglet « Allowed Apps »désactiver « VPN utilisé pour toutes les applications sauf celles sélectionnées » pour basculer en mode inclusif (« VPN utilisé uniquement pour les applications sélectionnées ») → cocher uniquement l’app voulue.
  5. Se connecter en tapant sur le nom du profil.

Toujours actif : Android → Paramètres → VPN → ⚙️ → « VPN permanent ». Laissez « Bloquer les connexions sans VPN » désactivé : en mode inclusif, seule l’app ciblée passe par le tunnel, donc le lockdown couperait Internet à toutes les autres. Pensez aussi à passer l’app en batterie « sans restriction » pour que le système ne la tue pas.

Les pièges rencontrés (en vrai)

  • AUTH_FAILED avec CyberGhost : les profils .ovpn manuels utilisent des identifiants OpenVPN dédiés, différents du token du client CLI. Récupérez les bons depuis vos connexions VPN existantes (par ex. nmcli -s -g vpn.secrets connection show NOM pour le mot de passe, nmcli -g vpn.data … pour l’identifiant).
  • Firefox Snap + PAC ignoré : dossier caché inaccessible → PAC hors ~/.config (voir plus haut).
  • Redirection de log en échec : sous sudo, la redirection > fichier s’exécute côté utilisateur ; si le dossier appartient à root, elle échoue. On rend le dossier de run inscriptible (chown) avant de lancer les démons.
  • PID du proxy : sous sudo ip netns exec …, capturez le vrai PID avec pgrep -x microsocks plutôt que $! (qui pointe le wrapper).

Limites — à lire avant de se croire invisible

Ce montage fait une chose précise : faire sortir un site par une IP différente. Ce n’est pas de l’anonymat, et il faut être honnête sur ce qu’un VPN ne fait pas :

  • Si vous êtes connecté à un compte, l’IP ne change rien à votre identification par ce compte.
  • Le fingerprinting du navigateur/appareil vous identifie indépendamment de l’IP.
  • Le fournisseur VPN voit votre trafic sortant : vous déplacez la confiance, vous ne la supprimez pas (les promesses « no-log » sont des affirmations, pas des garanties vérifiables).
  • Un VPN ne change ni votre juridiction, ni l’historique déjà enregistré côté service avant sa mise en place.
  • Sur Android, c’est par application : si vous ouvrez le même service dans une autre app hors tunnel, l’IP réelle fuit.

Autrement dit : excellent pour la confidentialité au quotidien et le choix de l’IP de sortie ; insuffisant, à lui seul, pour un vrai objectif d’anonymat (qui relève d’outils et d’une discipline d’usage bien plus larges : Tor, cloisonnement strict des identités, etc.).

Conclusion

Avec un network namespace, un proxy SOCKS et un PAC, on obtient sur Linux un split-tunnel par domaine propre, avec kill-switch et démarrage automatique. Sur Android, on retombe sur du par application via OpenVPN for Android en mode inclusif. Deux approches différentes pour un même besoin — et surtout, une bonne compréhension de ce que ça protège… et de ce que ça ne protège pas.

19 juillet 2026 /

Dans l’article précédent, nous avons monté une pile Docker gluetun + Transmission sur un NAS Synology : les torrents passent par le VPN, et l’interface web répond en HTTP sur le port 9091. Ça fonctionne, mais en HTTP clair — acceptable sur le réseau local, rédhibitoire dès qu’on veut y accéder depuis l’extérieur.

Ce second article ajoute la couche qui manque : un accès HTTPS avec un vrai certificat Let’s Encrypt, valable aussi bien en local que depuis Internet, du type https://nas.example.com:9092. Le tout sans jamais exposer le port 80 du NAS, grâce à la validation DNS.

Vue d’ensemble

Quatre briques :

  1. Un nom DNS qui pointe vers votre IP publique (ici nas.example.com, zone hébergée chez OVH) ;
  2. Un certificat Let’s Encrypt obtenu par acme.sh en validation DNS-01 via l’API OVH, et importé automatiquement dans DSM ;
  3. Le reverse proxy intégré à DSM (nginx), qui termine le HTTPS sur le port 9092 et retransmet en clair vers Transmission sur localhost:9091 ;
  4. Une redirection de port sur la box pour l’accès externe.
Internet ──► box (9092) ──► NAS:9092 ── nginx DSM (HTTPS, certificat) ──► localhost:9091 ──► Transmission

Transmission lui-même ne sait rien de tout ça : il continue d’écouter en HTTP sur 9091. C’est le reverse proxy qui porte le chiffrement — et le même mécanisme resservira pour n’importe quel autre service web du NAS.

Pourquoi la validation DNS-01 plutôt que HTTP-01 ?

Pour délivrer un certificat, Let’s Encrypt doit vérifier que vous contrôlez le nom demandé. Deux méthodes :

  • HTTP-01 : Let’s Encrypt vient chercher un fichier sur http://nas.example.com/.well-known/... → il faut exposer le port 80 du NAS sur Internet. Souvent impossible (le port 80 de la box est déjà pris ou translaté vers autre chose) et jamais souhaitable ;
  • DNS-01 : acme.sh dépose un enregistrement TXT temporaire dans la zone DNS via l’API du registrar, Let’s Encrypt le lit, le certificat est délivré. Aucun port à ouvrir, et ça permet même les certificats wildcard.

DNS-01 est donc la bonne méthode dès que votre registrar a une API — c’est le cas d’OVH, et acme.sh sait parler à des dizaines d’autres (Cloudflare, Gandi, etc.).

1. Le nom DNS

Dans la zone OVH, créez un enregistrement A pointant vers votre IP publique :

nas.example.com.    A    203.0.113.10

Si votre IP publique est dynamique, utilisez le DynHost d’OVH (ou le client DDNS de DSM) pour maintenir l’enregistrement à jour. Vérifiez avant de continuer :

nslookup nas.example.com    # doit renvoyer votre IP publique

2. Installer acme.sh sur le NAS

acme.sh est un client ACME en pur shell, sans dépendance — parfait pour un Synology. Un point important : installez-le sur un volume de données (/volume1), pas dans /root. Le home de root vit sur la partition système, reconstruite lors des grosses mises à jour DSM ; un volume de données, lui, survit à tout.

cd /volume1/docker        # ou tout autre dossier de /volume1
wget -O acme.sh.tar.gz https://github.com/acmesh-official/acme.sh/archive/master.tar.gz
tar xzf acme.sh.tar.gz && cd acme.sh-master
./acme.sh --install --home /volume1/docker/acme.sh --nocron

Le --nocron est volontaire : le cron d’acme.sh a besoin d’un environnement particulier sur Synology, on le créera à la main à l’étape 5.

⚠️ Piège Synology : /tmp est monté noexec. Si un script refuse de s’exécuter depuis /tmp avec « Permission denied », lancez-le via sh script.sh ou depuis /volume1.

3. Le jeton API OVH

Rendez-vous sur https://eu.api.ovh.com/createToken/ et créez un jeton avec uniquement les droits nécessaires à la gestion de zone :

GET     /domain/zone/*
PUT     /domain/zone/*
POST    /domain/zone/*
DELETE  /domain/zone/*

Principe du moindre privilège : ce jeton ne peut toucher qu’aux zones DNS, pas au reste de votre compte OVH. Vous obtenez trois valeurs (application key, application secret, consumer key) à exporter avant la première émission :

export OVH_END_POINT=ovh-eu
export OVH_AK=votre_application_key
export OVH_AS=votre_application_secret
export OVH_CK=votre_consumer_key

acme.sh les enregistre dans sa configuration (account.conf) au premier usage — les exports ne servent qu’une fois. Ne mettez jamais ces valeurs dans un script versionné.

4. Émettre le certificat et l’importer dans DSM

Émission (validation DNS-01 chez OVH, CA Let’s Encrypt) :

/volume1/docker/acme.sh/acme.sh --home /volume1/docker/acme.sh \
  --issue --dns dns_ovh \
  -d nas.example.com \
  --server letsencrypt

Deux à trois minutes plus tard (le temps de la propagation du TXT), le certificat est dans /volume1/docker/acme.sh/nas.example.com_ecc/. Reste à l’importer dans DSM — et c’est là qu’acme.sh brille : son hook de déploiement synology_dsm utilise l’API locale de DSM pour installer le certificat proprement, comme si vous l’aviez fait dans l’interface :

export SYNO_USE_TEMP_ADMIN=1
export SYNO_CREATE=1
export PATH=/usr/syno/sbin:/usr/syno/bin:$PATH
/volume1/docker/acme.sh/acme.sh --home /volume1/docker/acme.sh \
  --deploy -d nas.example.com \
  --deploy-hook synology_dsm

Trois subtilités qui font tout marcher :

  • SYNO_USE_TEMP_ADMIN=1 : le hook crée un compte administrateur temporaire le temps du déploiement, puis le supprime. Pas besoin de stocker votre mot de passe DSM dans un fichier ;
  • SYNO_CREATE=1 : indispensable au premier déploiement. Par défaut, le hook cherche un certificat existant à remplacer dans DSM ; s’il n’y en a pas encore, il échoue avec Unable to find certificate: and $SYNO_CREATE is not set. Cette variable l’autorise à créer l’entrée ;
  • le PATH doit inclure /usr/syno/sbin et /usr/syno/bin : le hook s’appuie sur les commandes Synology (synouser…) qui n’y sont pas par défaut sous sudo. Sans ça, échec silencieux.

Le certificat apparaît alors dans DSM → Panneau de configuration → Sécurité → Certificat.

5. Le renouvellement automatique

Un certificat Let’s Encrypt dure 90 jours ; acme.sh renouvelle à 60 jours, à condition d’être lancé régulièrement. Ajoutez cette ligne dans /etc/crontab du NAS (en root) — avec l’environnement complet, cron étant encore plus pauvre en PATH qu’un shell sudo :

0  3  *  *  *  root  PATH=/usr/syno/sbin:/usr/syno/bin:/usr/bin:/bin HOME=/volume1/docker/acme.sh SYNO_USE_TEMP_ADMIN=1 /volume1/docker/acme.sh/acme.sh --home /volume1/docker/acme.sh --cron

Tous les jours à 3 h, acme.sh vérifie l’échéance ; le jour venu, il renouvelle en DNS-01 et redéploie dans DSM via le hook, sans aucune intervention. Testez le circuit complet une fois :

/volume1/docker/acme.sh/acme.sh --home /volume1/docker/acme.sh --cron --force

6. Le reverse proxy DSM

DSM embarque nginx et une interface pour le piloter : Panneau de configuration → Portail de connexion → Avancé → Proxy inversé. Créez une règle :

Source (ce que voit le client) Destination (le service réel)
Protocole HTTPS HTTP
Nom d’hôte nas.example.com localhost
Port 9092 9091

À la création de la règle, DSM lui associe automatiquement le certificat par défaut du NAS (souvent l’auto-signé Synology) — d’où le cadenas barré si vous testez tout de suite. Associez donc le bon certificat à la règle : Sécurité → Certificat → Paramètres, et en face du service nas.example.com:9092, sélectionnez le certificat fraîchement importé.

⚠️ Résistez au bouton « Définir comme certificat par défaut » : il réassigne d’un coup tous les services de DSM au même certificat, y compris ceux qui ne doivent pas changer (QuickConnect utilise le sien, par exemple), et peut laisser nginx dans un état incohérent — vécu : connexions Secure SignIn mobiles cassées jusqu’à re-toucher une liaison de certificat pour forcer DSM à régénérer sa configuration. Assignez service par service, c’est deux clics de plus et zéro mauvaise surprise.

7. Le cas particulier d’un service derrière gluetun

Si le service cible est dans un conteneur attaché à gluetun (notre Transmission, donc), le reverse proxy renverra une erreur 502 avec la configuration gluetun par défaut : son pare-feu bloque les flux venant de l’hôte en dehors du tunnel. La solution est dans le compose du premier article :

- FIREWALL_OUTBOUND_SUBNETS=192.168.1.0/24,172.18.0.0/16

Cette ligne autorise gluetun à répondre au réseau local (et au réseau Docker du projet) hors tunnel — uniquement ces destinations-là, le reste du trafic continue de passer par le VPN.

8. L’accès externe

Dernière brique : sur la box/routeur, redirigez le port 9092 (TCP) vers l’IP du NAS, port 9092. Et c’est tout — surtout, ne redirigez pas le 9091 : le seul chemin depuis Internet doit être celui qui passe par le HTTPS du reverse proxy.

Résultat : https://nas.example.com:9092 fonctionne du canapé comme de l’autre bout du monde, avec un cadenas valide, l’authentification de Transmission par-dessus, et un renouvellement de certificat entièrement automatique.

Récapitulatif des pièges

Piège Symptôme Solution
HTTP-01 derrière une box Validation impossible, port 80 pris/translaté Validation DNS-01 via l’API du registrar
acme.sh dans /root Installation perdue après grosse mise à jour DSM Installer sur /volume1
/tmp monté noexec « Permission denied » sur un script pourtant exécutable Lancer via sh script.sh ou depuis /volume1
PATH sans /usr/syno/sbin Le hook synology_dsm échoue (sudo et cron) Préfixer PATH dans la commande et le crontab
Mot de passe DSM dans la conf Secret stocké en clair SYNO_USE_TEMP_ADMIN=1 (admin temporaire jetable)
Premier déploiement du certificat Unable to find certificate SYNO_CREATE=1 (le hook ne crée pas d’entrée par défaut)
« Définir comme certificat par défaut » Tous les services réassignés, état nginx incohérent Assigner le certificat service par service
Service derrière gluetun Reverse proxy en erreur 502 FIREWALL_OUTBOUND_SUBNETS dans le compose
Port 9091 redirigé sur la box Interface accessible en HTTP clair depuis Internet N’exposer que le 9092 (HTTPS)

Conclusion

Avec le premier article, les torrents passaient par le VPN. Avec celui-ci, l’interface est servie en HTTPS avec un certificat valide, renouvelé tout seul, accessible de l’extérieur sans exposer un seul octet en clair. Le schéma reverse proxy + certificat est réutilisable à l’identique pour tous les services web du NAS : une règle de proxy inversé et une liaison de certificat par service, et c’est tout le NAS qui parle proprement HTTPS.

19 juillet 2026 /

Vous avez un NAS Synology, un client BitTorrent (Transmission) et un abonnement VPN. Vous voulez que vos torrents passent par le VPN, et uniquement eux. Le reste du NAS — DSM, DDNS, QuickConnect, Secure SignIn, vos autres services — doit continuer à sortir avec votre IP normale.

Ce tutoriel explique comment y parvenir avec trois conteneurs Docker : gluetun (le tunnel VPN + pare-feu), Transmission (le client torrent) et autoheal (la rustine qui rend l’ensemble fiable dans la durée).

Pourquoi pas le VPN intégré de DSM ?

DSM propose nativement un client VPN (Panneau de configuration → Réseau → Interface réseau). Le problème : une fois connecté, ce VPN devient la route par défaut de tout le NAS. Conséquences vécues :

  • le DDNS enregistre l’adresse IP du serveur VPN au lieu de votre IP publique → vos accès externes pointent dans le vide ;
  • Secure SignIn et QuickConnect deviennent erratiques ;
  • tous les services du NAS sortent par le VPN, même ceux qui n’en ont aucun besoin.

Le routage sélectif (« policy routing ») est possible en bidouillant les tables de routage à la main, mais c’est fragile et écrasé aux mises à jour de DSM. La bonne solution, c’est d’isoler le VPN dans un conteneur.

Le principe

┌─────────────────────────── NAS Synology ───────────────────────────┐
│                                                                    │
│  DSM, DDNS, autres services ──────────────► Internet (IP normale)  │
│                                                                    │
│  ┌────────── réseau du conteneur gluetun ──────────┐               │
│  │                                                 │               │
│  │  transmission ──► gluetun ──► tunnel VPN ───────┼─► Internet    │
│  │                   (kill switch)                 │   (IP du VPN) │
│  └─────────────────────────────────────────────────┘               │
└────────────────────────────────────────────────────────────────────┘
  • gluetun est le seul conteneur à monter le tunnel VPN. Il intègre un pare-feu strict : si le tunnel tombe, rien ne sort. C’est un kill switch natif, aucune fuite d’IP possible.
  • Transmission n’a pas de réseau à lui : grâce à network_mode: "service:gluetun", il partage la pile réseau de gluetun. Tout son trafic passe donc obligatoirement par le tunnel, sans rien configurer côté Transmission.
  • autoheal surveille la santé de Transmission et le redémarre automatiquement quand il perd le réseau (on verra plus bas pourquoi ça arrive forcément un jour).

Prérequis

  • Un Synology avec Container Manager installé (Centre de paquets) ;
  • Un abonnement VPN fournissant une configuration OpenVPN (fichier .ovpn + identifiants). Ici, l’exemple utilise un fournisseur en mode « custom », mais gluetun connaît nativement des dizaines de fournisseurs (NordVPN, ProtonVPN, Mullvad, Surfshark…) — dans ce cas, la configuration est encore plus simple ;
  • Un accès SSH au NAS (ou l’éditeur de fichiers de File Station).

1. L’arborescence

Créez un dossier de projet dans le partage docker :

/volume1/docker/transmission-vpn/
├── docker-compose.yml
├── gluetun/
│   ├── custom.ovpn          # config OpenVPN du fournisseur
│   ├── openvpn_user         # identifiant VPN (une ligne, rien d'autre)
│   └── openvpn_password     # mot de passe VPN (une ligne, rien d'autre)
└── config/                  # config de Transmission (à créer vide)
mkdir -p /volume1/docker/transmission-vpn/{gluetun,config}

⚠️ Créez bien le dossier config/ avant le premier lancement : contrairement au Docker standard qui crée silencieusement les dossiers manquants d’un bind mount, celui de Synology refuse de démarrer le conteneur avec l’erreur Bind mount failed: '…/config' does not exist.

Les identifiants sont dans des fichiers séparés plutôt qu’en variables d’environnement dans le compose. Deux raisons : on peut versionner ou partager le docker-compose.yml sans exposer de secret, et on évite les mauvaises surprises de l’interpolation docker-compose (un $ ou un caractère spécial dans un mot de passe passé en variable d’environnement peut être interprété silencieusement).

chmod 600 gluetun/openvpn_user gluetun/openvpn_password

2. Préparer le fichier .ovpn (fournisseur « custom »)

Deux pièges qui font perdre des heures :

Piège n° 1 — utilisez une IP, pas un nom de domaine. Dans le fichier .ovpn, la ligne remote contient généralement un nom d’hôte :

remote monfournisseur-vpn.example.net 443

Au démarrage, gluetun verrouille son pare-feu avant d’établir le tunnel — et à ce stade, la résolution DNS peut échouer ou être bloquée. Remplacez le nom par son adresse IP :

nslookup monfournisseur-vpn.example.net
# puis dans custom.ovpn :
remote 203.0.113.37 443

Si un jour le conteneur devient unhealthy sans raison apparente, re-résolvez le nom : le fournisseur a probablement changé l’IP du serveur.

Piège n° 2 — supprimez la ligne auth-user-pass du .ovpn. Gluetun injecte lui-même cette directive avec le chemin de ses fichiers d’identifiants. Si votre .ovpn contient déjà un auth-user-pass nu (sans chemin), OpenVPN tente de demander les identifiants de façon interactive dans un conteneur sans terminal → échec immédiat.

3. Le docker-compose.yml

Voici le fichier complet, commenté ensuite bloc par bloc :

services:
  gluetun:
    image: qmcgaw/gluetun
    container_name: gluetun
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun:/dev/net/tun
    environment:
      - VPN_SERVICE_PROVIDER=custom
      - VPN_TYPE=openvpn
      - OPENVPN_CUSTOM_CONFIG=/gluetun/custom.ovpn
      - OPENVPN_USER_SECRETFILE=/gluetun/openvpn_user
      - OPENVPN_PASSWORD_SECRETFILE=/gluetun/openvpn_password
      - FIREWALL_INPUT_PORTS=9091
      - FIREWALL_OUTBOUND_SUBNETS=192.168.1.0/24,172.18.0.0/16
      - TZ=Europe/Paris
    volumes:
      - ./gluetun:/gluetun
    ports:
      - "9091:9091"
    restart: unless-stopped

  transmission:
    image: lscr.io/linuxserver/transmission:latest
    container_name: transmission
    network_mode: "service:gluetun"
    depends_on:
      - gluetun
    environment:
      - PUID=1026            # UID : le propriétaire des fichiers téléchargés (voir ci-dessous)
      - PGID=100             # GID : le groupe de ces fichiers
      - USER=admin           # identifiant de connexion à l'interface web (port 9091)
      - PASS=ChangezMoi      # mot de passe de l'interface web — se change ICI, pas dans settings.json
      - TZ=Europe/Paris
    volumes:
      - ./config:/config
      - /volume1/downloads:/volume1/downloads
    labels:
      - autoheal=true
    healthcheck:
      test: ["CMD-SHELL", "curl -sf -m 8 -o /dev/null https://api.ipify.org || exit 1"]
      interval: 30s
      timeout: 12s
      retries: 2
      start_period: 60s
    restart: unless-stopped

  autoheal:
    image: willfarrell/autoheal:latest
    container_name: autoheal
    environment:
      - AUTOHEAL_CONTAINER_LABEL=autoheal
      - AUTOHEAL_INTERVAL=15
      - AUTOHEAL_START_PERIOD=45
      - TZ=Europe/Paris
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    restart: unless-stopped

Bloc gluetun : le tunnel et le pare-feu

  • cap_add: NET_ADMIN et /dev/net/tun : indispensables pour créer une interface VPN dans un conteneur.
  • FIREWALL_INPUT_PORTS=9091 : le pare-feu de gluetun bloque tout par défaut, y compris en entrée. Cette ligne autorise l’accès à l’interface web de Transmission (port 9091). C’est sur gluetun que le port est publié (ports:), pas sur Transmission — logique, puisque Transmission vit dans le réseau de gluetun.
  • FIREWALL_OUTBOUND_SUBNETS=192.168.1.0/24,172.18.0.0/16 : autorise le trafic vers le réseau local en dehors du tunnel. Sans cette ligne, le NAS lui-même ne peut pas joindre le port publié — symptôme typique : un reverse proxy DSM devant Transmission qui renvoie une erreur 502. Adaptez 192.168.1.0/24 à votre réseau local (le 172.18.0.0/16 correspond au réseau Docker par défaut du projet).

Bloc transmission : tout passe par le tunnel

  • network_mode: "service:gluetun" : la ligne magique de tout le montage. Transmission n’a aucune interface réseau propre, il utilise celles de gluetun. Impossible de contourner le VPN, même par erreur de configuration.
  • PUID / PGID : un conteneur n’a pas ses propres utilisateurs — le processus Transmission écrit sur le disque du NAS avec un numéro d’utilisateur (UID) et de groupe (GID). Ces deux variables déterminent donc à qui appartiendront les fichiers téléchargés dans /volume1/downloads — et donc qui pourra les lire, les déplacer ou les supprimer ensuite (via File Station, SMB, etc.). Pour récupérer les vôtres, en SSH sur le NAS :
    $ id votreuser
    uid=1026(votreuser) gid=100(users) groups=100(users),101(administrators)

    Reportez le uid dans PUID et le gid dans PGID. Sur un Synology, le premier utilisateur créé porte généralement l’UID 1026 et appartient au groupe users (GID 100) — d’où les valeurs de l’exemple. Si vous migrez depuis une installation Transmission existante, utilisez plutôt l’UID/GID de l’ancien utilisateur (visible avec ls -lan /volume1/downloads) pour reprendre vos fichiers sans toucher aux permissions.

  • USER / PASS : les identifiants de connexion à l’interface web de Transmission. Attention, piège de l’image linuxserver — au démarrage, elle réécrit la configuration RPC de Transmission. Si ces deux variables sont absentes, elle désactive purement et simplement l’authentification de l’interface web, même si vous l’aviez activée dans settings.json. C’est donc ici, et uniquement ici, qu’on définit ou change ces identifiants.
  • Le healthcheck mérite une explication à part (section suivante).

Bloc autoheal : la fiabilité dans la durée

Le talon d’Achille de network_mode: "service:gluetun" : quand gluetun redémarre (mise à jour d’image, incident, reboot partiel), son espace réseau est détruit puis recréé. Transmission, lui, reste attaché à l’ancien espace réseau, désormais orphelin : le processus tourne, le conteneur est « Up », mais il n’a plus aucune connectivité. Docker ne le redémarre pas tout seul, puisque de son point de vue tout va bien.

La solution tient en deux morceaux :

  1. Un healthcheck qui teste la connectivité externe : curl https://api.ipify.org. Piège subtil : un healthcheck sur http://localhost:9091 ne détecte pas le problème, car le loopback continue de fonctionner dans un espace réseau orphelin. Il faut tester une sortie réelle vers Internet.
  2. autoheal, un micro-conteneur qui surveille le socket Docker et redémarre automatiquement tout conteneur portant le label autoheal=true dès qu’il passe unhealthy.

Résultat : après un redémarrage de gluetun, Transmission retrouve son réseau tout seul en ~90 secondes, sans intervention.

4. Lancement et vérification

cd /volume1/docker/transmission-vpn
docker compose up -d

(Sur Synology, le binaire complet est /var/packages/ContainerManager/target/usr/bin/docker si docker n’est pas dans votre PATH. Vous pouvez aussi importer le projet dans l’interface de Container Manager.)

Note sur les images : l’onglet « Registre » de Container Manager ne cherche que sur Docker Hub. Vous y trouverez qmcgaw/gluetun et willfarrell/autoheal, mais pas lscr.io/linuxserver/transmission, hébergée sur le registre de linuxserver (lscr.io) : inutile de la chercher dans l’interface, elle n’y apparaîtra jamais. Ce n’est pas un problème — docker compose up -d télécharge chaque image directement depuis le bon registre, celui indiqué dans son nom complet. C’est une des raisons de préférer la ligne de commande à l’interface pour ce projet.

La vérification qui compte — comparer l’IP de sortie du conteneur et celle du NAS :

# IP vue par Transmission (doit être celle du serveur VPN)
docker exec transmission curl -s https://api.ipify.org

# IP vue par le NAS (doit être votre IP publique normale)
curl -s https://api.ipify.org

Si les deux adresses diffèrent, mission accomplie. Testez aussi le kill switch :

docker stop gluetun
docker exec transmission curl -s -m 5 https://api.ipify.org   # doit échouer
docker start gluetun
# ~90 s plus tard, autoheal a redémarré transmission, tout refonctionne

L’interface web est accessible sur http://ip-du-nas:9091 avec les identifiants USER/PASS du compose.

Si l’interface ne répond pas du tout (connexion refusée, alors que le conteneur est healthy) : regardez config/settings.json. Sur une installation neuve, l’image linuxserver génère "rpc-bind-address": "[::]" — une adresse d’écoute IPv6. Or gluetun désactive l’IPv6 dans son espace réseau quand le tunnel n’en fournit pas : le bind échoue en silence et rien n’écoute sur 9091 (vérifiable avec docker exec transmission netstat -tln : le port pair 51413 est là, pas le 9091). Le correctif :

docker stop transmission
sed -i 's/"rpc-bind-address": "\[::\]"/"rpc-bind-address": "0.0.0.0"/' config/settings.json
docker start transmission

Ce réglage n’est pas réécrit par l’image au démarrage (contrairement à USER/PASS), la correction est donc définitive.

5. Mises à jour

Les tags :latest ne se mettent pas à jour tout seuls. Un petit script suffit :

#!/bin/bash
# Met à jour les images de la pile transmission-vpn
set -uo pipefail
cd /volume1/docker/transmission-vpn || exit 1
DOCKER=/var/packages/ContainerManager/target/usr/bin/docker
$DOCKER compose pull
$DOCKER compose up -d
$DOCKER image prune -f
$DOCKER compose ps

La configuration (docker-compose.yml, gluetun/, config/) n’est jamais touchée par une mise à jour d’images. Et si la mise à jour redémarre gluetun — c’est le cas — autoheal se charge de remettre Transmission sur pied.

Récapitulatif des pièges

Piège Symptôme Solution
VPN système DSM DDNS/QuickConnect/Secure SignIn cassés VPN conteneurisé (ce tutoriel)
remote avec un nom d’hôte gluetun ne démarre pas ou devient unhealthy Mettre l’IP en dur dans le .ovpn
auth-user-pass dans le .ovpn OpenVPN demande les identifiants et plante Supprimer la ligne, gluetun gère
Restart de gluetun Transmission « Up » mais sans réseau healthcheck externe + autoheal
Healthcheck sur localhost:9091 Passe au vert même sans réseau Tester une URL externe (api.ipify.org)
USER/PASS absents du compose Interface web sans authentification Toujours les définir dans le compose
Reverse proxy → 502 Le NAS ne joint pas le port publié FIREWALL_OUTBOUND_SUBNETS
Secrets en variables d’environnement Interpolation $ imprévisible OPENVPN_*_SECRETFILE
Dossier config/ absent Bind mount failed au premier lancement mkdir config avant le up -d (Docker Synology ne le crée pas)
rpc-bind-address "[::]" (install neuve) Conteneur healthy mais interface web injoignable Mettre 0.0.0.0 dans settings.json
Mêmes identifiants VPN sur deux machines Déconnexions en boucle (ping-restart) toutes les ~2 min Un jeu d’identifiants (ou au moins un serveur) par machine

Conclusion

Trois conteneurs, un seul fichier compose, et un cloisonnement propre : les torrents passent par le VPN avec un kill switch garanti, le NAS garde son IP publique pour tout le reste, et l’ensemble survit aux redémarrages et aux mises à jour sans intervention. Exactement ce que le client VPN intégré de DSM ne sait pas faire.

21 juin 2019 /

DRBD fait référence aux périphériques en mode bloc conçus pour former des clusters haute disponibilité (HA). Pour ce faire, un périphérique de bloc entier est mis en miroir via le réseau. DRBD peut être compris comme un raid-1 réseau.

Le problème est le suivant, DRBD ne se connecte pas à tous ses volumes réseaux suite à une panne d’un des contrôleurs de la baie de disques.

Après la remise en état du contrôleur, je me retrouve avec l’erreur suivante:

Supervision de l’etat des volumes DRBD depuis la baie n° 1

drbd-overview

0:datastores/0 Connected Primary/Secondary UpToDate/UpToDate
1:datastores/1 Connected Primary/Secondary UpToDate/UpToDate
2:images-stores02/0 StandAlone Primary/Unknown UpToDate/DUnknown 
3:images-stores03/0 Connected Primary/Secondary UpToDate/UpToDate
4:images-storesHS/0 StandAlone Primary/Unknown UpToDate/DUnknown

Supervision de l’etat des volumes DRBD depuis la baie n° 2

drbd-overview

0:datastores/0 Connected Secondary/Primary UpToDate/UpToDate
1:datastores/1 Connected Secondary/Primary UpToDate/UpToDate
2:images-stores02/0 WFConnection Primary/Unknown UpToDate/DUnknown
3:images-stores03/0 Connected Secondary/Primary UpToDate/UpToDate
4:images-storesHS/0 WFConnection Primary/Unknown UpToDate/DUnknown

Pour réparer cela:

Depuis la baie n° 2

drbdadm secondary images-stores02/0
drbdadm disconnect images-stores02/0
drbdadm connect --discard-my-data images-stores02/0

drbdadm secondary images-storesHS/0
drbdadm disconnect images-storesHS/0
drbdadm connect --discard-my-data images-storesHS/2F0

Depuis la baie n°1

drbdadm primairy mages-storesHS/0
drbdadm disconnect images-storesHS/0
drbdadm connect images-storesHS/0

drbdadm primairy images-stores02/0
drbdadm disconnect images-stores02/0
drbdadm connect images-stores02/0

On verifie que syncronisation s’est bien lancée:

cat /proc/drbd

version: 8.4.3 (api:1/proto:86-101)
srcversion: 1A9F77B1CA5FF92235C2213
0: cs:Connected ro:Secondary/Primary ds:UpToDate/UpToDate C r—–
ns:0 nr:0 dw:0 dr:0 al:0 bm:0 lo:0 pe:0 ua:0 ap:0 ep:1 wo:f oos:0
1: cs:Connected ro:Secondary/Primary ds:UpToDate/UpToDate C r—–
ns:0 nr:59608 dw:232804 dr:0 al:0 bm:23 lo:0 pe:0 ua:0 ap:0 ep:1 wo:f oos:0
2: cs:SyncTarget ro:Secondary/Primary ds:Inconsistent/UpToDate C r—–
ns:0 nr:888388 dw:888376 dr:972 al:0 bm:477 lo:3 pe:0 ua:3 ap:0 ep:1 wo:f oos:1404644
[===>…………….] sync’ed: 22.3% (1404644/1800120)K
finish: 0:13:30 speed: 1,724 (1,716) want: 102,400 K/sec
3: cs:Connected ro:Secondary/Primary ds:UpToDate/UpToDate C r—–
ns:0 nr:436052 dw:3326216 dr:0 al:0 bm:196 lo:1 pe:0 ua:0 ap:0 ep:1 wo:f oos:0
4: cs:SyncTarget ro:Secondary/Primary ds:Inconsistent/UpToDate C r—–
ns:0 nr:8702052 dw:8700428 dr:992 al:0 bm:606 lo:2 pe:0 ua:2 ap:0 ep:1 wo:f oos:4173260
[============>…….] sync’ed: 67.5% (4072/12488)Mfinish: 0:01:32 speed: 44,952 (36,060) want: 0 K/sec

Une fois la syncronisation terminée, vous devriez avoir cela:

cat /proc/drbd

version: 8.4.3 (api:1/proto:86-101)
srcversion: 1A9F77B1CA5FF92235C2213
0: cs:Connected ro:Secondary/Primary ds:UpToDate/UpToDate C r—–
ns:0 nr:0 dw:0 dr:0 al:0 bm:0 lo:0 pe:0 ua:0 ap:0 ep:1 wo:f oos:0
1: cs:Connected ro:Secondary/Primary ds:UpToDate/UpToDate C r—–
ns:0 nr:183320 dw:356516 dr:0 al:0 bm:23 lo:0 pe:0 ua:0 ap:0 ep:1 wo:f oos:0
2: cs:Connected ro:Secondary/Primary ds:UpToDate/UpToDate C r—–
ns:0 nr:7518312 dw:7518312 dr:972 al:0 bm:1437 lo:0 pe:0 ua:0 ap:0 ep:1 wo:f oos:0
3: cs:Connected ro:Secondary/Primary ds:UpToDate/UpToDate C r—–
ns:0 nr:1377700 dw:4267864 dr:0 al:0 bm:196 lo:0 pe:0 ua:0 ap:0 ep:1 wo:f oos:0
4: cs:Connected ro:Secondary/Primary ds:UpToDate/UpToDate C r—–
ns:0 nr:23442304 dw:23442288 dr:992 al:0 bm:1936 lo:1 pe:0 ua:1 ap:0 ep:1 wo:f oos:0

 

drbd-overview

0:datastores/0 Connected Primary/Secondary UpToDate/UpToDate
1:datastores/1 Connected Primary/Secondary UpToDate/UpToDate
2:images-stores02/0 Connected Primary/Secondary UpToDate/UpToDate
3:images-stores03/0 Connected Primary/Secondary UpToDate/UpToDate
4:images-storesHS/0 Connected Primary/Secondary UpToDate/UpToDate

 

drbd-overview

0:datastores/0 Connected Secondary/Primary UpToDate/UpToDate
1:datastores/1 Connected Secondary/Primary UpToDate/UpToDate
2:images-stores02/0 Connected Secondary/Primary UpToDate/UpToDate
3:images-stores03/0 Connected Secondary/Primary UpToDate/UpToDate
4:images-storesHS/0 Connected Secondary/Primary UpToDate/UpToDate

 

23 mai 2019 /

Création d’un partage entre un hyperviseur KVM et une de ses machines virtuelles

1. Activation de samba

systemctl enable smb nmb
systemctl start smb nmb
2. Identifier le bridge libvirt
ip a show dev virbr0
3. Modifier l’adresse du réseau default (virbr0)
virsh net-edit default
4. Création du répertoire partagé et du compte utilisateur Samba
mkdir /partage_vm
useradd -m -d /partage_vm partage
chown -R partage /partage_vm
chmod g+w /partage_vm
smbpasswd -a partage
5. Configuration dans /etc/samba/smb.conf
[global]
        workgroup = WorkGroup
        netbios name = Host
        server string = serveur %h (Samba %partagevm)
        # On souhaite n’autoriser l’accès que via certaines interfaces réseau
        bind interfaces only = Yes
        interfaces = 127.0.0.1, 192.168.122.1
        log file = /var/log/samba/log.%m
        max log size = 100

[partage_vm]
        path = /partage_vm/
        public = no
        browsable = yes
        valid users = partage
        guest ok = no
        writable = yes
        printable = no
        hide files = /lost+found/
        hide unreadable = yes
        # on force les permissions
        create mask = 777
        force create mode = 777
        security mask = 777
        force security mode = 777
6. Redémarrage Samba et ouverture firewall
systemctl restart nmb.service smb.service 
firewall-cmd --add-service=samba --permanent --add-source=192.168.122.1/24
firewall-cmd --reload
7. Test du partage depuis l’hyperviseur
smbclient //192.168.122.1/partage_vm -U partage
8. Correction SELinux pour un dossier spécifique
semanage fcontext -a -t samba_share_t '/partage_vm(/.*)?'
restorecon -R /partage_vm
9. Autoriser Samba dans /home avec SELinux
setsebool -P samba_enable_home_dirs on
10. Vérification du contexte SELinux
ls -Z /partage_vm
-rw-rw-r--. partage partage unconfined_u:object_r:samba_share_t:s0 truc.txt

Sur la VM, le dossier est accessible à l’adresse \\ip_de_virbr0\partage_vm