Full pre-authentication for Exchange with Netscaler

One day, the information security service decided that not only everyone should have access to e-mail outside the perimeter. We started looking for ways and realized that we need an ADC class product. We decided to try to implement it using the Citrix Netscaler that we have, which takes over the authentication function and check whether this employee can be allowed access to the mail system. Happened. No wonder the system is so expensive. There will be a long article-instruction.

Articles on how to make friends between Microsoft Excnage Server and Cirtix Netscaler are not exactly on every corner, but you can find them. All found articles did not close the task. Basically, articles ended on a content switcher (a content switcher can be implemented on HAProxy). Several articles have described web form interception. We also needed to deny access based on membership in an Active Directory group. It turns out that we ourselves came up with the idea that we want to combine authentication and authorization in one bottle and on one login screen.

Exchange virtual directories for webmail use standard (not forms-based) authentication. Those. both internal and external connections use the same mail servers.

Somehow it so happened that I am “caution, certified specialist” for one and the other product. In general, there was no one to write off, I had to do my homework myself.

Let’s immediately agree that further in the text we will use 198.51.100.0/24 for external (white) networks, 192.168.0.0/16 for internal networks. The namespace example.com for names accessible from the Internet and examle.local for naming internal servers.

The principle of operation is this:

The Content Switch accepts the connection and passes it to the appropriate virtual server. The virtual server performs authentication. Group membership is verified using a multi-factor authentication mechanism. The second factor does not require user intervention and occurs in the background. If the check is passed, then we start it, if not, we show the user a message that he was denied access.

Along the way, we protect the user from password brute force. To be honest, it was the protection against password brute force that was the main task for IT, since it is IT that users come to who have changed the password and forgot to change it on the phone, thereby causing account blocking.

Netscaler tried to reflect the relationship between entities in the image below. Some details could be missed

Let’s go to set up Netscaler. We will do it in stages. Explanations will follow.

Explanations

Citrix Netscaler since version 13 is called Citrix ADC

It is worth noting that for Exchnage 2013 you need to publish both OWA and ECP, and for Exchange 2016/2019 only OWA

If you have a hybrid and Teams, then you cannot pre-authenticate on EWS (the calendar in Teams will stop working)

In our example, the Exchange servers are named exch-server-nn and are located on the 192.168.9.0/24 network. If you have Exchange 2013, then exch-server-nn are servers with the ClientAccess role. Internal domain EXAMPLE.LOCAL, external name mail.example.com

1 Basic settings

Let’s create the basic entities necessary for work, namely servers, service groups, monitors, content switcher

#Create Server
add server exch-server-01 192.168.9.1
add server exch-server-02 192.168.9.2

#Create Service Groups
add serviceGroup svcgrp_ex2019pub_owa SSL -cip ENABLED X-Forwarded-For
add serviceGroup svcgrp_ex2019pub_async SSL -cip ENABLED X-Forwarded-For
add serviceGroup svcgrp_ex2019pub_rpc SSL -cip ENABLED X-Forwarded-For
add serviceGroup svcgrp_ex2019pub_ews SSL -cip ENABLED X-Forwarded-For
add serviceGroup svcgrp_ex2019pub_autodisover SSL -cip ENABLED X-Forwarded-For
add serviceGroup svcgrp_ex2019pub_oab SSL -cip ENABLED X-Forwarded-For
add serviceGroup svcgrp_ex2019pub_mapi SSL -cip ENABLED X-Forwarded-For
add serviceGroup svcgrp_ex2019pub_ecp SSL -cip ENABLED X-Forwarded-For

#Create monitors
add lb monitor mon_exch_owa HTTP-ECV -send "GET /owa/healthcheck.htm" recv 200 -LRTM DISABLED -secure YES
add lb monitor mon_exch_async HTTP-ECV -send "GET /Microsoft-Server-ActiveSync/healthcheck.htm" recv 200 -LRTM DISABLED -secure YES
add lb monitor mon_exch_rpc HTTP-ECV -send "GET /rpc/healthcheck.htm" recv 200 -LRTM DISABLED -secure YES
add lb monitor mon_exch_ews HTTP-ECV -send "GET /ews/healthcheck.htm" recv 200 -LRTM DISABLED -secure YES
add lb monitor mon_exch_autodiscover HTTP-ECV -send "GET /Autodiscover/healthcheck.htm" recv 200 -LRTM DISABLED -secure YES
add lb monitor mon_exch_oab HTTP-ECV -send "GET /oab/healthcheck.htm" recv 200 -LRTM DISABLED -secure YES
add lb monitor mon_exch_mapi HTTP-ECV -send "GET /mapi/healthcheck.htm" recv 200 -LRTM DISABLED -secure YES
add lb monitor mon_exch_ecp HTTP-ECV -send "GET /ecp/healthcheck.htm" recv 200 -LRTM DISABLED -secure YES

We connect one to the other, namely, we link servers and monitors to groups

#Bind Service Groups
#Replace FQDN and ip address regarding your environment
bind servicegroup svcgrp_ex2019pub_owa exch-server-01 443
bind servicegroup svcgrp_ex2019pub_owa exch-server-02 443
bind serviceGroup svcgrp_ex2019pub_owa -monitorName mon_mail_owa
bind servicegroup svcgrp_ex2019pub_async exch-server-01 443
bind servicegroup svcgrp_ex2019pub_async exch-server-02 443
bind servicegroup svcgrp_ex2019pub_async -monitorName mon_mail_async
bind servicegroup svcgrp_ex2019pub_rpc exch-server-01 443
bind servicegroup svcgrp_ex2019pub_rpc exch-server-02 443
bind servicegroup svcgrp_ex2019pub_rpc -monitorName mon_mail_rpc
bind servicegroup svcgrp_ex2019pub_ews exch-server-01 443
bind servicegroup svcgrp_ex2019pub_ews exch-server-02 443
bind servicegroup svcgrp_ex2019pub_ews -monitorName mon_mail_ews
bind servicegroup svcgrp_ex2019pub_autodisover exch-server-01 443
bind servicegroup svcgrp_ex2019pub_autodisover exch-server-02 443
bind servicegroup svcgrp_ex2019pub_autodisover -monitorName mon_mail_autodiscover
bind servicegroup svcgrp_ex2019pub_oab exch-server-01 443
bind servicegroup svcgrp_ex2019pub_oab exch-server-02 443
bind servicegroup svcgrp_ex2019pub_oab -monitorName mon_mail_oab
bind servicegroup svcgrp_ex2019pub_mapi exch-server-01 443
bind servicegroup svcgrp_ex2019pub_mapi exch-server-02 443
bind servicegroup svcgrp_ex2019pub_mapi -monitorName mon_mail_mapi
bind servicegroup svcgrp_ex2019pub_ecp exch-server-01 443
bind servicegroup svcgrp_ex2019pub_ecp exch-server-02 443
bind servicegroup svcgrp_ex2019pub_ecp -monitorName mon_mail_ecp

Let’s create virtual servers and link the corresponding service groups to them

#Create Load Balancer
add lb vserver lb_vs_ex2019pub_owa SSL 0.0.0.0 0 -persistenceType NONE
add lb vserver lb_vs_ex2019pub_async SSL 0.0.0.0 0 -persistenceType SRCIPDESTIP
add lb vserver lb_vs_ex2019pub_rpc SSL 0.0.0.0 0 -persistenceType SOURCEIP -timeout 30
add lb vserver lb_vs_ex2019pub_ews SSL 0.0.0.0 0 -persistenceType NONE
add lb vserver lb_vs_ex2019pub_autodiscover SSL 0.0.0.0 0 -persistenceType NONE -timeout 30
add lb vserver lb_vs_ex2019pub_oab SSL 0.0.0.0 0 -persistenceType NONE
add lb vserver lb_vs_ex2019pub_mapi SSL 0.0.0.0 0 -persistenceType SOURCEIP -timeout 30
add lb vserver lb_vs_ex2019pub_ecp SSL 0.0.0.0 0 -persistenceType NONE

#Bind Service Groups to vServer
bind lb vserver lb_vs_ex2019pub_owa svcgrp_ex2019pub_owa
bind lb vserver lb_vs_ex2019pub_async svcgrp_ex2019pub_async
bind lb vserver lb_vs_ex2019pub_rpc svcgrp_ex2019pub_rpc
bind lb vserver lb_vs_ex2019pub_ews svcgrp_ex2019pub_ews
bind lb vserver lb_vs_ex2019pub_autodiscover svcgrp_ex2019pub_autodisover
bind lb vserver lb_vs_ex2019pub_oab svcgrp_ex2019pub_oab
bind lb vserver lb_vs_ex2019pub_mapi svcgrp_ex2019pub_mapi
bind lb vserver lb_vs_ex2019pub_ecp svcgrp_ex2019pub_ecp

#Bind SSL certificate
#Replace certificate name
bind ssl vserver lb_vs_ex2019pub_owa -certkeyName 'mail.example.com'
bind ssl vserver lb_vs_ex2019pub_async -certkeyName 'mail.example.com'
bind ssl vserver lb_vs_ex2019pub_rpc -certkeyName 'mail.example.com'
bind ssl vserver lb_vs_ex2019pub_ews -certkeyName 'mail.example.com'
bind ssl vserver lb_vs_ex2019pub_autodiscover -certkeyName 'mail.example.com'
bind ssl vserver lb_vs_ex2019pub_oab -certkeyName 'mail.example.com'
bind ssl vserver lb_vs_ex2019pub_mapi -certkeyName 'mail.example.com'
bind ssl vserver lb_vs_ex2019pub_ecp -certkeyName 'mail.example.com'

Let’s move on to a little more interesting – the content switcher. Let’s create policies based on the URL and link the appropriate policies to the appropriate virtual servers. The address 198.51.100.100 is a white address accessible from the Internet (just in case, I note that the network 198.51.100.0/24 is used in the examples in the documentation and is prohibited from routing) Webmail will be the default action.

#Create Content Switch
#Replace IP address of Content Switch
add cs vserver cs_ex2019pub_http HTTP 198.51.100.100 80
add cs vserver cs_ex2019pub_ssl SSL 198.51.100.100 443

#Replace certificate name
bind ssl vserver cs_ex2019pub_ssl -certkeyName 'mail.example.com'

#Create Content Switch Policies
add cs action cs_act_ex2019pub_owa -targetLBVserver lb_vs_ex2019pub_owa
add cs policy cs_pol_ex2019pub_owa -rule 'HTTP.REQ.URL.SET_TEXT_MODE(IGNORECASE).CONTAINS("/owa")' -action cs_act_ex2019pub_owa
add cs action cs_act_ex2019pub_ews -targetLBVserver lb_vs_ex2019pub_ews
add cs policy cs_pol_ex2019pub_ews -rule 'HTTP.REQ.URL.SET_TEXT_MODE(IGNORECASE).CONTAINS("/ews")' -action cs_act_ex2019pub_ews
add cs action cs_act_ex2019pub_autodiscover -targetLBVserver lb_vs_ex2019pub_autodiscover
add cs policy cs_pol_ex2019pub_autodiscover -rule 'HTTP.REQ.URL.SET_TEXT_MODE(IGNORECASE).CONTAINS("/autodiscover")' -action cs_act_ex2019pub_autodiscover
add cs action cs_act_ex2019pub_async -targetLBVserver lb_vs_ex2019pub_async
add cs policy cs_pol_ex2019pub_async -rule 'HTTP.REQ.URL.SET_TEXT_MODE(IGNORECASE).CONTAINS("/Microsoft-Server-ActiveSync")' -action cs_act_ex2019pub_async
add cs action cs_act_ex2019pub_oab -targetLBVserver lb_vs_ex2019pub_oab
add cs policy cs_pol_ex2019pub_oab -rule 'HTTP.REQ.URL.SET_TEXT_MODE(IGNORECASE).CONTAINS("/oab")' -action cs_act_ex2019pub_oab
add cs action cs_act_ex2019pub_mapi -targetLBVserver lb_vs_ex2019pub_mapi
add cs policy cs_pol_ex2019pub_mapi -rule 'HTTP.REQ.URL.SET_TEXT_MODE(IGNORECASE).CONTAINS("/mapi")' -action cs_act_ex2019pub_mapi
add cs action cs_act_ex2019pub_rpc -targetLBVserver lb_vs_ex2019pub_rpc
add cs policy cs_pol_ex2019pub_rpc -rule 'HTTP.REQ.URL.SET_TEXT_MODE(IGNORECASE).CONTAINS("/rpc")' -action cs_act_ex2019pub_rpc
add cs action cs_act_ex2019pub_ecp -targetLBVserver lb_vs_ex2019pub_ecp
add cs policy cs_pol_ex2019pub_ecp -rule 'HTTP.REQ.URL.SET_TEXT_MODE(IGNORECASE).CONTAINS("/ecp")' -action cs_act_ex2019pub_ecp
add cs policy cs_pol_ex2019pub_cgi -rule 'HTTP.REQ.URL.SET_TEXT_MODE(IGNORECASE).CONTAINS("/cgi")' -action cs_act_ex2019pub_owa


