ldappool

package module
v0.1.0-alpha.2 Latest Latest
Warning

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

Go to latest
Published: Aug 17, 2026 License: MIT Imports: 20 Imported by: 0

README

ldappool

ldappool is a concurrency-safe pool of reusable, authenticated LDAP connections for Go. One pool represents exactly one endpoint, transport security policy, and fixed authentication identity.

The API deliberately prevents callers from rebinding, changing TLS state, closing a shared connection, or retaining the underlying ldap.Client.

Status: alpha/experimental. The API is under validation until v1.0.0, and prereleases are not recommended for production use.

Requirements

  • Go 1.25 language compatibility or later; use a currently supported, security-patched toolchain. This release is validated with Go 1.25.13 and Go 1.26.6.
  • github.com/go-ldap/ldap/v3 v3.4.14
  • LDAP, LDAPS, or LDAP with StartTLS

Install

go get github.com/imbrooklyn/ldappool@v0.1.0-alpha.2

Pin an exact tag you have reviewed, and do not rely on @latest while the module is experimental. Prereleases are not a production recommendation.

Why this package

  • Lazy connection creation: a temporarily unavailable directory does not prevent application startup.
  • Fixed authenticated identity per pool, with safe credential rotation.
  • FIFO acquisition, bounded waiters, cancellation, and direct connection handoff without broadcast wakeups.
  • Idle and lifetime eviction with exact MaxOpen accounting, including connections whose physical close is still running.
  • Pool-owned connection establishment, bounded exponential backoff, and an in-process credential guard that can optionally be shared across pools.
  • Callback-scoped leases that become invalid after return and reject concurrent operations.
  • Explicit write outcome semantics: an update that may have reached the server returns ErrOutcomeUnknown and is never retried automatically.
  • Verified TLS by default, redacted errors and events, passive snapshots, and a framework-neutral observer.

Basic use

roots, err := x509.SystemCertPool()
if err != nil {
    return err
}

pool, err := ldappool.New(ldappool.Config{
    Endpoint: "ldaps://ldap.example.com:636",
    TLS: ldappool.TLSConfig{
        Mode:       ldappool.TLSImplicit,
        ServerName: "ldap.example.com",
        RootCAs:    roots,
    },
    Auth: ldappool.AuthConfig{
        Mode:          ldappool.AuthSimple,
        BindPrincipal: "cn=service,ou=applications,dc=example,dc=com",
        Password:      ldappool.NewSecret(passwordBytes),
    },
})
if err != nil {
    return err // static configuration error only; no network access occurred
}
defer func() {
    closeCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    _ = pool.Close(closeCtx)
}()

request := ldap.NewSearchRequest(
    "dc=example,dc=com",
    ldap.ScopeWholeSubtree,
    ldap.NeverDerefAliases,
    100,
    10,
    false,
    "(uid=alice)",
    []string{"dn", "displayName"},
    nil,
)

var result *ldap.SearchResult
err = pool.WithConnection(ctx, func(conn *ldappool.Connection) error {
    var searchErr error
    result, searchErr = conn.Search(ctx, request)
    return searchErr
})

The Connection value is valid only while its callback is running. Saving it and using it later returns ErrLeaseInvalid; copying the value cannot extend the lease because every copy shares the same invalidation state. Starting two operations on the same lease concurrently returns ErrConcurrentUse.

Configuration defaults

All invalid static combinations fail in New before any network activity. Changing a configuration value after New does not reconfigure a pool; create a new pool, except that the simple-Bind password may be changed with RotateCredentials.

Field Zero/default behavior Main constraints
Endpoint No default Required ldap:// or ldaps:// URL with only host and optional port
TLS.Mode TLSAuto LDAPS uses implicit TLS; LDAP uses StartTLS; plaintext is explicit
TLS.ServerName Endpoint host ASCII DNS A-label or IP address, without a port
TLS.RootCAs System roots A non-nil pool replaces system roots and is copied
TLS.ClientCertificates None Every entry needs a parseable chain and private key
TLS.MinVersion TLS 1.2 TLS 1.2 or TLS 1.3 only
Auth.Mode Invalid Explicit AuthSimple or AuthAnonymous
Auth identity None Simple mode requires either principal + nonempty Secret, or a shared CredentialGuard; anonymous forbids all three
MaxOpen / MaxIdle 8 / min(2, MaxOpen) MaxOpen >= 1; 1 <= MaxIdle <= MaxOpen
MaxWaiters 1024 At least 1; bounds external FIFO waiters
MaxIdleTime / MaxLifetime 5m / 30m Positive and independent; the earlier expiry retires an idle connection
ConnectTimeout / RequestTimeout / CloseTimeout 10s / 30s / 10s Positive; each is a separate lifecycle budget
ConnectBackoff 100ms, 2x, 30s cap, 20% jitter Positive initial; multiplier >= 1; cap >= initial; jitter in [0,1)
CredentialBackoff 5s, 2x, 5m cap, 30% jitter Same validation; configured on CredentialGuardConfig instead when a guard is shared
Dialer net.Dialer Must honor context and return a fresh transport on every call
CredentialFailureClassifier Conservative built-in policy Trusted, synchronous, and must not retain raw diagnostics
Observer / ObserverBuffer Disabled / 256 when enabled Buffer is 1 through 65536; a nonzero buffer requires an observer

Negative sizes or durations are invalid; zero selects the defaults above. The ordinary and credential backoff maxima cannot be smaller than their initial delays. MaxIdleTime and MaxLifetime have no required ordering.

