README
¶
OpenLDAP Operator
A Kubernetes operator for managing external OpenLDAP instances, users, groups, and Access Control Lists (ACLs).
Overview
The OpenLDAP Operator allows you to:
- Connect to and manage external LDAP servers
- Create and manage LDAP users with references to specific servers
- Manage groups and group memberships
- Configure ACLs for search users
- Monitor connection status to LDAP servers
Features
- Namespaced Resources: All custom resources are namespaced for multi-tenancy
- Connection Management: Automatic connection monitoring and status reporting
- User Management: Create, update, and delete LDAP users with POSIX support
- Automatic Home Directories: Auto-generates
/home/<username>if not specified for POSIX accounts - Group Management: Manage LDAP groups (posixGroup, groupOfNames, groupOfUniqueNames) with membership via LDAPUser resources
- ACL Support: Configure search users with appropriate permissions
- Status Tracking: Real-time status updates for all managed resources
- TLS Support: Secure connections with configurable TLS settings (enabled by default)
- Comprehensive Testing: 90.6% test coverage with Docker-based integration tests
- Production Ready: Robust error handling and connection management
Custom Resources
LDAPServer
Represents an external LDAP server connection with status monitoring.
apiVersion: openldap.guided-traffic.com/v1
kind: LDAPServer
metadata:
name: my-ldap-server
namespace: default
spec:
host: ldap.example.com
port: 389
bindDN: "cn=admin,dc=example,dc=com"
bindPasswordSecret:
name: ldap-admin-secret
key: password
baseDN: "dc=example,dc=com"
tls:
enabled: false
status:
connectionStatus: Connected
lastChecked: "2023-08-26T10:00:00Z"
conditions: []
LDAPUser
Represents an LDAP user with reference to a specific LDAP server. Includes automatic home directory configuration for POSIX accounts.
apiVersion: openldap.guided-traffic.com/v1
kind: LDAPUser
metadata:
name: john-doe
namespace: default
spec:
ldapServerRef:
name: my-ldap-server
username: johndoe
email: john.doe@example.com
firstName: John
lastName: Doe
groups:
- developers
- users
# homeDirectory: /home/johndoe # Optional - auto-generated if not specified
userID: 1001
groupID: 1000
status:
phase: Ready
actualHomeDirectory: /home/johndoe # Shows the actual home directory used
conditions: []
Note: If homeDirectory is not specified or empty, it will automatically be set to /home/<username> to ensure POSIX compliance.
LDAPGroup
Represents an LDAP group with reference to a specific LDAP server. Group membership is managed through the groups field in LDAPUser resources.
apiVersion: openldap.guided-traffic.com/v1
kind: LDAPGroup
metadata:
name: developers
namespace: default
spec:
ldapServerRef:
name: my-ldap-server
groupName: developers
description: Development team
organizationalUnit: groups
groupType: posixGroup
groupID: 2001
# Note: Group membership is managed via LDAPUser.spec.groups
status:
phase: Ready
members: # List of current members (read-only)
- johndoe
- janedoe
memberCount: 2
conditions: []
Application Integration with Search Users
Creating a Search User for Application Access
Many applications need to connect to LDAP for authentication and user lookups. This requires a dedicated "search user" with read-only permissions. Here's how to create one using the OpenLDAP Operator:
1. Create a Search User
apiVersion: openldap.guided-traffic.com/v1
kind: LDAPUser
metadata:
name: app-searchuser
namespace: myapp-namespace
spec:
ldapServerRef:
name: my-ldap-server
username: searchuser
firstName: Application
lastName: SearchUser
email: noreply@mycompany.com
organizationalUnit: service-accounts
# POSIX attributes for proper access
userID: 9001
groupID: 9001
homeDirectory: /var/lib/searchuser
loginShell: /bin/false # No shell access
# Store password in a secret
passwordSecret:
name: searchuser-credentials
key: password
additionalAttributes:
description: ["Search user for application integration"]
employeeType: ["service-account"]
2. Create the Password Secret
apiVersion: v1
kind: Secret
metadata:
name: searchuser-credentials
namespace: myapp-namespace
type: Opaque
data:
password: <base64-encoded-strong-password>
Or create it via kubectl:
kubectl create secret generic searchuser-credentials \
--from-literal=password='YourStrongPassword123!' \
-n myapp-namespace
3. Create a Read-Only Group for Search Users
apiVersion: openldap.guided-traffic.com/v1
kind: LDAPGroup
metadata:
name: search-users
namespace: myapp-namespace
spec:
ldapServerRef:
name: my-ldap-server
groupName: search-users
description: Read-only users for application integration
organizationalUnit: groups
groupType: posixGroup
groupID: 9001
# Note: Add the searchuser to this group by updating the LDAPUser resource
Then update your search user to include this group:
# Update the LDAPUser to include group membership
apiVersion: openldap.guided-traffic.com/v1
kind: LDAPUser
metadata:
name: app-searchuser
namespace: myapp-namespace
spec:
# ... existing spec fields ...
groups:
- search-users # Add the user to the search-users group
4. Application Configuration
Configure your application to use the search user:
Java/Spring Application (application.yml):
spring:
ldap:
urls: ldap://ldap.example.com:389
base: dc=example,dc=com
username: cn=searchuser,ou=service-accounts,dc=example,dc=com
password: ${LDAP_SEARCH_PASSWORD}
user-search-base: ou=users
user-search-filter: (uid={0})
group-search-base: ou=groups
group-search-filter: member={0}
Python/Django Application (settings.py):
import ldap
from django_auth_ldap.config import LDAPSearch, PosixGroupType
AUTH_LDAP_SERVER_URI = "ldap://ldap.example.com:389"
AUTH_LDAP_BIND_DN = "cn=searchuser,ou=service-accounts,dc=example,dc=com"
AUTH_LDAP_BIND_PASSWORD = os.environ.get('LDAP_SEARCH_PASSWORD')
AUTH_LDAP_USER_SEARCH = LDAPSearch(
"ou=users,dc=example,dc=com",
ldap.SCOPE_SUBTREE,
"(uid=%(user)s)"
)
AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
"ou=groups,dc=example,dc=com",
ldap.SCOPE_SUBTREE,
"(objectClass=posixGroup)"
)
Node.js/Passport Application:
const LdapStrategy = require('passport-ldapauth');
passport.use(new LdapStrategy({
server: {
url: 'ldap://ldap.example.com:389',
bindDN: 'cn=searchuser,ou=service-accounts,dc=example,dc=com',
bindCredentials: process.env.LDAP_SEARCH_PASSWORD,
searchBase: 'ou=users,dc=example,dc=com',
searchFilter: '(uid={{username}})',
searchAttributes: ['uid', 'cn', 'mail', 'memberOf']
}
}));
5. LDAP ACL Configuration (Manual Setup)
To properly restrict the search user, configure ACLs on your LDAP server:
# Read-only access for search users
dn: olcDatabase={1}mdb,cn=config
changetype: modify
add: olcAccess
olcAccess: {2}to dn.subtree="ou=users,dc=example,dc=com"
by dn.exact="cn=searchuser,ou=service-accounts,dc=example,dc=com" read
by anonymous none
by * none
olcAccess: {3}to dn.subtree="ou=groups,dc=example,dc=com"
by dn.exact="cn=searchuser,ou=service-accounts,dc=example,dc=com" read
by anonymous none
by * none
6. Connection Testing
Test the search user connection:
# Test basic authentication
ldapwhoami -x -H ldap://ldap.example.com:389 \
-D "cn=searchuser,ou=service-accounts,dc=example,dc=com" \
-w "YourStrongPassword123!"
# Test user search
ldapsearch -x -H ldap://ldap.example.com:389 \
-D "cn=searchuser,ou=service-accounts,dc=example,dc=com" \
-w "YourStrongPassword123!" \
-b "ou=users,dc=example,dc=com" \
"(uid=johndoe)"
# Test group search
ldapsearch -x -H ldap://ldap.example.com:389 \
-D "cn=searchuser,ou=service-accounts,dc=example,dc=com" \
-w "YourStrongPassword123!" \
-b "ou=groups,dc=example,dc=com" \
"(cn=developers)"
7. Security Best Practices
Password Management:
- Use strong, generated passwords for search users
- Store passwords in Kubernetes secrets with proper RBAC
- Rotate passwords regularly
Access Control:
- Create dedicated organizational units for service accounts
- Use LDAP ACLs to restrict search user permissions to read-only
- Limit search scope to necessary OUs only
Monitoring:
- Monitor search user authentication attempts
- Set up alerts for failed authentication
- Regular audit of search user permissions
Application Configuration:
- Use environment variables for sensitive data
- Enable connection pooling for performance
- Implement proper error handling and retry logic
- Use TLS/LDAPS for production environments
8. Complete Example
Here's a complete example for setting up a search user for a web application:
# 1. Create namespace
kubectl create namespace mywebapp
# 2. Create LDAP server resource
cat <<EOF | kubectl apply -f -
apiVersion: openldap.guided-traffic.com/v1
kind: LDAPServer
metadata:
name: company-ldap
namespace: mywebapp
spec:
host: ldap.company.com
port: 389
bindDN: "cn=admin,dc=company,dc=com"
bindPasswordSecret:
name: ldap-admin-secret
key: password
baseDN: "dc=company,dc=com"
tls:
enabled: false
EOF
# 3. Create search user password
kubectl create secret generic searchuser-password \
--from-literal=password='SecurePassword123!' \
-n mywebapp
# 4. Create search user
cat <<EOF | kubectl apply -f -
apiVersion: openldap.guided-traffic.com/v1
kind: LDAPUser
metadata:
name: webapp-searchuser
namespace: mywebapp
spec:
ldapServerRef:
name: company-ldap
username: webapp-search
firstName: WebApp
lastName: SearchUser
email: webapp-search@company.com
organizationalUnit: service-accounts
userID: 9100
groupID: 9100
homeDirectory: /var/lib/webapp-search
loginShell: /bin/false
passwordSecret:
name: searchuser-password
key: password
additionalAttributes:
description: ["Search user for web application LDAP integration"]
employeeType: ["service-account"]
departmentNumber: ["IT"]
EOF
# 5. Verify creation
kubectl get ldapuser webapp-searchuser -n mywebapp
kubectl describe ldapuser webapp-searchuser -n mywebapp
This setup provides a secure, auditable way to create and manage search users for application LDAP integration using the OpenLDAP Operator.
Installation
Installation
The OpenLDAP Operator can be installed using Helm or traditional Kubernetes manifests.
Using Helm (Recommended)
# Add the Helm repository
helm repo add openldap-operator https://guided-traffic.github.io/openldap-operator/
helm repo update
# Install the operator
helm install openldap-operator openldap-operator/openldap-operator
# Or install from source (if needed)
git clone https://github.com/guided-traffic/openldap-operator.git
cd openldap-operator
helm install openldap-operator deploy/helm/openldap-operator
For detailed Helm installation options and configuration, see the Helm Installation Guide.
Using Kustomize
# Install CRDs and operator
make deploy IMG=openldap-operator:latest
Using kubectl
# Apply all manifests
kubectl apply -f config/crd/bases/
kubectl apply -f config/rbac/
kubectl apply -f config/manager/
Quick Start
- Install the CRDs:
kubectl apply -f config/crd/bases/
- Deploy the operator:
kubectl apply -f config/manager/
- Test the installation:
# Run unit tests
go test ./internal/ldap/... -short
# Run integration tests (requires Docker)
go test ./internal/ldap/... -v
# Check coverage
go test -coverprofile=coverage.out ./internal/ldap/...
go tool cover -func=coverage.out
Development
Prerequisites
- Go 1.21+
- Kubernetes cluster (local or remote)
- kubectl configured
- Docker (for integration tests)
Building
# Build the operator binary
make build
# Build Docker image
make docker-build
# Run linting and formatting
make lint
make fmt
Running locally
# Run the operator locally (against configured cluster)
make run
# Run with debug logging
make run ARGS="--log-level=debug"
Testing Workflow
# 1. Run unit tests first
go test ./... -short -v
# 2. Run integration tests (requires Docker)
go test ./internal/ldap/... -v
# 3. Check coverage
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html
# 4. View coverage report in browser
open coverage.html
Testing
The project includes comprehensive unit and integration tests with 90.6% test coverage and real LDAP integration:
# Run all tests (unit + integration)
make test-all
# Run unit tests only
make test-unit
# Run integration tests only (requires Docker)
make test-integration
# Run all tests with coverage report
make test-coverage
# Direct Go test commands
go test ./api/... ./internal/controller/... -v # Unit tests
go test ./internal/ldap/... -v # Integration tests
Docker-based Integration Tests
The project includes sophisticated Docker-based integration tests that use a real OpenLDAP server:
- Real LDAP Server: Uses
osixia/openldap:1.5.0for authentic testing - Automatic Setup/Teardown: Container lifecycle managed automatically
- Complete CRUD Testing: Users, groups, and membership operations
- Error Handling: Network failures, duplicate entries, and edge cases
- POSIX Support: Full testing of POSIX account and group attributes
Integration Test Features:
- ✅ Real LDAP server connection and authentication
- ✅ User creation with POSIX attributes (
uidNumber,gidNumber) - ✅ Group management (posixGroup, groupOfNames, groupOfUniqueNames)
- ✅ Group membership operations (add/remove users)
- ✅ Search operations with filters and attributes
- ✅ Error handling and duplicate detection
- ✅ TLS connection testing
- ✅ Organizational Unit (OU) management
Code Coverage
Current Test Coverage: 90.6% (Target: 80% - EXCEEDED!)
Coverage Breakdown by Package:
- LDAP Package: 90.6% coverage
- User Operations: 100%
- Group Operations: 94.4%
- Connection Management: 81.2%
- Search Functions: 100%
- Helper Functions: 100%
- API Package: 47.1% coverage
- Controller Package: 36.8% coverage
The coverage analysis includes:
- Detailed Function Coverage: Per-function coverage reports
- HTML Reports: Visual coverage analysis
- CI/CD Integration: Automated coverage tracking
- Coverage Goals: 80%+ target for critical packages
Test Environment Requirements
For Unit Tests:
- Go 1.21+
- No external dependencies
For Integration Tests:
- Docker (for LDAP container)
- Network connectivity
- 2+ minutes for full integration suite
Skipping Docker Tests: Integration tests automatically skip if Docker is unavailable, ensuring CI/CD compatibility.
Test Coverage:
- ✅ API type validation and defaults (SetDefaults, DeepCopy)
- ✅ LDAP client functionality with real server integration
- ✅ Controller reconciliation logic and error handling
- ✅ Connection management and authentication
- ✅ Real CRUD operations (Create, Read, Update, Delete)
- ✅ Group membership management
- ✅ Search and filter operations
- ✅ Error scenarios and edge cases
- ✅ POSIX attribute handling
- ✅ TLS connection support
Contributing
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests
- Submit a pull request
License
This project is licensed under the Apache License 2.0. See LICENSE.md for details.
Copyright 2024 Hans Fischer
Directories
¶
| Path | Synopsis |
|---|---|
|
api
|
|
|
v1
Package v1 contains API Schema definitions for the openldap v1 API group +kubebuilder:object:generate=true +groupName=openldap.guided-traffic.com
|
Package v1 contains API Schema definitions for the openldap v1 API group +kubebuilder:object:generate=true +groupName=openldap.guided-traffic.com |
|
internal
|
|
|
test
|
|
|
integration/runner
command
|