mirror of
https://github.com/taylanbakircioglu/haproxy-openmanager.git
synced 2026-09-16 15:45:11 +00:00
bd6a31cb0d
Adds opt-in TOTP-based Multi-Factor Authentication that is fully
backwards compatible with existing logins. Operators choose to enable
MFA per account; nothing changes for users who do not opt in.
Highlights
==========
* RFC 6238 TOTP (6 digits, 30s period, SHA1) with ±30s skew tolerance,
compatible with Microsoft / Google Authenticator, Authy, Duo, 1Password.
* Per-step replay protection (`mfa_last_used_totp_step`) so a captured
code cannot be reused inside the same window.
* Fernet-encrypted TOTP secrets at rest, key resolution via
`MFA_ENCRYPTION_KEY` env (HKDF-derived from `SECRET_KEY` as fallback).
* 10 single-use, bcrypt-hashed backup codes per user, formatted
`XXXX-YYYY` from a confusion-free alphabet (no 0/O/1/I/L).
* Two-step login flow: `POST /api/auth/login` returns `mfa_required`
+ `mfa_token`, then `POST /api/auth/login/mfa-verify` accepts a TOTP
code OR a backup code. JWT is minted only after MFA succeeds.
* Self-service: users enable / disable MFA from their own row in the
Users page; admins reset (single user or bulk) but never enable on
behalf of someone else (matches AWS IAM / GitHub / Google Workspace).
* Bulk emergency reset CLI: `scripts/admin-mfa-reset-all.sh`.
Security hardening
==================
* Atomic transactions with `SELECT … FOR UPDATE` on `mfa_pending_logins`
and `users` rows so concurrent verify / enroll calls cannot race.
* `/api/mfa/enroll/start` refuses re-enrollment when MFA is already on
(prevents silent secret rotation via a stolen JWT).
* Pydantic `ValidationError` messages are sanitized before reaching the
audit log so request bodies (TOTP / backup codes in flight) never
appear in plaintext.
* Slowapi rate limits are per-USER, not per-IP, with a trusted-proxy
XFF strategy so a single ingress address cannot exhaust the bucket
for thousands of operators (`MFA_TRUSTED_PROXY_CIDRS`,
`MFA_RATE_LIMIT_*` env-overridable).
* Login query now scopes to `is_active = TRUE` so a soft-deleted row
with the same username can no longer occlude the active user
(also closes a small account-enumeration side channel).
Database
========
Additive migrations (idempotent `ADD COLUMN IF NOT EXISTS`,
`CREATE TABLE IF NOT EXISTS`):
- users: mfa_enabled, mfa_method, mfa_secret_encrypted,
mfa_enrolled_at, mfa_last_used_at, mfa_last_used_totp_step
- mfa_backup_codes (user_id ON DELETE CASCADE)
- mfa_pending_logins (user_id ON DELETE CASCADE, challenge_token,
attempts, expires_at)
- mfa_pending_enrollments (user_id ON DELETE CASCADE)
Frontend
========
* Login page becomes a 3-phase state machine
(credentials → MFA → submitting); legacy single-step login is
preserved for users who haven't enrolled.
* New MFAEnrollModal (3-step wizard: QR + secret → verify → backup
codes) using `qrcode.react`.
* Users page shows MFA column + per-row enable/disable/reset actions.
Admins viewing other users with MFA off see a non-actionable info
icon explaining that only the user themselves can enable MFA.
Deployment
==========
* `MFA_ENCRYPTION_KEY` is added to `k8s/manifests/03-secrets.yaml` as
a placeholder; `SECRET_KEY` is also placeholder-ized so both are
injected by the existing pipeline pattern (sed-replace + apply).
* No new build-time env vars are required for the frontend. The SPA
uses `window.location.host` for `/api/*` and is routed by the
existing nginx ingress configuration.
* `frontend/.dockerignore` ensures host `.env*` files cannot bleed
into the production bundle.
Tests
=====
* New unit suites:
- `test_mfa_service.py` (TOTP, encryption, backup codes)
- `test_mfa_backwards_compat.py` (regression — non-MFA flow unchanged)
- `test_mfa_rate_limits.py` (env override + dataclass immutability)
- `test_mfa_rate_limit_key.py` (JWT key, trusted-proxy XFF, fallbacks)
* All existing 1000+ unit tests continue to pass.
Documentation
=============
* README MFA section (overview, day-to-day operations, emergency
reset CLI, env variables, rate-limit tuning).
* `scripts/README.md` documents the bulk reset script.
Issue: #18
HAProxy Management UI - Unit Tests
Overview
Comprehensive unit test suite for the HAProxy Management UI backend, covering critical business logic and ensuring reliability.
Test Structure
Backend Tests (backend/tests/)
test_soft_delete.py- Soft delete functionality and unique constraintstest_apply_process.py- Critical apply process that manages entity statestest_entity_sync.py- Entity-specific agent sync calculationstest_haproxy_config.py- HAProxy configuration generationtest_auth.py- Authentication and authorization
Frontend Tests (frontend/src/components/__tests__/)
EntitySyncStatus.test.js- Agent sync status componentApplyManagement.test.js- Apply management workflowSSLManagement.test.js- SSL certificate management
Running Tests
Backend Tests
# Install test dependencies
pip install -r backend/requirements-test.txt
# Run all tests
pytest
# Run specific test file
pytest backend/tests/test_apply_process.py
# Run with coverage
pytest --cov=backend --cov-report=html
# Run specific test
pytest backend/tests/test_soft_delete.py::TestSoftDeleteUniqueConstraints::test_backend_soft_delete_allows_name_reuse
Frontend Tests
# Run all frontend tests
npm test
# Run with coverage
npm run test:coverage
# Run in CI mode
npm run test:ci
Test Coverage Goals
- Backend: 70% minimum coverage
- Frontend: 70% minimum coverage
- Critical paths: 90%+ coverage (apply process, soft delete, entity sync)
Critical Test Areas
🔴 HIGH PRIORITY
- Apply Process - Prevents entity disappearance bugs
- Soft Delete Logic - Ensures proper unique constraint handling
- Entity Sync Calculations - Agent sync status accuracy
- Authentication/Authorization - Security validation
🟡 MEDIUM PRIORITY
- HAProxy Config Generation - Configuration correctness
- SSL Management - Certificate lifecycle
- Form Validations - Input validation
🟢 LOW PRIORITY
- UI Components - Visual behavior
- Utility Functions - Helper functions
Mock Strategy
Backend Mocking
- Database connections:
AsyncMockfor database operations - External APIs: Mock HTTP calls
- File operations: Mock file system access
Frontend Mocking
- API calls: Mock axios requests
- Ant Design components: Mock component behavior
- Context providers: Mock React contexts
Test Data
All tests use consistent mock data from conftest.py:
- Sample clusters, backends, frontends
- Mock users and authentication
- Config versions and SSL certificates
Debugging Tests
# Run with verbose output
pytest -v -s
# Run specific failing test
pytest backend/tests/test_apply_process.py::TestApplyProcess::test_apply_process_preserves_active_entities -v -s
# Drop into debugger on failure
pytest --pdb
Integration with CI/CD
Tests are designed to run in Azure DevOps pipeline:
# Example pipeline step
- script: |
pip install -r backend/requirements-test.txt
pytest --cov=backend --cov-report=xml
displayName: 'Run Backend Tests'
- script: |
npm ci
npm run test:ci
displayName: 'Run Frontend Tests'
Adding New Tests
- Follow naming convention:
test_*.pyfor backend,*.test.jsfor frontend - Use appropriate fixtures: Leverage existing mock data
- Test edge cases: Include error scenarios and boundary conditions
- Update coverage: Ensure new code maintains coverage thresholds
Common Issues
Backend
- Async tests: Use
@pytest.mark.asynciodecorator - Database mocking: Ensure proper mock setup for database operations
- Import paths: Use relative imports for testable modules
Frontend
- Component rendering: Wait for async operations with
waitFor - Event simulation: Use
fireEventfor user interactions - Mock cleanup: Clear mocks between tests with
jest.clearAllMocks()
Test Philosophy
These tests focus on:
- Business logic correctness over implementation details
- Critical path coverage over 100% coverage
- Regression prevention based on actual bugs encountered
- Maintainability with clear, readable test cases
The test suite is designed to catch the types of bugs we've actually encountered in production, particularly around the apply process and soft delete behavior.
Test deployment trigger - $(date)