feat(safety+audit+deploy): consistency_check + audit log file sink + deploy script
#2 Env switch safety: - mcp_common/environment.py: nuova consistency_check() che previene switch accidentali a mainnet. Solleva EnvironmentMismatchError se resolved=mainnet senza creds["environment"]="mainnet" esplicito, o se declared/resolved mismatch. Override via STRICT_MAINNET=false. - Wirato in app_factory.run_exchange_main al boot. - 6 nuovi test consistency. #3 Audit log persistence: - mcp_common/audit.py: TimedRotatingFileHandler aggiuntivo se env AUDIT_LOG_FILE settato. Rotation midnight UTC, retention 30gg default (AUDIT_LOG_BACKUP_DAYS). Format JSONL con SecretsFilter. - docker-compose.prod.yml: bind mount /var/log/cerbero-mcp + env AUDIT_LOG_FILE per i 4 servizi exchange (write endpoints). - 2 nuovi test file sink. #1 Deploy script: - scripts/deploy.sh: idempotente, fa docker login + clone/pull repo + copia secrets chmod 600 + crea .env + setup audit dir + pull image + up + smoke test pubblico HTTPS. - DEPLOYMENT.md aggiornato: sezioni 2 (script), 3 (safety mainnet), 4 (audit log query), renumber sezioni successive. Test: 488/488 verdi. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,8 @@ from __future__ import annotations
|
||||
import json
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from mcp_common.app_factory import ExchangeAppSpec, run_exchange_main
|
||||
from mcp_common.environment import EnvironmentInfo
|
||||
|
||||
@@ -75,7 +77,9 @@ def test_run_exchange_main_uses_default_port(tmp_path, monkeypatch):
|
||||
|
||||
def test_run_exchange_main_env_var_overrides_creds(tmp_path, monkeypatch):
|
||||
creds_file = tmp_path / "creds.json"
|
||||
creds_file.write_text(json.dumps({"testnet": True}))
|
||||
# `environment: mainnet` esplicito perché env var override → mainnet
|
||||
# e consistency_check richiede conferma per evitare switch accidentale.
|
||||
creds_file.write_text(json.dumps({"testnet": True, "environment": "mainnet"}))
|
||||
monkeypatch.setenv("TESTEX_CREDENTIALS_FILE", str(creds_file))
|
||||
monkeypatch.setenv("TESTEX_TESTNET", "false")
|
||||
|
||||
@@ -95,6 +99,33 @@ def test_run_exchange_main_env_var_overrides_creds(tmp_path, monkeypatch):
|
||||
assert captured["env_info"].source == "env"
|
||||
|
||||
|
||||
def test_run_exchange_main_aborts_on_mainnet_without_confirmation(tmp_path, monkeypatch):
|
||||
"""Mainnet senza creds['environment']='mainnet' → boot abort fail-fast."""
|
||||
from mcp_common.environment import EnvironmentMismatchError
|
||||
creds_file = tmp_path / "creds.json"
|
||||
creds_file.write_text(json.dumps({"testnet": False}))
|
||||
monkeypatch.setenv("TESTEX_CREDENTIALS_FILE", str(creds_file))
|
||||
monkeypatch.delenv("TESTEX_TESTNET", raising=False)
|
||||
monkeypatch.delenv("STRICT_MAINNET", raising=False)
|
||||
|
||||
spec = _make_spec()
|
||||
with pytest.raises(EnvironmentMismatchError):
|
||||
with patch("mcp_common.app_factory.uvicorn.run"):
|
||||
run_exchange_main(spec)
|
||||
|
||||
|
||||
def test_run_exchange_main_strict_mainnet_disabled_via_env(tmp_path, monkeypatch):
|
||||
"""STRICT_MAINNET=false permette mainnet senza conferma (warning soltanto)."""
|
||||
creds_file = tmp_path / "creds.json"
|
||||
creds_file.write_text(json.dumps({"testnet": False}))
|
||||
monkeypatch.setenv("TESTEX_CREDENTIALS_FILE", str(creds_file))
|
||||
monkeypatch.setenv("STRICT_MAINNET", "false")
|
||||
|
||||
spec = _make_spec()
|
||||
with patch("mcp_common.app_factory.uvicorn.run"):
|
||||
run_exchange_main(spec) # non solleva
|
||||
|
||||
|
||||
def test_run_exchange_main_missing_creds_file_exits(monkeypatch):
|
||||
monkeypatch.delenv("TESTEX_CREDENTIALS_FILE", raising=False)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user