🌘 Introduction
Comment déployer un site web avec rsync et SSH en appliquant des permissions avec un wrapper sudo restreint ?
Procédure pour transférer le site via rsync over SSH, puis fixer la propriété finale avec un script root restreint. Cette séparation permet d’automatiser les mises à jour tout en limitant les privilèges accordés sur le serveur.
Cet article 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. L’objectif est de proposer une méthode d’automatisation pour mettre à jour un site statique sans donner de privilèges root étendus. Cette approche remplace l’utilisation de lftp, moins efficace pour les synchronisations incrémentales.
Contexte de test : les essais ont été réalisés sur un serveur Yunohost (système Debian dérivé). Les commandes sont génériques et peuvent nécessiter des ajustements selon votre environnement.
Remarque importante : les UID et GID cités dans les exemples (par ex. 990:33) sont indicatifs. Adaptez-les à votre 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 le 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 /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 ; conservez-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). La permission attendue est 750 : rwx pour root, rx pour le groupe (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)
Le script local doit vérifier les variables d’environnement, lancer rsync, puis invoquer 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.
Il est recommandé d’exécuter d’abord un rsync -n (dry-run) pour vérifier la liste des transferts.
🌘 Tests et vérifications
Après le déploiement, vous pouvez 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érifiez que le parent existe, que les permissions du dossier permettent l’écriture par le groupe et que l’utilisateur de déploiement appartient bien au groupe approprié.
🌘 Remarques sécurité et opérationnelles
– conservez le wrapper minimal : un seul binaire root, une seule action.
– n’utilisez pas de jokers dans la ligne sudoers.
– protégez votre clé SSH locale (chmod 600) et ne la commitez jamais.
– documentez clairement les UID/GID et les chemins autorisés pour les audits.
– configurez sudo en NOPASSWD uniquement pour les commandes nécessaires au déploiement.
🌘 Résumé
Récapitulatif rapide :
- créez un utilisateur de déploiement et un groupe dédié ;
- rendez le dossier cible writable par le groupe (setgid) ;
- installez un wrapper root minimal qui n’accepte qu’un chemin canonique ;
- autorisez l’utilisateur de déploiement à exécuter ce wrapper via sudo sans mot de passe ;
- utilisez
rsynclocalement puis appelez 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).