4

UPDATE: Based on the comment from @Michael-O below, it seems like the correct way to handle this issue if for the LDAP JNDI provider or the SASL implementation to canonicalize the host name by doing a forward then a revers DNS lookup before issue in KRN service ticket request. I will try to reach out to the Open JDK security list and see if any answers come back from there.

I am trying to do a recursive LDAP search on the root DN against an Active Directory server using a session that is authenticated via GSSAPI using a Subject from a Kerberos LoginContext.

I am able to successfully bind to the server with URL ldap://dc1.example.com. The InitidalDirContext has java.naming.referral set to follow.

When I execute the search (&(objectClass=user)(userPrincipalName=sample_user@EXAMPLE.COM)) against the root DN of dc=example,dc=com, I get one SearchResult back:

CN=Sample User,OU=ExampleUsers,DC=example,DC=com

And several Continuation References:

ldap://example.com/CN=Configuration,DC=example,DC=com
ldap://ForestDnsZones.example.com/DC=ForestDnsZones,DC=example,DC=com
ldap://DomainDnsZones.example.com/DC=DomainDnsZones,DC=example,DC=com

I can iterate over the SearchResult just fine, but as soon as I encounter a continuation, I get a PartialResultsException. I checked DNS and all of the above host names resolve correctly. The exception I get looks like this:

javax.naming.PartialResultException 
  [Root exception is javax.naming.AuthenticationException: GSSAPI 
    [Root exception is javax.security.sasl.SaslException: GSS initiate failed 
      [Caused by GSSException: No valid credentials provided 
        (Mechanism level: Server not found in Kerberos database (7))]]].

Looking at the Kerberos trace, this error makes sense. When trying to follow the continuation, the LDAP library attempts to bind to ldap://example.com. Since we're using GSSAPI for authentication, this triggers a service ticket request for ldap/example.com. The response I see in the log is:

>>>KRBError:
     sTime is Thu Aug 21 14:27:20 EDT 2014 0000000000000
     suSec is 414575
     error code is 7
     error Message is Server not found in Kerberos database
     realm is EXAMPLE.COM
     sname is ldap/example.com
     msgType is 30

I checked Active Directory, and sure enough there isn't any servicePrincipalName attribute with value ldap/example.com anywhere on any of the domain controllers. I've tried manually adding a SPN for ldap/example.com to the SAVANT-DC1 domain controller's machine account. This works temporarily, but Active Directory seems to automatically purge the SPN entry after a couple of minutes.

It seems like the solution would be to do one of

  1. Get Active Directory to return continuations that contain the name of the domain controller instead of the domain. We know that we are able to obtain service tickets for SPNs in the form of ldap/dc1.example.com.
  2. Somehow map continuations on the java end of things to redirect ldap://example.com to ldap://dc1.example.com

I haven't bee able to figure out how to do (1).

I tried doing (2) using the JNDI Manual Referral Handling Example as a guide. I switched the java.naming.referral property to throw and wrote a custom referral handler that manually overrides the java.naming.provider.url property in the referral context. However LdapReferralException.getReferralContext() seems to ignore the java.naming.provider.url environment property. Looking at the OpenJDK code to LdapReferralContext.java seems to confirm this (line 105).

So that's where I am: I can't intercept and manipulate the referrals on the Java side because they are treated as a black box by the JNDI API. I can't manually create an LDAP SPN on the AD side of things because it won't stay persistent in the directory. Is there anything else I am missing?


Here is the code I am running

import java.io.File;
import java.security.PrivilegedExceptionAction;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.ReferralException;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;

public class LdapContinuationDemoAction implements PrivilegedExceptionAction<Object> {
  private final String ldapUrl;
  private final String ldapDn;
  private final String username;

  public static void main(String[] argv) {
    try {
      String username = "example_user@EXAMPLE.COM";
      String password = "Password1";
      String ldapUrl  = "ldap://dc1.example.com";
      String searchDn = "dc=example,dc=com";
      String pwd      = System.getProperty("user.dir");
      String krb5Conf = new File(pwd, "krb5.conf").getAbsolutePath();

      System.setProperty("java.security.krb5.conf", krb5Conf);
      System.setProperty("sun.security.krb5.debug", "true");

      // Login to the domain via Kerberos
      LoginContext loginCtx = new LoginContext("doesn't matter", null,
        getUsernamePasswordHandler(username, password),
        getKrb5Configuration());

      System.out.println("********************************");
      System.out.println("      KRB5 Login");
      System.out.println("********************************");
      loginCtx.login();

      // Execute the LDAP search as the user logged in above
      LdapContinuationDemoAction action = new LdapContinuationDemoAction(ldapUrl,
        searchDn, username);

      Subject.doAs(loginCtx.getSubject(), action);
    } catch( Exception e) {
      System.out.println();
      System.out.println("*** ERROR: " + e);
    }
  }

