Find a file
2026-03-03 17:50:17 +03:00
.github/workflows add security test 2026-03-02 16:22:07 +03:00
.idea first commit 2026-02-22 10:57:49 +03:00
alembic add security test 03 2026-03-02 16:44:48 +03:00
app fix user UI 9 2026-03-03 17:50:17 +03:00
context add security test 04 2026-03-02 17:18:10 +03:00
deploy add security test 2026-03-02 16:22:07 +03:00
docs add security test 2026-03-02 16:22:07 +03:00
e2e fix client chat v2 2026-02-27 21:10:01 +03:00
frontend fix upload files 2 2026-03-02 21:02:03 +03:00
scripts/ops add security test 11 2026-03-02 18:04:39 +03:00
tests fix user UI 9 2026-03-03 17:50:17 +03:00
tmp Third commit 2026-02-23 17:54:19 +03:00
.env.production add security test 04 2026-03-02 17:18:10 +03:00
.gitignore add security test 2026-03-02 16:22:07 +03:00
alembic.ini first commit 2026-02-22 10:57:49 +03:00
docker-compose.local.yml add security test 2026-03-02 16:22:07 +03:00
docker-compose.prod.cert.yml add cert 2 2026-02-28 15:36:33 +03:00
docker-compose.prod.nginx.yml add security test 04 2026-03-02 17:18:10 +03:00
docker-compose.prod.yml add security test 04 2026-03-02 17:18:10 +03:00
docker-compose.yml add security test 04 2026-03-02 17:18:10 +03:00
Dockerfile fix user UI 7 2026-03-03 14:13:59 +03:00
Makefile add mock data 2026-03-02 19:53:42 +03:00
README.md fix user UI 7 2026-03-03 14:24:11 +03:00
requirements.txt fix user UI 7 2026-03-03 14:13:59 +03:00

Legal Case Tracker (FastAPI)

Backend skeleton: public requests + OTP + public JWT cookie + admin (admin/lawyer) + files (self-hosted S3) + SLA/auto-assign (Celery) + quotes + dedicated chat microservice.

Run (Docker)

cp .env.example .env
docker compose -f docker-compose.yml -f docker-compose.local.yml up --build

Landing (frontend): http://localhost:8081 Admin UI: http://localhost:8081/admin API (backend): http://localhost:8002 Swagger: http://localhost:8002/docs Chat service health (via nginx): http://localhost:8081/chat-health Email service health (via nginx): http://localhost:8081/email-health

Testing (containers only)

Rule: backend tests must be executed only inside Docker containers. Do not run tests from host Python environment.

Start local stack (if not started):

docker compose -f docker-compose.yml -f docker-compose.local.yml up -d

Run full backend suite via unittest (default project mode):

docker compose -f docker-compose.yml -f docker-compose.local.yml exec -T backend \
  python -m unittest discover -s tests -p "test_*.py" -v

Run target tests via pytest inside one-off backend container:

docker compose -f docker-compose.yml -f docker-compose.local.yml run --rm --no-deps backend \
  sh -lc 'pip install --no-cache-dir pytest >/tmp/pip_pytest.log && python -m pytest -q tests/test_public_requests.py tests/test_public_cabinet.py'

Use this command when targeted pytest checks are required and pytest is not preinstalled in the backend image.

Production (ruakb.ru + ruakb.online, 80/443, TLS via Nginx + Certbot)

Production stack uses dedicated edge nginx (docker-compose.prod.nginx.yml).

Use production template before first deploy:

cp .env.production .env

Prerequisites:

  • DNS A record: ruakb.ru -> 45.150.36.116
  • Optional DNS A record: www.ruakb.ru -> 45.150.36.116
  • DNS A record: ruakb.online -> 45.150.36.116
  • Optional DNS A record: www.ruakb.online -> 45.150.36.116
  • Open server ports: 80/tcp, 443/tcp
  • DB credentials in .env must be consistent:
    • DATABASE_URL=postgresql+psycopg://postgres:<password>@db:5432/legal
    • POSTGRES_PASSWORD=<same password>

Production security baseline in .env:

