Implementing Centralized Authentication and Authorization with LDAP and Kerberos

The authentication system generally comprises account management, authentication, authorization, and auditing. This article briefly describes how to implement a centralized account, authentication, and authorization system based on LDAP and Kerberos. The chosen software are OpenLDAP and Kerberos.

LDAP is used for account management, while Kerberos handles authentication. Authorization is typically determined by the application, which can make authorization decisions based on attributes configured in the LDAP database.

Refer to the respective software distribution for installation instructions, which will not be covered here. The following configuration has been tested on Gentoo Linux.

First, configure LDAP by editing the LDAP server configuration file /etc/openldap/slapd.conf as follows:

  1include/etc/openldap/schema/core.schema 2include/etc/openldap/schema/cosine.schema 3include/etc/openldap/schema/inetorgperson.schema 4include/etc/openldap/schema/misc.schema 5include/etc/openldap/schema/nis.schema 6include/etc/openldap/schema/kerberos.schema 7 8pidfile/var/run/openldap/slapd.pid 9argsfile/var/run/openldap/slapd.args1011loglevelnone12idletimeout513writetimeout51415access to attrs=userPassword16    by self read17    by dn.exact="cn=ops,ou=Control,dc=demo,dc=local" write18    by anonymous auth1920access to dn.subtree="ou=Kerberos,dc=demo,dc=local"21    by dn.exact="cn=kdc-adm,ou=Control,dc=demo,dc=local" write22    by dn.exact="cn=kdc-srv,ou=Control,dc=demo,dc=local" read23    by * none2425access to dn.base=""26    by * read2728accessto*29byselfwrite30by dn.base="cn=ops,ou=Control,dc=demo,dc=local" write31    by users read32    by anonymous read3334#TLSCipherSuite        HIGH:MEDIUM:-SSLv235#TLSVerifyClient       never36#TLSCertificateFile    /etc/ssl/jsl/ldap.demo.local.pem37#TLSCertificateKeyFile /etc/ssl/jsl/ldap.demo.local.pem38#TLSCACertificateFile  /etc/ssl/jsl/ca-jsl.pem3940##41# BDB database definitions42##43databasehdb44suffix      "dc=demo,dc=local"45checkpoint323046rootdn      "cn=root,ou=Control,dc=demo,dc=local"47#rootpw      {SSHA}ifM5X6pQS2eO8hODguTPmjRLFyCnVWvP48directory/var/lib/openldap-data49dbconfigset_cachesize0268435456150dbconfigset_lg_regionmax26214451dbconfigset_lg_bsize209715252indexobjectClass,entryCSN,entryUUIDeq53indexuid,uidNumber,gidNumbereq,pres54indexou,krbPrincipalNameeq,pres,sub

Replace ‘dc=demo,dc=local’ with the correct domain name (you can also use the format O=xxx, C=yyy). The TLS certificate-related directives require the corresponding private key and certificate files to be prepared in advance. For details on how to create these, refer to this guide. We will skip the TLS configuration for now.

The string following the rootpw directive corresponds to the initial LDAP administrator password, generated by the command slappasswd -s 123456. After replacing this string with the correct password, uncomment the line.

Execute /etc/init.d/slapd start to start the LDAP service. The database will be empty at this point. Verify with slapcat or use ldapsearch to query:

 $ ldapsearch -x -D 'cn=root,ou=Control,dc=demo,dc=local' -w 123456 -h 127.0.0.1 -b 'dc=demo,dc=local'
# extended LDIF
#
# LDAPv3
# base  with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# search result
search: 2
result: 32 No such object

# numResponses: 1

Next, initialize the LDAP database by preparing a file named demo.ldif with the following content:

 dn: dc=demo,dc=local
dc: demo
objectClass: domain
objectClass: dcObject

dn: ou=Group,dc=demo,dc=local
ou: Group
objectClass: organizationalUnit

dn: ou=Aliases,dc=demo,dc=local
ou: Aliases
objectClass: organizationalUnit

dn: ou=People,dc=demo,dc=local
ou: People
objectClass: organizationalUnit

dn: ou=Kerberos,dc=demo,dc=local
ou: Kerberos
objectClass: organizationalUnit

dn: ou=Control,dc=demo,dc=local
ou: Control
objectClass: organizationalUnit

dn: cn=kdc-srv,ou=Control,dc=demo,dc=local
cn: kdc-srv
userPassword:: e1NTSEF9VDhBaThzNGhhd2VzODViaEVBTDFkbjZRNjkzRFFqTEMK
objectClass: simpleSecurityObject
objectClass: organizationalRole

dn: cn=kdc-adm,ou=Control,dc=demo,dc=local
cn: kdc-adm
userPassword:: e1NTSEF9QnRlTEtmb0xGQmxMS255OG52cndwMEswa2hzb3RENDYK
objectClass: simpleSecurityObject
objectClass: organizationalRole

dn: cn=root,ou=Control,dc=demo,dc=local
cn: root
userPassword:: e1NTSEF9bldwZW82anIvQmo0NDhKM1hIb1gveXcxa3I1WHh5cWUK
objectClass: simpleSecurityObject
objectClass: organizationalRole

dn: cn=demo_users,ou=Group,dc=demo,dc=local
cn: demo_users
gidNumber: 20000
objectClass: posixGroup

dn: uid=test,ou=People,dc=demo,dc=local
uid: test
uidNumber: 10000
gidNumber: 20000
sn: Test
cn: Test User
loginShell: /bin/bash
homeDirectory: /home/users/test
objectClass: person
objectClass: posixAccount
objectClass: inetOrgPerson
objectClass: organizationalPerson

The userPassword entries in this file are generated by the command slappasswd -s 123456 | base64. In the example, all userPassword entries correspond to the password 123456.

Execute the command ldapadd -x -D 'cn=root,ou=Control,dc=demo,dc=local' -w 123456 -h 127.0.0.1 -f /tmp/demo.ldif to import the data into the LDAP database.

 # ldapadd -x -D 'cn=root,ou=Control,dc=demo,dc=local' -w 123456 -h 127.0.0.1 -f /tmp/demo.ldif
adding new entry "dc=demo,dc=local"

adding new entry "ou=Group,dc=demo,dc=local"

adding new entry "ou=Aliases,dc=demo,dc=local"

adding new entry "ou=People,dc=demo,dc=local"

adding new entry "ou=Kerberos,dc=demo,dc=local"

adding new entry "ou=Control,dc=demo,dc=local"

adding new entry "cn=kdc-srv,ou=Control,dc=demo,dc=local"

adding new entry "cn=kdc-adm,ou=Control,dc=demo,dc=local"

adding new entry "cn=root,ou=Control,dc=demo,dc=local"

adding new entry "cn=demo_users,ou=Group,dc=demo,dc=local"

adding new entry "uid=test,ou=People,dc=demo,dc=local"

Now, execute ldapsearch -x -D 'cn=root,ou=Control,dc=demo,dc=local' -w 123456 -h 127.0.0.1 -b 'dc=demo,dc=local' to query the imported data.

In the above ldif file, the rootdn password is configured. Edit /etc/openldap/slapd.conf, comment out the line containing rootpw, then restart the LDAP service with /etc/init.d/slapd restart. Execute the ldapsearch command again, and you should get the same result.

At this point, the LDAP configuration is basically complete. If you need to use this LDAP for user authentication, you just need to add the userPassword attribute to the user (e.g., uid=test,ou=People,dc=demo,dc=local). If adding via the command line, prepare the following file (the password for userPassword is 123456):

plaintext
dn: uid=test,ou=People,dc=demo,dc=local
changetype: modify
add: userPassword
userPassword:: e1NTSEF9Ym0rZXloV1ExalB1aWNEVU1BaHlNM0hZVHh3REIrWU4K

Then execute the command:

plaintext
ldapmodify -x -D ‘cn=root,ou=Control,dc=demo,dc=local’ -w 123456 -h 127.0.0.1 -f /tmp/test.ldif

After the command executes successfully, confirm with:

plaintext
ldapsearch -x -D ‘uid=test,ou=People,dc=demo,dc=local’ -w 123456 -h 127.0.0.1 -b ‘ou=People,dc=demo,dc=local’

I recommend a very useful LDAP client, [Apache Directory Studio](http://directory.apache.org/studio/). With this client, you can easily modify the LDAP database without preparing LDIF files.

For large-scale user management, you can develop scripts based on business characteristics to simplify daily operations.

LDAP alone can achieve centralized account and authentication management. However, considering that password information in LDAP is stored directly in the database and requires sending the username and password directly to the LDAP server during authentication, this model has security risks in an insecure and untrusted environment. Therefore, next, we will introduce how to use Kerberos for user authentication.

Kerberos-related data also needs to be stored in a database. Here, we choose to use LDAP as its database for the convenience of data backup (only need to back up the LDAP database). If you need to use its own database, replace the following kdb5_ldap_util command with kdb5_util.

First, edit the /etc/krb5.conf file as follows:

plaintext
[libdefaults]
debug = false
default_realm = DEMO.LOCAL

[realms]
DEMO.LOCAL = {
#kdc = 127.0.0.1
#admin_server = 127.0.0.1
default_domain = demo.local
database_module = openldap_ldapconf
key_stash_file = /etc/krb5.DEMO.LOCAL
max_life = 1d 0h 0m 0s
max_renewable_life = 90d 0h 0m 0s
dict_file = /usr/share/dict/words
}

[domain_realm]
.demo.local = DEMO.LOCAL
demo.local = DEMO.LOCAL

[logging]
default = SYSLOG
admin_server = FILE:/var/log/kadmind.log
kdc = FILE:/var/log/kdc.log

[dbdefaults]
ldap_kerberos_container_dn = ou=Kerberos,dc=demo,dc=local

[dbmodules]
openldap_ldapconf = {
db_library = kldap
ldap_servers = ldapi://
ldap_kerberos_container_dn = ou=Kerberos,dc=demo,dc=local
ldap_kdc_dn = cn=kdc-srv,ou=Control,dc=demo,dc=local
ldap_kadmind_dn = cn=kdc-adm,ou=Control,dc=demo,dc=local
ldap_service_password_file = /etc/krb5.ldap
ldap_conns_per_server = 5
}

The ldap_kdc_dn and ldap_kadmind_dn correspond to the service and management accounts for Kerberos to access the LDAP database. The former needs read permissions, and the latter needs read and write permissions. The relevant ACL rules have been configured in the slapd.conf file at the beginning of this article.

First, initialize the database by executing the command:

plaintext
kdb5_ldap_util -D cn=kdc-adm,ou=Control,dc=demo,dc=local -H ldapi:// create -r DEMO.LOCAL -s

plaintext
# kdb5_ldap_util -D cn=kdc-adm,ou=Control,dc=demo,dc=local -w 123456 -H ldapi:// create -r DEMO.LOCAL -s
Initializing database for realm ‘DEMO.LOCAL’
You will be prompted for the database Master Password.
It is important that you NOT FORGET this password.
Enter KDC database master key:
Re-enter KDC database master key to verify:

Now, if you try to start the Kerberos service:

plaintext
/etc/init.d/mit-krb5kdc start

You will encounter the following error message:

plaintext
krb5kdc: Error reading password from stash: No such file or directory – while initializing database for realm DEMO.LOCAL

This is because Kerberos needs the passwords for ldap_kdc_dn and ldap_kadmind_dn to access the LDAP database. Execute the following commands:

plaintext
# kdb5_ldap_util -D cn=root,ou=Control,dc=demo,dc=local -w 123456 stashsrvpw -f /etc/krb5.ldap cn=kdc-adm,ou=Control,dc=demo,dc=local
Password for “cn=kdc-adm,ou=Control,dc=demo,dc=local”:
Re-enter password for “cn=kdc-adm,ou=Control,dc=demo,dc=local”:
# kdb5_ldap_util -D cn=root,ou=Control,dc=demo,dc=local -w 123456 stashsrvpw -f /etc/krb5.ldap cn=kdc-srv,ou=Control,dc=demo,dc=local
Password for “cn=kdc-srv,ou=Control,dc=demo,dc=local”:
Re-enter password for “cn=kdc-srv,ou=Control,dc=demo,dc=local”:
# cat /etc/krb5.ldap
cn=kdc-adm,ou=Control,dc=demo,dc=local#{HEX}313233343536
cn=kdc-srv,ou=Control,dc=demo,dc=local#{HEX}313233343536

As you can see, /etc/krb5.ldap stores the two passwords we entered in plain text, so the permissions of this file need to be controlled so that only root can read and write.

Execute again:

plaintext
/etc/init.d/mit-krb5kdc start

The service should start successfully.

To use Kerberos authentication, modify the user’s password field as follows (the password for userPassword is generated by `echo -n “{SASL}[email protected]” | base64`):

plaintext
dn: uid=test,ou=People,dc=demo,dc=local
changetype: modify
replace: userPassword
userPassword:: e1NBU0x9dGVzdEBERU1PLkxPQ0FM

Execute:

plaintext
ldapmodify -x -D ‘cn=root,ou=Control,dc=demo,dc=local’ -w 123456 -h 127.0.0.1 -f /tmp/test.ldif

You can see that we are actually using [SASL](http://en.wikipedia.org/wiki/Simple_Authentication_and_Security_Layer) to perform the actual authentication operation, so we need to configure the related service saslauthd first. Edit the saslauthd service configuration file and set its startup options to “-a kerberos5 -n 0”. The ‘-n 0’ parameter is to avoid memory leak issues. Execute:

plaintext
/etc/init.d/saslauthd start

Start the service. Confirm whether the authentication is successful with the command:

plaintext
testsaslauthd -u test -p 123456

Before actual testing, configure the test user’s password to 123456 with the command:

plaintext
kadmin.local -q ‘ank -pw 123456 test’

At this point, executing the test should show unsuccessful results:

plaintext
# testsaslauthd -u test -p 123456
0: NO “authentication failed”
# tail -n2 /var/log/auth.log
Sep 27 12:50:39 localhost saslauthd[18347]: auth_krb5: krb5_get_init_creds_password: -1765328164
Sep 27 12:50:39 localhost saslauthd[18347]: do_auth : auth failure: [user=test] [service=imap] [realm=] [mech=kerberos5] [reason=saslauthd internal error]
# grep — “-1765328164” /usr/include/krb5/krb5.h
#define KRB5_REALM_CANT_RESOLVE (-1765328164L)

From auth.log, you can see the error reason, but what does -1765328164 mean? `grep — “-1765328164” /usr/include/krb5/krb5.h` shows the meaning of this error code. Here, -1765328164 means that the Kerberos realm cannot be resolved, or simply put, the client cannot find the Kerberos server. In our production environment, Kerberos-related servers are configured via DNS. The corresponding bind configuration file is as follows:

plaintext

$ORIGIN _tcp.demo.local.
_kerberos                        SRV      0 0  88 scms.demo.local.
_kerberos-adm                    SRV      0 0 749 scms.demo.local.
_kpasswd                         SRV      0 0 464 scms.demo.local.
$ORIGIN _udp.demo.local.
_kerberos                        SRV      0 0  88 scms.demo.local.
_kerberos-master                 SRV      0 0  88 scms.demo.local.
_kpasswd                         SRV      0 0 464 scms.demo.local.
$ORIGIN demo.local.
_kerberos                        TXT      "DEMO.LOCAL"

Here, scms.demo.local is the Kerberos server. Let’s start with a simpler method to complete the test verification. Modify the /etc/krb5.conf file, uncomment the kdc and admin_server lines within the [realms] section, and then run the test command again.

 # testsaslauthd -u test -p 123456
0: NO "authentication failed"
linux-64 openldap # tail -n3 /var/log/auth.log
Sep 27 13:02:10 localhost saslauthd[18381]: auth_krb5: krb5_kt_read_service_key(): Key table file '/etc/krb5.keytab' not found (2)
Sep 27 13:02:10 localhost saslauthd[18381]: auth_krb5: k5support_verify_tgt
Sep 27 13:02:10 localhost saslauthd[18381]: do_auth         : auth failure: [user=test] [service=imap] [realm=] [mech=kerberos5] [reason=saslauthd internal error]

The error reason has changed this time, continue with the following commands.

 # kadmin.local -q "ank -clearpolicy -randkey host/localhost"
Authenticating as principal root/[email protected] with password.
Principal "host/[email protected]" created.
# kadmin.local -q "ktadd host/localhost"
Authenticating as principal root/[email protected] with password.
Entry for principal host/localhost with kvno 2, encryption type aes256-cts-hmac-sha1-96 added to keytab FILE:/etc/krb5.keytab.
Entry for principal host/localhost with kvno 2, encryption type aes128-cts-hmac-sha1-96 added to keytab FILE:/etc/krb5.keytab.
Entry for principal host/localhost with kvno 2, encryption type des3-cbc-sha1 added to keytab FILE:/etc/krb5.keytab.
Entry for principal host/localhost with kvno 2, encryption type arcfour-hmac added to keytab FILE:/etc/krb5.keytab.
# testsaslauthd -u test -p 123456
0: OK "Success."

At this point, the Kerberos authentication test is successful.

Next, we will test ldapsearch to see if LDAP authentication is successful:

 $ ldapsearch -x -D 'uid=test,ou=People,dc=demo,dc=local' -w 123456 -h 127.0.0.1 -b 'dc=demo,dc=local'
ldap_bind: Invalid credentials (49)

This is because we have not yet configured LDAP to use SASL authentication,

 # cat /etc/sasl2/slapd.conf
pwcheck_method: saslauthd
# /etc/init.d/slapd restart

Run ldapsearch again, and it should return the correct result this time.

Up to now, our goal of centralized user management and authentication has been achieved.

For centralized authorization, you can add relevant attributes to users in LDAP and then control it through the filter parameter of the LDAP query. For example: ldapi:///ou=People,dc=demo,dc=local?uid?sub?(&(objectClass=posixAccount)(ou=$group)), can be used to authorize only users in a specific group.

To configure applications to use LDAP authentication, generally, you need to set these parameters:

  • LDAP server address:
  • LDAP server port:
  • LDAP server protocol: SSL or TLS, or no encryption
  • LDAP BASE DN: e.g., ou=People,dc=demo,dc=local
  • LDAP filter:
  • Login name attribute: e.g., uid

The authentication process can be done by combining the login name attribute and BASEDN as the login DN, and trying to connect to the LDAP server with the user-provided password. If authentication is successful, authorization can be handled using the LDAP filter.

User and account authentication on Linux systems can also be achieved by configuring nss_ldap, pam_ldap, pam_krb5, etc.

Passwords stored in Kerberos can be set with certain policies, such as kadmin.local -q 'addpol -maxlife "90day" -minlength 8 -minclasses 3 -history 3 default' which sets the default password policy (maximum validity 90 days, minimum length 8, minimum character classes 3, etc.).

P.S. This article does not configure the kadmin service. If the related service is started, you may encounter issues where the service cannot be shut down properly. Apart from configuration errors, one possible reason is that kadmin uses /dev/random to obtain true random numbers, but the system’s entropy is insufficient, causing kadmin to block on reading /dev/random. You can increase entropy by running ping -f on another host.