diff --git a/docker-compose.yml b/docker-compose.yml index 8fdac63ae9e147e49ba9a205f63fee7a64dfab98..f25b9e48ee7804968ee320d54134c5f592e4e24c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,13 +5,17 @@ services: ldap: build: context: slapd/ - volumes: - - _run_ldap:/run/slapd - - ldap:/var/lib/ldap + domainname: "${LDAP_HOSTNAME:-ldap}.${LDAP_DOMAIN:-nowhere.com}" environment: LDAP_ROOTPASS: "${LDAP_ROOTPASS:-foo}" LDAP_DOMAIN: "${LDAP_DOMAIN:-nowhere.com}" LDAP_ORGANIZATION: "${LDAP_ORGANIZATION:-none}" + hostname: "${LDAP_HOSTNAME:-ldap}" + restart: always + volumes: + - _run_slapd:/run/slapd + - ldap:/var/lib/ldap + - backup_ldap:/var/backups/ldap nss-pam-ldapd: build: @@ -20,23 +24,29 @@ services: environment: LDAP_BASE_DN: "${LDAP_BASE_DN:-dc=nowhere,dc=com}" volumes: - - _run_ldap:/run/slapd + - _run_slapd:/run/slapd ldap-account-manager: build: context: ldap-account-manager/ links: - ldap - volumes: - - _run_ldap:/run/slapd - - ldap-account-manager:/var/lib/ldap-account-manager ports: - 80:80 + restart: always + volumes: + - _run_slapd:/run/slapd + - ldap-account-manager:/var/lib/ldap-account-manager volumes: - _run_ldap: + _run_slapd: ldap: ldap-account-manager: + labels: + snapshot: 'true' + backup_ldap: + labels: + snapshot: 'true' networks: default: diff --git a/slapd/Dockerfile b/slapd/Dockerfile index 8b3b550ca400dae3f3c75cb47f44240e17f70cd3..956aeffbfa138972ea2b49acb151e1d9b115e32d 100644 --- a/slapd/Dockerfile +++ b/slapd/Dockerfile @@ -1,19 +1,32 @@ -FROM debian:stretch-slim -RUN echo 'deb http://deb.debian.org/debian stretch-backports main' > /etc/apt/sources.list.d/backports.list && \ - apt-get update && \ +FROM debian:buster-slim +# hadolint ignore=DL3008 +RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - gnutls-bin=3.5.8-5+deb9u4 \ - ldap-utils=2.4.44+dfsg-5+deb9u2 \ - slapd=2.4.44+dfsg-5+deb9u2 \ + gettext-base \ + gnutls-bin \ + ldap-utils \ + slapd \ + ssl-cert \ + time \ && \ - mkdir -p /run/slapd && \ - rm -rf /tmp/* /var/tmp/* /var/lib/apt/lists/* /var/cache/apt/archives/* -COPY --chown=root:root entrypoint / + usermod -aG ssl-cert openldap && \ + rm -rf /tmp/* /var/tmp/* /var/cache/apt/archives/* /var/lib/apt/lists/* && \ + rm -rf /etc/ssl/certs/ssl-cert-snakeoil.pem /etc/ssl/private/ssl-cert-snakeoil.key && \ + rm -rf /var/lib/ldap/* /var/backups/ldap/* /run/slapd/* /etc/ldap/slapd.d +COPY --chown=root:root config.ldif /usr/share/slapd/ +COPY --chown=root:root skel.ldif /usr/share/slapd/ +COPY --chown=root:root entrypoint /usr/local/sbin/ +COPY --chown=root:root backup /usr/local/sbin/ EXPOSE 389 636 VOLUME [ "/var/lib/ldap" ] -VOLUME [ "/run/ldap" ] +VOLUME [ "/run/slapd" ] +VOLUME [ "/var/backups/ldap" ] ENV LDAP_URLS="ldap:/// ldapi:/// ldaps:///" \ - SLAPD_DEBUG_LEVEL="NONE" -ENTRYPOINT [ "/entrypoint" ] -CMD [ "slapd", "-F", "/etc/ldap/slapd.d", "-u", "openldap", "-g", "openldap", "-h", "\"$LDAP_URLS\"", "-d", "$SLAPD_DEBUG_LEVEL" ] -HEALTHCHECK CMD ldapsearch -b cn=config -H ldapi:/// > /dev/null || exit 1 + SLAPD_DEBUG_LEVEL="stats,stats2,none" \ + SSL_CERT_FILE="/etc/ssl/certs/ssl-cert-snakeoil.pem" \ + SSL_KEY_FILE="/etc/ssl/private/ssl-cert-snakeoil.key" \ + SSL_CA_FILE="/etc/ssl/certs/ssl-cert-snakeoil.pem" +ENTRYPOINT [ "entrypoint" ] +CMD [ "slapd", "-F", "/var/lib/ldap/config", "-u", "openldap", "-g", "openldap", "-h", "\"$LDAP_URLS\"", "-d", "$SLAPD_DEBUG_LEVEL" ] +HEALTHCHECK CMD ldapsearch -b cn=config > /dev/null || exit 1 +STOPSIGNAL INT diff --git a/slapd/README.md b/slapd/README.md index 0b4799bde22bcb20ea729a751d79bea64cdd5371..14f5eab6aa48835a1880278cb1e4b5e7ffe0d887 100644 --- a/slapd/README.md +++ b/slapd/README.md @@ -15,10 +15,27 @@ Name | Description | Default value `LDAP_ROOTPASS` | Root password. `LDAP_DOMAIN` | Domain. `LDAP_ORGANIZATION` | Organization. +`SLAPD_DEBUG_LEVEL` | The `slapd` debug/ log level. +`SSL_CERT_FILE` | Location of the SSL certificate file. +`SSL_KEY_FILE` | Location of the SSL key file. +`SSL_CA_FILE` | Location of the SSL certificate authority file. + +## SSL + +If the relevant environment variables aren't changed from their default values, +on startup the container will generate a key and self-signed certificate with +the FQDN of the container. If the location of the SSL key and certificate are +provided, those are used instead. ## Persistence -The database is at `/var/lib/ldap`. +The configuration (`cn=config`) and data LDAP directories reside in the +`config` and `data` diretories respectively in the `/var/lib/ldap` volume. +The LDAP directories are generated only if they're missing. Changes to +environment variables afterwards won't change the configuration, since that is +persisted to a volume. There's also the `/var/backups/ldap` volume where the +`backup` script saves snapshots of the LDAP directories (config directory +included). ## License diff --git a/slapd/backup b/slapd/backup new file mode 100755 index 0000000000000000000000000000000000000000..6ba3110ff0b866dbea711b51333110ffefba07b4 --- /dev/null +++ b/slapd/backup @@ -0,0 +1,9 @@ +#!/bin/sh +set -eux + +slapcat -n0 -v -l /var/backups/ldap/config.ldif + +for dn in $(ldapsearch -Y EXTERNAL -LLL -s base -b '' o namingContexts | sed -n '/namingContexts/ s/namingContexts: //gp') +do + slapcat -b "$dn" -v -l "/var/backups/$dn.ldif" +done diff --git a/slapd/config.ldif b/slapd/config.ldif new file mode 100644 index 0000000000000000000000000000000000000000..433d8261f04baf6af475af5df6333eef95a0af94 --- /dev/null +++ b/slapd/config.ldif @@ -0,0 +1,108 @@ +# Global config: +dn: cn=config +objectClass: olcGlobal +cn: config +# Where the pid file is put. The init.d script +# will not stop the server if you change this. +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 +# TLS options. +olcTLSCACertificateFile: ${SSL_CA_FILE} +olcTLSCertificateFile: ${SSL_CERT_FILE} +olcTLSCertificateKeyFile: ${SSL_KEY_FILE} +olcTLSCipherSuite: SECURE256:+SECURE128 +olcTLSProtocolMin: 3.1 +olcTLSDHParamFile: /usr/share/slapd/dh.pem + +# 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 + +# Load schemas +dn: cn=schema,cn=config +objectClass: olcSchemaConfig +cn: schema + +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 + +# 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 + +# 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: ${BASE_DN} +# Where the database file are physically stored for database #1 +olcDbDirectory: /var/lib/ldap/data +# olcRootDN directive for specifying a superuser on the database. This +# is needed for syncrepl. +olcRootDN: cn=admin,${BASE_DN} +olcRootPW: ${PASSWORD_HASH} +# Indexing options for database #1 +olcDbIndex: objectClass eq +olcDbIndex: cn,uid eq +olcDbIndex: uidNumber,gidNumber eq +olcDbIndex: member,memberUid 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 +# The admin dn (olcRootDN) bypasses ACLs and so has total access, +# everyone else can read everything. +olcAccess: to * + by * read + diff --git a/slapd/entrypoint b/slapd/entrypoint index a1ea36e31077d7f86bd240aa0250da9fa11d7a20..2317c15b8e4a88144998534205d5de69ab8c2099 100755 --- a/slapd/entrypoint +++ b/slapd/entrypoint @@ -1,18 +1,70 @@ #!/bin/sh set -eux +# Get the root password hash and unset the cleartext password. +PASSWORD_HASH="$(slappasswd -ns "$LDAP_ROOTPASS")" +unset LDAP_ROOTPASS +export PASSWORD_HASH + +# See https://github.com/moby/moby/issues/8231 +# shellcheck disable=SC2039 +ulimit -n 1024 || true + +# Create and set owner for runtime directories. install -d -o openldap -g openldap /run/slapd +install -d -o openldap -g openldap /var/backups/ldap install -d -o openldap -g openldap /var/lib/ldap +install -d -o openldap -g openldap /var/lib/ldap/config +install -d -o openldap -g openldap /var/lib/ldap/data + +# Base DN. +BASE_DN="dc=$(echo "$LDAP_DOMAIN" | sed 's/^\.//; s/\.$//; s/\./,dc=/g')" +export BASE_DN + +# DC. +DC="$(echo "$LDAP_DOMAIN" | sed 's/^\.//; s/\..*$//')" +export DC + +# Generate self-signed certificates if none are provided. +if [ "${SSL_CERT_FILE:-}" = "/etc/ssl/certs/ssl-cert-snakeoil.pem" ] || \ + [ "${SSL_KEY_FILE:-}" = "/etc/ssl/private/ssl-cert-snakeoil.key" ] +then + echo Generating self-signed key and certificate. >&2 + DEBIAN_FRONTEND=noninteractive time make-ssl-cert generate-default-snakeoil --force-overwrite +fi + +# Generate random DH parameters. +echo Generating DH parameters, this will take a while. >&2 +time openssl dhparam -out /usr/share/slapd/dh.pem 2048 + +# Run slapadd with the correct user and location of the config directory. +alias slapadd='chroot --userspec openldap:openldap / slapadd -gv -F /var/lib/ldap/config' + +# Create configuration is none is present. +if [ -z "$(find /var/lib/ldap/config -maxdepth 1 -mindepth 1)" ] +then + echo No configuration found, generating a new one. >&2 + # shellcheck disable=SC2002 + cat /usr/share/slapd/config.ldif | envsubst | slapadd -b 'cn=config' +fi + +# Create directory if none is present. +if [ -z "$(find /var/lib/ldap/data -maxdepth 1 -mindepth 1)" ] +then + echo No directory found, generating a new one, >&2 + # shellcheck disable=SC2002 + cat /usr/share/slapd/skel.ldif | envsubst | slapadd -b "$BASE_DN" +fi -cat << EOF | debconf-set-selections -v -slapd slapd/internal/generated_adminpw password ${LDAP_ROOTPASS:-} -slapd slapd/internal/adminpw password ${LDAP_ROOTPASS:-} -slapd slapd/password2 password ${LDAP_ROOTPASS:-} -slapd slapd/password1 password ${LDAP_ROOTPASS:-} -slapd slapd/domain string ${LDAP_DOMAIN:-} -slapd shared/organization string ${LDAP_ORGANIZATION:-} +# Configure the client. +cat << EOF >> /etc/ldap/ldap.conf +URI ldapi:/// +SASL_MECH EXTERNAL +BASE $BASE_DN EOF -DEBIAN_FRONTEND=noninteractive dpkg-reconfigure -f noninteractive slapd +# Unset the root password hash. +unset PASSWORD_HASH +# Run CMD. eval exec "$@" diff --git a/slapd/skel.ldif b/slapd/skel.ldif new file mode 100644 index 0000000000000000000000000000000000000000..fedc1898cd476a73e64dedade459f5b288702a7d --- /dev/null +++ b/slapd/skel.ldif @@ -0,0 +1,13 @@ +dn: ${BASE_DN} +objectClass: top +objectClass: dcObject +objectClass: organization +o: ${LDAP_ORGANIZAION} +dc: ${DC} + +dn: cn=admin,${BASE_DN} +objectClass: simpleSecurityObject +objectClass: organizationalRole +cn: admin +description: LDAP administrator +userPassword: ${PASSWORD_HASH}