2026-01-26 11:09:40 -05:00

57 lines
2.3 KiB
Python

"""
Custom PostgreSQL database backend that dynamically reloads credentials from Vault.
This wrapper ensures that Django picks up rotated database credentials from Vault
without requiring a container restart. Credentials are re-read from the Vault agent's
rendered secret files before each new connection is established.
"""
import os
from django.db.backends.postgresql import base
class DatabaseWrapper(base.DatabaseWrapper):
"""PostgreSQL wrapper that reloads credentials from Vault secret files."""
def get_connection_params(self):
"""
Reload credentials from Vault files before connecting.
This method is called each time Django establishes a new database connection.
It reads the latest credentials from /vault/secrets/app.env (maintained by
Vault agent) and updates the connection parameters.
Falls back to environment variables if the Vault secret file is unavailable
(e.g., in local development).
"""
params = super().get_connection_params()
# Determine which alias this is (default or admin)
alias = getattr(self, 'alias', 'default')
if alias == 'admin':
secret_file = '/vault/secrets/admin.env'
user_var = 'DB_ADMIN_USER'
password_var = 'DB_ADMIN_PASSWORD'
else:
secret_file = '/vault/secrets/app.env'
user_var = 'DB_USER'
password_var = 'DB_PASSWORD'
# Try to read fresh credentials from Vault agent's rendered file
try:
if os.path.exists(secret_file):
with open(secret_file, 'r') as f:
for line in f:
line = line.strip()
if line.startswith(f'export {user_var}='):
username = line.split('=', 1)[1].strip().strip('"').strip("'")
params['user'] = username
elif line.startswith(f'export {password_var}='):
password = line.split('=', 1)[1].strip().strip('"').strip("'")
params['password'] = password
except (FileNotFoundError, PermissionError, IOError):
# Fallback to environment variables (local development or error case)
pass
return params