Created
April 1, 2020 13:05
-
-
Save johnhpatton/448d5e387bcbefca7a53b9a91d7c38af to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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