ad

package
v0.4.0 Latest Latest
Warning

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

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

Documentation

Overview

Package ad integrates Active Directory as a service-task connector: a BPMN AD connector task performs an AD-specific provisioning operation — create a user, set a password, enable or disable an account, or add/remove a group member — against a model-authored server through the job path (ADR-0166), the same seam the ldap package uses (ADR-0154). AD speaks LDAP, so this connector dials and binds exactly like the generic LDAP connector; it exists because AD expresses those operations through mechanisms the generic connector cannot: a password is the binary `unicodePwd` attribute (UTF-16LE, quote-wrapped, LDAPS only), an account's enabled/disabled state is a bit in `userAccountControl` (a read-modify-write), and group membership is an *incremental* add/delete of a `member` value rather than a whole-attribute replace.

It inherits the job protocol's durability and non-blocking properties (ADR-0007): the processor creates a job carrying compiler.AdJobType and never talks to AD itself; the in-process Handler pulls the job, dials/binds/operates/closes off the run loop and after fsync, and completes it. The server URL and DNs live in the model as literal-or-FEEL values; the bind password is a server-side secret reference (ADR-0041). Every call is bounded by the shared connector budget (nettimeout.Default, ADR-0149). Delivery is at-least-once, so an operation must tolerate a replay.

Index

Constants

This section is empty.

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 an Active Directory connector task. Register it with a job.Runner under the reserved compiler.AdJobTypeIndex via HandleWithOutput; the runner then pulls activatable AD 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) — and performs the operation. Returning an error fails the job (retry, then an incident, ADR-0061); the runner completes it only on success.

func Run

func Run(_ context.Context, j Job, dialer Dialer, secret SecretResolver) (map[string]any, error)

Run performs a resolved job: it resolves the bind password from the caller's own secret store, dials and binds, performs the operation, and closes. It is the whole of the worker's half, and the in-process path calls it too, so there is one definition of what a resolved AD task means rather than two that drift.

Types

type Conn

type Conn interface {
	// Add creates an entry with the given attributes.
	Add(dn string, attrs map[string][]string) error
	// Modify applies the change operations to an existing entry.
	Modify(dn string, mods []Mod) error
	// ReadAttr returns one attribute's values for an entry (a base-object read), used
	// for the userAccountControl read-modify-write. Missing → an empty slice, no error.
	ReadAttr(dn, attr string) ([]string, error)
	// ModifyDN moves and/or renames an entry. In a directory those are one operation:
	// an entry's place in the tree *is* its name, so a mover is a DN change.
	ModifyDN(dn, newRDN, newSuperior string) error
	// Delete removes an entry. AD refuses to delete a container that still has
	// children, which is the behaviour a leaver process wants.
	Delete(dn string) error
	// DirSync reads what changed under a naming context since a cookie. It is the one
	// read this connector does — search stays with the generic LDAP connector — and it
	// is here because DirSync is Active Directory's own mechanism, not LDAP's.
	DirSync(req DirSyncRequest) (DirSyncResult, error)
	Close() error
}

Conn is a bound AD connection the worker operates over and then closes. It is an interface so the worker is testable without a live directory.

type Dialer

type Dialer interface {
	Dial(url, bindDN, bindPassword string, startTLS bool) (Conn, error)
}

Dialer opens and binds an AD connection. It is an interface so the worker is testable without a live server.

type DirSyncRequest

type DirSyncRequest struct {
	BaseDN     string
	Filter     string
	Attributes []string
	Cookie     []byte
	Flags      int64
	MaxEntries int32
}

DirSyncRequest asks Active Directory for everything that changed under a naming context since a cookie (MS-ADTS LDAP_SERVER_DIRSYNC_OID).

Cookie is opaque and belongs to the server: empty asks for a full pass, and the cookie a pass returns is the only correct input to the next one. Flags carries the DirSync flags; MaxEntries caps one pass, which costs nothing because a pass is resumable by construction.

type DirSyncResult

type DirSyncResult struct {
	Entries []Entry
	Cookie  []byte
	More    bool
}

DirSyncResult is one pass: the entries that changed, the cookie the next pass must present, and whether the server has more waiting right now.

type Entry

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

Entry is one directory entry a delta returns: its DN and its multi-valued attributes keyed by attribute name.

type GoDialer

type GoDialer struct{}

GoDialer dials a real AD server through github.com/go-ldap/ldap, bounded by the shared connector call budget (nettimeout.Default), like the LDAP connector.

func NewDialer

func NewDialer() GoDialer

NewDialer returns the production AD dialer.

func (GoDialer) Dial

func (GoDialer) Dial(rawURL, bindDN, bindPassword string, startTLS bool) (Conn, error)

type Job

type Job struct {
	URL        string `json:"url"`
	BindDN     string `json:"bindDN,omitempty"`
	BindSecret string `json:"bindSecretRef,omitempty"`
	StartTLS   bool   `json:"startTLS,omitempty"`
	Operation  string `json:"operation"`
	DN         string `json:"dn"`
	MemberDN   string `json:"memberDN,omitempty"`
	NewDN      string `json:"newDN,omitempty"`
	// NewPassword is the password a set-password writes. It is the operation's own
	// data and always was a process variable (ADR-0166), so it travels — and a worker
	// leasing the job already receives the task's variables regardless, which is why
	// carrying it here adds no exposure that the lease did not already have.
	NewPassword string `json:"newPassword,omitempty"`
	// Attributes is the resolved entry for create-user, create-group and
	// update-attributes.
	Attributes map[string][]string `json:"attributes,omitempty"`
	// The sync operation's own fields. Cookie is base64 of the server's opaque resume
	// token, because a process variable holds text and the token is binary.
	// CookieVariable is where the *new* cookie is written back, which is what lets a
	// reconciliation loop carry itself forward without any state in the connector.
	BaseDN         string `json:"baseDN,omitempty"`
	Filter         string `json:"filter,omitempty"`
	Cookie         string `json:"cookie,omitempty"`
	CookieVariable string `json:"cookieVariable,omitempty"`
	MaxEntries     int    `json:"maxEntries,omitempty"`
	ObjectSecurity bool   `json:"objectSecurity,omitempty"`
	ResultVariable string `json:"resultVariable,omitempty"`
}

Job is an AD task with everything the engine can evaluate already evaluated: the server, the operation, and its operands.

BindSecret is a *reference*, not a password — the same reference the model authored (ADR-0041). Whoever runs the job resolves it against whatever secret store it has.

func Resolve

func Resolve(store state.Reader, cp *compiler.CompiledProcess, detail *compiler.ConnectorTaskDetail, ei *model.ElementInstanceValue, elementInstanceKey uint64) (Job, error)

Resolve turns a compiled AD connector task into a Job: the authored values evaluated against the instance's variables, and the attribute object read out of the named variable. It is engine work by necessity — FEEL is compiled at deploy (ADR-0008/0015) and only the engine has the scope.

type MockDirectory added in v0.4.0

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

MockDirectory is an in-memory Active Directory for mock mode. It implements Dialer, so it drops into the worker exactly where GoDialer sits, and it is safe for concurrent use: one directory serves every job a worker leases.

func NewMockDirectory added in v0.4.0

func NewMockDirectory(seed ...Entry) *MockDirectory

NewMockDirectory returns an empty mock directory, or one holding the seed entries — the accounts a process expects to find already there. A worker seeds it from an LDIF or DSML file.

func (*MockDirectory) Dial added in v0.4.0

func (d *MockDirectory) Dial(rawURL, bindDN, bindPassword string, startTLS bool) (Conn, error)

Dial accepts a connection the way a domain controller would: the URL has to be an LDAP URL, and a simple bind that names a DN and sends no password is refused — that is what an unset ATLAS_CONNECTOR_<REF>_TOKEN looks like on the wire, and finding it here is cheaper than finding it against the real directory.

func (*MockDirectory) Entries added in v0.4.0

func (d *MockDirectory) Entries() []Entry

Entries returns the live entries, DN-sorted, as a snapshot. Tombstones are not among them: they are gone from the directory and present only in a delta.

func (*MockDirectory) Observe added in v0.4.0

func (d *MockDirectory) Observe(fn func(MockOperation))

Observe installs a callback run once per operation, after the directory's lock is released — so an observer that logs (which is what the worker's does) cannot deadlock the directory it is watching. Only one is held; a second replaces it.

func (*MockDirectory) Operations added in v0.4.0

func (d *MockDirectory) Operations() []MockOperation

Operations returns the newest operations the directory was asked to perform, oldest first.

type MockOperation added in v0.4.0

type MockOperation struct {
	Seq    uint64
	Op     string // bind, add, modify, modifydn, delete, dirsync
	DN     string
	Detail string
}

MockOperation is one thing the mock directory was asked to do. It is what a mockup run leaves behind: the worker logs each one, and MockDirectory.Operations holds the newest for a test to read.

Detail is a short human-readable summary — attribute names and values — with any password redacted.

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 the operation applies.

type ProcessLookup

type ProcessLookup func(defKey uint64) *compiler.CompiledProcess

ProcessLookup resolves a process-definition key to its compiled process.

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 AD task's bind-password *reference* into the 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