#Bind Content Switch Policies
bind cs vserver cs_ex2019pub_ssl -policyName cs_pol_ex2019pub_owa -priority 110
bind cs vserver cs_ex2019pub_ssl -policyName cs_pol_ex2019pub_ews -priority 120
bind cs vserver cs_ex2019pub_ssl -policyName cs_pol_ex2019pub_autodiscover -priority 130
bind cs vserver cs_ex2019pub_ssl -policyName cs_pol_ex2019pub_async -priority 140
bind cs vserver cs_ex2019pub_ssl -policyName cs_pol_ex2019pub_oab -priority 150
bind cs vserver cs_ex2019pub_ssl -policyName cs_pol_ex2019pub_mapi -priority 160
bind cs vserver cs_ex2019pub_ssl -policyName cs_pol_ex2019pub_rpc -priority 170
bind cs vserver cs_ex2019pub_ssl -policyName cs_pol_ex2019pub_ecp -priority 180
bind cs vserver cs_ex2019pub_ssl -policyName cs_pol_ex2019pub_cgi -priority 190

#default action
bind cs vserver cs_ex2019pub_ssl -lbvserver lb_vs_ex2019pub_owa

For port 80, the commands were not saved, since they were made from the web interface. The bottom line is that a resp_act_redirect_http_https_all responder is created, which redirects to the same url by changing http to https. The virtual server lb_vs_redirect_http_https_all has been created, to which this responder’s policy is linked. It should be something like this:

#Replace http to https
add responder action resp_act_redirect_http_https_all redirect '"https://"+HTTP.REQ.HOSTNAME+"/"'
add responder policy resp_pol_redirect_http_https_all 'true' resp_act_redirect_http_https_all

#default action
bind cs vserver cs_ex2019pub_http -lbvserver lb_vs_redirect_http_https_all

it is very important to save the configuration 🙂

save nsconfig

In general, in this form the system can already be used. Although for now it is a regular reverse proxy. Okay, let’s continue to complicate and wind.

2 Authentication and authorization server, ldap policy

svc_read_netscaler@example.local – service account in the domain with domain read permissions (the Domain Users group is enough)

192.168.3.11 – VIP address of the LDAP balancer, which is responsible for several domain controllers (in a simple case, the address of a domain controller)

“&(objectCategory=person)(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(proxyAddresses=*)” – LDAP filter that returns enabled user accounts that have mail on Exchange

maaa.examle.com is the address with the web form, which is redirected to for login/password verification. This is also a Netscaler entity and can act as SSO within Netscaler.

Let’s create LDAP servers and policies for these servers. Checked by sAMAccountName and checked by UserPrincipalName. In this case, two servers with disabled authentication will be needed in the next step.

# Create LDAP servers
add ldapAction auth_srv_ldap_ex2019pub_SAM -serverIP "192.168.3.11" -serverPort "389" -secType "TLS" -svrType "AD" -authTimeout 3 -ldapBindDn
"svc_read_netscaler@example.local" -ldapBase "DC=example,DC=local" -authentication enabled -ldapLoginName "sAMAccountName" -passwdChange ENABLED -
searchFilter "&(objectCategory=person)(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(proxyAddresses=*)" -groupAttrName "memberOf" -
subAttributeName "cn" -ssoNameAttribute "userPrincipalName" -email "mail" -requireUser YES -validateServerCert NO -nestedGroupExtraction OFF -followReferrals ON -
maxLDAPReferrals 1 -referralDNSLookup A-REC -nestedGroupExtraction ON -maxNestingLevel 2 -groupNameIdentifier "sAMAccountName" -groupSearchAttribute "memberOf" -
groupSearchSubAttribute "cn" -ldapbindDnPassword

add ldapAction auth_srv_ldap_ex2019pub_UPN -serverIP "192.168.3.11" -serverPort "389" -secType "TLS" -svrType "AD" -authTimeout 3 -ldapBindDn
"svc_read_netscaler@example.local" -ldapBase "DC=example,DC=local" -authentication enabled -ldapLoginName "userPrincipalName" -passwdChange ENABLED -
searchFilter "&(objectCategory=person)(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(proxyAddresses=*)" -groupAttrName "memberOf" -
subAttributeName "cn" -ssoNameAttribute "userPrincipalName" -email "mail" -requireUser YES -validateServerCert NO -nestedGroupExtraction OFF -followReferrals ON -
maxLDAPReferrals 1 -referralDNSLookup A-REC -nestedGroupExtraction ON -maxNestingLevel 2 -groupNameIdentifier "sAMAccountName" -groupSearchAttribute "memberOf" -
groupSearchSubAttribute "cn" -ldapbindDnPassword

add ldapAction auth_srv_ldap_ex2019pub_SAM_noAuth -serverIP "192.168.3.11" -serverPort "389" -secType "TLS" -svrType "AD" -authTimeout 3 -ldapBindDn
"svc_read_netscaler@example.local" -ldapBase "DC=example,DC=local" -authentication DISABLED -ldapLoginName "sAMAccountName" -passwdChange DISABLED -
searchFilter "&(objectCategory=person)(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(proxyAddresses=*)" -groupAttrName "memberOf" -
subAttributeName "cn" -ssoNameAttribute "userPrincipalName" -email "mail" -requireUser YES -validateServerCert NO -nestedGroupExtraction OFF -followReferrals ON -
maxLDAPReferrals 1 -referralDNSLookup A-REC -nestedGroupExtraction ON -maxNestingLevel 2 -groupNameIdentifier "sAMAccountName" -groupSearchAttribute "memberOf" -
groupSearchSubAttribute "cn" -ldapbindDnPassword

add ldapAction auth_srv_ldap_ex2019pub_UPN_noAuth -serverIP "192.168.3.11" -serverPort "389" -secType "TLS" -svrType "AD" -authTimeout 3 -ldapBindDn
"svc_read_netscaler@example.local" -ldapBase "DC=example,DC=local" -authentication DISABLED -ldapLoginName "userPrincipalName" -passwdChange
DISABLED -searchFilter "&(objectCategory=person)(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(proxyAddresses=*)" -groupAttrName "memberOf" -
subAttributeName "cn" -ssoNameAttribute "userPrincipalName" -email "mail" -requireUser YES -validateServerCert NO -nestedGroupExtraction OFF -followReferrals ON -
maxLDAPReferrals 1 -referralDNSLookup A-REC -nestedGroupExtraction ON -maxNestingLevel 2 -groupNameIdentifier "sAMAccountName" -groupSearchAttribute "memberOf" -
groupSearchSubAttribute "cn" -ldapbindDnPassword


#Create LDAP advanced policy
add authentication policy advauth_pol_ldap_ex2019pub_SAM -rule true -action auth_srv_ldap_ex2019pub_SAM
add authentication policy advauth_pol_ldap_ex2019pub_UPN -rule true -action auth_srv_ldap_ex2019pub_UPN
add authentication policy advauth_pol_ldap_ex2019pub_SAM_noAuth -rule true -action auth_srv_ldap_ex2019pub_SAM_noAuth
add authentication policy advauth_pol_ldap_ex2019pub_UPN_noAuth -rule true -action auth_srv_ldap_ex2019pub_UPN_noAuth

Now let’s create an AAA server and bind two of the above policies to it. Just this AAA server is engaged in password protection – this is AAA’s regular functionality

#Create AAA VServer
add authentication vserver aaa_vs_ex2019pub_ldap SSL 0.0.0.0 -maxLoginAttempts 3 -failedLoginTimeout 5
bind ssl vserver aaa_vs_ex2019pub_ldap -certkeyName 'mail.example.com'
bind authentication vserver aaa_vs_ex2019pub_ldap -portaltheme StoreFrontLogOnTheme


#Bind LDAP Policies to AAA vserver
bind authentication vserver aaa_vs_ex2019pub_ldap -policy advauth_pol_ldap_ex2019pub_SAM -priority 200 -gotoPriorityExpression NEXT
bind authentication vserver aaa_vs_ex2019pub_ldap -policy advauth_pol_ldap_ex2019pub_UPN -priority 210 -gotoPriorityExpression NEXT

Servers have been created. Now let’s define session policies and link them to the AAA server

#Create AAA Session Policy
add tm sessionAction tm_act_ex2019pub_owa_sso -defaultAuthorization ALLOW -SSO ON -ssoDomain 'examle.local'

#add tm sessionPolicy tm_pol_ex2019pub_owa_sso_v2 'HTTP.REQ.URL.SET_TEXT_MODE(IGNORECASE).CONTAINS("/owa/auth/logon.aspx")' tm_act_ex2019pub_owa_sso
add tm sessionPolicy tm_pol_ex2019pub_owa_sso 'ns_true' tm_act_ex2019pub_owa_sso

#Create SSO Form and Policies
add tm formSSOAction sso_profile_ex2019pub_owa -actionURL "/owa/auth.owa" -userField "username" -passwdField "password" -responsesize "60000" -ssoSuccessRule
'HTTP.RES.SET_COOKIE.COOKIE("cadata").VALUE("cadata").LENGTH.GT(70)' -nvtype DYNAMIC -submitMethod POST
add tm trafficAction traffic_prof_ex2019pub_owa -SSO ON -appTimeout 1 -formSSOAction sso_profile_ex2019pub_owa
add tm trafficAction traffic_prof_ex2019pub_owa_logout -InitiateLogout ON


add tm trafficPolicy traffic_pol_ex2019pub_owa 'HTTP.REQ.URL.SET_TEXT_MODE(IGNORECASE).CONTAINS("/owa/auth/logon.aspx")' traffic_prof_ex2019pub_owa
add tm trafficPolicy traffic_pol_ex2019pub_owa_logout 'HTTP.REQ.URL.SET_TEXT_MODE(IGNORECASE).CONTAINS("/owa/logoff.owa")' traffic_prof_ex2019pub_owa_logout

#Bind Traffic Session Policy to the AAA vServer
bind authentication vserver aaa_vs_ex2019pub_ldap -policy tm_pol_ex2019pub_owa_sso -priority 100 -gotoPriorityExpression NEXT

Next, you need to add a rule to the content switcher created in the first part so that the switcher understands how to deal with requests going to maaa.examle.com

#Content Switch for AAA
#Replace AuthenticationHost FQDN
add cs action cs_act_ex2019pub_maaa -targetVserver aaa_vs_ex2019pub_ldap
add cs policy cs_pol_ex2019pub_maaa -rule 'HTTP.REQ.HOSTNAME.SET_TEXT_MODE(IGNORECASE).EQ("maaa.examle.com")' -action cs_act_ex2019pub_maaa
bind cs vserver cs_ex2019pub_ssl -policyName cs_pol_ex2019pub_maaa -priority 90

It remains to turn the trousers into shorts with a slight movement of the hand, and if this did not work out, then bind the created AAA server to the previously created (in the first part) virtual servers. We bind the web form of authentication to web mail and the control panel, and basic authentication to the rest of the protocols.

Again, I note that in Exchange 2016/2019, ECP should not be released to the outside.

#Bind SSO Policies to the OWA vServer
bind lb vserver lb_vs_ex2019pub_owa -policyName traffic_pol_ex2019pub_owa -priority 100 -gotoPriorityExpression END -type REQUEST
bind lb vserver lb_vs_ex2019pub_owa -policyName traffic_pol_ex2019pub_owa_logout -priority 110 -gotoPriorityExpression END -type REQUEST

#Set FBA and 401 authentication
#Replace AuthenticationHost FQDN
set lb vserver lb_vs_ex2019pub_owa -Authentication ON -authnVsName aaa_vs_ex2019pub_ldap -AuthenticationHost maaa.examle.com
set lb vserver lb_vs_ex2019pub_ecp -Authentication ON -authnVsName aaa_vs_ex2019pub_ldap -AuthenticationHost maaa.examle.com
set lb vserver lb_vs_ex2019pub_async -authn401 ON -authnVsName aaa_vs_ex2019pub_ldap
set lb vserver lb_vs_ex2019pub_rpc -authn401 ON -authnVsName aaa_vs_ex2019pub_ldap
set lb vserver lb_vs_ex2019pub_ews -authn401 ON -authnVsName aaa_vs_ex2019pub_ldap
set lb vserver lb_vs_ex2019pub_oab -authn401 ON -authnVsName aaa_vs_ex2019pub_ldap
set lb vserver lb_vs_ex2019pub_mapi -authn401 ON -authnVsName aaa_vs_ex2019pub_ldap
set lb vserver lb_vs_ex2019pub_autodiscover -authn401 ON -authnVsName aaa_vs_ex2019pub_ldap

and of course, we save save ns config, in vain did they play Doom in childhood.

Wonderful! Interception of authentication by a web form and basic. But this implementation also needs further refinement, since basic does not suit us, but we imagined that we are specialists and we need to do everything right. So we keep making it harder.

3 Negotiate

Cyrillic logins do NOT work, only Latin. We implement NTLM authentication interception, which is necessary in order to further configure authorization. To do this, I used the functionality of two-factor authentication. The second factor is hidden from the user and is the extraction of groups on the account. Applied to RPC, OAB, EWS, MAPI, AutoDiscover services

svc_masa19 – domain account with limited delegation rights.

https://mail.example.local/mapi/ – resource where the mail server is available. (Not described in this article, but the bottom line is that the name mail.example.local lives on the balancer and several servers are responsible for this name)

Let’s get started. Let’s create the svc_masa19 account in Active Directory (for the curious, this stands for mail alternate service account)

Let’s create a KCD account and a policy for it on Netscaler. Here, in the example, there will be a way to create an account by password, although in decent societies it is customary to transfer such accounts to a third-party system using a keytab file, which is quite supported by Netscaler.

# Create kerberos account and session policy for SSO
add aaa kcdAccount kcd_acc_svc_masa19 -realmStr EXAMPLE.LOCAL -delegatedUser svc_masa19 -kcdPassword

add tm sessionAction tm_act_ex2019pub_kcd -defaultAuthorization ALLOW -SSO ON -ssoDomain 'EXAMPLE.LOCAL' -sessTimeout 10 -ssoCredential PRIMARY -httpOnlyCookie NO -kcdAccount kcd_acc_svc_masa19
add tm sessionPolicy tm_pol_ex2019pub_kcd 'ns_true' tm_act_ex2019pub_kcd

There will be some magic next. First, let’s create an action and a matching policy. Then we will create a scheme for a login scheme and labels for it. Let’s link to the unauthenticated LDAP labels created in the second step. This is necessary in order to get a list of groups on the account.

After that, we will create another AAA server, to which we will link the negotiation authentication and indicate that the label we just created should be used as the second factor.

# Add Negotiate authentication advanced policy and action
add negotiateAction neg_srv_ex2019pub_svc_masa19 -domain EXAMPLE.LOCAL -domainUser svc_masa19 -NTLMPath "https://mail.example.local/mapi/" -
domainUserPasswd
add authentication policy advauth_pol_neg_ex2019pub_svc_masa19 -rule true -action neg_srv_ex2019pub_svc_masa19

# Create login schema and policy label
add authentication loginSchema login_ex2019pub_ldap -authenticationSchema noschema
add authentication policyLabel pol_lab_ex2019pub_ldap -loginSchema login_ex2019pub_ldap

#Bind LDAP policy to policy label
bind authentication policylabel pol_lab_ex2019pub_ldap -policyName advauth_pol_ldap_ex2019pub_SAM_noAuth -priority 200 -gotoPriorityExpression NEXT
bind authentication policylabel pol_lab_ex2019pub_ldap -policyName advauth_pol_ldap_ex2019pub_UPN_noAuth -priority 210 -gotoPriorityExpression NEXT

# Create a new AAA vserver that uses KCD and bind the cert
add authentication vserver aaa_vs_ex2019pub_negotiate_2fa SSL 0.0.0.0 -maxLoginAttempts 3 -failedLoginTimeout 5
bind ssl vserver aaa_vs_ex2019pub_negotiate_2fa -certkeyName 'mail.example.com'

# bind the negotiate and SSO policies to the new AAA Server
bind authentication vserver aaa_vs_ex2019pub_negotiate_2fa -policy tm_pol_ex2019pub_kcd -priority 100
bind authentication vserver aaa_vs_ex2019pub_negotiate_2fa -policy advauth_pol_neg_ex2019pub_svc_masa19 -priority 100 -gotoPriorityExpression NEXT -nextFactor pol_lab_ex2019pub_ldap

It remains to link one to the other, namely the AAA authentication server, by agreement, link to the virtual servers RPC, MAPI, OAB, EWS, Autodiscover

# bind the new AAA Server to the Load balancers that need KCD
set lb vserver lb_vs_ex2019pub_mapi -authnVsName aaa_vs_ex2019pub_negotiate_2fa
set lb vserver lb_vs_ex2019pub_rpc -authnVsName aaa_vs_ex2019pub_negotiate_2fa
set lb vserver lb_vs_ex2019pub_ews -authnVsName aaa_vs_ex2019pub_negotiate_2fa
set lb vserver lb_vs_ex2019pub_oab -authnVsName aaa_vs_ex2019pub_negotiate_2fa
set lb vserver lb_vs_ex2019pub_autodiscover -authnVsName aaa_vs_ex2019pub_negotiate_2fa

remember about save ns config

For the svc_masa19 account, settings must be made. Add Delegation

Exchange servers must allow negotiation authentication

Get-OutlookAnywhere -server exch-server-01 | Set-OutlookAnywhere -IISAuthenticationMethods Negotiate,Ntlm,Basic

It would seem that the problem is solved. If suddenly someone has read up to this point and is still attentive, he noticed that the task from the security service has not yet been solved, namely, to let in who can and not let in, who cannot. That’s why

4 Authorization

There is a deny_mail.example.com_access group in Active Directory, whose members are prohibited from connecting to mail from outside.

Let’s create an authorization policy and link it to all virtual servers serving web protocols

#add and bind authorization policy
add authorization policy pol_auth_mail.example.com_deny "AAA.USER.IS_MEMBER_OF(\"deny_mail.example.com_access\")" DENY

# authorization on base and negotiate vservers
bind lb vs lb_vs_ex2019pub_async -policyName pol_auth_mail.example.com_deny -priority 100 -gotoPriorityExpression END -type REQUEST
bind lb vs lb_vs_ex2019pub_rpc -policyName pol_auth_mail.example.com_deny -priority 100 -gotoPriorityExpression END -type REQUEST
bind lb vs lb_vs_ex2019pub_ews -policyName pol_auth_mail.example.com_deny -priority 100 -gotoPriorityExpression END -type REQUEST
bind lb vs lb_vs_ex2019pub_oab -policyName pol_auth_mail.example.com_deny -priority 100 -gotoPriorityExpression END -type REQUEST
bind lb vs lb_vs_ex2019pub_mapi -policyName pol_auth_mail.example.com_deny -priority 100 -gotoPriorityExpression END -type REQUEST
bind lb vs lb_vs_ex2019pub_autodiscover -policyName pol_auth_mail.example.com_deny -priority 100 -gotoPriorityExpression END -type REQUEST

Let’s add a little courtesy, notify the user that he is denied entry. To do this, create a simple html page

Go AppExpert\Responder\Responder HTML Pages
Manually create the mail.example.com_deny page. I insist not to overclock with formatting and get by with text and paragraph or line break tags.

Then we will bind the output of this page to the corresponding criteria

#add responder policy and action
add responder action resp_act_ex2019pub_denypage respondwithhtmlpage mail.example.com_deny -responseStatusCode 200 -reasonPhrase '\"Access denied. Contact to your servicedesk.\"'
add responder policy resp_pol_ex2019pub_denypage 'AAA.USER.IS_MEMBER_OF(\"deny_mail.example.com_access\") && HTTP.REQ.URL.SET_TEXT_MODE(IGNORECASE).CONTAINS(\"/owa/logoff.owa\").NOT' resp_act_ex2019pub_denypage

#bind policy to vservers
bind lb vs lb_vs_ex2019pub_owa -policyName resp_pol_ex2019pub_denypage -priority 100 -gotoPriorityExpression END -type REQUEST
bind lb vs lb_vs_ex2019pub_ecp -policyName resp_pol_ex2019pub_denypage -priority 100 -gotoPriorityExpression END -type REQUEST

save nsconfig

Now it’s really done


Links that pointed to the solution

Not the easiest to read, but I think this article is the most complete.

More good articles to understand the process

https://github.com/slauger/netscaler_docs/blob/master/exchange2016.adoc
https://support.citrix.com/article/CTX222454 – How to Configure the NetScaler for Kerberos Constrained Delegation

And a lot of documentation on the Citrix website and on https://www.carlstalhood.com/

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *