You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hbase.apache.org by zg...@apache.org on 2019/01/30 11:04:04 UTC

[hbase] branch branch-2 updated: HBASE-21739 Move grant/revoke from regionserver to master

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

zghao 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 ec9029f  HBASE-21739 Move grant/revoke from regionserver to master
ec9029f is described below

commit ec9029f77e97818a5bc7a53f63af93444066dacd
Author: meiyi <my...@gamil.com>
AuthorDate: Wed Jan 30 14:55:51 2019 +0800

    HBASE-21739 Move grant/revoke from regionserver to master
    
    Signed-off-by: Guanghao Zhang <zg...@apache.org>
---
 .../java/org/apache/hadoop/hbase/client/Admin.java |  20 +++
 .../org/apache/hadoop/hbase/client/AsyncAdmin.java |  16 +++
 .../hadoop/hbase/client/AsyncHBaseAdmin.java       |  12 ++
 .../hbase/client/ConnectionImplementation.java     |  13 ++
 .../org/apache/hadoop/hbase/client/HBaseAdmin.java |  30 +++++
 .../hadoop/hbase/client/RawAsyncHBaseAdmin.java    |  25 ++++
 .../hbase/client/ShortCircuitMasterConnection.java |  17 +++
 .../hbase/security/access/AccessControlClient.java |  58 +++------
 .../hbase/security/access/AccessControlUtil.java   |  13 ++
 .../security/access/ShadedAccessControlUtil.java   | 130 +++++++++++---------
 .../src/main/protobuf/Master.proto                 |   5 +
 .../hadoop/hbase/coprocessor/MasterObserver.java   |  39 ++++++
 .../hadoop/hbase/master/MasterCoprocessorHost.java |  39 ++++++
 .../hadoop/hbase/master/MasterRpcServices.java     |  52 ++++++++
 .../hbase/security/access/AccessControlLists.java  |   4 +-
 .../hbase/security/access/AccessController.java    | 107 +++++++---------
 .../hbase/security/access/SecureTestUtil.java      |  49 ++------
 .../security/access/TestAccessController.java      |  88 ++++++++++---
 .../security/access/TestNamespaceCommands.java     | 136 ++++++++++++---------
 .../hadoop/hbase/thrift2/client/ThriftAdmin.java   |  11 ++
 20 files changed, 599 insertions(+), 265 deletions(-)

diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java
index 450b953..8d13b17 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java
@@ -52,6 +52,7 @@ import org.apache.hadoop.hbase.regionserver.wal.FailedLogCloseException;
 import org.apache.hadoop.hbase.replication.ReplicationException;
 import org.apache.hadoop.hbase.replication.ReplicationPeerConfig;
 import org.apache.hadoop.hbase.replication.ReplicationPeerDescription;
+import org.apache.hadoop.hbase.security.access.UserPermission;
 import org.apache.hadoop.hbase.snapshot.HBaseSnapshotException;
 import org.apache.hadoop.hbase.snapshot.RestoreSnapshotException;
 import org.apache.hadoop.hbase.snapshot.SnapshotCreationException;
@@ -2794,4 +2795,23 @@ public interface Admin extends Abortable, Closeable {
    * no quota information on that table.
    */
   SpaceQuotaSnapshotView getCurrentSpaceQuotaSnapshot(TableName tableName) throws IOException;
+
+  /**
+   * Grants user specific permissions
+   *
+   * @param userPermission user and permissions
+   * @param mergeExistingPermissions If set to false, later granted permissions will override
+   *          previous granted permissions. otherwise, it'll merge with previous granted
+   *          permissions.
+   * @throws IOException if a remote or network exception occurs
+   */
+  void grant(UserPermission userPermission, boolean mergeExistingPermissions) throws IOException;
+
+  /**
+   * Revokes user specific permissions
+   *
+   * @param userPermission user and permissions
+   * @throws IOException if a remote or network exception occurs
+   */
+  void revoke(UserPermission userPermission) throws IOException;
 }
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java
index d9a5fd1..5aafb13 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java
@@ -41,6 +41,7 @@ import org.apache.hadoop.hbase.quotas.QuotaSettings;
 import org.apache.hadoop.hbase.quotas.SpaceQuotaSnapshotView;
 import org.apache.hadoop.hbase.replication.ReplicationPeerConfig;
 import org.apache.hadoop.hbase.replication.ReplicationPeerDescription;
+import org.apache.hadoop.hbase.security.access.UserPermission;
 import org.apache.yetus.audience.InterfaceAudience;
 
 /**
@@ -1293,4 +1294,19 @@ public interface AsyncAdmin {
    */
   CompletableFuture<? extends SpaceQuotaSnapshotView> getCurrentSpaceQuotaSnapshot(
       TableName tableName);
+
+  /**
+   * Grants user specific permissions
+   * @param userPermission user and permissions
+   * @param mergeExistingPermissions If set to false, later granted permissions will override
+   *          previous granted permissions. otherwise, it'll merge with previous granted
+   *          permissions.
+   */
+  CompletableFuture<Void> grant(UserPermission userPermission, boolean mergeExistingPermissions);
+
+  /**
+   * Revokes user specific permissions
+   * @param userPermission user and permissions
+   */
+  CompletableFuture<Void> revoke(UserPermission userPermission);
 }
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java
index 983fd32..8894ad1 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java
@@ -41,6 +41,7 @@ import org.apache.hadoop.hbase.quotas.QuotaSettings;
 import org.apache.hadoop.hbase.quotas.SpaceQuotaSnapshot;
 import org.apache.hadoop.hbase.replication.ReplicationPeerConfig;
 import org.apache.hadoop.hbase.replication.ReplicationPeerDescription;
