Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KAFKA-4292: Configurable SASL callback handlers #2022

Merged
merged 3 commits into from
Apr 5, 2018

Conversation

rajinisivaram
Copy link
Contributor

@asfbot
Copy link

asfbot commented Mar 30, 2017

Refer to this link for build results (access rights to CI server needed):
https://builds.apache.org/job/kafka-pr-jdk8-scala2.11/2554/
Test FAILed (JDK 8 and Scala 2.11).

@asfbot
Copy link

asfbot commented Mar 30, 2017

Refer to this link for build results (access rights to CI server needed):
https://builds.apache.org/job/kafka-pr-jdk7-scala2.10/2556/
Test PASSed (JDK 7 and Scala 2.10).

@asfbot
Copy link

asfbot commented Mar 30, 2017

Refer to this link for build results (access rights to CI server needed):
https://builds.apache.org/job/kafka-pr-jdk8-scala2.12/2556/
Test PASSed (JDK 8 and Scala 2.12).

@asfbot
Copy link

asfbot commented Mar 30, 2017

Refer to this link for build results (access rights to CI server needed):
https://builds.apache.org/job/kafka-pr-jdk8-scala2.12/2550/
Test PASSed (JDK 8 and Scala 2.12).

@asfbot
Copy link

asfbot commented Mar 30, 2017

Refer to this link for build results (access rights to CI server needed):
https://builds.apache.org/job/kafka-pr-jdk7-scala2.10/2550/
Test FAILed (JDK 7 and Scala 2.10).

@asfbot
Copy link

asfbot commented Mar 30, 2017

Refer to this link for build results (access rights to CI server needed):
https://builds.apache.org/job/kafka-pr-jdk8-scala2.11/2560/
Test FAILed (JDK 8 and Scala 2.11).

}

/**
* Returns the delegation token owner if set on this instance. F
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: letter "F" at the end of the line

@@ -51,6 +51,12 @@
+ "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>)*;'";

public static final String SASL_CLIENT_CALLBACK_HANDLER_CLASS = "sasl.client.callback.handler.class";
public static final String SASL_CLIENT_CALLBACK_HANDLER_CLASS_DOC = "A Sasl client callback handler class that implements the AuthenticateCallbackHandler interface.";
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Similar to the other sasl config descriptions., we can use "SASL" Acronym in descriptions.

@@ -67,4 +68,9 @@
+ "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_MAP_DOC = "A map between Sasl mechanisms and Sasl server " +
"callback handler classes that implement the AuthenticateCallbackHandler interface. Key and value are " +
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: same as above

@rajinisivaram
Copy link
Contributor Author

@junrao Do you have time to review this PR? Thank you!

public static final String SASL_SERVER_CALLBACK_HANDLER_CLASS_MAP_DOC = "A map between SASL mechanisms and SASL server " +
"callback handler classes that implement the AuthenticateCallbackHandler interface. Key and value are " +
"separated by a colon and map entries are separated by commas. For example, " +
"'PLAIN=CustomPlainCallbackHandler,SCRAM-SHA-256=CustomScramCallbackHandler'.";
Copy link
Contributor

@rondagostino rondagostino Jan 27, 2018

Choose a reason for hiding this comment

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

Example should use ":" instead of "=": PLAIN:CustomPlainCallbackHandler,SCRAM-SHA-256:CustomScramCallbackHandler

It also might be good to somehow include an indication that the values are fully-qualified class names ("A map between SASL mechanisms and fully-qualified SASL server callback handler class names", or "org.mypackage.CustomPlainCalbackHandler")

String className = callbackClassMap.get(mechanism);
if (className != null)
callbackHandler = Utils.newInstance(className, AuthenticateCallbackHandler.class);
else if (mechanism.equals(PlainSaslServer.PLAIN_MECHANISM))
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it ever be acceptable to use the config map to override the callback handlers for the built-in mechanism implementations (GSSAPI, PLAIN, and the two SCRAM-related ones)? If so then the code is fine, but if not then the built-in mechanism names should be explicitly checked for first. Same goes for the client side checks above.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@rondagostino Thanks for the review. Yes, it is acceptable to override callback handlers for built-in mechanisms. One of the motivations for the KIP is to enable customization of built-in mechanisms to integrate with existing authentication servers (e.g. use an existing server or database to verify passwords for PLAIN).

@@ -222,4 +268,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, String mechanism) {
Class<? extends AuthenticateCallbackHandler> clazz = (Class<? extends AuthenticateCallbackHandler>) configs.get(SaslConfigs.SASL_CLIENT_CALLBACK_HANDLER_CLASS);
Copy link
Contributor

Choose a reason for hiding this comment

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

See comment below about the order of built-in mechanisms vs. non-built-in ones.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same as before, callback handlers can be overridden for built-in mechanisms too.

@asfgit
Copy link

asfgit commented Feb 5, 2018

Refer to this link for build results (access rights to CI server needed):
https://builds.apache.org/job/kafka-pr-test-coverage/333/

JaasContext.Type contextType,
String saslMechanism,
Class<? extends Login> defaultLoginClass) {
String prefix = contextType == JaasContext.Type.SERVER ? ListenerName.saslMechanismPrefix(saslMechanism) : "";
Copy link
Contributor

Choose a reason for hiding this comment

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

From SaslConfigs#SASL_LOGIN_CLASS_DOC:

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

This code is only using the SASL mechanism name in lower-case as the prefix. Assuming this observation is in fact a problem, I think it raises a broader issue, which is that the listener (SASL_SSL vs. SASL_PLAIN) is not known at this point in the code (or at least it isn't readily available).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Listener prefix is removed from the config name before it gets here. This code will find scram-sha-256.sasl.login.class in configs only if it was prefixed with the listener name that this method is being invoked for. It is the same with callback handlers too.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok, so no problem in the code at this point. This behavior -- stripping the listener prefix off before passing the config map to the code when it runs for a particular listener -- probably warrants documentation, but I'm not sure where that documentation should be (or if it exists already and I just haven't seen). Thanks for the clarification.

@rajinisivaram
Copy link
Contributor Author

@rondagostino KIP-86 didnt make login callbacks configurable since we didn't have a mechanism where we needed it. We currently use the same LoginCallbackHandler for all mechanisms. If we want to use callbacks for OAuth login, I think it will be better to do this with a separate configuration option sasl.login.callback.handler.class rather than reuse the client or server callbacks since these serve different purposes. I can add the option to this PR and update KIP-86 if you agree that it is the right approach for OAuth.

@rondagostino
Copy link
Contributor

@rajinisivaram Ok, I now understand the difference between the two client-side callback handlers. The SASL Client callback handler is supposed to mediate between the SASL Client and the JAAS Login Module -- when the SASL client needs something it should use the SASL Client callback handler to get it rather than going to get it by itself (for example, looking in the Subject's public or private credentials). By using the callback handler like this (as opposed to hard-coding the SASL Client to look somewhere) everything becomes pluggable. I hadn't been doing that, so I will make that change in the OAuth code. Now that I understand this difference, yes, we do need to be able to configure the Login callback handler. In the OAuth case this callback handler is responsible for providing the OAuthBearerToken. Can you add the sasl.login.callback.handler.class configuration option as suggested?

LoginMetadata<?> loginMetadata) throws IOException, LoginException {
this.loginMetadata = loginMetadata;
this.login = Utils.newInstance(loginMetadata.loginClass);
AuthenticateCallbackHandler callbackHandler = Utils.newInstance(loginMetadata.loginCallbackClass);
Copy link
Contributor

Choose a reason for hiding this comment

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

@rajinisivaram The login callback handler class isn't getting its #configure(Map<String, ?>, String, List) invoked. Perhaps it might be better to treat the login callback handler class the same way the client and server callback handler classes are treated, which is to create/configure them in SaslChannelBuilder? Note that the login callback handler class is potentially used both on the client side and on the server side (it is used on the broker when the mechanism is the inter-broker protocol).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@rondagostino Thanks for the review. Good catch. Added invocation of configure and missing handler in KafkaConfig. The lifecycle of LoginCallbackHandler is the same as that of Login, both managed by LoginManager. In particular, LoginCallbackHandler/Login can outlive SaslChannelBuilder. Hence these instances are created in LoginManager.

+ "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 "
Copy link
Contributor

@rondagostino rondagostino Mar 30, 2018

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.

Copy link
Contributor

@junrao junrao left a comment

Choose a reason for hiding this comment

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

@rajinisivaram : Thanks for the patch. Looks good overall. Just a few minor comments below.

* @param saslMechanism Negotiated SASL mechanism
* @param jaasConfigEntries JAAS configuration entries from the JAAS login context.
* This list contains a single entry for clients and may contain more than
* one entry for servers if multiple mechanisms are enabled on a listener.
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, in the ChannelBuilder, it seems that we always pass in the entry for one mechanism.

Also, could we add some comments to clarify the difference between configs and jaasConfigEntries? For example, if a key config is present in both, which one takes precedence.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

jaasConfigEntries can contain multiple entries if using static JAAS config with multiple entries in a single KafkaServer login context when multiple mechanisms are enabled. Updated the doc to reflect that and also added clarification for configs and jaasConfigEntries.

@@ -32,7 +31,8 @@
/**
* Configures this login instance.
*/
void configure(Map<String, ?> configs, JaasContext jaasContext);
void configure(Map<String, ?> configs, String contextName, Configuration configuration,
AuthenticateCallbackHandler loginCallbackHandler);
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be useful to add a comment to describe the difference between configs and Configuration.

@@ -33,6 +33,7 @@ import org.apache.kafka.clients.admin.{AlterConfigsOptions, ConfigEntry, Describ
import org.apache.kafka.common.config.ConfigResource
import org.apache.kafka.common.security.JaasUtils
import org.apache.kafka.common.security.scram._
import org.apache.kafka.common.security.scram.internal.{ScramCredentialUtils, ScramFormatter, ScramMechanism}
Copy link
Contributor

Choose a reason for hiding this comment

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

The import for org.apache.kafka.common.security.scram._ seems unused now?

@@ -184,6 +184,14 @@ public Password dynamicJaasConfig() {
* If login module name is specified, return option value only from that module.
*/
public String configEntryOption(String key, String loginModuleName) {
return configEntryOption(configurationEntries, key, loginModuleName);
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems configEntryOption() is never used?

@@ -937,6 +944,9 @@ object KafkaConfig {
.define(SaslMechanismInterBrokerProtocolProp, STRING, Defaults.SaslMechanismInterBrokerProtocol, MEDIUM, SaslMechanismInterBrokerProtocolDoc)
.define(SaslJaasConfigProp, PASSWORD, null, MEDIUM, SaslJaasConfigDoc)
.define(SaslEnabledMechanismsProp, LIST, Defaults.SaslEnabledMechanisms, MEDIUM, SaslEnabledMechanismsDoc)
.define(SaslServerCallbackHandlerClassProp, STRING, null, MEDIUM, SaslServerCallbackHandlerClassProp)
Copy link
Contributor

Choose a reason for hiding this comment

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

The last param should be Doc instead of Prop?

@@ -110,6 +108,7 @@
private final Map<String, ?> configs;
private final KafkaPrincipalBuilder principalBuilder;
private final DelegationTokenCache tokenCache;
private final Map<String, AuthenticateCallbackHandler> callbackHandlers;
Copy link
Contributor

Choose a reason for hiding this comment

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

tokenCache, credentialCache and jaasContexts are no longer used?

TestServerCallbackHandler.class.getName());
server = createEchoServer(securityProtocol);

jaasConfig.setClientOptions("PLAIN", TestServerCallbackHandler.USERNAME, TestServerCallbackHandler.PASSWORD);
Copy link
Contributor

Choose a reason for hiding this comment

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

These are the configs for the server. So, the name setClientOptions is a bit mis-leading.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is setting the client options based on the values used in the server callback. Added a comment to clarify.

@Test
public void testAuthenticateCallbackHandlerMechanisms() throws Exception {
SecurityProtocol securityProtocol = SecurityProtocol.SASL_PLAINTEXT;
configureMechanisms("DIGEST-MD5", Arrays.asList("DIGEST-MD5", "PLAIN"));
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to add DIGEST-MD5?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The test is using two mechanisms with two different callbacks to verify that the right callback is used when there are multiple mechanisms. Added comment and also updated the test to verify both mechanisms.

String prefix = ListenerName.forSecurityProtocol(securityProtocol).saslMechanismConfigPrefix("PLAIN");
saslServerConfigs.put(prefix + SaslConfigs.SASL_LOGIN_CLASS, TestLogin.class.getName());
server = createEchoServer(securityProtocol);
assertEquals(1, TestLogin.loginCount.get());
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, there is no connection yet, why would count be 1?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

login() is performed when the server channel builder is created, hence the count is one when the server starts even before any clients connect.


// This test uses SASL callback handler overrides for server connections of Kafka broker
// and client connections of Kafka producers and consumers. Client connections of Kafka brokers
// use default callback handlers. The second client used in the multi-user test
Copy link
Contributor

Choose a reason for hiding this comment

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

"Client connections of Kafka brokers use default callback handlers." It seems that the clients are using customized callbacks?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Broker use default client callback handlers for inter-broker communication. Producers/consumers are configured with customized callbacks. Updated the comment.

@rajinisivaram
Copy link
Contributor Author

@rondagostino @junrao Thanks for the reviews. I have addressed the comments.

Copy link
Contributor

@junrao junrao left a comment

Choose a reason for hiding this comment

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

@rajinisivaram Thanks for the updated patch. LGTM. Just a few minor comments below.

* from `jaasConfiguration`.
* @param contextName JAAS context name for this login which may be used to obtain
* the login context from `jaasConfiguration`.
* @param loginCallbackHandler Login callback handler instance to use for this Login.
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we add the java doc for jaasConfiguration?


@Override
public int hashCode() {
return Objects.hash(configInfo, loginClass);
Copy link
Contributor

Choose a reason for hiding this comment

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

Does the hashcode need to include loginCallbackClass ?


// This test uses SASL callback handler overrides for server connections of Kafka broker
// and client connections of Kafka producers and consumers. Client connections from Kafka brokers
// used for inter-broker communication use default callback handlers. The second client used in
Copy link
Contributor

Choose a reason for hiding this comment

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

We set KafkaConfig.SaslClientCallbackHandlerClassProp in line 106. So, it seems that the broker connection also has a customized callback?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, I had missed that earlier, updated the comment.

@rajinisivaram
Copy link
Contributor Author

@junrao Thanks for the review. I have addressed the comments. If there are no other comments, I will merge later on today after the builds complete.

@rajinisivaram
Copy link
Contributor Author

@rondagostino @junrao Thanks for the reviews, merging to trunk.

@rajinisivaram rajinisivaram merged commit 9f8c316 into apache:trunk Apr 5, 2018
ying-zheng pushed a commit to ying-zheng/kafka that referenced this pull request Jul 6, 2018
Implementation of KIP-86. Client, server and login callback handlers have been made configurable for both brokers and clients.

Reviewers: Jun Rao <junrao@gmail.com>, Ron Dagostino <rndgstn@gmail.com>, Manikumar Reddy <manikumar.reddy@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants