001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.hadoop.hbase.security.access;
020
021import java.io.IOException;
022import java.lang.reflect.UndeclaredThrowableException;
023import java.security.PrivilegedActionException;
024import java.security.PrivilegedExceptionAction;
025import java.util.List;
026import java.util.Map;
027import java.util.Optional;
028import java.util.concurrent.Callable;
029import java.util.concurrent.CountDownLatch;
030
031import com.google.protobuf.BlockingRpcChannel;
032import com.google.protobuf.ServiceException;
033
034import org.apache.hadoop.conf.Configuration;
035import org.apache.hadoop.hbase.Coprocessor;
036import org.apache.hadoop.hbase.HBaseTestingUtility;
037import org.apache.hadoop.hbase.HConstants;
038import org.apache.hadoop.hbase.MiniHBaseCluster;
039import org.apache.hadoop.hbase.NamespaceDescriptor;
040import org.apache.hadoop.hbase.TableName;
041import org.apache.hadoop.hbase.TableNotEnabledException;
042import org.apache.hadoop.hbase.Waiter.Predicate;
043import org.apache.hadoop.hbase.client.Admin;
044import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
045import org.apache.hadoop.hbase.client.Connection;
046import org.apache.hadoop.hbase.client.ConnectionFactory;
047import org.apache.hadoop.hbase.client.RegionInfo;
048import org.apache.hadoop.hbase.client.RetriesExhaustedWithDetailsException;
049import org.apache.hadoop.hbase.client.Table;
050import org.apache.hadoop.hbase.client.TableDescriptor;
051import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
052import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
053import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor;
054import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
055import org.apache.hadoop.hbase.coprocessor.MasterObserver;
056import org.apache.hadoop.hbase.coprocessor.ObserverContext;
057import org.apache.hadoop.hbase.io.hfile.HFile;
058import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
059import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos;
060import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.AccessControlService;
061import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.CheckPermissionsRequest;
062import org.apache.hadoop.hbase.regionserver.HRegion;
063import org.apache.hadoop.hbase.security.AccessDeniedException;
064import org.apache.hadoop.hbase.security.User;
065import org.apache.hadoop.hbase.security.access.Permission.Action;
066import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
067import org.apache.hbase.thirdparty.com.google.common.collect.Maps;
068import org.apache.hadoop.hbase.util.JVMClusterUtil.RegionServerThread;
069import org.slf4j.Logger;
070import org.slf4j.LoggerFactory;
071
072import static org.junit.Assert.assertEquals;
073import static org.junit.Assert.fail;
074
075/**
076 * Utility methods for testing security
077 */
078public class SecureTestUtil {
079
080  private static final Logger LOG = LoggerFactory.getLogger(SecureTestUtil.class);
081  private static final int WAIT_TIME = 10000;
082
083  public static void configureSuperuser(Configuration conf) throws IOException {
084    // The secure minicluster creates separate service principals based on the
085    // current user's name, one for each slave. We need to add all of these to
086    // the superuser list or security won't function properly. We expect the
087    // HBase service account(s) to have superuser privilege.
088    String currentUser = User.getCurrent().getName();
089    StringBuilder sb = new StringBuilder();
090    sb.append("admin,");
091    sb.append(currentUser);
092    // Assumes we won't ever have a minicluster with more than 5 slaves
093    for (int i = 0; i < 5; i++) {
094      sb.append(',');
095      sb.append(currentUser); sb.append(".hfs."); sb.append(i);
096    }
097    conf.set("hbase.superuser", sb.toString());
098  }
099
100  public static void enableSecurity(Configuration conf) throws IOException {
101    conf.set("hadoop.security.authorization", "false");
102    conf.set("hadoop.security.authentication", "simple");
103    conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, AccessController.class.getName() +
104      "," + MasterSyncObserver.class.getName());
105    conf.set(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY, AccessController.class.getName());
106    conf.set(CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY, AccessController.class.getName());
107    // Need HFile V3 for tags for security features
108    conf.setInt(HFile.FORMAT_VERSION_KEY, 3);
109    conf.set(User.HBASE_SECURITY_AUTHORIZATION_CONF_KEY, "true");
110    configureSuperuser(conf);
111  }
112
113  public static void verifyConfiguration(Configuration conf) {
114    String coprocs = conf.get(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY);
115    boolean accessControllerLoaded = false;
116    for (String coproc : coprocs.split(",")) {
117      try {
118        accessControllerLoaded = AccessController.class.isAssignableFrom(Class.forName(coproc));
119        if (accessControllerLoaded) break;
120      } catch (ClassNotFoundException cnfe) {
121      }
122    }
123    if (!(conf.get(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY).contains(
124        AccessController.class.getName())
125        && accessControllerLoaded && conf.get(
126        CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY).contains(
127        AccessController.class.getName()))) {
128      throw new RuntimeException("AccessController is missing from a system coprocessor list");
129    }
130    if (conf.getInt(HFile.FORMAT_VERSION_KEY, 2) < HFile.MIN_FORMAT_VERSION_WITH_TAGS) {
131      throw new RuntimeException("Post 0.96 security features require HFile version >= 3");
132    }
133
134    if (!conf.getBoolean(User.HBASE_SECURITY_AUTHORIZATION_CONF_KEY, false)) {
135      throw new RuntimeException("Post 2.0.0 security features require set "
136          + User.HBASE_SECURITY_AUTHORIZATION_CONF_KEY + " to true");
137    }
138  }
139
140  public static void checkTablePerms(Configuration conf, TableName table, byte[] family, byte[] column,
141      Permission.Action... actions) throws IOException {
142    Permission[] perms = new Permission[actions.length];
143    for (int i = 0; i < actions.length; i++) {
144      perms[i] = new TablePermission(table, family, column, actions[i]);
145    }
146
147    checkTablePerms(conf, table, perms);
148  }
149
150  public static void checkTablePerms(Configuration conf, TableName table, Permission... perms)
151  throws IOException {
152    CheckPermissionsRequest.Builder request = CheckPermissionsRequest.newBuilder();
153    for (Permission p : perms) {
154      request.addPermission(AccessControlUtil.toPermission(p));
155    }
156    try (Connection connection = ConnectionFactory.createConnection(conf)) {
157      try (Table acl = connection.getTable(table)) {
158        AccessControlService.BlockingInterface protocol =
159            AccessControlService.newBlockingStub(acl.coprocessorService(new byte[0]));
160        try {
161          protocol.checkPermissions(null, request.build());
162        } catch (ServiceException se) {
163          ProtobufUtil.toIOException(se);
164        }
165      }
166    }
167  }
168
169  /**
170   * An AccessTestAction performs an action that will be examined to confirm
171   * the results conform to expected access rights.
172   * <p>
173   * To indicate an action was allowed, return null or a non empty list of
174   * KeyValues.
175   * <p>
176   * To indicate the action was not allowed, either throw an AccessDeniedException
177   * or return an empty list of KeyValues.
178   */
179  public interface AccessTestAction extends PrivilegedExceptionAction<Object> { }
180
181  /** This fails only in case of ADE or empty list for any of the actions. */
182  public static void verifyAllowed(User user, AccessTestAction... actions) throws Exception {
183    for (AccessTestAction action : actions) {
184      try {
185        Object obj = user.runAs(action);
186        if (obj != null && obj instanceof List<?>) {
187          List<?> results = (List<?>) obj;
188          if (results != null && results.isEmpty()) {
189            fail("Empty non null results from action for user '" + user.getShortName() + "'");
190          }
191        }
192      } catch (AccessDeniedException ade) {
193        fail("Expected action to pass for user '" + user.getShortName() + "' but was denied");
194      }
195    }
196  }
197
198  /** This fails only in case of ADE or empty list for any of the users. */
199  public static void verifyAllowed(AccessTestAction action, User... users) throws Exception {
200    for (User user : users) {
201      verifyAllowed(user, action);
202    }
203  }
204
205  public static void verifyAllowed(User user, AccessTestAction action, int count) throws Exception {
206    try {
207      Object obj = user.runAs(action);
208      if (obj != null && obj instanceof List<?>) {
209        List<?> results = (List<?>) obj;
210        if (results != null && results.isEmpty()) {
211          fail("Empty non null results from action for user '" + user.getShortName() + "'");
212        }
213        assertEquals(count, results.size());
214      }
215    } catch (AccessDeniedException ade) {
216      fail("Expected action to pass for user '" + user.getShortName() + "' but was denied");
217    }
218  }
219
220  /** This passes only in case of ADE for all users. */
221  public static void verifyDenied(AccessTestAction action, User... users) throws Exception {
222    for (User user : users) {
223      verifyDenied(user, action);
224    }
225  }
226
227  /** This passes only in case of empty list for all users. */
228  public static void verifyIfEmptyList(AccessTestAction action, User... users) throws Exception {
229    for (User user : users) {
230      try {
231        Object obj = user.runAs(action);
232        if (obj != null && obj instanceof List<?>) {
233          List<?> results = (List<?>) obj;
234          if (results != null && !results.isEmpty()) {
235            fail("Unexpected action results: " +  results + " for user '"
236                + user.getShortName() + "'");
237          }
238        } else {
239          fail("Unexpected results for user '" + user.getShortName() + "'");
240        }
241      } catch (AccessDeniedException ade) {
242        fail("Expected action to pass for user '" + user.getShortName() + "' but was denied");
243      }
244    }
245  }
246
247  /** This passes only in case of null for all users. */
248  public static void verifyIfNull(AccessTestAction  action, User... users) throws Exception {
249    for (User user : users) {
250      try {
251        Object obj = user.runAs(action);
252        if (obj != null) {
253          fail("Non null results from action for user '" + user.getShortName() + "' : " + obj);
254        }
255      } catch (AccessDeniedException ade) {
256        fail("Expected action to pass for user '" + user.getShortName() + "' but was denied");
257      }
258    }
259  }
260
261  /** This passes only in case of ADE for all actions. */
262  public static void verifyDenied(User user, AccessTestAction... actions) throws Exception {
263    for (AccessTestAction action : actions) {
264      try {
265        user.runAs(action);
266        fail("Expected exception was not thrown for user '" + user.getShortName() + "'");
267      } catch (IOException e) {
268        boolean isAccessDeniedException = false;
269        if(e instanceof RetriesExhaustedWithDetailsException) {
270          // in case of batch operations, and put, the client assembles a
271          // RetriesExhaustedWithDetailsException instead of throwing an
272          // AccessDeniedException
273          for(Throwable ex : ((RetriesExhaustedWithDetailsException) e).getCauses()) {
274            if (ex instanceof AccessDeniedException) {
275              isAccessDeniedException = true;
276              break;
277            }
278          }
279        }
280        else {
281          // For doBulkLoad calls AccessDeniedException
282          // is buried in the stack trace
283          Throwable ex = e;
284          do {
285            if (ex instanceof AccessDeniedException) {
286              isAccessDeniedException = true;
287              break;
288            }
289          } while((ex = ex.getCause()) != null);
290        }
291        if (!isAccessDeniedException) {
292          fail("Expected exception was not thrown for user '" + user.getShortName() + "'");
293        }
294      } catch (UndeclaredThrowableException ute) {
295        // TODO why we get a PrivilegedActionException, which is unexpected?
296        Throwable ex = ute.getUndeclaredThrowable();
297        if (ex instanceof PrivilegedActionException) {
298          ex = ((PrivilegedActionException) ex).getException();
299        }
300        if (ex instanceof ServiceException) {
301          ServiceException se = (ServiceException)ex;
302          if (se.getCause() != null && se.getCause() instanceof AccessDeniedException) {
303            // expected result
304            return;
305          }
306        }
307        fail("Expected exception was not thrown for user '" + user.getShortName() + "'");
308      }
309    }
310  }
311
312  private static List<AccessController> getAccessControllers(MiniHBaseCluster cluster) {
313    List<AccessController> result = Lists.newArrayList();
314    for (RegionServerThread t: cluster.getLiveRegionServerThreads()) {
315      for (HRegion region: t.getRegionServer().getOnlineRegionsLocalContext()) {
316        Coprocessor cp = region.getCoprocessorHost().findCoprocessor(AccessController.class);
317        if (cp != null) {
318          result.add((AccessController)cp);
319        }
320      }
321    }
322    return result;
323  }
324
325  private static Map<AccessController,Long> getAuthManagerMTimes(MiniHBaseCluster cluster) {
326    Map<AccessController,Long> result = Maps.newHashMap();
327    for (AccessController ac: getAccessControllers(cluster)) {
328      result.put(ac, ac.getAuthManager().getMTime());
329    }
330    return result;
331  }
332
333  @SuppressWarnings("rawtypes")
334  private static void updateACLs(final HBaseTestingUtility util, Callable c) throws Exception {
335    // Get the current mtimes for all access controllers
336    final Map<AccessController,Long> oldMTimes = getAuthManagerMTimes(util.getHBaseCluster());
337
338    // Run the update action
339    c.call();
340
341    // Wait until mtimes for all access controllers have incremented
342    util.waitFor(WAIT_TIME, 100, new Predicate<IOException>() {
343      @Override
344      public boolean evaluate() throws IOException {
345        Map<AccessController,Long> mtimes = getAuthManagerMTimes(util.getHBaseCluster());
346        for (Map.Entry<AccessController,Long> e: mtimes.entrySet()) {
347          if (!oldMTimes.containsKey(e.getKey())) {
348            LOG.error("Snapshot of AccessController state does not include instance on region " +
349              e.getKey().getRegion().getRegionInfo().getRegionNameAsString());
350            // Error out the predicate, we will try again
351            return false;
352          }
353          long old = oldMTimes.get(e.getKey());
354          long now = e.getValue();
355          if (now <= old) {
356            LOG.info("AccessController on region " +
357              e.getKey().getRegion().getRegionInfo().getRegionNameAsString() +
358              " has not updated: mtime=" + now);
359            return false;
360          }
361        }
362        return true;
363      }
364    });
365  }
366
367  /**
368   * Grant permissions globally to the given user. Will wait until all active
369   * AccessController instances have updated their permissions caches or will
370   * throw an exception upon timeout (10 seconds).
371   */
372  public static void grantGlobal(final HBaseTestingUtility util, final String user,
373      final Permission.Action... actions) throws Exception {
374    SecureTestUtil.updateACLs(util, new Callable<Void>() {
375      @Override
376      public Void call() throws Exception {
377        try (Connection connection = ConnectionFactory.createConnection(util.getConfiguration())) {
378          try (Table acl = connection.getTable(AccessControlLists.ACL_TABLE_NAME)) {
379            BlockingRpcChannel service = acl.coprocessorService(HConstants.EMPTY_START_ROW);
380            AccessControlService.BlockingInterface protocol =
381                AccessControlService.newBlockingStub(service);
382            AccessControlUtil.grant(null, protocol, user, false, actions);
383          }
384        }
385        return null;
386      }
387    });
388  }
389
390  /**
391   * Revoke permissions globally from the given user. Will wait until all active
392   * AccessController instances have updated their permissions caches or will
393   * throw an exception upon timeout (10 seconds).
394   */
395  public static void revokeGlobal(final HBaseTestingUtility util, final String user,
396      final Permission.Action... actions) throws Exception {
397    SecureTestUtil.updateACLs(util, new Callable<Void>() {
398      @Override
399      public Void call() throws Exception {
400        try (Connection connection = ConnectionFactory.createConnection(util.getConfiguration())) {
401          try (Table acl = connection.getTable(AccessControlLists.ACL_TABLE_NAME)) {
402            BlockingRpcChannel service = acl.coprocessorService(HConstants.EMPTY_START_ROW);
403            AccessControlService.BlockingInterface protocol =
404                AccessControlService.newBlockingStub(service);
405            AccessControlUtil.revoke(null, protocol, user, actions);
406          }
407        }
408        return null;
409      }
410    });
411  }
412
413  /**
414   * Grant permissions on a namespace to the given user. Will wait until all active
415   * AccessController instances have updated their permissions caches or will
416   * throw an exception upon timeout (10 seconds).
417   */
418  public static void grantOnNamespace(final HBaseTestingUtility util, final String user,
419      final String namespace, final Permission.Action... actions) throws Exception {
420    SecureTestUtil.updateACLs(util, new Callable<Void>() {
421      @Override
422      public Void call() throws Exception {
423        try (Connection connection = ConnectionFactory.createConnection(util.getConfiguration())) {
424          try (Table acl = connection.getTable(AccessControlLists.ACL_TABLE_NAME)) {
425            BlockingRpcChannel service = acl.coprocessorService(HConstants.EMPTY_START_ROW);
426            AccessControlService.BlockingInterface protocol =
427                AccessControlService.newBlockingStub(service);
428            AccessControlUtil.grant(null, protocol, user, namespace, false, actions);
429          }
430        }
431        return null;
432      }
433    });
434  }
435
436  /**
437   * Grant permissions on a namespace to the given user using AccessControl Client.
438   * Will wait until all active AccessController instances have updated their permissions caches
439   * or will throw an exception upon timeout (10 seconds).
440   */
441  public static void grantOnNamespaceUsingAccessControlClient(final HBaseTestingUtility util,
442      final Connection connection, final String user, final String namespace,
443      final Permission.Action... actions) throws Exception {
444    SecureTestUtil.updateACLs(util, new Callable<Void>() {
445      @Override
446      public Void call() throws Exception {
447        try {
448          AccessControlClient.grant(connection, namespace, user, actions);
449        } catch (Throwable t) {
450          LOG.error("grant failed: ", t);
451        }
452        return null;
453      }
454    });
455  }
456
457  /**
458   * Revoke permissions on a namespace from the given user using AccessControl Client.
459   * Will wait until all active AccessController instances have updated their permissions caches
460   * or will throw an exception upon timeout (10 seconds).
461   */
462  public static void revokeFromNamespaceUsingAccessControlClient(final HBaseTestingUtility util,
463      final Connection connection, final String user, final String namespace,
464      final Permission.Action... actions) throws Exception {
465    SecureTestUtil.updateACLs(util, new Callable<Void>() {
466      @Override
467      public Void call() throws Exception {
468        try {
469          AccessControlClient.revoke(connection, namespace, user, actions);
470        } catch (Throwable t) {
471          LOG.error("revoke failed: ", t);
472        }
473        return null;
474      }
475    });
476  }
477
478  /**
479   * Revoke permissions on a namespace from the given user. Will wait until all active
480   * AccessController instances have updated their permissions caches or will
481   * throw an exception upon timeout (10 seconds).
482   */
483  public static void revokeFromNamespace(final HBaseTestingUtility util, final String user,
484      final String namespace, final Permission.Action... actions) throws Exception {
485    SecureTestUtil.updateACLs(util, new Callable<Void>() {
486      @Override
487      public Void call() throws Exception {
488        try (Connection connection = ConnectionFactory.createConnection(util.getConfiguration())) {
489          try (Table acl = connection.getTable(AccessControlLists.ACL_TABLE_NAME)) {
490            BlockingRpcChannel service = acl.coprocessorService(HConstants.EMPTY_START_ROW);
491            AccessControlService.BlockingInterface protocol =
492                AccessControlService.newBlockingStub(service);
493            AccessControlUtil.revoke(null, protocol, user, namespace, actions);
494          }
495        }
496        return null;
497      }
498    });
499  }
500
501  /**
502   * Grant permissions on a table to the given user. Will wait until all active
503   * AccessController instances have updated their permissions caches or will
504   * throw an exception upon timeout (10 seconds).
505   */
506  public static void grantOnTable(final HBaseTestingUtility util, final String user,
507      final TableName table, final byte[] family, final byte[] qualifier,
508      final Permission.Action... actions) throws Exception {
509    SecureTestUtil.updateACLs(util, new Callable<Void>() {
510      @Override
511      public Void call() throws Exception {
512        try (Connection connection = ConnectionFactory.createConnection(util.getConfiguration())) {
513          try (Table acl = connection.getTable(AccessControlLists.ACL_TABLE_NAME)) {
514            BlockingRpcChannel service = acl.coprocessorService(HConstants.EMPTY_START_ROW);
515            AccessControlService.BlockingInterface protocol =
516                AccessControlService.newBlockingStub(service);
517            AccessControlUtil.grant(null, protocol, user, table, family, qualifier, false, actions);
518          }
519        }
520        return null;
521      }
522    });
523  }
524
525  /**
526   * Grant permissions on a table to the given user using AccessControlClient. Will wait until all
527   * active AccessController instances have updated their permissions caches or will
528   * throw an exception upon timeout (10 seconds).
529   */
530  public static void grantOnTableUsingAccessControlClient(final HBaseTestingUtility util,
531      final Connection connection, final String user, final TableName table, final byte[] family,
532      final byte[] qualifier, final Permission.Action... actions) throws Exception {
533    SecureTestUtil.updateACLs(util, new Callable<Void>() {
534      @Override
535      public Void call() throws Exception {
536        try {
537          AccessControlClient.grant(connection, table, user, family, qualifier, actions);
538        } catch (Throwable t) {
539          LOG.error("grant failed: ", t);
540        }
541        return null;
542      }
543    });
544  }
545
546  /**
547   * Grant global permissions to the given user using AccessControlClient. Will wait until all
548   * active AccessController instances have updated their permissions caches or will
549   * throw an exception upon timeout (10 seconds).
550   */
551  public static void grantGlobalUsingAccessControlClient(final HBaseTestingUtility util,
552      final Connection connection, final String user, final Permission.Action... actions)
553      throws Exception {
554    SecureTestUtil.updateACLs(util, new Callable<Void>() {
555      @Override
556      public Void call() throws Exception {
557        try {
558          AccessControlClient.grant(connection, user, actions);
559        } catch (Throwable t) {
560          LOG.error("grant failed: ", t);
561        }
562        return null;
563      }
564    });
565  }
566
567  /**
568   * Revoke permissions on a table from the given user. Will wait until all active
569   * AccessController instances have updated their permissions caches or will
570   * throw an exception upon timeout (10 seconds).
571   */
572  public static void revokeFromTable(final HBaseTestingUtility util, final String user,
573      final TableName table, final byte[] family, final byte[] qualifier,
574      final Permission.Action... actions) throws Exception {
575    SecureTestUtil.updateACLs(util, new Callable<Void>() {
576      @Override
577      public Void call() throws Exception {
578        try (Connection connection = ConnectionFactory.createConnection(util.getConfiguration())) {
579          try (Table acl = connection.getTable(AccessControlLists.ACL_TABLE_NAME)) {
580            BlockingRpcChannel service = acl.coprocessorService(HConstants.EMPTY_START_ROW);
581            AccessControlService.BlockingInterface protocol =
582                AccessControlService.newBlockingStub(service);
583            AccessControlUtil.revoke(null, protocol, user, table, family, qualifier, actions);
584          }
585        }
586        return null;
587      }
588    });
589  }
590
591  /**
592   * Revoke permissions on a table from the given user using AccessControlClient. Will wait until
593   * all active AccessController instances have updated their permissions caches or will
594   * throw an exception upon timeout (10 seconds).
595   */
596  public static void revokeFromTableUsingAccessControlClient(final HBaseTestingUtility util,
597      final Connection connection, final String user, final TableName table, final byte[] family,
598      final byte[] qualifier, final Permission.Action... actions) throws Exception {
599    SecureTestUtil.updateACLs(util, new Callable<Void>() {
600      @Override
601      public Void call() throws Exception {
602        try {
603          AccessControlClient.revoke(connection, table, user, family, qualifier, actions);
604        } catch (Throwable t) {
605          LOG.error("revoke failed: ", t);
606        }
607        return null;
608      }
609    });
610  }
611
612  /**
613   * Revoke global permissions from the given user using AccessControlClient. Will wait until
614   * all active AccessController instances have updated their permissions caches or will
615   * throw an exception upon timeout (10 seconds).
616   */
617  public static void revokeGlobalUsingAccessControlClient(final HBaseTestingUtility util,
618      final Connection connection, final String user,final Permission.Action... actions)
619      throws Exception {
620    SecureTestUtil.updateACLs(util, new Callable<Void>() {
621      @Override
622      public Void call() throws Exception {
623        try {
624          AccessControlClient.revoke(connection, user, actions);
625        } catch (Throwable t) {
626          LOG.error("revoke failed: ", t);
627        }
628        return null;
629      }
630    });
631  }
632
633  public static class MasterSyncObserver implements MasterCoprocessor, MasterObserver {
634    volatile CountDownLatch tableCreationLatch = null;
635    volatile CountDownLatch tableDeletionLatch = null;
636
637    @Override
638    public Optional<MasterObserver> getMasterObserver() {
639      return Optional.of(this);
640    }
641
642    @Override
643    public void postCompletedCreateTableAction(
644        final ObserverContext<MasterCoprocessorEnvironment> ctx,
645        TableDescriptor desc, RegionInfo[] regions) throws IOException {
646      // the AccessController test, some times calls only and directly the
647      // postCompletedCreateTableAction()
648      if (tableCreationLatch != null) {
649        tableCreationLatch.countDown();
650      }
651    }
652
653    @Override
654    public void postCompletedDeleteTableAction(
655        final ObserverContext<MasterCoprocessorEnvironment> ctx,
656        final TableName tableName) throws IOException {
657      // the AccessController test, some times calls only and directly the
658      // postCompletedDeleteTableAction()
659      if (tableDeletionLatch != null) {
660        tableDeletionLatch.countDown();
661      }
662    }
663  }
664
665  public static Table createTable(HBaseTestingUtility testUtil, TableName tableName,
666      byte[][] families) throws Exception {
667    TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(tableName);
668    for (byte[] family : families) {
669      builder.setColumnFamily(ColumnFamilyDescriptorBuilder.of(family));
670    }
671    createTable(testUtil, testUtil.getAdmin(), builder.build());
672    return testUtil.getConnection().getTable(tableName);
673  }
674
675  public static void createTable(HBaseTestingUtility testUtil, TableDescriptor htd)
676      throws Exception {
677    createTable(testUtil, testUtil.getAdmin(), htd);
678  }
679
680  public static void createTable(HBaseTestingUtility testUtil, TableDescriptor htd,
681      byte[][] splitKeys) throws Exception {
682    createTable(testUtil, testUtil.getAdmin(), htd, splitKeys);
683  }
684
685  public static void createTable(HBaseTestingUtility testUtil, Admin admin, TableDescriptor htd)
686      throws Exception {
687    createTable(testUtil, admin, htd, null);
688  }
689
690  public static void createTable(HBaseTestingUtility testUtil, Admin admin, TableDescriptor htd,
691      byte[][] splitKeys) throws Exception {
692    // NOTE: We need a latch because admin is not sync,
693    // so the postOp coprocessor method may be called after the admin operation returned.
694    MasterSyncObserver observer = testUtil.getHBaseCluster().getMaster()
695      .getMasterCoprocessorHost().findCoprocessor(MasterSyncObserver.class);
696    observer.tableCreationLatch = new CountDownLatch(1);
697    if (splitKeys != null) {
698      admin.createTable(htd, splitKeys);
699    } else {
700      admin.createTable(htd);
701    }
702    observer.tableCreationLatch.await();
703    observer.tableCreationLatch = null;
704    testUtil.waitUntilAllRegionsAssigned(htd.getTableName());
705  }
706
707  public static void deleteTable(HBaseTestingUtility testUtil, TableName tableName)
708      throws Exception {
709    deleteTable(testUtil, testUtil.getAdmin(), tableName);
710  }
711
712  public static void createNamespace(HBaseTestingUtility testUtil, NamespaceDescriptor nsDesc)
713      throws Exception {
714    testUtil.getAdmin().createNamespace(nsDesc);
715  }
716
717  public static void deleteNamespace(HBaseTestingUtility testUtil, String namespace)
718      throws Exception {
719    testUtil.getAdmin().deleteNamespace(namespace);
720  }
721
722  public static void deleteTable(HBaseTestingUtility testUtil, Admin admin, TableName tableName)
723      throws Exception {
724    // NOTE: We need a latch because admin is not sync,
725    // so the postOp coprocessor method may be called after the admin operation returned.
726    MasterSyncObserver observer = testUtil.getHBaseCluster().getMaster()
727      .getMasterCoprocessorHost().findCoprocessor(MasterSyncObserver.class);
728    observer.tableDeletionLatch = new CountDownLatch(1);
729    try {
730      admin.disableTable(tableName);
731    } catch (TableNotEnabledException e) {
732      LOG.debug("Table: " + tableName + " already disabled, so just deleting it.");
733    }
734    admin.deleteTable(tableName);
735    observer.tableDeletionLatch.await();
736    observer.tableDeletionLatch = null;
737  }
738
739  public static String convertToNamespace(String namespace) {
740    return AccessControlLists.NAMESPACE_PREFIX + namespace;
741  }
742
743  public static void checkGlobalPerms(HBaseTestingUtility testUtil, Permission.Action... actions)
744      throws IOException {
745    Permission[] perms = new Permission[actions.length];
746    for (int i = 0; i < actions.length; i++) {
747      perms[i] = new Permission(actions[i]);
748    }
749    CheckPermissionsRequest.Builder request = CheckPermissionsRequest.newBuilder();
750    for (Action a : actions) {
751      request.addPermission(AccessControlProtos.Permission.newBuilder()
752          .setType(AccessControlProtos.Permission.Type.Global)
753          .setGlobalPermission(
754              AccessControlProtos.GlobalPermission.newBuilder()
755                  .addAction(AccessControlUtil.toPermissionAction(a)).build()));
756    }
757    try(Connection conn = ConnectionFactory.createConnection(testUtil.getConfiguration());
758        Table acl = conn.getTable(AccessControlLists.ACL_TABLE_NAME)) {
759      BlockingRpcChannel channel = acl.coprocessorService(new byte[0]);
760      AccessControlService.BlockingInterface protocol =
761        AccessControlService.newBlockingStub(channel);
762      try {
763        protocol.checkPermissions(null, request.build());
764      } catch (ServiceException se) {
765        ProtobufUtil.toIOException(se);
766      }
767    }
768  }
769
770  public static void checkTablePerms(HBaseTestingUtility testUtil, TableName table, byte[] family,
771      byte[] column, Permission.Action... actions) throws IOException {
772    Permission[] perms = new Permission[actions.length];
773    for (int i = 0; i < actions.length; i++) {
774      perms[i] = new TablePermission(table, family, column, actions[i]);
775    }
776    checkTablePerms(testUtil, table, perms);
777  }
778
779  public static void checkTablePerms(HBaseTestingUtility testUtil, TableName table,
780      Permission... perms) throws IOException {
781    CheckPermissionsRequest.Builder request = CheckPermissionsRequest.newBuilder();
782    for (Permission p : perms) {
783      request.addPermission(AccessControlUtil.toPermission(p));
784    }
785
786    try(Connection conn = ConnectionFactory.createConnection(testUtil.getConfiguration());
787        Table acl = conn.getTable(table)) {
788      AccessControlService.BlockingInterface protocol =
789        AccessControlService.newBlockingStub(acl.coprocessorService(new byte[0]));
790      try {
791        protocol.checkPermissions(null, request.build());
792      } catch (ServiceException se) {
793        ProtobufUtil.toIOException(se);
794      }
795    }
796  }
797
798}