Skip to content

Cas d'usage

Prendre en charge les clients hors ligne et isolés (air-gapped)

Licencier des machines sans Internet : fichiers de licence hors ligne autonomes, leases isolées (air-gapped) de longue durée, import d'un bloc de couverture signé par fichier, et quand choisir le hors ligne plutôt que l'activation en ligne.

Objectif. Licencier une machine qui ne peut pas — ou ne veut pas — joindre votre service : un poste de travail déconnecté, un réseau d’entreprise sécurisé, un boîtier embarqué. Tout ce dont le client a besoin arrive sous forme de fichier signé, vérifié localement, et rien n’appelle vers l’extérieur.

Quand l’utiliser

  • Utilisez ceci si certains clients fonctionnent sur des réseaux isolés, ou si vous vendez dans des environnements réglementés où les connexions sortantes sont interdites.
  • Utilisez ceci si un client normalement en ligne doit survivre à une longue coupure et que la fenêtre de lease de 14 jours ne suffit pas.
  • Pas ceci si vos clients sont habituellement connectés — l’activation en ligne classique fonctionne déjà hors ligne entre deux rafraîchissements et vous offre une application des sièges et une révocation en direct. Le licenciement hors ligne sacrifie ces avantages au profit de l’indépendance vis-à-vis du réseau.

Il existe trois briques hors ligne, indépendantes les unes des autres — choisissez ce que la situation exige :

BriqueCe que c’estSièges ?Révocable ?
Fichier de licence hors ligne (license.json)Une licence signée autonome — jamais d’aller-retour d’activationPas de suivi des siègesUniquement via une liste de révocation livrée
Lease hors ligne (air-gapped)Une lease signée ordinaire, émise pour une machine, avec un TTL longOui — confirme un siège à l’émissionÀ la revérification (aucune, si jamais en ligne)
Fichier de bloc de couvertureLe bloc signé de couverture des versions, livré sous forme de fichier—Remplacé par un fichier plus récent

Les trois se vérifient avec la même clé publique embarquée que votre application porte déjà, avec les mêmes contrôles RSA/SHA-256 que les leases en ligne — « hors ligne » ne change donc que la façon dont le document signé est livré, jamais la façon dont il est digne de confiance. Le détail du modèle de lease/anti-rollback se trouve dans Sécurité et modèle de lease ; cette page est consacrée à la tâche.

Fichier de licence hors lignelicense.json signé · sans siègelivré avec le produitLease air-gappedmachine-id → l'éditeur signeTTL long, liée à une machineconfirme un siège à l'émissionFichier de bloc de couvertureexport → importséquence croissante (anti-rollback)Une clé publique embarquéeRSA / SHA-256 · vérification localeproduit · machine · expiration · seqSous licence, hors lignejamais d'appel sortantLié à l'empreinte d'une machine, un fichier copié ailleurs est inutile ; un numéro de séquence inférieur est refusé.Un bloc manquant n'est jamais « sans restriction » — seul un document signé accorde quoi que ce soit.
Trois chemins de livraison hors ligne, une seule vérification locale : chaque fichier signé est contrôlé avec la clé déjà présente dans votre binaire.

Quand choisir le hors ligne plutôt que l’activation en ligne

Privilégiez l’activation en ligne par défaut — elle vous donne une application des sièges et une révocation en direct, et fonctionne tout de même hors ligne pendant toute la fenêtre de lease entre deux rafraîchissements. Choisissez un chemin hors ligne lorsque :

  • La machine peut vous joindre occasionnellement mais pas de façon fiable → l’activation en ligne avec un TTL de lease élargi suffit généralement ; ne passez à une lease isolée (air-gapped) (365 jours par défaut) que si elle risque de rester déconnectée plus longtemps que n’importe quelle fenêtre de lease avec laquelle vous êtes à l’aise.
  • La machine ne vous joindra jamais → une lease isolée (air-gapped) (conserve la liaison par machine et le décompte des sièges) ou un fichier de licence autonome (pas de réseau ni pour vous ni pour le client, pas de suivi des sièges — idéal pour les déploiements embarqués ou véritablement isolés).

Ce que vous faites (côté éditeur)

