Aller au contenu

Pipe stdin → SSH → psql : pourquoi et comment

Le pattern

echo "SQL ICI" | ssh root@serveur "docker exec -i container psql -U user -d db"

Deux étapes liées par | (pipe) :

  1. echo "..." — génère le SQL comme texte brut sur la sortie standard
  2. | — redirige cette sortie vers l'entrée standard (stdin) de la commande suivante
  3. ssh root@serveur "..." — se connecte au VPS et y exécute une commande
  4. docker exec -i container — exécute dans le container (-i = lit depuis stdin)
  5. psql -U user -d db — reçoit le SQL depuis stdin et l'exécute

Pourquoi pas -c "SQL" via SSH ?

Le shell interprète les $ comme des variables avant d'envoyer la commande.

# DANGEREUX — $2b$12$abc devient "b2abc" (variables vides)
ssh root@vps "docker exec postgres psql -c \"UPDATE t SET pwd='$2b$12$abc'\""

# SÛR — le $ passe tel quel, aucune interprétation
echo "UPDATE t SET pwd='\$2b\$12\$abc'" | ssh root@vps "docker exec -i postgres psql"

Cas concret : les hash bcrypt ($2b$12$...) sont détruits par le shell si passés en argument. Le pipe stdin les transmet intacts.


Guillemets dans le SQL

Dans echo "...", les guillemets doubles intérieurs doivent être échappés avec \" :

# Colonne PostgreSQL avec guillemets doubles (casse sensible)
echo "UPDATE utilisateurs SET \"idRole\" = 'role_admin' WHERE email = 'x@y.com';"
#                             ^^^^^^^^                    ← échappé car déjà dans "..."

Les guillemets simples 'valeur' n'ont pas besoin d'être échappés dans echo "...".


Variante : fichier temporaire (pour SQL long)

# Écrire le SQL dans un fichier
cat > /tmp/query.sql << 'EOF'
UPDATE utilisateurs
SET "idRole" = 'role_super_admin',
    "motDePasse" = '$2b$12$abc...',
    "updatedAt" = NOW()
WHERE email = 'user@domaine.com';
EOF

# Exécuter via pipe
cat /tmp/query.sql | ssh root@vps "docker exec -i shared-postgres psql -U fichepaie_user -d gestionFichePaie"

Le heredoc << 'EOF' (avec guillemets autour de EOF) désactive aussi l'interprétation des $.


Pourquoi $2b devient vide — explication détaillée

Le shell Unix interprète tout $mot comme une variable à substituer. Si la variable n'existe pas, elle est remplacée par une chaîne vide — sans erreur, silencieusement.

NOM="Bernard"
echo "Bonjour $NOM"   # → Bonjour Bernard   (variable existante)
echo "Bonjour $TOTO"  # → Bonjour           (variable inexistante = vide)
echo "Bonjour $2b"    # → Bonjour           (idem — $2b n'existe pas)

Un hash bcrypt ressemble exactement à des variables shell :

$2b$12$rwmRd6YnxHabhp2i.wXNbOYo
^^^  ^^^
 |    └── variable $12 → vide
 └──────── variable $2b → vide

Résultat après interprétation du shell :

# Hash original
$2b$12$rwmRd6YnxHabhp2i.wXNbOYo

# Après substitution des variables ($2b=vide, $12=vide)
rwmRd6YnxHabhp2i.wXNbOYo

Le hash est corrompu, bcrypt.compare() retourne false, la connexion échoue — sans aucun message d'erreur explicite.

La seule protection : ne jamais laisser le shell interpréter le SQL contenant un hash. Le pipe stdin court-circuite cette interprétation car le texte est transmis directement au processus destinataire.


Récapitulatif des règles

Situation Méthode Raison
SQL simple sans $ -c "SQL" ou pipe Les deux marchent
SQL avec hash bcrypt ($2b$...) Pipe stdin obligatoire Le shell mange les $
SQL long ou multi-lignes Fichier temp + pipe Plus lisible
SQL généré par Node.js execSync(cmd, { input: sql }) Pipe programmatique