ldap

package
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 21, 2026 License: AGPL-3.0 Imports: 21 Imported by: 0

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

View Source
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
)
View Source
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

type Entry struct {
	DN         string
	Attributes map[string][]string
}

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).

func NewDialer

func NewDialer() GoDialer

NewDialer returns the production LDAP dialer.

func (GoDialer) Dial

func (GoDialer) Dial(opts DialOptions) (Conn, error)

type Mod

type Mod struct {
	Op   modOp
	Attr string
	Vals []string
}

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

func (p *Pool) Close() error

Close closes every idle connection and stops the pool reusing any more. Connections currently borrowed are closed when their holder releases them.

func (*Pool) Dial

func (p *Pool) Dial(opts DialOptions) (Conn, error)

Dial returns a connection for opts, reusing an idle one when the pool holds it.

func (*Pool) IdleCount

func (p *Pool) IdleCount() int

IdleCount reports how many connections the pool is holding, for tests and for a future Workers-view number.

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

type SecretResolver func(ref string) string

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL