Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,24 @@ public class SaslConfigs {
public static final String SASL_JAAS_CONFIG = "sasl.jaas.config";
public static final String SASL_JAAS_CONFIG_DOC = "JAAS login context parameters for SASL connections in the format used by JAAS configuration files. "
+ "JAAS configuration file format is described <a href=\"http://docs.oracle.com/javase/8/docs/technotes/guides/security/jgss/tutorials/LoginConfigFile.html\">here</a>. "
+ "The format for the value is: '<loginModuleClass> <controlFlag> (<optionName>=<optionValue>)*;'";
+ "The format for the value is: '<loginModuleClass> <controlFlag> (<optionName>=<optionValue>)*;'. For brokers, "
+ "the config must be prefixed with listener prefix and SASL mechanism name in lower-case. For example, "
+ "listener.name.sasl_ssl.scram-sha-256.sasl.jaas.config=com.example.ScramLoginModule required;";

public static final String SASL_CLIENT_CALLBACK_HANDLER_CLASS = "sasl.client.callback.handler.class";
public static final String SASL_CLIENT_CALLBACK_HANDLER_CLASS_DOC = "The fully qualified name of a SASL client callback handler class "
+ "that implements the AuthenticateCallbackHandler interface.";

public static final String SASL_LOGIN_CALLBACK_HANDLER_CLASS = "sasl.login.callback.handler.class";
public static final String SASL_LOGIN_CALLBACK_HANDLER_CLASS_DOC = "The fully qualified name of a SASL login callback handler class "

@rondagostino rondagostino Mar 30, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this documentation should state that "For brokers, login callback handler config must be prefixed with listener prefix and SASL mechanism name in lower-case" because this is valid to use on both the client-side and the broker side.

+ "that implements the AuthenticateCallbackHandler interface. For brokers, login callback handler config must be prefixed with "
+ "listener prefix and SASL mechanism name in lower-case. For example, "
+ "listener.name.sasl_ssl.scram-sha-256.sasl.login.callback.handler.class=com.example.CustomScramLoginCallbackHandler";

public static final String SASL_LOGIN_CLASS = "sasl.login.class";
public static final String SASL_LOGIN_CLASS_DOC = "The fully qualified name of a class that implements the Login interface. "
+ "For brokers, login config must be prefixed with listener prefix and SASL mechanism name in lower-case. For example, "
+ "listener.name.sasl_ssl.scram-sha-256.sasl.login.class=com.example.CustomScramLogin";

public static final String SASL_KERBEROS_SERVICE_NAME = "sasl.kerberos.service.name";
public static final String SASL_KERBEROS_SERVICE_NAME_DOC = "The Kerberos principal name that Kafka runs as. "
Expand Down Expand Up @@ -95,6 +112,9 @@ public static void addClientSaslSupport(ConfigDef config) {
.define(SaslConfigs.SASL_KERBEROS_TICKET_RENEW_JITTER, ConfigDef.Type.DOUBLE, SaslConfigs.DEFAULT_KERBEROS_TICKET_RENEW_JITTER, ConfigDef.Importance.LOW, SaslConfigs.SASL_KERBEROS_TICKET_RENEW_JITTER_DOC)
.define(SaslConfigs.SASL_KERBEROS_MIN_TIME_BEFORE_RELOGIN, ConfigDef.Type.LONG, SaslConfigs.DEFAULT_KERBEROS_MIN_TIME_BEFORE_RELOGIN, ConfigDef.Importance.LOW, SaslConfigs.SASL_KERBEROS_MIN_TIME_BEFORE_RELOGIN_DOC)
.define(SaslConfigs.SASL_MECHANISM, ConfigDef.Type.STRING, SaslConfigs.DEFAULT_SASL_MECHANISM, ConfigDef.Importance.MEDIUM, SaslConfigs.SASL_MECHANISM_DOC)
.define(SaslConfigs.SASL_JAAS_CONFIG, ConfigDef.Type.PASSWORD, null, ConfigDef.Importance.MEDIUM, SaslConfigs.SASL_JAAS_CONFIG_DOC);
.define(SaslConfigs.SASL_JAAS_CONFIG, ConfigDef.Type.PASSWORD, null, ConfigDef.Importance.MEDIUM, SaslConfigs.SASL_JAAS_CONFIG_DOC)
.define(SaslConfigs.SASL_CLIENT_CALLBACK_HANDLER_CLASS, ConfigDef.Type.CLASS, null, ConfigDef.Importance.MEDIUM, SaslConfigs.SASL_CLIENT_CALLBACK_HANDLER_CLASS_DOC)
.define(SaslConfigs.SASL_LOGIN_CALLBACK_HANDLER_CLASS, ConfigDef.Type.CLASS, null, ConfigDef.Importance.MEDIUM, SaslConfigs.SASL_LOGIN_CALLBACK_HANDLER_CLASS_DOC)
.define(SaslConfigs.SASL_LOGIN_CLASS, ConfigDef.Type.CLASS, null, ConfigDef.Importance.MEDIUM, SaslConfigs.SASL_LOGIN_CLASS_DOC);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public class BrokerSecurityConfigs {
public static final String SASL_KERBEROS_PRINCIPAL_TO_LOCAL_RULES_CONFIG = "sasl.kerberos.principal.to.local.rules";
public static final String SSL_CLIENT_AUTH_CONFIG = "ssl.client.auth";
public static final String SASL_ENABLED_MECHANISMS_CONFIG = "sasl.enabled.mechanisms";
public static final String SASL_SERVER_CALLBACK_HANDLER_CLASS = "sasl.server.callback.handler.class";

public static final String PRINCIPAL_BUILDER_CLASS_DOC = "The fully qualified name of a class that implements the " +
"KafkaPrincipalBuilder interface, which is used to build the KafkaPrincipal object used during " +
Expand Down Expand Up @@ -67,4 +68,9 @@ public class BrokerSecurityConfigs {
+ "Only GSSAPI is enabled by default.";
public static final List<String> DEFAULT_SASL_ENABLED_MECHANISMS = Collections.singletonList(SaslConfigs.GSSAPI_MECHANISM);

public static final String SASL_SERVER_CALLBACK_HANDLER_CLASS_DOC = "The fully qualified name of a SASL server callback handler "
+ "class that implements the AuthenticateCallbackHandler interface. Server callback handlers must be prefixed with "
+ "listener prefix and SASL mechanism name in lower-case. For example, "
+ "listener.name.sasl_ssl.plain.sasl.server.callback.handler.class=com.example.CustomPlainCallbackHandler.";

}
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ public String configPrefix() {
}

public String saslMechanismConfigPrefix(String saslMechanism) {
return configPrefix() + saslMechanism.toLowerCase(Locale.ROOT) + ".";
return configPrefix() + saslMechanismPrefix(saslMechanism);
}

public static String saslMechanismPrefix(String saslMechanism) {
return saslMechanism.toLowerCase(Locale.ROOT) + ".";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,35 @@
import org.apache.kafka.common.config.SslConfigs;
import org.apache.kafka.common.config.internals.BrokerSecurityConfigs;
import org.apache.kafka.common.memory.MemoryPool;
import org.apache.kafka.common.security.auth.SecurityProtocol;
import org.apache.kafka.common.security.JaasContext;
import org.apache.kafka.common.security.token.delegation.DelegationTokenCache;
import org.apache.kafka.common.security.kerberos.KerberosShortNamer;
import org.apache.kafka.common.security.auth.AuthenticateCallbackHandler;
import org.apache.kafka.common.security.auth.Login;
import org.apache.kafka.common.security.auth.SecurityProtocol;
import org.apache.kafka.common.security.authenticator.CredentialCache;
import org.apache.kafka.common.security.authenticator.DefaultLogin;
import org.apache.kafka.common.security.authenticator.LoginManager;
import org.apache.kafka.common.security.authenticator.SaslClientAuthenticator;
import org.apache.kafka.common.security.authenticator.SaslClientCallbackHandler;
import org.apache.kafka.common.security.authenticator.SaslServerAuthenticator;
import org.apache.kafka.common.security.authenticator.SaslServerCallbackHandler;
import org.apache.kafka.common.security.kerberos.KerberosClientCallbackHandler;
import org.apache.kafka.common.security.kerberos.KerberosLogin;
import org.apache.kafka.common.security.kerberos.KerberosShortNamer;
import org.apache.kafka.common.security.plain.internal.PlainSaslServer;
import org.apache.kafka.common.security.plain.internal.PlainServerCallbackHandler;
import org.apache.kafka.common.security.scram.ScramCredential;
import org.apache.kafka.common.security.scram.internal.ScramMechanism;
import org.apache.kafka.common.security.scram.internal.ScramServerCallbackHandler;
import org.apache.kafka.common.security.ssl.SslFactory;
import org.apache.kafka.common.security.token.delegation.DelegationTokenCache;
import org.apache.kafka.common.utils.Java;
import org.apache.kafka.common.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.io.IOException;
import java.net.Socket;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
Expand Down Expand Up @@ -66,6 +79,7 @@ public class SaslChannelBuilder implements ChannelBuilder, ListenerReconfigurabl
private SslFactory sslFactory;
private Map<String, ?> configs;
private KerberosShortNamer kerberosShortNamer;
private Map<String, AuthenticateCallbackHandler> saslCallbackHandlers;

public SaslChannelBuilder(Mode mode,
Map<String, JaasContext> jaasContexts,
Expand All @@ -87,15 +101,25 @@ public SaslChannelBuilder(Mode mode,
this.clientSaslMechanism = clientSaslMechanism;
this.credentialCache = credentialCache;
this.tokenCache = tokenCache;
this.saslCallbackHandlers = new HashMap<>();
}

@SuppressWarnings("unchecked")
@Override
public void configure(Map<String, ?> configs) throws KafkaException {
try {
this.configs = configs;
boolean hasKerberos = jaasContexts.containsKey(SaslConfigs.GSSAPI_MECHANISM);
if (mode == Mode.SERVER)
createServerCallbackHandlers(configs);
else
createClientCallbackHandler(configs);
for (Map.Entry<String, AuthenticateCallbackHandler> entry : saslCallbackHandlers.entrySet()) {
String mechanism = entry.getKey();
entry.getValue().configure(configs, mechanism, jaasContexts.get(mechanism).configurationEntries());
}

if (hasKerberos) {
Class<? extends Login> defaultLoginClass = DefaultLogin.class;
if (jaasContexts.containsKey(SaslConfigs.GSSAPI_MECHANISM)) {
String defaultRealm;
try {
defaultRealm = defaultKerberosRealm();
Expand All @@ -106,12 +130,13 @@ public void configure(Map<String, ?> configs) throws KafkaException {
List<String> principalToLocalRules = (List<String>) configs.get(BrokerSecurityConfigs.SASL_KERBEROS_PRINCIPAL_TO_LOCAL_RULES_CONFIG);
if (principalToLocalRules != null)
kerberosShortNamer = KerberosShortNamer.fromUnparsedRules(defaultRealm, principalToLocalRules);
defaultLoginClass = KerberosLogin.class;
}
for (Map.Entry<String, JaasContext> entry : jaasContexts.entrySet()) {
String mechanism = entry.getKey();
// With static JAAS configuration, use KerberosLogin if Kerberos is enabled. With dynamic JAAS configuration,
// use KerberosLogin only for the LoginContext corresponding to GSSAPI
LoginManager loginManager = LoginManager.acquireLoginManager(entry.getValue(), mechanism, hasKerberos, configs);
LoginManager loginManager = LoginManager.acquireLoginManager(entry.getValue(), mechanism, defaultLoginClass, configs);
loginManagers.put(mechanism, loginManager);
subjects.put(mechanism, loginManager.subject());
}
Expand All @@ -120,7 +145,7 @@ public void configure(Map<String, ?> configs) throws KafkaException {
this.sslFactory = new SslFactory(mode, "none", isInterBrokerListener);
this.sslFactory.configure(configs);
}
} catch (Exception e) {
} catch (Throwable e) {
close();
throw new KafkaException(e);
}
Expand Down Expand Up @@ -156,11 +181,20 @@ public KafkaChannel buildChannel(String id, SelectionKey key, int maxReceiveSize
TransportLayer transportLayer = buildTransportLayer(id, key, socketChannel);
Authenticator authenticator;
if (mode == Mode.SERVER) {
authenticator = buildServerAuthenticator(configs, id, transportLayer, subjects);
authenticator = buildServerAuthenticator(configs,
saslCallbackHandlers,
id,
transportLayer,
subjects);
} else {
LoginManager loginManager = loginManagers.get(clientSaslMechanism);
authenticator = buildClientAuthenticator(configs, id, socket.getInetAddress().getHostName(),
loginManager.serviceName(), transportLayer, loginManager.subject());
authenticator = buildClientAuthenticator(configs,
saslCallbackHandlers.get(clientSaslMechanism),
id,
socket.getInetAddress().getHostName(),
loginManager.serviceName(),
transportLayer,
subjects.get(clientSaslMechanism));
}
return new KafkaChannel(id, transportLayer, authenticator, maxReceiveSize, memoryPool != null ? memoryPool : MemoryPool.NONE);
} catch (Exception e) {
Expand All @@ -174,6 +208,8 @@ public void close() {
for (LoginManager loginManager : loginManagers.values())
loginManager.release();
loginManagers.clear();
for (AuthenticateCallbackHandler handler : saslCallbackHandlers.values())
handler.close();
}

private TransportLayer buildTransportLayer(String id, SelectionKey key, SocketChannel socketChannel) throws IOException {
Expand All @@ -186,16 +222,23 @@ private TransportLayer buildTransportLayer(String id, SelectionKey key, SocketCh
}

// Visible to override for testing
protected SaslServerAuthenticator buildServerAuthenticator(Map<String, ?> configs, String id,
TransportLayer transportLayer, Map<String, Subject> subjects) throws IOException {
return new SaslServerAuthenticator(configs, id, jaasContexts, subjects,
kerberosShortNamer, credentialCache, listenerName, securityProtocol, transportLayer, tokenCache);
protected SaslServerAuthenticator buildServerAuthenticator(Map<String, ?> configs,
Map<String, AuthenticateCallbackHandler> callbackHandlers,
String id,
TransportLayer transportLayer,
Map<String, Subject> subjects) throws IOException {
return new SaslServerAuthenticator(configs, callbackHandlers, id, subjects,
kerberosShortNamer, listenerName, securityProtocol, transportLayer);
}

// Visible to override for testing
protected SaslClientAuthenticator buildClientAuthenticator(Map<String, ?> configs, String id,
String serverHost, String servicePrincipal, TransportLayer transportLayer, Subject subject) throws IOException {
return new SaslClientAuthenticator(configs, id, subject, servicePrincipal,
protected SaslClientAuthenticator buildClientAuthenticator(Map<String, ?> configs,
AuthenticateCallbackHandler callbackHandler,
String id,
String serverHost,
String servicePrincipal,
TransportLayer transportLayer, Subject subject) throws IOException {
return new SaslClientAuthenticator(configs, callbackHandler, id, subject, servicePrincipal,
serverHost, clientSaslMechanism, handshakeRequestEnable, transportLayer);
}

Expand Down Expand Up @@ -224,4 +267,30 @@ private static String defaultKerberosRealm() throws ClassNotFoundException, NoSu
getDefaultRealmMethod = classRef.getDeclaredMethod("getDefaultRealm", new Class[0]);
return (String) getDefaultRealmMethod.invoke(kerbConf, new Object[0]);
}

private void createClientCallbackHandler(Map<String, ?> configs) {
Class<? extends AuthenticateCallbackHandler> clazz = (Class<? extends AuthenticateCallbackHandler>) configs.get(SaslConfigs.SASL_CLIENT_CALLBACK_HANDLER_CLASS);
if (clazz == null)
clazz = clientSaslMechanism.equals(SaslConfigs.GSSAPI_MECHANISM) ? KerberosClientCallbackHandler.class : SaslClientCallbackHandler.class;
AuthenticateCallbackHandler callbackHandler = Utils.newInstance(clazz);
saslCallbackHandlers.put(clientSaslMechanism, callbackHandler);
}

private void createServerCallbackHandlers(Map<String, ?> configs) throws ClassNotFoundException {
for (String mechanism : jaasContexts.keySet()) {
AuthenticateCallbackHandler callbackHandler;
String prefix = ListenerName.saslMechanismPrefix(mechanism);
Class<? extends AuthenticateCallbackHandler> clazz =
(Class<? extends AuthenticateCallbackHandler>) configs.get(prefix + BrokerSecurityConfigs.SASL_SERVER_CALLBACK_HANDLER_CLASS);
if (clazz != null)
callbackHandler = Utils.newInstance(clazz);
else if (mechanism.equals(PlainSaslServer.PLAIN_MECHANISM))
callbackHandler = new PlainServerCallbackHandler();
else if (ScramMechanism.isScram(mechanism))
callbackHandler = new ScramServerCallbackHandler(credentialCache.cache(mechanism, ScramCredential.class), tokenCache);
else
callbackHandler = new SaslServerCallbackHandler();
saslCallbackHandlers.put(mechanism, callbackHandler);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ public Password dynamicJaasConfig() {
* Returns the configuration option for <code>key</code> from this context.
* If login module name is specified, return option value only from that module.
*/
public String configEntryOption(String key, String loginModuleName) {
public static String configEntryOption(List<AppConfigurationEntry> configurationEntries, String key, String loginModuleName) {
for (AppConfigurationEntry entry : configurationEntries) {
if (loginModuleName != null && !loginModuleName.equals(entry.getLoginModuleName()))
continue;
Expand Down
Loading