Skip to content

Commit 5a3c6a9

Browse files
authored
ZOOKEEPER-4839: Fix SASL DIGEST-MD5 authenticated with last successfully logined username
Reviewers: kezhuw, kezhuw, kezhuw, anmolnar Author: luoxiner Closes #2176 from luoxiner/master
1 parent 8900618 commit 5a3c6a9

File tree

11 files changed

+173
-64
lines changed

11 files changed

+173
-64
lines changed

zookeeper-server/src/main/java/org/apache/zookeeper/Login.java

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.Date;
2929
import java.util.Set;
3030
import java.util.concurrent.ThreadLocalRandom;
31+
import java.util.function.Supplier;
3132
import javax.security.auth.Subject;
3233
import javax.security.auth.callback.CallbackHandler;
3334
import javax.security.auth.kerberos.KerberosPrincipal;
@@ -48,7 +49,7 @@ public class Login {
4849
private static final String KINIT_COMMAND_DEFAULT = "/usr/bin/kinit";
4950
private static final Logger LOG = LoggerFactory.getLogger(Login.class);
5051
public static final String SYSTEM_USER = System.getProperty("user.name", "<NA>");
51-
public CallbackHandler callbackHandler;
52+
private final Supplier<CallbackHandler> callbackHandlerSupplier;
5253

5354
// LoginThread will sleep until 80% of time from last refresh to
5455
// ticket's expiry has been reached, at which time it will wake
@@ -89,17 +90,17 @@ public class Login {
8990
* name of section in JAAS file that will be use to login. Passed
9091
* as first param to javax.security.auth.login.LoginContext().
9192
*
92-
* @param callbackHandler
93-
* Passed as second param to
94-
* javax.security.auth.login.LoginContext().
93+
* @param callbackHandlerSupplier
94+
* Per connection callbackhandler supplier.
95+
*
9596
* @param zkConfig
9697
* client or server configurations
9798
* @throws javax.security.auth.login.LoginException
9899
* Thrown if authentication fails.
99100
*/
100-
public Login(final String loginContextName, CallbackHandler callbackHandler, final ZKConfig zkConfig) throws LoginException {
101+
public Login(final String loginContextName, Supplier<CallbackHandler> callbackHandlerSupplier, final ZKConfig zkConfig) throws LoginException {
101102
this.zkConfig = zkConfig;
102-
this.callbackHandler = callbackHandler;
103+
this.callbackHandlerSupplier = callbackHandlerSupplier;
103104
login = login(loginContextName);
104105
this.loginContextName = loginContextName;
105106
subject = login.getSubject();
@@ -274,6 +275,17 @@ public void run() {
274275
t.setDaemon(true);
275276
}
276277

278+
/**
279+
* Return a new CallbackHandler for connections
280+
* to avoid race conditions and state sharing in
281+
* connection login processing.
282+
*
283+
* @return connection dependent CallbackHandler
284+
*/
285+
public CallbackHandler newCallbackHandler() {
286+
return callbackHandlerSupplier.get();
287+
}
288+
277289
public void startThreadIfNeeded() {
278290
// thread object 't' will be null if a refresh thread is not needed.
279291
if (t != null) {
@@ -315,7 +327,7 @@ private synchronized LoginContext login(final String loginContextName) throws Lo
315327
+ ") and your "
316328
+ getLoginContextMessage());
317329
}
318-
LoginContext loginContext = new LoginContext(loginContextName, callbackHandler);
330+
LoginContext loginContext = new LoginContext(loginContextName, newCallbackHandler());
319331
loginContext.login();
320332
LOG.info("{} successfully logged in.", loginContextName);
321333
return loginContext;

zookeeper-server/src/main/java/org/apache/zookeeper/client/ZooKeeperSaslClient.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
import java.security.PrivilegedActionException;
2323
import java.security.PrivilegedExceptionAction;
2424
import java.util.concurrent.atomic.AtomicReference;
25+
import java.util.function.Supplier;
2526
import javax.security.auth.Subject;
27+
import javax.security.auth.callback.CallbackHandler;
2628
import javax.security.auth.login.AppConfigurationEntry;
2729
import javax.security.auth.login.Configuration;
2830
import javax.security.auth.login.LoginException;
@@ -240,9 +242,10 @@ private SaslClient createSaslClient(
240242
try {
241243
if (loginRef.get() == null) {
242244
LOG.debug("JAAS loginContext is: {}", loginContext);
243-
// note that the login object is static: it's shared amongst all zookeeper-related connections.
244-
// in order to ensure the login is initialized only once, it must be synchronized the code snippet.
245-
Login l = new Login(loginContext, new SaslClientCallbackHandler(null, "Client"), clientConfig);
245+
Supplier<CallbackHandler> callbackHandlerSupplier = () -> {
246+
return new SaslClientCallbackHandler(null, "Client");
247+
};
248+
Login l = new Login(loginContext, callbackHandlerSupplier, clientConfig);
246249
if (loginRef.compareAndSet(null, l)) {
247250
l.startThreadIfNeeded();
248251
}

zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerCnxnFactory.java

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,13 @@
2222
import java.net.InetSocketAddress;
2323
import java.nio.ByteBuffer;
2424
import java.util.Collections;
25+
import java.util.HashMap;
2526
import java.util.Map;
2627
import java.util.Set;
2728
import java.util.concurrent.ConcurrentHashMap;
29+
import java.util.function.Supplier;
2830
import javax.management.JMException;
31+
import javax.security.auth.callback.CallbackHandler;
2932
import javax.security.auth.login.AppConfigurationEntry;
3033
import javax.security.auth.login.Configuration;
3134
import javax.security.auth.login.LoginException;
@@ -41,6 +44,7 @@ public abstract class ServerCnxnFactory {
4144

4245
public static final String ZOOKEEPER_SERVER_CNXN_FACTORY = "zookeeper.serverCnxnFactory";
4346
private static final String ZOOKEEPER_MAX_CONNECTION = "zookeeper.maxCnxns";
47+
private static final String DIGEST_MD5_USER_PREFIX = "user_";
4448
public static final int ZOOKEEPER_MAX_CONNECTION_DEFAULT = 0;
4549

4650
private static final Logger LOG = LoggerFactory.getLogger(ServerCnxnFactory.class);
@@ -113,7 +117,6 @@ public void configure(InetSocketAddress addr, int maxcc, int backlog) throws IOE
113117

114118
public abstract void reconfigure(InetSocketAddress addr);
115119

116-
protected SaslServerCallbackHandler saslServerCallbackHandler;
117120
public Login login;
118121

119122
/** Maximum number of connections allowed from particular host (ip) */
@@ -269,8 +272,11 @@ protected void configureSaslLogin() throws IOException {
269272

270273
// jaas.conf entry available
271274
try {
272-
saslServerCallbackHandler = new SaslServerCallbackHandler(Configuration.getConfiguration());
273-
login = new Login(serverSection, saslServerCallbackHandler, new ZKConfig());
275+
Map<String, String> credentials = getDigestMd5Credentials(entries);
276+
Supplier<CallbackHandler> callbackHandlerSupplier = () -> {
277+
return new SaslServerCallbackHandler(credentials);
278+
};
279+
login = new Login(serverSection, callbackHandlerSupplier, new ZKConfig());
274280
setLoginUser(login.getUserName());
275281
login.startThreadIfNeeded();
276282
} catch (LoginException e) {
@@ -280,6 +286,28 @@ protected void configureSaslLogin() throws IOException {
280286
}
281287
}
282288

289+
/**
290+
* make server credentials map from configuration's server section.
291+
* @param appConfigurationEntries AppConfigurationEntry List
292+
* @return Server credentials map
293+
*/
294+
private static Map<String, String> getDigestMd5Credentials(final AppConfigurationEntry[] appConfigurationEntries) {
295+
Map<String, String> credentials = new HashMap<>();
296+
for (AppConfigurationEntry entry : appConfigurationEntries) {
297+
Map<String, ?> options = entry.getOptions();
298+
// Populate DIGEST-MD5 user -> password map with JAAS configuration entries from the "Server" section.
299+
// Usernames are distinguished from other options by prefixing the username with a "user_" prefix.
300+
for (Map.Entry<String, ?> pair : options.entrySet()) {
301+
String key = pair.getKey();
302+
if (key.startsWith(DIGEST_MD5_USER_PREFIX)) {
303+
String userName = key.substring(DIGEST_MD5_USER_PREFIX.length());
304+
credentials.put(userName, (String) pair.getValue());
305+
}
306+
}
307+
}
308+
return credentials;
309+
}
310+
283311
private static void setLoginUser(String name) {
284312
//Created this method to avoid ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD find bug issue
285313
loginUser = name;

zookeeper-server/src/main/java/org/apache/zookeeper/server/ZooKeeperSaslServer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public class ZooKeeperSaslServer {
4141
private SaslServer createSaslServer(final Login login) {
4242
synchronized (login) {
4343
Subject subject = login.getSubject();
44-
return SecurityUtils.createSaslServer(subject, "zookeeper", "zk-sasl-md5", login.callbackHandler, LOG);
44+
return SecurityUtils.createSaslServer(subject, "zookeeper", "zk-sasl-md5", login.newCallbackHandler(), LOG);
4545
}
4646
}
4747

zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/SaslServerCallbackHandler.java

Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -19,57 +19,29 @@
1919
package org.apache.zookeeper.server.auth;
2020

2121
import java.io.IOException;
22-
import java.util.HashMap;
2322
import java.util.Map;
2423
import javax.security.auth.callback.Callback;
2524
import javax.security.auth.callback.CallbackHandler;
2625
import javax.security.auth.callback.NameCallback;
2726
import javax.security.auth.callback.PasswordCallback;
2827
import javax.security.auth.callback.UnsupportedCallbackException;
29-
import javax.security.auth.login.AppConfigurationEntry;
30-
import javax.security.auth.login.Configuration;
3128
import javax.security.sasl.AuthorizeCallback;
3229
import javax.security.sasl.RealmCallback;
33-
import org.apache.zookeeper.server.ZooKeeperSaslServer;
3430
import org.slf4j.Logger;
3531
import org.slf4j.LoggerFactory;
3632

3733
public class SaslServerCallbackHandler implements CallbackHandler {
3834

39-
private static final String USER_PREFIX = "user_";
4035
private static final Logger LOG = LoggerFactory.getLogger(SaslServerCallbackHandler.class);
4136
private static final String SYSPROP_SUPER_PASSWORD = "zookeeper.SASLAuthenticationProvider.superPassword";
4237
private static final String SYSPROP_REMOVE_HOST = "zookeeper.kerberos.removeHostFromPrincipal";
4338
private static final String SYSPROP_REMOVE_REALM = "zookeeper.kerberos.removeRealmFromPrincipal";
4439

4540
private String userName;
46-
private final Map<String, String> credentials = new HashMap<>();
41+
private final Map<String, String> credentials;
4742

48-
public SaslServerCallbackHandler(Configuration configuration) throws IOException {
49-
String serverSection = System.getProperty(
50-
ZooKeeperSaslServer.LOGIN_CONTEXT_NAME_KEY,
51-
ZooKeeperSaslServer.DEFAULT_LOGIN_CONTEXT_NAME);
52-
53-
AppConfigurationEntry[] configurationEntries = configuration.getAppConfigurationEntry(serverSection);
54-
55-
if (configurationEntries == null) {
56-
String errorMessage = "Could not find a '" + serverSection + "' entry in this configuration: Server cannot start.";
57-
LOG.error(errorMessage);
58-
throw new IOException(errorMessage);
59-
}
60-
credentials.clear();
61-
for (AppConfigurationEntry entry : configurationEntries) {
62-
Map<String, ?> options = entry.getOptions();
63-
// Populate DIGEST-MD5 user -> password map with JAAS configuration entries from the "Server" section.
64-
// Usernames are distinguished from other options by prefixing the username with a "user_" prefix.
65-
for (Map.Entry<String, ?> pair : options.entrySet()) {
66-
String key = pair.getKey();
67-
if (key.startsWith(USER_PREFIX)) {
68-
String userName = key.substring(USER_PREFIX.length());
69-
credentials.put(userName, (String) pair.getValue());
70-
}
71-
}
72-
}
43+
public SaslServerCallbackHandler(Map<String, String> credentials) {
44+
this.credentials = credentials;
7345
}
7446

7547
public void handle(Callback[] callbacks) throws UnsupportedCallbackException {

zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/auth/SaslQuorumAuthLearner.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
import java.net.Socket;
2626
import java.security.PrivilegedActionException;
2727
import java.security.PrivilegedExceptionAction;
28+
import java.util.function.Supplier;
2829
import javax.security.auth.Subject;
30+
import javax.security.auth.callback.CallbackHandler;
2931
import javax.security.auth.login.AppConfigurationEntry;
3032
import javax.security.auth.login.Configuration;
3133
import javax.security.auth.login.LoginException;
@@ -62,9 +64,13 @@ public SaslQuorumAuthLearner(
6264
"SASL-authentication failed because the specified JAAS configuration section '%s' could not be found.",
6365
loginContext));
6466
}
67+
68+
Supplier<CallbackHandler> callbackSupplier = () -> {
69+
return new SaslClientCallbackHandler(null, "QuorumLearner");
70+
};
6571
this.learnerLogin = new Login(
6672
loginContext,
67-
new SaslClientCallbackHandler(null, "QuorumLearner"),
73+
callbackSupplier,
6874
new ZKConfig());
6975
this.learnerLogin.startThreadIfNeeded();
7076
} catch (LoginException e) {

zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/auth/SaslQuorumAuthServer.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import java.io.IOException;
2525
import java.net.Socket;
2626
import java.util.Set;
27+
import java.util.function.Supplier;
28+
import javax.security.auth.callback.CallbackHandler;
2729
import javax.security.auth.login.AppConfigurationEntry;
2830
import javax.security.auth.login.Configuration;
2931
import javax.security.auth.login.LoginException;
@@ -55,9 +57,10 @@ public SaslQuorumAuthServer(boolean quorumRequireSasl, String loginContext, Set<
5557
"SASL-authentication failed because the specified JAAS configuration section '%s' could not be found.",
5658
loginContext));
5759
}
58-
SaslQuorumServerCallbackHandler saslServerCallbackHandler = new SaslQuorumServerCallbackHandler(
59-
Configuration.getConfiguration(), loginContext, authzHosts);
60-
serverLogin = new Login(loginContext, saslServerCallbackHandler, new ZKConfig());
60+
Supplier<CallbackHandler> callbackSupplier = () -> {
61+
return new SaslQuorumServerCallbackHandler(entries, authzHosts);
62+
};
63+
serverLogin = new Login(loginContext, callbackSupplier, new ZKConfig());
6164
serverLogin.startThreadIfNeeded();
6265
} catch (Throwable e) {
6366
throw new SaslException("Failed to initialize authentication mechanism using SASL", e);
@@ -86,7 +89,7 @@ public void authenticate(Socket sock, DataInputStream din) throws SaslException
8689
serverLogin.getSubject(),
8790
QuorumAuth.QUORUM_SERVER_PROTOCOL_NAME,
8891
QuorumAuth.QUORUM_SERVER_SASL_DIGEST,
89-
serverLogin.callbackHandler,
92+
serverLogin.newCallbackHandler(),
9093
LOG);
9194
while (!ss.isComplete()) {
9295
challenge = ss.evaluateResponse(token);

zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/auth/SaslQuorumServerCallbackHandler.java

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
package org.apache.zookeeper.server.quorum.auth;
2020

21-
import java.io.IOException;
2221
import java.util.Collections;
2322
import java.util.HashMap;
2423
import java.util.Map;
@@ -29,7 +28,6 @@
2928
import javax.security.auth.callback.PasswordCallback;
3029
import javax.security.auth.callback.UnsupportedCallbackException;
3130
import javax.security.auth.login.AppConfigurationEntry;
32-
import javax.security.auth.login.Configuration;
3331
import javax.security.sasl.AuthorizeCallback;
3432
import javax.security.sasl.RealmCallback;
3533
import org.apache.zookeeper.server.auth.DigestLoginModule;
@@ -53,16 +51,8 @@ public class SaslQuorumServerCallbackHandler implements CallbackHandler {
5351
private final Set<String> authzHosts;
5452

5553
public SaslQuorumServerCallbackHandler(
56-
Configuration configuration,
57-
String serverSection,
58-
Set<String> authzHosts) throws IOException {
59-
AppConfigurationEntry[] configurationEntries = configuration.getAppConfigurationEntry(serverSection);
60-
61-
if (configurationEntries == null) {
62-
String errorMessage = "Could not find a '" + serverSection + "' entry in this configuration: Server cannot start.";
63-
LOG.error(errorMessage);
64-
throw new IOException(errorMessage);
65-
}
54+
AppConfigurationEntry[] configurationEntries,
55+
Set<String> authzHosts) {
6656

6757
Map<String, String> credentials = new HashMap<>();
6858
boolean isDigestAuthn = true;

zookeeper-server/src/test/java/org/apache/zookeeper/KerberosTicketRenewalTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,9 @@ private static class TestableKerberosLogin extends Login {
127127
private CountDownLatch continueRefreshThread = new CountDownLatch(1);
128128

129129
public TestableKerberosLogin() throws LoginException {
130-
super(JAAS_CONFIG_SECTION, (callbacks) -> {}, new ZKConfig());
130+
super(JAAS_CONFIG_SECTION, () -> {
131+
return (callbacks) -> {};
132+
}, new ZKConfig());
131133
}
132134

133135
@Override

0 commit comments

Comments
 (0)