Single SSID Multiple Networks Via 802.1x

Hi, Been awhile since I have posted, but I guess that’s a good thing it means I am busy! Anyways, had a two weekend project I wanted to share about using 802.1x FreeRadius Active Directory and an Aruba access point. This was a learning experience….

Let’s get right into it.

First I had a fresh copy of Gentoo Installed, I won’t get into the specifics of that, but I had to emerge Samba (with winbind and ads) and free radius, I was also located on the same LAN as my domain controller.

The samba config is very simple, The domains have been changed to protect the innocent.
/etc/samba/smb.conf

[global]
workgroup = EXAMPLE
server string = Radius
server role = member server
 
local master = no
preferred master = no
 
hosts allow = 127. 10.200.
log file = /var/log/samba/log.%m
max log size = 50
realm = EXAMPLE.EXAMPLE.com
security = ads
password server = dc1.example.example.com
kerberos method = secrets only
 
winbind separator = +
winbind use default domain = true
winbind offline logon = false
;winbind separator = +  
#winbind nss info = rfc2307
winbind trusted domains only = no
;winbind use default domain = yes
winbind enum users  = yes
winbind enum groups = yes
winbind refresh tickets = Yes
 
#idmap config *:backend = tdb
#idmap config *:backend = ad
#idmap config *:range = 70001-80000
 
idmap backend = tdb
idmap uid = 50000-99999
idmap gid = 50000-99999
 
#idmap config EXAMPLE:backend = ad
#idmap config EXAMPLE:schema_mode = rfc2307
#idmap config EXAMPLE:range = 500-40000
 
idmap cache time = 1
idmap negative cache time = 1
winbind cache time = 1
 
#============================ Share Definitions ==============================
[homes]
   comment = Home Directories
   browseable = no
   writable = yes

Next let’s setup kerberos (I emerged mit-krb5).
cat /etc/krb5.conf

[logging]
 default = FILE:/var/log/krb5libs.log
 kdc = FILE:/var/log/krb5kdc.log
 admin_server = FILE:/var/log/kadmind.log
 
[libdefaults]
 default_realm = EXAMPLE.EXAMPLE.COM
 dns_lookup_realm = false
 dns_lookup_kdc = false
 
[realms]
 EXAMPLE.EXAMPLE.com = {
  kdc = dc1.example.example.com:88
  admin_server = dc1.example.example.com:749
  default_domain = example.example.com
 }
 
[domain_realm]
 .example.example.com = EXAMPLE.EXAMPLE.COM
 example.example.com = EXAMPLE.EXAMPLE.COM
 
[kdc]
  profile = /var/kerberos/krb5kdc/kdc.conf
 
[appdefaults]
 pam = {
  debug = false
  ticket_lifetime = 36000
  renew_lifetime = 36000
  forwardable = true
  krb4_convert = false
 }

I do have a desire to fix the tdb backend in samba, because it caches for a LOOOOONNNNNGGGGG time. To fix that I made a script in cron, I sort of think that if you use centos or Debian you won’t have to do this:

/root/fixwinbind.sh

#!/bin/bash
rm /var/cache/samba/netsamlogon_cache.tdb
/etc/init.d/samba restart

edit your crontab

*/5 * * * * /root/fixwindbind.sh

I really encourage you to see if winbindd does better updates on your system before you do this hack.

Make sure everything starts (smbd nmbd winbindd)

Next we have to join the domain

net join –U Administrator

It will prompt for your domain admin password, type it there.

Boom not you are on the domain you can test it by doing a wbinfo -a

wbinfo -a example%examplepass

Let’s fix our nssswitch.conf file we need to do this because we want winbindd to look at groups and users as local to the system.

/etc/nsswitch.conf

passwd:      compat winbind
shadow:      compat winbind
group:       compat winbind
 
# passwd:    db files nis
# shadow:    db files nis
# group:     db files nis
 
hosts:       files dns
networks:    files dns
 
services:    db files winbind
protocols:   db files winbind
rpc:         db files
ethers:      db files
netmasks:    files
netgroup:    files winbind
bootparams:  files
 
automount:   files winbind
aliases:     files

Now you should be able to do a:

id <yourwindowsusername>

it should return something like this:

radius1 raddb # id user
uid=70001(user) gid=70001(domain users) groups=70001(domain users),70002(office-wifi),70006(network-systems),70050(BUILTIN+users)

