You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hbase.apache.org by el...@apache.org on 2018/03/28 00:28:53 UTC

[3/3] hbase git commit: HBASE-20199 Add a unit test to verify flush and snapshot permission requirements aren't excessive

HBASE-20199 Add a unit test to verify flush and snapshot permission requirements aren't excessive

Signed-off-by: Ted Yu <yu...@gmail.com>
Signed-off-by: Michael Stack <st...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/hbase/repo
Commit: http://git-wip-us.apache.org/repos/asf/hbase/commit/69f5d707
Tree: http://git-wip-us.apache.org/repos/asf/hbase/tree/69f5d707
Diff: http://git-wip-us.apache.org/repos/asf/hbase/diff/69f5d707

Branch: refs/heads/master
Commit: 69f5d707b64081f2c0d6dc7c097a0f5cc7418d05
Parents: 09ed7c7
Author: Josh Elser <el...@apache.org>
Authored: Mon Mar 26 17:52:37 2018 -0400
Committer: Josh Elser <el...@apache.org>
Committed: Tue Mar 27 20:17:08 2018 -0400

----------------------------------------------------------------------
 .../access/TestAdminOnlyOperations.java         | 268 --------------
 .../security/access/TestRpcAccessChecks.java    | 362 +++++++++++++++++++
 2 files changed, 362 insertions(+), 268 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/hbase/blob/69f5d707/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAdminOnlyOperations.java
----------------------------------------------------------------------
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAdminOnlyOperations.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAdminOnlyOperations.java
deleted file mode 100644
index 42d2f36..0000000
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAdminOnlyOperations.java
+++ /dev/null
@@ -1,268 +0,0 @@
-
-/**
- * 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.security.access;
-
-import static org.apache.hadoop.hbase.AuthUtil.toGroupEntry;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
-
-import com.google.protobuf.Service;
-import com.google.protobuf.ServiceException;
-import java.io.IOException;
-import java.security.PrivilegedExceptionAction;
-import java.util.Collections;
-import java.util.HashMap;
-import org.apache.hadoop.conf.Configuration;
-import org.apache.hadoop.hbase.HBaseClassTestRule;
-import org.apache.hadoop.hbase.HBaseTestingUtility;
-import org.apache.hadoop.hbase.ServerName;
-import org.apache.hadoop.hbase.TableName;
-import org.apache.hadoop.hbase.client.Admin;
-import org.apache.hadoop.hbase.client.Connection;
-import org.apache.hadoop.hbase.client.ConnectionFactory;
-import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
-import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor;
-import org.apache.hadoop.hbase.coprocessor.RegionServerCoprocessor;
-import org.apache.hadoop.hbase.ipc.protobuf.generated.TestProtos;
-import org.apache.hadoop.hbase.ipc.protobuf.generated.TestRpcServiceProtos;
-import org.apache.hadoop.hbase.security.AccessDeniedException;
-import org.apache.hadoop.hbase.security.User;
-import org.apache.hadoop.hbase.testclassification.MediumTests;
-import org.apache.hadoop.hbase.testclassification.SecurityTests;
-import org.junit.BeforeClass;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-
-/**
- * This class tests operations in MasterRpcServices which require ADMIN access.
- * It doesn't test all operations which require ADMIN access, only those which get vetted within
- * MasterRpcServices at the point of entry itself (unlike old approach of using
- * hooks in AccessController).
- *
- * Sidenote:
- * There is one big difference between how security tests for AccessController hooks work, and how
- * the tests in this class for security in MasterRpcServices work.
- * The difference arises because of the way AC & MasterRpcServices get the user.
- *
- * In AccessController, it first checks if there is an active rpc user in ObserverContext. If not,
- * it uses UserProvider for current user. This *might* make sense in the context of coprocessors,
- * because they can be called outside the context of RPCs.
- * But in the context of MasterRpcServices, only one way makes sense - RPCServer.getRequestUser().
- *
- * In AC tests, when we do FooUser.runAs on AccessController instance directly, it bypasses
- * the rpc framework completely, but works because UserProvider provides the correct user, i.e.
- * FooUser in this case.
- *
- * But this doesn't work for the tests here, so we go around by doing complete RPCs.
- */
-@Category({SecurityTests.class, MediumTests.class})
-public class TestAdminOnlyOperations {
-  @ClassRule
-  public static final HBaseClassTestRule CLASS_RULE =
-      HBaseClassTestRule.forClass(TestAdminOnlyOperations.class);
-
-  private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
-  private static Configuration conf;
-
-  // user granted with all global permission
-  private static User USER_ADMIN;
-  // user without admin permissions
-  private static User USER_NON_ADMIN;
-
-  private static final String GROUP_ADMIN = "admin_group";
-  private static User USER_GROUP_ADMIN;
-
-  // Dummy service to test execService calls. Needs to be public so can be loaded as Coprocessor.
-  public static class DummyCpService implements MasterCoprocessor, RegionServerCoprocessor {
-    public DummyCpService() {}
-
-    @Override
-    public Iterable<Service> getServices() {
-      return Collections.singleton(mock(TestRpcServiceProtos.TestProtobufRpcProto.class));
-    }
-  }
-
-  private static void enableSecurity(Configuration conf) throws IOException {
-    conf.set("hadoop.security.authorization", "false");
-    conf.set("hadoop.security.authentication", "simple");
-    conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, AccessController.class.getName() +
-      "," + DummyCpService.class.getName());
-    conf.set(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY, AccessController.class.getName());
-    conf.set(CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY, AccessController.class.getName() +
-      "," + DummyCpService.class.getName());
-    conf.set(User.HBASE_SECURITY_AUTHORIZATION_CONF_KEY, "true");
-    SecureTestUtil.configureSuperuser(conf);
-  }
-
-  @BeforeClass
-  public static void setup() throws Exception {
-    conf = TEST_UTIL.getConfiguration();
-
-    // Enable security
-    enableSecurity(conf);
-    TEST_UTIL.startMiniCluster();
-
-    // Wait for the ACL table to become available
-    TEST_UTIL.waitUntilAllRegionsAssigned(AccessControlLists.ACL_TABLE_NAME);
-
-    // Create users
-    USER_ADMIN = User.createUserForTesting(conf, "admin", new String[0]);
-    USER_NON_ADMIN = User.createUserForTesting(conf, "non_admin", new String[0]);
-    USER_GROUP_ADMIN =
-        User.createUserForTesting(conf, "user_group_admin", new String[] { GROUP_ADMIN });
-
-    // Assign permissions to users and groups
-    SecureTestUtil.grantGlobal(TEST_UTIL, USER_ADMIN.getShortName(), Permission.Action.ADMIN);
-    SecureTestUtil.grantGlobal(TEST_UTIL, toGroupEntry(GROUP_ADMIN), Permission.Action.ADMIN);
-    // No permissions to USER_NON_ADMIN
-  }
-
-  interface Action {
-    void run(Admin admin) throws Exception;
-  }
-
-  private void verifyAllowed(User user, Action action) throws Exception {
-    user.runAs((PrivilegedExceptionAction<?>) () -> {
-      try (Connection conn = ConnectionFactory.createConnection(conf);
-          Admin admin = conn.getAdmin()) {
-        action.run(admin);
-      } catch (IOException e) {
-        fail(e.toString());
-      }
-      return null;
-    });
-  }
-
-  private void verifyDenied(User user, Action action) throws Exception {
-    user.runAs((PrivilegedExceptionAction<?>) () -> {
-      boolean accessDenied = false;
-      try (Connection conn = ConnectionFactory.createConnection(conf);
-          Admin admin = conn.getAdmin()) {
-        action.run(admin);
-      } catch (AccessDeniedException e) {
-        accessDenied = true;
-      }
-      assertTrue("Expected access to be denied", accessDenied);
-      return null;
-    });
-  }
-
-  private void verifiedDeniedServiceException(User user, Action action) throws Exception {
-    user.runAs((PrivilegedExceptionAction<?>) () -> {
-      boolean accessDenied = false;
-      try (Connection conn = ConnectionFactory.createConnection(conf);
-          Admin admin = conn.getAdmin()) {
-        action.run(admin);
-      } catch (ServiceException e) {
-        // For MasterRpcServices.execService.
-        if (e.getCause() instanceof AccessDeniedException) {
-          accessDenied = true;
-        }
-      }
-      assertTrue("Expected access to be denied", accessDenied);
-      return null;
-    });
-
-  }
-
-  private void verifyAdminCheckForAction(Action action) throws Exception {
-    verifyAllowed(USER_ADMIN, action);
-    verifyAllowed(USER_GROUP_ADMIN, action);
-    verifyDenied(USER_NON_ADMIN, action);
-  }
-
-  @Test
-  public void testEnableCatalogJanitor() throws Exception {
-    verifyAdminCheckForAction((admin) -> admin.enableCatalogJanitor(true));
-  }
-
-  @Test
-  public void testRunCatalogJanitor() throws Exception {
-    verifyAdminCheckForAction((admin) -> admin.runCatalogJanitor());
-  }
-
-  @Test
-  public void testCleanerChoreRunning() throws Exception {
-    verifyAdminCheckForAction((admin) -> admin.cleanerChoreSwitch(true));
-  }
-
-  @Test
-  public void testRunCleanerChore() throws Exception {
-    verifyAdminCheckForAction((admin) -> admin.runCleanerChore());
-  }
-
-  @Test
-  public void testExecProcedure() throws Exception {
-    verifyAdminCheckForAction((admin) -> {
-      // Using existing table instead of creating a new one.
-      admin.execProcedure("flush-table-proc", TableName.META_TABLE_NAME.getNameAsString(),
-          new HashMap<>());
-    });
-  }
-
-  @Test
-  public void testExecService() throws Exception {
-    Action action = (admin) -> {
-      TestRpcServiceProtos.TestProtobufRpcProto.BlockingInterface service =
-          TestRpcServiceProtos.TestProtobufRpcProto.newBlockingStub(admin.coprocessorService());
-      service.ping(null, TestProtos.EmptyRequestProto.getDefaultInstance());
-    };
-
-    verifyAllowed(USER_ADMIN, action);
-    verifyAllowed(USER_GROUP_ADMIN, action);
-    // This is same as above verifyAccessDenied
-    verifiedDeniedServiceException(USER_NON_ADMIN, action);
-  }
-
-  @Test
-  public void testExecProcedureWithRet() throws Exception {
-    verifyAdminCheckForAction((admin) -> {
-      // Using existing table instead of creating a new one.
-      admin.execProcedureWithReturn("flush-table-proc", TableName.META_TABLE_NAME.getNameAsString(),
-          new HashMap<>());
-    });
-  }
-
-  @Test
-  public void testNormalize() throws Exception {
-    verifyAdminCheckForAction((admin) -> admin.normalize());
-  }
-
-  @Test
-  public void testSetNormalizerRunning() throws Exception {
-    verifyAdminCheckForAction((admin) -> admin.normalizerSwitch(true));
-  }
-
-  @Test
-  public void testExecRegionServerService() throws Exception {
-    Action action = (admin) -> {
-      ServerName serverName = TEST_UTIL.getHBaseCluster().getRegionServer(0).getServerName();
-      TestRpcServiceProtos.TestProtobufRpcProto.BlockingInterface service =
-          TestRpcServiceProtos.TestProtobufRpcProto.newBlockingStub(
-              admin.coprocessorService(serverName));
-      service.ping(null, TestProtos.EmptyRequestProto.getDefaultInstance());
-    };
-
-    verifyAllowed(USER_ADMIN, action);
-    verifyAllowed(USER_GROUP_ADMIN, action);
-    verifiedDeniedServiceException(USER_NON_ADMIN, action);
-  }
-}

