Dynamic VLAN assignment depending on LDAP user group and MAC address
Hi everyone. First of all, sorry if my email is very long, I am just trying not to leave any important details out. :) In my Company, I'd like to setup a freeradius based wifi authentication following the same principle: First check if a user is using the Company's laptop (or phone) by checking a list of MAC addresses. If the device is in the list, let the user authenticate through LDAP and get a VLAN depending on the user's group; if it's not present, authenticate the user against ldap, but assign the user to a "public" VLAN, which cannot reach our internal servers. This is basically to take care of users who connect to our network with their own devices, on which we don't have control and that could spread all sorts of malware in the internal network. So far, I managed to do the dynamic VLAN assignment, but cannot seem to get it to work together with the MAC checking. I can get an auth to be refused if the MAC is not listed in the authorized_macs file, but can't quite put the two things together. Perhaps I am a bit confused with regards to where to put the MAC check. For now, I just managed to get the check to work only on the authorization phase in sites-enabled/default, but then the VLAN assignment, which is done in the internal-tunnel, seems to overwrite my changes. So I tried to put the MAC check in the post-auth section in the default file, but the MAC check doesn't seem to ever work. Here are the relevant config files: Radius version: 2.1.10+dfsg-2+squeeze1 (running on Debian) --- policy.conf (...) rewrite_calling_station_id { if (Calling-Station-Id =~ /([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})/i){ update request { Calling-Station-Id := "%{1}-%{2}-%{3}-%{4}-%{5}-%{6}" } } else { noop } } } --- modules/files: (...) files authorized_macs { key = "%{tolower:%{Calling-Station-ID}}" usersfile = ${confdir}/authorized_macs compat = no } ---authorized_macs e8-99-c4-a2-39-36 Reply-Message = "Device with MAC Address %{Calling-Station-Id} authorized for network access" --- sites-available/default authorize { preprocess auth_log suffix eap { ok = return } expiration logintime pap } authenticate { Auth-Type PAP { pap } eap } preacct { preprocess acct_unique suffix } accounting { sql { fail = 1 } } session { radutmp sql { fail = 1 } } post-auth { rewrite_calling_station_id authorized_macs if (!ok) { update reply { Tunnel-Type = VLAN Tunnel-Medium-Type = IEEE-802 Tunnel-Private-Group-Id = 36 } } sql { fail = 1 } exec Post-Auth-Type REJECT { attr_filter.access_reject } } pre-proxy { } post-proxy { eap } --- sites-available/inner-tunnel authorize { preprocess auth_log suffix eap { ok = return } expiration logintime pap } authenticate { Auth-Type PAP { pap } eap } preacct { preprocess acct_unique suffix } accounting { sql { fail = 1 } } session { radutmp sql { fail = 1 } } post-auth { rewrite_calling_station_id authorized_macs if (!ok) { update reply { Tunnel-Type = VLAN Tunnel-Medium-Type = IEEE-802 Tunnel-Private-Group-Id = 36 } } sql { fail = 1 } exec Post-Auth-Type REJECT { attr_filter.access_reject } } pre-proxy { } post-proxy { eap } And here is an authentication example, with a device not listed in authorized_macs: (...) rad_recv: Access-Request packet from host 192.168.59.202 port 32769, id=129, length=345 User-Name = "fabrizio.vecchi" Calling-Station-Id = "60-fa-cd-47-1a-44" Called-Station-Id = "24-01-c7-28-aa-d0:MindCandyAuth" NAS-Port = 1 Cisco-AVPair = "audit-session-id=ca3ba8c0000000dede1c5852" NAS-IP-Address = 192.168.59.202 NAS-Identifier = "Cisco_6e:1f:4f" Airespace-Wlan-Id = 5 Service-Type = Framed-User Framed-MTU = 1300 NAS-Port-Type = Wireless-802.11 Tunnel-Type:0 = VLAN Tunnel-Medium-Type:0 = IEEE-802 Tunnel-Private-Group-Id:0 = "36" EAP-Message = 0x0206005f15800000005517030100506509e5008fb8b33c992bdddc007472c4f5d210aa8d535f74724cccc1bc99c4cb8785066c7ef4f262c470986626e1d31efc71f0d3b42b80663afc9fdc68715d1ee49c02af509c6b12de0bca5bf5501cba State = 0xf1f3e6cbf5f5f3adc22ef694ca5dfcba Message-Authenticator = 0xeff670953d883040f13b8dfc42d39849 # Executing section authorize from file /etc/freeradius/sites-enabled/default +- entering group authorize {...} ++[preprocess] returns ok [auth_log] expand: /var/log/freeradius/radacct/%{Client-IP-Address}/auth-detail-%Y%m%d -> /var/log/freeradius/radacct/192.168.59.202/auth-detail-20131011 [auth_log] /var/log/freeradius/radacct/%{Client-IP-Address}/auth-detail-%Y%m%d expands to /var/log/freeradius/radacct/192.168.59.202/auth-detail-20131011 [auth_log] expand: %t -> Fri Oct 11 17:12:54 2013 ++[auth_log] returns ok [suffix] No '@' in User-Name = "fabrizio.vecchi", looking up realm NULL [suffix] No such realm "NULL" ++[suffix] returns noop [eap] EAP packet type response id 6 length 95 [eap] Continuing tunnel setup. ++[eap] returns ok Found Auth-Type = EAP # Executing group from file /etc/freeradius/sites-enabled/default +- entering group authenticate {...} [eap] Request found, released from the list [eap] EAP/ttls [eap] processing type ttls [ttls] Authenticate [ttls] processing EAP-TLS TLS Length 85 [ttls] Length Included [ttls] eaptls_verify returned 11 [ttls] eaptls_process returned 7 [ttls] Session established. Proceeding to decode tunneled attributes. [ttls] Got tunneled request User-Name = "fabrizio.vecchi" User-Password = <password> FreeRADIUS-Proxied-To = 127.0.0.1 [ttls] Sending tunneled request User-Name = "fabrizio.vecchi" User-Password = <password> FreeRADIUS-Proxied-To = 127.0.0.1 Calling-Station-Id = "60-fa-cd-47-1a-44" Called-Station-Id = "24-01-c7-28-aa-d0:MindCandyAuth" NAS-Port = 1 Cisco-AVPair = "audit-session-id=ca3ba8c0000000dede1c5852" NAS-IP-Address = 192.168.59.202 NAS-Identifier = "Cisco_6e:1f:4f" Airespace-Wlan-Id = 5 Service-Type = Framed-User Framed-MTU = 1300 NAS-Port-Type = Wireless-802.11 Tunnel-Type:0 = VLAN Tunnel-Medium-Type:0 = IEEE-802 Tunnel-Private-Group-Id:0 = "36" server inner-tunnel { # Executing section authorize from file /etc/freeradius/sites-enabled/inner-tunnel +- entering group authorize {...} ++[control] returns notfound [eap] No EAP-Message, not doing EAP ++[eap] returns noop ++[files] returns noop [ldap] performing user authorization for fabrizio.vecchi [ldap] expand: %{Stripped-User-Name} -> [ldap] ... expanding second conditional [ldap] expand: %{User-Name} -> fabrizio.vecchi [ldap] expand: (uid=%{%{Stripped-User-Name}:-%{User-Name}}) -> (uid=fabrizio.vecchi) [ldap] expand: c=gb,dc=mindcandy,dc=com -> c=gb,dc=mindcandy,dc=com [ldap] ldap_get_conn: Checking Id: 0 [ldap] ldap_get_conn: Got Id: 0 [ldap] attempting LDAP reconnection [ldap] (re)connect to 192.168.50.41:389, authentication 0 [ldap] bind as cn=admin,dc=mindcandy,dc=com/4kaZi638uSFurX to 192.168.50.41:389 [ldap] waiting for bind result ... [ldap] Bind was successful [ldap] performing search in c=gb,dc=mindcandy,dc=com, with filter (uid=fabrizio.vecchi) [ldap] Added User-Password = {SSHA}mhuhx35skdNyJ7BrJuviLnMt2iDI3lFs in check items [ldap] No default NMAS login sequence [ldap] looking for check items in directory... [ldap] userPassword -> Password-With-Header == "{SSHA}mhuhx35skdNyJ7BrJuviLnMt2iDI3lFs" [ldap] sambaNtPassword -> NT-Password == 0x3730424545463943433843443839414435374133463731413541354446333742 [ldap] looking for reply items in directory... [ldap] user fabrizio.vecchi authorized to use remote access [ldap] ldap_release_conn: Release Id: 0 ++[ldap] returns ok [pap] Normalizing NT-Password from hex encoding [pap] Normalizing SSHA1-Password from base64 encoding [pap] Normalizing SSHA1-Password from base64 encoding ++[pap] returns updated Found Auth-Type = PAP # Executing group from file /etc/freeradius/sites-enabled/inner-tunnel +- entering group PAP {...} [pap] login attempt with password <password> [pap] Using NT encryption. [pap] expand: %{User-Password} -> <password> [pap] NT-Hash of <password> = 70beef9cc8cd89ad57a3f71a5a5df37b [pap] expand: %{mschap:NT-Hash %{User-Password}} -> 70beef9cc8cd89ad57a3f71a5a5df37b [pap] User authenticated successfully ++[pap] returns ok # Executing section post-auth from file /etc/freeradius/sites-enabled/inner-tunnel +- entering group post-auth {...} [sql] expand: %{User-Name} -> fabrizio.vecchi [sql] sql_set_user escaped user --> 'fabrizio.vecchi' [sql] expand: %{User-Password} -> <password> [sql] expand: INSERT INTO radpostauth (username, pass, reply, authdate) VALUES ( '%{User-Name}', '%{%{User-Password}:-%{Chap-Password}}', '%{reply:Packet-Type}', '%S') -> INSERT INTO radpostauth (username, pass, reply, authdate) VALUES ( 'fabrizio.vecchi', '<password>', 'Access-Accept', '2013-10-11 17:12:54') rlm_sql (sql) in sql_postauth: query is INSERT INTO radpostauth (username, pass, reply, authdate) VALUES ( 'fabrizio.vecchi', '<password>', 'Access-Accept', '2013-10-11 17:12:54') rlm_sql (sql): Reserving sql socket id: 3 rlm_sql (sql): Released sql socket id: 3 ++[sql] returns ok ++[ldap] returns noop ++? if (LDAP-Group == "cn=dept_tech_corporate_it,ou=Groups,c=gb,dc=mindcandy,dc=com") [ldap] Entering ldap_groupcmp() expand: c=gb,dc=mindcandy,dc=com -> c=gb,dc=mindcandy,dc=com expand: (|(&(objectClass=GroupOfNames)(member=%{control:Ldap-UserDn}))(&(objectClass=GroupOfUniqueNames)(uniquemember=%{control:Ldap-UserDn}))) -> (|(&(objectClass=GroupOfNames)(member=uid\3dfabrizio.vecchi\2cou\3dPeople\2cc\3dgb\2cdc\3dmindcandy\2cdc\3dcom))(&(objectClass=GroupOfUniqueNames)(uniquemember=uid\3dfabrizio.vecchi\2cou\3dPeople\2cc\3dgb\2cdc\3dmindcandy\2cdc\3dcom))) [ldap] ldap_get_conn: Checking Id: 0 [ldap] ldap_get_conn: Got Id: 0 [ldap] performing search in cn=dept_tech_corporate_it,ou=Groups,c=gb,dc=mindcandy,dc=com, with filter (|(&(objectClass=GroupOfNames)(member=uid\3dfabrizio.vecchi\2cou\3dPeople\2cc\3dgb\2cdc\3dmindcandy\2cdc\3dcom))(&(objectClass=GroupOfUniqueNames)(uniquemember=uid\3dfabrizio.vecchi\2cou\3dPeople\2cc\3dgb\2cdc\3dmindcandy\2cdc\3dcom))) rlm_ldap::ldap_groupcmp: User found in group cn=dept_tech_corporate_it,ou=Groups,c=gb,dc=mindcandy,dc=com [ldap] ldap_release_conn: Release Id: 0 ? Evaluating (LDAP-Group == "cn=dept_tech_corporate_it,ou=Groups,c=gb,dc=mindcandy,dc=com") -> TRUE ++? if (LDAP-Group == "cn=dept_tech_corporate_it,ou=Groups,c=gb,dc=mindcandy,dc=com") -> TRUE ++- entering if (LDAP-Group == "cn=dept_tech_corporate_it,ou=Groups,c=gb,dc=mindcandy,dc=com") {...} +++[reply] returns noop ++- if (LDAP-Group == "cn=dept_tech_corporate_it,ou=Groups,c=gb,dc=mindcandy,dc=com") returns noop ++ ... skipping elsif for request 5: Preceding "if" was taken ++ ... skipping elsif for request 5: Preceding "if" was taken ++ ... skipping elsif for request 5: Preceding "if" was taken ++ ... skipping elsif for request 5: Preceding "if" was taken ++ ... skipping else for request 5: Preceding "if" was taken } # server inner-tunnel [ttls] Got tunneled reply code 2 Tunnel-Type:0 = VLAN Tunnel-Medium-Type:0 = IEEE-802 Tunnel-Private-Group-Id:0 = "40" [ttls] Got tunneled Access-Accept [eap] Freeing handler ++[eap] returns ok # Executing section post-auth from file /etc/freeradius/sites-enabled/default +- entering group post-auth {...} ++- entering policy rewrite_calling_station_id {...} +++? if (Calling-Station-Id =~ /([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})/i) ? Evaluating (Calling-Station-Id =~ /([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})/i) -> TRUE +++? if (Calling-Station-Id =~ /([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})/i) -> TRUE +++- entering if (Calling-Station-Id =~ /([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})/i) {...} expand: %{1}-%{2}-%{3}-%{4}-%{5}-%{6} -> 60-fa-cd-47-1a-44 ++++[request] returns noop +++- if (Calling-Station-Id =~ /([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})[-:]?([0-9a-f]{2})/i) returns noop +++ ... skipping else for request 5: Preceding "if" was taken ++- policy rewrite_calling_station_id returns noop [authorized_macs] expand: %{Calling-Station-ID} -> 60-fa-cd-47-1a-44 [authorized_macs] expand: %{tolower:%{Calling-Station-ID}} -> 60-fa-cd-47-1a-44 ++[authorized_macs] returns noop ++? if (!ok) ? Evaluating !(ok) -> TRUE ++? if (!ok) -> TRUE ++- entering if (!ok) {...} +++[reply] returns noop ++- if (!ok) returns noop [sql] expand: %{User-Name} -> fabrizio.vecchi [sql] sql_set_user escaped user --> 'fabrizio.vecchi' [sql] expand: %{User-Password} -> [sql] ... expanding second conditional [sql] expand: %{Chap-Password} -> [sql] expand: INSERT INTO radpostauth (username, pass, reply, authdate) VALUES ( '%{User-Name}', '%{%{User-Password}:-%{Chap-Password}}', '%{reply:Packet-Type}', '%S') -> INSERT INTO radpostauth (username, pass, reply, authdate) VALUES ( 'fabrizio.vecchi', '', 'Access-Accept', '2013-10-11 17:12:54') rlm_sql (sql) in sql_postauth: query is INSERT INTO radpostauth (username, pass, reply, authdate) VALUES ( 'fabrizio.vecchi', '', 'Access-Accept', '2013-10-11 17:12:54') rlm_sql (sql): Reserving sql socket id: 2 rlm_sql (sql): Released sql socket id: 2 ++[sql] returns ok ++[exec] returns noop Sending Access-Accept of id 129 to 192.168.59.202 port 32769 Tunnel-Type:0 = VLAN Tunnel-Medium-Type:0 = IEEE-802 Tunnel-Private-Group-Id:0 = "40" MS-MPPE-Recv-Key = 0x0b1d759946c02f9063b69141cdebfd8a229d7d996fdea460aa1fb4e11182270a MS-MPPE-Send-Key = 0x62814f3d40bc9b08d2661cbc70a93c836f1e8f453e22ee77d3ad3f78dd2432f0 EAP-Message = 0x03060004 Message-Authenticator = 0x00000000000000000000000000000000 User-Name = "fabrizio.vecchi" Finished request 5. Going to the next request Waking up in 4.8 seconds. As you can see, the device wasn't listed in the file, the authentication went fine, saying that the tunnel that I should get has ID 40, but that wasn't overwritten by the authorized_macs check... Any pointers would be greatly appreciated. Thank you so much for your help. Fabrizio
Fabrizio Vecchi wrote:
First of all, sorry if my email is very long, I am just trying not to leave any important details out. :)
That's good.
So far, I managed to do the dynamic VLAN assignment, but cannot seem to get it to work together with the MAC checking.
They key thing to remember is that they are two independent pieces. Get them working independently. Then, put the pieces together.
I can get an auth to be refused if the MAC is not listed in the authorized_macs file, but can't quite put the two things together. Perhaps I am a bit confused with regards to where to put the MAC check.
Put it into "authorize".
For now, I just managed to get the check to work only on the authorization phase in sites-enabled/default, but then the VLAN assignment, which is done in the internal-tunnel, seems to overwrite my changes.
That can be fixed.
So I tried to put the MAC check in the post-auth section in the default file, but the MAC check doesn't seem to ever work.
Because the "users" file works differently there.
Here are the relevant config files:
Which seem to use a reasonable approach.
As you can see, the device wasn't listed in the file, the authentication went fine, saying that the tunnel that I should get has ID 40, but that wasn't overwritten by the authorized_macs check...
I think part of the problem is you're juggling a lot. You also mentioned MACs and VLANs... and then halfway through the message "Oh, there's an inner-tunnel, too". Stop with all of your solutions. Instead, write down exactly what you have. Write down what you want to happen in plain English. Write down what should happen, and when. Then, convert it to the configuration. Your system is using TTLS. OK... I'll ignore the question of *why* you're authenticating unknown MACs. That seems weird. The debug log shows this: [ttls] Got tunneled reply code 2 Tunnel-Type:0 = VLAN Tunnel-Medium-Type:0 = IEEE-802 Tunnel-Private-Group-Id:0 = "40" Where did that VLAN come from? Why is it there? If you don't know, that's a huge problem. Generally, the policies should be arranged like this: authorize: allow only known kinds of authentication decide which authentication method to use grab "known good" passwords authenticate: run authentication methods post-auth: return attributes for a successfully authenticated user In your case, I'd say return to a default configuration. Then, get the MAC address filtering working in post-auth. Once that's working, add VLAN assignment. Alan DeKok.
Hi Alan and thanks for the reply. On 12 October 2013 13:42, Alan DeKok <aland@deployingradius.com> wrote:
So far, I managed to do the dynamic VLAN assignment, but cannot seem to get it to work together with the MAC checking.
Get them working independently. Then, put the pieces together. I managed to get them to work independently, it's the putting together phase that I can't quite crack... :)
I can get an auth to be refused if the MAC is not listed in the authorized_macs file, but can't quite put the two things together. Perhaps I am a bit confused with regards to where to put the MAC check.
Put it into "authorize".
If I put the MAC check in the authorize section, then I can allow users to use only the devices in the authorized_macs file (unless I am missing something). I would like the users to use any device, but just have a limited access to the network if the device isn't listed in the authorized_macs file...
For now, I just managed to get the check to work only on the authorization phase in sites-enabled/default, but then the VLAN assignment, which is done in the internal-tunnel, seems to overwrite my changes.
That can be fixed.
So I tried to put the MAC check in the post-auth section in the default file, but the MAC check doesn't seem to ever work.
Because the "users" file works differently there.
Can you point me to some part of the docs that explain what is different, please?
Here are the relevant config files:
Which seem to use a reasonable approach.
As you can see, the device wasn't listed in the file, the authentication went fine, saying that the tunnel that I should get has ID 40, but that wasn't overwritten by the authorized_macs check...
I think part of the problem is you're juggling a lot. You also mentioned MACs and VLANs... and then halfway through the message "Oh, there's an inner-tunnel, too".
Stop with all of your solutions. Instead, write down exactly what you have. Write down what you want to happen in plain English. Write down what should happen, and when. Then, convert it to the configuration.
Your system is using TTLS. OK... I'll ignore the question of *why* you're authenticating unknown MACs. That seems weird.
What I am trying to achieve is the following: 1. Authenticate the users through LDAP 2. IF the user is using a device listed in the authorized_macs file, then assign a VLAN which depends on the user's LDAP group. 3. IF the user is using another (their own), allow them to access an "isolated" VLAN, which doesn't allow the device to talk to our servers. This will allow my colleagues to connect to the Internet with their devices, but not to mess around with our servers in case there is malware installed on them. Does this make sense?
The debug log shows this:
[ttls] Got tunneled reply code 2 Tunnel-Type:0 = VLAN Tunnel-Medium-Type:0 = IEEE-802 Tunnel-Private-Group-Id:0 = "40"
Where did that VLAN come from? Why is it there? If you don't know, that's a huge problem.
SO SORRY! I pasted twice the sites-available/default file! It comes from the post-auth section in the inner-tunnel file. In inner-tunnel, I check if the user belongs to the dept_tech_corporate_it group, and if so, I update the outher request by assigning a VLAN with ID 40. This part of the config works properly, as showed by the following few lines of debug output: rlm_ldap::ldap_groupcmp: User found in group cn=dept_tech_corporate_it,ou= Groups,c=gb,dc=mindcandy,dc=com [ldap] ldap_release_conn: Release Id: 0 ? Evaluating (LDAP-Group == "cn=dept_tech_corporate_it,ou=Groups,c=gb,dc=mindcandy,dc=com") -> TRUE
Generally, the policies should be arranged like this:
authorize: allow only known kinds of authentication decide which authentication method to use grab "known good" passwords
authenticate: run authentication methods
post-auth: return attributes for a successfully authenticated user
In your case, I'd say return to a default configuration. Then, get the MAC address filtering working in post-auth. Once that's working, add VLAN assignment.
That seems to be an approach similar to the one I am trying to use. I would like to do the check of the MAC address for last, so that I can associate the right VLAN to the user. But for some reason this check doesn't seem to work. I guess at the end of the day my question boils down to the following: where should I put the MAC check, so that the user gets assigned to the right VLAN? If I put it in the authorize part of sites-enabled/default, the VLAN update request will get overwritten by the post-auth part of sites-enabled/inner-tunnel; and if I put it in the post-auth of the file sites-enabled/default file (which gets executed after inner-tunnel), the authorized_macs function always returns noop. If I could get any pointers towards a working solution, I'd be really grateful. Thanks, Fabrizio
Fabrizio Vecchi wrote:
I guess at the end of the day my question boils down to the following: where should I put the MAC check, so that the user gets assigned to the right VLAN?
In post-auth.
If I put it in the authorize part of sites-enabled/default, the VLAN update request will get overwritten by the post-auth part of sites-enabled/inner-tunnel;
The default configuration for the inner-tunnel does *not* set a VLAN in post-auth. So one configuration you added prevents you from using another configuration you added.
and if I put it in the post-auth of the file sites-enabled/default file (which gets executed after inner-tunnel), the authorized_macs function always returns noop.
Delete the "set VLAN" stuff from the post-auth of the inner tunnel. As you've seen, it breaks the other configuration you're trying to use. When you put "authorized_macs" into the "post-auth", it runs the "post-auth" processing. Which doesn't read the "users" file... as the "users" file is done only in the "authorize" section. You should be able to put "authorized_macs.authorize" in the post-auth section. That will make it process the "users" file, and do what you want. Alan DeKok.
participants (2)
-
Alan DeKok -
Fabrizio Vecchi