May 032012

This post will cover post-installation steps necessary to go from a completely unmanaged machine to a machine that is setup to be an LDAP server with a basic DIT. This will also setup phpldapadmin for web-based administration of your LDAP directory.

Note: I use nginx here simply because I find it easier to deal with. There’s no requirement for it and you may find it easier to use apache.

The post-install script used to setup the LDAP server is below. The reason this is used is because there are a lot of one time things that happen during the installation of an LDAP server and I have not yet been able to represent some of these events in bcfg2. The script below depends on some files that are hosted on another web server. I will provide the necessary files needed below.

The custom php packages are available from The reason for using these packages is that php-fpm is not available from the stock RHEL repositories or from EPEL. Since I am already familiar with php-fpm and I prefer to use it, I decided to simply download only the necessary packages rather than use the entire repository.


# ssl settings
SSLSUBJ="/C=Country Code/ST=Some State/L=City/O=Organization Name/OU=Organizational Unit Name/CN=${HOSTNAME}"

# misc settings


    #FIXME: remove when bcfg2 selinux policy works properly
    setenforce 0

    #FIXME: remove when bcfg2 selinux policy works properly
    setenforce 1

    echo -n "Installing custom php packages for phpldapadmin..."
    yum -y --nogpgcheck install ${RPMS} >/dev/null
    # FIXME: update the kernel (kernel panics when not done here)
    yum -y update kernel >/dev/null
    echo "done"

    /usr/bin/openssl req -batch -new -x509 -nodes \
        -subj "${SSLSUBJ}" \
        -out ${WEBCERT} \
        -keyout ${WEBKEY} -days 3600 >/dev/null
    /usr/bin/openssl req -batch -new -x509 -nodes \
        -subj "${SSLSUBJ}" \
        -out ${SLAPDCERT} \
        -keyout ${SLAPDKEY} -days 3600 >/dev/null

    cacertdir_rehash /etc/openldap/cacerts

    # setup ldap admin password
    echo -n "Please enter a new ldap admin password: "
    read -s PASSWD
    # get bcfg2 password
    echo -n "Please enter the bcfg2 password (can be found in /etc/bcfg2.conf on an existing client): "
    read -s BCFG2PASSWD

    if [ -x /usr/sbin/slappasswd ]
        SLAPPASSWD=$(/usr/sbin/slappasswd -s ${PASSWD})
        echo "Failed to find slappasswd. Aborting."
        exit 1

    /usr/bin/curl -o ${LDAPDIR}/fix-admin-account.ldif ${LDIFDIR}/fix-admin-account.ldif
    /usr/bin/curl -o ${LDAPDIR}/new-ldap-setup.ldif ${LDIFDIR}/new-ldap-setup.ldif
    /usr/bin/curl -o ${LDAPDIR}/base.ldif ${LDIFDIR}/base.ldif
    sed -i "s|PWREPLACE|${SLAPPASSWD}|" ${LDAPDIR}/fix-admin-account.ldif ${LDAPDIR}/new-ldap-setup.ldif
    # this seems wrong. if someone knows how to do this better, please inform me.
    echo "olcRootPW: ${SLAPPASSWD}" >> /etc/openldap/slapd.d/cn=config/olcDatabase={0}config.ldif
    /bin/cp /usr/share/openldap-servers/DB_CONFIG.example /var/lib/ldap/DB_CONFIG
    chown -R ldap. /var/lib/ldap
    /sbin/service slapd start && sleep 1 # FIXME: how do you do this properly?
    ldapadd -w ${PASSWD} -x -D "cn=config" -f ${LDAPDIR}/fix-admin-account.ldif
    ldapadd -w ${PASSWD} -x -D "cn=admin,cn=config" -f ${LDAPDIR}/new-ldap-setup.ldif
    ldapadd -w ${PASSWD} -x -D "cn=Manager,dc=uh,dc=edu" -f ${LDAPDIR}/base.ldif

    /usr/bin/curl -o ${LDAPDIR}/sudo-index.ldif ${LDIFDIR}/sudo-index.ldif
    cp /usr/share/doc/$(rpm -q sudo --qf "%{NAME}"-"%{VERSION}")/schema.OpenLDAP /etc/openldap/schema/sudo.schema
    restorecon -F -R -v /etc/openldap/schema
    mkdir ${LDAPDIR}/sudo-ldap
    echo "include /etc/openldap/schema/sudo.schema" > ${LDAPDIR}/sudo-ldap/sudoschema.conf
    slapcat -f ${LDAPDIR}/sudo-ldap/sudoschema.conf -F /tmp \
            -n0 -s "cn={0}sudo,cn=schema,cn=config" > ${LDAPDIR}/sudo-ldap/sudo-tmp.ldif
    sed -i 's/{0}sudo/sudo/' ${LDAPDIR}/sudo-ldap/sudo-tmp.ldif
    head -n-8 ${LDAPDIR}/sudo-ldap/sudo-tmp.ldif > ${LDAPDIR}/sudo-ldap/sudo.ldif
    echo -e "\n$(cat ${LDAPDIR}/sudo-index.ldif)" >> ${LDAPDIR}/sudo-ldap/sudo.ldif # add in our sudo index
    rm ${LDAPDIR}/sudo-index.ldif
    ldapadd -w ${PASSWD} -x -D "cn=admin,cn=config" -f ${LDAPDIR}/sudo-ldap/sudo.ldif

    cp /usr/share/doc/$(rpm -q autofs --qf "%{NAME}"-"%{VERSION}")/autofs.schema /etc/openldap/schema/autofs.schema
    restorecon -F -R -v /etc/openldap/schema
    mkdir ${LDAPDIR}/autofs
    echo "include /etc/openldap/schema/core.schema" > ${LDAPDIR/autofs/autofs.conf
    echo "include /etc/openldap/schema/cosine.schema" >> ${LDAPDIR/autofs/autofs.conf
    echo "include /etc/openldap/schema/autofs.schema" >> ${LDAPDIR/autofs/autofs.conf
    slapcat -f ${LDAPDIR}/autofs/autofs.conf -F /tmp \
            -n0 -s "cn={2}autofs,cn=schema,cn=config" > ${LDAPDIR}/autofs/autofs-tmp.ldif
    sed -i 's/{2}autofs/autofs/' ${LDAPDIR}/autofs/autofs-tmp.ldif
    head -n-8 ${LDAPDIR}/autofs/autofs-tmp.ldif > ${LDAPDIR}/autofs/autofs.ldif
    ldapadd -w ${PASSWD} -x -D "cn=admin,cn=config" -f ${LDAPDIR}/autofs/autofs.ldif

    while true; do
        echo -n "Is this machine a master or a slave? [m/s] "
        read status
        case $status in
                /usr/bin/curl -o ${LDAPDIR}/olcaccess.ldif ${LDIFDIR}/olcaccess.ldif
                /usr/bin/curl -o ${LDAPDIR}/syncprov-module.ldif ${LDIFDIR}/syncprov-module.ldif
                /usr/bin/curl -o ${LDAPDIR}/syncprov.ldif ${LDIFDIR}/syncprov.ldif
                ldapmodify -w ${PASSWD} -D "cn=admin,cn=config" -f ${LDAPDIR}/olcaccess.ldif
                ldapmodify -w ${PASSWD} -D "cn=admin,cn=config" -f ${LDAPDIR}/syncprov-module.ldif
                ldapadd -w ${PASSWD} -D "cn=admin,cn=config" -f ${LDAPDIR}/syncprov.ldif
                # grab master SSL certificate
                /usr/bin/curl -o ${SLAPDMASTERCERT} ${HTTPDIR}/slapd-master.crt
                cacertdir_rehash /etc/openldap/cacerts

                /usr/bin/curl -o ${LDAPDIR}/olcaccess-slave.ldif ${LDIFDIR}/olcaccess-slave.ldif
                /usr/bin/curl -o ${LDAPDIR}/syncrepl.ldif ${LDIFDIR}/syncrepl.ldif
                ldapmodify -w ${PASSWD} -D "cn=admin,cn=config" -f ${LDAPDIR}/olcaccess-slave.ldif
                ldapmodify -w ${PASSWD} -D "cn=admin,cn=config" -f ${LDAPDIR}/syncrepl.ldif
                echo "Invalid response."

    /usr/sbin/bcfg2 -vqe -S https://bcfg2.server:6789 -x ${BCFG2PASSWD} --ca-cert=/etc/ -r packages
    /usr/sbin/bcfg2 -vqer packages

mkdir -p ${LDAPDIR}
echo "Setup complete. Please reboot."

Here are the accompanying ldif files needed.


# Set password for cn=admin,cn=config
dn: olcDatabase={0}config,cn=config
changetype: modify
replace: olcRootPW
replace: olcRootDN
olcRootDN: cn=admin,cn=config


# create modules area
dn: cn=module,cn=config
objectClass: olcModuleList
cn: module{0}
olcModulePath: /usr/lib64/openldap

# set access for the monitor db.
dn: olcDatabase={1}monitor,cn=config
changetype: modify
replace: olcAccess
olcAccess: {0}to * by dn.base="cn=Manager,dc=yourcompany,dc=com" read by * none

# change LDAP domain, password and access rights.
dn: olcDatabase={2}bdb,cn=config
changetype: modify
replace: olcSuffix
olcSuffix: dc=yourcompany,dc=com
replace: olcRootDN
olcRootDN: cn=Manager,dc=yourcompany,dc=com
replace: olcRootPW

# setup SSL
dn: cn=config
replace: olcTLSCertificateKeyFile
olcTLSCertificateKeyFile: /etc/pki/tls/private/slapd.key
replace: olcTLSCertificateFile
olcTLSCertificateFile: /etc/openldap/cacerts/slapd.crt
replace: olcTLSCipherSuite
olcTLSCipherSuite: HIGH:MEDIUM:-SSLv2


# setup basic tree
dn: dc=yourcompany,dc=com
dc: uh
objectClass: top
objectClass: domain

dn: ou=People,dc=yourcompany,dc=com
ou: People
objectClass: top
objectClass: organizationalUnit

dn: ou=Group,dc=yourcompany,dc=com
ou: Group
objectClass: top
objectClass: organizationalUnit

dn: cn=replicator,dc=yourcompany,dc=com
cn: replicator
objectClass: organizationalRole
objectClass: simpleSecurityObject
objectClass: top
description: LDAP replication user
userPassword: changeme


# add sudo index
dn: olcDatabase={2}bdb,cn=config
changetype: modify
add: olcDbIndex
olcDbIndex: sudoUser eq

These can be changed to match your needs. In this case, anyone in the group cn=ldapadmin,ou=yourorganizationalunit,dc=yourcompany,dc=com is given full access to the LDAP directory (UPDATE: Please note that the ldapadmin cn is a groupOfNames objectClass [_not_ a posixGroup]).

dn: olcDatabase={2}bdb,cn=config
changetype: modify
add: olcAccess
olcAccess: {0}to * by dn.base="cn=replicator,dc=yourcompany,dc=com" read by * break
olcAccess: {1}to * by group.exact="cn=ldapadmin,ou=yourorganizationalunit,dc=yourcompany,dc=com" write by * break
olcAccess: {2}to attrs=userPassword by self write by anonymous auth by * none
olcAccess: {3}to attrs=shadowLastChange by self write by * read
olcAccess: {4}to * by * read


# setup syncprov module
dn: cn=module{0},cn=config
changetype: modify
add: olcModuleLoad
olcModuleLoad: {1}syncprov

You will want to modify these settings according to your replication needs.


dn: olcOverlay={0}syncprov,olcDatabase={2}bdb,cn=config
objectClass: olcSyncProvConfig
olcOverlay: {0}syncprov
olcSpCheckpoint: 100 10
olcSpSessionlog: 100


dn: olcDatabase={2}bdb,cn=config
changetype: modify
add: olcAccess
olcAccess: {0}to * by group.exact="cn=ldapadmin,ou=yourorganizationalunit,dc=yourcompany,dc=com" write by * break
olcAccess: {1}to attrs=userPassword by self write by anonymous auth by * none
olcAccess: {2}to * by * read


dn: olcDatabase={2}bdb,cn=config
changetype: modify
add: olcSyncrepl
olcSyncrepl: {0}rid=000 provider=ldaps://ldap-master-server searchbase=dc=yourcompany,dc=com type=refreshAndPersist retry="5 5 300 +" bindmethod=simple binddn="cn=re
plicator,dc=yourcompany,dc=com" credentials="changeme" tls_cacertdir=/etc/openldap/cacerts                                                                              -

Here are the relevant bits from the ldap bundle in the bcfg2 repository

<Bundle name='ldap'>
        <Group name='ldap-server'>
                <BoundPath name='/etc/openldap/cacerts/slapd.crt' type='permissions' owner='ldap' group='ldap' perms='0600'/>
                <BoundPath name='/etc/pki/tls/private/slapd.key' type='permissions' owner='ldap' group='ldap' perms='0600'/>
                <Package name='ldapvi'/>
                <Package name='openldap-clients'/>
                <Package name='openldap-servers'/>
                        <Path name='/etc/sysconfig/ldap'/>
                        <BoundPath name='/etc/openldap/slapd.d' type='directory' owner='ldap' group='ldap' perms='0700'/>

                <Service name='slapd'/>

                <!-- phpLDAPadmin settings -->
                <Package name='php'/>
                        <BoundPath name='/var/lib/php/session' type='directory' owner='root' group='nginx' perms='0770'/>
                <Package name='php-fpm'/>
                        <Path name='/etc/php-fpm.d/www.conf'/>
                <Package name='php-ldap'/>
                <Package name='nginx'/>
                <Package name='phpldapadmin'/>
                <Service name='php-fpm'/>
                <Service name='nginx'/>
                <Path name='/etc/nginx/conf.d/phpldapadmin.conf'/>
                <Path name='/etc/openldap/ldap.conf'/>
                <Path name='/etc/phpldapadmin/config.php'/>
                <BoundPath name='/var/www/html/phpldapadmin' type='symlink' to='/usr/share/phpldapadmin/htdocs'/>
                <Path name='/usr/share/phpldapadmin/templates/creation/custom_uh.xml'/>

The /etc/sysconfig/ldap file needs to be modified to allow LDAPS by uncommenting SLAPD_LDAPS=yes. In /etc/php-fpm.d/www.conf, you need to make sure the user/group are set to nginx (if you are using nginx as your web server).

My nginx configuration for /etc/nginx/conf.d/phpldapadmin.conf looks like this.

server {
        listen          80;
        server_name     ldap-server-hostname;
        rewrite         ^/(.*) https://ldap-server-hostname/$1 permanent;

server {
        listen                  443; # listen also for IPv4 traffic on "regular" IPv4 sockets
        server_name             ldap-server-hostname;
        access_log              /var/log/nginx/ssl-access.log;
        error_log               /var/log/nginx/ssl-error.log;
        root                    /var/www/html/phpldapadmin;

        ssl                     on;
        ssl_certificate         /etc/pki/tls/certs/phpldapadmin.crt;
        ssl_certificate_key     /etc/pki/tls/private/phpldapadmin.key;

        index           index.php index.html;

        location ~ \.php$ {
                fastcgi_pass    localhost:9000;
                fastcgi_index   index.php;
                fastcgi_param   SCRIPT_FILENAME $document_root$fastcgi_script_name;
                include         fastcgi_params;
                fastcgi_param   HTTPS on;

I needed the following lines in /etc/openldap/ldap.conf to get phpldapadmin working properly.

URI             ldaps://localhost/
TLS_CACERTDIR   /etc/openldap/cacerts
TLS_REQCERT     never

Lastly, you will need to modify /etc/phpldapadmin/config.php with appropriate values for your site.

 Posted by at 13:16
Aug 262011

Recently, while trying to resolve a bug in Bcfg2, I ran into a situation which can be summed up by the following:

Python 2.7.1 (r271:86832, Mar 26 2011, 11:26:21)
[GCC 4.4.5] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import os, stat
>>> dev = os.makedev(1, 3)
>>> mode = stat.S_IFCHR | 0777
>>> print(mode)
>>> os.mknod('test', mode, dev)
>>> os.stat('test')
posix.stat_result(st_mode=8685, st_ino=1148358, st_dev=12L, st_nlink=1, st_uid=0, st_gid=0, st_size=0, st_atime=1314372451, st_mtime=1314372451, st_ctime=1314372451)

Above, you can see that the mode specified ends up being different than the mode which is set by os.mknod. Instead of a character device with permissions of 0777, I was ending up with permissions of 0755. If you follow the link, you will find no documentation mentioning the umask of the running process in the mknod section. However, you can search around the page and realize that the umask of the running process is masked out for other methods.

The inconsistency arises due to the implementation of mknod used by Python. For instance, if you run the above code on Windows under Cygwin, it does the Right Thing ™. This was my clue that there was something about the implementation that was off. Sure enough, after committing a simple fix, the problem disappeared.

I think this is simply a documentation issue, but I was unable to find any information on the problem while searching around. Hopefully this post will save someone from wasting a ton of time on the same issue.

 Posted by at 20:05
Jan 262011

I was recently setting up DBStats for a Bcfg2 installation and was having some serious performance issues when a client was uploading statistics to the server.['2.6.18-194.26.1.el5']['group:rpm', 'group:linux', 'group:redhat', 'group:redhat-5Server', 'group:redhat-5', 'group:x86_64']
Generated config for in 0.044s
Handled 1 events in 0.000s
Client reported state clean
Imported data for in 139.942095041 seconds

This is drastically slower than normal. So, I remounted the sqlite database on a ramdisk.

# losetup /dev/loop0 /bcfg2/bcfg2.sqlite
# mount -t ramfs /dev/loop0 /bcfg2/
# mount | grep ramfs
/dev/loop0 on /bcfg2 type ramfs (rw)

Here is the time it took once I moved the sqlite database to a ramdisk.['2.6.18-194.26.1.el5']['group:rpm', 'group:linux', 'group:redhat', 'group:redhat-5Server', 'group:redhat-5', 'gr
Generated config for in 0.074s
Handled 1 events in 0.000s
Client reported state clean
Imported data for in 1.16791296005 seconds

That’s faster by a factor of almost 120! As you can see, something is very odd with the performance hit we are taking when using an ext4 filesystem. Just for comparison, I created an ext3 partition to hold the sqlite database.

# mount | grep foo
/dev/loop1 on /foo type ext3 (rw)
# ls /foo/

Here is the same client update again when using ext3 to hold the sqlite database.['2.6.18-194.26.1.el5']['group:rpm', 'group:linux', 'group:redhat', 'group:redhat-5Server', 'group:redhat-5', 'gr
Generated config for in 0.037s
Handled 1 events in 0.000s
Client reported state clean
Imported data for in 1.60297989845 seconds

I was finally able to track this down to a change in the default kernel configuration used by Ubuntu for ext4 filesystems. The change is detailed at Ubuntu apparently decided it was a good idea to turn on barriers by default in 10.04 (Lucid). Luckily, I was able to remount the ext4 partition without barriers (-o barrier=0) and the performance dropped back down to something more reasonable.['2.6.18-194.26.1.el5']['group:rpm', 'group:linux', 'group:redhat', 'group:redhat-5Server', 'group:redhat-5', 'gr
Generated config for in 0.038s
Handled 1 events in 0.000s
Client reported state clean
Imported data for in 6.47736501694 seconds

That’s still much slower than ext3, but it’s at least acceptable in this particular case.

While I can understand the reasoning behind changing something like this, it does not appear to be a good idea to drastically reduce the performance of a LTS release without at least warning people VERY LOUDLY.

More information about this can be found at

 Posted by at 09:27
Nov 092010

The new Bcfg2 VPS is finally setup and active. After a little bit of tweaking, I was able to get the documentation building from the master git branch every five minutes. The current setup is such that the documentation for the latest stable version of Bcfg2 can be found at while the latest development branch documentation is at

I plan on publishing the configuration for the web server soon, however, I want to do this at the same time as the common Bcfg2 repository so that I can finally resolve both these issues. I’m also thinking that it will probably be a good idea to create a debian repository on the VPS so that we can automate the building of packages. Currently our mirror at is very out of date.

My goal is to try and do most of this during the next code sprint. I have found that these types of things are difficult to work on unless I set a specific time. I am hoping that by next year we will finally have a real working example of a live Bcfg2 repository for people to see and use. I think that having these examples will lower the barrier significantly as new users are often tripped up by minor mistakes which can be mitigated more easily if you begin from a working example.

That’s all for now. More to come once I get started working on this.

 Posted by at 20:54
Oct 262010

I have just started creating a new sample Bcfg2 repository on github. This post details the strategy used in this repository in order to make it easy to pull in updates and merge them seamlessly with your running Bcfg2 instance.

The primary goal for this repository is to be nondestructive when someone tries to pull in the changes from upstream. However, in order to do this, there is a slightly more complicated repository structure involved.

The first thing you’ll probably want to do is grab a copy of the repository:

git clone git://

As you inspect the repository, you’ll notice that I have tried to make use of Xinclude in order to prevent overwriting custom repository files. The idea is that you will have 3 separate files (file.xml, upstreamfile.xml, and localfile.xml). The basic format for file.xml will be a comment with information about the purpose of the file. Then it will xinclude localfile.xml and have a comment containing the xinclude for upstreamfile.xml. The reasoning behind this is that you will most often have slight changes in your local repository from what is contained upstream. Therefore, you will most likely populate localfile.xml and merge in any changes from upstreamfile.xml manually.

This appears to be working out well so far (although I haven’t really added enough content yet). I am also considering adding paths to default configuration files (e.g. ssh). I am thinking about populating them using group-specific files so that they can be overridden by adding a group file with a higher priority. I am also considering possibly adding a separate layout under Cfg/TCheetah/TGenshi and using altsrc to bind the entries from the Bundles.

I’m hoping this all works out and any comments/criticisms are welcome. I know that the useful examples out there are sparse and widespread. I’m hoping that we can get something together which allows people to collaborate on useful configurations easily so that the initial barrier to using Bcfg2 is decreased.

 Posted by at 17:39
Aug 252010

While most of this is covered in the Bcfg2 docs, people still ask questions from time to time about writing client tools. In this post, I will cover the answer to a specific question that was posted to the mailing list recently.

We would like to change how ConfigFiles are copied on the client. This
means rewriting the InstallConfigFile method in the POSIX client plugin.
I wanted to get feed back how best to go about doing this. Would it make
sense to create a new Plug-in or modify the current one?

This post will implement a simple client tool which subclasses the POSIX client tool driver and replaces the InstallConfigFile method with a custom method.

The first step is to create the new client tool:

[root@bcfg2] ~ # cat bcfg2/src/lib/Client/Tools/
import Bcfg2.Client.Tools
import Bcfg2.Options

class myPOSIX(Bcfg2.Client.Tools.POSIX.POSIX):
    name = 'myPOSIX'
    __execs__ = ['/bin/true']
    conflicts = ['POSIX']
    __handles__ = [('Path', 'file')]

    # Redefine InstallConfigFile here
    def InstallConfigFile(self, entry):
        return True

All this simple client tool does is redefine the InstallConfigFile method from Everything else is the same. This is extremely useful if you find the behavior of a client tool useful, but wish to redefine how a particular method is implemented.

It is also worth noting that if you have a case where you need to augment something, you might check with others either on the bcfg2-dev mailing list or in #bcfg2 on Freenode as they may have run into similar issues. We are more than willing to accept useful code upstream.

So, getting back to the client tool, if we go ahead and run this with the following Path specified:

[root@bcfg2] /var/lib/bcfg2 # cat Bundler/foo.xml
<Bundle name='foo'>
    <Path name='/root/foo'/>

[root@bcfg2] /var/lib/bcfg2 # cat Cfg/root/foo/foo

then we get the following:

[root@bcfg2] ~ # bcfg2 -qI


@@ -1,1 +1,2 @@


Install Path /root/foo: (y/N): y

[root@bcfg2] ~ # cat /root/foo
cat: /root/foo: No such file or directory

which is exactly what we expect since we replaced the Install method with what amounts to a noop method. This simple client tool was just used for illustrative purposes. If you were actually implementing this, it would obviously normally be something completely different (and more than likely useful).

That concludes this simple post outlining the basics of modifying existing Bcfg2 client tools.

 Posted by at 21:07