fix(exchange): preserve old archives during server key rotation
All checks were successful
State Corp Backend CI/CD / Quality gate (push) Successful in 3m19s
State Corp Backend CI/CD / Build linux/amd64 images once (push) Successful in 2m54s
State Corp Backend CI/CD / Refresh and release internal main (push) Has been skipped
State Corp Backend CI/CD / Release customer main (push) Has been skipped
State Corp Backend CI/CD / Release dev (push) Successful in 54s

This commit is contained in:
Aleksandr Meshchryakov
2026-09-14 00:04:53 +02:00
parent e99616ed6d
commit 78412f9e3b
5 changed files with 163 additions and 6 deletions

View File

@@ -412,6 +412,30 @@ class ExchangePackageImportService:
return header, bin_bytes[header_end:]
@classmethod
def _decryption_token(cls, header: dict[str, Any]) -> str:
token = str(getattr(settings, "EXCHANGE_SHARED_TOKEN", "") or "").strip()
if not token:
raise ExchangeImportError("EXCHANGE_SHARED_TOKEN не настроен")
previous_token = str(
getattr(settings, "EXCHANGE_PREVIOUS_SHARED_TOKEN", "") or ""
).strip()
previous_id = str(
getattr(settings, "EXCHANGE_PREVIOUS_KEY_ID", "") or ""
).strip()
current_id = str(getattr(settings, "EXCHANGE_KEY_ID", "") or "").strip()
if previous_token or previous_id:
if not previous_token or not previous_id or previous_id == current_id:
raise ExchangeImportError(
"Некорректно настроен предыдущий ключ расшифровки обмена"
)
if header.get("key_id") == previous_id:
return previous_token
# Preserve the existing current-key contract, including old containers
# without key_id. Never try the previous secret as a decryption fallback.
return token
@classmethod
def _decrypt_payload(
cls,
@@ -419,10 +443,7 @@ class ExchangePackageImportService:
header: dict[str, Any],
encrypted_payload: bytes,
) -> dict[str, Any]:
token = str(getattr(settings, "EXCHANGE_SHARED_TOKEN", "") or "").strip()
if not token:
raise ExchangeImportError("EXCHANGE_SHARED_TOKEN не настроен")
token = cls._decryption_token(header)
raw_key = hashlib.sha256(token.encode("utf-8")).digest()
nonce = cls._decode_base64_field(header, "nonce")
aad = cls._decode_base64_field(header, "aad")

View File

@@ -19,6 +19,9 @@ BACKUP_ENCRYPTION_KEY = os.getenv("BACKUP_ENCRYPTION_KEY", "")
BACKUP_KEY_ID = os.getenv("BACKUP_KEY_ID", "default")
EXCHANGE_SHARED_TOKEN = os.getenv("EXCHANGE_SHARED_TOKEN", "")
EXCHANGE_KEY_ID = os.getenv("EXCHANGE_KEY_ID", "dev-shared-token")
# Read old encrypted archives after rotation; never accepted as HTTP credentials.
EXCHANGE_PREVIOUS_SHARED_TOKEN = os.getenv("EXCHANGE_PREVIOUS_SHARED_TOKEN", "")
EXCHANGE_PREVIOUS_KEY_ID = os.getenv("EXCHANGE_PREVIOUS_KEY_ID", "")
warnings.filterwarnings(
"ignore",