Key Encryption

This commit is contained in:
2026-05-18 01:33:23 +05:00
parent a552e55c29
commit c3422f7c67
11 changed files with 831 additions and 16 deletions
+1
View File
@@ -19,6 +19,7 @@ def init_db() -> None:
from ..models import metric as _metric # noqa: F401
from ..models import settings as _settings # noqa: F401
from ..models import interface_stat as _ifs # noqa: F401
from ..models import vault as _vault # noqa: F401
Base.metadata.create_all(bind=engine)
_ensure_columns()
+36 -4
View File
@@ -57,9 +57,19 @@ def decode_token(token: str) -> dict[str, Any]:
# --- Симметричное шифрование секретов устройств -----------------------------
# Производный ключ из SECRET_KEY (для dev). В prod — KMS / Vault.
# Двухслойная схема:
# v1: Fernet от SHA256(SECRET_KEY) — устаревший формат (без префикса для
# обратной совместимости). Только дешифрация. Запись новых v1 запрещена.
# v2: AES-256-GCM от DEK из services.vault, префикс "v2:".
#
# encrypt_secret() требует, чтобы vault был инициализирован и разблокирован.
# decrypt_secret() умеет читать оба формата.
# Подробнее см. services/vault.py.
def _fernet() -> Fernet:
SECRET_PREFIX_V2 = "v2:"
def _legacy_fernet() -> Fernet:
import base64
import hashlib
@@ -69,8 +79,30 @@ def _fernet() -> Fernet:
def encrypt_secret(value: str) -> str:
return _fernet().encrypt(value.encode()).decode()
"""Шифрует секрет DEK'ом из vault. Если vault ещё не инициализирован
(свежий апгрейд с 0.6.x) — fallback на legacy Fernet, чтобы существующие
сценарии не ломались. После /api/v1/vault/init все секреты пишутся как v2.
"""
from ..services.vault import vault_service # локальный импорт против цикла
initialized = vault_service.is_initialized_cached()
if initialized is False:
# legacy v1: пишем Fernet-токен без префикса
return _legacy_fernet().encrypt(value.encode()).decode()
# initialized is True (или None — тогда поверим, что инициализирован, и
# если на самом деле нет — VaultNotInitialized всплывёт; админу всё равно
# пора создавать мастер-пароль).
return vault_service.encrypt_secret(value)
def decrypt_secret(token: str) -> str:
return _fernet().decrypt(token.encode()).decode()
"""Дешифрует секрет. Поддерживает v2 (vault) и legacy v1 (SECRET_KEY)."""
if token.startswith(SECRET_PREFIX_V2):
from ..services.vault import vault_service
return vault_service.decrypt_secret_v2(token)
# Legacy: Fernet-токен без префикса.
return _legacy_fernet().decrypt(token.encode()).decode()
def is_legacy_secret(token: str) -> bool:
"""True, если секрет ещё не мигрирован на v2 (Fernet от SECRET_KEY)."""
return not token.startswith(SECRET_PREFIX_V2)