HBASE-20886 [Auth] Support keytab login in hbase client
Signed-off-by: Sean Busbey <busbey@apache.org>
This commit is contained in:
parent
584093c23f
commit
e14b60a539
|
@ -21,6 +21,9 @@ import static org.apache.hadoop.hbase.client.ConnectionUtils.NO_NONCE_GENERATOR;
|
||||||
import static org.apache.hadoop.hbase.client.ConnectionUtils.getStubKey;
|
import static org.apache.hadoop.hbase.client.ConnectionUtils.getStubKey;
|
||||||
import static org.apache.hadoop.hbase.client.NonceGenerator.CLIENT_NONCES_ENABLED_KEY;
|
import static org.apache.hadoop.hbase.client.NonceGenerator.CLIENT_NONCES_ENABLED_KEY;
|
||||||
|
|
||||||
|
import org.apache.hadoop.hbase.AuthUtil;
|
||||||
|
import org.apache.hadoop.hbase.ChoreService;
|
||||||
|
import org.apache.hadoop.security.UserGroupInformation;
|
||||||
import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting;
|
import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting;
|
||||||
|
|
||||||
import org.apache.hbase.thirdparty.io.netty.util.HashedWheelTimer;
|
import org.apache.hbase.thirdparty.io.netty.util.HashedWheelTimer;
|
||||||
|
@ -99,10 +102,15 @@ class AsyncConnectionImpl implements AsyncConnection {
|
||||||
private final AtomicReference<CompletableFuture<MasterService.Interface>> masterStubMakeFuture =
|
private final AtomicReference<CompletableFuture<MasterService.Interface>> masterStubMakeFuture =
|
||||||
new AtomicReference<>();
|
new AtomicReference<>();
|
||||||
|
|
||||||
|
private ChoreService authService;
|
||||||
|
|
||||||
public AsyncConnectionImpl(Configuration conf, AsyncRegistry registry, String clusterId,
|
public AsyncConnectionImpl(Configuration conf, AsyncRegistry registry, String clusterId,
|
||||||
User user) {
|
User user) {
|
||||||
this.conf = conf;
|
this.conf = conf;
|
||||||
this.user = user;
|
this.user = user;
|
||||||
|
if (user.isLoginFromKeytab()) {
|
||||||
|
spawnRenewalChore(user.getUGI());
|
||||||
|
}
|
||||||
this.connConf = new AsyncConnectionConfiguration(conf);
|
this.connConf = new AsyncConnectionConfiguration(conf);
|
||||||
this.registry = registry;
|
this.registry = registry;
|
||||||
this.rpcClient = RpcClientFactory.createClient(conf, clusterId);
|
this.rpcClient = RpcClientFactory.createClient(conf, clusterId);
|
||||||
|
@ -119,6 +127,11 @@ class AsyncConnectionImpl implements AsyncConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void spawnRenewalChore(final UserGroupInformation user) {
|
||||||
|
authService = new ChoreService("Relogin service");
|
||||||
|
authService.scheduleChore(AuthUtil.getAuthRenewalChore(user));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Configuration getConfiguration() {
|
public Configuration getConfiguration() {
|
||||||
return conf;
|
return conf;
|
||||||
|
@ -128,6 +141,9 @@ class AsyncConnectionImpl implements AsyncConnection {
|
||||||
public void close() {
|
public void close() {
|
||||||
IOUtils.closeQuietly(rpcClient);
|
IOUtils.closeQuietly(rpcClient);
|
||||||
IOUtils.closeQuietly(registry);
|
IOUtils.closeQuietly(registry);
|
||||||
|
if (authService != null) {
|
||||||
|
authService.shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -20,10 +20,12 @@ package org.apache.hadoop.hbase.client;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.security.PrivilegedExceptionAction;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
import org.apache.hadoop.conf.Configuration;
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.hbase.AuthUtil;
|
||||||
import org.apache.hadoop.hbase.HBaseConfiguration;
|
import org.apache.hadoop.hbase.HBaseConfiguration;
|
||||||
import org.apache.yetus.audience.InterfaceAudience;
|
import org.apache.yetus.audience.InterfaceAudience;
|
||||||
import org.apache.hadoop.hbase.security.User;
|
import org.apache.hadoop.hbase.security.User;
|
||||||
|
@ -47,6 +49,16 @@ import org.apache.hadoop.hbase.util.ReflectionUtils;
|
||||||
* }
|
* }
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
|
* Since 2.2.0, Connection created by ConnectionFactory can contain user-specified kerberos
|
||||||
|
* credentials if caller has following two configurations set:
|
||||||
|
* <ul>
|
||||||
|
* <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>
|
||||||
|
* By this way, caller can directly connect to kerberized cluster without caring login and
|
||||||
|
* credentials renewal logic in application.
|
||||||
|
* <pre>
|
||||||
|
* </pre>
|
||||||
* Similarly, {@link Connection} also returns {@link Admin} and {@link RegionLocator}
|
* Similarly, {@link Connection} also returns {@link Admin} and {@link RegionLocator}
|
||||||
* implementations.
|
* implementations.
|
||||||
* @see Connection
|
* @see Connection
|
||||||
|
@ -84,7 +96,8 @@ public class ConnectionFactory {
|
||||||
* @return Connection object for <code>conf</code>
|
* @return Connection object for <code>conf</code>
|
||||||
*/
|
*/
|
||||||
public static Connection createConnection() throws IOException {
|
public static Connection createConnection() throws IOException {
|
||||||
return createConnection(HBaseConfiguration.create(), null, null);
|
Configuration conf = HBaseConfiguration.create();
|
||||||
|
return createConnection(conf, null, AuthUtil.loginClient(conf));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -111,7 +124,7 @@ public class ConnectionFactory {
|
||||||
* @return Connection object for <code>conf</code>
|
* @return Connection object for <code>conf</code>
|
||||||
*/
|
*/
|
||||||
public static Connection createConnection(Configuration conf) throws IOException {
|
public static Connection createConnection(Configuration conf) throws IOException {
|
||||||
return createConnection(conf, null, null);
|
return createConnection(conf, null, AuthUtil.loginClient(conf));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -140,7 +153,7 @@ public class ConnectionFactory {
|
||||||
*/
|
*/
|
||||||
public static Connection createConnection(Configuration conf, ExecutorService pool)
|
public static Connection createConnection(Configuration conf, ExecutorService pool)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
return createConnection(conf, pool, null);
|
return createConnection(conf, pool, AuthUtil.loginClient(conf));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -196,13 +209,8 @@ public class ConnectionFactory {
|
||||||
* @param pool the thread pool to use for batch operations
|
* @param pool the thread pool to use for batch operations
|
||||||
* @return Connection object for <code>conf</code>
|
* @return Connection object for <code>conf</code>
|
||||||
*/
|
*/
|
||||||
public static Connection createConnection(Configuration conf, ExecutorService pool, User user)
|
public static Connection createConnection(Configuration conf, ExecutorService pool,
|
||||||
throws IOException {
|
final User user) throws IOException {
|
||||||
if (user == null) {
|
|
||||||
UserProvider provider = UserProvider.instantiate(conf);
|
|
||||||
user = provider.getCurrent();
|
|
||||||
}
|
|
||||||
|
|
||||||
String className = conf.get(ClusterConnection.HBASE_CLIENT_CONNECTION_IMPL,
|
String className = conf.get(ClusterConnection.HBASE_CLIENT_CONNECTION_IMPL,
|
||||||
ConnectionImplementation.class.getName());
|
ConnectionImplementation.class.getName());
|
||||||
Class<?> clazz;
|
Class<?> clazz;
|
||||||
|
@ -216,7 +224,9 @@ public class ConnectionFactory {
|
||||||
Constructor<?> constructor = clazz.getDeclaredConstructor(Configuration.class,
|
Constructor<?> constructor = clazz.getDeclaredConstructor(Configuration.class,
|
||||||
ExecutorService.class, User.class);
|
ExecutorService.class, User.class);
|
||||||
constructor.setAccessible(true);
|
constructor.setAccessible(true);
|
||||||
return (Connection) constructor.newInstance(conf, pool, user);
|
return user.runAs(
|
||||||
|
(PrivilegedExceptionAction<Connection>)() ->
|
||||||
|
(Connection) constructor.newInstance(conf, pool, user));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new IOException(e);
|
throw new IOException(e);
|
||||||
}
|
}
|
||||||
|
@ -243,7 +253,7 @@ public class ConnectionFactory {
|
||||||
public static CompletableFuture<AsyncConnection> createAsyncConnection(Configuration conf) {
|
public static CompletableFuture<AsyncConnection> createAsyncConnection(Configuration conf) {
|
||||||
User user;
|
User user;
|
||||||
try {
|
try {
|
||||||
user = UserProvider.instantiate(conf).getCurrent();
|
user = AuthUtil.loginClient(conf);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
CompletableFuture<AsyncConnection> future = new CompletableFuture<>();
|
CompletableFuture<AsyncConnection> future = new CompletableFuture<>();
|
||||||
future.completeExceptionally(e);
|
future.completeExceptionally(e);
|
||||||
|
@ -269,7 +279,7 @@ public class ConnectionFactory {
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public static CompletableFuture<AsyncConnection> createAsyncConnection(Configuration conf,
|
public static CompletableFuture<AsyncConnection> createAsyncConnection(Configuration conf,
|
||||||
User user) {
|
final User user) {
|
||||||
CompletableFuture<AsyncConnection> future = new CompletableFuture<>();
|
CompletableFuture<AsyncConnection> future = new CompletableFuture<>();
|
||||||
AsyncRegistry registry = AsyncRegistryFactory.getRegistry(conf);
|
AsyncRegistry registry = AsyncRegistryFactory.getRegistry(conf);
|
||||||
registry.getClusterId().whenComplete((clusterId, error) -> {
|
registry.getClusterId().whenComplete((clusterId, error) -> {
|
||||||
|
@ -284,7 +294,10 @@ public class ConnectionFactory {
|
||||||
Class<? extends AsyncConnection> clazz = conf.getClass(HBASE_CLIENT_ASYNC_CONNECTION_IMPL,
|
Class<? extends AsyncConnection> clazz = conf.getClass(HBASE_CLIENT_ASYNC_CONNECTION_IMPL,
|
||||||
AsyncConnectionImpl.class, AsyncConnection.class);
|
AsyncConnectionImpl.class, AsyncConnection.class);
|
||||||
try {
|
try {
|
||||||
future.complete(ReflectionUtils.newInstance(clazz, conf, registry, clusterId, user));
|
future.complete(
|
||||||
|
user.runAs((PrivilegedExceptionAction<? extends AsyncConnection>)() ->
|
||||||
|
ReflectionUtils.newInstance(clazz, conf, registry, clusterId, user))
|
||||||
|
);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
future.completeExceptionally(e);
|
future.completeExceptionally(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,9 @@ import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import org.apache.hadoop.conf.Configuration;
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.hbase.AuthUtil;
|
||||||
import org.apache.hadoop.hbase.CallQueueTooBigException;
|
import org.apache.hadoop.hbase.CallQueueTooBigException;
|
||||||
|
import org.apache.hadoop.hbase.ChoreService;
|
||||||
import org.apache.hadoop.hbase.DoNotRetryIOException;
|
import org.apache.hadoop.hbase.DoNotRetryIOException;
|
||||||
import org.apache.hadoop.hbase.HConstants;
|
import org.apache.hadoop.hbase.HConstants;
|
||||||
import org.apache.hadoop.hbase.HRegionLocation;
|
import org.apache.hadoop.hbase.HRegionLocation;
|
||||||
|
@ -76,6 +78,7 @@ import org.apache.hadoop.hbase.util.Pair;
|
||||||
import org.apache.hadoop.hbase.util.ReflectionUtils;
|
import org.apache.hadoop.hbase.util.ReflectionUtils;
|
||||||
import org.apache.hadoop.hbase.util.Threads;
|
import org.apache.hadoop.hbase.util.Threads;
|
||||||
import org.apache.hadoop.ipc.RemoteException;
|
import org.apache.hadoop.ipc.RemoteException;
|
||||||
|
import org.apache.hadoop.security.UserGroupInformation;
|
||||||
import org.apache.yetus.audience.InterfaceAudience;
|
import org.apache.yetus.audience.InterfaceAudience;
|
||||||
import org.apache.zookeeper.KeeperException;
|
import org.apache.zookeeper.KeeperException;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -215,6 +218,8 @@ class ConnectionImplementation implements ClusterConnection, Closeable {
|
||||||
/** lock guards against multiple threads trying to query the meta region at the same time */
|
/** lock guards against multiple threads trying to query the meta region at the same time */
|
||||||
private final ReentrantLock userRegionLock = new ReentrantLock();
|
private final ReentrantLock userRegionLock = new ReentrantLock();
|
||||||
|
|
||||||
|
private ChoreService authService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* constructor
|
* constructor
|
||||||
* @param conf Configuration object
|
* @param conf Configuration object
|
||||||
|
@ -223,6 +228,9 @@ class ConnectionImplementation implements ClusterConnection, Closeable {
|
||||||
ExecutorService pool, User user) throws IOException {
|
ExecutorService pool, User user) throws IOException {
|
||||||
this.conf = conf;
|
this.conf = conf;
|
||||||
this.user = user;
|
this.user = user;
|
||||||
|
if (user != null && user.isLoginFromKeytab()) {
|
||||||
|
spawnRenewalChore(user.getUGI());
|
||||||
|
}
|
||||||
this.batchPool = pool;
|
this.batchPool = pool;
|
||||||
this.connectionConfig = new ConnectionConfiguration(conf);
|
this.connectionConfig = new ConnectionConfiguration(conf);
|
||||||
this.closed = false;
|
this.closed = false;
|
||||||
|
@ -312,6 +320,11 @@ class ConnectionImplementation implements ClusterConnection, Closeable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void spawnRenewalChore(final UserGroupInformation user) {
|
||||||
|
authService = new ChoreService("Relogin service");
|
||||||
|
authService.scheduleChore(AuthUtil.getAuthRenewalChore(user));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param useMetaReplicas
|
* @param useMetaReplicas
|
||||||
*/
|
*/
|
||||||
|
@ -1925,6 +1938,9 @@ class ConnectionImplementation implements ClusterConnection, Closeable {
|
||||||
if (rpcClient != null) {
|
if (rpcClient != null) {
|
||||||
rpcClient.close();
|
rpcClient.close();
|
||||||
}
|
}
|
||||||
|
if (authService != null) {
|
||||||
|
authService.shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -22,6 +22,7 @@ import java.io.IOException;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
import org.apache.hadoop.conf.Configuration;
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.hbase.security.User;
|
||||||
import org.apache.hadoop.hbase.security.UserProvider;
|
import org.apache.hadoop.hbase.security.UserProvider;
|
||||||
import org.apache.hadoop.hbase.util.DNS;
|
import org.apache.hadoop.hbase.util.DNS;
|
||||||
import org.apache.hadoop.hbase.util.Strings;
|
import org.apache.hadoop.hbase.util.Strings;
|
||||||
|
@ -65,35 +66,112 @@ import org.slf4j.LoggerFactory;
|
||||||
*
|
*
|
||||||
* See the "Running Canary in a Kerberos-enabled Cluster" section of the HBase Reference Guide for
|
* 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.
|
* an example of configuring a user of this Auth Chore to run on a secure cluster.
|
||||||
|
* <pre>
|
||||||
|
* </pre>
|
||||||
|
* This class will be internal use only from 2.2.0 version, and will transparently work
|
||||||
|
* for kerberized applications. For more, please refer
|
||||||
|
* <a href="http://hbase.apache.org/book.html#hbase.secure.configuration">Client-side Configuration for Secure Operation</a>
|
||||||
|
*
|
||||||
|
* @deprecated since 2.2.0, to be removed in hbase-3.0.0.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
@InterfaceAudience.Public
|
@InterfaceAudience.Public
|
||||||
public class AuthUtil {
|
public final class AuthUtil {
|
||||||
|
// TODO: Mark this class InterfaceAudience.Private from 3.0.0
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(AuthUtil.class);
|
private static final Logger LOG = LoggerFactory.getLogger(AuthUtil.class);
|
||||||
|
|
||||||
/** Prefix character to denote group names */
|
/** Prefix character to denote group names */
|
||||||
private static final String GROUP_PREFIX = "@";
|
private static final String GROUP_PREFIX = "@";
|
||||||
|
|
||||||
|
/** Client keytab file */
|
||||||
|
public static final String HBASE_CLIENT_KEYTAB_FILE = "hbase.client.keytab.file";
|
||||||
|
|
||||||
|
/** Client principal */
|
||||||
|
public static final String HBASE_CLIENT_KERBEROS_PRINCIPAL = "hbase.client.keytab.principal";
|
||||||
|
|
||||||
private AuthUtil() {
|
private AuthUtil() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if security is enabled and if so, launches chore for refreshing kerberos ticket.
|
* For kerberized cluster, return login user (from kinit or from keytab if specified).
|
||||||
* @param conf the hbase service configuration
|
* For non-kerberized cluster, return system user.
|
||||||
* @return a ScheduledChore for renewals, if needed, and null otherwise.
|
* @param conf configuartion file
|
||||||
|
* @return user
|
||||||
|
* @throws IOException login exception
|
||||||
*/
|
*/
|
||||||
public static ScheduledChore getAuthChore(Configuration conf) throws IOException {
|
@InterfaceAudience.Private
|
||||||
UserProvider userProvider = UserProvider.instantiate(conf);
|
public static User loginClient(Configuration conf) throws IOException {
|
||||||
// login the principal (if using secure Hadoop)
|
UserProvider provider = UserProvider.instantiate(conf);
|
||||||
boolean securityEnabled =
|
User user = provider.getCurrent();
|
||||||
userProvider.isHadoopSecurityEnabled() && userProvider.isHBaseSecurityEnabled();
|
boolean securityOn = provider.isHBaseSecurityEnabled() && provider.isHadoopSecurityEnabled();
|
||||||
if (!securityEnabled) return null;
|
|
||||||
String host = null;
|
if (securityOn) {
|
||||||
|
boolean fromKeytab = provider.shouldLoginFromKeytab();
|
||||||
|
if (user.getUGI().hasKerberosCredentials()) {
|
||||||
|
// There's already a login user.
|
||||||
|
// But we should avoid misuse credentials which is a dangerous security issue,
|
||||||
|
// so here check whether user specified a keytab and a principal:
|
||||||
|
// 1. Yes, check if user principal match.
|
||||||
|
// a. match, just return.
|
||||||
|
// b. mismatch, login using keytab.
|
||||||
|
// 2. No, user may login through kinit, this is the old way, also just return.
|
||||||
|
if (fromKeytab) {
|
||||||
|
return checkPrincipalMatch(conf, user.getUGI().getUserName()) ? user :
|
||||||
|
loginFromKeytabAndReturnUser(provider);
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
} else if (fromKeytab) {
|
||||||
|
// Kerberos is on and client specify a keytab and principal, but client doesn't login yet.
|
||||||
|
return loginFromKeytabAndReturnUser(provider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean checkPrincipalMatch(Configuration conf, String loginUserName) {
|
||||||
|
String configuredUserName = conf.get(HBASE_CLIENT_KERBEROS_PRINCIPAL);
|
||||||
|
boolean match = configuredUserName.equals(loginUserName);
|
||||||
|
if (!match) {
|
||||||
|
LOG.warn("Trying to login with a different user: {}, existed user is {}.",
|
||||||
|
configuredUserName, loginUserName);
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static User loginFromKeytabAndReturnUser(UserProvider provider) throws IOException {
|
||||||
try {
|
try {
|
||||||
host = Strings.domainNamePointerToHostName(DNS.getDefaultHost(
|
provider.login(HBASE_CLIENT_KEYTAB_FILE, HBASE_CLIENT_KERBEROS_PRINCIPAL);
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
LOG.error("Error while trying to login as user {} through {}, with message: {}.",
|
||||||
|
HBASE_CLIENT_KERBEROS_PRINCIPAL, HBASE_CLIENT_KEYTAB_FILE,
|
||||||
|
ioe.getMessage());
|
||||||
|
throw ioe;
|
||||||
|
}
|
||||||
|
return provider.getCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For kerberized cluster, return login user (from kinit or from keytab).
|
||||||
|
* Principal should be the following format: name/fully.qualified.domain.name@REALM.
|
||||||
|
* For non-kerberized cluster, return system user.
|
||||||
|
* <p>
|
||||||
|
* NOT recommend to use to method unless you're sure what you're doing, it is for canary only.
|
||||||
|
* Please use User#loginClient.
|
||||||
|
* @param conf configuration file
|
||||||
|
* @return user
|
||||||
|
* @throws IOException login exception
|
||||||
|
*/
|
||||||
|
private static User loginClientAsService(Configuration conf) throws IOException {
|
||||||
|
UserProvider provider = UserProvider.instantiate(conf);
|
||||||
|
if (provider.isHBaseSecurityEnabled() && provider.isHadoopSecurityEnabled()) {
|
||||||
|
try {
|
||||||
|
if (provider.shouldLoginFromKeytab()) {
|
||||||
|
String host = Strings.domainNamePointerToHostName(DNS.getDefaultHost(
|
||||||
conf.get("hbase.client.dns.interface", "default"),
|
conf.get("hbase.client.dns.interface", "default"),
|
||||||
conf.get("hbase.client.dns.nameserver", "default")));
|
conf.get("hbase.client.dns.nameserver", "default")));
|
||||||
userProvider.login("hbase.client.keytab.file", "hbase.client.kerberos.principal", host);
|
provider.login(HBASE_CLIENT_KEYTAB_FILE, HBASE_CLIENT_KERBEROS_PRINCIPAL, host);
|
||||||
|
}
|
||||||
} catch (UnknownHostException e) {
|
} catch (UnknownHostException e) {
|
||||||
LOG.error("Error resolving host name: " + e.getMessage(), e);
|
LOG.error("Error resolving host name: " + e.getMessage(), e);
|
||||||
throw e;
|
throw e;
|
||||||
|
@ -101,9 +179,52 @@ public class AuthUtil {
|
||||||
LOG.error("Error while trying to perform the initial login: " + e.getMessage(), e);
|
LOG.error("Error while trying to perform the initial login: " + e.getMessage(), e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return provider.getCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
final UserGroupInformation ugi = userProvider.getCurrent().getUGI();
|
/**
|
||||||
Stoppable stoppable = new Stoppable() {
|
* Checks if security is enabled and if so, launches chore for refreshing kerberos ticket.
|
||||||
|
* @return a ScheduledChore for renewals.
|
||||||
|
*/
|
||||||
|
@InterfaceAudience.Private
|
||||||
|
public static ScheduledChore getAuthRenewalChore(final UserGroupInformation user) {
|
||||||
|
if (!user.hasKerberosCredentials()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stoppable stoppable = createDummyStoppable();
|
||||||
|
// if you're in debug mode this is useful to avoid getting spammed by the getTGT()
|
||||||
|
// you can increase this, keeping in mind that the default refresh window is 0.8
|
||||||
|
// e.g. 5min tgt * 0.8 = 4min refresh so interval is better be way less than 1min
|
||||||
|
final int CHECK_TGT_INTERVAL = 30 * 1000; // 30sec
|
||||||
|
return new ScheduledChore("RefreshCredentials", stoppable, CHECK_TGT_INTERVAL) {
|
||||||
|
@Override
|
||||||
|
protected void chore() {
|
||||||
|
try {
|
||||||
|
user.checkTGTAndReloginFromKeytab();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error("Got exception while trying to refresh credentials: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
* @deprecated Deprecated since 2.2.0, this method will be internal use only after 3.0.0.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public static ScheduledChore getAuthChore(Configuration conf) throws IOException {
|
||||||
|
// TODO: Mark this method InterfaceAudience.Private from 3.0.0
|
||||||
|
User user = loginClientAsService(conf);
|
||||||
|
return getAuthRenewalChore(user.getUGI());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stoppable createDummyStoppable() {
|
||||||
|
return new Stoppable() {
|
||||||
private volatile boolean isStopped = false;
|
private volatile boolean isStopped = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -116,25 +237,6 @@ public class AuthUtil {
|
||||||
return isStopped;
|
return isStopped;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// if you're in debug mode this is useful to avoid getting spammed by the getTGT()
|
|
||||||
// you can increase this, keeping in mind that the default refresh window is 0.8
|
|
||||||
// e.g. 5min tgt * 0.8 = 4min refresh so interval is better be way less than 1min
|
|
||||||
final int CHECK_TGT_INTERVAL = 30 * 1000; // 30sec
|
|
||||||
|
|
||||||
ScheduledChore refreshCredentials =
|
|
||||||
new ScheduledChore("RefreshCredentials", stoppable, CHECK_TGT_INTERVAL) {
|
|
||||||
@Override
|
|
||||||
protected void chore() {
|
|
||||||
try {
|
|
||||||
ugi.checkTGTAndReloginFromKeytab();
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.error("Got exception while trying to refresh credentials: " + e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return refreshCredentials;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -27,9 +27,11 @@ import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
import org.apache.hadoop.conf.Configuration;
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.hbase.AuthUtil;
|
||||||
import org.apache.hadoop.hbase.util.Methods;
|
import org.apache.hadoop.hbase.util.Methods;
|
||||||
import org.apache.hadoop.security.Groups;
|
import org.apache.hadoop.security.Groups;
|
||||||
import org.apache.hadoop.security.SecurityUtil;
|
import org.apache.hadoop.security.SecurityUtil;
|
||||||
|
@ -136,6 +138,13 @@ public abstract class User {
|
||||||
ugi.addToken(token);
|
ugi.addToken(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if user credentials are obtained from keytab.
|
||||||
|
*/
|
||||||
|
public boolean isLoginFromKeytab() {
|
||||||
|
return ugi.isFromKeytab();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) {
|
if (this == o) {
|
||||||
|
@ -231,6 +240,16 @@ public abstract class User {
|
||||||
SecureHadoopUser.login(conf, fileConfKey, principalConfKey, localhost);
|
SecureHadoopUser.login(conf, fileConfKey, principalConfKey, localhost);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login with the given keytab and principal.
|
||||||
|
* @param keytabLocation path of keytab
|
||||||
|
* @param pricipalName login principal
|
||||||
|
* @throws IOException underlying exception from UserGroupInformation.loginUserFromKeytab
|
||||||
|
*/
|
||||||
|
public static void login(String keytabLocation, String pricipalName) throws IOException {
|
||||||
|
SecureHadoopUser.login(keytabLocation, pricipalName);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether or not Kerberos authentication is configured for Hadoop.
|
* Returns whether or not Kerberos authentication is configured for Hadoop.
|
||||||
* For non-secure Hadoop, this always returns <code>false</code>.
|
* For non-secure Hadoop, this always returns <code>false</code>.
|
||||||
|
@ -250,6 +269,21 @@ public abstract class User {
|
||||||
return "kerberos".equalsIgnoreCase(conf.get(HBASE_SECURITY_CONF_KEY));
|
return "kerberos".equalsIgnoreCase(conf.get(HBASE_SECURITY_CONF_KEY));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In secure environment, if a user specified his keytab and principal,
|
||||||
|
* a hbase client will try to login with them. Otherwise, hbase client will try to obtain
|
||||||
|
* ticket(through kinit) from system.
|
||||||
|
* @param conf configuration file
|
||||||
|
* @return true if keytab and principal are configured
|
||||||
|
*/
|
||||||
|
public static boolean shouldLoginFromKeytab(Configuration conf) {
|
||||||
|
Optional<String> keytab =
|
||||||
|
Optional.ofNullable(conf.get(AuthUtil.HBASE_CLIENT_KEYTAB_FILE));
|
||||||
|
Optional<String> principal =
|
||||||
|
Optional.ofNullable(conf.get(AuthUtil.HBASE_CLIENT_KERBEROS_PRINCIPAL));
|
||||||
|
return keytab.isPresent() && principal.isPresent();
|
||||||
|
}
|
||||||
|
|
||||||
/* Concrete implementations */
|
/* Concrete implementations */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -345,6 +379,19 @@ public abstract class User {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login through configured keytab and pricipal.
|
||||||
|
* @param keytabLocation location of keytab
|
||||||
|
* @param principalName principal in keytab
|
||||||
|
* @throws IOException exception from UserGroupInformation.loginUserFromKeytab
|
||||||
|
*/
|
||||||
|
public static void login(String keytabLocation, String principalName)
|
||||||
|
throws IOException {
|
||||||
|
if (isSecurityEnabled()) {
|
||||||
|
UserGroupInformation.loginUserFromKeytab(principalName, keytabLocation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the result of {@code UserGroupInformation.isSecurityEnabled()}.
|
* Returns the result of {@code UserGroupInformation.isSecurityEnabled()}.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -160,6 +160,15 @@ public class UserProvider extends BaseConfigurable {
|
||||||
return User.isSecurityEnabled();
|
return User.isSecurityEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In secure environment, if a user specified his keytab and principal,
|
||||||
|
* a hbase client will try to login with them. Otherwise, hbase client will try to obtain
|
||||||
|
* ticket(through kinit) from system.
|
||||||
|
*/
|
||||||
|
public boolean shouldLoginFromKeytab() {
|
||||||
|
return User.shouldLoginFromKeytab(this.getConf());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the current user within the current execution context
|
* @return the current user within the current execution context
|
||||||
* @throws IOException if the user cannot be loaded
|
* @throws IOException if the user cannot be loaded
|
||||||
|
@ -182,7 +191,8 @@ public class UserProvider extends BaseConfigurable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log in the current process using the given configuration keys for the credential file and login
|
* Log in the current process using the given configuration keys for the credential file and login
|
||||||
* principal.
|
* principal. It is for SPN(Service Principal Name) login. SPN should be this format,
|
||||||
|
* servicename/fully.qualified.domain.name@REALM.
|
||||||
* <p>
|
* <p>
|
||||||
* <strong>This is only applicable when running on secure Hadoop</strong> -- see
|
* <strong>This is only applicable when running on secure Hadoop</strong> -- see
|
||||||
* org.apache.hadoop.security.SecurityUtil#login(Configuration,String,String,String). On regular
|
* org.apache.hadoop.security.SecurityUtil#login(Configuration,String,String,String). On regular
|
||||||
|
@ -197,4 +207,15 @@ public class UserProvider extends BaseConfigurable {
|
||||||
throws IOException {
|
throws IOException {
|
||||||
User.login(getConf(), fileConfKey, principalConfKey, localhost);
|
User.login(getConf(), fileConfKey, principalConfKey, localhost);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login with given keytab and principal. This can be used for both SPN(Service Principal Name)
|
||||||
|
* and UPN(User Principal Name) which format should be clientname@REALM.
|
||||||
|
* @param fileConfKey config name for client keytab
|
||||||
|
* @param principalConfKey config name for client principal
|
||||||
|
* @throws IOException underlying exception from UserGroupInformation.loginUserFromKeytab
|
||||||
|
*/
|
||||||
|
public void login(String fileConfKey, String principalConfKey) throws IOException {
|
||||||
|
User.login(getConf().get(fileConfKey), getConf().get(principalConfKey));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.apache.hadoop.hbase.security;
|
||||||
|
|
||||||
import org.apache.hadoop.conf.Configuration;
|
import org.apache.hadoop.conf.Configuration;
|
||||||
import org.apache.hadoop.fs.CommonConfigurationKeys;
|
import org.apache.hadoop.fs.CommonConfigurationKeys;
|
||||||
|
import org.apache.hadoop.hbase.AuthUtil;
|
||||||
import org.apache.hadoop.hbase.HBaseConfiguration;
|
import org.apache.hadoop.hbase.HBaseConfiguration;
|
||||||
import org.apache.yetus.audience.InterfaceAudience;
|
import org.apache.yetus.audience.InterfaceAudience;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -36,6 +37,8 @@ public class HBaseKerberosUtils {
|
||||||
public static final String KRB_PRINCIPAL = "hbase.regionserver.kerberos.principal";
|
public static final String KRB_PRINCIPAL = "hbase.regionserver.kerberos.principal";
|
||||||
public static final String MASTER_KRB_PRINCIPAL = "hbase.master.kerberos.principal";
|
public static final String MASTER_KRB_PRINCIPAL = "hbase.master.kerberos.principal";
|
||||||
public static final String KRB_KEYTAB_FILE = "hbase.regionserver.keytab.file";
|
public static final String KRB_KEYTAB_FILE = "hbase.regionserver.keytab.file";
|
||||||
|
public static final String CLIENT_PRINCIPAL = AuthUtil.HBASE_CLIENT_KERBEROS_PRINCIPAL;
|
||||||
|
public static final String CLIENT_KEYTAB = AuthUtil.HBASE_CLIENT_KEYTAB_FILE;
|
||||||
|
|
||||||
public static boolean isKerberosPropertySetted() {
|
public static boolean isKerberosPropertySetted() {
|
||||||
String krbPrincipal = System.getProperty(KRB_PRINCIPAL);
|
String krbPrincipal = System.getProperty(KRB_PRINCIPAL);
|
||||||
|
@ -54,6 +57,14 @@ public class HBaseKerberosUtils {
|
||||||
setSystemProperty(KRB_KEYTAB_FILE, keytabFile);
|
setSystemProperty(KRB_KEYTAB_FILE, keytabFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setClientPrincipalForTesting(String clientPrincipal) {
|
||||||
|
setSystemProperty(CLIENT_PRINCIPAL, clientPrincipal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setClientKeytabForTesting(String clientKeytab) {
|
||||||
|
setSystemProperty(CLIENT_KEYTAB, clientKeytab);
|
||||||
|
}
|
||||||
|
|
||||||
public static void setSystemProperty(String propertyName, String propertyValue) {
|
public static void setSystemProperty(String propertyName, String propertyValue) {
|
||||||
System.setProperty(propertyName, propertyValue);
|
System.setProperty(propertyName, propertyValue);
|
||||||
}
|
}
|
||||||
|
@ -66,6 +77,14 @@ public class HBaseKerberosUtils {
|
||||||
return System.getProperty(KRB_PRINCIPAL);
|
return System.getProperty(KRB_PRINCIPAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getClientPrincipalForTesting() {
|
||||||
|
return System.getProperty(CLIENT_PRINCIPAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getClientKeytabForTesting() {
|
||||||
|
return System.getProperty(CLIENT_KEYTAB);
|
||||||
|
}
|
||||||
|
|
||||||
public static Configuration getConfigurationWoPrincipal() {
|
public static Configuration getConfigurationWoPrincipal() {
|
||||||
Configuration conf = HBaseConfiguration.create();
|
Configuration conf = HBaseConfiguration.create();
|
||||||
conf.set(CommonConfigurationKeys.HADOOP_SECURITY_AUTHENTICATION, "kerberos");
|
conf.set(CommonConfigurationKeys.HADOOP_SECURITY_AUTHENTICATION, "kerberos");
|
||||||
|
|
|
@ -17,17 +17,21 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.hadoop.hbase.security;
|
package org.apache.hadoop.hbase.security;
|
||||||
|
|
||||||
import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.getConfigurationWoPrincipal;
|
import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.getClientKeytabForTesting;
|
||||||
|
import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.getClientPrincipalForTesting;
|
||||||
import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.getKeytabFileForTesting;
|
import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.getKeytabFileForTesting;
|
||||||
import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.getPrincipalForTesting;
|
import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.getPrincipalForTesting;
|
||||||
import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.getSecuredConfiguration;
|
import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.getSecuredConfiguration;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.apache.hadoop.conf.Configuration;
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.hbase.AuthUtil;
|
||||||
import org.apache.hadoop.hbase.HBaseClassTestRule;
|
import org.apache.hadoop.hbase.HBaseClassTestRule;
|
||||||
import org.apache.hadoop.hbase.HBaseTestingUtility;
|
import org.apache.hadoop.hbase.HBaseTestingUtility;
|
||||||
import org.apache.hadoop.hbase.testclassification.SecurityTests;
|
import org.apache.hadoop.hbase.testclassification.SecurityTests;
|
||||||
|
@ -57,12 +61,18 @@ public class TestUsersOperationsWithSecureHadoop {
|
||||||
|
|
||||||
private static String PRINCIPAL;
|
private static String PRINCIPAL;
|
||||||
|
|
||||||
|
private static String CLIENT_NAME;
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void setUp() throws Exception {
|
public static void setUp() throws Exception {
|
||||||
KDC = TEST_UTIL.setupMiniKdc(KEYTAB_FILE);
|
KDC = TEST_UTIL.setupMiniKdc(KEYTAB_FILE);
|
||||||
PRINCIPAL = "hbase/" + HOST;
|
PRINCIPAL = "hbase/" + HOST;
|
||||||
KDC.createPrincipal(KEYTAB_FILE, PRINCIPAL);
|
CLIENT_NAME = "foo";
|
||||||
|
KDC.createPrincipal(KEYTAB_FILE, PRINCIPAL, CLIENT_NAME);
|
||||||
HBaseKerberosUtils.setPrincipalForTesting(PRINCIPAL + "@" + KDC.getRealm());
|
HBaseKerberosUtils.setPrincipalForTesting(PRINCIPAL + "@" + KDC.getRealm());
|
||||||
|
HBaseKerberosUtils.setKeytabFileForTesting(KEYTAB_FILE.getAbsolutePath());
|
||||||
|
HBaseKerberosUtils.setClientPrincipalForTesting(CLIENT_NAME + "@" + KDC.getRealm());
|
||||||
|
HBaseKerberosUtils.setClientKeytabForTesting(KEYTAB_FILE.getAbsolutePath());
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterClass
|
@AfterClass
|
||||||
|
@ -84,13 +94,8 @@ public class TestUsersOperationsWithSecureHadoop {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testUserLoginInSecureHadoop() throws Exception {
|
public void testUserLoginInSecureHadoop() throws Exception {
|
||||||
UserGroupInformation defaultLogin = UserGroupInformation.getLoginUser();
|
// Default login is system user.
|
||||||
Configuration conf = getConfigurationWoPrincipal();
|
UserGroupInformation defaultLogin = UserGroupInformation.getCurrentUser();
|
||||||
User.login(conf, HBaseKerberosUtils.KRB_KEYTAB_FILE, HBaseKerberosUtils.KRB_PRINCIPAL,
|
|
||||||
"localhost");
|
|
||||||
|
|
||||||
UserGroupInformation failLogin = UserGroupInformation.getLoginUser();
|
|
||||||
assertTrue("ugi should be the same in case fail login", defaultLogin.equals(failLogin));
|
|
||||||
|
|
||||||
String nnKeyTab = getKeytabFileForTesting();
|
String nnKeyTab = getKeytabFileForTesting();
|
||||||
String dnPrincipal = getPrincipalForTesting();
|
String dnPrincipal = getPrincipalForTesting();
|
||||||
|
@ -98,7 +103,7 @@ public class TestUsersOperationsWithSecureHadoop {
|
||||||
assertNotNull("KerberosKeytab was not specified", nnKeyTab);
|
assertNotNull("KerberosKeytab was not specified", nnKeyTab);
|
||||||
assertNotNull("KerberosPrincipal was not specified", dnPrincipal);
|
assertNotNull("KerberosPrincipal was not specified", dnPrincipal);
|
||||||
|
|
||||||
conf = getSecuredConfiguration();
|
Configuration conf = getSecuredConfiguration();
|
||||||
UserGroupInformation.setConfiguration(conf);
|
UserGroupInformation.setConfiguration(conf);
|
||||||
|
|
||||||
User.login(conf, HBaseKerberosUtils.KRB_KEYTAB_FILE, HBaseKerberosUtils.KRB_PRINCIPAL,
|
User.login(conf, HBaseKerberosUtils.KRB_KEYTAB_FILE, HBaseKerberosUtils.KRB_PRINCIPAL,
|
||||||
|
@ -107,4 +112,40 @@ public class TestUsersOperationsWithSecureHadoop {
|
||||||
assertFalse("ugi should be different in in case success login",
|
assertFalse("ugi should be different in in case success login",
|
||||||
defaultLogin.equals(successLogin));
|
defaultLogin.equals(successLogin));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoginWithUserKeytabAndPrincipal() throws Exception {
|
||||||
|
String clientKeytab = getClientKeytabForTesting();
|
||||||
|
String clientPrincipal = getClientPrincipalForTesting();
|
||||||
|
assertNotNull("Path for client keytab is not specified.", clientKeytab);
|
||||||
|
assertNotNull("Client principal is not specified.", clientPrincipal);
|
||||||
|
|
||||||
|
Configuration conf = getSecuredConfiguration();
|
||||||
|
conf.set(AuthUtil.HBASE_CLIENT_KEYTAB_FILE, clientKeytab);
|
||||||
|
conf.set(AuthUtil.HBASE_CLIENT_KERBEROS_PRINCIPAL, clientPrincipal);
|
||||||
|
UserGroupInformation.setConfiguration(conf);
|
||||||
|
|
||||||
|
UserProvider provider = UserProvider.instantiate(conf);
|
||||||
|
assertTrue("Client principal or keytab is empty", provider.shouldLoginFromKeytab());
|
||||||
|
|
||||||
|
provider.login(AuthUtil.HBASE_CLIENT_KEYTAB_FILE, AuthUtil.HBASE_CLIENT_KERBEROS_PRINCIPAL);
|
||||||
|
User loginUser = provider.getCurrent();
|
||||||
|
assertEquals(CLIENT_NAME, loginUser.getShortName());
|
||||||
|
assertEquals(getClientPrincipalForTesting(), loginUser.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAuthUtilLogin() throws Exception {
|
||||||
|
String clientKeytab = getClientKeytabForTesting();
|
||||||
|
String clientPrincipal = getClientPrincipalForTesting();
|
||||||
|
Configuration conf = getSecuredConfiguration();
|
||||||
|
conf.set(AuthUtil.HBASE_CLIENT_KEYTAB_FILE, clientKeytab);
|
||||||
|
conf.set(AuthUtil.HBASE_CLIENT_KERBEROS_PRINCIPAL, clientPrincipal);
|
||||||
|
UserGroupInformation.setConfiguration(conf);
|
||||||
|
|
||||||
|
User user = AuthUtil.loginClient(conf);
|
||||||
|
assertTrue(user.isLoginFromKeytab());
|
||||||
|
assertEquals(CLIENT_NAME, user.getShortName());
|
||||||
|
assertEquals(getClientPrincipalForTesting(), user.getName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -179,7 +179,25 @@ Add the following to the `hbase-site.xml` file on every client:
|
||||||
</property>
|
</property>
|
||||||
----
|
----
|
||||||
|
|
||||||
The client environment must be logged in to Kerberos from KDC or keytab via the `kinit` command before communication with the HBase cluster will be possible.
|
Before 2.2.0 version, the client environment must be logged in to Kerberos from KDC or keytab via the `kinit` command before communication with the HBase cluster will be possible.
|
||||||
|
|
||||||
|
Since 2.2.0, client can specify the following configurations in `hbase-site.xml`:
|
||||||
|
[source,xml]
|
||||||
|
----
|
||||||
|
<property>
|
||||||
|
<name>hbase.client.keytab.file</name>
|
||||||
|
<value>/local/path/to/client/keytab</value>
|
||||||
|
</property>
|
||||||
|
|
||||||
|
<property>
|
||||||
|
<name>hbase.client.keytab.principal</name>
|
||||||
|
<value>foo@EXAMPLE.COM</value>
|
||||||
|
</property>
|
||||||
|
----
|
||||||
|
Then application can automatically do the login and credential renewal jobs without client interference.
|
||||||
|
|
||||||
|
It's optional feature, client, who upgrades to 2.2.0, can still keep their login and credential renewal logic already did in older version, as long as keeping `hbase.client.keytab.file`
|
||||||
|
and `hbase.client.keytab.principal` are unset.
|
||||||
|
|
||||||
Be advised that if the `hbase.security.authentication` in the client- and server-side site files do not match, the client will not be able to communicate with the cluster.
|
Be advised that if the `hbase.security.authentication` in the client- and server-side site files do not match, the client will not be able to communicate with the cluster.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue