This is an automated email from the ASF dual-hosted git repository.
stoty pushed a commit to branch branch-2
in repository https://gitbox.apache.org/repos/asf/hbase.git
The following commit(s) were added to refs/heads/branch-2 by this push:
new 03effb9a26a HBASE-29080 Validate Negotiated SASL QoP Against Requested
(#6601) (#6782)
03effb9a26a is described below
commit 03effb9a26acd74c58c97053a1247df0de279157
Author: Istvan Toth <[email protected]>
AuthorDate: Thu Mar 13 18:06:06 2025 +0100
HBASE-29080 Validate Negotiated SASL QoP Against Requested (#6601) (#6782)
Signed-off-by: Andrew Purtell <[email protected]>
Signed-off-by: Duo Zhang <[email protected]>
Signed-off-by: Andor Molnár <[email protected]>
---
.../hbase/security/AbstractHBaseSaslRpcClient.java | 10 ++++
.../hadoop/hbase/security/HBaseSaslRpcClient.java | 3 ++
.../hbase/security/NettyHBaseSaslRpcClient.java | 3 +-
.../security/NettyHBaseSaslRpcClientHandler.java | 2 +-
.../org/apache/hadoop/hbase/security/SaslUtil.java | 22 ++++++++
.../apache/hadoop/hbase/security/TestSaslUtil.java | 58 ++++++++++++++++++++++
.../hadoop/hbase/ipc/ServerRpcConnection.java | 6 ++-
.../hbase/ipc/SimpleServerRpcConnection.java | 8 +--
.../hadoop/hbase/security/HBaseSaslRpcServer.java | 6 +++
9 files changed, 107 insertions(+), 11 deletions(-)
diff --git
a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/AbstractHBaseSaslRpcClient.java
b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/AbstractHBaseSaslRpcClient.java
index 4e6f2eab478..7ac86189c27 100644
---
a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/AbstractHBaseSaslRpcClient.java
+++
b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/AbstractHBaseSaslRpcClient.java
@@ -20,6 +20,7 @@ package org.apache.hadoop.hbase.security;
import java.io.IOException;
import java.net.InetAddress;
import java.util.Map;
+import javax.security.sasl.Sasl;
import javax.security.sasl.SaslClient;
import javax.security.sasl.SaslException;
import org.apache.hadoop.conf.Configuration;
@@ -105,6 +106,15 @@ public abstract class AbstractHBaseSaslRpcClient {
return saslClient.evaluateChallenge(challenge);
}
+ /**
+ * Check that SASL has successfully negotiated a QOP according to the
requested rpcProtection
+ * @throws IOException if the negotiated QOP is insufficient
+ */
+ protected void verifyNegotiatedQop() throws IOException {
+ SaslUtil.verifyNegotiatedQop(saslProps.get(Sasl.QOP),
+ (String) saslClient.getNegotiatedProperty(Sasl.QOP));
+ }
+
/** Release resources used by wrapped saslClient */
public void dispose() {
SaslUtil.safeDispose(saslClient);
diff --git
a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcClient.java
b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcClient.java
index ebf0a7f875f..fa1a196017d 100644
---
a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcClient.java
+++
b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcClient.java
@@ -147,6 +147,9 @@ public class HBaseSaslRpcClient extends
AbstractHBaseSaslRpcClient {
LOG.debug("SASL client context established. Negotiated QoP: "
+ saslClient.getNegotiatedProperty(Sasl.QOP));
}
+
+ verifyNegotiatedQop();
+
// initial the inputStream, outputStream for both Sasl encryption
// and Crypto AES encryption if necessary
// if Crypto AES encryption enabled, the
saslInputStream/saslOutputStream is
diff --git
a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClient.java
b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClient.java
index 47d380d7104..6624dad8e9c 100644
---
a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClient.java
+++
b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClient.java
@@ -45,9 +45,10 @@ public class NettyHBaseSaslRpcClient extends
AbstractHBaseSaslRpcClient {
super(conf, provider, token, serverAddr, serverPrincipal, fallbackAllowed,
rpcProtection);
}
- public void setupSaslHandler(ChannelPipeline p, String addAfter) {
+ public void setupSaslHandler(ChannelPipeline p, String addAfter) throws
IOException {
String qop = (String) saslClient.getNegotiatedProperty(Sasl.QOP);
LOG.trace("SASL client context established. Negotiated QoP {}", qop);
+ verifyNegotiatedQop();
if (qop == null || "auth".equalsIgnoreCase(qop)) {
return;
}
diff --git
a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClientHandler.java
b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClientHandler.java
index 567b5675b71..1f9c0ccdb9a 100644
---
a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClientHandler.java
+++
b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClientHandler.java
@@ -85,7 +85,7 @@ public class NettyHBaseSaslRpcClientHandler extends
SimpleChannelInboundHandler<
ctx.alloc().buffer(4 +
response.length).writeInt(response.length).writeBytes(response));
}
- private void tryComplete(ChannelHandlerContext ctx) {
+ private void tryComplete(ChannelHandlerContext ctx) throws IOException {
if (!saslRpcClient.isComplete()) {
return;
}
diff --git
a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/SaslUtil.java
b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/SaslUtil.java
index dfb0a49db4a..520de9cc98b 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/SaslUtil.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/SaslUtil.java
@@ -17,7 +17,10 @@
*/
package org.apache.hadoop.hbase.security;
+import java.io.IOException;
+import java.util.Arrays;
import java.util.Base64;
+import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import javax.security.sasl.Sasl;
@@ -132,4 +135,23 @@ public class SaslUtil {
LOG.error("Error disposing of SASL server", e);
}
}
+
+ public static void verifyNegotiatedQop(String requestedQopString, String
negotiatedQop)
+ throws IOException {
+ // We use the SASL QOP names here, not the HBase names
+ if (requestedQopString == null || requestedQopString.isEmpty()) {
+ // None requested, nothing to check
+ return;
+ }
+ List<String> requestedQops =
Arrays.asList(requestedQopString.toLowerCase().split(","));
+ if (negotiatedQop == null) {
+ // Null negotiated QOP is equivalent to "auth" (for mechanisms without
QOP support)
+ negotiatedQop = QualityOfProtection.AUTHENTICATION.getSaslQop();
+ }
+ if (requestedQops.contains(negotiatedQop.toLowerCase())) {
+ return;
+ }
+ throw new IOException("Could not negotiate requested SASL QOP. Requested:"
+ requestedQopString
+ + " , negotiated:" + negotiatedQop);
+ }
}
diff --git
a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestSaslUtil.java
b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestSaslUtil.java
index ccb23a99e37..9e0b28b131b 100644
---
a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestSaslUtil.java
+++
b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestSaslUtil.java
@@ -18,7 +18,9 @@
package org.apache.hadoop.hbase.security;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import java.io.IOException;
import java.util.Map;
import javax.security.sasl.Sasl;
import org.apache.hadoop.hbase.HBaseClassTestRule;
@@ -61,4 +63,60 @@ public class TestSaslUtil {
props = SaslUtil.initSaslProperties("");
assertEquals("auth", props.get(Sasl.QOP));
}
+
+ @Test
+ public void testVerifyQop() throws IOException {
+ String nullQop = null;
+ String authentication = "auth";
+ String integrity = "auth-int";
+ String confidentality = "auth-conf";
+ String anyQop = "auth-conf,auth-int,auth";
+
+ // Empty requested, got empty
+ SaslUtil.verifyNegotiatedQop(nullQop, nullQop);
+
+ // Auth requested, got null
+ SaslUtil.verifyNegotiatedQop(authentication, nullQop);
+
+ // Auth requested, got auth
+ SaslUtil.verifyNegotiatedQop(authentication, authentication);
+
+ // Auth requested, got confidentiality.
+ assertThrows(IOException.class,
+ () -> SaslUtil.verifyNegotiatedQop(authentication, confidentality));
+
+ // Integrity requested requested, got null
+ assertThrows(IOException.class, () ->
SaslUtil.verifyNegotiatedQop(integrity, nullQop));
+
+ // Integrity requested requested, got auth
+ assertThrows(IOException.class, () ->
SaslUtil.verifyNegotiatedQop(integrity, authentication));
+
+ // Integrity requested requested, got conf
+ assertThrows(IOException.class, () ->
SaslUtil.verifyNegotiatedQop(integrity, authentication));
+
+ // Confidentiality requested requested, got null
+ assertThrows(IOException.class, () ->
SaslUtil.verifyNegotiatedQop(confidentality, nullQop));
+
+ // Confidentiality requested requested, got auth
+ assertThrows(IOException.class,
+ () -> SaslUtil.verifyNegotiatedQop(confidentality, authentication));
+
+ // Confidentiality requested requested, got integrity
+ assertThrows(IOException.class, () ->
SaslUtil.verifyNegotiatedQop(confidentality, integrity));
+
+ // Confidentiality requested requested, got confidentiality
+ assertThrows(IOException.class, () ->
SaslUtil.verifyNegotiatedQop(confidentality, integrity));
+
+ // Any requested, got null
+ SaslUtil.verifyNegotiatedQop(anyQop, null);
+
+ // Any requested, got auth
+ SaslUtil.verifyNegotiatedQop(anyQop, authentication);
+
+ // Any requested, got integrity
+ SaslUtil.verifyNegotiatedQop(anyQop, integrity);
+
+ // Any requested, got confidentiality
+ SaslUtil.verifyNegotiatedQop(anyQop, confidentality);
+ }
}
diff --git
a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java
b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java
index 7335e9765c7..bca27f61645 100644
---
a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java
+++
b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java
@@ -351,10 +351,12 @@ abstract class ServerRpcConnection implements Closeable {
}
void finishSaslNegotiation() throws IOException {
- String qop = saslServer.getNegotiatedQop();
+ String negotiatedQop = saslServer.getNegotiatedQop();
+ SaslUtil.verifyNegotiatedQop(saslServer.getRequestedQop(), negotiatedQop);
ugi = provider.getAuthorizedUgi(saslServer.getAuthorizationID(),
this.rpcServer.secretManager);
RpcServer.LOG.debug(
- "SASL server context established. Authenticated client: {}. Negotiated
QoP is {}", ugi, qop);
+ "SASL server context established. Authenticated client: {}. Negotiated
QoP is {}", ugi,
+ negotiatedQop);
rpcServer.metrics.authenticationSuccess();
RpcServer.AUDITLOG.info(RpcServer.AUTH_SUCCESSFUL_FOR + ugi);
}
diff --git
a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/SimpleServerRpcConnection.java
b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/SimpleServerRpcConnection.java
index 4fe71de73e4..4b5206dcbc7 100644
---
a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/SimpleServerRpcConnection.java
+++
b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/SimpleServerRpcConnection.java
@@ -252,15 +252,9 @@ class SimpleServerRpcConnection extends
ServerRpcConnection {
doRawSaslReply(SaslStatus.SUCCESS, new BytesWritable(replyToken),
null, null);
}
if (saslServer.isComplete()) {
+ finishSaslNegotiation();
String qop = saslServer.getNegotiatedQop();
useWrap = qop != null && !"auth".equalsIgnoreCase(qop);
- ugi =
- provider.getAuthorizedUgi(saslServer.getAuthorizationID(),
this.rpcServer.secretManager);
- RpcServer.LOG.debug(
- "SASL server context established. Authenticated client: {}.
Negotiated QoP is {}", ugi,
- qop);
- this.rpcServer.metrics.authenticationSuccess();
- RpcServer.AUDITLOG.info(RpcServer.AUTH_SUCCESSFUL_FOR + ugi);
saslContextEstablished = true;
}
}
diff --git
a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcServer.java
b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcServer.java
index 6d375e0014a..50e0e57e201 100644
---
a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcServer.java
+++
b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcServer.java
@@ -42,12 +42,14 @@ public class HBaseSaslRpcServer {
private final AttemptingUserProvidingSaslServer serverWithProvider;
private final SaslServer saslServer;
private CryptoAES cryptoAES;
+ private final Map<String, String> saslProps;
public HBaseSaslRpcServer(SaslServerAuthenticationProvider provider,
Map<String, String> saslProps, SecretManager<TokenIdentifier>
secretManager)
throws IOException {
serverWithProvider = provider.createServer(secretManager, saslProps);
saslServer = serverWithProvider.getServer();
+ this.saslProps = saslProps;
}
public boolean isComplete() {
@@ -91,6 +93,10 @@ public class HBaseSaslRpcServer {
return (String) saslServer.getNegotiatedProperty(Sasl.QOP);
}
+ public String getRequestedQop() {
+ return (String) saslProps.get(Sasl.QOP);
+ }
+
public String getAuthorizationID() {
return saslServer.getAuthorizationID();
}