Server:Server Status

OpenLDAP, Kerberos and SASL – My Experience In The Homelab

I previously posted my experiences setting up OpenLDAP on Ubuntu Server, using my own custom schema. This whole ordeal is for a couple of reasons...I wanted to learn about openLDAP and how schema works, and I wanted to eventually create something akin to "Active Directory" from my home that wasn't just "use Samba" or "use FreeIPA." I don't have anything against Samba, but it feels like using Samba is trying to acheive Microsoft functionality with Microsoft compatibility, and I don't need Microsoft compatibility, so I wanted to do it without. I have no Microsoft devices in my ecosystem and have no plans to add any. I don't have anything against FreeIPA either, but their docs target rpm distros and I tend to live in deb land, and I found the initial install frustrating unless I switched platforms...it felt like lock-in. There are other solutions and this is a problem that has been solved other ways but with shortcomings, but I wanted to really do something "from scratch" so I share this not as a recommendation, but as a start-to-finish resource with some nuggets of wisdom that may help someone else whose journey brushes up against parts of mine.

When I started, I was just authenticating web services like nextcloud, jellyfin, SAML, etc. Tying those into openLDAP was very easy and has worked very well for a long time. Now though, I want to use Kerberos for Linux PAM authentication as I am about to spin up a bunch of small servers (I got a new hypervisor and am redoing a lot of infrastructure, I don't want different credential stores all over the place). Because of this, I'm bolting on MIT Kerberos as my network authentication provider; that being said, getting OpenLDAP and Kerberos to work together and only use 1 password was not intuitive. Kerberos will use LDAP as it's database, and authentication will happen through SASL. Ultimately, requests will go to openLDAP, then depending on the account, the password will either be validated in OpenLDAP for web users, or it will defer to SASL for users doing both web/PAM stuff, which will then authenticate against the Kerberos passoword it stores in LDAP. Circular much?

Here is the vision, to help you see what I'm working towards. Ultimately I have about 20 users, all of which might access web services like nextcloud, but 4 core users which will actually log into computers. One of those 4 (me) has sudo rights on any computer. All 20 users should be able to log into nextcloud, jellyfin, etc without issue, and for now I'm managing password resets if they come to my house (I had previously used phpldapadmin for password resets, but that was struggling on php7+ when I last tried). For the internal users, they will be able to sit at ANY computer and log in using their same username and password. If an web only user tries to sit and log into a computer, the'll get username not found. If any of the 3 non-sudo users tries sudo, they'll get an unauthorized error. If my account tries sudo, I can become root. I can handle password resets for the internal users via kpasswd or kadmin.local.

I don't know if this is a great solution, however it has been a great learning experience and it is working. The one piece that is not clean is resetting passwords. Because some passwords are stored in LDAP and some are stored in kerberos, I have to know where to change/update/reset them. Most accounts I use Apache Directory Studio as an analogue to ADUC, but for my nice shiny Kerberos accounts, I have to use kpasswd on the command line. This is not a great operational procedure; if there was a decent GUI/webapp I could see making all accounts kerberos accounts. I think this is solvable, but I'm stopping here because I want to move on to other priorities, and I have a workable solution.

I'm including all my various final config files below so that the final working state can be documented for anyone else attempting to implement a solution like this. When I was done, I burned the host to the ground and rebuilt it from this documentation to make sure it was correct. Domain, subdomain, and host names are changed of course and passwords are redacted. I used my name "surfrock66" as the prototype account, but you could replace with yours. Honestly, if you did a find-replace of "subdomain", then "domain", then "ldapserverhostname", then "surfrock66", most of this would be copy/paste. I don't recommend that; this isn't meant to be a drop in, this is a learning journey that someone could follow to learn and recreate.

Some accounts are created with the ldif files below, and in my personal case I've got the hashed passwords already in the files. If you're copying this, you should generate all the passwords offline, run the ldif commands to create the account, then use Apache Directory Studio (connecting as admin) to actually change password as needed. You should pregenerate the following passwords:

  • admin (cn=admin,dc=subdomain,dc=domain,dc=com and admin@SUBDOMAIN.DOMAIN.COM) (used for both slapd admin AND kerberos admin)
  • config admin (cn=admin,cn=config)
  • KDC Master Key
  • Your user account, replace surfrock66 (cn=surfrock66,ou=accounts,dc=subdomain,dc=domain,dc=com)
  • kdc-service (cn=kdc-service,ou=accounts,dc=subdomain,dc=domain,dc=com)
  • kadmin-service (cn=kadmin-service,ou=accounts,dc=subdomain,dc=domain,dc=com)
  • ldapbinduser (cn=ldapbinduser,ou=accounts,dc=subdomain,dc=domain,dc=com) (This password will be put in plain text in places that store bind credentials in config files, as an FYI.)

The commands to install stuff start here; I put a dpkg-reconfigure after to answer the prompts for any given package install. If you're not prompted, you need to run through them:

apt -y update
apt -y install db-util db5.3-util krb5-admin-server krb5-config krb5-kdc krb5-kdc-ldap krb5-user ldap-utils libgssrpc4 libkadm5clnt-mit12 libkadm5srv-mit12 libkdb5-10 libltdl7 libodbc2 libsasl2-modules-gssapi-mit libverto-libevent1 libverto1 sasl2-bin schema2ldif slapd

dpkg-reconfigure slapd
# Don't omit initial configuration
# Domain = subdomain.domain.com
# Organization = subdomain.domain.com
# Set an admin password
# Do you want the DB removed when SlapD is purged...yes

dpkg-reconfigure krb5-config
# Default Kerberos Realm is SUBDOMAIN.DOMAIN.COM
# Primary KDC is fqdn
# Administrative server is fqdn

dpkg-reconfigure krb5-admin-server
# Ok

dpkg-reconfigure krb5-kdc
# No, don't create KDC config manually

mkdir /var/log/kerberos
touch /var/log/kerberos/krb5kdc.log
touch /etc/krb5kdc/service.keyfile
vi /usr/lib/systemd/system/krb5-kdc.service
# Add "/var/log/kerberos" to the line "ReadWriteDirectories"

vi /usr/lib/systemd/system/krb5-admin-server.service
# Add "/var/log/kerberos" to the line "ReadWriteDirectories"

systemctl daemon-reload
adduser openldap sasl

If you want to use an SSL cert to do TLS, you'll need to set that up. On ubuntu, there's some weird permission stuff. At a high level, we're creating an ssl-certs group, setting the cert file permissions to permit group reading, then adding the openldap service's account to that group; you can use whatever gid you want. I personally have an easy-rsa CA, so I have an internal CA and domain issued cert/key pair. That may be too much for some people, which is fine, you would then omit the 2 TLS ldif files below.

groupadd -g 901 ssl-cert
usermod -a -G ssl-cert openldap
# Create the CA Cert File
/etc/ssl/certs/subdomain.domain.com.rootca.YYYY.MM.DD.pem
# Create the Certificate Private Key File
/etc/ssl/private/ldap.subdomain.domain.com.key
# Create the Certificate File
/etc/ssl/certs/ldap.subdomain.domain.com.crt
chown root:ssl-cert /etc/ssl/certs/subdomain.domain.com.rootca.YYYY.MM.DD.pem
chown root:ssl-cert /etc/ssl/private
chown root:ssl-cert /etc/ssl/private/ldapserverhostname.subdomain.domain.com.key
chown root:ssl-cert /etc/ssl/certs/ldapserverhostname.subdomain.domain.com.crt
chmod 710 /etc/ssl/private

First, here are all of the ldif files that represent the configuration of slapd and the ldap environment. These are all of them as I set them up. Note there are 2 types of ldapadd commands, some require authentication of the admin account, some don't. I pasted them exactly as I successfully ran them. One thing to consider is that you can't really just paste a password in for a lot of these accounts. If you put nonsense in the password field, you could later open the object in Apache Directory Studio and set the password from the admin account, where it'll add it encrypted. Once you're done, you can then use ADS to export the object as an ldif, so it's easy to recreate later (which is how I got most of these ldif's at first).

