Однажды в службе информационной безопасности решили, что не только лишь все должны иметь доступ к электронной почте снаружи периметра. Начали искать способы и поняли, что нам нужен продукт класса ADC. Решили попробовать реализовать с помощью имеющего у нас Citrix Netscaler, который берёт на себя функцию аутентификации и проверят, можно ли данному сотруднику разрешить доступ в почтовую систему. Получилось. Не зря система такая дорогая. Будет длинная статья-инструкция.

Статей на тему того, как подружить Microsoft Excnage Server и Cirtix Netscaler не то, чтобы на каждом углу, но найти можно. Все найденные статьи не закрывали поставленную задачу. В основном статьи заканчивались на переключателе контента (переключатель контента можно реализовать на HAProxy). Некоторые статьи описывали перехват веб-формы. Нам же необходимо было запретить доступ на основе членства в группе Active Directory. Получается, что мы сами себе придумали, что мы хотим совместить аутентификацию и авторизацию в одном флаконе и на одном экране логина.

Виртуальные каталоги Exchange для веб-почты используют стандартную аутентификацию (не на основе форм). Т.е. и для внутренних и для внешних подключений используются одни и те же почтовые сервера.

Как-то так получилось, что я "осторожно, сертифицированный специалист" и по одному и по другому продукту. В общем, списывать было не у кого, пришлось делать домашку самому.

Давайте сразу договоримся, что далее в тексте будем использовать 198.51.100.0/24 для внешних (белых) сетей, 192.168.0.0/16 для внутренних сетей. Пространство имён example.com для имён, доступных из сети интернет и examle.local для именования внутренних серверов.

Принцип работы такой:

Content Switch принимает соединение и передаёт его соответствующему виртуальному серверу. Виртуальный сервер производит аутентификацию. Проверка членства в группе производится с помощью механизма многофакторной аутентификации. Второй фактор не требует участия пользователя и происходит фоном. Если проверка пройдена, то пускаем, если нет - показываем пользователю сообщение, что ему в доступе отказано.

Попутно защищаем пользователя от перебора пароля. Если честно, то именно защита от перебора пароля была для ИТ основной задачей, поскольку именно к ИТ приходят пользователи, которые сменили пароль и забыли поменять его на телефоне, тем самым вызывая блокировку учётной записи.

Схему взаимосвязи сущностей Netscaler постарался отразить на изображении ниже. Какие-то детали мог упустить

Пойдём уже настраивать Netscaler. Будем делать это поэтапно. Пояснения будут по ходу.

Пояснения

Citrix Netscaler с версии 13 называется Citrix ADC

Стоит отметить, что для Exchnage 2013 необходимо опубликовать и OWA и ECP, а для Exchange 2016/2019 только OWA

Если у вас гибрид и Teams, то нельзя делать предаутентификацию на EWS (перестанет работать календарь в Teams)

В нашем примере сервера Exchange именуются exch-server-nn и находятся в сети 192.168.9.0/24. Если у вас Exchange 2013, то exch-server-nn - это сервера с ролью ClientAccess. Внутренний домен EXAMPLE.LOCAL, внешнее имя mail.example.com

1 Основные настройки

Создадим базовые сущности, необходимые для работы, а именно сервера, сервис-группы, мониторы, переключатель контента

#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

Соединяем одно с другим, а именно линкуем сервера и мониторы к группам

#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

Создадим виртуальные сервера и прилинкуем к ним соответствующие сервисные группы

#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'

Переходим к чуть более интересному - переключатель контента. Создадим политики на основании URL и прилинкуем соответствующие политики к соответствующим виртуальным серверам. Адрес 198.51.100.100 - это белый адрес, доступный из сети интернет (на всякий случай отмечу, что сеть 198.51.100.0/24 используется в примерах в документации и запрещена к маршрутизации) В качестве действия по умолчанию будет веб-почта.

#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

Для 80 порта команды не сохранились, поскольку делались из веб-интерфейса. Суть заключается в том, что создаётся ответчик resp_act_redirect_http_https_all, который перенаправляет на тот же самый url меняя http на https. Создан виртуальный сервер lb_vs_redirect_http_https_all, к которому прилинкована политика этого ответчика. Должно быть примерно так:

#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

очень важно сохранить конфигурацию :)

save ns config

В общем-то, в данном виде система уже может быть использована. Хотя на сейчас она представляет собой обычный обратный прокси. Хорошо, продолжим усложнять и накручивать.

2 Сервер аутентификации и авторизации, политика ldap

svc_read_netscaler@example.local - сервисная учётная запись в домене с правами чтения домена (достаточно группы Domain Users)

192.168.3.11 - VIP адрес балансировщика LDAP, за которым отвечают несколько котроллеров домена (в простом случае адрес контроллера домена)

