Documentation
¶
Overview ¶
Package ldap integrates a generic LDAP directory as a service-task connector: a BPMN LDAP connector task performs a directory operation — search an entry, add / modify / delete an entry, or set an entry's password — against a model-authored server through the job path (ADR-0154), the same seam the rest and scim packages use for HTTP (ADR-0067/0151). It inherits the job protocol's durability and non-blocking properties (ADR-0007):
- An LDAP connector task creates a job carrying the reserved compiler.LdapJobType. The processor never performs the directory call itself, so it stays allocation-free (invariant I1) and free of any LDAP dependency.
- The in-process Handler — a job worker — pulls those jobs, dials and binds off the processor goroutine and after fsync (invariant I2, never inside applyToState / I4), performs the operation, writes a search's entries into the task's result variable, and completes the job, which drives the token onward.
The server URL, bind DN, and target/base DN live in the model as literal-or-FEEL values (the fx toggle, ADR-0067); the bind password is never authored there — it names a server-side secret the worker resolves at runtime (ADR-0041). Every call is bounded by the shared connector budget (nettimeout.Default, ADR-0149), since the worker runs on the run-loop goroutine.
Delivery is at-least-once: a crash between "the directory accepted the change" and "job completed" replays the operation, so add/modify/delete must be authored to tolerate a replay (add of an existing entry, delete of a gone entry) — the worker surfaces the directory's error, and the retry/incident path (ADR-0061) handles it.
Index ¶
Constants ¶
const ( // ModAdd adds values to an attribute, ModDelete removes them, and ModReplace // replaces the attribute wholesale. ModAdd modOp = goldap.AddAttribute ModDelete modOp = goldap.DeleteAttribute ModReplace modOp = goldap.ReplaceAttribute )
const ( DefaultMaxIdlePerTarget = 4 DefaultIdleTTL = 30 * time.Second )
Default pool bounds. The idle window is deliberately short: a directory closes an idle connection on its own schedule and never tells the client, so a connection held much longer than this is one whose next use fails. Thirty seconds is long enough to carry a burst of jobs — a bulk reconciliation, a multi-instance loop — and short enough that the failure mode stays rare.
Variables ¶
This section is empty.
Functions ¶
func Handler ¶
func Handler(store state.Reader, lookup ProcessLookup, dialer Dialer, secret SecretResolver) job.OutputHandler
Handler builds a job handler that performs a generic LDAP connector task. Register it with a job.Runner under the reserved compiler.LdapJobTypeIndex via HandleWithOutput; the runner then pulls activatable LDAP jobs, and for each the handler resolves the task's url / bind DN / operation / DNs from the compiled process, dials and binds through dialer — evaluating any FEEL values over the instance's variables (ADR-0067) and resolving the bind password from a secret reference (ADR-0041) — performs the operation, and for a search returns the entries as the task's result variable. Returning an error fails the job (retry, then an incident, ADR-0061); the runner completes it only on success.
Types ¶
type Conn ¶
type Conn interface {
Search(req SearchRequest) ([]Entry, error)
Add(dn string, attrs map[string][]string) error
Modify(dn string, mods []Mod) error
Delete(dn string) error
SetPassword(dn, newPassword string) error
Close() error
}
Conn is a bound LDAP connection the worker operates over and then closes. It is an interface so the worker is testable without a live directory.
type DialOptions ¶
type DialOptions struct {
// URL is ldap://host:389 or ldaps://host:636; StartTLS upgrades a plain
// connection.
URL string
StartTLS bool
// BindDN and BindPassword authenticate a simple bind. An empty BindDN leaves the
// connection anonymous — unless ClientCert is set, in which case the certificate
// is the identity and the bind is SASL EXTERNAL.
BindDN string
BindPassword string
// ClientCert is a PEM bundle (certificate plus private key) presented to the
// server. It arrives resolved from a secret reference; the model never carries it
// (ADR-0041).
ClientCert string
// CACert is an optional PEM bundle of roots used to verify the server, for a
// directory with a private CA. Empty uses the host's trust store.
CACert string
}
DialOptions is everything a connection needs. It is a struct rather than a parameter list because the list had already reached four and TLS added two more; past that, a call site is a row of positional booleans nobody can read.
type Dialer ¶
type Dialer interface {
Dial(opts DialOptions) (Conn, error)
}
Dialer opens and binds an LDAP connection. It is an interface so the worker is testable without a live server, and so a pooling implementation can stand in front of the real one.
type Entry ¶
Entry is one directory entry a search returns: its DN and its multi-valued attributes keyed by attribute name.
type GoDialer ¶
type GoDialer struct{}
GoDialer dials a real LDAP server through github.com/go-ldap/ldap. The dial and every subsequent operation are bounded by the shared connector call budget (nettimeout.Default), since the worker runs on the run-loop goroutine (ADR-0149/0153).
type Mod ¶
Mod is one attribute change in a modify: an operation, the attribute, and the values it applies.
type Pool ¶
type Pool struct {
// contains filtered or unexported fields
}
Pool is a Dialer that reuses bound connections instead of dialing, binding and tearing down one per job.
Why ¶
ADR-0154 shipped a connection per operation, which is the right default and the wrong steady state: a joiner run over a few hundred accounts pays a TCP handshake, a TLS handshake and a bind for every single entry it touches, against a server that would happily have kept the first connection.
What makes it safe ¶
The pool key is a fingerprint of *everything that decides who the connection is authenticated as* — the URL, STARTTLS, the bind DN, the bind password, and the client certificate. A connection bound as one identity can therefore never be handed to a job asking for another, and a rotated password does not reuse a connection bound with the old one: the fingerprint changes with it, so the old entries simply age out unused.
A connection whose operation returned an error is never pooled. LDAP errors do not distinguish "your filter was wrong" from "this socket is gone" reliably enough to bet a later job on, so the pool does not try: any error retires the connection.
What it does not do ¶
It does not retry. A pooled connection the server closed while it sat idle fails its next operation, and that job takes the engine's ordinary retry-then-incident path (ADR-0061). Retrying inside the connector would mean re-sending a write whose outcome is unknown, which is a worse failure than the one it would paper over.
func NewPool ¶
func NewPool(d Dialer, opts PoolOptions) *Pool
NewPool wraps a dialer with connection reuse.
func (*Pool) Close ¶
Close closes every idle connection and stops the pool reusing any more. Connections currently borrowed are closed when their holder releases them.
type PoolOptions ¶
type PoolOptions struct {
MaxIdlePerTarget int
IdleTTL time.Duration
// Now is injected by tests so idle expiry is assertable without sleeping.
Now func() time.Time
}
PoolOptions configures a Pool. A zero value takes the defaults.
type ProcessLookup ¶
type ProcessLookup func(defKey uint64) *compiler.CompiledProcess
ProcessLookup resolves a process-definition key to its compiled process. The worker uses it to find the server, bind DN, operation, and DNs an LDAP job belongs to, so one handler serves every deployed process.
type SearchRequest ¶
type SearchRequest struct {
BaseDN string
Scope string
Filter string
Attributes []string
// PageSize drives the simple paged-results control (RFC 2696): the search is
// fetched in pages of this size, so a directory's administrative size limit does
// not refuse a legitimate search. 0 asks for one unpaged search.
PageSize int32
// MaxEntries caps how many entries may be returned. Exceeding it is an error
// rather than a truncation: a short result set is a wrong answer, not a partial
// one, and a process branching on the count would branch on it confidently
// (the same rule the SQL connectors apply to rows, ADR-0173). 0 is unbounded.
MaxEntries int32
}
SearchRequest addresses an LDAP search: the base DN, a scope ("base"/"one"/"sub"), a filter (empty → "(objectClass=*)"), and the attributes to return (nil → the server's default set).
type SecretResolver ¶
SecretResolver returns the secret value for a reference name, or "" if unknown. The worker uses it to turn an LDAP task's bind-password *reference* into the actual credential at call time (ADR-0041), so the password never lives in the model.