-----
====== FreeRADIUS with LDAP ======
===== Introduction =====
* FreeRADIUS includes support for LDAP integration.
* To ensure maximum benefit of RADIUSdesk we will use a hybrid setup for LDAP integration.
* The next section will be used as a basic introduction to FreeRADIUS and LDAP
====== Basic FreeRADIUS with LDAP ======
===== OpenLDAP =====
==== Install OpenLDAP ====
* We will install OpenLDAP and do a basic setup that can be used by FreeRADIUS.
* When you install OpenLDAP (slapd) the package installer will ask you to provide a password.
* Provide one but keep in mind that we will reconfigure everything after the install.
* OpenLDAP server is called **slapd**.
* The program to extract info from the OpenLDAP directory is called **slapcat**.
# Update repository lists
sudo apt update
# Install OpenLDAP and standard utility tools
sudo apt install -y slapd ldap-utils
# Install the FreeRADIUS LDAP module (rlm_ldap)
sudo apt install -y freeradius-ldap
==== Configure OpenLDAP ====
* After the slapd package installed we can run the following command to reconfigure slapd and provide a basic setup.
# Configure or reconfigure slapd defaults interactively
sudo dpkg-reconfigure slapd
* **Omit OpenLDAP server configuration?** No.
* **DNS domain name:** Enter your domain (e.g., radiusdesk.com creating dc=radiusdesk,dc=com).
* **Organization name:** Enter your organization name (We specified radiusdesk).
* **Administrator password:** We used a simple password since we're testing.
* After you answered these questions slapd should be reconfigured with those items.
==== Check OpenLDAP ====
* You can confirm it by issuing the following command
sudo slapcat
dn: dc=radiusdesk,dc=com
objectClass: top
objectClass: dcObject
objectClass: organization
o: radiusdesk
dc: radiusdesk
structuralObjectClass: organization
entryUUID: 8c68dc16-1a3f-1041-9add-250d07e316fc
creatorsName: cn=admin,dc=radiusdesk,dc=com
createTimestamp: 20260722173600Z
entryCSN: 20260722173600.213629Z#000000#000#000000
modifiersName: cn=admin,dc=radiusdesk,dc=com
modifyTimestamp: 20260722173600Z
* This confirms the basic setup of slapd.
* Next we will add an Organizational Unit (OU) under which we can add our users. (Remember LDAP is a directory/hierarchy)
==== Create the Organizational Unit (OU) ====
* Create a file named ou.ldif to set up a dedicated container for your network accounts.
dn: ou=people,dc=radiusdesk,dc=com
objectClass: organizationalUnit
ou: people
* Apply this file to your OpenLDAP directory:
ldapadd -x -D "cn=admin,dc=radiusdesk,dc=com" -W -f ou.ldif
==== Create the User Account ====
* Create a file named user.ldif.
* This template creates a user named testuser with the password mysecretpassword.
dn: uid=testuser,ou=people,dc=radiusdesk,dc=com
objectClass: top
objectClass: account
objectClass: simpleSecurityObject
uid: testuser
userPassword: mysecretpassword
description: RADIUS Test User
* Apply this file to your OpenLDAP directory:
ldapadd -x -D "cn=admin,dc=radiusdesk,dc=com" -W -f user.ldif
* If you run **slapcat** again these items should be in the reply.
sudo slapcat
dn: dc=radiusdesk,dc=com
objectClass: top
objectClass: dcObject
objectClass: organization
o: radiusdesk
dc: radiusdesk
structuralObjectClass: organization
entryUUID: 8c68dc16-1a3f-1041-9add-250d07e316fc
creatorsName: cn=admin,dc=radiusdesk,dc=com
createTimestamp: 20260722173600Z
entryCSN: 20260722173600.213629Z#000000#000#000000
modifiersName: cn=admin,dc=radiusdesk,dc=com
modifyTimestamp: 20260722173600Z
dn: ou=people,dc=radiusdesk,dc=com
objectClass: organizationalUnit
ou: people
structuralObjectClass: organizationalUnit
entryUUID: a6c420c6-1a48-1041-895a-41f787ad35f7
creatorsName: cn=admin,dc=radiusdesk,dc=com
createTimestamp: 20260722184109Z
entryCSN: 20260722184109.903088Z#000000#000#000000
modifiersName: cn=admin,dc=radiusdesk,dc=com
modifyTimestamp: 20260722184109Z
dn: uid=testuser,ou=people,dc=radiusdesk,dc=com
objectClass: top
objectClass: account
objectClass: simpleSecurityObject
uid: testuser
userPassword:: bXlzZWNyZXRwYXNzd29yZA==
description: RADIUS Test User
structuralObjectClass: account
entryUUID: f0928490-1a48-1041-895b-41f787ad35f7
creatorsName: cn=admin,dc=radiusdesk,dc=com
createTimestamp: 20260722184313Z
entryCSN: 20260722184313.729405Z#000000#000#000000
modifiersName: cn=admin,dc=radiusdesk,dc=com
modifyTimestamp: 20260722184313Z
* Now that we have a working LDAP setup we can configure FreeRADIUS to work with this LDAP server.
*
===== FreeRADIUS + LDAP =====
* We assume the following:
* You have installed the OpenLDAP server on the same host that FreeRADIUS is installed on.
* You have a clean install of FreeRADIUS. (default config)
* If you have installed RADIUSdesk on the host it will have a modified FreeRADIUS config.
* You can however activate the default config again with the following steps
sudo su
service freeradius stop
cd /etc
mv /etc/freeradius /etc/freeradius.rd
cp -r /etc/freeradius.orig /etc/freeradius
chown -R freerad:freerad /etc/freeradius
==== Enable the FreeRADIUS LDAP Module ====
* FreeRADIUS keeps available modules in one directory and active ones in another.
* Enable the LDAP module by creating a symbolic link.
sudo su
# Navigate to the enabled modules directory
cd /etc/freeradius/3.0/mods-enabled/
# Link the LDAP configuration module
sudo ln -s ../mods-available/ldap .
==== Map FreeRADIUS to OpenLDAP ====
* Edit the module configuration file to point FreeRADIUS to your local OpenLDAP instance.
* Edit ///etc/freeradius/3.0/mods-available/ldap// and update these core lines:
ldap {
# LDAP server connection parameters
server = "localhost"
port = 389
# Identity used to search the directory
identity = "cn=admin,dc=radiusdesk,dc=com"
password = testing123
# Base DN where user lookups will begin
base_dn = "dc=radiusdesk,dc=com"
# User lookup mapping filter
update {
control:Password-With-Header += 'userPassword'
}
user {
base_dn = "ou=people,dc=radiusdesk,dc=com"
filter = "(uid=%{%{Stripped-User-Name}:-%{User-Name}})"
}
}
==== Check the Default Virtual Server ====
* You must instruct FreeRADIUS to check the LDAP backend during incoming connection requests.
* Open /etc/freeradius/3.0/sites-enabled/default and confirm the following is in the authorize block
#
# The ldap module reads passwords from the LDAP database.
-ldap
* The **-** prefix to **ldap** means it will silently be ignored when the LDAP module is not enabled.
==== Test it out ====
* Test the RADIUS authentication to confirm if it is working as intended
* Start FreeRADIUS in debug mode
sudo systemctl stop freeradius
sudo freeradius -X
* Use the radtest program to do a test authentication.
radtest testuser mysecretpassword localhost 1812 testing123
#This is the return on my setup
Sent Access-Request Id 249 from 0.0.0.0:56819 to 127.0.0.1:1812 length 78
User-Name = "testuser"
User-Password = "mysecretpassword"
NAS-IP-Address = 127.0.1.1
NAS-Port = 1812
Message-Authenticator = 0x00
Cleartext-Password = "mysecretpassword"
Received Access-Accept Id 249 from 127.0.0.1:1812 to 127.0.0.1:56819 length 38
Message-Authenticator = 0x216fe46614354e897d645ce27ec9e0d8
* Lets look at the LDAP module's debug output in FreeRADIUS
rlm_ldap (ldap): Reserved connection (1)
(2) ldap: EXPAND (uid=%{%{Stripped-User-Name}:-%{User-Name}})
(2) ldap: --> (uid=testuser)
(2) ldap: Performing search in "ou=people,dc=radiusdesk,dc=com" with filter "(uid=testuser)", scope "sub"
(2) ldap: Waiting for search result...
(2) ldap: User object found at DN "uid=testuser,ou=people,dc=radiusdesk,dc=com"
(2) ldap: Processing user attributes
(2) ldap: control:Password-With-Header += 'mysecretpassword'
rlm_ldap (ldap): Released connection (1)
Need 1 more connections to reach min connections (3)
Need more connections to reach 10 spares
rlm_ldap (ldap): Opening additional connection (7), 1 of 30 pending slots used
rlm_ldap (ldap): Connecting to ldap://localhost:389
rlm_ldap (ldap): Waiting for bind result...
rlm_ldap (ldap): Bind successful
(2) [ldap] = updated
(2) [expiration] = noop
(2) [logintime] = noop
(2) pap: No {...} in Password-With-Header, re-writing to Cleartext-Password
(2) pap: Removing &control:Password-With-Header
(2) [pap] = updated
(2) } # authorize = updated
(2) Found Auth-Type = PAP
* Based on the debug output we can see the following happens when the ldap module is angaged
* The LDAP module does a search for the user.
* When the LDAP server replies with a result the LDAP module looks for the **userPassword** LDAP reply attribute.
* This is the clear-text password of the user and **not all LDAP servers provides it**.
* OpenLDAP does provide it (control:Password-With-Header += 'mysecretpassword')
* It then tries an LDAP bind as the user to confirm the password is working.
* If this worked it sets the Cleartext-Password so we can do PAP authentication. (Auth-Type = PAP)
* Not all LDAP authentications works like this since not all LDAP servers will return the user's password in cleartext.
* We will cover those later in the document.
====== FreeRADIUS with Secure LDAP ======
* When you plan to use any external LDAP server with FreeRADIUS they will typically be forcing you to connect securely via SSL/TLS on port 636.
* In this section we will:
* Create a PKI. This includes creating a CA with cert and key for slapd (OpenLDAP)
* Configure OpenLDAP to also support **ldaps**.
* Configure FreeRADIUS to communicate via ldaps with slapd (OpenLDAP).
===== Create PKI =====
==== Create the CA ====
* Create a working directory
mkdir -p ~/pki
cd ~/pki
* Generate the CA key
openssl genpkey -algorithm RSA -out ca.key -pkeyopt rsa_keygen_bits:4096
* Create the CA certificate
openssl req -new -x509 -days 3650 -key ca.key -sha256 -out ca.crt
#### This is what we filled in ####
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:ZA
State or Province Name (full name) [Some-State]:Gauteng
Locality Name (eg, city) []:Johannesburg
Organization Name (eg, company) [Internet Widgits Pty Ltd]:RADIUSdesk.com
Organizational Unit Name (eg, section) []:PKI
Common Name (e.g. server FQDN or YOUR name) []:RADIUSdesk Internal Root CA
Email Address []:dirk@gmail.com
* Keep **ca.key** secret.
==== Generate the LDAP server key ====
* Issue the following command:
openssl genpkey -algorithm RSA -out ldap.key -pkeyopt rsa_keygen_bits:4096
==== Create an OpenSSL configuration ====
* Create ldap.conf. Use the following as reference.
[req]
default_bits = 4096
prompt = no
distinguished_name = dn
req_extensions = req_ext
[dn]
CN = ldap.radiusdesk.com
[req_ext]
subjectAltName = @alt_names
[alt_names]
DNS.1 = ldap.radiusdesk.com
DNS.2 = ldap
IP.1 = 127.0.0.1
* **Please Note**: The subjectAltName (SAN) is important. Modern TLS ignores the Common Name.
==== Create the CSR ====
* The following command will create the sign request:
openssl req -new -key ldap.key -out ldap.csr -config ldap.cnf
==== Sign the request ====
* The following command will use the sign request to create a certificate:
openssl x509 -req -in ldap.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out ldap.crt -days 825 -sha256 -copy_extensions copy
* Now you have the following files:
* **ca.crt** - The CA public file
* **ca.key** - The CA private file
* **ldap.key** - The LDAP private file
* **ldap.crt** - The LDAP public file
* Everything is now in place in terms of our PKI for us to configure slapd (OpenLDAP) to support ldaps.
===== Configure OpenLDAP for ldaps =====
* Issue the following commands
sudo mkdir /etc/ldap/tls
#Copy the following files over
sudo cp ca.crt ldap.key ldap.crt /etc/ldap/tls
#Change their permissions
sudo chown openldap:openldap /etc/ldap/tls/*
sudo chmod 600 /etc/ldap/tls/ldap.key
sudo chmod 644 /etc/ldap/tls/*.crt
* Configure slapd to use those files.
* Create **tls.ldif** with the following contents
dn: cn=config
changetype: modify
replace: olcTLSCertificateFile
olcTLSCertificateFile: /etc/ldap/tls/ldap.crt
-
replace: olcTLSCertificateKeyFile
olcTLSCertificateKeyFile: /etc/ldap/tls/ldap.key
-
replace: olcTLSCACertificateFile
olcTLSCACertificateFile: /etc/ldap/tls/ca.crt
* Apply it to the directory with the following commnand:
sudo ldapmodify -Y EXTERNAL -H ldapi:/// -f tls.ldif
* Enable LDAPS by editing **/etc/default/slapd**.
* Look for the following section and change accordingly:
# slapd normally serves ldap only on all TCP-ports 389. slapd can also
# service requests on TCP-port 636 (ldaps) and requests via unix
# sockets.
# Example usage:
# SLAPD_SERVICES="ldap://127.0.0.1:389/ ldaps:/// ldapi:///"
#SLAPD_SERVICES="ldap:/// ldapi:///"
SLAPD_SERVICES="ldap:/// ldapi:/// ldaps:///"
* Restart **slapd**.
sudo systemctl restart slapd
* Confirm that it is now listening on port 636 also:
sudo ss -lnpt | grep 636
LISTEN 0 2048 0.0.0.0:636 0.0.0.0:* users:(("slapd",pid=698775,fd=11))
LISTEN 0 2048 [::]:636 [::]:* users:(("slapd",pid=698775,fd=12))
* To make the cert's FQDN work smooth add it to the /etc/hosts file (ldap.radiusdesk.com in our case).
127.0.0.1 localhost
127.0.1.1 xubuntu-24-4
127.0.0.1 ldap.radiusdesk.com
# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
* Test the certificate with the following command:
openssl s_client -connect ldap.radiusdesk.com:636 -CAfile ca.crt
#IT should end with something like this:
---
SSL handshake has read 3905 bytes and written 401 bytes
Verification: OK
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 4096 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
* You can now test with the ldapsearch utility.
Important is to specify the CA cert (LDAPTLS_CACERT) that ldapsearch needs to use else if **will fail**.
LDAPTLS_CACERT=/etc/ldap/tls/ca.crt ldapsearch -H ldaps://ldap.radiusdesk.com -x -D "cn=admin,dc=radiusdesk,dc=com" -w testing123 -b dc=radiusdesk,dc=com
* If you want this CA to be part of the list of trusted CA's on the system you can do the following:
sudo cp /etc/ldap/tls/ca.crt /usr/local/share/ca-certificates/radiusdesk-ca.crt
sudo update-ca-certificates
#This is the output on our system
Updating certificates in /etc/ssl/certs...
rehash: warning: skipping ca-certificates.crt,it does not contain exactly one certificate or CRL
1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d...
Processing triggers for ca-certificates-java (20240118) ...
Adding debian:radiusdesk-ca.pem
done.
done.
#Now we can just use plain ldapsearch
ldapsearch -H ldaps://ldap.radiusdesk.com -x -D "cn=admin,dc=radiusdesk,dc=com" -w testing123 -b dc=radiusdesk,dc=com
==== What does update-ca-certificates do? ====
I like to know how things work. If you also like to know how things work the following insert is for you :-)
* The primary master CA file that is updated and can be used on Ubuntu is **/etc/ssl/certs/ca-certificates.crt**
* This command rebuilds the system's trusted certificate store by consolidating certificates from a few key locations:
* **System Certificates:** The command reads the configuration file **/etc/ca-certificates.conf** to determine which certificates from **/usr/share/ca-certificates/** should be trusted.
* **Local Certificates:** It automatically trusts all .crt files found in /usr/local/share/ca-certificates/. This is the place where we add our internal CA certificate.
* **Output:** It then generates the single, concatenated bundle file at **/etc/ssl/certs/ca-certificates.crt**. This file is the master list used by many applications (like OpenSSL) to verify SSL/TLS connections.
==== Update FreeRADIUS for ldaps:// ====
* Do the following steps.
sudo su
#Copy the OpenLDAP's CA to FreeRADIUS
cp /etc/ldap/tls/ca /etc/freeradius/3.0/certs/ca.crt
* Edit **/etc/freeradius/3.0/mods-enabled/ldap**
#Look for these items...
#server = 'localhost'
#---This has to match the cert's ubjectAltName (SAN)---
server = 'ldap.radiusdesk.com'
# server = 'ldap.rrdns.example.org'
# server = 'ldap.rrdns.example.org'
# Port to connect on, defaults to 389, will be ignored for LDAP URIs.
##port = 389
#--We specify the ldaps port--
port = 636
#--- Then loop for the tls section
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
#--- Disable start_tls since we are forcing ssl/tls ---
#start_tls = yes
# ca_file = ${certdir}/cacert.pem
#--- The CA file from OpenLDAP--
ca_file = /etc/freeradius/3.0/certs/ca.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.
#---Enforce FreeRADIUS to check the validity of the certificate ---
require_cert = 'demand'
#
# Check the CRL, as with the EAP module.
#
* After you made these changes, restart FreeRADIUS in debug mode and to a test authentication to confirm that it is now using ldaps (port 636)
* See the results from out setup below:
rlm_ldap (ldap): Reserved connection (3)
(3) ldap: EXPAND (uid=%{%{Stripped-User-Name}:-%{User-Name}})
(3) ldap: --> (uid=testuser)
(3) ldap: Performing search in "ou=people,dc=radiusdesk,dc=com" with filter "(uid=testuser)", scope "sub"
(3) ldap: Waiting for search result...
(3) ldap: User object found at DN "uid=testuser,ou=people,dc=radiusdesk,dc=com"
(3) ldap: Processing user attributes
(3) ldap: control:Password-With-Header += 'mysecretpassword'
rlm_ldap (ldap): Released connection (3)
Need more connections to reach 10 spares
rlm_ldap (ldap): Opening additional connection (7), 1 of 25 pending slots used
rlm_ldap (ldap): Connecting to ldap://ldap.radiusdesk.com:636
rlm_ldap (ldap): Waiting for bind result...
rlm_ldap (ldap): Bind successful