You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kudu.apache.org by ab...@apache.org on 2021/04/09 13:53:11 UTC

[kudu] 02/03: [java] KUDU-1884 Make SASL proto name configurable

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

abukor pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/kudu.git

commit e69b3c652d0c21393015aad8e5acd5ece2098573
Author: Attila Bukor <ab...@apache.org>
AuthorDate: Wed Apr 7 14:45:22 2021 +0200

    [java] KUDU-1884 Make SASL proto name configurable
    
    The server-side Kerberos service principal name must match the
    client-side SASL protocol name. This patch makes this configurable
    through the client builder API.
    
    Change-Id: I4a881f2cf125651277927b43a5b31afc173a9bab
    Reviewed-on: http://gerrit.cloudera.org:8080/17280
    Reviewed-by: Alexey Serbin <as...@cloudera.com>
    Tested-by: Attila Bukor <ab...@apache.org>
    Reviewed-by: Grant Henke <gr...@apache.org>
---
 .../org/apache/kudu/client/AsyncKuduClient.java    | 17 +++++++++++++-
 .../java/org/apache/kudu/client/Connection.java    | 11 ++++++---
 .../org/apache/kudu/client/ConnectionCache.java    |  9 ++++++--
 .../java/org/apache/kudu/client/KuduClient.java    | 14 +++++++++++
 .../java/org/apache/kudu/client/Negotiator.java    |  8 +++++--
 .../org/apache/kudu/client/TestNegotiator.java     |  2 +-
 .../java/org/apache/kudu/client/TestSecurity.java  | 27 ++++++++++++++++++++++
 .../apache/kudu/test/cluster/MiniKuduCluster.java  | 16 ++++++++++---
 8 files changed, 92 insertions(+), 12 deletions(-)

diff --git a/java/kudu-client/src/main/java/org/apache/kudu/client/AsyncKuduClient.java b/java/kudu-client/src/main/java/org/apache/kudu/client/AsyncKuduClient.java
index d8ba883..0710db8 100644
--- a/java/kudu-client/src/main/java/org/apache/kudu/client/AsyncKuduClient.java
+++ b/java/kudu-client/src/main/java/org/apache/kudu/client/AsyncKuduClient.java
@@ -425,7 +425,7 @@ public class AsyncKuduClient implements AutoCloseable {
     this.requestTracker = new RequestTracker(clientId);
 
     this.securityContext = new SecurityContext();
-    this.connectionCache = new ConnectionCache(securityContext, bootstrap);
+    this.connectionCache = new ConnectionCache(securityContext, bootstrap, b.saslProtocolName);
     this.tokenReacquirer = new AuthnTokenReacquirer(this);
     this.authzTokenCache = new AuthzTokenCache(this);
   }
