tk_ota_push
tk_ota_push est une solution 2-en-1 souveraine et on-premise pour Flutter, conçue pour les institutions financières, banques et entreprises (FinTech, Mobile Money, Télécoms). Elle allie :
- Un SDK Mobile (
lib/) : Pilotage intelligent des mises à jour à chaud (Hot Code Push) avec Remote Config Gatekeeper en 3 étapes (0 appel réseau inutile), bouclier de sécurité mobile (Anti-Repackaging APK Xender, détection Root/Jailbreak, Pinning TLS) et gestionnaire d'assets dynamiques. - Une CLI de Déploiement (
bin/) : Outil natif en pur Dart pour compiler, verrouiller les Releases du Store, générer les différentiels binaires (bidiff+zstd) et publier les patchs signés cryptographiquement via HashiCorp Vault.
🏛️ Architecture Globale & Flux de Données
flowchart TD
subgraph Client["📱 Application Mobile Flutter"]
App["App Métier (main.dart)"]
RC["Remote Config Distant<br/>(Firebase / API Interne)"]
GK{"🛡️ Gatekeeper<br/>3 Conditions"}
Shield["🔒 Bouclier Sécurité<br/>(Certificat APK / Root / TLS)"]
Updater["Moteur Natif Rust<br/>(libflutter / Updater)"]
end
subgraph Server["🏢 Serveur Souverain On-Premise"]
API["Spring Boot API<br/>(Data Plane / Admin)"]
Vault["🔑 HashiCorp Vault<br/>(Transit Engine / Clés RSA)"]
Storage["📦 Coffre-Fort d'Artefacts<br/>(Releases & Différentiels)"]
DB[("PostgreSQL 16<br/>Audit & Métadonnées")]
end
App -->|1. Initialisation| GK
RC -->|2. Charge Payload JSON| GK
GK -->|Condition échouée| Sleep["0 Appel Réseau (0 Mo Data)"]
GK -->|3 Conditions réunies| Shield
Shield -->|Appareil sain| Updater
Updater -->|3. POST /api/v1/patches/check| API
API -->|4. Renvoie URL & Signature| Updater
API -.->|Signe condensat SHA-256| Vault
Updater -->|5. Télécharge Micro-Patch (~100 Ko)| Storage
Updater -->|6. Événements télémétrie| DB
⚡ Pourquoi le "Remote Config Gatekeeper" ? (0 Appel Inutile)
Dans une architecture classique, le moteur natif appelle le serveur OTA à chaque lancement de l'application. Pour une banque ayant 5 millions d'utilisateurs actifs, cela engendre 10 à 15 millions de requêtes HTTP quotidiennes inutiles, saturant l'infrastructure et consommant le forfait data mobile (3G/4G) des clients.
En configurant auto_update: false dans shorebird.yaml, le SDK tk_ota_push prend le relais et évalue localement les 3 conditions cumulatives en mémoire en 0 milliseconde :
flowchart LR
C1["1. Version Cible<br/>(target_release_version)"] -->|Match| C2["2. Validation Sécurité<br/>(patch_available == true)"]
C2 -->|Match| C3["3. Monotonie Patch<br/>(current < latest)"]
C3 -->|Match| OK["✅ Appel Serveur Autorisé"]
C1 -.->|Différent| NO["🚫 0 Appel Réseau"]
C2 -.->|False| NO
C3 -.->|Déjà à jour| NO
Format du Payload Remote Config
Que vous utilisiez Firebase Remote Config, une route API interne de la banque ou notre endpoint dédié, voici le JSON consommé par le SDK :
{
"target_release_version": "1.0.0+3",
"patch_available": true,
"latest_patch_number": 2,
"force_update": false,
"release_notes": "Optimisation des virements instantanés et correctif UI."
}
| Champ | Type | Description |
|---|---|---|
target_release_version |
String |
Version installée ciblée (ex: 1.0.0+3). Si l'utilisateur est en 1.0.0+2, l'appel est ignoré. |
patch_available |
bool |
Interrupteur maître. Si false, les serveurs OTA ne sont pas interrogés. |
latest_patch_number |
int |
Dernier numéro de patch disponible. Si le client a déjà ce patch, aucun appel n'est fait. |
force_update |
bool |
false = mode silencieux éco-data ; true = modale bloquante de sécurité. |
release_notes |
String? |
Message affiché à l'utilisateur lors d'une mise à jour critique. |
📦 Installation & Configuration
1. Dépendance Flutter
Ajoutez tk_ota_push à votre pubspec.yaml :
dependencies:
flutter:
sdk: flutter
tk_ota_push: ^0.3.0
2. Configuration Souveraine (shorebird.yaml)
À la racine de votre application Flutter :
# Identifiant de l'Organisation Multi-Tenant
tenant_id: sara-money
# Identifiant de l'Application Mobile
app_id: sara-money-demo
# URL du Serveur OTA Souverain (lue par la CLI et le moteur mobile C++/Rust)
base_url: http://localhost:8080 # ou https://ota.saramoney.cm en production
# Canal de distribution par défaut (stable, staging, beta)
channel: stable
# Clé publique RSA de vérification (Vault)
patch_public_key: <VOTRE_CLE_PUBLIQUE_RSA_FOURNIE_PAR_LE_SERVEUR>
# Délègue la décision réseau au Gatekeeper 3 étapes
auto_update: false
# Mode de vérification cryptographique strict
patch_verification: strict
Déclarez le fichier dans pubspec.yaml :
flutter:
assets:
- shorebird.yaml
💻 Intégration dans le Code Dart (main.dart)
import 'package:flutter/material.dart';
import 'package:tk_ota_push/tk_ota_push.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 1. Initialisation du SDK avec Bouclier de Sécurité Bancaire
final ota = TkOtaPush.init(
appReleaseVersion: '1.0.0+3',
requireWifiForSilentUpdate: false, // true = télécharge uniquement en Wi-Fi
minBatteryPercentageForSilentUpdate: 20,
securityPolicy: const OtaSecurityPolicy(
blockRootedOrJailbrokenDevices: true, // Bloque les appareils compromis
expectedApkSignaturesSha256: [
'6b6929c8ff345cf008496328d7bff081a8ccfb89498bf9916ed668be3c3dedcf', // Empreinte certificat APK officiel
],
pinnedServerCertSha256Fingerprints: [
'42d889f8f4b6bf9d3abb767d3b5f215e0009e4d26cb7b958497e8b291e1b96dc', // Pinning TLS
],
),
// Hook de résilience post-rollback : déclenché si un patch défectueux est révoqué
onRollbackDetected: (event) async {
debugPrint('⚠️ ROLLBACK DÉTECTÉ : #${event.previousPatchNumber} -> #${event.currentPatchNumber}');
// Purger le cache SQLite / SharedPreferences pour éviter tout conflit de schéma
},
// Déclenché si force_update == true dans le Remote Config
onCriticalUpdateRequired: (config, triggerImmediateDownload) async {
debugPrint('🚨 MISE À JOUR CRITIQUE OBLIGATOIRE : ${config.releaseNotes}');
// Afficher une boîte de dialogue bloquante UI
await triggerImmediateDownload();
},
onSecurityViolation: (verdict) async {
debugPrint('⛔ VIOLATION DE SÉCURITÉ : ${verdict.reason}');
},
);
// 2. Synchronisation pilotée par le Remote Config
final remoteConfigPayload = {
'target_release_version': '1.0.0+3',
'patch_available': true,
'latest_patch_number': 2,
'force_update': false,
'release_notes': 'Correction calcul des intérêts de compte épargne.',
};
final outcome = await ota.syncWithRemoteConfig(
OtaRemoteConfig.fromJson(remoteConfigPayload),
);
debugPrint('Résultat Gatekeeper : ${outcome.message}');
debugPrint('Appel réseau effectué ? ${outcome.networkCallPerformed}');
runApp(const MonApplicationBancaire());
}
🎨 Politiques d'Exécution & Exemples Visuels
1. Mode Silencieux / Éco-Data (force_update: false)
Idéal pour les évolutions courantes. Le SDK inspecte l'état du réseau et de la batterie :
- Si les conditions sont optimales, le micro-patch différentiel (~50 à 150 Ko) est téléchargé en arrière-plan sans interrompre l'utilisateur.
- Il s'applique automatiquement au prochain démarrage à froid (Cold Start).
- Si la batterie est faible ou que le Wi-Fi est requis, le téléchargement est différé via
workmanager.
2. Mode Critique Forcé (force_update: true)
Idéal pour les correctifs d'urgence (faille de sécurité, régulation bancaire). Une modale bloquante s'affiche immédiatement :
Future<void> showCriticalUpdateDialog(
BuildContext context,
OtaRemoteConfig config,
Future<bool> Function() onDownload,
) {
return showDialog(
context: context,
barrierDismissible: false,
builder: (ctx) => AlertDialog(
icon: const Icon(Icons.security_update_warning, size: 48, color: Colors.orange),
title: const Text('Mise à Jour de Sécurité Obligatoire'),
content: Text(config.releaseNotes ?? 'Veuillez patienter pendant la mise à jour.'),
actions: [
FilledButton(
onPressed: () async {
final success = await onDownload();
if (success) {
// Notifier l'utilisateur de redémarrer
}
},
child: const Text('Mettre à jour maintenant'),
),
],
),
);
}
🛡️ Le Bouclier de Sécurité Mobile (OtaSecurityPolicy)
| Protection | Risque Éliminé | Fonctionnement |
|---|---|---|
| Anti-Repackaging APK | Faux APK modifiés ou infectés partagés via Xender / WhatsApp / Telegram | Compare l'empreinte SHA-256 du certificat de signature de l'APK avec expectedApkSignaturesSha256. Si altéré $\rightarrow$ refus strict du patch. |
| Détection Root / Jailbreak | Appareils compromis ou débogueurs malveillants | Détecte les binaires su, Magisk, SuperSU, Cydia. |
| Certificate Pinning TLS | Attaques Man-in-the-Middle (MitM) sur réseaux publics | Valide l'empreinte SHA-256 du certificat HTTPS du serveur OTA souverain. |
🖼️ Gestionnaire d'Assets Dynamiques (OtaAssetManager)
Permet de mettre à jour des fichiers statiques (traductions JSON fr_CM.json, bannières promotionnelles, icônes SVG) sans aucune recompilation :
final assetManager = OtaAssetManager();
final descriptor = OtaRemoteAssetDescriptor(
assetKey: 'assets/i18n/fr.json',
downloadUrl: 'https://tks-cloud.duckdns.org/api/v1/assets/fr-v2.json',
expectedSha256: '99e887f4f4b6bf9d3abb767d3b5f215e0009e4d26cb7b958497e8b291e1b96dc',
);
// Télécharge, vérifie l'intégrité SHA-256 et stocke dans le cache sécurisé
final file = await assetManager.fetchAndCacheAsset(descriptor);
🛠️ CLI de Déploiement (tk-ota-push)
La CLI est incluse dans le package et peut être activée globalement sur n'importe quel poste développeur ou serveur CI/CD :
dart pub global activate tk_ota_push
1. Authentification Développeur (Passerelle Web & PKCE)
tk-ota-push login
(Si --web-url est omis, la CLI utilise web_console_url de shorebird.yaml ou http://localhost:3000).
Ouvre automatiquement votre navigateur pour l'authentification Keycloak / SSO et génère une session chiffrée locale (~/.tk_ota_push/credentials.json).
Vérifier la session active et le Tenant connecté :
tk-ota-push whoami
2. Publication d'une Release du Store (Zero-Config)
À exécuter le jour où vous soumettez votre binaire sur Google Play ou Apple App Store. La version est lue dans pubspec.yaml, et le serveur ainsi que l'organisation dans shorebird.yaml :
# Commande Zero-Config (1 clic)
tk-ota-push release
Reçu généré dans le terminal :
┌──────────────────────────────────────────────────────────────────────────
│ 🔒 REÇU DE VERROUILLAGE RELEASE (RÉFÉRENCE STORE)
├──────────────────────────────────────────────────────────────────────────
│ ID Release (UUID) : a42e271a-31ab-4476-ae69-24e5d2b8448f
│ Application (Tenant): sara-money-demo (sara-money)
│ Version & Cible : v1.0.0+3 • android (aarch64)
│ Binaire Référence : libapp.so (Scellé)
│ Garde-Fou Natif : pubspec.lock (42d889f8... Scellé)
│ Garde-Fou Assets : flutter_assets/ (99e887f4... Scellé)
│ Symboles (.symbols) : Archivés (app.android-arm64.symbols)
│ Statut Release : LIVE_ON_STORE
└──────────────────────────────────────────────────────────────────────────
3. Publication d'un Patch à Chaud (Différentiel bidiff + zstd)
Après avoir modifié du code Dart, publiez le patch directement sans répéter l'URL du serveur ou le tenant :
# Publication standard
tk-ota-push patch -m "Correction calcul frais de virement"
Reçu généré dans le terminal :
┌──────────────────────────────────────────────────────────────────────────
│ 🚀 REÇU DE PUBLICATION PATCH OTA #1
├──────────────────────────────────────────────────────────────────────────
│ ID Patch (UUID) : 8b73f831-29cf-4cb6-8801-44ebda29910a
│ Application & Version: sara-money-demo • v1.0.0+3
│ Cible & Canal : android (aarch64) • Canal 'stable'
│ Statut & Rollout : APPROVED_AND_SIGNED (Déploiement : 100%)
│ Taille Différentiel : 142.0 KB (145408 octets)
│ SHA-256 Binaire : 112233445566778899aabbccddeeff001122334455...
│ Signature Vault RSA : d7a81c4e2b0f... (Scellée dans Vault)
│ Symboles (.symbols) : Archivés (app.android-arm64.symbols)
│ Auteur (Git / Dev) : dev@saramoney.cm
└──────────────────────────────────────────────────────────────────────────
4. Workflow à 4 Yeux (--staged) & Promotion de Patch
Pour soumettre un patch à validation sans le déployer immédiatement :
# 1. Le développeur soumet le patch sur staging (staged pour revue RSSI)
tk-ota-push patch --channel staging --staged -m "Hotfix audit COBAC"
# 2. Le SuperAdmin ou RSSI approuve et promeut vers la production (Re-signature Vault automatique)
tk-ota-push promote --patch-id <UUID_DU_PATCH> --to-channel stable
5. Téléchargement des Symboles d'Obfuscation (Crash Triage)
tk-ota-push symbols --patch-id <UUID_DU_PATCH>
flutter symbolize -i crash.txt -d patch_<UUID>.symbols
⚡ Cascade de Résolution des Paramètres (v0.3.0)
Pour chaque paramètre, la CLI applique un ordre de priorité déterministe garantissant une configuration Zero-Config sans risque d'erreur :
| Paramètre | 1. Flag CLI | 2. shorebird.yaml |
3. Session Active | 4. Variable d'Env | 5. Valeur par Défaut |
|---|---|---|---|---|---|
| Serveur OTA | --server <url> |
base_url |
(N/A) | OTA_SERVER_URL |
http://localhost:8080 |
| Tenant ID | --tenant-id <id> |
tenant_id |
session.tenantId |
OTA_TENANT_ID |
default |
| App ID | --app-id <id> |
app_id |
(N/A) | (N/A) | Erreur bloquante |
| Canal | --channel <nom> |
channel |
(N/A) | (N/A) | stable |
| Version | Argument positionnel | (N/A) | (N/A) | (N/A) | pubspec.yaml (version: x.y.z+b) |
📜 Options & Flags de la CLI
| Commande | Option / Flag | Description |
|---|---|---|
release / patch |
--server <url> |
URL du serveur OTA (défaut : lu dans shorebird.yaml ou http://localhost:8080). |
release / patch |
--tenant-id <id> |
Identifiant du Tenant (défaut : lu dans shorebird.yaml ou session active). |
release / patch |
--channel <nom> |
Canal de destination : stable, staging, beta (défaut : lu dans shorebird.yaml ou stable). |
release / patch |
--app-id <id> |
Identifiant unique de l'app (défaut : lu dans shorebird.yaml). |
release / patch |
--project <dir> |
Chemin du projet Flutter (défaut : .). |
release / patch |
--split-debug-info=<dir> |
Répertoire des symboles Dart pour l'archivage automatique .symbols. |
release / patch |
--obfuscate / --no-obfuscate |
Active ou désactive l'obfuscation de code Dart (activé par défaut). |
patch |
--rollout <1-100> |
Pourcentage de déploiement progressif Canary (défaut : 100). |
patch |
--staged |
Soumet le patch à l'état STAGED pour approbation à 4 yeux (Maker-Checker). |
patch |
--message (-m) |
Description métier ou traçabilité Git liée au correctif. |
promote |
--patch-id <uuid> |
UUID du patch à promouvoir vers un canal cible. |
promote |
--to-channel <nom> |
Canal cible (défaut : stable). |
symbols |
--patch-id <uuid> |
UUID du patch dont on télécharge les symboles dé-obfuscateurs. |
symbols |
--output (-o) |
Chemin du fichier .symbols en sortie. |
| Toutes | --json |
Affiche la réponse brute de l'API en JSON (pour intégrations CI/CD). |
📄 Licence
Ce package est distribué sous licence MIT. Consultez le fichier LICENSE pour plus d'informations.
Libraries
- tk_ota_push
- SDK mobile du package
tk_ota_push.