Forum Discussion
Ldap query from ltm
Hi all, I'm searching an irule that would direct all the authenticated users (that belong to a specific group defined on the ldap profile/object) to a specific pool. All the others users (that aren't on that group) have to be redirect to a secondary pool. I've found a lot of more complicated ladp irule but nothing for this scenario...I'm not black belt on writing irule and any help would be appreciated, Best regards, Mauro
38 Replies
- Kevin_Stewart
Employee
I wouldn't recommend trying to write an LDAP query iRule. It's certainly possible, but it's binary and very complex. Your best bet, in my opinion, is to have a look at the Access Policy Manager (APM) module. It'll do what you're asking for very easily and with minimal (if any) iRule coding.
- Kevin_Stewart
Employee
The LDAP auth profile is a remnant of the Advanced Client Authentication (ACA) module, and will soon disappear (reportedly post-v11). It'd also be a very complicated iRule to do what you're asking for, because the ACA LDAP auth iRule is designed specifically for auth and not query. Like I said, you can technically build an LDAP query/proxy with iRules, but you'd probably need an iRules black belt on staff to maintain it. So I think you have three primary options:
-
Investigate upgrading to a platform that supports three modules.
-
Investigate adding another (potentially virtual) LTM/APM layer.
-
Write a service on a local web server that can query LDAP and return the data via HTTP response. With this you can use a sideband call in an iRule to send the request to the web server and get your LDAP data that way.
-
- maurox_59221
Nimbostratus
Hi Kevin,
thank you, you're very helpful ! I'll test it soon, but before we need to purchase the APM license for the other (internal) BIG-IP cluster: in the middle, I'm wondering to start this configuration following your previous suggestion:
"Write a service on a local web server that can query LDAP and return the data via HTTP response. With this you can use a sideband call in an iRule to send the request to the web server and get your LDAP data that way"
I've already asked to my colleagues to configure an internal web server that will contain all the users that have to be authenticated before accessing this application. So,the client (a mobile device) will contact the VIP on the LTM and it will contact the internal web server using an irule: if the user is on the users' list, he could access the pool. If not, it won't access anything and would be dropped.
Do you have an irule similar that I could use as an example? Thank you in advance,
Mauro
- Kevin_Stewart
Employee
Okay, let's say you have a PHP web server that can make LDAP calls based on a parameter in the query string (ex. "?find=user"). Example:
set_time_limit(30); error_reporting(E_ALL); ini_set('error_reporting', E_ALL); ini_set('display_errors',1); $ldapserver = '10.80.0.200'; $ldapuser = 'CN=Administrator,CN=users,DC=mydomain,DC=com'; $ldappass = 'password'; $ldaptree = "CN=Users,DC=mydomain,DC=com"; if(!isset($_REQUEST["find"])) { echo "No query parameter"; } else { // query string parameter to use in search $findthis = $_REQUEST["find"]; // what value(s) to return $justthese = array("userPrincipalName"); $ldapconn = ldap_connect($ldapserver) or die("Could not connect to LDAP server."); if($ldapconn) { $ldapbind = ldap_bind($ldapconn, $ldapuser, $ldappass) or die ("Error trying to bind: ".ldap_error($ldapconn)); if ($ldapbind) { $result = ldap_search($ldapconn,$ldaptree, "(sAMAccountName=$findthis)", $justthese) or die ("Error in search query: ".ldap_error($ldapconn)); $data = ldap_get_entries($ldapconn, $result); if($data["count"] > 0) { echo $data[0]["userprincipalname"][0]; } else { echo "No data"; } } else { echo "LDAP bind failed..."; } } ldap_close($ldapconn); }** Ref: http://www.php.net/manual/en/function.ldap-search.php
- Kevin_Stewart
Employee
Now, create an internal virtual server to load balance this web service, and apply this iRule to your application virtual server to call the web service (via sideband with a query parameter).
when RULE_INIT { user-defined: name of internal web service virtual server set static::WSVIP "webservice-vs" user-defined: debug enable/disable (1/0) set static::DEBUG 1 } when HTTP_REQUEST { Prepare the sideband call set conn [connect -timeout 3000 -idle 30 -status conn_status $static::WSVIP] if { $static::DEBUG } { log local0. "conn_status = $conn_status" } if { $conn eq "" } { if { $static::DEBUG } { log local0. "Sideband connection could not be established" } return } Prepare user information to transmit to APM sideband call set userdata "bob.user" Create the data to send to the APM set data "GET /ldaplookup.php?find=$userdata HTTP/1.1\r\nHost: [HTTP::host]\r\nUser-Agent: cUrl\r\nAccept: */*\r\n\r\n" if { $static::DEBUG } { log local0. "data = $data" } Send the sideband call set send_info [send -timeout 3000 -status send_status $conn $data] if { $static::DEBUG } { log local0. "send_status = $send_status" } Receive the APM response (via data "peek") set start [clock clicks -milliseconds] for {set i 0} {$i <= $static::retries} {incr i} { set recv_data [recv -peek -status peek_status -timeout 10 $conn] if { [string match "HTTP/*\r\n\r\n*" $recv_data] } { if { [string match -nocase "*Content-Length: *" $recv_data] }{ set header_length [expr {[string first "\r\n\r\n" $recv_data] + 4}] set payload_length [findstr [string tolower $recv_data] "content-length: " 16 "\r"] if { $payload_length ne "" and $payload_length > 0 } { set recv_data [recv -peek -timeout 3000 -status recv_status [expr {$header_length + $payload_length}] $conn] break } else { break } } else { break } } } set returned_data [findstr $recv_data "\r\n\r\n" 4] if { $static::DEBUG } { log local0. "recv_data = $recv_data" } if { $static::DEBUG } { log local0. "filtered data = $returned_data" } Close the connection close $conn The data returned from the web server is now in the $returned_data variable... ...do something here... }The send string data now contains a query string instead of an arbitrary header. This could be done either way though.
Prepare user information to transmit to APM sideband call set userdata "bob.user" Create the data to send to the APM set data "GET /ldaplookup.php?find=$userdata HTTP/1.1\r\nHost: [HTTP::host]\r\nUser-Agent: cUrl\r\nAccept: */*\r\n\r\n"And then the returned sideband call LDAP data can be found in the $recv_data or $returned_data variables. What you do with that data after this is up to you. Also notice that the web service PHP will return "No data" if the LDAP query doesn't find anything. The $returned_data variable would actually contain this error message, so you could very simply issue a reject based on this value.
if { $returned_data equals "No data" } { log local0. "No data found - rejecting user" reject } - Kevin_Stewart
Employee
If you're talking about a query that entails a simple "if exists" check, or at best a single value lookup, then a data group of 1000 or more entries is both fast and efficient. So depending on what you're querying on in the request, the iRule would look something like this:
when HTTP_REQUEST { if { [class match $value_from_uri equals my_datagroup] } { do something here } } - Kevin_Stewart
Employee
"$value_from_uri" is an arbitrary variable I used to illustrate the syntax. I'm not sure exactly what you'd be searching the LDAP or web server for, or where in the request payload you'd find it. The point is that you're going to presumably extract some value from the request and then look for that value in data group. Here's another example to illustrate that:
when HTTP_REQUEST { if { [class match [string tolower [HTTP::uri]] starts_with my_datagroup] } { do something } }Here I'm searching the data group for the beginning of the request URI. Try to think about what you were going to query the LDAP server for (a user name perhaps?), what you expected to receive from the query (a list of URIs they could access? or just existence?), and where you were going to get the information from to make the request (a logon form?).
- Kevin_Stewart
Employee
So this value is defined by the client? Are you authenticating the user beyond this query string value?
To get this query string value, you can use the URI::query command. Example:
when HTTP_REQUEST { if { [string tolower [HTTP::uri]] contains "microsoft-server-activeSync?cmd" } { set user [URI::query [HTTP::uri] User] if { [class match $user my_datagroup] } { User in data group. Allow access? } } }By the way, is this user value in every request, or do we need to set and track a session token beyond this (initial?) request?
- maurox_59221
Nimbostratus
Hi kevin, finaly, as you've suggested, I've activated the APM module on another (virtual) appliance. But this is my first time working with the APM module and I'm having some problems:
This is the task list that you've suggested:
Provision APM on the separate box. OK, done
Create an APM LDAP AAA object that sets up the authentication to the LDAP server. OK. Do I need administrator right for the user?
Create an access policy that includes a simply LDAP query KO :-( I don't find on the APM menu where I can configure this access policy
Thanks in advance for your help,
Mauro
- maurox_59221
Nimbostratus
I've found that menu and I've created the AD auth and resources macro. But If I try to edit the "AD auth" object, I can't find the AAA ldap server previusly created :-(
- Kevin_Stewart
Employee
Create an APM LDAP AAA object that sets up the authentication to the LDAP server. OK. Do I need administrator right for the user?
It's an LDAP query, so you just need to specify an account that has the rights to make such a query.
Create an access policy that includes a simply LDAP query KO 😞 I don't find on the APM menu where I can configure this access policy
Once you've created the access policy, open it up, go to the third tab (Access Policy), and click the "Edit Access Policy for Profile" link. This will bring up a new window with the access profile's "visual policy". Inside the visual policy, create an LDAP Query agent, specify the previously-created LDAP AAA, and then the following properties:
-
SearchDN: the distinguished name path where the users can be found (ex. cn=users,dc=mydomain,dc=com)
-
SearchFilter: this is what you're looking for. The syntax would be something like:
attribute-name=%{session.value}where "attribute-name" is the literal name of the AD/LDAP attribute (ex. userPrincipalName, sAMAccountName, etc.). And "%{session.value}" is a session variable that you've already assigned prior to this LDAP query agent (via iRule or Variable Assignment agent).
-
Branch Rules: on the Branch Rules tab, click the "change" link next to the default expression, in the next window, click the "x" to the right of that expression to delete it, click the "Add Expression" button, and select "LDAP Query" and "LDAP Query Passed" from the drop down boxes, and click the "Add Expression" button. Click the "Finished" button, and then optionally change the name field to something more appropriate like "Query passed". Click Save.
With this in place, the agent will perform the query and follow the "Query passed" branch if the query was successful. Run a quick test, and then run an access policy report in the GUI. If the LDAP query was successful, you'll see a bunch of session.ldap.last.attr session variables in the session cache. Any of these can be used in evaluations after the LDAP query agent.
I've found that menu and I've created the AD auth and resources macro. But If I try to edit the "AD auth" object, I can't find the AAA ldap server previusly created
An AD Auth/Query will only allow an AD AAA object, likewise for an LDAP Auth/Query. You can use either to perform a query, but the LDAP query is generally faster.
-
Help guide the future of your DevCentral Community!
What tools do you use to collaborate? (1min - anonymous)Recent Discussions
Related Content
* Getting Started on DevCentral
* Community Guidelines
* Community Terms of Use / EULA
* Community Ranking Explained
* Community Resources
* Contact the DevCentral Team
* Update MFA on account.f5.com
