mirror of
https://github.com/netauth/ldap.git
synced 2026-11-03 11:43:44 -06:00
AGENTS.md: Add documentation
This commit is contained in:
parent
18acf05b6b
commit
dbd0e3d689
116
AGENTS.md
Normal file
116
AGENTS.md
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
# AGENTS.md
|
||||
|
||||
## What this is
|
||||
|
||||
A standalone LDAPv3 **read-only bridge** in front of a NetAuth server
|
||||
(`github.com/netauth/netauth`). It translates LDAP bind/search operations into
|
||||
NetAuth RPC calls via the `netauth/protocol` protobuf types. It is not a full
|
||||
LDAP server and never stores data; it only reshapes and serves live NetAuth
|
||||
data. Single static binary (`ldapd`), intended to run bound to localhost.
|
||||
|
||||
There are **no tests anywhere** in this repo (no `*_test.go` files). Do not
|
||||
assume `go test ./...` validates anything.
|
||||
|
||||
## Commands
|
||||
|
||||
```sh
|
||||
go build ./... # build
|
||||
go vet ./...
|
||||
go run . # run locally (reads config like the real binary, see below)
|
||||
go mod vendor # repo vendors deps; vendor/ and dist/ are gitignored
|
||||
|
||||
# Production-style build (matches Dockerfile)
|
||||
CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-extldflags "-static"' -o ldapd .
|
||||
```
|
||||
|
||||
Release is via [goreleaser](.goreleaser.yml) triggered by `v*` tags in
|
||||
[.github/workflows/release.yml](.github/workflows/release.yml); the before-hooks
|
||||
run `go mod vendor` and `scripts/prepare.sh` (regenerates `NOTICE` from vendored
|
||||
license files via `scripts/vendor-licenses`, which needs the vendor tree present).
|
||||
Note `dist/config.yaml` is a generated/stale goreleaser artifact, not the config
|
||||
actually used.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
main.go # wiring only: config, logging, client, serve
|
||||
internal/ldap/ # the LDAP server (package ldap)
|
||||
ldap.go # New(), routing, SetDomain(), Serve/ServeTLS
|
||||
type.go # server struct + naClient interface (the seam for fakes)
|
||||
option.go # functional options: WithLogger, WithNetAuth, WithAnonBind
|
||||
bind.go # bind handler (auth against NetAuth)
|
||||
search.go # RootDSE, entity & group search handlers
|
||||
bleve_mapper.go # LDAP filter -> Bleve query string translation
|
||||
util.go # DN parsing (entityIDFromDN)
|
||||
internal/buildinfo/ # Version var ("dev"), advertised in RootDSE
|
||||
```
|
||||
|
||||
Architecture: `main` builds a `viper` config + `netauth` client, constructs the
|
||||
server with functional options, and calls `Serve`/`ServeTLS`. The underlying
|
||||
LDAP engine is a `RouteMux` (net/http-style routing on LDAP ops) with handlers
|
||||
keyed by base DN and scope.
|
||||
|
||||
## Non-obvious gotchas
|
||||
|
||||
- **Forked LDAP libraries**: the server stack is
|
||||
`github.com/ps78674/ldapserver` + `github.com/ps78674/goldap`, not the more
|
||||
common `nwaples/ldapserver` / `go-ldap` packages. Import as
|
||||
`ldap "github.com/ps78674/ldapserver"`. Check the vendored API before
|
||||
assuming upstream signatures.
|
||||
- **Routes depend on the domain**: `New()` only registers NotFound/Abandon/Bind
|
||||
and the RootDSE route. The `ou=entities` / `ou=groups` search routes are
|
||||
registered as a side effect of `SetDomain()`, which **must be called** after
|
||||
`New()` or searches won't route at all. Calling it twice re-registers routes.
|
||||
- **Base DN construction**: `SetDomain` takes a plain domain (`example.com`),
|
||||
splits on `.`, prepends `dc=netauth`, yielding `dc=netauth,dc=example,dc=com`.
|
||||
Tree layout under it is fixed: `uid=<id>,ou=entities,<base>` and
|
||||
`cn=<name>,ou=groups,<base>`.
|
||||
- **Supported LDAP filters are extremely limited** (`bleve_mapper.go`): only
|
||||
equality match, OR, and attribute-present filters; only the attributes `uid`
|
||||
(→ `ID:`) and `cn` (→ `Name:`); values are passed through as raw Bleve query
|
||||
strings to NetAuth's search. Everything else returns
|
||||
`unwillingToPerform`/"Filter type not supported".
|
||||
- **Anonymous bind fall-through** (`bind.go`): when `allowAnon` is enabled and
|
||||
the bind DN is empty, a success response is written but the handler **does
|
||||
not return** — execution continues into `entityIDFromDN("")`, which errors
|
||||
and writes a second response. This appears to be a bug; be careful not to
|
||||
"fix" it blindly if behavior parity matters, and definitely add a response
|
||||
write guard if you touch it.
|
||||
- **Only simple bind** — no SASL; anything else is rejected outright.
|
||||
- **DN parsing known defect**: `entityIDFromDN` splits on `,` without handling
|
||||
escaped commas (documented as acceptable since NetAuth IDs can't contain
|
||||
commas). Error strings there have typos ("is underneath", "must be rooted");
|
||||
leave them alone unless asked.
|
||||
- **Search fan-out**: each entity result triggers an extra `EntityGroups` RPC
|
||||
for `memberOf`, and each group result triggers `GroupMembers` for `member`.
|
||||
A broad search is N+1 RPCs against NetAuth. Group expansion is done
|
||||
server-side by NetAuth (flattened, fully expanded groups).
|
||||
- **Logging is dual-library**: app logging uses `hashicorp/go-hclog`; the
|
||||
vendored ldapserver uses `logrus`, and `main.go` hijacks the global logrus
|
||||
logger to funnel protocol traces into hclog at forced Trace level via
|
||||
`NETAUTH_LOGLEVEL`. Use `s.l` (hclog, keyed with `"key", value` pairs) inside
|
||||
the server package.
|
||||
- **NetAuth dependency seam**: server code talks to NetAuth only through the
|
||||
`naClient` interface in `type.go:12`. If you add a NetAuth call, add it to
|
||||
that interface, not directly to the concrete client.
|
||||
- **Version injection mismatch**: goreleaser ldflags set `main.version` etc.,
|
||||
but the RootDSE reports `internal/buildinfo.Version`, which stays `"dev"` in
|
||||
built binaries.
|
||||
|
||||
## Configuration
|
||||
|
||||
viper-driven; config file `config.{yaml,...}` searched in `/etc/netauth/`,
|
||||
`$HOME/.netauth/`, `.` — **a missing config file is fatal** (exit 5), even with
|
||||
all values provided by env. Env vars use the `NETAUTH_` prefix with `_`
|
||||
replacing `.` (e.g. `NETAUTH_LDAP_BIND`, `NETAUTH_LDAP_DOMAIN`,
|
||||
`NETAUTH_LOGLEVEL`). Known keys (defaults in `main.go:16-22`):
|
||||
`ldap.bind` (localhost:389), `ldap.tls` (false), `ldap.key`, `ldap.cert`,
|
||||
`ldap.allow_anon` (false), `ldap.domain` (no default — required).
|
||||
|
||||
## Style notes
|
||||
|
||||
- Commit message convention: `component: Capitalized description`
|
||||
(e.g. `internal/ldap: Add option to bind anonmyously`).
|
||||
- Handlers are methods on `*server` named `handle<X>`; response pattern is
|
||||
construct response → `w.Write(res)` on every exit path.
|
||||
- Loop style favors `for i := range x` throughout.
|
||||
Loading…
Reference in a new issue