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