You can see that now all your GID’s are available on your Gentoo/Linux install

Let’s take a look at the access point, I am using An Aruba 325, CAUTION: THIS AP IS EXPENSIVE, but I think it’s worth it. I am not going to go though exactly how to setup the Aruba, Like doing a factory reset and such, but this is a high level config.

Login to the Aruba in the Web browser:

I set mine up as a single AP, since I only had one with no controller. Click on the AP SSID and push edit:

I set mine up as an employee network:

This next screen is important:

Ok so this defines out tagged VLANS to the Aruba, so you can see I have vlan 1 untagged, vlan33 tagged, and vlan55 tagged to the Aruba I am also using Tunnel-Private-Group-Id, Aruba does have some other variables that are more Aruba centric, but I thought I would use a more widely accepted one.

Since I already had my radius configured you need to suffer through this screen:

My radius config looks like this:

Make sure you remember what password you put in, also the IP address is the IP address of your radius server, You also MUST know the IP address of your ap herby now called

Once they have authenticated they have no restrictions:

Now lets get free radius done. in Gentoo I did the

USE="ldap mysql odbc pam python readline ssl" emerge freeradius

I have a desire to switch to LDAP at some point but not right now.

after free radius is configured we first should edit the clients.conf file

/etc/raddb/clients.conf

client <apip> {
        secret          = YourTotallyAwesomeKeyYouPutInYourAPForRadius
        shortname	= <apip>
}