  private LdapContinuationDemoAction(String ldapUrl, String ldapDn,
    String username) {
    this.ldapUrl  = ldapUrl;
    this.ldapDn   = ldapDn;
    this.username = username;
  }

  // Perform a recursive LDAP search for a user principal and print the results
  @Override
  public Object run() throws Exception {
    System.out.println("********************************");
    System.out.println("      LDAP Login");
    System.out.println("********************************");

    //Setup the directory context environment
    Properties dirCtxProps = new Properties();
    dirCtxProps.put(Context.INITIAL_CONTEXT_FACTORY,      "com.sun.jndi.ldap.LdapCtxFactory");
    dirCtxProps.put(Context.PROVIDER_URL,                 this.ldapUrl);
    dirCtxProps.put(Context.SECURITY_AUTHENTICATION,      "GSSAPI");
    dirCtxProps.put("java.naming.ldap.attributes.binary", "objectSID");
    dirCtxProps.put(Context.REFERRAL,                     "follow");

    DirContext dirCtx = new InitialDirContext(dirCtxProps);

    // enable recursive searching
    SearchControls ctrls = new SearchControls();
    ctrls.setSearchScope(SearchControls.SUBTREE_SCOPE);

    // do the search
    NamingEnumeration<SearchResult> results = dirCtx.search(this.ldapDn,
      "(&(objectClass=user)(userPrincipalName={0}))",
      new Object[] { this.username }, ctrls);

    System.out.println("********************************");
    System.out.println("      LDAP User Info");
    System.out.println("********************************");
    int resultNum = 0;

    while (results.hasMore()) {
      resultNum++;

      Attributes userAttr = results.next().getAttributes();

      System.out.println("ldap result " + resultNum + ": User DN: "
        + userAttr.get("distinguishedName").get());
      System.out.println();
    }


    return null;
  }

  // JAAS callback handler for username and password Kerberos authn
  private static CallbackHandler getUsernamePasswordHandler(
    final String username, final String password) {

    final CallbackHandler handler = new CallbackHandler() {
      @Override
      public void handle(final Callback[] callback) {
        for (int i = 0; i < callback.length; i++) {
          if (callback[i] instanceof NameCallback) {
            final NameCallback nameCallback = (NameCallback) callback[i];
            nameCallback.setName(username);
          } else if (callback[i] instanceof PasswordCallback) {
            final PasswordCallback passCallback = (PasswordCallback) callback[i];
            passCallback.setPassword(password.toCharArray());
          } else {
            System.err.println("Unsupported Callback: "
              + callback[i].getClass().getName());
          }
        }
      }
    };

    return handler;
  }

  // dynamically build a Kerberos JAAS configuration so we don't need a login.conf
  private static Configuration getKrb5Configuration() {
    return new Configuration() {

      @Override
      public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
        Map<String, String> options = new HashMap<String, String>();
        options.put("client", "true");
        return new AppConfigurationEntry[] {
          new AppConfigurationEntry(
            "com.sun.security.auth.module.Krb5LoginModule",
            LoginModuleControlFlag.REQUIRED, options)
        };
      }
    };
  }

}

Here is my krb5.conf:

[libdefaults]
  default_realm = EXAMPLE.COM

[realms]
  EXAMPLE.COM = {
    kdc = dc1.example.com
    default_domain = example.com
  }

[domain_realm]
  .example.com = EXAMPLE.COM
  example.com = EXAMPLE.COM

Here is the output from the above code

********************************
      KRB5 Login
