Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51eecbb57a | ||
|
|
edd2633455 | ||
|
|
0e26e6eb11 | ||
|
|
916b53446a |
No files matched your search
@@ -640,7 +640,9 @@ bouton **Aide** des joueurs et la page **Demandes des joueurs**, puis le **mode
|
||||
depuis la page **Signature** (propriétaires), qui montre aussi l'état de chaque contenu. Côté joueur,
|
||||
`"keyringUrl"` et `"contentSigning"` (`"warn"` puis `"require"`) dans `launcher.json` (intégré à l'exe) ;
|
||||
en « require », un manifest non signé, mal signé ou plus ancien que le dernier vu est refusé (le jeu installé
|
||||
reste jouable), et un catalogue refusé est ignoré. Après le retrait d'une clé compromise, relevez
|
||||
reste jouable), et un catalogue refusé est ignoré. Le trousseau est retéléchargé sans redémarrer le launcher quand
|
||||
un contenu est signé par une clé absente ou non autorisée pour ce contenu (au plus une fois par minute), et au plus
|
||||
toutes les 30 minutes (une clé retirée n'est plus acceptée par un launcher laissé ouvert). Après le retrait d'une clé compromise, relevez
|
||||
`"keyringMinSequence"` au numéro du nouveau trousseau. Mise en place : `server/deploy/LISEZMOI.md`, étape 3 quinquies.
|
||||
* **Page « Journal d'audit »** (permission `audit.read` : propriétaires et administrateurs) : connexions,
|
||||
invitations, approbations, publications, modifications et refus, les plus récents d'abord (par pages
|
||||
|
||||
@@ -911,7 +911,9 @@ plus avant la recherche des mises à jour des jeux** (pas d'attente pour `--unin
|
||||
pendant une maintenance) et aussitôt après une publication depuis le menu de ce PC (`IAdminContext.NotifyMaintenanceChanged`).
|
||||
8 Ko au plus, 8 s au plus ; règles contrôlées même si signé ; signature vérifiée par
|
||||
`ContentTrustService.VerifyMaintenanceAsync` (autorisation `maintenance`, date de signature non antérieure) ; révision
|
||||
inférieure à l'état connu refusée ; même fichier que la fois précédente : rien à revérifier. **Rien n'est gardé sur le
|
||||
inférieure à l'état connu refusée ; même fichier que la fois précédente (déjà accepté) : rien à revérifier ; fichier
|
||||
refusé : revérifié à chaque relecture (un trousseau re-signé avec l'autorisation `maintenance` pendant que le launcher
|
||||
tourne est retéléchargé et pris en compte, 3.3.1). **Rien n'est gardé sur le
|
||||
disque** : injoignable au démarrage = pas de maintenance (le jeu installé reste jouable hors ligne, un joueur sans
|
||||
connexion n'est jamais bloqué) ; pendant que le launcher tourne, une coupure ou un fichier refusé garde le dernier état
|
||||
vérifié, un 404 termine la maintenance.
|
||||
@@ -1016,8 +1018,12 @@ Trousseau téléchargé au démarrage (en parallèle du catalogue), vérifié (s
|
||||
s'il est plus ancien que le dernier vu, différent sous le même numéro, ou plus ancien que `keyringMinSequence`
|
||||
(launcher.json intégré, à relever après le retrait d'une clé compromise) ; hors ligne : dernier trousseau vu
|
||||
(revérifié à la lecture du cache). Aucune erreur de téléchargement (délai, réponse invalide, bogue) ne remonte à
|
||||
l'appelant : le cache est utilisé. Contenu signé par une clé absente du trousseau : nouveau téléchargement, au plus
|
||||
une fois toutes les 5 minutes. Manifest (`ManifestClient`) et catalogue (`CatalogService`, avant tout contrôle des valeurs)
|
||||
l'appelant : le cache est utilisé. Nouveau téléchargement du trousseau (mêmes contrôles) : après un échec réseau
|
||||
(1 min), quand un contenu est signé par une clé absente du trousseau **ou présente sans l'autorisation de ce contenu**
|
||||
(clé ajoutée ou autorisation donnée depuis : au plus une fois par minute, `KeyChangeRetryDelay`), et quand le trousseau
|
||||
en mémoire a plus de 30 minutes (`KeyringMaxAge`, à la vérification suivante : un launcher laissé ouvert cesse de faire
|
||||
confiance à une clé retirée). Avant la 3.3.1, une autorisation ajoutée à une clé déjà connue n'était vue qu'au
|
||||
redémarrage (mode maintenance refusé à chaque relecture après la re-signature du trousseau). Manifest (`ManifestClient`) et catalogue (`CatalogService`, avant tout contrôle des valeurs)
|
||||
vérifiés : clé du trousseau autorisée, signature valide, **date de signature ≥ la dernière acceptée** pour ce
|
||||
contenu (un serveur compromis ne peut pas resservir une ancienne version légitime mais vulnérable). « warn » :
|
||||
journal seulement ; « require » : manifest refusé (`ContentSecurityException`, message « Mise à jour bloquée par
|
||||
|
||||
@@ -286,8 +286,11 @@ Limites :
|
||||
textes) ;
|
||||
* un launcher accepte tout trousseau signé par la clé racine dont le numéro est au moins `keyringMinSequence`
|
||||
et au moins celui du dernier trousseau qu'il a vu ;
|
||||
* un appareil ajouté au trousseau pendant qu'un launcher tourne est connu de ce launcher au plus tard 5 minutes
|
||||
après (nouveau téléchargement du trousseau) ou à son prochain démarrage.
|
||||
* un appareil ajouté au trousseau, ou une autorisation ajoutée à un appareil, pendant qu'un launcher tourne est
|
||||
connu de ce launcher dès le contenu suivant signé par cet appareil (nouveau téléchargement du trousseau, au plus une
|
||||
fois par minute) ;
|
||||
* un launcher laissé ouvert retélécharge le trousseau toutes les 30 minutes au plus (à la vérification suivante) : un
|
||||
appareil retiré, ou une autorisation enlevée, n'y est plus accepté sans attendre son redémarrage.
|
||||
|
||||
La clé racine (`launcher-signing.key`) ne doit jamais aller sur le VPS ni dans Git, ni servir de clé
|
||||
d'administrateur (le menu d'administration la refuse, quel que soit le nom du fichier) : c'est elle qui protège
|
||||
@@ -481,7 +484,9 @@ Prérequis : étapes 3 bis (catalogue) et 3 quinquies (trousseau).
|
||||
concernés y apparaît, signalée « AUTORISATIONS ÉLARGIES » par `tools\sign-keyring.bat` : c'est attendu.
|
||||
5. **Activer** depuis la page « Maintenance » (message facultatif, confirmation), **désactiver** de même. Les launchers
|
||||
relisent le fichier au démarrage (avant toute mise à jour automatique), puis toutes les 2 minutes (toutes les minutes
|
||||
pendant une maintenance) ; celui de l'administrateur qui publie bascule aussitôt.
|
||||
pendant une maintenance) ; celui de l'administrateur qui publie bascule aussitôt. Un launcher resté ouvert pendant
|
||||
la re-signature du trousseau (point 4) prend la nouvelle autorisation en compte à la relecture suivante
|
||||
(launcher 3.3.1 et suivants ; en 3.3.0, il fallait le redémarrer).
|
||||
|
||||
Comportement des launchers :
|
||||
- **Hors ligne** ou fichier injoignable au démarrage : pas de maintenance (le jeu installé reste jouable hors ligne).
|
||||
|
||||
@@ -41,8 +41,23 @@ public sealed class ContentSecurityException : Exception
|
||||
public sealed class ContentTrustService
|
||||
{
|
||||
private static readonly TimeSpan FetchTimeout = TimeSpan.FromSeconds(20);
|
||||
private static readonly TimeSpan RetryDelay = TimeSpan.FromMinutes(1);
|
||||
private static readonly TimeSpan UnknownKeyRetryDelay = TimeSpan.FromMinutes(5);
|
||||
|
||||
/// <summary>Nouvel essai après un échec réseau (trousseau jamais obtenu pendant ce démarrage).</summary>
|
||||
public static readonly TimeSpan RetryDelay = TimeSpan.FromMinutes(1);
|
||||
|
||||
/// <summary>
|
||||
/// Nouveau téléchargement, au plus à ce rythme, quand un contenu est signé par une clé absente du trousseau ou non
|
||||
/// autorisée pour ce contenu : clé ajoutée ou autorisation donnée (ex. « maintenance ») pendant que le launcher tourne.
|
||||
/// Plus court que la relecture du mode maintenance, pour qu'une activation juste après la republication du trousseau
|
||||
/// soit vue à la relecture suivante.
|
||||
/// </summary>
|
||||
public static readonly TimeSpan KeyChangeRetryDelay = TimeSpan.FromMinutes(1);
|
||||
|
||||
/// <summary>
|
||||
/// Âge maximal du trousseau en mémoire : au-delà, il est retéléchargé à la vérification suivante (une clé retirée ou
|
||||
/// une autorisation enlevée est prise en compte sans redémarrer le launcher).
|
||||
/// </summary>
|
||||
public static readonly TimeSpan KeyringMaxAge = TimeSpan.FromMinutes(30);
|
||||
private static readonly TimeSpan MaxFutureSignedAt = TimeSpan.FromDays(1);
|
||||
|
||||
private readonly Func<Uri, long, CancellationToken, Task<byte[]>> _download;
|
||||
@@ -82,12 +97,15 @@ public sealed class ContentTrustService
|
||||
|
||||
/// <summary>
|
||||
/// Trousseau à utiliser : celui du serveur s'il est valide et pas plus ancien que le dernier vu, sinon le dernier
|
||||
/// vu (cache), sinon null. Téléchargé une fois par démarrage (nouvel essai après un échec réseau, ou après quelques
|
||||
/// minutes quand un contenu est signé par une clé encore inconnue : clé ajoutée pendant que le launcher tourne).
|
||||
/// vu (cache), sinon null. Téléchargé au démarrage, puis de nouveau :
|
||||
/// • après un échec réseau (<see cref="RetryDelay"/>) ;
|
||||
/// • quand un contenu est signé par une clé absente ou non autorisée pour ce contenu (<see cref="KeyChangeRetryDelay"/>) ;
|
||||
/// • quand il a plus de <see cref="KeyringMaxAge"/> (launcher ouvert longtemps).
|
||||
/// Chaque nouveau trousseau passe par les mêmes contrôles (signature racine, numéro jamais en baisse).
|
||||
/// </summary>
|
||||
public Task<KeyringDocument?> GetKeyringAsync(CancellationToken ct = default) => GetKeyringAsync(false, ct);
|
||||
|
||||
private async Task<KeyringDocument?> GetKeyringAsync(bool unknownKey, CancellationToken ct)
|
||||
private async Task<KeyringDocument?> GetKeyringAsync(bool keyChanged, CancellationToken ct)
|
||||
{
|
||||
Task<KeyringDocument?> task;
|
||||
lock (_lock)
|
||||
@@ -95,7 +113,8 @@ public sealed class ContentTrustService
|
||||
var elapsed = _time.GetUtcNow() - _lastAttempt;
|
||||
if (_keyringTask is null
|
||||
|| (_keyringTask.IsCompleted && !_keyringFetched && elapsed > RetryDelay)
|
||||
|| (_keyringTask.IsCompleted && unknownKey && elapsed > UnknownKeyRetryDelay))
|
||||
|| (_keyringTask.IsCompleted && keyChanged && elapsed > KeyChangeRetryDelay)
|
||||
|| (_keyringTask.IsCompleted && elapsed > KeyringMaxAge))
|
||||
{
|
||||
_lastAttempt = _time.GetUtcNow();
|
||||
_keyringTask = FetchKeyringAsync();
|
||||
@@ -151,7 +170,7 @@ public sealed class ContentTrustService
|
||||
}
|
||||
catch (Exception ex) when (ex is InvalidDataException or JsonException or NotSupportedException or FormatException)
|
||||
{
|
||||
lock (_lock) _keyringFetched = true; // contenu refusé : pas de nouvel essai pendant ce démarrage
|
||||
lock (_lock) _keyringFetched = true; // contenu refusé : nouvel essai seulement aux conditions de GetKeyringAsync
|
||||
Log.Error($"Trousseau du serveur refusé : {ex.Message}" + (cached is null ? "" : $" (le n° {cached.Sequence} déjà vu reste utilisé)."));
|
||||
return cached;
|
||||
}
|
||||
@@ -274,8 +293,9 @@ public sealed class ContentTrustService
|
||||
private async Task VerifyAsync(string kind, string id, string digest, ContentSignature? signature, string what, CancellationToken ct)
|
||||
{
|
||||
var keyring = await GetKeyringAsync(false, ct).ConfigureAwait(false);
|
||||
// Clé inconnue (ajoutée depuis le démarrage) : nouveau téléchargement du trousseau, limité dans le temps.
|
||||
if (keyring is not null && signature?.KeyId is { } keyId && !keyring.Keys.Any(k => string.Equals(k.KeyId, keyId, StringComparison.Ordinal)))
|
||||
// Clé absente du trousseau, ou présente sans l'autorisation de ce contenu (clé ajoutée, ou autorisation donnée,
|
||||
// depuis le dernier téléchargement) : nouveau téléchargement du trousseau, limité dans le temps.
|
||||
if (keyring is not null && KeyMayBeNewer(keyring, kind, signature))
|
||||
keyring = await GetKeyringAsync(true, ct).ConfigureAwait(false);
|
||||
|
||||
string problem;
|
||||
@@ -322,6 +342,14 @@ public sealed class ContentTrustService
|
||||
if (!Report(what, problem)) throw new ContentSecurityException($"{what} : {problem}.");
|
||||
}
|
||||
|
||||
/// <summary>La signature vient d'une clé que ce trousseau ne connaît pas, ou n'autorise pas pour ce contenu.</summary>
|
||||
private static bool KeyMayBeNewer(KeyringDocument keyring, string kind, ContentSignature? signature)
|
||||
{
|
||||
if (signature?.KeyId is not { } keyId) return false;
|
||||
var key = keyring.Keys.FirstOrDefault(k => string.Equals(k.KeyId, keyId, StringComparison.Ordinal));
|
||||
return key?.Scopes is not { } scopes || !scopes.Contains(ContentKinds.ScopeOf(kind), StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ État
|
||||
|
||||
private sealed class TrustState
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
(1.0.0 → 1.0.1 → 1.1.0…). Les launchers des joueurs ne se mettent à jour que vers
|
||||
une version STRICTEMENT supérieure à la leur.
|
||||
-->
|
||||
<Version>3.3.0</Version>
|
||||
<Version>3.3.1</Version>
|
||||
<Authors>Lanisys</Authors>
|
||||
<Company>LanisysGames</Company>
|
||||
<Copyright>© LanisysGames</Copyright>
|
||||
|
||||
@@ -271,6 +271,74 @@ public class ContentTrustTests
|
||||
Assert.Equal(2, server.Requests);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task A_scope_granted_while_the_launcher_runs_is_found_by_downloading_the_keyring_again()
|
||||
{
|
||||
using var folder = new TempFolder();
|
||||
using var root = SigningKit.NewKey();
|
||||
using var nicolas = SigningKit.NewKey();
|
||||
var server = new FakeServer();
|
||||
server.Serve(SigningKit.Keyring(root, 1, SigningKit.Entry(nicolas, "Nicolas", SigningScopes.Releases)));
|
||||
var clock = new ManualClock();
|
||||
var trust = Trust(server, folder.Path, root, ContentSigningMode.Require, time: clock);
|
||||
var catalog = new CatalogDocument { Revision = 1, Games = new Dictionary<string, CatalogGame> { ["EZR"] = new() { Tagline = "Texte" } } };
|
||||
catalog = catalog with
|
||||
{
|
||||
Signature = SigningKit.Sign(nicolas, ContentKinds.Catalog, ContentKinds.CatalogId, ContentSigning.CatalogDigest(catalog))
|
||||
};
|
||||
Assert.False(await trust.VerifyCatalogAsync(catalog)); // clé connue, sans l'autorisation « catalog »
|
||||
Assert.Equal(1, server.Requests);
|
||||
|
||||
// Trousseau republié avec l'autorisation (ex. « maintenance » après la mise à jour 3.3) : vu sans redémarrer,
|
||||
// au plus une fois par KeyChangeRetryDelay.
|
||||
server.Serve(SigningKit.Keyring(root, 2, SigningKit.Entry(nicolas, "Nicolas", SigningScopes.Releases, SigningScopes.Catalog)));
|
||||
Assert.False(await trust.VerifyCatalogAsync(catalog));
|
||||
Assert.Equal(1, server.Requests);
|
||||
clock.Now += ContentTrustService.KeyChangeRetryDelay + TimeSpan.FromSeconds(1);
|
||||
Assert.True(await trust.VerifyCatalogAsync(catalog));
|
||||
Assert.Equal(2, server.Requests);
|
||||
Assert.Equal(2, trust.CachedKeyring!.Sequence);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task A_launcher_left_open_downloads_the_keyring_again_and_stops_trusting_a_removed_key()
|
||||
{
|
||||
using var folder = new TempFolder();
|
||||
using var root = SigningKit.NewKey();
|
||||
using var nicolas = SigningKit.NewKey();
|
||||
using var compromised = SigningKit.NewKey();
|
||||
var server = new FakeServer();
|
||||
server.Serve(SigningKit.Keyring(root, 1, SigningKit.Entry(nicolas, "Nicolas"), SigningKit.Entry(compromised, "PC volé")));
|
||||
var clock = new ManualClock();
|
||||
var trust = Trust(server, folder.Path, root, ContentSigningMode.Require, time: clock);
|
||||
await trust.VerifyManifestAsync(Manifest("1.2", compromised), "EZR");
|
||||
|
||||
// Clé retirée du trousseau (n° 2) : encore acceptée tant que le trousseau en mémoire est récent…
|
||||
server.Serve(SigningKit.Keyring(root, 2, SigningKit.Entry(nicolas, "Nicolas")));
|
||||
clock.Now += TimeSpan.FromMinutes(10);
|
||||
await trust.VerifyManifestAsync(Manifest("1.3", compromised), "EZR");
|
||||
Assert.Equal(1, server.Requests);
|
||||
|
||||
// … puis refusée dès qu'il a dépassé son âge maximal (sans redémarrer le launcher).
|
||||
clock.Now += ContentTrustService.KeyringMaxAge;
|
||||
await Assert.ThrowsAsync<ContentSecurityException>(() => trust.VerifyManifestAsync(Manifest("1.4", compromised), "EZR"));
|
||||
Assert.Equal(2, server.Requests);
|
||||
await trust.VerifyManifestAsync(Manifest("1.4", nicolas), "EZR");
|
||||
|
||||
// Serveur injoignable au téléchargement suivant : dernier trousseau vu (n° 2) conservé, pas d'erreur.
|
||||
server.Keyring = null;
|
||||
clock.Now += ContentTrustService.KeyringMaxAge + TimeSpan.FromMinutes(1);
|
||||
await trust.VerifyManifestAsync(Manifest("1.5", nicolas), "EZR");
|
||||
Assert.Equal(3, server.Requests);
|
||||
await Assert.ThrowsAsync<ContentSecurityException>(() => trust.VerifyManifestAsync(Manifest("1.6", compromised), "EZR"));
|
||||
|
||||
// Serveur compromis qui ressert l'ancien trousseau n° 1 : refusé (retour en arrière), la clé reste exclue.
|
||||
server.Serve(SigningKit.Keyring(root, 1, SigningKit.Entry(nicolas, "Nicolas"), SigningKit.Entry(compromised, "PC volé")));
|
||||
clock.Now += ContentTrustService.KeyringMaxAge + TimeSpan.FromMinutes(1);
|
||||
await Assert.ThrowsAsync<ContentSecurityException>(() => trust.VerifyManifestAsync(Manifest("1.7", compromised), "EZR"));
|
||||
Assert.Equal(2, trust.CachedKeyring!.Sequence);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task A_signature_dated_far_in_the_future_does_not_block_later_releases()
|
||||
{
|
||||
|
||||
@@ -166,6 +166,50 @@ public class MaintenanceServiceTests
|
||||
Assert.Empty(changes);
|
||||
}
|
||||
|
||||
/// <summary>Horloge avancée à la main (trousseau).</summary>
|
||||
private sealed class ManualClock : TimeProvider
|
||||
{
|
||||
public DateTimeOffset Now { get; set; } = DateTimeOffset.UtcNow;
|
||||
public override DateTimeOffset GetUtcNow() => Now;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task A_maintenance_published_after_the_keyring_gained_the_scope_is_applied_without_restarting()
|
||||
{
|
||||
// Cas signalé en 3.3.0 : launcher ouvert, trousseau republié avec l'autorisation « maintenance » (étape 3 decies),
|
||||
// puis maintenance activée. Le launcher gardait l'ancien trousseau (clé connue, sans l'autorisation) jusqu'au
|
||||
// redémarrage : chaque relecture refusait la maintenance.
|
||||
using var folder = new TempFolder();
|
||||
using var root = SigningKit.NewKey();
|
||||
using var admin = SigningKit.NewKey();
|
||||
var server = new FakeServer
|
||||
{
|
||||
Keyring = Encoding.UTF8.GetBytes(Keyring.Serialize(SigningKit.Keyring(root, 1,
|
||||
SigningKit.Entry(admin, "Nicolas", SigningScopes.Releases, SigningScopes.Catalog))))
|
||||
};
|
||||
var clock = new ManualClock();
|
||||
var trust = new ContentTrustService(server.Download, folder.Path, KeyringUrl, new[] { SigningKit.Root(root) },
|
||||
ContentSigningMode.Require, time: clock);
|
||||
var service = new MaintenanceService(server.Download, MaintenanceUrl, trust, clock);
|
||||
var changes = new List<MaintenanceDocument?>();
|
||||
service.Changed += changes.Add;
|
||||
|
||||
// Démarrage : rien de publié, trousseau n° 1 en mémoire.
|
||||
Assert.NotNull(await trust.GetKeyringAsync());
|
||||
Assert.False(await service.RefreshAsync());
|
||||
|
||||
// Trousseau n° 2 (autorisation « maintenance » ajoutée), puis maintenance activée quelques minutes plus tard.
|
||||
server.Keyring = Encoding.UTF8.GetBytes(Keyring.Serialize(SigningKit.Keyring(root, 2,
|
||||
SigningKit.Entry(admin, "Nicolas", SigningScopes.Releases, SigningScopes.Catalog, SigningScopes.Maintenance))));
|
||||
clock.Now += TimeSpan.FromMinutes(3);
|
||||
server.Serve(Signed(admin, 1, true, "Retour vers 18 h."));
|
||||
|
||||
// Relecture périodique suivante : trousseau retéléchargé, maintenance appliquée.
|
||||
Assert.True(await service.RefreshAsync());
|
||||
Assert.True(service.IsActive);
|
||||
Assert.Equal("Retour vers 18 h.", Assert.Single(changes)!.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Offline_at_startup_or_without_address_there_is_no_maintenance_and_warn_mode_applies_it()
|
||||
{
|
||||
|
||||
+49
-63
@@ -1,98 +1,84 @@
|
||||
# Launcher 3.3.0
|
||||
# Launcher 3.3.1
|
||||
LAUNCHER - NOTES DE MISE À JOUR
|
||||
Mode maintenance - Mise à jour du 10 novembre 2026
|
||||
Correctif du mode maintenance - Mise à jour du 10 novembre 2026
|
||||
==================================================================
|
||||
|
||||
Nouvelle page « Maintenance » dans le menu d'administration : un bouton met tous les launchers en mode maintenance. Le launcher passe au rouge / orange, affiche « Maintenance en cours, merci de revenir plus tard » au centre et bloque tout, sauf la désinstallation des jeux et le menu d'administration.
|
||||
Un launcher resté ouvert pendant la re-signature du trousseau ne voyait pas la maintenance activée ensuite : chaque relecture (toutes les 2 minutes) la refusait jusqu'au redémarrage. Il prend désormais le nouveau trousseau en compte tout seul, et le relit aussi régulièrement quand il reste ouvert longtemps.
|
||||
|
||||
Le serveur est à redéployer et un bloc nginx est à ajouter ; le trousseau est à re-signer après la diffusion de cette version.
|
||||
Aucune action sur le serveur. Le launcher se met à jour automatiquement.
|
||||
|
||||
|
||||
------------------------------------------------------------------
|
||||
EN BREF
|
||||
------------------------------------------------------------------
|
||||
- NOUVEAU : page « Maintenance » (activer, modifier le message,
|
||||
terminer), permission maintenance.manage.
|
||||
- NOUVEAU : launcher en rouge / orange, carte « Maintenance en
|
||||
cours » au centre, bouton « MAINTENANCE ».
|
||||
- NOUVEAU : tout bloqué chez les joueurs, sauf la désinstallation
|
||||
des jeux et le menu d'administration.
|
||||
- AMÉLIORÉ : bandeau au tableau de bord, ligne dans la page
|
||||
« Signature », catégorie dans le journal d'audit.
|
||||
- SÉCURITÉ : mode maintenance signé (autorisation « mode
|
||||
maintenance » du trousseau) et vérifié par les launchers.
|
||||
- SERVEUR : à redéployer ; bloc nginx maintenance.json à ajouter.
|
||||
- QUALITÉ : 11 nouveaux tests automatisés (367 au total).
|
||||
- CORRIGÉ : mode maintenance activé après la re-signature du
|
||||
trousseau non vu par un launcher déjà ouvert (redémarrage
|
||||
nécessaire en 3.3.0).
|
||||
- AMÉLIORÉ : trousseau retéléchargé quand un contenu est signé par
|
||||
une clé connue mais pas encore autorisée pour ce contenu.
|
||||
- SÉCURITÉ : trousseau relu au plus toutes les 30 minutes : un
|
||||
appareil retiré n'est plus accepté par un launcher laissé ouvert.
|
||||
- QUALITÉ : 3 nouveaux tests automatisés (370 au total).
|
||||
|
||||
|
||||
------------------------------------------------------------------
|
||||
PAGE « MAINTENANCE »
|
||||
CORRECTIF
|
||||
------------------------------------------------------------------
|
||||
- État publié : en cours depuis quand, par qui ; ou terminée.
|
||||
- Message facultatif pour les joueurs : 300 caractères, 6 lignes,
|
||||
texte brut, aucun lien ; compteur, contrôle en direct, aperçu.
|
||||
- « Activer la maintenance » : confirmation détaillée.
|
||||
- « Modifier le message » : même heure de début.
|
||||
- « Terminer la maintenance » : confirmation.
|
||||
- Le launcher de l'administrateur bascule aussitôt.
|
||||
- Tableau de bord : bandeau « Maintenance en cours » pour tous les
|
||||
administrateurs.
|
||||
- Cause : le launcher gardait en mémoire le trousseau lu au
|
||||
démarrage. Il ne le retéléchargeait que pour une clé inconnue,
|
||||
pas pour une clé connue qui vient de recevoir une autorisation
|
||||
(« mode maintenance » ajoutée à l'étape 3 decies).
|
||||
- Effet en 3.3.0 : « la clé … n'est pas autorisée à signer ce
|
||||
contenu » dans le journal à chaque relecture, maintenance
|
||||
ignorée jusqu'au redémarrage.
|
||||
- Désormais : nouveau téléchargement du trousseau, au plus une
|
||||
fois par minute ; la maintenance s'affiche à la relecture
|
||||
suivante (2 minutes au plus).
|
||||
- Concerne aussi les formulaires « Aide », le catalogue et les
|
||||
versions des jeux signés par un appareil qui vient d'être
|
||||
autorisé.
|
||||
|
||||
|
||||
------------------------------------------------------------------
|
||||
CHEZ LES JOUEURS
|
||||
VÉRIFICATIONS PÉRIODIQUES DU LAUNCHER
|
||||
------------------------------------------------------------------
|
||||
- Couleurs rouge / orange, carte au centre avec le message et
|
||||
l'heure de début, fondu au début et à la fin.
|
||||
- Bloqués : installation, mises à jour (automatiques comprises),
|
||||
vérification, réparation, localisation, lancement (raccourcis
|
||||
compris), paramètres, bouton « Aide », site officiel.
|
||||
- Toujours possibles : désinstallation, menu d'administration.
|
||||
- Non interrompus : téléchargement commencé, partie en cours, mise
|
||||
à jour du launcher lui-même.
|
||||
- Lu au démarrage avant la recherche des mises à jour, puis toutes
|
||||
les 2 minutes (1 minute pendant une maintenance).
|
||||
- Hors ligne au démarrage : pas de maintenance (le jeu installé
|
||||
reste jouable).
|
||||
- Mode maintenance : au démarrage (4 s au plus), puis toutes les
|
||||
2 minutes (1 minute pendant une maintenance), et aussitôt après
|
||||
une publication depuis le menu de ce PC.
|
||||
- Versions des jeux (manifest) : au démarrage, puis toutes les
|
||||
30 minutes (updateCheckIntervalMinutes).
|
||||
- Trousseau : au démarrage ; après un échec réseau (1 minute) ;
|
||||
clé inconnue ou non autorisée (1 minute au plus) ; au plus
|
||||
toutes les 30 minutes (nouveau).
|
||||
- Catalogue : au démarrage seulement.
|
||||
- Mise à jour du launcher : au démarrage, et bouton « Rechercher »
|
||||
des paramètres.
|
||||
- Formulaires « Aide » : à l'ouverture de la fenêtre « Aide ».
|
||||
|
||||
|
||||
------------------------------------------------------------------
|
||||
SÉCURITÉ
|
||||
------------------------------------------------------------------
|
||||
- Publication : permission maintenance.manage, requête signée et
|
||||
document signé par la clé de la session, audit.
|
||||
- Launchers : signature vérifiée selon contentSigning ; document
|
||||
non signé, mal signé ou plus ancien que le dernier vu ignoré en
|
||||
« require ».
|
||||
- Aucun lien accepté dans le message ; titre fixe dans le code.
|
||||
- Affichage pour les joueurs, pas une barrière de sécurité : les
|
||||
serveurs du jeu restent seuls juges des connexions.
|
||||
- Chaque trousseau retéléchargé passe par les mêmes contrôles :
|
||||
signature de la clé racine, numéro jamais en baisse, minimum
|
||||
keyringMinSequence ; un ancien trousseau resservi est refusé.
|
||||
- Serveur injoignable : dernier trousseau vérifié conservé.
|
||||
|
||||
|
||||
------------------------------------------------------------------
|
||||
SERVEUR
|
||||
------------------------------------------------------------------
|
||||
- À redéployer : server/deploy/LISEZMOI.md, étapes 8 et 3 decies.
|
||||
- nginx : bloc location = /catalog/maintenance.json
|
||||
(server/deploy/nginx-catalog.conf).
|
||||
- Aucun réglage à ajouter : fichier écrit à côté du trousseau.
|
||||
- Ordre : serveur et nginx, launcher 3.3.0, puis trousseau re-signé
|
||||
avec l'autorisation « mode maintenance ».
|
||||
- Aucun changement.
|
||||
|
||||
|
||||
------------------------------------------------------------------
|
||||
DÉVELOPPEMENT
|
||||
------------------------------------------------------------------
|
||||
- Contrats : MaintenanceDocument, MaintenanceRules,
|
||||
ContentKinds.Maintenance, SigningScopes.Maintenance,
|
||||
AdminPermissions.MaintenanceManage, ApiRoutes.Maintenance.
|
||||
- Serveur : module Maintenance, MaintenanceStore.
|
||||
- Launcher : MaintenanceService (Core), Themes/Maintenance.xaml,
|
||||
GameViewModel.IsMaintenance, LauncherViewModel.ApplyMaintenance.
|
||||
- Menu : MaintenanceAdminModule, MaintenancePanelViewModel.
|
||||
- Outil ui-snapshot : scènes main-maintenance, main-maintenance-
|
||||
plain, admin-page-maintenance, admin-page-maintenance-edit.
|
||||
- Documentation : docs/ADMIN-ARCHITECTURE.md (§ 11 septies).
|
||||
- Core : ContentTrustService (KeyChangeRetryDelay, KeyringMaxAge,
|
||||
KeyMayBeNewer).
|
||||
- Tests : ContentTrustTests (2), MaintenanceServiceTests (1).
|
||||
- Documentation : README, docs/ADMIN-ARCHITECTURE.md,
|
||||
server/deploy/LISEZMOI.md (étapes 3 quinquies et 3 decies).
|
||||
|
||||
|
||||
La sécurité des mises à jour du launcher ne change pas : la signature numérique et l'empreinte SHA-256 de chaque nouvelle version sont vérifiées avant toute installation.
|
||||
Reference in new issue
Block a user