@leiter groups for role based permissions in nextcloud and co
- JavaScript 97.9%
- Dockerfile 2.1%
| lib | ||
| test | ||
| .dockerignore | ||
| .gitignore | ||
| changeConfig.js | ||
| CHANGELOG.md | ||
| ctldap.example.config | ||
| ctldap.js | ||
| docker-compose.yml | ||
| Dockerfile | ||
| LICENSE | ||
| OPTIMIZATION-PLAN.md | ||
| package.json | ||
| README.md | ||
| yarn.lock | ||
ctsldap — ChurchTools → LDAP bridge
ctsldap is a small Node.js service that presents the people and groups of a
ChurchTools instance over LDAP, so that LDAP-aware
applications can authenticate and resolve users and groups against ChurchTools.
In this deployment it exists for exactly one consumer: Nextcloud, which uses it as its user backend for single sign-on.
This repository is a fork of karl007/ctldap-ms
(itself a fork of churchtools/ctldap-ms),
packaged to run as a Docker container on the Nextcloud host.
Security note — read this first
Never expose port 1389 to the internet.
The upstream code depends on ldapjs 1.0.2, which has an unhandled exception in
its ASN.1 parser. Any malformed LDAP packet — exactly what internet-wide port
scanners send — crashes the process. The previous bare-metal deployment listened
on 0.0.0.0:1389 and was restarted 854 times by its supervisor as a direct
result.
The containerised setup in this repository therefore:
- publishes no ports at all (there is no
ports:key indocker-compose.yml), - joins the pre-existing external Docker network
caddy, - is reachable only from inside that network, as host
ctldapon port1389.
Nextcloud is configured to talk to ctldap:1389. Nothing outside the Docker
network can reach the LDAP listener. Keep it that way.
Also note that ctldap.config contains the ChurchTools API credentials and the
LDAP root password in cleartext. It is listed in .gitignore and must never
be committed, pasted into an issue, or baked into a container image.
Architecture
Nextcloud ──LDAP──▶ ctsldap ──HTTPS/REST──▶ ChurchTools
(container) (container) (external)
└──────── docker network "caddy" ────────┘
ctldap.js— the LDAP server and the ChurchTools API client.changeConfig.js— helper to add/update per-site sections in the config file.Dockerfile— two-stage build on node 22 LTS, pinned by digest so the base image cannot change under an unchanged tag. The old node 16 pin existed only for the nativebcrypt@3; since password hashing moved to the pure-JSbcryptjs, nothing in the tree needs a C++ toolchain. The test suite is verified on node 16, 18, 20, 22 and 24. To move the base image, resolve the new digest withdocker buildx imagetools inspect node:22-bookworm-slimand update it in bothDockerfileanddocker-compose.yml.docker-compose.yml— runs the image withread_only: true,no-new-privileges, atmpfsfor/tmp, log rotation, and the config bind-mounted read-only.
Configuration
The application reads a single INI-style file, ctldap.config, from its working
directory. Inside the container that resolves to /app/ctldap.config, which is
supplied by a read-only bind mount.
cp ctldap.example.config ctldap.config
$EDITOR ctldap.config
chmod 640 ctldap.config
Settings that matter most:
| Key | Meaning |
|---|---|
ct_uri |
Root URL of the ChurchTools instance. |
api_user |
ChurchTools user for API access. Needs churchcore:administer persons and churchdb:view. |
api_password |
Password for that user. Use a long random value. |
ldap_user |
LDAP bind user, e.g. root (a virtual user, recommended). |
ldap_password |
Password for the LDAP bind user. Use a long random value. |
ldap_base_dn |
LDAP base, e.g. churchtools → o=churchtools. |
ldap_ip |
Must be 0.0.0.0 in the container so Docker can route to it. |
ldap_port |
1389. |
cache_lifetime |
How long (ms) fetched ChurchTools data is reused. |
log_level |
error, warn, info (default) or debug. Only debug logs member DNs. |
health_port |
Enables the health/metrics endpoint on loopback. Unset = off. |
ctldap.example.config documents every option and contains placeholders only.
Health and metrics
With health_port set, the service exposes two read-only routes on loopback:
GET /health—200while starting or healthy,503when degraded, with the reasons listed (cache too old, ChurchTools failure rate too high, entry count below an alarm floor, sanity floor tripped).GET /metrics— counters and entry counts as JSON.
Both payloads are counters and sizes only: no member, no DN, no login name and
not the ChurchTools URI. The endpoint binds 127.0.0.1, so unlike the LDAP port
it is not reachable from the caddy network. The container healthcheck uses it
when it is configured and falls back to the plain TCP probe when it is not.
ctldap.configis in.gitignore. Do not remove it from there.
Resulting DNs
With ldap_base_dn=churchtools:
- bind DN:
cn=root,ou=users,o=churchtools - users:
ou=users,o=churchtools - groups:
ou=groups,o=churchtools
Build and run
From the repository root, with ctldap.config in place and the external caddy
network already existing:
docker compose build
docker compose up -d
Check it:
docker compose ps # expect: healthy
docker compose logs -f ctldap
The image ships a healthcheck that opens a TCP connection to 1389 inside the
container, so an unhealthy state means the LDAP listener is not accepting
connections.
To verify from another container on the caddy network:
docker run --rm --network caddy alpine sh -c 'nc -z -v ctldap 1389'
Contributing
Branching and review convention for this repository:
mainis the integration branch and is expected to stay deployable.- Do not commit directly to
main. - Create a feature branch per change, e.g.
fix/ct-uri-debug-leakorfeat/asn1-error-guard. - Open a pull request against
main. Keep the pull request focused on one concern and describe what was verified. - Never include real configuration values, credentials, log excerpts containing personal data, or member data in commits, pull requests or issues.
OPTIMIZATION-PLAN.md records the reviewed findings and the planned work.
License
See LICENSE. Upstream changes are documented in
CHANGELOG.md.