HBASE-15780 Make AuthUtil public and rely on it for talking to secure HBase.

Signed-off-by: Gary Helmling <garyh@apache.org>
Signed-off-by: Mikhail Antonov <antonov@apache.org>
Signed-off-by: stack <stack@apache.org>
This commit is contained in:
Sean Busbey 2016-05-13 08:48:45 -07:00
parent c47511baa7
commit e0aff10901
8 changed files with 82 additions and 27 deletions

View File

@ -913,7 +913,7 @@ public class ZKUtil {
if (superUsers != null) {
List<String> groups = new ArrayList<String>();
for (String user : superUsers) {
if (user.startsWith(AuthUtil.GROUP_PREFIX)) {
if (AuthUtil.isGroupPrincipal(user)) {
// TODO: Set node ACL for groups when ZK supports this feature
groups.add(user);
} else {

View File

@ -341,7 +341,7 @@ public class ZooKeeperWatcher implements Watcher, Abortable, Closeable {
for (String user : superUsers) {
boolean hasAccess = false;
// TODO: Validate super group members also when ZK supports setting node ACL for groups.
if (!user.startsWith(AuthUtil.GROUP_PREFIX)) {
if (!AuthUtil.isGroupPrincipal(user)) {
for (ACL acl : acls) {
if (user.equals(acl.getId().getId())) {
if (acl.getPerms() == Perms.ALL) {
@ -370,7 +370,7 @@ public class ZooKeeperWatcher implements Watcher, Abortable, Closeable {
public static boolean isSuperUserId(String[] superUsers, Id id) {
for (String user : superUsers) {
// TODO: Validate super group members also when ZK supports setting node ACL for groups.
if (!user.startsWith(AuthUtil.GROUP_PREFIX) && new Id("sasl", user).equals(id)) {
if (!AuthUtil.isGroupPrincipal(user) && new Id("sasl", user).equals(id)) {
return true;
}
}

View File

@ -32,15 +32,48 @@ import org.apache.hadoop.hbase.util.Strings;
import org.apache.hadoop.security.UserGroupInformation;
/**
* Utility methods for helping with security tasks.
* Utility methods for helping with security tasks. Downstream users
* may rely on this class to handle authenticating via keytab where
* long running services need access to a secure HBase cluster.
*
* Callers must ensure:
*
* <ul>
* <li>HBase configuration files are in the Classpath
* <li>hbase.client.keytab.file points to a valid keytab on the local filesystem
* <li>hbase.client.kerberos.principal gives the Kerberos principal to use
* </ul>
*
* <pre>
* {@code
* ChoreService choreService = null;
* // Presumes HBase configuration files are on the classpath
* final Configuration conf = HBaseConfiguration.create();
* final ScheduledChore authChore = AuthUtil.getAuthChore(conf);
* if (authChore != null) {
* choreService = new ChoreService("MY_APPLICATION");
* choreService.scheduleChore(authChore);
* }
* try {
* // do application work
* } finally {
* if (choreService != null) {
* choreService.shutdown();
* }
* }
* }
* </pre>
*
* See the "Running Canary in a Kerberos-enabled Cluster" section of the HBase Reference Guide for
* an example of configuring a user of this Auth Chore to run on a secure cluster.
*/
@InterfaceAudience.Private
@InterfaceStability.Evolving
@InterfaceAudience.Public
@InterfaceStability.Stable
public class AuthUtil {
private static final Log LOG = LogFactory.getLog(AuthUtil.class);
/** Prefix character to denote group names */
public static final String GROUP_PREFIX = "@";
private static final String GROUP_PREFIX = "@";
private AuthUtil() {
super();
@ -48,6 +81,8 @@ public class AuthUtil {
/**
* Checks if security is enabled and if so, launches chore for refreshing kerberos ticket.
* @param conf the hbase service configuration
* @return a ScheduledChore for renewals, if needed, and null otherwise.
*/
public static ScheduledChore getAuthChore(Configuration conf) throws IOException {
UserProvider userProvider = UserProvider.instantiate(conf);
@ -109,6 +144,7 @@ public class AuthUtil {
* principal. Currently this simply checks if the name starts with the
* special group prefix character ("@").
*/
@InterfaceAudience.Private
public static boolean isGroupPrincipal(String name) {
return name != null && name.startsWith(GROUP_PREFIX);
}
@ -117,6 +153,7 @@ public class AuthUtil {
* Returns the actual name for a group principal (stripped of the
* group prefix).
*/
@InterfaceAudience.Private
public static String getGroupName(String aclKey) {
if (!isGroupPrincipal(aclKey)) {
return aclKey;
@ -128,6 +165,7 @@ public class AuthUtil {
/**
* Returns the group entry with the group prefix for a group principal.
*/
@InterfaceAudience.Private
public static String toGroupEntry(String name) {
return GROUP_PREFIX + name;
}

View File

@ -32,6 +32,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.hbase.ScheduledChore.ChoreServicer;
import org.apache.hadoop.hbase.classification.InterfaceAudience;
import org.apache.hadoop.hbase.classification.InterfaceStability;
/**
* ChoreService is a service that can be used to schedule instances of {@link ScheduledChore} to run
@ -52,13 +53,15 @@ import org.apache.hadoop.hbase.classification.InterfaceAudience;
* When finished with a ChoreService it is good practice to call {@link ChoreService#shutdown()}.
* Calling this method ensures that all scheduled chores are cancelled and cleaned up properly.
*/
@InterfaceAudience.Private
@InterfaceAudience.Public
@InterfaceStability.Stable
public class ChoreService implements ChoreServicer {
private static final Log LOG = LogFactory.getLog(ChoreService.class);
/**
* The minimum number of threads in the core pool of the underlying ScheduledThreadPoolExecutor
*/
@InterfaceAudience.Private
public final static int MIN_CORE_POOL_SIZE = 1;
/**
@ -92,12 +95,15 @@ public class ChoreService implements ChoreServicer {
* @param coreThreadPoolPrefix Prefix that will be applied to the Thread name of all threads
* spawned by this service
*/
@InterfaceAudience.Private
@VisibleForTesting
public ChoreService(final String coreThreadPoolPrefix) {
this(coreThreadPoolPrefix, MIN_CORE_POOL_SIZE, false);
}
/**
* @param coreThreadPoolPrefix Prefix that will be applied to the Thread name of all threads
* spawned by this service
* @param jitter Should chore service add some jitter for all of the scheduled chores. When set
* to true this will add -10% to 10% jitter.
*/
@ -111,6 +117,8 @@ public class ChoreService implements ChoreServicer {
* @param corePoolSize The initial size to set the core pool of the ScheduledThreadPoolExecutor
* to during initialization. The default size is 1, but specifying a larger size may be
* beneficial if you know that 1 thread will not be enough.
* @param jitter Should chore service add some jitter for all of the scheduled chores. When set
* to true this will add -10% to 10% jitter.
*/
public ChoreService(final String coreThreadPoolPrefix, int corePoolSize, boolean jitter) {
this.coreThreadPoolPrefix = coreThreadPoolPrefix;
@ -130,14 +138,6 @@ public class ChoreService implements ChoreServicer {
choresMissingStartTime = new HashMap<ScheduledChore, Boolean>();
}
/**
* @param coreThreadPoolPrefix Prefix that will be applied to the Thread name of all threads
* spawned by this service
*/
public static ChoreService getInstance(final String coreThreadPoolPrefix) {
return new ChoreService(coreThreadPoolPrefix);
}
/**
* @param chore Chore to be scheduled. If the chore is already scheduled with another ChoreService
* instance, that schedule will be cancelled (i.e. a Chore can only ever be scheduled
@ -179,11 +179,13 @@ public class ChoreService implements ChoreServicer {
scheduleChore(chore);
}
@InterfaceAudience.Private
@Override
public synchronized void cancelChore(ScheduledChore chore) {
cancelChore(chore, true);
}
@InterfaceAudience.Private
@Override
public synchronized void cancelChore(ScheduledChore chore, boolean mayInterruptIfRunning) {
if (chore != null && scheduledChores.containsKey(chore)) {
@ -200,12 +202,14 @@ public class ChoreService implements ChoreServicer {
}
}
@InterfaceAudience.Private
@Override
public synchronized boolean isChoreScheduled(ScheduledChore chore) {
return chore != null && scheduledChores.containsKey(chore)
&& !scheduledChores.get(chore).isDone();
}
@InterfaceAudience.Private
@Override
public synchronized boolean triggerNow(ScheduledChore chore) {
if (chore == null) {
@ -293,6 +297,7 @@ public class ChoreService implements ChoreServicer {
}
}
@InterfaceAudience.Private
@Override
public synchronized void onChoreMissedStartTime(ScheduledChore chore) {
if (chore == null || !scheduledChores.containsKey(chore)) return;

View File

@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.hbase.classification.InterfaceAudience;
import org.apache.hadoop.hbase.classification.InterfaceStability;
import com.google.common.annotations.VisibleForTesting;
@ -40,7 +41,8 @@ import com.google.common.annotations.VisibleForTesting;
* Don't subclass ScheduledChore if the task relies on being woken up for something to do, such as
* an entry being added to a queue, etc.
*/
@InterfaceAudience.Private
@InterfaceAudience.Public
@InterfaceStability.Stable
public abstract class ScheduledChore implements Runnable {
private static final Log LOG = LogFactory.getLog(ScheduledChore.class);
@ -116,6 +118,8 @@ public abstract class ScheduledChore implements Runnable {
/**
* This constructor is for test only. It allows us to create an object and to call chore() on it.
*/
@InterfaceAudience.Private
@VisibleForTesting
protected ScheduledChore() {
this.name = null;
this.stopper = null;
@ -165,7 +169,7 @@ public abstract class ScheduledChore implements Runnable {
}
/**
* @see java.lang.Thread#run()
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
@ -327,6 +331,7 @@ public abstract class ScheduledChore implements Runnable {
return choreServicer != null && choreServicer.isChoreScheduled(this);
}
@InterfaceAudience.Private
@VisibleForTesting
public synchronized void choreForTesting() {
chore();
@ -352,6 +357,12 @@ public abstract class ScheduledChore implements Runnable {
protected synchronized void cleanup() {
}
/**
* A summation of this chore in human readable format. Downstream users should not presume
* parsing of this string can relaibly be done between versions. Instead, they should rely
* on the public accessor methods to get the information they desire.
*/
@InterfaceAudience.Private
@Override
public String toString() {
return "[ScheduledChore: Name: " + getName() + " Period: " + getPeriod() + " Unit: "

View File

@ -24,11 +24,12 @@ import org.apache.hadoop.hbase.classification.InterfaceStability;
/**
* Implementers are Stoppable.
*/
@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.COPROC)
@InterfaceStability.Evolving
@InterfaceAudience.Public
@InterfaceStability.Stable
public interface Stoppable {
/**
* Stop this service.
* Implementers should favor logging errors over throwing RuntimeExceptions.
* @param why Why we're stopping.
*/
void stop(String why);

View File

@ -233,7 +233,7 @@ public class TestChoreService {
@Test (timeout=20000)
public void testInitialChorePrecedence() throws InterruptedException {
ChoreService service = ChoreService.getInstance("testInitialChorePrecedence");
ChoreService service = new ChoreService("testInitialChorePrecedence");
final int period = 100;
final int failureThreshold = 5;
@ -264,7 +264,7 @@ public class TestChoreService {
public void testCancelChore() throws InterruptedException {
final int period = 100;
ScheduledChore chore1 = new DoNothingChore("chore1", period);
ChoreService service = ChoreService.getInstance("testCancelChore");
ChoreService service = new ChoreService("testCancelChore");
try {
service.scheduleChore(chore1);
assertTrue(chore1.isScheduled());
@ -342,7 +342,7 @@ public class TestChoreService {
final int period = 100;
// Small delta that acts as time buffer (allowing chores to complete if running slowly)
final int delta = 5;
ChoreService service = ChoreService.getInstance("testFrequencyOfChores");
ChoreService service = new ChoreService("testFrequencyOfChores");
CountingChore chore = new CountingChore("countingChore", period);
try {
service.scheduleChore(chore);
@ -368,7 +368,7 @@ public class TestChoreService {
public void testForceTrigger() throws InterruptedException {
final int period = 100;
final int delta = 5;
ChoreService service = ChoreService.getInstance("testForceTrigger");
ChoreService service = new ChoreService("testForceTrigger");
final CountingChore chore = new CountingChore("countingChore", period);
try {
service.scheduleChore(chore);
@ -715,7 +715,7 @@ public class TestChoreService {
@Test (timeout=20000)
public void testStopperForScheduledChores() throws InterruptedException {
ChoreService service = ChoreService.getInstance("testStopperForScheduledChores");
ChoreService service = new ChoreService("testStopperForScheduledChores");
Stoppable stopperForGroup1 = new SampleStopper();
Stoppable stopperForGroup2 = new SampleStopper();
final int period = 100;

View File

@ -82,8 +82,8 @@ public class TableAuthManager implements Closeable {
}
/**
* Returns a combined map of user and group permissions, with group names prefixed by
* {@link AuthUtil#GROUP_PREFIX}.
* Returns a combined map of user and group permissions, with group names
* distinguished according to {@link AuthUtil.isGroupPrincipal}
*/
public ListMultimap<String,T> getAllPermissions() {
ListMultimap<String,T> tmp = ArrayListMultimap.create();