Citrix ADC has had a feature called AAA (authentication, authorization, and accounting) for many years now. It’s purpose is the secure load balanced websites. Providing load balancing for webservers ( think IIS for on-prem sharepoint, outlook web access, or internally built web apps, think apache web servers, nginx, etc for larger cloud hosted or other linux based hosted apps) is at the very core of where Citrix ADC came from ( NetScaler Inc back in the day!).
However, some of these web based applications have authentication. Now, this can be handled on the webserver – it’s a few clicks in IIS, or a few lines of config in a .htpasswd. However, this means that un-authenticated traffic needs to be allowed through the DMZ and onto a webserver.
This is why we were asked by our customers years ago to create this feature. Which we did, in 2012. Read about it here. After that we had some queries … one of which I’ll address here. A customer asked us if we could block a user from logging in twice. Oh – and if we could generate an alert when that happened. Logging the client IP in the alert. Interesting! This is my first shot at how this would work. I can already think of ways to optimise it, streamline etc, but right now, I thought I’d put out the the config, just in case I never get around to the polished config. Because I never do. 🙂
Anyhow – here’s how I tackled it!
When someone is authenticated by the ADC, there are a few different cookies set during the auth process, but the one that is used POST authentication is NSC_TMAA. So – lets do all our checking on that.
I created a session map ( think of it as a two column table). Username goes in one as the key, and the value is the aaa session ID. We use two responder policies. The first one checks if the user has logged in (and the ADC has recorded a session). If the user HASN’T, then it adds the user and the session ID to the table. If the user HAS logged in, it moves to the second responder policy.
This second policy checks that the sessionID matches the one that is saved… (so this is a request from the same user who originally logged in. If it doesn’t match (i.e. the session ID has been allocated by a SUBSEQUENT login – then we respond with our message. If it DOES match, then the request flows as normal. After 20 minutes of inactivity, the session ID is removed from the session map. ( so if someone doesn’t manually log out, they cannot suspend activity to that account).
There is scope to add further logic to this if you so require. Alerting can be configured to alert you IF a second login for a user occurs. ( so you have a record of WHEN this happens, and the IP address of where the duplicate login request came from etc.) This is at the VERY end. We can add a string before or after the username or the session ID. (e.g. we can add a client.ip.src – which can be used for example to check is the IP came from the same location..) I’ve commented each line and explained each variable as best I could below – so all of the above should make more sense when you read through the config line by line. This is very much a rough’n’ready setup – as I said, not polished, and I’m open to any suggestions, corrections etc.
add ns variable session_map -type "map(text(20),text(64),10000)" -expires 1200
# This creates the session map – which has two columns, and a size of 10,000 rows ( handles up to 10,000 concurrent user sessions)
# Column 1 ( text(20)) is a 20 character field which stores the username. Change this value if you have larger usernames.
# Column 2 is the session ID. This is usually 32 chars long, I’ve allowed for double.
# If the variable is not referenced or accessed for 20 minutes (1200 seconds), it expires. This means if a user does not submit a HTTP request during 20 minutes, it will have expired. This value should align with the session timeout configured in AAA.
add ns assignment add_session -variable "$session_map[aaa.user.login_name]" -set aaa.USER.SESSIONID
# This action adds a line to the above session map. The first value is the extracted AAA username after they have logged in. (remember, this action is invoked by a responder policy applied to the Load Balanced VServer protected by AAA, not the AAA Vserver itself.
add ns assignment delete_session -variable "$session_map[aaa.user.login_name]" -clear
#This action deletes the session when the user clicks the logout button ( which results in a HTTP Get Request to hit the appliance with the following string contained in the URL: byebye
add responder action no-doubles respondwithhtmlpage no-double-login -responseStatusCode 200 -headers NS-Blocker("Kicked Out")
# This is the responder action which serves back a page indicating that this user has already logged in and has an active session.
add responder policy add_user_session "$session_map.valueExists(aaa.user.login_name).not" add_session
# This is the responder policy which adds a user to a session if that username does not already exist in the table (e.g. pseudocode flow is “If not logged in, then add session. If they ARE logged in, then this expression fails and the next responder policy is hit.”
add responder policy block-double-sessions "$session_map.valueExists(aaa.user.login_name) && $session_map[aaa.user.login_name].eq(aaa.USER.SESSIONID).NOT" no-doubles
# This policy gets hit when someone logs in a second time from a different browser or client. Their name already exists in the session map, and this expression checks if the session ID they have been given matches the one recorded. We can also log the client IP, User-Agent and other request entities into a Syslog message which can alert the admins of these double logins, and where they come from.
add responder policy delete_user_session "http.req.url.contains(\"byebye\")" delete_session
# When the device sees this URL, it deletes the user session from the session map. This would allow the same user to login again.
# “Test Web App” is the LB Vserver where the responder policies are bound.
bind lb vserver "Test Web App" -policyName delete_user_session -priority 90 -gotoPriorityExpression END -type REQUEST
# This is the LB VServer where the responder policies get bound to add user sessions, and to block an existing user from logging in. The first policy to be hit is the logout policy, if the user sends in a logout URL.
bind lb vserver "Test Web App" -policyName add_user_session -priority 100 -gotoPriorityExpression NEXT -type REQUEST
# This policy adds a user login name and sessionID
bind lb vserver "Test Web App" -policyName block-double-sessions -priority 110 -gotoPriorityExpression END -type REQUEST
# This is the binding to block a second successful login from reaching the web app if the user is already logged in.
add tm trafficAction logout_session -appTimeout 1 -persistentCookie OFF -INitiateLogout ON
add tm trafficPolicy logout_session_pol "http.REQ.URL.CONTAINS(\"byebye\")" logout_session
# This traffic action and policy is designed to capture a user logout event, which will cancel or invalidate a users login session to the AAA Vserver. Once this policy is hit, the next request to hit the LB VServer will result in the user being redirected to the login page again.
bind lb vserver "Test Web App" -policyName logout_session_pol -priority 100 -gotoPriorityExpression END -type REQUEST
This is where the traffic policy is bound to the LB VServer. Note the bind priority is 100 – which is ok, as this is not a responder policy – so no conflict.
This is the custom logging action I’ve created which can be bound to the responder policy blocking the second login. Dont forget to enable user defined log messages (line 2 of the below 3 lines of config.
add audit messageaction log_session_assignment EMERGENCY "\"capturing this user: \" + aaa.USER.LOGIN_NAME + \" with this session value: \" + aaa.USER.SESSIONID + \"Login-Session-Information: \" + aaa.USER.LOGIN_ATTEMPTS" -logtoNewnslog YES
set audit syslogParams -userdefinedAuditlog YES
set responder policy block-double-sessions -logAction log_session_assignment