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/),
|
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).
|
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
|
### Added
|
||||||
|
|
||||||
- Initial release of LDAP Docker development tool
|
- Initial release of LDAP Docker development tool
|
||||||
- OpenLDAP 1.5.0 container with SSL/TLS support
|
- OpenLDAP 1.5.0 container with SSL/TLS support
|
||||||
- phpLDAPadmin web interface for easy administration
|
- phpLDAPadmin web interface for easy administration
|
||||||
- Pre-configured test users and groups for testing.local domain
|
- Pre-configured test users and groups for testing.local domain
|
||||||
- SSL certificate generation script using Python cryptography
|
- SSL certificate generation script using Python cryptography
|
||||||
- Comprehensive CLI tool for managing LDAP server (`ldap-docker` command)
|
|
||||||
- Makefile with convenient shortcuts for common operations
|
- Makefile with convenient shortcuts for common operations
|
||||||
- Interactive quickstart script (`quickstart.sh`) for guided setup
|
- Example Python authentication script demonstrating LDAP integration with environment variable support
|
||||||
- Example Python authentication script demonstrating LDAP integration
|
|
||||||
- Support for custom dev-ca certificates
|
- Support for custom dev-ca certificates
|
||||||
- Persistent Docker volumes for data and configuration
|
- Persistent Docker volumes for data and configuration
|
||||||
- Test suite for certificate generation
|
- 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:
|
- Comprehensive documentation:
|
||||||
- README.md - Full project documentation
|
- README.md - Full project documentation with integrated configuration guide
|
||||||
- GETTING_STARTED.md - Beginner-friendly guide
|
|
||||||
- QUICKREF.md - Quick command reference
|
|
||||||
- certs/README.md - Certificate management 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
|
### Test Data
|
||||||
|
|
||||||
- 4 pre-configured test users (admin, jdoe, jsmith, testuser)
|
- 4 pre-configured test users (admin, jdoe, jsmith, testuser)
|
||||||
- 3 test groups (admins, developers, users)
|
- 3 test groups (admins, developers, users)
|
||||||
- All test users use password: `password123`
|
- All test users use password: `password123`
|
||||||
- Admin credentials: `cn=admin,dc=testing,dc=local` / `admin_password`
|
- Admin credentials: `cn=admin,dc=testing,dc=local` / `admin_password`
|
||||||
|
|
||||||
### Infrastructure
|
### Infrastructure
|
||||||
|
|
||||||
- Docker Compose configuration for easy deployment
|
- Docker Compose configuration for easy deployment
|
||||||
- UV package manager integration for Python dependencies
|
- UV package manager integration for Python dependencies
|
||||||
|
- `.env` file support for configuration
|
||||||
- Cross-platform support (MacOS, Linux, Windows)
|
- Cross-platform support (MacOS, Linux, Windows)
|
||||||
- Rancher Desktop and Docker Desktop compatibility
|
- 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
|
### Fixed
|
||||||
|
|
||||||
- Updated `pyproject.toml` to use `dependency-groups.dev` instead of deprecated `tool.uv.dev-dependencies`
|
- 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
|
- Added `tool.hatch.build.targets.wheel.packages` configuration to fix build errors
|
||||||
- Removed obsolete `version` field from `docker-compose.yml` (Docker Compose v2+ compatibility)
|
- 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 LDAP user password hashes to use proper SSHA format generated by `slappasswd`
|
||||||
- Fixed attribute type conversion in example scripts for uidNumber and gidNumber
|
- 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
|
### Technical Details
|
||||||
- Base DN: `dc=testing,dc=local`
|
|
||||||
- LDAP Port: 389 (standard)
|
- Base DN: `dc=testing,dc=local` (configurable via `LDAP_BASE_DN`)
|
||||||
- LDAPS Port: 636 (SSL/TLS)
|
- LDAP Port: 389 (configurable via `LDAP_PORT`)
|
||||||
- Web Admin Port: 8080
|
- LDAPS Port: 636 (configurable via `LDAPS_PORT`)
|
||||||
- Python 3.9+ required
|
- Web Admin Port: 8080 (configurable via `PHPLDAPADMIN_PORT`)
|
||||||
|
- Python 3.9+ required (optional, for certificate generation and examples)
|
||||||
- Docker/Rancher Desktop required
|
- Docker/Rancher Desktop required
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
### Planned Features
|
### Planned Features
|
||||||
- Additional integration examples (Node.js, Go, Ruby, etc.)
|
|
||||||
- Health check endpoints
|
|
||||||
- Automated backup scripts
|
|
||||||
- Docker image with pre-built configuration
|
- Docker image with pre-built configuration
|
||||||
- Kubernetes/Helm deployment examples
|
|
||||||
- LDAP replication setup guide
|
|
||||||
- Performance tuning guide
|
|
||||||
- Security hardening options
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Release Notes
|
## Release Notes
|
||||||
|
|
||||||
### Version 0.1.0
|
### Version 0.1.0
|
||||||
|
|
||||||
This is the initial release providing a complete LDAP development environment suitable for:
|
This is the initial release providing a complete LDAP development environment suitable for:
|
||||||
|
|
||||||
- Testing LDAP authentication in applications
|
- Testing LDAP authentication in applications
|
||||||
- Development and integration testing
|
- Development and integration testing
|
||||||
- Learning LDAP concepts
|
- Learning LDAP concepts
|
||||||
- Prototyping LDAP-based systems
|
- 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:**
|
**Important Security Notes:**
|
||||||
|
|
||||||
- This tool is for **DEVELOPMENT USE ONLY**
|
- This tool is for **DEVELOPMENT USE ONLY**
|
||||||
- Default passwords are well-known and insecure
|
- Default passwords are well-known and insecure
|
||||||
- Self-signed certificates are not suitable for production
|
- Self-signed certificates are not suitable for production
|
||||||
- Never use this with real user data or in production environments
|
- Never use this with real user data or in production environments
|
||||||
|
|
||||||
### Upgrade Instructions
|
### Upgrade Instructions
|
||||||
|
|
||||||
Not applicable for initial release.
|
Not applicable for initial release.
|
||||||
|
|
||||||
### Breaking Changes
|
### Breaking Changes
|
||||||
|
|
||||||
Not applicable for initial release.
|
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
|
# Load environment variables from .env file if it exists
|
||||||
ifneq (,$(wildcard ./.env))
|
ifneq (,$(wildcard ./.env))
|
||||||
@@ -15,7 +15,7 @@ help: ## Show this help message
|
|||||||
@echo "Usage: make [target]"
|
@echo "Usage: make [target]"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Available targets:"
|
@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
|
install: ## Install Python dependencies with UV
|
||||||
@echo "Installing dependencies with UV..."
|
@echo "Installing dependencies with UV..."
|
||||||
@@ -25,7 +25,7 @@ install: ## Install Python dependencies with UV
|
|||||||
|
|
||||||
install-dev: ## Install development dependencies
|
install-dev: ## Install development dependencies
|
||||||
@echo "Installing development dependencies with UV..."
|
@echo "Installing development dependencies with UV..."
|
||||||
uv sync --all-extras
|
uv sync --group dev
|
||||||
@echo "✅ Development dependencies installed"
|
@echo "✅ Development dependencies installed"
|
||||||
|
|
||||||
init: install certs-check ## Initialize the environment (install deps, check certs)
|
init: install certs-check ## Initialize the environment (install deps, check certs)
|
||||||
@@ -109,23 +109,33 @@ logs-admin: ## View phpLDAPadmin logs
|
|||||||
status: ## Show container status
|
status: ## Show container status
|
||||||
@docker-compose ps
|
@docker-compose ps
|
||||||
|
|
||||||
test-connection: ## Test connection to LDAP server
|
verify-connection: ## Verify connection to LDAP server (requires running container)
|
||||||
@echo "Testing LDAP connection..."
|
@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()"
|
@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
|
verify-auth: ## Verify authentication with admin user (requires running container)
|
||||||
@echo "Testing LDAP authentication..."
|
@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()"
|
@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..."
|
@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()"
|
@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
|
verify-ssl: ## Verify SSL/TLS connection (requires running container)
|
||||||
@echo "Testing LDAPS connection..."
|
@echo "Verifying LDAPS connection..."
|
||||||
openssl s_client -connect localhost:$${LDAPS_PORT:-636} -CAfile certs/ca.crt </dev/null
|
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
|
shell: ## Open a shell in the LDAP container
|
||||||
docker-compose exec openldap bash
|
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 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 "*.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 ".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 d -name "htmlcov" -exec rm -rf {} + 2>/dev/null || true
|
||||||
find . -type f -name ".coverage" -delete 2>/dev/null || true
|
find . -type f -name ".coverage" -delete 2>/dev/null || true
|
||||||
rm -rf build/ dist/ .eggs/
|
rm -rf build/ dist/ .eggs/
|
||||||
@@ -158,8 +168,8 @@ dev-setup: install-dev certs-generate start ## Complete development setup
|
|||||||
@echo ""
|
@echo ""
|
||||||
@echo "Next steps:"
|
@echo "Next steps:"
|
||||||
@echo " - View logs: make logs"
|
@echo " - View logs: make logs"
|
||||||
@echo " - Test connection: make test-connection"
|
@echo " - Verify connection: make verify-connection"
|
||||||
@echo " - List users: make test-users"
|
@echo " - List users: make verify-users"
|
||||||
@echo " - Open admin UI: open http://localhost:8080"
|
@echo " - Open admin UI: open http://localhost:8080"
|
||||||
|
|
||||||
quick-start: certs-check start ## Quick start (assumes certs exist)
|
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
|
# 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
|
## Features
|
||||||
|
|
||||||
- 🔒 **SSL/TLS Support** - LDAPS on port 636 with custom certificate support
|
- **SSL/TLS Support** - LDAPS on port `{.env:LDAPS_PORT}` with custom certificate support
|
||||||
- 👥 **Pre-populated Users** - Test users and groups ready to use
|
- **Pre-populated Users** - Test users and groups ready to use
|
||||||
- 🌐 **Web Admin Interface** - phpLDAPadmin for easy management
|
- **Web Admin Interface** - phpLDAPadmin for easy management
|
||||||
- 🐳 **Docker-based** - Easy deployment with Docker Compose
|
- **Docker-based** - Easy deployment with Docker Compose
|
||||||
- 🔧 **Python Management Tools** - UV-based CLI for convenience
|
- **Fully Configurable** - Customize ports, domain, credentials via `.env` file
|
||||||
- 🍎 **Cross-platform** - Works on MacOS (Rancher Desktop), Linux, and Windows
|
- **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
|
## Table of Contents
|
||||||
|
|
||||||
- [Prerequisites](#prerequisites)
|
- [Prerequisites](#prerequisites)
|
||||||
- [Quick Start](#quick-start)
|
- [Quick Start](#quick-start)
|
||||||
|
- [Verify Installation](#verify-installation)
|
||||||
- [Custom Certificates (Dev-CA)](#custom-certificates-dev-ca)
|
- [Custom Certificates (Dev-CA)](#custom-certificates-dev-ca)
|
||||||
- [Usage](#usage)
|
- [Usage](#usage)
|
||||||
|
- [Configuration](#configuration)
|
||||||
- [Test Users](#test-users)
|
- [Test Users](#test-users)
|
||||||
|
- [Next Steps](#next-steps)
|
||||||
- [Management Tools](#management-tools)
|
- [Management Tools](#management-tools)
|
||||||
- [Project Structure](#project-structure)
|
- [Project Structure](#project-structure)
|
||||||
- [Troubleshooting](#troubleshooting)
|
- [Troubleshooting](#troubleshooting)
|
||||||
@@ -29,67 +35,122 @@ Perfect for local development and testing of applications that need LDAP authent
|
|||||||
|
|
||||||
### Required
|
### Required
|
||||||
|
|
||||||
- **Docker** or **Rancher Desktop** (recommended for MacOS)
|
- **Docker** - Any provider that can run Docker Compose
|
||||||
- MacOS: [Download Rancher Desktop](https://rancherdesktop.io/)
|
- [Docker Desktop](https://www.docker.com/products/docker-desktop) (Windows, Mac, Linux)
|
||||||
- Alternatively: [Docker Desktop](https://www.docker.com/products/docker-desktop)
|
- [Rancher Desktop](https://rancherdesktop.io/) (Alternative for Mac/Linux)
|
||||||
- **Python 3.9+** (for management scripts)
|
- Or native Docker on Linux
|
||||||
- **UV Package Manager** (recommended)
|
|
||||||
|
- **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
|
```bash
|
||||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
```
|
```
|
||||||
|
- **OpenSSL** - For manual certificate verification
|
||||||
### Optional
|
- **LDAP utilities** - Command-line tools (ldapsearch, ldapadd, etc.)
|
||||||
|
|
||||||
- **OpenSSL** (for certificate verification)
|
|
||||||
- **LDAP utilities** (ldapsearch, ldapadd, etc.) for testing
|
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
### Option 1: Using Make (Recommended)
|
### Simple Way (Docker Only)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Install dependencies
|
# 1. Generate certificates (requires Python)
|
||||||
make install
|
python3 scripts/generate_certs.py
|
||||||
|
|
||||||
# 2. Generate certificates (or copy your own - see below)
|
# 2. Start the server
|
||||||
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
|
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
|
|
||||||
# 3. View logs
|
# 3. View logs
|
||||||
docker-compose logs -f openldap
|
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
|
```bash
|
||||||
|
# Complete setup (installs Python deps, generates certs, starts server)
|
||||||
make dev-setup
|
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)
|
## 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
|
```bash
|
||||||
# Copy your certificates to the certs directory
|
# Copy your certificates to the certs directory
|
||||||
@@ -103,15 +164,15 @@ chmod 644 certs/server.crt
|
|||||||
chmod 600 certs/server.key
|
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
|
### Certificate Requirements
|
||||||
|
|
||||||
The OpenLDAP container expects three files in the `certs/` directory:
|
The OpenLDAP container expects three files in the `certs/` directory:
|
||||||
|
|
||||||
- `ca.crt` - Your CA root certificate
|
- `ca.crt` - Your CA root certificate (filename: `{.env:LDAP_TLS_CA_CRT_FILENAME}`)
|
||||||
- `server.crt` - Server certificate for ldap.testing.local
|
- `server.crt` - Server certificate for `{.env:LDAP_HOSTNAME}` (filename: `{.env:LDAP_TLS_CRT_FILENAME}`)
|
||||||
- `server.key` - Private key for the server certificate
|
- `server.key` - Private key for the server certificate (filename: `{.env:LDAP_TLS_KEY_FILENAME}`)
|
||||||
|
|
||||||
### Generating Certificates with Your OpenSSL-based Dev-CA
|
### Generating Certificates with Your OpenSSL-based Dev-CA
|
||||||
|
|
||||||
@@ -124,16 +185,16 @@ cd /path/to/your/dev-ca
|
|||||||
# Generate server key
|
# Generate server key
|
||||||
openssl genrsa -out ldap-server.key 4096
|
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 \
|
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)
|
# Sign with your CA (adjust paths as needed)
|
||||||
openssl x509 -req -in ldap-server.csr \
|
openssl x509 -req -in ldap-server.csr \
|
||||||
-CA ca-cert.pem -CAkey ca-key.pem \
|
-CA ca-cert.pem -CAkey ca-key.pem \
|
||||||
-CAcreateserial -out ldap-server.crt \
|
-CAcreateserial -out ldap-server.crt \
|
||||||
-days 365 -sha256 \
|
-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
|
# Copy to LDAP Docker project
|
||||||
cp ldap-server.crt /path/to/ldap_docker/certs/server.crt
|
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:
|
Once started, the following services are available:
|
||||||
|
|
||||||
| Service | URL/Connection | Description |
|
| Service | URL/Connection | Description |
|
||||||
|---------|----------------|-------------|
|
| ------------ | ----------------------- | --------------------------- |
|
||||||
| LDAP | `ldap://localhost:389` | Standard LDAP (unencrypted) |
|
| LDAP | `ldap://localhost:{.env:LDAP_PORT}` | Standard LDAP (unencrypted) |
|
||||||
| LDAPS | `ldaps://localhost:636` | LDAP over SSL/TLS |
|
| LDAPS | `ldaps://localhost:{.env:LDAPS_PORT}` | LDAP over SSL/TLS |
|
||||||
| phpLDAPadmin | `http://localhost:8080` | Web-based administration |
|
| phpLDAPadmin | `http://localhost:{.env:PHPLDAPADMIN_PORT}` | Web-based administration |
|
||||||
|
|
||||||
### Admin Credentials
|
### Admin Credentials
|
||||||
|
|
||||||
- **Admin DN:** `cn=admin,dc=testing,dc=local`
|
- **Admin DN:** `cn=admin,{.env:LDAP_BASE_DN}`
|
||||||
- **Password:** `admin_password`
|
- **Password:** `{.env:LDAP_ADMIN_PASSWORD}`
|
||||||
- **Base DN:** `dc=testing,dc=local`
|
- **Base DN:** `{.env:LDAP_BASE_DN}`
|
||||||
|
|
||||||
|
> **Note:** Default values shown. Customize these by creating a `.env` file (see [Configuration](#configuration)).
|
||||||
|
|
||||||
### Common Commands
|
### Common Commands
|
||||||
|
|
||||||
@@ -174,8 +237,11 @@ make logs
|
|||||||
# Check status
|
# Check status
|
||||||
make status
|
make status
|
||||||
|
|
||||||
# Run tests
|
# Run unit tests
|
||||||
make test-all
|
make test
|
||||||
|
|
||||||
|
# Verify running server
|
||||||
|
make verify-all
|
||||||
|
|
||||||
# Open shell in container
|
# Open shell in container
|
||||||
make shell
|
make shell
|
||||||
@@ -184,16 +250,119 @@ make shell
|
|||||||
make clean-all
|
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:
|
The LDAP directory is pre-populated with the following test users:
|
||||||
|
|
||||||
| Username | Full Name | Email | Password | UID |
|
| Username | Full Name | Email | Password | UID |
|
||||||
|----------|-----------|-------|----------|-----|
|
| -------- | ---------- | -------------------------------- | ----------- | ----- |
|
||||||
| admin | Admin User | admin@testing.local | password123 | 10000 |
|
| admin | Admin User | admin@`{.env:LDAP_DOMAIN}` | password123 | 10000 |
|
||||||
| jdoe | John Doe | jdoe@testing.local | password123 | 10001 |
|
| jdoe | John Doe | jdoe@`{.env:LDAP_DOMAIN}` | password123 | 10001 |
|
||||||
| jsmith | Jane Smith | jsmith@testing.local | password123 | 10002 |
|
| jsmith | Jane Smith | jsmith@`{.env:LDAP_DOMAIN}` | password123 | 10002 |
|
||||||
| testuser | Test User | testuser@testing.local | password123 | 10003 |
|
| 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
|
### Test Groups
|
||||||
|
|
||||||
@@ -204,57 +373,126 @@ The LDAP directory is pre-populated with the following test users:
|
|||||||
### Testing Authentication
|
### Testing Authentication
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Test with ldapsearch
|
# Test with ldapsearch (using default ports from .env)
|
||||||
ldapsearch -H ldap://localhost:389 \
|
ldapsearch -H ldap://localhost:{.env:LDAP_PORT} \
|
||||||
-D "uid=jdoe,ou=people,dc=testing,dc=local" \
|
-D "uid=jdoe,ou=people,{.env:LDAP_BASE_DN}" \
|
||||||
-w password123 \
|
-w password123 \
|
||||||
-b "dc=testing,dc=local" \
|
-b "{.env:LDAP_BASE_DN}" \
|
||||||
"(uid=jdoe)"
|
"(uid=jdoe)"
|
||||||
|
|
||||||
# Test LDAPS with SSL
|
# Test LDAPS with SSL
|
||||||
ldapsearch -H ldaps://localhost:636 \
|
ldapsearch -H ldaps://localhost:{.env:LDAPS_PORT} \
|
||||||
-D "uid=jdoe,ou=people,dc=testing,dc=local" \
|
-D "uid=jdoe,ou=people,{.env:LDAP_BASE_DN}" \
|
||||||
-w password123 \
|
-w password123 \
|
||||||
-b "dc=testing,dc=local" \
|
-b "{.env:LDAP_BASE_DN}" \
|
||||||
"(uid=jdoe)"
|
"(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
|
## 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
|
```bash
|
||||||
# Install dependencies with UV
|
# Server management
|
||||||
make install
|
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
|
# Generate certificates (requires Python)
|
||||||
uv sync
|
python3 scripts/generate_certs.py
|
||||||
```
|
```
|
||||||
|
|
||||||
### CLI Tool Usage
|
### Using Make (Optional Convenience)
|
||||||
|
|
||||||
|
If you have Make installed, use these shortcuts:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# View available commands
|
make help # View all available commands
|
||||||
uv run ldap-docker --help
|
|
||||||
|
|
||||||
# Server management
|
# Server management
|
||||||
uv run ldap-docker server start
|
make start # Start the LDAP server
|
||||||
uv run ldap-docker server stop
|
make stop # Stop the LDAP server
|
||||||
uv run ldap-docker server logs -f
|
make restart # Restart the LDAP server
|
||||||
|
make logs # View server logs
|
||||||
|
make status # Check server status
|
||||||
|
|
||||||
# Certificate management
|
# Certificate management
|
||||||
uv run ldap-docker certs generate
|
make certs-generate # Generate SSL certificates
|
||||||
uv run ldap-docker certs check
|
make certs-check # Verify SSL certificates
|
||||||
|
|
||||||
# Testing
|
# Unit Testing (requires Python + pytest)
|
||||||
uv run ldap-docker test connection
|
make test # Run unit tests
|
||||||
uv run ldap-docker test auth
|
make test-cov # Run tests with coverage
|
||||||
uv run ldap-docker test users
|
|
||||||
|
|
||||||
# Initialize environment
|
# Verification (requires running container + ldap3)
|
||||||
uv run ldap-docker init
|
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
|
## Project Structure
|
||||||
@@ -269,7 +507,6 @@ ldap_docker/
|
|||||||
├── ldif/ # LDAP Data Interchange Format files
|
├── ldif/ # LDAP Data Interchange Format files
|
||||||
│ └── 01-users.ldif # Initial user and group data
|
│ └── 01-users.ldif # Initial user and group data
|
||||||
├── scripts/ # Management scripts
|
├── scripts/ # Management scripts
|
||||||
│ ├── cli.py # CLI tool for managing LDAP
|
|
||||||
│ └── generate_certs.py # Certificate generation utility
|
│ └── generate_certs.py # Certificate generation utility
|
||||||
├── docker-compose.yml # Docker Compose configuration
|
├── docker-compose.yml # Docker Compose configuration
|
||||||
├── pyproject.toml # Python project configuration (UV)
|
├── pyproject.toml # Python project configuration (UV)
|
||||||
@@ -332,14 +569,14 @@ docker-compose ps
|
|||||||
make logs
|
make logs
|
||||||
|
|
||||||
# Test basic connectivity
|
# Test basic connectivity
|
||||||
telnet localhost 389
|
telnet localhost {.env:LDAP_PORT}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Problem:** Authentication fails
|
**Problem:** Authentication fails
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Verify credentials
|
# 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
|
# Check LDAP logs
|
||||||
docker-compose logs openldap | grep -i error
|
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:
|
Edit `ldif/01-users.ldif` to add more users or modify existing ones:
|
||||||
|
|
||||||
```ldif
|
```ldif
|
||||||
dn: uid=newuser,ou=people,dc=testing,dc=local
|
dn: uid=newuser,ou=people,{.env:LDAP_BASE_DN}
|
||||||
objectClass: inetOrgPerson
|
objectClass: inetOrgPerson
|
||||||
objectClass: posixAccount
|
objectClass: posixAccount
|
||||||
objectClass: shadowAccount
|
objectClass: shadowAccount
|
||||||
uid: newuser
|
uid: newuser
|
||||||
cn: New User
|
cn: New User
|
||||||
sn: User
|
sn: User
|
||||||
mail: newuser@testing.local
|
mail: newuser@{.env:LDAP_DOMAIN}
|
||||||
userPassword: {SSHA}5en6G6MezRroT3XKqkdPOmY/BFQ=
|
userPassword: {SSHA}5en6G6MezRroT3XKqkdPOmY/BFQ=
|
||||||
uidNumber: 10004
|
uidNumber: 10004
|
||||||
gidNumber: 10004
|
gidNumber: 10004
|
||||||
@@ -380,6 +617,8 @@ homeDirectory: /home/newuser
|
|||||||
loginShell: /bin/bash
|
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:
|
Then restart with fresh data:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -389,39 +628,43 @@ make start
|
|||||||
|
|
||||||
### Modifying Configuration
|
### Modifying Configuration
|
||||||
|
|
||||||
Edit `docker-compose.yml` to change:
|
The easiest way to customize settings is with a `.env` file:
|
||||||
- Port mappings
|
|
||||||
- Environment variables
|
|
||||||
- Volume mounts
|
|
||||||
- Resource limits
|
|
||||||
|
|
||||||
### Python Development
|
|
||||||
|
|
||||||
```bash
|
```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
|
make install-dev
|
||||||
|
|
||||||
# Run tests
|
# Run tests
|
||||||
uv run pytest
|
uv run pytest
|
||||||
|
|
||||||
# Format code
|
# Format and lint code
|
||||||
uv run black scripts/
|
ruff format scripts/
|
||||||
|
ruff check scripts/
|
||||||
# Lint code
|
|
||||||
uv run ruff check scripts/
|
|
||||||
|
|
||||||
# Type check
|
|
||||||
uv run mypy scripts/
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Building on Other Platforms
|
### Cross-Platform Compatibility
|
||||||
|
|
||||||
This project is designed to work on:
|
This project uses standard Docker images and docker-compose.yml format, so it works anywhere Docker runs:
|
||||||
- **MacOS** (with Rancher Desktop or Docker Desktop)
|
|
||||||
- **Linux** (with Docker and Docker Compose)
|
|
||||||
- **Windows** (with Docker Desktop and WSL2)
|
|
||||||
|
|
||||||
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
|
## Security Notes
|
||||||
|
|
||||||
@@ -442,6 +685,7 @@ MIT License - See LICENSE file for details
|
|||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Contributions are welcome! This is a development tool, so:
|
Contributions are welcome! This is a development tool, so:
|
||||||
|
|
||||||
1. Keep it simple and easy to use
|
1. Keep it simple and easy to use
|
||||||
2. Maintain cross-platform compatibility
|
2. Maintain cross-platform compatibility
|
||||||
3. Update documentation for any changes
|
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:
|
The OpenLDAP container expects the following files in this directory:
|
||||||
|
|
||||||
- `ca.crt` - Certificate Authority certificate (your dev-ca root certificate)
|
- `ca.crt` - Certificate Authority certificate (your dev-ca root certificate) - filename: `{.env:LDAP_TLS_CA_CRT_FILENAME}`
|
||||||
- `server.crt` - Server certificate for ldap.testing.local
|
- `server.crt` - Server certificate for `{.env:LDAP_HOSTNAME}` - filename: `{.env:LDAP_TLS_CRT_FILENAME}`
|
||||||
- `server.key` - Private key for the server certificate
|
- `server.key` - Private key for the server certificate - filename: `{.env:LDAP_TLS_KEY_FILENAME}`
|
||||||
|
|
||||||
## Using Your Custom Dev-CA Certificates
|
## 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
|
```bash
|
||||||
# Copy your dev-ca generated certificates to this directory
|
# 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:**
|
**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:
|
- The certificate can also include SANs (Subject Alternative Names) like:
|
||||||
- `DNS:ldap.testing.local`
|
- `DNS:{.env:LDAP_HOSTNAME}`
|
||||||
- `DNS:localhost`
|
- `DNS:localhost`
|
||||||
- `IP:127.0.0.1`
|
- `IP:127.0.0.1`
|
||||||
- Ensure the private key is readable by the container (permissions should be 600 or 644)
|
- 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
|
# Generate server private key
|
||||||
openssl genrsa -out server.key 4096
|
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 \
|
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
|
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
|
extendedKeyUsage = serverAuth,clientAuth
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
@@ -104,12 +104,12 @@ openssl rsa -noout -modulus -in server.key | openssl md5
|
|||||||
Once the container is running with your certificates:
|
Once the container is running with your certificates:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Test LDAPS connection (port 636)
|
# Test LDAPS connection
|
||||||
openssl s_client -connect localhost:636 -CAfile certs/ca.crt
|
openssl s_client -connect localhost:{.env:LDAPS_PORT} -CAfile certs/ca.crt
|
||||||
|
|
||||||
# Test with ldapsearch
|
# Test with ldapsearch
|
||||||
ldapsearch -H ldaps://localhost:636 -x -b "dc=testing,dc=local" \
|
ldapsearch -H ldaps://localhost:{.env:LDAPS_PORT} -x -b "{.env:LDAP_BASE_DN}" \
|
||||||
-D "cn=admin,dc=testing,dc=local" -w admin_password
|
-D "cn=admin,{.env:LDAP_BASE_DN}" -w {.env:LDAP_ADMIN_PASSWORD}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
@@ -117,7 +117,7 @@ ldapsearch -H ldaps://localhost:636 -x -b "dc=testing,dc=local" \
|
|||||||
### Certificate Errors
|
### Certificate Errors
|
||||||
|
|
||||||
If you see TLS/SSL errors in the logs:
|
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
|
2. Check that all three files are present and readable
|
||||||
3. Ensure the server certificate is signed by the CA
|
3. Ensure the server certificate is signed by the CA
|
||||||
4. Check certificate expiration dates
|
4. Check certificate expiration dates
|
||||||
@@ -131,4 +131,4 @@ If the container fails to start:
|
|||||||
|
|
||||||
## Security Note
|
## 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.
|
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
|
## Available Examples
|
||||||
|
|
||||||
### 1. Simple Authentication (`simple_auth.py`)
|
### 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.
|
A Python script demonstrating basic LDAP authentication and user information retrieval.
|
||||||
|
|
||||||
**Features:**
|
**Features:**
|
||||||
|
|
||||||
- Authenticate users with username/password
|
- Authenticate users with username/password
|
||||||
- Retrieve detailed user information
|
- Retrieve detailed user information
|
||||||
- Get user group memberships
|
- Get user group memberships
|
||||||
@@ -27,14 +30,14 @@ python examples/simple_auth.py --username jsmith --password password123
|
|||||||
python examples/simple_auth.py --list-users
|
python examples/simple_auth.py --list-users
|
||||||
|
|
||||||
# Use a different LDAP server
|
# 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:**
|
**Example Output:**
|
||||||
|
|
||||||
```
|
```
|
||||||
🔐 LDAP Authentication Example
|
LDAP Authentication Example
|
||||||
Server: ldap://localhost:389
|
Server: ldap://localhost:{.env:LDAP_PORT}
|
||||||
|
|
||||||
Attempting to authenticate user: jdoe
|
Attempting to authenticate user: jdoe
|
||||||
✅ Authentication successful for user: jdoe
|
✅ Authentication successful for user: jdoe
|
||||||
@@ -52,7 +55,7 @@ Last Name: Doe
|
|||||||
Email: jdoe@testing.local
|
Email: jdoe@testing.local
|
||||||
UID Number: 10001
|
UID Number: 10001
|
||||||
GID 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...
|
Fetching user groups...
|
||||||
@@ -69,17 +72,17 @@ User belongs to 2 group(s):
|
|||||||
from ldap3 import Server, Connection
|
from ldap3 import Server, Connection
|
||||||
|
|
||||||
# Connect and authenticate
|
# Connect and authenticate
|
||||||
server = Server('ldap://localhost:389')
|
server = Server('ldap://localhost:{.env:LDAP_PORT}')
|
||||||
conn = Connection(
|
conn = Connection(
|
||||||
server,
|
server,
|
||||||
user='uid=jdoe,ou=people,dc=testing,dc=local',
|
user='uid=jdoe,ou=people,{.env:LDAP_BASE_DN}',
|
||||||
password='password123',
|
password='password123',
|
||||||
auto_bind=True
|
auto_bind=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# Search for users
|
# Search for users
|
||||||
conn.search(
|
conn.search(
|
||||||
'dc=testing,dc=local',
|
'{.env:LDAP_BASE_DN}',
|
||||||
'(objectClass=inetOrgPerson)',
|
'(objectClass=inetOrgPerson)',
|
||||||
attributes=['uid', 'cn', 'mail']
|
attributes=['uid', 'cn', 'mail']
|
||||||
)
|
)
|
||||||
@@ -94,243 +97,17 @@ conn.unbind()
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Search for a user
|
# Search for a user
|
||||||
ldapsearch -H ldap://localhost:389 \
|
ldapsearch -H ldap://localhost:{.env:LDAP_PORT} \
|
||||||
-D "cn=admin,dc=testing,dc=local" \
|
-D "cn=admin,{.env:LDAP_BASE_DN}" \
|
||||||
-w admin_password \
|
-w {.env:LDAP_ADMIN_PASSWORD} \
|
||||||
-b "dc=testing,dc=local" \
|
-b "{.env:LDAP_BASE_DN}" \
|
||||||
"(uid=jdoe)"
|
"(uid=jdoe)"
|
||||||
|
|
||||||
# List all users
|
# List all users
|
||||||
ldapsearch -H ldap://localhost:389 \
|
ldapsearch -H ldap://localhost:{.env:LDAP_PORT} \
|
||||||
-D "cn=admin,dc=testing,dc=local" \
|
-D "cn=admin,{.env:LDAP_BASE_DN}" \
|
||||||
-w admin_password \
|
-w {.env:LDAP_ADMIN_PASSWORD} \
|
||||||
-b "ou=people,dc=testing,dc=local" \
|
-b "ou=people,{.env:LDAP_BASE_DN}" \
|
||||||
"(objectClass=inetOrgPerson)" \
|
"(objectClass=inetOrgPerson)" \
|
||||||
uid cn mail
|
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 argparse
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from typing import Dict, List, Optional
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from ldap3 import ALL, Connection, Server
|
from ldap3 import ALL, Connection, Server
|
||||||
from ldap3.core.exceptions import LDAPException, LDAPBindError
|
from ldap3.core.exceptions import LDAPBindError, LDAPException
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print("Error: ldap3 library not found.")
|
print("Error: ldap3 library not found.")
|
||||||
print("Install it with: uv pip install ldap3")
|
print("Install it with: uv pip install ldap3")
|
||||||
@@ -27,9 +26,9 @@ except ImportError:
|
|||||||
# Configuration
|
# Configuration
|
||||||
LDAP_PORT = os.environ.get("LDAP_PORT", "389")
|
LDAP_PORT = os.environ.get("LDAP_PORT", "389")
|
||||||
LDAP_SERVER = f"ldap://localhost:{LDAP_PORT}"
|
LDAP_SERVER = f"ldap://localhost:{LDAP_PORT}"
|
||||||
LDAP_BASE_DN = "dc=testing,dc=local"
|
LDAP_BASE_DN = os.environ.get("LDAP_BASE_DN", "dc=testing,dc=local")
|
||||||
LDAP_PEOPLE_OU = "ou=people,dc=testing,dc=local"
|
LDAP_PEOPLE_OU = f"ou=people,{LDAP_BASE_DN}"
|
||||||
LDAP_GROUPS_OU = "ou=groups,dc=testing,dc=local"
|
LDAP_GROUPS_OU = f"ou=groups,{LDAP_BASE_DN}"
|
||||||
|
|
||||||
|
|
||||||
class LDAPAuthenticator:
|
class LDAPAuthenticator:
|
||||||
@@ -66,8 +65,8 @@ class LDAPAuthenticator:
|
|||||||
print(f"❌ Authentication failed for user: {username}")
|
print(f"❌ Authentication failed for user: {username}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
except LDAPBindError as e:
|
except LDAPBindError:
|
||||||
print(f"❌ Authentication failed: Invalid credentials")
|
print("❌ Authentication failed: Invalid credentials")
|
||||||
return False
|
return False
|
||||||
except LDAPException as e:
|
except LDAPException as e:
|
||||||
print(f"❌ LDAP error: {e}")
|
print(f"❌ LDAP error: {e}")
|
||||||
@@ -76,7 +75,7 @@ class LDAPAuthenticator:
|
|||||||
print(f"❌ Unexpected error: {e}")
|
print(f"❌ Unexpected error: {e}")
|
||||||
return False
|
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.
|
Retrieve detailed information about a user.
|
||||||
|
|
||||||
@@ -121,7 +120,7 @@ class LDAPAuthenticator:
|
|||||||
print(f"Error retrieving user info: {e}")
|
print(f"Error retrieving user info: {e}")
|
||||||
return None
|
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.
|
Get all groups that a user belongs to.
|
||||||
|
|
||||||
@@ -154,7 +153,7 @@ class LDAPAuthenticator:
|
|||||||
print(f"Error retrieving user groups: {e}")
|
print(f"Error retrieving user groups: {e}")
|
||||||
return []
|
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.
|
List all users in the directory.
|
||||||
|
|
||||||
@@ -192,7 +191,7 @@ class LDAPAuthenticator:
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def print_user_info(user_info: Dict):
|
def print_user_info(user_info: dict):
|
||||||
"""Pretty print user information."""
|
"""Pretty print user information."""
|
||||||
print("\n" + "=" * 50)
|
print("\n" + "=" * 50)
|
||||||
print("USER INFORMATION")
|
print("USER INFORMATION")
|
||||||
@@ -225,10 +224,10 @@ def main():
|
|||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# Admin credentials for retrieving user info
|
# Admin credentials for retrieving user info
|
||||||
admin_dn = "cn=admin,dc=testing,dc=local"
|
admin_dn = os.environ.get("LDAP_ADMIN_DN", f"cn=admin,{LDAP_BASE_DN}")
|
||||||
admin_password = "admin_password"
|
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")
|
print(f"Server: {args.server}\n")
|
||||||
|
|
||||||
# Initialize authenticator
|
# Initialize authenticator
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "ldap-docker"
|
name = "ldap-docker"
|
||||||
version = "0.1.0"
|
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"
|
readme = "README.md"
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
license = { text = "MIT" }
|
license = { text = "MIT" }
|
||||||
@@ -12,44 +12,22 @@ authors = [
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"ldap3>=2.9.1",
|
"ldap3>=2.9.1",
|
||||||
"cryptography>=41.0.0",
|
"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]
|
[build-system]
|
||||||
requires = ["hatchling"]
|
requires = ["hatchling"]
|
||||||
build-backend = "hatchling.build"
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
[tool.hatch.build.targets.wheel]
|
[tool.hatch.build.targets.wheel]
|
||||||
packages = ["scripts", "tests"]
|
packages = ["scripts"]
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = [
|
dev = [
|
||||||
"pytest>=7.4.0",
|
"pytest>=7.4.0",
|
||||||
"pytest-cov>=4.1.0",
|
"pytest-cov>=4.1.0",
|
||||||
"black>=23.0.0",
|
|
||||||
"ruff>=0.1.0",
|
"ruff>=0.1.0",
|
||||||
"mypy>=1.5.0",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.black]
|
|
||||||
line-length = 100
|
|
||||||
target-version = ["py39", "py310", "py311", "py312"]
|
|
||||||
include = '\.pyi?$'
|
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
line-length = 100
|
line-length = 100
|
||||||
target-version = "py39"
|
target-version = "py39"
|
||||||
@@ -63,20 +41,13 @@ select = [
|
|||||||
"UP", # pyupgrade
|
"UP", # pyupgrade
|
||||||
]
|
]
|
||||||
ignore = [
|
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
|
"B008", # do not perform function calls in argument defaults
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.ruff.per-file-ignores]
|
[tool.ruff.per-file-ignores]
|
||||||
"__init__.py" = ["F401"]
|
"__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]
|
[tool.pytest.ini_options]
|
||||||
testpaths = ["tests"]
|
testpaths = ["tests"]
|
||||||
python_files = ["test_*.py"]
|
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.
|
development environment.
|
||||||
|
|
||||||
Modules:
|
Modules:
|
||||||
- cli: Command-line interface for managing LDAP server
|
|
||||||
- generate_certs: SSL/TLS certificate generation utility
|
- generate_certs: SSL/TLS certificate generation utility
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = "0.1.0"
|
__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 argparse
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import sys
|
import sys
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -17,7 +17,7 @@ try:
|
|||||||
from cryptography.hazmat.backends import default_backend
|
from cryptography.hazmat.backends import default_backend
|
||||||
from cryptography.hazmat.primitives import hashes, serialization
|
from cryptography.hazmat.primitives import hashes, serialization
|
||||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||||
from cryptography.x509.oid import ExtensionOID, NameOID
|
from cryptography.x509.oid import ExtendedKeyUsageOID, NameOID
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print("Error: cryptography library not found.")
|
print("Error: cryptography library not found.")
|
||||||
print("Install it with: uv pip install cryptography")
|
print("Install it with: uv pip install cryptography")
|
||||||
@@ -55,8 +55,8 @@ def generate_ca_certificate(
|
|||||||
.issuer_name(issuer)
|
.issuer_name(issuer)
|
||||||
.public_key(private_key.public_key())
|
.public_key(private_key.public_key())
|
||||||
.serial_number(x509.random_serial_number())
|
.serial_number(x509.random_serial_number())
|
||||||
.not_valid_before(datetime.utcnow())
|
.not_valid_before(datetime.now(timezone.utc))
|
||||||
.not_valid_after(datetime.utcnow() + timedelta(days=days_valid))
|
.not_valid_after(datetime.now(timezone.utc) + timedelta(days=days_valid))
|
||||||
.add_extension(
|
.add_extension(
|
||||||
x509.BasicConstraints(ca=True, path_length=None),
|
x509.BasicConstraints(ca=True, path_length=None),
|
||||||
critical=True,
|
critical=True,
|
||||||
@@ -90,7 +90,7 @@ def generate_server_certificate(
|
|||||||
ca_cert: x509.Certificate,
|
ca_cert: x509.Certificate,
|
||||||
ca_key: rsa.RSAPrivateKey,
|
ca_key: rsa.RSAPrivateKey,
|
||||||
hostname: str = "ldap.testing.local",
|
hostname: str = "ldap.testing.local",
|
||||||
san_list: list[str] = None,
|
san_list: list[str] | None = None,
|
||||||
days_valid: int = 365,
|
days_valid: int = 365,
|
||||||
) -> x509.Certificate:
|
) -> x509.Certificate:
|
||||||
"""Generate a server certificate signed by the CA."""
|
"""Generate a server certificate signed by the CA."""
|
||||||
@@ -123,8 +123,8 @@ def generate_server_certificate(
|
|||||||
.issuer_name(ca_cert.subject)
|
.issuer_name(ca_cert.subject)
|
||||||
.public_key(private_key.public_key())
|
.public_key(private_key.public_key())
|
||||||
.serial_number(x509.random_serial_number())
|
.serial_number(x509.random_serial_number())
|
||||||
.not_valid_before(datetime.utcnow())
|
.not_valid_before(datetime.now(timezone.utc))
|
||||||
.not_valid_after(datetime.utcnow() + timedelta(days=days_valid))
|
.not_valid_after(datetime.now(timezone.utc) + timedelta(days=days_valid))
|
||||||
.add_extension(
|
.add_extension(
|
||||||
x509.SubjectAlternativeName(san_entries),
|
x509.SubjectAlternativeName(san_entries),
|
||||||
critical=False,
|
critical=False,
|
||||||
@@ -148,7 +148,7 @@ def generate_server_certificate(
|
|||||||
critical=True,
|
critical=True,
|
||||||
)
|
)
|
||||||
.add_extension(
|
.add_extension(
|
||||||
x509.ExtendedKeyUsage([x509.ExtendedKeyUsageOID.SERVER_AUTH]),
|
x509.ExtendedKeyUsage([ExtendedKeyUsageOID.SERVER_AUTH]),
|
||||||
critical=False,
|
critical=False,
|
||||||
)
|
)
|
||||||
.add_extension(
|
.add_extension(
|
||||||
@@ -236,9 +236,7 @@ def main():
|
|||||||
|
|
||||||
# Check if certificates already exist
|
# Check if certificates already exist
|
||||||
if not args.force:
|
if not args.force:
|
||||||
existing = [
|
existing = [p for p in [ca_cert_path, server_cert_path, server_key_path] if p.exists()]
|
||||||
p for p in [ca_cert_path, server_cert_path, server_key_path] if p.exists()
|
|
||||||
]
|
|
||||||
if existing:
|
if existing:
|
||||||
print("Error: The following certificate files already exist:")
|
print("Error: The following certificate files already exist:")
|
||||||
for p in existing:
|
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
|
import tempfile
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING, cast
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@@ -14,18 +15,25 @@ try:
|
|||||||
from cryptography import x509
|
from cryptography import x509
|
||||||
from cryptography.hazmat.primitives import hashes
|
from cryptography.hazmat.primitives import hashes
|
||||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
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
|
CRYPTO_AVAILABLE = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
CRYPTO_AVAILABLE = False
|
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 the module to test
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||||
|
|
||||||
if CRYPTO_AVAILABLE:
|
if CRYPTO_AVAILABLE or TYPE_CHECKING:
|
||||||
from scripts.generate_certs import (
|
from scripts.generate_certs import (
|
||||||
generate_ca_certificate,
|
generate_ca_certificate,
|
||||||
generate_private_key,
|
generate_private_key,
|
||||||
@@ -63,10 +71,8 @@ class TestCertificateGeneration:
|
|||||||
assert cert.subject == cert.issuer
|
assert cert.subject == cert.issuer
|
||||||
|
|
||||||
# Check basic constraints
|
# Check basic constraints
|
||||||
basic_constraints = cert.extensions.get_extension_for_oid(
|
basic_constraints = cert.extensions.get_extension_for_oid(ExtensionOID.BASIC_CONSTRAINTS)
|
||||||
x509.ExtensionOID.BASIC_CONSTRAINTS
|
assert cast(BasicConstraints, basic_constraints.value).ca is True
|
||||||
)
|
|
||||||
assert basic_constraints.value.ca is True
|
|
||||||
|
|
||||||
def test_generate_ca_certificate_validity(self):
|
def test_generate_ca_certificate_validity(self):
|
||||||
"""Test that CA certificate has correct validity period."""
|
"""Test that CA certificate has correct validity period."""
|
||||||
@@ -74,11 +80,11 @@ class TestCertificateGeneration:
|
|||||||
days_valid = 365
|
days_valid = 365
|
||||||
cert = generate_ca_certificate(key, days_valid=days_valid)
|
cert = generate_ca_certificate(key, days_valid=days_valid)
|
||||||
|
|
||||||
now = datetime.utcnow()
|
now = datetime.now(timezone.utc)
|
||||||
expected_expiry = now + timedelta(days=days_valid)
|
expected_expiry = now + timedelta(days=days_valid)
|
||||||
|
|
||||||
# Allow 1 minute tolerance for test execution time
|
# 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):
|
def test_generate_server_certificate(self):
|
||||||
"""Test that a server certificate can be generated."""
|
"""Test that a server certificate can be generated."""
|
||||||
@@ -105,9 +111,9 @@ class TestCertificateGeneration:
|
|||||||
|
|
||||||
# Check basic constraints - should not be a CA
|
# Check basic constraints - should not be a CA
|
||||||
basic_constraints = server_cert.extensions.get_extension_for_oid(
|
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):
|
def test_generate_server_certificate_san(self):
|
||||||
"""Test that server certificate includes Subject Alternative Names."""
|
"""Test that server certificate includes Subject Alternative Names."""
|
||||||
@@ -126,11 +132,15 @@ class TestCertificateGeneration:
|
|||||||
|
|
||||||
# Get SAN extension
|
# Get SAN extension
|
||||||
san_ext = server_cert.extensions.get_extension_for_oid(
|
san_ext = server_cert.extensions.get_extension_for_oid(
|
||||||
x509.ExtensionOID.SUBJECT_ALTERNATIVE_NAME
|
ExtensionOID.SUBJECT_ALTERNATIVE_NAME
|
||||||
)
|
)
|
||||||
|
|
||||||
# Extract DNS names
|
# 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
|
# Check that all DNS names are present
|
||||||
for name in san_list:
|
for name in san_list:
|
||||||
@@ -191,15 +201,15 @@ class TestCertificateGeneration:
|
|||||||
|
|
||||||
# Verify server cert is not a CA
|
# Verify server cert is not a CA
|
||||||
server_basic_constraints = server_cert.extensions.get_extension_for_oid(
|
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
|
# Verify CA cert is a CA
|
||||||
ca_basic_constraints = ca_cert.extensions.get_extension_for_oid(
|
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")
|
@pytest.mark.skipif(not CRYPTO_AVAILABLE, reason="cryptography library not available")
|
||||||
@@ -219,12 +229,12 @@ class TestCertificateValidation:
|
|||||||
# Check server certificate extensions
|
# Check server certificate extensions
|
||||||
ext_oids = [ext.oid for ext in server_cert.extensions]
|
ext_oids = [ext.oid for ext in server_cert.extensions]
|
||||||
|
|
||||||
assert x509.ExtensionOID.SUBJECT_ALTERNATIVE_NAME in ext_oids
|
assert ExtensionOID.SUBJECT_ALTERNATIVE_NAME in ext_oids
|
||||||
assert x509.ExtensionOID.BASIC_CONSTRAINTS in ext_oids
|
assert ExtensionOID.BASIC_CONSTRAINTS in ext_oids
|
||||||
assert x509.ExtensionOID.KEY_USAGE in ext_oids
|
assert ExtensionOID.KEY_USAGE in ext_oids
|
||||||
assert x509.ExtensionOID.EXTENDED_KEY_USAGE in ext_oids
|
assert ExtensionOID.EXTENDED_KEY_USAGE in ext_oids
|
||||||
assert x509.ExtensionOID.SUBJECT_KEY_IDENTIFIER in ext_oids
|
assert ExtensionOID.SUBJECT_KEY_IDENTIFIER in ext_oids
|
||||||
assert x509.ExtensionOID.AUTHORITY_KEY_IDENTIFIER in ext_oids
|
assert ExtensionOID.AUTHORITY_KEY_IDENTIFIER in ext_oids
|
||||||
|
|
||||||
def test_certificate_validity_dates(self):
|
def test_certificate_validity_dates(self):
|
||||||
"""Test that certificates have correct validity dates."""
|
"""Test that certificates have correct validity dates."""
|
||||||
@@ -232,14 +242,14 @@ class TestCertificateValidation:
|
|||||||
days_valid = 100
|
days_valid = 100
|
||||||
cert = generate_ca_certificate(key, days_valid=days_valid)
|
cert = generate_ca_certificate(key, days_valid=days_valid)
|
||||||
|
|
||||||
now = datetime.utcnow()
|
now = datetime.now(timezone.utc)
|
||||||
|
|
||||||
# Check not_valid_before is around now
|
# 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
|
# Check not_valid_after is around now + days_valid
|
||||||
expected_expiry = now + timedelta(days=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():
|
def test_imports():
|
||||||
|
|||||||
Reference in New Issue
Block a user