-
Notifications
You must be signed in to change notification settings - Fork 13.9k
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
Conversation
f7189cf
to
7f42041
Compare
7f42041
to
27b132a
Compare
38213b8
to
dfd26c9
Compare
Refer to this link for build results (access rights to CI server needed): |
Refer to this link for build results (access rights to CI server needed): |
Refer to this link for build results (access rights to CI server needed): |
Refer to this link for build results (access rights to CI server needed): |
Refer to this link for build results (access rights to CI server needed): |
Refer to this link for build results (access rights to CI server needed): |
b40d503
to
1d54a0e
Compare
} | ||
|
||
/** | ||
* Returns the delegation token owner if set on this instance. F |
There was a problem hiding this comment.
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."; |
There was a problem hiding this comment.
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 " + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: same as above
1d54a0e
to
27ffbc7
Compare
@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'."; |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
792e52d
to
97f5c9b
Compare
97f5c9b
to
3301fd0
Compare
Refer to this link for build results (access rights to CI server needed): |
3301fd0
to
0394612
Compare
bdf84b9
to
84d14ef
Compare
JaasContext.Type contextType, | ||
String saslMechanism, | ||
Class<? extends Login> defaultLoginClass) { | ||
String prefix = contextType == JaasContext.Type.SERVER ? ListenerName.saslMechanismPrefix(saslMechanism) : ""; |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
@rondagostino KIP-86 didnt make login callbacks configurable since we didn't have a mechanism where we needed it. We currently use the same |
@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 |
84d14ef
to
6cd1533
Compare
LoginMetadata<?> loginMetadata) throws IOException, LoginException { | ||
this.loginMetadata = loginMetadata; | ||
this.login = Utils.newInstance(loginMetadata.loginClass); | ||
AuthenticateCallbackHandler callbackHandler = Utils.newInstance(loginMetadata.loginCallbackClass); |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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 " |
There was a problem hiding this comment.
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.
There was a problem hiding this 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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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} |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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")); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
2bea156
to
a8aaa90
Compare
@rondagostino @junrao Thanks for the reviews. I have addressed the comments. |
There was a problem hiding this 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. |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
@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. |
@rondagostino @junrao Thanks for the reviews, merging to trunk. |
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>
Implementation of KIP-86: https://cwiki.apache.org/confluence/display/KAFKA/KIP-86%3A+Configurable+SASL+callback+handlers