MARWAN docs NOC knowledge base
Docseduroam

eduroam setup

Set up an eduroam Service Provider, and optionally an Identity Provider, with FreeRADIUS on EL9 (Rocky, Alma, RHEL).

Updated Sami Ait Ali Oulahcen

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_log
linelog 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/cui
cui_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-proxy
DEFAULT
...
        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
}
#############
Important

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-tunnel with 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:

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"