Home LinuxEnable 2FA using FreeRADIUS with OpenLDAP

Enable 2FA using FreeRADIUS with OpenLDAP

by Lakindu Jayasena
39.4K views 10 mins read
Enable 2FA on FreeRADIUS with OpenLDAP

Two-Factor Authentication (2FA) is a form of multi-factor authentication (MFA) that strengthens security by requiring more than just a username and password. With 2FA enabled, users must provide an additional authentication code, also known as the second factor, generated by a virtual or hardware MFA solution.

One of the most widely used virtual authenticators is Google Authenticator, developed by Google. It implements time-based one-time passwords (TOTP) to provide secure two-step verification. The app generates a unique six-digit one-time password (OTP) that users enter alongside their normal login credentials, ensuring that even if a password is compromised, unauthorized access is still prevented.

In this article, I will demonstrate how to integrate the Google Authenticator PAM module with FreeRADIUS to enable two-factor authentication (2FA) for enterprise users stored in OpenLDAP. Any client devices or hosts connected to this RADIUS server will be protected with 2FA. For this walkthrough, I’m using Ubuntu 24.04 LTS as the operating system.

About FreeRADIUS and Google Authenticator PAM

RADIUS (Remote Authentication Dial-in User Service) is a standardized authentication framework that can be used to authenticate many different devices, including VPNs, Routers, Switches, Computers, and many more. While there are several RADIUS software, FreeRADIUS is one of the most popular RADIUS software in the open-source world.

PAM (Pluggable Authentication Module) is for Linux system users and password authentication. Since it has a PAM, this is also suitable for integrating it with the Google Authenticator PAM module. Google Authenticator PAM is a great free module that allows FreeRADIUS to talk to Google Authenticator.

Let’s Configure the FreeRADIUS Server to Enable 2FA

Installing FreeRADIUS and Google Authenticator PAM Module

Installing FreeRADIUS and Google Authenticator on Ubuntu 24.04 is straightforward and requires only a few simple steps. You can set them up quickly by running the following commands.

apt-get update
apt-get upgrade

apt-get install freeradius freeradius-common freeradius-utils freeradius-ldap
apt-get install libpam-google-authenticator

Verify the FreeRADIUS installation.

Note: Since Google Authenticator relies on a Time-based One-Time Password configuration, it is important to keep the system time properly synchronized between the server generating the verification codes and the user’s mobile MFA device. Any time drift can cause the 2FA codes to be rejected.

Configuring FreeRADIUS Server

Once the required packages are installed, the next step is to configure FreeRADIUS by editing its configuration files. The primary configuration file is /etc/freeradius/3.0/radiusd.conf.

In this file, ensure that the FreeRADIUS server is set to run under the freerad user account. By default, newer versions of FreeRADIUS already set both the user and group to freerad, whereas older versions may still use root. Running the service as a non-root user, such as freerad, is considered a security best practice and helps minimize potential risks.

Additionally, in the same configuration file under the Logging section, set auth = yes to enable logging of client authentication results. This ensures that all authentication attempts are recorded in the log file for monitoring and troubleshooting.

#
#  Logging section.  The various "log_*" configuration items
#  will eventually be moved here.
#
log {
        #  Log all (accept and reject) authentication results to the log file.
        #
        #  This is the same as setting "auth_accept = yes" and
        #  "auth_reject = yes"
        #
        #  allowed values: {no, yes}
        #
        auth = yes
}

The next config file that we need to edit is /etc/freeradius/3.0/users. This file defines user authorization for FreeRADIUS. Here, you can configure the server to authorize only LDAP users who belong to a specific LDAP group by default, while rejecting authentication attempts from users not meeting this criteria.

DEFAULT Ldap-Group == "cn=network.admins,ou=infra,dc=ldap,dc=example,dc=com"

DEFAULT Auth-Type := Reject
    Reply-Message = "Your are not allowed to access Network Devices."

Next, we will configure the /etc/freeradius/3.0/sites-enabled/default file, one of the primary virtual server configuration files in FreeRADIUS. This file determines how the FreeRADIUS server processes and handles all incoming authentication and accounting requests.

authorize {
        #
        #  Some broken equipment sends passwords with embedded zeros.
        #  i.e. the debug output will show
        #
        #       User-Password = "password\000\000"
        #
        #  This policy will fix it to just be "password".
        #
#       filter_password
        filter_uuid
        filter_google_otp
}
authenticate {
        #
        #  PAP authentication, when a back-end database listed
        #  in the 'authorize' section supplies a password.  The
        #  password can be clear-text, or encrypted.
        Auth-Type PAP {
                pap
                if (&Google-Password) {
                        update request {
                                &User-Name := "%{&User-UUID}"
                               &User-Password := "%{&Google-Password}"
                        }
                        pam
                } else {
                       update reply {
                              Reply-Message := "Login incorrect: TOTP Fail"
                        }
                        reject
                }
        }
}

The above configuration snippet uses two new filters (“filter_uuid” & “filter_google_otp” ) defined inside /etc/freeradius/3.0/policy.d/filter that help to extract the username part of the input email address and the 6-digit TOTP from the password.

filter_uuid {
        if (&User-Name =~ /^(.*)@example\.com$/) {
                update request {
                        &User-UUID := "%{1}"
                }
        }
}

filter_google_otp {
        if (&User-Password =~ /^(.*)([0-9]{6})$/) {
               update request {
                        &Google-Password := "%{2}"
                        &User-Password := "%{1}"
                }
        }
}

Since we are using custom attributes like User-UUID and Google-Password, we need to map the human-readable names to the corresponding binary data in RADIUS packets, as attribute names alone have no meaning in the protocol. To achieve this, add the new attributes to the /etc/freeradius/3.0/dictionary file as follows.

ATTRIBUTE       Google-Password         3000    string
ATTRIBUTE       User-UUID               3001    string

Set up FreeRadius Clients

This is where you configure the shared secret key used by RADIUS clients (e.g., network devices, VPN servers, or Wi-Fi controllers) to authenticate with the FreeRADIUS server. For security reasons, always replace the default secret with a strong, unique key.

client office-router {
	ipaddr		= office-router.example.com
	secret		= S3cureP@ssw0rd
    require_message_authenticator = yes  # Optional, recommended for extra security
}

You can add multiple clients by repeating similar blocks for each device or server that needs RADIUS access.

Configure FreeRadius LDAP Module

Next, we will configure the FreeRADIUS LDAP module to connect to the OpenLDAP server. To begin, open and edit the file: /etc/freeradius/3.0/mods-available/ldap

Note: Make sure that your LDAP server (In this case, ldap.example.com) is reachable via the 389 port from this FreeRADIUS server

ldap {
	server = 'ldap.example.com'
	identity = 'cn=admin,dc=ldap,dc=example,dc=com'
	password = 'EnterBindUserPassword'
	base_dn = 'dc=ldap,dc=example,dc=com'

	#
	#  User object identification.
	#
	user {
		#  Where to start searching in the tree for users
		base_dn = "ou=network-admins,ou=staff,dc=ldap,dc=example,dc=com"

		#  Filter for user objects, should be specific enough
		#  to identify a single user object.
		filter = "(mail=%{%{Stripped-User-Name}:-%{User-Name}})"
	}

	#
	#  User membership checking.
	#
	group {
		#  Where to start searching in the tree for groups
		base_dn = "ou=groups,ou=staff,dc=ldap,dc=example,dc=com"

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

		#  Attribute that uniquely identifies a group.
		#  Is used when converting group DNs to group
		#  names.
        #Comment this
		#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 = "(|(&(objectClass=GroupOfNames)(member=%{control:Ldap-UserDn}))(&(objectClass=GroupOfNames)(member=%{control:Ldap-UserDn})))"

		#  The attribute in user objects which contain the names
		#  or DNs of groups a user is a member of.
		membership_attribute = 'memberOf'

	}
}

Once the FreeRadius LDAP module is configured, enable the module by issuing the following commands.

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

Configuring FreeRADIUS with Google Authenticator PAM

In this setup, FreeRADIUS handles LDAP authentication first, followed by TOTP verification through the Google Authenticator PAM module. To enable this functionality, you must first activate the PAM module in FreeRADIUS. You can do this by executing the following commands:

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

Once the module is enabled, configure the /etc/pam.d/radiusd file to integrate the Google Authenticator PAM module. After LDAP password authentication, the module verifies the user’s one-time password stored in their .google_authenticator file, enforcing 2FA. The forward_pass option ensures the password is passed to PAM, and the module runs under the freerad user for security.

auth required pam_google_authenticator.so user=freerad forward_pass secret=/var/lib/freeradius/google-authenticator/${USER}/.google_authenticator

#Comment out the following lines
#@include common-auth
#@include common-account
#@include common-password
#@include common-session

Finally, as with most Linux services, FreeRADIUS must be restarted after updating configuration files to apply all changes.

