NetScaler can change expired AD passwords, we all know that. But did you ever wonder if you can implement a warning prior to that expiration date? Well, wonder no longer!
- Solution Approach
- Resulting User Experience
- The math behind the scenes
I actually have heard that requirement a few times in the past. But I always had to answer “not possible”. Recently I’ve started exploring nFactor more and more and I got hooked by this feature and the flexibility it provides.
For another nFactor use case have a look at NetScaler Gateway: SAML with multiple IDPs using nFactor as well! In future I might also do a general writeup of how nFactor works because the documentation on this topic is really lacking the fundamentals. Let me know if you’d be interested in that!
Disclaimer: First of all, let’s get things straight! I admit the solution is a bit overkill in terms of complexity vs. results. My main goal was to push the limits of the nFactor feature and to show people what’s possible if you think outside the box!
So how did I achieve this?
First I query the user’s Password Last Set attribute during the authentication from Active Directory and store it. This is done in the first factor of the nFactor chain which is basically just a simple traditional authentication.
I then do some math based around that attribute, the configured maximum password age as well as the desired warning threshold to find out whether a warning should be displayed or not. This is done in the second factor of the nFactor chain which is a decision stage only and not visible to the user.
If a warning should be displayed, the third factor in the nFactor chain is executed which is a custom forged “login mask” without any input fields displaying the warning message shown above.
As I mentioned earlier, the whole concept is a bit of an overkill. You have been warned!
First you have to create two login schemas to use and save them in /nsconfig/loginschema/. The first one is UsernamePassword.xml which is based on the SingleAuth.xml that comes with NetScaler with some minor alterations:
<?xml version="1.0" encoding="UTF-8"?> <AuthenticateResponse xmlns="http://citrix.com/authentication/response/1"> <Status>success</Status> <Result>more-info</Result> <StateContext></StateContext> <AuthenticationRequirements> <PostBack>/nf/auth/doAuthentication.do</PostBack> <CancelPostBack>/nf/auth/doLogoff.do</CancelPostBack> <CancelButtonText>Cancel</CancelButtonText> <Requirements> <Requirement><Credential><ID>login</ID><SaveID>ExplicitForms-Username</SaveID><Type>username</Type></Credential><Label><Text>User name</Text><Type>plain</Type></Label><Input><AssistiveText>Please supply username</AssistiveText><Text><Secret>false</Secret><ReadOnly>false</ReadOnly><InitialValue></InitialValue><Constraint>.+</Constraint></Text></Input></Requirement> <Requirement><Credential><ID>passwd</ID><SaveID>ExplicitForms-Password</SaveID><Type>password</Type></Credential><Label><Text>Password:</Text><Type>plain</Type></Label><Input><Text><Secret>true</Secret><ReadOnly>false</ReadOnly><InitialValue></InitialValue><Constraint>.+</Constraint></Text></Input></Requirement> <Requirement><Credential><ID>loginBtn</ID><Type>none</Type></Credential><Label><Type>none</Type></Label><Input><Button>Log On</Button></Input></Requirement> </Requirements> </AuthenticationRequirements> </AuthenticateResponse>
The second and more interesting one is the ngw-pw-expire.auth.schema.expire-notification.xml which displays the actual password expiry message
<?xml version="1.0" encoding="UTF-8"?> <AuthenticateResponse xmlns="http://citrix.com/authentication/response/1"> <Status>success</Status> <Result>more-info</Result> <StateContext></StateContext> <AuthenticationRequirements> <PostBack>/nf/auth/doAuthentication.do</PostBack> <CancelPostBack>/nf/auth/doLogoff.do</CancelPostBack> <CancelButtonText>Cancel</CancelButtonText> <Requirements> <Requirement><Credential><Type>none</Type></Credential><Label><Text>Your password will expire in a few days, consider changing it soon!</Text><Type>confirmation</Type></Label><Input /></Requirement> <Requirement><Credential><ID>loginBtn</ID><Type>none</Type></Credential><Label><Type>none</Type></Label><Input><Button>Continue...</Button></Input></Requirement> </Requirements> </AuthenticationRequirements> </AuthenticateResponse>
For both of them you can then create the Login Schema config objects. For the Username/Password you’ll also need a policy as it is bound to the AAA vServer (which only accepts policies).
In addition one “no schema” Login Schema is created for the second factor.
add authentication loginSchema ngw-pw-expire.auth.schema.usernamepassword -authenticationSchema "/nsconfig/loginschema/UsernamePassword.xml" -SSOCredentials YES add authentication loginSchemaPolicy ngw-pw-expire.auth.schema.usernamepassword.true -rule true -action ngw-pw-expire.auth.schema.usernamepassword add authentication loginSchema ngw-pw-expire.auth.schema.expire-notification -authenticationSchema "/nsconfig/loginschema/ngw-pw-expire.auth.schema.expire-notification.xml" add authentication loginSchema ngw-pw-expire.auth.schema.noschema -authenticationSchema noschema
Next we’ll need some authentication profiles and policies:
- One LDAP Action that queries the users pwdLastSet as Attribute1
- One standard LDAP Policy for authentication
- One dummy NO_AUTHN Policy set to true
- One NO_AUTHN Policy where all the magic is happening and that determines whether the user’s password has expired
Note that in the last line you configure the maximum age of the password (42 days) as well as the notification threshold (7 days).
add authentication ldapAction ngw-pw-expire.auth.act.ldap-nerdscaler.lab-SAM <strong>-Attribute1 pwdLastSet</strong> -serverIP x.x.x.x -serverPort xxx -ldapBase "DC=xxxx,DC=xx" -ldapBindDn firstname.lastname@example.org -ldapBindDnPassword xxx -ldapLoginName sAMAccountName add authentication Policy ngw-pw-expire.auth.pol.ldap-nerdscaler.lab-SAM.true -rule true -action ngw-pw-expire.auth.act.ldap-nerdscaler.lab-SAM add authentication Policy ngw-pw-expire.auth.pol.noauth.true -rule true -action NO_AUTHN add authentication Policy ngw-pw-expire.auth.pol.noauth.pw-expired -rule "HTTP.REQ.USER.ATTRIBUTE(1).SUBSTR( 0, HTTP.REQ.USER.ATTRIBUTE(1).LENGTH.SUB(8) ).TYPECAST_NUM_AT.SUB(1164447360).DIV(6).DIV(60).DIV(24).<strong>ADD(42).SUB(7).</strong>LE( SYS.TIME.DIV(60).DIV(60).DIV(24) )" -action NO_AUTHN
*In case you’re interested in what’s happening behind the scenes in that password expiry policies I’ve included a breakdown at the end of this article.
Once these supplementaries are configured we can start to stitch everything together. I’d like to start from the end of the chain.
3rd Factor: Password Expiry Message
The third factor is a simple Policy Set with the previously configured Login Schema message as well as a dummy NO_AUTHN Policy that always succeeds.
add authentication policylabel ngw-pw-expire.auth.pol.label.expire-notification -loginSchema ngw-pw-expire.auth.schema.expire-notification bind authentication policylabel ngw-pw-expire.auth.pol.label.expire-notification -policyName ngw-pw-expire.auth.pol.noauth.true -priority 100 -gotoPriorityExpression NEXT
2nd Factor: Check Password Expiry
The second factor is the decision point. It checks the users password age and either sends him to the 3rd factor or ends the authentication chain with an always-true policy. Both authentication policies are of type NO_AUTHN and the schema is “no schema” which means the user doesn’t see anything of this stage.
add authentication policylabel ngw-pw-expire.auth.pol.label.check-expiry -loginSchema ngw-pw-expire.auth.schema.noschema bind authentication policylabel ngw-pw-expire.auth.pol.label.check-expiry -policyName ngw-pw-expire.auth.pol.noauth.pw-expired -priority 100 -gotoPriorityExpression NEXT -nextFactor ngw-pw-expire.auth.pol.label.expire-notification bind authentication policylabel ngw-pw-expire.auth.pol.label.check-expiry -policyName ngw-pw-expire.auth.pol.noauth.true -priority 110 -gotoPriorityExpression NEXT
1st Factor: Authentication
Finally for the first factor we need to create an AAA vServer and bind our regular Username/Password configuration. As a mandatory next factor we configure our previously created second factor Policy Label
add authentication vserver ngw-pw-expire.auth.vs.int SSL 0.0.0.0 bind ssl vserver ngw-pw-expire.auth.vs.int -certkeyName xxx.xxx.xx bind authentication vserver ngw-pw-expire.auth.vs.int -policy ngw-pw-expire.auth.pol.ldap-nerdscaler.lab-SAM.true -priority 100 -gotoPriorityExpression NEXT <strong>-nextFactor ngw-pw-expire.auth.pol.label.check-expiry</strong> bind authentication vserver ngw-pw-expire.auth.vs.int -policy ngw-pw-expire.auth.schema.usernamepassword.true -priority 100 -gotoPriorityExpression END
NetScaler Gateway Configuration
Finally, in order to use this whole contraption in a NetScaler Gateway, we need to create and configure an Authentication Profile.
add authentication authnProfile ngw-pw-expire.auth.prof -authnVsName ngw-pw-expire.auth.vs.int set vpn vserver ngw-pw-expire.ngw.vs.ssl.445 -authnProfile ngw-pw-expire.auth.prof
And that’s it! (Finally – I did warn you, didn’t I?)
Resulting User Experience
This configuration results in the following user experience. The user logs in as always using his Active Directory credentials.
Now if his password expires within the next 7 days he’s prompted with the following dialog after the login.
After clicking “Continue” the user is forwarded to Storefront as usual.
Now if your happy with the result feel free to leave at this stage but if you want to drill down a little what’s happening in the policy that checks the password expiry you’re welcome to stay.
The math behind the scenes
Now this gets a little nerdy and a little dirty as well, you’ll see why in a minute.
To begin with I’d like to encourage you to have a look into the Microsoft article How to obtain password expiration date by using LDAP ADSI provider that sets the stage for this scene.
Allright, so we do have our pwdLastSet attribute gathered during the authentication. Next thing we need to do is a simple equation
(pwdLastSet + maxPwdAge – notificationThreshold) > currentTime
Simple, right? That’s what I thought until I noticed one things:
- NetScaler works with Unix timestamps (seconds since 1970)
- Windows works with FILETIME (100-nanoseconds since 1600)
Simple, right? Just do the math. Convert the FILETIME to seconds, subtract however many seconds (11644473600 seconds) there are between the two dates and your done. Actually there are plenty of articles out there on how to convert those.
You would result in something like this:
If (((( pwdLastSet / 1000 / 10000 ) – 11644473600 ) / 60 / 60 / 24 ) + maxPwdAge – notificationThreshold ) < ( currentTime / 60 / 60 / 24 )
So, no problem right? Wrong! That’s what I thought until I realized that the FILETIME is too big to be stored in a 32bit Integer and NetScaler apparently only handles 32bit Integers.
So is this the end of the Jurney? No! And this is the part where it get’s dirty.
How can we do math without doing math? Elementary 101. When dividing by multiples of 10 you can simply cut the zeros on both ends if present. Well, there are no zeros in the pwdLastSet attribute but I figured that nanosecond-precision is not that important when we’re doing a warning message that’s warning about days.
So I replaced “( pwdLastSet / 1000 / 10000 )” with “Substring( pwdLastSet, Len( pwdLastSet ) – 7 )”.
If (((Substring( pwdLastSet, Len( pwdLastSet ) – 7 ) – 11644473600 ) / 60 / 60 / 24 ) + maxPwdAge – notificationThreshold ) < ( currentTime / 60 / 60 / 24 )
Almost there. Just that we’re still a little too large so I needed to drop one more zero across the whole equation:
If (((Substring( pwdLastSet, Len( pwdLastSet ) –
78 ) – 1164447360 0) / 6 0/ 60 / 24 ) + maxPwdAge – notificationThreshold ) < ( currentTime / 60 / 60 / 24 )
Resulting in the final equasion:
If (((Substring( pwdLastSet, Len( pwdLastSet ) – 8 ) – 1164447360 ) / 6 / 60 / 24 ) + maxPwdAge – notificationThreshold ) < ( currentTime / 60 / 60 / 24 )
And in NetScaler Advanced Policy language that’s the rule you’ve seen before:
HTTP.REQ.USER.ATTRIBUTE(1).SUBSTR( 0, HTTP.REQ.USER.ATTRIBUTE(1).LENGTH.SUB(8) ).TYPECAST_NUM_AT.SUB(1164447360).DIV(6).DIV(60).DIV(24).ADD(42).SUB(7).LE( SYS.TIME.DIV(60).DIV(60).DIV(24) )
Pretty cool stuff!
9 Replies to “NetScaler Gateway Password Expiry Warning with nFactor”
Manuel, an excellent example!
I want to use the field from AD “Pager” as the 2 factor (additional password), and the value “pager” can be seen http.req.user. Attribute (1), but compare it to the Passwd1 field.
I want to implement the following verification mechanism
Username = LDAP (samaccountnname) -ok
passwd = LDAP (password) -ok
PASSWD1 = LDAP (pager)????
I’d be happy to help
Sorry for the late reply!
Interesting approach. I guess the challange is to actually make NetScaler compare the two values.
You might be able to create something like:
1st Factor: Ask for all three values, but make sure that the 3rd value is a custom field (see Citrix’s domain dropdown example) – Apply LDAP policy and retrieve ATTRIBUTE(1)
2nd Factor: NO_SCHEMA, NO_AUTH policy with a “if ATTRIB(1) = FIELD passed from 1st factor” rule applied.
This would then only successfully authenticate the second stage if both values match.
But that would produce a bad error message for users who put a wrong pager value: “no policy applied”. You could conquer this by creating a third factor just for them with a dummy “wrong pager” message similar to my “password expired” warning above. Plus eventually a fourth factor that actually redirects them to the logout somehow finally.
Hope that makes sense – to unleash the full potential of nFactor you definitly need to bend your head sometimes :-)
Great article. Been searching for this solution for months !
Do you know if it’s possible to set one of the nfactor loginschemas to allow the user to change the password?
Creating the loginschemas I guess is no issue, but is it possible to create the expression to allow the Netscaler to update LDAP with users password change?
in fact I was thinking about that as well for a while – nothing straight forward came to my mind.
Two things came to my mind which I haven’t tried yet though:
– Investigate what’s the exact URL that NetScaler uses for expired passwords and add a second button like “change now” which links to that URL – not sure whether NS even accepts direct calls of these pages though
– Set a cookie in nFactor (via JS in the schema or via Rewrite somehow) – and then apply a session policy that forwards you to StoreFronts password change facility /Citrix/StoreWeb/whatever/the/passwordchange/path/is
Again, just thoughts, never tried any of those yet.
Let me know if you do!
I have configured nFactor authentication for NS GW to implement native OTP. User name upfront to check group membership and present next factor based on group membership. 2nd factor is password only or password & OTP.
After implementing this, users are not able to change password when password is expired or they are notified when password is about to expire.
Could you help me how can I set password expiry notice or password change in this scenario?
Thanks for the great article. I am seeing an issue when a password doesn’t meet the requirements in the expression, it doesn’t send them to Storefront and displays the “No active policy during authentication”. If it is within the expiration it displays the Expiry Message and allows the user to hit continue and passes through to Storefront just fine. Did I miss something?
If you could point me in the right direction that would be great. Thanks!
Hi neztik, did you ever find a solution to this error message “No active policy during Authentication” ?
Our users see this after they have changed their password when the “Change password at next logon” is checked on the users AD account.
I do recall some NS versions after I wrote this article having problems with Factors that contain only NO_AUTHN and NOSCHEMA throwing that exact same message – so that might be the problem.
But furthermore, did peek at the latest 12.1 release notes?
[# 703474] Support for 14-day password expiry notification for LDAP based authentication
I’m not sure if this applies to NetScaler Gateway as well but if you use a AAA vServer for your NGW authentication I’m sure it will!
I haven’t tried that one out myself yet so please let me know if that works for you!
(Plus I should definitly include a note on that in the top of the article)
Thank you for this great article. Do you know if it is possible to use this in combination with Azure MFA? i am thinking somehow 1st LDAP auth, 2nd RADIUS auth and 3th password expiry warning.