Перейти к содержимому

F-03 Infisical Python integration

Approach

Custom httpx-based client вместо infisicalsdk package:

  • Pros: full control над cache/retry/stale logic, минимум deps (только httpx + tenacity + cachetools + structlog), no SDK upgrade hell
  • Cons: maintain self, нет automatic API compatibility tracking

API endpoints используем напрямую:

  • POST /api/v1/auth/universal-auth/login (Machine Identity → access_token)
  • GET /api/v3/secrets/raw/{secretName}?workspaceId=...&environment=...&secretPath=...

Architecture

packages/core/src/razmakh_core/secrets/:

  • exceptions.py — InfisicalError hierarchy
  • infisical_client.py — main client class
  • secret_ref.py — URI parser razmakh://env/path/KEY
  • __init__.py — public API exports

Cache + stale-while-error pattern

Two-level TTL:

  • TTL=120s: fresh cache hit, no network call
  • TTL=120s..720s (10 min stale window): returns stale + WARN log + tries fresh fetch on each call
  • TTL>720s: cache evicted, fail-fast

Это balance:

  • 120s fresh = secrets propagation lag после rotation (acceptable для P22 runtime config)
  • 10 min stale = enough для short Infisical outages (restart, network blip)
  • Beyond 10 min — assume permanently broken, app should know

Test coverage (26 tests)

  • Config: missing env vars, empty creds, full setup
  • Auth: 401 fail, token caching
  • Get secret: success, cache hit, 404, force_refresh bypass
  • Stale-while-error: stale returned on connection error, too old raises, no cache raises
  • Retry: 5xx → retry → success
  • Cache invalidation: all + per-project

Все 26 ✅ + integration smoke test против real Infisical (https://secrets.razmakh.ru) — cfut_7lYQmQQ... retrieved + cached + via secret_ref.

Lint adjustments

ruff strict catches false positives в tests:

  • S105/S106/S107 (hardcoded password) — fixture values “csec”, “cid”
  • RUF001/002/003 (ambiguous unicode) — кириллица в comments / strings

Fix: [tool.ruff.lint.per-file-ignores]:

"**/tests/**/*.py" = ["S101", "S105", "S106", "S107"]
"**/*.py" = ["RUF001", "RUF002", "RUF003"]

Глобальный RUF001-003 ignore = intentional. Razmakh русскоязычный проект, кириллица в comments / docstrings нормальна, не security issue.

What this enables

  • F-04 SQLAlchemy + Alembic может resolved DB password из Infisical (razmakh://prod/_internal/postgres/PASSWORD)
  • F-08 FastAPI startup hook читает secrets через resolve_secret_ref() для:
    • WB API tokens per organization (Phase 1.5+)
    • Cloudflare token (для Caddy admin API если нужно)
    • LLM API keys (DeepSeek / GigaChat)
  • Backend config файл хранит secret_ref://... URIs вместо real values
  • GitHub Actions deploy не нужно знать ни одного real secret кроме INFISICAL_CLIENT_*

Deferred (Phase 1.5+)

  • Async variant AsyncInfisicalClient (когда FastAPI startup async deps)
  • Per-organization client factory (Block 1.5.1 — multi-org RBAC)
  • Cross-tenant probe в CI (Block 1.5.5)
  • Chaos test: Infisical 5 min down (Block 1.5.5)
  • Auto-rotation webhook → cache invalidate

ID metric

  • Implementation time: ~50 min
  • Test development: ~30 min (включая фиксы pytest respx context manager pattern)
  • Docs: ~15 min
  • Integration smoke test against real Infisical: 2 min
  • Total: ~1.5h vs 2-3 дня в plan estimate (~10x faster через LLM)