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?
Although Stingray does not include a built-in LDAP protocol parser, you can use TrafficScript LDAP Library 1.1 to provide the TrafficScript functionality to decode and build up LDAP packets. Then with a little TrafficScript logic, you can implement some sophisticated traffic management policies for LDAP traffic.
Example Uses
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.rts 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 of course).
Chose an 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=nbttech,dc=com” then we will use the nbttech 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.rts 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=nbttech,dc=com” )){
pool.use(“nbttech-ldap”);
} elseif( string.endswith( $details["bindDN"], “dc=riverbed,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.
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.rts 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!" );
}
}
StartTLS
At the time of writing, Stingray doesn't support StartTLS for LDAP out of the box, but with this TrafficScript you can implement it yourself 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.rts 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.
Use LDAP Simple Authentication for other Services
The libLDAPauth.rts library is a small library which uses libLDAP to verify user/passwords against an ldap server. At present it only does a simple BindRequest. You may want to look at the built-in 'auth.query()' TrafficScript function now; that makes this legacy library somewhat redundant.
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.rts as la;
$auth = la.checkLdapAuth( "10.4.5.1", "389", "cn=user,dc=znbttech,dc=com", "password” );
if ( $auth == 0 ) {
# success
log.info( "UserAuthenticated" );
} else {
# Failed
log.warn( "User failed authentication:" . $auth);
}
... View more