From d20452c6fe3b31658aec41ae3bb6ce53dc9f0f1b Mon Sep 17 00:00:00 2001 From: Mark Craig <mark.craig@forgerock.com> Date: Fri, 20 Apr 2012 12:23:07 +0000 Subject: [PATCH] More advice for LDAP application developers --- opendj3/src/main/docbkx/dev-guide/chap-best-practices.xml | 228 +++++++++++++++++++++++++++++++++++++++++++++++--------- 1 files changed, 190 insertions(+), 38 deletions(-) diff --git a/opendj3/src/main/docbkx/dev-guide/chap-best-practices.xml b/opendj3/src/main/docbkx/dev-guide/chap-best-practices.xml index 441784c..386b729 100644 --- a/opendj3/src/main/docbkx/dev-guide/chap-best-practices.xml +++ b/opendj3/src/main/docbkx/dev-guide/chap-best-practices.xml @@ -59,7 +59,7 @@ manage certificates rather than passwords, directory servers like OpenDJ can do client authentication as well.</para> </section> - + <section xml:id="reuse-connections"> <title>Reuse Connections</title> @@ -111,70 +111,222 @@ specific filters. As a rule, prefer equality filters over substring filters.</para> + <para>Some directory servers like OpenDJ reject unindexed searches by + default, because unindexed searches are generally far more resource intensive. + If your application needs to use a filter that results in an unindexed search, + then work with your directory administrator to find a solution, such as having + the directory maintain the indexes required by your application.</para> + <para>Furthermore, always use <literal>&</literal> with <literal>!</literal> to restrict the potential result set before returning all entries that do not match part of the filter. For example, <literal >(&(location=Oslo)(!(mail=birthday.girl@example.com)))</literal>.</para> </section> - + <section xml:id="make-modifications-specific"> <title>Make Modifications Specific</title> - <para>TODO</para> + <para>When you modify attributes with multiple values, for example when you + modify a list of group members, replace or delete specific values + individually, rather than replacing the entire list of values. Making + modifications specific helps directory servers replicate your changes more + effectively.</para> </section> - + <section xml:id="trust-result-codes"> <title>Trust Result Codes</title> - <para>TODO</para> + + <para>Trust the LDAP result code that your application gets from the + directory server. For example, if you request a modify application and you + get <literal>ResultCode.SUCCESS</literal>, then consider the operation a + success rather than issuing a search immediately to get the modified + entry.</para> + + <para>The LDAP replication model is loosely convergent. In other words, + the directory server can, and probably does, send you + <literal>ResultCode.SUCCESS</literal> before replicating your change to + every directory server instance across the network. If you issue a read + immediately after a write, and a load balancer sends your request to another + directory server instance, you could get a result that differs from what + you expect.</para> + + <para>The loosely convergent model also means that the entry could have + changed since you read it. If needed, you can use <link xlink:show="new" + xlink:href="http://tools.ietf.org/html/rfc4528">LDAP assertions</link> to set + conditions for your LDAP operations.</para> </section> - - <section xml:id="limit-dealings-with-groups"> - <title>Limit Dealings With Groups</title> - <para>TODO</para> + + <section xml:id="ismemberof-for-membership"> + <title>Check Group Membership on the Account, Not the Group</title> + + <para>If you need to determine which groups an account belongs to, request + <literal>isMemberOf</literal> for example with OpenDJ when you read the + account entry. Other directory servers use other names for this attribute + that identifies the groups to which an account belongs.</para> </section> - - <section xml:id="read-the-dse"> - <title>Read the DSE</title> - <para>TODO</para> + + <section xml:id="as-directory-what-it-supports"> + <title>Ask the Directory Server What It Supports</title> + + <para>Directory servers expose their capabilities, suffixes they support, + and so forth as attribute values on the root DSE. See the section on + <link xlink:href="dev-guide#read-root-dse" + xlink:role="http://docbook.org/xlink/role/olink"><citetitle>Reading Root + DSEs</citetitle></link>.</para> + + <para>This allows your application to discover a variety of information at + run time, rather than storing configuration separately. Thus putting effort + into querying the directory about its configuration and the features it + supports can make your application easier to deploy and to maintain.</para> + + <para>For example, rather than hard-coding + <literal>dc=example,dc=com</literal> as a suffix DN in your configuration, + you can search the root DSE on OpenDJ for <literal>namingContexts</literal>, + and then search under the naming context DNs to locate the entries you are + looking for in order to initialize your configuration.</para> + + <para>Directory servers also expose their schema over LDAP. The root DSE + attribute <literal>subschemaSubentry</literal> shows the DN of the entry + holding LDAP schema definitions. See the section, <link + xlink:href="dev-guide#get-schema-information" + xlink:role="http://docbook.org/xlink/role/olink"><citetitle>Getting Schema + Information</citetitle></link>. Note that LDAP object class and attribute + type names are case-insensitive, so <literal>isMemberOf</literal> and + <literal>ismemberof</literal> refer to the same attribute for example.</para> </section> - - <section xml:id="limit-resource-use"> - <title>Use Resource-intensive Features Sparingly</title> - <para>TODO</para> + + <section xml:id="storing-large-attributes"> + <title>Store Large Attribute Values By Reference</title> + + <para>When you use large attribute values such as photos or audio messages, + consider storing the objects themselves elsewhere and keeping only a reference + to external content on directory entries. In order to serve results quickly + with high availability, directory servers both cache content and also + replicate it everywhere.</para> + + <para>Textual entries with a bunch of attributes and perhaps a certificate + are often no larger than a few KB. Your directory administrator might + therefore be disappointed to learn that your popular application stores + users' photo and .mp3 collections as attributes of their accounts.</para> </section> - - <section xml:id="avoid-hard-coding"> - <title>Avoid Hard-coding Certain Information</title> - <para>TODO</para> + + <section xml:id="careful-with-persistent-search-and-server-side-sorting"> + <title>Take Care With Persistent Search & Server-Side Sorting</title> + + <para>A persistent search lets your application receive updates from the + server as they happen by keeping the connection open and forcing the server + to check whether to return additional results any time it performs a + modification in the scope of your search. Directory administrators therefore + might hesitate to grant persistent search access to your application. + Directory servers like OpenDJ can let you discover updates with less + overhead by searching the change log periodically. If you do have to use + a persistent search instead, try to narrow the scope of your search.</para> + + <para>Directory servers also support a resource-intensive operation called + server-side sorting. When your application requests a server-side sort, the + directory server retrieves all the entries matching your search, and then + returns the whole set of entries in sorted order. For result sets of any size + server-side sorting therefore ties up server resources that could be used + elsewhere. Alternatives include both sorting the results after your + application receives them, and also working with the directory administrator + to have appropriate browsing (virtual list view) indexes maintained on the + directory server for applications that must regularly page through long + lists of search results.</para> </section> - + <section xml:id="reuse-schemas"> <title>Reuse Schemas Where Possible</title> - <para>TODO</para> + + <para>Directory servers like OpenDJ come with schema definitions for a wide + range of standard object classes and attribute types. This is because + directories are designed to be shared by many applications. Directories + use unique, typically <link xlink:href="http://www.iana.org/" + xlink:show="new">IANA</link>-registered object identifiers (OID) to avoid + object class and attribute type name clashes. The overall goal is + Internet-wide interoperability.</para> + + <para>You therefore should reuse schema definitions that already exist + whenever you reasonably can. Reuse them as is. Do not try to redefine + existing schema definitions.</para> + + <para>If you must add schema definitions for your application, extend + existing object classes with AUXILIARY classes of your own. Take care to + name your definitions such that they do not clash with other names.</para> + + <para>When you have defined schema required for your application, work with + the directory administrator to have your definitions added to the directory + service. Directory servers like OpenDJ let directory administrators update + schema definitions over LDAP, so there is not generally a need to interrupt + the service to add your application. Directory administrators can however + have other reasons why they hesitate to add your schema definitions. + Coming to the discussion prepared with good schema definitions, explanations + of why they should be added, and evident regard for interoperability makes + it easier for the directory administrator to grant your request.</para> </section> - + <section xml:id="handle-referrals"> <title>Handle Referrals</title> - <para>TODO</para> + + <para>When a directory server returns a search result, the result is not + necessarily an entry. If the result is a referral, then your application + should follow up with an additional search based on the URIs provided in + the result.</para> </section> - - <section xml:id="directory-not-relational-db"> - <title>Treat a Directory as a Directory</title> - <para>TODO</para> - </section> - + <section xml:id="check-result-codes"> <title>Troubleshooting: Check Result Codes</title> - <para>TODO</para> + + <para>LDAP result codes are standard and clearly defined. When you receive + a <literal>Result</literal>, check the <literal>ResultCode</literal> value to + determine what action your application should take. When the result is not + what you expect, you can also read or at least log the message string from + <literal>ResultCode.getDiagnosticMessage()</literal>.</para> </section> - + <section xml:id="check-log-files"> <title>Troubleshooting: Check Server Log Files</title> - <para>TODO</para> + + <para>If you can read the directory server access log, then you can check + what the server did with your application's request. For example, the + following OpenDJ access log excerpt shows a successful connection from + <literal>cn=My Killer App,ou=Apps,dc=example,dc=com</literal> performing + a simple bind after Start TLS, and then a simple search before unbind. + The lines are wrapped for readability, whereas in the log each record starts + with the time stamp.</para> + + <programlisting language="none">[20/Apr/2012:13:31:05 +0200] CONNECT conn=5 + from=127.0.0.1:51561 to=127.0.0.1:1389 protocol=LDAP +[20/Apr/2012:13:31:05 +0200] EXTENDED REQ conn=5 op=0 msgID=1 name="StartTLS" + oid="1.3.6.1.4.1.1466.20037" +[20/Apr/2012:13:31:05 +0200] EXTENDED RES conn=5 op=0 msgID=1 name="StartTLS" + oid="1.3.6.1.4.1.1466.20037" result=0 etime=0 +[20/Apr/2012:13:31:07 +0200] BIND REQ conn=5 op=1 msgID=2 version=3 type=SIMPLE + dn="cn=My Killer App,ou=Apps,dc=example,dc=com" +[20/Apr/2012:13:31:07 +0200] BIND RES conn=5 op=1 msgID=2 result=0 + authDN="cn=My Killer App,ou=Apps,dc=example,dc=com" etime=1 +[20/Apr/2012:13:31:07 +0200] SEARCH REQ conn=5 op=2 msgID=3 + base="dc=example,dc=com" scope=wholeSubtree + filter="(uid=kvaughan)" attrs="isMemberOf" +[20/Apr/2012:13:31:07 +0200] SEARCH RES conn=5 op=2 msgID=3 + result=0 nentries=1 etime=6 +[20/Apr/2012:13:31:07 +0200] UNBIND REQ conn=5 op=3 msgID=4 +[20/Apr/2012:13:31:07 +0200] DISCONNECT conn=5 reason="Client Unbind"</programlisting> + + <para>Notice that each operation type is shown in upper case, and that the + server tracks both the connection (<literal>conn=5</literal>), operation + (<literal>op=[0-3]</literal>), and message ID (<literal>msgID=[1-4]</literal>) + numbers to make it easy to filter records. The <literal>etime</literal> refers + to how long the server worked on the request in milliseconds. Result code + 0 corresponds to <literal>ResultCode.SUCCESS</literal>, as described in + <link xlink:href="http://tools.ietf.org/html/rfc4511#section-4.1.9" + xlink:show="new">RFC 4511</link>.</para> </section> - - <section xml:id="inspect-network-packets"> - <title>Troubleshooting: Inspect Network Packets</title> - <para>TODO</para> + + <section xml:id="inspect-network-traffic"> + <title>Troubleshooting: Inspect Network Traffic</title> + + <para>If result codes and server logs are not enough, many network tools + can interpret LDAP packets. Get the necessary certificates to decrypt + encrypted packet content.</para> </section> </chapter> -- Gitblit v1.10.0