Skip to content

Instantly share code, notes, and snippets.

@johnhpatton
Created April 1, 2020 13:05
Show Gist options
  • Save johnhpatton/448d5e387bcbefca7a53b9a91d7c38af to your computer and use it in GitHub Desktop.
Save johnhpatton/448d5e387bcbefca7a53b9a91d7c38af to your computer and use it in GitHub Desktop.
#!/bin/bash
# Set to user id for access:
OPENCONNECT_USER="YOUR_VPN_USER_ID"
# Set to server without scheme:
OPENCONNECT_HOST="YOUR_VPN_HOST_NAME"
# This script works with globalprotect and anyconnect.
# Set to protocol, either "gp" or "anyconnect".
OPENCONNECT_PROTOCOL="gp"
# PREREQUISITES:
# - openconnect v8.0 or higher is required for GlobalProtect.
# - admin access to the host running the script.
#
# INSTRUCTIONS
# 1. Update the OPENCONNECT_USER, OPENCONNECT_HOST, and OPENCONNECT_PROTOCOL
# variables above.
#
# 2. Place script under: /usr/sbin/openconnect.ctl
#
# 3. set the following aliases:
#
# alias vpnconnect='sudo /usr/sbin/openconnect.ctl -a start vpn'
# alias vpndisconnect='sudo /usr/sbin/openconnect.ctl -a stop vpn'
#
# 4. get the group for your user and add the following to /etc/sudoers.d/vpn:
#
# Cmnd_Alias OPENCONNECT_CMDS = /usr/sbin/openconnect.ctl
# %YOUR_GROUP_HERE ALL=(root) NOPASSWD: OPENCONNECT_CMDS
#
# NOTE: replace "YOUR_GROUP_HERE" in the above line with the primary group
# for your account.
#
# 5. logout/back in, run vpnconnect to connect.
# bin folder, do not dereference symlinks to maintain clean structures
_CTL=${0##*/}
CTL_BIN="$(cd "$(dirname -- "$0")" && pwd)"
# absolute path to control script and capture args in global var
_CMD=${CTL_BIN}/${_CTL}
_ARGS=$*
# Set INTERACTIVE to 1 if interactive, otherwise 0
INTERACTIVE=$(( ! $(expr index "$-" i) == 0 ))
OPENCONNECT_PID="/var/run/openconnect.pid"
OPENCONNECT_CSD_POST_SCRIPT="${CTL_BIN}/csd-post.sh"
CSD_POST_URL="https://gitlab.com/openconnect/openconnect/raw/master/trojans/csd-post.sh"
# Needed for GlobalProtect:
OPENCONNECT_HIP_REPORT_POST_SCRIPT="${CTL_BIN}/hipreport.sh"
HIP_REPORT_POST_URL="https://gitlab.com/openconnect/openconnect/raw/master/trojans/hipreport.sh"
SOCAT_PID="/var/run/socat.pid"
dmesg | grep -i hypervisor &>/dev/null && MACHINE_TYPE="vm" || MACHINE_TYPE="host"
if [ "${MACHINE_TYPE}" == "vm" ]; then
# use last NIC for SOCAT routing on VM if two NICs are provisioned
SOCAT_DEVICE="$(cat /proc/net/dev | grep enp | awk '{print substr($1, 1, length($1)-1)}' | tail -1)"
else
# use last NIC for SOCAT routing on host
SOCAT_DEVICE="$(cat /proc/net/dev | grep -E '(eth|en|wlp)' | awk '{print substr($1, 1, length($1)-1)}' | tail -1)"
fi
if (( $EUID != 0 )); then
echo "ERROR: Run as root or with sudo."
exit 1
fi
###################################################################
## ##
## L O W L E V E L F U N C T I O N S ##
## ##
###################################################################
# DATETIME "macro"
# Date/Time Stamp
# Returns now in format: YYYYMMDD-HH:MM:SS
DATETIME() {
date "+%Y%m%d-%T"
}
# trim()
# Trims white space on passed in string
trim() {
set -f
set -- $*
printf "%s\\n" "$*"
set +f
}
###################################################################
## ##
## U S E R M E S S A G E F U N C T I O N S ##
## ##
###################################################################
# Interactive or not?
[ -t "0" ] && INTERACTIVE=1 || INTERACTIVE=0
# If interactive, set output colors:
if (( INTERACTIVE )); then
# CONSTANTS
# foreground colors, use with echo -e
NC='\e[0m' # No Color, reset
# normal; bold; high intensity; bold + high intensity
BLACK='\e[0;30m'; BBLACK='\e[1;30m'; IBLACK='\e[0;90m'; BIBLACK='\e[1;90m';
RED='\e[0;31m'; BRED='\e[1;31m'; IRED='\e[0;91m'; BIRED='\e[1;91m';
GREEN='\e[0;32m'; BGREEN='\e[1;32m'; IGREEN='\e[0;92m'; BIGREEN='\e[1;92m';
YELLOW='\e[0;33m'; BYELLOW='\e[1;33m'; IYELLOW='\e[0;93m'; BIYELLOW='\e[1;93m';
BLUE='\e[0;34m'; BBLUE='\e[1;34m'; IBLUE='\e[0;94m'; BIBLUE='\e[1;94m';
PURPLE='\e[0;35m'; BPURPLE='\e[1;35m'; IPURPLE='\e[0;95m'; BIPURPLE='\e[1;95m';
CYAN='\e[0;36m'; BCYAN='\e[1;36m'; ICYAN='\e[0;96m'; BICYAN='\e[1;96m';
WHITE='\e[0;37m'; BWHITE='\e[1;37m'; IWHITE='\e[0;97m'; BIWHITE='\e[1;97m';
fi
# loggers
# adjust colors for terminal display needs
logdebug() { (( DEBUG )) && logmsg "${WHITE}DEBUG: ${NC}$1${NC}"; }
loginfo() { logmsg "${BICYAN} INFO: ${NC}$1${NC}"; }
logok() { logmsg "${BIGREEN} OK: ${NC}$1${NC}"; }
logwarn() { logmsg "${BIYELLOW} WARN: ${NC}$1${NC}"; }
logcrit() { logmsg "${BIRED} CRIT: ${NC}$1${NC}"; }
logmsg() { [[ ${FUNCNAME[1]} =~ log.* ]] && echo -e "$1" || loginfo "$1"; }
# is_running
# returns: 0 (success, running)
# 1 (failure, not running)
is_running() {
local pidfile="$1"
local retval=0
if [ ! -f "${pidfile}" ] || ! kill -0 $(<"${pidfile}") &>/dev/null; then
[ -f "${pidfile}" ] && rm -f "${pidfile}"
retval=1
fi
return $((retval))
}
###################################################################
## ##
## C O N T R O L F U N C T I O N S ##
## ##
###################################################################
# display_usage
# Displays a simple usage message
display_usage() {
cat <<USAGE_MESSAGE
openconnect.ctl options:
$_CTL -a {ACTION} [-p] [-h]
For full usage help, use: $_CTL -a help
USAGE_MESSAGE
}
# display_help
# Displays a simple usage message
display_help() {
cat <<HELP_MESSAGE
openconnect.ctl options:
$_CTL -a {ACTION} [-p] [-h]
-a ACTION options:
start - starts the VPN
stop - shuts the VPN down
restart - stops/starts the VPN
reconnect - reinititializes the VPN connection
status - prints the status of the VPN
help - this message
-p: Enable a forward proxy listener for host routing
over the VPN connection
NOTE: optional
-h: prints basic usage message
HELP_MESSAGE
}
openconnect_start_opts() {
local openconnect_opts="--background "
local csd_script=""
local csd_source=""
(( DEBUG )) && openconnect_opts+="--verbose "
openconnect_opts+="--protocol=${OPENCONNECT_PROTOCOL} "
openconnect_opts+="--user=${OPENCONNECT_USER} "
openconnect_opts+="--pid-file=${OPENCONNECT_PID} "
if [ "${OPENCONNECT_PROTOCOL}" == "anyconnect" ]; then
csd_script="${OPENCONNECT_CSD_POST_SCRIPT}"
csd_source="${CSD_POST_URL}"
openconnect_opts+="--csd-user=${OPENCONNECT_USER} "
elif [ "${OPENCONNECT_PROTOCOL}" == "gp" ]; then
csd_script="${OPENCONNECT_HIP_REPORT_POST_SCRIPT}"
csd_source="${HIP_REPORT_POST_URL}"
fi
if [ -n "${csd_script}" ]; then
if [ ! -f "${csd_script}" ]; then
echo "WARN: ${csd_script} not found, attempting to download..."
if ! curl -kLs --fail -o ${csd_script} "${csd_source}"; then
logcrit "Unable to download the script from:\n\n\t\t${csd_source}\n\n\tCheck your internet connection and verify the resource is still available."
exit 1
elif [ ! -f "${csd_script}" ]; then
logcrit "Unable to write to ${csd_script}"
exit 1
fi
chmod +x ${csd_script}
fi
openconnect_opts+="--csd-wrapper=${csd_script} "
fi
openconnect_opts+="--os=win "
openconnect_opts+=" ${OPENCONNECT_HOST}"
echo "${openconnect_opts}"
}
status() {
is_running "${OPENCONNECT_PID}" && loginfo "VPN is running." || loginfo "VPN is stopped."
is_running "${SOCAT_PID}" && loginfo "DNS proxy is running." || loginfo "DNS proxy is stopped."
}
start_dns_proxy() {
loginfo "Getting DNS bind IP from device: ${SOCAT_DEVICE}."
BIND_ADDRESS=$( ip -f inet addr show ${SOCAT_DEVICE} | sed -En -e 's/.*inet ([0-9.]+).*/\1/p' )
[ -z "${BIND_ADDRESS}" ] && { logcrit "Could not determine bind IP from ${SOCAT_DEVICE}, unable to configure DNS forwarding"; return 1; }
NAMESERVER=$( cat /etc/resolv.conf | grep nameserver | head -1 | awk 'NF{ print $NF }' )
loginfo "Setting up host only DNS proxy listener..."
socat -T10 UDP4-LISTEN:53,fork,bind=${BIND_ADDRESS},range=192.168.56.1/32 UDP4:${NAMESERVER}:53 &>/dev/null &
echo $! > "${SOCAT_PID}"
loginfo "sshuttle can be re-enabled on host."
}
start_vpn() {
local forwarder="$2"
is_running "${OPENCONNECT_PID}" && { loginfo "VPN already running."; return 0; }
logdebug "$(openconnect_start_opts)"
openconnect $(openconnect_start_opts)
sleep 1
if is_running "${OPENCONNECT_PID}"; then
if (( DNS_PROXY )); then
start_socat || logcrit "DNS proxy did not start correctly, check logs."
fi
loginfo "VPN is up and running."
else
logcrit "VPN not started, check logs."
exit 127
fi
}
stop_dns_proxy() {
if is_running "${SOCAT_PID}"; then
loginfo "Shutting down DNS proxy."
kill $(<"${SOCAT_PID}")
rm -f "${SOCAT_PID}"
loginfo "DNS proxy stopped, disable sshuttle routing on host."
fi
}
stop_vpn() {
if is_running "${OPENCONNECT_PID}"; then
loginfo "Shutting down VPN down."
kill -SIGINT $(<"${OPENCONNECT_PID}")
loginfo "Removing VPN routes."
ip r | grep ppp0 && ip r | grep default | head -n1 | xargs sudo ip r del
sleep 1
loginfo "VPN is stopped and routes are reset."
else
loginfo "VPN not running"
fi
}
################################################################################
# #
# M A I N P R O G R A M #
# #
################################################################################
ACTION="usage"
DNS_PROXY=0
VPN=0
DEBUG=0
init_opts() {
local short_opts="a:dh"
local long_opts="action:,help"
local tmp;
local instance_config_set=0
OPTIONS=$( getopt -o "$short_opts" --long "$long_opts" -n "$_CTL" -- "$@" )
if (( $? )); then
logcrit "Failed parsing options ${OPTIONS}."
exit 1
fi
eval set -- "$OPTIONS"
# extract options and their arguments into variables.
while true ; do
case "$1" in
-a|--action)
case "$2" in
"") shift 2 ;;
start|stop|restart|reconnect|status|help)
ACTION=$2
shift 2
;;
-- ) break ;;
*) shift 2 ;;
esac
;;
-d|--debug)
DEBUG=1
;;
-h|--help)
ACTION=usage
;;
-- ) shift; break ;;
esac
done
for svc in "$@"; do
case "$svc" in
vpn)
VPN=1
shift; break ;;
dnsproxy)
[ "${MACHINE_TYPE}" != "vm" ] && DNS_PROXY=1 || logwarn "unable to set up proxy, this only works for VMs."
shift; break ;;
*)
logcrit "Invalid service: $svc"
ACTION="usage"
shift; break ;;
esac
done
}
init_opts $@
case "$ACTION" in
start)
if (( VPN )) || (( DNS_PROXY )); then
(( VPN )) && start_vpn
(( DNS_PROXY )) && start_dns_proxy
else
display_usage
fi
;;
stop)
if (( VPN )) || (( DNS_PROXY )); then
(( VPN )) && stop_vpn
(( DNS_PROXY )) && stop_dns_proxy
else
display_usage
fi
;;
restart)
if (( VPN )) || (( DNS_PROXY )); then
(( DNS_PROXY )) && stop_dns
(( VPN )) && stop_vpn && start_vpn
(( DNS_PROXY )) && start_dns_proxy
else
display_usage
fi
;;
status)
status
;;
help|usage)
display_${ACTION}
;;
*)
display_usage
exit 1
;;
esac
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment