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