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}