org.hbase.async.SecureRpcHelper 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.
The newest version!
/*
* Copyright (C) 2015-2018 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;
import java.lang.reflect.Constructor;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.HashMap;
import java.util.Map;
import javax.security.auth.Subject;
import javax.security.sasl.Sasl;
import javax.security.sasl.SaslClient;
import javax.security.sasl.SaslException;
import org.hbase.async.auth.ClientAuthProvider;
import org.hbase.async.auth.KerberosClientAuthProvider;
import org.hbase.async.auth.SimpleClientAuthProvider;
import org.hbase.async.SecureRpcHelper;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This base class provides the logic needed to interface with SASL supported
* RPC versions. It is used by RegionClient to perform RPC handshaking as well
* as wrapping/unwrapping the rpc payload depending on the selected QOP level.
*
*
* For Kerberos the following configurations need to be set as system properties.
*
* - hbase.security.authentication=<MECHANISM>
* - hbase.kerberos.regionserver.principal=<REGIONSERVER PRINCIPAL>
* - hbase.rpc.protection=[authentication|integrity|privacy]
* - hbase.sasl.clientconfig=<JAAS Profile Name>
* - java.security.auth.login.config=<Path to JAAS conf>
*
* i.e.
*
*
* - hbase.security.authentication=kerberos
* - hbase.kerberos.regionserver.principal=hbase/[email protected]
* - hbase.rpc.protection=authentication
* - hbase.sasl.clientconfig=Client
* - java.security.auth.login.config=/path/to/jaas.conf
*
* @since 1.7
*/
public abstract class SecureRpcHelper {
public static final String SECURITY_AUTHENTICATION_KEY =
"hbase.security.authentication";
public static final String RPC_QOP_KEY =
"hbase.rpc.protection";
private static final Logger LOG = LoggerFactory.getLogger(SecureRpcHelper.class);
/** The HBaseClient config to pull values from */
protected final Config config;
/** The region client this helper will be working with */
protected final RegionClient region_client;
/** The IP of the region client */
protected final String host_ip;
/** The authentication provider, e.g. Simple or Kerberos */
protected ClientAuthProvider client_auth_provider;
/** The Sasl client if used */
protected SaslClient sasl_client;
/** Whether or not to encrypt/decrypt the payload on the socket */
protected boolean use_wrap;
/**
* Ctor that instantiates the authentication provider and attempts to
* authenticate at the same time.
* @param hbase_client The Hbase client we belong to
* @param region_client The region client we're dealing with
* @param remote_endpoint The remote endpoint of the HBase Region server.
*/
public SecureRpcHelper(final HBaseClient hbase_client,
final RegionClient region_client, final SocketAddress remote_endpoint) {
config = hbase_client.getConfig();
this.host_ip = ((InetSocketAddress)remote_endpoint).getAddress()
.getHostAddress();
this.region_client = region_client;
initSecureClientProvider(hbase_client);
}
/**
* Instantiates the proper security provider based on the configuration. The
* provider can be either "simple", "kerberos" or a canonical class name
* to load from the class path. Note that this will attempt to authenticate
* so it will throw an exception if the login failed.
* @param hbase_client The hbase client we belong to.
* @throws IllegalStateException if the config contained a class name and we
* couldn't instantiate the class.
*/
private void initSecureClientProvider(final HBaseClient hbase_client) {
final String mechanism = config.hasProperty(SECURITY_AUTHENTICATION_KEY) ?
config.getString(SECURITY_AUTHENTICATION_KEY).trim() : "simple";
if ("simple".equalsIgnoreCase(mechanism)) {
client_auth_provider = new SimpleClientAuthProvider(hbase_client);
use_wrap = false;
return;
}
if ("kerberos".equalsIgnoreCase(mechanism)) {
client_auth_provider = new KerberosClientAuthProvider(hbase_client);
} else {
try {
final Class> clazz = Class.forName(mechanism);
final Constructor> ctor = clazz.getConstructor(HBaseClient.class);
client_auth_provider = (ClientAuthProvider)ctor.newInstance(hbase_client);
LOG.info("Successfully instantiated a security provider of type: " +
clazz.getCanonicalName());
} catch (Exception e) {
throw new IllegalStateException(
"Failed to load specified SecureClientProvider: " + mechanism, e);
}
}
final String qop = parseQOP();
use_wrap = qop != null && !"auth".equalsIgnoreCase(qop);
final Map props = new HashMap(2);
props.put(Sasl.QOP, parseQOP());
props.put(Sasl.SERVER_AUTH, "true");
sasl_client = client_auth_provider.newSaslClient(host_ip, props);
}
/**
* Helper that checks to see if we should encrypt the packets between HBase
* based on the client config. If the client has set "integrity" or "privacy"
* then the packets will be encrypted. If set to "authentication" then no
* encryption is used. Any other value will throw an exception
* @return A string to compare against to see if we should wrap or not.
* @throws IllegalArgumentException if the config doesn't contain one of
* the three strings above.
*/
private String parseQOP() {
final String protection = config.hasProperty(RPC_QOP_KEY) ?
config.getString(RPC_QOP_KEY) : "authentication";
if ("integrity".equalsIgnoreCase(protection)) {
return "auth-int";
}
if ("privacy".equalsIgnoreCase(protection)) {
return "auth-conf";
}
if ("authentication".equalsIgnoreCase(protection)) {
return "auth";
}
throw new IllegalArgumentException("Unrecognized rpc protection level: "
+ protection);
}
/**
* When QOP of auth-int or auth-conf is selected
* This is used to unwrap the contents from the passed buffer payload. It
* should be called as soon as it comes off the Netty channel.
* Note that the ReplayingBufferDecoder may throw an error if we are unable
* to read the full buffer and replay the data.
* @param payload A wrapper around content.
* @return The content extracted from the payload.
* @throws IllegalStateException if the sasl client was unable to unwrap
* the payload
*/
public ChannelBuffer unwrap(final ChannelBuffer payload) {
if (!use_wrap) {
return payload;
}
final int len = payload.readInt();
try {
final ChannelBuffer unwrapped = ChannelBuffers.wrappedBuffer(
sasl_client.unwrap(payload.readBytes(len).array(), 0, len));
// If encryption was enabled, it's a good bet that you shouldn't log it
//if (LOG.isDebugEnabled()) {
// LOG.debug("Unwrapped payload: " + Bytes.pretty(unwrapped));
//}
return unwrapped;
} catch (SaslException e) {
throw new IllegalStateException("Failed to unwrap payload", e);
}
}
/**
* When QOP of auth-int or auth-conf is selected
* This is used to wrap the contents into the proper payload (ie encryption,
* signature, etc) and should be called just before sending the buffer to Netty.
* @param content The content to be wrapped.
* @return The wrapped payload.
* @throws IllegalStateException if the sasl client was unable to wrap
* the payload
*/
public ChannelBuffer wrap(final ChannelBuffer content) {
if (!use_wrap) {
return content;
}
try {
final byte[] payload = new byte[content.readableBytes()];
content.readBytes(payload);
final byte[] wrapped = sasl_client.wrap(payload, 0, payload.length);
final ChannelBuffer ret = ChannelBuffers.wrappedBuffer(
new byte[4 + wrapped.length]);
ret.clear();
ret.writeInt(wrapped.length);
ret.writeBytes(wrapped);
//if (LOG.isDebugEnabled()) {
// LOG.debug("Wrapped payload: " + Bytes.pretty(ret));
//}
return ret;
} catch (SaslException e) {
throw new IllegalStateException("Failed to wrap payload", e);
}
}
/**
* Sends the initial handshake and/or SASL challenge to the region server
* @param channel The channel to write to
*/
public abstract void sendHello(final Channel channel);
/**
* Handles a buffer received from the region server. It may be security
* related or it may be a wrapped packet if security has been negotiated.
* If wrapping is disabled, the original buffer is returned. If the data is
* security related, a null should be returned.
* NOTE: If the buffer IS security related, make sure to drain the entire
* buffer or Netty will replay it until you're done consuming.
* @param buf The buffer off the channel
* @param chan The channel that we received the buffer from
* @return The possibly unwrapped channel buffer for further decoding. If null
* then the response was security related.
*/
public abstract ChannelBuffer handleResponse(final ChannelBuffer buf,
final Channel chan);
/**
* Handles passing a buffer off to the sasl client for challenge evaluation
* @param b The buffer to process
* @return The results of the challenge if successful, or null if there was
* a SaslException.
* @throws IllegalStateException when processing of the action failed due to
* a PrivilegedActionException
*/
protected byte[] processChallenge(final byte[] b) {
try {
final class PrivilegedAction implements PrivilegedExceptionAction {
@Override
public byte[] run() {
try {
return sasl_client.evaluateChallenge(b);
} catch (SaslException e) {
LOG.error("Failed Sasl challenge", e);
return null;
}
}
@Override
public String toString() {
return "evaluate sasl challenge";
}
}
return Subject.doAs(client_auth_provider.getClientSubject(),
new PrivilegedAction());
} catch (PrivilegedActionException e) {
throw new IllegalStateException("Failed to process challenge", e);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy