How do I revert back to using IPA-issued Web & LDAP certs?

For IPA v4.6.x.

So you have an IPA installation with a CA and you decided you don’t want your users to have to install the IPA CA certificate(s) so instead you want to use certificates for the Web and LDAP using some known 3rd party issuer. Sure, that works fine. You’d do something like:

Install the 3rd party CA chain and update your IPA master:

# ipa-cacert-manage install /path/to/root.pem -t CT,,
# ipa-cacert-manage install intermediate.cert.pem -t CT,,
# ipa-certupdate

Install the 3rd-party provided server certificate. In this case I have it as two separate files, the public cert and the private key.

# ipa-server-certinstall --dirman-password password -w -d --pin '' server.cert.pem server.cert.key root.pem \

Great. IPA is working fine and my users don’t need to import the IPA CA.

Two years later…

My 3rd party certs are expiring soon and I don’t want to pay for new ones and I want to switch back to using IPA-issued certificates. We can use certmonger for that. This assumes that your CA is still up and functioning properly.

I’d start by backing up the two NSS databases. It is safest to do this offline (ipactl stop). You need to copy *.db from /etc/dirsrv/slapd-EXAMPLE-TEST and /etc/httpd/alias someplace safe, then restart the world (ipactl start).

First the web server:

# ipa-getcert request -d /etc/httpd/alias -n Server-Cert -K HTTP/`hostname` -D `hostname` -C /usr/libexec/ipa/certmonger/restart_httpd -p /etc/httpd/alias/pwdfile.txt

Edit /etc/httpd/conf.d/nss.conf and replace the value of NSSNickname with Server-Cert.

Wait a bit to be sure the cert is issued. You can run this to see the status:

# ipa-getcert list -d /etc/httpd/alias -n Server-Cert

Now the LDAP server:

# ipa-getcert request -d /etc/dirsrv/slapd-EXAMPLE-TEST -n Server-Cert -D `hostname` -K ldap/`hostname` -C "/usr/libexec/ipa/certmonger/restart_dirsrv EXAMPLE-TEST" -p /etc/dirsrv/slapd-EXAMPLE-TEST/pwdfile.txt

Similarly wait for it to be issued. To track the status:

# ipa-getcert list -d /etc/dirsrv/slapd-EXAMPLE-TEST -n Server-Cert

Once it is issued run:

# ipactl stop

Now edit /etc/dirsrv/slapd-EXAMPLE-TEST/dse.ldif. We could do this while the server is online but we need to restart anyway and your favorite editor is easier than ldapmodify. Replace the value of nsSSLPersonalitySSL with Server-Cert

Now restart the world:

# ipactl start

Connect to each port if you want to confirm that the certificate and chain are correct, e.g.

# openssl s_client -host `hostname` -port 443
depth=1 O = EXAMPLE.TEST, CN = Certificate Authority
verify return:1
depth=0 O = EXAMPLE.TEST, CN = ipa.example.test
verify return:1
Certificate chain
0 s:/O=EXAMPLE.TEST/CN=ipa.example.test
i:/O=EXAMPLE.TEST/CN=Certificate Authority
1 s:/O=EXAMPLE.TEST/CN=Certificate Authority
i:/O=EXAMPLE.TEST/CN=Certificate Authority

Setting up a Mac (OSX) as an IPA client

I periodically see people trying to setup a Mac running OSX as an IPA client. I don’t have one myself so can’t really provide assistance.

There is this guide which seems to be pretty thorough,

This upstream ticket also has some information on setting up a client, though it isn’t always directly related to simply configuring a client,

So I record this here so I know where to look later šŸ™‚

How do I get a certificate for my web site with IPA?

That’s a bit of a loaded question that begs additional questions:

  1. Is the web server enrolled as an IPA client?
  2. What format does the private key and certificate need to be in? (OpenSSL-style PEM, NSS, other?)

