JDK-8160768 : Add capability to custom resolve host/domain names within the default JNDI LDAP provider
  • Type: Enhancement
  • Component: core-libs
  • Sub-Component: javax.naming
  • Affected Version: 8,9,11
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows_7
  • CPU: x86_64
  • Submitted: 2016-07-04
  • Updated: 2022-11-25
  • Resolved: 2018-11-12
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 11 JDK 12 JDK 8 Other
11.0.8-oracleFixed 12 b20Fixed 8-poolResolved openjdk7uFixed
Related Reports
CSR :  
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
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:


Comments
JDK 11u CSR is here: JDK-8251380. Approving for OpenJDK 11u.
31-08-2020

JDK11u Fix Request: Patch needed some manual crafting to not modify Java Spec. Review approval has been given: https://mail.openjdk.java.net/pipermail/jdk-updates-dev/2020-August/003713.html
31-08-2020

Fix Request (8u) I would like to backport this to 8u for parity with Oracle 8u261. The original patch does not apply cleanly. 8u patch has been reviewed.
28-08-2020

8u code review: https://mail.openjdk.java.net/pipermail/jdk8u-dev/2020-August/012345.html
27-08-2020

I removed openjdk-na again. With JDK-8240523 being pushed, I think this is a candidate for 11.0.9+.
16-07-2020

Thanks, Rob. I'm labeling openjdk-na for this bug.
10-03-2020

I've linked JDK-8240523. Fixing this without breaking the module spec is a work in progress.
09-03-2020

What is the JCK Case? Do you have a bug ID?
09-03-2020

Done under the JCK case.
09-03-2020

Would it be possible to link the backout issue? Thanks!
09-03-2020

I think it was pushed in error, then reverted.
08-03-2020

It was, however, backorted to Oracle update releases. So maybe only certain aspects of the original change? Or the change was modified in a way that does not violate SE spec?
08-03-2020

Also, a backport of the CSR is missing.
06-03-2020

Fix request (11u) I would like to downport this for parity with 11.0.8-oracle. Applies clean. I withdraw this downport request for now.
06-03-2020

Alan, you are right. I see jck test ModuleGraphTest / javaNaming_exportsTest failing: Extra export javax.naming.ldap.spi found in module java.naming Testcase "javaNaming_exportsTest" failed with message: Expected: true, was: false
06-03-2020

This issue involves changes to the Java SE API, it is not appropriate for an update release.
05-03-2020

URL: http://hg.openjdk.java.net/jdk/jdk/rev/a609d549992a User: robm Date: 2018-11-12 16:34:22 +0000
12-11-2018

I cannot support this FC extension request. It's a P4 and there doesn't seem to be a case for trying to get it into JDK 9 at this late stage. I also have concerns that about the dependency on the DNS provider. I think the right thing is to continue the review/discussion on core-libs-dev with a view to coming to agreement on a solution for JDK 10.
06-03-2017

FC Extension Request Latest webrev: http://cr.openjdk.java.net/~robm/8160768/webrev.04 Remaining work: The fix is currently under review on core-libs-dev: http://mail.openjdk.java.net/pipermail/core-libs-dev/2017-February/046221.html Although reviewers have signed off there are some small adjustments that will be made to the ldap context property documentation and the implementation based on customer feedback. This work should be completed by early next week. (~14th Feb) Risk: The fix adds a new Ldap context property "com.sun.jndi.ldap.dnsProvider". This property allows users to provide a custom implementation of BiFunction<List<String>, String, HashTable<?,?>> which is responsible for resolving domain names for ldap servers. The fix performs a SecurityManager check for setFactory permission as well as a package access check. Justification: This issue has cropped up a number of times over the years. The current implementation does not play well with ldap:/// style urls which appear to be prevalent in MS Active Directory environments. Estimated date of completion: The reviews should be complete by late next week. (~17 Feb) The fix also requires CCC approval.
16-02-2017