Skip to content

3.x: preserve OVERLOADED responses during authentication #1159

Description

@dkropachev

Problem

ScyllaDB will return the native-protocol OVERLOADED error instead of BAD_CREDENTIALS when authentication cannot proceed because the server is overloaded.

Java driver 3.x converts every authentication-phase ERROR response into AuthenticationException, even though its decoder can produce OverloadedException.

private ListenableFuture<Void> authenticateV1(
Authenticator authenticator, final ProtocolVersion protocolVersion, final Executor executor) {
Requests.Credentials creds =
new Requests.Credentials(((ProtocolV1Authenticator) authenticator).getCredentials());
try {
Future authResponseFuture = write(creds);
return Futures.transformAsync(
authResponseFuture,
new AsyncFunction<Message.Response, Void>() {
@Override
public ListenableFuture<Void> apply(Message.Response authResponse) throws Exception {
switch (authResponse.type) {
case READY:
return checkClusterName(protocolVersion, executor);
case ERROR:
incrementAuthErrorMetric();
throw new AuthenticationException(
endPoint, ((Responses.Error) authResponse).message);
default:
throw new TransportException(
endPoint,
String.format(
"Unexpected %s response message from server to a CREDENTIALS message",
authResponse.type));
}
}
},
executor);
} catch (Exception e) {
return Futures.immediateFailedFuture(e);
}
}
private ListenableFuture<Void> authenticateV2(
final Authenticator authenticator,
final ProtocolVersion protocolVersion,
final Executor executor) {
byte[] initialResponse = authenticator.initialResponse();
if (null == initialResponse) initialResponse = EMPTY_BYTE_ARRAY;
try {
Future authResponseFuture = write(new Requests.AuthResponse(initialResponse));
return Futures.transformAsync(
authResponseFuture, onV2AuthResponse(authenticator, protocolVersion, executor), executor);
} catch (Exception e) {
return Futures.immediateFailedFuture(e);
}
}
private AsyncFunction<Message.Response, Void> onV2AuthResponse(
final Authenticator authenticator,
final ProtocolVersion protocolVersion,
final Executor executor) {
return new AsyncFunction<Message.Response, Void>() {
@Override
public ListenableFuture<Void> apply(Message.Response authResponse) throws Exception {
switch (authResponse.type) {
case AUTH_SUCCESS:
logger.trace("{} Authentication complete", this);
authenticator.onAuthenticationSuccess(((Responses.AuthSuccess) authResponse).token);
return checkClusterName(protocolVersion, executor);
case AUTH_CHALLENGE:
byte[] responseToServer =
authenticator.evaluateChallenge(((Responses.AuthChallenge) authResponse).token);
if (responseToServer == null) {
// If we generate a null response, then authentication has completed, proceed without
// sending a further response back to the server.
logger.trace("{} Authentication complete (No response to server)", this);
return checkClusterName(protocolVersion, executor);
} else {
// Otherwise, send the challenge response back to the server
logger.trace("{} Sending Auth response to challenge", this);
Future nextResponseFuture = write(new Requests.AuthResponse(responseToServer));
return Futures.transformAsync(
nextResponseFuture,
onV2AuthResponse(authenticator, protocolVersion, executor),
executor);
}
case ERROR:
// This is not very nice, but we're trying to identify if we
// attempted v2 auth against a server which only supports v1
// The AIOOBE indicates that the server didn't recognise the
// initial AuthResponse message
String message = ((Responses.Error) authResponse).message;
if (message.startsWith("java.lang.ArrayIndexOutOfBoundsException: 15"))
message =
String.format(
"Cannot use authenticator %s with protocol version 1, "
+ "only plain text authentication is supported with this protocol version",
authenticator);
incrementAuthErrorMetric();
throw new AuthenticationException(endPoint, message);

DriverException asException(EndPoint endPoint) {
switch (code) {
case SERVER_ERROR:
return new ServerError(endPoint, message);
case PROTOCOL_ERROR:
return new ProtocolError(endPoint, message);
case BAD_CREDENTIALS:
return new AuthenticationException(endPoint, message);
case UNAVAILABLE:
return ((UnavailableException) infos)
.copy(endPoint); // We copy to have a nice stack trace
case OVERLOADED:
return new OverloadedException(endPoint, message);
case IS_BOOTSTRAPPING:

The false AuthenticationException classification makes a transient overload look like invalid credentials, increments authentication-error metrics, and bypasses normal transient connection handling.

Java driver 4.x already handles this condition operationally: only AUTH_ERROR becomes AuthenticationException; other setup errors close the incomplete channel and remain eligible for node failover and reconnection.

Expected behavior

An OVERLOADED response to CREDENTIALS or AUTH_RESPONSE propagates as OverloadedException. The driver discards the incomplete connection and tries other contact points or reconnects according to policy.

Acceptance criteria

  • Protocol-v1 and SASL authentication propagate OverloadedException.
  • BAD_CREDENTIALS continues to produce AuthenticationException.
  • Authentication-error metrics count credential/authentication failures, not overload.
  • Initial control-connection setup tries another contact point after authentication-phase overload.
  • Regression tests cover overload classification and failover behavior.

Related

Jira: https://scylladb.atlassian.net/browse/DRIVER-1121

Parent: https://scylladb.atlassian.net/browse/DRIVER-115

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions