Talos is our authentication, DNS, and DHCP server. It hosts an LDAP database used to serve user information and credentials, the latter used by a Kerberos administration and KDC server it also serves. It is authoritative over the cslabs.clarkson.edu, 128.153.144/23, and 128.153.146/24 zones in DNS and DHCP, and is responsible for certain BOOTP information (like declaring the PXE server--Metapod). As the zone DNS server, it is necessarily at 128.153.145.3, the address which OIT expects to delegate DNS. It is managed by Ansible.

Setup

Start with a minimal installation of Debian Jessie, and proceed to install some useful packages; e.g., htop, vim, sudo. This should be enough to get through the next few steps.

Setting up firewall and other stuff

Setting up LDAP

Before You Begin

If you'd like or are in a hurry, you can skip to Installation below. This may contain some valuable information for setup or troubleshooting, however.

First, a foreword about LDAP: the Lightweight Directory Access Protocol is a protocol designed to provide a speedy, reliable way for networked machines to retrieve small records (that are often ubiquitously needed for various purposes--not the least of which identification across clients). LDAP records (or entries, as I will use interchangeably) are arranged in a hierarchical manner in a one-to-many relationship--much like filesystem hierarchies. These records are identified by a unique string, reminiscent of an absolute file path, called the distinguished name, or DN, which is used consistently to refer to the record throughout the architecture. DNs, as written out, consist of one or more components, each with a key and value separated by an equal sign '=', and joined together with a comma ',' separator. DNs can (and regularly do) have embedded spaces. The base DN for an LDAP database is usually derived from a domain name--a sufficiently unique identifier--as a series of domain components, or DCs, which will look something like dc=example,dc=com for the hypothetical "example.com" domain. Records below the base DN are arranged with more specific components on the right, such as cn=admin,dc=example,dc=com. It's worth mentioning that you will encounter CN (common name), OU (organizational unit), and other more domain-specific keys such as uid and olcDatabase.

Records each possess attributes, a multi-map of string values to a sequence of string values. While it is entirely possible to have arbitrary attributes, records in LDAP normally have properties dictated by a schema. Such a schema consists of attribute types, which define the names of attributes with the kinds of values that can be stored in an attribute (its syntax), its sorting/collation order, its comparison semantics, whether or not it needs to be unique per record, and so forth, as well as classes, which consist of a set of required attribute types (by name), and a set of optional attribute types. For example, an attribute type is something like a "userPassword", which stores an Octet String, is compared via Octet String comparison, and need not be unique to a record, while an object class is something like "posixUser" which MAY have a "userPassword" but MUST have a "uidNumber" and a "gidNumber" (both of which have syntax Integer). Classes have the additional property that they may be structural, auxiliary, or abstract. A record must have one, and only one, structural class, but can possess zero or more auxiliary classes. Classes can participate in inheritance; any may inherit arbitrarily from abstract classes, whereas auxiliary and structural classes may only inherit from their own kind (in addition to abstract). For example, consider the following Python code:

If we consider y to represent a record in the LDAP database, then Y would be its singular structural class, which inherits from the structural class X. Auxiliary classes are not well-represented in this language, except perhaps as well-known attributes defined on the object y itself. Beyond the properties that class Y defines (here absolutely none), the Mixin class can be thought of as an abstract class from which Y also inherits some properties. All of these ultimately inherit from object, whose analogue in LDAP is top, a class which is simultaneously (and confoundingly) abstract and structural--the only such class.

It would be wise to familiarize yourself with some important classes, amongst them "posixAccount", "posixGroup", "organizationalUnit", and "simpleSecurityObject", amongst others. These and many more are defined in various RFCs and included with the OpenLDAP installation (under cn=schema,cn=config, described metareferentially through LDAP access). A web-based tool for browsing local schemas will be installed much later in this tutorial.

Access to the database is controlled through a process called binding. It should be noted that LDAP makes no distinction between users and records; a connecting client can connect to any record it so chooses. Of course, the most obvious things to bind to are user accounts, which are represented as first-class objects in most major LDAP schemas. In the simple case, one can use "simpleSecurityObject" with OpenLDAP to make any object in any part of the database be bindable, but posixAccounts are more likely to be used regularly. A connection that is not yet bound is said to be anonymous.

Access control is another topic entirely. You should be well aware of OpenLDAP's access control mini-language, which reads fairly straightforward. Amongst other things to be aware of, some important points are:

The first matching "to" clause is selected, in the order it is specified in the database. In particular, a "to *" clause will cancel all further searches, because it will immediately match everything. As a rule of thumb, specify more specific "to" clauses first, followed by more generic ones, with "to *" at the end.

The "by" clauses match similarly; place "by *" at the end of the list.

Giving an access level of "none" (or, equivalently, omitting it entirely) will cause results (when trying to search a record) indistinguishable from that record not existing. You may want to be the root account to be sure you're getting the entire picture.

olcAccess lines are a little cumbersome; you are well advised to Keep It Simple, Stupid.

Finally, LDIF--the LDAP Data Interchange Format. It is standardized as RFC2849, which does a good job of being one of the more concise RFCs I've read (the examples at the end are particularly informative). Basically, the syntax is as follows:

As with most script-like languages, lines whose first non-whitespace character is a pound-sign "#" are ignored, and blank lines have no effect on parsing or performance outside of a production.

Record indications are headed by a "dn: " field, followed by the DN, on its own line. From there on, all attributes and modifications are applied to that DN. For non-empty LDIF files, this must be the first non-comment, non-blank line.

Attributes are indicated similarly, with key, ": ", and value, all strings. If the value is too long, it may wrap onto continuation lines, which begin with exactly one space. If the value contains possibly illegal characters, such as non-ASCII ordinals, control sequences that may disrupt the parser, and so forth, the key may be followed instead by two colons ":: ", and the value will be base64 encoded. Wrapping similarly applies to base64-encoded values. Multi-valued attributes are ordered in the order in which they appear, unless they have a leading integer in {angle brackets} preceding the value, in which case they are first stably sorted by that integer in ascending order. Outputs from ldapsearch will always contain this integer for multi-valued attributes.

Some attributes have special behavior when passed through certain tools; ldapmodify is the most prominent of these. This command expects the "changetype: " attribute to be one of "add", "replace", or "delete", which is then to be followed by one or more "add: ", "replace: ", or "delete: " attributes, specifying a data attribute name (which must then follow that specification for all but delete), terminated by a dash "-" on a line of its own. Multiple additions and/or replacements may be specified this way, but (AFAIK) they must consistently be the same operation.

While not technically LDIF (and there is an LDIF way of specifying this), ldapmodrdn uses a format without any colon-separated headers in which each pair of non-blank, non-comment lines is, in order:

An existent DN.

A relative DN (RDN), whose first n components will replace the first n components of the existent DN.

Installation

Setting up OpenLDAP

For this purpose, the instructions on the Debian Wiki are invaluable, though somewhat flawed. Presented here is a slightly more instruction-oriented and pedantic version.

First, become root (sudo or su). Though it is not mentioned much, all the commands in the setup assume you are root.

From a root terminal, apt-get install slapd ldap-utils. During this process, you may be prompted for an administrator password for the database. It does not, however, prompt you about certain other things, like the base DN for the main database. After the packages are installed, I recommend running dpkg-reconfigure -p low slapd and verifying the results. It may tell you that it will overwrite the database previously created--that's OK. Remember to set the password to something memorable, but you probably won't need it for a little while.

Using ldap-utils

Now would be a good time to familiarize yourself with the tools in ldap-utils. They mostly take the same options, permitting you to log in in various ways:

-H <URI> sets the URI to attempt to contact the server on.

-Y <AUTH> sets the SASL authentication method.

-D <DN> sets the DN to bind to (very probably "cn=admin,<your_dc_components>" after the setup)

-W will instruct the utility to prompt for a bind password, and is best used with -D above.

The exact method you choose will probably heavily depend on the URI in -H. In particular, ldapi:/// represents a Unix domain socket, and allows one to log in using the UIDs present on the local machine. By default, root has access to both the config database (at cn=config) and the regular database (under your dc components). As root, you will generally want to use <ldapcommand> -H ldapi:// -Y EXTERNAL to perform work. **Do not use this as a non-root user, especially on cn=config**; the default privileges restrict anyone but root from even knowing about the existence of the cn=config base DN.

Eventually, for some work (or testing), you might want to log in as the administrator you set up. This is usually accomplished with <ldapcommand> -H ldap://localhost/ -D "cn=admin,<dc_components>" -W. You will be prompted for the password you entered during setup. It should be noted that OpenLDAP, by default, will add this user DN to a special config attribute on the database called "olcRootDN", which means that (1) the password is dictated by "olcRootPW" on this database, and not necessarily by the password in "userPassword", and (2) cn=admin will have unrestricted access regardless of the settings of "olcAccess" on that database. (More on that later.)

The commands that are most important in this set are:

ldapsearch, which takes -b <baseDN> and an optional -s <scope>, which searches for the baseDN and returns results in LDIF format. Scope may be one of:

