From f71953987b669d362d7b4c8edcedef6fa8bd45ae Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Wed, 27 Nov 2019 18:59:11 -0500 Subject: [PATCH 01/41] HBASE-23347 Allowable custom authentication methods for RPCs --- PluggableRpcAuthentication.md | 140 +++++ .../hadoop/hbase/ipc/AbstractRpcClient.java | 8 - .../hbase/ipc/BlockingRpcConnection.java | 6 +- .../hadoop/hbase/ipc/NettyRpcConnection.java | 6 +- .../hadoop/hbase/ipc/RpcConnection.java | 116 ++-- .../security/AbstractHBaseSaslRpcClient.java | 112 +--- .../hbase/security/HBaseSaslRpcClient.java | 22 +- .../security/NettyHBaseSaslRpcClient.java | 9 +- .../NettyHBaseSaslRpcClientHandler.java | 5 +- .../hadoop/hbase/security/SaslUtil.java | 6 +- ...tractSaslClientAuthenticationProvider.java | 62 ++ .../AuthenticationProviderSelector.java | 47 ++ .../provider/DefaultProviderSelector.java | 86 +++ ...igestSaslClientAuthenticationProvider.java | 130 +++++ .../GssSaslClientAuthenticationProvider.java | 74 +++ .../security/provider/SaslAuthMethod.java | 96 ++++ .../SaslClientAuthenticationProvider.java | 80 +++ .../SaslClientAuthenticationProviders.java | 203 +++++++ ...impleSaslClientAuthenticationProvider.java | 68 +++ ....provider.SaslClientAuthenticationProvider | 3 + .../security/TestHBaseSaslRpcClient.java | 75 +-- ...TestSaslClientAuthenticationProviders.java | 66 +++ .../hadoop/hbase/HBaseInterfaceAudience.java | 5 + .../hadoop/hbase/ipc/ServerRpcConnection.java | 77 +-- .../hbase/security/HBaseSaslRpcServer.java | 142 +---- ...igestSaslServerAuthenticationProvider.java | 141 +++++ .../GssSaslServerAuthenticationProvider.java | 120 ++++ .../SaslServerAuthenticationProvider.java | 50 ++ .../SaslServerAuthenticationProviders.java | 162 ++++++ ...impleSaslServerAuthenticationProvider.java | 55 ++ ....provider.SaslServerAuthenticationProvider | 3 + .../hadoop/hbase/security/TestSecureIPC.java | 6 +- .../TestCustomSaslAuthenticationProvider.java | 534 ++++++++++++++++++ ...TestSaslServerAuthenticationProviders.java | 60 ++ .../src/test/resources/log4j.properties | 1 + 35 files changed, 2336 insertions(+), 440 deletions(-) create mode 100644 PluggableRpcAuthentication.md create mode 100644 hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AbstractSaslClientAuthenticationProvider.java create mode 100644 hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java create mode 100644 hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DefaultProviderSelector.java create mode 100644 hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java create mode 100644 hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java create mode 100644 hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslAuthMethod.java create mode 100644 hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProvider.java create mode 100644 hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java create mode 100644 hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslClientAuthenticationProvider.java create mode 100644 hbase-client/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider create mode 100644 hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslClientAuthenticationProviders.java create mode 100644 hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java create mode 100644 hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java create mode 100644 hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProvider.java create mode 100644 hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProviders.java create mode 100644 hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java create mode 100644 hbase-server/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslServerAuthenticationProvider create mode 100644 hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java create mode 100644 hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslServerAuthenticationProviders.java diff --git a/PluggableRpcAuthentication.md b/PluggableRpcAuthentication.md new file mode 100644 index 000000000000..16b7324ca579 --- /dev/null +++ b/PluggableRpcAuthentication.md @@ -0,0 +1,140 @@ +# Pluggable Authentication for HBase RPCs + +## Background + +As a distributed database, HBase must be able to authenticate users and HBase +services across an untrusted network. Clients and HBase services are treated +equivalently in terms of authentication (and this is the only time we will +draw such a distinction). + +There are currently three modes of authentication which are supported by HBase +today via the configuration property `hbase.security.authentication` + +1. `SIMPLE` +2. `KERBEROS` +3. `TOKEN` + +`SIMPLE` authentication is effectively no authentication; HBase assumes the user +is who they claim to be. `KERBEROS` uses authenticates clients via the KerberosV5 +protocol using the GSSAPI mechanism of the Java Simple Authentication and Security +Layer (SASL) protocol. `TOKEN` is a username-password based authentication protocol +which uses short-lived passwords that can only be obtained via a `KERBEROS` authenticated +request. `TOKEN` authentication is synonymous with Hadoop-style [Delegation Tokens](https://steveloughran.gitbooks.io/kerberos_and_hadoop/content/sections/hadoop_tokens.html#delegation-tokens). `TOKEN` authentication uses the `DIGEST-MD5` +SASL mechanism. + +[SASL](https://docs.oracle.com/javase/8/docs/technotes/guides/security/sasl/sasl-refguide.html) +is a library which specifies a network protocol that can authenticate a client +and a server using an arbitrary mechanism. SASL ships with a [number of mechanisms](https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml) +out of the box and it is possible to implement custom mechanisms. SASL is effectively +decoupling an RPC client-server model from the mechanism used to authenticate those +requests (e.g. the RPC code is identical whether username-password, Kerberos, or any +other method is used to authenticate the request). + +The `SIMPLE` implementation does not use SASL, but instead has its own RPC logic +built into the HBase RPC protocol. `KERBEROS` and `TOKEN` both use SASL to authenticate, +relying on the `Token` interface that is intertwined with the Hadoop `UserGroupInformation` +class. SASL decouples an RPC from the mechanism used to authenticate that request. + +## Problem statement + +Despite HBase already shipping authentication implementations which leverage SASL, +it is (effectively) impossible to add a new authentication implementation to HBase. The +use of the `org.apache.hadoop.hbase.security.AuthMethod` enum makes it impossible +to define a new method of authentication. Also, the RPC implementation is written +to only use the methods that are expressly shipped in HBase. Adding a new authentication +method would require copying and modifying the RpcClient implementation. + +While it is possible to add a new authentication method to HBase, it cannot be done +cleanly or sustainably. This is what is meant by "impossible". + +## Proposal + +HBase should expose interfaces which allow for pluggable authentication mechanisms +such that HBase can authenticate against external systems. Because the RPC implementation +can already support SASL, HBase can standardize on SASL, allowing any authentication method +which is capable of using SASL to negotiate authentication. `KERBEROS` and `TOKEN` methods +will naturally fit into these new interfaces, but `SIMPLE` authentication will not (see the following +chapter for a tangent on SIMPLE authentication today) + +### Tangent: on SIMPLE authentication + +`SIMPLE` authentication in HBase today is treated as a special case. My impression is that +this stems from HBase not originally shipping an RPC solution that had any authentication. + +Re-implementing `SIMPLE` authentication such that it also flows through SASL (e.g. via +the `PLAIN` SASL mechanism) would simplify the HBase codebase such that all authentication +occurs via SASL. This was not done for the initial implementation to reduce the scope +of the changeset. Changing `SIMPLE` authentication to use SASL may result in some +performance impact in setting up a new RPC. The same conditional logic to determine +`if (sasl) ... else SIMPLE` logic is propagated in this implementation. + +## Implementation Overview + +HBASE-XXXXX includes a refactoring of HBase RPC authentication where all current methods +are ported to a new set of interfaces, and all RPC implementations are updated to use +the new interfaces. In the spirit of SASL, the expectation is that users can provide +their own authentication methods at runtime, and HBase should be capable of negotiating +a client who tries to authenticate via that custom authentication method. The implementation +refers to this "bundle" of client and server logic as an "authentication provider". + +### Providers + +One authentication provider includes the following pieces: + +1. Client-side logic (providing a credential) +2. Server-side logic (validating a credential from a client) +3. Client selection logic to choose a provider (from many that may be available) + +A provider's client and server side logic are considered to be one-to-one. A `Foo` client-side provider +should never be used to authenticate against a `Bar` server-side provider. + +We do expect that both clients and servers will have access to multiple providers. A server may +be capable of authenticating via methods which a client in unaware of. A client may attempt to authenticate +against a server which the server does not know how to process. In both cases, the RPC +should fail when a client and server do not have matching providers. The server identifies +client authentication mechanisms via a `byte authCode` (which is already sent today with HBase RPCs). + +A client may also have multiple providers available for it to use in authenticating against +HBase. The client must have some logic to select which provider to use. Because we are +allowing custom providers, we must also allow a custom selection logic such that the +correct provider can be chosen. This is a formalization of the logic already present +in `org.apache.hadoop.hbase.security.token.AuthenticationTokenSelector`. + +To enable the above, we have three new classes to support the user extensibility: + +1. `interface SaslClientAuthenticationProvider` +2. `interface SaslServerAuthenticationProvider extends SaslClientAuthenticationProvider` +3. `interface AuthenticationProviderSelector` + +Each Authentication Provider implementation is a singleton and is intended to be shared +across all RPCs. A provider selector is chosen per client, based on the given configuration. + +A client authentication provider is uniquely identified among other providers +by the following characteristics: + +1. A name, e.g. "KERBEROS", "TOKEN" +2. A byte (a value between 0 and 255) + +In addition to these attributes, a provider also must define the following attributes: + +3. The SASL mechanim being used. +4. The Hadoop AuthenticationMethod, e.g. "TOKEN", "KERBEROS", "CERTIFICATE" +5. The Token "kind", the name used to identify a TokenIdentifier, e.g. `HBASE_AUTH_TOKEN` + +It is allowed (even expected) that there may be multiple providers that use `TOKEN` authentication. + +### Factories + +To ease development with these unknown set of providers, there are two classes which +find, instantiate, and cache the provider singletons. + +1. Client side: `class SaslClientAuthenticationProviders` +2. Server side: `class SaslServerAuthenticationProviders` + +These classes use [Java ServiceLoader](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html) +to find implementations available on the classpath. The provided HBase implementations +for the three out-of-the-box implementations all register themselves via the `ServiceLoader`. + +Each class also enables providers to be added via explicit configuration in hbase-site.xml. +This enables unit tests to define custom implementations that may be toy/naive/unsafe without +any worry that these may be inadvertently deployed onto a production HBase cluster. diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/AbstractRpcClient.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/AbstractRpcClient.java index 629efe6c203c..b769a713b2dd 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/AbstractRpcClient.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/AbstractRpcClient.java @@ -104,14 +104,6 @@ public abstract class AbstractRpcClient implements RpcC private static final ScheduledExecutorService IDLE_CONN_SWEEPER = Executors .newScheduledThreadPool(1, Threads.newDaemonThreadFactory("Idle-Rpc-Conn-Sweeper")); - @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="MS_MUTABLE_COLLECTION_PKGPROTECT", - justification="the rest of the system which live in the different package can use") - protected final static Map> TOKEN_HANDLERS = new HashMap<>(); - - static { - TOKEN_HANDLERS.put(Kind.HBASE_AUTH_TOKEN, new AuthenticationTokenSelector()); - } - protected boolean running = true; // if client runs protected final Configuration conf; diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java index c5b1e5766ac0..dc9958f0994b 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java @@ -361,7 +361,7 @@ private void disposeSasl() { private boolean setupSaslConnection(final InputStream in2, final OutputStream out2) throws IOException { - saslRpcClient = new HBaseSaslRpcClient(authMethod, token, serverPrincipal, + saslRpcClient = new HBaseSaslRpcClient(this.rpcClient.conf, provider, token, serverPrincipal, this.rpcClient.fallbackAllowed, this.rpcClient.conf.get("hbase.rpc.protection", QualityOfProtection.AUTHENTICATION.name().toLowerCase(Locale.ROOT)), this.rpcClient.conf.getBoolean(CRYPTO_AES_ENABLED_KEY, CRYPTO_AES_ENABLED_DEFAULT)); @@ -389,7 +389,7 @@ private void handleSaslConnectionFailure(final int currRetries, final int maxRet user.doAs(new PrivilegedExceptionAction() { @Override public Object run() throws IOException, InterruptedException { - if (shouldAuthenticateOverKrb()) { + if (provider.isKerberos()) { if (currRetries < maxRetries) { if (LOG.isDebugEnabled()) { LOG.debug("Exception encountered while connecting to " + @@ -459,7 +459,7 @@ private void setupIOstreams() throws IOException { if (useSasl) { final InputStream in2 = inStream; final OutputStream out2 = outStream; - UserGroupInformation ticket = getUGI(); + UserGroupInformation ticket = provider.unwrapUgi(remoteId.ticket.getUGI()); boolean continueSasl; if (ticket == null) { throw new FatalConnectionException("ticket/user is null"); diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/NettyRpcConnection.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/NettyRpcConnection.java index b8620b1b743c..07744bdee7fd 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/NettyRpcConnection.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/NettyRpcConnection.java @@ -159,7 +159,7 @@ private void scheduleRelogin(Throwable error) { @Override public void run() { try { - if (shouldAuthenticateOverKrb()) { + if (provider.isKerberos()) { relogin(); } } catch (IOException e) { @@ -183,7 +183,7 @@ private void failInit(Channel ch, IOException e) { } private void saslNegotiate(final Channel ch) { - UserGroupInformation ticket = getUGI(); + UserGroupInformation ticket = provider.unwrapUgi(remoteId.getTicket().getUGI()); if (ticket == null) { failInit(ch, new FatalConnectionException("ticket/user is null")); return; @@ -191,7 +191,7 @@ private void saslNegotiate(final Channel ch) { Promise saslPromise = ch.eventLoop().newPromise(); final NettyHBaseSaslRpcClientHandler saslHandler; try { - saslHandler = new NettyHBaseSaslRpcClientHandler(saslPromise, ticket, authMethod, token, + saslHandler = new NettyHBaseSaslRpcClientHandler(saslPromise, ticket, provider, token, serverPrincipal, rpcClient.fallbackAllowed, this.rpcClient.conf); } catch (IOException e) { failInit(ch, e); diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java index 1fffebbf5b00..9e235e8a6902 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java @@ -17,34 +17,33 @@ */ package org.apache.hadoop.hbase.ipc; -import org.apache.hbase.thirdparty.io.netty.util.HashedWheelTimer; -import org.apache.hbase.thirdparty.io.netty.util.Timeout; -import org.apache.hbase.thirdparty.io.netty.util.TimerTask; - import java.io.IOException; import java.net.UnknownHostException; import java.util.concurrent.TimeUnit; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HConstants; -import org.apache.yetus.audience.InterfaceAudience; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.apache.hadoop.hbase.codec.Codec; -import org.apache.hadoop.hbase.protobuf.generated.AuthenticationProtos; +import org.apache.hadoop.hbase.security.SecurityInfo; +import org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider; +import org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProviders; import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.ConnectionHeader; import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; -import org.apache.hadoop.hbase.security.AuthMethod; -import org.apache.hadoop.hbase.security.SecurityInfo; import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; +import org.apache.hadoop.hbase.util.Pair; import org.apache.hadoop.io.Text; import org.apache.hadoop.io.compress.CompressionCodec; import org.apache.hadoop.security.SecurityUtil; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; -import org.apache.hadoop.security.token.TokenSelector; +import org.apache.hbase.thirdparty.io.netty.util.HashedWheelTimer; +import org.apache.hbase.thirdparty.io.netty.util.Timeout; +import org.apache.hbase.thirdparty.io.netty.util.TimerTask; +import org.apache.yetus.audience.InterfaceAudience; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Base class for ipc connection. @@ -56,8 +55,6 @@ abstract class RpcConnection { protected final ConnectionId remoteId; - protected final AuthMethod authMethod; - protected final boolean useSasl; protected final Token token; @@ -81,6 +78,8 @@ abstract class RpcConnection { // the last time we were picked up from connection pool. protected long lastTouched; + protected SaslClientAuthenticationProvider provider; + protected RpcConnection(Configuration conf, HashedWheelTimer timeoutTimer, ConnectionId remoteId, String clusterId, boolean isSecurityEnabled, Codec codec, CompressionCodec compressor) throws IOException { @@ -95,19 +94,20 @@ protected RpcConnection(Configuration conf, HashedWheelTimer timeoutTimer, Conne UserGroupInformation ticket = remoteId.getTicket().getUGI(); SecurityInfo securityInfo = SecurityInfo.getInfo(remoteId.getServiceName()); this.useSasl = isSecurityEnabled; - Token token = null; String serverPrincipal = null; + // Choose the correct Token and AuthenticationProvider for this client to use + SaslClientAuthenticationProviders providers = SaslClientAuthenticationProviders.getInstance(conf); if (useSasl && securityInfo != null) { - AuthenticationProtos.TokenIdentifier.Kind tokenKind = securityInfo.getTokenKind(); - if (tokenKind != null) { - TokenSelector tokenSelector = AbstractRpcClient.TOKEN_HANDLERS - .get(tokenKind); - if (tokenSelector != null) { - token = tokenSelector.selectToken(new Text(clusterId), ticket.getTokens()); - } else if (LOG.isDebugEnabled()) { - LOG.debug("No token selector found for type " + tokenKind); - } + Pair> pair = + providers.selectProvider(new Text(clusterId), ticket); + if (pair == null) { + LOG.error("Found no valid authentication method from {} with tokens={}", + providers.toString(), ticket.getTokens()); + throw new RuntimeException("Found no valid authentication method from options"); } + this.provider = pair.getFirst(); + this.token = pair.getSecond(); + String serverKey = securityInfo.getServerPrincipal(); if (serverKey == null) { throw new IOException("Can't obtain server Kerberos config key from SecurityInfo"); @@ -118,70 +118,22 @@ protected RpcConnection(Configuration conf, HashedWheelTimer timeoutTimer, Conne LOG.debug("RPC Server Kerberos principal name for service=" + remoteId.getServiceName() + " is " + serverPrincipal); } - } - this.token = token; - this.serverPrincipal = serverPrincipal; - if (!useSasl) { - authMethod = AuthMethod.SIMPLE; - } else if (token != null) { - authMethod = AuthMethod.DIGEST; + } else if (!useSasl) { + // Hack, while SIMPLE doesn't go via SASL. + provider = providers.getSimpleProvider(); + this.token = null; } else { - authMethod = AuthMethod.KERBEROS; + throw new RuntimeException("Could not compute valid client authentication provider"); } - // Log if debug AND non-default auth, else if trace enabled. - // No point logging obvious. - if ((LOG.isDebugEnabled() && !authMethod.equals(AuthMethod.SIMPLE)) || - LOG.isTraceEnabled()) { - // Only log if not default auth. - LOG.debug("Use " + authMethod + " authentication for service " + remoteId.serviceName - + ", sasl=" + useSasl); - } + this.serverPrincipal = serverPrincipal; + + LOG.debug("Using {} authentication for service{}, sasl={}", + provider.getSaslAuthMethod().getName(), remoteId.serviceName, useSasl); reloginMaxBackoff = conf.getInt("hbase.security.relogin.maxbackoff", 5000); this.remoteId = remoteId; } - private UserInformation getUserInfo(UserGroupInformation ugi) { - if (ugi == null || authMethod == AuthMethod.DIGEST) { - // Don't send user for token auth - return null; - } - UserInformation.Builder userInfoPB = UserInformation.newBuilder(); - if (authMethod == AuthMethod.KERBEROS) { - // Send effective user for Kerberos auth - userInfoPB.setEffectiveUser(ugi.getUserName()); - } else if (authMethod == AuthMethod.SIMPLE) { - // Send both effective user and real user for simple auth - userInfoPB.setEffectiveUser(ugi.getUserName()); - if (ugi.getRealUser() != null) { - userInfoPB.setRealUser(ugi.getRealUser().getUserName()); - } - } - return userInfoPB.build(); - } - - protected UserGroupInformation getUGI() { - UserGroupInformation ticket = remoteId.getTicket().getUGI(); - if (authMethod == AuthMethod.KERBEROS) { - if (ticket != null && ticket.getRealUser() != null) { - ticket = ticket.getRealUser(); - } - } - return ticket; - } - - protected boolean shouldAuthenticateOverKrb() throws IOException { - UserGroupInformation loginUser = UserGroupInformation.getLoginUser(); - UserGroupInformation currentUser = UserGroupInformation.getCurrentUser(); - UserGroupInformation realUser = currentUser.getRealUser(); - return authMethod == AuthMethod.KERBEROS && loginUser != null && - // Make sure user logged in using Kerberos either keytab or TGT - loginUser.hasKerberosCredentials() && - // relogin only in case it is the login user (e.g. JT) - // or superuser (like oozie). - (loginUser.equals(currentUser) || loginUser.equals(realUser)); - } - protected void relogin() throws IOException { if (UserGroupInformation.isLoginKeytabBased()) { UserGroupInformation.getLoginUser().reloginFromKeytab(); @@ -216,7 +168,7 @@ protected byte[] getConnectionHeaderPreamble() { System.arraycopy(HConstants.RPC_HEADER, 0, preamble, 0, rpcHeaderLen); preamble[rpcHeaderLen] = HConstants.RPC_CURRENT_VERSION; synchronized (this) { - preamble[rpcHeaderLen + 1] = authMethod.code; + preamble[rpcHeaderLen + 1] = provider.getSaslAuthMethod().getCode(); } return preamble; } @@ -225,7 +177,7 @@ protected ConnectionHeader getConnectionHeader() { ConnectionHeader.Builder builder = ConnectionHeader.newBuilder(); builder.setServiceName(remoteId.getServiceName()); UserInformation userInfoPB; - if ((userInfoPB = getUserInfo(remoteId.ticket.getUGI())) != null) { + if ((userInfoPB = provider.getUserInfo(remoteId.ticket.getUGI())) != null) { builder.setUserInfo(userInfoPB); } if (this.codec != null) { diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/AbstractHBaseSaslRpcClient.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/AbstractHBaseSaslRpcClient.java index 94e5d5d00495..f96273d67784 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/AbstractHBaseSaslRpcClient.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/AbstractHBaseSaslRpcClient.java @@ -17,25 +17,17 @@ */ package org.apache.hadoop.hbase.security; -import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting; - import java.io.IOException; import java.util.Map; -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.sasl.RealmCallback; -import javax.security.sasl.RealmChoiceCallback; -import javax.security.sasl.Sasl; import javax.security.sasl.SaslClient; import javax.security.sasl.SaslException; import org.apache.yetus.audience.InterfaceAudience; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; @@ -46,7 +38,6 @@ */ @InterfaceAudience.Private public abstract class AbstractHBaseSaslRpcClient { - private static final Logger LOG = LoggerFactory.getLogger(AbstractHBaseSaslRpcClient.class); private static final byte[] EMPTY_TOKEN = new byte[0]; @@ -65,9 +56,10 @@ public abstract class AbstractHBaseSaslRpcClient { * @param fallbackAllowed does the client allow fallback to simple authentication * @throws IOException */ - protected AbstractHBaseSaslRpcClient(AuthMethod method, Token token, + protected AbstractHBaseSaslRpcClient(Configuration conf, SaslClientAuthenticationProvider provider, + Token token, String serverPrincipal, boolean fallbackAllowed) throws IOException { - this(method, token, serverPrincipal, fallbackAllowed, "authentication"); + this(conf, provider, token, serverPrincipal, fallbackAllowed, "authentication"); } /** @@ -79,53 +71,18 @@ protected AbstractHBaseSaslRpcClient(AuthMethod method, Token token, + protected AbstractHBaseSaslRpcClient(Configuration conf, + SaslClientAuthenticationProvider provider, Token token, String serverPrincipal, boolean fallbackAllowed, String rpcProtection) throws IOException { this.fallbackAllowed = fallbackAllowed; saslProps = SaslUtil.initSaslProperties(rpcProtection); - switch (method) { - case DIGEST: - if (LOG.isDebugEnabled()) LOG.debug("Creating SASL " + AuthMethod.DIGEST.getMechanismName() - + " client to authenticate to service at " + token.getService()); - saslClient = createDigestSaslClient(new String[] { AuthMethod.DIGEST.getMechanismName() }, - SaslUtil.SASL_DEFAULT_REALM, new SaslClientCallbackHandler(token)); - break; - case KERBEROS: - if (LOG.isDebugEnabled()) { - LOG.debug("Creating SASL " + AuthMethod.KERBEROS.getMechanismName() - + " client. Server's Kerberos principal name is " + serverPrincipal); - } - if (serverPrincipal == null || serverPrincipal.length() == 0) { - throw new IOException("Failed to specify server's Kerberos principal name"); - } - String[] names = SaslUtil.splitKerberosName(serverPrincipal); - if (names.length != 3) { - throw new IOException( - "Kerberos principal does not have the expected format: " + serverPrincipal); - } - saslClient = createKerberosSaslClient( - new String[] { AuthMethod.KERBEROS.getMechanismName() }, names[0], names[1]); - break; - default: - throw new IOException("Unknown authentication method " + method); - } + + saslClient = provider.createClient(conf, serverPrincipal, token, fallbackAllowed, saslProps); if (saslClient == null) { - throw new IOException("Unable to find SASL client implementation"); + throw new IOException("Authentication provider " + provider.getClass() + " returned a null SaslClient"); } } - protected SaslClient createDigestSaslClient(String[] mechanismNames, String saslDefaultRealm, - CallbackHandler saslClientCallbackHandler) throws IOException { - return Sasl.createSaslClient(mechanismNames, null, null, saslDefaultRealm, saslProps, - saslClientCallbackHandler); - } - - protected SaslClient createKerberosSaslClient(String[] mechanismNames, String userFirstPart, - String userSecondPart) throws IOException { - return Sasl.createSaslClient(mechanismNames, null, userFirstPart, userSecondPart, saslProps, - null); - } - public byte[] getInitialResponse() throws SaslException { if (saslClient.hasInitialResponse()) { return saslClient.evaluateChallenge(EMPTY_TOKEN); @@ -146,53 +103,4 @@ public byte[] evaluateChallenge(byte[] challenge) throws SaslException { public void dispose() { SaslUtil.safeDispose(saslClient); } - - @VisibleForTesting - static class SaslClientCallbackHandler implements CallbackHandler { - private final String userName; - private final char[] userPassword; - - public SaslClientCallbackHandler(Token token) { - this.userName = SaslUtil.encodeIdentifier(token.getIdentifier()); - this.userPassword = SaslUtil.encodePassword(token.getPassword()); - } - - @Override - public void handle(Callback[] callbacks) throws UnsupportedCallbackException { - NameCallback nc = null; - PasswordCallback pc = null; - RealmCallback rc = null; - for (Callback callback : callbacks) { - if (callback instanceof RealmChoiceCallback) { - continue; - } else if (callback instanceof NameCallback) { - nc = (NameCallback) callback; - } else if (callback instanceof PasswordCallback) { - pc = (PasswordCallback) callback; - } else if (callback instanceof RealmCallback) { - rc = (RealmCallback) callback; - } else { - throw new UnsupportedCallbackException(callback, "Unrecognized SASL client callback"); - } - } - if (nc != null) { - if (LOG.isDebugEnabled()) { - LOG.debug("SASL client callback: setting username: " + userName); - } - nc.setName(userName); - } - if (pc != null) { - if (LOG.isDebugEnabled()) { - LOG.debug("SASL client callback: setting userPassword"); - } - pc.setPassword(userPassword); - } - if (rc != null) { - if (LOG.isDebugEnabled()) { - LOG.debug("SASL client callback: setting realm: " + rc.getDefaultText()); - } - rc.setText(rc.getDefaultText()); - } - } - } } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcClient.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcClient.java index 37d3cddfaaa9..17d72ec0e1cd 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcClient.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcClient.java @@ -33,10 +33,8 @@ import javax.security.sasl.SaslException; import org.apache.hadoop.conf.Configuration; -import org.apache.yetus.audience.InterfaceAudience; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.apache.hadoop.hbase.io.crypto.aes.CryptoAES; +import org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider; import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos; import org.apache.hadoop.io.WritableUtils; import org.apache.hadoop.ipc.RemoteException; @@ -44,6 +42,9 @@ import org.apache.hadoop.security.SaslOutputStream; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; +import org.apache.yetus.audience.InterfaceAudience; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * A utility class that encapsulates SASL logic for RPC client. Copied from @@ -61,15 +62,16 @@ public class HBaseSaslRpcClient extends AbstractHBaseSaslRpcClient { private OutputStream cryptoOutputStream; private boolean initStreamForCrypto; - public HBaseSaslRpcClient(AuthMethod method, Token token, - String serverPrincipal, boolean fallbackAllowed) throws IOException { - super(method, token, serverPrincipal, fallbackAllowed); + public HBaseSaslRpcClient(Configuration conf, SaslClientAuthenticationProvider provider, + Token token, String serverPrincipal, boolean fallbackAllowed) + throws IOException { + super(conf, provider, token, serverPrincipal, fallbackAllowed); } - public HBaseSaslRpcClient(AuthMethod method, Token token, - String serverPrincipal, boolean fallbackAllowed, String rpcProtection, - boolean initStreamForCrypto) throws IOException { - super(method, token, serverPrincipal, fallbackAllowed, rpcProtection); + public HBaseSaslRpcClient(Configuration conf, SaslClientAuthenticationProvider provider, + Token token, String serverPrincipal, boolean fallbackAllowed, + String rpcProtection, boolean initStreamForCrypto) throws IOException { + super(conf, provider, token, serverPrincipal, fallbackAllowed, rpcProtection); this.initStreamForCrypto = initStreamForCrypto; } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClient.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClient.java index 6771e328516d..a9ab6a585372 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClient.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClient.java @@ -27,6 +27,8 @@ import org.apache.yetus.audience.InterfaceAudience; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; @@ -38,9 +40,10 @@ public class NettyHBaseSaslRpcClient extends AbstractHBaseSaslRpcClient { private static final Logger LOG = LoggerFactory.getLogger(NettyHBaseSaslRpcClient.class); - public NettyHBaseSaslRpcClient(AuthMethod method, Token token, - String serverPrincipal, boolean fallbackAllowed, String rpcProtection) throws IOException { - super(method, token, serverPrincipal, fallbackAllowed, rpcProtection); + public NettyHBaseSaslRpcClient(Configuration conf, SaslClientAuthenticationProvider provider, + Token token, String serverPrincipal, boolean fallbackAllowed, + String rpcProtection) throws IOException { + super(conf, provider, token, serverPrincipal, fallbackAllowed, rpcProtection); } public void setupSaslHandler(ChannelPipeline p) { diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClientHandler.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClientHandler.java index eb4f20545f7b..05fcb47f7d68 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClientHandler.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClientHandler.java @@ -31,6 +31,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.hadoop.hbase.ipc.FallbackDisallowedException; +import org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; @@ -60,13 +61,13 @@ public class NettyHBaseSaslRpcClientHandler extends SimpleChannelInboundHandler< * simple. */ public NettyHBaseSaslRpcClientHandler(Promise saslPromise, UserGroupInformation ugi, - AuthMethod method, Token token, String serverPrincipal, + SaslClientAuthenticationProvider provider, Token token, String serverPrincipal, boolean fallbackAllowed, Configuration conf) throws IOException { this.saslPromise = saslPromise; this.ugi = ugi; this.conf = conf; - this.saslRpcClient = new NettyHBaseSaslRpcClient(method, token, serverPrincipal, + this.saslRpcClient = new NettyHBaseSaslRpcClient(conf, provider, token, serverPrincipal, fallbackAllowed, conf.get( "hbase.rpc.protection", SaslUtil.QualityOfProtection.AUTHENTICATION.name().toLowerCase())); } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/SaslUtil.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/SaslUtil.java index 72851b6a9104..ad2067f2cf22 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/SaslUtil.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/SaslUtil.java @@ -68,15 +68,15 @@ public static String[] splitKerberosName(String fullName) { return fullName.split("[/@]"); } - static String encodeIdentifier(byte[] identifier) { + public static String encodeIdentifier(byte[] identifier) { return Base64.getEncoder().encodeToString(identifier); } - static byte[] decodeIdentifier(String identifier) { + public static byte[] decodeIdentifier(String identifier) { return Base64.getDecoder().decode(Bytes.toBytes(identifier)); } - static char[] encodePassword(byte[] password) { + public static char[] encodePassword(byte[] password) { return Base64.getEncoder().encodeToString(password).toCharArray(); } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AbstractSaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AbstractSaslClientAuthenticationProvider.java new file mode 100644 index 000000000000..c631442a7aed --- /dev/null +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AbstractSaslClientAuthenticationProvider.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.security.provider; + +import org.apache.hadoop.hbase.HBaseInterfaceAudience; +import org.apache.hadoop.io.Text; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.yetus.audience.InterfaceAudience; +import org.apache.yetus.audience.InterfaceStability; + +/** + * Base implementation of {@link SaslClientAuthenticationProvider}. All implementations should extend + * this class instead of directly implementing the interface. + */ +@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) +@InterfaceStability.Evolving +public abstract class AbstractSaslClientAuthenticationProvider implements SaslClientAuthenticationProvider { + public static final Text AUTH_TOKEN_TYPE = new Text("HBASE_AUTH_TOKEN"); + + + @Override + public final Text getTokenKind() { + // All HBase authentication tokens are "HBASE_AUTH_TOKEN"'s. We differentiate between them + // via the code(). + return AUTH_TOKEN_TYPE; + } + + /** + * Provides a hash code to identify this AuthenticationProvider among others. These two fields must be + * unique to ensure that authentication methods are clearly separated. + */ + @Override + public final int hashCode() { + return getSaslAuthMethod().hashCode(); + } + + @Override + public UserGroupInformation unwrapUgi(UserGroupInformation ugi) { + // Unwrap the UGI with the real user when we're using Kerberos auth + if (isKerberos() && ugi != null && ugi.getRealUser() != null) { + return ugi.getRealUser(); + } + + // Otherwise, use the UGI we were given + return ugi; + } +} diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java new file mode 100644 index 000000000000..be22b9e57c10 --- /dev/null +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java @@ -0,0 +1,47 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.security.provider; + +import java.util.Map; +import java.util.Set; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HBaseInterfaceAudience; +import org.apache.hadoop.hbase.util.Pair; +import org.apache.hadoop.io.Text; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.token.Token; +import org.apache.hadoop.security.token.TokenIdentifier; +import org.apache.yetus.audience.InterfaceAudience; +import org.apache.yetus.audience.InterfaceStability; + +@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) +@InterfaceStability.Evolving +public interface AuthenticationProviderSelector { + + /** + * Initializes the implementation with configuration and a set of providers available. + */ + void configure(Configuration conf, Map availableProviders); + + /** + * Chooses the authentication provider which should be used given the provided client context + * from the authentication providers passed in via {@link #configure(Configuration, Set)}. + */ + Pair> selectProvider(Text clusterId, UserGroupInformation ugi); +} diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DefaultProviderSelector.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DefaultProviderSelector.java new file mode 100644 index 000000000000..4e54fded2c1d --- /dev/null +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DefaultProviderSelector.java @@ -0,0 +1,86 @@ +package org.apache.hadoop.hbase.security.provider; + +import java.util.Map; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.security.User; +import org.apache.hadoop.hbase.util.Pair; +import org.apache.hadoop.io.Text; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.token.Token; +import org.apache.hadoop.security.token.TokenIdentifier; +import org.apache.yetus.audience.InterfaceAudience; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@InterfaceAudience.Private +public class DefaultProviderSelector implements AuthenticationProviderSelector { + private static final Logger LOG = LoggerFactory.getLogger(DefaultProviderSelector.class); + + Configuration conf; + SimpleSaslClientAuthenticationProvider simpleAuth = null; + GssSaslClientAuthenticationProvider krbAuth = null; + DigestSaslClientAuthenticationProvider digestAuth = null; + Map providers; + + @Override + public void configure(Configuration conf, Map providers) { + this.conf = conf; + this.providers = providers; + for (SaslClientAuthenticationProvider provider : providers.values()) { + if (provider instanceof SimpleSaslClientAuthenticationProvider) { + if (simpleAuth != null) { + LOG.warn("Ignoring duplicate SimpleSaslClientAuthenticationProvider: previous={}, ignored={}", + simpleAuth.getClass(), provider.getClass()); + } else { + simpleAuth = (SimpleSaslClientAuthenticationProvider) provider; + } + } else if (provider instanceof GssSaslClientAuthenticationProvider) { + if (krbAuth != null) { + LOG.warn("Ignoring duplicate GssSaslClientAuthenticationProvider: previous={}, ignored={}", + krbAuth.getClass(), provider.getClass()); + } else { + krbAuth = (GssSaslClientAuthenticationProvider) provider; + } + } else if (provider instanceof DigestSaslClientAuthenticationProvider) { + if (digestAuth != null) { + LOG.warn("Ignoring duplicate DigestSaslClientAuthenticationProvider: previous={}, ignored={}", + digestAuth.getClass(), provider.getClass()); + } else { + digestAuth = (DigestSaslClientAuthenticationProvider) provider; + } + } else { + LOG.warn("Ignoring unknown SaslClientAuthenticationProvider: {}", provider.getClass()); + } + } + } + + @Override + public Pair> selectProvider( + Text clusterId, UserGroupInformation ugi) { + if (clusterId == null) { + throw new NullPointerException("Null clusterId was given"); + } + // Superfluous: we dont' do SIMPLE auth over SASL, but we should to simplify. + if (!User.isHBaseSecurityEnabled(conf)) { + return new Pair<>(simpleAuth, null); + } + // Must be digest auth, look for a token. + // TestGenerateDelegationToken is written expecting DT is used when DT and Krb are both present. + // (for whatever that's worth). + for (Token token : ugi.getTokens()) { + // We need to check for two things: + // 1. This token is for the HBase cluster we want to talk to + // 2. We have suppporting client implementation to handle the token (the "kind" of token) + if (clusterId.equals(token.getService()) && digestAuth.getTokenKind().equals(token.getKind())) { + return new Pair<>(digestAuth, token); + } + } + if (ugi.hasKerberosCredentials()) { + return new Pair<>(krbAuth, null); + } + LOG.warn("No matching SASL authentication provider and supporting token found from providers {} to HBase cluster {}", providers, clusterId); + return null; + } + +} diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java new file mode 100644 index 000000000000..c2e183cc48ed --- /dev/null +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.security.provider; + +import java.io.IOException; +import java.util.Map; + +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.sasl.RealmCallback; +import javax.security.sasl.RealmChoiceCallback; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslClient; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HBaseInterfaceAudience; +import org.apache.hadoop.hbase.security.SaslUtil; +import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; +import org.apache.hadoop.security.token.Token; +import org.apache.hadoop.security.token.TokenIdentifier; +import org.apache.yetus.audience.InterfaceAudience; +import org.apache.yetus.audience.InterfaceStability; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) +@InterfaceStability.Evolving +public class DigestSaslClientAuthenticationProvider extends AbstractSaslClientAuthenticationProvider { + + private static final String MECHANISM = "DIGEST-MD5"; + private static final SaslAuthMethod SASL_AUTH_METHOD = new SaslAuthMethod( + "DIGEST", (byte)82, MECHANISM, AuthenticationMethod.TOKEN); + + public static String getMechanism() { + return MECHANISM; + } + + @Override + public SaslClient createClient(Configuration conf, String serverPrincipal, + Token token, boolean fallbackAllowed, + Map saslProps) throws IOException { + return Sasl.createSaslClient(new String[] { MECHANISM }, null, null, + SaslUtil.SASL_DEFAULT_REALM, saslProps, new DigestSaslClientCallbackHandler(token)); + } + + @Override + public SaslAuthMethod getSaslAuthMethod() { + return SASL_AUTH_METHOD; + } + + public static class DigestSaslClientCallbackHandler implements CallbackHandler { + private static final Logger LOG = LoggerFactory.getLogger(DigestSaslClientCallbackHandler.class); + private final String userName; + private final char[] userPassword; + + public DigestSaslClientCallbackHandler(Token token) { + this.userName = SaslUtil.encodeIdentifier(token.getIdentifier()); + this.userPassword = SaslUtil.encodePassword(token.getPassword()); + } + + @Override + public void handle(Callback[] callbacks) throws UnsupportedCallbackException { + NameCallback nc = null; + PasswordCallback pc = null; + RealmCallback rc = null; + for (Callback callback : callbacks) { + if (callback instanceof RealmChoiceCallback) { + continue; + } else if (callback instanceof NameCallback) { + nc = (NameCallback) callback; + } else if (callback instanceof PasswordCallback) { + pc = (PasswordCallback) callback; + } else if (callback instanceof RealmCallback) { + rc = (RealmCallback) callback; + } else { + throw new UnsupportedCallbackException(callback, "Unrecognized SASL client callback"); + } + } + if (nc != null) { + if (LOG.isDebugEnabled()) { + LOG.debug("SASL client callback: setting username: " + userName); + } + nc.setName(userName); + } + if (pc != null) { + if (LOG.isDebugEnabled()) { + LOG.debug("SASL client callback: setting userPassword"); + } + pc.setPassword(userPassword); + } + if (rc != null) { + if (LOG.isDebugEnabled()) { + LOG.debug("SASL client callback: setting realm: " + rc.getDefaultText()); + } + rc.setText(rc.getDefaultText()); + } + } + } + + @Override + public UserInformation getUserInfo(UserGroupInformation user) { + // Don't send user for token auth. Copied from RpcConnection. + return null; + } + + @Override + public boolean isKerberos() { + return false; + } +} diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java new file mode 100644 index 000000000000..31d8d8a1800e --- /dev/null +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.security.provider; + +import java.io.IOException; +import java.util.Map; + +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslClient; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HBaseInterfaceAudience; +import org.apache.hadoop.hbase.security.SaslUtil; +import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; +import org.apache.hadoop.security.token.Token; +import org.apache.hadoop.security.token.TokenIdentifier; +import org.apache.yetus.audience.InterfaceAudience; +import org.apache.yetus.audience.InterfaceStability; + +@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) +@InterfaceStability.Evolving +public class GssSaslClientAuthenticationProvider extends AbstractSaslClientAuthenticationProvider { + private static final String MECHANISM = "GSSAPI"; + private static final SaslAuthMethod SASL_AUTH_METHOD = new SaslAuthMethod( + "KERBEROS", (byte)81, MECHANISM, AuthenticationMethod.KERBEROS); + + @Override + public SaslClient createClient(Configuration conf, String serverPrincipal, + Token token, boolean fallbackAllowed, + Map saslProps) throws IOException { + String[] names = SaslUtil.splitKerberosName(serverPrincipal); + if (names.length != 3) { + throw new IOException("Kerberos principal '" + serverPrincipal + + "' does not have the expected format"); + } + return Sasl.createSaslClient(new String[] { MECHANISM }, null, names[0], names[1], saslProps, + null); + } + + @Override + public SaslAuthMethod getSaslAuthMethod() { + return SASL_AUTH_METHOD; + } + + @Override + public UserInformation getUserInfo(UserGroupInformation user) { + UserInformation.Builder userInfoPB = UserInformation.newBuilder(); + // Send effective user for Kerberos auth + userInfoPB.setEffectiveUser(user.getUserName()); + return userInfoPB.build(); + } + + @Override + public boolean isKerberos() { + return true; + } +} diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslAuthMethod.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslAuthMethod.java new file mode 100644 index 000000000000..0ecb34d0da20 --- /dev/null +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslAuthMethod.java @@ -0,0 +1,96 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.security.provider; + +import java.util.Objects; + +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.hadoop.hbase.HBaseInterfaceAudience; +import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; +import org.apache.yetus.audience.InterfaceAudience; +import org.apache.yetus.audience.InterfaceStability; + +/** + * Describes the way in which some {@link SaslClientAuthenticationProvider} authenticates over SASL. + */ +@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) +@InterfaceStability.Evolving +public class SaslAuthMethod { + + private final String name; + private final byte code; + private final String saslMech; + private final AuthenticationMethod method; + + public SaslAuthMethod(String name, byte code, String saslMech, AuthenticationMethod method) { + this.name = name; + this.code = code; + this.saslMech = saslMech; + this.method = method; + } + + /** + * Returns the unique name to identify this authentication method among other HBase auth methods. + */ + public String getName() { + return name; + } + + /** + * Returns the unique value to identify this authentication method among other HBase auth methods. + */ + public byte getCode() { + return code; + } + + /** + * Returns the SASL mechanism used by this authentication method. + */ + public String getSaslMechanism() { + return saslMech; + } + + /** + * Returns the Hadoop {@link AuthenticationMethod} for this method. + */ + public AuthenticationMethod getAuthMethod() { + return method; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof SaslAuthMethod)) { + return false; + } + SaslAuthMethod other = (SaslAuthMethod) o; + return Objects.equals(name, other.name) && + code == other.code && + Objects.equals(saslMech, other.saslMech) && + Objects.equals(method, other.method); + } + + @Override + public int hashCode() { + return new HashCodeBuilder() + .append(name) + .append(code) + .append(saslMech) + .append(method) + .toHashCode(); + } +} diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProvider.java new file mode 100644 index 000000000000..1130cac65e89 --- /dev/null +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProvider.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.security.provider; + +import java.io.IOException; +import java.util.Map; + +import javax.security.sasl.SaslClient; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HBaseInterfaceAudience; +import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; +import org.apache.hadoop.io.Text; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.token.Token; +import org.apache.hadoop.security.token.TokenIdentifier; +import org.apache.yetus.audience.InterfaceAudience; +import org.apache.yetus.audience.InterfaceStability; + +/** + * Encapsulation of client-side logic to authenticate to HBase via some means over SASL. + * Implementations should not directly implement this interface, but instead extend + * {@link AbstractSaslClientAuthenticationProvider}. + * + * Implementations of this interface must make an implementation of {link {@link #hashCode()} + * which returns the same value across multiple instances of the provider implementation. + */ +@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) +@InterfaceStability.Evolving +public interface SaslClientAuthenticationProvider { + + /** + * Creates the SASL client instance for this auth'n method. + */ + SaslClient createClient(Configuration conf, String serverPrincipal, + Token token, boolean fallbackAllowed, + Map saslProps) throws IOException; + + /** + * Returns the attributes which identify how this provider authenticates. + */ + SaslAuthMethod getSaslAuthMethod(); + + /** + * Returns the name of the type used by the TokenIdentifier. + */ + Text getTokenKind(); + + /** + * Constructs a {@link UserInformation} from the given {@link UserGroupInformation} + */ + UserInformation getUserInfo(UserGroupInformation user); + + /** + * Returns true if this provider is based on Kerberos. False, otherwise. + */ + boolean isKerberos(); + + /** + * Performs any necessary unwrapping of a "proxy" user UGI which is executing some request + * on top of a "real" user. This operation may simply return the original UGI if that is + * what is appropriate for the given implementation. + */ + UserGroupInformation unwrapUgi(UserGroupInformation ugi); +} diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java new file mode 100644 index 000000000000..952d489f4a71 --- /dev/null +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java @@ -0,0 +1,203 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.security.provider; + +import java.util.HashMap; +import java.util.Optional; +import java.util.ServiceLoader; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HBaseInterfaceAudience; +import org.apache.hadoop.hbase.util.Pair; +import org.apache.hadoop.io.Text; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.token.Token; +import org.apache.hadoop.security.token.TokenIdentifier; +import org.apache.yetus.audience.InterfaceAudience; +import org.apache.yetus.audience.InterfaceStability; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Accessor for all SaslAuthenticationProvider instances. + */ +@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) +@InterfaceStability.Evolving +public class SaslClientAuthenticationProviders { + private static final Logger LOG = LoggerFactory.getLogger(SaslClientAuthenticationProviders.class); + + public static final String SELECTOR_KEY = "hbase.client.sasl.provider.class"; + public static final String EXTRA_PROVIDERS_KEY = "hbase.client.sasl.provider.extras"; + + private static final AtomicReference providersRef = + new AtomicReference<>(); + + private final HashMap providers; + private final AuthenticationProviderSelector selector; + + private SaslClientAuthenticationProviders(HashMap providers, + AuthenticationProviderSelector selector) { + this.providers = providers; + this.selector = selector; + } + + /** + * Returns the number of providers that have been registered. + */ + public int getNumRegisteredProviders() { + return providers.size(); + } + + /** + * Returns a singleton instance of {@link SaslClientAuthenticationProviders}. + */ + public static synchronized SaslClientAuthenticationProviders getInstance(Configuration conf) { + SaslClientAuthenticationProviders providers = providersRef.get(); + if (providers == null) { + providers = instantiate(conf); + providersRef.set(providers); + } + + return providers; + } + + /** + * Removes the cached singleton instance of {@link SaslClientAuthenticationProviders}. + */ + public static synchronized void reset() { + providersRef.set(null); + } + + /** + * Adds the given {@code provider} to the set, only if an equivalent provider does not + * already exist in the set. + */ + static void addProviderIfNotExists(SaslClientAuthenticationProvider provider, + HashMap providers) { + SaslClientAuthenticationProvider existingProvider = providers.get( + provider.getSaslAuthMethod().getCode()); + if (existingProvider != null) { + throw new RuntimeException("Already registered authentication provider with " + + provider.getSaslAuthMethod().getCode() + " " + existingProvider.getClass()); + } + providers.put(provider.getSaslAuthMethod().getCode(), provider); + } + + /** + * Instantiates the ProviderSelector implementation from the provided configuration. + */ + static AuthenticationProviderSelector instantiateSelector(Configuration conf, + HashMap providers) { + Class clz = conf.getClass( + SELECTOR_KEY, DefaultProviderSelector.class, AuthenticationProviderSelector.class); + try { + AuthenticationProviderSelector selector = clz.newInstance(); + selector.configure(conf, providers); + if (LOG.isTraceEnabled()) { + LOG.trace("Loaded ProviderSelector {}", selector.getClass()); + } + return selector; + } catch (InstantiationException | IllegalAccessException e) { + throw new RuntimeException("Failed to instantiate " + clz + + " as the ProviderSelector defined by " + SELECTOR_KEY, e); + } + } + + /** + * Extracts and instantiates authentication providers from the configuration. + */ + static void addExplicitProviders(Configuration conf, + HashMap providers) { + for(String implName : conf.getStringCollection(EXTRA_PROVIDERS_KEY)) { + Class clz; + // Load the class from the config + try { + clz = Class.forName(implName); + } catch (ClassNotFoundException e) { + LOG.warn("Failed to load SaslClientAuthenticationProvider {}", implName, e); + continue; + } + + // Make sure it's the right type + if (!SaslClientAuthenticationProvider.class.isAssignableFrom(clz)) { + LOG.warn("Ignoring SaslClientAuthenticationProvider {} because it is not an instance of" + + " SaslClientAuthenticationProvider", clz); + continue; + } + + // Instantiate it + SaslClientAuthenticationProvider provider; + try { + provider = (SaslClientAuthenticationProvider) clz.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + LOG.warn("Failed to instantiate SaslClientAuthenticationProvider {}", clz, e); + continue; + } + + // Add it to our set, only if it doesn't conflict with something else we've + // already registered. + addProviderIfNotExists(provider, providers); + } + } + + /** + * Instantiates all client authentication providers and returns an instance of + * {@link SaslClientAuthenticationProviders}. + */ + static SaslClientAuthenticationProviders instantiate(Configuration conf) { + ServiceLoader loader = + ServiceLoader.load(SaslClientAuthenticationProvider.class); + HashMap providers = new HashMap<>(); + for (SaslClientAuthenticationProvider provider : loader) { + addProviderIfNotExists(provider, providers); + } + + addExplicitProviders(conf, providers); + + AuthenticationProviderSelector selector = instantiateSelector(conf, providers); + + if (LOG.isTraceEnabled()) { + String loadedProviders = providers.values().stream() + .map((provider) -> provider.getClass().getName()) + .collect(Collectors.joining(", ")); + LOG.trace("Found SaslClientAuthenticationProviders {}", loadedProviders); + } + return new SaslClientAuthenticationProviders(providers, selector); + } + + public SaslClientAuthenticationProvider getSimpleProvider() { + Optional optional = providers.values().stream() + .filter((p) -> p instanceof SimpleSaslClientAuthenticationProvider) + .findFirst(); + return optional.get(); + } + + public Pair> selectProvider( + Text clusterId, UserGroupInformation clientUser) { + return selector.selectProvider(clusterId, clientUser); + } + + @Override + public String toString() { + return providers.values().stream() + .map((p) -> p.getClass().getName()) + .collect(Collectors.joining(", ", "providers=[", "], selector=")) + selector.getClass(); + } +} diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslClientAuthenticationProvider.java new file mode 100644 index 000000000000..0a5ab581de3f --- /dev/null +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslClientAuthenticationProvider.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.security.provider; + +import java.io.IOException; +import java.util.Map; + +import javax.security.sasl.SaslClient; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HBaseInterfaceAudience; +import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; +import org.apache.hadoop.security.token.Token; +import org.apache.hadoop.security.token.TokenIdentifier; +import org.apache.yetus.audience.InterfaceAudience; +import org.apache.yetus.audience.InterfaceStability; + +@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) +@InterfaceStability.Evolving +public class SimpleSaslClientAuthenticationProvider extends AbstractSaslClientAuthenticationProvider { + private static final SaslAuthMethod SASL_AUTH_METHOD = new SaslAuthMethod( + "SIMPLE", (byte)80, "", AuthenticationMethod.SIMPLE); + + @Override + public SaslClient createClient(Configuration conf, String serverPrincipal, + Token token, boolean fallbackAllowed, + Map saslProps) throws IOException { + return null; + } + + @Override + public UserInformation getUserInfo(UserGroupInformation user) { + UserInformation.Builder userInfoPB = UserInformation.newBuilder(); + // Send both effective user and real user for simple auth + userInfoPB.setEffectiveUser(user.getUserName()); + if (user.getRealUser() != null) { + userInfoPB.setRealUser(user.getRealUser().getUserName()); + } + return userInfoPB.build(); + } + + @Override + public boolean isKerberos() { + return false; + } + + @Override + public SaslAuthMethod getSaslAuthMethod() { + return SASL_AUTH_METHOD; + } +} diff --git a/hbase-client/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider b/hbase-client/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider new file mode 100644 index 000000000000..732844fdb3c4 --- /dev/null +++ b/hbase-client/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider @@ -0,0 +1,3 @@ +org.apache.hadoop.hbase.security.provider.GssSaslClientAuthenticationProvider +org.apache.hadoop.hbase.security.provider.DigestSaslClientAuthenticationProvider +org.apache.hadoop.hbase.security.provider.SimpleSaslClientAuthenticationProvider \ No newline at end of file diff --git a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestHBaseSaslRpcClient.java b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestHBaseSaslRpcClient.java index 5e97ada2be3e..4b3be04a8ed0 100644 --- a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestHBaseSaslRpcClient.java +++ b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestHBaseSaslRpcClient.java @@ -21,15 +21,16 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.io.IOException; +import java.util.Map; + 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.TextOutputCallback; @@ -38,8 +39,14 @@ import javax.security.sasl.RealmChoiceCallback; import javax.security.sasl.Sasl; import javax.security.sasl.SaslClient; + +import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseClassTestRule; -import org.apache.hadoop.hbase.security.AbstractHBaseSaslRpcClient.SaslClientCallbackHandler; +import org.apache.hadoop.hbase.HBaseConfiguration; +import org.apache.hadoop.hbase.security.provider.DigestSaslClientAuthenticationProvider; +import org.apache.hadoop.hbase.security.provider.DigestSaslClientAuthenticationProvider.DigestSaslClientCallbackHandler; +import org.apache.hadoop.hbase.security.provider.GssSaslClientAuthenticationProvider; +import org.apache.hadoop.hbase.security.provider.SimpleSaslClientAuthenticationProvider; import org.apache.hadoop.hbase.testclassification.SecurityTests; import org.apache.hadoop.hbase.testclassification.SmallTests; import org.apache.hadoop.hbase.util.Bytes; @@ -47,6 +54,7 @@ import org.apache.hadoop.io.DataOutputBuffer; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; +import org.apache.hbase.thirdparty.com.google.common.base.Strings; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.junit.BeforeClass; @@ -57,8 +65,6 @@ import org.junit.rules.ExpectedException; import org.mockito.Mockito; -import org.apache.hbase.thirdparty.com.google.common.base.Strings; - @Category({SecurityTests.class, SmallTests.class}) public class TestHBaseSaslRpcClient { @@ -89,8 +95,9 @@ public static void before() { public void testSaslClientUsesGivenRpcProtection() throws Exception { Token token = createTokenMockWithCredentials(DEFAULT_USER_NAME, DEFAULT_USER_PASSWORD); + DigestSaslClientAuthenticationProvider provider = new DigestSaslClientAuthenticationProvider(); for (SaslUtil.QualityOfProtection qop : SaslUtil.QualityOfProtection.values()) { - String negotiatedQop = new HBaseSaslRpcClient(AuthMethod.DIGEST, token, + String negotiatedQop = new HBaseSaslRpcClient(HBaseConfiguration.create(), provider, token, "principal/host@DOMAIN.COM", false, qop.name(), false) { public String getQop() { return saslProps.get(Sasl.QOP); @@ -101,7 +108,7 @@ public String getQop() { } @Test - public void testSaslClientCallbackHandler() throws UnsupportedCallbackException { + public void testDigestSaslClientCallbackHandler() throws UnsupportedCallbackException { final Token token = createTokenMock(); when(token.getIdentifier()).thenReturn(Bytes.toBytes(DEFAULT_USER_NAME)); when(token.getPassword()).thenReturn(Bytes.toBytes(DEFAULT_USER_PASSWORD)); @@ -112,7 +119,7 @@ public void testSaslClientCallbackHandler() throws UnsupportedCallbackException final RealmChoiceCallback realmChoiceCallback = mock(RealmChoiceCallback.class); Callback[] callbackArray = {nameCallback, passwordCallback, realmCallback, realmChoiceCallback}; - final SaslClientCallbackHandler saslClCallbackHandler = new SaslClientCallbackHandler(token); + final DigestSaslClientCallbackHandler saslClCallbackHandler = new DigestSaslClientCallbackHandler(token); saslClCallbackHandler.handle(callbackArray); verify(nameCallback).setName(anyString()); verify(realmCallback).setText(any()); @@ -120,17 +127,17 @@ public void testSaslClientCallbackHandler() throws UnsupportedCallbackException } @Test - public void testSaslClientCallbackHandlerWithException() { + public void testDigestSaslClientCallbackHandlerWithException() { final Token token = createTokenMock(); when(token.getIdentifier()).thenReturn(Bytes.toBytes(DEFAULT_USER_NAME)); when(token.getPassword()).thenReturn(Bytes.toBytes(DEFAULT_USER_PASSWORD)); - final SaslClientCallbackHandler saslClCallbackHandler = new SaslClientCallbackHandler(token); + final DigestSaslClientCallbackHandler saslClCallbackHandler = new DigestSaslClientCallbackHandler(token); try { saslClCallbackHandler.handle(new Callback[] { mock(TextOutputCallback.class) }); } catch (UnsupportedCallbackException expEx) { //expected } catch (Exception ex) { - fail("testSaslClientCallbackHandlerWithException error : " + ex.getMessage()); + fail("testDigestSaslClientCallbackHandlerWithException error : " + ex.getMessage()); } } @@ -196,21 +203,16 @@ private boolean assertIOExceptionWhenGetStreamsBeforeConnectCall(String principa boolean inState = false; boolean outState = false; - HBaseSaslRpcClient rpcClient = new HBaseSaslRpcClient(AuthMethod.DIGEST, - createTokenMockWithCredentials(principal, password), principal, false) { - @Override - public SaslClient createDigestSaslClient(String[] mechanismNames, - String saslDefaultRealm, CallbackHandler saslClientCallbackHandler) - throws IOException { - return Mockito.mock(SaslClient.class); - } - + DigestSaslClientAuthenticationProvider provider = new DigestSaslClientAuthenticationProvider() { @Override - public SaslClient createKerberosSaslClient(String[] mechanismNames, - String userFirstPart, String userSecondPart) throws IOException { + public SaslClient createClient(Configuration conf, String serverPrincipal, + Token token, boolean fallbackAllowed, + Map saslProps) { return Mockito.mock(SaslClient.class); } }; + HBaseSaslRpcClient rpcClient = new HBaseSaslRpcClient(HBaseConfiguration.create(), provider, + createTokenMockWithCredentials(principal, password), principal, false); try { rpcClient.getInputStream(); @@ -231,21 +233,16 @@ public SaslClient createKerberosSaslClient(String[] mechanismNames, private boolean assertIOExceptionThenSaslClientIsNull(String principal, String password) { try { - new HBaseSaslRpcClient(AuthMethod.DIGEST, - createTokenMockWithCredentials(principal, password), principal, false) { + DigestSaslClientAuthenticationProvider provider = new DigestSaslClientAuthenticationProvider() { @Override - public SaslClient createDigestSaslClient(String[] mechanismNames, - String saslDefaultRealm, CallbackHandler saslClientCallbackHandler) - throws IOException { - return null; - } - - @Override - public SaslClient createKerberosSaslClient(String[] mechanismNames, - String userFirstPart, String userSecondPart) throws IOException { + public SaslClient createClient(Configuration conf, String serverPrincipal, + Token token, boolean fallbackAllowed, + Map saslProps) { return null; } }; + new HBaseSaslRpcClient(HBaseConfiguration.create(), provider, + createTokenMockWithCredentials(principal, password), principal, false); return false; } catch (IOException ex) { return true; @@ -265,7 +262,8 @@ private boolean assertSuccessCreationKerberosPrincipal(String principal) { private boolean assertSuccessCreationDigestPrincipal(String principal, String password) { HBaseSaslRpcClient rpcClient = null; try { - rpcClient = new HBaseSaslRpcClient(AuthMethod.DIGEST, + rpcClient = new HBaseSaslRpcClient(HBaseConfiguration.create(), + new DigestSaslClientAuthenticationProvider(), createTokenMockWithCredentials(principal, password), principal, false); } catch(Exception ex) { LOG.error(ex.getMessage(), ex); @@ -285,7 +283,9 @@ private boolean assertSuccessCreationSimplePrincipal(String principal, String pa private HBaseSaslRpcClient createSaslRpcClientForKerberos(String principal) throws IOException { - return new HBaseSaslRpcClient(AuthMethod.KERBEROS, createTokenMock(), principal, false); + return new HBaseSaslRpcClient(HBaseConfiguration.create(), + new GssSaslClientAuthenticationProvider(), createTokenMock(), + principal, false); } private Token createTokenMockWithCredentials( @@ -301,7 +301,8 @@ private Token createTokenMockWithCredentials( private HBaseSaslRpcClient createSaslRpcClientSimple(String principal, String password) throws IOException { - return new HBaseSaslRpcClient(AuthMethod.SIMPLE, createTokenMock(), principal, false); + return new HBaseSaslRpcClient(HBaseConfiguration.create(), new SimpleSaslClientAuthenticationProvider(), + createTokenMock(), principal, false); } @SuppressWarnings("unchecked") diff --git a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslClientAuthenticationProviders.java b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslClientAuthenticationProviders.java new file mode 100644 index 000000000000..d4b6d54faace --- /dev/null +++ b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslClientAuthenticationProviders.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.security.provider; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; + +import java.util.HashMap; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HBaseConfiguration; +import org.apache.hadoop.hbase.testclassification.SecurityTests; +import org.apache.hadoop.hbase.testclassification.SmallTests; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +@Category({SmallTests.class, SecurityTests.class}) +public class TestSaslClientAuthenticationProviders { + + @Test + public void testCannotAddTheSameProviderTwice() { + HashMap registeredProviders = new HashMap<>(); + SaslClientAuthenticationProvider p1 = new SimpleSaslClientAuthenticationProvider(); + SaslClientAuthenticationProvider p2 = new SimpleSaslClientAuthenticationProvider(); + + SaslClientAuthenticationProviders.addProviderIfNotExists(p1, registeredProviders); + assertEquals(1, registeredProviders.size()); + + try { + SaslClientAuthenticationProviders.addProviderIfNotExists(p2, registeredProviders); + } catch (RuntimeException e) {} + + assertSame("Expected the original provider to be present", p1, + registeredProviders.entrySet().iterator().next().getValue()); + } + + @Test + public void testInstanceIsCached() { + Configuration conf = HBaseConfiguration.create(); + SaslClientAuthenticationProviders providers1 = SaslClientAuthenticationProviders.getInstance(conf); + SaslClientAuthenticationProviders providers2 = SaslClientAuthenticationProviders.getInstance(conf); + assertSame(providers1, providers2); + + SaslClientAuthenticationProviders.reset(); + + SaslClientAuthenticationProviders providers3 = SaslClientAuthenticationProviders.getInstance(conf); + assertNotSame(providers1, providers3); + assertEquals(providers1.getNumRegisteredProviders(), providers3.getNumRegisteredProviders()); + } +} diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/HBaseInterfaceAudience.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/HBaseInterfaceAudience.java index d5d4643a48da..12b8398362ae 100644 --- a/hbase-common/src/main/java/org/apache/hadoop/hbase/HBaseInterfaceAudience.java +++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/HBaseInterfaceAudience.java @@ -53,4 +53,9 @@ private HBaseInterfaceAudience(){} * Denotes classes used by hbck tool for fixing inconsistent state of HBase. */ public static final String HBCK = "HBCK"; + + /** + * Denotes classes that can be used to build custom authentication solutions. + */ + public static final String AUTHENTICATION = "Authentication"; } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java index cff917f89f53..4b3032ca74e4 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java @@ -45,11 +45,13 @@ import org.apache.hadoop.hbase.nio.ByteBuff; import org.apache.hadoop.hbase.nio.SingleByteBuff; import org.apache.hadoop.hbase.security.AccessDeniedException; -import org.apache.hadoop.hbase.security.AuthMethod; import org.apache.hadoop.hbase.security.HBaseSaslRpcServer; import org.apache.hadoop.hbase.security.SaslStatus; import org.apache.hadoop.hbase.security.SaslUtil; import org.apache.hadoop.hbase.security.User; +import org.apache.hadoop.hbase.security.provider.SaslServerAuthenticationProvider; +import org.apache.hadoop.hbase.security.provider.SaslServerAuthenticationProviders; +import org.apache.hadoop.hbase.security.provider.SimpleSaslServerAuthenticationProvider; import org.apache.hbase.thirdparty.com.google.protobuf.BlockingService; import org.apache.hbase.thirdparty.com.google.protobuf.ByteInput; import org.apache.hbase.thirdparty.com.google.protobuf.ByteString; @@ -67,7 +69,6 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.io.BytesWritable; -import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Writable; import org.apache.hadoop.io.WritableUtils; import org.apache.hadoop.io.compress.CompressionCodec; @@ -76,7 +77,6 @@ import org.apache.hadoop.security.authorize.AuthorizationException; import org.apache.hadoop.security.authorize.ProxyUsers; import org.apache.hadoop.security.token.SecretManager.InvalidToken; -import org.apache.hadoop.security.token.TokenIdentifier; /** Reads calls from a connection and queues them for handling. */ @edu.umd.cs.findbugs.annotations.SuppressWarnings( @@ -108,7 +108,8 @@ abstract class ServerRpcConnection implements Closeable { protected CompressionCodec compressionCodec; protected BlockingService service; - protected AuthMethod authMethod; + protected SaslServerAuthenticationProvider provider; +// protected AuthMethod authMethod; protected boolean saslContextEstablished; protected boolean skipInitialSaslHandshake; private ByteBuffer unwrappedData; @@ -127,10 +128,12 @@ abstract class ServerRpcConnection implements Closeable { protected User user = null; protected UserGroupInformation ugi = null; + protected SaslServerAuthenticationProviders saslProviders = null; public ServerRpcConnection(RpcServer rpcServer) { this.rpcServer = rpcServer; this.callCleanup = null; + this.saslProviders = SaslServerAuthenticationProviders.getInstance(rpcServer.getConf()); } @Override @@ -160,26 +163,7 @@ public VersionInfo getVersionInfo() { private String getFatalConnectionString(final int version, final byte authByte) { return "serverVersion=" + RpcServer.CURRENT_VERSION + ", clientVersion=" + version + ", authMethod=" + authByte + - ", authSupported=" + (authMethod != null) + " from " + toString(); - } - - private UserGroupInformation getAuthorizedUgi(String authorizedId) - throws IOException { - UserGroupInformation authorizedUgi; - if (authMethod == AuthMethod.DIGEST) { - TokenIdentifier tokenId = HBaseSaslRpcServer.getIdentifier(authorizedId, - this.rpcServer.secretManager); - authorizedUgi = tokenId.getUser(); - if (authorizedUgi == null) { - throw new AccessDeniedException( - "Can't retrieve username from tokenIdentifier."); - } - authorizedUgi.addTokenIdentifier(tokenId); - } else { - authorizedUgi = UserGroupInformation.createRemoteUser(authorizedId); - } - authorizedUgi.setAuthenticationMethod(authMethod.authenticationMethod.getAuthMethod()); - return authorizedUgi; + ", authName=" + provider.getSaslAuthMethod().getName() + " from " + toString(); } /** @@ -362,9 +346,9 @@ public void saslReadAndProcess(ByteBuff saslToken) throws IOException, try { if (saslServer == null) { saslServer = - new HBaseSaslRpcServer(authMethod, rpcServer.saslProps, rpcServer.secretManager); + new HBaseSaslRpcServer(provider, rpcServer.saslProps, rpcServer.secretManager); RpcServer.LOG.debug("Created SASL server with mechanism={}", - authMethod.getMechanismName()); + provider.getSaslAuthMethod().getAuthMethod()); } RpcServer.LOG.debug("Read input token of size={} for processing by saslServer." + "evaluateResponse()", saslToken.limit()); @@ -400,7 +384,7 @@ public void saslReadAndProcess(ByteBuff saslToken) throws IOException, if (saslServer.isComplete()) { String qop = saslServer.getNegotiatedQop(); useWrap = qop != null && !"auth".equalsIgnoreCase(qop); - ugi = getAuthorizedUgi(saslServer.getAuthorizationID()); + ugi = provider.getAuthorizedUgi(saslServer.getAuthorizationID(), this.rpcServer.secretManager); if (RpcServer.LOG.isDebugEnabled()) { RpcServer.LOG.debug("SASL server context established. Authenticated client: " + ugi + ". Negotiated QoP is " + qop); @@ -473,7 +457,7 @@ private boolean authorizeConnection() throws IOException { // authorize real user. doAs is allowed only for simple or kerberos // authentication if (ugi != null && ugi.getRealUser() != null - && (authMethod != AuthMethod.DIGEST)) { + && provider.supportsProtocolAuthentication()) { ProxyUsers.authorize(ugi, this.getHostAddress(), this.rpcServer.conf); } this.rpcServer.authorize(ugi, connectionHeader, getHostInetAddress()); @@ -512,7 +496,7 @@ private void processConnectionHeader(ByteBuff buf) throws IOException { if (!useSasl) { ugi = protocolUser; if (ugi != null) { - ugi.setAuthenticationMethod(AuthMethod.SIMPLE.authenticationMethod); + ugi.setAuthenticationMethod(AuthenticationMethod.SIMPLE); } // audit logging for SASL authenticated users happens in saslReadAndProcess() if (authenticatedWithFallback) { @@ -521,13 +505,13 @@ private void processConnectionHeader(ByteBuff buf) throws IOException { } } else { // user is authenticated - ugi.setAuthenticationMethod(authMethod.authenticationMethod); + ugi.setAuthenticationMethod(provider.getSaslAuthMethod().getAuthMethod()); //Now we check if this is a proxy user case. If the protocol user is //different from the 'user', it is a proxy user scenario. However, //this is not allowed if user authenticated with DIGEST. if ((protocolUser != null) && (!protocolUser.getUserName().equals(ugi.getUserName()))) { - if (authMethod == AuthMethod.DIGEST) { + if (!provider.supportsProtocolAuthentication()) { // Not allowed to doAs if token authentication is used throw new AccessDeniedException("Authenticated user (" + ugi + ") doesn't match what the client claims to be (" @@ -741,18 +725,20 @@ protected final boolean processPreamble(ByteBuffer preambleBuffer) throws IOExce } int version = preambleBuffer.get() & 0xFF; byte authbyte = preambleBuffer.get(); - this.authMethod = AuthMethod.valueOf(authbyte); + if (version != SimpleRpcServer.CURRENT_VERSION) { String msg = getFatalConnectionString(version, authbyte); doBadPreambleHandling(msg, new WrongVersionException(msg)); return false; } - if (authMethod == null) { + this.provider = this.saslProviders.selectProvider(authbyte); + if (this.provider == null) { String msg = getFatalConnectionString(version, authbyte); doBadPreambleHandling(msg, new BadAuthException(msg)); return false; } - if (this.rpcServer.isSecurityEnabled && authMethod == AuthMethod.SIMPLE) { + // TODO this is a wart while simple auth'n doesn't go through sasl. + if (this.rpcServer.isSecurityEnabled && provider instanceof SimpleSaslServerAuthenticationProvider) { if (this.rpcServer.allowFallbackToSimpleAuth) { this.rpcServer.metrics.authenticationFallback(); authenticatedWithFallback = true; @@ -762,18 +748,17 @@ protected final boolean processPreamble(ByteBuffer preambleBuffer) throws IOExce return false; } } - if (!this.rpcServer.isSecurityEnabled && authMethod != AuthMethod.SIMPLE) { - doRawSaslReply(SaslStatus.SUCCESS, new IntWritable(SaslUtil.SWITCH_TO_SIMPLE_AUTH), null, - null); - authMethod = AuthMethod.SIMPLE; - // client has already sent the initial Sasl message and we - // should ignore it. Both client and server should fall back - // to simple auth from now on. - skipInitialSaslHandshake = true; - } - if (authMethod != AuthMethod.SIMPLE) { - useSasl = true; - } + // TODO can we remove this fallback? Is this even a good idea? +// if (!this.rpcServer.isSecurityEnabled && authMethod != AuthMethod.SIMPLE) { +// doRawSaslReply(SaslStatus.SUCCESS, new IntWritable(SaslUtil.SWITCH_TO_SIMPLE_AUTH), null, +// null); +// authMethod = AuthMethod.SIMPLE; +// // client has already sent the initial Sasl message and we +// // should ignore it. Both client and server should fall back +// // to simple auth from now on. +// skipInitialSaslHandshake = true; +// } + useSasl = !(provider instanceof SimpleSaslServerAuthenticationProvider); return true; } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcServer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcServer.java index b3924350b41c..25916a0100df 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcServer.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcServer.java @@ -20,16 +20,8 @@ import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; -import java.security.PrivilegedExceptionAction; import java.util.Map; -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.sasl.AuthorizeCallback; -import javax.security.sasl.RealmCallback; import javax.security.sasl.Sasl; import javax.security.sasl.SaslException; import javax.security.sasl.SaslServer; @@ -37,6 +29,7 @@ import org.apache.yetus.audience.InterfaceAudience; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.apache.hadoop.hbase.security.provider.SaslServerAuthenticationProvider; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.SecretManager; import org.apache.hadoop.security.token.SecretManager.InvalidToken; @@ -55,43 +48,9 @@ public class HBaseSaslRpcServer { private UserGroupInformation attemptingUser; // user name before auth - public HBaseSaslRpcServer(AuthMethod method, Map saslProps, + public HBaseSaslRpcServer(SaslServerAuthenticationProvider provider, Map saslProps, SecretManager secretManager) throws IOException { - switch (method) { - case DIGEST: - if (secretManager == null) { - throw new AccessDeniedException("Server is not configured to do DIGEST authentication."); - } - saslServer = Sasl.createSaslServer(AuthMethod.DIGEST.getMechanismName(), null, - SaslUtil.SASL_DEFAULT_REALM, saslProps, new SaslDigestCallbackHandler(secretManager)); - break; - case KERBEROS: - UserGroupInformation current = UserGroupInformation.getCurrentUser(); - String fullName = current.getUserName(); - if (LOG.isDebugEnabled()) { - LOG.debug("Kerberos principal name is " + fullName); - } - String[] names = SaslUtil.splitKerberosName(fullName); - if (names.length != 3) { - throw new AccessDeniedException( - "Kerberos principal name does NOT have the expected " + "hostname part: " + fullName); - } - try { - saslServer = current.doAs(new PrivilegedExceptionAction() { - @Override - public SaslServer run() throws SaslException { - return Sasl.createSaslServer(AuthMethod.KERBEROS.getMechanismName(), names[0], - names[1], saslProps, new SaslGssCallbackHandler()); - } - }); - } catch (InterruptedException e) { - // should not happen - throw new AssertionError(e); - } - break; - default: - throw new IOException("Unknown authentication method " + method); - } + saslServer = provider.createServer(secretManager, saslProps); } public boolean isComplete() { @@ -138,99 +97,4 @@ public static T getIdentifier(String id, } return tokenIdentifier; } - - /** CallbackHandler for SASL DIGEST-MD5 mechanism */ - private class SaslDigestCallbackHandler implements CallbackHandler { - private SecretManager secretManager; - - public SaslDigestCallbackHandler(SecretManager secretManager) { - this.secretManager = secretManager; - } - - private char[] getPassword(TokenIdentifier tokenid) throws InvalidToken { - return SaslUtil.encodePassword(secretManager.retrievePassword(tokenid)); - } - - /** {@inheritDoc} */ - @Override - public void handle(Callback[] callbacks) throws InvalidToken, UnsupportedCallbackException { - NameCallback nc = null; - PasswordCallback pc = null; - AuthorizeCallback ac = null; - for (Callback callback : callbacks) { - if (callback instanceof AuthorizeCallback) { - ac = (AuthorizeCallback) callback; - } else if (callback instanceof NameCallback) { - nc = (NameCallback) callback; - } else if (callback instanceof PasswordCallback) { - pc = (PasswordCallback) callback; - } else if (callback instanceof RealmCallback) { - continue; // realm is ignored - } else { - throw new UnsupportedCallbackException(callback, "Unrecognized SASL DIGEST-MD5 Callback"); - } - } - if (pc != null) { - TokenIdentifier tokenIdentifier = getIdentifier(nc.getDefaultName(), secretManager); - char[] password = getPassword(tokenIdentifier); - UserGroupInformation user = tokenIdentifier.getUser(); // may throw exception - attemptingUser = user; - if (LOG.isTraceEnabled()) { - LOG.trace("SASL server DIGEST-MD5 callback: setting password " + "for client: " + - tokenIdentifier.getUser()); - } - pc.setPassword(password); - } - if (ac != null) { - String authid = ac.getAuthenticationID(); - String authzid = ac.getAuthorizationID(); - if (authid.equals(authzid)) { - ac.setAuthorized(true); - } else { - ac.setAuthorized(false); - } - if (ac.isAuthorized()) { - if (LOG.isTraceEnabled()) { - String username = getIdentifier(authzid, secretManager).getUser().getUserName(); - LOG.trace( - "SASL server DIGEST-MD5 callback: setting " + "canonicalized client ID: " + username); - } - ac.setAuthorizedID(authzid); - } - } - } - } - - /** CallbackHandler for SASL GSSAPI Kerberos mechanism */ - private static class SaslGssCallbackHandler implements CallbackHandler { - - /** {@inheritDoc} */ - @Override - public void handle(Callback[] callbacks) throws UnsupportedCallbackException { - AuthorizeCallback ac = null; - for (Callback callback : callbacks) { - if (callback instanceof AuthorizeCallback) { - ac = (AuthorizeCallback) callback; - } else { - throw new UnsupportedCallbackException(callback, "Unrecognized SASL GSSAPI Callback"); - } - } - if (ac != null) { - String authid = ac.getAuthenticationID(); - String authzid = ac.getAuthorizationID(); - if (authid.equals(authzid)) { - ac.setAuthorized(true); - } else { - ac.setAuthorized(false); - } - if (ac.isAuthorized()) { - if (LOG.isDebugEnabled()) { - LOG.debug( - "SASL server GSSAPI callback: setting " + "canonicalized client ID: " + authzid); - } - ac.setAuthorizedID(authzid); - } - } - } - } } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java new file mode 100644 index 000000000000..e574bed6f9e0 --- /dev/null +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java @@ -0,0 +1,141 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.security.provider; + +import java.io.IOException; +import java.util.Map; + +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.sasl.AuthorizeCallback; +import javax.security.sasl.RealmCallback; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslServer; + +import org.apache.hadoop.hbase.HBaseInterfaceAudience; +import org.apache.hadoop.hbase.security.AccessDeniedException; +import org.apache.hadoop.hbase.security.HBaseSaslRpcServer; +import org.apache.hadoop.hbase.security.SaslUtil; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.token.SecretManager; +import org.apache.hadoop.security.token.SecretManager.InvalidToken; +import org.apache.yetus.audience.InterfaceAudience; +import org.apache.yetus.audience.InterfaceStability; +import org.apache.hadoop.security.token.TokenIdentifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) +@InterfaceStability.Evolving +public class DigestSaslServerAuthenticationProvider extends DigestSaslClientAuthenticationProvider + implements SaslServerAuthenticationProvider { + private static final Logger LOG = LoggerFactory.getLogger(DigestSaslServerAuthenticationProvider.class); + + @Override + public SaslServer createServer(SecretManager secretManager, + Map saslProps) throws IOException { + if (secretManager == null) { + throw new AccessDeniedException("Server is not configured to do DIGEST authentication."); + } + return Sasl.createSaslServer(getSaslAuthMethod().getSaslMechanism(), null, + SaslUtil.SASL_DEFAULT_REALM, saslProps, new SaslDigestCallbackHandler(secretManager)); + } + + /** CallbackHandler for SASL DIGEST-MD5 mechanism */ + private class SaslDigestCallbackHandler implements CallbackHandler { + private SecretManager secretManager; + + public SaslDigestCallbackHandler(SecretManager secretManager) { + this.secretManager = secretManager; + } + + private char[] getPassword(TokenIdentifier tokenid) throws InvalidToken { + return SaslUtil.encodePassword(secretManager.retrievePassword(tokenid)); + } + + /** {@inheritDoc} */ + @Override + public void handle(Callback[] callbacks) throws InvalidToken, UnsupportedCallbackException { + NameCallback nc = null; + PasswordCallback pc = null; + AuthorizeCallback ac = null; + for (Callback callback : callbacks) { + if (callback instanceof AuthorizeCallback) { + ac = (AuthorizeCallback) callback; + } else if (callback instanceof NameCallback) { + nc = (NameCallback) callback; + } else if (callback instanceof PasswordCallback) { + pc = (PasswordCallback) callback; + } else if (callback instanceof RealmCallback) { + continue; // realm is ignored + } else { + throw new UnsupportedCallbackException(callback, "Unrecognized SASL DIGEST-MD5 Callback"); + } + } + if (pc != null) { + TokenIdentifier tokenIdentifier = HBaseSaslRpcServer.getIdentifier(nc.getDefaultName(), secretManager); + char[] password = getPassword(tokenIdentifier); + if (LOG.isTraceEnabled()) { + LOG.trace("SASL server DIGEST-MD5 callback: setting password " + "for client: " + + tokenIdentifier.getUser()); + } + pc.setPassword(password); + } + if (ac != null) { + String authid = ac.getAuthenticationID(); + String authzid = ac.getAuthorizationID(); + if (authid.equals(authzid)) { + ac.setAuthorized(true); + } else { + ac.setAuthorized(false); + } + if (ac.isAuthorized()) { + if (LOG.isTraceEnabled()) { + String username = HBaseSaslRpcServer.getIdentifier(authzid, secretManager).getUser().getUserName(); + LOG.trace( + "SASL server DIGEST-MD5 callback: setting " + "canonicalized client ID: " + username); + } + ac.setAuthorizedID(authzid); + } + } + } + } + + @Override + public boolean supportsProtocolAuthentication() { + return false; + } + + @Override + public UserGroupInformation getAuthorizedUgi(String authzId, + SecretManager secretManager) throws IOException { + UserGroupInformation authorizedUgi; + TokenIdentifier tokenId = HBaseSaslRpcServer.getIdentifier(authzId, secretManager); + authorizedUgi = tokenId.getUser(); + if (authorizedUgi == null) { + throw new AccessDeniedException( + "Can't retrieve username from tokenIdentifier."); + } + authorizedUgi.addTokenIdentifier(tokenId); + authorizedUgi.setAuthenticationMethod(getSaslAuthMethod().getAuthMethod()); + return authorizedUgi; + } +} diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java new file mode 100644 index 000000000000..ecff86fb9ab3 --- /dev/null +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java @@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.security.provider; + +import java.io.IOException; +import java.security.PrivilegedExceptionAction; +import java.util.Map; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.sasl.AuthorizeCallback; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.hadoop.hbase.HBaseInterfaceAudience; +import org.apache.hadoop.hbase.security.AccessDeniedException; +import org.apache.hadoop.hbase.security.SaslUtil; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.token.SecretManager; +import org.apache.hadoop.security.token.TokenIdentifier; +import org.apache.yetus.audience.InterfaceAudience; +import org.apache.yetus.audience.InterfaceStability; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) +@InterfaceStability.Evolving +public class GssSaslServerAuthenticationProvider extends GssSaslClientAuthenticationProvider implements SaslServerAuthenticationProvider { + private static final Logger LOG = LoggerFactory.getLogger(GssSaslServerAuthenticationProvider.class); + + @Override + public SaslServer createServer(SecretManager secretManager, + Map saslProps) throws IOException { + UserGroupInformation current = UserGroupInformation.getCurrentUser(); + String fullName = current.getUserName(); + if (LOG.isDebugEnabled()) { + LOG.debug("Server's Kerberos principal name is " + fullName); + } + String[] names = SaslUtil.splitKerberosName(fullName); + if (names.length != 3) { + throw new AccessDeniedException( + "Kerberos principal does NOT contain an instance (hostname): " + fullName); + } + try { + return current.doAs(new PrivilegedExceptionAction() { + @Override + public SaslServer run() throws SaslException { + return Sasl.createSaslServer(getSaslAuthMethod().getSaslMechanism(), names[0], + names[1], saslProps, new SaslGssCallbackHandler()); + } + }); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Failed to construct GSS SASL server"); + } + } + + /** CallbackHandler for SASL GSSAPI Kerberos mechanism */ + private static class SaslGssCallbackHandler implements CallbackHandler { + + /** {@inheritDoc} */ + @Override + public void handle(Callback[] callbacks) throws UnsupportedCallbackException { + AuthorizeCallback ac = null; + for (Callback callback : callbacks) { + if (callback instanceof AuthorizeCallback) { + ac = (AuthorizeCallback) callback; + } else { + throw new UnsupportedCallbackException(callback, "Unrecognized SASL GSSAPI Callback"); + } + } + if (ac != null) { + String authid = ac.getAuthenticationID(); + String authzid = ac.getAuthorizationID(); + if (authid.equals(authzid)) { + ac.setAuthorized(true); + } else { + ac.setAuthorized(false); + } + if (ac.isAuthorized()) { + if (LOG.isDebugEnabled()) { + LOG.debug( + "SASL server GSSAPI callback: setting " + "canonicalized client ID: " + authzid); + } + ac.setAuthorizedID(authzid); + } + } + } + } + + @Override + public boolean supportsProtocolAuthentication() { + return true; + } + + @Override + public UserGroupInformation getAuthorizedUgi(String authzId, SecretManager secretManager) + throws IOException { + UserGroupInformation ugi = UserGroupInformation.createRemoteUser(authzId); + ugi.setAuthenticationMethod(getSaslAuthMethod().getAuthMethod()); + return ugi; + } +} diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProvider.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProvider.java new file mode 100644 index 000000000000..13561f635868 --- /dev/null +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProvider.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.security.provider; + +import java.io.IOException; +import java.util.Map; + +import javax.security.sasl.SaslServer; + +import org.apache.hadoop.hbase.HBaseInterfaceAudience; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.token.SecretManager; +import org.apache.hadoop.security.token.TokenIdentifier; +import org.apache.yetus.audience.InterfaceAudience; +import org.apache.yetus.audience.InterfaceStability; + +/** + * Encapsulates the server-side logic to authenticate a client over SASL. Tied one-to-one to + * a single client authentication implementation. + */ +@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) +@InterfaceStability.Evolving +public interface SaslServerAuthenticationProvider extends SaslClientAuthenticationProvider { + + /** + * Creates the SaslServer to accept incoming SASL authentication requests. + */ + SaslServer createServer(SecretManager secretManager, + Map saslProps) throws IOException; + + boolean supportsProtocolAuthentication(); + + UserGroupInformation getAuthorizedUgi(String authzId, + SecretManager secretManager) throws IOException; +} diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProviders.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProviders.java new file mode 100644 index 000000000000..e40e2632c58d --- /dev/null +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProviders.java @@ -0,0 +1,162 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.security.provider; + +import java.util.HashMap; +import java.util.ServiceLoader; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +import org.apache.hadoop.conf.Configuration; +import org.apache.yetus.audience.InterfaceAudience; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@InterfaceAudience.Private +public class SaslServerAuthenticationProviders { + private static final Logger LOG = LoggerFactory.getLogger(SaslClientAuthenticationProviders.class); + + public static final String EXTRA_PROVIDERS_KEY = "hbase.server.sasl.provider.extras"; + private static final AtomicReference holder = new AtomicReference<>(); + + private final Configuration conf; + private final HashMap providers; + + private SaslServerAuthenticationProviders(Configuration conf, + HashMap providers) { + this.conf = conf; + this.providers = providers; + } + + /** + * Returns the number of registered providers. + */ + public int getNumRegisteredProviders() { + return providers.size(); + } + + /** + * Returns a singleton instance of {@link SaslServerAuthenticationProviders}. + */ + public static SaslServerAuthenticationProviders getInstance(Configuration conf) { + SaslServerAuthenticationProviders providers = holder.get(); + if (null == providers) { + synchronized (holder) { + // Someone else beat us here + providers = holder.get(); + if (null != providers) { + return providers; + } + + providers = createProviders(conf); + holder.set(providers); + } + } + return providers; + } + + /** + * Removes the cached singleton instance of {@link SaslServerAuthenticationProviders}. + */ + public static void reset() { + synchronized (holder) { + holder.set(null); + } + } + + /** + * Adds the given provider into the map of providers if a mapping for the auth code does not + * already exist in the map. + */ + static void addProviderIfNotExists(SaslServerAuthenticationProvider provider, + HashMap providers) { + final byte newProviderAuthCode = provider.getSaslAuthMethod().getCode(); + final SaslServerAuthenticationProvider alreadyRegisteredProvider = providers.get( + newProviderAuthCode); + if (alreadyRegisteredProvider != null) { + throw new RuntimeException("Trying to load SaslServerAuthenticationProvider " + + provider.getClass() + ", but "+ alreadyRegisteredProvider.getClass() + + " is already registered with the same auth code"); + } + providers.put(newProviderAuthCode, provider); + } + + /** + * Adds any providers defined in the configuration. + */ + static void addExtraProviders(Configuration conf, + HashMap providers) { + for (String implName : conf.getStringCollection(EXTRA_PROVIDERS_KEY)) { + Class clz; + try { + clz = Class.forName(implName); + } catch (ClassNotFoundException e) { + LOG.warn("Failed to find SaslServerAuthenticationProvider class {}", implName, e); + continue; + } + + if (!SaslServerAuthenticationProvider.class.isAssignableFrom(clz)) { + LOG.warn("Server authentication class {} is not an instance of " + + "SaslServerAuthenticationProvider", clz); + continue; + } + + try { + SaslServerAuthenticationProvider provider = + (SaslServerAuthenticationProvider) clz.newInstance(); + addProviderIfNotExists(provider, providers); + } catch (InstantiationException | IllegalAccessException e) { + LOG.warn("Failed to instantiate {}", clz, e); + } + } + } + + /** + * Loads server authentication providers from the classpath and configuration, and then creates + * the SaslServerAuthenticationProviders instance. + */ + static SaslServerAuthenticationProviders createProviders(Configuration conf) { + ServiceLoader loader = + ServiceLoader.load(SaslServerAuthenticationProvider.class); + HashMap providers = new HashMap<>(); + for (SaslServerAuthenticationProvider provider : loader) { + addProviderIfNotExists(provider, providers); + } + + addExtraProviders(conf, providers); + + if (LOG.isTraceEnabled()) { + String loadedProviders = providers.values().stream() + .map((provider) -> provider.getClass().getName()) + .collect(Collectors.joining(", ")); + if (loadedProviders.isEmpty()) { + loadedProviders = "None!"; + } + LOG.trace("Found SaslServerAuthenticationProviders {}", loadedProviders); + } + return new SaslServerAuthenticationProviders(conf, providers); + } + + /** + * Selects the appropriate SaslServerAuthenticationProvider from those available. If there is no + * matching provider for the given {@code authByte}, this method will return null. + */ + public SaslServerAuthenticationProvider selectProvider(byte authByte) { + return providers.get(Byte.valueOf(authByte)); + } +} diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java new file mode 100644 index 000000000000..3e6ab323f843 --- /dev/null +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.security.provider; + +import java.io.IOException; +import java.util.Map; + +import javax.security.sasl.SaslServer; + +import org.apache.hadoop.hbase.HBaseInterfaceAudience; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.token.SecretManager; +import org.apache.hadoop.security.token.TokenIdentifier; +import org.apache.yetus.audience.InterfaceAudience; +import org.apache.yetus.audience.InterfaceStability; + +@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) +@InterfaceStability.Evolving +public class SimpleSaslServerAuthenticationProvider extends SimpleSaslClientAuthenticationProvider + implements SaslServerAuthenticationProvider { + + @Override + public SaslServer createServer(SecretManager secretManager, + Map saslProps) throws IOException { + return null; + } + + @Override + public boolean supportsProtocolAuthentication() { + return true; + } + + @Override + public UserGroupInformation getAuthorizedUgi(String authzId, SecretManager secretManager) + throws IOException { + UserGroupInformation ugi = UserGroupInformation.createRemoteUser(authzId); + ugi.setAuthenticationMethod(getSaslAuthMethod().getAuthMethod()); + return ugi; + } +} diff --git a/hbase-server/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslServerAuthenticationProvider b/hbase-server/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslServerAuthenticationProvider new file mode 100644 index 000000000000..66099c9f6d86 --- /dev/null +++ b/hbase-server/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslServerAuthenticationProvider @@ -0,0 +1,3 @@ +org.apache.hadoop.hbase.security.provider.DigestSaslServerAuthenticationProvider +org.apache.hadoop.hbase.security.provider.GssSaslServerAuthenticationProvider +org.apache.hadoop.hbase.security.provider.SimpleSaslServerAuthenticationProvider \ No newline at end of file diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestSecureIPC.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestSecureIPC.java index a53b489565cf..6a5b5072a2c8 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestSecureIPC.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestSecureIPC.java @@ -107,9 +107,11 @@ public class TestSecureIPC { public static Collection parameters() { List params = new ArrayList<>(); List rpcClientImpls = Arrays.asList( - BlockingRpcClient.class.getName(), NettyRpcClient.class.getName()); + BlockingRpcClient.class.getName(), + NettyRpcClient.class.getName()); List rpcServerImpls = Arrays.asList( - SimpleRpcServer.class.getName(), NettyRpcServer.class.getName()); + SimpleRpcServer.class.getName(), + NettyRpcServer.class.getName()); for (String rpcClientImpl : rpcClientImpls) { for (String rpcServerImpl : rpcServerImpls) { params.add(new Object[] { rpcClientImpl, rpcServerImpl }); diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java new file mode 100644 index 000000000000..a8c1c21c4e88 --- /dev/null +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java @@ -0,0 +1,534 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.security.provider; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.ByteArrayInputStream; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutput; +import java.io.File; +import java.io.IOException; +import java.security.PrivilegedExceptionAction; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +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.sasl.AuthorizeCallback; +import javax.security.sasl.RealmCallback; +import javax.security.sasl.RealmChoiceCallback; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslClient; +import javax.security.sasl.SaslServer; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.hbase.Cell; +import org.apache.hadoop.hbase.CellUtil; +import org.apache.hadoop.hbase.HBaseClassTestRule; +import org.apache.hadoop.hbase.HBaseTestingUtility; +import org.apache.hadoop.hbase.HConstants; +import org.apache.hadoop.hbase.LocalHBaseCluster; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.client.Admin; +import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; +import org.apache.hadoop.hbase.client.Connection; +import org.apache.hadoop.hbase.client.ConnectionFactory; +import org.apache.hadoop.hbase.client.Get; +import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.client.RetriesExhaustedException; +import org.apache.hadoop.hbase.client.Table; +import org.apache.hadoop.hbase.client.TableDescriptorBuilder; +import org.apache.hadoop.hbase.coprocessor.CoprocessorHost; +import org.apache.hadoop.hbase.ipc.BlockingRpcClient; +import org.apache.hadoop.hbase.ipc.RpcClientFactory; +import org.apache.hadoop.hbase.ipc.RpcServerFactory; +import org.apache.hadoop.hbase.ipc.SimpleRpcServer; +import org.apache.hadoop.hbase.security.AccessDeniedException; +import org.apache.hadoop.hbase.security.HBaseKerberosUtils; +import org.apache.hadoop.hbase.security.SaslUtil; +import org.apache.hadoop.hbase.security.token.SecureTestCluster; +import org.apache.hadoop.hbase.security.token.TokenProvider; +import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; +import org.apache.hadoop.hbase.testclassification.MediumTests; +import org.apache.hadoop.hbase.testclassification.SecurityTests; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.hbase.util.FSUtils; +import org.apache.hadoop.hbase.util.Pair; +import org.apache.hadoop.io.Text; +import org.apache.hadoop.io.WritableUtils; +import org.apache.hadoop.minikdc.MiniKdc; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; +import org.apache.hadoop.security.token.SecretManager; +import org.apache.hadoop.security.token.SecretManager.InvalidToken; +import org.apache.hadoop.security.token.Token; +import org.apache.hadoop.security.token.TokenIdentifier; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.rules.TestName; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Tests the pluggable authentication framework with SASL using a contrived authentication system. + * + * This tests holds a "user database" in memory as a hashmap. Clients provide their password + * in the client Hadoop configuration. The servers validate this password via the "user database". + */ +@Category({MediumTests.class, SecurityTests.class}) +public class TestCustomSaslAuthenticationProvider { + private static final Logger LOG = LoggerFactory.getLogger(TestCustomSaslAuthenticationProvider.class); + + @ClassRule + public static final HBaseClassTestRule CLASS_RULE = + HBaseClassTestRule.forClass(TestCustomSaslAuthenticationProvider.class); + + private static final Map USER_DATABASE = createUserDatabase(); + + private static final String USER1_PASSWORD = "foobarbaz"; + private static final String USER2_PASSWORD = "bazbarfoo"; + + private static Map createUserDatabase() { + Map db = new ConcurrentHashMap<>(); + db.put("user1", USER1_PASSWORD); + db.put("user2", USER2_PASSWORD); + return db; + } + + public static String getPassword(String user) { + String password = USER_DATABASE.get(user); + if (password == null) { + throw new IllegalStateException("Cannot request password for a user that doesn't exist"); + } + return password; + } + + /** + * A custom tokenidentifier for our custom auth'n method. Unique from the TokenIdentifier + * used for delegation tokens. + */ + public static class PasswordAuthTokenIdentifier extends TokenIdentifier { + public static final Text PASSWORD_AUTH_TOKEN = new Text("HBASE_PASSWORD_TEST_TOKEN"); + private String username; + + public PasswordAuthTokenIdentifier() {} + + public PasswordAuthTokenIdentifier(String username) { + this.username = username; + } + + @Override + public void readFields(DataInput in) throws IOException { + this.username = WritableUtils.readString(in); + } + + @Override + public void write(DataOutput out) throws IOException { + WritableUtils.writeString(out, username); + } + + @Override + public Text getKind() { + return PASSWORD_AUTH_TOKEN; + } + + @Override + public UserGroupInformation getUser() { + if (username == null || "".equals(username)) { + return null; + } + return UserGroupInformation.createRemoteUser(username); + } + } + + public static Token createPasswordToken( + String username, String password, String clusterId) { + PasswordAuthTokenIdentifier id = new PasswordAuthTokenIdentifier(username); + Token token = new Token<>(id.getBytes(), Bytes.toBytes(password), + id.getKind(), new Text(clusterId)); + return token; + } + + /** + * Client provider that finds custom Token in the user's UGI and authenticates with the server + * via DIGEST-MD5 using that password. + */ + public static class InMemoryClientProvider extends AbstractSaslClientAuthenticationProvider { + public static final String MECHANISM = "DIGEST-MD5"; + public static final SaslAuthMethod SASL_AUTH_METHOD = new SaslAuthMethod( + "IN_MEMORY", (byte)42, MECHANISM, AuthenticationMethod.TOKEN); + + @Override + public SaslClient createClient(Configuration conf, String serverPrincipal, + Token token, boolean fallbackAllowed, + Map saslProps) throws IOException { + return Sasl.createSaslClient(new String[] { MECHANISM }, null, null, + SaslUtil.SASL_DEFAULT_REALM, saslProps, new InMemoryClientProviderCallbackHandler(token)); + } + + public Optional> findToken(UserGroupInformation ugi) { + List> tokens = ugi.getTokens().stream() + .filter((token) -> token.getKind().equals(PasswordAuthTokenIdentifier.PASSWORD_AUTH_TOKEN)) + .collect(Collectors.toList()); + if (tokens.isEmpty()) { + return Optional.empty(); + } + if (tokens.size() > 1) { + throw new IllegalStateException("Cannot handle more than one PasswordAuthToken"); + } + return Optional.of(tokens.get(0)); + } + + @Override + public SaslAuthMethod getSaslAuthMethod() { + return SASL_AUTH_METHOD; + } + + /** + * Sasl CallbackHandler which extracts information from our custom token and places + * it into the Sasl objects. + */ + public class InMemoryClientProviderCallbackHandler implements CallbackHandler { + private final Token token; + public InMemoryClientProviderCallbackHandler(Token token) { + this.token = token; + } + + @Override + public void handle(Callback[] callbacks) throws UnsupportedCallbackException { + NameCallback nc = null; + PasswordCallback pc = null; + RealmCallback rc = null; + for (Callback callback : callbacks) { + if (callback instanceof RealmChoiceCallback) { + continue; + } else if (callback instanceof NameCallback) { + nc = (NameCallback) callback; + } else if (callback instanceof PasswordCallback) { + pc = (PasswordCallback) callback; + } else if (callback instanceof RealmCallback) { + rc = (RealmCallback) callback; + } else { + throw new UnsupportedCallbackException(callback, "Unrecognized SASL client callback"); + } + } + if (nc != null) { + nc.setName(SaslUtil.encodeIdentifier(token.getIdentifier())); + } + if (pc != null) { + pc.setPassword(SaslUtil.encodePassword(token.getPassword())); + } + if (rc != null) { + rc.setText(rc.getDefaultText()); + } + } + } + + @Override + public UserInformation getUserInfo(UserGroupInformation user) { + return null; + } + + @Override + public boolean isKerberos() { + return false; + } + } + + /** + * Server provider which validates credentials from an in-memory database. + */ + public static class InMemoryServerProvider extends InMemoryClientProvider implements SaslServerAuthenticationProvider { + + @Override + public SaslServer createServer(SecretManager secretManager, + Map saslProps) throws IOException { + return Sasl.createSaslServer(getSaslAuthMethod().getSaslMechanism(), null, + SaslUtil.SASL_DEFAULT_REALM, saslProps, + new InMemoryServerProviderCallbackHandler()); + } + + /** + * Pulls the correct password for the user who started the SASL handshake so that SASL + * can validate that the user provided the right password. + */ + private class InMemoryServerProviderCallbackHandler implements CallbackHandler { + + @Override + public void handle(Callback[] callbacks) throws InvalidToken, UnsupportedCallbackException { + NameCallback nc = null; + PasswordCallback pc = null; + AuthorizeCallback ac = null; + for (Callback callback : callbacks) { + if (callback instanceof AuthorizeCallback) { + ac = (AuthorizeCallback) callback; + } else if (callback instanceof NameCallback) { + nc = (NameCallback) callback; + } else if (callback instanceof PasswordCallback) { + pc = (PasswordCallback) callback; + } else if (callback instanceof RealmCallback) { + continue; // realm is ignored + } else { + throw new UnsupportedCallbackException(callback, "Unrecognized SASL DIGEST-MD5 Callback"); + } + } + if (nc != null && pc != null) { + byte[] encodedId = SaslUtil.decodeIdentifier(nc.getDefaultName()); + PasswordAuthTokenIdentifier id = new PasswordAuthTokenIdentifier(); + try { + id.readFields(new DataInputStream(new ByteArrayInputStream(encodedId))); + } catch (IOException e) { + throw (InvalidToken) new InvalidToken("Can't de-serialize tokenIdentifier").initCause(e); + } + char[] actualPassword = SaslUtil.encodePassword( + Bytes.toBytes(getPassword(id.getUser().getUserName()))); + pc.setPassword(actualPassword); + } + if (ac != null) { + String authid = ac.getAuthenticationID(); + String authzid = ac.getAuthorizationID(); + if (authid.equals(authzid)) { + ac.setAuthorized(true); + } else { + ac.setAuthorized(false); + } + if (ac.isAuthorized()) { + ac.setAuthorizedID(authzid); + } + } + } + } + + @Override + public boolean supportsProtocolAuthentication() { + return false; + } + + @Override + public UserGroupInformation getAuthorizedUgi(String authzId, + SecretManager secretManager) throws IOException { + UserGroupInformation authorizedUgi; + byte[] encodedId = SaslUtil.decodeIdentifier(authzId); + PasswordAuthTokenIdentifier tokenId = new PasswordAuthTokenIdentifier(); + try { + tokenId.readFields(new DataInputStream(new ByteArrayInputStream(encodedId))); + } catch (IOException e) { + throw new IOException("Can't de-serialize PasswordAuthTokenIdentifier", e); + } + authorizedUgi = tokenId.getUser(); + if (authorizedUgi == null) { + throw new AccessDeniedException( + "Can't retrieve username from tokenIdentifier."); + } + authorizedUgi.addTokenIdentifier(tokenId); + authorizedUgi.setAuthenticationMethod(getSaslAuthMethod().getAuthMethod()); + return authorizedUgi; + } + } + + /** + * Custom provider which can select our custom provider, amongst other tokens which + * may be available. + */ + public static class InMemoryProviderSelector extends DefaultProviderSelector { + private InMemoryClientProvider inMemoryProvider; + + @Override + public void configure(Configuration conf, Map providers) { + super.configure(conf, providers); + Optional o = providers.values().stream() + .filter((p) -> p instanceof InMemoryClientProvider) + .findAny(); + if (!o.isPresent()) { + throw new RuntimeException( + "InMemoryClientProvider not found in available providers: " + providers); + } + inMemoryProvider = (InMemoryClientProvider) o.get(); + } + + @Override + public Pair> selectProvider( + Text clusterId, UserGroupInformation ugi) { + Pair> superPair = + super.selectProvider(clusterId, ugi); + + Optional> optional = inMemoryProvider.findToken(ugi); + if (optional.isPresent()) { + LOG.info("Using InMemoryClientProvider"); + return new Pair<>(inMemoryProvider, optional.get()); + } + + LOG.info("InMemoryClientProvider not usable, falling back to {}", superPair); + return superPair; + } + } + + static LocalHBaseCluster createCluster(HBaseTestingUtility util, File keytabFile, + MiniKdc kdc) throws Exception { + String servicePrincipal = "hbase/localhost"; + String spnegoPrincipal = "HTTP/localhost"; + kdc.createPrincipal(keytabFile, servicePrincipal); + util.startMiniZKCluster(); + + HBaseKerberosUtils.setSecuredConfiguration(util.getConfiguration(), + servicePrincipal + "@" + kdc.getRealm(), spnegoPrincipal + "@" + kdc.getRealm()); + HBaseKerberosUtils.setSSLConfiguration(util, SecureTestCluster.class); + + util.getConfiguration().setStrings(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY, + TokenProvider.class.getName()); + util.startMiniDFSCluster(1); + Path rootdir = util.getDataTestDirOnTestFS("TestGenerateDelegationToken"); + FSUtils.setRootDir(util.getConfiguration(), rootdir); + LocalHBaseCluster cluster = new LocalHBaseCluster(util.getConfiguration(), 1); + return cluster; + } + + private static final HBaseTestingUtility UTIL = new HBaseTestingUtility(); + private static final Configuration CONF = UTIL.getConfiguration(); + private static LocalHBaseCluster CLUSTER; + private static File KEYTAB_FILE; + + @BeforeClass + public static void setupCluster() throws Exception { + KEYTAB_FILE = new File( + UTIL.getDataTestDir("keytab").toUri().getPath()); + final MiniKdc kdc = UTIL.setupMiniKdc(KEYTAB_FILE); + + // Switch back to NIO for now. + CONF.set(RpcClientFactory.CUSTOM_RPC_CLIENT_IMPL_CONF_KEY, BlockingRpcClient.class.getName()); + CONF.set(RpcServerFactory.CUSTOM_RPC_SERVER_IMPL_CONF_KEY, SimpleRpcServer.class.getName()); + + // Adds our test impls instead of creating service loader entries which + // might inadvertently get them loaded on a real cluster. + CONF.setStrings(SaslClientAuthenticationProviders.EXTRA_PROVIDERS_KEY, + InMemoryClientProvider.class.getName()); + CONF.setStrings(SaslServerAuthenticationProviders.EXTRA_PROVIDERS_KEY, + InMemoryServerProvider.class.getName()); + CONF.set(SaslClientAuthenticationProviders.SELECTOR_KEY, InMemoryProviderSelector.class.getName()); + + CLUSTER = createCluster(UTIL, KEYTAB_FILE, kdc); + CLUSTER.startup(); + } + + @AfterClass + public static void teardownCluster() throws Exception { + if (CLUSTER != null) { + CLUSTER.shutdown(); + CLUSTER = null; + } + UTIL.shutdownMiniZKCluster(); + } + + @Rule + public TestName name = new TestName(); + TableName tableName; + String clusterId; + + @Before + public void createTable() throws Exception { + tableName = TableName.valueOf(name.getMethodName()); + + // Create a table and write a record as the service user (hbase) + UserGroupInformation serviceUgi = UserGroupInformation.loginUserFromKeytabAndReturnUGI( + "hbase/localhost", KEYTAB_FILE.getAbsolutePath()); + clusterId = serviceUgi.doAs(new PrivilegedExceptionAction() { + @Override public String run() throws Exception { + try (Connection conn = ConnectionFactory.createConnection(CONF); + Admin admin = conn.getAdmin();) { + admin.createTable(TableDescriptorBuilder + .newBuilder(tableName) + .setColumnFamily(ColumnFamilyDescriptorBuilder.of("f1")) + .build()); + + UTIL.waitTableAvailable(tableName); + + try (Table t = conn.getTable(tableName)) { + Put p = new Put(Bytes.toBytes("r1")); + p.addColumn(Bytes.toBytes("f1"), Bytes.toBytes("q1"), Bytes.toBytes("1")); + t.put(p); + } + + return admin.getClusterMetrics().getClusterId(); + } + } + }); + + assertNotNull(clusterId); + } + + @Test + public void testPositiveAuthentication() throws Exception { + // Validate that we can read that record back out as the user with our custom auth'n + final Configuration clientConf = new Configuration(CONF); + UserGroupInformation user1 = UserGroupInformation.createUserForTesting( + "user1", new String[0]); + user1.addToken(createPasswordToken("user1", USER1_PASSWORD, clusterId)); + user1.doAs(new PrivilegedExceptionAction() { + @Override public Void run() throws Exception { + try (Connection conn = ConnectionFactory.createConnection(clientConf); + Table t = conn.getTable(tableName)) { + Result r = t.get(new Get(Bytes.toBytes("r1"))); + assertNotNull(r); + assertFalse("Should have read a non-empty Result", r.isEmpty()); + final Cell cell = r.getColumnLatestCell(Bytes.toBytes("f1"), Bytes.toBytes("q1")); + assertTrue("Unexpected value", CellUtil.matchingValue(cell, Bytes.toBytes("1"))); + + return null; + } + } + }); + } + + @Test(expected = RetriesExhaustedException.class) + public void testNegativeAuthentication() throws Exception { + // Validate that we can read that record back out as the user with our custom auth'n + final Configuration clientConf = new Configuration(CONF); + clientConf.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 3); + UserGroupInformation user1 = UserGroupInformation.createUserForTesting( + "user1", new String[0]); + user1.addToken(createPasswordToken("user1", "definitely not the password", clusterId)); + user1.doAs(new PrivilegedExceptionAction() { + @Override public Void run() throws Exception { + try (Connection conn = ConnectionFactory.createConnection(clientConf); + Table t = conn.getTable(tableName)) { + t.get(new Get(Bytes.toBytes("r1"))); + fail("Should not successfully authenticate with HBase"); + return null; + } + } + }); + } +} diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslServerAuthenticationProviders.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslServerAuthenticationProviders.java new file mode 100644 index 000000000000..77d0d416f1e5 --- /dev/null +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslServerAuthenticationProviders.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.security.provider; + +import static org.junit.Assert.*; + +import java.util.HashMap; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HBaseConfiguration; +import org.junit.Test; + +public class TestSaslServerAuthenticationProviders { + + @Test + public void testCannotAddTheSameProviderTwice() { + HashMap registeredProviders = new HashMap<>(); + SimpleSaslServerAuthenticationProvider p1 = new SimpleSaslServerAuthenticationProvider(); + SimpleSaslServerAuthenticationProvider p2 = new SimpleSaslServerAuthenticationProvider(); + + SaslServerAuthenticationProviders.addProviderIfNotExists(p1, registeredProviders); + assertEquals(1, registeredProviders.size()); + + try { + SaslServerAuthenticationProviders.addProviderIfNotExists(p2, registeredProviders); + } catch (RuntimeException e) {} + + assertSame("Expected the original provider to be present", p1, + registeredProviders.entrySet().iterator().next().getValue()); + } + + @Test + public void testInstanceIsCached() { + Configuration conf = HBaseConfiguration.create(); + SaslServerAuthenticationProviders providers1 = SaslServerAuthenticationProviders.getInstance(conf); + SaslServerAuthenticationProviders providers2 = SaslServerAuthenticationProviders.getInstance(conf); + assertSame(providers1, providers2); + + SaslServerAuthenticationProviders.reset(); + + SaslServerAuthenticationProviders providers3 = SaslServerAuthenticationProviders.getInstance(conf); + assertNotSame(providers1, providers3); + assertEquals(providers1.getNumRegisteredProviders(), providers3.getNumRegisteredProviders()); + } +} diff --git a/hbase-server/src/test/resources/log4j.properties b/hbase-server/src/test/resources/log4j.properties index fcb66007dc35..785371d9587d 100644 --- a/hbase-server/src/test/resources/log4j.properties +++ b/hbase-server/src/test/resources/log4j.properties @@ -68,3 +68,4 @@ log4j.logger.org.apache.hadoop.metrics2.impl.MetricsSystemImpl=WARN log4j.logger.org.apache.hadoop.metrics2.util.MBeans=WARN # Enable this to get detailed connection error/retry logging. # log4j.logger.org.apache.hadoop.hbase.client.ConnectionImplementation=TRACE +log4j.logger.org.apache.directory=WARN From 00933f77ef1a34e083133234e294d0a31bd7fab4 Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Tue, 3 Dec 2019 15:46:31 -0500 Subject: [PATCH 02/41] Fix some logger calls --- .../provider/GssSaslServerAuthenticationProvider.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java index ecff86fb9ab3..045c5d3de21d 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java @@ -50,9 +50,7 @@ public SaslServer createServer(SecretManager secretManager, Map saslProps) throws IOException { UserGroupInformation current = UserGroupInformation.getCurrentUser(); String fullName = current.getUserName(); - if (LOG.isDebugEnabled()) { - LOG.debug("Server's Kerberos principal name is " + fullName); - } + LOG.debug("Server's Kerberos principal name is {}", fullName); String[] names = SaslUtil.splitKerberosName(fullName); if (names.length != 3) { throw new AccessDeniedException( @@ -95,10 +93,7 @@ public void handle(Callback[] callbacks) throws UnsupportedCallbackException { ac.setAuthorized(false); } if (ac.isAuthorized()) { - if (LOG.isDebugEnabled()) { - LOG.debug( - "SASL server GSSAPI callback: setting " + "canonicalized client ID: " + authzid); - } + LOG.debug("SASL server GSSAPI callback: setting canonicalized client ID: {}", authzid); ac.setAuthorizedID(authzid); } } From d2bade24b5025fb3b8e606ed27b30bb6ffd0fcff Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Tue, 3 Dec 2019 15:46:39 -0500 Subject: [PATCH 03/41] Expand/edit the write-up --- PluggableRpcAuthentication.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/PluggableRpcAuthentication.md b/PluggableRpcAuthentication.md index 16b7324ca579..fd49e0e27836 100644 --- a/PluggableRpcAuthentication.md +++ b/PluggableRpcAuthentication.md @@ -30,6 +30,14 @@ decoupling an RPC client-server model from the mechanism used to authenticate th requests (e.g. the RPC code is identical whether username-password, Kerberos, or any other method is used to authenticate the request). +RFC's define what [SASL mechanisms exist](https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xml), +but what RFC's define are a superset of the mechanisms that are +[implemented in Java](https://docs.oracle.com/javase/8/docs/technotes/guides/security/sasl/sasl-refguide.html#SUN). +This document limits discussion to SASL mechanisms in the abstract, focusing on those which are well-defined and +implemented in Java today by the JDK itself. However, it is completely possible that a developer can implement +and register their own SASL mechanism. Writing a custom mechanism is outside of the scope of this document, but +not outside of the realm of possibility. + The `SIMPLE` implementation does not use SASL, but instead has its own RPC logic built into the HBase RPC protocol. `KERBEROS` and `TOKEN` both use SASL to authenticate, relying on the `Token` interface that is intertwined with the Hadoop `UserGroupInformation` @@ -42,7 +50,8 @@ it is (effectively) impossible to add a new authentication implementation to HBa use of the `org.apache.hadoop.hbase.security.AuthMethod` enum makes it impossible to define a new method of authentication. Also, the RPC implementation is written to only use the methods that are expressly shipped in HBase. Adding a new authentication -method would require copying and modifying the RpcClient implementation. +method would require copying and modifying the RpcClient implementation, in addition +to modifying the RpcServer to invoke the correct authentication check. While it is possible to add a new authentication method to HBase, it cannot be done cleanly or sustainably. This is what is meant by "impossible". @@ -70,7 +79,7 @@ performance impact in setting up a new RPC. The same conditional logic to determ ## Implementation Overview -HBASE-XXXXX includes a refactoring of HBase RPC authentication where all current methods +HBASE-23347 includes a refactoring of HBase RPC authentication where all current methods are ported to a new set of interfaces, and all RPC implementations are updated to use the new interfaces. In the spirit of SASL, the expectation is that users can provide their own authentication methods at runtime, and HBase should be capable of negotiating @@ -89,7 +98,7 @@ A provider's client and server side logic are considered to be one-to-one. A `Fo should never be used to authenticate against a `Bar` server-side provider. We do expect that both clients and servers will have access to multiple providers. A server may -be capable of authenticating via methods which a client in unaware of. A client may attempt to authenticate +be capable of authenticating via methods which a client is unaware of. A client may attempt to authenticate against a server which the server does not know how to process. In both cases, the RPC should fail when a client and server do not have matching providers. The server identifies client authentication mechanisms via a `byte authCode` (which is already sent today with HBase RPCs). @@ -117,7 +126,7 @@ by the following characteristics: In addition to these attributes, a provider also must define the following attributes: -3. The SASL mechanim being used. +3. The SASL mechanism being used. 4. The Hadoop AuthenticationMethod, e.g. "TOKEN", "KERBEROS", "CERTIFICATE" 5. The Token "kind", the name used to identify a TokenIdentifier, e.g. `HBASE_AUTH_TOKEN` From a1f87ac2ba2fa74eadf315f106451ebbe0eceb14 Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Wed, 4 Dec 2019 12:07:50 -0500 Subject: [PATCH 04/41] Try to fix all of the checkstyle, javadoc, and findbugs nits. --- .../hadoop/hbase/ipc/AbstractRpcClient.java | 6 ---- .../hadoop/hbase/ipc/RpcConnection.java | 16 +++++---- .../security/AbstractHBaseSaslRpcClient.java | 19 +++++----- .../security/NettyHBaseSaslRpcClient.java | 6 ++-- .../NettyHBaseSaslRpcClientHandler.java | 5 ++- ...tractSaslClientAuthenticationProvider.java | 20 ++++++++--- .../AuthenticationProviderSelector.java | 5 +-- .../provider/DefaultProviderSelector.java | 35 ++++++++++++++----- ...igestSaslClientAuthenticationProvider.java | 9 +++-- .../security/provider/SaslAuthMethod.java | 4 +-- .../SaslClientAuthenticationProvider.java | 5 +-- .../SaslClientAuthenticationProviders.java | 13 +++---- ...impleSaslClientAuthenticationProvider.java | 6 ++-- ....provider.SaslClientAuthenticationProvider | 16 +++++++++ .../security/TestHBaseSaslRpcClient.java | 16 +++++---- ...TestSaslClientAuthenticationProviders.java | 15 ++++++-- .../hadoop/hbase/ipc/ServerRpcConnection.java | 17 ++++----- .../hbase/security/HBaseSaslRpcServer.java | 11 +++--- ...igestSaslServerAuthenticationProvider.java | 13 ++++--- .../GssSaslServerAuthenticationProvider.java | 10 +++--- .../SaslServerAuthenticationProviders.java | 12 ++++--- ....provider.SaslServerAuthenticationProvider | 16 +++++++++ .../TestCustomSaslAuthenticationProvider.java | 24 ++++++++----- ...TestSaslServerAuthenticationProviders.java | 23 +++++++++--- 24 files changed, 216 insertions(+), 106 deletions(-) diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/AbstractRpcClient.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/AbstractRpcClient.java index b769a713b2dd..d4187203377c 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/AbstractRpcClient.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/AbstractRpcClient.java @@ -41,8 +41,6 @@ import java.net.SocketAddress; import java.net.UnknownHostException; import java.util.Collection; -import java.util.HashMap; -import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; @@ -58,17 +56,13 @@ import org.apache.hadoop.hbase.client.MetricsConnection; import org.apache.hadoop.hbase.codec.Codec; import org.apache.hadoop.hbase.codec.KeyValueCodec; -import org.apache.hadoop.hbase.protobuf.generated.AuthenticationProtos.TokenIdentifier.Kind; import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.hbase.security.UserProvider; -import org.apache.hadoop.hbase.security.token.AuthenticationTokenSelector; import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; import org.apache.hadoop.hbase.util.PoolMap; import org.apache.hadoop.hbase.util.Threads; import org.apache.hadoop.io.compress.CompressionCodec; import org.apache.hadoop.ipc.RemoteException; -import org.apache.hadoop.security.token.TokenIdentifier; -import org.apache.hadoop.security.token.TokenSelector; import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos; diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java index 9e235e8a6902..b4e23b0b562d 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java @@ -27,9 +27,6 @@ import org.apache.hadoop.hbase.security.SecurityInfo; import org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider; import org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProviders; -import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; -import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.ConnectionHeader; -import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; import org.apache.hadoop.hbase.util.Pair; import org.apache.hadoop.io.Text; @@ -45,6 +42,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; +import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.ConnectionHeader; +import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; + /** * Base class for ipc connection. */ @@ -96,7 +97,8 @@ protected RpcConnection(Configuration conf, HashedWheelTimer timeoutTimer, Conne this.useSasl = isSecurityEnabled; String serverPrincipal = null; // Choose the correct Token and AuthenticationProvider for this client to use - SaslClientAuthenticationProviders providers = SaslClientAuthenticationProviders.getInstance(conf); + SaslClientAuthenticationProviders providers = + SaslClientAuthenticationProviders.getInstance(conf); if (useSasl && securityInfo != null) { Pair> pair = providers.selectProvider(new Text(clusterId), ticket); @@ -174,10 +176,10 @@ protected byte[] getConnectionHeaderPreamble() { } protected ConnectionHeader getConnectionHeader() { - ConnectionHeader.Builder builder = ConnectionHeader.newBuilder(); + final ConnectionHeader.Builder builder = ConnectionHeader.newBuilder(); builder.setServiceName(remoteId.getServiceName()); - UserInformation userInfoPB; - if ((userInfoPB = provider.getUserInfo(remoteId.ticket.getUGI())) != null) { + final UserInformation userInfoPB = provider.getUserInfo(remoteId.ticket.getUGI()); + if (userInfoPB != null) { builder.setUserInfo(userInfoPB); } if (this.codec != null) { diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/AbstractHBaseSaslRpcClient.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/AbstractHBaseSaslRpcClient.java index f96273d67784..93bfc569514c 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/AbstractHBaseSaslRpcClient.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/AbstractHBaseSaslRpcClient.java @@ -23,13 +23,13 @@ import javax.security.sasl.SaslClient; import javax.security.sasl.SaslException; -import org.apache.yetus.audience.InterfaceAudience; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; +import org.apache.yetus.audience.InterfaceAudience; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * A utility class that encapsulates SASL logic for RPC client. Copied from @@ -50,21 +50,23 @@ public abstract class AbstractHBaseSaslRpcClient { /** * Create a HBaseSaslRpcClient for an authentication method - * @param method the requested authentication method + * @param conf the configuration object + * @param provider the authentication provider * @param token token to use if needed by the authentication method * @param serverPrincipal the server principal that we are trying to set the connection up to * @param fallbackAllowed does the client allow fallback to simple authentication * @throws IOException */ - protected AbstractHBaseSaslRpcClient(Configuration conf, SaslClientAuthenticationProvider provider, - Token token, + protected AbstractHBaseSaslRpcClient(Configuration conf, + SaslClientAuthenticationProvider provider, Token token, String serverPrincipal, boolean fallbackAllowed) throws IOException { this(conf, provider, token, serverPrincipal, fallbackAllowed, "authentication"); } /** * Create a HBaseSaslRpcClient for an authentication method - * @param method the requested authentication method + * @param conf configuration object + * @param provider the authentication provider * @param token token to use if needed by the authentication method * @param serverPrincipal the server principal that we are trying to set the connection up to * @param fallbackAllowed does the client allow fallback to simple authentication @@ -79,7 +81,8 @@ protected AbstractHBaseSaslRpcClient(Configuration conf, saslClient = provider.createClient(conf, serverPrincipal, token, fallbackAllowed, saslProps); if (saslClient == null) { - throw new IOException("Authentication provider " + provider.getClass() + " returned a null SaslClient"); + throw new IOException("Authentication provider " + provider.getClass() + + " returned a null SaslClient"); } } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClient.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClient.java index a9ab6a585372..efea5c169fe7 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClient.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClient.java @@ -24,13 +24,13 @@ import javax.security.sasl.Sasl; -import org.apache.yetus.audience.InterfaceAudience; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; +import org.apache.yetus.audience.InterfaceAudience; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Implement SASL logic for netty rpc client. diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClientHandler.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClientHandler.java index 05fcb47f7d68..6ff9ff1c253f 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClientHandler.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClientHandler.java @@ -61,9 +61,8 @@ public class NettyHBaseSaslRpcClientHandler extends SimpleChannelInboundHandler< * simple. */ public NettyHBaseSaslRpcClientHandler(Promise saslPromise, UserGroupInformation ugi, - SaslClientAuthenticationProvider provider, Token token, String serverPrincipal, - boolean fallbackAllowed, Configuration conf) - throws IOException { + SaslClientAuthenticationProvider provider, Token token, + String serverPrincipal, boolean fallbackAllowed, Configuration conf) throws IOException { this.saslPromise = saslPromise; this.ugi = ugi; this.conf = conf; diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AbstractSaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AbstractSaslClientAuthenticationProvider.java index c631442a7aed..7e1ff427375d 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AbstractSaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AbstractSaslClientAuthenticationProvider.java @@ -24,12 +24,13 @@ import org.apache.yetus.audience.InterfaceStability; /** - * Base implementation of {@link SaslClientAuthenticationProvider}. All implementations should extend - * this class instead of directly implementing the interface. + * Base implementation of {@link SaslClientAuthenticationProvider}. All implementations should + * extend this class instead of directly implementing the interface. */ @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) @InterfaceStability.Evolving -public abstract class AbstractSaslClientAuthenticationProvider implements SaslClientAuthenticationProvider { +public abstract class AbstractSaslClientAuthenticationProvider implements + SaslClientAuthenticationProvider { public static final Text AUTH_TOKEN_TYPE = new Text("HBASE_AUTH_TOKEN"); @@ -41,14 +42,23 @@ public final Text getTokenKind() { } /** - * Provides a hash code to identify this AuthenticationProvider among others. These two fields must be - * unique to ensure that authentication methods are clearly separated. + * Provides a hash code to identify this AuthenticationProvider among others. These two fields + * must be unique to ensure that authentication methods are clearly separated. */ @Override public final int hashCode() { return getSaslAuthMethod().hashCode(); } + @Override + public final boolean equals(Object o) { + // SaslClientAuthProviders should be unique via their hashCode(). + if (o instanceof AbstractSaslClientAuthenticationProvider) { + return this.hashCode() == o.hashCode(); + } + return false; + } + @Override public UserGroupInformation unwrapUgi(UserGroupInformation ugi) { // Unwrap the UGI with the real user when we're using Kerberos auth diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java index be22b9e57c10..b86e54400f87 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java @@ -41,7 +41,8 @@ public interface AuthenticationProviderSelector { /** * Chooses the authentication provider which should be used given the provided client context - * from the authentication providers passed in via {@link #configure(Configuration, Set)}. + * from the authentication providers passed in via {@link #configure(Configuration, Map)}. */ - Pair> selectProvider(Text clusterId, UserGroupInformation ugi); + Pair> selectProvider( + Text clusterId, UserGroupInformation ugi); } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DefaultProviderSelector.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DefaultProviderSelector.java index 4e54fded2c1d..2e1083b9d56d 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DefaultProviderSelector.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DefaultProviderSelector.java @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.apache.hadoop.hbase.security.provider; import java.util.Map; @@ -30,22 +47,22 @@ public void configure(Configuration conf, Map> // We need to check for two things: // 1. This token is for the HBase cluster we want to talk to // 2. We have suppporting client implementation to handle the token (the "kind" of token) - if (clusterId.equals(token.getService()) && digestAuth.getTokenKind().equals(token.getKind())) { + if (clusterId.equals(token.getService()) && + digestAuth.getTokenKind().equals(token.getKind())) { return new Pair<>(digestAuth, token); } } if (ugi.hasKerberosCredentials()) { return new Pair<>(krbAuth, null); } - LOG.warn("No matching SASL authentication provider and supporting token found from providers {} to HBase cluster {}", providers, clusterId); + LOG.warn("No matching SASL authentication provider and supporting token found from providers {}" + + " to HBase cluster {}", providers, clusterId); return null; } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java index c2e183cc48ed..1de23a5c5de2 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java @@ -33,7 +33,6 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseInterfaceAudience; import org.apache.hadoop.hbase.security.SaslUtil; -import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; import org.apache.hadoop.security.token.Token; @@ -43,9 +42,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; + @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) @InterfaceStability.Evolving -public class DigestSaslClientAuthenticationProvider extends AbstractSaslClientAuthenticationProvider { +public class DigestSaslClientAuthenticationProvider extends + AbstractSaslClientAuthenticationProvider { private static final String MECHANISM = "DIGEST-MD5"; private static final SaslAuthMethod SASL_AUTH_METHOD = new SaslAuthMethod( @@ -69,7 +71,8 @@ public SaslAuthMethod getSaslAuthMethod() { } public static class DigestSaslClientCallbackHandler implements CallbackHandler { - private static final Logger LOG = LoggerFactory.getLogger(DigestSaslClientCallbackHandler.class); + private static final Logger LOG = + LoggerFactory.getLogger(DigestSaslClientCallbackHandler.class); private final String userName; private final char[] userPassword; diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslAuthMethod.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslAuthMethod.java index 0ecb34d0da20..7930564cb9f6 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslAuthMethod.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslAuthMethod.java @@ -36,7 +36,7 @@ public class SaslAuthMethod { private final byte code; private final String saslMech; private final AuthenticationMethod method; - + public SaslAuthMethod(String name, byte code, String saslMech, AuthenticationMethod method) { this.name = name; this.code = code; @@ -59,7 +59,7 @@ public byte getCode() { } /** - * Returns the SASL mechanism used by this authentication method. + * Returns the SASL mechanism used by this authentication method. */ public String getSaslMechanism() { return saslMech; diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProvider.java index 1130cac65e89..246fe0380513 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProvider.java @@ -24,7 +24,6 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseInterfaceAudience; -import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; import org.apache.hadoop.io.Text; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.Token; @@ -32,12 +31,14 @@ import org.apache.yetus.audience.InterfaceAudience; import org.apache.yetus.audience.InterfaceStability; +import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; + /** * Encapsulation of client-side logic to authenticate to HBase via some means over SASL. * Implementations should not directly implement this interface, but instead extend * {@link AbstractSaslClientAuthenticationProvider}. * - * Implementations of this interface must make an implementation of {link {@link #hashCode()} + * Implementations of this interface must make an implementation of {@code hashCode()} * which returns the same value across multiple instances of the provider implementation. */ @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java index 952d489f4a71..081ecf22044d 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java @@ -40,7 +40,7 @@ */ @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) @InterfaceStability.Evolving -public class SaslClientAuthenticationProviders { +public final class SaslClientAuthenticationProviders { private static final Logger LOG = LoggerFactory.getLogger(SaslClientAuthenticationProviders.class); public static final String SELECTOR_KEY = "hbase.client.sasl.provider.class"; @@ -52,7 +52,8 @@ public class SaslClientAuthenticationProviders { private final HashMap providers; private final AuthenticationProviderSelector selector; - private SaslClientAuthenticationProviders(HashMap providers, + private SaslClientAuthenticationProviders( + HashMap providers, AuthenticationProviderSelector selector) { this.providers = providers; this.selector = selector; @@ -79,7 +80,7 @@ public static synchronized SaslClientAuthenticationProviders getInstance(Configu } /** - * Removes the cached singleton instance of {@link SaslClientAuthenticationProviders}. + * Removes the cached singleton instance of {@link SaslClientAuthenticationProviders}. */ public static synchronized void reset() { providersRef.set(null); @@ -94,8 +95,8 @@ static void addProviderIfNotExists(SaslClientAuthenticationProvider provider, SaslClientAuthenticationProvider existingProvider = providers.get( provider.getSaslAuthMethod().getCode()); if (existingProvider != null) { - throw new RuntimeException("Already registered authentication provider with " + - provider.getSaslAuthMethod().getCode() + " " + existingProvider.getClass()); + throw new RuntimeException("Already registered authentication provider with " + + provider.getSaslAuthMethod().getCode() + " " + existingProvider.getClass()); } providers.put(provider.getSaslAuthMethod().getCode(), provider); } @@ -188,7 +189,7 @@ public SaslClientAuthenticationProvider getSimpleProvider() { .findFirst(); return optional.get(); } - + public Pair> selectProvider( Text clusterId, UserGroupInformation clientUser) { return selector.selectProvider(clusterId, clientUser); diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslClientAuthenticationProvider.java index 0a5ab581de3f..07ba13e4f564 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslClientAuthenticationProvider.java @@ -24,7 +24,6 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseInterfaceAudience; -import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; import org.apache.hadoop.security.token.Token; @@ -32,9 +31,12 @@ import org.apache.yetus.audience.InterfaceAudience; import org.apache.yetus.audience.InterfaceStability; +import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; + @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) @InterfaceStability.Evolving -public class SimpleSaslClientAuthenticationProvider extends AbstractSaslClientAuthenticationProvider { +public class SimpleSaslClientAuthenticationProvider extends + AbstractSaslClientAuthenticationProvider { private static final SaslAuthMethod SASL_AUTH_METHOD = new SaslAuthMethod( "SIMPLE", (byte)80, "", AuthenticationMethod.SIMPLE); diff --git a/hbase-client/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider b/hbase-client/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider index 732844fdb3c4..d624bc17390a 100644 --- a/hbase-client/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider +++ b/hbase-client/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider @@ -1,3 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + org.apache.hadoop.hbase.security.provider.GssSaslClientAuthenticationProvider org.apache.hadoop.hbase.security.provider.DigestSaslClientAuthenticationProvider org.apache.hadoop.hbase.security.provider.SimpleSaslClientAuthenticationProvider \ No newline at end of file diff --git a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestHBaseSaslRpcClient.java b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestHBaseSaslRpcClient.java index 4b3be04a8ed0..a49929ca3f8a 100644 --- a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestHBaseSaslRpcClient.java +++ b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestHBaseSaslRpcClient.java @@ -54,7 +54,6 @@ import org.apache.hadoop.io.DataOutputBuffer; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; -import org.apache.hbase.thirdparty.com.google.common.base.Strings; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.junit.BeforeClass; @@ -65,6 +64,8 @@ import org.junit.rules.ExpectedException; import org.mockito.Mockito; +import org.apache.hbase.thirdparty.com.google.common.base.Strings; + @Category({SecurityTests.class, SmallTests.class}) public class TestHBaseSaslRpcClient { @@ -119,7 +120,8 @@ public void testDigestSaslClientCallbackHandler() throws UnsupportedCallbackExce final RealmChoiceCallback realmChoiceCallback = mock(RealmChoiceCallback.class); Callback[] callbackArray = {nameCallback, passwordCallback, realmCallback, realmChoiceCallback}; - final DigestSaslClientCallbackHandler saslClCallbackHandler = new DigestSaslClientCallbackHandler(token); + final DigestSaslClientCallbackHandler saslClCallbackHandler = + new DigestSaslClientCallbackHandler(token); saslClCallbackHandler.handle(callbackArray); verify(nameCallback).setName(anyString()); verify(realmCallback).setText(any()); @@ -131,7 +133,8 @@ public void testDigestSaslClientCallbackHandlerWithException() { final Token token = createTokenMock(); when(token.getIdentifier()).thenReturn(Bytes.toBytes(DEFAULT_USER_NAME)); when(token.getPassword()).thenReturn(Bytes.toBytes(DEFAULT_USER_PASSWORD)); - final DigestSaslClientCallbackHandler saslClCallbackHandler = new DigestSaslClientCallbackHandler(token); + final DigestSaslClientCallbackHandler saslClCallbackHandler = + new DigestSaslClientCallbackHandler(token); try { saslClCallbackHandler.handle(new Callback[] { mock(TextOutputCallback.class) }); } catch (UnsupportedCallbackException expEx) { @@ -233,7 +236,8 @@ public SaslClient createClient(Configuration conf, String serverPrincipal, private boolean assertIOExceptionThenSaslClientIsNull(String principal, String password) { try { - DigestSaslClientAuthenticationProvider provider = new DigestSaslClientAuthenticationProvider() { + DigestSaslClientAuthenticationProvider provider = + new DigestSaslClientAuthenticationProvider() { @Override public SaslClient createClient(Configuration conf, String serverPrincipal, Token token, boolean fallbackAllowed, @@ -301,8 +305,8 @@ private Token createTokenMockWithCredentials( private HBaseSaslRpcClient createSaslRpcClientSimple(String principal, String password) throws IOException { - return new HBaseSaslRpcClient(HBaseConfiguration.create(), new SimpleSaslClientAuthenticationProvider(), - createTokenMock(), principal, false); + return new HBaseSaslRpcClient(HBaseConfiguration.create(), + new SimpleSaslClientAuthenticationProvider(), createTokenMock(), principal, false); } @SuppressWarnings("unchecked") diff --git a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslClientAuthenticationProviders.java b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslClientAuthenticationProviders.java index d4b6d54faace..79404c56a6bf 100644 --- a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslClientAuthenticationProviders.java +++ b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslClientAuthenticationProviders.java @@ -24,15 +24,21 @@ import java.util.HashMap; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HBaseClassTestRule; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.testclassification.SecurityTests; import org.apache.hadoop.hbase.testclassification.SmallTests; +import org.junit.ClassRule; import org.junit.Test; import org.junit.experimental.categories.Category; @Category({SmallTests.class, SecurityTests.class}) public class TestSaslClientAuthenticationProviders { + @ClassRule + public static final HBaseClassTestRule CLASS_RULE = + HBaseClassTestRule.forClass(TestSaslClientAuthenticationProviders.class); + @Test public void testCannotAddTheSameProviderTwice() { HashMap registeredProviders = new HashMap<>(); @@ -53,13 +59,16 @@ public void testCannotAddTheSameProviderTwice() { @Test public void testInstanceIsCached() { Configuration conf = HBaseConfiguration.create(); - SaslClientAuthenticationProviders providers1 = SaslClientAuthenticationProviders.getInstance(conf); - SaslClientAuthenticationProviders providers2 = SaslClientAuthenticationProviders.getInstance(conf); + SaslClientAuthenticationProviders providers1 = + SaslClientAuthenticationProviders.getInstance(conf); + SaslClientAuthenticationProviders providers2 = + SaslClientAuthenticationProviders.getInstance(conf); assertSame(providers1, providers2); SaslClientAuthenticationProviders.reset(); - SaslClientAuthenticationProviders providers3 = SaslClientAuthenticationProviders.getInstance(conf); + SaslClientAuthenticationProviders providers3 = + SaslClientAuthenticationProviders.getInstance(conf); assertNotSame(providers1, providers3); assertEquals(providers1.getNumRegisteredProviders(), providers3.getNumRegisteredProviders()); } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java index 4b3032ca74e4..fe24a8ec458e 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java @@ -162,8 +162,8 @@ public VersionInfo getVersionInfo() { private String getFatalConnectionString(final int version, final byte authByte) { return "serverVersion=" + RpcServer.CURRENT_VERSION + - ", clientVersion=" + version + ", authMethod=" + authByte + - ", authName=" + provider.getSaslAuthMethod().getName() + " from " + toString(); + ", clientVersion=" + version + ", authMethod=" + authByte + + ", authName=" + provider.getSaslAuthMethod().getName() + " from " + toString(); } /** @@ -384,11 +384,11 @@ public void saslReadAndProcess(ByteBuff saslToken) throws IOException, if (saslServer.isComplete()) { String qop = saslServer.getNegotiatedQop(); useWrap = qop != null && !"auth".equalsIgnoreCase(qop); - ugi = provider.getAuthorizedUgi(saslServer.getAuthorizationID(), this.rpcServer.secretManager); - if (RpcServer.LOG.isDebugEnabled()) { - RpcServer.LOG.debug("SASL server context established. Authenticated client: " + ugi + - ". Negotiated QoP is " + qop); - } + ugi = provider.getAuthorizedUgi(saslServer.getAuthorizationID(), + this.rpcServer.secretManager); + RpcServer.LOG.debug( + "SASL server context established. Authenticated client: {}. Negotiated QoP is {}", + ugi, qop); this.rpcServer.metrics.authenticationSuccess(); RpcServer.AUDITLOG.info(RpcServer.AUTH_SUCCESSFUL_FOR + ugi); saslContextEstablished = true; @@ -738,7 +738,8 @@ protected final boolean processPreamble(ByteBuffer preambleBuffer) throws IOExce return false; } // TODO this is a wart while simple auth'n doesn't go through sasl. - if (this.rpcServer.isSecurityEnabled && provider instanceof SimpleSaslServerAuthenticationProvider) { + if (this.rpcServer.isSecurityEnabled && + provider instanceof SimpleSaslServerAuthenticationProvider) { if (this.rpcServer.allowFallbackToSimpleAuth) { this.rpcServer.metrics.authenticationFallback(); authenticatedWithFallback = true; diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcServer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcServer.java index 25916a0100df..f3b7327c2536 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcServer.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcServer.java @@ -26,14 +26,14 @@ import javax.security.sasl.SaslException; import javax.security.sasl.SaslServer; -import org.apache.yetus.audience.InterfaceAudience; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.apache.hadoop.hbase.security.provider.SaslServerAuthenticationProvider; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.SecretManager; import org.apache.hadoop.security.token.SecretManager.InvalidToken; import org.apache.hadoop.security.token.TokenIdentifier; +import org.apache.yetus.audience.InterfaceAudience; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * A utility class that encapsulates SASL logic for RPC server. Copied from @@ -48,8 +48,9 @@ public class HBaseSaslRpcServer { private UserGroupInformation attemptingUser; // user name before auth - public HBaseSaslRpcServer(SaslServerAuthenticationProvider provider, Map saslProps, - SecretManager secretManager) throws IOException { + public HBaseSaslRpcServer(SaslServerAuthenticationProvider provider, + Map saslProps, SecretManager secretManager) + throws IOException { saslServer = provider.createServer(secretManager, saslProps); } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java index e574bed6f9e0..cdcc0b190eac 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java @@ -37,9 +37,9 @@ import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.SecretManager; import org.apache.hadoop.security.token.SecretManager.InvalidToken; +import org.apache.hadoop.security.token.TokenIdentifier; import org.apache.yetus.audience.InterfaceAudience; import org.apache.yetus.audience.InterfaceStability; -import org.apache.hadoop.security.token.TokenIdentifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,7 +47,8 @@ @InterfaceStability.Evolving public class DigestSaslServerAuthenticationProvider extends DigestSaslClientAuthenticationProvider implements SaslServerAuthenticationProvider { - private static final Logger LOG = LoggerFactory.getLogger(DigestSaslServerAuthenticationProvider.class); + private static final Logger LOG = LoggerFactory.getLogger( + DigestSaslServerAuthenticationProvider.class); @Override public SaslServer createServer(SecretManager secretManager, @@ -60,7 +61,7 @@ public SaslServer createServer(SecretManager secretManager, } /** CallbackHandler for SASL DIGEST-MD5 mechanism */ - private class SaslDigestCallbackHandler implements CallbackHandler { + private static class SaslDigestCallbackHandler implements CallbackHandler { private SecretManager secretManager; public SaslDigestCallbackHandler(SecretManager secretManager) { @@ -91,7 +92,8 @@ public void handle(Callback[] callbacks) throws InvalidToken, UnsupportedCallbac } } if (pc != null) { - TokenIdentifier tokenIdentifier = HBaseSaslRpcServer.getIdentifier(nc.getDefaultName(), secretManager); + TokenIdentifier tokenIdentifier = HBaseSaslRpcServer.getIdentifier( + nc.getDefaultName(), secretManager); char[] password = getPassword(tokenIdentifier); if (LOG.isTraceEnabled()) { LOG.trace("SASL server DIGEST-MD5 callback: setting password " + "for client: " + @@ -109,7 +111,8 @@ public void handle(Callback[] callbacks) throws InvalidToken, UnsupportedCallbac } if (ac.isAuthorized()) { if (LOG.isTraceEnabled()) { - String username = HBaseSaslRpcServer.getIdentifier(authzid, secretManager).getUser().getUserName(); + String username = HBaseSaslRpcServer.getIdentifier( + authzid, secretManager).getUser().getUserName(); LOG.trace( "SASL server DIGEST-MD5 callback: setting " + "canonicalized client ID: " + username); } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java index 045c5d3de21d..48cc379745dc 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java @@ -42,8 +42,10 @@ @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) @InterfaceStability.Evolving -public class GssSaslServerAuthenticationProvider extends GssSaslClientAuthenticationProvider implements SaslServerAuthenticationProvider { - private static final Logger LOG = LoggerFactory.getLogger(GssSaslServerAuthenticationProvider.class); +public class GssSaslServerAuthenticationProvider extends GssSaslClientAuthenticationProvider + implements SaslServerAuthenticationProvider { + private static final Logger LOG = LoggerFactory.getLogger( + GssSaslServerAuthenticationProvider.class); @Override public SaslServer createServer(SecretManager secretManager, @@ -106,8 +108,8 @@ public boolean supportsProtocolAuthentication() { } @Override - public UserGroupInformation getAuthorizedUgi(String authzId, SecretManager secretManager) - throws IOException { + public UserGroupInformation getAuthorizedUgi(String authzId, + SecretManager secretManager) throws IOException { UserGroupInformation ugi = UserGroupInformation.createRemoteUser(authzId); ugi.setAuthenticationMethod(getSaslAuthMethod().getAuthMethod()); return ugi; diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProviders.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProviders.java index e40e2632c58d..bd4142f3ed20 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProviders.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProviders.java @@ -28,11 +28,13 @@ import org.slf4j.LoggerFactory; @InterfaceAudience.Private -public class SaslServerAuthenticationProviders { - private static final Logger LOG = LoggerFactory.getLogger(SaslClientAuthenticationProviders.class); +public final class SaslServerAuthenticationProviders { + private static final Logger LOG = LoggerFactory.getLogger( + SaslClientAuthenticationProviders.class); public static final String EXTRA_PROVIDERS_KEY = "hbase.server.sasl.provider.extras"; - private static final AtomicReference holder = new AtomicReference<>(); + private static final AtomicReference holder = + new AtomicReference<>(); private final Configuration conf; private final HashMap providers; @@ -71,7 +73,7 @@ public static SaslServerAuthenticationProviders getInstance(Configuration conf) } /** - * Removes the cached singleton instance of {@link SaslServerAuthenticationProviders}. + * Removes the cached singleton instance of {@link SaslServerAuthenticationProviders}. */ public static void reset() { synchronized (holder) { @@ -111,7 +113,7 @@ static void addExtraProviders(Configuration conf, } if (!SaslServerAuthenticationProvider.class.isAssignableFrom(clz)) { - LOG.warn("Server authentication class {} is not an instance of " + LOG.warn("Server authentication class {} is not an instance of " + "SaslServerAuthenticationProvider", clz); continue; } diff --git a/hbase-server/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslServerAuthenticationProvider b/hbase-server/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslServerAuthenticationProvider index 66099c9f6d86..50abdb7cb2ed 100644 --- a/hbase-server/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslServerAuthenticationProvider +++ b/hbase-server/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslServerAuthenticationProvider @@ -1,3 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + org.apache.hadoop.hbase.security.provider.DigestSaslServerAuthenticationProvider org.apache.hadoop.hbase.security.provider.GssSaslServerAuthenticationProvider org.apache.hadoop.hbase.security.provider.SimpleSaslServerAuthenticationProvider \ No newline at end of file diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java index a8c1c21c4e88..58e37a27b32e 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java @@ -76,7 +76,6 @@ import org.apache.hadoop.hbase.security.SaslUtil; import org.apache.hadoop.hbase.security.token.SecureTestCluster; import org.apache.hadoop.hbase.security.token.TokenProvider; -import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; import org.apache.hadoop.hbase.testclassification.MediumTests; import org.apache.hadoop.hbase.testclassification.SecurityTests; import org.apache.hadoop.hbase.util.Bytes; @@ -102,6 +101,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; + /** * Tests the pluggable authentication framework with SASL using a contrived authentication system. * @@ -110,7 +111,8 @@ */ @Category({MediumTests.class, SecurityTests.class}) public class TestCustomSaslAuthenticationProvider { - private static final Logger LOG = LoggerFactory.getLogger(TestCustomSaslAuthenticationProvider.class); + private static final Logger LOG = LoggerFactory.getLogger( + TestCustomSaslAuthenticationProvider.class); @ClassRule public static final HBaseClassTestRule CLASS_RULE = @@ -271,7 +273,8 @@ public boolean isKerberos() { /** * Server provider which validates credentials from an in-memory database. */ - public static class InMemoryServerProvider extends InMemoryClientProvider implements SaslServerAuthenticationProvider { + public static class InMemoryServerProvider extends InMemoryClientProvider + implements SaslServerAuthenticationProvider { @Override public SaslServer createServer(SecretManager secretManager, @@ -302,7 +305,7 @@ public void handle(Callback[] callbacks) throws InvalidToken, UnsupportedCallbac } else if (callback instanceof RealmCallback) { continue; // realm is ignored } else { - throw new UnsupportedCallbackException(callback, "Unrecognized SASL DIGEST-MD5 Callback"); + throw new UnsupportedCallbackException(callback, "Unrecognized SASL Callback"); } } if (nc != null && pc != null) { @@ -311,7 +314,8 @@ public void handle(Callback[] callbacks) throws InvalidToken, UnsupportedCallbac try { id.readFields(new DataInputStream(new ByteArrayInputStream(encodedId))); } catch (IOException e) { - throw (InvalidToken) new InvalidToken("Can't de-serialize tokenIdentifier").initCause(e); + throw (InvalidToken) new InvalidToken( + "Can't de-serialize tokenIdentifier").initCause(e); } char[] actualPassword = SaslUtil.encodePassword( Bytes.toBytes(getPassword(id.getUser().getUserName()))); @@ -367,7 +371,8 @@ public static class InMemoryProviderSelector extends DefaultProviderSelector { private InMemoryClientProvider inMemoryProvider; @Override - public void configure(Configuration conf, Map providers) { + public void configure(Configuration conf, + Map providers) { super.configure(conf, providers); Optional o = providers.values().stream() .filter((p) -> p instanceof InMemoryClientProvider) @@ -437,7 +442,8 @@ public static void setupCluster() throws Exception { InMemoryClientProvider.class.getName()); CONF.setStrings(SaslServerAuthenticationProviders.EXTRA_PROVIDERS_KEY, InMemoryServerProvider.class.getName()); - CONF.set(SaslClientAuthenticationProviders.SELECTOR_KEY, InMemoryProviderSelector.class.getName()); + CONF.set(SaslClientAuthenticationProviders.SELECTOR_KEY, + InMemoryProviderSelector.class.getName()); CLUSTER = createCluster(UTIL, KEYTAB_FILE, kdc); CLUSTER.startup(); @@ -472,7 +478,7 @@ public void createTable() throws Exception { .newBuilder(tableName) .setColumnFamily(ColumnFamilyDescriptorBuilder.of("f1")) .build()); - + UTIL.waitTableAvailable(tableName); try (Table t = conn.getTable(tableName)) { @@ -480,7 +486,7 @@ public void createTable() throws Exception { p.addColumn(Bytes.toBytes("f1"), Bytes.toBytes("q1"), Bytes.toBytes("1")); t.put(p); } - + return admin.getClusterMetrics().getClusterId(); } } diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslServerAuthenticationProviders.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslServerAuthenticationProviders.java index 77d0d416f1e5..e899dc001b1c 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslServerAuthenticationProviders.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslServerAuthenticationProviders.java @@ -17,16 +17,28 @@ */ package org.apache.hadoop.hbase.security.provider; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; import java.util.HashMap; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HBaseClassTestRule; import org.apache.hadoop.hbase.HBaseConfiguration; +import org.apache.hadoop.hbase.testclassification.SecurityTests; +import org.apache.hadoop.hbase.testclassification.SmallTests; +import org.junit.ClassRule; import org.junit.Test; +import org.junit.experimental.categories.Category; +@Category({SmallTests.class, SecurityTests.class}) public class TestSaslServerAuthenticationProviders { + @ClassRule + public static final HBaseClassTestRule CLASS_RULE = + HBaseClassTestRule.forClass(TestSaslServerAuthenticationProviders.class); + @Test public void testCannotAddTheSameProviderTwice() { HashMap registeredProviders = new HashMap<>(); @@ -47,13 +59,16 @@ public void testCannotAddTheSameProviderTwice() { @Test public void testInstanceIsCached() { Configuration conf = HBaseConfiguration.create(); - SaslServerAuthenticationProviders providers1 = SaslServerAuthenticationProviders.getInstance(conf); - SaslServerAuthenticationProviders providers2 = SaslServerAuthenticationProviders.getInstance(conf); + SaslServerAuthenticationProviders providers1 = + SaslServerAuthenticationProviders.getInstance(conf); + SaslServerAuthenticationProviders providers2 = + SaslServerAuthenticationProviders.getInstance(conf); assertSame(providers1, providers2); SaslServerAuthenticationProviders.reset(); - SaslServerAuthenticationProviders providers3 = SaslServerAuthenticationProviders.getInstance(conf); + SaslServerAuthenticationProviders providers3 = + SaslServerAuthenticationProviders.getInstance(conf); assertNotSame(providers1, providers3); assertEquals(providers1.getNumRegisteredProviders(), providers3.getNumRegisteredProviders()); } From 6dc490243b0d552d4b82832f7e034606d68ca41d Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Wed, 4 Dec 2019 12:09:03 -0500 Subject: [PATCH 05/41] Move the writeup into dev-support/design-docs (thanks busbey) --- .../design-docs/HBASE-23347-pluggable-authentication.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename PluggableRpcAuthentication.md => dev-support/design-docs/HBASE-23347-pluggable-authentication.md (100%) diff --git a/PluggableRpcAuthentication.md b/dev-support/design-docs/HBASE-23347-pluggable-authentication.md similarity index 100% rename from PluggableRpcAuthentication.md rename to dev-support/design-docs/HBASE-23347-pluggable-authentication.md From 0dbd470562791640673c564e8fd9f8ca129360cc Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Wed, 4 Dec 2019 13:36:10 -0500 Subject: [PATCH 06/41] Consolidate the provider+token acquisition, client-side. --- .../apache/hadoop/hbase/ipc/RpcConnection.java | 12 ++++++------ .../SaslClientAuthenticationProviders.java | 15 +++++++++++++-- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java index b4e23b0b562d..d32102ef88ff 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java @@ -99,16 +99,14 @@ protected RpcConnection(Configuration conf, HashedWheelTimer timeoutTimer, Conne // Choose the correct Token and AuthenticationProvider for this client to use SaslClientAuthenticationProviders providers = SaslClientAuthenticationProviders.getInstance(conf); + Pair> pair; if (useSasl && securityInfo != null) { - Pair> pair = - providers.selectProvider(new Text(clusterId), ticket); + pair = providers.selectProvider(new Text(clusterId), ticket); if (pair == null) { LOG.error("Found no valid authentication method from {} with tokens={}", providers.toString(), ticket.getTokens()); throw new RuntimeException("Found no valid authentication method from options"); } - this.provider = pair.getFirst(); - this.token = pair.getSecond(); String serverKey = securityInfo.getServerPrincipal(); if (serverKey == null) { @@ -122,12 +120,14 @@ protected RpcConnection(Configuration conf, HashedWheelTimer timeoutTimer, Conne } } else if (!useSasl) { // Hack, while SIMPLE doesn't go via SASL. - provider = providers.getSimpleProvider(); - this.token = null; + pair = providers.getSimpleProvider(); } else { throw new RuntimeException("Could not compute valid client authentication provider"); } + this.provider = pair.getFirst(); + this.token = pair.getSecond(); + // May be null this.serverPrincipal = serverPrincipal; LOG.debug("Using {} authentication for service{}, sasl={}", diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java index 081ecf22044d..26d2a8b67216 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java @@ -183,13 +183,24 @@ static SaslClientAuthenticationProviders instantiate(Configuration conf) { return new SaslClientAuthenticationProviders(providers, selector); } - public SaslClientAuthenticationProvider getSimpleProvider() { + /** + * Returns the provider and token pair for SIMPLE authentication. + * + * This method is a "hack" while SIMPLE authentication for HBase does not flow through + * the SASL codepath. + */ + public Pair> + getSimpleProvider() { Optional optional = providers.values().stream() .filter((p) -> p instanceof SimpleSaslClientAuthenticationProvider) .findFirst(); - return optional.get(); + return new Pair<>(optional.get(), null); } + /** + * Chooses the best authentication provider and corresponding token given the HBase cluster + * identifier and the user. + */ public Pair> selectProvider( Text clusterId, UserGroupInformation clientUser) { return selector.selectProvider(clusterId, clientUser); From 06f4a4e29002bdc4978016b0e256722124e8b855 Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Wed, 4 Dec 2019 14:52:51 -0500 Subject: [PATCH 07/41] Add license to design doc --- .../HBASE-23347-pluggable-authentication.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/dev-support/design-docs/HBASE-23347-pluggable-authentication.md b/dev-support/design-docs/HBASE-23347-pluggable-authentication.md index fd49e0e27836..820f74a4ee23 100644 --- a/dev-support/design-docs/HBASE-23347-pluggable-authentication.md +++ b/dev-support/design-docs/HBASE-23347-pluggable-authentication.md @@ -1,3 +1,21 @@ + + # Pluggable Authentication for HBase RPCs ## Background From dd0381c3471ac7fa65810772a32575f5e0166af9 Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Wed, 4 Dec 2019 14:53:01 -0500 Subject: [PATCH 08/41] Round two of checkstyle fixing --- .../java/org/apache/hadoop/hbase/ipc/RpcConnection.java | 6 +++--- .../security/provider/AuthenticationProviderSelector.java | 1 - .../provider/GssSaslClientAuthenticationProvider.java | 3 ++- .../provider/SaslClientAuthenticationProviders.java | 3 ++- .../provider/SimpleSaslServerAuthenticationProvider.java | 4 ++-- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java index d32102ef88ff..97998325e8c0 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java @@ -35,13 +35,13 @@ import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; -import org.apache.hbase.thirdparty.io.netty.util.HashedWheelTimer; -import org.apache.hbase.thirdparty.io.netty.util.Timeout; -import org.apache.hbase.thirdparty.io.netty.util.TimerTask; import org.apache.yetus.audience.InterfaceAudience; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.apache.hbase.thirdparty.io.netty.util.HashedWheelTimer; +import org.apache.hbase.thirdparty.io.netty.util.Timeout; +import org.apache.hbase.thirdparty.io.netty.util.TimerTask; import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.ConnectionHeader; import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java index b86e54400f87..5bb011b77ae2 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java @@ -18,7 +18,6 @@ package org.apache.hadoop.hbase.security.provider; import java.util.Map; -import java.util.Set; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseInterfaceAudience; diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java index 31d8d8a1800e..a5de720b8812 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java @@ -26,7 +26,6 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseInterfaceAudience; import org.apache.hadoop.hbase.security.SaslUtil; -import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; import org.apache.hadoop.security.token.Token; @@ -34,6 +33,8 @@ import org.apache.yetus.audience.InterfaceAudience; import org.apache.yetus.audience.InterfaceStability; +import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; + @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) @InterfaceStability.Evolving public class GssSaslClientAuthenticationProvider extends AbstractSaslClientAuthenticationProvider { diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java index 26d2a8b67216..172b21891ef2 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java @@ -41,7 +41,8 @@ @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) @InterfaceStability.Evolving public final class SaslClientAuthenticationProviders { - private static final Logger LOG = LoggerFactory.getLogger(SaslClientAuthenticationProviders.class); + private static final Logger LOG = LoggerFactory.getLogger( + SaslClientAuthenticationProviders.class); public static final String SELECTOR_KEY = "hbase.client.sasl.provider.class"; public static final String EXTRA_PROVIDERS_KEY = "hbase.client.sasl.provider.extras"; diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java index 3e6ab323f843..5f41eccd8223 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java @@ -46,8 +46,8 @@ public boolean supportsProtocolAuthentication() { } @Override - public UserGroupInformation getAuthorizedUgi(String authzId, SecretManager secretManager) - throws IOException { + public UserGroupInformation getAuthorizedUgi(String authzId, + SecretManager secretManager) throws IOException { UserGroupInformation ugi = UserGroupInformation.createRemoteUser(authzId); ugi.setAuthenticationMethod(getSaslAuthMethod().getAuthMethod()); return ugi; From e04c31df3483561560ff90251e411d8c519f7857 Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Wed, 4 Dec 2019 15:20:25 -0500 Subject: [PATCH 09/41] Remove serverPrincipal and pass through InetAddress+SecurityInfo This decouples the auth'n provider from the kerberos-specifical principal we previously passed. Hopefully, this gives enough context to the provider to know how to get any relevant details to correctly authenticate with the service. --- .../hbase/ipc/BlockingRpcConnection.java | 10 ++++--- .../hadoop/hbase/ipc/NettyRpcConnection.java | 2 +- .../hadoop/hbase/ipc/RpcConnection.java | 24 +++++------------ .../security/AbstractHBaseSaslRpcClient.java | 12 ++++++--- .../hbase/security/HBaseSaslRpcClient.java | 14 +++++----- .../security/NettyHBaseSaslRpcClient.java | 7 ++--- .../NettyHBaseSaslRpcClientHandler.java | 8 +++--- ...igestSaslClientAuthenticationProvider.java | 6 +++-- .../GssSaslClientAuthenticationProvider.java | 26 +++++++++++++++--- .../SaslClientAuthenticationProvider.java | 4 ++- ...impleSaslClientAuthenticationProvider.java | 6 +++-- .../security/TestHBaseSaslRpcClient.java | 27 ++++++++++++------- .../TestCustomSaslAuthenticationProvider.java | 6 +++-- 13 files changed, 93 insertions(+), 59 deletions(-) diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java index dc9958f0994b..711c5126a77c 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java @@ -361,9 +361,10 @@ private void disposeSasl() { private boolean setupSaslConnection(final InputStream in2, final OutputStream out2) throws IOException { - saslRpcClient = new HBaseSaslRpcClient(this.rpcClient.conf, provider, token, serverPrincipal, - this.rpcClient.fallbackAllowed, this.rpcClient.conf.get("hbase.rpc.protection", - QualityOfProtection.AUTHENTICATION.name().toLowerCase(Locale.ROOT)), + saslRpcClient = new HBaseSaslRpcClient(this.rpcClient.conf, provider, token, + serverAddress, securityInfo, this.rpcClient.fallbackAllowed, + this.rpcClient.conf.get("hbase.rpc.protection", + QualityOfProtection.AUTHENTICATION.name().toLowerCase(Locale.ROOT)), this.rpcClient.conf.getBoolean(CRYPTO_AES_ENABLED_KEY, CRYPTO_AES_ENABLED_DEFAULT)); return saslRpcClient.saslConnect(in2, out2); } @@ -406,7 +407,8 @@ public Object run() throws IOException, InterruptedException { return null; } else { String msg = "Couldn't setup connection for " - + UserGroupInformation.getLoginUser().getUserName() + " to " + serverPrincipal; + + UserGroupInformation.getLoginUser().getUserName() + " to " + + securityInfo.getServerPrincipal(); LOG.warn(msg, ex); throw new IOException(msg, ex); } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/NettyRpcConnection.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/NettyRpcConnection.java index 07744bdee7fd..5b8deaac5542 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/NettyRpcConnection.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/NettyRpcConnection.java @@ -192,7 +192,7 @@ private void saslNegotiate(final Channel ch) { final NettyHBaseSaslRpcClientHandler saslHandler; try { saslHandler = new NettyHBaseSaslRpcClientHandler(saslPromise, ticket, provider, token, - serverPrincipal, rpcClient.fallbackAllowed, this.rpcClient.conf); + serverAddress, securityInfo, rpcClient.fallbackAllowed, this.rpcClient.conf); } catch (IOException e) { failInit(ch, e); return; diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java index 97998325e8c0..8362bbf84e00 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java @@ -18,6 +18,7 @@ package org.apache.hadoop.hbase.ipc; import java.io.IOException; +import java.net.InetAddress; import java.net.UnknownHostException; import java.util.concurrent.TimeUnit; @@ -31,7 +32,6 @@ import org.apache.hadoop.hbase.util.Pair; import org.apache.hadoop.io.Text; import org.apache.hadoop.io.compress.CompressionCodec; -import org.apache.hadoop.security.SecurityUtil; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; @@ -60,7 +60,9 @@ abstract class RpcConnection { protected final Token token; - protected final String serverPrincipal; // server's krb5 principal name + protected final InetAddress serverAddress; + + protected final SecurityInfo securityInfo; protected final int reloginMaxBackoff; // max pause before relogin on sasl failure @@ -87,15 +89,16 @@ protected RpcConnection(Configuration conf, HashedWheelTimer timeoutTimer, Conne if (remoteId.getAddress().isUnresolved()) { throw new UnknownHostException("unknown host: " + remoteId.getAddress().getHostName()); } + this.serverAddress = remoteId.getAddress().getAddress(); this.timeoutTimer = timeoutTimer; this.codec = codec; this.compressor = compressor; this.conf = conf; UserGroupInformation ticket = remoteId.getTicket().getUGI(); - SecurityInfo securityInfo = SecurityInfo.getInfo(remoteId.getServiceName()); + this.securityInfo = SecurityInfo.getInfo(remoteId.getServiceName()); this.useSasl = isSecurityEnabled; - String serverPrincipal = null; + // Choose the correct Token and AuthenticationProvider for this client to use SaslClientAuthenticationProviders providers = SaslClientAuthenticationProviders.getInstance(conf); @@ -107,17 +110,6 @@ protected RpcConnection(Configuration conf, HashedWheelTimer timeoutTimer, Conne providers.toString(), ticket.getTokens()); throw new RuntimeException("Found no valid authentication method from options"); } - - String serverKey = securityInfo.getServerPrincipal(); - if (serverKey == null) { - throw new IOException("Can't obtain server Kerberos config key from SecurityInfo"); - } - serverPrincipal = SecurityUtil.getServerPrincipal(conf.get(serverKey), - remoteId.address.getAddress().getCanonicalHostName().toLowerCase()); - if (LOG.isDebugEnabled()) { - LOG.debug("RPC Server Kerberos principal name for service=" + remoteId.getServiceName() - + " is " + serverPrincipal); - } } else if (!useSasl) { // Hack, while SIMPLE doesn't go via SASL. pair = providers.getSimpleProvider(); @@ -127,8 +119,6 @@ protected RpcConnection(Configuration conf, HashedWheelTimer timeoutTimer, Conne this.provider = pair.getFirst(); this.token = pair.getSecond(); - // May be null - this.serverPrincipal = serverPrincipal; LOG.debug("Using {} authentication for service{}, sasl={}", provider.getSaslAuthMethod().getName(), remoteId.serviceName, useSasl); diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/AbstractHBaseSaslRpcClient.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/AbstractHBaseSaslRpcClient.java index 93bfc569514c..339ea143d4dd 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/AbstractHBaseSaslRpcClient.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/AbstractHBaseSaslRpcClient.java @@ -18,6 +18,7 @@ package org.apache.hadoop.hbase.security; import java.io.IOException; +import java.net.InetAddress; import java.util.Map; import javax.security.sasl.SaslClient; @@ -59,8 +60,9 @@ public abstract class AbstractHBaseSaslRpcClient { */ protected AbstractHBaseSaslRpcClient(Configuration conf, SaslClientAuthenticationProvider provider, Token token, - String serverPrincipal, boolean fallbackAllowed) throws IOException { - this(conf, provider, token, serverPrincipal, fallbackAllowed, "authentication"); + InetAddress serverAddr, SecurityInfo securityInfo, boolean fallbackAllowed) + throws IOException { + this(conf, provider, token, serverAddr, securityInfo, fallbackAllowed, "authentication"); } /** @@ -75,11 +77,13 @@ protected AbstractHBaseSaslRpcClient(Configuration conf, */ protected AbstractHBaseSaslRpcClient(Configuration conf, SaslClientAuthenticationProvider provider, Token token, - String serverPrincipal, boolean fallbackAllowed, String rpcProtection) throws IOException { + InetAddress serverAddr, SecurityInfo securityInfo, boolean fallbackAllowed, + String rpcProtection) throws IOException { this.fallbackAllowed = fallbackAllowed; saslProps = SaslUtil.initSaslProperties(rpcProtection); - saslClient = provider.createClient(conf, serverPrincipal, token, fallbackAllowed, saslProps); + saslClient = provider.createClient( + conf, serverAddr, securityInfo, token, fallbackAllowed, saslProps); if (saslClient == null) { throw new IOException("Authentication provider " + provider.getClass() + " returned a null SaslClient"); diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcClient.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcClient.java index 17d72ec0e1cd..94e7b7edb508 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcClient.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcClient.java @@ -27,6 +27,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.InetAddress; import java.nio.ByteBuffer; import javax.security.sasl.Sasl; @@ -63,15 +64,16 @@ public class HBaseSaslRpcClient extends AbstractHBaseSaslRpcClient { private boolean initStreamForCrypto; public HBaseSaslRpcClient(Configuration conf, SaslClientAuthenticationProvider provider, - Token token, String serverPrincipal, boolean fallbackAllowed) - throws IOException { - super(conf, provider, token, serverPrincipal, fallbackAllowed); + Token token, InetAddress serverAddr, SecurityInfo securityInfo, + boolean fallbackAllowed) throws IOException { + super(conf, provider, token, serverAddr, securityInfo, fallbackAllowed); } public HBaseSaslRpcClient(Configuration conf, SaslClientAuthenticationProvider provider, - Token token, String serverPrincipal, boolean fallbackAllowed, - String rpcProtection, boolean initStreamForCrypto) throws IOException { - super(conf, provider, token, serverPrincipal, fallbackAllowed, rpcProtection); + Token token, InetAddress serverAddr, SecurityInfo securityInfo, + boolean fallbackAllowed, String rpcProtection, boolean initStreamForCrypto) + throws IOException { + super(conf, provider, token, serverAddr, securityInfo, fallbackAllowed, rpcProtection); this.initStreamForCrypto = initStreamForCrypto; } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClient.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClient.java index efea5c169fe7..a5b980350d15 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClient.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClient.java @@ -21,6 +21,7 @@ import org.apache.hbase.thirdparty.io.netty.handler.codec.LengthFieldBasedFrameDecoder; import java.io.IOException; +import java.net.InetAddress; import javax.security.sasl.Sasl; @@ -41,9 +42,9 @@ public class NettyHBaseSaslRpcClient extends AbstractHBaseSaslRpcClient { private static final Logger LOG = LoggerFactory.getLogger(NettyHBaseSaslRpcClient.class); public NettyHBaseSaslRpcClient(Configuration conf, SaslClientAuthenticationProvider provider, - Token token, String serverPrincipal, boolean fallbackAllowed, - String rpcProtection) throws IOException { - super(conf, provider, token, serverPrincipal, fallbackAllowed, rpcProtection); + Token token, InetAddress serverAddr, SecurityInfo securityInfo, + boolean fallbackAllowed, String rpcProtection) throws IOException { + super(conf, provider, token, serverAddr, securityInfo, fallbackAllowed, rpcProtection); } public void setupSaslHandler(ChannelPipeline p) { diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClientHandler.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClientHandler.java index 6ff9ff1c253f..e011cc612e54 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClientHandler.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/NettyHBaseSaslRpcClientHandler.java @@ -24,6 +24,7 @@ import org.apache.hbase.thirdparty.io.netty.util.concurrent.Promise; import java.io.IOException; +import java.net.InetAddress; import java.security.PrivilegedExceptionAction; import org.apache.hadoop.conf.Configuration; @@ -62,12 +63,13 @@ public class NettyHBaseSaslRpcClientHandler extends SimpleChannelInboundHandler< */ public NettyHBaseSaslRpcClientHandler(Promise saslPromise, UserGroupInformation ugi, SaslClientAuthenticationProvider provider, Token token, - String serverPrincipal, boolean fallbackAllowed, Configuration conf) throws IOException { + InetAddress serverAddr, SecurityInfo securityInfo, boolean fallbackAllowed, + Configuration conf) throws IOException { this.saslPromise = saslPromise; this.ugi = ugi; this.conf = conf; - this.saslRpcClient = new NettyHBaseSaslRpcClient(conf, provider, token, serverPrincipal, - fallbackAllowed, conf.get( + this.saslRpcClient = new NettyHBaseSaslRpcClient(conf, provider, token, serverAddr, + securityInfo, fallbackAllowed, conf.get( "hbase.rpc.protection", SaslUtil.QualityOfProtection.AUTHENTICATION.name().toLowerCase())); } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java index 1de23a5c5de2..ef970efc3ac6 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java @@ -18,6 +18,7 @@ package org.apache.hadoop.hbase.security.provider; import java.io.IOException; +import java.net.InetAddress; import java.util.Map; import javax.security.auth.callback.Callback; @@ -33,6 +34,7 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseInterfaceAudience; import org.apache.hadoop.hbase.security.SaslUtil; +import org.apache.hadoop.hbase.security.SecurityInfo; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; import org.apache.hadoop.security.token.Token; @@ -58,8 +60,8 @@ public static String getMechanism() { } @Override - public SaslClient createClient(Configuration conf, String serverPrincipal, - Token token, boolean fallbackAllowed, + public SaslClient createClient(Configuration conf, InetAddress serverAddr, + SecurityInfo securityInfo, Token token, boolean fallbackAllowed, Map saslProps) throws IOException { return Sasl.createSaslClient(new String[] { MECHANISM }, null, null, SaslUtil.SASL_DEFAULT_REALM, saslProps, new DigestSaslClientCallbackHandler(token)); diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java index a5de720b8812..96625e6201c4 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java @@ -18,6 +18,7 @@ package org.apache.hadoop.hbase.security.provider; import java.io.IOException; +import java.net.InetAddress; import java.util.Map; import javax.security.sasl.Sasl; @@ -26,26 +27,43 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseInterfaceAudience; import org.apache.hadoop.hbase.security.SaslUtil; +import org.apache.hadoop.hbase.security.SecurityInfo; +import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; +import org.apache.hadoop.security.SecurityUtil; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; import org.apache.yetus.audience.InterfaceAudience; import org.apache.yetus.audience.InterfaceStability; - -import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) @InterfaceStability.Evolving public class GssSaslClientAuthenticationProvider extends AbstractSaslClientAuthenticationProvider { + private static final Logger LOG = LoggerFactory.getLogger( + GssSaslClientAuthenticationProvider.class); private static final String MECHANISM = "GSSAPI"; private static final SaslAuthMethod SASL_AUTH_METHOD = new SaslAuthMethod( "KERBEROS", (byte)81, MECHANISM, AuthenticationMethod.KERBEROS); + String getServerPrincipal(Configuration conf, SecurityInfo securityInfo, InetAddress server) + throws IOException { + String serverKey = securityInfo.getServerPrincipal(); + if (serverKey == null) { + throw new IOException("Can't obtain server Kerberos config key from SecurityInfo"); + } + return SecurityUtil.getServerPrincipal(conf.get(serverKey), + server.getCanonicalHostName().toLowerCase()); + } + @Override - public SaslClient createClient(Configuration conf, String serverPrincipal, - Token token, boolean fallbackAllowed, + public SaslClient createClient(Configuration conf, InetAddress serverAddr, + SecurityInfo securityInfo, Token token, boolean fallbackAllowed, Map saslProps) throws IOException { + String serverPrincipal = getServerPrincipal(conf, securityInfo, serverAddr); + LOG.debug("Setting up Kerberos RPC to server={}", serverPrincipal); String[] names = SaslUtil.splitKerberosName(serverPrincipal); if (names.length != 3) { throw new IOException("Kerberos principal '" + serverPrincipal diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProvider.java index 246fe0380513..4ff680dea4ce 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProvider.java @@ -18,12 +18,14 @@ package org.apache.hadoop.hbase.security.provider; import java.io.IOException; +import java.net.InetAddress; import java.util.Map; import javax.security.sasl.SaslClient; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseInterfaceAudience; +import org.apache.hadoop.hbase.security.SecurityInfo; import org.apache.hadoop.io.Text; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.Token; @@ -48,7 +50,7 @@ public interface SaslClientAuthenticationProvider { /** * Creates the SASL client instance for this auth'n method. */ - SaslClient createClient(Configuration conf, String serverPrincipal, + SaslClient createClient(Configuration conf, InetAddress serverAddr, SecurityInfo securityInfo, Token token, boolean fallbackAllowed, Map saslProps) throws IOException; diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslClientAuthenticationProvider.java index 07ba13e4f564..31b916697980 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslClientAuthenticationProvider.java @@ -18,12 +18,14 @@ package org.apache.hadoop.hbase.security.provider; import java.io.IOException; +import java.net.InetAddress; import java.util.Map; import javax.security.sasl.SaslClient; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseInterfaceAudience; +import org.apache.hadoop.hbase.security.SecurityInfo; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; import org.apache.hadoop.security.token.Token; @@ -41,8 +43,8 @@ public class SimpleSaslClientAuthenticationProvider extends "SIMPLE", (byte)80, "", AuthenticationMethod.SIMPLE); @Override - public SaslClient createClient(Configuration conf, String serverPrincipal, - Token token, boolean fallbackAllowed, + public SaslClient createClient(Configuration conf, InetAddress serverAddress, + SecurityInfo securityInfo, Token token, boolean fallbackAllowed, Map saslProps) throws IOException { return null; } diff --git a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestHBaseSaslRpcClient.java b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestHBaseSaslRpcClient.java index a49929ca3f8a..201f32384d27 100644 --- a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestHBaseSaslRpcClient.java +++ b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestHBaseSaslRpcClient.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.when; import java.io.IOException; +import java.net.InetAddress; import java.util.Map; import javax.security.auth.callback.Callback; @@ -99,7 +100,8 @@ public void testSaslClientUsesGivenRpcProtection() throws Exception { DigestSaslClientAuthenticationProvider provider = new DigestSaslClientAuthenticationProvider(); for (SaslUtil.QualityOfProtection qop : SaslUtil.QualityOfProtection.values()) { String negotiatedQop = new HBaseSaslRpcClient(HBaseConfiguration.create(), provider, token, - "principal/host@DOMAIN.COM", false, qop.name(), false) { + Mockito.mock(InetAddress.class), Mockito.mock(SecurityInfo.class), false, qop.name(), + false) { public String getQop() { return saslProps.get(Sasl.QOP); } @@ -208,14 +210,15 @@ private boolean assertIOExceptionWhenGetStreamsBeforeConnectCall(String principa DigestSaslClientAuthenticationProvider provider = new DigestSaslClientAuthenticationProvider() { @Override - public SaslClient createClient(Configuration conf, String serverPrincipal, - Token token, boolean fallbackAllowed, - Map saslProps) { + public SaslClient createClient(Configuration conf, InetAddress serverAddress, + SecurityInfo securityInfo, Token token, + boolean fallbackAllowed, Map saslProps) { return Mockito.mock(SaslClient.class); } }; HBaseSaslRpcClient rpcClient = new HBaseSaslRpcClient(HBaseConfiguration.create(), provider, - createTokenMockWithCredentials(principal, password), principal, false); + createTokenMockWithCredentials(principal, password), Mockito.mock(InetAddress.class), + Mockito.mock(SecurityInfo.class), false); try { rpcClient.getInputStream(); @@ -239,14 +242,16 @@ private boolean assertIOExceptionThenSaslClientIsNull(String principal, String p DigestSaslClientAuthenticationProvider provider = new DigestSaslClientAuthenticationProvider() { @Override - public SaslClient createClient(Configuration conf, String serverPrincipal, + public SaslClient createClient(Configuration conf, InetAddress serverAddress, + SecurityInfo securityInfo, Token token, boolean fallbackAllowed, Map saslProps) { return null; } }; new HBaseSaslRpcClient(HBaseConfiguration.create(), provider, - createTokenMockWithCredentials(principal, password), principal, false); + createTokenMockWithCredentials(principal, password), Mockito.mock(InetAddress.class), + Mockito.mock(SecurityInfo.class), false); return false; } catch (IOException ex) { return true; @@ -268,7 +273,8 @@ private boolean assertSuccessCreationDigestPrincipal(String principal, String pa try { rpcClient = new HBaseSaslRpcClient(HBaseConfiguration.create(), new DigestSaslClientAuthenticationProvider(), - createTokenMockWithCredentials(principal, password), principal, false); + createTokenMockWithCredentials(principal, password), Mockito.mock(InetAddress.class), + Mockito.mock(SecurityInfo.class), false); } catch(Exception ex) { LOG.error(ex.getMessage(), ex); } @@ -289,7 +295,7 @@ private HBaseSaslRpcClient createSaslRpcClientForKerberos(String principal) throws IOException { return new HBaseSaslRpcClient(HBaseConfiguration.create(), new GssSaslClientAuthenticationProvider(), createTokenMock(), - principal, false); + Mockito.mock(InetAddress.class), Mockito.mock(SecurityInfo.class), false); } private Token createTokenMockWithCredentials( @@ -306,7 +312,8 @@ private Token createTokenMockWithCredentials( private HBaseSaslRpcClient createSaslRpcClientSimple(String principal, String password) throws IOException { return new HBaseSaslRpcClient(HBaseConfiguration.create(), - new SimpleSaslClientAuthenticationProvider(), createTokenMock(), principal, false); + new SimpleSaslClientAuthenticationProvider(), createTokenMock(), + Mockito.mock(InetAddress.class), Mockito.mock(SecurityInfo.class), false); } @SuppressWarnings("unchecked") diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java index 58e37a27b32e..1f7bf30f7266 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java @@ -28,6 +28,7 @@ import java.io.DataOutput; import java.io.File; import java.io.IOException; +import java.net.InetAddress; import java.security.PrivilegedExceptionAction; import java.util.List; import java.util.Map; @@ -74,6 +75,7 @@ import org.apache.hadoop.hbase.security.AccessDeniedException; import org.apache.hadoop.hbase.security.HBaseKerberosUtils; import org.apache.hadoop.hbase.security.SaslUtil; +import org.apache.hadoop.hbase.security.SecurityInfo; import org.apache.hadoop.hbase.security.token.SecureTestCluster; import org.apache.hadoop.hbase.security.token.TokenProvider; import org.apache.hadoop.hbase.testclassification.MediumTests; @@ -194,8 +196,8 @@ public static class InMemoryClientProvider extends AbstractSaslClientAuthenticat "IN_MEMORY", (byte)42, MECHANISM, AuthenticationMethod.TOKEN); @Override - public SaslClient createClient(Configuration conf, String serverPrincipal, - Token token, boolean fallbackAllowed, + public SaslClient createClient(Configuration conf, InetAddress serverAddr, + SecurityInfo securityInfo, Token token, boolean fallbackAllowed, Map saslProps) throws IOException { return Sasl.createSaslClient(new String[] { MECHANISM }, null, null, SaslUtil.SASL_DEFAULT_REALM, saslProps, new InMemoryClientProviderCallbackHandler(token)); From 996b78057bf3c1b4e060cfd8289798bb38d07952 Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Thu, 5 Dec 2019 11:50:29 -0500 Subject: [PATCH 10/41] Fix up the test and get it running more reliably --- ...hbase.security.provider.SaslClientAuthenticationProvider | 1 - hbase-server/pom.xml | 6 ++++++ .../org/apache/hadoop/hbase/ipc/ServerRpcConnection.java | 5 +++-- ...hbase.security.provider.SaslServerAuthenticationProvider | 1 - 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/hbase-client/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider b/hbase-client/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider index d624bc17390a..566f5c3d89ed 100644 --- a/hbase-client/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider +++ b/hbase-client/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider @@ -13,7 +13,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - org.apache.hadoop.hbase.security.provider.GssSaslClientAuthenticationProvider org.apache.hadoop.hbase.security.provider.DigestSaslClientAuthenticationProvider org.apache.hadoop.hbase.security.provider.SimpleSaslClientAuthenticationProvider \ No newline at end of file diff --git a/hbase-server/pom.xml b/hbase-server/pom.xml index a2577da0750c..741078b6a082 100644 --- a/hbase-server/pom.xml +++ b/hbase-server/pom.xml @@ -47,6 +47,12 @@ hbase-webapps/** + + src/main/resources + + **/** + + diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java index fe24a8ec458e..2d8e6d767fb5 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java @@ -109,7 +109,6 @@ abstract class ServerRpcConnection implements Closeable { protected BlockingService service; protected SaslServerAuthenticationProvider provider; -// protected AuthMethod authMethod; protected boolean saslContextEstablished; protected boolean skipInitialSaslHandshake; private ByteBuffer unwrappedData; @@ -163,7 +162,9 @@ public VersionInfo getVersionInfo() { private String getFatalConnectionString(final int version, final byte authByte) { return "serverVersion=" + RpcServer.CURRENT_VERSION + ", clientVersion=" + version + ", authMethod=" + authByte + - ", authName=" + provider.getSaslAuthMethod().getName() + " from " + toString(); + // The provider may be null if we failed to parse the header of the request + ", authName=" + (provider == null ? "unknown" : provider.getSaslAuthMethod().getName()) + + " from " + toString(); } /** diff --git a/hbase-server/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslServerAuthenticationProvider b/hbase-server/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslServerAuthenticationProvider index 50abdb7cb2ed..30c648ced0c3 100644 --- a/hbase-server/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslServerAuthenticationProvider +++ b/hbase-server/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslServerAuthenticationProvider @@ -13,7 +13,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - org.apache.hadoop.hbase.security.provider.DigestSaslServerAuthenticationProvider org.apache.hadoop.hbase.security.provider.GssSaslServerAuthenticationProvider org.apache.hadoop.hbase.security.provider.SimpleSaslServerAuthenticationProvider \ No newline at end of file From e07e2735ed385c6886ef09aab1a74a45620ffd16 Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Thu, 5 Dec 2019 15:34:21 -0500 Subject: [PATCH 11/41] Fix javadoc --- .../hadoop/hbase/ipc/BlockingRpcConnection.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java index 711c5126a77c..888a4d7e5272 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java @@ -54,6 +54,7 @@ import org.apache.hadoop.hbase.security.HBaseSaslRpcClient; import org.apache.hadoop.hbase.security.SaslUtil; import org.apache.hadoop.hbase.security.SaslUtil.QualityOfProtection; +import org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider; import org.apache.hadoop.hbase.trace.TraceUtil; import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; import org.apache.hadoop.hbase.util.ExceptionUtil; @@ -376,11 +377,11 @@ private boolean setupSaslConnection(final InputStream in2, final OutputStream ou * connection again. The other problem is to do with ticket expiry. To handle that, a relogin is * attempted. *

- * The retry logic is governed by the {@link #shouldAuthenticateOverKrb} method. In case when the - * user doesn't have valid credentials, we don't need to retry (from cache or ticket). In such - * cases, it is prudent to throw a runtime exception when we receive a SaslException from the - * underlying authentication implementation, so there is no retry from other high level (for eg, - * HCM or HBaseAdmin). + * The retry logic is governed by the {@link SaslClientAuthenticationProvider#isKerberos()} + * method. In case when the user doesn't have valid credentials, we don't need to retry (from + * cache or ticket). In such cases, it is prudent to throw a runtime exception when we receive a + * SaslException from the underlying authentication implementation, so there is no retry from + * other high level (for eg, HCM or HBaseAdmin). *

*/ private void handleSaslConnectionFailure(final int currRetries, final int maxRetries, From 1801b00766c6d051d252076bd0bef83345367258 Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Thu, 5 Dec 2019 20:44:21 -0500 Subject: [PATCH 12/41] Get rid of isKerberos, introducing canRetry() and relogin() methods The logic is still kind of weird, but I think an improvement. --- .../hbase/ipc/BlockingRpcConnection.java | 81 ++++++++++--------- .../hadoop/hbase/ipc/NettyRpcConnection.java | 4 +- .../hadoop/hbase/ipc/RpcConnection.java | 8 -- ...tractSaslClientAuthenticationProvider.java | 12 --- ...igestSaslClientAuthenticationProvider.java | 5 -- .../GssSaslClientAuthenticationProvider.java | 23 +++++- .../SaslClientAuthenticationProvider.java | 26 ++++-- ...impleSaslClientAuthenticationProvider.java | 5 -- .../TestCustomSaslAuthenticationProvider.java | 5 -- 9 files changed, 86 insertions(+), 83 deletions(-) diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java index 888a4d7e5272..a9e87456e1b3 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java @@ -377,11 +377,10 @@ private boolean setupSaslConnection(final InputStream in2, final OutputStream ou * connection again. The other problem is to do with ticket expiry. To handle that, a relogin is * attempted. *

- * The retry logic is governed by the {@link SaslClientAuthenticationProvider#isKerberos()} - * method. In case when the user doesn't have valid credentials, we don't need to retry (from - * cache or ticket). In such cases, it is prudent to throw a runtime exception when we receive a - * SaslException from the underlying authentication implementation, so there is no retry from - * other high level (for eg, HCM or HBaseAdmin). + * The retry logic is governed by the {@link SaslClientAuthenticationProvider#canRetry()} + * method. Some providers have the ability to obtain new credentials and then re-attempt to + * authenticate with HBase services. Other providers will continue to fail if they failed the + * first time -- for those, we want to fail-fast. *

*/ private void handleSaslConnectionFailure(final int currRetries, final int maxRetries, @@ -391,41 +390,49 @@ private void handleSaslConnectionFailure(final int currRetries, final int maxRet user.doAs(new PrivilegedExceptionAction() { @Override public Object run() throws IOException, InterruptedException { - if (provider.isKerberos()) { - if (currRetries < maxRetries) { - if (LOG.isDebugEnabled()) { - LOG.debug("Exception encountered while connecting to " + - "the server : " + StringUtils.stringifyException(ex)); - } - // try re-login - relogin(); - disposeSasl(); - // have granularity of milliseconds - // we are sleeping with the Connection lock held but since this - // connection instance is being used for connecting to the server - // in question, it is okay - Thread.sleep(ThreadLocalRandom.current().nextInt(reloginMaxBackoff) + 1); - return null; - } else { - String msg = "Couldn't setup connection for " - + UserGroupInformation.getLoginUser().getUserName() + " to " - + securityInfo.getServerPrincipal(); - LOG.warn(msg, ex); - throw new IOException(msg, ex); + // A provider which failed authentication, but doesn't have the ability to relogin with + // some external system (e.g. username/password, the password either works or it doesn't) + if (!provider.canRetry()) { + LOG.warn("Exception encountered while connecting to the server : " + ex); + if (ex instanceof RemoteException) { + throw (RemoteException) ex; } - } else { - LOG.warn("Exception encountered while connecting to " + "the server : " + ex); - } - if (ex instanceof RemoteException) { - throw (RemoteException) ex; + if (ex instanceof SaslException) { + String msg = "SASL authentication failed." + + " The most likely cause is missing or invalid credentials. Consider 'kinit'."; + LOG.error(HBaseMarkers.FATAL, msg, ex); + throw new RuntimeException(msg, ex); + } + throw new IOException(ex); } - if (ex instanceof SaslException) { - String msg = "SASL authentication failed." - + " The most likely cause is missing or invalid credentials." + " Consider 'kinit'."; - LOG.error(HBaseMarkers.FATAL, msg, ex); - throw new RuntimeException(msg, ex); + + // Other providers, like kerberos, could request a new ticket from a keytab. Let + // them try again. + if (currRetries < maxRetries) { + if (LOG.isDebugEnabled()) { + LOG.debug("Exception encountered while connecting to " + + "the server : " + StringUtils.stringifyException(ex)); + } + + // Invoke the provider to perform the relogin + provider.relogin(); + + // Get rid of any old state on the SaslClient + disposeSasl(); + + // have granularity of milliseconds + // we are sleeping with the Connection lock held but since this + // connection instance is being used for connecting to the server + // in question, it is okay + Thread.sleep(ThreadLocalRandom.current().nextInt(reloginMaxBackoff) + 1); + return null; + } else { + String msg = "Couldn't setup connection for " + + UserGroupInformation.getLoginUser().getUserName() + " to " + + securityInfo.getServerPrincipal(); + LOG.warn(msg, ex); + throw new IOException(msg, ex); } - throw new IOException(ex); } }); } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/NettyRpcConnection.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/NettyRpcConnection.java index 5b8deaac5542..b7843054ceac 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/NettyRpcConnection.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/NettyRpcConnection.java @@ -159,8 +159,8 @@ private void scheduleRelogin(Throwable error) { @Override public void run() { try { - if (provider.isKerberos()) { - relogin(); + if (provider.canRetry()) { + provider.relogin(); } } catch (IOException e) { LOG.warn("Relogin failed", e); diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java index 8362bbf84e00..a530e17ba17a 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java @@ -126,14 +126,6 @@ protected RpcConnection(Configuration conf, HashedWheelTimer timeoutTimer, Conne this.remoteId = remoteId; } - protected void relogin() throws IOException { - if (UserGroupInformation.isLoginKeytabBased()) { - UserGroupInformation.getLoginUser().reloginFromKeytab(); - } else { - UserGroupInformation.getLoginUser().reloginFromTicketCache(); - } - } - protected void scheduleTimeoutTask(final Call call) { if (call.timeout > 0) { call.timeoutTask = timeoutTimer.newTimeout(new TimerTask() { diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AbstractSaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AbstractSaslClientAuthenticationProvider.java index 7e1ff427375d..d7b07db1d9eb 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AbstractSaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AbstractSaslClientAuthenticationProvider.java @@ -19,7 +19,6 @@ import org.apache.hadoop.hbase.HBaseInterfaceAudience; import org.apache.hadoop.io.Text; -import org.apache.hadoop.security.UserGroupInformation; import org.apache.yetus.audience.InterfaceAudience; import org.apache.yetus.audience.InterfaceStability; @@ -58,15 +57,4 @@ public final boolean equals(Object o) { } return false; } - - @Override - public UserGroupInformation unwrapUgi(UserGroupInformation ugi) { - // Unwrap the UGI with the real user when we're using Kerberos auth - if (isKerberos() && ugi != null && ugi.getRealUser() != null) { - return ugi.getRealUser(); - } - - // Otherwise, use the UGI we were given - return ugi; - } } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java index ef970efc3ac6..67e01d54c430 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java @@ -127,9 +127,4 @@ public UserInformation getUserInfo(UserGroupInformation user) { // Don't send user for token auth. Copied from RpcConnection. return null; } - - @Override - public boolean isKerberos() { - return false; - } } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java index 96625e6201c4..6557fb5150c8 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java @@ -87,7 +87,28 @@ public UserInformation getUserInfo(UserGroupInformation user) { } @Override - public boolean isKerberos() { + public boolean canRetry() { return true; } + + @Override + public void relogin() throws IOException { + // Check if UGI thinks we need to do another login + if (UserGroupInformation.isLoginKeytabBased()) { + UserGroupInformation.getLoginUser().reloginFromKeytab(); + } else { + UserGroupInformation.getLoginUser().reloginFromTicketCache(); + } + } + + @Override + public UserGroupInformation unwrapUgi(UserGroupInformation ugi) { + // Unwrap the UGI with the real user when we're using Kerberos auth + if (ugi != null && ugi.getRealUser() != null) { + return ugi.getRealUser(); + } + + // Otherwise, use the UGI we were given + return ugi; + } } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProvider.java index 4ff680dea4ce..20f6ad26fce1 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProvider.java @@ -26,6 +26,7 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseInterfaceAudience; import org.apache.hadoop.hbase.security.SecurityInfo; +import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; import org.apache.hadoop.io.Text; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.Token; @@ -33,8 +34,6 @@ import org.apache.yetus.audience.InterfaceAudience; import org.apache.yetus.audience.InterfaceStability; -import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; - /** * Encapsulation of client-side logic to authenticate to HBase via some means over SASL. * Implementations should not directly implement this interface, but instead extend @@ -69,15 +68,26 @@ SaslClient createClient(Configuration conf, InetAddress serverAddr, SecurityInfo */ UserInformation getUserInfo(UserGroupInformation user); - /** - * Returns true if this provider is based on Kerberos. False, otherwise. - */ - boolean isKerberos(); - /** * Performs any necessary unwrapping of a "proxy" user UGI which is executing some request * on top of a "real" user. This operation may simply return the original UGI if that is * what is appropriate for the given implementation. */ - UserGroupInformation unwrapUgi(UserGroupInformation ugi); + default UserGroupInformation unwrapUgi(UserGroupInformation ugi) { + return ugi; + } + + /** + * Returns true if the implementation is capable of performing some action which may allow a + * failed authentication to become a successful authentication. Otherwise, returns false + */ + default boolean canRetry() { + return false; + } + + /** + * Executes any necessary logic to re-login the client. Not all implementations will have + * any logic that needs to be executed. + */ + default void relogin() throws IOException {} } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslClientAuthenticationProvider.java index 31b916697980..c53c26ed0e21 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslClientAuthenticationProvider.java @@ -60,11 +60,6 @@ public UserInformation getUserInfo(UserGroupInformation user) { return userInfoPB.build(); } - @Override - public boolean isKerberos() { - return false; - } - @Override public SaslAuthMethod getSaslAuthMethod() { return SASL_AUTH_METHOD; diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java index 1f7bf30f7266..bf45e36e50d6 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java @@ -265,11 +265,6 @@ public void handle(Callback[] callbacks) throws UnsupportedCallbackException { public UserInformation getUserInfo(UserGroupInformation user) { return null; } - - @Override - public boolean isKerberos() { - return false; - } } /** From 1b57e3e2fa2e088d6286d207b8f7f066d0b39c64 Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Thu, 5 Dec 2019 20:46:50 -0500 Subject: [PATCH 13/41] Re-fix chekcstyle and javadoc --- .../hadoop/hbase/security/AbstractHBaseSaslRpcClient.java | 6 ++++-- .../provider/GssSaslClientAuthenticationProvider.java | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/AbstractHBaseSaslRpcClient.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/AbstractHBaseSaslRpcClient.java index 339ea143d4dd..44acf8a48639 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/AbstractHBaseSaslRpcClient.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/AbstractHBaseSaslRpcClient.java @@ -54,7 +54,8 @@ public abstract class AbstractHBaseSaslRpcClient { * @param conf the configuration object * @param provider the authentication provider * @param token token to use if needed by the authentication method - * @param serverPrincipal the server principal that we are trying to set the connection up to + * @param serverAddr the address of the hbase service + * @param securityInfo the security details for the remote hbase service * @param fallbackAllowed does the client allow fallback to simple authentication * @throws IOException */ @@ -70,7 +71,8 @@ protected AbstractHBaseSaslRpcClient(Configuration conf, * @param conf configuration object * @param provider the authentication provider * @param token token to use if needed by the authentication method - * @param serverPrincipal the server principal that we are trying to set the connection up to + * @param serverAddr the address of the hbase service + * @param securityInfo the security details for the remote hbase service * @param fallbackAllowed does the client allow fallback to simple authentication * @param rpcProtection the protection level ("authentication", "integrity" or "privacy") * @throws IOException diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java index 6557fb5150c8..07242788a3a1 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java @@ -28,7 +28,6 @@ import org.apache.hadoop.hbase.HBaseInterfaceAudience; import org.apache.hadoop.hbase.security.SaslUtil; import org.apache.hadoop.hbase.security.SecurityInfo; -import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; import org.apache.hadoop.security.SecurityUtil; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; @@ -39,6 +38,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; + @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) @InterfaceStability.Evolving public class GssSaslClientAuthenticationProvider extends AbstractSaslClientAuthenticationProvider { From d0981deff571303a307ec87bcc927031f9bdae08 Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Fri, 6 Dec 2019 12:49:58 -0500 Subject: [PATCH 14/41] Fix grammar on design doc --- dev-support/design-docs/HBASE-23347-pluggable-authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-support/design-docs/HBASE-23347-pluggable-authentication.md b/dev-support/design-docs/HBASE-23347-pluggable-authentication.md index 820f74a4ee23..9aa8a8580c78 100644 --- a/dev-support/design-docs/HBASE-23347-pluggable-authentication.md +++ b/dev-support/design-docs/HBASE-23347-pluggable-authentication.md @@ -33,7 +33,7 @@ today via the configuration property `hbase.security.authentication` 3. `TOKEN` `SIMPLE` authentication is effectively no authentication; HBase assumes the user -is who they claim to be. `KERBEROS` uses authenticates clients via the KerberosV5 +is who they claim to be. `KERBEROS` authenticates clients via the KerberosV5 protocol using the GSSAPI mechanism of the Java Simple Authentication and Security Layer (SASL) protocol. `TOKEN` is a username-password based authentication protocol which uses short-lived passwords that can only be obtained via a `KERBEROS` authenticated From 37b3b93687ecc72250330d3c774ae048a27b6a4c Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Fri, 6 Dec 2019 12:50:08 -0500 Subject: [PATCH 15/41] Make DefaultProviderSelector only initializable once Also makes unexpected input a hard-failure, rather than just a warning. --- .../provider/DefaultProviderSelector.java | 41 ++++++------ .../provider/TestDefaultProviderSelector.java | 62 +++++++++++++++++++ 2 files changed, 84 insertions(+), 19 deletions(-) create mode 100644 hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestDefaultProviderSelector.java diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DefaultProviderSelector.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DefaultProviderSelector.java index 2e1083b9d56d..3808950a6d9e 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DefaultProviderSelector.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DefaultProviderSelector.java @@ -18,6 +18,7 @@ package org.apache.hadoop.hbase.security.provider; import java.util.Map; +import java.util.Objects; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.security.User; @@ -38,38 +39,41 @@ public class DefaultProviderSelector implements AuthenticationProviderSelector { SimpleSaslClientAuthenticationProvider simpleAuth = null; GssSaslClientAuthenticationProvider krbAuth = null; DigestSaslClientAuthenticationProvider digestAuth = null; - Map providers; @Override - public void configure(Configuration conf, Map providers) { - this.conf = conf; - this.providers = providers; - for (SaslClientAuthenticationProvider provider : providers.values()) { + public synchronized void configure( + Configuration conf, Map providers) { + if (this.conf != null) { + throw new IllegalStateException("configure() should only be called once"); + } + this.conf = Objects.requireNonNull(conf); + for (SaslClientAuthenticationProvider provider : Objects.requireNonNull(providers).values()) { if (provider instanceof SimpleSaslClientAuthenticationProvider) { if (simpleAuth != null) { - LOG.warn("Ignoring duplicate SimpleSaslClientAuthenticationProvider: previous={}," - + " ignored={}", simpleAuth.getClass(), provider.getClass()); - } else { - simpleAuth = (SimpleSaslClientAuthenticationProvider) provider; + throw new IllegalStateException( + "Encountered multiple SimpleSaslClientAuthenticationProvider instances"); } + simpleAuth = (SimpleSaslClientAuthenticationProvider) provider; } else if (provider instanceof GssSaslClientAuthenticationProvider) { if (krbAuth != null) { - LOG.warn("Ignoring duplicate GssSaslClientAuthenticationProvider: previous={}," - + " ignored={}", krbAuth.getClass(), provider.getClass()); - } else { - krbAuth = (GssSaslClientAuthenticationProvider) provider; + throw new IllegalStateException( + "Encountered multiple GssSaslClientAuthenticationProvider instances"); } + krbAuth = (GssSaslClientAuthenticationProvider) provider; } else if (provider instanceof DigestSaslClientAuthenticationProvider) { if (digestAuth != null) { - LOG.warn("Ignoring duplicate DigestSaslClientAuthenticationProvider: previous={}," - + " ignored={}", digestAuth.getClass(), provider.getClass()); - } else { - digestAuth = (DigestSaslClientAuthenticationProvider) provider; + throw new IllegalStateException( + "Encountered multiple DigestSaslClientAuthenticationProvider instances"); } + digestAuth = (DigestSaslClientAuthenticationProvider) provider; } else { LOG.warn("Ignoring unknown SaslClientAuthenticationProvider: {}", provider.getClass()); } } + if (simpleAuth == null || krbAuth == null || digestAuth == null) { + throw new IllegalStateException("Failed to load SIMPLE, KERBEROS, and DIGEST authentication " + + "providers. Classpath is not sane."); + } } @Override @@ -97,8 +101,7 @@ public Pair> if (ugi.hasKerberosCredentials()) { return new Pair<>(krbAuth, null); } - LOG.warn("No matching SASL authentication provider and supporting token found from providers {}" - + " to HBase cluster {}", providers, clusterId); + LOG.warn("No matching SASL authentication provider and supporting token found from providers."); return null; } diff --git a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestDefaultProviderSelector.java b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestDefaultProviderSelector.java new file mode 100644 index 000000000000..ba406dce8fcd --- /dev/null +++ b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestDefaultProviderSelector.java @@ -0,0 +1,62 @@ +package org.apache.hadoop.hbase.security.provider; + +import static org.junit.Assert.assertNotNull; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.hadoop.conf.Configuration; +import org.junit.Before; +import org.junit.Test; + +public class TestDefaultProviderSelector { + + DefaultProviderSelector selector; + @Before + public void setup() { + selector = new DefaultProviderSelector(); + } + + + @Test(expected = IllegalStateException.class) + public void testExceptionOnMissingProviders() { + selector.configure(new Configuration(false), Collections.emptyMap()); + } + + @Test(expected = NullPointerException.class) + public void testNullConfiguration() { + selector.configure(null, Collections.emptyMap()); + } + + @Test(expected = NullPointerException.class) + public void testNullProviderMap() { + selector.configure(new Configuration(false), null); + } + + @Test(expected = IllegalStateException.class) + public void testDuplicateProviders() { + Map providers = new HashMap<>(); + providers.put((byte) 1, new SimpleSaslClientAuthenticationProvider()); + providers.put((byte) 2, new SimpleSaslClientAuthenticationProvider()); + selector.configure(new Configuration(false), providers); + } + + @Test + public void testExpectedProviders() { + Map providers = new HashMap<>(); + + for (SaslClientAuthenticationProvider provider : Arrays.asList( + new SimpleSaslClientAuthenticationProvider(), new GssSaslClientAuthenticationProvider(), + new DigestSaslClientAuthenticationProvider())) { + providers.put(provider.getSaslAuthMethod().getCode(), provider); + } + + selector.configure(new Configuration(false), providers); + + assertNotNull("Simple provider was null", selector.simpleAuth); + assertNotNull("Kerberos provider was null", selector.krbAuth); + assertNotNull("Digest provider was null", selector.digestAuth); + } +} From a4d01990e1f5d610532aded5b113b8119b27d7c7 Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Fri, 6 Dec 2019 15:57:15 -0500 Subject: [PATCH 16/41] Break apart the client/server authP interfaces from each other Common SaslAuthenticationProvider interface encapsulates generic logic that applies to both client and server, with separate SaslClientAP and SaslServerAP interfaces to prevent client from polluting server, and vice versa. --- .../AuthenticationProviderSelector.java | 2 +- .../BuiltInSaslAuthenticationProvider.java | 38 +++++++++++++++ .../DigestSaslAuthenticationProvider.java | 40 ++++++++++++++++ ...igestSaslClientAuthenticationProvider.java | 16 +------ .../GssSaslAuthenticationProvider.java | 39 ++++++++++++++++ .../GssSaslClientAuthenticationProvider.java | 16 ++----- .../provider/SaslAuthenticationProvider.java | 46 +++++++++++++++++++ .../SaslClientAuthenticationProvider.java | 16 ++----- .../SimpleSaslAuthenticationProvider.java | 38 +++++++++++++++ ...impleSaslClientAuthenticationProvider.java | 10 +--- ...igestSaslServerAuthenticationProvider.java | 2 +- .../GssSaslServerAuthenticationProvider.java | 2 +- .../SaslServerAuthenticationProvider.java | 2 +- ...impleSaslServerAuthenticationProvider.java | 2 +- 14 files changed, 216 insertions(+), 53 deletions(-) create mode 100644 hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInSaslAuthenticationProvider.java create mode 100644 hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslAuthenticationProvider.java create mode 100644 hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslAuthenticationProvider.java create mode 100644 hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslAuthenticationProvider.java create mode 100644 hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslAuthenticationProvider.java diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java index 5bb011b77ae2..69afec787914 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java @@ -1,4 +1,4 @@ -/** +/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInSaslAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInSaslAuthenticationProvider.java new file mode 100644 index 000000000000..7ee435abcae3 --- /dev/null +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInSaslAuthenticationProvider.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.security.provider; + +import org.apache.hadoop.hbase.HBaseInterfaceAudience; +import org.apache.hadoop.io.Text; +import org.apache.yetus.audience.InterfaceAudience; +import org.apache.yetus.audience.InterfaceStability; + +/** + * Base class for all Apache HBase, built-in {@link SaslAuthenticationProvider}'s to extend. + */ +@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) +@InterfaceStability.Evolving +public abstract class BuiltInSaslAuthenticationProvider implements SaslAuthenticationProvider { + + public static final Text AUTH_TOKEN_TYPE = new Text("HBASE_AUTH_TOKEN"); + + @Override + public Text getTokenKind() { + return AUTH_TOKEN_TYPE; + } +} diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslAuthenticationProvider.java new file mode 100644 index 000000000000..980a0c89d720 --- /dev/null +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslAuthenticationProvider.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.security.provider; + +import org.apache.hadoop.hbase.HBaseInterfaceAudience; +import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; +import org.apache.yetus.audience.InterfaceAudience; +import org.apache.yetus.audience.InterfaceStability; + +/** + * Base client for client/server implementations for the HBase delegation token auth'n method. + */ +@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) +@InterfaceStability.Evolving +public class DigestSaslAuthenticationProvider extends BuiltInSaslAuthenticationProvider { + + static final String MECHANISM = "DIGEST-MD5"; + static final SaslAuthMethod SASL_AUTH_METHOD = new SaslAuthMethod( + "DIGEST", (byte)82, MECHANISM, AuthenticationMethod.TOKEN); + + @Override + public SaslAuthMethod getSaslAuthMethod() { + return SASL_AUTH_METHOD; + } +} diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java index 67e01d54c430..79076f750168 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java @@ -36,7 +36,6 @@ import org.apache.hadoop.hbase.security.SaslUtil; import org.apache.hadoop.hbase.security.SecurityInfo; import org.apache.hadoop.security.UserGroupInformation; -import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; import org.apache.yetus.audience.InterfaceAudience; @@ -48,16 +47,10 @@ @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) @InterfaceStability.Evolving -public class DigestSaslClientAuthenticationProvider extends - AbstractSaslClientAuthenticationProvider { +public class DigestSaslClientAuthenticationProvider extends DigestSaslAuthenticationProvider + implements SaslClientAuthenticationProvider { private static final String MECHANISM = "DIGEST-MD5"; - private static final SaslAuthMethod SASL_AUTH_METHOD = new SaslAuthMethod( - "DIGEST", (byte)82, MECHANISM, AuthenticationMethod.TOKEN); - - public static String getMechanism() { - return MECHANISM; - } @Override public SaslClient createClient(Configuration conf, InetAddress serverAddr, @@ -67,11 +60,6 @@ public SaslClient createClient(Configuration conf, InetAddress serverAddr, SaslUtil.SASL_DEFAULT_REALM, saslProps, new DigestSaslClientCallbackHandler(token)); } - @Override - public SaslAuthMethod getSaslAuthMethod() { - return SASL_AUTH_METHOD; - } - public static class DigestSaslClientCallbackHandler implements CallbackHandler { private static final Logger LOG = LoggerFactory.getLogger(DigestSaslClientCallbackHandler.class); diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslAuthenticationProvider.java new file mode 100644 index 000000000000..509163821a5d --- /dev/null +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslAuthenticationProvider.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.security.provider; + +import org.apache.hadoop.hbase.HBaseInterfaceAudience; +import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; +import org.apache.yetus.audience.InterfaceAudience; +import org.apache.yetus.audience.InterfaceStability; + +/** + * Base client for client/server implementations for the "KERBEROS" HBase auth'n method. + */ +@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) +@InterfaceStability.Evolving +public class GssSaslAuthenticationProvider extends BuiltInSaslAuthenticationProvider { + + static final SaslAuthMethod SASL_AUTH_METHOD = new SaslAuthMethod( + "KERBEROS", (byte)81, "GSSAPI", AuthenticationMethod.KERBEROS); + + @Override + public SaslAuthMethod getSaslAuthMethod() { + return SASL_AUTH_METHOD; + } +} diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java index 07242788a3a1..e50c04d9ab7e 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java @@ -30,7 +30,6 @@ import org.apache.hadoop.hbase.security.SecurityInfo; import org.apache.hadoop.security.SecurityUtil; import org.apache.hadoop.security.UserGroupInformation; -import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; import org.apache.yetus.audience.InterfaceAudience; @@ -42,12 +41,10 @@ @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) @InterfaceStability.Evolving -public class GssSaslClientAuthenticationProvider extends AbstractSaslClientAuthenticationProvider { +public class GssSaslClientAuthenticationProvider extends GssSaslAuthenticationProvider + implements SaslClientAuthenticationProvider { private static final Logger LOG = LoggerFactory.getLogger( GssSaslClientAuthenticationProvider.class); - private static final String MECHANISM = "GSSAPI"; - private static final SaslAuthMethod SASL_AUTH_METHOD = new SaslAuthMethod( - "KERBEROS", (byte)81, MECHANISM, AuthenticationMethod.KERBEROS); String getServerPrincipal(Configuration conf, SecurityInfo securityInfo, InetAddress server) throws IOException { @@ -70,13 +67,8 @@ public SaslClient createClient(Configuration conf, InetAddress serverAddr, throw new IOException("Kerberos principal '" + serverPrincipal + "' does not have the expected format"); } - return Sasl.createSaslClient(new String[] { MECHANISM }, null, names[0], names[1], saslProps, - null); - } - - @Override - public SaslAuthMethod getSaslAuthMethod() { - return SASL_AUTH_METHOD; + return Sasl.createSaslClient(new String[] { getSaslAuthMethod().getSaslMechanism() }, null, + names[0], names[1], saslProps, null); } @Override diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslAuthenticationProvider.java new file mode 100644 index 000000000000..72a01b318d75 --- /dev/null +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslAuthenticationProvider.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.security.provider; + +import org.apache.hadoop.hbase.HBaseInterfaceAudience; +import org.apache.hadoop.io.Text; +import org.apache.yetus.audience.InterfaceAudience; +import org.apache.yetus.audience.InterfaceStability; + +/** + * Encapsulation of client-side logic to authenticate to HBase via some means over SASL. + * Implementations should not directly implement this interface, but instead extend + * {@link AbstractSaslClientAuthenticationProvider}. + * + * Implementations of this interface must make an implementation of {@code hashCode()} + * which returns the same value across multiple instances of the provider implementation. + */ +@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) +@InterfaceStability.Evolving +public interface SaslAuthenticationProvider { + + /** + * Returns the attributes which identify how this provider authenticates. + */ + SaslAuthMethod getSaslAuthMethod(); + + /** + * Returns the name of the type used by the TokenIdentifier. + */ + Text getTokenKind(); +} diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProvider.java index 20f6ad26fce1..6709f0a4fd76 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProvider.java @@ -26,14 +26,14 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseInterfaceAudience; import org.apache.hadoop.hbase.security.SecurityInfo; -import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; -import org.apache.hadoop.io.Text; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; import org.apache.yetus.audience.InterfaceAudience; import org.apache.yetus.audience.InterfaceStability; +import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; + /** * Encapsulation of client-side logic to authenticate to HBase via some means over SASL. * Implementations should not directly implement this interface, but instead extend @@ -44,7 +44,7 @@ */ @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) @InterfaceStability.Evolving -public interface SaslClientAuthenticationProvider { +public interface SaslClientAuthenticationProvider extends SaslAuthenticationProvider { /** * Creates the SASL client instance for this auth'n method. @@ -53,16 +53,6 @@ SaslClient createClient(Configuration conf, InetAddress serverAddr, SecurityInfo Token token, boolean fallbackAllowed, Map saslProps) throws IOException; - /** - * Returns the attributes which identify how this provider authenticates. - */ - SaslAuthMethod getSaslAuthMethod(); - - /** - * Returns the name of the type used by the TokenIdentifier. - */ - Text getTokenKind(); - /** * Constructs a {@link UserInformation} from the given {@link UserGroupInformation} */ diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslAuthenticationProvider.java new file mode 100644 index 000000000000..85c8caec6199 --- /dev/null +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslAuthenticationProvider.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.security.provider; + +import org.apache.hadoop.hbase.HBaseInterfaceAudience; +import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; +import org.apache.yetus.audience.InterfaceAudience; +import org.apache.yetus.audience.InterfaceStability; + +/** + * Base client for client/server implementations for the "SIMPLE" HBase auth'n method. + */ +@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) +@InterfaceStability.Evolving +public class SimpleSaslAuthenticationProvider extends BuiltInSaslAuthenticationProvider { + private static final SaslAuthMethod SASL_AUTH_METHOD = new SaslAuthMethod( + "SIMPLE", (byte)80, "", AuthenticationMethod.SIMPLE); + + @Override + public SaslAuthMethod getSaslAuthMethod() { + return SASL_AUTH_METHOD; + } +} diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslClientAuthenticationProvider.java index c53c26ed0e21..e885a0cc2484 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslClientAuthenticationProvider.java @@ -27,7 +27,6 @@ import org.apache.hadoop.hbase.HBaseInterfaceAudience; import org.apache.hadoop.hbase.security.SecurityInfo; import org.apache.hadoop.security.UserGroupInformation; -import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; import org.apache.yetus.audience.InterfaceAudience; @@ -38,9 +37,7 @@ @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) @InterfaceStability.Evolving public class SimpleSaslClientAuthenticationProvider extends - AbstractSaslClientAuthenticationProvider { - private static final SaslAuthMethod SASL_AUTH_METHOD = new SaslAuthMethod( - "SIMPLE", (byte)80, "", AuthenticationMethod.SIMPLE); + SimpleSaslAuthenticationProvider implements SaslClientAuthenticationProvider { @Override public SaslClient createClient(Configuration conf, InetAddress serverAddress, @@ -59,9 +56,4 @@ public UserInformation getUserInfo(UserGroupInformation user) { } return userInfoPB.build(); } - - @Override - public SaslAuthMethod getSaslAuthMethod() { - return SASL_AUTH_METHOD; - } } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java index cdcc0b190eac..16bec7237c8c 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java @@ -45,7 +45,7 @@ @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) @InterfaceStability.Evolving -public class DigestSaslServerAuthenticationProvider extends DigestSaslClientAuthenticationProvider +public class DigestSaslServerAuthenticationProvider extends DigestSaslAuthenticationProvider implements SaslServerAuthenticationProvider { private static final Logger LOG = LoggerFactory.getLogger( DigestSaslServerAuthenticationProvider.class); diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java index 48cc379745dc..86100a36719d 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java @@ -42,7 +42,7 @@ @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) @InterfaceStability.Evolving -public class GssSaslServerAuthenticationProvider extends GssSaslClientAuthenticationProvider +public class GssSaslServerAuthenticationProvider extends GssSaslAuthenticationProvider implements SaslServerAuthenticationProvider { private static final Logger LOG = LoggerFactory.getLogger( GssSaslServerAuthenticationProvider.class); diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProvider.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProvider.java index 13561f635868..babc9d2172cf 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProvider.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProvider.java @@ -35,7 +35,7 @@ */ @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) @InterfaceStability.Evolving -public interface SaslServerAuthenticationProvider extends SaslClientAuthenticationProvider { +public interface SaslServerAuthenticationProvider extends SaslAuthenticationProvider { /** * Creates the SaslServer to accept incoming SASL authentication requests. diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java index 5f41eccd8223..a1fde3db4068 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java @@ -31,7 +31,7 @@ @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) @InterfaceStability.Evolving -public class SimpleSaslServerAuthenticationProvider extends SimpleSaslClientAuthenticationProvider +public class SimpleSaslServerAuthenticationProvider extends SimpleSaslAuthenticationProvider implements SaslServerAuthenticationProvider { @Override From 223725bacd9ecf796caba5e75b5a8dbc9b4bb21b Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Fri, 6 Dec 2019 16:32:24 -0500 Subject: [PATCH 17/41] Updating design doc for last refactoring of the AP interfaces --- .../HBASE-23347-pluggable-authentication.md | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/dev-support/design-docs/HBASE-23347-pluggable-authentication.md b/dev-support/design-docs/HBASE-23347-pluggable-authentication.md index 9aa8a8580c78..3f66c704de68 100644 --- a/dev-support/design-docs/HBASE-23347-pluggable-authentication.md +++ b/dev-support/design-docs/HBASE-23347-pluggable-authentication.md @@ -127,14 +127,21 @@ allowing custom providers, we must also allow a custom selection logic such that correct provider can be chosen. This is a formalization of the logic already present in `org.apache.hadoop.hbase.security.token.AuthenticationTokenSelector`. -To enable the above, we have three new classes to support the user extensibility: - -1. `interface SaslClientAuthenticationProvider` -2. `interface SaslServerAuthenticationProvider extends SaslClientAuthenticationProvider` -3. `interface AuthenticationProviderSelector` - -Each Authentication Provider implementation is a singleton and is intended to be shared -across all RPCs. A provider selector is chosen per client, based on the given configuration. +To enable the above, we have some new interfaces to support the user extensibility: + +1. `interface SaslAuthenticationProvider` +2. `interface SaslClientAuthenticationProvider extends SaslAuthenticationProvider` +3. `interface SaslServerAuthenticationProvider extends SaslAuthenticationProvider` +4. `interface AuthenticationProviderSelector` + +The `SaslAuthenticationProvider` shares logic which is common to the client and the +server (though, this is up to the developer to guarantee this). The client and server +interfaces each have logic specific to the HBase RPC client and HBase RPC server +codbase, as their name implies. As described above, an implementation +of one `SaslClientAuthenticationProvider` must match exactly one implementation of +`SaslServerAuthenticationProvider`. Each Authentication Provider implementation is +a singleton and is intended to be shared across all RPCs. A provider selector is +chosen per client based on that client's configuration. A client authentication provider is uniquely identified among other providers by the following characteristics: From 32571b5e3334a70b3c7d3663599f0d17d5dc0b89 Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Mon, 9 Dec 2019 14:54:17 -0500 Subject: [PATCH 18/41] Findbugs, checkstyle, and license fixups --- .../hbase/ipc/BlockingRpcConnection.java | 2 +- .../provider/DefaultProviderSelector.java | 29 ++++++++++++++----- .../provider/TestDefaultProviderSelector.java | 18 +++++++++++- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java index a9e87456e1b3..f78aac3b0e9d 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java @@ -419,7 +419,7 @@ public Object run() throws IOException, InterruptedException { // Get rid of any old state on the SaslClient disposeSasl(); - + // have granularity of milliseconds // we are sleeping with the Connection lock held but since this // connection instance is being used for connecting to the server diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DefaultProviderSelector.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DefaultProviderSelector.java index 3808950a6d9e..6ebce8bbd196 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DefaultProviderSelector.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DefaultProviderSelector.java @@ -19,6 +19,8 @@ import java.util.Map; import java.util.Objects; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.security.User; @@ -34,6 +36,7 @@ @InterfaceAudience.Private public class DefaultProviderSelector implements AuthenticationProviderSelector { private static final Logger LOG = LoggerFactory.getLogger(DefaultProviderSelector.class); + private final ReadWriteLock LOCK = new ReentrantReadWriteLock(); Configuration conf; SimpleSaslClientAuthenticationProvider simpleAuth = null; @@ -41,12 +44,17 @@ public class DefaultProviderSelector implements AuthenticationProviderSelector { DigestSaslClientAuthenticationProvider digestAuth = null; @Override - public synchronized void configure( - Configuration conf, Map providers) { - if (this.conf != null) { - throw new IllegalStateException("configure() should only be called once"); + public void configure(Configuration conf, Map providers) { + try { + LOCK.writeLock().lock(); + if (this.conf != null) { + throw new IllegalStateException("configure() should only be called once"); + } + this.conf = Objects.requireNonNull(conf); + } finally { + LOCK.writeLock().unlock(); } - this.conf = Objects.requireNonNull(conf); + for (SaslClientAuthenticationProvider provider : Objects.requireNonNull(providers).values()) { if (provider instanceof SimpleSaslClientAuthenticationProvider) { if (simpleAuth != null) { @@ -82,9 +90,14 @@ public Pair> if (clusterId == null) { throw new NullPointerException("Null clusterId was given"); } - // Superfluous: we dont' do SIMPLE auth over SASL, but we should to simplify. - if (!User.isHBaseSecurityEnabled(conf)) { - return new Pair<>(simpleAuth, null); + try { + LOCK.readLock().lock(); + // Superfluous: we dont' do SIMPLE auth over SASL, but we should to simplify. + if (!User.isHBaseSecurityEnabled(conf)) { + return new Pair<>(simpleAuth, null); + } + } finally { + LOCK.readLock().unlock(); } // Must be digest auth, look for a token. // TestGenerateDelegationToken is written expecting DT is used when DT and Krb are both present. diff --git a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestDefaultProviderSelector.java b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestDefaultProviderSelector.java index ba406dce8fcd..0d490b16994b 100644 --- a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestDefaultProviderSelector.java +++ b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestDefaultProviderSelector.java @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.apache.hadoop.hbase.security.provider; import static org.junit.Assert.assertNotNull; @@ -18,7 +35,6 @@ public class TestDefaultProviderSelector { public void setup() { selector = new DefaultProviderSelector(); } - @Test(expected = IllegalStateException.class) public void testExceptionOnMissingProviders() { From 80fb68230115f35cb8e8728bd02b8b894b7bdf25 Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Mon, 9 Dec 2019 15:39:30 -0500 Subject: [PATCH 19/41] Throw an exception instead of returning a null SaslServer --- .../provider/SimpleSaslServerAuthenticationProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java index a1fde3db4068..082c22176189 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java @@ -37,7 +37,7 @@ public class SimpleSaslServerAuthenticationProvider extends SimpleSaslAuthentica @Override public SaslServer createServer(SecretManager secretManager, Map saslProps) throws IOException { - return null; + throw new RuntimeException("HBase SIMPLE authentication doesn't use SASL"); } @Override From 37038b39d878ec39c2bd47f43a589bffc57b7a94 Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Mon, 9 Dec 2019 15:39:49 -0500 Subject: [PATCH 20/41] Restore pulling the attemping-user log message for delegation token auth'n --- .../hadoop/hbase/ipc/ServerRpcConnection.java | 2 +- .../hbase/security/HBaseSaslRpcServer.java | 16 ++++-- .../AttemptingUserProvidingSaslServer.java | 53 +++++++++++++++++++ ...igestSaslServerAuthenticationProvider.java | 21 ++++++-- .../GssSaslServerAuthenticationProvider.java | 13 ++--- .../SaslServerAuthenticationProvider.java | 4 +- ...impleSaslServerAuthenticationProvider.java | 5 +- 7 files changed, 91 insertions(+), 23 deletions(-) create mode 100644 hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/AttemptingUserProvidingSaslServer.java diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java index 2d8e6d767fb5..6ffad5410efa 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java @@ -371,7 +371,7 @@ public void saslReadAndProcess(ByteBuff saslToken) throws IOException, String clientIP = this.toString(); // attempting user could be null RpcServer.AUDITLOG - .warn(RpcServer.AUTH_FAILED_FOR + clientIP + ":" + saslServer.getAttemptingUser()); + .warn("{} {}: {}", RpcServer.AUTH_FAILED_FOR, clientIP, saslServer.getAttemptingUser()); throw e; } if (replyToken != null) { diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcServer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcServer.java index f3b7327c2536..6189dc712857 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcServer.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcServer.java @@ -21,11 +21,13 @@ import java.io.DataInputStream; import java.io.IOException; import java.util.Map; +import java.util.Optional; import javax.security.sasl.Sasl; import javax.security.sasl.SaslException; import javax.security.sasl.SaslServer; +import org.apache.hadoop.hbase.security.provider.AttemptingUserProvidingSaslServer; import org.apache.hadoop.hbase.security.provider.SaslServerAuthenticationProvider; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.SecretManager; @@ -44,14 +46,14 @@ public class HBaseSaslRpcServer { private static final Logger LOG = LoggerFactory.getLogger(HBaseSaslRpcServer.class); + private final AttemptingUserProvidingSaslServer serverWithProvider; private final SaslServer saslServer; - private UserGroupInformation attemptingUser; // user name before auth - public HBaseSaslRpcServer(SaslServerAuthenticationProvider provider, Map saslProps, SecretManager secretManager) throws IOException { - saslServer = provider.createServer(secretManager, saslProps); + serverWithProvider = provider.createServer(secretManager, saslProps); + saslServer = serverWithProvider.getServer(); } public boolean isComplete() { @@ -67,8 +69,12 @@ public void dispose() { SaslUtil.safeDispose(saslServer); } - public UserGroupInformation getAttemptingUser() { - return attemptingUser; + public String getAttemptingUser() { + Optional optionalUser = serverWithProvider.getAttemptingUser(); + if (optionalUser.isPresent()) { + optionalUser.get().toString(); + } + return "Unknown"; } public byte[] wrap(byte[] buf, int off, int len) throws SaslException { diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/AttemptingUserProvidingSaslServer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/AttemptingUserProvidingSaslServer.java new file mode 100644 index 000000000000..9b45ded7c4f8 --- /dev/null +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/AttemptingUserProvidingSaslServer.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.security.provider; + +import java.util.Optional; +import java.util.function.Supplier; + +import javax.security.sasl.SaslServer; + +import org.apache.hadoop.hbase.HBaseInterfaceAudience; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.yetus.audience.InterfaceAudience; +import org.apache.yetus.audience.InterfaceStability; + +/** + * Wrapper around a SaslServer which provides the last user attempting to authenticate via SASL, + * if the server/mechanism allow figuring that out. + */ +@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) +@InterfaceStability.Evolving +public class AttemptingUserProvidingSaslServer { + private final Supplier producer; + private final SaslServer saslServer; + + public AttemptingUserProvidingSaslServer( + SaslServer saslServer, Supplier producer) { + this.saslServer = saslServer; + this.producer = producer; + } + + public SaslServer getServer() { + return saslServer; + } + + public Optional getAttemptingUser() { + return Optional.of(producer.get()); + } +} diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java index 16bec7237c8c..2baf3e39e590 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; @@ -50,22 +51,31 @@ public class DigestSaslServerAuthenticationProvider extends DigestSaslAuthentica private static final Logger LOG = LoggerFactory.getLogger( DigestSaslServerAuthenticationProvider.class); + private AtomicReference attemptingUser = new AtomicReference<>(null); + @Override - public SaslServer createServer(SecretManager secretManager, + public AttemptingUserProvidingSaslServer createServer( + SecretManager secretManager, Map saslProps) throws IOException { if (secretManager == null) { throw new AccessDeniedException("Server is not configured to do DIGEST authentication."); } - return Sasl.createSaslServer(getSaslAuthMethod().getSaslMechanism(), null, - SaslUtil.SASL_DEFAULT_REALM, saslProps, new SaslDigestCallbackHandler(secretManager)); + final SaslServer server = Sasl.createSaslServer(getSaslAuthMethod().getSaslMechanism(), null, + SaslUtil.SASL_DEFAULT_REALM, saslProps, + new SaslDigestCallbackHandler(secretManager, attemptingUser)); + + return new AttemptingUserProvidingSaslServer(server, () -> attemptingUser.get()); } /** CallbackHandler for SASL DIGEST-MD5 mechanism */ private static class SaslDigestCallbackHandler implements CallbackHandler { - private SecretManager secretManager; + private final SecretManager secretManager; + private final AtomicReference attemptingUser; - public SaslDigestCallbackHandler(SecretManager secretManager) { + public SaslDigestCallbackHandler(SecretManager secretManager, + AtomicReference attemptingUser) { this.secretManager = secretManager; + this.attemptingUser = attemptingUser; } private char[] getPassword(TokenIdentifier tokenid) throws InvalidToken { @@ -94,6 +104,7 @@ public void handle(Callback[] callbacks) throws InvalidToken, UnsupportedCallbac if (pc != null) { TokenIdentifier tokenIdentifier = HBaseSaslRpcServer.getIdentifier( nc.getDefaultName(), secretManager); + attemptingUser.set(tokenIdentifier.getUser()); char[] password = getPassword(tokenIdentifier); if (LOG.isTraceEnabled()) { LOG.trace("SASL server DIGEST-MD5 callback: setting password " + "for client: " + diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java index 86100a36719d..b921ae6baec9 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java @@ -27,7 +27,6 @@ import javax.security.sasl.AuthorizeCallback; import javax.security.sasl.Sasl; import javax.security.sasl.SaslException; -import javax.security.sasl.SaslServer; import org.apache.hadoop.hbase.HBaseInterfaceAudience; import org.apache.hadoop.hbase.security.AccessDeniedException; @@ -48,7 +47,8 @@ public class GssSaslServerAuthenticationProvider extends GssSaslAuthenticationPr GssSaslServerAuthenticationProvider.class); @Override - public SaslServer createServer(SecretManager secretManager, + public AttemptingUserProvidingSaslServer createServer( + SecretManager secretManager, Map saslProps) throws IOException { UserGroupInformation current = UserGroupInformation.getCurrentUser(); String fullName = current.getUserName(); @@ -59,11 +59,12 @@ public SaslServer createServer(SecretManager secretManager, "Kerberos principal does NOT contain an instance (hostname): " + fullName); } try { - return current.doAs(new PrivilegedExceptionAction() { + return current.doAs(new PrivilegedExceptionAction() { @Override - public SaslServer run() throws SaslException { - return Sasl.createSaslServer(getSaslAuthMethod().getSaslMechanism(), names[0], - names[1], saslProps, new SaslGssCallbackHandler()); + public AttemptingUserProvidingSaslServer run() throws SaslException { + return new AttemptingUserProvidingSaslServer(Sasl.createSaslServer( + getSaslAuthMethod().getSaslMechanism(), names[0], names[1], saslProps, + new SaslGssCallbackHandler()), () -> null); } }); } catch (InterruptedException e) { diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProvider.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProvider.java index babc9d2172cf..931c67986778 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProvider.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProvider.java @@ -20,8 +20,6 @@ import java.io.IOException; import java.util.Map; -import javax.security.sasl.SaslServer; - import org.apache.hadoop.hbase.HBaseInterfaceAudience; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.SecretManager; @@ -40,7 +38,7 @@ public interface SaslServerAuthenticationProvider extends SaslAuthenticationProv /** * Creates the SaslServer to accept incoming SASL authentication requests. */ - SaslServer createServer(SecretManager secretManager, + AttemptingUserProvidingSaslServer createServer(SecretManager secretManager, Map saslProps) throws IOException; boolean supportsProtocolAuthentication(); diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java index 082c22176189..0b9e89e7a352 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java @@ -20,8 +20,6 @@ import java.io.IOException; import java.util.Map; -import javax.security.sasl.SaslServer; - import org.apache.hadoop.hbase.HBaseInterfaceAudience; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.SecretManager; @@ -35,7 +33,8 @@ public class SimpleSaslServerAuthenticationProvider extends SimpleSaslAuthentica implements SaslServerAuthenticationProvider { @Override - public SaslServer createServer(SecretManager secretManager, + public AttemptingUserProvidingSaslServer createServer( + SecretManager secretManager, Map saslProps) throws IOException { throw new RuntimeException("HBase SIMPLE authentication doesn't use SASL"); } From 9fdb763efcd82a5189de37fe8883654c0f6cf233 Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Mon, 9 Dec 2019 16:27:58 -0500 Subject: [PATCH 21/41] Attempt to restore the fallback to simple authn path --- .../hadoop/hbase/ipc/ServerRpcConnection.java | 28 +++++++++++-------- .../SaslServerAuthenticationProviders.java | 15 ++++++++++ .../TestCustomSaslAuthenticationProvider.java | 10 ++++--- 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java index 6ffad5410efa..d49a9043e9a2 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java @@ -29,6 +29,7 @@ import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import java.security.GeneralSecurityException; +import java.util.Objects; import java.util.Properties; import org.apache.commons.crypto.cipher.CryptoCipherFactory; @@ -69,6 +70,7 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.io.BytesWritable; +import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Writable; import org.apache.hadoop.io.WritableUtils; import org.apache.hadoop.io.compress.CompressionCodec; @@ -739,8 +741,7 @@ protected final boolean processPreamble(ByteBuffer preambleBuffer) throws IOExce return false; } // TODO this is a wart while simple auth'n doesn't go through sasl. - if (this.rpcServer.isSecurityEnabled && - provider instanceof SimpleSaslServerAuthenticationProvider) { + if (this.rpcServer.isSecurityEnabled && isSimpleAuthentication()) { if (this.rpcServer.allowFallbackToSimpleAuth) { this.rpcServer.metrics.authenticationFallback(); authenticatedWithFallback = true; @@ -750,20 +751,23 @@ protected final boolean processPreamble(ByteBuffer preambleBuffer) throws IOExce return false; } } - // TODO can we remove this fallback? Is this even a good idea? -// if (!this.rpcServer.isSecurityEnabled && authMethod != AuthMethod.SIMPLE) { -// doRawSaslReply(SaslStatus.SUCCESS, new IntWritable(SaslUtil.SWITCH_TO_SIMPLE_AUTH), null, -// null); -// authMethod = AuthMethod.SIMPLE; -// // client has already sent the initial Sasl message and we -// // should ignore it. Both client and server should fall back -// // to simple auth from now on. -// skipInitialSaslHandshake = true; -// } + if (!this.rpcServer.isSecurityEnabled && !isSimpleAuthentication()) { + doRawSaslReply(SaslStatus.SUCCESS, new IntWritable(SaslUtil.SWITCH_TO_SIMPLE_AUTH), null, + null); + provider = saslProviders.getSimpleProvider(); + // client has already sent the initial Sasl message and we + // should ignore it. Both client and server should fall back + // to simple auth from now on. + skipInitialSaslHandshake = true; + } useSasl = !(provider instanceof SimpleSaslServerAuthenticationProvider); return true; } + boolean isSimpleAuthentication() { + return Objects.requireNonNull(provider) instanceof SimpleSaslServerAuthenticationProvider; + } + public abstract boolean isConnectionOpen(); public abstract ServerCall createCall(int id, BlockingService service, MethodDescriptor md, diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProviders.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProviders.java index bd4142f3ed20..8af75e1d6d90 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProviders.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProviders.java @@ -18,6 +18,7 @@ package org.apache.hadoop.hbase.security.provider; import java.util.HashMap; +import java.util.Optional; import java.util.ServiceLoader; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; @@ -161,4 +162,18 @@ static SaslServerAuthenticationProviders createProviders(Configuration conf) { public SaslServerAuthenticationProvider selectProvider(byte authByte) { return providers.get(Byte.valueOf(authByte)); } + + /** + * Extracts the SIMPLE authentication provider. + */ + public SaslServerAuthenticationProvider getSimpleProvider() { + Optional opt = providers.values() + .stream() + .filter((p) -> p instanceof SimpleSaslServerAuthenticationProvider) + .findFirst(); + if (!opt.isPresent()) { + throw new RuntimeException("SIMPLE authentication provider not available when it should be"); + } + return opt.get(); + } } diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java index bf45e36e50d6..74cf9cb8acf6 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java @@ -274,11 +274,13 @@ public static class InMemoryServerProvider extends InMemoryClientProvider implements SaslServerAuthenticationProvider { @Override - public SaslServer createServer(SecretManager secretManager, + public AttemptingUserProvidingSaslServer createServer( + SecretManager secretManager, Map saslProps) throws IOException { - return Sasl.createSaslServer(getSaslAuthMethod().getSaslMechanism(), null, - SaslUtil.SASL_DEFAULT_REALM, saslProps, - new InMemoryServerProviderCallbackHandler()); + return new AttemptingUserProvidingSaslServer( + Sasl.createSaslServer(getSaslAuthMethod().getSaslMechanism(), null, + SaslUtil.SASL_DEFAULT_REALM, saslProps, new InMemoryServerProviderCallbackHandler()), + () -> null); } /** From 041585393513f4029644f91c04dc7233f06ea875 Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Tue, 17 Dec 2019 14:56:01 -0500 Subject: [PATCH 22/41] (primarily) Address Reid's feedback. Trying to avoid non-functional changes in here and keep those separate (to potentially apply later or in a follow-on). --- .../HBASE-23347-pluggable-authentication.md | 2 +- .../hbase/ipc/BlockingRpcConnection.java | 2 +- .../AuthenticationProviderSelector.java | 8 ++- .../provider/DefaultProviderSelector.java | 51 +++++++-------- .../DigestSaslAuthenticationProvider.java | 5 +- .../GssSaslAuthenticationProvider.java | 2 +- .../provider/SaslAuthenticationProvider.java | 9 +-- .../SaslClientAuthenticationProviders.java | 35 +++++----- .../SimpleSaslAuthenticationProvider.java | 2 +- ....provider.SaslClientAuthenticationProvider | 2 +- .../security/TestHBaseSaslRpcClient.java | 6 +- .../provider/TestDefaultProviderSelector.java | 22 +++---- ...TestSaslClientAuthenticationProviders.java | 64 +++++++++++++++++++ .../AttemptingUserProvidingSaslServer.java | 2 +- ...igestSaslServerAuthenticationProvider.java | 26 +++++--- ....provider.SaslServerAuthenticationProvider | 3 +- .../hadoop/hbase/security/TestSecureIPC.java | 6 +- .../TestCustomSaslAuthenticationProvider.java | 8 +-- 18 files changed, 164 insertions(+), 91 deletions(-) diff --git a/dev-support/design-docs/HBASE-23347-pluggable-authentication.md b/dev-support/design-docs/HBASE-23347-pluggable-authentication.md index 3f66c704de68..88117f14478a 100644 --- a/dev-support/design-docs/HBASE-23347-pluggable-authentication.md +++ b/dev-support/design-docs/HBASE-23347-pluggable-authentication.md @@ -137,7 +137,7 @@ To enable the above, we have some new interfaces to support the user extensibili The `SaslAuthenticationProvider` shares logic which is common to the client and the server (though, this is up to the developer to guarantee this). The client and server interfaces each have logic specific to the HBase RPC client and HBase RPC server -codbase, as their name implies. As described above, an implementation +codebase, as their name implies. As described above, an implementation of one `SaslClientAuthenticationProvider` must match exactly one implementation of `SaslServerAuthenticationProvider`. Each Authentication Provider implementation is a singleton and is intended to be shared across all RPCs. A provider selector is diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java index f78aac3b0e9d..d0c370009e90 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java @@ -399,7 +399,7 @@ public Object run() throws IOException, InterruptedException { } if (ex instanceof SaslException) { String msg = "SASL authentication failed." - + " The most likely cause is missing or invalid credentials. Consider 'kinit'."; + + " The most likely cause is missing or invalid credentials."; LOG.error(HBaseMarkers.FATAL, msg, ex); throw new RuntimeException(msg, ex); } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java index 69afec787914..dee65a6cf872 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java @@ -17,6 +17,7 @@ */ package org.apache.hadoop.hbase.security.provider; +import java.util.Collection; import java.util.Map; import org.apache.hadoop.conf.Configuration; @@ -34,9 +35,12 @@ public interface AuthenticationProviderSelector { /** - * Initializes the implementation with configuration and a set of providers available. + * Initializes the implementation with configuration and a set of providers available. This method + * should be called exactly once per implementation prior to calling + * {@link #selectProvider(Text, UserGroupInformation)}. */ - void configure(Configuration conf, Map availableProviders); + void configure(Configuration conf, + Collection availableProviders); /** * Chooses the authentication provider which should be used given the provided client context diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DefaultProviderSelector.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DefaultProviderSelector.java index 6ebce8bbd196..56b426908a75 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DefaultProviderSelector.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DefaultProviderSelector.java @@ -17,10 +17,9 @@ */ package org.apache.hadoop.hbase.security.provider; -import java.util.Map; +import java.util.Collection; import java.util.Objects; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.Set; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.security.User; @@ -33,10 +32,19 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import net.jcip.annotations.NotThreadSafe; + +/** + * Default implementation of {@link AuthenticationProviderSelector} which can choose from: Simple + * authentication, Kerberos authentication, and Delegation Token authentication. + * + * This implementation is not thread-safe. {@link #configure(Configuration, Set)} and + * {@link #selectProvider(Text, UserGroupInformation)} is not safe if they are called concurrently. + */ @InterfaceAudience.Private +@NotThreadSafe public class DefaultProviderSelector implements AuthenticationProviderSelector { private static final Logger LOG = LoggerFactory.getLogger(DefaultProviderSelector.class); - private final ReadWriteLock LOCK = new ReentrantReadWriteLock(); Configuration conf; SimpleSaslClientAuthenticationProvider simpleAuth = null; @@ -44,31 +52,28 @@ public class DefaultProviderSelector implements AuthenticationProviderSelector { DigestSaslClientAuthenticationProvider digestAuth = null; @Override - public void configure(Configuration conf, Map providers) { - try { - LOCK.writeLock().lock(); - if (this.conf != null) { - throw new IllegalStateException("configure() should only be called once"); - } - this.conf = Objects.requireNonNull(conf); - } finally { - LOCK.writeLock().unlock(); + public void configure( + Configuration conf, Collection providers) { + if (this.conf != null) { + throw new IllegalStateException("configure() should only be called once"); } + this.conf = Objects.requireNonNull(conf); - for (SaslClientAuthenticationProvider provider : Objects.requireNonNull(providers).values()) { - if (provider instanceof SimpleSaslClientAuthenticationProvider) { + for (SaslClientAuthenticationProvider provider : Objects.requireNonNull(providers)) { + final String name = provider.getSaslAuthMethod().getName(); + if (SimpleSaslAuthenticationProvider.SASL_AUTH_METHOD.getName().contentEquals(name)) { if (simpleAuth != null) { throw new IllegalStateException( "Encountered multiple SimpleSaslClientAuthenticationProvider instances"); } simpleAuth = (SimpleSaslClientAuthenticationProvider) provider; - } else if (provider instanceof GssSaslClientAuthenticationProvider) { + } else if (GssSaslAuthenticationProvider.SASL_AUTH_METHOD.getName().equals(name)) { if (krbAuth != null) { throw new IllegalStateException( "Encountered multiple GssSaslClientAuthenticationProvider instances"); } krbAuth = (GssSaslClientAuthenticationProvider) provider; - } else if (provider instanceof DigestSaslClientAuthenticationProvider) { + } else if (DigestSaslAuthenticationProvider.SASL_AUTH_METHOD.getName().equals(name)) { if (digestAuth != null) { throw new IllegalStateException( "Encountered multiple DigestSaslClientAuthenticationProvider instances"); @@ -90,15 +95,11 @@ public Pair> if (clusterId == null) { throw new NullPointerException("Null clusterId was given"); } - try { - LOCK.readLock().lock(); - // Superfluous: we dont' do SIMPLE auth over SASL, but we should to simplify. - if (!User.isHBaseSecurityEnabled(conf)) { - return new Pair<>(simpleAuth, null); - } - } finally { - LOCK.readLock().unlock(); + // Superfluous: we don't do SIMPLE auth over SASL, but we should to simplify. + if (!User.isHBaseSecurityEnabled(conf)) { + return new Pair<>(simpleAuth, null); } + // Must be digest auth, look for a token. // TestGenerateDelegationToken is written expecting DT is used when DT and Krb are both present. // (for whatever that's worth). diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslAuthenticationProvider.java index 980a0c89d720..610f644540ae 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslAuthenticationProvider.java @@ -29,9 +29,8 @@ @InterfaceStability.Evolving public class DigestSaslAuthenticationProvider extends BuiltInSaslAuthenticationProvider { - static final String MECHANISM = "DIGEST-MD5"; - static final SaslAuthMethod SASL_AUTH_METHOD = new SaslAuthMethod( - "DIGEST", (byte)82, MECHANISM, AuthenticationMethod.TOKEN); + public static final SaslAuthMethod SASL_AUTH_METHOD = new SaslAuthMethod( + "DIGEST", (byte)82, "DIGEST-MD5", AuthenticationMethod.TOKEN); @Override public SaslAuthMethod getSaslAuthMethod() { diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslAuthenticationProvider.java index 509163821a5d..77ae036ef4df 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslAuthenticationProvider.java @@ -29,7 +29,7 @@ @InterfaceStability.Evolving public class GssSaslAuthenticationProvider extends BuiltInSaslAuthenticationProvider { - static final SaslAuthMethod SASL_AUTH_METHOD = new SaslAuthMethod( + public static final SaslAuthMethod SASL_AUTH_METHOD = new SaslAuthMethod( "KERBEROS", (byte)81, "GSSAPI", AuthenticationMethod.KERBEROS); @Override diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslAuthenticationProvider.java index 72a01b318d75..739d8125d2c3 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslAuthenticationProvider.java @@ -24,11 +24,12 @@ /** * Encapsulation of client-side logic to authenticate to HBase via some means over SASL. - * Implementations should not directly implement this interface, but instead extend - * {@link AbstractSaslClientAuthenticationProvider}. + * It is suggested that custom implementations extend the abstract class in the type hierarchy + * instead of directly implementing this interface (clients have a base class available, but + * servers presently do not). * - * Implementations of this interface must make an implementation of {@code hashCode()} - * which returns the same value across multiple instances of the provider implementation. + * Implementations of this interface must be unique among each other via the {@code byte} + * returned by {@link SaslAuthMethod#getCode()} on {@link #getSaslAuthMethod()}. */ @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) @InterfaceStability.Evolving diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java index 172b21891ef2..e9345f685ecf 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java @@ -17,6 +17,8 @@ */ package org.apache.hadoop.hbase.security.provider; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Optional; import java.util.ServiceLoader; @@ -50,11 +52,11 @@ public final class SaslClientAuthenticationProviders { private static final AtomicReference providersRef = new AtomicReference<>(); - private final HashMap providers; + private final Collection providers; private final AuthenticationProviderSelector selector; private SaslClientAuthenticationProviders( - HashMap providers, + Collection providers, AuthenticationProviderSelector selector) { this.providers = providers; this.selector = selector; @@ -93,20 +95,20 @@ public static synchronized void reset() { */ static void addProviderIfNotExists(SaslClientAuthenticationProvider provider, HashMap providers) { - SaslClientAuthenticationProvider existingProvider = providers.get( - provider.getSaslAuthMethod().getCode()); + Byte code = provider.getSaslAuthMethod().getCode(); + SaslClientAuthenticationProvider existingProvider = providers.get(code); if (existingProvider != null) { - throw new RuntimeException("Already registered authentication provider with " + - provider.getSaslAuthMethod().getCode() + " " + existingProvider.getClass()); + throw new RuntimeException("Already registered authentication provider with " + code + " " + + existingProvider.getClass()); } - providers.put(provider.getSaslAuthMethod().getCode(), provider); + providers.put(code, provider); } /** * Instantiates the ProviderSelector implementation from the provided configuration. */ static AuthenticationProviderSelector instantiateSelector(Configuration conf, - HashMap providers) { + Collection providers) { Class clz = conf.getClass( SELECTOR_KEY, DefaultProviderSelector.class, AuthenticationProviderSelector.class); try { @@ -166,21 +168,24 @@ static void addExplicitProviders(Configuration conf, static SaslClientAuthenticationProviders instantiate(Configuration conf) { ServiceLoader loader = ServiceLoader.load(SaslClientAuthenticationProvider.class); - HashMap providers = new HashMap<>(); + HashMap providerMap = new HashMap<>(); for (SaslClientAuthenticationProvider provider : loader) { - addProviderIfNotExists(provider, providers); + addProviderIfNotExists(provider, providerMap); } - addExplicitProviders(conf, providers); + addExplicitProviders(conf, providerMap); - AuthenticationProviderSelector selector = instantiateSelector(conf, providers); + Collection providers = Collections.unmodifiableCollection( + providerMap.values()); if (LOG.isTraceEnabled()) { - String loadedProviders = providers.values().stream() + String loadedProviders = providers.stream() .map((provider) -> provider.getClass().getName()) .collect(Collectors.joining(", ")); LOG.trace("Found SaslClientAuthenticationProviders {}", loadedProviders); } + + AuthenticationProviderSelector selector = instantiateSelector(conf, providers); return new SaslClientAuthenticationProviders(providers, selector); } @@ -192,7 +197,7 @@ static SaslClientAuthenticationProviders instantiate(Configuration conf) { */ public Pair> getSimpleProvider() { - Optional optional = providers.values().stream() + Optional optional = providers.stream() .filter((p) -> p instanceof SimpleSaslClientAuthenticationProvider) .findFirst(); return new Pair<>(optional.get(), null); @@ -209,7 +214,7 @@ public Pair> @Override public String toString() { - return providers.values().stream() + return providers.stream() .map((p) -> p.getClass().getName()) .collect(Collectors.joining(", ", "providers=[", "], selector=")) + selector.getClass(); } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslAuthenticationProvider.java index 85c8caec6199..56846ad775b2 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslAuthenticationProvider.java @@ -28,7 +28,7 @@ @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) @InterfaceStability.Evolving public class SimpleSaslAuthenticationProvider extends BuiltInSaslAuthenticationProvider { - private static final SaslAuthMethod SASL_AUTH_METHOD = new SaslAuthMethod( + public static final SaslAuthMethod SASL_AUTH_METHOD = new SaslAuthMethod( "SIMPLE", (byte)80, "", AuthenticationMethod.SIMPLE); @Override diff --git a/hbase-client/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider b/hbase-client/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider index 566f5c3d89ed..713f4693d73e 100644 --- a/hbase-client/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider +++ b/hbase-client/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider @@ -15,4 +15,4 @@ # limitations under the License. org.apache.hadoop.hbase.security.provider.GssSaslClientAuthenticationProvider org.apache.hadoop.hbase.security.provider.DigestSaslClientAuthenticationProvider -org.apache.hadoop.hbase.security.provider.SimpleSaslClientAuthenticationProvider \ No newline at end of file +org.apache.hadoop.hbase.security.provider.SimpleSaslClientAuthenticationProvider diff --git a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestHBaseSaslRpcClient.java b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestHBaseSaslRpcClient.java index 201f32384d27..9fc510c365a1 100644 --- a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestHBaseSaslRpcClient.java +++ b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestHBaseSaslRpcClient.java @@ -37,7 +37,6 @@ import javax.security.auth.callback.TextOutputCallback; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.sasl.RealmCallback; -import javax.security.sasl.RealmChoiceCallback; import javax.security.sasl.Sasl; import javax.security.sasl.SaslClient; @@ -119,14 +118,13 @@ public void testDigestSaslClientCallbackHandler() throws UnsupportedCallbackExce final NameCallback nameCallback = mock(NameCallback.class); final PasswordCallback passwordCallback = mock(PasswordCallback.class); final RealmCallback realmCallback = mock(RealmCallback.class); - final RealmChoiceCallback realmChoiceCallback = mock(RealmChoiceCallback.class); - Callback[] callbackArray = {nameCallback, passwordCallback, realmCallback, realmChoiceCallback}; + // We can provide a realmCallback, but HBase presently does nothing with it. + Callback[] callbackArray = {nameCallback, passwordCallback, realmCallback}; final DigestSaslClientCallbackHandler saslClCallbackHandler = new DigestSaslClientCallbackHandler(token); saslClCallbackHandler.handle(callbackArray); verify(nameCallback).setName(anyString()); - verify(realmCallback).setText(any()); verify(passwordCallback).setPassword(any()); } diff --git a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestDefaultProviderSelector.java b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestDefaultProviderSelector.java index 0d490b16994b..070c1bb75119 100644 --- a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestDefaultProviderSelector.java +++ b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestDefaultProviderSelector.java @@ -21,8 +21,8 @@ import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; -import java.util.Map; +import java.util.HashSet; +import java.util.Set; import org.apache.hadoop.conf.Configuration; import org.junit.Before; @@ -38,12 +38,12 @@ public void setup() { @Test(expected = IllegalStateException.class) public void testExceptionOnMissingProviders() { - selector.configure(new Configuration(false), Collections.emptyMap()); + selector.configure(new Configuration(false), Collections.emptySet()); } @Test(expected = NullPointerException.class) public void testNullConfiguration() { - selector.configure(null, Collections.emptyMap()); + selector.configure(null, Collections.emptySet()); } @Test(expected = NullPointerException.class) @@ -53,21 +53,17 @@ public void testNullProviderMap() { @Test(expected = IllegalStateException.class) public void testDuplicateProviders() { - Map providers = new HashMap<>(); - providers.put((byte) 1, new SimpleSaslClientAuthenticationProvider()); - providers.put((byte) 2, new SimpleSaslClientAuthenticationProvider()); + Set providers = new HashSet<>(); + providers.add(new SimpleSaslClientAuthenticationProvider()); + providers.add(new SimpleSaslClientAuthenticationProvider()); selector.configure(new Configuration(false), providers); } @Test public void testExpectedProviders() { - Map providers = new HashMap<>(); - - for (SaslClientAuthenticationProvider provider : Arrays.asList( + HashSet providers = new HashSet<>(Arrays.asList( new SimpleSaslClientAuthenticationProvider(), new GssSaslClientAuthenticationProvider(), - new DigestSaslClientAuthenticationProvider())) { - providers.put(provider.getSaslAuthMethod().getCode(), provider); - } + new DigestSaslClientAuthenticationProvider())); selector.configure(new Configuration(false), providers); diff --git a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslClientAuthenticationProviders.java b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslClientAuthenticationProviders.java index 79404c56a6bf..0b2f5bbf5141 100644 --- a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslClientAuthenticationProviders.java +++ b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslClientAuthenticationProviders.java @@ -21,13 +21,25 @@ import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; +import java.io.IOException; +import java.net.InetAddress; import java.util.HashMap; +import java.util.Map; + +import javax.security.sasl.SaslClient; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseClassTestRule; import org.apache.hadoop.hbase.HBaseConfiguration; +import org.apache.hadoop.hbase.security.SecurityInfo; +import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; import org.apache.hadoop.hbase.testclassification.SecurityTests; import org.apache.hadoop.hbase.testclassification.SmallTests; +import org.apache.hadoop.io.Text; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; +import org.apache.hadoop.security.token.Token; +import org.apache.hadoop.security.token.TokenIdentifier; import org.junit.ClassRule; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -72,4 +84,56 @@ public void testInstanceIsCached() { assertNotSame(providers1, providers3); assertEquals(providers1.getNumRegisteredProviders(), providers3.getNumRegisteredProviders()); } + + @Test(expected = RuntimeException.class) + public void testDifferentConflictingImplementationsFail() { + Configuration conf = HBaseConfiguration.create(); + conf.setStrings(SaslClientAuthenticationProviders.EXTRA_PROVIDERS_KEY, + ConflictingProvider1.class.getName(), ConflictingProvider2.class.getName()); + SaslClientAuthenticationProviders.getInstance(conf); + } + + static class ConflictingProvider1 implements SaslClientAuthenticationProvider { + static final SaslAuthMethod METHOD1 = new SaslAuthMethod( + "FOO", (byte)12, "DIGEST-MD5", AuthenticationMethod.SIMPLE); + @Override public SaslAuthMethod getSaslAuthMethod() { + return METHOD1; + } + + @Override public Text getTokenKind() { + return null; + } + + @Override public SaslClient createClient(Configuration conf, InetAddress serverAddr, + SecurityInfo securityInfo, Token token, boolean fallbackAllowed, + Map saslProps) throws IOException { + return null; + } + + @Override public UserInformation getUserInfo(UserGroupInformation user) { + return null; + } + } + + static class ConflictingProvider2 implements SaslClientAuthenticationProvider { + static final SaslAuthMethod METHOD2 = new SaslAuthMethod( + "BAR", (byte)12, "DIGEST-MD5", AuthenticationMethod.SIMPLE); + @Override public SaslAuthMethod getSaslAuthMethod() { + return METHOD2; + } + + @Override public Text getTokenKind() { + return null; + } + + @Override public SaslClient createClient(Configuration conf, InetAddress serverAddr, + SecurityInfo securityInfo, Token token, boolean fallbackAllowed, + Map saslProps) throws IOException { + return null; + } + + @Override public UserInformation getUserInfo(UserGroupInformation user) { + return null; + } + } } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/AttemptingUserProvidingSaslServer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/AttemptingUserProvidingSaslServer.java index 9b45ded7c4f8..e6dc3574726e 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/AttemptingUserProvidingSaslServer.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/AttemptingUserProvidingSaslServer.java @@ -48,6 +48,6 @@ public SaslServer getServer() { } public Optional getAttemptingUser() { - return Optional.of(producer.get()); + return Optional.ofNullable(producer.get()); } } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java index 2baf3e39e590..6ae6694f8744 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java @@ -107,27 +107,33 @@ public void handle(Callback[] callbacks) throws InvalidToken, UnsupportedCallbac attemptingUser.set(tokenIdentifier.getUser()); char[] password = getPassword(tokenIdentifier); if (LOG.isTraceEnabled()) { - LOG.trace("SASL server DIGEST-MD5 callback: setting password " + "for client: " + + LOG.trace("SASL server DIGEST-MD5 callback: setting password for client: {}", tokenIdentifier.getUser()); } pc.setPassword(password); } if (ac != null) { - String authid = ac.getAuthenticationID(); - String authzid = ac.getAuthorizationID(); - if (authid.equals(authzid)) { + // The authentication ID is the identifier (username) of the user who authenticated via + // SASL (the one who provided credentials). The authorization ID is who the remote user + // "asked" to be once they authenticated. This is akin to the UGI/JAAS "doAs" notion, e.g. + // authentication ID is the "real" user and authorization ID is the "proxy" user. + // + // For DelegationTokens: we do not expect any remote user with a delegation token to execute + // any RPCs as a user other than themselves. We disallow all cases where the real user + // does not match who the remote user wants to execute a request as someone else. + String authenticatedUserId = ac.getAuthenticationID(); + String userRequestedToExecuteAs = ac.getAuthorizationID(); + if (authenticatedUserId.equals(userRequestedToExecuteAs)) { ac.setAuthorized(true); - } else { - ac.setAuthorized(false); - } - if (ac.isAuthorized()) { if (LOG.isTraceEnabled()) { String username = HBaseSaslRpcServer.getIdentifier( - authzid, secretManager).getUser().getUserName(); + userRequestedToExecuteAs, secretManager).getUser().getUserName(); LOG.trace( "SASL server DIGEST-MD5 callback: setting " + "canonicalized client ID: " + username); } - ac.setAuthorizedID(authzid); + ac.setAuthorizedID(userRequestedToExecuteAs); + } else { + ac.setAuthorized(false); } } } diff --git a/hbase-server/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslServerAuthenticationProvider b/hbase-server/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslServerAuthenticationProvider index 30c648ced0c3..1c7af4918f61 100644 --- a/hbase-server/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslServerAuthenticationProvider +++ b/hbase-server/src/main/resources/META-INF/services/org.apache.hadoop.hbase.security.provider.SaslServerAuthenticationProvider @@ -15,4 +15,5 @@ # limitations under the License. org.apache.hadoop.hbase.security.provider.DigestSaslServerAuthenticationProvider org.apache.hadoop.hbase.security.provider.GssSaslServerAuthenticationProvider -org.apache.hadoop.hbase.security.provider.SimpleSaslServerAuthenticationProvider \ No newline at end of file +org.apache.hadoop.hbase.security.provider.SimpleSaslServerAuthenticationProvider + diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestSecureIPC.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestSecureIPC.java index 6a5b5072a2c8..a53b489565cf 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestSecureIPC.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestSecureIPC.java @@ -107,11 +107,9 @@ public class TestSecureIPC { public static Collection parameters() { List params = new ArrayList<>(); List rpcClientImpls = Arrays.asList( - BlockingRpcClient.class.getName(), - NettyRpcClient.class.getName()); + BlockingRpcClient.class.getName(), NettyRpcClient.class.getName()); List rpcServerImpls = Arrays.asList( - SimpleRpcServer.class.getName(), - NettyRpcServer.class.getName()); + SimpleRpcServer.class.getName(), NettyRpcServer.class.getName()); for (String rpcClientImpl : rpcClientImpls) { for (String rpcServerImpl : rpcServerImpls) { params.add(new Object[] { rpcClientImpl, rpcServerImpl }); diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java index 74cf9cb8acf6..01b55c363795 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java @@ -30,6 +30,7 @@ import java.io.IOException; import java.net.InetAddress; import java.security.PrivilegedExceptionAction; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; @@ -46,7 +47,6 @@ import javax.security.sasl.RealmChoiceCallback; import javax.security.sasl.Sasl; import javax.security.sasl.SaslClient; -import javax.security.sasl.SaslServer; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; @@ -141,7 +141,7 @@ public static String getPassword(String user) { } /** - * A custom tokenidentifier for our custom auth'n method. Unique from the TokenIdentifier + * A custom token identifier for our custom auth'n method. Unique from the TokenIdentifier * used for delegation tokens. */ public static class PasswordAuthTokenIdentifier extends TokenIdentifier { @@ -371,9 +371,9 @@ public static class InMemoryProviderSelector extends DefaultProviderSelector { @Override public void configure(Configuration conf, - Map providers) { + Collection providers) { super.configure(conf, providers); - Optional o = providers.values().stream() + Optional o = providers.stream() .filter((p) -> p instanceof InMemoryClientProvider) .findAny(); if (!o.isPresent()) { From a1bd33d1a39844a1badc024334cb43d881a93093 Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Tue, 17 Dec 2019 15:26:06 -0500 Subject: [PATCH 23/41] Address Duo's feedback. --- .../provider/TestCustomSaslAuthenticationProvider.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java index 01b55c363795..0677ad748183 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java @@ -376,11 +376,10 @@ public void configure(Configuration conf, Optional o = providers.stream() .filter((p) -> p instanceof InMemoryClientProvider) .findAny(); - if (!o.isPresent()) { - throw new RuntimeException( - "InMemoryClientProvider not found in available providers: " + providers); - } - inMemoryProvider = (InMemoryClientProvider) o.get(); + + inMemoryProvider = (InMemoryClientProvider) o.orElseThrow( + () -> new RuntimeException("InMemoryClientProvider not found in available providers: " + + providers)); } @Override From deb4b7da45de0061a93611d04d15c07f23eca76b Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Tue, 17 Dec 2019 15:44:01 -0500 Subject: [PATCH 24/41] Mark all built-in auth'n providers as private We provide a number of implementations of our LimitedPrivate interface but these are provided for user authentication, not as stable extension points for end-users. They may copy and modify this code, but should not rely on these implementations in their own code. Thanks to Duo for the suggestion. --- .../provider/BuiltInSaslAuthenticationProvider.java | 10 ++++++---- .../provider/DigestSaslAuthenticationProvider.java | 5 +---- .../DigestSaslClientAuthenticationProvider.java | 5 +---- .../provider/GssSaslAuthenticationProvider.java | 5 +---- .../provider/GssSaslClientAuthenticationProvider.java | 5 +---- .../provider/SimpleSaslAuthenticationProvider.java | 5 +---- .../SimpleSaslClientAuthenticationProvider.java | 5 +---- .../DigestSaslServerAuthenticationProvider.java | 5 +---- .../provider/GssSaslServerAuthenticationProvider.java | 5 +---- .../SimpleSaslServerAuthenticationProvider.java | 5 +---- 10 files changed, 15 insertions(+), 40 deletions(-) diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInSaslAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInSaslAuthenticationProvider.java index 7ee435abcae3..59d83497de82 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInSaslAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInSaslAuthenticationProvider.java @@ -17,16 +17,18 @@ */ package org.apache.hadoop.hbase.security.provider; -import org.apache.hadoop.hbase.HBaseInterfaceAudience; import org.apache.hadoop.io.Text; import org.apache.yetus.audience.InterfaceAudience; -import org.apache.yetus.audience.InterfaceStability; /** * Base class for all Apache HBase, built-in {@link SaslAuthenticationProvider}'s to extend. + * + * HBase users should take care to note that this class (and its sub-classes) are marked with the + * {@code InterfaceAudience.Private} annotation. These implementations are available for users to + * read, copy, and modify, but should not be extended or re-used in binary form. There are no + * compatibility guarantees provided for implementations of this class. */ -@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) -@InterfaceStability.Evolving +@InterfaceAudience.Private public abstract class BuiltInSaslAuthenticationProvider implements SaslAuthenticationProvider { public static final Text AUTH_TOKEN_TYPE = new Text("HBASE_AUTH_TOKEN"); diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslAuthenticationProvider.java index 610f644540ae..7cbdecd642be 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslAuthenticationProvider.java @@ -17,16 +17,13 @@ */ package org.apache.hadoop.hbase.security.provider; -import org.apache.hadoop.hbase.HBaseInterfaceAudience; import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; import org.apache.yetus.audience.InterfaceAudience; -import org.apache.yetus.audience.InterfaceStability; /** * Base client for client/server implementations for the HBase delegation token auth'n method. */ -@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) -@InterfaceStability.Evolving +@InterfaceAudience.Private public class DigestSaslAuthenticationProvider extends BuiltInSaslAuthenticationProvider { public static final SaslAuthMethod SASL_AUTH_METHOD = new SaslAuthMethod( diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java index 79076f750168..0c6e0aae28f7 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java @@ -32,21 +32,18 @@ import javax.security.sasl.SaslClient; import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.hbase.HBaseInterfaceAudience; import org.apache.hadoop.hbase.security.SaslUtil; import org.apache.hadoop.hbase.security.SecurityInfo; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; import org.apache.yetus.audience.InterfaceAudience; -import org.apache.yetus.audience.InterfaceStability; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; -@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) -@InterfaceStability.Evolving +@InterfaceAudience.Private public class DigestSaslClientAuthenticationProvider extends DigestSaslAuthenticationProvider implements SaslClientAuthenticationProvider { diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslAuthenticationProvider.java index 77ae036ef4df..07101848e507 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslAuthenticationProvider.java @@ -17,16 +17,13 @@ */ package org.apache.hadoop.hbase.security.provider; -import org.apache.hadoop.hbase.HBaseInterfaceAudience; import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; import org.apache.yetus.audience.InterfaceAudience; -import org.apache.yetus.audience.InterfaceStability; /** * Base client for client/server implementations for the "KERBEROS" HBase auth'n method. */ -@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) -@InterfaceStability.Evolving +@InterfaceAudience.Private public class GssSaslAuthenticationProvider extends BuiltInSaslAuthenticationProvider { public static final SaslAuthMethod SASL_AUTH_METHOD = new SaslAuthMethod( diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java index e50c04d9ab7e..7ba14068ab3b 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java @@ -25,7 +25,6 @@ import javax.security.sasl.SaslClient; import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.hbase.HBaseInterfaceAudience; import org.apache.hadoop.hbase.security.SaslUtil; import org.apache.hadoop.hbase.security.SecurityInfo; import org.apache.hadoop.security.SecurityUtil; @@ -33,14 +32,12 @@ import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; import org.apache.yetus.audience.InterfaceAudience; -import org.apache.yetus.audience.InterfaceStability; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; -@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) -@InterfaceStability.Evolving +@InterfaceAudience.Private public class GssSaslClientAuthenticationProvider extends GssSaslAuthenticationProvider implements SaslClientAuthenticationProvider { private static final Logger LOG = LoggerFactory.getLogger( diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslAuthenticationProvider.java index 56846ad775b2..3f1122c75413 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslAuthenticationProvider.java @@ -17,16 +17,13 @@ */ package org.apache.hadoop.hbase.security.provider; -import org.apache.hadoop.hbase.HBaseInterfaceAudience; import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; import org.apache.yetus.audience.InterfaceAudience; -import org.apache.yetus.audience.InterfaceStability; /** * Base client for client/server implementations for the "SIMPLE" HBase auth'n method. */ -@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) -@InterfaceStability.Evolving +@InterfaceAudience.Private public class SimpleSaslAuthenticationProvider extends BuiltInSaslAuthenticationProvider { public static final SaslAuthMethod SASL_AUTH_METHOD = new SaslAuthMethod( "SIMPLE", (byte)80, "", AuthenticationMethod.SIMPLE); diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslClientAuthenticationProvider.java index e885a0cc2484..e723f3c6e11d 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslClientAuthenticationProvider.java @@ -24,18 +24,15 @@ import javax.security.sasl.SaslClient; import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.hbase.HBaseInterfaceAudience; import org.apache.hadoop.hbase.security.SecurityInfo; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; import org.apache.yetus.audience.InterfaceAudience; -import org.apache.yetus.audience.InterfaceStability; import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; -@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) -@InterfaceStability.Evolving +@InterfaceAudience.Private public class SimpleSaslClientAuthenticationProvider extends SimpleSaslAuthenticationProvider implements SaslClientAuthenticationProvider { diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java index 6ae6694f8744..b3236d653764 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java @@ -31,7 +31,6 @@ import javax.security.sasl.Sasl; import javax.security.sasl.SaslServer; -import org.apache.hadoop.hbase.HBaseInterfaceAudience; import org.apache.hadoop.hbase.security.AccessDeniedException; import org.apache.hadoop.hbase.security.HBaseSaslRpcServer; import org.apache.hadoop.hbase.security.SaslUtil; @@ -40,12 +39,10 @@ import org.apache.hadoop.security.token.SecretManager.InvalidToken; import org.apache.hadoop.security.token.TokenIdentifier; import org.apache.yetus.audience.InterfaceAudience; -import org.apache.yetus.audience.InterfaceStability; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) -@InterfaceStability.Evolving +@InterfaceAudience.Private public class DigestSaslServerAuthenticationProvider extends DigestSaslAuthenticationProvider implements SaslServerAuthenticationProvider { private static final Logger LOG = LoggerFactory.getLogger( diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java index b921ae6baec9..8a542c69c0dc 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java @@ -28,19 +28,16 @@ import javax.security.sasl.Sasl; import javax.security.sasl.SaslException; -import org.apache.hadoop.hbase.HBaseInterfaceAudience; import org.apache.hadoop.hbase.security.AccessDeniedException; import org.apache.hadoop.hbase.security.SaslUtil; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.SecretManager; import org.apache.hadoop.security.token.TokenIdentifier; import org.apache.yetus.audience.InterfaceAudience; -import org.apache.yetus.audience.InterfaceStability; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) -@InterfaceStability.Evolving +@InterfaceAudience.Private public class GssSaslServerAuthenticationProvider extends GssSaslAuthenticationProvider implements SaslServerAuthenticationProvider { private static final Logger LOG = LoggerFactory.getLogger( diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java index 0b9e89e7a352..ed7bf4ce9e76 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java @@ -20,15 +20,12 @@ import java.io.IOException; import java.util.Map; -import org.apache.hadoop.hbase.HBaseInterfaceAudience; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.SecretManager; import org.apache.hadoop.security.token.TokenIdentifier; import org.apache.yetus.audience.InterfaceAudience; -import org.apache.yetus.audience.InterfaceStability; -@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) -@InterfaceStability.Evolving +@InterfaceAudience.Private public class SimpleSaslServerAuthenticationProvider extends SimpleSaslAuthenticationProvider implements SaslServerAuthenticationProvider { From aba09303410b1f96ea537199a566533482d236af Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Tue, 17 Dec 2019 15:59:34 -0500 Subject: [PATCH 25/41] Rename DefaultProviderSelector and expand on docs --- ...ctor.java => BuiltInProviderSelector.java} | 20 +++++++++++++------ .../SaslClientAuthenticationProviders.java | 2 +- .../provider/TestDefaultProviderSelector.java | 4 ++-- .../TestCustomSaslAuthenticationProvider.java | 2 +- 4 files changed, 18 insertions(+), 10 deletions(-) rename hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/{DefaultProviderSelector.java => BuiltInProviderSelector.java} (83%) diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DefaultProviderSelector.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInProviderSelector.java similarity index 83% rename from hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DefaultProviderSelector.java rename to hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInProviderSelector.java index 56b426908a75..fa9d8047e819 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DefaultProviderSelector.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInProviderSelector.java @@ -22,6 +22,7 @@ import java.util.Set; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HBaseInterfaceAudience; import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.hbase.util.Pair; import org.apache.hadoop.io.Text; @@ -35,16 +36,22 @@ import net.jcip.annotations.NotThreadSafe; /** - * Default implementation of {@link AuthenticationProviderSelector} which can choose from: Simple - * authentication, Kerberos authentication, and Delegation Token authentication. + * Default implementation of {@link AuthenticationProviderSelector} which can choose from the + * authentication implementations which HBase provides out of the box: Simple, Kerberos, and + * Delegation Token authentication. + * + * This implementation will ignore any {@link SaslAuthenticationProvider}'s which are available + * on the classpath or specified in the configuration because HBase cannot correctly choose which + * token should be returned to a client when multiple are present. It is expected that users + * implement their own {@link AuthenticationProviderSelector} when writing a custom provider. * * This implementation is not thread-safe. {@link #configure(Configuration, Set)} and * {@link #selectProvider(Text, UserGroupInformation)} is not safe if they are called concurrently. */ -@InterfaceAudience.Private +@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) @NotThreadSafe -public class DefaultProviderSelector implements AuthenticationProviderSelector { - private static final Logger LOG = LoggerFactory.getLogger(DefaultProviderSelector.class); +public class BuiltInProviderSelector implements AuthenticationProviderSelector { + private static final Logger LOG = LoggerFactory.getLogger(BuiltInProviderSelector.class); Configuration conf; SimpleSaslClientAuthenticationProvider simpleAuth = null; @@ -115,7 +122,8 @@ public Pair> if (ugi.hasKerberosCredentials()) { return new Pair<>(krbAuth, null); } - LOG.warn("No matching SASL authentication provider and supporting token found from providers."); + LOG.debug( + "No matching SASL authentication provider and supporting token found from providers."); return null; } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java index e9345f685ecf..10bf6cf351b8 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java @@ -110,7 +110,7 @@ static void addProviderIfNotExists(SaslClientAuthenticationProvider provider, static AuthenticationProviderSelector instantiateSelector(Configuration conf, Collection providers) { Class clz = conf.getClass( - SELECTOR_KEY, DefaultProviderSelector.class, AuthenticationProviderSelector.class); + SELECTOR_KEY, BuiltInProviderSelector.class, AuthenticationProviderSelector.class); try { AuthenticationProviderSelector selector = clz.newInstance(); selector.configure(conf, providers); diff --git a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestDefaultProviderSelector.java b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestDefaultProviderSelector.java index 070c1bb75119..1a6b316111e2 100644 --- a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestDefaultProviderSelector.java +++ b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestDefaultProviderSelector.java @@ -30,10 +30,10 @@ public class TestDefaultProviderSelector { - DefaultProviderSelector selector; + BuiltInProviderSelector selector; @Before public void setup() { - selector = new DefaultProviderSelector(); + selector = new BuiltInProviderSelector(); } @Test(expected = IllegalStateException.class) diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java index 0677ad748183..9e18df29f2dd 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java @@ -366,7 +366,7 @@ public UserGroupInformation getAuthorizedUgi(String authzId, * Custom provider which can select our custom provider, amongst other tokens which * may be available. */ - public static class InMemoryProviderSelector extends DefaultProviderSelector { + public static class InMemoryProviderSelector extends BuiltInProviderSelector { private InMemoryClientProvider inMemoryProvider; @Override From 69485a0e763ba41fa58bce49e6a7881f58de4fc2 Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Tue, 17 Dec 2019 16:30:36 -0500 Subject: [PATCH 26/41] All of belugabehr's logging and exception-throwing changes These are kept separate because they are largely changing code that was otherwise unchanged by this changeset. --- .../hadoop/hbase/ipc/BlockingRpcConnection.java | 9 ++------- .../org/apache/hadoop/hbase/ipc/RpcConnection.java | 6 ++++-- .../hbase/security/AbstractHBaseSaslRpcClient.java | 2 -- .../security/provider/BuiltInProviderSelector.java | 5 ++--- .../DigestSaslClientAuthenticationProvider.java | 12 +++--------- .../GssSaslClientAuthenticationProvider.java | 3 ++- .../provider/SaslServerAuthenticationProviders.java | 2 -- 7 files changed, 13 insertions(+), 26 deletions(-) diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java index d0c370009e90..f28cfa627186 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java @@ -400,7 +400,6 @@ public Object run() throws IOException, InterruptedException { if (ex instanceof SaslException) { String msg = "SASL authentication failed." + " The most likely cause is missing or invalid credentials."; - LOG.error(HBaseMarkers.FATAL, msg, ex); throw new RuntimeException(msg, ex); } throw new IOException(ex); @@ -409,10 +408,7 @@ public Object run() throws IOException, InterruptedException { // Other providers, like kerberos, could request a new ticket from a keytab. Let // them try again. if (currRetries < maxRetries) { - if (LOG.isDebugEnabled()) { - LOG.debug("Exception encountered while connecting to " + - "the server : " + StringUtils.stringifyException(ex)); - } + LOG.debug("Exception encountered while connecting to the server", ex); // Invoke the provider to perform the relogin provider.relogin(); @@ -427,10 +423,9 @@ public Object run() throws IOException, InterruptedException { Thread.sleep(ThreadLocalRandom.current().nextInt(reloginMaxBackoff) + 1); return null; } else { - String msg = "Couldn't setup connection for " + String msg = "Failed to initiate connection for " + UserGroupInformation.getLoginUser().getUserName() + " to " + securityInfo.getServerPrincipal(); - LOG.warn(msg, ex); throw new IOException(msg, ex); } } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java index a530e17ba17a..ec103041577a 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java @@ -106,8 +106,10 @@ protected RpcConnection(Configuration conf, HashedWheelTimer timeoutTimer, Conne if (useSasl && securityInfo != null) { pair = providers.selectProvider(new Text(clusterId), ticket); if (pair == null) { - LOG.error("Found no valid authentication method from {} with tokens={}", - providers.toString(), ticket.getTokens()); + if (LOG.isTraceEnabled()) { + LOG.trace("Found no valid authentication method from providers={} with tokens={}", + providers.toString(), ticket.getTokens()); + } throw new RuntimeException("Found no valid authentication method from options"); } } else if (!useSasl) { diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/AbstractHBaseSaslRpcClient.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/AbstractHBaseSaslRpcClient.java index 44acf8a48639..f94fefde21d5 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/AbstractHBaseSaslRpcClient.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/AbstractHBaseSaslRpcClient.java @@ -39,8 +39,6 @@ */ @InterfaceAudience.Private public abstract class AbstractHBaseSaslRpcClient { - private static final Logger LOG = LoggerFactory.getLogger(AbstractHBaseSaslRpcClient.class); - private static final byte[] EMPTY_TOKEN = new byte[0]; protected final SaslClient saslClient; diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInProviderSelector.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInProviderSelector.java index fa9d8047e819..c4b559398f19 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInProviderSelector.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInProviderSelector.java @@ -99,9 +99,8 @@ public void configure( @Override public Pair> selectProvider( Text clusterId, UserGroupInformation ugi) { - if (clusterId == null) { - throw new NullPointerException("Null clusterId was given"); - } + Objects.requireNonNull(clusterId, "Null clusterId was given"); + // Superfluous: we don't do SIMPLE auth over SASL, but we should to simplify. if (!User.isHBaseSecurityEnabled(conf)) { return new Pair<>(simpleAuth, null); diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java index 0c6e0aae28f7..f88995e6ab9c 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java @@ -87,21 +87,15 @@ public void handle(Callback[] callbacks) throws UnsupportedCallbackException { } } if (nc != null) { - if (LOG.isDebugEnabled()) { - LOG.debug("SASL client callback: setting username: " + userName); - } + LOG.debug("SASL client callback: setting username: {}", userName); nc.setName(userName); } if (pc != null) { - if (LOG.isDebugEnabled()) { - LOG.debug("SASL client callback: setting userPassword"); - } + LOG.debug("SASL client callback: setting userPassword"); pc.setPassword(userPassword); } if (rc != null) { - if (LOG.isDebugEnabled()) { - LOG.debug("SASL client callback: setting realm: " + rc.getDefaultText()); - } + LOG.debug("SASL client callback: setting realm: {}", rc.getDefaultText()); rc.setText(rc.getDefaultText()); } } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java index 7ba14068ab3b..dec8d1932c90 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java @@ -47,7 +47,8 @@ String getServerPrincipal(Configuration conf, SecurityInfo securityInfo, InetAdd throws IOException { String serverKey = securityInfo.getServerPrincipal(); if (serverKey == null) { - throw new IOException("Can't obtain server Kerberos config key from SecurityInfo"); + throw new IllegalArgumentException( + "Can't obtain server Kerberos config key from SecurityInfo"); } return SecurityUtil.getServerPrincipal(conf.get(serverKey), server.getCanonicalHostName().toLowerCase()); diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProviders.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProviders.java index 8af75e1d6d90..cd4eb00ba8b8 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProviders.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProviders.java @@ -37,12 +37,10 @@ public final class SaslServerAuthenticationProviders { private static final AtomicReference holder = new AtomicReference<>(); - private final Configuration conf; private final HashMap providers; private SaslServerAuthenticationProviders(Configuration conf, HashMap providers) { - this.conf = conf; this.providers = providers; } From 3773f40164d68418aa6b5b0fcd28067b29e8a6fe Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Wed, 18 Dec 2019 12:33:38 -0500 Subject: [PATCH 27/41] Come up with a better name and interface for unwrapUgi Also improve the javadoc on the new `getRealUser` method. Try to do a better job explaining why this is here. --- .../hbase/ipc/BlockingRpcConnection.java | 2 +- .../hadoop/hbase/ipc/NettyRpcConnection.java | 2 +- .../GssSaslClientAuthenticationProvider.java | 4 +++- .../SaslClientAuthenticationProvider.java | 20 ++++++++++++++----- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java index f28cfa627186..51b88511ec11 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java @@ -464,7 +464,7 @@ private void setupIOstreams() throws IOException { if (useSasl) { final InputStream in2 = inStream; final OutputStream out2 = outStream; - UserGroupInformation ticket = provider.unwrapUgi(remoteId.ticket.getUGI()); + UserGroupInformation ticket = provider.getRealUser(remoteId.ticket); boolean continueSasl; if (ticket == null) { throw new FatalConnectionException("ticket/user is null"); diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/NettyRpcConnection.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/NettyRpcConnection.java index b7843054ceac..66f5bc0f8460 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/NettyRpcConnection.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/NettyRpcConnection.java @@ -183,7 +183,7 @@ private void failInit(Channel ch, IOException e) { } private void saslNegotiate(final Channel ch) { - UserGroupInformation ticket = provider.unwrapUgi(remoteId.getTicket().getUGI()); + UserGroupInformation ticket = provider.getRealUser(remoteId.getTicket().getUGI()); if (ticket == null) { failInit(ch, new FatalConnectionException("ticket/user is null")); return; diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java index dec8d1932c90..4815aad7b3c5 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java @@ -27,6 +27,7 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.security.SaslUtil; import org.apache.hadoop.hbase.security.SecurityInfo; +import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.security.SecurityUtil; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.Token; @@ -93,7 +94,8 @@ public void relogin() throws IOException { } @Override - public UserGroupInformation unwrapUgi(UserGroupInformation ugi) { + public UserGroupInformation getRealUser(User user) { + final UserGroupInformation ugi = user.getUGI(); // Unwrap the UGI with the real user when we're using Kerberos auth if (ugi != null && ugi.getRealUser() != null) { return ugi.getRealUser(); diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProvider.java index 6709f0a4fd76..b1800ccbabff 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProvider.java @@ -26,6 +26,7 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseInterfaceAudience; import org.apache.hadoop.hbase.security.SecurityInfo; +import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; @@ -59,12 +60,21 @@ SaslClient createClient(Configuration conf, InetAddress serverAddr, SecurityInfo UserInformation getUserInfo(UserGroupInformation user); /** - * Performs any necessary unwrapping of a "proxy" user UGI which is executing some request - * on top of a "real" user. This operation may simply return the original UGI if that is - * what is appropriate for the given implementation. + * Returns the "real" user, the user who has the credentials being authenticated by the + * remote service, in the form of an {@link UserGroupInformation} object. + * + * It is common in the Hadoop "world" to have distinct notions of a "real" user and a "proxy" + * user. A "real" user is the user which actually has the credentials (often, a Kerberos ticket), + * but some code may be running as some other user who has no credentials. This method gives + * the authentication provider a chance to acknowledge this is happening and ensure that any + * RPCs are executed with the real user's credentials, because executing them as the proxy user + * would result in failure because no credentials exist to authenticate the RPC. + * + * Not all implementations will need to implement this method. By default, the provided User's + * UGI is returned directly. */ - default UserGroupInformation unwrapUgi(UserGroupInformation ugi) { - return ugi; + default UserGroupInformation getRealUser(User ugi) { + return ugi.getUGI(); } /** From e73ca7f4c854f08d47f8bbf3c9f593be8868022e Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Wed, 18 Dec 2019 15:41:37 -0500 Subject: [PATCH 28/41] Next round of checkstyle/javadoc issue fixing. --- .../apache/hadoop/hbase/ipc/BlockingRpcConnection.java | 2 +- .../hbase/security/AbstractHBaseSaslRpcClient.java | 2 -- .../provider/AuthenticationProviderSelector.java | 2 +- .../hbase/security/provider/BuiltInProviderSelector.java | 9 ++++----- .../provider/DigestSaslClientAuthenticationProvider.java | 6 ++---- .../provider/TestSaslClientAuthenticationProviders.java | 3 ++- .../provider/TestCustomSaslAuthenticationProvider.java | 4 ++-- 7 files changed, 12 insertions(+), 16 deletions(-) diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java index 51b88511ec11..99708e31628a 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/BlockingRpcConnection.java @@ -415,7 +415,7 @@ public Object run() throws IOException, InterruptedException { // Get rid of any old state on the SaslClient disposeSasl(); - + // have granularity of milliseconds // we are sleeping with the Connection lock held but since this // connection instance is being used for connecting to the server diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/AbstractHBaseSaslRpcClient.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/AbstractHBaseSaslRpcClient.java index f94fefde21d5..b1f0861e3512 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/AbstractHBaseSaslRpcClient.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/AbstractHBaseSaslRpcClient.java @@ -29,8 +29,6 @@ import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; import org.apache.yetus.audience.InterfaceAudience; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * A utility class that encapsulates SASL logic for RPC client. Copied from diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java index dee65a6cf872..b006ff3ae2e7 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java @@ -44,7 +44,7 @@ void configure(Configuration conf, /** * Chooses the authentication provider which should be used given the provided client context - * from the authentication providers passed in via {@link #configure(Configuration, Map)}. + * from the authentication providers passed in via {@link #configure(Configuration, Collection)}. */ Pair> selectProvider( Text clusterId, UserGroupInformation ugi); diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInProviderSelector.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInProviderSelector.java index c4b559398f19..670d5c88d702 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInProviderSelector.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInProviderSelector.java @@ -19,7 +19,8 @@ import java.util.Collection; import java.util.Objects; -import java.util.Set; + +import net.jcip.annotations.NotThreadSafe; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseInterfaceAudience; @@ -33,8 +34,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import net.jcip.annotations.NotThreadSafe; - /** * Default implementation of {@link AuthenticationProviderSelector} which can choose from the * authentication implementations which HBase provides out of the box: Simple, Kerberos, and @@ -45,7 +44,7 @@ * token should be returned to a client when multiple are present. It is expected that users * implement their own {@link AuthenticationProviderSelector} when writing a custom provider. * - * This implementation is not thread-safe. {@link #configure(Configuration, Set)} and + * This implementation is not thread-safe. {@link #configure(Configuration, Collection)} and * {@link #selectProvider(Text, UserGroupInformation)} is not safe if they are called concurrently. */ @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) @@ -65,7 +64,7 @@ public void configure( throw new IllegalStateException("configure() should only be called once"); } this.conf = Objects.requireNonNull(conf); - + for (SaslClientAuthenticationProvider provider : Objects.requireNonNull(providers)) { final String name = provider.getSaslAuthMethod().getName(); if (SimpleSaslAuthenticationProvider.SASL_AUTH_METHOD.getName().contentEquals(name)) { diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java index f88995e6ab9c..1df9bc6fb9a2 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java @@ -47,14 +47,12 @@ public class DigestSaslClientAuthenticationProvider extends DigestSaslAuthenticationProvider implements SaslClientAuthenticationProvider { - private static final String MECHANISM = "DIGEST-MD5"; - @Override public SaslClient createClient(Configuration conf, InetAddress serverAddr, SecurityInfo securityInfo, Token token, boolean fallbackAllowed, Map saslProps) throws IOException { - return Sasl.createSaslClient(new String[] { MECHANISM }, null, null, - SaslUtil.SASL_DEFAULT_REALM, saslProps, new DigestSaslClientCallbackHandler(token)); + return Sasl.createSaslClient(new String[] { getSaslAuthMethod().getSaslMechanism() }, null, + null, SaslUtil.SASL_DEFAULT_REALM, saslProps, new DigestSaslClientCallbackHandler(token)); } public static class DigestSaslClientCallbackHandler implements CallbackHandler { diff --git a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslClientAuthenticationProviders.java b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslClientAuthenticationProviders.java index 0b2f5bbf5141..bfd0aa6615ca 100644 --- a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslClientAuthenticationProviders.java +++ b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslClientAuthenticationProviders.java @@ -32,7 +32,6 @@ import org.apache.hadoop.hbase.HBaseClassTestRule; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.security.SecurityInfo; -import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; import org.apache.hadoop.hbase.testclassification.SecurityTests; import org.apache.hadoop.hbase.testclassification.SmallTests; import org.apache.hadoop.io.Text; @@ -44,6 +43,8 @@ import org.junit.Test; import org.junit.experimental.categories.Category; +import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; + @Category({SmallTests.class, SecurityTests.class}) public class TestSaslClientAuthenticationProviders { diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java index 9e18df29f2dd..84c65481c74d 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java @@ -280,7 +280,7 @@ public AttemptingUserProvidingSaslServer createServer( return new AttemptingUserProvidingSaslServer( Sasl.createSaslServer(getSaslAuthMethod().getSaslMechanism(), null, SaslUtil.SASL_DEFAULT_REALM, saslProps, new InMemoryServerProviderCallbackHandler()), - () -> null); + () -> null); } /** @@ -378,7 +378,7 @@ public void configure(Configuration conf, .findAny(); inMemoryProvider = (InMemoryClientProvider) o.orElseThrow( - () -> new RuntimeException("InMemoryClientProvider not found in available providers: " + () -> new RuntimeException("InMemoryClientProvider not found in available providers: " + providers)); } From e2b950d5e71d1414a1326a7b93355b748ed723d3 Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Wed, 18 Dec 2019 18:19:43 -0500 Subject: [PATCH 29/41] Fix compilation error --- .../java/org/apache/hadoop/hbase/ipc/NettyRpcConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/NettyRpcConnection.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/NettyRpcConnection.java index 66f5bc0f8460..7d91fd9d6f04 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/NettyRpcConnection.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/NettyRpcConnection.java @@ -183,7 +183,7 @@ private void failInit(Channel ch, IOException e) { } private void saslNegotiate(final Channel ch) { - UserGroupInformation ticket = provider.getRealUser(remoteId.getTicket().getUGI()); + UserGroupInformation ticket = provider.getRealUser(remoteId.getTicket()); if (ticket == null) { failInit(ch, new FatalConnectionException("ticket/user is null")); return; From fabf242038aae323fd7a4f907cb1ba39bcd0fd5c Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Wed, 18 Dec 2019 18:50:05 -0500 Subject: [PATCH 30/41] WIP, stubbing out ShadeSaslAP, missing lots of stuff, still. --- .../provider/example/SaslPlainServer.java | 161 ++++++++++++++++++ .../ShadeSaslAuthenticationProvider.java | 37 ++++ ...ShadeSaslClientAuthenticationProvider.java | 98 +++++++++++ ...ShadeSaslServerAuthenticationProvider.java | 118 +++++++++++++ .../provider/example/ShadeTokenUtil.java | 6 + .../hadoop/hbase/ipc/ServerRpcConnection.java | 3 +- .../hbase/security/HBaseSaslRpcServer.java | 5 +- ...igestSaslServerAuthenticationProvider.java | 3 +- .../GssSaslServerAuthenticationProvider.java | 3 +- .../SaslServerAuthenticationProvider.java | 4 +- ...impleSaslServerAuthenticationProvider.java | 3 +- .../TestCustomSaslAuthenticationProvider.java | 2 +- 12 files changed, 435 insertions(+), 8 deletions(-) create mode 100644 hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/SaslPlainServer.java create mode 100644 hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslAuthenticationProvider.java create mode 100644 hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslClientAuthenticationProvider.java create mode 100644 hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslServerAuthenticationProvider.java create mode 100644 hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeTokenUtil.java diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/SaslPlainServer.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/SaslPlainServer.java new file mode 100644 index 000000000000..baaccdc40763 --- /dev/null +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/SaslPlainServer.java @@ -0,0 +1,161 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hadoop.hbase.security.provider.example; + +import java.nio.charset.StandardCharsets; +import java.security.Provider; +import java.util.Map; + +import javax.security.auth.callback.*; +import javax.security.sasl.AuthorizeCallback; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslServerFactory; + +import org.apache.hadoop.classification.InterfaceAudience; + +/** + * This class was copied from Hadoop Common (3.1.2) and subsequently modified. + */ +@InterfaceAudience.Private +public class SaslPlainServer implements SaslServer { + @SuppressWarnings("serial") + public static class SecurityProvider extends Provider { + public SecurityProvider() { + super("SaslPlainServer", 1.0, "SASL PLAIN Authentication Server"); + put("SaslServerFactory.PLAIN", + SaslPlainServerFactory.class.getName()); + } + } + + public static class SaslPlainServerFactory implements SaslServerFactory { + @Override + public SaslServer createSaslServer(String mechanism, String protocol, + String serverName, Map props, CallbackHandler cbh) + throws SaslException { + return "PLAIN".equals(mechanism) ? new SaslPlainServer(cbh) : null; + } + @Override + public String[] getMechanismNames(Map props){ + return (props == null) || "false".equals(props.get(Sasl.POLICY_NOPLAINTEXT)) + ? new String[]{"PLAIN"} + : new String[0]; + } + } + + private CallbackHandler cbh; + private boolean completed; + private String authz; + + SaslPlainServer(CallbackHandler callback) { + this.cbh = callback; + } + + @Override + public String getMechanismName() { + return "PLAIN"; + } + + @Override + public byte[] evaluateResponse(byte[] response) throws SaslException { + if (completed) { + throw new IllegalStateException("PLAIN authentication has completed"); + } + if (response == null) { + throw new IllegalArgumentException("Received null response"); + } + try { + String payload; + try { + payload = new String(response, StandardCharsets.UTF_8); + } catch (Exception e) { + throw new IllegalArgumentException("Received corrupt response", e); + } + // [ authz, authn, password ] + String[] parts = payload.split("\u0000", 3); + if (parts.length != 3) { + throw new IllegalArgumentException("Received corrupt response"); + } + if (parts[0].isEmpty()) { // authz = authn + parts[0] = parts[1]; + } + + NameCallback nc = new NameCallback("SASL PLAIN"); + nc.setName(parts[1]); + PasswordCallback pc = new PasswordCallback("SASL PLAIN", false); + pc.setPassword(parts[2].toCharArray()); + AuthorizeCallback ac = new AuthorizeCallback(parts[1], parts[0]); + cbh.handle(new Callback[]{nc, pc, ac}); + if (ac.isAuthorized()) { + authz = ac.getAuthorizedID(); + } + } catch (Exception e) { + throw new SaslException("PLAIN auth failed: " + e.toString(), e); + } finally { + completed = true; + } + return null; + } + + private void throwIfNotComplete() { + if (!completed) { + throw new IllegalStateException("PLAIN authentication not completed"); + } + } + + @Override + public boolean isComplete() { + return completed; + } + + @Override + public String getAuthorizationID() { + throwIfNotComplete(); + return authz; + } + + @Override + public Object getNegotiatedProperty(String propName) { + throwIfNotComplete(); + return Sasl.QOP.equals(propName) ? "auth" : null; + } + + @Override + public byte[] wrap(byte[] outgoing, int offset, int len) + throws SaslException { + throwIfNotComplete(); + throw new IllegalStateException( + "PLAIN supports neither integrity nor privacy"); + } + + @Override + public byte[] unwrap(byte[] incoming, int offset, int len) + throws SaslException { + throwIfNotComplete(); + throw new IllegalStateException( + "PLAIN supports neither integrity nor privacy"); + } + + @Override + public void dispose() throws SaslException { + cbh = null; + authz = null; + } +} \ No newline at end of file diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslAuthenticationProvider.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslAuthenticationProvider.java new file mode 100644 index 000000000000..a6158ba727a7 --- /dev/null +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslAuthenticationProvider.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.security.provider.example; + +import org.apache.hadoop.hbase.security.provider.SaslAuthMethod; +import org.apache.hadoop.hbase.security.provider.SaslAuthenticationProvider; +import org.apache.hadoop.io.Text; +import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; + +public abstract class ShadeSaslAuthenticationProvider implements SaslAuthenticationProvider { + public static final SaslAuthMethod METHOD = new SaslAuthMethod( + "SHADE", (byte) 15, "PLAIN", AuthenticationMethod.TOKEN); + public static final Text TOKEN_KIND = new Text("SHADE_TOKEN"); + + @Override public SaslAuthMethod getSaslAuthMethod() { + return METHOD; + } + + @Override public Text getTokenKind() { + return TOKEN_KIND; + } +} diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslClientAuthenticationProvider.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslClientAuthenticationProvider.java new file mode 100644 index 000000000000..06a347d6c3d9 --- /dev/null +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslClientAuthenticationProvider.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.security.provider.example; + +import java.io.IOException; +import java.net.InetAddress; +import java.util.Map; + +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.sasl.RealmCallback; +import javax.security.sasl.RealmChoiceCallback; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslClient; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.security.SaslUtil; +import org.apache.hadoop.hbase.security.SecurityInfo; +import org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider; +import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.token.Token; +import org.apache.hadoop.security.token.TokenIdentifier; + +public class ShadeSaslClientAuthenticationProvider extends ShadeSaslAuthenticationProvider + implements SaslClientAuthenticationProvider { + + @Override + public SaslClient createClient(Configuration conf, InetAddress serverAddr, + SecurityInfo securityInfo, Token token, boolean fallbackAllowed, + Map saslProps) throws IOException { + return Sasl.createSaslClient(new String[] { getSaslAuthMethod().getSaslMechanism()}, null, null, + SaslUtil.SASL_DEFAULT_REALM, saslProps, new ShadeSaslClientCallbackHandler(token)); + } + + @Override + public UserInformation getUserInfo(UserGroupInformation user) { + return null; + } + + static class ShadeSaslClientCallbackHandler implements CallbackHandler { + private final String username; + private final char[] password; + public ShadeSaslClientCallbackHandler( + Token token) throws IOException { + this.username = Bytes.toString(token.decodeIdentifier().getBytes()); + this.password = Bytes.toString(token.getPassword()).toCharArray(); + } + + @Override + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + NameCallback nc = null; + PasswordCallback pc = null; + RealmCallback rc = null; + for (Callback callback : callbacks) { + if (callback instanceof RealmChoiceCallback) { + continue; + } else if (callback instanceof NameCallback) { + nc = (NameCallback) callback; + } else if (callback instanceof PasswordCallback) { + pc = (PasswordCallback) callback; + } else if (callback instanceof RealmCallback) { + rc = (RealmCallback) callback; + } else { + throw new UnsupportedCallbackException(callback, "Unrecognized SASL client callback"); + } + } + if (nc != null) { + nc.setName(username); + } + if (pc != null) { + pc.setPassword(password); + } + if (rc != null) { + rc.setText(rc.getDefaultText()); + } + } + } +} diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslServerAuthenticationProvider.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslServerAuthenticationProvider.java new file mode 100644 index 000000000000..dffa5ac8dccf --- /dev/null +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslServerAuthenticationProvider.java @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.security.provider.example; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +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.sasl.AuthorizeCallback; +import javax.security.sasl.RealmCallback; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.security.provider.AttemptingUserProvidingSaslServer; +import org.apache.hadoop.hbase.security.provider.SaslServerAuthenticationProvider; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.token.SecretManager; +import org.apache.hadoop.security.token.TokenIdentifier; + +public class ShadeSaslServerAuthenticationProvider extends ShadeSaslAuthenticationProvider + implements SaslServerAuthenticationProvider { + + private AtomicReference attemptingUser = new AtomicReference<>(null); + + @Override + public AttemptingUserProvidingSaslServer createServer(Configuration conf, + SecretManager secretManager, Map saslProps) + throws IOException { + return new AttemptingUserProvidingSaslServer( + new SaslPlainServer( + new ShadeSaslServerCallbackHandler(attemptingUser)), () -> attemptingUser.get()); + } + + @Override + public boolean supportsProtocolAuthentication() { + return false; + } + + @Override + public UserGroupInformation getAuthorizedUgi(String authzId, + SecretManager secretManager) throws IOException { + return UserGroupInformation.createRemoteUser(authzId); + } + + static class ShadeSaslServerCallbackHandler implements CallbackHandler { + private final AtomicReference attemptingUser; + + public ShadeSaslServerCallbackHandler(AtomicReference attemptingUser) { + this.attemptingUser = attemptingUser; + } + + @Override public void handle(Callback[] callbacks) throws UnsupportedCallbackException { + NameCallback nc = null; + PasswordCallback pc = null; + AuthorizeCallback ac = null; + for (Callback callback : callbacks) { + if (callback instanceof AuthorizeCallback) { + ac = (AuthorizeCallback) callback; + } else if (callback instanceof NameCallback) { + nc = (NameCallback) callback; + } else if (callback instanceof PasswordCallback) { + pc = (PasswordCallback) callback; + } else if (callback instanceof RealmCallback) { + continue; // realm is ignored + } else { + throw new UnsupportedCallbackException(callback, "Unrecognized SASL PLAIN Callback"); + } + } + + if (nc != null && pc != null) { + String username = nc.getName(); + UserGroupInformation ugi = createUgiForRemoteUser(username); + char[] password = pc.getPassword(); + + attemptingUser.set(ugi); + + // TODO validate + } + + if (ac != null) { + String authenticatedUserId = ac.getAuthenticationID(); + String userRequestedToExecuteAs = ac.getAuthorizationID(); + if (authenticatedUserId.equals(userRequestedToExecuteAs)) { + ac.setAuthorized(true); + ac.setAuthorizedID(userRequestedToExecuteAs); + } else { + ac.setAuthorized(false); + } + } + } + + UserGroupInformation createUgiForRemoteUser(String username) { + UserGroupInformation ugi = UserGroupInformation.createRemoteUser(username); + ugi.setAuthenticationMethod(ShadeSaslAuthenticationProvider.METHOD.getAuthMethod()); + return ugi; + } + } + +} diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeTokenUtil.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeTokenUtil.java new file mode 100644 index 000000000000..0bff484e7738 --- /dev/null +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeTokenUtil.java @@ -0,0 +1,6 @@ +package org.apache.hadoop.hbase.security.provider.example; + +public class ShadeTokenUtil { + + // TODO build a method which returns a Hadoop Token for the ShadeAP +} diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java index d49a9043e9a2..b16b95e9c4d7 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java @@ -349,7 +349,8 @@ public void saslReadAndProcess(ByteBuff saslToken) throws IOException, try { if (saslServer == null) { saslServer = - new HBaseSaslRpcServer(provider, rpcServer.saslProps, rpcServer.secretManager); + new HBaseSaslRpcServer( + rpcServer.getConf(), provider, rpcServer.saslProps, rpcServer.secretManager); RpcServer.LOG.debug("Created SASL server with mechanism={}", provider.getSaslAuthMethod().getAuthMethod()); } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcServer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcServer.java index 6189dc712857..2fa43082c13b 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcServer.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcServer.java @@ -27,6 +27,7 @@ import javax.security.sasl.SaslException; import javax.security.sasl.SaslServer; +import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.security.provider.AttemptingUserProvidingSaslServer; import org.apache.hadoop.hbase.security.provider.SaslServerAuthenticationProvider; import org.apache.hadoop.security.UserGroupInformation; @@ -49,10 +50,10 @@ public class HBaseSaslRpcServer { private final AttemptingUserProvidingSaslServer serverWithProvider; private final SaslServer saslServer; - public HBaseSaslRpcServer(SaslServerAuthenticationProvider provider, + public HBaseSaslRpcServer(Configuration conf, SaslServerAuthenticationProvider provider, Map saslProps, SecretManager secretManager) throws IOException { - serverWithProvider = provider.createServer(secretManager, saslProps); + serverWithProvider = provider.createServer(conf, secretManager, saslProps); saslServer = serverWithProvider.getServer(); } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java index b3236d653764..de9f2cfc6628 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java @@ -31,6 +31,7 @@ import javax.security.sasl.Sasl; import javax.security.sasl.SaslServer; +import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.security.AccessDeniedException; import org.apache.hadoop.hbase.security.HBaseSaslRpcServer; import org.apache.hadoop.hbase.security.SaslUtil; @@ -51,7 +52,7 @@ public class DigestSaslServerAuthenticationProvider extends DigestSaslAuthentica private AtomicReference attemptingUser = new AtomicReference<>(null); @Override - public AttemptingUserProvidingSaslServer createServer( + public AttemptingUserProvidingSaslServer createServer(Configuration conf, SecretManager secretManager, Map saslProps) throws IOException { if (secretManager == null) { diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java index 8a542c69c0dc..87f671c672d5 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java @@ -28,6 +28,7 @@ import javax.security.sasl.Sasl; import javax.security.sasl.SaslException; +import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.security.AccessDeniedException; import org.apache.hadoop.hbase.security.SaslUtil; import org.apache.hadoop.security.UserGroupInformation; @@ -44,7 +45,7 @@ public class GssSaslServerAuthenticationProvider extends GssSaslAuthenticationPr GssSaslServerAuthenticationProvider.class); @Override - public AttemptingUserProvidingSaslServer createServer( + public AttemptingUserProvidingSaslServer createServer(Configuration conf, SecretManager secretManager, Map saslProps) throws IOException { UserGroupInformation current = UserGroupInformation.getCurrentUser(); diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProvider.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProvider.java index 931c67986778..6eb365f00459 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProvider.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProvider.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.util.Map; +import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseInterfaceAudience; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.SecretManager; @@ -38,7 +39,8 @@ public interface SaslServerAuthenticationProvider extends SaslAuthenticationProv /** * Creates the SaslServer to accept incoming SASL authentication requests. */ - AttemptingUserProvidingSaslServer createServer(SecretManager secretManager, + AttemptingUserProvidingSaslServer createServer(Configuration conf, + SecretManager secretManager, Map saslProps) throws IOException; boolean supportsProtocolAuthentication(); diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java index ed7bf4ce9e76..2cf3bdab7970 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.util.Map; +import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.SecretManager; import org.apache.hadoop.security.token.TokenIdentifier; @@ -30,7 +31,7 @@ public class SimpleSaslServerAuthenticationProvider extends SimpleSaslAuthentica implements SaslServerAuthenticationProvider { @Override - public AttemptingUserProvidingSaslServer createServer( + public AttemptingUserProvidingSaslServer createServer(Configuration conf, SecretManager secretManager, Map saslProps) throws IOException { throw new RuntimeException("HBase SIMPLE authentication doesn't use SASL"); diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java index 84c65481c74d..d7ed9753124f 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java @@ -274,7 +274,7 @@ public static class InMemoryServerProvider extends InMemoryClientProvider implements SaslServerAuthenticationProvider { @Override - public AttemptingUserProvidingSaslServer createServer( + public AttemptingUserProvidingSaslServer createServer(Configuration conf, SecretManager secretManager, Map saslProps) throws IOException { return new AttemptingUserProvidingSaslServer( From d9da2bf27d201e7f603c89005734a4ea0d10becf Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Thu, 19 Dec 2019 19:17:07 -0500 Subject: [PATCH 31/41] Finish out the rest of the impl --- .../AuthenticationProviderSelector.java | 1 - hbase-examples/pom.xml | 11 + .../example/ShadeClientTokenUtil.java | 42 ++++ .../example/ShadeProviderSelector.java | 46 ++++ ...ShadeSaslClientAuthenticationProvider.java | 2 +- ...ShadeSaslServerAuthenticationProvider.java | 79 +++++- .../example/ShadeTokenIdentifier.java | 40 +++ .../provider/example/ShadeTokenUtil.java | 6 - .../TestShadeSaslAuthenticationProvider.java | 235 ++++++++++++++++++ 9 files changed, 448 insertions(+), 14 deletions(-) create mode 100644 hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeClientTokenUtil.java create mode 100644 hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeProviderSelector.java create mode 100644 hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeTokenIdentifier.java delete mode 100644 hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeTokenUtil.java create mode 100644 hbase-examples/src/test/java/org/apache/hadoop/hbase/security/provider/example/TestShadeSaslAuthenticationProvider.java diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java index b006ff3ae2e7..7eb31cac171f 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java @@ -18,7 +18,6 @@ package org.apache.hadoop.hbase.security.provider; import java.util.Collection; -import java.util.Map; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseInterfaceAudience; diff --git a/hbase-examples/pom.xml b/hbase-examples/pom.xml index f2ff2fd482c9..3a191c9a73c2 100644 --- a/hbase-examples/pom.xml +++ b/hbase-examples/pom.xml @@ -174,6 +174,17 @@ mockito-core test + + org.apache.hadoop + hadoop-minikdc + test + + + bouncycastle + bcprov-jdk15 + + + diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeClientTokenUtil.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeClientTokenUtil.java new file mode 100644 index 000000000000..d70ea721ade7 --- /dev/null +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeClientTokenUtil.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.security.provider.example; + +import java.io.IOException; + +import org.apache.hadoop.hbase.client.Connection; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.io.Text; +import org.apache.hadoop.security.token.Token; +import org.apache.hadoop.security.token.TokenIdentifier; + +/** + * Used to acquire tokens for the ShadeSaslAuthenticationProvider. + */ +public class ShadeClientTokenUtil { + + private ShadeClientTokenUtil() {} + + public static Token obtainToken( + Connection conn, String username, char[] password) throws IOException { + ShadeTokenIdentifier identifier = new ShadeTokenIdentifier(username); + return new Token<>(identifier.getBytes(), Bytes.toBytes(new String(password)), + ShadeSaslAuthenticationProvider.TOKEN_KIND, + new Text(conn.getAdmin().getClusterMetrics().getClusterId())); + } +} diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeProviderSelector.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeProviderSelector.java new file mode 100644 index 000000000000..a47854362fc1 --- /dev/null +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeProviderSelector.java @@ -0,0 +1,46 @@ +package org.apache.hadoop.hbase.security.provider.example; + +import java.util.Collection; +import java.util.Optional; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.security.provider.BuiltInProviderSelector; +import org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider; +import org.apache.hadoop.hbase.util.Pair; +import org.apache.hadoop.io.Text; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.token.Token; +import org.apache.hadoop.security.token.TokenIdentifier; + +public class ShadeProviderSelector extends BuiltInProviderSelector { + + private ShadeSaslClientAuthenticationProvider shade; + + @Override + public void configure( + Configuration conf, Collection providers) { + super.configure(conf, providers); + + this.shade = (ShadeSaslClientAuthenticationProvider) providers.stream() + .filter((p) -> p instanceof ShadeSaslClientAuthenticationProvider) + .findFirst() + .orElseThrow(() -> new RuntimeException( + "ShadeSaslClientAuthenticationProvider not loaded")); + } + + @Override + public Pair> selectProvider( + Text clusterId, UserGroupInformation ugi) { + Pair> pair = + super.selectProvider(clusterId, ugi); + + Optional> optional = ugi.getTokens().stream() + .filter((t) -> ShadeSaslAuthenticationProvider.TOKEN_KIND.equals(t.getKind())) + .findFirst(); + if (optional.isPresent()) { + return new Pair<>(shade, optional.get()); + } + + return pair; + } +} diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslClientAuthenticationProvider.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslClientAuthenticationProvider.java index 06a347d6c3d9..3b9ceaf4824c 100644 --- a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslClientAuthenticationProvider.java +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslClientAuthenticationProvider.java @@ -62,7 +62,7 @@ static class ShadeSaslClientCallbackHandler implements CallbackHandler { private final char[] password; public ShadeSaslClientCallbackHandler( Token token) throws IOException { - this.username = Bytes.toString(token.decodeIdentifier().getBytes()); + this.username = token.decodeIdentifier().getUser().getUserName(); this.password = Bytes.toString(token.getPassword()).toCharArray(); } diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslServerAuthenticationProvider.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslServerAuthenticationProvider.java index dffa5ac8dccf..2edf347d43b5 100644 --- a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslServerAuthenticationProvider.java +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslServerAuthenticationProvider.java @@ -17,7 +17,10 @@ */ package org.apache.hadoop.hbase.security.provider.example; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; @@ -30,14 +33,25 @@ import javax.security.sasl.RealmCallback; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.FSDataInputStream; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; import org.apache.hadoop.hbase.security.provider.AttemptingUserProvidingSaslServer; import org.apache.hadoop.hbase.security.provider.SaslServerAuthenticationProvider; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.SecretManager; import org.apache.hadoop.security.token.TokenIdentifier; +import org.apache.hadoop.util.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class ShadeSaslServerAuthenticationProvider extends ShadeSaslAuthenticationProvider implements SaslServerAuthenticationProvider { + private static final Logger LOG = LoggerFactory.getLogger( + ShadeSaslServerAuthenticationProvider.class); + + public static final String PASSWORD_FILE_KEY = "hbase.security.shade.password.file"; + static final char SEPARATOR = '='; private AtomicReference attemptingUser = new AtomicReference<>(null); @@ -45,9 +59,55 @@ public class ShadeSaslServerAuthenticationProvider extends ShadeSaslAuthenticati public AttemptingUserProvidingSaslServer createServer(Configuration conf, SecretManager secretManager, Map saslProps) throws IOException { + Map passwordDatabase = readPasswordDB(conf); + return new AttemptingUserProvidingSaslServer( new SaslPlainServer( - new ShadeSaslServerCallbackHandler(attemptingUser)), () -> attemptingUser.get()); + new ShadeSaslServerCallbackHandler(attemptingUser, passwordDatabase)), + () -> attemptingUser.get()); + } + + Map readPasswordDB(Configuration conf) throws IOException { + String passwordFileName = conf.get(PASSWORD_FILE_KEY); + if (passwordFileName == null) { + throw new RuntimeException(PASSWORD_FILE_KEY + + " is not defined in configuration, cannot use this implementation"); + } + + Path passwordFile = new Path(passwordFileName); + FileSystem fs = passwordFile.getFileSystem(conf); + if (!fs.exists(passwordFile)) { + throw new RuntimeException("Configured password file does not exist: " + passwordFile); + } + + Map passwordDb = new HashMap<>(); + try (FSDataInputStream fdis = fs.open(passwordFile); + BufferedReader reader = new BufferedReader(new InputStreamReader(fdis))) { + String line = null; + int offset = 0; + while ((line = reader.readLine()) != null) { + line.trim(); + String[] parts = StringUtils.split(line, SEPARATOR); + if (parts.length < 2) { + LOG.warn("Password file contains invalid record on line {}, skipping", offset + 1); + continue; + } + + final String username = parts[0]; + StringBuilder builder = new StringBuilder(); + for (int i = 1; i < parts.length; i++) { + if (builder.length() > 0) { + builder.append(SEPARATOR); + } + builder.append(parts[i]); + } + + passwordDb.put(username, builder.toString().toCharArray()); + offset++; + } + } + + return passwordDb; } @Override @@ -63,9 +123,12 @@ public UserGroupInformation getAuthorizedUgi(String authzId, static class ShadeSaslServerCallbackHandler implements CallbackHandler { private final AtomicReference attemptingUser; + private final Map passwordDatabase; - public ShadeSaslServerCallbackHandler(AtomicReference attemptingUser) { + public ShadeSaslServerCallbackHandler(AtomicReference attemptingUser, + Map passwordDatabase) { this.attemptingUser = attemptingUser; + this.passwordDatabase = passwordDatabase; } @Override public void handle(Callback[] callbacks) throws UnsupportedCallbackException { @@ -88,12 +151,17 @@ public ShadeSaslServerCallbackHandler(AtomicReference atte if (nc != null && pc != null) { String username = nc.getName(); - UserGroupInformation ugi = createUgiForRemoteUser(username); - char[] password = pc.getPassword(); + UserGroupInformation ugi = createUgiForRemoteUser(username); attemptingUser.set(ugi); - // TODO validate + char[] actualPassword = passwordDatabase.get(username); + if (actualPassword == null) { + // How should we gracefully fail the authentication? + throw new RuntimeException("Could not obtain password for user"); + } + + pc.setPassword(actualPassword); } if (ac != null) { @@ -114,5 +182,4 @@ UserGroupInformation createUgiForRemoteUser(String username) { return ugi; } } - } diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeTokenIdentifier.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeTokenIdentifier.java new file mode 100644 index 000000000000..d75d14d6fdba --- /dev/null +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeTokenIdentifier.java @@ -0,0 +1,40 @@ +package org.apache.hadoop.hbase.security.provider.example; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import org.apache.hadoop.io.Text; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.token.TokenIdentifier; + +public class ShadeTokenIdentifier extends TokenIdentifier { + private String username; + + public ShadeTokenIdentifier(String username) { + this.username = username; + } + + @Override + public void write(DataOutput out) throws IOException { + out.writeUTF(username); + } + + @Override + public void readFields(DataInput in) throws IOException { + username = in.readUTF(); + } + + @Override + public Text getKind() { + return ShadeSaslAuthenticationProvider.TOKEN_KIND; + } + + @Override + public UserGroupInformation getUser() { + if (username == null || "".equals(username)) { + return null; + } + return UserGroupInformation.createRemoteUser(username); + } +} diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeTokenUtil.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeTokenUtil.java deleted file mode 100644 index 0bff484e7738..000000000000 --- a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeTokenUtil.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.apache.hadoop.hbase.security.provider.example; - -public class ShadeTokenUtil { - - // TODO build a method which returns a Hadoop Token for the ShadeAP -} diff --git a/hbase-examples/src/test/java/org/apache/hadoop/hbase/security/provider/example/TestShadeSaslAuthenticationProvider.java b/hbase-examples/src/test/java/org/apache/hadoop/hbase/security/provider/example/TestShadeSaslAuthenticationProvider.java new file mode 100644 index 000000000000..dfa03193e598 --- /dev/null +++ b/hbase-examples/src/test/java/org/apache/hadoop/hbase/security/provider/example/TestShadeSaslAuthenticationProvider.java @@ -0,0 +1,235 @@ +package org.apache.hadoop.hbase.security.provider.example; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.security.PrivilegedExceptionAction; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.FSDataOutputStream; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.hbase.Cell; +import org.apache.hadoop.hbase.CellUtil; +import org.apache.hadoop.hbase.HBaseClassTestRule; +import org.apache.hadoop.hbase.HBaseTestingUtility; +import org.apache.hadoop.hbase.HConstants; +import org.apache.hadoop.hbase.LocalHBaseCluster; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.client.Admin; +import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; +import org.apache.hadoop.hbase.client.Connection; +import org.apache.hadoop.hbase.client.ConnectionFactory; +import org.apache.hadoop.hbase.client.Get; +import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.client.RetriesExhaustedException; +import org.apache.hadoop.hbase.client.Table; +import org.apache.hadoop.hbase.client.TableDescriptorBuilder; +import org.apache.hadoop.hbase.coprocessor.CoprocessorHost; +import org.apache.hadoop.hbase.ipc.BlockingRpcClient; +import org.apache.hadoop.hbase.ipc.RpcClientFactory; +import org.apache.hadoop.hbase.ipc.RpcServerFactory; +import org.apache.hadoop.hbase.ipc.SimpleRpcServer; +import org.apache.hadoop.hbase.security.HBaseKerberosUtils; +import org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProviders; +import org.apache.hadoop.hbase.security.provider.SaslServerAuthenticationProviders; +import org.apache.hadoop.hbase.security.token.SecureTestCluster; +import org.apache.hadoop.hbase.security.token.TokenProvider; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.hbase.util.FSUtils; +import org.apache.hadoop.minikdc.MiniKdc; +import org.apache.hadoop.security.UserGroupInformation; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TestShadeSaslAuthenticationProvider { + + @ClassRule + public static final HBaseClassTestRule CLASS_RULE = + HBaseClassTestRule.forClass(TestShadeSaslAuthenticationProvider.class); + + private static final char[] USER1_PASSWORD = "foobarbaz".toCharArray(); + + static LocalHBaseCluster createCluster(HBaseTestingUtility util, File keytabFile, + MiniKdc kdc, Map userDatabase) throws Exception { + String servicePrincipal = "hbase/localhost"; + String spnegoPrincipal = "HTTP/localhost"; + kdc.createPrincipal(keytabFile, servicePrincipal); + util.startMiniZKCluster(); + + HBaseKerberosUtils.setSecuredConfiguration(util.getConfiguration(), + servicePrincipal + "@" + kdc.getRealm(), spnegoPrincipal + "@" + kdc.getRealm()); + HBaseKerberosUtils.setSSLConfiguration(util, SecureTestCluster.class); + + util.getConfiguration().setStrings(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY, + TokenProvider.class.getName()); + util.startMiniDFSCluster(1); + Path testDir = util.getDataTestDirOnTestFS("TestShadeSaslAuthenticationProvider"); + USER_DATABASE_FILE = new Path(testDir, "user-db.txt"); + + createUserDBFile( + USER_DATABASE_FILE.getFileSystem(CONF), USER_DATABASE_FILE, userDatabase); + CONF.set(ShadeSaslServerAuthenticationProvider.PASSWORD_FILE_KEY, + USER_DATABASE_FILE.toString()); + + Path rootdir = new Path(testDir, "hbase-root"); + FSUtils.setRootDir(CONF, rootdir); + LocalHBaseCluster cluster = new LocalHBaseCluster(CONF, 1); + return cluster; + } + + static void createUserDBFile(FileSystem fs, Path p, + Map userDatabase) throws IOException { + if (fs.exists(p)) { + fs.delete(p, true); + } + try (FSDataOutputStream out = fs.create(p); + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out))) { + for (Entry e : userDatabase.entrySet()) { + writer.write(e.getKey()); + writer.write(ShadeSaslServerAuthenticationProvider.SEPARATOR); + writer.write(e.getValue()); + writer.newLine(); + } + } + } + + private static final HBaseTestingUtility UTIL = new HBaseTestingUtility(); + private static final Configuration CONF = UTIL.getConfiguration(); + private static LocalHBaseCluster CLUSTER; + private static File KEYTAB_FILE; + private static Path USER_DATABASE_FILE; + + @BeforeClass + public static void setupCluster() throws Exception { + KEYTAB_FILE = new File( + UTIL.getDataTestDir("keytab").toUri().getPath()); + final MiniKdc kdc = UTIL.setupMiniKdc(KEYTAB_FILE); + + // Switch back to NIO for now. + CONF.set(RpcClientFactory.CUSTOM_RPC_CLIENT_IMPL_CONF_KEY, BlockingRpcClient.class.getName()); + CONF.set(RpcServerFactory.CUSTOM_RPC_SERVER_IMPL_CONF_KEY, SimpleRpcServer.class.getName()); + + // Adds our test impls instead of creating service loader entries which + // might inadvertently get them loaded on a real cluster. + CONF.setStrings(SaslClientAuthenticationProviders.EXTRA_PROVIDERS_KEY, + ShadeSaslClientAuthenticationProvider.class.getName()); + CONF.setStrings(SaslServerAuthenticationProviders.EXTRA_PROVIDERS_KEY, + ShadeSaslServerAuthenticationProvider.class.getName()); + CONF.set(SaslClientAuthenticationProviders.SELECTOR_KEY, + ShadeProviderSelector.class.getName()); + + CLUSTER = createCluster(UTIL, KEYTAB_FILE, kdc, + Collections.singletonMap("user1", USER1_PASSWORD)); + CLUSTER.startup(); + } + + @AfterClass + public static void teardownCluster() throws Exception { + if (CLUSTER != null) { + CLUSTER.shutdown(); + CLUSTER = null; + } + UTIL.shutdownMiniZKCluster(); + } + + @Rule + public TestName name = new TestName(); + TableName tableName; + String clusterId; + + @Before + public void createTable() throws Exception { + tableName = TableName.valueOf(name.getMethodName()); + + // Create a table and write a record as the service user (hbase) + UserGroupInformation serviceUgi = UserGroupInformation.loginUserFromKeytabAndReturnUGI( + "hbase/localhost", KEYTAB_FILE.getAbsolutePath()); + clusterId = serviceUgi.doAs(new PrivilegedExceptionAction() { + @Override public String run() throws Exception { + try (Connection conn = ConnectionFactory.createConnection(CONF); + Admin admin = conn.getAdmin();) { + admin.createTable(TableDescriptorBuilder + .newBuilder(tableName) + .setColumnFamily(ColumnFamilyDescriptorBuilder.of("f1")) + .build()); + + UTIL.waitTableAvailable(tableName); + + try (Table t = conn.getTable(tableName)) { + Put p = new Put(Bytes.toBytes("r1")); + p.addColumn(Bytes.toBytes("f1"), Bytes.toBytes("q1"), Bytes.toBytes("1")); + t.put(p); + } + + return admin.getClusterMetrics().getClusterId(); + } + } + }); + + assertNotNull(clusterId); + } + + @Test + public void testPositiveAuthentication() throws Exception { + final Configuration clientConf = new Configuration(CONF); + try (Connection conn = ConnectionFactory.createConnection(clientConf)) { + UserGroupInformation user1 = UserGroupInformation.createUserForTesting( + "user1", new String[0]); + user1.addToken(ShadeClientTokenUtil.obtainToken(conn, "user1", USER1_PASSWORD)); + user1.doAs(new PrivilegedExceptionAction() { + @Override public Void run() throws Exception { + try (Table t = conn.getTable(tableName)) { + Result r = t.get(new Get(Bytes.toBytes("r1"))); + assertNotNull(r); + assertFalse("Should have read a non-empty Result", r.isEmpty()); + final Cell cell = r.getColumnLatestCell(Bytes.toBytes("f1"), Bytes.toBytes("q1")); + assertTrue("Unexpected value", CellUtil.matchingValue(cell, Bytes.toBytes("1"))); + + return null; + } + } + }); + } + } + + @Test(expected = RetriesExhaustedException.class) + public void testNegativeAuthentication() throws Exception { + // Validate that we can read that record back out as the user with our custom auth'n + final Configuration clientConf = new Configuration(CONF); + clientConf.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 3); + try (Connection conn = ConnectionFactory.createConnection(clientConf)) { + UserGroupInformation user1 = UserGroupInformation.createUserForTesting( + "user1", new String[0]); + user1.addToken( + ShadeClientTokenUtil.obtainToken(conn, "user1", "not a real password".toCharArray())); + user1.doAs(new PrivilegedExceptionAction() { + @Override public Void run() throws Exception { + try (Connection conn = ConnectionFactory.createConnection(clientConf); + Table t = conn.getTable(tableName)) { + t.get(new Get(Bytes.toBytes("r1"))); + fail("Should not successfully authenticate with HBase"); + return null; + } + } + }); + } + } +} From d546b8a122b2b8516ea1bebf9b34f0edfae2f29b Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Tue, 14 Jan 2020 16:55:26 -0500 Subject: [PATCH 32/41] Get the hbase-example working --- .../hadoop/hbase/ipc/RpcConnection.java | 2 +- hbase-examples/pom.xml | 11 ++++++ .../provider/example/SaslPlainServer.java | 2 +- .../example/ShadeClientTokenUtil.java | 2 ++ .../example/ShadeProviderSelector.java | 19 ++++++++++ .../ShadeSaslAuthenticationProvider.java | 4 ++- ...ShadeSaslClientAuthenticationProvider.java | 14 ++++++-- ...ShadeSaslServerAuthenticationProvider.java | 16 +++++---- .../example/ShadeTokenIdentifier.java | 27 +++++++++++++- ...ache.hadoop.security.token.TokenIdentifier | 18 ++++++++++ .../TestShadeSaslAuthenticationProvider.java | 35 ++++++++++++------- .../src/test/resources/log4j.properties | 1 + 12 files changed, 127 insertions(+), 24 deletions(-) create mode 100644 hbase-examples/src/main/resources/META-INF/services/org.apache.hadoop.security.token.TokenIdentifier diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java index ec103041577a..5bd3d3683204 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java @@ -122,7 +122,7 @@ protected RpcConnection(Configuration conf, HashedWheelTimer timeoutTimer, Conne this.provider = pair.getFirst(); this.token = pair.getSecond(); - LOG.debug("Using {} authentication for service{}, sasl={}", + LOG.debug("Using {} authentication for service={}, sasl={}", provider.getSaslAuthMethod().getName(), remoteId.serviceName, useSasl); reloginMaxBackoff = conf.getInt("hbase.security.relogin.maxbackoff", 5000); this.remoteId = remoteId; diff --git a/hbase-examples/pom.xml b/hbase-examples/pom.xml index 3a191c9a73c2..b73146b1a7b5 100644 --- a/hbase-examples/pom.xml +++ b/hbase-examples/pom.xml @@ -185,6 +185,17 @@ + + org.bouncycastle + bcprov-jdk15on + test + + + org.apache.hbase + hbase-http + test + test-jar + diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/SaslPlainServer.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/SaslPlainServer.java index baaccdc40763..024d6181d5d4 100644 --- a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/SaslPlainServer.java +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/SaslPlainServer.java @@ -29,7 +29,7 @@ import javax.security.sasl.SaslServer; import javax.security.sasl.SaslServerFactory; -import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.yetus.audience.InterfaceAudience; /** * This class was copied from Hadoop Common (3.1.2) and subsequently modified. diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeClientTokenUtil.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeClientTokenUtil.java index d70ea721ade7..a2ad5eab8bf7 100644 --- a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeClientTokenUtil.java +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeClientTokenUtil.java @@ -24,10 +24,12 @@ import org.apache.hadoop.io.Text; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; +import org.apache.yetus.audience.InterfaceAudience; /** * Used to acquire tokens for the ShadeSaslAuthenticationProvider. */ +@InterfaceAudience.Private public class ShadeClientTokenUtil { private ShadeClientTokenUtil() {} diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeProviderSelector.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeProviderSelector.java index a47854362fc1..fbc61a01696d 100644 --- a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeProviderSelector.java +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeProviderSelector.java @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.apache.hadoop.hbase.security.provider.example; import java.util.Collection; @@ -11,7 +28,9 @@ import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; +import org.apache.yetus.audience.InterfaceAudience; +@InterfaceAudience.Private public class ShadeProviderSelector extends BuiltInProviderSelector { private ShadeSaslClientAuthenticationProvider shade; diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslAuthenticationProvider.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslAuthenticationProvider.java index a6158ba727a7..4f1cd812c416 100644 --- a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslAuthenticationProvider.java +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslAuthenticationProvider.java @@ -21,11 +21,13 @@ import org.apache.hadoop.hbase.security.provider.SaslAuthenticationProvider; import org.apache.hadoop.io.Text; import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; +import org.apache.yetus.audience.InterfaceAudience; +@InterfaceAudience.Private public abstract class ShadeSaslAuthenticationProvider implements SaslAuthenticationProvider { public static final SaslAuthMethod METHOD = new SaslAuthMethod( "SHADE", (byte) 15, "PLAIN", AuthenticationMethod.TOKEN); - public static final Text TOKEN_KIND = new Text("SHADE_TOKEN"); + public static final Text TOKEN_KIND = new Text("HBASE_EXAMPLE_SHADE_TOKEN"); @Override public SaslAuthMethod getSaslAuthMethod() { return METHOD; diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslClientAuthenticationProvider.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslClientAuthenticationProvider.java index 3b9ceaf4824c..8c4c992eb946 100644 --- a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslClientAuthenticationProvider.java +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslClientAuthenticationProvider.java @@ -40,7 +40,9 @@ import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; +import org.apache.yetus.audience.InterfaceAudience; +@InterfaceAudience.Private public class ShadeSaslClientAuthenticationProvider extends ShadeSaslAuthenticationProvider implements SaslClientAuthenticationProvider { @@ -54,7 +56,9 @@ public SaslClient createClient(Configuration conf, InetAddress serverAddr, @Override public UserInformation getUserInfo(UserGroupInformation user) { - return null; + UserInformation.Builder userInfoPB = UserInformation.newBuilder(); + userInfoPB.setEffectiveUser(user.getUserName()); + return userInfoPB.build(); } static class ShadeSaslClientCallbackHandler implements CallbackHandler { @@ -62,7 +66,13 @@ static class ShadeSaslClientCallbackHandler implements CallbackHandler { private final char[] password; public ShadeSaslClientCallbackHandler( Token token) throws IOException { - this.username = token.decodeIdentifier().getUser().getUserName(); + TokenIdentifier id = token.decodeIdentifier(); + if (id == null) { + // Something is wrong with the environment if we can't get our Identifier back out. + throw new IllegalStateException("Could not extract Identifier from Token"); + } + UserGroupInformation ugi = id.getUser(); + this.username = ugi.getUserName(); this.password = Bytes.toString(token.getPassword()).toCharArray(); } diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslServerAuthenticationProvider.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslServerAuthenticationProvider.java index 2edf347d43b5..ce54ee10acc2 100644 --- a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslServerAuthenticationProvider.java +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslServerAuthenticationProvider.java @@ -20,6 +20,7 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; @@ -40,11 +41,14 @@ import org.apache.hadoop.hbase.security.provider.SaslServerAuthenticationProvider; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.SecretManager; +import org.apache.hadoop.security.token.SecretManager.InvalidToken; import org.apache.hadoop.security.token.TokenIdentifier; import org.apache.hadoop.util.StringUtils; +import org.apache.yetus.audience.InterfaceAudience; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +@InterfaceAudience.Private public class ShadeSaslServerAuthenticationProvider extends ShadeSaslAuthenticationProvider implements SaslServerAuthenticationProvider { private static final Logger LOG = LoggerFactory.getLogger( @@ -131,7 +135,9 @@ public ShadeSaslServerCallbackHandler(AtomicReference atte this.passwordDatabase = passwordDatabase; } - @Override public void handle(Callback[] callbacks) throws UnsupportedCallbackException { + @Override public void handle(Callback[] callbacks) + throws InvalidToken, UnsupportedCallbackException { + LOG.info("SaslServerCallbackHandler called", new Exception()); NameCallback nc = null; PasswordCallback pc = null; AuthorizeCallback ac = null; @@ -155,13 +161,11 @@ public ShadeSaslServerCallbackHandler(AtomicReference atte UserGroupInformation ugi = createUgiForRemoteUser(username); attemptingUser.set(ugi); + char[] clientPassword = pc.getPassword(); char[] actualPassword = passwordDatabase.get(username); - if (actualPassword == null) { - // How should we gracefully fail the authentication? - throw new RuntimeException("Could not obtain password for user"); + if (!Arrays.equals(clientPassword, actualPassword)) { + throw new InvalidToken("Authentication failed for " + username); } - - pc.setPassword(actualPassword); } if (ac != null) { diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeTokenIdentifier.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeTokenIdentifier.java index d75d14d6fdba..d7bb56dc0e73 100644 --- a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeTokenIdentifier.java +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeTokenIdentifier.java @@ -1,5 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.apache.hadoop.hbase.security.provider.example; +import static java.util.Objects.requireNonNull; + import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; @@ -7,12 +26,18 @@ import org.apache.hadoop.io.Text; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.TokenIdentifier; +import org.apache.yetus.audience.InterfaceAudience; +@InterfaceAudience.Private public class ShadeTokenIdentifier extends TokenIdentifier { private String username; + public ShadeTokenIdentifier() { + // for ServiceLoader + } + public ShadeTokenIdentifier(String username) { - this.username = username; + this.username = requireNonNull(username); } @Override diff --git a/hbase-examples/src/main/resources/META-INF/services/org.apache.hadoop.security.token.TokenIdentifier b/hbase-examples/src/main/resources/META-INF/services/org.apache.hadoop.security.token.TokenIdentifier new file mode 100644 index 000000000000..c9660d763a68 --- /dev/null +++ b/hbase-examples/src/main/resources/META-INF/services/org.apache.hadoop.security.token.TokenIdentifier @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +org.apache.hadoop.hbase.security.provider.example.ShadeTokenIdentifier diff --git a/hbase-examples/src/test/java/org/apache/hadoop/hbase/security/provider/example/TestShadeSaslAuthenticationProvider.java b/hbase-examples/src/test/java/org/apache/hadoop/hbase/security/provider/example/TestShadeSaslAuthenticationProvider.java index dfa03193e598..65ec8e16942f 100644 --- a/hbase-examples/src/test/java/org/apache/hadoop/hbase/security/provider/example/TestShadeSaslAuthenticationProvider.java +++ b/hbase-examples/src/test/java/org/apache/hadoop/hbase/security/provider/example/TestShadeSaslAuthenticationProvider.java @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.apache.hadoop.hbase.security.provider.example; import static org.junit.Assert.assertFalse; @@ -20,6 +37,7 @@ import org.apache.hadoop.fs.Path; import org.apache.hadoop.hbase.Cell; import org.apache.hadoop.hbase.CellUtil; +import org.apache.hadoop.hbase.DoNotRetryIOException; import org.apache.hadoop.hbase.HBaseClassTestRule; import org.apache.hadoop.hbase.HBaseTestingUtility; import org.apache.hadoop.hbase.HConstants; @@ -32,19 +50,16 @@ import org.apache.hadoop.hbase.client.Get; import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.client.Result; -import org.apache.hadoop.hbase.client.RetriesExhaustedException; import org.apache.hadoop.hbase.client.Table; import org.apache.hadoop.hbase.client.TableDescriptorBuilder; import org.apache.hadoop.hbase.coprocessor.CoprocessorHost; -import org.apache.hadoop.hbase.ipc.BlockingRpcClient; -import org.apache.hadoop.hbase.ipc.RpcClientFactory; -import org.apache.hadoop.hbase.ipc.RpcServerFactory; -import org.apache.hadoop.hbase.ipc.SimpleRpcServer; import org.apache.hadoop.hbase.security.HBaseKerberosUtils; import org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProviders; import org.apache.hadoop.hbase.security.provider.SaslServerAuthenticationProviders; import org.apache.hadoop.hbase.security.token.SecureTestCluster; import org.apache.hadoop.hbase.security.token.TokenProvider; +import org.apache.hadoop.hbase.testclassification.MediumTests; +import org.apache.hadoop.hbase.testclassification.SecurityTests; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.FSUtils; import org.apache.hadoop.minikdc.MiniKdc; @@ -55,10 +70,10 @@ import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; +import org.junit.experimental.categories.Category; import org.junit.rules.TestName; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +@Category({MediumTests.class, SecurityTests.class}) public class TestShadeSaslAuthenticationProvider { @ClassRule @@ -123,10 +138,6 @@ public static void setupCluster() throws Exception { UTIL.getDataTestDir("keytab").toUri().getPath()); final MiniKdc kdc = UTIL.setupMiniKdc(KEYTAB_FILE); - // Switch back to NIO for now. - CONF.set(RpcClientFactory.CUSTOM_RPC_CLIENT_IMPL_CONF_KEY, BlockingRpcClient.class.getName()); - CONF.set(RpcServerFactory.CUSTOM_RPC_SERVER_IMPL_CONF_KEY, SimpleRpcServer.class.getName()); - // Adds our test impls instead of creating service loader entries which // might inadvertently get them loaded on a real cluster. CONF.setStrings(SaslClientAuthenticationProviders.EXTRA_PROVIDERS_KEY, @@ -210,7 +221,7 @@ public void testPositiveAuthentication() throws Exception { } } - @Test(expected = RetriesExhaustedException.class) + @Test(expected = DoNotRetryIOException.class) public void testNegativeAuthentication() throws Exception { // Validate that we can read that record back out as the user with our custom auth'n final Configuration clientConf = new Configuration(CONF); diff --git a/hbase-examples/src/test/resources/log4j.properties b/hbase-examples/src/test/resources/log4j.properties index c322699ced24..4e5f014bc6cf 100644 --- a/hbase-examples/src/test/resources/log4j.properties +++ b/hbase-examples/src/test/resources/log4j.properties @@ -66,3 +66,4 @@ log4j.logger.org.apache.hadoop.metrics2.impl.MetricsSystemImpl=WARN log4j.logger.org.apache.hadoop.metrics2.util.MBeans=WARN # Enable this to get detailed connection error/retry logging. # log4j.logger.org.apache.hadoop.hbase.client.ConnectionImplementation=TRACE +log4j.logger.org.apache.directory=WARN From 1e39093c92c8a12161d3092d6bc69bb0df323cbc Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Tue, 14 Jan 2020 17:01:10 -0500 Subject: [PATCH 33/41] Add a note for developers to remind them that TokenIdentifier requires ServiceLoader-related updates --- .../design-docs/HBASE-23347-pluggable-authentication.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dev-support/design-docs/HBASE-23347-pluggable-authentication.md b/dev-support/design-docs/HBASE-23347-pluggable-authentication.md index 88117f14478a..f6c72039d43c 100644 --- a/dev-support/design-docs/HBASE-23347-pluggable-authentication.md +++ b/dev-support/design-docs/HBASE-23347-pluggable-authentication.md @@ -157,6 +157,11 @@ In addition to these attributes, a provider also must define the following attri It is allowed (even expected) that there may be multiple providers that use `TOKEN` authentication. +N.b. Hadoop requires all `TokenIdentifier` implements to have a no-args constructor and a `ServiceLoader` +entry in their packaging JAR file (e.g. `META-INF/services/org.apache.hadoop.security.token.TokenIdentifier`). +Otherwise, parsing the `TokenIdentifier` on the server-side end of an RPC from a Hadoop `Token` will return +`null` to the caller (often, in the `CallbackHandler` implementation). + ### Factories To ease development with these unknown set of providers, there are two classes which From f0d64f1e7796d7ea8f542b9839769fcdae15d492 Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Tue, 14 Jan 2020 17:06:18 -0500 Subject: [PATCH 34/41] Start ripping out Text from API, sub for String --- .../AbstractSaslClientAuthenticationProvider.java | 5 ++--- .../provider/AuthenticationProviderSelector.java | 2 +- .../security/provider/BuiltInProviderSelector.java | 10 +++++++--- .../provider/BuiltInSaslAuthenticationProvider.java | 5 ++--- .../security/provider/SaslAuthenticationProvider.java | 2 +- .../TestSaslClientAuthenticationProviders.java | 5 ++--- .../provider/example/ShadeProviderSelector.java | 5 +++-- .../example/ShadeSaslAuthenticationProvider.java | 4 ++-- 8 files changed, 20 insertions(+), 18 deletions(-) diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AbstractSaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AbstractSaslClientAuthenticationProvider.java index d7b07db1d9eb..d018ce19921b 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AbstractSaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AbstractSaslClientAuthenticationProvider.java @@ -18,7 +18,6 @@ package org.apache.hadoop.hbase.security.provider; import org.apache.hadoop.hbase.HBaseInterfaceAudience; -import org.apache.hadoop.io.Text; import org.apache.yetus.audience.InterfaceAudience; import org.apache.yetus.audience.InterfaceStability; @@ -30,11 +29,11 @@ @InterfaceStability.Evolving public abstract class AbstractSaslClientAuthenticationProvider implements SaslClientAuthenticationProvider { - public static final Text AUTH_TOKEN_TYPE = new Text("HBASE_AUTH_TOKEN"); + public static final String AUTH_TOKEN_TYPE = "HBASE_AUTH_TOKEN"; @Override - public final Text getTokenKind() { + public final String getTokenKind() { // All HBase authentication tokens are "HBASE_AUTH_TOKEN"'s. We differentiate between them // via the code(). return AUTH_TOKEN_TYPE; diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java index 7eb31cac171f..db9dd4d55408 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java @@ -46,5 +46,5 @@ void configure(Configuration conf, * from the authentication providers passed in via {@link #configure(Configuration, Collection)}. */ Pair> selectProvider( - Text clusterId, UserGroupInformation ugi); + String clusterId, UserGroupInformation ugi); } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInProviderSelector.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInProviderSelector.java index 670d5c88d702..a8e2899b8bef 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInProviderSelector.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInProviderSelector.java @@ -56,6 +56,7 @@ public class BuiltInProviderSelector implements AuthenticationProviderSelector { SimpleSaslClientAuthenticationProvider simpleAuth = null; GssSaslClientAuthenticationProvider krbAuth = null; DigestSaslClientAuthenticationProvider digestAuth = null; + Text digestAuthTokenKind = null; @Override public void configure( @@ -85,6 +86,7 @@ public void configure( "Encountered multiple DigestSaslClientAuthenticationProvider instances"); } digestAuth = (DigestSaslClientAuthenticationProvider) provider; + digestAuthTokenKind = new Text(digestAuth.getTokenKind()); } else { LOG.warn("Ignoring unknown SaslClientAuthenticationProvider: {}", provider.getClass()); } @@ -97,7 +99,7 @@ public void configure( @Override public Pair> selectProvider( - Text clusterId, UserGroupInformation ugi) { + String clusterId, UserGroupInformation ugi) { Objects.requireNonNull(clusterId, "Null clusterId was given"); // Superfluous: we don't do SIMPLE auth over SASL, but we should to simplify. @@ -105,6 +107,8 @@ public Pair> return new Pair<>(simpleAuth, null); } + final Text clusterIdAsText = clusterId == null ? new Text() : new Text(clusterId); + // Must be digest auth, look for a token. // TestGenerateDelegationToken is written expecting DT is used when DT and Krb are both present. // (for whatever that's worth). @@ -112,8 +116,8 @@ public Pair> // We need to check for two things: // 1. This token is for the HBase cluster we want to talk to // 2. We have suppporting client implementation to handle the token (the "kind" of token) - if (clusterId.equals(token.getService()) && - digestAuth.getTokenKind().equals(token.getKind())) { + if (clusterIdAsText.equals(token.getService()) && + digestAuthTokenKind.equals(token.getKind())) { return new Pair<>(digestAuth, token); } } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInSaslAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInSaslAuthenticationProvider.java index 59d83497de82..c1b7ddb7c554 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInSaslAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInSaslAuthenticationProvider.java @@ -17,7 +17,6 @@ */ package org.apache.hadoop.hbase.security.provider; -import org.apache.hadoop.io.Text; import org.apache.yetus.audience.InterfaceAudience; /** @@ -31,10 +30,10 @@ @InterfaceAudience.Private public abstract class BuiltInSaslAuthenticationProvider implements SaslAuthenticationProvider { - public static final Text AUTH_TOKEN_TYPE = new Text("HBASE_AUTH_TOKEN"); + public static final String AUTH_TOKEN_TYPE = "HBASE_AUTH_TOKEN"; @Override - public Text getTokenKind() { + public String getTokenKind() { return AUTH_TOKEN_TYPE; } } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslAuthenticationProvider.java index 739d8125d2c3..db961a526cba 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslAuthenticationProvider.java @@ -43,5 +43,5 @@ public interface SaslAuthenticationProvider { /** * Returns the name of the type used by the TokenIdentifier. */ - Text getTokenKind(); + String getTokenKind(); } diff --git a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslClientAuthenticationProviders.java b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslClientAuthenticationProviders.java index bfd0aa6615ca..c503a20c7c51 100644 --- a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslClientAuthenticationProviders.java +++ b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslClientAuthenticationProviders.java @@ -34,7 +34,6 @@ import org.apache.hadoop.hbase.security.SecurityInfo; import org.apache.hadoop.hbase.testclassification.SecurityTests; import org.apache.hadoop.hbase.testclassification.SmallTests; -import org.apache.hadoop.io.Text; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; import org.apache.hadoop.security.token.Token; @@ -101,7 +100,7 @@ static class ConflictingProvider1 implements SaslClientAuthenticationProvider { return METHOD1; } - @Override public Text getTokenKind() { + @Override public String getTokenKind() { return null; } @@ -123,7 +122,7 @@ static class ConflictingProvider2 implements SaslClientAuthenticationProvider { return METHOD2; } - @Override public Text getTokenKind() { + @Override public String getTokenKind() { return null; } diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeProviderSelector.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeProviderSelector.java index fbc61a01696d..df0ca87a4f36 100644 --- a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeProviderSelector.java +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeProviderSelector.java @@ -33,6 +33,7 @@ @InterfaceAudience.Private public class ShadeProviderSelector extends BuiltInProviderSelector { + private final Text SHADE_TOKEN_KIND_TEXT = new Text(ShadeSaslAuthenticationProvider.TOKEN_KIND); private ShadeSaslClientAuthenticationProvider shade; @Override @@ -49,12 +50,12 @@ public void configure( @Override public Pair> selectProvider( - Text clusterId, UserGroupInformation ugi) { + String clusterId, UserGroupInformation ugi) { Pair> pair = super.selectProvider(clusterId, ugi); Optional> optional = ugi.getTokens().stream() - .filter((t) -> ShadeSaslAuthenticationProvider.TOKEN_KIND.equals(t.getKind())) + .filter((t) -> SHADE_TOKEN_KIND_TEXT.equals(t.getKind())) .findFirst(); if (optional.isPresent()) { return new Pair<>(shade, optional.get()); diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslAuthenticationProvider.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslAuthenticationProvider.java index 4f1cd812c416..35d3c74115c2 100644 --- a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslAuthenticationProvider.java +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslAuthenticationProvider.java @@ -27,13 +27,13 @@ public abstract class ShadeSaslAuthenticationProvider implements SaslAuthenticationProvider { public static final SaslAuthMethod METHOD = new SaslAuthMethod( "SHADE", (byte) 15, "PLAIN", AuthenticationMethod.TOKEN); - public static final Text TOKEN_KIND = new Text("HBASE_EXAMPLE_SHADE_TOKEN"); + public static final String TOKEN_KIND = "HBASE_EXAMPLE_SHADE_TOKEN"; @Override public SaslAuthMethod getSaslAuthMethod() { return METHOD; } - @Override public Text getTokenKind() { + @Override public String getTokenKind() { return TOKEN_KIND; } } From 37ad980bb83976e14f7017612fe547a0c2e02484 Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Tue, 14 Jan 2020 17:11:54 -0500 Subject: [PATCH 35/41] Some more text, some more cleanup --- .../hadoop/hbase/ipc/RpcConnection.java | 2 +- .../AuthenticationProviderSelector.java | 3 +-- .../provider/SaslAuthenticationProvider.java | 1 - .../SaslClientAuthenticationProviders.java | 3 +-- .../provider/example/SaslPlainServer.java | 20 +++++++++---------- .../example/ShadeClientTokenUtil.java | 2 +- 6 files changed, 14 insertions(+), 17 deletions(-) diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java index 5bd3d3683204..8e7831a898f6 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java @@ -104,7 +104,7 @@ protected RpcConnection(Configuration conf, HashedWheelTimer timeoutTimer, Conne SaslClientAuthenticationProviders.getInstance(conf); Pair> pair; if (useSasl && securityInfo != null) { - pair = providers.selectProvider(new Text(clusterId), ticket); + pair = providers.selectProvider(clusterId, ticket); if (pair == null) { if (LOG.isTraceEnabled()) { LOG.trace("Found no valid authentication method from providers={} with tokens={}", diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java index db9dd4d55408..5c518c16fa4b 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java @@ -22,7 +22,6 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseInterfaceAudience; import org.apache.hadoop.hbase.util.Pair; -import org.apache.hadoop.io.Text; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; @@ -36,7 +35,7 @@ public interface AuthenticationProviderSelector { /** * Initializes the implementation with configuration and a set of providers available. This method * should be called exactly once per implementation prior to calling - * {@link #selectProvider(Text, UserGroupInformation)}. + * {@link #selectProvider(String, UserGroupInformation)}. */ void configure(Configuration conf, Collection availableProviders); diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslAuthenticationProvider.java index db961a526cba..1f6d821ce953 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslAuthenticationProvider.java @@ -18,7 +18,6 @@ package org.apache.hadoop.hbase.security.provider; import org.apache.hadoop.hbase.HBaseInterfaceAudience; -import org.apache.hadoop.io.Text; import org.apache.yetus.audience.InterfaceAudience; import org.apache.yetus.audience.InterfaceStability; diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java index 10bf6cf351b8..3068a0b8ac3d 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java @@ -28,7 +28,6 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseInterfaceAudience; import org.apache.hadoop.hbase.util.Pair; -import org.apache.hadoop.io.Text; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; @@ -208,7 +207,7 @@ static SaslClientAuthenticationProviders instantiate(Configuration conf) { * identifier and the user. */ public Pair> selectProvider( - Text clusterId, UserGroupInformation clientUser) { + String clusterId, UserGroupInformation clientUser) { return selector.selectProvider(clusterId, clientUser); } diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/SaslPlainServer.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/SaslPlainServer.java index 024d6181d5d4..b335678cc333 100644 --- a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/SaslPlainServer.java +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/SaslPlainServer.java @@ -50,7 +50,7 @@ public static class SaslPlainServerFactory implements SaslServerFactory { public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Map props, CallbackHandler cbh) throws SaslException { - return "PLAIN".equals(mechanism) ? new SaslPlainServer(cbh) : null; + return "PLAIN".equals(mechanism) ? new SaslPlainServer(cbh) : null; } @Override public String[] getMechanismNames(Map props){ @@ -59,7 +59,7 @@ public String[] getMechanismNames(Map props){ : new String[0]; } } - + private CallbackHandler cbh; private boolean completed; private String authz; @@ -72,7 +72,7 @@ public String[] getMechanismNames(Map props){ public String getMechanismName() { return "PLAIN"; } - + @Override public byte[] evaluateResponse(byte[] response) throws SaslException { if (completed) { @@ -119,7 +119,7 @@ private void throwIfNotComplete() { throw new IllegalStateException("PLAIN authentication not completed"); } } - + @Override public boolean isComplete() { return completed; @@ -130,19 +130,19 @@ public String getAuthorizationID() { throwIfNotComplete(); return authz; } - + @Override public Object getNegotiatedProperty(String propName) { - throwIfNotComplete(); + throwIfNotComplete(); return Sasl.QOP.equals(propName) ? "auth" : null; } - + @Override public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException { throwIfNotComplete(); throw new IllegalStateException( - "PLAIN supports neither integrity nor privacy"); + "PLAIN supports neither integrity nor privacy"); } @Override @@ -150,9 +150,9 @@ public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException { throwIfNotComplete(); throw new IllegalStateException( - "PLAIN supports neither integrity nor privacy"); + "PLAIN supports neither integrity nor privacy"); } - + @Override public void dispose() throws SaslException { cbh = null; diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeClientTokenUtil.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeClientTokenUtil.java index a2ad5eab8bf7..364409fa4fbb 100644 --- a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeClientTokenUtil.java +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeClientTokenUtil.java @@ -38,7 +38,7 @@ public static Token obtainToken( Connection conn, String username, char[] password) throws IOException { ShadeTokenIdentifier identifier = new ShadeTokenIdentifier(username); return new Token<>(identifier.getBytes(), Bytes.toBytes(new String(password)), - ShadeSaslAuthenticationProvider.TOKEN_KIND, + new Text(ShadeSaslAuthenticationProvider.TOKEN_KIND), new Text(conn.getAdmin().getClusterMetrics().getClusterId())); } } From 60b028900352247d01bef891f568c5085db7445c Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Wed, 15 Jan 2020 11:29:20 -0500 Subject: [PATCH 36/41] A couple of straggling Text's still hanging around. --- .../hbase/security/provider/example/ShadeTokenIdentifier.java | 3 ++- .../provider/TestCustomSaslAuthenticationProvider.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeTokenIdentifier.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeTokenIdentifier.java index d7bb56dc0e73..225532384654 100644 --- a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeTokenIdentifier.java +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeTokenIdentifier.java @@ -30,6 +30,7 @@ @InterfaceAudience.Private public class ShadeTokenIdentifier extends TokenIdentifier { + private static final Text TEXT_TOKEN_KIND = new Text(ShadeSaslAuthenticationProvider.TOKEN_KIND); private String username; public ShadeTokenIdentifier() { @@ -52,7 +53,7 @@ public void readFields(DataInput in) throws IOException { @Override public Text getKind() { - return ShadeSaslAuthenticationProvider.TOKEN_KIND; + return TEXT_TOKEN_KIND; } @Override diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java index d7ed9753124f..9de6917d06cc 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java @@ -384,7 +384,7 @@ public void configure(Configuration conf, @Override public Pair> selectProvider( - Text clusterId, UserGroupInformation ugi) { + String clusterId, UserGroupInformation ugi) { Pair> superPair = super.selectProvider(clusterId, ugi); From 664b7bcd178857e27ff12394aa878eedb873f2fb Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Wed, 15 Jan 2020 11:29:32 -0500 Subject: [PATCH 37/41] Add SaslServerAuthenticationProvider#init(Configuration) Adds a unit test to have some general confidence about this being called automatically. Cleans up the provided Providers a little (most don't need to do any initialization). --- ...ShadeSaslServerAuthenticationProvider.java | 10 ++- .../hbase/security/HBaseSaslRpcServer.java | 2 +- ...igestSaslServerAuthenticationProvider.java | 3 +- .../GssSaslServerAuthenticationProvider.java | 3 +- .../SaslServerAuthenticationProvider.java | 8 +- .../SaslServerAuthenticationProviders.java | 14 ++++ ...impleSaslServerAuthenticationProvider.java | 3 +- .../TestCustomSaslAuthenticationProvider.java | 2 +- ...TestSaslServerAuthenticationProviders.java | 74 +++++++++++++++++++ 9 files changed, 106 insertions(+), 13 deletions(-) diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslServerAuthenticationProvider.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslServerAuthenticationProvider.java index ce54ee10acc2..d677b22cdbfe 100644 --- a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslServerAuthenticationProvider.java +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslServerAuthenticationProvider.java @@ -58,13 +58,17 @@ public class ShadeSaslServerAuthenticationProvider extends ShadeSaslAuthenticati static final char SEPARATOR = '='; private AtomicReference attemptingUser = new AtomicReference<>(null); + private Map passwordDatabase; @Override - public AttemptingUserProvidingSaslServer createServer(Configuration conf, + public void init(Configuration conf) throws IOException { + passwordDatabase = readPasswordDB(conf); + } + + @Override + public AttemptingUserProvidingSaslServer createServer( SecretManager secretManager, Map saslProps) throws IOException { - Map passwordDatabase = readPasswordDB(conf); - return new AttemptingUserProvidingSaslServer( new SaslPlainServer( new ShadeSaslServerCallbackHandler(attemptingUser, passwordDatabase)), diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcServer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcServer.java index 2fa43082c13b..7ee24c7e49a4 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcServer.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcServer.java @@ -53,7 +53,7 @@ public class HBaseSaslRpcServer { public HBaseSaslRpcServer(Configuration conf, SaslServerAuthenticationProvider provider, Map saslProps, SecretManager secretManager) throws IOException { - serverWithProvider = provider.createServer(conf, secretManager, saslProps); + serverWithProvider = provider.createServer(secretManager, saslProps); saslServer = serverWithProvider.getServer(); } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java index de9f2cfc6628..b3236d653764 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslServerAuthenticationProvider.java @@ -31,7 +31,6 @@ import javax.security.sasl.Sasl; import javax.security.sasl.SaslServer; -import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.security.AccessDeniedException; import org.apache.hadoop.hbase.security.HBaseSaslRpcServer; import org.apache.hadoop.hbase.security.SaslUtil; @@ -52,7 +51,7 @@ public class DigestSaslServerAuthenticationProvider extends DigestSaslAuthentica private AtomicReference attemptingUser = new AtomicReference<>(null); @Override - public AttemptingUserProvidingSaslServer createServer(Configuration conf, + public AttemptingUserProvidingSaslServer createServer( SecretManager secretManager, Map saslProps) throws IOException { if (secretManager == null) { diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java index 87f671c672d5..8a542c69c0dc 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslServerAuthenticationProvider.java @@ -28,7 +28,6 @@ import javax.security.sasl.Sasl; import javax.security.sasl.SaslException; -import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.security.AccessDeniedException; import org.apache.hadoop.hbase.security.SaslUtil; import org.apache.hadoop.security.UserGroupInformation; @@ -45,7 +44,7 @@ public class GssSaslServerAuthenticationProvider extends GssSaslAuthenticationPr GssSaslServerAuthenticationProvider.class); @Override - public AttemptingUserProvidingSaslServer createServer(Configuration conf, + public AttemptingUserProvidingSaslServer createServer( SecretManager secretManager, Map saslProps) throws IOException { UserGroupInformation current = UserGroupInformation.getCurrentUser(); diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProvider.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProvider.java index 6eb365f00459..3487cfcd586e 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProvider.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProvider.java @@ -36,11 +36,15 @@ @InterfaceStability.Evolving public interface SaslServerAuthenticationProvider extends SaslAuthenticationProvider { + /** + * Allows implementations to initialize themselves, prior to creating a server. + */ + default void init(Configuration conf) throws IOException {} + /** * Creates the SaslServer to accept incoming SASL authentication requests. */ - AttemptingUserProvidingSaslServer createServer(Configuration conf, - SecretManager secretManager, + AttemptingUserProvidingSaslServer createServer(SecretManager secretManager, Map saslProps) throws IOException; boolean supportsProtocolAuthentication(); diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProviders.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProviders.java index cd4eb00ba8b8..d1c22b7b4550 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProviders.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SaslServerAuthenticationProviders.java @@ -17,6 +17,7 @@ */ package org.apache.hadoop.hbase.security.provider; +import java.io.IOException; import java.util.HashMap; import java.util.Optional; import java.util.ServiceLoader; @@ -150,6 +151,19 @@ static SaslServerAuthenticationProviders createProviders(Configuration conf) { } LOG.trace("Found SaslServerAuthenticationProviders {}", loadedProviders); } + + // Initialize the providers once, before we get into the RPC path. + providers.forEach((b,provider) -> { + try { + // Give them a copy, just to make sure there is no funny-business going on. + provider.init(new Configuration(conf)); + } catch (IOException e) { + LOG.error("Failed to initialize {}", provider.getClass(), e); + throw new RuntimeException( + "Failed to initialize " + provider.getClass().getName(), e); + } + }); + return new SaslServerAuthenticationProviders(conf, providers); } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java index 2cf3bdab7970..ed7bf4ce9e76 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslServerAuthenticationProvider.java @@ -20,7 +20,6 @@ import java.io.IOException; import java.util.Map; -import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.SecretManager; import org.apache.hadoop.security.token.TokenIdentifier; @@ -31,7 +30,7 @@ public class SimpleSaslServerAuthenticationProvider extends SimpleSaslAuthentica implements SaslServerAuthenticationProvider { @Override - public AttemptingUserProvidingSaslServer createServer(Configuration conf, + public AttemptingUserProvidingSaslServer createServer( SecretManager secretManager, Map saslProps) throws IOException { throw new RuntimeException("HBase SIMPLE authentication doesn't use SASL"); diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java index 9de6917d06cc..644aefd1299e 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java @@ -274,7 +274,7 @@ public static class InMemoryServerProvider extends InMemoryClientProvider implements SaslServerAuthenticationProvider { @Override - public AttemptingUserProvidingSaslServer createServer(Configuration conf, + public AttemptingUserProvidingSaslServer createServer( SecretManager secretManager, Map saslProps) throws IOException { return new AttemptingUserProvidingSaslServer( diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslServerAuthenticationProviders.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslServerAuthenticationProviders.java index e899dc001b1c..aa7b834116a1 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslServerAuthenticationProviders.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslServerAuthenticationProviders.java @@ -20,14 +20,22 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import java.io.IOException; import java.util.HashMap; +import java.util.Map; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseClassTestRule; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.testclassification.SecurityTests; import org.apache.hadoop.hbase.testclassification.SmallTests; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; +import org.apache.hadoop.security.token.SecretManager; +import org.apache.hadoop.security.token.TokenIdentifier; +import org.junit.Before; import org.junit.ClassRule; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -39,6 +47,12 @@ public class TestSaslServerAuthenticationProviders { public static final HBaseClassTestRule CLASS_RULE = HBaseClassTestRule.forClass(TestSaslServerAuthenticationProviders.class); + @Before + public void reset() { + // Clear out any potentially bogus state from the providers class + SaslServerAuthenticationProviders.reset(); + } + @Test public void testCannotAddTheSameProviderTwice() { HashMap registeredProviders = new HashMap<>(); @@ -72,4 +86,64 @@ public void testInstanceIsCached() { assertNotSame(providers1, providers3); assertEquals(providers1.getNumRegisteredProviders(), providers3.getNumRegisteredProviders()); } + + @Test + public void instancesAreInitialized() { + Configuration conf = HBaseConfiguration.create(); + conf.set(SaslServerAuthenticationProviders.EXTRA_PROVIDERS_KEY, + InitCheckingSaslServerAuthenticationProvider.class.getName()); + + SaslServerAuthenticationProviders providers = + SaslServerAuthenticationProviders.getInstance(conf); + + SaslServerAuthenticationProvider provider = + providers.selectProvider(InitCheckingSaslServerAuthenticationProvider.ID); + assertEquals(InitCheckingSaslServerAuthenticationProvider.class, provider.getClass()); + + assertTrue("Provider was not inititalized", + ((InitCheckingSaslServerAuthenticationProvider) provider).isInitialized()); + } + + public static class InitCheckingSaslServerAuthenticationProvider + implements SaslServerAuthenticationProvider { + public static final byte ID = (byte)88; + private boolean initialized = false; + + public synchronized void init(Configuration conf) { + this.initialized = true; + } + + public synchronized boolean isInitialized() { + return initialized; + } + + @Override + public SaslAuthMethod getSaslAuthMethod() { + return new SaslAuthMethod("INIT_CHECKING", ID, "DIGEST-MD5", AuthenticationMethod.TOKEN); + } + + @Override + public String getTokenKind() { + return "INIT_CHECKING_TOKEN"; + } + + @Override + public AttemptingUserProvidingSaslServer createServer( + SecretManager secretManager, + Map saslProps) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean supportsProtocolAuthentication() { + return false; + } + + @Override + public UserGroupInformation getAuthorizedUgi( + String authzId, SecretManager secretManager) + throws IOException { + throw new UnsupportedOperationException(); + } + } } From af3c6e78f9292e532766a3be9b3cf9e9b9eb9274 Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Wed, 15 Jan 2020 12:25:09 -0500 Subject: [PATCH 38/41] Fix up some broken tests --- .../security/provider/TestDefaultProviderSelector.java | 9 +++++++++ .../example/TestShadeSaslAuthenticationProvider.java | 3 +-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestDefaultProviderSelector.java b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestDefaultProviderSelector.java index 1a6b316111e2..eff3b5f8dd0a 100644 --- a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestDefaultProviderSelector.java +++ b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestDefaultProviderSelector.java @@ -25,11 +25,20 @@ import java.util.Set; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HBaseClassTestRule; +import org.apache.hadoop.hbase.testclassification.SmallTests; import org.junit.Before; +import org.junit.ClassRule; import org.junit.Test; +import org.junit.experimental.categories.Category; +@Category({SmallTests.class}) public class TestDefaultProviderSelector { + @ClassRule + public static final HBaseClassTestRule CLASS_RULE = + HBaseClassTestRule.forClass(TestDefaultProviderSelector.class); + BuiltInProviderSelector selector; @Before public void setup() { diff --git a/hbase-examples/src/test/java/org/apache/hadoop/hbase/security/provider/example/TestShadeSaslAuthenticationProvider.java b/hbase-examples/src/test/java/org/apache/hadoop/hbase/security/provider/example/TestShadeSaslAuthenticationProvider.java index 65ec8e16942f..21dd98ae860d 100644 --- a/hbase-examples/src/test/java/org/apache/hadoop/hbase/security/provider/example/TestShadeSaslAuthenticationProvider.java +++ b/hbase-examples/src/test/java/org/apache/hadoop/hbase/security/provider/example/TestShadeSaslAuthenticationProvider.java @@ -56,7 +56,6 @@ import org.apache.hadoop.hbase.security.HBaseKerberosUtils; import org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProviders; import org.apache.hadoop.hbase.security.provider.SaslServerAuthenticationProviders; -import org.apache.hadoop.hbase.security.token.SecureTestCluster; import org.apache.hadoop.hbase.security.token.TokenProvider; import org.apache.hadoop.hbase.testclassification.MediumTests; import org.apache.hadoop.hbase.testclassification.SecurityTests; @@ -91,7 +90,7 @@ static LocalHBaseCluster createCluster(HBaseTestingUtility util, File keytabFile HBaseKerberosUtils.setSecuredConfiguration(util.getConfiguration(), servicePrincipal + "@" + kdc.getRealm(), spnegoPrincipal + "@" + kdc.getRealm()); - HBaseKerberosUtils.setSSLConfiguration(util, SecureTestCluster.class); + HBaseKerberosUtils.setSSLConfiguration(util, TestShadeSaslAuthenticationProvider.class); util.getConfiguration().setStrings(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY, TokenProvider.class.getName()); From 68ceacd77b1ec137dcdfa39382a8fc56b7b7ad04 Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Wed, 15 Jan 2020 13:33:53 -0500 Subject: [PATCH 39/41] Swap UGI for User --- .../org/apache/hadoop/hbase/ipc/RpcConnection.java | 7 +++---- .../provider/AuthenticationProviderSelector.java | 6 +++--- .../security/provider/BuiltInProviderSelector.java | 14 ++++++++------ .../DigestSaslClientAuthenticationProvider.java | 4 ++-- .../GssSaslClientAuthenticationProvider.java | 4 ++-- .../provider/SaslClientAuthenticationProvider.java | 2 +- .../SaslClientAuthenticationProviders.java | 4 ++-- .../SimpleSaslClientAuthenticationProvider.java | 10 ++++++---- .../TestSaslClientAuthenticationProviders.java | 6 +++--- .../hadoop/hbase/HBaseClassTestRuleChecker.java | 8 +++++--- .../security/provider/example/SaslPlainServer.java | 6 +++--- .../provider/example/ShadeProviderSelector.java | 8 ++++---- .../example/ShadeSaslAuthenticationProvider.java | 3 +-- .../ShadeSaslClientAuthenticationProvider.java | 8 ++++---- .../TestShadeSaslAuthenticationProvider.java | 4 ++-- .../TestCustomSaslAuthenticationProvider.java | 13 +++++++------ 16 files changed, 56 insertions(+), 51 deletions(-) diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java index 8e7831a898f6..195a16d16d36 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/RpcConnection.java @@ -26,13 +26,12 @@ import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.codec.Codec; import org.apache.hadoop.hbase.security.SecurityInfo; +import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider; import org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProviders; import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; import org.apache.hadoop.hbase.util.Pair; -import org.apache.hadoop.io.Text; import org.apache.hadoop.io.compress.CompressionCodec; -import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; import org.apache.yetus.audience.InterfaceAudience; @@ -95,7 +94,7 @@ protected RpcConnection(Configuration conf, HashedWheelTimer timeoutTimer, Conne this.compressor = compressor; this.conf = conf; - UserGroupInformation ticket = remoteId.getTicket().getUGI(); + User ticket = remoteId.getTicket(); this.securityInfo = SecurityInfo.getInfo(remoteId.getServiceName()); this.useSasl = isSecurityEnabled; @@ -162,7 +161,7 @@ protected byte[] getConnectionHeaderPreamble() { protected ConnectionHeader getConnectionHeader() { final ConnectionHeader.Builder builder = ConnectionHeader.newBuilder(); builder.setServiceName(remoteId.getServiceName()); - final UserInformation userInfoPB = provider.getUserInfo(remoteId.ticket.getUGI()); + final UserInformation userInfoPB = provider.getUserInfo(remoteId.ticket); if (userInfoPB != null) { builder.setUserInfo(userInfoPB); } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java index 5c518c16fa4b..a681d53719d0 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/AuthenticationProviderSelector.java @@ -21,8 +21,8 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseInterfaceAudience; +import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.hbase.util.Pair; -import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; import org.apache.yetus.audience.InterfaceAudience; @@ -35,7 +35,7 @@ public interface AuthenticationProviderSelector { /** * Initializes the implementation with configuration and a set of providers available. This method * should be called exactly once per implementation prior to calling - * {@link #selectProvider(String, UserGroupInformation)}. + * {@link #selectProvider(String, User)}. */ void configure(Configuration conf, Collection availableProviders); @@ -45,5 +45,5 @@ void configure(Configuration conf, * from the authentication providers passed in via {@link #configure(Configuration, Collection)}. */ Pair> selectProvider( - String clusterId, UserGroupInformation ugi); + String clusterId, User user); } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInProviderSelector.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInProviderSelector.java index a8e2899b8bef..30fe6ffad1bb 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInProviderSelector.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInProviderSelector.java @@ -17,6 +17,8 @@ */ package org.apache.hadoop.hbase.security.provider; +import static java.util.Objects.requireNonNull; + import java.util.Collection; import java.util.Objects; @@ -27,7 +29,6 @@ import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.hbase.util.Pair; import org.apache.hadoop.io.Text; -import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; import org.apache.yetus.audience.InterfaceAudience; @@ -45,7 +46,7 @@ * implement their own {@link AuthenticationProviderSelector} when writing a custom provider. * * This implementation is not thread-safe. {@link #configure(Configuration, Collection)} and - * {@link #selectProvider(Text, UserGroupInformation)} is not safe if they are called concurrently. + * {@link #selectProvider(Text, User)} is not safe if they are called concurrently. */ @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) @NotThreadSafe @@ -99,8 +100,9 @@ public void configure( @Override public Pair> selectProvider( - String clusterId, UserGroupInformation ugi) { - Objects.requireNonNull(clusterId, "Null clusterId was given"); + String clusterId, User user) { + requireNonNull(clusterId, "Null clusterId was given"); + requireNonNull(user, "Null user was given"); // Superfluous: we don't do SIMPLE auth over SASL, but we should to simplify. if (!User.isHBaseSecurityEnabled(conf)) { @@ -112,7 +114,7 @@ public Pair> // Must be digest auth, look for a token. // TestGenerateDelegationToken is written expecting DT is used when DT and Krb are both present. // (for whatever that's worth). - for (Token token : ugi.getTokens()) { + for (Token token : user.getTokens()) { // We need to check for two things: // 1. This token is for the HBase cluster we want to talk to // 2. We have suppporting client implementation to handle the token (the "kind" of token) @@ -121,7 +123,7 @@ public Pair> return new Pair<>(digestAuth, token); } } - if (ugi.hasKerberosCredentials()) { + if (user.getUGI().hasKerberosCredentials()) { return new Pair<>(krbAuth, null); } LOG.debug( diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java index 1df9bc6fb9a2..a84f24b9080e 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/DigestSaslClientAuthenticationProvider.java @@ -34,7 +34,7 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.security.SaslUtil; import org.apache.hadoop.hbase.security.SecurityInfo; -import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; import org.apache.yetus.audience.InterfaceAudience; @@ -100,7 +100,7 @@ public void handle(Callback[] callbacks) throws UnsupportedCallbackException { } @Override - public UserInformation getUserInfo(UserGroupInformation user) { + public UserInformation getUserInfo(User user) { // Don't send user for token auth. Copied from RpcConnection. return null; } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java index 4815aad7b3c5..2db865d8cf3a 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/GssSaslClientAuthenticationProvider.java @@ -71,10 +71,10 @@ public SaslClient createClient(Configuration conf, InetAddress serverAddr, } @Override - public UserInformation getUserInfo(UserGroupInformation user) { + public UserInformation getUserInfo(User user) { UserInformation.Builder userInfoPB = UserInformation.newBuilder(); // Send effective user for Kerberos auth - userInfoPB.setEffectiveUser(user.getUserName()); + userInfoPB.setEffectiveUser(user.getUGI().getUserName()); return userInfoPB.build(); } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProvider.java index b1800ccbabff..4b1cabcfc494 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProvider.java @@ -57,7 +57,7 @@ SaslClient createClient(Configuration conf, InetAddress serverAddr, SecurityInfo /** * Constructs a {@link UserInformation} from the given {@link UserGroupInformation} */ - UserInformation getUserInfo(UserGroupInformation user); + UserInformation getUserInfo(User user); /** * Returns the "real" user, the user who has the credentials being authenticated by the diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java index 3068a0b8ac3d..56c36e8eae70 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SaslClientAuthenticationProviders.java @@ -27,8 +27,8 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseInterfaceAudience; +import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.hbase.util.Pair; -import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; import org.apache.yetus.audience.InterfaceAudience; @@ -207,7 +207,7 @@ static SaslClientAuthenticationProviders instantiate(Configuration conf) { * identifier and the user. */ public Pair> selectProvider( - String clusterId, UserGroupInformation clientUser) { + String clusterId, User clientUser) { return selector.selectProvider(clusterId, clientUser); } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslClientAuthenticationProvider.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslClientAuthenticationProvider.java index e723f3c6e11d..3a9142f34c44 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslClientAuthenticationProvider.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/SimpleSaslClientAuthenticationProvider.java @@ -25,6 +25,7 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.security.SecurityInfo; +import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; @@ -44,12 +45,13 @@ public SaslClient createClient(Configuration conf, InetAddress serverAddress, } @Override - public UserInformation getUserInfo(UserGroupInformation user) { + public UserInformation getUserInfo(User user) { + final UserGroupInformation ugi = user.getUGI(); UserInformation.Builder userInfoPB = UserInformation.newBuilder(); // Send both effective user and real user for simple auth - userInfoPB.setEffectiveUser(user.getUserName()); - if (user.getRealUser() != null) { - userInfoPB.setRealUser(user.getRealUser().getUserName()); + userInfoPB.setEffectiveUser(ugi.getUserName()); + if (ugi.getRealUser() != null) { + userInfoPB.setRealUser(ugi.getRealUser().getUserName()); } return userInfoPB.build(); } diff --git a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslClientAuthenticationProviders.java b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslClientAuthenticationProviders.java index c503a20c7c51..c1317028d35a 100644 --- a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslClientAuthenticationProviders.java +++ b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/provider/TestSaslClientAuthenticationProviders.java @@ -32,9 +32,9 @@ import org.apache.hadoop.hbase.HBaseClassTestRule; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.security.SecurityInfo; +import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.hbase.testclassification.SecurityTests; import org.apache.hadoop.hbase.testclassification.SmallTests; -import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; @@ -110,7 +110,7 @@ static class ConflictingProvider1 implements SaslClientAuthenticationProvider { return null; } - @Override public UserInformation getUserInfo(UserGroupInformation user) { + @Override public UserInformation getUserInfo(User user) { return null; } } @@ -132,7 +132,7 @@ static class ConflictingProvider2 implements SaslClientAuthenticationProvider { return null; } - @Override public UserInformation getUserInfo(UserGroupInformation user) { + @Override public UserInformation getUserInfo(User user) { return null; } } diff --git a/hbase-common/src/test/java/org/apache/hadoop/hbase/HBaseClassTestRuleChecker.java b/hbase-common/src/test/java/org/apache/hadoop/hbase/HBaseClassTestRuleChecker.java index 2508a27ee555..c8d2cdbe495e 100644 --- a/hbase-common/src/test/java/org/apache/hadoop/hbase/HBaseClassTestRuleChecker.java +++ b/hbase-common/src/test/java/org/apache/hadoop/hbase/HBaseClassTestRuleChecker.java @@ -41,9 +41,11 @@ public class HBaseClassTestRuleChecker extends RunListener { @Override public void testStarted(Description description) throws Exception { Category[] categories = description.getTestClass().getAnnotationsByType(Category.class); - for (Class c : categories[0].value()) { - if (c == IntegrationTests.class) { - return; + if (categories.length > 1) { + for (Class c : categories[0].value()) { + if (c == IntegrationTests.class) { + return; + } } } for (Field field : description.getTestClass().getFields()) { diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/SaslPlainServer.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/SaslPlainServer.java index b335678cc333..a826e7a197ed 100644 --- a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/SaslPlainServer.java +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/SaslPlainServer.java @@ -96,13 +96,13 @@ public byte[] evaluateResponse(byte[] response) throws SaslException { if (parts[0].isEmpty()) { // authz = authn parts[0] = parts[1]; } - + NameCallback nc = new NameCallback("SASL PLAIN"); nc.setName(parts[1]); PasswordCallback pc = new PasswordCallback("SASL PLAIN", false); pc.setPassword(parts[2].toCharArray()); AuthorizeCallback ac = new AuthorizeCallback(parts[1], parts[0]); - cbh.handle(new Callback[]{nc, pc, ac}); + cbh.handle(new Callback[]{nc, pc, ac}); if (ac.isAuthorized()) { authz = ac.getAuthorizedID(); } @@ -144,7 +144,7 @@ public byte[] wrap(byte[] outgoing, int offset, int len) throw new IllegalStateException( "PLAIN supports neither integrity nor privacy"); } - + @Override public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException { diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeProviderSelector.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeProviderSelector.java index df0ca87a4f36..6e369f946ee3 100644 --- a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeProviderSelector.java +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeProviderSelector.java @@ -21,11 +21,11 @@ import java.util.Optional; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.hbase.security.provider.BuiltInProviderSelector; import org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider; import org.apache.hadoop.hbase.util.Pair; import org.apache.hadoop.io.Text; -import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; import org.apache.yetus.audience.InterfaceAudience; @@ -50,11 +50,11 @@ public void configure( @Override public Pair> selectProvider( - String clusterId, UserGroupInformation ugi) { + String clusterId, User user) { Pair> pair = - super.selectProvider(clusterId, ugi); + super.selectProvider(clusterId, user); - Optional> optional = ugi.getTokens().stream() + Optional> optional = user.getTokens().stream() .filter((t) -> SHADE_TOKEN_KIND_TEXT.equals(t.getKind())) .findFirst(); if (optional.isPresent()) { diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslAuthenticationProvider.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslAuthenticationProvider.java index 35d3c74115c2..80f7acee7b23 100644 --- a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslAuthenticationProvider.java +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslAuthenticationProvider.java @@ -19,7 +19,6 @@ import org.apache.hadoop.hbase.security.provider.SaslAuthMethod; import org.apache.hadoop.hbase.security.provider.SaslAuthenticationProvider; -import org.apache.hadoop.io.Text; import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; import org.apache.yetus.audience.InterfaceAudience; @@ -34,6 +33,6 @@ public abstract class ShadeSaslAuthenticationProvider implements SaslAuthenticat } @Override public String getTokenKind() { - return TOKEN_KIND; + return TOKEN_KIND; } } diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslClientAuthenticationProvider.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslClientAuthenticationProvider.java index 8c4c992eb946..e7821d4964b7 100644 --- a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslClientAuthenticationProvider.java +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslClientAuthenticationProvider.java @@ -34,6 +34,7 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.security.SaslUtil; import org.apache.hadoop.hbase.security.SecurityInfo; +import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider; import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; import org.apache.hadoop.hbase.util.Bytes; @@ -55,9 +56,9 @@ public SaslClient createClient(Configuration conf, InetAddress serverAddr, } @Override - public UserInformation getUserInfo(UserGroupInformation user) { + public UserInformation getUserInfo(User user) { UserInformation.Builder userInfoPB = UserInformation.newBuilder(); - userInfoPB.setEffectiveUser(user.getUserName()); + userInfoPB.setEffectiveUser(user.getUGI().getUserName()); return userInfoPB.build(); } @@ -71,8 +72,7 @@ public ShadeSaslClientCallbackHandler( // Something is wrong with the environment if we can't get our Identifier back out. throw new IllegalStateException("Could not extract Identifier from Token"); } - UserGroupInformation ugi = id.getUser(); - this.username = ugi.getUserName(); + this.username = id.getUser().getUserName(); this.password = Bytes.toString(token.getPassword()).toCharArray(); } diff --git a/hbase-examples/src/test/java/org/apache/hadoop/hbase/security/provider/example/TestShadeSaslAuthenticationProvider.java b/hbase-examples/src/test/java/org/apache/hadoop/hbase/security/provider/example/TestShadeSaslAuthenticationProvider.java index 21dd98ae860d..001842f22c89 100644 --- a/hbase-examples/src/test/java/org/apache/hadoop/hbase/security/provider/example/TestShadeSaslAuthenticationProvider.java +++ b/hbase-examples/src/test/java/org/apache/hadoop/hbase/security/provider/example/TestShadeSaslAuthenticationProvider.java @@ -97,7 +97,7 @@ static LocalHBaseCluster createCluster(HBaseTestingUtility util, File keytabFile util.startMiniDFSCluster(1); Path testDir = util.getDataTestDirOnTestFS("TestShadeSaslAuthenticationProvider"); USER_DATABASE_FILE = new Path(testDir, "user-db.txt"); - + createUserDBFile( USER_DATABASE_FILE.getFileSystem(CONF), USER_DATABASE_FILE, userDatabase); CONF.set(ShadeSaslServerAuthenticationProvider.PASSWORD_FILE_KEY, @@ -212,7 +212,7 @@ public void testPositiveAuthentication() throws Exception { assertFalse("Should have read a non-empty Result", r.isEmpty()); final Cell cell = r.getColumnLatestCell(Bytes.toBytes("f1"), Bytes.toBytes("q1")); assertTrue("Unexpected value", CellUtil.matchingValue(cell, Bytes.toBytes("1"))); - + return null; } } diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java index 644aefd1299e..957810e692e8 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/provider/TestCustomSaslAuthenticationProvider.java @@ -76,6 +76,7 @@ import org.apache.hadoop.hbase.security.HBaseKerberosUtils; import org.apache.hadoop.hbase.security.SaslUtil; import org.apache.hadoop.hbase.security.SecurityInfo; +import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.hbase.security.token.SecureTestCluster; import org.apache.hadoop.hbase.security.token.TokenProvider; import org.apache.hadoop.hbase.testclassification.MediumTests; @@ -203,8 +204,8 @@ public SaslClient createClient(Configuration conf, InetAddress serverAddr, SaslUtil.SASL_DEFAULT_REALM, saslProps, new InMemoryClientProviderCallbackHandler(token)); } - public Optional> findToken(UserGroupInformation ugi) { - List> tokens = ugi.getTokens().stream() + public Optional> findToken(User user) { + List> tokens = user.getTokens().stream() .filter((token) -> token.getKind().equals(PasswordAuthTokenIdentifier.PASSWORD_AUTH_TOKEN)) .collect(Collectors.toList()); if (tokens.isEmpty()) { @@ -262,7 +263,7 @@ public void handle(Callback[] callbacks) throws UnsupportedCallbackException { } @Override - public UserInformation getUserInfo(UserGroupInformation user) { + public UserInformation getUserInfo(User user) { return null; } } @@ -384,11 +385,11 @@ public void configure(Configuration conf, @Override public Pair> selectProvider( - String clusterId, UserGroupInformation ugi) { + String clusterId, User user) { Pair> superPair = - super.selectProvider(clusterId, ugi); + super.selectProvider(clusterId, user); - Optional> optional = inMemoryProvider.findToken(ugi); + Optional> optional = inMemoryProvider.findToken(user); if (optional.isPresent()) { LOG.info("Using InMemoryClientProvider"); return new Pair<>(inMemoryProvider, optional.get()); From e391c1913b59f3ecf77b665e5c9a152cac585059 Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Wed, 15 Jan 2020 14:27:16 -0500 Subject: [PATCH 40/41] Undo this, meant it to come in via HBASE-23695 --- .../apache/hadoop/hbase/HBaseClassTestRuleChecker.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/hbase-common/src/test/java/org/apache/hadoop/hbase/HBaseClassTestRuleChecker.java b/hbase-common/src/test/java/org/apache/hadoop/hbase/HBaseClassTestRuleChecker.java index c8d2cdbe495e..2508a27ee555 100644 --- a/hbase-common/src/test/java/org/apache/hadoop/hbase/HBaseClassTestRuleChecker.java +++ b/hbase-common/src/test/java/org/apache/hadoop/hbase/HBaseClassTestRuleChecker.java @@ -41,11 +41,9 @@ public class HBaseClassTestRuleChecker extends RunListener { @Override public void testStarted(Description description) throws Exception { Category[] categories = description.getTestClass().getAnnotationsByType(Category.class); - if (categories.length > 1) { - for (Class c : categories[0].value()) { - if (c == IntegrationTests.class) { - return; - } + for (Class c : categories[0].value()) { + if (c == IntegrationTests.class) { + return; } } for (Field field : description.getTestClass().getFields()) { From fa3051612ea5f277304843512706647a3cf5b2db Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Wed, 15 Jan 2020 16:32:40 -0500 Subject: [PATCH 41/41] Fix more QABot found issues --- .../hbase/security/provider/BuiltInProviderSelector.java | 4 ++-- .../hbase/security/provider/example/SaslPlainServer.java | 9 ++++++--- .../security/provider/example/ShadeClientTokenUtil.java | 2 +- .../example/ShadeSaslClientAuthenticationProvider.java | 4 ++-- .../example/ShadeSaslServerAuthenticationProvider.java | 4 ++-- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInProviderSelector.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInProviderSelector.java index 30fe6ffad1bb..8286380a4dfc 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInProviderSelector.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/provider/BuiltInProviderSelector.java @@ -46,7 +46,7 @@ * implement their own {@link AuthenticationProviderSelector} when writing a custom provider. * * This implementation is not thread-safe. {@link #configure(Configuration, Collection)} and - * {@link #selectProvider(Text, User)} is not safe if they are called concurrently. + * {@link #selectProvider(String, User)} is not safe if they are called concurrently. */ @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) @NotThreadSafe @@ -109,7 +109,7 @@ public Pair> return new Pair<>(simpleAuth, null); } - final Text clusterIdAsText = clusterId == null ? new Text() : new Text(clusterId); + final Text clusterIdAsText = new Text(clusterId); // Must be digest auth, look for a token. // TestGenerateDelegationToken is written expecting DT is used when DT and Krb are both present. diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/SaslPlainServer.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/SaslPlainServer.java index a826e7a197ed..9c2ce9b7db61 100644 --- a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/SaslPlainServer.java +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/SaslPlainServer.java @@ -22,7 +22,10 @@ import java.security.Provider; import java.util.Map; -import javax.security.auth.callback.*; +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.sasl.AuthorizeCallback; import javax.security.sasl.Sasl; import javax.security.sasl.SaslException; @@ -63,7 +66,7 @@ public String[] getMechanismNames(Map props){ private CallbackHandler cbh; private boolean completed; private String authz; - + SaslPlainServer(CallbackHandler callback) { this.cbh = callback; } @@ -158,4 +161,4 @@ public void dispose() throws SaslException { cbh = null; authz = null; } -} \ No newline at end of file +} diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeClientTokenUtil.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeClientTokenUtil.java index 364409fa4fbb..1f3238ad598e 100644 --- a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeClientTokenUtil.java +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeClientTokenUtil.java @@ -30,7 +30,7 @@ * Used to acquire tokens for the ShadeSaslAuthenticationProvider. */ @InterfaceAudience.Private -public class ShadeClientTokenUtil { +public final class ShadeClientTokenUtil { private ShadeClientTokenUtil() {} diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslClientAuthenticationProvider.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslClientAuthenticationProvider.java index e7821d4964b7..7cda97b09d46 100644 --- a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslClientAuthenticationProvider.java +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslClientAuthenticationProvider.java @@ -36,13 +36,13 @@ import org.apache.hadoop.hbase.security.SecurityInfo; import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider; -import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; import org.apache.hadoop.hbase.util.Bytes; -import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.TokenIdentifier; import org.apache.yetus.audience.InterfaceAudience; +import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; + @InterfaceAudience.Private public class ShadeSaslClientAuthenticationProvider extends ShadeSaslAuthenticationProvider implements SaslClientAuthenticationProvider { diff --git a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslServerAuthenticationProvider.java b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslServerAuthenticationProvider.java index d677b22cdbfe..dc8d89b71d21 100644 --- a/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslServerAuthenticationProvider.java +++ b/hbase-examples/src/main/java/org/apache/hadoop/hbase/security/provider/example/ShadeSaslServerAuthenticationProvider.java @@ -72,7 +72,7 @@ public AttemptingUserProvidingSaslServer createServer( return new AttemptingUserProvidingSaslServer( new SaslPlainServer( new ShadeSaslServerCallbackHandler(attemptingUser, passwordDatabase)), - () -> attemptingUser.get()); + () -> attemptingUser.get()); } Map readPasswordDB(Configuration conf) throws IOException { @@ -94,7 +94,7 @@ Map readPasswordDB(Configuration conf) throws IOException { String line = null; int offset = 0; while ((line = reader.readLine()) != null) { - line.trim(); + line = line.trim(); String[] parts = StringUtils.split(line, SEPARATOR); if (parts.length < 2) { LOG.warn("Password file contains invalid record on line {}, skipping", offset + 1);