Repository: ambari Updated Branches: refs/heads/trunk fa763504b -> ef3da6d48
AMBARI-8851. Return correct key number value for dynamically created MIT KDC keytab files Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/ef3da6d4 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/ef3da6d4 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/ef3da6d4 Branch: refs/heads/trunk Commit: ef3da6d483d1f18fcd2853d47c2cd1a1bfd83e5f Parents: fa76350 Author: Robert Levas <[email protected]> Authored: Tue Dec 23 10:56:25 2014 -0500 Committer: John Speidel <[email protected]> Committed: Tue Dec 23 10:58:15 2014 -0500 ---------------------------------------------------------------------- .../kerberos/ADKerberosOperationHandler.java | 14 +- .../kerberos/CreateKeytabFilesServerAction.java | 68 +++---- .../kerberos/CreatePrincipalsServerAction.java | 15 +- .../kerberos/KerberosOperationHandler.java | 165 +++++++--------- .../kerberos/KerberosServerAction.java | 34 +++- .../kerberos/MITKerberosOperationHandler.java | 91 ++++++++- .../AbstractKerberosOperationHandlerTest.java | 196 ++++++++----------- .../kerberos/KerberosOperationHandlerTest.java | 8 +- 8 files changed, 321 insertions(+), 270 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/ef3da6d4/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandler.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandler.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandler.java index 75fc5c2..d963c1b 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandler.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandler.java @@ -20,7 +20,6 @@ package org.apache.ambari.server.serveraction.kerberos; import org.apache.ambari.server.AmbariException; -import org.apache.ambari.server.serveraction.kerberos.KerberosCredential; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -32,7 +31,6 @@ import java.io.UnsupportedEncodingException; import java.util.HashSet; import java.util.Properties; import java.util.Set; -import java.util.StringTokenizer; /** * Implementation of <code>KerberosOperationHandler</code> to created principal in Active Directory @@ -222,11 +220,11 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler { * * @param principal a String containing the principal to add * @param password a String containing the password to use when creating the principal - * @return true if the principal was successfully created; otherwise false + * @return an Integer declaring the generated key number * @throws AmbariException */ @Override - public boolean createServicePrincipal(String principal, String password) + public Integer createServicePrincipal(String principal, String password) throws AmbariException { if (principal == null) { throw new AmbariException("principal is null"); @@ -275,7 +273,7 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler { } catch (NamingException ne) { throw new AmbariException("Can not created principal : " + principal, ne); } - return true; + return 0; } /** @@ -285,11 +283,11 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler { * * @param principal a String containing the principal to update * @param password a String containing the password to set - * @return true if the password was successfully updated; otherwise false + * @return an Integer declaring the new key number * @throws AmbariException */ @Override - public boolean setPrincipalPassword(String principal, String password) throws AmbariException { + public Integer setPrincipalPassword(String principal, String password) throws AmbariException { if (principal == null) { throw new AmbariException("principal is null"); } @@ -316,7 +314,7 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler { } catch (UnsupportedEncodingException ue) { throw new AmbariException("Unsupported encoding UTF-16LE", ue); } - return true; + return 0; } /** http://git-wip-us.apache.org/repos/asf/ambari/blob/ef3da6d4/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/CreateKeytabFilesServerAction.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/CreateKeytabFilesServerAction.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/CreateKeytabFilesServerAction.java index e8e3a2e..be70ba1 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/CreateKeytabFilesServerAction.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/CreateKeytabFilesServerAction.java @@ -115,44 +115,44 @@ public class CreateKeytabFilesServerAction extends KerberosServerAction { commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", "", message); } else { Map<String, String> principalPasswordMap = getPrincipalPasswordMap(requestSharedDataContext); - - if (principalPasswordMap != null) { - String host = identityRecord.get(HOSTNAME); - String keytabFilePath = identityRecord.get(KEYTAB_FILE_PATH); - - if ((host != null) && !host.isEmpty() && (keytabFilePath != null) && !keytabFilePath.isEmpty()) { - // Look up the current evaluatedPrincipal's password. - // If found create th keytab file, else skip it. - String password = principalPasswordMap.get(evaluatedPrincipal); - - if (password == null) { - String message = String.format("Failed to create keytab file for %s, missing password", evaluatedPrincipal); - LOG.error(message); - commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", "", message); - } else { - // Determine where to store the keytab file. It should go into a host-specific - // directory under the previously determined data directory. - File hostDirectory = new File(getDataDirectoryPath(), host); - - // Ensure the host directory exists... - if (hostDirectory.exists() || hostDirectory.mkdirs()) { - File keytabFile = new File(hostDirectory, DigestUtils.sha1Hex(keytabFilePath)); - - if (operationHandler.createKeytabFile(evaluatedPrincipal, password, keytabFile)) { - LOG.debug("Successfully created keytab file for {} at {}", - evaluatedPrincipal, keytabFile.getAbsolutePath()); - } else { - String message = String.format("Failed to create keytab file for %s at %s", - evaluatedPrincipal, keytabFile.getAbsolutePath()); - LOG.error(message); - commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", "", message); - } + Map<String, Integer> principalKeyNumberMap = getPrincipalKeyNumberMap(requestSharedDataContext); + + String host = identityRecord.get(HOSTNAME); + String keytabFilePath = identityRecord.get(KEYTAB_FILE_PATH); + + if ((host != null) && !host.isEmpty() && (keytabFilePath != null) && !keytabFilePath.isEmpty()) { + // Look up the current evaluatedPrincipal's password. + // If found create th keytab file, else skip it. + String password = principalPasswordMap.get(evaluatedPrincipal); + + if (password == null) { + String message = String.format("Failed to create keytab file for %s, missing password", evaluatedPrincipal); + LOG.error(message); + commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", "", message); + } else { + // Determine where to store the keytab file. It should go into a host-specific + // directory under the previously determined data directory. + File hostDirectory = new File(getDataDirectoryPath(), host); + + // Ensure the host directory exists... + if (hostDirectory.exists() || hostDirectory.mkdirs()) { + File keytabFile = new File(hostDirectory, DigestUtils.sha1Hex(keytabFilePath)); + Integer keyNumber = principalKeyNumberMap.get(evaluatedPrincipal); + + if (operationHandler.createKeytabFile(evaluatedPrincipal, password, keyNumber, keytabFile)) { + LOG.debug("Successfully created keytab file for {} at {}", + evaluatedPrincipal, keytabFile.getAbsolutePath()); } else { - String message = String.format("Failed to create keytab file for %s, the container directory does not exist: %s", - evaluatedPrincipal, hostDirectory.getAbsolutePath()); + String message = String.format("Failed to create keytab file for %s at %s", + evaluatedPrincipal, keytabFile.getAbsolutePath()); LOG.error(message); commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", "", message); } + } else { + String message = String.format("Failed to create keytab file for %s, the container directory does not exist: %s", + evaluatedPrincipal, hostDirectory.getAbsolutePath()); + LOG.error(message); + commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", "", message); } } } http://git-wip-us.apache.org/repos/asf/ambari/blob/ef3da6d4/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/CreatePrincipalsServerAction.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/CreatePrincipalsServerAction.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/CreatePrincipalsServerAction.java index 3785e19..a6392da 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/CreatePrincipalsServerAction.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/CreatePrincipalsServerAction.java @@ -66,8 +66,9 @@ public class CreatePrincipalsServerAction extends KerberosServerAction { * If a password has not been previously created the current evaluatedPrincipal, create a "secure" * password using {@link KerberosOperationHandler#createSecurePassword()}. Then if the principal * does not exist in the KDC, create it using the generated password; else if it does exist update - * its password. Finally store the generated password in the shared principal-to-password map so - * that subsequent process may use it if necessary. + * its password. Finally store the generated password in the shared principal-to-password map and + * store the new key numbers in the shared principal-to-key_number map so that subsequent process + * may use the data if necessary. * * @param identityRecord a Map containing the data for the current identity record * @param evaluatedPrincipal a String indicating the relevant principal @@ -87,6 +88,7 @@ public class CreatePrincipalsServerAction extends KerberosServerAction { CommandReport commandReport = null; Map<String, String> principalPasswordMap = getPrincipalPasswordMap(requestSharedDataContext); + Map<String, Integer> principalKeyNumberMap = getPrincipalKeyNumberMap(requestSharedDataContext); String password = principalPasswordMap.get(evaluatedPrincipal); @@ -98,8 +100,11 @@ public class CreatePrincipalsServerAction extends KerberosServerAction { // A new password/key would have been generated after exporting the keytab anyways. LOG.warn("Principal already exists, setting new password - {}", evaluatedPrincipal); - if (operationHandler.setPrincipalPassword(evaluatedPrincipal, password)) { + Integer keyNumber = operationHandler.setPrincipalPassword(evaluatedPrincipal, password); + + if (keyNumber != null) { principalPasswordMap.put(evaluatedPrincipal, password); + principalKeyNumberMap.put(evaluatedPrincipal, keyNumber); LOG.debug("Successfully set password for principal {}", evaluatedPrincipal); } else { String message = String.format("Failed to set password for principal %s, unknown reason", evaluatedPrincipal); @@ -108,9 +113,11 @@ public class CreatePrincipalsServerAction extends KerberosServerAction { } } else { LOG.debug("Creating new principal - {}", evaluatedPrincipal); + Integer keyNumber = operationHandler.createServicePrincipal(evaluatedPrincipal, password); - if (operationHandler.createServicePrincipal(evaluatedPrincipal, password)) { + if (keyNumber != null) { principalPasswordMap.put(evaluatedPrincipal, password); + principalKeyNumberMap.put(evaluatedPrincipal, keyNumber); LOG.debug("Successfully created new principal {}", evaluatedPrincipal); } else { String message = String.format("Failed to create principal %s, unknown reason", evaluatedPrincipal); http://git-wip-us.apache.org/repos/asf/ambari/blob/ef3da6d4/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandler.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandler.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandler.java index 45b60f5..ae2d4b2 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandler.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandler.java @@ -37,8 +37,11 @@ import java.io.IOException; import java.io.OutputStream; import java.security.SecureRandom; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; /** * KerberosOperationHandler is an abstract class providing basic implementations of common Kerberos @@ -61,6 +64,19 @@ public abstract class KerberosOperationHandler { private final static char[] SECURE_PASSWORD_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890?.!$%^*()-_+=~".toCharArray(); + + /** + * The default set of ciphers to use for creating keytab entries + */ + private static final Set<EncryptionType> DEFAULT_CIPHERS = Collections.unmodifiableSet( + new HashSet<EncryptionType>() {{ + add(EncryptionType.DES_CBC_MD5); + add(EncryptionType.DES3_CBC_SHA1_KD); + add(EncryptionType.RC4_HMAC); + add(EncryptionType.AES128_CTS_HMAC_SHA1_96); + add(EncryptionType.AES256_CTS_HMAC_SHA1_96); + }}); + private KerberosCredential administratorCredentials; private String defaultRealm; @@ -165,10 +181,10 @@ public abstract class KerberosOperationHandler { * * @param principal a String containing the principal to add * @param password a String containing the password to use when creating the principal - * @return true if the principal was successfully created; otherwise false + * @return an Integer declaring the generated key number * @throws AmbariException */ - public abstract boolean createServicePrincipal(String principal, String password) + public abstract Integer createServicePrincipal(String principal, String password) throws AmbariException; /** @@ -178,10 +194,10 @@ public abstract class KerberosOperationHandler { * * @param principal a String containing the principal to update * @param password a String containing the password to set - * @return true if the password was successfully updated; otherwise false + * @return an Integer declaring the new key number * @throws AmbariException */ - public abstract boolean setPrincipalPassword(String principal, String password) + public abstract Integer setPrincipalPassword(String principal, String password) throws AmbariException; /** @@ -205,7 +221,7 @@ public abstract class KerberosOperationHandler { * @return true if the keytab file was successfully created; false otherwise * @throws AmbariException */ - public boolean createKeytabFile(String principal, String password, File keytabFile) + public boolean createKeytabFile(String principal, String password, Integer keyNumber, File keytabFile) throws AmbariException { boolean success = false; @@ -216,119 +232,70 @@ public abstract class KerberosOperationHandler { } else if (keytabFile == null) { throw new AmbariException(String.format("Failed to create keytab file for %s, missing file path", principal)); } else { - // Create a set of keys and relevant keytab entries - Map<EncryptionType, EncryptionKey> keys = KerberosKeyFactory.getKerberosKeys(principal, password); - - if (keys != null) { - KerberosTime timestamp = new KerberosTime(); - List<KeytabEntry> keytabEntries = new ArrayList<KeytabEntry>(); - - Keytab keytab; - - if (keytabFile.exists() && keytabFile.canRead() && (keytabFile.length() > 0)) { - // If the keytab file already exists, read it in and append the new keytabs to it so that - // potentially important data is not lost - try { - keytab = Keytab.read(keytabFile); - } catch (IOException e) { - // There was an issue reading in the existing keytab file... we might loose some keytabs - // but that is unlikely... - keytab = new Keytab(); - } + Keytab keytab; + Set<EncryptionType> ciphers = new HashSet<EncryptionType>(DEFAULT_CIPHERS); + List<KeytabEntry> keytabEntries = new ArrayList<KeytabEntry>(); - // In case there were any existing keytab entries, add them to the new entries list do - // they are not lost - List<KeytabEntry> existingEntries = keytab.getEntries(); - if ((existingEntries != null) && !existingEntries.isEmpty()) { - keytabEntries.addAll(existingEntries); - } - } else { + if (keytabFile.exists() && keytabFile.canRead() && (keytabFile.length() > 0)) { + // If the keytab file already exists, read it in and append the new keytabs to it so that + // potentially important data is not lost + try { + keytab = Keytab.read(keytabFile); + } catch (IOException e) { + // There was an issue reading in the existing keytab file... we might loose some keytabs + // but that is unlikely... keytab = new Keytab(); } - for (EncryptionKey encryptionKey : keys.values()) { - keytabEntries.add(new KeytabEntry(principal, 1, timestamp, (byte) 0, encryptionKey)); - } + // In case there were any existing keytab entries, add them to the new entries list so + // they are not lost. While at it, remove ciphers that already exist for the given principal + // so duplicate entries aren't added to the file. + List<KeytabEntry> existingEntries = keytab.getEntries(); + if ((existingEntries != null) && !existingEntries.isEmpty()) { - keytab.setEntries(keytabEntries); + for (KeytabEntry entry : existingEntries) { + // Remove ciphers that will cause duplicate entries + if (principal.equals(entry.getPrincipalName())) { + ciphers.remove(entry.getKey().getKeyType()); + } - try { - keytab.write(keytabFile); - success = true; - } catch (IOException e) { - String message = String.format("Failed to export keytab file for %s", principal); - LOG.error(message, e); - - if (!keytabFile.delete()) { - keytabFile.deleteOnExit(); + keytabEntries.add(entry); } - - throw new AmbariException(message, e); } + } else { + keytab = new Keytab(); } - } - - return success; - } - - /** - * Create a keytab file using the set of supplied principal-to-password map. - * <p/> - * If a file exists where filePath points to, it will be overwritten. - * - * @param credentials a Map of principals to password, each entry will be placed in the specified file - * @param keytabFile a File containing the absolute path to the keytab file - * @return true if the keytab file was successfully created; false otherwise - * @throws AmbariException - */ - public boolean createKeytabFile(Map<String, String> credentials, File keytabFile) - throws AmbariException { - boolean success = false; - - if (credentials == null) { - throw new AmbariException("Failed to create keytab file, missing credentials"); - } else if (keytabFile == null) { - throw new AmbariException("Failed to create keytab file, missing file path"); - } else { - List<KeytabEntry> keytabEntries = new ArrayList<KeytabEntry>(); - KerberosTime timestamp = new KerberosTime(); - // For each set of credentials in the map, create a set of keys and relevant keytab entries - for (Map.Entry<String, String> entry : credentials.entrySet()) { - String principal = entry.getKey(); - String password = entry.getValue(); + if (ciphers.isEmpty()) { + // There are no new keys to create + success = true; + } else { + // Create a set of keys and relevant keytab entries + Map<EncryptionType, EncryptionKey> keys = KerberosKeyFactory.getKerberosKeys(principal, password, ciphers); - if (principal == null) { - LOG.warn("Missing principal, skipping entry"); - } else if (password == null) { - LOG.warn("Missing password, skipping entry"); - } else { - Map<EncryptionType, EncryptionKey> keys = KerberosKeyFactory.getKerberosKeys(principal, password); + if (keys != null) { + byte keyVersion = (keyNumber == null) ? 0 : keyNumber.byteValue(); + KerberosTime timestamp = new KerberosTime(); for (EncryptionKey encryptionKey : keys.values()) { - keytabEntries.add(new KeytabEntry(principal, 1, timestamp, (byte) 0, encryptionKey)); + keytabEntries.add(new KeytabEntry(principal, 1, timestamp, keyVersion, encryptionKey)); } - } - } - // If there are keytab entries, create and write the keytab file - if (!keytabEntries.isEmpty()) { - Keytab keytab = new Keytab(); + keytab.setEntries(keytabEntries); - keytab.setEntries(keytabEntries); + try { + keytab.write(keytabFile); + success = true; + } catch (IOException e) { + String message = String.format("Failed to export keytab file for %s", principal); + LOG.error(message, e); - try { - keytab.write(keytabFile); - success = true; - } catch (IOException e) { - String message = String.format("Failed to export keytab file"); - LOG.error(message, e); + if (!keytabFile.delete()) { + keytabFile.deleteOnExit(); + } - if (!keytabFile.delete()) { - keytabFile.deleteOnExit(); + throw new AmbariException(message, e); } - - throw new AmbariException(message, e); } } } http://git-wip-us.apache.org/repos/asf/ambari/blob/ef3da6d4/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosServerAction.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosServerAction.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosServerAction.java index 71a7659..a99628c 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosServerAction.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosServerAction.java @@ -73,13 +73,17 @@ public abstract class KerberosServerAction extends AbstractServerAction { */ public static final String DATA_DIRECTORY_PREFIX = ".ambari_"; - /* - * Kerberos action shared data entry names + * Kerberos action shared data entry name for the principal-to-password map */ private static final String PRINCIPAL_PASSWORD_MAP = "principal_password_map"; /* + * Kerberos action shared data entry name for the principal-to-key_number map + */ + private static final String PRINCIPAL_KEY_NUMBER_MAP = "principal_key_number_map"; + + /* * Key used in kerberosCommandParams in ExecutionCommand for base64 encoded keytab content */ public static final String KEYTAB_CONTENT_BASE64 = "keytab_content_base64"; @@ -183,6 +187,32 @@ public abstract class KerberosServerAction extends AbstractServerAction { } /** + * Gets the shared principal-to-key_number Map used to store principals and key numbers for + * use within the current request context. + * <p/> + * If the requested Map is not found in requestSharedDataContext, one will be created and stored, + * ensuring that a Map will always be returned, assuming requestSharedDataContext is not null. + * + * @param requestSharedDataContext a Map to be used a shared data among all ServerActions related + * to a given request + * @return A Map of principals-to-key_numbers + */ + protected static Map<String, Integer> getPrincipalKeyNumberMap(Map<String, Object> requestSharedDataContext) { + if (requestSharedDataContext == null) { + return null; + } else { + Object map = requestSharedDataContext.get(PRINCIPAL_KEY_NUMBER_MAP); + + if (map == null) { + map = new HashMap<String, String>(); + requestSharedDataContext.put(PRINCIPAL_KEY_NUMBER_MAP, map); + } + + return (Map<String, Integer>) map; + } + } + + /** * Given a (command parameter) Map, attempts to safely retrieve the "data_directory" property. * * @param commandParameters a Map containing the dictionary of data to interrogate http://git-wip-us.apache.org/repos/asf/ambari/blob/ef3da6d4/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/MITKerberosOperationHandler.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/MITKerberosOperationHandler.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/MITKerberosOperationHandler.java index 2ebfc97..04d43a5 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/MITKerberosOperationHandler.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/MITKerberosOperationHandler.java @@ -24,8 +24,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; +import java.text.NumberFormat; +import java.text.ParseException; import java.util.ArrayList; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * MITKerberosOperationHandler is an implementation of a KerberosOperationHandler providing @@ -35,6 +39,13 @@ import java.util.List; * available */ public class MITKerberosOperationHandler extends KerberosOperationHandler { + + /** + * A regular expression pattern to use to parse the key number from the text captured from the + * get_principal kadmin command + */ + private final static Pattern PATTERN_GET_KEY_NUMBER = Pattern.compile("^.*?Key: vno (\\d+).*$", Pattern.DOTALL); + private final static Logger LOG = LoggerFactory.getLogger(MITKerberosOperationHandler.class); @@ -105,11 +116,11 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler { * * @param principal a String containing the principal add * @param password a String containing the password to use when creating the principal - * @return true if the principal was successfully created; otherwise false + * @return an Integer declaring the generated key number * @throws AmbariException */ @Override - public boolean createServicePrincipal(String principal, String password) + public Integer createServicePrincipal(String principal, String password) throws AmbariException { if ((principal == null) || principal.isEmpty()) { @@ -141,7 +152,11 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler { // If there is data from STDOUT, see if the following string exists: // Principal "<principal>" created - return (stdOut != null) && stdOut.contains(String.format("Principal \"%s\" created", principal)); + if ((stdOut != null) && stdOut.contains(String.format("Principal \"%s\" created", principal))) { + return getKeyNumber(principal); + } else { + throw new AmbariException(String.format("Failed to create service principal for %s", principal)); + } } else { LOG.warn("Failed to create service principal for {}:\n\tExitCode: {}\n\tSTDOUT: {}\n\tSTDERR: {}", principal, result.getExitCode(), result.getStdout(), result.getStderr()); @@ -167,11 +182,11 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler { * * @param principal a String containing the principal to update * @param password a String containing the password to set - * @return true if the password was successfully updated; otherwise false + * @return an Integer declaring the new key number * @throws AmbariException */ @Override - public boolean setPrincipalPassword(String principal, String password) throws AmbariException { + public Integer setPrincipalPassword(String principal, String password) throws AmbariException { if ((principal == null) || principal.isEmpty()) { throw new AmbariException("Failed to set password - no principal specified"); } else { @@ -197,7 +212,7 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler { if (result != null) { if (result.isSuccessful()) { - return true; + return getKeyNumber(principal); } else { LOG.warn("Failed to set password for {}:\n\tExitCode: {}\n\tSTDOUT: {}\n\tSTDERR: {}", principal, result.getExitCode(), result.getStdout(), result.getStderr()); @@ -257,6 +272,70 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler { } /** + * Retrieves the current key number assigned to the identity identified by the specified principal + * + * @param principal a String declaring the principal to look up + * @return an Integer declaring the current key number + * @throws AmbariException if an error occurs while looking up the relevant key number + */ + private Integer getKeyNumber(String principal) throws AmbariException { + if ((principal == null) || principal.isEmpty()) { + throw new AmbariException("Failed to get key number for principal - no principal specified"); + } else { + // Create the kdamin query: get_principal <principal> + String query = String.format("get_principal %s", principal); + + try { + ShellCommandUtil.Result result = invokeKAdmin(query); + + if (result != null) { + if (result.isSuccessful()) { + String stdOut = result.getStdout(); + + if (stdOut == null) { + LOG.warn("Failed to get key number for {}:\n\tExitCode: {}\n\tSTDOUT: NULL\n\tSTDERR: {}", + principal, result.getExitCode(), result.getStderr()); + throw new AmbariException(String.format("Failed to get key number for %s", principal)); + } + + Matcher matcher = PATTERN_GET_KEY_NUMBER.matcher(stdOut); + + if (matcher.matches()) { + NumberFormat numberFormat = NumberFormat.getIntegerInstance(); + String keyNumber = matcher.group(1); + + numberFormat.setGroupingUsed(false); + try { + Number number = numberFormat.parse(keyNumber); + return (number == null) ? 0 : number.intValue(); + } catch (ParseException e) { + LOG.warn("Failed to get key number for {} - invalid key number value ({}):\n\tExitCode: {}\n\tSTDOUT: NULL\n\tSTDERR: {}", + principal, keyNumber, result.getExitCode(), result.getStderr()); + throw new AmbariException(String.format("Failed to get key number for %s", principal)); + } + } else { + LOG.warn("Failed to get key number for {} - unexpected STDOUT data:\n\tExitCode: {}\n\tSTDOUT: NULL\n\tSTDERR: {}", + principal, result.getExitCode(), result.getStderr()); + throw new AmbariException(String.format("Failed to get key number for %s", principal)); + } + } else { + LOG.warn("Failed to get key number for {}:\n\tExitCode: {}\n\tSTDOUT: {}\n\tSTDERR: {}", + principal, result.getExitCode(), result.getStdout(), result.getStderr()); + throw new AmbariException(String.format("Failed to get key number for %s", principal)); + } + } else { + String message = String.format("Failed to get key number for %s - Unknown reason", principal); + LOG.warn(message); + throw new AmbariException(message); + } + } catch (AmbariException e) { + LOG.error(String.format("Failed to get key number for %s", principal), e); + throw e; + } + } + } + + /** * Invokes the kadmin shell command to issue queries * * @param query a String containing the query to send to the kdamin command http://git-wip-us.apache.org/repos/asf/ambari/blob/ef3da6d4/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/AbstractKerberosOperationHandlerTest.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/AbstractKerberosOperationHandlerTest.java b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/AbstractKerberosOperationHandlerTest.java index f21260a..92a4100 100644 --- a/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/AbstractKerberosOperationHandlerTest.java +++ b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/AbstractKerberosOperationHandlerTest.java @@ -24,16 +24,22 @@ import org.apache.commons.codec.binary.Base64; import org.apache.directory.server.kerberos.shared.keytab.Keytab; import org.apache.directory.server.kerberos.shared.keytab.KeytabEntry; import org.junit.After; +import org.junit.Rule; import org.junit.Test; import org.junit.Before; +import org.junit.rules.TemporaryFolder; import java.io.File; import java.io.FileInputStream; -import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Set; public abstract class AbstractKerberosOperationHandlerTest { + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + protected final KerberosOperationHandler handler; protected AbstractKerberosOperationHandlerTest(KerberosOperationHandler handler) { @@ -71,13 +77,13 @@ public abstract class AbstractKerberosOperationHandlerTest { } @Override - public boolean createServicePrincipal(String principal, String password) throws AmbariException { - return false; + public Integer createServicePrincipal(String principal, String password) throws AmbariException { + return 0; } @Override - public boolean setPrincipalPassword(String principal, String password) throws AmbariException { - return false; + public Integer setPrincipalPassword(String principal, String password) throws AmbariException { + return 0; } @Override @@ -117,158 +123,122 @@ public abstract class AbstractKerberosOperationHandlerTest { } @Test - public void testCreateKeytabFileAllAtOnce() throws Exception { - - File file = File.createTempFile("ambari_ut_", ".dat"); + public void testCreateKeytabFileOneAtATime() throws Exception { + File file = folder.newFile(); final String principal1 = "[email protected]"; final String principal2 = "[email protected]"; + int count; - try { - Assert.assertTrue(handler.createKeytabFile(new HashMap<String, String>() { - { - put(principal1, handler.createSecurePassword()); - } - }, file)); + Assert.assertTrue(handler.createKeytabFile(principal1, handler.createSecurePassword(), 0, file)); - Keytab keytab = Keytab.read(file); - Assert.assertNotNull(keytab); + Keytab keytab = Keytab.read(file); + Assert.assertNotNull(keytab); - List<KeytabEntry> entries = keytab.getEntries(); - Assert.assertNotNull(entries); - Assert.assertFalse(entries.isEmpty()); + List<KeytabEntry> entries = keytab.getEntries(); + Assert.assertNotNull(entries); + Assert.assertFalse(entries.isEmpty()); - for (KeytabEntry entry : entries) { - Assert.assertEquals(principal1, entry.getPrincipalName()); - } + count = entries.size(); + + for (KeytabEntry entry : entries) { + Assert.assertEquals(principal1, entry.getPrincipalName()); + } - Assert.assertTrue(handler.createKeytabFile(new HashMap<String, String>() { - { - put(principal1, handler.createSecurePassword()); - put(principal2, handler.createSecurePassword()); - } - }, file)); + Assert.assertTrue(handler.createKeytabFile(principal2, handler.createSecurePassword(), 0, file)); - keytab = Keytab.read(file); - Assert.assertNotNull(keytab); + keytab = Keytab.read(file); + Assert.assertNotNull(keytab); - entries = keytab.getEntries(); - Assert.assertNotNull(entries); - Assert.assertFalse(entries.isEmpty()); - } finally { - if (!file.delete()) { - file.deleteOnExit(); - } - } + entries = keytab.getEntries(); + Assert.assertNotNull(entries); + Assert.assertFalse(entries.isEmpty()); + + Assert.assertEquals(count * 2, entries.size()); } @Test - public void testCreateKeytabFileOneAtATime() throws Exception { - File file = File.createTempFile("ambari_ut_", ".dat"); + public void testEnsureKeytabFileContainsNoDuplicates() throws Exception { + File file = folder.newFile(); final String principal1 = "[email protected]"; final String principal2 = "[email protected]"; - int count; - - try { - Assert.assertTrue(handler.createKeytabFile(principal1, handler.createSecurePassword(), file)); - - Keytab keytab = Keytab.read(file); - Assert.assertNotNull(keytab); + Set<String> seenEntries = new HashSet<String>(); - List<KeytabEntry> entries = keytab.getEntries(); - Assert.assertNotNull(entries); - Assert.assertFalse(entries.isEmpty()); - - count = entries.size(); - - for (KeytabEntry entry : entries) { - Assert.assertEquals(principal1, entry.getPrincipalName()); - } + Assert.assertTrue(handler.createKeytabFile(principal1, handler.createSecurePassword(), 0, file)); + Assert.assertTrue(handler.createKeytabFile(principal2, handler.createSecurePassword(), 0, file)); - Assert.assertTrue(handler.createKeytabFile(principal2, handler.createSecurePassword(), file)); + // Attempt to add duplicate entries + Assert.assertTrue(handler.createKeytabFile(principal2, handler.createSecurePassword(), 0, file)); - keytab = Keytab.read(file); - Assert.assertNotNull(keytab); + Keytab keytab = Keytab.read(file); + Assert.assertNotNull(keytab); - entries = keytab.getEntries(); - Assert.assertNotNull(entries); - Assert.assertFalse(entries.isEmpty()); + List<KeytabEntry> entries = keytab.getEntries(); + Assert.assertNotNull(entries); + Assert.assertFalse(entries.isEmpty()); - Assert.assertEquals(count * 2, entries.size()); - } finally { - if (!file.delete()) { - file.deleteOnExit(); - } + for (KeytabEntry entry : entries) { + String seenEntry = String.format("%s|%s", entry.getPrincipalName(), entry.getKey().getKeyType().toString()); + Assert.assertFalse(seenEntries.contains(seenEntry)); + seenEntries.add(seenEntry); } } @Test public void testCreateKeytabFileExceptions() throws Exception { - File file = File.createTempFile("ambari_ut_", ".dat"); + File file = folder.newFile(); final String principal1 = "[email protected]"; try { - try { - handler.createKeytabFile(null, handler.createSecurePassword(), file); - Assert.fail("AmbariException not thrown with null principal"); - } catch (Throwable t) { - Assert.assertEquals(AmbariException.class, t.getClass()); - } + handler.createKeytabFile(null, handler.createSecurePassword(), 0, file); + Assert.fail("AmbariException not thrown with null principal"); + } catch (Throwable t) { + Assert.assertEquals(AmbariException.class, t.getClass()); + } - try { - handler.createKeytabFile(principal1, null, file); - Assert.fail("AmbariException not thrown with null password"); - } catch (Throwable t) { - Assert.assertEquals(AmbariException.class, t.getClass()); - } + try { + handler.createKeytabFile(principal1, null, null, file); + Assert.fail("AmbariException not thrown with null password"); + } catch (Throwable t) { + Assert.assertEquals(AmbariException.class, t.getClass()); + } - try { - handler.createKeytabFile(principal1, handler.createSecurePassword(), null); - Assert.fail("AmbariException not thrown with null file"); - } catch (Throwable t) { - Assert.assertEquals(AmbariException.class, t.getClass()); - } - } finally { - if (!file.delete()) { - file.deleteOnExit(); - } + try { + handler.createKeytabFile(principal1, handler.createSecurePassword(), 0, null); + Assert.fail("AmbariException not thrown with null file"); + } catch (Throwable t) { + Assert.assertEquals(AmbariException.class, t.getClass()); } } @Test public void testCreateKeytabFileFromBase64EncodedData() throws Exception { - File file = File.createTempFile("ambari_ut_", ".dat"); + File file = folder.newFile(); final String principal = "[email protected]"; - try { - Assert.assertTrue(handler.createKeytabFile(principal, handler.createSecurePassword(), file)); + Assert.assertTrue(handler.createKeytabFile(principal, handler.createSecurePassword(), 0, file)); - FileInputStream fis = new FileInputStream(file); - byte[] data = new byte[(int) file.length()]; + FileInputStream fis = new FileInputStream(file); + byte[] data = new byte[(int) file.length()]; - Assert.assertEquals(data.length, fis.read(data)); - fis.close(); + Assert.assertEquals(data.length, fis.read(data)); + fis.close(); - File f = handler.createKeytabFile(Base64.encodeBase64String(data)); + File f = handler.createKeytabFile(Base64.encodeBase64String(data)); - try { - Keytab keytab = Keytab.read(f); - Assert.assertNotNull(keytab); + try { + Keytab keytab = Keytab.read(f); + Assert.assertNotNull(keytab); - List<KeytabEntry> entries = keytab.getEntries(); - Assert.assertNotNull(entries); - Assert.assertFalse(entries.isEmpty()); + List<KeytabEntry> entries = keytab.getEntries(); + Assert.assertNotNull(entries); + Assert.assertFalse(entries.isEmpty()); - for (KeytabEntry entry : entries) { - Assert.assertEquals(principal, entry.getPrincipalName()); - } - } finally { - if (!f.delete()) { - f.deleteOnExit(); - } + for (KeytabEntry entry : entries) { + Assert.assertEquals(principal, entry.getPrincipalName()); } } finally { - if (!file.delete()) { - file.deleteOnExit(); + if (!f.delete()) { + f.deleteOnExit(); } } } http://git-wip-us.apache.org/repos/asf/ambari/blob/ef3da6d4/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandlerTest.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandlerTest.java b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandlerTest.java index 296580e..72d58e2 100644 --- a/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandlerTest.java +++ b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandlerTest.java @@ -41,13 +41,13 @@ public class KerberosOperationHandlerTest extends AbstractKerberosOperationHandl } @Override - public boolean createServicePrincipal(String principal, String password) throws AmbariException { - return false; + public Integer createServicePrincipal(String principal, String password) throws AmbariException { + return 0; } @Override - public boolean setPrincipalPassword(String principal, String password) throws AmbariException { - return false; + public Integer setPrincipalPassword(String principal, String password) throws AmbariException { + return 0; } @Override
