#!/bin/sh # vpldpf-connect - OpenVPN, OpenLDAP and PF for managing VPN access # vpn + ldap + pf = vpldpf # this script creates PF rules for a user when they connect. Without # pf rules they'll fall under the firewall's default deny policy and wont # have access to anything ############## License #################### #Copyright (c) <2009>, #All rights reserved. # #Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: # * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. # * Neither the name of the nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. #THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ############################################### #about ldap searching for roles #find the dn of the uid we want to check #search roles to see what they've been granted access to #for each grant, get the dbgAddress attribute and make a pf rule for it #about pf and anchors #pf.conf has "anchor vpnusers/*" #each user has a "sub-anchor" file in the /etc/pfanchors/vpnusers directory #to load it, we call pfctl -a vpnusers/USERNAME -f /etc/pfanchors/vpnusers/USERNAME #users may have multiple connections, so this script intentionally ignores pre-existing rules. #the disconnect script will remove rules that contain the User's source IP and leave others. #openvpn uses ping timers to expire user sessions, it doesn't actively disconnect users. #as such openvpn-status.log is sometimes not accurate for the lenth of the timeout, but it can be #counted on to reliably call the disconnect script when a user isn't connected after a bit ############## globals ################# SIP=${ifconfig_pool_remote_ip} #source IP passed from openvpn NAME=${common_name} #authenticated username passed from openvpn SERVICES='/etc/vpldpf.services' PFANCHORDIR='/etc/pfanchors/vpnusers' PFANCHORFILE='/etc/pfanchors/vpnusers.anchor' LDAPURI='ldaps://my.ldap.server.com' BINDDN='uid=anonymous,dc=example,dc=com' BINDPW='bindpw' ############## functions ################### function log { echo "`date` VPLDPF :: ${NAME} :: $@" } function findUserDN { #given the vpn ID, return the dn that correponds to the LDAP UID for that person fudNAME=$1 fudSEARCHBASE='ou=staff,dc=example,dc=com' fudUID=`ldapsearch -LLL -x -H ${LDAPURI} -D ${BINDDN} -b ${fudSEARCHBASE} -w ${BINDPW} "(uid=${fudNAME})" dn 2>&1 | head -n1` fudOUT=$? echo "${fudUID}" | sed -e 's/^dn: //' return "${fudOUT}" } function findRoles { #given the DN of a user, return the roles that user belongs to #I don't think I'll need this function, but I'm writing it jik frDN=$1 frSEARCHBASE='ou=roles,dc=example,dc=com' frROLES=`ldapsearch -LLL -x -H ${LDAPURI} -D ${BINDDN} -b ${frSEARCHBASE} -w ${BINDPW} "(&(objectclass=dbgRole)(member=${frDN}))" dn 2>&1` frOUT=$? echo "${frROLES}" | sed -e 's/^dn: //' return "${frOUT}" } function findGrants { #given the DN of a user, return all of his dbgGrants fgDN=$1 fgSEARCHBASE='ou=roles,dc=example,dc=com' fgGRANTS=`ldapsearch -LLL -x -H ${LDAPURI} -D ${BINDDN} -b ${fgSEARCHBASE} -w ${BINDPW} "(&(objectclass=dbgRole)(member=${fgDN}))" dbgGrants | grep -v '^dn:' 2>&1` fgOUT=$? echo "${fgGRANTS}" | sed -e 's/^dbgGrants: //' return "${fgOUT}" } function findAddress { #given the cn of an asset object, return it's dbgAddress attribute(s) faCN=$1 faSEARCHBASE='ou=assets,dc=example,dc=com' faADDRESS=`ldapsearch -LLL -x -H ${LDAPURI} -D ${BINDDN} -b ${faSEARCHBASE} -w ${BINDPW} "(cn=${faCN})" dbgAddress | grep -v '^dn:' 2>&1` faOUT=$? echo "${faADDRESS}" | sed -e 's/^dbgAddress: //' return "${faOUT}" } function findPort { #given the name of a service return its port number(s) fpSERVICE=$1 fpPORT=`grep "^${fpSERVICE} " ${SERVICES} | cut -d\ -f2 2>&1` fpOUT=$? echo "${fpPORT}" return ${fpOUT} } function writePFRule { # write a pf rule to the anchor directory for this person #int writePFRule ( name sourceip proto destip port ) wprNAME=$1 wprSIP=$2 wprPROTO=$3 wprDIP=$4 wprPORT=$5 #write the rule echo "pass in proto ${wprPROTO} from ${wprSIP} to ${wprDIP} port ${wprPORT}" >> ${PFANCHORDIR}/${wprNAME} wprOUT=$? return "${wprOUT}" } function commitPF { # commit a given users pf rules to his pf anchor cpfNAME=$1 cpfRULESFILE="${PFANCHORDIR}/${cpfNAME}" if [ -f "${cpfRULESFILE}" ] then if grep -q ${SIP} ${cpfRULESFILE} then echo "committing rules" #eliminate redunant rules cpfTEMP=`mktemp -p ${PFANCHORDIR}` cat ${cpfRULESFILE} | sort | uniq > ${cpfTEMP} mv ${cpfTEMP} ${cpfRULESFILE} #flush the existing anchor rules for this user pfctl -a vpnusers/${cpfNAME} -F rules #add the new anchor rules pfctl -a vpnusers/${cpfNAME} -f ${cpfRULESFILE} else echo "No New Rules Found" fi else echo "No rules to commit" fi } ################ Begin Program ################ log "Connect Called" #a user connects. Get his DN DN=`findUserDN ${NAME}` if [ "$?" -ne 0 ] then log "ERROR--> Couldn't find your ldap DN. Send this log to ops@dbg.com" exit 42 fi #We found him in ldap, get what roles he's been granted GRANTS=`findGrants "${DN}"` if [ "$?" -ne 0 ] then if [ -z "${GRANTS}" ] then log "ERROR--> you don't appear to have been granted access to anything. Go away." exit 42 else log "ERROR--> an error occured finding your grants: ${GRANTS}" exit 42 fi else log "Grants: ${GRANTS}" fi #for each thing we've been granted access to, get it's address and write a pf rule for it #a grant attribute looks like this: ig05.web.dbg.com:tomcat echo "$GRANTS" | while read GRANT do SERVER=`echo ${GRANT} | cut -d: -f1` SERVICE=`echo ${GRANT} | cut -d: -f2` PORTS=`findPort ${SERVICE}` if [ "$?" -ne 0 ] || [ -z "${PORTS}" ] then log "WARNING--> ports could be found for: ${GRANT}: ${PORTS}" continue fi ADDRESSES=`findAddress ${SERVER}` if [ "$?" -ne 0 ] || [ -z "${ADDRESSES}" ] then log "WARNING--> no address was found for ${GRANT}: ${ADDRESSES}" continue fi if [ -n "${ADDRESSES}" ] #should be taken care of but just in case then echo "${ADDRESSES}" | while read ADDRESS do echo "${PORTS}" | while read PORT do PORTNUMBER=`echo ${PORT} | cut -d/ -f1` PORTPROTO=`echo ${PORT} | cut -d/ -f2` log "Granting access to ${ADDRESS} ${PORT}" writePFRule ${NAME} ${SIP} ${PORTPROTO} ${ADDRESS} ${PORTNUMBER} done done fi done out=`commitPF $NAME` if [ "$?" -ne 0 ] then log "ERROR--> an error occured committing the rules to PF: $out" exit 42 else log "$out" fi exit 0