01.config.InstallMemberOf.ldif (ldapadd -Y EXTERNAL -H ldapi:/// -f 01.config.InstallMemberOf.ldif)

dn: cn=module{0},cn=config
changetype: modify
add: olcModuleLoad
olcModuleLoad: memberof.la

02.schema.attribute.sshPublicKey.ldif (ldapadd -Y EXTERNAL -H ldapi:/// -f 02.schema.attribute.sshPublicKey.ldif)

dn: cn=sshPublicKey,cn=schema,cn=config
objectClass: olcSchemaConfig
cn: attribute.sshPublicKey
#
# LDAP Public Key Patch schema for use with openssh-ldappubkey
#                              useful with PKA-LDAP also
#
# Adjusted: Dennis Leeuw <dleeuw@made-it.com>
#           Making the uid a MUST, but the sshPublicKey a MAY
#           so we can add the objectClass and later add the key
#
# Author: Eric AUGE <eau@phear.org>
# 
# Based on the proposal of : Mark Ruijter
#
# octetString SYNTAX
olcAttributeTypes: ( 1.3.6.1.4.1.24552.500.1.1.1.13 NAME 'sshPublicKey' 
  DESC 'MANDATORY: OpenSSH Public key'
  EQUALITY octetStringMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
# printableString SYNTAX yes|no
olcObjectClasses: ( 1.3.6.1.4.1.24552.500.1.1.2.0 NAME 'ldapPublicKey' SUP top AUXILIARY
  DESC 'MANDATORY: OpenSSH LPK objectclass'
  MUST uid
  MAY sshPublicKey
  )

03.schema.sudo.ldif (ldapadd -Y EXTERNAL -H ldapi:/// -f 03.schema.sudo.ldif)

dn: cn=sudo,cn=schema,cn=config
objectClass: olcSchemaConfig
cn: sudo
olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.1 NAME 'sudoUser' DESC 'User(s) who may  run sudo' EQUALITY caseExactIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.2 NAME 'sudoHost' DESC 'Host(s) who may run sudo' EQUALITY caseExactIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.3 NAME 'sudoCommand' DESC 'Command(s) to be executed by sudo' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.4 NAME 'sudoRunAs' DESC 'User(s) impersonated by sudo (deprecated)' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.5 NAME 'sudoOption' DESC 'Options(s) followed by sudo' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.6 NAME 'sudoRunAsUser' DESC 'User(s) impersonated by sudo' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: ( 1.3.6.1.4.1.15953.9.1.7 NAME 'sudoRunAsGroup' DESC 'Group(s) impersonated by sudo' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcObjectClasses: ( 1.3.6.1.4.1.15953.9.2.1 NAME 'sudoRole' SUP top STRUCTURAL DESC 'Sudoer Entries' MUST ( cn ) MAY ( sudoUser $ sudoHost $ sudoCommand $ sudoRunAs $ sudoRunAsUser $ sudoRunAsGroup $ sudoOption $ description ) )

04.schema.objectClass.domainAccount.ldif (ldapadd -Y EXTERNAL -H ldapi:/// -f 04.schema.objectClass.domainAccount.ldif)

dn: cn=objectClass.domainAccount,cn=schema,cn=config
objectClass: olcSchemaConfig
cn: objectClass.domainAccount
olcObjectClasses: ( 1.3.6.1.4.1.57470.2.2.1 NAME 'domainAccount'
  DESC 'A user/account/person in the organization'
  SUP top STRUCTURAL
  MUST ( cn $ name $ sn $ uid )
  MAY ( 
  audio $   businessCategory $ carLicense $ departmentNumber $ 
  description $ destinationIndicator $ displayName $ 
  employeeNumber $ employeeType $ facsimileTelephoneNumber $ 
  gecos $ gidNumber $ givenName $ homeDirectory $ homePhone $ 
  homePostalAddress $ initials $ internationaliSDNNumber $ 
  jpegPhoto $ l $ labeledURI $ loginShell $ 
  mail $ manager $ mobile $ o $ ou $ pager $ photo $ 
  physicalDeliveryOfficeName $ postalAddress $ postalCode $ 
  postOfficeBox $ preferredDeliveryMethod $ 
  preferredLanguage $ registeredAddress $ roomNumber $ 
  secretary $ seeAlso $ shadowExpire $ shadowInactive $ 
  shadowLastChange $ shadowMax $ shadowMin $ shadowWarning $ 
  sshPublicKey $ st $ street $ telephoneNumber $ 
  teletexTerminalIdentifier $ telexNumber $ title $ 
  uidNumber $ userCertificate $ userPassword $ userPKCS12 $ 
  userSMIMECertificate $ x121Address $ x500uniqueIdentifier ) )

05.schema.objectClass.domainGroup.ldif (ldapadd -Y EXTERNAL -H ldapi:/// -f 05.schema.objectClass.domainGroup.ldif)

dn: cn=objectClass.domainGroup,cn=schema,cn=config
objectClass: olcSchemaConfig
cn: objectClass.domainGroup
olcObjectClasses: ( 1.3.6.1.4.1.57470.2.2.2 NAME 'domainGroup'
  DESC 'A group of names in the organization'
  SUP top STRUCTURAL
  MUST ( cn )
  MAY ( 
  businessCategory $ description $ gidNumber $ member $
  o $ ou $ owner $ seeAlso ) )

06.config.EnableMemberOf.ldif (ldapadd -Y EXTERNAL -H ldapi:/// -f 06.config.EnableMemberOf.ldif)

We're configuring 2 things here, changing the memberof overlay to use "domainGroup" instead of the default objectClass "groupOfNames" and enabling referential integrity.

dn: olcOverlay=memberof,olcDatabase={1}mdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcMemberOf
olcOverlay: memberof
olcMemberOfRefint: TRUE
olcMemberOfGroupOC: domainGroup

07.ous.ldif (ldapadd -x -D 'cn=admin,dc=subdomain,dc=domain,dc=com' -W -H ldapi:/// -f 07.ous.ldif)

You need to authenticate with the admin account for this

dn: ou=accounts,dc=subdomain,dc=domain,dc=com
objectClass: organizationalUnit
ou: accounts

dn: ou=groups,dc=subdomain,dc=domain,dc=com
objectClass: organizationalUnit
ou: groups

dn: ou=hosts,dc=subdomain,dc=domain,dc=com
objectClass: organizationalUnit
ou: hosts

08.user.ldapbinduser.ldif (ldapadd -x -D 'cn=admin,dc=subdomain,dc=domain,dc=com' -W -H ldapi:/// -f 08.user.ldapbinduser.ldif)

You need to authenticate with the admin account for this. (Password obviously redacted)

version: 1

dn: cn=ldapbinduser,ou=accounts,dc=subdomain,dc=domain,dc=com
cn: ldapbinduser
description: LDAP Bind User
displayname: ldapbinduser
gecos: ldapbinduser
gidnumber: 20000
homedirectory: /nonexistent
loginshell: No Login
name: ldapbinduser
objectclass: domainAccount
objectclass: top
ou: ou=accounts,dc=subdomain,dc=domain,dc=com
sn: ldapbinduser
uid: ldapbinduser
uidnumber: 2999
userpassword: {MD5}Redacted==

09.user.kdc-service.ldif (ldapadd -x -D 'cn=admin,dc=subdomain,dc=domain,dc=com' -W -H ldapi:/// -f 09.user.kdc-service.ldif)

You need to authenticate with the admin account for this. (Password obviously redacted)

version: 1

dn: cn=kdc-service,ou=accounts,dc=subdomain,dc=domain,dc=com
objectClass: domainAccount
objectClass: simpleSecurityObject
objectClass: top
cn: kdc-service
name: kdc-service
sn: kdc-service
uid: kdc-service
userPassword:: Redacted=
description: Account used for the Kerberos KDC

10.user.kadmin-service.ldif (ldapadd -x -D 'cn=admin,dc=subdomain,dc=domain,dc=com' -W -H ldapi:/// -f 10.user.kadmin-service.ldif)

You need to authenticate with the admin account for this. (Password obviously redacted)

version: 1

dn: cn=kadmin-service,ou=accounts,dc=subdomain,dc=domain,dc=com
objectClass: domainAccount
objectClass: simpleSecurityObject
objectClass: top
cn: kadmin-service
name: kadmin-service
sn: kadmin-service
uid: kadmin-service
userPassword:: Redacted=
description: Account used for the Kerberos Admin server

11.user.surfrock66.ldif (ldapadd -x -D 'cn=admin,dc=subdomain,dc=domain,dc=com' -W -H ldapi:/// -f 11.user.surfrock66.ldif)

You need to authenticate with the admin account for this. (Password obviously redacted) You should replace this with your user; also this account is baked into future ldif files, so change it there too. Update the info in this one with whatever personal info you want.

version: 1

dn: cn=surfrock66,ou=accounts,dc=subdomain,dc=domain,dc=com
cn: surfrock66
displayname: surfrock66
gecos: surfrock66
gidnumber: 1000
givenname: Redacted
homedirectory: /home/surfrock66
homepostaladdress:: Redacted==
initials: Redacted
l: Redacted
labeleduri: Redacted
loginshell: /bin/bash
mail: Redacted
mobile: Redacted
name: surfrock66
o: Redacted
objectclass: domainAccount
objectclass: top
ou: ou=accounts,dc=subdomain,dc=domain,dc=com
postalcode: Redacted
preferredlanguage: English
sn: Gullo
sshpublickey: ssh-rsa ---== surfrock66@openLDAP
st: Redacted
street: Redacted
telephonenumber: Redacted
uid: surfrock66
uidnumber: 1000
userpassword: {MD5}Redacted==

12.access.permissions.ldif (ldapadd -Y EXTERNAL -H ldapi:/// -f 12.access.permissions.ldif)

Make sure to replace your account name in here.

dn: olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcAccess
olcAccess: {0}to *
  by dn="cn=admin,dc=subdomain,dc=domain,dc=com" manage
  by dn="cn=surfrock66,ou=accounts,dc=subdomain,dc=domain,dc=com" manage
  by dn="cn=ldapbinduser,ou=accounts,dc=subdomain,dc=domain,dc=com" read
  by dn="cn=kdc-service,ou=accounts,dc=subdomain,dc=domain,dc=com" read
  by dn="cn=kadmin-service,ou=accounts,dc=subdomain,dc=domain,dc=com" write
  by * break
-
add: olcAccess
olcAccess: {1}to dn.children="ou=accounts,dc=subdomain,dc=domain,dc=com" attrs=userPassword,shadowExpire,shadowInactive,shadowLastChange,shadowMax,shadowMin,shadowWarning
  by self write
  by anonymous auth
-
add: olcAccess
olcAccess: {2}to dn.subtree="dc=subdomain,dc=domain,dc=com"
  by self read

13.config.defineCACert.ldif (ldapadd -Y EXTERNAL -H ldapi:/// -f 13.config.defineCACert.ldif)

Don't modify the order; it has to be CA -> Key -> Cert

dn: cn=config
changetype: modify
add: olcTLSCACertificateFile
olcTLSCACertificateFile: /etc/ssl/certs/subdomain.domain.com.rootca.YYYY.MM.DD.pem
-
add: olcTLSCertificateKeyFile
olcTLSCertificateKeyFile: /etc/ssl/private/ldapserverhostname.subdomain.domain.com.key
-
add: olcTLSCertificateFile
olcTLSCertificateFile: /etc/ssl/certs/ldapserverhostname.subdomain.domain.com.crt

14.config.forceTLS.ldif (ldapadd -Y EXTERNAL -H ldapi:/// -f 14.config.forceTLS.ldif)

dn: olcDatabase={0}config,cn=config
changetype: modify
add: olcSecurity
olcSecurity: ssf=71
-

dn: olcDatabase={1}mdb,cn=config
changetype: modify
add: olcSecurity
olcSecurity: ssf=71

GOTTA RESTART SLAPD AFTER THIS ONE with 'systemctl restart slapd'

15.config.setConfigAdminPass.ldif (ldapadd -Y EXTERNAL -H ldapi:/// -f 15.config.setConfigAdminPass.ldif)

(Password obviously redacted)

dn: cn=config
changetype: modify

dn: olcDatabase={0}config,cn=config
changetype: modify
add: olcRootPW
olcRootPW: {SSHA}REDACTED

16.config.kerberosSchema.ldif (ldapadd -Y EXTERNAL -H ldapi:/// -f 16.config.kerberosSchema.ldif)

dn: cn=kerberos,cn=schema,cn=config
objectClass: olcSchemaConfig
cn: kerberos
olcAttributeTypes: {0}( 2.16.840.1.113719.1.301.4.1.1 NAME 'krbPrincipalName'
 EQUALITY caseExactIA5Match SUBSTR caseExactSubstringsMatch SYNTAX 1.3.6.1.4.1
 .1466.115.121.1.26 )
olcAttributeTypes: {1}( 1.2.840.113554.1.4.1.6.1 NAME 'krbCanonicalName' EQUAL
 ITY caseExactIA5Match SUBSTR caseExactSubstringsMatch SYNTAX 1.3.6.1.4.1.1466
 .115.121.1.26 SINGLE-VALUE )
olcAttributeTypes: {2}( 2.16.840.1.113719.1.301.4.3.1 NAME 'krbPrincipalType'
 EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {3}( 2.16.840.1.113719.1.301.4.5.1 NAME 'krbUPEnabled' DESC
  'Boolean' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )
olcAttributeTypes: {4}( 2.16.840.1.113719.1.301.4.6.1 NAME 'krbPrincipalExpira
 tion' EQUALITY generalizedTimeMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SING
 LE-VALUE )
olcAttributeTypes: {5}( 2.16.840.1.113719.1.301.4.8.1 NAME 'krbTicketFlags' EQ
 UALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {6}( 2.16.840.1.113719.1.301.4.9.1 NAME 'krbMaxTicketLife'
 EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {7}( 2.16.840.1.113719.1.301.4.10.1 NAME 'krbMaxRenewableAg
 e' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {8}( 2.16.840.1.113719.1.301.4.14.1 NAME 'krbRealmReference
 s' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
olcAttributeTypes: {9}( 2.16.840.1.113719.1.301.4.15.1 NAME 'krbLdapServers' E
 QUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {10}( 2.16.840.1.113719.1.301.4.17.1 NAME 'krbKdcServers' E
 QUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
olcAttributeTypes: {11}( 2.16.840.1.113719.1.301.4.18.1 NAME 'krbPwdServers' E
 QUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
olcAttributeTypes: {12}( 2.16.840.1.113719.1.301.4.24.1 NAME 'krbHostServer' E
 QUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {13}( 2.16.840.1.113719.1.301.4.25.1 NAME 'krbSearchScope'
 EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {14}( 2.16.840.1.113719.1.301.4.26.1 NAME 'krbPrincipalRefe
 rences' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
 )
olcAttributeTypes: {15}( 2.16.840.1.113719.1.301.4.28.1 NAME 'krbPrincNamingAt
 tr' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALU
 E )
olcAttributeTypes: {16}( 2.16.840.1.113719.1.301.4.29.1 NAME 'krbAdmServers' E
 QUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
olcAttributeTypes: {17}( 2.16.840.1.113719.1.301.4.30.1 NAME 'krbMaxPwdLife' E
 QUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {18}( 2.16.840.1.113719.1.301.4.31.1 NAME 'krbMinPwdLife' E
 QUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {19}( 2.16.840.1.113719.1.301.4.32.1 NAME 'krbPwdMinDiffCha
 rs' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {20}( 2.16.840.1.113719.1.301.4.33.1 NAME 'krbPwdMinLength'
  EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {21}( 2.16.840.1.113719.1.301.4.34.1 NAME 'krbPwdHistoryLen
 gth' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE
 )
olcAttributeTypes: {22}( 1.3.6.1.4.1.5322.21.2.1 NAME 'krbPwdMaxFailure' EQUAL
 ITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {23}( 1.3.6.1.4.1.5322.21.2.2 NAME 'krbPwdFailureCountInter
 val' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE
 )
olcAttributeTypes: {24}( 1.3.6.1.4.1.5322.21.2.3 NAME 'krbPwdLockoutDuration'
 EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {25}( 1.2.840.113554.1.4.1.6.2 NAME 'krbPwdAttributes' EQUA
 LITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {26}( 1.2.840.113554.1.4.1.6.3 NAME 'krbPwdMaxLife' EQUALIT
 Y integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {27}( 1.2.840.113554.1.4.1.6.4 NAME 'krbPwdMaxRenewableLife
 ' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {28}( 1.2.840.113554.1.4.1.6.5 NAME 'krbPwdAllowedKeysalts'
  EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALU
 E )
olcAttributeTypes: {29}( 2.16.840.1.113719.1.301.4.36.1 NAME 'krbPwdPolicyRefe
 rence' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 S
 INGLE-VALUE )
olcAttributeTypes: {30}( 2.16.840.1.113719.1.301.4.37.1 NAME 'krbPasswordExpir
 ation' EQUALITY generalizedTimeMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SIN
 GLE-VALUE )
olcAttributeTypes: {31}( 2.16.840.1.113719.1.301.4.39.1 NAME 'krbPrincipalKey'
  EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
olcAttributeTypes: {32}( 2.16.840.1.113719.1.301.4.40.1 NAME 'krbTicketPolicyR
 eference' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.1
 2 SINGLE-VALUE )
olcAttributeTypes: {33}( 2.16.840.1.113719.1.301.4.41.1 NAME 'krbSubTrees' EQU
 ALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
olcAttributeTypes: {34}( 2.16.840.1.113719.1.301.4.42.1 NAME 'krbDefaultEncSal
 tTypes' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {35}( 2.16.840.1.113719.1.301.4.43.1 NAME 'krbSupportedEncS
 altTypes' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {36}( 2.16.840.1.113719.1.301.4.44.1 NAME 'krbPwdHistory' E
 QUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
olcAttributeTypes: {37}( 2.16.840.1.113719.1.301.4.45.1 NAME 'krbLastPwdChange
 ' EQUALITY generalizedTimeMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-V
 ALUE )
olcAttributeTypes: {38}( 1.3.6.1.4.1.5322.21.2.5 NAME 'krbLastAdminUnlock' EQU
 ALITY generalizedTimeMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE
 )
olcAttributeTypes: {39}( 2.16.840.1.113719.1.301.4.46.1 NAME 'krbMKey' EQUALIT
 Y octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
olcAttributeTypes: {40}( 2.16.840.1.113719.1.301.4.47.1 NAME 'krbPrincipalAlia
 ses' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {41}( 2.16.840.1.113719.1.301.4.48.1 NAME 'krbLastSuccessfu
 lAuth' EQUALITY generalizedTimeMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SIN
 GLE-VALUE )
olcAttributeTypes: {42}( 2.16.840.1.113719.1.301.4.49.1 NAME 'krbLastFailedAut
 h' EQUALITY generalizedTimeMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-
 VALUE )
olcAttributeTypes: {43}( 2.16.840.1.113719.1.301.4.50.1 NAME 'krbLoginFailedCo
 unt' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE
 )
olcAttributeTypes: {44}( 2.16.840.1.113719.1.301.4.51.1 NAME 'krbExtraData' EQ
 UALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
olcAttributeTypes: {45}( 2.16.840.1.113719.1.301.4.52.1 NAME 'krbObjectReferen
 ces' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
olcAttributeTypes: {46}( 2.16.840.1.113719.1.301.4.53.1 NAME 'krbPrincContaine
 rRef' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
olcAttributeTypes: {47}( 2.16.840.1.113730.3.8.15.2.1 NAME 'krbPrincipalAuthIn
 d' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {48}( 1.3.6.1.4.1.5322.21.2.4 NAME 'krbAllowedToDelegateTo'
  EQUALITY caseExactIA5Match SUBSTR caseExactSubstringsMatch SYNTAX 1.3.6.1.4.
 1.1466.115.121.1.26 )
olcObjectClasses: {0}( 2.16.840.1.113719.1.301.6.1.1 NAME 'krbContainer' SUP t
 op STRUCTURAL MUST cn )
olcObjectClasses: {1}( 2.16.840.1.113719.1.301.6.2.1 NAME 'krbRealmContainer'
 SUP top STRUCTURAL MUST cn MAY ( krbMKey $ krbUPEnabled $ krbSubTrees $ krbSe
 archScope $ krbLdapServers $ krbSupportedEncSaltTypes $ krbDefaultEncSaltType
 s $ krbTicketPolicyReference $ krbKdcServers $ krbPwdServers $ krbAdmServers
 $ krbPrincNamingAttr $ krbPwdPolicyReference $ krbPrincContainerRef ) )
olcObjectClasses: {2}( 2.16.840.1.113719.1.301.6.3.1 NAME 'krbService' SUP top
  ABSTRACT MUST cn MAY ( krbHostServer $ krbRealmReferences ) )
olcObjectClasses: {3}( 2.16.840.1.113719.1.301.6.4.1 NAME 'krbKdcService' SUP
 krbService STRUCTURAL )
olcObjectClasses: {4}( 2.16.840.1.113719.1.301.6.5.1 NAME 'krbPwdService' SUP
 krbService STRUCTURAL )
olcObjectClasses: {5}( 2.16.840.1.113719.1.301.6.8.1 NAME 'krbPrincipalAux' SU
 P top AUXILIARY MAY ( krbPrincipalName $ krbCanonicalName $ krbUPEnabled $ kr
 bPrincipalKey $ krbTicketPolicyReference $ krbPrincipalExpiration $ krbPasswo
 rdExpiration $ krbPwdPolicyReference $ krbPrincipalType $ krbPwdHistory $ krb
 LastPwdChange $ krbLastAdminUnlock $ krbPrincipalAliases $ krbLastSuccessfulA
 uth $ krbLastFailedAuth $ krbLoginFailedCount $ krbExtraData $ krbAllowedToDe
 legateTo $ krbPrincipalAuthInd ) )
olcObjectClasses: {6}( 2.16.840.1.113719.1.301.6.9.1 NAME 'krbPrincipal' SUP t
 op STRUCTURAL MUST krbPrincipalName MAY krbObjectReferences )
olcObjectClasses: {7}( 2.16.840.1.113719.1.301.6.11.1 NAME 'krbPrincRefAux' SU
 P top AUXILIARY MAY krbPrincipalReferences )
olcObjectClasses: {8}( 2.16.840.1.113719.1.301.6.13.1 NAME 'krbAdmService' SUP
  krbService STRUCTURAL )
olcObjectClasses: {9}( 2.16.840.1.113719.1.301.6.14.1 NAME 'krbPwdPolicy' SUP
 top STRUCTURAL MUST cn MAY ( krbMaxPwdLife $ krbMinPwdLife $ krbPwdMinDiffCha
 rs $ krbPwdMinLength $ krbPwdHistoryLength $ krbPwdMaxFailure $ krbPwdFailure
 CountInterval $ krbPwdLockoutDuration $ krbPwdAttributes $ krbPwdMaxLife $ kr
 bPwdMaxRenewableLife $ krbPwdAllowedKeysalts ) )
olcObjectClasses: {10}( 2.16.840.1.113719.1.301.6.16.1 NAME 'krbTicketPolicyAu
 x' SUP top AUXILIARY MAY ( krbTicketFlags $ krbMaxTicketLife $ krbMaxRenewabl
 eAge ) )
olcObjectClasses: {11}( 2.16.840.1.113719.1.301.6.17.1 NAME 'krbTicketPolicy'
 SUP top STRUCTURAL MUST cn )

17.config.SASLConfig.ldif (ldapadd -Y EXTERNAL -H ldapi:/// -f 17.config.SASLConfig.ldif)

dn: cn=config
changetype: modify
add: olcSaslHost
olcSaslHost: hostname.subdomain.domain.com
-
add: olcSaslRealm
olcSaslRealm: SUBDOMAIN.DOMAIN.COM
-
add: olcAuthzRegexp
olcAuthzRegexp: {0}"cn=([^/]*),cn=subdomain.domain.com,cn=GSSAPI,cn=auth" "cn=$1,ou=accounts,dc=subdomain,dc=domain,dc=com"
-
add: olcAuthzRegexp
olcAuthzRegexp: {1}"cn=host/([^/]*).subdomain.domain.com,cn=subdomain.domain.com,cn=GSSAPI,cn=auth" "cn=$1,ou=hosts,dc=subdomain,dc=domain,dc=com"
-
add: olcAuthzRegexp
olcAuthzRegexp: {2}"uid=ldap/admin,cn=subdomain.domain.com,cn=GSSAPI,cn=auth" "cn=admin,dc=subdomain,dc=domain,dc=com"

18.ou.sudoers.ldif (ldapadd -x -D 'cn=admin,dc=subdomain,dc=domain,dc=com' -W -H ldapi:/// -f 18.ou.sudoers.ldif)

dn: ou=SUDOers,dc=subdomain,dc=domain,dc=com
objectclass: organizationalunit
ou: SUDOers
description: Users who can use Sudo

19.sudorole.default.ldif (ldapadd -x -D 'cn=admin,dc=subdomain,dc=domain,dc=com' -W -H ldapi:/// -f 19.sudorole.default.ldif)

These are default sudo attributes given to all sudo users

dn: cn=defaults,ou=SUDOers,dc=subdomain,dc=domain,dc=com
objectClass: top
objectClass: sudoRole
cn: defaults
description: Default Sudo Options
sudoOption: !visiblepw
sudoOption: always_set_home
sudoOption: match_group_by_gid
sudoOption: always_query_group_plugin
sudoOption: env_reset
sudoOption: env_keep =  "COLORS DISPLAY HOSTNAME HISTSIZE KDEDIR LS_COLORS"
sudoOption: env_keep += "MAIL PS1 PS2 QTDIR USERNAME LANG LC_ADDRESS LC_CTYPE"
sudoOption: env_keep += "LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES"
sudoOption: env_keep += "LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE"
sudoOption: env_keep += "LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY"
sudoOption: env_keep+=SSH_AUTH_SOCK
sudoOption: secure_path = /sbin:/bin:/usr/sbin:/usr/bin

20.sudorole.sudo.ldif (ldapadd -x -D 'cn=admin,dc=subdomain,dc=domain,dc=com' -W -H ldapi:/// -f 20.sudorole.sudo.ldif)

This is the normal sudo role, where the user is basically root

dn: cn=sudo,ou=SUDOers,dc=subdomain,dc=domain,dc=com
objectClass: top
objectClass: sudoRole
cn: sudo
sudoHost: ALL
sudoRunAsUser: ALL
sudoCommand: ALL

21.sudouser.surfrock66.ldif (ldapadd -x -D 'cn=admin,dc=subdomain,dc=domain,dc=com' -W -H ldapi:/// -f 21.sudouser.surfrock66.ldif)

This is how you add a user to sudo for all joined devices. At the end of this post is a link to an article on adding more nuanced sudo roles and permissions, but this is your template for adding a user to sudo.

dn: cn=sudo,ou=SUDOers,dc=subdomain,dc=domain,dc=com
changetype: modify
add: sudoUser
sudoUser: surfrock66

Now we get into configuration files. Here is just about everything for OpenLDAP:

cat /etc/default/slapd | grep -v -e "#" -e "^[[:space:]]*$"

SLAPD_CONF=
SLAPD_USER="openldap"
SLAPD_GROUP="openldap"
SLAPD_PIDFILE=
SLAPD_SERVICES="ldap:/// ldapi:///"
SLAPD_SENTINEL_FILE=/etc/ldap/noslapd
export KRB5_KTNAME="FILE:/etc/ldap/kerberos.ldap.ldapserverhostname.subdomain.domain.com.keytab"
SLAPD_OPTIONS=""

cat /etc/ldap/ldap.conf | grep -v -e "#" -e "^[[:space:]]*$"

TLS_CACERT      /etc/ssl/certs/subdomain.domain.com.rootca.YYYY.MM.YY.pem
SASL_MECH       GSSAPI
SASL_REALM      SUBDOMAIN.DOMAIN.COM

cat /usr/lib/sasl2/slapd.conf | grep -v -e "#" -e "^[[:space:]]*$"

mech_list: plain
pwcheck_method: saslauthd
saslauthd_path: /var/run/saslauthd/mux

Here is the config for saslauthd:

cat /etc/default/saslauthd | grep -v -e "#" -e "^[[:space:]]*$"

START=yes
KRB5_TRACE="/dev/stderr"
DESC="SASL Authentication Daemon"
NAME="saslauthd"
MECHANISMS="kerberos5"
MECH_OPTIONS=""
THREADS=5
OPTIONS="-c -m /var/run/saslauthd"

cat /etc/krb5.conf | grep -v -e "#" -e "^[[:space:]]*$"

[libdefaults]
    default_realm = SUBDOMAIN.DOMAIN.COM
    kdc_timesync = 1
    ccache_type = 4
    forwardable = true
    proxiable = true
    fcc-mit-ticketflags = true
[logging]
    kdc = SYSLOG:DEBUG
    admin_server = SYSLOG:DEBUG
    default = SYSLOG:DEBUG
    kdc = FILE:/var/log/kerberos/krb5kdc.log
    admin_server = FILE:/var/log/kerberos/kadmin.log
    default = FILE:/var/log/kerberos/krb5lib.log
[realms]
    SUBDOMAIN.DOMAIN.COM = {
        kdc = ldapserverhostname.subdomain.domain.com
        admin_server = ldapserverhostname.subdomain.domain.com
        default_domain = subdomain.domain.com
        database_module = openldap_ldapconf
    }
[domain_realm]
    .subdomain.domain.com = SUBDOMAIN.DOMAIN.COM
    subdomain.domain.com = SUBDOMAIN.DOMAIN.COM
[dbdefaults]
    ldap_kerberos_container_dn = cn=krbContainer,dc=subdomain,dc=domain,dc=com
[dbmodules]
    openldap_ldapconf = {
        db_library = kldap
        disable_last_success = true
        disable_lockout  = true
        acl_file = /etc/krb5kdc/kadm5.acl
        key_stash_file = /etc/krb5kdc/stash
        ldap_kdc_dn = "cn=kdc-service,ou=accounts,dc=subdomain,dc=domain,dc=com"
        ldap_kadmind_dn = "cn=kadmin-service,ou=accounts,dc=subdomain,dc=domain,dc=com"
        ldap_service_password_file = /etc/krb5kdc/service.keyfile
        ldap_servers = ldapi:///
        ldap_conns_per_server = 5
        }

cat /etc/krb5kdc/kadm5.acl (My user will be an administrator, hence adding it with all privs):

*/admin@SUBDOMAIN.DOMAIN.COM *
surfrock66@SUBDOMAIN.DOMAIN.COM *

cat /etc/rsyslog.d/51-slapd.conf # Direct logs to /var/log/slapd.log

local4.* /var/log/slapd.log

Relevant Bind9 DNS entries in the home domain's zone:

; Kerberos Records
_kerberos._tcp          SRV     0       0       88      ldapserverhostname
_kerberos._udp          SRV     0       0       88      ldapserverhostname
_kerberos-master._tcp   SRV     0       0       88      ldapserverhostname
_kerberos-master._udp   SRV     0       0       88      ldapserverhostname
_kerberos-adm._tcp      SRV     0       0       749     ldapserverhostname
_kpasswd._udp           SRV     0       0       464     ldapserverhostname

If UFW is in play you need to open the required firewall ports:

ufw allow 21/tcp # Kerberos ftp uses the default port
ufw allow 23/tcp # Kerberos telnet uses the default port
ufw allow 88/udp # Kerberos V5 KDC
ufw allow 88/tcp # Kerberos V5 KDC
ufw allow 464/udp # Kerberos V5 Kpasswd
ufw allow 464/tcp # Kerberos V5 Kpasswd
ufw allow 543/tcp # Kerberos authenticated rlogin
ufw allow 544/tcp # and remote shell
ufw allow 749/tcp # Kerberos 5 admin/changepw
ufw allow 749/udp # Kerberos 5 admin/changepw
ufw allow 754/tcp # Kerberos slave propagation
ufw allow 2105/tcp # Kerberos auth. & encrypted rlogin
ufw allow 4444/tcp # Kerberos 5 to 4 ticket translator

And now, the operational nonsense of running this. You should reboot at this point, then do the following commands; make sure to generate and store very strong passwords for each step when asked:

Create the realm:

# You need the LDAP admin password, and a stront password for the KDC master key
kdb5_ldap_util -D cn=admin,dc=subdomain,dc=domain,dc=com -H ldapi:/// create -subtrees dc=subdomain,dc=domain,dc=com -sscope SUB -r SUBDOMAIN.DOMAIN.COM

Stash the passwords for binding:

# kdc-service LDAP Password
kdb5_ldap_util -D cn=admin,dc=subdomain,dc=domain,dc=com stashsrvpw -f /etc/krb5kdc/service.keyfile cn=kdc-service,ou=accounts,dc=subdomain,dc=domain,dc=com
# kadmin-service LDAP Password
kdb5_ldap_util -D cn=admin,dc=subdomain,dc=domain,dc=com stashsrvpw -f /etc/krb5kdc/service.keyfile cn=kadmin-service,ou=accounts,dc=subdomain,dc=domain,dc=com
# KDC Master Key
kdb5_util stash -f /etc/krb5kdc/stash

Restart the kerberos services:

systemctl restart krb5-kdc
systemctl restart krb5-admin-server

Enter the kadmin service and start creating and managing Kerberos Principals:

kadmin.local

# Take control of kadmin/admin@SUBDOMAIN.DOMAIN.COM and set the password to your LDAP admin password
cpw kadmin/admin

# Create my user
addprinc surfrock66

# This is so saslauthd has access to kerberos
addprinc -randkey ldap/ldapserverhostname.subdomain.domain.com@SUBDOMAIN.DOMAIN.COM
ktadd -k /etc/ldap/ldap.keytab ldap/ldapserverhostname.subdomain.domain.com@SUBDOMAIN.DOMAIN.COM

addprinc -randkey host/ldapserverhostname.subdomain.domain.com@SUBDOMAIN.DOMAIN.COM
ktadd -k /etc/ldap/ldap.keytab host/ldapserverhostname.subdomain.domain.com@SUBDOMAIN.DOMAIN.COM
ktadd -k /etc/krb5.keytab host/ldapserverhostname.subdomain.domain.com@SUBDOMAIN.DOMAIN.COM

exit

Initialize host kerberos:

chown openldap:openldap /etc/ldap/ldap.keytab
chmod 640 /etc/ldap/ldap.keytab
kinit -k -t /etc/krb5.keytab
systemctl restart slapd krb5-kdc krb5-admin-server saslauthd

Now we can create our users, and shift their password to use kerberos and not LDAP. First, I will create the user in Apache Directory Studio. I will set a password there, for testing. Once that's done, I will test the account from the ldap server, and another computer:

From the LDAP server:

ldapsearch -x -D cn=testuser,ou=accounts,dc=subdomain,dc=domain,dc=com -W -b dc=subdomain,dc=domain,dc=com -H ldapi:///

And from a different computer, where we use -ZZ to check starttls:

ldapsearch -ZZ -x -D cn=testuser,ou=accounts,dc=subdomain,dc=domain,dc=com -W -b dc=subdomain,dc=domain,dc=com -H ldap://ldapserverhostname.subdomain.domain.com

On the LDAP server, we get in with kadmin and create a new user principle; for this test, you should use a different password than the LDAP password to help verify how you are authenticating:

kadmin.local
addprinc testuser

This will create a principle testuser@SUBDOMAIN.DOMAIN.COM. Then, in ApacheDirectoryStudio, I change the user's password to exactly "{SASL}testuser@SUBDOMAIN.DOMAIN.COM" which is code to tell LDAP to forward the password attempt to kerberos via SASL.

Change a kerberos password (You will need the old password)

kpasswd testuser

Or, you do it from kadmin on the ldap server:

kadmin.local
cpw testuser

Now, to onboard a new computer, we do the following on the new client. This is one big blast, so if it's confusing, take it apart and go line by line; the focus of this article is more on the server config, so the client config is fairly abridged; this is literally my copy/paste from my onboarding script, there are lots of ways to automate this

apt update
apt -y install sssd-ldap sssd-krb5 ldap-utils krb5-user sssd-common sssd libpam-sss libnss-sss sssd-tools libsss-sudo
# Default realm is SUBDOMAIN.DOMAIN.COM

# Ensure the rootCA's cert, that validates the LDAP server, is /etc/ssl/certs/subdomain.domain.com.rootca.YYYY.MM.DD.pem or wherever you put it, just modify the sssd.conf below.  Make sure not to ignore the "ldap_schema" and "ldap_group_member" as they are required to make memberof work.  

sed -i "s|$(hostname)|$(hostname).subdomain.domain.com $(hostname)|g" /etc/hosts
hostnamectl set-hostname client-hostname.subdomain.domain.com

echo -e "[sssd]" > /etc/sssd/sssd.conf
echo -e "config_file_version = 2" >> /etc/sssd/sssd.conf
echo -e "debug_level = 4" >> /etc/sssd/sssd.conf
echo -e "services = nss, pam, sudo" >> /etc/sssd/sssd.conf
echo -e "domains = subdomain.domain.com" >> /etc/sssd/sssd.conf
echo -e "" >> /etc/sssd/sssd.conf
echo -e "[sudo]" >> /etc/sssd/sssd.conf
echo -e "" >> /etc/sssd/sssd.conf
echo -e "[domain/subdomain.domain.com]" >> /etc/sssd/sssd.conf
echo -e "debug_level = 4" >> /etc/sssd/sssd.conf
echo -e "cache_credentials = True" >> /etc/sssd/sssd.conf
echo -e "enumerate = True" >> /etc/sssd/sssd.conf
echo -e "" >> /etc/sssd/sssd.conf
echo -e "id_provider = ldap" >> /etc/sssd/sssd.conf
echo -e "sudo_provider = ldap" >> /etc/sssd/sssd.conf
echo -e "" >> /etc/sssd/sssd.conf
echo -e "ldap_uri = ldap://ldapserverhostname.subdomain.domain.com" >> /etc/sssd/sssd.conf
echo -e "ldap_search_base = dc=subdomain,dc=domain,dc=com" >> /etc/sssd/sssd.conf
echo -e "ldap_group_search_base = ou=groups,dc=subdomain,dc=domain,dc=com" >> /etc/sssd/sssd.conf
echo -e "ldap_sudo_search_base = ou=SUDOers,dc=subdomain,dc=domain,dc=com" >> /etc/sssd/sssd.conf
echo -e "ldap_sudo_full_refresh_interval=86400" >> /etc/sssd/sssd.conf
echo -e "ldap_sudo_smart_refresh_interval=3600" >> /etc/sssd/sssd.conf
echo -e "ldap_schema = rfc2307bis" >> /etc/sssd/sssd.conf
echo -e "" >> /etc/sssd/sssd.conf
echo -e "ldap_user_object_class = domainAccount" >> /etc/sssd/sssd.conf
echo -e "ldap_group_object_class = domainGroup" >> /etc/sssd/sssd.conf
echo -e "ldap_group_member = member" >> /etc/sssd/sssd.conf
echo -e "" >> /etc/sssd/sssd.conf
echo -e "ldap_default_bind_dn = cn=ldapbinduser,ou=accounts,dc=subdomain,dc=domain,dc=com" >> /etc/sssd/sssd.conf
echo -e "ldap_default_authtok = PLAINTEXTPASSFORBINDUSER" >> /etc/sssd/sssd.conf
echo -e "ldap_id_use_start_tls = True" >> /etc/sssd/sssd.conf
echo -e "ldap_tls_reqcert = demand" >> /etc/sssd/sssd.conf
echo -e "ldap_tls_cacert = /etc/ssl/certs/subdomain.domain.com.rootca.YYYY.MM.DD.pem" >> /etc/sssd/sssd.conf
echo -e "ldap_tls_cacertdir = /etc/ssl/certs" >> /etc/sssd/sssd.conf
echo -e "" >> /etc/sssd/sssd.conf
echo -e "auth_provider = krb5" >> /etc/sssd/sssd.conf
echo -e "chpass_provider = krb5" >> /etc/sssd/sssd.conf
echo -e "" >> /etc/sssd/sssd.conf
echo -e "krb5_server = ldapserverhostname.subdomain.domain.com" >> /etc/sssd/sssd.conf
echo -e "krb5_kpasswd = ldapserverhostname.subdomain.domain.com" >> /etc/sssd/sssd.conf
echo -e "krb5_realm = SUBDOMAIN.DOMAIN.COM" >> /etc/sssd/sssd.conf
echo -e "" >> /etc/sssd/sssd.conf
echo -e "create_homedir = True" >> /etc/sssd/sssd.conf

chmod 600 /etc/sssd/sssd.conf

echo -e "[libdefaults]" > /etc/krb5.conf
echo -e "tdefault_realm = SUBDOMAIN.DOMAIN.COM" >> /etc/krb5.conf
echo -e "tkdc_timesync = 1" >> /etc/krb5.conf
echo -e "tccache_type = 4" >> /etc/krb5.conf
echo -e "tforwardable = true" >> /etc/krb5.conf
echo -e "tproxiable = true" >> /etc/krb5.conf
echo -e "tfcc-mit-ticketflags = true" >> /etc/krb5.conf
echo -e "[realms]" >> /etc/krb5.conf
echo -e "tSUBDOMAIN.DOMAIN.COM = {" >> /etc/krb5.conf
echo -e "ttkdc = ldapserverhostname.subdomain.domain.com" >> /etc/krb5.conf
echo -e "ttadmin_server = ldapserverhostname.subdomain.domain.com" >> /etc/krb5.conf
echo -e "ttdefault_domain = subdomain.domain.com" >> /etc/krb5.conf
echo -e "t}" >> /etc/krb5.conf
echo -e "[domain_realm]" >> /etc/krb5.conf
echo -e "t.subdomain.domain.com = SUBDOMAIN.DOMAIN.COM" >> /etc/krb5.conf
echo -e "tsubdomain.domain.com = SUBDOMAIN.DOMAIN.COM" >> /etc/krb5.conf

echo -e "TLS_CACERTt/etc/ssl/certs/subdomain.domain.com.rootca.YYYY.MM.DD.pem" > /etc/ldap/ldap.conf

echo -e "passwd:ttfiles systemd sss" > /etc/nsswitch.conf
echo -e "group:ttfiles systemd sss" >> /etc/nsswitch.conf
echo -e "shadow:ttfiles sss" >> /etc/nsswitch.conf
echo -e "gshadow:tfiles" >> /etc/nsswitch.conf
echo -e "sudoers:tfiles sss" >> /etc/nsswitch.conf
echo -e "" >> /etc/nsswitch.conf
echo -e "hosts:ttfiles mdns4_minimal [NOTFOUND=return] dns" >> /etc/nsswitch.conf
echo -e "networks:tfiles" >> /etc/nsswitch.conf
echo -e "" >> /etc/nsswitch.conf
echo -e "protocols:tdb files" >> /etc/nsswitch.conf
echo -e "services:tdb files sss" >> /etc/nsswitch.conf
echo -e "ethers:ttdb files" >> /etc/nsswitch.conf
echo -e "rpc:ttdb files" >> /etc/nsswitch.conf
echo -e "" >> /etc/nsswitch.conf
echo -e "netgroup:tnis sss" >> /etc/nsswitch.conf
echo -e "automount:tsss" >> /etc/nsswitch.conf

echo -e "[Seat:*]" >> /etc/lightdm/lightdm.conf
echo -e "greeter-show-manual-login=true" >> /etc/lightdm/lightdm.conf

kadmin -p surfrock66@SUBDOMAIN.DOMAIN.COM
# Login with password

addprinc -randkey host/client-hostname.subdomain.domain.com@SUBDOMAIN.DOMAIN.COM

ktadd -k /etc/krb5.keytab host/client-hostname.subdomain.domain.com@SUBDOMAIN.DOMAIN.COM
exit

kinit -k -t /etc/krb5.keytab
systemctl restart sssd
systemctl restart lightdm

# Test with an LDAP username
getent passwd username

Now, let's say you have an existing computer with an account, specifically one with that username/uid combination. This is ok; after setting this all up, you can go in and do "userdel username". When you try to login again, it will scoop up the account from LDAP/Kerberos, and as long as the UID is the same, all file persmissions will persist.

There are many more pieces here which I will now work to solve, but as it is, we're pretty much done. I use syncthing to keep user directories in sync, which works well. I want to tie ssh into ldap, which is supported, but realistically I'm the only ssh user right now and I already put in my keys when onboarding a new device, so it's not critical. The last piece is a better user-interface for managing password changes and, to be blunt, directory info. I could see a world where a system like this better emulates Active Directory or other directory services out there, but for the homelab, it's working great.

Below are my references; there are certainly more as this has been a meandering journey over MANY MONTHS:

Leave a Reply