From 70ffd9b09c93f2a23ead9852008a24dbf3819626 Mon Sep 17 00:00:00 2001 From: Paul Date: Sat, 9 Sep 2017 17:54:45 +0200 Subject: [PATCH] Initial commit --- .dockerignore | 3 + .gitignore | 1 + Dockerfile | 27 ++++++ README.md | 30 ++++++ docker-compose.yml | 15 +++ entrypoint.sh | 93 +++++++++++++++++++ fixtures/config.ldif | 156 ++++++++++++++++++++++++++++++++ fixtures/example_structure.ldif | 49 ++++++++++ 8 files changed, 374 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100755 entrypoint.sh create mode 100644 fixtures/config.ldif create mode 100644 fixtures/example_structure.ldif diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..9a98de6 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +docker-compose.yml +.git/ +data/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8fce603 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +data/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1750f35 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +FROM debian:stretch +EXPOSE 389 + +ENV DEBIAN_FRONTEND noninteractive \ + CONFDIR /etc/ldap/slapd.d \ + DATADIR /var/lib/ldap + +# add our users and groups first to ensure their IDs get assigned consitently +RUN groupadd -r -g 500 openldap && useradd -r -u 500 -g openldap openldap + + +RUN \ + apt-get update && \ + apt-get install --yes --no-install-recommends \ + slapd \ + ldap-utils \ + ca-certificates && \ + # remove the default config, since the entrypoint + # will populate it by hand. + rm -rf /etc/ldap/slapd.d && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +COPY entrypoint.sh /entrypoint.sh +COPY fixtures/ /usr/share/slapd/fixtures/ + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..b8f7439 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +## slapd +Slapd offers a LDAP server, which we mostly use for authentication of various +services. +Therefore, a lot of services have a direct (or indirect) dependency on this +image. + +### Building slapd + +``` +docker build -t zombi/slapd . +``` + +### Running slapd + +``` +docker run -d --name ldap -v /data/ldap:/data -p 389:389 zombi/slapd +``` + +### Backing up data +Data in this container is considered essential, since it influences almost +all other services we run. + +We recently discovered that **simply copying all the data from `data` DOES +NOT WORK**, therefore we included scripts for backing up the slapd database +into a compact .ldif format. + +running `tools/create-ldap-backup.sh` will create two files: +* `conf.ldif` is a backup of the configuration. +* `data.ldif` contains all the saved datasets. + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..b21b3e9 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,15 @@ +version: '2' + +services: + ldap: + build: . + ports: + - "3810:389" + environment: + - "ROOTPW=pass" + - "ORGANIZATION=zombi" + - "DATADIR=/data" + - "CONFDIR=/conf" + volumes: + - ./data/ldap/config:/conf + - ./data/ldap/data:/data diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..a1d5e32 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,93 @@ +#!/bin/bash -e +set -eo pipefail + +# set default values for undefined vars +CONFDIR=${CONFDIR:-'/etc/ldap/slapd.d'} +DATADIR=${DATADIR:-'/var/lib/ldap'} + +if [[ -z "$ORGANIZATION" && -z "$SUFFIX" ]] ; then + fail 'neither ORGANIZATION nor SUFFIX supplied as environment var' +fi + +if [[ -n "$ORGANIZATION" ]] ; then + # if an organization was set, we can generate the RootDN from that + SUFFIX=${SUFFIX:-"o=${ORGANIZATION}"} +fi + +function fail { + # write to stderr + echo "ERROR: $@" >&2 + exit 1 +} + +function configure { + sed \ + -e "s|@SUFFIX@|${SUFFIX}|g" \ + -e "s|@PASSWORD@|${ROOTPW}|g" \ + -e "s|@DATADIR@|${DATADIR}|g" \ + /usr/share/slapd/fixtures/config.ldif \ + | slapadd -F "$CONFDIR" -b "cn=config" + + return $? +} + +function init_fixtures { + mkdir -p /docker-entrypoint-initdb.d + # if no files exist in this directory then copy example structure + if ! ls /docker-entrypoint-initdb.d/* 1> /dev/null 2>&1 ; then + cp /usr/share/slapd/fixtures/example_structure.ldif /docker-entrypoint-initdb.d/structure.ldif + fi + + for f in /docker-entrypoint-initdb.d/*; do + + case "$f" in + *.sh) + echo "$0: running $f"; . "$f" + ;; + *.ldif) + sed \ + -e "s|@SUFFIX@|${SUFFIX}|g" \ + -e "s|@PASSWORD@|${ROOTPW}|g" \ + -e "s|@DATADIR@|${DATADIR}|g" \ + -e "s|@ORGANIZATION@|${ORGANIZATION}|g" \ + "$f" \ + | slapadd -F "$CONFDIR" -b "$SUFFIX" + ;; + *) echo "$0: ignoring $f" ;; + esac + echo + done + +} + +# Reduce maximum number of open file descriptors to 1024 +# otherwise slapd consumes two orders of magnitude more of RAM +# see https://github.com/docker/docker/issues/8231 +ulimit -n 1024 || fail "could not set ulimits" + +# create dirs if they not already exists +[ -d $CONFDIR ] || mkdir -p $CONFDIR +[ -d $DATADIR ] || mkdir -p $DATADIR + +if [[ ! -d "$CONFDIR/cn=config" ]] ; then + echo "configuration not found, creating one for $SUFFIX .." + [[ -z "$ROOTPW" ]] && fail "ROOTPW not set." + [[ -z "$SUFFIX" ]] && fail "SUFFIX not set." + + # if rootpw is not already in hashed format, hash it first + [[ "${ROOTPW:0:1}" == '{' ]] \ + ||ROOTPW=`slappasswd -s "$ROOTPW"` + + configure || fail "could not create slapd config" +fi + +if [[ ! -f "$DATADIR/data.mdb" ]] ; then + init_fixtures +fi + +# fix file permissions +chown -R openldap:openldap $CONFDIR $DATADIR \ + || fail "could not change permissions" + +echo "Starting slapd." +exec /usr/sbin/slapd -F $CONFDIR -u openldap -g openldap -h 'ldapi:// ldap://' -d stats diff --git a/fixtures/config.ldif b/fixtures/config.ldif new file mode 100644 index 0000000..856d6de --- /dev/null +++ b/fixtures/config.ldif @@ -0,0 +1,156 @@ +# this file was adapted from the default /usr/share/slapd/slapd.init.ldif +# Global config: +dn: cn=config +objectClass: olcGlobal +cn: config +olcPidFile: /var/run/slapd/slapd.pid +# List of arguments that were passed to the server +olcArgsFile: /var/run/slapd/slapd.args +# Read slapd-config(5) for possible values +olcLogLevel: none +# The tool-threads parameter sets the actual amount of cpu's that is used +# for indexing. +olcToolThreads: 1 + +# Frontend settings +dn: olcDatabase={-1}frontend,cn=config +objectClass: olcDatabaseConfig +objectClass: olcFrontendConfig +olcDatabase: {-1}frontend +# The maximum number of entries that is returned for a search operation +olcSizeLimit: 500 +# Allow unlimited access to local connection from the local root user +olcAccess: {0}to * by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage by * break +# Allow unauthenticated read access for schema and base DN autodiscovery +olcAccess: {1}to dn.exact="" by * read +olcAccess: {2}to dn.base="cn=Subschema" by * read + +# Config db settings +dn: olcDatabase=config,cn=config +objectClass: olcDatabaseConfig +olcDatabase: config +# Allow unlimited access to local connection from the local root user +olcAccess: to * by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage by * break +olcRootDN: cn=admin,cn=config +olcRootPW: @PASSWORD@ + +# Load schemas +dn: cn=schema,cn=config +objectClass: olcSchemaConfig +cn: schema + +# base schemas +include: file:///etc/ldap/schema/core.ldif +include: file:///etc/ldap/schema/cosine.ldif +include: file:///etc/ldap/schema/nis.ldif +include: file:///etc/ldap/schema/inetorgperson.ldif + +# additional schemas +# include: file:///etc/ldap/schema/ppolicy.ldif +# +# Load module +dn: cn=module{0},cn=config +objectClass: olcModuleList +cn: module{0} +# Where the dynamically loaded modules are stored +olcModulePath: /usr/lib/ldap +olcModuleLoad: back_mdb + +# Load memberof module +dn: cn=module{1},cn=config +objectClass: olcModuleList +objectClass: top +cn: module{1} +olcModulePath: /usr/lib/ldap +olcModuleLoad: memberof.la + +# Load refint module +dn: cn=module{2},cn=config +objectClass: olcModuleList +objectClass: top +cn: module{2} +olcModulePath: /usr/lib/ldap +olcModuleLoad: refint.la + +# Set defaults for the backend +dn: olcBackend=mdb,cn=config +objectClass: olcBackendConfig +olcBackend: mdb + +# The database definition. +dn: olcDatabase=mdb,cn=config +objectClass: olcDatabaseConfig +objectClass: olcMdbConfig +olcDatabase: mdb +# Checkpoint the database periodically in case of system +# failure and to speed slapd shutdown. +olcDbCheckpoint: 512 30 +olcDbMaxSize: 1073741824 +# Save the time that the entry gets modified, for database #1 +olcLastMod: TRUE +# The base of your directory in database #1 +olcSuffix: @SUFFIX@ +# Where the database file are physically stored for database #1 +olcDbDirectory: @DATADIR@ +# olcRootDN directive for specifying a superuser on the database. This +# is needed for syncrepl. +olcRootDN: cn=admin,@SUFFIX@ +olcRootPW: @PASSWORD@ +# Indexing options for database #1 +olcDbIndex: objectClass eq +olcDbIndex: cn,uid eq +olcDbIndex: uidNumber,gidNumber eq +olcDbIndex: member,memberUid eq +# additional attributes +olcDbIndex: mail,associatedDomain eq +olcDbIndex: memberOf eq +# The userPassword by default can be changed by the entry owning it if +# they are authenticated. Others should not be able to see it, except +# the admin entry above. +olcAccess: to attrs=userPassword + by self write + by anonymous auth + by * none +# Allow update of authenticated user's shadowLastChange attribute. +# Updating it on password change is implemented at least by libpam-ldap, +# libpam-ldapd, and the slapo-smbk5pwd overlay. +olcAccess: to attrs=shadowLastChange + by self write + by * read +# ou=People users can see ou=People node +olcAccess: to dn.exact="ou=People,@SUFFIX@" + by dn.subtree="ou=People,@SUFFIX@" read + by * break +# User can only access their own profile +# Services can read all User nodes +olcAccess: to dn.subtree="ou=People,@SUFFIX@" + by self read + by dn.subtree="ou=Services,ou=People,@SUFFIX@" read + by * none +# allow to read domain attributes for service accounts +olcAccess: to dn.subtree="ou=Domains,@SUFFIX@" + by dn.subtree="ou=Services,ou=People,@SUFFIX@" read +# The admin dn (olcRootDN) bypasses ACLs and so has total access, +# everyone logged in can read everything. +olcAccess: to * + by anonymous none + by * read + +# memberof overlay manages the memberOf attribute based on referential +# groups +dn: olcOverlay={0}memberof,olcDatabase={1}mdb,cn=config +objectClass: olcConfig +objectClass: olcMemberOf +objectClass: olcOverlayConfig +objectClass: top +olcOverlay: memberof + +# refint overlay preserves referential integrety, by watching for renames of +# referenced fields +dn: olcOverlay={1}refint,olcDatabase={1}mdb,cn=config +objectClass: olcConfig +objectClass: olcOverlayConfig +objectClass: olcRefintConfig +objectClass: top +olcOverlay: {1}refint +olcRefintAttribute: memberof member manager owner diff --git a/fixtures/example_structure.ldif b/fixtures/example_structure.ldif new file mode 100644 index 0000000..f33f3e6 --- /dev/null +++ b/fixtures/example_structure.ldif @@ -0,0 +1,49 @@ +# Create RootDN first: +dn: @SUFFIX@ +objectClass: organization +objectClass: top +o: @ORGANIZATION@ + +dn: ou=People,@SUFFIX@ +ou: people +objectClass: organizationalUnit +objectClass: top +description: People associated with @ORGANIZATION@ + +dn: ou=Services,ou=People,@SUFFIX@ +ou: services +objectClass: organizationalUnit +objectClass: top +description: Accounts for services of @ORGANIZATION@ + +dn: ou=Groups,@SUFFIX@ +ou: groups +objectClass: organizationalUnit +objectClass: top +description: User groups within @ORGANIZATION@ + +dn: ou=System,ou=Groups,@SUFFIX@ +ou: system +objectClass: organizationalUnit +objectClass: top +description: Posix groups within @ORGANIZATION@ + +dn: cn=user,ou=System,ou=Groups,@SUFFIX@ +cn: user +objectClass: posixGroup +objectClass: top +gidNumber: 30000 +description: Default system user group + +dn: ou=Domains,@SUFFIX@ +ou: domains +objectClass: organizationalUnit +objectClass: top +description: Domains controlled by @ORGANIZATION@ + +dn: ou=Policies,@SUFFIX@ +ou: policies +objectClass: organizationalUnit +objectClass: top +description: Password policies for @ORGANIZATION@ +