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}