Forum Discussion

SysTopher's avatar
SysTopher
Icon for Nimbostratus rankNimbostratus
Jan 20, 2016

F5 LTM virtual server with dual LDAP sources using LDAP Proxy iRule

Hey everyone,

 

I'm looking to setup an LDAP virtual servers, but I need it to be able to check against two different domain LDAP sources. We have two domains and users who need to access an application from both domains. Unfortunately the application has a limitation of only being able to specify one LDAP connection.

 

I would like to point the application at our LDAP virtual server and then have it query one domain and then the other if the user isn't found.

 

I've found the LDAP Proxy iRule that seems to be on the right track, but I'm not quite fluent in iRules yet to figure out if this can help me accomplish what I'm hoping to do.

 

Has anyone successfully used the LDAP Proxy iRule to allow users to authenticate this way?

 

8 Replies

  • Hi SysThopher,

     

    since nobody has answered yet, I'll take my luck and try to help out... ;-)

     

    The LDAP Proxy iRule is very advanced and developed for a very specific purpose. I guess it would require some pretty good LDAP protocol knowledge (or enought time to study all the required opcodes) to change this rule with success.

     

    To estimate the required effort, you may answer a few simple questions...

     

    1. Is your LDAP application using a "simple" LDAP bind? (you may ask the vendor or use wireshark)
    2. Are your users providing a NT-DOMAIN or UPN notation or any other prefix or suffix that can be mapped to a given LDAP instance? Note: It would be already enough, if just one of your LDAPs uses a fixed convention...
    3. Are you using the LDAP just for a pure bind authentication, or do you need to read/write access to it?
    4. Are your application require LDAP or LDAPS access?

    Cheers, Kai

     

  • Hello Kai,

     

    Thank you very much for the response.

     

    1. I'm checking with the vendor on the simple bind.
    2. Our hope was that the users would not have to provide the domain notation in any way, since many of them don't necessarily know it. We give them a username and they don't really know the domain it's attached to. So the hope would be that we could have them type their username only and the proxy would query both. The naming conventions between the two domains are different so there is not a chance of the same username existing in both.
    3. LDAP would be used purely for authentication.
    4. The application works with both LDAP and LDAPS, but we had planned to use LDAPS.
  • Hi SysTopher,

    sounds promissing so far...

    regarding 1.) you may use a wireshark capture to find out. A simple Bind always contains clear text credentials. So if you see your username password on LDAP(without S) on port 389, then it would be a "simple" Bind authentication...

    regarding 2.) If the usernames do have a fixed notation, then it could be possible to just parse the initial bind request for certain domain strings and then issue a [pool] command to select the LDAP instance. In this case you dont even have to dig into the LDAP opcodes. It would then require a very simple iRule then to pull of the trick...(I guess less than 20 lines)

    regarding 3.) is it really pure authentication (aka. validating the user credentials) or do you need to resolve group memberships (aka. authorizing the users)? If the later is the case, are the different LDAPs sharing an unified base name for the lookups or at least having an identical Base-DN lenght? I'm asking since I duno, if LDAP opcodes are always taking care of the field lenght or if LDAP uses fixed limiters here and there. I'm just preparing for the worst... 😉

    regarding 4.) its not that important for the final solution. Pure LDAP is just easier to analyse... ;-D

    Cheers, Kai

  • Hi Kai,

     

    It looks like the application would require read access to ldap. Here is some information the vendor has provided about the LDAP access:

     

    The application allows system administrators to specify a base DN for authentication queries. Using this base DN, the username (sAMAccountName or userPrincipalName attribute) and hashed password, the application will issue a subtree LDAP query.

     

    Does the application expect to read LDAP attributes from a user entry via LDAP? Yes: login disabled, expiration date, account locked (too many login attempts), password expired, account expired

     

    What LDAP query is used after bind to authenticate the user? depends on the configuration set by system administrator. A basic example would be: (&(objectClass=user)(userPrincipalName={0}))

     

    If "sam" (Active Directory sAMAccountName) is used, then the default query could look like: (&(objectClass=organizationalPerson)(sAMAccountName={0}))

     

    System administrator can configure the IQX application to perform Ldap authentication either by trying an LDAP bind (using provided username and hashed password) or by using a bind user. When using a bind user, the username and password for bind user are stored in a property file. When using a bind user and "sam" attribute, system administrators can set extra filters for LDAP queries (for example to select only users in certain groups or OU).

     

  • Hi SysTopher,

    Did a quick test in my lab using wireshark, ldap admin and some existing hex replacement iRules.

    Here are my results...

    • It seems to be possible to make a simple routing decission on the initial LDAP bind using a well known username suffix/prefix pattern. Its plain ASCII...
    • It seems to be possible to simply hex replace the Base-DN. But just as long as the input an output Base-DN name would have the same length.
    • It seems to be possible to pad as much as needed SPACE characters into the Base-DN translation to maintain the same Base-DN length. (e.g App using "DC=itacs,DC=net" and F5 translating to "DC=itacs, DC=de")
    • It seems to be possible to hex translate just the initial Base-DN search. Well, at least my LDAP client didn't complained that the Base-DN has entirely changed for the retrieved results. It has even followed the provided referals to the original Base-DN.

    It shouldn't be that complicated to wrap my test snippets into an PoC iRule for further testings. Give me a few days... its weekend now.. 😉

    Cheers, Kai

     

  • Hi SysTopher,

    I've polished my test code a little...

    The iRule would forward the LDAP(S) requests to a different pool, if the username of the initial simple-bind request matches any of the $static::other_domains strings. In addition it would then translate any requests to the original Base-DN to match the new Base-DN of the other LDAP instance.

    Cheers, Kai

    Config

     

    when RULE_INIT {
    
        
         Minimalistic simple-bind request LDAP(S) proxy with Base-DN rewrite
        
    
         Configuration of the other other LDAP instance username prefix/suffixes
        set static::other_domains [list "itacs\\" "@itacs.de"]              ; List of lower case domain strings
    
         Configuration of the other LDAP(S) instance pool names
        set static::other_ldap_poolname OTHER_LDAP_POOL                     ; Value of the other pool name
        set static::other_ldaps_poolname OTHER_LDAPS_POOL                   ; Value of the other pool name
    
         Configuration of the Base-DN translation strings
        
         Important: The Base-DNs MUST have the same lenght.
                    You have to pad SPACES to match the length
        
        binary scan "OU=xyz,DC=your-domain,DC=tld" H* temp(dn_default)      ; This is the default Base-DN
        binary scan "OU=f5-team,  DC=itacs, DC=de" H* temp(dn_other)        ; This is the other Base-DN. Pad SPACES to match the length
    
        set static::other_base_dn_map [list $temp(dn_default) $temp(dn_other)]
        unset -nocomplain temp
    
    }
    

     

    LDAP:386 iRule

     

    when CLIENT_ACCEPTED {
         TCP session init
        set session_binding_ldap 1
        set session_other_active 0
         Collecting TCP data
        TCP::collect
    }
    when CLIENT_DATA {
        if { $session_binding_ldap } then {
             Searching for simple Bind request to the other LDAP instance 
            set session_binding_ldap 0
            foreach temp(domain_string) $static::other_domains {
                if { [string tolower [TCP::payload]] contains $temp(domain_string) } then {
                     Forwarding the request to the other LDAP instance 
                    set session_other_active 1
                    pool $static::other_ldap_poolname
                    log -noname local0.debug "LDAP simple bind request for other LDAP instance detected. Forwarding the connection to pool [LB::server pool]"
                    break
                }
            }
            if { $session_other_active == 0 } then {
                 Forwarding the request to the default LDAP instance
                log -noname local0.debug "LDAP request for default LDAP instance detected. Forwarding the connection to pool [LB::server pool]"
                 Releasing TCP data
                TCP::release
                unset -nocomplain temp
            }
        }
        if { $session_other_active } then {
             Translating Base-DNs for the other LDAP instance
            binary scan [TCP::payload] H* temp(hex_tcp_payload)
            set temp(new_tcp_payload) [binary format H* [string map $static::other_base_dn_map $temp(hex_tcp_payload)]]
            TCP::payload replace 0 [string length [TCP::payload]] $temp(new_tcp_payload)
             Releasing TCP data
            TCP::release
             Collecting further TCP data
            TCP::collect
            unset -nocomplain temp
        }
    }
    

     

    LDAPS:636 iRule

     

    when CLIENTSSL_HANDSHAKE {
         SSL session init
        set session_binding_ldap 1
        set session_other_active 0
         Collecting SSL data
        SSL::collect
    }
    when CLIENTSSL_DATA {
        if { $session_binding_ldap } then {
             Searching for simple Bind request to the other LDAPS instance 
            set session_binding_ldap 0 
            foreach temp(domain_string) $static::other_domains {
                if { [string tolower [SSL::payload]] contains $temp(domain_string) } then {
                     Forwarding the request to the other LDAP instance 
                    set session_other_active 1
                    pool $static::other_ldaps_poolname
                    log -noname local0.debug "Bind request for other LDAPS instance detected. Forwarding the connection to pool [LB::server pool]"
                    break
                }
            }
            if { $session_other_active == 0 } then {
                 Forwarding the request to the default LDAPS instance
                log -noname local0.debug "LDAPS request for default LDAPS instance detected. Forwarding the connection to pool [LB::server pool]"
                 Releasing SSL data
                SSL::release
                unset -nocomplain temp
            }
        }
        if { $session_other_active } then {
             Translating Base-DNs for the other LDAPS instance
            binary scan [SSL::payload] H* temp(hex_ssl_payload)
            set temp(new_ssl_payload) [binary format H* [string map $static::other_base_dn_map $temp(hex_ssl_payload)]]
            SSL::payload replace 0 [string length [SSL::payload]] $temp(new_ssl_payload)
             Releasing SSL data
            SSL::release
             Collecting further SSL data
            SSL::collect
            unset -nocomplain temp
        }
    }
    

     

  • Wow Kai, this is great! I haven't had a chance to review it closely yet, but I REALLY appreciate the effort and assistance with this. I unfortunately have not heard back yet from the vendor on the simple bind, but hopefully that will not cause issues once I do hear back.

     

    I'll take a look at your iRules and see what I can get working. Again, MUCH appreciation for your willingness to go above and beyond to assist with this. I'll let you know my results.

     

    • Kai_Wilke's avatar
      Kai_Wilke
      Icon for MVP rankMVP
      You're welcome. I had somewhat fun while exploring/hacking the LDAP protocol and coding the solution. It was absolutely a fresh breath compared to the daily HTTP/HTTPS codings^^ ;-)