Airflow Security: The DAG That Leaked DB Credentials
Airflow security starts with a rotated Fernet key, secrets backends, and least-privilege RBAC.
20+ years shipping production infrastructure and CI/CD at scale. Drawn from code that ran under real load.
- ✓Airflow installed and running with webserver and scheduler.
- ✓Familiarity with connections and the Airflow UI.
- ✓A secrets backend (Vault or a cloud secret manager) for the hands-on parts.
- Airflow's threat model spans the webserver, the API, the scheduler, and every DAG file that can leak credentials.
- Fernet encrypts connection passwords in the metadata DB; a default key means the encryption is decoration.
- Secrets backends (Vault, AWS Secrets Manager, GCP Secret Manager) beat encrypted connections: rotation never touches code.
- RBAC scopes access: Admin, User, Op, and Viewer roles plus DAG-level access_control.
- The leaked-credentials incident happened because the key was default and creds lived in DAG code.
- Least privilege is a setting, not a slogan: start from Viewer and grant up.
Your DAG files are kitchen recipe cards, and the database password is the key to the pantry. Writing the password on the recipe card means anyone who photocopies the card owns the pantry. Airflow security is a lock on the file drawer (Fernet key), a vault behind the kitchen (secrets backend), and a rule that only certain chefs can open certain drawers (RBAC).
Airflow is a credential magnet. It connects to databases, object storage, APIs, and clouds — and every one of those connections is a password someone wants. The platform has real security machinery: Fernet encryption, secrets backends, RBAC, and OAuth. Most teams run the defaults and call it a day.
Our wake-up call was a DAG that leaked database credentials into a shared repo. The password sat in DAG code for months. Even the connections stored in Airflow were decryptable, because the Fernet key was the default shipped one.
Security here isn't exotic. It's three decisions done right: where credentials live, who can read what, and how people authenticate.
If your Fernet key is still the default, you don't have encryption. You have obscurity.
1. The Threat Model for a Workflow Platform
Every Airflow deployment has five attack surfaces: the webserver, the API, the scheduler, the metadata DB, and the DAG files themselves. The last one is the one teams forget.
DAG files are executable code that runs with the service account's permissions. A credential in a DAG file is a credential in the repo, in the image, and on every machine that syncs code.
The connections table is the crown jewels: every password the platform holds, encrypted with one key. If that key is weak or stolen, everything decrypts at once.
Design for the worst case: one surface compromised. What survives?
2. Fernet: What It Encrypts, Where the Key Lives
Airflow encrypts connection passwords and variables at rest in the metadata DB using Fernet, a symmetric-key scheme. The key lives in your airflow.cfg or an env var.
Here's the trap: the default key is a published sample. If you never replaced it, anyone with DB access — or a dump of the DB — can decrypt every connection with the key from the docs.
Rotation is a real operation: generate a new key, re-encrypt existing connections, then swap the config. Miss the re-encrypt step and your connections decrypt to garbage.
The official flow is a three-step command sequence, not a config swap: set fernet_key to new_key,old_key (new first), run airflow rotate-fernet-key to re-encrypt every connection and variable with the new key, then set fernet_key to just the new key. The env var form uses double underscores — AIRFLOW__CORE__FERNET_KEY — and overrides the config file value.
Store the key like the crown jewel it is: a file with tight permissions, an env var from KMS, or a secrets manager.
3. Secrets Backends vs Connection Encryption
Fernet protects the metadata DB copy of a password. A secrets backend removes the copy entirely: Airflow fetches the credential from Vault, AWS Secrets Manager, or GCP Secret Manager at task runtime.
With a backend, rotation is a vault operation, not a code deploy or a UI edit. The DAG doesn't change. The connection doesn't change. The vault rotates, and the next run uses the new value.
Fail-open versus fail-closed matters here: if the vault is unreachable, do your tasks crash or run with a stale secret? Decide, and write it in the runbook.
Backends don't replace Fernet; they shrink what Fernet protects.
Backends also cover more than connections: the same [secrets] machinery resolves variables and config values from Vault, AWS Secrets Manager (SecretsManagerBackend), or GCP Secret Manager (CloudSecretManagerBackend), each configured with prefix settings like connections_path/connections_prefix and variables_path/variables_prefix.
4. RBAC: Roles and DAG-Level Access
Airflow ships four default roles: Admin, User, Op, and Viewer. Admin can touch everything, including connections. Viewer can only look.
Most teams live on a default 'User for everyone' — which means any engineer can edit connections and trigger production DAGs. That's a breach waiting for a phishing email.
The fix is least privilege: Viewer by default, Op for operators, User only where editing DAGs is part of the job, Admin for a named few. DAG-level access_control then scopes who can run, edit, or read individual DAGs.
Start from Viewer. Grant up. Never down.
The four defaults aren't a ceiling: access_control maps DAG permissions to any role name, including custom team roles — a FinanceTeam role with can_read and can_edit is the common pattern for letting a team own its DAGs without touching Admin surfaces.
5. Authentication: Default vs OAuth/LDAP
Out of the box, Airflow's webserver asks for a username and password — and the default setup is not something you want facing the internet. The API is wide open until you configure an auth backend.
Production wants identity you already manage: OAuth2/OIDC or LDAP. Users sign in with the company SSO, accounts deactivate centrally, and there's no shared 'airflow admin' password floating around.
You still need an initial admin, created from the CLI, not left at defaults. And the API needs its own auth backend so scripts and CI use scoped tokens.
If the webserver isn't behind SSO, it's a public door to the connections table.
One more surface worth closing: set expose_config to False so the UI never renders the running configuration — including options that reveal secrets-related settings — to users who don't need them.
6. Audit Logs and the API
Security without audit is hope. Airflow's event logs record DAG changes, connection edits, and user actions in the metadata DB — but only if you read them.
Ship those logs somewhere searchable: SIEM, object storage, or a log aggregator. Correlate with the API: who triggered a DAG, who edited a connection, at what time, from which token.
The API is also a surface. Every script and CI pipeline using it needs scoped credentials, and the audit trail must cover it.
An incident without logs is a rumor. Make sure your next one has receipts.
7. A Security Checklist for Production
Run this before anything touches production.
- Fernet key: generated, rotated, never the default.
- Secrets: connections resolve from a backend; nothing hardcoded in DAGs.
- RBAC: Viewer default, access_control on every DAG, Admin for named few.
- Auth: webserver behind OAuth/LDAP; API behind its own auth backend.
- Audit: event logs shipped and reviewed.
- Least privilege on the service account the scheduler runs as.
Six items, one afternoon, and the leaked-password class of incident closes for good.
The DAG That Leaked DB Credentials
- Default Fernet keys mean your encrypted connections are decryptable by anyone who reads the docs.
- Credentials in DAG code leak the day the repo's audience changes.
- A secrets backend moves rotation out of code deploys entirely.
- RBAC scoping is what limits blast radius when something does leak.
| File | Command / Code | Purpose |
|---|---|---|
| rotate_fernet.sh | python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().d... | 2. Fernet |
| airflow.cfg | [secrets] | 3. Secrets Backends vs Connection Encryption |
| orders_dag.py | with DAG( | 4. RBAC |
| auth.env | export AIRFLOW__WWW__AUTH_BACKENDS="airflow.providers.fab.auth_manager.oauth_pro... | 5. Authentication |
Key takeaways
Common mistakes to avoid
4 patternsKeeping the default Fernet key
Hardcoding credentials in DAG code
One broad role for everyone
Webserver and API exposed without real auth
Interview Questions on This Topic
What is the Fernet key and what happens if it's left at the default?
Frequently Asked Questions
20+ years shipping production infrastructure and CI/CD at scale. Drawn from code that ran under real load.
That's Airflow. Mark it forged?
3 min read · try the examples if you haven't