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.