Deploying FreeIPA

Marc Billow February 26, 2018


Before we begin, a bit about what FreeIPA actually is: FreeIPA, or Red Hat Identity Management, is a management interface and API built on top of several well-known, open source projects: 389 Directory Server, MIT Kerberos, DogTag for managing certificates and SSSD for client enrollment. The goal of the FreeIPA project is to create a cohesive, central, extensible, and easy to maintain user management and authentication system. FreeIPA provides an easy to use UI/UX for managing everything from user accounts, permissions, host based access controls to global sudo rules and certificate control. If you would like to play around with the interface, there is a public demo instance available.

Our Situation

For years Computer Science House has relied on LDAP and Kerberos for user management and single sign-on. The culture and communication within our organization and the services we provide revolve around these accounts, so it is essential that the infrastructure around them is solid. We will go more in-depth on this in the next section, but we also store information about a person’s membership in custom LDAP attributes, making LDAP a source-of-truth for many of the projects our members work on. This also presents the hurdle of properly handling ACLs to user data for service accounts used by these projects.

FreeIPA also supports an ongoing initiative within CSH to have fully redundant services between our data center and RIT’s datacenter across campus. csh-ds01 and csh-ds02 are both in our data center on different hypervisors and csh-ds03 is on a hypervisor in the secondary data center.

Handling Custom Attributes

As aforementioned, we store a lot of our membership data in LDAP. We have groups for Active Membership, Current Students, and even a group for those who have been taught how to use the 3D printers (for regulating access). Also, we store information like social media accounts, RIT usernames, and drink balances for our on-floor vending machines. Over the years, we have expanded out LDAP schema to hold values like these, so it was important that this data get carried over. FreeIPA comes with a built-in LDAP migration tool, yet before we could migrate this data we needed to make sure the schema was there to support it.

Extending the Schema

Because FreeIPA is 389 Server under-the-hood, it is possible to write LDIFs that add custom objectTypes with new attributeTypes. By default, in our setup, each user gets assigned two more objectTypes: ritStudent and cshMember. ritStudent has attributes for fields like majors, minors, graduation year, RIT username, etc. cshMember allows us to store drink balances, membership dates, external accounts, etc.

By default, FreeIPA schema gets loaded from /etc/dirsrv/slapd-{REALM}/schema. In this directory we defined both of the objectTypes and assigned them their respective attributeTypes. For example ritStudent has been included below:


dn: cn=schema

attributeTypes: ( 1.3.6.1.4.1.3319.8.501 NAME 'ritDn' SINGLE-VALUE EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch DESC 'RIT Account' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )

attributeTypes: ( 1.3.6.1.4.1.3319.8.502 NAME 'major' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch DESC 'RIT Major' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )

attributeTypes: ( 1.3.6.1.4.1.3319.8.503 NAME 'minor' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch DESC 'RIT Minor' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )

attributeTypes: ( 1.3.6.1.4.1.3319.8.504 NAME 'ritYear' SINGLE-VALUE EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch DESC 'RIT Year' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )



dn: cn=schema

objectClasses: ( 1.3.6.1.4.1.3319.8.500 NAME 'ritStudent' AUXILIARY MAY ( ritDn $ major $ minor $ ritYear ) )

Extending the UI/API

Once the attributes are added to LDAP, you need to modify the API and Web UI they exist as well as the values that they are meant to hold.

The API is written in Python and plugins are loaded from /usr/lib/python2.7/site-packages/ipalib/plugins/. Again, using ritStudent as an example:

from ipalib.plugins import user
from ipalib.parameters import Str
from ipalib import _

user.user.takes_params = user.user.takes_params + (
    Str('ritdn?',
        cli_name = 'ritdn',
        label = _('RIT Account'),
    ),
    Str('major*',
        cli_name = 'major',
        label = _('RIT Major'),
    ),
    Str('minor*',
        cli_name = 'minor',
        label = _('RIT Minor'),
    ),
    Str('rityear?',
        cli_name = 'rityear',
        label = _('RIT Year'),
    ),
)

Now that the CLI and API know about the attributes, we need to extend the Web UI to show the attributes we added. The frontend is written in Javascript and extensions are loaded from /usr/share/ipa/ui/js/plugins/ where each extension has a folder with a Javascript file in it. Example: /usr/share/ipa/ui/js/plugins/rit-student/rit-student.js

define([
        'freeipa/phases',
        'freeipa/user'
    ],
    function(phases, user_mod) {
        // helper function
        function get_item(array, attr, value) {
            for (var i = 0, l = array.length; i < l; i++) {
                if (array[i][attr] === value) return array[i];
            }
            return null;
        }
        var student_plugin = {};
        student_plugin.add_student_pre_op = function() {
            var facet = get_item(user_mod.entity_spec.facets, '$type', 'details');
            var section = get_item(facet.sections, 'name', 'identity');

            section.fields.push({
                name: 'ritdn',
                label: 'RIT Account'
            });

            section.fields.push({
                name: 'major',
                label: 'RIT Major',
                $type: 'multivalued'
            });

            section.fields.push({
                name: 'minor',
                label: 'RIT Minor',
                $type: 'multivalued'
            });

            section.fields.push({
                name: 'rityear',
                label: 'RIT Year'
            });

            return true;

        };
        phases.on('customization', student_plugin.add_student_pre_op);
        return student_plugin;
    });

These Python and Javascript plugins need to be replicated to each IPA server in order for the fields to be accessible through those servers. Schema though, is automatically replicated.

FreeRADIUS and Wireless

Computer Science House administers its own wireless network which uses WPA2 Enterprise authentication through RADIUS authentication over TLS. In the past we have used a custom script which used MD4 hashes stored in Kerberos and handled the MSCHAPv2 challenge response protocol manually. Though this solution worked, it was not well documented and difficult to troubleshoot. FreeIPA though has the ability to support cross-domain trusts with Active Directory out-of-the-box. We used this feature to have IPA generate the necessary hashes, store them in LDAP, and then gave FreeRADIUS’s service account the ability to view this field.

This greatly simplifies our FreeRADIUS configuration since we can just use the default LDAP module and point it at the ipaNtHash attribute of the user trying to authenticate.

Deployment

Since we use Puppet as our configuration management platform, we decided that to efficiently enroll our existing hosts, we would push out a manifest that installed the freeipa-client package and ran ipa-client-install for us with the proper parameters. The easiest way to do this, that I could think of, was to create a puppet account in FreeIPA and give the necessary permissions to enroll hosts and create host objects. (Both operations are necessary to enroll a new host from scratch.) Our Puppet environments are controlled by a Git repo using r10k with Heira and secrets are encrypted using Heira-Eyaml. We are able to automatically enroll hosts in our domain after: configuring the user, encrypting the password in the Heira config, and configuring the ipaclient puppet module. This allows for the rapid bootstrapping of a new server with a single Puppet run.


More blog posts