Reply
Golden
emarcus
Posts: 232
Registered: 05-28-2010
0

TrafficScript LDAP Library

[ Edited ]

(Originally posted April 7, 2011)

 

About this extension

  • Author:       Mark Boddington
  • Released:  07/04/2011 - 8:42pm
  • License:     BSD

 

Have you ever wanted to make use of LDAPs StartTLS functionality, or to restrict the search filters and attributes which external users can use when querying your directory service? How about simply rejecting or closing LDAP connections at will?

Well now you can.....

 

The Extensions


  • libBerCodec – This is a TrafficScript library which implements all of the required Basic Encoding Rules (BER) functionality for LDAP. It does not completely implement BER though, LDAP doesn't use all of the available types, and this library hasn't implemented those not required by LDAP.
  • LibLdap – This is a TrafficScript library of functions which can be used to inspect and manipulate LDAP requests and responses. It requires libBerCodec to encode the LDAP packets.
  • LibLdapAuth – This is a small library which uses libLdap to provide simple LDAP authentication to other services.

Basic Encoding Rules (BER)

 

BER is part of the ASN.1 specification, and BER encoding is used by LDAP to encode its data. I'm not going to say anything more about BER. The library provided here is complete enough for LDAP, but it is by no means complete for the BER specification. For example it does not currently know how to encode floats (that part of the RFC made my brain hurt).

 

LibLdap

 

The libLdap library contains the functions you are most likely to use from your TrafficScripts directly. Most of this article will deal with a number of use-cases where this library can help. So lets start with something simple:

 

Example Uses

 

LibLdap – Restrict LDAP Operations

 

The script below will read in a packet from the client using getPacket(). The getPacket() function returns a hash containing the current request in a hash along with the protocolOp (Protocol Operation), Message ID (all LDAP packets have an ID), and some other data. Once we have the packet and we know what the protocolOp is we can decide what to do with it.

 

    Import libLdap as ldap;
    $packet = ldap.getPacket();
    $op = ldap.getOp($packet);
    $ip= request.getRemoteIP();
    if(!string.ipmaskmatch( $ip,"10.0.0.0/8")){
            # Non LAN clients can only BIND, UNBIND, and SEARCH
            if(($op !=“BindRequest”)&&($op !=“SearchRequest”)
              &&($op !=“UnbindRequest”)){
                    # Send back a Notice of Disconnection and close the socket
                    ldap.close();
            }
    }

 

 

In this case, we simply check if the client is on the LAN (in the 10.0.0.0/8 subnet), and if it is not, then we restrict the types of requests they can make to binding, searching, and unbinding from the server.

 

Please Note: It's important to remember to set LDAP TrafficScript rules you create to run on "every" request, not just "once" (unless you're only interested in the Bind ofcourse).


LibLdap – Chose a LDAP server based on bindDN

 

Here's another scenario for you, this time we have multiple LDAP servers sitting on the network, and we want to direct users to a server based on their binding credentials. If the BindDN contains “dc=zeus,dc=com” then we will use the Zeus ldap Servers. Alternatively if the BindDN contains “dc=riverbed,dc=com” then we will use the Riverbed ldap servers. Anonymous binds will be rejected.

 

    Import libLdap as ldap;
    $packet = ldap.getPacket();
    $op = ldap.getOp($packet);
    if( $op =="BindRequest"){  
       $details = ldap.getBindDetails($packet);
       if( $details["bindDN"]==“”){
          # Anonymous bind. I don't think so.
          ldap.rejectAnonBind($packet["messageID"]);
       }elseif(string.endswith($details["bindDN"],“dc=zeus,dc=com”)){
          pool.use(“zeus-ldap”);
       }elseif(string.endswith($details["bindDN"],“dc=zeus,dc=com”)){
          pool.use(“riverbed-ldap”);
       }else{
          # Unknown bind domain. Log a warning and reject with invalid Credentials (49)
          log.warn(“UnknownBind:“. $details["bindDN"]);
          ldap.rejectBind($packet["messageID"],49);
       }
    }

 

 

The getBindDetails() function also returns details of the authentication method (SIMPLE/SASL), the LDAP version, and the authentication data itself.

 

LibLdap – Override Attributes and filters

 

Another thing you may wish to do is to limit which attributes are sent out from the LDAP server, and possibly which parts of the LDAP tree are searchable. The libLdap library can be used to set attributes and filters on incoming requests to restrict which information the LDAP server returns.

In the TrafficScript below, we are going to override everything (mwah ha ha ha)....

 

  • First the script will detect the BindRequest and use connection.data.set() to store the bindDN in memory, If the bind is anonymous we will close the connection.
  • If we then get a SearchRequest from the user we will retrieve the bindDN and limit the query base object to match the “dc” or “ou” used in the bind.
  • We will also limit the attributes to “cn”, “uid”, and “gid”.
  • Set the filter to match the bind users common name, and finally
  • set the search scope to a single level.

You are obviously unlikely to limit all of those things at once or all the time, but this sample script lets you see how they all work:

 

    import libLdap as ldap;
    $packet = ldap.getPacket();
    $op = ldap.getOp($packet);
    if( $op =="BindRequest"){
       
       $details = ldap.getBindDetails($packet);
       if( $details["bindDN"]==""){
          ldap.rejectAnonBind($packet["messageID"]);
       }else{
          connection.data.set("bindDN", $details["bindDN"]);
       }
         
    }elseif( $op =="SearchRequest"){
       
       $details = ldap.getSearchDetails($packet);  
       $bindDN = connection.data.get("bindDN");
       
       if(string.regexMatch($bindDN,"(cn=.*?),(dc|ou=.*)")){
         
          # Set the Base object to be in the same domain as the binder
          $details["baseObject"]= $2;
         
          # restrict the attributes returned to cn, uid and gid
          ldap.setSearchAttributes($details,"cn uid gid");
         
          # Override the user supplied filter too. They can only search themselves.
          ldap.setSearchFilter($details,"(". $1 .")");
         
          # Change the scope... why not ;-)
          $details["scope"]=1;
         
          # Commit the changes
          request.set( ldap.updateSearch($packet, $details));
       }else{
          # Oh dear failed to process search... Lets reject it ;-)
          ldap.rejectSearch($packet["messageID"],"Failed to nobble search. So Denied. Sorry!");
       }
    }

 

 

Libldap – StartTLS

 

At the time of writing ZTM doesn't support StartTLS out of the box, but with this TrafficScript you can implement it your self quite simply. You will need to create two LDAP services, one plain ldap, and the other a ldaps service with SSL Offload enabled. Then you need a loop back pool to link the plain ldap service with the ldaps service. Once you have that set up, you just need the script:

 

    import libLdap as ldap;
    if( connection.data.get("TLS")){
       # the connection is already in TLS mode, stop processing
       break;
    }
    $packet = ldap.getPacket();
    $op = ldap.getOp($packet);
    if( $op =="ExtendedRequest"){
       if( ldap.isStartTLS($packet)){
          $ip = request.getRemoteIP();
          # Only Accept StartTLS requests if the clients are not local
          if(!string.ipmaskmatch( $ip,"10.0.0.0/8")){
             # Set the TLS flag on the connection
             connection.data.set("TLS","yes");
             # Send the TSL acceptance packet
             ldap.acceptStartTLS($packet);
             # Use the ldaps loopback pool, and we're done :-)
             pool.use("ldap-loop");
          }else{
             # LAN clients have TLS rejected and can carry on in clear text
             ldap.rejectStartTLS($packet,"Use Plain Text please");
          }
       }
    }

 

 

The first thing this script does is check to see if a TLS flag has been set on the connection. If it exists then startTLS has already happened and we should exit, the packet is almost certainly encrypted. If the TLS flag is not set, we continue. The code checks for the startTLS command, and if the user is a remote user it sends back an accept, sets the TLS flag on the connection, and selects the loop back pool. Further LDAP processing can be performed in the LDAPS Virtual Server. If however we find the user is in the 10/8 subnet we reject the startTLS command and make them continue in plain text.

 

LibLdapAuth – Use LDAP Simple Authentication for other Services

 

The libLdapAuth library is a small library which uses libLdap to verify user/passwords against an ldap server. At present it only does a simple BindRequest. The simplest way to use is by using the checkLdapAuth() function. This function returns the LDAP result code, which should be 0 if the authentication was successful.

 

    Import libLdapAuth as la;
    $auth = la.checkLdapAuth("127.0.0.1","389",
           "cn=user,dc=zeus,dc=com","password” );
    if ( $auth == 0 ) {
       # success
       log.info("UserAuthenticated");
    } else {
       #Failed
       log.warn("User failed authentication:" . $auth);
    }

 

 

Well that’s all from me. I hope you have found these LDAP libraries, and my selection of examples useful. And if you have a need for which the current release does not cater, then please feel free to embrace and extend to suit. Enjoy!

 

Download this extension below:

‬‪‬‪‬‪