Files
Bertrand Gouny 02b5f0354d 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.
2026-10-27 11:10:13 +01:00

179 lines
6.3 KiB
Go

package tests
import (
"context"
_ "embed"
"fmt"
"strings"
"dagger.io/dagger"
)
// replicationPassword contains characters that must be quoted or escaped in olcSyncRepl,
// double quotes are rejected.
const replicationPassword = `Repl passw0rd 'single' \ $HOME`
const defaultSchemas = "core.ldif cosine.ldif inetorgperson.ldif rfc2307bis.ldif"
//go:embed testdata/templates.sh
var templatesCheck string
// ReadOnlyTemplates checks projected-volume sources and temporary-copy cleanup.
func ReadOnlyTemplates(ctx context.Context, client *dagger.Client, image *dagger.Container) error {
_, err := expect(ctx, image, true, nil, "container", "run", "--", "bash", "-c", templatesCheck)
return err
}
// allSchemas can be loaded together. Excluded:
// - nis.ldif and virtualMail.ldif conflict with rfc2307bis.ldif and postfix-book.ldif,
// they are tested by AlternativeSchemas;
// - msuser.ldif conflicts with inetorgperson.ldif and contains duplicated attribute types
// (OpenLDAP ships it "for informational purposes only").
var allSchemas = []string{
"core.ldif", "cosine.ldif", "inetorgperson.ldif", "rfc2307bis.ldif",
// OpenLDAP
"collective.ldif", "corba.ldif", "dsee.ldif", "duaconf.ldif", "dyngroup.ldif", "java.ldif",
"misc.ldif", "namedobject.ldif", "openldap.ldif", "pmi.ldif",
// osixia/openldap
"eduperson.ldif", "kerberos.ldif", "openssh-lpk.ldif", "postfix-book.ldif", "samba.ldif",
"sudo.ldif", "totp.ldif",
}
// allModules can be loaded together. Excluded: dyngroup.so conflicts with dynlist.so and
// mqtt.so conflicts with lastbind.so.
var allModules = []string{
"back_mdb.so", "argon2.so", "ppolicy.so", "unique.so", "refint.so", "memberof.so", "syncprov.so",
"accesslog.so", "auditlog.so", "autoca.so", "back_asyncmeta.so", "back_dnssrv.so", "back_ldap.so",
"back_meta.so", "back_null.so", "back_passwd.so", "back_relay.so", "back_sock.so", "back_sql.so",
"collect.so", "constraint.so", "dds.so", "deref.so", "dynlist.so", "homedir.so", "lastbind.so",
"lloadd.so", "nestgroup.so", "otp.so", "pcache.so", "pw-pbkdf2.so", "pw-sha2.so", "remoteauth.so",
"retcode.so", "rwm.so", "seqmod.so", "sssvlv.so", "translucent.so", "valsort.so",
}
// rootPasswords sets the config and database root passwords to the test password.
func rootPasswords(ctx context.Context, image *dagger.Container) (env, error) {
hash, err := hashPassword(ctx, image)
if err != nil {
return nil, err
}
return env{
"OPENLDAP_BOOTSTRAP_CONFIG_ROOT_PASSWORD_HASHED": hash,
"OPENLDAP_BOOTSTRAP_DATA_ROOT_PASSWORD_HASHED": hash,
}, nil
}
// Default starts the image with its default configuration.
func Default(ctx context.Context, client *dagger.Client, image *dagger.Container) error {
passwords, err := rootPasswords(ctx, image)
if err != nil {
return err
}
const node = "ldap"
service, err := startNode(ctx, image, node, passwords, nil, nil)
if err != nil {
return err
}
defer stopNodes(ctx, []*dagger.Service{service})
return check(ctx, image, nil, env{"TEST_NODES": node, "TEST_SCHEMAS": defaultSchemas}, defaultCheck)
}
// AlternativeSchemas bootstraps with the schemas that conflict with the default ones.
func AlternativeSchemas(ctx context.Context, client *dagger.Client, image *dagger.Container) error {
schemas := "core.ldif cosine.ldif inetorgperson.ldif nis.ldif virtualMail.ldif"
r, err := expect(ctx, withEnv(image, env{"OPENLDAP_BOOTSTRAP_SCHEMAS": schemas}), true,
[]string{"config file testing succeeded"}, "container", "--skip-process")
if err != nil {
return err
}
_, err = expect(ctx, r.container, true, []string{"cn: {3}nis", "cn: {4}virtualMail"},
"container", "run", "--", "slapcat", "-n", "0", "-a", "(objectClass=olcSchemaConfig)")
return err
}
// AllFeatures enables every bootstrap feature, all compatible modules and schemas, on a
// 3 nodes multi-provider cluster with TLS required.
func AllFeatures(ctx context.Context, client *dagger.Client, image *dagger.Container) error {
hash, err := hashPassword(ctx, image)
if err != nil {
return err
}
nodes := []string{"ldap1", "ldap2", "ldap3"}
certs := certificates(client, nodes)
hosts := make([]string, 0, len(nodes))
for _, n := range nodes {
hosts = append(hosts, fmt.Sprintf("ldap://%s:3890", n))
}
features := env{
"OPENLDAP_BOOTSTRAP_MODULES": strings.Join(allModules, " "),
"OPENLDAP_BOOTSTRAP_SCHEMAS": strings.Join(allSchemas, " "),
"OPENLDAP_BOOTSTRAP_CONFIG_ROOT_PASSWORD_HASHED": hash,
"OPENLDAP_BOOTSTRAP_DATA_ROOT_PASSWORD_HASHED": hash,
"OPENLDAP_BOOTSTRAP_DATA_READONLY": "true",
"OPENLDAP_BOOTSTRAP_DATA_READONLY_PASSWORD_HASHED": hash,
"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": "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, " "),
"OPENLDAP_BOOTSTRAP_REPLICATION_DATA_READONLY_PASSWORD": replicationPassword,
}
if _, err := expect(ctx, withEnv(image, merge(features, env{"OPENLDAP_BOOTSTRAP_REPLICATION_DATA_READONLY_PASSWORD": `pass"word`})), false,
[]string{"must not contain double quotes"}, "container", "--skip-process"); err != nil {
return fmt.Errorf("replication password with double quotes: %w", err)
}
services := make([]*dagger.Service, 0, len(nodes))
defer func() { stopNodes(ctx, services) }()
for _, n := range nodes {
urls := env{"OPENLDAP_URLS": fmt.Sprintf("ldap://%s:3890 ldaps://%s:6360 ldapi:///", n, n)}
service, err := startNode(ctx, image, n, merge(features, urls), certs, nil)
if err != nil {
return err
}
services = append(services, service)
}
return check(ctx, image, certs, env{
"TEST_NODES": strings.Join(nodes, " "),
"TEST_MODULES": strings.Join(allModules, " "),
"TEST_SCHEMAS": strings.Join(allSchemas, " "),
"TEST_REPLICATION_PASSWORD": replicationPassword,
}, fullCheck)
}
//go:embed testdata/default.sh
var defaultCheck string
//go:embed testdata/full.sh
var fullCheck string