MSP — triage cross-tenant et livraison
1. Triage cross-tenant du lundi matin
Dans le tableau de bord
- Login en MSP-admin → Sidebar → MSP cockpit
- Tous tenants triés par urgency descending
- Cliquer tenant urgency le plus haut → ouvre en contexte tenant
- Filtre ‘urgency > 50’ pour isoler le travail du matin
/msp/cockpit — une page, tous les tenants que vous gérez, triés par
urgency composite :
Ou via API (avancé — pour automatisation)
urgency = (open_critical × 10) + (open_high × 3) + (sla_breach_minutes / 60) + (kev_open_cves) + (trust_score_delta_24h × -2) -- une baisse est urgente + (overdue_eats × 5)Workflow :
- Top 3 tenants par urgency → cliquer → fix
- Par tenant vous voyez un mini Trust Score (actuel + delta 24h) + top open alert + top open kernel CVE
- Un clic “Open in tenant context” utilise l’impersonation RBAC (voir §8) — pas de re-login par tenant
Filtre : urgency > 50 affiche typiquement 2-5 tenants. Le reste se
trouve sous 50 et peut attendre cette semaine — pas aujourd’hui.
2. Rapport de remise tenant — ce qu’on a fait ce mois
Dans le tableau de bord
- Sidebar → Audit Packs → choisir le mois du client
- Cliquer ‘Download for handover’ → filtre actor LIKE ’%@yourMSP.com’
- Le PDF + .sig résultants sont signés par le hub
- Email au client — il vérifie offline avec monsys-verify-eat CLI
Le client demande chaque mois : “qu’est-ce que votre équipe a fait pour moi ?”
Le Monthly Audit Pack couvre déjà cela :
2026-04.jsonl.gz— tous les EATs exécutés au nom du client2026-04.pdf— agrégé : évolution Trust Score, mises à jour noyau exécutées, CVEs corrigées, alertes traitées, sessions ouvertes
MSP-spécifique : filtrer le PDF sur les actions de votre seule équipe
(u.email LIKE '%@yourMSP.com') :
Ou via API (avancé — pour automatisation)
curl 'https://app.monsys.ai/api/v1/audit-packs/<pack_id>/download?format=pdf&actor_filter=@yourMSP.com' \ -H "Authorization: Bearer $TOKEN" -o handover-acme-2026-04.pdfEnvoyez ce PDF + .sig directement au client. Il vérifie lui-même
que cela vient du runtime monsys, pas de votre éditeur :
./monsys-verify-eat-linux-x64 verify-pack \ --pack handover-acme-2026-04.pdf \ --sig handover-acme-2026-04.sig \ --pubkey https://transparency.monsys.ai/pubkeys/hub.pub3. Branding white-label par tenant
Dans le tableau de bord
- Sidebar → Settings → Tenant branding (par client)
- Uploader logo + couleur + product name + custom_domain
- Client ouvre custom_domain → voit propre brand, votre MSP comme ‘powered by’
- Scope RBAC reste votre gestion ; client uniquement read-only
Depuis schema 31 le branding per-tenant est dans le hub. Le portail client affiche leur logo, couleur, domaine — pas votre brand monsys.ai.
Ou via API (avancé — pour automatisation)
curl -X PUT https://app.monsys.ai/api/v1/tenants/<id>/branding \ -H "Authorization: Bearer $MSP_ADMIN_TOKEN" \ -F 'logo=@acme-logo.svg' \ -F 'primary_color=#1e3a8a' \ -F 'product_name=AcmeOps' \ -F 'custom_domain=ops.acme.com'Le client ouvre ops.acme.com → voit AcmeOps comme brand, votre MSP
crédité en footer comme “powered by”. Le client ne peut pas faire
d’actions admin lui-même (c’est votre RBAC scope), mais peut voir
read-only ce qui se passe + télécharger ses propres preuves d’audit.
4. Auto-grouper les agents par tenant via tag
Dans le tableau de bord
- Sidebar → Groups → ‘Nouveau groupe’ (en contexte tenant)
- Rule : all_of [tag=production, tag=eu-west-1]
- Ajouter runbook markdown dans le champ ‘Runbook’
- GroupMembershipWorker (tick 5min) met à jour automatiquement l’appartenance
Un client a 40 hôtes répartis sur dev/staging/prod. Gérer des groupes statiques = charge de maintenance. Groupes dynamiques via tag rule :
Ou via API (avancé — pour automatisation)
curl -X POST https://app.monsys.ai/api/v1/groups \ -H "Authorization: Bearer $TOKEN" \ -d '{ "tenant_id": "<acme_uuid>", "name": "production-eu", "rule": { "all_of": [ {"tag": "production"}, {"tag": "eu-west-1"} ] }, "runbook_md": "# Production EU runbook\n\n…" }'GroupMembershipWorker (toutes les 5 min) hashe le set et met à jour
l’appartenance. Un nouvel hôte s’enregistrant avec tags
production,eu-west-1 atterrit automatiquement dans ce groupe +
hérite du runbook + SLA + rotation on-call.
5. EAT pré-émis pour urgence hors heures de bureau
Dans le tableau de bord
- Sidebar → Playbooks → choisir ‘Isolate network’
- Bouton ‘Pre-issue pour agent’ → choisir host + fenêtre valide
- Condition (heartbeat lost / critical alert) + TOTP
- Agent reçoit EAT via WS, l’active lui-même quand condition matche
Problème : le client a un incident à 2 h. Votre engineer on-call est éveillé mais doit d’abord s’authentifier au hub + TOTP + émettre un EAT Ed25519. C’est 5 minutes supplémentaires quand les secondes comptent.
Solution (mig 091) : playbook EATs pré-émis — émis à un agent spécifique avec TTL court ET une condition que seul le contexte on-call peut activer.
Ou via API (avancé — pour automatisation)
curl -X POST https://app.monsys.ai/api/v1/agents/<id>/pre-issued-eats \ -H "Authorization: Bearer $TOKEN" \ -H "X-TOTP-Code: 123456" \ -d '{ "playbook_id": "<isolate-network-playbook_id>", "valid_from": "2026-05-19T18:00:00Z", "valid_until": "2026-05-20T08:00:00Z", "conditions": { "heartbeat_lost_minutes": 5, "or_severity_critical": true }, "reason": "After-hours coverage for ACME — Saturday night" }'Pendant la fenêtre, si l’agent détecte qu’il n’a pas pu envoyer de heartbeat depuis >5 min OU qu’une alerte critique est ouverte, il peut exécuter l’EAT pré-émis lui-même (une fois, nonce single-use consommé). Les preuves d’audit sont identiques à un EAT normal.
Cas d’usage :
- Isolation réseau quand un pattern ransomware est détecté
- Redémarrage d’une app spécifique sans input opérateur
- Quarantaine d’un fichier suspect
6. Signature multi-parties pour actions irréversibles
Dans le tableau de bord
- Sidebar → Emergency → ‘Nouvel EAT niveau 3’
- Saisir action + raison + required_approvers = 2
- Autres admins reçoivent push sur PWA mobile avec ‘Approve’/‘Reject’
- À N approbations (chacune propre TOTP, split-control) → EAT fires
Certaines actions sont si destructrices qu’un TOTP unique ne suffit pas (restore DB prod, mise à jour noyau laptop CEO, rotation secrets fleet-wide). Les EATs niveau 3 exigent quorum.
Ou via API (avancé — pour automatisation)
curl -X POST https://app.monsys.ai/api/v1/emergency/quorum \ -H "Authorization: Bearer $TOKEN" \ -H "X-TOTP-Code: 123456" \ -d '{ "agent_id": "<id>", "actions": [{ "kind": "run_playbook", "id": "db-restore" }], "reason": "Restore from 2026-04-15 snapshot per ticket TKT-9001", "required_approvers": 2 }'Le hub envoie une notification ntfy à chaque autre engineer MSP avec rôle admin. La PWA mobile affiche “Pending approval — DB restore on ACME” :
ACME / db-prod-01RunPlaybook: db-restoreRequested by alice@yourMSP.com at 14:23Reason: Restore from 2026-04-15 snapshot per ticket TKT-9001[Approve with TOTP] [Reject]Seulement quand N approbations sont collectées ET chacune via un flux
TOTP différent (split-control : aucun engineer seul ne peut approuver
deux fois) l’EAT se déclenche. La preuve de quorum atterrit dans
audit_log :
SELECT event_type, event_data FROM audit_log WHERE event_type = 'emergency_quorum_approved' AND event_data->>'nonce' = '<nonce>';Le tenant voit dans le PDF /audit-packs que l’action a été exécutée
via quorum 2-of-2 — preuve forte pour la séparation des devoirs SOC2.
7. MSP billing — agréger tous les tenants dans une facture
Dans le tableau de bord
- Sidebar → Billing → onglet ‘Cross-tenant overview’
- Table : agents actifs par tenant + facturables (après 5 gratuits)
- Total monthly_eur par client
- Bouton ‘Export for invoice’ → CSV par mois
Pour les clients où vous payez la facture (vous re-facturez ensuite) :
Ou via API (avancé — pour automatisation)
WITH per_tenant AS ( SELECT t.id, t.name, COUNT(*) FILTER (WHERE a.is_active=true) AS active_agents, COUNT(*) FILTER (WHERE a.is_active=true) - 5 AS billable FROM tenants t JOIN agents a ON a.tenant_id = t.id WHERE t.msp_owner = $1::UUID AND t.created_at < date_trunc('month', NOW()) GROUP BY t.id)SELECT name, active_agents, GREATEST(billable, 0) AS billable_agents, GREATEST(billable, 0) * 3.0 AS monthly_eur FROM per_tenant ORDER BY monthly_eur DESC;Les 5 premiers agents par tenant sont gratuits (par tenant, pas par
MSP). La colonne msp_owner sur tenants est la relation qui lie
votre rôle MSP.
8. Impersonation RBAC pour support cross-tenant
Dans le tableau de bord
- Tenant switcher top-right → choisir client + ‘Impersonate’
- Saisir raison + durée 60min + TOTP
- Travailler en contexte client — actions ont double acteur dans audit_log
- Client voit événement impersonation_started dans son propre Audit Pack
L’engineer Alice (MSP admin) veut prendre une action dans le contexte d’ACME. Au lieu d’une nouvelle connexion : impersonate.
Ou via API (avancé — pour automatisation)
curl -X POST https://app.monsys.ai/api/v1/auth/impersonate \ -H "Authorization: Bearer $MSP_ADMIN_TOKEN" \ -H "X-TOTP-Code: 123456" \ -d '{ "tenant_id": "<acme_uuid>", "duration_minutes": 60, "reason": "Investigating alert #847 on web-03" }'# returns scoped tokenLe token résultant a tenant=ACME et toutes les requêtes tournent dans
le contexte RLS d’ACME. Le journal d’audit montre
impersonation_started + toutes les actions avec double actor : votre
user_id (votre email) ET tenant=ACME. Le client voit dans son propre
audit pack que le MSP a impersonated — pas de surprise dans les
audit trails.
Auto-expire après 60 min. Terminer plus tôt via
POST /api/v1/auth/impersonate/end.