Building a tiered access OpenVPN gateway on OpenBSD
Introduction
This is a work in progress and largely untested, don't try it at home
I recently needed to expand the scope of the OpenVPN gateway I administer to allow clients to have very limited access over it to a server on our network while still allowing certain employees more access. I did an initial quick and dirty config using client-config-directories but I wasn't satisfied with that from a maintenance point of view. This page will document my improved approach, using learn-address and client-connect scripts.
The Design
I wanted to add the concept of 'profiles' to OpenVPN to allow me to assign users access depending on their certificate's common name. I also wanted it to be scripted only using OpenBSD's shell commands so that no additional software with possible security holes or upgrade issues could interfere with the VPN's operation.
I have 2 configuration directories: profile and profileoptions. Profiles contains a set of files, each representing a profile. Each Profile file contains a list of common names, each on a new line, that delineates who is a member of that profile. A common name can only appear in one profile file at any one time, multiple matches are an error.
The other directory, profileoptions, contains the openvpn options for that profile. These can be any of the following openvpn config directives: push, push-reset, iroute, ifconfig-push and config. See the OpenVPN Man Page for further details.
In addition, the pf.conf file should have a table for each vpn profile. The tables should be named according to the format "vpn<ProfileName>" so client's connecting as an admin have their IP in the vpnadmin table.
The Scripts
client-config.sh
#!/bin/sh # This is an openvpn client-connect script that pushes routes or other # openvpn client config stuff to a newly connected client depending on # their detected 'profile'. # $1 is tempfile # everything else is passed via environment variables. common_name is # all we care about in this script. Profiles=`grep -l "^${common_name}$" profiles/*` if [ $? -ne 0 ]; then echo "No profile matched" exit 1 fi MatchCount=`echo $Profiles | awk '{ printf "%d", NF }'` if [ $MatchCount -gt 1 ]; then echo "Multiple profiles matched, wtf" exit 1 fi Profile=`echo $Profiles | awk -F / '{ print $NF }'` ProfileConfig="profileoptions/${Profile}" if [ -f $ProfileConfig ]; then cp $ProfileConfig $1 else echo "Config for profile $Profile not found" exit 1 fi
listen-address.sh
#!/bin/sh # This is an openvpn learn-address script that modifies PF rules dynamically # and supports 'profiles' to allow you to manage client connections without # the headache of assigning static IPs. # $1 is add/update/delete # $2 is IP/Subnet/MAC # $3 is certificate's common name Profiles=`grep -l "^${3}$" profiles/*` if [ $? -ne 0 ]; then echo "No profile matched" exit 1 fi MatchCount=`echo $Profiles | awk '{ printf "%d", NF }'` if [ $MatchCount -gt 1 ]; then echo "Multiple profiles matched, wtf" exit 1 fi Profile=`echo $Profiles | awk -F / '{ print $NF }'` Table="vpn${Profile}" if [ $1 != "delete" ]; then # if we're adding or updating echo "pfctl -t ${Table} -T add $2" fi if [ $1 != "add" ]; then # if we're updating or deleting echo "pfctl -t ${Table} -T delete $2" fi