Centralized authentication using OpenLDAP
This guide introduces the basics of LDAP and shows readers how to setup OpenLDAP for authentication purposes between a group of computers.
Getting started with OpenLDAP
What is LDAP?
LDAP stands for Lightweight Directory Access Protocol. Based on X.500 it encompasses most of its primary functions, but lacks the more esoteric functions that X.500 has. Now what is this X.500 and why is there an LDAP?
X.500 is a model for Directory Services in the OSI concept. It contains namespace definitions and the protocols for querying and updating the directory. However, X.500 has been found to be overkill in many situations. Enter LDAP. Like X.500 it provides a data/namespace model for the directory and a protocol. However, LDAP is designed to run directly over the TCP/IP stack. See LDAP as a slimmed-down version of X.500.
What is a directory?
A directory is a specialized database designed for frequent queries but infrequent updates. Unlike general databases they don't contain transaction support or roll-back functionality. Directories are easily replicated to increase availability and reliability. When directories are replicated, temporary inconsistencies are allowed as long as they get synchronized eventually.
How is information structured?
All information inside a directory is structured hierarchically. Even more, to enter data inside a directory, the directory must know how to store this data inside a tree. Let's take a look at a fictional company and an Internet-like tree:
dc: org
|
dc: genfic ## (Organisation)
/ \
ou: People servers ## (Organisational Units)
/ \ ..
uid: .. John ## (OU-specific data)
Since data is not fed to the database in this ASCII-art like manner, every node of such a tree must be defined. To name such nodes, LDAP uses a naming scheme. Most LDAP distributions (including OpenLDAP) already contain quite a number of predefined (and general approved) schemas, such as the inetOrgPerson, or a frequently used schema to define users which Unix/Linux boxes can use, called posixAccount
Interested users are encouraged to read the OpenLDAP Admin Guide.
What can it be used for?
LDAP can be used for various things. This document focuses on centralized user management, keeping all user accounts in a single LDAP location (which doesn't mean that it's housed on a single server, LDAP supports high availability and redundancy), yet other goals can be achieved using LDAP as well.
- Public Key Infrastructure
- Shared Calendar
- Shared Addressbook
- Storage for DHCP, DNS, ...
- System Class Configuration Directives (keeping track of several server configurations)
- Centralized Authentication (PosixAccount)
- ...
OpenLDAP server setup
Common notes
The domain example.com is an example in this guide. The domain can be renamed as suitable to the readers. However, make sure that the top node is an official top level domain (.net, .com, .cc, .be, etc.). Since LDAP does not provide encryption in transfer it is necessary to create TLS server certificates. It is common practice to relate server DNS, certificate CN and LDAP CN. For this example the server will be reachable by ldap.example.com only over ldaps://. The server certificate will be for exactly this host thus CN=ldap.example.com. For TLS see Certificates and Certificates/Become your own CA.
Base configuration
Follow OpenLDAP Server to setup the server. Use the Quick Start and TLS sections (TLS must be configured or passwords will be sent in the clear!). The directory must allows anonymous binds and access control must allow read access to anonymous users.
It is possible to create DN that gives read access to clients, however, those credentials will need to be installed on every client. Because that credential must be so widely distributed, they're not going to be much a secret. The password will be virtually impossible to change without breaking every client. Use firewalls and access control rules to restrict access instead of blocking access all anonymous access. If allowing read to anonymous users is unacceptable, either use client certificates or use Kerberos for authentication instead of LDAP
Schemas
There are 2 different schemas to choose from: rfc2307 and rfc2307bis. They are very similar - the only differ in how group membership is represented. In rfc2307, groups contain the users who are members (via the group's multi-valued memberUID attribute), In, rfc2307bis groups contain the full DN of users who are members (via the group's multi-valued member attribute).
To add a user uid=bertram,ou=People,dc=example,dc=com to the cn=IT_admins,ou=groups,dc=example,dc=com group in rfc2307 schema:
dn: cn=IT_admins,ou=groups,dc=example,dc=com
changetype: modify
add: memberUID
memberUID: bertram
To add a user uid=bertram,ou=People,dc=example,dc=com to the cn=IT_admins,ou=groups,dc=example,dc=com group in rfc2307bis schema:
dn: cn=IT_admins,ou=groups,dc=example,dc=com
changetype: modify
add: member
member: uid=bertram,ou=People,dc=example,dc=com
The rfc2307 schema is more common, and better supported than the rfc2307bis schema, but the latter has support for nested groups and back-references in the user to groups they are members of.
RFC 2037 Schema
To use the rfc2307 schema: , add the schemas (and its dependency) to the server:
root #
ldapadd -H ldapi:/// -Y EXTERNAL -f /etc/openldap/schema/cosine.ldif
root #
ldapadd -H ldapi:/// -Y EXTERNAL -f /etc/openldap/schema/nis.ldif
RFC 2037bis Schema
To use the rfc2307bis schema , it must first be converted to an LDIF file:
# The first 2 schemas are dependencies
include /etc/openldap/schema/core.schema
include /etc/openldap/schema/cosine.schema
include /etc/openldap/schema/rfc2307bis.schema
user $
mkdir myconfig
user $
slaptest -f rfc2307.conf -F myconfig
user $
sed -e '/^#/d' -e '/^dn: /s/$/,cn=schema,cn=config/g' -e 's/{[[:digit:]]*}//g' -e '/^structuralObjectClass/d' -e '/^entryUUID/d' -e '/^creatorsName/d' -e '/^createTimestamp/d' -e '/^entryCSN/d' -e '/^modifiersName/d' -e '/^modifyTimestamp/d' < myconfig/cn\=config/cn\=schema/cn\=\{2\}rfc2307bis.ldif > rfc2307bis.ldif
The cosine schema needs to be loaded on the server first otherwise next line will spit out an error: AttributeType not found: "manager"
root #
ldapadd -H ldapi:/// -Y EXTERNAL -f rfc2307bis.ldif
user $
rm -fr myconfig
The back-references (memberOf) used by this schema cannot be created manually, the combination of the dyngroups schema, the dynlist module and the dynlist overlay are needed. Note that net-nds/openldap need to compiled withe the overlays USE flag. It can all be configured in a single LDIF file:
include: /etc/openldap/schema/dyngroup.ldif
dn: cn=module,cn=config
objectClass: olcModuleList
cn: module
olcModulePath: /usr/lib64/openldap/openldap
olcModuleLoad: dynlist.so
# This assumes the directory database is number 2. Adjust as needed
dn: olcOverlay=dynlist,olcDatabase={2}mdb,cn=config
changetype: add
objectClass: olcOverlayConfig
objectClass: olcDynListConfig
OlcOverlay: dynlist
olcDynListAttrSet: groupOfURLs memberURL member+memberOf@groupOfMembers
Add the above to the server:
root #
ldapadd -H ldapi:/// -Y EXTERNAL -f add-memberOf.ldif
Removing a group will remove all back-references to the group, but removing a user will not remove their membership to the group. The dynlist overlay works well with the refint overlay, which removes "dangling" reference to users in the group when the user is removed. Note, however, if the refint module and overlay is installed on a replicating server (producer), it must also be installed on all consumers replicating from it.
UID and GID values, uniqueness, and allocation
The directory will contain "global users". The username and ID numbers used on the server must not conflict. "System" users should always be local accounts and never in LDAP. Some space should be allocated for local users. By default, the LDAP server does not enforce uniqueness on usernames, UIDs and GIDs, however its possible to use the unique module and overlay to do so.
dn: cn=module,cn=config
changetype: add
objectClass: olcModuleList
cn: module
olcModulePath: /usr/lib64/openldap/openldap
olcModuleLoad: unique.so
# This assumes the directory database is number 2. Adjust as needed
dn: olcOverlay=unique,olcDatabase={2}mdb,cn=config
changetype: add
objectClass: olcOverlayConfig
objectClass: olcUniqueConfig
olcOverlay: unique
Now create filters for the attributes:
# TThis assumes the directory database is number 2 ad the overlay is number 2. Adjust as needed
dn: olcOverlay={2}unique,olcDatabase={2}mdb,cn=config
changetype: modify
replace: olcUniqueURI
olcUniqueURI: ldap:///?uidNumber,gidNumber,mail?sub
This ensures no 2 DN the the same subtree have the same uidNumber, gidNumber, or mail attribute.
Keeping track of allocation is difficult. Here's Debian's solution.
Note, the if the unique module and overlay is installed on a replicating server (producer), it must also be installed on all consumers replicating from it.
Password policies
Kerberos users should skip this section
LDAP simply stores the password hashes, it does not enforce password policies. The password policy overlay can enforce password policies and requires the overlay USE flag on net-nds/openldap.
Install module and add the ppolicy overlay
First, the module must be added and the overlay added
# We need this schema because pwdPolicy is AUXILIARY and STRUCTRAL objectClass is needed
include: /etc/openldap/schema/namedobject.ldif
dn: cn=module,cn=config
objectClass: olcModuleList
cn: module
olcModulePath: /usr/lib64/openldap/openldap
olcModuleLoad: ppolicy.so
# This assumes the directory database is number 2. Adjust as needed
dn: olcOverlay=ppolicy,olcDatabase={2}mdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcPPolicyConfig
olcOverlay: ppolicy
olcPPolicyDefault: cn=default,ou=ppolicies,dc=example,dc=com
# Uncomment the next line to disclose "account locked" status on binds failed due to account locking
#olcPPolicyUseLockout: TRUE
# Uncomment the next line if this is a read-only replica.
#olcPPolicyForwardUpdates: TRUE
root #
ldapmodify -H ldapi:/// -Y EXTERNAL -f ppolicy-add.ldif
Password Policy Configuration
Configuration of this module is discussed in Password Policies and a detailed explanation of each parameter can be found in slapo-ppolicy(5).
The password policies are stored in the directory itself, so 2 more DNs have to be created: one for the password policies, and another for the default policy:
dn: ou=ppolicies,dc=example,dc=com
objectClass: organizationalUnit
objectClass: top
ou: ppolicies
dn: cn=default,ou=ppolicies,dc=example,dc=com
objectClass: namedPolicy
objectClass: pwdPolicy
# This attribute is required even though "userPassword" is the only supported value
pwdAttribute: userPassword
cn: default
The password policy comprises functionality of the shadow, pwhistory, faillock, and pwqdc/pwquality modules, except for password complexity, which requires an external module not in Portage.
Options available are:
- Minimum and maximum password age
- Password history
- Minimum and maximum password length
- Expiration warning
- Grace logins (both number of uses and duration based)
- Whether to enable lockout, consecutive failed bind attempts before lockout and lockout timeout
- Cooldown time between last consecutive failed bind attempts but before lockout
- Whether the user is required to change their password after an administrative reset
- Whether the old password must be sent to do a password changes
- Minimum and maximum delay (implemented as a temporary lockout) between bind attempts
- Whether to disable idle accounts (lastbind functionality needed - set olcLastBind attribute on the database in the config)
Not available, but theoretically supported by the ppolicy module:
- Password complexity
Here's an example default policy:
dn: cn=default,ou=ppolicies,dc=example,dc=com
changetype: modify
add: pwdMinAge
# 1 day
pwdMinAge: 3600
-
add: pwdMaxAge
# 42 days
pwdMaxAge: 151200
-
add: pwdInHistory
pwdInHistory: 24
-
add: pwdMinLength
pwdMinLength: 7
-
add: pwdLockout
pwdLockout: TRUE
-
add: pwdLockoutDuration
# 20 minutes
pwdLockoutDuration: 1200
-
add: pwdMaxFailure
pwdMaxFailure: 20
-
add: pwdFailureCountInterval
# 30 minutes
pwdFailureCountInterval: 3600
-
add: pwdMustChange
pwdMustChange: TRUE
-
add: pwdSafeModify
pwdSafeModify: TRUE
This policy requires a minimum password length of 7 characters, limit password changes to once a day, mandates a password change after 42 days, disallows the use of the last 24 passwords, enabled lockout after 20 attempts for 15 minutes, resets the failed counter after 30 minutes,requires the old password to be sent before changing to a new password, and requires a password change after an administrative password reset.
Password policies can be set for individual accounts. Set the pwdPolicySubentry to the DN of the policy. Note the pwdPolicySubentry aatribute is an operational attribute and will not be shown by ldapsearch unless specifically requested.
To exempt an account (for example, the replicator account) from the default password policy, create an empty password policy and set pwdPolicySubentry for the account to it:
dn: cn=empty,ou=ppolicies,dc=example,dc=com
changetype: add
objectClass: namedPolicy
objectClass: pwdPolicy
pwdAttribute: userPassword
cn: empty
dn: cn=replicator,dc=example,dc=com
changetype: modify
add: pwdPolicySubentry
pwdPolicySubentry: dn: cn=empty,ou=ppolicies,dc=example,dc=com
The RootDN ignores all password policies.
Configuring the OpenLDAP client tools
There are numerous methods/tools that can be used for remote authentication. Some distributions also have their own easy to use configuration tool. Below there are some in no particular order. It is possible to combine local users and centrally authorized accounts at the same time. This is important because, for instance, if the LDAP server cannot be accessed one can still login as root.
- sys-auth/sssd . Its primary function is to provide access to identity and authentication remote resource through a common framework that can provide caching and offline support to the system. It provides PAM and NSS modules, and in the future will support D-Bus interfaces for extended user information. It also provides a better database to store local users as well as extended user data.
- sys-auth/nss-pam-ldapd (Name Service Look up Daemon). Similar to SSSD, but older. Less dependencies and lighter, but not as full featured.
Both will be covered below with the minimum necessary configuration options to get working.
Common configuration
Edit the LDAP Client configuration file. This file is read by ldapsearch and other ldap command line tools.
BASE dc=example,dc=com
URI ldap://ldap.example.com:389/ ldap://ldap-1.example.com:389/ ldap://ldap-2.example.com:389/
TLS_CACERT /etc/openldap/ca.crt
Test the running server with the following command:
user $
ldapsearch -x -D "cn=Manager,dc=example,dc=com" -W
If errors are received, try adding -d 1023
to increase the verbosity and solve the issue.
Before starting any change to the client side authentication configuration, make sure that the LDAP server can be reached and presents the correct information. The following steps assume a user Bert Ram was created in the LDAP with login name bertram. Exchange accordingly with a user from the LDAP instance. Use the manager role with caution. But at least check with the LDAP read user role and a user that will logon to the client(s) to be configured:
user $
ldapsearch -H ldaps://ldap.example.com -b "dc=example,dc=com" -x -W -LLL (uid=bertram)
Enter LDAP Password: dn: uid=bertram,ou=people,dc=example,dc=com objectClass: inetOrgPerson objectClass: posixAccount objectClass: shadowAccount sn: bertram cn: bertram uid: bertram uidNumber: 10002 gidNumber: 10002 homeDirectory: /home/bertram loginShell: /bin/bash gecos: bertram
The above command should show the user's entry (sans password). The output will vary depending on the schema used. Test the user's password and view the initial groups
user $
ldapsearch -ZZ -H ldap://ldap.example.com -b "dc=example,dc=com" -x -LLL '(|(&(objectClass=posixAccount)(uid=bertram))(&(objectClass=posixGroup)(memberUid=bertram))(&(objectClass=posixGroup)(member=uid=bertram,ou=people,dc=example,dc=com)))'
dn: uid=bertram,ou=people,dc=example,dc=com objectClass: inetOrgPerson objectClass: posixAccount objectClass: shadowAccount sn: bertram cn: bertram uid: bertram uidNumber: 10002 gidNumber: 10002 homeDirectory: /home/bertram loginShell: /bin/bash gecos: bertram dn: cn=IT_admins,ou=groups,dc=example,dc=com objectClass: posixGroup cn: IT_admins gidNumber: 10003 memberUid: bertram
The above command should show the user's entry and any global supplementary groups they are in. The output will vary depending on the schema used.
Configuring SSSD
sys-auth/sssd is the preferred LDAP login solution. It has many features, including the ability to cache credentials, so logins are possible even if the LDAP server is offline, so as long as the user logged in successfully at least once. Note that sys-auth/sssd works with Kerberos, IPA, and Active Directory as well.
First, configure sssd:
[sssd]
config_file_version = 2
services = nss, pam
domains = example
[domain/example]
id_provider = ldap
auth_provider = ldap
ldap_uri = ldap://ldap.example.com
# Uncomment out the next line if using rfc2307bis
# ldap_schema = rfc2307bis
# This is the default starting in 2.10.0
ldap_id_use_start_tls = true
ldap_search_base = dc=example,dc=com
# If the server disallows anonymous binds, use client certificates or Kerberos instead
# For legacy setups, uncomment out the next 2 lines and replace their values appropriately
# ldap_default_bind_dn = cn=sssd,dc=example,gc=org
# ldap_default_authtok = secret
# Allows the user to login even if the LDAP server is down, as long as they've logged in at least once
cache_credentials = true
Then configure nss by appending sss to the passwd, shadow and group lines:
passwd: files sss
shadow: files sss
group: files sss
Test nss:
user $
getent passwd bertram
user $
getent shadow bertram
user $
getent group bertram
Each command should produce one line of output, unless there's a local user with the same user/group name, then there should be 2 lines per command. Enable and start the sssd service
OpenRC:
root #
rc-config add sssd default
root #
rc-service sssd enable
Systemd:
root #
systemctl enable --now sssd
Add the following to /etc/portage/package.use/00sssd:
*/* sssd
In particular, sys-auth/pambase has an (experimental!) sssd USE flag which enables SSSD support. Re-emerge the packages with changed USE flags but don't run etc-update or dispatch-conf yet.
root #
emerge -auvDU @world
As with all PAM modifications, make a backup and have at least one logged in root session, before any changes are made - whether by Portage or the user
The configuration done should work "out of the box". The configuration does not include automated home directory creation,, so no non-local logins will be allowed until a home directory is created. To enable automated home directory creation, append the following to /etc/pam.d/system-auth (please read the warning above!):
session optional pam_mkhomedir.so
Automated home directory creation does not work correctly on SELinux systems. In that case, install app-misc/oddjob (in GURU), enable and start the oddjobd daemon and replace the above line's pam_mkhomedir.so with pam_oddjob_mkhomedir.so
Test the login from another computer (using SSH), another VT (if local), or in a different root window with login and verify it succeeds (if automatic home directory creation is enabled, try it once with the directory created and again with the directory not created).
sssd supports netgroups, sudo, and automounting as well. Add the appropriate lines to /etc/nsswitch.conf and /etc/sssd/sssd.conf. See sssd.conf(5) for sssd general configuration and sssd-sudo(5) for sudo configuration.
Configuring nss-pam-ldapd
Emerge sys-auth/nss-pam-ldapd
root #
emerge --ask sys-auth/nss-pam-ldapd
Change /etc/nslcd.conf. It should contain
- URI to contact LDAP server.
- Base (general) to lookup DNs
- A statement to use TLS, otherwise password will be sent in the clear
- Base per group, passwd and/ or shadow in case your LDAP tree deviates from defaults (as mentioned in the file's comments)
In Gentoo a user has a primary group with a GID matching the user's UID. This is absolutely valid in OpenLDAP, too. In addition a user is also part of other groups, e.g. wheel, audio or usb. OpenLDAP or any other LDAP cannot associate this with the user itself. Instead the user becomes a member of the group in LDAP. The membership relation is then mapped on client side so that additional groups (=initgroups) appear after the authentication happened.
Make sure that /etc/nslcd.conf is owned by nslcd and only readable by nslcd through
chmod 400
.uid nslcd
gid nslcd
# Uncomment the next line to troubleshoot nslcd
#log syslog debug
# Enable TLS or passwords will be sent in the clear
ssl start_tls
tls_cacertfile /etc/openldap/ca.crt
# If the server disallows anonymous binds, use client certificates or Kerberos instead
# For legacy setups, uncomment out the next 2 lines and replace their values appropriately
#binddn cn=nslcd,dc=example,dc=com
#bindpw secret
# Both rfc2307 and rfc2307bis should work "out of the box" with no maps
Test nss:
user $
getent passwd bertram
user $
getent shadow bertram
user $
getent group bertram
Each command should produce one line of output, unless there's a local user with the same user/group name, then there should be 2 lines per command. Enable and start nslcd:
OpenRC:
root #
rc-service add nslcd default
root #
rc-service nslcd start
Systemd:
root #
systemctl enable --now nslcd
In /etc/nsswitch.conf the passwd, group, and shadow lines need to be appended with ldap:
passwd: files ldap
group: files ldap
shadow: files ldap
Next, configure PAM to allow LDAP authorization. PAM configurations (and the PAM configuration format itself) have gotten significantly more complex, and the advice of inserting pam_ldap after pam_unix no longer works on it own. The configuration cannot be described simply though adds and deletions, so a sample file modeled after the SSSD configuration will be used.
As with all PAM modifications, make a backup and have at least one logged in root session, before any changes are made - whether by Portage or the user
auth required pam_env.so
auth [default=1 ignore=ignore success=ok] pam_usertype.so isregular
auth [default=3 ignore=ignore success=ok] pam_localuser.so
auth requisite pam_faillock.so preauth
auth sufficient pam_unix.so nullok try_first_pass
auth [default=die] pam_faillock.so authfail
# This assumes that global users start at 10000. Replace as neeeded.
auth required pam_ldap.so nullok try_first_pass minimum_uid=10000
auth optional pam_cap.so
account required pam_unix.so
account required pam_faillock.so
account sufficient pam_localuser.so
account sufficient pam_usertype.so issystem
# This assumes that global users start at 10000. Replace as neeeded.
account [default=bad success=ok user_unknown=ignore] pam_ldap.so minimum_uid=10000
password required pam_passwdqc.so config=/etc/security/passwdqc.conf
password sufficient pam_unix.so try_first_pass use_authtok nullok sha512 shadow
# This assumes that global users start at 10000. Replace as neeeded.
password sufficient pam_ldap.so try_use_authtok nullok minimum_uid=10000
password required pam_deny.so
session required pam_limits.so
session required pam_env.so
session required pam_unix.so
# This assumes that global users start at 10000. Replace as neeeded.
session optional pam_ldap.so minimum_uid=10000
The configuration does not include automated home directory creation,, so no non-local logins will be allowed until a home directory is created. To enable automated home directory creation, append the following to /etc/pam.d/system-auth (please read the warning above!):
session optional pam_mkhomedir.so
Automated home directory creation does not work correctly on SELinux systems. In that case, install app-misc/oddjob (in GURU), enable and start the oddjobd daemon and replace the above line's pam_mkhomedir.so with pam_oddjob_mkhomedir.so
Test the login from another computer (using SSH), another VT (if local), or in a different root window with login and verify it succeeds (if automatic home directory creation is enabled, try it once with the directory created and again with the directory not created).
Convert file userbase to LDAP
On the server, enable the RootDN by setting the RootPW
Creating LDIF files by hand
Here's a template for adding user along with their group.
dn: uid=${LDAP_USERNAME},ou=people,${MY_DOMAIN_DC}
changetype: add
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
sn: ${LDAP_USERNAME}
cn: ${LDAP_USERNAME}
userPassword: {CRYPT}x
loginShell: /bin/bash
uidNumber: ${LDAP_UID}
gidNumber: ${LDAP_GID}
homeDirectory: /home/${LDAP_USERNAME}
dn: cn=${LDAP_USERNAME},ou=groups,${MY_DOMAIN_DC}
changetype: add
# Uncomment out the next line if using rfc2307bis
# objectClass: groupOfMembers
objectClass: posixGroup
cn: ${LDAP_USERNAME}
gidNumber: ${LDAP_GID}
memberUID: ${LDAP_USERNAME}
# Delete the above line and uncomment out the next line if using rfc2307bis
# member: uid=${LDAP_USERNAME},ou=people,${MY_DOMAIN_DC}
Replace the variables with sed:
user $
sed -e 's/${LDAP_USERNAME}/bertram/g' -e 's/${MY_DOMAIN_DC}/dc=example,dc=com/g' -e 's/${LDAP_UID}/10000/g' -e 's/${LDAP_GID}/10000/g' < ldap-usergroupadd-add.ldif.in > ldap-usergroupadd-add.ldif
root #
ldapmodify -H ldap://ldap.example.com -D "cn=Manager,dc=example,dc=com" -W -f ldapusergroup-add.ldif.in
Don't forget the set the user password with slapasswd:
root #
ldappasswd -H ldap://ldap.example.com -D "cn=Manager,dc=example,dc=com" -W -S uid=bertram,ou=people,dc=example,dc=com
To add just a group:
dn: cn=${LDAP_GROUPNAME},ou=groups,${MY_DOMAIN_DC}
changetype: add
# Uncomment out the next line if using rfc2307bis
# objectClass: groupOfMembers
objectClass: posixGroup
cn: ${LDAP_GROUPNAME}
gidNumber: ${LDAP_GID}
Replace the variables with sed:
user $
sed -e 's/${LDAP_GROUPNAME}/admin/g' -e 's/${MY_DOMAIN_DC}/dc=example,dc=com/g' -e 's/${LDAP_GID}/20000/g' < ldap-group-add.ldif.in > ldap-group-add.ldif
root #
ldapmodify -H ldap://ldap.example.com -D "cn=Manager,dc=example,dc=com" -W -f ldap-group-add.ldif
For test purposes, this is doable, however doing this with more than a dozen or so entries gets tedious, and is unworkable with hundreds or thousands for users.
Bulk import
There were scripts that imported users from the passwd/shadow/database into LDAP, but those scripts have bit-rotted. THere isn't a widely distributed program to do this. The files with the import data are easy to parse and likewise it is easy to create LDIF file. The following bash shell script will get the job done:
#!/bin/bash
help() {
printf "Usage $0: [OPTIONS] <MY_DOMAIN_DC>\n"
printf -- " -u --uid-start=NUMBER. Where to start uidNumber numbering in LDAP (default 10000)\n"
printf -- " -g --gid-start=NUMBER. Where to start gidNumber numbering in LDAP (default 10000)\n"
printf -- " -s --schema=SCHEMA. Schema to use, either \"RFC2307\" or \"RFC2307bis\" (default RFC2307)\n"
printf -- " -h --help. This message\n"
printf "An LDAP directory component is required! (e.g. \"dc=example,dc=com\")\n"
exit 1
}
declare -i LOCAL_UID_START LOCAL_UID_END LOCAL_GID_START LOCAL_GID_END LDAP_UID_START LDAP_GID_START
declare LDAP_SCHEMA MY_DOMAIN_DC
LOCAL_UID_START=$(awk '/^UID_MIN/ {print $2}' /etc/login.defs)
LOCAL_GID_START=$(awk '/^GID_MIN/ {print $2}' /etc/login.defs)
LOCAL_UID_END=$(awk '/^UID_MAX/ {print $2}' /etc/login.defs)
LOCAL_GID_END=$(awk '/^GID_MAX/ {print $2}' /etc/login.defs)
if ! OPTS=$(getopt -o s:u:g:h --long schema:,uid-start:,gid-start:,help -n "$0" -- $@)
then
exit 1
fi
eval set -- "${OPTS}"
while true
do
case "$1" in
-h|--help)
help
;;
-s|--schema)
if [ "${2^^}" = "RFC2307" ]
then
LDAP_SCHEMA="RFC2307"
elif [ "${2^^}" = "RFC2307BIS" ]
then
LDAP_SCHEMA="RFC2307BIS"
else
help
fi
shift 2
;;
-u|--uid-start)
if [ "$2" -eq "$2" ] && [ "$2" -ge "${LOCAL_UID_START}" ]
then
LDAP_UID_START=$(( "$2" ))
else
printf "Invalid value for --uid-start\n"
help
fi
shift 2
;;
-g|--gid-start)
if [ "$2" -eq "$2" ] && [ "$2" -ge "${LOCAL_GID_START}" ]
then
LDAP_GID_START=$(( "$2" ))
else
printf "Invalid value for --gid-start\n"
help
fi
shift 2
;;
--)
shift
break
;;
esac
done
if [ $# -ne 1 ]
then
help
fi
MY_DOMAIN_DC="$1"
LDAP_UID_START="${LDAP_UID_START:-10000}"
LDAP_GID_START="${LDAP_GID_START:-10000}"
LDAP_SCHEMA="${LDAP_SCHEMA:-RFC2307}"
while read F
do
declare LOCAL_UID LOCAL_GID LDAP_USERNAME LDAP_PASSWORD LDAP_GECOS \
LDAP_HOMEDIR LDAP_LOGIN_SHELL
LOCAL_UID=$(echo "${F}" | awk 'BEGIN { FS=":" } {print $3 }')
if [ "${LOCAL_UID}" -lt "${LOCAL_UID_START}" ] || \
[ "${LOCAL_UID}" -gt "${LOCAL_UID_END}" ]
then
continue
fi
LOCAL_GID="$(echo "${F}" | awk 'BEGIN { FS=":" } {print $4 }')"
LDAP_USERNAME="$(echo "${F}" | awk 'BEGIN { FS=":" } {print $1 }')"
LDAP_PASSWORD="$(awk "BEGIN { FS=\":\" } /^${LDAP_USERNAME}/ { if ( \$2 ) { print \"{CRYPT}\"\$2 } }" /etc/shadow)"
LDAP_GECOS="$(echo ${F} | awk "BEGIN { FS=\":\" } { if ( \$5 ) { print \$5 } else { print \"${LDAP_USERNAME}\" } }")"
LDAP_HOMEDIR="$(echo "${F}" | awk 'BEGIN { FS=":" } {print $6 }')"
LDAP_LOGIN_SHELL="$(echo "${F}" | awk 'BEGIN { FS=":" } {print $7 }')"
printf "dn: uid=%s,ou=People,${MY_DOMAIN_DC}\n" "${LDAP_USERNAME}"
printf "changetype: add\n"
printf "objectClass: inetOrgPerson\n"
printf "objectClass: posixAccount\n"
printf "objectClass: shadowAccount\n"
printf "sn: %s\n" "$(echo ${LDAP_GECOS} | awk '{print $NF}')"
printf "cn: %s\n" "${LDAP_GECOS}"
printf "uid: %s\n" "${LDAP_USERNAME}"
printf "uidNumber: %i\n" "$(( ${LDAP_UID_START} + ${LOCAL_UID} - ${LOCAL_UID_START} ))"
printf "gidNumber: %i\n" "$(( ${LDAP_GID_START} + ${LOCAL_GID} - ${LOCAL_GID_START} ))"
printf "homeDirectory: %s\n" "${LDAP_HOMEDIR}"
printf "loginShell: %s\n" "${LDAP_LOGIN_SHELL}"
printf "gecos: %s\n" "${LDAP_GECOS}"
if [ -n "${LDAP_PASSWORD}" ]
then
printf "userPassword: %s\n" "${LDAP_PASSWORD}"
fi
printf "\n"
done < /etc/passwd
while read F
do
declare LOCAL_GID LDAP_GROUPAME
declare -a LDAP_MEMBERSHIP
LOCAL_GID="$(echo "${F}" | awk 'BEGIN { FS=":" } {print $3 }')"
if [ "${LOCAL_GID}" -lt "${LOCAL_GID_START}" ] || \
[ "${LOCAL_GID}" -gt "${LOCAL_GID_END}" ]
then
continue
fi
LDAP_GROUPNAME="$(echo ${F} | awk 'BEGIN { FS=":" } {print $1 }')"
read -a LDAP_MEMBERSHIP -d, < <(echo ${F} | awk 'BEGIN { FS=":" } {print $4 }')
printf "dn: cn=%s,ou=groups,${MY_DOMAIN_DC}\n" "${LDAP_GROUPNAME}"
printf "changetype: add\n"
if [ "${LDAP_SCHEMA}" = "RFC2307BIS" ]
then
printf "objectClass: groupOfMembers\n"
fi
printf "objectClass: posixGroup\n"
printf "cn: %s\n" "${LDAP_GROUPNAME}"
printf "gidNumber: %i\n" "$(( ${LDAP_GID_START} + ${LOCAL_GID} - ${LOCAL_GID_START} ))"
if [ -n "${LDAP_MEMBERSHIP[$@]}" ]
then
for I in "${LDAP_MEMBERSHIP[$@]}"
do
MEMBER_GROUP_UID=$(getent passwd "${I}" | awk 'BEGIN { FS=":" } {print $3 }')
if [ "${MEMBER_GROUP_UID}" -ge "${LOCAL_UID_START}" ] || \
[ "${MEMBER_GROUP_UID}" -le "${LOCAL_UID_END}" ]
then
if [ "${LDAP_SCHEMA}" = "RFC2307" ]
then
printf "memberUID: %s\n" "${I}"
else
printf "member: uid=%s,ou=people,${MY_DOMAIN_DC}\n" "${I}"
fi
fi
done
fi
printf "\n"
done < /etc/group
The above script must be run as root to import. it generate a LDIF file that can be imported via ldapmodify or slapmodify. The schema, UID, and GID stating point are optional an be specified via options. The directory component string (dc=example,dc=com) is a required argument.
Troubleshooting
Emerge errors after conversion to LDAP
If for any reasons local user accounts (i.e. /etc/passwd /etc/shadow) or groups (i.e. /etc/group) are deleted after converting the file userbase to LDAP, errors may be encountered relating to missing user (or group) while emerging certain packages.
Example of error while emerging www-servers/apache due to missing "apache" local user account:
root #
emerge -1 www-servers/apache
... Installing build system files make[1]: Leaving directory '/var/tmp/portage/www-servers/apache-2.4.59-r1/work/httpd-2.4.59' chown: invalid user: ‘apache:apache’ * ERROR: www-servers/apache-2.4.59-r1::gentoo failed (install phase): * fowners failed
In such cases, re-emerge the local group and user package:
root #
emerge -1 acct-group/apache acct-user/apache
Acknowledgements
We would like to thank Matt Heler for lending us his box for the purpose of this guide. Thanks also go to the cool guys in #ldap (webchat) on the Libera Chat IRC network.
This page is based on a document formerly found on our main website gentoo.org.
The following people contributed to the original document: Benjamin Coles, Sven Vermeulen (SwifT) , Brandon Hale, Benny Chuang, jokey,
They are listed here because wiki history does not allow for any external attribution. If you edit the wiki article, please do not add yourself here; your contributions are recorded on each article's associated history page.