You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by jo...@apache.org on 2017/06/05 20:00:50 UTC

[11/17] ambari git commit: AMBARI-20938. LDAPS connections to an Active Directory when enabling Kerberos should validate the server's SSL certificate (rlevas)

AMBARI-20938. LDAPS connections to an Active Directory when enabling Kerberos should validate the server's SSL certificate (rlevas)


Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/c3c06ea9
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/c3c06ea9
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/c3c06ea9

Branch: refs/heads/branch-feature-AMBARI-12556
Commit: c3c06ea9889e0a462d67e8a765b41d0a4aead9b2
Parents: 9acb7a6
Author: Robert Levas <rl...@hortonworks.com>
Authored: Fri Jun 2 15:43:43 2017 -0400
Committer: Robert Levas <rl...@hortonworks.com>
Committed: Fri Jun 2 15:43:43 2017 -0400

----------------------------------------------------------------------
 ambari-server/conf/unix/ambari.properties       |   4 +
 ambari-server/docs/configuration/index.md       |   5 +-
 .../server/configuration/Configuration.java     |  20 +++-
 .../server/controller/KerberosHelperImpl.java   |   6 +
 .../security/InternalSSLSocketFactory.java      | 112 +++++++++++++++++++
 .../InternalSSLSocketFactoryNonTrusting.java    |  49 ++++++++
 .../InternalSSLSocketFactoryTrusting.java       |  48 ++++++++
 .../kerberos/ADKerberosOperationHandler.java    |  34 ++++--
 .../KerberosKDCSSLConnectionException.java      |  45 ++++++++
 .../kerberos/TrustingSSLSocketFactory.java      | 101 -----------------
 .../ADKerberosOperationHandlerTest.java         |  96 +++++++++++++++-
 11 files changed, 403 insertions(+), 117 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/c3c06ea9/ambari-server/conf/unix/ambari.properties
----------------------------------------------------------------------
diff --git a/ambari-server/conf/unix/ambari.properties b/ambari-server/conf/unix/ambari.properties
index b8b645d..212591f 100644
--- a/ambari-server/conf/unix/ambari.properties
+++ b/ambari-server/conf/unix/ambari.properties
@@ -80,6 +80,10 @@ server.execution.scheduler.misfire.toleration.minutes=480
 # Kerberos settings
 kerberos.keytab.cache.dir = $ROOT/var/lib/ambari-server/data/cache
 
+# Validate trust when communicating with the KDC (via SSL) when performing Kerberos-related
+# operations, if applicable
+kerberos.operation.verify.kdc.trust = true
+
 # Default timeout in seconds before task is killed
 agent.task.timeout=900
 # Default timeout in seconds before package installation task is killed

http://git-wip-us.apache.org/repos/asf/ambari/blob/c3c06ea9/ambari-server/docs/configuration/index.md
----------------------------------------------------------------------
diff --git a/ambari-server/docs/configuration/index.md b/ambari-server/docs/configuration/index.md
index ff9ce54..2394264 100644
--- a/ambari-server/docs/configuration/index.md
+++ b/ambari-server/docs/configuration/index.md
@@ -148,8 +148,9 @@ The following are the properties which can be used to configure Ambari.
 | kdcserver.connection.check.timeout | The timeout, in milliseconds, to wait when communicating with a Kerberos Key Distribution Center. |`10000` | 
 | kerberos.check.jaas.configuration | Determines whether Kerberos-enabled Ambari deployments should use JAAS to validate login credentials. |`false` | 
 | kerberos.keytab.cache.dir | The location on the Ambari Server where Kerberos keytabs are cached. |`/var/lib/ambari-server/data/cache` | 
