Learn F5 Technologies, Get Answers & Share Community Solutions Join DevCentral

Filter by:
  • Solution
  • Technology
Answers

Is there any builtins for LDAP parsing?

I am thinking about setting up a vip ldaps://ldap.domainname.com:636 that will use different backend ldap server instances depending on the "ou". Are there any builtin iRules to assist?

-Brian
0
Rate this Question

Answers to this Question

placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER
There aren't currently any built in LDAP inspectors as far as I'm aware. You should, however, be able to use normal TCP inspectors to break down the traffic and search for the desired data.

-Colin
0
placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER
Is there any plans to include such functionality in the future?

Thanks,
Brian
0
placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER
This type of question is usually best directed at Technical Support, as they can often times get more information about the product roadmap and see if there is any public information that they can share regarding the questions you have.

-Colin
0
placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER
I don't know of any built in features, but it should be possible to do parsing of LDAP messages by hand so long as you don't want to get too deep into the flows. Especially if you control the clients and/or servers so you can force them to re-open connections as needed).

Below is enough iRules magic to sometimes parse the initial BIND DN from a carefully chosen LDAP BindRequest. It's important to note that this makes several incorrect assumptions about fixed lengths, and simply cannot work for anything more than prototype demonstration purposes (even then, it's ify!). And it's slow.

If you use OpenLDAP 2.2.15 and set "allow bind_v2" and force ldapsearch to use LDAPv2 (-P 2), and use a short BIND DN, password, this just might work.


rule never_tested_complex_ldap_parser {
when CLIENT_DATA {
set tcp_content [TCP::payload]

########### VERIFY THIS IS AN LDAPMESSAGE
set matches [binary scan $tcp_content c char]

if {$matches != 1} {
log local0.notice "uh oh! $matches"
exit
}

if {$char != 0x30} {
log local0.notice [format "Should be 0x30, is: %02X" $char]
}

########### EXTRACT LDAP LENGTH
set total_length 0
set offset 1
set temp ""
set char ""

binary scan [string index $tcp_content $offset] c char

while {$char != 0x02} {
append temp $char
incr offset

binary scan [string index $tcp_content $offset] c char
}
set total_length $temp

log local0.notice [format "Length: %02X" $total_length]


############# EXTRACT MESSAGE ID
set temp ""
incr offset

binary scan [string index $tcp_content $offset] c len
set i 0

log local0.notice [format "Length of message ID is: %02X" $len]

incr offset

while {$i < $len} {
binary scan [string index $tcp_content $offset] c char

append temp $char
incr offset
incr i
}

binary scan $temp a* message_id

log local0.notice [format "Message ID is: %02X" $message_id]


############## EXTRACT PROTOCOL OPERATION

set temp ""
binary scan [string index $tcp_content $offset] c char

while {$char != 0x02} {
append temp $char
incr offset

binary scan [string index $tcp_content $offset] c char
}

if {[string length $temp] == 4} {
binary scan $temp a2a2 protocol_operation remaining_length
set protocol_operation [expr $protocol_operation & 31]
} else {
log local0.notice "unknown header values after message_id, before version, EXIT"
exit
}

log local0.notice [format "Protocol Operation: %02X" $protocol_operation]
log local0.notice [format "Remaining bytes in LDAP message: %02X" $remaining_length]


############# EXTRACT LDAP VERSION
set temp ""
incr offset

binary scan [string index $tcp_content $offset] c len
set i 0

log local0.notice [format "Length of LDAP version is: %02X" $len]

incr offset

while {$i < $len} {
binary scan [string index $tcp_content $offset] c char

append temp $char
incr offset
incr i
}

binary scan $temp a* ldap_version

log local0.notice [format "LDAP version is: %02X" $ldap_version]

############### EXTRACT BIND DN

incr offset
incr offset
set temp ""
binary scan [string index $tcp_content $offset] c char

while {[expr $char & 128] != 128} {
append temp [format "%c" $char]
incr offset

binary scan [string index $tcp_content $offset] c char
}
set bind_dn $temp

log local0.notice "BIND DN: $bind_dn"

if {[string match -nocase "*l=Seattle*" $bind_dn]} {
pool my_seattle_pool
} else {
pool main_pool
}
}
}






This of course only gets you to the very first initial BindRequest, which typically isn't all that useful. It's a start, though.

I found the following text to be a good reference for manually inspecting/parsing/generating LDAP messages. I hope it helps.