http://git-wip-us.apache.org/repos/asf/hbase/blob/69f5d707/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestRpcAccessChecks.java
----------------------------------------------------------------------
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestRpcAccessChecks.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestRpcAccessChecks.java
new file mode 100644
index 0000000..55873bb
--- /dev/null
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestRpcAccessChecks.java
@@ -0,0 +1,362 @@
+
+/**
+ * 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.security.access;
+
+import static org.apache.hadoop.hbase.AuthUtil.toGroupEntry;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+
+import com.google.protobuf.Service;
+import com.google.protobuf.ServiceException;
+import java.io.IOException;
+import java.security.PrivilegedExceptionAction;
+import java.util.Collections;
+import java.util.HashMap;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.Cell;
+import org.apache.hadoop.hbase.CellUtil;
+import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.HBaseTestingUtility;
+import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.client.Admin;
+import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
+import org.apache.hadoop.hbase.client.Connection;
+import org.apache.hadoop.hbase.client.ConnectionFactory;
+import org.apache.hadoop.hbase.client.Get;
+import org.apache.hadoop.hbase.client.Put;
+import org.apache.hadoop.hbase.client.Result;
+import org.apache.hadoop.hbase.client.Table;
+import org.apache.hadoop.hbase.client.TableDescriptor;
+import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
+import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
+import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor;
+import org.apache.hadoop.hbase.coprocessor.RegionServerCoprocessor;
+import org.apache.hadoop.hbase.ipc.protobuf.generated.TestProtos;
+import org.apache.hadoop.hbase.ipc.protobuf.generated.TestRpcServiceProtos;
+import org.apache.hadoop.hbase.security.AccessDeniedException;
+import org.apache.hadoop.hbase.security.User;
+import org.apache.hadoop.hbase.testclassification.MediumTests;
+import org.apache.hadoop.hbase.testclassification.SecurityTests;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.TestName;
+
+/**
+ * This class tests operations in MasterRpcServices which require ADMIN access.
+ * It doesn't test all operations which require ADMIN access, only those which get vetted within
+ * MasterRpcServices at the point of entry itself (unlike old approach of using
+ * hooks in AccessController).
+ *
+ * Sidenote:
+ * There is one big difference between how security tests for AccessController hooks work, and how
+ * the tests in this class for security in MasterRpcServices work.
+ * The difference arises because of the way AC & MasterRpcServices get the user.
+ *
+ * In AccessController, it first checks if there is an active rpc user in ObserverContext. If not,
+ * it uses UserProvider for current user. This *might* make sense in the context of coprocessors,
+ * because they can be called outside the context of RPCs.
+ * But in the context of MasterRpcServices, only one way makes sense - RPCServer.getRequestUser().
+ *
+ * In AC tests, when we do FooUser.runAs on AccessController instance directly, it bypasses
+ * the rpc framework completely, but works because UserProvider provides the correct user, i.e.
+ * FooUser in this case.
+ *
+ * But this doesn't work for the tests here, so we go around by doing complete RPCs.
+ */
+@Category({SecurityTests.class, MediumTests.class})
+public class TestRpcAccessChecks {
+  @ClassRule
+  public static final HBaseClassTestRule CLASS_RULE =
+      HBaseClassTestRule.forClass(TestRpcAccessChecks.class);
+
+  @Rule
+  public final TestName TEST_NAME = new TestName();
+
+  private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
+  private static Configuration conf;
+
+  // user granted with all global permission
+  private static User USER_ADMIN;
+  // user without admin permissions
+  private static User USER_NON_ADMIN;
+
+  private static final String GROUP_ADMIN = "admin_group";
+  private static User USER_GROUP_ADMIN;
+
+  // Dummy service to test execService calls. Needs to be public so can be loaded as Coprocessor.
+  public static class DummyCpService implements MasterCoprocessor, RegionServerCoprocessor {
+    public DummyCpService() {}
+
+    @Override
+    public Iterable<Service> getServices() {
+      return Collections.singleton(mock(TestRpcServiceProtos.TestProtobufRpcProto.class));
+    }
+  }
+
+  private static void enableSecurity(Configuration conf) throws IOException {
+    conf.set("hadoop.security.authorization", "false");
+    conf.set("hadoop.security.authentication", "simple");
+    conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, AccessController.class.getName() +
+      "," + DummyCpService.class.getName());
+    conf.set(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY, AccessController.class.getName());
+    conf.set(CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY, AccessController.class.getName() +
+      "," + DummyCpService.class.getName());
+    conf.set(User.HBASE_SECURITY_AUTHORIZATION_CONF_KEY, "true");
+    SecureTestUtil.configureSuperuser(conf);
+  }
+
+  @BeforeClass
+  public static void setup() throws Exception {
+    conf = TEST_UTIL.getConfiguration();
+
+    // Enable security
+    enableSecurity(conf);
+    TEST_UTIL.startMiniCluster();
+
+    // Wait for the ACL table to become available
+    TEST_UTIL.waitUntilAllRegionsAssigned(AccessControlLists.ACL_TABLE_NAME);
+
+    // Create users
+    USER_ADMIN = User.createUserForTesting(conf, "admin", new String[0]);
+    USER_NON_ADMIN = User.createUserForTesting(conf, "non_admin", new String[0]);
+    USER_GROUP_ADMIN =
+        User.createUserForTesting(conf, "user_group_admin", new String[] { GROUP_ADMIN });
+
+    // Assign permissions to users and groups
+    SecureTestUtil.grantGlobal(TEST_UTIL, USER_ADMIN.getShortName(), Permission.Action.ADMIN);
+    SecureTestUtil.grantGlobal(TEST_UTIL, toGroupEntry(GROUP_ADMIN), Permission.Action.ADMIN);
+    // No permissions to USER_NON_ADMIN
+  }
+
+  interface Action {
+    void run(Admin admin) throws Exception;
+  }
+
+  private void verifyAllowed(User user, Action action) throws Exception {
+    user.runAs((PrivilegedExceptionAction<?>) () -> {
+      try (Connection conn = ConnectionFactory.createConnection(conf);
+          Admin admin = conn.getAdmin()) {
+        action.run(admin);
+      } catch (IOException e) {
+        fail(e.toString());
+      }
+      return null;
+    });
+  }
+
+  private void verifyDenied(User user, Action action) throws Exception {
+    user.runAs((PrivilegedExceptionAction<?>) () -> {
+      boolean accessDenied = false;
+      try (Connection conn = ConnectionFactory.createConnection(conf);
+          Admin admin = conn.getAdmin()) {
+        action.run(admin);
+      } catch (AccessDeniedException e) {
+        accessDenied = true;
+      }
+      assertTrue("Expected access to be denied", accessDenied);
+      return null;
+    });
+  }
+
+  private void verifiedDeniedServiceException(User user, Action action) throws Exception {
+    user.runAs((PrivilegedExceptionAction<?>) () -> {
+      boolean accessDenied = false;
+      try (Connection conn = ConnectionFactory.createConnection(conf);
+          Admin admin = conn.getAdmin()) {
+        action.run(admin);
+      } catch (ServiceException e) {
+        // For MasterRpcServices.execService.
+        if (e.getCause() instanceof AccessDeniedException) {
+          accessDenied = true;
+        }
+      }
+      assertTrue("Expected access to be denied", accessDenied);
+      return null;
+    });
+
+  }
+
+  private void verifyAdminCheckForAction(Action action) throws Exception {
+    verifyAllowed(USER_ADMIN, action);
+    verifyAllowed(USER_GROUP_ADMIN, action);
+    verifyDenied(USER_NON_ADMIN, action);
+  }
+
+  @Test
+  public void testEnableCatalogJanitor() throws Exception {
+    verifyAdminCheckForAction((admin) -> admin.enableCatalogJanitor(true));
+  }
+
+  @Test
+  public void testRunCatalogJanitor() throws Exception {
+    verifyAdminCheckForAction((admin) -> admin.runCatalogJanitor());
+  }
+
+  @Test
+  public void testCleanerChoreRunning() throws Exception {
+    verifyAdminCheckForAction((admin) -> admin.cleanerChoreSwitch(true));
+  }
+
+  @Test
+  public void testRunCleanerChore() throws Exception {
+    verifyAdminCheckForAction((admin) -> admin.runCleanerChore());
+  }
+
+  @Test
+  public void testExecProcedure() throws Exception {
+    verifyAdminCheckForAction((admin) -> {
+      // Using existing table instead of creating a new one.
+      admin.execProcedure("flush-table-proc", TableName.META_TABLE_NAME.getNameAsString(),
+          new HashMap<>());
+    });
+  }
+
+  @Test
+  public void testExecService() throws Exception {
+    Action action = (admin) -> {
+      TestRpcServiceProtos.TestProtobufRpcProto.BlockingInterface service =
+          TestRpcServiceProtos.TestProtobufRpcProto.newBlockingStub(admin.coprocessorService());
+      service.ping(null, TestProtos.EmptyRequestProto.getDefaultInstance());
+    };
+
+    verifyAllowed(USER_ADMIN, action);
+    verifyAllowed(USER_GROUP_ADMIN, action);
+    // This is same as above verifyAccessDenied
+    verifiedDeniedServiceException(USER_NON_ADMIN, action);
+  }
+
+  @Test
+  public void testExecProcedureWithRet() throws Exception {
+    verifyAdminCheckForAction((admin) -> {
+      // Using existing table instead of creating a new one.
+      admin.execProcedureWithReturn("flush-table-proc", TableName.META_TABLE_NAME.getNameAsString(),
+          new HashMap<>());
+    });
+  }
+
+  @Test
+  public void testNormalize() throws Exception {
+    verifyAdminCheckForAction((admin) -> admin.normalize());
+  }
+
+  @Test
+  public void testSetNormalizerRunning() throws Exception {
+    verifyAdminCheckForAction((admin) -> admin.normalizerSwitch(true));
+  }
+
+  @Test
+  public void testExecRegionServerService() throws Exception {
+    Action action = (admin) -> {
+      ServerName serverName = TEST_UTIL.getHBaseCluster().getRegionServer(0).getServerName();
+      TestRpcServiceProtos.TestProtobufRpcProto.BlockingInterface service =
+          TestRpcServiceProtos.TestProtobufRpcProto.newBlockingStub(
+              admin.coprocessorService(serverName));
+      service.ping(null, TestProtos.EmptyRequestProto.getDefaultInstance());
+    };
+
+    verifyAllowed(USER_ADMIN, action);
+    verifyAllowed(USER_GROUP_ADMIN, action);
+    verifiedDeniedServiceException(USER_NON_ADMIN, action);
+  }
+
+  @Test
+  public void testTableFlush() throws Exception {
+    TableName tn = TableName.valueOf(TEST_NAME.getMethodName());
+    TableDescriptor desc = TableDescriptorBuilder.newBuilder(tn)
+        .setColumnFamily(ColumnFamilyDescriptorBuilder.of("f1")).build();
+    Action adminAction = (admin) -> {
+      admin.createTable(desc);
+      // Avoid giving a global permission which may screw up other tests
+      SecureTestUtil.grantOnTable(
+          TEST_UTIL, USER_NON_ADMIN.getShortName(), tn, null, null, Permission.Action.READ,
+          Permission.Action.WRITE, Permission.Action.CREATE);
+    };
+    verifyAllowed(USER_ADMIN, adminAction);
+
+    Action userAction = (admin) -> {
+      Connection conn = admin.getConnection();
+      final byte[] rowKey = Bytes.toBytes("row1");
+      final byte[] col = Bytes.toBytes("q1");
+      final byte[] val = Bytes.toBytes("v1");
+      try (Table table = conn.getTable(tn)) {
+        // Write a value
+        Put p = new Put(rowKey);
+        p.addColumn(Bytes.toBytes("f1"), col, val);
+        table.put(p);
+        // Flush should not require ADMIN permission
+        admin.flush(tn);
+        // Nb: ideally, we would verify snapshot permission too (as that was fixed in the
+        //   regression HBASE-20185) but taking a snapshot requires ADMIN permission which
+        //   masks the root issue.
+        // Make sure we read the value
+        Result result = table.get(new Get(rowKey));
+        assertFalse(result.isEmpty());
+        Cell c = result.getColumnLatestCell(Bytes.toBytes("f1"), col);
+        assertArrayEquals(val, CellUtil.cloneValue(c));
+      }
+    };
+    verifyAllowed(USER_NON_ADMIN, userAction);
+  }
+
+  @Test
+  public void testTableFlushAndSnapshot() throws Exception {
+    TableName tn = TableName.valueOf(TEST_NAME.getMethodName());
+    TableDescriptor desc = TableDescriptorBuilder.newBuilder(tn)
+        .setColumnFamily(ColumnFamilyDescriptorBuilder.of("f1")).build();
+    Action adminAction = (admin) -> {
+      admin.createTable(desc);
+      // Giving ADMIN here, but only on this table, *not* globally
+      SecureTestUtil.grantOnTable(
+          TEST_UTIL, USER_NON_ADMIN.getShortName(), tn, null, null, Permission.Action.READ,
+          Permission.Action.WRITE, Permission.Action.CREATE, Permission.Action.ADMIN);
+    };
+    verifyAllowed(USER_ADMIN, adminAction);
+
+    Action userAction = (admin) -> {
+      Connection conn = admin.getConnection();
+      final byte[] rowKey = Bytes.toBytes("row1");
+      final byte[] col = Bytes.toBytes("q1");
+      final byte[] val = Bytes.toBytes("v1");
+      try (Table table = conn.getTable(tn)) {
+        // Write a value
+        Put p = new Put(rowKey);
+        p.addColumn(Bytes.toBytes("f1"), col, val);
+        table.put(p);
+        // Flush should not require ADMIN permission
+        admin.flush(tn);
+        // Table admin should be sufficient to snapshot this table
+        admin.snapshot(tn.getNameAsString() + "_snapshot1", tn);
+        // Read the value just because
+        Result result = table.get(new Get(rowKey));
+        assertFalse(result.isEmpty());
+        Cell c = result.getColumnLatestCell(Bytes.toBytes("f1"), col);
+        assertArrayEquals(val, CellUtil.cloneValue(c));
+      }
+    };
+    verifyAllowed(USER_NON_ADMIN, userAction);
+  }
+}