6.3.2. RADIUS Authentication with return of attributes#

The howto is applicable to SVA2 and SVA3 and the community version of LinOTP.

When authenticating against LinOTP via the RADIUS protocol, some clients require information that is returned in the form of attributes.

Such information includes, for example, the users’ group memberships, which are then used to configure the user profiles on the client.

The handling of such requests is best done in the freeRADIUS server with the LDAP module and the post-auth section.

The authentication setup can allow users to log in with either the sAMAccountName or the userPrincipalName.

To ensure that LinOTP uses the correct token assignment, it is necessary in such cases to normalize the user names arriving in freeRADIUS (rewrite them to one of the valid forms).

Invalid names can also be discarded and not authenticated.

Such processing is also carried out with the help of the LDAP module in freeRADIUS within the authorize section.

Activating the rlm_module in freeRADIUS#

After installing the freeRADIUS module LDAP, it is located in /etc/freeradius/3.0/mods-available/ldap

apt-get update
apt-get install freeradius-ldap

cd /etc/freeradius/3.0/mods-enabled; ln -s ../mods-available/ldap

Configuration of the LDAP module in freeRADIUS#

On the Smart Virtual Appliance, the configuration file is defined by a macro file and rendered together with the configuration of the SVA. To be able to maintain the file directly in the freeRADIUS configuration, the macro can be switched off. In any case, the administrator of the SVA is responsible for maintaining the content of the following configuration.

# This is an auto generated file, generated by LinOTP Smart Virtual Appliance.
# Please do not edit.
# Rather edit the template etc-freeradius-modules-ldap.mako
#
# This file defines the RADIUS LDAP connection.
#
# The following line switches off the use of this macro.
# To switch it on again, use the path in Destination
# /etc/freeradius/3.0/mods-available/ldap
## Destination: /tmp/ldap
## Before:
## After: radius
## Data: radius
## Priority: 80

Connection of the LDAP module with the LDAP servers#

The header of the file /etc/freeradius/3.0/mods-available/ldap and the tls section in this file are edited.

# -*- text -*-
#
#  $Id: b1cb352d0023bc691cf5114b772f4980fddbb213 $