Lease hors ligne (air-gapped) — liée à une machine

Le client vous envoie son identifiant de machine ; vous émettez une lease de longue durée pour celui-ci :

# le client lit d'abord son identifiant de machine :  keyright machine-id   (ou MachineFingerprint.Current().ToBoundString())
curl -X POST $BASE/admin/licenses/LIC-…/offline-lease -H "X-Admin-Token: $TOKEN" -H "content-type: application/json" \
  -d '{"machineId":"KRM1:…","days":365}'  > offline-lease.json
# -> { "machineId":"KRM1:…", "ttlDays":365, "lease":"{…signed lease JSON…}" }

Émettre une lease hors ligne confirme un siège pour cette machine, de sorte que les activations isolées (air-gapped) comptent toujours dans la limite de sièges — vous ne pouvez pas émettre un nombre illimité de leases hors ligne au-delà du nombre de sièges. La lease est liée à cette unique empreinte, le fichier est donc inutilisable sur toute autre machine (MachineMismatch).

Dans le tableau de bord, c’est l’action License file de la licence — elle signe la lease hors ligne pour l’identifiant de machine que vous fournissez et vous remet le JSON à livrer :

La page de licence Stark Industries dans Keyright avec un bouton « License file » dans la rangée d'actions (à côté de Renew, Add seats et Send portal link) — le contrôle qui signe une lease hors ligne de longue durée pour une machine isolée (air-gapped) — au-dessus du panneau Devices listant les machines liées et les sièges, et une carte Details affichant l'édition pro et ses droits.
L'action License file d'une licence émet la lease hors ligne signée pour un identifiant de machine donné — l'équivalent dans le tableau de bord de l'appel offline-lease ci-dessus — que vous livrez par fichier à la machine isolée.

Fichier de licence hors ligne autonome — aucun service du tout

Pour les déploiements véritablement isolés ou embarqués, signez un fichier de licence avec la CLI et livrez-le avec le produit — jamais d’activation :

keyright license --sign --licensee "Acme Corp" --product acme-app --tier pro \
  --private-key acme-app.private.json --expiry 2027-12-31 --out license.json

Il n’y a pas de suivi des sièges dans ce mode ; c’est le choix adapté aux boîtiers isolés et embarqués. Pour couper un tel build, vous livrez une liste de révocation signée (GET /admin/revocation-list) avec votre prochaine mise à jour.

Fichier de bloc de couverture — pour les machines isolées (air-gapped) qui appliquent la couverture des versions

Une machine déconnectée ne peut pas recevoir son bloc de couverture à l’activation, livrez-le donc sous forme de fichier :

# une licence
curl $BASE/admin/licenses/LIC-…/coverage/block -H "X-Admin-Token: $TOKEN"
# -> { "block":"…signed envelope…" }

# toutes les licences d'un produit, pour une livraison isolée (air-gapped) en masse
curl $BASE/admin/products/acme-app/coverage/export -H "X-Admin-Token: $TOKEN"
# -> { "product":"acme-app", "count":5, "items":[ { "licenseId":"…", "coverageBlock":"…" }, … ] }

Un renouvellement pour un client isolé (air-gapped) n’est qu’un fichier de couverture plus récent : il porte un numéro de séquence plus élevé et remplace donc celui en cache, tandis qu’un fichier plus ancien est refusé (anti-rollback). L’export respecte votre droit exactement comme l’activation, il ne peut donc jamais verrouiller qui que ce soit.

Dans votre code

Tout ce qui suit se vérifie localement, sans réseau, avec la clé publique que le client embarque déjà. Les trois chemins sont présentés dans chaque SDK (l’import d’un bloc de couverture est un appel .NET à ce jour — les autres SDK utilisent le chemin de la lease, ou vérifient l’enveloppe en HTTP brut) :

.NET

using Keyright.Client;
using Keyright.Coverage;

// (A) Fichier de licence autonome / variable d'environnement : validez simplement ce qui est présent — Validate() le découvre par
//     chemin explicite, la variable d'environnement KEYRIGHT_LICENSE, ConfigLicensePath, ou l'emplacement app-data de l'OS.
LicenseInfo info = License.Validate();          // hors ligne, ne lève jamais d'exception ; échoue en mode fermé vers Free en cas de problème
if (info.IsPaid) { /* sous licence */ }

// (B) Lease air-gapped : importez le fichier que l'éditeur a signé pour CETTE machine. Celui-ci LÈVE une exception si invalide.
try
{
    LicenseInfo leased = License.ImportOfflineLease(File.ReadAllText("offline-lease.json"));
    // leased.Status == Valid, entièrement hors ligne ; mauvaise machine/produit/signature/expiration -> lève une exception
}
catch (InvalidOperationException ex) { ShowOfflineLeaseError(ex.Message); }

// (C) Fichier de bloc de couverture : importez l'enveloppe signée ; vérifie, lie à la licence, applique l'anti-rollback.
CoverageVerifyResult r = License.ImportCoverageBlock(File.ReadAllText("acme.coverage.json"));
if (!r.Ok)
{
    // r.Status: Malformed | UnknownKey | BadSignature | RolledBack | WrongBinding | UnsupportedVersion
    ShowImportError(r.Status);
}

Node.js

const fs = require('fs');

// (A) Validation hors ligne — synchrone, sans réseau, ne lève jamais d'exception ; les échecs se résolvent en 'free'.
const { info } = license.validate();
if (info.isPaid) { /* sous licence */ }

// (B) Lease air-gapped : importez le fichier que l'éditeur a signé pour CETTE machine. Lève une exception si invalide.
try {
  const leased = license.importOfflineLease(fs.readFileSync('offline-lease.json', 'utf8'));
  // leased.isValid === true, entièrement hors ligne
} catch (err) { showOfflineLeaseError(err.message); }

// (C) Fichier de bloc de couverture : pas d'appel SDK en Node — importez-le avec le SDK .NET, ou vérifiez
//     vous-même l'enveloppe signée en HTTP brut (ci-dessous).

Python

# (A) Validation hors ligne — sans réseau, ne lève jamais d'exception ; les échecs se résolvent en Edition.FREE.
info = license.validate_info()
if info.is_paid:
    ...  # sous licence

# (B) Lease air-gapped : importez le fichier que l'éditeur a signé pour CETTE machine. Lève une exception si invalide.
try:
    with open("offline-lease.json", encoding="utf-8") as f:
        leased = license.import_offline_lease(f.read())   # leased.is_valid, entièrement hors ligne
except ValueError as ex:
    show_offline_lease_error(str(ex))

# (C) Fichier de bloc de couverture : pas d'appel SDK en Python — utilisez le SDK .NET, ou vérifiez l'enveloppe en HTTP brut.

Java

import java.nio.file.Files;
import java.nio.file.Path;

// (A) Validation hors ligne — sans réseau, ne lève jamais d'exception ; les échecs se résolvent en Edition.FREE.
LicenseInfo info = client.validateInfo();
if (info.isPaid()) { /* sous licence */ }

// (B) Lease air-gapped : importez le fichier que l'éditeur a signé pour CETTE machine. Lève une exception si invalide.
try {
    LicenseInfo leased = client.importOfflineLease(Files.readString(Path.of("offline-lease.json")));
} catch (IllegalArgumentException | IllegalStateException ex) {
    showOfflineLeaseError(ex.getMessage());
}

// (C) Fichier de bloc de couverture : pas d'appel SDK en Java — utilisez le SDK .NET, ou vérifiez l'enveloppe en HTTP brut.

Tout langage (HTTP brut)

# (A/B) Une lease hors ligne est une lease signée ordinaire avec un leaseExpiresUtc lointain — vérifiez-la avec
#       EXACTEMENT le même code qu'une lease en ligne : reconstruisez la charge utile canonique, vérifiez la signature RSA,
#       puis contrôlez produit / machine / expiration, en échouant en mode fermé sur tout. Voir « Utiliser l'API HTTP directement ».
#       (Les leases hors ligne ne sont jamais marquées trial, aucun suffixe |trial ne s'applique donc.)
# (C)   Un fichier de bloc de couverture porte sa propre enveloppe signée et un numéro de séquence monotone :
#       vérifiez la signature de la même façon, liez-le à la licence, et rejetez un numéro de séquence
#       inférieur à celui que la machine détient déjà (anti-rollback).

