Created
July 13, 2023 19:24
-
-
Save johnhpatton/a0864bfc6f95a18875b545173e4f7d61 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 | |
# May be required to avoid SSL certificate validation. | |
# NOTE: manually validate the CA in this case. | |
export INSECURE=${INSECURE:-true} | |
# Set to user id for access: | |
OPENCONNECT_USER="USERNAME" | |
# Set to server without scheme, use what's configured in client config: | |
OPENCONNECT_HOST="HOSTNAME" | |
# Set to VPN group, if needed -- may need to dig through the client | |
# configuration to get this. | |
OPENCONNECT_GROUP="GROUP" | |
# Set to protocol, script works with gp and anyconnect: | |
OPENCONNECT_PROTOCOL="anyconnect" | |
# 1. Update the OPENCONNECT_USER, OPENCONNECT_HOST, and OPENCONNECT_PROTOCOL | |
# variables above. | |
# | |
# 2. Place script under: /usr/local/sbin/openconnect.ctl | |
# | |
# 3. Set the following aliases: | |
# | |
# alias vpnconnect='sudo /usr/local/sbin/openconnect.ctl -a start vpn' | |
# alias vpndisconnect='sudo /usr/local/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/local/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. | |
# | |
# 6. VPN gateway not compliant with "secure" cyphers, lower cypher strength | |
# and enable unsafe renogotiation if experiencing issues. This is not a | |
# configuration that can be corrected on the client side. This configuration | |
# goes in the /etc/ssl/openssl.cnf file. | |
# | |
# In the [openssl_init] section, adjust the ssl_conf setting with the | |
# following setting if needed: | |
# | |
# ssl_conf = ssl_sect | |
# | |
# Add the following to the end of the file: | |
# | |
# [ssl_sect] | |
# system_default = system_default_sect | |
# | |
# [system_default_sect] | |
# Options = UnsafeLegacyRenegotiation | |
# CipherString = DEFAULT:@SECLEVEL=1 | |
# MinProtocol = TLSv1.2 | |
# | |
# Retry vpn connection. | |
# 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 )) | |
SCRIPT_EXIT_CODE=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 | |
function DATETIME() { | |
date "+%Y%m%d-%T" | |
} | |
# trim() | |
# Trims white space on passed in string | |
function 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 | |
function logdebug() { (( DEBUG )) && logmsg "${WHITE}DEBUG: ${NC}$1${NC}"; } | |
function loginfo() { logmsg "${BICYAN} INFO: ${NC}$1${NC}"; } | |
function logok() { logmsg "${BIGREEN} OK: ${NC}$1${NC}"; } | |
function logwarn() { logmsg "${BIYELLOW} WARN: ${NC}$1${NC}"; } | |
function logcrit() { logmsg "${BIRED} CRIT: ${NC}$1${NC}"; } | |
function logmsg() { [[ ${FUNCNAME[1]} =~ log.* ]] && echo -e "$1" || loginfo "$1"; } | |
function stat() { | |
local pidfile="$1" | |
local retval=0 | |
if [ ! -f "${pidfile}" ] || ! kill -0 $(cat ${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 | |
function 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 | |
function 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 | |
} | |
function 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} " | |
[ -n "${OPENCONNECT_GROUP}" ] && openconnect_opts+="--authgroup ${OPENCONNECT_GROUP} " | |
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 | |
(( DEBUG )) && openconnect_opts+="--printcookie " | |
(( DEBUG )) && openconnect_opts+="--dump-http-traffic " | |
openconnect_opts+="--os=win " | |
openconnect_opts+="${OPENCONNECT_HOST}" | |
echo "${openconnect_opts}" > /tmp/opts.log | |
echo "${openconnect_opts}" | |
} | |
function status() { | |
local vpn="$1" | |
local dns_proxy="$2" | |
if (( vpn )); then | |
stat "${OPENCONNECT_PID}" && { loginfo "VPN is running."; SCRIPT_EXIT_CODE=1; } || loginfo "VPN is stopped." | |
fi | |
if (( dns_proxy )); then | |
stat "${SOCAT_PID}" && { loginfo "DNS proxy is running."; SCRIPT_EXIT_CODE=1; } || loginfo "DNS proxy is stopped." | |
fi | |
} | |
function start_dns_proxy() { | |
type -P socat &?>/dev/null || { logwarn "Command not in path: socat. Unable to start DNS forwarding receiver."; return 1; } | |
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." | |
} | |
function start_vpn() { | |
local retval=0 | |
stat "${OPENCONNECT_PID}" && { loginfo "VPN already running."; return 0; } | |
echo "$(openconnect_start_opts)" | |
openconnect $(openconnect_start_opts) | |
sleep 1 | |
if stat "${OPENCONNECT_PID}"; then | |
if (( DNS_PROXY )); then | |
start_socat || logcrit "DNS proxy did not start correctly, check logs." | |
retval=1 | |
fi | |
loginfo "VPN is up and running." | |
else | |
logcrit "VPN not started, check logs." | |
retval=1 | |
fi | |
return $((retval)) | |
} | |
function stop_dns_proxy() { | |
if stat "${SOCAT_PID}"; then | |
loginfo "Shutting down DNS proxy." | |
kill $(cat ${SOCAT_PID}) | |
rm -f "${SOCAT_PID}" | |
loginfo "DNS proxy stopped, disable sshuttle routing on host." | |
fi | |
} | |
function stop_vpn() { | |
if stat "${OPENCONNECT_PID}"; then | |
loginfo "Shutting down VPN down." | |
kill -SIGINT $(cat "${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 | |
} | |
function requires_admin() { | |
if (( $EUID != 0 )); then | |
echo "ERROR: Run as root or with sudo." | |
exit 1 | |
fi | |
} | |
ACTION="usage" | |
DNS_PROXY=0 | |
VPN=0 | |
DEBUG=0 | |
function 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 | |
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 | |
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 | |
} | |
################################################################### | |
## ## | |
## M A I N ## | |
## ## | |
################################################################### | |
function init_opts $@ | |
case "$ACTION" in | |
start) | |
if (( VPN )) || (( DNS_PROXY )); then | |
(( VPN )) && { start_vpn || exit 27; } | |
(( DNS_PROXY )) && { start_dns_proxy || exit 96; } | |
else | |
display_usage | |
fi | |
;; | |
stop) | |
if (( VPN )) || (( DNS_PROXY )); then | |
(( VPN )) && { stop_vpn || exit 27; } | |
(( DNS_PROXY )) && { stop_dns_proxy || exit 96; } | |
else | |
display_usage | |
fi | |
;; | |
restart) | |
if (( VPN )) || (( DNS_PROXY )); then | |
(( DNS_PROXY )) && stop_dns || { logwarn "DNS proxy didn't stop, will not attempt to restart"; DNS_PROXY=0; } | |
(( VPN )) && { stop_vpn || exit 27; } && { start_vpn || exit 27; } | |
(( DNS_PROXY )) && { start_dns_proxy || exit 96; } | |
else | |
display_usage | |
fi | |
;; | |
status) | |
status ${VPN} ${DNS_PROXY} | |
;; | |
help|usage) | |
display_${ACTION} | |
;; | |
*) | |
display_usage | |
exit 1 | |
;; | |
esac | |
exit ${SCRIPT_EXIT_CODE} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment