Active Directory group check via winbind + rlm_unix, not LDAP

Eloy Paris peloy at chapus.net
Sun Aug 31 17:53:26 CEST 2014


Hello,

I have a pretty common requirement: authenticate wireless users against
Active Directory and prevent SSID cross-connections, i.e. users in group
A can only connect to SSID A and users in group B can only connect to
SSID B.

I have seen plenty of messages in the archives about how to accomplish
this. The authentication part is easy and has excellent documentation,
e.g.

http://deployingradius.com/documents/configuration/active_directory.html

The group checking part is also well understood and documented
(rlm_ldap), and whoever asks apparently gets told to use LDAP. At least
I could not find anything on the alternate approach described here
(apologies if this is old news).

I decided to look into a different approach, which does not involve
LDAP -- since the machine already has winbind running (for ntlm_auth),
why not to use the Name Service Switch and rlm_unix to check for group
membership and avoid usind LDAP altogether? Group membership information
is already available if one has added "winbind" to "passwd" and "group"
in /etc/nsswitch.conf.

I apparently got this to work and wanted to share the solution in the
hope that it will be helpful to someone, but also to ask if anyone sees
any issues with the approach:

First, I configured winbind for ntlm_auth use by FreeRADIUS, as
explained elsewhere. Then, I verified that the system can see Active
Directory users and groups as if they were Unix users and groups:

shell$ id DOMAIN\\username
uid=10017(DOMAIN\username) gid=10002(DOMAIN\domain users) groups=10002(DOMAIN\domain users),10024(DOMAIN\computer-lab-monitoring),10008(DOMAIN\domain admins),10034(DOMAIN\vdi teachers),10010(DOMAIN\teachers),10026(DOMAIN\vdi users),10011(DOMAIN\teacher assistants),10074(DOMAIN\schema admins)

Then, I put this logic, which is similar to what one would normally use
if LDAP and Ldap-Groups were in use, in the post-authentication section
of my sites-enabled/default:

        if (NAS-Port-Type == Wireless-802.11) {
                if (Called-Station-Id =~ /.*:SSID-A/i) {
			# Can't do 'if (Group != "xxxxx")' because !=
			# operator doesn't work for group checking. Careful
			# with the number of backslashes.
                        if (!(Group == "DOMAIN\\\\group A") ) {
                                update reply {
                                        Reply-Message = "User not allowed to join this wireless network"
                                }
                                reject
                        }

                }
                elsif (Called-Station-Id =~ /.*:SSID-B/i) {
                        if (!(Group == "DOMAIN\\\\group B") ) {
                                update reply {
                                        Reply-Message = "User not allowed to join this wireless network"
                                }
                                reject
                        }
                }
        }

This works if the EAP identity is "DOMAIN\username". However, I don't
want to make things unnecessarily complicated for my users so I want
them to be able to enter just "username" when they configure their
devices.

The main issue to address, however, is that if the identity is entered
as "username" (not "DOMAIN\username"), the group check will fail because
the Unix user ID for users that are known to the system via winbind is
"DOMAIN\username", not "username" (see output from the "id" command
above).

"Not a problem", I thought, "I'll just manipulate User-Name before
anything happens and prefix it with "DOMAIN\". Turns out that was a bad
idea because that made User-Name different than the EAP identity hidden
in the EAP message, which caused the "rlm_eap: identity does not match
User-Name, setting from EAP identity" message that has bitten so many
people before.

The solution I came up with was to still manipulate the User-Name but
towards the end, in the post-auth section, instead of at the beginning.
This way EAP uses the right identity, but the rlm_unix Group check uses
the correct "DOMAIN\username" User-Name.

The final configuration looks like this:

        if (NAS-Port-Type == Wireless-802.11) {
		# If User-Name doesn't contain our domain then add it.
		# It's needed for the Group check to use the correct
		# username.
                if (User-Name !~ /DOMAIN\\\\/i) {
                        update request {
                                User-Name := "DOMAIN\\\\%{User-Name}"
                        }
                }

                if (Called-Station-Id =~ /.*:SSID-A/i) {
			# Can't do 'if (Group != "xxxxx")' because !=
			# operator doesn't work for group checking. Careful
			# with the number of backslashes.
                        if (!(Group == "DOMAIN\\\\group A") ) {
                                update reply {
                                        Reply-Message = "User not allowed to join this wireless network"
                                }
                                reject
                        }

                }
                elsif (Called-Station-Id =~ /.*:SSID-B/i) {
                        if (!(Group == "DOMAIN\\\\group B") ) {
                                update reply {
                                        Reply-Message = "User not allowed to join this wireless network"
                                }
                                reject
                        }
                }
        }

I do not know about the performance impact but this should not require
additional network traffic to check for group membership because winbind
caches this information.

Another possible advantage is redundancy -- I understand the LDAP method
does not allow for multiple LDAP servers. Using winbindd (I theorize, I
am not sure about this) provides redundancy because the group membership
comes from the domain controller, which is found using DNS lookups --
if a controller goes down then another (hopefully) takes its place and
winbindd will be able to find it with no configuration changes.

This approach seemed simple to me -- no additional configuration other
than manipulating the User-Name in the post-authentication phase.

Things can also be made to work if the user chooses to configure the
supplicant with "DOMAIN\user" as the identity -- in this case one needs
to configure "with_ntdomain_hack = yes" in modules/mschap, create an
empty "DOMAIN" realm in proxy.conf, and enable the ntdomain realm in the
authorize section of sites-enabled/inner-tunnel. Doing this will allow
both "DOMAIN\user" and just "user" to work.

Any thoughts, gotchas or hidden traps with this approach? I have tested
this in pre-deployment and it works. I will start testing this in
production to see if users have any issues.

Cheers,

Eloy Paris.-



More information about the Freeradius-Users mailing list