Revision $Id: webjob-pad-ssh-tunnel.base,v 1.4 2005/07/29 21:42:24 klm Exp $ Purpose This recipe demonstrates how establish a reverse tunnel using, OpenSSH, WebJob, and PaD. Tunnel setup is driven with WebJob and secured through OpenSSH. Dynamic authentication, through PaD, eliminates the need to maintain extra SSH credentials on the client. Motivation The motivation for this recipe was to find a way to establish and control SSH access to hosts within a network that explicitly or implicitly blocks inbound TCP connections. At the same time, this mechanism must not expose either endpoint to undue or prolonged risk. Requirements Cooking with this recipe requires an operational WebJob server. If you do not have one of those, refer to the instructions provided in the README.INSTALL file that comes with the source distribution. The latest source distribution is available here: http://sourceforge.net/project/showfiles.php?group_id=40788 Each client must be running UNIX (or Cygwin on Win2K) and have basic system utilities, OpenSSH, and WebJob (1.5.0 or higher) installed. The server must be running UNIX and have basic system utilities, PaD utilities, OpenSSH, and WebJob installed. The commands presented throughout this recipe were designed to be executed within a Bourne shell (i.e., sh or bash). Solution The solution is to periodically download webjob_tunnel_id.pad and execute the desired OpenSSH tunnel command. The following steps describe how to implement this solution. 1. Create a locked user account called 'tunnel' on the WebJob server. This account must be locked to prevent unnecessary logins and password authentication. All authentication for this recipe will be done using SSH keys. Before you begin, set the following environment variables in your shell as they will be referenced throughout the remainder of the recipe. # TUNNEL_ID="10001" # TUNNEL_USER="tunnel" # TUNNEL_GROUP="tunnel" # TUNNEL_HOME="/usr/home/${TUNNEL_USER}" FreeBSD: # pw groupadd ${TUNNEL_GROUP} -g ${TUNNEL_ID} # pw useradd ${TUNNEL_USER} -u ${TUNNEL_ID} -g ${TUNNEL_GROUP} -d ${TUNNEL_HOME} -s /bin/sh -c "SSH Tunnel User" Linux and Solaris: # groupadd -g ${TUNNEL_ID} ${TUNNEL_GROUP} # useradd -u ${TUNNEL_ID} -g ${TUNNEL_GROUP} -d ${TUNNEL_HOME} -s /bin/sh -c "SSH Tunnel User" ${TUNNEL_USER} Create a root-owned, home directory for the tunnel user. This directory should be root-owned so that the tunnel user, alone, does not have sufficient privileges to create or modify dot files. # mkdir -p ${TUNNEL_HOME} # find ${TUNNEL_HOME} -type d -exec chmod 750 {} \; # find ${TUNNEL_HOME} -type f -exec chmod 640 {} \; # chown -R 0:${TUNNEL_GROUP} ${TUNNEL_HOME} 2. Create an SSH key pair for the tunnel user on the WebJob server. Then, add the public key along with any options you desire to tunnel's authorized_keys file. The key you create in this step will not be protected with a passphrase. Therefore, we recommend that you restrict its use by applying various key options. This is important because the private key will be vulnerable to capture on the client during job execution. Details about the various key options can be found in the "AUTHORIZED_KEYS FILE FORMAT" section of the sshd(8) man page. # KEY_NAME="webjob_tunnel_id" # KEY_TYPE="rsa" # ALLOWED_HOSTS="1.1.1.1" # KEY_OPTIONS="from=\"${ALLOWED_HOSTS}\",command=\"sleep 60\",no-X11-forwarding,no-agent-forwarding,no-pty" # mkdir -p ${TUNNEL_HOME}/.ssh # ssh-keygen -q -t ${KEY_TYPE} -C ${KEY_NAME} -f ${TUNNEL_HOME}/.ssh/${KEY_NAME} -N "" # echo ${KEY_OPTIONS} `cat ${TUNNEL_HOME}/.ssh/${KEY_NAME}.pub` > ${TUNNEL_HOME}/.ssh/authorized_keys # find ${TUNNEL_HOME}/.ssh -type d -exec chmod 750 {} \; # find ${TUNNEL_HOME}/.ssh -type f -exec chmod 640 {} \; # chown -R 0:${TUNNEL_GROUP} ${TUNNEL_HOME}/.ssh 3. Create a PaD script that contains the private SSH key. Put this script in the appropriate commands directory. Set the WEBJOB_CLIENT and WEBJOB_COMMANDS environment variables as appropriate for your server. Note: If you only want this key to be available to a particular client, don't set WEBJOB_CLIENT to 'common'. Instead, set it to the specific client ID you want to use -- e.g., 'client_0001'. # WEBJOB_CLIENT=common # WEBJOB_COMMANDS=/integrity/profiles/${WEBJOB_CLIENT}/commands # pad-make-script -c ${TUNNEL_HOME}/.ssh/${KEY_NAME} | sed 's/PAD_UMASK-022/PAD_UMASK-077/g;' > ${WEBJOB_COMMANDS}/${KEY_NAME}.pad # chown 0:0 ${WEBJOB_COMMANDS}/${KEY_NAME}.pad # chmod 644 ${WEBJOB_COMMANDS}/${KEY_NAME}.pad Note: It's important that you modify the PaD script's default umask as shown above. If the key file does not have restrictive permissions (e.g., 0600), ssh will reject it, and the tunnel won't be established. More details on PaD, can be found here: http://webjob.sourceforge.net/WebJob/PayloadAndDelivery.shtml 4. Create a cron job on each client that periodically creates a tunnel by way of webjob_tunnel_id.pad. The arguments passed to webjob_tunnel_id.pad should form a valid OpenSSH command. The following examples demonstrate how to establish a short-lived, reverse tunnel. If one or more connections are established through the tunnel within a 60 second window, the tunnel will persist for the duration of the longest connection or 60 seconds -- whichever is longer. Note that the PaD script will replace the %payload token with webjob_tunnel_id prior to executing tunnel. Here's an example reverse tunnel: 0 * * * * ${WEBJOB_HOME=/usr/local/integrity}/bin/webjob -e -f ${WEBJOB_HOME}/etc/upload.cfg webjob_tunnel_id.pad ssh -i %payload -o BatchMode=yes -R 10001:localhost:22 tunnel@1.1.1.1 sleep 60 One problem to watch out for is OpenSSH's host key checking. If the server's key isn't known to the client, OpenSSH's default action will be to ask, and that could cause the job to hang. Basically, there are two ways to avoid this situation: make sure the client's known_hosts file contains the appropriate key or disable StrictHostKeyChecking. As a fallback plan, you can also set WebJob's RunTimeLimit control to abort the job after a specified amount of time. Closing Remarks Using cron to invoke webjob_tunnel_id.pad is not conducive to a centralized management scheme unless crontabs are also being centrally managed (e.g., via WebJob). A better approach would be to periodically (e.g., hourly) run a centrally managed meta script that runs webjob_tunnel_id.pad as a subtask. The SSH keys used in this recipe are dynamic in the sense that they do not persist on the client. However, they are static on the server. A better approach would be to have the server automatically generate a new key for each job that is executed. Another approach would be to have a cron job on the server that periodically generates new keys and expires/removes old keys. Credits This recipe was brought to you by Klayton Monroe. Appendix 1 The following script is a work-in-progress, and it needs a lot more polishing and error handling. Send your patches ;) The following command will extract the webjob_setup_tunnel_account script. sed -e '1,/^--- webjob_setup_tunnel_account ---$/d; /^--- webjob_setup_tunnel_account ---$/,$d' webjob-pad-ssh-tunnel.txt > webjob_setup_tunnel_account --- webjob_setup_tunnel_account --- #!/bin/sh -e ###################################################################### # # $Id: webjob_setup_tunnel_account,v 1.4 2005/07/29 22:00:11 klm Exp $ # ###################################################################### # # Copyright 2004-2004 The WebJob Project, All Rights Reserved. # ###################################################################### # # Purpose: Prepare a WebJob server for SSH tunnel access. # ###################################################################### IFS=' ' PATH=/sbin:/usr/sbin:/usr/local/sbin:/bin:/usr/bin:/usr/local/bin:${WEBJOB_HOME=/usr/local/webjob}/bin PROGRAM=`basename $0` Usage() { echo 1>&2 echo "Usage: ${PROGRAM} [-a address] [-c client-id] [-d webjob-basedir] [-g group] [-h home] [-i id] [-t key-type] [-u user] -s {freebsd|linux|solaris}" 1>&2 echo 1>&2 exit 1 } while getopts "a:c:d:g:h:i:s:t:u:" OPTION ; do case "${OPTION}" in a) ALLOWED_HOSTS="${OPTARG}" ;; c) WEBJOB_CLIENT="${OPTARG}" ;; d) WEBJOB_BASEDIR="${OPTARG}" ;; g) TARGET_GROUP="${OPTARG}" ;; h) TARGET_HOME="${OPTARG}" ;; i) TARGET_ID="${OPTARG}" ;; s) SYSTEM_TYPE="${OPTARG}" ;; t) KEY_TYPE="${OPTARG}" ;; u) TARGET_USER="${OPTARG}" ;; *) Usage ;; esac done if [ ${OPTIND} -le $# ] ; then Usage fi if [ -z "${SYSTEM_TYPE}" ] ; then Usage fi TARGET_ID=${TARGET_ID-873} TARGET_USER=${TARGET_USER-tunnel} TARGET_GROUP=${TARGET_GROUP-tunnel} TARGET_HOME=${TARGET_HOME-/usr/home/${TARGET_USER}} # # Run some conflict tests. # egrep ${TARGET_ID} /etc/passwd > /dev/null 2>&1 ; UID_EXISTS=$? egrep ${TARGET_USER} /etc/passwd > /dev/null 2>&1 ; USER_EXISTS=$? egrep ${TARGET_ID} /etc/group > /dev/null 2>&1 ; GID_EXISTS=$? egrep ${TARGET_GROUP} /etc/group > /dev/null 2>&1 ; GROUP_EXISTS=$? if [ ${UID_EXISTS} -eq 0 -o ${GID_EXISTS} -eq 0 -o ${USER_EXISTS} -eq 0 -o ${GROUP_EXISTS} -eq 0 -o -d ${TARGET_HOME} ] ; then echo "${PROGRAM}: You must remove any uid, gid, user, group, or home dir conflicts before this script will run." 1>&2 exit 2 fi # # Create the tunnel account. # case "${SYSTEM_TYPE}" in freebsd) pw groupadd ${TARGET_GROUP} -g ${TARGET_ID} pw useradd ${TARGET_USER} -u ${TARGET_ID} -g ${TARGET_GROUP} -d ${TARGET_HOME} -s /bin/sh -c "SSH Tunnel User" ;; linux|solaris) groupadd -g ${TARGET_ID} ${TARGET_GROUP} useradd -u ${TARGET_ID} -g ${TARGET_GROUP} -d ${TARGET_HOME} -s /bin/sh -c "SSH Tunnel User" ${TARGET_USER} ;; *) esac # # Create directory structure. # DIRS=" ${TARGET_HOME} ${TARGET_HOME}/.ssh " for DIR in ${DIRS} ; do mkdir -p ${DIR} done # # Create SSH key pair. # KEY_NAME="webjob_${TARGET_USER}_id" KEY_TYPE=${KEY_TYPE-rsa} ALLOWED_HOSTS=${ALLOWED_HOSTS-1.1.1.1} KEY_OPTIONS="from=\"${ALLOWED_HOSTS}\",command=\"sleep 60\",no-X11-forwarding,no-agent-forwarding,no-pty" ssh-keygen -q -t ${KEY_TYPE} -C ${KEY_NAME} -f ${TARGET_HOME}/.ssh/${KEY_NAME} -N "" echo ${KEY_OPTIONS} `cat ${TARGET_HOME}/.ssh/${KEY_NAME}.pub` > ${TARGET_HOME}/.ssh/authorized_keys # # Tidy up perms # find ${TARGET_HOME} -type d -exec chmod 750 {} \; find ${TARGET_HOME} -type f -exec chmod 640 {} \; # # Tidy up ownerships # chown -R 0:${TARGET_GROUP} ${TARGET_HOME} # # Package private SSH key as a PaD file and install in the specified commands directory. # WEBJOB_CLIENT=${WEBJOB_CLIENT-common} WEBJOB_BASEDIR=${WEBJOB_BASEDIR-/var/webjob} WEBJOB_COMMANDS=${WEBJOB_BASEDIR}/profiles/${WEBJOB_CLIENT}/commands pad-make-script -c ${TARGET_HOME}/.ssh/${KEY_NAME} | sed 's/PAD_UMASK-022/PAD_UMASK-077/g;' > ${WEBJOB_COMMANDS}/${KEY_NAME}.pad chown 0:0 ${WEBJOB_COMMANDS}/${KEY_NAME}.pad chmod 644 ${WEBJOB_COMMANDS}/${KEY_NAME}.pad --- webjob_setup_tunnel_account ---