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