According to the Solace documentation, the only way to configure Kerberos authentication is via providing jaas.conf and a keytab:
In my application, I am trying to do programmatic Kerberos authentication. You may ask why? Well several reasons:
- I don’t want the keytabs and any file deps.
- I fetch the credentials from vault in the container during runtime. I have mechanisms in place to fetch creds rotations etc.
- Major one: I have Database authentication as well via JavaKerberos & Integrated security via creds from vault.
The Code:
For programmatic Kerberos authentication, generally the code goes like this:
public class MyCustomUsernamePasswordCallbackHandler implements CallbackHandler {
private final String username;
private final String password;
public MyCustomUsernamePasswordCallbackHandler(String username, String password) {
this.username = username;
this.password = password;
}
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (Callback callback : callbacks) {
if (callback instanceof NameCallback) {
((NameCallback) callback).setName(username);
} else if (callback instanceof PasswordCallback) {
((PasswordCallback) callback).setPassword(password.toCharArray());
} else {
throw new UnsupportedCallbackException(callback);
}
}
}
}
CallBackHandler callbackHandler = new MyCustomUsernamePasswordCallbackHandler("solace-user@YOUR.REALM", "yourPassword");
LoginContext loginContext = new LoginContext(
"SolaceGSS",
null,
new UsernamePasswordCallbackHandler("solace-user@YOUR.REALM", "yourPassword"),
new Configuration() {
@Override
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
Map<String, String> options = new HashMap<>();
options.put("useTicketCache", "false");
options.put("storeKey", "false");
options.put("doNotPrompt", "false");
options.put("useFirstPass", "true");
options.put("principal", "solace-user@YOUR.REALM");
options.put("debug", "true");
return new AppConfigurationEntry[] {
new AppConfigurationEntry(
"com.sun.security.auth.module.Krb5LoginModule",
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
options
) };
}
});
loginContext.login();
Subject subject = loginContext.getSubject();
return Subject.doAs(subject, (PrivilegedAction<SolConnectionFactory>) () -> {
SolConnectionFactory cf = SolJmsUtility.createConnectionFactory();
Hashtable<String, Object> env = new Hashtable<>();
env.put(SupportedProperty.SOLACE_JMS_HOST, "tcp://your-solace-host:port");
env.put(SupportedProperty.SOLACE_JMS_AUTHENTICATION_SCHEME, SupportedProperty.AUTHENTICATION_SCHEME_GSS_KRB);
env.put(SupportedProperty.SOLACE_JMS_KRB_SERVICE_NAME, "solace");
return SolJmsUtility.createConnectionFactory(env);
});
Now, the login actually gets successful. But the issue is, Solace doesn’t care about the LoginContext and session I created! Internally it tries to create it’s own login context. It tries to only read the global configuration and disregards the above Login subject I created with my custom jaas config and callback handler.That code resides in SimpleSmfClient → getKRBToken method.
So when I try to listen to anything with @JmsListener, it tries to authenticate on its own, with it’s own login context, and authentication fails becz it doesn’t find any jaas configs for Solace (Global one).
This is weird!
It gets messed up when there are other use cases like, say I want to pause Solace connections via JmsListenerEndpointRegistry when my DB connection is getting renewed. Then when I resume listeners, it tries to authenticate again… that too via it’s own new internal Login context.
What is the solution for this? What is the workaround? Is there a fix?