Skip to content

About

Web frontend for LDAP

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

15 stars

Watchers

1 watching

Forks

Latest commit

 

History

1,529 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

LDAP Manager

Template Drift managed by netresearch/.github templates

LDAP Manager Logo

Web-based LDAP administration interface for managing users, groups, and computers.

Supports both Active Directory and OpenLDAP with per-user credential binding.

CI Container codecov License: MIT Go Version Release GHCR

Features

  • Browse, search, and filter users, groups, and computers
  • View and edit user details, group memberships, and attributes
  • Per-user LDAP credential binding (no shared admin account required)
  • Active Directory and OpenLDAP support with automatic detection
  • Direct-bind authentication fallback for non-person accounts
  • Configurable via environment variables or command-line flags
  • LDAP connection pooling with health checks
  • LRU caching with background refresh
  • Distroless Docker image (13 MB, nonroot, read-only filesystem)
  • Multi-platform builds (linux/amd64, linux/arm64, darwin, windows)

Quick Start (local test run)

This section gets the app running on your own machine. It is not a production setup — see Production setup before serving it to anyone else.

Docker (Recommended)

docker run -d --name ldap-manager \
  -e LDAP_SERVER=ldaps://dc1.example.com:636 \
  -e LDAP_BASE_DN="DC=example,DC=com" \
  -e LDAP_READONLY_USER=readonly \
  -e LDAP_READONLY_PASSWORD=password \
  -e LDAP_IS_AD=true \
  -p 3000:3000 \
  ghcr.io/netresearch/ldap-manager:latest

Open http://localhost:3000 and log in with your LDAP credentials.

Plain HTTP works on localhost only. Cookies are marked Secure by default, and a browser keeps such a cookie over plain HTTP only for loopback addresses. Reach the same container by IP or hostname over http:// and every login fails with csrf: token invalid, because the browser discarded the CSRF cookie. Put TLS in front, or set COOKIE_SECURE=false.

Docker Compose (Development)

The project includes a directory to develop against — OpenLDAP with seed data and ACLs, phpLDAPadmin, and an nginx TLS reverse proxy:

docker compose --profile dev up -d   # OpenLDAP, phpLDAPadmin, nginx
make dev                             # the app itself, with templ watching

This gives you:

  • OpenLDAP with pre-configured users (admin/admin, jdoe/password, jsmith/password)
  • phpLDAPadmin on http://localhost:8080
  • nginx TLS reverse proxy on https://localhost:8443, forwarding to the app you run with make dev

The app runs on the host rather than in a container, which is what makes the reload fast. HTTPS through nginx is also the way to exercise the default COOKIE_SECURE=true locally — see Production setup.

Native Build

Prerequisites: Go 1.27+, templ CLI.

templ generate
go build -o ldap-manager ./cmd/ldap-manager

./ldap-manager \
  --ldap-server ldaps://dc1.example.com:636 \
  --active-directory \
  --base-dn DC=example,DC=com \
  --readonly-user readonly \
  --readonly-password readonly

The server listens on port 3000 by default. Set the PORT environment variable to override.

Stack

Go + Fiber + Templ on the backend; Pico CSS + a hand-written internal/web/static/app.css + vendored htmx on the frontend. No Node.js toolchain — vendored files are refreshed with bash scripts/vendor.sh, which verifies SHA-256 checksums against scripts/vendor.lock.

Production setup

The app speaks plain HTTP and has no TLS of its own. Terminate TLS in front of it (Traefik, nginx, Caddy) and keep COOKIE_SECURE=true, which is the default.

Three settings have to agree, or logins fail with a CSRF error:

  • The browser must reach the site over HTTPS. Otherwise it discards the Secure session and CSRF cookies, and the login POST arrives without them.

  • The proxy must be trusted. TRUSTED_PROXIES defaults to 127.0.0.0/8, ::1/128 and 172.16.0.0/12, which covers a sidecar terminator. A terminator on another network — a Kubernetes pod range, an external load balancer — has to be named, or the app keeps believing it serves plain HTTP and the CSRF middleware rejects the browser's https:// origin with csrf: origin does not match host.

    Keep the list as narrow as the deployment allows. It also decides whose X-Forwarded-For the login rate limiter counts against, so a trusted peer can name its own client IP; a list wide enough to include ordinary clients lets them rotate that header and evade the brute-force limit.

  • COOKIE_SECURE=false belongs to plain-HTTP deployments only. It stops the cookie problem and nothing else: credentials still cross the network in the clear. Use it for a local trial, not for a service others log into.

