🌘 Introduction
Je te présente une procédure pour déployer un site avec rsync via SSH, puis appliquer une propriété finale contrôlée par un wrapper root restreint. Mon objectif ici est de t’offrir une méthode d’automatisation pour la mise à jour d’un site web statique sans donner de privilèges root étendus. J’ai mis en place cette nouvelle procédure pour remplacer l’utilisation de lftp, qui est moins efficace pour les synchronisations incrémentales étant donné que tous les fichiers sont transférés à chaque fois.
Contexte de test : j’ai réalisé les essais sur un serveur Yunohost (système Debian dérivé). Les commandes sont génériques mais peuvent nécessiter des ajustements selon ton environnement.
Remarque importante : les UID et GID cités dans les exemples (par ex. 990:33) sont des exemples. Adapte-les à ton contexte.
🌘 Raison technique
rsyncminimise le volume transféré et préserve les métadonnées utiles.- Au lieu d’autoriser
sudoglobalement, on expose un binaire root unique et vérifié qui ne peut agir que sur un chemin autorisé.
🌘 Créer l’utilisateur et le groupe (serveur / Yunohost)
Procédure minimale à exécuter en tant que root sur ton serveur :
# Crée le groupe dédié au déploiement (ignore l'erreur si le groupe existe déjà)
groupadd web-deploy || true
# Crée un utilisateur système pour les déploiements (connexion par clé SSH)
useradd -m -s /bin/bash deployuser || adduser --disabled-password --gecos "" deployuser
# Ajoute l'utilisateur au groupe de déploiement
usermod -aG web-deploy deployuser
Utilise des noms cohérents pour ton infrastructure. L’utilisateur deployuser servira uniquement à la connexion SSH pour rsync.
🌘 Préparer le dossier cible (serveur / Yunohost)
But : permettre l’écriture par les membres du groupe et préserver le groupe sur les nouveaux fichiers.
TARGET=/var/www/application/www
mkdir -p "$TARGET"
# Rends le dossier appartenant à root:web-deploy
chown -R root:web-deploy "$TARGET"
# Rends les dossiers : setgid + rwx pour le groupe
find "$TARGET" -type d -exec chmod 2775 {} \;
# Rends les fichiers : rw pour le propriétaire et le groupe
find "$TARGET" -type f -exec chmod 664 {} \;
Le bit setgid (2) sur les dossiers force l’héritage du groupe.
🌘 Wrapper root restreint pour appliquer la propriété (serveur / Yunohost)
Le wrapper est situé dans le fichier /usr/local/sbin/deploy_chown_site.sh. Il vérifie la cible canonique (readlink -f) et n’autorise que la racine autorisée ou ses sous-chemins. Le code ci-dessous contient des commentaires explicatifs ; garde-les pour la maintenance.
Contenu recommandé (avec commentaires) :
#!/bin/bash
set -euo pipefail
# Chemin autorisé : remplace-le par le chemin canonique de ton site
ALLOWED="/var/www/application/www"
# Vérifie le nombre d'arguments
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <path>"
exit 2
fi
TARGET="$1"
# Obtiens le chemin canonique de la cible
TARGET_ABS="$(readlink -f "$TARGET")" || { echo "target_readlink_failed"; exit 3; }
# Obtiens le chemin canonique autorisé
ALLOWED_ABS="$(readlink -f "$ALLOWED")"
# N'autorise que la racine autorisée ou ses sous-chemins
case "$TARGET_ABS" in
"$ALLOWED_ABS" | "$ALLOWED_ABS"/*)
# Applique la propriété finale (exemple d'UID:GID)
exec /bin/chown -R 990:33 "$TARGET_ABS"
;;
*)
echo "Target not allowed"
exit 3
;;
esac
Sécurise le script :
chown root:root /usr/local/sbin/deploy_chown_site.sh
chmod 750 /usr/local/sbin/deploy_chown_site.sh
Vérifie que seul root possède le fichier (évite les modifications non autorisées). Nous recherchons la permission 750 : rwx pour root, rx pour le groupe (pour permettre l’exécution), et aucune permission pour les autres.
ls -l /usr/local/sbin/deploy_chown_site.sh
Remplace 990:33 par l’UID:GID que tu souhaites appliquer pour ton environnement, ou rends cette valeur paramétrable si tu gères ça dans un contexte contrôlé.
🌘 sudoers : autoriser uniquement ce wrapper (serveur)
Crée /etc/sudoers.d/deploy_chown contenant la ligne suivante :
deployuser ALL=(root) NOPASSWD: /usr/local/sbin/deploy_chown_site.sh
Vérifie la syntaxe avec visudo -c et assure-toi que le fichier a les permissions 0440.
🌘 Script local deploy.sh (client)
Ton code devrait avoir le comportement suivant : vérifie les variables d’environnement, lance rsync, puis invoque le wrapper via ssh.
Exemple de fichier .env.template :
# Nom de l'utilisateur SSH autorisé à déployer
DEPLOY_USER=your_deploy_user
# Hôte SSH de destination
DEPLOY_HOST=your.deployment.host
# Clé privée utilisée pour l'authentification SSH
DEPLOY_KEY=/path/to/your/ssh/key
# Chemin cible du déploiement sur le serveur
DEPLOY_PATH=/path/to/your/deploy/path
# Port SSH, habituellement 22
DEPLOY_PORT=22
DEPLOY_USER, DEPLOY_HOST, DEPLOY_KEY et DEPLOY_PATH sont requis. DEPLOY_PORT est optionnel si tu utilises le port SSH standard, auquel cas il peut rester à 22.
Extrait :
if [ -z "${DEPLOY_USER:-}" ] || [ -z "${DEPLOY_HOST:-}" ] || [ -z "${DEPLOY_PATH:-}" ] || [ -z "${DEPLOY_KEY:-}" ]; then
echo "Veuillez définir DEPLOY_USER, DEPLOY_HOST, DEPLOY_PATH et DEPLOY_KEY"
exit 1
fi
RSYNC_SSH_OPTS="-i \"$DEPLOY_KEY\" -p $DEPLOY_PORT -o StrictHostKeyChecking=no"
rsync -avz --delete --rsync-path="sudo mkdir -p '$DEPLOY_PATH' && sudo rsync" -e "ssh $RSYNC_SSH_OPTS" _site/ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH"
ssh -i "$DEPLOY_KEY" -p "$DEPLOY_PORT" "$DEPLOY_USER@$DEPLOY_HOST" \
"sudo /usr/local/sbin/deploy_chown_site.sh '$DEPLOY_PATH' || echo 'chown wrapper failed'"
Cette variante suppose que sudo est configuré sans mot de passe pour les commandes invoquées à distance. Sans NOPASSWD, rsync et mkdir ne pourront pas répondre à une invite interactive, et le déploiement échouera.
Je te recommande d’abord d’exécuter un rsync -n (dry-run) pour vérifier la liste des transferts.
🌘 Tests et vérifications
Après le déploiement, tu peux vérifier la propriété et les permissions :
ssh -i "$DEPLOY_KEY" -p "$DEPLOY_PORT" "$DEPLOY_USER@$DEPLOY_HOST" \
"ls -ld '$DEPLOY_PATH' && stat -c '%U %G %a' '$DEPLOY_PATH'"
Si rsync renvoie Permission denied, vérifie que le parent existe, que les permissions du dossier permettent l’écriture par le groupe, et que l’utilisateur de déploiement est bien dans le groupe approprié.
🌘 Remarques sécurité et opérationnelles
- garde le wrapper minimal : un seul binaire root, une seule action.
- ne mets pas de jokers dans la ligne sudoers.
- protège ta clé SSH locale (
chmod 600) et ne la commite jamais. - documente clairement les UID/GID et les chemins autorisés pour les audits.
- configure
sudoenNOPASSWDuniquement pour les commandes nécessaires au déploiement.
🌘 Résumé
Récapitulatif rapide :
- crée un utilisateur de déploiement et un groupe dédié ;
- rends le dossier cible writable par le groupe (setgid) ;
- installe un wrapper root minimal qui n’accepte qu’un chemin canonique ;
- autorise l’utilisateur de déploiement à exécuter ce wrapper via sudo sans mot de passe ;
- utilise
rsynclocalement puis appelle le wrapper pour fixer la propriété finale.
Cette approche sépare le transfert de fichiers (opéré par rsync sous l’utilisateur de déploiement) de l’opération privilégiée finale (contrôlée par un wrapper root).