Skip to content

Commit

Permalink
Add the choice of whether to perform certificate verification when co…
Browse files Browse the repository at this point in the history
…nnect to ldaps://
  • Loading branch information
longping_tang authored and longping_tang committed Apr 22, 2021
1 parent b6a971f commit d5a64d8
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 6 deletions.
2 changes: 1 addition & 1 deletion src/main/java/hudson/security/LDAPSecurityRealm.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
38 changes: 33 additions & 5 deletions src/main/java/jenkins/security/plugins/ldap/LDAPConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ public class LDAPConfiguration extends AbstractDescribableImpl<LDAPConfiguration
*/
private final String server;

/**
* whether to verify ldaps sever certificate? default is false
*/
private boolean sslVerify;

/**
* The root DN to connect to. Normally something like "dc=sun,dc=com"
*/
Expand Down Expand Up @@ -166,6 +171,18 @@ public String getServer() {
return server;
}

/**
* whether to verify ldaps sever certificate? default is false
*/
public boolean isSslVerify() {
return sslVerify;
}

@DataBoundSetter
public void setSslVerify(boolean sslVerify) {
this.sslVerify = sslVerify;
}

public String getServerUrl() {
StringBuilder buf = new StringBuilder();
boolean first = true;
Expand Down Expand Up @@ -396,14 +413,15 @@ public String getDisplayName() {
return "ldap";
}

public FormValidation doCheckServer(@QueryParameter String value, @QueryParameter String managerDN, @QueryParameter Secret managerPasswordSecret,@QueryParameter String rootDN) {
public FormValidation doCheckServer(@QueryParameter String value, @QueryParameter boolean sslVerify, @QueryParameter String managerDN, @QueryParameter Secret managerPasswordSecret,@QueryParameter String rootDN) {
String server = value;
String managerPassword = Secret.toString(managerPasswordSecret);

if(!Jenkins.get().hasPermission(Jenkins.ADMINISTER))
return FormValidation.ok();
String url = LDAPSecurityRealm.toProviderUrl(server,rootDN);

try {
try(SetContextClassLoader sccl = new SetContextClassLoader()) {
Hashtable<String,String> props = new Hashtable<String,String>();
if(managerDN!=null && managerDN.trim().length() > 0 && !"undefined".equals(managerDN)) {
props.put(Context.SECURITY_PRINCIPAL,managerDN);
Expand All @@ -412,7 +430,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("");
Expand Down Expand Up @@ -456,14 +477,18 @@ public DescriptorExtensionList<LDAPGroupMembershipStrategy, Descriptor<LDAPGroup
* @return null if not found.
*/
private String inferRootDN(String server) {
try {
try(SetContextClassLoader sccl = new SetContextClassLoader()) {
Hashtable<String, String> props = new Hashtable<String, String>();
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("");
Expand Down Expand Up @@ -574,6 +599,9 @@ public ApplicationContext createApplicationContext(LDAPSecurityRealm realm) {
Map<String, Object> 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
<f:textbox/>
</f:entry>
<f:advanced title="${%Advanced Server Configuration}">
<f:entry field="sslVerify">
<f:checkbox title="${%SSL Verify}"/>
</f:entry>
<f:entry field="rootDN" title="${%root DN}">
<f:textbox/>
</f:entry>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<div>
<p>
If checked and ldap server is an ldaps:// style URL, requiring the certificate to be verified.
</p>
<p>
If unchecked (the default), Jenkins will not verify the server certificate when it connects to
perform a query.
</p>
</div>

0 comments on commit d5a64d8

Please sign in to comment.