"&(objectCategory=person)(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(proxyAddresses=*)" - LDAP фильтр, который выдаёт включенные учётные записи пользователей, имеющих почту на Exchange

maaa.examle.com - это адрес с веб-формой, на который происходит перенаправление для проверки логина/пароля. Это тоже сущность Netscaler, может выступать в роли SSO в пределах Netscaler.

Создадим LDAP сервера и политики к этим серверам. С проверкой по sAMAccountName и с проверкой по UserPrincipalName. При этом два сервера с отключенной аутентификацией понадобятся на следующем этапе.

# 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

Теперь создадим AAA сервер и привяжем к нему две из вышесозданных политик. Как раз этот AAA сервер занимается защитой от перебора пароля - это у AAA штатный функционал

#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

Сервера созданы. Теперь определим политики сессий и прилинкуем их к AAA серверу

#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

Далее необходимо добавить правило в переключатель контента, созданный а первой части, чтобы переключатель понимал, как поступать с запросами, идущими на 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

Осталось лёгким движением руки превратить брюки в шорты, а если это не получилось, то привязать созданный AAA сервер к ранее созданным (в первой части) виртуальным серверам. К веб почте и панели управления привязываем веб форму аутентификации, к остальным протоколам basic аутентификацию.

Снова отмечу, что в Exchange 2016/2019 не следует выпускать наружу ECP.

#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

ну и конечно же сохраняемся save ns config, зря что ли в детстве в Doom играли.

Прекрасно! Настроен перехват аутентификации веб формой и basic. Но и эта реализация требует дальнейшей доработки, поскольку basic нас не устраивает, мы же возомнили себе, что мы специалисты и нам надо всё делать правильно. Поэтому продолжаем усложнять.

3 Negotiate

Кириллические логины НЕ работают, только латиница. Реализуем перехват аутентификации по NTLM, который необходим для того, чтобы в дальнейшем настроить авторизацию. Для этого использовал функционал двухфакторной аутентификации. Второй фактор скрыт от пользователя и представляет из себя извлечение групп на учётной записи. Применён на сервисы RPC, OAB, EWS, MAPI, AutoDiscover

svc_masa19 - учётная запись в домене с правами ограниченного делегирования.

https://mail.example.local/mapi/ - ресурс, по которому доступен почтовый сервер. (В данной статье не описано, но суть в том, что имя mail.example.local живёт на балансировщике и за этим именем отвечает несколько серверов)

Приступим. Создадим в Active Directory учётную запись svc_masa19 (для любопытных это расшифровывается как mail alternate service account)

На Nеtscaler создадим KCD аккаунт и политику к нему. Здесь в примере будет способ заведения учётной записи по паролю, хотя в приличных обществах такие учётные записи принято передавать в стороннюю систему с помощью keytab файла, что вполне себе поддерживает 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

Дальше будет немного магии. Для начала создадим действие и политику согласования. Затем создадим схему схему логина и метки к ней. Прилинкуем к меткам LDAP без проверки подлинности, созданные на втором этапе. Это нужно для того, чтобы получить список групп на учётной записи.

После чего создадим ещё один AAA сервер, к которому прилинкуем проверку подлинности по согласованию и укажем, что в качестве второго фактора использовать созданную только что метку.

# 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

Остаётся прилинковать одно к другому, а именно AAA сервер аутентификации по согласованию прилинковать к виртуальным серверам 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

помним про save ns config

Для учётной записи svc_masa19 необходимо произвести настройки. Добавить делегирование

На Exchange серверах необходимо разрешить аутентификацию согласованием

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

Казалось бы всё, задача решена. Если вдруг кто-то дочитал до этого места и всё ещё внимателен, то заметил, что до сих пор не решена задача от службы безопасности, а именно, пускать кого можно и не пускать, кого нельзя. Поэтому

4 Авторизация

В Active Directory есть группа deny_mail.example.com_access, членам которой запрещено подключаться к почте из вне.

Создадим политику авторизации и прилинкуем её ко всем виртуальным серверам, обслуживающим веб протоколы

#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

Добавим немного вежливости, уведомим пользователя, что ему отказано во входе. Для этого создадим простую html страничку

Перейти AppExpert\Responder\Responder HTML Pages
Вручную создать страницу mail.example.com_deny. Настаиваю не разгоняться с форматированием и обойтись текстом и тегами абзаца или обрыва строки.

После чего привяжем вывод этой страницы к соответствующим критериям

#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 ns config

Вот теперь действительно готово


Ссылки, которые навели на решение

Не самой простой для чтения, но наиболее полной мне кажется эта статья

Ещё неплохие статьи для понимая процесса

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

И ещё куча документации на сайте Citrix и на https://www.carlstalhood.com/

Комментарии (0)