Building an Active Directory Authenticated and Managed OpenVPN Server Part 3

Now that OpenVPN was all set up, the only thing left to do was the Automation. The script that I created, takes care of the certificate/key creation of the users, the configuration customization, the configuration delivery, the Certificate Revoke List creation and configuration updates. Since this article is almost exclusively about one script, I will first loose few words about each of the main functions and post the entire script afterwards.

Getting the VPN users

One of the more important parts of the script is getting the list of VPN users. The function uses winbind to get the users in the VPN group in the Active Directory and cuts the output into a comma separated list. For this to work, the computer needs to be a member of the Active Directory. The function changes a variable if winbind fails or the list of users is empty. The variable will be checked in the script after the function is used. The Administrator gets notified by E-Mail in case of an error. In order for the E-Mail part to work, you need to configure the MTA on the System. On my server Exim4 had trouble resolving some aliases on my Mail Server, so i replaced it with SSMTP.

function func_VPN_get_user_list {
    VPN_user_list=""
    VPN_user_list=$(wbinfo --group-info $VPN_AD_Group | cut -d ':' -f 4)
    #output is a comma seperated list of VPN Users
    if [ -z "$VPN_user_list" ]
    then
        VPN_ERR=1
        echo -e 'There is a problem with the VPN script -- user list is empty' | mutt -s "ERROR the OpenVPN user script encountered a problem" --openvpn@$AD_NAME
        echo $(date +"%d.%m.%Y %T") ERROR VPN user list is empty, aborting script >> $VPN_userscript_log
    fi
}

Creating the individual configuration files

This function is called when a new VPN user is found and the configuration files for all users are updated. It copies every ovpn file in the template folder to the users folder and appends all necessary certificates and keys. After creating the files, it sends a welcome E-Mail to the user with the configuration files as attachment. The E-Mails will be sent to the address “username [at] domainname [dot] tl”. So if your E-Mail Addresses have another naming scheme this needs to be changed. Furthermore E-Mail is only a reasonably save way to deliver these files, if the Mail Server is on your local network and all user Access is only possible through encrypted connections.

