User Tools

Site Tools


samba:freeradius-ad

Setting up Freeradius and Samba AD for WPA2 WiFi Auth

Warning: This configuration works on a Debian 12 setup, has not been tested on Debian 13.

There are several steps to make this work. This is not a pure Samba article, but I think it's good to have it here in my notebook for my reference. Radius is quite a complex system; it took me a while to make things work. Most of this has been possible with the help of Kees Van Vloten and his Freeradius Enterprise WiFi notes. They are presented here in a different fashion.

Steps to follow:

  • You already have a Samba member server joined to the domain. See Setting up a Member Server.
  • You have created an AD permission group (named 'MAD\wifi' in our example) that contains all Windows machines allowed to connect to WiFi.
  • Configure Samba to allow ntlm_auth to use mschapv2 and ntlmv2.
  • Create a PKI infrastructure to generate and maintain certificates.
  • Install and configure Freeradius on a member server.
  • Install the CA certificate on Windows clients.

Configure Samba to allow ntlm_auth to use mschapv2 and ntlmv2

This one is pretty straightforward. Add the following line to `/etc/samba/smb.conf` in *all* your DCs:

ntlm auth = mschapv2-and-ntlmv2-only

Save and restart Samba AD DC:

/etc/init.d/samba-ad-dc restart

Install EasyRSA to manage your PKI

EasyRSA is part of the OpenVPN group and allows you to very easily manage a simple (internal only) Public Key Infrastructure. See EasyRSA.

You do not need root privileges to run EasyRSA. This is a very simple collection of programs; it can be run on any machine or server, and it has no dependencies on Samba or Freeradius.

git clone https://github.com/OpenVPN/easy-rsa
cd ~/easy-rsa/easyrsa3
cp vars.example vars

Edit `./vars` file and have:

set_var EASYRSA_REQ_COUNTRY     "ES"
set_var EASYRSA_REQ_PROVINCE    "Madrid"
set_var EASYRSA_REQ_CITY        "Madrid"
set_var EASYRSA_REQ_ORG         "Caponato Enterprises"
set_var EASYRSA_REQ_EMAIL       "capo@caponato.es"
set_var EASYRSA_REQ_OU          "ITS"

In order to have a location for a CDP — Certificate Revocation List Distribution Point — for your internal Certification Authority, edit `./x509-types/COMMON` and:

authorityInfoAccess = caIssuers;URI:http://pki.caponato.es/pki/ca.crt
crlDistributionPoints = URI:http://pki.caponato.es/cdp/crl.pem

You need to set up a basic webserver, create a virtual host (in our example `pki`), and upload those files (`ca.crt` and `crl.pem`) when they are issued. Setting up the webserver is beyond the scope of this article.

See for better and longer explanations.

I named my Radius server `radius-1`:

cd ~/easy-rsa/easyrsa3
./easyrsa init-pki
./easyrsa build-ca
./easyrsa gen-req radius-1 nopass
./easyrsa sign-req server radius-1
./easyrsa gen-crl

Upload your CA and CRL to your PKI webserver. Please note your destination directories in the webserver will be different.

Please note, if you revoke any certificate, you must generate a new CRL (Certificate Revocation List) file and redistribute it to your PKI webserver. Create your CA / CRL combo file and redistribute.

cd ~/easy-rsa/easyrsa3
rsync ./pki/ca.crt pki:/var/www/html/pki/
rsync ./pki/crl.pem  pki:/var/www/html/cdp/

Copy your newly generated certificates to your Radius server: private key, public key, CA cert, and CRL certificates:

cd ~/easy-rsa/easyrsa3
rsync -av ./pki/private/radius-1.key radius-1:/etc/ssl/private/
rsync -av ./pki/issued/radius-1.crt radius-1:/etc/ssl/certs/
rsync -av ./pki/ca.crt radius-1:/etc/ssl/certs/ca-caponato.pem
rsync -av ./pki/crl.pem radius-1:/etc/ssl/certs/crl-caponato.pem
# Please note CA and CRL certificates have been renamed for clarity.
# Please note CA cert extension has been changed to pem — equivalent to crt.

Log on to your `radius-1` server and set up permissions for certificates correctly:

chmod 640 /etc/ssl/private/radius-1.key
chgrp ssl-cert /etc/ssl/private/radius-1.key
chmod 644 /etc/ssl/certs/radius-1.crt
chmod 644 /etc/ssl/certs/ca-caponato.pem
chmod 644 /etc/ssl/certs/crl-caponato.pem

Your certificates are all set and ready for Freeradius!

