APM Cookbook: Modify LDAP Attribute Values using iRulesLX
Introduction
Access Policy Manager (APM) does not have the ability to modify LDAP attribute values using the native features of the product. In the past I’ve used some creative unsupported solutions to modify LDAP attribute values, but with the release of BIG-IP 12.1 and iRulesLX, you can now modify LDAP attribute values, in a safe and in supported manner.
Before we get started
I have a pre-configured Active Directory 2012 R2 server which I will be using as my LDAP server with an IP address of 10.1.30.101. My BIG-IP is running TMOS 12.1 and the iRules Language eXtension has been licensed and provisioned. Make sure your BIG-IP has internet access to download the required Node.JS packages. For this solution I’ve opted to use the ldapjs package.
The iRulesLX requires the following session variable to be set for the LDAP Modify to execute:
- Distinguished Name (DN): session.ad.last.attr.dn
- Attribute Name (e.g. carLicense): session.ldap.modify.attribute
- Attribute Value: session.ldap.modify.value
This guide also assumes you have a basic level of understanding and troubleshooting at a Local Traffic Manager (LTM) level and you BIG-IP Self IP, VLANs, Routes, etc.. are all configured and working as expected.
Step 1 – iRule and iRulesLX Configuration
1.1 Create a new iRulesLX workspace
Local Traffic >> iRules >> LX Workspaces >> “Create”
Supply the following:
- Name: ldap_modify_workspace
Select “Finished" to save.
You will now have any empty workspace, ready to cut/paste the TCL iRule and Node.JS code.
1.2 Add the iRule
Select “Add iRule” and supply the following:
- Name: ldap_modify_apm_event
- Select OK
Cut / Paste the following iRule into the workspace editor on the right hand side. Select “Save File” to save.
# Author: Brett Smith @f5 when RULE_INIT { # Debug logging control. # 0 = debug logging off, 1 = debug logging on. set static::ldap_debug 0 } when ACCESS_POLICY_AGENT_EVENT { if { [ACCESS::policy agent_id] eq "ldap_modify" } { # Get the APM session data set dn [ACCESS::session data get session.ad.last.attr.dn] set ldap_attribute [ACCESS::session data get session.ldap.modify.attribute] set ldap_value [ACCESS::session data get session.ldap.modify.value] # Basic Error Handling - Don't execute Node.JS if LDAP attribute name or value is null if { (([string trim $ldap_attribute] eq "") or ([string trim $ldap_value] eq "")) } { ACCESS::session data set session.ldap.modify.result 255 } else { # Initialise the iRulesLX extension set rpc_handle [ILX::init ldap_modify_extension] if { $static::ldap_debug == 1 }{ log local0. "rpc_handle: $rpc_handle" } # Pass the LDAP Attribute and Value to Node.JS and save the iRulesLX response set rpc_response [ILX::call $rpc_handle ldap_modify $dn $ldap_attribute $ldap_value] if { $static::ldap_debug == 1 }{ log local0. "rpc_response: $rpc_response" } ACCESS::session data set session.ldap.modify.result $rpc_response } } }
1.3 Add an Extension
Select “Add extenstion” and supply the following:
- Name: ldap_modify_extension
- Select OK
Cut / Paste the following Node.JS and replace the default index.js. Select “Save File” to save.
// Author: Brett Smith @f5 // index.js for ldap_modify_apm_events // Debug logging control. // 0 = debug off, 1 = debug level 1, 2 = debug level 2 var debug = 1; // Includes var f5 = require('f5-nodejs'); var ldap = require('ldapjs'); // Create a new rpc server for listening to TCL iRule calls. var ilx = new f5.ILXServer(); // Start listening for ILX::call and ILX::notify events. ilx.listen(); // Unbind LDAP Connection function ldap_unbind(client){ client.unbind(function(err) { if (err) { if (debug >= 1) { console.log('Error Unbinding.'); } } else { if (debug >= 1) { console.log('Unbind Successful.'); } } }); } // LDAP Modify method, requires DN, LDAP Attribute Name and Value ilx.addMethod('ldap_modify', function(ldap_data, response) { // LDAP Server Settings var bind_url = 'ldaps://10.1.30.101:636'; var bind_dn = 'CN=LDAP Admin,CN=Users,DC=f5,DC=demo'; var bind_pw = 'Password123'; // DN, LDAP Attribute Name and Value from iRule var ldap_dn = ldap_data.params()[0]; var ldap_attribute = ldap_data.params()[1]; var ldap_value = ldap_data.params()[2]; if (debug >= 2) { console.log('dn: ' + ldap_dn + ',attr: ' + ldap_attribute + ',val: ' + ldap_value); } var ldap_modification = {}; ldap_modification[ldap_attribute] = ldap_value; var ldap_change = new ldap.Change({ operation: 'replace', modification: ldap_modification }); if (debug >= 1) { console.log('Creating LDAP Client.'); } // Create LDAP Client var ldap_client = ldap.createClient({ url: bind_url, tlsOptions: { 'rejectUnauthorized': false } // Ignore Invalid Certificate - Self Signed etc.. }); // Bind to the LDAP Server ldap_client.bind(bind_dn, bind_pw, function(err) { if (err) { if (debug >= 1) { console.log('Error Binding to: ' + bind_url); } response.reply('1'); // Bind Failed return; } else { if (debug >= 1) { console.log('LDAP Bind Successful.'); } // LDAP Modify ldap_client.modify(ldap_dn, ldap_change, function(err) { if (err) { if (debug >= 1) { console.log('LDAP Modify Failed.'); } ldap_unbind(ldap_client); response.reply('2'); // Modify Failed } else { if (debug >= 1) { console.log('LDAP Modify Successful.'); } ldap_unbind(ldap_client); response.reply('0'); // No Error } }); } }); });
You will need to modify the bind_url, bind_dn, and bind_pw variables to match your LDAP server settings.
1.4 Install the ldapjs package
- SSH to the BIG-IP as root
- cd /var/ilx/workspaces/Common/ldap_modify_workspace/extensions/ldap_modify_extension
- npm install ldapjs -save
You should expect the following output from above command:
[root@big-ip1:Active:Standalone] ldap_modify_extension # npm install ldapjs -save
ldapjs@1.0.0 node_modules/ldapjs
├── assert-plus@0.1.5
├── dashdash@1.10.1
├── asn1@0.2.3
├── ldap-filter@0.2.2
├── once@1.3.2 (wrappy@1.0.2)
├── vasync@1.6.3
├── backoff@2.4.1 (precond@0.2.3)
├── verror@1.6.0 (extsprintf@1.2.0)
├── dtrace-provider@0.6.0 (nan@2.4.0)
└── bunyan@1.5.1 (safe-json-stringify@1.0.3, mv@2.1.1)
1.5 Create a new iRulesLX plugin
Local Traffic >> iRules >> LX Plugin >> “Create”
Supply the following:
- Name: ldap_modify_plugin
- From Workspace: ldap_modify_workspace
Select “Finished" to save.
If you look in /var/log/ltm, you will see the extension start a process per TMM for the iRuleLX plugin.
big-ip1 info sdmd[6415]: 018e000b:6: Extension /Common/ldap_modify_plugin:ldap_modify_extension started, pid:24396
big-ip1 info sdmd[6415]: 018e000b:6: Extension /Common/ldap_modify_plugin:ldap_modify_extension started, pid:24397
big-ip1 info sdmd[6415]: 018e000b:6: Extension /Common/ldap_modify_plugin:ldap_modify_extension started, pid:24398
big-ip1 info sdmd[6415]: 018e000b:6: Extension /Common/ldap_modify_plugin:ldap_modify_extension started, pid:24399
Step 2 – Create a test Access Policy
2.1 Create an Access Profile and Policy
We can now bring it all together using the Visual Policy Editor (VPE). In this test example, I will not be using a password just for simplicity.
Access Policy >> Access Profiles >> Access Profile List >> “Create”
Supply the following:
- Name: ldap_modify_ap
- Profile Type: LTM-APM
- Profile Scope: Profile
- Languages: English (en)
- Use the default settings for all other settings.
Select “Finished” to save.
2.2 Edit the Access Policy in the VPE
Access Policy >> Access Profiles >> Access Profile List >> “Edit” (ldap_modify_ap)
On the fallback branch after the Start object, add a Logon Page object.
Change the second field to:
- Type: text
- Post Variable Name: attribute
- Session Variable Name: attribute
- Read Only: No
Add a third field:
- Type: text
- Post Variable Name: value
- Session Variable Name: value
- Read Only: No
In the “Customization” section further down the page, set the “Form Header Text” to what ever you like and change “Logon Page Input Field #2” and “Logon Page Input Field #3” to something meaningful, see my example below for inspiration. Leave the “Branch Rules” as the default. Don’t forget to “Save”.
On the fallback branch after the Logon Page object, add an AD Query object.
This step verifies the username is correct against Active Directory/LDAP, returns the Distinguished Name (DN) and stores the value in session.ad.last.attr.dn which will be used by the iRulesLX.
Supply the following:
- Server: Select your LDAP or AD Server
- SearchFilter: sAMAccountName=%{session.logon.last.username}
- Select Add new entry
- Required Attributes: dn
Under Branch Rules, delete the default and add a new one, by selecting Add Branch Rule.
Update the Branch Rule settings:
Name: AD Query Passed
Expression (Advanced): expr { [mcget {session.ad.last.queryresult}] == 1 }
Select “Finished”, then “Save” when your done.
On the AD Query Passed branch after the AD Query object, add a Variable Assign object.
This step assigns the Attribute Name to session.ldap.modify.attribute and the Attribute Value entered on the Logon Page to session.ldap.modify.value.
Supply the following:
- Name: Assign LDAP Variables
Add the Variable assignments by selecting Add new entry >> change.
Variable Assign 1:
- Custom Variable (Unsecure): session.ldap.modify.attribute
- Session Variable: session.logon.last.attribute
Variable Assign 2:
-
- Custom Variable (Secure): session.ldap.modify.value
- Session Variable: session.logon.last.value
Select “Finished”, then “Save” when your done.Leave the “Branch Rules” as the default.
On the fallback branch after the Assign LDAP Variables object, add a iRule object.
Supply the following:
- Name: LDAP Modify
- ID: ldap_modify
Under Branch Rules, add a new one, by selecting Add Branch Rule.
Update the Branch Rule settings:
Name: LDAP Modify Successful
Expression (Advanced): expr { [mcget {session.ldap.modify.result}] == "0" }
Select “Finished”, then “Save” when your done.
The finished policy should look similar to this:
As this is just a test policy I used to test my Node.JS and to show how the LDAP Modify works, I will not have a pool member attached to the virtual server, I have just left the branch endings as Deny. In a real word scenario, you would not allow a user to change any LDAP Attributes and Values.
Apply the test Access Policy (ldap_modify_ap) to a HTTPS virtual server for testing and the iRuleLX under the Resources section.
Step 3 - OK, let’s give this a test!
To test, just open a browser to the HTTPS virtual server you created, and supply a Username, Attribute and Value to be modified. In my example, I want to change the Value of the carLicense attribute to test456.
Prior to me hitting the Logon button, I did a ldapsearch from the command line of the BIG-IP:
ldapsearch -x -h 10.1.30.101 -D "cn=LDAP Admin,cn=users,dc=f5,dc=demo" -b "dc=f5,dc=demo" -w 'Password123' '(sAMAccountName=test.user)' | grep carLicense
carLicense: abc123
Post submission, I performed the same ldapsearch and the carLicense value has changed. It works!
ldapsearch -x -h 10.1.30.101 -D "cn=LDAP Admin,cn=users,dc=f5,dc=demo" -b "dc=f5,dc=demo" -w 'Password123' '(sAMAccountName=test.user)' | grep carLicense
carLicense: test456
Below is some basic debug log from the Node.JS:
big-ip1 info sdmd[6415]: 018e0017:6: pid[24399] plugin[/Common/ldap_modify_plugin.ldap_modify_extension] Creating LDAP Client.
big-ip1 info sdmd[6415]: 018e0017:6: pid[24399] plugin[/Common/ldap_modify_plugin.ldap_modify_extension] LDAP Bind Successful.
big-ip1 info sdmd[6415]: 018e0017:6: pid[24399] plugin[/Common/ldap_modify_plugin.ldap_modify_extension] LDAP Modify Successful.
big-ip1 info sdmd[6415]: 018e0017:6: pid[24399] plugin[/Common/ldap_modify_plugin.ldap_modify_extension] Unbind Successful.
Conclusion
You can now modify LDAP attribute values, in safe and in supported manner with iRulesLX. Think of the possibilities!
For an added bonus, you can add addtional branch rules to the iRule Event - LDAP Modify, as the Node.JS returns the following error codes:
1 - LDAP Bind Failed
2 - LDAP Modified Failed
I would also recommend using Macros.
Please note, this my own work and has not been formally tested by F5 Networks.