Posted on 07-01-2015 02:29 PM
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:
#!/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.
Thank you all you wonderful people for any contributions in advance.
Mac
Posted on 07-25-2015 01:11 PM
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.
Posted on 07-25-2015 01:28 PM
@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.
Posted on 07-27-2015 12:27 PM
@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!
Posted on 06-12-2016 01:37 PM
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:
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:
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.
Posted on 10-18-2016 06:00 AM
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.
Posted on 02-01-2020 09:14 AM
Hi. I'm sorry if I missed it, but did you end up posting the full application?