sub (default if omitted): returns the subtree--the baseDN and all of its descendants.

one: returns the baseDN and all of its immediate children.

children: returns only the children of the baseDN.

base: returns only the baseDN.

ldapmodify, which takes -f <file> (or input from stdin) and alters the database according to a modification (usually addition, replacement, or deletion of attributes).

ldapadd, which takes similar to the above, but adds records to the database.

ldapmodrdn, which moves records.

ldapdelete, which deletes records.

You may want to read through the search results in entirety to get a feeling for LDIF. Note well that ldapmodify with replacement will replace all values of a multivalue attribute, and that you are responsible for ensuring the correct ordering afterward--it cannot be used for insertion or replacement of a single element. For more on the syntax, I direct you to the man pages--each of which has at least one simple, contrived example that will get you pointed in the right direction. Keep in mind that, returning valid LDIF, ldapsearch may be used to transfer or convert databases in mass as well.

phpldapadmin

LDAP is a bit tedious to work with on its own, if you can't tell; the LDIF format, while standardized and textual, is quite verbose, and not easy to work with even in fairly powerful text editors (like vim :). More powerful users may prefer to use LDAP bindings in their preferred languages; OpenLDAP is itself a C library, and bindings are available for various scripting languages, including Python and PHP. Web UIs are also available, but many (as FreeIPA and others) impose additional structure on the LDAP database and introduce various schemas that may or may not interoperate well with the base standards. I prefer the direct access affored by phpldapadmin--written with said scripting language bindings--since it doesn't try to parse or specialize any of the data, and likely won't fail in unexpected ways if I end up breaking something.

First things first, in your root console, apt-get install phpldapadmin. This will also install apache2 if you haven't gotten that already. In any case, the apache2 instance will be configured such that phpldapadmin will be mounted on your site at /phpldapadmin/. I recommend changing this, as it is a well known path and likely to be hammered by various automated password crackers (which are probably readily aware of the existence of a cn=admin,<dc_components> account on LDAP anyway). To do so, go into /etc/apache2/conf-enabled and find phpldapadmin.conf, then find within that the following lines (near the top):

Change the first argument of the Alias as you see fit, then run /etc/init.d/apache2 restart in your root shell.

