OpenLDAP Workshop

From Training Material
Jump to navigation Jump to search


title
OpenLDAP Workshop
author
Alexander Patrakov

Course plan ⌘

  • Day 1
    • Introduction
    • How to install OpenLDAP
      • Note: the setup on CentOS will be INSECURE - all passwords exposed to anonymous
    • What to store there
  • Day 2
    • How to configure, secure and fine-tune it
  • Day 3
    • Various multi-server use cases

LDAP compared to databases ⌘

  • Designed with "directory services" in mind
    • Borrows a lot from X.500
  • Hierarchical
  • Decentralized
    • Through referrals and replication
  • Schema-driven
  • ASN1-based binary protocol

LDAP RFCs ⌘

  • Old RFCs were RFC2251 - RFC2256 and others
    • Developed in 1997
  • The big clarification and rewrite of LDAP RFCs happened in 2006
    • LDAP: Technical Specification Road Map RFC4510
    • ...
    • LDAP: Content Synchronization Operation RFC4533

LDAP servers ⌘

  • Michigan-based
    • OpenLDAP
    • ReOpenLDAP
    • 389 Directory Server
  • Java-based
    • ApacheDS
    • OpenDJ
  • Platform-specific
    • ActiveDirectory
    • SAMBA 4
  • There are others

Installation of OpenLDAP ⌘

  • Always use the system's package manager
    • apt install slapd
    • yum install openldap-servers
  • This way, you'll get timely security updates
    • OpenLDAP developers don't always release new versions if the vulnerability is only minor
    • Your distribution's maintainers are subscribed to the private "distros" list and get notified in advance
    • There is no way for you to subscribe
    • Recent vulnerabilities
      • That's mostly about crashes
  • Your distribution has an old version?
    • Doesn't really matter, as long as security patches are applied on top
    • Changelog lists mostly crashes in conditions that you won't hit

Installation from source ⌘

  • Just a ./configure --lots-of-options && make && make install
  • See how major distros do it
    • Debian
    • CentOS 7
      • Both Debian and CentOS also apply dozens of patches!
      • The most important difference is the TLS backend
    • Arch

Known legal issues ⌘

  • OpenLDAP BDB and HDB backends use Berkeley DB
    • Berkeley DB version 6.0 has switched to AGPL
      • Requires you to make the source code available if you run a service accessible by others
      • Not acceptable
  • OpenLDAP libraries link to SSL/TLS libraries
    • OpenSSL license is incompatible with GPL
      • Problem for GPLed applications that use LDAP libraries
      • Solutions:
        • Declare OpenSSL to be a system library (legally questionable but done in Arch, Fedora and CentOS)
        • Link to GnuTLS (done in Debian)
        • Link to Mozilla NSS (was done before in Fedora & CentOS, no longer supported)

Configuration mechanisms ⌘

  • /etc/openldap/slapd.conf: the configuration file
    • Obsolete, not even supplied by most distributions by default
    • Still handy because configuration works in terms of the desired state
  • /etc/openldap/slapd.d/: the "configuration" directory
    • Initially empty on Arch, pre-populated in other distributions
    • Not intended for editing manually ­– use ldapmodify with cn=config
      • Changes apply without restarting – that's the supposed benefit
      • Counter-intuitive: you formulate desired changes, not the desired state itself
  • /etc/openldap/slapd.ldif (on Arch): file that you can use for such import (placed elsewhere in other distributions)
    • CentOS 7: /usr/share/openldap-servers/slapd.ldif
  • /etc/openldap/schema/: the schema files
    • *.schema are for including into slapd.conf, *.ldif is for importing into cn=config

