This document describes the changes made to support configurable LDAP TLS certificate verification via the LDAP_TLS_REQUIRE_CERT environment variable.
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.
Made the LDAP TLS certificate verification configurable via the LDAP_TLS_REQUIRE_CERT environment variable, allowing different verification levels based on deployment needs.
Controls how strictly the LDAP client verifies SSL/TLS certificates.
Possible Values:
never- Don't require or verify certificates (use for self-signed certs or testing)allow- Allow connection without certificate verificationtry- Try to verify but proceed if verification failsdemand(default) - Require valid certificate (most secure)hard- Same asdemand
Example:
export LDAP_TLS_REQUIRE_CERT=neverOr in Kubernetes:
kubectl set env deployment/form-workflows-app -n default LDAP_TLS_REQUIRE_CERT=neverChanges:
- Added
configure_ldap_connection(conn)helper function that readsLDAP_TLS_REQUIRE_CERTenvironment variable and configures the LDAP connection accordingly - Updated three LDAP connection initialization points to use the helper:
get_user_manager()function (line 281)search_ldap_users()function (line 359)get_ldap_user_attributes()function (line 443)
Key Function:
def configure_ldap_connection(conn):
"""
Configure LDAP connection with TLS settings from environment variables.
Environment Variables:
LDAP_TLS_REQUIRE_CERT: TLS certificate verification level
- 'never': Don't require or verify certificates
- 'allow': Allow connection without cert verification
- 'try': Try to verify but proceed if verification fails
- 'demand' or 'hard': Require valid certificate (default)
"""
tls_require_cert = os.getenv('LDAP_TLS_REQUIRE_CERT', 'demand').lower()
if tls_require_cert == 'never':
conn.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
elif tls_require_cert == 'allow':
conn.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_ALLOW)
elif tls_require_cert == 'try':
conn.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_TRY)
else: # 'demand' or 'hard' or any other value
conn.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_DEMAND)
conn.set_option(ldap.OPT_REFERRALS, 0)Changes:
- Added
_configure_ldap_connection(conn)helper function that imports and uses the mainconfigure_ldap_connectionfunction fromldap_backend - Includes fallback implementation if
ldap_backendis not available - Updated LDAP connection initialization in
_update_ldap_entry()method (line 217)
Changes:
- Modified the LDAP configuration section to read
LDAP_TLS_REQUIRE_CERTfrom environment usingconfig()function - Dynamically sets
tls_cert_optionbased on the environment variable value - Updated
AUTH_LDAP_CONNECTION_OPTIONSto use the dynamictls_cert_optioninstead of hardcodedldap.OPT_X_TLS_NEVER
Before:
AUTH_LDAP_CONNECTION_OPTIONS = {
ldap.OPT_DEBUG_LEVEL: 0,
ldap.OPT_REFERRALS: 0,
ldap.OPT_X_TLS_REQUIRE_CERT: ldap.OPT_X_TLS_NEVER, # Hardcoded
ldap.OPT_NETWORK_TIMEOUT: 5,
ldap.OPT_TIMEOUT: 5,
}After:
# Configure TLS certificate verification based on environment variable
tls_require_cert = config('LDAP_TLS_REQUIRE_CERT', default='demand').lower()
if tls_require_cert == 'never':
tls_cert_option = ldap.OPT_X_TLS_NEVER
elif tls_require_cert == 'allow':
tls_cert_option = ldap.OPT_X_TLS_ALLOW
elif tls_require_cert == 'try':
tls_cert_option = ldap.OPT_X_TLS_TRY
else: # 'demand' or 'hard' or any other value
tls_cert_option = ldap.OPT_X_TLS_DEMAND
AUTH_LDAP_CONNECTION_OPTIONS = {
ldap.OPT_DEBUG_LEVEL: 0,
ldap.OPT_REFERRALS: 0,
ldap.OPT_X_TLS_REQUIRE_CERT: tls_cert_option, # Configurable
ldap.OPT_NETWORK_TIMEOUT: 5,
ldap.OPT_TIMEOUT: 5,
}Changes:
- Updated the test script to read
LDAP_TLS_REQUIRE_CERTfrom environment - Applies the same TLS configuration logic as the main application
- Displays the TLS verification level being used during testing
export LDAP_TLS_REQUIRE_CERT=never
python manage.py runserverexport LDAP_TLS_REQUIRE_CERT=demand
python manage.py runserverUpdate the deployment environment variables:
kubectl set env deployment/form-workflows-app -n default LDAP_TLS_REQUIRE_CERT=neverOr add to the deployment YAML:
env:
- name: LDAP_TLS_REQUIRE_CERT
value: "never"After making these changes, you can test LDAP connectivity:
cd form-workflows
python test_ldap_connection.pyThe test script will show which TLS verification level is being used.
- Production: Use
demand(default) to ensure certificate verification - Development/Testing: Use
neverorallowfor self-signed certificates - Staging: Use
tryto attempt verification but allow fallback
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:
export LDAP_TLS_REQUIRE_CERT=never- Rebuild the application with these changes
- Set the environment variable in your deployment
- Redeploy the application
- Test LDAP connectivity
# Navigate to form-workflows directory
cd form-workflows
# Rebuild the Docker image
docker build -t form-workflows:latest .
# Update Kubernetes deployment
kubectl set env deployment/form-workflows-app -n default LDAP_TLS_REQUIRE_CERT=never
# Restart the deployment to pick up code changes
kubectl rollout restart deployment/form-workflows-app -n default
# Monitor the rollout
kubectl rollout status deployment/form-workflows-app -n defaultSolution: Verify the environment variable is set correctly:
kubectl exec -it deployment/form-workflows-app -- env | grep LDAP_TLS_REQUIRE_CERTSolution: Ensure the deployment has been restarted after setting the environment variable:
kubectl rollout restart deployment/form-workflows-app -n defaultSolution: Try using the non-SSL LDAP URL instead:
kubectl set env deployment/form-workflows-app -n default LDAP_PRIMARY_URL=ldap://your-server:389