If the answer to question 1 is YES then you can do this on that client machine (to be clear, this first step can be done anywhere or in the UI):

$ kinit admin$ ipa service-add HTTP/

You can use certmonger to request and manage the certificate which includes renewals (bonus!).

If you are using NSS and let’s say mod_nss you’d do something like after creating the database and/or putting the password into /etc/httpd/alias/pwdfile.txt:

# ipa-getcert request -K HTTP/ -d /etc/httpd/alias -n MyWebCert -p /etc/httpd/alias/pwdfile.txt -D

Let’s break down what these options mean:

  • -K is the Kerberos principal to store the certificate with. You do NOT need to get a keytab for this service
  • -d the NSS database directory. You can use whatever you want but be sure the service has read access and SELinux permission access to it.
  • -n the NSS nickname. This is just a shortcut name to your cert, use what you want.
  • -p the path to the pin/password for the NSS database
  • -D creates a DNS SAN for the hostname This is current best practice.

You’ll also need to add the IPA cert chain to the NSS database using certutil.

If you are using OpenSSL and say mod_ssl you’d do something like:

# ipa-getcert request -K HTTP/ -k /etc/pki/tls/private/httpd.key -f /etc/pki/tls/certs/httpd.pem -D

Similar options as above but instad -f -d and -n:

  • -k path to the key file
  • -f path to the certificate file

To check on the status of your new request you can run:

# ipa-getcert list -n <numeric id that was spit out before>

It should be in status MONITORING.

If the answer to #1 is NO then you have two options: use certmonger on a different machine to generate the key and certificate and transfer them to the target or generate a CSR manually.

For the first case, using certmonger on a different machine, the steps are similar to the YES case.

Create a host and service for the web server:

$ kinit admin$ ipa host-add$ ipa service-add HTTP/

Now we need to grant the rights to the current machine to get certificates for the HTTP service of

$ ipa service-add-host --hosts <your current machine FQDN> HTTP/

Now run the appropriate ipa-getcert command above to match the format you need and check the status in a similar way.

Once it’s done you need to transfer the cert and key to the webserver.

Finally, if you want to get certificates on an un-enrolled system the basic steps are:

  1. Create a host entry and service as above
  2. Generate a CSR, see (or the next section)
  3. Submit that CSR per the above docs

If your webserver is not registered in DNS then you can use the –force option to host-add and service-add to force their creation.

This should pretty generically apply to all versions of IPA v4+, and probably to v3 as well.


Batch adding users

Doing bulk IPA operations from the command-line can be inefficient because each command requires a round trip. So a loop like this can be rather slow:

for line in $(cat /etc/passwd); do
        IFS=' '
        username=$(echo $line|cut -f1 -d:)
        password=$(echo $line|cut -f2 -d:)
        uid=$(echo $line|cut -f3 -d:)  
        gid=$(echo $line|cut -f4 -d:)
        ipa user-add $username --first=NIS --last=USER --password --gidnumber=$gid --uid=$uid --gecos=$gecos --homedir=$homedir --shell=$shell --setattr userpassword={crypt}$password

There is a round trip for every user.

The obvious way to improve this is to reduce the number of round trips by using the IPA batch command. Here is the skeleton of a program to read /etc/passwd. It lacks a whole ton of error checking and may be filled with errors but it should illustrate how the batch command works.

This will batch the creation of 50 users at a time.

from ipalib import api
from ipalib import errors
import sys

def add_batch_operation(command, *args, **kw):
        "method": command,
        "params": [args, kw],

def flush_batch_operation():
    if not batch_args:
        return None

    kw = {}

        return api.Command['batch'](*batch_args, **kw)
    except errors.CCacheError as e:


