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

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


The following commit(s) were added to refs/heads/branch-3 by this push:
     new 0aadea9d71b HBASE-28952 Add coprocessor hook to authorize user based 
on client SSL certificate chain (#6447)
0aadea9d71b is described below

commit 0aadea9d71b987e29674550217740c8fbe5137d4
Author: Andor Molnár <[email protected]>
AuthorDate: Fri Jan 17 02:09:23 2025 -0600

    HBASE-28952 Add coprocessor hook to authorize user based on client SSL 
certificate chain (#6447)
    
    Signed-off-by: Bryan Beaudreault <[email protected]>
    (cherry picked from commit d477bf163bf51d38e6596131f6608da6f9597f95)
---
 .../hadoop/hbase/coprocessor/CoprocessorHost.java  |   1 +
 .../hadoop/hbase/coprocessor/RpcCoprocessor.java   |  32 ++++++
 .../coprocessor/RpcCoprocessorEnvironment.java     |  28 +++++
 .../hadoop/hbase/coprocessor/RpcObserver.java      |  74 +++++++++++++
 .../hadoop/hbase/ipc/RpcCoprocessorHost.java       | 122 +++++++++++++++++++++
 .../org/apache/hadoop/hbase/ipc/RpcServer.java     |  22 ++++
 .../hadoop/hbase/ipc/RpcServerInterface.java       |   2 +
 .../hadoop/hbase/ipc/ServerRpcConnection.java      |   3 +
 .../hbase/coprocessor/TestRpcCoprocessor.java      | 109 ++++++++++++++++++
 9 files changed, 393 insertions(+)

diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/CoprocessorHost.java
 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/CoprocessorHost.java
index c1ba9e274ad..137fe3b061d 100644
--- 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/CoprocessorHost.java
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/CoprocessorHost.java
@@ -62,6 +62,7 @@ public abstract class CoprocessorHost<C extends Coprocessor, 
E extends Coprocess
     "hbase.coprocessor.user.region.classes";
   public static final String MASTER_COPROCESSOR_CONF_KEY = 
"hbase.coprocessor.master.classes";
   public static final String WAL_COPROCESSOR_CONF_KEY = 
"hbase.coprocessor.wal.classes";
+  public static final String RPC_COPROCESSOR_CONF_KEY = 
"hbase.coprocessor.rpc.classes";
   public static final String ABORT_ON_ERROR_KEY = 
"hbase.coprocessor.abortonerror";
   public static final boolean DEFAULT_ABORT_ON_ERROR = true;
   public static final String COPROCESSORS_ENABLED_CONF_KEY = 
"hbase.coprocessor.enabled";
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/RpcCoprocessor.java
 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/RpcCoprocessor.java
new file mode 100644
index 00000000000..108e023cfa8
--- /dev/null
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/RpcCoprocessor.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hbase.coprocessor;
+
+import java.util.Optional;
+import org.apache.hadoop.hbase.Coprocessor;
+import org.apache.hadoop.hbase.HBaseInterfaceAudience;
+import org.apache.yetus.audience.InterfaceAudience;
+import org.apache.yetus.audience.InterfaceStability;
+
[email protected](HBaseInterfaceAudience.COPROC)
[email protected]
+public interface RpcCoprocessor extends Coprocessor {
+  default Optional<RpcObserver> getRpcObserver() {
+    return Optional.empty();
+  }
+}
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/RpcCoprocessorEnvironment.java
 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/RpcCoprocessorEnvironment.java
new file mode 100644
index 00000000000..2f3b2c8e67c
--- /dev/null
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/RpcCoprocessorEnvironment.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hbase.coprocessor;
+
+import org.apache.hadoop.hbase.CoprocessorEnvironment;
+import org.apache.hadoop.hbase.HBaseInterfaceAudience;
+import org.apache.yetus.audience.InterfaceAudience;
+import org.apache.yetus.audience.InterfaceStability;
+
[email protected](HBaseInterfaceAudience.COPROC)
[email protected]
+public interface RpcCoprocessorEnvironment extends 
CoprocessorEnvironment<RpcCoprocessor> {
+}
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/RpcObserver.java
 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/RpcObserver.java
new file mode 100644
index 00000000000..4bc13c17515
--- /dev/null
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/RpcObserver.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hbase.coprocessor;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.security.cert.X509Certificate;
+import org.apache.hadoop.hbase.HBaseInterfaceAudience;
+import org.apache.yetus.audience.InterfaceAudience;
+import org.apache.yetus.audience.InterfaceStability;
+
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos;
+
+/**
+ * Coprocessors implement this interface to observe and mediate RPC events in 
Master and RS
+ * instances.
+ * <p>
+ * Since most implementations will be interested in only a subset of hooks, 
this class uses
+ * 'default' functions to avoid having to add unnecessary overrides. When the 
functions are
+ * non-empty, it's simply to satisfy the compiler by returning value of 
expected (non-void) type. It
+ * is done in a way that these default definitions act as no-op. So our 
suggestion to implementation
+ * would be to not call these 'default' methods from overrides.
+ * <p>
+ * <h3>Exception Handling</h3><br>
+ * For all functions, exception handling is done as follows:
+ * <ul>
+ * <li>Exceptions of type {@link IOException} are reported back to client.</li>
+ * <li>For any other kind of exception:
+ * <ul>
+ * <li>Be aware that this coprocessor doesn't support abortion. If the 
configuration
+ * {@link CoprocessorHost#ABORT_ON_ERROR_KEY} is set to true, the event will 
be logged, but the RPC
+ * server won't be aborted.</li>
+ * <li>Otherwise, coprocessor is removed from the server.</li>
+ * </ul>
+ * </li>
+ * </ul>
+ */
[email protected](HBaseInterfaceAudience.COPROC)
[email protected]
+public interface RpcObserver {
+
+  /**
+   * Called before authorizing connection
+   * @param ctx the coprocessor instance's environment
+   */
+  default void 
preAuthorizeConnection(ObserverContext<RpcCoprocessorEnvironment> ctx,
+    RPCProtos.ConnectionHeader connectionHeader, InetAddress remoteAddr) 
throws IOException {
+  }
+
+  /**
+   * Called after successfully authorizing connection
+   * @param ctx                    the coprocessor instance's environment
+   * @param userName               the user name
+   * @param clientCertificateChain list of peer certificates from SSL 
connection
+   */
+  default void 
postAuthorizeConnection(ObserverContext<RpcCoprocessorEnvironment> ctx,
+    String userName, X509Certificate[] clientCertificateChain) throws 
IOException {
+  }
+}
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/RpcCoprocessorHost.java
 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/RpcCoprocessorHost.java
new file mode 100644
index 00000000000..2ced2311989
--- /dev/null
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/RpcCoprocessorHost.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hbase.ipc;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.net.InetAddress;
+import java.security.cert.X509Certificate;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.coprocessor.BaseEnvironment;
+import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
+import org.apache.hadoop.hbase.coprocessor.RpcCoprocessor;
+import org.apache.hadoop.hbase.coprocessor.RpcCoprocessorEnvironment;
+import org.apache.hadoop.hbase.coprocessor.RpcObserver;
+import org.apache.hadoop.hbase.security.User;
+import org.apache.yetus.audience.InterfaceAudience;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos;
+
[email protected]
+public class RpcCoprocessorHost extends CoprocessorHost<RpcCoprocessor, 
RpcCoprocessorEnvironment> {
+
+  private static final Logger LOG = 
LoggerFactory.getLogger(RpcCoprocessorHost.class);
+
+  private static class RpcEnvironment extends BaseEnvironment<RpcCoprocessor>
+    implements RpcCoprocessorEnvironment {
+
+    public RpcEnvironment(RpcCoprocessor impl, int priority, int seq, 
Configuration conf) {
+      super(impl, priority, seq, conf);
+    }
+  }
+
+  public RpcCoprocessorHost(final Configuration conf) {
+    // RPCServer cannot be aborted, so we don't pass Abortable down here.
+    super(null);
+    this.conf = conf;
+    boolean coprocessorsEnabled =
+      conf.getBoolean(COPROCESSORS_ENABLED_CONF_KEY, 
DEFAULT_COPROCESSORS_ENABLED);
+    LOG.trace("System coprocessor loading is {}", (coprocessorsEnabled ? 
"enabled" : "disabled"));
+    loadSystemCoprocessors(conf, RPC_COPROCESSOR_CONF_KEY);
+  }
+
+  @Override
+  public RpcCoprocessorEnvironment createEnvironment(RpcCoprocessor instance, 
int priority,
+    int sequence, Configuration conf) {
+    return new RpcEnvironment(instance, priority, sequence, conf);
+  }
+
+  @Override
+  public RpcCoprocessor checkAndGetInstance(Class<?> implClass)
+    throws InstantiationException, IllegalAccessException {
+    try {
+      if (RpcCoprocessor.class.isAssignableFrom(implClass)) {
+        return 
implClass.asSubclass(RpcCoprocessor.class).getDeclaredConstructor().newInstance();
+      } else {
+        LOG.error("{} is not of type RpcCoprocessor. Check the configuration 
of {}",
+          implClass.getName(), CoprocessorHost.RPC_COPROCESSOR_CONF_KEY);
+        return null;
+      }
+    } catch (NoSuchMethodException | InvocationTargetException e) {
+      throw (InstantiationException) new 
InstantiationException(implClass.getName()).initCause(e);
+    }
+  }
+
+  private final ObserverGetter<RpcCoprocessor, RpcObserver> rpcObserverGetter =
+    RpcCoprocessor::getRpcObserver;
+
+  abstract class RpcObserverOperation extends 
ObserverOperationWithoutResult<RpcObserver> {
+    public RpcObserverOperation() {
+      super(rpcObserverGetter);
+    }
+
+    public RpcObserverOperation(boolean bypassable) {
+      this(null, bypassable);
+    }
+
+    public RpcObserverOperation(User user) {
+      super(rpcObserverGetter, user);
+    }
+
+    public RpcObserverOperation(User user, boolean bypassable) {
+      super(rpcObserverGetter, user, bypassable);
+    }
+  }
+
+  public void preAuthorizeConnection(RPCProtos.ConnectionHeader 
connectionHeader,
+    InetAddress remoteAddr) throws IOException {
+    execOperation(coprocEnvironments.isEmpty() ? null : new 
RpcObserverOperation() {
+      @Override
+      protected void call(RpcObserver observer) throws IOException {
+        observer.preAuthorizeConnection(this, connectionHeader, remoteAddr);
+      }
+    });
+  }
+
+  public void postAuthorizeConnection(final String userName,
+    final X509Certificate[] clientCertificates) throws IOException {
+    execOperation(coprocEnvironments.isEmpty() ? null : new 
RpcObserverOperation() {
+      @Override
+      protected void call(RpcObserver observer) throws IOException {
+        observer.postAuthorizeConnection(this, userName, clientCertificates);
+      }
+    });
+  }
+}
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/RpcServer.java 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/RpcServer.java
index 4ff1a0b5482..fce51f36014 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/RpcServer.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/RpcServer.java
@@ -42,6 +42,7 @@ import org.apache.hadoop.hbase.ExtendedCellScanner;
 import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.Server;
 import org.apache.hadoop.hbase.conf.ConfigurationObserver;
+import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
 import org.apache.hadoop.hbase.io.ByteBuffAllocator;
 import org.apache.hadoop.hbase.monitoring.MonitoredRPCHandler;
 import org.apache.hadoop.hbase.monitoring.TaskMonitor;
@@ -54,6 +55,7 @@ import 
org.apache.hadoop.hbase.security.SaslUtil.QualityOfProtection;
 import org.apache.hadoop.hbase.security.User;
 import org.apache.hadoop.hbase.security.UserProvider;
 import org.apache.hadoop.hbase.security.token.AuthenticationTokenSecretManager;
+import org.apache.hadoop.hbase.util.CoprocessorConfigurationUtil;
 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
 import org.apache.hadoop.hbase.util.GsonUtil;
 import org.apache.hadoop.hbase.util.Pair;
@@ -220,6 +222,8 @@ public abstract class RpcServer implements 
RpcServerInterface, ConfigurationObse
 
   protected volatile boolean allowFallbackToSimpleAuth;
 
+  volatile RpcCoprocessorHost cpHost;
+
   /**
    * Used to get details for scan with a scanner_id<br/>
    * TODO try to figure out a better way and remove reference from 
regionserver package later.
@@ -312,6 +316,8 @@ public abstract class RpcServer implements 
RpcServerInterface, ConfigurationObse
 
     this.isOnlineLogProviderEnabled = getIsOnlineLogProviderEnabled(conf);
     this.scheduler = scheduler;
+
+    initializeCoprocessorHost(getConf());
   }
 
   @Override
@@ -324,6 +330,13 @@ public abstract class RpcServer implements 
RpcServerInterface, ConfigurationObse
       refreshAuthManager(newConf, new HBasePolicyProvider());
     }
     refreshSlowLogConfiguration(newConf);
+    if (
+      CoprocessorConfigurationUtil.checkConfigurationChange(getConf(), newConf,
+        CoprocessorHost.RPC_COPROCESSOR_CONF_KEY)
+    ) {
+      LOG.info("Update the RPC coprocessor(s) because the configuration has 
changed");
+      initializeCoprocessorHost(newConf);
+    }
   }
 
   private void refreshSlowLogConfiguration(Configuration newConf) {
@@ -898,4 +911,13 @@ public abstract class RpcServer implements 
RpcServerInterface, ConfigurationObse
   public List<BlockingServiceAndInterface> getServices() {
     return services;
   }
+
+  private void initializeCoprocessorHost(Configuration conf) {
+    this.cpHost = new RpcCoprocessorHost(conf);
+  }
+
+  @Override
+  public RpcCoprocessorHost getRpcCoprocessorHost() {
+    return cpHost;
+  }
 }
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/RpcServerInterface.java
 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/RpcServerInterface.java
index 9bf5fc3817d..a2df50118ad 100644
--- 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/RpcServerInterface.java
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/RpcServerInterface.java
@@ -85,4 +85,6 @@ public interface RpcServerInterface {
    */
   void setNamedQueueRecorder(final NamedQueueRecorder namedQueueRecorder);
 
+  /** Return RPC's instance of {@link RpcCoprocessorHost} */
+  RpcCoprocessorHost getRpcCoprocessorHost();
 }
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 c17a8da9041..dcf64b14276 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
@@ -366,6 +366,7 @@ abstract class ServerRpcConnection implements Closeable {
       processConnectionHeader(buf);
       callCleanupIfNeeded();
       this.connectionHeaderRead = true;
+      
this.rpcServer.getRpcCoprocessorHost().preAuthorizeConnection(connectionHeader, 
addr);
       if (rpcServer.needAuthorization() && !authorizeConnection()) {
         // Throw FatalConnectionException wrapping ACE so client does right 
thing and closes
         // down the connection instead of trying to read non-existent retun.
@@ -373,6 +374,8 @@ abstract class ServerRpcConnection implements Closeable {
           + connectionHeader.getServiceName() + " is unauthorized for user: " 
+ ugi);
       }
       this.user = this.rpcServer.userProvider.create(this.ugi);
+      this.rpcServer.getRpcCoprocessorHost().postAuthorizeConnection(
+        this.user != null ? this.user.getName() : null, 
this.clientCertificateChain);
     }
   }
 