Renewing an Expired RADIUS Server Certificate

This note covers renewing a RADIUS server certificate that has expired or is about to expire. The procedure renews the server certificate while keeping the same CA, so Windows clients require no changes — they keep trusting whatever new cert the CA signs.

If the CA itself has expired, this guide does not apply — you would need to issue a new CA and redistribute it via GPO to every domain client.

Symptoms

In /var/log/freeradius/radius.log:

ERROR: (TLS) Alert read:fatal:certificate expired
Auth: Login incorrect (eap_peap: (TLS) Alert read:fatal:certificate expired): [user] (from client unifi-ap ...)

Also large numbers of Cannot continue, as the peer is misbehaving errors from Windows clients — same root cause, Windows just tears down the TLS session rather than sending a clean alert.

Diagnosis

On the RADIUS server, check the server cert, the CA cert, and the CRL:

openssl x509 -in /etc/ssl/certs/radius-1.crt -noout -subject -issuer -dates
openssl x509 -in /etc/freeradius/3.0/ca_and_crl.pem -noout -subject -issuer -dates
openssl crl  -in /etc/freeradius/3.0/ca_and_crl.pem -noout -lastupdate -nextupdate

If the server cert is expired but the CA is still valid → continue. If the CRL is also near expiry, regenerate it as part of this work (check_crl = yes makes an expired CRL break PEAP the same way).

EasyRSA defaults are 825 days for certs and 180 days for the CRL. Increase them before reissuing:

cd ~/easy-rsa/easyrsa3
sed -i 's/^#set_var EASYRSA_CERT_EXPIRE.*/set_var EASYRSA_CERT_EXPIRE  3650/' vars
sed -i 's/^#set_var EASYRSA_CRL_DAYS.*/set_var EASYRSA_CRL_DAYS     3650/' vars
grep -E 'EASYRSA_CERT_EXPIRE|EASYRSA_CRL_DAYS' vars

Both lines should appear without a leading #.

Revoke and reissue

Run commands one at a time. Each command that touches the CA prompts for the CA passphrase — if you paste a block, the next command becomes the (wrong) passphrase and revoke/gen-crl fail with bad decrypt errors.

1. Revoke the old cert

cd ~/easy-rsa/easyrsa3
./easyrsa revoke radius-1

Type yes at confirmation, then enter the CA passphrase. The old cert/key/req are moved to pki/revoked/.

2. Generate a fresh CRL

./easyrsa gen-crl

Enter CA passphrase. The new pki/crl.pem now lists the revoked serial.

3. Generate a new request

./easyrsa gen-req radius-1 nopass

Press Enter at every DN prompt to accept the defaults. When reissuing radius-2, type the correct hostname at the Common Name prompt — the default in brackets may show the previous CN.

4. Sign the request

./easyrsa sign-req server radius-1

Type yes to confirm, then enter the CA passphrase.

5. Verify

openssl x509 -in pki/issued/radius-1.crt -noout -subject -dates
openssl crl  -in pki/crl.pem -noout -lastupdate -nextupdate

Cert notAfter and CRL nextUpdate should both be ~10 years in the future.

Distribute the new files

To radius-1

From the PKI server:

cd ~/easy-rsa/easyrsa3
rsync -av ./pki/private/radius-1.key radius-1:/etc/ssl/private/
rsync -av ./pki/issued/radius-1.crt  radius-1:/etc/ssl/certs/
rsync -av ./pki/crl.pem              radius-1:/etc/ssl/certs/crl-caponato.pem

On radius-1:

chmod 640 /etc/ssl/private/radius-1.key
chgrp ssl-cert /etc/ssl/private/radius-1.key
chmod 644 /etc/ssl/certs/radius-1.crt
chmod 644 /etc/ssl/certs/crl-caponato.pem
cat /etc/ssl/certs/ca-caponato.pem /etc/ssl/certs/crl-caponato.pem > /etc/freeradius/3.0/ca_and_crl.pem

Restart and verify

systemctl restart freeradius
systemctl status freeradius --no-pager

For deeper verification, stop the service and run in debug mode to watch a real client authenticate cleanly:

systemctl stop freeradius
freeradius -X

Look for Login OK and Sent Access-Accept lines, then Ctrl-C and start the service.

Install the CA certificate on Windows clients

Windows clients need to trust the Radius server's certificate. The easiest way is to deploy the CA certificate via GPO.

  1. Open Group Policy Management on your DC.
  2. Create or edit a GPO linked to the OU containing your computers.
  3. Navigate to: Computer Configuration → Policies → Windows Settings → Security Settings → Public Key Policies → Trusted Root Certification Authorities.
  4. Right-click → Import → select your ca.crt file.