#
#  Lightweight Directory Access Protocol (LDAP)
#
ldap {
       #  Note that this needs to match the name(s) in the LDAP server
       #  certificate, if you're using ldaps.  See OpenLDAP documentation
       #  for the behavioral semantics of specifying more than one host.
       #
       #  Depending on the libldap in use, server may be an LDAP URI.
       #  In the case of OpenLDAP this allows additional the following
       #  additional schemes:
       #  - ldap://  (LDAP, default Port 389)
       #  - ldaps:// (LDAP over SSL, default Port 636)
       #  - StartTLS or SSL show section tls {start_tls, ca_file}
       #  - ldapi:// (LDAP over Unix socket)
       #  - ldapc:// (Connectionless LDAP)
       # examples:
       # server = 'localhost'
       # server = 'ldap://dc1.example.org:389'

       server = 'ldap://w2k19-dc1.corp.example.edu'
       # The second server is used here as a failover (openLDAP).
       server = 'ldap://w2k19-dc2.corp.example.edu'

       #  Port to connect on, defaults to 389, will be ignored for LDAP URIs.
       # port = 389

       #  Administrator account for searching and possibly modifying.
       #  If using SASL + KRB5 these should be commented out.
       # identity = 'cn=admin,dc=example,dc=org'
       # password = mypass

       identity = "CN=query,CN=Users,DC=corp,DC=example,DC=edu"
       password = "Test#123!"

       #  Unless overridden in another section, the dn from which all
       #  searches will start from.
       base_dn = 'dc=corp,dc=example,dc=edu'

... unchanged until section tls ...

 tls {
       # Set this to 'yes' to use TLS encrypted connections
       # to the LDAP database by using the StartTLS extended
       # operation.
       #
       # The StartTLS operation is supposed to be
       # used with normal ldap connections instead of
       # using ldaps (port 636) connections
       #
       # StartTLS with server ldap://myhost:389
       # start_tls = yes

       # CA-Cert (base64) of the Windows AD PKI previously uploaded in the GUI:8443
       ca_file = /usr/local/share/ca-certificates/lseappliance/certificate-000.crt

       # ca_path       = ${certdir}
       # certificate_file = /path/to/radius.crt
       # private_key_file = /path/to/radius.key
       # random_file = /dev/urandom

       #  Certificate Verification requirements.  Can be:
       #    'never' (do not even bother trying)
       #    'allow' (try, but don't fail if the certificate
       #               cannot be verified)
       #    'demand' (fail if the certificate does not verify)
       #    'hard'  (similar to 'demand' but fails if TLS
       #             cannot negotiate)
       #
       #  The default is libldap's default, which varies based
       #  on the contents of ldap.conf.

       # require_cert  = 'demand'
       require_cert    = 'allow'
       }

… unchanged until the end …

After this first adjustment, the rlm_ldap is connected to the server:

systemctl stop freeradius
freeradius -X #Debug = -X, -XX, -XXX

Customize the filter for users#

This filter depends on the attributes used for the user names during authentication. In a Microsoft Active Directory, this is often the sAMAccountName, but the userPrincipalName or the common name are also conceivable.

If different forms of the attribute are permitted, these should be converted to the form configured in the UserIdResolver before use in LinOTP.

To achieve the best effect of the filters, ldapsearch is an indispensable tool. The filters used here can be tested with ldapsearch.

The user {} section of the /etc/freeradius/3.0/mods-available/ldap file is edited.

user {
       #  Where to start searching in the tree for users
       base_dn = "${..base_dn}"

       #  Filter for user objects, should be specific enough
       #  to identify a single user object.
       #
       #  For Active Directory, you should mostly use
       #  "samaccountname=" instead of "uid="
       #
       filter = "(sAMAccountName=%{%{Stripped-User-Name}:-%{User-Name}})"

       #  SASL parameters to use for user binds
       #
       #  When we're prompted by the SASL library, these control
       #  the responses given.
       #
       #  Any of the config items below may be an attribute ref
       #  or and expansion, so different SASL mechs, proxy IDs
       #  and realms may be used for different users.
       sasl {
               # SASL mechanism
       #       mech = 'PLAIN'

               # SASL authorisation identity to proxy.
       #       proxy = &User-Name

               # SASL realm. Used for kerberos.
       #       realm = 'example.org'
               }

       #  Search scope, may be 'base', 'one', sub' or 'children'
       # scope = 'sub'
....

ldapsearch -x -H ldap://w2k19-dc.corp.example.edu -b dc=corp,dc=example,dc=edu -W -D ‘CN=query,CN=Users,DC=corp,DC=example,DC=edu’ “(sAMAccountName=peterc)”

peterc is an example of a user name stripped of additions, :- sets it to the user name if it is empty.

Adjustment of the filter for the attribute to be returned#

If the groups to be filtered are not in the same part of the directory, the base_dn must be adjusted first.

The filter is divided into stages for a better overview.

The ldap module restricts filtering to the objectClass posixGroup.

The required attribute is the common name of the group, defined by: name_attribute = cn

The group {} section of the /etc/freeradius/3.0/mods-available/ldap file is edited.

group {
       #  Where to start searching in the tree for groups
       base_dn = "${..base_dn}"

       #  Filter for group objects, should match all available
       #  group objects a user might be a member of.
       filter = '(objectClass=posixGroup)'

       # Search scope, may be 'base', 'one', sub' or 'children'
       # scope = 'sub'

       #  Attribute that uniquely identifies a group.
       #  Is used when converting group DNs to group
       #  names.
       name_attribute = cn

       #  Filter to find group objects a user is a member of.
       #  That is, group objects with attributes that
       #  identify members (the inverse of membership_attribute).
       membership_filter = '(|(member=%{control:Ldap-UserDn})(memberUid=%{%{Stripped-User-Name}:-%{User-Name}}))'

       #  The attribute in user objects which contain the names
       #  or DNs of groups a user is a member of.
       #
       #  Unless a conversion between group name and group DN is
       #  needed, there's no requirement for the group objects
       #  referenced to actually exist.
       membership_attribute = 'memberOf'

       #  If cacheable_name or cacheable_dn are enabled,
       #  all group information for the user will be
       #  retrieved from the directory and written to LDAP-Group
       #  attributes appropriate for the instance of rlm_ldap.
       #
       #  For group comparisons these attributes will be checked
       #  instead of querying the LDAP directory directly.
       #
       #  This feature is intended to be used with rlm_cache.
       #
       #  If you wish to use this feature, you should enable
       #  the type that matches the format of your check items
       #  i.e. if your groups are specified as DNs then enable
       #  cacheable_dn else enable cacheable_name.
       # cacheable_name = 'no'
       # cacheable_dn = 'no'

       #  Override the normal cache attribute (<inst>-LDAP-Group or
       #  LDAP-Group if using the default instance) and create a
       #  custom attribute.  This can help if multiple module instances
       #  are used in fail-over.
       # cache_attribute = 'LDAP-Cached-Membership'
       }

Configuration of the site linotp#

To check the group membership, a check is carried out in the post-auth section.

Other checks are carried out in suitable sections. Rewriting usernames or setting realms belongs in the authorize section.

Check group membership#

section post-auth in /etc/freeradius/3.0/sites-enabled/linotp

post-auth {
     # Test if User memberOf the Group (CN)
     if (LDAP-Group == "VPN-Group1") {
          update reply {
               # Return attribute for client
               &Unix-FTP-Shell := "Group1"
               }
               noop
     }
     elseif (LDAP-Group == "VPN-Group2") {
          update reply {
          # Return attribute for client
          &Unix-FTP-Shell := "Group2"
               }
               noop
     }
     #elseif (LDAP-Group == "VPN-Group3") {
          #update reply {
          ## Return attribute for client
          #&Unix-FTP-Shell := "Group3"
               #}
               #noop
     #}
     else {
               reject
     }
     Post-Auth-Type REJECT {
          attr_filter.access_reject
     }
}

After the group membership has been checked by the rlm_ldap module, the Unix FTP shell attribute is filled and returned.

The attribute is selected depending on the client making the request.

These come from the attribute tree https://www.iana.org/assignments/radius-types/radius-types.xhtml maintained by the IANA.

In order to use them, they must be made known; the dictionary file is used for this purpose.

It is better to use an attribute registered by the vendor, the manufacturer of the client.

Testing the function#

Start the freeRADIUS server in debug mode:#

systemctl stop freeradius
freeradius -X[XX]
# The freeradius reads its configuration, checks the connection (ldap) and refers to configuration errors.
# If the server is ready to process requests, it displays its prompt.

Starting a client request to the RADUIS server:#

echo "User-Name = aduser, User-Password = xxxxxx" | radclient -x <ip_radius-server> auth 'Secret'
# User-Name - sAMAccountName
# User-Password - according to the policy for otppin Password|PIN+OTP in one word.
# Option -x - debug, also shows the return from the server

It is important to test positive and negative variants of authentication.