Introduction
lldap-operator is a Kubernetes operator that manages lldap users and groups as native Kubernetes resources via Custom Resource Definitions (CRDs).
Note: This project is in early development. APIs are not yet stable.
Features
- Declare lldap users and groups as Kubernetes CRDs
- Reconcile desired state with lldap via its GraphQL API
- Password management through Kubernetes Secret references
- Standard operator patterns: status conditions, finalizers, observed generation
Installation
Prerequisites
Quick Start
helm repo add lldap-operator \
https://lukidoescode.github.io/lldap-operator
helm install lldap-operator \
lldap-operator/lldap-operator
Configuration
See the Configuration Reference for all available Helm values.
Configuration Reference
Helm Values
Operator Environment Variables
CRD Reference
The lldap-operator manages the following Custom Resource Definitions:
| Kind | Description | Status |
|---|---|---|
LldapUser | Manages lldap user accounts | Schema only |
LldapGroup | Manages lldap groups | Schema only |
LldapMembership | Manages group membership relationships | Schema only |
LldapAttributeSchema | Manages custom attribute schemas | Schema only |
All resources use the API group
lldap-operator.lukidoescode.com/v1alpha1.
LldapUser
An LldapUser resource declares an lldap user account managed by the operator.
Each resource must carry the lldap-operator.lukidoescode.com/lldap-instance
label whose value selects which lldap instance the user belongs to. The operator
ignores resources without this label.
Example
apiVersion: lldap-operator.lukidoescode.com/v1alpha1
kind: LldapUser
metadata:
name: alice
labels:
lldap-operator.lukidoescode.com/lldap-instance: default
spec:
username: "alice.smith"
email: "alice.smith@example.com"
displayName: "Alice Smith"
firstName: "Alice"
lastName: "Smith"
passwordPolicy: Manage
passwordSecretRef:
name: alice-password
key: password
attributes:
- name: department
value:
- "Engineering"
---
apiVersion: v1
kind: Secret
metadata:
name: alice-password
type: Opaque
stringData:
password: "change-me-on-first-login"
Spec Fields
| Field | Type | Required | Description |
|---|---|---|---|
username | string | yes | LDAP username (1–64 chars, [a-zA-Z0-9._-]) |
email | string | yes | Email address |
displayName | string | no | Display name |
firstName | string | no | First name |
lastName | string | no | Last name |
attributes | AttributeValue[] | no | Custom attribute values; each requires a corresponding LldapAttributeSchema |
passwordPolicy | string | no | One of Manage (default), InitialOnly, Ignore |
passwordSecretRef | object | no | Reference to a Secret containing the password |
passwordSecretRef.name | string | yes (when set) | Name of the Secret |
passwordSecretRef.key | string | yes (when set) | Key within the Secret containing the password |
Validation Rules
The following rules are enforced at admission time by the Kubernetes API server via CRD-embedded CEL expressions (requires Kubernetes 1.25+).
-
Instance label required — rejected with:
metadata.labels must include 'lldap-operator.lukidoescode.com/lldap-instance' with a non-empty valueThe label must be set to a non-empty string identifying which lldap instance owns this user.
-
Password secret required for managed policies — rejected with:
spec.passwordSecretRef is required when spec.passwordPolicy is 'Manage' or 'InitialOnly' (only 'Ignore' permits omitting it)When
passwordPolicyisManage(the default) orInitialOnly, the operator needs a Secret reference to source the password from. OnlyIgnorepermits omittingpasswordSecretRef.
Group memberships are not declared on LldapUser. Use LldapMembership to
attach users to groups.
Status
| Field | Type | Description |
|---|---|---|
uuid | string | User UUID assigned by lldap |
passwordHash | string | Hash of the last reconciled password, used to detect Secret changes |
observedGeneration | int64 | Last observed resource generation |
conditions | Condition[] | Standard Kubernetes conditions |
LldapGroup
An LldapGroup resource declares an lldap group managed by the operator.
Each resource must carry the lldap-operator.lukidoescode.com/lldap-instance
label whose value selects which lldap instance the group belongs to.
Example
apiVersion: lldap-operator.lukidoescode.com/v1alpha1
kind: LldapGroup
metadata:
name: engineering
labels:
lldap-operator.lukidoescode.com/lldap-instance: default
spec:
displayName: "Engineering"
attributes:
- name: costCenter
value:
- "1234"
Spec Fields
| Field | Type | Required | Description |
|---|---|---|---|
displayName | string | yes | Group display name (1–128 chars) |
attributes | AttributeValue[] | no | Custom attribute values; each requires a corresponding LldapAttributeSchema with target: Group |
Status
| Field | Type | Description |
|---|---|---|
groupId | integer | Group ID assigned by lldap |
uuid | string | Group UUID assigned by lldap |
observedGeneration | int64 | Last observed resource generation |
conditions | Condition[] | Standard Kubernetes conditions |
LldapMembership
An LldapMembership resource binds an lldap user to an lldap group.
Each resource must carry the lldap-operator.lukidoescode.com/lldap-instance
label whose value selects which lldap instance the membership applies to.
The userName and groupName fields reference the lldap-side identifiers
(the spec.username of an LldapUser and the spec.displayName of an
LldapGroup), not Kubernetes metadata.name values.
Example
apiVersion: lldap-operator.lukidoescode.com/v1alpha1
kind: LldapMembership
metadata:
name: alice-engineering
labels:
lldap-operator.lukidoescode.com/lldap-instance: default
spec:
userName: "alice.smith"
groupName: "Engineering"
Spec Fields
| Field | Type | Required | Description |
|---|---|---|---|
userName | string | yes | lldap username (1–64 chars) |
groupName | string | yes | lldap group display name (1–128 chars) |
Validation Rules
Admission-time validation (CEL)
-
Instance label required — rejected with:
metadata.labels must include 'lldap-operator.lukidoescode.com/lldap-instance' with a non-empty valueThe label must be set to a non-empty string identifying which lldap instance the membership belongs to.
Reconcile-time validation (reference checks)
The reconciler verifies cross-resource references before applying a membership:
- The referenced
LldapUser(matched byspec.usernameequal to this resource’sspec.userName) must exist in the same namespace as the membership. - The referenced
LldapGroup(matched byspec.displayNameequal to this resource’sspec.groupName) must exist in the same namespace as the membership. - All three resources (user, group, membership) must carry the same
lldap-operator.lukidoescode.com/lldap-instancelabel value.
On failure the reconciler returns a hard error and the membership’s
Ready condition is set to False with one of these reasons:
UserNotFound, GroupNotFound, MissingNamespace, MissingInstanceLabel.
Note: the reconciler is not yet wired up. The validation function
(validate_membership_references) is implemented in lldap-operator-reconciler
and will be invoked once the reconciler is built.
Status
| Field | Type | Description |
|---|---|---|
observedGeneration | int64 | Last observed resource generation |
conditions | Condition[] | Standard Kubernetes conditions |
LldapAttributeSchema
An LldapAttributeSchema resource declares a custom attribute on either the
user or group entity in lldap.
Each resource must carry the lldap-operator.lukidoescode.com/lldap-instance
label whose value selects which lldap instance owns the attribute schema.
lldap does not support mutating an attribute schema after creation: the attribute can only be added or deleted. Updates to a schema’s type or flags require deleting and recreating the resource. Validation enforcing this constraint is tracked separately.
Example
apiVersion: lldap-operator.lukidoescode.com/v1alpha1
kind: LldapAttributeSchema
metadata:
name: department
labels:
lldap-operator.lukidoescode.com/lldap-instance: default
spec:
attributeName: "department"
attributeType: "String"
target: "User"
isList: false
isVisible: true
isEditable: true
Spec Fields
| Field | Type | Required | Description |
|---|---|---|---|
attributeName | string | yes | Attribute identifier (1–64 chars, [a-zA-Z][a-zA-Z0-9_]*) |
attributeType | string | yes | One of String, Integer, JpegPhoto, DateTime |
target | string | yes | One of User, Group |
isList | bool | no | Whether the attribute holds multiple values |
isVisible | bool | no | Whether the attribute is visible to LDAP clients |
isEditable | bool | no | Whether the attribute is editable through the lldap UI |
Status
| Field | Type | Description |
|---|---|---|
observedGeneration | int64 | Last observed resource generation |
conditions | Condition[] | Standard Kubernetes conditions |
Multi-Instance Deployment
Password Management
Troubleshooting
Architecture
This chapter will describe the architecture of how the LLDAP operator fits into the eco system of a Kubernetes deployment and how to employ it with Kyverno and OPA/Gatekeeper.
Development Guide
This chapter is for contributors who want to build and test the operator locally.
Prerequisites
Local Setup
./scripts/local-dev-setup.sh
This creates a kind cluster, deploys an lldap test instance, and installs the CRDs.
Building
cargo build --workspace
Running Tests
cargo test --workspace
Code Quality
Before submitting changes, ensure the following pass:
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace