SMUS Wireless Configuration

From SMUSwiki
Revision as of 13:57, 17 November 2011 by Chris.kloosterman (talk | contribs)
Jump to navigation Jump to search

Introduction

This page will discuss the backend for the SMUS wireless configuration.

Prerequisites

Our requirements for wireless on campus are fairly complex. We decided on several prerequisites, for a variety of reasons.

802.1x

We wanted staff and students to be able to sign into the wireless system themselves without any help from the MIS department. Several implementations use a captive portal mechanism to do this, but that results in an SSID with no encryption. By using 802.1x, we can use WPA2-Enterprise encryption on the SSIDs, and staff and students can still configure the connection themselves.

Active Directory Integration

Wherever possible, we have staff and students use their active directory credentials to log into whatever they're trying to access. We wanted this system to also use the active directory credentials. The AD server had preexisting groups we wanted to take advantage of as well, and we added more groups later to further separate out our users.

Different Student and Staff IP addresses

Students on our campus are subject to bandwidth limitations and packet shaping. We do not want to apply these restrictions to staff members. Our packet shaper uses a simple IP range to track which machines to apply restrictions to, so we needed our students to be assigned a different set of IP addresses than our staff.

VLANs for mobile labs

Our mobile laptop labs also need different IP ranges. As we have a senior and middle school, we apply different web filtering to each set of computers. We achieved this using VLAN assignments in freeradius after the machines logged in, based on a regular expression check on the computer name.

Single simultaneous login for students

