diff --git a/src/main/java/hudson/security/LDAPSecurityRealm.java b/src/main/java/hudson/security/LDAPSecurityRealm.java index e2b7e765..bfa0d193 100644 --- a/src/main/java/hudson/security/LDAPSecurityRealm.java +++ b/src/main/java/hudson/security/LDAPSecurityRealm.java @@ -1587,7 +1587,7 @@ public FormValidation validate(LDAPSecurityRealm realm, String user, String pass // we can only do deep validation if the connection is correct LDAPConfiguration.LDAPConfigurationDescriptor confDescriptor = Jenkins.getActiveInstance().getDescriptorByType(LDAPConfiguration.LDAPConfigurationDescriptor.class); for (LDAPConfiguration configuration : realm.getConfigurations()) { - FormValidation connectionCheck = confDescriptor.doCheckServer(configuration.getServerUrl(), configuration.getManagerDN(), configuration.getManagerPasswordSecret(),configuration.getRootDN()); + FormValidation connectionCheck = confDescriptor.doCheckServer(configuration.getServerUrl(), configuration.isSslVerify(),configuration.getManagerDN(), configuration.getManagerPasswordSecret(),configuration.getRootDN()); if (connectionCheck.kind != FormValidation.Kind.OK) { return connectionCheck; } diff --git a/src/main/java/jenkins/security/plugins/ldap/BlindSSLSocketFactory.java b/src/main/java/jenkins/security/plugins/ldap/BlindSSLSocketFactory.java new file mode 100644 index 00000000..d5ea37c4 --- /dev/null +++ b/src/main/java/jenkins/security/plugins/ldap/BlindSSLSocketFactory.java @@ -0,0 +1,96 @@ +package jenkins.security.plugins.ldap; + +import javax.net.SocketFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.security.GeneralSecurityException; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; + +public class BlindSSLSocketFactory extends SSLSocketFactory { + private static final BlindSSLSocketFactory INSTANCE; + + static { + final X509TrustManager dummyTrustManager = + new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { + return null; + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) {} + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) {} + }; + try { + final SSLContext context = SSLContext.getInstance("SSL"); + final TrustManager[] trustManagers = {dummyTrustManager}; + final SecureRandom rng = new SecureRandom(); + context.init(null, trustManagers, rng); + INSTANCE = new BlindSSLSocketFactory(context.getSocketFactory()); + } catch (GeneralSecurityException e) { + throw new RuntimeException("Cannot create BlindSslSocketFactory", e); + } + } + + public static SocketFactory getDefault() { + return INSTANCE; + } + + private final SSLSocketFactory sslFactory; + + private BlindSSLSocketFactory(SSLSocketFactory sslFactory) { + this.sslFactory = sslFactory; + } + + @Override + public Socket createSocket(Socket s, String host, int port, boolean autoClose) + throws IOException { + return sslFactory.createSocket(s, host, port, autoClose); + } + + @Override + public String[] getDefaultCipherSuites() { + return sslFactory.getDefaultCipherSuites(); + } + + @Override + public String[] getSupportedCipherSuites() { + return sslFactory.getSupportedCipherSuites(); + } + + @Override + public Socket createSocket() throws IOException { + return sslFactory.createSocket(); + } + + @Override + public Socket createSocket(String host, int port) throws IOException, UnknownHostException { + return sslFactory.createSocket(host, port); + } + + @Override + public Socket createSocket(InetAddress host, int port) throws IOException { + return sslFactory.createSocket(host, port); + } + + @Override + public Socket createSocket(String host, int port, InetAddress localHost, int localPort) + throws IOException, UnknownHostException { + return sslFactory.createSocket(host, port, localHost, localPort); + } + + @Override + public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) + throws IOException { + return sslFactory.createSocket(address, port, localAddress, localPort); + } +} diff --git a/src/main/java/jenkins/security/plugins/ldap/LDAPConfiguration.java b/src/main/java/jenkins/security/plugins/ldap/LDAPConfiguration.java index 7d8ccfbd..4d7a8a63 100644 --- a/src/main/java/jenkins/security/plugins/ldap/LDAPConfiguration.java +++ b/src/main/java/jenkins/security/plugins/ldap/LDAPConfiguration.java @@ -100,6 +100,11 @@ public class LDAPConfiguration extends AbstractDescribableImpl props = new Hashtable(); if(managerDN!=null && managerDN.trim().length() > 0 && !"undefined".equals(managerDN)) { props.put(Context.SECURITY_PRINCIPAL,managerDN); @@ -412,7 +425,10 @@ public FormValidation doCheckServer(@QueryParameter String value, @QueryParamete props.put(Context.SECURITY_CREDENTIALS,managerPassword); } props.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); - props.put(Context.PROVIDER_URL, LDAPSecurityRealm.toProviderUrl(server,rootDN)); + props.put(Context.PROVIDER_URL, url); + if(url.startsWith("ldaps:") && !sslVerify) { + props.put("java.naming.ldap.factory.socket", BlindSSLSocketFactory.class.getName()); + } DirContext ctx = new InitialDirContext(props); ctx.getAttributes(""); @@ -456,14 +472,18 @@ public DescriptorExtensionList props = new Hashtable(); + String url = LDAPSecurityRealm.toProviderUrl(getServerUrl(), ""); if (managerDN != null) { props.put(Context.SECURITY_PRINCIPAL, managerDN); props.put(Context.SECURITY_CREDENTIALS, getManagerPassword()); } props.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); - props.put(Context.PROVIDER_URL, LDAPSecurityRealm.toProviderUrl(getServerUrl(), "")); + props.put(Context.PROVIDER_URL, url); + if(url.startsWith("ldaps:") && !sslVerify) { + props.put("java.naming.ldap.factory.socket", BlindSSLSocketFactory.class.getName()); + } DirContext ctx = new InitialDirContext(props); Attributes atts = ctx.getAttributes(""); @@ -574,6 +594,9 @@ public ApplicationContext createApplicationContext(LDAPSecurityRealm realm) { Map vars = new HashMap<>(); vars.put("com.sun.jndi.ldap.connect.timeout", "30000"); // timeout if no connection after 30 seconds vars.put("com.sun.jndi.ldap.read.timeout", "60000"); // timeout if no response after 60 seconds + if(getLDAPURL().startsWith("ldaps:") && !sslVerify) { + vars.put("java.naming.ldap.factory.socket", BlindSSLSocketFactory.class.getName()); + } vars.putAll(getExtraEnvVars()); contextSource.setBaseEnvironmentProperties(vars); contextSource.afterPropertiesSet(); diff --git a/src/main/resources/jenkins/security/plugins/ldap/LDAPConfiguration/config.jelly b/src/main/resources/jenkins/security/plugins/ldap/LDAPConfiguration/config.jelly index 3ee96c6a..b2cf0f58 100644 --- a/src/main/resources/jenkins/security/plugins/ldap/LDAPConfiguration/config.jelly +++ b/src/main/resources/jenkins/security/plugins/ldap/LDAPConfiguration/config.jelly @@ -4,6 +4,9 @@ + + + diff --git a/src/main/resources/jenkins/security/plugins/ldap/LDAPConfiguration/help-sslVerify.html b/src/main/resources/jenkins/security/plugins/ldap/LDAPConfiguration/help-sslVerify.html new file mode 100644 index 00000000..c47f1062 --- /dev/null +++ b/src/main/resources/jenkins/security/plugins/ldap/LDAPConfiguration/help-sslVerify.html @@ -0,0 +1,9 @@ +
+

+ If checked and ldap server is an ldaps:// style URL, requiring the certificate to be verified. +

+

+ If unchecked (the default), Jenkins will not verify the server certificate when it connects to + perform a query. +

+
\ No newline at end of file