/* * 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.ambari.server.serveraction.kerberos; import java.io.File; import java.io.IOException; import java.lang.reflect.Type; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.ambari.server.AmbariException; import org.apache.ambari.server.agent.CommandReport; import org.apache.ambari.server.controller.KerberosHelper; import org.apache.ambari.server.state.Cluster; import org.apache.ambari.server.state.ServiceComponentHost; import org.apache.ambari.server.state.kerberos.KerberosComponentDescriptor; import org.apache.ambari.server.state.kerberos.KerberosDescriptor; import org.apache.ambari.server.state.kerberos.KerberosIdentityDescriptor; import org.apache.ambari.server.state.kerberos.KerberosServiceDescriptor; import org.apache.ambari.server.utils.StageUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.reflect.TypeToken; import com.google.inject.Inject; public abstract class AbstractPrepareKerberosServerAction extends KerberosServerAction { private final static Logger LOG = LoggerFactory.getLogger(AbstractPrepareKerberosServerAction.class); /** * KerberosHelper */ @Inject private KerberosHelper kerberosHelper; @Inject private KerberosIdentityDataFileWriterFactory kerberosIdentityDataFileWriterFactory; @Inject private KerberosConfigDataFileWriterFactory kerberosConfigDataFileWriterFactory; @Override protected CommandReport processIdentity(Map<String, String> identityRecord, String evaluatedPrincipal, KerberosOperationHandler operationHandler, Map<String, String> kerberosConfiguration, Map<String, Object> requestSharedDataContext) throws AmbariException { throw new UnsupportedOperationException(); } KerberosHelper getKerberosHelper() { return kerberosHelper; } void processServiceComponentHosts(Cluster cluster, KerberosDescriptor kerberosDescriptor, List<ServiceComponentHost> schToProcess, Collection<String> identityFilter, String dataDirectory, Map<String, Map<String, String>> currentConfigurations, Map<String, Map<String, String>> kerberosConfigurations, boolean includeAmbariIdentity, Map<String, Set<String>> propertiesToBeIgnored) throws AmbariException { actionLog.writeStdOut("Processing Kerberos identities and configurations"); if (!schToProcess.isEmpty()) { if (dataDirectory == null) { String message = "The data directory has not been set. Generated data can not be stored."; LOG.error(message); throw new AmbariException(message); } // Create the file used to store details about principals and keytabs to create File identityDataFile = new File(dataDirectory, KerberosIdentityDataFileWriter.DATA_FILE_NAME); KerberosIdentityDataFileWriter kerberosIdentityDataFileWriter; // Create the context to use for filtering Kerberos Identities based on the state of the cluster Map<String, Object> filterContext = new HashMap<>(); filterContext.put("configurations", currentConfigurations); filterContext.put("services", cluster.getServices().keySet()); actionLog.writeStdOut(String.format("Writing Kerberos identity data metadata file to %s", identityDataFile.getAbsolutePath())); try { kerberosIdentityDataFileWriter = kerberosIdentityDataFileWriterFactory.createKerberosIdentityDataFileWriter(identityDataFile); } catch (IOException e) { String message = String.format("Failed to write index file - %s", identityDataFile.getAbsolutePath()); LOG.error(message, e); actionLog.writeStdOut(message); actionLog.writeStdErr(message + "\n" + e.getLocalizedMessage()); throw new AmbariException(message, e); } try { Map<String, Set<String>> propertiesToIgnore = null; // Iterate over the components installed on the current host to get the service and // component-level Kerberos descriptors in order to determine which principals, // keytab files, and configurations need to be created or updated. for (ServiceComponentHost sch : schToProcess) { String hostName = sch.getHostName(); String serviceName = sch.getServiceName(); String componentName = sch.getServiceComponentName(); KerberosServiceDescriptor serviceDescriptor = kerberosDescriptor.getService(serviceName); if (serviceDescriptor != null) { List<KerberosIdentityDescriptor> serviceIdentities = serviceDescriptor.getIdentities(true, filterContext); // Add service-level principals (and keytabs) kerberosHelper.addIdentities(kerberosIdentityDataFileWriter, serviceIdentities, identityFilter, hostName, serviceName, componentName, kerberosConfigurations, currentConfigurations); propertiesToIgnore = gatherPropertiesToIgnore(serviceIdentities, propertiesToIgnore); KerberosComponentDescriptor componentDescriptor = serviceDescriptor.getComponent(componentName); if (componentDescriptor != null) { List<KerberosIdentityDescriptor> componentIdentities = componentDescriptor.getIdentities(true, filterContext); // Calculate the set of configurations to update and replace any variables // using the previously calculated Map of configurations for the host. kerberosHelper.mergeConfigurations(kerberosConfigurations, componentDescriptor.getConfigurations(true), currentConfigurations); // Add component-level principals (and keytabs) kerberosHelper.addIdentities(kerberosIdentityDataFileWriter, componentIdentities, identityFilter, hostName, serviceName, componentName, kerberosConfigurations, currentConfigurations); propertiesToIgnore = gatherPropertiesToIgnore(componentIdentities, propertiesToIgnore); } } } // Add ambari-server identities only if 'kerberos-env.create_ambari_principal = true' if (includeAmbariIdentity && kerberosHelper.createAmbariIdentities(currentConfigurations.get("kerberos-env"))) { List<KerberosIdentityDescriptor> ambariIdentities = kerberosHelper.getAmbariServerIdentities(kerberosDescriptor); if (!ambariIdentities.isEmpty()) { for (KerberosIdentityDescriptor identity : ambariIdentities) { // If the identity represents the ambari-server user, use the component name "AMBARI_SERVER_SELF" // so it can be distinguished between other identities related to the AMBARI-SERVER // component. String componentName = KerberosHelper.AMBARI_SERVER_KERBEROS_IDENTITY_NAME.equals(identity.getName()) ? "AMBARI_SERVER_SELF" : "AMBARI_SERVER"; List<KerberosIdentityDescriptor> componentIdentities = Collections.singletonList(identity); kerberosHelper.addIdentities(kerberosIdentityDataFileWriter, componentIdentities, identityFilter, KerberosHelper.AMBARI_SERVER_HOST_NAME, "AMBARI", componentName, kerberosConfigurations, currentConfigurations); propertiesToIgnore = gatherPropertiesToIgnore(componentIdentities, propertiesToIgnore); } } } if ((propertiesToBeIgnored != null) && (propertiesToIgnore != null)) { propertiesToBeIgnored.putAll(propertiesToIgnore); } } catch (IOException e) { String message = String.format("Failed to write index file - %s", identityDataFile.getAbsolutePath()); LOG.error(message, e); actionLog.writeStdOut(message); actionLog.writeStdErr(message + "\n" + e.getLocalizedMessage()); throw new AmbariException(message, e); } finally { if (kerberosIdentityDataFileWriter != null) { // Make sure the data file is closed try { kerberosIdentityDataFileWriter.close(); } catch (IOException e) { String message = "Failed to close the index file writer"; LOG.warn(message, e); actionLog.writeStdOut(message); actionLog.writeStdErr(message + "\n" + e.getLocalizedMessage()); } } } } } protected Map<String, ? extends Collection<String>> getServiceComponentFilter() { String serializedValue = getCommandParameterValue(SERVICE_COMPONENT_FILTER); if(serializedValue != null) { Type type = new TypeToken<Map<String, ? extends Collection<String>>>() {}.getType(); return StageUtils.getGson().fromJson(serializedValue, type); } else { return null; } } protected Set<String> getHostFilter() { String serializedValue = getCommandParameterValue(HOST_FILTER); if(serializedValue != null) { Type type = new TypeToken<Set<String>>() {}.getType(); return StageUtils.getGson().fromJson(serializedValue, type); } else { return null; } } protected Collection<String> getIdentityFilter() { String serializedValue = getCommandParameterValue(IDENTITY_FILTER); if(serializedValue != null) { Type type = new TypeToken<Collection<String>>() {}.getType(); return StageUtils.getGson().fromJson(serializedValue, type); } else { return null; } } private Map<String, Set<String>> gatherPropertiesToIgnore(List<KerberosIdentityDescriptor> identities, Map<String, Set<String>> propertiesToIgnore) { Map<String, Map<String, String>> identityConfigurations = kerberosHelper.getIdentityConfigurations(identities); if ((identityConfigurations != null) && !identityConfigurations.isEmpty()) { if (propertiesToIgnore == null) { propertiesToIgnore = new HashMap<>(); } for (Map.Entry<String, Map<String, String>> entry : identityConfigurations.entrySet()) { String configType = entry.getKey(); Map<String, String> properties = entry.getValue(); if ((properties != null) && !properties.isEmpty()) { Set<String> propertyNames = propertiesToIgnore.get(configType); if (propertyNames == null) { propertyNames = new HashSet<>(); propertiesToIgnore.put(configType, propertyNames); } propertyNames.addAll(properties.keySet()); } } } return propertiesToIgnore; } /** * Processes configuration changes to determine if any work needs to be done. * <p/> * If work is to be done, a data file containing the details is created so it they changes may be * processed in the appropriate stage. * * @param dataDirectory the directory in which to write the configuration changes data file * @param kerberosConfigurations the Kerberos-specific configuration map * @param propertiesToBeRemoved a map of properties to be removed from the current configuration, * grouped by configuration type. * @throws AmbariException */ protected void processConfigurationChanges(String dataDirectory, Map<String, Map<String, String>> kerberosConfigurations, Map<String, Set<String>> propertiesToBeRemoved) throws AmbariException { actionLog.writeStdOut("Determining configuration changes"); // If there are configurations to set, create a (temporary) data file to store the configuration // updates and fill it will the relevant configurations. if (!kerberosConfigurations.isEmpty()) { if (dataDirectory == null) { String message = "The data directory has not been set. Generated data can not be stored."; LOG.error(message); throw new AmbariException(message); } File configFile = new File(dataDirectory, KerberosConfigDataFileWriter.DATA_FILE_NAME); KerberosConfigDataFileWriter kerberosConfDataFileWriter = null; actionLog.writeStdOut(String.format("Writing configuration changes metadata file to %s", configFile.getAbsolutePath())); try { kerberosConfDataFileWriter = kerberosConfigDataFileWriterFactory.createKerberosConfigDataFileWriter(configFile); // add properties to be set for (Map.Entry<String, Map<String, String>> entry : kerberosConfigurations.entrySet()) { String type = entry.getKey(); Map<String, String> properties = entry.getValue(); if (properties != null) { for (Map.Entry<String, String> configTypeEntry : properties.entrySet()) { kerberosConfDataFileWriter.addRecord(type, configTypeEntry.getKey(), configTypeEntry.getValue(), KerberosConfigDataFileWriter.OPERATION_TYPE_SET); } } } // add properties to be removed if (propertiesToBeRemoved != null) { for (Map.Entry<String, Set<String>> entry : propertiesToBeRemoved.entrySet()) { String type = entry.getKey(); Set<String> properties = entry.getValue(); if (properties != null) { for (String property : properties) { kerberosConfDataFileWriter.addRecord(type, property, "", KerberosConfigDataFileWriter.OPERATION_TYPE_REMOVE); } } } } } catch (IOException e) { String message = String.format("Failed to write kerberos configurations file - %s", configFile.getAbsolutePath()); LOG.error(message, e); actionLog.writeStdOut(message); actionLog.writeStdErr(message + "\n" + e.getLocalizedMessage()); throw new AmbariException(message, e); } finally { if (kerberosConfDataFileWriter != null) { try { kerberosConfDataFileWriter.close(); } catch (IOException e) { String message = "Failed to close the kerberos configurations file writer"; LOG.warn(message, e); actionLog.writeStdOut(message); actionLog.writeStdErr(message + "\n" + e.getLocalizedMessage()); } } } } } }