function func_VPN_create_config {
    #This function creates the OpenVPN configuration files for every user.
    #The user specific configuration files are created from nomal OpenVPN configs and get all necesarry certificates and keys appended.
    #After this all files are E-Mailed to the user. E-Mail might not be a good way to distribute these files in every environment.
    VPN_Mailattach=""
    VPN_config_usr=$1
    for VPN_templateloc in ${VPN_template_dir}/*.ovpn; do
        if [ ! "$(ls -A ${VPN_template_dir}/*.ovpn)" ]
        then
            #check if there are actually any templates
            echo ERROR! $(date +"%d.%m.%Y %T") there are no templates in the directory $VPN_temp_dir >> $VPN_userscript_log
            echo ERROR! $(date +"%d.%m.%Y %T") there are no templates in the directory $VPN_temp_dir
            exit
        else
            #use ${VPN_templateloc##*/} to get just the filename of the template
            echo creating configuration file ${VPN_config_usr}_${VPN_templateloc##*/} >> $VPN_userscript_log
            echo creating configuration file ${VPN_config_usr}_${VPN_templateloc##*/}
            echo VPN user: ${VPN_config_usr}
            echo Client dir: ${VPN_client_dir}
            echo -----------
            echo copieing to ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            echo -----------
            cp $VPN_templateloc ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            echo "" >> ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            cat $VPN_CA_CERT >> ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            echo "" >> ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            #While many webgui solutions i used had this in the client config, OpenVPN doku says its only needed on the server side
            #echo   >> ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            #cat $VPN_DH_KEY >> ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            #echo   >> ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            echo "" >> ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            cat $VPN_TLS_KEY >> ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            echo "" >> ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            echo "" >> ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            cat  ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}.crt >> ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            echo "" >> ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            echo "" >> ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            cat ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}.key >> ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            echo "" >> ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            echo finished creating configuration file ${VPN_config_usr}_${VPN_templateloc##*/} >> $VPN_userscript_log
        fi
        #list all Config loacations in a variable, so we wont have to retrieve them again later on
        export VPN_Mailattach="$VPN_Mailattach -a ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}"
    done
    echo mailattach outside: $VPN_Mailattach    
    echo $(date +"%d.%m.%Y %T") done creating files for user ${VPN_config_usr} >> $VPN_userscript_log
    #Send all config files to the users E-Mail address
    #This assumes the user has a Mail-Account as username@$AD_NAME
    #mutt requires you to have fully configured MTA on the Server this script is running on
    #the MTA on my server is limited to sending Mails to my local mail server and does not accept incoming mail        
    echo -e 'Hello '${VPN_config_usr}',\n
    Copy the desired config file to the OpenVPN config folder. \n
    For WinXP and Vista/Win7 32 Bit this is:"C:\\Program Files\\OpenVPN\\config"\n
    For Win Vista/Win7 64Bit this is:"C:\\Program Files(x86)\\OpenVPN\\config"\n
    If you do not know which config to choose, check the internal section of https://www.skelleton.net \n
    This file contains your private VPN key, please store it safely!' | mutt -s "Your new OpenVPN config files for ${AD_NAME}"$VPN_Mailattach -- ${VPN_config_usr}@$AD_NAME
}

Adding new users

This function uses the list of users in the vpn group and matches it against the folder names in the client directory. If there is a user name, that does not match any folder name in there, the folder will be created. After that the key and certificate for the user will be created with OpenSSL and once that is done, the function to create the configuration files is called.

function func_VPN_new_users {
    #This function checks if a Client config directory exists for every user in the group. 
    #If not create the dir, certificates and configs.

    #The function func_VPN_get_user_list has to be run before this function
    if [ -z "$VPN_user_list" ]
    then
        VPN_ERR=1
        #echo -e 'There is a problem with the VPN script -- user list is empty' | mutt -s "ERROR the OpenVPN user script encountered a problem" --openvpn@$AD_NAME
        echo $(date +"%d.%m.%Y %T") ERROR VPN user list is empty, aborting script >> $VPN_userscript_log
        echo $(date +"%d.%m.%Y %T") ERROR VPN user list is empty, aborting script
    fi
    echo $(date +"%d.%m.%Y %T") checking for new users
    #input for the for loop is a comma seperated list, so we have to tell it, that the delim is ","
    for i in ${VPN_user_list//,/ }; do
        echo "Checking if ccd for $i" exists
        #check if the user directory already exists, create it if not else move on
        echo ${VPN_client_dir}/$i
        if [ ! -d "${VPN_client_dir}/${i}" ]
        then
            echo $(date +"%d.%m.%Y %T") Found new VPN user $i creating key certs and configs.... >> $VPN_userscript_log
            echo $(date +"%d.%m.%Y %T") Found new VPN user $i creating key certs and configs....
            mkdir "${VPN_client_dir}/${i}"
            #create the key and cert for the user after creating his dir
            #the -nodes in the request specifies, that the key will not be encryptes and thus will not require a password
            #dont use - nodes if you do not have another form of authentication in your OpenVPN
            #Batch mode is important for Automated Scripts
            #it would probably be enough to state only cn and email in the subject, since the other values are also the defaults in my ssl config
            #openssl req -nodes -new -keyout ${VPN_client_dir}/${i}/${i}.key -out ${VPN_client_dir}/${i}/${i}_csr.pem -rand $VPN_RANDFILE 4096 -subj"/C=DE/ST=Skelletonia/O=skelleton.net/OU=OpenVPN_Users/CN=${i}/emailAddress=${i}@${AD_NAME}" -batch -config $KEY_CONFIG
            echo creating certificate request and key
            openssl req -nodes -new -keyout ${VPN_client_dir}/${i}/${i}.key -out ${VPN_client_dir}/${i}/${i}_csr.pem -rand $VPN_RANDFILE -subj "/C=DE/ST=Skelletonia/O=skelleton.net/OU=OpenVPN_Users/CN=${i}/emailAddress=${i}@${AD_NAME}" -batch -config $KEY_CONFIG
            echo signing request
            openssl ca -out ${VPN_client_dir}/${i}/${i}.crt -in ${VPN_client_dir}/${i}/${i}_csr.pem -batch -config $KEY_CONFIG 
            chmod 0600 ${VPN_client_dir}/${i}/${i}.key
            #build the config file(s) for all templates found in $VPN_template_dir
            func_VPN_create_config $i
            #notify the OpenVPN admin
            echo -e 'New user added to OpenVPN' | mutt -s "The user $i has been added to OpenVPN" -- openvpn@$AD_NAME
        elif [ -d "${VPN_client_dir}/${i}" ]
        then
            echo directory ${VPN_client_dir}/${i} exists
        else
            echo check if directory ${VPN_client_dir}/${i} exists failed
        fi
    done
    func_VPN__archive_certs
    echo $(date +"%d.%m.%Y %T") done checking for new users
}

Revoking certificates and creating the Certificate Revoke List

This function checks every folder in the client directory for a corresponding user in the OpenVPN group. If none is found, the certificate will be revoked and a new certificate revoke list will be created at the end of the check. A new certificate revoke list will also be created if the current CRL is too old.

function func_VPN_revoke {
    #This function revokes the certificates for all users, who are not in the AD group anymore
    echo $(date +"%d.%m.%Y %T") checking if certificates need to be revoked
    NEW_CRL=0
    for VPN_ccd in ${VPN_client_dir}/*; do
        VPN_ccd=${VPN_ccd%*/}
        #if [ ! ${VPN_ccd##*/} in $VPN_user_list ]; then
        if echo $VPN_user_list | grep -q ${VPN_ccd##*/}
        then
            echo ${VPN_ccd##*/} is in list: $VPN_user_list
        else
            NEW_CRL=1
            echo revoking ${VPN_client_dir}/${VPN_ccd##*/}/${VPN_ccd##*/}.crt
            echo userlist $VPN_user_list
            openssl ca -revoke ${VPN_client_dir}/${VPN_ccd##*/}/${VPN_ccd##*/}.crt -batch -config $KEY_CONFIG
            rm -R $VPN_ccd
            echo -e "Hello ${VPN_ccd##*/}, your access to skelleton.net OpenVPN has been revoked. This is an automated message. If you believe there has been a mistake contact openvpn@${AD_NAME}" | mutt -s "OpenVPN access has been revoked." -- ${VPN_ccd##*/}@$AD_NAME
            echo $(date +"%d.%m.%Y %T") $Vpn_ccd has been deleted and user certificates have been revoked, because the user is no longer in the Active Directory group
            echo $(date +"%d.%m.%Y %T") $Vpn_ccd has been deleted and user certificates have been revoked, because the user is no longer in the Active Directory group >> $VPN_userscript_log
        fi
    done
    #create CRL if a certificate was revoked or if the current CRL is about to expire
    echo NEW_CRL: $NEW_CRL
    if [ $NEW_CRL == 1 ]
    then
        echo certificate revoked creating new crl
        func_VPN_create_CRL
    elif [ -e "$(find $CRLPATH -mmin +5)" ]
    then
        echo crl to old creating new one
        func_VPN_create_CRL
    fi
    echo $(date +"%d.%m.%Y %T") done checking if certificates need to be revoked
}

Updating the configuration files

This function calls the function to create configuration files for all users. In the script it is only called if the file “new_configs.txt” can be found in the template directory.

function func_VPN_update_configs {
    #This function creates new config files for all users. It assumes that all current users have a client config dir
    echo $(date +"%d.%m.%Y %T") creating new configs for all users
    for VPN_ccd in ${VPN_client_dir}/*; do
        func_VPN_create_config ${VPN_ccd##*/}
    done
    echo -e "All client configurations for server $VPN_NAME have been updated and distributed" | mutt -s "OpenVPN configuration files updated for $VPN_NAME " -- openvpn@$AD_NAME
    echo $(date +"%d.%m.%Y %T") done updating configs
    echo $(date +"%d.%m.%Y %T") updated all user configuration files >> $VPN_userscript_log
}

Preparing the E-Mail System (mutt and ssmtp)

At first I used Exim4 as MTA, but it had some problems with resolving some of my mail aliases. Because of this i switched to ssmtp for E-Mail delivery. Ssmtp is not a full featured MTA, it can not deliver mail to the local system. Since I only need to send mail to my Mail Server, it is perfect. The configuration is done in one file and very straight forward.
I use Mutt as a mail client, because it offers all the functions I need and it is preinstalled on Debian.

And finally the complete script

#!/bin/bash

# This Script querys the members of an AD group and performs actions for each member
# The computer has to be a Member of the Active Directory since winbind is used
# This Scrip assumes, that you use an Active Directory Group for your OpenVPN authorisation

#Fixed Variables
VPN_AD_Group=OpenVPN_Users
VPN_NAME=openvpn.skelleton.net
VPN_dir="/etc/openvpn/${VPN_NAME}"
VPN_client_dir="${VPN_dir}/clients"
VPN_template_dir="${VPN_dir}/templates"
VPN_userscript_log="${VPN_dir}/logs/VPN_userscript.log"
VPN_UPDATE_ALL="$VPN_template_dir/new_configs.txt"
KEY_CONFIG="${VPN_dir}/CA_OpenVPN_skelleton.net/skelleton.net_ca_openvpn.cnf"
VPN_CA_CERT="${VPN_dir}/cacert_root.pem"
VPN_DH_KEY="${VPN_dir}/DH4096.pem"
VPN_TLS_KEY="${VPN_dir}/private/ta.key"
AD_NAME=skelleton.net
VPN_RANDFILE="${VPN_dir}/CA_OpenVPN_skelleton.net/private/.rand"
CRLPATH="${VPN_dir}/CA_OpenVPN_skelleton.net/crl_OpenVPN.pem"
VPN_ERR=0
CA_newcerts_dir="${VPN_dir}/CA_OpenVPN_skelleton.net/newcerts"
CA_certs_dir="${VPN_dir}/CA_OpenVPN_skelleton.net/certs"

#Define Functions
function func_VPN__archive_certs {
#this function archives issued certificates by moving them from the newcerts to certs and and link the hash value as index

echo $(date +"%d.%m.%Y %T") Starting Archiving of certificates in the directory $CA_newcerts_dir >> $VPN_userscript_log
if [ ! "$(ls -A ${CA_newcerts_dir}/*.pem)" ]; then
                #check if there are actually any certs in the newcerts folder
                echo no new certificates
                echo $(date +"%d.%m.%Y %T") Ending Archiving there are no new certificates in the directory $CA_newcerts_dir >> $VPN_userscript_log
else
    for CertSN in ${CA_newcerts_dir}/*.pem; do
        echo certsn: $CertSN
        echo moving $certSN to ${CA_certs_dir}/${CertSN##*/}
        mv $CertSN ${CA_certs_dir}/${CertSN##*/} || echo $(date +"%d.%m.%Y %T") ERROR_ARC while moving certificate $CERTSN into $CA_newcerts_dir >> $VPN_userscript_log
        #The Number at the end should increase, if the cert exists already -> cert has been reissued
        indexfile=$(openssl x509 -hash -noout -in ${CA_certs_dir}/${CertSN##*/})
        if [ ! -e ${CA_certs_dir}/${indexfile}.0 ]; then
            echo the certificate has not been issued before creating link ${indexfile}.0
            ln -s ${CA_certs_dir}/${CertSN##*/} ${CA_certs_dir}/${indexfile}.0 && echo $(date +"%d.%m.%Y %T") Archived Certificate ${CertSN##*/} >> $VPN_userscript_log || echo $(date +"%d.%m.%Y %T") ERROR_ARC error while creating symlink for certificate ${CertSN##*/} >> $cert_archiver_log
        else
            echo certificate has been issued before incrementing index
            echo $(date +"%d.%m.%Y %T") Certificate ${CertSN##*/} has been reissued, incermenting index >> $VPN_userscript_log
            i=0
            success=0
            while [ ! $success == 1 ]
            do
                i=$((i + 1))
                echo check if ${indexfile}.$i exists
                if [ ! -e ${CA_certs_dir}/${indexfile}.$i ]; then
                    ln -s ${CA_certs_dir}/${CertSN##*/} ${CA_certs_dir}/${indexfile}.$i && echo $(date +"%d.%m.%Y %T") Archived Certificate ${CertSN##*/} >> $VPN_userscript_log || echo $(date +"%d.%m.%Y %T") ERROR_ARC error while creating symlink for certificate ${CertSN##*/} >> $VPN_userscript_log
                    success=1
                fi
            done
        fi
    done;
fi    
echo $(date +"%d.%m.%Y %T") Ending Archiving >> $VPN_userscript_log
}
function func_VPN_get_user_list {
    VPN_user_list=""
    VPN_user_list=$(wbinfo --group-info $VPN_AD_Group | cut -d ':' -f 4)
    #output is a comma seperated list of VPN Users
    if [ -z "$VPN_user_list" ]
    then
        VPN_ERR=1
        echo -e 'There is a problem with the VPN script -- user list is empty' | mutt -s "ERROR the OpenVPN user script encountered a problem" --openvpn@$AD_NAME
        echo $(date +"%d.%m.%Y %T") ERROR VPN user list is empty, aborting script >> $VPN_userscript_log
    fi
}

function func_VPN_create_config {
    #This function creates the OpenVPN configuration files for every user.
    #The user specific configuration files are created from nomal OpenVPN configs and get all necesarry certificates and keys appended.
    #After this all files are E-Mailed to the user. E-Mail might not be a good way to distribute these files in every environment.
    VPN_Mailattach=""
    VPN_config_usr=$1
    for VPN_templateloc in ${VPN_template_dir}/*.ovpn; do
        if [ ! "$(ls -A ${VPN_template_dir}/*.ovpn)" ]
        then
            #check if there are actually any templates
            echo ERROR! $(date +"%d.%m.%Y %T") there are no templates in the directory $VPN_temp_dir >> $VPN_userscript_log
            echo ERROR! $(date +"%d.%m.%Y %T") there are no templates in the directory $VPN_temp_dir
            exit
        else
            #use ${VPN_templateloc##*/} to get just the filename of the template
            echo creating configuration file ${VPN_config_usr}_${VPN_templateloc##*/} >> $VPN_userscript_log
            echo creating configuration file ${VPN_config_usr}_${VPN_templateloc##*/}
            echo VPN user: ${VPN_config_usr}
            echo Client dir: ${VPN_client_dir}
            echo -----------
            echo copieing to ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            echo -----------
            cp $VPN_templateloc ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            echo "" >> ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            cat $VPN_CA_CERT >> ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            echo "" >> ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            #While many webgui solutions i used had this in the client config, OpenVPN doku says its only needed on the server side
            #echo   >> ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            #cat $VPN_DH_KEY >> ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            #echo   >> ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            echo "" >> ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            cat $VPN_TLS_KEY >> ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            echo "" >> ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            echo "" >> ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            cat  ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}.crt >> ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            echo "" >> ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            echo "" >> ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            cat ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}.key >> ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            echo "" >> ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}
            echo finished creating configuration file ${VPN_config_usr}_${VPN_templateloc##*/} >> $VPN_userscript_log
        fi
        #list all Config loacations in a variable, so we wont have to retrieve them again later on
        export VPN_Mailattach="$VPN_Mailattach -a ${VPN_client_dir}/${VPN_config_usr}/${VPN_config_usr}_${VPN_templateloc##*/}"
    done
    echo mailattach outside: $VPN_Mailattach    
    echo $(date +"%d.%m.%Y %T") done creating files for user ${VPN_config_usr} >> $VPN_userscript_log
    #Send all config files to the users E-Mail address
    #This assumes the user has a Mail-Account as username@$AD_NAME
    #mutt requires you to have fully configured MTA on the Server this script is running on
    #the MTA on my server is limited to sending Mails to my local mail server and does not accept incoming mail        
    echo -e 'Hello '${VPN_config_usr}',\n
    Copy the desired config file to the OpenVPN config folder. \n
    For WinXP and Vista/Win7 32 Bit this is:"C:\\Program Files\\OpenVPN\\config"\n
    For Win Vista/Win7 64Bit this is:"C:\\Program Files(x86)\\OpenVPN\\config"\n
    If you do not know which config to choose, check the internal section of https://www.skelleton.net \n
    This file contains your private VPN key, please store it safely!' | mutt -s "Your new OpenVPN config files for ${AD_NAME}"$VPN_Mailattach -- ${VPN_config_usr}@$AD_NAME
}
function func_VPN_create_CRL {
        echo creating CRL $CRLPATH 
        openssl ca -gencrl -config $KEY_CONFIG -out $CRLPATH && echo $(date +"%d.%m.%Y %T") New CRL created >> $VPN_userscript_log || echo $(date +"%d.%m.%Y %T") ERROR_CRL error creating CRL >> $VPN_userscript_log
}
function func_VPN_new_users {
    #This function checks if a Client config directory exists for every user in the group. 
    #If not create the dir, certificates and configs.

    #The function func_VPN_get_user_list has to be run before this function
    if [ -z "$VPN_user_list" ]
    then
        VPN_ERR=1
        #echo -e 'There is a problem with the VPN script -- user list is empty' | mutt -s "ERROR the OpenVPN user script encountered a problem" --openvpn@$AD_NAME
        echo $(date +"%d.%m.%Y %T") ERROR VPN user list is empty, aborting script >> $VPN_userscript_log
        echo $(date +"%d.%m.%Y %T") ERROR VPN user list is empty, aborting script
    fi
    echo $(date +"%d.%m.%Y %T") checking for new users
    #input for the for loop is a comma seperated list, so we have to tell it, that the delim is ","
    for i in ${VPN_user_list//,/ }; do
        echo "Checking if ccd for $i" exists
        #check if the user directory already exists, create it if not else move on
        echo ${VPN_client_dir}/$i
        if [ ! -d "${VPN_client_dir}/${i}" ]
        then
            echo $(date +"%d.%m.%Y %T") Found new VPN user $i creating key certs and configs.... >> $VPN_userscript_log
            echo $(date +"%d.%m.%Y %T") Found new VPN user $i creating key certs and configs....
            mkdir "${VPN_client_dir}/${i}"
            #create the key and cert for the user after creating his dir
            #the -nodes in the request specifies, that the key will not be encryptes and thus will not require a password
            #dont use - nodes if you do not have another form of authentication in your OpenVPN
            #Batch mode is important for Automated Scripts
            #it would probably be enough to state only cn and email in the subject, since the other values are also the defaults in my ssl config
            #openssl req -nodes -new -keyout ${VPN_client_dir}/${i}/${i}.key -out ${VPN_client_dir}/${i}/${i}_csr.pem -rand $VPN_RANDFILE 4096 -subj"/C=DE/ST=Skelletonia/O=skelleton.net/OU=OpenVPN_Users/CN=${i}/emailAddress=${i}@${AD_NAME}" -batch -config $KEY_CONFIG
            echo creating certificate request and key
            openssl req -nodes -new -keyout ${VPN_client_dir}/${i}/${i}.key -out ${VPN_client_dir}/${i}/${i}_csr.pem -rand $VPN_RANDFILE -subj "/C=DE/ST=Skelletonia/O=skelleton.net/OU=OpenVPN_Users/CN=${i}/emailAddress=${i}@${AD_NAME}" -batch -config $KEY_CONFIG
            echo signing request
            openssl ca -out ${VPN_client_dir}/${i}/${i}.crt -in ${VPN_client_dir}/${i}/${i}_csr.pem -batch -config $KEY_CONFIG 
            chmod 0600 ${VPN_client_dir}/${i}/${i}.key
            #build the config file(s) for all templates found in $VPN_template_dir
            func_VPN_create_config $i
            #notify the OpenVPN admin
            echo -e 'New user added to OpenVPN' | mutt -s "The user $i has been added to OpenVPN" -- openvpn@$AD_NAME
        elif [ -d "${VPN_client_dir}/${i}" ]
        then
            echo directory ${VPN_client_dir}/${i} exists
        else
            echo check if directory ${VPN_client_dir}/${i} exists failed
        fi
    done
    func_VPN__archive_certs
    echo $(date +"%d.%m.%Y %T") done checking for new users
}
function func_VPN_revoke {
    #This function revokes the certificates for all users, who are not in the AD group anymore
    echo $(date +"%d.%m.%Y %T") checking if certificates need to be revoked
    NEW_CRL=0
    for VPN_ccd in ${VPN_client_dir}/*; do
        VPN_ccd=${VPN_ccd%*/}
        #if [ ! ${VPN_ccd##*/} in $VPN_user_list ]; then
        if echo $VPN_user_list | grep -q ${VPN_ccd##*/}
        then
            echo ${VPN_ccd##*/} is in list: $VPN_user_list
        else
            NEW_CRL=1
            echo revoking ${VPN_client_dir}/${VPN_ccd##*/}/${VPN_ccd##*/}.crt
            echo userlist $VPN_user_list
            openssl ca -revoke ${VPN_client_dir}/${VPN_ccd##*/}/${VPN_ccd##*/}.crt -batch -config $KEY_CONFIG
            rm -R $VPN_ccd
            echo -e "Hello ${VPN_ccd##*/}, your access to skelleton.net OpenVPN has been revoked. This is an automated message. If you believe there has been a mistake contact openvpn@${AD_NAME}" | mutt -s "OpenVPN access has been revoked." -- ${VPN_ccd##*/}@$AD_NAME
            echo $(date +"%d.%m.%Y %T") $Vpn_ccd has been deleted and user certificates have been revoked, because the user is no longer in the Active Directory group
            echo $(date +"%d.%m.%Y %T") $Vpn_ccd has been deleted and user certificates have been revoked, because the user is no longer in the Active Directory group >> $VPN_userscript_log
        fi
    done
    #create CRL if a certificate was revoked or if the current CRL is about to expire
    echo NEW_CRL: $NEW_CRL
    if [ $NEW_CRL == 1 ]
    then
        echo certificate revoked creating new crl
        func_VPN_create_CRL
    elif [ -e "$(find $CRLPATH -mmin +5)" ]
    then
        echo crl to old creating new one
        func_VPN_create_CRL
    fi
    echo $(date +"%d.%m.%Y %T") done checking if certificates need to be revoked
}
function func_VPN_update_configs {
    #This function creates new config files for all users. It assumes that all current users have a client config dir
    echo $(date +"%d.%m.%Y %T") creating new configs for all users
    for VPN_ccd in ${VPN_client_dir}/*; do
        func_VPN_create_config ${VPN_ccd##*/}
    done
    echo -e "All client configurations for server $VPN_NAME have been updated and distributed" | mutt -s "OpenVPN configuration files updated for $VPN_NAME " -- openvpn@$AD_NAME
    echo $(date +"%d.%m.%Y %T") done updating configs
    echo $(date +"%d.%m.%Y %T") updated all user configuration files >> $VPN_userscript_log
}
#run the script
echo $(date +"%d.%m.%Y %T") Starting VPN_userscript >> $VPN_userscript_log

func_VPN_get_user_list
if [ $VPN_ERR == 0 ]
then
    func_VPN_new_users
    func_VPN_revoke
    if [ -f $VPN_UPDATE_ALL ]
    then
        func_VPN_update_configs
        rm $VPN_UPDATE_ALL
    fi
else
    echo $(date +"%d.%m.%Y %T") ERROR the script encountered an error while fetching VPN users
fi

echo $(date +"%d.%m.%Y %T") VPN_userscript finished running >> $VPN_userscript_log

Site that where helpful in wrting this Article Series

Paul Eggleton’s Weblog
The OpenVPN Documentation
The OpenSSL Documentation
This Article on macfreek.nl

Leave a Reply

Your email address will not be published. Required fields are marked *