Configure FreeRADIUS attributes (AVPs)

This how-to describes the necessary configuration changes for the LinOTP Smart Virtual Appliance (SVA) to add RADIUS attribute value pairs (AVPs) to authentication responses depending on group memberships.

Configure FreeRADIUS

The SVA generates some system configuration settings of the operating system via a set of meta files. This also applies for the most relevant parts of the FreeRADIUS AVPs configuration.

If you don't use the LinOTP SVA you have to apply the changes directly to the FreeRADIUS configuration files in /etc/freeradius.

Log in to the SVA and activate the root shell:

unsupported

Change the mako files

Navigate to the Appliance configuration directory:

cd /etc/lseappliance/config-templates

Backup the original meta configuration file /etc/lseappliance/config-templates/etc-freeradius-modules-ldap.mako and apply the necessary changes:

cp -a etc-freeradius-modules-ldap.mako etc-freeradius-modules-ldap.mako.backup
nano etc-freeradius-modules-ldap.mako

Configure the connection details to the AD/LDAP and what should be used as group filter.

New file content:

##
<%
ldap = config.get('radius', {}).get('ldap', {})
%>
ldap {
   #
   #  Note that this needs to match the name in the LDAP
   #  server certificate, if you're using ldaps.
#    server = "${ldap.get('server', '')}"
#    identity = "${ldap.get('binddn', '')}"
#    password = "${ldap.get('password', '')}"
#    basedn = "${ldap.get('basedn', '')}"
#    filter = "(${ldap.get('loginattr',
#    '')}=%{%{Stripped-User-Name}:-%{User-Name}})"
   #base_filter = "(objectclass=radiusprofile)"

#################
### IP or FQDN of your AD/LDAP server (if you use ldaps mind the FQDN needs to
### match the server certificate
   server = "192.168.122.207"
### the credentials to acces AD/LDAP
   identity = "cn=query,dc=example,dc=net"
   password = "PASSWORD"
### The part of the DIT you want to connect to
   basedn = "dc=example,dc=net"
### The string used as sAMAccountName for LDAP query. If the variable
### %{Stripped-User-Name} is defined it is used. Otherwise fall back to
### %{User-Name} taken from the authentication request. See section
### "additional changes" in this guide for how to generate the stripped
### user name
   filter = "(sAMAccountName=%{%{Stripped-User-Name}:-%{User-Name}})"
################

   #  How many connections to keep open to the LDAP server.
   #  This saves time over opening a new LDAP socket for
   #  every authentication request.
   ldap_connections_number = 5

   # seconds to wait for LDAP query to finish. default: 20
   timeout = 4

   #  seconds LDAP server has to process the query (server-side
   #  time limit). default: 20
   #
   #  LDAP_OPT_TIMELIMIT is set to this value.
   timelimit = 3

   #
   #  seconds to wait for response of the server. (network
   #   failures) default: 10
   #
   #  LDAP_OPT_NETWORK_TIMEOUT is set to this value.
   net_timeout = 1

   # Mapping of RADIUS dictionary attributes to LDAP
   # directory attributes.
   <%text>
   dictionary_mapping = ${confdir}/ldap.attrmap
   </%text>

   groupname_attribute = cn
   # groupmembership_filter =
   # "(|(&(objectClass=groupOfNames)(member=%{control:Ldap-UserDn}))(&(objectClass=groupOfUniqueNames)(uniquemember=%{control:Ldap-UserDn})))"

###########
## The LDAP search query for group membership
   groupmembership_filter = "(&(objectClass=group)(member=%{control:Ldap-UserDn}))"
###########

   groupmembership_attribute = radiusGroupName
}

Backup the meta configuration file etc-freeradius-sites-available-linotp.mako and apply the necessary changes:

cp -a etc-freeradius-sites-available-linotp.mako etc-freeradius-sites-available-linotp.mako.backup
nano etc-freeradius-sites-available-linotp.mako

In this file the checks for group memberships are configured and the actions triggered (setting AVPs) depending on a successful validation.

Only the section "post-auth" needs to be changed:

post-auth {

########
### Add the ldap module to be processed
       ldap
#######

       exec
       Post-Auth-Type REJECT {
               attr_filter.access_reject
       }

##########
### Test for membership of group "networkers" and set AVP "Cisco-AVPair"
   if (LDAP-Group == "networkers") {
       update reply {
           Cisco-AVPair = "networktobeconfigured"
       }
   }
   else {
       update reply {
           Cisco-AVPair = "groupnotfound"
  }
 }

### Test for membership and set attribute Class
  if (LDAP-Group == "administrators") {
       update reply {
          Class  += "admin"
       }
   }
       else {
       update reply {
           Class += "user"
       }
 }

### Test for membership to group "vpn" and set one more attribute Class
  if (LDAP-Group == "vpn" ) {
       update reply {
          Class  += "vpn"
       }
   }
else {
       update reply {
           Class += "local"
       }
 }
###########

}

Regenerate the FreeRADIUS configuration files based on the modifications done before:

appliance_configure.py -c generate_config -o radius

This will create a new versions of /etc/freeradius/modules/ldap and /etc/freeradius/sites-enabled/linotp

Additional changes

In some cases it might be necessary to modify more FreeRADIUS configuration files directly. In this example the realm/domain part is stripped from the user name. This can be useful if the sAMAccountName contains the name only - a query for group membership with name+domain will fail because a user of this name can not be found.

Preprocessing changes can be configured in /etc/freeradius/hints. The following lines will define a variable %{Stripped-User-Name} as it is used above in etc-freeradius-modules-ldap.mako.

DEFAULT User-Name =~ "(.+)@([^@]+)$"
       Stripped-User-Name := "%{1}

Restart FreeRADIUS

To apply the changes FreeRADIUS needs to be restarted:

service freeradius restart

Troubleshooting

To verify if FreeRADIUS responses with the correct AVPs or to debug problems FreeRADIUS can be started interactively from unsupported mode.

  • First stop the running service:
service freeradius stop
  • Start FreeRADIUS in debug mode:
freeradius -X
  • Trigger the login of a user (or use e.g. radtest for a simulation)

There should be lines of membership checks and a response containing the desired AVPs:

++? if (LDAP-Group == "networkers")
...
rlm_ldap::ldap_groupcmp: User found in group networkers
 [ldap] ldap_release_conn: Release Id: 0
? Evaluating (LDAP-Group == "networkers") -> TRUE
...
++? if (LDAP-Group == "administrators")
...
rlm_ldap::ldap_groupcmp: ldap_get_values() failed
 [ldap] ldap_release_conn: Release Id: 0
? Evaluating (LDAP-Group == "administrators") -> FALSE
...
++? if (LDAP-Group == "vpn")
...
rlm_ldap::ldap_groupcmp: User found in group vpn
 [ldap] ldap_release_conn: Release Id: 0
? Evaluating (LDAP-Group == "vpn" ) -> TRUE
...
Sending Access-Accept of id 144 to 192.168.122.1 port 34554
       Cisco-AVPair = "networktobeconfigured"
       Class = 0x75736572
       Class = 0x76706e

If radtest is used on client side it should show something like this:

rad_recv: Access-Accept packet from host 192.168.122.224 port 1812, id=144,
length=60
       Cisco-AVPair = "networktobeconfigured"
       Class = 0x75736572
       Class = 0x76706e