From 1b245166635a9b3e3aed96974b99afa3305c866e Mon Sep 17 00:00:00 2001 From: Patrick de Ruiter Date: Fri, 26 Dec 2025 03:57:31 +0100 Subject: [PATCH] feat: add multi-master replication support - Add syncprov module to init-config.sh - Create init-replication.sh for configuring N-way multi-master - Update entrypoint to handle replication configuration - Support LDAP_REPLICATION_ENABLED, LDAP_SERVER_ID, LDAP_REPLICATION_HOSTS - Replica servers can sync DIT from existing masters --- docker-entrypoint.sh | 88 ++++++++++++++++++------ scripts/init-config.sh | 1 + scripts/init-replication.sh | 129 ++++++++++++++++++++++++++++++++++++ 3 files changed, 196 insertions(+), 22 deletions(-) create mode 100755 scripts/init-replication.sh diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 3b5b9e9..3924f07 100644 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -44,6 +44,12 @@ export LDAP_TLS_VERIFY_CLIENT="${LDAP_TLS_VERIFY_CLIENT:-try}" export LDAP_LOG_LEVEL="${LDAP_LOG_LEVEL:-256}" export LDAP_READONLY="${LDAP_READONLY:-false}" +# Replication settings +export LDAP_REPLICATION_ENABLED="${LDAP_REPLICATION_ENABLED:-false}" +export LDAP_SERVER_ID="${LDAP_SERVER_ID:-1}" +export LDAP_REPLICATION_HOSTS="${LDAP_REPLICATION_HOSTS:-}" +export LDAP_BOOTSTRAP_PRIMARY="${LDAP_BOOTSTRAP_PRIMARY:-false}" + log_info "OpenLDAP Container Starting" log_info "Domain: $LDAP_DOMAIN" log_info "Base DN: $LDAP_BASE_DN" @@ -53,36 +59,43 @@ log_info "Organisation: $LDAP_ORGANISATION" if [ ! -f /var/lib/openldap/openldap-data/data.mdb ]; then log_info "First run - initializing OpenLDAP..." - # Initialize cn=config + # Initialize cn=config (always needed) /scripts/init-config.sh - # Load schemas in order + # Load schemas in order (always needed) /scripts/init-schemas.sh - # Configure overlays + # Configure overlays (always needed) /scripts/init-overlays.sh - # Create base DIT - /scripts/init-dit.sh + # Determine if we should initialize DIT or replicate from peer + # If replication is enabled and we're not the bootstrap primary, skip DIT init + if [ "$LDAP_REPLICATION_ENABLED" = "true" ] && [ "$LDAP_BOOTSTRAP_PRIMARY" != "true" ]; then + log_info "Replication enabled - this server will sync DIT from peers" + log_info "Skipping local DIT initialization..." + else + # Create base DIT + /scripts/init-dit.sh - # Configure ACLs - /scripts/init-acls.sh + # Configure ACLs + /scripts/init-acls.sh - # Create service accounts if requested - if [ "$LDAP_CREATE_SERVICE_ACCOUNTS" = "true" ]; then - /scripts/init-services.sh - fi + # Create service accounts if requested + if [ "$LDAP_CREATE_SERVICE_ACCOUNTS" = "true" ]; then + /scripts/init-services.sh + fi - # Process custom LDIF files if present - if [ -d /ldif/custom ] && [ "$(ls -A /ldif/custom 2>/dev/null)" ]; then - log_info "Processing custom LDIF files..." - for ldif in /ldif/custom/*.ldif; do - if [ -f "$ldif" ]; then - log_info "Loading: $ldif" - ldapadd -x -H "$LDAPI_SOCKET" -D "cn=admin,$LDAP_BASE_DN" -w "$LDAP_ADMIN_PASSWORD" -f "$ldif" || \ - log_warn "Failed to load $ldif (may already exist)" - fi - done + # Process custom LDIF files if present + if [ -d /ldif/custom ] && [ "$(ls -A /ldif/custom 2>/dev/null)" ]; then + log_info "Processing custom LDIF files..." + for ldif in /ldif/custom/*.ldif; do + if [ -f "$ldif" ]; then + log_info "Loading: $ldif" + ldapadd -x -H "$LDAPI_SOCKET" -D "cn=admin,$LDAP_BASE_DN" -w "$LDAP_ADMIN_PASSWORD" -f "$ldif" || \ + log_warn "Failed to load $ldif (may already exist)" + fi + done + fi fi log_info "Initialization complete." @@ -108,7 +121,38 @@ fi log_info "Starting slapd with URLs: $SLAPD_URLS" -# Start slapd +# If replication is enabled, start slapd in background first to configure replication +if [ "$LDAP_REPLICATION_ENABLED" = "true" ]; then + log_info "Starting slapd in background for replication configuration..." + + /usr/sbin/slapd -h "$SLAPD_URLS" \ + -F /etc/openldap/slapd.d \ + -u ldap -g ldap & + + SLAPD_PID=$! + + # Wait for slapd to be ready + log_info "Waiting for slapd to be ready..." + sleep 2 + for i in $(seq 1 30); do + if ldapsearch -x -H ldap://localhost -b "" -s base "objectClass=*" >/dev/null 2>&1; then + log_info "slapd is ready" + break + fi + sleep 1 + done + + # Configure replication + /scripts/init-replication.sh + + # Stop background slapd gracefully + log_info "Restarting slapd in foreground mode..." + kill $SLAPD_PID 2>/dev/null || true + wait $SLAPD_PID 2>/dev/null || true + sleep 1 +fi + +# Start slapd in foreground (final) exec /usr/sbin/slapd -h "$SLAPD_URLS" \ -F /etc/openldap/slapd.d \ -u ldap -g ldap \ diff --git a/scripts/init-config.sh b/scripts/init-config.sh index c6469fc..ac5805a 100644 --- a/scripts/init-config.sh +++ b/scripts/init-config.sh @@ -31,6 +31,7 @@ olcModuleLoad: memberof.so olcModuleLoad: refint.so olcModuleLoad: unique.so olcModuleLoad: ppolicy.so +olcModuleLoad: syncprov.so dn: cn=schema,cn=config objectClass: olcSchemaConfig diff --git a/scripts/init-replication.sh b/scripts/init-replication.sh new file mode 100755 index 0000000..6c4dbc4 --- /dev/null +++ b/scripts/init-replication.sh @@ -0,0 +1,129 @@ +#!/bin/sh +set -e + +. /scripts/utils.sh + +# Skip if replication not enabled +if [ "$LDAP_REPLICATION_ENABLED" != "true" ]; then + log_info "Replication not enabled, skipping..." + exit 0 +fi + +# Validate required variables +if [ -z "$LDAP_SERVER_ID" ]; then + log_error "LDAP_SERVER_ID is required for replication" + exit 1 +fi + +if [ -z "$LDAP_REPLICATION_HOSTS" ]; then + log_error "LDAP_REPLICATION_HOSTS is required for replication" + exit 1 +fi + +# Use admin credentials for replication if not specified +LDAP_REPLICATION_DN="${LDAP_REPLICATION_DN:-cn=admin,$LDAP_BASE_DN}" +LDAP_REPLICATION_PASSWORD="${LDAP_REPLICATION_PASSWORD:-$LDAP_ADMIN_PASSWORD}" + +log_info "Configuring multi-master replication..." +log_info "Server ID: $LDAP_SERVER_ID" +log_info "Replication hosts: $LDAP_REPLICATION_HOSTS" + +# Wait for slapd to be ready +wait_for_slapd 30 "$LDAPI_SOCKET" + +# Configure serverID in cn=config +log_info "Setting serverID..." +cat > /tmp/repl-serverid.ldif << EOF +dn: cn=config +changetype: modify +replace: olcServerID +olcServerID: ${LDAP_SERVER_ID} +EOF + +ldapmodify -Y EXTERNAL -H "$LDAPI_SOCKET" -f /tmp/repl-serverid.ldif 2>/dev/null || \ + log_warn "ServerID may already be set" + +# Configure syncprov overlay on the database +log_info "Configuring syncprov overlay..." +cat > /tmp/repl-syncprov.ldif << EOF +dn: olcOverlay=syncprov,olcDatabase={1}mdb,cn=config +changetype: add +objectClass: olcOverlayConfig +objectClass: olcSyncProvConfig +olcOverlay: syncprov +olcSpCheckpoint: 100 10 +olcSpSessionlog: 100 +EOF + +ldapmodify -Y EXTERNAL -H "$LDAPI_SOCKET" -f /tmp/repl-syncprov.ldif 2>/dev/null || \ + log_warn "syncprov overlay may already exist" + +# Build syncrepl configuration for each peer +# Format: LDAP_REPLICATION_HOSTS="ldap://server1.example.com,ldap://server2.example.com" +REPLICA_NUM=0 +SYNCREPL_CONFIG="" + +# Parse the comma-separated list of hosts +OLD_IFS="$IFS" +IFS=',' +for host in $LDAP_REPLICATION_HOSTS; do + REPLICA_NUM=$((REPLICA_NUM + 1)) + + # Trim whitespace + host=$(echo "$host" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + + log_info "Adding replication peer $REPLICA_NUM: $host" + + SYNCREPL_CONFIG="${SYNCREPL_CONFIG} +olcSyncRepl: rid=$(printf '%03d' $REPLICA_NUM) + provider=${host} + bindmethod=simple + binddn=\"${LDAP_REPLICATION_DN}\" + credentials=${LDAP_REPLICATION_PASSWORD} + searchbase=\"${LDAP_BASE_DN}\" + scope=sub + schemachecking=on + type=refreshAndPersist + retry=\"60 +\" + timeout=1" +done +IFS="$OLD_IFS" + +# Configure syncrepl and mirrormode on the database +log_info "Configuring syncrepl and mirrormode..." +cat > /tmp/repl-syncrepl.ldif << EOF +dn: olcDatabase={1}mdb,cn=config +changetype: modify +replace: olcSyncRepl +${SYNCREPL_CONFIG} +- +replace: olcMirrorMode +olcMirrorMode: TRUE +EOF + +ldapmodify -Y EXTERNAL -H "$LDAPI_SOCKET" -f /tmp/repl-syncrepl.ldif || { + log_error "Failed to configure syncrepl" + cat /tmp/repl-syncrepl.ldif + exit 1 +} + +# Add additional indexes for replication +log_info "Adding replication indexes..." +cat > /tmp/repl-indexes.ldif << EOF +dn: olcDatabase={1}mdb,cn=config +changetype: modify +add: olcDbIndex +olcDbIndex: entryCSN eq +- +add: olcDbIndex +olcDbIndex: entryUUID eq +EOF + +ldapmodify -Y EXTERNAL -H "$LDAPI_SOCKET" -f /tmp/repl-indexes.ldif 2>/dev/null || \ + log_warn "Indexes may already exist" + +# Cleanup +rm -f /tmp/repl-*.ldif + +log_info "Multi-master replication configured successfully" +log_info "This server (ID=$LDAP_SERVER_ID) will replicate with $REPLICA_NUM peer(s)"