A.7.1 LDAPMessage (bind request)

LDAPMessage ::= SEQUENCE {
messageID MessageID,
protocolOp CHOICE {
bindRequest BindRequest,
bindResponse BindResponse,
unbindRequest UnbindRequest,
searchRequest SearchRequest,
searchResponse SearchResponse,
modifyRequest ModifyRequest,
modifyResponse ModifyResponse,
addRequest AddRequest,
addResponse AddResponse,
delRequest DelRequest,
delResponse DelResponse,
modifyRDNRequest ModifyRDNRequest,
modifyRDNResponse ModifyRDNResponse,
compareDNRequest CompareRequest,
compareDNResponse CompareResponse,
abandonRequest AbandonRequest
}
}

BindRequest ::= [APPLICATION 0] SEQUENCE {
version INTEGER (1 .. 127),
name LDAPDN,
authentication CHOICE {
simple [0] OCTET STRING,
krbv42LDAP [1] OCTET STRING,
krbv42DSA [2] OCTET STRING
}
}


An ASN1 "SEQUENCE" is an ordered list of objects, so describing
such a format as a standard ASCII diagram is relatively
straight-forward.

** QUICK ASN1 PRIMER:
Each ASN1 object is represented as a group of IDENTIFIER, LENGTH,
and CONTENT. In the IDENTIFIER, the top two bits identify
UNIVERSAL (00) vs. APPLICATION (01) vs. CONTEXT-SPECIFIC (10),
vs. PRIVATE (11). The next bit is PRIMITIVE (0) vs. STRUCTURED
(1). The remaining 5 bits are the "tag".

If a tag number is greater than 31 (i.e. can't be represented in
the 5 bits alotted), bits 5 through 1 of the first octet are set
to 1, and each remaining octet has a 1 or 0 in bit 8, depending
on whether the tag number is continued in the next octet.

For example, APPLICATION 293 is encoded as: 01111111 10000010
00100101. The first octet (01111111) identifies as an APPLICATION
(first to bits are 01), is STRUCTURED (bit 6 is set), and we can
see that the 5 tag bits are set, which indicates the length is
stored in the next octet(s). The top bit in the next octet is
set (10000010), so we know there is one or more octets following
this one. The next octet (00100101) has the top bit cleared,
indicating it is the last octet for this field. To find the tag
value simply strip off the top bit of each octet AFTER the first
one, and string them end to end. So 011111111000001000100101
becomes: 00000100100101, which is 293. This is known as "long
form" encoding. If the value fits in one octet (i.e. value < 31)
this is known as "short form" encoding.

This same type of encoding also applies to LENGTH. LENGTH has
one additional form of encoding, "indefinite form". If the
LENGTH field contains only one octet of 10000000, this signals
that there will be an end-of-content marker, which is represented
as two octets in a row of all-zeros at the end of a structured
data element.

**BACK TO ASCII DIAGRAM:
The top-level LDAPMessage SEQUENCE has an IDENTIFIER of 0x30,
which is 00110000, meaning UNIVERSAL, STRUCTURED, with a tag of
16. A tag of 16 is an "ASN1 SEQUENCE", naturally.

So the IDENTIFIER is always 0x30, which is followed by the total
LDAP message length, and then the content. This group of
attributes (IDENTIFIER, LENGTH, CONTENT: ILC) is how all ASN1
data elements are described, and this does not changed whether
they are top-level like LDAPMessage or nested like BindRequest.

Given the following LDAPMessage:
30 49 02 01 01 60 44 02 01 02 04 38 63 6e 3d 61
64 6d 69 6e 2c 6f 75 3d 70 72 6f 64 2c 6f 3d 54
68 65 20 4f 6c 6f 72 6f 6e 73 20 43 6c 6f 6f 6e
69 6e 67 20 43 6f 6f 70 6f 72 61 74 69 6f 6e 2c
63 3d 55 53 80 05 65 6c 76 69 73


