blob: b0e0092529fd981f5b0ff71eda68a5a0fd658945 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.accumulo.core.conf;
import static java.util.Objects.requireNonNull;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.security.alias.CredentialProvider;
import org.apache.hadoop.security.alias.CredentialProviderFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Shim around Hadoop's CredentialProviderFactory provided by hadoop-common.
*/
public class HadoopCredentialProvider {
private static final Logger log = LoggerFactory.getLogger(HadoopCredentialProvider.class);
private static final String CREDENTIAL_PROVIDER_PATH = "hadoop.security.credential.provider.path";
// access to cachedProviders should be synchronized when necessary
private static final ConcurrentHashMap<String,List<CredentialProvider>> cachedProviders =
new ConcurrentHashMap<>();
/**
* Set the Hadoop Credential Provider path in the provided Hadoop Configuration.
*
* @param conf
* the Hadoop Configuration object
* @param path
* the credential provider paths to set
*/
public static void setPath(Configuration conf, String path) {
conf.set(CREDENTIAL_PROVIDER_PATH, path);
}
/**
* Fetch/cache the configured providers.
*
* @return The List of CredentialProviders, or null if they could not be loaded
*/
private static List<CredentialProvider> getProviders(Configuration conf) {
String path = conf.get(CREDENTIAL_PROVIDER_PATH);
if (path == null || path.isEmpty()) {
log.debug("Failed to get CredentialProviders; no provider path specified");
return null;
}
final List<CredentialProvider> providers;
try {
providers = CredentialProviderFactory.getProviders(conf);
} catch (IOException e) {
log.warn("Exception invoking CredentialProviderFactory.getProviders(conf)", e);
return null;
}
return cachedProviders.computeIfAbsent(path, p -> providers);
}
/**
* Attempt to extract the password from any configured CredentialProviders for the given alias. If
* no providers or credential is found, null is returned.
*
* @param conf
* Configuration for CredentialProvider
* @param alias
* Name of CredentialEntry key
* @return The credential if found, null otherwise
*/
public static char[] getValue(Configuration conf, String alias) {
requireNonNull(alias);
List<CredentialProvider> providerList = getProviders(requireNonNull(conf));
return providerList == null ? null : providerList.stream().map(provider -> {
try {
return provider.getCredentialEntry(alias);
} catch (IOException e) {
log.warn("Failed to call getCredentialEntry(alias) for provider {}", provider, e);
return null;
}
}).filter(Objects::nonNull).map(CredentialProvider.CredentialEntry::getCredential).findFirst()
.orElseGet(() -> {
// If we didn't find it, this isn't an error, it just wasn't set in the CredentialProvider
log.trace("Could not extract credential for {} from providers", alias);
return null;
});
}
/**
* Attempt to extract all aliases from any configured CredentialProviders.
*
* @param conf
* Configuration for the CredentialProvider
* @return A list of aliases. An empty list if no CredentialProviders are configured, or the
* providers are empty.
*/
public static List<String> getKeys(Configuration conf) {
List<CredentialProvider> providerList = getProviders(requireNonNull(conf));
return providerList == null ? Collections.emptyList()
: providerList.stream().flatMap(provider -> {
List<String> aliases = null;
try {
aliases = provider.getAliases();
} catch (IOException e) {
log.warn("Problem getting aliases from provider {}", provider, e);
}
return aliases == null ? Stream.empty() : aliases.stream();
}).collect(Collectors.toList());
}
/**
* Create a CredentialEntry using the configured Providers. If multiple CredentialProviders are
* configured, the first will be used.
*
* @param conf
* Configuration for the CredentialProvider
* @param name
* CredentialEntry name (alias)
* @param credential
* The credential
*/
public static void createEntry(Configuration conf, String name, char[] credential)
throws IOException {
requireNonNull(conf);
requireNonNull(name);
requireNonNull(credential);
List<CredentialProvider> providers = getProviders(conf);
if (providers == null || providers.isEmpty()) {
throw new IOException("Could not fetch any CredentialProviders");
}
CredentialProvider provider = providers.get(0);
if (providers.size() != 1) {
log.warn("Found more than one CredentialProvider. Using first provider found ({})", provider);
}
provider.createCredentialEntry(name, credential);
provider.flush();
}
}