diff --git 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/coprocessor/TestRpcCoprocessor.java
 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/coprocessor/TestRpcCoprocessor.java
new file mode 100644
index 00000000000..5ba3e819918
--- /dev/null
+++ 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/coprocessor/TestRpcCoprocessor.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hbase.coprocessor;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.security.cert.X509Certificate;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.HBaseTestingUtil;
+import org.apache.hadoop.hbase.ipc.RpcCoprocessorHost;
+import org.apache.hadoop.hbase.testclassification.CoprocessorTests;
+import org.apache.hadoop.hbase.testclassification.MediumTests;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos;
+
+@Category({ CoprocessorTests.class, MediumTests.class })
+public class TestRpcCoprocessor {
+
+  @ClassRule
+  public static final HBaseClassTestRule CLASS_RULE =
+    HBaseClassTestRule.forClass(TestRpcCoprocessor.class);
+
+  public static class AuthorizationRpcObserver implements RpcCoprocessor, 
RpcObserver {
+    final AtomicInteger ctPostAuthorization = new AtomicInteger(0);
+    final AtomicInteger ctPreAuthorization = new AtomicInteger(0);
+    String userName = null;
+
+    @Override
+    public Optional<RpcObserver> getRpcObserver() {
+      return Optional.of(this);
+    }
+
+    @Override
+    public void 
preAuthorizeConnection(ObserverContext<RpcCoprocessorEnvironment> ctx,
+      RPCProtos.ConnectionHeader connectionHeader, InetAddress remoteAddr) 
throws IOException {
+      ctPreAuthorization.incrementAndGet();
+    }
+
+    @Override
+    public void 
postAuthorizeConnection(ObserverContext<RpcCoprocessorEnvironment> ctx,
+      String userName, X509Certificate[] clientCertificateChain) throws 
IOException {
+      ctPostAuthorization.incrementAndGet();
+      this.userName = userName;
+    }
+  }
+
+  private static HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
+
+  @BeforeClass
+  public static void setupBeforeClass() throws Exception {
+    // set configure to indicate which cp should be loaded
+    Configuration conf = TEST_UTIL.getConfiguration();
+    conf.set(CoprocessorHost.RPC_COPROCESSOR_CONF_KEY, 
AuthorizationRpcObserver.class.getName());
+    
TEST_UTIL.getConfiguration().setBoolean(CoprocessorHost.ABORT_ON_ERROR_KEY, 
false);
+    TEST_UTIL.startMiniCluster();
+  }
+
+  @AfterClass
+  public static void teardownAfterClass() throws Exception {
+    TEST_UTIL.shutdownMiniCluster();
+  }
+
+  @Test
+  public void testHooksCalledFromMaster() {
+    RpcCoprocessorHost coprocHostMaster =
+      
TEST_UTIL.getMiniHBaseCluster().getMaster().getRpcServer().getRpcCoprocessorHost();
+    AuthorizationRpcObserver observer =
+      coprocHostMaster.findCoprocessor(AuthorizationRpcObserver.class);
+    assertEquals(2, observer.ctPreAuthorization.get());
+    assertEquals(2, observer.ctPostAuthorization.get());
+    assertEquals(System.getProperty("user.name"), observer.userName);
+  }
+
+  @Test
+  public void testHooksCalledFromRegionServer() {
+    RpcCoprocessorHost coprocHostRs =
+      
TEST_UTIL.getMiniHBaseCluster().getRegionServer(0).getRpcServer().getRpcCoprocessorHost();
+    AuthorizationRpcObserver observer =
+      coprocHostRs.findCoprocessor(AuthorizationRpcObserver.class);
+    assertEquals(3, observer.ctPreAuthorization.get());
+    assertEquals(3, observer.ctPostAuthorization.get());
+    assertEquals(System.getProperty("user.name"), observer.userName);
+  }
+}

Reply via email to