Friday, April 15, 2016

We recently bought at work a CloudBees Jenkins Enterprise license and I wanted to tie the user accounts to a directory service. I first tried to set up Jenkins authentication via the AWS Directory Service, hoping it will be pretty much like talking to an Active Directory server. That proved to be impossible to set up, at least for me. I also tried to have an LDAP proxy server talking to the AWS Directory Service and have Jenkins authenticate against the LDAP proxy. No dice. I ended up setting up a good old-fashioned LDAP server and managed to get Jenkins working with it. Here are some of my notes.

OpenLDAP server setup

I followed this excellent guide from Digital Ocean. The server was an Ubuntu 14.04 EC2 instance in my case. What follows in terms of the server setup is taken almost verbatim from the DO guide.

Set the hostname

# hostnamectl set-hostname my-ldap-server

Edit /etc/hosts and make sure this entry exists:

LOCAL_IP_ADDRESS my-ldap-server.mycompany.com my-ldap-server

(it makes a difference that the FQDN is the first entry in the line above!)Make sure the following types of names are returned when you run hostname with different options:

Give OpenLDAP Access to the LDAP Server Key

# usermod -aG ssl-cert openldap

# chown :ssl-cert /etc/ssl/private/ldap_server.key

# chmod 640 /etc/ssl/private/ldap_server.key

Configure OpenLDAP to Use the Certificate and Keys

IMPORTANT NOTE: in modern versions of slapd, configuring the server is not done via slapd.conf anymore. Instead, you put together ldif files and run LDAP client utilities such as ldapmodify against the local server. The Distinguished Name of the entity you want to modify in terms of configuration is generally dn: cn=config but it can also be the LDAP database dn: olcDatabase={1}hdb,cn=config.

Create users (note the shadow attributes set to -1, which means they will be ignored):

# cat add_user_myuser.ldif

dn: uid=myuser,ou=mypeople,dc=mycompany,dc=com

objectClass: top

objectClass: account

objectClass: posixAccount

objectClass: shadowAccount

cn: myuser

uid: myuser

uidNumber: 2001

gidNumber: 201

homeDirectory: /home/myuser

loginShell: /bin/bash

gecos: myuser

userPassword: {crypt}x

shadowLastChange: -1

shadowMax: -1

shadowWarning: -1

# ./add_ldap_ldif.sh add_user_myuser.ldif

# ./set_ldap_pass.sh myuser MYPASS

Enable LDAPS

In /etc/default/slapd set:

SLAPD_SERVICES="ldap:/// ldaps:/// ldapi:///"

Enable debugging

This was a life saver when it came to troubleshooting connection issues from clients such as Jenkins or other Linux boxes. To enable full debug output, set olcLogLevel to -1:

# cat enable_debugging.ldif

dn: cn=config

changetype: modify

add: olcLogLevel

olcLogLevel: -1

# ldapadd -H ldapi:// -Y EXTERNAL -f enable_debugging.ldif

# service slapd force-reload

Configuring Jenkins LDAP authentication

Verify LDAPS connectivity from Jenkins to LDAP server

In my case, the Jenkins server is in the same VPC and subnet as the LDAP server, so I added an /etc/hosts entry on the Jenkins box pointing to the FQDN of the LDAP server so it can hit its internal IP address:

IP_ADDRESS_OF_LDAP_SERVER my-ldap-server.mycompany.com

I verified that port 636 (used by LDAPS) on the LDAP server is reachable from the Jenkins server:

# telnet my-ldap-server.mycompany.com 636

Trying IP_ADDRESS_OF_LDAP_SERVER...

Connected to my-ldap-server.mycompany.com.

Escape character is '^]'.

Set up LDAPS client on Jenkins server (StartTLSdoes not work w/ Jenkins LDAP plugin!)

# apt-get install ldap-utils

IMPORTANT: Copy over /etc/ssl/certs/ca_server.pem from LDAP server as /etc/ldap/ca_certs.pem on Jenkins server and then:

Jenkins settings for LDAP plugin

This took me a while to get right. The trick was to set the rootDN to dc=mycompany, dc=com and the userSearchBase to ou=mypeople (or to whatever name you gave to your users' organizational unit). I also tried to get LDAP groups to work but wasn't very successful.

At this point, I was able to create users on the LDAP server and have them log in to Jenkins. With CloudBees Jenkins Enterprise, I was also able to use the Role-Based Access Control and Folder plugins in order to create project-specific folders and folder-specific groups specifying various roles. For example, a folder MyProjectNumber1 would have a Developers group defined inside it, as well as an Administrators group and a Readers group. These groups would be associated with fine-grained roles that only allow certain Jenkins operations for each group.

I tried to have these groups read by Jenkins from the LDAP server, but was unsuccessful. Instead, I had to populate the folder-specific groups in Jenkins with user names that were at least still defined in LDAP. So that was half a win. Still waiting to see if I can define the groups in LDAP, but for now this is a workaround that works for me.

Allowing users to change their LDAP password

This was again a seemingly easy task but turned out to be pretty complicated. I set up another small EC2 instance to act as a jumpbox for users who want to change their LDAP password.

The jumpbox is in the same VPC and subnet as the LDAP server, so I added an /etc/hosts entry on the jumpbox pointing to the FQDN of the LDAP server so it can hit its internal IP address:

IP_ADDRESS_OF_LDAP_SERVER my-ldap-server.mycompany.com

I verified that port 636 (used by LDAPS) on the LDAP server is reachable from the jumpbox:

# telnet my-ldap-server.mycompany.com 636

Trying IP_ADDRESS_OF_LDAP_SERVER...

Connected to my-ldap-server.mycompany.com.

Escape character is '^]'.

# apt-get install ldap-utils

IMPORTANT: Copy over /etc/ssl/certs/ca_server.pem from LDAP server as /etc/ldap/ca_certs.pem on the jumpbox and then:

At this point, users were able to log in via ssh to the jumpbox using a pre-set LDAP password, and change their LDAP password by using the regular Unix 'passwd' command.

I am still fine-tuning the LDAP setup on all fronts: LDAP server, LDAP client jumpbox and Jenkis server. The setup I have so far allows me to have a single sign-on account for users to log in to Jenkins. Some of my next steps is to use the same user LDAP accounts for authentication and access control into MySQL and other services.