********************************
Config name: C:\src\scratch\krb5\krb5.conf
>>> KdcAccessibility: reset
Using builtin default etypes for default_tkt_enctypes
default etypes for default_tkt_enctypes: 18 17 16 23 1 3.
>>> KrbAsReq creating message
>>> KrbKdcReq send: kdc=dc1.example.com UDP:88, timeout=30000, number of retries =3, #bytes=158
>>> KDCCommunication: kdc=dc1.example.com UDP:88, timeout=30000,Attempt =1, #bytes=158
>>> KrbKdcReq send: #bytes read=227
>>>Pre-Authentication Data:
     PA-DATA type = 19
     PA-ETYPE-INFO2 etype = 18, salt = EXAMPLE.COMexample_user, s2kparams = null
     PA-ETYPE-INFO2 etype = 23, salt = null, s2kparams = null
     PA-ETYPE-INFO2 etype = 3, salt = EXAMPLE.COMexample_user, s2kparams = null

>>>Pre-Authentication Data:
     PA-DATA type = 2
     PA-ENC-TIMESTAMP
>>>Pre-Authentication Data:
     PA-DATA type = 16

>>>Pre-Authentication Data:
     PA-DATA type = 15

>>> KdcAccessibility: remove dc1.example.com
>>> KDCRep: init() encoding tag is 126 req type is 11
>>>KRBError:
     sTime is Thu Aug 21 16:35:42 EDT 2014 0000000000000
     suSec is 659371
     error code is 25
     error Message is Additional pre-authentication required
     realm is EXAMPLE.COM
     sname is krbtgt/EXAMPLE.COM
     eData provided.
     msgType is 30
>>>Pre-Authentication Data:
     PA-DATA type = 19
     PA-ETYPE-INFO2 etype = 18, salt = EXAMPLE.COMexample_user, s2kparams = null
     PA-ETYPE-INFO2 etype = 23, salt = null, s2kparams = null
     PA-ETYPE-INFO2 etype = 3, salt = EXAMPLE.COMexample_user, s2kparams = null

>>>Pre-Authentication Data:
     PA-DATA type = 2
     PA-ENC-TIMESTAMP
>>>Pre-Authentication Data:
     PA-DATA type = 16

>>>Pre-Authentication Data:
     PA-DATA type = 15

KrbAsReqBuilder: PREAUTH FAILED/REQ, re-send AS-REQ
Using builtin default etypes for default_tkt_enctypes
default etypes for default_tkt_enctypes: 18 17 16 23 1 3.
Using builtin default etypes for default_tkt_enctypes
default etypes for default_tkt_enctypes: 18 17 16 23 1 3.
>>> EType: sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType
>>> KrbAsReq creating message
>>> KrbKdcReq send: kdc=dc1.example.com UDP:88, timeout=30000, number of retries =3, #bytes=240
>>> KDCCommunication: kdc=dc1.example.com UDP:88, timeout=30000,Attempt =1, #bytes=240
>>> KrbKdcReq send: #bytes read=1425
>>> KdcAccessibility: remove dc1.example.com
>>> EType: sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType
>>> KrbAsRep cons in KrbAsReq.getReply example_user
********************************
      LDAP Login
