Remote support Self Service tool for users off of the corporate network.

signetmac
Contributor

Hi JAMF-ers.

I posted the following back in May in an old feature request thread regarding having remote access to machines through Casper even when off network. I'm not certain that anyone has looked at the thread since, and it occurs to me that a greater range of people might be interested who have not found that thread... and really, I'm looking for help making this a better service and would love some input from people more talented with SSH and bash scripting in general than am I. There are certainly dozens of ways I've overlooked to make this service more secure and better. Please give me suggestions if you have any.

The following policy has come in very handy on more than a few occasions when TeamViewer was connecting, but just giving me a black window, and users were having difficulty with VPN configurations for one reason or another.

It involves a go-between host, available to the internet (on your DMZ, like mine, but it will even work on your home computer if you set up NAT'ing on your router) with SSH access. Optimally you've created a chrooted, non-shell account on that host for optimal security, and generate a public/private ssh key for it. (I'll call the account 'Ernestine' for purposes of sharing with you all, after Lily Tomlin's Laugh-In switch board operator) You don't need the private on your go-between host, only the public key. The private key is referenced in both the Self Service Policy, and in the administrators app.

It also assumes that you have a JSS proxy on your DMZ, so that people can execute Self Service Policies from home. If you don't, find some way to roll the following up into an executable on the end user's system. The "Remote Support" Policy goes like this:

  1. A package places Ernestine's private key in a /private/tmp/ folder.
  2. Runs the following script:
#!/bin/bash
######################################
# Script generates random number to use as password and seed
# unused forward ports in the unregistered range for RemoteARD.app
######################################
# Customize for your location

adminName=""     # add the name of the administrator account you use on your end users' systems
adminAddy=""     # add the email address to either a support admin, or a Distribution List
telcoAddy=""     # add the URL to your DMZ'd server acting as the switchboard operator
operatorName=""  # the go-between account. For my example, 'ernestine'

######################################
# Get information for email

useris=$(ls -l /dev/console | awk -F"1 " '{print $2}' | awk -F"  " '{print $1}')
hname=$(hostname)

######################################
# Generates potential integer for use both as password and seeding forwarding ports

function genPorts ()
{
 passPort=`jot -r 1 10000 14409`
 vport=$((5900 + $passPort))
 sport=$((22 + $passPort))
}

######################################
# Given a port number, returns "" if available, "False" if not

function isportavail()
{
 local portsiu=`netstat -nat | egrep 'udp|tcp' | awk '{print $4}' | egrep -o 'w+$' | sort -n | uniq`
 local isitinuse=`echo "$portsiu" | egrep -o "$1" 2> /dev/null`
if [ -z "$isitinuse" ];
 then
  echo ""
 else
  echo "False"
 fi
}

genPorts

######################################
# Keep generating seeds until a range of unused ports are found

until [ -z "$(isportavail "$vport")" ] && [ -z "$(isportavail "$sport")" ]
do
 genPorts
done

######################################
# Send email to administrator with connection specifics

/usr/bin/mail -s "User is requesting assistance" $adminAddy<<EOF
User, $useris on system, $hname 
is requesting remote assistance with their computer. 

Launch the RemoteSupportAdmin app, and type: ${passPort} when prompted.

To connect manually over ssh, use:  ssh $adminName@127.0.0.1 -p ${sport}
To connect manually over ARD, use:  open -a /Applications/Remote Desktop.app vnc://$adminName@127.0.0.1:${vport}
EOF

######################################
# Turn on services

/usr/sbin/systemsetup -setremotelogin on &  > /dev/null 2>&1

/System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -configure -allowAccessFor -allUsers -privs -all -restart -agent -menu

######################################
# Set up tunnel to go-between server

nohup /usr/bin/ssh -f -N -T -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -R $vport:localhost:5900 -R $sport:localhost:22 $operatorName@$telcoAddy -i /tmp/Resources/id_rsa & > /dev/null 2>&1

sleep 20

That's it. The end user fires off this policy. It chooses a random port to seed the port forwarding, it sets up port forwarded services to the go-between host, and it emails the admin with the key random seed number that s/he then uses with an app on the support end to set up a port forwarding session with the same go-between that forwards the active ports from the end user's system to the admin's system.

The admin's application is created in Xcode as an Applescript GUI application called RemoteSupportAdmin.app. Its RemoteSupportAdmin.app/Contents/Resources directory contains two important files, Ernestine's private key, and connect.sh, which handles the bulk of the work.

The Applescript copies the app's own Resources directory to a local tmp folder, prompts the admin for the random seed from the email, and feeds that to the script, now located under /private/tmp/Resources.

The Applescript:

property passPort : ""

set path_to_me to the POSIX path of (path to me)
do shell script "cp -R " & path_to_me & "Contents/Resources /tmp/" with administrator privileges

display dialog "Enter the client's connection number:" default answer passPort
set the passPort to text returned of the result

do shell script "sh /tmp/Resources/connect.sh " & passPort with administrator privileges
end

The connect.sh script:

#!/bin/bash
######################################
# Customize for your location

adminName=""     # the name of the administrator account you use on your end users' systems
telcoAddy=""     # add the URL to your DMZ'd server acting as the switchboard operator 
operatorName=""  # the go-between account. For my example, 'ernestine'

######################################
# Do math to arrive at the proper ports from the random seed handed over by the applescript

vport=$((5900 + $1))
sport=$((22 + $1))

######################################
# Forward the end user's forwarded ports to yourself

nohup /usr/bin/ssh -C -c blowfish -N -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -L $vport:localhost:$vport -L $sport:localhost:$sport $operatorName@$telcoAddy -i /tmp/Resources/id_rsa & > /dev/null 2>&1
sleep 3

######################################
# Launch the appropriate programs to take advantage of the forwarded ports
open -a /Applications/Remote Desktop.app vnc://$adminName@127.0.0.1:$vport
echo "ssh $adminName@localhost -p ${sport} -o StrictHostKeyChecking=no" > /tmp/rsadmin.command; chmod +x /tmp/rsadmin.command; open /tmp/rsadmin.command
exit

Room for improvement: Again, some things are still rough.

  1. I took the shortcut of incorporating a pkg on the Policy end to install ernestine's private key, but I suppose you could avoid that by writing out in the connection bash script the key to a file, and setting permissions.
  2. I experienced a lot of frustration referring to the RemoteSupportAdmin app's internally contained Resources directory, taking into account the fact that it could be run from different paths on the file system depending on the admin's preferences, so I took the easy way out and copied the Resources directory to a predictable path.
  3. I'm sure someone out there knows how to make the ssh connections more secure and reliable. Please, by all means pitch in.

Thank you all you wonderful people for any contributions in advance.

Mac

6 REPLIES 6

jarradyuhas
Contributor

As the creator of the feature request, thank you and I really hope that JAMF takes a look at this script and finds a way to incorporate it into their system.

mm2270
Legendary Contributor III

@signetmac Although I had seen this before when you posted it, just wanted to say nice work on all this, since its popped up to the top of the forum.

Also, regarding #2, if you used something like Platypus to build your app instead of strictly in Applescript, its pretty easy to refer to the Resources directory. Platypus built apps run their scripts from the Resources directory, so you can get the working path of the script and use that as a variable to reference other files in the same path no matter where its being run from. I've built several small single purpose apps with Platypus that do this, like using an embedded version of cocoaDialog for GUI elements for example. It might be possible to do the same thing with Applescript apps, but I've never looked that far into it.

signetmac
Contributor

@mm2270 @jarradyuhas Thank you both for your supportive comments. I will play with your suggestion regarding Platypus, mm. Haven't used it in years. Good feedback!

signetmac
Contributor

I've done some work with this again. El Cap made the email function fail, and rather than find a way to rebuild that, I decided to scrap it for a more TeamViewer-esque experience.

On the JSS side, the "Help Desk Helper" policy installs three files:
e7273b1ce2964799a1d158e63cac94fd

remotetemp.plist is a standard user. The end user gets to choose during the session whether or not to elevate its privileges, and it gets torn down after the session ends. Very much like TeamViewer... you cannot admin without the user's cooperation. The id_rsa is necessary for the port forwarding, and the graphic is for use with jamfhelper. (the code below assumes you already have a 'corplogo.png in the same place, as we do)

Here is the script as it currently runs after the package installation:

#!/bin/bash
######################################
# Script generates random number to use as password and seed
# unused forward ports in the unregistered range for RemoteARD.app
######################################
# Variables

declare -i passport
go_between=  # Set this variable to the URL of the host that acts as the relay
kickstart="/System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart"
jhelper="/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfhelper"

######################################
# Functions
######################################
# Set a trap to clean up after the fact

cleanup() {

  # The /tmp/hdh and the remotetemp user were both packaged and installed prior to this script running
  # as part of the overall policy. The script also generates a pp file under /tmp with the passport number
  rm -Rf /tmp/hdh
  rm /tmp/pp
  rm "/private/var/db/dslocal/nodes/Default/users/remotetemp.plist"
  mv /Library/Preferences/com.apple.RemoteManagement.plist_bak /Library/Preferences/com.apple.RemoteManagement.plist
  if [[ $ard_was_off == True ]]; then
    rm "/Library/Application Support/Apple/Remote Desktop/RemoteManagement.launchd"
  fi

  ssh_portf=($(ps -ax | grep [s]sh 
    | grep portf 
    | awk '{print $1}'))
  for ssh_proc in "$ssh_portf"; do
    kill $ssh_proc
  done

}

######################################
# Generates potential integer for use both as password and seeding forwarding ports

