Skip to content

Commit b9e7d06

Browse files
committed
Release v0.4.1: Add configurable LDAP TLS certificate verification
- Add configure_ldap_connection() helper function for TLS configuration - Support LDAP_TLS_REQUIRE_CERT environment variable (never/allow/try/demand) - Update all LDAP connection points to use configurable TLS settings - Add comprehensive documentation in LDAP_TLS_CONFIGURATION.md - Improve security by making TLS verification configurable per environment
1 parent f696c64 commit b9e7d06

File tree

6 files changed

+339
-6
lines changed

6 files changed

+339
-6
lines changed

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
- Advanced reporting and analytics
1616
- Multi-tenancy support
1717

18+
## [0.4.1] - 2025-11-11
19+
20+
### Added
21+
- **Configurable LDAP TLS Certificate Verification**
22+
- Added `configure_ldap_connection()` helper function in `ldap_backend.py`
23+
- Support for `LDAP_TLS_REQUIRE_CERT` environment variable with options: `never`, `allow`, `try`, `demand` (default)
24+
- Updated all LDAP connection initialization points to use configurable TLS settings
25+
- Added `_configure_ldap_connection()` helper in `ldap_handler.py` with fallback implementation
26+
27+
### Changed
28+
- **LDAP Backend Improvements**
29+
- All LDAP connections now respect the `LDAP_TLS_REQUIRE_CERT` environment variable
30+
- Updated `get_user_manager()`, `search_ldap_users()`, and `get_ldap_user_attributes()` functions
31+
- Updated `LDAPUpdateHandler` to use configurable TLS settings
32+
33+
### Documentation
34+
- Added `LDAP_TLS_CONFIGURATION.md` with comprehensive documentation on TLS configuration options
35+
- Documented security considerations for different TLS verification levels
36+
- Added deployment instructions for Kubernetes environments
37+
1838
## [0.4.0] - 2025-11-06
1939

2040
### Added - SJCME Migration Support

