Forum Discussion
Jason_Witt_4207
Mar 10, 2006Historic F5 Account
Fun with ldap
For anyone that is interested, I recently was posed with the problem of proxying ldap requests. The requirement was to send read and write requests to different pools. As any familiar with ldap knows, you need to send a bind request to authenticate. The following will transparently resend the bind requests to the newly selected server prior to sending the new read/write request.
Other functionality could be added to this, such as more verification of the ldap fields to ensure a valid request is being made.
class ldap_writes {
6
8
10
12
}
rule ldap_proxy {
when RULE_INIT {
Read Pool
set ::readPool sun_ldap_read
Write Pool
set ::writePool sun_ldap_write
Turn on debugging
set ::debug 0
A lookup table for debugging
array set ::msg_types {
0 "bind request"
1 "bind response"
2 "unbind request"
3 "search request"
4 "search response"
6 "modify request"
7 "modify response"
8 "add request"
9 "add response"
10 "delete request"
11 "delete response"
12 "modifydn request"
13 "modifydn response"
14 "compare request"
15 "compare response"
16 "abandon request"
23 "extended request"
24 "extended response"
}
}
when CLIENT_ACCEPTED {
set rebind 0
set binding ""
set replayop ""
set writing 0
TCP::collect
}
when CLIENT_DATA {
Grab the current payload collected
set payload [TCP::payload]
Pull the first 2 bytes.
binary scan $payload H2c ber_t ber_len
The first byte is the tag signifying an LDAP message,
Always is hex 30, if that is not so reject
if { $ber_t ne "30" } {
reject
return
}
The second byte is one of two values:
a) The length of the packet minus the above
defining byte and the length byte
OR
b) an octet describing how many subsequent bytes
hold the packet length
In either case the message type (what we are after)
follows the message id field which too can be a variable
number of bytes.
set len_bytes 0
if { [expr [expr ($ber_len + 0x100) % 0x100] & 128] > 0 } {
set len_bytes [expr [expr ($ber_len + 0x100) % 0x100] & 127]
}
How many bytes is the message id
binary scan $payload x[expr 3 + $len_bytes]c msgid_bytes
The message type is then 4 bytes + number length bytes + number of
message id bytes offset.
binary scan $payload x[expr 4 + $len_bytes + $msgid_bytes]c msgtype
msgtype - BER encoded value, bits 1-5 are the actual
type, 6 is the data type, 7-8 are the data class
Here we only care about the lower 5 bits
set msgtype [expr $msgtype & 31]
if {$::debug and
[catch {
log local0. "message type is: $::msg_types($msgtype) $msgtype"
}
]
} {
log local. "Bad message type: $msgtype"
reject
}
Each connection should start with a bind request
We'll save this packet for later rebinding when we
flip between servers
if { $msgtype == 0 } {
if {$::debug} {log local0. "Bind Request with: ldap_read"}
set writing 0
set rebind 0
set binding $payload
LB::detach
pool $::readPool
If we come across a write request and are currently not
sending data to the write pool, detach, and set the rebind
flag so we can send the bind packet before we actually send
our write request
} elseif {[matchclass $msgtype equals $::ldap_writes] and $writing != 1} {
if {$::debug} {log local0. "Rebinding with: ldap_write"}
set rebind 1
set writing 1
set replayop $payload
TCP::payload replace 0 [TCP::payload length] $binding
LB::detach
pool $::writePool
If we come across a read request while we are bound to a write server
we need to detach and rebind with a read server from the read pool
} elseif {![matchclass $msgtype equals $::ldap_writes] and $writing == 1} {
if {$::debug} {log local0. "Rebinding with: ldap_read"}
set rebind 1
set writing 0
set replayop $payload
TCP::payload replace 0 [TCP::payload length] $binding
LB::detach
pool $::readPool
}
TCP::release
TCP::collect
}
when SERVER_CONNECTED {
A change in the type of request has been detected
requiring a rebind, we've sent the bind now we need to
wait for the response before we send the actual request
if { $rebind == 1 } {
TCP::collect
}
}
when SERVER_DATA {
if { $rebind == 1 } {
set rebind 0
See above for details on this block. Stupid iRules, no proc grrrr
set payload [TCP::payload]
Pull the first 2 bytes.
binary scan $payload H2c ber_t ber_len
set len_bytes 0
if { [expr [expr ($ber_len + 0x100) % 0x100] & 128] > 0 } {
set len_bytes [expr [expr ($ber_len + 0x100) % 0x100] & 127]
}
binary scan $payload x[expr 3 + $len_bytes]c msgid_bytes
binary scan $payload x[expr 4 + $len_bytes + $msgid_bytes]c msgtype
set msgtype [expr $msgtype & 31]
If the msgtype we have here is for a bind response just discard
it as we don't need to send it to the client
if {$msgtype == 1 } {
TCP::payload replace 0 [TCP::payload length] ""
}
Now send the actual read or write op to the server
It should now have processed the bind
TCP::respond $replayop
}
TCP::release
}
}
18 Replies
Sort By
- Peter_Wang_7400
Nimbostratus
Yes, ldaps works without the irule and I have ssl on both client and server sides. - nitass
Employee
we have to use SSL:: instead of TCP:: command since it is SSL.root@ve1110(Active)(/Common)(tmos) list ltm virtual bar ltm virtual bar { destination 172.28.19.252:636 ip-protocol tcp mask 255.255.255.255 profiles { myclientssl { context clientside } serverssl-insecure-compatible { context serverside } tcp { } } rules { myrule } snat automap vlans-disabled } root@ve1110(Active)(/Common)(tmos) list ltm profile client-ssl myclientssl ltm profile client-ssl myclientssl { app-service none cert salmon.crt defaults-from clientssl key salmon.key } root@ve1110(Active)(/Common)(tmos) list ltm data-group internal ldap_writes ltm data-group internal ldap_writes { records { 6 { } 8 { } 10 { } 12 { } } type integer } root@ve1110(Active)(/Common)(tmos) list ltm pool sun_ldap_read ltm pool sun_ldap_read { members { 200.200.200.103:636 { address 200.200.200.103 } } } root@ve1110(Active)(/Common)(tmos) list ltm pool sun_ldap_write ltm pool sun_ldap_write { members { 200.200.200.113:636 { address 200.200.200.113 } } } root@ve1110(Active)(/Common)(tmos) list ltm rule myrule ltm rule myrule { when RULE_INIT { Read Pool set static::readPool sun_ldap_read Write Pool set static::writePool sun_ldap_write Turn on debugging set static::ldap_debug 1 A lookup table for debugging array set static::msg_types { 0 "bind request" 1 "bind response" 2 "unbind request" 3 "search request" 4 "search response" 6 "modify request" 7 "modify response" 8 "add request" 9 "add response" 10 "delete request" 11 "delete response" 12 "modifydn request" 13 "modifydn response" 14 "compare request" 15 "compare response" 16 "abandon request" 23 "extended request" 24 "extended response" } } when CLIENTSSL_HANDSHAKE { set rebind 0 set binding "" set replayop "" set writing 0 SSL::collect } when CLIENTSSL_DATA { Grab the current payload collected set payload [SSL::payload] Pull the first 2 bytes. binary scan $payload H2c ber_t ber_len The first byte is the tag signifying an LDAP message, Always is hex 30, if that is not so reject if { $ber_t ne "30" } { reject return } The second byte is one of two values: a) The length of the packet minus the above defining byte and the length byte OR b) an octet describing how many subsequent bytes hold the packet length In either case the message type (what we are after) follows the message id field which too can be a variable number of bytes. set len_bytes 0 if { [expr [expr ($ber_len + 0x100) % 0x100] & 128] > 0 } { set len_bytes [expr [expr ($ber_len + 0x100) % 0x100] & 127] } How many bytes is the message id binary scan $payload x[expr 3 + $len_bytes]c msgid_bytes The message type is then 4 bytes + number length bytes + number of message id bytes offset. binary scan $payload x[expr 4 + $len_bytes + $msgid_bytes]c msgtype msgtype - BER encoded value, bits 1-5 are the actual type, 6 is the data type, 7-8 are the data class Here we only care about the lower 5 bits set msgtype [expr $msgtype & 31] if {$static::ldap_debug and [catch { log local0. "message type is: $static::msg_types($msgtype) $msgtype" } ] } { log local0. "Bad message type: $msgtype" reject } Each connection should start with a bind request We'll save this packet for later rebinding when we flip between servers if { $msgtype == 0 } { if {$static::ldap_debug} {log local0. "Bind Request with: ldap_read"} set writing 0 set rebind 0 set binding $payload LB::detach pool $static::readPool If we come across a write request and are currently not sending data to the write pool, detach, and set the rebind flag so we can send the bind packet before we actually send our write request } elseif {[matchclass $msgtype equals ldap_writes] and $writing != 1} { if {$static::ldap_debug} {log local0. "Rebinding with: ldap_write"} set rebind 1 set writing 1 set replayop $payload SSL::payload replace 0 [SSL::payload length] $binding LB::detach pool $static::writePool If we come across a read request while we are bound to a write server we need to detach and rebind with a read server from the read pool } elseif {![matchclass $msgtype equals ldap_writes] and $writing == 1} { if {$static::ldap_debug} {log local0. "Rebinding with: ldap_read"} set rebind 1 set writing 0 set replayop $payload SSL::payload replace 0 [SSL::payload length] $binding LB::detach pool $static::readPool } SSL::release SSL::collect } when SERVER_CONNECTED { A change in the type of request has been detected requiring a rebind, we've sent the bind now we need to wait for the response before we send the actual request if { $rebind == 1 } { SSL::collect } } when SERVERSSL_DATA { if { $rebind == 1 } { set rebind 0 See above for details on this block. Stupid iRules, no proc grrrr set payload [TCP::payload] Pull the first 2 bytes. binary scan $payload H2c ber_t ber_len set len_bytes 0 if { [expr [expr ($ber_len + 0x100) % 0x100] & 128] > 0 } { set len_bytes [expr [expr ($ber_len + 0x100) % 0x100] & 127] } binary scan $payload x[expr 3 + $len_bytes]c msgid_bytes binary scan $payload x[expr 4 + $len_bytes + $msgid_bytes]c msgtype set msgtype [expr $msgtype & 31] If the msgtype we have here is for a bind response just discard it as we don't need to send it to the client if {$msgtype == 1 } { SSL::payload replace 0 [SSL::payload length] "" } Now send the actual read or write op to the server It should now have processed the bind SSL::respond $replayop } SSL::release } } [root@ve1110:Active] config tail -f /var/log/ltm Apr 19 02:44:31 tmm info tmm[7321]: Rule /Common/myrule : message type is: bind request 0 Apr 19 02:44:31 tmm info tmm[7321]: Rule /Common/myrule : Bind Request with: ldap_read Apr 19 02:44:31 tmm info tmm[7321]: Rule /Common/myrule : message type is: search request 3 Apr 19 02:44:31 tmm info tmm[7321]: Rule /Common/myrule : message type is: unbind request 2
- Peter_Wang_7400
Nimbostratus
Thanks heaps, nitass. - nitass
Employee
One question though, is it a typo in your irule? TCP::payload or SSL::payload? oops, thanks. it should be SSL::payload. - Hamish
Cirrocumulus
If you're using v10 or above, it might be better to replace all the matchclass'es with their [class .. ] equivalent. The class command is more flexible, and has better performance that the deprecated functions. - Peter_Wang_7400
Nimbostratus
I have updated from - nitass
Employee
Is it correct?yes, it is correct. - malcolm1
Nimbostratus
Hi All
Looking for Irule that will change an LDAPSEARCH as below
Current Query:
ldapsearch -x -H -D cn=ldapmon,o=services -W -l 121 -z 3 "(&(uid= E13859)(objectclass=Person))" 1.1
new Query
ldapsearch -x -H -D cn=ldapmon,o=services -W -b cn=E13859,o=users 1.1
Search for the value after uid=”” and put that in the new query after cn=””
Recent Discussions
Related Content
DevCentral Quicklinks
* 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
Discover DevCentral Connects