now lets edit our inner-tunnel and default files, you have a VERY specific requirement in our setup to only auth via usernames and passwords so first things first find the authorize { section and add this line:
/etc/raddb/sites-available/inner-tunnel

if (User-Name =~ /host\//) {
    reject
}

right below ntdomain I added:

perl

Right below update control I added:

if(!control:Auth-Type) {
    update control {
        Auth-Type = "ntlm_auth"
    }
}

Now find the authenticate { section
right below:

Auth-Type MS-CHAP {
    mschap
}

I added

ntlm_auth

Follows the same steps for /etc/raddb/sites-available/default, The changes are e identical

Now we need to adjust our eap, I would HIGLY recommend getting a signed cert from comodo or something, doing that is out of the scope of this tutorial but I did this for mine, and it prevented non trusted alerts on the client side.

ok anyways edit: /etc/raddb/mods-enabled/eap
You will want to find these lines:

default_eap_type =

Change it to

default_eap_type = peap

again, I am going to skip the tis-config tis-common section, but you MUST set this up to make it work…

scoot on down to peap {

we want to make that use mschapv2 (it might already be setup like this)

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

That’s in in this file, now let’s fix up mschap
/etc/raddb/mods-enabled/mschap
in section mschap {

with_ntdomain_hack=yes
use_mppe = yes
require_encryption = yes
require_strong = yes
ntlm_auth = "/usr/bin/ntlm_auth --request-nt-key --username=%{mschap:User-Name:-None} --challenge=%{%{mschap:Challenge}:-00} --nt-response=%{%{mschap:NT-Response}:-00}"

That’s it in mschap

now lets mixup ntlm_auth
/etc/raddb/mods-enabled/ntlm_auth
change the program = line to this:

program = "/usr/bin/ntlm_auth --request-nt-key --domain=EXAMPLE --username=%{mschap:User-Name} --password=%{User-Password}"

Are you still with me???

Now we have the requirement of dropping the user group office-wifi into vlan 33 and office-wifi2 into vlan55

So there is a couple of ways to solve this I picked a perl script:

/etc/raddb/mods-config/perl/auth.pl

use Data::Dumper;
our (%RAD_REQUEST, %RAD_REPLY, %RAD_CHECK, %RAD_STATE);
use constant {
	RLM_MODULE_REJECT   => 0, # immediately reject the request
	RLM_MODULE_OK       => 2, # the module is OK, continue
	RLM_MODULE_HANDLED  => 3, # the module handled the request, so stop
	RLM_MODULE_INVALID  => 4, # the module considers the request invalid
	RLM_MODULE_USERLOCK => 5, # reject the request (user is locked out)
	RLM_MODULE_NOTFOUND => 6, # user not found
	RLM_MODULE_NOOP     => 7, # module succeeded without doing anything
	RLM_MODULE_UPDATED  => 8, # OK (pairs modified)
	RLM_MODULE_NUMCODES => 9  # How many return codes there are
};
use constant {
	L_AUTH         => 2,  # Authentication message
	L_INFO         => 3,  # Informational message
	L_ERR          => 4,  # Error message
	L_WARN         => 5,  # Warning
	L_PROXY        => 6,  # Proxy messages
	L_ACCT         => 7,  # Accounting messages
	L_DBG          => 16, # Only displayed when debugging is enabled
	L_DBG_WARN     => 17, # Warning only displayed when debugging is enabled
	L_DBG_ERR      => 18, # Error only displayed when debugging is enabled
	L_DBG_WARN_REQ => 19, # Less severe warning only displayed when debugging is enabled
	L_DBG_ERR_REQ  => 20, # Less severe error only displayed when debugging is enabled
};
 
sub authorize {
	if ($RAD_REQUEST{'FreeRADIUS-Proxied-To'} == "127.0.0.1") {
		return RLM_MODULE_OK;		
	}
	$username = "";
	if ($RAD_REQUEST{'User-Name'} =~ /\\/) {
		#strip the backslash
		@u = split(/\\/,$RAD_REQUEST{'User-Name'});
		$username = $u[1];
	} else {
		$username = $RAD_REQUEST{'User-Name'};
	}
	#my $output = `id $RAD_REQUEST{'User-Name'}`;
	&radiusd::radlog(L_DBG, "**********Username: $username");
	my $output = `id $username`;
	@var = split(/groups=/,$output);
	@groups = split(/,/,$var[1]);
	foreach(@groups) {
        	my @var = split(/\(/,$_);
        	$var[1] =~ s/\)//g;
		if ( $RAD_REQUEST{'NAS-Port-Type'} eq "Wireless-802.11") {
        		if ($var[1] eq "office-wifi") {
				$RAD_REPLY{'Tunnel-Type'} = "VLAN";
				$RAD_REPLY{'Tunnel-Medium-Type'} = "IEEE-802";
				$RAD_REPLY{'Tunnel-Private-Group-ID'} = "33";
				return RLM_MODULE_OK;
        		} elsif ($var[1] eq "office-wifi2") {
				$RAD_REPLY{'Tunnel-Type'} = "VLAN";
				$RAD_REPLY{'Tunnel-Medium-Type'} = "IEEE-802";
				$RAD_REPLY{'Tunnel-Private-Group-ID'} = "55";
				return RLM_MODULE_OK;
        		} else {
                                return RLM_MODULE_REJECT;
                        }
		}
		if ($RAD_REQUEST{'MS-RAS-Vendor'} eq "11") {
	        	if ($var[1] eq "network-systems") {
				$RAD_REPLY{'Service-Type'} = "6";
   				$RAD_REPLY{'HP-Command-String'} =".*";
				return RLM_MODULE_OK;
			}
		}
	}
	return RLM_MODULE_REJECT;
}
 
# Function to handle authenticate
sub authenticate {
	return RLM_MODULE_OK;
}
 
# Function to handle preacct
sub preacct {
	return RLM_MODULE_OK;
}
 
# Function to handle accounting
sub accounting {
	return RLM_MODULE_OK;
}
 
sub checksimul {
	return RLM_MODULE_OK;
}
sub pre_proxy {
	return RLM_MODULE_OK;
}
 
# Function to handle post_proxy
sub post_proxy {
	return RLM_MODULE_OK;
}
 
# Function to handle post_auth
sub post_auth {
	return RLM_MODULE_OK;
}
 
sub xlat {
}
 
sub detach {
}
 
sub log_request_attributes {
	# This shouldn't be done in production environments!
	# This is only meant for debugging!
	for (keys %RAD_REQUEST) {
		&radiusd::radlog(L_DBG, "RAD_REQUEST: $_ = $RAD_REQUEST{$_}");
	}
}

Feel free to edit the file above at will if the user is not in either group they will not be able to access the wpa2 enterprise network.

That’s basically it. Not much else to do, you can run

radiusd -X

to run radius in the foreground or

/etc/init.d/radiusd start

to run it in the background.

On your windows AD server anyone in the office-wifi group will be placed on VLAN 33 anyone on office-wifi2 will be on VLAN 55

Here is the video of it working:

VLAN ON!

–John