Setup FreeRADIUS: LDAP Authentication and Authorization

Introduction to FreeRADIUS: LDAP Authentication and Authorization

In this article, we will create a scenario in which there will be two user groups who have different privileges for managing the network. When one of the users login to the router (NAS), a RADIUS request will be created and sent to the FreeRADIUS server. Then the RADIUS server will query the LDAP (Lightweight Directory Access Protocol) server if this user is existed and has the right credentials.

These are the summarized steps. I will cover the details later in this article with packet capture by Wireshark. Following shows the network topology.

Advertisement

Setup FreeRADIUS: LDAP Authentication and Authorization

I assume you have already installed FreeRADIUS server with the default configuration and its utility packages. If not please visit here (FreeRADIUS PART 1). Since I configured NAS for authentication and authorization before in this article (https://www.golinuxcloud.com/aaa-network-policy-server/), I will not include the configuration here.

 

Configure FreeRADIUS for LDAP

Step-1: FreeRADIUS comes with a lot of modules and configuration files. We will not use all the files and modules. Fore sake of learning, I will use couple of them. Let’s continue with virtual server files which are stored under "/etc/freeradius/3.0/sites-enabled" directory. Two virtual servers are configured in default when you install a FreeRADIUS server. Following shows the path and the list of the servers.

root@freeradius:/home/celal# ls -l /etc/freeradius/3.0/sites-enabled/
total 0
lrwxrwxrwx 1 freerad freerad 26 Şub 16 14:36 default -> ../sites-available/default
lrwxrwxrwx 1 freerad freerad 31 Şub 16 14:36 inner-tunnel -> ../sites-available/inner-tunnel
root@freeradius:/home/celal#

Notice that when I list the files under the directory, it points to another directory. It is called a symbolic link. "default" and "inner-tunnel" servers are not present in "/etc/freeradius/3.0/sites-enabled" directory. Instead, they are stored in "/etc/freeradius/3.0/sites-available". It is a concept borrowed from Apache server. It will make no difference if you store them in "sites-enabled" directory or create a symbolic link to another folder. I will go for creating the server under "sites-enabled".

 

Step-2: Since the default servers contain a tone of configuration, I will back them up, then remove them from the folder.

root@freeradius:/home/celal# mkdir backup
root@freeradius:/home/celal# cp /etc/freeradius/3.0/sites-enabled/default  /home/celal/backup/
root@freeradius:/home/celal# cp /etc/freeradius/3.0/sites-enabled/inner-tunnel  /home/celal/backup/

 

Step-3: Remove the default virtual servers.

root@freeradius:/home/celal# rm /etc/freeradius/3.0/sites-enabled/default
root@freeradius:/home/celal# rm /etc/freeradius/3.0/sites-enabled/inner-tunnel

 

Step-4: Create a new virtual server with your preferred text editor. I will use nano.

Advertisement
root@freeradius:/home/celal# nano /etc/freeradius/3.0/sites-enabled/my_server

Then add the lines below.

server my_server {
listen {
        type = auth
        ipaddr = *
        port = 1812
}
authorize {
        ldap
        if (ok || updated)  {
        update control {
        Auth-Type := ldap
        }
        }
}
authenticate {
        Auth-Type LDAP {
                ldap
        }
}
}

I will use ldap module in the the "Authorization" block to search for users and groups in the LDAP database. It is similar to "files" module which uses the local "users" file as source of the credentials. Since there is no authentication type set in the lines below "ldap" module, we need to set the "Auth-Type" in the "if" block. When a module is executed (in this case "ldap"), it returns a code. I will use that code as condition for setting the authentication type. Following shows the list of available return codes.

Module return code Description
notfound Information was not found
noop The module did nothing
ok The module succeeded
updated The module updated the request
fail The module failed
reject The module rejected the request
userlock The user was locked out

 

With "Authentication" module, LDAP database will be used for confirming the user’s credentials.

 

Step-5: FreeRADIUS runs the modules in "/etc/freeradius/3.0/mods-enabled/" directory. We need to copy ldap module to that directory. Do it as below.

root@freeradius:/home/celal# cp /etc/freeradius/3.0/mods-available/ldap /etc/freeradius/3.0/mods-enabled/

Step-6: Open ldap module with your preferred text editor an modify it as it suits you.

ldap {
   server = 'mydomain.com'
   base_dn = 'CN=Users,DC=mydomain,DC=com'
   identity = 'CN=celal,CN=Users,DC=mydomain,DC=com'
   password = ' PassWord123'

   user {
      filter = "(sAMAccountName=%{%{Stripped-User-Name}:-%{User-Name}})"  
   }

}

Before a client sends a ldap search request to the ldap server, it needs to bind, which is almost equivalent of authentication. Because of that, I need to define a ldap sever, a user for binding and a base_dn where to search users. Since I will use a Windows LDAP, I will specify "sAMAccountName" in the user section which is a user object attribute that holds username.

Advertisement

 

Step-7: In this step, I will define a RADIUS client with IP address and shared secret. Open "client" file.

root@freeradius:/home/celal# nano /etc/freeradius/3.0/clients.conf

And add the lines below, modifying for yourself.

client cisco_router {
   ipaddr = 192.168.1.15
   secret = celaldogan
}

 

Step-8: Run FreeRADIUS in debug mode with "freeradius -X"

Setup FreeRADIUS: LDAP Authentication and Authorization

 

As you see I got an error about eap module, because I deleted default virtual servers in the beginning. I do not need eap module, thus I will delete that module and launch it again.

root@freeradius:/home/celal# rm /etc/freeradius/3.0/mods-enabled/eap

 

Testing FreeRADIUS for LDAP Authentication and Authorization

There are two ways to test the FreeRADIUS server, with ssh loging to the router or using "radtest" tool. I prefer the second one this time.

Step-1: Apply the command below for testing.

Advertisement
root@freeradius:/home/celal# radtest jane PassWord123 127.0.0.1 1812 testing123
Sent Access-Request Id 240 from 0.0.0.0:53470 to 127.0.0.1:1812 length 74
        User-Name = "jane"
        User-Password = "PassWord123"
        NAS-IP-Address = 127.0.1.1
        NAS-Port = 1812
        Message-Authenticator = 0x00
        Cleartext-Password = "PassWord123"
Received Access-Accept Id 240 from 127.0.0.1:1812 to 127.0.0.1:53470 length 20
root@freeradius:/home/celal#

Following screenshot shows debug output from the FreeRADIUS server when it receives the request.

Setup FreeRADIUS: LDAP Authentication and Authorization

The authentication for jane is working, but notice that there is not anything related to authorization in the screenshot above. We need to add some configuration for that.

 

Step-2: First we need to pull the group information from the ldap server.

Open "dictionary" file and define an attribute for ldap group.

root@freeradius:/home/celal# nano /etc/freeradius/3.0/dictionary

 

And add "ATTRIBUTE My_Group 3000 string" line to the file as below.

Setup FreeRADIUS: LDAP Authentication and Authorization

 

Open ldap module.

Advertisement
root@freeradius:/home/celal# nano /etc/freeradius/3.0/mods-enabled/ldap

 

and add the line below and save the module.

control:My_Group                ='memberOf'

 

Step-3: Configure the virtual server to check the group information and send authorization information accordingly.

post-auth {
if (&control:My_Group =~ /Network Admin/) {
        update reply {
                Cisco-AVPair = "shell:priv-lvl=15"
    }
}
if (&control:My_Group =~ /Network Operator/) {
        update reply {
                Cisco-AVPair = "shell:priv-lvl=7"
    }
}
}

"post-auth" module is executed after a successful authentication. I am setting the privilege level based on the user’s group. I am checking the groups in the "if" condition with a regex.

 

Step-4: Capture the packets while testing with "radtest jane PassWord123 127.0.0.1 1812 testing123" command.

Setup FreeRADIUS: LDAP Authentication and Authorization

 

The debug output from FreeRADIUS is below.

Setup FreeRADIUS: LDAP Authentication and Authorization

 

Analyzing the packets between the FreeRADIUS and the LDAP server

Packet Number 1: Before we query the ldap server, we need to bind (authenticate).

Advertisement

Setup FreeRADIUS: LDAP Authentication and Authorization

 

We specify that we would like to use "simple binding", which carry the credentials in clear text.

Packet Number 2: It is a "bindResponse" packet, which informs that the binding has been successful.

Setup FreeRADIUS: LDAP Authentication and Authorization

 

Packet Number 3: It is a "searchRequest" packet. We are asking ldap server to send us the user information in the attributes. Now we only need "memberOf" attribute. The other ones are the default attributes FreeRADIUS adds.

Setup FreeRADIUS: LDAP Authentication and Authorization

 

Packet Number 4: The ldap server sends the user information to the radius server in this packet.

Setup FreeRADIUS: LDAP Authentication and Authorization

 

Packet Number 5: After gathering the user’s information, we bind (authenticate) with the user (jane) in this packet.

Setup FreeRADIUS: LDAP Authentication and Authorization

 

Packet Number 6: The provided credentials are verified with this packet.

Advertisement

Setup FreeRADIUS: LDAP Authentication and Authorization

 

Final thoughts

LDAP database can be used for authentication and authorization. We kept the configuration files as simple as we can for learning purpose in this article.

 

Didn't find what you were looking for? Perform a quick search across GoLinuxCloud

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can either use the comments section or contact me form.

Thank You for your support!!

7 thoughts on “Setup FreeRADIUS: LDAP Authentication and Authorization”

  1. A very useful blog! I would have 2 concerns here.
    1. memberOf is rarely a single entry in AD Ldap so MyGroup would be populated by the 1st group found which might not be the one you were looking for.
    2. If memberOf matches neither ‘Network Admin’ or ‘Network Operator’ it would give a user level=1 access (read-only on most switches).

    Still thank you for explaining radius sites and modules in an easy-to-understand format.

    Reply
    • To address #1 I sugest Changing

      control:My_Group                ='memberOf'
      
      to
      
      control:My_Group                +='memberOf'

      Not to familiar with post-auth so #2 would still a concern for me.

      Reply
      • 1) memberOf attribute can contain comma separated multiple groups.
        Please look at the packet coming from the server here (https://www.golinuxcloud.com/wp-content/uploads/wireshark-265.png)
        we used “=~” operator, which matches if the request contains an attribute which matches the given regular expression.

        2) When memberOf matches neither of the group, FreeRadius will not add "Cisco-AVPair = "shell:priv-lvl=X" attribute.
        However, I did not test the authorization without sending the attribute.

        Reply
        • I went ahead I tested ldap module in my lab with both ~=, +=, and checked debug output with radiusd -X

          ~= only returns the 1st memberOf result

             (1) ldap: control:My_Group += 'CN=Admin User,OU=My Groups,DC=domain,DC=local'

          += returns all memberOf results

             (1) ldap: control:My_Group += 'CN=Admin User,OU=My Groups,DC=domain,DC=local'
             (1) ldap: control:My_Group += 'CN=Network Admin,OU=My Groups,DC=domain,DC=local'
             (1) ldap: control:My_Group += 'CN=Network Operator,OU=My Groups,DC=domain,DC=local'
             (1) ldap: control:My_Group += 'CN=dev,OU=My Groups,DC=domain,DC=local'

          I confirmed that when neither group matches it logs me into my dell switch so I added a != to the end of the controls to enforce priv-lvl=0 like so

          POST-AUTH:
             post-auth {
             if (&control:My_Group =~ /Network Admin/ ) {
                     update reply {
                             Cisco-AVPair = "shell:priv-lvl=15"
                 }
             }
             if (&control:My_Group !~ /deny_the_rest/) {
                     update reply {
                             Cisco-AVPair = "shell:priv-lvl=0"
                 }
             }

          In the above example when multiple My_Group’s are returned, post-auth will only accept/trigger off the 1st result. If I set (&control:My_Group =~ /Admin User/) it works as expected but if I tell it to trigger off (&control:My_Group =~ /Network Admin/) it will not work..

          RADIUSD -X Example:
             ...
             (1) ldap: User object found at DN "CN=Michael Zietlow,OU=TestLab,OU=TestLab Users,DC=domain,DC=local"
             (1) ldap: Processing user attributes
             (1) ldap: control:My_Group += 'CN=Admin User,OU=My Groups,DC=domain,DC=local'
             (1) ldap: control:My_Group += 'CN=Network Admin,OU=My Groups,DC=domain,DC=local'
             (1) ldap: control:My_Group += 'CN=Network Operator,OU=My Groups,DC=domain,DC=local'
             (1) ldap: control:My_Group += 'CN=dev,OU=My Groups,DC=domain,DC=local'
             ...
             (1) # Executing section post-auth from file /etc/raddb/sites-enabled/inner-tunnel
             (1)   post-auth {
             (1)     if (&control:My_Group =~ /Network Admin/ ) {
             (1)     if (&control:My_Group =~ /Network Admin/ )  -> FALSE
             (1)     if (&control:My_Group !~ /deny_the_rest/) {
             (1)     if (&control:My_Group !~ /deny_the_rest/)  -> TRUE
             (1)     if (&control:My_Group !~ /deny_the_rest/)  {
             (1)       update reply {
             (1)         Cisco-AVPair = "shell:priv-lvl=0"
             (1)       } # update reply = noop
             (1)     } # if (&control:My_Group !~ /deny_the_rest/)  = noop
             (1)   } # post-auth = noop
             (1) Sent Access-Accept Id 56 from 192.168.1.2:1812 to 192.168.1.3:53175 length 0
             (1)   Cisco-AVPair = "shell:priv-lvl=0"
             (1) Finished request
          Reply
  2. How can I assign specific role for LDAP’s user every login. The default is network-operator if you don’t define any role (I need network-admin). I’ve done any research but no proper solution yet. The official document is kind of hard to understand and apply in real cases.
    Thanks for your help, sir.

    Reply

Leave a Comment

X