CVE-2019-1003009
Description
An improper certificate validation vulnerability exists in Jenkins Active Directory Plugin 2.10 and earlier in src/main/java/hudson/plugins/active_directory/ActiveDirectoryDomain.java, src/main/java/hudson/plugins/active_directory/ActiveDirectorySecurityRealm.java, src/main/java/hudson/plugins/active_directory/ActiveDirectoryUnixAuthenticationProvider.java that allows attackers to impersonate the Active Directory server Jenkins connects to for authentication if Jenkins is configured to use StartTLS.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Jenkins Active Directory Plugin 2.10 and earlier fails to validate certificates when using StartTLS, allowing attackers to impersonate the AD server.
Vulnerability
An improper certificate validation vulnerability exists in Jenkins Active Directory Plugin 2.10 and earlier in src/main/java/hudson/plugins/active_directory/ActiveDirectoryDomain.java, src/main/java/hudson/plugins/active_directory/ActiveDirectorySecurityRealm.java, and src/main/java/hudson/plugins/active_directory/ActiveDirectoryUnixAuthenticationProvider.java. The flaw occurs when Jenkins is configured to use StartTLS, which results in no certificate validation against a trusted certificate authority [1][4].
Exploitation
An attacker can impersonate the Active Directory server that Jenkins connects to for authentication. The attacker must be in a network position to intercept or redirect LDAP traffic (e.g., through a man-in-the-middle attack) when Jenkins initiates a StartTLS connection. No additional privileges within Jenkins are required to exploit this vulnerability; the attacker only needs to provide a malicious LDAP server that Jenkins trusts due to the missing certificate validation [1].
Impact
Successful exploitation allows the attacker to impersonate the Active Directory server. This means the attacker can authenticate arbitrary users on Jenkins, potentially gaining administrative access to the Jenkins instance, depending on authorization configuration. The core impact is authentication bypass and privilege escalation due to compromise of the authentication source [1][4].
Mitigation
The Jenkins Security Advisory 2019-01-28 recommends upgrading the Active Directory Plugin to version 2.11 or later [1]. The commit fixing this issue deprecates the tlsConfiguration field and related constructors to enforce proper certificate validation via ActiveDirectoryInternalUsersDatabase [3]. No workarounds short of disabling StartTLS or upgrading are mentioned in the available references.
AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.jenkins-ci.plugins:active-directoryMaven | < 2.11 | 2.11 |
Affected products
2- Jenkins project/Jenkins Active Directory Pluginv5Range: 2.10 and earlier
Patches
1520faf5bb107[SECURITY-859]
14 files changed · +236 −42
src/main/java/hudson/plugins/active_directory/ActiveDirectoryDomain.java+42 −6 modified@@ -29,6 +29,7 @@ import hudson.model.AbstractDescribableImpl; import hudson.model.Descriptor; import hudson.util.FormValidation; +import hudson.util.ListBoxModel; import hudson.util.Secret; import jenkins.model.Jenkins; import org.acegisecurity.BadCredentialsException; @@ -49,6 +50,7 @@ import javax.servlet.ServletException; import java.io.IOException; import java.io.Serializable; +import java.util.ArrayList; import java.util.Hashtable; import java.util.List; import java.util.logging.Level; @@ -105,6 +107,18 @@ public class ActiveDirectoryDomain extends AbstractDescribableImpl<ActiveDirecto public Secret bindPassword; + /** + * Selects the SSL strategy to follow on the TLS connections + * + * <p> + * Even if we are not using any of the TLS ports (3269/636) the plugin will try to establish a TLS channel + * using startTLS. Because of this, we need to be able to specify the SSL strategy on the plugin + * + * <p> + * For the moment there are two possible values: trustAllCertificates and trustStore. + */ + protected TlsConfiguration tlsConfiguration; + // domain name prefixes // see http://technet.microsoft.com/en-us/library/cc759550(WS.10).aspx public enum Catalog { @@ -126,8 +140,13 @@ public ActiveDirectoryDomain(String name, String servers) { this(name, servers, null, null, null); } - @DataBoundConstructor + @Deprecated public ActiveDirectoryDomain(String name, String servers, String site, String bindName, String bindPassword) { + this(name, servers, site, bindName, bindPassword, TlsConfiguration.TRUST_ALL_CERTIFICATES); + } + + @DataBoundConstructor + public ActiveDirectoryDomain(String name, String servers, String site, String bindName, String bindPassword, TlsConfiguration tlsConfiguration) { this.name = name; // Append default port if not specified servers = fixEmpty(servers); @@ -144,6 +163,7 @@ public ActiveDirectoryDomain(String name, String servers, String site, String bi this.site = fixEmpty(site); this.bindName = fixEmpty(bindName); this.bindPassword = Secret.fromString(fixEmpty(bindPassword)); + this.tlsConfiguration = tlsConfiguration; } @Restricted(NoExternalUse.class) @@ -171,6 +191,11 @@ public String getSite() { return site; } + @Restricted(NoExternalUse.class) + public TlsConfiguration getTlsConfiguration() { + return tlsConfiguration; + } + /** * Get the record from a domain * @@ -240,12 +265,23 @@ public static String fixEmpty(String s) { @Extension public static class DescriptorImpl extends Descriptor<ActiveDirectoryDomain> { public String getDisplayName() { return ""; } + + public ListBoxModel doFillTlsConfigurationItems() { + ListBoxModel model = new ListBoxModel(); + for (TlsConfiguration tlsConfiguration : TlsConfiguration.values()) { + model.add(tlsConfiguration.getDisplayName(),tlsConfiguration.name()); + } + return model; + } public FormValidation doValidateTest(@QueryParameter(fixEmpty = true) String name, @QueryParameter(fixEmpty = true) String servers, @QueryParameter(fixEmpty = true) String site, @QueryParameter(fixEmpty = true) String bindName, - @QueryParameter(fixEmpty = true) String bindPassword) throws IOException, ServletException, NamingException { + @QueryParameter(fixEmpty = true) String bindPassword, @QueryParameter(fixEmpty = true) TlsConfiguration tlsConfiguration) throws IOException, ServletException, NamingException { + ActiveDirectoryDomain domain = new ActiveDirectoryDomain(name, servers, site, bindName, bindPassword, tlsConfiguration); + List<ActiveDirectoryDomain> domains = new ArrayList<>(1); + domains.add(domain); - // Create a fake ActiveDirectorySecurityRealm - ActiveDirectorySecurityRealm activeDirectorySecurityRealm = new ActiveDirectorySecurityRealm(name, site, bindName, bindPassword, servers); + ActiveDirectorySecurityRealm activeDirectorySecurityRealm = new ActiveDirectorySecurityRealm(null, domains, site, bindName, + bindPassword, null, GroupLookupStrategy.AUTO, false, true, null, false, (ActiveDirectoryInternalUsersDatabase) null); ClassLoader ccl = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); @@ -273,7 +309,6 @@ public FormValidation doValidateTest(@QueryParameter(fixEmpty = true) String nam return FormValidation.warningWithMarkup("Leaving blank <b>`Bind DN`</b> means that any operation performed will use anonymous binding. Keep in mind that this is not recommended as some servers <a href=\"https://support.microsoft.com/en-us/help/326690/anonymous-ldap-operations-to-active-directory-are-disabled-on-windows\">do not allow it by default.</a>"); } - ActiveDirectoryDomain domain = activeDirectorySecurityRealm.getDomain(name); Attribute domainAttribute = domain.getRecordFromDomain(); // As per JENKINS-36148 only show error message in case the servers list is empty @@ -300,7 +335,8 @@ public FormValidation doValidateTest(@QueryParameter(fixEmpty = true) String nam if (bindName != null) { // Make sure the bind actually works try { - DirContext context = activeDirectorySecurityRealm.getDescriptor().bind(bindName, Secret.toString(password), obtainerServers); + Hashtable<String, String> props = new Hashtable<>(0); + DirContext context = activeDirectorySecurityRealm.getDescriptor().bind(bindName, Secret.toString(password), obtainerServers, props, tlsConfiguration); try { // Actually do a search to make sure the credential is valid Attributes userAttributes = new LDAPSearchBuilder(context, toDC(name)).subTreeScope().searchOne("(objectClass=user)");
src/main/java/hudson/plugins/active_directory/ActiveDirectorySecurityRealm.java+28 −19 modified@@ -205,7 +205,8 @@ public class ActiveDirectorySecurityRealm extends AbstractPasswordBasedSecurityR * <p> * For the moment there are two possible values: trustAllCertificates and trustStore. */ - protected TlsConfiguration tlsConfiguration; + @Deprecated + protected transient TlsConfiguration tlsConfiguration; /** * The Jenkins internal user to fall back in case f {@link NamingException} @@ -245,10 +246,17 @@ public ActiveDirectorySecurityRealm(String domain, List<ActiveDirectoryDomain> d this(domain, domains, site, bindName, bindPassword, server, groupLookupStrategy, removeIrrelevantGroups, customDomain, cache, startTls, tlsConfiguration, null); } + @Deprecated + public ActiveDirectorySecurityRealm(String domain, List<ActiveDirectoryDomain> domains, String site, String bindName, + String bindPassword, String server, GroupLookupStrategy groupLookupStrategy, boolean removeIrrelevantGroups, Boolean customDomain, CacheConfiguration cache, Boolean startTls, TlsConfiguration tlsConfiguration, ActiveDirectoryInternalUsersDatabase internalUsersDatabase) { + this(domain, domains, site, bindName, bindPassword, server, groupLookupStrategy, removeIrrelevantGroups, customDomain, cache, startTls, (ActiveDirectoryInternalUsersDatabase) null); + } + + @DataBoundConstructor // as Java signature, this binding doesn't make sense, so please don't use this constructor public ActiveDirectorySecurityRealm(String domain, List<ActiveDirectoryDomain> domains, String site, String bindName, - String bindPassword, String server, GroupLookupStrategy groupLookupStrategy, boolean removeIrrelevantGroups, Boolean customDomain, CacheConfiguration cache, Boolean startTls, TlsConfiguration tlsConfiguration, ActiveDirectoryInternalUsersDatabase internalUsersDatabase) { + String bindPassword, String server, GroupLookupStrategy groupLookupStrategy, boolean removeIrrelevantGroups, Boolean customDomain, CacheConfiguration cache, Boolean startTls, ActiveDirectoryInternalUsersDatabase internalUsersDatabase) { if (customDomain!=null && !customDomain) domains = null; this.domain = fixEmpty(domain); @@ -260,7 +268,6 @@ public ActiveDirectorySecurityRealm(String domain, List<ActiveDirectoryDomain> d this.groupLookupStrategy = groupLookupStrategy; this.removeIrrelevantGroups = removeIrrelevantGroups; this.cache = cache; - this.tlsConfiguration = tlsConfiguration; this.startTls = startTls; this.internalUsersDatabase = internalUsersDatabase; } @@ -318,6 +325,7 @@ public GroupLookupStrategy getGroupLookupStrategy() { } // for jelly use only + @Deprecated @Restricted(NoExternalUse.class) public TlsConfiguration getTlsConfiguration() { return tlsConfiguration; @@ -405,6 +413,12 @@ public Object readResolve() throws ObjectStreamException { activeDirectoryDomain.site = site; } } + // SECURITY-859 Make tlsConfiguration independent of each domain + if (tlsConfiguration != null) { + for (ActiveDirectoryDomain activeDirectoryDomain : activeDirectoryDomains) { + activeDirectoryDomain.tlsConfiguration = tlsConfiguration; + } + } } if (startTls == null) { this.startTls = true; @@ -539,14 +553,6 @@ public ListBoxModel doFillGroupLookupStrategyItems() { return model; } - public ListBoxModel doFillTlsConfigurationItems() { - ListBoxModel model = new ListBoxModel(); - for (TlsConfiguration tlsConfiguration : TlsConfiguration.values()) { - model.add(tlsConfiguration.getDisplayName(),tlsConfiguration.name()); - } - return model; - } - private boolean isTrustAllCertificatesEnabled(TlsConfiguration tlsConfiguration) { return (tlsConfiguration == null || TlsConfiguration.TRUST_ALL_CERTIFICATES.equals(tlsConfiguration)); } @@ -555,7 +561,7 @@ private boolean isTrustAllCertificatesEnabled(TlsConfiguration tlsConfiguration) @Deprecated public DirContext bind(String principalName, String password, List<SocketInfo> ldapServers, Hashtable<String, String> props) throws NamingException { - return bind(principalName, password, ldapServers, props, null); + return bind(principalName, password, ldapServers, props, TlsConfiguration.TRUST_ALL_CERTIFICATES); } /** @@ -590,7 +596,7 @@ public DirContext bind(String principalName, String password, List<SocketInfo> l for (SocketInfo ldapServer : ldapServers) { try { - LdapContext context = bind(principalName, password, ldapServer, newProps); + LdapContext context = bind(principalName, password, ldapServer, newProps, tlsConfiguration); LOGGER.fine("Bound to " + ldapServer); return context; } catch (javax.naming.AuthenticationException e) { @@ -640,6 +646,7 @@ private void customizeLdapProperties(Hashtable<String, String> props) { customizeLdapProperty(props, "com.sun.jndi.ldap.read.timeout"); } + @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD", justification = "Deprecated method.It will removed at some point") @IgnoreJRERequirement @Deprecated private LdapContext bind(String principalName, String password, SocketInfo server, Hashtable<String, String> props) throws NamingException { @@ -930,12 +937,14 @@ public boolean isActivated() { SecurityRealm securityRealm = Jenkins.getActiveInstance().getSecurityRealm(); if (securityRealm instanceof ActiveDirectorySecurityRealm) { ActiveDirectorySecurityRealm activeDirectorySecurityRealm = (ActiveDirectorySecurityRealm) securityRealm; - // AdministrativeMonitor if native authentication, using customDomains and tlsConfiguration not saved - if (activeDirectorySecurityRealm.tlsConfiguration == null && activeDirectorySecurityRealm.getDescriptor().canDoNativeAuth() && activeDirectorySecurityRealm.domains != null) { - return true; - // AdministrativeMonitor in Unix environment if tlsConfiguration not saved - } else if (activeDirectorySecurityRealm.tlsConfiguration == null && !activeDirectorySecurityRealm.getDescriptor().canDoNativeAuth()) { - return true; + for (ActiveDirectoryDomain activeDirectoryDomain : activeDirectorySecurityRealm.getDomains()) { + // AdministrativeMonitor if native authentication, using customDomains and tlsConfiguration not saved + if (activeDirectoryDomain.tlsConfiguration == null && activeDirectorySecurityRealm.getDescriptor().canDoNativeAuth() && activeDirectorySecurityRealm.domains != null) { + return true; + // AdministrativeMonitor in Unix environment if tlsConfiguration not saved + } else if (activeDirectoryDomain.tlsConfiguration == null && !activeDirectorySecurityRealm.getDescriptor().canDoNativeAuth()) { + return true; + } } } return false;
src/main/java/hudson/plugins/active_directory/ActiveDirectoryUnixAuthenticationProvider.java+5 −5 modified@@ -154,6 +154,7 @@ public class ActiveDirectoryUnixAuthenticationProvider extends AbstractActiveDir * <p> * For the moment there are two possible values: trustAllCertificates and trustStore. */ + @Deprecated protected TlsConfiguration tlsConfiguration; /** @@ -195,7 +196,6 @@ public ActiveDirectoryUnixAuthenticationProvider(ActiveDirectorySecurityRealm re } this.userCache = cache.getUserCache(); this.groupCache = cache.getGroupCache(); - this.tlsConfiguration =realm.tlsConfiguration; this.threadPoolExecutor = new ThreadPoolExecutor( corePoolSize, maxPoolSize, @@ -354,7 +354,7 @@ public UserDetails call() throws AuthenticationException, NamingException { // two step approach. Use a special credential to obtain DN for the // user trying to login, then authenticate. try { - context = descriptor.bind(bindName, bindPassword, ldapServers, props, tlsConfiguration); + context = descriptor.bind(bindName, bindPassword, ldapServers, props, domain.getTlsConfiguration()); anonymousBind = false; } catch (NamingException e) { throw new AuthenticationServiceException("Failed to bind to LDAP server with the bind name/password", e); @@ -367,7 +367,7 @@ public UserDetails call() throws AuthenticationException, NamingException { try { // if we are just retrieving the user, try using anonymous bind by empty password (see RFC 2829 5.1) // but if that fails, that's not BadCredentialException but UserMayOrMayNotExistException - context = descriptor.bind(userPrincipalName, anonymousBind ? "" : password, ldapServers, props); + context = descriptor.bind(userPrincipalName, anonymousBind ? "" : password, ldapServers, props, domain.getTlsConfiguration()); } catch (BadCredentialsException e) { if (anonymousBind) // in my observation, if we attempt an anonymous bind and AD doesn't allow it, it still passes the bind method @@ -407,7 +407,7 @@ public UserDetails call() throws AuthenticationException, NamingException { // if we've used the credential specifically for the bind, we // need to verify the provided password to do authentication LOGGER.log(Level.FINE, "Attempting to validate password for DN={0}", dn); - DirContext test = descriptor.bind(dnFormatted, password, ldapServers, props, tlsConfiguration); + DirContext test = descriptor.bind(dnFormatted, password, ldapServers, props, domain.getTlsConfiguration()); // Binding alone is not enough to test the credential. Need to actually perform some query operation. // but if the authentication fails this throws an exception try { @@ -522,7 +522,7 @@ public ActiveDirectoryGroupDetails call() throws NamingException { ClassLoader ccl = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); try { - DirContext context = descriptor.bind(domain.getBindName(), domain.getBindPassword().getPlainText(), obtainLDAPServers(domain)); + DirContext context = descriptor.bind(domain.getBindName(), domain.getBindPassword().getPlainText(), obtainLDAPServers(domain), props, domain.getTlsConfiguration()); try { final String domainDN = toDC(domain.getName());
src/main/resources/hudson/plugins/active_directory/ActiveDirectorySecurityRealm/configAdvanced.jelly+0 −3 modified@@ -15,9 +15,6 @@ <f:entry field="removeIrrelevantGroups" title="${%Remove irrelevant groups}"> <f:checkbox /> </f:entry> - <f:entry field="tlsConfiguration" title="${%TLS Configuration}"> - <f:select /> - </f:entry> <f:entry field="environmentProperties" title="${%Environment Properties}"> <f:repeatableProperty field="environmentProperties" /> </f:entry>
src/main/resources/hudson/plugins/active_directory/ActiveDirectorySecurityRealm/config.jelly+4 −1 modified@@ -60,8 +60,11 @@ <f:entry field="bindPassword" title="${%Bind Password}"> <f:password /> </f:entry> + <f:entry field="tlsConfiguration" title="${%TLS Configuration}"> + <f:select /> + </f:entry> <f:nested> - <f:validateButton with="name,servers,site,site,bindName,bindPassword" title="${%Test Domain}" method="validateTest"/> + <f:validateButton with="name,servers,site,bindName,bindPassword,tlsConfiguration" title="${%Test Domain}" method="validateTest"/> </f:nested> </table> <div align="right">
src/test/java/hudson/plugins/active_directory/ActiveDirectorySecurityRealmTest.java+57 −1 modified@@ -16,6 +16,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertFalse; @@ -25,6 +26,9 @@ public class ActiveDirectorySecurityRealmTest { @Rule public JenkinsRule jenkinsRule = new JenkinsRule(); + public final static String AD_DOMAIN = "samdom.example.com"; + public final static String AD_MANAGER_DN = "CN=Administrator,CN=Users,DC=samdom,DC=example,DC=com"; + @LocalData @Test public void testReadResolveSingleDomain() throws Exception { @@ -41,6 +45,8 @@ public void testReadResolveSingleDomain() throws Exception { assertNotNull(activeDirectorySecurityRealm.getDomains().get(0).getBindPassword()); // JENKINS-39423 Make Site independent of each domain assertEquals("site", activeDirectorySecurityRealm.getDomains().get(0).getSite()); + // SECURITY-859 Make tlsConfiguration independent of each domain + assertEquals(TlsConfiguration.TRUST_ALL_CERTIFICATES, activeDirectorySecurityRealm.getDomains().get(0).getTlsConfiguration()); } } @@ -60,6 +66,8 @@ public void testReadResolveSingleDomainSingleServer() throws Exception { assertNotNull(activeDirectorySecurityRealm.getDomains().get(0).getBindPassword()); // JENKINS-39423 Make Site independent of each domain assertEquals("site", activeDirectorySecurityRealm.getDomains().get(0).getSite()); + // SECURITY-859 Make tlsConfiguration independent of each domain + assertEquals(TlsConfiguration.TRUST_ALL_CERTIFICATES, activeDirectorySecurityRealm.getDomains().get(0).getTlsConfiguration()); } } @@ -79,6 +87,8 @@ public void testReadResolveSingleDomainWithTwoServers() throws Exception { assertNotNull(activeDirectorySecurityRealm.getDomains().get(0).getBindPassword()); // JENKINS-39423 Make Site independent of each domain assertEquals("site", activeDirectorySecurityRealm.getDomains().get(0).getSite()); + // SECURITY-859 Make tlsConfiguration independent of each domain + assertEquals(TlsConfiguration.TRUST_ALL_CERTIFICATES, activeDirectorySecurityRealm.getDomains().get(0).getTlsConfiguration()); } } @@ -100,6 +110,9 @@ public void testReadResolveTwoDomainsWithoutSpaceAfterComma() throws Exception { assertNotNull(activeDirectorySecurityRealm.getDomains().get(0).getBindPassword()); assertEquals("bindUser", activeDirectorySecurityRealm.getDomains().get(1).getBindName()); assertNotNull(activeDirectorySecurityRealm.getDomains().get(1).getBindPassword()); + // SECURITY-859 Make tlsConfiguration independent of each domain + assertEquals(TlsConfiguration.TRUST_ALL_CERTIFICATES, activeDirectorySecurityRealm.getDomains().get(0).getTlsConfiguration()); + assertEquals(TlsConfiguration.TRUST_ALL_CERTIFICATES, activeDirectorySecurityRealm.getDomains().get(1).getTlsConfiguration()); } } @@ -121,6 +134,9 @@ public void testReadResolveTwoDomainsWithoutSpaceAfterCommaAndSingleServer() thr assertNotNull(activeDirectorySecurityRealm.getDomains().get(0).getBindPassword()); assertEquals("bindUser", activeDirectorySecurityRealm.getDomains().get(1).getBindName()); assertNotNull(activeDirectorySecurityRealm.getDomains().get(1).getBindPassword()); + // SECURITY-859 Make tlsConfiguration independent of each domain + assertEquals(TlsConfiguration.TRUST_ALL_CERTIFICATES, activeDirectorySecurityRealm.getDomains().get(0).getTlsConfiguration()); + assertEquals(TlsConfiguration.TRUST_ALL_CERTIFICATES, activeDirectorySecurityRealm.getDomains().get(1).getTlsConfiguration()); } } @@ -142,6 +158,9 @@ public void testReadResolveTwoDomainsWithoutSpaceAfterCommaAndTwoServers() throw assertNotNull(activeDirectorySecurityRealm.getDomains().get(0).getBindPassword()); assertEquals("bindUser", activeDirectorySecurityRealm.getDomains().get(1).getBindName()); assertNotNull(activeDirectorySecurityRealm.getDomains().get(1).getBindPassword()); + // SECURITY-859 Make tlsConfiguration independent of each domain + assertEquals(TlsConfiguration.TRUST_ALL_CERTIFICATES, activeDirectorySecurityRealm.getDomains().get(0).getTlsConfiguration()); + assertEquals(TlsConfiguration.TRUST_ALL_CERTIFICATES, activeDirectorySecurityRealm.getDomains().get(1).getTlsConfiguration()); } } @@ -163,6 +182,9 @@ public void testReadResolveTwoDomainsWithSpaceAfterComma() throws Exception { assertNotNull(activeDirectorySecurityRealm.getDomains().get(0).getBindPassword()); assertEquals("bindUser", activeDirectorySecurityRealm.getDomains().get(1).getBindName()); assertNotNull(activeDirectorySecurityRealm.getDomains().get(1).getBindPassword()); + // SECURITY-859 Make tlsConfiguration independent of each domain + assertEquals(TlsConfiguration.TRUST_ALL_CERTIFICATES, activeDirectorySecurityRealm.getDomains().get(0).getTlsConfiguration()); + assertEquals(TlsConfiguration.TRUST_ALL_CERTIFICATES, activeDirectorySecurityRealm.getDomains().get(1).getTlsConfiguration()); } } @@ -184,6 +206,9 @@ public void testReadResolveTwoDomainsWithSpaceAfterCommaAndSingleServer() throws assertNotNull(activeDirectorySecurityRealm.getDomains().get(0).getBindPassword()); assertEquals("bindUser", activeDirectorySecurityRealm.getDomains().get(1).getBindName()); assertNotNull(activeDirectorySecurityRealm.getDomains().get(1).getBindPassword()); + // SECURITY-859 Make tlsConfiguration independent of each domain + assertEquals(TlsConfiguration.TRUST_ALL_CERTIFICATES, activeDirectorySecurityRealm.getDomains().get(0).getTlsConfiguration()); + assertEquals(TlsConfiguration.TRUST_ALL_CERTIFICATES, activeDirectorySecurityRealm.getDomains().get(1).getTlsConfiguration()); } } @@ -205,6 +230,9 @@ public void testReadResolveTwoDomainsWithSpaceAfterCommaAndTwoServers() throws E assertNotNull(activeDirectorySecurityRealm.getDomains().get(0).getBindPassword()); assertEquals("bindUser", activeDirectorySecurityRealm.getDomains().get(1).getBindName()); assertNotNull(activeDirectorySecurityRealm.getDomains().get(1).getBindPassword()); + // SECURITY-859 Make tlsConfiguration independent of each domain + assertEquals(TlsConfiguration.TRUST_ALL_CERTIFICATES, activeDirectorySecurityRealm.getDomains().get(0).getTlsConfiguration()); + assertEquals(TlsConfiguration.TRUST_ALL_CERTIFICATES, activeDirectorySecurityRealm.getDomains().get(1).getTlsConfiguration()); } } @@ -224,7 +252,8 @@ public void testReadResolveMultiDomainSingleDomainOneDisplayName() throws Except assertNotNull(activeDirectorySecurityRealm.getDomains().get(0).getBindPassword()); // JENKINS-39423 Make Site independent of each domain assertEquals("site", activeDirectorySecurityRealm.getDomains().get(0).getSite()); - } + // SECURITY-859 If there is not tlsConfiguration saved on disk, keep it as null + assertNull(activeDirectorySecurityRealm.getDomains().get(0).getTlsConfiguration()); } } @LocalData @@ -248,6 +277,10 @@ public void testReadResolveMultiDomainTwoDomainsOneDisplayName() throws Exceptio // JENKINS-39423 Make Site independent of each domain assertEquals("site", activeDirectorySecurityRealm.getDomains().get(0).getSite()); assertEquals("site", activeDirectorySecurityRealm.getDomains().get(1).getSite()); + // SECURITY-859 If there is not tlsConfiguration saved on disk, keep it as null + assertNull(activeDirectorySecurityRealm.getDomains().get(0).getTlsConfiguration()); + assertNull(activeDirectorySecurityRealm.getDomains().get(1).getTlsConfiguration()); + } } @@ -305,4 +338,27 @@ public void testCacheOptionAlwaysVisible() throws Exception { assertTrue(domElement != null); } + @Issue("SECURITY-859") + @LocalData + @Test + public void testReadResolveMultipleDomainsOneDomainEndToEnd() throws Exception { + ActiveDirectorySecurityRealm activeDirectorySecurityRealm = (ActiveDirectorySecurityRealm) jenkinsRule.jenkins.getSecurityRealm(); + // Check there is one domain + assertEquals(activeDirectorySecurityRealm.getDomains().size(), 1); + // Check domain + assertEquals(activeDirectorySecurityRealm.getDomains().get(0).getName(), AD_DOMAIN); + // Check bindName + assertEquals(activeDirectorySecurityRealm.getDomains().get(0).getBindName(), AD_MANAGER_DN); + // Check groupLookupStrategy + assertEquals(activeDirectorySecurityRealm.getGroupLookupStrategy(), GroupLookupStrategy.RECURSIVE); + // Check removeIrrelevantGroups + assertEquals(activeDirectorySecurityRealm.removeIrrelevantGroups,true); + // Check cache Size + assertEquals(activeDirectorySecurityRealm.getCache().getSize(),500); + // Check cache TTLS + assertEquals(activeDirectorySecurityRealm.getCache().getTtl(),1800); + // Check tlsConfiguration + assertEquals(activeDirectorySecurityRealm.getDomains().get(0).getTlsConfiguration(), TlsConfiguration.JDK_TRUSTSTORE); + } + }
src/test/java/hudson/plugins/active_directory/docker/TheFlintstonesTest.java+100 −7 modified@@ -27,6 +27,7 @@ import hudson.plugins.active_directory.ActiveDirectoryDomain; import hudson.plugins.active_directory.ActiveDirectorySecurityRealm; import hudson.plugins.active_directory.GroupLookupStrategy; +import hudson.util.Secret; import jenkins.model.Jenkins; import org.acegisecurity.AuthenticationServiceException; import org.acegisecurity.userdetails.UserDetails; @@ -36,12 +37,12 @@ import org.jenkinsci.test.acceptance.docker.DockerFixture; import org.jenkinsci.test.acceptance.docker.DockerRule; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; +import javax.naming.CommunicationException; import javax.naming.NamingException; import javax.servlet.ServletException; import java.io.IOException; @@ -65,6 +66,7 @@ import hudson.security.GroupDetails; import hudson.util.RingBufferLogHandler; +import org.jvnet.hudson.test.recipes.LocalData; /** * Integration tests with Docker @@ -84,8 +86,7 @@ public class TheFlintstonesTest { public String dockerIp; public int dockerPort; - @Before - public void setUp() throws Exception { + public void dynamicSetUp() throws Exception { TheFlintstones d = docker.get(); dockerIp = d.ipBound(3268); dockerPort = d.port(3268); @@ -109,14 +110,42 @@ public void setUp() throws Exception { } } + public void manualSetUp() throws Exception { + TheFlintstones d = docker.get(); + dockerIp = d.ipBound(3268); + dockerPort = d.port(3268); + + ActiveDirectorySecurityRealm activeDirectorySecurityRealm = (ActiveDirectorySecurityRealm) j.jenkins.getSecurityRealm(); + for (ActiveDirectoryDomain activeDirectoryDomain : activeDirectorySecurityRealm.getDomains()) { + activeDirectoryDomain.bindPassword = Secret.fromString(AD_MANAGER_DN_PASSWORD); + activeDirectoryDomain.servers = dockerIp + ":" + dockerPort; + } + + while(!FileUtils.readFileToString(d.getLogfile()).contains("custom (exit status 0; expected)")) { + Thread.sleep(1000); + } + UserDetails userDetails = null; + int i = 0; + while (i < MAX_RETRIES && userDetails == null) { + try { + userDetails = j.jenkins.getSecurityRealm().loadUserByUsername("Fred"); + } catch (AuthenticationServiceException e) { + Thread.sleep(1000); + } + i ++; + } + } + @Test public void simpleLoginSuccessful() throws Exception { + dynamicSetUp(); UserDetails userDetails = j.jenkins.getSecurityRealm().loadUserByUsername("Fred"); assertThat(userDetails.getUsername(), is("Fred")); } @Test public void simpleLoginFails() throws Exception { + dynamicSetUp(); try { j.jenkins.getSecurityRealm().loadUserByUsername("Homer"); } catch (UsernameNotFoundException e) { @@ -127,29 +156,33 @@ public void simpleLoginFails() throws Exception { @Issue("JENKINS-36148") @Test public void checkDomainHealth() throws Exception { + dynamicSetUp(); ActiveDirectorySecurityRealm securityRealm = (ActiveDirectorySecurityRealm) Jenkins.getInstance().getSecurityRealm(); ActiveDirectoryDomain domain = securityRealm.getDomain(AD_DOMAIN); assertEquals("NS: dc1.samdom.example.com.", domain.getRecordFromDomain().toString().trim()); } @Issue("JENKINS-36148") @Test - public void validateCustomDomainController() throws ServletException, NamingException, IOException { + public void validateCustomDomainController() throws ServletException, NamingException, IOException, Exception { + dynamicSetUp(); ActiveDirectoryDomain.DescriptorImpl adDescriptor = new ActiveDirectoryDomain.DescriptorImpl(); - assertEquals("OK: Success", adDescriptor.doValidateTest(AD_DOMAIN, dockerIp + ":" + dockerPort, null, AD_MANAGER_DN, AD_MANAGER_DN_PASSWORD).toString().trim()); + assertEquals("OK: Success", adDescriptor.doValidateTest(AD_DOMAIN, dockerIp + ":" + dockerPort, null, AD_MANAGER_DN, AD_MANAGER_DN_PASSWORD, null).toString().trim()); } @Issue("JENKINS-36148") @Test - public void validateDomain() throws ServletException, NamingException, IOException { + public void validateDomain() throws ServletException, NamingException, IOException, Exception { + dynamicSetUp(); ActiveDirectoryDomain.DescriptorImpl adDescriptor = new ActiveDirectoryDomain.DescriptorImpl(); - assertEquals("OK: Success", adDescriptor.doValidateTest(AD_DOMAIN, null, null, AD_MANAGER_DN, AD_MANAGER_DN_PASSWORD).toString().trim()); + assertEquals("OK: Success", adDescriptor.doValidateTest(AD_DOMAIN, null, null, AD_MANAGER_DN, AD_MANAGER_DN_PASSWORD, null).toString().trim()); } @Issue("JENKINS-45576") @Test public void loadGroupFromGroupname() throws Exception { + dynamicSetUp(); String groupname = "The Rubbles"; GroupDetails group = j.jenkins.getSecurityRealm().loadGroupByGroupname(groupname); assertThat(group.getName(), is("The Rubbles")); @@ -158,6 +191,7 @@ public void loadGroupFromGroupname() throws Exception { @Issue("JENKINS-45576") @Test public void loadGroupFromAlias() throws Exception { + dynamicSetUp(); // required to monitor the log messages, removing this line the test will fail List<String> logMessages = captureLogMessages(20); @@ -195,6 +229,65 @@ public synchronized void publish(LogRecord record) { return logMessages; } + // ReadResolve tlsConfiguration migration tests + + @LocalData + @Test + public void testSimpleLoginSuccessfulAfterReadResolveTlsConfigurationSingleDomain() throws Exception { + manualSetUp(); + UserDetails userDetails = j.jenkins.getSecurityRealm().loadUserByUsername("Fred"); + assertThat(userDetails.getUsername(), is("Fred")); + } + + @LocalData + @Test + public void testSimpleLoginFailsAfterReadResolveTlsConfigurationSingleDomain() throws Exception { + manualSetUp(); + try { + j.jenkins.getSecurityRealm().loadUserByUsername("Homer"); + } catch (UsernameNotFoundException e) { + assertTrue(e.getMessage().contains("Authentication was successful but cannot locate the user information for Homer")); + } + } + + @LocalData + @Test + public void testSimpleLoginSuccessAfterReadResolveTlsConfigurationMultipleDomainsOneDomain() throws Exception { + manualSetUp(); + UserDetails userDetails = j.jenkins.getSecurityRealm().loadUserByUsername("Fred"); + assertThat(userDetails.getUsername(), is("Fred")); + } + + @LocalData + @Test + public void testSimpleLoginFailsAfterReadResolveTlsConfigurationMultipleDomainsOneDomain() throws Exception { + manualSetUp(); + try { + j.jenkins.getSecurityRealm().loadUserByUsername("Homer"); + } catch (UsernameNotFoundException e) { + assertTrue(e.getMessage().contains("Authentication was successful but cannot locate the user information for Homer")); + } + } + + // TlsConfiguration tests + @LocalData + @Test + public void testSimpleLoginSuccessfulTrustingAllCertificates() throws Exception { + manualSetUp(); + UserDetails userDetails = j.jenkins.getSecurityRealm().loadUserByUsername("Fred"); + assertThat(userDetails.getUsername(), is("Fred")); + } + + @LocalData + @Test + public void testSimpleLoginFailsTrustingJDKTrustStore() throws Exception { + try { + manualSetUp(); + } catch (CommunicationException e) { + assertTrue(e.getMessage().contains("simple bind failed")); + } + } + @DockerFixture(id = "ad-dc", ports= {135, 138, 445, 39, 464, 389, 3268}, udpPorts = {53}, matchHostPorts = true) public static class TheFlintstones extends DockerContainer {
src/test/resources/hudson/plugins/active_directory/ActiveDirectorySecurityRealmTest/testReadResolveMultipleDomainsOneDomainEndToEnd.zip+0 −0 addedsrc/test/resources/hudson/plugins/active_directory/docker/TheFlintstonesTest/testSimpleLoginFailsAfterReadResolveTlsConfigurationMultipleDomainsOneDomain.zip+0 −0 addedsrc/test/resources/hudson/plugins/active_directory/docker/TheFlintstonesTest/testSimpleLoginFailsAfterReadResolveTlsConfigurationSingleDomain.zip+0 −0 addedsrc/test/resources/hudson/plugins/active_directory/docker/TheFlintstonesTest/testSimpleLoginFailsTrustingJDKTrustStore.zip+0 −0 addedsrc/test/resources/hudson/plugins/active_directory/docker/TheFlintstonesTest/testSimpleLoginSuccessAfterReadResolveTlsConfigurationMultipleDomainsOneDomain.zip+0 −0 addedsrc/test/resources/hudson/plugins/active_directory/docker/TheFlintstonesTest/testSimpleLoginSuccessfulAfterReadResolveTlsConfigurationSingleDomain.zip+0 −0 addedsrc/test/resources/hudson/plugins/active_directory/docker/TheFlintstonesTest/testSimpleLoginSuccessfulTrustingAllCertificates.zip+0 −0 added
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4- github.com/advisories/GHSA-2h95-4xw9-m68jghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2019-1003009ghsaADVISORY
- github.com/jenkinsci/active-directory-plugin/commit/520faf5bb1078d75e5fed10b7bf5ac6241fe2fc4ghsaWEB
- jenkins.io/security/advisory/2019-01-28/ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.