Aller au contenu

MSP — triage cross-tenant et livraison

1. Triage cross-tenant du lundi matin

Dans le tableau de bord

  1. Login en MSP-admin → Sidebar → MSP cockpit
  2. Tous tenants triés par urgency descending
  3. Cliquer tenant urgency le plus haut → ouvre en contexte tenant
  4. 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 :

  1. Top 3 tenants par urgency → cliquer → fix
  2. Par tenant vous voyez un mini Trust Score (actuel + delta 24h) + top open alert + top open kernel CVE
  3. 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

  1. Sidebar → Audit Packs → choisir le mois du client
  2. Cliquer ‘Download for handover’ → filtre actor LIKE ’%@yourMSP.com’
  3. Le PDF + .sig résultants sont signés par le hub
  4. 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 client
  • 2026-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)

Fenêtre de terminal
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.pdf

Envoyez ce PDF + .sig directement au client. Il vérifie lui-même que cela vient du runtime monsys, pas de votre éditeur :

Fenêtre de terminal
./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.pub

3. Branding white-label par tenant

Dans le tableau de bord

  1. Sidebar → Settings → Tenant branding (par client)
  2. Uploader logo + couleur + product name + custom_domain
  3. Client ouvre custom_domain → voit propre brand, votre MSP comme ‘powered by’
  4. 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)

Fenêtre de terminal
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

  1. Sidebar → Groups → ‘Nouveau groupe’ (en contexte tenant)
  2. Rule : all_of [tag=production, tag=eu-west-1]
  3. Ajouter runbook markdown dans le champ ‘Runbook’
  4. 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)

Fenêtre de terminal
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

  1. Sidebar → Playbooks → choisir ‘Isolate network’
  2. Bouton ‘Pre-issue pour agent’ → choisir host + fenêtre valide
  3. Condition (heartbeat lost / critical alert) + TOTP
  4. 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)

Fenêtre de terminal
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

  1. Sidebar → Emergency → ‘Nouvel EAT niveau 3’
  2. Saisir action + raison + required_approvers = 2
  3. Autres admins reçoivent push sur PWA mobile avec ‘Approve’/‘Reject’
  4. À 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)

Fenêtre de terminal
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-01
RunPlaybook: db-restore
Requested by alice@yourMSP.com at 14:23
Reason: 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

  1. Sidebar → Billing → onglet ‘Cross-tenant overview’
  2. Table : agents actifs par tenant + facturables (après 5 gratuits)
  3. Total monthly_eur par client
  4. 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

  1. Tenant switcher top-right → choisir client + ‘Impersonate’
  2. Saisir raison + durée 60min + TOTP
  3. Travailler en contexte client — actions ont double acteur dans audit_log
  4. 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)

Fenêtre de terminal
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 token

Le 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.