The CA certificate will be pushed to all domain-joined machines on the next Group Policy refresh.

You can force an immediate refresh on a client with:

gpupdate /force

To verify, open certmgr.msc on a client machine and check that your CA appears under Trusted Root Certification Authorities.

Install and configure Freeradius on a member server

Install Freeradius, and add the `freerad` user to Winbind and SSL groups:

apt-get install freeradius freeradius-common freeradius-utils makepasswd
usermod -a -G winbindd_priv,ssl-cert freerad

Configuration steps:

Create a combined CA and CRL certificate:

cat /etc/ssl/certs/ca-caponato.pem /etc/ssl/certs/crl-caponato.pem > /etc/freeradius/3.0/ca_and_crl.pem

Configure Freeradius

Configure clients.conf

This allows your APs to talk to the Radius service.

For a single AP, add this to `/etc/freeradius/3.0/clients.conf`:

# For each access-point add:
client <AP_HOSTNAME> {
    ipaddr = <AP_IPADDRESS>
    netmask = 32
    secret = <AP_SECRET>
    shortname = <AP_HOSTNAME>
}

Or, if you have a subnet with all APs in it — like my setup — you can do:

# For all access-points add:
client unifi-ap {
    ipaddr = 192.168.254.0/24
    secret = radius-24
    shortname = unifi-ap
}

Update proxy.conf:

Pay attention to the line, at the end of the file:

realm mad.caponato.es

And adjust to your setup.

/etc/freeradius/3.0/proxy.conf

proxy server {
    default_fallback = no
}
home_server localhost {
    type = auth
    ipaddr = 127.0.0.1
    port = 1812
    secret = testing123
    response_window = 20
    zombie_period = 40
    revive_interval = 120
    status_check = status-server
    check_interval = 30
    check_timeout = 4
    num_answers_to_alive = 3
    max_outstanding = 65536
    coa {
        irt = 2
        mrt = 16
        mrc = 5
        mrd = 30
    }
    limit {
        max_connections = 16
        max_requests = 0
        lifetime = 0
        idle_timeout = 0
    }
}
home_server_pool samba_auth_failover {
    type = fail-over
    home_server = localhost
}
realm mad.caponato.es {
    auth_pool = samba_auth_failover
}
realm LOCAL {
}

Create the sites samba_default and samba_inner-tunnel

nano /etc/freeradius/3.0/sites-available/samba_default

######################################################################
#
#   As of 2.0.0, FreeRADIUS supports virtual hosts using the
#   "server" section, and configuration directives.
#
#   Virtual hosts should be put into the "sites-available"
#   directory.  Soft links should be created in the "sites-enabled"
#   directory to these files.  This is done in a normal installation.
#
#   If you are using 802.1X (EAP) authentication, please see also
#   the "inner-tunnel" virtual server.  You will likely have to edit
#   that, too, for authentication to work.
#
######################################################################

server default {
    listen {
        type = auth
        ipaddr = *
        port = 1812
        limit {
              max_connections = 16
              lifetime = 0
              idle_timeout = 30
        }
    }
    listen {
        ipaddr = *
        port = 1813
        type = acct
        limit {
              max_connections = 16
              lifetime = 0
              idle_timeout = 30
        }
    }
    # IPv6 versions of the above - read their full config to understand options
    authorize {
        filter_username
        preprocess
        eap {
            ok = return
            #updated = return
        }
    }
    authenticate {
        eap
    }
    preacct {
        preprocess
    }
    accounting {
    }
    post-auth {
        update {
            &reply: += &session-state:
        }
        # For Exec-Program and Exec-Program-Wait
        exec
        #  Remove reply message if the response contains an EAP-Message
        remove_reply_message_if_eap
        Post-Auth-Type REJECT {
            attr_filter.access_reject
            # Insert EAP-Failure message if the request was rejected by policy instead of because of an authentication failure
            eap
            #  Remove reply message if the response contains an EAP-Message
            remove_reply_message_if_eap
        }
    }
    post-proxy {
        #  You MUST also use the 'nostrip' option in the 'realm' configuration.  Otherwise, the User-Name attribute
        #  in the proxied request will not match the user name  hidden inside of the EAP packet, and the end server will
        #  reject the EAP request.
        eap
    }
}

nano /etc/freeradius/3.0/sites-available/samba_inner-tunnel

######################################################################
#
# This is a virtual server that handles *only* inner tunnel
# requests for EAP-TTLS and PEAP types.
#
######################################################################

