feat(bootstrap): configure management ACLs and align feature names

Expose management hidden attribute lists through bootstrap environment defaults and keep ACL rendering in the LDIF template. Align management assets with database-readonly naming and drop the MONITOR and AUDITLOG flag suffixes.

Update documentation, migration notes and integration coverage for custom hidden attributes, empty effective lists, audit logging and monitoring.
This commit is contained in:
Bertrand Gouny
2026-10-27 11:10:13 +01:00
parent b7c12e1c78
commit 02b5f0354d
19 changed files with 135 additions and 73 deletions
+16 -3
View File
@@ -6,10 +6,13 @@ Project-specific changes only. See upstream [OpenLDAP release notes](https://www
### Added
- Expose management account hidden attributes through
`OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_HIDDEN_ATTRIBUTES` and
`OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_HIDDEN_PPOLICY_ATTRIBUTES`.
- 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,
By default, 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.
@@ -25,7 +28,7 @@ Project-specific changes only. See upstream [OpenLDAP release notes](https://www
contents change, ignoring duplicate events and unchanged projected volumes.
- Default `OPENLDAP_BOOTSTRAP_MODULES` to `auto` to load base and enabled feature
modules; explicit lists retain full control over module selection and order.
- Add optional file-based auditing with `OPENLDAP_BOOTSTRAP_AUDITLOG_ENABLED`
- Add optional file-based auditing with `OPENLDAP_BOOTSTRAP_AUDITLOG`
and `OPENLDAP_BOOTSTRAP_AUDITLOG_FILE`.
- Add `OPENLDAP_BOOTSTRAP_DATA_LAST_BIND` and `OPENLDAP_BOOTSTRAP_DATA_LAST_BIND_PRECISION`
to track successful LDAP authentications in `pwdLastSuccess` (disabled by default).
@@ -35,6 +38,16 @@ Project-specific changes only. See upstream [OpenLDAP release notes](https://www
- Test default behavior, automatic role creation and preservation of a group after
deletion of its last real member.
### Changed
- Rename bootstrap feature flags `OPENLDAP_BOOTSTRAP_MONITOR_ENABLED` and
`OPENLDAP_BOOTSTRAP_AUDITLOG_ENABLED` to `OPENLDAP_BOOTSTRAP_MONITOR` and
`OPENLDAP_BOOTSTRAP_AUDITLOG`. Update deployment environments to the new names;
the previous names are no longer read.
- Align management bootstrap scripts, directories and LDIF filenames with the
existing `database-readonly` convention by using `database-management`.
Custom LDIF overrides must use the new filenames.
### Fixed
- Hide `pwdHistory` from the data read-only account when ppolicy is enabled
+5 -3
View File
@@ -79,7 +79,7 @@ docker exec openldap container run -- \
-b "dc=keycloakers,dc=org" "(objectClass=*)"
```
A successful response returns the organization entry and the `cn=admin` account.
A successful response returns the organization entry. The root DN `cn=admin` authenticates without requiring a directory entry.
**Passing command-line arguments to OpenLDAP**
@@ -388,14 +388,16 @@ docker run --rm --user $UID -v $(pwd)/schema:/data \
| `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_MANAGEMENT_HIDDEN_ATTRIBUTES` | Comma-separated attributes hidden from the management account | `userPassword,shadowLastChange` |
| `OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_HIDDEN_PPOLICY_ATTRIBUTES` | Additional hidden attributes when ppolicy is enabled | `pwdHistory` |
| `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 | `` |
| `OPENLDAP_BOOTSTRAP_MONITOR_ENABLED` | Enable monitor backend | `false` |
| `OPENLDAP_BOOTSTRAP_MONITOR` | Enable monitor backend | `false` |
| `OPENLDAP_BOOTSTRAP_MONITOR_READONLY` | Enable read-only monitor account creation | `false` |
| `OPENLDAP_BOOTSTRAP_MONITOR_READONLY_DN` | DN of the read-only monitor account | `cn=readonly-monitor,${OPENLDAP_BOOTSTRAP_SUFFIX}` |
| `OPENLDAP_BOOTSTRAP_MONITOR_READONLY_PASSWORD_HASHED` | Hashed password of the read-only monitor account; generated if empty when enabled | `` |
| `OPENLDAP_BOOTSTRAP_AUDITLOG_ENABLED` | Log successful LDAP writes | `false` |
| `OPENLDAP_BOOTSTRAP_AUDITLOG` | Log successful LDAP writes | `false` |
| `OPENLDAP_BOOTSTRAP_AUDITLOG_FILE` | Audit LDIF file; requires a writable parent directory | `/var/log/openldap/audit.ldif` |
| `OPENLDAP_BOOTSTRAP_LDIF_CONFIG_DIR` | Source directory for config LDIF bootstrap files | `/container/services/openldap-bootstrap/assets/ldif/config` |
| `OPENLDAP_BOOTSTRAP_LDIF_DATA_DIR` | Source directory for data LDIF bootstrap files | `/container/services/openldap-bootstrap/assets/ldif/data` |
+3 -1
View File
@@ -24,7 +24,9 @@ After the shared version check, `tests/` runs integration scenarios on each buil
- 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;
configurable hidden attributes with/without ppolicy, rejection of empty combined
lists, hidden hashes/history by default, 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
+1 -1
View File
@@ -19,7 +19,7 @@ func AuditLog(ctx context.Context, client *dagger.Client, image *dagger.Containe
settings := env{}
file := "/var/log/openldap/audit.ldif"
if mode != "default" {
settings["OPENLDAP_BOOTSTRAP_AUDITLOG_ENABLED"] = "true"
settings["OPENLDAP_BOOTSTRAP_AUDITLOG"] = "true"
}
if mode == "custom" {
file = "/var/log/openldap/custom audit.ldif"
+6 -6
View File
@@ -126,18 +126,18 @@ func AllFeatures(ctx context.Context, client *dagger.Client, image *dagger.Conta
"OPENLDAP_BOOTSTRAP_DATA_READONLY": "true",
"OPENLDAP_BOOTSTRAP_DATA_READONLY_PASSWORD_HASHED": hash,
"OPENLDAP_BOOTSTRAP_MONITOR_ENABLED": "true",
"OPENLDAP_BOOTSTRAP_MONITOR": "true",
"OPENLDAP_BOOTSTRAP_MONITOR_READONLY": "true",
"OPENLDAP_BOOTSTRAP_MONITOR_READONLY_PASSWORD_HASHED": hash,
"OPENLDAP_BOOTSTRAP_TLS": "true",
"OPENLDAP_BOOTSTRAP_TLS_REQUIRED": "true",
"OPENLDAP_BOOTSTRAP_AUDITLOG_ENABLED": "true",
"OPENLDAP_BOOTSTRAP_REFINT": "true",
"OPENLDAP_BOOTSTRAP_PPOLICY": "true",
"OPENLDAP_BOOTSTRAP_UNIQUE": "true",
"OPENLDAP_BOOTSTRAP_MEMBEROF": "true",
"OPENLDAP_BOOTSTRAP_AUDITLOG": "true",
"OPENLDAP_BOOTSTRAP_REFINT": "true",
"OPENLDAP_BOOTSTRAP_PPOLICY": "true",
"OPENLDAP_BOOTSTRAP_UNIQUE": "true",
"OPENLDAP_BOOTSTRAP_MEMBEROF": "true",
"OPENLDAP_BOOTSTRAP_REPLICATION": "true",
"OPENLDAP_BOOTSTRAP_REPLICATION_HOSTS": strings.Join(hosts, " "),
+20 -2
View File
@@ -21,7 +21,7 @@ func DataManagement(ctx context.Context, client *dagger.Client, image *dagger.Co
"OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_PASSWORD_HASHED": passwords["OPENLDAP_BOOTSTRAP_DATA_ROOT_PASSWORD_HASHED"],
})
for _, mode := range []string{"disabled", "tree", "plain", "policy", "custom", "supplied"} {
for _, mode := range []string{"disabled", "tree", "plain", "policy", "custom", "supplied", "hidden", "policy-hidden", "policy-only", "empty", "policy-disabled"} {
if err := func() error {
usersDN := "ou=users," + suffix
groupsDN := "ou=groups," + suffix
@@ -35,6 +35,19 @@ func DataManagement(ctx context.Context, client *dagger.Client, image *dagger.Co
settings["OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT"] = "false"
case "tree":
settings["OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT"] = "false"
case "hidden":
settings["OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_HIDDEN_ATTRIBUTES"] = "userPassword,shadowLastChange,mail"
case "policy-hidden", "policy-only", "empty":
settings["OPENLDAP_BOOTSTRAP_PPOLICY"] = "true"
settings["OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_HIDDEN_PPOLICY_ATTRIBUTES"] = "pwdHistory,mail"
if mode == "policy-only" || mode == "empty" {
settings["OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_HIDDEN_ATTRIBUTES"] = ""
}
if mode == "empty" {
settings["OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_HIDDEN_PPOLICY_ATTRIBUTES"] = ""
}
case "policy-disabled":
settings["OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_HIDDEN_PPOLICY_ATTRIBUTES"] = "pwdHistory,mail"
case "policy":
settings = merge(settings, env{
"OPENLDAP_BOOTSTRAP_PPOLICY": "true",
@@ -42,7 +55,7 @@ func DataManagement(ctx context.Context, client *dagger.Client, image *dagger.Co
"OPENLDAP_BOOTSTRAP_REFINT": "true",
"OPENLDAP_BOOTSTRAP_REFINT_NOTHING_DN": "cn=empty-group," + servicesDN,
"OPENLDAP_BOOTSTRAP_UNIQUE": "true",
"OPENLDAP_BOOTSTRAP_AUDITLOG_ENABLED": "true",
"OPENLDAP_BOOTSTRAP_AUDITLOG": "true",
"OPENLDAP_BOOTSTRAP_DATA_READONLY": "true",
"OPENLDAP_BOOTSTRAP_DATA_READONLY_PASSWORD_HASHED": passwords["OPENLDAP_BOOTSTRAP_DATA_ROOT_PASSWORD_HASHED"],
})
@@ -72,6 +85,11 @@ ou: services
"OPENLDAP_BOOTSTRAP_DATA_TREE_SERVICES_DN": servicesDN,
"OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_DN": managementDN,
})
if mode == "empty" {
_, err := expect(ctx, withEnv(nodeImage, settings), false,
[]string{"Management hidden attributes must not be empty"}, "container", "--skip-process")
return err
}
node := "management-" + mode
service, err := startNode(ctx, nodeImage, node, settings, nil, nil)
if err != nil {
+8 -8
View File
@@ -27,19 +27,19 @@ func AutomaticModules(ctx context.Context, client *dagger.Client, image *dagger.
{"default", nil, baseModules, false},
{"custom order", env{"OPENLDAP_BOOTSTRAP_MODULES": "back_mdb.so pw-sha2.so argon2.so memberof.so"}, "back_mdb.so pw-sha2.so argon2.so memberof.so", false},
{"explicit aliases", env{
"OPENLDAP_BOOTSTRAP_MODULES": explicitModules,
"OPENLDAP_BOOTSTRAP_AUDITLOG_ENABLED": "true",
"OPENLDAP_BOOTSTRAP_PPOLICY": "true",
"OPENLDAP_BOOTSTRAP_UNIQUE": "true",
"OPENLDAP_BOOTSTRAP_REFINT": "true",
"OPENLDAP_BOOTSTRAP_MEMBEROF": "true",
"OPENLDAP_BOOTSTRAP_MODULES": explicitModules,
"OPENLDAP_BOOTSTRAP_AUDITLOG": "true",
"OPENLDAP_BOOTSTRAP_PPOLICY": "true",
"OPENLDAP_BOOTSTRAP_UNIQUE": "true",
"OPENLDAP_BOOTSTRAP_REFINT": "true",
"OPENLDAP_BOOTSTRAP_MEMBEROF": "true",
}, explicitModules, false},
{"missing explicit module", env{
"OPENLDAP_BOOTSTRAP_MODULES": baseModules,
"OPENLDAP_BOOTSTRAP_UNIQUE": "true",
}, "", true},
{"all automatic", env{
"OPENLDAP_BOOTSTRAP_AUDITLOG_ENABLED": "true",
"OPENLDAP_BOOTSTRAP_AUDITLOG": "true",
"OPENLDAP_BOOTSTRAP_PPOLICY": "true",
"OPENLDAP_BOOTSTRAP_UNIQUE": "true",
"OPENLDAP_BOOTSTRAP_REFINT": "true",
@@ -49,7 +49,7 @@ func AutomaticModules(ctx context.Context, client *dagger.Client, image *dagger.
}, baseModules + " ppolicy.so unique.so refint.so memberof.so auditlog.so syncprov.so", false},
}
for _, feature := range []struct{ flag, module string }{
{"AUDITLOG_ENABLED", "auditlog"},
{"AUDITLOG", "auditlog"},
{"PPOLICY", "ppolicy"},
{"UNIQUE", "unique"},
{"REFINT", "refint"},
+16 -6
View File
@@ -77,7 +77,14 @@ expect_rc 0 bind_search "${URI}" "${TEST_MANAGEMENT_DN}" -b "${TEST_SUFFIX}" \
'(&(uid=alice)(objectClass=inetOrgPerson))' uid displayName mail userPassword
expect_output "^dn: ${USER_DN}$"
expect_output '^displayName: Test User$'
expect_output '^mail: alice@example.org$'
case "${TEST_MANAGEMENT_MODE}" in
hidden|policy-hidden|policy-only)
expect_no_output '^mail:'
;;
*)
expect_output '^mail: alice@example.org$'
;;
esac
expect_no_output '^userPassword:'
expect_rc 0 bind_search "${URI}" "${TEST_MANAGEMENT_DN}" -b "${TEST_GROUPS_DN}" \
"(&(member=${APP_ADMIN_DN})(objectClass=groupOfNames))" cn member
@@ -96,7 +103,7 @@ for dn in "${USER_DN}" "${APP_ADMIN_DN}"; do
done
ok 'management resets ordinary and application-admin passwords without the old password'
if [ "${TEST_MANAGEMENT_MODE}" = "policy" ]; then
if [[ "${TEST_MANAGEMENT_MODE}" = policy* && "${TEST_MANAGEMENT_MODE}" != "policy-disabled" ]]; then
# Authelia's standard LDAP Modify also needs to work with pwdSafeModify=TRUE.
expect_rc 0 bind_modify "${URI}" "${TEST_MANAGEMENT_DN}" <<LDIF
dn: ${USER_DN}
@@ -109,13 +116,16 @@ LDIF
expect_output '^userPassword:: '
expect_output '^pwdHistory:'
expect_rc 0 bind_search "${URI}" "${TEST_MANAGEMENT_DN}" -b "${USER_DN}" -s base userPassword pwdHistory
expect_no_output '^(userPassword|pwdHistory):'
expect_rc 0 bind_search "${URI}" "cn=readonly,${TEST_SUFFIX}" -b "${USER_DN}" -s base cn pwdHistory
expect_no_output '^userPassword:'
expect_no_output '^pwdHistory:'
expect_fail ldappasswd -x -H "${URI}" -D "cn=readonly,${TEST_SUFFIX}" -w "${TEST_PASSWORD}" -s "${NEW_PASSWORD}" "${USER_DN}"
if [ "${TEST_MANAGEMENT_MODE}" = "policy" ]; then
expect_rc 0 bind_search "${URI}" "cn=readonly,${TEST_SUFFIX}" -b "${USER_DN}" -s base cn pwdHistory
expect_no_output '^pwdHistory:'
expect_fail ldappasswd -x -H "${URI}" -D "cn=readonly,${TEST_SUFFIX}" -w "${TEST_PASSWORD}" -s "${NEW_PASSWORD}" "${USER_DN}"
fi
expect_fail ldappasswd -x -H "${URI}" -D "${APP_ADMIN_DN}" -w "${NEW_PASSWORD}" -a "${NEW_PASSWORD}" -s short
expect_output 'quality|too short|Constraint violation'
ok 'policy-enabled LDAP Modify hashes passwords and keeps history private'
ok 'policy-enabled LDAP Modify hashes passwords and honors history visibility settings'
fi
# Write-only password access must not even permit comparison of stored hashes.
+4
View File
@@ -39,6 +39,10 @@ ok "authenticated users can read allowed attributes"
expect_rc 0 bind_search "${URI}" "${CONFIG_ADMIN_DN}" -b "cn=config" -s base dn
ok "config admin can read cn=config"
expect_rc 0 bind_search "${URI}" "${CONFIG_ADMIN_DN}" -b "cn=config" '(olcDatabase=monitor)' olcMonitoring
expect_output '^olcMonitoring: FALSE$'
ok "monitoring is disabled by default"
expect_rc 0 bind_search "${URI}" "${CONFIG_ADMIN_DN}" -b "cn=schema,cn=config" -s one cn
for schema in ${TEST_SCHEMAS}; do
expect_output "^cn: \{[0-9]+\}${schema%.ldif}$"
+3
View File
@@ -49,6 +49,9 @@ ok "anonymous users cannot read the database"
for node in "${NODES[@]}"; do
uri="$(ldaps_uri "${node}")"
expect_rc 0 bind_search "${uri}" "${CONFIG_ADMIN_DN}" -b "cn=config" '(olcDatabase=monitor)' olcMonitoring
expect_output '^olcMonitoring: TRUE$'
expect_rc 0 bind_search "${uri}" "${CONFIG_ADMIN_DN}" -b "cn=module{0},cn=config" -s base olcModuleLoad
for module in ${TEST_MODULES}; do
expect_output "^olcModuleLoad: \{[0-9]+\}${module}$"
+4 -2
View File
@@ -34,18 +34,20 @@ OPENLDAP_BOOTSTRAP_DATA_TREE_SERVICES_DN="ou=services,${OPENLDAP_BOOTSTRAP_SUFFI
OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT=false
OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_DN="cn=management,${OPENLDAP_BOOTSTRAP_DATA_TREE_SERVICES_DN}"
OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_PASSWORD_HASHED=""
OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_HIDDEN_ATTRIBUTES="userPassword,shadowLastChange"
OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_HIDDEN_PPOLICY_ATTRIBUTES="pwdHistory"
OPENLDAP_BOOTSTRAP_DATA_READONLY=false
OPENLDAP_BOOTSTRAP_DATA_READONLY_DN="cn=readonly,${OPENLDAP_BOOTSTRAP_SUFFIX}"
OPENLDAP_BOOTSTRAP_DATA_READONLY_PASSWORD_HASHED=""
OPENLDAP_BOOTSTRAP_MONITOR_ENABLED=false
OPENLDAP_BOOTSTRAP_MONITOR=false
OPENLDAP_BOOTSTRAP_MONITOR_READONLY=false
OPENLDAP_BOOTSTRAP_MONITOR_READONLY_DN="cn=readonly-monitor,${OPENLDAP_BOOTSTRAP_SUFFIX}"
OPENLDAP_BOOTSTRAP_MONITOR_READONLY_PASSWORD_HASHED=""
OPENLDAP_BOOTSTRAP_AUDITLOG_ENABLED=false
OPENLDAP_BOOTSTRAP_AUDITLOG=false
OPENLDAP_BOOTSTRAP_AUDITLOG_FILE="/var/log/openldap/audit.ldif"
OPENLDAP_BOOTSTRAP_TLS=false
@@ -4,37 +4,37 @@ This directory contains LDAP Data Interchange Format (LDIF) files used to bootst
Files with a `.template` extension use `${VARIABLE}` placeholders that are substituted with environment variables at runtime. Static `.ldif` files are applied as-is.
Files are processed in lexicographic order within each directory (numerical prefixes control ordering).
Rendered LDIF files are assembled in version-sort order (`sort -V`); numerical prefixes control ordering.
## Structure
```
ldif/
├── config/ # OpenLDAP configuration database (cn=config)
│ ├── base/ # Core configuration (always applied)
│ ├── tls/ # TLS settings (OPENLDAP_BOOTSTRAP_TLS=true)
│ ├── tls-required/ # Enforce TLS (OPENLDAP_BOOTSTRAP_TLS_REQUIRED=true)
│ ├── database-readonly/ # Read-only data DB user (OPENLDAP_BOOTSTRAP_DATA_READONLY=true)
│ ├── data-management/ # Scoped application account ACLs (OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT=true)
│ ├── monitor-readonly/ # Read-only monitor user (OPENLDAP_BOOTSTRAP_MONITOR_READONLY=true)
│ ├── auditlog/ # Audit file (OPENLDAP_BOOTSTRAP_AUDITLOG_ENABLED=true)
│ ├── ppolicy/ # Password policy overlay (OPENLDAP_BOOTSTRAP_PPOLICY=true)
│ ├── ppolicy-readonly/ # Hide history from the data read-only account (PPOLICY and DATA_READONLY enabled)
│ ├── refint/ # Referential integrity overlay (OPENLDAP_BOOTSTRAP_REFINT=true)
│ ├── unique/ # Unique attributes overlay (OPENLDAP_BOOTSTRAP_UNIQUE=true)
│ ├── memberof/ # Memberof overlay (OPENLDAP_BOOTSTRAP_MEMBEROF=true)
│ ├── replication/ # Replication (OPENLDAP_BOOTSTRAP_REPLICATION=true)
│ └── custom/ # User-provided config LDIF files
│ ├── base/ # Core configuration (always applied)
│ ├── tls/ # TLS settings (OPENLDAP_BOOTSTRAP_TLS=true)
│ ├── tls-required/ # Enforce TLS (OPENLDAP_BOOTSTRAP_TLS_REQUIRED=true)
│ ├── database-readonly/ # Read-only data DB user (OPENLDAP_BOOTSTRAP_DATA_READONLY=true)
│ ├── database-management/ # Scoped application account ACLs (OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT=true)
│ ├── monitor-readonly/ # Read-only monitor user (OPENLDAP_BOOTSTRAP_MONITOR_READONLY=true)
│ ├── auditlog/ # Audit file (OPENLDAP_BOOTSTRAP_AUDITLOG=true)
│ ├── ppolicy/ # Password policy overlay (OPENLDAP_BOOTSTRAP_PPOLICY=true)
│ ├── ppolicy-readonly/ # Hide history from the data read-only account (PPOLICY and DATA_READONLY enabled)
│ ├── refint/ # Referential integrity overlay (OPENLDAP_BOOTSTRAP_REFINT=true)
│ ├── unique/ # Unique attributes overlay (OPENLDAP_BOOTSTRAP_UNIQUE=true)
│ ├── memberof/ # Memberof overlay (OPENLDAP_BOOTSTRAP_MEMBEROF=true)
│ ├── replication/ # Replication (OPENLDAP_BOOTSTRAP_REPLICATION=true)
│ └── custom/ # User-provided config LDIF files
└── data/ # LDAP directory data (populated on first bootstrap)
├── base/ # Root organization entry (always applied)
├── data-tree/ # Users, groups and services OUs (OPENLDAP_BOOTSTRAP_DATA_TREE=true)
├── data-management/ # Application account (OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT=true)
├── database-readonly/ # Read-only account entry (OPENLDAP_BOOTSTRAP_DATA_READONLY=true)
├── monitor-readonly/ # Read-only monitor account (OPENLDAP_BOOTSTRAP_MONITOR_READONLY=true)
├── ppolicy/ # Password policy group and default policy (OPENLDAP_BOOTSTRAP_PPOLICY=true)
├── refint-nothing/ # Passwordless replacement role (REFINT enabled and NOTHING_DN set)
├── replication/ # Replication service accounts (OPENLDAP_BOOTSTRAP_REPLICATION=true)
└── custom/ # User-provided data LDIF files
├── base/ # Root organization entry (always applied)
├── data-tree/ # Users, groups and services OUs (OPENLDAP_BOOTSTRAP_DATA_TREE=true)
├── database-management/ # Application account (OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT=true)
├── database-readonly/ # Read-only account entry (OPENLDAP_BOOTSTRAP_DATA_READONLY=true)
├── monitor-readonly/ # Read-only monitor account (OPENLDAP_BOOTSTRAP_MONITOR_READONLY=true)
├── ppolicy/ # Password policy group and default policy (OPENLDAP_BOOTSTRAP_PPOLICY=true)
├── refint-nothing/ # Passwordless replacement role (REFINT enabled and NOTHING_DN set)
├── replication/ # Replication service accounts (OPENLDAP_BOOTSTRAP_REPLICATION=true)
└── custom/ # User-provided data LDIF files
```
## Numbered prefix grouping behavior
@@ -79,22 +79,27 @@ Core OpenLDAP configuration applied on every bootstrap. Files are numbered to co
ACL files ending in `-close.ldif` append a final `access to * by * none` rule to deny any unmatched access, ensuring a safe default.
## data/data-tree and data/data-management
## data/data-tree and data/database-management
`OPENLDAP_BOOTSTRAP_DATA_TREE=true` creates `ou=users`, `ou=groups` and `ou=services`
below the suffix. `OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT=true` creates one account,
by default `cn=management,ou=services,<suffix>`, to read users/groups and reset user
passwords. It cannot edit profiles or groups or read password hashes/history.
passwords. By default, 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.
`80-database-management-account.ldif`. Do not duplicate automatically created OUs.
## config/data-management
## config/database-management
`63-data-management-acl.ldif.template` limits password writes to the users branch.
`OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_HIDDEN_ATTRIBUTES` lists comma-separated
attributes hidden from the management account (default `userPassword,shadowLastChange`).
When ppolicy is enabled, `OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_HIDDEN_PPOLICY_ATTRIBUTES`
is added (default `pwdHistory`). The combined list must not be empty.
`63-database-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.
@@ -155,7 +160,7 @@ docker run --name openldap \
osixia/openldap:2.6.15-beta
```
Put `.ldif` files in these directories, or use `.ldif.template` files with `${VARIABLE}` placeholders to substitute environment variables. Leave the mounts writable so bootstrap can convert templates into `.ldif` files.
Put `.ldif` files in these directories, or use `.ldif.template` files with `${VARIABLE}` placeholders to substitute environment variables. The mounts may be read-only: bootstrap renders templates in a temporary writable copy.
There are three ways to customize the bootstrap:
@@ -1,4 +1,4 @@
dn: olcDatabase=monitor,cn=config
objectClass: olcDatabaseConfig
olcDatabase: monitor
olcMonitoring: ${OPENLDAP_BOOTSTRAP_MONITOR_ENABLED}
olcMonitoring: ${OPENLDAP_BOOTSTRAP_MONITOR}
@@ -33,7 +33,7 @@ if [ "${TYPE}" = "env" ]; then
[unique]="${OPENLDAP_BOOTSTRAP_UNIQUE}"
[refint]="${OPENLDAP_BOOTSTRAP_REFINT}"
[memberof]="${OPENLDAP_BOOTSTRAP_MEMBEROF}"
[auditlog]="${OPENLDAP_BOOTSTRAP_AUDITLOG_ENABLED}"
[auditlog]="${OPENLDAP_BOOTSTRAP_AUDITLOG}"
[syncprov]="${OPENLDAP_BOOTSTRAP_REPLICATION}"
)
for module in ppolicy unique refint memberof auditlog syncprov; do
@@ -57,7 +57,7 @@ if [ "${TYPE}" = "env" ]; then
OPENLDAP_BOOTSTRAP_SCHEMAS_LDIF+="include: file://${OPENLDAP_SCHEMAS_DIR}/${schema}"$'\n'
done
OPENLDAP_BOOTSTRAP_MONITOR_ENABLED=${OPENLDAP_BOOTSTRAP_MONITOR_ENABLED^^}
OPENLDAP_BOOTSTRAP_MONITOR=${OPENLDAP_BOOTSTRAP_MONITOR^^}
OPENLDAP_BOOTSTRAP_DATA_LAST_BIND=${OPENLDAP_BOOTSTRAP_DATA_LAST_BIND^^}
OPENLDAP_BOOTSTRAP_ORGANIZATION_DC=$(get_dn_attr dc "${OPENLDAP_BOOTSTRAP_SUFFIX}")
@@ -66,7 +66,7 @@ if [ "${TYPE}" = "env" ]; then
container log fatal "OPENLDAP_BOOTSTRAP_SUFFIX must start with dc= (got ${OPENLDAP_BOOTSTRAP_SUFFIX})"
fi
prepare_env_file "${ENV_FILE}" OPENLDAP_GROUP_GID OPENLDAP_USER_UID OPENLDAP_BOOTSTRAP_CONFIG_ROOT_PASSWORD_HASHED OPENLDAP_BOOTSTRAP_DATA_ROOT_PASSWORD_HASHED OPENLDAP_BOOTSTRAP_MODULES_LDIF OPENLDAP_BOOTSTRAP_SCHEMAS_LDIF OPENLDAP_BOOTSTRAP_MONITOR_ENABLED OPENLDAP_BOOTSTRAP_DATA_LAST_BIND OPENLDAP_BOOTSTRAP_ORGANIZATION_DC
prepare_env_file "${ENV_FILE}" OPENLDAP_GROUP_GID OPENLDAP_USER_UID OPENLDAP_BOOTSTRAP_CONFIG_ROOT_PASSWORD_HASHED OPENLDAP_BOOTSTRAP_DATA_ROOT_PASSWORD_HASHED OPENLDAP_BOOTSTRAP_MODULES_LDIF OPENLDAP_BOOTSTRAP_SCHEMAS_LDIF OPENLDAP_BOOTSTRAP_MONITOR OPENLDAP_BOOTSTRAP_DATA_LAST_BIND OPENLDAP_BOOTSTRAP_ORGANIZATION_DC
elif [ "${TYPE}" = "ldif" ]; then
SOURCE_DIR="$2"
@@ -26,9 +26,12 @@ if [ "${OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT}" = "true" ]; then
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"
if [ "${OPENLDAP_BOOTSTRAP_PPOLICY}" = "true" ] && [ -n "${OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_HIDDEN_PPOLICY_ATTRIBUTES}" ]; then
OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_HIDDEN_ATTRIBUTES+="${OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_HIDDEN_ATTRIBUTES:+,}${OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_HIDDEN_PPOLICY_ATTRIBUTES}"
fi
if [ -z "${OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_HIDDEN_ATTRIBUTES}" ]; then
container log fatal "Management hidden attributes must not be empty"
fi
prepare_env_file "${ENV_FILE}" OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_CN OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_PASSWORD_HASHED OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT_HIDDEN_ATTRIBUTES
@@ -37,7 +40,7 @@ if [ "${OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT}" = "true" ]; then
SOURCE_DIR="$2"
OUTPUT_DIR="$3"
prepare_ldif_files "${SOURCE_DIR}/data-management" "${OUTPUT_DIR}"
prepare_ldif_files "${SOURCE_DIR}/database-management" "${OUTPUT_DIR}"
fi
fi
@@ -6,7 +6,7 @@ set -euo pipefail
# print commands and their arguments as they are executed
container log level eq trace && set -x
if [ "${OPENLDAP_BOOTSTRAP_AUDITLOG_ENABLED}" = "true" ]; then
if [ "${OPENLDAP_BOOTSTRAP_AUDITLOG}" = "true" ]; then
# shellcheck disable=SC1091
source "/container/services/openldap-bootstrap/assets/scripts/helpers/common.sh"
@@ -16,9 +16,9 @@ Scripts are executed in numeric order. Most are feature-gated by an environment
| `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 |
| `25-database-management.sh` | `OPENLDAP_BOOTSTRAP_DATA_MANAGEMENT=true` | Creates one account below the services branch, reading users/groups and resetting user passwords with configurable hidden attributes and no 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 |
| `35-auditlog.sh` | `OPENLDAP_BOOTSTRAP_AUDITLOG=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 |
| `45-refint-nothing.sh` | `OPENLDAP_BOOTSTRAP_REFINT=true` and `OPENLDAP_BOOTSTRAP_REFINT_NOTHING_DN` non-empty | Creates the passwordless replacement role |
| `50-ppolicy.sh` | `OPENLDAP_BOOTSTRAP_PPOLICY=true` | Configures password policy, hides history from the data read-only account while preserving normal ACL evaluation, and validates inactivity and progressive lockout prerequisites |