Configuration

All options can be set via environment variables, a .env file, or command-line flags. Run ./ldap-manager --help for the full list.

Environment Variable Flag Default Description
LDAP_SERVER --ldap-server (required) LDAP URI (ldap:// or ldaps://)
LDAP_BASE_DN --base-dn (required) Base DN for LDAP searches
LDAP_IS_AD --active-directory false Enable Active Directory mode
LDAP_READONLY_USER --readonly-user Service account DN for background cache
LDAP_READONLY_PASSWORD --readonly-password Service account password
LDAP_ADMIN_GROUP --admin-group Group DN whose members may view the password-expiry roster
LDAP_TLS_SKIP_VERIFY --tls-skip-verify false Skip TLS certificate verification
PORT 3000 HTTP listen port
COOKIE_SECURE --cookie-secure true Require HTTPS for cookies
TRUSTED_PROXIES --trusted-proxies 127.0.0.0/8,::1/128,172.16.0.0/12 Peers whose X-Forwarded-* headers are believed
PERSIST_SESSIONS --persist-sessions false Persist sessions to BoltDB
SESSION_DURATION --session-duration 30m Session lifetime
LOG_LEVEL --log-level info Log level (trace, debug, info, warn, error)

When no readonly user is configured, the app uses per-user LDAP credentials for all operations and the background cache is disabled.

Password-expiry roster

/password-expiry lists accounts whose LDAP password is expiring, resolved live via simple-ldap-go's expiry API. It is admin-only and needs the service account.

An admin is a member of LDAP_ADMIN_GROUP or an account carrying Active Directory's adminCount=1. Note that adminCount is sticky: Active Directory sets it when an account joins a protected group and never clears it on removal, so an account that was ever privileged keeps roster access. Prefer LDAP_ADMIN_GROUP membership where you want access to track current privilege. On OpenLDAP there is no adminCount, so LDAP_ADMIN_GROUP is the only way to grant access — without it, the roster is reachable by no one. Group membership is read from the user's memberOf, which Active Directory populates automatically; an OpenLDAP deployment must have the memberof overlay enabled for the group gate to work.

The default view shows accounts due within a window (?days=, default 30, capped at 366); a Show all accounts toggle adds the never-expires and unknown accounts with a status badge. On OpenLDAP, expiry needs the ppolicy overlay; accounts the directory reports nothing about show as unknown.

Traefik Integration

For production deployments behind Traefik:

docker network create traefik

# In .env:
TRAEFIK_ENABLE=true
TRAEFIK_HOST=example.com
# Access: https://ldap-manager.example.com

Screenshots

Users List User Detail
Users List User Detail
Groups List Group Detail
Groups List Group Detail

Docker Image

Published to GitHub Container Registry:

  • 13 MB distroless runtime (nonroot UID 65532, read-only filesystem, no shell)
  • Multi-platform: linux/amd64, linux/arm64, linux/arm/v7
  • OCI-compliant labels and health check built in
docker run -d --name ldap-manager \
  -v /etc/ssl/certs:/etc/ssl/certs:ro -p 3000:3000 \
  ghcr.io/netresearch/ldap-manager:latest \
  --ldap-server ldaps://dc1.example.com:636 \
  --base-dn DC=example,DC=com

Documentation

Full documentation is available in docs/:

Accessibility

LDAP Manager's login page conforms to WCAG 2.2 Level AAA in comfortable density (the default on touch devices, narrow viewports, and under prefers-reduced-motion). In compact density (the default on desktop), the login page meets Level AA; all AAA success criteria are met except 2.5.5 Target Size (Enhanced), which is a deliberate density-preference trade-off.

Conformance is enforced in CI by a contrast unit test (internal/web/contrast_test.go) and an axe-core AAA pass on every E2E run (internal/e2e/axe_test.go). Additional routes will be brought under the same guarantee as they migrate in subsequent slices.

The relationship graph view at /graph (and the List | Graph mode toggle on the user, group, and computer list pages) meets WCAG 2.2 Level AA. An equivalent flat edge table is always rendered below the visual canvas, providing an AAA-equivalent text alternative for every interaction and relationship the canvas displays. The graph pages are covered by the same axe-core ratchet (internal/e2e/axe_graph_test.go).

Contributing

Contributions welcome! Please open a Pull Request.

This project uses Conventional Commits and formats code with gofumpt + goimports.

License

MIT

About

Web frontend for LDAP

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

15 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages