OpenVPN + RADIUS auth + RADIUS attributes

Hi all,

after some time I decided to move my VPN server from a Rasbperry PI to my OpenWrt router (x86) because of crypt performance. As my initial setup was using authentication via RADIUS and also considered returned attribute (for IP), I was struggeling how to achieve the same with OpenWrt. After several tries of compiling the existing OpenVPN plugins I decided to try a different approach via "radclient", which is available as package. And finally I came up with a setup where all my reqiurements work and even additional ones have been added. Basically I am now doing the authentication in a bash script and write the response attribute into the user-specific OpenVPN file. This works really great for me and I wanted to share my solution, if somebody else is having similar problems.

#!/bin/bash

# passed by openvpn (in the environment):
#
# $username
# $password
#
# Current supported radius attributes
#
# Framed-IP-Address : client ip address
# MS-Primary-DNS-Server : primary dns server
# MS-Secondary-DNS-Server : secondary dns server
# MS-CHAP-Domain : network domain
#
# Dependencies (OpenWrt packages):
#
# bash
# freeradius3-utils (radclient)
#
# required OpenVPN configuration:
#
# auth-user-pass-verify /path/to/script/auth.radius.sh via-env
# script-security 3
# client-config-dir /etc/openvpn/files

radiusserver=192.168.X.X
radiussecret=secret
nasidentifier=someidentifier

# no change needed below this line

ourname=auth.radius.sh
facility=auth

output=$(mktemp)
error=$(mktemp)

# clean temp files when we terminate
trap "rm -f ${output} ${error}" EXIT

logger -p "${facility}.info" -t "$ourname" "Trying to authenticate user ${username} against RADIUS"

echo "User-Name=${username},User-Password=${password},NAS-Identifier=${nasidentifier}" | radclient -x $radiusserver $facility $radiussecret 1>"${output}" 2>"${error}"

# look for "Access-Accept" line in the output of radclient
result=`cat $output`

echo "" > "/etc/openvpn/files/${username}"

# Access Accept received
if [[ $result == *"Access-Accept"* ]]; then
  # Read Framed-IP-Address from response
  ip=`echo $result | grep -o 'Framed-IP-Address = .*' | cut -d' ' -f3`

  if [ ! -z "$ip" ];
  then
    echo "ifconfig-push $ip 255.255.255.255" >> "/etc/openvpn/files/${username}"
  fi

  # Read MS-Chap-Domain from response
  domain=`echo $result | grep -o 'MS-CHAP-Domain = .*' | cut -d' ' -f3 | cut -d'"' -f2`

  if [ ! -z "$domain" ];
  then
    echo "push \"dhcp-option-push DOMAIN ${domain}\"" >> "/etc/openvpn/files/${username}"
  fi

  # Read MS-Primary-DNS-Server from response
  primarydns=`echo $result | grep -o 'MS-Primary-DNS-Server = .*' | cut -d' ' -f3`

  if [ ! -z "$primarydns" ];
  then
    echo "push \"dhcp-option DNS ${primarydns}\"" >> "/etc/openvpn/files/${username}"
  fi

  # Read MS-Secondary-DNS-Server from response
  secondarydns=`echo $result | grep -o 'MS-Secondary-DNS-Server = .*' | cut -d' ' -f3`

  if [ ! -z "$secondarydns" ];
  then
    echo "push \"dhcp-option DNS ${secondarydns}\"" >> "/etc/openvpn/files/${username}"
  fi

  logger -p "${facility}.info" -t "{$ourname}" "User ${username} authenticated successfully"
  exit 0
# Authentication failed
else

  logger -p "${facility}.err" -t "${ourname}" "User ${username} NOT authenticated"
  exit 1
fi

It is surely not a perfect solution but it works. Feel free to adjust it to your needs.

Bye

A small update to also support multiple REDIS servers.

#!/bin/bash

# passed by openvpn (in the environment):
#
# $username
# $password
#
# Current supported radius attributes
#
# Framed-IP-Address : client ip address
# MS-Primary-DNS-Server : primary dns server
# MS-Secondary-DNS-Server : secondary dns server
# MS-CHAP-Domain : network domain
#
# Dependencies (OpenWrt packages):
#
# bash
# freeradius3-utils (radclient)
#
# required OpenVPN configuration:
#
# auth-user-pass-verify /etc/openvpn/server/auth.radius.sh via-env
# script-security 3

radiusservers=( "192.168.XX.X" "192.168.XX.X" )
radiussecret=secret
nasidentifier=identifier
clientconfigdir="/etc/openvpn/files"

# no change needed below this line

ourname=auth.radius.sh
facility=auth

output=$(mktemp)
error=$(mktemp)

# clean temp files when we terminate
trap "rm -f ${output} ${error}" EXIT

logger -p "${facility}.info" -t "$ourname" "Trying to authenticate user ${username} against RADIUS"

