eduroam, short for “education roaming”, is a global federation of academic institutions providing secure and seamless wireless internet access for students, faculty, and staff.
The goal of this tutorial is to setup a working eduroam Service Provider (SP) instance and optionally an Identity Provider (IdP) instance as well.
An eduroam Service Provider or SP is an instance that offers eduroam as a service, i.e. publishing the eduroam SSID on premises and allowing people to connect. This can be anyone from libraries to coffee shops or train stations.
An eduroam Identity Provider or IdP is an instance that joins local users to the eduroam federation, allowing them to use the eduroam network anywhere they go. Only institutions in education or research may join the federation.
Now that we have a basic understanding of what eduroam is and what it does, let’s get to setting up our own instance.
We assume you have a fresh installation of EL9 (Rocky/Alma/RHEL), with static IP addresses and ports 1812/udp and 1813/udp open.
Bismillah, let us begin !
1. Synchronize the system clock#
Clock synchronization is very important when dealing with authentication and authorization requests. It ensure accurate timestamping and facilitates effective log analysis.
Make sure ntpd or chronyd is ruuning & enabled:
systemctl status chronyd
If it’s not running install ntp and start/enable it.
Then check the date for correct time zone:
timedatectl set-timezone Africa/Casablanca
2. Update and reboot#
We will update the system to get security patches and reboot to work with the latest available kernel.
dnf -y update
reboot
3. Install freeradius#
We’ll be working with freeradius for our eduroam instance. So let us install freeradius from the epel-release repository.
dnf -y install epel-release
dnf -y install freeradius freeradius-utils
The default config in /etc/raddb/radiusd.conf should be fine, but you can tune to your liking.
4. Configure clients and proxies#
In this section we’ll configure the files clients.conf and proxy.conf which are both crucial in any RADIUS deployment.
4.1 Configure clients#
FreeRADIUS uses the clients.conf file to establish which devices are authorised to interact with the RADIUS server. For this, it uses a set of IP addresses and corresponding shared secrets to control access to the RADIUS server.
You’ll find that the existing /etc/raddb/clients.conf is bloated. For simplicity’s sake, we’ll rename it /etc/raddb/clients.conf.og, and create a new one with the following configuration.
/etc/raddb/clients.conf#############
### Wireless controller config ###
#client uni_wlc_ipv4 {
# ipaddr = 192.1xx.0.18
# secret = sharedSecretGoesHere
# require_message_authenticator = no
# nas_type = other
# Operator-Name = 1uni.ac.ma
# add_cui = yes
#}
#client uni_wlc_ipv6 {
# ipaddr = 2001:db8::18
# secret = sharedSecretGoesHere
# require_message_authenticator = no
# nas_type = other
# Operator-Name = 1uni.ac.ma
# add_cui = yes
#}
### IPv6 localhost server for tests ###
client localhost_ipv6 {
ipv6addr = ::1
secret = testing123
require_message_authenticator = no
nas_type = other
limit {
max_connections = 16
lifetime = 0
idle_timeout = 30
}
}
### Morocco eTLR servers ###
client eduroam-morocco-flr1-ipv4 {
ipaddr = 196.200.131.17
secret = placeSharedSecr3there
nastype = 'eduroam_flr'
}
client eduroam-morocco-flr1-ipv6 {
ipaddr = 2001:4310:f6:d::17
secret = placeSharedSecr3tHere
nastype = 'eduroam_flr'
}
client eduroam-morocco-flr2-ipv4 {
ipaddr = 196.200.160.18
secret = placeSharedSecretHere
nastype = 'eduroam_flr'
}
client eduroam-morocco-flr2-ipv6 {
ipaddr = 2001:4310:f1::18
secret = placeSharedSecretHere
nastype = 'eduroam_flr'
}
#############
4.2 CUI for eduroam#
Let’s setup CUI for eduroam logging:
dnf --enablerepo=crb install freeradius-sqlite
Create a new file named /etc/raddb/mods-available/eduroam_cui_log
touch /etc/raddb/mods-available/eduroam_cui_log
chmod 640 /etc/raddb/mods-available/eduroam_cui_log
chgrp radiusd /etc/raddb/mods-available/eduroam_cui_log
Then edit it with the following content
/etc/raddb/mods-available/eduroam_cui_loglinelog cui_log {
# filename = syslog
filename = ${logdir}/radius.log
format = ""
reference = "auth_log.%{%{reply:Packet-Type}:-format}"
auth_log {
Access-Accept = "%t : eduroam-auth#ORG=%{request:Realm}#USER=%{User-Name}#CSI=%{%{Calling-Station-Id}:-Unknown Caller Id}#NAS=%{%{Called-Station-Id}:-Unknown Access Point}#CUI=%{%{reply:Chargeable-User-Identity}:-Unknown}#MSG=%{%{EAP-Message}:-No EAP Message}#RESULT=OK#"
Access-Reject = "%t : eduroam-auth#ORG=%{request:Realm}#USER=%{User-Name}#CSI=%{%{Calling-Station-Id}:-Unknown Caller Id}#NAS=%{%{Called-Station-Id}:-Unknown Access Point}#CUI=%{%{reply:Chargeable-User-Identity}:-Unknown}#MSG=%{%{reply:Reply-Message}:-No Failure Reason}#RESULT=FAIL#"
}
}
Create a softlink
cd /etc/raddb/mods-enabled; ln -s ../mods-available/eduroam_cui_log; ln -s ../mods-available/cui
Edit the CUI policy file /etc/raddb/policy.d/cui
/etc/raddb/policy.d/cuicui_hash_key = "changeme" # --> replace with a random string
# if you use a secondary or backup FreeRADIUS server, use the same cui_hash_key
# this allows you to keep the same CUI log even if the FreeRADIUS server change
cui_require_operator_name = "yes"
Modify the post proxy attribute filter:
mods-config/attr_filter/post-proxyDEFAULT
...
User-Name =* ANY,
Chargeable-User-Identity =* ANY,
...
Edit policy.d/filter, add a filter function 'cui_filter'. Simple example :
# Filter the Chargeable-User-Identity attribute
cui_filter {
if (&reply:Chargeable-User-Identity =~ /REPLACE-WITH-CUI-TO-MATCH/) {
update request {
&Module-Failure-Message += "Rejected: CUI matching '%{reply:Chargeable-User-Identity}'"
}
reject
}
}
4.3 Configure proxies#
The proxy.conf file in FreeRADIUS manages RADIUS requests proxying between servers. It basically routes authentication and authorization requests across network infrastructure.
Same as before, the existing /etc/raddb/proxy.conf is quite big. To simplify, we rename it /etc/raddb/proxy.conf.og, and create a new one with the following configuration.
/etc/raddb/proxy.conf#############
proxy server {
default_fallback = no
}
home_server localhost {
type = auth
ipaddr = ::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
}
}
# Please modify this with your university's domain
realm uni.ac.ma {
type = radius
authhost = LOCAL
accthost = LOCAL
}
# Please modify this with your university's domain
realm "~(.*\.)uni\.ac\.ma$" {
type = radius
authhost = LOCAL
accthost = LOCAL
}
realm LOCAL {
type = radius
authhost = LOCAL
accthost = LOCAL
}
realm NULL {
type = radius
authhost = LOCAL
accthost = LOCAL
}
### Morocco eTLR servers
home_server eduroam_morocco_flr1_ipv4 {
type = auth+acct
ipaddr = 196.200.131.17
port = 1812
secret = sharedSecretGoesHere
response_window = 20
zombie_period = 40
status_check = status-server
revive_interval = 60
check_interval = 30
num_answers_to_alive = 3
}
home_server eduroam_morocco_flr1_ipv6 {
type = auth+acct
ipaddr = 2001:4310:f6:d::17
port = 1812
secret = sharedSecretGoesHere
response_window = 20
zombie_period = 40
status_check = status-server
revive_interval = 60
check_interval = 30
num_answers_to_alive = 3
}
home_server eduroam_morocco_flr2_ipv4 {
type = auth+acct
ipaddr = 196.200.160.18
port = 1812
secret = sharedSecretGoesHere
response_window = 20
zombie_period = 40
status_check = status-server
revive_interval = 60
check_interval = 30
num_answers_to_alive = 3
}
home_server eduroam_morocco_flr2_ipv6 {
type = auth+acct
ipaddr = 2001:4310:f1::18
port = 1812
secret = sharedSecretGoesHere
response_window = 20
zombie_period = 40
status_check = status-server
revive_interval = 60
check_interval = 30
num_answers_to_alive = 3
}
home_server_pool eduroam_morocco_pool {
type = keyed-balance
home_server = eduroam_morocco_flr2_ipv4
home_server = eduroam_morocco_flr1_ipv4
}
### Catch all non local users and route the requests to Morocco's eFLR servers
realm "~.+$" {
auth_pool = eduroam_morocco_pool
nostrip
}
#############
Don’t forget to modify the file configuration with your own parameters, especially the university/institute’s domain and shared secret.
5. Configure the sites eduroam and eduroam-inner-tunnel#
Now let’s configure the eduroam site and inner tunnel. Delete all the files in sites-enabled folder. Create two files called eduroam and eduroam-inner-tunnel and paste the contents from below.
5.1 Configure eduroam#
unlink /etc/raddb/sites-enabled/default
vi /etc/raddb/sites-available/eduroam
/etc/raddb/sites-available/eduroam##############
# The domain users will add to their username to have their credentials
# routed to your institution. You will also need to register this
# and your RADIUS server addresses with your NRO.
operator_name = "univ.ma"
# The VLAN to assign eduroam visitors
eduroam_default_guest_vlan = "17"
# The VLAN to assign your students/staff
eduroam_default_local_vlan = "14"
server eduroam {
listen {
type = auth
ipaddr = *
port = 0
limit {
max_connections = 16
lifetime = 0
idle_timeout = 30
}
}
listen {
type = acct
ipaddr = *
port = 0
limit {
}
}
listen {
type = auth
ipv6addr = ::
port = 0
limit {
max_connections = 16
lifetime = 0
idle_timeout = 30
}
}
listen {
type = acct
ipv6addr = ::
port = 0
limit {
}
}
authorize {
update request {
Operator-Name := "1univ.ma"
# The "1" above is important, do not change.
}
cui
preprocess
filter_username
auth_log
suffix
eap {
ok = return
updated = return
}
files
#ldap
pap
}
pre-proxy {
attr_filter.pre-proxy
#linelog_send_proxy_request
cui
}
post-proxy {
attr_filter.post-proxy
#linelog_recv_proxy_response
}
authenticate {
Auth-Type PAP {
pap
#ldap
}
Auth-Type CHAP {
chap
#ldap
}
Auth-Type MS-CHAP {
mschap
#ldap
}
#Auth-Type LDAP {
#ldap
#}
eap
}
post-auth {
cui
cui_log
update reply {
Tunnel-Type := VLAN
Tunnel-Medium-Type := IEEE-802
}
if (&control:Proxy-To-Realm) {
update reply {
Tunnel-Private-Group-ID = ${eduroam_default_guest_vlan}
}
}
else {
update reply {
Tunnel-Private-Group-ID = ${eduroam_default_local_vlan}
}
}
# We're sending a response to one of OUR network devices for one of
# OUR users so provide it with the real user-identity.
if (&session-state:Stripped-User-Name) {
update reply {
User-Name := "%{session-state:Stripped-User-Name}@%{Stripped-User-Domain}"
}
}
#linelog_send_accept
Post-Auth-Type REJECT {
attr_filter.access_reject
#linelog_send_reject
}
}
}
##############
Once we’ve created the file, let us fix eduroam site file permissions and add a soft link to it from the /etc/raddb/sites-enabled/.
chgrp radiusd /etc/raddb/sites-available/eduroam
chmod 640 /etc/raddb/sites-available/eduroam
chcon system_u:object_r:radiusd_etc_t:s0 /etc/raddb/sites-available/eduroam
cd /etc/raddb/sites-enabled/
ln -s ../sites-available/eduroam eduroam
chgrp -ch radiusd eduroam
5.2 Configure eduroam-inner-tunnel#
We’ll do the same thing for eduroam-inner-tunnel:
- Unlink the default
/etc/raddb/sites-enabled/inner-tunnel. - Create the new
/etc/raddb/sites-available/eduroam-inner-tunnelwith contents from below. - Then create a soft link in
/etc/raddb/sites-enabled/.
/etc/raddb/sites-available/eduroam-inner-tunnel###############
server inner-tunnel {
listen {
type = auth
ipaddr = 127.0.0.1
port = 18120 # Used for testing only. Requests proxied internally.
}
authorize {
#
# The chap module will set 'Auth-Type := CHAP' if we are
# handling a CHAP request and Auth-Type has not already been set
chap
#
# If the users are logging in with an MS-CHAP-Challenge
# attribute for authentication, the mschap module will find
# the MS-CHAP-Challenge attribute, and add 'Auth-Type := MS-CHAP'
# to the request, which will cause the server to then use
# the mschap module for authentication.
mschap
#
# Pull crypt'd passwords from /etc/passwd or /etc/shadow,
# using the system API's to get the password. If you want
# to read /etc/passwd or /etc/shadow directly, see the
# passwd module, above.
#
# unix
#
# Look for IPASS style 'realm/', and if not found, look for
# '@realm', and decide whether or not to proxy, based on
# that.
# IPASS
#
# If you are using multiple kinds of realms, you probably
# want to set "ignore_null = yes" for all of them.
# Otherwise, when the first style of realm doesn't match,
# the other styles won't be checked.
#
# Note that proxying the inner tunnel authentication means
# that the user MAY use one identity in the outer session
# (e.g. "anonymous", and a different one here
# (e.g. "user@example.com"). The inner session will then be
# proxied elsewhere for authentication. If you are not
# careful, this means that the user can cause you to forward
# the authentication to another RADIUS server, and have the
# accounting logs *not* sent to the other server. This makes
# it difficult to bill people for their network activity.
#
suffix
# ntdomain
#
# The "suffix" module takes care of stripping the domain
# (e.g. "@example.com") from the User-Name attribute, and the
# next few lines ensure that the request is not proxied.
#
# If you want the inner tunnel request to be proxied, delete
# the next few lines.
#
update control {
Proxy-To-Realm := LOCAL
}
#
# This module takes care of EAP-MSCHAPv2 authentication.
#
# It also sets the EAP-Type attribute in the request
# attribute list to the EAP type from the packet.
#
# The example below uses module failover to avoid querying all
# of the following modules if the EAP module returns "ok".
# Therefore, your LDAP and/or SQL servers will not be queried
# for the many packets that go back and forth to set up TTLS
# or PEAP. The load on those servers will therefore be reduced.
#
eap {
ok = return
}
#
# Read the 'users' file
files
#
# Look in an SQL database. The schema of the database
# is meant to mirror the "users" file.
#
# See "Authorization Queries" in sql.conf
# sql
#
# If you are using /etc/smbpasswd, and are also doing
# mschap authentication, the un-comment this line, and
# configure the 'etc_smbpasswd' module, above.
# etc_smbpasswd
#
# The ldap module will set Auth-Type to LDAP if it has not
# already been set
#ldap
#
# Enforce daily limits on time spent logged in.
# daily
#
# Use the checkval module
# checkval
expiration
logintime
#
# If no other module has claimed responsibility for
# authentication, then try to use PAP. This allows the
# other modules listed above to add a "known good" password
# to the request, and to do nothing else. The PAP module
# will then see that password, and use it to do PAP
# authentication.
#
# This module should be listed last, so that the other modules
# get a chance to set Auth-Type for themselves.
#
pap
}
authenticate {
Auth-Type PAP {
pap
}
Auth-Type CHAP {
chap
}
Auth-Type MS-CHAP {
mschap
}
#Auth-Type LDAP {
#ldap
#}
eap
}
session {
}
post-auth {
reply_log
Post-Auth-Type REJECT {
attr_filter.access_reject
reply_log
update outer.session-state {
&Module-Failure-Message := &request:Module-Failure-Message
}
}
}
pre-proxy {
}
post-proxy {
eap
}
}
#############
6. Configure certificates for the eap module#
If you already a certificate and key, copy them to /etc/raddb/certs/. Then change the cert files accordingly in /etc/raddb/mods-enabled/eap (see below). Alternatively you can create a self-signed certificate and key using openssl. (NB: the key file should have 600 in permissions)
/etc/raddb/mods-enabled/eap##############
eap {
default_eap_type = ttls
timer_expire = 60
ignore_unknown_eap_types = no
cisco_accounting_username_bug = no
max_sessions = ${max_requests}
md5 {
}
leap {
}
gtc {
auth_type = PAP
}
tls-config tls-common {
certificate_file = ${certdir}/eduroam.univ.ma.pem
private_key_file = ${certdir}/eduroam.univ.ma.key
# private_key_password = whatever
ca_file = ${cadir}/univCACert.pem
# If your AP drops packets towards the client, try reducing this.
fragment_size = 1024
# When issuing client certificates embed the OCSP URL in the
# certificate if you want to be able to revoke them later.
ocsp {
enable = yes
override_cert_url = no
use_nonce = yes
}
}
tls {
tls = tls-common
}
ttls {
tls = tls-common
default_eap_type = gtc
copy_request_to_tunnel = yes
use_tunneled_reply = yes
virtual_server = "inner-tunnel"
}
peap {
tls = tls-common
default_eap_type = mschapv2
copy_request_to_tunnel = no
use_tunneled_reply = no
virtual_server = "inner-tunnel"
}
mschapv2 {
}
}
##############
7. Start your radius server#
To test if our configuration is correct, let’s first start the server in debug mode.
radiusd -X
If all is OK, now let’s start our server.
systemctl start radiusd
And then let’s enable our radius daemon, so that it starts automatically everytime the system reboots.
systemctl enable radiusd
8. (For IdPs only) Configure your internal authentication method#
We are currently supporting 2 ways of configuring user authentication with freeradius:
- For openldap, go to eduroam IdP with OpenLDAP
- For active directory, go to eduroam IdP with Active Directory
9. Finally test your radius server#
Now that we’re done configuring our RADIUS eduroam server, let’s do some tests ! You can run radius X to troubleshoot any possible issues.
[root@eduroam ~]# radtest edutest edutest localhost 1812 testing123
Sent Access-Request Id 77 from 0.0.0.0:37126 to 127.0.0.1:1812 length 77
User-Name = "edutest"
User-Password = "edutest"
NAS-IP-Address = 196.200.160.149
NAS-Port = 1812
Message-Authenticator = 0x00
Cleartext-Password = "edutest"
Received Access-Accept Id 77 from 127.0.0.1:1812 to 127.0.0.1:37126 length 36
Tunnel-Type:0 = VLAN
Tunnel-Medium-Type:0 = IEEE-802
Tunnel-Private-Group-Id:0 = "14"