AMBARI-9014. Design admin principal session expiration handling API call (rlevas)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/efe79f01 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/efe79f01 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/efe79f01 Branch: refs/heads/trunk Commit: efe79f015a1ea995a62db922cdbf9edb47c8bcf8 Parents: e67c5ea Author: Robert Levas <[email protected]> Authored: Tue Jan 13 13:27:14 2015 -0500 Committer: Robert Levas <[email protected]> Committed: Tue Jan 13 13:27:14 2015 -0500 ---------------------------------------------------------------------- .../server/controller/ControllerModule.java | 3 + .../server/controller/KerberosHelper.java | 81 +++- .../kerberos/ADKerberosOperationHandler.java | 292 +++++++------ .../kerberos/CreateKeytabFilesServerAction.java | 20 +- .../kerberos/CreatePrincipalsServerAction.java | 60 +-- .../kerberos/KerberosOperationHandler.java | 150 ++++--- .../KerberosOperationHandlerFactory.java | 6 +- .../kerberos/KerberosServerAction.java | 17 +- .../kerberos/MITKerberosOperationHandler.java | 432 +++++++++---------- .../server/controller/KerberosHelperTest.java | 78 +++- .../ADKerberosOperationHandlerTest.java | 241 +++++++++-- .../AbstractKerberosOperationHandlerTest.java | 245 ----------- .../KerberosOperationHandlerFactoryTest.java | 4 +- .../kerberos/KerberosOperationHandlerTest.java | 206 ++++++++- .../MITKerberosOperationHandlerTest.java | 320 +++++++++++++- 15 files changed, 1377 insertions(+), 778 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/efe79f01/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java index 9662669..8647f26 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java @@ -68,6 +68,7 @@ import org.apache.ambari.server.scheduler.ExecutionScheduler; import org.apache.ambari.server.scheduler.ExecutionSchedulerImpl; import org.apache.ambari.server.security.SecurityHelper; import org.apache.ambari.server.security.SecurityHelperImpl; +import org.apache.ambari.server.serveraction.kerberos.KerberosOperationHandlerFactory; import org.apache.ambari.server.state.Cluster; import org.apache.ambari.server.state.Clusters; import org.apache.ambari.server.state.Config; @@ -201,6 +202,8 @@ public class ControllerModule extends AbstractModule { bind(SessionManager.class).toInstance(sessionManager); bind(SessionIdManager.class).toInstance(sessionIdManager); + bind(KerberosOperationHandlerFactory.class); + bind(Configuration.class).toInstance(configuration); bind(OsFamily.class).toInstance(os_family); bind(HostsMap.class).toInstance(hostsMap); http://git-wip-us.apache.org/repos/asf/ambari/blob/efe79f01/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelper.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelper.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelper.java index 0533228..ef0d096 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelper.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelper.java @@ -102,6 +102,9 @@ public class KerberosHelper { @Inject private ConfigHelper configHelper; + @Inject + private KerberosOperationHandlerFactory kerberosOperationHandlerFactory; + /** * The Handler implementation that provides the logic to enable Kerberos */ @@ -384,22 +387,86 @@ public class KerberosHelper { // If there are ServiceComponentHosts to process, make sure the administrator credentials // are available if (!serviceComponentHostsToProcess.isEmpty()) { - if (getEncryptedAdministratorCredentials(cluster) == null) { + try { + String credentials = getEncryptedAdministratorCredentials(cluster); + if (credentials == null) { + throw new IllegalArgumentException( + "Missing KDC administrator credentials.\n" + + "The KDC administrator credentials must be set in session by updating the relevant Cluster resource." + + "This may be done by issuing a PUT to the api/v1/clusters/(cluster name) API entry point with the following payload:\n" + + "{\n" + + " \"session_attributes\" : {\n" + + " \"kerberos_admin\" : {\"principal\" : \"(PRINCIPAL)\", \"password\" : \"(PASSWORD)\"}\n" + + " }\n" + + "}" + ); + } else { + KerberosOperationHandler operationHandler = kerberosOperationHandlerFactory.getKerberosOperationHandler(kdcType); + + if (operationHandler == null) { + throw new AmbariException("Failed to get an appropriate Kerberos operation handler."); + } else { + byte[] key = Integer.toHexString(cluster.hashCode()).getBytes(); + KerberosCredential kerberosCredentials = KerberosCredential.decrypt(credentials, key); + + try { + operationHandler.open(kerberosCredentials, realm); + if (!operationHandler.testAdministratorCredentials()) { + throw new IllegalArgumentException( + "Invalid KDC administrator credentials.\n" + + "The KDC administrator credentials must be set in session by updating the relevant Cluster resource." + + "This may be done by issuing a PUT to the api/v1/clusters/(cluster name) API entry point with the following payload:\n" + + "{\n" + + " \"session_attributes\" : {\n" + + " \"kerberos_admin\" : {\"principal\" : \"(PRINCIPAL)\", \"password\" : \"(PASSWORD)\"}\n" + + " }\n" + + "}" + ); + } + } catch (KerberosAdminAuthenticationException e) { + throw new IllegalArgumentException( + "Invalid KDC administrator credentials.\n" + + "The KDC administrator credentials must be set in session by updating the relevant Cluster resource." + + "This may be done by issuing a PUT to the api/v1/clusters/(cluster name) API entry point with the following payload:\n" + + "{\n" + + " \"session_attributes\" : {\n" + + " \"kerberos_admin\" : {\"principal\" : \"(PRINCIPAL)\", \"password\" : \"(PASSWORD)\"}\n" + + " }\n" + + "}", + e + ); + } catch (KerberosKDCConnectionException e) { + throw new AmbariException("Failed to connect to KDC - " + e.getMessage() + "\n" + + "Update the KDC settings in krb5-conf and kerberos-env configurations to correct this issue.", + e); + } catch (KerberosOperationException e) { + throw new AmbariException(e.getMessage(), e); + } finally { + try { + operationHandler.close(); + } catch (KerberosOperationException e) { + // Ignore this... + } + } + } + } + } catch (IllegalArgumentException e) { try { FileUtils.deleteDirectory(dataDirectory); - } catch (IOException e) { + } catch (Throwable t) { LOG.warn(String.format("The data directory (%s) was not deleted due to an error condition - {%s}", - dataDirectory.getAbsolutePath(), e.getMessage()), e); + dataDirectory.getAbsolutePath(), t.getMessage()), t); } - throw new AmbariException("Missing KDC administrator credentials"); + + throw e; } // Determine if the any auth_to_local configurations need to be set dynamically // Lazily create the auth_to_local rules String authToLocal = null; - for(Map<String, String> configuration: kerberosConfigurations.values()) { - for(Map.Entry<String,String> entry: configuration.entrySet()) { - if("_AUTH_TO_LOCAL_RULES".equals(entry.getValue())) { + for (Map<String, String> configuration : kerberosConfigurations.values()) { + for (Map.Entry<String, String> entry : configuration.entrySet()) { + if ("_AUTH_TO_LOCAL_RULES".equals(entry.getValue())) { if (authToLocal == null) { authToLocal = authToLocalBuilder.generate(realm); } http://git-wip-us.apache.org/repos/asf/ambari/blob/efe79f01/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 a8eed2b..01913e4 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 @@ -19,18 +19,16 @@ package org.apache.ambari.server.serveraction.kerberos; -import org.apache.ambari.server.AmbariException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import javax.naming.*; import javax.naming.directory.*; +import javax.naming.ldap.Control; import javax.naming.ldap.InitialLdapContext; import javax.naming.ldap.LdapContext; import java.io.UnsupportedEncodingException; -import java.util.HashSet; import java.util.Properties; -import java.util.Set; /** * Implementation of <code>KerberosOperationHandler</code> to created principal in Active Directory @@ -41,28 +39,22 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler { private static final String LDAP_CONTEXT_FACTORY_CLASS = "com.sun.jndi.ldap.LdapCtxFactory"; - private String adminPrincipal; - private String adminPassword; - private String realm; - private String ldapUrl; private String principalContainerDn; - private static final int ONELEVEL_SCOPE = SearchControls.ONELEVEL_SCOPE; + private static final int ONE_LEVEL_SCOPE = SearchControls.ONELEVEL_SCOPE; private static final String LDAP_ATUH_MECH_SIMPLE = "simple"; private LdapContext ldapContext; - private SearchControls searchControls; /** * Prepares and creates resources to be used by this KerberosOperationHandler. - * This method in this class would always throw <code>AmabriException</code> reporting + * This method in this class would always throw <code>KerberosOperationException</code> reporting * ldapUrl is not provided. - * Please use <code>open(KerberosCredential administratorCredentials, String defaultRealm, + * Use <code>open(KerberosCredential administratorCredentials, String defaultRealm, * String ldapUrl, String principalContainerDn)</code> for successful operation. * <p/> - * <p/> * It is expected that this KerberosOperationHandler will not be used before this call. * * @param administratorCredentials a KerberosCredential containing the administrative credentials @@ -71,7 +63,7 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler { */ @Override public void open(KerberosCredential administratorCredentials, String realm) - throws AmbariException { + throws KerberosOperationException { open(administratorCredentials, realm, null, null); } @@ -85,56 +77,42 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler { * @param realm a String declaring the default Kerberos realm (or domain) * @param ldapUrl ldapUrl of ldap back end where principals would be created * @param principalContainerDn DN of the container in ldap back end where principals would be created + * @throws KerberosKDCConnectionException if a connection to the KDC cannot be made + * @throws KerberosAdminAuthenticationException if the administrator credentials fail to authenticate + * @throws KerberosRealmException if the realm does not map to a KDC + * @throws KerberosOperationException if an unexpected error occurred */ @Override public void open(KerberosCredential administratorCredentials, String realm, String ldapUrl, String principalContainerDn) - throws AmbariException { + throws KerberosOperationException { + + if (isOpen()) { + close(); + } + if (administratorCredentials == null) { - throw new AmbariException("admininstratorCredential not provided"); + throw new KerberosAdminAuthenticationException("administrator Credential not provided"); } if (realm == null) { - throw new AmbariException("realm not provided"); + throw new KerberosRealmException("realm not provided"); } if (ldapUrl == null) { - throw new AmbariException("ldapUrl not provided"); + throw new KerberosKDCConnectionException("ldapUrl not provided"); } if (principalContainerDn == null) { - throw new AmbariException("principalContainerDn not provided"); + throw new KerberosLDAPContainerException("principalContainerDn not provided"); } - this.adminPrincipal = administratorCredentials.getPrincipal(); - this.adminPassword = administratorCredentials.getPassword(); - this.realm = realm; - this.ldapUrl = ldapUrl; - this.principalContainerDn = principalContainerDn; - createLdapContext(); - } - - private void createLdapContext() throws AmbariException { - LOG.info("Creating ldap context"); - - Properties env = new Properties(); - env.put(Context.INITIAL_CONTEXT_FACTORY, LDAP_CONTEXT_FACTORY_CLASS); - env.put(Context.PROVIDER_URL, ldapUrl); - env.put(Context.SECURITY_PRINCIPAL, adminPrincipal); - env.put(Context.SECURITY_CREDENTIALS, adminPassword); - env.put(Context.SECURITY_AUTHENTICATION, LDAP_ATUH_MECH_SIMPLE); - env.put(Context.REFERRAL, "follow"); - try { - ldapContext = new InitialLdapContext(env, null); - } catch (NamingException ne) { - LOG.error("Can not created ldapContext", ne); - throw new AmbariException("Can not created ldapContext", ne); - } + setAdministratorCredentials(administratorCredentials); + setDefaultRealm(realm); - searchControls = new SearchControls(); - searchControls.setSearchScope(ONELEVEL_SCOPE); + this.ldapUrl = ldapUrl; + this.principalContainerDn = principalContainerDn; + this.ldapContext = createLdapContext(); + this.searchControls = createSearchControls(); - Set<String> userSearchAttributes = new HashSet<String>(); - userSearchAttributes.add("cn"); - searchControls.setReturningAttributes(userSearchAttributes.toArray( - new String[userSearchAttributes.size()])); + setOpen(true); } /** @@ -143,37 +121,16 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler { * It is expected that this KerberosOperationHandler will not be used after this call. */ @Override - public void close() { - try { - if (ldapContext != null) { + public void close() throws KerberosOperationException { + if (ldapContext != null) { + try { ldapContext.close(); + } catch (NamingException e) { + throw new KerberosOperationException("Unexpected error", e); } - } catch (NamingException ne) { - // ignored, nothing we could do about it } - } - - /** - * Maps Keberos realm name to AD dc tree syntaz - * - * @param realm kerberos realm name - * @return mapped dc tree string - */ - private static String realmToDcs(String realm) { - if (realm == null || realm.isEmpty()) { - return realm; - } - String[] tokens = realm.split("\\."); - StringBuilder sb = new StringBuilder(); - int len = tokens.length; - if (len > 0) { - sb.append("dc=").append(tokens[0]); - } - for (int i = 1; i < len; i++) { - sb.append(",").append("dc=").append(tokens[i]); - } - return sb.toString(); + setOpen(false); } /** @@ -183,24 +140,27 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler { * * @param principal a String containing the principal to test * @return true if the principal exists; false otherwise - * @throws AmbariException + * @throws KerberosOperationException */ @Override - public boolean principalExists(String principal) throws AmbariException { + public boolean principalExists(String principal) throws KerberosOperationException { + if (!isOpen()) { + throw new KerberosOperationException("This operation handler has not be opened"); + } if (principal == null) { - throw new AmbariException("principal is null"); + throw new KerberosOperationException("principal is null"); } NamingEnumeration<SearchResult> searchResultEnum = null; try { searchResultEnum = ldapContext.search( - principalContainerDn, - "(cn=" + principal + ")", - searchControls); + principalContainerDn, + "(cn=" + principal + ")", + searchControls); if (searchResultEnum.hasMore()) { return true; } } catch (NamingException ne) { - throw new AmbariException("can not check if principal exists: " + principal, ne); + throw new KerberosOperationException("can not check if principal exists: " + principal, ne); } finally { try { if (searchResultEnum != null) { @@ -221,16 +181,19 @@ 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 an Integer declaring the generated key number - * @throws AmbariException + * @throws KerberosOperationException */ @Override public Integer createServicePrincipal(String principal, String password) - throws AmbariException { + throws KerberosOperationException { + if (!isOpen()) { + throw new KerberosOperationException("This operation handler has not be opened"); + } if (principal == null) { - throw new AmbariException("principal is null"); + throw new KerberosOperationException("principal is null"); } if (password == null) { - throw new AmbariException("principal password is null"); + throw new KerberosOperationException("principal password is null"); } Attributes attributes = new BasicAttributes(); @@ -243,7 +206,7 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler { attributes.put(cn); Attribute upn = new BasicAttribute("userPrincipalName"); - upn.add(principal + "@" + realm.toLowerCase()); + upn.add(String.format("%s@%s", principal, getDefaultRealm().toLowerCase())); attributes.put(upn); Attribute spn = new BasicAttribute("servicePrincipalName"); @@ -259,7 +222,7 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler { try { passwordAttr.add(quotedPasswordVal.getBytes("UTF-16LE")); } catch (UnsupportedEncodingException ue) { - throw new AmbariException("Can not encode password with UTF-16LE", ue); + throw new KerberosOperationException("Can not encode password with UTF-16LE", ue); } attributes.put(passwordAttr); @@ -267,7 +230,7 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler { Name name = new CompositeName().add("cn=" + principal + "," + principalContainerDn); ldapContext.createSubcontext(name, attributes); } catch (NamingException ne) { - throw new AmbariException("Can not created principal : " + principal, ne); + throw new KerberosOperationException("Can not create principal : " + principal, ne); } return 0; } @@ -280,35 +243,38 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler { * @param principal a String containing the principal to update * @param password a String containing the password to set * @return an Integer declaring the new key number - * @throws AmbariException + * @throws KerberosOperationException */ @Override - public Integer setPrincipalPassword(String principal, String password) throws AmbariException { + public Integer setPrincipalPassword(String principal, String password) throws KerberosOperationException { + if (!isOpen()) { + throw new KerberosOperationException("This operation handler has not be opened"); + } if (principal == null) { - throw new AmbariException("principal is null"); + throw new KerberosOperationException("principal is null"); } if (password == null) { - throw new AmbariException("principal password is null"); + throw new KerberosOperationException("principal password is null"); } - if (!principalExists(principal)) { - if (password == null) { - throw new AmbariException("principal not found : " + principal); + try { + if (!principalExists(principal)) { + throw new KerberosOperationException("principal not found : " + principal); } + } catch (KerberosOperationException e) { + e.printStackTrace(); } try { - createLdapContext(); - ModificationItem[] mods = new ModificationItem[1]; String quotedPasswordVal = "\"" + password + "\""; mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, - new BasicAttribute("UnicodePwd", quotedPasswordVal.getBytes("UTF-16LE"))); + new BasicAttribute("UnicodePwd", quotedPasswordVal.getBytes("UTF-16LE"))); ldapContext.modifyAttributes( - new CompositeName().add("cn=" + principal + "," + principalContainerDn), - mods); + new CompositeName().add("cn=" + principal + "," + principalContainerDn), + mods); } catch (NamingException ne) { - throw new AmbariException("Can not set password for principal : " + principal, ne); + throw new KerberosOperationException("Can not set password for principal : " + principal, ne); } catch (UnsupportedEncodingException ue) { - throw new AmbariException("Unsupported encoding UTF-16LE", ue); + throw new KerberosOperationException("Unsupported encoding UTF-16LE", ue); } return 0; } @@ -320,61 +286,109 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler { * * @param principal a String containing the principal to remove * @return true if the principal was successfully removed; otherwise false - * @throws AmbariException + * @throws KerberosOperationException */ @Override - public boolean removeServicePrincipal(String principal) throws AmbariException { + public boolean removeServicePrincipal(String principal) throws KerberosOperationException { + if (!isOpen()) { + throw new KerberosOperationException("This operation handler has not be opened"); + } if (principal == null) { - throw new AmbariException("principal is null"); + throw new KerberosOperationException("principal is null"); } - if (!principalExists(principal)) { - return false; + try { + if (!principalExists(principal)) { + return false; + } + } catch (KerberosOperationException e) { + e.printStackTrace(); } try { Name name = new CompositeName().add("cn=" + principal + "," + principalContainerDn); ldapContext.destroySubcontext(name); } catch (NamingException ne) { - throw new AmbariException("Can not remove principal: " + principal); + throw new KerberosOperationException("Can not remove principal: " + principal); } + return true; } /** - * Implementation of main method to illustrate the use of operations on this class + * Helper method to create the LDAP context needed to interact with the Active Directory. * - * @param args not used here - * @throws Throwable + * @return the relevant LdapContext + * @throws KerberosKDCConnectionException if a connection to the KDC cannot be made + * @throws KerberosAdminAuthenticationException if the administrator credentials fail to authenticate + * @throws KerberosRealmException if the realm does not map to a KDC + * @throws KerberosOperationException if an unexpected error occurred */ - public static void main(String[] args) throws Throwable { - - // SSL Certificate of AD should have been imported into truststore when that certificate - // is not issued by trusted authority. This is typical with self signed certificated in - // development environment - System.setProperty("javax.net.ssl.trustStore", - "/tmp/workspace/ambari/apache-ambari-rd/cacerts"); - - ADKerberosOperationHandler handler = new ADKerberosOperationHandler(); - - KerberosCredential kc = new KerberosCredential( - "[email protected]", "hadoop", null); // null keytab - - handler.open(kc, "KNOX.COM", - "ldaps://dillwin12.knox.com:636", "ou=service accounts,dc=knox,dc=com"); - - // does the princial already exist? - System.out.println("Principal exists: " + handler.principalExists("nn/c1508.ambari.apache.org")); - - //create principal - handler.createServicePrincipal("nn/c1508.ambari.apache.org", "welcome"); + protected LdapContext createLdapContext() throws KerberosOperationException { + KerberosCredential administratorCredentials = getAdministratorCredentials(); + + Properties properties = new Properties(); + properties.put(Context.INITIAL_CONTEXT_FACTORY, LDAP_CONTEXT_FACTORY_CLASS); + properties.put(Context.PROVIDER_URL, ldapUrl); + properties.put(Context.SECURITY_PRINCIPAL, administratorCredentials.getPrincipal()); + properties.put(Context.SECURITY_CREDENTIALS, administratorCredentials.getPassword()); + properties.put(Context.SECURITY_AUTHENTICATION, LDAP_ATUH_MECH_SIMPLE); + properties.put(Context.REFERRAL, "follow"); + properties.put("java.naming.ldap.factory.socket", TrustingSSLSocketFactory.class.getName()); - //update the password - handler.setPrincipalPassword("nn/c1508.ambari.apache.org", "welcome10"); - - // remove the principal - // handler.removeServicePrincipal("nn/c1508.ambari.apache.org"); + try { + return createInitialLdapContext(properties, null); + } catch (CommunicationException e) { + String message = String.format("Failed to communicate with the Active Directory at %s: %s", ldapUrl, e.getMessage()); + LOG.warn(message, e); + throw new KerberosKDCConnectionException(message, e); + } catch (AuthenticationException e) { + String message = String.format("Failed to authenticate with the Active Directory at %s: %s", ldapUrl, e.getMessage()); + LOG.warn(message, e); + throw new KerberosAdminAuthenticationException(message, e); + } catch (NamingException e) { + String error = e.getMessage(); + + if ((error != null) && !error.isEmpty()) { + String message = String.format("Failed to communicate with the Active Directory at %s: %s", ldapUrl, e.getMessage()); + LOG.warn(message, e); + + if (error.startsWith("Cannot parse url:")) { + throw new KerberosKDCConnectionException(message, e); + } else { + throw new KerberosOperationException(message, e); + } + } else { + throw new KerberosOperationException("Unexpected error condition", e); + } + } + } - handler.close(); + /** + * Helper method to create the LDAP context needed to interact with the Active Directory. + * <p/> + * This is mainly used to help with building mocks for test cases. + * + * @param properties environment used to create the initial DirContext. + * Null indicates an empty environment. + * @param controls connection request controls for the initial context. + * If null, no connection request controls are used. + * @return the relevant LdapContext + * @throws NamingException if a naming exception is encountered + */ + protected LdapContext createInitialLdapContext(Properties properties, Control[] controls) + throws NamingException { + return new InitialLdapContext(properties, controls); + } + /** + * Helper method to create the SearchControls instance + * + * @return the relevant SearchControls + */ + protected SearchControls createSearchControls() { + SearchControls searchControls = new SearchControls(); + searchControls.setSearchScope(ONE_LEVEL_SCOPE); + searchControls.setReturningAttributes(new String[]{"cn"}); + return searchControls; } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/efe79f01/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 be70ba1..6e53140 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 @@ -139,13 +139,19 @@ public class CreateKeytabFilesServerAction extends KerberosServerAction { 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 at %s", - evaluatedPrincipal, keytabFile.getAbsolutePath()); - LOG.error(message); + try { + 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 at %s", + evaluatedPrincipal, keytabFile.getAbsolutePath()); + LOG.error(message); + commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", "", message); + } + } catch (KerberosOperationException e) { + String message = String.format("Failed to create keytab file for %s - %s", evaluatedPrincipal, e.getMessage()); + LOG.error(message, e); commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", "", message); } } else { http://git-wip-us.apache.org/repos/asf/ambari/blob/efe79f01/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 a6392da..38ca320 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 @@ -95,35 +95,41 @@ public class CreatePrincipalsServerAction extends KerberosServerAction { if (password == null) { password = operationHandler.createSecurePassword(); - if (operationHandler.principalExists(evaluatedPrincipal)) { - // Create a new password since we need to know what it is. - // A new password/key would have been generated after exporting the keytab anyways. - LOG.warn("Principal already exists, setting new password - {}", evaluatedPrincipal); - - 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); - LOG.error(message); - commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", "", message); - } - } else { - LOG.debug("Creating new principal - {}", evaluatedPrincipal); - Integer keyNumber = operationHandler.createServicePrincipal(evaluatedPrincipal, password); - - if (keyNumber != null) { - principalPasswordMap.put(evaluatedPrincipal, password); - principalKeyNumberMap.put(evaluatedPrincipal, keyNumber); - LOG.debug("Successfully created new principal {}", evaluatedPrincipal); + try { + if (operationHandler.principalExists(evaluatedPrincipal)) { + // Create a new password since we need to know what it is. + // A new password/key would have been generated after exporting the keytab anyways. + LOG.warn("Principal already exists, setting new password - {}", evaluatedPrincipal); + + 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); + LOG.error(message); + commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", "", message); + } } else { - String message = String.format("Failed to create principal %s, unknown reason", evaluatedPrincipal); - LOG.error(message); - commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", "", message); + LOG.debug("Creating new principal - {}", evaluatedPrincipal); + Integer keyNumber = 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); + LOG.error(message); + commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", "", message); + } } + } catch (KerberosOperationException e) { + String message = String.format("Failed to create principal %s - %s", evaluatedPrincipal, e.getMessage()); + LOG.error(message, e); + commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", "", message); } } http://git-wip-us.apache.org/repos/asf/ambari/blob/efe79f01/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 ae2d4b2..5a1310d 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 @@ -18,7 +18,6 @@ package org.apache.ambari.server.serveraction.kerberos; -import org.apache.ambari.server.AmbariException; import org.apache.ambari.server.utils.ShellCommandUtil; import org.apache.commons.codec.binary.Base64; import org.apache.directory.server.kerberos.shared.crypto.encryption.KerberosKeyFactory; @@ -77,9 +76,9 @@ public abstract class KerberosOperationHandler { add(EncryptionType.AES256_CTS_HMAC_SHA1_96); }}); - private KerberosCredential administratorCredentials; - private String defaultRealm; - + private KerberosCredential administratorCredentials = null; + private String defaultRealm = null; + private boolean open = false; /** * Create a secure (random) password using a secure random number generator and a set of (reasonable) @@ -121,38 +120,36 @@ public abstract class KerberosOperationHandler { } /** - * Prepares and creates resources to be used by this KerberosOperationHandler - * <p/> - * It is expected that this KerberosOperationHandler will not be used before this call. - * - * @param administratorCredentials a KerberosCredential containing the administrative credentials - * for the relevant KDC - * @param defaultRealm a String declaring the default Kerberos realm (or domain) - */ - public abstract void open(KerberosCredential administratorCredentials, String defaultRealm) - throws AmbariException; - - /** - * Prepares and creates resources to be used by this KerberosOperationHandler. - * Implementation in this class is ignoring parameters ldapUrl and principalContainerDn and delegate to - * <code>open(KerberosCredential administratorCredentials, String defaultRealm)</code> - * Subclasses that want to use these parameters need to override this method. - * - * <p/> - * It is expected that this KerberosOperationHandler will not be used before this call. - * - * @param administratorCredentials a KerberosCredential containing the administrative credentials - * for the relevant KDC - * @param defaultRealm a String declaring the default Kerberos realm (or domain) - * @param ldapUrl ldapUrl of ldap back end where principals would be created - * @param principalContainerDn DN of the container in ldap back end where principals would be created - * - */ - public void open(KerberosCredential administratorCredentials, String defaultRealm, - String ldapUrl, String principalContainerDn) - throws AmbariException { - open(administratorCredentials, defaultRealm); - } + * Prepares and creates resources to be used by this KerberosOperationHandler + * <p/> + * It is expected that this KerberosOperationHandler will not be used before this call. + * + * @param administratorCredentials a KerberosCredential containing the administrative credentials + * for the relevant KDC + * @param defaultRealm a String declaring the default Kerberos realm (or domain) + */ + public abstract void open(KerberosCredential administratorCredentials, String defaultRealm) + throws KerberosOperationException; + + /** + * Prepares and creates resources to be used by this KerberosOperationHandler. + * Implementation in this class is ignoring parameters ldapUrl and principalContainerDn and delegate to + * <code>open(KerberosCredential administratorCredentials, String defaultRealm)</code> + * Subclasses that want to use these parameters need to override this method. + * <p/> + * It is expected that this KerberosOperationHandler will not be used before this call. + * + * @param administratorCredentials a KerberosCredential containing the administrative credentials + * for the relevant KDC + * @param defaultRealm a String declaring the default Kerberos realm (or domain) + * @param ldapUrl ldapUrl of ldap back end where principals would be created + * @param principalContainerDn DN of the container in ldap back end where principals would be created + */ + public void open(KerberosCredential administratorCredentials, String defaultRealm, + String ldapUrl, String principalContainerDn) + throws KerberosOperationException { + open(administratorCredentials, defaultRealm); + } /** * Closes and cleans up any resources used by this KerberosOperationHandler @@ -160,7 +157,7 @@ public abstract class KerberosOperationHandler { * It is expected that this KerberosOperationHandler will not be used after this call. */ public abstract void close() - throws AmbariException; + throws KerberosOperationException; /** * Test to see if the specified principal exists in a previously configured KDC @@ -169,10 +166,10 @@ public abstract class KerberosOperationHandler { * * @param principal a String containing the principal to test * @return true if the principal exists; false otherwise - * @throws AmbariException + * @throws KerberosOperationException */ public abstract boolean principalExists(String principal) - throws AmbariException; + throws KerberosOperationException; /** * Creates a new principal in a previously configured KDC @@ -182,10 +179,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 an Integer declaring the generated key number - * @throws AmbariException + * @throws KerberosOperationException */ public abstract Integer createServicePrincipal(String principal, String password) - throws AmbariException; + throws KerberosOperationException; /** * Updates the password for an existing principal in a previously configured KDC @@ -195,10 +192,10 @@ public abstract class KerberosOperationHandler { * @param principal a String containing the principal to update * @param password a String containing the password to set * @return an Integer declaring the new key number - * @throws AmbariException + * @throws KerberosOperationException */ public abstract Integer setPrincipalPassword(String principal, String password) - throws AmbariException; + throws KerberosOperationException; /** * Removes an existing principal in a previously configured KDC @@ -207,10 +204,27 @@ public abstract class KerberosOperationHandler { * * @param principal a String containing the principal to remove * @return true if the principal was successfully removed; otherwise false - * @throws AmbariException + * @throws KerberosOperationException */ public abstract boolean removeServicePrincipal(String principal) - throws AmbariException; + throws KerberosOperationException; + + /** + * Tests to ensure the connection information and credentials allow for administrative + * connectivity to the KDC + * + * @return true of successful; otherwise false + * @throws KerberosOperationException if a failure occurs while testing the + * administrator credentials + */ + public boolean testAdministratorCredentials() throws KerberosOperationException { + KerberosCredential credentials = getAdministratorCredentials(); + if (credentials == null) { + throw new KerberosOperationException("Missing KDC administrator credentials"); + } else { + return principalExists(credentials.getPrincipal()); + } + } /** * Create or append to a keytab file using the specified principal and password. @@ -219,18 +233,18 @@ public abstract class KerberosOperationHandler { * @param password a String containing the password to use when creating the principal * @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 + * @throws KerberosOperationException */ public boolean createKeytabFile(String principal, String password, Integer keyNumber, File keytabFile) - throws AmbariException { + throws KerberosOperationException { boolean success = false; if ((principal == null) || principal.isEmpty()) { - throw new AmbariException("Failed to create keytab file, missing principal"); + throw new KerberosOperationException("Failed to create keytab file, missing principal"); } else if (password == null) { - throw new AmbariException(String.format("Failed to create keytab file for %s, missing password", principal)); + throw new KerberosOperationException(String.format("Failed to create keytab file for %s, missing password", principal)); } else if (keytabFile == null) { - throw new AmbariException(String.format("Failed to create keytab file for %s, missing file path", principal)); + throw new KerberosOperationException(String.format("Failed to create keytab file for %s, missing file path", principal)); } else { Keytab keytab; Set<EncryptionType> ciphers = new HashSet<EncryptionType>(DEFAULT_CIPHERS); @@ -294,7 +308,7 @@ public abstract class KerberosOperationHandler { keytabFile.deleteOnExit(); } - throw new AmbariException(message, e); + throw new KerberosOperationException(message, e); } } } @@ -320,6 +334,24 @@ public abstract class KerberosOperationHandler { } /** + * Test this KerberosOperationHandler to see whether is was previously open or not + * + * @return a boolean value indicating whether this KerberosOperationHandler was open (true) or not (false) + */ + public boolean isOpen() { + return open; + } + + /** + * Sets whether this KerberosOperationHandler is open or not. + * + * @param open a boolean value indicating whether this KerberosOperationHandler was open (true) or not (false) + */ + public void setOpen(boolean open) { + this.open = open; + } + + /** * Given base64-encoded keytab data, decode the String to binary data and write it to a (temporary) * file. * <p/> @@ -328,10 +360,10 @@ public abstract class KerberosOperationHandler { * * @param keytabData a String containing base64-encoded keytab data * @return a File pointing to the decoded keytab file or null if not successful - * @throws AmbariException + * @throws KerberosOperationException */ protected File createKeytabFile(String keytabData) - throws AmbariException { + throws KerberosOperationException { boolean success = false; File tempFile = null; @@ -354,12 +386,12 @@ public abstract class KerberosOperationHandler { String message = String.format("Failed to write to temporary keytab file %s: %s", tempFile.getAbsolutePath(), e.getLocalizedMessage()); LOG.error(message, e); - throw new AmbariException(message, e); + throw new KerberosOperationException(message, e); } catch (IOException e) { String message = String.format("Failed to write to temporary keytab file %s: %s", tempFile.getAbsolutePath(), e.getLocalizedMessage()); LOG.error(message, e); - throw new AmbariException(message, e); + throw new KerberosOperationException(message, e); } finally { if (fos != null) { try { @@ -390,10 +422,10 @@ public abstract class KerberosOperationHandler { * * @param command an array of String value representing the command and its arguments * @return a ShellCommandUtil.Result declaring the result of the operation - * @throws AmbariException + * @throws KerberosOperationException */ protected ShellCommandUtil.Result executeCommand(String[] command) - throws AmbariException { + throws KerberosOperationException { if ((command == null) || (command.length == 0)) { return null; @@ -403,11 +435,11 @@ public abstract class KerberosOperationHandler { } catch (IOException e) { String message = String.format("Failed to execute the command: %s", e.getLocalizedMessage()); LOG.error(message, e); - throw new AmbariException(message, e); + throw new KerberosOperationException(message, e); } catch (InterruptedException e) { String message = String.format("Failed to wait for the command to complete: %s", e.getLocalizedMessage()); LOG.error(message, e); - throw new AmbariException(message, e); + throw new KerberosOperationException(message, e); } } } http://git-wip-us.apache.org/repos/asf/ambari/blob/efe79f01/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandlerFactory.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandlerFactory.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandlerFactory.java index 30e3c35..a717b90 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandlerFactory.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandlerFactory.java @@ -18,9 +18,12 @@ package org.apache.ambari.server.serveraction.kerberos; +import com.google.inject.Singleton; + /** * KerberosOperationHandlerFactory gets relevant KerberosOperationHandlers given a KDCType. */ +@Singleton public class KerberosOperationHandlerFactory { /** @@ -32,7 +35,7 @@ public class KerberosOperationHandlerFactory { * @param kdcType the relevant KDCType * @return a KerberosOperationHandler */ - public static KerberosOperationHandler getKerberosOperationHandler(KDCType kdcType) { + public KerberosOperationHandler getKerberosOperationHandler(KDCType kdcType) { KerberosOperationHandler handler = null; // If not specified, use KDCType.MIT_KDC as a default @@ -48,7 +51,6 @@ public class KerberosOperationHandlerFactory { case ACTIVE_DIRECTORY: handler = new ADKerberosOperationHandler(); break; - } return handler; http://git-wip-us.apache.org/repos/asf/ambari/blob/efe79f01/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 a99628c..2f91826 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 @@ -97,6 +97,15 @@ public abstract class KerberosServerAction extends AbstractServerAction { private Clusters clusters = null; /** + * The KerberosOperationHandlerFactory to use to obtain KerberosOperationHandler instances + * <p/> + * This is needed to help with test cases to mock a KerberosOperationHandler + */ + @Inject + private KerberosOperationHandlerFactory kerberosOperationHandlerFactory; + + + /** * Given a (command parameter) Map and a property name, attempts to safely retrieve the requested * data. * @@ -301,7 +310,7 @@ public abstract class KerberosServerAction extends AbstractServerAction { throw new AmbariException(message); } - KerberosOperationHandler handler = KerberosOperationHandlerFactory.getKerberosOperationHandler(kdcType); + KerberosOperationHandler handler = kerberosOperationHandlerFactory.getKerberosOperationHandler(kdcType); if (handler == null) { String message = String.format("Failed to process the identities, a KDC operation handler was not found for the KDC type of : %s", kdcType.toString()); @@ -325,9 +334,7 @@ public abstract class KerberosServerAction extends AbstractServerAction { break; } } - } catch (AmbariException e) { - // Catch this separately from IOException since the reason it was thrown was not the same - // Note: AmbariException is an IOException, so there may be some confusion + } catch (KerberosOperationException e) { throw new AmbariException(e.getMessage(), e); } catch (IOException e) { String message = String.format("Failed to process the identities, cannot read the index file: %s", @@ -349,7 +356,7 @@ public abstract class KerberosServerAction extends AbstractServerAction { // exception since there is little we can or care to do about it now. try { handler.close(); - } catch (AmbariException e) { + } catch (KerberosOperationException e) { // Ignore this... } } http://git-wip-us.apache.org/repos/asf/ambari/blob/efe79f01/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 04d43a5..a70b412 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 @@ -18,7 +18,6 @@ package org.apache.ambari.server.serveraction.kerberos; -import org.apache.ambari.server.AmbariException; import org.apache.ambari.server.utils.ShellCommandUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,14 +49,16 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler { @Override - public void open(KerberosCredential administratorCredentials, String defaultRealm) throws AmbariException { + public void open(KerberosCredential administratorCredentials, String defaultRealm) throws KerberosOperationException { setAdministratorCredentials(administratorCredentials); setDefaultRealm(defaultRealm); + setOpen(true); } @Override - public void close() throws AmbariException { + public void close() throws KerberosOperationException { // There is nothing to do here. + setOpen(false); } /** @@ -68,11 +69,18 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler { * * @param principal a String containing the principal to test * @return true if the principal exists; false otherwise - * @throws AmbariException + * @throws KerberosKDCConnectionException if a connection to the KDC cannot be made + * @throws KerberosAdminAuthenticationException if the administrator credentials fail to authenticate + * @throws KerberosRealmException if the realm does not map to a KDC + * @throws KerberosOperationException if an unexpected error occurred */ @Override public boolean principalExists(String principal) - throws AmbariException { + throws KerberosOperationException { + + if (!isOpen()) { + throw new KerberosOperationException("This operation handler has not be opened"); + } if (principal == null) { return false; @@ -80,30 +88,18 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler { // Create the KAdmin query to execute: String query = String.format("get_principal %s", principal); + ShellCommandUtil.Result result; try { - ShellCommandUtil.Result result = invokeKAdmin(query); - - if (result != null) { - if (result.isSuccessful()) { - String stdOut = result.getStdout(); - - // If there is data from STDOUT, see if the following string exists: - // Principal: <principal> - return (stdOut != null) && stdOut.contains(String.format("Principal: %s", principal)); - } else { - LOG.warn("Failed to query for principal {}:\n\tExitCode: {}\n\tSTDOUT: {}\n\tSTDERR: {}", - principal, result.getExitCode(), result.getStdout(), result.getStderr()); - throw new AmbariException(String.format("Failed to query for principal %s", principal)); - } - } else { - String message = String.format("Failed to query for principal %s - Unknown reason", principal); - LOG.warn(message); - throw new AmbariException(message); - } - } catch (AmbariException e) { + result = invokeKAdmin(query); + } catch (KerberosOperationException e) { LOG.error(String.format("Failed to query for principal %s", principal), e); throw e; } + + // If there is data from STDOUT, see if the following string exists: + // Principal: <principal> + String stdOut = result.getStdout(); + return (stdOut != null) && stdOut.contains(String.format("Principal: %s", principal)); } } @@ -117,60 +113,41 @@ 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 an Integer declaring the generated key number - * @throws AmbariException + * @throws KerberosKDCConnectionException if a connection to the KDC cannot be made + * @throws KerberosAdminAuthenticationException if the administrator credentials fail to authenticate + * @throws KerberosRealmException if the realm does not map to a KDC + * @throws KerberosOperationException if an unexpected error occurred */ @Override public Integer createServicePrincipal(String principal, String password) - throws AmbariException { + throws KerberosOperationException { + + if (!isOpen()) { + throw new KerberosOperationException("This operation handler has not be opened"); + } if ((principal == null) || principal.isEmpty()) { - throw new AmbariException("Failed to create new principal - no principal specified"); + throw new KerberosOperationException("Failed to create new principal - no principal specified"); + } else if ((password == null) || password.isEmpty()) { + throw new KerberosOperationException("Failed to create new principal - no password specified"); } else { // Create the kdamin query: add_principal <-randkey|-pw <password>> <principal> - StringBuilder queryBuilder = new StringBuilder(); - - queryBuilder.append("add_principal"); - - // If a password was not supplied, have the KDC generate a random key, else use the supplied - // password - if ((password == null) || password.isEmpty()) { - queryBuilder.append(" -randkey"); - } else { - queryBuilder.append(" -pw "); - queryBuilder.append(password); - } - - queryBuilder.append(" "); - queryBuilder.append(principal); - + ShellCommandUtil.Result result; try { - ShellCommandUtil.Result result = invokeKAdmin(queryBuilder.toString()); - - if (result != null) { - if (result.isSuccessful()) { - String stdOut = result.getStdout(); - - // If there is data from STDOUT, see if the following string exists: - // Principal "<principal>" created - 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()); - throw new AmbariException(String.format("Failed to create service principal for %s", principal)); - } - } else { - String message = String.format("Failed to create service principal for %s - Unknown reason", principal); - LOG.warn(message); - throw new AmbariException(message); - } - } catch (AmbariException e) { + result = invokeKAdmin(String.format("add_principal -pw %s %s", password, principal)); + } catch (KerberosOperationException e) { LOG.error(String.format("Failed to create new principal for %s", principal), e); throw e; } + + // If there is data from STDOUT, see if the following string exists: + // Principal "<principal>" created + String stdOut = result.getStdout(); + if ((stdOut != null) && stdOut.contains(String.format("Principal \"%s\" created", principal))) { + return getKeyNumber(principal); + } else { + throw new KerberosOperationException(String.format("Failed to create service principal for %s", principal)); + } } } @@ -183,50 +160,31 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler { * @param principal a String containing the principal to update * @param password a String containing the password to set * @return an Integer declaring the new key number - * @throws AmbariException + * @throws KerberosKDCConnectionException if a connection to the KDC cannot be made + * @throws KerberosAdminAuthenticationException if the administrator credentials fail to authenticate + * @throws KerberosRealmException if the realm does not map to a KDC + * @throws KerberosOperationException if an unexpected error occurred */ @Override - public Integer setPrincipalPassword(String principal, String password) throws AmbariException { + public Integer setPrincipalPassword(String principal, String password) throws KerberosOperationException { + if (!isOpen()) { + throw new KerberosOperationException("This operation handler has not be opened"); + } + if ((principal == null) || principal.isEmpty()) { - throw new AmbariException("Failed to set password - no principal specified"); + throw new KerberosOperationException("Failed to set password - no principal specified"); + } else if ((password == null) || password.isEmpty()) { + throw new KerberosOperationException("Failed to set password - no password specified"); } else { // Create the kdamin query: change_password <-randkey|-pw <password>> <principal> - StringBuilder queryBuilder = new StringBuilder(); - - queryBuilder.append("change_password"); - - // If a password was not supplied, have the KDC generate a random key, else use the supplied - // password - if ((password == null) || password.isEmpty()) { - queryBuilder.append(" -randkey"); - } else { - queryBuilder.append(" -pw "); - queryBuilder.append(password); - } - - queryBuilder.append(" "); - queryBuilder.append(principal); - try { - ShellCommandUtil.Result result = invokeKAdmin(queryBuilder.toString()); - - if (result != null) { - if (result.isSuccessful()) { - return getKeyNumber(principal); - } else { - LOG.warn("Failed to set password for {}:\n\tExitCode: {}\n\tSTDOUT: {}\n\tSTDERR: {}", - principal, result.getExitCode(), result.getStdout(), result.getStderr()); - throw new AmbariException(String.format("Failed to update password for %s", principal)); - } - } else { - String message = String.format("Failed to set password for %s - Unknown reason", principal); - LOG.warn(message); - throw new AmbariException(message); - } - } catch (AmbariException e) { + invokeKAdmin(String.format("change_password -pw %s %s", password, principal)); + } catch (KerberosOperationException e) { LOG.error(String.format("Failed to set password for %s", principal), e); throw e; } + + return getKeyNumber(principal); } } @@ -237,37 +195,33 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler { * * @param principal a String containing the principal to remove * @return true if the principal was successfully removed; otherwise false - * @throws AmbariException + * @throws KerberosKDCConnectionException if a connection to the KDC cannot be made + * @throws KerberosAdminAuthenticationException if the administrator credentials fail to authenticate + * @throws KerberosRealmException if the realm does not map to a KDC + * @throws KerberosOperationException if an unexpected error occurred */ @Override - public boolean removeServicePrincipal(String principal) throws AmbariException { + public boolean removeServicePrincipal(String principal) throws KerberosOperationException { + if (!isOpen()) { + throw new KerberosOperationException("This operation handler has not be opened"); + } + if ((principal == null) || principal.isEmpty()) { - throw new AmbariException("Failed to remove new principal - no principal specified"); + throw new KerberosOperationException("Failed to remove new principal - no principal specified"); } else { + ShellCommandUtil.Result result; + try { - ShellCommandUtil.Result result = invokeKAdmin(String.format("delete_principal -force %s", principal)); - - if (result != null) { - if (result.isSuccessful()) { - String stdOut = result.getStdout(); - - // If there is data from STDOUT, see if the following string exists: - // Principal "<principal>" created - return (stdOut != null) && !stdOut.contains("Principal does not exist"); - } else { - LOG.warn("Failed to remove service principal for {}:\n\tExitCode: {}\n\tSTDOUT: {}\n\tSTDERR: {}", - principal, result.getExitCode(), result.getStdout(), result.getStderr()); - throw new AmbariException(String.format("Failed to remove service principal for %s", principal)); - } - } else { - String message = String.format("Failed to remove service principal for %s - Unknown reason", principal); - LOG.warn(message); - throw new AmbariException(message); - } - } catch (AmbariException e) { + result = invokeKAdmin(String.format("delete_principal -force %s", principal)); + } catch (KerberosOperationException e) { LOG.error(String.format("Failed to remove new principal for %s", principal), e); throw e; } + + // If there is data from STDOUT, see if the following string exists: + // Principal "<principal>" created + String stdOut = result.getStdout(); + return (stdOut != null) && !stdOut.contains("Principal does not exist"); } } @@ -276,62 +230,59 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler { * * @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 + * @throws KerberosKDCConnectionException if a connection to the KDC cannot be made + * @throws KerberosAdminAuthenticationException if the administrator credentials fail to authenticate + * @throws KerberosRealmException if the realm does not map to a KDC + * @throws KerberosOperationException if an unexpected error occurred */ - private Integer getKeyNumber(String principal) throws AmbariException { + private Integer getKeyNumber(String principal) throws KerberosOperationException { + if (!isOpen()) { + throw new KerberosOperationException("This operation handler has not be opened"); + } + if ((principal == null) || principal.isEmpty()) { - throw new AmbariException("Failed to get key number for principal - no principal specified"); + throw new KerberosOperationException("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); + ShellCommandUtil.Result result; 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) { + result = invokeKAdmin(query); + } catch (KerberosOperationException e) { LOG.error(String.format("Failed to get key number for %s", principal), e); throw e; } + + String stdOut = result.getStdout(); + if (stdOut == null) { + String message = String.format("Failed to get key number for %s:\n\tExitCode: %s\n\tSTDOUT: NULL\n\tSTDERR: %s", + principal, result.getExitCode(), result.getStderr()); + LOG.warn(message); + throw new KerberosOperationException(message); + } + + 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) { + String message = String.format("Failed to get key number for %s - invalid key number value (%s):\n\tExitCode: %s\n\tSTDOUT: NULL\n\tSTDERR: %s", + principal, keyNumber, result.getExitCode(), result.getStderr()); + LOG.warn(message); + throw new KerberosOperationException(message); + } + } else { + String message = String.format("Failed to get key number for %s - unexpected STDOUT data:\n\tExitCode: %s\n\tSTDOUT: NULL\n\tSTDERR: %s", + principal, result.getExitCode(), result.getStderr()); + LOG.warn(message); + throw new KerberosOperationException(message); + } } } @@ -340,77 +291,104 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler { * * @param query a String containing the query to send to the kdamin command * @return a ShellCommandUtil.Result containing the result of the operation - * @throws AmbariException + * @throws KerberosKDCConnectionException if a connection to the KDC cannot be made + * @throws KerberosAdminAuthenticationException if the administrator credentials fail to authenticate + * @throws KerberosRealmException if the realm does not map to a KDC + * @throws KerberosOperationException if an unexpected error occurred */ private ShellCommandUtil.Result invokeKAdmin(String query) - throws AmbariException { + throws KerberosOperationException { ShellCommandUtil.Result result = null; - if ((query != null) && !query.isEmpty()) { - KerberosCredential administratorCredentials = getAdministratorCredentials(); - String defaultRealm = getDefaultRealm(); + if ((query == null) || query.isEmpty()) { + throw new KerberosOperationException("Missing kadmin query"); + } + KerberosCredential administratorCredentials = getAdministratorCredentials(); + String defaultRealm = getDefaultRealm(); - List<String> command = new ArrayList<String>(); - File tempKeytabFile = null; + List<String> command = new ArrayList<String>(); + File tempKeytabFile = null; - try { - String adminPrincipal = (administratorCredentials == null) - ? null - : administratorCredentials.getPrincipal(); + try { + String adminPrincipal = (administratorCredentials == null) + ? null + : administratorCredentials.getPrincipal(); - if ((adminPrincipal == null) || adminPrincipal.isEmpty()) { - // Set the kdamin interface to be kadmin.local - command.add("kadmin.local"); - } else { - String adminPassword = administratorCredentials.getPassword(); - String adminKeyTab = administratorCredentials.getKeytab(); - - // Set the kdamin interface to be kadmin - command.add("kadmin"); - - // Add the administrative principal - command.add("-p"); - command.add(adminPrincipal); - - if ((adminKeyTab != null) && !adminKeyTab.isEmpty()) { - tempKeytabFile = createKeytabFile(adminKeyTab); - - if (tempKeytabFile != null) { - // Add keytab file administrative principal - command.add("-k"); - command.add("-t"); - command.add(tempKeytabFile.getAbsolutePath()); - } - } else if (adminPassword != null) { - // Add password for administrative principal - command.add("-w"); - command.add(adminPassword); + if ((adminPrincipal == null) || adminPrincipal.isEmpty()) { + // Set the kdamin interface to be kadmin.local + command.add("kadmin.local"); + } else { + String adminPassword = administratorCredentials.getPassword(); + String adminKeyTab = administratorCredentials.getKeytab(); + + // Set the kdamin interface to be kadmin + command.add("kadmin"); + + // Add the administrative principal + command.add("-p"); + command.add(adminPrincipal); + + if ((adminKeyTab != null) && !adminKeyTab.isEmpty()) { + tempKeytabFile = createKeytabFile(adminKeyTab); + + if (tempKeytabFile != null) { + // Add keytab file administrative principal + command.add("-k"); + command.add("-t"); + command.add(tempKeytabFile.getAbsolutePath()); } + } else if (adminPassword != null) { + // Add password for administrative principal + command.add("-w"); + command.add(adminPassword); } + } - if ((defaultRealm != null) && !defaultRealm.isEmpty()) { - // Add default realm clause - command.add("-r"); - command.add(defaultRealm); - } + if ((defaultRealm != null) && !defaultRealm.isEmpty()) { + // Add default realm clause + command.add("-r"); + command.add(defaultRealm); + } - // Add kadmin query - command.add("-q"); - command.add(query.replace("\"", "\\\"")); + // Add kadmin query + command.add("-q"); + command.add(query.replace("\"", "\\\"")); - result = executeCommand(command.toArray(new String[command.size()])); - } finally { - // If a temporary keytab file was created, clean it up. - if (tempKeytabFile != null) { - if (!tempKeytabFile.delete()) { - tempKeytabFile.deleteOnExit(); - } + result = executeCommand(command.toArray(new String[command.size()])); + + if (!result.isSuccessful()) { + String message = String.format("Failed to execute kadmin:\n\tExitCode: %s\n\tSTDOUT: %s\n\tSTDERR: %s", + result.getExitCode(), result.getStdout(), result.getStderr()); + LOG.warn(message); + + // Test STDERR to see of any "expected" error conditions were encountered... + String stdErr = result.getStderr(); + // Did admin credentials fail? + if (stdErr.contains("Client not found in Kerberos database")) { + throw new KerberosAdminAuthenticationException(stdErr); + } else if (stdErr.contains("Incorrect password while initializing")) { + throw new KerberosAdminAuthenticationException(stdErr); + } + // Did we fail to connect to the KDC? + else if (stdErr.contains("Cannot contact any KDC")) { + throw new KerberosKDCConnectionException(stdErr); + } + // Was the realm invalid? + else if (stdErr.contains("Missing parameters in krb5.conf required for kadmin client")) { + throw new KerberosRealmException(stdErr); + } else { + throw new KerberosOperationException("Unexpected error condition executing the kadmin command"); + } + } + } finally { + // If a temporary keytab file was created, clean it up. + if (tempKeytabFile != null) { + if (!tempKeytabFile.delete()) { + tempKeytabFile.deleteOnExit(); } } } return result; } - - } http://git-wip-us.apache.org/repos/asf/ambari/blob/efe79f01/ambari-server/src/test/java/org/apache/ambari/server/controller/KerberosHelperTest.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/KerberosHelperTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/KerberosHelperTest.java index 8f39f21..dea5d61 100644 --- a/ambari-server/src/test/java/org/apache/ambari/server/controller/KerberosHelperTest.java +++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/KerberosHelperTest.java @@ -21,6 +21,7 @@ package org.apache.ambari.server.controller; import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Injector; +import junit.framework.Assert; import org.apache.ambari.server.AmbariException; import org.apache.ambari.server.actionmanager.ActionManager; import org.apache.ambari.server.actionmanager.HostRoleCommand; @@ -32,7 +33,12 @@ import org.apache.ambari.server.controller.internal.RequestStageContainer; import org.apache.ambari.server.metadata.RoleCommandOrder; import org.apache.ambari.server.orm.DBAccessor; import org.apache.ambari.server.security.SecurityHelper; +import org.apache.ambari.server.serveraction.kerberos.KDCType; import org.apache.ambari.server.serveraction.kerberos.KerberosCredential; +import org.apache.ambari.server.serveraction.kerberos.KerberosOperationException; +import org.apache.ambari.server.serveraction.kerberos.KerberosOperationHandler; +import org.apache.ambari.server.serveraction.kerberos.KerberosOperationHandlerTest; +import org.apache.ambari.server.serveraction.kerberos.KerberosOperationHandlerFactory; import org.apache.ambari.server.state.Cluster; import org.apache.ambari.server.state.Clusters; import org.apache.ambari.server.state.Config; @@ -77,6 +83,43 @@ public class KerberosHelperTest extends EasyMockSupport { @Before public void setUp() throws Exception { + final KerberosOperationHandlerFactory kerberosOperationHandlerFactory = createNiceMock(KerberosOperationHandlerFactory.class); + + expect(kerberosOperationHandlerFactory.getKerberosOperationHandler(KDCType.MIT_KDC)) + .andReturn(new KerberosOperationHandler() { + @Override + public void open(KerberosCredential administratorCredentials, String defaultRealm) throws KerberosOperationException { + setAdministratorCredentials(administratorCredentials); + setDefaultRealm(defaultRealm); + } + + @Override + public void close() throws KerberosOperationException { + + } + + @Override + public boolean principalExists(String principal) throws KerberosOperationException { + return "principal".equals(principal); + } + + @Override + public Integer createServicePrincipal(String principal, String password) throws KerberosOperationException { + return null; + } + + @Override + public Integer setPrincipalPassword(String principal, String password) throws KerberosOperationException { + return null; + } + + @Override + public boolean removeServicePrincipal(String principal) throws KerberosOperationException { + return false; + } + }) + .anyTimes(); + injector = Guice.createInjector(new AbstractModule() { @Override @@ -96,6 +139,7 @@ public class KerberosHelperTest extends EasyMockSupport { bind(StageFactory.class).toInstance(createNiceMock(StageFactory.class)); bind(Clusters.class).toInstance(createNiceMock(ClustersImpl.class)); bind(ConfigHelper.class).toInstance(createNiceMock(ConfigHelper.class)); + bind(KerberosOperationHandlerFactory.class).toInstance(kerberosOperationHandlerFactory); } }); } @@ -141,6 +185,32 @@ public class KerberosHelperTest extends EasyMockSupport { @Test public void testEnableKerberos() throws Exception { + testEnableKerberos(new KerberosCredential("principal", "password", "keytab")); + } + + @Test(expected = IllegalArgumentException.class) + public void testEnableKerberosMissingCredentials() throws Exception { + try { + testEnableKerberos(null); + } + catch (IllegalArgumentException e) { + Assert.assertTrue(e.getMessage().startsWith("Missing KDC administrator credentials")); + throw e; + } + } + + @Test(expected = IllegalArgumentException.class) + public void testEnableKerberosInvalidCredentials() throws Exception { + try { + testEnableKerberos(new KerberosCredential("invalid_principal", "password", "keytab")); + } + catch (IllegalArgumentException e) { + Assert.assertTrue(e.getMessage().startsWith("Invalid KDC administrator credentials")); + throw e; + } + } + + private void testEnableKerberos(final KerberosCredential kerberosCredential) throws Exception { KerberosHelper kerberosHelper = injector.getInstance(KerberosHelper.class); final ServiceComponentHost sch1 = createNiceMock(ServiceComponentHost.class); @@ -214,9 +284,11 @@ public class KerberosHelperTest extends EasyMockSupport { .andReturn(new StackId("HDP", "2.2")) .anyTimes(); expect(cluster.getSessionAttributes()).andReturn(new HashMap<String, Object>(){{ - put("kerberos_admin/" + KerberosCredential.KEY_NAME_PRINCIPAL, "principal"); - put("kerberos_admin/" + KerberosCredential.KEY_NAME_PASSWORD, "password"); - put("kerberos_admin/" + KerberosCredential.KEY_NAME_KEYTAB, "keytab"); + if(kerberosCredential != null) { + put("kerberos_admin/" + KerberosCredential.KEY_NAME_PRINCIPAL, kerberosCredential.getPrincipal()); + put("kerberos_admin/" + KerberosCredential.KEY_NAME_PASSWORD, kerberosCredential.getPassword()); + put("kerberos_admin/" + KerberosCredential.KEY_NAME_KEYTAB, kerberosCredential.getKeytab()); + } }}).anyTimes(); final Clusters clusters = injector.getInstance(Clusters.class);
