Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8160768

Add capability to custom resolve host/domain names within the default JNDI LDAP provider

    XMLWordPrintable

Details

    • b20
    • x86_64
    • windows_7

    Backports

      Description

        A DESCRIPTION OF THE REQUEST :
        Currently, the Oracle JNDI LDAP provider takes a host name from ldap://example.com/... as-is and tries to connect to. This is deemed to fail for several reasons:
        1. This might be a domain and not a host name
        2. Kerberos and Digest MD5 always require a FQDN to operate on otherwise authentication will fail
        3. Using host names is not reliable in a big enrivonment because servers are commissioned and decommissioned at any time without subject to prior notice. User shall rely on domain names only.

        The propopal is to provide a context property for a factory which maps this domain name to a host name at its discretion (implementation detail). It should cover a domain name coming from ldap:///dc=example,dc=com (as a replacement for ServiceLocator), from a referral where you do not have control over the URL returned from the server or simply an initial context where you can simply say ldap://example.com whithout knowing which servers handle this domain.

        The proposed solution hooks into the spots where a host name is passed before being connected with LdapClient, perform custom resolution and result one or more possible targets. If nothing is returned, use as-is.

        In detail:
        One provides an implementation of javax.naming.spi.ObjectFactory which will receive the following arguments to getObjectInstance:
        Object obj:
        Either a string passed as domainName:port OR Reference object with the value domainName:port and the type DomainPort
        Name name: null
        Context nameCtx: null
        Hashtable environment: the environment passed to the initial or referral context to piggy back properties to the (custom) implementation.
        Return value: String -- one host name OR String[] -- multiple host names (as fallback)

        This object factory could be either used from the NamingManager or the ResourceManager. There are two possible spots where this will be called. Right before LdapClient#getInstance is called and/or LdapCtxFactory#getUsingURL. It shall be done place to avoid duplicate resolution.

        Oracle can provide one default implementation which does the same as the ServiceLocator now. More sophisticated approaches are to be implemented by the caller.

        Using merely host name canonicalization (A and PTR record) is deemed to fail with Active Directory.

        This RFE solves previously reported bug 9089870.

        JUSTIFICATION :
        Microsoft takes a very sophisticated approach on not to rely on host names because servers can be provisioned and decommissioned any time. Instead, they heavily rely on DNS domain names and DNS SRV records at runtime. I.e., an initial or referral URL does not contain a host name, but only a DNS domain name. While you can connect to the service with this name, you cannot easily authenticate against it with Kerberos/Digest MD5 because one cannot bind the same SPN ldap/<dnsDomainName>@<REALM>, e.g., ldap/example.com@EXAMPLE.COM to more than one account. If you try authenticate anyway, you will receive a "Server not found in Kerberos database (7)" error. Therefore, one has to perform a DNS SRV query (_ldap._tcp.<dnsDomainName>) to test whether this name is a host name or a DNS domain name served by one or more machines. If it turns out to be a DNS domain name, you have to select one target host from the query response (according to RFC 2782, construct a special SPN ldap/<targetHost>/<dnsDomainName>@<REALM> or a regular one ldap/<targetHost>@ <REALM>, obtain a service ticket for and connect to that target host. If it is a regular host name, which is not the usual case with Active Directory, Oracle's internal implementation will behave correctly.
        The referral follow implementation cannot be made to work because there is no way to tell the internal classes to perform this DNS SRV query and pass the appropriate server name(s) for the SPN to the SaslClient. It is deemed to fail. Note, that host name canocalization might sound reasonable within the SaslClient, but this is deemed to fail too for two reasons: First, the SaslClient will receive an arbitrary IP address without knowing whether the LDAP client socket will use the same one. You will have a service ticket issued for another host and your authentication will fail. Second, most Kerberos implementations rely on reverse DNS records, but Microsoft's SSPI Kerberos provider does not care about reverse DNS, it does not canonicalize host names by default and there is no guarantee, that reverse DNS is set up properly. Using throw will not make it any better because the referral URL returned by ReferralException.getReferralInfo() cannot be changed with the calculated values from DNS. ReferralException.getReferralContext() will unconditionally reuse that value. The only way (theoretically) to achieve this is to construct an InitialDirContext with the new URL manually and work with it appropriately.

        My writeup: http://tomcatspnegoad.sourceforge.net/referral-handling

        A terrible and non-maintainable workaround to intercept all URLs manually (initial or referral) reconstruct an InititialDirContext and perform operations. This adds signifact amount of boilerplate code, additionally there is not LdapURL class which makes this handling easy in Java, one needs to write this first.

        How would a simple implementation for Active Directory look like if Oracle would add the support for:

        domainName: passed domain name via LDAP URL
        site: passed via Hashtable environment

        1. Split DomainPort
        2. if (site != null)
           perform 3. with _ldap._tcp.<site>._sites.<domainName> (or _gc... if port is 3268 or 3269)
        3. Perform DNS SRV query based on port IF site-based list is empty:
          if (port in (3268, 3269)
            lookup _gc._tcp.<domainName>
          else
          lookup _ldap._tcp.<domainName>
        4. Return list of hostnames

        See here for more details: https://technet.microsoft.com/en-us/library/cc759550%28v=ws.10%29.aspx

        A complex implementation would even issue an LDAP ping to domainName to autodiscover the client site and some more stuff: https://msdn.microsoft.com/en-us/library/cc223811.aspx
        The very same mechanism is implemented in Microsoft's .NET equivalent to perform the lookups described as above. ldap://example.com just works.

        If someone does not use Active Directory but simply another directory server and has proper DNS SRV records, Oracle's default implemenation would work ideally here.


        EXPECTED VERSUS ACTUAL BEHAVIOR :
        EXPECTED -
        ObjectFactory implementation is called and lets the custom code resolve the domain name that the initial or referral context can successfully authenticate without knowning the FQDN.
        ACTUAL -
        The authentication simply fails with Server not found in Kerberos database.

        CUSTOMER SUBMITTED WORKAROUND :
        A cheap version is to patch com.sun.jndi.ldap.LdapCtx with:

        ====
        Index: com/sun/jndi/ldap/LdapCtx.java
        ===================================================================
        --- com/sun/jndi/ldap/LdapCtx.java (revision 284)
        +++ com/sun/jndi/ldap/LdapCtx.java (working copy)
        @@ -2695,6 +2695,13 @@
                         ldapVersion = (ver != null) ? Integer.parseInt(ver) :
                             DEFAULT_LDAP_VERSION;
         
        + String[] servers = ServiceLocator.getLdapService(hostname, envprops);
        +
        + if (servers != null) {
        + String selectedServer = servers[0];
        + hostname = selectedServer.substring(0, selectedServer.lastIndexOf('.'));
        + }
        +
                         clnt = LdapClient.getInstance(
                             usePool, // Whether to use connection pooling
        ====

        and prepend it to the bootclasspath: -Xbootclasspath/p:

        Attachments

          Issue Links

            Activity

              People

                robm Robert Mckenna
                webbuggrp Webbug Group
                Votes:
                0 Vote for this issue
                Watchers:
                15 Start watching this issue

                Dates

                  Created:
                  Updated:
                  Resolved: