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) { if (superUsers != null) {
List<String> groups = new ArrayList<String>(); List<String> groups = new ArrayList<String>();
for (String user : superUsers) { 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 // TODO: Set node ACL for groups when ZK supports this feature
groups.add(user); groups.add(user);
} else { } else {

View File

@ -341,7 +341,7 @@ public class ZooKeeperWatcher implements Watcher, Abortable, Closeable {
for (String user : superUsers) { for (String user : superUsers) {
boolean hasAccess = false; boolean hasAccess = false;
// TODO: Validate super group members also when ZK supports setting node ACL for groups. // 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) { for (ACL acl : acls) {
if (user.equals(acl.getId().getId())) { if (user.equals(acl.getId().getId())) {
if (acl.getPerms() == Perms.ALL) { if (acl.getPerms() == Perms.ALL) {
@ -370,7 +370,7 @@ public class ZooKeeperWatcher implements Watcher, Abortable, Closeable {
public static boolean isSuperUserId(String[] superUsers, Id id) { public static boolean isSuperUserId(String[] superUsers, Id id) {
for (String user : superUsers) { for (String user : superUsers) {
// TODO: Validate super group members also when ZK supports setting node ACL for groups. // 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; return true;
} }
} }

View File

@ -32,15 +32,48 @@ import org.apache.hadoop.hbase.util.Strings;
import org.apache.hadoop.security.UserGroupInformation; 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 @InterfaceAudience.Public
@InterfaceStability.Evolving @InterfaceStability.Stable
public class AuthUtil { public class AuthUtil {
private static final Log LOG = LogFactory.getLog(AuthUtil.class); private static final Log LOG = LogFactory.getLog(AuthUtil.class);
/** Prefix character to denote group names */ /** Prefix character to denote group names */
public static final String GROUP_PREFIX = "@"; private static final String GROUP_PREFIX = "@";
private AuthUtil() { private AuthUtil() {
super(); super();
@ -48,6 +81,8 @@ public class AuthUtil {
/** /**
* Checks if security is enabled and if so, launches chore for refreshing kerberos ticket. * 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 { public static ScheduledChore getAuthChore(Configuration conf) throws IOException {
UserProvider userProvider = UserProvider.instantiate(conf); UserProvider userProvider = UserProvider.instantiate(conf);
@ -109,6 +144,7 @@ public class AuthUtil {
* principal. Currently this simply checks if the name starts with the * principal. Currently this simply checks if the name starts with the
* special group prefix character ("@"). * special group prefix character ("@").
*/ */
@InterfaceAudience.Private
public static boolean isGroupPrincipal(String name) { public static boolean isGroupPrincipal(String name) {
return name != null && name.startsWith(GROUP_PREFIX); 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 * Returns the actual name for a group principal (stripped of the
* group prefix). * group prefix).
*/ */
@InterfaceAudience.Private
public static String getGroupName(String aclKey) { public static String getGroupName(String aclKey) {
if (!isGroupPrincipal(aclKey)) { if (!isGroupPrincipal(aclKey)) {
return aclKey; return aclKey;
@ -128,6 +165,7 @@ public class AuthUtil {
/** /**
* Returns the group entry with the group prefix for a group principal. * Returns the group entry with the group prefix for a group principal.
*/ */
@InterfaceAudience.Private
public static String toGroupEntry(String name) { public static String toGroupEntry(String name) {
return GROUP_PREFIX + 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.commons.logging.LogFactory;
import org.apache.hadoop.hbase.ScheduledChore.ChoreServicer; import org.apache.hadoop.hbase.ScheduledChore.ChoreServicer;
import org.apache.hadoop.hbase.classification.InterfaceAudience; 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 * 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()}. * 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. * 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 { public class ChoreService implements ChoreServicer {
private static final Log LOG = LogFactory.getLog(ChoreService.class); private static final Log LOG = LogFactory.getLog(ChoreService.class);
/** /**
* The minimum number of threads in the core pool of the underlying ScheduledThreadPoolExecutor * The minimum number of threads in the core pool of the underlying ScheduledThreadPoolExecutor
*/ */
@InterfaceAudience.Private
public final static int MIN_CORE_POOL_SIZE = 1; 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 * @param coreThreadPoolPrefix Prefix that will be applied to the Thread name of all threads
* spawned by this service * spawned by this service
*/ */
@InterfaceAudience.Private
@VisibleForTesting @VisibleForTesting
public ChoreService(final String coreThreadPoolPrefix) { public ChoreService(final String coreThreadPoolPrefix) {
this(coreThreadPoolPrefix, MIN_CORE_POOL_SIZE, false); 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 * @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. * 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 * @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 * 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. * 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) { public ChoreService(final String coreThreadPoolPrefix, int corePoolSize, boolean jitter) {
this.coreThreadPoolPrefix = coreThreadPoolPrefix; this.coreThreadPoolPrefix = coreThreadPoolPrefix;
@ -130,14 +138,6 @@ public class ChoreService implements ChoreServicer {
choresMissingStartTime = new HashMap<ScheduledChore, Boolean>(); 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 * @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 * 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); scheduleChore(chore);
} }
@InterfaceAudience.Private
@Override @Override
public synchronized void cancelChore(ScheduledChore chore) { public synchronized void cancelChore(ScheduledChore chore) {
cancelChore(chore, true); cancelChore(chore, true);
} }
@InterfaceAudience.Private
@Override @Override
public synchronized void cancelChore(ScheduledChore chore, boolean mayInterruptIfRunning) { public synchronized void cancelChore(ScheduledChore chore, boolean mayInterruptIfRunning) {
if (chore != null && scheduledChores.containsKey(chore)) { if (chore != null && scheduledChores.containsKey(chore)) {
@ -200,12 +202,14 @@ public class ChoreService implements ChoreServicer {
} }
} }
@InterfaceAudience.Private
@Override @Override
public synchronized boolean isChoreScheduled(ScheduledChore chore) { public synchronized boolean isChoreScheduled(ScheduledChore chore) {
return chore != null && scheduledChores.containsKey(chore) return chore != null && scheduledChores.containsKey(chore)
&& !scheduledChores.get(chore).isDone(); && !scheduledChores.get(chore).isDone();
} }
@InterfaceAudience.Private
@Override @Override
public synchronized boolean triggerNow(ScheduledChore chore) { public synchronized boolean triggerNow(ScheduledChore chore) {
if (chore == null) { if (chore == null) {
@ -293,6 +297,7 @@ public class ChoreService implements ChoreServicer {
} }
} }
@InterfaceAudience.Private
@Override @Override
public synchronized void onChoreMissedStartTime(ScheduledChore chore) { public synchronized void onChoreMissedStartTime(ScheduledChore chore) {
if (chore == null || !scheduledChores.containsKey(chore)) return; 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.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.classification.InterfaceAudience;
import org.apache.hadoop.hbase.classification.InterfaceStability;
import com.google.common.annotations.VisibleForTesting; 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 * 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. * an entry being added to a queue, etc.
*/ */
@InterfaceAudience.Private @InterfaceAudience.Public
@InterfaceStability.Stable
public abstract class ScheduledChore implements Runnable { public abstract class ScheduledChore implements Runnable {
private static final Log LOG = LogFactory.getLog(ScheduledChore.class); 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. * This constructor is for test only. It allows us to create an object and to call chore() on it.
*/ */
@InterfaceAudience.Private
@VisibleForTesting
protected ScheduledChore() { protected ScheduledChore() {
this.name = null; this.name = null;
this.stopper = 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 @Override
public void run() { public void run() {
@ -327,6 +331,7 @@ public abstract class ScheduledChore implements Runnable {
return choreServicer != null && choreServicer.isChoreScheduled(this); return choreServicer != null && choreServicer.isChoreScheduled(this);
} }
@InterfaceAudience.Private
@VisibleForTesting @VisibleForTesting
public synchronized void choreForTesting() { public synchronized void choreForTesting() {
chore(); chore();
@ -352,6 +357,12 @@ public abstract class ScheduledChore implements Runnable {
protected synchronized void cleanup() { 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 @Override
public String toString() { public String toString() {
return "[ScheduledChore: Name: " + getName() + " Period: " + getPeriod() + " Unit: " return "[ScheduledChore: Name: " + getName() + " Period: " + getPeriod() + " Unit: "

View File

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

View File

@ -233,7 +233,7 @@ public class TestChoreService {
@Test (timeout=20000) @Test (timeout=20000)
public void testInitialChorePrecedence() throws InterruptedException { public void testInitialChorePrecedence() throws InterruptedException {
ChoreService service = ChoreService.getInstance("testInitialChorePrecedence"); ChoreService service = new ChoreService("testInitialChorePrecedence");
final int period = 100; final int period = 100;
final int failureThreshold = 5; final int failureThreshold = 5;
@ -264,7 +264,7 @@ public class TestChoreService {
public void testCancelChore() throws InterruptedException { public void testCancelChore() throws InterruptedException {
final int period = 100; final int period = 100;
ScheduledChore chore1 = new DoNothingChore("chore1", period); ScheduledChore chore1 = new DoNothingChore("chore1", period);
ChoreService service = ChoreService.getInstance("testCancelChore"); ChoreService service = new ChoreService("testCancelChore");
try { try {
service.scheduleChore(chore1); service.scheduleChore(chore1);
assertTrue(chore1.isScheduled()); assertTrue(chore1.isScheduled());
@ -342,7 +342,7 @@ public class TestChoreService {
final int period = 100; final int period = 100;
// Small delta that acts as time buffer (allowing chores to complete if running slowly) // Small delta that acts as time buffer (allowing chores to complete if running slowly)
final int delta = 5; final int delta = 5;
ChoreService service = ChoreService.getInstance("testFrequencyOfChores"); ChoreService service = new ChoreService("testFrequencyOfChores");
CountingChore chore = new CountingChore("countingChore", period); CountingChore chore = new CountingChore("countingChore", period);
try { try {
service.scheduleChore(chore); service.scheduleChore(chore);
@ -368,7 +368,7 @@ public class TestChoreService {
public void testForceTrigger() throws InterruptedException { public void testForceTrigger() throws InterruptedException {
final int period = 100; final int period = 100;
final int delta = 5; final int delta = 5;
ChoreService service = ChoreService.getInstance("testForceTrigger"); ChoreService service = new ChoreService("testForceTrigger");
final CountingChore chore = new CountingChore("countingChore", period); final CountingChore chore = new CountingChore("countingChore", period);
try { try {
service.scheduleChore(chore); service.scheduleChore(chore);
@ -715,7 +715,7 @@ public class TestChoreService {
@Test (timeout=20000) @Test (timeout=20000)
public void testStopperForScheduledChores() throws InterruptedException { public void testStopperForScheduledChores() throws InterruptedException {
ChoreService service = ChoreService.getInstance("testStopperForScheduledChores"); ChoreService service = new ChoreService("testStopperForScheduledChores");
Stoppable stopperForGroup1 = new SampleStopper(); Stoppable stopperForGroup1 = new SampleStopper();
Stoppable stopperForGroup2 = new SampleStopper(); Stoppable stopperForGroup2 = new SampleStopper();
final int period = 100; 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 * Returns a combined map of user and group permissions, with group names
* {@link AuthUtil#GROUP_PREFIX}. * distinguished according to {@link AuthUtil.isGroupPrincipal}
*/ */
public ListMultimap<String,T> getAllPermissions() { public ListMultimap<String,T> getAllPermissions() {
ListMultimap<String,T> tmp = ArrayListMultimap.create(); ListMultimap<String,T> tmp = ArrayListMultimap.create();