seed_port() {
  passPort=`jot -r 1 10000 14409`
  vport=$((5900 + $passPort))
  sport=$((22 + $passPort))
}

######################################
# Given a port number, returns "" if available, "False" if not

isportavail() {
  local portsiu=$(netstat -nat 
    | egrep 'udp|tcp' 
    | awk '{print $4}' 
    | egrep -o 'w+$' 
    | sort -n | uniq)
  local isitinuse=$(echo "$portsiu" 
    | egrep -o "$1" 2> /dev/null)
  if [ -z "$isitinuse" ]; then
    echo ""
  else
    echo "False"
  fi

}

######################################
# Keep generating seeds until a range of unused ports are found

gen_ports() {
  seed_port

  until [ -z "$(isportavail "$vport")" ] && [ -z "$(isportavail "$sport")" ]
  do
    seed_port
  done
  echo "$passPort" > /tmp/pp
}

######################################
# Turn on services

turn_on_svcs() {
  /usr/sbin/systemsetup -setremotelogin on &  > /dev/null 2>&1
  if [[ ! $(/usr/bin/dscl . list /Groups | grep com.apple.access_ssh-disabled) ]]; then
    if [[ ! $(/usr/bin/dscl . read /Groups/com.apple.access_ssh | grep remotetemp) ]]; then
      /usr/bin/dscl . -append /Groups/com.apple.access_ssh GroupMembership remotetemp
    fi
  fi

  # Test ARDs existing state before mucking about, so we can set things back later.
  if [ ! $(cat "/Library/Application Support/Apple/Remote Desktop/RemoteManagement.launchd" 2> /dev/null) ]; then
    ard_was_off=True
    printf "enabled" > "/Library/Application Support/Apple/Remote Desktop/RemoteManagement.launchd"
  fi

  mv /Library/Preferences/com.apple.RemoteManagement.plist /Library/Preferences/com.apple.RemoteManagement.plist_bak
  $kickstart -configure -allowAccessFor -allUsers -privs -all -restart -agent -menu
  $kickstart -restart -agent 
}

######################################
# Set up tunnel to go-between server

ssh_go_between_client_end() {
  /usr/bin/ssh -NTCf -i /private/tmp/hdh/id_rsa -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no 
  -o ConnectTimeout=6 -R $vport:localhost:5900 -R $sport:localhost:22 portf@$go_between & > /dev/null 2>&1
  disown

  sleep 3
}

######################################
# Messaging to employee - Pop up window with connection specifics

passport_prompt() {
  desc="Read the following number to the person helping you:"
  "$jhelper" -windowType utility -windowPosition lr -title "Temp Remote Access Pass" 
    -description "$desc" -button1 "${passPort}" -alignDescription center 
    -icon "/Library/Application Support/JAMF/bin/LNEIT.png" -iconSize 80
}

