Please Note:- I just want to validate whether the following can be achieved using the JAAS/GSSAPI. I am not able to find any pointers.
Let me first clear the constraints on my application:
- We can't have a static krb.conf file. It is dynamic and constantly changing. Hence, caching LoginContext objects in memory is not possible as the Subject's credentials are invalidated once the krb.conf file is modified.
- We want to manage large set of realms of which we don't have any prior information, hence static krb.conf file is not possible.
- "
sun.security.krb5.internal.tools.Kinit
" is proprietary and always get a warning it might get removed in future releases. So we can't use this to generate the cache because of the risk of getting a runtime issues. Note: Cached TGTs don't expire even if the krb.conf file is changed natively. But the problem here is, we will have to make our code of generating and maintaining the krb.conf file much complex. We want to avoid this- We are keeping the option of running the kinit tool using Runtime#exec(...) as the last resort to generate the TGT cache files (which will reduce the dependency on sun.security.krb5.internal.tools.Kinit package) but will make our krb.conf generation and maintenance logic more complex.
- I am in search of figuring out a more easy solution.
- Our current implementation is very straightforward: We change our krb.conf file to suit the current realm and domain information and then delete the file. Everything being very dynamic, it is very efficient but the problem is, we are not able to cache the TGTs and service tickets which increases the load on KDC server and results in performance hit!
With the thorough introduction, let us move to the PoC that I am trying out. Please provide pointers or on:
- Whether I am moving in the right direction or not?
- Are the things that I am envisioning even possible or not?
- If anyone of you have come across such a scenario, what strategy have you employed?
We are connecting to Active Directory DCs to form a LdapContext/ DirContext to fetch the AD object Data.
When there is not cached data available, proceed normally to create the LoginContext as usual:
System.setProperty("java.security.krb5.conf", "C:\\Windows\\krb5.ini");
System.setProperty("sun.security.jgss.debug","true");
LoginContext lc = null;
try {
lc = new LoginContext(LdapCtxGSSAPIEx.class.getName(),
new LoginCallbackHandler(username,password));
// Attempt authentication
// You might want to do this in a "for" loop to give
// user more than one chance to enter correct username/password
lc.login();
} catch (LoginException le) {
System.err.println("Authentication attempt failed" + le);
System.exit(-1);
}
Solution #1:
Once the valid LoginContext
is created, use GSSAPI to get the service ticket for ldap. I know of the other way (which actually is our current implementation, and shown in Solution#2) to form the LdapContext in the PrivilegedAction#run(). But that is not helping in caching. Hence, trying a PoC on storing the Service Ticket instead of the TGT. Getting the ldap service ticket is as follows:
// servicePrincipalName = ldap/ad01.example.lab@EXAMPLE.LAB
GSSManager manager = GSSManager.getInstance();
GSSName serverName = manager.createName( servicePrincipalName,
GSSName.NT_HOSTBASED_SERVICE);
final GSSContext context = manager.createContext( serverName, krb5OID, null,
GSSContext.DEFAULT_LIFETIME);
// The GSS context initiation has to be performed as a privileged action.
byte[] serviceTicket = Subject.doAs( subject, new PrivilegedAction<byte[]>() {
public byte[] run() {
try {
byte[] token = new byte[0];
// This is a one pass context initialisation.
context.requestMutualAuth( false);
context.requestCredDeleg( false);
return context.initSecContext( token, 0, token.length);
}
catch ( GSSException e) {
e.printStackTrace();
return null;
}
}
});
Questions:
- How can I use this service ticket to form the
LdapContext
?- Can I store this ticket in a file (encoded) and then later use the ticket in the same way to form the LdapContext ?
Solution #2: Creating the LdapContext as seen in most of the tutorials and also in our current implementation:
DirContext ctx = Subject.doAs(lc.getSubject(), new JndiAction<DirContext>(args,providerURL ));
class JndiAction<DirContext> implements java.security.PrivilegedAction<DirContext> {
.......
public DirContext run() {
return performJndiOperation(args, this.providerURL);
}
public DirContext performJndiOperation(String[] args, String providerURL){
......
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
// Must use fully qualified hostname
env.put(Context.PROVIDER_URL, providerURL);
// Request the use of the "GSSAPI" SASL mechanism
// Authenticate by using already established Kerberos credentials
env.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
// Request mutual authentication
env.put("javax.security.sasl.server.authentication", "true");
DirContext ctx = new InitialDirContext(env);
return ctx;
......
}
.......
}
When this line: DirContext ctx = new InitialDirContext(env);
in the above code is executed: the Subject
is populated with a new PrivateCredential
with the service principal : ldap/ad01.example.lab@EXAMPLE.LAB
Question: Can I store this service ticket for further reference to create the LdapContext instead of again performing all the steps of authentication again and again?
What are delegation credentials? Would they help in solving my issue?
UPDATE: Why store the service ticket and not the TGT in cache? ---> To avoid kinit explicitly. Storing the service ticket will fit in with our current solution easily. Also, this is going to be a temporary solution as we are going to ask our customers the krb.conf file as per their needs and get off the responsibility of creating a krb.conf file. But for now, we have to do this!
Please Help!