It would be interpreted as follows:
0x30: IDENTIFIER (UNIVERSAL, STRUCTURED, tag 16: ASN1 SEQUENCE)
0x49: LENGTH 73 bytes (not including IDENTIFIER or LENGTH).
Added back in (73+2) is equal to the amount of TCP data.
0x02: CONTENT, the first byte of CONTENT of the LDAPMessage
SEQUENCE. The first element within the LDAPMessage is
the MessageID, so this 0x02 is the IDENTIFIER of MessageID.
0x02 means UNIVERSAL, PRIMITIVE, tag of 2: INTEGER.
0x01: LENGTH 1. The length of the INTEGER of MessageID is 1
(again, lengths only include bytes AFTER the length
field itself).
0x01: CONTENT 1. The MessageID value is 1.
0x60: IDENTIFIER (APPLICATION, STRUCTURED, tag 0: BindRequest).
0x44: LENGTH 68. Length of BindRequest object is 68 bytes.
0x02: CONTENT of BindRequest. The first object within a
BindRequest is version, so this first byte is the
IDENTIFIER of the version field of the BindRequest. An
IDENTIFIER of 0x02 means UNIVERSAL, PRIMITIVE, tag of 2,
which is an INTEGER.
0x01: LENGTH 1. The length of the INTEGER version is 1 byte.
0x02: CONTENT 2. The INTEGER version within the BindRequest
has a value of 2. LDAP version 2.
0x04: IDENTIFIER (UNIVERSAL, PRIMITIVE, tag: 4 OCTET String).
0x38: LENGTH 56. BindRequest name (BINDDN).
0x63..0x53: CONTENT
"cn=admin,ou=prod,o=The Olorons Clooning Cooporation,c=US"
0x80: IDENTIFIER (CONTEXT-SPECIFIC, PRIMITIVE, tag 0:
authentication==simple).
0x05: LENGTH: 5.
0x65..0x73: CONTENT "elvis"

In ASN1 style, the values are:

LDAPMessage ::= SEQUENCE {
messageID 1
protocolOp 0 (bindRequest)

BindRequest ::= [APPLICATION 0] SEQUENCE {
version 2,
name "cn=admin,ou=prod,o=The Olorons Clooning Cooporation,c=US"
authentication 0 (simple) "elvis"
}
}


In typical ASCII form, these are represented as follows:

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Always 0x30 | Total Len ... | Always 0x02 | MsgID Len ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| MsgID ... | Protocol Op | Msg Len ... | Always 0x02 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| LDAP Ver Len | LDAP Ver ... | Always 0x04 | BINDDN Len ...|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| BIND DN ... | Always 0x80 | Password Len | Password ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

In practice, Total Len is likely to be 1 byte on BindRequests and
and some simple searches, but is likely to be 2 bytes during a
typical user session. MsgID Len and MsgID are likely to be 1 byte
each for typical LDAP use cases, as with LDAP Ver. BIND DN Len is
likely to be more than 1 byte in real deployments, and the BIND DN
itself will always be many octects (highly variable). Password
Len is unlikely to be more than 1 byte, but Password itself will
always be variable.

MsgID is the number which clients and servers used to uniquely
identify a single transactions, knowing that LDAP calls can be
asynchronous. BIND_REQ starts with 1, server echo's 1 in
BIND_RES, the next transaction (perhaps a SEARCH_REQ) would then
use 2, and again the server would echo this back in the response.

Protocol Operation is actually (protoOp & 31), given that the top
3 bits are used for IDENTIFIER overhead.

IDENTIFIER (tag), LENGTH, CONTENT (ILC) is standard BER encoding.
"OCTET String" has an identifier of 4 (UNIVERSAL, PRIMITIVE,
tag: 4). "INTEGER" has an identifier of 2. "BOOLEAN" has an
identifier of 1.

Request-side APPLICATION tags: BIND 0, UNBIND 2, SEARCH 3,
MODIFY 6, ADD 8, DELETE 10, MODRDN 12, COMPARE 14, ABANDON 16,
EXTENDED 23.

Response-side APPLICATION tags: BIND 1, SEARCH_ENTRY 4,
SEARCH_REF 19, SEARCH_RESULT 5, MODIFY 7, ADD 9, DELETE 11,
MODRDN 13, COMPARE 15, EXTENDED 24.

CONTEXT-SPECIFIC tags: AUTH_SIMPLE 0, AUTH_KRBV4LDAP 1,
AUTH_KRBV4DSA 2, AUTH_SASL 3.

FILTER_AND 0, FILTER_OR 1, FILTER_NOT 2, FILTER_EQUALITY 3,
FILTER_SUBSTRINGS 4, FILTER_GE 5, FILTER_LE 6,
FILTER_PRESENT 7, FILTER_APPROX 8, FILTER_EXTENSIBLE 9.

MOD_ADD 0, MOD_DELETE 1, MOD_REPLACE 2

0