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,sample.user@coveros.com,mrcoe,developers
Test,User,test.user@coveros.com,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!

Leave a comment

Your email address will not be published. Required fields are marked *

X