######################################
# Messaging to employee - Pop up window with option to end session

permission_prompt() {
  desc="Click 'End' to turn off remote access"
  "$jhelper" -windowType hud -windowPosition lr -title "HDH Controller: ${passPort}" 
    -description "$desc" -button1 "End" -alignDescription center 
    -icon "/Library/Application Support/JAMF/bin/hardatwork.png" -iconSize 80   
}

######################################
# Logic starts here
######################################

trap cleanup INT EXIT

gen_ports
turn_on_svcs
ssh_go_between_client_end
passport_prompt
permission_prompt

exit 0

The end user sees:
e084be0d768d48b8b561f5aa47b5f817
88b3ecaa9a394f0b8298d48b6b4da4e6

The second window appears after the first one is dismissed, and suspends the policy, keeping the ssh reverse tunnel open and giving the end user the ability to terminate the session at will.

On the admin's side, my code no longer involves a GUI app explicitly for the purposes of managing remote connections. Instead, it is a routine in a larger app that I am using for aggregating information about the person and the person's computer with whom/which I'm interacting. The ability to negotiate a remote connection using the passport variable is one of many options offered on the menu of the admin app:

  Contact Info:
           Real Name:  Apple Test
           Job Title:  QA Instance
        Phone Number:  415.555.1212
       Email Address:  Apple.Test@mycompany.com
   City [ST] Country:  San Francisco CA US

      JSS Info:
       Computer Name:  LNM_Loaner_2
       Last Check-in:  1d:22h:12m:22s ago
      Computer Model:  13-inch_MacBook_Pro_(2011)
          OS Version:  10.10.4
          IP Address:  10.90.4.84
           AD Status:  Not_Bound
     HD Percent Full:  40
           FV Status:  Encrypted

      AD Stats:
     Account expires:  Never
    Password expires:  in 76 days
      Lockout status:  locked out

  e] email user        u] unlock account
  c] call user         p] password reset
  g] show groups       j] open computers in JSS
  r] remote into host  t] ticket creation

  d] data refresh      n] new user
  q] quit

  How may I be of service:

Although I'm not yet prepared to post the full application (will post to bitbucket as soon as I'm satisfied with it) Here is the routine that it runs when someone either types r in the interactive menu, or simply calls the program with hdh -r [passport number]:

hdh_remote() {
    local unqName="$(date +%s)"
    local vport=$((5900 + $1))
    local sport=$((22 + $1))
    local go_between=       # URL to host acting as the relay
    mkdir -p /tmp/hdh

    /usr/bin/ssh -qNTCf -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no 
      -L $vport:localhost:$vport -L $sport:localhost:$sport portf@$go_between 
      -i "/Library/Application Support/JAMF/bin/hdh_rsa" & > /dev/null 2>&1
    disown
    sleep 3

  open vnc://remotetemp@127.0.0.1:$vport
  echo "ssh remotetemp@localhost -p ${sport} -o StrictHostKeyChecking=no" 
  > /tmp/hdh/rsadmin_$unqName.command

  chmod +x /tmp/hdh/rsadmin_$unqName.command & > /dev/null 2>&1
  open /tmp/hdh/rsadmin_$unqName.command & > /dev/null 2>&1
}

NOTE: the $go_between variable in the above routine was set in the parent script. You'll want to define that variable on your own. The id_rsa referenced in the routine is the same file as is installed on the client machine. For admins running the hdh program, I've just installed it under /Library/Application Support/JAMF/bin

EDIT: On Southwest in-flight wifi right now and it helped me take care of some inconsistency. This script was occasionally erroring and on a hunch i added -o ConnectTimeout=6 to the ssh command on my end ( I should add it on the client end too ) to keep the initial port forward from timing out before a successful connection on low bandwidth networks. Seemed to help reliability a great deal.

spyguitar
New Contributor

This looks great. I'm considering implementing this for our MSP clients - most of them are connected to us via VPN, but several are behind home routers, etc. Any further progress on v.2, or should I go forward with your original work?

Happy to test and report any issues - we're alpha testing in-house for the next month or so before rolling anything out to production.

mattkinabrew
New Contributor II

Hi. I'm sorry if I missed it, but did you end up posting the full application?