Eu tive uma exigência semelhante há alguns anos. Eu resolvi com o seguinte script.
#!/bin/bash
# This script is designed to be scheduled by cron as often as is required
# If STARTFILE exists, it will start/restart the connection
# If STOPFILE exists, it will stop the connection
# If PORTFILE exist, makes sure the tunnel is started, to survive restarts
#
# PORTFILE will always contain the port number on the remote host that the SSH connection is tunneled to
#
# This utilizes the control socket option of SSH to control the tunnel
# Base name, used by other variables
NAME="ssh_tunnel"
# Create this file to start the server
STARTFILE="/etc/$NAME/$NAME.start"
# This file will contain the port number on the remote server to connect to to access the tunnel
PORTFILE="/etc/$NAME/$NAME.port"
# Create this file to stop the server
STOPFILE="/etc/$NAME/$NAME.stop"
# The user and host to connect the tunnel to
REMOTE="user@hostname"
# The private key of the user on the remote server to create the tunnel to
KEYFILE="/etc/$NAME/.ssh/$NAME"
# The control socket of the SSH connection
SOCKET="/var/run/$NAME.socket"
# First port to try and listen on at remote host
LISTEN=9000
# Last port to try and listen on at remote host
MAXPORT=9999
SSH=$(which ssh)
# We need to run as root, otherwise it will fail
if [ "$(id -u)" != "0" ]; then
echo "Must be run as root!"
exit 1
fi
# Make directory if if doesn't exist
if [ ! -d "/etc/$NAME" ]; then
mkdir "/etc/$NAME"
fi
# Starts the tunnel and updates the control files
start_tunnel() {
# Remove port file, since it is outdated, if it exists
if [ -f ${PORTFILE} ]; then
rm -f ${PORTFILE}
fi
# Start tunnel and wait 2 seconds.. It the tunnel isn't up, then the port is busy (or the public key is foobar)
while true; do
${SSH} -M -S ${SOCKET} -2 -4 -C -f -N -i ${KEYFILE} -o CheckHostIP=no -o KeepAlive=yes -o StrictHostKeyChecking=no -o ExitOnForwardFailure=yes -o BatchMode=yes ${REMOTE} -R $LISTEN:localhost:22
sleep 2
check_tunnel && break
set LISTEN=LISTEN+1
if [ $LISTEN -eq $MAXPORT ]; then
# No ports available (or more likely, the public key is incorrect)
exit 1
fi
done
echo ${LISTEN} > ${PORTFILE}
# Remove startfile, since the process is now started
if [ -f ${STARTFILE} ]; then
rm -f ${STARTFILE}
fi
}
# Stops the tunnel and cleans up the control files
stop_tunnel() {
# Remove portfile and stopfile if they exist
if [ -f ${PORTFILE} ]; then
rm -f ${PORTFILE}
fi
if [ -f ${STOPFILE} ]; then
rm -f ${STOPFILE}
fi
${SSH} -S ${SOCKET} -O exit ${REMOTE} > /dev/null 2>&1
}
# Check if the tunnel is up
check_tunnel() {
if [ -e ${SOCKET} ]; then
(${SSH} -S ${SOCKET} -O check ${REMOTE} 2>&1 | grep -q "running") && return 0
fi
return 1
}
# Use a lock file so only one instance is running
(
flock -n 9 || exit 1
if [ -f ${STARTFILE} ]; then
# Restart if running, otherwise just start
check_tunnel && stop_tunnel
start_tunnel
elif [ -f ${STOPFILE} ]; then
# Stop if running
check_tunnel && stop_tunnel
elif [ -f ${PORTFILE} ]; then
# The tunnel should be running, might not be after a reboot, for example
check_tunnel || start_tunnel
fi
) 9>/var/run/$NAME.lock
O script foi projetado para ser agendado como uma tarefa cron (no meu caso, a cada minuto).
Verifique a existência do arquivo especificado na variável STARTFILE
. Isso foi criado por meio de uma interface da Web, mas deve ser muito fácil modificar esse script para atender às suas necessidades. A razão para o controle da interface web é que o cliente talvez não queira ter um backdoor permanente em sua rede .. :)
Infelizmente, não posso compartilhar a web part, pois isso faz parte de um projeto muito maior.
De qualquer forma, o que você deve configurar para que isso funcione é configurar as variáveis REMOTE
e KEYFILE
para que ele aponte para um usuário / host e uma chave privada válidos. O resto é bem opcional.
Depois disso, basta criar o STARTFILE
e executar o script (ou agendá-lo via cron) e o túnel deve ser iniciado.
Digamos que você esteja executando o script no servidor A
e REMOTE
esteja definido como user@B
. Se o conteúdo de PORTFILE
on A
for 9000
, você poderá fazer o login no servidor A
via B:9000
, usando qualquer usuário válido do servidor A
.
Espero que tudo isso faça sentido ...