Package contents ⌘

  • /usr/bin/ldap{search,add,modify,delete,...}: command-line clients
    • They talk to the LDAP server via network
  • /usr/bin/slapd: the server
    • Listens to the network, reads/modifies its database
      • The database is typically in /var/lib/openldap/ or /var/lib/ldap/
  • /usr/bin/slap{add,cat,index,...}: command-line tools
    • They interact with the database directly
  • /usr/lib/openldap/*.so: overlays, backends and plugins as loadable modules
  • /usr/lib/libldap{,_r}.so: client libraries
    • _r means "reentrant"
  • /usr/lib/liblber.so: library that implements LDAP Basic Encoding Rules
    • Mostly an implementation detail
  • /usr/include/{ldap,ldif,lber}.h: C headers
  • Finally, there are manual pages

Packages in CentOS 7 ⌘

  • openldap: contains libraries
  • openldap-devel: headers
  • openldap-servers: servers, modules and slap* tools
  • openldap-clients: ldap* clients

Default configuration in CentOS 7 ⌘

  • Based on cn=config
    • pre-existing slapd.conf converted automatically
    • template slapd.conf not even supplied
    • If you try to apply your old habits, restore slapd.conf, and remove slapd.d, it will be converted again during the first package upgrade
      • Other distributions are not that evil
  • Mentions things like dc=my-domain,dc=com that we'll need to change
  • Does not specify the password

slapd.conf... ⌘

  • slapd.conf
    • Changes require restart
    • Friendly to configuration management
    • Can be "replicated" manually if needed
    • Can be converted to cn=config at any time
      • mkdir slapd.d ; slaptest -f slapd.conf -F slapd.d (do it somewhere in your home directory)
    • Many documented examples exist
    • Easy to undo mistakes
      • If you are sure that nothing in your database depends on them
    • Will stop working in OpenLDAP 2.5

...vs cn=config ⌘

  • cn=config
    • Many (but not all) changes can be applied dynamically
    • Configuration management tools have to be taught how to apply changes
      • May be non-trivial — the tools are about copying the desired configuration files, not about applying changes
    • Can be replicated automatically
    • No way back to slapd.conf except creating one manually, converting, and trying to match
    • A lot of documentation is available only in slapd.conf format
    • Some mistakes (like an unneeded module) cannot be undone at all
      • Because your database likely depends on them already
    • Is our dark future

If you still want to use slapd.conf in CentOS 7 ⌘

  • You have to write it yourself
    • Good template (still needs some minor manual cleanup and addition of schema files):
sed 's@^\(#\?\)olc\([^:]*\):@\1\L\2@' slapd.ldif | grep -v ':' > slapd.conf
  • Alternatives:
  • You have to keep a dummy /etc/openldap/slapd.d/cn=config.ldif file
    • Keep it invalid so that tools fall back to slapd.conf
# Dynamic configuration is not used
dn: cn=config
  • Add to /etc/sysconfig/slapd:
SLAPD_OPTIONS="-f /etc/openldap/slapd.conf"

Initial database creation ⌘

  • This step is fully automated in all distributions
  • Useful only if you screw up your dev server and want a fresh start
  • Also this is how you restore backups
mkdir -m 0700 /etc/openldap/slapd.d
slapadd -F /etc/openldap/slapd.d -n 0 -l /path/to/slapd.ldif
slapadd -F /etc/openldap/slapd.d -l /path/to/data.ldif
chown -R ldap:ldap /etc/openldap/slapd.d /var/lib/openldap
  • Adjust the paths as needed on your distribution
  • Here is the default upstream slapd.ldif
    • Needs directories adjusted
    • A copy is usually installed somewhere in either /usr or /etc by your distribution
    • You can tweak it before adding to slapd.d

Starting the server ⌘

  • Assuming that the configuration is good-enough:
    • service slapd start
    • This, essentially, runs /usr/bin/slapd -u ldap -g ldap
    • Use -h "ldap://10.0.3.45 ldap://192.0.2.1 ldapi://" to listen on specific sockets
      • The default port is 389
      • "ldapi" means UNIX-domain socket, commonly used for editing cn=config
      • "ldaps" means "LDAP over TLS on port 636", deprecated in favor of STARTTLS on port 389
    • Options are usually adjustable in a distribution-specific startup file
  • The default configuration in CentOS 7 is good enough to reconfigure the server using ldapmodify
  • The default database is empty, so we can freely change the domain

Changing the domain ⌘

  • Create an LDIF file describing the change
dn: olcDatabase={2}hdb,cn=config
changetype: modify
replace: olcSuffix
olcSuffix: dc=training,dc=com
-
replace: olcRootDN
olcRootDN: cn=Manager,dc=training,dc=com
  • Apply the modifications using ldapmodify as root
# ldapmodify -Y EXTERNAL -H ldapi:// -f change-domain.ldif

Setting root password ⌘

  • Hash the password (optional)
# slappasswd
  • Create an LDIF file describing the change
dn: olcDatabase={2}hdb,cn=config
changetype: modify
add: olcRootPW
olcRootPW: {SSHA}.....
-
  • Apply the modifications using ldapmodify as root
# ldapmodify -Y EXTERNAL -H ldapi:// -f set-password.ldif

Making a hole in the firewall ⌘

  • LDAP uses port 389
# firewall-cmd --permanent --add-service=ldap
  • We'll also use Apache for phpLDAPadmin
# firewall-cmd --permanent --add-service=http
# firewall-cmd --permanent --add-service=https
  • Allow Apache to connect to LDAP
# setsebool httpd_can_connect_ldap on
# setsebool -P httpd_can_connect_ldap on
  • Apply the changes
# firewall-cmd --reload

Notes on root DN and password ⌘

  • The DN does not need to actually exist in order to work
  • If it does exist, both the password from its userPassword attribute and from olcRootPW work!
    • Think about olcRootPW (or maybe even as olcRootDN) as a backdoor
    • Such temporary backdoor is useful for initial population of the database
    • An alternative is to do the initial population via slapadd

LDAP data model ⌘

  • Each entry has attributes
    • Mandatory or optional – defined by the schema
      • Actually, by objectClasses
    • Multiple values permitted unless the schema says no
    • Type depends on the name
  • Typically, one of the attributes is chosen as a "relative distinguished name" (RDN)
    • Format: name=value, e.g.: uid=john
  • There is exactly one parent, except for roots
  • Full distinguished name of the record = RDN + "," + parent's DN
    • E.g.: uid=john, ou=employees, dc=acme, dc=org

Attribute types, syntaxes and object classes ⌘

  • Parts of the schema
  • Syntaxes define what values are valid for an attribute
  • Matching rules specify how to compare attribute values
    • Both are ultimately encoded by OIDs
  • attributeTypes define attribute OID and name, and describe the syntax that the attribute uses, as well as matching rules
  • objectClasses define class OID and name, and describe the mandatory and permissible attributes
    • They can be either structural or auxiliary
    • Inheritance (SUP) is supported
    • An entry must have exactly one structural objectClass that is not a SUP of other structural objectClasses used in this entry

Populating the directory with fake data ⌘

  • Use LDIF Generator (in Java)
    • Generates inetOrgPerson and organizationalUnit
  • Or another LDIF Generator (in C)
    • Generates POSIX accounts and groups
  • There are other alternatives
  • You can write your own

General-purpose graphical clients ⌘

Special-purpose clients ⌘

Other useful tools ⌘

  • ldapvi
    • Handy on cn=config, too!
    • ldapvi -Y EXTERNAL -h ldapi:/// -b cn=config --may
  • tcpdump and wireshark

Most common use cases ⌘

  • Authentication backend
    • OwnCloud, Drupal, UNIX accounts, mail servers, ...
  • Address book
    • Thunderbird, other clients
  • Storage for variable data
    • For DHCP leases, DNS zones, ...


LDAP operations ⌘

  • RFC4511 defines the following operations:
    • Bind (supplies authentication credentials)
    • Unbind (closes connection - not the reverse of Bind)
    • Unsolicited Notification (not really an operation...)
    • Search
    • Modify
    • Add
    • Delete
    • Modify DN
    • Compare
    • Abandon (i.e. cancel a pending operation)
    • Extended Operation (catch-all for protocol extensions)
    • StartTLS

Searching from command line ⌘

  • Use ldapsearch
    • Use authentication-related parameters from the next slides
    • Specify the protocol and host using "-H"
    • Specify the base DN using "-b"
    • Specify the scope using "-s" ("base", "one", "sub")
    • Then, specify the filter and the wanted attributes
      • The default filter is "(objectclass=*)" which matches everything, and by default all "user attributes" (i.e. those in the schema, and not those made up by the LDAP server) are returned


Filters ⌘

  • A filter is a tree of attribute value assertions connected by boolean operations
  • An attribute value assertion is written as: (attribute op value)
    • E.g.: (uid=1000)
  • Available operators
    • Comparison with the value: attr=val attr>=val attr<=val
    • Approximate matching: attr~=val
    • Substring testing: attr=a*b*c*d
    • Testing attribute presence: attr=*
  • To combine several assertions using a logical OR, write: (|(uid=1000)(uid=1001)(uid=1002))
  • Logical AND becomes "&", logical NOT looks like this: (!(uid=1000))
  • On the wire, filters are represented differently

Three-valued logic ⌘

  • An attribute value assertion may be true, false, or undefined
    • The assertion is true if it is satisfied by at least one value of the attribute
    • The assertion is undefined if the attribute is not in the schema, the asserted value is invalid, or something else does not make sense
    • The assertion is false if the attribute is known, but none of the values satisfy it

Attribute specification ⌘

  • Just name the wanted attributes
    • Operational or not
  • Or say "dn" if you only want the DN
  • "*" means all non-operational attributes
  • "+" means all operational attributes

Authentication ⌘

  • The client supplies credentials during the "Bind" operation
    • Anonymous bind: just that
      • ldapsearch -x
    • Simple bind: with DN and password (the common case)
      • ldapsearch -x -D <dn> -W
      • OpenLDAP will verify the password against the "userPassword" attribute of the given DN
        • The attribute is multivalued, here is how to have multiple passwords per account
    • SASL bind: think of it as "something complex" or "something external"
      • See next slide
      • Not commonly supported by clients

SASL Authentication ⌘

  • Available mechanisms (selectable by -Y):
    • LOGIN and PLAIN
      • username (-U) and password (-W) sent in clear text
      • no benefit over simple bind
    • CRAM-MD5 and DIGEST-MD5
      • password not sent in clear text
        • but stored as clear text on the server (even worse!)
      • DIGEST-MD5 is deprecated by RFC6331
    • Kerberos (GSSAPI)
      • Out of scope for this workshop
    • EXTERNAL
      • Usually means "uid+gid" on UNIX-domain sockets, or client TLS certificate
      • Most commonly used with cn=config, to express the notion "root can edit config"

Authentication in your application ⌘

  • So, your application wants to authenticate users using LDAP
  • This is typically a two-step process
  • First, given a username, construct a full DN
    • Use the application-specific service account and search for this user
    • Alternatively, just blindly construct the DN if you know where all your users are
  • Then, attempt to bind with the just-obtained DN and the user-entered password
    • If the password is not empty, and the bind works, then the user has authenticated successfully
      • Forgetting about the empty password is a common vulnerability — it sometimes results in a successful anonymous bind
  • There are applications (e.g. Magento) that employ different logic
    • They keep their own password attribute that they know how to verify
    • If it doesn't exist, they can try to bind as described

Password security ⌘

  • Simple bind operation sends password in clear text
    • That's OK over TLS (both legacy ldaps:// and ldap:// + STARTTLS)
    • OK over a trusted wired network
    • OK over UNIX-domain socket
      • But on UNIX-domain sockets, we know the local user and thus can apply EXTERNAL SASL authentication
  • SASL bind operation does not send password in clear text
    • But often requires password storage in clear text

Importance of specifying base DN ⌘

  • ldapsearch -b "" will find nothing
  • The base DN must be within one of supported naming contexts
  • The list of naming contexts can sometimes be autodiscovered:
    • ldapsearch -H ldap://ldap.example.com -x -s base -b "" "*" "+"
      • This entry is called rootDSE
        • rootDSE = root DSA-specific Entry, DSA = Directory System Agent

Autodiscovery of the schema ⌘

  • Mostly used by general-purpose clients and for debugging
  • "cn=Subschema" comes from "subschemaSubentry: cn=Subschema" on the rootDSE
ldapsearch -o ldif-wrap=no -H ldap://ldap.example.com -x -s base -b 'cn=Subschema' \
 -D cn=admin,dc=example,dc=com -W '*' '+'

LDIF ⌘

  • LDAP Data Interchange Format
  • Defined by RFC2849
  • Describes both data and desired changes
  • Used when importing data or performing modifications using command-line clients and administrator-friendly tools
  • Does not travel over the wire

Adding new entries ⌘

  • Use ldapadd
  • Prepare a LDIF file containing the records
    • Start each record with its DN
    • Then list objectClass values
    • Then list other attributes
    • Terminate the record with a blank line

Deleting entries ⌘

  • Use ldapdelete
    • You can provide DNs on the command line or on standard input

Renaming entries ⌘

  • Use ldapmodrdn
  • Caveats
    • It accepts the old DN and the new RDN on the command line
      • Or in a file, as pairs of lines
    • The old RDN is kept, use "-r" to remove it
    • Use "-s newparentdn" to move the entry to the new parent

Modifications ⌘

  • Separate kind of LDIF
  • Accepted by ldapmodify
  • Best illustrated by examples from RFC2849
    • Yes, this can be used for adding new entries, deleting old ones, and renames as well

Deleting attributes and values ⌘

  • This means "delete all descriptions":
dn: ...
changetype: modify
delete: description
-
  • This means "delete this particular description"
    • Works only when there is a matching rule in the schema
dn: ...
changetype: modify
delete: description
description: something misleading
-
  • Commonly-hit ldapvi bug: cannot delete single-valued attributes without a matching rule
    • Save ldif, delete the line about the value manually, apply with ldapmodify

Common object classes ⌘

  • dcObject (something to have "dc" on)
  • country, organization, organizationalUnit (to represent structure)
  • inetOrgPerson (to store information about people)
  • simpleSecurityObject (stores a password)
  • groupOfNames (LDAP way of representing groups, put DNs in the "member" attribute)
    • Used in ACLs by OpenLDAP itself, or in applications such as NextCloud
  • groupOfUniqueNames (less used variant)
  • alias, referral (LDAP lookalikes of symlinks)
  • extensibleObject (can store any attribute defined on the server)
  • More in RFC4519

Extending the schema ⌘

  • With slapd.conf, that's easy: just "include" the schema file
  • With cn=config, you have to convert schema to ldif first
    • For standard schema files, this has been done already
    • For custom schema files, you have to convert them yourself using slaptest
      • Here is a script
      • See also the big comment at the top of /etc/openldap/schema/openldap.ldif

On empty groups ⌘

objectclass ( 2.5.6.9 NAME 'groupOfNames'
        SUP top STRUCTURAL
        MUST ( member $ cn )
        MAY ( businessCategory $ seeAlso $ owner $ ou $ o $ description ) )
  • You see — at least one member is mandatory
  • Empty groups do not exist according to the RFC
    • They don't exist in OpenLDAP
    • Some other LDAP servers violate the RFC and include "member" as "MAY"
  • Common workarounds:
    • Do the same RFC violation (not recommended)
    • Create a dummy member (possibly of some silly objectClass)
    • Use groupOfMembers (from RFC2307bis)
    • Use namedObject (schema here)
    • Do not create a group

On nested groups ⌘

  • It is perfectly valid to add a group as a member of another group
  • Common interpretation: all members of one group should be treated as members of another
    • It's up to the client to interpret the situation like that
    • This interpretation is optional

Aliases and referrals ⌘

  • Similar to symlinks
  • Aliases are for referencing entries on the same server
    • Dereferenced by the server
    • The objectClass is "alias", the attribute is "aliasedobjectname"
  • Referrals are for redirectling clients to other servers
    • Usually dereferenced by the client
    • The objectClass is "referral", the attribute is "ref"
  • In both cases, please use extensibleObject so that the naming attribute can be stored

Referrals and authentication ⌘

  • The server returned a referral
  • The client's credentials do not necessarily apply to the referred-to server
    • There is no simple solution

Deleting referrals ⌘

  • If you try it naively, you'll get a referral
    • "Please delete it there"
    • Obviously not what you mean
  • Solution: manageDsaIT control
    • Meaning: return referrals as normal objects
    • ldapmodify (or ldapdelete) -M
  • No special flag is needed to delete aliases

Using LDAP for Apache Basic Authentication ⌘

  • authnz_ldap module must be loaded
  • Enable TLS: LDAPTrustedMode TLS
  • Finding the DN based on username
    • If you have to search:
      • do it as this DN: AuthLDAPBindDN dn
      • use this password: AuthLDAPBindPassword password
      • search according to this URL: AuthLDAPURL ldap://host:port/basedn?attribute?scope?filter
        • The actual filter will be constructed as (&(<attribute>=...)(<filter>))
        • The filter has to return exactly one dn
    • If you want to construct the DN instead of searching:
      • AuthLDAPInitialBindAsUser on
      • AuthLDAPInitialBindPattern (.+) uid=$1,ou=...,
  • Making the authorization decision
    • Require valid-user
    • Require ldap-user "John Doe" "..."
    • Require ldap-group cn=...,ou=...

Keeping POSIX users and groups in LDAP ⌘

  • Originally specified in RFC2307
    • That's what comes with OpenLDAP in a schema file
  • Then amended in a draft specification RFC2307bis
    • Not fully adopted yet
    • Expired in 2010
  • And now there is also DBIS!
    • Claims to be a successor of RFC2307 and RFC2307bis
    • Has a design paper
    • Not in Ubuntu
    • Not supported by SSSD
    • No idea who uses it

POSIX objectClasses ⌘

  • posixAccount
    • best practice: use uid as RDN
    • AUXILIARY — use account as a structural objectClass
    • Also combines well with inetOrgPerson
  • shadowAccount
    • combine with posixAccount
  • posixGroup
    • best practice: use cn as RDN
    • STRUCTURAL in RFC2307, AUXILIARY in RFC2307bis
      • combine with groupOfMembers, also defined in RFC2307bis
  • other rarely-used classes

Evolution of NSS LDAP service ⌘

  • Originally written as nss_ldap and pam_ldap by PADL Software
    • Had a huge drawback: LDAP connections were opened from NSS libraries - which means "from any application"
      • Unexpected file descriptors
      • Interference with fork()
    • Relied on nscd for caching
    • Relied on pam_ccreds (of the same company) for disconnected operation
  • Cleaned up by Arthur de Jong, result: nss-pam-ldapd
    • NSS and PAM modules query a central "nslcd" daemon which queries LDAP servers
    • Still relies on nscd
  • RedHat's SSSD project
    • Supports caching, offline operation, authentication against AD

Writing custom schema ⌘

  • Get a base OID for your organization or project from a legitimate source
  • Plan how to use your OID space
    • Reserve a branch for attributes, a branch for objectClasses
  • Register your attribute/objectClass/matchingRule/syntax/etc names with IANA
    • Use x-projectname- prefix if you don't yet want to do that
  • Familiarize yourself with standard OIDs for booleans, integers, strings, etc.
  • Use these numbers

LDAP URLs ⌘

  • Encapsulate address, port, and search parameters
  • ldap://host:port/basedn?attr1,attr2?scope?(filter)?extension1,extension2
  • E.g. get CN of all groups under ou=Groups,dc=training,dc=com:
    • ldap://training.com/ou=Groups,dc=training,dc=com?cn?sub?(objectClass=groupOfNames)


Server structure ⌘

  • Global settings
    • Logging
    • TLS
  • Loadable modules
  • Backends representing different database types
  • Databases representing different naming contexts
    • including cn=config
    • also there is cn=monitor
    • and one HDB-backed database
    • you can add another
  • Overlays
    • Provide additional functionality on top of databases

Configuration tree and objectClasses ⌘

  • cn=config (objectClass: olcGlobal) - global options
    • cn=module{X} (objectClass: olcModuleList) - loadable modules
    • cn=schema (objectClass: olcSchemaConfig) - schema definition
    • olcBackend=<databasetype> (objectClass: olcBackendConfig) - options specific to a backend type (not really used)
    • olcDatabase=frontend (objectClass: olcFrontendConfig) - frontend database configuration
    • olcDatabase={X}<databasetype> (objectClass: olcDatabaseConfig, objectClass: olc<databasetype>Config) - backend database configuration
      • olcOverlay={X}<overlaytype> (objectClass: olcOverlayConfig, objectClass: olc<overlaytype>Config) - overlays

Logging ⌘

  • cn=config
  • olcLogLevel: <some integer> or <list of strings>
  • Sum of flags representing what should be logged to syslog
    • Example: olcLogLevel: 129
  • Strings work just as well
    • Example: olcLogLevel: stats sync
    • Default: olcLogLevel: stats
    • man slapd-config

TLS ⌘

  • The modern way is to use StartTLS on port 389
  • The obsolete (?) way is to use TLS on port 636
    • Still handy for debugging certificate issues with openssl s_client
    • And the only way to convince Thunderbird to encrypt its LDAP traffic (bugzilla entry)
    • Obsolete? Not sure anymore. See Design Considerations from RFC 8314 (but that's about email, not LDAP)
  • You will need a domain name, a server certificate, its chain, and the corresponding private key
  • Two ways to deal with it:
    • Self-signed (snakeoil) certificates, or private CAs
    • Commercial certificates
    • Let's try both

Create a snakeoil TLS certificate with OpenSSL ⌘

  • We'll do it this way: CA signs end-entity certificates
    • Well, in the real world, there is an intermediate CA certificate, but we'll skip it
# CA
openssl genrsa -out rootCA.key 2048
openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.pem
# Server
openssl genrsa -out server.example.key 2048
openssl req -new -key server.example.key -out server.example.csr
openssl x509 -req -in server.example.csr \
    -CA rootCA.pem -CAkey rootCA.key -CAcreateserial \
    -out server.example.crt -days 500 -sha256

Get a trusted DV TLS certificate in 10 minutes ⌘

  • Register a free 3rd-level domain specifically for this training here
  • Create A or AAAA records for IPv4 or IPv6 addresses of your LDAP servers
  • Install httpd with mod_ssl
    • This will create a snakeoil certificate
    • We need a web server for certbot
  • Install certbot and python2-certbot-apache, so that we can get valid free TLS certificates from LetsEncrypt
  • Create virtual hosts on one of the servers
    • So that certbot has an idea which files to modify
  • Run certbot --apache
    • OK, now we have a valid certificate!
    • And a private key, and a chain too! All in /etc/letsencrypt/live

Private key and certificate storage in CentOS 7 ⌘

  • Please ignore this slide on other systems!
  • CentOS 7, historically, stored certificates in nssdb, in /etc/openldap/certs
  • Then they decided to switch to OpenSSL for maintainability reasons
    • But they still need to support people who were using nssdb before
    • Thus: convert nssdb to a directory with PEM files on startup
  • By default, there is a snakeoil certificate in nssdb that we need to replace
    • Recommendation: just store PEM files in /etc/openldap/certs
      • Don't move them elsewhere, or SELinux may bite you
    • After a suitable adjustment of slapd configuration, you can remove nssdb files
    • Ignore old tutorials that mention pk12util and certutil, that's just extra unneeded complexity

More on TLS trust flags ⌘

  • With Mozilla NSS backend, when importing a CA certificate, these flags can be specified:
    • T = trust as a CA that can sign TLS client certificates
    • C = trust as a CA that can sign TLS server certificates
      • Nice separation of authority
    • Authoritative documentation
  • With other backends, no such flags exist
    • Your server CA can go rogue and issue a client certificate that you don't want!
    • Solutions:
      • Use your private CA that surely won't do that
      • olcTLSVerifyClient: never
        • ...which is the default, and which means that TLS client certificates are not used

Actually enabling TLS ⌘

  • Modify the following attributes of cn=config
    • olcTLSCACertificateFile
      • either the nickname of the CA certificate in NSS DB (quoted if it contains spaces), or a filename with concatenated certificates in PEM format
      • Multiple values should be separated by commas
    • olcTLSCACertificatePath
      • Directory with either OpenSSL-style CA certificates or Mozilla NSS database (used by other TLS options)
    • olcTLSCertificateFile
      • either the nickname of the server certificate in NSS DB or a path to a PEM file with such certificate
    • olcTLSCertificateKeyFile
      • Either a file with a password for the NSS DB, or a private key file in PEM format
  • Options that mention files are still valid on CentOS
  • "systemctl restart slapd" to apply new settings

Using TLS from ldapsearch ⌘

  • ldapsearch -Z = try STARTTLS, but continue if it fails
  • ldapsearch -ZZ = require TLS
  • Set TLS_CACERT or TLS_CACERTDIR in /etc/openldap/ldap.conf
    • Otherwise the root CA will be rejected as self-signed
    • This also applies to non-command-line clients, such as PHP!
    • And also to slapd's built-in LDAP replication client

SASL identity mangling ⌘

  • SASL uses usernames or something like that - not DNs, so there is a mapping
    • slapd initially constructs a DN of the form: uid=<username>,cn=<mechanism>,cn=auth
    • Or, if the realm was also provided: uid=<username>,cn=<realm>,cn=<mechanism>,cn=auth
    • Finally, the value of olcAuthzRegexp/authz-regexp is used to map this to the final DN
  • If you can construct the final DN from the initial one using regular expressions:
    • olcAuthzRegexp: uid=([^,]*),cn=.*,cn=auth dn:uid=$1,ou=People,dc=example,dc=com
    • olcAuthzRegexp can have multiple values, you can add {X} in front of the value for sorting, the first matching value wins
    • An alternative mechanism is olcAuthIDRewrite
  • The other option is to search
    • olcAuthzRegexp: uid=([^,]*),cn=.*,cn=auth ldap:///...$1...
    • The filter must return exactly one entry
    • The search is done as the authentication identity, ACLs apply, "auth" permission needed

Password-based SASL mechanisms ⌘

  • LOGIN, PLAIN, DIGEST-MD5, CRAM-MD5
  • Install cyrus-sasl library
  • Install subpackages for SASL mechanisms that you would like to expose
    • cyrus-sasl-plain, cyrus-sasl-md5, ...
  • Configure identity mangling
    • The mechanism would be "plain" or "login" or ...
  • Restart slapd (changes DO NOT apply on-the-fly)
    • Verify that supportedSASLMechanisms in rootDSE are correct
  • Test the result
    • ldapsearch -Y PLAIN -ZZ -H ldap://ldap.example.com/ -U user -b dc=example,dc=com
    • -ZZ is needed because LOGIN and PLAIN are not exposed on non-SSL connections

Modules ⌘

  • cn=module{X},cn=config
    • X is an integer
    • You don't specify the X when populating the database
    • The only meaningful attributes are olcModulePath and olcModuleLoad
  • Modules are loaded in batches
    • Sorted according to X
    • olcModuleLoad is multivalued, but new values cannot be added at runtime
  • No such problem with slapd.conf

Backend evolution ⌘

  • First, there was BDB
  • Then, HDB (the default in most distributions)
    • "H" stands for "hierarchical" (structure of internal data)
      • Stores DNs more efficiently
      • Supports subtree renames
    • still needs tuning (next slide)
  • The new default is MDB
    • Lightning Memory-Mapped Database
    • not based on Berkeley DB
    • needs no tuning at all - relies on page cache

HDB files ⌘

  • Data files
    • __db.001, __db.002, etc
    • *.bdb
  • Logs
    • log.0000000001, etc
    • may be needed for recovery, including point-in-time recovery
    • useful for incremental backup

HDB tuning ⌘

  • Maybe skip it and just use MDB?
  • OK, DB_CONFIG in the directory with the database
    • Defaults can be set in slapd.conf, using "dbconfig" option
      • Ignored if DB_CONFIG already exists
    • Crucial settings:
      • cachesize (in entries, ideally should cover the whole working set, default = 1000)
      • idlcachesize = 3 * cachesize
      • dncachesize (default = unlimited, ideally should cover the whole database)
      • checkpoint !!!

Don't forget to index your entries ⌘

  • Indexes help when application uses them
  • Any search not using indexes is logged
bdb_equality_candidates: (gidNumber) not indexed
  • After any modifications of the "index" statement, rebuild the indexes
    • systemctl stop slapd
    • slapindex
    • chown -R ldap:ldap /var/lib/openldap
    • systemctl start slapd

If HDB complains about locks ⌘

  • Just give it more
# Number of objects that can be locked at the same time.
dbconfig set_lk_max_objects 1500
# Number of locks (both requested and granted)
dbconfig set_lk_max_locks 1500
# Number of lockers
dbconfig set_lk_max_lockers 1500

Changing DB_CONFIG settings ⌘

  • Make sure they actually propagate into DB_CONFIG
  • systemctl stop slapd
  • Run db_recover in the directory with the database
  • Fix ownership (should be ldap:ldap)
  • systemctl start slapd

If slapd eats too much memory ⌘

  • Check whether you have too high "ulimit -n"
  • Switch to MDB backend
  • Tune the cache size down (losing performance) for HDB if you can't use MDB

On checkpointing ⌘

  • BDB checkpointing terminates the transaction
  • Logs containing only completed transactions can be archived
    • Read: removed
    • dbconfig set_flags DB_LOG_AUTOREMOVE
      • Makes recovery impossible
  • By default, in CentOS checkpointing is disabled
    • Consequence: logs cannot be removed, and will eat all disk space
      • Berkeley DB loses all data since the last checkpoint if it runs out of disk space. Without checkpoints, this means "loses all data".
    • No such trap in Debian and Ubuntu
  • Automatic checkpointing is possible
    • checkpoint <kbyte> <min>

Access control ⌘

  • Default policy without ACL statements: only rootdn can perform updates, everyone (including anonymous) can read anything
    • Including (hashed or possibly unhashed) passwords
    • Debian and Ubuntu fix this by adding ACL statements that protect passwords
  • ACLs can be added to backend databases, or to the frontend
    • Frontend ACL is appended to backend
  • olcAccess attribute or "access" statement in slapd.conf

What can be written to ⌘

  • If you allow your users to write data, think about security
    • Can they create additional (unauthorized) accounts with valid passwords?
    • Can they set their group ID to "sudo" or user id to 0?

ACL syntax ⌘

  • {X}access to <what> by <whom> <action> by <whom1> <action1> ...
    • The first access line with a matching <what> is applied, unless there is a "break"
  • Examples of "what":
    • dn.sub=...
    • attrs=...
    • filter=(...)
    • *
  • Examples of "whom":
    • anonymous, users, self
    • self.level{1}, self.level{-1}
    • dn.base=...
    • group=...
    • dnattr=... (use case: manager of a group)
  • Examples of "action":
    • none disclose auth compare search read write/add/delete manage
      • Each privilege implies all preceding ones
    • After an action, a "break" can be written
      • Causes futher ACLs to be consulted
  • man slapd.access

ACLs and nested groups ⌘

  • Doable via the undocumented "set" feature
    • Allow access to members of members of members... of "cn=that" group
      • by set="[cn=that,...]/member* & user" write
    • Allow access to manager of manager of manager... of the entry itself
      • by set.expand="[$0]/manager* & user" manage
      • or, by set="this/manager* & user" manage
  • $0 means "what's matched by <what>"
  • * means "recursively expand"

Testing ACLs ⌘

  • Use "slapacl"
    • slapacl -b <to-what> -D <by-whom>
    • On CentOS 7 with slapd.conf, add -f /etc/openldap/slapd.conf to prevent conversion

Overlays ⌘

  • Provide additional functionality to a database
    • Access log in the database itself
      • Used by delta-syncrepl
    • Audit log on disk
    • Password policies
      • Including the ability to lock out a user
    • Chaining (i.e. expansion of referrals on the server)
    • Arbitrary regex-based constraints on attribute values
    • Attribute uniqueness checks
    • Referential integrity
      • Don't use together with access logging — will deadlock the server
    • Maintenance of memberOf
    • Dynamic lists based on the labeledURI attribute
      • dynamic lists and memberOf interfere, don't use them together
    • Multi-valued attribute value sorting
    • Caching of remote backends
    • Synchronization provider
  • Order of overlays is important

memberOf ⌘

  • We are talking about LDAP groups, not POSIX groups, here
  • Normally, groups list DNs of their members in the "member" attribute
  • It is possible to search for groups that have a certain member
    • That's slow
  • Some applications rely on the ability to get the list of such groups without a search
    • They use the "memberOf" operational attribute
      • It is not provided by default
  • Overlay, by default, acts upon groupOfNames and the "member" attribute, can be reconfigured
moduleload memberof
...
database ...
overlay memberof
  • Every group created before this module is enabled has to be deleted and restored from backup

unique ⌘

  • Ensures that values of a certain attribute are unique
    • Useful e.g. for POSIX user and group IDs
moduleload unique
...
database ...
overlay unique
unique_uri
    ldap:///?uidNumber?sub?(objectClass=posixAccount)
    ldap:///?gidNumber?sub?(objectClass=posixGroup)

Referential integrity ⌘

  • Some attributes reference other DNs
  • If you delete these entries, dangling links are left by default
  • The refint overlay cleans them up
moduleload refint
...
database ...
overlay refint
refint_attributes memberof member manager owner

Replication ⌘

  • Standards-based — described in RFC4533
    • Object-based — retransmits the whole entry even if only one attribute changed
    • Optimizations available in the form of Delta-syncrepl
  • Two modes:
    • refreshOnly (connects to the master periodically, downloads updates)
      • A cookie is used to keep the knowledge what's already downloaded
    • refreshAndPersist (connects, syncs the snapshot, gets notified about updates immediately)
  • Master-master replication can be obtained by having two nodes replicate from each other

Replication: configuration on the master ⌘

  • Need syncprov overlay
moduleload syncprov
...
database ...
overlay syncprov
# How often to write the contextCSN from memory to database?
# the first number is the number of operations, the second is time in minutes
syncprov-checkpoint 100 10
# How many operations to log in memory?
syncprov-sessionlog 1000
  • Need an ACL that allows reading absolutely everything by some DN (used by slaves)
  • For Delta-syncrepl, the accesslog overlay must be also enabled, and syncprov-reloadhint must be set to TRUE

Replication: configuration on the slave ⌘

  • Need rootdn
  • Need the long "syncrepl" option in the database
syncrepl rid=001
  provider=ldap://master.example.com
  type=refreshAndPersist
  retry="5 5 300 +"
  searchbase="dc=example,dc=com"
  attrs="*,+"
  bindmethod=simple
  binddn="uid=replication,dc=example,dc=com"
  credentials=verylongpassword
  starttls=critical
  • A good idea is to define updateref so that writes are directed to the master

Master-master replication ⌘

  • Just two databases replicating from each other
  • Both should have unique serverID
  • Both should have mirrormode on
    • Otherwise, both will be read-only

Rewriting content on the fly ⌘

  • rwm overlay
    • Experimental
    • Can remap objectClass and attribute names
    • Can change DN suffix
    • Can perform other regular-expression based rewriting
    • Has been successfully used for translation between ActiveDirectory and RFC2307 schema

Resources and tutorials ⌘