org.hbase.async.auth.KerberosClientAuthProvider Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of asynchbase Show documentation
Show all versions of asynchbase Show documentation
An alternative HBase client library for applications requiring fully
asynchronous, non-blocking and thread-safe HBase connectivity.
/*
* Copyright (C) 2015 The Async HBase Authors. All rights reserved.
* This file is part of Async HBase.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the StumbleUpon nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.hbase.async.auth;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.Principal;
import java.security.PrivilegedExceptionAction;
import java.util.Map;
import java.util.Set;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginException;
import javax.security.sasl.AuthorizeCallback;
import javax.security.sasl.RealmCallback;
import javax.security.sasl.Sasl;
import javax.security.sasl.SaslClient;
import org.apache.zookeeper.server.auth.KerberosName;
import org.hbase.async.HBaseClient;
import org.jboss.netty.util.HashedWheelTimer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Handles Kerberos based authentication using the Java Authentication and
* Authorization Service (JAAS).
* The class requires a few settings including:
* asynchbase.security.auth.kerberos.regionserver.principal - a principal name
* or template to use for authentication. The value can have the token "_HOST"
* which will be replaced with the cannonical name of the localhost.
* @since 1.7
*/
public class KerberosClientAuthProvider extends ClientAuthProvider {
public static final String PASSWORD_KEY = "hbase.regionserver.kerberos.password";
public static final String PRINCIPAL_KEY = "hbase.kerberos.regionserver.principal";
private static final Logger LOG = LoggerFactory.getLogger(
KerberosClientAuthProvider.class);
/** The principal name */
private String client_principal_name;
/**
* Default ctor that will attempt a login and setup the Login singleton
* @param hbase_client The HBaseClient to fetch configuration and timers from
* @throws IllegalArgumentException if the
* asynchbase.security.auth.simple.username is missing, null or empty.
* @throws IllegalStateException if the login was unsuccessful
*/
public KerberosClientAuthProvider(final HBaseClient hbase_client) {
super(hbase_client);
String password = null;
if (hbase_client.getConfig().hasProperty(PASSWORD_KEY)) {
password = hbase_client.getConfig().getString(PASSWORD_KEY);
}
try {
Login.initUserIfNeeded(hbase_client.getConfig(),
(HashedWheelTimer)hbase_client.getTimer(),
hbase_client.getConfig().getString(Login.LOGIN_CONTEXT_NAME_KEY),
new ClientCallbackHandler(password));
} catch (LoginException e) {
throw new IllegalStateException("Failed to get login context", e);
}
//-- Prepare principals needed for SaslClient --
final Login client_login = Login.getCurrentLogin();
client_principal_name = getClientPrincipalName(client_login);
}
@Override
public SaslClient newSaslClient(final String service_ip,
final Map props) {
final Login client_login = Login.getCurrentLogin();
String server_principal = hbase_client.getConfig().getString(PRINCIPAL_KEY);
if (server_principal.contains("_HOST")) {
try {
final String host = InetAddress.getByName(service_ip)
.getCanonicalHostName();
server_principal = server_principal.replaceAll("_HOST", host);
} catch (UnknownHostException e) {
throw new IllegalStateException("Failed to resolve hostname for: " +
service_ip, e);
}
}
LOG.info("Connecting to " + server_principal);
final KerberosName service_kerberos_name = new KerberosName(server_principal);
final String service_name = service_kerberos_name.getServiceName();
final String service_hostname = service_kerberos_name.getHostName();
//-- create SaslClient --
try {
final class PriviledgedAction implements
PrivilegedExceptionAction {
@Override
public SaslClient run() throws Exception {
LOG.info("Client will use GSSAPI as SASL mechanism.");
final String[] mechanism = { "GSSAPI" };
LOG.debug("Creating sasl client: client=" + client_principal_name +
", service=" + service_name + ", serviceHostname=" + service_hostname);
return Sasl.createSaslClient(
mechanism,
null, // authorization ID
service_name,
service_hostname,
props,
null); // callback
}
@Override
public String toString() {
return "create sasl client";
}
}
return Subject.doAs(client_login.getSubject(), new PriviledgedAction());
} catch (Exception e) {
LOG.error("Error creating SASL client", e);
throw new IllegalStateException("Error creating SASL client", e);
}
}
@Override
public String getClientUsername() {
return client_principal_name;
}
@Override
public byte getAuthMethodCode() {
return KEBEROS_CLIENT_AUTH_CODE;
}
@Override
public Subject getClientSubject() {
return Login.getCurrentLogin().getSubject();
}
/**
* Return the principal name if set
* @param login The login object to pull the name from
* @return The name if found, null if not
*/
private String getClientPrincipalName(final Login login) {
if (login.getSubject() == null) {
return null;
}
final Set principals = login.getSubject().getPrincipals();
if (principals == null || principals.isEmpty()) {
return null;
}
final Principal principal = principals.iterator().next();
final KerberosName name = new KerberosName(principal.getName());
return name.toString();
}
/**
* A callback executed on authentication to validate the name or password.
* Right now the only way to set a password is via the config file, and that
* isn't particularly secure. So use a key cache instead.
*/
static class ClientCallbackHandler implements CallbackHandler {
private String password;
/** @param the password to use for auth */
public ClientCallbackHandler(final String password) {
this.password = password;
}
@Override
public void handle(final Callback[] callbacks)
throws UnsupportedCallbackException {
for (final Callback callback : callbacks) {
LOG.debug("Processing callback: " + callback.getClass());
if (callback instanceof NameCallback) {
final NameCallback name_callback = (NameCallback) callback;
name_callback.setName(name_callback.getDefaultName());
} else if (callback instanceof PasswordCallback) {
final PasswordCallback password_callback = (PasswordCallback)callback;
if (password != null) {
password_callback.setPassword(password.toCharArray());
} else {
LOG.warn("Could not login: the client is being asked for a password, but the " +
" client code does not currently support obtaining a password from the user." +
" Make sure that the client is configured to use a ticket cache (using" +
" the JAAS configuration setting 'useTicketCache=true)' and restart the client. If" +
" you still get this message after that, the TGT in the ticket cache has expired and must" +
" be manually refreshed. To do so, first determine if you are using a password or a" +
" keytab. If the former, run kinit in a Unix shell in the environment of the user who" +
" is running this asynchbase client using the command" +
" 'kinit ' (where is the name of the client's Kerberos principal)." +
" If the latter, do" +
" 'kinit -k -t ' (where is the name of the Kerberos principal, and" +
" is the location of the keytab file). After manually refreshing your cache," +
" restart this client. If you continue to see this message after manually refreshing" +
" your cache, ensure that your KDC host's clock is in sync with this host's clock.");
}
} else if (callback instanceof RealmCallback) {
final RealmCallback realm_callback = (RealmCallback) callback;
realm_callback.setText(realm_callback.getDefaultText());
} else if (callback instanceof AuthorizeCallback) {
final AuthorizeCallback authorize_callback =
(AuthorizeCallback) callback;
final String authid = authorize_callback.getAuthenticationID();
final String authzid = authorize_callback.getAuthorizationID();
if (authid.equals(authzid)) {
authorize_callback.setAuthorized(true);
authorize_callback.setAuthorizedID(authzid);
} else {
authorize_callback.setAuthorized(false);
}
} else {
throw new UnsupportedCallbackException(callback,
"Unrecognized SASL ClientCallback: " + callback.getClass());
}
}
}
}
}