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 com.google.protobuf.ServiceException;
024import java.io.IOException;
025import java.lang.reflect.UndeclaredThrowableException;
026import java.security.PrivilegedActionException;
027import java.security.PrivilegedExceptionAction;
028import java.util.List;
029import java.util.Map;
030import java.util.Optional;
031import java.util.concurrent.Callable;
032import java.util.concurrent.CountDownLatch;
033import org.apache.hadoop.conf.Configuration;
034import org.apache.hadoop.hbase.Coprocessor;
035import org.apache.hadoop.hbase.HBaseTestingUtility;
036import org.apache.hadoop.hbase.MiniHBaseCluster;
037import org.apache.hadoop.hbase.NamespaceDescriptor;
038import org.apache.hadoop.hbase.TableName;
039import org.apache.hadoop.hbase.TableNotEnabledException;
040import org.apache.hadoop.hbase.Waiter.Predicate;
041import org.apache.hadoop.hbase.client.Admin;
042import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
043import org.apache.hadoop.hbase.client.Connection;
044import org.apache.hadoop.hbase.client.ConnectionFactory;
045import org.apache.hadoop.hbase.client.RegionInfo;
046import org.apache.hadoop.hbase.client.RetriesExhaustedWithDetailsException;
047import org.apache.hadoop.hbase.client.Table;
048import org.apache.hadoop.hbase.client.TableDescriptor;
049import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
050import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
051import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor;
052import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
053import org.apache.hadoop.hbase.coprocessor.MasterObserver;
054import org.apache.hadoop.hbase.coprocessor.ObserverContext;
055import org.apache.hadoop.hbase.io.hfile.HFile;
056import org.apache.hadoop.hbase.ipc.RemoteWithExtrasException;
057import org.apache.hadoop.hbase.regionserver.HRegion;
058import org.apache.hadoop.hbase.security.AccessDeniedException;
059import org.apache.hadoop.hbase.security.User;
060import org.apache.hadoop.hbase.util.JVMClusterUtil.RegionServerThread;
061import org.slf4j.Logger;
062import org.slf4j.LoggerFactory;
063
064import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
065import org.apache.hbase.thirdparty.com.google.common.collect.Maps;
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(MiniHBaseCluster 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> getAuthManagerMTimes(MiniHBaseCluster cluster) {
299    Map<AccessController, Long> result = Maps.newHashMap();
300    for (AccessController ac : getAccessControllers(cluster)) {
301      result.put(ac, ac.getAuthManager().getMTime());
302    }
303    return result;
304  }
305
306  @SuppressWarnings("rawtypes")
307  private static void updateACLs(final HBaseTestingUtility util, Callable c) throws Exception {
308    // Get the current mtimes for all access controllers
309    final Map<AccessController, Long> oldMTimes = getAuthManagerMTimes(util.getHBaseCluster());
310
311    // Run the update action
312    c.call();
313
314    // Wait until mtimes for all access controllers have incremented
315    util.waitFor(WAIT_TIME, 100, new Predicate<IOException>() {
316      @Override
317      public boolean evaluate() throws IOException {
318        Map<AccessController, Long> mtimes = getAuthManagerMTimes(util.getHBaseCluster());
319        for (Map.Entry<AccessController, Long> e : mtimes.entrySet()) {
320          if (!oldMTimes.containsKey(e.getKey())) {
321            LOG.error("Snapshot of AccessController state does not include instance on region "
322              + e.getKey().getRegion().getRegionInfo().getRegionNameAsString());
323            // Error out the predicate, we will try again
324            return false;
325          }
326          long old = oldMTimes.get(e.getKey());
327          long now = e.getValue();
328          if (now <= old) {
329            LOG.info("AccessController on region "
330              + e.getKey().getRegion().getRegionInfo().getRegionNameAsString()
331              + " has not updated: mtime=" + now);
332            return false;
333          }
334        }
335        return true;
336      }
337    });
338  }
339
340  /**
341   * Grant permissions globally to the given user. Will wait until all active AccessController
342   * instances have updated their permissions caches or will throw an exception upon timeout (10
343   * seconds).
344   */
345  public static void grantGlobal(final HBaseTestingUtility util, final String user,
346    final Permission.Action... actions) throws Exception {
347    SecureTestUtil.updateACLs(util, new Callable<Void>() {
348      @Override
349      public Void call() throws Exception {
350        try (Connection connection = ConnectionFactory.createConnection(util.getConfiguration())) {
351          connection.getAdmin().grant(
352            new UserPermission(user, Permission.newBuilder().withActions(actions).build()), false);
353        }
354        return null;
355      }
356    });
357  }
358
359  /**
360   * Grant permissions globally to the given user. Will wait until all active AccessController
361   * instances have updated their permissions caches or will throw an exception upon timeout (10
362   * seconds).
363   */
364  public static void grantGlobal(final User caller, final HBaseTestingUtility util,
365    final String user, final Permission.Action... actions) throws Exception {
366    SecureTestUtil.updateACLs(util, new Callable<Void>() {
367      @Override
368      public Void call() throws Exception {
369        Configuration conf = util.getConfiguration();
370        try (Connection connection = ConnectionFactory.createConnection(conf, caller)) {
371          connection.getAdmin().grant(
372            new UserPermission(user, Permission.newBuilder().withActions(actions).build()), false);
373        }
374        return null;
375      }
376    });
377  }
378
379  /**
380   * Revoke permissions globally from the given user. Will wait until all active AccessController
381   * instances have updated their permissions caches or will throw an exception upon timeout (10
382   * seconds).
383   */
384  public static void revokeGlobal(final HBaseTestingUtility util, final String user,
385    final Permission.Action... actions) throws Exception {
386    SecureTestUtil.updateACLs(util, new Callable<Void>() {
387      @Override
388      public Void call() throws Exception {
389        try (Connection connection = ConnectionFactory.createConnection(util.getConfiguration())) {
390          connection.getAdmin()
391            .revoke(new UserPermission(user, Permission.newBuilder().withActions(actions).build()));
392        }
393        return null;
394      }
395    });
396  }
397
398  /**
399   * Revoke permissions globally from the given user. Will wait until all active AccessController
400   * instances have updated their permissions caches or will throw an exception upon timeout (10
401   * seconds).
402   */
403  public static void revokeGlobal(final User caller, final HBaseTestingUtility util,
404    final String user, final Permission.Action... actions) throws Exception {
405    SecureTestUtil.updateACLs(util, new Callable<Void>() {
406      @Override
407      public Void call() throws Exception {
408        Configuration conf = util.getConfiguration();
409        try (Connection connection = ConnectionFactory.createConnection(conf, caller)) {
410          connection.getAdmin()
411            .revoke(new UserPermission(user, Permission.newBuilder().withActions(actions).build()));
412        }
413        return null;
414      }
415    });
416  }
417
418  /**
419   * Grant permissions on a namespace to the given user. Will wait until all active AccessController
420   * instances have updated their permissions caches or will throw an exception upon timeout (10
421   * seconds).
422   */
423  public static void grantOnNamespace(final HBaseTestingUtility util, final String user,
424    final String namespace, final Permission.Action... actions) throws Exception {
425    SecureTestUtil.updateACLs(util, new Callable<Void>() {
426      @Override
427      public Void call() throws Exception {
428        try (Connection connection = ConnectionFactory.createConnection(util.getConfiguration())) {
429          connection.getAdmin().grant(
430            new UserPermission(user, Permission.newBuilder(namespace).withActions(actions).build()),
431            false);
432        }
433        return null;
434      }
435    });
436  }
437
438  /**
439   * Grant permissions on a namespace to the given user. Will wait until all active AccessController
440   * instances have updated their permissions caches or will throw an exception upon timeout (10
441   * seconds).
442   */
443  public static void grantOnNamespace(final User caller, final HBaseTestingUtility util,
444    final String user, final String namespace, final Permission.Action... actions)
445    throws Exception {
446    SecureTestUtil.updateACLs(util, new Callable<Void>() {
447      @Override
448      public Void call() throws Exception {
449        Configuration conf = util.getConfiguration();
450        try (Connection connection = ConnectionFactory.createConnection(conf, caller)) {
451          connection.getAdmin().grant(
452            new UserPermission(user, Permission.newBuilder(namespace).withActions(actions).build()),
453            false);
454        }
455        return null;
456      }
457    });
458  }
459
460  /**
461   * Grant permissions on a namespace to the given user using AccessControl Client. Will wait until
462   * all active AccessController instances have updated their permissions caches or will throw an
463   * exception upon timeout (10 seconds).
464   */
465  public static void grantOnNamespaceUsingAccessControlClient(final HBaseTestingUtility util,
466    final Connection connection, final String user, final String namespace,
467    final Permission.Action... actions) throws Exception {
468    SecureTestUtil.updateACLs(util, new Callable<Void>() {
469      @Override
470      public Void call() throws Exception {
471        try {
472          AccessControlClient.grant(connection, namespace, user, actions);
473        } catch (Throwable t) {
474          LOG.error("grant failed: ", t);
475        }
476        return null;
477      }
478    });
479  }
480
481  /**
482   * Revoke permissions on a namespace from the given user using AccessControl Client. Will wait
483   * until all active AccessController instances have updated their permissions caches or will throw
484   * an exception upon timeout (10 seconds).
485   */
486  public static void revokeFromNamespaceUsingAccessControlClient(final HBaseTestingUtility util,
487    final Connection connection, final String user, final String namespace,
488    final Permission.Action... actions) throws Exception {
489    SecureTestUtil.updateACLs(util, new Callable<Void>() {
490      @Override
491      public Void call() throws Exception {
492        try {
493          AccessControlClient.revoke(connection, namespace, user, actions);
494        } catch (Throwable t) {
495          LOG.error("revoke failed: ", t);
496        }
497        return null;
498      }
499    });
500  }
501
502  /**
503   * Revoke permissions on a namespace from the given user. Will wait until all active
504   * AccessController instances have updated their permissions caches or will throw an exception
505   * upon timeout (10 seconds).
506   */
507  public static void revokeFromNamespace(final HBaseTestingUtility util, final String user,
508    final String namespace, 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          connection.getAdmin().revoke(new UserPermission(user,
514            Permission.newBuilder(namespace).withActions(actions).build()));
515        }
516        return null;
517      }
518    });
519  }
520
521  /**
522   * Revoke permissions on a namespace from the given user. Will wait until all active
523   * AccessController instances have updated their permissions caches or will throw an exception
524   * upon timeout (10 seconds).
525   */
526  public static void revokeFromNamespace(final User caller, final HBaseTestingUtility util,
527    final String user, final String namespace, final Permission.Action... actions)
528    throws Exception {
529    SecureTestUtil.updateACLs(util, new Callable<Void>() {
530      @Override
531      public Void call() throws Exception {
532        Configuration conf = util.getConfiguration();
533        try (Connection connection = ConnectionFactory.createConnection(conf, caller)) {
534          connection.getAdmin().revoke(new UserPermission(user,
535            Permission.newBuilder(namespace).withActions(actions).build()));
536        }
537        return null;
538      }
539    });
540  }
541
542  /**
543   * Grant permissions on a table to the given user. Will wait until all active AccessController
544   * instances have updated their permissions caches or will throw an exception upon timeout (10
545   * seconds).
546   */
547  public static void grantOnTable(final HBaseTestingUtility util, final String user,
548    final TableName table, final byte[] family, final byte[] qualifier,
549    final Permission.Action... actions) throws Exception {
550    SecureTestUtil.updateACLs(util, new Callable<Void>() {
551      @Override
552      public Void call() throws Exception {
553        try (Connection connection = ConnectionFactory.createConnection(util.getConfiguration())) {
554          connection.getAdmin().grant(new UserPermission(user, Permission.newBuilder(table)
555            .withFamily(family).withQualifier(qualifier).withActions(actions).build()), false);
556        }
557        return null;
558      }
559    });
560  }
561
562  /**
563   * Grant permissions on a table to the given user. Will wait until all active AccessController
564   * instances have updated their permissions caches or will throw an exception upon timeout (10
565   * seconds).
566   */
567  public static void grantOnTable(final User caller, final HBaseTestingUtility util,
568    final String user, final TableName table, final byte[] family, final byte[] qualifier,
569    final Permission.Action... actions) throws Exception {
570    SecureTestUtil.updateACLs(util, new Callable<Void>() {
571      @Override
572      public Void call() throws Exception {
573        Configuration conf = util.getConfiguration();
574        try (Connection connection = ConnectionFactory.createConnection(conf, caller)) {
575          connection.getAdmin().grant(new UserPermission(user, Permission.newBuilder(table)
576            .withFamily(family).withQualifier(qualifier).withActions(actions).build()), false);
577        }
578        return null;
579      }
580    });
581  }
582
583  /**
584   * Grant permissions on a table to the given user using AccessControlClient. Will wait until all
585   * active AccessController instances have updated their permissions caches or will throw an
586   * exception upon timeout (10 seconds).
587   */
588  public static void grantOnTableUsingAccessControlClient(final HBaseTestingUtility util,
589    final Connection connection, final String user, final TableName table, final byte[] family,
590    final byte[] qualifier, final Permission.Action... actions) throws Exception {
591    SecureTestUtil.updateACLs(util, new Callable<Void>() {
592      @Override
593      public Void call() throws Exception {
594        try {
595          AccessControlClient.grant(connection, table, user, family, qualifier, actions);
596        } catch (Throwable t) {
597          LOG.error("grant failed: ", t);
598        }
599        return null;
600      }
601    });
602  }
603
604  /**
605   * Grant global permissions to the given user using AccessControlClient. Will wait until all
606   * active AccessController instances have updated their permissions caches or will throw an
607   * exception upon timeout (10 seconds).
608   */
609  public static void grantGlobalUsingAccessControlClient(final HBaseTestingUtility util,
610    final Connection connection, final String user, final Permission.Action... actions)
611    throws Exception {
612    SecureTestUtil.updateACLs(util, new Callable<Void>() {
613      @Override
614      public Void call() throws Exception {
615        try {
616          AccessControlClient.grant(connection, user, actions);
617        } catch (Throwable t) {
618          LOG.error("grant failed: ", t);
619        }
620        return null;
621      }
622    });
623  }
624
625  /**
626   * Revoke permissions on a table from the given user. Will wait until all active AccessController
627   * instances have updated their permissions caches or will throw an exception upon timeout (10
628   * seconds).
629   */
630  public static void revokeFromTable(final HBaseTestingUtility util, final String user,
631    final TableName table, final byte[] family, final byte[] qualifier,
632    final Permission.Action... actions) throws Exception {
633    SecureTestUtil.updateACLs(util, new Callable<Void>() {
634      @Override
635      public Void call() throws Exception {
636        try (Connection connection = ConnectionFactory.createConnection(util.getConfiguration())) {
637          connection.getAdmin().revoke(new UserPermission(user, Permission.newBuilder(table)
638            .withFamily(family).withQualifier(qualifier).withActions(actions).build()));
639        }
640        return null;
641      }
642    });
643  }
644
645  /**
646   * Revoke permissions on a table from the given user. Will wait until all active AccessController
647   * instances have updated their permissions caches or will throw an exception upon timeout (10
648   * seconds).
649   */
650  public static void revokeFromTable(final User caller, final HBaseTestingUtility util,
651    final String user, final TableName table, final byte[] family, final byte[] qualifier,
652    final Permission.Action... actions) throws Exception {
653    SecureTestUtil.updateACLs(util, new Callable<Void>() {
654      @Override
655      public Void call() throws Exception {
656        Configuration conf = util.getConfiguration();
657        try (Connection connection = ConnectionFactory.createConnection(conf, caller)) {
658          connection.getAdmin().revoke(new UserPermission(user, Permission.newBuilder(table)
659            .withFamily(family).withQualifier(qualifier).withActions(actions).build()));
660        }
661        return null;
662      }
663    });
664  }
665
666  /**
667   * Revoke permissions on a table from the given user using AccessControlClient. Will wait until
668   * all active AccessController instances have updated their permissions caches or will throw an
669   * exception upon timeout (10 seconds).
670   */
671  public static void revokeFromTableUsingAccessControlClient(final HBaseTestingUtility util,
672    final Connection connection, final String user, final TableName table, final byte[] family,
673    final byte[] qualifier, final Permission.Action... actions) throws Exception {
674    SecureTestUtil.updateACLs(util, new Callable<Void>() {
675      @Override
676      public Void call() throws Exception {
677        try {
678          AccessControlClient.revoke(connection, table, user, family, qualifier, actions);
679        } catch (Throwable t) {
680          LOG.error("revoke failed: ", t);
681        }
682        return null;
683      }
684    });
685  }
686
687  /**
688   * Revoke global permissions from the given user using AccessControlClient. Will wait until all
689   * active AccessController instances have updated their permissions caches or will throw an
690   * exception upon timeout (10 seconds).
691   */
692  public static void revokeGlobalUsingAccessControlClient(final HBaseTestingUtility util,
693    final Connection connection, final String user, final Permission.Action... actions)
694    throws Exception {
695    SecureTestUtil.updateACLs(util, new Callable<Void>() {
696      @Override
697      public Void call() throws Exception {
698        try {
699          AccessControlClient.revoke(connection, user, actions);
700        } catch (Throwable t) {
701          LOG.error("revoke failed: ", t);
702        }
703        return null;
704      }
705    });
706  }
707
708  public static class MasterSyncObserver implements MasterCoprocessor, MasterObserver {
709    volatile CountDownLatch tableCreationLatch = null;
710    volatile CountDownLatch tableDeletionLatch = null;
711
712    @Override
713    public Optional<MasterObserver> getMasterObserver() {
714      return Optional.of(this);
715    }
716
717    @Override
718    public void postCompletedCreateTableAction(
719      final ObserverContext<MasterCoprocessorEnvironment> ctx, TableDescriptor desc,
720      RegionInfo[] regions) throws IOException {
721      // the AccessController test, some times calls only and directly the
722      // postCompletedCreateTableAction()
723      if (tableCreationLatch != null) {
724        tableCreationLatch.countDown();
725      }
726    }
727
728    @Override
729    public void postCompletedDeleteTableAction(
730      final ObserverContext<MasterCoprocessorEnvironment> ctx, final TableName tableName)
731      throws IOException {
732      // the AccessController test, some times calls only and directly the
733      // postCompletedDeleteTableAction()
734      if (tableDeletionLatch != null) {
735        tableDeletionLatch.countDown();
736      }
737    }
738  }
739
740  public static Table createTable(HBaseTestingUtility testUtil, TableName tableName,
741    byte[][] families) throws Exception {
742    TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(tableName);
743    for (byte[] family : families) {
744      builder.setColumnFamily(ColumnFamilyDescriptorBuilder.of(family));
745    }
746    createTable(testUtil, testUtil.getAdmin(), builder.build());
747    return testUtil.getConnection().getTable(tableName);
748  }
749
750  public static void createTable(HBaseTestingUtility testUtil, TableDescriptor htd)
751    throws Exception {
752    createTable(testUtil, testUtil.getAdmin(), htd);
753  }
754
755  public static void createTable(HBaseTestingUtility testUtil, TableDescriptor htd,
756    byte[][] splitKeys) throws Exception {
757    createTable(testUtil, testUtil.getAdmin(), htd, splitKeys);
758  }
759
760  public static void createTable(HBaseTestingUtility testUtil, Admin admin, TableDescriptor htd)
761    throws Exception {
762    createTable(testUtil, admin, htd, null);
763  }
764
765  public static void createTable(HBaseTestingUtility 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 deleteTable(HBaseTestingUtility testUtil, TableName tableName)
783    throws Exception {
784    deleteTable(testUtil, testUtil.getAdmin(), tableName);
785  }
786
787  public static void createNamespace(HBaseTestingUtility testUtil, NamespaceDescriptor nsDesc)
788    throws Exception {
789    testUtil.getAdmin().createNamespace(nsDesc);
790  }
791
792  public static void deleteNamespace(HBaseTestingUtility testUtil, String namespace)
793    throws Exception {
794    testUtil.getAdmin().deleteNamespace(namespace);
795  }
796
797  public static void deleteTable(HBaseTestingUtility testUtil, Admin admin, TableName tableName)
798    throws Exception {
799    // NOTE: We need a latch because admin is not sync,
800    // so the postOp coprocessor method may be called after the admin operation returned.
801    MasterSyncObserver observer = testUtil.getHBaseCluster().getMaster().getMasterCoprocessorHost()
802      .findCoprocessor(MasterSyncObserver.class);
803    observer.tableDeletionLatch = new CountDownLatch(1);
804    try {
805      admin.disableTable(tableName);
806    } catch (TableNotEnabledException e) {
807      LOG.debug("Table: " + tableName + " already disabled, so just deleting it.");
808    }
809    admin.deleteTable(tableName);
810    observer.tableDeletionLatch.await();
811    observer.tableDeletionLatch = null;
812  }
813
814  public static String convertToNamespace(String namespace) {
815    return PermissionStorage.NAMESPACE_PREFIX + namespace;
816  }
817
818  public static void checkGlobalPerms(HBaseTestingUtility testUtil, Permission.Action... actions)
819    throws IOException {
820    Permission[] perms = new Permission[actions.length];
821    for (int i = 0; i < actions.length; i++) {
822      perms[i] = new Permission(actions[i]);
823    }
824    checkPermissions(testUtil.getConfiguration(), perms);
825  }
826
827  public static void checkTablePerms(HBaseTestingUtility testUtil, TableName table, byte[] family,
828    byte[] column, Permission.Action... actions) throws IOException {
829    Permission[] perms = new Permission[actions.length];
830    for (int i = 0; i < actions.length; i++) {
831      perms[i] = Permission.newBuilder(table).withFamily(family).withQualifier(column)
832        .withActions(actions[i]).build();
833    }
834    checkTablePerms(testUtil, perms);
835  }
836
837  public static void checkTablePerms(HBaseTestingUtility testUtil, Permission... perms)
838    throws IOException {
839    checkPermissions(testUtil.getConfiguration(), perms);
840  }
841
842  private static void checkPermissions(Configuration conf, Permission... perms) throws IOException {
843    try (Connection conn = ConnectionFactory.createConnection(conf)) {
844      List<Boolean> hasUserPermissions =
845        conn.getAdmin().hasUserPermissions(Lists.newArrayList(perms));
846      for (int i = 0; i < hasUserPermissions.size(); i++) {
847        if (!hasUserPermissions.get(i).booleanValue()) {
848          throw new AccessDeniedException("Insufficient permissions " + perms[i]);
849        }
850      }
851    }
852  }
853}