From 8b77a05409d4fdb60e4bf7fcd49d0e0837bb8cf1 Mon Sep 17 00:00:00 2001 From: Liraz Siri Date: Mon, 24 Aug 2026 07:53:43 +0000 Subject: [PATCH 1/7] Port OpenLDAP to Debian Trixie Move the appliance release contract to v19 while retaining the Debian-packaged OpenLDAP, phpLDAPadmin, Lighttpd and PHP-FPM stack. Refresh the README to match the existing Debian phpLDAPadmin source, administrator DN, generated LDAPS certificates and actual Webmin module. Add focused acceptance coverage for normal services, administrator LDAP and trusted LDAPS binds, the default directory structure, an add-search-delete entry round trip, the phpLDAPadmin HTTPS interface and signed Trixie updater candidates. Document the README crosswalk, Core inheritance and Docker boundary. Verified with Bash and shell syntax checks, Python source compilation and git diff checks. --- README.rst | 33 ++++++------ changelog | 15 +++++- docs/v19.0-testing.md | 60 +++++++++++++++++++++ plan/main | 2 +- tests/v19.sh | 118 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 209 insertions(+), 19 deletions(-) create mode 100644 docs/v19.0-testing.md create mode 100755 tests/v19.sh diff --git a/README.rst b/README.rst index 686c82c..84dfb32 100644 --- a/README.rst +++ b/README.rst @@ -12,31 +12,30 @@ and on top of that: - OpenLDAP configurations: - - Installed and maintained through package management system (slapd - ldap-utils packages) - - Set LDAP domain and admin password on firstboot (convenience, - security). - - TLS support for ldaps out of the box (security). Note, you can - find the pre-generated CA certificate as /etc/ldap/tls/ca_cert.pem - and/or regenerate one with the 'turnkey-regen-ldap-certs' command. - - Includes Users/Groups OU and default PosixGroup (convenience). - -- Includes phpLDAPadmin for web based LDAP administration, with SSL - support out of the box. - - - Installed from upstream source code to /var/www/phpldapadmin + - OpenLDAP and its client utilities installed and maintained through + Debian's package management system. + - LDAP domain and administrator password configured at first boot. + - TLS support for LDAPS out of the box. The generated CA certificate is + available at ``/etc/ldap/tls/ca_cert.pem`` and certificates can be + regenerated with ``turnkey-regen-ldap-certs``. + - Users and Groups organizational units plus a default ``users`` POSIX + group. + +- phpLDAPadmin installed from Debian and served over TLS for web-based LDAP + administration. -- Webmin modules for configuring Apache2, PHP, MySQL and Postfix. +- Webmin LDAP server module. See the `OpenLDAP docs`_ for further details. Credentials *(passwords set at first boot)* ------------------------------------------- -- Webmin, SSH, MySQL: username **root** -- OpenLDAP: default domain **example.com** +- Webmin, SSH: username **root** +- OpenLDAP and phpLDAPadmin: administrator DN **cn=admin,dc=example,dc=com** + for the default domain **example.com** -.. _OpenLDAP: http://www.openldap.org/ +.. _OpenLDAP: https://www.openldap.org/ .. _TurnKey Core: https://www.turnkeylinux.org/core .. _OpenLDAP docs: https://www.turnkeylinux.org/docs/openldap diff --git a/changelog b/changelog index 339affb..5e82a80 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,17 @@ +turnkey-openldap-19.0 (1) turnkey; urgency=low + + * Upgrade the base distribution to Debian 13/Trixie. + + * Update OpenLDAP, phpLDAPadmin, Lighttpd and PHP-FPM from the Debian Trixie + repositories. + + * Retain first-boot domain and administrator configuration, generated LDAPS + certificates, the default directory structure and the Webmin LDAP module. + + * See the TurnKey Core 19.0 changelog for changes common to all appliances. + + -- TurnKey Linux release engineering Mon, 24 Aug 2026 00:00:00 +0000 + turnkey-openldap-18.1 (1) turnkey; urgency=low * v18.1 rebuild - includes latest Debian & TurnKey packages. @@ -299,4 +313,3 @@ turnkey-openldap-12.0 (1) turnkey; urgency=low appliances. Here we only describe changes specific to this appliance. -- Alon Swartz Wed, 01 Aug 2012 08:00:00 +0200 - diff --git a/docs/v19.0-testing.md b/docs/v19.0-testing.md new file mode 100644 index 0000000..5b9786e --- /dev/null +++ b/docs/v19.0-testing.md @@ -0,0 +1,60 @@ +# OpenLDAP 19.0 testing + +## Scope and source decision + +OpenLDAP 19.0 uses Debian 13 Trixie packages for the OpenLDAP server and +clients, phpLDAPadmin, Lighttpd, PHP-FPM, LDAPS certificate tooling and LDAP +client configuration. Debian supplies the complete application stack, so no +upstream repository or downloaded application artifact is required. The +Webmin LDAP server module continues to come from the signed TurnKey Trixie +repository. + +The acceptance command is: + +```sh +/sandboxed-git/turnkey/tools/test-v19-appliance openldap \ + --source /home/agent/.local/worktrees/turnkey-apps/openldap/wish-openldap-v19-trixie +``` + +It builds a Trixie root filesystem in TKLDev, imports the configured root into +Docker, starts the normal systemd init path with first-boot credentials and the +test domain, then runs `tests/v19.sh`. The retained report under +`~/.local/state/turnkey-v19-harness/runs/openldap/` records the exact source +and harness commits, archive and tree hashes, installed versions, +configured-root mode, application evidence, updater evidence, cleanup result, +and verdict. + +## README crosswalk + +| README contract | Focused check | Required result | Evidence | +| --- | --- | --- | --- | +| OpenLDAP server and clients come from Debian packages | Query installed versions, refresh APT, and inspect candidates for `slapd`, `ldap-utils` and `libldap-common` | Signed Trixie metadata is accepted and each installed package retains an eligible candidate | `tests/v19.sh`; harness updater fields | +| First boot configures the LDAP domain and administrator password | Discover the naming context and perform an authenticated bind as its `cn=admin` DN using the first-boot password | Administrator bind succeeds against the configured naming context | `tests/v19.sh`; harness `runtime_checks` | +| Users and Groups organizational units plus the default `users` POSIX group are present | Search the configured naming context for the three documented entries | All documented entries are returned | `tests/v19.sh` | +| OpenLDAP stores and retrieves directory entries | Add a disposable `inetOrgPerson`, search its attributes, delete it, and prove it is no longer searchable | Add, search, attribute verification and delete complete successfully | `tests/v19.sh`; harness `runtime_checks` | +| LDAPS is available with a generated CA certificate | Bind through the appliance hostname on port 636 with certificate verification required and the generated CA configured | Trusted administrator LDAPS bind succeeds and port 636 is listening | `tests/v19.sh`; harness `runtime_checks` | +| phpLDAPadmin provides TLS-protected web administration | Request `https://127.0.0.1/` while Lighttpd and PHP-FPM are active | phpLDAPadmin login interface is returned over HTTPS | `tests/v19.sh`; installed versions in report | +| Webmin includes the LDAP server module | Query dpkg state for `webmin-ldap-server` | Module is installed | `tests/v19.sh` | +| Webmin and SSH use the inherited root credential; Postfix is locally bound | Covered by the unchanged Core implementation | Passed by the Core 19 baseline | Core run `20260824t010251z-1634-32241`, source `24c82ee3540ce545422742b0e28ba6b687c53ec2`, verdict `PASS` | + +The README's external reference links are descriptive and do not add runtime +behaviors. + +## Updater check + +`tests/v19.sh` runs `apt-get update` and checks eligible candidates for +`slapd`, `ldap-utils`, `phpldapadmin`, `lighttpd`, `php-fpm` and +`libldap-common` with `apt-cache policy`. It records installed versions before +the refresh and proves they remain unchanged afterward. A pass means APT +accepted signed Debian and TurnKey Trixie metadata, every identity-defining +package has an eligible candidate, and no Bookworm source remains configured. + +## Known limitation + +The Docker acceptance path does not exercise the installer, kernel, bootloader +or physical hardware. This appliance adds no behavior at those boundaries, so +the validated Core 19 baseline supplies the inherited evidence. + +## Deferred issues + +None recorded before the functional acceptance run. diff --git a/plan/main b/plan/main index c6b35d0..7328b21 100644 --- a/plan/main +++ b/plan/main @@ -4,7 +4,7 @@ samba slapd ldap-utils ldapscripts -libldap-common /* is only recommends in Bookworm - closes #1939 */ +libldap-common /* provides the TLS-aware LDAP client configuration */ gnutls-bin lighttpd diff --git a/tests/v19.sh b/tests/v19.sh new file mode 100755 index 0000000..6f0dd32 --- /dev/null +++ b/tests/v19.sh @@ -0,0 +1,118 @@ +#!/bin/bash +set -Eeuo pipefail +umask 077 + +result=${TKL_TEST_RESULT:?TKL_TEST_RESULT is required} +password=${TKL_TEST_APP_PASS:?TKL_TEST_APP_PASS is required} +entry_uid=tklv19$$ +entry_file=/tmp/tkl-openldap-entry.$$.ldif +response=/tmp/tkl-openldap-response.$$ +policy=/tmp/tkl-openldap-policy.$$ +entry_dn= +admin_dn= + +cleanup() { + if [[ -n $entry_dn ]]; then + ldapdelete -x -H ldap://127.0.0.1 \ + -D "$admin_dn" -w "$password" "$entry_dn" >/dev/null 2>&1 || true + fi + rm -f -- "$entry_file" "$response" "$policy" +} +trap cleanup EXIT + +php_fpm_service=$(systemctl list-unit-files 'php*-fpm.service' --no-legend | + awk 'NR == 1 {print $1}') +test -n "$php_fpm_service" +systemctl --quiet is-active slapd.service lighttpd.service \ + "$php_fpm_service" multi-user.target + +slapd_version=$(dpkg-query -W -f='${Version}' slapd) +ldap_utils_version=$(dpkg-query -W -f='${Version}' ldap-utils) +phpldapadmin_version=$(dpkg-query -W -f='${Version}' phpldapadmin) +lighttpd_version=$(dpkg-query -W -f='${Version}' lighttpd) +php_version=$(dpkg-query -W -f='${Version}' php-fpm) +libldap_version=$(dpkg-query -W -f='${Version}' libldap-common) + +base_dn=$(ldapsearch -LLL -x -H ldap://127.0.0.1 \ + -s base -b '' namingContexts | + awk '/^namingContexts: / && !found { + sub(/^namingContexts: /, ""); print; found=1 + }') +test -n "$base_dn" +admin_dn="cn=admin,$base_dn" + +ldapwhoami -x -H ldap://127.0.0.1 \ + -D "$admin_dn" -w "$password" >"$response" +grep -Fqi "dn:$admin_dn" "$response" + +ldapsearch -LLL -x -H ldap://127.0.0.1 \ + -D "$admin_dn" -w "$password" -b "$base_dn" \ + '(|(ou=Users)(ou=Groups)(cn=users))' dn >"$response" +grep -Fqi "dn: ou=Users,$base_dn" "$response" +grep -Fqi "dn: ou=Groups,$base_dn" "$response" +grep -Fqi "dn: cn=users,ou=Groups,$base_dn" "$response" + +entry_dn="uid=$entry_uid,ou=Users,$base_dn" +cat >"$entry_file" </dev/null +ldapsearch -LLL -x -H ldap://127.0.0.1 \ + -D "$admin_dn" -w "$password" -b "$entry_dn" -s base \ + '(objectClass=inetOrgPerson)' uid description >"$response" +grep -Fxq "uid: $entry_uid" "$response" +grep -Fxq 'description: openldap-roundtrip-ok' "$response" +ldapdelete -x -H ldap://127.0.0.1 \ + -D "$admin_dn" -w "$password" "$entry_dn" +entry_dn= +if ldapsearch -LLL -x -H ldap://127.0.0.1 \ + -D "$admin_dn" -w "$password" -b "uid=$entry_uid,ou=Users,$base_dn" \ + -s base dn >/dev/null 2>&1; then + echo 'deleted LDAP entry remained searchable' >&2 + exit 1 +fi + +ldap_host=$(hostname -f) +test -s /etc/ldap/tls/ca_cert.pem +test -s /etc/ldap/tls/openldap_crt.pem +LDAPTLS_CACERT=/etc/ldap/tls/ca_cert.pem LDAPTLS_REQCERT=demand \ + ldapwhoami -x -H "ldaps://$ldap_host" \ + -D "$admin_dn" -w "$password" >"$response" +grep -Fqi "dn:$admin_dn" "$response" +ss -ltn | awk '$4 ~ /:636$/ { found=1 } END { exit !found }' + +curl --insecure --fail --location --silent --show-error \ + https://127.0.0.1/ >"$response" +grep -qi 'phpLDAPadmin' "$response" +dpkg-query -W webmin-ldap-server >/dev/null + +before="$slapd_version|$ldap_utils_version|$phpldapadmin_version|$lighttpd_version|$php_version|$libldap_version" +apt-get update >/dev/null +for package in slapd ldap-utils phpldapadmin lighttpd php-fpm libldap-common; do + apt-cache policy "$package" >"$policy" + candidate=$(awk '/Candidate:/ {print $2}' "$policy") + test -n "$candidate" + test "$candidate" != '(none)' + grep -Eq 'http://deb\.debian\.org/debian trixie/main|http://security\.debian\.org/debian-security trixie-security/main' "$policy" +done +after="$(dpkg-query -W -f='${Version}' slapd)|$(dpkg-query -W -f='${Version}' ldap-utils)|$(dpkg-query -W -f='${Version}' phpldapadmin)|$(dpkg-query -W -f='${Version}' lighttpd)|$(dpkg-query -W -f='${Version}' php-fpm)|$(dpkg-query -W -f='${Version}' libldap-common)" +test "$after" = "$before" +grep -Rqs '^Suites: trixie' /etc/apt/sources.list.d +! grep -Rqi bookworm /etc/apt/sources.list.d + +cat >"$result" < Date: Mon, 24 Aug 2026 08:00:29 +0000 Subject: [PATCH 2/7] Handle an already-stopped slapd during reinitialization Debian Trixie's noninteractive slapd reconfiguration leaves the daemon stopped in the Fab chroot. The container stop helper previously let pgrep's no-process status abort the script under set -e before restart_slapd could start the newly configured service. Treat the absent process as the existing helper's intended already-stopped state. This keeps normal live-container and systemd behavior unchanged while allowing the build-time initialization flow to continue. Verified with Bash syntax and git diff checks; acceptance run 20260824t075354z-6354-9330 supplied the failing pgrep-after-reconfigure path. --- overlay/usr/lib/inithooks/bin/openldap-reinit.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/overlay/usr/lib/inithooks/bin/openldap-reinit.sh b/overlay/usr/lib/inithooks/bin/openldap-reinit.sh index f0c3e2a..87ac32b 100755 --- a/overlay/usr/lib/inithooks/bin/openldap-reinit.sh +++ b/overlay/usr/lib/inithooks/bin/openldap-reinit.sh @@ -22,7 +22,7 @@ stop_slapd() { if systemd-detect-virt -c 2>&1>/dev/null; then # workaround for systemctl stop not working for slapd on container builds echo "WARN: using workaround for stopping slapd on container ..." - pid=$(pgrep slapd) + pid=$(pgrep slapd || true) sleep 5 if [[ -n "$pid" ]] && ps -p $pid >/dev/null; then kill $pid @@ -309,4 +309,3 @@ if [ "$SLAPD_RUNNING" == "0" ]; then else stop_slapd fi - From 1b071943316fddd66d3764bd29c70a4994a19311 Mon Sep 17 00:00:00 2001 From: Liraz Siri Date: Mon, 24 Aug 2026 08:09:23 +0000 Subject: [PATCH 3/7] Start slapd through its init script in container builds Fab runs appliance configuration in a chroot that reports container virtualization. In that environment systemctl accepts the slapd start request but explicitly ignores it, so the following LDAP data load has no server to contact. Use the already-supported slapd init script for the container start path. The manual stop workaround and normal non-container systemd path remain unchanged. Verified the failure with the retained 20260824t080037z-1648-5973 build log and a direct chroot systemctl probe; bash syntax and whitespace checks pass. --- overlay/usr/lib/inithooks/bin/openldap-reinit.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/overlay/usr/lib/inithooks/bin/openldap-reinit.sh b/overlay/usr/lib/inithooks/bin/openldap-reinit.sh index 87ac32b..3eea80a 100755 --- a/overlay/usr/lib/inithooks/bin/openldap-reinit.sh +++ b/overlay/usr/lib/inithooks/bin/openldap-reinit.sh @@ -48,7 +48,7 @@ restart_slapd() { if systemd-detect-virt -c 2>&1>/dev/null; then # workaround for systemctl stop not working for slapd on container builds stop_slapd - systemctl start slapd + service slapd start else systemctl restart slapd fi From b5f630bef9c6bffe5a64a2eb39ce444f2a03244e Mon Sep 17 00:00:00 2001 From: Liraz Siri Date: Mon, 24 Aug 2026 08:24:49 +0000 Subject: [PATCH 4/7] Preserve OpenLDAP reinitialization output The firstboot wrapper captured all output from the reinitialization script and discarded it. When an LDAP setup command failed, administrators received only a Python exception with no failing-operation evidence. Run the script with inherited output while preserving the same arguments and checked exit-status contract. This does not alter successful OpenLDAP configuration behavior. Verified Python compilation, shell syntax, and whitespace checks; acceptance run 20260824t080939z-4020-15323 demonstrates the previously opaque failure. --- overlay/usr/lib/inithooks/bin/openldap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/overlay/usr/lib/inithooks/bin/openldap.py b/overlay/usr/lib/inithooks/bin/openldap.py index 2ced702..2d2842c 100755 --- a/overlay/usr/lib/inithooks/bin/openldap.py +++ b/overlay/usr/lib/inithooks/bin/openldap.py @@ -65,7 +65,7 @@ def main(): inithooks_cache.write('APP_DOMAIN', domain) script = os.path.join(os.path.dirname(__file__), 'openldap-reinit.sh') - subprocess.check_output([script, domain, password]) + subprocess.check_call([script, domain, password]) if __name__ == "__main__": From 1f8d2e2caa82a8b1f169bfe182f851f1df7301e1 Mon Sep 17 00:00:00 2001 From: Liraz Siri Date: Mon, 24 Aug 2026 08:41:47 +0000 Subject: [PATCH 5/7] Treat an already-stopped slapd as success Trixie package reconfiguration can leave slapd with no running process. The container stop helper reached a bare return in that state, inherited the preceding false test status, and aborted under errexit before restart. Return success explicitly when there is nothing to stop. This preserves the existing manual-stop behavior for a running daemon and allows the established init-script start to proceed. Verified by the preserved output from acceptance run 20260824t082453z-5969-17072 plus shell syntax and whitespace checks. --- overlay/usr/lib/inithooks/bin/openldap-reinit.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/overlay/usr/lib/inithooks/bin/openldap-reinit.sh b/overlay/usr/lib/inithooks/bin/openldap-reinit.sh index 3eea80a..8da78c4 100755 --- a/overlay/usr/lib/inithooks/bin/openldap-reinit.sh +++ b/overlay/usr/lib/inithooks/bin/openldap-reinit.sh @@ -27,7 +27,7 @@ stop_slapd() { if [[ -n "$pid" ]] && ps -p $pid >/dev/null; then kill $pid else - return + return 0 fi for _ in {0..10}; do if ! ps -p $pid >/dev/null; then From 91028f2a8367e620409dbb729168ddc368071075 Mon Sep 17 00:00:00 2001 From: Liraz Siri Date: Mon, 24 Aug 2026 09:01:55 +0000 Subject: [PATCH 6/7] Use the Trixie OpenSSL cipher defaults for slapd Debian Trixie links slapd against OpenSSL 3. The legacy GnuTLS priority value normal is rejected when the TLS attributes are loaded, aborting OpenLDAP initialization with LDAP error 80. Remove only the obsolete cipher override. Certificate, key, CA, and client verification configuration remain explicit, while cipher selection follows the maintained Debian/OpenSSL defaults. A disposable Trixie slapd 2.6.10 probe accepted every other TLS attribute and reproduced error 80 only for olcTLSCipherSuite. Shell syntax, Python compilation, and whitespace checks pass. --- overlay/usr/lib/inithooks/bin/openldap-reinit.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/overlay/usr/lib/inithooks/bin/openldap-reinit.sh b/overlay/usr/lib/inithooks/bin/openldap-reinit.sh index 8da78c4..2387669 100755 --- a/overlay/usr/lib/inithooks/bin/openldap-reinit.sh +++ b/overlay/usr/lib/inithooks/bin/openldap-reinit.sh @@ -154,9 +154,6 @@ olcTLSCertificateFile: $TLS_LDAP_CRT add: olcTLSCertificateKeyFile olcTLSCertificateKeyFile: $TLS_LDAP_KEY - -add: olcTLSCipherSuite -olcTLSCipherSuite: normal -- add: olcTLSVerifyClient olcTLSVerifyClient: never EOL From 2484b81e77a6c2cf0d6ae1a25009beb5fd1b5c03 Mon Sep 17 00:00:00 2001 From: Liraz Siri Date: Mon, 24 Aug 2026 09:26:08 +0000 Subject: [PATCH 7/7] Record the passing OpenLDAP 19 acceptance Document the exact passing source and harness commits, retained artifact hashes, installed versions, updater result, and the README-defined LDAP, LDAPS, and management-interface evidence. Also record the Trixie OpenSSL cipher migration finding and distinguish the known Docker test-path limitations from application behavior. No appliance behavior changed after the passing run. --- docs/v19.0-testing.md | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/docs/v19.0-testing.md b/docs/v19.0-testing.md index 5b9786e..7b5c3d7 100644 --- a/docs/v19.0-testing.md +++ b/docs/v19.0-testing.md @@ -24,6 +24,37 @@ and harness commits, archive and tree hashes, installed versions, configured-root mode, application evidence, updater evidence, cleanup result, and verdict. +## Acceptance result + +Run `20260824t090202z-3087-7105` passed against source commit +`91028f2a8367e620409dbb729168ddc368071075` with harness commit +`b6f8b8c2f3e8f00fd5cf36869e645fc08f01f87e`. The source archive and +transport archive both have SHA-256 +`5591f973238e5a2be00a9239092094e69f9ed52830b2535bbfde99736b831406`. +The retained report is +`~/.local/state/turnkey-v19-harness/runs/openldap/20260824t090202z-3087-7105/report.txt`. + +The run recorded these results: + +- Trixie HTTPS preflight, root filesystem build, Docker import, normal systemd + boot, runtime tests and cleanup all passed. +- `slapd`, Lighttpd and PHP-FPM were active after first boot. +- Administrator LDAP and trusted LDAPS binds passed. The default directory + structure and an add, search and delete round trip passed. +- The phpLDAPadmin HTTPS interface and installed Webmin LDAP module passed. +- Installed identity-defining versions were `slapd 2.6.10+dfsg-1`, + `ldap-utils 2.6.10+dfsg-1`, `phpldapadmin 1.2.6.7-4`, + `lighttpd 1.4.79-2`, `php-fpm 2:8.4+96` and + `libldap-common 2.6.10+dfsg-1`. +- The updater check refreshed signed Trixie metadata, found eligible + candidates for every checked package and left the installed versions + unchanged. + +During migration, a disposable Debian Trixie container isolated an LDAP error +80 to the old `olcTLSCipherSuite: normal` value. Trixie links slapd to OpenSSL +3, so v19 uses the maintained Debian/OpenSSL cipher defaults. The CA, +certificate, key and client verification attributes were accepted unchanged. + ## README crosswalk | README contract | Focused check | Required result | Evidence | @@ -57,4 +88,7 @@ the validated Core 19 baseline supplies the inherited evidence. ## Deferred issues -None recorded before the functional acceptance run. +No nonblocking application issues were observed in the supported OpenLDAP, +LDAPS or management-interface flows. The configured-root overlayfs fallback +and Docker hostname and sysctl warnings are limitations of this inexpensive +test path and are recorded in the retained harness report.