+import org.apache.hadoop.hbase.security.access.UserPermission;
 import org.apache.yetus.audience.InterfaceAudience;
 
 /**
@@ -789,4 +790,15 @@ class AsyncHBaseAdmin implements AsyncAdmin {
   public CompletableFuture<SpaceQuotaSnapshot> getCurrentSpaceQuotaSnapshot(TableName tableName) {
     return wrap(rawAdmin.getCurrentSpaceQuotaSnapshot(tableName));
   }
+
+  @Override
+  public CompletableFuture<Void> grant(UserPermission userPermission,
+      boolean mergeExistingPermissions) {
+    return wrap(rawAdmin.grant(userPermission, mergeExistingPermissions));
+  }
+
+  @Override
+  public CompletableFuture<Void> revoke(UserPermission userPermission) {
+    return wrap(rawAdmin.revoke(userPermission));
+  }
 }
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionImplementation.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionImplementation.java
index f5e5e4c..d4b46ae 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionImplementation.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionImplementation.java
@@ -92,6 +92,7 @@ import org.apache.hbase.thirdparty.com.google.protobuf.ServiceException;
 
 import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
 import org.apache.hadoop.hbase.shaded.protobuf.RequestConverter;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.ClientService.BlockingInterface;
@@ -1765,6 +1766,18 @@ class ConnectionImplementation implements ClusterConnection, Closeable {
           IsRpcThrottleEnabledRequest request) throws ServiceException {
         return stub.isRpcThrottleEnabled(controller, request);
       }
+
+      @Override
+      public AccessControlProtos.GrantResponse grant(RpcController controller,
+          AccessControlProtos.GrantRequest request) throws ServiceException {
+        return stub.grant(controller, request);
+      }
+
+      @Override
+      public AccessControlProtos.RevokeResponse revoke(RpcController controller,
+          AccessControlProtos.RevokeRequest request) throws ServiceException {
+        return stub.revoke(controller, request);
+      }
     };
   }
 
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java
index 8e2ff69..a7b55b1 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java
@@ -90,6 +90,8 @@ import org.apache.hadoop.hbase.regionserver.wal.FailedLogCloseException;
 import org.apache.hadoop.hbase.replication.ReplicationException;
 import org.apache.hadoop.hbase.replication.ReplicationPeerConfig;
 import org.apache.hadoop.hbase.replication.ReplicationPeerDescription;
+import org.apache.hadoop.hbase.security.access.ShadedAccessControlUtil;
+import org.apache.hadoop.hbase.security.access.UserPermission;
 import org.apache.hadoop.hbase.snapshot.ClientSnapshotDescriptionUtils;
 import org.apache.hadoop.hbase.snapshot.HBaseSnapshotException;
 import org.apache.hadoop.hbase.snapshot.RestoreSnapshotException;
@@ -112,6 +114,8 @@ import org.apache.hbase.thirdparty.com.google.protobuf.ServiceException;
 
 import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
 import org.apache.hadoop.hbase.shaded.protobuf.RequestConverter;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.GrantRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.RevokeRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.AdminService;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.ClearCompactionQueuesRequest;
@@ -4438,4 +4442,30 @@ public class HBaseAdmin implements Admin {
         }
       });
   }
+
+  @Override
+  public void grant(UserPermission userPermission, boolean mergeExistingPermissions)
+      throws IOException {
+    executeCallable(new MasterCallable<Void>(getConnection(), getRpcControllerFactory()) {
+      @Override
+      protected Void rpcCall() throws Exception {
+        GrantRequest req =
+            ShadedAccessControlUtil.buildGrantRequest(userPermission, mergeExistingPermissions);
+        this.master.grant(getRpcController(), req);
+        return null;
+      }
+    });
+  }
+
+  @Override
+  public void revoke(UserPermission userPermission) throws IOException {
+    executeCallable(new MasterCallable<Void>(getConnection(), getRpcControllerFactory()) {
+      @Override
+      protected Void rpcCall() throws Exception {
+        RevokeRequest req = ShadedAccessControlUtil.buildRevokeRequest(userPermission);
+        this.master.revoke(getRpcController(), req);
+        return null;
+      }
+    });
+  }
 }
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java
index b93b741..7c394c1 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java
@@ -81,6 +81,8 @@ import org.apache.hadoop.hbase.quotas.SpaceQuotaSnapshot;
 import org.apache.hadoop.hbase.replication.ReplicationException;
 import org.apache.hadoop.hbase.replication.ReplicationPeerConfig;
 import org.apache.hadoop.hbase.replication.ReplicationPeerDescription;
+import org.apache.hadoop.hbase.security.access.ShadedAccessControlUtil;
+import org.apache.hadoop.hbase.security.access.UserPermission;
 import org.apache.hadoop.hbase.snapshot.ClientSnapshotDescriptionUtils;
 import org.apache.hadoop.hbase.snapshot.RestoreSnapshotException;
 import org.apache.hadoop.hbase.snapshot.SnapshotCreationException;
@@ -100,6 +102,10 @@ import org.apache.hbase.thirdparty.io.netty.util.TimerTask;
 
 import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
 import org.apache.hadoop.hbase.shaded.protobuf.RequestConverter;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.GrantRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.GrantResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.RevokeRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.RevokeResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.AdminService;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.ClearCompactionQueuesRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.ClearCompactionQueuesResponse;
@@ -3699,4 +3705,23 @@ class RawAsyncHBaseAdmin implements AsyncAdmin {
       .filter(s -> s.getTableName().equals(protoTableName)).findFirst()
       .map(s -> SpaceQuotaSnapshot.toSpaceQuotaSnapshot(s.getSnapshot())).orElse(null));
   }
+
+  @Override
+  public CompletableFuture<Void> grant(UserPermission userPermission,
+      boolean mergeExistingPermissions) {
+    return this.<Void> newMasterCaller()
+        .action((controller, stub) -> this.<GrantRequest, GrantResponse, Void> call(controller,
+          stub, ShadedAccessControlUtil.buildGrantRequest(userPermission, mergeExistingPermissions),
+          (s, c, req, done) -> s.grant(c, req, done), resp -> null))
+        .call();
+  }
+
+  @Override
+  public CompletableFuture<Void> revoke(UserPermission userPermission) {
+    return this.<Void> newMasterCaller()
+        .action((controller, stub) -> this.<RevokeRequest, RevokeResponse, Void> call(controller,
+          stub, ShadedAccessControlUtil.buildRevokeRequest(userPermission),
+          (s, c, req, done) -> s.revoke(c, req, done), resp -> null))
+        .call();
+  }
 }
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ShortCircuitMasterConnection.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ShortCircuitMasterConnection.java
index e684e23..821ba28 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ShortCircuitMasterConnection.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ShortCircuitMasterConnection.java
@@ -20,6 +20,11 @@ package org.apache.hadoop.hbase.client;
 import org.apache.yetus.audience.InterfaceAudience;
 import org.apache.hbase.thirdparty.com.google.protobuf.RpcController;
 import org.apache.hbase.thirdparty.com.google.protobuf.ServiceException;
+
+import org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.GrantRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.GrantResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.RevokeRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.RevokeResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.CoprocessorServiceRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.CoprocessorServiceResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.AbortProcedureRequest;
@@ -654,4 +659,16 @@ public class ShortCircuitMasterConnection implements MasterKeepAliveConnection {
       IsRpcThrottleEnabledRequest request) throws ServiceException {
     return stub.isRpcThrottleEnabled(controller, request);
   }
+
+  @Override
+  public GrantResponse grant(RpcController controller, GrantRequest request)
+      throws ServiceException {
+    return stub.grant(controller, request);
+  }
+
+  @Override
+  public RevokeResponse revoke(RpcController controller, RevokeRequest request)
+      throws ServiceException {
+    return stub.revoke(controller, request);
+  }
 }
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlClient.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlClient.java
index 981db76..1031cfe 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlClient.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlClient.java
@@ -94,10 +94,9 @@ public class AccessControlClient {
       final String userName, final byte[] family, final byte[] qual, boolean mergeExistingPermissions,
       final Permission.Action... actions) throws Throwable {
     // TODO: Priority is not used.
-    try (Table table = connection.getTable(ACL_TABLE_NAME)) {
-      AccessControlUtil.grant(null, getAccessControlServiceStub(table), userName, tableName,
-        family, qual, mergeExistingPermissions, actions);
-    }
+    UserPermission userPermission =
+        new UserPermission(userName, new TablePermission(tableName, family, qual, actions));
+    connection.getAdmin().grant(userPermission, mergeExistingPermissions);
   }
 
   /**
@@ -129,11 +128,9 @@ public class AccessControlClient {
    */
   private static void grant(Connection connection, final String namespace, final String userName,
       boolean mergeExistingPermissions, final Permission.Action... actions) throws Throwable {
-    // TODO: Pass an rpcController.
-    try (Table table = connection.getTable(ACL_TABLE_NAME)) {
-      AccessControlUtil.grant(null, getAccessControlServiceStub(table), userName, namespace,
-        mergeExistingPermissions, actions);
-    }
+    UserPermission userPermission =
+        new UserPermission(userName, new NamespacePermission(namespace, actions));
+    connection.getAdmin().grant(userPermission, mergeExistingPermissions);
   }
 
   /**
@@ -152,7 +149,7 @@ public class AccessControlClient {
   }
 
   /**
-   * Grants permission on the specified namespace for the specified user.
+   * Grant global permissions for the specified user.
    * @param connection
    * @param userName
    * @param mergeExistingPermissions If set to false, later granted permissions will override
@@ -163,11 +160,8 @@ public class AccessControlClient {
    */
   private static void grant(Connection connection, final String userName,
       boolean mergeExistingPermissions, final Permission.Action... actions) throws Throwable {
-    // TODO: Pass an rpcController
-    try (Table table = connection.getTable(ACL_TABLE_NAME)) {
-      AccessControlUtil.grant(null, getAccessControlServiceStub(table), userName,
-              mergeExistingPermissions, actions);
-    }
+    UserPermission userPermission = new UserPermission(userName, new GlobalPermission(actions));
+    connection.getAdmin().grant(userPermission, mergeExistingPermissions);
   }
 
   /**
@@ -204,19 +198,13 @@ public class AccessControlClient {
   public static void revoke(Connection connection, final TableName tableName,
       final String username, final byte[] family, final byte[] qualifier,
       final Permission.Action... actions) throws Throwable {
-    /** TODO: Pass an rpcController
-    HBaseRpcController controller
-      = ((ClusterConnection) connection).getRpcControllerFactory().newController();
-    controller.setPriority(tableName);
-    */
-    try (Table table = connection.getTable(ACL_TABLE_NAME)) {
-      AccessControlUtil.revoke(null, getAccessControlServiceStub(table), username, tableName,
-        family, qualifier, actions);
-    }
+    UserPermission userPermission =
+        new UserPermission(username, new TablePermission(tableName, family, qualifier, actions));
+    connection.getAdmin().revoke(userPermission);
   }
 
   /**
-   * Revokes the permission on the table for the specified user.
+   * Revokes the permission on the namespace for the specified user.
    * @param connection The Connection instance to use
    * @param namespace
    * @param userName
@@ -225,14 +213,9 @@ public class AccessControlClient {
    */
   public static void revoke(Connection connection, final String namespace,
       final String userName, final Permission.Action... actions) throws Throwable {
-    /** TODO: Pass an rpcController
-    HBaseRpcController controller
-      = ((ClusterConnection) connection).getRpcControllerFactory().newController();
-      */
-    try (Table table = connection.getTable(ACL_TABLE_NAME)) {
-      AccessControlUtil.revoke(null, getAccessControlServiceStub(table), userName, namespace,
-        actions);
-    }
+    UserPermission userPermission =
+        new UserPermission(userName, new NamespacePermission(namespace, actions));
+    connection.getAdmin().revoke(userPermission);
   }
 
   /**
@@ -241,13 +224,8 @@ public class AccessControlClient {
    */
   public static void revoke(Connection connection, final String userName,
       final Permission.Action... actions) throws Throwable {
-    /** TODO: Pass an rpc controller.
-    HBaseRpcController controller
-      = ((ClusterConnection) connection).getRpcControllerFactory().newController();
-      */
-    try (Table table = connection.getTable(ACL_TABLE_NAME)) {
-      AccessControlUtil.revoke(null, getAccessControlServiceStub(table), userName, actions);
-    }
+    UserPermission userPermission = new UserPermission(userName, new GlobalPermission(actions));
+    connection.getAdmin().revoke(userPermission);
   }
 
   /**
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlUtil.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlUtil.java
index b37440c..4bae943 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlUtil.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlUtil.java
@@ -25,6 +25,7 @@ import java.util.Map;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.client.Admin;
 import org.apache.yetus.audience.InterfaceAudience;
 import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
 import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos;
@@ -490,7 +491,9 @@ public class AccessControlUtil {
    * @param userShortName the short name of the user to grant permissions
    * @param actions the permissions to be granted
    * @throws ServiceException
+   * @deprecated Use {@link Admin#grant(UserPermission, boolean)} instead.
    */
