I am using a Java Spring Boot application with Waffle SSO library waffle-spring-security4 2.0.0 and have installed and configured MIT Kerberos. The ticket cache is working fine, and running "kinit" creates Kerberos tickets in the ticket cache (C:\Users\XYZ\krb5cc_XYZ). I have verified this by running "klist".
I have implemented JAAS Keytab authentication, which generates the GSSCredential and GSSContext. However, I am unsure about how to extract the Kerberos KT ticket from JAAS or Waffle SSO and authenticate JSch using the extracted ticket.
The following code generates a Kerberos service ticket and successfully authenticates JSch, enabling me to run a command on the remote machine. However, when I run the code for another user, the previous user's TGT is being used. I have disposed of the GSSCredential and GSSContext, but it does not solve the issue.
JSch itself performs all the GSS Authentication and creates the GSSCredential and GSSContext, but I don't have the control to modify the internal behavior and configuration. Therefore, I am performing JAAS authentication and creating GSSCredential and GSSContext.
What suggestions do you have for what I am missing?
public static ArrayList<String> simpleKerberos(String command)
throws LoginException, GSSException, JSchException, IOException, PrivilegedActionException {
SunJaasKerberosClient client = new SunJaasKerberosClient();
client.setDebug(true); // enable debug logs
ArrayList<String> results = new ArrayList<String>();
SecurityContext sec = SecurityContextHolder.getContext();
Authentication authentication = sec.getAuthentication();
WindowsPrincipal principal = (WindowsPrincipal) authentication.getPrincipal();
String userNtId = principal.getName();
String[] userInfo = userNtId.split("\\\\");
String username = userInfo[1].toLowerCase() + "@" + userInfo[0] + ".DOMAIN.COM";
String userId = userInfo[1];
String keyTabFilePath = "C:\\Users\\" + userId + "\\" + userId + ".keytab";
String ticketCache = "FILE:C:\\Users\\" + userId + "\\krb5cc_" + userId + "";
String loginContextName = "Krb5Login";
System.setProperty("java.security.krb5.conf", "C:\\ProgramData\\MIT\\Kerberos5\\krb5.ini");
System.setProperty("java.security.auth.login.config", "C:\\ProgramData\\MIT\\Kerberos5\\jaas.config");
//System.setProperty("sun.security.jgss.native", "true");
System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
System.setProperty("sun.security.krb5.debug", "true");
System.setProperty("java.security.debug", "gssloginconfig,configfile,configparser,logincontext");
System.setProperty("KRB5CCNAME", ticketCache);
System.setProperty("javax.security.auth.kerberos.ticket.cache", "FILE:" + ticketCache);
System.setProperty("KRB5_KTNAME", "C:\\Users\\" + userId + "\\" + userId + ".keytab");
System.setProperty("user.home", "C:\\Users\\" + userId + "");
System.setProperty("user.name", userId);
System.setProperty("principal", username);
String host = "REMOTE.MACHINE.FQDN.COM";
String realm = "SUBDOMAIN.DOMAIN.COM";
Configuration loginConfig = new Configuration() {
@Override
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
if (name.equals(loginContextName)) {
Map<String, Object> options = new HashMap<>();
options.put("useKeyTab", "true");
options.put("useTicketCache", "true");
options.put("debug", "true");
options.put("principal", username);
options.put("ticketCache", ticketCache);
// Create a Subject that represents the current user's security context
Subject subject = new Subject();
Principal principal = new KerberosPrincipal(username);
subject.getPrincipals().add(principal);
options.put("javax.security.auth.subject", subject);
// options.put("runAs", "authenticated");
// options.put("doNotPrompt", "true");
return new AppConfigurationEntry[] {
new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule",
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options) };
}
return null;
}
};
LoginContext loginContext = new LoginContext(loginContextName, null,
new KerberosKeytabCallbackHandler(username, keyTabFilePath), loginConfig);
loginContext.login();
GSSManager gssManager = GSSManager.getInstance();
GSSName clientName = gssManager.createName(username, GSSName.NT_USER_NAME);
GSSCredential credential = null;
Subject subject = null;
try {
subject = loginContext.getSubject();
credential = Subject.doAs(subject, (PrivilegedExceptionAction<GSSCredential>) () -> {
return gssManager.createCredential(clientName, GSSCredential.DEFAULT_LIFETIME,
new Oid("1.2.840.113554.1.2.2"), GSSCredential.ACCEPT_ONLY); ---Not sure, If I should've used INITIATE_ONLY
});
} catch (PrivilegedActionException e) {
e.printStackTrace();
}
Set<GSSCredential> gssCredentials = subject.getPrivateCredentials(GSSCredential.class);
gssCredentials.add(credential);
if (credential == null) {
throw new RuntimeException("Could not obtain Kerberos credentials for " + username);
}
GSSContext gssContext = gssManager.createContext(clientName, new Oid("1.2.840.113554.1.2.2"), credential,
GSSContext.DEFAULT_LIFETIME);
gssContext.requestCredDeleg(true);
gssContext.requestMutualAuth(true);
gssContext.requestConf(true);
gssContext.requestInteg(true);
gssContext.requestReplayDet(true);
gssContext.requestSequenceDet(true);
gssContext.requestAnonymity(false);
gssContext.requestLifetime(GSSContext.DEFAULT_LIFETIME);
gssContext.setChannelBinding(null);
JSch jsch = new JSch();
JSch.setConfig("PreferredAuthentications", "gssapi-with-mic");
Session session = jsch.getSession(username, host, 22);
session.setConfig("StrictHostKeyChecking", "no");
UserInfo user = new KerberosUserInfo(credential, gssContext);
session.setUserInfo(user);
session.setConfig("userauth.gssapi-with-mic", "com.jcraft.jsch.UserAuthGSSAPIWithMIC");
session.connect();
Channel channel = session.openChannel("exec");
((ChannelExec) channel).setCommand(command);
channel.setInputStream(null);
((ChannelExec) channel).setErrStream(System.err);
InputStream in = channel.getInputStream();
channel.connect();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
String line = null;
while ((line = reader.readLine()) != null) {
results.add(line);
}
jsch.removeAllIdentity();
channel.disconnect();
session.disconnect();
loginContext.logout();
loginConfig.refresh();
gssContext.dispose();
credential.dispose();
return results;
}
Kerberos UserInfo
class KerberosUserInfo implements UserInfo {
private GSSCredential credential;
private GSSContext context;
public KerberosUserInfo(GSSCredential credential) {
this.credential = credential;
}
public KerberosUserInfo(GSSCredential credential, GSSContext context) {
this.credential = credential;
this.context = context;
}
@Override
public String getPassphrase() {
return null;
}
@Override
public String getPassword() {
return null;
}
@Override
public boolean promptPassphrase(String message) {
return false;
}
@Override
public boolean promptPassword(String message) {
return false;
}
public boolean promptYesNo(String message) {
return true;
}
@Override
public void showMessage(String message) {
System.out.println(message);
}
/*
* public GSSCredential getGSSCredential() { return credential; }
*/
public GSSCredential getGSSCredential() {
return credential;
}
public GSSContext getGSSContext() {
return context;
}
}
KerberosKeytabCallbackHandler
public class KerberosKeytabCallbackHandler implements CallbackHandler {
private final String principal;
private final String keytab;
public KerberosKeytabCallbackHandler(String principal, String keytab) {
this.principal = Objects.requireNonNull(principal);
this.keytab = Objects.requireNonNull(keytab);
}
@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (Callback callback : callbacks) {
if (callback instanceof NameCallback) {
((NameCallback) callback).setName(principal);
} else if (callback instanceof PasswordCallback) {
((PasswordCallback) callback).setPassword(keytab.toCharArray());
} else {
throw new UnsupportedCallbackException(callback);
}
}
}
}