Complete guide on how to set up FreeRADIUS 3.X with MySQL backend database
EAP-TTLS and MySQL TLS database communication
802.1X Authentication Authorization Accounting
Alpine Linux lightweight Docker container
Today we are going to explain how to set up a FreeRADIUS 3 server for Authentication, Authorization and Accounting (AAA) along with a MySQL database for credentials storage and accounting logs accessed only through encrypted TLS connections. We will show how to set up FreeRADIUS with the secure EAP-TTLS (Tunneled TLS) communication.
What is FreeRADIUS
FreeRADIUS is the most popular and most widely deployed open source RADIUS server. It supports all the most common client authentication protocols and its fast and scalable. It supports also Two Factor Authentication
FreeRADIUS can be used for WiFi client authentication as well as with any other system that require client authentication as for example OpenVPN server.
Each user will have a credential in the form
<username>@<domain>
and a respective password.
For this tutorial let’s assume we have the acme.com
domain and a [email protected]
user.
How does RADIUS works?
- A client will connect to a Network Access Server (NAS) first. This could be a router providing Wireless access. The client will not have any connectivity, the router is allowing only RADIUS traffic.
- The router acting as a NAS will receive a
Access Request
message from the client and will forward it to the RADIUS server. - The RADIUS Server will check the credentials and send back a response that could be
Access-Accept
orAccess-Reject
or aAccess-Challenge
. - The NAS will forward the response to the client, and if the authentication succeed, it will then open the communication to the internal network. Ref: How does RADIUS work? by Cisco
EAP-TTLS and PAP
We will setup FreeRADIUS with EAP-TTLS. This means each client connecting to the NAS will first establish a secure TLS tunnel. This is called First Phase. In the Second Phase the client will send his credentials in cleartext* with Password Authentication Protocol (PAP) but since those are sent over the TLS channel, none can read it and none can eavesdrop which user is trying to authenticate to the NAS.
*The password is not actually sent in cleartext but it is rather hashed with the key shared between the NAS and the RADIUS Server. Still is not considered secure but since it is sent over an encrypted channel, that makes the mitigation.
MySQL initialization
MySQL is used to keep track of clients login attempts, connection times and other accounting features and it will store the user credentials.
First of all you need a MySQL Database running, then you have to create the tables needed for FreeRADIUS.
Here is the FreeRADIUS SQL schema:
#
# Table structure for table 'radacct'
#
CREATE TABLE radacct (
radacctid bigint(21) NOT NULL auto_increment,
acctsessionid varchar(64) NOT NULL default '',
acctuniqueid varchar(32) NOT NULL default '',
username varchar(64) NOT NULL default '',
realm varchar(64) default '',
nasipaddress varchar(15) NOT NULL default '',
nasportid varchar(15) default NULL,
nasporttype varchar(32) default NULL,
acctstarttime datetime NULL default NULL,
acctupdatetime datetime NULL default NULL,
acctstoptime datetime NULL default NULL,
acctinterval int(12) default NULL,
acctsessiontime int(12) unsigned default NULL,
acctauthentic varchar(32) default NULL,
connectinfo_start varchar(50) default NULL,
connectinfo_stop varchar(50) default NULL,
acctinputoctets bigint(20) default NULL,
acctoutputoctets bigint(20) default NULL,
calledstationid varchar(50) NOT NULL default '',
callingstationid varchar(50) NOT NULL default '',
acctterminatecause varchar(32) NOT NULL default '',
servicetype varchar(32) default NULL,
framedprotocol varchar(32) default NULL,
framedipaddress varchar(15) NOT NULL default '',
PRIMARY KEY (radacctid),
UNIQUE KEY acctuniqueid (acctuniqueid),
KEY username (username),
KEY framedipaddress (framedipaddress),
KEY acctsessionid (acctsessionid),
KEY acctsessiontime (acctsessiontime),
KEY acctstarttime (acctstarttime),
KEY acctinterval (acctinterval),
KEY acctstoptime (acctstoptime),
KEY nasipaddress (nasipaddress)
) ENGINE = INNODB;
#
# Table structure for table 'radcheck'
#
CREATE TABLE radcheck (
id int(11) unsigned NOT NULL auto_increment,
username varchar(64) NOT NULL default '',
attribute varchar(64) NOT NULL default '',
op char(2) NOT NULL DEFAULT '==',
value varchar(253) NOT NULL default '',
PRIMARY KEY (id),
KEY username (username(32))
);
#
# Table structure for table 'radgroupcheck'
#
CREATE TABLE radgroupcheck (
id int(11) unsigned NOT NULL auto_increment,
groupname varchar(64) NOT NULL default '',
attribute varchar(64) NOT NULL default '',
op char(2) NOT NULL DEFAULT '==',
value varchar(253) NOT NULL default '',
PRIMARY KEY (id),
KEY groupname (groupname(32))
);
#
# Table structure for table 'radgroupreply'
#
CREATE TABLE radgroupreply (
id int(11) unsigned NOT NULL auto_increment,
groupname varchar(64) NOT NULL default '',
attribute varchar(64) NOT NULL default '',
op char(2) NOT NULL DEFAULT '=',
value varchar(253) NOT NULL default '',
PRIMARY KEY (id),
KEY groupname (groupname(32))
);
#
# Table structure for table 'radreply'
#
CREATE TABLE radreply (
id int(11) unsigned NOT NULL auto_increment,
username varchar(64) NOT NULL default '',
attribute varchar(64) NOT NULL default '',
op char(2) NOT NULL DEFAULT '=',
value varchar(253) NOT NULL default '',
PRIMARY KEY (id),
KEY username (username(32))
);
#
# Table structure for table 'radusergroup'
#
CREATE TABLE radusergroup (
username varchar(64) NOT NULL default '',
groupname varchar(64) NOT NULL default '',
priority int(11) NOT NULL default '1',
KEY username (username(32))
);
#
# Table structure for table 'radpostauth'
#
CREATE TABLE radpostauth (
id int(11) NOT NULL auto_increment,
username varchar(64) NOT NULL default '',
pass varchar(64) NOT NULL default '',
reply varchar(32) NOT NULL default '',
authdate timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) ENGINE = INNODB;
#
# Table structure for table 'nas'
#
CREATE TABLE nas (
id int(10) NOT NULL auto_increment,
nasname varchar(128) NOT NULL,
shortname varchar(32),
type varchar(30) DEFAULT 'other',
ports int(5),
secret varchar(60) DEFAULT 'secret' NOT NULL,
server varchar(64),
community varchar(50),
description varchar(200) DEFAULT 'RADIUS Client',
PRIMARY KEY (id),
KEY nasname (nasname)
);
To execute just run:
mysql -u root -prootpass radius < schema.sql
assuming you already created the radius
database.
And then we are going to create a user “radius” with the right privileges.
Here we create the user radius with password donotuseme (if you want to restrict connections from a specific IP address only, you specify the IP instead of *):
#
# Create default administrator for RADIUS
#
CREATE USER 'radius'@'*';
SET PASSWORD FOR 'radius'@'*' = 'donotuseme';
ALTER USER 'radius'@'*' IDENTIFIED WITH mysql_native_password BY 'donotuseme';
# The server can read any table in SQL
GRANT SELECT ON radius.* TO 'radius'@'*';
# The server can write to the accounting and post-auth logging table.
#
# i.e.
GRANT ALL on radius.radacct TO 'radius'@'*';
GRANT ALL on radius.radpostauth TO 'radius'@'*';
#
# Insert a valid FreeRADIUS User credential
#
INSERT into radius.radcheck (username,attribute,op,value) values("[email protected]", "SSHA2-256-Password", ":=", "ag6J2U52nmn7gkQM2h4eXEYQnHON7W9DyyGKxUSiAsFzYWx0c2FsdHNhbHQ=");
In the second part of the SQL code we are going to insert a valid RADIUS user credential for a valid REALM (@acme.com we will get to this later) and generate a SHA256 salted password compatible with FreeRADIUS.
To do so I created a Python script here. Just change “test” to the password you want to hash, and use a random salt. (I set the salt size limit to 12 charaters)
For example the password ‘test’ becomes ag6J2U52nmn7gkQM2h4eXEYQnHON7W9DyyGKx-
USiAsFzYWx0c2FsdHNhbHQ=
and we are going to save it as SSHA2-256-Password in the database.
MySQL TLS connection setup
Now we are setting up MySQL to encrypt each communication with TLS 1.2 and rejecting every cleartext connection.
This is my /etc/my.cnf
:
# This group is read both both by the client and the server
# use it for options that affect everything
[client-server]
# This group is read by the server
[mysqld]
ssl-cipher=TLSv1.2
ssl-ca=/mysql-certs/ca.crt
ssl-cert=/mysql-certs/mysql.acme.com.crt
ssl-key=/mysql-certs/mysql.acme.com.pem
require_secure_transport=ON
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0
datadir=/var/lib/mysql
bind-address=0.0.0.0
log-error=/var/log/mysql/error.log
skip-log-bin
log-output=FILE
general-log=1
general_log_file=/var/log/mysql/general.log
port=3306
user=mysql
socket=/run/mysqld/mysqld.sock
pid-file=/run/mysqld/mysqld.pid
default_authentication_plugin=mysql_native_password
[client]
ssl-cipher=TLSv1.2
ssl-ca=/mysql-certs/ca.crt
Off course make sure to copy your certificates under /mysql-certs
.
The require_secure_transport=ON
directive makes sure to reject every unencrypted connection request.
And we are done with the MySQL part. If you want I wrote a Docker Container with this complete setup. At the end of the tutorial the link.
FreeRadius 3 configuration
First install the FreeRADIUS packages required: (alpine linux)
apk add freeradius openssl freeradius-mysql freeradius-eap
then the files we are going to modify are:
- /etc/raddb/clients.conf
- /etc/raddb/sites-available/default
- /etc/raddb/sites-available/inner-tunnel
- /etc/raddb/mods-available/sql
- /etc/raddb/mods-available/eap
NOTE: It’s super easy to break the server with wrong configuration, so each time you modify something, make sure the server is starting correctly.
To run radius in debug mode: radiusd -X
/etc/raddb/client.conf configuration file
Here you need to specify the clients allowed to send requests to the RADIUS server. Most of the times we want to configure a router as NAS or Network Access Server so that WiFi clients connecting will communicate to the RADIUS server via the router.
Add these lines at the end of the file:
RADIUS_SECRET
is a shared password that will be set also on the router.ipaddr
is the IP subnet of the router.client router_nas { ipaddr = 192.168.10.0/24 secret = RADIUS_SECRET }
/etc/raddb/default configuration file
Under the authorize {}
section:
Uncomment these lines
eap {
ok = return
updated = return
}
and
sql
Under authenticate {}
section make sure to uncomment:
eap
Under the accounting {}
section uncomment the sql line:
sql
Under the session {}
uncomment the sql
line.
Under the post-auth {}
section uncomment the sql
line.
Under the post-auth {}
section, under the Post-Auth-Type REJECT {}
uncomment the sql
line.
/etc/raddb/sites-available/inner-tunnel
This is a virtual server that handles only inner tunnel requests for EAP-TTLS and PEAP types.
Under authorize {}
make sure both sql
and eap {..}
are uncommented.
Under authenticate {}
make sure eap
is uncommented.
Under session {}
make sure sql
is uncommented.
Under post-auth {}
make sure sql
is uncommented as well as in the Post-Auth-Type REJECT {}
subsection.
/etc/raddb/mods-available/sql
This is the configuration file for the SQL module.
sql {
driver = "rlm_sql_mysql"
mysql {
# If any of the files below are set, TLS encryption is enabled
tls {
ca_file = "/etc/raddb/ca.crt"
# If you want to use client certificates for MySQL TLS
certificate_file = "/etc/raddb/rad_client.crt"
private_key_file = "/etc/raddb/rad_client.pem"
}
# If yes, (or auto and libmysqlclient reports warnings are
# available), will retrieve and log additional warnings from
# the server if an error has occured. Defaults to 'auto'
warnings = yes
}
dialect = "mysql"
# Connection info:
server = "RADIUS_SERVER_IP"
port = 3306
login = "radius"
password = "RADIUS_USR_PASSWORD"
# Database table configuration for everything except Oracle
radius_db = "radius"
read_clients = "yes"
sql_user_name = "%{User-Name}"
# If you want both stop and start records logged to the
# same SQL table, leave this as is. If you want them in
# different tables, put the start table in acct_table1
# and stop table in acct_table2
acct_table1 = "radacct"
acct_table2 = "radacct"
# Allow for storing data after authentication
postauth_table = "radpostauth"
# Tables containing 'check' items
authcheck_table = "radcheck"
groupcheck_table = "radgroupcheck"
# Tables containing 'reply' items
authreply_table = "radreply"
groupreply_table = "radgroupreply"
# Table to keep group info
usergroup_table = "radusergroup"
# Remove stale session if checkrad does not see a double login
delete_stale_sessions = yes
pool {
start = ${thread[pool].start_servers}
# Minimum number of connections to keep open
min = ${thread[pool].min_spare_servers}
# Maximum number of connections
max = ${thread[pool].max_servers}
# Spare connections to be left idle
spare = ${thread[pool].max_spare_servers}
# Number of uses before the connection is closed
uses = 0
# The number of seconds to wait after the server tries
# to open a connection, and fails. During this time,
# no new connections will be opened.
retry_delay = 30
# The lifetime (in seconds) of the connection
lifetime = 0
# idle timeout (in seconds). A connection which is
# unused for this length of time will be closed.
idle_timeout = 60
}
# Table to keep radius client info
client_table = "nas"
# This entry should be used for the default instance (sql {})
# of the SQL module.
group_attribute = "SQL-Group"
# Read database-specific queries
$INCLUDE ${modconfdir}/${.:name}/main/${dialect}/queries.conf
}
To enable the SQL module make sure /etc/raddb/mods-enabled/sql
is existing, otherwise:
$ ln -s /etc/raddb/mods-available/sql /etc/raddb/mods-enabled/sql
/etc/raddb/mods-available/eap
Among the other options, the ones that need to be changed are:
eap {
...
default_eap_type = ttls
...
tls-config tls-common {
private_key_file = /etc/raddb/radius.acme.com.key
certificate_file = /etc/raddb/radius.acme.com.crt
ca_file = /etc/raddb/ca.crt
# For DH cipher suites to work, you have to
# run OpenSSL to create the DH file first:
# openssl dhparam -out certs/dh 2048
dh_file = /etc/raddb/dh.pem
random_file = /dev/urandom
cipher_list = "HIGH"
cipher_server_preference = yes
tls_min_version = "1.2"
ecdh_curve = "secp384r1"
}
...
ttls {
# make sure these lines are the same
tls = tls-common
virtual_server = "inner-tunnel"
}
}
Off course copy the respective certificates to /etc/raddb/
To enable the EAP module make sure /etc/raddb/mods-enabled/eap
is existing, otherwise:
$ ln -s /etc/raddb/mods-available/eap /etc/raddb/mods-enabled/eap
NOTE Assuming you have the current latest OPENSSL version installed. If FreeRADIUS is giving some security error related to openssl you need to add allow_vulnerable_openssl = "CVE-2016-6309"
to the /etc/raddb/radiusd.conf
under the security {}
section.
We have now finished with the settings. As said above, after each file configuration change, restart the FreeRADIUS server and check that everything is running without errors.
Testing
You can run FreeRADIUS in debug mode and start to test it.
$ radiusd -X
You can test the basic PAP authentication (without EAP-TTLS) with:
$ radtest test@acme.com test 192.168.5.12 0 testing123 -x
Assuming:
192.168.5.12
is the FreeRADIUS IP addresstesting123
is the shared password[email protected]
is a user stored in the database
Now you can try to configure the Router to use EAP for WiFi client authentication. Always look at the logs in the FreeRADIUS server and try to understand what is going on.
Testing EAP-TTLS
wpa-supplicant is supplying the eapol_test program to test RADIUS EAP.
Create a file, eapol-tls.conf
for example:
network={
ssid="SSID_OF_THE_WIFI_NETWORK"
key_mgmt=WPA-EAP
eap=TTLS
identity="[email protected]"
anonymous_identity="anonymous"
password="INSERT_THE_USER_PASSWORD"
phase2="auth=PAP"
eapol_flags=3
}
And run it with:
$ eapol_test -a192.168.5.12 -p1812 -stest -ceapol-tls.conf -r0 -stesting123
Where for example:
192.168.5.12
is the FreeRADIUS server IP address1812
is the default porttest
is the passwordeapol-tls.conf
is the config file for the testtesting123
is the shared key
If everything has been set up correctly you should be able to see Access-Accept
.
Congratulations! Now you can continue setting up the Router with your new FreeRADIUS server!
GitHub Repository
Here is the link to the Github freeRADIUS + MySQL repository.
Please feel free to make any comment! If anything is unclear, just write in the comment and I will update the post!
Thanks for reading!
Last modified: 26 January 2022