mirror of
https://github.com/osixia/container-openldap.git
synced 2026-11-06 20:48:18 -04:00
feat(bootstrap): expose global request limits
This commit is contained in:
@@ -6,6 +6,8 @@ Project-specific changes only. See upstream [OpenLDAP release notes](https://www
|
||||
|
||||
### Added
|
||||
|
||||
- Expose bootstrap settings for pending request limits, incoming LDAP message sizes
|
||||
and blocked network write timeouts, preserving OpenLDAP defaults.
|
||||
- Add `OPENLDAP_BOOTSTRAP_PPOLICY_DEFAULT_MIN_DELAY` and `OPENLDAP_BOOTSTRAP_PPOLICY_DEFAULT_MAX_DELAY`
|
||||
for progressive per-account lockouts after failed authentications (disabled by default).
|
||||
- Add `openldap-ctl password hash --file` and `--stdin` to hash secrets without
|
||||
|
||||
@@ -352,6 +352,11 @@ docker run --rm --user $UID -v $(pwd)/schema:/data \
|
||||
| `OPENLDAP_BOOTSTRAP_GLOBAL_SIZE_LIMIT` | Global LDAP size limit | `500` |
|
||||
| `OPENLDAP_BOOTSTRAP_GLOBAL_TIME_LIMIT` | Global LDAP time limit | `120` |
|
||||
| `OPENLDAP_BOOTSTRAP_GLOBAL_IDLE_TIMEOUT` | Global LDAP idle timeout | `3600` |
|
||||
| `OPENLDAP_BOOTSTRAP_GLOBAL_WRITE_TIMEOUT` | Timeout for blocked network writes, in seconds (`0`: disabled) | `0` |
|
||||
| `OPENLDAP_BOOTSTRAP_GLOBAL_CONN_MAX_PENDING` | Maximum pending requests per anonymous connection | `100` |
|
||||
| `OPENLDAP_BOOTSTRAP_GLOBAL_CONN_MAX_PENDING_AUTH` | Maximum pending requests per authenticated connection | `1000` |
|
||||
| `OPENLDAP_BOOTSTRAP_GLOBAL_SOCKBUF_MAX_INCOMING` | Maximum incoming LDAP message size for anonymous connections, in bytes | `262143` |
|
||||
| `OPENLDAP_BOOTSTRAP_GLOBAL_SOCKBUF_MAX_INCOMING_AUTH` | Maximum incoming LDAP message size for authenticated connections, in bytes | `4194303` |
|
||||
| `OPENLDAP_BOOTSTRAP_FRONTEND_PASSWORD_HASH` | Password hash scheme configured in `olcPasswordHash` | `{ARGON2}` |
|
||||
| `OPENLDAP_BOOTSTRAP_CONFIG_ROOT_DN` | Root DN for `cn=config` | `cn=admin,cn=config` |
|
||||
| `OPENLDAP_BOOTSTRAP_CONFIG_ROOT_PASSWORD_HASHED` | Hashed password for `cn=config`; generated if empty | `` |
|
||||
|
||||
@@ -21,6 +21,8 @@ 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;
|
||||
- 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
|
||||
and temporary-copy cleanup on success and failure;
|
||||
- TLS certificate renewals: restart slapd on changed contents, ignore identical
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
|
||||
"dagger.io/dagger"
|
||||
)
|
||||
|
||||
// GlobalLimits checks native defaults, custom limits and request size enforcement.
|
||||
func GlobalLimits(ctx context.Context, client *dagger.Client, image *dagger.Container) error {
|
||||
passwords, err := rootPasswords(ctx, image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, mode := range []string{"default", "custom"} {
|
||||
settings := env{}
|
||||
if mode == "custom" {
|
||||
settings = env{
|
||||
"OPENLDAP_BOOTSTRAP_GLOBAL_WRITE_TIMEOUT": "5",
|
||||
"OPENLDAP_BOOTSTRAP_GLOBAL_CONN_MAX_PENDING": "10",
|
||||
"OPENLDAP_BOOTSTRAP_GLOBAL_CONN_MAX_PENDING_AUTH": "20",
|
||||
"OPENLDAP_BOOTSTRAP_GLOBAL_SOCKBUF_MAX_INCOMING": "1024",
|
||||
"OPENLDAP_BOOTSTRAP_GLOBAL_SOCKBUF_MAX_INCOMING_AUTH": "2048",
|
||||
}
|
||||
}
|
||||
if err := func() error {
|
||||
node := "limits-" + mode
|
||||
service, err := startNode(ctx, image, node, merge(passwords, settings), nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stopNodes(ctx, []*dagger.Service{service})
|
||||
return check(ctx, image, nil, env{
|
||||
"TEST_NODES": node,
|
||||
"TEST_LIMITS_MODE": mode,
|
||||
}, limitsCheck)
|
||||
}(); err != nil {
|
||||
return fmt.Errorf("global limits %s: %w", mode, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//go:embed testdata/limits.sh
|
||||
var limitsCheck string
|
||||
@@ -19,6 +19,7 @@ type scenario struct {
|
||||
|
||||
var scenarios = []scenario{
|
||||
{"default bootstrap", Default},
|
||||
{"global request limits", GlobalLimits},
|
||||
{"read-only bootstrap templates", ReadOnlyTemplates},
|
||||
{"alternative schemas", AlternativeSchemas},
|
||||
{"TLS certificate reload", TLSReload},
|
||||
|
||||
Vendored
+60
@@ -0,0 +1,60 @@
|
||||
#!/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
|
||||
|
||||
URI="ldap://${TEST_NODES}:3890"
|
||||
|
||||
eventually has_entry "${URI}" "${TEST_SUFFIX}"
|
||||
expect_rc 0 bind_search "${URI}" "${CONFIG_ADMIN_DN}" -b cn=config -s base \
|
||||
olcWriteTimeout olcConnMaxPending olcConnMaxPendingAuth \
|
||||
olcSockbufMaxIncoming olcSockbufMaxIncomingAuth
|
||||
|
||||
if [ "${TEST_LIMITS_MODE}" = default ]; then
|
||||
expect_output '^olcWriteTimeout: 0$'
|
||||
expect_output '^olcConnMaxPending: 100$'
|
||||
expect_output '^olcConnMaxPendingAuth: 1000$'
|
||||
expect_output '^olcSockbufMaxIncoming: 262143$'
|
||||
expect_output '^olcSockbufMaxIncomingAuth: 4194303$'
|
||||
else
|
||||
expect_output '^olcWriteTimeout: 5$'
|
||||
expect_output '^olcConnMaxPending: 10$'
|
||||
expect_output '^olcConnMaxPendingAuth: 20$'
|
||||
expect_output '^olcSockbufMaxIncoming: 1024$'
|
||||
expect_output '^olcSockbufMaxIncomingAuth: 2048$'
|
||||
fi
|
||||
ok "global limits (${TEST_LIMITS_MODE}) are loaded in cn=config"
|
||||
|
||||
# These filters match the root DSE while making each incoming request large enough
|
||||
# to distinguish the anonymous and authenticated message size limits.
|
||||
MEDIUM=$(printf '%1400s' '' | tr ' ' x)
|
||||
LARGE=$(printf '%2800s' '' | tr ' ' x)
|
||||
MEDIUM_FILTER="(|(objectClass=*)(description=${MEDIUM}))"
|
||||
LARGE_FILTER="(|(objectClass=*)(description=${LARGE}))"
|
||||
|
||||
if [ "${TEST_LIMITS_MODE}" = default ]; then
|
||||
expect_rc 0 ldapsearch -LLL -x -H "${URI}" -b '' -s base "${LARGE_FILTER}" namingContexts
|
||||
expect_output '^namingContexts:'
|
||||
expect_rc 0 bind_search "${URI}" "${ADMIN_DN}" -b '' -s base "${LARGE_FILTER}" namingContexts
|
||||
expect_output '^namingContexts:'
|
||||
ok 'native message size defaults allow ordinary LDAP requests'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
expect_rc 0 ldapsearch -LLL -x -H "${URI}" -b '' -s base '(objectClass=*)' namingContexts
|
||||
expect_output '^namingContexts:'
|
||||
expect_fail ldapsearch -LLL -x -H "${URI}" -b '' -s base "${MEDIUM_FILTER}" namingContexts
|
||||
expect_output "Can't contact LDAP server"
|
||||
expect_rc 0 bind_search "${URI}" "${ADMIN_DN}" -b '' -s base "${MEDIUM_FILTER}" namingContexts
|
||||
expect_output '^namingContexts:'
|
||||
expect_fail bind_search "${URI}" "${ADMIN_DN}" -b '' -s base "${LARGE_FILTER}" namingContexts
|
||||
expect_output "Can't contact LDAP server"
|
||||
expect_rc 0 bind_search "${URI}" "${ADMIN_DN}" -b "${TEST_SUFFIX}" -s base dn
|
||||
expect_output '^dn:'
|
||||
ok 'oversized messages close the offending connection and preserve normal access'
|
||||
@@ -7,6 +7,11 @@ OPENLDAP_BOOTSTRAP_SCHEMAS="core.ldif cosine.ldif inetorgperson.ldif rfc2307bis.
|
||||
OPENLDAP_BOOTSTRAP_GLOBAL_SIZE_LIMIT=500
|
||||
OPENLDAP_BOOTSTRAP_GLOBAL_TIME_LIMIT=120
|
||||
OPENLDAP_BOOTSTRAP_GLOBAL_IDLE_TIMEOUT=3600
|
||||
OPENLDAP_BOOTSTRAP_GLOBAL_WRITE_TIMEOUT=0
|
||||
OPENLDAP_BOOTSTRAP_GLOBAL_CONN_MAX_PENDING=100
|
||||
OPENLDAP_BOOTSTRAP_GLOBAL_CONN_MAX_PENDING_AUTH=1000
|
||||
OPENLDAP_BOOTSTRAP_GLOBAL_SOCKBUF_MAX_INCOMING=262143
|
||||
OPENLDAP_BOOTSTRAP_GLOBAL_SOCKBUF_MAX_INCOMING_AUTH=4194303
|
||||
|
||||
OPENLDAP_BOOTSTRAP_FRONTEND_PASSWORD_HASH="{ARGON2}"
|
||||
|
||||
|
||||
@@ -59,7 +59,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) |
|
||||
| `00-global.ldif.template` | Global search limits, idle/write timeouts, pending requests and incoming message sizes |
|
||||
| `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 |
|
||||
|
||||
@@ -4,3 +4,8 @@ cn: config
|
||||
olcSizeLimit: ${OPENLDAP_BOOTSTRAP_GLOBAL_SIZE_LIMIT}
|
||||
olcTimeLimit: ${OPENLDAP_BOOTSTRAP_GLOBAL_TIME_LIMIT}
|
||||
olcIdleTimeout: ${OPENLDAP_BOOTSTRAP_GLOBAL_IDLE_TIMEOUT}
|
||||
olcWriteTimeout: ${OPENLDAP_BOOTSTRAP_GLOBAL_WRITE_TIMEOUT}
|
||||
olcConnMaxPending: ${OPENLDAP_BOOTSTRAP_GLOBAL_CONN_MAX_PENDING}
|
||||
olcConnMaxPendingAuth: ${OPENLDAP_BOOTSTRAP_GLOBAL_CONN_MAX_PENDING_AUTH}
|
||||
olcSockbufMaxIncoming: ${OPENLDAP_BOOTSTRAP_GLOBAL_SOCKBUF_MAX_INCOMING}
|
||||
olcSockbufMaxIncomingAuth: ${OPENLDAP_BOOTSTRAP_GLOBAL_SOCKBUF_MAX_INCOMING_AUTH}
|
||||
|
||||
@@ -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: root passwords, automatic or explicit modules, schemas and base LDIF files |
|
||||
| `00-base.sh` | always | Core bootstrap: root passwords, modules, schemas and base LDIF files, including global request limits |
|
||||
| `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