Security defaults

TLSAuto selects implicit TLS for ldaps:// and StartTLS for ldap://. Both verify the server certificate and reference identity before a simple Bind is sent. TLS 1.2 is the default minimum. InsecureSkipVerify is intentionally not exposed.

Secret, Config, TLSConfig, AuthConfig, Pool, Connection, and the sensitive classifier input use constant redacted String and GoString representations. Pool and lease internals are additionally stored behind opaque state pointers, so explicitly copied handle values cannot be recursively formatted into secrets or fork lifecycle state. Do not serialize configuration values: they necessarily hold the endpoint, Bind principal, trust material, optional client key, and secret needed to establish connections. Certificate DER and slices are copied, but an arbitrary client private-key signer cannot be cloned; it is a trusted shared object and must not be mutated after New.

Plaintext simple Bind requires both TLSPlaintext and AllowInsecurePlaintextCredentials; this explicit development-only mode must not be used on an untrusted network. Anonymous authentication must also be selected explicitly.

A pool never supports a second Bind. End-user password authentication must use a separate, short-lived connection that is always closed and never enters this pool.

Operations

The v0.1 surface contains only synchronous operations that complete inside a lease:

  • Search, SearchWithPaging, DirSync, and Compare
  • Add, Modify, Del, ModifyDN, and PasswordModify

Asynchronous searches, streaming responses, Bind, StartTLS, Unbind, arbitrary Extended operations, WhoAmI, ModifyWithResult, and the raw LDAP client are not exposed. ModifyWithResult and generic Extended operations remain excluded because the selected upstream version does not strictly reject every unexpected response shape.

Context and operation outcomes

Acquisition observes the caller context. Connection establishment uses a separate pool-owned context, so cancellation of the request that happened to trigger a cold connection does not cancel recovery for every other caller.

Every operation combines its context with RequestTimeout. A cancellation or timeout that races an active synchronous request closes that physical connection. This is required because go-ldap's synchronous methods do not all accept a context and an old response must never be observed by the next lease.

No operation is retried automatically. For writes:

  • errors.Is(err, ldappool.ErrNotSent) means no request byte was written.
  • errors.As(err, *ldappool.OperationError) and ResultCode() identify a complete server LDAP result.
  • errors.Is(err, ldappool.ErrOutcomeUnknown) means the request may have taken effect, but the response could not be confirmed. Reconcile state or apply an application-specific idempotency strategy before retrying.

An operation whose success has already been confirmed remains successful even if its context is canceled immediately afterward.

Startup, backoff, and credentials

New validates and copies configuration but performs no DNS lookup, dial, TLS handshake, or Bind. The first waiter starts connection creation. Warmup is optional and does not poison or close the pool when it fails.

Ordinary connection backoff limits only new dials. It never prevents reuse of an already healthy connection. If a healthy connection is borrowed, a waiter remains queued for its return even while creation is delayed.

By default, each Pool owns an independent credential guard. Applications that use the same Bind identity against multiple endpoints can create one CredentialGuard and assign it to Auth.CredentialGuard in every pool. The guard owns the principal, password, classifier, backoff policy, generation, and failure state. Bind operations are globally single-flight across attached pools, so the first credential failure updates the shared state before another pool can send a Bind. Dial and TLS setup may still proceed independently.

guard, err := ldappool.NewCredentialGuard(ldappool.CredentialGuardConfig{
    BindPrincipal: "cn=service,ou=applications,dc=example,dc=com",
    Password:      ldappool.NewSecret(passwordBytes),
})
if err != nil {
    return err
}

pool, err := ldappool.New(ldappool.Config{
    Endpoint: "ldaps://dc1.example.com:636",
    TLS:      tlsConfig,
    Auth: ldappool.AuthConfig{
        Mode:            ldappool.AuthSimple,
        CredentialGuard: guard,
    },
})

Per-pool CredentialBackoff and CredentialFailureClassifier settings are invalid in shared mode; configure them once on CredentialGuardConfig.

The conservative default treats Busy and Unavailable as ordinary connection failures; it blocks future Bind attempts for invalid credentials, unsupported or inappropriate authentication, security-policy failures, and unknown complete Bind results. A custom CredentialFailureClassifier can implement vendor policy. Its raw diagnostic input is sensitive and must never be logged or retained. Vendor-specific classifiers belong in separate optional modules.

A classifier is a trusted extension and must return promptly; it must not wait on the same pool attempt that invoked it.

Use Pool.RotateCredentials to replace the password for the same Bind principal. In shared mode it rotates the shared guard and retires old-generation connections in every attached pool; calling CredentialGuard.RotateCredentials has the same cross-pool effect and also works when no pool is attached. Existing borrowed connections are closed on return. Pool.ResetCredentialGuard resets the shared guard when present; CredentialGuard.Reset is the pool-independent equivalent. Reset only after an operator has established that another probe is safe.

Observability

Snapshot is passive: it takes no connection and performs no network health check. Counter fields are mutually consistent at one pool-lock instant; credential fields are sampled separately and may straddle a concurrent guard transition.

An optional Observer receives redacted events from a bounded asynchronous queue. Pool paths never wait for the observer. Full queues increment Snapshot.ObserverDropped; observer panics are recovered. Events and snapshots never include passwords, principals, DNs, filters, attribute values, or raw server diagnostics. Do not put such values into metric labels in an adapter.

Shutdown

Close(ctx) rejects new acquisition, wakes waiters, allows an LDAP operation already in flight to finish within the shutdown budget, and then force-closes remaining transports. A borrowed lease between operations is invalidated immediately. ForceClose(ctx) starts with forced transport closure. Both are idempotent and share the same terminal state.

Physical connections remain counted as open until their transport Close returns or panics. A contract-violating custom transport whose Close blocks forever can therefore make shutdown return ErrCloseFailed; it cannot cause the pool to exceed MaxOpen.

Compatibility and support

The module follows semantic versioning. During v0.x, incompatible corrections may still be necessary. From v1.0.0, documented exported behavior will remain compatible within v1. Dependency upgrades are reviewed against upstream source and wire-conformance tests; new upstream methods never become public automatically.

See SECURITY.md, CONTRIBUTING.md, and CHANGELOG.md.

License

MIT. See LICENSE and THIRD_PARTY_NOTICES.md.

Documentation

Overview

Package ldappool provides a concurrency-safe pool of reusable, authenticated LDAP connections.

A Pool has exactly one endpoint, transport-security policy, and authentication identity. Pools using the same simple-Bind identity may share a CredentialGuard to coordinate credential validation, rotation, and failure state across endpoints. Connections are exposed only through callback-scoped leases, so a caller cannot rebind, start TLS, close, or retain the underlying LDAP client. The package never retries LDAP operations automatically. In particular, a write that may have reached the server is reported with ErrOutcomeUnknown.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrPoolClosed indicates that the Pool is closing or closed.
	ErrPoolClosed = errors.New("ldappool: pool closed")
	// ErrWaitQueueFull indicates that MaxWaiters has been reached.
	ErrWaitQueueFull = errors.New("ldappool: wait queue full")
	// ErrCredentialDelayed indicates that new authentication attempts are delayed.
	ErrCredentialDelayed = errors.New("ldappool: credential attempt delayed")
	// ErrCredentialBlocked indicates that new authentication attempts are blocked.
	ErrCredentialBlocked = errors.New("ldappool: credential attempt blocked")
	// ErrNotSent indicates that no byte of the LDAP request was written.
	ErrNotSent = errors.New("ldappool: request was not sent")
	// ErrOutcomeUnknown indicates that a write may have taken effect but was not confirmed.
	ErrOutcomeUnknown = errors.New("ldappool: write outcome unknown")
	// ErrLeaseInvalid indicates use outside the callback-scoped lease.
	ErrLeaseInvalid = errors.New("ldappool: connection lease invalid")
	// ErrConcurrentUse indicates overlapping operations on one lease.
	ErrConcurrentUse = errors.New("ldappool: concurrent lease use")
	// ErrDependencyPanic indicates a recovered panic in a dependency boundary.
	ErrDependencyPanic = errors.New("ldappool: dependency panic")
	// ErrTransport indicates a transport failure.
	ErrTransport = errors.New("ldappool: transport failure")
	// ErrProtocol indicates a malformed, mismatched, or unexpected response.
	ErrProtocol = errors.New("ldappool: protocol failure")
	// ErrCloseFailed indicates that resource cleanup failed or exceeded its budget.
	ErrCloseFailed = errors.New("ldappool: close failed")
)

Functions

This section is empty.

Types

type AcquireError

type AcquireError struct {
	// Stage identifies where acquisition failed.
	Stage FailureStage
	// RetryAt is the earliest effective retry time when known.
	RetryAt time.Time
	// Cause retains a programmatically inspectable error chain.
	Cause error
	// contains filtered or unexported fields
}

AcquireError reports why a connection could not be acquired.

func (*AcquireError) Error

func (e *AcquireError) Error() string

func (*AcquireError) Unwrap

func (e *AcquireError) Unwrap() []error

type AuthConfig

type AuthConfig struct {
	// Mode must explicitly select simple or anonymous authentication.
	Mode AuthMode
	// BindPrincipal is the fixed simple-Bind identity.
	BindPrincipal string
	// Password is the fixed simple-Bind secret.
	Password Secret
	// CredentialGuard optionally supplies a shared simple-Bind identity,
	// password, classifier, and guard state. When non-nil, BindPrincipal and
	// Password must be empty. Pools using the same guard serialize Bind
	// operations and share block/backoff decisions.
	CredentialGuard *CredentialGuard
}

AuthConfig configures the Pool's fixed LDAP authentication identity.

func (AuthConfig) GoString

func (AuthConfig) GoString() string

GoString returns a constant redacted representation.

func (AuthConfig) String

func (AuthConfig) String() string

String returns a constant redacted representation.

type AuthMode

type AuthMode uint8

AuthMode selects the fixed authentication identity for a Pool.

const (
	// AuthUnspecified is invalid and forces callers to choose an authentication mode.
	AuthUnspecified AuthMode = iota
	// AuthSimple performs one simple Bind while creating each connection.
	AuthSimple
	// AuthAnonymous sends no Bind request.
	AuthAnonymous
)

type BackoffConfig

type BackoffConfig struct {
	// Initial is the first failure delay.
	Initial time.Duration
	// Multiplier controls exponential growth and must be at least one.
	Multiplier float64
	// Max caps the jittered delay.
	Max time.Duration
	// Jitter is a multiplicative fraction in [0, 1).
	Jitter float64
}

BackoffConfig configures capped exponential backoff with multiplicative jitter. Jitter is a fraction in [0, 1).

type CloseError

type CloseError struct {
	// Remaining is the number of open physical connections at timeout.
	Remaining int
	// TimedOut reports whether the shutdown wait budget expired.
	TimedOut bool
	// Cause retains timeout or physical-close errors.
	Cause error
}

CloseError reports incomplete or failed physical cleanup.

func (*CloseError) Error

func (e *CloseError) Error() string

func (*CloseError) Unwrap

func (e *CloseError) Unwrap() []error

type Config

type Config struct {
	// Endpoint is a required ldap:// or ldaps:// URL containing only host and port.
	Endpoint string
	// TLS configures transport encryption and peer verification.
	TLS TLSConfig
	// Auth configures the fixed LDAP identity.
	Auth AuthConfig

	// AllowInsecurePlaintextCredentials is a development-only second gate for a
	// plaintext simple Bind.
	AllowInsecurePlaintextCredentials bool

	// MaxOpen includes connecting, idle, borrowed, and physically closing
	// connections. Zero defaults to 8.
	MaxOpen int
	// MaxIdle bounds retained idle connections. Zero defaults to min(2, MaxOpen).
	MaxIdle int
	// MaxWaiters bounds externally queued acquisitions. Zero defaults to 1024.
	MaxWaiters int

	// MaxIdleTime retires a connection after this idle duration. Zero defaults
	// to 5 minutes.
	MaxIdleTime time.Duration
	// MaxLifetime retires a connection after this total lifetime. Zero defaults
	// to 30 minutes; it is independent of MaxIdleTime.
	MaxLifetime time.Duration
	// ConnectTimeout bounds Dial, TLS, and Bind together. Zero defaults to 10 seconds.
	ConnectTimeout time.Duration
	// RequestTimeout bounds one public synchronous operation. Zero defaults to 30 seconds.
	RequestTimeout time.Duration
	// CloseTimeout bounds shutdown, callback cleanup, and Warmup cleanup waits.
	// Zero defaults to 10 seconds.
	CloseTimeout time.Duration

	// ConnectBackoff limits new connection attempts after ordinary failures.
	// Zero fields default to 100ms initial, 2x, 30s maximum, and 20% jitter.
	ConnectBackoff BackoffConfig
	// CredentialBackoff controls delayed credential probes for a private guard.
	// Zero fields default to 5s initial, 2x, 5m maximum, and 30% jitter. It must
	// be zero when Auth.CredentialGuard is non-nil.
	CredentialBackoff BackoffConfig

	// Dialer optionally supplies a context-aware transport. Nil uses net.Dialer.
	Dialer Dialer
	// CredentialFailureClassifier optionally applies deployment credential
	// policy to a private guard. Nil uses the conservative built-in classifier;
	// it must be nil when Auth.CredentialGuard is non-nil.
	CredentialFailureClassifier CredentialFailureClassifier
	// Observer optionally receives redacted asynchronous events. Nil disables events.
	Observer Observer
	// ObserverBuffer bounds the observer event queue. Zero defaults to 256 when
	// Observer is non-nil and is invalid otherwise when nonzero.
	ObserverBuffer int
}

Config configures a Pool. New applies documented defaults, validates all fields, and copies copyable security material. Trusted interface values and arbitrary private-key signers are retained by reference and must be immutable after New. Changing ordinary Config fields after New has no effect.

func (Config) GoString

func (Config) GoString() string

GoString returns a constant redacted representation.

func (Config) String

func (Config) String() string

String returns a constant redacted representation.

type ConfigError

type ConfigError struct {
	// Field identifies the invalid field without including its value.
	Field string
	// Reason is a static, non-sensitive validation reason.
	Reason string
}

ConfigError reports invalid static configuration without echoing field values.

func (*ConfigError) Error

func (e *ConfigError) Error() string

type Connection

type Connection struct {
	// contains filtered or unexported fields
}

Connection is a callback-scoped, single-operation-at-a-time LDAP lease. Its zero value and every value retained after WithConnection returns are invalid. Copying a Connection does not create a new lease; copies share the same invalidation and busy state. Connection intentionally exposes no raw client or session-changing methods.

func (*Connection) Add

func (c *Connection) Add(ctx context.Context, request *ldap.AddRequest) error

Add performs an LDAP Add operation. It is never retried automatically.

func (*Connection) Compare

func (c *Connection) Compare(ctx context.Context, dn, attribute, value string) (bool, error)

Compare performs an LDAP Compare operation.

func (*Connection) Del

func (c *Connection) Del(ctx context.Context, request *ldap.DelRequest) error

Del performs an LDAP Delete operation. It is never retried automatically.

func (*Connection) DirSync

func (c *Connection) DirSync(ctx context.Context, request *ldap.SearchRequest, flags, maxAttrCount int64, cookie []byte) (*ldap.SearchResult, error)

DirSync performs one bounded synchronous search using the Microsoft DirSync control. It does not expose the asynchronous response API.

func (Connection) GoString

func (Connection) GoString() string

GoString returns a constant redacted representation.

func (*Connection) Modify

func (c *Connection) Modify(ctx context.Context, request *ldap.ModifyRequest) error

Modify performs an LDAP Modify operation. It is never retried automatically.

func (*Connection) ModifyDN

func (c *Connection) ModifyDN(ctx context.Context, request *ldap.ModifyDNRequest) error

ModifyDN performs an LDAP Modify DN operation. It is never retried automatically.

func (*Connection) PasswordModify

func (c *Connection) PasswordModify(ctx context.Context, request *ldap.PasswordModifyRequest) (*ldap.PasswordModifyResult, error)

PasswordModify performs RFC 3062 Password Modify. It is a write and is never retried automatically.

func (*Connection) Search

func (c *Connection) Search(ctx context.Context, request *ldap.SearchRequest) (*ldap.SearchResult, error)

Search performs one complete synchronous LDAP search.

func (*Connection) SearchWithPaging

func (c *Connection) SearchWithPaging(ctx context.Context, request *ldap.SearchRequest, pagingSize uint32) (*ldap.SearchResult, error)

SearchWithPaging performs all pages synchronously within this lease. It copies the paging control and outer request slices before go-ldap mutates them.

func (Connection) String

func (Connection) String() string

String returns a constant redacted representation.

type Counts

type Counts struct {
	// Open is connecting plus idle plus borrowed plus closing.
	Open int
	// Idle is the number available for immediate acquisition.
	Idle int
	// Borrowed is the number owned by callback leases.
	Borrowed int
	// Connecting is zero or one.
	Connecting int
	// Closing remains part of Open until physical close terminates.
	Closing int
	// Waiters includes queued external and internal acquisitions.
	Waiters int
}

Counts is a consistent snapshot of Pool counters taken under the Pool lock.

type CredentialAction

type CredentialAction uint8

CredentialAction controls only future connection authentication attempts. It never determines whether an existing connection is healthy.

const (
	// CredentialNotCredential treats a Bind result as an ordinary connection failure.
	CredentialNotCredential CredentialAction = iota
	// CredentialDelay delays a future authentication probe using credential backoff.
	CredentialDelay
	// CredentialBlock blocks authentication probes until reset or rotation.
	CredentialBlock
)

type CredentialDecision

type CredentialDecision struct {
	// Action controls future connection authentication attempts.
	Action CredentialAction
	// Reason must be a safe, low-cardinality reason value.
	Reason CredentialReason
}

CredentialDecision is returned by CredentialFailureClassifier.

type CredentialError

type CredentialError struct {
	// State is the credential guard state that denied creation.
	State CredentialState
	// Reason is a sanitized, low-cardinality classification.
	Reason CredentialReason
	// RetryAt is the credential probe time when delayed.
	RetryAt time.Time
	// Cause retains sanitized LDAP result and dependency causes.
	Cause error
}

CredentialError reports a delayed or blocked connection-creation decision.

func (*CredentialError) Error

func (e *CredentialError) Error() string

func (*CredentialError) Unwrap

func (e *CredentialError) Unwrap() []error

type CredentialFailure

type CredentialFailure struct {
	// ResultCode is the standard LDAP result code.
	ResultCode uint16
	// Diagnostic is sensitive vendor text available only at the classifier boundary.
	Diagnostic string
}

CredentialFailure contains the server result presented to a custom classifier. Diagnostic may contain vendor-specific text and must not be logged or retained. The core package never publishes it in errors or events.

func (CredentialFailure) GoString

func (CredentialFailure) GoString() string

GoString returns a constant redacted representation because Diagnostic is sensitive.

func (CredentialFailure) String

func (CredentialFailure) String() string

String returns a constant redacted representation because Diagnostic is sensitive.

type CredentialFailureClassifier

type CredentialFailureClassifier interface {
	Classify(CredentialFailure) CredentialDecision
}

CredentialFailureClassifier optionally classifies a complete Bind result. Implementations must return promptly and must not retain or log CredentialFailure.Diagnostic. They must not wait on the Pool whose connection attempt is being classified.

type CredentialFailureClassifierFunc

type CredentialFailureClassifierFunc func(CredentialFailure) CredentialDecision

CredentialFailureClassifierFunc adapts a function to a classifier.

func (CredentialFailureClassifierFunc) Classify

Classify calls f(failure).

type CredentialGuard

type CredentialGuard struct {
	// contains filtered or unexported fields
}

CredentialGuard owns a simple-Bind identity, its current password, and the credential failure state shared by every attached Pool. It serializes Bind operations across those pools. Its zero value is invalid; construct it with NewCredentialGuard.

func NewCredentialGuard

func NewCredentialGuard(config CredentialGuardConfig) (*CredentialGuard, error)

NewCredentialGuard validates and copies one simple-Bind identity. Pools can share the returned guard by assigning it to AuthConfig.CredentialGuard.

func (CredentialGuard) GoString

func (CredentialGuard) GoString() string

GoString returns a constant redacted representation.

func (*CredentialGuard) Reset

func (g *CredentialGuard) Reset() error

Reset clears shared credential guard state without changing the password or reviving existing connections.

func (*CredentialGuard) RotateCredentials

func (g *CredentialGuard) RotateCredentials(secret Secret) error

RotateCredentials replaces the shared password, resets credential state, cancels old-generation connection attempts, and retires old-generation connections in every attached Pool.

func (CredentialGuard) String

func (CredentialGuard) String() string

String returns a constant redacted representation.

type CredentialGuardConfig

type CredentialGuardConfig struct {
	// BindPrincipal is the fixed simple-Bind identity shared by attached pools.
	BindPrincipal string
	// Password is the initial simple-Bind secret.
	Password Secret
	// CredentialBackoff controls delayed credential probes. Zero fields select
	// the same defaults as Config.CredentialBackoff.
	CredentialBackoff BackoffConfig
	// CredentialFailureClassifier optionally applies deployment credential
	// policy for every pool using this guard.
	CredentialFailureClassifier CredentialFailureClassifier
}

CredentialGuardConfig configures a credential guard that may be shared by multiple pools using the same simple-Bind identity.

func (CredentialGuardConfig) GoString

func (CredentialGuardConfig) GoString() string

GoString returns a constant redacted representation.

func (CredentialGuardConfig) String

func (CredentialGuardConfig) String() string

String returns a constant redacted representation.

type CredentialReason

type CredentialReason string

CredentialReason is a sanitized, low-cardinality classification.

const (
	CredentialReasonNone              CredentialReason = "none"
	CredentialReasonInvalid           CredentialReason = "invalid"
	CredentialReasonPolicy            CredentialReason = "policy"
	CredentialReasonUnsupported       CredentialReason = "unsupported"
	CredentialReasonUnknown           CredentialReason = "unknown"
	CredentialReasonClassifierPanic   CredentialReason = "classifier_panic"
	CredentialReasonManualReset       CredentialReason = "manual_reset"
	CredentialReasonCredentialRotated CredentialReason = "credential_rotated"
)

type CredentialState

type CredentialState string

CredentialState is one Pool's in-process credential guard state.

const (
	CredentialClear    CredentialState = "clear"
	CredentialDelayed  CredentialState = "delayed"
	CredentialHalfOpen CredentialState = "half_open"
	CredentialBlocked  CredentialState = "blocked"
)

type DeliveryState

type DeliveryState uint8

DeliveryState describes whether a request could have reached the server.

const (
	// DeliveryNotSent means no request byte was written.
	DeliveryNotSent DeliveryState = iota
	// DeliveryDispatched means the request may have reached the server.
	DeliveryDispatched
	// DeliveryConfirmed means a complete expected LDAP result was received.
	DeliveryConfirmed
)

func (DeliveryState) String

func (d DeliveryState) String() string

String returns the stable delivery-state name.

type Dialer

type Dialer interface {
	DialContext(ctx context.Context, network, address string) (net.Conn, error)
}

Dialer creates a fresh transport for every call.

Implementations must honor ctx and must never return the same live net.Conn instance from more than one call. A proxying implementation must still route the supplied address to one stable logical LDAP endpoint for the Pool's lifetime.

type Event

type Event struct {
	// Sequence is monotonically delivered within one Pool observer.
	Sequence uint64
	// Type identifies the event.
	Type EventType
	// At is the event time.
	At time.Time
	// Duration is populated for timed events.
	Duration time.Duration
	// Operation is populated for LDAP operation events.
	Operation Operation
	// Stage identifies a lifecycle failure stage or StageNone.
	Stage FailureStage
	// Reason is a sanitized event or eviction reason.
	Reason FailureReason
	// CredentialState is populated for credential events.
	CredentialState CredentialState
	// CredentialReason is a sanitized credential classification.
	CredentialReason CredentialReason
	// RetryAt is populated for delayed creation events.
	RetryAt time.Time
	// Counts is a mutually consistent pool-counter sample.
	Counts Counts
}

Event is a redacted observation record.

type EventType

type EventType string

EventType identifies an observation event. Values are stable and contain no request or identity data.

const (
	EventDialStart           EventType = "dial_start"
	EventDialSuccess         EventType = "dial_success"
	EventDialFailure         EventType = "dial_failure"
	EventTLSFailure          EventType = "tls_failure"
	EventBindSuccess         EventType = "bind_success"
	EventBindFailure         EventType = "bind_failure"
	EventAcquireQueued       EventType = "acquire_queued"
	EventAcquireSuccess      EventType = "acquire_success"
	EventAcquireCanceled     EventType = "acquire_canceled"
	EventBackoffArmed        EventType = "backoff_armed"
	EventCredentialState     EventType = "credential_state"
	EventEviction            EventType = "eviction"
	EventOperationCanceled   EventType = "operation_canceled"
	EventWriteOutcomeUnknown EventType = "write_outcome_unknown"
	EventCloseTimeout        EventType = "close_timeout"
	EventForceClose          EventType = "force_close"
	EventPoolRecovered       EventType = "pool_recovered"
	EventDependencyPanic     EventType = "dependency_panic"
)

type FailureReason

type FailureReason string

FailureReason is a sanitized event or eviction reason.

const (
	ReasonNone                FailureReason = "none"
	ReasonDial                FailureReason = "dial_failure"
	ReasonTLSValidation       FailureReason = "tls_validation_failure"
	ReasonBind                FailureReason = "bind_failure"
	ReasonIdleTimeout         FailureReason = "idle_timeout"
	ReasonMaxLifetime         FailureReason = "max_lifetime"
	ReasonMaxIdle             FailureReason = "max_idle"
	ReasonCredentialRotation  FailureReason = "credential_rotation"
	ReasonTransport           FailureReason = "transport_error"
	ReasonProtocol            FailureReason = "protocol_error"
	ReasonRequestTimeout      FailureReason = "request_timeout"
	ReasonContextCancellation FailureReason = "context_cancellation"
	ReasonCallbackPanic       FailureReason = "callback_panic"
	ReasonDependencyPanic     FailureReason = "dependency_panic"
	ReasonLeaseReturnedBusy   FailureReason = "lease_returned_busy"
	ReasonPoolClose           FailureReason = "pool_close"
	ReasonInitialization      FailureReason = "initialization_failure"
	ReasonCloseFailure        FailureReason = "close_failure"
)

type FailureStage

type FailureStage string

FailureStage identifies a sanitized lifecycle stage.

const (
	// StageNone means no failure stage has been recorded.
	StageNone FailureStage = "none"
	// StageAcquire identifies acquisition and waiter failures.
	StageAcquire FailureStage = "acquire"
	// StageDial identifies raw transport creation.
	StageDial FailureStage = "dial"
	// StageTLS identifies TLS establishment or verification.
	StageTLS FailureStage = "tls"
	// StageBind identifies fixed-identity authentication.
	StageBind FailureStage = "bind"
	// StageOperation identifies a public LDAP operation.
	StageOperation FailureStage = "operation"
	// StageClose identifies physical resource cleanup.
	StageClose FailureStage = "close"
)

type Observer

type Observer interface {
	Observe(Event)
}

Observer receives events from a bounded asynchronous dispatcher. Observe must return promptly. A panic is recovered and a blocked observer cannot block Pool operations, although it can permanently occupy its dispatcher.

type ObserverFunc

type ObserverFunc func(Event)

ObserverFunc adapts a function to Observer.

func (ObserverFunc) Observe

func (f ObserverFunc) Observe(event Event)

Observe calls f(event).

type Operation

type Operation string

Operation identifies a supported LDAP operation without including request data.

const (
	// OperationSearch identifies Search.
	OperationSearch Operation = "search"
	// OperationSearchWithPaging identifies SearchWithPaging.
	OperationSearchWithPaging Operation = "search_with_paging"
	// OperationDirSync identifies synchronous DirSync.
	OperationDirSync Operation = "dir_sync"
	// OperationCompare identifies Compare.
	OperationCompare Operation = "compare"
	// OperationAdd identifies Add.
	OperationAdd Operation = "add"
	// OperationModify identifies Modify.
	OperationModify Operation = "modify"
	// OperationDelete identifies Del.
	OperationDelete Operation = "delete"
	// OperationModifyDN identifies ModifyDN.
	OperationModifyDN Operation = "modify_dn"
	// OperationPasswordModify identifies PasswordModify.
	OperationPasswordModify Operation = "password_modify"
)

type OperationError

type OperationError struct {
	// Operation identifies the LDAP operation.
	Operation Operation
	// Delivery describes whether the request could have reached the server.
	Delivery DeliveryState
	// Code is a standard LDAP result code when HasCode is true.
	Code uint16
	// HasCode reports whether a complete server LDAP result was received.
	HasCode bool
	// Cause retains a redacted, programmatically inspectable chain.
	Cause error
	// contains filtered or unexported fields
}

OperationError reports a sanitized LDAP operation failure.

func (*OperationError) Error

func (e *OperationError) Error() string

func (*OperationError) ResultCode

func (e *OperationError) ResultCode() (uint16, bool)

ResultCode returns a server LDAP result code when the operation received a complete LDAP result.

func (*OperationError) Unwrap

func (e *OperationError) Unwrap() []error

type PanicError

type PanicError struct {
	// Source is a fixed internal dependency-boundary category.
	Source string
}

PanicError describes a recovered panic without retaining or printing its payload.

func (*PanicError) Error

func (e *PanicError) Error() string

func (*PanicError) Unwrap

func (e *PanicError) Unwrap() error

type Pool

type Pool struct {
	// contains filtered or unexported fields
}

Pool owns reusable physical LDAP connections for one fixed identity. Its zero value is invalid; construct it with New. Its state is held behind an opaque pointer so accidental value formatting cannot traverse secrets.

func New

func New(config Config) (*Pool, error)

New validates static configuration and returns a cold Pool. It performs no DNS lookup, network dial, TLS handshake, or Bind.

func (*Pool) Close

func (p *Pool) Close(ctx context.Context) error

Close stops acquisition, allows in-flight operations to finish within the shutdown budget, and then force-closes remaining transports. It is idempotent.

func (*Pool) ForceClose

func (p *Pool) ForceClose(ctx context.Context) error

ForceClose stops acquisition and immediately closes every transport. It is idempotent and shares the same terminal lifecycle as Close.

func (Pool) GoString

func (Pool) GoString() string

GoString returns a constant redacted representation.

func (*Pool) ResetCredentialGuard

func (p *Pool) ResetCredentialGuard() error

ResetCredentialGuard explicitly clears this Pool's credential guard. When shared, the reset applies to every attached Pool. It does not change credentials or revive existing connections.

func (*Pool) RotateCredentials

func (p *Pool) RotateCredentials(secret Secret) error

RotateCredentials replaces the simple-Bind password, increments the credential generation, cancels old connection attempts, and retires all old generation connections. When the Pool uses a shared CredentialGuard, the rotation applies to every attached Pool. The Bind principal cannot change.

func (*Pool) Snapshot

func (p *Pool) Snapshot() Snapshot

Snapshot returns a passive, redacted state snapshot.

func (Pool) String

func (Pool) String() string

String returns a constant redacted representation.

func (*Pool) Warmup

func (p *Pool) Warmup(ctx context.Context, target int) (returnErr error)

Warmup establishes enough authenticated connections for the Pool to have at least target healthy open connections at one instant. Excess idle connections above MaxIdle are closed within one cleanup budget before Warmup returns; cleanup failures are returned. A failure does not close or permanently poison the Pool.

func (*Pool) WithConnection

func (p *Pool) WithConnection(ctx context.Context, callback func(*Connection) error) (returnErr error)

WithConnection acquires a FIFO callback-scoped lease. The Connection becomes permanently invalid when callback returns. callback must not be nil and must not allow an LDAP operation to outlive it; a busy return invalidates the lease, force-closes its transport, and waits only for the cleanup budget.

Example
package main

import (
	"context"
	"time"

	"github.com/go-ldap/ldap/v3"
	"github.com/imbrooklyn/ldappool"
)

func main() {
	pool, err := ldappool.New(ldappool.Config{
		Endpoint: "ldaps://ldap.example.com",
		Auth: ldappool.AuthConfig{
			Mode:          ldappool.AuthSimple,
			BindPrincipal: "cn=service,dc=example,dc=com",
			Password:      ldappool.NewSecret([]byte("load-from-a-secret-store")),
		},
	})
	if err != nil {
		return
	}
	defer func() {
		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
		defer cancel()
		_ = pool.Close(ctx)
	}()

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	request := ldap.NewSearchRequest(
		"dc=example,dc=com",
		ldap.ScopeWholeSubtree,
		ldap.NeverDerefAliases,
		100,
		5,
		false,
		"(objectClass=person)",
		[]string{"dn"},
		nil,
	)
	_ = pool.WithConnection(ctx, func(connection *ldappool.Connection) error {
		_, searchErr := connection.Search(ctx, request)
		return searchErr
	})
}

type PoolState

type PoolState string

PoolState is a read-only projection of lifecycle and connectivity state.

const (
	PoolCold              PoolState = "cold"
	PoolConnecting        PoolState = "connecting"
	PoolReady             PoolState = "ready"
	PoolTransientBackoff  PoolState = "transient_backoff"
	PoolCredentialDelayed PoolState = "credential_delayed"
	PoolCredentialBlocked PoolState = "credential_blocked"
	PoolClosing           PoolState = "closing"
	PoolClosed            PoolState = "closed"
)

type Secret

type Secret struct {
	// contains filtered or unexported fields
}

Secret contains sensitive authentication material.

NewSecret copies its input. Pool construction and credential rotation copy a Secret again so the caller may discard its copy immediately. Go strings made by the LDAP dependency cannot be reliably erased from memory.

func NewSecret

func NewSecret(value []byte) Secret

NewSecret returns a Secret containing a copy of value.

func (Secret) GoString

func (Secret) GoString() string

GoString always returns a redaction marker.

func (Secret) String

func (Secret) String() string

String always returns a redaction marker.

type Snapshot

type Snapshot struct {
	// CapturedAt is the snapshot time.
	CapturedAt time.Time
	// State is the projected pool lifecycle/connectivity state.
	State PoolState
	// Counts is captured atomically under the Pool lock.
	Counts Counts
	// Generation is the current fixed-identity credential generation.
	Generation uint64

	// RetryAt is the ordinary connection-creation retry time.
	RetryAt time.Time
	// ConnectFailures is the current consecutive ordinary failure count.
	ConnectFailures uint64
	// LastFailureStage is the last sanitized connection lifecycle stage.
	LastFailureStage FailureStage
	// LastFailureReason is the last sanitized connection lifecycle reason.
	LastFailureReason FailureReason

	// CredentialState is sampled separately from Counts.
	CredentialState CredentialState
	// CredentialReason is the current sanitized guard reason.
	CredentialReason CredentialReason
	// CredentialRetryAt is the next delayed half-open probe time.
	CredentialRetryAt time.Time
	// CredentialFailures is the guard backoff failure count.
	CredentialFailures uint64

	// ObserverDropped is the cumulative number of events dropped at enqueue.
	ObserverDropped uint64
}

Snapshot is a passive, redacted view of in-memory state. Counts are mutually consistent; credential fields are sampled separately and may straddle one concurrent transition.

type TLSConfig

type TLSConfig struct {
	// Mode selects implicit TLS, StartTLS, or the explicit plaintext escape
	// hatch. Its zero value is TLSAuto.
	Mode TLSMode
	// ServerName is the DNS name or IP reference identity used for verification.
	// An empty value uses the endpoint host.
	ServerName string
	// RootCAs replaces the system trust roots when non-nil. Nil uses the system
	// roots.
	RootCAs *x509.CertPool
	// ClientCertificates contains optional client certificate chains and keys.
	// Certificate slices and DER are copied; arbitrary private-key signers are
	// retained by reference and must be immutable after New.
	ClientCertificates []tls.Certificate
	// MinVersion is either tls.VersionTLS12 or tls.VersionTLS13. Zero defaults
	// to TLS 1.2.
	MinVersion uint16
}

TLSConfig configures server authentication and optional client certificates.

func (TLSConfig) GoString

func (TLSConfig) GoString() string

GoString returns a constant redacted representation.

func (TLSConfig) String

func (TLSConfig) String() string

String returns a constant redacted representation.

type TLSMode

type TLSMode uint8

TLSMode controls how transport security is established.

const (
	// TLSAuto selects implicit TLS for ldaps:// and StartTLS for ldap://.
	TLSAuto TLSMode = iota
	// TLSStartTLS upgrades an ldap:// connection before authentication.
	TLSStartTLS
	// TLSImplicit establishes TLS before starting LDAP.
	TLSImplicit
	// TLSPlaintext disables transport encryption. Simple authentication also
	// requires AllowInsecurePlaintextCredentials and is intended only for
	// isolated development environments.
	TLSPlaintext
)

func (TLSMode) String

func (m TLSMode) String() string

String returns the stable TLS mode name.

Jump to

Keyboard shortcuts

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