server inner-tunnel {
    listen {
           ipaddr = 127.0.0.1
           port = 18120
           type = auth
    }
    authorize {
        filter_username
        mschap
        update control {
            &Proxy-To-Realm := LOCAL
        }
        eap {
            ok = return
        }
    }
    authenticate {
        Auth-Type MS-CHAP {
            mschap
        }
        eap
    }
    post-auth {
        Post-Auth-Type REJECT {
            attr_filter.access_reject
            update outer.session-state {
                &Module-Failure-Message := &request:Module-Failure-Message
            }
        }
    } 
    post-proxy {
        eap
    }
}

Remove default sites, and create the symlinks for the Samba sites

# Remove default sites
rm /etc/freeradius/3.0/sites-enabled/default
rm /etc/freeradius/3.0/sites-enabled/inner-tunnel


# Enable Samba sites
ln -s /etc/freeradius/3.0/sites-available/samba_default /etc/freeradius/3.0/sites-enabled/samba_default
ln -s /etc/freeradius/3.0/sites-available/samba_inner-tunnel /etc/freeradius/3.0/sites-enabled/samba_inner-tunnel

Configure mschap

Make sure you correctly update the lines:

  1. -domain=MAD \
  2. -require-membership-of=MAD\wifi \

to your setup.

nano /etc/freeradius/3.0/mods-available/mschap

# Microsoft CHAP authentication
#
#  This module supports MS-CHAP and MS-CHAPv2 authentication.
#  It also enforces the SMB-Account-Ctrl attribute.
#
mschap {
    use_mppe = yes
    with_ntdomain_hack = yes
    require_encryption = yes
    require_strong = yes
    ntlm_auth = "/usr/bin/ntlm_auth --request-nt-key \
                --allow-mschapv2 \
                --domain=MAD \
                --require-membership-of=MAD\wifi \
                --username=%{%{mschap:User-Name}:-00} \
                --challenge=%{%{mschap:Challenge}:-00} \
                --nt-response=%{%{mschap:NT-Response}:-00}"


    pool {
        start = ${thread[pool].start_servers}
        min = ${thread[pool].min_spare_servers}
        max = ${thread[pool].max_servers}
        spare = ${thread[pool].max_spare_servers}
        uses = 0
        retry_delay = 30
        lifetime = 86400
        cleanup_interval = 300
        idle_timeout = 600
    }
    passchange {
    }
}

Configure eap

nano /etc/freeradius/3.0/mods-available/eap

eap {
        default_eap_type = mschapv2
        timer_expire = 60
        ignore_unknown_eap_types = no
        cisco_accounting_username_bug = no
        max_sessions = ${max_requests}
        tls-config tls-common {
                #private_key_password = whatever
                private_key_file = /etc/ssl/private/radius-1.key
                certificate_file = /etc/ssl/certs/radius-1.crt
                ca_file = /etc/freeradius/3.0/ca_and_crl.pem
                ca_path = ${cadir}
                check_crl = yes
                cipher_list = "DEFAULT"
                cipher_server_preference = no
                tls_min_version = "1.2"
                # FreeRADIUS only supports TLS 1.3 for special builds of wpa_supplicant and Windows
                tls_max_version = "1.2"
                ecdh_curve = "prime256v1"
                cache {
                        enable = no
                        store {
                                Tunnel-Private-Group-Id
                        }
                }
                verify {
                }
        }
        tls {
                tls = tls-common
        }
        peap {
                tls = tls-common
                default_eap_type = mschapv2
                copy_request_to_tunnel = no
                use_tunneled_reply = no
                virtual_server = "inner-tunnel"
        }
        mschapv2 {
        }
}

Enable authentication logging

Edit /etc/freeradius/3.0/radiusd.conf and set auth = yes inside the log {} section. This ensures that authentication accept/reject events are logged to /var/log/freeradius/radius.log when running as a daemon.

Find the log section and change:

log {
    ...
    auth = yes
    ...
}

By default auth is set to no, which means the daemon logs nothing about authentication events.

Enable required modules only

# Remove all default modules
rm /etc/freeradius/3.0/mods-enabled/*

# Enable only what's needed
cd /etc/freeradius/3.0/mods-enabled
for mod in always attr_filter eap exec expr files linelog mschap preprocess realm utf8; do ln -s ../mods-available/$mod .; done

Enable and start Freeradius:

systemctl enable freeradius
systemctl start freeradius

Setting up a second Radius server for resilience

A second Radius server provides failover in case the primary becomes unavailable. The UniFi controller allows you to configure a primary and secondary Radius server per SSID.

Prerequisites

  • A second Samba member server joined to the domain (e.g. radius-2).
  • The freerad user added to winbindd_priv and ssl-cert groups.
  • A separate certificate issued for radius-2 via EasyRSA, copied to the right place in radius-2 server.
  • The CA certificate copied on to radius-2

Set permissions on radius-2:

chmod 640 /etc/ssl/private/radius-2.key
chgrp ssl-cert /etc/ssl/private/radius-2.key
chmod 644 /etc/ssl/certs/radius-2.crt

Create the combined CA and CRL file:

cat /etc/ssl/certs/ca-caponato.pem /etc/ssl/certs/crl-caponato.pem > /etc/freeradius/3.0/ca_and_crl.pem

Sync configuration from radius-1

All Freeradius configuration is identical between nodes except for the certificate paths in the eap module. Sync everything except that file:

rsync -av --delete radius-1:/etc/freeradius/ /etc/freeradius/

Then edit /etc/freeradius/3.0/mods-available/eap on radius-2 and update the certificate paths:

private_key_file = /etc/ssl/private/radius-2.key
certificate_file = /etc/ssl/certs/radius-2.crt

The ca_file remains the same on both nodes as they share the same CA.

Validate and start

freeradius -XC
systemctl enable freeradius
systemctl start freeradius

Configure UniFi for failover

In the UniFi controller, go to Settings → Profiles → RADIUS and add both servers. The controller will use the primary and fall back to the secondary if the primary is unreachable.

Preparing for client onboarding to the enterprise network

We are now ready to onboard our first client to the enterprise network. As per the configuration established earlier, the authorized group for network access is identified as `wifi`. Every user who needs access to the enterprise Wi-Fi network, as well as every workstation that requires joining the network, must be a member of the Active Directory group named `wifi`.

To ensure smooth and secure access, it is essential that this group remains up-to-date with the correct membership. Please implement regular audits and updates of this group to ensure that only authorized users and devices are granted access. This will help maintain the security and integrity of the network while minimizing unauthorized access.

If you require every computer to be able to join the network, the easiest way is to add the `Domain Computers` group as a member of the `wifi` group.

Moving forward, please make sure that all new employees or devices requiring network access are promptly added to the `wifi` group, and that any users or devices that no longer need access are removed in a timely manner.

If any automation is in place to manage this group, it should also be reviewed and updated regularly to reflect the most current user and device access needs.

How it works

This is a brief overview of the authentication flow when a Windows machine connects to the enterprise WiFi network.

The players

  • Supplicant: the Windows machine connecting to WiFi.
  • Authenticator: the UniFi access point. It does not make authentication decisions — it forwards everything to Radius.
  • Authentication server: Freeradius, which validates credentials against Samba AD via ntlm_auth.

The flow

  1. The Windows machine associates with the SSID (configured as WPA2-Enterprise) and sends its machine credentials (host/PCNAME.domain) or user credentials.
  2. The access point forwards the request as a RADIUS Access-Request to Freeradius.
  3. Freeradius initiates a PEAP session — a TLS tunnel is established between the client and Freeradius. This is where the CA certificate is validated by the client.
  4. Inside the TLS tunnel, MSCHAPv2 authentication takes place. Freeradius calls ntlm_auth via the mschap module.
  5. ntlm_auth authenticates against Samba AD (via winbind) and checks that the machine or user is a member of the AD group specified by --require-membership-of (in our case MAD\wifi).
  6. If ntlm_auth returns success (exit code 0), Freeradius sends an Access-Accept back to the access point, along with MPPE encryption keys for WPA2.
  7. If authentication fails or the machine/user is not in the group, Freeradius sends an Access-Reject and the client is denied access.
  8. The access point grants or denies network access based on the Radius response.

Why so many round-trips?

A single authentication generates approximately 12 RADIUS Access-Request / Access-Challenge exchanges. This is normal — the PEAP/TLS handshake requires multiple round-trips to establish the encrypted tunnel before the actual MSCHAPv2 authentication can take place inside it.

Key points

  • The AD group check happens inside ntlm_auth, not in Freeradius policy. If you need to change the group, edit the ntlm_auth line in /etc/freeradius/3.0/mods-available/mschap.
  • Both machine accounts (host/PCNAME) and user accounts can authenticate, as long as they are members of the wifi group.
  • The access point never sees the credentials — it only relays encrypted EAP packets between the client and Freeradius.

Caponato's Samba notebook. Start here or else Main menu.

samba/freeradius-ad.txt · Last modified: by caponato