Citrix Netscaler Gateway 12
Contents
Introduction
This article covers how to adjust an integration between pinsafe protocol and Citrix Netscaler Gateway 12.
Swivel can provide Two Factor authentication with SMS, Token, and Mobile Phone Client and strong Single Channel Authentication with TURing or Pinpad, or in the Taskbar using RADIUS. For all the methods which do not require an image at the article Citrix_Netscaler_Gateway_11 covers them.
To use the Single Channel Image such as the TURing Image, the Swivel server must be made accessible. The client requests the images from the Swivel server, and is usually configured using Network Address Translation, often with a proxy server. The Swivel virtual or hardware appliance is configured with a proxy port to allow an additional layer of protection. The Netscaler can be configured using its load balancing bridging feature to allow a Swivel Severs IP to provide Single Channel images, such as TURing and PINpad. Both the authentication methods need an image for which there are a set of rules to be applied. This document covers the application of those rules through the NS command line.
Integration Architecture
Swivel Secure → Radius → Nas → Netscaler → login page → AD → login customised page
Turing Image Integration
You can customise the labels from the web console. Under NetScaler Gateway, select Portal Themes, then the theme you are using, and Edit. On the right, click Logon Page, and the text can be edited there.
There is need to have a valid certificate for the turing image to appear – it needs to go public - as a trial you can try a self signed certificate that is trusted by the host: cd /usr/local/share/ca-certificates/swivel.crt
Default Theme
Below there are a set of rewrite and responder actions & policies that need to be installed. Before you install them, edit the file and change the URL in the responder action near the bottom to the correct URL for your TURing. You don't need the "SCImage" part - that will be added automatically. To install the rules, you need to open a command prompt on the NetScaler. You can just paste the entire file contents into the shell window. Once you have installed them, they have to be bound to a virtual server. There isn’t a script for that as it will be different for each installation. It's easiest to do this in the netscaler’s web admin console.
Rewrite Rules
add rewrite action ReAct_Pinsafe_AppendEULA replace_all "HTTP.RES.BODY(1000000)" "\"form.append(eula_section,field_login,pinsafe_button,pinsafe_image)\"" -search "text(\"form.append(eula_section,field_login)\")"
add rewrite action ReAct_Pinsafe_Append replace_all "HTTP.RES.BODY(1000000)" "\"form.append(field_login,pinsafe_button,pinsafe_image)\"" -search "text(\"form.append(field_login)\")"
add rewrite action ReAct_pinsafe.js insert_after_all "HTTP.RES.BODY(12000)" q{"<script type=\"text/javascript\" src=\"/vpn/pinsafe.js\"></script>"} -search q{text("<script type=\"text/javascript\" src=\"/vpn/login.js\"></script>")}
add rewrite action ReAct_Pinsafe_ButtonVar insert_after_all "HTTP.RES.BODY(1000000)" q{"\r\n var pinsafe_button=$(\"\").addClass('field').addClass('buttons');\r\nvar pinsafe_image=$(\"\");\r\n"} -search q{text("var field_login=$(\"\").addClass('field').addClass('buttons');")}add rewrite action ReAct_Pinsafe_ButtonInput insert_after_all "HTTP.RES.BODY(1000000)" q|"//pinsafe: create button input\r\nvar Pinsafe = $(\"<input type='button' onclick='showTuring()' value='Get Code'></input>\").attr({'id':'Get_Code','value':'Get Code','class':'custombutton login_page'}).appendTo(right_pinsafebutton); \r\n"+"//pinsafe: create turing image \r\nvar PinsafeImg = $(\"
<img id=imgTuring name=imgTuring style='padding-top:10px; padding-right:10px' height='97' width='360px' align='right' />\").appendTo(right_pinsafeimage);\r\n"| -search q|text("var Login = $(\"<input type='submit'></input>\").attr({'id':'Log_On','value':'Log On','class':'custombutton login_page','disabled':'disabled'}).appendTo(right_loginbutton);")|
add rewrite action ReAct_Set_Username_Blur replace_all "HTTP.RES.BODY(100000)" q|".focus(function(){loginFieldCheck();}).blur(function(){showTuring();})"| -search q|text(".focus(function(){loginFieldCheck();})")|
add rewrite policy RePol_Pinsafe_ButtonVar "HTTP.REQ.URL.EQ(\"/vpn/js/gateway_login_form_view.js\")" ReAct_Pinsafe_ButtonVar
add rewrite policy RePol_Pinsafe_LeftRightVar "HTTP.REQ.URL.EQ(\"/vpn/js/gateway_login_form_view.js\")" ReAct_Pinsafe_LeftRightVar
add rewrite policy RePol_Pinsafe_ButtonInput "HTTP.REQ.URL.EQ(\"/vpn/js/gateway_login_form_view.js\")" ReAct_Pinsafe_ButtonInput
add rewrite policy RePol_Pinsafe_AppendEULA "HTTP.REQ.URL.EQ(\"/vpn/js/gateway_login_form_view.js\")" ReAct_Pinsafe_AppendEULA
add rewrite policy RePol_Pinsafe_Append "HTTP.REQ.URL.EQ(\"/vpn/js/gateway_login_form_view.js\")" ReAct_Pinsafe_Append
add rewrite policy RePol_Set_Username_Blur "HTTP.REQ.URL.EQ(\"/vpn/js/gateway_login_form_view.js\")" ReAct_Set_Username_Blur
add rewrite policy RePol_pinsafe.js "HTTP.REQ.URL.EQ(\"/vpn/index.html\")" ReAct_pinsafe.js
add responder action ResAct_pinsafe.js respondwith "\"HTTP/1.1 200 OK\r\n\r\n\"+\"var pinsafeUrl = \\\"https://pinsafens.swivelsecure.com:8443/proxy/\\\";\n\"+\"\nfunction showImage(sUrl) {\n\tsUser = document.getElementById(\\\"Enter user name\\\").value;\n\tif (sUser==\\\"\\\") {\n\t\tdocument.getElementsByName(\\\"login\\\").focus();\n\t} else {\n \"+\"\n\t\t// Find the image using Mozilla compatible approach…\n\t\tvarImg = document.getElementById(\\\"imgTuring\\\");\n \"+\"\n\t\t//Set the image src\n\t\tvarImg.src = sUrl + \\\"\?username=\\\" + sUser + \\\"&random=\\\" + Math.round(Math.random()*100000);\n \"+\"\n\t}\n}\n\"+\"\nfunction showTuring() {\n\tshowImage(pinsafeUrl + \\\"SCImage\\\");\n}\n\"+\"\nfunction sendMessage() {\n\tshowImage(pinsafeUrl + \\\"DCMessage\\\");\n}\"\n"
add responder policy ResPol_pinsafe.js "HTTP.REQ.URL.EQ(\"/vpn/pinsafe.js\")" ResAct_pinsafe.js
Binding the applied rules
This is done at the netscaler GUI.
Select the virtual server you are going to use, and edit it. Scroll down to the Policies section and click "+". Select Responder policy, then click Continue. Click "Add Binding" and select the policy "ResPol_pinsafe.js". Click Bind. Click Close, then click + again. This time, select "Rewrite" as the policy, and "Response" as the type. Click "Add Binding" and then select the rewrite policies just added, one at a time. After each one, make sure the GOTO expression is "NEXT", to ensure that all policies are executed. This doesn’t apply to the responder policy. In the end there should be 7 policies in all. It doesn't matter which order you add them.
The last thing you will need to do is to persuade NetScaler not to use the cached version of its JavaScript. Go back to the command prompt, and open a shell. The following have been tested successfully for Netscaler’s web files, and we recommend trying both to ensure the result:
cd /netscaler/ns_gui/vpn/js cd /var/netscaler/gui/vpn/js
After getting to those locations apply touch as Netscaler seems to cache java files.
touch gateway_login_form_view.js
You should now get the TURing image embedded into the login page.
Green Bubble Theme
For this theme the first rewrite action is different from the first in the default theme:
add rewrite action ReAct_Pinsafe_LeftRightVar insert_after_all "HTTP.RES.BODY(1000000)" q{"\r\n//pinsafe: create pinsafe div\r\n"+"var left_pinsafebutton=$(\"\").addClass('left').appendTo(pinsafe_button);\r\n"+"var right_pinsafebutton=$(\"\").addClass('right').appendTo(pinsafe_button);\r\nvar left_pinsafeimage =$(\"\").addClass('left').appendTo(pinsafe_image);\r\n"+"var right_pinsafeimage=$(\"\").addClass('right').appendTo(pinsafe_image);\r\n"} -search q{text("var right_loginbutton=$(\"\").addClass('right').appendTo(field_login);")}You will need to touch the .js file again to ensure the change is applied.
RfWebUI theme
For RfWebUI, you don't need rewrite actions: the request for scripts.js is built into the login page already. You can't use responder actions on RfWebUI.
// Set this to be the correct URL for the required image.
var swivelUrl = "https://pinsafens.swivelsecure.com:8443/proxy/SCImage";
function addSwivelCustom(form, enter_user) {
// Swivel changes to login page.var url_div = $("").attr({"id":"SwivelUrl","style":"display:none"});
if (enter_user) { enter_user.onblur = function(){showTuring();}; }
var refresh_button = $("<a>Refresh</a>").attr({"id":"Refresh_Button","href":"#"}).addClass("button forms-authentication-button last-child default").click(function(){showTuring();});var refresh_div = $("").addClass("buttonscontainer right").append(refresh_button); var turing_button_div = $("").addClass("field buttonsrow").append(refresh_div);
// Insert image button. form.append(turing_button_div[0]); form.append(url_div[0]);
var turingDiv = $("<div/>").attr({"id":"turingDiv"}).addClass("field buttonsrow"); var turingDiv2 = $("<div/>").addClass("buttonscontainer right"); var turingImage = $("<img/>").attr({"id":"imgTuring"}); turingDiv2.append(turingImage[0]); turingDiv.append(turingDiv2[0]); form.append(turingDiv[0]);
}
function showTuring() {
var usernameFields = document.getElementsByName("login");
if (usernameFields && usernameFields.length > 0) { var username = usernameFields[0].value;
if (username && username != "") { document.getElementById("turingDiv").style.display = ""; var rand = "" + Math.floor(Math.random()*10000); var img = document.getElementById("imgTuring"); var source = swivelUrl + "?username=" + username + "&random=" + rand; img.src = source; } else { document.getElementById("turingDiv").style.display = "none"; } }
}
function insertTuring(formcontainer) {
var form = formcontainer.firstChild; if (form != null) { addSwivelCustom(form, null); }
}
function checkForm() {
var form = document.forms[0]; var userfield = document.getElementById("login"); if (form != null && userfield != null) { addSwivelCustom(form, userfield); } else { setTimeout(function(){ checkForm(); }, 500); }
}
checkForm();
function checkUserNotFound() {
var form = document.forms[0]; var userfield = document.getElementById("login"); var notfound = $("div#access_denied_title"); if (form != null && notfound[0] != null && !notfound[0].getAttribute('swivel')) { notfound.attr({"swivel":"true"}); addSwivelCustom(form, userfield); } setTimeout(function(){ checkUserNotFound(); }, 500);
}
checkUserNotFound();
X1
This is a new theme for the NS12 which allows straightforward customization of the login page, ensuring a quick customization of features which in the other themes need to be CLI edited.
Pinpad Integration
The following rules apply to the default theme. The green bubble theme should also receive this rules.
Add rewrite action ReAct_pinpad_js insert_after_all "HTTP.RES.BODY(12000)" q{"\r\n<script type=\"text/javascript\" src=\"/vpn/pinpad.js\"></script>\r\n<link rel=\"stylesheet\" type=\"text/css\" href=\"/vpn/pinpad.css\"/>\r\n"} -search q{text("<script type=\"text/javascript\" src=\"/vpn/login.js\"></script>")}
add rewrite action ReAct_Insert_Pinpad replace_all "HTTP.RES.BODY(1000000)" q|"form.append(field_errormsg);\r\n\tvar refresh_button=$(\"<input></input>\").attr({\"id\":\"Refresh_Pinpad\",\"type\":\"button\",\"value\":\"Refresh\"}).addClass(\"custombutton turingButton\").click(function(){showPinpad();});"+"\r\n\tvar pinpad_button_div=$(\"\").addClass(\"turingDiv\").append(refresh_button);"+"\r\n\tfield_login.append(pinpad_button_div);\r\n\t"+"var pinpad1 = $(\"<img />\").attr({\"id\":\"pinpad1\",\"src\":\"/vpn/images/pinpadBlank.png\"}).addClass(\"pinpadImage\").click(function(){addPinpadDigit('1');});\r\n\t"+"var pinpad2 = $(\"<img />\").attr({\"id\":\"pinpad2\",\"src\":\"/vpn/images/pinpadBlank.png\"}).addClass(\"pinpadImage\").click(function(){addPinpadDigit('2');});\r\n\t"+"var pinpad3 = $(\"<img />\").attr({\"id\":\"pinpad3\",\"src\":\"/vpn/images/pinpadBlank.png\"}).addClass(\"pinpadImage\").click(function(){addPinpadDigit('3');});\r\n\t"+"var pinpad4 = $(\"<img />\").attr({\"id\":\"pinpad4\",\"src\":\"/vpn/images/pinpadBlank.png\"}).addClass(\"pinpadImage\").click(function(){addPinpadDigit('4');});\r\n\t"+"var pinpad5 = $(\"<img />\").attr({\"id\":\"pinpad5\",\"src\":\"/vpn/images/pinpadBlank.png\"}).addClass(\"pinpadImage\").click(function(){addPinpadDigit('5');});\r\n\t"+"var pinpad6 = $(\"<img />\").attr({\"id\":\"pinpad6\",\"src\":\"/vpn/images/pinpadBlank.png\"}).addClass(\"pinpadImage\").click(function(){addPinpadDigit('6');});\r\n\t"+"var pinpad7 = $(\"<img />\").attr({\"id\":\"pinpad7\",\"src\":\"/vpn/images/pinpadBlank.png\"}).addClass(\"pinpadImage\").click(function(){addPinpadDigit('7');});\r\n\t"+"var pinpad8 = $(\"<img />\").attr({\"id\":\"pinpad8\",\"src\":\"/vpn/images/pinpadBlank.png\"}).addClass(\"pinpadImage\").click(function(){addPinpadDigit('8');});\r\n\t"+"var pinpad9 = $(\"<img />\").attr({\"id\":\"pinpad9\",\"src\":\"/vpn/images/pinpadBlank.png\"}).addClass(\"pinpadImage\").click(function(){addPinpadDigit('9');});\r\n\t"+"var pinpad0 = $(\"<img />\").attr({\"id\":\"pinpad0\",\"src\":\"/vpn/images/pinpadBlank.png\"}).addClass(\"pinpadImage\").click(function(){addPinpadDigit('0');});\r\n\t"+"var pinpad_top = $(\"\").addClass(\"divPinpadTop\").append(pinpad1,pinpad2,pinpad3);\r\n\t"+"var pinpad_middle = $(\"\").addClass(\"divPinpadMiddle\").append(pinpad4,pinpad5,pinpad6,pinpad7);\r\n\t"+"var pinpad_bottom = $(\"\").addClass(\"divPinpadBottom\").append(pinpad8,pinpad9,pinpad0);\r\n\t"+"var pinpad_div = $(\"\").addClass(\"pinpadHidden\").attr(\"id\",\"divPinpad\").append(pinpad_top,pinpad_middle,pinpad_bottom);\r\n\t"+"form.append(pinpad_div);"| -search q{text("form.append(field_errormsg);")}add rewrite policy RePol_pinpad_js "HTTP.REQ.URL.EQ(\"/vpn/index.html\")" ReAct_pinpad_js
add rewrite policy RePol_Insert_Pinpad "HTTP.REQ.URL.EQ(\"/vpn/js/gateway_login_form_view.js\")" ReAct_Insert_Pinpad
add responder action ResAct_pinpad.js respondwith "\"HTTP/1.1 200 OK\r\n\r\n\"+\"var pinpadUrl=\\\"https://swivel.mycompany.com:8443/proxy/SCPinPad\\\";\r\nvar pinpadField=\\\"passwd\\\";\r\n\r\n\"+\"function showPinpad() {\r\n\t\"+\"sUser = document.getElementsByName(\\\"login\\\")[0].value;\r\n\tif (sUser!=\\\"\\\") {\r\n\t\t\"+\"var divPinpad=document.getElementById(\\\"divPinpad\\\");\r\n\t\tdivPinpad.className = \\\"pinpadVisible\\\";\r\n\t\tvar imageUrl = pinpadUrl + \\\"\?username=\\\";\"+\"\r\n\tvar padno = Math.round(Math.random()*100000);\r\n\t\tfor (idx=0; idx<10; idx++) {\r\n\t\t\tvar digit=document.getElementById(\\\"pinpad\\\" + idx);\r\n\t\t\tdigit.src=imageUrl+sUser+\\\"&padno=\\\"+padno+\\\":\\\"+idx;\r\n\t\t}\r\n\t}\r\n}\"+\"\r\n\r\nfunction addPinpadDigit(digit) {\r\n\tsOtc=document.getElementById(pinpadField);\r\n\tsOtc.value += digit;\r\n}\r\n\""
add responder action ResAct_pinpad.css respondwith "\"HTTP/1.1 200 OK\r\n\r\n\"+\"div.pinpadHidden { display : none; }\r\n\"+\"div.pinpadVisible { clear : both; float : right; display : inline; position: relative; text-align: center; left : -20px; top : 10px; }\r\n\"+\"div.divPinpadTop {}\r\n\"+\"div.divPinpadMiddle { margin-top: -12px; }\r\n\"+\"div.divPinpadBottom { margin-top: -12px; }\r\n\"+\"img.pinpadImage { margin: 5px; }\r\n\""
add responder policy ResPol_pinpad.js "HTTP.REQ.URL.EQ(\"/vpn/pinpad.js\")" ResAct_pinpad.js
add responder policy ResPol_pinpad.css "HTTP.REQ.URL.EQ(\"/vpn/pinpad.css\")" ResAct_pinpad.css
Delete previous rules
The optimal option is to unbound all the rules through the NS GUI and after delete them. Also bear in mind the need to touch the .js files mentioned throughout the article as NS caches the previous versions - so changes might not be visible or immediately available.
Adjust Buttons at the login page
For further adjustments of the login page read the following section. Bear in mind X1 theme allows a quick editing of some features so the following might not apply. Normally the login page can be slightly edited, we are not going onto details regarding aesthetics and branding but only renaming of some sections which report to this integration.
Edit Password to OTC
The example below describes the use of the english language at the login interface.
> shell root@VLABSRV0# cd /var/netscaler/logon/themes/Default/resources root@VLABSRV0# chmod +w en.xml root@VLABSRV0# vi en.xml
[change word directly – beginning of the word - cw – write – escape - :wq!]
ng> <String id="User_name">User name</String> <Property id="Enter user name" property="title">Enter user name</Property> <String id="Password">OTC</String> <String id="Password2">Password 2</String> <String id="Enter password">Enter password</String> <Property id="Log_On" property="value">Log On</Property> <String id="You need to enter login name">You need to enter login name</Stri ng> <String id="You need to enter passwd">You need to enter a password</String> * <String id="Enter_password2_Alert">You need to enter the second password </String> <String id="domain">Domain</String> <String id="eula_title">End User License Agreement</String> <String id="eula_agreement">I accept the </String> <String id="terms">Terms & Conditions</String> <String id="errorMessageLabelBase">errorMessageLabel</String> <String id="eulaback">Back</String> <String id="errorMessageLabel4001">Incorrect credentials. Try again.</String > <String id="errorMessageLabel4002">You do not have permission to log on at t his time.</String> <String id="errorMessageLabel4003">Cannot connect to server. Try connecting en.xml: 597 lines, 51853 characters. root@VLABSRV015# exit shell
- You can also change “You need to enter a password” to “You need to enter an OTC”. We recommend avoiding obvious naming, mainly as a security measure.
Troubleshooting
If the logging in is not working please check the certificate and if the netscaler as the same valid certificate. Also if there as been made any change to the ip’s check if there is a firewall blocking the content.
It has been reported that sometimes the JavaScript file gets cached. To resolved this you should touch gateway_login_form_view.js and try to log after. NetScaler tends to cache JavaScript files, and doesn't detect changes made by rewrite rules. You have to force it to refresh its cache.
If the pinsafe.js file is coming through OK it means that some of the rules are working.
Netscaler Upgrade from 11 to 12
As recommended by CITRIX, for previous versions the upgrade should be made gradually, eg from NS 11.0 to NS 11.1 prior to get to NS 12. The upgrade should be easily done through the NS GUI but if you bump into trouble the CLI upgrade version is also easy.
Download the build file from Citrix page, Netscaler Gateway 12, upload it to /flash through Filezilla/WinSCP. Example below:
soc@support ~ $ ssh nsroot@10.10.10.21 > save config > shell root@VLABSRV0# cd /nsconfig root@VLABSRV0# cp ns.conf ns.conf11.ns root@VLABSRV0# cd /var/nsinstall
root@VLABSRV0# mkdir nsinstall12 root@VLABSRV0# cd nsinstall12 root@VLABSRV0# mv /flash/build-12.0-53.13_nc_32.tgz . root@VLABSRV0# tar -xvzf build-12.0-53.13_nc_32.tgz (...) root@VLABSRV0# ./installns installns: [36026]: VERSION ns-12.0-53.13.gz (...) installns: [36026]: installns version (12.0-53.13) kernel (ns-12.0-53.13.gz)
The Netscaler version 12.0-53.13 checksum file is located on http://www.mycitrix.com under Support > Downloads > Citrix NetScaler. Select the Release 12.0-53.13 link and expand the "Show Documentation" link to view the SHA2 checksum file for build 12.0-53.13.
There may be a pause of up to 3 minutes while data is written to the flash. Do not interrupt the installation process once it has begun.
Installation will proceed in 5 seconds, CTRL-C to abort Installation is starting ... installns: [36026]: Installation is starting ... installns: [36026]: detected Version >= NS6.0 installns: [36026]: Installation path for kernel is /flash (...) installns: [36026]: Installing Linux EPA and Linux EPA version file... (...) Installation has completed. Reboot NOW? [Y/N] Y Rebooting … installns: [36026]: Rebooting ...