""" 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