APP_ENV=prod
PRODUCTION_ENFORCE_SECURE_SETTINGS=true
OTP_DEV_MODE=false
ADMIN_BOOTSTRAP_ENABLED=false
PUBLIC_COOKIE_SECURE=true
PUBLIC_COOKIE_SAMESITE=lax
PUBLIC_ALLOWED_WEB_ORIGINS=https://ruakb.ru,https://www.ruakb.ru,https://ruakb.online,https://www.ruakb.online
CORS_ORIGINS=https://ruakb.ru,https://www.ruakb.ru,https://ruakb.online,https://www.ruakb.online
CORS_ALLOW_METHODS=GET,POST,PUT,PATCH,DELETE,OPTIONS
CORS_ALLOW_HEADERS=Authorization,Content-Type,X-Requested-With,X-Request-ID
S3_USE_SSL=true
S3_VERIFY_SSL=true
S3_CA_CERT_PATH=/etc/ssl/minio/ca.crt
MINIO_TLS_ENABLED=true
CHAT_ENCRYPTION_SECRET=<strong-random-secret>
DATA_ENCRYPTION_SECRET=<strong-random-secret>
DATA_ENCRYPTION_ACTIVE_KID=<kid>
DATA_ENCRYPTION_KEYS=<kid>=<strong-random-secret>
CHAT_ENCRYPTION_ACTIVE_KID=<kid>
CHAT_ENCRYPTION_KEYS=<kid>=<strong-random-secret>
ADMIN_JWT_SECRET=<strong-random-secret>
PUBLIC_JWT_SECRET=<strong-random-secret>
INTERNAL_SERVICE_TOKEN=<strong-random-secret>
MINIO_ROOT_USER=<non-default-user>
MINIO_ROOT_PASSWORD=<strong-random-password>

Initialize internal TLS for MinIO before first production start:

make prod-minio-tls-init

This generates:

  • deploy/tls/minio/public.crt
  • deploy/tls/minio/private.key
  • deploy/tls/minio/ca.crt

Production frontend/backend/worker trust deploy/tls/minio/ca.crt and use HTTPS to MinIO inside docker network.

Initial certificate issue (bootstrap with nginx on port 80 only):

make prod-cert-init LETSENCRYPT_EMAIL=you@example.com DOMAIN=ruakb.ru WWW_DOMAIN=www.ruakb.ru

By default prod-cert-init also includes ruakb.online and www.ruakb.online. If needed, override:

make prod-cert-init \
  LETSENCRYPT_EMAIL=you@example.com \
  DOMAIN=ruakb.ru WWW_DOMAIN=www.ruakb.ru \
  SECOND_DOMAIN=ruakb.online SECOND_WWW_DOMAIN=www.ruakb.online

Regular production start/update:

make prod-up

make prod-up includes strict preflight checks (scripts/ops/deploy_prod.sh) and fails on insecure production env values (weak/default secrets, localhost origins, disabled strict origin checks, non-TOTP-required admin auth, etc.).

Certificate renew:

make prod-cert-renew

Internal secret rotation (excluding external providers such as SMS/Telegram):

make prod-secrets-generate

This creates .env.prod with new generated internal secrets.

Apply generated secrets to running production stack:

make prod-secrets-apply

This will backup current .env, replace it with .env.prod, rotate Postgres password in DB, recreate containers, run migrations, and execute health checks.

Encryption KID rotation (without data loss):

make rotate-encryption-kid
# restart backend/chat/worker
make reencrypt-active-kid

rotate-encryption-kid appends a new kid=secret into DATA_ENCRYPTION_KEYS and CHAT_ENCRYPTION_KEYS and switches *_ACTIVE_KID to the new value. reencrypt-active-kid re-encrypts historical invoice requisites, admin TOTP secrets, and chat message bodies.

Safety guard for apply mode:

  • script requires confirmation token ROTATE-PROD-SECRETS;
  • default make prod-secrets-apply passes it via CONFIRM_TOKEN;
  • you can override explicitly:
make prod-secrets-apply CONFIRM_TOKEN=ROTATE-PROD-SECRETS

Checks:

curl -I https://ruakb.ru
curl -fsS https://ruakb.ru/health
curl -fsS https://ruakb.ru/chat-health
docker compose -f docker-compose.yml -f docker-compose.prod.nginx.yml exec -T backend sh -lc 'python - <<PY\nfrom app.services.s3_storage import get_s3_storage\nprint(get_s3_storage().client._endpoint.host)\nPY'
ss -lntp | egrep ':(80|443|5432|6379|8002|8081|9000|9001)\\b'

Incident response (PDn)

Create a standard incident checklist report:

make incident-checklist

or with details:

./scripts/ops/incident_checklist.sh \
  --severity HIGH \
  --category UNAUTHORIZED_ACCESS \
  --summary "Suspicious read access to request cards" \
  --track-number TRK-XXXX

Generated markdown report is saved to reports/incidents/incident-<timestamp>.md.

Migrations

docker compose exec backend alembic upgrade head

Seed Quotes (Upsert)

make seed-quotes

Loads 50 justice-themed quotes into quotes with idempotent upsert by (author, text).

OTP SMS provider (SMS Aero)

OTP sending is implemented through a dedicated SMS service layer (app/services/sms_service.py).

Public auth mode can be selected via environment:

