Release v0.1.0
- Initial release of LDAP Docker development tool - Full .env configuration support with comprehensive documentation - Pre-configured test users and SSL/TLS support - Consolidated documentation in README
This commit is contained in:
87
CHANGELOG.md
87
CHANGELOG.md
@@ -5,90 +5,135 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.1.0] - 2025-01-XX
|
||||
## [0.1.0] - 2025-10-20
|
||||
|
||||
### Added
|
||||
|
||||
- Initial release of LDAP Docker development tool
|
||||
- OpenLDAP 1.5.0 container with SSL/TLS support
|
||||
- phpLDAPadmin web interface for easy administration
|
||||
- Pre-configured test users and groups for testing.local domain
|
||||
- SSL certificate generation script using Python cryptography
|
||||
- Comprehensive CLI tool for managing LDAP server (`ldap-docker` command)
|
||||
- Makefile with convenient shortcuts for common operations
|
||||
- Interactive quickstart script (`quickstart.sh`) for guided setup
|
||||
- Example Python authentication script demonstrating LDAP integration
|
||||
- Example Python authentication script demonstrating LDAP integration with environment variable support
|
||||
- Support for custom dev-ca certificates
|
||||
- Persistent Docker volumes for data and configuration
|
||||
- Test suite for certificate generation
|
||||
- Comprehensive environment variable reference table in Configuration section with usage cross-references
|
||||
- `{.env:VARIABLE_NAME}` syntax throughout documentation to indicate configurable values
|
||||
- Explanatory notes about `.env` configuration flexibility at key sections
|
||||
- Comprehensive documentation:
|
||||
- README.md - Full project documentation
|
||||
- GETTING_STARTED.md - Beginner-friendly guide
|
||||
- QUICKREF.md - Quick command reference
|
||||
- README.md - Full project documentation with integrated configuration guide
|
||||
- certs/README.md - Certificate management guide
|
||||
- examples/README.md - Integration patterns and examples
|
||||
- examples/README.md - Integration patterns and code examples
|
||||
|
||||
### Changed
|
||||
|
||||
- Renamed Makefile targets from `test-*` to `verify-*` for integration checks that require a running container
|
||||
- `test-connection` → `verify-connection`
|
||||
- `test-auth` → `verify-auth`
|
||||
- `test-users` → `verify-users`
|
||||
- `test-ssl` → `verify-ssl`
|
||||
- `test-all` → `verify-all`
|
||||
- Added separate `make test` and `make test-cov` targets for running unit tests with pytest
|
||||
- Improved naming clarity: "verify" commands check running containers, "test" commands run unit tests
|
||||
|
||||
### Test Data
|
||||
|
||||
- 4 pre-configured test users (admin, jdoe, jsmith, testuser)
|
||||
- 3 test groups (admins, developers, users)
|
||||
- All test users use password: `password123`
|
||||
- Admin credentials: `cn=admin,dc=testing,dc=local` / `admin_password`
|
||||
|
||||
### Infrastructure
|
||||
|
||||
- Docker Compose configuration for easy deployment
|
||||
- UV package manager integration for Python dependencies
|
||||
- `.env` file support for configuration
|
||||
- Cross-platform support (MacOS, Linux, Windows)
|
||||
- Rancher Desktop and Docker Desktop compatibility
|
||||
|
||||
### Configuration
|
||||
|
||||
- Environment variable support for all configurable values
|
||||
- `.env.example` file with comprehensive documentation
|
||||
- Configurable ports (LDAP, LDAPS, phpLDAPadmin)
|
||||
- Configurable domain and organization settings
|
||||
- Configurable SSL/TLS certificate filenames
|
||||
- Configurable admin credentials
|
||||
|
||||
### Documentation
|
||||
|
||||
- README with clear Quick Start, Configuration, and Troubleshooting sections
|
||||
- All code examples (Python, bash, ldapsearch) reference `.env` variables
|
||||
- examples/README.md with `.env` variable references in all code samples
|
||||
- certs/README.md with `.env` variables for hostnames, ports, and certificate filenames
|
||||
- Admin Credentials, Testing Authentication, and Certificate Requirements sections use `.env` syntax
|
||||
- Default Test Users table shows dynamic email addresses based on `LDAP_DOMAIN`
|
||||
- Development section LDIF examples reference `.env` variables
|
||||
|
||||
### Fixed
|
||||
|
||||
- Updated `pyproject.toml` to use `dependency-groups.dev` instead of deprecated `tool.uv.dev-dependencies`
|
||||
- Added `tool.hatch.build.targets.wheel.packages` configuration to fix build errors
|
||||
- Removed obsolete `version` field from `docker-compose.yml` (Docker Compose v2+ compatibility)
|
||||
- Fixed LDAP user password hashes to use proper SSHA format generated by `slappasswd`
|
||||
- Fixed attribute type conversion in example scripts for uidNumber and gidNumber
|
||||
- Fixed `scripts/__init__.py` import error that prevented pytest from running
|
||||
- Fixed timezone-aware datetime comparisons in certificate generation tests (updated to use `not_valid_before_utc` and `not_valid_after_utc`)
|
||||
|
||||
### Technical Details
|
||||
- Base DN: `dc=testing,dc=local`
|
||||
- LDAP Port: 389 (standard)
|
||||
- LDAPS Port: 636 (SSL/TLS)
|
||||
- Web Admin Port: 8080
|
||||
- Python 3.9+ required
|
||||
|
||||
- Base DN: `dc=testing,dc=local` (configurable via `LDAP_BASE_DN`)
|
||||
- LDAP Port: 389 (configurable via `LDAP_PORT`)
|
||||
- LDAPS Port: 636 (configurable via `LDAPS_PORT`)
|
||||
- Web Admin Port: 8080 (configurable via `PHPLDAPADMIN_PORT`)
|
||||
- Python 3.9+ required (optional, for certificate generation and examples)
|
||||
- Docker/Rancher Desktop required
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Planned Features
|
||||
- Additional integration examples (Node.js, Go, Ruby, etc.)
|
||||
- Health check endpoints
|
||||
- Automated backup scripts
|
||||
|
||||
- Docker image with pre-built configuration
|
||||
- Kubernetes/Helm deployment examples
|
||||
- LDAP replication setup guide
|
||||
- Performance tuning guide
|
||||
- Security hardening options
|
||||
|
||||
---
|
||||
|
||||
## Release Notes
|
||||
|
||||
### Version 0.1.0
|
||||
|
||||
This is the initial release providing a complete LDAP development environment suitable for:
|
||||
|
||||
- Testing LDAP authentication in applications
|
||||
- Development and integration testing
|
||||
- Learning LDAP concepts
|
||||
- Prototyping LDAP-based systems
|
||||
|
||||
**Key Features:**
|
||||
|
||||
- Quick setup with `docker-compose up -d`
|
||||
- Fully configurable via `.env` file
|
||||
- Pre-populated with test users and groups
|
||||
- SSL/TLS support with custom certificate capability
|
||||
- Web-based administration interface
|
||||
- Comprehensive documentation with clear examples
|
||||
|
||||
**Important Security Notes:**
|
||||
|
||||
- This tool is for **DEVELOPMENT USE ONLY**
|
||||
- Default passwords are well-known and insecure
|
||||
- Self-signed certificates are not suitable for production
|
||||
- Never use this with real user data or in production environments
|
||||
|
||||
### Upgrade Instructions
|
||||
|
||||
Not applicable for initial release.
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
Not applicable for initial release.
|
||||
|
||||
---
|
||||
|
||||
For support, issues, or feature requests, please refer to the project documentation or open an issue on the project repository.
|
||||
For support, issues, or feature requests, please refer to the project documentation or open an issue on the project repository.
|
||||
|
||||
@@ -1,288 +0,0 @@
|
||||
# Getting Started with LDAP Docker
|
||||
|
||||
Welcome! This guide will help you get your LDAP development server up and running in just a few minutes.
|
||||
|
||||
## What is This?
|
||||
|
||||
LDAP Docker is a ready-to-use OpenLDAP server for development and testing. It comes pre-configured with:
|
||||
|
||||
- ✅ SSL/TLS support (LDAPS)
|
||||
- ✅ Test users and groups
|
||||
- ✅ Web-based admin interface
|
||||
- ✅ Easy certificate management
|
||||
- ✅ Simple Python CLI tools
|
||||
|
||||
Perfect for testing applications that need LDAP authentication without setting up a complex infrastructure.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
You'll need these installed on your Mac:
|
||||
|
||||
1. **Docker or Rancher Desktop** (for running containers)
|
||||
- Rancher Desktop: https://rancherdesktop.io/ (recommended for Mac)
|
||||
- Or Docker Desktop: https://www.docker.com/products/docker-desktop
|
||||
|
||||
2. **Python 3.9+** (usually pre-installed on Mac)
|
||||
```bash
|
||||
python3 --version
|
||||
```
|
||||
|
||||
3. **UV Package Manager** (optional but recommended)
|
||||
```bash
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
```
|
||||
|
||||
## Quick Start (5 Minutes)
|
||||
|
||||
### Option 1: Automated Setup (Easiest)
|
||||
|
||||
Run the quick start script that will guide you through everything:
|
||||
|
||||
```bash
|
||||
./quickstart.sh
|
||||
```
|
||||
|
||||
This interactive script will:
|
||||
1. Check all prerequisites
|
||||
2. Install UV (if you want)
|
||||
3. Generate SSL certificates
|
||||
4. Start the LDAP server
|
||||
5. Verify everything works
|
||||
|
||||
### Option 2: Manual Setup (Step by Step)
|
||||
|
||||
If you prefer to understand each step:
|
||||
|
||||
```bash
|
||||
# 1. Install Python dependencies
|
||||
make install
|
||||
|
||||
# 2. Generate SSL certificates
|
||||
make certs-generate
|
||||
|
||||
# 3. Start the LDAP server
|
||||
make start
|
||||
|
||||
# 4. Test the connection
|
||||
make test-connection
|
||||
```
|
||||
|
||||
That's it! Your LDAP server is now running.
|
||||
|
||||
## Using Your Own Dev-CA Certificates
|
||||
|
||||
Since you mentioned you maintain a local dev-ca, here's how to use your own certificates:
|
||||
|
||||
```bash
|
||||
# Instead of 'make certs-generate', copy your certificates:
|
||||
cp /path/to/your/dev-ca/ca-cert.pem certs/ca.crt
|
||||
cp /path/to/your/dev-ca/ldap-server.crt certs/server.crt
|
||||
cp /path/to/your/dev-ca/ldap-server.key certs/server.key
|
||||
|
||||
# Set proper permissions
|
||||
chmod 644 certs/ca.crt certs/server.crt
|
||||
chmod 600 certs/server.key
|
||||
|
||||
# Then start the server
|
||||
make start
|
||||
```
|
||||
|
||||
**Important:** Your server certificate should be issued for hostname `ldap.testing.local` or include it as a Subject Alternative Name (SAN).
|
||||
|
||||
## Accessing Your LDAP Server
|
||||
|
||||
Once running, you can access:
|
||||
|
||||
| Service | URL | Purpose |
|
||||
|---------|-----|---------|
|
||||
| **LDAP** | `ldap://localhost:389` | Standard LDAP (unencrypted) |
|
||||
| **LDAPS** | `ldaps://localhost:636` | Secure LDAP with SSL/TLS |
|
||||
| **Admin UI** | `http://localhost:8080` | Web interface (phpLDAPadmin) |
|
||||
|
||||
### Login Credentials
|
||||
|
||||
- **Admin DN:** `cn=admin,dc=testing,dc=local`
|
||||
- **Password:** `admin_password`
|
||||
|
||||
### Test Users (All use password: `password123`)
|
||||
|
||||
- `jdoe` - John Doe (jdoe@testing.local)
|
||||
- `jsmith` - Jane Smith (jsmith@testing.local)
|
||||
- `testuser` - Test User (testuser@testing.local)
|
||||
- `admin` - Admin User (admin@testing.local)
|
||||
|
||||
## Verify Everything Works
|
||||
|
||||
### Test 1: List all users
|
||||
```bash
|
||||
make test-users
|
||||
```
|
||||
|
||||
You should see output like:
|
||||
```
|
||||
Found 4 user(s):
|
||||
|
||||
- Admin User: admin (admin@testing.local)
|
||||
- John Doe: jdoe (jdoe@testing.local)
|
||||
- Jane Smith: jsmith (jsmith@testing.local)
|
||||
- Test User: testuser (testuser@testing.local)
|
||||
```
|
||||
|
||||
### Test 2: Try the web interface
|
||||
|
||||
Open http://localhost:8080 in your browser and login with:
|
||||
- Login DN: `cn=admin,dc=testing,dc=local`
|
||||
- Password: `admin_password`
|
||||
|
||||
### Test 3: Run the example script
|
||||
|
||||
```bash
|
||||
python examples/simple_auth.py
|
||||
```
|
||||
|
||||
This authenticates user `jdoe` and displays their information.
|
||||
|
||||
## Common Commands
|
||||
|
||||
```bash
|
||||
# Server management
|
||||
make start # Start LDAP server
|
||||
make stop # Stop LDAP server
|
||||
make restart # Restart LDAP server
|
||||
make logs # View logs (live)
|
||||
make status # Check if running
|
||||
|
||||
# Testing
|
||||
make test-users # List all users
|
||||
make test-auth # Test authentication
|
||||
make test-all # Run all tests
|
||||
|
||||
# Maintenance
|
||||
make clean # Clean build artifacts
|
||||
make down # Stop and remove containers
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now that your LDAP server is running, you can:
|
||||
|
||||
1. **Integrate with Your Application**
|
||||
- See `examples/README.md` for code samples
|
||||
- Point your app to `ldap://localhost:389`
|
||||
|
||||
2. **Add Custom Users**
|
||||
- Edit `ldif/01-users.ldif`
|
||||
- Run `make down-volumes && make start` to reload
|
||||
|
||||
3. **Configure for Your Use Case**
|
||||
- Edit `docker-compose.yml` for custom settings
|
||||
- See `.env.example` for environment variables
|
||||
|
||||
4. **Learn More**
|
||||
- `README.md` - Full documentation
|
||||
- `QUICKREF.md` - Command reference
|
||||
- `certs/README.md` - Certificate management
|
||||
- `examples/README.md` - Integration examples
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Docker is not running"
|
||||
Start Rancher Desktop from your Applications folder. Look for its icon in the menu bar.
|
||||
|
||||
### "Connection refused"
|
||||
Wait 10-30 seconds after starting - the server needs time to initialize:
|
||||
```bash
|
||||
make logs # Watch until you see "slapd starting"
|
||||
```
|
||||
|
||||
### "Certificate errors"
|
||||
Verify certificates exist:
|
||||
```bash
|
||||
ls -la certs/
|
||||
```
|
||||
|
||||
Regenerate if needed:
|
||||
```bash
|
||||
make certs-generate --force
|
||||
```
|
||||
|
||||
### "Port already in use"
|
||||
Check if something is using LDAP ports:
|
||||
```bash
|
||||
lsof -i :389
|
||||
lsof -i :636
|
||||
```
|
||||
|
||||
### Still stuck?
|
||||
Check the full troubleshooting section in `README.md` or view logs:
|
||||
```bash
|
||||
make logs
|
||||
```
|
||||
|
||||
## Docker Basics (If You're New)
|
||||
|
||||
Since you mentioned being new to Docker, here are the basics:
|
||||
|
||||
- **Container**: A lightweight, isolated environment running your LDAP server
|
||||
- **Image**: The blueprint for creating containers (we use `osixia/openldap`)
|
||||
- **Volume**: Persistent storage for LDAP data (survives restarts)
|
||||
- **docker-compose**: Tool for managing multi-container applications (LDAP + Admin UI)
|
||||
|
||||
When you run `make start`, Docker:
|
||||
1. Downloads the LDAP image (first time only)
|
||||
2. Creates containers from the image
|
||||
3. Mounts your certificates and data files
|
||||
4. Starts the LDAP service
|
||||
|
||||
The `Makefile` is just a collection of shortcuts for common Docker commands.
|
||||
|
||||
## Building Elsewhere
|
||||
|
||||
This project works on:
|
||||
- ✅ **MacOS** (with Rancher Desktop or Docker Desktop)
|
||||
- ✅ **Linux** (with Docker installed)
|
||||
- ✅ **Windows** (with Docker Desktop + WSL2)
|
||||
|
||||
The same `docker-compose.yml` works everywhere - that's the beauty of Docker!
|
||||
|
||||
To share your setup with a colleague:
|
||||
```bash
|
||||
# Just copy the whole project
|
||||
git clone <your-repo>
|
||||
cd ldap_docker
|
||||
make dev-setup
|
||||
```
|
||||
|
||||
## Quick Reference Card
|
||||
|
||||
Keep these handy:
|
||||
|
||||
```bash
|
||||
# Start server
|
||||
make start
|
||||
|
||||
# View logs
|
||||
make logs
|
||||
|
||||
# List users
|
||||
make test-users
|
||||
|
||||
# Stop server
|
||||
make stop
|
||||
|
||||
# Help
|
||||
make help
|
||||
```
|
||||
|
||||
## Support & Documentation
|
||||
|
||||
- **Quick commands**: `make help`
|
||||
- **Full guide**: `README.md`
|
||||
- **Examples**: `examples/README.md`
|
||||
- **Command reference**: `QUICKREF.md`
|
||||
|
||||
---
|
||||
|
||||
**Ready to start?** Run `./quickstart.sh` or `make dev-setup` and you'll be up in 5 minutes! 🚀
|
||||
|
||||
If you run into any issues, check the logs with `make logs` or see the Troubleshooting section in `README.md`.
|
||||
38
Makefile
38
Makefile
@@ -1,4 +1,4 @@
|
||||
.PHONY: help install init start stop restart down logs status certs-generate certs-check test-connection test-auth test-users clean clean-all
|
||||
.PHONY: help install init start stop restart down logs status certs-generate certs-check verify-connection verify-auth verify-users verify-ssl verify-all test test-cov clean clean-all
|
||||
|
||||
# Load environment variables from .env file if it exists
|
||||
ifneq (,$(wildcard ./.env))
|
||||
@@ -15,7 +15,7 @@ help: ## Show this help message
|
||||
@echo "Usage: make [target]"
|
||||
@echo ""
|
||||
@echo "Available targets:"
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}'
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' Makefile | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}'
|
||||
|
||||
install: ## Install Python dependencies with UV
|
||||
@echo "Installing dependencies with UV..."
|
||||
@@ -25,7 +25,7 @@ install: ## Install Python dependencies with UV
|
||||
|
||||
install-dev: ## Install development dependencies
|
||||
@echo "Installing development dependencies with UV..."
|
||||
uv sync --all-extras
|
||||
uv sync --group dev
|
||||
@echo "✅ Development dependencies installed"
|
||||
|
||||
init: install certs-check ## Initialize the environment (install deps, check certs)
|
||||
@@ -109,23 +109,33 @@ logs-admin: ## View phpLDAPadmin logs
|
||||
status: ## Show container status
|
||||
@docker-compose ps
|
||||
|
||||
test-connection: ## Test connection to LDAP server
|
||||
@echo "Testing LDAP connection..."
|
||||
verify-connection: ## Verify connection to LDAP server (requires running container)
|
||||
@echo "Verifying LDAP connection..."
|
||||
@export LDAP_PORT=$${LDAP_PORT:-389}; uv run python -c "import os; from ldap3 import Server, Connection, ALL; s = Server(f\"ldap://localhost:{os.environ.get('LDAP_PORT', '389')}\", get_info=ALL); c = Connection(s, auto_bind=True); print('✅ Connection successful'); c.unbind()"
|
||||
|
||||
test-auth: ## Test authentication with admin user
|
||||
@echo "Testing LDAP authentication..."
|
||||
verify-auth: ## Verify authentication with admin user (requires running container)
|
||||
@echo "Verifying LDAP authentication..."
|
||||
@export LDAP_PORT=$${LDAP_PORT:-389}; uv run python -c "import os; from ldap3 import Server, Connection; s = Server(f\"ldap://localhost:{os.environ.get('LDAP_PORT', '389')}\"); c = Connection(s, 'cn=admin,dc=testing,dc=local', 'admin_password', auto_bind=True); print('✅ Authentication successful'); c.unbind()"
|
||||
|
||||
test-users: ## List all users in LDAP
|
||||
verify-users: ## List all users in LDAP (requires running container)
|
||||
@echo "Listing LDAP users..."
|
||||
@export LDAP_PORT=$${LDAP_PORT:-389}; uv run python -c "import os; from ldap3 import Server, Connection; s = Server(f\"ldap://localhost:{os.environ.get('LDAP_PORT', '389')}\"); c = Connection(s, 'cn=admin,dc=testing,dc=local', 'admin_password', auto_bind=True); c.search('dc=testing,dc=local', '(objectClass=inetOrgPerson)', attributes=['uid', 'cn', 'mail']); [print(f' - {e.cn}: {e.uid} ({e.mail})') for e in c.entries]; c.unbind()"
|
||||
|
||||
test-ssl: ## Test SSL/TLS connection
|
||||
@echo "Testing LDAPS connection..."
|
||||
verify-ssl: ## Verify SSL/TLS connection (requires running container)
|
||||
@echo "Verifying LDAPS connection..."
|
||||
openssl s_client -connect localhost:$${LDAPS_PORT:-636} -CAfile certs/ca.crt </dev/null
|
||||
|
||||
test-all: test-connection test-auth test-users ## Run all tests
|
||||
verify-all: verify-connection verify-auth verify-users ## Run all verification checks (requires running container)
|
||||
|
||||
test: ## Run unit tests with pytest
|
||||
@echo "Running unit tests..."
|
||||
uv run pytest
|
||||
|
||||
test-cov: ## Run unit tests with coverage report
|
||||
@echo "Running unit tests with coverage..."
|
||||
uv run pytest --cov=scripts --cov-report=term-missing --cov-report=html
|
||||
@echo ""
|
||||
@echo "📊 Coverage report generated in htmlcov/index.html"
|
||||
|
||||
shell: ## Open a shell in the LDAP container
|
||||
docker-compose exec openldap bash
|
||||
@@ -141,7 +151,7 @@ clean: ## Clean Python build artifacts
|
||||
find . -type f -name "*.pyo" -delete 2>/dev/null || true
|
||||
find . -type d -name "*.egg-info" -exec rm -rf {} + 2>/dev/null || true
|
||||
find . -type d -name ".pytest_cache" -exec rm -rf {} + 2>/dev/null || true
|
||||
find . -type d -name ".mypy_cache" -exec rm -rf {} + 2>/dev/null || true
|
||||
|
||||
find . -type d -name "htmlcov" -exec rm -rf {} + 2>/dev/null || true
|
||||
find . -type f -name ".coverage" -delete 2>/dev/null || true
|
||||
rm -rf build/ dist/ .eggs/
|
||||
@@ -158,8 +168,8 @@ dev-setup: install-dev certs-generate start ## Complete development setup
|
||||
@echo ""
|
||||
@echo "Next steps:"
|
||||
@echo " - View logs: make logs"
|
||||
@echo " - Test connection: make test-connection"
|
||||
@echo " - List users: make test-users"
|
||||
@echo " - Verify connection: make verify-connection"
|
||||
@echo " - List users: make verify-users"
|
||||
@echo " - Open admin UI: open http://localhost:8080"
|
||||
|
||||
quick-start: certs-check start ## Quick start (assumes certs exist)
|
||||
|
||||
@@ -1,436 +0,0 @@
|
||||
# Port Configuration Guide
|
||||
|
||||
This guide explains how to configure custom ports for your LDAP Docker environment to avoid conflicts with other services.
|
||||
|
||||
## Quick Start
|
||||
|
||||
**To change ports, simply edit the `.env` file:**
|
||||
|
||||
```bash
|
||||
# Edit .env file
|
||||
LDAP_PORT=20389
|
||||
LDAPS_PORT=20636
|
||||
PHPLDAPADMIN_PORT=8080
|
||||
```
|
||||
|
||||
Then restart:
|
||||
```bash
|
||||
make down && make start
|
||||
```
|
||||
|
||||
That's it! All scripts and tools will automatically use your custom ports.
|
||||
|
||||
---
|
||||
|
||||
## Why Custom Ports?
|
||||
|
||||
You might want to use custom ports if:
|
||||
- You have another LDAP server running on standard ports (389/636)
|
||||
- Port 389 requires root/admin privileges
|
||||
- You're running multiple LDAP environments simultaneously
|
||||
- Your organization has specific port requirements
|
||||
|
||||
## Files Involved
|
||||
|
||||
The port configuration system uses these files:
|
||||
|
||||
### 1. `.env` - Your Configuration (Edit This!)
|
||||
```bash
|
||||
LDAP_PORT=20389 # Your custom LDAP port
|
||||
LDAPS_PORT=20636 # Your custom LDAPS port
|
||||
PHPLDAPADMIN_PORT=8080 # Web admin interface port
|
||||
```
|
||||
|
||||
### 2. `docker-compose.yml` - Uses Environment Variables
|
||||
```yaml
|
||||
ports:
|
||||
- "${LDAP_PORT:-389}:389" # Host:Container mapping
|
||||
- "${LDAPS_PORT:-636}:636"
|
||||
```
|
||||
|
||||
The `${LDAP_PORT:-389}` syntax means:
|
||||
- Use `$LDAP_PORT` if set in `.env`
|
||||
- Otherwise use default `389`
|
||||
|
||||
### 3. Scripts - Auto-detect Ports
|
||||
All Python scripts and Makefile commands read from `.env` automatically:
|
||||
- `scripts/cli.py`
|
||||
- `scripts/ldapsearch_helper.sh`
|
||||
- `examples/simple_auth.py`
|
||||
- `Makefile`
|
||||
|
||||
## How to Change Ports
|
||||
|
||||
### Method 1: Edit .env File (Recommended)
|
||||
|
||||
1. **Edit `.env`:**
|
||||
```bash
|
||||
vim .env # or your favorite editor
|
||||
```
|
||||
|
||||
2. **Change the port values:**
|
||||
```bash
|
||||
LDAP_PORT=20389
|
||||
LDAPS_PORT=20636
|
||||
```
|
||||
|
||||
3. **Restart the containers:**
|
||||
```bash
|
||||
make down && make start
|
||||
```
|
||||
|
||||
4. **Verify:**
|
||||
```bash
|
||||
docker-compose ps
|
||||
# Should show 0.0.0.0:20389->389/tcp
|
||||
```
|
||||
|
||||
### Method 2: Environment Variables (Temporary)
|
||||
|
||||
For one-time use without modifying `.env`:
|
||||
|
||||
```bash
|
||||
LDAP_PORT=30389 LDAPS_PORT=30636 docker-compose up -d
|
||||
```
|
||||
|
||||
### Method 3: Create .env.local (Advanced)
|
||||
|
||||
For personal overrides without modifying the main `.env`:
|
||||
|
||||
```bash
|
||||
# Create .env.local (git-ignored)
|
||||
echo "LDAP_PORT=12389" > .env.local
|
||||
echo "LDAPS_PORT=12636" >> .env.local
|
||||
|
||||
# Docker Compose will merge both files
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
## Testing with Custom Ports
|
||||
|
||||
### Automatic - Use Our Tools
|
||||
|
||||
All our tools automatically detect your ports from `.env`:
|
||||
|
||||
```bash
|
||||
# These all work automatically with your custom ports
|
||||
make test-users
|
||||
make test-connection
|
||||
uv run python examples/simple_auth.py
|
||||
```
|
||||
|
||||
### Generate ldapsearch Commands
|
||||
|
||||
Use our helper script to generate commands with your ports:
|
||||
|
||||
```bash
|
||||
./scripts/ldapsearch_helper.sh
|
||||
```
|
||||
|
||||
Output includes commands like:
|
||||
```bash
|
||||
# List all users
|
||||
ldapsearch -H ldap://localhost:20389 \
|
||||
-D "cn=admin,dc=testing,dc=local" \
|
||||
-w admin_password \
|
||||
-b "ou=people,dc=testing,dc=local" \
|
||||
"(objectClass=inetOrgPerson)" \
|
||||
uid cn mail
|
||||
```
|
||||
|
||||
### Manual ldapsearch Commands
|
||||
|
||||
Replace `389` with your `LDAP_PORT` and `636` with your `LDAPS_PORT`:
|
||||
|
||||
```bash
|
||||
# Standard LDAP (unencrypted)
|
||||
ldapsearch -H ldap://localhost:20389 \
|
||||
-D "cn=admin,dc=testing,dc=local" \
|
||||
-w admin_password \
|
||||
-b "dc=testing,dc=local"
|
||||
|
||||
# LDAPS (SSL/TLS)
|
||||
ldapsearch -H ldaps://localhost:20636 \
|
||||
-D "cn=admin,dc=testing,dc=local" \
|
||||
-w admin_password \
|
||||
-b "dc=testing,dc=local"
|
||||
|
||||
# Search for specific user
|
||||
ldapsearch -H ldap://localhost:20389 \
|
||||
-D "cn=admin,dc=testing,dc=local" \
|
||||
-w admin_password \
|
||||
-b "dc=testing,dc=local" \
|
||||
"(uid=jdoe)"
|
||||
|
||||
# Test user authentication
|
||||
ldapsearch -H ldap://localhost:20389 \
|
||||
-D "uid=jdoe,ou=people,dc=testing,dc=local" \
|
||||
-w password123 \
|
||||
-b "dc=testing,dc=local" \
|
||||
"(uid=jdoe)"
|
||||
```
|
||||
|
||||
### Using Python
|
||||
|
||||
```python
|
||||
import os
|
||||
from ldap3 import Server, Connection
|
||||
|
||||
# Automatically uses LDAP_PORT from environment
|
||||
port = os.environ.get('LDAP_PORT', '389')
|
||||
server = Server(f'ldap://localhost:{port}')
|
||||
conn = Connection(server,
|
||||
user='cn=admin,dc=testing,dc=local',
|
||||
password='admin_password',
|
||||
auto_bind=True)
|
||||
|
||||
conn.search('dc=testing,dc=local', '(objectClass=*)')
|
||||
print(f"Connected on port {port}")
|
||||
conn.unbind()
|
||||
```
|
||||
|
||||
## Common Port Configurations
|
||||
|
||||
### Development (Unprivileged Ports)
|
||||
```bash
|
||||
LDAP_PORT=20389
|
||||
LDAPS_PORT=20636
|
||||
PHPLDAPADMIN_PORT=8080
|
||||
```
|
||||
|
||||
### Testing (High Ports)
|
||||
```bash
|
||||
LDAP_PORT=30389
|
||||
LDAPS_PORT=30636
|
||||
PHPLDAPADMIN_PORT=8090
|
||||
```
|
||||
|
||||
### Multiple Environments
|
||||
```bash
|
||||
# Environment 1 (.env)
|
||||
LDAP_PORT=20389
|
||||
LDAPS_PORT=20636
|
||||
|
||||
# Environment 2 (separate directory or .env.local)
|
||||
LDAP_PORT=21389
|
||||
LDAPS_PORT=21636
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Port Already in Use
|
||||
|
||||
**Error:** `Bind for 0.0.0.0:20389 failed: port is already allocated`
|
||||
|
||||
**Check what's using the port:**
|
||||
```bash
|
||||
lsof -i :20389
|
||||
# or
|
||||
netstat -an | grep 20389
|
||||
```
|
||||
|
||||
**Solutions:**
|
||||
1. Stop the conflicting service
|
||||
2. Choose a different port in `.env`
|
||||
3. Use `make down` to stop this LDAP server first
|
||||
|
||||
### Scripts Not Using Custom Ports
|
||||
|
||||
**Problem:** Scripts still connect to port 389
|
||||
|
||||
**Solution:** Ensure `.env` is loaded:
|
||||
|
||||
1. **Check .env exists:**
|
||||
```bash
|
||||
ls -la .env
|
||||
```
|
||||
|
||||
2. **Verify ports are set:**
|
||||
```bash
|
||||
grep LDAP_PORT .env
|
||||
```
|
||||
|
||||
3. **Reload environment:**
|
||||
```bash
|
||||
source .env # For bash scripts
|
||||
make down && make start # Restart containers
|
||||
```
|
||||
|
||||
### Docker Compose Not Reading .env
|
||||
|
||||
**Problem:** Containers still on default ports
|
||||
|
||||
**Check:**
|
||||
```bash
|
||||
# View what Docker Compose sees
|
||||
docker-compose config | grep -A2 ports
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Ensure .env is in project root
|
||||
pwd # Should be ldap_docker/
|
||||
ls .env # Should exist
|
||||
|
||||
# Force reload
|
||||
docker-compose down
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Can't Connect After Port Change
|
||||
|
||||
**Verify containers are running on correct ports:**
|
||||
```bash
|
||||
docker-compose ps
|
||||
# Should show: 0.0.0.0:20389->389/tcp
|
||||
```
|
||||
|
||||
**Test connectivity:**
|
||||
```bash
|
||||
# Test if port is listening
|
||||
nc -zv localhost 20389
|
||||
|
||||
# Test LDAP response
|
||||
ldapsearch -H ldap://localhost:20389 -x -b "" -s base
|
||||
```
|
||||
|
||||
**Check logs:**
|
||||
```bash
|
||||
make logs
|
||||
# Look for: "slapd starting"
|
||||
```
|
||||
|
||||
## Port Reference Table
|
||||
|
||||
| Service | Default Port | Docker Internal Port | Configurable Via |
|
||||
|---------|--------------|---------------------|------------------|
|
||||
| LDAP | 389 | 389 | `LDAP_PORT` in .env |
|
||||
| LDAPS | 636 | 636 | `LDAPS_PORT` in .env |
|
||||
| phpLDAPadmin | 8080 | 80 | `PHPLDAPADMIN_PORT` in .env |
|
||||
|
||||
**Note:** The "Docker Internal Port" (right side of mapping) never changes. Only the host port (left side) is configurable.
|
||||
|
||||
## Advanced: Port Mapping Explained
|
||||
|
||||
Docker port mapping format: `HOST:CONTAINER`
|
||||
|
||||
```yaml
|
||||
ports:
|
||||
- "20389:389"
|
||||
```
|
||||
|
||||
This means:
|
||||
- **20389** = Port on your Mac (accessible via `localhost:20389`)
|
||||
- **389** = Port inside the Docker container (LDAP's standard port)
|
||||
|
||||
The container always listens on 389 internally. We just map it to 20389 externally.
|
||||
|
||||
## Testing Multiple Configurations
|
||||
|
||||
Run multiple LDAP servers simultaneously:
|
||||
|
||||
```bash
|
||||
# Terminal 1: First instance
|
||||
cd ldap_docker_1
|
||||
echo "LDAP_PORT=20389" > .env
|
||||
echo "LDAPS_PORT=20636" >> .env
|
||||
make start
|
||||
|
||||
# Terminal 2: Second instance
|
||||
cd ldap_docker_2
|
||||
echo "LDAP_PORT=21389" > .env
|
||||
echo "LDAPS_PORT=21636" >> .env
|
||||
make start
|
||||
|
||||
# Now you have two LDAP servers running!
|
||||
ldapsearch -H ldap://localhost:20389 ... # Server 1
|
||||
ldapsearch -H ldap://localhost:21389 ... # Server 2
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use .env for configuration** - Don't hardcode ports in scripts
|
||||
2. **Document your ports** - Add comments in .env explaining your choices
|
||||
3. **Use unprivileged ports** - Ports above 1024 don't require root
|
||||
4. **Avoid common ports** - Skip 8080, 3000, 5000 (often used by other tools)
|
||||
5. **Be consistent** - If LDAP is 20389, make LDAPS 20636 (same prefix)
|
||||
|
||||
## Integration Examples
|
||||
|
||||
### Configure Your Application
|
||||
|
||||
After changing ports, update your application's LDAP configuration:
|
||||
|
||||
```python
|
||||
# Django settings.py
|
||||
AUTH_LDAP_SERVER_URI = "ldap://localhost:20389"
|
||||
|
||||
# Flask
|
||||
LDAP_HOST = "localhost"
|
||||
LDAP_PORT = 20389
|
||||
|
||||
# Environment variable
|
||||
export LDAP_URL="ldap://localhost:20389"
|
||||
```
|
||||
|
||||
### Using with Docker Networks
|
||||
|
||||
If your application is also in Docker:
|
||||
|
||||
```yaml
|
||||
# Your app's docker-compose.yml
|
||||
services:
|
||||
your-app:
|
||||
environment:
|
||||
LDAP_URL: "ldap://ldap-server:389" # Use container name and internal port
|
||||
networks:
|
||||
- ldap_docker_ldap-network # Connect to LDAP network
|
||||
|
||||
networks:
|
||||
ldap_docker_ldap-network:
|
||||
external: true
|
||||
```
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```bash
|
||||
# View current configuration
|
||||
cat .env | grep PORT
|
||||
|
||||
# Generate ldapsearch commands
|
||||
./scripts/ldapsearch_helper.sh
|
||||
|
||||
# Test connection on custom port
|
||||
make test-connection
|
||||
|
||||
# Check what ports containers are using
|
||||
docker-compose ps
|
||||
|
||||
# Change ports (example)
|
||||
echo "LDAP_PORT=12389" > .env
|
||||
echo "LDAPS_PORT=12636" >> .env
|
||||
make down && make start
|
||||
|
||||
# Verify new ports work
|
||||
make test-users
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
**To use custom ports:**
|
||||
1. Edit `LDAP_PORT` and `LDAPS_PORT` in `.env`
|
||||
2. Run `make down && make start`
|
||||
3. All tools automatically use your new ports!
|
||||
|
||||
**Need ldapsearch commands?**
|
||||
- Run `./scripts/ldapsearch_helper.sh`
|
||||
- Or manually replace 389 → your LDAP_PORT, 636 → your LDAPS_PORT
|
||||
|
||||
That's it! The port configuration system is designed to be simple and automatic.
|
||||
|
||||
---
|
||||
|
||||
**See Also:**
|
||||
- [README.md](README.md) - Full documentation
|
||||
- [GETTING_STARTED.md](GETTING_STARTED.md) - Setup guide
|
||||
- [QUICKREF.md](QUICKREF.md) - Command reference
|
||||
354
QUICKREF.md
354
QUICKREF.md
@@ -1,354 +0,0 @@
|
||||
# LDAP Docker Quick Reference
|
||||
|
||||
Quick reference for common operations and configurations.
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
```bash
|
||||
# Complete setup
|
||||
make dev-setup
|
||||
|
||||
# Or step by step
|
||||
make install # Install dependencies
|
||||
make certs-generate # Generate certificates
|
||||
make start # Start server
|
||||
make test-connection # Test it works
|
||||
```
|
||||
|
||||
## 🎯 Common Commands
|
||||
|
||||
### Server Management
|
||||
|
||||
```bash
|
||||
make start # Start LDAP server
|
||||
make stop # Stop LDAP server
|
||||
make restart # Restart LDAP server
|
||||
make down # Stop and remove containers
|
||||
make logs # View logs (follow mode)
|
||||
make status # Check container status
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
make test-connection # Test LDAP connection
|
||||
make test-auth # Test authentication
|
||||
make test-users # List all users
|
||||
make test-all # Run all tests
|
||||
```
|
||||
|
||||
### Certificates
|
||||
|
||||
```bash
|
||||
make certs-generate # Generate self-signed certs
|
||||
make certs-check # Verify certificates
|
||||
```
|
||||
|
||||
## 🔑 Default Credentials
|
||||
|
||||
### Admin Access
|
||||
|
||||
- **DN:** `cn=admin,dc=testing,dc=local`
|
||||
- **Password:** `admin_password`
|
||||
- **Base DN:** `dc=testing,dc=local`
|
||||
|
||||
### phpLDAPadmin
|
||||
|
||||
- URL: http://localhost:8080
|
||||
- Login DN: `cn=admin,dc=testing,dc=local`
|
||||
- Password: `admin_password`
|
||||
|
||||
## 👥 Test Users
|
||||
|
||||
All users have password: `password123`
|
||||
|
||||
| Username | Full Name | Email | DN |
|
||||
|----------|-----------|-------|-----|
|
||||
| `admin` | Admin User | admin@testing.local | `uid=admin,ou=people,dc=testing,dc=local` |
|
||||
| `jdoe` | John Doe | jdoe@testing.local | `uid=jdoe,ou=people,dc=testing,dc=local` |
|
||||
| `jsmith` | Jane Smith | jsmith@testing.local | `uid=jsmith,ou=people,dc=testing,dc=local` |
|
||||
| `testuser` | Test User | testuser@testing.local | `uid=testuser,ou=people,dc=testing,dc=local` |
|
||||
|
||||
## 🌐 Service Ports
|
||||
|
||||
| Service | Port | URL/Connection |
|
||||
|---------|------|----------------|
|
||||
| LDAP | 389 | `ldap://localhost:389` |
|
||||
| LDAPS | 636 | `ldaps://localhost:636` |
|
||||
| phpLDAPadmin | 8080 | `http://localhost:8080` |
|
||||
|
||||
## 🔍 Common LDAP Queries
|
||||
|
||||
### Search All Users
|
||||
|
||||
```bash
|
||||
ldapsearch -H ldap://localhost:389 \
|
||||
-D "cn=admin,dc=testing,dc=local" \
|
||||
-w admin_password \
|
||||
-b "ou=people,dc=testing,dc=local" \
|
||||
"(objectClass=inetOrgPerson)"
|
||||
```
|
||||
|
||||
### Search Specific User
|
||||
|
||||
```bash
|
||||
ldapsearch -H ldap://localhost:389 \
|
||||
-D "cn=admin,dc=testing,dc=local" \
|
||||
-w admin_password \
|
||||
-b "dc=testing,dc=local" \
|
||||
"(uid=jdoe)"
|
||||
```
|
||||
|
||||
### Search All Groups
|
||||
|
||||
```bash
|
||||
ldapsearch -H ldap://localhost:389 \
|
||||
-D "cn=admin,dc=testing,dc=local" \
|
||||
-w admin_password \
|
||||
-b "ou=groups,dc=testing,dc=local" \
|
||||
"(objectClass=groupOfNames)"
|
||||
```
|
||||
|
||||
### Anonymous Bind (Read-Only)
|
||||
|
||||
```bash
|
||||
ldapsearch -H ldap://localhost:389 \
|
||||
-x \
|
||||
-b "dc=testing,dc=local" \
|
||||
"(objectClass=*)"
|
||||
```
|
||||
|
||||
## 🔒 LDAPS/SSL Testing
|
||||
|
||||
### Test SSL Connection
|
||||
|
||||
```bash
|
||||
openssl s_client -connect localhost:636 -CAfile certs/ca.crt
|
||||
```
|
||||
|
||||
### LDAPS Search
|
||||
|
||||
```bash
|
||||
ldapsearch -H ldaps://localhost:636 \
|
||||
-D "cn=admin,dc=testing,dc=local" \
|
||||
-w admin_password \
|
||||
-b "dc=testing,dc=local"
|
||||
```
|
||||
|
||||
### Verify Certificate
|
||||
|
||||
```bash
|
||||
openssl verify -CAfile certs/ca.crt certs/server.crt
|
||||
openssl x509 -in certs/server.crt -text -noout
|
||||
```
|
||||
|
||||
## 🐍 Python LDAP3 Examples
|
||||
|
||||
### Simple Connection
|
||||
|
||||
```python
|
||||
from ldap3 import Server, Connection
|
||||
|
||||
server = Server('ldap://localhost:389')
|
||||
conn = Connection(server,
|
||||
user='cn=admin,dc=testing,dc=local',
|
||||
password='admin_password',
|
||||
auto_bind=True)
|
||||
print("Connected!")
|
||||
conn.unbind()
|
||||
```
|
||||
|
||||
### Search Users
|
||||
|
||||
```python
|
||||
from ldap3 import Server, Connection
|
||||
|
||||
server = Server('ldap://localhost:389')
|
||||
conn = Connection(server,
|
||||
user='cn=admin,dc=testing,dc=local',
|
||||
password='admin_password',
|
||||
auto_bind=True)
|
||||
|
||||
conn.search('dc=testing,dc=local',
|
||||
'(objectClass=inetOrgPerson)',
|
||||
attributes=['uid', 'cn', 'mail'])
|
||||
|
||||
for entry in conn.entries:
|
||||
print(f"{entry.cn}: {entry.mail}")
|
||||
|
||||
conn.unbind()
|
||||
```
|
||||
|
||||
### Authenticate User
|
||||
|
||||
```python
|
||||
from ldap3 import Server, Connection
|
||||
|
||||
server = Server('ldap://localhost:389')
|
||||
conn = Connection(server,
|
||||
user='uid=jdoe,ou=people,dc=testing,dc=local',
|
||||
password='password123')
|
||||
|
||||
if conn.bind():
|
||||
print("Authentication successful!")
|
||||
else:
|
||||
print("Authentication failed!")
|
||||
conn.unbind()
|
||||
```
|
||||
|
||||
## 🐳 Docker Commands
|
||||
|
||||
### View Logs
|
||||
|
||||
```bash
|
||||
docker-compose logs -f openldap # Follow LDAP logs
|
||||
docker-compose logs --tail=100 openldap # Last 100 lines
|
||||
docker-compose logs phpldapadmin # Admin UI logs
|
||||
```
|
||||
|
||||
### Container Shell Access
|
||||
|
||||
```bash
|
||||
docker-compose exec openldap bash # Shell in LDAP container
|
||||
docker ps # List running containers
|
||||
docker-compose ps # List project containers
|
||||
```
|
||||
|
||||
### Volume Management
|
||||
|
||||
```bash
|
||||
docker volume ls # List volumes
|
||||
docker-compose down -v # Remove volumes (deletes data!)
|
||||
```
|
||||
|
||||
## 🔧 Troubleshooting Quick Fixes
|
||||
|
||||
### Server Won't Start
|
||||
|
||||
```bash
|
||||
# Check if ports are in use
|
||||
lsof -i :389
|
||||
lsof -i :636
|
||||
lsof -i :8080
|
||||
|
||||
# Check Docker is running
|
||||
docker version
|
||||
|
||||
# View error logs
|
||||
docker-compose logs openldap
|
||||
```
|
||||
|
||||
### Certificate Errors
|
||||
|
||||
```bash
|
||||
# Verify certificates exist
|
||||
ls -la certs/
|
||||
|
||||
# Regenerate certificates
|
||||
make certs-generate --force
|
||||
|
||||
# Check certificate validity
|
||||
openssl x509 -in certs/server.crt -noout -dates
|
||||
```
|
||||
|
||||
### Connection Refused
|
||||
|
||||
```bash
|
||||
# Check container is running
|
||||
docker-compose ps
|
||||
|
||||
# Wait for initialization (can take 10-30 seconds)
|
||||
make logs
|
||||
|
||||
# Restart server
|
||||
make restart
|
||||
```
|
||||
|
||||
### Authentication Fails
|
||||
|
||||
```bash
|
||||
# Verify credentials
|
||||
# Default: cn=admin,dc=testing,dc=local / admin_password
|
||||
|
||||
# Check if users are loaded
|
||||
make test-users
|
||||
|
||||
# View LDAP directory structure
|
||||
ldapsearch -H ldap://localhost:389 -x -b "dc=testing,dc=local" -s base
|
||||
```
|
||||
|
||||
### Data Not Appearing
|
||||
|
||||
```bash
|
||||
# Check if LDIF files were loaded
|
||||
docker-compose logs openldap | grep -i ldif
|
||||
|
||||
# Rebuild with fresh data
|
||||
make down-volumes # WARNING: Deletes all data!
|
||||
make start
|
||||
```
|
||||
|
||||
## 📁 File Locations
|
||||
|
||||
### Configuration Files
|
||||
|
||||
- `docker-compose.yml` - Docker services configuration
|
||||
- `pyproject.toml` - Python dependencies
|
||||
- `.env.example` - Environment variables template
|
||||
|
||||
### Data Files
|
||||
|
||||
- `ldif/01-users.ldif` - Initial LDAP data
|
||||
- `certs/` - SSL certificates (git-ignored)
|
||||
|
||||
### Scripts
|
||||
|
||||
- `scripts/cli.py` - CLI management tool
|
||||
- `scripts/generate_certs.py` - Certificate generator
|
||||
- `quickstart.sh` - Interactive setup script
|
||||
|
||||
## 🎓 LDAP Basics
|
||||
|
||||
### DN (Distinguished Name)
|
||||
|
||||
Format: `attribute=value,ou=unit,dc=domain,dc=tld`
|
||||
|
||||
Examples:
|
||||
- `cn=admin,dc=testing,dc=local` - Admin user
|
||||
- `uid=jdoe,ou=people,dc=testing,dc=local` - Regular user
|
||||
- `cn=developers,ou=groups,dc=testing,dc=local` - Group
|
||||
|
||||
### Common Object Classes
|
||||
|
||||
- `inetOrgPerson` - Person with internet attributes
|
||||
- `posixAccount` - Unix/Linux account
|
||||
- `groupOfNames` - Group with members
|
||||
|
||||
### Common Attributes
|
||||
|
||||
- `uid` - User ID (username)
|
||||
- `cn` - Common Name (full name)
|
||||
- `sn` - Surname (last name)
|
||||
- `mail` - Email address
|
||||
- `userPassword` - Hashed password
|
||||
- `member` - Group member DN
|
||||
|
||||
## 🔗 Useful Links
|
||||
|
||||
- [OpenLDAP Documentation](https://www.openldap.org/doc/)
|
||||
- [LDAP3 Python Library](https://ldap3.readthedocs.io/)
|
||||
- [RFC 4511 - LDAP Protocol](https://tools.ietf.org/html/rfc4511)
|
||||
- [phpLDAPadmin](http://phpldapadmin.sourceforge.net/)
|
||||
|
||||
## 💡 Tips
|
||||
|
||||
1. **Use LDAPS in applications**: Always prefer `ldaps://` over `ldap://`
|
||||
2. **Test with anonymous bind first**: Use `-x` flag with ldapsearch
|
||||
3. **Check logs when troubleshooting**: `make logs` is your friend
|
||||
4. **Certificate hostname must match**: Ensure SAN includes `ldap.testing.local`
|
||||
5. **Wait after starting**: Server needs 10-30 seconds to initialize
|
||||
6. **Backup before experimenting**: Use `make down` not `make down-volumes`
|
||||
|
||||
---
|
||||
|
||||
**Need more help?** See full documentation in README.md
|
||||
490
README.md
490
README.md
@@ -1,25 +1,31 @@
|
||||
# LDAP Docker
|
||||
|
||||
This is a development tool project that, when deployed, offers an SSL-capable OpenLDAP server populated with test users for a fictional `testing.local` domain.
|
||||
A pre-configured OpenLDAP server with SSL/TLS support and test users for the `{.env:LDAP_DOMAIN}` domain.
|
||||
|
||||
Perfect for local development and testing of applications that need LDAP authentication without setting up a complex infrastructure.
|
||||
Perfect for local development and testing of applications that need LDAP authentication without setting up a complex infrastructure. Just run `docker-compose up -d` and you're ready to go!
|
||||
|
||||
## Features
|
||||
|
||||
- 🔒 **SSL/TLS Support** - LDAPS on port 636 with custom certificate support
|
||||
- 👥 **Pre-populated Users** - Test users and groups ready to use
|
||||
- 🌐 **Web Admin Interface** - phpLDAPadmin for easy management
|
||||
- 🐳 **Docker-based** - Easy deployment with Docker Compose
|
||||
- 🔧 **Python Management Tools** - UV-based CLI for convenience
|
||||
- 🍎 **Cross-platform** - Works on MacOS (Rancher Desktop), Linux, and Windows
|
||||
- **SSL/TLS Support** - LDAPS on port `{.env:LDAPS_PORT}` with custom certificate support
|
||||
- **Pre-populated Users** - Test users and groups ready to use
|
||||
- **Web Admin Interface** - phpLDAPadmin for easy management
|
||||
- **Docker-based** - Easy deployment with Docker Compose
|
||||
- **Fully Configurable** - Customize ports, domain, credentials via `.env` file
|
||||
- **Optional Management Tools** - Makefile and Python scripts for convenience
|
||||
- **Cross-platform** - Works anywhere Docker runs
|
||||
|
||||
> **📝 Note:** Throughout this documentation, values shown as `{.env:VARIABLE_NAME}` indicate they can be customized via environment variables. See [Configuration](#configuration) for details.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Quick Start](#quick-start)
|
||||
- [Verify Installation](#verify-installation)
|
||||
- [Custom Certificates (Dev-CA)](#custom-certificates-dev-ca)
|
||||
- [Usage](#usage)
|
||||
- [Configuration](#configuration)
|
||||
- [Test Users](#test-users)
|
||||
- [Next Steps](#next-steps)
|
||||
- [Management Tools](#management-tools)
|
||||
- [Project Structure](#project-structure)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
@@ -29,67 +35,122 @@ Perfect for local development and testing of applications that need LDAP authent
|
||||
|
||||
### Required
|
||||
|
||||
- **Docker** or **Rancher Desktop** (recommended for MacOS)
|
||||
- MacOS: [Download Rancher Desktop](https://rancherdesktop.io/)
|
||||
- Alternatively: [Docker Desktop](https://www.docker.com/products/docker-desktop)
|
||||
- **Python 3.9+** (for management scripts)
|
||||
- **UV Package Manager** (recommended)
|
||||
- **Docker** - Any provider that can run Docker Compose
|
||||
- [Docker Desktop](https://www.docker.com/products/docker-desktop) (Windows, Mac, Linux)
|
||||
- [Rancher Desktop](https://rancherdesktop.io/) (Alternative for Mac/Linux)
|
||||
- Or native Docker on Linux
|
||||
|
||||
- **SSL Certificates** - Either:
|
||||
- Generate with the included Python script (requires Python 3.9+)
|
||||
- OR bring your own certificates (see [Custom Certificates](#custom-certificates-dev-ca))
|
||||
|
||||
That's it! Everything runs in Docker containers.
|
||||
|
||||
### Optional Tools
|
||||
|
||||
These tools provide convenient shortcuts but are **not required**:
|
||||
|
||||
- **Make** - For using `make start` instead of `docker-compose up -d`
|
||||
- **Python 3.9+** - For certificate generation script and examples
|
||||
- **UV Package Manager** - Fast Python dependency management
|
||||
```bash
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
```
|
||||
|
||||
### Optional
|
||||
|
||||
- **OpenSSL** (for certificate verification)
|
||||
- **LDAP utilities** (ldapsearch, ldapadd, etc.) for testing
|
||||
- **OpenSSL** - For manual certificate verification
|
||||
- **LDAP utilities** - Command-line tools (ldapsearch, ldapadd, etc.)
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Option 1: Using Make (Recommended)
|
||||
### Simple Way (Docker Only)
|
||||
|
||||
```bash
|
||||
# 1. Install dependencies
|
||||
make install
|
||||
# 1. Generate certificates (requires Python)
|
||||
python3 scripts/generate_certs.py
|
||||
|
||||
# 2. Generate certificates (or copy your own - see below)
|
||||
make certs-generate
|
||||
|
||||
# 3. Start the server
|
||||
make start
|
||||
|
||||
# 4. Test the connection
|
||||
make test-connection
|
||||
|
||||
# 5. List users
|
||||
make test-users
|
||||
```
|
||||
|
||||
### Option 2: Using Docker Compose Directly
|
||||
|
||||
```bash
|
||||
# 1. Generate certificates
|
||||
python scripts/generate_certs.py
|
||||
|
||||
# 2. Start services
|
||||
# 2. Start the server
|
||||
docker-compose up -d
|
||||
|
||||
# 3. View logs
|
||||
docker-compose logs -f openldap
|
||||
|
||||
# 4. Stop the server
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
### Option 3: Complete Dev Setup
|
||||
That's it! The server is now running at `ldap://localhost:{.env:LDAP_PORT}` and `ldaps://localhost:{.env:LDAPS_PORT}`.
|
||||
|
||||
This will install everything, generate certificates, and start the server:
|
||||
### Convenient Way (Using Make)
|
||||
|
||||
If you have Make installed, use these shortcuts:
|
||||
|
||||
```bash
|
||||
# Complete setup (installs Python deps, generates certs, starts server)
|
||||
make dev-setup
|
||||
|
||||
# Or step by step:
|
||||
make install # Install Python dependencies
|
||||
make certs-generate # Generate SSL certificates
|
||||
make start # Start the server
|
||||
make verify-users # Verify users are loaded
|
||||
make logs # View logs
|
||||
```
|
||||
|
||||
Run `make help` to see all available commands.
|
||||
|
||||
### Using Your Own Certificates
|
||||
|
||||
Instead of generating certificates, copy yours to the `certs/` directory:
|
||||
|
||||
```bash
|
||||
cp /path/to/ca.crt certs/ca.crt
|
||||
cp /path/to/server.crt certs/server.crt
|
||||
cp /path/to/server.key certs/server.key
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
> **💡 Tip:** Want to customize ports, passwords, or domain? Copy `.env.example` to `.env` and edit your settings. See the [Configuration](#configuration) section for details.
|
||||
|
||||
## Verify Installation
|
||||
|
||||
After starting the server, verify everything is working:
|
||||
|
||||
### Test 1: List All Users
|
||||
|
||||
```bash
|
||||
make verify-users
|
||||
```
|
||||
|
||||
You should see output like:
|
||||
|
||||
```
|
||||
Found 4 user(s):
|
||||
|
||||
- Admin User: admin (admin@{.env:LDAP_DOMAIN})
|
||||
- John Doe: jdoe (jdoe@{.env:LDAP_DOMAIN})
|
||||
- Jane Smith: jsmith (jsmith@{.env:LDAP_DOMAIN})
|
||||
- Test User: testuser (testuser@{.env:LDAP_DOMAIN})
|
||||
```
|
||||
|
||||
### Test 2: Try the Web Interface
|
||||
|
||||
Open `http://localhost:{.env:PHPLDAPADMIN_PORT}` in your browser and login with:
|
||||
|
||||
- **Login DN:** `cn=admin,{.env:LDAP_BASE_DN}`
|
||||
- **Password:** `{.env:LDAP_ADMIN_PASSWORD}`
|
||||
|
||||
### Test 3: Run the Example Script (Optional - Requires Python)
|
||||
|
||||
```bash
|
||||
python examples/simple_auth.py
|
||||
```
|
||||
|
||||
This authenticates user `jdoe` and displays their information.
|
||||
|
||||
## Custom Certificates (Dev-CA)
|
||||
|
||||
If you maintain your own dev-ca (as mentioned), you can use your own certificates instead of self-signed ones:
|
||||
If you wish, you can use your own certificates instead of self-signed ones:
|
||||
|
||||
### Using Your Dev-CA Certificates
|
||||
### Using Your Certificates
|
||||
|
||||
```bash
|
||||
# Copy your certificates to the certs directory
|
||||
@@ -103,15 +164,15 @@ chmod 644 certs/server.crt
|
||||
chmod 600 certs/server.key
|
||||
```
|
||||
|
||||
**Important:** The server certificate should be issued for the hostname `ldap.testing.local` or include it as a Subject Alternative Name (SAN).
|
||||
**Important:** The server certificate should be issued for the hostname `{.env:LDAP_HOSTNAME}` or include it as a Subject Alternative Name (SAN).
|
||||
|
||||
### Certificate Requirements
|
||||
|
||||
The OpenLDAP container expects three files in the `certs/` directory:
|
||||
|
||||
- `ca.crt` - Your CA root certificate
|
||||
- `server.crt` - Server certificate for ldap.testing.local
|
||||
- `server.key` - Private key for the server certificate
|
||||
- `ca.crt` - Your CA root certificate (filename: `{.env:LDAP_TLS_CA_CRT_FILENAME}`)
|
||||
- `server.crt` - Server certificate for `{.env:LDAP_HOSTNAME}` (filename: `{.env:LDAP_TLS_CRT_FILENAME}`)
|
||||
- `server.key` - Private key for the server certificate (filename: `{.env:LDAP_TLS_KEY_FILENAME}`)
|
||||
|
||||
### Generating Certificates with Your OpenSSL-based Dev-CA
|
||||
|
||||
@@ -124,16 +185,16 @@ cd /path/to/your/dev-ca
|
||||
# Generate server key
|
||||
openssl genrsa -out ldap-server.key 4096
|
||||
|
||||
# Generate certificate signing request
|
||||
# Generate certificate signing request (use your LDAP_HOSTNAME value)
|
||||
openssl req -new -key ldap-server.key -out ldap-server.csr \
|
||||
-subj "/CN=ldap.testing.local"
|
||||
-subj "/CN={.env:LDAP_HOSTNAME}"
|
||||
|
||||
# Sign with your CA (adjust paths as needed)
|
||||
openssl x509 -req -in ldap-server.csr \
|
||||
-CA ca-cert.pem -CAkey ca-key.pem \
|
||||
-CAcreateserial -out ldap-server.crt \
|
||||
-days 365 -sha256 \
|
||||
-extfile <(printf "subjectAltName=DNS:ldap.testing.local,DNS:localhost,IP:127.0.0.1")
|
||||
-extfile <(printf "subjectAltName=DNS:{.env:LDAP_HOSTNAME},DNS:localhost,IP:127.0.0.1")
|
||||
|
||||
# Copy to LDAP Docker project
|
||||
cp ldap-server.crt /path/to/ldap_docker/certs/server.crt
|
||||
@@ -147,17 +208,19 @@ cp ca-cert.pem /path/to/ldap_docker/certs/ca.crt
|
||||
|
||||
Once started, the following services are available:
|
||||
|
||||
| Service | URL/Connection | Description |
|
||||
|---------|----------------|-------------|
|
||||
| LDAP | `ldap://localhost:389` | Standard LDAP (unencrypted) |
|
||||
| LDAPS | `ldaps://localhost:636` | LDAP over SSL/TLS |
|
||||
| phpLDAPadmin | `http://localhost:8080` | Web-based administration |
|
||||
| Service | URL/Connection | Description |
|
||||
| ------------ | ----------------------- | --------------------------- |
|
||||
| LDAP | `ldap://localhost:{.env:LDAP_PORT}` | Standard LDAP (unencrypted) |
|
||||
| LDAPS | `ldaps://localhost:{.env:LDAPS_PORT}` | LDAP over SSL/TLS |
|
||||
| phpLDAPadmin | `http://localhost:{.env:PHPLDAPADMIN_PORT}` | Web-based administration |
|
||||
|
||||
### Admin Credentials
|
||||
|
||||
- **Admin DN:** `cn=admin,dc=testing,dc=local`
|
||||
- **Password:** `admin_password`
|
||||
- **Base DN:** `dc=testing,dc=local`
|
||||
- **Admin DN:** `cn=admin,{.env:LDAP_BASE_DN}`
|
||||
- **Password:** `{.env:LDAP_ADMIN_PASSWORD}`
|
||||
- **Base DN:** `{.env:LDAP_BASE_DN}`
|
||||
|
||||
> **Note:** Default values shown. Customize these by creating a `.env` file (see [Configuration](#configuration)).
|
||||
|
||||
### Common Commands
|
||||
|
||||
@@ -174,8 +237,11 @@ make logs
|
||||
# Check status
|
||||
make status
|
||||
|
||||
# Run tests
|
||||
make test-all
|
||||
# Run unit tests
|
||||
make test
|
||||
|
||||
# Verify running server
|
||||
make verify-all
|
||||
|
||||
# Open shell in container
|
||||
make shell
|
||||
@@ -184,16 +250,119 @@ make shell
|
||||
make clean-all
|
||||
```
|
||||
|
||||
## Test Users
|
||||
## Configuration
|
||||
|
||||
You can customize the LDAP server behavior using environment variables. The easiest way is to create a `.env` file in the project root.
|
||||
|
||||
### Quick Configuration
|
||||
|
||||
```bash
|
||||
# Copy the example configuration
|
||||
cp .env.example .env
|
||||
|
||||
# Edit with your preferred settings
|
||||
nano .env # or vim, code, etc.
|
||||
```
|
||||
|
||||
### Available Configuration Options
|
||||
|
||||
The `.env.example` file includes all available options with documentation. Below is a comprehensive reference of all environment variables:
|
||||
|
||||
| Variable | Default Value | Description | Usage Reference |
|
||||
|----------|---------------|-------------|-----------------|
|
||||
| **Domain & Organization** ||||
|
||||
| `LDAP_DOMAIN` | `testing.local` | Your LDAP domain | Used in: [Services](#accessing-the-services), [Test Users](#default-test-users) |
|
||||
| `LDAP_ORGANISATION` | `Testing Organization` | Organization name | Used in LDAP server metadata |
|
||||
| `LDAP_BASE_DN` | `dc=testing,dc=local` | Base Distinguished Name (auto-derived from domain) | Used in: [Services](#accessing-the-services), [Admin Credentials](#admin-credentials), [Test Users](#default-test-users) |
|
||||
| **Credentials** ||||
|
||||
| `LDAP_ADMIN_PASSWORD` | `admin_password` | Admin user password | Used in: [Admin Credentials](#admin-credentials), [Verify Installation](#verify-installation) |
|
||||
| `LDAP_CONFIG_PASSWORD` | `config_password` | Configuration admin password | Used for LDAP server configuration |
|
||||
| **Ports** ||||
|
||||
| `LDAP_PORT` | `389` | Standard LDAP port (unencrypted) | Used in: [Services](#accessing-the-services), [Testing Authentication](#testing-authentication) |
|
||||
| `LDAPS_PORT` | `636` | LDAPS/SSL port (encrypted) | Used in: [Services](#accessing-the-services), [Testing Authentication](#testing-authentication) |
|
||||
| `PHPLDAPADMIN_PORT` | `8080` | Web UI port | Used in: [Services](#accessing-the-services), [Verify Installation](#verify-installation) |
|
||||
| **SSL/TLS** ||||
|
||||
| `LDAP_TLS` | `true` | Enable TLS support | Controls SSL/TLS functionality |
|
||||
| `LDAP_TLS_CRT_FILENAME` | `server.crt` | Server certificate filename | Used in: [Custom Certificates](#custom-certificates-dev-ca) |
|
||||
| `LDAP_TLS_KEY_FILENAME` | `server.key` | Server private key filename | Used in: [Custom Certificates](#custom-certificates-dev-ca) |
|
||||
| `LDAP_TLS_CA_CRT_FILENAME` | `ca.crt` | CA certificate filename | Used in: [Custom Certificates](#custom-certificates-dev-ca) |
|
||||
| `LDAP_TLS_VERIFY_CLIENT` | `try` | Client certificate verification mode | Options: `never`, `allow`, `try`, `demand` |
|
||||
| **Container Configuration** ||||
|
||||
| `LDAP_HOSTNAME` | `ldap.testing.local` | LDAP server hostname | Used in certificate validation |
|
||||
| `LDAP_CONTAINER_NAME` | `ldap-server` | LDAP container name | Used in Docker commands |
|
||||
| `PHPLDAPADMIN_CONTAINER_NAME` | `ldap-admin` | phpLDAPadmin container name | Used in Docker commands |
|
||||
| **Other Options** ||||
|
||||
| `LDAP_LOG_LEVEL` | `256` | Logging verbosity | Higher = more verbose |
|
||||
|
||||
> **Tip:** Throughout this documentation, values shown as `{.env:VARIABLE_NAME}` indicate they are configured via these environment variables.
|
||||
|
||||
#### Summary by Category
|
||||
|
||||
##### Domain & Organization
|
||||
- `LDAP_DOMAIN` - Your LDAP domain (default: `testing.local`)
|
||||
- `LDAP_ORGANISATION` - Organization name (default: `Testing Organization`)
|
||||
- `LDAP_BASE_DN` - Base DN, auto-derived from domain if not set
|
||||
|
||||
##### Credentials
|
||||
- `LDAP_ADMIN_PASSWORD` - Admin password (default: `admin_password`)
|
||||
- `LDAP_CONFIG_PASSWORD` - Config admin password (default: `config_password`)
|
||||
|
||||
##### Ports
|
||||
- `LDAP_PORT` - Standard LDAP port (default: `389`)
|
||||
- `LDAPS_PORT` - LDAPS/SSL port (default: `636`)
|
||||
- `PHPLDAPADMIN_PORT` - Web UI port (default: `8080`)
|
||||
|
||||
##### SSL/TLS
|
||||
- `LDAP_TLS` - Enable TLS (default: `true`)
|
||||
- `LDAP_TLS_CRT_FILENAME` - Server cert filename (default: `server.crt`)
|
||||
- `LDAP_TLS_KEY_FILENAME` - Server key filename (default: `server.key`)
|
||||
- `LDAP_TLS_CA_CRT_FILENAME` - CA cert filename (default: `ca.crt`)
|
||||
|
||||
##### Container Names
|
||||
- `LDAP_HOSTNAME` - LDAP server hostname (default: `ldap.testing.local`)
|
||||
- `LDAP_CONTAINER_NAME` - Container name (default: `ldap-server`)
|
||||
- `PHPLDAPADMIN_CONTAINER_NAME` - Admin UI container (default: `ldap-admin`)
|
||||
|
||||
##### Other Options
|
||||
- `LDAP_LOG_LEVEL` - Logging verbosity (default: `256`)
|
||||
- `DEBUG` - Enable debug output (default: `false`)
|
||||
- `TZ` - Timezone (default: `UTC`)
|
||||
|
||||
### Example Custom Configuration
|
||||
|
||||
```bash
|
||||
# .env
|
||||
LDAP_DOMAIN=mycompany.local
|
||||
LDAP_ORGANISATION=My Company
|
||||
LDAP_ADMIN_PASSWORD=mysecurepassword
|
||||
|
||||
# Use different ports
|
||||
LDAP_PORT=1389
|
||||
LDAPS_PORT=1636
|
||||
PHPLDAPADMIN_PORT=9080
|
||||
```
|
||||
|
||||
After creating or modifying `.env`, restart the containers:
|
||||
|
||||
```bash
|
||||
docker-compose down
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
For a complete list of options with detailed descriptions, see `.env.example`.
|
||||
|
||||
## Default Test Users
|
||||
|
||||
The LDAP directory is pre-populated with the following test users:
|
||||
|
||||
| Username | Full Name | Email | Password | UID |
|
||||
|----------|-----------|-------|----------|-----|
|
||||
| admin | Admin User | admin@testing.local | password123 | 10000 |
|
||||
| jdoe | John Doe | jdoe@testing.local | password123 | 10001 |
|
||||
| jsmith | Jane Smith | jsmith@testing.local | password123 | 10002 |
|
||||
| testuser | Test User | testuser@testing.local | password123 | 10003 |
|
||||
| Username | Full Name | Email | Password | UID |
|
||||
| -------- | ---------- | -------------------------------- | ----------- | ----- |
|
||||
| admin | Admin User | admin@`{.env:LDAP_DOMAIN}` | password123 | 10000 |
|
||||
| jdoe | John Doe | jdoe@`{.env:LDAP_DOMAIN}` | password123 | 10001 |
|
||||
| jsmith | Jane Smith | jsmith@`{.env:LDAP_DOMAIN}` | password123 | 10002 |
|
||||
| testuser | Test User | testuser@`{.env:LDAP_DOMAIN}` | password123 | 10003 |
|
||||
|
||||
> **Note:** Email addresses are based on `{.env:LDAP_DOMAIN}` (default: `testing.local`). User DNs follow the pattern `uid=username,ou=people,{.env:LDAP_BASE_DN}`.
|
||||
|
||||
### Test Groups
|
||||
|
||||
@@ -204,57 +373,126 @@ The LDAP directory is pre-populated with the following test users:
|
||||
### Testing Authentication
|
||||
|
||||
```bash
|
||||
# Test with ldapsearch
|
||||
ldapsearch -H ldap://localhost:389 \
|
||||
-D "uid=jdoe,ou=people,dc=testing,dc=local" \
|
||||
# Test with ldapsearch (using default ports from .env)
|
||||
ldapsearch -H ldap://localhost:{.env:LDAP_PORT} \
|
||||
-D "uid=jdoe,ou=people,{.env:LDAP_BASE_DN}" \
|
||||
-w password123 \
|
||||
-b "dc=testing,dc=local" \
|
||||
-b "{.env:LDAP_BASE_DN}" \
|
||||
"(uid=jdoe)"
|
||||
|
||||
# Test LDAPS with SSL
|
||||
ldapsearch -H ldaps://localhost:636 \
|
||||
-D "uid=jdoe,ou=people,dc=testing,dc=local" \
|
||||
ldapsearch -H ldaps://localhost:{.env:LDAPS_PORT} \
|
||||
-D "uid=jdoe,ou=people,{.env:LDAP_BASE_DN}" \
|
||||
-w password123 \
|
||||
-b "dc=testing,dc=local" \
|
||||
-b "{.env:LDAP_BASE_DN}" \
|
||||
"(uid=jdoe)"
|
||||
|
||||
# Best practice: Test auth by searching for user's own entry
|
||||
# This works for all users, not just admin
|
||||
ldapsearch -H ldap://localhost:{.env:LDAP_PORT} \
|
||||
-D "uid=jdoe,ou=people,{.env:LDAP_BASE_DN}" \
|
||||
-w password123 \
|
||||
-b "uid=jdoe,ou=people,{.env:LDAP_BASE_DN}" \
|
||||
-s base \
|
||||
"(objectClass=*)"
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now that your LDAP server is running, you can:
|
||||
|
||||
### 1. Integrate with Your Application
|
||||
|
||||
Point your application to the LDAP server:
|
||||
|
||||
- **LDAP URL:** `ldap://localhost:{.env:LDAP_PORT}`
|
||||
- **LDAPS URL:** `ldaps://localhost:{.env:LDAPS_PORT}`
|
||||
- **Base DN:** `{.env:LDAP_BASE_DN}`
|
||||
|
||||
See `examples/README.md` for code samples and integration patterns.
|
||||
|
||||
### 2. Add Custom Users
|
||||
|
||||
Edit `ldif/01-users.ldif` to add more users or modify existing ones, then reload:
|
||||
|
||||
```bash
|
||||
make down-volumes # WARNING: Deletes all data
|
||||
make start
|
||||
```
|
||||
|
||||
### 3. Customize Configuration
|
||||
|
||||
Create a `.env` file to customize ports, credentials, and behavior:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# Edit .env with your settings
|
||||
docker-compose down && docker-compose up -d
|
||||
```
|
||||
|
||||
See the [Configuration](#configuration) section for all available options.
|
||||
|
||||
### 4. Learn More
|
||||
|
||||
- `certs/README.md` - Certificate management guide
|
||||
- `examples/README.md` - Integration examples and patterns
|
||||
- [OpenLDAP Documentation](https://www.openldap.org/doc/)
|
||||
|
||||
## Management Tools
|
||||
|
||||
This project includes Python-based management tools using UV.
|
||||
You can manage the LDAP server using `docker-compose` commands directly, or use the included Makefile for convenience.
|
||||
|
||||
### Installation
|
||||
### Using Docker Compose (No Additional Tools Required)
|
||||
|
||||
```bash
|
||||
# Install dependencies with UV
|
||||
make install
|
||||
# Server management
|
||||
docker-compose up -d # Start server
|
||||
docker-compose stop # Stop server
|
||||
docker-compose restart # Restart server
|
||||
docker-compose logs -f openldap # View logs
|
||||
docker-compose ps # Check status
|
||||
docker-compose down # Remove containers
|
||||
docker-compose down -v # Remove containers and data
|
||||
|
||||
# Or manually
|
||||
uv sync
|
||||
# Generate certificates (requires Python)
|
||||
python3 scripts/generate_certs.py
|
||||
```
|
||||
|
||||
### CLI Tool Usage
|
||||
### Using Make (Optional Convenience)
|
||||
|
||||
If you have Make installed, use these shortcuts:
|
||||
|
||||
```bash
|
||||
# View available commands
|
||||
uv run ldap-docker --help
|
||||
make help # View all available commands
|
||||
|
||||
# Server management
|
||||
uv run ldap-docker server start
|
||||
uv run ldap-docker server stop
|
||||
uv run ldap-docker server logs -f
|
||||
make start # Start the LDAP server
|
||||
make stop # Stop the LDAP server
|
||||
make restart # Restart the LDAP server
|
||||
make logs # View server logs
|
||||
make status # Check server status
|
||||
|
||||
# Certificate management
|
||||
uv run ldap-docker certs generate
|
||||
uv run ldap-docker certs check
|
||||
make certs-generate # Generate SSL certificates
|
||||
make certs-check # Verify SSL certificates
|
||||
|
||||
# Testing
|
||||
uv run ldap-docker test connection
|
||||
uv run ldap-docker test auth
|
||||
uv run ldap-docker test users
|
||||
# Unit Testing (requires Python + pytest)
|
||||
make test # Run unit tests
|
||||
make test-cov # Run tests with coverage
|
||||
|
||||
# Initialize environment
|
||||
uv run ldap-docker init
|
||||
# Verification (requires running container + ldap3)
|
||||
make verify-connection # Verify LDAP connection
|
||||
make verify-auth # Verify authentication
|
||||
make verify-users # List all users
|
||||
make verify-all # Run all verification checks
|
||||
|
||||
# Setup
|
||||
make install # Install Python dependencies
|
||||
make dev-setup # Complete development setup
|
||||
|
||||
# Cleanup
|
||||
make clean # Clean build artifacts
|
||||
make down-volumes # Remove containers and data
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
@@ -269,7 +507,6 @@ ldap_docker/
|
||||
├── ldif/ # LDAP Data Interchange Format files
|
||||
│ └── 01-users.ldif # Initial user and group data
|
||||
├── scripts/ # Management scripts
|
||||
│ ├── cli.py # CLI tool for managing LDAP
|
||||
│ └── generate_certs.py # Certificate generation utility
|
||||
├── docker-compose.yml # Docker Compose configuration
|
||||
├── pyproject.toml # Python project configuration (UV)
|
||||
@@ -332,14 +569,14 @@ docker-compose ps
|
||||
make logs
|
||||
|
||||
# Test basic connectivity
|
||||
telnet localhost 389
|
||||
telnet localhost {.env:LDAP_PORT}
|
||||
```
|
||||
|
||||
**Problem:** Authentication fails
|
||||
|
||||
```bash
|
||||
# Verify credentials
|
||||
# Default admin: cn=admin,dc=testing,dc=local / admin_password
|
||||
# Default admin: cn=admin,{.env:LDAP_BASE_DN} / {.env:LDAP_ADMIN_PASSWORD}
|
||||
|
||||
# Check LDAP logs
|
||||
docker-compose logs openldap | grep -i error
|
||||
@@ -365,14 +602,14 @@ make start
|
||||
Edit `ldif/01-users.ldif` to add more users or modify existing ones:
|
||||
|
||||
```ldif
|
||||
dn: uid=newuser,ou=people,dc=testing,dc=local
|
||||
dn: uid=newuser,ou=people,{.env:LDAP_BASE_DN}
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: posixAccount
|
||||
objectClass: shadowAccount
|
||||
uid: newuser
|
||||
cn: New User
|
||||
sn: User
|
||||
mail: newuser@testing.local
|
||||
mail: newuser@{.env:LDAP_DOMAIN}
|
||||
userPassword: {SSHA}5en6G6MezRroT3XKqkdPOmY/BFQ=
|
||||
uidNumber: 10004
|
||||
gidNumber: 10004
|
||||
@@ -380,6 +617,8 @@ homeDirectory: /home/newuser
|
||||
loginShell: /bin/bash
|
||||
```
|
||||
|
||||
> **Note:** Replace `{.env:LDAP_BASE_DN}` and `{.env:LDAP_DOMAIN}` with your actual values from the `.env` file (defaults: `dc=testing,dc=local` and `testing.local`).
|
||||
|
||||
Then restart with fresh data:
|
||||
|
||||
```bash
|
||||
@@ -389,39 +628,43 @@ make start
|
||||
|
||||
### Modifying Configuration
|
||||
|
||||
Edit `docker-compose.yml` to change:
|
||||
- Port mappings
|
||||
- Environment variables
|
||||
- Volume mounts
|
||||
- Resource limits
|
||||
|
||||
### Python Development
|
||||
The easiest way to customize settings is with a `.env` file:
|
||||
|
||||
```bash
|
||||
# Install development dependencies
|
||||
cp .env.example .env
|
||||
# Edit .env with your preferences
|
||||
docker-compose down && docker-compose up -d
|
||||
```
|
||||
|
||||
See `.env.example` for all available options with detailed documentation, or the [Configuration](#configuration) section.
|
||||
|
||||
For advanced customization, you can also edit `docker-compose.yml` to change volume mounts, resource limits, or other Docker-specific settings.
|
||||
|
||||
### Python Development (Optional)
|
||||
|
||||
If you're contributing to the Python scripts:
|
||||
|
||||
```bash
|
||||
# Install development dependencies (requires UV)
|
||||
make install-dev
|
||||
|
||||
# Run tests
|
||||
uv run pytest
|
||||
|
||||
# Format code
|
||||
uv run black scripts/
|
||||
|
||||
# Lint code
|
||||
uv run ruff check scripts/
|
||||
|
||||
# Type check
|
||||
uv run mypy scripts/
|
||||
# Format and lint code
|
||||
ruff format scripts/
|
||||
ruff check scripts/
|
||||
```
|
||||
|
||||
### Building on Other Platforms
|
||||
### Cross-Platform Compatibility
|
||||
|
||||
This project is designed to work on:
|
||||
- **MacOS** (with Rancher Desktop or Docker Desktop)
|
||||
- **Linux** (with Docker and Docker Compose)
|
||||
- **Windows** (with Docker Desktop and WSL2)
|
||||
This project uses standard Docker images and docker-compose.yml format, so it works anywhere Docker runs:
|
||||
|
||||
The `docker-compose.yml` uses standard Docker images and should work anywhere Docker runs.
|
||||
- **Linux** - Native Docker
|
||||
- **MacOS** - Docker Desktop, Rancher Desktop, or Colima
|
||||
- **Windows** - Docker Desktop (with WSL2)
|
||||
|
||||
No platform-specific code or configuration needed!
|
||||
|
||||
## Security Notes
|
||||
|
||||
@@ -442,6 +685,7 @@ MIT License - See LICENSE file for details
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! This is a development tool, so:
|
||||
|
||||
1. Keep it simple and easy to use
|
||||
2. Maintain cross-platform compatibility
|
||||
3. Update documentation for any changes
|
||||
|
||||
@@ -6,13 +6,13 @@ This directory should contain your SSL/TLS certificates for the LDAP server.
|
||||
|
||||
The OpenLDAP container expects the following files in this directory:
|
||||
|
||||
- `ca.crt` - Certificate Authority certificate (your dev-ca root certificate)
|
||||
- `server.crt` - Server certificate for ldap.testing.local
|
||||
- `server.key` - Private key for the server certificate
|
||||
- `ca.crt` - Certificate Authority certificate (your dev-ca root certificate) - filename: `{.env:LDAP_TLS_CA_CRT_FILENAME}`
|
||||
- `server.crt` - Server certificate for `{.env:LDAP_HOSTNAME}` - filename: `{.env:LDAP_TLS_CRT_FILENAME}`
|
||||
- `server.key` - Private key for the server certificate - filename: `{.env:LDAP_TLS_KEY_FILENAME}`
|
||||
|
||||
## Using Your Custom Dev-CA Certificates
|
||||
|
||||
If you maintain your own dev-ca (as mentioned), simply copy your certificates here:
|
||||
If you maintain your own dev-ca, simply copy your certificates here:
|
||||
|
||||
```bash
|
||||
# Copy your dev-ca generated certificates to this directory
|
||||
@@ -22,9 +22,9 @@ cp /path/to/your/dev-ca/ca-cert.pem ./ca.crt
|
||||
```
|
||||
|
||||
**Important Notes:**
|
||||
- The server certificate should be issued for the hostname `ldap.testing.local`
|
||||
- The server certificate should be issued for the hostname `{.env:LDAP_HOSTNAME}` (default: `ldap.testing.local`)
|
||||
- The certificate can also include SANs (Subject Alternative Names) like:
|
||||
- `DNS:ldap.testing.local`
|
||||
- `DNS:{.env:LDAP_HOSTNAME}`
|
||||
- `DNS:localhost`
|
||||
- `IP:127.0.0.1`
|
||||
- Ensure the private key is readable by the container (permissions should be 600 or 644)
|
||||
@@ -46,13 +46,13 @@ openssl req -new -x509 -days 3650 -key ca.key -out ca.crt \
|
||||
# Generate server private key
|
||||
openssl genrsa -out server.key 4096
|
||||
|
||||
# Generate server certificate signing request
|
||||
# Generate server certificate signing request (use your LDAP_HOSTNAME value)
|
||||
openssl req -new -key server.key -out server.csr \
|
||||
-subj "/C=US/ST=State/L=City/O=Testing Org/CN=ldap.testing.local"
|
||||
-subj "/C=US/ST=State/L=City/O=Testing Org/CN={.env:LDAP_HOSTNAME}"
|
||||
|
||||
# Create extensions file for SAN
|
||||
# Create extensions file for SAN (use your LDAP_HOSTNAME value)
|
||||
cat > server-ext.cnf <<EOF
|
||||
subjectAltName = DNS:ldap.testing.local,DNS:localhost,IP:127.0.0.1
|
||||
subjectAltName = DNS:{.env:LDAP_HOSTNAME},DNS:localhost,IP:127.0.0.1
|
||||
extendedKeyUsage = serverAuth,clientAuth
|
||||
EOF
|
||||
|
||||
@@ -104,12 +104,12 @@ openssl rsa -noout -modulus -in server.key | openssl md5
|
||||
Once the container is running with your certificates:
|
||||
|
||||
```bash
|
||||
# Test LDAPS connection (port 636)
|
||||
openssl s_client -connect localhost:636 -CAfile certs/ca.crt
|
||||
# Test LDAPS connection
|
||||
openssl s_client -connect localhost:{.env:LDAPS_PORT} -CAfile certs/ca.crt
|
||||
|
||||
# Test with ldapsearch
|
||||
ldapsearch -H ldaps://localhost:636 -x -b "dc=testing,dc=local" \
|
||||
-D "cn=admin,dc=testing,dc=local" -w admin_password
|
||||
ldapsearch -H ldaps://localhost:{.env:LDAPS_PORT} -x -b "{.env:LDAP_BASE_DN}" \
|
||||
-D "cn=admin,{.env:LDAP_BASE_DN}" -w {.env:LDAP_ADMIN_PASSWORD}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
@@ -117,7 +117,7 @@ ldapsearch -H ldaps://localhost:636 -x -b "dc=testing,dc=local" \
|
||||
### Certificate Errors
|
||||
|
||||
If you see TLS/SSL errors in the logs:
|
||||
1. Verify the certificate hostname matches `ldap.testing.local`
|
||||
1. Verify the certificate hostname matches `{.env:LDAP_HOSTNAME}` (default: `ldap.testing.local`)
|
||||
2. Check that all three files are present and readable
|
||||
3. Ensure the server certificate is signed by the CA
|
||||
4. Check certificate expiration dates
|
||||
@@ -131,4 +131,4 @@ If the container fails to start:
|
||||
|
||||
## Security Note
|
||||
|
||||
These certificates are for **development use only**. Never use self-signed or development certificates in production environments.
|
||||
These certificates are for **development use only**. Never use self-signed or development certificates in production environments.
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
This directory contains example scripts and applications demonstrating how to use the LDAP server for authentication and user management.
|
||||
|
||||
> **📝 Note:** In the examples below, values shown as `{.env:VARIABLE_NAME}` are configurable via environment variables in your `.env` file. The actual default values are: `LDAP_PORT=389`, `LDAPS_PORT=636`, `LDAP_BASE_DN=dc=testing,dc=local`, and `LDAP_ADMIN_PASSWORD=admin_password`. See the main [Configuration](../README.md#configuration) section for all available options.
|
||||
|
||||
## Available Examples
|
||||
|
||||
### 1. Simple Authentication (`simple_auth.py`)
|
||||
@@ -9,6 +11,7 @@ This directory contains example scripts and applications demonstrating how to us
|
||||
A Python script demonstrating basic LDAP authentication and user information retrieval.
|
||||
|
||||
**Features:**
|
||||
|
||||
- Authenticate users with username/password
|
||||
- Retrieve detailed user information
|
||||
- Get user group memberships
|
||||
@@ -27,14 +30,14 @@ python examples/simple_auth.py --username jsmith --password password123
|
||||
python examples/simple_auth.py --list-users
|
||||
|
||||
# Use a different LDAP server
|
||||
python examples/simple_auth.py --server ldaps://localhost:636
|
||||
python examples/simple_auth.py --server ldaps://localhost:{.env:LDAPS_PORT}
|
||||
```
|
||||
|
||||
**Example Output:**
|
||||
|
||||
```
|
||||
🔐 LDAP Authentication Example
|
||||
Server: ldap://localhost:389
|
||||
LDAP Authentication Example
|
||||
Server: ldap://localhost:{.env:LDAP_PORT}
|
||||
|
||||
Attempting to authenticate user: jdoe
|
||||
✅ Authentication successful for user: jdoe
|
||||
@@ -52,7 +55,7 @@ Last Name: Doe
|
||||
Email: jdoe@testing.local
|
||||
UID Number: 10001
|
||||
GID Number: 10001
|
||||
DN: uid=jdoe,ou=people,dc=testing,dc=local
|
||||
DN: uid=jdoe,ou=people,{.env:LDAP_BASE_DN}
|
||||
==================================================
|
||||
|
||||
Fetching user groups...
|
||||
@@ -69,17 +72,17 @@ User belongs to 2 group(s):
|
||||
from ldap3 import Server, Connection
|
||||
|
||||
# Connect and authenticate
|
||||
server = Server('ldap://localhost:389')
|
||||
server = Server('ldap://localhost:{.env:LDAP_PORT}')
|
||||
conn = Connection(
|
||||
server,
|
||||
user='uid=jdoe,ou=people,dc=testing,dc=local',
|
||||
user='uid=jdoe,ou=people,{.env:LDAP_BASE_DN}',
|
||||
password='password123',
|
||||
auto_bind=True
|
||||
)
|
||||
|
||||
# Search for users
|
||||
conn.search(
|
||||
'dc=testing,dc=local',
|
||||
'{.env:LDAP_BASE_DN}',
|
||||
'(objectClass=inetOrgPerson)',
|
||||
attributes=['uid', 'cn', 'mail']
|
||||
)
|
||||
@@ -94,243 +97,17 @@ conn.unbind()
|
||||
|
||||
```bash
|
||||
# Search for a user
|
||||
ldapsearch -H ldap://localhost:389 \
|
||||
-D "cn=admin,dc=testing,dc=local" \
|
||||
-w admin_password \
|
||||
-b "dc=testing,dc=local" \
|
||||
ldapsearch -H ldap://localhost:{.env:LDAP_PORT} \
|
||||
-D "cn=admin,{.env:LDAP_BASE_DN}" \
|
||||
-w {.env:LDAP_ADMIN_PASSWORD} \
|
||||
-b "{.env:LDAP_BASE_DN}" \
|
||||
"(uid=jdoe)"
|
||||
|
||||
# List all users
|
||||
ldapsearch -H ldap://localhost:389 \
|
||||
-D "cn=admin,dc=testing,dc=local" \
|
||||
-w admin_password \
|
||||
-b "ou=people,dc=testing,dc=local" \
|
||||
ldapsearch -H ldap://localhost:{.env:LDAP_PORT} \
|
||||
-D "cn=admin,{.env:LDAP_BASE_DN}" \
|
||||
-w {.env:LDAP_ADMIN_PASSWORD} \
|
||||
-b "ou=people,{.env:LDAP_BASE_DN}" \
|
||||
"(objectClass=inetOrgPerson)" \
|
||||
uid cn mail
|
||||
```
|
||||
|
||||
### Web Application Integration
|
||||
|
||||
#### Flask Example
|
||||
|
||||
```python
|
||||
from flask import Flask, request, jsonify
|
||||
from ldap3 import Server, Connection
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/login', methods=['POST'])
|
||||
def login():
|
||||
username = request.json.get('username')
|
||||
password = request.json.get('password')
|
||||
|
||||
server = Server('ldap://localhost:389')
|
||||
user_dn = f'uid={username},ou=people,dc=testing,dc=local'
|
||||
|
||||
try:
|
||||
conn = Connection(server, user=user_dn, password=password)
|
||||
if conn.bind():
|
||||
return jsonify({'status': 'success', 'message': 'Authenticated'})
|
||||
else:
|
||||
return jsonify({'status': 'error', 'message': 'Invalid credentials'}), 401
|
||||
except:
|
||||
return jsonify({'status': 'error', 'message': 'Authentication failed'}), 401
|
||||
```
|
||||
|
||||
#### Django Example
|
||||
|
||||
```python
|
||||
# settings.py
|
||||
import ldap
|
||||
from django_auth_ldap.config import LDAPSearch
|
||||
|
||||
AUTH_LDAP_SERVER_URI = "ldap://localhost:389"
|
||||
AUTH_LDAP_BIND_DN = "cn=admin,dc=testing,dc=local"
|
||||
AUTH_LDAP_BIND_PASSWORD = "admin_password"
|
||||
AUTH_LDAP_USER_SEARCH = LDAPSearch(
|
||||
"ou=people,dc=testing,dc=local",
|
||||
ldap.SCOPE_SUBTREE,
|
||||
"(uid=%(user)s)"
|
||||
)
|
||||
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
'django_auth_ldap.backend.LDAPBackend',
|
||||
'django.contrib.auth.backends.ModelBackend',
|
||||
]
|
||||
```
|
||||
|
||||
## Common Integration Patterns
|
||||
|
||||
### 1. Simple Bind Authentication
|
||||
|
||||
The most straightforward approach - try to bind with user credentials:
|
||||
|
||||
```python
|
||||
def authenticate_user(username, password):
|
||||
server = Server('ldap://localhost:389')
|
||||
user_dn = f'uid={username},ou=people,dc=testing,dc=local'
|
||||
conn = Connection(server, user=user_dn, password=password)
|
||||
return conn.bind()
|
||||
```
|
||||
|
||||
### 2. Search and Bind
|
||||
|
||||
Search for the user first, then authenticate:
|
||||
|
||||
```python
|
||||
def authenticate_user(username, password):
|
||||
# First, search for the user with admin credentials
|
||||
server = Server('ldap://localhost:389')
|
||||
admin_conn = Connection(
|
||||
server,
|
||||
user='cn=admin,dc=testing,dc=local',
|
||||
password='admin_password',
|
||||
auto_bind=True
|
||||
)
|
||||
|
||||
admin_conn.search(
|
||||
'ou=people,dc=testing,dc=local',
|
||||
f'(uid={username})',
|
||||
attributes=['dn']
|
||||
)
|
||||
|
||||
if not admin_conn.entries:
|
||||
return False
|
||||
|
||||
user_dn = admin_conn.entries[0].entry_dn
|
||||
admin_conn.unbind()
|
||||
|
||||
# Now authenticate as the user
|
||||
user_conn = Connection(server, user=user_dn, password=password)
|
||||
return user_conn.bind()
|
||||
```
|
||||
|
||||
### 3. Group-Based Authorization
|
||||
|
||||
Check if user belongs to specific groups:
|
||||
|
||||
```python
|
||||
def user_has_role(username, required_group):
|
||||
server = Server('ldap://localhost:389')
|
||||
conn = Connection(
|
||||
server,
|
||||
user='cn=admin,dc=testing,dc=local',
|
||||
password='admin_password',
|
||||
auto_bind=True
|
||||
)
|
||||
|
||||
user_dn = f'uid={username},ou=people,dc=testing,dc=local'
|
||||
|
||||
conn.search(
|
||||
'ou=groups,dc=testing,dc=local',
|
||||
f'(&(objectClass=groupOfNames)(member={user_dn})(cn={required_group}))',
|
||||
attributes=['cn']
|
||||
)
|
||||
|
||||
return len(conn.entries) > 0
|
||||
```
|
||||
|
||||
## Testing Your Integration
|
||||
|
||||
### 1. Start the LDAP Server
|
||||
|
||||
```bash
|
||||
make start
|
||||
```
|
||||
|
||||
### 2. Test Connection
|
||||
|
||||
```bash
|
||||
python examples/simple_auth.py --list-users
|
||||
```
|
||||
|
||||
### 3. Test Authentication
|
||||
|
||||
```bash
|
||||
python examples/simple_auth.py --username jdoe --password password123
|
||||
```
|
||||
|
||||
### 4. Test with Your Application
|
||||
|
||||
Point your application to:
|
||||
- LDAP URL: `ldap://localhost:389`
|
||||
- LDAPS URL: `ldaps://localhost:636` (with SSL)
|
||||
- Base DN: `dc=testing,dc=local`
|
||||
|
||||
## Available Test Accounts
|
||||
|
||||
| Username | Password | Groups | Purpose |
|
||||
|----------|----------|--------|---------|
|
||||
| admin | password123 | admins | Administrative testing |
|
||||
| jdoe | password123 | developers, users | Regular user testing |
|
||||
| jsmith | password123 | developers, users | Regular user testing |
|
||||
| testuser | password123 | users | Basic user testing |
|
||||
|
||||
## SSL/TLS Configuration
|
||||
|
||||
For production-like testing with LDAPS:
|
||||
|
||||
```python
|
||||
import ssl
|
||||
from ldap3 import Server, Connection, Tls
|
||||
|
||||
tls = Tls(
|
||||
ca_certs_file='certs/ca.crt',
|
||||
validate=ssl.CERT_REQUIRED
|
||||
)
|
||||
|
||||
server = Server('ldaps://localhost:636', use_ssl=True, tls=tls)
|
||||
conn = Connection(server, user=user_dn, password=password, auto_bind=True)
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Connection Refused
|
||||
|
||||
```bash
|
||||
# Check if LDAP server is running
|
||||
make status
|
||||
|
||||
# Start if not running
|
||||
make start
|
||||
```
|
||||
|
||||
### Authentication Fails
|
||||
|
||||
```bash
|
||||
# Verify user exists
|
||||
make test-users
|
||||
|
||||
# Check LDAP logs
|
||||
make logs
|
||||
```
|
||||
|
||||
### Python ImportError
|
||||
|
||||
```bash
|
||||
# Install ldap3 library
|
||||
uv pip install ldap3
|
||||
# or
|
||||
pip install ldap3
|
||||
```
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [ldap3 Documentation](https://ldap3.readthedocs.io/)
|
||||
- [LDAP Protocol Overview](https://ldap.com/ldap-protocol/)
|
||||
- [Django LDAP Authentication](https://django-auth-ldap.readthedocs.io/)
|
||||
- [Flask-LDAP3-Login](https://flask-ldap3-login.readthedocs.io/)
|
||||
|
||||
## Contributing Examples
|
||||
|
||||
Have an example for a specific framework or use case? Contributions are welcome!
|
||||
|
||||
Examples we'd love to see:
|
||||
- Express.js / Node.js authentication
|
||||
- Ruby on Rails integration
|
||||
- Go LDAP client
|
||||
- Java Spring Security LDAP
|
||||
- PHP authentication
|
||||
- Docker Compose with application stack
|
||||
|
||||
Submit a pull request with your example!
|
||||
@@ -13,11 +13,10 @@ Usage:
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
try:
|
||||
from ldap3 import ALL, Connection, Server
|
||||
from ldap3.core.exceptions import LDAPException, LDAPBindError
|
||||
from ldap3.core.exceptions import LDAPBindError, LDAPException
|
||||
except ImportError:
|
||||
print("Error: ldap3 library not found.")
|
||||
print("Install it with: uv pip install ldap3")
|
||||
@@ -27,9 +26,9 @@ except ImportError:
|
||||
# Configuration
|
||||
LDAP_PORT = os.environ.get("LDAP_PORT", "389")
|
||||
LDAP_SERVER = f"ldap://localhost:{LDAP_PORT}"
|
||||
LDAP_BASE_DN = "dc=testing,dc=local"
|
||||
LDAP_PEOPLE_OU = "ou=people,dc=testing,dc=local"
|
||||
LDAP_GROUPS_OU = "ou=groups,dc=testing,dc=local"
|
||||
LDAP_BASE_DN = os.environ.get("LDAP_BASE_DN", "dc=testing,dc=local")
|
||||
LDAP_PEOPLE_OU = f"ou=people,{LDAP_BASE_DN}"
|
||||
LDAP_GROUPS_OU = f"ou=groups,{LDAP_BASE_DN}"
|
||||
|
||||
|
||||
class LDAPAuthenticator:
|
||||
@@ -66,8 +65,8 @@ class LDAPAuthenticator:
|
||||
print(f"❌ Authentication failed for user: {username}")
|
||||
return False
|
||||
|
||||
except LDAPBindError as e:
|
||||
print(f"❌ Authentication failed: Invalid credentials")
|
||||
except LDAPBindError:
|
||||
print("❌ Authentication failed: Invalid credentials")
|
||||
return False
|
||||
except LDAPException as e:
|
||||
print(f"❌ LDAP error: {e}")
|
||||
@@ -76,7 +75,7 @@ class LDAPAuthenticator:
|
||||
print(f"❌ Unexpected error: {e}")
|
||||
return False
|
||||
|
||||
def get_user_info(self, username: str, admin_dn: str, admin_password: str) -> Optional[Dict]:
|
||||
def get_user_info(self, username: str, admin_dn: str, admin_password: str) -> dict | None:
|
||||
"""
|
||||
Retrieve detailed information about a user.
|
||||
|
||||
@@ -121,7 +120,7 @@ class LDAPAuthenticator:
|
||||
print(f"Error retrieving user info: {e}")
|
||||
return None
|
||||
|
||||
def get_user_groups(self, username: str, admin_dn: str, admin_password: str) -> List[str]:
|
||||
def get_user_groups(self, username: str, admin_dn: str, admin_password: str) -> list[str]:
|
||||
"""
|
||||
Get all groups that a user belongs to.
|
||||
|
||||
@@ -154,7 +153,7 @@ class LDAPAuthenticator:
|
||||
print(f"Error retrieving user groups: {e}")
|
||||
return []
|
||||
|
||||
def list_all_users(self, admin_dn: str, admin_password: str) -> List[Dict]:
|
||||
def list_all_users(self, admin_dn: str, admin_password: str) -> list[dict]:
|
||||
"""
|
||||
List all users in the directory.
|
||||
|
||||
@@ -192,7 +191,7 @@ class LDAPAuthenticator:
|
||||
return []
|
||||
|
||||
|
||||
def print_user_info(user_info: Dict):
|
||||
def print_user_info(user_info: dict):
|
||||
"""Pretty print user information."""
|
||||
print("\n" + "=" * 50)
|
||||
print("USER INFORMATION")
|
||||
@@ -225,10 +224,10 @@ def main():
|
||||
args = parser.parse_args()
|
||||
|
||||
# Admin credentials for retrieving user info
|
||||
admin_dn = "cn=admin,dc=testing,dc=local"
|
||||
admin_password = "admin_password"
|
||||
admin_dn = os.environ.get("LDAP_ADMIN_DN", f"cn=admin,{LDAP_BASE_DN}")
|
||||
admin_password = os.environ.get("LDAP_ADMIN_PASSWORD", "admin_password")
|
||||
|
||||
print(f"\n🔐 LDAP Authentication Example")
|
||||
print("\nLDAP Authentication Example")
|
||||
print(f"Server: {args.server}\n")
|
||||
|
||||
# Initialize authenticator
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "ldap-docker"
|
||||
version = "0.1.0"
|
||||
description = "A development tool for running OpenLDAP with SSL in Docker"
|
||||
description = "Docker-based OpenLDAP server with SSL/TLS support and test users for development"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.9"
|
||||
license = { text = "MIT" }
|
||||
@@ -12,44 +12,22 @@ authors = [
|
||||
dependencies = [
|
||||
"ldap3>=2.9.1",
|
||||
"cryptography>=41.0.0",
|
||||
"click>=8.1.0",
|
||||
"python-dotenv>=1.0.0",
|
||||
"pyyaml>=6.0.0",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"pytest>=7.4.0",
|
||||
"pytest-cov>=4.1.0",
|
||||
"black>=23.0.0",
|
||||
"ruff>=0.1.0",
|
||||
"mypy>=1.5.0",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
ldap-docker = "scripts.cli:main"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["scripts", "tests"]
|
||||
packages = ["scripts"]
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"pytest>=7.4.0",
|
||||
"pytest-cov>=4.1.0",
|
||||
"black>=23.0.0",
|
||||
"ruff>=0.1.0",
|
||||
"mypy>=1.5.0",
|
||||
]
|
||||
|
||||
[tool.black]
|
||||
line-length = 100
|
||||
target-version = ["py39", "py310", "py311", "py312"]
|
||||
include = '\.pyi?$'
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 100
|
||||
target-version = "py39"
|
||||
@@ -63,20 +41,13 @@ select = [
|
||||
"UP", # pyupgrade
|
||||
]
|
||||
ignore = [
|
||||
"E501", # line too long (handled by black)
|
||||
"E501", # line too long (handled by ruff format)
|
||||
"B008", # do not perform function calls in argument defaults
|
||||
]
|
||||
|
||||
[tool.ruff.per-file-ignores]
|
||||
"__init__.py" = ["F401"]
|
||||
|
||||
[tool.mypy]
|
||||
python_version = "3.9"
|
||||
warn_return_any = true
|
||||
warn_unused_configs = true
|
||||
disallow_untyped_defs = false
|
||||
ignore_missing_imports = true
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
python_files = ["test_*.py"]
|
||||
|
||||
381
quickstart.sh
381
quickstart.sh
@@ -1,381 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Quick Start Script for LDAP Docker
|
||||
# This script will guide you through setting up and starting the LDAP server
|
||||
#
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Emoji support (works on most terminals)
|
||||
CHECK_MARK="✅"
|
||||
CROSS_MARK="❌"
|
||||
WARNING="⚠️"
|
||||
ROCKET="🚀"
|
||||
GEAR="⚙️"
|
||||
|
||||
# Get the directory where this script is located
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
# Functions
|
||||
print_header() {
|
||||
echo -e "${BLUE}================================================${NC}"
|
||||
echo -e "${BLUE}$1${NC}"
|
||||
echo -e "${BLUE}================================================${NC}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}${CHECK_MARK} $1${NC}"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}${CROSS_MARK} $1${NC}"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}${WARNING} $1${NC}"
|
||||
}
|
||||
|
||||
print_info() {
|
||||
echo -e "${BLUE}${GEAR} $1${NC}"
|
||||
}
|
||||
|
||||
# Check if command exists
|
||||
command_exists() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Check Docker
|
||||
check_docker() {
|
||||
print_info "Checking Docker..."
|
||||
|
||||
if command_exists docker; then
|
||||
if docker version >/dev/null 2>&1; then
|
||||
print_success "Docker is installed and running"
|
||||
return 0
|
||||
else
|
||||
print_error "Docker is installed but not running"
|
||||
echo ""
|
||||
echo "Please start Docker or Rancher Desktop and try again."
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
print_error "Docker is not installed"
|
||||
echo ""
|
||||
echo "Please install one of the following:"
|
||||
echo " - Rancher Desktop (recommended for MacOS): https://rancherdesktop.io/"
|
||||
echo " - Docker Desktop: https://www.docker.com/products/docker-desktop"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Check docker-compose
|
||||
check_docker_compose() {
|
||||
print_info "Checking docker-compose..."
|
||||
|
||||
if command_exists docker-compose; then
|
||||
print_success "docker-compose is available"
|
||||
return 0
|
||||
elif docker compose version >/dev/null 2>&1; then
|
||||
print_success "docker compose (v2) is available"
|
||||
return 0
|
||||
else
|
||||
print_warning "docker-compose not found"
|
||||
echo "Docker Compose is usually included with Docker Desktop and Rancher Desktop."
|
||||
echo "If you're using a standalone Docker installation, please install docker-compose."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Check Python
|
||||
check_python() {
|
||||
print_info "Checking Python..."
|
||||
|
||||
if command_exists python3; then
|
||||
PYTHON_VERSION=$(python3 --version | cut -d' ' -f2)
|
||||
print_success "Python ${PYTHON_VERSION} is installed"
|
||||
return 0
|
||||
elif command_exists python; then
|
||||
PYTHON_VERSION=$(python --version | cut -d' ' -f2)
|
||||
print_success "Python ${PYTHON_VERSION} is installed"
|
||||
return 0
|
||||
else
|
||||
print_error "Python is not installed"
|
||||
echo "Please install Python 3.9 or higher."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Check/Install UV
|
||||
check_install_uv() {
|
||||
print_info "Checking UV package manager..."
|
||||
|
||||
if command_exists uv; then
|
||||
UV_VERSION=$(uv --version | cut -d' ' -f2 || echo "unknown")
|
||||
print_success "UV ${UV_VERSION} is installed"
|
||||
return 0
|
||||
else
|
||||
print_warning "UV is not installed"
|
||||
echo ""
|
||||
echo "UV is a fast Python package manager (recommended for this project)."
|
||||
read -p "Would you like to install UV now? [Y/n] " -n 1 -r
|
||||
echo
|
||||
|
||||
if [[ $REPLY =~ ^[Yy]$ ]] || [[ -z $REPLY ]]; then
|
||||
print_info "Installing UV..."
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
|
||||
# Source the shell config to get UV in PATH
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
|
||||
if command_exists uv; then
|
||||
print_success "UV installed successfully"
|
||||
return 0
|
||||
else
|
||||
print_error "UV installation may have completed but it's not in PATH"
|
||||
echo "Please restart your terminal and run this script again."
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
print_warning "Skipping UV installation"
|
||||
echo "You can install dependencies manually with pip if needed."
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Check certificates
|
||||
check_certificates() {
|
||||
print_info "Checking SSL certificates..."
|
||||
|
||||
if [[ -f "certs/ca.crt" && -f "certs/server.crt" && -f "certs/server.key" ]]; then
|
||||
print_success "SSL certificates found"
|
||||
return 0
|
||||
else
|
||||
print_warning "SSL certificates not found"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Generate certificates
|
||||
generate_certificates() {
|
||||
print_info "Generating SSL certificates..."
|
||||
|
||||
if command_exists uv && [[ -f "scripts/generate_certs.py" ]]; then
|
||||
uv run python scripts/generate_certs.py
|
||||
print_success "Certificates generated"
|
||||
elif command_exists python3; then
|
||||
python3 scripts/generate_certs.py
|
||||
print_success "Certificates generated"
|
||||
else
|
||||
print_error "Cannot generate certificates - Python not available"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Install dependencies
|
||||
install_dependencies() {
|
||||
print_info "Installing Python dependencies..."
|
||||
|
||||
if command_exists uv; then
|
||||
uv sync
|
||||
print_success "Dependencies installed with UV"
|
||||
elif command_exists python3 && command_exists pip3; then
|
||||
pip3 install -e .
|
||||
print_success "Dependencies installed with pip"
|
||||
else
|
||||
print_warning "Cannot install dependencies automatically"
|
||||
echo "Please install dependencies manually."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Start LDAP server
|
||||
start_server() {
|
||||
print_info "Starting LDAP server..."
|
||||
|
||||
if command_exists docker-compose; then
|
||||
docker-compose up -d
|
||||
else
|
||||
docker compose up -d
|
||||
fi
|
||||
|
||||
print_success "LDAP server started"
|
||||
|
||||
# Wait a bit for the server to initialize
|
||||
print_info "Waiting for server to initialize (10 seconds)..."
|
||||
sleep 10
|
||||
}
|
||||
|
||||
# Test connection
|
||||
test_connection() {
|
||||
print_info "Testing LDAP connection..."
|
||||
|
||||
# Simple check if container is running
|
||||
if docker ps | grep -q "ldap-server"; then
|
||||
print_success "LDAP server container is running"
|
||||
return 0
|
||||
else
|
||||
print_warning "LDAP server container may not be fully started yet"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Print final information
|
||||
print_final_info() {
|
||||
echo ""
|
||||
print_header "${ROCKET} LDAP Server is Ready! ${ROCKET}"
|
||||
|
||||
echo "Services are now available at:"
|
||||
echo ""
|
||||
echo " ${GREEN}LDAP:${NC} ldap://localhost:389"
|
||||
echo " ${GREEN}LDAPS:${NC} ldaps://localhost:636"
|
||||
echo " ${GREEN}Admin UI:${NC} http://localhost:8080"
|
||||
echo ""
|
||||
echo "Admin Credentials:"
|
||||
echo " ${BLUE}DN:${NC} cn=admin,dc=testing,dc=local"
|
||||
echo " ${BLUE}Password:${NC} admin_password"
|
||||
echo ""
|
||||
echo "Test Users (password: password123):"
|
||||
echo " - jdoe (John Doe)"
|
||||
echo " - jsmith (Jane Smith)"
|
||||
echo " - testuser (Test User)"
|
||||
echo ""
|
||||
echo "Useful Commands:"
|
||||
echo " ${BLUE}make logs${NC} - View server logs"
|
||||
echo " ${BLUE}make test-users${NC} - List all users"
|
||||
echo " ${BLUE}make status${NC} - Check server status"
|
||||
echo " ${BLUE}make stop${NC} - Stop the server"
|
||||
echo " ${BLUE}make help${NC} - See all available commands"
|
||||
echo ""
|
||||
echo "Documentation:"
|
||||
echo " - README.md for full documentation"
|
||||
echo " - certs/README.md for certificate management"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Main script
|
||||
main() {
|
||||
clear
|
||||
print_header "${ROCKET} LDAP Docker Quick Start ${ROCKET}"
|
||||
|
||||
echo "This script will set up and start your LDAP development server."
|
||||
echo ""
|
||||
|
||||
# Step 1: Check prerequisites
|
||||
print_header "Step 1: Checking Prerequisites"
|
||||
|
||||
PREREQ_OK=true
|
||||
|
||||
check_docker || PREREQ_OK=false
|
||||
check_docker_compose || PREREQ_OK=false
|
||||
check_python || PREREQ_OK=false
|
||||
|
||||
if [ "$PREREQ_OK" = false ]; then
|
||||
echo ""
|
||||
print_error "Some prerequisites are missing. Please install them and try again."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
print_success "All prerequisites are met!"
|
||||
echo ""
|
||||
|
||||
# Step 2: UV installation (optional but recommended)
|
||||
print_header "Step 2: Package Manager Setup"
|
||||
check_install_uv
|
||||
HAS_UV=$?
|
||||
echo ""
|
||||
|
||||
# Step 3: SSL Certificates
|
||||
print_header "Step 3: SSL Certificate Setup"
|
||||
|
||||
if check_certificates; then
|
||||
echo ""
|
||||
print_info "Existing certificates will be used."
|
||||
read -p "Regenerate certificates? [y/N] " -n 1 -r
|
||||
echo
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
generate_certificates
|
||||
fi
|
||||
else
|
||||
echo ""
|
||||
echo "SSL certificates are required for LDAPS (secure LDAP)."
|
||||
echo ""
|
||||
echo "You can:"
|
||||
echo " 1. Generate self-signed certificates now (recommended for quick start)"
|
||||
echo " 2. Copy certificates from your dev-ca manually to certs/"
|
||||
echo ""
|
||||
read -p "Generate self-signed certificates now? [Y/n] " -n 1 -r
|
||||
echo
|
||||
|
||||
if [[ $REPLY =~ ^[Yy]$ ]] || [[ -z $REPLY ]]; then
|
||||
generate_certificates
|
||||
else
|
||||
print_info "Please copy your certificates to certs/ directory:"
|
||||
echo " - certs/ca.crt"
|
||||
echo " - certs/server.crt"
|
||||
echo " - certs/server.key"
|
||||
echo ""
|
||||
read -p "Press Enter when ready to continue..."
|
||||
|
||||
if ! check_certificates; then
|
||||
print_error "Certificates still not found. Exiting."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Step 4: Install dependencies (optional)
|
||||
if [ $HAS_UV -eq 0 ]; then
|
||||
print_header "Step 4: Installing Dependencies"
|
||||
install_dependencies
|
||||
echo ""
|
||||
else
|
||||
print_info "Skipping dependency installation (UV not available)"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Step 5: Start server
|
||||
print_header "Step 5: Starting LDAP Server"
|
||||
start_server
|
||||
echo ""
|
||||
|
||||
# Step 6: Test
|
||||
print_header "Step 6: Verifying Installation"
|
||||
test_connection
|
||||
echo ""
|
||||
|
||||
# Success!
|
||||
print_final_info
|
||||
|
||||
# Offer to view logs
|
||||
echo ""
|
||||
read -p "View server logs now? [y/N] " -n 1 -r
|
||||
echo
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo ""
|
||||
print_info "Showing logs (press Ctrl+C to exit)..."
|
||||
sleep 2
|
||||
if command_exists docker-compose; then
|
||||
docker-compose logs -f openldap
|
||||
else
|
||||
docker compose logs -f openldap
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Run main function
|
||||
main
|
||||
|
||||
exit 0
|
||||
@@ -5,9 +5,7 @@ This package contains management and utility scripts for the LDAP Docker
|
||||
development environment.
|
||||
|
||||
Modules:
|
||||
- cli: Command-line interface for managing LDAP server
|
||||
- generate_certs: SSL/TLS certificate generation utility
|
||||
"""
|
||||
|
||||
__version__ = "0.1.0"
|
||||
__all__ = ["cli", "generate_certs"]
|
||||
|
||||
464
scripts/cli.py
464
scripts/cli.py
@@ -1,464 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
CLI tool for managing the LDAP Docker development environment.
|
||||
|
||||
This tool provides convenient commands for starting, stopping, and testing
|
||||
the LDAP server, as well as managing test users and certificates.
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
import click
|
||||
|
||||
try:
|
||||
import ldap3
|
||||
from ldap3 import ALL, Connection, Server
|
||||
from ldap3.core.exceptions import LDAPException
|
||||
except ImportError:
|
||||
ldap3 = None
|
||||
|
||||
|
||||
# Constants
|
||||
PROJECT_ROOT = Path(__file__).parent.parent
|
||||
CERTS_DIR = PROJECT_ROOT / "certs"
|
||||
LDIF_DIR = PROJECT_ROOT / "ldif"
|
||||
DEFAULT_HOST = "localhost"
|
||||
DEFAULT_PORT = int(os.environ.get("LDAP_PORT", "389"))
|
||||
DEFAULT_LDAPS_PORT = int(os.environ.get("LDAPS_PORT", "636"))
|
||||
DEFAULT_BASE_DN = "dc=testing,dc=local"
|
||||
DEFAULT_ADMIN_DN = "cn=admin,dc=testing,dc=local"
|
||||
DEFAULT_ADMIN_PASSWORD = "admin_password"
|
||||
|
||||
|
||||
def run_command(cmd: list[str], cwd: Optional[Path] = None, check: bool = True) -> subprocess.CompletedProcess:
|
||||
"""Run a shell command and return the result."""
|
||||
if cwd is None:
|
||||
cwd = PROJECT_ROOT
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
cwd=cwd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=check,
|
||||
)
|
||||
return result
|
||||
except subprocess.CalledProcessError as e:
|
||||
click.echo(f"Error running command: {' '.join(cmd)}", err=True)
|
||||
click.echo(f"Exit code: {e.returncode}", err=True)
|
||||
if e.stdout:
|
||||
click.echo(f"stdout: {e.stdout}", err=True)
|
||||
if e.stderr:
|
||||
click.echo(f"stderr: {e.stderr}", err=True)
|
||||
raise
|
||||
|
||||
|
||||
def check_docker():
|
||||
"""Check if Docker is available."""
|
||||
try:
|
||||
result = run_command(["docker", "version"], check=False)
|
||||
if result.returncode != 0:
|
||||
click.echo("Error: Docker is not running or not installed.", err=True)
|
||||
click.echo("Please ensure Docker (or Rancher Desktop) is running.", err=True)
|
||||
sys.exit(1)
|
||||
except FileNotFoundError:
|
||||
click.echo("Error: Docker command not found.", err=True)
|
||||
click.echo("Please install Docker or Rancher Desktop.", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def check_certificates():
|
||||
"""Check if SSL certificates exist."""
|
||||
required_files = ["ca.crt", "server.crt", "server.key"]
|
||||
missing = [f for f in required_files if not (CERTS_DIR / f).exists()]
|
||||
|
||||
if missing:
|
||||
click.echo("⚠️ Warning: Missing SSL certificate files:", err=True)
|
||||
for f in missing:
|
||||
click.echo(f" - {CERTS_DIR / f}", err=True)
|
||||
click.echo("\nYou can:", err=True)
|
||||
click.echo(" 1. Copy your dev-ca certificates to the certs/ directory", err=True)
|
||||
click.echo(" 2. Generate self-signed certificates: ldap-docker certs generate", err=True)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@click.group()
|
||||
@click.version_option(version="0.1.0")
|
||||
def cli():
|
||||
"""LDAP Docker Development Tool - Manage your OpenLDAP development environment."""
|
||||
pass
|
||||
|
||||
|
||||
@cli.group()
|
||||
def server():
|
||||
"""Manage the LDAP server container."""
|
||||
pass
|
||||
|
||||
|
||||
@server.command("start")
|
||||
@click.option("--detach", "-d", is_flag=True, default=True, help="Run in detached mode")
|
||||
@click.option("--build", is_flag=True, help="Build images before starting")
|
||||
def server_start(detach: bool, build: bool):
|
||||
"""Start the LDAP server and optional phpLDAPadmin."""
|
||||
check_docker()
|
||||
check_certificates()
|
||||
|
||||
cmd = ["docker-compose", "up"]
|
||||
if detach:
|
||||
cmd.append("-d")
|
||||
if build:
|
||||
cmd.append("--build")
|
||||
|
||||
click.echo("Starting LDAP server...")
|
||||
result = run_command(cmd)
|
||||
|
||||
if result.returncode == 0:
|
||||
click.echo("✅ LDAP server started successfully!")
|
||||
click.echo(f"\nLDAP server is available at:")
|
||||
click.echo(f" - LDAP: ldap://localhost:389")
|
||||
click.echo(f" - LDAPS: ldaps://localhost:636")
|
||||
click.echo(f" - Admin: http://localhost:8080 (phpLDAPadmin)")
|
||||
click.echo(f"\nAdmin credentials:")
|
||||
click.echo(f" - DN: {DEFAULT_ADMIN_DN}")
|
||||
click.echo(f" - Password: {DEFAULT_ADMIN_PASSWORD}")
|
||||
|
||||
if detach:
|
||||
click.echo("\nWaiting for server to be ready...")
|
||||
time.sleep(5)
|
||||
# Try to check if server is responding
|
||||
result = run_command(
|
||||
["docker-compose", "ps", "--filter", "status=running"],
|
||||
check=False
|
||||
)
|
||||
if "ldap-server" in result.stdout:
|
||||
click.echo("✅ Server is running")
|
||||
|
||||
|
||||
@server.command("stop")
|
||||
def server_stop():
|
||||
"""Stop the LDAP server."""
|
||||
check_docker()
|
||||
|
||||
click.echo("Stopping LDAP server...")
|
||||
run_command(["docker-compose", "stop"])
|
||||
click.echo("✅ LDAP server stopped")
|
||||
|
||||
|
||||
@server.command("restart")
|
||||
def server_restart():
|
||||
"""Restart the LDAP server."""
|
||||
check_docker()
|
||||
|
||||
click.echo("Restarting LDAP server...")
|
||||
run_command(["docker-compose", "restart"])
|
||||
click.echo("✅ LDAP server restarted")
|
||||
|
||||
click.echo("\nWaiting for server to be ready...")
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
@server.command("down")
|
||||
@click.option("--volumes", "-v", is_flag=True, help="Remove volumes (deletes all data)")
|
||||
def server_down(volumes: bool):
|
||||
"""Stop and remove the LDAP server containers."""
|
||||
check_docker()
|
||||
|
||||
cmd = ["docker-compose", "down"]
|
||||
if volumes:
|
||||
if not click.confirm("⚠️ This will delete all LDAP data. Continue?"):
|
||||
click.echo("Aborted.")
|
||||
return
|
||||
cmd.append("-v")
|
||||
|
||||
click.echo("Removing LDAP server containers...")
|
||||
run_command(cmd)
|
||||
click.echo("✅ Containers removed")
|
||||
|
||||
|
||||
@server.command("logs")
|
||||
@click.option("--follow", "-f", is_flag=True, help="Follow log output")
|
||||
@click.option("--tail", "-n", default=100, help="Number of lines to show from the end")
|
||||
@click.option("--service", default="openldap", help="Service to show logs for")
|
||||
def server_logs(follow: bool, tail: int, service: str):
|
||||
"""View LDAP server logs."""
|
||||
check_docker()
|
||||
|
||||
cmd = ["docker-compose", "logs", f"--tail={tail}"]
|
||||
if follow:
|
||||
cmd.append("-f")
|
||||
cmd.append(service)
|
||||
|
||||
# For follow mode, we want to pass through to the terminal
|
||||
try:
|
||||
subprocess.run(cmd, cwd=PROJECT_ROOT)
|
||||
except KeyboardInterrupt:
|
||||
click.echo("\n")
|
||||
|
||||
|
||||
@server.command("status")
|
||||
def server_status():
|
||||
"""Check LDAP server status."""
|
||||
check_docker()
|
||||
|
||||
result = run_command(["docker-compose", "ps"], check=False)
|
||||
click.echo(result.stdout)
|
||||
|
||||
|
||||
@cli.group()
|
||||
def certs():
|
||||
"""Manage SSL/TLS certificates."""
|
||||
pass
|
||||
|
||||
|
||||
@certs.command("generate")
|
||||
@click.option("--force", is_flag=True, help="Overwrite existing certificates")
|
||||
@click.option("--hostname", default="ldap.testing.local", help="Server hostname")
|
||||
def certs_generate(force: bool, hostname: str):
|
||||
"""Generate self-signed SSL certificates for development."""
|
||||
script_path = PROJECT_ROOT / "scripts" / "generate_certs.py"
|
||||
|
||||
if not script_path.exists():
|
||||
click.echo(f"Error: Certificate generation script not found: {script_path}", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
cmd = [sys.executable, str(script_path), "--hostname", hostname]
|
||||
if force:
|
||||
cmd.append("--force")
|
||||
|
||||
try:
|
||||
subprocess.run(cmd, check=True)
|
||||
except subprocess.CalledProcessError:
|
||||
click.echo("Failed to generate certificates", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@certs.command("check")
|
||||
def certs_check():
|
||||
"""Verify SSL certificates."""
|
||||
required_files = {
|
||||
"ca.crt": "CA Certificate",
|
||||
"server.crt": "Server Certificate",
|
||||
"server.key": "Server Private Key",
|
||||
}
|
||||
|
||||
click.echo("Checking SSL certificates...\n")
|
||||
|
||||
all_exist = True
|
||||
for filename, description in required_files.items():
|
||||
filepath = CERTS_DIR / filename
|
||||
if filepath.exists():
|
||||
size = filepath.stat().st_size
|
||||
click.echo(f"✅ {description}: {filepath} ({size} bytes)")
|
||||
else:
|
||||
click.echo(f"❌ {description}: {filepath} (missing)")
|
||||
all_exist = False
|
||||
|
||||
if all_exist:
|
||||
click.echo("\n✅ All required certificates are present")
|
||||
|
||||
# Try to verify the certificate chain
|
||||
try:
|
||||
result = run_command([
|
||||
"openssl", "verify", "-CAfile",
|
||||
str(CERTS_DIR / "ca.crt"),
|
||||
str(CERTS_DIR / "server.crt")
|
||||
], check=False)
|
||||
|
||||
if result.returncode == 0:
|
||||
click.echo("✅ Certificate chain is valid")
|
||||
else:
|
||||
click.echo("⚠️ Certificate chain verification failed")
|
||||
click.echo(result.stderr)
|
||||
except FileNotFoundError:
|
||||
click.echo("ℹ️ OpenSSL not found, skipping certificate verification")
|
||||
else:
|
||||
click.echo("\n❌ Some certificates are missing")
|
||||
click.echo("Run 'ldap-docker certs generate' to create them")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@cli.group()
|
||||
def test():
|
||||
"""Test LDAP server connectivity and queries."""
|
||||
pass
|
||||
|
||||
|
||||
@test.command("connection")
|
||||
@click.option("--host", default=DEFAULT_HOST, help="LDAP server host")
|
||||
@click.option("--port", default=DEFAULT_PORT, help="LDAP server port")
|
||||
@click.option("--use-ssl", is_flag=True, help="Use LDAPS instead of LDAP")
|
||||
def test_connection(host: str, port: int, use_ssl: bool):
|
||||
"""Test basic connection to LDAP server."""
|
||||
if ldap3 is None:
|
||||
click.echo("Error: ldap3 library not installed", err=True)
|
||||
click.echo("Install it with: uv pip install ldap3", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
if use_ssl:
|
||||
port = DEFAULT_LDAPS_PORT
|
||||
url = f"ldaps://{host}:{port}"
|
||||
else:
|
||||
url = f"ldap://{host}:{port}"
|
||||
|
||||
click.echo(f"Testing connection to {url}...")
|
||||
|
||||
try:
|
||||
server = Server(url, get_info=ALL, use_ssl=use_ssl)
|
||||
conn = Connection(server, auto_bind=True)
|
||||
|
||||
click.echo("✅ Successfully connected to LDAP server")
|
||||
click.echo(f"\nServer info:")
|
||||
click.echo(f" Vendor: {server.info.vendor_name if server.info else 'Unknown'}")
|
||||
click.echo(f" Version: {server.info.vendor_version if server.info else 'Unknown'}")
|
||||
|
||||
conn.unbind()
|
||||
|
||||
except LDAPException as e:
|
||||
click.echo(f"❌ Connection failed: {e}", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@test.command("auth")
|
||||
@click.option("--host", default=DEFAULT_HOST, help="LDAP server host")
|
||||
@click.option("--port", default=DEFAULT_PORT, help="LDAP server port")
|
||||
@click.option("--use-ssl", is_flag=True, help="Use LDAPS")
|
||||
@click.option("--user", default=DEFAULT_ADMIN_DN, help="User DN")
|
||||
@click.option("--password", default=DEFAULT_ADMIN_PASSWORD, help="Password")
|
||||
def test_auth(host: str, port: int, use_ssl: bool, user: str, password: str):
|
||||
"""Test authentication with LDAP server."""
|
||||
if ldap3 is None:
|
||||
click.echo("Error: ldap3 library not installed", err=True)
|
||||
click.echo("Install it with: uv pip install ldap3", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
if use_ssl:
|
||||
port = DEFAULT_LDAPS_PORT
|
||||
url = f"ldaps://{host}:{port}"
|
||||
else:
|
||||
url = f"ldap://{host}:{port}"
|
||||
|
||||
click.echo(f"Testing authentication to {url}...")
|
||||
click.echo(f"User: {user}")
|
||||
|
||||
try:
|
||||
server = Server(url, get_info=ALL, use_ssl=use_ssl)
|
||||
conn = Connection(server, user=user, password=password, auto_bind=True)
|
||||
|
||||
click.echo("✅ Authentication successful")
|
||||
|
||||
# Try to perform a simple search
|
||||
conn.search(DEFAULT_BASE_DN, "(objectClass=*)", search_scope="BASE")
|
||||
if conn.entries:
|
||||
click.echo(f"✅ Base DN accessible: {DEFAULT_BASE_DN}")
|
||||
|
||||
conn.unbind()
|
||||
|
||||
except LDAPException as e:
|
||||
click.echo(f"❌ Authentication failed: {e}", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@test.command("users")
|
||||
@click.option("--host", default=DEFAULT_HOST, help="LDAP server host")
|
||||
@click.option("--port", default=DEFAULT_PORT, help="LDAP server port")
|
||||
@click.option("--use-ssl", is_flag=True, help="Use LDAPS")
|
||||
def test_users(host: str, port: int, use_ssl: bool):
|
||||
"""List all users in the LDAP directory."""
|
||||
if ldap3 is None:
|
||||
click.echo("Error: ldap3 library not installed", err=True)
|
||||
click.echo("Install it with: uv pip install ldap3", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
if use_ssl:
|
||||
port = DEFAULT_LDAPS_PORT
|
||||
url = f"ldaps://{host}:{port}"
|
||||
else:
|
||||
url = f"ldap://{host}:{port}"
|
||||
|
||||
try:
|
||||
server = Server(url, get_info=ALL, use_ssl=use_ssl)
|
||||
conn = Connection(
|
||||
server,
|
||||
user=DEFAULT_ADMIN_DN,
|
||||
password=DEFAULT_ADMIN_PASSWORD,
|
||||
auto_bind=True
|
||||
)
|
||||
|
||||
# Search for all users
|
||||
conn.search(
|
||||
DEFAULT_BASE_DN,
|
||||
"(objectClass=inetOrgPerson)",
|
||||
attributes=["uid", "cn", "mail", "uidNumber"]
|
||||
)
|
||||
|
||||
if conn.entries:
|
||||
click.echo(f"Found {len(conn.entries)} user(s):\n")
|
||||
for entry in conn.entries:
|
||||
click.echo(f" - {entry.cn}: {entry.uid} ({entry.mail})")
|
||||
else:
|
||||
click.echo("No users found")
|
||||
|
||||
conn.unbind()
|
||||
|
||||
except LDAPException as e:
|
||||
click.echo(f"❌ Query failed: {e}", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@cli.command("init")
|
||||
def init():
|
||||
"""Initialize the LDAP Docker environment."""
|
||||
click.echo("Initializing LDAP Docker environment...\n")
|
||||
|
||||
# Check Docker
|
||||
click.echo("1. Checking Docker...")
|
||||
check_docker()
|
||||
click.echo(" ✅ Docker is available\n")
|
||||
|
||||
# Check certificates
|
||||
click.echo("2. Checking SSL certificates...")
|
||||
if not check_certificates():
|
||||
if click.confirm("\nGenerate self-signed certificates now?", default=True):
|
||||
certs_generate.callback(force=False, hostname="ldap.testing.local")
|
||||
else:
|
||||
click.echo("\nℹ️ You can generate certificates later with: ldap-docker certs generate")
|
||||
click.echo(" Or copy your dev-ca certificates to the certs/ directory")
|
||||
else:
|
||||
click.echo(" ✅ Certificates are present\n")
|
||||
|
||||
# Start server
|
||||
click.echo("\n3. Starting LDAP server...")
|
||||
if click.confirm("Start the LDAP server now?", default=True):
|
||||
server_start.callback(detach=True, build=False)
|
||||
else:
|
||||
click.echo("\nℹ️ You can start the server later with: ldap-docker server start")
|
||||
|
||||
click.echo("\n✅ Initialization complete!")
|
||||
click.echo("\nUseful commands:")
|
||||
click.echo(" - View logs: ldap-docker server logs -f")
|
||||
click.echo(" - Test connection: ldap-docker test connection")
|
||||
click.echo(" - List users: ldap-docker test users")
|
||||
click.echo(" - Stop server: ldap-docker server stop")
|
||||
|
||||
|
||||
def main():
|
||||
"""Entry point for the CLI."""
|
||||
try:
|
||||
cli()
|
||||
except KeyboardInterrupt:
|
||||
click.echo("\n\nInterrupted by user")
|
||||
sys.exit(130)
|
||||
except Exception as e:
|
||||
click.echo(f"Error: {e}", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -9,7 +9,7 @@ For production, use proper certificates from your dev-ca or a trusted CA.
|
||||
import argparse
|
||||
import ipaddress
|
||||
import sys
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
@@ -17,7 +17,7 @@ try:
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
from cryptography.x509.oid import ExtensionOID, NameOID
|
||||
from cryptography.x509.oid import ExtendedKeyUsageOID, NameOID
|
||||
except ImportError:
|
||||
print("Error: cryptography library not found.")
|
||||
print("Install it with: uv pip install cryptography")
|
||||
@@ -55,8 +55,8 @@ def generate_ca_certificate(
|
||||
.issuer_name(issuer)
|
||||
.public_key(private_key.public_key())
|
||||
.serial_number(x509.random_serial_number())
|
||||
.not_valid_before(datetime.utcnow())
|
||||
.not_valid_after(datetime.utcnow() + timedelta(days=days_valid))
|
||||
.not_valid_before(datetime.now(timezone.utc))
|
||||
.not_valid_after(datetime.now(timezone.utc) + timedelta(days=days_valid))
|
||||
.add_extension(
|
||||
x509.BasicConstraints(ca=True, path_length=None),
|
||||
critical=True,
|
||||
@@ -90,7 +90,7 @@ def generate_server_certificate(
|
||||
ca_cert: x509.Certificate,
|
||||
ca_key: rsa.RSAPrivateKey,
|
||||
hostname: str = "ldap.testing.local",
|
||||
san_list: list[str] = None,
|
||||
san_list: list[str] | None = None,
|
||||
days_valid: int = 365,
|
||||
) -> x509.Certificate:
|
||||
"""Generate a server certificate signed by the CA."""
|
||||
@@ -123,8 +123,8 @@ def generate_server_certificate(
|
||||
.issuer_name(ca_cert.subject)
|
||||
.public_key(private_key.public_key())
|
||||
.serial_number(x509.random_serial_number())
|
||||
.not_valid_before(datetime.utcnow())
|
||||
.not_valid_after(datetime.utcnow() + timedelta(days=days_valid))
|
||||
.not_valid_before(datetime.now(timezone.utc))
|
||||
.not_valid_after(datetime.now(timezone.utc) + timedelta(days=days_valid))
|
||||
.add_extension(
|
||||
x509.SubjectAlternativeName(san_entries),
|
||||
critical=False,
|
||||
@@ -148,7 +148,7 @@ def generate_server_certificate(
|
||||
critical=True,
|
||||
)
|
||||
.add_extension(
|
||||
x509.ExtendedKeyUsage([x509.ExtendedKeyUsageOID.SERVER_AUTH]),
|
||||
x509.ExtendedKeyUsage([ExtendedKeyUsageOID.SERVER_AUTH]),
|
||||
critical=False,
|
||||
)
|
||||
.add_extension(
|
||||
@@ -236,9 +236,7 @@ def main():
|
||||
|
||||
# Check if certificates already exist
|
||||
if not args.force:
|
||||
existing = [
|
||||
p for p in [ca_cert_path, server_cert_path, server_key_path] if p.exists()
|
||||
]
|
||||
existing = [p for p in [ca_cert_path, server_cert_path, server_key_path] if p.exists()]
|
||||
if existing:
|
||||
print("Error: The following certificate files already exist:")
|
||||
for p in existing:
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# LDAP Search Helper
|
||||
# Generates ldapsearch commands configured for your environment
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
# Colors
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Get the directory where this script is located
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
# Load environment variables from .env if it exists
|
||||
if [ -f "$PROJECT_ROOT/.env" ]; then
|
||||
source "$PROJECT_ROOT/.env"
|
||||
fi
|
||||
|
||||
# Set defaults if not in environment
|
||||
LDAP_PORT=${LDAP_PORT:-389}
|
||||
LDAPS_PORT=${LDAPS_PORT:-636}
|
||||
LDAP_BASE_DN=${LDAP_BASE_DN:-dc=testing,dc=local}
|
||||
LDAP_ADMIN_PASSWORD=${LDAP_ADMIN_PASSWORD:-admin_password}
|
||||
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo -e "${BLUE}LDAP Search Command Generator${NC}"
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo ""
|
||||
echo "Configuration:"
|
||||
echo " LDAP Port: $LDAP_PORT"
|
||||
echo " LDAPS Port: $LDAPS_PORT"
|
||||
echo " Base DN: $LDAP_BASE_DN"
|
||||
echo ""
|
||||
|
||||
# Function to print a command example
|
||||
print_cmd() {
|
||||
local description="$1"
|
||||
local command="$2"
|
||||
|
||||
echo -e "${GREEN}# $description${NC}"
|
||||
echo -e "${YELLOW}$command${NC}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo -e "${BLUE}Basic Connection Tests${NC}"
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo ""
|
||||
|
||||
print_cmd "Test server is responding (anonymous bind)" \
|
||||
"ldapsearch -H ldap://localhost:$LDAP_PORT -x -b \"\" -s base"
|
||||
|
||||
print_cmd "Test with admin credentials" \
|
||||
"ldapsearch -H ldap://localhost:$LDAP_PORT -D \"cn=admin,$LDAP_BASE_DN\" -w $LDAP_ADMIN_PASSWORD -b \"$LDAP_BASE_DN\""
|
||||
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo -e "${BLUE}Search Users${NC}"
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo ""
|
||||
|
||||
print_cmd "List all users" \
|
||||
"ldapsearch -H ldap://localhost:$LDAP_PORT -D \"cn=admin,$LDAP_BASE_DN\" -w $LDAP_ADMIN_PASSWORD -b \"ou=people,$LDAP_BASE_DN\" \"(objectClass=inetOrgPerson)\" uid cn mail"
|
||||
|
||||
print_cmd "Search for specific user (jdoe)" \
|
||||
"ldapsearch -H ldap://localhost:$LDAP_PORT -D \"cn=admin,$LDAP_BASE_DN\" -w $LDAP_ADMIN_PASSWORD -b \"$LDAP_BASE_DN\" \"(uid=jdoe)\""
|
||||
|
||||
print_cmd "Test user authentication (as jdoe)" \
|
||||
"ldapsearch -H ldap://localhost:$LDAP_PORT -D \"uid=jdoe,ou=people,$LDAP_BASE_DN\" -w password123 -b \"$LDAP_BASE_DN\" \"(uid=jdoe)\""
|
||||
|
||||
print_cmd "Get all user attributes for jdoe" \
|
||||
"ldapsearch -H ldap://localhost:$LDAP_PORT -D \"cn=admin,$LDAP_BASE_DN\" -w $LDAP_ADMIN_PASSWORD -b \"ou=people,$LDAP_BASE_DN\" \"(uid=jdoe)\" '*' '+'"
|
||||
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo -e "${BLUE}Search Groups${NC}"
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo ""
|
||||
|
||||
print_cmd "List all groups" \
|
||||
"ldapsearch -H ldap://localhost:$LDAP_PORT -D \"cn=admin,$LDAP_BASE_DN\" -w $LDAP_ADMIN_PASSWORD -b \"ou=groups,$LDAP_BASE_DN\" \"(objectClass=groupOfNames)\" cn member"
|
||||
|
||||
print_cmd "Find groups for user jdoe" \
|
||||
"ldapsearch -H ldap://localhost:$LDAP_PORT -D \"cn=admin,$LDAP_BASE_DN\" -w $LDAP_ADMIN_PASSWORD -b \"ou=groups,$LDAP_BASE_DN\" \"(member=uid=jdoe,ou=people,$LDAP_BASE_DN)\" cn"
|
||||
|
||||
print_cmd "Get members of developers group" \
|
||||
"ldapsearch -H ldap://localhost:$LDAP_PORT -D \"cn=admin,$LDAP_BASE_DN\" -w $LDAP_ADMIN_PASSWORD -b \"ou=groups,$LDAP_BASE_DN\" \"(cn=developers)\" member"
|
||||
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo -e "${BLUE}LDAPS (SSL/TLS) Commands${NC}"
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo ""
|
||||
|
||||
print_cmd "Test LDAPS connection" \
|
||||
"ldapsearch -H ldaps://localhost:$LDAPS_PORT -D \"cn=admin,$LDAP_BASE_DN\" -w $LDAP_ADMIN_PASSWORD -b \"$LDAP_BASE_DN\""
|
||||
|
||||
print_cmd "LDAPS with CA certificate verification" \
|
||||
"LDAPTLS_CACERT=$PROJECT_ROOT/certs/ca.crt ldapsearch -H ldaps://localhost:$LDAPS_PORT -D \"cn=admin,$LDAP_BASE_DN\" -w $LDAP_ADMIN_PASSWORD -b \"$LDAP_BASE_DN\""
|
||||
|
||||
print_cmd "Check SSL certificate" \
|
||||
"openssl s_client -connect localhost:$LDAPS_PORT -CAfile $PROJECT_ROOT/certs/ca.crt -showcerts"
|
||||
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo -e "${BLUE}Secure Commands (Password Prompt)${NC}"
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo ""
|
||||
|
||||
print_cmd "Admin search with password prompt (more secure)" \
|
||||
"ldapsearch -H ldap://localhost:$LDAP_PORT -D \"cn=admin,$LDAP_BASE_DN\" -W -b \"$LDAP_BASE_DN\""
|
||||
|
||||
print_cmd "User authentication with password prompt" \
|
||||
"ldapsearch -H ldap://localhost:$LDAP_PORT -D \"uid=jdoe,ou=people,$LDAP_BASE_DN\" -W -b \"$LDAP_BASE_DN\" \"(uid=jdoe)\""
|
||||
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo -e "${BLUE}Advanced Queries${NC}"
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo ""
|
||||
|
||||
print_cmd "Search with wildcard (all users starting with 'j')" \
|
||||
"ldapsearch -H ldap://localhost:$LDAP_PORT -D \"cn=admin,$LDAP_BASE_DN\" -w $LDAP_ADMIN_PASSWORD -b \"ou=people,$LDAP_BASE_DN\" \"(uid=j*)\" uid cn"
|
||||
|
||||
print_cmd "Search by email domain" \
|
||||
"ldapsearch -H ldap://localhost:$LDAP_PORT -D \"cn=admin,$LDAP_BASE_DN\" -w $LDAP_ADMIN_PASSWORD -b \"ou=people,$LDAP_BASE_DN\" \"(mail=*@testing.local)\" uid mail"
|
||||
|
||||
print_cmd "Count total users" \
|
||||
"ldapsearch -H ldap://localhost:$LDAP_PORT -D \"cn=admin,$LDAP_BASE_DN\" -w $LDAP_ADMIN_PASSWORD -b \"ou=people,$LDAP_BASE_DN\" \"(objectClass=inetOrgPerson)\" dn | grep -c '^dn:'"
|
||||
|
||||
print_cmd "Export entire directory to LDIF file" \
|
||||
"ldapsearch -H ldap://localhost:$LDAP_PORT -D \"cn=admin,$LDAP_BASE_DN\" -w $LDAP_ADMIN_PASSWORD -b \"$LDAP_BASE_DN\" -LLL > ldap_backup.ldif"
|
||||
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo -e "${BLUE}Quick Reference${NC}"
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo ""
|
||||
echo "Common flags:"
|
||||
echo " -H : LDAP URI (ldap:// or ldaps://)"
|
||||
echo " -D : Bind DN (user to authenticate as)"
|
||||
echo " -w : Password (visible in process list)"
|
||||
echo " -W : Prompt for password (more secure)"
|
||||
echo " -x : Use simple authentication"
|
||||
echo " -b : Base DN to search from"
|
||||
echo " -s : Scope (base, one, sub)"
|
||||
echo " -LLL: LDIF output without comments"
|
||||
echo ""
|
||||
echo "Test credentials:"
|
||||
echo " Admin: cn=admin,$LDAP_BASE_DN / $LDAP_ADMIN_PASSWORD"
|
||||
echo " Test user: uid=jdoe,ou=people,$LDAP_BASE_DN / password123"
|
||||
echo ""
|
||||
echo -e "${GREEN}Tip:${NC} Copy any command above and run it in your terminal!"
|
||||
echo ""
|
||||
|
||||
# Option to run a command interactively
|
||||
if [ "$1" = "--interactive" ] || [ "$1" = "-i" ]; then
|
||||
echo ""
|
||||
echo "Select a command to run:"
|
||||
echo " 1) List all users"
|
||||
echo " 2) Search for user jdoe"
|
||||
echo " 3) Test user authentication"
|
||||
echo " 4) List all groups"
|
||||
echo " 5) Test LDAPS connection"
|
||||
echo " 6) Custom command"
|
||||
echo ""
|
||||
read -p "Choice (1-6): " choice
|
||||
|
||||
case $choice in
|
||||
1)
|
||||
ldapsearch -H ldap://localhost:$LDAP_PORT -D "cn=admin,$LDAP_BASE_DN" -w $LDAP_ADMIN_PASSWORD -b "ou=people,$LDAP_BASE_DN" "(objectClass=inetOrgPerson)" uid cn mail
|
||||
;;
|
||||
2)
|
||||
ldapsearch -H ldap://localhost:$LDAP_PORT -D "cn=admin,$LDAP_BASE_DN" -w $LDAP_ADMIN_PASSWORD -b "$LDAP_BASE_DN" "(uid=jdoe)"
|
||||
;;
|
||||
3)
|
||||
read -p "Enter password for jdoe: " -s user_pass
|
||||
echo ""
|
||||
ldapsearch -H ldap://localhost:$LDAP_PORT -D "uid=jdoe,ou=people,$LDAP_BASE_DN" -w "$user_pass" -b "$LDAP_BASE_DN" "(uid=jdoe)"
|
||||
;;
|
||||
4)
|
||||
ldapsearch -H ldap://localhost:$LDAP_PORT -D "cn=admin,$LDAP_BASE_DN" -w $LDAP_ADMIN_PASSWORD -b "ou=groups,$LDAP_BASE_DN" "(objectClass=groupOfNames)" cn member
|
||||
;;
|
||||
5)
|
||||
LDAPTLS_CACERT=$PROJECT_ROOT/certs/ca.crt ldapsearch -H ldaps://localhost:$LDAPS_PORT -D "cn=admin,$LDAP_BASE_DN" -w $LDAP_ADMIN_PASSWORD -b "$LDAP_BASE_DN"
|
||||
;;
|
||||
6)
|
||||
read -p "Enter custom filter: " filter
|
||||
ldapsearch -H ldap://localhost:$LDAP_PORT -D "cn=admin,$LDAP_BASE_DN" -w $LDAP_ADMIN_PASSWORD -b "$LDAP_BASE_DN" "$filter"
|
||||
;;
|
||||
*)
|
||||
echo "Invalid choice"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
@@ -5,8 +5,9 @@ This module contains unit tests for the generate_certs.py script.
|
||||
"""
|
||||
|
||||
import tempfile
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, cast
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -14,18 +15,25 @@ try:
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
from cryptography.x509.oid import NameOID
|
||||
from cryptography.x509 import BasicConstraints, SubjectAlternativeName
|
||||
from cryptography.x509.oid import ExtensionOID, NameOID
|
||||
|
||||
CRYPTO_AVAILABLE = True
|
||||
except ImportError:
|
||||
CRYPTO_AVAILABLE = False
|
||||
if TYPE_CHECKING:
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
from cryptography.x509 import BasicConstraints, SubjectAlternativeName
|
||||
from cryptography.x509.oid import ExtensionOID, NameOID
|
||||
|
||||
# Import the module to test
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
if CRYPTO_AVAILABLE:
|
||||
if CRYPTO_AVAILABLE or TYPE_CHECKING:
|
||||
from scripts.generate_certs import (
|
||||
generate_ca_certificate,
|
||||
generate_private_key,
|
||||
@@ -63,10 +71,8 @@ class TestCertificateGeneration:
|
||||
assert cert.subject == cert.issuer
|
||||
|
||||
# Check basic constraints
|
||||
basic_constraints = cert.extensions.get_extension_for_oid(
|
||||
x509.ExtensionOID.BASIC_CONSTRAINTS
|
||||
)
|
||||
assert basic_constraints.value.ca is True
|
||||
basic_constraints = cert.extensions.get_extension_for_oid(ExtensionOID.BASIC_CONSTRAINTS)
|
||||
assert cast(BasicConstraints, basic_constraints.value).ca is True
|
||||
|
||||
def test_generate_ca_certificate_validity(self):
|
||||
"""Test that CA certificate has correct validity period."""
|
||||
@@ -74,11 +80,11 @@ class TestCertificateGeneration:
|
||||
days_valid = 365
|
||||
cert = generate_ca_certificate(key, days_valid=days_valid)
|
||||
|
||||
now = datetime.utcnow()
|
||||
now = datetime.now(timezone.utc)
|
||||
expected_expiry = now + timedelta(days=days_valid)
|
||||
|
||||
# Allow 1 minute tolerance for test execution time
|
||||
assert abs((cert.not_valid_after - expected_expiry).total_seconds()) < 60
|
||||
assert abs((cert.not_valid_after_utc - expected_expiry).total_seconds()) < 60
|
||||
|
||||
def test_generate_server_certificate(self):
|
||||
"""Test that a server certificate can be generated."""
|
||||
@@ -105,9 +111,9 @@ class TestCertificateGeneration:
|
||||
|
||||
# Check basic constraints - should not be a CA
|
||||
basic_constraints = server_cert.extensions.get_extension_for_oid(
|
||||
x509.ExtensionOID.BASIC_CONSTRAINTS
|
||||
ExtensionOID.BASIC_CONSTRAINTS
|
||||
)
|
||||
assert basic_constraints.value.ca is False
|
||||
assert cast(BasicConstraints, basic_constraints.value).ca is False
|
||||
|
||||
def test_generate_server_certificate_san(self):
|
||||
"""Test that server certificate includes Subject Alternative Names."""
|
||||
@@ -126,11 +132,15 @@ class TestCertificateGeneration:
|
||||
|
||||
# Get SAN extension
|
||||
san_ext = server_cert.extensions.get_extension_for_oid(
|
||||
x509.ExtensionOID.SUBJECT_ALTERNATIVE_NAME
|
||||
ExtensionOID.SUBJECT_ALTERNATIVE_NAME
|
||||
)
|
||||
|
||||
# Extract DNS names
|
||||
dns_names = [name.value for name in san_ext.value if isinstance(name, x509.DNSName)]
|
||||
dns_names = [
|
||||
name.value
|
||||
for name in cast(SubjectAlternativeName, san_ext.value)
|
||||
if isinstance(name, x509.DNSName)
|
||||
]
|
||||
|
||||
# Check that all DNS names are present
|
||||
for name in san_list:
|
||||
@@ -191,15 +201,15 @@ class TestCertificateGeneration:
|
||||
|
||||
# Verify server cert is not a CA
|
||||
server_basic_constraints = server_cert.extensions.get_extension_for_oid(
|
||||
x509.ExtensionOID.BASIC_CONSTRAINTS
|
||||
ExtensionOID.BASIC_CONSTRAINTS
|
||||
)
|
||||
assert server_basic_constraints.value.ca is False
|
||||
assert cast(BasicConstraints, server_basic_constraints.value).ca is False
|
||||
|
||||
# Verify CA cert is a CA
|
||||
ca_basic_constraints = ca_cert.extensions.get_extension_for_oid(
|
||||
x509.ExtensionOID.BASIC_CONSTRAINTS
|
||||
ExtensionOID.BASIC_CONSTRAINTS
|
||||
)
|
||||
assert ca_basic_constraints.value.ca is True
|
||||
assert cast(BasicConstraints, ca_basic_constraints.value).ca is True
|
||||
|
||||
|
||||
@pytest.mark.skipif(not CRYPTO_AVAILABLE, reason="cryptography library not available")
|
||||
@@ -219,12 +229,12 @@ class TestCertificateValidation:
|
||||
# Check server certificate extensions
|
||||
ext_oids = [ext.oid for ext in server_cert.extensions]
|
||||
|
||||
assert x509.ExtensionOID.SUBJECT_ALTERNATIVE_NAME in ext_oids
|
||||
assert x509.ExtensionOID.BASIC_CONSTRAINTS in ext_oids
|
||||
assert x509.ExtensionOID.KEY_USAGE in ext_oids
|
||||
assert x509.ExtensionOID.EXTENDED_KEY_USAGE in ext_oids
|
||||
assert x509.ExtensionOID.SUBJECT_KEY_IDENTIFIER in ext_oids
|
||||
assert x509.ExtensionOID.AUTHORITY_KEY_IDENTIFIER in ext_oids
|
||||
assert ExtensionOID.SUBJECT_ALTERNATIVE_NAME in ext_oids
|
||||
assert ExtensionOID.BASIC_CONSTRAINTS in ext_oids
|
||||
assert ExtensionOID.KEY_USAGE in ext_oids
|
||||
assert ExtensionOID.EXTENDED_KEY_USAGE in ext_oids
|
||||
assert ExtensionOID.SUBJECT_KEY_IDENTIFIER in ext_oids
|
||||
assert ExtensionOID.AUTHORITY_KEY_IDENTIFIER in ext_oids
|
||||
|
||||
def test_certificate_validity_dates(self):
|
||||
"""Test that certificates have correct validity dates."""
|
||||
@@ -232,14 +242,14 @@ class TestCertificateValidation:
|
||||
days_valid = 100
|
||||
cert = generate_ca_certificate(key, days_valid=days_valid)
|
||||
|
||||
now = datetime.utcnow()
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
# Check not_valid_before is around now
|
||||
assert abs((cert.not_valid_before - now).total_seconds()) < 60
|
||||
assert abs((cert.not_valid_before_utc - now).total_seconds()) < 60
|
||||
|
||||
# Check not_valid_after is around now + days_valid
|
||||
expected_expiry = now + timedelta(days=days_valid)
|
||||
assert abs((cert.not_valid_after - expected_expiry).total_seconds()) < 60
|
||||
assert abs((cert.not_valid_after_utc - expected_expiry).total_seconds()) < 60
|
||||
|
||||
|
||||
def test_imports():
|
||||
|
||||
Reference in New Issue
Block a user