Question (TL; DR)
Lors de l'attribution dynamique de ports pour le transfert à distance ( -R
option aka ), comment un script sur la machine distante (provenant par exemple de .bashrc
) peut-il déterminer quels ports ont été choisis par OpenSSH?
Contexte
J'utilise OpenSSH (aux deux extrémités) pour me connecter à notre serveur central, que je partage avec plusieurs autres utilisateurs. Pour ma session à distance (pour l'instant), je voudrais transmettre X, cups et pulseaudio.
Le plus trivial est le transfert de X, en utilisant l' -X
option. L'adresse X allouée est stockée dans la variable d'environnement DISPLAY
et à partir de là, je peux déterminer le port TCP correspondant, dans la plupart des cas de toute façon. Mais je n'en ai presque jamais besoin, car Xlib fait honneur DISPLAY
.
J'ai besoin d'un mécanisme similaire pour les tasses et pulseaudio. Les bases pour les deux services existent, sous la forme des variables environnementales CUPS_SERVER
et PULSE_SERVER
, respectivement. Voici des exemples d'utilisation:
ssh -X -R12345:localhost:631 -R54321:localhost:4713 datserver
export CUPS_SERVER=localhost:12345
lowriter #and I can print using my local printer
lpr -P default -o Duplex=DuplexNoTumble minutes.pdf #printing through the tunnel
lpr -H localhost:631 -P default -o Duplex=DuplexNoTumble minutes.pdf #printing remotely
mpg123 mp3s/van_halen/jump.mp3 #annoy co-workers
PULSE_SERVER=localhost:54321 mpg123 mp3s/van_halen/jump.mp3 #listen to music through the tunnel
Le problème est réglé CUPS_SERVER
et PULSE_SERVER
correctement.
Nous utilisons beaucoup les transferts de ports et j'ai donc besoin d'allocations de ports dynamiques. Les allocations de ports statiques ne sont pas une option.
OpenSSH dispose d'un mécanisme d'allocation dynamique des ports sur le serveur distant, en spécifiant 0
comme port de liaison pour le transfert à distance (l' -R
option). En utilisant la commande suivante, OpenSSH allouera dynamiquement des ports pour les coupes et le transfert d'impulsions.
ssh -X -R0:localhost:631 -R0:localhost:4713 datserver
Lorsque j'utilise cette commande, ssh
imprimera ce qui suit pour STDERR
:
Allocated port 55710 for remote forward to 127.0.0.1:4713
Allocated port 41273 for remote forward to 127.0.0.1:631
Voilà les informations que je veux! Finalement, je veux générer:
export CUPS_SERVER=localhost:41273
export PULSE_SERVER=localhost:55710
Cependant, les messages "Port alloué ..." sont créés sur ma machine locale et envoyés à STDERR
, auxquels je ne peux pas accéder sur la machine distante. Curieusement, OpenSSH ne semble pas avoir les moyens de récupérer des informations sur les transferts de port.
Comment puis-je récupérer ces informations pour les mettre dans un script shell pour définir correctement CUPS_SERVER
et PULSE_SERVER
sur l'hôte distant?
Impasses
La seule chose facile que j'ai pu trouver était d'augmenter la verbosité du sshd
jusqu'à ce que ces informations puissent être lues dans les journaux. Ce n'est pas viable car ces informations révèlent beaucoup plus d'informations qu'il n'est raisonnable de rendre accessibles aux utilisateurs non root.
Je pensais à patcher OpenSSH pour prendre en charge une séquence d'échappement supplémentaire qui affiche une belle représentation de la structure interne permitted_opens
, mais même si c'est ce que je veux, je ne peux toujours pas accéder aux séquences d'échappement client depuis le côté serveur.
Il doit y avoir une meilleure façon
L'approche suivante semble très instable et se limite à une session SSH par utilisateur. Cependant, j'ai besoin d'au moins deux sessions simultanées et d'autres utilisateurs encore plus. Mais j'ai essayé ...
Lorsque les étoiles sont alignées correctement, après avoir sacrifié un poulet ou deux, je peux abuser du fait que ce sshd
n'est pas démarré en tant qu'utilisateur, mais abandonne les privilèges après une connexion réussie, pour ce faire:
obtenir une liste des numéros de port pour toutes les sockets d'écoute qui appartiennent à mon utilisateur
netstat -tlpen | grep ${UID} | sed -e 's/^.*:\([0-9]\+\) .*$/\1/'
obtenir une liste des numéros de port pour toutes les sockets d'écoute qui appartiennent aux processus démarrés par mon utilisateur
lsof -u ${UID} 2>/dev/null | grep LISTEN | sed -e 's/.*:\([0-9]\+\) (LISTEN).*$/\1/'
Tous les ports qui se trouvent dans le premier ensemble, mais pas dans le deuxième ensemble ont une forte probabilité d'être mes ports de transfert, et en effet de soustraire les rendements des ensembles
41273
,55710
et6010
; tasses, pouls et X, respectivement.6010
est identifié comme le port X utilisantDISPLAY
.41273
est le port des tasses, carlpstat -h localhost:41273 -a
retourne0
.55710
est le port d'impulsion, carpactl -s localhost:55710 stat
retourne0
. (Il imprime même le nom d'hôte de mon client!)
(Pour effectuer la soustraction définie sort -u
, enregistrer la sortie des lignes de commande ci-dessus et utiliser comm
pour effectuer la soustraction.)
Pulseaudio me permet d'identifier le client et, à toutes fins utiles, cela peut servir d'ancrage pour séparer les sessions SSH qui doivent être séparées. Cependant, je ne l' ai pas trouvé un moyen de lier 41273
, 55710
et 6010
au même sshd
processus. netstat
ne divulguera pas ces informations aux utilisateurs non root. Je reçois seulement un -
dans la PID/Program name
colonne où je voudrais lire 2339/54
(dans ce cas particulier). Si proche ...
la source
netstat
vous ne verrez pas le PID pour les processus que vous ne possédez pas ou qui sont de l'espace noyau. Par exempleRéponses:
Prenez deux (voir l'historique pour une version qui fait scp du côté serveur et est un peu plus simple), cela devrait le faire. L'essentiel est le suivant:
Tout d'abord, configurez le côté distant, vous devez activer l'envoi d'une variable env dans la configuration sshd :
Trouvez la ligne avec
AcceptEnv
et ajoutez-MY_PORT_FILE
y (ou ajoutez la ligne dans laHost
section de droite s'il n'y en a pas encore). Pour moi, la ligne est devenue ceci:N'oubliez pas de redémarrer sshd pour que cela prenne effet.
De plus, pour que les scripts ci-dessous fonctionnent, faites
mkdir ~/portfiles
du côté distant!Ensuite, du côté local, un extrait de script qui
L'extrait de script:
Ensuite, un extrait pour le côté distant, adapté pour .bashrc :
Remarque : Le code ci-dessus n'est bien sûr pas très testé et peut contenir toutes sortes de bugs, erreurs de copier-coller, etc. Quiconque l'utilise mieux le comprend également, utilisez-le à vos risques et périls! Je l'ai testé en utilisant juste une connexion localhost, et cela a fonctionné pour moi, dans mon test env. YMMV.
la source
scp
passer du côté distant au côté local, ce que je ne peux pas. J'avais une approche similaire, mais je terminaisssh
en arrière-plan après avoir établi la connexion, puis envoyais ce fichier de local à distant viascp
, puis tirais lessh
client au premier plan et exécutais un script sur le côté distant. Je n'ai pas compris comment créer un script d'arrière-plan et de mise en avant des processus locaux et distants correctement. Envelopper et intégrer lessh
client local avec certains scripts distants comme celui-ci ne semble pas être une bonne approche.(while [ ... ] ; do sleep 1 ; done ; scp ... )&
. Attendez ensuite au premier plan dans le serveur.bashrc
(en supposant que le client envoie la variable env droite) pour que le fichier apparaisse. Je mettrai à jour la réponse plus tard après quelques tests (probablement pas de temps avant demain).~/.ssh-${PID}-forwards
Un extrait pour le côté local, adapté pour .bashrc:
la source
J'ai obtenu le même résultat en créant un canal sur le client local, puis en redirigeant stderr vers le canal qui est également redirigé vers l'entrée de ssh. Il ne nécessite pas plusieurs connexions ssh pour présumer un port connu libre qui pourrait échouer. De cette façon, la bannière de connexion et le texte "Port alloué ### ..." sont redirigés vers l'hôte distant.
J'ai un script simple sur l'hôte
getsshport.sh
qui est exécuté sur l'hôte distant qui lit l'entrée redirigée et analyse le port. Tant que ce script ne se termine pas, le transfert à distance ssh reste ouvert.côté local
3>&1 1>&2 2>&3
est une petite astuce pour échanger stderr et stdout, afin que stderr soit canalisé vers cat, et toute la sortie normale de ssh soit affichée sur stderr.côté distant ~ / getsshport.sh
J'ai essayé
grep
le message "port alloué" du côté local avant de l'envoyer via ssh, mais il semble que ssh bloquera l'attente de l'ouverture du canal sur stdin. grep n'ouvre pas le canal d'écriture jusqu'à ce qu'il reçoive quelque chose, donc cela se bloque essentiellement.cat
cependant ne semble pas avoir ce même comportement, et ouvre immédiatement le canal d'écriture permettant à ssh d'ouvrir la connexion.c'est le même problème du côté distant, et pourquoi
read
ligne par ligne au lieu de simplement grep de stdin - sinon `/ tmp / allocationsports 'n'est pas écrit jusqu'à ce que le tunnel ssh soit fermé, ce qui va à l'encontre de l'objectifIl
~/getsshport.sh
est préférable de canaliser le stderr de ssh dans une commande similaire , car sans spécifier de commande, le texte de la bannière ou tout ce qui se trouve dans le tuyau est exécuté sur le shell distant.la source
renice +10 $$; exec cat
avant ledone
pour économiser des ressources.Il s'agit d'une opération délicate, une gestion supplémentaire côté serveur le long de
SSH_CONNECTION
ouDISPLAY
serait géniale, mais ce n'est pas facile à ajouter: une partie du problème est que seul lessh
client connaît la destination locale, le paquet de demande (au serveur) contient uniquement l'adresse et le port distants.Les autres réponses ici ont diverses solutions peu judicieuses pour capturer ce côté client et l'envoyer au serveur. Voici une approche alternative qui n'est pas beaucoup plus jolie pour être honnête, mais au moins cette laideur est conservée côté client ;-)
SendEnv
afin que nous puissions envoyer certaines variables d'environnement nativement via ssh (probablement pas par défaut)AcceptEnv
pour l'accepter (probablement pas activé par défaut)ssh
sortie du client stderr avec une bibliothèque chargée dynamiquement et mettre à jour l'environnement client ssh pendant la configuration de la connexionCela fonctionne (heureusement, pour l'instant de toute façon) parce que les renvois à distance sont configurés et enregistrés avant l'échange de l'environnement (confirmer avec
ssh -vv ...
). La bibliothèque chargée dynamiquement doit capturer lawrite()
fonction libc (ssh_confirm_remote_forward()
→logit()
→do_log()
→write()
). La redirection ou l'encapsulation de fonctions dans un binaire ELF (sans recompilation) est des ordres de grandeur plus complexes que de faire de même pour une fonction dans une bibliothèque dynamique.Sur le client
.ssh/config
(ou en ligne de commande-o SendEnv ...
)Sur le serveur
sshd_config
(modification racine / administrative requise)Cette approche fonctionne pour les clients Linux et ne nécessite rien de spécial sur le serveur, elle devrait fonctionner pour les autres * nix avec quelques ajustements mineurs. Fonctionne depuis au moins OpenSSH 5.8p1 jusqu'à 7.5p1.
Compiler avec
gcc -Wall -shared -ldl -Wl,-soname,rfwd -o rfwd.so rfwd.c
Invoke avec:Le code:
(Il existe des pièges à ours glibc liés à la gestion des versions des symboles avec cette approche, mais
write()
cela ne pose pas ce problème.)Si vous vous sentez courageux, vous pouvez prendre le
setenv()
code associé et le patcher enssh.c
ssh_confirm_remote_forward()
fonction de rappel.Cela définit les variables d'environnement nommées
SSH_RFWD_nnn
, inspectez-les dans votre profil, par exemple dansbash
Mises en garde:
ssh
actuellement ne consigne pas clairement le transfert complet du formulaire * local: port: remote: port * (si nécessaire, une analyse plus approfondie desdebug1
messages avecssh -v
serait nécessaire), mais vous n'en avez pas besoin pour votre cas d'utilisationVous pouvez (en partie) le faire de manière interactive avec l'échappement
~#
, bizarrement l'implémentation saute les canaux qui écoutent, elle ne répertorie que les canaux ouverts (c'est-à-dire TCP ESTABLISHED), et elle n'imprime en aucun cas les champs utiles. Voirchannels.c
channel_open_message()
Vous pouvez corriger cette fonction pour imprimer les détails des
SSH_CHANNEL_PORT_LISTENER
créneaux horaires, mais cela ne vous donne que les renvois locaux (les canaux ne sont pas la même chose que les renvois réels ). Ou, vous pouvez le patcher pour vider les deux tables de transfert de laoptions
structure globale :Cela fonctionne bien, bien que ce ne soit pas une solution "programmatique", avec la mise en garde que le code client ne met pas (encore, il est marqué XXX dans la source) la liste lorsque vous ajoutez / supprimez des renvois à la volée (
~C
)Si le ou les serveurs sont Linux, vous avez une option de plus, c'est celle que j'utilise généralement, bien que pour le transfert local plutôt que distant.
lo
est 127.0.0.1/8, sous Linux, vous pouvez vous lier de manière transparente à n'importe quelle adresse en 127/8 , vous pouvez donc utiliser des ports fixes si vous utilisez des adresses 127.xyz uniques, par exemple:Ceci est soumis à la liaison de ports privilégiés <1024, OpenSSH ne prend pas en charge les capacités Linux et dispose d'une vérification UID codée en dur sur la plupart des plates-formes.
Des octets judicieusement choisis (mnémoniques ASCII dans mon cas) aident à démêler le désordre à la fin de la journée.
la source