killme2008 / killme2008/xmemcached

Authentication failure on CRAM-MD5

Open
#144 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Java
Stars
764
Forks
271
PR merge metrics
No merged PRs in 30d

Description

Hello @killme2008

I'm using this library to connect a Memcached node with `CRAM-MD5` SASL authentication. but I'm getting an authentication error.

I should mention that I tried similar code using the library `net.spy.memcached` and its client can successfully connect and fetch the data.

the versions I'm using:
this library: `2.4.8`
library `net.spy.memcached`: `2.12.3`

here is a reproducible code. just remember I created and ran 3 different Memcached nodes on 3 different ports on my local PC to test these two libraries against all the authentication methods (`no authentication`, `PLAIN`, and `CRAM-MD5`). both libraries successfully connect and fetch data using `no authentication` and `PLAIN` methods. but when I'm trying `CRAM-MD5`, only `net.spy.memcached` client can pass the authentication and get the data. `Xmemcached` client failed to authenticate. I'm wondering why. it seems I'm using closely similar configurations in both libraries. you can create similar nodes on your machine and then test the code below yourself.

My question is what should I do to make the Xmemcached client pass the `CRAM-MD5` authentication? I prefer using your library due to its synchronized methods. I'm using Memcached in an environment that does not need more than one node and also one client. so I don't want to struggle with the multithreading problems.

Thanks for your help.

```Java
import com.google.code.yanf4j.core.Session;
import net.rubyeye.xmemcached.MemcachedClientBuilder;
import net.rubyeye.xmemcached.XMemcachedClient;
import net.rubyeye.xmemcached.XMemcachedClientBuilder;
import net.rubyeye.xmemcached.auth.AuthInfo;
import net.rubyeye.xmemcached.auth.PlainCallbackHandler;
import net.rubyeye.xmemcached.command.BinaryCommandFactory;
import net.rubyeye.xmemcached.exception.MemcachedException;
import net.rubyeye.xmemcached.networking.MemcachedSession;
import net.spy.memcached.*;
import net.spy.memcached.auth.AuthDescriptor;
import net.spy.memcached.protocol.TCPMemcachedNodeImpl;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.TimeoutException;

public class App {
public enum MemcachedAuthenticationMethod {
NONE(null, null, null, 11213),
PLAIN("PLAIN", "username", "password", 11211),
CRAM_MD5("CRAM-MD5", "username", "123456", 11212);
private final String value;
private final String username;
private final String password;
private final Integer port;

MemcachedAuthenticationMethod(String value, String username, String password, Integer port) {
this.value = value;
this.username = username;
this.password = password;
this.port = port;
}

public String getValue() {
return value;
}

public String getUsername() {
return username;
}

public String getPassword() {
return password;
}

public Integer getPort() {
return port;
}
}

public static final MemcachedAuthenticationMethod memcachedAuthMethod = MemcachedAuthenticationMethod.CRAM_MD5;
public static final InetSocketAddress inetSocketAddress = new InetSocketAddress("memcached.obj.com", memcachedAuthMethod.getPort());

public static void showStatInfo(Map generalStats, Map settingsStats) throws Exception {
if (generalStats == null || generalStats.isEmpty()) {
throw new Exception("general stats is empty");
}
System.out.println(inetSocketAddress);
System.out.println("version: " + generalStats.get("version"));
System.out.println("pid: " + generalStats.get("pid"));
System.out.println("accepting_conns: " + generalStats.get("accepting_conns"));
System.out.println("tcpport: " + settingsStats.get("tcpport"));
System.out.println("udpport: " + settingsStats.get("udpport"));
System.out.println("evictions: " + settingsStats.get("evictions"));
System.out.println("item_size_max: " + settingsStats.get("item_size_max"));
System.out.println("cas_enabled: " + settingsStats.get("cas_enabled"));
System.out.println("maxconns_fast: " + settingsStats.get("maxconns_fast"));
System.out.println("lru_crawler: " + settingsStats.get("lru_crawler"));
System.out.println("proxy_enabled: " + settingsStats.get("proxy_enabled"));
}

public static void main(String[] args) {
spyMain();
System.out.println();
xMain();
}

public static void spyMain() {
System.out.println("**************************** Fetch using the library 'net.spy.memcached' *****************************");
MemcachedClient memcachedClient = null;
try {
ConnectionFactory connectionFactory = new DefaultConnectionFactory();
if (memcachedAuthMethod.getValue() != null) {
System.setProperty("net.spy.memcached.auth.AuthThreshold", "1");
final AuthDescriptor ad = new AuthDescriptor(
new String[]{memcachedAuthMethod.getValue()},
new net.spy.memcached.auth.PlainCallbackHandler(memcachedAuthMethod.getUsername(), memcachedAuthMethod.getPassword())
);
connectionFactory = new ConnectionFactoryBuilder()
.setProtocol(ConnectionFactoryBuilder.Protocol.BINARY)
.setAuthDescriptor(ad)
.setAuthWaitTime(5000)
.build();
}

memcachedClient = new MemcachedClient(connectionFactory, Collections.singletonList(inetSocketAddress));

MemcachedNode node = memcachedClient.getNodeLocator().getAll().stream().findFirst().orElse(null);
if (node == null) {
return;
} else if (node instanceof TCPMemcachedNodeImpl && !node.isAuthenticated()) {
return;
} else if (!node.isActive()) {
return;
}

Map generalStats = memcachedClient.getStats().get(inetSocketAddress);
Map settingsStats = memcachedClient.getStats("settings").get(inetSocketAddress);

showStatInfo(generalStats, settingsStats);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (Exception e) {
System.out.println("Exception discovering Memcached object on " + e);
throw new RuntimeException(e);
} finally {
if (memcachedClient != null) {
memcachedClient.shutdown();
}
}
}

public static void xMain() {
System.out.println("**************************** Fetch using the library 'Xmemcached' *****************************");
XMemcachedClient memcachedClient = null;
try {
MemcachedClientBuilder builder = new XMemcachedClientBuilder(Collections.singletonList(inetSocketAddress));
if (memcachedAuthMethod.getValue() != null) {
System.setProperty("net.rubyeye.xmemcached.auth_max_attempts", "0");
final AuthInfo ad = new AuthInfo(
new PlainCallbackHandler(memcachedAuthMethod.getUsername(), memcachedAuthMethod.getPassword()),
new String[]{memcachedAuthMethod.getValue()}
);
builder.addAuthInfo(inetSocketAddress, ad);
builder.setCommandFactory(new BinaryCommandFactory());
builder.setConnectTimeout(5000);
builder.setOpTimeout(5000);
}

memcachedClient = (XMemcachedClient) builder.build();

Session session = memcachedClient.getConnector().getSessionSet().stream().findFirst().orElse(null);
if (session == null) {
return;
} else if (session instanceof MemcachedSession && ((MemcachedSession) session).isAuthFailed()) {
return;
} else if (session.isClosed()) {
return;
}

Map generalStats = memcachedClient.getStats().get(inetSocketAddress);
Map settingsStats = memcachedClient.getStatsByItem("settings").get(inetSocketAddress);

showStatInfo(generalStats, settingsStats);
} catch (InterruptedException | MemcachedException | TimeoutException | IOException e) {
throw new RuntimeException(e);
} catch (Exception e) {
System.out.println("Exception discovering Memcached object on " + e);
throw new RuntimeException(e);
} finally {
if (memcachedClient != null) {
try {
memcachedClient.shutdown();
} catch (IOException ignored) {
}
}
}
}
}
```

and here is the console result:

```bash
**************************** Fetch using the library 'net.spy.memcached' *****************************
2024-01-07 14:56:57.784 INFO net.spy.memcached.MemcachedConnection: Setting retryQueueSize to -1
2024-01-07 14:56:57.790 INFO net.spy.memcached.MemcachedConnection: Added {QA sa=memcached.obj.com/20.150.30.16:11212, #Rops=0, #Wops=0, #iq=0, topRop=null, topWop=null, toWrite=0, interested=0} to connect queue
2024-01-07 14:56:58.033 INFO net.spy.memcached.auth.AuthThread: Authenticated to memcached.obj.com/20.150.30.16:11212
2024-01-07 14:56:58.037 INFO net.spy.memcached.MemcachedConnection: Shut down memcached client
2024-01-07 14:56:58.037 WARN net.spy.memcached.auth.AuthThreadMonitor: Connection shutdown in progress - interrupting waiting authentication thread.
2024-01-07 14:56:58.037 WARN net.spy.memcached.auth.AuthThread: Authentication failed to memcached.obj.com/20.150.30.16:11212, Status: {OperationStatus success=false: cancelled}
memcached.obj.com/20.150.30.16:11212
version: 1.6.22
pid: 1
accepting_conns: 1
tcpport: 11211
udpport: 0
evictions: on
item_size_max: 1048576
cas_enabled: yes
maxconns_fast: yes
lru_crawler: yes
proxy_enabled: null

**************************** Fetch using the library 'Xmemcached' *****************************
[main] INFO net.rubyeye.xmemcached.XMemcachedClient - XMemcachedClient is using Binary protocol
[main] INFO com.google.code.yanf4j.nio.impl.SelectorManager - Creating 8 reactors...
[main] INFO com.google.code.yanf4j.core.impl.AbstractController - The Controller started at localhost/127.0.0.1:0 ...
[Thread-12] ERROR net.rubyeye.xmemcached.auth.AuthTask - Authentication failed to memcached.obj.com/20.150.30.16:11212
[Thread-12] WARN net.rubyeye.xmemcached.auth.AuthTask - Reopen connection to memcached.obj.com/20.150.30.16:11212,beacause auth fail
[Xmemcached-Reactor-0] INFO com.google.code.yanf4j.core.impl.AbstractController - Add a session: 20.150.30.16:11212
[main] INFO com.google.code.yanf4j.core.impl.AbstractController - Controller has been stopped.
```

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with the XMemcached authentication entry points shown in the report, especially AuthTask, AuthInfo, PlainCallbackHandler, and BinaryCommandFactory, and compare them with the working net.spy.memcached configuration. Reproduce the CRAM-MD5 case against the supplied three-node setup; done means the Xmemcached client authenticates successfully and fetches the same stats as the comparison client.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
authentication, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.