security: encrypt sensitive DB fields, clean up dynamic import

Encrypt Proxmox server passwords and VM cloud-init passwords in the database:
- Add backend/app/crypto.py: Fernet-based encrypt/decrypt helpers + EncryptedString
  SQLAlchemy TypeDecorator (transparent to all existing code, no API changes)
- Add FERNET_KEY config field (env var); without it values stay plaintext for
  backward compatibility, with a logged warning
- Update models.py: ProxmoxServer.password and VMInstance.cloud_init_password
  now use EncryptedString(255) instead of String(255)
- Add migration 14 in migrations/migrations.py: idempotent re-encryption of
  existing plaintext rows (skipped when FERNET_KEY is not set)

CSRF analysis:
- /api/auth/change-password is protected by JWT Bearer token (Authorization header)
- Browsers cannot auto-include Authorization headers cross-site → CSRF is not
  applicable here; added explanatory comment

Improve migration loading:
- Add migrations/__init__.py to make it a proper Python package
- Replace fragile importlib dynamic loading in main.py with a standard import

https://claude.ai/code/session_019rK2wvaWg5zXFwmAPPEB7R
This commit is contained in:
Claude
2026-03-06 16:17:45 +00:00
parent 0b898f5c43
commit 5f688abff3
8 changed files with 173 additions and 21 deletions

View File

@@ -95,26 +95,10 @@ async def lifespan(app: FastAPI):
# Run database migrations automatically
try:
import sys
from pathlib import Path
import importlib.util
# Add migrations directory to path
migrations_path = Path(__file__).parent.parent / 'migrations'
sys.path.insert(0, str(migrations_path))
from .db import engine
# Load and run unified migrations
spec = importlib.util.spec_from_file_location(
"migrations",
migrations_path / "migrations.py"
)
migrations_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(migrations_module)
migrations_module.run_all_migrations(engine)
from migrations.migrations import run_all_migrations
run_all_migrations(engine)
logger.info("Database migrations completed")
except Exception as e:
logger.warning(f"Migration check failed: {e}")