@@ -2731,6 +2731,7 @@ public class AsyncKuduClient implements AutoCloseable {
     private Executor workerExecutor;
     private int workerCount = DEFAULT_WORKER_COUNT;
     private boolean statisticsDisabled = false;
+    private String saslProtocolName = "kudu";
 
     /**
      * Creates a new builder for a client that will connect to the specified masters.
@@ -2892,6 +2893,20 @@ public class AsyncKuduClient implements AutoCloseable {
     }
 
     /**
+     * Set the SASL protocol name.
+     * SASL protocol name is used when connecting to a secure (Kerberos-enabled)
+     * cluster. It must match the servers' service principal name (SPN).
+     *
+     * Optional.
+     * If not provided, it will use the default SASL protocol name ("kudu").
+     * @return this builder
+     */
+    public AsyncKuduClientBuilder saslProtocolName(String saslProtocolName) {
+      this.saslProtocolName = saslProtocolName;
+      return this;
+    }
+
+    /**
      * Creates a new client that connects to the masters.
      * Doesn't block and won't throw an exception if the masters don't exist.
      * @return a new asynchronous Kudu client
diff --git a/java/kudu-client/src/main/java/org/apache/kudu/client/Connection.java b/java/kudu-client/src/main/java/org/apache/kudu/client/Connection.java
index e712475..74445ef 100644
--- a/java/kudu-client/src/main/java/org/apache/kudu/client/Connection.java
+++ b/java/kudu-client/src/main/java/org/apache/kudu/client/Connection.java
@@ -108,6 +108,8 @@ class Connection extends SimpleChannelInboundHandler<Object> {
   /** The Netty client bootstrap used to configure and initialize a connected channel. */
   private final Bootstrap bootstrap;
 
+  private final String saslProtocolName;
+
   /** The underlying Netty's socket channel. */
   private SocketChannel channel;
 
@@ -178,14 +180,17 @@ class Connection extends SimpleChannelInboundHandler<Object> {
    * @param credentialsPolicy policy controlling which credentials to use while negotiating on the
    *                          connection to the target server:
    *                          if {@link CredentialsPolicy#PRIMARY_CREDENTIALS}, the authentication
-   *                          token from the security context is ignored
+   * @param saslProtocolName SASL protocol name used when connecting to secure
+   *                         clusters. Must match the servers' service principal name.
    */
   Connection(ServerInfo serverInfo,
              SecurityContext securityContext,
              Bootstrap bootstrap,
-             CredentialsPolicy credentialsPolicy) {
+             CredentialsPolicy credentialsPolicy,
+             String saslProtocolName) {
     this.serverInfo = serverInfo;
     this.securityContext = securityContext;
+    this.saslProtocolName = saslProtocolName;
     this.state = State.NEW;
     this.credentialsPolicy = credentialsPolicy;
     this.bootstrap = bootstrap.clone();
@@ -208,7 +213,7 @@ class Connection extends SimpleChannelInboundHandler<Object> {
     }
     ctx.writeAndFlush(Unpooled.wrappedBuffer(CONNECTION_HEADER), ctx.voidPromise());
     Negotiator negotiator = new Negotiator(serverInfo.getAndCanonicalizeHostname(), securityContext,
-        (credentialsPolicy == CredentialsPolicy.PRIMARY_CREDENTIALS));
+        (credentialsPolicy == CredentialsPolicy.PRIMARY_CREDENTIALS), saslProtocolName);
     ctx.pipeline().addBefore(ctx.name(), "negotiation", negotiator);
     negotiator.sendHello(ctx);
   }
diff --git a/java/kudu-client/src/main/java/org/apache/kudu/client/ConnectionCache.java b/java/kudu-client/src/main/java/org/apache/kudu/client/ConnectionCache.java
index 83bff27..705f962 100644
--- a/java/kudu-client/src/main/java/org/apache/kudu/client/ConnectionCache.java
+++ b/java/kudu-client/src/main/java/org/apache/kudu/client/ConnectionCache.java
@@ -56,6 +56,8 @@ class ConnectionCache {
   /** Netty's bootstrap to use by connections. */
   private final Bootstrap bootstrap;
 
+  private final String saslProtocolName;
+
   /**
    * Container mapping server IP/port into the established connection from the client to the
    * server. It may be up to two connections per server: one established with secondary
@@ -67,9 +69,11 @@ class ConnectionCache {
 
   /** Create a new empty ConnectionCache given the specified parameters. */
   ConnectionCache(SecurityContext securityContext,
-                  Bootstrap bootstrap) {
+                  Bootstrap bootstrap,
+                  String saslProtocolName) {
     this.securityContext = securityContext;
     this.bootstrap = bootstrap;
+    this.saslProtocolName = saslProtocolName;
   }
 
   /**
@@ -122,7 +126,8 @@ class ConnectionCache {
         result = new Connection(serverInfo,
                                 securityContext,
                                 bootstrap,
-                                credentialsPolicy);
+                                credentialsPolicy,
+                                saslProtocolName);
         connections.add(result);
         // There can be at most 2 connections to the same destination: one with primary and another
         // with secondary credentials.
diff --git a/java/kudu-client/src/main/java/org/apache/kudu/client/KuduClient.java b/java/kudu-client/src/main/java/org/apache/kudu/client/KuduClient.java
index a77689b..ad518a6 100644
--- a/java/kudu-client/src/main/java/org/apache/kudu/client/KuduClient.java
+++ b/java/kudu-client/src/main/java/org/apache/kudu/client/KuduClient.java
@@ -609,6 +609,20 @@ public class KuduClient implements AutoCloseable {
     }
 
     /**
+     * Set the SASL protocol name.
+     * SASL protocol name is used when connecting to a secure (Kerberos-enabled)
+     * cluster. It must match the servers' service principal name (SPN).
+     *
+     * Optional.
+     * If not provided, it will use the default SASL protocol name ("kudu").
+     * @return this builder
+     */
+    public KuduClientBuilder saslProtocolName(String saslProtocolName) {
+      clientBuilder.saslProtocolName(saslProtocolName);
+      return this;
+    }
+
+    /**
      * Creates a new client that connects to the masters.
      * Doesn't block and won't throw an exception if the masters don't exist.
      * @return a new asynchronous Kudu client
diff --git a/java/kudu-client/src/main/java/org/apache/kudu/client/Negotiator.java b/java/kudu-client/src/main/java/org/apache/kudu/client/Negotiator.java
index e570665..234984b 100644
--- a/java/kudu-client/src/main/java/org/apache/kudu/client/Negotiator.java
+++ b/java/kudu-client/src/main/java/org/apache/kudu/client/Negotiator.java
@@ -222,14 +222,18 @@ public class Negotiator extends SimpleChannelInboundHandler<CallResponse> {
 
   private Certificate peerCert;
 
+  private String saslProtocolName;
+
   @InterfaceAudience.LimitedPrivate("Test")
   boolean overrideLoopbackForTests;
 
   public Negotiator(String remoteHostname,
                     SecurityContext securityContext,
-                    boolean ignoreAuthnToken) {
+                    boolean ignoreAuthnToken,
+                    String saslProtocolName) {
     this.remoteHostname = remoteHostname;
     this.securityContext = securityContext;
+    this.saslProtocolName = saslProtocolName;
     SignedTokenPB token = securityContext.getAuthenticationToken();
     if (token != null) {
       if (ignoreAuthnToken) {
@@ -456,7 +460,7 @@ public class Negotiator extends SimpleChannelInboundHandler<CallResponse> {
       try {
         saslClient = Sasl.createSaslClient(new String[]{ clientMech.name() },
                                            null,
-                                           "kudu",
+                                           saslProtocolName,
                                            remoteHostname,
                                            props,
                                            saslCallback);
diff --git a/java/kudu-client/src/test/java/org/apache/kudu/client/TestNegotiator.java b/java/kudu-client/src/test/java/org/apache/kudu/client/TestNegotiator.java
index 4d4dce6..0da8368 100644
--- a/java/kudu-client/src/test/java/org/apache/kudu/client/TestNegotiator.java
+++ b/java/kudu-client/src/test/java/org/apache/kudu/client/TestNegotiator.java
@@ -117,7 +117,7 @@ public class TestNegotiator {
   }
 
   private void startNegotiation(boolean fakeLoopback) {
-    Negotiator negotiator = new Negotiator("127.0.0.1", secContext, false);
+    Negotiator negotiator = new Negotiator("127.0.0.1", secContext, false, "kudu");
     negotiator.overrideLoopbackForTests = fakeLoopback;
     embedder = new EmbeddedChannel(negotiator);
     negotiator.sendHello(embedder.pipeline().firstContext());
diff --git a/java/kudu-client/src/test/java/org/apache/kudu/client/TestSecurity.java b/java/kudu-client/src/test/java/org/apache/kudu/client/TestSecurity.java
index d4f993d..88037a08 100644
--- a/java/kudu-client/src/test/java/org/apache/kudu/client/TestSecurity.java
+++ b/java/kudu-client/src/test/java/org/apache/kudu/client/TestSecurity.java
@@ -55,6 +55,7 @@ public class TestSecurity {
   private static final String TABLE_NAME = "TestSecurity-table";
   private static final int TICKET_LIFETIME_SECS = 10;
   private static final int RENEWABLE_LIFETIME_SECS = 20;
+  public static final String CUSTOM_PRINCIPAL = "oryx";
 
   private CapturingLogAppender cla;
   private MiniKuduCluster miniCluster;
@@ -64,6 +65,7 @@ public class TestSecurity {
     LONG_LEADER_ELECTION,
     SHORT_TOKENS_AND_TICKETS,
     START_TSERVERS,
+    CUSTOM_PRINCIPAL,
   }
 
   private static class KeyValueMessage {
@@ -89,6 +91,9 @@ public class TestSecurity {
               .kdcRenewLifetime(RENEWABLE_LIFETIME_SECS + "s")
               .kdcTicketLifetime(TICKET_LIFETIME_SECS + "s");
     }
+    if (opts.contains(Option.CUSTOM_PRINCIPAL)) {
+      mcb.principal(CUSTOM_PRINCIPAL);
+    }
     miniCluster = mcb.numMasterServers(3)
         .numTabletServers(opts.contains(Option.START_TSERVERS) ? 3 : 0)
         .build();
@@ -491,4 +496,26 @@ public class TestSecurity {
       }
     }
   }
+
+  @Test(timeout = 60000)
+  public void testNonDefaultPrincipal() throws Exception {
+    startCluster(ImmutableSet.of(Option.CUSTOM_PRINCIPAL, Option.START_TSERVERS));
+    try {
+      this.client.createTable("TestSecurity-nondefault-principal-1",
+          getBasicSchema(),
+          getBasicCreateTableOptions());
+      Assert.fail("default client shouldn't be able to connect to the cluster.");
+    } catch (NonRecoverableException e) {
+      Assert.assertThat(e.getMessage(), CoreMatchers.containsString(
+          "this client is not authenticated"
+      ));
+    }
+    KuduClient client = new KuduClient.KuduClientBuilder(miniCluster.getMasterAddressesAsString())
+            .saslProtocolName(CUSTOM_PRINCIPAL)
+            .build();
+    Assert.assertNotNull(client.createTable( "TestSecurity-nondefault-principal-2",
+        getBasicSchema(),
+        getBasicCreateTableOptions()));
+  }
+
 }
diff --git a/java/kudu-test-utils/src/main/java/org/apache/kudu/test/cluster/MiniKuduCluster.java b/java/kudu-test-utils/src/main/java/org/apache/kudu/test/cluster/MiniKuduCluster.java
index 09ac649..2dcd781 100644
--- a/java/kudu-test-utils/src/main/java/org/apache/kudu/test/cluster/MiniKuduCluster.java
+++ b/java/kudu-test-utils/src/main/java/org/apache/kudu/test/cluster/MiniKuduCluster.java
@@ -106,6 +106,7 @@ public final class MiniKuduCluster implements AutoCloseable {
   private final ImmutableList<String> extraMasterFlags;
   private final ImmutableList<String> locationInfo;
   private final String clusterRoot;
+  private final String principal;
 
   private MiniKdcOptionsPB kdcOptionsPb;
   private final Common.HmsMode hmsMode;
@@ -118,7 +119,8 @@ public final class MiniKuduCluster implements AutoCloseable {
       List<String> locationInfo,
       MiniKdcOptionsPB kdcOptionsPb,
       String clusterRoot,
-      Common.HmsMode hmsMode) {
+      Common.HmsMode hmsMode,
+      String principal) {
     this.enableKerberos = enableKerberos;
     this.numMasters = numMasters;
     this.numTservers = numTservers;
@@ -126,6 +128,7 @@ public final class MiniKuduCluster implements AutoCloseable {
     this.extraMasterFlags = ImmutableList.copyOf(extraMasterFlags);
     this.locationInfo = ImmutableList.copyOf(locationInfo);
     this.kdcOptionsPb = kdcOptionsPb;
+    this.principal = principal;
     this.hmsMode = hmsMode;
 
     if (clusterRoot == null) {
@@ -220,7 +223,8 @@ public final class MiniKuduCluster implements AutoCloseable {
         .addAllExtraMasterFlags(extraMasterFlags)
         .addAllExtraTserverFlags(extraTserverFlags)
         .setMiniKdcOptions(kdcOptionsPb)
-        .setClusterRoot(clusterRoot);
+        .setClusterRoot(clusterRoot)
+        .setPrincipal(principal);
 
     // Set up the location mapping command flag if there is location info.
     if (!locationInfo.isEmpty()) {
@@ -610,6 +614,7 @@ public final class MiniKuduCluster implements AutoCloseable {
     private final List<String> extraMasterServerFlags = new ArrayList<>();
     private final List<String> locationInfo = new ArrayList<>();
     private String clusterRoot = null;
+    private String principal = "kudu";
 
     private MiniKdcOptionsPB.Builder kdcOptionsPb = MiniKdcOptionsPB.newBuilder();
     private Common.HmsMode hmsMode = Common.HmsMode.NONE;
@@ -690,6 +695,11 @@ public final class MiniKuduCluster implements AutoCloseable {
       return this;
     }
 
+    public MiniKuduClusterBuilder principal(String principal) {
+      this.principal = principal;
+      return this;
+    }
+
     /**
      * Builds and starts a new {@link MiniKuduCluster} using builder state.
      * @return the newly started {@link MiniKuduCluster}
@@ -700,7 +710,7 @@ public final class MiniKuduCluster implements AutoCloseable {
           new MiniKuduCluster(enableKerberos,
               numMasterServers, numTabletServers,
               extraTabletServerFlags, extraMasterServerFlags, locationInfo,
-              kdcOptionsPb.build(), clusterRoot, hmsMode);
+              kdcOptionsPb.build(), clusterRoot, hmsMode, principal);
       try {
         cluster.start();
       } catch (IOException e) {