Portfolio
CV
20 avr. 2026 Case Study

RedOps : framework Java de modules d'audit par réflexion

Framework Java extensible pour exécuter des modules d'audit de sécurité de manière standardisée, basé sur une annotation @Module, un chargement dynamique par réflexion, et une interface graphique de monitoring.

JavaRéflexionSécurité offensiveArchitecture logicielle

L’objectif

RedOps part d’un projet ISEP autour de la sécurité offensive, mais l’angle qui m’intéressait c’était l’architecture derrière : comment construire un système de plugins en Java, sans dépendre d’un framework type Spring, juste avec la réflexion native du JDK.

Concrètement, l’idée c’est qu’on puisse écrire une nouvelle classe, l’annoter avec @Module, et qu’elle soit détectée au runtime et exécutée automatiquement au démarrage. Les modules livrés avec le projet (ping, scan de ports basique) restent volontairement simples ; le but n’était pas de livrer un outil de pentest complet mais de valider le pattern d’extensibilité.

Architecture

L’annotation @Module et le scan par réflexion

Tout repose sur une annotation custom :

@Module(name = "Simple Ping", order = 0)
public class Ping extends AbstractModule {
    @Override
    protected ExecutionStatus process(InetAddress address) throws InterruptedException {
        boolean reachable = address.isReachable(5000);
        return reachable ? ExecutionStatus.SUCCESS : ExecutionStatus.ERROR;
    }
}

Au démarrage, AnnotationConfigLoader scanne le package com.redops (via la librairie Reflections) pour retrouver toutes les classes portant @Module, en extrait les métadonnées (nom, ordre d’exécution) et construit la configuration. Pas de fichier XML ou YAML à maintenir : ajouter un module revient à écrire une classe et une annotation, le framework se débrouille pour la découvrir.

Le pipeline d’orchestration

ModuleManager est le point d’entrée de l’orchestration et délègue à trois composants qui ont chacun une seule responsabilité :

  • ModuleInitializer lit la config et appelle ModuleInstantiator pour instancier chaque module par réflexion, en lui fournissant les dépendances dont son constructeur a besoin (voir plus bas)
  • ModuleExecutor parcourt les ModuleInfo dans l’ordre et appelle run() sur chaque module
  • Le shutdown est géré séparément pour garder le cleanup découplé de l’exécution
public class ModuleManager {
    public Stream<ModuleInfo> startModules() {
        List<ModuleInfo> modules = initializer.initializeModules();
        executor.executeModules(modules);
        return modules.stream();
    }
}

Une injection de dépendances très basique

ModuleInstantiator inspecte les paramètres du constructeur via réflexion, et selon leur type, fournit l’instance correspondante, comme par exemple l’adresse IP cible du module. Pas de container DI, pas d’annotations dédiées comme @Inject : juste un if/switch sur le type attendu et la bonne instance fournie à la main. C’est volontairement minimaliste, le framework résout ce dont un module a besoin avant de l’instancier, plutôt que de laisser le module aller chercher ses dépendances lui-même.

Le cycle de vie d’un module

Chaque module hérite de AbstractModule, qui encapsule le changement de statut autour de l’appel à process() :

public abstract class AbstractModule {
    public ExecutionStatus status = ExecutionStatus.NOT_STARTED;

    public void run() throws UnknownHostException, InterruptedException {
        setStatus(ExecutionStatus.IN_PROGRESS);
        ExecutionStatus result = process(getTargetAddress());
        setStatus(result);
    }

    protected abstract ExecutionStatus process(InetAddress address)
        throws InterruptedException, IOException;
}

Les statuts (SUCCESS, WARNING, ERROR, CRITICAL_ERROR…) sont définis dans un enum dédié et remontés en temps réel à l’interface graphique. Un module qui échoue n’arrête pas les autres, le ModuleExecutor log l’erreur et continue la séquence.

Interface graphique

L’UI (Swing + FlatLaf pour un aspect plus moderne, JFreeChart pour les graphiques de résultats) tourne sur l’Event Dispatch Thread, séparée du thread d’exécution des modules. La synchronisation entre les deux passe par SwingUtilities.invokeLater(), pour éviter les accès concurrents typiques de Swing.

Ce que j’ai appris

Comprendre où s’arrêtent les garanties de la réflexion : sans framework comme Spring derrière, je dois inspecter à la main les paramètres du constructeur et décider quoi fournir selon leur type. Il aurait été possible de mettre en place une liste plus grande pour élargir le nombre de dépendances, mais pas vraiment utile dans ce cas. Le côté threading (exécution des modules hors EDT, mise à jour de l’UI via invokeLater) a aussi été une bonne introduction aux problèmes classiques de concurrence dans une appli desktop.

Avertissement

RedOps reste un projet pédagogique : les modules fournis sont volontairement basiques (ping, scan de ports), pensés pour démontrer le pattern d’extensibilité plutôt que pour un usage de pentest réel. Le code est sous licence MIT et disponible sur GitHub.