********************************
Found ticket for example_user@EXAMPLE.COM to go to krbtgt/EXAMPLE.COM@EXAMPLE.COM expiring on Fri Aug 22 02:35:42 EDT 2014
Entered Krb5Context.initSecContext with state=STATE_NEW
Found ticket for example_user@EXAMPLE.COM to go to krbtgt/EXAMPLE.COM@EXAMPLE.COM expiring on Fri Aug 22 02:35:42 EDT 2014
Service ticket not found in the subject
>>> Credentials acquireServiceCreds: same realm
Using builtin default etypes for default_tgs_enctypes
default etypes for default_tgs_enctypes: 18 17 16 23 1 3.
>>> CksumType: sun.security.krb5.internal.crypto.RsaMd5CksumType
>>> EType: sun.security.krb5.internal.crypto.ArcFourHmacEType
>>> KrbKdcReq send: kdc=dc1.example.com UDP:88, timeout=30000, number of retries =3, #bytes=1392
>>> KDCCommunication: kdc=dc1.example.com UDP:88, timeout=30000,Attempt =1, #bytes=1392
>>> KrbKdcReq send: #bytes read=1398
>>> KdcAccessibility: remove dc1.example.com
>>> EType: sun.security.krb5.internal.crypto.ArcFourHmacEType
>>> KrbApReq: APOptions are 00000000 00000000 00000000 00000000
>>> EType: sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType
Krb5Context setting mySeqNumber to: 774790609
Krb5Context setting peerSeqNumber to: 0
Created InitSecContextToken:
0000: 01 00 6E 00 00 00 00 00   00 00 A0 03 02 01 05 A1  ..n..)0..%......
0010: 03 02 01 0E A2 00 00 00   00 00 00 00 00 A3 82 04  ................
0020: 00 00 00 00 00 00 00 00   2D A0 03 02 01 05 A1 0E  5a..10..-.......
0030: 1B 0C 55 54 42 53 41 56   2E 4C 4F 43 41 4C A2 2A  ..EXAMPLE.COM.*
0040: 30 28 A0 03 02 01 00 A1   21 30 1F 1B 04 6C 64 61  0(......!0...lda
0050: 70 1B 17 73 61 76 61 6E   74 2D 64 63 31 2E 75 74  p..dc1.ut
0060: 62 73 61 76 2E 6C 6F 63   61 6C A3 82 03 E8 30 82  bsav.local....0.
0070: 03 E4 A0 03 02 01 12 A1   03 02 01 08 A2 82 03 D6  ................
---8<--- Snipping a bunch of binary

Krb5Context.unwrap: token=[05 04 01 ff 00 0c 00 0c 00 00 00 00 2e 2e 5d d1 f5 d2 e8 21 c1 23 92 20 61 f4 77 a8 07 a0 00 00 ]
Krb5Context.unwrap: data=[07 a0 00 00 ]
Krb5Context.wrap: data=[01 01 00 00 ]
Krb5Context.wrap: token=[05 04 00 ff 00 0c 00 00 00 00 00 00 2e 2e 5d d1 00 00 00 00 00 00 00 00 fa b6 79 67 ce db 58 d2 ]
********************************
      LDAP User Info
********************************
ldap result 1: User DN: CN=Sample User,OU=ExampleUsers,DC=example,DC=com

Found ticket for example_user@EXAMPLE.COM to go to krbtgt/EXAMPLE.COM@EXAMPLE.COM expiring on Fri Aug 22 02:35:42 EDT 2014
Entered Krb5Context.initSecContext with state=STATE_NEW
Found ticket for example_user@EXAMPLE.COM to go to krbtgt/EXAMPLE.COM@EXAMPLE.COM expiring on Fri Aug 22 02:35:42 EDT 2014
Found ticket for example_user@EXAMPLE.COM to go to ldap/dc1.example.com@EXAMPLE.COM expiring on Fri Aug 22 02:35:42 EDT 2014
Service ticket not found in the subject
>>> Credentials acquireServiceCreds: same realm
Using builtin default etypes for default_tgs_enctypes
default etypes for default_tgs_enctypes: 18 17 16 23 1 3.
>>> CksumType: sun.security.krb5.internal.crypto.RsaMd5CksumType
>>> EType: sun.security.krb5.internal.crypto.ArcFourHmacEType
>>> KrbKdcReq send: kdc=dc1.example.com UDP:88, timeout=30000, number of retries =3, #bytes=1381
>>> KDCCommunication: kdc=dc1.example.com UDP:88, timeout=30000,Attempt =1, #bytes=1381
>>> KrbKdcReq send: #bytes read=94
>>> KdcAccessibility: remove dc1.example.com
>>> KDCRep: init() encoding tag is 126 req type is 13
>>>KRBError:
     sTime is Thu Aug 21 16:35:46 EDT 2014 0000000000000
     suSec is 918178
     error code is 7
     error Message is Server not found in Kerberos database
     realm is EXAMPLE.COM
     sname is ldap/example.com
     msgType is 30
KrbException: Server not found in Kerberos database (7)
    at sun.security.krb5.KrbTgsRep.<init>(KrbTgsRep.java:70)
    at sun.security.krb5.KrbTgsReq.getReply(KrbTgsReq.java:192)
    at sun.security.krb5.KrbTgsReq.sendAndGetCreds(KrbTgsReq.java:203)
    at sun.security.krb5.internal.CredentialsUtil.serviceCreds(CredentialsUtil.java:311)
    at sun.security.krb5.internal.CredentialsUtil.acquireServiceCreds(CredentialsUtil.java:115)
    at sun.security.krb5.Credentials.acquireServiceCreds(Credentials.java:442)
    at sun.security.jgss.krb5.Krb5Context.initSecContext(Krb5Context.java:641)
    at sun.security.jgss.GSSContextImpl.initSecContext(GSSContextImpl.java:248)
    at sun.security.jgss.GSSContextImpl.initSecContext(GSSContextImpl.java:179)
    at com.sun.security.sasl.gsskerb.GssKrb5Client.evaluateChallenge(GssKrb5Client.java:193)
    at com.sun.jndi.ldap.sasl.LdapSasl.saslBind(LdapSasl.java:123)
    at com.sun.jndi.ldap.LdapClient.authenticate(LdapClient.java:232)
    at com.sun.jndi.ldap.LdapCtx.connect(LdapCtx.java:2740)
    at com.sun.jndi.ldap.LdapCtx.<init>(LdapCtx.java:316)
    at com.sun.jndi.ldap.LdapCtxFactory.getUsingURL(LdapCtxFactory.java:193)
    at com.sun.jndi.ldap.LdapCtxFactory.getLdapCtxInstance(LdapCtxFactory.java:152)
    at com.sun.jndi.url.ldap.ldapURLContextFactory.getObjectInstance(ldapURLContextFactory.java:52)
    at javax.naming.spi.NamingManager.getURLObject(NamingManager.java:601)
    at javax.naming.spi.NamingManager.processURL(NamingManager.java:381)
    at javax.naming.spi.NamingManager.processURLAddrs(NamingManager.java:361)
    at javax.naming.spi.NamingManager.getObjectInstance(NamingManager.java:333)
    at com.sun.jndi.ldap.LdapReferralContext.<init>(LdapReferralContext.java:111)
    at com.sun.jndi.ldap.LdapReferralException.getReferralContext(LdapReferralException.java:150)
    at com.sun.jndi.ldap.LdapNamingEnumeration.hasMoreReferrals(LdapNamingEnumeration.java:357)
    at com.sun.jndi.ldap.LdapNamingEnumeration.hasMoreImpl(LdapNamingEnumeration.java:226)
    at com.sun.jndi.ldap.LdapNamingEnumeration.hasMore(LdapNamingEnumeration.java:189)
    at LdapContinuationDemoAction.run(LdapContinuationDemoAction.java:123)
    at java.security.AccessController.doPrivileged(Native Method)
    at javax.security.auth.Subject.doAs(Subject.java:415)
    at LdapContinuationDemoAction.main(LdapContinuationDemoAction.java:52)
Caused by: KrbException: Identifier doesn't match expected value (906)
    at sun.security.krb5.internal.KDCRep.init(KDCRep.java:143)
    at sun.security.krb5.internal.TGSRep.init(TGSRep.java:66)
    at sun.security.krb5.internal.TGSRep.<init>(TGSRep.java:61)
    at sun.security.krb5.KrbTgsRep.<init>(KrbTgsRep.java:55)
    ... 29 more

*** ERROR: java.security.PrivilegedActionException: javax.naming.PartialResultException [Root exception is javax.naming.AuthenticationException: GSSAPI [Root exception is javax.security.sasl.SaslException: GSS initiate failed [Caused by GSSException: No valid credentials provided (Mechanism level: Server not found in Kerberos database (7))]]]
Dominic A.
  • 496
  • 4
  • 9
  • This does not look like a Java problem but like a Windows AD admin one since you cannot set the SPN persistently?! – Michael-O Aug 27 '14 at 20:22
  • @Michael-O -- I'm not sure setting the a SPN manually is a viable choice long term since SPNs need to be unique and there can be multiple domain controllers bound to the domain name. For example if there were two domain controllers for a domain `example.com`: `dc1.example.com` and `dc2.example.com`. I could put the `ldap/example.com` under `dc1`. However when my client resolves `ldap://example.com`, due to round-robin DNS, the connection request might end up getting serviced by `dc2`. Since `dc2` does not own the SPN, it would not be able to validate the kerberos ticket. – Dominic A. Sep 03 '14 at 19:12
  • Where you actually able to resolve the issue? Have you contacted the mailing list. I have stumbled upon this at work and know now for sure that this is not a SASL issue. A forward and reverse lookup is (not) always the right thing to do. I will post an analysis on this and a theoretical workaround. – Michael-O Mar 11 '16 at 14:10

1 Answers1

4

You cannot and shall not register a SPN with the canonical realm name. SPNs have to be machine specific in this case. If you really want to use ldap://example.com, make sure that reverse DNS is perfomed before a SPN is built. MIT Kerberos, Heimdal and JGSS will peform a reverse DNS lookup by default but SSPI won't, so this is not realiable.

A better solution would be rather than providing a hostname, use DNS SRV to locate a DC and then perform a bind. So change your URL to ldap:///DC=example,DC=com.

Edit (2016-03-14): After more than 1,5 years I have stumbled upon this myself at work and made some research with Windows tools, Wireshark and Microsoft's documentation on the topic. Some of my previous statements need to be reverted, some updated. Here is the explanation also documented in my Tomcat SPNEGO/AD Authenticator:

Edit (2021-06-06): For those who still suffer from this, use my Active Directory DNS Locator and you are done.

Michael-O
  • 18,123
  • 6
  • 55
  • 121
  • Thanks, @Michael-O, I suspected that trying to work around it by defining a new SPN was the wrong tact. However, I do not control the URL I am binding to in this instance. I initially bind to `ldap://dc1.example.com` and get back referrals that look like `ldap://example.com/CN=Configuration,DC=example,DC=com`. – Dominic A. Sep 03 '14 at 20:01
  • 1
    Can you perform a CNAME lookup for `example.com`. Alternatively, you should switch to using the GC instead fo plain LDAP. Use port 3268. – Michael-O Sep 03 '14 at 20:07
  • Unfortunately there is no opportunity to intercept the referral URL and resolve it to a canonical name between the time the referral is returned and the time the LDAP library attempts to resolve it. It's all internal to how the Java LDAP provider handles referrals. The GC is a good point. Unfortunately the global catalog port is not on the list of **approved and blessed ports** for our application, so we aren't allowed to access it. It's a good idea for the next go-round though. – Dominic A. Sep 03 '14 at 20:40
  • If everything is so tight, you are probably lost. Try those options adn report. – Michael-O Sep 03 '14 at 21:07
  • Hitting the GC port definitely works. Our work around is to just specify multiple non-root DNs and initiate the search from each in turn and concatenate the results. Alternatively, we can catch the `ReferralException`s that get thrown when searching from the root DN and ignore them. My main concern is why is the AD controller returning URLs for which no SPN exists? The DC is perfectly aware no `ldap/example.com` SPN exists, so why return URLs in the from of `ldap://example.com` that will be impossible to obtain a ticket for? Every domain I've tested against has exhibited this behavior. – Dominic A. Sep 04 '14 at 14:14
  • The answer to your questions are simple, 1) LDAP does not care about authentication because this is completely handled by SASL. It cannot know that such a canonical SPN does not exist. 2) the realm DNS name is returned because returning a DC domain name is not safe. It can change over time, because Windows registers LDAP servers dynamically with DNS. `example.com` is guaranteed to work because of DNS round-robin. Enable reverse DNS resolution in your `krb.conf` and JGSS should resolve the real DNS name before a SPN is built. – Michael-O Sep 04 '14 at 16:22
  • I've set both `dns_canonicalize_hostname = true` and `rdns = true` in krb5.ini under `[libdefaults]`. I've also verified both forward and reverse DNS is working, e.g.: `nslookup example.com` returns `10.1.1.13`, `10.1.1.14`; `nslookup 10.1.1.13` returns `dc-01.example.com`; `nslookup 10.1.1.14` returns `dc-02.example.com`. However, GSS still requests a ticket for `ldap/example.com` when following the referral and I still see the same failure. Maybe the LDAP JNDI provider needs to be enhanced with an option to canonicalize host names before following referrals? – Dominic A. Sep 08 '14 at 15:59
  • 1
    @DominicA., just because you can set those options, they do not mean that they are respected by JGSS. Those props are available to [MIT Kerberos](http://web.mit.edu/kerberos/krb5-devel/doc/admin/conf_files/krb5_conf.html#libdefaults). Your DNS setup seems to be fine. What you can do now is to contact security-dev@openjdk mailing list Weijun Wang is quite fast in answering those kind of questions. Here is the creation of the [GSS context](http://hg.openjdk.java.net/jdk7u/jdk7u/jdk/file/828c4fedd29f/src/share/classes/com/sun/security/sasl/gsskerb/GssKrb5Client.java#l95). – Michael-O Sep 08 '14 at 18:42