From b7c12e1c788202ed2724182debe96c6e2b2cdb12 Mon Sep 17 00:00:00 2001 From: Bertrand Gouny Date: Mon, 26 Oct 2026 21:54:40 +0100 Subject: [PATCH] Add optional directory tree and scoped management account --- AGENTS.md | 5 + CHANGELOG.md | 5 + README.md | 7 + build/README.md | 4 + build/tests/data_management.go | 110 +++++++++++ build/tests/test.go | 1 + build/tests/testdata/data-management.sh | 172 ++++++++++++++++++ environment/.env.bootstrap | 9 + .../openldap-bootstrap/assets/ldif/README.md | 25 +++ .../63-data-management-acl.ldif.template | 20 ++ .../80-data-management-account.ldif.template | 6 + .../data/data-tree/30-data-tree.ldif.template | 14 ++ .../assets/scripts/05-data-tree.sh | 36 ++++ .../assets/scripts/25-data-management.sh | 43 +++++ .../assets/scripts/README.md | 2 + 15 files changed, 459 insertions(+) create mode 100644 build/tests/data_management.go create mode 100644 build/tests/testdata/data-management.sh create mode 100644 services/openldap-bootstrap/assets/ldif/config/data-management/63-data-management-acl.ldif.template create mode 100644 services/openldap-bootstrap/assets/ldif/data/data-management/80-data-management-account.ldif.template create mode 100644 services/openldap-bootstrap/assets/ldif/data/data-tree/30-data-tree.ldif.template create mode 100755 services/openldap-bootstrap/assets/scripts/05-data-tree.sh create mode 100755 services/openldap-bootstrap/assets/scripts/25-data-management.sh diff --git a/AGENTS.md b/AGENTS.md index 3ea7daf..541930b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,6 +2,11 @@ Guidance for coding agents working on `osixia/openldap` (v2), an OpenLDAP container image built on [osixia/baseimage](https://github.com/osixia/container-baseimage) (Alpine). +## Documentation + +- Keep the main `README.md` concise and simple. For new options, add only the required environment-variable table rows there; put explanations in the relevant service README. +- Document bootstrap features briefly alongside the other features in the existing bootstrap READMEs. Do not create a separate documentation page for a small feature unless explicitly requested, and do not add feature explanations to the main README. + ## Repository layout | Path | Role | diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ba1ed5..7467878 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ Project-specific changes only. See upstream [OpenLDAP release notes](https://www ### Added +- Add optional `OPENLDAP_BOOTSTRAP_DATA_TREE` to create users, groups and services + OUs, and `OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT` for one scoped application account. + It reads users/groups and resets user passwords, including with ppolicy safe + modification, without hash/history reads or writes to profiles, memberships, + service accounts or configuration. Both features are disabled by default. - Add `openldap-ctl validate-ldif` for files and directories to check data entry LDIF syntax, values and schemas without importing entries or stopping slapd. - Expose bootstrap settings for pending request limits, incoming LDAP message sizes diff --git a/README.md b/README.md index b8f2238..a7a0ce5 100644 --- a/README.md +++ b/README.md @@ -381,6 +381,13 @@ docker run --rm --user $UID -v $(pwd)/schema:/data \ | `OPENLDAP_BOOTSTRAP_DATA_LAST_BIND_PRECISION` | Minimum interval between `pwdLastSuccess` updates, in seconds | `3600` | | `OPENLDAP_BOOTSTRAP_DATA_ROOT_DN` | Root DN for main database | `cn=admin,${OPENLDAP_BOOTSTRAP_SUFFIX}` | | `OPENLDAP_BOOTSTRAP_DATA_ROOT_PASSWORD_HASHED` | Hashed password for main database root DN; generated if empty | `` | +| `OPENLDAP_BOOTSTRAP_DATA_TREE` | Create the users, groups and services OUs | `false` | +| `OPENLDAP_BOOTSTRAP_DATA_TREE_USERS_DN` | Users OU DN | `ou=users,${OPENLDAP_BOOTSTRAP_SUFFIX}` | +| `OPENLDAP_BOOTSTRAP_DATA_TREE_GROUPS_DN` | Groups OU DN | `ou=groups,${OPENLDAP_BOOTSTRAP_SUFFIX}` | +| `OPENLDAP_BOOTSTRAP_DATA_TREE_SERVICES_DN` | Services OU DN | `ou=services,${OPENLDAP_BOOTSTRAP_SUFFIX}` | +| `OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT` | Enable user/group reads and user password resets | `false` | +| `OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_DN` | Management account DN | `cn=management,${OPENLDAP_BOOTSTRAP_DATA_TREE_SERVICES_DN}` | +| `OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_PASSWORD_HASHED` | Hashed management password; generated if empty when enabled | `` | | `OPENLDAP_BOOTSTRAP_DATA_READONLY` | Enable read-only data account creation | `false` | | `OPENLDAP_BOOTSTRAP_DATA_READONLY_DN` | DN of the read-only data account | `cn=readonly,${OPENLDAP_BOOTSTRAP_SUFFIX}` | | `OPENLDAP_BOOTSTRAP_DATA_READONLY_PASSWORD_HASHED` | Hashed password of the read-only data account; generated if empty when enabled | `` | diff --git a/build/README.md b/build/README.md index 1db58db..71bfcfa 100644 --- a/build/README.md +++ b/build/README.md @@ -21,6 +21,10 @@ own native runner, then publishes them together from their OCI archives After the shared version check, `tests/` runs integration scenarios on each built image: - default bootstrap; +- standard users/groups/services tree and management account: independent opt-ins, + custom DNs and supplied OUs, generated passwords, user/group + reads, password resets including application administrators, ppolicy-safe resets, + hidden hashes/history, and denied profile, membership, entry and service changes; - global request limits: native defaults, custom values and incoming message size enforcement for anonymous and authenticated connections; - read-only bootstrap templates with projected-volume symlinks, custom overrides diff --git a/build/tests/data_management.go b/build/tests/data_management.go new file mode 100644 index 0000000..3f8ee7d --- /dev/null +++ b/build/tests/data_management.go @@ -0,0 +1,110 @@ +package tests + +import ( + "context" + _ "embed" + "fmt" + "strings" + + "dagger.io/dagger" +) + +// DataManagement checks optional tree creation and the scoped password-reset account. +func DataManagement(ctx context.Context, client *dagger.Client, image *dagger.Container) error { + passwords, err := rootPasswords(ctx, image) + if err != nil { + return err + } + base := merge(passwords, env{ + "OPENLDAP_BOOTSTRAP_DATA_TREE": "true", + "OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT": "true", + "OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_PASSWORD_HASHED": passwords["OPENLDAP_BOOTSTRAP_DATA_ROOT_PASSWORD_HASHED"], + }) + + for _, mode := range []string{"disabled", "tree", "plain", "policy", "custom", "supplied"} { + if err := func() error { + usersDN := "ou=users," + suffix + groupsDN := "ou=groups," + suffix + servicesDN := "ou=services," + suffix + managementDN := "cn=management," + servicesDN + settings := merge(base) + nodeImage := image + switch mode { + case "disabled": + settings["OPENLDAP_BOOTSTRAP_DATA_TREE"] = "false" + settings["OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT"] = "false" + case "tree": + settings["OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT"] = "false" + case "policy": + settings = merge(settings, env{ + "OPENLDAP_BOOTSTRAP_PPOLICY": "true", + "OPENLDAP_BOOTSTRAP_MEMBEROF": "true", + "OPENLDAP_BOOTSTRAP_REFINT": "true", + "OPENLDAP_BOOTSTRAP_REFINT_NOTHING_DN": "cn=empty-group," + servicesDN, + "OPENLDAP_BOOTSTRAP_UNIQUE": "true", + "OPENLDAP_BOOTSTRAP_AUDITLOG_ENABLED": "true", + "OPENLDAP_BOOTSTRAP_DATA_READONLY": "true", + "OPENLDAP_BOOTSTRAP_DATA_READONLY_PASSWORD_HASHED": passwords["OPENLDAP_BOOTSTRAP_DATA_ROOT_PASSWORD_HASHED"], + }) + case "custom": + usersDN = "ou=people," + suffix + groupsDN = "ou=roles," + suffix + servicesDN = "ou=machines," + suffix + managementDN = "cn=sso," + servicesDN + case "supplied": + settings["OPENLDAP_BOOTSTRAP_DATA_TREE"] = "false" + nodeImage = image.WithNewFile("/container/services/openldap-bootstrap/assets/ldif/data/custom/30-custom-tree.ldif", fmt.Sprintf(`dn: %s +objectClass: organizationalUnit +ou: users + +dn: %s +objectClass: organizationalUnit +ou: groups + +dn: %s +objectClass: organizationalUnit +ou: services +`, usersDN, groupsDN, servicesDN)) + } + settings = merge(settings, env{ + "OPENLDAP_BOOTSTRAP_DATA_TREE_USERS_DN": usersDN, + "OPENLDAP_BOOTSTRAP_DATA_TREE_GROUPS_DN": groupsDN, + "OPENLDAP_BOOTSTRAP_DATA_TREE_SERVICES_DN": servicesDN, + "OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_DN": managementDN, + }) + node := "management-" + mode + service, err := startNode(ctx, nodeImage, node, settings, nil, nil) + if err != nil { + return err + } + defer stopNodes(ctx, []*dagger.Service{service}) + return check(ctx, image, nil, env{ + "TEST_NODES": node, + "TEST_MANAGEMENT_MODE": mode, + "TEST_USERS_DN": usersDN, + "TEST_GROUPS_DN": groupsDN, + "TEST_SERVICES_DN": servicesDN, + "TEST_MANAGEMENT_DN": managementDN, + }, dataManagementCheck) + }(); err != nil { + return fmt.Errorf("data management %s: %w", mode, err) + } + } + + generated, err := expect(ctx, withEnv(image, merge(base, env{"OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_PASSWORD_HASHED": ""})), true, + []string{"Generated database management user password:", "config file testing succeeded"}, "container", "--skip-process") + if err != nil { + return err + } + data, err := generated.container.WithExec([]string{"container", "run", "--", "slapcat", "-n", "1", "-a", "(cn=management)"}).Stdout(ctx) + if err != nil { + return err + } + if !strings.Contains(data, "cn: management") || !strings.Contains(data, "userPassword:: ") { + return fmt.Errorf("generated management account/password missing") + } + return nil +} + +//go:embed testdata/data-management.sh +var dataManagementCheck string diff --git a/build/tests/test.go b/build/tests/test.go index 429bd57..cc6bc92 100644 --- a/build/tests/test.go +++ b/build/tests/test.go @@ -19,6 +19,7 @@ type scenario struct { var scenarios = []scenario{ {"default bootstrap", Default}, + {"standard data tree and management account", DataManagement}, {"global request limits", GlobalLimits}, {"read-only bootstrap templates", ReadOnlyTemplates}, {"alternative schemas", AlternativeSchemas}, diff --git a/build/tests/testdata/data-management.sh b/build/tests/testdata/data-management.sh new file mode 100644 index 0000000..9ee1cf5 --- /dev/null +++ b/build/tests/testdata/data-management.sh @@ -0,0 +1,172 @@ +#!/bin/bash +# shellcheck disable=SC2153 + +set -euo pipefail + +# if container log level is trace: +# print commands and their arguments as they are executed +container log level eq trace && set -x + +# shellcheck disable=SC1091 +source /tmp/openldap-test-lib.sh + +URI="ldap://${TEST_NODES}:3890" +USER_DN="uid=alice,${TEST_USERS_DN}" +APP_ADMIN_DN="uid=alice-admin,${TEST_USERS_DN}" +GROUP_DN="cn=cloud-admins,${TEST_GROUPS_DN}" +SERVICE_DN="cn=other-service,${TEST_SERVICES_DN}" +OUTSIDE_DN="uid=outside,${TEST_SUFFIX}" +NEW_PASSWORD="Changed-passw0rd-2026" +NEXT_PASSWORD="Another-passw0rd-2026" + +eventually has_entry "${URI}" "${TEST_SUFFIX}" + +if [ "${TEST_MANAGEMENT_MODE}" = "disabled" ]; then + for dn in "${TEST_USERS_DN}" "${TEST_GROUPS_DN}" "${TEST_SERVICES_DN}" "${TEST_MANAGEMENT_DN}"; do + expect_rc 32 bind_search "${URI}" "${ADMIN_DN}" -b "${dn}" -s base dn + done + ok 'tree and management are disabled by default' + exit 0 +fi + +for dn in "${TEST_USERS_DN}" "${TEST_GROUPS_DN}" "${TEST_SERVICES_DN}"; do + expect_rc 0 bind_search "${URI}" "${ADMIN_DN}" -b "${dn}" -s base objectClass + expect_output '^objectClass: organizationalUnit$' +done + +if [ "${TEST_MANAGEMENT_MODE}" = "tree" ]; then + expect_rc 32 bind_search "${URI}" "${ADMIN_DN}" -b "${TEST_MANAGEMENT_DN}" -s base dn + ok 'tree creation does not enable the management account' + exit 0 +fi + +expect_rc 0 bind_search "${URI}" "${ADMIN_DN}" -b "${TEST_MANAGEMENT_DN}" -s base objectClass userPassword +expect_output '^objectClass: simpleSecurityObject$' +expect_output '^userPassword:' +expect_rc 0 ldapwhoami -x -H "${URI}" -D "${TEST_MANAGEMENT_DN}" -w "${TEST_PASSWORD}" + +for dn in "${USER_DN}" "${APP_ADMIN_DN}" "${OUTSIDE_DN}"; do + uid="${dn%%,*}" + expect_rc 0 bind_modify "${URI}" "${ADMIN_DN}" -a <`, to read users/groups and reset user +passwords. It cannot edit profiles or groups or read password hashes/history. +Both options are independent and disabled by default. + +The OU DNs, account DN and password hash are configurable through the matching +`OPENLDAP_BOOTSTRAP_DATA_TREE_*_DN` and `OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_*` variables. +If custom LDIFs supply the OUs, leave tree creation disabled and load them before +`80-data-management-account.ldif`. Do not duplicate automatically created OUs. + +## config/data-management + +`63-data-management-acl.ldif.template` limits password writes to the users branch. +Its `=wm` privileges allow administrative resets without the old password, without +hash read/compare. With ppolicy, these resets bypass quality, history-reuse and +minimum-age checks: enforce password requirements in the application. +Service accounts and configuration remain protected; other identities keep their +usual ACLs. + ## config/ppolicy-readonly `64-password-history-acl.ldif.template` denies `pwdHistory` access to the data diff --git a/services/openldap-bootstrap/assets/ldif/config/data-management/63-data-management-acl.ldif.template b/services/openldap-bootstrap/assets/ldif/config/data-management/63-data-management-acl.ldif.template new file mode 100644 index 0000000..ac041ed --- /dev/null +++ b/services/openldap-bootstrap/assets/ldif/config/data-management/63-data-management-acl.ldif.template @@ -0,0 +1,20 @@ +# Grant only write and password-administrator privileges, without hash read/compare. +# The manage bit allows ppolicy resets without knowing the old password. +olcAccess: to dn.subtree="${OPENLDAP_BOOTSTRAP_DATA_TREE_USERS_DN}" attrs=userPassword + by dn.exact="${OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_DN}" =wm + by * break +olcAccess: to attrs=${OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_HIDDEN_ATTRIBUTES} + by dn.exact="${OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_DN}" none + by * break +olcAccess: to dn.subtree="${OPENLDAP_BOOTSTRAP_DATA_TREE_USERS_DN}" + by dn.exact="${OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_DN}" read + by * break +olcAccess: to dn.subtree="${OPENLDAP_BOOTSTRAP_DATA_TREE_GROUPS_DN}" + by dn.exact="${OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_DN}" read + by * break +olcAccess: to dn.base="${OPENLDAP_BOOTSTRAP_SUFFIX}" attrs=entry,objectClass,o,dc + by dn.exact="${OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_DN}" read + by * break +olcAccess: to * + by dn.exact="${OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_DN}" none + by * break diff --git a/services/openldap-bootstrap/assets/ldif/data/data-management/80-data-management-account.ldif.template b/services/openldap-bootstrap/assets/ldif/data/data-management/80-data-management-account.ldif.template new file mode 100644 index 0000000..11ceeea --- /dev/null +++ b/services/openldap-bootstrap/assets/ldif/data/data-management/80-data-management-account.ldif.template @@ -0,0 +1,6 @@ +dn: ${OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_DN} +objectClass: top +objectClass: organizationalRole +objectClass: simpleSecurityObject +cn: ${OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_CN} +userPassword: ${OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_PASSWORD_HASHED} diff --git a/services/openldap-bootstrap/assets/ldif/data/data-tree/30-data-tree.ldif.template b/services/openldap-bootstrap/assets/ldif/data/data-tree/30-data-tree.ldif.template new file mode 100644 index 0000000..54e5d23 --- /dev/null +++ b/services/openldap-bootstrap/assets/ldif/data/data-tree/30-data-tree.ldif.template @@ -0,0 +1,14 @@ +dn: ${OPENLDAP_BOOTSTRAP_DATA_TREE_USERS_DN} +objectClass: top +objectClass: organizationalUnit +ou: ${OPENLDAP_BOOTSTRAP_DATA_TREE_USERS_OU} + +dn: ${OPENLDAP_BOOTSTRAP_DATA_TREE_GROUPS_DN} +objectClass: top +objectClass: organizationalUnit +ou: ${OPENLDAP_BOOTSTRAP_DATA_TREE_GROUPS_OU} + +dn: ${OPENLDAP_BOOTSTRAP_DATA_TREE_SERVICES_DN} +objectClass: top +objectClass: organizationalUnit +ou: ${OPENLDAP_BOOTSTRAP_DATA_TREE_SERVICES_OU} diff --git a/services/openldap-bootstrap/assets/scripts/05-data-tree.sh b/services/openldap-bootstrap/assets/scripts/05-data-tree.sh new file mode 100755 index 0000000..0eebaa4 --- /dev/null +++ b/services/openldap-bootstrap/assets/scripts/05-data-tree.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# shellcheck disable=SC2034 +# shellcheck disable=SC1091 +# shellcheck disable=SC2153 + +set -euo pipefail + +# if container log level is trace: +# print commands and their arguments as they are executed +container log level eq trace && set -x + +if [ "${OPENLDAP_BOOTSTRAP_DATA_TREE}" = "true" ]; then + + source "/container/services/openldap-bootstrap/assets/scripts/helpers/common.sh" + + check_args "$@" + + TYPE="$1" + + if [ "${TYPE}" = "env" ]; then + ENV_FILE="$2" + + OPENLDAP_BOOTSTRAP_DATA_TREE_USERS_OU=$(get_dn_attr ou "${OPENLDAP_BOOTSTRAP_DATA_TREE_USERS_DN}") + OPENLDAP_BOOTSTRAP_DATA_TREE_GROUPS_OU=$(get_dn_attr ou "${OPENLDAP_BOOTSTRAP_DATA_TREE_GROUPS_DN}") + OPENLDAP_BOOTSTRAP_DATA_TREE_SERVICES_OU=$(get_dn_attr ou "${OPENLDAP_BOOTSTRAP_DATA_TREE_SERVICES_DN}") + + prepare_env_file "${ENV_FILE}" OPENLDAP_BOOTSTRAP_DATA_TREE_USERS_OU OPENLDAP_BOOTSTRAP_DATA_TREE_GROUPS_OU OPENLDAP_BOOTSTRAP_DATA_TREE_SERVICES_OU + + elif [ "${TYPE}" = "ldif" ]; then + SOURCE_DIR="$2" + OUTPUT_DIR="$3" + + prepare_ldif_files "${SOURCE_DIR}/data-tree" "${OUTPUT_DIR}" + fi + +fi diff --git a/services/openldap-bootstrap/assets/scripts/25-data-management.sh b/services/openldap-bootstrap/assets/scripts/25-data-management.sh new file mode 100755 index 0000000..160793a --- /dev/null +++ b/services/openldap-bootstrap/assets/scripts/25-data-management.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# shellcheck disable=SC2034 +# shellcheck disable=SC1091 +# shellcheck disable=SC2153 + +set -euo pipefail + +# if container log level is trace: +# print commands and their arguments as they are executed +container log level eq trace && set -x + +if [ "${OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT}" = "true" ]; then + + source "/container/services/openldap-bootstrap/assets/scripts/helpers/common.sh" + + check_args "$@" + + TYPE="$1" + + if [ "${TYPE}" = "env" ]; then + ENV_FILE="$2" + + source "/container/services/openldap-bootstrap/assets/scripts/helpers/password.sh" + + OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_CN=$(get_dn_attr cn "${OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_DN}") + + ensure_hashed_password OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_PASSWORD_HASHED "database management user" + + OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_HIDDEN_ATTRIBUTES="userPassword,shadowLastChange" + if [ "${OPENLDAP_BOOTSTRAP_PPOLICY}" = "true" ]; then + OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_HIDDEN_ATTRIBUTES+=",pwdHistory" + fi + + prepare_env_file "${ENV_FILE}" OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_CN OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_PASSWORD_HASHED OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_HIDDEN_ATTRIBUTES + + elif [ "${TYPE}" = "ldif" ]; then + SOURCE_DIR="$2" + OUTPUT_DIR="$3" + + prepare_ldif_files "${SOURCE_DIR}/data-management" "${OUTPUT_DIR}" + fi + +fi diff --git a/services/openldap-bootstrap/assets/scripts/README.md b/services/openldap-bootstrap/assets/scripts/README.md index 3cf637b..3328e6e 100644 --- a/services/openldap-bootstrap/assets/scripts/README.md +++ b/services/openldap-bootstrap/assets/scripts/README.md @@ -12,9 +12,11 @@ Scripts are executed in numeric order. Most are feature-gated by an environment | Script | Trigger | Description | |--------|---------|-------------| | `00-base.sh` | always | Core bootstrap: root passwords, modules, schemas and base LDIF files, including global request limits | +| `05-data-tree.sh` | `OPENLDAP_BOOTSTRAP_DATA_TREE=true` | Creates the users, groups and services OUs | | `10-tls.sh` | `OPENLDAP_BOOTSTRAP_TLS=true` | Applies TLS certificate and protocol configuration | | `15-tls-required.sh` | `OPENLDAP_BOOTSTRAP_TLS_REQUIRED=true` | Enforces minimum 128-bit SSF (warns if TLS is not also enabled) | | `20-database-readonly.sh` | `OPENLDAP_BOOTSTRAP_DATA_READONLY=true` | Creates a read-only service account for the data database | +| `25-data-management.sh` | `OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT=true` | Creates one account below the services branch, reading users/groups and resetting user passwords without hash/history access or other write permissions | | `30-monitor-readonly.sh` | `OPENLDAP_BOOTSTRAP_MONITOR_READONLY=true` | Creates a read-only service account for the monitor database | | `35-auditlog.sh` | `OPENLDAP_BOOTSTRAP_AUDITLOG_ENABLED=true` | Configures file-based auditing on the data database | | `40-refint.sh` | `OPENLDAP_BOOTSTRAP_REFINT=true` | Configures the referential integrity overlay and optional replacement DN |