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

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