- slapddgen
- Usage
- Modifying the templates
- Docker
- Configuration
cn=config.ldif
cn=module{0}.ldif,cn=config
olcDatabase={-1}frontend.ldif,cn=config
olcDatabase={0}config.ldif,cn=config
olcDatabase={1}monitor.ldif,cn=config
olcDatabase={2}mdb.ldif,cn=config
olcOverlay={0}memberof.ldif,olcDatabase={2}mdb,cn=config
olcOverlay={1}refint.ldif,olcDatabase={2}mdb,cn=config
olcOverlay={2}ppolicy.ldif,olcDatabase={2}mdb,cn=config
olcOverlay={3}unique.ldif,olcDatabase={2}mdb,cn=config
cn=schema.ldif,cn=config
cn=*.ldif,cn=schema,cn=config
NOTE: This tool is very very alpha. Works for me, might work for you.
NOTE: Ensure your editor does not strip trailing whitespace if you
open any of the files in cn=schema
as that will break the ldif format.
slapddgen
generates a slapd.d
that you can load into an OpenLDAP
server by pointing at it with slapd -F
. This will start a server
with online configuration, aka olc
, meaning the configuration
itself is stored in the directory server, not in slapd.conf
.
The input for this tool is contained in config.json
, or whatever
--config_file
is pointed at. There's too many things to do this
with environment variables or CLI switches. Removing things from
config.json
is guaranteed to blow shit up, though for the modules,
ACLs, indices and unique empty arrays should be fine.
Once you've installed slapddgen you can run it with a slapddgen generate --config_file=/path/to/config.json --output_dir=/path/to/write/config/to
If you know what you're doing you can modify the templates in
templates/slapd.d
to suit your needs. Doing so if you've pip install
ed
slapddgen will be annoying, so in that case cloning the repository and
doing a pip install -e .
instead will be more helpful.
There is also an example Dockerfile
that you can use to package
it all up in a Docker container. This can be useful for testing purposes
amongst other things.
VERSION=$(git rev-parse --abbrev-ref HEAD)
docker build --no-cache=true \
--build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \
--build-arg COMMIT=$(git rev-parse HEAD) \
--build-arg VERSION=${VERSION} \
-t ldap:${VERSION} .
When building a release use git describe --abbrev=0 --tags
instead
for the VERSION
.
docker run -it -p 3389:389 -p 6636:636 ldap
This will expose port 389 in the container, the ldap://
URL, to the host
on port 3389 (so you don't need to bind on a priviledged port) and do the
same for port 636, ldaps://
.
You can change the entrypoint with --entrypoint=/bin/sh
. Once you've done
that you can still manually launch OpenLDAP in the container by running
slapd -u ldap -g ldap -d 256
.
When running it for production purposes it's important to ensure configuration and data can persist to disk. Otherwise you'd start up with a fresh and empty directory service every time your restart the container.
To that end, you should probably volume mount the following:
- Configuration:
/etc/openldap
- Database:
/var/lib/openldap/openldap-data
A separate user, ldapd
is create which defaults to a UID of 55555
. This
is to ensure you can correctly map a host user and the container user so you don't
get into permission issues with the volume mounts.
In order to change the UID of the ldapd
user in the container you have to
rebuild it and pass in --build-arg UID=<number>
.
slapddgen generates a bunch of configuration for you and assumes the following DIT layout:
- {{suffix}}
--- {{baseOU}},{{suffix}}
----- ou=accounts,{{baseOU}},{{suffix}}
----- ou=groups,{{baseOU}},{{suffix}}
----- ou=policy,{{baseOU}},{{suffix}}
You can of course change this yourself by modifying the templates.
The configuration generated uses online/dynamic configuration, aka olc, aka
olcConfig
. As such there is no slapd.conf
to speak of and you'll have to
apply ldif's with the help of ldapmodify
to configure the OpenLDAP server
further.
The resulting slapd.d
directory can be used to start an OpenLDAP server
from scratch. The following sections assume that you've run slapddgen and
explain some of the things in the generated configuration.
The following settings are defaults related to Alpine:
olcConfigFile: /etc/openldap/slapd.conf
olcConfigDir: /etc/openldap/slapd.d/
olcArgsFile: /run/openldap/slapd.args
olcPidFile: /run/openldap/slapd.pid
Of note is:
olcPasswordCryptSaltFormat: $6$rounds=50000$%.16s
This uses crypt's SHA512 with 50.000 rounds (key stretching). This relates to
the olcPasswordHash
setting in olcDatabase={-1}frontend.ldif
as well as
olcPPolicyHashCleartext
in olcOverlay={2}ppolicy.ldif,olcDatabase={2}mdb,cn=config
.
Configures which modules are loaded. Of note are olcModulePath
and the
multitude of olcModuleLoad
entries, relative to olcModulePath
.
The frontend database is a special, pseudo, database which contains settings
that apply as defaults to all the other olcDatabase
s.
The two interesting entries are:
olcMonitoring: FALSE
olcPasswordHash: {CRYPT}
olcMontioring
disables the monitoring overlay for all databases. It'll be
enabled explicitly for olcDatabase={2}mdb.ldif
. The other ensures that we
always use {CRYPT}
as our password hashing mechanism. Which settings are
applied to password hashing is defined by olcPasswordCryptSaltFormat
.
The only thing of interest here are the olcAccess
entries, defining the
ACLs which allow entities to read/write cn=config
.
Like the previous section, olcAccess
is the only interesting bit.
This one is interesting, as this defines the data storage of our actual entries in the DIT, as well as other things like ACLs.
Of interest:
olcSuffix: dc=example,dc=com
olcRootDN: cn=Manager,dc=example,dc=com
olcRootPW: {CRYPT}super-long-hash
olcMonitoring: TRUE
olcDbMaxSize: 536870912
olcDbDirectory: /var/lib/openldap/openldap-data
olcDbIndex: objectClass eq
olcDbIndex: uid eq,sub
olcDbIndex: uidNumber eq
olcDbIndex: gidNumber eq
olcDbIndex: member eq
olcDbIndex: memberof eq
olcSuffix
defines the default suffix for all entries. olcRootDN
is the
DN of this database "root" user, and can modify anything regardless of ACLs.
The olcRootPW
is the password of the olcRootDN
, generated with
slappasswd -h '{CRYPT}' -c '$6$rounds=50000$%.16s'
.
olcMonitoring
enables monitoring for this database and olcDbDirectory
contains the path on disk where this database is stored.
The olcDbIndex
entries define the indices OpenLDAP will maintain for us
and help speed up search operations.
olcDbMaxSize
defines the size of the memory map that will be allocated
for the database. It cannot grow past this value at runtime though you
can set it to a higher value and then restart. The value is in bytes,
our default is half a gibibyte (GiB).
As usual there's also a number of olcAccess
entries governing access to
the data.
Here we store the configuration for the memberof overlay. The memberof
overlay adds a virtual attribute memberOf
to any entry in the DIT storing
what things this DN is a member of.
Imagine you have three groupOfNames
that refer to a user in the member
attribute. The memberOf
attribute for that user will then contain the
DNs of these three groupOfNames
. Note that memberOf
will only contain
the immediate/direct memberships, not any inherited ones (it will
not recursively expand the member attributes).
The settings of interest are:
olcMemberOfDangling: error
olcMemberOfDanglingError: constraintViolation
olcMemberOfRefInt: TRUE
olcMemberOfGroupOC: groupOfNames
olcMemberOfMemberAD: member
olcMemberOfMemberOfAD: memberOf
olcMemberOfDangling
specifies that we'll return an error in case a
modification would result in a dangling reference, returning an error of
type olcMemberOfDanglingError
.
olcMemberOfRefInt
specifies that we use the referential integrity
overlay (see the next section).
olcMemberOfGroupOC
specifies which objectClass
we monitor for
dangling references. This is usually only useful for gorups and we use
groupOfNames
for groups. olcMemberOfAD
specifies the attribute used
to store members of a group in, that's member
and olcMemberOfMemberOfAD
specifes the attribute in which the relation is stored, memberOf
.
This configures the referential integrity overlay. Referential integrity will take care of updating any references to a DN stored in the specified attributes should that DN get renamed/moved.
Of note:
olcRefintAttribute: owner
olcRefintAttribute: manager
olcRefintAttribute: memberOf
olcRefintAttribute: member
olcRefintAttribute: roleOccupant
olcRefintModifiersName: cn=Manager,dc=example,dc=com
The olcRefintAttribute
s specify which attributes we want to enforce
referential integrity for. This can only every be attributes that store
a distinguished name.
olcRefintModifiersName
specifes what we'll store in the modifiersName
operational attribute for any entry that gets updated by the referential
integrity overlay.
The password policy overlay allows to configure certain minimum
requirements for passwords (like those stored in the userPassword
attribute).
Of note:
olcPPolicyHashCleartext: TRUE
olcPPolicyDefault: cn=password,ou=policy,dc=example,dc=com
olcPPolicyUseLockout: FALSE
olcPPolicyHashCleartext
means that the server will automatically hash
any password sent in as clear text using olcPasswordHash
.
olcPPolicyDefault
defines an entry in the DIT where we store our
default password policy on which we can set other settings. The entry
should have objectClass: pwdPolicy
. Its settings are documented
here.
You can create additional/different password policies in the ou=policy
part of the tree and attach them to a user by adding the pwdPolicySubentry
attribute on them, pointing to a different policy.
olcPPolicyUseLockout
instructs the server whether it should return an
InvalidCredentials
(FALSE
) when attempting to bind or an AccountLocked
(TRUE
) instead. This configuration defaults to FALSE
in order to not be
able to use this feature to enumerate accounts.
The unique overlay allows enforcing uniqueness contraints of attributes on
a part of the tree (or the whole tree). Though the RDN is always enforced to
be unique (so you can't have two people with the same uid
for example)
other attributes are not, most crucially things like uidNumber
, gidNumber
or homeDirectory
.
You can create constraints by adding an olcUniqueURI
entry of the form:
ldap:///[base dn]?[attributes...]?scope[?filter]
.
For example, if you wanted to enforce unique gidNumber
s you could do:
ldap:///?gidNumber?sub
. Unfortunately this will enforce gidNumber
constraints across both posixAccount
and posixGroup
so you won't be
able to create a posixAccount
with gidNumber
set to a posixGroup
s
GID, which is undesirable. Instead do something like this:
olcUniqueURI: ldap:///ou=groups,ou=example,dc=example,dc=com?gidNumber?sub
olcUniqueURI: ldap:///ou=accounts,ou=example,dc=example,dc=com?gidNumber?sub
olcUniqueURI: ldap:///ou=example,dc=example,dc=com?uidNumber?sub
Now the constraint will be enforced independently on these parts of the tree, not as a whole.
Nothing of relevance to see or change here. It just needs to exist. Once
the server has been started this entry will be updated automatically
with the server's internal object classes and attributes (mostly all the
olc
* object classes and attributes).
Contains the schema files loaded by the server.
Updating this is rather tricky. The "easiest" way is to generate a slapd.conf
with the include
statements for the schemas and then dump it out
using slapdest -f /path/to/slapd.conf -F /path/to/slapd.d
.