# RFC: StellaOps.Authority.Plugin.Ldap **Status:** Accepted – Auth Guild, Security Guild, DevEx Docs sign-off (2025-11-03) **Authors:** Plugin Team 4 (Auth Libraries & Identity Providers) **Related initiatives:** PLG7 backlog, CORE5 event handlers, DOC4 developer guide > Review log captured in `docs/notes/2025-11-03-authority-plugin-ldap-review.md`. Decisions below reflect consensus from Auth Guild, Security Guild, and DevEx Docs walkthroughs held on 2025-11-03. ## 0. Review Summary (2025-11-03) - Confirmed plugin charter: deliver offline-friendly LDAP credential verification with deterministic caching and audit parity with the Standard plugin. - Resolved open questions: - **Client provisioning audit trail:** plugin always mirrors credential lifecycle metadata (actor, timestamp, hashed secret reference) into Authority’s Mongo store even when LDAP writes succeed; directory writes remain optional and secrets never persist locally. - **Mutual TLS support:** LDAPS client certificates are required for regulated installations; configuration gains optional client certificate bindings with secret-provider integration and deterministic trust-store selection. - **Group-to-role mapping:** mappings accept static DN dictionaries and deterministic `regex` matchers; regex captures project to canonical roles via substitution (documented contract for policy automation). - Follow-up implementation issues filed in `StellaOps.Authority.Plugin.Standard/TASKS.md` (see Section 11) to track scaffolding, mutual TLS enablement, audit mirror, and mapping enhancements. ## 1. Problem Statement Many on-prem StellaOps deployments rely on existing LDAP/Active Directory domains for workforce identity. The current Standard Mongo-backed plugin requires duplicating users and secrets, which increases operational overhead and violates corporate policy in some regulated environments. We need a sovereign, offline-friendly LDAP plugin that: - Supports password grant and bootstrap provisioning flows without storing credentials in Mongo. - Enforces StellaOps security policies (lockout, password policy hints, audit logging) while delegating credential validation to LDAP. - Operates deterministically in offline or partially connected environments by caching directory metadata when necessary. ## 2. Goals - Provide a first-party `StellaOps.Authority.Plugin.Ldap` plugin advertising `password` and optional `clientProvisioning` capabilities at launch. - Support username/password authentication against LDAP bind operations with configurable DN templates. - Allow optional bootstrap seeding of service accounts by writing into LDAP (guarded behind explicit configuration) or by mapping to pre-existing entries. - Surface directory-derived claims (groups, attributes) for downstream authorization via `IClaimsEnricher`. - Integrate with Authority lockout telemetry and structured logging without persisting secrets locally. ## 3. Non-Goals - Implement multi-factor authentication out of the box (future enhancement once TOTP/WebAuthn strategy is finalised). - Provide write-heavy directory management (e.g., user creation workflows) beyond optional bootstrap service account seeding. - Replace the Standard plugin; both must remain supported and selectable per environment. ## 4. Key Constraints & Assumptions - Offline-first posture: deployments may operate without outbound internet and with intermittent directory connectivity (e.g., read-only replicas). The plugin must tolerate transient LDAP connectivity failures and degrade gracefully. - Deterministic behaviour: identical configuration and directory state must yield identical token issuance results. Cached metadata (e.g., group lookups) must have defined expiration. - Security: No plaintext credential storage; TLS must be enforced for LDAP connections unless explicitly overridden for air-gapped lab environments. ## 5. High-Level Architecture 1. **Configuration binding** (`ldap.yaml`): defines server endpoints, bind strategy, claim mapping, and optional bootstrap overrides. 2. **Connection factory**: pooled LDAP connections using a resilient client (preferred dependency: `Novell.Directory.Ldap.NETStandard`). 3. **Credential validator** (`IUserCredentialStore`): performs bind-as-user flow with optional fallback bind using service account when directories disallow anonymous search. 4. **Claims enricher** (`IClaimsEnricher`): queries group membership/attributes and projects them into canonical roles/claims. 5. **Optional client provisioning** (`IClientProvisioningStore`): maintains machine/service principals either in Mongo (metadata) or via LDAP `serviceConnectionPoint` entries based on configuration. 6. **Health checks**: periodic LDAP `whoami` or `search` probes surfaced through `AuthorityPluginHealthResult`. ``` Authority Host ├── Plugin Manifest (ldap) ├── Registrar → registers ConnectionFactory, LdapCredentialStore, LdapClaimsEnricher ├── Password Grant Handler → CredentialStore.VerifyPasswordAsync → LDAP Bind └── Claims Pipeline → ClaimsEnricher.EnrichAsync → LDAP group lookup ``` ## 6. Configuration Schema (Draft) ```yaml connection: host: "ldaps://ldap.example.internal" port: 636 useStartTls: false validateCertificates: true clientCertificate: pfxPath: "file:/etc/stellaops/certs/ldap-client.pfx" passwordSecret: "file:/etc/stellaops/secrets/ldap-client-pfx.txt" sendChain: true trustStore: mode: "system" # system | bundle bundlePath: "file:/etc/stellaops/trust/ldap-root.pem" bindDn: "cn=stellaops-bind,ou=service,dc=example,dc=internal" bindPasswordSecret: "file:/etc/stellaops/secrets/ldap-bind.txt" searchBase: "dc=example,dc=internal" usernameAttribute: "uid" userDnFormat: "uid={username},ou=people,dc=example,dc=internal" # optional template security: requireTls: true allowInsecureWithEnvToggle: false # requires STELLAOPS_LDAP_ALLOW_INSECURE=true allowedCipherSuites: [] # optional allow-list referralChasing: false lockout: useAuthorityPolicies: true # reuse Authority lockout counters directoryLockoutAttribute: "pwdAccountLockedTime" claims: groupAttribute: "memberOf" groupToRoleMap: "cn=stellaops-admins,ou=groups,dc=example,dc=internal": "operators" "cn=stellaops-read,ou=groups,dc=example,dc=internal": "auditors" regexMappings: - pattern: "^cn=stellaops-(?P[a-z-]+),ou=groups,dc=example,dc=internal$" roleFormat: "{role}" # yields operators/investigate/etc. extraAttributes: displayName: "displayName" email: "mail" clientProvisioning: enabled: false containerDn: "ou=service,dc=example,dc=internal" secretAttribute: "userPassword" auditMirror: enabled: true collectionName: "ldap_client_provisioning" health: probeIntervalSeconds: 60 timeoutSeconds: 5 ``` ## 7. Capability Mapping | Capability | Implementation Notes | |------------|---------------------| | `password` | Bind-as-user validation with Authority lockout integration. Mandatory. Requires TLS and optionally client certificate binding per regulated install posture. | | `clientProvisioning` | Optional; when enabled, creates/updates LDAP entries for machine clients **and** mirrors lifecycle metadata into Mongo for audit parity. | | `bootstrap` | Exposed only when bootstrap manifest provides service account credentials AND directory write permissions are confirmed during startup; always records audit mirror entries. | | `mfa` | Not supported in MVP. Future iteration may integrate TOTP attributes or external MFA providers. | ## 8. Operational Considerations - **Offline cache:** provide optional Mongo cache for group membership to keep `/ready` responsive if LDAP is temporarily unreachable. Cache entries must include TTL and invalidation hooks. - **Secrets management:** accept `file:` and environment variable references; integrate with existing `StellaOps.Configuration` secret providers. - **Mutual TLS & trust anchors:** support client certificate authentication with deterministic trust-store selection (`system` vs bundled file) to satisfy regulated deployments; surface validation outcomes via health endpoints. - **Audit mirror:** write deterministic Mongo records capturing provisioning operations (actor, LDAP DN, operation type, hashed secret reference) to align with Authority audit policy even when LDAP is authoritative. - **Observability:** emit structured logs with event IDs (`LDAP_BIND_START`, `LDAP_BIND_FAILURE`, `LDAP_GROUP_LOOKUP`), counters for success/failure, and latency histograms. - **Throttling:** reuse Authority rate-limiting middleware; add per-connection throttles to avoid saturating directory servers during brute-force attacks. ## 9. Security & Compliance - Enforce TLS (`ldaps://` or STARTTLS) by default. Provide explicit `allowInsecure` flag gated behind environment variable for lab/testing only. - Support optional mutual TLS (client cert authentication) with secret-backed PFX loader and deterministic trust bundle selection. - Support password hash migration by detecting directory lockout attributes and surfacing `RequiresPasswordReset` when policies demand changes. - Log distinguished names only at `Debug` level to avoid leaking sensitive structure in default logs. - Coordinate with Security Guild for penetration testing before GA; incorporate audit log entries for bind attempts and provisioning changes. ## 10. Testing Strategy - **Unit tests:** mock LDAP connections to validate DN formatting, error mapping, and capability negotiation. - **Integration tests:** run against an ephemeral OpenLDAP container (seeded via LDIF fixtures) within CI. Include mutual TLS handshake verification (valid/expired certs) and offline cache regression (disconnect LDAP mid-test). - **Determinism tests:** feed identical LDIF snapshots and configuration to ensure output tokens/claims remain stable across runs. - **Smoke tests:** `dotnet test` harness plus manual `dotnet run` scenario verifying `/token` password grants, `/internal/users` bootstrap with LDAP-backed store, and Mongo audit mirror entries. ## 11. Implementation Plan 1. Scaffold `StellaOps.Authority.Plugin.Ldap` project + companion test project (net10.0, `` true). 2. Implement configuration binding/validation, including secret-backed client certificate + trust-store options and `allowInsecureWithEnvToggle`. 3. Build connection factory + credential store with bind logic, TLS enforcement, and deterministic retry policies. 4. Implement claims enricher with regex mapping support and optional Mongo-backed cache layer. 5. Add client provisioning store with LDAP write toggles and Mongo audit mirror (`ldap_client_provisioning` collection). 6. Wire health checks, telemetry, and structured audit events (bind attempts, provisioning, cache fallbacks). 7. Deliver bootstrap validation that inspects directory permissions and logs deterministic capability summary. 8. Extend developer guide and samples with LDAP configuration guidance; include mutual TLS and regex mapping examples. 9. Update docs/TASKS trackers and release notes entry; ensure CI coverage (unit, integration with OpenLDAP, determinism, smoke tests). ## 12. Resolved Questions - **Audit mirror:** Client provisioning always persists lifecycle metadata in Mongo for audit parity; LDAP remains the credential source of truth. - **Mutual TLS:** Plugin must support optional client certificate authentication with secret-backed key material and deterministic trust-store selection. - **Group mapping:** Provide deterministic regex mapping support to translate directory DNs into Authority roles/scopes without custom scripts. ## 13. Timeline (Tentative) - **Week 1:** RFC review & sign-off. - **Week 2-3:** Implementation & unit tests. - **Week 4:** Integration tests + documentation updates. - **Week 5:** Security review, release candidate packaging. ## 14. Approval - **Auth Guild Lead:** ✅ Approved 2025-11-03 (see review log). - **Security Guild Representative:** ✅ Approved 2025-11-03 (see review log). - **DevEx Docs:** ✅ Approved 2025-11-03 (see review log). --- Please add comments inline or via PR review. Once approved, track execution under PLG7.