BIG-IP APM - Customized Logon Page
Note: This solution is only applicable to version 10.x, version 11 doesn't use advCustHelp.
The default logon page for the Access Policy Manager module is pretty basic, particularly so if only the minimal username and password is configured. However, APM is wildly flexible. In this tech tip, I’ll cover customizing the logon page by adding a dropdown box in addition to the standard username and password fields.
Introduction
Background Information
The goal here is to provide access to multiple web applications behind APM through the use of an admin-defined dropdown menu and different LTM pools for each web application. We will be generating the list dynamically through the use of data groups so there will be no need to manually edit the iRule code each time an admin decides to add another option.
Solution Overview
Combining advanced customization, data groups, and iRules, we can dynamically generate html code for each key value pair in the data group. We simply add a session variable in the logon page through advanced customization and insert our html code, generated with iRules, through the session variable. The data group serves as a user friendly way of adding more applications as a layer of indirection.
Create the Access Policy
Before creating the custom logon page, an access policy needs to be defined. This can be done by utilizing the Device Wizards option under the Templates and Wizards main tab. Select Web Application Access Management for Local Traffic Virtual Servers as shown below in Figure 1.
Follow the steps in the wizard (AAA server, virtual IP, pool info, etc) to get to the summary page shown below in Figure 2. Before clicking finished, enter the Visual Policy Editor to make a few changes.
Immediately after the start box in the VPE, add an iRule Event as show in Figure 3. This will trigger the iRule event ACCESS_POLICY_AGENT_EVENT in the iRule featured later in this article.
Make sure to assign an ID to the event before saving it (it's significant only in differentiating the use of multiple iRule events). Next, click on the Logon Page event in the VPE and add a text field immediately after the password field. Set the Post Variable Name and the Session Variable Name to ‘appname’. (The name is not significant but will need to match a statement in the HTML that will be replaced later in the article. This ensures that the logon page agent will know to expect appname as one of the POST parameters.) In the Logon Page Input Field #3 box, enter ‘Application’ and click save. See Figure 4 below for details.
Note: The logon page agent will only parse and store POST parameters it knows about. Also, the 'Application' entry in Logon Page Input Field #3 is entered for the sake of completeness, but is technically unnecessary as the custom HTML will override this.
Finally, update the access policy by clicking Apply Access Policy and finish the Device Wizard.
Customize the Logon Page
Now that the policy is created, login via ssh to the command line interface to complete several steps.
1. Change Directory into the specific policy’s logon directory:
cd /config/customization/advanced/logon/<policy_name>_act_logon_page_ag (where <policy_name> is your policy name. devCenEx_act_logon_page_ag in this case.)
2. Make a copy of the tmp_logon_en.inc file (the name logon_en.inc is significant.)
cp tmp_logon_en.inc logon_en.inc
3. Add group and world read permissions to the logon_en.inc file
chmod a+r logon_en.inc
4. Edit and save the logon_en.inc file, replacing this PHP code with the HTML code below it. Notice that the label and select tags reference Applications and appname (respectively) from our Logon Page in Figure 4.
PHP (remove)
<? //------------------------------------------------------------ foreach( $fields_settings as $field_settings ) { if( $field_settings["type"] != "none" ) { if( $GLOBALS["label_position"] == "above" ){ ?> <tr> <td colspan=2 class="credentials_table_unified_cell" ><label for="<? print( $field_settings["type"] ); ?>"><? print( $field_settings["caption"] ); ?></label><input type=<? print( $field_settings["type"] ); ?> name=<? print( $field_settings["name"] ); ?> class="credentials_input_<? print( $field_settings["type"] ); ?>" <? print( ( $field_settings["rw"] == 0 ? "disabled" : "" ) ); ?> value="<? print( $field_settings["value"] ); ?>" autocomplete="off"></td> </tr> <? }else{ ?> <tr> <td class="credentials_table_label_cell" ><? print( $field_settings["caption"] ); ?></td> <td class="credentials_table_field_cell"><input type="<? print( $field_settings["type"] ); ?>" name="<? print( $field_settings["name"] ); ?>" class="credentials_input_<? print( $field_settings["type"] ); ?>" <? print( ( $field_settings["rw"] == 0 ? "disabled" : "" ) ); ?> value="<? print( $field_settings["value"] ); ?>" autocomplete="off"></td> </tr> <? } } } //------------------------------------------------------------ ?>
Custom HTML (add)
<tr> <td colspan=2 class="credentials_table_unified_cell" ><label for="text">Username</label> <input type=text name=username class="credentials_input_text" value="" autocomplete="off" autocapitalize="off"></td> </tr> <tr> <td colspan=2 class="credentials_table_unified_cell" ><label for="password">Password</label> <input type=password name=password class="credentials_input_password" value="" autocomplete="off" autocapitalize="off"></td> </tr> <tr> <td colspan=2 class="credentials_table_unified_cell" ><label for="text">Applications</label> <select name="appname"> %{session.custom.logon_opt} </select> </td> </tr>
Note: The HTML above can be further customized if desired. The important section of code is:<select name>=”appname”>
%{session.custom.logon_opt}
</select>
The POST variable was created in the Logon page and the session variable will be utilized in the iRule.UPDATE: As an alternative to replacing the php code with html as shown above, the javascript below can be added to the javascript section of the same file:function dropdown(){
var allTDs = document.getElementsByTagName('td');for(i=0; i < allTDs.length; i++) {
if(allTDs[i].innerHTML.indexOf('appname') > 0 && allTDs[i].innerHTML.indexOf('auth_form') == -1) {var replacetext = '<label for="text">Application</label><select name="appname" value="" autocomplete="off">%{session.custom.logon_opt}</select>';allTDs[i].innerHTML = replacetext;}}}To ensure this javascript is run, insert the dropdown() function shown above in line 1 of the OnLoad() function defined in the same file.
5. Apply the customizations to the policy
advCustHelp <your policy name> (The following two commands are included with instructions on updated the access policy when this command is run.)
b customization group <your policy_name>_act_logon_page_ag action update
b profile access <your policy name> generation action increment
6. Create a string class for the applications. The string should be the name of the application to be displayed in the Logon Page and the value should be the pool that hosts the applicable service. The resulting configuration of the class is shown entered below via bigpipe then via tmsh (See Figure 5 immediatedly after for the GUI entry for the class.)
b class ExampleDataGroup '{ { "OWA" { "intranet-pool" } "Share Point" { "sharepoint-pool" } } }'
tmsh create /ltm data-group ExampleDataGroup type string records add { "OWA" { data "intranet-pool" } "Share Point" { data "sharepoint-pool" } }
7. Create the iRule (via iRule editor, GUI, or tmsh)
when ACCESS_POLICY_AGENT_EVENT {if {[ACCESS::policy agent_id] eq "GenHtml"} {set htmlstr ""# Pull data from the data group
set keys [class names ExampleDataGroup]# log local0. "DATA GROUP:: $keys"
foreach key $keys {
# log local0. "KEY:: $key"
# Add a new option in the drop down box for each key
append htmlstr "<option value=\"$key\">$key</option>"}# log local0. "HTML STRING:: $htmlstr"
# Using the session variable we inserted through advanced customization,
# we can insert the html code we generated above
ACCESS::session data set "session.custom.logon_opt" $htmlstr}}when ACCESS_ACL_ALLOWED {set appname [ACCESS::session data get "session.logon.last.appname"]# log local0. "appname:: $appname"
set value [class search -value ExampleDataGroup equals $appname]# log local0. "value:: $value"
if {[string length $value] > 0} {pool $value
# log local0. "POOL:: $value"
}}Note that the class name referenced in the iRule should match the actual class name (instead of ExampleDataGroup as shown above)
8. Finally, apply the iRule to the APM virtual server and the process is complete!
tmsh modify /ltm virtual custom_dropdown_vs rules { custom_dropdown_iRule }
Figure 6 below shows the resulting logon page from the customizations performed. This is just the surface of what can be done to customize the logon page. Many thanks to F5er ystephie for writing up the documentation steps for this solution.
Related Articles
- DevCentral Wiki: BIG-IP Access Policy Manager (APM) Wiki Home
- Auto-launch Remote Desktop Sessions with APM > DevCentral > F5 ...
- NTLM/ Outlook Anywhere/ Big-IP APM - DevCentral - F5 DevCentral ...
- Web Application Login Integration with APM > DevCentral > F5 ...
- F5 Tutorial: BIG-IP APM with SecureAuth
- Set APM Cookies to HttpOnly - DevCentral - F5 DevCentral ...
- DevCentral Wiki: APM
- nested virtuals with APM - DevCentral - F5 DevCentral > Community ...
- Pete Silva - apm
- BigIp_VE_10.1.0 — APM module and Hypervisor Support - DevCentral ...
- James_Goodwin_3Historic F5 AccountThis is a great example demonstrating how Access Policy Manager (APM) can be integrated directly with LTM, to bring user identity into load balancing and other traffic management functions.
- secu-idNimbostratusHello,
- hooleylistCirrostratusCould you add the APM version this was tested on? As secu-id said, it would help to get an update for 11.1.
- fouken_30253NimbostratusNot sure about anyone else, but a search for me revealed the page being used was /var/sam/www/php_include/webtop/renderer/customization/logon/Common/{policyname}_act_logon_page_ag/{language}/logon.inc
- Rise_77519NimbostratusHi guys,
- InnONimbostratusNice post, Jason.
- twilliamsonNimbostratusCould this article be updated for APM version 11.5?
- Ben_Thornton_10NimbostratusTop tip - if you want this to be saved in the UCS file do not edit the file on the box. You need v11 to do this (I'm on 11.5.1) Great page - used this to allow user to choose between split tunnel VPN and Full VPN. In Access policy on the GUI go to Customisation --> Advanced Select edit mode (as opposed to properties) Then go to Access Profiles --> /Common/{Access Profile Name} --> Macros --> {Resource you applied policy to} --> Logon Pages --> Logon Page --> logon.inc. Edit and save there.
- Kevin_Davies_40Nacreous
Implemented in ACCESS_SESSION_STARTED so no policy entries needed. The dropdown javascript worked perfectly in v13 customisation placed into logon.inc. The call to dropdown can be placed before OnLoad() with a semicolon between them. We used this to choose from a list of product descriptions which provided the login username.
- dragonflymrCirrostratus
@Kevin - could you post some more detailed info how to implement this in logon.inc - I tried to paste code like that:
function dropdown(){ var allTDs = document.getElementsByTagName('td'); for(i=0; i < allTDs.length; i++) { if(allTDs[i].innerHTML.indexOf('appname') > 0 && allTDs[i].innerHTML.indexOf('auth_form') == -1) { var replacetext = 'Application%{session.custom.logon_opt}'; allTDs[i].innerHTML = replacetext; } } }; function OnLoad()
as well as
function OnLoad() { function dropdown(){ var allTDs = document.getElementsByTagName('td'); for(i=0; i < allTDs.length; i++) { if(allTDs[i].innerHTML.indexOf('appname') > 0 && allTDs[i].innerHTML.indexOf('auth_form') == -1) { var replacetext = 'Application%{session.custom.logon_opt}'; allTDs[i].innerHTML = replacetext; } } }
but nothing happens on the logon page, just text field is displayed as defined in Logon Page. I can see that modified code is send to client so that is not the reason.
Thanks in advance, Piotr