systemctl restart freeradius.service

Create Linux Users & TOTP Store

For the PAM module to function properly, a corresponding Linux user account must exist on the FreeRADIUS server that matches the OpenLDAP username. Additionally, a separate location is required to securely store that user’s TOTP (one-time password) data.

For example, if an LDAP user logs in with the email [email protected], there must be a Linux account named test-user01 on the FreeRADIUS server. The user’s TOTP data should be stored in a dedicated directory, such as: /var/lib/freeradius/google-authenticator/test-user01. This setup ensures that TOTP verification works securely alongside LDAP authentication.

Let’s create the Linux user account and the corresponding TOTP storage directory required for Google Authenticator.

#Create Linux User
adduser test-user01

#Create the Directory Structure & Permissions
mkdir -p /var/lib/freeradius/google-authenticator
chown -R freerad:freerad /var/lib/freeradius/google-authenticator
chmod 750 /var/lib/freeradius/google-authenticator

#This need to create for each user
mkdir /var/lib/freeradius/google-authenticator/test-user01
chmod 700 /var/lib/freeradius/google-authenticator/test-user01
chown freerad:freerad /var/lib/freeradius/google-authenticator/test-user01

Next, save the Google Authenticator TOTP codes in the directory you just created.

sudo -u freerad google-authenticator -t -d -f -r 3 -R 30 -w 3 -s /var/lib/freeradius/google-authenticator/test-user01/.google_authenticator

The configuration of Google Authenticator secret keys for Linux users has been covered in my previous article, Secure AWS EC2 Instances with Multi-Factor Authentication. Refer to the Configuring Google Authenticator section there.

Once it is done, verify that the freerad user can access the generated TOTP codes.

sudo -u freerad cat /var/lib/freeradius/google-authenticator/test-user01/.google_authenticator

Testing RADIUS Authentication with LDAP User

The FreeRADIUS software package includes a command-line tool called radtest, which allows you to send test requests directly to the RADIUS server for verification. 

#Command Syntax
radtest <username> <password+google authenticator TOTP> localhost 1812 <RADIUS secret key>

#Example:
radtest [email protected] ldapuserpassword123456 localhost 1812 S3cureP@ssw0rd

If your authentication is successful, the FreeRADIUS server will respond with an Access-Accept message.

2FA enabled FreeRADIUS Testing with OpenLDAP User

Troubleshooting Commands

When configuring FreeRADIUS with PAM and Google Authenticator, debugging is essential to identify issues quickly.

Run FreeRADIUS in Debug Mode

This runs FreeRADIUS in debug mode, displaying detailed logs of authentication attempts and module interactions. Note that you must stop the running FreeRADIUS service before using this command.

freeradius -X

Check PAM Authentication Logs

This log contains all PAM-related entries, including Google Authenticator messages, which helps in diagnosing issues related to two-factor authentication.

tail -f /var/log/auth.log

Conclusion

By configuring FreeRADIUS 3.2.x with OpenLDAP and Google Authenticator, you add a strong two-factor authentication (2FA) layer to your network components. This ensures that only legitimate users with both their LDAP password and time-based OTP token can gain access. This setup works seamlessly for Network devices, VPNs, Wi-Fi (802.1X), and enterprise network authentication, providing a secure and centralized login system.

Related Articles

3 comments

freerad_guy August 24, 2021 - 9:35 AM

Hello,

The behavior of my configuration is strange, the password needs to be completed with the TOTP otherwise it doesn’t authenticate but I can put whatever number and it works. It seems that the freeradius does not check whether the 6 numbers of the TOTP are correct, do you have an idea ?

Reply
Manish Taneja October 23, 2021 - 2:04 PM

I have the same issue. Was there a fix for this?

Reply
Alex March 11, 2022 - 6:35 PM

Hello Lakindu, thanks for this post! Let me ask you a little question please. I get an error in the $radiusd -X log.

including dictionary file /usr/share/freeradius/dictionary
including dictionary file /usr/share/freeradius/dictionary.dhcp
including dictionary file /usr/share/freeradius/dictionary.vqp
Errors reading /etc/raddb/dictionary: dict_init: /etc/raddb/dictionary[50] invalid keyword “ATTRIBUTE      ”

I followed all step you mentioned with my config but seems to be a problem with the dictionary.
Do you have any tip for this? I didn’t find anything.

Thanks in advance.

Reply

Leave a Comment

* By using this form you agree with the storage and handling of your data by this website.