Introduction and Background
As an introductory disclaimer, I alone did not devise this solution, but merely completed its development in its latest iteration. Much of the legwork was developed by an expert team of Citrix Consulting and Citrix ADC Engineering professionals over several iterations for a customer with unique constraints, which prevented them from deploying Citrix Federated Authentication Service (FAS).
Update April 27th 2020, I’ve released a similar article on Okta SAML auth at Citrix Gateway without FAS, but using Okta’s LDAP POST via SWA functions instead of an ADC-hosted IDP.
I should note before we get too far along that this alternative solution has a hard requirement of Citrix ADC standing in as an on-prem IDP (in lieu of ADFS for example), and is therefore not universally suitable for all customers. With that said, this requirement is easy enough to work around, if willing to use a separate domain name for the Citrix portal. I.e., if the main domain of company.com is already federated in Azure AD with ADFS, registering company.net or companyapps.com and federating that domain in Azure AD with the ADC-hosted IDP could work around that. Users would just need to be aware of the proper URL such as myapps.company.net vs. myapps.company.com (or create a helpful redirect for myapps.company.com to myapps.company.net).
Furthermore, this guide, in theory, should work for other SAML solutions beyond Azure AD, but I have yet to test drive this.
Starting off with a little bit of background, customers seeking to leverage SAML authentication at Citrix Gateway by default tend to need some additional Citrix components. Citrix FAS enables users to authenticate via SAML in order to maintain a single sign-on (SSO) experience to their Citrix resources once logged in, just as when using LDAP.
Why do we need FAS? Because SAML isn’t natively spoken by Windows. So if it’s your only authentication method at Citrix Gateway (i.e you’re not using it in combo with LDAP) you’re not going to log into Citrix Gateway and SSO into your Citrix resources. A user authenticating via SAML at Citrix Gateway would be passed through to Citrix StoreFront but would get a second Windows login prompt when launching the app or desktop in absence of FAS. If you’re interested in Citrix FAS (which remains the “lead with” strategy for SAML auth to Citrix resources), I suggest checking out Carl Stalhood’s FAS article, which is as always, a thorough walkthrough.
Side note The Beer Drinker’s Guide to SAML provides a hilarious and layman’s term overview of SAML for those who do not yet fully grasp its constructs or how it works. Highly recommended.
SAML not providing SSO out of the box with Citrix isn’t a Citrix issue, it’s a Windows limitation. That’s why FAS was conceived. It bridges the gap between SAML and Windows-native authentication methods. In the case of FAS, it’s using certificates as that mechanism (or more specifically virtual smart cards).
For some customers, FAS may not be possible due to various direct or indirect reasons. In the case of the particular customer this solution was developed for, they had the following challenges for their SAML solution of choice (Azure AD \ Azure MFA):
- Due to corporate policy, cannot sync passwords nor password hashes into Azure AD.
- Due to an Active Directory limitation in their environment, unable to leverage ADFS as an IDP for Azure AD to interface with, which could help overcome the prior point.
- Not able to use Microsoft Network Policy Server (NPS) with the Azure MFA extension. This legacy mode does not allow for conditional access policies which is a non-starter for some customers.
On account of the first two points, a solution was devised using a Citrix ADC-hosted IDP AAA-TM vServer to stand in for ADFS, and federating Azure AD with this domain using the IDP. With this in place, we have means to capture the user’s LDAP credentials when authenticating to the IDP and replay them during a later authentication sequence in an LDAP policy, thus achieving SSO to Citrix resources. We achieve this store and retrieval logic by using the Citrix ADC variables and assignment function which stores data in memory (encrypted if specified) for later retrieval.
The purpose of this article is to walk through the setup of this solution. ADC CLI commands to build out the configuration (swapping in one’s own IPs, certs, names, etc.) are included at the end of the article.
For the purposes of this article, I have labbed out the solution with gateway.ferroque.dev as the Citrix Gateway, and idp.ferroque.dev as the Citrix ADC-hosted IDP (AAA-TM vServer). The authentication flow is as so:
- User connects to https://gateway.ferroque.dev.
- Citrix ADC evaluates the first authentication policy bound to the authentication vServer in the authentication profile associated to the Citrix Gateway vServer.
- Citrix ADC sends a SAML request to Azure AD (SAML Request # 1). In this initial sequence, the Citrix ADC is acting as a SAML Service Provider (SP) and Azure AD is acting as an Identity Provider (IdP).
- Azure AD upon receiving SAML Request # 1 sends a new SAML request to Citrix ADC. (SAML Request # 2). Here, Azure AD is acting as a SAML SP and Citrix ADC is acting as an IDP.
- Azure AD redirects the user to https://idp.ferroque.dev as per the federation configurations for the domain, and is prompted for AD credentials (sAMAccountName format in this scenario, but could accommodate for UPN as well). Citrix ADC validates LDAP credentials.
- Citrix ADC invokes a global variable and assignment configuration to store the user credentials for up to 1 hour before expiring them. These credentials are stored in a map in memory and are encrypted so they remain obfuscated in event for a core dump.
- Upon successful authentication, Citrix ADC evaluates SAML IdP policy.
- Citrix ADC sends a SAML response or assertion to Azure AD (Response to SAML Request #2).
- Azure AD applies conditional access policies, multi-factor authentication, etc.
- If MFA is successful, Azure AD sends a SAML assertion to Citrix ADC as a (Response to SAML Request #1).
- Citrix ADC evaluates LDAP credentials (using a second LDAP server using UPN) such that they are the last credentials checked for SSO, using a login schema configured to extract the previously stored password from step #6.
- User is authenticated to https://gateway.ferroque.dev and passes through to StoreFront.
A high-level flow looks like this:
LDAP Auth –> Store Credentials –> MFA –> Retrieve Credentials –> SSO to Citrix Gateway (and subsequently to StoreFront and Citrix apps)
As you might have guessed, this solution will use nFactor in Citrix ADC on the AAA vServers. The diagram below outlines the various authentication components involved in this solution which will be built out later in this guide:
For the purposes of this article, the following assumptions are being made:
- A Citrix ADC 13.0 and Citrix Gateway vServer already built and integrated into a Citrix Virtual Apps & Desktops (CVAD) environment (StoreFront, Citrix Site).
- Functional Windows domain and a service account for LDAP.
- A domain not yet enabled for federation added to Azure AD (custom domain) and has been ‘verified’.
- The ability to have Citrix ADC act as an IDP for the user domain (i.e. another solution such as ADFS is not already doing this for the domain).
- TLS certificate (SAN, named, or wildcard) to cover two AAA-TM vServers, Citrix Gateway vServer.
- Public key certificate for the IDP AAA-TM vServer for use in IDP federation process between Azure AD and Azure MFA
- Sufficient rights in Azure AD to federate a domain.
- An IP for the IDP AAA-TM.
- An Enterprise Application configured for SAML authentication for use by our Citrix Gateway.
- Citrix ADC Advanced (formerly Enterprise) or above license.
Step 1 – Create Variables and Assignments
Variables are a powerful AppExpert function on Citrix ADC which allows the storing of data within memory for a period of time and can be called upon by referencing an assignment corresponding to the variable. In our use case, we’re building a variable map for username and password, captured from the initial LDAP authentication the user experiences at the Citrix-owned IDP. Using variables allows us to call on credentials for re-use between authentication sessions. For more information on variables, please reference Configuring and Using Variables.
We’ll first start by creating the variable itself. The following screens detail the key inputs needed. The values will be stored in appliance memory so be mindful of this when sizing inputs. In the below example, we have a key and value length totalling 512 bytes, and up to 1,000 entries permitted within a 1 hour period max. As we’ll be encrypting and there will surely be some metadata and overhead, multiplying by 1.1x should give us enough of a buffer. 512 x 1000 x 1.1 = 563,200 bytes or 0.5 MB.
We’ll now create the assignment to pair with the variable. The variable shown below will be what we use to call upon the key data for credential replay later. We are encrypting the stored credentials within appliance memory to obfuscate the passwords should a core dump need to be sent to a third party for analysis (Citrix Support, etc.). The key expression will use the sAMAccountName the user enters into the first LDAP prompt at the ADC-owned IDP.
Step 2 – Create LDAP Servers
This solution uses two different LDAP servers for two different phases of the authentication sequence. The customer this was developed for wanted to permit users to log in with UPN or sAMAccountName. For the purposes of this article, we’re sticking with sAMAccoutName for simplicity. Adding UPN as a secondary authentication server for initial login is not a significant ordeal, however.
The second LDAP server we call the “SSO” server. Assuming we’re getting a NameID\UPN from the Azure AD to Citrix Gateway AAA vServer in the second half of the auth sequence we use an LDAP server configured with the Server Logon Attribute of userPrincipalName to correctly look up and authenticate the user. This will also be the credential pair passed over to StoreFront.
Step 3 – Create SAML IDP Profile, SAML SP Server, and IDP Policies
We’ll now build the SAML IDP configuration the ADC will use. We will bind this to the appropriate IDP AAA-TM vServer in the subsequent step.
- Assertion Consumer Service Url will always be for Azure: https://login.microsoftonline.com/login.srf
- Service Provider Logout URL will be FQDN of your IDP followed by cgi/tmlogout
- For the IDP Certificate Name, bind the IDP certificate (i.e. one you have a private key for, the same one you will bind to your ADC-owned IDP AAA vServer. This is NOT the Azure IDP signing certificate!).
- Audience: Will always be: urn:federation:MicrosoftOnline
- Leave skew time default at 5 minutes
- Set Name ID Expression to: AAA.USER.ATTRIBUTE(2).B64ENCODE
- Set Sign Assertion to Assertion
- Change Signature Algorithm to RSA-SHA256, SHA256
- Define the Attribute 1 values as shown below
We’ll then create a SAML IDP policy linking to our newly created IDP profile. The expression will look explicitly the Microsoft Online URL:
If not already completed, go ahead and build out the Azure AD enterprise application configuration as one would if federating Citrix ADC with FAS. This article will not go through those details, plenty others do, but here is a screenshot of the lab config. Be sure to assign users. Be sure to configure the SAML SP server to use the certificate downloaded from Azure for the IDP certificate (not the certificate of the ADC-owned IDP).
Now we move on to create the SAML SP profile which the Citrix Gateway’s AAA vServer will use as the first authentication factor. This is where the enterprise app details created previously will be used. The Azure enterprise app IDP certificate should be downloaded and installed on the ADC. It may show up under the “Unknown” certificate store once installed.
- Redirect URL is the Azure enterprise application’s “Login URL” provided at the time of app creation. Append ?whr=yourdomain.com after /saml2 if you have more than one domain and need to auto-select the domain for the user to reduce login steps such as hitting Azure, being asked for email, then being redirected to IDP and asked to enter email again plus password.
- Bind the Signing Certificate provided by the Azure enterprise application config as “IDP Certificate Name”.
- Although the config will default to NameID for the user field, enter it anyway.
- Select the signing certificate which in this case will be the same TLS certificate you bound to the Citrix Gateway and its non-addressable vServer.
- Input the Issuer Name and Audience parameters, which should be the URL of the Citrix Gateway users are logging into.
- Tune the Signature Algorithms as shown.
- Check off Force Authentication.
- Implement the attributes as shown.
With the SAML SP server out of the way, we’ll create the advanced authentication SAML SP policy linking to the server of type “SAML”. In this example, we’re calling it saml_sp_policy_to_aad_idp. The expression to be used is “HTTP.REQ.COOKIE.NAME_VALUE(“NSC_TASS”).CONTAINS(“idp.ferroque.dev”).NOT” and the policy will link to SAML_MSFT_SRV as the action.
Step 4 – Building AAA-TM vServers and Auth Profiles
The solution requires two AAA-TM vServers. The first one is a non-addressable AAA vServer which will be linked to the Citrix Gateway vServer. This AAA flow uses Azure AD SAML policy, followed by an LDAP factor (second LDAP aka SSO LDAP) that seamlessly passes through credentials stored in a variable.
The second AAA vServer needs to have an IP address and be reachable by users. It performs the role of SAML IDP, as well as the first LDAP factor. Post-LDAP authentication, the last, invisible factor stores the credentials in a variable value.
Build out two generic AAA vServers as shown below and harden to org. standards. Use the server certificate of the Citrix Gateway on the AAA_GATEWAYNOFAS vServer, and use an appropriate server certificate on the AAA_IDP vServer. Note the public key of the AAA_IDP’s certificate will be needed to create the Azure AD federation task in step 10.
Then create the authentication profile.
Step 5 – Create Login Schemas
Create two Login Schema profiles. Both will use a noschema schema. Both are used on LDAP factors, and one is used to store the LDAP credentials from the initial LDAP authentication (sAMAccountName in our example) and the second one for retrieval of those credentials and used in conjunction with the second LDAP authentication (for SSO using UPN).
StoreCreds_LS does not require any user or password expression entire and could just as easily be substituted for built-in LSCHEMA_INT.
The “PreFillUsernamePassword_LS” however, is our value retrieval schema and does require additional configuration in order to grab the previously stored password. This expression in the password field looks up the username in the variable map. The string looks for the username before the @ symbol, as Azure AD will be sending back UPN. It is assumed that the username portion of UPN matches that of sAMAccountName. If it does not in your organization, modifications to SAML and LDAP server properties for user attributes may be needed. The expression also decrypts the stored password for use.
Step 6 – Create Authentication Policies
Two authentication policies (for our two LDAP factors) are needed. They will respectively link with the previously created LDAP servers. In both cases, the expression of ‘true’ is sufficient.
We also need to create the policies for our store credential procedures when authenticating at the ADC-owned IDP. Note that “store_creds_policy” cannot be created in the GUI as of ADC 13.0 b55.24. The command has to be run from CLI to create an authentication policy that can reference a variable assignment (which will thus store the credentials as configured earlier). This command only seems to work on 13.0, in testing on 12.1 b56.22 it crashes the appliance forcing a reboot. Have not tried on earlier builds of 12.1 as we require 13.0 regardless.
add authentication Policy store_creds_policy -rule true -action idp_usercreds_assign
Step 7 – Create Authentication Policy Labels
We must create two policy labels to accommodate for “second factors” on the respective AAA vServers.
For the Citrix Gateway’s corresponding vServer, the first factor is Azure MFA, followed by the auto-filled credential LDAP (SSO UPN) authentication as a second factor which we’ll configure on a policy label in order to set the right login schema.
For the IDP’s vServer, the first factor is LDAP (SAM) followed by a policy label with an initial policy to store the username and password credentials and a second policy that passes through and gives a success state as no “success state” response consumable by nFactor when calling the assignment.
Step 8 – Create nFactor Flows on AAA-TM vServers
Edit the properties of the non-addressable AAA vServer used by Citrix Gateway (AAA_GATEWAYNOFAS). Bind the SAML SP policy created earlier by clicking “Authentication Policy”, and select the PreFillUsernamePassword_PL policy label as the next factor. Bind a “noschema” loginschema to the AAA vServer itself. Click “Done” to accept changes and get back to the previous screen.
Edit the properties of the AAA_IDP vServer (the one with the routable IP) and we will bind two policies here; SAML IDP and LDAP.
For LDAP, click “Authentication Policy” and bind the sAMAccountName LDAP policy and select the next factor as the Assign_StoreCreds_PL policy label.
Next, let’s bind the SAML IDP policy. Note that if you make changes to the SAML IDP policy expression after binding to the vServer, you may need to unbind and re-bind in order for it to take effect.
Step 9 – Link Auth Profile to Citrix Gateway
The easiest step of all, binding the authentication profile we created earlier to the Citrix Gateway vServer. This authentication profile should link to the non-addressable AAA vServer for the Gateway.
Step 10 – Federate Domain with Azure AD using Citrix ADC IDP
We’ll now create our federation in Azure AD for the domain, in our case ferroque.dev which was previously added and validated in Azure AD (as a prerequisite) under the Azure > Custom domain name section of the Azure portal. Once the commands are invoked, note that it can take 15 minutes or so for the changes to replicate.
Note: If Azure AD SAML authentication is already in use, it is important this be the last step as you’ll effectively be changing the way users authenticate to Azure AD for their SaaS apps at this point. Prior to this, authenticating directly at the IDP to verify no errors are encountered is important (ignore the “Target URL not found for redirect after successful login.” message can be ignored in such a test).
First, from an administrative prompt on a Windows system, run the following commands to install and log into the Azure PowerShell cmdlets.
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 Install-Module -Name MSOnline
You should get an output like this:
Next, run the following command to authenticate to Azure AD.
Log in with the global admin account or another account with sufficient rights to conduct domain federation.
For the next step you’ll want to grab the public key from the certificate you’ll be using to secure the IDP. In my case I was using a wildcard certificate for all the vServers in the lab and just exported the certificate without private key in DER format. In the example below, the federation commands will look for the file in C:\. This is needed for Azure AD to trust the IDP’s assertions.
The following variables and commands will invoke the federation of Azure AD to the domain which in this example is ferroque.dev. Note the URIs used in this configuration, which should match URIs used previously in the various SAML configurations on the ADC. Note that $dom variable references the “verified” domain you will have added to Azure as a prior pre-requisite.
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("C:\idp_publicKey.crt") $certData = [system.convert]::tobase64string($cert.rawdata) $dom = "ferroque.dev" $fedBrandName = "Ferroque Dev Domain" $logoffuri = "https://idp.ferroque.dev/cgi/tmlogout" $logonuri = "https://idp.ferroque.dev/saml/login" $issueruri= "https://idp.ferroque.dev/" $ecpUrl = "https://idp.ferroque.dev/saml/login" Set-MsolDomainAuthentication -DomainName $dom -federationBrandName $fedBrandName -Authentication Federated -PassiveLogOnUri $logonuri -SigningCertificate $certData -IssuerUri $issueruri -ActiveLogOnUri $ecpUrl -LogOffUri $logoffuri –PreferredAuthenticationProtocol SAMLP
Also an important note, the $issueruri variable must match the “Issuer Name” on the SAML IDP profile exactly or Azure SAML authentication issues will occur (AADSTS50107 error) as shown below once configuration is complete and you are testing the flow. In my case, the trailing slash was not present in my SAML IDP profile to match the federation configs. Easy fix.
Running the following command will give you an output to validate your configurations.
Get-MsolDomainFederationSettings -DomainName ferroque.dev
For reference, the output of the above commands should look something like this. If your trusted signing certificate did not present itself in a Base64 blob, you have an issue needing correcting for your certificate.
Examing the Azure AD GUI properties for custom domains should now show the domain as federated as well.
Moment of Truth – Validation
At this point all necessary configurations should be in place. Attempting to log into the Citrix Gateway should have the user redirected briefly to Azure AD, then to the Citrix ADC-hosted IDP. Upon successful authentication via LDAP, the user should be redirected back to Azure AD where presumably some form of MFA (token, push, etc.) will take place, at which point the user should be directed back to the Citrix Gateway and seamlessly passed through to StoreFront.
Accessing Gateway Page, Redirected to Azure and to Citrix ADC IDP. First LDAP Auth (sAMAccoutName)
Successful Response from IDP AAA to Azure – Azure Auth (MFA, etc.)
Successful Response from Azure to Citrix Gateway AAA and Passthrough to StoreFront
Successful Launch of Citrix App – No Second Authentication Prompt
Citrix ADC Configuration
Below you will find all the necessary config params to build the configs mentioned above on the ADC. You’ll naturally want to adjust object names, certificate names, IPs.
## Variable and Assignment ## add ns variable idp_usercreds_var -type "map(text(256),text(256),1000)" -ifFull undef -ifValueTooBig undef -expires 3600 add ns assignment idp_usercreds_assign -variable "$idp_usercreds_var[AAA.USER.NAME]" -set AAA.USER.PASSWD.ENCRYPT ## Certificate Certkeys and Linking ## add ssl certKey MSFT_IDP_CERT -cert Ferroque-CitrixGateway-NOFAS-TEST.cer -passcrypt @@@@@@@@@@@ -encrypted -encryptmethod ENCMTHD_3 add ssl certKey wildcard_ferroque_dev -cert wildcard_ferroque_dev.pfx -key wildcard_ferroque_dev.pfx -inform PFX -passcrypt @@@@@@@@@@@ -encrypted -encryptmethod ENCMTHD_3 add ssl certKey SectigoINTER -cert SectigoINTER.cer -inform DER add ssl certKey SectigoROOT -cert SectigoROOT.cer -inform DER link ssl certKey wildcard_ferroque_dev SectigoINTER link ssl certKey SectigoINTER SectigoROOT ## LDAP, SAML IDP, SAML SP Actions ## add authentication samlIdPProfile SAML_IDP_PROF -samlIdPCertName wildcard_ferroque_dev -assertionConsumerServiceURL "https://login.microsoftonline.com/login.srf" -samlIssuerName "https://idp.ferroque.dev/" -rejectUnsignedRequests OFF -audience urn:federation:MicrosoftOnline -NameIDFormat persistent -NameIDExpr "AAA.USER.ATTRIBUTE(2).B64ENCODE" -Attribute1 IDPEmail -Attribute1Expr "AAA.USER.ATTRIBUTE(1)" -Attribute1FriendlyName mail -Attribute1Format URI -SPLogoutUrl "https://idp.ferroque.dev/cgi/tmlogout" add authentication ldapAction LDAP_FERROSYS -serverIP 220.127.116.11 -serverPort 636 -ldapBase "OU=your,DC=domain,DC=DEV" -ldapBindDn firstname.lastname@example.org -ldapBindDnPassword @@@@@@@@@@@ -encrypted -encryptmethod ENCMTHD_3 -ldapLoginName sAMAccountName -groupAttrName memberOf -subAttributeName cn -secType SSL -passwdChange ENABLED -Attribute1 mail -Attribute2 objectGUID -Attribute9 sAMAccountName -Attribute10 userPrincipalName add authentication ldapAction LDAP_FERROSYS_SSO -serverIP 18.104.22.168 -serverPort 636 -ldapBase "OU=your,DC=domain,DC=DEV" -ldapBindDn email@example.com -ldapBindDnPassword @@@@@@@@@@@ -encrypted -encryptmethod ENCMTHD_3 -ldapLoginName userPrincipalName -groupAttrName memberOf -subAttributeName cn -secType SSL -ssoNameAttribute userPrincipalName -passwdChange ENABLED -Attribute1 mail -Attribute2 objectGUID -Attribute9 sAMAccountName -Attribute10 userPrincipalName add authentication samlAction SAML_MSFT_SRV -samlIdPCertName MSFT_IDP_CERT -samlSigningCertName wildcard_ferroque_dev -samlRedirectUrl "https://login.microsoftonline.com/xxxxxxxxxxxxxxxxxxxx/saml2?whr=ferroque.dev" -samlUserField NameID -samlIssuerName "https://gateway.ferroque.dev/" -Attribute1 NameID -Attribute2 email -enforceUserName OFF -forceAuthn ON -audience "https://gateway.ferroque.dev/" add authentication authnProfile AAA_GATEWAY-NOFAS_authprof -authnVsName AAA_GATEWAYNOFAS -AuthenticationHost gateway.ferroque.dev -AuthenticationDomain ferroque.dev add authentication authnProfile AAA_IDP_authprof -authnVsName AAA_IDP -AuthenticationHost idp.ferroque.dev -AuthenticationDomain ferroque.dev ## AAA-TM Servers ## add authentication vserver AAA_IDP SSL 22.214.171.124 443 -maxLoginAttempts 10 -failedLoginTimeout 15 add authentication vserver AAA_GATEWAYNOFAS SSL 0.0.0.0 -maxLoginAttempts 10 -failedLoginTimeout 15 set ssl vserver AAA_IDP -ssl3 DISABLED -tls1 DISABLED -tls11 DISABLED -dtls1 DISABLED -HSTS ENABLED -maxage 157680000 set ssl vserver AAA_GATEWAYNOFAS -ssl3 DISABLED -tls1 DISABLED -tls11 DISABLED -dtls1 DISABLED -HSTS ENABLED -maxage 157680000 bind authentication vserver AAA_IDP -portaltheme RfWebUI bind authentication vserver AAA_GATEWAYNOFAS -portaltheme RfWebUI bind authentication vserver AAA_GATEWAYNOFAS -policy noschema -priority 100 -gotoPriorityExpression END bind authentication vserver AAA_GATEWAYNOFAS -policy saml_sp_policy_to_aad_idp -priority 100 -nextFactor PreFillUsernamePassword_PL -gotoPriorityExpression NEXT bind authentication vserver AAA_IDP -policy SAML_IDP -priority 100 -gotoPriorityExpression NEXT bind authentication vserver AAA_IDP -policy authsrv_ldaps_authOn_sAMAccountName -priority 100 -nextFactor Assign_StoreCreds_PL -gotoPriorityExpression NEXT bind ssl vserver AAA_IDP -certkeyName wildcard_ferroque_dev bind ssl vserver AAA_GATEWAYNOFAS -certkeyName wildcard_ferroque_dev ## Gateway Server ## add vpn vserver gateway.ferroque.dev SSL 126.96.36.199 443 -downStateFlush DISABLED -Listenpolicy NONE -tcpProfileName nstcp_default_XA_XD_profile -maxLoginAttempts 10 -failedLoginTimeout 15 -authnProfile AAA_GATEWAY-NOFAS_authprof add vpn sessionAction BASELINE_SESSION_WEB_PROF -sessTimeout 60 -clientSecurityLog ON -splitTunnel OFF -transparentInterception ON -defaultAuthorizationAction ALLOW -ssoCredential PRIMARY -icaProxy ON -wihome "https://storefront.ferroque.dev/Citrix/StoreWeb" -ntDomain ferroque.dev -clientlessVpnMode OFF add vpn sessionPolicy BASELINE_SESSION_WEB_POL true BASELINE_SESSION_WEB_PROF bind vpn vserver gateway.ferroque.dev -staServer "https://xdc1.ferroque.dev" bind vpn vserver gateway.ferroque.dev -staServer "https://xdc2.ferroque.dev" set ssl vserver gateway.ferroque.dev -ssl3 DISABLED -tls1 DISABLED -tls11 DISABLED -dtls1 DISABLED -HSTS ENABLED -maxage 157680000 bind vpn vserver gateway.ferroque.dev -portaltheme RfWebUI bind vpn vserver gateway.ferroque.dev -policy BASELINE_SESSION_WEB_POL -priority 100 -gotoPriorityExpression NEXT -type REQUEST bind ssl vserver gateway.ferroque.dev -certkeyName wildcard_ferroque_dev ## Login Schemas, Policies, Policy Labels ## add authentication loginSchema PreFillUsernamePassword_LS -authenticationSchema noschema -passwdExpression "$idp_usercreds_var[AAA.USER.NAME.TO_LOWER.BEFORE_STR(\"@\")].DECRYPT" -userCredentialIndex 5 -passwordCredentialIndex 6 -SSOCredentials YES add authentication loginSchema StoreCreds_LS -authenticationSchema noschema -SSOCredentials YES add authentication samlIdPPolicy SAML_IDP -rule "HTTP.REQ.HEADER(\"Referer\").CONTAINS(\"login.microsoftonline.com\")" -action SAML_IDP_PROF add authentication noAuthAction NO_AUTHN add authentication loginSchemaPolicy noschema -rule true -action LSCHEMA_INT add authentication Policy authsrv_ldaps_authOn_sAMAccountName -rule true -action LDAP_FERROSYS add authentication Policy store_creds_policy_finish -rule true -action NO_AUTHN add authentication Policy saml_sp_policy_to_aad_idp -rule "HTTP.REQ.COOKIE.NAME_VALUE(\"NSC_TASS\").CONTAINS(\"idp.ferroque.dev\").NOT" -action SAML_MSFT_SRV add authentication Policy store_creds_policy -rule true -action idp_usercreds_assign add authentication Policy authsrv_ldaps_authOn_SSO -rule true -action LDAP_FERROSYS_SSO add authentication policylabel Assign_StoreCreds_PL -loginSchema StoreCreds_LS add authentication policylabel PreFillUsernamePassword_PL -loginSchema PreFillUsernamePassword_LS bind authentication policylabel Assign_StoreCreds_PL -policyName store_creds_policy -priority 100 -gotoPriorityExpression NEXT bind authentication policylabel Assign_StoreCreds_PL -policyName store_creds_policy_finish -priority 110 -gotoPriorityExpression NEXT bind authentication policylabel PreFillUsernamePassword_PL -policyName authsrv_ldaps_authOn_SSO -priority 100 -gotoPriorityExpression NEXT
As this is an elaborate configuration, there are many opportunities for things to go wrong. Having three PuTTy sessions open with the following commands at the ready are quite useful, all executed from shell. Note that it may be worth enabling DEBUG logging on the global internal Syslog configuration of the appliance while troubleshooting, and disabling it afterward. System > Auditing > Settings > Change Auditing Syslog Settings > Log Levels = ALL.
Examining authentication logs:
shell cat /tmp/aaad.debug
Examining Syslog events during authentication (note you can append ‘| grep -i xxx’ where xxx us a key term such as SAML or AAA to filter on:
shell tail -f /var/log/ns.log
Examining policy hits on nFactor configuration:
shell nsconmsg -d current -g pcb_hits
The SAML-Tracer extension for Chrome or FireFox is also an invaluable tool for debugging SAML authentication.
Additionally, an error message such as this below may indicate the variable is not successfully finding a matching user ID in the map in order to pull the password, or the user ID itself is not being passed through correctly. The former would show up as an error in the aaad.debug logs about a null password error. The latter may indicate the user was not found when performing the SSO LDAP config (the second LDAP auth in the sequence). In such case there may be an issue with the SSO LDAP server config such as sAMAccountName used instead of userPrincipalName for Server Logon Name Attribute, or some other mismatch between NameID received back from Azure’s assertion and the Server Logon Name Attribute defined on the SSO LDAP server on the ADC.
Special thanks to Citrites including Rene Gamache, Florin Bejan, Maude Courcy, Blair Parker, Saman Salehian, and Citrix Alumni Jay Chandrasekar. And an especially huge shout out to Citrix’s Dileep Reddem for his mastery on the “under the hood” internals of ADC. Without his generous assistance this solution would likely have taken much longer.