#!/bin/sh # Opens SSH reverse tunnels to a sish edge and reconnects forever. See README.md. set -eu set -f # no globbing: routes may contain wildcards (*.example.com) die() { echo "sish-client: $*" >&2; exit 64; } : "${SISH_HOST:?is required}" "${SISH_HOST_KEY:?is required}" "${SISH_ROUTES:?is required}" SNI_PROXY=${SISH_SNI_PROXY:-true} PROXY_PROTOCOL=${SISH_PROXY_PROTOCOL:-2} KEY=${SISH_KEY_FILE:-/secrets/id_ed25519} case $SNI_PROXY in true | false) ;; *) die "SISH_SNI_PROXY must be true or false" ;; esac case $PROXY_PROTOCOL in 1 | 2 | off) ;; *) die "SISH_PROXY_PROTOCOL must be 1, 2 or off" ;; esac [ -r "$KEY" ] || die "cannot read private key $KEY" # Pin the edge host key: the only entry ssh will trust. A newline would allow # extra known_hosts lines (e.g. a wildcard entry), so the key must be one line. case $SISH_HOST_KEY in *' '*) die "SISH_HOST_KEY must be a single line" ;; esac KNOWN_HOSTS=$(mktemp) # private 0600 file with a random name, safe in a shared /tmp printf 'sish-edge %s\n' "$SISH_HOST_KEY" > "$KNOWN_HOSTS" # host:bind-port=target:port -> -R host:bind-port:target:port # Each route stays a single argument to -R, so it cannot inject ssh options. forwards="" for r in $(printf '%s' "$SISH_ROUTES" | tr , ' '); do case $r in *=*) ;; *) die "bad route '$r', expected host:port=target:port" ;; esac forwards="$forwards -R ${r%%=*}:${r#*=}" done [ -n "$forwards" ] || die "SISH_ROUTES contains no routes" # sish session options, sent as the remote command opts="" if [ "$SNI_PROXY" = true ]; then opts="sni-proxy=true"; fi if [ "$PROXY_PROTOCOL" != off ]; then opts="$opts proxy-protocol=$PROXY_PROTOCOL"; fi # -F /dev/null: ignore all ssh_config files, every option is set here. # The server can only open channels for the forwards requested above; agent and # X11 forwarding are off by default. while :; do # shellcheck disable=SC2086 # $forwards and $opts are split on purpose ssh -F /dev/null -T -p "${SISH_PORT:-2222}" -i "$KEY" \ -o BatchMode=yes -o IdentitiesOnly=yes -o ExitOnForwardFailure=yes \ -o ServerAliveInterval=15 -o ServerAliveCountMax=3 \ -o HostKeyAlias=sish-edge -o StrictHostKeyChecking=yes \ -o UserKnownHostsFile="$KNOWN_HOSTS" -o GlobalKnownHostsFile=/dev/null \ $forwards "connector@$SISH_HOST" $opts \ || echo "sish-client: ssh exited ($?), reconnecting in 5s" >&2 sleep 5 done