+  @Deprecated
   public static void grant(RpcController controller,
       AccessControlService.BlockingInterface protocol, String userShortName, boolean mergeExistingPermissions,
       Permission.Action... actions) throws ServiceException {
@@ -517,7 +520,9 @@ public class AccessControlUtil {
    * @param q optional qualifier
    * @param actions the permissions to be granted
    * @throws ServiceException
+   * @deprecated Use {@link Admin#grant(UserPermission, boolean)} instead.
    */
+  @Deprecated
   public static void grant(RpcController controller,
       AccessControlService.BlockingInterface protocol, String userShortName, TableName tableName,
       byte[] f, byte[] q, boolean mergeExistingPermissions, Permission.Action... actions)
@@ -543,7 +548,9 @@ public class AccessControlUtil {
    * @param namespace the short name of the user to grant permissions
    * @param actions the permissions to be granted
    * @throws ServiceException
+   * @deprecated Use {@link Admin#grant(UserPermission, boolean)} instead.
    */
+  @Deprecated
   public static void grant(RpcController controller,
       AccessControlService.BlockingInterface protocol, String userShortName, String namespace,
       boolean mergeExistingPermissions, Permission.Action... actions) throws ServiceException {
@@ -567,7 +574,9 @@ public class AccessControlUtil {
    * @param userShortName the short name of the user to revoke permissions
    * @param actions the permissions to be revoked
    * @throws ServiceException on failure
+   * @deprecated Use {@link Admin#grant(UserPermission, boolean)} instead.
    */
+  @Deprecated
   public static void revoke(RpcController controller,
       AccessControlService.BlockingInterface protocol, String userShortName,
       Permission.Action... actions) throws ServiceException {
@@ -595,7 +604,9 @@ public class AccessControlUtil {
    * @param q optional qualifier
    * @param actions the permissions to be revoked
    * @throws ServiceException on failure
+   * @deprecated Use {@link Admin#grant(UserPermission, boolean)} instead.
    */
+  @Deprecated
   public static void revoke(RpcController controller,
       AccessControlService.BlockingInterface protocol, String userShortName, TableName tableName,
       byte[] f, byte[] q, Permission.Action... actions) throws ServiceException {
@@ -620,7 +631,9 @@ public class AccessControlUtil {
    * @param namespace optional table name
    * @param actions the permissions to be revoked
    * @throws ServiceException on failure
+   * @deprecated Use {@link Admin#grant(UserPermission, boolean)} instead.
    */
+  @Deprecated
   public static void revoke(RpcController controller,
       AccessControlService.BlockingInterface protocol, String userShortName, String namespace,
       Permission.Action... actions) throws ServiceException {
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/access/ShadedAccessControlUtil.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/access/ShadedAccessControlUtil.java
index 7e36656..67fdba3 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/access/ShadedAccessControlUtil.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/access/ShadedAccessControlUtil.java
@@ -18,20 +18,21 @@
 
 package org.apache.hadoop.hbase.security.access;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.security.access.Permission.Action;
+import org.apache.yetus.audience.InterfaceAudience;
 
 import org.apache.hbase.thirdparty.com.google.common.collect.ArrayListMultimap;
 import org.apache.hbase.thirdparty.com.google.common.collect.ListMultimap;
-import org.apache.hadoop.hbase.TableName;
-import org.apache.yetus.audience.InterfaceAudience;
-import org.apache.hadoop.hbase.security.access.Permission.Action;
+import org.apache.hbase.thirdparty.com.google.protobuf.ByteString;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.GrantRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.RevokeRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos;
-import org.apache.hbase.thirdparty.com.google.protobuf.ByteString;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
 
 /**
  * Convert protobuf objects in AccessControl.proto under hbase-protocol-shaded to user-oriented
@@ -49,20 +50,18 @@ public class ShadedAccessControlUtil {
   /**
    * Convert a client user permission to a user permission shaded proto.
    */
-  public static
-      org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.Permission.Action
-      toPermissionAction(Permission.Action action) {
+  public static AccessControlProtos.Permission.Action toPermissionAction(Permission.Action action) {
     switch (action) {
-    case READ:
-      return org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.Permission.Action.READ;
-    case WRITE:
-      return org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.Permission.Action.WRITE;
-    case EXEC:
-      return org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.Permission.Action.EXEC;
-    case CREATE:
-      return org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.Permission.Action.CREATE;
-    case ADMIN:
-      return org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.Permission.Action.ADMIN;
+      case READ:
+        return AccessControlProtos.Permission.Action.READ;
+      case WRITE:
+        return AccessControlProtos.Permission.Action.WRITE;
+      case EXEC:
+        return AccessControlProtos.Permission.Action.EXEC;
+      case CREATE:
+        return AccessControlProtos.Permission.Action.CREATE;
+      case ADMIN:
+        return AccessControlProtos.Permission.Action.ADMIN;
     }
     throw new IllegalArgumentException("Unknown action value " + action.name());
   }
@@ -70,8 +69,7 @@ public class ShadedAccessControlUtil {
   /**
    * Convert a Permission.Action shaded proto to a client Permission.Action object.
    */
-  public static Permission.Action toPermissionAction(
-      org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.Permission.Action action) {
+  public static Permission.Action toPermissionAction(AccessControlProtos.Permission.Action action) {
     switch (action) {
     case READ:
       return Permission.Action.READ;
@@ -94,9 +92,9 @@ public class ShadedAccessControlUtil {
    * @return the converted list of Actions
    */
   public static List<Permission.Action> toPermissionActions(
-      List<org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.Permission.Action> protoActions) {
+      List<AccessControlProtos.Permission.Action> protoActions) {
     List<Permission.Action> actions = new ArrayList<>(protoActions.size());
-    for (org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.Permission.Action a : protoActions) {
+    for (AccessControlProtos.Permission.Action a : protoActions) {
       actions.add(toPermissionAction(a));
     }
     return actions;
@@ -163,20 +161,15 @@ public class ShadedAccessControlUtil {
    * @param perm the client Permission
    * @return the protobuf Permission
    */
-  public static org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.Permission
-      toPermission(Permission perm) {
-    org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.Permission.Builder ret =
-        org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.Permission
-            .newBuilder();
+  public static AccessControlProtos.Permission toPermission(Permission perm) {
+    AccessControlProtos.Permission.Builder ret = AccessControlProtos.Permission.newBuilder();
     if (perm instanceof NamespacePermission) {
       NamespacePermission nsPerm = (NamespacePermission) perm;
-      ret.setType(
-        org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.Permission.Type.Namespace);
-      org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.NamespacePermission.Builder builder =
-        org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.NamespacePermission
-          .newBuilder();
+      ret.setType(AccessControlProtos.Permission.Type.Namespace);
+      AccessControlProtos.NamespacePermission.Builder builder =
+          AccessControlProtos.NamespacePermission.newBuilder();
       builder.setNamespaceName(org.apache.hbase.thirdparty.com.google.protobuf.ByteString
-        .copyFromUtf8(nsPerm.getNamespace()));
+          .copyFromUtf8(nsPerm.getNamespace()));
       Permission.Action[] actions = perm.getActions();
       if (actions != null) {
         for (Permission.Action a : actions) {
@@ -186,11 +179,9 @@ public class ShadedAccessControlUtil {
       ret.setNamespacePermission(builder);
     } else if (perm instanceof TablePermission) {
       TablePermission tablePerm = (TablePermission) perm;
-      ret.setType(
-        org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.Permission.Type.Table);
-      org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.TablePermission.Builder builder =
-        org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.TablePermission
-          .newBuilder();
+      ret.setType(AccessControlProtos.Permission.Type.Table);
+      AccessControlProtos.TablePermission.Builder builder =
+          AccessControlProtos.TablePermission.newBuilder();
       builder.setTableName(toProtoTableName(tablePerm.getTableName()));
       if (tablePerm.hasFamily()) {
         builder.setFamily(ByteString.copyFrom(tablePerm.getFamily()));
@@ -207,11 +198,9 @@ public class ShadedAccessControlUtil {
       ret.setTablePermission(builder);
     } else {
       // perm.getAccessScope() == Permission.Scope.GLOBAL
-      ret.setType(
-        org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.Permission.Type.Global);
-      org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.GlobalPermission.Builder builder =
-        org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.GlobalPermission
-          .newBuilder();
+      ret.setType(AccessControlProtos.Permission.Type.Global);
+      AccessControlProtos.GlobalPermission.Builder builder =
+          AccessControlProtos.GlobalPermission.newBuilder();
       Permission.Action[] actions = perm.getActions();
       if (actions != null) {
         for (Permission.Action a : actions) {
@@ -230,9 +219,9 @@ public class ShadedAccessControlUtil {
    * @return the converted UserPermission
    */
   public static ListMultimap<String, Permission> toUserTablePermissions(
-      org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.UsersAndPermissions proto) {
+      AccessControlProtos.UsersAndPermissions proto) {
     ListMultimap<String, Permission> perms = ArrayListMultimap.create();
-    org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.UsersAndPermissions.UserPermissions userPerm;
+    AccessControlProtos.UsersAndPermissions.UserPermissions userPerm;
     for (int i = 0; i < proto.getUserPermissionsCount(); i++) {
       userPerm = proto.getUserPermissions(i);
       for (int j = 0; j < userPerm.getPermissionsCount(); j++) {
@@ -249,16 +238,13 @@ public class ShadedAccessControlUtil {
    * @param perm the list of user and table permissions
    * @return the protobuf UserTablePermissions
    */
-  public static
-      org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.UsersAndPermissions
+  public static AccessControlProtos.UsersAndPermissions
       toUserTablePermissions(ListMultimap<String, UserPermission> perm) {
-    org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.UsersAndPermissions.Builder builder =
-        org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.UsersAndPermissions
-            .newBuilder();
+    AccessControlProtos.UsersAndPermissions.Builder builder =
+        AccessControlProtos.UsersAndPermissions.newBuilder();
     for (Map.Entry<String, Collection<UserPermission>> entry : perm.asMap().entrySet()) {
-      org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.UsersAndPermissions.UserPermissions.Builder userPermBuilder =
-          org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.UsersAndPermissions.UserPermissions
-              .newBuilder();
+      AccessControlProtos.UsersAndPermissions.UserPermissions.Builder userPermBuilder =
+          AccessControlProtos.UsersAndPermissions.UserPermissions.newBuilder();
       userPermBuilder.setUser(ByteString.copyFromUtf8(entry.getKey()));
       for (UserPermission userPerm : entry.getValue()) {
         userPermBuilder.addPermissions(toPermission(userPerm.getPermission()));
@@ -267,4 +253,34 @@ public class ShadedAccessControlUtil {
     }
     return builder.build();
   }
+
+  /**
+   * Converts a user permission proto to a client user permission object.
+   * @param proto the protobuf UserPermission
+   * @return the converted UserPermission
+   */
+  public static UserPermission toUserPermission(AccessControlProtos.UserPermission proto) {
+    return new UserPermission(proto.getUser().toStringUtf8(), toPermission(proto.getPermission()));
+  }
+
+  /**
+   * Convert a client user permission to a user permission proto
+   * @param perm the client UserPermission
+   * @return the protobuf UserPermission
+   */
+  public static AccessControlProtos.UserPermission toUserPermission(UserPermission perm) {
+    return AccessControlProtos.UserPermission.newBuilder()
+        .setUser(ByteString.copyFromUtf8(perm.getUser()))
+        .setPermission(toPermission(perm.getPermission())).build();
+  }
+
+  public static GrantRequest buildGrantRequest(UserPermission userPermission,
+      boolean mergeExistingPermissions) {
+    return GrantRequest.newBuilder().setUserPermission(toUserPermission(userPermission))
+        .setMergeExistingPermissions(mergeExistingPermissions).build();
+  }
+
+  public static RevokeRequest buildRevokeRequest(UserPermission userPermission) {
+    return RevokeRequest.newBuilder().setUserPermission(toUserPermission(userPermission)).build();
+  }
 }
diff --git a/hbase-protocol-shaded/src/main/protobuf/Master.proto b/hbase-protocol-shaded/src/main/protobuf/Master.proto
index 29a10cc..84ae78e 100644
--- a/hbase-protocol-shaded/src/main/protobuf/Master.proto
+++ b/hbase-protocol-shaded/src/main/protobuf/Master.proto
@@ -36,6 +36,7 @@ import "Procedure.proto";
 import "Quota.proto";
 import "Replication.proto";
 import "Snapshot.proto";
+import "AccessControl.proto";
 
 /* Column-level protobufs */
 
@@ -1010,6 +1011,10 @@ service MasterService {
   /** Get if is rpc throttled enabled */
   rpc IsRpcThrottleEnabled (IsRpcThrottleEnabledRequest)
     returns (IsRpcThrottleEnabledResponse);
+
+  rpc Grant(GrantRequest) returns (GrantResponse);
+
+  rpc Revoke(RevokeRequest) returns (RevokeResponse);
 }
 
 // HBCK Service definitions.
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/MasterObserver.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/MasterObserver.java
index de50e04..5abbfc8 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/MasterObserver.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/MasterObserver.java
@@ -36,6 +36,7 @@ import org.apache.hadoop.hbase.master.RegionPlan;
 import org.apache.hadoop.hbase.net.Address;
 import org.apache.hadoop.hbase.quotas.GlobalQuotaSettings;
 import org.apache.hadoop.hbase.replication.ReplicationPeerConfig;
+import org.apache.hadoop.hbase.security.access.UserPermission;
 import org.apache.yetus.audience.InterfaceAudience;
 import org.apache.yetus.audience.InterfaceStability;
 
@@ -1486,4 +1487,42 @@ public interface MasterObserver {
   default void postIsRpcThrottleEnabled(final ObserverContext<MasterCoprocessorEnvironment> ctx,
       final boolean rpcThrottleEnabled) throws IOException {
   }
+
+  /**
+   * Called before granting user permissions.
+   * @param ctx the coprocessor instance's environment
+   * @param userPermission the user and permissions
+   * @param mergeExistingPermissions True if merge with previous granted permissions
+   */
+  default void preGrant(ObserverContext<MasterCoprocessorEnvironment> ctx,
+      UserPermission userPermission, boolean mergeExistingPermissions) throws IOException {
+  }
+
+  /**
+   * Called after granting user permissions.
+   * @param ctx the coprocessor instance's environment
+   * @param userPermission the user and permissions
+   * @param mergeExistingPermissions True if merge with previous granted permissions
+   */
+  default void postGrant(ObserverContext<MasterCoprocessorEnvironment> ctx,
+      UserPermission userPermission, boolean mergeExistingPermissions) throws IOException {
+  }
+
+  /**
+   * Called before revoking user permissions.
+   * @param ctx the coprocessor instance's environment
+   * @param userPermission the user and permissions
+   */
+  default void preRevoke(ObserverContext<MasterCoprocessorEnvironment> ctx,
+      UserPermission userPermission) throws IOException {
+  }
+
+  /**
+   * Called after revoking user permissions.
+   * @param ctx the coprocessor instance's environment
+   * @param userPermission the user and permissions
+   */
+  default void postRevoke(ObserverContext<MasterCoprocessorEnvironment> ctx,
+      UserPermission userPermission) throws IOException {
+  }
 }
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterCoprocessorHost.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterCoprocessorHost.java
index 7f2faf6..63eab79 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterCoprocessorHost.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterCoprocessorHost.java
@@ -57,6 +57,7 @@ import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
 import org.apache.hadoop.hbase.quotas.GlobalQuotaSettings;
 import org.apache.hadoop.hbase.replication.ReplicationPeerConfig;
 import org.apache.hadoop.hbase.security.User;
+import org.apache.hadoop.hbase.security.access.UserPermission;
 import org.apache.yetus.audience.InterfaceAudience;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -1740,4 +1741,42 @@ public class MasterCoprocessorHost
       }
     });
   }
+
+  public void preGrant(UserPermission userPermission, boolean mergeExistingPermissions)
+      throws IOException {
+    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
+      @Override
+      public void call(MasterObserver observer) throws IOException {
+        observer.preGrant(this, userPermission, mergeExistingPermissions);
+      }
+    });
+  }
+
+  public void postGrant(UserPermission userPermission, boolean mergeExistingPermissions)
+      throws IOException {
+    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
+      @Override
+      public void call(MasterObserver observer) throws IOException {
+        observer.postGrant(this, userPermission, mergeExistingPermissions);
+      }
+    });
+  }
+
+  public void preRevoke(UserPermission userPermission) throws IOException {
+    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
+      @Override
+      public void call(MasterObserver observer) throws IOException {
+        observer.preRevoke(this, userPermission);
+      }
+    });
+  }
+
+  public void postRevoke(UserPermission userPermission) throws IOException {
+    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
+      @Override
+      public void call(MasterObserver observer) throws IOException {
+        observer.postRevoke(this, userPermission);
+      }
+    });
+  }
 }
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java
index 4e22df5..e579084 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java
@@ -49,6 +49,7 @@ import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
 import org.apache.hadoop.hbase.client.MasterSwitchType;
 import org.apache.hadoop.hbase.client.RegionInfo;
 import org.apache.hadoop.hbase.client.RegionInfoBuilder;
+import org.apache.hadoop.hbase.client.Table;
 import org.apache.hadoop.hbase.client.TableDescriptor;
 import org.apache.hadoop.hbase.client.TableState;
 import org.apache.hadoop.hbase.client.VersionInfoUtil;
@@ -92,8 +93,11 @@ import org.apache.hadoop.hbase.replication.ReplicationPeerConfig;
 import org.apache.hadoop.hbase.replication.ReplicationPeerDescription;
 import org.apache.hadoop.hbase.security.User;
 import org.apache.hadoop.hbase.security.access.AccessChecker;
+import org.apache.hadoop.hbase.security.access.AccessControlLists;
 import org.apache.hadoop.hbase.security.access.AccessController;
 import org.apache.hadoop.hbase.security.access.Permission;
+import org.apache.hadoop.hbase.security.access.ShadedAccessControlUtil;
+import org.apache.hadoop.hbase.security.access.UserPermission;
 import org.apache.hadoop.hbase.security.visibility.VisibilityController;
 import org.apache.hadoop.hbase.snapshot.ClientSnapshotDescriptionUtils;
 import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils;
@@ -114,6 +118,10 @@ import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations;
 
 import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
 import org.apache.hadoop.hbase.shaded.protobuf.ResponseConverter;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.GrantRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.GrantResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.RevokeRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.AccessControlProtos.RevokeResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.CompactRegionRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.CompactRegionResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.GetRegionInfoRequest;
@@ -2468,6 +2476,50 @@ public class MasterRpcServices extends RSRpcServices
     }
   }
 
+  @Override
+  public GrantResponse grant(RpcController controller, GrantRequest request)
+      throws ServiceException {
+    try {
+      final UserPermission perm =
+          ShadedAccessControlUtil.toUserPermission(request.getUserPermission());
+      boolean mergeExistingPermissions = request.getMergeExistingPermissions();
+      if (master.cpHost != null) {
+        master.cpHost.preGrant(perm, mergeExistingPermissions);
+      }
+      try (Table table = master.getConnection().getTable(AccessControlLists.ACL_TABLE_NAME)) {
+        AccessControlLists.addUserPermission(getConfiguration(), perm, table,
+          mergeExistingPermissions);
+      }
+      if (master.cpHost != null) {
+        master.cpHost.postGrant(perm, mergeExistingPermissions);
+      }
+      return GrantResponse.getDefaultInstance();
+    } catch (IOException ioe) {
+      throw new ServiceException(ioe);
+    }
+  }
+
+  @Override
+  public RevokeResponse revoke(RpcController controller, RevokeRequest request)
+      throws ServiceException {
+    try {
+      final UserPermission userPermission =
+          ShadedAccessControlUtil.toUserPermission(request.getUserPermission());
+      if (master.cpHost != null) {
+        master.cpHost.preRevoke(userPermission);
+      }
+      try (Table table = master.getConnection().getTable(AccessControlLists.ACL_TABLE_NAME)) {
+        AccessControlLists.removeUserPermission(master.getConfiguration(), userPermission, table);
+      }
+      if (master.cpHost != null) {
+        master.cpHost.postRevoke(userPermission);
+      }
+      return RevokeResponse.getDefaultInstance();
+    } catch (IOException ioe) {
+      throw new ServiceException(ioe);
+    }
+  }
+
   private boolean containMetaWals(ServerName serverName) throws IOException {
     Path logDir = new Path(master.getWALRootDir(),
         AbstractFSWALProvider.getWALDirectoryName(serverName.toString()));
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlLists.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlLists.java
index 34480d3..7560a93 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlLists.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlLists.java
@@ -130,7 +130,7 @@ public class AccessControlLists {
    * @param t acl table instance. It is closed upon method return.
    * @throws IOException in the case of an error accessing the metadata table
    */
-  static void addUserPermission(Configuration conf, UserPermission userPerm, Table t,
+  public static void addUserPermission(Configuration conf, UserPermission userPerm, Table t,
       boolean mergeExistingPermissions) throws IOException {
     Permission permission = userPerm.getPermission();
     Permission.Action[] actions = permission.getActions();
@@ -222,7 +222,7 @@ public class AccessControlLists {
    * @param t acl table
    * @throws IOException if there is an error accessing the metadata table
    */
-  static void removeUserPermission(Configuration conf, UserPermission userPerm, Table t)
+  public static void removeUserPermission(Configuration conf, UserPermission userPerm, Table t)
       throws IOException {
     if (null == userPerm.getPermission().getActions() ||
         userPerm.getPermission().getActions().length == 0) {
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java
index a105e4b..6d7158d 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java
@@ -2053,6 +2053,10 @@ public class AccessController implements MasterCoprocessor, RegionCoprocessor,
 
   /* ---- Protobuf AccessControlService implementation ---- */
 
+  /**
+   * @deprecated Use {@link Admin#grant(UserPermission, boolean)} instead.
+   */
+  @Deprecated
   @Override
   public void grant(RpcController controller,
       AccessControlProtos.GrantRequest request,
@@ -2070,36 +2074,10 @@ public class AccessController implements MasterCoprocessor, RegionCoprocessor,
           LOG.debug("Received request from {} to grant access permission {}",
             caller.getName(), perm.toString());
         }
+        preGrantOrRevoke(caller, "grant", perm);
 
-        switch(request.getUserPermission().getPermission().getType()) {
-          case Global :
-            accessChecker.requireGlobalPermission(caller, "grant", Action.ADMIN, "");
-            break;
-          case Table :
-            TablePermission tablePerm = (TablePermission) perm.getPermission();
-            accessChecker.requirePermission(caller, "grant", tablePerm.getTableName(),
-              tablePerm.getFamily(), tablePerm.getQualifier(), null, Action.ADMIN);
-            break;
-          case Namespace :
-            NamespacePermission namespacePer = (NamespacePermission) perm.getPermission();
-            accessChecker.requireNamespacePermission(caller, "grant", namespacePer.getNamespace(),
-                null, Action.ADMIN);
-           break;
-        }
-
-        User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
-          @Override
-          public Void run() throws Exception {
-            // regionEnv is set at #start. Hopefully not null at this point.
-            try (Table table = regionEnv.getConnection().
-                getTable(AccessControlLists.ACL_TABLE_NAME)) {
-              AccessControlLists.addUserPermission(regionEnv.getConfiguration(), perm, table,
-                  request.getMergeExistingPermissions());
-            }
-            return null;
-          }
-        });
-
+        // regionEnv is set at #start. Hopefully not null at this point.
+        regionEnv.getConnection().getAdmin().grant(perm, request.getMergeExistingPermissions());
         if (AUDITLOG.isTraceEnabled()) {
           // audit log should store permission changes in addition to auth results
           AUDITLOG.trace("Granted permission " + perm.toString());
@@ -2116,9 +2094,12 @@ public class AccessController implements MasterCoprocessor, RegionCoprocessor,
     done.run(response);
   }
 
+  /**
+   * @deprecated Use {@link Admin#revoke(UserPermission)} instead.
+   */
+  @Deprecated
   @Override
-  public void revoke(RpcController controller,
-      AccessControlProtos.RevokeRequest request,
+  public void revoke(RpcController controller, AccessControlProtos.RevokeRequest request,
       RpcCallback<AccessControlProtos.RevokeResponse> done) {
     final UserPermission perm = AccessControlUtil.toUserPermission(request.getUserPermission());
     AccessControlProtos.RevokeResponse response = null;
@@ -2133,35 +2114,9 @@ public class AccessController implements MasterCoprocessor, RegionCoprocessor,
           LOG.debug("Received request from {} to revoke access permission {}",
             caller.getShortName(), perm.toString());
         }
-
-        switch(request.getUserPermission().getPermission().getType()) {
-          case Global :
-            accessChecker.requireGlobalPermission(caller, "revoke", Action.ADMIN, "");
-            break;
-          case Table :
-            TablePermission tablePerm = (TablePermission) perm.getPermission();
-            accessChecker.requirePermission(caller, "revoke", tablePerm.getTableName(),
-              tablePerm.getFamily(), tablePerm.getQualifier(), null, Action.ADMIN);
-            break;
-          case Namespace :
-            NamespacePermission namespacePer = (NamespacePermission) perm.getPermission();
-            accessChecker.requireNamespacePermission(caller, "revoke",
-              namespacePer.getNamespace(), null, Action.ADMIN);
-            break;
-        }
-
-        User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
-          @Override
-          public Void run() throws Exception {
-            // regionEnv is set at #start. Hopefully not null here.
-            try (Table table = regionEnv.getConnection().
-                getTable(AccessControlLists.ACL_TABLE_NAME)) {
-              AccessControlLists.removeUserPermission(regionEnv.getConfiguration(), perm, table);
-            }
-            return null;
-          }
-        });
-
+        preGrantOrRevoke(caller, "revoke", perm);
+        // regionEnv is set at #start. Hopefully not null here.
+        regionEnv.getConnection().getAdmin().revoke(perm);
         if (AUDITLOG.isTraceEnabled()) {
           // audit log should record all permission changes
           AUDITLOG.trace("Revoked permission " + perm.toString());
@@ -2674,4 +2629,36 @@ public class AccessController implements MasterCoprocessor, RegionCoprocessor,
     }
     done.run(response);
   }
+
+  @Override
+  public void preGrant(ObserverContext<MasterCoprocessorEnvironment> ctx,
+      UserPermission userPermission, boolean mergeExistingPermissions) throws IOException {
+    preGrantOrRevoke(getActiveUser(ctx), "grant", userPermission);
+  }
+
+  @Override
+  public void preRevoke(ObserverContext<MasterCoprocessorEnvironment> ctx,
+      UserPermission userPermission) throws IOException {
+    preGrantOrRevoke(getActiveUser(ctx), "revoke", userPermission);
+  }
+
+  private void preGrantOrRevoke(User caller, String request, UserPermission userPermission)
+      throws IOException {
+    switch (userPermission.getPermission().scope) {
+      case GLOBAL:
+        accessChecker.requireGlobalPermission(caller, request, Action.ADMIN, "");
+        break;
+      case NAMESPACE:
+        NamespacePermission namespacePerm = (NamespacePermission) userPermission.getPermission();
+        accessChecker.requireNamespacePermission(caller, request, namespacePerm.getNamespace(),
+          null, Action.ADMIN);
+        break;
+      case TABLE:
+        TablePermission tablePerm = (TablePermission) userPermission.getPermission();
+        accessChecker.requirePermission(caller, request, tablePerm.getTableName(),
+          tablePerm.getFamily(), tablePerm.getQualifier(), null, Action.ADMIN);
+        break;
+      default:
+    }
+  }
 }
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/SecureTestUtil.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/SecureTestUtil.java
index 3655352..e392b3b 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/SecureTestUtil.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/SecureTestUtil.java
@@ -34,7 +34,6 @@ import com.google.protobuf.ServiceException;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.Coprocessor;
 import org.apache.hadoop.hbase.HBaseTestingUtility;
-import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.MiniHBaseCluster;
 import org.apache.hadoop.hbase.NamespaceDescriptor;
 import org.apache.hadoop.hbase.TableName;
@@ -375,12 +374,8 @@ public class SecureTestUtil {
       @Override
       public Void call() throws Exception {
         try (Connection connection = ConnectionFactory.createConnection(util.getConfiguration())) {
-          try (Table acl = connection.getTable(AccessControlLists.ACL_TABLE_NAME)) {
-            BlockingRpcChannel service = acl.coprocessorService(HConstants.EMPTY_START_ROW);
-            AccessControlService.BlockingInterface protocol =
-                AccessControlService.newBlockingStub(service);
-            AccessControlUtil.grant(null, protocol, user, false, actions);
-          }
+          connection.getAdmin().grant(new UserPermission(user, new GlobalPermission(actions)),
+            false);
         }
         return null;
       }
@@ -398,12 +393,7 @@ public class SecureTestUtil {
       @Override
       public Void call() throws Exception {
         try (Connection connection = ConnectionFactory.createConnection(util.getConfiguration())) {
-          try (Table acl = connection.getTable(AccessControlLists.ACL_TABLE_NAME)) {
-            BlockingRpcChannel service = acl.coprocessorService(HConstants.EMPTY_START_ROW);
-            AccessControlService.BlockingInterface protocol =
-                AccessControlService.newBlockingStub(service);
-            AccessControlUtil.revoke(null, protocol, user, actions);
-          }
+          connection.getAdmin().revoke(new UserPermission(user, new GlobalPermission(actions)));
         }
         return null;
       }
@@ -421,12 +411,8 @@ public class SecureTestUtil {
       @Override
       public Void call() throws Exception {
         try (Connection connection = ConnectionFactory.createConnection(util.getConfiguration())) {
-          try (Table acl = connection.getTable(AccessControlLists.ACL_TABLE_NAME)) {
-            BlockingRpcChannel service = acl.coprocessorService(HConstants.EMPTY_START_ROW);
-            AccessControlService.BlockingInterface protocol =
-                AccessControlService.newBlockingStub(service);
-            AccessControlUtil.grant(null, protocol, user, namespace, false, actions);
-          }
+          connection.getAdmin()
+              .grant(new UserPermission(user, new NamespacePermission(namespace, actions)), false);
         }
         return null;
       }
@@ -486,12 +472,8 @@ public class SecureTestUtil {
       @Override
       public Void call() throws Exception {
         try (Connection connection = ConnectionFactory.createConnection(util.getConfiguration())) {
-          try (Table acl = connection.getTable(AccessControlLists.ACL_TABLE_NAME)) {
-            BlockingRpcChannel service = acl.coprocessorService(HConstants.EMPTY_START_ROW);
-            AccessControlService.BlockingInterface protocol =
-                AccessControlService.newBlockingStub(service);
-            AccessControlUtil.revoke(null, protocol, user, namespace, actions);
-          }
+          connection.getAdmin()
+              .revoke(new UserPermission(user, new NamespacePermission(namespace, actions)));
         }
         return null;
       }
@@ -510,12 +492,9 @@ public class SecureTestUtil {
       @Override
       public Void call() throws Exception {
         try (Connection connection = ConnectionFactory.createConnection(util.getConfiguration())) {
-          try (Table acl = connection.getTable(AccessControlLists.ACL_TABLE_NAME)) {
-            BlockingRpcChannel service = acl.coprocessorService(HConstants.EMPTY_START_ROW);
-            AccessControlService.BlockingInterface protocol =
-                AccessControlService.newBlockingStub(service);
-            AccessControlUtil.grant(null, protocol, user, table, family, qualifier, false, actions);
-          }
+          connection.getAdmin().grant(
+            new UserPermission(user, new TablePermission(table, family, qualifier, actions)),
+            false);
         }
         return null;
       }
@@ -576,12 +555,8 @@ public class SecureTestUtil {
       @Override
       public Void call() throws Exception {
         try (Connection connection = ConnectionFactory.createConnection(util.getConfiguration())) {
-          try (Table acl = connection.getTable(AccessControlLists.ACL_TABLE_NAME)) {
-            BlockingRpcChannel service = acl.coprocessorService(HConstants.EMPTY_START_ROW);
-            AccessControlService.BlockingInterface protocol =
-                AccessControlService.newBlockingStub(service);
-            AccessControlUtil.revoke(null, protocol, user, table, family, qualifier, actions);
-          }
+          connection.getAdmin().revoke(
+            new UserPermission(user, new TablePermission(table, family, qualifier, actions)));
         }
         return null;
       }
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java
index 3d4f039..4d35fb9 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java
@@ -1167,13 +1167,10 @@ public class TestAccessController extends SecureTestUtil {
     AccessTestAction grantAction = new AccessTestAction() {
       @Override
       public Object run() throws Exception {
-        try(Connection conn = ConnectionFactory.createConnection(conf);
-            Table acl = conn.getTable(AccessControlLists.ACL_TABLE_NAME)) {
-          BlockingRpcChannel service = acl.coprocessorService(TEST_TABLE.getName());
-          AccessControlService.BlockingInterface protocol =
-            AccessControlService.newBlockingStub(service);
-          AccessControlUtil.grant(null, protocol, USER_RO.getShortName(), TEST_TABLE, TEST_FAMILY,
-              null, false, Action.READ);
+        try (Connection conn = ConnectionFactory.createConnection(conf)) {
+          conn.getAdmin().grant(new UserPermission(USER_RO.getShortName(),
+              new TablePermission(TEST_TABLE, TEST_FAMILY, Action.READ)),
+            false);
         }
         return null;
       }
@@ -1182,13 +1179,9 @@ public class TestAccessController extends SecureTestUtil {
     AccessTestAction revokeAction = new AccessTestAction() {
       @Override
       public Object run() throws Exception {
-        try(Connection conn = ConnectionFactory.createConnection(conf);
-            Table acl = conn.getTable(AccessControlLists.ACL_TABLE_NAME)) {
-          BlockingRpcChannel service = acl.coprocessorService(TEST_TABLE.getName());
-          AccessControlService.BlockingInterface protocol =
-            AccessControlService.newBlockingStub(service);
-          AccessControlUtil.revoke(null, protocol, USER_RO.getShortName(), TEST_TABLE, TEST_FAMILY, null,
-            Action.READ);
+        try(Connection conn = ConnectionFactory.createConnection(conf)) {
+            conn.getAdmin().revoke(new UserPermission(USER_RO.getShortName(),
+                            new TablePermission(TEST_TABLE, TEST_FAMILY, Action.READ)));
         }
         return null;
       }
@@ -1222,6 +1215,57 @@ public class TestAccessController extends SecureTestUtil {
       }
     };
 
+    AccessTestAction preGrantAction = new AccessTestAction() {
+      @Override
+      public Object run() throws Exception {
+        ACCESS_CONTROLLER.preGrant(ObserverContextImpl.createAndPrepare(CP_ENV),
+          new UserPermission(USER_RO.getShortName(),
+              new TablePermission(TEST_TABLE, TEST_FAMILY, Action.READ)),
+          false);
+        return null;
+      }
+    };
+
+    AccessTestAction preRevokeAction = new AccessTestAction() {
+      @Override
+      public Object run() throws Exception {
+        ACCESS_CONTROLLER.preRevoke(ObserverContextImpl.createAndPrepare(CP_ENV),
+          new UserPermission(USER_RO.getShortName(),
+              new TablePermission(TEST_TABLE, TEST_FAMILY, Action.READ)));
+        return null;
+      }
+    };
+
+    AccessTestAction grantCPAction = new AccessTestAction() {
+      @Override
+      public Object run() throws Exception {
+        try (Connection conn = ConnectionFactory.createConnection(conf);
+            Table acl = conn.getTable(AccessControlLists.ACL_TABLE_NAME)) {
+          BlockingRpcChannel service = acl.coprocessorService(TEST_TABLE.getName());
+          AccessControlService.BlockingInterface protocol =
+              AccessControlService.newBlockingStub(service);
+          AccessControlUtil.grant(null, protocol, USER_RO.getShortName(), TEST_TABLE, TEST_FAMILY,
+            null, false, Action.READ);
+        }
+        return null;
+      }
+    };
+
+    AccessTestAction revokeCPAction = new AccessTestAction() {
+      @Override
+      public Object run() throws Exception {
+        try (Connection conn = ConnectionFactory.createConnection(conf);
+            Table acl = conn.getTable(AccessControlLists.ACL_TABLE_NAME)) {
+          BlockingRpcChannel service = acl.coprocessorService(TEST_TABLE.getName());
+          AccessControlService.BlockingInterface protocol =
+              AccessControlService.newBlockingStub(service);
+          AccessControlUtil.revoke(null, protocol, USER_RO.getShortName(), TEST_TABLE, TEST_FAMILY,
+            null, Action.READ);
+        }
+        return null;
+      }
+    };
+
     verifyAllowed(grantAction, SUPERUSER, USER_ADMIN, USER_OWNER, USER_GROUP_ADMIN);
     verifyDenied(grantAction, USER_CREATE, USER_RW, USER_RO, USER_NONE, USER_GROUP_READ,
         USER_GROUP_WRITE, USER_GROUP_CREATE);
@@ -1237,6 +1281,22 @@ public class TestAccessController extends SecureTestUtil {
       verifyAllowed(getGlobalPermissionsAction, SUPERUSER, USER_ADMIN, USER_GROUP_ADMIN);
       verifyDenied(getGlobalPermissionsAction, USER_CREATE, USER_OWNER, USER_RW, USER_RO,
         USER_NONE, USER_GROUP_READ, USER_GROUP_WRITE, USER_GROUP_CREATE);
+
+      verifyAllowed(preGrantAction, SUPERUSER, USER_ADMIN, USER_OWNER, USER_GROUP_ADMIN);
+      verifyDenied(preGrantAction, USER_CREATE, USER_RW, USER_RO, USER_NONE, USER_GROUP_READ,
+        USER_GROUP_WRITE, USER_GROUP_CREATE);
+
+      verifyAllowed(preRevokeAction, SUPERUSER, USER_ADMIN, USER_OWNER, USER_GROUP_ADMIN);
+      verifyDenied(preRevokeAction, USER_CREATE, USER_RW, USER_RO, USER_NONE, USER_GROUP_READ,
+        USER_GROUP_WRITE, USER_GROUP_CREATE);
+
+      verifyAllowed(grantCPAction, SUPERUSER, USER_ADMIN, USER_OWNER, USER_GROUP_ADMIN);
+      verifyDenied(grantCPAction, USER_CREATE, USER_RW, USER_RO, USER_NONE, USER_GROUP_READ,
+        USER_GROUP_WRITE, USER_GROUP_CREATE);
+
+      verifyAllowed(revokeCPAction, SUPERUSER, USER_ADMIN, USER_OWNER, USER_GROUP_ADMIN);
+      verifyDenied(revokeCPAction, USER_CREATE, USER_RW, USER_RO, USER_NONE, USER_GROUP_READ,
+        USER_GROUP_WRITE, USER_GROUP_CREATE);
     } finally {
       // Cleanup, Grant the revoked permission back to the user
       grantOnTable(TEST_UTIL, USER_RO.getShortName(), TEST_TABLE, TEST_FAMILY, null,
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestNamespaceCommands.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestNamespaceCommands.java
index 5872f29..d693032 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestNamespaceCommands.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestNamespaceCommands.java
@@ -355,40 +355,27 @@ public class TestNamespaceCommands extends SecureTestUtil {
   }
 
   @Test
-  public void testGrantRevoke() throws Exception{
+  public void testGrantRevoke() throws Exception {
     final String testUser = "testUser";
-
     // Test if client API actions are authorized
-
     AccessTestAction grantAction = new AccessTestAction() {
       @Override
       public Object run() throws Exception {
-        Connection connection = ConnectionFactory.createConnection(conf);
-        Table acl = connection.getTable(AccessControlLists.ACL_TABLE_NAME);
-        try {
-          BlockingRpcChannel service =
-              acl.coprocessorService(HConstants.EMPTY_START_ROW);
-          AccessControlService.BlockingInterface protocol =
-            AccessControlService.newBlockingStub(service);
-          AccessControlUtil.grant(null, protocol, testUser, TEST_NAMESPACE, false, Action.WRITE);
-        } finally {
-          acl.close();
-          connection.close();
+        try (Connection connection = ConnectionFactory.createConnection(conf)) {
+          connection.getAdmin().grant(
+            new UserPermission(testUser, new NamespacePermission(TEST_NAMESPACE, Action.WRITE)),
+            false);
         }
         return null;
       }
     };
-
     AccessTestAction grantNamespaceAction = new AccessTestAction() {
       @Override
       public Object run() throws Exception {
-        try(Connection conn = ConnectionFactory.createConnection(conf);
-            Table acl = conn.getTable(AccessControlLists.ACL_TABLE_NAME)) {
-          BlockingRpcChannel service = acl.coprocessorService(HConstants.EMPTY_START_ROW);
-          AccessControlService.BlockingInterface protocol =
-            AccessControlService.newBlockingStub(service);
-          AccessControlUtil.grant(null, protocol, USER_GROUP_NS_ADMIN.getShortName(),
-            TEST_NAMESPACE, false, Action.READ);
+        try (Connection conn = ConnectionFactory.createConnection(conf)) {
+          conn.getAdmin().grant(
+            new UserPermission(USER_GROUP_NS_ADMIN.getShortName(), TEST_NAMESPACE, Action.READ),
+            false);
         }
         return null;
       }
@@ -397,37 +384,19 @@ public class TestNamespaceCommands extends SecureTestUtil {
     AccessTestAction revokeAction = new AccessTestAction() {
       @Override
       public Object run() throws Exception {
-        Connection connection = ConnectionFactory.createConnection(conf);
-        Table acl = connection.getTable(AccessControlLists.ACL_TABLE_NAME);
-        try {
-          BlockingRpcChannel service =
-              acl.coprocessorService(HConstants.EMPTY_START_ROW);
-          AccessControlService.BlockingInterface protocol =
-            AccessControlService.newBlockingStub(service);
-          AccessControlUtil.revoke(null, protocol, testUser, TEST_NAMESPACE, Action.WRITE);
-        } finally {
-          acl.close();
-          connection.close();
+        try (Connection connection = ConnectionFactory.createConnection(conf)) {
+          connection.getAdmin().revoke(
+            new UserPermission(testUser, new NamespacePermission(TEST_NAMESPACE, Action.WRITE)));
         }
         return null;
       }
     };
-
     AccessTestAction revokeNamespaceAction = new AccessTestAction() {
       @Override
       public Object run() throws Exception {
-        Connection connection = ConnectionFactory.createConnection(conf);
-        Table acl = connection.getTable(AccessControlLists.ACL_TABLE_NAME);
-        try {
-          BlockingRpcChannel service =
-              acl.coprocessorService(HConstants.EMPTY_START_ROW);
-          AccessControlService.BlockingInterface protocol =
-            AccessControlService.newBlockingStub(service);
-          AccessControlUtil.revoke(null, protocol, USER_GROUP_NS_ADMIN.getShortName(),
-            TEST_NAMESPACE, Action.READ);
-        } finally {
-          acl.close();
-          connection.close();
+        try (Connection connection = ConnectionFactory.createConnection(conf)) {
+          connection.getAdmin().revoke(new UserPermission(USER_GROUP_NS_ADMIN.getShortName(),
+              new NamespacePermission(TEST_NAMESPACE, Action.READ)));
         }
         return null;
       }
@@ -436,16 +405,57 @@ public class TestNamespaceCommands extends SecureTestUtil {
     AccessTestAction getPermissionsAction = new AccessTestAction() {
       @Override
       public Object run() throws Exception {
-        Connection connection = ConnectionFactory.createConnection(conf);
-        Table acl = connection.getTable(AccessControlLists.ACL_TABLE_NAME);
-        try {
+        try (Connection connection = ConnectionFactory.createConnection(conf);
+            Table acl = connection.getTable(AccessControlLists.ACL_TABLE_NAME)) {
           BlockingRpcChannel service = acl.coprocessorService(HConstants.EMPTY_START_ROW);
           AccessControlService.BlockingInterface protocol =
-            AccessControlService.newBlockingStub(service);
+              AccessControlService.newBlockingStub(service);
           AccessControlUtil.getUserPermissions(null, protocol, Bytes.toBytes(TEST_NAMESPACE));
-        } finally {
-          acl.close();
-          connection.close();
+        }
+        return null;
+      }
+    };
+
+    AccessTestAction preGrantAction = new AccessTestAction() {
+      @Override
+      public Object run() throws Exception {
+        ACCESS_CONTROLLER.preGrant(ObserverContextImpl.createAndPrepare(CP_ENV),
+          new UserPermission(testUser, new NamespacePermission(TEST_NAMESPACE, Action.WRITE)),
+          false);
+        return null;
+      }
+    };
+    AccessTestAction preRevokeAction = new AccessTestAction() {
+      @Override
+      public Object run() throws Exception {
+        ACCESS_CONTROLLER.preRevoke(ObserverContextImpl.createAndPrepare(CP_ENV),
+          new UserPermission(testUser, new NamespacePermission(TEST_NAMESPACE, Action.WRITE)));
+        return null;
+      }
+    };
+
+    AccessTestAction grantCPAction = new AccessTestAction() {
+      @Override
+      public Object run() throws Exception {
+        try (Connection connection = ConnectionFactory.createConnection(conf);
+            Table acl = connection.getTable(AccessControlLists.ACL_TABLE_NAME)) {
+          BlockingRpcChannel service = acl.coprocessorService(HConstants.EMPTY_START_ROW);
+          AccessControlService.BlockingInterface protocol =
+              AccessControlService.newBlockingStub(service);
+          AccessControlUtil.grant(null, protocol, testUser, TEST_NAMESPACE, false, Action.WRITE);
+        }
+        return null;
+      }
+    };
+    AccessTestAction revokeCPAction = new AccessTestAction() {
+      @Override
+      public Object run() throws Exception {
+        try (Connection connection = ConnectionFactory.createConnection(conf);
+            Table acl = connection.getTable(AccessControlLists.ACL_TABLE_NAME)) {
+          BlockingRpcChannel service = acl.coprocessorService(HConstants.EMPTY_START_ROW);
+          AccessControlService.BlockingInterface protocol =
+              AccessControlService.newBlockingStub(service);
+          AccessControlUtil.revoke(null, protocol, testUser, TEST_NAMESPACE, Action.WRITE);
         }
         return null;
       }
@@ -455,7 +465,6 @@ public class TestNamespaceCommands extends SecureTestUtil {
     verifyDenied(grantAction, USER_GLOBAL_CREATE, USER_GLOBAL_WRITE, USER_GLOBAL_READ,
       USER_GLOBAL_EXEC, USER_NS_CREATE, USER_NS_WRITE, USER_NS_READ, USER_NS_EXEC,
       USER_TABLE_CREATE, USER_TABLE_WRITE, USER_GROUP_READ, USER_GROUP_WRITE, USER_GROUP_CREATE);
-
     verifyAllowed(grantNamespaceAction, SUPERUSER, USER_GLOBAL_ADMIN, USER_GROUP_ADMIN,
       USER_NS_ADMIN, USER_GROUP_NS_ADMIN);
     verifyDenied(grantNamespaceAction, USER_GLOBAL_CREATE, USER_GLOBAL_WRITE, USER_GLOBAL_READ,
@@ -466,7 +475,6 @@ public class TestNamespaceCommands extends SecureTestUtil {
     verifyDenied(revokeAction, USER_GLOBAL_CREATE, USER_GLOBAL_WRITE, USER_GLOBAL_READ,
       USER_GLOBAL_EXEC, USER_NS_CREATE, USER_NS_WRITE, USER_NS_READ, USER_NS_EXEC,
       USER_TABLE_CREATE, USER_TABLE_WRITE, USER_GROUP_READ, USER_GROUP_WRITE, USER_GROUP_CREATE);
-
     verifyAllowed(revokeNamespaceAction, SUPERUSER, USER_GLOBAL_ADMIN, USER_GROUP_ADMIN,
       USER_NS_ADMIN, USER_GROUP_NS_ADMIN);
     verifyDenied(revokeNamespaceAction, USER_GLOBAL_CREATE, USER_GLOBAL_WRITE, USER_GLOBAL_READ,
@@ -478,6 +486,24 @@ public class TestNamespaceCommands extends SecureTestUtil {
     verifyDenied(getPermissionsAction, USER_GLOBAL_CREATE, USER_GLOBAL_WRITE, USER_GLOBAL_READ,
       USER_GLOBAL_EXEC, USER_NS_CREATE, USER_NS_WRITE, USER_NS_READ, USER_NS_EXEC,
       USER_TABLE_CREATE, USER_TABLE_WRITE, USER_GROUP_READ, USER_GROUP_WRITE, USER_GROUP_CREATE);
+
+    verifyAllowed(preGrantAction, SUPERUSER, USER_GLOBAL_ADMIN, USER_GROUP_ADMIN, USER_NS_ADMIN);
+    verifyDenied(preGrantAction, USER_GLOBAL_CREATE, USER_GLOBAL_WRITE, USER_GLOBAL_READ,
+      USER_GLOBAL_EXEC, USER_NS_CREATE, USER_NS_WRITE, USER_NS_READ, USER_NS_EXEC,
+      USER_TABLE_CREATE, USER_TABLE_WRITE, USER_GROUP_READ, USER_GROUP_WRITE, USER_GROUP_CREATE);
+    verifyAllowed(preRevokeAction, SUPERUSER, USER_GLOBAL_ADMIN, USER_GROUP_ADMIN, USER_NS_ADMIN);
+    verifyDenied(preRevokeAction, USER_GLOBAL_CREATE, USER_GLOBAL_WRITE, USER_GLOBAL_READ,
+      USER_GLOBAL_EXEC, USER_NS_CREATE, USER_NS_WRITE, USER_NS_READ, USER_NS_EXEC,
+      USER_TABLE_CREATE, USER_TABLE_WRITE, USER_GROUP_READ, USER_GROUP_WRITE, USER_GROUP_CREATE);
+
+    verifyAllowed(grantCPAction, SUPERUSER, USER_GLOBAL_ADMIN, USER_GROUP_ADMIN, USER_NS_ADMIN);
+    verifyDenied(grantCPAction, USER_GLOBAL_CREATE, USER_GLOBAL_WRITE, USER_GLOBAL_READ,
+      USER_GLOBAL_EXEC, USER_NS_CREATE, USER_NS_WRITE, USER_NS_READ, USER_NS_EXEC,
+      USER_TABLE_CREATE, USER_TABLE_WRITE, USER_GROUP_READ, USER_GROUP_WRITE, USER_GROUP_CREATE);
+    verifyAllowed(revokeCPAction, SUPERUSER, USER_GLOBAL_ADMIN, USER_GROUP_ADMIN, USER_NS_ADMIN);
+    verifyDenied(revokeCPAction, USER_GLOBAL_CREATE, USER_GLOBAL_WRITE, USER_GLOBAL_READ,
+      USER_GLOBAL_EXEC, USER_NS_CREATE, USER_NS_WRITE, USER_NS_READ, USER_NS_EXEC,
+      USER_TABLE_CREATE, USER_TABLE_WRITE, USER_GROUP_READ, USER_GROUP_WRITE, USER_GROUP_CREATE);
   }
 
   @Test
diff --git a/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java b/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java
index 5a1f4e8..e85c143 100644
--- a/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java
+++ b/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java
@@ -59,6 +59,7 @@ import org.apache.hadoop.hbase.quotas.SpaceQuotaSnapshot;
 import org.apache.hadoop.hbase.replication.ReplicationException;
 import org.apache.hadoop.hbase.replication.ReplicationPeerConfig;
 import org.apache.hadoop.hbase.replication.ReplicationPeerDescription;
+import org.apache.hadoop.hbase.security.access.UserPermission;
 import org.apache.hadoop.hbase.thrift2.ThriftUtilities;
 import org.apache.hadoop.hbase.thrift2.generated.TColumnFamilyDescriptor;
 import org.apache.hadoop.hbase.thrift2.generated.THBaseService;
@@ -1409,4 +1410,14 @@ public class ThriftAdmin implements Admin {
   public SpaceQuotaSnapshot getCurrentSpaceQuotaSnapshot(TableName tableName) throws IOException {
     throw new NotImplementedException("getCurrentSpaceQuotaSnapshot not supported in ThriftAdmin");
   }
+
+  @Override
+  public void grant(UserPermission userPermission, boolean mergeExistingPermissions) {
+    throw new NotImplementedException("grant not supported in ThriftAdmin");
+  }
+
+  @Override
+  public void revoke(UserPermission userPermission) {
+    throw new NotImplementedException("revoke not supported in ThriftAdmin");
+  }
 }