-
-
Save agilecreativity/697235ec3f4b20715871596594ace700 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
#!/usr/bin/env bash | |
# | |
# DESC | |
# Bash script to manage Cisco AnyConnect VPN connections. | |
# | |
# Integrates with OS X KeyChain for password accesss. | |
# | |
# USAGE | |
# To use this script to manage VPN connections, create the following: | |
# | |
# Keychain Entries for each VPN | |
# | |
# - Open the Keychain App | |
# - Select "File" > "New Password Item..." | |
# - Specify the Keychain Item Name (becomes KEYCHAIN_ID in our conf file, see below) | |
# - Specify the Account Name (becomes KEYCHAIN_ACCOUNT in our conf file, see below) | |
# - Specify the Password using the password you use to connect to this VPN. | |
# | |
# I believe you could set this up to use your existing user's keychain | |
# entry so that your VPN creds will update as you change the password on | |
# your local account. As it is, I am currently going in and manually | |
# updating the password on my DLX vpn Keychain entry as my password | |
# changes. | |
# | |
# ~/.vpn/vpns.conf | |
# This file defines your available VPNs. It should look something like | |
# this: | |
# | |
# [dlx] | |
# VPN_HOSTNAME=vpn1.datalogix.com | |
# VPN_GROUP=odc-user | |
# VPN_USERNAME=user.name | |
# KEYCHAIN_ID=dlx-vpn | |
# KEYCHAIN_ACCOUNT=user.name | |
# | |
# [ora] | |
# VPN_HOSTNAME=myaccess.oraclevpn.com | |
# VPN_GROUP=NONE | |
# VPN_USERNAME=username_us | |
# KEYCHAIN_ID=ora-vpn | |
# KEYCHAIN_ACCOUNT=user.name | |
# | |
# The above file defines two VPN connections that the script will | |
# understand as 'dlx' and 'ora' (e.g. vpn connect dlx). | |
# | |
# TODO: | |
# - Improve 'usage', particularly around args for commands that support them. | |
# | |
cmds=('connect' 'con' 'disconnect' 'dis' 'status' 'stat' 'list' 'ls') | |
keys=( | |
'VPN_ID' | |
'VPN_HOSTNAME' | |
'VPN_GROUP' | |
'VPN_USERNAME' | |
'KEYCHAIN_ID' | |
'KEYCHAIN_ACCOUNT' | |
) | |
declare -r VPN_ID=0 | |
declare -r VPN_HOSTNAME=1 | |
declare -r VPN_GROUP=2 | |
declare -r VPN_USERNAME=3 | |
declare -r KEYCHAIN_ID=4 | |
declare -r KEYCHAIN_ACCOUNT=5 | |
die() { | |
echo -e >&2 "$@" | |
exit 1 | |
} | |
msg() { | |
echo -e >&2 "$@" | |
} | |
usage() { | |
if [ -n "$1" ]; then echo -e >&2 "$1"; fi | |
die "Usage: vpn $(IFS=\|; echo "${cmds[*]}") [args]" | |
} | |
array_contains () { | |
local el="$1" | |
local arr=("${@:2}") | |
for e in "${arr[@]}"; do [[ "${e}" == "${el}" ]] && return 0; done | |
return 1 | |
} | |
load_vpns() { | |
local config_file=~/.vpn/vpns.conf | |
local array_name='' | |
local line_num=0 | |
while read line; do | |
((++line_num)) | |
if [[ $line =~ ^[[:space:]]*$ ]]; then | |
continue | |
elif [[ $line =~ ^\[([[:alpha:]][[:alnum:]]*)\]$ ]]; then | |
array_name=${BASH_REMATCH[1]} | |
vpn_names+=($array_name) | |
# Need the arrays declared here to be global (i.e. no declare -a ...). | |
eval "${array_name}=()" | |
# Set the VPN_ID in the config. | |
eval ${array_name}[${VPN_ID}]=${array_name} | |
elif [[ $line =~ ^([^=]+)=(.*)$ ]]; then | |
[[ -n array_name ]] || die "Config file error line $line_num: no array name defined" | |
key="${BASH_REMATCH[1]}" | |
val="${BASH_REMATCH[2]}" | |
[[ ${keys[*]} =~ ${key} ]] || die "Config file error line $line_num: illegal key '${key}'" | |
eval ${array_name}[${key}]=${val} | |
else | |
die "*** Error line $line_num: $line" | |
fi | |
done < $config_file | |
# TODO: finish this... | |
for vpn_name in "${vpn_names[@]}"; do | |
eval config=( \${$vpn_name[@]} ) | |
if (( ${#config[@]} != ${#keys[@]} )); then | |
echo 'bad config...' | |
fi | |
done | |
} | |
get_password() { | |
local keychain_account=$1 | |
local keychain_id=$2 | |
echo $(security find-generic-password \ | |
-gw -l "${keychain_id}") | |
} | |
get_active_connection() { | |
local expr='notice: Connected to ' | |
local con=$(/opt/cisco/anyconnect/bin/vpn stats |grep "${expr}") | |
local vpn_host=$(echo $con |sed -e "s/.*${expr}//" -e 's/\.*$//') | |
echo $vpn_host | |
} | |
get_vpn_by_connection() { | |
local vpn_host=$1 | |
local domain=$(echo $vpn_host |awk -F. '{print $(NF-1)"."$NF}') | |
for vpn_name in "${vpn_names[@]}"; do | |
eval config=( \${$vpn_name[@]} ) | |
if [[ ${config[$VPN_HOSTNAME]} =~ .*${domain}$ ]]; then | |
echo ${config[$VPN_ID]} | |
return | |
fi | |
done | |
die "vpn not found for vpn host '${vpn_hostname}'" | |
} | |
get_active_vpn() { | |
vpn_host=$(get_active_connection) | |
local vpn | |
if [ -n "${vpn_host}" ]; then | |
vpn=$(get_vpn_by_connection ${vpn_host}) | |
else | |
vpn=none | |
fi | |
echo "${vpn}" | |
} | |
connected() { | |
[ -n "$(get_active_connection)" ] && return 0 | |
return 1 | |
} | |
connect() { | |
local vpn_name=$1 | |
array_contains "${vpn_name}" "${vpn_names[@]}" || die "Invalid vpn id '${vpn_name}'" | |
local current_vpn=$(get_active_vpn) | |
if [[ "${current_vpn}" == "${vpn_name}" ]]; then | |
echo "Already connected to '${vpn_name}'" | |
return | |
elif [[ "${current_vpn}" != 'none' ]]; then | |
echo "Disconnecting from '${current_vpn}'..." | |
disconnect | |
sleep 1 | |
fi | |
# Get the vpn configuration array from the vpn name. | |
eval local vpn=( \${$vpn_name[@]} ) | |
local hostname=${vpn[$VPN_HOSTNAME]} | |
local group=${vpn[$VPN_GROUP]} | |
local username=${vpn[$VPN_USERNAME]} | |
local password=$(get_password ${vpn[$KEYCHAIN_ACCOUNT]} ${vpn[$KEYCHAIN_ID]}) | |
# | |
# Build up format string and args for printf. We can't simply embed '\n' | |
# between the args and pass it diretly to printf as an argument may contain | |
# a printf formatting character in it (e.g. %). | |
# | |
[ ${group} != "NONE" ] && { format+="%s\n"; options+=("${group}"); } | |
[ -n ${username} ] && { format+="%s\n"; options+=("${username}"); } | |
[ -n ${password} ] && { format+="%s\n"; options+=("${password}"); } | |
# | |
# Note the follwoing below: | |
# | |
# - The awk command. For some reason, rather than simply exiting on a | |
# failed login attempt when reading from stdin, the Cisco vpn client just | |
# keeps trying over an over again until the user kills it. This can have | |
# some fairly unpleasant side affects (e.g. if you're login is tied to an | |
# AD or LDAP provider, your account may get locked out on multiple failed | |
# login attempts). The awk command below will look for the string 'Login | |
# failed' and force an exit. | |
# | |
# - The sed command. The command line output inclued the password in | |
# plain text, which obviusly we don't want. It outputs the password on | |
# the same line as the username, which _would_ be worth seeing in the | |
# output. The sed command removes the password from the output while | |
# preserveng the username. | |
# | |
# - The grep command. | |
# Just playing it safe. | |
# | |
printf "${format}" $(IFS=' ';echo "${options[*]}") \ | |
| /opt/cisco/anyconnect/bin/vpn -s connect ${hostname} \ | |
| awk -v s="Login failed" '$0~s{print $0; exit(1)} 1' \ | |
| sed 's/^\(Username: \[.*\]\).*/\1/' \ | |
| grep -vi 'password:' | |
} | |
disconnect() { | |
if connected; then | |
/opt/cisco/anyconnect/bin/vpn disconnect | |
else | |
echo 'No connection found' | |
fi | |
} | |
status() { | |
echo "connection: $(get_active_vpn)" | |
} | |
list() { | |
local arg=$1 | |
for vpn_name in "${vpn_names[@]}"; do | |
echo ${vpn_name} | |
if [[ "${arg}" == '-d' || "${arg}" == '--details' ]]; then | |
eval vpn_config=( \${$vpn_name[@]} ) | |
echo " VPN_ID: ${vpn_config[$VPN_ID]}" | |
echo " VPN_HOSTNAME: ${vpn_config[$VPN_HOSTNAME]}" | |
echo " VPN_GROUP: ${vpn_config[$VPN_GROUP]}" | |
echo " VPN_USERNAME: ${vpn_config[$VPN_USERNAME]}" | |
echo " KEYCHAIN_ID: ${vpn_config[$KEYCHAIN_ID]}" | |
echo " KEYCHAIN_ACCOUNT: ${vpn_config[$KEYCHAIN_ACCOUNT]}" | |
fi | |
done | |
} | |
main() { | |
local cmd=$1 | |
local cmd_args=${@:2} | |
case "${cmd}" in | |
connect|con) | |
load_vpns | |
connect "${cmd_args[0]}" | |
;; | |
disconnect|dis) | |
disconnect | |
;; | |
status|stat) | |
load_vpns | |
status | |
;; | |
list|ls) | |
load_vpns | |
list "${cmd_args[0]}" | |
;; | |
esac | |
# for vpn_name in "${vpn_names[@]}"; do | |
# eval this_arr=( \${$vpn_name[@]} ) | |
# echo 'vpn_name: ' $vpn_name | |
# echo 'config: ' ${this_arr[@]} | |
# echo "VPN_ID: ${this_arr[$VPN_ID]}" | |
# echo "VPN_GROUP: ${this_arr[$VPN_GROUP]}" | |
# echo "GROUP: ${this_arr[$GROUP]}" | |
# echo "USERNAME: ${this_arr[$USERNAME]}" | |
# echo "KEYCHAIN_ID: ${this_arr[$KEYCHAIN_ID]}" | |
# echo "KEYCHAIN_ACCOUNT: ${this_arr[$KEYCHAIN_ACCOUNT]}" | |
# done | |
} | |
[ $# -ge 1 ] || usage | |
cmd=${1} | |
cmd_args=${@:2} | |
if [[ ! ${cmds[*]} =~ ${cmd} ]]; then | |
usage "Illegal command (${cmd})." | |
fi | |
vpn_names=() | |
main $cmd $cmd_args |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment