This is an automated email from the ASF dual-hosted git repository.

stoty pushed a commit to branch branch-2.6
in repository https://gitbox.apache.org/repos/asf/hbase.git


The following commit(s) were added to refs/heads/branch-2.6 by this push:
     new 8ae3e0a6548 HBASE-29080 Validate Negotiated SASL QoP Against Requested 
(#6601) (#6782)
8ae3e0a6548 is described below

commit 8ae3e0a65486148257eec9944e178353a94bbea1
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]>
    (cherry picked from commit 03effb9a26acd74c58c97053a1247df0de279157)
---
 .../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();
   }

Reply via email to