By now, if you hit the IP address of your LDAP server in your web browser, you should see either the default page (if you just installed apache2 with phpldapadmin) or whatever was mounted on your root before. Navigate to whatever you changed the Alias to (or /phpldapadmin if you didn't), and you should see a login present. (If not, try to fix that before continuing; doing so is beyond the scope of this document.)

Of course, phpldapadmin has some unfortunate defaults, using the well-known example domain "example.com" (dc=example,dc=com) as its default base DN. We'll need to change that. Navigate yourself to /etc/phpldapadmin and open config.php, searching for a line like the following:

/* Array of base DNs of your LDAP server. Leave this blank to have phpLDAPadmin
auto-detect it for you. */
$servers->setValue('server','base',array('dc=example,dc=com'));

First off, if the line is commented with either # or //, uncomment it. Then proceed to change the DN in the single quotes to a DN of your desire. If you'd like, you can comma-separate more DNs (in single quotes) to add them. I like to add cn=config to this, and find that commenting the line out (to leave it blank) won't add this base for you.

While we're in here, it would be a good idea to change the URI as well; find:

and (since we're on the same machine) change the URI to ldapi:///. This will prevent unnecessary network traffic and encodings by using a Unix domain socket for IPC.

Finally, you may want to find this line as well:

/* The DN of the user for phpLDAPadmin to bind with. For anonymous binds or
'cookie','session' or 'sasl' auth_types, LEAVE THE LOGIN_DN AND LOGIN_PASS
BLANK. If you specify a login_attr in conjunction with a cookie or session
auth_type, then you can also specify the bind_id/bind_pass here for searching
the directory for users (ie, if your LDAP server does not allow anonymous
binds. */
$servers->setValue('login','bind_id','cn=admin,dc=example,dc=com');
# $servers->setValue('login','bind_id','cn=Manager,dc=example,dc=com');

and either comment it out (as the line below it) or change it to a proper account. Note that I do not recommend revealing your cn=admin DN, so I recommend either commenting it out, or replacing it with a "template" that will be used to populate the login name field (like "cn=,ou=users,<dc_components>") so that regular users have an easier time logging in.

Administrating cn=config

If you've added cn=config to your root DNs, you will need to permission them properly. This deserves its own subsection, because it will probably be the first (and hopefully last) time you have to get your hands dirty with the ldap-utils tools. Get back on your root terminal, and run something to the effect of ldapsearch -Y EXTERNAL -H ldapi:/// -b 'olcDatabase={0}config,cn=config'. The following should spit out on your terminal:

This means that only local root can do anything (including even see) this database. We would like to make sure that something we can bind to remotely can do this configuration (we're not running a web browser on our headless server, right?). Luckily, it's pretty easy; retrieve your last command with Up and redirect its output to a file (anywhere--/tmp is a good place); then, edit it until it looks something like this:

The green sections are bits you'll want to add yourself. And, yes, you'll want to delete all the other cruft as well. Once you're done doing that, write the file back out and run it through ldapmodify -Y EXTERNAL -H ldapi:/// -f <file>. The changes will be commited, and your cn=admin account should have access to cn=config now (for remote configuration).

Setting up SSL

Note: While these instructions suffice for a quick-and-dirty setup, it should be noted that COSI now has its own CA certificate, and it is used properly in the real setup. See that page for more details.

If you're going to be using phpldapadmin over an untrusted or insecure network, it's generally a good idea to setup SSL/TLS on the webserver. In the most recent configuration, it's pretty simple; in your root terminal, run a2enconf ssl to create the relevant symlinks. Then, create a certificate. It's a bit of a process to go through to create a real CA and a real service certificate, or to get it from some other CA, so (for now) we can stick with a simple self-signed certificate, which can be made with the somewhat arduous command line:

Restart Apache (/etc/init.d/apache2 restart) and direct your browser to that server with the HTTPS protocol. You will be warned about an invalid certificate (browsers do not like self signed certificates even with all else good), but once you confirm an exception, your communication with the server will be properly encrypted.

Now that you have a key and certificate, you can also take a moment to setup LDAPS--which will be important if you plan on using LDAP authentication for remote services, as this means passwords may traverse the network. The easiest way I've found to do so is to stop the service (/etc/init.d/slapd stop), and find the file /etc/ldap/slapd.d/cn=config.ldif, adding amongst the LDIF (in proper format, which isn't too hard):

Then, in /etc/default/slapd, add "ldaps:///" to the SLAPD_HOSTS. Once you start slapd, you should be able to confirm (e.g., with lsof -i tcp) that it is listening on LDAPS.

Configuring the database

Now that you have the ability to administrate all the important parts of your database from the web interface, you can drop your root shell and grab your web browser. Direct it to the site (HTTPS, preferably) and log in as your cn=admin account. We can move on to the fun part now :)

Presumably, you have access to cn=config now, and it should be in phpldapadmin. Go into olcDatabase={1}whatever,cn=config and make sure that the olcAccess lines look reasonable (they're generally pretty readable). If you want to change something, feel free to do so now or later. If you look down in that record, you'll also see olcRootDN, initialized to your cn=admin account. This means you will never lose administrative access to the database when bound as that DN, so keep in mind that you will be overriding access restrictions while doing this.

Now would be a fun time to start adding users and groups--we'll use posixAccounts and posixGroups, respectively. Since posixAccounts require a mandatory GID, we'll be creating a group first.

It should be noted that, with the posix* accounts, resolving authentication groups is non-trivial; posixAccount objects hold no information as to their group membership--all members are listed in the groups. This means you should keep the number of groups small, as the number of requests needed to resolve a group and its users is constant time (one request), but the time required to resolve a user and its groups is linear with the total number of groups. Keep that in mind.

Before we add a posixGroup, let's get organized. Create an Organizational Unit (OU) under your dc components, and call it "groups" or something of the sort. (Think of an OU as a filesystem directory.) Now, under that OU, create a new (child) entry, and let it be a posixGroup.

From within phpldapadmin, you can click on the "Schema" link to see the schemas of the LDAP classes and attribute types--we can try this now with our posixGroup (don't worry, you can create it later, too). Click "Schemas", where you'll see an exhaustive list of object classes, and select "posixGroup" from the dropdown. It should show us that we REQUIRE "cn" (common name--the name of the group as it will appear everywhere) and gidNumber (its numeric ID), and that the attributes "description", "memberUid" (the usernames of member users--a multivalued attribute), and "userPassword" (see man newgrp) are OPTIONAL. Now that we know what we need, we can do the thing :)

Go back into ou=groups,<dc_components> and create a child entry. You can use either phpldapadmin's automatic Posix Group template, or just use Default--both are just about as easy, though the template for Posix Group will suggest a gidNumber for you. I prefer control over the GID namespace (especially because phpldapadmin chose 500, well within the system block of GIDs), so I'd use Default. Configure away--and try to choose very high numbers for your numeric IDs so they don't conflict with the lower, 1000+ IDs typically used for local accounts :)

Setting up Kerberos

Before you begin

Kerberos is a trusted-third-party PKI which provides cryptographically strong proof of authenticity for both services and users of those services. It is not based in X.509, like OpenSSL, but rather an implementation of a distinct protocol.

In the usual case, Kerberos is pretty elegantly simple; a KDC server runs two services, one for the usual Kerberos protocol (that involves distributing tickets), and one for administration (e.g., things like users changing passwords). The central entities of Kerberos are this KDC, the principals (which are like, but not exactly corresponding to users--a user can have multiple principals with different privileges, and a service can own principals too), and tickets, which are ephemeral certificates of authenticity issued by the KDC.

Kerberos' cryptography is much stronger than LDAP, in the sense that LDAP would gladly send the password for a BIND operation in plaintext if you configured it to do so (which is why setting up LDAPS is a good idea). Furthermore, as bona fide certificates, if the key for a ticket is leaked, it only remains compromised until the expiry of that ticket. Of course, the user's password can still be compromised, which will allow an attacker to get as many tickets as needed...

Configuring Kerberos to work together with LDAP requires two points of contact:

LDAP may be configured as the database backend for Kerberos. Note that the storage for Kerberos principals must, in the most straightforward setup, be distinct from the set of (POSIX) users. They are two different entities.

Kerberos principals will be named to coincide with LDAP users, despite the two being very different entities. Yes, this does result in a little data duplication (of usernames, and particularly grievously, of passwords), but the two systems work together surprisingly well once properly configured; LDAP provides most of the groups and passwd data, aside from the password for authentication, which is provided by Kerberos.

So, why use Kerberos if it takes more work and requires more out of the LDAP database for doing nothing more than replicating one of its present features? In one word: NFS. Kerberos tickets are the only sensible way to secure an NFS share such that an attacker with theoretically arbitrary packet inspection/injection capabilities cannot interfere with (or, if so chosen, exfiltrate) data in a file share. Because local machines are issued one--and only one--ticket per user, compromise of that machine still will only grant enough privileges to modify the share on behalf of all of the users whose tickets are present on that machine. For public machines, such as the lab machines, with which just about every user is trusted with root access, this usually limits the damage to the sole user at the time (usually the one who sudo'd), and thus exposes no additional privileges in that case. Of course, NFS isn't the only service which can be protected thusly; Kerberos tickets can be negotiated through HTTP by various UAs, and a quick search for "kerberos" in the APT repositories shows plenty of software ready to take advantage of that, and theoretically any software that links against the Kerberos libraries can use these security features to their benefit. Nonetheless, at the time of this writing, NFS and Microsoft's AD remains the only (and perhaps most important) users of the Kerberos standards and protocol.

Installation

Getting a KDC up and running is actually quite straightforward, especially on Debian. Basically, as root, apt-get install krb5-admin-server. You will probably be prompted to set up some things like a master password and a few default principals; you can safely ignore these, as we're about to switch the backends anyway. (If you don't want to use an LDAP backend, though, everything is very much already configured.)

Making it work with LDAP as a database backend requires a little more contrivance. You'll want to also get the krb5-kdc-ldap package, which will contain some tools for helping set up the database, particularly kdb5_ldap_util. You'll want to go ahead and run that now, using a command somewhat like:

Where ou=kerberos,dc=my,dc=domain,dc=com is the subtree that will hold the realm (and is to be created--if it exists already, this will fail!). Make sure that cn=admin,dc=my,dc=domain,dc=com is replaced with an actual LDAP account with permissions to edit the database in that way (namely, creating some new subtrees). MY.DOMAIN.COM will be, of course, the Kerberos realm.

During this process, you will be prompted to enter a master password. Make a very strong one and keep it in a very safe place.

With that underway, we need to tell the administrative servers to use LDAP as their backend. Find the /etc/krb5kdc/kdc.conf file and make it look something like this (on Talos, at least):

Note the existence of a cn=kerberos,ou=services,... bind DN. This should be an account that has access to change this part of the LDAP database and a reasonably strong password; you'll want to make this account elsewhere (and you can make it whatever bind DN you like, of course). The password itself will be stashed in the various, user-secret files stash and service.keyfile, by using the following command:

With that in place, you should check kinit and kpasswd to make sure they're working fine, and you have an operational KDC!

Populating Kerberos

At this point, you might have an LDAP database with a few user entries, but probably not any operational principals. This is something you'll want to fix. Get into a terminal and run kadmin.local--since you're doing this on the actual administrative server, it will bypass any restrictions and immediately give you an administrative prompt. From here, let's add a principal:

kadmin: addprinc username

It should prompt you for a password, grouse a little about using a sane default, and proceed to make the principal. That's really all there is to it.

At some point, you'll want to probably make some administrative accounts so you can do kadmin normally. If you have a default principal on a machine (such as "username" in this case), MIT Kerberos will automatically assume that you want to elevate to "username/admin" to do administrative work. Indeed, the default installation of MIT Kerberos has "*/admin" be administrative, so let's get to it:

kadmin: addprinc username/admin

Now wherever you may be in the network, you should be able to kadmin -p username/admin and do administrative things. You'll need this later to enroll hosts for GSS services. Until then, go ahead and have fun with adding any other users that need accounts--users in your LDAP database may be a good place to start. (A script for automating this is left as an exercise to the reader, in part because it will require the invention of automatic passwords.)

Using GSSAPI

GSSAPI is an interesting beast that requires not only user principals as you've been adding above, but also machine credentials. In particular, the clients generally have to have a key on them that simultaneously authenticates (and identifies) them as well as provides them secure access to the services. The server, on the other hand, needs its own key of this sort. Now that you can kadmin from anywhere, you can go ahead and make them from anywhere, but you'll want to know the naming scheme first:

GSSAPI keys for services have principals of the form "<service-name>/<dns-name>", whereas machine keys for clients usually just use "host" as the service. For example, NFS servers use "nfs" as a service name, so you'll probably want to spit out a couple of keys like this:

...and so forth. If you don't have differential access for hosts on your network (as you probably shouldn't--user credentials should be the ones verified), you can take a shortcut and just make one host key for the entire domain:

host/my.domain.com

You can look at man rpc.gssd on Debian to see what formats of machine key the GSS daemon will look for in the key tables (keytabs).

Adding these keys can be done with addprinc, but you probably don't need to care about the password. You can ask kadmin to create a random password that you don't care about with:

kadmin: addprinc -randkey nfs/nfs-server.domain.com

...and so forth with the rest of the keys.

Now, on the hosts that you want to add these to the keytabs (yes, you will need to use kadmin on those machines, locally, with your credentials), use the following to import the key into the default keytab:

kadmin: ktadd nfs/nfs-server.domain.com

That should basically do it for this step! Give out your host and service keys as needed--keys are pretty cheap :)