Our students are limited to one simultaneous wireless connection. This is to discourage password sharing (one student using another student's account will then stop the original student from being able to log in). As different grades have different access times, we didn't want grade 12 students sharing their credentials to students in lower grades. This is also potentially a bandwidth saving measure, as a single student can't have their iPod, iPhone, computer, etc. clogging up the same access point simultaneously. Again, these restrictions do not apply to staff.

Login times by grade

We apply different login hours by grade. Grade 12s can use the wireless longer into the evening than grade 11s, which can use it longer than grade 10s, etc.

Guest logins

As there are frequently parents and other guests on campus, we wanted it to be fairly simple for them to get their wireless device onto our network. These devices are sometimes not as secure as other devices, so they are separated out onto their own network, and we check their computers for up-to-date antivirus protection through a captive portal before allowing them to login. Antivirus protection is not checked on smartphones and similar devices, only full computers.

Software Used

We use a combination of Freeradius (to do radius authentication and accounting), and Samba (for active directory integration) on the backend. The wireless access points are configured using the Meraki cloud controller, although other wifi systems can also tap into the same radius configuration (we tested with Dlink access points as well). Whatever wifi system is selected must support 802.1x authentication through WPA2-Enterprise encryption, as well as radius accounting packets (to track simultaneous logins).

Simultaneous logins are tracked using a MySQL database, although it should also be possible to use Postgres as Freeradius supports it. The automated scripts we run would need to be modified slightly to support Postgres.

Bug-free support for the functions we're using arrived recently to Samba and Freeradius, and older versions definitely don't work properly. We are running Debian 6 with the stock versions of all packages. We were previously running Ubuntu 10.04 and the versions included there did not work. Freeradius 2.1.10 and Samba 3.5.6 are included with Debian 6, so those are the recommended versions.

Our VLANs are routed through a PFSense 2.0.0 virtual machine. This VM can apply firewalls to any combination of subnet connections, and has good performance for the throughput we need. Routing and firewalling can also be done through a dedicated router.

Hardware Used

Our radius server runs as a VMWare ESXi 5.0 virtual machine. It has a single virtual processor (2.53 GHz) with 256 MB of ram. This is more than sufficient to handle our incoming connections. Our MySQL database server, which handles many other databases as well, has 4 virtual processors and 3 GB of ram.

We use Meraki MR16 access points.

Clients we have successfully authenticated on this network include Windows XP, Vista, and 7; Mac OS X; Android, Blackberry, and iOS smartphones; etc. Through MAC address whitelisting, we have also put other devices onto the guest network.

Samba Configuration

Samba must be installed and the Linux machine must be joined to the active directory domain before radius can do NTLM authentication.

sudo apt-get install samba winbind smbclient krb5-user
sudo vi /etc/krb5.conf
  • Ensure default realm is set to your active directory DNS name (ours is SMUS.LOCAL)
  • Under realms, add a section:
        SMUS.LOCAL = {
                kdc = <domain controller DNS name>
                kdc = <backup domain controller DNS name>
                admin_server = <domain controller DNS name>
        }
  • Now test that your Kerberos settings are correct
sudo kinit Administrator
  • Enter your domain administrator password, and if it comes back without any errors you've set up Kerberos correctly
  • Now configure Samba
sudo vi /etc/samba/smb.conf
  • Change workgroup to your active directory short name (Ours is "SMUSLOCAL")
  • Uncomment "security = user" and change to "security = ads"
  • Immediately underneath, add the following lines:
password server = <domain controller DNS name>

Now join the Active Directory domain:

sudo net ads join -U Administrator

Then restart Samba and Winbind so that you can authenticate against the AD domain:

sudo /etc/init.d/smbd stop; sudo /etc/init.d/nmbd stop; sudo /etc/init.d/winbind stop
sudo /etc/init.d/smbd start; sudo /etc/init.d/nmbd start; sudo /etc/init.d/winbind start

Freeradius Configuration

Installation

sudo apt-get install freeradius freeradius-ldap freeradius-mysql
  • Allow the freerad user access to query winbind
sudo adduser freerad winbindd_priv

Clients

We must set up clients that are allowed to authenticate via radius. We have set up one client definition for senior/middle school Meraki access points, another definition for junior school Meraki access points, and a several definitions for the Meraki cloud controller, which does the guest authentication.

client <ip range>/24 {
        secret = <radius secret>
        shortname = meraki_ap
        nastype     = other
}

client <ip range>/24 {
        secret = <radius secret>
        shortname = meraki_jr_ap
        nastype     = other
}

client 64.156.192.220/32 {
        secret = <radius secret>
        shortname = meraki_cloud1
        nastype     = other
}

client 64.156.192.245/32 {
        secret = <radius secret>
        shortname = meraki_cloud2
        nastype     = other
}

client 74.50.51.16/32 {
        secret = <radius secret>
        shortname = meraki_cloud3
        nastype     = other
}

client 74.50.53.101/32 {
        secret = <radius secret>
        shortname = meraki_cloud4
        nastype     = other
}

Policies

Add each of these policies under /etc/freeradius/policy.conf

Require_staff Policy

This ensures that the Huntgroup-Name variable is set when we actually authenticate the user later.

        require_staff {
                update request {
                        Huntgroup-Name := "<staff group name>"
                }
        }

Require_student Policy

This ensures that the Huntgroup-Name variable is set when we actually authenticate the user later. We also update any replies to indicate that the student's session timeout is 5 minutes to force them to reauthenticate every 5 minutes.

        require_student {
                update request {
                        Huntgroup-Name := "<student group name>"
                }
                update reply {
                        Session-Timeout = 300
                }
        }

Require_guest Policy

Guests are authenticated differently because they are not doing 802.1x EAP/MSCHAP authentication. Instead, they use NTLM authentication (a username and hashed password). We will set up the auth module later.

        require_guest {
                update request {
                        Huntgroup-Name := "<guest group name>"
                }
                update control {
                        Auth-Type := "ntlm_auth_guest"
                }
        }

Single_login Policy

This just adds a check to ensure that the user only has one device on at a time. We haven't configured how to check that yet, but we must have a policy in place before the next steps.

       single_login {
               update control {
                       Simultaneous-Use := 1
               }
       }


Logintime Policy

In the require_student policy, we also place an LDAP group check to see what times the user should be allowed to log in. Here is the full require_student policy after adding the LDAP group checks. Note that the Huntgroup-Name group check is done through the active directory login, and so supports recursive group checking, but the LDAP group checks do not support recursive group checking.

        require_student {
                update request {
                        Huntgroup-Name := "<student group name>"
                }
                if (Ldap-Group == "Grade 12 Board") {
                        # special group, 6 AM - 12 AM Su - Th, 6 AM - 1 AM (next day) Fr - Sa
                        update control {
                                Login-Time := "Su0600-0100,Mo0600-0000,Tu0600-0000,We0600-0000,Th0600-0000,Fr0600-0000,Sa0600-0100"
                        }
                }
                elsif (Ldap-Group == "Grade 11 Board") {
                        # special group, 6 AM - 11 PM Su - Th, 6 AM - 12 AM Fr - Sa
                        update control {
                                Login-Time := "Su0600-2300,Mo0600-2300,Tu0600-2300,We0600-2300,Th0600-2300,Fr0600-0000,Sa0600-0000"
                        }
                }
                elsif (Ldap-Group == "Grade 10 Board") {
                        # special group, 6 AM - 10:30 PM Su - Th, 6 AM - 11:30 PM Fr - Sa
                        update control {
                                Login-Time := "Su0600-2230,Mo0600-2230,Tu0600-2230,We0600-2230,Th0600-2230,Fr0600-2330,Sa0600-2330"
                        }
                }
                else {
                        # default group, 6 AM - 10 PM daily
                        update control {
                                Login-Time := "All0600-2200"
                        }
                }
                update reply {
                        Session-Timeout = 300
                }
        }

Special_vlan Policy

When we authenticate computers, we also check to see if the machine should go onto a special VLAN. We check computer names for the computer lab VLANs, and we also check which access point they're binding to to check for Junior School machines, which are assigned different VLANs as they are a separate campus.

        check_special_vlan {
                if ("%{User-Name}" =~ /N7-MIDD-205-/i) {
                        update reply {
                                Tunnel-Private-Group-ID = 50
                                Tunnel-Type = "VLAN"
                                Tunnel-Medium-Type = "IEEE-802"
                        }
                }
                if ("%{User-Name}" =~ /N7-MIDD-CAR-/i) {
                        update reply {
                                Tunnel-Private-Group-ID = 50
                                Tunnel-Type = "VLAN"
                                Tunnel-Medium-Type = "IEEE-802"
                        }
                }
                if ("%{User-Name}" =~ /N7-SCHO-CAR-/i) {
                        update reply {
                                Tunnel-Private-Group-ID = 51
                                Tunnel-Type = "VLAN"
                                Tunnel-Medium-Type = "IEEE-802"
                        }
                }
                if ("%{User-Name}" =~ /N7-SCHO-LIB-/i) {
                        update reply {
                                Tunnel-Private-Group-ID = 51
                                Tunnel-Type = "VLAN"
                                Tunnel-Medium-Type = "IEEE-802"
                        }
                }
                if ("%{Client-Shortname}" == "meraki_jr_ap") {
                        # authenticating from JR school, change up the VLAN numbers
                        if ("%{Huntgroup-Name}" == "<student group name>") {
                                # was going to be on student VLAN, put on JR student VLAN
                                update reply {
                                        Tunnel-Private-Group-ID = 204
                                        Tunnel-Type = "VLAN"
                                        Tunnel-Medium-Type = "IEEE-802"
                                }
                        }
                        else {
                                # otherwise put on JR staff VLAN
                                update reply {
                                        Tunnel-Private-Group-ID = 200
                                        Tunnel-Type = "VLAN"
                                        Tunnel-Medium-Type = "IEEE-802"
                                }
                        }
                }
        }

Virtual Hosts

We use virtual hosts so that there are different radius servers available to authenticate staff, students, and guests.

sudo vi /etc/freeradius/radiusd.conf
  • Comment out all of the listen blocks, as we will be setting these up for each virtualhost
  • Now set up your virtualhosts
cd /etc/freeradius/sites-available
cp default staff
cp default student
cp default guest
cd /etc/freeradius/sites-enabled
rm default
ln -s ../sites-available/staff .
ln -s ../sites-available/student .
ln -s ../sites-available/guest .

Staff Virtual Host

sudo vi /etc/freeradius/sites-enabled/staff
  • Set the server alias to "server staff" rather than "server default"
  • Add a listen block immediately under "server staff {"
    • The port is set to 0 as that makes it the default (1812 for radius and 1813 for accounting)
    • Note that the staff server handles the radius accounting for all IP addresses, but you can configure them separately if desired
        listen {
                port = 0
                type = auth
                ipaddr = <ip address>
        }

        listen {
                port = 0
                type = acct
                ipaddr = *
        }
  • Update the authorize block so that it has the following items:
preprocess
require_staff
suffix
eap {
  ok = return
}
files
  • Update the authentication block so that it has the following items:
eap
  • Update the preacct block so that it has the following items:
preprocess
acct_unique
suffix
  • Update the accounting block so that it has the following items:
sql
attr_filter.accounting_response
  • Update the session block so that it has the following items:
sql
  • Update the post-auth block so that it has the following items:
check_special_vlan
exec
Post-Auth-Type REJECT {
  attr_filter.access_reject
}
  • Update the post-proxy block so that it has the following items:
eap

Student Virtual Host

sudo vi /etc/freeradius/sites-enabled/student
  • Set the server alias to "server student" rather than "server default"
  • Add a listen block immediately under "server student {"
    • The port is set to 0 as that makes it the default (1812 for radius and 1813 for accounting)
        listen {
                port = 0
                type = auth
                ipaddr = <ip address>
        }
  • Update the authorize block so that it has the following items:
preprocess
ldap
require_student
single_login
suffix
eap {
  ok = return
}
files
expiration
logintime
  • Update the authentication block so that it has the following items:
eap
  • Comment out the preacct and accounting blocks, as they are not used
  • Update the session block so that it has the following items:
sql
  • Update the post-auth block so that it has the following items:
check_special_vlan
exec
Post-Auth-Type REJECT {
  attr_filter.access_reject
}
  • Update the post-proxy block so that it has the following items:
eap

Guest Virtual Host

sudo vi /etc/freeradius/sites-enabled/guest
  • Set the server alias to "server guest" rather than "server default"
  • Add a listen block immediately under "server guest {"
    • The port is set to 0 as that makes it the default (1812 for radius and 1813 for accounting)
        listen {
                port = 0
                type = auth
                ipaddr = <ip address>
        }
  • Update the authorize block so that it has the following items:
preprocess
require_guest
suffix
eap {
  ok = return
}
files
  • Update the authentication block so that it has the following items:
ntlm_auth_guest
  • Comment out the preacct and accounting blocks as they are not used
  • Update the session block so that it has the following items:
sql
  • Update the post-auth block so that it has the following items:
check_special_vlan
exec
Post-Auth-Type REJECT {
  attr_filter.access_reject
}
  • Update the post-proxy block so that it has the following items:
eap

Inner-Tunnel Virtual Host

The inner-tunnel vhost should already be mostly set up, as it's generally included with Freeradius' default configuration. If it's not, symlink it from the sites-available:

sudo ln -s /etc/freeradius/sites-available/inner-tunnel /etc/freeradius/sites-enabled

Now start editing:

sudo vi /etc/freeradius/sites-enabled/inner-tunnel
  • Update the authorize block so that it has the following items:
chap
mschap
suffix
update control {
  Proxy-To-Realm := LOCAL
}
eap {
  ok = return
}
files
expiration
logintime
pap
  • Update the authentication block so that it has the following items. Note that if mschap authentication fails (this means that the user isn't a member of the huntgroup we set earlier), we also check whether mschap_computers authenticates the user correctly.
Auth-Type PAP {
  pap
}
Auth-Type CHAP {
  chap
}
Auth-Type MS-CHAP {
  mschap {
    reject = 2
  }
  if (reject) {
    mschap_computers
  }
}
unix
eap
  • Comment out the accounting, preacct, and session blocks, as they are not used
  • Update the post-auth block so that it has the following items:
Post-Auth-Type REJECT {
  attr_filter.access_reject
}
  • Update the post-proxy block so that it has the following items:
eap

Certificates

We need to generate a CSR and get a real certificate for this server. Here are the steps from memory, so they may not be 100% correct.

cd /etc/freeradius/certs
openssl req -out <server_name>.csr -new -newkey rsa:2048 -nodes -keyout <server_name>.key

Now send the CSR to a certificate authority and get it signed. If the CA uses a certificate chain, you need to put all of the certificates into one file, with the server's certificate at the top, then the CA that signed that cert, then the next CA up the chain, etc. Upload the resulting certificate file as <server_name>.crt .

EAP

Now configure EAP to use the certificate along with other parameters.

sudo vi /etc/freeradius/eap.conf

Inside the eap block, set the following:

default_eap_type = peap

Inside the eap/tls block, set the following:

private_key_password =
private_key_file = ${certdir}/<server_name>.key
certificate_file = ${certdir}/<server_name>.crt

Inside the eap/tls/cache block, set the following:

enable = yes
lifetime = 4 # hours
max_entries = 1000

Radius accounting/SQL Connection

cp /etc/freeradius/sql.conf /etc/freeradius/sql
sudo vi /etc/freeradius/modules/sql
  • Set the following values under the SQL block
server = "<database ip address>"
login = "<db user>"
password = "<db password>"
  • If you have a lot of radius clients, you may also need to boost the num_sql_socks to 10 or higher
  • Now edit the simultaneous count query
sudo vi /etc/freeradius/sql/mysql/dialup.conf
  • Uncomment the simul_count_query SQL statement
  • Change the statement to:
SELECT COUNT(*) \
   FROM ${acct_table1} \
   WHERE username LIKE '%{SQL-User-Name}' \
   AND callingstationid NOT LIKE '%{Calling-Station-Id}' \
   AND acctstoptime IS NULL

This ensures that users are allowed to reauthenticate if their MAC address hasn't changed. This allows roaming between access points without triggering the simultaneous use restriction.

LDAP Connection

sudo vi /etc/freeradius/modules/ldap

  • Configure TLS if desired under the ldap/tls block
  • Set the following values in the ldap block:
server = "<domain controller>:3268"
identity = "<domain query user, for example cn=ldap_user,cn=users,dc=...>"
password = "<domain query user AD password>"
basedn = "<AD base DN>"
filter = "(samAccountName=%{%{mschap:User-Name}:-%{User-Name}})"
groupname_attribute = cn
groupmembership_filter = "(&(objectClass=group)(member=%{control:Ldap-UserDn}))"
groupmembership_attribute = memberOf
chase_referrals = yes
rebind = yes

MSCHAP module

sudo vi /etc/freeradius/modules/mschap
  • Set the following values under the mschap block:
with_ntdomain_hack = yes
ntlm_auth = "/usr/bin/ntlm_auth --request-nt-key --challenge=%{mschap:Challenge:-00} --nt-response=%{mschap:NT-Response:-00} --domain=<Domain short name> --username=%{Stripped-User-Name:-%{mschap:User-Name}} --require-membership-of=<Domain short name>\\\\%{outer.request:Huntgroup-Name}"

MSCHAP_Computers module

sudo cp /etc/freeradius/modules/mschap /etc/freeradius/modules/mschap_computers
  • Change the block from "mschap {" to "mschap mschap_computers {"
  • Change the following values under the mschap mschap_computers block:
ntlm_auth = "/usr/bin/ntlm_auth --request-nt-key --challenge=%{mschap:Challenge:-00} --nt-response=%{mschap:NT-Response:-00} --domain=<Domain short name> --username=%{Stripped-User-Name:-%{mschap:User-Name}} --require-membership-of=<Domain short name>\\\\Domain\\ Computers"

Set up MySQL database

  • Download the freeradius distribution on your database server:
wget ftp://ftp.freeradius.org/pub/freeradius/freeradius-server-2.1.10.tar.bz2
tar -jxvf freeradius-server-2.1.10.tar.bz2
  • Set up a database for freeradius
mysql -u root -p
CREATE DATABASE radius;
GRANT ALL PRIVILEGES ON radius.* to '<radius_user>'@'<radius_ip>';
exit;
cd freeradius-server-2.1.10
mysql -u root -p radius < raddb/sql/mysql/schema.sql

Restart freeradius and pray

This starts Freeradius in debug mode. You should watch this fly by and verify that all of the configuration you have done above worked properly. If it didn't, Freeradius will complain and then exit. Then, try to get something to actually authenticate (follow the Meraki configuration in the next step to get it using your radius server).

sudo /etc/init.d/freeradius stop
sudo freeradius -X

To start radius for real, CTRL+C your debug session, then run:

sudo /etc/init.d/freeradius start

Accounting Expiration Script

We noticed that our Meraki APs were very good at sending radius authentication packets, but not so good at sending radius unauthentication packets when the clients left. So, we wrote a script that expires accounting records automatically if they have been active for > 7 minutes, and run the script every 2 minutes. This effectively means that if the Meraki AP doesn't send a logout accounting message, the student can still switch to a different device within 10 minutes.

This script also deletes all radius accounting information older than 14 days. Even still we have 430,000 records in our table at the moment.

vi /root/cleanup_radius.sh
#!/bin/bash
# trash auth records from > 14 days ago
mysql -u <radius_db_user> -p<radius_password> -h <db_ip> radius -e "DELETE FROM radacct WHERE acctstarttime < DATE_SUB(NOW(), INTERVAL 14 DAY)"
# expire student auth records from > 7 minutes ago (script is run every 2 minutes so it takes less than 10 minutes to expire old records)
mysql -u <radius_db_user> -p<radius_password> -h <db_ip> radius -e "UPDATE radacct SET acctstoptime = NOW() WHERE acctstarttime < DATE_SUB(NOW(), INTERVAL 7 MINUTE) AND acctstoptime IS NULL AND calledstationid LIKE '%AirSMUS-student'"

Now add this to the crontab:

crontab -e
*/2 * * * * /root/cleanup_radius.sh

Meraki configuration to use radius server

Additional Information

Packetshaper Integration