for radiusserver in "${radiusservers[@]}"
do
  echo "User-Name=${username},User-Password=${password},NAS-Identifier=${nasidentifier}" | radclient -x $radiusserver $facility $radiussecret 1>"${output}" 2>"${error}"

  # look for "Access-Accept" line in the output of radclient
  result=`cat $output`

  echo "" > "${clientconfigdir}/${username}"

  # Access Accept received
  if [[ $result == *"Access-Accept"* ]]; then
    # Read Framed-IP-Address from response
    ip=`echo $result | grep -o 'Framed-IP-Address = .*' | cut -d' ' -f3`

    if [ ! -z "$ip" ];
    then
      echo "ifconfig-push $ip 255.255.255.255" >> "${clientconfigdir}/${username}"
    fi

    # Read MS-Chap-Domain from response
    domain=`echo $result | grep -o 'MS-CHAP-Domain = .*' | cut -d' ' -f3 | cut -d'"' -f2`

    if [ ! -z "$domain" ];
    then
      echo "push \"dhcp-option-push DOMAIN ${domain}\"" >> "${clientconfigdir}/${username}"
    fi

    # Read MS-Primary-DNS-Server from response
    primarydns=`echo $result | grep -o 'MS-Primary-DNS-Server = .*' | cut -d' ' -f3`

    if [ ! -z "$primarydns" ];
    then
      echo "push \"dhcp-option DNS ${primarydns}\"" >> "${clientconfigdir}/${username}"
    fi

    # Read MS-Secondary-DNS-Server from response
    secondarydns=`echo $result | grep -o 'MS-Secondary-DNS-Server = .*' | cut -d' ' -f3`

    if [ ! -z "$secondarydns" ];
    then
      echo "push \"dhcp-option DNS ${secondarydns}\"" >> "${clientconfigdir}/${username}"
    fi

    logger -p "${facility}.info" -t "{$ourname}" "User ${username} authenticated successfully"
    exit 0
  # Authentication failed
  elif [[ $result == *"Access-Reject"* ]]; then

    logger -p "${facility}.err" -t "${ourname}" "User ${username} NOT authenticated"
    exit 1
  else
    echo "failed for server ${radiusserver}"
  fi
done

logger -p "${facility}.err" -t "${ourname}" "User ${username} NOT authenticated"

exit 1

There was an issue with assigning domain and network mask, which is fixed here:

#!/bin/bash

# passed by openvpn (in the environment):
#
# $username
# $password
#
# Current supported radius attributes
#
# Framed-IP-Address : client ip address
# MS-Primary-DNS-Server : primary dns server
# MS-Secondary-DNS-Server : secondary dns server
# MS-CHAP-Domain : network domain
#
# Dependencies (OpenWrt packages):
#
# bash
# freeradius3-utils (radclient)
#
# required OpenVPN configuration:
#
# auth-user-pass-verify /etc/openvpn/server/auth.radius.sh via-env
# script-security 3

radiusservers=( "192.168.XX.X" "192.168.XX.X" )
radiussecret=secret
nasidentifier=identifier
clientconfigdir="/etc/openvpn/files"

# no change needed below this line

ourname=auth.radius.sh
facility=auth

output=$(mktemp)
error=$(mktemp)

# clean temp files when we terminate
trap "rm -f ${output} ${error}" EXIT

logger -p "${facility}.info" -t "$ourname" "Trying to authenticate user ${username} against RADIUS"

for radiusserver in "${radiusservers[@]}"
do
  echo "User-Name=${username},User-Password=${password},NAS-Identifier=${nasidentifier}" | radclient -x $radiusserver $facility $radiussecret 1>"${output}" 2>"${error}"

  # look for "Access-Accept" line in the output of radclient
  result=`cat $output`

  echo "" > "${clientconfigdir}/${username}"

  # Access Accept received
  if [[ $result == *"Access-Accept"* ]]; then
    # Read Framed-IP-Address from response
    ip=`echo $result | grep -o 'Framed-IP-Address = .*' | cut -d' ' -f3`

    if [ ! -z "$ip" ];
    then
      echo "ifconfig-push $ip 255.255.255.0" >> "${clientconfigdir}/${username}"
    fi

    # Read MS-Chap-Domain from response
    domain=`echo $result | grep -o 'MS-CHAP-Domain = .*' | cut -d' ' -f3 | cut -d'"' -f2`

    if [ ! -z "$domain" ];
    then
      echo "push \"dhcp-option DOMAIN ${domain}\"" >> "${clientconfigdir}/${username}"
    fi

    # Read MS-Primary-DNS-Server from response
    primarydns=`echo $result | grep -o 'MS-Primary-DNS-Server = .*' | cut -d' ' -f3`

    if [ ! -z "$primarydns" ];
    then
      echo "push \"dhcp-option DNS ${primarydns}\"" >> "${clientconfigdir}/${username}"
    fi

    # Read MS-Secondary-DNS-Server from response
    secondarydns=`echo $result | grep -o 'MS-Secondary-DNS-Server = .*' | cut -d' ' -f3`

    if [ ! -z "$secondarydns" ];
    then
      echo "push \"dhcp-option DNS ${secondarydns}\"" >> "${clientconfigdir}/${username}"
    fi

    logger -p "${facility}.info" -t "{$ourname}" "User ${username} authenticated successfully"
    exit 0
  # Authentication failed
  elif [[ $result == *"Access-Reject"* ]]; then

    logger -p "${facility}.err" -t "${ourname}" "User ${username} NOT authenticated"
    exit 1
  else
    echo "failed for server ${radiusserver}"
  fi
done

logger -p "${facility}.err" -t "${ourname}" "User ${username} NOT authenticated"

exit 1