I recently needed to setup OpenLDAP for a client. We setup an entire pipeline, similar to SecureCI and wanted to tie all of the tools into one login system. The installation was pretty straitforward, but we wanted to ensure our tooling stack was secured, so we moved a bit beyond the basics. This is all done for an install on an Ubuntu machine, but the steps should work just fine for RHEL, just switch out aptitude for yum.
First things first, we installed OpenLDAP. For this walkthrough I’ll use a domain equal to dc=coveros,dc=com but for our client I used their company name instead
sudo apt-get update sudo apt-get install slapd ldap-utils
While this got OpenLDAP installed, we wanted to ensure everything was properly configured, so we re-configured the package
sudo dpkg-reconfigure slapd
I accepted all of the default responses, except changed the domain and organization name, and used the same administrator password that I had previously set.
Once I got that completed, I wanted some sort of web access to manage LDAP, so I installed PHPldapadmin.
sudo apt-get install phpldapadmin
Finally, I needed to fix some of the connection information.
sudo vim /etc/phpldapadmin/config.php
I set the server host to localhost
$servers->setValue('server','host','localhost');
And I fixed the base and bind_id to match our domain
$servers->setValue('server','base',array('dc=coveros,dc=com')); $servers->setValue('login','bind_id','cn=admin,dc=coveros,dc=com');
Before I could get my users all setup, I needed to fix a password issue. It’s something that was resolved with the latest PHP release, so I simply has to change line
$default = $this->getServer()->getValue('appearance','password_hash');
to
$default = $this->getServer()->getValue('appearance','password_hash_custom');
in /usr/share/phpldapadmin/lib/TemplateRender.php
Finally, I was able to access phpopenldap just by going to our machine through a browser at http://[MACHINE_IP]/phpldapadmin
Next, I wanted to setup our users under the dc=coveros,dc=com. The cn=admin user already existed, as it was created with openldap. I wanted an additional user cn=query to be able to perform all queries, without having to use an actual user, nor expose the admin user. I created this user outside of any groups, to ensure no additional permissions would be granted to this user in any apps LDAP was tied to.
I then manually setup 3 different groups under dc=coveros,dc=com: one for users, one for roles, and one for projects through the GUI. Then I populated the roles and projects manually with what I expected.
To create all of these users I wrote a simple script, that took in a csv file. The csv looked like the below
Sample,User,[email protected],mrcoe,developers Test,User,[email protected],mrcoe,testers
And my script looked like
#!/usr/bin/env bash # Make sure only root can run our script if [ "$(id -u)" != "0" ]; then echo "This script must be run as root" 1>&2 exit 1 fi #check our inputs if [ "$#" -ne 1 ]; then echo "Illegal number of parameters, provide an input file" exit 1 fi file=$1 if [ ! -f "$file" ]; then echo "Input file provided does not exist" exit 1 fi #determine the ldap groups groups=( $(ldapsearch -w [ADMIN_PASSWORD] -xD "cn=admin,dc=coveros,dc=com" -b "ou=groups,dc=coveros,dc=com" | grep "dn: cn=" | cut -d "=" -f 2 | cut -d "," -f 1) ) #determine the ldap projects projects=( $(ldapsearch -w [ADMIN_PASSWORD] -xD "cn=admin,dc=coveros,dc=com" -b "ou=projects,dc=coveros,dc=com" | grep "dn: cn=" | cut -d "=" -f 2 | cut -d "," -f 1) ) #loop through our file while read line; do #retrieve our variables firstname=`echo $line | cut -d ',' -f 1` firstname=${firstname,,}; firstname=${firstname^} lastname=`echo $line | cut -d ',' -f 2` lastname=${lastname,,}; lastname=${lastname^} email=`echo $line | cut -d ',' -f 3` email=${email,,} project=`echo $line | cut -d ',' -f 4` project=${project,,} group=`echo $line | cut -d ',' -f 5` group=${group,,} #create our cn/uid uid=${firstname:0:1}${lastname} uid=${uid,,} #determine new number gid=`ldapsearch -w [ADMIN_PASSWORD] -xD "cn=admin,dc=coveros,dc=com" -b "ou=users,dc=coveros,dc=com" | grep gid | cut -d " " -f 2 | sort -n | sed -n '$p'` ((gid++)) #check our project if [[ " ${projects[*]} " != *" $project "* ]] && [ "$project" != "" ]; then echo "Not a valid project for '$line'" continue fi #check our group if [[ " ${groups[*]} " != *" $group "* ]]; then echo "Not a valid group for '$line'" continue fi #create our user if [ -f "user.ldif" ]; then rm "user.ldif" fi echo "dn: uid=$uid,ou=users,dc=coveros,dc=com" >> user.ldif echo "cn: $firstname $lastname" >> user.ldif echo "givenName: $firstname" >> user.ldif echo "sn: $lastname" >> user.ldif echo "uid: $uid" >> user.ldif echo "uidNumber: $gid" >> user.ldif echo "gidNumber: $gid" >> user.ldif echo "homeDirectory: /home/$uid" >> user.ldif echo "mail: $email" >> user.ldif echo "objectClass: top" >> user.ldif echo "objectClass: posixAccount" >> user.ldif echo "objectClass: shadowAccount" >> user.ldif echo "objectClass: inetOrgPerson" >> user.ldif echo "objectClass: organizationalPerson" >> user.ldif echo "objectClass: person" >> user.ldif echo "loginShell: /bin/bash" >> user.ldif #actually create the yser ldapadd -w [ADMIN_PASSWORD] -cxD "cn=admin,dc=coveros,dc=com" -f user.ldif rm "user.ldif" #add our user to their group if [ -f "group.ldif" ]; then rm "group.ldif" fi echo "dn: cn=$group,ou=groups,dc=coveros,dc=com" >> group.ldif echo "changetype: modify" >> group.ldif echo "add: memberUid" >> group.ldif echo "memberUid: $uid" >> group.ldif #actually modify the user ldapmodify -w [ADMIN_PASSWORD] -xD "cn=admin,dc=coveros,dc=com" -f group.ldif rm "group.ldif" #add our user to their project if [ -f "project.ldif" ]; then rm "project.ldif" fi if [ "$project" != "" ]; then echo "dn: cn=$project,ou=projects,dc=coveros,dc=com" >> project.ldif echo "changetype: modify" >> project.ldif echo "add: memberUid" >> project.ldif echo "memberUid: $uid" >> project.ldif #actually modify the user ldapmodify -w [ADMIN_PASSWORD] -xD "cn=admin,dc=coveros,dc=com" -f project.ldif rm "project.ldif" fi done < $file
The ending structure looked like the below
dc=coveros,dc=com cn=admin cn=query ou=groups cn=admins cn=business analysts cn=developers cn=product owners cn=testers ou=projects ... ou=users ... uid=jenkins ...
You’ll also notice a jenkins user was created. I made the jenkins user part of the developers group, so that our automated builds/jobs could use this user for access, but not get too many permissions. Similar to the query user, this will be a shared user to help get stuff done! Passwords were purposefully not initially set for users, and full password management will be discussed in a future post, but for now, it can be left up to the user as an exercise using PWM
Finally, I wanted to ensure that phpopenldap was properly locked down. We created extra users with permissions, to ensure that the admin user wasn’t ever exposed, but I still wanted 2 factor authentication before anyone could access LDAP through the GUI. To do this, I setup http in front of my phpopenldap site, and had it reference the LDAP credentials. This meant that someone first needs to login using their LDAP credentials, and then they would STILL need the admin password in order to make any changes via the LDAP GUI. If someone has access to the machine, they could make changes that way, but that requires having their public key setup on the machine hosting LDAP, so I felt pretty secure on that end.
Setting up httpd was very straitforward.
I enabled the LDAP module
a2enmod authnz_ldap
And then simply added in an authentication block to my enabled site
<Location /> Order deny,allow Deny from All AuthName "Coveros DevOps Server" AuthType Basic AuthBasicProvider ldap #AuthzLDAPAuthoritative off AuthLDAPUrl ldap://localhost:389/ou=users,dc=coveros,dc=com?uid Require valid-user Satisfy any </Location>
Then restarting apache2 did the trick.
That was it, not too complex, but not too straitforward either. Good luck getting this setup, and be sure to drop some comments if you have questions!