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 java.lang.reflect.InvocationTargetException; 021import java.util.Collection; 022import java.util.Collections; 023import java.util.HashMap; 024import java.util.Optional; 025import java.util.ServiceLoader; 026import java.util.concurrent.atomic.AtomicReference; 027import java.util.stream.Collectors; 028import org.apache.hadoop.conf.Configuration; 029import org.apache.hadoop.hbase.HBaseInterfaceAudience; 030import org.apache.hadoop.hbase.security.User; 031import org.apache.hadoop.hbase.util.Pair; 032import org.apache.hadoop.security.token.Token; 033import org.apache.hadoop.security.token.TokenIdentifier; 034import org.apache.yetus.audience.InterfaceAudience; 035import org.apache.yetus.audience.InterfaceStability; 036import org.slf4j.Logger; 037import org.slf4j.LoggerFactory; 038 039/** 040 * Accessor for all SaslAuthenticationProvider instances. 041 */ 042@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.AUTHENTICATION) 043@InterfaceStability.Evolving 044public final class SaslClientAuthenticationProviders { 045 private static final Logger LOG = 046 LoggerFactory.getLogger(SaslClientAuthenticationProviders.class); 047 048 public static final String SELECTOR_KEY = "hbase.client.sasl.provider.class"; 049 public static final String EXTRA_PROVIDERS_KEY = "hbase.client.sasl.provider.extras"; 050 051 private static final AtomicReference<SaslClientAuthenticationProviders> providersRef = 052 new AtomicReference<>(); 053 054 private final Collection<SaslClientAuthenticationProvider> providers; 055 private final AuthenticationProviderSelector selector; 056 057 private SaslClientAuthenticationProviders(Collection<SaslClientAuthenticationProvider> providers, 058 AuthenticationProviderSelector selector) { 059 this.providers = providers; 060 this.selector = selector; 061 } 062 063 /** 064 * Returns the number of providers that have been registered. 065 */ 066 public int getNumRegisteredProviders() { 067 return providers.size(); 068 } 069 070 /** 071 * Returns a singleton instance of {@link SaslClientAuthenticationProviders}. 072 */ 073 public static synchronized SaslClientAuthenticationProviders getInstance(Configuration conf) { 074 SaslClientAuthenticationProviders providers = providersRef.get(); 075 if (providers == null) { 076 providers = instantiate(conf); 077 providersRef.set(providers); 078 } 079 080 return providers; 081 } 082 083 /** 084 * Removes the cached singleton instance of {@link SaslClientAuthenticationProviders}. 085 */ 086 public static synchronized void reset() { 087 providersRef.set(null); 088 } 089 090 /** 091 * Adds the given {@code provider} to the set, only if an equivalent provider does not already 092 * exist in the set. 093 */ 094 static void addProviderIfNotExists(SaslClientAuthenticationProvider provider, 095 HashMap<Byte, SaslClientAuthenticationProvider> providers) { 096 Byte code = provider.getSaslAuthMethod().getCode(); 097 SaslClientAuthenticationProvider existingProvider = providers.get(code); 098 if (existingProvider != null) { 099 throw new RuntimeException("Already registered authentication provider with " + code + " " 100 + existingProvider.getClass()); 101 } 102 providers.put(code, provider); 103 } 104 105 /** 106 * Instantiates the ProviderSelector implementation from the provided configuration. 107 */ 108 static AuthenticationProviderSelector instantiateSelector(Configuration conf, 109 Collection<SaslClientAuthenticationProvider> providers) { 110 Class<? extends AuthenticationProviderSelector> clz = conf.getClass(SELECTOR_KEY, 111 BuiltInProviderSelector.class, AuthenticationProviderSelector.class); 112 try { 113 AuthenticationProviderSelector selector = clz.getConstructor().newInstance(); 114 selector.configure(conf, providers); 115 if (LOG.isTraceEnabled()) { 116 LOG.trace("Loaded ProviderSelector {}", selector.getClass()); 117 } 118 return selector; 119 } catch (InstantiationException | IllegalAccessException | NoSuchMethodException 120 | InvocationTargetException e) { 121 throw new RuntimeException( 122 "Failed to instantiate " + clz + " as the ProviderSelector defined by " + SELECTOR_KEY, e); 123 } 124 } 125 126 /** 127 * Extracts and instantiates authentication providers from the configuration. 128 */ 129 static void addExplicitProviders(Configuration conf, 130 HashMap<Byte, SaslClientAuthenticationProvider> providers) { 131 for (String implName : conf.getStringCollection(EXTRA_PROVIDERS_KEY)) { 132 Class<?> clz; 133 // Load the class from the config 134 try { 135 clz = Class.forName(implName); 136 } catch (ClassNotFoundException e) { 137 LOG.warn("Failed to load SaslClientAuthenticationProvider {}", implName, e); 138 continue; 139 } 140 141 // Make sure it's the right type 142 if (!SaslClientAuthenticationProvider.class.isAssignableFrom(clz)) { 143 LOG.warn("Ignoring SaslClientAuthenticationProvider {} because it is not an instance of" 144 + " SaslClientAuthenticationProvider", clz); 145 continue; 146 } 147 148 // Instantiate it 149 SaslClientAuthenticationProvider provider; 150 try { 151 provider = (SaslClientAuthenticationProvider) clz.getConstructor().newInstance(); 152 } catch (InstantiationException | IllegalAccessException | NoSuchMethodException 153 | InvocationTargetException e) { 154 LOG.warn("Failed to instantiate SaslClientAuthenticationProvider {}", clz, e); 155 continue; 156 } 157 158 // Add it to our set, only if it doesn't conflict with something else we've 159 // already registered. 160 addProviderIfNotExists(provider, providers); 161 } 162 } 163 164 /** 165 * Instantiates all client authentication providers and returns an instance of 166 * {@link SaslClientAuthenticationProviders}. 167 */ 168 static SaslClientAuthenticationProviders instantiate(Configuration conf) { 169 ServiceLoader<SaslClientAuthenticationProvider> loader = 170 ServiceLoader.load(SaslClientAuthenticationProvider.class); 171 HashMap<Byte, SaslClientAuthenticationProvider> providerMap = new HashMap<>(); 172 for (SaslClientAuthenticationProvider provider : loader) { 173 addProviderIfNotExists(provider, providerMap); 174 } 175 176 addExplicitProviders(conf, providerMap); 177 178 Collection<SaslClientAuthenticationProvider> providers = 179 Collections.unmodifiableCollection(providerMap.values()); 180 181 if (LOG.isTraceEnabled()) { 182 String loadedProviders = providers.stream().map((provider) -> provider.getClass().getName()) 183 .collect(Collectors.joining(", ")); 184 LOG.trace("Found SaslClientAuthenticationProviders {}", loadedProviders); 185 } 186 187 AuthenticationProviderSelector selector = instantiateSelector(conf, providers); 188 return new SaslClientAuthenticationProviders(providers, selector); 189 } 190 191 /** 192 * Returns the provider and token pair for SIMPLE authentication. This method is a "hack" while 193 * SIMPLE authentication for HBase does not flow through the SASL codepath. 194 */ 195 public Pair<SaslClientAuthenticationProvider, Token<? extends TokenIdentifier>> 196 getSimpleProvider() { 197 Optional<SaslClientAuthenticationProvider> optional = providers.stream() 198 .filter((p) -> p instanceof SimpleSaslClientAuthenticationProvider).findFirst(); 199 return new Pair<>(optional.get(), null); 200 } 201 202 /** 203 * Chooses the best authentication provider and corresponding token given the HBase cluster 204 * identifier and the user. 205 */ 206 public Pair<SaslClientAuthenticationProvider, Token<? extends TokenIdentifier>> 207 selectProvider(String clusterId, User clientUser) { 208 return selector.selectProvider(clusterId, clientUser); 209 } 210 211 @Override 212 public String toString() { 213 return providers.stream().map((p) -> p.getClass().getName()) 214 .collect(Collectors.joining(", ", "providers=[", "], selector=")) + selector.getClass(); 215 } 216}