001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.security.provider;
019
020import static java.util.Objects.requireNonNull;
021
022import java.util.Collection;
023import java.util.Objects;
024import net.jcip.annotations.NotThreadSafe;
025import org.apache.hadoop.conf.Configuration;
026import org.apache.hadoop.hbase.HBaseInterfaceAudience;
027import org.apache.hadoop.hbase.security.User;
028import org.apache.hadoop.hbase.util.Pair;
029import org.apache.hadoop.io.Text;
030import org.apache.hadoop.security.UserGroupInformation;
031import org.apache.hadoop.security.token.Token;
032import org.apache.hadoop.security.token.TokenIdentifier;
033import org.apache.yetus.audience.InterfaceAudience;
034import org.slf4j.Logger;
035import org.slf4j.LoggerFactory;
036
037/**
038 * Default implementation of {@link AuthenticationProviderSelector} which can choose from the
039 * authentication implementations which HBase provides out of the box: Simple, Kerberos, and
040 * Delegation Token authentication. This implementation will ignore any
041 * {@link SaslAuthenticationProvider}'s which are available on the classpath or specified in the
042 * configuration because HBase cannot correctly choose which token should be returned to a client
043 * when multiple are present. It is expected that users implement their own
044 * {@link AuthenticationProviderSelector} when writing a custom provider. This implementation is not
045 * thread-safe. {@link #configure(Configuration, Collection)} and
046 * {@link #selectProvider(String, User)} is not safe if they are called concurrently.
047 */
048@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION)
049@NotThreadSafe
050public class BuiltInProviderSelector implements AuthenticationProviderSelector {
051  private static final Logger LOG = LoggerFactory.getLogger(BuiltInProviderSelector.class);
052
053  Configuration conf;
054  SimpleSaslClientAuthenticationProvider simpleAuth = null;
055  GssSaslClientAuthenticationProvider krbAuth = null;
056  DigestSaslClientAuthenticationProvider digestAuth = null;
057  Text digestAuthTokenKind = null;
058
059  @Override
060  public void configure(Configuration conf,
061    Collection<SaslClientAuthenticationProvider> providers) {
062    if (this.conf != null) {
063      throw new IllegalStateException("configure() should only be called once");
064    }
065    this.conf = Objects.requireNonNull(conf);
066
067    for (SaslClientAuthenticationProvider provider : Objects.requireNonNull(providers)) {
068      final String name = provider.getSaslAuthMethod().getName();
069      if (SimpleSaslAuthenticationProvider.SASL_AUTH_METHOD.getName().contentEquals(name)) {
070        if (simpleAuth != null) {
071          throw new IllegalStateException(
072            "Encountered multiple SimpleSaslClientAuthenticationProvider instances");
073        }
074        simpleAuth = (SimpleSaslClientAuthenticationProvider) provider;
075      } else if (GssSaslAuthenticationProvider.SASL_AUTH_METHOD.getName().equals(name)) {
076        if (krbAuth != null) {
077          throw new IllegalStateException(
078            "Encountered multiple GssSaslClientAuthenticationProvider instances");
079        }
080        krbAuth = (GssSaslClientAuthenticationProvider) provider;
081      } else if (DigestSaslAuthenticationProvider.SASL_AUTH_METHOD.getName().equals(name)) {
082        if (digestAuth != null) {
083          throw new IllegalStateException(
084            "Encountered multiple DigestSaslClientAuthenticationProvider instances");
085        }
086        digestAuth = (DigestSaslClientAuthenticationProvider) provider;
087        digestAuthTokenKind = new Text(digestAuth.getTokenKind());
088      } else {
089        LOG.warn("Ignoring unknown SaslClientAuthenticationProvider: {}", provider.getClass());
090      }
091    }
092    if (simpleAuth == null || krbAuth == null || digestAuth == null) {
093      throw new IllegalStateException("Failed to load SIMPLE, KERBEROS, and DIGEST authentication "
094        + "providers. Classpath is not sane.");
095    }
096  }
097
098  @Override
099  public Pair<SaslClientAuthenticationProvider, Token<? extends TokenIdentifier>>
100    selectProvider(String clusterId, User user) {
101    requireNonNull(clusterId, "Null clusterId was given");
102    requireNonNull(user, "Null user was given");
103
104    // Superfluous: we don't do SIMPLE auth over SASL, but we should to simplify.
105    if (!User.isHBaseSecurityEnabled(conf)) {
106      return new Pair<>(simpleAuth, null);
107    }
108
109    final Text clusterIdAsText = new Text(clusterId);
110
111    // Must be digest auth, look for a token.
112    // TestGenerateDelegationToken is written expecting DT is used when DT and Krb are both present.
113    // (for whatever that's worth).
114    for (Token<? extends TokenIdentifier> token : user.getTokens()) {
115      // We need to check for two things:
116      // 1. This token is for the HBase cluster we want to talk to
117      // 2. We have suppporting client implementation to handle the token (the "kind" of token)
118      if (
119        clusterIdAsText.equals(token.getService()) && digestAuthTokenKind.equals(token.getKind())
120      ) {
121        return new Pair<>(digestAuth, token);
122      }
123    }
124    // Unwrap PROXY auth'n method if that's what we have coming in.
125    final UserGroupInformation currentUser = user.getUGI();
126    // May be null if Hadoop AuthenticationMethod is PROXY
127    final UserGroupInformation realUser = currentUser.getRealUser();
128    if (
129      currentUser.hasKerberosCredentials()
130        || (realUser != null && realUser.hasKerberosCredentials())
131    ) {
132      return new Pair<>(krbAuth, null);
133    }
134    // This indicates that a client is requesting some authentication mechanism which the servers
135    // don't know how to process (e.g. there is no provider which can support it). This may be
136    // a bug or simply a misconfiguration of client *or* server.
137    LOG.warn("No matching SASL authentication provider and supporting token found from providers"
138      + " for user: {}", user);
139    return null;
140  }
141
142}