LDAP-based AuthManager for Apache Airflow 3.x
Project description
[!NOTE] The code in this repository is written with the help of AI (specifically, ChatGPT 5), including this README (lazy, I know). Although I tried my best to streamline and validate most of it, there may still be some intricacies that need to be sorted out.
Airflow LDAP Auth Manager
A drop-in Auth Manager for Apache Airflow 3.x that authenticates users against LDAP/Active Directory and maps LDAP groups to Airflow roles (admin / editor / viewer). It supports redundant LDAP servers, secure transport (LDAPS or StartTLS), and secret indirection for bind credentials via Airflow’s Secrets Backend (Variables).
Features
- LDAP/AD authentication using
ldap3 - Group → role mapping (admin > editor > viewer)
- Redundancy & failover via
ldap3.ServerPool(ROUND_ROBIN by default) - TLS: LDAPS or StartTLS (with certificate verification)
- Secrets-friendly config:
bind_dn_secretandbind_password_secretread from Airflow Variables (resolved through your configured secrets backend) - Clean Airflow 3 API: uses the Airflow SDK (
airflow.sdk.*) - Simple login UI with configurable instance name and login tip
- Helpful logging: logs which LDAP server the connection bound to
To-do
- Extend the user & group search base config items to allow multiple entries (or LDAP
ORsyntax) - Package this and upload to pypi
Requirements
- Python: 3.12+
- Airflow: 3.1+ (Auth Manager interface & SDK)
- Libraries:
ldap3,fastapi,jinja2(fastapi&jinja2is likely installed already as Airflow dependencies) - Optional (recommended): a Secrets Backend (Vault, AWS Secrets Manager, GCP SM, Azure KV) configured for Airflow Variables
Installation
Install from PyPi into the environment where Airflow is installed:
pip install airflow-ldap-auth-manager
Or install after cloning the project:
git clone <repo url>
cd airflow-ldap-auth-manager
pip install .
Configure Airflow
Changes needed in airflow.cfg:
Enable the Auth Manager
[core]
# Fully qualified path to the auth manager class in this repo
auth_manager = airflow_ldap_auth_manager.LdapAuthManager
Make sure JWT settings are configured correctly
[api_auth]
jwt_secret = # Needs to be set
jwt_algorithm = # Either leave empty, or make sure consistent across all machines
jwt_audience = # Either leave empty, or make sure consistent across all machines
jwt_issuer = # Either leave empty, or make sure consistent across all machines
LDAP settings
Add a section for the LDAP auth manager (adjust to your environment):
[ldap_auth_manager]
server_uri = ldaps://ldap1.example.com:636,ldaps://ldap2.example.com:636
bind_dn =
bind_password =
bind_dn_secret = secret/path/to/airflow/variable/for/bind_dn
bind_password_secret = path/to/airflow/variable/for/bind_password
user_search_base = OU=Users,DC=company_name
user_search_filter = (|(uid={username})(sAMAccountName={username})(mail={username}))
group_search_base = OU=Groups,DC=company_name
group_member_attr = member
admin_groups = airflow-admins
editor_groups = airflow-editors
viewer_groups = airflow-viewers,airflow-auditors
username_attr = uid
email_attr = mail
start_tls = false
verify_ssl = true
post_login_redirect = /
logout_redirect = /
debug_logging = false
Environment variable overrides are supported in the standard Airflow fashion, e.g.:`
AIRFLOW__LDAP_AUTH_MANAGER__BIND_DN_SECRET=api_server/ldap_auth_manager/bind_dn
Branding & login hint (optional)
[api]
# Human-friendly name for titles/headers on the login page
instance_name = Company Airflow
[ldap_auth_manager]
# Optional helper text shown under "Sign in"
login_tip = Using your Company credentials
Restart the api-server after changes.
Configuration reference
[ldap_auth_manager]
# LDAP authentication/authorization settings for LdapAuthManager.
# This section supports multiple redundant servers, secure transport (LDAPS or StartTLS),
# and secret indirection for bind credentials via Airflow’s Secrets Backend.
# Comma-separated list of LDAP server URIs.
# - Supports ldap:// and ldaps:// schemes.
# - When multiple URIs are provided, the manager builds an ldap3 ServerPool with ROUND_ROBIN
# strategy for load distribution and failover.
# - For ldaps://, TLS is implicit from connect; for ldap:// + start_tls=true, StartTLS is
# negotiated before bind.
# - Mixing ldaps:// with start_tls=true is not meaningful; prefer one approach.
#
# Variable: AIRFLOW__LDAP_AUTH_MANAGER__SERVER_URI
#
server_uri = ldaps://ldap1.example.com:636,ldaps://ldap2.example.com:636
# Name of the Airflow Variable that contains the LDAP bind DN (service account).
# The Variable is resolved through the configured Secrets Backend (e.g. Vault, AWS SM, etc.).
# If set, any plaintext `bind_dn` value is ignored. Leave empty to attempt anonymous bind.
# Remember: This needs to be set in the "Airflow/Variables" section in your secret manager,
# NOT "Airflow/Config"!
#
# Variable: AIRFLOW__LDAP_AUTH_MANAGER__BIND_DN_SECRET
#
bind_dn_secret = secret/path/to/airflow/variable/for/bind_dn
bind_dn =
# Name of the Airflow Variable that contains the LDAP bind password (service account).
# The Variable is resolved through the configured Secrets Backend.
# If set, any plaintext `bind_password` value is ignored.
# Remember: This needs to be set in the "Airflow/Variables" section in your secret manager,
# NOT "Airflow/Config"!
#
# Variable: AIRFLOW__LDAP_AUTH_MANAGER__BIND_PASSWORD_SECRET
#
bind_password_secret = path/to/airflow/variable/for/bind_password
bind_password =
# Base DN under which user entries are searched.
# Example (Active Directory): OU=Users,OU=Country,DC=example,DC=com
# Supports multiple base DNs, separated by newlines, semicolons, or as a JSON array.
#
# Variable: AIRFLOW__LDAP_AUTH_MANAGER__USER_SEARCH_BASE
#
user_search_base = OU=Users,DC=company_name
# LDAP filter template to locate the authenticating user.
# The literal "{username}" placeholder is replaced with the submitted login identifier.
# Must be a valid RFC 4515 filter. The username value is safely escaped before substitution.
# Example: (|(uid={username})(sAMAccountName={username})(mail={username}))
#
# Variable: AIRFLOW__LDAP_AUTH_MANAGER__USER_SEARCH_FILTER
#
user_search_filter = (|(uid={username})(sAMAccountName={username})(mail={username}))
# Base DN under which group entries are searched for authorization.
#
# Variable: AIRFLOW__LDAP_AUTH_MANAGER__GROUP_SEARCH_BASE
#
group_search_base = OU=Groups,DC=company_name
# The group attribute that lists membership (DNs of user entries).
# Common values:
# - Active Directory: member
# - RFC2307/posix groups: memberUid (then matching is by username instead of DN)
#
# Variable: AIRFLOW__LDAP_AUTH_MANAGER__GROUP_MEMBER_ATTR
#
group_member_attr = member
# Comma-separated list of groups that grant the Airflow "admin" role.
# Values can be group CNs or full DNs under group_search_base (matching is case-insensitive).
#
# Variable: AIRFLOW__LDAP_AUTH_MANAGER__ADMIN_GROUPS
#
admin_groups = airflow-admins
# Comma-separated list of groups that grant the Airflow "editor" role.
# Leave empty to disable this mapping.
#
# Variable: AIRFLOW__LDAP_AUTH_MANAGER__EDITOR_GROUPS
#
editor_groups = airflow-editors
# Comma-separated list of groups that grant the Airflow "viewer" role.
# Users are mapped to the highest role matched (admin > editor > viewer).
#
# Variable: AIRFLOW__LDAP_AUTH_MANAGER__VIEWER_GROUPS
#
viewer_groups = airflow-viewers,airflow-auditors
# Attribute on the user entry to use as the Airflow username.
# Typical values: uid, sAMAccountName, userPrincipalName
#
# Variable: AIRFLOW__LDAP_AUTH_MANAGER__USERNAME_ATTR
#
username_attr = uid
# Attribute on the user entry that contains the email address.
# Typical values: mail, userPrincipalName
#
# Variable: AIRFLOW__LDAP_AUTH_MANAGER__EMAIL_ATTR
#
email_attr = mail
# Whether to perform StartTLS on ldap:// connections before bind.
# - true : Use StartTLS (only applies to ldap:// URIs).
# - false : Do not use StartTLS. For ldaps:// URIs, TLS is already implicit.
#
# Variable: AIRFLOW__LDAP_AUTH_MANAGER__START_TLS
#
start_tls = false
# Whether to verify the server certificate for TLS (ldaps or StartTLS).
# Set to true in production with a valid trust store. Set to false only for testing.
#
# Variable: AIRFLOW__LDAP_AUTH_MANAGER__VERIFY_SSL
#
verify_ssl = true
# Path (relative to the Airflow web root) to redirect a user after successful login.
# Example: "/" or "/home"
#
# Variable: AIRFLOW__LDAP_AUTH_MANAGER__POST_LOGIN_REDIRECT
#
post_login_redirect = /
# Path (relative to the Airflow web root) to redirect a user after logout.
# Example: "/" or "/login"
#
# Variable: AIRFLOW__LDAP_AUTH_MANAGER__LOGOUT_REDIRECT
#
logout_redirect = /
# Enable debug logging for LDAP operations.
# This can be useful to troubleshoot LDAP issues, but beware that it may log
# sensitive information such as usernames and group names.
#
# Variable: AIRFLOW__LDAP_AUTH_MANAGER__DEBUG_LOGGING
#
debug_logging = false
# Optional login hint shown under "Sign in" title. Leave empty to hide.
#
# Variable: AIRFLOW__LDAP_AUTH_MANAGER__LOGIN_TIP
#
login_tip = Using your Company credentials
# Optional override to the support hint shown under the "Sign in" button.
#
# Variable: AIRFLOW__LDAP_AUTH_MANAGER__SUPPORT_TIP
#
support_tip = Having issues? Raise a ticket to the helpdesk.
UI & templates
- Login template:
ldap_login.html - Static assets are served under
/auth/static/(e.g./auth/static/style.css,/auth/static/airflow.svg) - Form posts to
/auth/token
If you see 404 Not Found for /auth/static/..., ensure the StaticFiles mount path in your FastAPI router matches the URLs used in the template.
Logging & diagnostics
If debug_logging = true in config:
After successful bind, the manager logs which LDAP server was selected from the pool, e.g.:
LDAP bound to ldaps://ldap2.example.com:636 (pool_strategy=ROUND_ROBIN, start_tls=False)
Optionally, it can log the authenticated identity via the LDAP whoami extended operation.
Security notes
- Choose one TLS mode: either
ldaps://...orldap://withstart_tls=true. Mixing them is discouraged. - Keep
verify_ssl = truein production and ensure your trust store contains the issuing CA(s). - Bind credentials should preferrably be supplied via
*_secretindirection (Variables → Secrets Backend), not plaintext.
Troubleshooting
- Can’t bind / invalid credentials: test with
ldapsearchusing the same DN/password and base/filter. - User not found: verify
user_search_baseanduser_search_filter(remember{username}substitution). - Group mapping not applied: confirm
group_search_base,group_member_attr, and that your groups are in the configured bases. - TLS errors: verify certificates and CA chain; ensure the hostname matches the server certificate CN/SAN.
- Static assets 404: check the FastAPI
StaticFilesmount matches/auth/static.
Contributing
Issues and PRs are welcome. Please include:
- A clear description of the problem or feature
- Repro steps or tests when possible
- Your Airflow, Python, and LDAP server versions
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file airflow_ldap_auth_manager-0.1.1.tar.gz.
File metadata
- Download URL: airflow_ldap_auth_manager-0.1.1.tar.gz
- Upload date:
- Size: 25.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f24d2abfcc05bb4741ba207f21bfa20f1b8f6bc8934d264bafda284ea4230c8a
|
|
| MD5 |
216fad828731ccc57e39361f3fe4f562
|
|
| BLAKE2b-256 |
ae01dd13cdeec7910079241f3b2cee14dae283339a876a1029c1a556dd8f0807
|
File details
Details for the file airflow_ldap_auth_manager-0.1.1-py3-none-any.whl.
File metadata
- Download URL: airflow_ldap_auth_manager-0.1.1-py3-none-any.whl
- Upload date:
- Size: 21.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cf71c0519c385fca8bcd8f354e1f2a4d4ae8ace020efeff8046aca204cee65c3
|
|
| MD5 |
c15d33bca28d20fb743eb138c337fffb
|
|
| BLAKE2b-256 |
02a0829d1ad047d053e1ec023422a3ea9b378d5b7bf14a48a72f1580a56a45d1
|