Skip to content

Commit 317db31

Browse files
authored
HADOOP-19306. Support user defined auth Callback in SaslRpcServer. (#7140)
1 parent 7999db5 commit 317db31

File tree

15 files changed

+291
-112
lines changed

15 files changed

+291
-112
lines changed

hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/CommonConfigurationKeysPublic.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,12 @@ public class CommonConfigurationKeysPublic {
736736
*/
737737
public static final String HADOOP_RPC_PROTECTION =
738738
"hadoop.rpc.protection";
739+
public static final String HADOOP_SECURITY_SASL_MECHANISM_KEY
740+
= "hadoop.security.sasl.mechanism";
741+
public static final String HADOOP_SECURITY_SASL_MECHANISM_DEFAULT
742+
= "DIGEST-MD5";
743+
public static final String HADOOP_SECURITY_SASL_CUSTOMIZEDCALLBACKHANDLER_CLASS_KEY
744+
= "hadoop.security.sasl.CustomizedCallbackHandler.class";
739745
/** Class to override Sasl Properties for a connection */
740746
public static final String HADOOP_SECURITY_SASL_PROPS_RESOLVER_CLASS =
741747
"hadoop.security.saslproperties.resolver.class";

hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/Server.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@
106106
import org.apache.hadoop.ipc.protobuf.RpcHeaderProtos.RPCTraceInfoProto;
107107
import org.apache.hadoop.net.NetUtils;
108108
import org.apache.hadoop.security.AccessControlException;
109-
import org.apache.hadoop.security.SaslConstants;
109+
import org.apache.hadoop.security.SaslMechanismFactory;
110110
import org.apache.hadoop.security.SaslPropertiesResolver;
111111
import org.apache.hadoop.security.SaslRpcServer;
112112
import org.apache.hadoop.security.SaslRpcServer.AuthMethod;
@@ -2143,6 +2143,10 @@ public Server getServer() {
21432143
return Server.this;
21442144
}
21452145

2146+
public Configuration getConf() {
2147+
return Server.this.getConf();
2148+
}
2149+
21462150
/* Return true if the connection has no outstanding rpc */
21472151
private boolean isIdle() {
21482152
return rpcCount.get() == 0;
@@ -2606,7 +2610,7 @@ private RpcSaslProto buildSaslNegotiateResponse()
26062610
// accelerate token negotiation by sending initial challenge
26072611
// in the negotiation response
26082612
if (enabledAuthMethods.contains(AuthMethod.TOKEN)
2609-
&& SaslConstants.SASL_MECHANISM_DEFAULT.equals(AuthMethod.TOKEN.getMechanismName())) {
2613+
&& SaslMechanismFactory.isDefaultMechanism(AuthMethod.TOKEN.getMechanismName())) {
26102614
saslServer = createSaslServer(AuthMethod.TOKEN);
26112615
byte[] challenge = saslServer.evaluateResponse(new byte[0]);
26122616
RpcSaslProto.Builder negotiateBuilder =
Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,80 @@
1515
* See the License for the specific language governing permissions and
1616
* limitations under the License.
1717
*/
18-
package org.apache.hadoop.hdfs.protocol.datatransfer.sasl;
18+
package org.apache.hadoop.security;
19+
20+
import org.apache.hadoop.conf.Configuration;
21+
import org.slf4j.Logger;
22+
import org.slf4j.LoggerFactory;
1923

2024
import javax.security.auth.callback.Callback;
2125
import javax.security.auth.callback.UnsupportedCallbackException;
2226
import java.io.IOException;
2327
import java.lang.reflect.InvocationTargetException;
2428
import java.lang.reflect.Method;
29+
import java.util.HashMap;
2530
import java.util.List;
31+
import java.util.Map;
2632

2733
/** For handling customized {@link Callback}. */
2834
public interface CustomizedCallbackHandler {
29-
class DefaultHandler implements CustomizedCallbackHandler{
35+
Logger LOG = LoggerFactory.getLogger(CustomizedCallbackHandler.class);
36+
37+
class Cache {
38+
private static final Map<String, CustomizedCallbackHandler> MAP = new HashMap<>();
39+
40+
private static synchronized CustomizedCallbackHandler getSynchronously(
41+
String key, Configuration conf) {
42+
//check again synchronously
43+
final CustomizedCallbackHandler cached = MAP.get(key);
44+
if (cached != null) {
45+
return cached; //cache hit
46+
}
47+
48+
//cache miss
49+
final Class<?> clazz = conf.getClass(key, DefaultHandler.class);
50+
LOG.info("{} = {}", key, clazz);
51+
if (clazz == DefaultHandler.class) {
52+
return DefaultHandler.INSTANCE;
53+
}
54+
55+
final Object created;
56+
try {
57+
created = clazz.newInstance();
58+
} catch (Exception e) {
59+
LOG.warn("Failed to create a new instance of {}, fallback to {}",
60+
clazz, DefaultHandler.class, e);
61+
return DefaultHandler.INSTANCE;
62+
}
63+
64+
final CustomizedCallbackHandler handler = created instanceof CustomizedCallbackHandler ?
65+
(CustomizedCallbackHandler) created : CustomizedCallbackHandler.delegate(created);
66+
MAP.put(key, handler);
67+
return handler;
68+
}
69+
70+
private static CustomizedCallbackHandler get(String key, Configuration conf) {
71+
final CustomizedCallbackHandler cached = MAP.get(key);
72+
return cached != null ? cached : getSynchronously(key, conf);
73+
}
74+
75+
public static synchronized void clear() {
76+
MAP.clear();
77+
}
78+
79+
private Cache() { }
80+
}
81+
82+
class DefaultHandler implements CustomizedCallbackHandler {
83+
private static final DefaultHandler INSTANCE = new DefaultHandler();
84+
3085
@Override
3186
public void handleCallbacks(List<Callback> callbacks, String username, char[] password)
3287
throws UnsupportedCallbackException {
3388
if (!callbacks.isEmpty()) {
34-
throw new UnsupportedCallbackException(callbacks.get(0));
89+
final Callback cb = callbacks.get(0);
90+
throw new UnsupportedCallbackException(callbacks.get(0),
91+
"Unsupported callback: " + (cb == null ? null : cb.getClass()));
3592
}
3693
}
3794
}
@@ -55,6 +112,10 @@ static CustomizedCallbackHandler delegate(Object delegated) {
55112
};
56113
}
57114

115+
static CustomizedCallbackHandler get(String key, Configuration conf) {
116+
return Cache.get(key, conf);
117+
}
118+
58119
void handleCallbacks(List<Callback> callbacks, String name, char[] password)
59120
throws UnsupportedCallbackException, IOException;
60121
}

hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/SaslConstants.java

Lines changed: 0 additions & 45 deletions
This file was deleted.
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.security;
19+
20+
import org.apache.hadoop.HadoopIllegalArgumentException;
21+
import org.apache.hadoop.classification.InterfaceAudience;
22+
import org.apache.hadoop.classification.InterfaceStability;
23+
import org.apache.hadoop.conf.Configuration;
24+
import org.slf4j.Logger;
25+
import org.slf4j.LoggerFactory;
26+
27+
import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_SECURITY_SASL_MECHANISM_DEFAULT;
28+
import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_SECURITY_SASL_MECHANISM_KEY;
29+
30+
/**
31+
* SASL related constants.
32+
*/
33+
@InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce", "YARN", "HBase"})
34+
@InterfaceStability.Evolving
35+
public final class SaslMechanismFactory {
36+
static final Logger LOG = LoggerFactory.getLogger(SaslMechanismFactory.class);
37+
38+
private static final String SASL_MECHANISM_ENV = "HADOOP_SASL_MECHANISM";
39+
private static final String SASL_MECHANISM;
40+
41+
static {
42+
// env
43+
final String envValue = System.getenv(SASL_MECHANISM_ENV);
44+
LOG.debug("{} = {} (env)", SASL_MECHANISM_ENV, envValue);
45+
46+
// conf
47+
final Configuration conf = new Configuration(false);
48+
final String confValue = conf.get(HADOOP_SECURITY_SASL_MECHANISM_KEY,
49+
HADOOP_SECURITY_SASL_MECHANISM_DEFAULT);
50+
LOG.debug("{} = {} (conf)", HADOOP_SECURITY_SASL_MECHANISM_KEY, confValue);
51+
52+
if (envValue != null && confValue != null) {
53+
if (!envValue.equals(confValue)) {
54+
throw new HadoopIllegalArgumentException("SASL Mechanism mismatched: env "
55+
+ SASL_MECHANISM_ENV + " is " + envValue + " but conf "
56+
+ HADOOP_SECURITY_SASL_MECHANISM_KEY + " is " + confValue);
57+
}
58+
}
59+
60+
SASL_MECHANISM = envValue != null ? envValue
61+
: confValue != null ? confValue
62+
: HADOOP_SECURITY_SASL_MECHANISM_DEFAULT;
63+
LOG.debug("SASL_MECHANISM = {} (effective)", SASL_MECHANISM);
64+
}
65+
66+
public static String getMechanism() {
67+
return SASL_MECHANISM;
68+
}
69+
70+
public static boolean isDefaultMechanism(String mechanism) {
71+
return HADOOP_SECURITY_SASL_MECHANISM_DEFAULT.equals(mechanism);
72+
}
73+
74+
private SaslMechanismFactory() {}
75+
}

hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/SaslRpcServer.java

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import java.nio.charset.StandardCharsets;
2727
import java.security.PrivilegedExceptionAction;
2828
import java.security.Security;
29+
import java.util.ArrayList;
30+
import java.util.List;
2931
import java.util.Map;
3032

3133
import javax.security.auth.callback.Callback;
@@ -43,16 +45,16 @@
4345
import org.apache.hadoop.classification.InterfaceAudience;
4446
import org.apache.hadoop.classification.InterfaceStability;
4547
import org.apache.hadoop.conf.Configuration;
46-
import org.apache.hadoop.ipc.RetriableException;
4748
import org.apache.hadoop.ipc.Server;
4849
import org.apache.hadoop.ipc.Server.Connection;
49-
import org.apache.hadoop.ipc.StandbyException;
5050
import org.apache.hadoop.security.token.SecretManager;
5151
import org.apache.hadoop.security.token.SecretManager.InvalidToken;
5252
import org.apache.hadoop.security.token.TokenIdentifier;
5353
import org.slf4j.Logger;
5454
import org.slf4j.LoggerFactory;
5555

56+
import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_SECURITY_SASL_CUSTOMIZEDCALLBACKHANDLER_CLASS_KEY;
57+
5658
/**
5759
* A utility class for dealing with SASL on RPC server
5860
*/
@@ -223,8 +225,8 @@ public enum AuthMethod {
223225
SIMPLE((byte) 80, ""),
224226
KERBEROS((byte) 81, "GSSAPI"),
225227
@Deprecated
226-
DIGEST((byte) 82, SaslConstants.SASL_MECHANISM),
227-
TOKEN((byte) 82, SaslConstants.SASL_MECHANISM),
228+
DIGEST((byte) 82, SaslMechanismFactory.getMechanism()),
229+
TOKEN((byte) 82, SaslMechanismFactory.getMechanism()),
228230
PLAIN((byte) 83, "PLAIN");
229231

230232
/** The code for this method. */
@@ -234,6 +236,8 @@ public enum AuthMethod {
234236
private AuthMethod(byte code, String mechanismName) {
235237
this.code = code;
236238
this.mechanismName = mechanismName;
239+
LOG.info("{} {}: code={}, mechanism=\"{}\"",
240+
getClass().getSimpleName(), name(), code, mechanismName);
237241
}
238242

239243
private static final int FIRST_CODE = values()[0].code;
@@ -276,28 +280,44 @@ public void write(DataOutput out) throws IOException {
276280
/** CallbackHandler for SASL mechanism. */
277281
@InterfaceStability.Evolving
278282
public static class SaslDigestCallbackHandler implements CallbackHandler {
283+
private final CustomizedCallbackHandler customizedCallbackHandler;
279284
private SecretManager<TokenIdentifier> secretManager;
280285
private Server.Connection connection;
281286

282287
public SaslDigestCallbackHandler(
283288
SecretManager<TokenIdentifier> secretManager,
284289
Server.Connection connection) {
290+
this(secretManager, connection, connection.getConf());
291+
}
292+
293+
public SaslDigestCallbackHandler(
294+
SecretManager<TokenIdentifier> secretManager,
295+
Server.Connection connection,
296+
Configuration conf) {
285297
this.secretManager = secretManager;
286298
this.connection = connection;
299+
this.customizedCallbackHandler = CustomizedCallbackHandler.get(
300+
HADOOP_SECURITY_SASL_CUSTOMIZEDCALLBACKHANDLER_CLASS_KEY, conf);
287301
}
288302

289-
private char[] getPassword(TokenIdentifier tokenid) throws InvalidToken,
290-
StandbyException, RetriableException, IOException {
303+
private char[] getPassword(TokenIdentifier tokenid) throws IOException {
291304
return encodePassword(secretManager.retriableRetrievePassword(tokenid));
292305
}
293306

307+
private char[] getPassword(String name) throws IOException {
308+
final TokenIdentifier tokenIdentifier = getIdentifier(name, secretManager);
309+
final UserGroupInformation user = tokenIdentifier.getUser();
310+
connection.attemptingUser = user;
311+
LOG.debug("SASL server callback: setting password for client: {}", user);
312+
return getPassword(tokenIdentifier);
313+
}
314+
294315
@Override
295-
public void handle(Callback[] callbacks) throws InvalidToken,
296-
UnsupportedCallbackException, StandbyException, RetriableException,
297-
IOException {
316+
public void handle(Callback[] callbacks) throws UnsupportedCallbackException, IOException {
298317
NameCallback nc = null;
299318
PasswordCallback pc = null;
300319
AuthorizeCallback ac = null;
320+
List<Callback> unknownCallbacks = null;
301321
for (Callback callback : callbacks) {
302322
if (callback instanceof AuthorizeCallback) {
303323
ac = (AuthorizeCallback) callback;
@@ -308,20 +328,14 @@ public void handle(Callback[] callbacks) throws InvalidToken,
308328
} else if (callback instanceof RealmCallback) {
309329
continue; // realm is ignored
310330
} else {
311-
throw new UnsupportedCallbackException(callback,
312-
"Unrecognized SASL Callback");
331+
if (unknownCallbacks == null) {
332+
unknownCallbacks = new ArrayList<>();
333+
}
334+
unknownCallbacks.add(callback);
313335
}
314336
}
315337
if (pc != null) {
316-
TokenIdentifier tokenIdentifier = getIdentifier(nc.getDefaultName(),
317-
secretManager);
318-
char[] password = getPassword(tokenIdentifier);
319-
UserGroupInformation user = null;
320-
user = tokenIdentifier.getUser(); // may throw exception
321-
connection.attemptingUser = user;
322-
323-
LOG.debug("SASL server callback: setting password for client: {}", user);
324-
pc.setPassword(password);
338+
pc.setPassword(getPassword(nc.getDefaultName()));
325339
}
326340
if (ac != null) {
327341
String authid = ac.getAuthenticationID();
@@ -341,6 +355,11 @@ public void handle(Callback[] callbacks) throws InvalidToken,
341355
ac.setAuthorizedID(authzid);
342356
}
343357
}
358+
if (unknownCallbacks != null) {
359+
final String name = nc != null ? nc.getDefaultName() : null;
360+
final char[] password = name != null ? getPassword(name) : null;
361+
customizedCallbackHandler.handleCallbacks(unknownCallbacks, name, password);
362+
}
344363
}
345364
}
346365

0 commit comments

Comments
 (0)