Idempotent in headless mode

This set of changes adjusts the script so that you can run it multiple times with the same input and not have any unexpected changes. This makes it appropriate for "enforcing state", as required by automated provisioners like Puppet, Salt, Chef, or Ansible.

 - Unbound, OpenVPN, easy-rsa, and other dependencies are only installed from upstream if they are not already present. This prevents multiple runs of the script from causing unexpected version upgrades.
 - The easy-rsa system is put in a folder called "easy-rsa-auto" so it can't conflict with the "easy-rsa" folder from some older OpenVPN packages
 - The easy-rsa CA is only initialized once
 - SERVER_CN and SERVER_NAME are randomly generated once and saved for future reference
 - File append ('>>') is only done strictly after a file is created with '>' (e.g. /etc/sysctl.d/20-openvpn.conf)
 - Clients are only added to easy-rsa once
 - If AUTO_INSTALL == y, then the script operates in install mode and doesn't enter manageMenu
This commit is contained in:
John E 2020-03-29 10:13:45 -07:00
parent c2a4edc714
commit a939ac72a9
2 changed files with 126 additions and 93 deletions

View file

@ -72,6 +72,8 @@ Other variables can be set depending on your choice (encryption, compression). Y
Password-protected clients are not supported by the headless installation method since user input is expected by Easy-RSA. Password-protected clients are not supported by the headless installation method since user input is expected by Easy-RSA.
The headless install is more-or-less idempotent, in that it has been made safe to run multiple times with the same parameters, e.g. by a state provisioner like Terraform/Salt/Chef/Puppet. It will only install and regenerate the Easy-RSA PKI if it doesn't already exist, and it will only install OpenVPN and other upstream dependencies if OpenVPN isn't already installed. It will recreate all local config and re-generate the client file on each headless run.
### Headless User Addition ### Headless User Addition
It's also possible to automate the addition of a new user. Here, the key is to provide the (string) value of the `MENU_OPTION` variable along with the remaining mandatory variables before invoking the script. It's also possible to automate the addition of a new user. Here, the key is to provide the (string) value of the `MENU_OPTION` variable along with the remaining mandatory variables before invoking the script.

View file

@ -98,6 +98,7 @@ function initialCheck () {
} }
function installUnbound () { function installUnbound () {
# If Unbound isn't installed, install it
if [[ ! -e /etc/unbound/unbound.conf ]]; then if [[ ! -e /etc/unbound/unbound.conf ]]; then
if [[ "$OS" =~ (debian|ubuntu) ]]; then if [[ "$OS" =~ (debian|ubuntu) ]]; then
@ -137,7 +138,9 @@ prefetch: yes' >> /etc/unbound/unbound.conf
# Get root servers list # Get root servers list
curl -o /etc/unbound/root.hints https://www.internic.net/domain/named.cache curl -o /etc/unbound/root.hints https://www.internic.net/domain/named.cache
mv /etc/unbound/unbound.conf /etc/unbound/unbound.conf.old if [[ ! -f /etc/unbound/unbound.conf.old ]]; then
mv /etc/unbound/unbound.conf /etc/unbound/unbound.conf.old
fi
echo 'server: echo 'server:
use-syslog: yes use-syslog: yes
@ -596,8 +599,12 @@ function installOpenVPN () {
PASS=${PASS:-1} PASS=${PASS:-1}
CONTINUE=${CONTINUE:-y} CONTINUE=${CONTINUE:-y}
# Behind NAT, we'll default to the publicly reachable IPv4. # Behind NAT, we'll default to the publicly reachable IPv4/IPv6.
PUBLIC_IPV4=$(curl ifconfig.co) if [[ $IPV6_SUPPORT == "y" ]]; then
PUBLIC_IPV4=$(curl ifconfig.co)
else
PUBLIC_IPV4=$(curl -4 ifconfig.co)
fi
ENDPOINT=${ENDPOINT:-$PUBLIC_IPV4} ENDPOINT=${ENDPOINT:-$PUBLIC_IPV4}
fi fi
@ -623,33 +630,38 @@ function installOpenVPN () {
fi fi
fi fi
if [[ "$OS" =~ (debian|ubuntu) ]]; then # If OpenVPN isn't installed yet, install it. This script is more-or-less
apt-get update # idempotent on multiple runs, but will only install OpenVPN from upstream
apt-get -y install ca-certificates gnupg # the first time.
# We add the OpenVPN repo to get the latest version. if [[ ! -e /etc/openvpn/server.conf ]]; then
if [[ "$VERSION_ID" == "8" ]]; then if [[ "$OS" =~ (debian|ubuntu) ]]; then
echo "deb http://build.openvpn.net/debian/openvpn/stable jessie main" > /etc/apt/sources.list.d/openvpn.list
wget -O - https://swupdate.openvpn.net/repos/repo-public.gpg | apt-key add -
apt-get update apt-get update
apt-get -y install ca-certificates gnupg
# We add the OpenVPN repo to get the latest version.
if [[ "$VERSION_ID" = "8" ]]; then
echo "deb http://build.openvpn.net/debian/openvpn/stable jessie main" > /etc/apt/sources.list.d/openvpn.list
wget -O - https://swupdate.openvpn.net/repos/repo-public.gpg | apt-key add -
apt-get update
fi
if [[ "$VERSION_ID" = "16.04" ]]; then
echo "deb http://build.openvpn.net/debian/openvpn/stable xenial main" > /etc/apt/sources.list.d/openvpn.list
wget -O - https://swupdate.openvpn.net/repos/repo-public.gpg | apt-key add -
apt-get update
fi
# Ubuntu > 16.04 and Debian > 8 have OpenVPN >= 2.4 without the need of a third party repository.
apt-get install -y openvpn iptables openssl wget ca-certificates curl
elif [[ "$OS" = 'centos' ]]; then
yum install -y epel-release
yum install -y openvpn iptables openssl wget ca-certificates curl tar 'policycoreutils-python*'
elif [[ "$OS" = 'amzn' ]]; then
amazon-linux-extras install -y epel
yum install -y openvpn iptables openssl wget ca-certificates curl
elif [[ "$OS" = 'fedora' ]]; then
dnf install -y openvpn iptables openssl wget ca-certificates curl
elif [[ "$OS" = 'arch' ]]; then
# Install required dependencies and upgrade the system
pacman --needed --noconfirm -Syu openvpn iptables openssl wget ca-certificates curl
fi fi
if [[ "$VERSION_ID" == "16.04" ]]; then
echo "deb http://build.openvpn.net/debian/openvpn/stable xenial main" > /etc/apt/sources.list.d/openvpn.list
wget -O - https://swupdate.openvpn.net/repos/repo-public.gpg | apt-key add -
apt-get update
fi
# Ubuntu > 16.04 and Debian > 8 have OpenVPN >= 2.4 without the need of a third party repository.
apt-get install -y openvpn iptables openssl wget ca-certificates curl
elif [[ "$OS" == 'centos' ]]; then
yum install -y epel-release
yum install -y openvpn iptables openssl wget ca-certificates curl tar 'policycoreutils-python*'
elif [[ "$OS" == 'amzn' ]]; then
amazon-linux-extras install -y epel
yum install -y openvpn iptables openssl wget ca-certificates curl
elif [[ "$OS" == 'fedora' ]]; then
dnf install -y openvpn iptables openssl wget ca-certificates curl
elif [[ "$OS" == 'arch' ]]; then
# Install required dependencies and upgrade the system
pacman --needed --noconfirm -Syu openvpn iptables openssl wget ca-certificates curl
fi fi
# Find out if the machine uses nogroup or nobody for the permissionless group # Find out if the machine uses nogroup or nobody for the permissionless group
@ -664,60 +676,72 @@ function installOpenVPN () {
rm -rf /etc/openvpn/easy-rsa/ rm -rf /etc/openvpn/easy-rsa/
fi fi
# Install the latest version of easy-rsa from source # Install the latest version of easy-rsa from source, if not already
local version="3.0.6" # installed.
wget -O ~/EasyRSA-unix-v${version}.tgz https://github.com/OpenVPN/easy-rsa/releases/download/v${version}/EasyRSA-unix-v${version}.tgz if [[ ! -d /etc/openvpn/easy-rsa-auto/ ]]; then
tar xzf ~/EasyRSA-unix-v${version}.tgz -C ~/ local version="3.0.6"
mv ~/EasyRSA-v${version} /etc/openvpn/easy-rsa wget -O ~/EasyRSA-unix-v${version}.tgz https://github.com/OpenVPN/easy-rsa/releases/download/v${version}/EasyRSA-unix-v${version}.tgz
chown -R root:root /etc/openvpn/easy-rsa/ tar xzf ~/EasyRSA-unix-v${version}.tgz -C ~/
rm -f ~/EasyRSA-unix-v${version}.tgz mkdir -p /etc/openvpn/easy-rsa-auto
mv ~/EasyRSA-v${version}/* /etc/openvpn/easy-rsa-auto/
chown -R root:root /etc/openvpn/easy-rsa-auto/
rm -f ~/EasyRSA-unix-v${version}.tgz
cd /etc/openvpn/easy-rsa/ || return cd /etc/openvpn/easy-rsa-auto/ || return
case $CERT_TYPE in case $CERT_TYPE in
1) 1)
echo "set_var EASYRSA_ALGO ec" > vars echo "set_var EASYRSA_ALGO ec" > vars
echo "set_var EASYRSA_CURVE $CERT_CURVE" >> vars echo "set_var EASYRSA_CURVE $CERT_CURVE" >> vars
;; ;;
2) 2)
echo "set_var EASYRSA_KEY_SIZE $RSA_KEY_SIZE" > vars echo "set_var EASYRSA_KEY_SIZE $RSA_KEY_SIZE" > vars
;; ;;
esac esac
# Generate a random, alphanumeric identifier of 16 characters for CN and one for server name # Generate a random, alphanumeric identifier of 16 characters for CN and one for server name
SERVER_CN="cn_$(head /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1)" SERVER_CN="cn_$(head /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1)"
SERVER_NAME="server_$(head /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1)" echo "$SERVER_CN" > SERVER_CN_GENERATED
echo "set_var EASYRSA_REQ_CN $SERVER_CN" >> vars SERVER_NAME="server_$(head /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1)"
echo "$SERVER_NAME" > SERVER_NAME_GENERATED
# Create the PKI, set up the CA, the DH params and the server certificate echo "set_var EASYRSA_REQ_CN $SERVER_CN" >> vars
./easyrsa init-pki
# Workaround to remove unharmful error until easy-rsa 3.0.7 # Create the PKI, set up the CA, the DH params and the server certificate
# https://github.com/OpenVPN/easy-rsa/issues/261 ./easyrsa init-pki
sed -i 's/^RANDFILE/#RANDFILE/g' pki/openssl-easyrsa.cnf
./easyrsa --batch build-ca nopass # Workaround to remove unharmful error until easy-rsa 3.0.7
# https://github.com/OpenVPN/easy-rsa/issues/261
sed -i 's/^RANDFILE/#RANDFILE/g' pki/openssl-easyrsa.cnf
if [[ $DH_TYPE == "2" ]]; then ./easyrsa --batch build-ca nopass
# ECDH keys are generated on-the-fly so we don't need to generate them beforehand
openssl dhparam -out dh.pem $DH_KEY_SIZE if [[ $DH_TYPE == "2" ]]; then
# ECDH keys are generated on-the-fly so we don't need to generate them beforehand
openssl dhparam -out dh.pem $DH_KEY_SIZE
fi
./easyrsa build-server-full "$SERVER_NAME" nopass
EASYRSA_CRL_DAYS=3650 ./easyrsa gen-crl
case $TLS_SIG in
1)
# Generate tls-crypt key
openvpn --genkey --secret /etc/openvpn/tls-crypt.key
;;
2)
# Generate tls-auth key
openvpn --genkey --secret /etc/openvpn/tls-auth.key
;;
esac
else
# If easy-rsa is already installed, grab the generated SERVER_NAME
# for client configs
cd /etc/openvpn/easy-rsa-auto/ || return
SERVER_NAME=$(cat SERVER_NAME_GENERATED)
fi fi
./easyrsa build-server-full "$SERVER_NAME" nopass
EASYRSA_CRL_DAYS=3650 ./easyrsa gen-crl
case $TLS_SIG in
1)
# Generate tls-crypt key
openvpn --genkey --secret /etc/openvpn/tls-crypt.key
;;
2)
# Generate tls-auth key
openvpn --genkey --secret /etc/openvpn/tls-auth.key
;;
esac
# Move all the generated files # Move all the generated files
cp pki/ca.crt pki/private/ca.key "pki/issued/$SERVER_NAME.crt" "pki/private/$SERVER_NAME.key" /etc/openvpn/easy-rsa/pki/crl.pem /etc/openvpn cp pki/ca.crt pki/private/ca.key "pki/issued/$SERVER_NAME.crt" "pki/private/$SERVER_NAME.key" /etc/openvpn/easy-rsa-auto/pki/crl.pem /etc/openvpn
if [[ $DH_TYPE == "2" ]]; then if [[ $DH_TYPE == "2" ]]; then
cp dh.pem /etc/openvpn cp dh.pem /etc/openvpn
fi fi
@ -859,8 +883,8 @@ verb 3" >> /etc/openvpn/server.conf
mkdir -p /var/log/openvpn mkdir -p /var/log/openvpn
# Enable routing # Enable routing
echo 'net.ipv4.ip_forward=1' >> /etc/sysctl.d/20-openvpn.conf echo 'net.ipv4.ip_forward=1' > /etc/sysctl.d/20-openvpn.conf
if [[ "$IPV6_SUPPORT" == 'y' ]]; then if [[ "$IPV6_SUPPORT" = 'y' ]]; then
echo 'net.ipv6.conf.all.forwarding=1' >> /etc/sysctl.d/20-openvpn.conf echo 'net.ipv6.conf.all.forwarding=1' >> /etc/sysctl.d/20-openvpn.conf
fi fi
# Apply sysctl rules # Apply sysctl rules
@ -1029,16 +1053,23 @@ function newClient () {
read -rp "Select an option [1-2]: " -e -i 1 PASS read -rp "Select an option [1-2]: " -e -i 1 PASS
done done
cd /etc/openvpn/easy-rsa/ || return CLIENTEXISTS=$(tail -n +2 /etc/openvpn/easy-rsa-auto/pki/index.txt | grep -c -E "/CN=$CLIENT\$")
case $PASS in if [[ "$CLIENTEXISTS" = '1' ]]; then
1) echo ""
./easyrsa build-client-full "$CLIENT" nopass echo "The specified client CN was found in easy-rsa."
;; else
2) cd /etc/openvpn/easy-rsa-auto/ || return
echo "⚠️ You will be asked for the client password below ⚠️" case $PASS in
./easyrsa build-client-full "$CLIENT" 1)
;; ./easyrsa build-client-full "$CLIENT" nopass
esac ;;
2)
echo "⚠️ You will be asked for the client password below ⚠️"
./easyrsa build-client-full "$CLIENT"
;;
esac
echo "Client $CLIENT added."
fi
# Home directory of the user, where the client configuration (.ovpn) will be written # Home directory of the user, where the client configuration (.ovpn) will be written
if [ -e "/home/$CLIENT" ]; then # if $1 is a user name if [ -e "/home/$CLIENT" ]; then # if $1 is a user name
@ -1060,15 +1091,15 @@ function newClient () {
cp /etc/openvpn/client-template.txt "$homeDir/$CLIENT.ovpn" cp /etc/openvpn/client-template.txt "$homeDir/$CLIENT.ovpn"
{ {
echo "<ca>" echo "<ca>"
cat "/etc/openvpn/easy-rsa/pki/ca.crt" cat "/etc/openvpn/easy-rsa-auto/pki/ca.crt"
echo "</ca>" echo "</ca>"
echo "<cert>" echo "<cert>"
awk '/BEGIN/,/END/' "/etc/openvpn/easy-rsa/pki/issued/$CLIENT.crt" awk '/BEGIN/,/END/' "/etc/openvpn/easy-rsa-auto/pki/issued/$CLIENT.crt"
echo "</cert>" echo "</cert>"
echo "<key>" echo "<key>"
cat "/etc/openvpn/easy-rsa/pki/private/$CLIENT.key" cat "/etc/openvpn/easy-rsa-auto/pki/private/$CLIENT.key"
echo "</key>" echo "</key>"
case $TLS_SIG in case $TLS_SIG in
@ -1087,7 +1118,7 @@ function newClient () {
} >> "$homeDir/$CLIENT.ovpn" } >> "$homeDir/$CLIENT.ovpn"
echo "" echo ""
echo "Client $CLIENT added, the configuration file is available at $homeDir/$CLIENT.ovpn." echo "The configuration file has been written to $homeDir/$CLIENT.ovpn."
echo "Download the .ovpn file and import it in your OpenVPN client." echo "Download the .ovpn file and import it in your OpenVPN client."
exit 0 exit 0
@ -1110,8 +1141,8 @@ function revokeClient () {
read -rp "Select one client [1-$NUMBEROFCLIENTS]: " CLIENTNUMBER read -rp "Select one client [1-$NUMBEROFCLIENTS]: " CLIENTNUMBER
fi fi
CLIENT=$(tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | sed -n "$CLIENTNUMBER"p) CLIENT=$(tail -n +2 /etc/openvpn/easy-rsa-auto/pki/index.txt | grep "^V" | cut -d '=' -f 2 | sed -n "$CLIENTNUMBER"p)
cd /etc/openvpn/easy-rsa/ || return cd /etc/openvpn/easy-rsa-auto/ || return
./easyrsa --batch revoke "$CLIENT" ./easyrsa --batch revoke "$CLIENT"
EASYRSA_CRL_DAYS=3650 ./easyrsa gen-crl EASYRSA_CRL_DAYS=3650 ./easyrsa gen-crl
# Cleanup # Cleanup
@ -1119,7 +1150,7 @@ function revokeClient () {
rm -f "pki/private/$CLIENT.key" rm -f "pki/private/$CLIENT.key"
rm -f "pki/issued/$CLIENT.crt" rm -f "pki/issued/$CLIENT.crt"
rm -f /etc/openvpn/crl.pem rm -f /etc/openvpn/crl.pem
cp /etc/openvpn/easy-rsa/pki/crl.pem /etc/openvpn/crl.pem cp /etc/openvpn/easy-rsa-auto/pki/crl.pem /etc/openvpn/crl.pem
chmod 644 /etc/openvpn/crl.pem chmod 644 /etc/openvpn/crl.pem
find /home/ -maxdepth 2 -name "$CLIENT.ovpn" -delete find /home/ -maxdepth 2 -name "$CLIENT.ovpn" -delete
rm -f "/root/$CLIENT.ovpn" rm -f "/root/$CLIENT.ovpn"
@ -1277,7 +1308,7 @@ function manageMenu () {
initialCheck initialCheck
# Check if OpenVPN is already installed # Check if OpenVPN is already installed
if [[ -e /etc/openvpn/server.conf ]]; then if [[ -e /etc/openvpn/server.conf && $AUTO_INSTALL != "y" ]]; then
manageMenu manageMenu
else else
installOpenVPN installOpenVPN