Add optional directory tree and scoped management account

This commit is contained in:
Bertrand Gouny
2026-10-26 21:54:40 +01:00
parent 18917aaf73
commit b7c12e1c78
15 changed files with 459 additions and 0 deletions
+5
View File
@@ -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 |
+5
View File
@@ -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
+7
View File
@@ -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 | `` |
+4
View File
@@ -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
+110
View File
@@ -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
+1
View File
@@ -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},
+172
View File
@@ -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 <<LDIF
dn: ${dn}
objectClass: inetOrgPerson
uid: ${uid#uid=}
cn: Test User
sn: User
displayName: Test User
mail: ${uid#uid=}@example.org
userPassword: ${TEST_PASSWORD}
LDIF
done
expect_rc 0 bind_modify "${URI}" "${ADMIN_DN}" -a <<LDIF
dn: ${GROUP_DN}
objectClass: groupOfNames
cn: cloud-admins
member: ${APP_ADMIN_DN}
dn: ${SERVICE_DN}
objectClass: organizationalRole
objectClass: simpleSecurityObject
cn: other-service
userPassword: ${TEST_PASSWORD}
LDIF
# Searches use the same user/group scope and attributes as Authelia.
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$'
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
expect_output '^cn: cloud-admins$'
expect_output "^member: ${APP_ADMIN_DN}$"
ok 'management can read user profiles and group memberships'
# An extended operation hashes server-side, including when ppolicy is disabled.
# Application administrators are ordinary users for password-reset permissions.
for dn in "${USER_DN}" "${APP_ADMIN_DN}"; do
expect_rc 0 ldappasswd -x -H "${URI}" -D "${TEST_MANAGEMENT_DN}" -w "${TEST_PASSWORD}" -s "${NEW_PASSWORD}" "${dn}"
expect_rc 0 ldapwhoami -x -H "${URI}" -D "${dn}" -w "${NEW_PASSWORD}"
expect_rc 49 ldapwhoami -x -H "${URI}" -D "${dn}" -w "${TEST_PASSWORD}"
expect_rc 0 bind_search "${URI}" "${ADMIN_DN}" -b "${dn}" -s base userPassword
expect_output '^userPassword:: '
done
ok 'management resets ordinary and application-admin passwords without the old password'
if [ "${TEST_MANAGEMENT_MODE}" = "policy" ]; 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}
changetype: modify
replace: userPassword
userPassword: ${NEXT_PASSWORD}
LDIF
expect_rc 0 ldapwhoami -x -H "${URI}" -D "${USER_DN}" -w "${NEXT_PASSWORD}"
expect_rc 0 bind_search "${URI}" "${ADMIN_DN}" -b "${USER_DN}" -s base userPassword pwdHistory
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 '^pwdHistory:'
expect_fail ldappasswd -x -H "${URI}" -D "cn=readonly,${TEST_SUFFIX}" -w "${TEST_PASSWORD}" -s "${NEW_PASSWORD}" "${USER_DN}"
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'
fi
# Write-only password access must not even permit comparison of stored hashes.
expect_rc 50 ldapcompare -x -H "${URI}" -D "${TEST_MANAGEMENT_DN}" -w "${TEST_PASSWORD}" "${USER_DN}" "userPassword:${NEW_PASSWORD}"
expect_fail bind_modify "${URI}" "${TEST_MANAGEMENT_DN}" <<LDIF
dn: ${USER_DN}
changetype: modify
replace: mail
mail: takeover@example.org
LDIF
expect_fail bind_modify "${URI}" "${TEST_MANAGEMENT_DN}" <<LDIF
dn: ${GROUP_DN}
changetype: modify
add: member
member: ${USER_DN}
LDIF
expect_fail bind_modify "${URI}" "${TEST_MANAGEMENT_DN}" -a <<LDIF
dn: uid=forbidden,${TEST_USERS_DN}
objectClass: inetOrgPerson
uid: forbidden
cn: forbidden
sn: forbidden
LDIF
expect_fail bind_modify "${URI}" "${TEST_MANAGEMENT_DN}" -a <<LDIF
dn: cn=forbidden,${TEST_GROUPS_DN}
objectClass: groupOfNames
cn: forbidden
member: ${USER_DN}
LDIF
expect_fail ldapdelete -x -H "${URI}" -D "${TEST_MANAGEMENT_DN}" -w "${TEST_PASSWORD}" "${USER_DN}"
expect_fail ldapdelete -x -H "${URI}" -D "${TEST_MANAGEMENT_DN}" -w "${TEST_PASSWORD}" "${GROUP_DN}"
expect_fail ldapmodrdn -x -H "${URI}" -D "${TEST_MANAGEMENT_DN}" -w "${TEST_PASSWORD}" "${USER_DN}" uid=renamed
ok 'management cannot edit profiles, memberships, create, delete or rename entries'
for dn in "${SERVICE_DN}" "${TEST_MANAGEMENT_DN}" "${OUTSIDE_DN}"; do
expect_rc 32 bind_search "${URI}" "${TEST_MANAGEMENT_DN}" -b "${dn}" -s base '*' '+'
expect_fail ldappasswd -x -H "${URI}" -D "${TEST_MANAGEMENT_DN}" -w "${TEST_PASSWORD}" -s "${NEW_PASSWORD}" "${dn}"
expect_rc 0 ldapwhoami -x -H "${URI}" -D "${dn}" -w "${TEST_PASSWORD}"
done
expect_fail bind_modify "${URI}" "${TEST_MANAGEMENT_DN}" <<LDIF
dn: olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcRootPW
olcRootPW: ${NEW_PASSWORD}
LDIF
expect_fail ldappasswd -x -H "${URI}" -D "${TEST_MANAGEMENT_DN}" -w "${TEST_PASSWORD}" -s "${NEW_PASSWORD}" "${ADMIN_DN}"
expect_rc 0 ldapwhoami -x -H "${URI}" -D "${ADMIN_DN}" -w "${TEST_PASSWORD}"
ok 'service accounts, entries outside users/groups and LDAP configuration remain protected'
expect_rc 0 ldappasswd -x -H "${URI}" -D "${APP_ADMIN_DN}" -w "${NEW_PASSWORD}" -a "${NEW_PASSWORD}" -s "${NEXT_PASSWORD}"
expect_rc 0 ldapwhoami -x -H "${URI}" -D "${APP_ADMIN_DN}" -w "${NEXT_PASSWORD}"
ok 'normal self-service password changes remain available'
+9
View File
@@ -26,6 +26,15 @@ OPENLDAP_BOOTSTRAP_DATA_LAST_BIND_PRECISION=3600
OPENLDAP_BOOTSTRAP_DATA_ROOT_DN="cn=admin,${OPENLDAP_BOOTSTRAP_SUFFIX}"
OPENLDAP_BOOTSTRAP_DATA_ROOT_PASSWORD_HASHED=""
OPENLDAP_BOOTSTRAP_DATA_TREE=false
OPENLDAP_BOOTSTRAP_DATA_TREE_USERS_DN="ou=users,${OPENLDAP_BOOTSTRAP_SUFFIX}"
OPENLDAP_BOOTSTRAP_DATA_TREE_GROUPS_DN="ou=groups,${OPENLDAP_BOOTSTRAP_SUFFIX}"
OPENLDAP_BOOTSTRAP_DATA_TREE_SERVICES_DN="ou=services,${OPENLDAP_BOOTSTRAP_SUFFIX}"
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_READONLY=false
OPENLDAP_BOOTSTRAP_DATA_READONLY_DN="cn=readonly,${OPENLDAP_BOOTSTRAP_SUFFIX}"
OPENLDAP_BOOTSTRAP_DATA_READONLY_PASSWORD_HASHED=""
@@ -15,6 +15,7 @@ ldif/
│ ├── 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)
@@ -26,6 +27,8 @@ ldif/
│ └── 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)
@@ -76,6 +79,28 @@ 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
`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.
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
@@ -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
@@ -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}
@@ -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}
+36
View File
@@ -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
@@ -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
@@ -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 |