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