blob: 2eef4b9ce0ee26c53f0e82ec02c74c42ae7fd627 [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.ambari.server.state.kerberos;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.TreeMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
/**
* KerberosDescriptorUpdateHelper provides routines for upgrading the user-specified Kerberos descriptor
* when changing stacks.
* <p>
* This implementation should work for stack upgrades and downgrades.
*/
public class KerberosDescriptorUpdateHelper {
private static final Logger LOG = LoggerFactory.getLogger(KerberosDescriptorUpdateHelper.class);
/**
* The entry point into upgrading a user-specified Kerberos descriptor.
* <p>
* The supplied Kerberos descriptors will remain untouched and new Kerberos descriptor instance will
* created and returned with the update data.
*
* @param beginningStackKerberosDescriptor the Kerberos descriptor for the previous stack version
* @param endingStackKerberosDescriptor the Kerberos descriptor for the new stack version
* @param userKerberosDescriptor the user-specified Kerberos descriptor
* @return a new Kerberos descriptor containing the updated user-specified data
*/
public static KerberosDescriptor updateUserKerberosDescriptor(KerberosDescriptor beginningStackKerberosDescriptor,
KerberosDescriptor endingStackKerberosDescriptor,
KerberosDescriptor userKerberosDescriptor) {
KerberosDescriptor updated = new KerberosDescriptor(userKerberosDescriptor.toMap());
updated.setProperties(processProperties(
beginningStackKerberosDescriptor.getProperties(),
endingStackKerberosDescriptor.getProperties(),
updated.getProperties()));
updated.setConfigurations(processConfigurations(
beginningStackKerberosDescriptor.getConfigurations(),
endingStackKerberosDescriptor.getConfigurations(),
updated.getConfigurations()));
updated.setIdentities(processIdentities(
beginningStackKerberosDescriptor.getIdentities(),
endingStackKerberosDescriptor.getIdentities(),
updated.getIdentities()));
updated.setAuthToLocalProperties(processAuthToLocalProperties(
beginningStackKerberosDescriptor.getAuthToLocalProperties(),
endingStackKerberosDescriptor.getAuthToLocalProperties(),
updated.getAuthToLocalProperties()));
updated.setServices(processServices(
beginningStackKerberosDescriptor.getServices(),
endingStackKerberosDescriptor.getServices(),
updated.getServices()));
return updated;
}
/**
* Processes the service-level Kerberos descriptors to add, remove, or update data in the user-specified
* Kerberos descriptor.
*
* @param previousStackServices a map of {@link KerberosServiceDescriptor}s from the previous stack version's Kerberos descriptor
* @param newStackServices a map of {@link KerberosServiceDescriptor}s from the new stack version's Kerberos descriptor
* @param userServices a map of {@link KerberosServiceDescriptor}s from the user-supplied Kerberos descriptor
* @return a map of updated {@link KerberosServiceDescriptor}s
*/
private static Map<String, KerberosServiceDescriptor> processServices(Map<String, KerberosServiceDescriptor> previousStackServices,
Map<String, KerberosServiceDescriptor> newStackServices,
Map<String, KerberosServiceDescriptor> userServices) {
if ((userServices == null) || userServices.isEmpty() || ((previousStackServices == null) && (newStackServices == null))) {
return userServices;
}
Map<String, KerberosServiceDescriptor> updatedServices = new TreeMap<String, KerberosServiceDescriptor>();
if (previousStackServices == null) {
previousStackServices = Collections.emptyMap();
}
if (newStackServices == null) {
newStackServices = Collections.emptyMap();
}
for (Map.Entry<String, KerberosServiceDescriptor> entry : userServices.entrySet()) {
String name = entry.getKey();
KerberosServiceDescriptor userValue = entry.getValue();
if (userValue != null) {
if (newStackServices.containsKey(name)) {
KerberosServiceDescriptor oldValue = previousStackServices.get(name);
KerberosServiceDescriptor newValue = newStackServices.get(name);
LOG.debug("Processing service {} for modifications", name);
updatedServices.put(name, processService(oldValue, newValue, userValue));
} else if (previousStackServices.containsKey(name)) {
LOG.debug("Removing service {} from user-specified Kerberos Descriptor", name);
// Nothing to do here, just don't add it to the updated configurations map...
} else {
LOG.debug("Leaving service {} in user-specified Kerberos Descriptor unchanged since it was user-defined.", name);
updatedServices.put(name, userValue);
}
}
}
// Note: there is no need to add service definitions that do not exist since they will get
// added dynamically when merged with the stack default value.
return updatedServices;
}
/**
* Processes a {@link KerberosServiceDescriptor} to change the user-supplied data based on the changes
* observed between the previous stack version's data and the new stack version's data.
*
* @param previousStackService a {@link KerberosServiceDescriptor} from the previous stack version's Kerberos descriptor
* @param newStackService a {@link KerberosServiceDescriptor} from the new stack version's Kerberos descriptor
* @param userService a {@link KerberosServiceDescriptor} from the user-specified Kerberos descriptor
* @return the updated {@link KerberosServiceDescriptor}
*/
private static KerberosServiceDescriptor processService(KerberosServiceDescriptor previousStackService,
KerberosServiceDescriptor newStackService,
KerberosServiceDescriptor userService) {
KerberosServiceDescriptor updatedService = new KerberosServiceDescriptor(userService.toMap());
updatedService.setAuthToLocalProperties(processAuthToLocalProperties(
(previousStackService == null) ? null : previousStackService.getAuthToLocalProperties(),
(newStackService == null) ? null : newStackService.getAuthToLocalProperties(),
updatedService.getAuthToLocalProperties()));
updatedService.setConfigurations(processConfigurations(
(previousStackService == null) ? null : previousStackService.getConfigurations(),
(newStackService == null) ? null : newStackService.getConfigurations(),
updatedService.getConfigurations()));
updatedService.setIdentities(processIdentities(
(previousStackService == null) ? null : previousStackService.getIdentities(),
(newStackService == null) ? null : newStackService.getIdentities(),
updatedService.getIdentities()));
Map<String, KerberosComponentDescriptor> userServiceComponents = updatedService.getComponents();
Map<String, KerberosComponentDescriptor> newServiceComponents = (newStackService == null) ? null : newStackService.getComponents();
Map<String, KerberosComponentDescriptor> oldServiceComponents = (previousStackService == null) ? null : previousStackService.getComponents();
if (newServiceComponents == null) {
newServiceComponents = Collections.emptyMap();
}
if (oldServiceComponents == null) {
oldServiceComponents = Collections.emptyMap();
}
if (userServiceComponents != null) {
Iterator<Map.Entry<String, KerberosComponentDescriptor>> iterator = userServiceComponents.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, KerberosComponentDescriptor> entry = iterator.next();
String name = entry.getKey();
KerberosComponentDescriptor userValue = entry.getValue();
if (userValue == null) {
iterator.remove(); // This is a bad entry... remove it.
} else if (newServiceComponents.containsKey(name)) {
KerberosComponentDescriptor oldValue = oldServiceComponents.get(name);
KerberosComponentDescriptor newValue = newServiceComponents.get(name);
LOG.debug("Processing component {}/{} for modifications", updatedService.getName(), name);
processComponent(oldValue, newValue, userValue);
} else {
LOG.debug("Removing component {}/{} from user-specified Kerberos Descriptor", updatedService.getName(), name);
iterator.remove();
}
}
}
return updatedService;
}
/**
* Processes a {@link KerberosComponentDescriptor} to change the user-supplied data based on the changes
* observed between the previous stack version's data and the new stack version's data.
* <p>
* The supplied userComponent value is updated in place.
*
* @param previousStackComponent a {@link KerberosComponentDescriptor} from the previous stack version's Kerberos descriptor
* @param newStackComponent a {@link KerberosComponentDescriptor} from the new stack version's Kerberos descriptor
* @param userComponent a {@link KerberosComponentDescriptor} from the user-specified Kerberos descriptor
* @return the updated {@link KerberosComponentDescriptor}
*/
private static KerberosComponentDescriptor processComponent(KerberosComponentDescriptor previousStackComponent,
KerberosComponentDescriptor newStackComponent,
KerberosComponentDescriptor userComponent) {
userComponent.setAuthToLocalProperties(processAuthToLocalProperties(
(previousStackComponent == null) ? null : previousStackComponent.getAuthToLocalProperties(),
(newStackComponent == null) ? null : newStackComponent.getAuthToLocalProperties(),
userComponent.getAuthToLocalProperties()));
userComponent.setConfigurations(processConfigurations(
(previousStackComponent == null) ? null : previousStackComponent.getConfigurations(),
(newStackComponent == null) ? null : newStackComponent.getConfigurations(),
userComponent.getConfigurations()));
userComponent.setIdentities(processIdentities(
(previousStackComponent == null) ? null : previousStackComponent.getIdentities(),
(newStackComponent == null) ? null : newStackComponent.getIdentities(),
userComponent.getIdentities()));
return userComponent;
}
/**
* Processes a the list of configuration specification (<code>&lt;configuration type&gt;/&lt;property name&gt;</code>)
* identifying the properties that should be automatically updated with generated auth-to-local rules.
* <p>
* If no user-specified properties are set, <code>null</code> is returned.
* <p>
* Else the configuration specifications from the previous stack are removed from the user-specified
* data and the configuration specifications from the new stack are added to the user-specified,
* leaving the new list of configuration specifications as well as any user-specified changes.
*
* @param previousStackAuthToLocalProperties the auth-to-local properties from the previous stack version's Kerberos descriptor
* @param newStackAuthToLocalProperties the auth-to-local properties from the new stack version's Kerberos descriptor
* @param userAuthToLocalProperties the auth-to-local properties from the user-specified Kerberos descriptor
* @return an updated {@link Set} of configuration specifications
*/
private static Set<String> processAuthToLocalProperties(Set<String> previousStackAuthToLocalProperties,
Set<String> newStackAuthToLocalProperties,
Set<String> userAuthToLocalProperties) {
if (userAuthToLocalProperties == null) {
return null;
}
TreeSet<String> updatedAuthToLocalProperties = new TreeSet<String>(userAuthToLocalProperties);
// Remove old configuration specifications, leaving the user-specified ones.
if (previousStackAuthToLocalProperties != null) {
updatedAuthToLocalProperties.removeAll(previousStackAuthToLocalProperties);
}
// Add the new configuration specifications
if (newStackAuthToLocalProperties != null) {
updatedAuthToLocalProperties.addAll(newStackAuthToLocalProperties);
}
return updatedAuthToLocalProperties;
}
/**
* Processes the identity-level Kerberos descriptors to add, remove, or update data in the user-specified
* Kerberos descriptor.
*
* @param previousStackIdentities a map of {@link KerberosIdentityDescriptor}s from the previous stack version's Kerberos descriptor
* @param newStackIdentities a map of {@link KerberosIdentityDescriptor}s from the new stack version's Kerberos descriptor
* @param userIdentities a map of {@link KerberosIdentityDescriptor}s from the user-supplied Kerberos descriptor
* @return a list of updated {@link KerberosIdentityDescriptor}s
*/
private static List<KerberosIdentityDescriptor> processIdentities(List<KerberosIdentityDescriptor> previousStackIdentities,
List<KerberosIdentityDescriptor> newStackIdentities,
List<KerberosIdentityDescriptor> userIdentities) {
if ((userIdentities == null) || userIdentities.isEmpty() || ((previousStackIdentities == null) && (newStackIdentities == null))) {
return userIdentities;
}
// Create maps to make processing easier....
Map<String, KerberosIdentityDescriptor> previousStackIdentityMap = toMap(previousStackIdentities);
Map<String, KerberosIdentityDescriptor> newStackIdentityMap = toMap(newStackIdentities);
Map<String, KerberosIdentityDescriptor> userStackIdentityMap = toMap(userIdentities);
Map<String, KerberosIdentityDescriptor> updatedIdentities = new TreeMap<String, KerberosIdentityDescriptor>();
if (previousStackIdentityMap == null) {
previousStackIdentityMap = Collections.emptyMap();
}
if (newStackIdentityMap == null) {
newStackIdentityMap = Collections.emptyMap();
}
// Find identities to modify or remove
for (Map.Entry<String, KerberosIdentityDescriptor> entry : userStackIdentityMap.entrySet()) {
String name = entry.getKey();
KerberosIdentityDescriptor userValue = entry.getValue();
if (userValue != null) {
if (newStackIdentityMap.containsKey(name)) {
// Modify the new stack identity value by changing on the principal value and/or keytab
// file value since they are the only fields in this structure that should be changed
// by a user. However, the new stack identity may have been converted to a pure reference
// where the user changes will then be ignored.
KerberosIdentityDescriptor newValue = newStackIdentityMap.get(name);
KerberosIdentityDescriptor previousValue = previousStackIdentityMap.get(name);
updatedIdentities.put(name, processIdentity(previousValue, newValue, userValue));
} else if (previousStackIdentityMap.containsKey(name)) {
LOG.debug("Removing identity named {} from user-specified Kerberos Descriptor", name);
// Nothing to do here, just don't add it to the updated identity map...
} else {
LOG.debug("Leaving identity named {} in user-specified Kerberos Descriptor unchanged since it was user-defined.", name);
updatedIdentities.put(name, userValue);
}
}
}
// Note: there is no need to add identity definitions that do not exist since they will get
// added dynamically when merged with the stack default value.
return new ArrayList<KerberosIdentityDescriptor>(updatedIdentities.values());
}
/**
* Processes a {@link KerberosIdentityDescriptor} to change the user-supplied data based on the changes
* observed between the previous stack version's data and the new stack version's data.
*
* @param previousStackIdentity a {@link KerberosIdentityDescriptor} from the previous stack version's Kerberos descriptor
* @param newStackIdentity a {@link KerberosIdentityDescriptor} from the new stack version's Kerberos descriptor
* @param userIdentity a {@link KerberosIdentityDescriptor} from the user-specified Kerberos descriptor
* @return a new, updated, {@link KerberosIdentityDescriptor}
*/
private static KerberosIdentityDescriptor processIdentity(KerberosIdentityDescriptor previousStackIdentity,
KerberosIdentityDescriptor newStackIdentity,
KerberosIdentityDescriptor userIdentity) {
KerberosIdentityDescriptor updatedValue = new KerberosIdentityDescriptor(newStackIdentity.toMap());
KerberosPrincipalDescriptor updatedValuePrincipal = updatedValue.getPrincipalDescriptor();
KerberosKeytabDescriptor updatedValueKeytab = updatedValue.getKeytabDescriptor();
// If the new identity definition is a reference and no longer has a principal definition,
// Ignore any user changes to the old principal definition.
if (updatedValuePrincipal != null) {
KerberosPrincipalDescriptor oldValuePrincipal = previousStackIdentity.getPrincipalDescriptor();
String previousValuePrincipalValue = null;
KerberosPrincipalDescriptor userValuePrincipal = userIdentity.getPrincipalDescriptor();
String userValuePrincipalValue = null;
if (oldValuePrincipal != null) {
previousValuePrincipalValue = oldValuePrincipal.getValue();
}
if (userValuePrincipal != null) {
userValuePrincipalValue = userValuePrincipal.getValue();
}
// If the user changed the stack default, replace the new stack default value with the user's
// changed value
if ((userValuePrincipalValue != null) && !userValuePrincipalValue.equals(previousValuePrincipalValue)) {
updatedValuePrincipal.setValue(userValuePrincipalValue);
}
}
// If the new identity definition is a reference and no longer has a keytab definition,
// Ignore any user changes to the old keytab definition.
if (updatedValueKeytab != null) {
KerberosKeytabDescriptor oldValueKeytab = previousStackIdentity.getKeytabDescriptor();
String previousValueKeytabFile = null;
KerberosKeytabDescriptor userValueKeytab = userIdentity.getKeytabDescriptor();
String userValueKeytabFile = null;
if (oldValueKeytab != null) {
previousValueKeytabFile = oldValueKeytab.getFile();
}
if (userValueKeytab != null) {
userValueKeytabFile = userValueKeytab.getFile();
}
// If the user changed the stack default, replace the new stack default value with the user's
// changed value
if ((userValueKeytabFile != null) && !userValueKeytabFile.equals(previousValueKeytabFile)) {
updatedValueKeytab.setFile(userValueKeytabFile);
}
}
// Remove the when clause
updatedValue.setWhen(null);
return updatedValue;
}
/**
* Processes the configuration-level Kerberos descriptors to add, remove, or update data in the user-specified
* Kerberos descriptor.
*
* @param previousStackConfigurations a map of {@link KerberosConfigurationDescriptor}s from the previous stack version's Kerberos descriptor
* @param newStackConfigurations a map of {@link KerberosConfigurationDescriptor}s from the new stack version's Kerberos descriptor
* @param userConfigurations a map of {@link KerberosConfigurationDescriptor}s from the user-supplied Kerberos descriptor
* @return a map of updated {@link KerberosConfigurationDescriptor}s
*/
private static Map<String, KerberosConfigurationDescriptor> processConfigurations(Map<String, KerberosConfigurationDescriptor> previousStackConfigurations,
Map<String, KerberosConfigurationDescriptor> newStackConfigurations,
Map<String, KerberosConfigurationDescriptor> userConfigurations) {
if ((userConfigurations == null) || ((previousStackConfigurations == null) && (newStackConfigurations == null))) {
return userConfigurations;
}
Map<String, KerberosConfigurationDescriptor> updatedConfigurations = new TreeMap<String, KerberosConfigurationDescriptor>();
if (previousStackConfigurations == null) {
previousStackConfigurations = Collections.emptyMap();
}
if (newStackConfigurations == null) {
newStackConfigurations = Collections.emptyMap();
}
// Find configurations to modify or remove
for (Map.Entry<String, KerberosConfigurationDescriptor> entry : userConfigurations.entrySet()) {
String name = entry.getKey();
KerberosConfigurationDescriptor userValue = entry.getValue();
if (userValue != null) {
if (newStackConfigurations.containsKey(name)) {
KerberosConfigurationDescriptor oldValue = previousStackConfigurations.get(name);
KerberosConfigurationDescriptor newValue = newStackConfigurations.get(name);
LOG.debug("Processing configuration type {} for modifications", name);
updatedConfigurations.put(name, processConfiguration(oldValue, newValue, userValue));
} else if (previousStackConfigurations.containsKey(name)) {
LOG.debug("Removing configuration type {} from user-specified Kerberos Descriptor", name);
// Nothing to do here, just don't add it to the updated configurations map...
} else {
LOG.debug("Leaving configuration type {} in user-specified Kerberos Descriptor unchanged since it was user-defined.", name);
updatedConfigurations.put(name, userValue);
}
}
}
// Note: there is no need to add configuration definitions that do not exist in the user-specified
// descriptor since they will get added dynamically when merged with the stack default value.
return updatedConfigurations;
}
/**
* Processes a {@link KerberosConfigurationDescriptor} to change the user-supplied data based on the changes
* observed between the previous stack version's data and the new stack version's data.
*
* @param previousStackConfiguration a {@link KerberosConfigurationDescriptor} from the previous stack version's Kerberos descriptor
* @param newStackConfiguration a {@link KerberosConfigurationDescriptor} from the new stack version's Kerberos descriptor
* @param userConfiguration a {@link KerberosConfigurationDescriptor} from the user-specified Kerberos descriptor
* @return an updated {@link KerberosConfigurationDescriptor}
*/
private static KerberosConfigurationDescriptor processConfiguration(KerberosConfigurationDescriptor previousStackConfiguration,
KerberosConfigurationDescriptor newStackConfiguration,
KerberosConfigurationDescriptor userConfiguration) {
KerberosConfigurationDescriptor updatedValue = new KerberosConfigurationDescriptor((userConfiguration == null) ? null : userConfiguration.toMap());
Map<String, String> previousValue = (previousStackConfiguration == null) ? null : previousStackConfiguration.getProperties();
Map<String, String> newValue = (newStackConfiguration == null) ? null : newStackConfiguration.getProperties();
Map<String, String> userValue = updatedValue.getProperties();
updatedValue.setProperties(processProperties(previousValue, newValue, userValue));
return updatedValue;
}
/**
* Processes a map of global or configuration properties to change the user-supplied data based on
* the changes observed between the previous stack version's data and the new stack version's data.
* <p>
* If a property exists in both the previous and new stacks, and the user has not changed it; then
* the value of the property will be updated to the new stack version's value. Else, if the user
* changed the value, the changed value will be left as-is.
* <p>
* If a property exists only in the previous stack, then it will be removed.
* <p>
* If a property exists only in the new stack, then it will be added.
*
* @param previousStackProperties properties from the previous stack version's Kerberos descriptor
* @param newStackProperties properties from the new stack version's Kerberos descriptor
* @param userProperties properties from the user-specified Kerberos descriptor
* @return a new map of updated properties
*/
private static Map<String, String> processProperties(Map<String, String> previousStackProperties,
Map<String, String> newStackProperties,
Map<String, String> userProperties) {
if ((previousStackProperties == null) && (newStackProperties == null)) {
return userProperties;
} else {
Map<String, String> updatedProperties = new TreeMap<String, String>();
if (userProperties != null) {
updatedProperties.putAll(userProperties);
}
if (previousStackProperties == null) {
previousStackProperties = Collections.emptyMap();
}
if (newStackProperties == null) {
newStackProperties = Collections.emptyMap();
}
// Find properties to modify and remove
for (Map.Entry<String, String> entry : previousStackProperties.entrySet()) {
String name = entry.getKey();
if (newStackProperties.containsKey(name)) {
String previousValue = entry.getValue();
String newValue = newStackProperties.get(name);
String userValue = updatedProperties.get(name);
// See if the user property should be modified...
// Test if the old property value is different than the new property value and that the user did
// not update the value from the old default. If the user updated the value, then it would be
// risky to change it to the new stack default value.
if (((previousValue == null) ? (newValue != null) : !previousValue.equals(newValue)) &&
((previousValue == null) ? (userValue == null) : previousValue.equals(userValue))) {
LOG.debug("Modifying property named {} from user-specified Kerberos Descriptor", name);
updatedProperties.put(name, newValue);
}
} else {
LOG.debug("Removing property named {} from user-specified Kerberos Descriptor", name);
updatedProperties.remove(name);
}
}
// Find properties to add
for (Map.Entry<String, String> entry : newStackProperties.entrySet()) {
String name = entry.getKey();
if (!previousStackProperties.containsKey(name) && !updatedProperties.containsKey(name)) {
// A new property was found, add it...
LOG.debug("Adding property named {} to user-specified Kerberos Descriptor", name);
updatedProperties.put(name, entry.getValue());
}
}
return updatedProperties;
}
}
/**
* A convenience method used to change a list of {@link KerberosIdentityDescriptor} items into a map
* of identity names or {@link org.apache.ambari.server.api.services.HostKerberosIdentityService}
* items.
*
* @param identities a list of {@link KerberosIdentityDescriptor}s
* @return a map of identity names or {@link org.apache.ambari.server.api.services.HostKerberosIdentityService}
*/
private static Map<String, KerberosIdentityDescriptor> toMap(List<KerberosIdentityDescriptor> identities) {
if (identities == null) {
return null;
} else {
Map<String, KerberosIdentityDescriptor> map = new TreeMap<String, KerberosIdentityDescriptor>();
for (KerberosIdentityDescriptor identity : identities) {
map.put(identity.getName(), identity);
}
return map;
}
}
}