diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..cb5901e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,36 @@ +ARG ALPINE_VERSION=3.15 + +FROM alpine:${ALPINE_VERSION} + +LABEL zero-downtime.net.image.maintainer="stefan@zero-downtime.net" \ + zero-downtime.net.image.license="AGPLv3" + +RUN apk upgrade -U --available --no-cache && \ + apk add --no-cache \ + openvpn \ + nftables \ + bash \ + easy-rsa \ + openvpn-auth-pam \ + google-authenticator \ + libqrencode && \ + ln -s /usr/share/easy-rsa/easyrsa /usr/local/bin + +# Needed by scripts +ENV OPENVPN=/etc/openvpn +ENV EASYRSA=/usr/share/easy-rsa \ + EASYRSA_CRL_DAYS=3650 \ + EASYRSA_PKI=$OPENVPN/pki + +VOLUME ["/etc/openvpn"] + +EXPOSE 1194/udp + +CMD ["ovpn_run"] + +ADD ./bin /usr/local/bin +RUN chmod a+x /usr/local/bin/* + +# Add support for OTP authentication using a PAM module +ADD ./otp/openvpn /etc/pam.d/ + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ad58673 --- /dev/null +++ b/Makefile @@ -0,0 +1,6 @@ +REGISTRY := public.ecr.aws/zero-downtime +IMAGE := zdt-openvpn +REGION := us-east-1 + +include .ci/podman.mk + diff --git a/README.md b/README.md index 763d6af..4afda7f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ # zdt-openvpn -OpenVPN container incl. easy-rsa, TOTP, prometheus exporter \ No newline at end of file +OpenVPN container incl. easy-rsa, TOTP, prometheus exporter + +## Credits: +- https://amilstead.com/blog/openvpn-with-google-authenticator-totp/#example-client-config-generator +- https://github.com/seb-daehne/docker-openvpn.git +- GitHub @ [kylemanna/docker-openvpn](https://github.com/kylemanna/docker-openvpn) diff --git a/bin/ovpn_copy_server_files b/bin/ovpn_copy_server_files new file mode 100755 index 0000000..b99f54c --- /dev/null +++ b/bin/ovpn_copy_server_files @@ -0,0 +1,47 @@ +#!/bin/bash +## @licence MIT +## @author Copyright (C) 2015 Robin Schneider + +set -e + +if [ -z "$OPENVPN" ]; then + export OPENVPN="$PWD" +fi +if ! source "$OPENVPN/ovpn_env.sh"; then + echo "Could not source $OPENVPN/ovpn_env.sh." + exit 1 +fi + +TARGET="$OPENVPN/server" +if [ -n "$1" ]; then + TARGET="$1" +fi +mkdir -p "${TARGET}" + +## Ensure that no other keys then the one for the server is present. +rm -rf "$TARGET/pki/private" "$TARGET/pki/issued" + +FILES=( + "openvpn.conf" + "ovpn_env.sh" + "pki/private/${OVPN_CN}.key" + "pki/issued/${OVPN_CN}.crt" + "pki/dh.pem" + "pki/ta.key" + "pki/ca.crt" + "ccd" +) + +if [ -f "${OPENVPN}/pki/crl.pem" ]; then + FILES+=("pki/crl.pem") +fi + +# Ensure the ccd directory exists, even if empty +mkdir -p "ccd" + +# rsync isn't available to keep size down +# cp --parents isn't in busybox version +# hack the directory structure with tar +tar cf - -C "${OPENVPN}" "${FILES[@]}" | tar xvf - -C "${TARGET}" + +echo "Created the openvpn configuration for the server: $TARGET" diff --git a/bin/ovpn_genconfig b/bin/ovpn_genconfig new file mode 100755 index 0000000..c2ebca6 --- /dev/null +++ b/bin/ovpn_genconfig @@ -0,0 +1,412 @@ +#!/bin/bash + +# +# Generate OpenVPN configs +# + +TMP_PUSH_CONFIGFILE=$(mktemp -t vpn_push.XXXXXXX) +TMP_ROUTE_CONFIGFILE=$(mktemp -t vpn_route.XXXXXXX) +TMP_EXTRA_CONFIGFILE=$(mktemp -t vpn_extra.XXXXXXX) + +#Traceback on Error and Exit come from https://docwhat.org/tracebacks-in-bash/ +set -eu + +_showed_traceback=f + +traceback() { + # Hide the traceback() call. + local -i start=$(( ${1:-0} + 1 )) + local -i end=${#BASH_SOURCE[@]} + local -i i=0 + local -i j=0 + + echo "Traceback (last called is first):" 1>&2 + for ((i=${start}; i < ${end}; i++)); do + j=$(( $i - 1 )) + local function="${FUNCNAME[$i]}" + local file="${BASH_SOURCE[$i]}" + local line="${BASH_LINENO[$j]}" + echo " ${function}() in ${file}:${line}" 1>&2 + done +} + +on_error() { + local _ec="$?" + local _cmd="${BASH_COMMAND:-unknown}" + traceback 1 + _showed_traceback=t + echo "The command ${_cmd} exited with exit code ${_ec}." 1>&2 +} +trap on_error ERR + + +on_exit() { + echo "Cleaning up before Exit ..." + rm -f $TMP_PUSH_CONFIGFILE + rm -f $TMP_ROUTE_CONFIGFILE + rm -f $TMP_EXTRA_CONFIGFILE + local _ec="$?" + if [[ $_ec != 0 && "${_showed_traceback}" != t ]]; then + traceback 1 + fi +} +trap on_exit EXIT + +# Convert 1.2.3.4/24 -> 255.255.255.0 +cidr2mask() +{ + local i + local subnetmask="" + local cidr=${1#*/} + local full_octets=$(($cidr/8)) + local partial_octet=$(($cidr%8)) + + for ((i=0;i<4;i+=1)); do + if [ $i -lt $full_octets ]; then + subnetmask+=255 + elif [ $i -eq $full_octets ]; then + subnetmask+=$((256 - 2**(8-$partial_octet))) + else + subnetmask+=0 + fi + [ $i -lt 3 ] && subnetmask+=. + done + echo $subnetmask +} + +# Used often enough to justify a function +getroute() { + echo ${1%/*} $(cidr2mask $1) +} + +usage() { + echo "usage: $0 [-d]" + echo " -u SERVER_PUBLIC_URL" + echo " [-e EXTRA_SERVER_CONFIG ]" + echo " [-E EXTRA_CLIENT_CONFIG ]" + echo " [-f FRAGMENT ]" + echo " [-n DNS_SERVER ...]" + echo " [-p PUSH ...]" + echo " [-r ROUTE ...]" + echo " [-s SERVER_SUBNET]" + echo + echo "optional arguments:" + echo " -2 Enable two factor authentication using Google Authenticator." + echo " -a Authenticate packets with HMAC using the given message digest algorithm (auth)." + echo " -b Disable 'push block-outside-dns'" + echo " -c Enable client-to-client option" + echo " -C A list of allowable TLS ciphers delimited by a colon (cipher)." + echo " -d Disable default route" + echo " -D Do not push dns servers" + echo " -k Set keepalive. Default: '10 60'" + echo " -m Set client MTU" + echo " -N Configure NAT to access external server network" + echo " -t Use TAP device (instead of TUN device)" + echo " -T Encrypt packets with the given cipher algorithm instead of the default one (tls-cipher)." + echo " -z Enable comp-lzo compression." +} + +process_route_config() { + local ovpn_route_config='' + ovpn_route_config="$1" + # If user passed "0" skip this, assume no extra routes + [[ "$ovpn_route_config" == "0" ]] && break; + echo "Processing Route Config: '${ovpn_route_config}'" + [[ -n "$ovpn_route_config" ]] && echo "route $(getroute $ovpn_route_config)" >> "$TMP_ROUTE_CONFIGFILE" +} + +process_push_config() { + local ovpn_push_config='' + ovpn_push_config="$1" + echo "Processing PUSH Config: '${ovpn_push_config}'" + [[ -n "$ovpn_push_config" ]] && echo "push \"$ovpn_push_config\"" >> "$TMP_PUSH_CONFIGFILE" +} + +process_extra_config() { + local ovpn_extra_config='' + ovpn_extra_config="$1" + echo "Processing Extra Config: '${ovpn_extra_config}'" + [[ -n "$ovpn_extra_config" ]] && echo "$ovpn_extra_config" >> "$TMP_EXTRA_CONFIGFILE" +} + +if [ "${DEBUG:-}" == "1" ]; then + set -x +fi + +set -e + +if [ -z "${OPENVPN:-}" ]; then + export OPENVPN="$PWD" +fi +if [ -z "${EASYRSA_PKI:-}" ]; then + export EASYRSA_PKI="$OPENVPN/pki" +fi + +OVPN_AUTH='' +OVPN_CIPHER='' +OVPN_CLIENT_TO_CLIENT='' +OVPN_CN='' +OVPN_COMP_LZO=0 +OVPN_DEFROUTE=1 +OVPN_DEVICE="tun" +OVPN_DEVICEN=0 +OVPN_DISABLE_PUSH_BLOCK_DNS=0 +OVPN_DNS=1 +OVPN_DNS_SERVERS=() +OVPN_ENV=${OPENVPN}/ovpn_env.sh +OVPN_EXTRA_CLIENT_CONFIG=() +OVPN_EXTRA_SERVER_CONFIG=() +OVPN_FRAGMENT='' +OVPN_KEEPALIVE="10 60" +OVPN_MTU='' +OVPN_NAT=0 +OVPN_PORT='' +OVPN_PROTO='' +OVPN_PUSH=() +OVPN_ROUTES=() +OVPN_SERVER=192.168.255.0/24 +OVPN_SERVER_URL='' +OVPN_TLS_CIPHER='' + +# Import existing configuration if present +[ -r "$OVPN_ENV" ] && source "$OVPN_ENV" + +# Parse arguments +while getopts ":a:e:E:C:T:r:s:du:bcp:n:k:DNm:f:tz2" opt; do + case $opt in + a) + OVPN_AUTH="$OPTARG" + ;; + e) + mapfile -t TMP_EXTRA_SERVER_CONFIG <<< "$OPTARG" + for i in "${TMP_EXTRA_SERVER_CONFIG[@]}"; do + OVPN_EXTRA_SERVER_CONFIG+=("$i") + done + ;; + E) + mapfile -t TMP_EXTRA_CLIENT_CONFIG <<< "$OPTARG" + for i in "${TMP_EXTRA_CLIENT_CONFIG[@]}"; do + OVPN_EXTRA_CLIENT_CONFIG+=("$i") + done + ;; + C) + OVPN_CIPHER="$OPTARG" + ;; + T) + OVPN_TLS_CIPHER="$OPTARG" + ;; + r) + mapfile -t TMP_ROUTES <<< "$OPTARG" + for i in "${TMP_ROUTES[@]}"; do + OVPN_ROUTES+=("$i") + done + ;; + s) + OVPN_SERVER="$OPTARG" + ;; + d) + OVPN_DEFROUTE=0 + OVPN_DISABLE_PUSH_BLOCK_DNS=1 + ;; + u) + OVPN_SERVER_URL="$OPTARG" + ;; + b) + OVPN_DISABLE_PUSH_BLOCK_DNS=1 + ;; + c) + OVPN_CLIENT_TO_CLIENT=1 + ;; + p) + mapfile -t TMP_PUSH <<< "$OPTARG" + for i in "${TMP_PUSH[@]}"; do + OVPN_PUSH+=("$i") + done + ;; + n) + mapfile -t TMP_DNS_SERVERS <<< "$OPTARG" + for i in "${TMP_DNS_SERVERS[@]}"; do + OVPN_DNS_SERVERS+=("$i") + done + ;; + D) + OVPN_DNS=0 + ;; + N) + OVPN_NAT=1 + ;; + k) + OVPN_KEEPALIVE="$OPTARG" + ;; + m) + OVPN_MTU="$OPTARG" + ;; + t) + OVPN_DEVICE="tap" + ;; + z) + OVPN_COMP_LZO=1 + ;; + 2) + OVPN_OTP_AUTH=1 + ;; + f) + OVPN_FRAGMENT="$OPTARG" + ;; + \?) + set +x + echo "Invalid option: -$OPTARG" >&2 + usage + exit 1 + ;; + :) + set +x + echo "Option -$OPTARG requires an argument." >&2 + usage + exit 1 + ;; + esac +done + +# Create ccd directory for static routes +[ ! -d "${OPENVPN:-}/ccd" ] && mkdir -p ${OPENVPN:-}/ccd + +# Server name is in the form "udp://vpn.example.com:1194" +if [[ "${OVPN_SERVER_URL:-}" =~ ^((udp|tcp|udp6|tcp6)://)?([0-9a-zA-Z\.\-]+)(:([0-9]+))?$ ]]; then + OVPN_PROTO=${BASH_REMATCH[2]}; + OVPN_CN=${BASH_REMATCH[3]}; + OVPN_PORT=${BASH_REMATCH[5]}; +else + set +x + echo "Common name not specified, see '-u'" + usage + exit 1 +fi + +# Apply defaults. If dns servers were not defined with -n, use google nameservers +set +u +[ -z "$OVPN_DNS_SERVERS" ] && OVPN_DNS_SERVERS=("8.8.8.8" "8.8.4.4") +[ -z "$OVPN_PROTO" ] && OVPN_PROTO=udp +[ -z "$OVPN_PORT" ] && OVPN_PORT=1194 +set -u +[ "${#OVPN_ROUTES[@]}" == "0" ] && [ "$OVPN_DEFROUTE" == "1" ] && OVPN_ROUTES+=("192.168.254.0/24") + +# Preserve config +if [ -f "$OVPN_ENV" ]; then + bak_env=$OVPN_ENV.$(date +%s).bak + echo "Backing up $OVPN_ENV -> $bak_env" + mv "$OVPN_ENV" "$bak_env" +fi + +# Save the current OVPN_ vars to the ovpn_env.sh file +(set | grep '^OVPN_') | while read -r var; do + echo "declare -x $var" >> "$OVPN_ENV" +done + +conf=${OPENVPN:-}/openvpn.conf +if [ -f "$conf" ]; then + bak=$conf.$(date +%s).bak + echo "Backing up $conf -> $bak" + mv "$conf" "$bak" +fi + +# Echo extra client configurations +if [ ${#OVPN_EXTRA_CLIENT_CONFIG[@]} -gt 0 ]; then + for i in "${OVPN_EXTRA_CLIENT_CONFIG[@]}"; do + echo "Processing Extra Client Config: $i" + done +fi + +cat > "$conf" <> "$conf" +[ -n "$OVPN_CIPHER" ] && echo "cipher $OVPN_CIPHER" >> "$conf" +[ -n "$OVPN_AUTH" ] && echo "auth $OVPN_AUTH" >> "$conf" + +[ -n "${OVPN_CLIENT_TO_CLIENT:-}" ] && echo "client-to-client" >> "$conf" +[ "$OVPN_COMP_LZO" == "1" ] && echo "comp-lzo" >> "$conf" +[ "$OVPN_COMP_LZO" == "0" ] && echo "comp-lzo no" >> "$conf" + +[ -n "${OVPN_FRAGMENT:-}" ] && echo "fragment $OVPN_FRAGMENT" >> "$conf" + +# Append route commands +if [ ${#OVPN_ROUTES[@]} -gt 0 ]; then + for i in "${OVPN_ROUTES[@]}"; do + process_route_config "$i" + done + echo -e "\n### Route Configurations Below" >> "$conf" + cat $TMP_ROUTE_CONFIGFILE >> "$conf" +fi + +# Append push commands +[ "$OVPN_DNS" == "1" ] && for i in "${OVPN_DNS_SERVERS[@]}"; do + process_push_config "dhcp-option DNS $i" +done + +if [ "$OVPN_COMP_LZO" == "0" ]; then + process_push_config "comp-lzo no" +fi + +[ ${#OVPN_PUSH[@]} -gt 0 ] && for i in "${OVPN_PUSH[@]}"; do + process_push_config "$i" +done + +echo -e "\n### Push Configurations Below" >> "$conf" +cat $TMP_PUSH_CONFIGFILE >> "$conf" + +# Append optional OTP authentication support +if [ -n "${OVPN_OTP_AUTH:-}" ]; then + echo -e "\n\n# Enable OTP+PAM for user authentication" >> "$conf" + echo "plugin /usr/lib/openvpn/plugins/openvpn-plugin-auth-pam.so openvpn" >> "$conf" + echo "reneg-sec 0" >> "$conf" +fi + +# Append extra server configurations +if [ ${#OVPN_EXTRA_SERVER_CONFIG[@]} -gt 0 ]; then + for i in "${OVPN_EXTRA_SERVER_CONFIG[@]}"; do + process_extra_config "$i" + done + echo -e "\n### Extra Configurations Below" >> "$conf" + cat $TMP_EXTRA_CONFIGFILE >> "$conf" +fi + +set +e + +# Clean-up duplicate configs +if diff -q "${bak_env:-}" "$OVPN_ENV" 2>/dev/null; then + echo "Removing duplicate back-up: $bak_env" + rm -fv "$bak_env" +fi +if diff -q "${bak:-}" "$conf" 2>/dev/null; then + echo "Removing duplicate back-up: $bak" + rm -fv "$bak" +fi + +echo "Successfully generated config" diff --git a/bin/ovpn_getclient b/bin/ovpn_getclient new file mode 100755 index 0000000..092aeea --- /dev/null +++ b/bin/ovpn_getclient @@ -0,0 +1,132 @@ +#!/bin/bash + +# +# Get an OpenVPN client configuration file +# + +if [ "$DEBUG" == "1" ]; then + set -x +fi + +set -e + +if [ -z "$OPENVPN" ]; then + export OPENVPN="$PWD" +fi +if ! source "$OPENVPN/ovpn_env.sh"; then + echo "Could not source $OPENVPN/ovpn_env.sh." + exit 1 +fi +if [ -z "$EASYRSA_PKI" ]; then + export EASYRSA_PKI="$OPENVPN/pki" +fi + +cn="$1" +parm="$2" + +if [ ! -f "$EASYRSA_PKI/private/${cn}.key" ]; then + echo "Unable to find \"${cn}\", please try again or generate the key first" >&2 + exit 1 +fi + +get_client_config() { + mode="$1" + echo " +client +nobind +dev $OVPN_DEVICE +remote-cert-tls server + +remote $OVPN_CN $OVPN_PORT $OVPN_PROTO" + if [ "$OVPN_PROTO" == "udp6" ]; then + echo "remote $OVPN_CN $OVPN_PORT udp" + fi + if [ "$OVPN_PROTO" == "tcp6" ]; then + echo "remote $OVPN_CN $OVPN_PORT tcp" + fi + for i in "${OVPN_EXTRA_CLIENT_CONFIG[@]}"; do + echo "$i" + done + if [ "$mode" == "combined" ]; then + echo " + +$(cat $EASYRSA_PKI/private/${cn}.key) + + +$(openssl x509 -in $EASYRSA_PKI/issued/${cn}.crt) + + +$(cat $EASYRSA_PKI/ca.crt) + +key-direction 1 + +$(cat $EASYRSA_PKI/ta.key) + +" + elif [ "$mode" == "separated" ]; then + echo " +key ${cn}.key +ca ca.crt +cert ${cn}.crt +tls-auth ta.key 1 +" + fi + + if [ "$OVPN_DEFROUTE" != "0" ];then + echo "redirect-gateway def1" + fi + + if [ -n "$OVPN_MTU" ]; then + echo "tun-mtu $OVPN_MTU" + fi + + if [ -n "$OVPN_TLS_CIPHER" ]; then + echo "tls-cipher $OVPN_TLS_CIPHER" + fi + + if [ -n "$OVPN_CIPHER" ]; then + echo "cipher $OVPN_CIPHER" + fi + + if [ -n "$OVPN_AUTH" ]; then + echo "auth $OVPN_AUTH" + fi + + if [ -n "$OVPN_OTP_AUTH" ]; then + echo "auth-user-pass" + echo "auth-nocache" + fi + + if [ "$OVPN_COMP_LZO" == "1" ]; then + echo "comp-lzo" + fi + + if [ -n "$OVPN_OTP_AUTH" ]; then + echo reneg-sec 0 + fi +} + +dir="$OPENVPN/clients/$cn" +case "$parm" in + "separated") + mkdir -p "$dir" + get_client_config "$parm" > "$dir/${cn}.ovpn" + cp "$EASYRSA_PKI/private/${cn}.key" "$dir/${cn}.key" + cp "$EASYRSA_PKI/ca.crt" "$dir/ca.crt" + cp "$EASYRSA_PKI/issued/${cn}.crt" "$dir/${cn}.crt" + cp "$EASYRSA_PKI/ta.key" "$dir/ta.key" + ;; + "" | "combined") + get_client_config "combined" + ;; + "combined-save") + mkdir -p "$dir" + get_client_config "combined" > "$dir/${cn}-combined.ovpn" + ;; + *) + echo "This script can produce the client configuration in two formats:" >&2 + echo " 1. combined (default): All needed configuration and cryptographic material is in one file (Use \"combined-save\" to write the configuration file in the same path as the separated parameter does)." >&2 + echo " 2. separated: Separated files." >&2 + echo "Please specify one of those options as second parameter." >&2 + ;; +esac diff --git a/bin/ovpn_getclient_all b/bin/ovpn_getclient_all new file mode 100755 index 0000000..f760495 --- /dev/null +++ b/bin/ovpn_getclient_all @@ -0,0 +1,25 @@ +#!/bin/bash +## @licence MIT +## @author Copyright (C) 2015 Robin Schneider + +if [ -z "$OPENVPN" ]; then + export OPENVPN="$PWD" +fi +if ! source "$OPENVPN/ovpn_env.sh"; then + echo "Could not source $OPENVPN/ovpn_env.sh." + exit 1 +fi +if [ -z "$EASYRSA_PKI" ]; then + export EASYRSA_PKI="$OPENVPN/pki" +fi + +pushd "$EASYRSA_PKI" +for name in issued/*.crt; do + name=${name%.crt} + name=${name#issued/} + if [ "$name" != "$OVPN_CN" ]; then + ovpn_getclient "$name" separated + ovpn_getclient "$name" combined-save + fi +done +popd diff --git a/bin/ovpn_initpki b/bin/ovpn_initpki new file mode 100755 index 0000000..11a3ccb --- /dev/null +++ b/bin/ovpn_initpki @@ -0,0 +1,43 @@ +#!/bin/bash + +# +# Initialize the EasyRSA PKI +# + +if [ "$DEBUG" == "1" ]; then + set -x +fi + +set -e + +source "$OPENVPN/ovpn_env.sh" + +# Specify "nopass" as arg[2] to make the CA insecure (not recommended!) +nopass=$1 + +# Provides a sufficient warning before erasing pre-existing files +easyrsa init-pki + +# CA always has a password for protection in event server is compromised. The +# password is only needed to sign client/server certificates. No password is +# needed for normal OpenVPN operation. +easyrsa build-ca $nopass + +easyrsa gen-dh +openvpn --genkey secret $EASYRSA_PKI/ta.key + +# Was nice to autoset, but probably a bad idea in practice, users should +# have to explicitly specify the common name of their server +#if [ -z "$cn"]; then +# #TODO: Handle IPv6 (when I get a VPS with IPv6)... +# ip4=$(dig +short myip.opendns.com @resolver1.opendns.com) +# ptr=$(dig +short -x $ip4 | sed -e 's:\.$::') +# +# [ -n "$ptr" ] && cn=$ptr || cn=$ip4 +#fi + +# For a server key with a password, manually init; this is autopilot +easyrsa build-server-full "$OVPN_CN" nopass + +# Generate the CRL for client/server certificates revocation. +easyrsa gen-crl diff --git a/bin/ovpn_listclients b/bin/ovpn_listclients new file mode 100755 index 0000000..47f4459 --- /dev/null +++ b/bin/ovpn_listclients @@ -0,0 +1,54 @@ +#!/bin/bash + +if [ -z "$OPENVPN" ]; then + export OPENVPN="$PWD" +fi +if ! source "$OPENVPN/ovpn_env.sh"; then + echo "Could not source $OPENVPN/ovpn_env.sh." + exit 1 +fi +if [ -z "$EASYRSA_PKI" ]; then + export EASYRSA_PKI="$OPENVPN/pki" +fi + +cd "$EASYRSA_PKI" + +if [ -e crl.pem ]; then + cat ca.crt crl.pem > cacheck.pem +else + cat ca.crt > cacheck.pem +fi + +echo "name,begin,end,status" +for name in issued/*.crt; do + path=$name + begin=$(openssl x509 -noout -startdate -in $path | awk -F= '{ print $2 }') + end=$(openssl x509 -noout -enddate -in $path | awk -F= '{ print $2 }') + + name=${name%.crt} + name=${name#issued/} + if [ "$name" != "$OVPN_CN" ]; then + # check for revocation or expiration + command="openssl verify -crl_check -CAfile cacheck.pem $path" + result=$($command) + if [ $(echo "$result" | wc -l) == 1 ] && [ "$(echo "$result" | grep ": OK")" ]; then + status="VALID" + else + result=$(echo "$result" | tail -n 1 | grep error | cut -d" " -f2) + case $result in + 10) + status="EXPIRED" + ;; + 23) + status="REVOKED" + ;; + *) + status="INVALID" + esac + fi + echo "$name,$begin,$end,$status" + fi +done + +# Clean +rm cacheck.pem diff --git a/bin/ovpn_otp_user b/bin/ovpn_otp_user new file mode 100755 index 0000000..dcebd4a --- /dev/null +++ b/bin/ovpn_otp_user @@ -0,0 +1,34 @@ +#!/bin/bash + +# +# Generate OpenVPN users via google authenticator +# + +if ! source "$OPENVPN/ovpn_env.sh"; then + echo "Could not source $OPENVPN/ovpn_env.sh." + exit 1 +fi + +if [ "x$OVPN_OTP_AUTH" != "x1" ]; then + echo "OTP authentication not enabled, please regenerate configuration using -2 flag" + exit 1 +fi + +if [ -z $1 ]; then + echo "Usage: ovpn_otp_user USERNAME" + exit 1 +fi + +# Ensure the otp folder is present +[ -d /etc/openvpn/otp ] || mkdir -p /etc/openvpn/otp + +# Binary is present in image, save an $user.google_authenticator file in /etc/openvpn/otp +if [ "$2" == "interactive" ]; then + # Authenticator will ask for other parameters. User can choose rate limit, token reuse policy and time window policy + # Always use time base OTP otherwise storage for counters must be configured somewhere in volume + google-authenticator --time-based --force -l "${1}@${OVPN_CN}" -s /etc/openvpn/otp/${1}.google_authenticator +else + # Skip confirmation if not running in interctive mode. Essential for integration tests. + google-authenticator --time-based --disallow-reuse --force --rate-limit=3 --rate-time=30 --window-size=3 \ + -l "${1}@${OVPN_CN}" -s /etc/openvpn/otp/${1}.google_authenticator --no-confirm +fi diff --git a/bin/ovpn_revokeclient b/bin/ovpn_revokeclient new file mode 100755 index 0000000..00fefd9 --- /dev/null +++ b/bin/ovpn_revokeclient @@ -0,0 +1,39 @@ +#!/bin/bash + +# +# Revoke a client certificate +# + +if [ "$DEBUG" == "1" ]; then + set -x +fi + +set -e + +if [ -z "$OPENVPN" ]; then + export OPENVPN="$PWD" +fi +if ! source "$OPENVPN/ovpn_env.sh"; then + echo "Could not source $OPENVPN/ovpn_env.sh." + exit 1 +fi +if [ -z "$EASYRSA_PKI" ]; then + export EASYRSA_PKI="$OPENVPN/pki" +fi + +cn="$1" + +if [ ! -f "$EASYRSA_PKI/private/${cn}.key" ]; then + echo "Unable to find \"${cn}\", please try again or generate the key first" >&2 + exit 1 +fi + +revoke_client_certificate(){ + easyrsa revoke "$1" + echo "Generating the Certificate Revocation List :" + easyrsa gen-crl + cp -f "$EASYRSA_PKI/crl.pem" "$OPENVPN/crl.pem" + chmod 644 "$OPENVPN/crl.pem" +} + +revoke_client_certificate "$cn" diff --git a/bin/ovpn_run b/bin/ovpn_run new file mode 100755 index 0000000..1899d8f --- /dev/null +++ b/bin/ovpn_run @@ -0,0 +1,132 @@ +#!/bin/bash + +# +# Run the OpenVPN server normally +# + +if [ "$DEBUG" == "1" ]; then + set -x +fi + +set -e + +cd $OPENVPN + +# bootstrap things? +if [[ "$BOOTSTRAP_CA" == "TRUE" ]]; then + echo "bootstrapping ca" + + #check if it already exist to not overwrite things + if [ -f "/etc/openvpn/ovpn_env.sh" ]; then + echo "config already initialized - skipping" + else + ovpn_genconfig -u $PROTOCOL://$VPN_HOSTNAME + source "$OPENVPN/ovpn_env.sh" + easyrsa init-pki + + easyrsa --batch --req-cn=$CA_SERVERNAME build-ca nopass + + easyrsa gen-dh + openvpn --genkey secret $EASYRSA_PKI/ta.key + # For a server key with a password, manually init; this is autopilot + easyrsa build-server-full "$OVPN_CN" nopass + + # Generate the CRL for client/server certificates revocation. + easyrsa gen-crl + fi +fi + + +# Build runtime arguments array based on environment +USER_ARGS=("${@}") +ARGS=() + +IPTABLES="iptables-nft" + +# Checks if ARGS already contains the given value +function hasArg { + local element + for element in "${@:2}"; do + [ "${element}" == "${1}" ] && return 0 + done + return 1 +} + +# Adds the given argument if it's not already specified. +function addArg { + local arg="${1}" + [ $# -ge 1 ] && local val="${2}" + if ! hasArg "${arg}" "${USER_ARGS[@]}"; then + ARGS+=("${arg}") + [ $# -ge 1 ] && ARGS+=("${val}") + fi +} + +# set up iptables rules and routing +# this allows rules/routing to be altered by supplying this function +# in an included file, such as ovpn_env.sh +function setupIptablesAndRouting { + $IPTABLES -t nat -C POSTROUTING -s $OVPN_SERVER -o $OVPN_NATDEVICE -j MASQUERADE 2>/dev/null || { + $IPTABLES -t nat -A POSTROUTING -s $OVPN_SERVER -o $OVPN_NATDEVICE -j MASQUERADE + } + for i in "${OVPN_ROUTES[@]}"; do + $IPTABLES -t nat -C POSTROUTING -s "$i" -o $OVPN_NATDEVICE -j MASQUERADE 2>/dev/null || { + $IPTABLES -t nat -A POSTROUTING -s "$i" -o $OVPN_NATDEVICE -j MASQUERADE + } + done +} + + +addArg "--config" "$OPENVPN/openvpn.conf" + +source "$OPENVPN/ovpn_env.sh" + +mkdir -p /dev/net +if [ ! -c /dev/net/tun ]; then + mknod /dev/net/tun c 10 200 +fi + +if [ -d "$OPENVPN/ccd" ]; then + addArg "--client-config-dir" "$OPENVPN/ccd" +fi + +# When using --net=host, use this to specify nat device. +[ -z "$OVPN_NATDEVICE" ] && OVPN_NATDEVICE=eth0 + +# Setup NAT forwarding if requested +if [ "$OVPN_DEFROUTE" != "0" ] || [ "$OVPN_NAT" == "1" ] ; then + # call function to setup iptables rules and routing + # this allows rules to be customized by supplying + # a replacement function in, for example, ovpn_env.sh + setupIptablesAndRouting +fi + +# Use a copy of crl.pem as the CRL Needs to be readable by the user/group +# OpenVPN is running as. Only pass arguments to OpenVPN if it's found. +if [ "$EASYRSA_PKI/crl.pem" -nt "$OPENVPN/crl.pem" ]; then + cp -f "$EASYRSA_PKI/crl.pem" "$OPENVPN/crl.pem" + chmod 644 "$OPENVPN/crl.pem" +fi + +if [ -r "$OPENVPN/crl.pem" ]; then + addArg "--crl-verify" "$OPENVPN/crl.pem" +fi + +ip -6 route show default 2>/dev/null +if [ $? = 0 ]; then + echo "Checking IPv6 Forwarding" + if [ "$(