Pregunta (TL; DR)
Al asignar puertos dinámicamente para el reenvío remoto (también conocido como -R
opción), ¿cómo puede un script en la máquina remota (por ejemplo, procedente de .bashrc
) determinar qué puertos fueron elegidos por OpenSSH?
Antecedentes
Utilizo OpenSSH (en ambos extremos) para conectarme a nuestro servidor central, que comparto con muchos otros usuarios. Para mi sesión remota (por ahora) me gustaría reenviar X, cups y pulseaudio.
Lo más trivial es reenviar X, usando la -X
opción. La dirección X asignada se almacena en la variable de entorno DISPLAY
y a partir de eso puedo determinar el puerto TCP correspondiente, en la mayoría de los casos de todos modos. Pero casi nunca necesito hacerlo, porque Xlib lo honra DISPLAY
.
Necesito un mecanismo similar para tazas y pulseaudio. Existen los conceptos básicos para ambos servicios, en forma de variables ambientales CUPS_SERVER
y PULSE_SERVER
, respectivamente. Aquí hay ejemplos de uso:
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
El problema está configurando CUPS_SERVER
y PULSE_SERVER
correctamente.
Usamos muchos reenvíos de puertos y, por lo tanto, necesito asignaciones de puertos dinámicas. Las asignaciones de puertos estáticos no son una opción.
OpenSSH tiene un mecanismo para la asignación dinámica de puertos en el servidor remoto, al especificar 0
como puerto de enlace para el reenvío remoto (la -R
opción). Al usar el siguiente comando, OpenSSH asignará dinámicamente puertos para tazas y reenvío de pulsos.
ssh -X -R0:localhost:631 -R0:localhost:4713 datserver
Cuando uso ese comando, ssh
imprimiremos lo siguiente para 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
¡Ahí está la información que quiero! En definitiva quiero generar:
export CUPS_SERVER=localhost:41273
export PULSE_SERVER=localhost:55710
Sin embargo, los mensajes de "Puerto asignado ..." se crean en mi máquina local y se envían a los STDERR
cuales no puedo acceder desde la máquina remota. Curiosamente, OpenSSH no parece tener medios para recuperar información sobre reenvíos de puertos.
¿Cómo obtengo esa información para ponerla en un script de shell para configurar adecuadamente CUPS_SERVER
y PULSE_SERVER
en el host remoto?
Callejones sin salida
Lo único fácil que pude encontrar fue aumentar la verbosidad de la sshd
información hasta que se pueda leer la información de los registros. Esto no es viable ya que esa información revela mucha más información de la que es razonable hacer accesible para usuarios no root.
Estaba pensando en parchear OpenSSH para admitir una secuencia de escape adicional que imprima una buena representación de la estructura interna permitted_opens
, pero incluso si eso es lo que quiero, todavía no puedo acceder a las secuencias de escape del cliente desde el lado del servidor.
Debe haber una mejor manera
El siguiente enfoque parece muy inestable y está limitado a una sesión SSH por usuario. Sin embargo, necesito al menos dos sesiones simultáneas y otros usuarios aún más. Pero lo intenté ...
Cuando las estrellas están alineadas correctamente, después de haber sacrificado una gallina o dos, puedo abusar del hecho de que sshd
no se inicia como mi usuario, sino que elimina los privilegios después de iniciar sesión correctamente, para hacer esto:
obtener una lista de números de puerto para todos los enchufes de escucha que pertenecen a mi usuario
netstat -tlpen | grep ${UID} | sed -e 's/^.*:\([0-9]\+\) .*$/\1/'
obtener una lista de números de puerto para todos los sockets de escucha que pertenecen a procesos que mi usuario inició
lsof -u ${UID} 2>/dev/null | grep LISTEN | sed -e 's/.*:\([0-9]\+\) (LISTEN).*$/\1/'
Todos los puertos que están en el primer conjunto, pero no en el segundo conjunto tienen una alta probabilidad de ser mis puertos de reenvío, y de hecho restando los rendimientos de los conjuntos
41273
,55710
y6010
; tazas, pulso y X, respectivamente.6010
se identifica como el puerto X usandoDISPLAY
.41273
es el puerto de tazas, porquelpstat -h localhost:41273 -a
regresa0
.55710
es el puerto de pulso, porquepactl -s localhost:55710 stat
regresa0
. (¡Incluso imprime el nombre de host de mi cliente!)
(Para hacer la substracción del conjunto I sort -u
y almacenar la salida de las líneas de comando anteriores y usar comm
para hacer la substracción).
Pulseaudio me permite identificar al cliente y, para todos los efectos, esto puede servir como un ancla para separar las sesiones SSH que necesitan separarse. Sin embargo, no he encontrado una manera de vincular 41273
, 55710
y 6010
al mismo sshd
proceso. netstat
no divulgará esa información a usuarios no root. Solo recibo un -
en la PID/Program name
columna donde me gustaría leer 2339/54
(en este caso particular). Tan cerca ...
fuente
netstat
no le mostrará el PID para los procesos que no posee o que son kernel-space. Por ejemploRespuestas:
Tome dos (vea el historial para una versión que hace scp desde el lado del servidor y es un poco más simple), esto debería hacerlo. La esencia de esto es esto:
Primero, la configuración en el lado remoto, debe habilitar el envío de una variable env en la configuración sshd :
Busque la línea
AcceptEnv
y agréguelaMY_PORT_FILE
(o agregue la línea debajo de laHost
sección derecha si aún no hay una) Para mí, la línea se convirtió en esto:Asimismo, recuerda a reiniciar sshd para que esto tenga efecto.
Además, para que funcionen las siguientes secuencias de comandos, ¡hazlo
mkdir ~/portfiles
en el lado remoto!Luego, en el lado local, un fragmento de script que
El fragmento de script:
Luego un fragmento para el lado remoto, adecuado para .bashrc :
Nota : Por supuesto, el código anterior no se ha probado exhaustivamente y puede contener todo tipo de errores, errores de copiar y pegar, etc. Cualquiera que lo use mejor también lo entiende, ¡ úselo bajo su propio riesgo! Lo probé usando solo la conexión localhost, y funcionó para mí, en mi entorno de prueba. YMMV.
fuente
scp
desde el lado remoto al lado local, lo que no puedo. Tuve un enfoque similar, pero lo envolvíssh
en segundo plano después de establecer la conexión, luego envié ese archivo de local a remoto a través descp
y luego empujé alssh
cliente a primer plano y ejecuté un script en el lado remoto. No he descubierto cómo hacer un script de fondo y poner en primer plano procesos locales y remotos muy bien. Ajustar e integrar elssh
cliente local con algunos scripts remotos como ese no parece un buen enfoque.(while [ ... ] ; do sleep 1 ; done ; scp ... )&
. Luego, espere en primer plano en el servidor.bashrc
(suponiendo que el cliente envíe la variable env correcta) para que aparezca el archivo. Actualizaré la respuesta más tarde después de algunas pruebas (probablemente no habrá tiempo hasta mañana).~/.ssh-${PID}-forwards
Un fragmento para el lado local, adecuado para .bashrc:
fuente
He logrado lo mismo al crear una tubería en el cliente local, luego redirigir stderr a la tubería que también se redirige a la entrada de ssh. No requiere múltiples conexiones ssh para suponer un puerto libre conocido que podría fallar. De esta manera, el banner de inicio de sesión y el texto "Puerto asignado ### ..." se redirige al host remoto.
Tengo un script simple en el host
getsshport.sh
que se ejecuta en el host remoto que lee la entrada redirigida y analiza el puerto. Mientras este script no finalice, el avance remoto ssh permanece abierto.lado local
3>&1 1>&2 2>&3
es un pequeño truco para intercambiar stderr y stdout, de modo que stderr se canalice a cat y toda la salida normal de ssh se muestre en stderr.lado remoto ~ / getsshport.sh
Yo probé
grep
al mensaje "puerto asignado" en el lado local en primer lugar antes de enviarlo a través de SSH, pero parece que ssh bloqueará la espera de la tubería para abrir la entrada estándar. grep no abre la tubería para escribir hasta que recibe algo, por lo que esto básicamente se interrumpe.cat
sin embargo, no parece tener este mismo comportamiento, y abre la tubería para escribir inmediatamente permitiendo que ssh abra la conexión.este es el mismo problema en el lado remoto, y por qué
read
línea por línea en lugar de solo grep de stdin; de lo contrario, `/ tmp / alloport 'no se escribe hasta que se cierra el túnel ssh, lo que frustra todo el propósito~/getsshport.sh
Se prefiere canalizar el stderr de ssh en un comando como , ya que sin especificar un comando, el texto del banner o cualquier otra cosa en la tubería se ejecuta en el shell remoto.fuente
renice +10 $$; exec cat
antes deldone
para ahorrar recursos.Esto es complicado, el manejo adicional del lado del servidor a lo largo de las líneas
SSH_CONNECTION
oDISPLAY
sería genial, pero no es fácil de agregar: parte del problema es que solo elssh
cliente conoce el destino local, el paquete de solicitud (al servidor) contiene solo la dirección remota y el puerto.Las otras respuestas aquí tienen varias soluciones sencillas para capturar este lado del cliente y enviarlo al servidor. Aquí hay un enfoque alternativo que no es mucho más bonito para ser honesto, pero al menos esta fiesta fea se mantiene del lado del cliente ;-)
SendEnv
para que podamos enviar algunas variables de entorno de forma nativa a través de ssh (probablemente no predeterminado)AcceptEnv
para aceptar lo mismo (probablemente no esté habilitado de forma predeterminada)ssh
salida del cliente stderr con una biblioteca cargada dinámicamente y actualice el entorno del cliente ssh durante la configuración de la conexiónEsto funciona (felizmente, por ahora de todos modos) porque los reenvíos remotos se configuran y graban antes de intercambiar el entorno (confirmar con
ssh -vv ...
). La biblioteca cargada dinámicamente tiene que capturar lawrite()
función libc (ssh_confirm_remote_forward()
→logit()
→do_log()
→write()
). Las funciones de redireccionamiento o ajuste en un binario ELF (sin recompilar) son órdenes de magnitud más complejas que hacer lo mismo para una función en una biblioteca dinámica.En el cliente
.ssh/config
(o línea de comando-o SendEnv ...
)En el servidor
sshd_config
(se requiere cambio administrativo / raíz)Este enfoque funciona para clientes Linux y no requiere nada especial en el servidor, debería funcionar para otros * nix con algunos ajustes menores. Funciona desde al menos OpenSSH 5.8p1 hasta 7.5p1.
Compilar con
gcc -Wall -shared -ldl -Wl,-soname,rfwd -o rfwd.so rfwd.c
Invoke con:El código:
(Hay algunas trampas de oso glibc relacionadas con el control de versiones de símbolos con este enfoque, pero
write()
no tiene este problema).Si te sientes valiente, puedes tomar el
setenv()
código relacionado y parcharlo en lassh.c
ssh_confirm_remote_forward()
función de devolución de llamada.Esto establece las variables de entorno nombradas
SSH_RFWD_nnn
, inspeccione estas en su perfil, por ejemplo, enbash
Advertencias:
ssh
actualmente no registra claramente el reenvío completo del formulario * local: puerto: remoto: puerto * (si fuera necesario, se requeriría un análisis adicional de losdebug1
mensajesssh -v
), pero no lo necesita para su caso de usoPuede (en parte) hacer esto interactivamente con el escape
~#
, curiosamente, la implementación omite los canales que están escuchando, solo enumera los abiertos (es decir, TCP ESTABLECIDO) y no imprime los campos útiles en ningún caso. Verchannels.c
channel_open_message()
Puede parchear esa función para imprimir los detalles de las
SSH_CHANNEL_PORT_LISTENER
ranuras, pero eso solo le proporciona los reenvíos locales (los canales no son lo mismo que los reenvíos reales ). O bien, puede parchearlo para volcar las dos tablas de reenvío de laoptions
estructura global :Esto funciona bien, aunque no es una solución "programática", con la advertencia de que el código del cliente no actualiza (todavía, está marcado como XXX en la fuente) la lista cuando agrega / elimina reenvíos sobre la marcha (
~C
)Si los servidores son Linux, tiene una opción más, esta es la que uso generalmente, aunque para el reenvío local en lugar de remoto.
lo
es 127.0.0.1/8, en Linux puede vincularse de forma transparente a cualquier dirección en 127/8 , por lo que puede usar puertos fijos si usa direcciones 127.xyz únicas, por ejemplo:Esto está sujeto a la vinculación de puertos privilegiados <1024, OpenSSH no es compatible con las capacidades de Linux y tiene una comprobación de UID codificada en la mayoría de las plataformas.
Los octetos sabiamente elegidos (mnemotécnicos ordinales ASCII en mi caso) ayudan a desenredar el desastre al final del día.
fuente