- Sunday, 20 April 2003 Add support to our LAN for MIT Kerberos V5 Well, I've been playing around with Kerberos V5 and I think it's simply wonderful. It's the single-sign-on came true. So, I've decided to deploy it on my LAN. REQUISITES ---------- NTP (Network Time Protocol, a protocol used to synchronize the clock of the computer)should be setup and working properly. Kerberos is a time-sensitive network authentication protocol. Authentication won't work if the clock skews between computers in more than +/- 5 minutes. CONCEPTS -------- KEYTAB: A keytab file contains the secret keys of hosts so they can communicate between them. Kerberos uses symmetric cryptography based on a shared- secret key. Those shared-secret keys must be kept in a file, which must be secured properly so no one, except "root" and the Kerberos services could read it. A host shared-secret key is created when the host principal is first created using the "addprinc" command of the KAdmin service. Normally, the "-randkey" argument is used so the key is generated randomly instead of being interactively prompted (and confirmed). Normally, security principals are *never* assigned random keys, and they are assigned by the administrator and later, optionally, changed by theit owner. It's very important that the keytab file is properly secured, although it must be shared between all servers of a Kerberos realm, let they be KDC server or standard Kerberos servers (like SSH or Kerberized FTP servers). Clients don't access to the keytab. The keytab is usually placed at "/etc/krb5.keytab" and contains several records for each host principal. Normally, two records exist for each host, one for DES and another one for Triple-DES. Also, each record has a version number that is increased at the moment of adding the host to the keytab file using the "ktadd" command. If the version number of a host differs between keytabs, no communication will take place. Every time "ktadd" is used, the host principal is written to the keytab, but it's version number is increased. This could lead to different version numbers across some keytabs. The "kvno" command can be used to check the version number of a host principal, for example: # kvno host/kerberos.felipe-alfaro.com host/kerberos.felipe-alfaro.com@FELIPE-ALFARO.COM: kvno = 3 TICKET GRANTING TICKET (TGT): A host needs a Kerberos ticket in order to access a network server that relies on Keberos V5 for authentication. To be able to obtain an specific ticket for a network service, the client needs to request such ticket to the Key Distrution Center (KDC). However, the client needs to authenticate against the KDC. This is done only once: the client first authenticates against the KDC, normally by supplying a security principal name and a password and, if both are checked to be correct, the KDC sends a Ticket Granting Ticket (TGT) to the client. The client will then use this TGT when requesting additional tickets for servers in the network. Simply by presenting the TGT to any KDC, the KDC can expedite an specific ticket granting communication between that client and a network server (for example, an SSH or FTP server). INSTRUCTIONS ------------ The first step is to deploy the Kerberos V5 Key Distribution Center (KDC) on a computer that has 24h365d uptime, so the ideal candidate is "small". Since Engarde Linux doesn't have a Kerberos V5 KDC server by default, I had to download it from "ftp://ftp.engardelinux.org/pub/engarde/contrib". To install it: # lidsadm -S -- -LIDS_GLOBAL # rpm -ivh krb5-libs-1.2.2-4.i386.rpm # rpm -ivh krb5-workstation-1.2.2-4.i386.rpm # rpm -ivh krb5-server-1.2.2-4.i386.rpm Before we re-enable LIDS, some file tweaking must be done as init scripts for the KDC and KAdmin have some glitches: 1. The Kerberos V5 contrib packages deploy the service scripts at the incorrect directory. So, we need to move all files from "/etc/rc.d/init.d" to "/etc/init.d". 2. Remove "/etc/rc.d/init.d" and "/etc/rc.d" (they should be empty now). 3. Edit "/etc/init.d/kadmin" and "/etc/init.d/krb5kdc" and then remove the lines that source "/etc/sysconfig/network" and the ${NETWORKING] check at the beginning. 4. In "/etc/init.d/krb5kdc" replace "/etc/rc.d/init.d/functions" at the beginning of the file with "/etc/rc.d/functions". Now, we can re-enable LIDS: # /usr/sbin/config_lids.pl # lidsadm -S -- +LIDS_GLOBAL So, we have Kerberos V5 installed. We should make sure it's started automagically if the machine is rebooted: # chkconfig --add kadmin # chkconfig --add krb5kdc # chkconfig --level 345 kadmin on # chkconfig --level 345 krb5kdc on We can't start Kerberos services yet. First, we need to create the needed entries in our DNS server. Although I've been unable to completely avoid text-based configuration files and use DNS completely for KDC and realm lookups, maybe one day I guess it. For now, we need to add the following entries to our master "db.felipe-alfaro.com" database. NOTE: Don't forget to increase in one the serial number of the zone, so all our slave DNS servers will get informed of the changes and be able to perform an incremental zone transfer (IXFR). kerberos.felipe-alfaro.com. IN CNAME small.felipe-alfaro.com. Out KDC will have the canonical name "kerberos" which is the recommended by the MIT. It's simply a pointer to our real KDC located at "small". _kerberos.felipe-alfaro.com. IN TXT "FELIPE-ALFARO.COM" This entry is *theoretically* used by Kerberos clients to guess the realm from the FQDN of a host name. The client will look up a TXT record in DNS composed of "_kerberos" plus the FQDN of the host. For example, if a client is trying to access the machine "foo.felipe-alfaro.com", it will try to locate a TXT record called "_kerberos.foo.felipe-alfaro.com". If the record can't be found, the client will fall back and will try with the domain name part of the host, that is, "_kerberos.felipe-alfaro.com". If still no record can be found, a look up will be sent for "_kerberos.com" and so on until a TXT record is found or no more fallback is possible. If a TXT record is found, the contents (descriptive text) of the record is assumed to be the Kerberos realm for that host. In our case, we won't mantain TXT records for every host, so we'll depend on fallback, "_kerberos" plus the FQDN, that is, "_kerberos.felipe-alfaro.com". _kerberos._udp.felipe-alfaro.com. IN SRV 0 0 88 kerberos.felipe-alfaro.com. This record is used to locate all the KDCs. Note that the domain name appended to "_kerberos._udp" is the lower case representation of the Kerberos realm and does *not* need to necessarily be equal to the DNS domain name, In our case, the DNS domain name and Kerberos realms are identical. As we do only have one KDC, only one record is used. If we had two more KDC servers, we would add two more records for them. _kerberos-master._udp.felipe-alfaro.com. IN SRV 0 0 88 kerberos.felipe-alfaro.com. This record points to the master KDC. Kerberos is similar to DNS, NIS and LDAP in which a master server is the only one in charge for database updates which are then replicated to slave servers. _kerberos-adm._tcp.felipe.alfaro.com. IN SRV 0 0 749 kerberos.felipe-alfaro.com. This record points the Kerberos V5 KAdmin server, the one that is used to administer Kerberos (for example, adding new principals, policies, etc.) _kpasswd._udp.felipe-alfaro.com. IN SRV 0 0 464 kerberos.felipe-alfaro.com. This is used to the kpasswd service that allows principals to change their passwords without full access to the KAdmin service. The next step is to reload the DNS server for it to pick up the changes: # service named reload or # service named restart Now that we have the DNS infraestructure in place, we need to edit the configuration files. First, we'll edit "/etc/krb5.conf": # cat /etc/krb5.conf [logging] default = FILE:/var/log/krb5libs.log kdc = FILE:/var/log/krb5kdc.log admin_server = FILE:/var/log/kadmind.log [libdefaults] ticket_lifetime = 24000 default_realm = FELIPE-ALFARO.COM dns_lookup_realm = false dns_lookup_kdc = false [realms] FELIPE-ALFARO.COM = { kdc = kerberos.felipe-alfaro.com:88 admin_server = kerberos.felipe-alfaro.com:749 default_domain = felipe-alfaro.com } [domain_realm] .felipe-alfaro.com = FELIPE-ALFARO.COM felipe-alfaro.com = FELIPE-ALFARO.COM [kdc] profile = /var/kerberos/krb5kdc/kdc.conf [pam] debug = true ticket_lifetime = 36000 renew_lifetime = 36000 forwardable = true krb4_convert = false The "[realms]" section defines the know Kerberos realms and should be superseded by DNS sometime in the future (when I guess how to use DNS for KDC and realm lookups). The "[domain_realms]" provides the needed translations between DNS domain names and Kerberos realms. Don't forget to define "default_realm" correctly. Next, we'll edit "/var/kerberos/krb5kdc/kdc.conf": # cat /var/kerberos/krb5kdc/kdc.conf [kdcdefaults] acl_file = /var/kerberos/krb5kdc/kadm5.acl dict_file = /usr/share/dict/words admin_keytab = /var/kerberos/krb5kdc/kadm5.keytab [realms] FELIPE-ALFARO.COM = { master_key_type = des-cbc-crc supported_enctypes = des-cbc-crc:normal des3-cbc-raw:normal des3-cbc-sha1:normal des-cbc-crc:v4 des-cbc-crc:afs3 } And now, we'll define the ACL used to allow any principal with the admin role access to the KAdmin server: # cat /var/kerberos/krb5kdc/kadm5.acl */admin@FELIPE-ALFARO.COM * Now, we'll create the Kerberos database using "kdb5_util" tool: # /usr/kerberos/sbin/kdb5_util create -r FELIPE-ALFARO.COM -s This will create the database for the FELIPE-ALFARO.COM Kerberos realm and its corresponding stash file (currently don't know what it's used for, but...) During the process, we'll be asked for the master password used for recovery and backup purpouses. Once the database has been created, we'll add the "admin/admin" security principal used to access the KAdmin service, but since Kerberos is not fully operational and KAdmin is not started, we'll use a special version of the "kadmin" tool called "kadmin.local" that doesn't require Kerberos to be started: # /usr/kerberos/sbin/kadmin.local kadmin.local: addprinc admin/admin Now, we'll add the "kadmin/admin" and "kadmin/changepw" principals to the KAdmin keytab file, which are used by the "KAdmin" and "kpasswd" services respectively. The KAdmin keytab is *not* the same standard keytab used for standard services, and it's usually located at "/var/kerberos/krb5kdc/kadm5.keytab": kadmin.local: ktadd -k /var/kerberos/krb5kdc/kadm5.keytab \ kadmin/admin kadmin/changepw Now, we can start the KDC and KAdmin servers: # /etc/init.d/kadmin start # /etc/init.d/krb5kdc start # tail /var/log/kadmin.log ... small.felipe-alfaro.com kadmind[27420](info): starting # tail /var/log/krb5kdc.log ... small.felipe-alfaro.com krb5kdc[27441](info): commencing operation We have to add all security principals that we will use with Kerberos. Security principals can be of different kind, but mainly we'll use two types: host principals and security principals. The former is used to perform authentication and ticket granting at the host level, while the latter is used by processes, tasks or processes to authenticate to a Kerberos KDC. The principals are added using the KAdmin service. We'll need to identify to the KAdmin service using the "admin/admin" security principal we added before: # /usr/kerberos/sbin/kadmin -p admin/admin We'll now add all out host principals: kadmin: addprinc -randkey host/kerberos.felipe-alfaro.com kadmin: addprinc -randkey host/small.felipe-alfaro.com kadmin: addprinc -randkey host/glass.felipe-alfaro.com kadmin: addprinc -randkey host/linux.felipe-alfaro.com kadmin: addprinc -randkey host/teapot.felipe-alfaro.com kadmin: addprinc -randkey host/compaq.felipe-alfaro.com The next step is adding the security principals for "kerberos" and the host itself to the standard keytab file, usually "/etc/krb5.keytab": kadmin: ktadd host/kerberos.felipe-alfaro.com kadmin: ktadd host/small.felipe-alfaro.com Each host is in fact a security (host) principal and has a symmetric, secret key shared between the host and the KDC. So, only that host and the KDC know the key. The KDC does already know that key, as we specified it (well, not exactly: we told the KDC to generate a random key by using -randkey during the principal creation using "addprinc") when creating the host principal, and then exported it to the KDC local keytab file. However, we must now find a way to send that key to the host itself for it to store. That key must be kept in a "keytab" file and sent to the host securely. So for each computer that will host a service that relies upon Kerberos authentication, we will need to create a keytab with that host's secret key and send that keytab file to the host using a secure method (for example, on a disk or a secure network connection). We will generate the keytab file using the "kadmin" administrative interface: kadmin: ktadd -k "/path/to/keytab/for/the/host" "host/principal" And then we will transfer the keytab file to the corresponding host and storing it usually as "/etc/krb5.keytab", or another place where only the Kerberized service itself can access the keytab file securely. For example, "glass" will host an OpenSSH server (sshd) with Kerberos V5 support. To generate its keytab file, we execute the following command from within "kadmin" (or "kadmin.local"): kadmin: ktadd -k /root/glass.krb5.keytab host/glass.felipe-alfaro.com This will create the file "/root/glass.krb5.keytab" containing the secret key for the "KDC-glass" association. Then, we will transfer the "/root/glass.krb5.keytab" file to the host "glass" securely. The keytab file should be stored as "/etc/krb5.keytab" within its file- system, or a more secure place where only the OpenSSH "sshd" daemon can access it (I yet have to find how to tell OpenSSH how to search an specific file for its keytab), Once the keytab files have been distributed to all network servers that will host Kerberos-based services, we have to configure all of them. First, we'll need to install krb5-libs and krb5-workstation packages (if not already installed) and then edit the "/etc/krb5.conf" file on all of them. The file should look similar to: [logging] default = FILE:/var/log/krb5libs.log kdc = FILE:/var/log/krb5kdc.log admin_server = FILE:/var/log/kadmind.log [libdefaults] ticket_lifetime = 24000 default_realm = FELIPE-ALFARO.COM dns_lookup_realm = true dns_lookup_kdc = true [realms] FELIPE-ALFARO.COM = { kdc = kerberos.felipe-alfaro.com:88 admin_server = kerberos.felipe-alfaro.com:749 default_domain = felipe-alfaro.com } [domain_realm] .felipe-alfaro.com = FELIPE-ALFARO.COM felipe-alfaro.com = FELIPE-ALFARO.COM [kdc] profile = /var/kerberos/krb5kdc/kdc.conf [appdefaults] pam = { debug = true ticket_lifetime = 36000 renew_lifetime = 36000 forwardable = true krb4_convert = false } Now, we'll need to add all security principals that we'll be using: # kadmin -p admin/admin kadmin: addprinc falfaro kadmin: addprinc test ... At this point, Kerberos should be usable from any of the configured network servers. To test, we'll use "kinit" to check the TGT service is running properly: # kinit If everything is correct, "klist" should display our newly acquired TGT: # klist Ticket cache: FILE:/tmp/krb5cc_0 Default principal: root@FELIPE-ALFARO.COM Valid starting Expires Service principal 04/21/03 00:21:29 04/21/03 10:21:29 krbtgt/FELIPE-ALFARO.COM@FELIPE-ALFARO.COM Kerberos 4 ticket cache: /tmp/tkt0 klist: You have no tickets cached Finally, we can enable Kerberos V5 authentication for PAM. To do so, we can run "authconfig" and select Kerberos V5, or we can manually modify "/etc/pam.d/system-auth" and add the following lines: auth sufficient /lib/security/$ISA/pam_krb5.so use_first_pass account [default=bad success=ok user_unknown=ignore service_err=ignore system_err=ignore] /lib/security/$ISA/pam_krb5.so password sufficient /lib/security/$ISA/pam_krb5.so use_authtok session optional /lib/security/$ISA/pam_krb5.so The "use_first_pass" of "pam_krb5.so" first tries to use local password (files) authentication before trying Kerberos. Using separate keytab files for each Kerberized service -------------------------------------------------------- There are times when we want to use separate keytab files for each Kerberized service, for example, when each service runs with a different set of credentials. For example, OpenSSH runs as "root", but OpenLDAP runs as "ldap". Since the keytab file must be readable by the service, we could either give the keytab file world readable permissions (which is NOT a good idea), or else we could use separate keytab files, one for each service. By default, the keytab file is located at "/etc/krb5.keytab", but can be overriden by using the "KRB5_KTNAME" environment variable. By setting this variable and then exporting it to the Kerberized server process, we can force it to search for the keytab at an specific location. For example, if we do export KRB5_KTNAME="FILE:/etc/openldap/ldap.keytab" in the init.d script for the OpenLDAP service, we can tell the "slapd" daemon to look for the keytab file in "etc/openldap/ldap.keytab". SAMPLE NETWORK SERVICE: OpenSSH ------------------------------- To configure OpenSSH for Kerberos support, first we must be sure that the OpenSSH version we're using has been Kerberized, that is, compiled with Kerberos support. For example, Engarde Linux OpenSSH is not Kerberos-aware, so we won't be able to leverage Kerberos authentication unless we recompile it. As a side note, it seems that as of OpenSSH 3.5, the implementation of the SSH protocol version 2 does not still support Kerberos authentication so we'll fallback to version 1. Yes, I know it's less secure, and it took me a while to discover this: I had to look at the OpenSSH sources just to discover that krb5_auth was only implemented for the version 1 of the protocol. To enable Kerberos, edit "/etc/ssh/sshd_config" and make sure the following lines are present by either uncommenting them or adding them: Protocol 1 PasswordAuthentication no KerberosAuthentication yes KerberosTicketCleanup yes KerberosTgtPassing yes Now, simply by restarting the "sshd" daemon, Kerberos should be fully functional. It's not so difficult at a first sight, but facts like different versions of the keytab file, or improper configuration, can lead to an administrative nightmare while trying to make SSH work. It took me a while to do it.