eduroam SP setup

Author

Sami Ait Ali Oulahcen

Published

January 4, 2022

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 and open firewall

We’ll be working with freeradius for our eduroam instance. So let us install freeradius from the epel-release repository.
FreeRADIUS is a modular, opensource implementation of the RADIUS protocol developed and distributed under the GNU GPLv2 licence.

dnf -y install epel-release
dnf -y install freeradius freeradius-utils

The default config in /etc/raddb/radiusd.conf should be fine, but we can tune to our liking.

Let’s open radius ports on firewalld

firewall-cmd --permanent --add-service=radius
firewall-cmd --reload

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
#}

### IPv4 localhost server for tests ###
client localhost_ipv4 {
        ipaddr  = 127.0.0.1
        secret  = testing123
        require_message_authenticator = no
        nas_type = other
        limit {
                max_connections = 16
                lifetime = 0
                idle_timeout = 30
        }
}

### 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 (Optional) CUI for eduroam

Chargeable-User-Identity (CUI) serves as an alias to the user’s real identity and is an answer to accounting problems caused by the use of anonymous identity in some EAP methods. In eduroam, the primary use of CUI is in incident handling, but it can also enhance local accounting.

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:

/etc/raddb/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
        }

}

# Modify with our university's domain
realm uni.ac.ma {
        type            = radius
        authhost        = LOCAL
        accthost        = LOCAL
}

# Modify with our 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
}
#############

Let’s not forget to modify the file configuration with our own parameters, especially the university/institute’s domain and shared secret.

We’ll need to configure the shared secrets in this file with the National Roaming Operator (NRO).

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 our institution.  We will also need to register this
# and our RADIUS server addresses with the NRO.
operator_name = "univ.ma"

# The VLAN to assign eduroam visitors
eduroam_default_guest_vlan = "17"

# The VLAN to assign our 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 eduroam-inner-tunnel {
    listen {
        type = auth
        ipaddr = 127.0.0.1
        port = 18120 # Used for testing only.  Requests proxied internally.
    }

    authorize {
            chap
            mschap
            suffix
        update control {
            Proxy-To-Realm := LOCAL
            }
            eap {
                    ok = return
            }
            files
            expiration
            logintime
            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 have a certificate and key, copy them to /etc/raddb/certs/.
Alternatively you can create a self-signed certificate and key using openssl by following this guide. (NB: the key file should have 600 in permissions)
Recommended certificate properties: https://wiki.geant.org/pages/viewpage.action?pageId=121346259#Howtodeployeduroamonsiteoroncampus(ADVANCED)-Consideration2:Recommendedcertificateproperties

Let’s rename /etc/raddb/mods-available/eap into /etc/raddb/mods-available/eap.og and then put the content below into the file.

/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 our 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 = "eduroam-inner-tunnel"
    }

    peap {
        tls = tls-common
        default_eap_type = mschapv2
                copy_request_to_tunnel = no
                use_tunneled_reply = no
                virtual_server = "eduroam-inner-tunnel"
    }

    mschapv2 {
    }
}
##############

7. Start the 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 the FreeRADIUS daemon.

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 an 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. Test the 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 user@uni.ac.ma userPasswd localhost 1812 testing123
Sent Access-Request Id 77 from 0.0.0.0:37126 to 127.0.0.1:1812 length 77
    User-Name = "user@uni.ac.ma"
    User-Password = "userPasswd"
    NAS-IP-Address = 196.200.160.149
    NAS-Port = 1812
    Message-Authenticator = 0x00
    Cleartext-Password = "userPasswd"
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"