lineno = 0
batch_args = 0
count = 0
batch_args = list()
with open("/etc/passwd", "r") as passwd:
    for line in passwd:
        lineno += 1
            (login, password, uid, gid, gecos, homedir, shell) = \
        except ValueError as ve:
            print("Malformed line %d: %s" % (lineno, ve))

        if gecos:
                first, last = gecos.split(' ', 1)
            except ValueError:
                print("Unable to parse gecos line %d" % lineno)
            print("Missing gecos line %d" % lineno)

        params = [login]
        kw = {
            'givenname': first,
            'sn': last,
            'cn': gecos,
            'userpassword': '{crypt}' + password,
            'gecos': gecos,
            'homedirectory': homedir,
            'loginshell': shell,

        add_batch_operation('user_add', *params, **kw)
        count += 1

        if count % 50 == 0:
            print("%d entries" % count)
            results = flush_batch_operation()
            for result in results.get('results'):
                if result.get('error') != None:
            batch_args = list()


certmonger CA subsystem renewal

The CA subsystem certificate (OCSP, Audit, etc) are renewed directly against dogtag rather than being processed through IPA like the Apache and 389-ds server certificates are.

certmonger does the renewa by issuing a request like this:

GET /ca/ee/ca/profileSubmit?profileId=caServerCert&serial_num=5&renewal=true&xml=true&requestor_name=IPA

The serial number value comes from the current certificate being tracked by certmonger. Dogtag will generate its own CSR based on the template values currently in LDAP, cn=5,ou=ca,ou=requests,o=ipaca

Migration and User Private Groups

When adding an IPA user they are typically created with a User-Private Group (UPG). This is a group of the same name, with the same GID. It is treated specially in that it cannot have members and does not typically appears in group searches using the IPA API (unless the private option is included).

Migrating from another LDAP source, including another IPA server, does not create UPGs. There are a number of reasons for this:

  1. It can be expensive to be sure that no groups reference any given user
  2. What to do if one group cannot be made into a UPG.Ā  There is no interactive mode so it is either skip it, add it as a non-UPG or drop the group members and add it as a UPG.

We took the easy way out and don’t convert any. There is an RFE to be able to do this during migration.

This came up recently on the freeipa-users list and I thought about what it would take to convert a group back into a UPG.

My first solution was a rather compex set of ldapmodify operations.

One to update the group:

$ kinit admin
$ ldapmodify -Y GSSAPI
dn: cn=test,cn=groups,cn=accounts,dc=example,dc=com
changetype: modify
add: objectclass
objectClass: mepManagedEntry
add: mepManagedBy
mepManagedBy: uid=test,cn=users,cn=accounts,dc=example,dc=com
delete: objectclass
objectClass: ipausergroup
delete: objectclass
objectClass: groupofnames
delete: objectclass
objectClass: nestedgroup


And one to update the user:

$ ldapmodify -Y GSSAPI
dn: uid=test,cn=users,cn=accounts,dc=example,dc=com
changetype: modify
add: objectclass
objectClass: mepOriginEntry
add: mepManagedEntry
mepManagedEntry: cn=test,cn=groups,cn=accounts,dc=example,dc=com


This seemed cumbersome especially if there are a lot of groups to convert. It also doesn’t consider the case where a group has a member so is nonconvertible. An ObjectclassViolation LDAP error would be thrown in that case.

So I poked at the group-detach command and came up with this. If you drop this file,, into /usr/lib/python-*/site-packages/ipaserver/plugins and restart Apache you’ll have the group-attach command:

import six

from ipalib import Str
from ipalib.plugable import Registry
from .baseldap import (
from ipalib import _, ngettext
from ipalib import errors
from ipalib import output

register = Registry()

class group_attach(LDAPQuery):
    __doc__ = _('Attach a managed group to a user.')

    takes_parms = (
            doc=_('User to attach group to'),
            flags=['no_create', 'no_update', 'no_search'],

    has_output = output.standard_value
    msg_summary = _('Attached group "%(value)s" to user "%(value)s"')

    def execute(self, *keys, **options):
        This requires updating both the user and the group. We first need to
        verify that both the user and group can be updated, then we go
        about our work. We don't want a situation where only the user or
        group can be modified and we're left in a bad state.
        ldap = self.obj.backend

        group_dn = self.obj.get_dn(*keys, **options)
        user_dn = self.api.Object['user'].get_dn(*keys)

            user_attrs = ldap.get_entry(user_dn)
        except errors.NotFound:
            raise self.obj.handle_not_found(*keys)
        is_managed = self.obj.has_objectclass(
            user_attrs['objectclass'], 'mepmanagedentry'
        if (not ldap.can_write(user_dn, "objectclass") or
                not ldap.can_write(user_dn, "mepManagedEntry")
                and is_managed):
            raise errors.ACIError(
                info=_('not allowed to modify user entries')

        group_attrs = ldap.get_entry(group_dn)
        is_managed = self.obj.has_objectclass(
            group_attrs['objectclass'], 'mepmanagedby'
        if (not ldap.can_write(group_dn, "objectclass") or
                not ldap.can_write(group_dn, "mepManagedBy")
                and is_managed):
            raise errors.ACIError(
                info=_('not allowed to modify group entries')

        objectclasses = user_attrs['objectclass']
        if 'meporiginentry' in [x.lower() for x in objectclasses]:
            raise errors.ACIError(
                info=_('The user is already attached to a group')

        group_attrs = ldap.get_entry(group_dn)
        objectclasses = group_attrs['objectclass']
        if 'mepmanagedentry' in [x.lower() for x in objectclasses]:
            raise errors.ACIError(
                info=_('The group is already managed')
        if group_attrs.get('member'):
            raise errors.ACIError(
                info=_('The group has members')

        for objectclass in ('ipausergroup', 'groupofnames', 'nestedgroup'):
                i = objectclasses.index(objectclass)
            except ValueError:
                # this should never happen
            del objectclasses[i]


        group_attrs['mepManagedBy'] = user_dn
        group_attrs['objectclass'] = objectclasses

            user_attrs['mepManagedEntry'] = group_dn
        except ValueError:
            # Somehow the user isn't managed, let it pass for now. We'll
            # let the group throw "Not managed".

        return dict(
            value=pkey_to_value(keys[0], options),

This leaves some things to be desired, notably the exceptions are ACIError rather than something perhaps more relevant.

$ ipa group-detach test
Detached group "test" from user "test"

$ ipa group-attach test
Attached group "test" to user "test"

There be dragons. I have barely tested this, just enough to scratch the itch of my curiosity.

Developers should learn to love the IPA lite-server

If you’re trying to debug an issue in a plugin then the lite-server is for you. It has a number of advantages:

  • It runs in-tree which means you don’t need to commit, build code, re-install, etc
  • Or worse, avoid directly editing files in /usr/lib/python3.6/*
  • It is very pdb friendly
  • Auto-reloads modified python code
  • It doesn’t run as root

You’ll need two sessions to your IPA master. In one you run the lite-server via:

$ export KRB5CCNAME=~/.ipa/ccache
$ kinit admin
$ make lite-server

In the second we run the client. You’ll need to say which configuration to use:

$ export IPA_CONFDIR=~/.ipa

Now copy the installed configuration there:

$ cp /etc/ipa/default.conf ~/.ipa
$ cp /etc/ipa/ca.crt ~/.ipa

Edit ~/.ipa/default.conf and change the xmlrpc_uri to:


Now you can run your command locally:

$ kinit admin
$ PYTHONPATH=. python3 ./ipa user-show admin

And if something isn’t working right, stick pdb in ipaserver/plugins/ in the show pre_callbackĀ  command and re-run (notice that the lite-server picks up the change automatically):

$ PYTHONPATH=. python3 ./ipa user-show admin

And in the lite-server session:

> /home/rcrit/redhat/freeipa/ipaserver/plugins/
-> return dn


Frustrated rantings of a developer