Did you know you can authenticate a user on a NetScaler Gateway using an Active Directory object attribute value? I’ve covered NetScaler Gateway authentication using security group extraction or by the OU the user ID is in before here in my “How to setup Citrix Netscaler (Access Gateway) with multiple domains for web browsers and mobile devices” article:
Those are really common methods at most companies to allow authentication on your NetScaler Gateway. But what happens if your users are in multiple root level OUs and they are not in any kind of common security group that allows NetScaler Gateway access? Many large companies have users in many different security groups and doing this can cause token size bloat which is a huge headache for your AD admins to deal with:
So the last thing you want is require yet another security group and bloat the token size in those instances. One thing you can do is this scenario is authenticate off an Active Directory attribute on the user’s account. For example, if you populate a certain attribute field in AD for all your users that you do not for service accounts, administrator accounts, privileged accounts, test accounts, etc., you can look for the presence of this field being set and allow access based on that. There’s not a whole lot of information out there on doing this and pretty much nil on how to write your search expression. So let me try to explain how to do it.
1. First you need to take a good look at your AD user accounts and see what attributes are filled out you may be able to use. Open up ADUC and set the View to Advanced Features. It will have a check mark next to it when it’s set:
2. Now navigate to a user account object and double click it. You should see a tab called “Attribute Editor”:
3. Click the Attribute Editor tab and start scrolling through your attributes for something you want to use. Again, you want to try and find something that is set for regular users but not for others that you do not want to authenticate against your NetScaler Gateway. In an Exchange 2010 or higher environment, there is a AD schema extension you can run which gives you “extensionAttribute” fields you can assign values to. There are 15 of them and they are numbered extensionAttribute1 through extensionAttribute15. Microsoft wants companies to use these attribute fields to store custom company information. After all, AD is designed to be extensible. This is covered on Microsoft TechNet but there’s actually a much better in plain English explanation here by Don Jones:
Just be aware that these Exchange added attributes are cleared when you disable a user’s mailbox. That actually works in our favor if anything when we’re talking about keying authentication off a field such as this. When a user’s account is disabled when they leave the company, their mailbox is pretty likely to be too. As is their remote access privileges. So perfect!
In my example scenario, let’s say extensionAttribute15 is set to an integer value. We have a group of users and their extensionAttribute15 values:
1 2 3 4 5 6 |
James = 1000 Mary = 2000 Ethan = 3000 Lisa = 8000 Lisa's test account = <not set> Bill = 12000 |
All the the regular user accounts have a number set (integer value) but Lisa’s test account is not set (null value). We want to block Lisa (who has the not set value below) from being able to authenticate on the NetScaler Gateway but allow everyone else to authenticate.
4. Open up your NetScaler Gateway console. I’m going to use a simple LDAP authentication policy for this example. Go to NetScaler Gateway > Policies > Authentication > LDAP then click on the Servers tab and double click on your LDAP server to open it:
5. Too keep things simple in this example I’m using LDAP on port 389 in plaintext. Please, please, please do not do this in your production environment. Use LDAPS at a minimum. This is just for example purposes. I am hoping you read my previous article and know all about setting the Base DN and Administrator Bind DN so I’m going to skip that part. Right below all that is the “Server Logon Name Attribute”. This is set to “samAccountName” which is very basic. As long as the user authenticates with the right password, they’re going to be let in. So we want to add a Search Filter in addition to this. The Search Filter needs to look at extensionAttribute15 and see if it has a number or not. This will block Lisa’s test account but allow the rest of the gang through.
This where things get a little tricky. That little question mark next to the Search Filter says you can use an = sign but it doesn’t show other relational operators. I found that in order to allow everyone with a number through but not allow the null value, you can do this:
extensionAttribute15>=0
This is saying allow anything greater than or equal to 0 through but deny everyone else. A null value is considered less than 0 to the NetScaler. So it will block users who don’t have extensionAttribute15 set.
If for example, you want to do a “not equals” the !=
denoting “not equals” or <>
also denoting “not equals” will not work here. Funny right? Especially since you can use those in other kinds of policy expressions. I found if you want to do a not equals, it has to be in this format:
!(extensionAttribute15=0)
which is saying if the attribute is equal to 0, then deny. Any other number will be an allow.
If you want to do an OR statement allowing either attribute with a value to let the user in, you can use:
|(extensionAttribute14>=0)(extensionAttribute15>=0)
If you want to do an AND statement meaning both attributes must be valid to allow the user in, you can use.
&(extensionAttribute14>=0)(extensionAttribute15>=0)
6. Now test your new authentication policy using each of the users above. All the users will be able to login except for Lisa’s test account. I wrote an article a while back on “How to troubleshoot RADIUS or TACACS authentication issues on a Netscaler/Access Gateway” here that may come in handy for you:
Easiest thing to do, open an SSH session using Putty and do the following commands to watch the authentication process for each user:
shell
followed by:
cat /tmp/aaad.debug
Hope this helps! Leave a comment below if you have any questions or suggestions.
UPDATE:
Had a use case where I needed to DENY users in a specific OU from logging in while ALLOWING all other OUs in AD to be able to login. The request from the client was to specifically filter out the OU and use no other AD attributes. The OU the user account resides in is in the “distinguishedName” attribute but I was not able to successfully write a search filter for this. Though my filter syntax was correct I had a lot of trouble getting it to work as expected. The alternative solution to accomplish this is to not even attempt to filter using an AD attribute. Instead of trying to filter at the NetScaler config level using a Search Filter, do it at the AD level using permissions.
The service account you are using to do your LDAP bind should have a DENY on all permissions to the OU you do not want users to login from. Just go into ADUC, right click the OU you want to block, go to Properties, go to Security tab, and add the service account user ID and set the deny on all permissions. The service account will no longer be able to enumerate/find users in that OU and so they are blocked from being able to login! You can verify this by opening up ADUC while you are logged in as the service account and you’ll notice the OU you set the DENY on has disappeared. If you do a aaad.debug on your NetScaler you can see it will return a “user not found” message and send a reject whenever you try and login with an account in the OU you have denied. Thanks to my friend Lee for advising me on this simple solution!
UPDATE 2:
Someone asked me if this type of filter syntax can be used for security groups as well. In the proposed scenario, we have 2 NetScaler Gateways. We want to allow all users to use the first NetScaler Gateway vserver. We want to restrict access and DENY a subset of users in a security group while allowing everyone else in on the second NetScaler Gateway vserver. We just need to create another policy with the DENY expression and place it at a higher priority than the default ALLOW policy. This is how it would be:
On GW1 – all domain users allowed
1. Create an LDAP policy that allows the whole domain root or a users OU like normal.
On GW2 – all domain users allowed except the restricted group
1. Create LDAP policy 1 with priority 90 = deny “Restricted_Users_Security_Group” via Search Filter using
1 |
!(memberOf=CN=Restricted_Users_Security_Group,OU=SecurityGroups,DC=yourdomain,DC=local) |
2. Create LDAP policy 2 with priority 100 = allow the whole domain root or a users OU like normal.
So what is happening is the deny has a higher priority and the user will fail on GW2. If the user is not a member of the security group, then the 2nd policy will get hit which will give them an allow and let them login.
Phil Dusome
May 11, 2016 at 8:00 AM
Hi Jason: Was searching everywhere for example of how to DENY a specific group. Thanks for that tidbit. It seemed intuitively obvious that if the session default was ALLOW, then specified groups would be denied and that if the default was DENY, then specified groups would be allowed. I guess not so obvious to Citrix.
Andy
January 12, 2017 at 5:17 AM
Hi Jason, great post thanks. We are looking to authenticate users based on matching an AD attribute ourselves. However it must be a specific match rather than a value greater than 0 which you used in your example above.
The logic to me says that if we simply specify “extensionattribute15” in the attributes field with no further modification it will look for an exact match and deny if they are different would you agree?
Prabhu R
July 27, 2017 at 9:38 AM
How are we defining Allow and Deny in the LDAP Policies.
Have requirement to that Only one Gateway where nned to bind an “restricted_Group”, Except this group all users should able to connect to the gateway.
Seth Cross
October 31, 2017 at 12:44 PM
I’m a little late to the party here, but thank you for your article Jason!
I haven’t tested this yet, but I’m looking to deny multiple security groups (update #2, but more than one). Can I do a: !|(case1)(case2)(case3) type of syntax, or is only one operator allowed in before the parenthesis?
I kind of wish they would put a ‘Groups Denied to Login’ field to complement the ‘Groups Allowed to Login’ field.