| /* |
| * 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 java.util.Objects.requireNonNull; |
| |
| import java.util.Collection; |
| import java.util.Objects; |
| |
| import net.jcip.annotations.NotThreadSafe; |
| |
| 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; |
| 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; |
| |
| /** |
| * 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, Collection)} and |
| * {@link #selectProvider(String, User)} is not safe if they are called concurrently. |
| */ |
| @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) |
| @NotThreadSafe |
| public class BuiltInProviderSelector implements AuthenticationProviderSelector { |
| private static final Logger LOG = LoggerFactory.getLogger(BuiltInProviderSelector.class); |
| |
| Configuration conf; |
| SimpleSaslClientAuthenticationProvider simpleAuth = null; |
| GssSaslClientAuthenticationProvider krbAuth = null; |
| DigestSaslClientAuthenticationProvider digestAuth = null; |
| Text digestAuthTokenKind = null; |
| |
| @Override |
| public void configure( |
| Configuration conf, Collection<SaslClientAuthenticationProvider> 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)) { |
| 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 (GssSaslAuthenticationProvider.SASL_AUTH_METHOD.getName().equals(name)) { |
| if (krbAuth != null) { |
| throw new IllegalStateException( |
| "Encountered multiple GssSaslClientAuthenticationProvider instances"); |
| } |
| krbAuth = (GssSaslClientAuthenticationProvider) provider; |
| } else if (DigestSaslAuthenticationProvider.SASL_AUTH_METHOD.getName().equals(name)) { |
| if (digestAuth != null) { |
| throw new IllegalStateException( |
| "Encountered multiple DigestSaslClientAuthenticationProvider instances"); |
| } |
| digestAuth = (DigestSaslClientAuthenticationProvider) provider; |
| digestAuthTokenKind = new Text(digestAuth.getTokenKind()); |
| } 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 |
| public Pair<SaslClientAuthenticationProvider, Token<? extends TokenIdentifier>> selectProvider( |
| 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)) { |
| return new Pair<>(simpleAuth, null); |
| } |
| |
| 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. |
| // (for whatever that's worth). |
| for (Token<? extends TokenIdentifier> 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) |
| if (clusterIdAsText.equals(token.getService()) && |
| digestAuthTokenKind.equals(token.getKind())) { |
| return new Pair<>(digestAuth, token); |
| } |
| } |
| // Unwrap PROXY auth'n method if that's what we have coming in. |
| final UserGroupInformation currentUser = user.getUGI(); |
| // May be null if Hadoop AuthenticationMethod is PROXY |
| final UserGroupInformation realUser = currentUser.getRealUser(); |
| if (currentUser.hasKerberosCredentials() || |
| (realUser != null && realUser.hasKerberosCredentials())) { |
| return new Pair<>(krbAuth, null); |
| } |
| // This indicates that a client is requesting some authentication mechanism which the servers |
| // don't know how to process (e.g. there is no provider which can support it). This may be |
| // a bug or simply a misconfiguration of client *or* server. |
| LOG.warn("No matching SASL authentication provider and supporting token found from providers" |
| + " for user: {}", user); |
| return null; |
| } |
| |
| } |