-| kerberos.operation.retries | The number of times failed kerberos operations should be retried to execute. |`3` | 
-| kerberos.operation.retry.timeout | The time to wait (in seconds) between failed kerberos operations retries. |`10` | 
+| kerberos.operation.retries | The number of times failed Kerberos operations should be retried to execute. |`3` | 
+| kerberos.operation.retry.timeout | The time to wait (in seconds) between failed Kerberos operations retries. |`10` | 
+| kerberos.operation.verify.kdc.trust | Validate the trust of the SSL certificate provided by the KDC when performing Kerberos operations over SSL. |`true` | 
 | ldap.sync.username.collision.behavior | Determines how to handle username collision while updating from LDAP.<br/><br/>The following are examples of valid values:<ul><li>`skip`<li>`convert`</ul> |`convert` | 
 | log4j.monitor.delay | Indicates the delay, in milliseconds, for the log4j monitor to check for changes |`300000` | 
 | logsearch.metadata.cache.expire.timeout | The time, in hours, that the Ambari Server will hold Log File metadata in its internal cache before making a request to the LogSearch Portal to get the latest metadata. |`24` | 

http://git-wip-us.apache.org/repos/asf/ambari/blob/c3c06ea9/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java b/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java
index d3b8cc5..965b57b 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java
@@ -1451,13 +1451,25 @@ public class Configuration {
    * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */
 
 
-  @Markdown(description = "The number of times failed kerberos operations should be retried to execute.")
+  @Markdown(description = "The number of times failed Kerberos operations should be retried to execute.")
   public static final ConfigurationProperty<Integer> KERBEROS_OPERATION_RETRIES = new ConfigurationProperty<>(
       "kerberos.operation.retries", 3);
 
-  @Markdown(description = "The time to wait (in seconds) between failed kerberos operations retries.")
+  @Markdown(description = "The time to wait (in seconds) between failed Kerberos operations retries.")
   public static final ConfigurationProperty<Integer> KERBEROS_OPERATION_RETRY_TIMEOUT = new ConfigurationProperty<>(
       "kerberos.operation.retry.timeout", 10);
+
+  /**
+   * A flag indicating whether to validate the trust of an SSL certificate provided by a KDC when
+   * performing Kerberos operations.
+   *
+   * For example, when communicating with an Active Directory using
+   * LDAPS. The default behavior is to validate the trust.
+   */
+  @Markdown(description = "Validate the trust of the SSL certificate provided by the KDC when performing Kerberos operations over SSL.")
+  public static final ConfigurationProperty<Boolean> KERBEROS_OPERATION_VERIFY_KDC_TRUST = new ConfigurationProperty<>(
+      "kerberos.operation.verify.kdc.trust", Boolean.TRUE);
+
   /**
    * The type of connection pool to use with JDBC connections to the database.
    */
@@ -6080,6 +6092,10 @@ public class Configuration {
     return Integer.valueOf(getProperty(KERBEROS_OPERATION_RETRY_TIMEOUT));
   }
 
+  public boolean validateKerberosOperationSSLCertTrust() {
+    return Boolean.parseBoolean(getProperty(KERBEROS_OPERATION_VERIFY_KDC_TRUST));
+  }
+
   /**
    * Return configured acceptors for agent api connector. Default = null
    */

http://git-wip-us.apache.org/repos/asf/ambari/blob/c3c06ea9/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java
index 10df7a2..7b6ac7e 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java
@@ -75,6 +75,7 @@ import org.apache.ambari.server.serveraction.kerberos.KerberosIdentityDataFileWr
 import org.apache.ambari.server.serveraction.kerberos.KerberosIdentityDataFileWriterFactory;
 import org.apache.ambari.server.serveraction.kerberos.KerberosInvalidConfigurationException;
 import org.apache.ambari.server.serveraction.kerberos.KerberosKDCConnectionException;
+import org.apache.ambari.server.serveraction.kerberos.KerberosKDCSSLConnectionException;
 import org.apache.ambari.server.serveraction.kerberos.KerberosLDAPContainerException;
 import org.apache.ambari.server.serveraction.kerberos.KerberosMissingAdminCredentialsException;
 import org.apache.ambari.server.serveraction.kerberos.KerberosOperationException;
@@ -1579,6 +1580,11 @@ public class KerberosHelperImpl implements KerberosHelper {
                 "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 (KerberosKDCSSLConnectionException e) {
+            throw new KerberosInvalidConfigurationException(
+                "Failed to connect to KDC - " + e.getMessage() + "\n" +
+                    "Make sure the server's SSL certificate or CA certificates have been imported into Ambari's truststore.",
+                e);
           } catch (KerberosRealmException e) {
             throw new KerberosInvalidConfigurationException(
                 "Failed to find a KDC for the specified realm - " + e.getMessage() + "\n" +

http://git-wip-us.apache.org/repos/asf/ambari/blob/c3c06ea9/ambari-server/src/main/java/org/apache/ambari/server/security/InternalSSLSocketFactory.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/InternalSSLSocketFactory.java b/ambari-server/src/main/java/org/apache/ambari/server/security/InternalSSLSocketFactory.java
new file mode 100644
index 0000000..9ecf5d1
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/InternalSSLSocketFactory.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.security;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.security.SecureRandom;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+/**
+ * InternalSSLSocketFactory is an abstract class implementing a SSLSocketFactory.
+ * <p>
+ * Implementers of this class need to specific the SSLContext protocol and whether the server's SSL
+ * certificates should be trusted or validated.
+ */
+public class InternalSSLSocketFactory extends SSLSocketFactory {
+  private SSLSocketFactory socketFactory;
+
+  InternalSSLSocketFactory(String protocol, boolean trusting) {
+    try {
+      SSLContext ctx = SSLContext.getInstance(protocol);
+
+      // If trusting, install our own TrustManager to accept certificates without validating the
+      // trust chain; else, use the default TrustManager, which validates the certificate's trust chain.
+      TrustManager[] trustManagers = (trusting)
+          ? new TrustManager[]{new LenientTrustManager()}
+          : null;
+
+      ctx.init(null, trustManagers, new SecureRandom());
+      socketFactory = ctx.getSocketFactory();
+    } catch (Exception ex) {
+      ex.printStackTrace(System.err);  /* handle exception */
+    }
+  }
+
+  @Override
+  public String[] getDefaultCipherSuites() {
+    return socketFactory.getDefaultCipherSuites();
+  }
+
+  @Override
+  public String[] getSupportedCipherSuites() {
+    return socketFactory.getSupportedCipherSuites();
+  }
+
+  @Override
+  public Socket createSocket(Socket socket, String string, int i, boolean bln) throws IOException {
+    return socketFactory.createSocket(socket, string, i, bln);
+  }
+
+  @Override
+  public Socket createSocket(String string, int i) throws IOException {
+    return socketFactory.createSocket(string, i);
+  }
+
+  @Override
+  public Socket createSocket(String string, int i, InetAddress ia, int i1) throws IOException {
+    return socketFactory.createSocket(string, i, ia, i1);
+  }
+
+  @Override
+  public Socket createSocket(InetAddress ia, int i) throws IOException {
+    return socketFactory.createSocket(ia, i);
+  }
+
+  @Override
+  public Socket createSocket(InetAddress ia, int i, InetAddress ia1, int i1) throws IOException {
+    return socketFactory.createSocket(ia, i, ia1, i1);
+  }
+
+
+  /**
+   * LenientTrustManager is a TrustManager that accepts all certificates without validating the
+   * chain of trust.
+   */
+  public static class LenientTrustManager implements X509TrustManager {
+    public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException {
+      // do nothing
+    }
+
+    public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException {
+      // do nothing
+    }
+
+    public X509Certificate[] getAcceptedIssuers() {
+      return new X509Certificate[0];
+    }
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/c3c06ea9/ambari-server/src/main/java/org/apache/ambari/server/security/InternalSSLSocketFactoryNonTrusting.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/InternalSSLSocketFactoryNonTrusting.java b/ambari-server/src/main/java/org/apache/ambari/server/security/InternalSSLSocketFactoryNonTrusting.java
new file mode 100644
index 0000000..84eada8
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/InternalSSLSocketFactoryNonTrusting.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.security;
+
+import javax.net.SocketFactory;
+import javax.net.ssl.SSLSocketFactory;
+
+/**
+ * InternalSSLSocketFactoryNonTrusting is a non-trusting {@link SSLSocketFactory} implementation
+ * using the TLSv1.2 protocol.
+ * <p>
+ * This SSL socket factory should allow for at least the following SSL protocols:
+ * <ul>
+ * <li>TLSv1.2</li>
+ * <li>TLSv1.1</li>
+ * <li>TLSv1</li>
+ * </ul>
+ * <p>
+ * However, the actual set of enabled SSL protocols is dependent on the underlying JVM.
+ * <p>
+ * This SSL socket factory creates non-trusting connections, meaning that the server's SSL certificate
+ * will be validated using the JVM-specific internal {@link javax.net.ssl.TrustManager}
+ */
+public class InternalSSLSocketFactoryNonTrusting extends InternalSSLSocketFactory {
+
+  public InternalSSLSocketFactoryNonTrusting() {
+    super("TLSv1.2", false);
+  }
+
+  public static SocketFactory getDefault() {
+    return new InternalSSLSocketFactoryNonTrusting();
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/c3c06ea9/ambari-server/src/main/java/org/apache/ambari/server/security/InternalSSLSocketFactoryTrusting.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/InternalSSLSocketFactoryTrusting.java b/ambari-server/src/main/java/org/apache/ambari/server/security/InternalSSLSocketFactoryTrusting.java
new file mode 100644
index 0000000..4bb3604
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/InternalSSLSocketFactoryTrusting.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.security;
+
+import javax.net.SocketFactory;
+import javax.net.ssl.SSLSocketFactory;
+
+/**
+ * InternalSSLSocketFactoryTrusting is a trusting {@link SSLSocketFactory} implementation
+ * using the TLSv1.2 protocol.
+ * <p>
+ * This SSL socket factory should allow for at least the following SSL protocols:
+ * <ul>
+ * <li>TLSv1.2</li>
+ * <li>TLSv1.1</li>
+ * <li>TLSv1</li>
+ * </ul>
+ * <p>
+ * However, the actual set of enabled SSL protocols is dependent on the underlying JVM.
+ * <p>
+ * This SSL socket factory creates trusting connections, meaning that the server's SSL certificate
+ * will not be tested to see if it is trusted.
+ */
+public class InternalSSLSocketFactoryTrusting extends InternalSSLSocketFactory {
+  public InternalSSLSocketFactoryTrusting() {
+    super("TLSv1.2", true);
+  }
+
+  public static SocketFactory getDefault() {
+    return new InternalSSLSocketFactoryTrusting();
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/c3c06ea9/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 b2dbd5e..8903fa1 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
@@ -46,7 +46,11 @@ import javax.naming.ldap.InitialLdapContext;
 import javax.naming.ldap.LdapContext;
 import javax.naming.ldap.LdapName;
 import javax.naming.ldap.Rdn;
+import javax.net.ssl.SSLHandshakeException;
 
+import org.apache.ambari.server.configuration.Configuration;
+import org.apache.ambari.server.security.InternalSSLSocketFactoryNonTrusting;
+import org.apache.ambari.server.security.InternalSSLSocketFactoryTrusting;
 import org.apache.ambari.server.security.credential.PrincipalKeyCredential;
 import org.apache.commons.codec.digest.DigestUtils;
 import org.apache.commons.lang.StringUtils;
@@ -60,6 +64,7 @@ import org.apache.velocity.exception.ResourceNotFoundException;
 
 import com.google.common.reflect.TypeToken;
 import com.google.gson.Gson;
+import com.google.inject.Inject;
 
 /**
  * Implementation of <code>KerberosOperationHandler</code> to created principal in Active Directory
@@ -107,7 +112,11 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
    * The Gson instance to use to convert the template-generated JSON structure to a Map of attribute
    * names to values.
    */
-  private Gson gson = new Gson();
+  @Inject
+  private Gson gson;
+
+  @Inject
+  private Configuration configuration;
 
   /**
    * Prepares and creates resources to be used by this KerberosOperationHandler
@@ -175,8 +184,6 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
 
     this.createTemplate = kerberosConfiguration.get(KERBEROS_ENV_AD_CREATE_ATTRIBUTES_TEMPLATE);
 
-    this.gson = new Gson();
-
     setOpen(true);
   }
 
@@ -189,8 +196,6 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
   public void close() throws KerberosOperationException {
     this.searchControls = null;
 
-    this.gson = null;
-
     if (this.ldapContext != null) {
       try {
         this.ldapContext.close();
@@ -447,14 +452,29 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
     properties.put(Context.SECURITY_CREDENTIALS, String.valueOf(administratorCredential.getKey()));
     properties.put(Context.SECURITY_AUTHENTICATION, "simple");
     properties.put(Context.REFERRAL, "follow");
-    properties.put("java.naming.ldap.factory.socket", TrustingSSLSocketFactory.class.getName());
+
+    if (ldapUrl.startsWith("ldaps")) {
+      if (configuration.validateKerberosOperationSSLCertTrust()) {
+        properties.put("java.naming.ldap.factory.socket", InternalSSLSocketFactoryNonTrusting.class.getName());
+      } else {
+        properties.put("java.naming.ldap.factory.socket", InternalSSLSocketFactoryTrusting.class.getName());
+      }
+    }
 
     try {
       return createInitialLdapContext(properties, null);
     } catch (CommunicationException e) {
+      Throwable rootCause = e.getRootCause();
+
       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);
+
+      if(rootCause instanceof SSLHandshakeException) {
+        throw new KerberosKDCSSLConnectionException(message, e);
+      }
+      else {
+        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);

http://git-wip-us.apache.org/repos/asf/ambari/blob/c3c06ea9/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosKDCSSLConnectionException.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosKDCSSLConnectionException.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosKDCSSLConnectionException.java
new file mode 100644
index 0000000..1e588c5
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosKDCSSLConnectionException.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.serveraction.kerberos;
+
+/**
+ * KerberosKDCSSLConnectionException is a KerberosOperationException thrown in the event a connection
+ * to the KDC was not able to be made due to an SSL issue.
+ */
+public class KerberosKDCSSLConnectionException extends KerberosOperationException {
+
+  /**
+   * Creates a new KerberosKDCSSLConnectionException with a message
+   *
+   * @param message a String containing the message indicating the reason for this exception
+   */
+  public KerberosKDCSSLConnectionException(String message) {
+    super(message);
+  }
+
+  /**
+   * Creates a new KerberosKDCSSLConnectionException with a message and a cause
+   *
+   * @param message a String containing the message indicating the reason for this exception
+   * @param cause   a Throwable declaring the previously thrown Throwable that led to this exception
+   */
+  public KerberosKDCSSLConnectionException(String message, Throwable cause) {
+    super(message, cause);
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/c3c06ea9/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/TrustingSSLSocketFactory.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/TrustingSSLSocketFactory.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/TrustingSSLSocketFactory.java
deleted file mode 100644
index 52b3703..0000000
--- a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/TrustingSSLSocketFactory.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ambari.server.serveraction.kerberos;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.Socket;
-import java.net.UnknownHostException;
-import java.security.SecureRandom;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-
-import javax.net.SocketFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSocketFactory;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.X509TrustManager;
-
-public class TrustingSSLSocketFactory extends SSLSocketFactory {
-  private SSLSocketFactory socketFactory;
-
-  public TrustingSSLSocketFactory() {
-    try {
-      SSLContext ctx = SSLContext.getInstance("TLS");
-      ctx.init(null, new TrustManager[]{new LenientTrustManager()}, new SecureRandom());
-      socketFactory = ctx.getSocketFactory();
-    } catch (Exception ex) {
-      ex.printStackTrace(System.err);  /* handle exception */
-    }
-  }
-
-  public static SocketFactory getDefault() {
-    return new TrustingSSLSocketFactory();
-  }
-
-  @Override
-  public String[] getDefaultCipherSuites() {
-    return socketFactory.getDefaultCipherSuites();
-  }
-
-  @Override
-  public String[] getSupportedCipherSuites() {
-    return socketFactory.getSupportedCipherSuites();
-  }
-
-  @Override
-  public Socket createSocket(Socket socket, String string, int i, boolean bln) throws IOException {
-    return socketFactory.createSocket(socket, string, i, bln);
-  }
-
-  @Override
-  public Socket createSocket(String string, int i) throws IOException, UnknownHostException {
-    return socketFactory.createSocket(string, i);
-  }
-
-  @Override
-  public Socket createSocket(String string, int i, InetAddress ia, int i1) throws IOException, UnknownHostException {
-    return socketFactory.createSocket(string, i, ia, i1);
-  }
-
-  @Override
-  public Socket createSocket(InetAddress ia, int i) throws IOException {
-    return socketFactory.createSocket(ia, i);
-  }
-
-  @Override
-  public Socket createSocket(InetAddress ia, int i, InetAddress ia1, int i1) throws IOException {
-    return socketFactory.createSocket(ia, i, ia1, i1);
-  }
-
-
-  public static class LenientTrustManager implements X509TrustManager {
-    public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException {
-      // do nothing
-    }
-
-    public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException {
-      // do nothing
-    }
-
-    public X509Certificate[] getAcceptedIssuers() {
-      return new java.security.cert.X509Certificate[0];
-    }
-  }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/c3c06ea9/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandlerTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandlerTest.java b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandlerTest.java
index 603f744..49f8d56 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandlerTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandlerTest.java
@@ -41,13 +41,22 @@ import javax.naming.directory.SearchResult;
 import javax.naming.ldap.Control;
 import javax.naming.ldap.LdapContext;
 
+import org.apache.ambari.server.configuration.Configuration;
+import org.apache.ambari.server.security.InternalSSLSocketFactoryNonTrusting;
+import org.apache.ambari.server.security.InternalSSLSocketFactoryTrusting;
 import org.apache.ambari.server.security.credential.PrincipalKeyCredential;
+import org.apache.ambari.server.state.Clusters;
+import org.apache.ambari.server.state.stack.OsFamily;
 import org.easymock.Capture;
 import org.easymock.CaptureType;
 import org.easymock.IAnswer;
 import org.junit.Ignore;
 import org.junit.Test;
 
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
 import junit.framework.Assert;
 
 public class ADKerberosOperationHandlerTest extends KerberosOperationHandlerTest {
@@ -112,6 +121,8 @@ public class ADKerberosOperationHandlerTest extends KerberosOperationHandlerTest
 
   @Test(expected = KerberosAdminAuthenticationException.class)
   public void testTestAdministratorCredentialsIncorrectAdminPassword() throws Exception {
+    Injector injector = getInjector();
+
     PrincipalKeyCredential kc = new PrincipalKeyCredential(DEFAULT_ADMIN_PRINCIPAL, "wrong");
     Map<String, String> kerberosEnvMap = new HashMap<String, String>() {
       {
@@ -123,6 +134,7 @@ public class ADKerberosOperationHandlerTest extends KerberosOperationHandlerTest
     ADKerberosOperationHandler handler = createMockBuilder(ADKerberosOperationHandler.class)
         .addMockedMethod(ADKerberosOperationHandler.class.getDeclaredMethod("createInitialLdapContext", Properties.class, Control[].class))
         .createNiceMock();
+    injector.injectMembers(handler);
 
     expect(handler.createInitialLdapContext(anyObject(Properties.class), anyObject(Control[].class))).andAnswer(new IAnswer<LdapContext>() {
       @Override
@@ -140,6 +152,8 @@ public class ADKerberosOperationHandlerTest extends KerberosOperationHandlerTest
 
   @Test(expected = KerberosAdminAuthenticationException.class)
   public void testTestAdministratorCredentialsIncorrectAdminPrincipal() throws Exception {
+    Injector injector = getInjector();
+
     PrincipalKeyCredential kc = new PrincipalKeyCredential("wrong", DEFAULT_ADMIN_PASSWORD);
     Map<String, String> kerberosEnvMap = new HashMap<String, String>() {
       {
@@ -151,6 +165,7 @@ public class ADKerberosOperationHandlerTest extends KerberosOperationHandlerTest
     ADKerberosOperationHandler handler = createMockBuilder(ADKerberosOperationHandler.class)
         .addMockedMethod(ADKerberosOperationHandler.class.getDeclaredMethod("createInitialLdapContext", Properties.class, Control[].class))
         .createNiceMock();
+    injector.injectMembers(handler);
 
     expect(handler.createInitialLdapContext(anyObject(Properties.class), anyObject(Control[].class))).andAnswer(new IAnswer<LdapContext>() {
       @Override
@@ -197,6 +212,8 @@ public class ADKerberosOperationHandlerTest extends KerberosOperationHandlerTest
 
   @Test
   public void testTestAdministratorCredentialsSuccess() throws Exception {
+    Injector injector = getInjector();
+
     PrincipalKeyCredential kc = new PrincipalKeyCredential(DEFAULT_ADMIN_PRINCIPAL, DEFAULT_ADMIN_PASSWORD);
     Map<String, String> kerberosEnvMap = new HashMap<String, String>() {
       {
@@ -209,6 +226,7 @@ public class ADKerberosOperationHandlerTest extends KerberosOperationHandlerTest
         .addMockedMethod(ADKerberosOperationHandler.class.getDeclaredMethod("createInitialLdapContext", Properties.class, Control[].class))
         .addMockedMethod(ADKerberosOperationHandler.class.getDeclaredMethod("createSearchControls"))
         .createNiceMock();
+    injector.injectMembers(handler);
 
     expect(handler.createInitialLdapContext(anyObject(Properties.class), anyObject(Control[].class)))
         .andAnswer(new IAnswer<LdapContext>() {
@@ -250,6 +268,8 @@ public class ADKerberosOperationHandlerTest extends KerberosOperationHandlerTest
 
   @Test
   public void testProcessCreateTemplateDefault() throws Exception {
+    Injector injector = getInjector();
+
     PrincipalKeyCredential kc = new PrincipalKeyCredential(DEFAULT_ADMIN_PRINCIPAL, DEFAULT_ADMIN_PASSWORD);
     Map<String, String> kerberosEnvMap = new HashMap<String, String>() {
       {
@@ -265,6 +285,7 @@ public class ADKerberosOperationHandlerTest extends KerberosOperationHandlerTest
         .addMockedMethod(ADKerberosOperationHandler.class.getDeclaredMethod("createInitialLdapContext", Properties.class, Control[].class))
         .addMockedMethod(ADKerberosOperationHandler.class.getDeclaredMethod("createSearchControls"))
         .createNiceMock();
+    injector.injectMembers(handler);
 
     @SuppressWarnings("unchecked")
     NamingEnumeration<SearchResult> searchResult = createNiceMock(NamingEnumeration.class);
@@ -360,6 +381,8 @@ public class ADKerberosOperationHandlerTest extends KerberosOperationHandlerTest
 
   @Test
   public void testProcessCreateTemplateCustom() throws Exception {
+    Injector injector = getInjector();
+
     PrincipalKeyCredential kc = new PrincipalKeyCredential(DEFAULT_ADMIN_PRINCIPAL, DEFAULT_ADMIN_PASSWORD);
     Map<String, String> kerberosEnvMap = new HashMap<String, String>() {
       {
@@ -394,6 +417,7 @@ public class ADKerberosOperationHandlerTest extends KerberosOperationHandlerTest
         .addMockedMethod(ADKerberosOperationHandler.class.getDeclaredMethod("createInitialLdapContext", Properties.class, Control[].class))
         .addMockedMethod(ADKerberosOperationHandler.class.getDeclaredMethod("createSearchControls"))
         .createNiceMock();
+    injector.injectMembers(handler);
 
     @SuppressWarnings("unchecked")
     NamingEnumeration<SearchResult> searchResult = createNiceMock(NamingEnumeration.class);
@@ -464,16 +488,18 @@ public class ADKerberosOperationHandlerTest extends KerberosOperationHandlerTest
 
   @Test
   public void testDigests() throws Exception {
+    Injector injector = getInjector();
+
     PrincipalKeyCredential kc = new PrincipalKeyCredential(DEFAULT_ADMIN_PRINCIPAL, DEFAULT_ADMIN_PASSWORD);
     Map<String, String> kerberosEnvMap = new HashMap<>();
     kerberosEnvMap.put(ADKerberosOperationHandler.KERBEROS_ENV_LDAP_URL, DEFAULT_LDAP_URL);
     kerberosEnvMap.put(ADKerberosOperationHandler.KERBEROS_ENV_PRINCIPAL_CONTAINER_DN, DEFAULT_PRINCIPAL_CONTAINER_DN);
     kerberosEnvMap.put(ADKerberosOperationHandler.KERBEROS_ENV_AD_CREATE_ATTRIBUTES_TEMPLATE, "" +
-            "{" +
-            "\"principal_digest\": \"$principal_digest\"," +
-            "\"principal_digest_256\": \"$principal_digest_256\"," +
-            "\"principal_digest_512\": \"$principal_digest_512\"" +
-            "}"
+        "{" +
+        "\"principal_digest\": \"$principal_digest\"," +
+        "\"principal_digest_256\": \"$principal_digest_256\"," +
+        "\"principal_digest_512\": \"$principal_digest_512\"" +
+        "}"
     );
 
     Capture<Attributes> capturedAttributes = newCapture();
@@ -482,6 +508,7 @@ public class ADKerberosOperationHandlerTest extends KerberosOperationHandlerTest
         .addMockedMethod(ADKerberosOperationHandler.class.getDeclaredMethod("createInitialLdapContext", Properties.class, Control[].class))
         .addMockedMethod(ADKerberosOperationHandler.class.getDeclaredMethod("createSearchControls"))
         .createNiceMock();
+    injector.injectMembers(handler);
 
     @SuppressWarnings("unchecked")
     NamingEnumeration<SearchResult> searchResult = createNiceMock(NamingEnumeration.class);
@@ -628,4 +655,63 @@ public class ADKerberosOperationHandlerTest extends KerberosOperationHandlerTest
 
     handler.close();
   }
+
+  @Test
+  public void testCreateLdapContextSSLSocketFactoryTrusting() throws Exception {
+    testCreateLdapContextSSLSocketFactory(true);
+  }
+
+  @Test
+  public void testCreateLdapContextSSLSocketFactoryNonTrusting() throws Exception {
+    testCreateLdapContextSSLSocketFactory(false);
+  }
+
+  private void testCreateLdapContextSSLSocketFactory(boolean trusting) throws Exception {
+    Injector injector = getInjector();
+
+    Configuration configuration = injector.getInstance(Configuration.class);
+    expect(configuration.validateKerberosOperationSSLCertTrust()).andReturn(!trusting).once();
+
+    LdapContext initialContext = createNiceMock(LdapContext.class);
+
+    Capture<? extends Properties> capturedProperties = newCapture(CaptureType.FIRST);
+
+    ADKerberosOperationHandler handler = createMockBuilder(ADKerberosOperationHandler.class)
+        .addMockedMethod(ADKerberosOperationHandler.class.getDeclaredMethod("createInitialLdapContext", Properties.class, Control[].class))
+        .createNiceMock();
+    injector.injectMembers(handler);
+
+    expect(handler.createInitialLdapContext(capture(capturedProperties), anyObject(Control[].class)))
+        .andReturn(initialContext)
+        .once();
+
+    replayAll();
+
+    Map<String, String> kerberosConfiguration = new HashMap<String, String>();
+    kerberosConfiguration.put(ADKerberosOperationHandler.KERBEROS_ENV_LDAP_URL, DEFAULT_LDAP_URL);
+    kerberosConfiguration.put(ADKerberosOperationHandler.KERBEROS_ENV_PRINCIPAL_CONTAINER_DN, DEFAULT_PRINCIPAL_CONTAINER_DN);
+
+    handler.open(new PrincipalKeyCredential("principal", "key"), "EXAMPLE.COM", kerberosConfiguration);
+
+    Properties properties = capturedProperties.getValue();
+    Assert.assertNotNull(properties);
+
+    String socketFactoryClassName = properties.getProperty("java.naming.ldap.factory.socket");
+    if (trusting) {
+      Assert.assertEquals(InternalSSLSocketFactoryTrusting.class.getName(), socketFactoryClassName);
+    } else {
+      Assert.assertEquals(InternalSSLSocketFactoryNonTrusting.class.getName(), socketFactoryClassName);
+    }
+  }
+
+  private Injector getInjector() {
+    return Guice.createInjector(new AbstractModule() {
+      @Override
+      protected void configure() {
+        bind(Clusters.class).toInstance(createNiceMock(Clusters.class));
+        bind(Configuration.class).toInstance(createNiceMock(Configuration.class));
+        bind(OsFamily.class).toInstance(createNiceMock(OsFamily.class));
+      }
+    });
+  }
 }
\ No newline at end of file