mirror of
https://github.com/osixia/container-openldap.git
synced 2026-11-06 12:40:36 -04:00
feat(bootstrap): load enabled feature modules automatically
This commit is contained in:
+3
-1
@@ -8,8 +8,10 @@ Project-specific changes only. See upstream [OpenLDAP release notes](https://www
|
||||
|
||||
- Add optional `openldap-tls-reload` service to restart slapd after TLS file
|
||||
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`
|
||||
and `OPENLDAP_BOOTSTRAP_AUDITLOG_FILE`; load `auditlog.so` automatically when enabled.
|
||||
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).
|
||||
- Add `OPENLDAP_BOOTSTRAP_REFINT_NOTHING_DN` to configure `olcRefintNothing` and
|
||||
|
||||
@@ -337,7 +337,7 @@ docker run --rm --user $UID -v $(pwd)/schema:/data \
|
||||
| ----------------------------------------------------- | --------------------------------------------------------------------------------- | ----------------------------------------------------------- |
|
||||
| `OPENLDAP_BOOTSTRAP_ORGANIZATION` | Organization name used in bootstrap data | `Example Org` |
|
||||
| `OPENLDAP_BOOTSTRAP_SUFFIX` | Base LDAP suffix; must start with `dc=` | `dc=example,dc=org` |
|
||||
| `OPENLDAP_BOOTSTRAP_MODULES` | OpenLDAP modules loaded during bootstrap | `back_mdb.so argon2.so ppolicy.so unique.so refint.so memberof.so syncprov.so` |
|
||||
| `OPENLDAP_BOOTSTRAP_MODULES` | `auto`: base and enabled feature modules; an explicit list sets the exact modules and order | `auto` |
|
||||
| `OPENLDAP_BOOTSTRAP_SCHEMAS` | LDIF schemas imported during bootstrap | `core.ldif cosine.ldif inetorgperson.ldif rfc2307bis.ldif` |
|
||||
| `OPENLDAP_BOOTSTRAP_GLOBAL_SIZE_LIMIT` | Global LDAP size limit | `500` |
|
||||
| `OPENLDAP_BOOTSTRAP_GLOBAL_TIME_LIMIT` | Global LDAP time limit | `120` |
|
||||
@@ -357,7 +357,7 @@ docker run --rm --user $UID -v $(pwd)/schema:/data \
|
||||
| `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; loads `auditlog.so` automatically | `false` |
|
||||
| `OPENLDAP_BOOTSTRAP_AUDITLOG_ENABLED` | 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` |
|
||||
|
||||
@@ -27,6 +27,7 @@ After the shared version check, `tests/` runs integration scenarios on each buil
|
||||
projected volumes and duplicate events, and preserve intentional service stops;
|
||||
run bootstrap, schema conversion and TLS renewals with `/tmp` mounted read-only;
|
||||
- alternative schemas (`nis`, `virtualMail`);
|
||||
- automatic modules: feature flags, replication, exact custom ordering and aliases;
|
||||
- auditlog: automatic module loading, writes, custom paths, rotation and restarts;
|
||||
- last successful binds: disabled by default, precision throttling, failed binds
|
||||
and compatibility with ppolicy;
|
||||
|
||||
@@ -26,9 +26,9 @@ func AuditLog(ctx context.Context, client *dagger.Client, image *dagger.Containe
|
||||
settings["OPENLDAP_BOOTSTRAP_AUDITLOG_FILE"] = file
|
||||
}
|
||||
if mode == "minimal" || mode == "explicit" {
|
||||
settings["OPENLDAP_BOOTSTRAP_MODULES"] = "back_mdb.so argon2.so"
|
||||
settings["OPENLDAP_BOOTSTRAP_MODULES"] = "back_mdb.so argon2.so auditlog.so"
|
||||
if mode == "explicit" {
|
||||
settings["OPENLDAP_BOOTSTRAP_MODULES"] += " /usr/lib/openldap/auditlog.so"
|
||||
settings["OPENLDAP_BOOTSTRAP_MODULES"] = "back_mdb.so argon2.so /usr/lib/openldap/auditlog.so"
|
||||
}
|
||||
}
|
||||
if err := check(ctx, withEnv(image, merge(passwords, settings)), nil, env{
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
|
||||
"dagger.io/dagger"
|
||||
)
|
||||
|
||||
// AutomaticModules checks automatic selection and exact explicit module ordering.
|
||||
func AutomaticModules(ctx context.Context, client *dagger.Client, image *dagger.Container) error {
|
||||
passwords, err := rootPasswords(ctx, image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
const baseModules = "back_mdb.so argon2.so"
|
||||
const explicitModules = "back_mdb.so pw-sha2.so argon2.so memberof refint.so unique /usr/lib/openldap/ppolicy.so auditlog"
|
||||
type moduleCase struct {
|
||||
name string
|
||||
settings env
|
||||
modules string
|
||||
fails bool
|
||||
}
|
||||
cases := []moduleCase{
|
||||
{"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",
|
||||
}, 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_PPOLICY": "true",
|
||||
"OPENLDAP_BOOTSTRAP_UNIQUE": "true",
|
||||
"OPENLDAP_BOOTSTRAP_REFINT": "true",
|
||||
"OPENLDAP_BOOTSTRAP_MEMBEROF": "true",
|
||||
"OPENLDAP_BOOTSTRAP_REPLICATION": "true",
|
||||
"OPENLDAP_BOOTSTRAP_REPLICATION_DATA_READONLY_PASSWORD": replicationPassword,
|
||||
}, baseModules + " ppolicy.so unique.so refint.so memberof.so auditlog.so syncprov.so", false},
|
||||
}
|
||||
for _, feature := range []struct{ flag, module string }{
|
||||
{"AUDITLOG_ENABLED", "auditlog"},
|
||||
{"PPOLICY", "ppolicy"},
|
||||
{"UNIQUE", "unique"},
|
||||
{"REFINT", "refint"},
|
||||
{"MEMBEROF", "memberof"},
|
||||
} {
|
||||
cases = append(cases, moduleCase{feature.module, env{"OPENLDAP_BOOTSTRAP_" + feature.flag: "true"}, baseModules + " " + feature.module + ".so", false})
|
||||
}
|
||||
for _, tc := range cases {
|
||||
if err := check(ctx, withEnv(image, merge(passwords, tc.settings)), nil, env{
|
||||
"TEST_MODULES": tc.modules,
|
||||
"TEST_BOOTSTRAP_FAILS": fmt.Sprint(tc.fails),
|
||||
}, modulesCheck); err != nil {
|
||||
return fmt.Errorf("modules %s: %w", tc.name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//go:embed testdata/modules.sh
|
||||
var modulesCheck string
|
||||
@@ -22,6 +22,7 @@ var scenarios = []scenario{
|
||||
{"read-only bootstrap templates", ReadOnlyTemplates},
|
||||
{"alternative schemas", AlternativeSchemas},
|
||||
{"TLS certificate reload", TLSReload},
|
||||
{"automatic module loading", AutomaticModules},
|
||||
{"audit logging", AuditLog},
|
||||
{"last successful binds", LastBind},
|
||||
{"refint empty-group placeholders", RefintNothing},
|
||||
|
||||
Vendored
+40
@@ -0,0 +1,40 @@
|
||||
#!/bin/bash
|
||||
|
||||
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
|
||||
|
||||
if [ "${OPENLDAP_BOOTSTRAP_REPLICATION:-false}" = "true" ]; then
|
||||
OPENLDAP_BOOTSTRAP_REPLICATION_HOSTS="ldap://$(hostname -f):3890 ldap://other:3890"
|
||||
export OPENLDAP_BOOTSTRAP_REPLICATION_HOSTS
|
||||
fi
|
||||
|
||||
if container --log-level debug --skip-process > /tmp/openldap-modules-bootstrap.log 2>&1; then
|
||||
[ "${TEST_BOOTSTRAP_FAILS}" = "false" ] || fail 'missing explicit module was automatically added'
|
||||
else
|
||||
if [ "${TEST_BOOTSTRAP_FAILS}" = "true" ]; then
|
||||
if ! grep -q 'str2ad(olcUniqueURI): attribute type undefined' /tmp/openldap-modules-bootstrap.log; then
|
||||
tail -60 /tmp/openldap-modules-bootstrap.log
|
||||
fail 'bootstrap did not fail for the missing unique module'
|
||||
fi
|
||||
ok 'explicit module lists do not add missing feature modules'
|
||||
exit 0
|
||||
fi
|
||||
tail -60 /tmp/openldap-modules-bootstrap.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
expect_rc 0 container run -- slapcat -n 0 -a '(objectClass=olcModuleList)' -o ldif_wrap=no
|
||||
ACTUAL_MODULES=$(sed -n 's/^olcModuleLoad: {[0-9]*}//p' "${OUTPUT_FILE}")
|
||||
read -ra EXPECTED_MODULES <<< "${TEST_MODULES}"
|
||||
EXPECTED_ORDER=$(printf '%s\n' "${EXPECTED_MODULES[@]}")
|
||||
if [ "${ACTUAL_MODULES}" != "${EXPECTED_ORDER}" ]; then
|
||||
cat "${OUTPUT_FILE}" >&2
|
||||
fail "expected modules in order: ${TEST_MODULES}"
|
||||
fi
|
||||
ok "loaded modules in order: ${TEST_MODULES}"
|
||||
@@ -1,7 +1,7 @@
|
||||
OPENLDAP_BOOTSTRAP_ORGANIZATION="Example Org"
|
||||
OPENLDAP_BOOTSTRAP_SUFFIX="dc=example,dc=org"
|
||||
|
||||
OPENLDAP_BOOTSTRAP_MODULES="back_mdb.so argon2.so ppolicy.so unique.so refint.so memberof.so syncprov.so"
|
||||
OPENLDAP_BOOTSTRAP_MODULES="auto"
|
||||
OPENLDAP_BOOTSTRAP_SCHEMAS="core.ldif cosine.ldif inetorgperson.ldif rfc2307bis.ldif"
|
||||
|
||||
OPENLDAP_BOOTSTRAP_GLOBAL_SIZE_LIMIT=500
|
||||
|
||||
@@ -60,7 +60,7 @@ Core OpenLDAP configuration applied on every bootstrap. Files are numbered to co
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `00-global.ldif.template` | Global limits (size, time, idle timeout) |
|
||||
| `10-module.ldif.template` | Module path and dynamic module list |
|
||||
| `10-module.ldif.template` | Module path and module list (`auto` or explicit order) |
|
||||
| `20-schema.ldif` | Schema configuration container |
|
||||
| `30-schema-import.ldif.template` | Additional schemas to load |
|
||||
| `40-frontend.ldif.template` | Frontend database (password hash algorithm) |
|
||||
|
||||
@@ -25,18 +25,28 @@ if [ "${TYPE}" = "env" ]; then
|
||||
ensure_hashed_password OPENLDAP_BOOTSTRAP_CONFIG_ROOT_PASSWORD_HASHED "config root"
|
||||
ensure_hashed_password OPENLDAP_BOOTSTRAP_DATA_ROOT_PASSWORD_HASHED "database root"
|
||||
|
||||
auditlog_module=false
|
||||
for module in ${OPENLDAP_BOOTSTRAP_MODULES}; do
|
||||
OPENLDAP_BOOTSTRAP_MODULES_LDIF+="olcModuleload: ${module}"$'\n'
|
||||
case "${module##*/}" in
|
||||
auditlog|auditlog.so|auditlog.la) auditlog_module=true ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ "${OPENLDAP_BOOTSTRAP_AUDITLOG_ENABLED}" = "true" ] && [ "${auditlog_module}" = "false" ]; then
|
||||
OPENLDAP_BOOTSTRAP_MODULES_LDIF+="olcModuleload: auditlog.so"$'\n'
|
||||
modules="${OPENLDAP_BOOTSTRAP_MODULES}"
|
||||
if [ "${modules}" = "auto" ]; then
|
||||
modules="back_mdb.so argon2.so"
|
||||
declare -A auto_modules=(
|
||||
[ppolicy]="${OPENLDAP_BOOTSTRAP_PPOLICY}"
|
||||
[unique]="${OPENLDAP_BOOTSTRAP_UNIQUE}"
|
||||
[refint]="${OPENLDAP_BOOTSTRAP_REFINT}"
|
||||
[memberof]="${OPENLDAP_BOOTSTRAP_MEMBEROF}"
|
||||
[auditlog]="${OPENLDAP_BOOTSTRAP_AUDITLOG_ENABLED}"
|
||||
[syncprov]="${OPENLDAP_BOOTSTRAP_REPLICATION}"
|
||||
)
|
||||
for module in ppolicy unique refint memberof auditlog syncprov; do
|
||||
if [ "${auto_modules[${module}]}" = "true" ]; then
|
||||
modules+=" ${module}.so"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
for module in ${modules}; do
|
||||
OPENLDAP_BOOTSTRAP_MODULES_LDIF+="olcModuleload: ${module}"$'\n'
|
||||
done
|
||||
|
||||
# schemas are included by path instead of by content: large schemas (e.g. msuser.ldif)
|
||||
# exceed the kernel size limit of a single environment variable and make every
|
||||
# following command fail with "Argument list too long"
|
||||
|
||||
@@ -11,7 +11,7 @@ Scripts are executed in numeric order. Most are feature-gated by an environment
|
||||
|
||||
| Script | Trigger | Description |
|
||||
|--------|---------|-------------|
|
||||
| `00-base.sh` | always | Core bootstrap: password hashing for root DNs, module and schema setup, last successful bind settings, base LDIF files |
|
||||
| `00-base.sh` | always | Core bootstrap: root passwords, automatic or explicit modules, schemas and base LDIF files |
|
||||
| `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 |
|
||||
|
||||
Reference in New Issue
Block a user