Quelques garanties du SDK à ce sujet, pour que votre expérience utilisateur soit correcte :

  • Validate() et les vérifications de couverture ne lèvent jamais d’exception pour des conditions hors ligne/réseau ; ImportOfflineLease lève une exception pour une lease invalide, encadrez-le donc.
  • Un bloc manquant n’est jamais « sans restriction ». Retirer un bloc de couverture d’une machine donne UnavailableOffline (sur un build appliquant les règles) ou aucune couverture — jamais un passage silencieux. CheckCoverageAsync renvoie UnavailableOffline lorsque rien n’est en cache et que le service est injoignable — le signal pour afficher « importez un fichier de licence à jour ».
  • L’anti-rollback est un numéro de séquence. Un import RolledBack signifie que le fichier est plus ancien que ce que la machine détient déjà et a été ignoré ; WrongBinding signifie qu’il a été émis pour une autre licence/un autre produit ; UnknownKey signifie une clé que votre build n’embarque pas (pendant une rotation de clés, conservez l’ancienne clé publique dans AdditionalPublicKeysBase64).

Les consommateurs en HTTP brut vérifient une lease hors ligne avec exactement le même code qu’une lease en ligne — c’est une lease signée ordinaire avec un leaseExpiresUtc lointain — en suivant la recette de la charge utile canonique dans Utiliser l’API HTTP directement.

Anti-rollback et modèle de lease, en bref

Chaque document hors ligne est lié à l’empreinte d’une machine (à l’intérieur de la charge utile signée, de sorte qu’une copie sur une autre machine échoue avec MachineMismatch), et les blocs de couverture portent un numéro de séquence monotone afin qu’un fichier plus ancien ne puisse pas être rejoué pour restaurer une couverture échue. L’expiration fait partie des octets signés — modifier une date casse la signature — et reculer l’horloge est détecté par la détection de manipulation de l’horloge sur les essais et les licences à durée déterminée. C’est le résumé ; le traitement complet, y compris pourquoi une licence mono-siège ne peut pas s’exécuter sur deux machines même hors ligne, se trouve dans Sécurité et modèle de lease.

Cloud ou auto-hébergé

  • Les flux hors ligne sont identiques — mêmes endpoints, mêmes fichiers, même vérification locale. La seule différence est $BASE/$TOKEN.
  • Les fichiers de licence autonomes via la CLI nécessitent la clé privée du produit pour signer ; c’est naturel en auto-hébergé (vous la détenez), et sur Cloud vous utiliserez typiquement le chemin de la lease isolée (air-gapped) à la place (émise côté serveur), puisque la clé privée ne quitte jamais le service hébergé.
  • Une remarque sur la désactivation hors ligne : une machine isolée (air-gapped) ne peut pas rendre son siège d’elle-même. Libérez-le côté éditeur une fois que votre backend a une connectivité, ou émettez des leases hors ligne avec un TTL assez court pour que le siège d’une machine mise hors service revienne de lui-même.

Vérifier

  1. Fichier de licence : signez un license.json, faites pointer KEYRIGHT_LICENSE dessus sur une machine déconnectée, lancez l’application — Validate() renvoie sous licence sans réseau.
  2. Lease isolée (air-gapped) : lisez l’identifiant d’une machine de test, émettez une lease hors ligne de 365 jours pour celle-ci, ImportOfflineLease sur cette machine — Valid, hors ligne. Copiez le fichier sur une seconde machine — MachineMismatch.
  3. Fichier de couverture : exportez le bloc de couverture d’une licence, importez-le avec ImportCoverageBlock sur un build appliquant les règles, confirmez que la version couverte s’exécute et qu’une version hors plage affiche le message d’import/renouvellement.
  4. Anti-rollback : importez un fichier de couverture plus ancien après un plus récent — attendez-vous à RolledBack et à aucun changement.
  5. Comptabilité des sièges : confirmez que l’émission de la lease hors ligne a consommé un siège (le compteur used de la licence a augmenté), ce qui prouve que le hors ligne n’est pas une faille dans le décompte des sièges.

Voir aussi