cancel
Showing results for 
Search instead for 
Did you mean: 
Smithy
Cirrostratus
Cirrostratus

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.

0151T000003d6tfQAA.png

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
        }
    }
}

0151T000003d6tgQAA.png

 

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.

0151T000003d6thQAA.png

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.

0151T000003d6tiQAA.png

 

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.

0151T000003d6tjQAA.png

0151T000003d6tkQAA.png

 

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”.

0151T000003d6tlQAA.png

 

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

0151T000003d6tmQAA.png

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.

0151T000003d6tnQAA.png

0151T000003d6toQAA.png

 

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.

    0151T000003d6tpQAA.png

     

    On the fallback branch after the Assign LDAP Variables object, add a iRule object.

    Supply the following:

    • Name: LDAP Modify
    • ID: ldap_modify

    0151T000003d6tqQAA.png

    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.

    0151T000003d6trQAA.png

     

    The finished policy should look similar to this:

    0151T000003d6tsQAA.png

     

    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.

    0151T000003d6ttQAA.png

     

    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.

    0151T000003d6tuQAA.png

     

    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.

    Comments
    gregan_306943
    Nimbostratus
    Nimbostratus

    Hi Brett,

     

    When installing ldapjs i'm getting errors returned in the cli.

     

    Active:In Sync] irule_ldap_extension npm install ldapjs -save npm ERR! Linux 2.6.32-431.56.1.el6. npm ERR! argv "/usr/bin/node" "/usr/bin/.npm__" "install" "ldapjs" "-save" npm ERR! node v0.12.15 npm ERR! npm v2.15.1 npm ERR! code EAI_AGAIN npm ERR! errno EAI_AGAIN npm ERR! syscall getaddrinfo

     

    npm ERR! getaddrinfo EAI_AGAIN npm ERR! npm ERR! If you need help, you may report this error at: npm ERR! https://github.com/npm/npm/issues

     

    JaiASingla_2931
    Nimbostratus
    Nimbostratus

    Hi Gregan,

     

    You will be fine if you make sure DNS is working on your unit and you have internet connectivity on the unit,

     

    Thanks Jai

     

    forsan
    Altostratus
    Altostratus

    Hi,

     

    I'm getting some errors when trying this out. Have you seen this before?

     

    info sdmd[7077]: 018e0017:6: pid[25600] plugin[/Common/ldap_modify_plugin.ldap_modify_extension] dn: null,attr: employeeID,val: $CK$yeY8fCTj$mf5momsvuPKFH7MHQFlJPA==

     

    info sdmd[7077]: 018e0017:6: pid[25600] plugin[/Common/ldap_modify_plugin.ldap_modify_extension] Creating LDAP Client.

     

    info sdmd[7077]: 018e0017:6: pid[25600] plugin[/Common/ldap_modify_plugin.ldap_modify_extension] LDAP Bind Successful.

     

    err sdmd[7077]: 018e0018:3: pid[25600] plugin[/Common/ldap_modify_plugin.ldap_modify_extension] /var/sdm/plugin_store/plugins/:Common:ldap_modify_plugin_45236_4/extensions/ldap_modify_extension/node_modules/ldapjs/lib/dn.js:171

     

    err sdmd[7077]: 018e0018:3: pid[25600] plugin[/Common/ldap_modify_plugin.ldap_modify_extension] throw new TypeError('name (string) required');

     

    err sdmd[7077]: 018e0018:3: pid[25600] plugin[/Common/ldap_modify_plugin.ldap_modify_extension] ^

     

    err sdmd[7077]: 018e0018:3: pid[25600] plugin[/Common/ldap_modify_plugin.ldap_modify_extension] TypeError: name (string) required

     

    err sdmd[7077]: 018e0018:3: pid[25600] plugin[/Common/ldap_modify_plugin.ldap_modify_extension] at Object.parse (/var/sdm/plugin_store/plugins/:Common:ldap_modify_plugin_45236_4/extensions/ldap_modify_extension/node_modules/ldapjs/lib/dn.js:171:11)

     

    info sdmd[7077]: 018e0017:6: pid[25600] plugin[/Common/ldap_modify_plugin.ldap_modify_extension] MPI.0 ilx:45687:1 size 2

     

    info sdmd[7077]: 018e000f:6: Extension /Common/ldap_modify_plugin:ldap_modify_extension pid 25600 exited with status 1

     

    info sdmd[7077]: 018e000b:6: Extension /Common/ldap_modify_plugin:ldap_modify_extension started, pid:25626

     

    info sdmd[7077]: 018e000f:6: Extension Unknown Extension pid 25600 exited with status 1 err sdmd[7077]: 018e0011:3: Received sigchld for unknown pid 25600

     

    Br Andréas

     

    Slayer001
    Cirrus
    Cirrus

    I came across this when looking for a method to let the user reset their forgotten password. Is there a way to reset the unicodePwd attribute via this method. As I get plugin[/Common/plugin_ldap_modify.ldap_modify_extension] LDAP Modify Failed. when trying this.

    I found some more information regarding the unicodePwd attribute (https://ldapwiki.com/wiki/Passwords%20Using%20LDIF😞

    "The modify request should contain a single replace operation with the new password enclosed in quotation marks and be Base64 encoded"

    I added a base64 encoded value of the password between " marks but it still fails with the same Modify failed. and following error code: setup_io: it's not allowed to set the NT hash password directly

     

    I noticed that unicodePwd has 2 colons behind it when it is changed via ldapmodify to indicate the base64 encoding. I'm not very familiar with javascript but I guess that's not included in the ldap_modify_extension?

    Gym
    Cirrus
    Cirrus

    @Slayer001

    I've successfully implemented what you're trying to do, as a proof of concept. You need to modify the index.js in the iLX workspace. For example (what I did), you could add the following function that encodes the value, then call the function IF the attribute name is unicodePwd.

    // From https://github.com/ldapjs/node-ldapjs/issues/92#issuecomment-29070786 function encodePassword(password) { return new Buffer('"' + password + '"', 'utf16le').toString(); }

    To call that function, I inserted the following line just after the three LDAP variables are declared:

    if (ldap_attribute == 'unicodePwd') { ldap_value = encodePassword(ldap_value) }

     

     

     

    Scott_Larson
    Nimbostratus
    Nimbostratus

    ​on TMOS 13.1, I followed this example exactly, but I get an error right away when it's trying to parse the URL to create the LDAP client (note: this is an error constructing the objects (parsing the URL), not connecting to the server!)

     

    Here are the errors that get logged (I removed the timestamp and other extraneous data from these log messages):

     

    plugin[/Common/ldap_modify_plugin.ldap_modify_extension] Creating LDAP Client.

     plugin[/Common/ldap_modify_plugin.ldap_modify_extension] /var/sdm/plugin_store/plugins/:Common:ldap_modify_plugin_118679_3/extensions/ldap_modify_extension/node_modules/ldapjs/lib/url.js:15

    plugin[/Common/ldap_modify_plugin.ldap_modify_extension] throw new TypeError(urlStr + ' is an invalid LDAP url (scope)')

    plugin[/Common/ldap_modify_plugin.ldap_modify_extension] TypeError: ldaps://10.1.30.101:636 is an invalid LDAP url (scope)

    plugin[/Common/ldap_modify_plugin.ldap_modify_extension]    at Object.parse (/var/sdm/plugin_store/plugins/:Common:ldap_modify_plugin_118679_3/extensions/ldap_modify_extension/node_modules/ldapjs/lib/url.js:15:13)

    plugin[/Common/ldap_modify_plugin.ldap_modify_extension]    at new Client (/var/sdm/plugin_store/plugins/:Common:ldap_modify_plugin_118679_3/extensions/ldap_modify_extension/node_modules/ldapjs/lib/client/client.js:114:33)

    plugin[/Common/ldap_modify_plugin.ldap_modify_extension]    at Object.createClient (/var/sdm/plugin_store/plugins/:Common:ldap_modify_plugin_118679_3/extensions/ldap_modify_extension/node_modules/ldapjs/lib/client/index.js:17:12)

    plugin[/Common/ldap_modify_plugin.ldap_modify_extension]    at Object.ldap_modify (/var/sdm/plugin_store/plugins/:Common:ldap_modify_plugin_118679_3/extensions/ldap_modify_extension/index.js:55:28)

    plugin[/Common/ldap_modify_plugin.ldap_modify_extension]    at ILXServerWrap.ilxServerMessageCallback [as onmessage] (/var/sdm/plugin_store/plugins/:Common:ldap_modify_plugin_118679_3/extensions/ldap_modify_extension/node_modules/f5-nodejs/lib/ilx_server.js:150:44)

     

    Note: I looked at line 15 of url.js and here are the lines leading up to that and including line 15:

    try {

       parsedURL = new url.URL(urlStr)

    } catch (error) {

       throw new TypeError(urlStr + ' is an invalid LDAP url (scope)')

    }

     

     

    Note that although the error message says "(scope)", I do not believe the error lies with the scope since the example didn't even contain a scope.  I think (scope) is a typo by the developer. 

     

    Anyone have any ideas?

     

    Bernhard_Mitter
    Nimbostratus
    Nimbostratus

    Hi, i have exactly the same error "TypeError: ldaps://10.1.30.101:636 is an invalid LDAP url (scope)" on my 15.1.2 installation.

     

    EDIT:

    I solved the problem by downgrading ldapjs node module to version 1.0.1.

    npm install ldapjs@1.0.1 --no-bin-links --save

    cheers, Bernhard

    Mac
    Nimbostratus
    Nimbostratus

    I am using this to modify AD.  Everything works until there is a "comma" in the CN portion of the Distinguished Name.  Has anyone encountered this issue?   Any suggesitons on a fix?

     

    Works

    cn=doe jon, ou=example, dc=com

    Fails

    cn=doe, jon, ou=example, dc=com

     

     

     

    SOLVED.

    Added the following escape procedure

    proc FixDN dn {
    return [regsub -all {\\\,} $dn {\\,}]
    }

    Version history
    Last update:
    ‎27-Sep-2016 04:45
    Updated by:
    Contributors