Kerberizing NFS

We've come to the important part--we need NFS to work with Kerberos security. It is at this point that I will mention that this setup was done on a different machine--namely, Metapod, which was running FreeBSD 10.1 as opposed to Linux--furthermore, this particular FreeBSD box was very much non-standard to begin with. The default on FreeBSD is Heimdal Kerberos, so you'll want to somehow get and make sure you can run MIT Kerberos (the default, it seems, on the Linuces). The two systems don't interoperate well, especially in user interface, so be careful with your $PATH. I'll assume that the MIT tools are in your path, but it's worth mentioning that, on Metapod, the tools need to be absolutely referenced in /usr/local/bin/.

If you haven't done it already, or if you've done it wrong, use the steps above to get the service key for NFS into the local keytab. Then, modify the /etc/exports file to include the following parameter on all lines that you want to export with proper protection:

-sec=krb5:krb5i:krb5p

The colon (:) is a separator character, and this forms a list; other valid types are sys and none, which provide little (if any) cryptographically-guaranteed security. The Kerberos securities are given as krb5, which just verifies the user for the RPC tunnel (somewhat vulnerable to MitM attachs), krb5i which adds a little overhead but guarantees message integrity, and krb5p which adds a lot of overhead but encrypts messages sent over the network. More often than not, krb5i is fairly suitable.

Restart NFS, and make sure that the GSS daemon (gssd on FreeBSD) is running before you restart NFS--FreeBSD's nfsd requires this.

Go to your clients, and make sure the keytabs contain the host keys as mentioned. Again, start rpc.gssd (as it is on Debian), and make sure it is running somewhere. You may wish to pass it -f -v -v -v -v in another terminal so as to watch debugging output for troubleshooting; now try this:

mount -t nfs4 -o sec=krb5i nfs-server.domain.com:/mount/point/ /mnt

If all goes well, this should mount the remote /mount/point/ at the local /mnt. For troubleshooting, you can add -v to get more output from the mount command.

Note: As of this writing, NFS4 doesn't work on the venerable Metapod, so we're using NFS3. The mount command looks superficially different:

...but the function is just about exactly the same. We're working on getting NFS working properly right now.

Working with PAM, NSS, and NFS

At this point, we're configuring clients to be able to securely mount mountpoints based on what keys are present on a machine; as mentioned above, this is nice in that it will prevent a local root from devastating a NFS share (well, not directly, given a root squash, but through su). Since most of our machines have unrestricted sudo, this is a useful property. Nonetheless, we need to make sure at least some of our information--our UIDs, our groups--in order to play nicely with things like filesystem permissions as exported on the NFS share (even though we're not trusting the local kernel all too much). Let's tie things in a nice, pretty bow and make it so we can use the LDAP and Kerberos to login to our client machines.

On Debian, get these packages: libpam-ldap libpam-krb5 libnss-ldap nfs-client krb5-user. The first two have a collection of PAM modules, the next is for NameSpace Switch, and the last is NFS. These packages will ask for a lot of configuration information that you should already know (e.g., the LDAP server's URI, "ldaps://talos.cslabs.clarkson.edu/", the search base, "dc=cslabs,dc=clarkson,dc=edu", etc.), so give it what you can. When it asks for a LDAP administrator DN and password, you can (and probably will want to) elide them--they're optional, and even if you stash them "securely" on the client, don't forget that our users have local root regularly.

Note: Using ldaps: is highly recommended; however, our setup with our own CA certificate requires you to get it first, or the secure handshake will fail. See OpenSSL CA for how to install the certificate on the machine. Once that's done, return here.

Setting up NSS

libnss-ldap doesn't configure /etc/nsswitch.conf for you by default--which is a good thing. Go ahead and edit that file as root, and append "ldap" to at least the "passwd" and "group" entries--in practice, I also do so for "gpasswd" and "shadow", and the latter may be necessary for auth to work. For example, here's what a couple lines should look like:

passwd: compat ldap
group: compat ldap
shadow: compat ldap

Files on your local machine may vary, but that's the general template.

Save and exit, and, assuming your configuration is valid, you should be able to issue this command:

getent passwd

...and the terminal should spit out at you both the set of local users (like root and various system accounts) as well as users present in LDAP (as you've made them). If you're not seeing one or the other, check your nsswitch file, your LDAP configuration, and the presence of that CA certificate in /etc/ssl/ (assuming you're using LDAPS--yes, please use it).

Setting up Kerberos

Now we'll need to set up Kerberos. The default configuration file is in /etc/krb5.conf, and you'll need to edit it. Under the header [realms], be sure a section exists that looks like this:

While that should technically do it, in case DNS is being stupid, it's good practice to edit this stanza near the top of the file, too:

[libdefaults]
default_realm = CSLABS.CLARKSON.EDU

Now you should be able to kinit <your_principal/user_name> and get a ticket for yourself. You can later kdestroy this if you'd like, but it might be handy to keep around for NFS (later).

Setting up PAM

Once you're sure that's working, go into /etc/pam.d/ and prepare to look at some files. Debian generally configures them in a somewhat useful, though silly way. Enter common-auth and be sure that:

The ordering is the pam_unix.so line first, followed by pam_krb5.so, then pam_ldap.so.

All of these modules contain "try_first_pass" as their parameter.

Perhaps most importantly, make sure the "success=N" lines properly count the number of lines to the pam_deny.so default line (so that it skips over that if they succeed).

That will do it for authentication--but you should really test this by using su or another terminal before you rescind your root terminal. Don't disconnect until you're sure it's working, or you're going to have a bad day!

Oops: If you accidentally make it impossible to login to a machine (even as root), don't worry! You will need to reboot it (most machines respond to a press of the power button to power-off); usually, the machine will then boot to GRUB or another such bootloader. You will need to find a way to edit the kernel command line (TAB in GRUB and PXE); I would recommend using the "(recovery mode)" option to start, and appending init=/bin/sh, which will run a root shell as init. This will give you immediate and unrestricted root access to the machine (and pretty fast boot times), under the careful notice that, as init, you will be responsible for setting up certain things (like network interfaces) if you need them. FreeBSD has a similar "Single-user" mode available in its bootloader that has similar functionality. Finally, as a last resort, you can boot the machine into a recovery disk, such as a CRUX CD or a flash drive, and mount the local disk partition. One such useful partition may be network-bootable as a "Diagnostic" option, so stay tuned :)

I mentioned in the passing that LDAP DNs and Kerberos principals can have diverging passwords, which can be problematic. Luckily, the default common-password will take care of updating both at the same time--but only if they converge to begin with. Since this is going to be an issue at first, I've put a nag message in common-session to ensure that users are instructed to use kpasswd to change their Kerberos password at their leisure (or bug an administrator to do so) when the session mode of pam_krb5.so fails--most likely, because they logged in with a local or LDAP account and, thus, are not associated with a principal and a TGT key. Reconstructing this is left as another exercise to the reader, but I'll give you a hint: it makes clever use of the skip directives you've already seen, and pam_echo.so with a local file. As an administrator, you will need to wrangle these into place.

Setting up sudo

It is generally a good idea to ensure that someone has the keys to the system other than yourself for better survivability (so that other people can maintain it). Options for sudo are controlled via the file /etc/sudoers (possibly in /usr/local/ on BSD), whose format is miserably documented. Luckily, you can usually use the default as a template :) . You should probably know that the ALL=(ALL) ALL or ALL=(ALL:ALL) ALL is basically root access. On Debian, the default "%sudo" group can be left alone and used with local accounts (and the "%wheel" group as well on BSD's sudo); however, you'll probably want to make sure certain other groups (ones managed via LDAP) can administrate the machine as well:

%admins, administrators, who, by necessity, need to be able to administrate all of the services (and who can freely change LDAP data to accomplish these ends); these should almost always be added.

%services, service principals, which are automatic accounts that need to be able to administrate services (and have total LDAP control as well); services like Ansible fall into this category, and should almost always be added.

%maintainers, maintainers who will be freely capable of ensuring that the services keep functioning; giving them root access is generally a good idea so they can reconfigure system services if needed.

%users, the group for all network users, which need not be trusted, but generally receive the same permissions as csguest (including sudo access on the open lab machines). In some cases it may be worth explicitly denying access to users in PAM; see the configuration on Talos for a reference.

Other groups may be configured at a later time; be sure someone has a good rationale for making them! As mentioned above, time required to login to a service using a network account goes up linearly with the number of groups :)

Setting up NFS

In order to get NFS working, we first have to provision a "host key" or machine key. This is going to be used by rpc.gssd to attest the identity of the connecting machine; if we were so inclined, we could base much of our access control on the machines accessing the share. This is, however, usually unnecessary in our setup :)

That said, the fallback for no particular host key for the machine is a host key for the realm, and we do have such a key ready as host/cslabs.clarkson.edu. You'll want to add that to the keytab on the local machine so it can mount Metapod's NFS shares. For that, you'll need kadmin:

(where those long runs of spaces are supposed to be tab stops, which isn't easy to confer in this format). It should be noted that sec=krb5imust correspond with a valid -sec= mode on Metapod, which is currently set to krb5i:krb5p. I've mentioned previously why krb5 and krb5p are usually bad ideas, so I won't reiterate that here.

Note the nolock option; applications that heavily depend on locks (like most severe software projects; web browsers, office applications, etc.) will run far too slow without it. It's common to also include nosuid for untrusted network mounts, but we're not too worried about this on our open clients :)

See if it works! Try mount /mnt. If you get an error saying that "an incorrect parameter was specified", you will probably need to run rpc.gssd and try again. Don't worry about that transient error; rpc.gssd is a startup service and should start normally on the next reboot, allowing /mnt to be automatically mounted on startup with the rest of the filesystems.

At this point, you can try to su - or login to your Kerberos/LDAP account; assuming you've done everything properly, you should be in your networked home directory (and capable of viewing and modifying files in it :)

Setting up SSH

There's one other good use of Kerberos/GSSAPI: SSH authentication. Using this, you only need to authenticate once--the time you get the Kerberos ticket. Afterward, through GSSAPI magic, any SSHd you connect to can verify your identity through the ticket, rather than you having to enter a password again.

Software based on OpenSSH already supports this natively, but you'll need to make sure the configuration is enabled, and you may need an extra key. For starters, let's enable it on the server; from there (and as root), edit /etc/ssh/sshd_config, finding and uncommenting or adding this line:

GSSAPIAuthentication yes

You can also enable it on the clients with the same directive in /etc/ssh/ssh_config (note the lack of a "d"); in practice, I've found it usually is, but it doesn't hurt to check.

Now there's a little bit of a problem, here; the SSH GSSAPI implementation requires an exactly matching qualified domain name in its Kerberos host/... key. So, for example, for Phoenix, we'd need to make a key that exactly matches its FQDN:

That's usually all there is to it! One more neat trick: if you're using non-standard ports with SSH (we are), you may want to also edit your user-local SSH config, ~/.ssh/config to add the following lines:

Host myhost
Port <port>
Host myhost.fq.dn
Port <port>

Repeat, of course, for every host that you wish to so configure; once you're done, you should be able to get into any service you're authorized for with a simple ssh host. As long as your NFS home is mounted wherever your sessions are, this configuration should follow you around too :)