[UPDATE February 23, 2024] It has come to our attention that leveraging client IP information in DUO allows for the application of distinct policies, a feature previously overlooked in our integration. Specifically, we had failed to transmit this information to the DUO proxy server, thereby preventing the implementation of such policies. By default, DUO utilizes the RADIUS calling-station-id attribute as the client IP. In response, I have updated the NetScaler RADIUS action to include this attribute, ensuring that the client IP information is now accurately passed to the DUO proxy server. This enhancement enables the effective utilization of client IP data for policy application within DUO.
As Cisco DUO's iframe integration reaches its end of support on September 30, 2024, organizations must transition to DUO Universal Prompt or adopt a RADIUS configuration without the iframe for continued support.
In this article, we explore how to utilize the NetScaler nFactor framework to replicate the iframe functionality while leveraging the non-iframe RADIUS integration, ensuring a smooth transition for users and administrators alike.
User Experience Overview:
The integration facilitates a seamless user experience, ensuring secure authentication while maintaining convenience. Users are presented with a familiar interface where they can select authentication methods such as DUO Push, SMS, phone call, or passcode entry, ensuring flexibility and accessibility.
The following image, depict the user experience using this integration method:
How this works
In addition to passcodes, DUO has special words that can be sent via RADIUS to the DUO proxy server that will trigger actions as followed:
- push: Sends a push notification to the user's device.
- sms: Sends an SMS with passcodes to the user's device.
- phone: Initiates a phone call to the user's device for confirmation.
- push#: Sends a push notification to the specified device (e.g., push2 for the second device).
- sms#: Sends an SMS with passcodes to the specified device (e.g., sms3 for the third device).
- phone#: Initiates a phone call to the specified device for confirmation (e.g., phone2 for the second device).
NetScaler communicates with the DUO proxy server by sending the appropriate keyword based on the user's selection:
- push: When the DUO Push option is selected.
- passcode: When the user enters a passcode.
- sms: When the Send SMS option is chosen.
- phone: When the Call Phone option is selected.
Users can manually select the passcode option and input special keywords (e.g., push2, phone3, sms4) to authenticate with alternative devices.
Authentication Flow
The authentication flow is configured with two factors:
- DUO MFA via RADIUS: The first factor verifies the user's MFA against DUO via RADIUS.
- LDAP Password: The second factor authenticates the user's password via LDAP.
This sequential configuration helps to prevent that Active Directory (AD) accounts are locked due to excessive authentication attempts.
DUO Configuration
On the DUO proxy configuration file (authproxy.cfg), make sure you have a client session with [duo_only_client] defined and create a new radius_server_duo_only entry on a port that is available, make sure to add the NSIP of both HA pair nodes and SNIP as RADIUS clients as needed.
[duo_only_client] [radius_server_duo_only99] api_host=api-XXXXXXXX.duosecurity.com ikey=XXXXXXXXXXXXXXXXXXXX skey=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX failmode=safe radius_ip_1=192.168.0.100 radius_secret_1=RADIUS_SECRET_123 radius_ip_2=192.168.0.151 radius_secret_2=RADIUS_SECRET_123 radius_ip_3=192.168.0.152 radius_secret_3=RADIUS_SECRET_123 port=18128
Use this configuration as a reference for setting up the DUO proxy server in your environment. Make sure to adapt the settings to match your specific setup, including any changes in IP addresses, shared secrets, or port numbers. If further assistance is required, consult the DUO support documentation or reach out to their support team for guidance on configuring a new radius_server_duo_only session on the authproxy.cfg file.
NetScaler Configuration
Assuming that the Citrix Gateway virtual server is already provisioned with necessary configurations such as certificates, Secure Ticket Authority (STA) servers, and session policies/profiles, follow these additional steps to integrate Cisco DUO using the nFactor extensibility features:
- Disable Single Sign-on Domain: Ensure that the "Single Sign-on Domain" is disabled on the session profile. This step is crucial for passing the UserPrincipalName to StoreFront.
- nFactor Extensibility Features: Utilize the nFactor extensibility features to customize the authentication process and integrate Cisco DUO options seamlessly. This involves creating a new XML Schema and adding jQuery code to the portal theme scripts.js file.
XML schema
Create a new xml file called duo.xml located at /nsconfig/loginschema with the following content:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <AuthenticateResponse xmlns="http://citrix.com/authentication/response/1"> <Status>success</Status> <Result>more-info</Result> <StateContext/> <AuthenticationRequirements> <PostBack>/nf/auth/doAuthentication.do</PostBack> <CancelPostBack>/nf/auth/doLogoff.do</CancelPostBack> <CancelButtonText>Cancel</CancelButtonText> <Requirements> <Requirement> <Credential><Type>none</Type></Credential> <Label><Text>dualauth_please_log_on</Text><Type>nsg-login-label</Type></Label> <Input/> </Requirement> <Requirement> <Credential><ID>login</ID><SaveID>login</SaveID><Type>username</Type></Credential> <Label><Text>dualauth_user_name</Text><Type>nsg-login-label</Type></Label> <Input><Text><ReadOnly>false</ReadOnly><InitialValue/><Constraint>.+</Constraint></Text></Input> </Requirement> <Requirement> <Credential><ID>passwd1</ID><SaveID>passwd1</SaveID><Type>password</Type></Credential> <Label><Text>dualauth_password</Text><Type>nsg-login-label</Type></Label> <Input><Text><Secret>true</Secret><Constraint>.+</Constraint></Text></Input> </Requirement> <Requirement> <Credential><ID>passwd2</ID><Type>duopasscode</Type></Credential> <Label><Text>dualauth_passcode</Text><Type>nsg-login-label</Type></Label> <Input/><!-- input field generated by custom handler --> </Requirement> <Requirement> <Credential><ID>passwd</ID><SaveID>passwd</SaveID><Type>duoselector</Type></Credential> <Label><Type>none</Type></Label> <Input/><!-- radio buttons created by custom handler --> </Requirement> <Requirement> <Credential><Type>duoinfotext</Type></Credential> <Label><Type>none</Type></Label> <Input/><!-- information message from custom handler --> </Requirement> <Requirement> <Credential><ID>Logon</ID><Type>none</Type></Credential> <Label><Type>none</Type></Label> <Input><Button>dualauth_submit</Button></Input> </Requirement> </Requirements> </AuthenticationRequirements> </AuthenticateResponse>
Portal Theme
Create a new portal theme called RfWebUI_DUO with the following NetScaler command:
add vpn portaltheme RfWebUI_DUO -basetheme RfWebUI
Add the following content to /var/netscaler/logon/themes/RfWebUI_DUO/script.js file:
// This function creates the passcode field for Duo CTXS.ExtensionAPI.addCustomCredentialHandler({ // Credential type defined in login schema getCredentialTypeName: function() { return "duopasscode"; }, // Generate HTML for the custom credential getCredentialTypeMarkup: function(requirements) { var input = $("<input>"); input.attr("id","passwd2"); input.attr("name","passwd2"); //input.attr("type","password"); input.attr("type","text"); input.attr("autocomplete","off"); input.attr("spellcheck","false"); input.prop("disabled",true); input.on("change",function(){$("#duocode").attr("value",$("#passwd2").val());}); return input; } }); // This function creates the radio buttons for Duo CTXS.ExtensionAPI.addCustomCredentialHandler({ // Credential type defined in login schema getCredentialTypeName: function() { return "duoselector"; }, // Generate HTML for the custom credential getCredentialTypeMarkup: function(requirements) { var table = $("<table></table>"); table.addClass("radioButtons"); table.css("border-spacing",0); table.css("height",44); var tr = $("<tr></tr>"); tr.appendTo(table); var tdbase = $("<td></td>"); tdbase.css("width","25%"); var btbase = $("<input>"); btbase.attr("type","radio"); btbase.attr("name","passwd"); var lbbase = $("<label></label>"); lbbase.css("font-size","12px"); lbbase.css("color","#999999"); var button = btbase.clone(); button.attr("value","push"); button.attr("id","duopush"); var label = lbbase.clone(); label.attr("for","duopush"); label.text(" Duo Push"); var td = tdbase.clone(); td.append(button,label); td.appendTo(tr); button.on("change",function(){$("#passwd2").prop("disabled",true)}); button.prop("checked","checked"); button = btbase.clone(); button.attr("value","code"); button.attr("id","duocode"); label = lbbase.clone(); label.attr("for","duocode"); label.text(" Passcode"); td = tdbase.clone(); td.append(button,label); td.appendTo(tr); button.on("change",function(){$("#passwd2").prop("disabled",false)}); button = btbase.clone(); button.attr("value","sms"); button.attr("id","duotext"); label = lbbase.clone(); label.attr("for","duotext"); label.text(" Send SMS"); td = tdbase.clone(); td.append(button,label); td.appendTo(tr); button.on("change",function(){$("#passwd2").prop("disabled",true)}); button = btbase.clone(); button.attr("value","phone"); button.attr("id","duocall"); label = lbbase.clone(); label.attr("for","duocall"); label.text(" Call Phone"); td = tdbase.clone(); td.append(button,label); td.appendTo(tr); button.on("change",function(){$("#passwd2").prop("disabled",true)}); return table; } }); // This function creates the information text for Duo CTXS.ExtensionAPI.addCustomCredentialHandler({ // Credential type defined in login schema getCredentialTypeName: function() { return "duoinfotext"; }, // Generate HTML for the custom credential getCredentialTypeMarkup: function(requirements) { var link = $("<a>here</a>"); link.attr("href","https://support.fqdn.com.lab"); link.attr("target","_blank"); link.css("color","#02a1c1"); var div = $("<div></div>"); div.css("width",385); div.css("font-size","12px"); div.css("color","#999999"); div.css("text-align","center"); div.append("Click ",link," if you are not enrolled in Duo."); div.append("<br>","For assistance call the Help Desk, (XXX) XXX-XXXX."); return div; } });
NetScaler commands
To the nFactor flow on NetScaler, you would use a series of CLI commands. Below are the commands to put together the configuration for integrating Cisco DUO using nFactor extensibility features and enabling/disabling the passcode field:
Login Schema
add authentication loginSchema DUO_LS -authenticationSchema "/nsconfig/loginschema/duo.xml" add authentication loginSchemaPolicy DUO_LSPOL -rule TRUE -action DUO_LS
RADIUS policy/action
In this particular step, the authTimeout is set to 60 seconds. This duration allows users a window of up to one minute to respond to authentication prompts such as push notifications or phone calls. Adjusting the authTimeout value provides flexibility in accommodating user response times within the nFactor authentication flow. The callingstationid parameter is used to pass the user source IP to the DUO proxy server.
add authentication radiusAction DUO_SRV -serverIP 192.168.0.51 -serverPort 18128 -authTimeout 60 -radKey RADIUS_SECRET_123 -callingstationid ENABLED add authentication Policy DUO_POL -rule TRUE -action DUO_SRV
LDAP policy/action
In this step, the ssoNameAttribute parameter is configured to specify the userPrincipalName. As a result, there's no need to explicitly specify the Single Sign-on Domain on the session policy. This configuration streamlines the authentication process by automatically utilizing the userPrincipalName attribute for Single Sign-on purposes without additional policy settings.
add authentication ldapAction LDAP_SRV -serverIP 192.168.0.5 -serverPort 636 -ldapBase "dc=company,dc=lab" -ldapBindDn serviceaccount@company.lab -ldapBindDnPassword SERVICE_ACCOUNT_PASSWORD -ldapLoginName sAMAccountName -groupAttrName memberOf -subAttributeName cn -secType SSL -ssoNameAttribute userprincipalname -passwdChange ENABLED add authentication Policy LDAP_POL -rule TRUE -action LDAP_SRV
Policy Label
This policy label represents the second factor of the authentication flow.
add authentication policylabel LDAP_PL_noschema -loginSchema LSCHEMA_INT bind authentication policylabel LDAP_PL_noschema -policyName LDAP_POL -priority 100 -gotoPriorityExpression NEXT
Authentication logic
This authentication logic is implemented on an authentication virtual server.
add authentication vserver DUO_AAA SSL 0.0.0.0 bind authentication vserver DUO_AAA -policy DUO_LSPOL -priority 100 -gotoPriorityExpression END bind authentication vserver DUO_AAA -policy DUO_POL -priority 100 -nextFactor LDAP_PL_noschema -gotoPriorityExpression NEXT bind ssl vserver DUO_AAA -certkeyName Sample01_SAN
The last command in this block binds a certificate to the authentication virtual server. This step is optional and primarily serves to display the virtual server in an UP (operational) state.
Authentication Profile
The authentication profile is used to link the authentication virtual server with a Citrix Gateway virtual server.
add authentication authnProfile DUO_AuthProf -authnVsName DUO_AAA
Citrix Gateway configuration
As mentioned previously, we are considering and pre-existing Citrix Gateway virtual server, in this case it is called "gw214.company.lab DUO".
The following command will link it to the authentication logic:
set vpn vserver "gw214.company.lab DUO" -authnProfile DUO_AuthProf
The following command binds the new RfWebUI theme to the Citrix Gateway virtual server.
bind vpn vserver "gw214.company.lab DUO" -portaltheme RfWebUI_DUO
No SMS option
If your organization deems SMS as an unsuitable MFA method, you have the option to disable it within the authentication interface. This can be accomplished by adjusting the script.js file.
To implement this change, follow these steps:
- Modify the width of the table cell (td) to 33% to accommodate the remaining authentication options effectively.
- Comment out the section of the code responsible for generating the SMS option.
By making these adjustments, the SMS option will no longer be displayed within the authentication interface, ensuring that only the preferred MFA methods are presented to users during authentication.
// This function creates the passcode field for Duo CTXS.ExtensionAPI.addCustomCredentialHandler({ // Credential type defined in login schema getCredentialTypeName: function() { return "duopasscode"; }, // Generate HTML for the custom credential getCredentialTypeMarkup: function(requirements) { var input = $("<input>"); input.attr("id","passwd2"); input.attr("name","passwd2"); //input.attr("type","password"); input.attr("type","text"); input.attr("autocomplete","off"); input.attr("spellcheck","false"); input.prop("disabled",true); input.on("change",function(){$("#duocode").attr("value",$("#passwd2").val());}); return input; } }); // This function creates the radio buttons for Duo CTXS.ExtensionAPI.addCustomCredentialHandler({ // Credential type defined in login schema getCredentialTypeName: function() { return "duoselector"; }, // Generate HTML for the custom credential getCredentialTypeMarkup: function(requirements) { var table = $("<table></table>"); table.addClass("radioButtons"); table.css("border-spacing",0); table.css("height",44); var tr = $("<tr></tr>"); tr.appendTo(table); var tdbase = $("<td></td>"); // tdbase.css("width","25%"); tdbase.css("width","33%"); var btbase = $("<input>"); btbase.attr("type","radio"); btbase.attr("name","passwd"); var lbbase = $("<label></label>"); lbbase.css("font-size","12px"); lbbase.css("color","#999999"); var button = btbase.clone(); button.attr("value","push"); button.attr("id","duopush"); var label = lbbase.clone(); label.attr("for","duopush"); label.text(" Duo Push"); var td = tdbase.clone(); td.append(button,label); td.appendTo(tr); button.on("change",function(){$("#passwd2").prop("disabled",true)}); button.prop("checked","checked"); button = btbase.clone(); button.attr("value","code"); button.attr("id","duocode"); label = lbbase.clone(); label.attr("for","duocode"); label.text(" Passcode"); td = tdbase.clone(); td.append(button,label); td.appendTo(tr); button.on("change",function(){$("#passwd2").prop("disabled",false)}); // button = btbase.clone(); // button.attr("value","sms"); // button.attr("id","duotext"); // label = lbbase.clone(); // label.attr("for","duotext"); // label.text(" Send SMS"); // td = tdbase.clone(); // td.append(button,label); // td.appendTo(tr); // button.on("change",function(){$("#passwd2").prop("disabled",true)}); button = btbase.clone(); button.attr("value","phone"); button.attr("id","duocall"); label = lbbase.clone(); label.attr("for","duocall"); label.text(" Call Phone"); td = tdbase.clone(); td.append(button,label); td.appendTo(tr); button.on("change",function(){$("#passwd2").prop("disabled",true)}); return table; } }); // This function creates the information text for Duo CTXS.ExtensionAPI.addCustomCredentialHandler({ // Credential type defined in login schema getCredentialTypeName: function() { return "duoinfotext"; }, // Generate HTML for the custom credential getCredentialTypeMarkup: function(requirements) { var link = $("<a>here</a>"); link.attr("href","https://support.fqdn.com.lab"); link.attr("target","_blank"); link.css("color","#02a1c1"); var div = $("<div></div>"); div.css("width",385); div.css("font-size","12px"); div.css("color","#999999"); div.css("text-align","center"); div.append("Click ",link," if you are not enrolled in Duo."); div.append("<br>","For assistance call the Help Desk, (XXX) XXX-XXXX."); return div; } });
Conclusion
In conclusion, this article highlights the versatility of NetScaler nFactor in addressing the imminent discontinuation of the DUO iframe integration. By leveraging nFactor's capabilities, organizations can seamlessly transition to alternative authentication methods while maintaining security and a similar user experience.
In this article, I've demonstrated a direct connection to the DUO RADIUS proxy and LDAP servers for simplicity. However, as a best practice, it's recommended to implement a load balancing virtual server for these components. This approach enhances the reliability and availability of the authentication infrastructure, ensuring seamless operation even in the event of server failures or high traffic loads.
It's worth noting that users with multiple devices can conveniently authenticate by selecting the passcode option and inputting special keywords such as push#, sms#, phone# followed by the device number. For example, entering push2 will trigger a push notification to the user's second registered device.
Credit for the original engineering of this solution goes to my former colleague, Edson da Luz, in 2021. I've made minor updates to present it here. Kudos to Edson for his creativity and skill in implementing this logic.
This article serves as a testament to the adaptability and innovation within the cybersecurity landscape, offering practical solutions to evolving challenges in authentication and access management.
Recommended Comments
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now