As part of my OpenLDAP under Ubuntu Linux project, this post documents configuring Linux client authentication and authorization through LDAP, using Pluggable Authentication Modules (PAM) and Name Service Switch (NSS). As with my previous posts, this post was written against Ubuntu Linux's latest release, 10.10 ("Maverick Meerkat").
As Linux uses numeric IDs for users and groups (in separate domains), I highly recommend creating a numbering strategy to keep things organized. Per existing Debian policy:
- 9 - 99: Reserved for local system / package accounts, and should be consistent across systems.
(For example, 0 is "
- 100 - 999: Typically for dynamically allocated system accounts.
- 1,000 - 59,999: Dynamically allocated user accounts.
- (For the first user created on an Ubuntu system, the user ID and primary group ID are probably both 1,000.)
- 65,000 - 65,533: Reserved for global allocation by the Debian project.
- 65,534: The "
nobody" user and "
Before any attempts are considered to organize the available user IDs into blocks, according to the above list, this would leave only 59,000 valid IDs for actual users. This shouldn't be a problem for many organizations, but what if a larger organization needed all of their employees on a large network in the same domain? (There are quite a few companies with over 250,000 employees.) Apparently, the 16-bit limit is only a recommendation, with a current maximum limit of 32 bits. However, 32-bit systems may treat this as signed, allowing for a safe total of 31 bits, or just over 2 billion accounts. So I'm not going to worry about this any further for now, knowing that IDs 65,536 - 2,147,483,648 should be available for future use if necessary.
For now, I'm breaking down the above 1,000+ range as follows:
- 1,000 - 1,999: Local accounts, not in LDAP.
- 2,000 - 2,999: Administrative groups, in LDAP.
- 3,000 - 3,999: Service accounts (users and groups), in LDAP.
- 4,000 - 4,999: Reserved for future use.
- 5,000 - 59,999: Actual users.
At a minimum, this offers one significant benefit. As LDAP is a network service, the unfortunate reality is that it will go down on occasion. If all the accounts were in LDAP, there would be no way to access the systems while LDAP is unavailable. Linux can be configured to look for accounts both locally, as well as in LDAP, which will be detailed below. There should be at least one account created locally in the 1,000 - 1,999 range for emergency use independent of LDAP.
Before making any configuration changes, review what is returned by the "
getent passwd" and "
getent group" commands.
Nothing should be visible from LDAP yet, only locally-defined users and groups.
For simplicity, I'd like to install only the NSS support (provided by
libnss-ldap) to start with, then review the outputs of
getent before continuing with PAM.
libpam-ldap (for PAM) in its dependency hierarchy, so, I guess we'll just take everything together in one batch:
sudo apt-get install libpam-ldap libnss-ldap
During configuration of
ldap-auth-config, answer the following prompts:
- LDAP server Uniform Resource Identifier:
- This assumes that the LDAP server is running on the same machine. Any valid LDAP URL can be used here.
- For any serious usage, or any usage with real user accounts hosted on a seperate LDAP server, this needs to be properly configured to use
ldaps://(SSL/TLS) to protect user credentials between the client and the LDAP server.
- See below for a current issue with using
- Distinguished name of the search base:
dc=example,dc=com(Default uses "
- LDAP version to use:
- Make local root Database admin:
No(Default is Yes)
- Does the LDAP database require login?
Yes(Default is No)
- Unprivileged database user:
cn=ldap.proxy,ou=serviceAccounts,dc=example,dc=com(Default is "
- Password for database login account:
- Local crypt to use when changing passwords:
/etc/ldap.conf, which did not exist prior to this configuration.
(The closest base reference I can find is
To re-run the prompted configuration (if necessary), run "
Now NSS needs to be configured.
/etc/nsswitch.conf to use "
files ldap" for the "
group", and "
shadow" databases instead of the existing "
This can be done automatically by running "
sudo auth-client-config -t nss -p lac_ldap".
getent commands, and combined results from both the local accounts as well as LDAP should now be visible.
It appears that simply by installing one of the above packages (probably either
ldap-auth-config), PAM was also already configured with default LDAP extensions.
Logins can be attempted by using "
Attempting to login as a user that does not exist either locally or in LDAP should return a "Unknown id: " error message.
Otherwise, a "Password: " prompt should be displayed, and using a password that matches an account configured in LDAP should successfully change to that user.
(Alternatively, use "
sudo login" for a more comprehensive login and test.)
To see what was configured in PAM for LDAP, here is a diff comparing "
/etc/pam.d" before and after this configuration, basically showing the additions using "
diff -ur before/common-account after/common-account --- before/common-account 2010-12-29 17:14:02.264282406 -0600 +++ after/common-account 2010-12-29 17:14:36.554657351 -0600 @@ -14,7 +14,8 @@ # # here are the per-package modules (the "Primary" block) -account [success=1 new_authtok_reqd=done default=ignore] pam_unix.so +account [success=2 new_authtok_reqd=done default=ignore] pam_unix.so +account [success=1 default=ignore] pam_ldap.so # here's the fallback if no module succeeds account requisite pam_deny.so # prime the stack with a positive return value if there isn't one already; diff -ur before/common-auth after/common-auth --- before/common-auth 2010-12-29 17:14:02.264282406 -0600 +++ after/common-auth 2010-12-29 17:14:36.554657351 -0600 @@ -14,7 +14,8 @@ # pam-auth-update(8) for details. # here are the per-package modules (the "Primary" block) -auth [success=1 default=ignore] pam_unix.so nullok_secure +auth [success=2 default=ignore] pam_unix.so nullok_secure +auth [success=1 default=ignore] pam_ldap.so use_first_pass # here's the fallback if no module succeeds auth requisite pam_deny.so # prime the stack with a positive return value if there isn't one already; diff -ur before/common-password after/common-password --- before/common-password 2010-12-29 17:14:02.264282406 -0600 +++ after/common-password 2010-12-29 17:14:36.554657351 -0600 @@ -22,7 +22,8 @@ # pam-auth-update(8) for details. # here are the per-package modules (the "Primary" block) -password [success=1 default=ignore] pam_unix.so obscure sha512 +password [success=2 default=ignore] pam_unix.so obscure sha512 +password [success=1 user_unknown=ignore default=die] pam_ldap.so use_authtok try_first_pass # here's the fallback if no module succeeds password requisite pam_deny.so # prime the stack with a positive return value if there isn't one already; diff -ur before/common-session after/common-session --- before/common-session 2010-12-29 17:14:02.264282406 -0600 +++ after/common-session 2010-12-29 17:14:36.554657351 -0600 @@ -22,5 +22,6 @@ session required pam_permit.so # and here are more per-package modules (the "Additional" block) session required pam_unix.so +session optional pam_ldap.so session optional pam_ck_connector.so nox11 # end of pam-auth-update config diff -ur before/common-session-noninteractive after/common-session-noninteractive --- before/common-session-noninteractive 2010-12-29 17:14:02.264282406 -0600 +++ after/common-session-noninteractive 2010-12-29 17:14:36.554657351 -0600 @@ -22,4 +22,5 @@ session required pam_permit.so # and here are more per-package modules (the "Additional" block) session required pam_unix.so +session optional pam_ldap.so # end of pam-auth-update config
Here are some additional (manual) modifications, in addition to the above, and described below:
diff -ur after//common-account custom//common-account --- after//common-account 2010-12-29 17:14:36.554657351 -0600 +++ custom//common-account 2010-12-29 17:17:22.626472292 -0600 @@ -14,8 +14,13 @@ # # here are the per-package modules (the "Primary" block) -account [success=2 new_authtok_reqd=done default=ignore] pam_unix.so -account [success=1 default=ignore] pam_ldap.so +#account [success=2 new_authtok_reqd=done default=ignore] pam_unix.so +#account [success=1 default=ignore] pam_ldap.so + +account [success=1 default=ignore] pam_succeed_if.so uid < 2000 quiet +account [success=2 user_unknown=ignore default=bad] pam_ldap.so +account [success=1 new_authtok_reqd=done default=ignore] pam_unix.so + # here's the fallback if no module succeeds account requisite pam_deny.so # prime the stack with a positive return value if there isn't one already; diff -ur after//common-password custom//common-password --- after//common-password 2010-12-29 17:14:36.554657351 -0600 +++ custom//common-password 2010-12-29 17:17:22.626472292 -0600 @@ -22,8 +22,13 @@ # pam-auth-update(8) for details. # here are the per-package modules (the "Primary" block) -password [success=2 default=ignore] pam_unix.so obscure sha512 -password [success=1 user_unknown=ignore default=die] pam_ldap.so use_authtok try_first_pass +#password [success=2 default=ignore] pam_unix.so obscure sha512 +#password [success=1 user_unknown=ignore default=die] pam_ldap.so use_authtok try_first_pass + +password [success=1 default=ignore] pam_succeed_if.so uid < 2000 quiet +password [success=2 user_unknown=ignore default=die] pam_ldap.so try_first_pass +password [success=1 default=ignore] pam_unix.so obscure sha512 + # here's the fallback if no module succeeds password requisite pam_deny.so # prime the stack with a positive return value if there isn't one already; diff -ur after//common-session custom//common-session --- after//common-session 2010-12-29 17:14:36.554657351 -0600 +++ custom//common-session 2010-12-29 17:17:22.626472292 -0600 @@ -22,6 +22,7 @@ session required pam_permit.so # and here are more per-package modules (the "Additional" block) session required pam_unix.so +session required pam_mkhomedir.so session optional pam_ldap.so session optional pam_ck_connector.so nox11 # end of pam-auth-update config
As mentioned above, and partially inspired by http://wiki.debian.org/LDAP/PAM, the edits in
common-password configure LDAP to not be used for accounts with a UID below 2,000.
However, the above approach seems a little cleaner, as
pam_succeed_if is used first as the conditional, and then channels the request to either
pam_unix as appropriate, without having to ignore "user_known" return statuses from LDAP for local users.
I've tested this and can confirms that it also successfully allows local logins if the LDAP server is unavailable.
(I haven't tested the other configuration, but it looks like if LDAP can't return "user_unknown", it will default to a "bad" result, causing local logins to also fail.)
The final edit in
common-sesson simply adds
pam_mkhomedir.so to create home directories at login as necessary.
Two other references that I found helpful were "LDAPClientAuthentication" in the Ubuntu Community Documentation, and Brian White's "LDAP Authentication" LinuxWiki.
Now that everything should be mostly working, there are a few further enhancements and fixes that can be made:
First, it may not be desirable for everyone in LDAP to have access to every LDAP-enabled client machine.
/etc/ldap.conf provides several options for limiting which accounts can be used from the current machine.
Options include "
pam_min_uid", and "
I find "
pam_groupdn" to be the most effective and useful, especially after setting "
pam_member_attribute" to "
memberUid" to allow reusing
Next (as mentioned above), is an issue with using
This is really only applicable (possible) when running the LDAP server on the same machine as a client.
Basically, "connection_read(): no connection!" warnings are logged when using
ldapi:///, which are resolved simply by switching over to
This actually appears to be an issue with the
libldap client library provided by OpenLDAP itself (2.4.21), and not the
This was reported in full detail as OpenLDAP ITS # 6548.
Even after being confirmed on the same ticket with a follow-up by Gerald Turner, the ticket was promptly closed without proper justification, as far as I can understand.
This is now open as Ubuntu bug # 594840, where it is marked as confirmed and affecting at least 7 people as of this writing.
Binary package hint:
pam_group module allows assigning groups based on group membership. This is handy, if for example you want to automagically enable various rights on a specific server dependent on a users membership in a given netgroup. The following line in
/etc/security/group.conf makes a user part of the admin group on the machine if they are part of the net group developers.
Unfortunately, this only works for NIS net groups. This isn't much help if your network groups are held in LDAP, which are recognized as normal NSS groups.
Patch is attached which fixes this. NSS groups are recognized by '%'. Ie;
Reportedly, this functionality has been included in PAM 1.1.2. Thanks to Edward for his involvement in providing a solution for this! (As of this writing, Ubuntu Maverick is still using version 1.1.1.)