PUBLIC_AUTH_MODE=sms          # sms | email | sms_or_email | totp
EMAIL_SERVICE_ENABLED=true    # false -> email-service stays healthy but sending is disabled
EMAIL_PROVIDER=dummy          # dummy | smtp | service
EMAIL_SERVICE_URL=http://email-service:8010
INTERNAL_SERVICE_TOKEN=change_me_internal_service_token
OTP_EMAIL_FALLBACK_ENABLED=true
OTP_SMS_MIN_BALANCE=20
ADMIN_AUTH_MODE=password_totp_optional  # password | password_totp_optional | password_totp_required
TOTP_ISSUER=Правовой Трекер

Configure provider in .env:

SMS_PROVIDER=smsaero
SMSAERO_EMAIL=your_email@example.com
SMSAERO_API_KEY=your_api_key
OTP_SMS_TEMPLATE=Your verification code: {code}
OTP_DEV_MODE=false
OTP_AUTOTEST_FORCE_MOCK_SMS=true

For SMTP email OTP:

EMAIL_PROVIDER=smtp
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=mailer@example.com
SMTP_PASSWORD=your_password
SMTP_FROM=mailer@example.com
SMTP_USE_TLS=true
SMTP_USE_SSL=false
OTP_EMAIL_SUBJECT_TEMPLATE=Код подтверждения: {code}
OTP_EMAIL_TEMPLATE=Ваш код подтверждения: {code}

For dedicated email microservice (recommended in production):

EMAIL_SERVICE_ENABLED=true
EMAIL_PROVIDER=service
EMAIL_SERVICE_URL=http://email-service:8010
INTERNAL_SERVICE_TOKEN=<strong-random-token>

To keep infrastructure healthy but disable email sending temporarily:

EMAIL_SERVICE_ENABLED=false

Admin/Lawyer TOTP endpoints:

  • GET /api/admin/auth/totp/status
  • POST /api/admin/auth/totp/setup
  • POST /api/admin/auth/totp/enable
  • POST /api/admin/auth/totp/backup/regenerate
  • POST /api/admin/auth/totp/disable

For local/dev mock mode:

SMS_PROVIDER=dummy

In this mode OTP code is printed to backend logs.

You can also force mock mode with real provider settings:

OTP_DEV_MODE=true

When enabled, real SMS sending is disabled and OTP code is printed to backend logs.

Additionally, to protect SMS budget during automated tests:

OTP_AUTOTEST_FORCE_MOCK_SMS=true

When this flag is enabled and runtime is detected as autotest (pytest/unittest/APP_ENV=test|ci), SMS_PROVIDER=smsaero is automatically forced to mock mode for OTP sending.

Admin health-check endpoint (no SMS send): GET /api/admin/system/sms-provider-health

Secure Chat (encrypted at rest)

Chat logic is isolated in app/services/chat_secure_service.py.

  • Message bodies are encrypted before storing in DB (messages.body) and transparently decrypted on read.
  • Encryption key priority:
    1. CHAT_ENCRYPTION_SECRET
    2. DATA_ENCRYPTION_SECRET
    3. JWT secrets fallback (not recommended for production)

Recommended production config:

CHAT_ENCRYPTION_SECRET=<long-random-secret>
DATA_ENCRYPTION_SECRET=<long-random-secret>

Chat API runs in a dedicated container (chat-service) with separate FastAPI entrypoint: app/chat_main.py

Nginx routes only chat API prefixes to the chat container:

  • /api/public/chat/*
  • /api/admin/chat/*

Attachment antivirus and content checks

Attachment scanning is asynchronous (Celery queue uploads) and supports ClamAV + content policy checks.

Environment flags:

ATTACHMENT_SCAN_ENABLED=true
ATTACHMENT_SCAN_ENFORCE=true
ATTACHMENT_ALLOWED_MIME_TYPES=application/pdf,image/jpeg,image/png,video/mp4,text/plain
CLAMAV_ENABLED=true
CLAMAV_HOST=clamav
CLAMAV_PORT=3310
CLAMAV_TIMEOUT_SECONDS=20

Compose profiles by environment:

  • local (docker-compose.local.yml): clamav uses mkodockx/docker-clamav:alpine (multi-arch, including arm64).
  • prod (docker-compose.prod*.yml): clamav stays on official clamav/clamav with platform: linux/amd64.

Scan statuses on attachments:

  • PENDING (file uploaded, scan in progress)
  • CLEAN (safe to download)
  • INFECTED (blocked)
  • ERROR (scan failed, blocked when enforcement is on)

When ATTACHMENT_SCAN_ENFORCE=true, public/admin download endpoints block non-clean files.

Security CI pipeline (SEC-14)

GitHub Actions workflow: /Users/tronosfera/Develop/Law/.github/workflows/security-ci.yml

Checks:

  • SAST: bandit for app/ and scripts/.
  • Dependency scan: pip-audit for requirements.txt.
  • Container scan: trivy on backend Docker image.

Fail thresholds (configurable in workflow env):

  • BANDIT_MAX_HIGH (default 0)
  • DEP_MAX_VULNS (default 0)
  • TRIVY_MAX_HIGH (default 0)
  • TRIVY_MAX_CRITICAL (default 0)

Artifacts uploaded for each run:

  • reports/security/bandit.json
  • reports/security/pip-audit.json
  • reports/security/sast-deps-summary.txt
  • reports/security/trivy-image.json
  • reports/security/trivy-image.sarif
  • reports/security/trivy-summary.txt

Security smoke (SEC-15)

Local/manual run:

make security-smoke

or with explicit target URL:

./scripts/ops/security_smoke.sh https://ruakb.online

What is checked:

  • public health endpoints (/health, /chat-health, /email-health);
  • required security headers (X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Content-Security-Policy);
  • TLS certificate availability/expiry (for https:// URLs);
  • production cookie/origin env flags (PUBLIC_COOKIE_SECURE, PUBLIC_COOKIE_SAMESITE, PUBLIC_STRICT_ORIGIN_CHECK);
  • attachment scan service availability (clamav, when scan is enabled);
  • DB access to security_audit_log (table exists + query works).

Report is saved to:

  • reports/security/security-smoke-<timestamp>.md

Example cron (every 15 minutes):

*/15 * * * * cd /opt/Law && ./scripts/ops/security_smoke.sh https://ruakb.online >> /var/log/law-security-smoke.log 2>&1

Full production security audit and auto-repair

Single command to verify full security block and auto-generate missing technical artifacts:

make prod-security-audit DOMAIN=ruakb.ru WWW_DOMAIN=www.ruakb.ru SECOND_DOMAIN=ruakb.online SECOND_WWW_DOMAIN=www.ruakb.online LETSENCRYPT_EMAIL=you@example.com

What this workflow does (scripts/ops/prod_security_audit.sh):

  • checks required compose/nginx files;
  • restores/generates .env if missing (.env.prod or .env.production + rotate_prod_secrets.sh);
  • generates MinIO internal TLS bundle if missing (prod-minio-tls-init equivalent);
  • starts/reconciles production stack (nginx profile);
  • applies DB migrations;
  • validates production security config (validate_production_security_or_raise);
  • runs local and external security smoke checks;
  • generates incident checklist snapshot report.

Optional: auto-bootstrap Let's Encrypt certs if HTTPS health is failing:

make prod-security-audit AUTO_CERT_INIT=1 DOMAIN=ruakb.ru WWW_DOMAIN=www.ruakb.ru SECOND_DOMAIN=ruakb.online SECOND_WWW_DOMAIN=www.ruakb.online LETSENCRYPT_EMAIL=you@example.com

If localhost loopback probes are not desired on production host:

make prod-security-audit SKIP_LOCAL_SMOKE=1 DOMAIN=ruakb.ru WWW_DOMAIN=www.ruakb.ru SECOND_DOMAIN=ruakb.online SECOND_WWW_DOMAIN=www.ruakb.online LETSENCRYPT_EMAIL=you@example.com

By default local smoke probes now try multiple loopback endpoints:

  • https://127.0.0.1
  • https://localhost
  • http://127.0.0.1
  • http://localhost

You can override:

make prod-security-audit LOCAL_SMOKE_CANDIDATES="https://127.0.0.1,http://127.0.0.1"

Dedicated security scheduler entity

security-scheduler is a separate container service that runs periodic smoke checks automatically.

Start/update:

make prod-security-scheduler-up

Logs:

make prod-security-scheduler-logs

Main env knobs:

SECURITY_SCHEDULER_INTERVAL_SECONDS=900
SECURITY_SCHEDULER_INTERNAL_BASE_URL=http://frontend
SECURITY_SCHEDULER_EXTERNAL_DOMAINS=ruakb.ru,ruakb.online
SECURITY_SCHEDULER_SKIP_DOCKER_CHECKS=1
SECURITY_SCHEDULER_RUN_INCIDENT_ON_FAIL=1

Scheduler script:

  • /Users/tronosfera/Develop/Law/scripts/ops/security_scheduler.sh

Container health and alerting

Docker Compose is configured with:

  • restart: unless-stopped for core services
  • healthcheck for db, redis, backend, chat-service, frontend
  • startup ordering via depends_on: condition: service_healthy

Quick checks:

docker compose up -d
docker compose ps
curl -fsS http://localhost:8081/health
curl -fsS http://localhost:8081/chat-health
curl -fsS http://localhost:8081/email-health

Alert-ready smoke script (for cron/CI):

./scripts/ops/check_chat_health.sh

Exit code 0 means healthy, non-zero means alert condition.