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:
+106
-7
@@ -62,7 +62,106 @@ Settings → Applications → Generate Token). Aggiungilo come secret a
|
||||
livello user (User Settings → Secrets → New Secret) con nome
|
||||
`REGISTRY_TOKEN`. Tutti i tuoi repo ereditano il secret automaticamente.
|
||||
|
||||
## 2. Setup iniziale del VPS
|
||||
## 2. Deploy automatizzato (script)
|
||||
|
||||
Il modo più rapido è `scripts/deploy.sh`, idempotente. Esegui sul VPS:
|
||||
|
||||
```bash
|
||||
# Prerequisiti
|
||||
export GITEA_PAT="<PAT con scope read:package>"
|
||||
export GITEA_USER=adriano
|
||||
mkdir -p ~/cerbero-secrets
|
||||
# Copia (via scp dal posto sicuro) i secret in ~/cerbero-secrets/:
|
||||
# deribit.json bybit.json hyperliquid.json alpaca.json
|
||||
# macro.json sentiment.json core.token observer.token
|
||||
|
||||
# Clone temporaneo solo per lo script
|
||||
curl -sL -o /tmp/deploy.sh \
|
||||
https://git.tielogic.xyz/Adriano/Cerbero-mcp/raw/branch/main/scripts/deploy.sh
|
||||
chmod +x /tmp/deploy.sh
|
||||
|
||||
# Run (richiede sudo per /opt/cerbero-mcp e /var/log/cerbero-mcp)
|
||||
/tmp/deploy.sh
|
||||
```
|
||||
|
||||
Lo script esegue: docker login registry → clone/pull repo in
|
||||
`/opt/cerbero-mcp` → copia secrets con `chmod 600` → genera `.env`
|
||||
iniziale (testnet) → crea `/var/log/cerbero-mcp` con permessi `1000:1000`
|
||||
→ pull image dal registry → `docker compose up -d` → smoke test pubblico.
|
||||
|
||||
Per aggiornare in seguito: ri-esegui lo stesso script (preserva `.env`).
|
||||
|
||||
## 3. Safety: switch testnet → mainnet
|
||||
|
||||
`mcp_common.environment.consistency_check` (richiamato dal boot
|
||||
`run_exchange_main`) PREVIENE switch accidentali:
|
||||
|
||||
- Se l'ambiente risolto è **mainnet** ma il secret JSON corrispondente
|
||||
non contiene `"environment": "mainnet"` esplicito → boot abort con
|
||||
`EnvironmentMismatchError`.
|
||||
- Se il secret dichiara un environment diverso da quello risolto (es.
|
||||
`creds["environment"]="mainnet"` ma env var setta testnet) → boot abort.
|
||||
|
||||
**Per passare a mainnet su un exchange specifico** (es. bybit):
|
||||
|
||||
1. Edita `secrets/bybit.json`: aggiungi `"environment": "mainnet"`.
|
||||
2. Modifica `.env`: `BYBIT_TESTNET=false`.
|
||||
3. `docker compose -f docker-compose.prod.yml --env-file .env restart mcp-bybit`.
|
||||
|
||||
Senza il flag esplicito nel secret, il container mcp-bybit fallirà al
|
||||
boot e Watchtower NON aggiornerà su versioni con cred mainnet rotti.
|
||||
|
||||
Override `STRICT_MAINNET=false` in `.env` permette mainnet senza la
|
||||
conferma esplicita (downgrade safety, sconsigliato in produzione).
|
||||
|
||||
## 4. Audit log persistente
|
||||
|
||||
Tutti i write endpoint (`place_order`, `place_combo_order`, `cancel_*`,
|
||||
`set_*`, `close_*`, `transfer_*`, `amend_*`, `switch_*`) emettono un
|
||||
record JSON strutturato sul logger `mcp.audit`.
|
||||
|
||||
**Sink**:
|
||||
- stdout/stderr container (sempre, visibile via `docker logs`).
|
||||
- File JSONL persistente su volume host:
|
||||
`${AUDIT_LOG_DIR:-/var/log/cerbero-mcp}/<service>.audit.jsonl`.
|
||||
Rotation a mezzanotte UTC con retention `AUDIT_LOG_BACKUP_DAYS`
|
||||
(default 30 giorni).
|
||||
|
||||
**Esempio record**:
|
||||
|
||||
```json
|
||||
{
|
||||
"audit_event": "write_op",
|
||||
"action": "place_order",
|
||||
"exchange": "bybit",
|
||||
"principal": "core",
|
||||
"target": "BTCUSDT",
|
||||
"payload": {"side": "Buy", "qty": 0.01, "price": 60000, "leverage": 3},
|
||||
"result": {"order_id": "abc123", "status": "submitted"}
|
||||
}
|
||||
```
|
||||
|
||||
**Query operative**:
|
||||
|
||||
```bash
|
||||
# Tutto l'audit log oggi
|
||||
tail -f /var/log/cerbero-mcp/*.audit.jsonl
|
||||
|
||||
# Solo place_order su bybit
|
||||
jq -c 'select(.action=="place_order" and .exchange=="bybit")' \
|
||||
/var/log/cerbero-mcp/bybit.audit.jsonl
|
||||
|
||||
# Errori
|
||||
jq -c 'select(.error)' /var/log/cerbero-mcp/*.audit.jsonl
|
||||
|
||||
# Operazioni di un principal
|
||||
jq -c 'select(.principal=="core")' /var/log/cerbero-mcp/*.audit.jsonl
|
||||
```
|
||||
|
||||
I secret (api_key, password) sono filtrati automaticamente da
|
||||
`SecretsFilter` prima di arrivare al sink.
|
||||
|
||||
## 5. Setup iniziale del VPS (manuale, alternativa allo script)
|
||||
|
||||
**Pre-requisiti**: Docker Engine ≥ 24, `docker compose` plugin, accesso SSH
|
||||
sudo, dominio DNS A record `cerbero-mcp.tielogic.xyz` → IP del VPS, porte 80
|
||||
@@ -138,7 +237,7 @@ docker compose -f docker-compose.prod.yml logs -f gateway
|
||||
Caddy chiede automaticamente il certificato Let's Encrypt al primo
|
||||
contatto su `https://cerbero-mcp.tielogic.xyz`.
|
||||
|
||||
## 3. Auto-update via Watchtower
|
||||
## 6. Auto-update via Watchtower
|
||||
|
||||
Watchtower (servizio `watchtower` nel compose) polla il registry ogni
|
||||
`WATCHTOWER_POLL_INTERVAL` secondi. Se trova un nuovo digest dietro al tag
|
||||
@@ -172,7 +271,7 @@ Rimuovi la label `com.centurylinklabs.watchtower.enable=true` per quel
|
||||
servizio nel compose (oppure imposta `=false`). Watchtower lo ignora ma
|
||||
continua a tenere aggiornati gli altri.
|
||||
|
||||
## 4. Rollback
|
||||
## 7. Rollback
|
||||
|
||||
```bash
|
||||
# Trova lo SHA della versione precedente
|
||||
@@ -187,7 +286,7 @@ docker compose -f docker-compose.prod.yml --env-file .env up -d
|
||||
Watchtower NON downgraderà perché il digest del tag pin corrisponde a quello
|
||||
locale.
|
||||
|
||||
## 5. Smoke test post-deploy
|
||||
## 8. Smoke test post-deploy
|
||||
|
||||
```bash
|
||||
# Da fuori VPS (laptop)
|
||||
@@ -204,7 +303,7 @@ curl -X POST https://cerbero-mcp.tielogic.xyz/mcp-deribit/tools/place_order \
|
||||
GATEWAY=http://localhost bash tests/smoke/run.sh
|
||||
```
|
||||
|
||||
## 6. Sicurezza VPS
|
||||
## 9. Sicurezza VPS
|
||||
|
||||
- Firewall `ufw`: `allow 22, 80, 443`. Tutto il resto deny in.
|
||||
- `fail2ban` su SSH e (opz) sul log Caddy 401.
|
||||
@@ -214,7 +313,7 @@ GATEWAY=http://localhost bash tests/smoke/run.sh
|
||||
- Audit log in `docker compose logs <service> | grep audit_event` — per
|
||||
produzione meglio redirezionare a syslog o a un servizio dedicato.
|
||||
|
||||
## 7. Note Traefik / reverse proxy davanti a Gitea
|
||||
## 10. Note Traefik / reverse proxy davanti a Gitea
|
||||
|
||||
Gitea è esposto via Traefik (ROOT_URL `https://git.tielogic.xyz`). Per il push
|
||||
di image Docker il reverse proxy deve consentire upload di body grossi (un
|
||||
@@ -234,7 +333,7 @@ http:
|
||||
|
||||
Applicalo come middleware al router Gitea.
|
||||
|
||||
## 8. Aggiornamento del compose stesso (file YAML)
|
||||
## 11. Aggiornamento del compose stesso (file YAML)
|
||||
|
||||
Watchtower aggiorna le **image**, non il `docker-compose.prod.yml`. Se cambi
|
||||
struttura (nuovi servizi, nuove env var) devi:
|
||||
|
||||
Reference in New Issue
Block a user