Jenkins has a very popular Active directory plugin to use Active Directory for authentication. Before the version 2.5, if you lost connectivy to your Active Directory you were stuck. The only solution was to temporary disable security. 2.5 version bring a feature called fall-back user. I wasn't able to use it, probably because I didn't understand how the feature works. So as one of Amazon's principle says: it's time to dive deep!
The feature was developped after this improvement 39065. The issue has been fixed by a nice commit of Felix Belzunce Arcos. At first glance, we can see:
- an interesting call to updatePasswordInJenkinsInternalDatabase. This method of
ActiveDirectoryUnixAuthenticationProvider.java is called by
retrieveUser
. It means that each time you authenticate a user, the password in internal database is updated if it is the internal user . - if the
retrieveUser
catch ajavax.naming.NamingException
jenkins try to authenticate the user against the internal database.
When starting a fresh Jenkins installation we get empty folder users:
# ls -l /var/lib/jenkins/users/
total 0
My jenkins instance is configured to have a fall-back user (thebestuser). After first login:
# ls -l /var/lib/jenkins/users/
total 4
drwx------. 2 jenkins jenkins 24 14 juin 12:01 thebestuser_2536622897412429280
-rw-r--r--. 1 jenkins jenkins 319 14 juin 12:01 users.xml
Look at the content:
# grep -A 1 -B 1 password /var/lib/jenkins/users/hebestuser_2536622897412429280/config.xml
<hudson.security.HudsonPrivateSecurityRealm_-Details>
<passwordHash>#jbcrypt:$2a$10$WZsuWs.45fdsfsc6d6q465d4sq64fs6q/45dsqdqWSDSDRG</passwordHash>
</hudson.security.HudsonPrivateSecurityRealm_-Details>
So Jenkins users database is just XML files on the disk. I try to simulate the connexion loss by a simple iptable rules.
firewall-cmd --direct --add-rule ipv4 filter OUTPUT 0 -p tcp -m tcp --dport=389 -j DROP
I tested it but it failed! Here is an extract of the jenkins.log
juin 14, 2019 12:17:29 PM hudson.plugins.active_directory.ActiveDirectorySecurityRealm$DescriptorImpl bind
AVERTISSEMENT: Failed to bind to 172.30.4.10:389
javax.naming.CommunicationException: 172.30.4.10:389 [Root exception is java.net.SocketTimeoutException: connect timed out]
at com.sun.jndi.ldap.Connection.<init>(Connection.java:228)
at com.sun.jndi.ldap.LdapClient.<init>(LdapClient.java:137)
at com.sun.jndi.ldap.LdapClient.getInstance(LdapClient.java:1609)
at com.sun.jndi.ldap.LdapCtx.connect(LdapCtx.java:2749)
at com.sun.jndi.ldap.LdapCtx.<init>(LdapCtx.java:319)
at com.sun.jndi.ldap.LdapCtxFactory.getUsingURL(LdapCtxFactory.java:192)
at com.sun.jndi.ldap.LdapCtxFactory.getLdapCtxInstance(LdapCtxFactory.java:151)
at hudson.plugins.active_directory.ActiveDirectorySecurityRealm$DescriptorImpl.bind(ActiveDirectorySecurityRealm.java:668)
at hudson.plugins.active_directory.ActiveDirectorySecurityRealm$DescriptorImpl.bind(ActiveDirectorySecurityRealm.java:599)
at hudson.plugins.active_directory.ActiveDirectoryUnixAuthenticationProvider$1.call(ActiveDirectoryUnixAuthenticationProvider.java:359)
at hudson.plugins.active_directory.ActiveDirectoryUnixAuthenticationProvider$1.call(ActiveDirectoryUnixAuthenticationProvider.java:342)
at com.google.common.cache.LocalCache$LocalManualCache$1.load(LocalCache.java:4767)
at com.google.common.cache.LocalCache$LoadingValueReference.loadFuture(LocalCache.java:3568)
It doesn't work because the LDAP layer throws a CommunicationException not a Naming Exception. Jenkins improvement to come?