Browse Source

KAFKA-7915: Don't return sensitive authentication errors to clients (#6252)

Don't return error messages from `SaslException` to clients. Error messages to be returned to clients to aid debugging must be thrown as AuthenticationExceptions. This is a fix for a regression from KAFKA-7352.

Reviewers: Ron Dagostino <rndgstn@gmail.com>, Ismael Juma <ismael@juma.me.uk
pull/6257/head
Rajini Sivaram 6 years ago committed by GitHub
parent
commit
5148d7b0ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      clients/src/main/java/org/apache/kafka/common/security/authenticator/SaslServerAuthenticator.java
  2. 83
      clients/src/test/java/org/apache/kafka/common/security/authenticator/SaslAuthenticatorTest.java

5
clients/src/main/java/org/apache/kafka/common/security/authenticator/SaslServerAuthenticator.java

@ -465,10 +465,11 @@ public class SaslServerAuthenticator implements Authenticator { @@ -465,10 +465,11 @@ public class SaslServerAuthenticator implements Authenticator {
// Handle retriable Kerberos exceptions as I/O exceptions rather than authentication exceptions
throw e;
} else {
// DO NOT include error message from the `SaslException` in the client response since it may
// contain sensitive data like the existence of the user.
String errorMessage = "Authentication failed during "
+ reauthInfo.authenticationOrReauthenticationText()
+ " due to invalid credentials with SASL mechanism " + saslMechanism + ": "
+ e.getMessage();
+ " due to invalid credentials with SASL mechanism " + saslMechanism;
sendKafkaResponse(requestContext, new SaslAuthenticateResponse(Errors.SASL_AUTHENTICATION_FAILED,
errorMessage));
throw new SaslAuthenticationException(errorMessage, e);

83
clients/src/test/java/org/apache/kafka/common/security/authenticator/SaslAuthenticatorTest.java

@ -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)

Loading…
Cancel
Save