@ -45,6 +45,7 @@ import javax.security.auth.login.Configuration;
@@ -45,6 +45,7 @@ import javax.security.auth.login.Configuration;
import javax.security.auth.login.AppConfigurationEntry ;
import javax.security.auth.login.LoginContext ;
import javax.security.auth.login.LoginException ;
import javax.security.sasl.SaslException ;
import org.apache.kafka.clients.NetworkClient ;
import org.apache.kafka.common.KafkaException ;
@ -259,6 +260,74 @@ public class SaslAuthenticatorTest {
@@ -259,6 +260,74 @@ public class SaslAuthenticatorTest {
}
}
/ * *
* Verify that messages from SaslExceptions thrown in the server during authentication are not
* propagated to the client since these may contain sensitive data .
* /
@Test
public void testClientExceptionDoesNotContainSensitiveData ( ) throws Exception {
InvalidScramServerCallbackHandler . reset ( ) ;
SecurityProtocol securityProtocol = SecurityProtocol . SASL_PLAINTEXT ;
TestJaasConfig jaasConfig = configureMechanisms ( "SCRAM-SHA-256" , Collections . singletonList ( "SCRAM-SHA-256" ) ) ;
jaasConfig . createOrUpdateEntry ( TestJaasConfig . LOGIN_CONTEXT_SERVER , PlainLoginModule . class . getName ( ) , new HashMap < > ( ) ) ;
String callbackPrefix = ListenerName . forSecurityProtocol ( securityProtocol ) . saslMechanismConfigPrefix ( "SCRAM-SHA-256" ) ;
saslServerConfigs . put ( callbackPrefix + BrokerSecurityConfigs . SASL_SERVER_CALLBACK_HANDLER_CLASS ,
InvalidScramServerCallbackHandler . class . getName ( ) ) ;
server = createEchoServer ( securityProtocol ) ;
try {
InvalidScramServerCallbackHandler . sensitiveException =
new IOException ( "Could not connect to password database locahost:8000" ) ;
createAndCheckClientAuthenticationFailure ( securityProtocol , "1" , "SCRAM-SHA-256" , null ) ;
InvalidScramServerCallbackHandler . sensitiveException =
new SaslException ( "Password for existing user " + TestServerCallbackHandler . USERNAME + " is invalid" ) ;
createAndCheckClientAuthenticationFailure ( securityProtocol , "1" , "SCRAM-SHA-256" , null ) ;
InvalidScramServerCallbackHandler . reset ( ) ;
InvalidScramServerCallbackHandler . clientFriendlyException =
new SaslAuthenticationException ( "Credential verification failed" ) ;
createAndCheckClientAuthenticationFailure ( securityProtocol , "1" , "SCRAM-SHA-256" ,
InvalidScramServerCallbackHandler . clientFriendlyException . getMessage ( ) ) ;
} finally {
InvalidScramServerCallbackHandler . reset ( ) ;
}
}
public static class InvalidScramServerCallbackHandler implements AuthenticateCallbackHandler {
// We want to test three types of exceptions:
// 1) IOException since we can throw this from callback handlers. This may be sensitive.
// 2) SaslException (also an IOException) which may contain data from external (or JRE) servers and callbacks and may be sensitive
// 3) SaslAuthenticationException which is from our own code and is used only for client-friendly exceptions
// We use two different exceptions here since the only checked exception CallbackHandler can throw is IOException,
// covering case 1) and 2). For case 3), SaslAuthenticationException is a RuntimeExceptiom.
static volatile IOException sensitiveException ;
static volatile SaslAuthenticationException clientFriendlyException ;
@Override
public void configure ( Map < String , ? > configs , String saslMechanism , List < AppConfigurationEntry > jaasConfigEntries ) {
}
@Override
public void handle ( Callback [ ] callbacks ) throws IOException {
if ( sensitiveException ! = null )
throw sensitiveException ;
if ( clientFriendlyException ! = null )
throw clientFriendlyException ;
}
@Override
public void close ( ) {
reset ( ) ;
}
static void reset ( ) {
sensitiveException = null ;
clientFriendlyException = null ;
}
}
/ * *
* Tests that mechanisms that are not supported in Kafka can be plugged in without modifying
* Kafka code if Sasl client and server providers are available .
@ -1814,17 +1883,9 @@ public class SaslAuthenticatorTest {
@@ -1814,17 +1883,9 @@ public class SaslAuthenticatorTest {
ChannelState finalState = createAndCheckClientConnectionFailure ( securityProtocol , node ) ;
Exception exception = finalState . exception ( ) ;
assertTrue ( "Invalid exception class " + exception . getClass ( ) , exception instanceof SaslAuthenticationException ) ;
if ( expectedErrorMessage ! = null )
// check for full equality
assertEquals ( expectedErrorMessage , exception . getMessage ( ) ) ;
else {
String expectedErrorMessagePrefix = "Authentication failed during authentication due to invalid credentials with SASL mechanism "
+ mechanism + ": " ;
if ( exception . getMessage ( ) . startsWith ( expectedErrorMessagePrefix ) )
return ;
// we didn't match a recognized error message, so fail
fail ( "Incorrect failure message: " + exception . getMessage ( ) ) ;
}
String expectedExceptionMessage = expectedErrorMessage ! = null ? expectedErrorMessage :
"Authentication failed during authentication due to invalid credentials with SASL mechanism " + mechanism ;
assertEquals ( expectedExceptionMessage , exception . getMessage ( ) ) ;
}
private ChannelState createAndCheckClientConnectionFailure ( SecurityProtocol securityProtocol , String node )