LDAP_TLS_CONFIGURATION.md

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
# LDAP TLS Certificate Configuration
2+
3+
## Overview
4+
5+
This document describes the changes made to support configurable LDAP TLS certificate verification via the `LDAP_TLS_REQUIRE_CERT` environment variable.
6+
7+
## Problem
8+
9+
The application was hardcoded to use `ldap.OPT_X_TLS_NEVER` for TLS certificate verification, which meant it would never verify SSL/TLS certificates when connecting to LDAP servers. This was not configurable and could cause issues in different environments.
10+
11+
## Solution
12+
13+
Made the LDAP TLS certificate verification configurable via the `LDAP_TLS_REQUIRE_CERT` environment variable, allowing different verification levels based on deployment needs.
14+
15+
## Environment Variable
16+
17+
### `LDAP_TLS_REQUIRE_CERT`
18+
19+
Controls how strictly the LDAP client verifies SSL/TLS certificates.
20+
21+
**Possible Values:**
22+
- `never` - Don't require or verify certificates (use for self-signed certs or testing)
23+
- `allow` - Allow connection without certificate verification
24+
- `try` - Try to verify but proceed if verification fails
25+
- `demand` (default) - Require valid certificate (most secure)
26+
- `hard` - Same as `demand`
27+
28+
**Example:**
29+
```bash
30+
export LDAP_TLS_REQUIRE_CERT=never
31+
```
32+
33+
Or in Kubernetes:
34+
```bash
35+
kubectl set env deployment/form-workflows-app -n default LDAP_TLS_REQUIRE_CERT=never
36+
```
37+
38+
## Files Modified
39+
40+
### 1. `django_forms_workflows/ldap_backend.py`
41+
42+
**Changes:**
43+
- Added `configure_ldap_connection(conn)` helper function that reads `LDAP_TLS_REQUIRE_CERT` environment variable and configures the LDAP connection accordingly
44+
- Updated three LDAP connection initialization points to use the helper:
45+
- `get_user_manager()` function (line 281)
46+
- `search_ldap_users()` function (line 359)
47+
- `get_ldap_user_attributes()` function (line 443)
48+
49+
**Key Function:**
50+
```python
51+
def configure_ldap_connection(conn):
52+
"""
53+
Configure LDAP connection with TLS settings from environment variables.
54+
55+
Environment Variables:
56+
LDAP_TLS_REQUIRE_CERT: TLS certificate verification level
57+
- 'never': Don't require or verify certificates
58+
- 'allow': Allow connection without cert verification
59+
- 'try': Try to verify but proceed if verification fails
60+
- 'demand' or 'hard': Require valid certificate (default)
61+
"""
62+
tls_require_cert = os.getenv('LDAP_TLS_REQUIRE_CERT', 'demand').lower()
63+
64+
if tls_require_cert == 'never':
65+
conn.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
66+
elif tls_require_cert == 'allow':
67+
conn.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_ALLOW)
68+
elif tls_require_cert == 'try':
69+
conn.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_TRY)
70+
else: # 'demand' or 'hard' or any other value
71+
conn.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_DEMAND)
72+
73+
conn.set_option(ldap.OPT_REFERRALS, 0)
74+
```
75+
76+
### 2. `django_forms_workflows/handlers/ldap_handler.py`
77+
78+
**Changes:**
79+
- Added `_configure_ldap_connection(conn)` helper function that imports and uses the main `configure_ldap_connection` function from `ldap_backend`
80+
- Includes fallback implementation if `ldap_backend` is not available
81+
- Updated LDAP connection initialization in `_update_ldap_entry()` method (line 217)
82+
83+
### 3. `form-workflows/config/settings.py`
84+
85+
**Changes:**
86+
- Modified the LDAP configuration section to read `LDAP_TLS_REQUIRE_CERT` from environment using `config()` function
87+
- Dynamically sets `tls_cert_option` based on the environment variable value
88+
- Updated `AUTH_LDAP_CONNECTION_OPTIONS` to use the dynamic `tls_cert_option` instead of hardcoded `ldap.OPT_X_TLS_NEVER`
89+
90+
**Before:**
91+
```python
92+
AUTH_LDAP_CONNECTION_OPTIONS = {
93+
ldap.OPT_DEBUG_LEVEL: 0,
94+
ldap.OPT_REFERRALS: 0,
95+
ldap.OPT_X_TLS_REQUIRE_CERT: ldap.OPT_X_TLS_NEVER, # Hardcoded
96+
ldap.OPT_NETWORK_TIMEOUT: 5,
97+
ldap.OPT_TIMEOUT: 5,
98+
}
99+
```
100+
101+
**After:**
102+
```python
103+
# Configure TLS certificate verification based on environment variable
104+
tls_require_cert = config('LDAP_TLS_REQUIRE_CERT', default='demand').lower()
105+
106+
if tls_require_cert == 'never':
107+
tls_cert_option = ldap.OPT_X_TLS_NEVER
108+
elif tls_require_cert == 'allow':
109+
tls_cert_option = ldap.OPT_X_TLS_ALLOW
110+
elif tls_require_cert == 'try':
111+
tls_cert_option = ldap.OPT_X_TLS_TRY
112+
else: # 'demand' or 'hard' or any other value
113+
tls_cert_option = ldap.OPT_X_TLS_DEMAND
114+
115+
AUTH_LDAP_CONNECTION_OPTIONS = {
116+
ldap.OPT_DEBUG_LEVEL: 0,
117+
ldap.OPT_REFERRALS: 0,
118+
ldap.OPT_X_TLS_REQUIRE_CERT: tls_cert_option, # Configurable
119+
ldap.OPT_NETWORK_TIMEOUT: 5,
120+
ldap.OPT_TIMEOUT: 5,
121+
}
122+
```
123+
124+
### 4. `form-workflows/test_ldap_connection.py`
125+
126+
**Changes:**
127+
- Updated the test script to read `LDAP_TLS_REQUIRE_CERT` from environment
128+
- Applies the same TLS configuration logic as the main application
129+
- Displays the TLS verification level being used during testing
130+
131+
## Usage
132+
133+
### For Development/Testing (Self-Signed Certificates)
134+
135+
```bash
136+
export LDAP_TLS_REQUIRE_CERT=never
137+
python manage.py runserver
138+
```
139+
140+
### For Production (Valid Certificates)
141+
142+
```bash
143+
export LDAP_TLS_REQUIRE_CERT=demand
144+
python manage.py runserver
145+
```
146+
147+
### For Kubernetes Deployment
148+
149+
Update the deployment environment variables:
150+
151+
```bash
152+
kubectl set env deployment/form-workflows-app -n default LDAP_TLS_REQUIRE_CERT=never
153+
```
154+
155+
Or add to the deployment YAML:
156+
157+
```yaml
158+
env:
159+
- name: LDAP_TLS_REQUIRE_CERT
160+
value: "never"
161+
```
162+
163+
## Testing
164+
165+
After making these changes, you can test LDAP connectivity:
166+
167+
```bash
168+
cd form-workflows
169+
python test_ldap_connection.py
170+
```
171+
172+
The test script will show which TLS verification level is being used.
173+
174+
## Security Considerations
175+
176+
- **Production**: Use `demand` (default) to ensure certificate verification
177+
- **Development/Testing**: Use `never` or `allow` for self-signed certificates
178+
- **Staging**: Use `try` to attempt verification but allow fallback
179+
180+
## Backward Compatibility
181+
182+
The default value is `demand`, which is the most secure option. However, if you were previously using the hardcoded `never` setting, you'll need to explicitly set:
183+
184+
```bash
185+
export LDAP_TLS_REQUIRE_CERT=never
186+
```
187+
188+
## Next Steps
189+
190+
1. **Rebuild the application** with these changes
191+
2. **Set the environment variable** in your deployment
192+
3. **Redeploy** the application
193+
4. **Test** LDAP connectivity
194+
195+
### For form-workflows Deployment
196+
197+
```bash
198+
# Navigate to form-workflows directory
199+
cd form-workflows
200+
201+
# Rebuild the Docker image
202+
docker build -t form-workflows:latest .
203+
204+
# Update Kubernetes deployment
205+
kubectl set env deployment/form-workflows-app -n default LDAP_TLS_REQUIRE_CERT=never
206+
207+
# Restart the deployment to pick up code changes
208+
kubectl rollout restart deployment/form-workflows-app -n default
209+
210+
# Monitor the rollout
211+
kubectl rollout status deployment/form-workflows-app -n default
212+
```
213+
214+
## Troubleshooting
215+
216+
### Issue: LDAP connection still fails with certificate errors
217+
218+
**Solution:** Verify the environment variable is set correctly:
219+
```bash
220+
kubectl exec -it deployment/form-workflows-app -- env | grep LDAP_TLS_REQUIRE_CERT
221+
```
222+
223+
### Issue: Environment variable not being read
224+
225+
**Solution:** Ensure the deployment has been restarted after setting the environment variable:
226+
```bash
227+
kubectl rollout restart deployment/form-workflows-app -n default
228+
```
229+
230+
### Issue: Still getting SSL errors
231+
232+
**Solution:** Try using the non-SSL LDAP URL instead:
233+
```bash
234+
kubectl set env deployment/form-workflows-app -n default LDAP_PRIMARY_URL=ldap://your-server:389
235+
```
236+
237+
## References
238+
239+
- [python-ldap Documentation](https://www.python-ldap.org/en/latest/)
240+
- [django-auth-ldap Documentation](https://django-auth-ldap.readthedocs.io/)
241+
- [OpenLDAP TLS Configuration](https://www.openldap.org/doc/admin24/tls.html)
242+

django_forms_workflows/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Enterprise-grade, database-driven form builder with approval workflows
44
"""
55

6-
__version__ = "0.4.0"
6+
__version__ = "0.4.1"
77
__author__ = "Django Forms Workflows Contributors"
88
__license__ = "LGPL-3.0-only"
99

django_forms_workflows/handlers/ldap_handler.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,38 @@
1313
logger = logging.getLogger(__name__)
1414

1515

16+
def _configure_ldap_connection(conn):
17+
"""
18+
Configure LDAP connection with TLS settings from environment variables.
19+
20+
This is a local helper that imports and uses the configure_ldap_connection
21+
function from ldap_backend module.
22+
23+
Args:
24+
conn: LDAP connection object
25+
"""
26+
try:
27+
from django_forms_workflows.ldap_backend import configure_ldap_connection
28+
configure_ldap_connection(conn)
29+
except ImportError:
30+
# Fallback if ldap_backend is not available
31+
import os
32+
import ldap
33+
34+
tls_require_cert = os.getenv('LDAP_TLS_REQUIRE_CERT', 'demand').lower()
35+
36+
if tls_require_cert == 'never':
37+
conn.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
38+
elif tls_require_cert == 'allow':
39+
conn.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_ALLOW)
40+
elif tls_require_cert == 'try':
41+
conn.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_TRY)
42+
else:
43+
conn.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_DEMAND)
44+
45+
conn.set_option(ldap.OPT_REFERRALS, 0)
46+
47+
1648
class LDAPUpdateHandler(BaseActionHandler):
1749
"""
1850
Handler for updating LDAP attributes with form data.
@@ -182,7 +214,7 @@ def _update_ldap(self, dn, attributes):
182214

183215
# Connect to LDAP
184216
conn = ldap.initialize(server_uri)
185-
conn.set_option(ldap.OPT_REFERRALS, 0)
217+
_configure_ldap_connection(conn)
186218
conn.simple_bind_s(bind_dn, bind_password)
187219

188220
# Build modification list

0 commit comments

Comments
 (0)