Skip to content

Instantly share code, notes, and snippets.

@seahorsepip
Last active March 22, 2024 15:30
Show Gist options
  • Save seahorsepip/5bc644a850d2ac3c62c9da521eabbecd to your computer and use it in GitHub Desktop.
Save seahorsepip/5bc644a850d2ac3c62c9da521eabbecd to your computer and use it in GitHub Desktop.
Reverse SSH tunnel NGINX config script

To be used with /etc/ssh/sshd_config ForceCommand.

Requirements:

Example usage:

#!/bin/bash
clear
declare -A parsed=()
function rand () {
head /dev/urandom | tr -dc a-z | head -c 3 ; echo ''
}
function get_port () {
tail /var/log/auth.log --lines 200 \
| grep 'Local forwarding listening on 127.0.0.1 port' \
| tail -n1 \
| ([[ $(cat) =~ .*[[:space:]]([[:digit:]]*).* ]]; echo ${BASH_REMATCH[1]})
}
function get_name () {
tail /var/log/auth.log --lines 200 \
| grep 'server_input_global_request: tcpip-forward listen' \
| tail -n1 \
| ([[ $(cat) =~ .*[[:space:]]listen[[:space:]](.*)[[:space:]]port.* ]]; echo ${BASH_REMATCH[1]}) \
| tr -s '/'
}
function parse () {
IFS='#' read -ra servers <<< "${config//\}\}/#}"
parsed[.length]=${#servers[@]}
for i in "${!servers[@]}"
do
IFS='#' read -ra locations <<< "${servers[i]//location /#}"
local server_name=${locations:20:-12}
unset "locations[0]"
locations=("${locations[@]}")
parsed[$server_name]=$i
parsed[$i]=$server_name
parsed[$i.length]=${#locations[@]}
for j in "${!locations[@]}"
do
[[ ${locations[$j]} =~ (.*)[[:space:]]\{proxy_pass[[:space:]].*:(.*)\; ]]
local location=${BASH_REMATCH[1]}
local proxy_pass=${BASH_REMATCH[2]}
if [ ${#location} -gt 2 ]
then
location=${location:1:-6}
proxy_pass=${proxy_pass:0:-2}
fi
parsed[$i.$location]=${j}
parsed[$i.$j.location]=$location
parsed[$i.$j.port]=$proxy_pass
done
done
}
function sort_locations () {\
for ((i=0; i <= $((${#locations[@]} - 2)); ++i))
do
for ((j=((i + 1)); j <= ((${#locations[@]} - 1)); ++j))
do
a=${locations[i]}
if [ ${#a} -eq 1 ]
then
a=''
fi
a=${a//[^\/]}
b=${locations[j]}
if [ ${#b} -eq 1 ]
then
b=''
fi
b=${b//[^\/]}
if [ ${#b} -gt ${#a} ]
then
tmp=${locations[i]}
locations[i]=${locations[j]}
locations[j]=$tmp
fi
done
done
}
function stringify () {
config=''
for i in $(seq 0 $((${parsed[.length]} - 1)))
do
if [ -n "${parsed[$i]}" ]
then
config+="server {server_name ${parsed[$i]};listen 443;"
locations=()
declare -A ports=()
for j in $(seq 0 $((${parsed[$i.length]} - 1)))
do
if [ -n "${parsed[$i.$j.location]}" ]
then
locations[$j]=${parsed[$i.$j.location]}
ports[${parsed[$i.$j.location]}]=${parsed[$i.$j.port]}
fi
done
sort_locations
local location
for location in "${locations[@]}"
do
if [ -n "$location" ]
then
local proxy_pass="http://127.0.0.1:${ports[$location]}"
if [ ${#location} -gt 1 ]
then
location="~$location(/.*)\$"
proxy_pass+="\$1\$is_args\$args"
fi
config+="location $location {proxy_pass $proxy_pass;}"
fi
done
config+="}"
fi
done
}
function add () {
local i=${parsed[$server_name]}
if [ -z "$i" ]
then
i=${parsed[.length]}
parsed[.length]=$(($i + 1))
parsed[$server_name]=$i
parsed[$i]=$server_name
fi
if [ -n "${parsed[$i.$location]}" ]
then
echo "The web address $url is already in use."
exit 1
fi
local j=${parsed[$i.length]}
if [ -z "$j" ]
then
j=0
fi
parsed[$i.length]=$(($j + 1))
parsed[$i.$location]=$j
parsed[$i.$j.location]=$location
parsed[$i.$j.port]=$port
}
function remove () {
local i=${parsed[$server_name]}
local j=${parsed[$i.$location]}
unset "parsed[$i.$j.location]"
if [ ${parsed[$i.length]} -eq 1 ]
then
unset "parsed[$i]"
fi
}
function load () {
config=$(cat $file \
| tr -d '\n\r' \
| tr -s ' ')
}
function store () {
echo -e "$config" > $file
}
file='/etc/nginx/sites-available/tunnel.seapip.com'
name=$(get_name)
[[ "$name" =~ \/?([^\/]*)\/?(.*)\/? ]]
server_name=${BASH_REMATCH[1]}
location="/${BASH_REMATCH[2]}"
if [ "$server_name" == 'localhost' ]
then
server_name=$(rand)
fi
server_name+=".seapip.com"
port=$(get_port)
url="https://$server_name"
if [ ${#location} -gt 1 ]
then
url+="$location/"
fi
load && parse && add && stringify && store
trap "load && parse && remove && stringify && store" EXIT
echo "$url"
read -r -d '' _ </dev/tty
@seahorsepip
Copy link
Author

seahorsepip commented Jul 14, 2023

I would highly recommend to not remove authentication 😅

Instead you can setup authentication with an ssh key instead of password so no input is required and if you don't want to pass a username either, you can try to use a user called "root" since most ssh clients will use that by default. But please make sure to use strong ssh key authentication!

(Serveo probably is a custom ssh server, it could be done with this script and a root user with no password or key and all permissions restricted but I wouldn't recommend making your server publicly available)

@ekawahyu
Copy link

Well, user tunnel in my case, is set as passwordless and no PAM. Correct me if I am wrong, but with the ForceCommand set for user tunnel, there is no chance it can get into the shell, right? Unless there is other means of getting into the shell that I don't know about.

@seahorsepip
Copy link
Author

Well, user tunnel in my case, is set as passwordless and no PAM. Correct me if I am wrong, but with the ForceCommand set for user tunnel, there is no chance it can get into the shell, right? Unless there is other means of getting into the shell that I don't know about.

There have been some ways to get around it and I think you might still be able connect with sftp, so you'll need to limit file access correctly etc. I would not rely on force command to make things secure.

@virtueer
Copy link

Thanks for this pretty script. I was wondering is there any other way to get ssh args without reading auth.log file?

@seahorsepip
Copy link
Author

Thanks for this pretty script. I was wondering is there any other way to get ssh args without reading auth.log file?

No idea, I'm far from knowledgeable with ssh scripting, this implementation was done based on throwing together ideas from multiple sources 😅

If you do find a way, let me know, the current log file approach is rather ugly and requires giving acces to the file :/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment