Zero Trust TLS Everywhere JWT + RSA-4096 Ansible natif Open Source · Go v2.0.0 Proxy Multi-zone Event Hooks

Des agents
pour Ansible

Exécutez vos playbooks sur des hôtes derrière NAT, firewall ou DMZ. Les agents initient eux-mêmes la connexion — vous n'ouvrez aucun port entrant.

8 risques que vous prenez à chaque playbook

Ansible est un outil remarquable. Mais son modèle SSH classique crée des angles morts sécurité et des dettes opérationnelles que la plupart des équipes découvrent trop tard.

💥

Une machine compromise, toute votre infra exposée

Votre Control Node centralise les accès SSH à tous vos hôtes. Un token CI/CD leaké, une dépendance malveillante, un accès mal révoqué — et c'est l'ensemble de votre parc qui est à portée d'un attaquant. En une seule étape. Sans alarme.

La surface d'attaque croît linéairement avec la taille de votre infra.

🔑

Révoquer une clef SSH : une urgence à 3h du matin

Une clef volée. L'horloge tourne. Vous devez la supprimer sur 300 hôtes, maintenant. Avec SSH classique, c'est une opération manuelle, hôte par hôte, qui prend des heures — sans blacklist centrale, sans expiration automatique.

La rotation de clefs n'est pas un événement planifié. C'est une urgence.

📋

Qui a lancé quoi, sur quoi, et quand ?

Ansible n'a pas de log d'audit centralisé. Les traces restent sur le Control Node — modifiables, supprimables. En cas d'incident ou de contrôle ISO 27001, NIS2 ou SOC2, vous ne pouvez pas prouver qu'une configuration a été appliquée, ni par qui.

L'auditeur pose la question. Vous cherchez dans des logs dispersés sur plusieurs machines.

🚪

Tout le monde peut tout faire

Ansible n'a pas de RBAC natif. Sur un Control Node standard, quiconque a accès peut lancer n'importe quel playbook sur n'importe quel hôte. AWX apporte du contrôle d'accès — au prix d'une stack lourde, d'une base de données et d'une interface à maintenir.

Vos droits sont gérés dans Azure AD. Votre automation n'a aucun contrôle d'accès.

📡

Votre infra dérive. Vous ne le savez pas.

Ansible pousse une configuration, puis rien. Entre deux runs, un admin fait un changement manuel, un paquet se met à jour, une règle firewall est modifiée. Votre infra dérive silencieusement de l'état décrit dans vos playbooks — et vous ne le saurez qu'au prochain incident.

Push-and-forget n'est pas de la gestion de configuration. C'est de l'espoir.

⏸

Un hôte offline ? La tâche disparaît.

Un redémarrage inattendu, une coupure réseau — si un hôte est indisponible au moment du playbook, la tâche échoue et n'est jamais rejouée. En rolling deploy sur 50 machines, il suffit d'une qui redémarre pour avoir un parc partiellement patché, sans alerte.

Votre fenêtre de maintenance a 4 heures. 3 hôtes n'ont pas reçu le patch. Vous ne le savez pas.

🔀

Un hôte compromis peut attaquer votre Control Node

C'est votre Control Node qui initie les connexions SSH vers les hôtes. Un hôte compromis connaît son adresse IP — et cette machine détient les clefs SSH de tout votre parc. Compromettre un hôte → cibler le Control Node → accéder à l'intégralité de l'infrastructure.

Vous avez segmenté vos hôtes. Mais le Control Node est le dénominateur commun de tous vos segments.

🌐

Multi-cloud, multi-site : une dette réseau permanente

Production, DMZ, disaster recovery, cloud A, cloud B, sites edge — chaque segment doit avoir une route vers le Control Node. VPN site-à-site, peering cloud, tunnels SSH : chaque connexion est une règle firewall de plus, une surface d'attaque de plus, une configuration à maintenir.

Votre infra est multi-cloud. Votre automation ne devrait pas exiger un réseau plat entre tous vos environnements.

Ansible Control Node ──SSH──▶ Hôte cible ← BLOQUÉ derrière NAT/firewall

Le modèle inversé Ansible-SecAgent

Ansible-SecAgent inverse le flux de contrôle. L'agent sur l'hôte cible initie une connexion WebSocket persistante vers le serveur relay. Ansible n'a besoin d'atteindre que le relay server — jamais les hôtes directement.

Mononode

Simple & rapide

Un seul serveur relay pour démarrer. Idéal pour les petites infrastructures ou pour valider la solution en quelques minutes.

  • 1 secagent-server (Go, 4.65 MiB)
  • 1 NATS JetStream (embarqué)
  • 1 Caddy (TLS automatique)
  • SQLite inclus — zéro config DB
  • docker compose up -d → prêt en 30s
Multi-nodes · HA

Scalable & haute dispo

Plusieurs nodes relay derrière un load balancer. Les agents se connectent à n'importe quel node, le routage est transparent via cluster NATS.

  • N secagent-server en parallèle
  • Cluster NATS JetStream (3 nodes)
  • Load balancer (Nginx / Ingress K8s)
  • PostgreSQL externe (RDS/CloudSQL)
  • Helm chart Kubernetes inclus

Votre Ansible existant, inchangé

Ansible-SecAgent s'intègre comme un plugin Ansible standard. Vos playbooks, rôles et collections fonctionnent sans modification. La transition est progressive : SSH reste disponible en parallèle.

Playbooks inchangés

Tous vos playbooks, rôles et collections Ansible Galaxy fonctionnent sans modification. Changez juste connection: relay dans ansible.cfg.

SSH toujours disponible

Ansible-SecAgent ne supprime pas SSH. Les deux coexistent. Idéal pour une migration progressive : passez hôte par hôte sans rupture de service.

Inventaire dynamique

Le plugin inventory liste automatiquement tous les agents connectés avec leurs facts. Vos inventaires statiques restent compatibles en parallèle.

become / sudo supporté

become: true et become_user fonctionnent comme avec SSH. Le become_pass est transmis de façon sécurisée, masqué dans tous les logs.

# ansible.cfg — seul changement requis [defaults] connection = relay # was: ssh inventory = relay_inventory.py # dynamic, auto-populated [relay_connection] server_url = https://relay.example.com token = ${ANSIBLE_RELAY_TOKEN}

Sécurité by design

Ansible-SecAgent est conçu autour d'un modèle Zero Trust. Chaque décision d'architecture a une justification sécurité explicite, documentée dans les specs techniques.

RSA-4096 par agent

Chaque agent génère sa paire de clefs RSA-4096 au premier démarrage. La clef privée ne quitte jamais l'hôte.

Enrôlement contrôlé

La clef publique de l'agent doit être pré-autorisée en base de données avant tout enrôlement. Zéro TOFU (Trust On First Use).

JWT chiffré RSAES-OAEP

Le token JWT retourné à l'agent est chiffré avec sa clef publique RSA. Illisible sans la clef privée, même en cas d'interception réseau.

Blacklist JTI

Révocation immédiate par JTI (JWT ID unique). La connexion WebSocket active est fermée avec le code 4001 — l'agent ne reconnecte pas automatiquement.

Dual-key JWT

Rotation des secrets JWT sans interruption de service. Grace period configurable : les anciens tokens restent valides pendant la migration.

TLS obligatoire

WSS (WebSocket over TLS) et HTTPS sur toutes les connexions. Terminaison TLS via Caddy. Aucun plain HTTP accepté.

become_pass masqué

Le mot de passe sudo/su est transmis via stdin chiffré et masqué dans tous les logs — agent, serveur et plugins Ansible.

AES-256-GCM

Les secrets serveur (RSA keypair, JWT secrets) sont chiffrés en base de données via AES-256-GCM dérivé de RSA_MASTER_KEY.

NOUVEAU v2.0

4ème rôle JWT : relay

Le rôle relay (distinct de agent/plugin/admin) authentifie les relay nodes en mode pull. Créé via POST /api/admin/tokens — jamais partagé avec les agents.

Vecteur d'attaque SSH classique Ansible-SecAgent
Port entrant exposé Port 22 ouvert Aucun port entrant
Interception du token d'auth Clef SSH en clair sur disque JWT chiffré RSA-OAEP
Agent compromis → propagation Clef SSH donne accès direct JTI révocable immédiatement
Agent non autorisé TOFU implicite possible Pre-authorize obligatoire
Rotation des secrets Manuelle, downtime Dual-key, grace period, zero downtime
Fuite become_pass dans logs Risque selon configuration Masqué dans tous les logs

Proxy/Gateway multi-zone — secagent_repeater

Ansible-SecAgent v2.0 introduit le mode proxy : un serveur central agrège plusieurs zones réseau isolées (DMZ, cloud privé, sites edge) en un point d'entrée unique. Ansible n'a besoin d'atteindre qu'un seul endpoint pour piloter l'intégralité du parc.

Mode Pull

Les relays initient la connexion

Chaque relay DMZ ouvre une WebSocket persistante vers le proxy central via WSS /ws/relay. Le relay s'authentifie avec un JWT rôle relay. Aucune connexion entrante depuis le proxy vers les DMZ.

  • Connexion sortante uniquement depuis chaque DMZ
  • JWT rôle relay dédié (distinct de agent/plugin/admin)
  • Protocole WS : 10 types de messages bidirectionnels
  • Fermeture propre codes 4010/4011
Mode Push

Le proxy initie les connexions REST

Le proxy contacte directement des relays enregistrés en DB via HTTP REST. Inventaire agrégé automatiquement toutes les 30s (configurable via PROXY_INVENTORY_POLL_INTERVAL).

  • Relays enregistrés via POST /api/admin/relays
  • Poll inventaire toutes les 30s (configurable)
  • Table DB : relay_nodes + relay_routing
  • CLI : secagent-server relays list|add|remove|status

Inventaire agrégé

GET /api/inventory agrège automatiquement les agents de tous les relays connectés et les agents directs. Un seul appel, visibilité complète du parc.

Routage transparent

/api/exec/{host}, /api/upload/{host}, /api/fetch/{host} routés automatiquement vers le relay propriétaire de l'hôte via la table relay_routing.

Chaînage proxy→proxy

Topologies hiérarchiques multi-niveaux supportées (is_proxy: true dans relay_hello). Protection anti-boucle via en-tête X-Relay-Hops (budget 8, HTTP 508 si épuisé).

Docker Compose multi-zones

Topologie qualif incluse : proxy + 2 relays DMZ + agents simulés, réseaux isolés. Prêt en une commande.

# Démarrage mode repeater (proxy central) $ PROXY_MODE=true \ PROXY_RELAYS='[{"url":"http://relay-dmz1:7770","mode":"push"}]' \ JWT_SECRET_KEY=<key> ADMIN_TOKEN=<token> ./secagent-server # Ou via Docker Compose multi-zones $ cd DEPLOYMENT/qualif $ docker compose -f docker-compose.proxy.yml up -d # → proxy + relay-dmz1 + relay-dmz2 + agents simulés démarrés # Gérer les relays depuis la CLI $ secagent-server relays list --format table ID URL MODE STATUS AGENTS 1 http://relay-dmz1:7770 push connected 3 2 http://relay-dmz2:7771 push connected 2

Event Hooks — Actions configurables par JSON

Déclenchez automatiquement des actions (notifications, scripts, appels API, écriture de fichiers) sur les événements de vos agents relay. Configuration zéro-code via un fichier JSON — hot-reload via SIGHUP.

4 types d'actions

shell — exécute un script système
file — écrit dans un fichier de log
api — appel HTTP avec méthode configurable
webhook — HTTP POST signé HMAC-SHA256

Événements disponibles

agent.connected — connexion WSS établie
agent.disconnected — déconnexion WSS
task.completed — tâche terminée avec succès
task.failed — tâche en erreur

Templates de variables

Injectez des variables dynamiques dans vos actions : {{.AgentID}}, {{.Hostname}}, {{.Event}}, {{.Timestamp}}, {{.Status}}

Dispatcher asynchrone

Queue interne de 1000 entrées, non-bloquant. Hot-reload via SIGHUP sans redémarrer le serveur. Log complet dans la table action_log.

# ~/.secagent/hooks.json { "hooks": [ { "event": "agent.connected", "action": { "type": "shell", "command": "notify-send 'Agent connecté: {{.AgentID}}'" } }, { "event": "task.completed", "action": { "type": "api", "url": "https://mon-cmdb/api/update", "method": "POST" } }, { "event": "agent.disconnected", "action": { "type": "webhook", "url": "https://alerting.example.com/relay", "secret": "${HMAC_SECRET}" } } ] } # Consulter les logs d'exécution $ secagent-server hooks log --limit 10 --event agent.connected $ secagent-server hooks status # Hot-reload sans redémarrage $ kill -SIGHUP $(pgrep secagent-server)

Implémentées par version

Ansible-SecAgent est développé par phases incrémentales, chacune validée par tests automatisés et audit sécurité avant déploiement.

v0.1 — Agent Python MVP
  • Enrollment RSA-4096 : génération clef, POST /api/register, JWT OAEP
  • Connexion WSS persistante avec reconnexion backoff exponentiel (1s → 60s)
  • Exécution de commandes via subprocess (buffer stdout 5 MB, timeout, kill)
  • Transfert de fichiers : put_file et fetch_file (base64, limite 500 KB)
  • Élévation de privilèges : become via stdin, become_pass masqué dans logs
  • Registre async persisté JSON : reprise après redémarrage agent
  • Déploiement systemd : Restart=on-failure, User dédié, NoNewPrivileges
v0.2 — Server Python (FastAPI + NATS)
  • API REST FastAPI : /api/register, /api/exec, /api/upload, /api/fetch, /api/inventory
  • NATS JetStream : streams RELAY_TASKS + RELAY_RESULTS, routing inter-nodes HA
  • Auth JWT : rôles agent/plugin/admin, blacklist JTI, révocation WS code 4001
  • SQLite : tables agents, authorized_keys, blacklist avec schéma versionné
  • Inventaire dynamique : format JSON Ansible standard, filtre only_connected
v0.3 — Plugins Ansible
  • Connection plugin : remplace SSH, exec_command / put_file / fetch_file natifs
  • become support : transmission become_pass au serveur relay
  • Inventory plugin : GET /api/inventory → hostvars + groupes Ansible
  • Configuration via ansible.cfg ou variables d'hôte (ansible_connection: relay)
  • Mapping HTTP → AnsibleConnectionError : 503 UNREACHABLE, 504 timeout
v1.0 — Réécriture Go
  • Serveur Go : binaire 4.65 MiB, 0 restart en production, goroutines
  • Agent Go : 94 tests PASS, 3 agents qualif connectés en continu
  • secagent-inventory : binaire Go standalone, 19 tests, format Ansible natif
  • SQLite via modernc (CGO-free), Gorilla WebSocket, JWT Go natif
  • Dockerfile multi-stage golang:alpine → alpine : image minimale
v1.1 — CLI Management
  • CLI cobra intégrée dans le binaire secagent-server (zéro binaire supplémentaire)
  • minions : list / get / suspend / resume / revoke / authorize / vars
  • security : keys status / rotate --grace Nh · tokens list · blacklist list
  • inventory list --only-connected · server status / stats
  • --format table|json|yaml sur toutes les commandes
  • Dual-key JWT : rotation sans interruption, grace period configurable
  • Message WS rekey : agents migrent vers le nouveau secret sans déconnexion
  • AES-256-GCM : chiffrement RSA keypair serveur et JWT secrets en DB
  • 441 tests PASS, 0 fail · Audit sécurité : 0 CRITIQUE, 0 HAUT
v1.2 — Ansible Container
  • Container relay-ansible : multi-stage build (Go builder + Python runtime)
  • secagent-inventory : binaire Go standalone intégré dans le container
  • Connection plugin relay.py : chargé depuis ansible_plugins/connection_plugins/
  • ExecCommand handler : relaie commandes Ansible vers agents via WebSocket bloquant
  • Inventory filtering : par défaut retourne tous les minions, relay_status : "connected" | "disconnected"
  • End-to-end validation : ansible all -m ping, ansible-playbook, dynamic inventory ✅
  • Network : relay-ansible sur ansible_server_default (connecté à relay-api)
  • Dockerfile : golang:1.25-alpine (Stage 1) → python:3.11-slim (Stage 2)
  • Phase 11 COMPLETE : tous les composants Ansible natif + relay intégrés et testés ✅
v1.1.0 — Event Hooks Unifiés
  • Système de hooks unifié (internal/hooks/) — actions déclenchées par événements agent
  • 4 types d'action : webhook (HMAC-SHA256), shell (subprocess + env SECAGENT_*), file (append O_CREATE), api (méthode configurable)
  • Moteur de template : {{hostname}}, {{event}}, {{timestamp}}, {{status}}, {{enrolled_at}}
  • Dispatcher asynchrone (queue 1000, non-bloquant) avec hot-reload via SIGHUP
  • CLI : secagent-server hooks status et hooks log --limit --event --hostname --format
  • Table action_log — trace toutes les exécutions d'actions en DB
  • Route GET /api/admin/hooks/log — consultation via API admin
v2.0.0 — Proxy/Gateway multi-zone (secagent_repeater)
  • Mode Proxy (PROXY_MODE=true) — agrégation multi-zones (DMZ, cloud privé, edge) en un point d'entrée unique
  • Mode pull : les relays initient la connexion vers le proxy via WSS /ws/relay (JWT rôle relay)
  • Mode push : le proxy contacte des relays enregistrés en DB via HTTP REST, poll inventaire 30s
  • Inventaire unifié : GET /api/inventory agrège agents de tous les relays + agents directs
  • Routage transparent : exec/upload/fetch routés automatiquement vers le relay propriétaire de l'hôte
  • Chaînage proxy→proxy : topologies hiérarchiques multi-niveaux avec protection anti-boucle (X-Relay-Hops)
  • Endpoints admin relays : POST/GET/DELETE/status sur /api/admin/relays
  • CLI : secagent-server relays list|get|status|add|remove
  • Docker Compose multi-zones : proxy + 2 relays DMZ + agents simulés — 917/920 tests PASS, QA VALIDATED

Administrez depuis le terminal

La CLI cobra est intégrée directement dans le binaire secagent-server. Accessible depuis un container Docker ou un pod Kubernetes — sans binaire supplémentaire.

secagent-server — management CLI
$ docker exec relay-api secagent-server minions list --format table HOSTNAME STATUS LAST SEEN VERSION IP qualif-host-01 connected 2026-03-06 14:32 v1.1.0 192.168.1.101 qualif-host-02 connected 2026-03-06 14:32 v1.1.0 192.168.1.102 qualif-host-03 connected 2026-03-06 14:31 v1.1.0 192.168.1.103 prod-web-01 suspended 2026-03-06 09:15 v1.1.0 10.0.1.10 prod-db-01 connected 2026-03-06 14:32 v1.1.0 10.0.1.20 5 agents (4 connected, 1 suspended) $ docker exec relay-api secagent-server security keys status --format table KEY SECRET STATUS EXPIRES current jwt_secret_current active 2026-04-05 14:00 previous jwt_secret_previous grace 2026-03-07 14:00 (grace 24h) RSA keypair rsa-4096 encrypted (AES-256-GCM in DB)
🤖

Gestion des agents

Listez, suspendez, révoquez et autorisez les agents. Gérez leurs variables d'inventaire.

secagent-server minions list
secagent-server minions get my-host
secagent-server minions suspend my-host
secagent-server minions resume  my-host
secagent-server minions revoke  my-host
secagent-server minions authorize new-host \
  --pubkey "$(cat pubkey.pem)"
secagent-server minions vars set my-host \
  env=prod region=eu-west
🔐

Sécurité & clefs

Rotation des secrets JWT zero downtime, gestion des tokens actifs et de la blacklist JTI.

# Rotation JWT (grace period configurable)
secagent-server security keys rotate --grace 24h

# État des clefs
secagent-server security keys status

# Tokens actifs et blacklist
secagent-server security tokens list
secagent-server security blacklist list
secagent-server security blacklist purge
📋

Inventaire

Visualisez les agents connectés et leurs facts directement depuis la CLI.

# Agents connectés uniquement
secagent-server inventory list --only-connected

# Format JSON (compatible Ansible)
secagent-server inventory list --format json

# Format YAML
secagent-server inventory list --format yaml
📊

Statut serveur

Consultez l'état du serveur relay : uptime, connexions actives, tâches exécutées, mémoire.

# État général
secagent-server server status --format table

# Statistiques détaillées
secagent-server server stats --format json

# Exemple de sortie :
# UPTIME    AGENTS  TASKS 24H  MEMORY
# 3d 14h    4 / 5   127        12 MB
Accès : La CLI lit les variables d'environnement du container (ADMIN_TOKEN, JWT_SECRET_KEY). Aucune configuration supplémentaire requise.
# Docker
docker exec relay-api secagent-server <commande>

# Kubernetes
kubectl exec -n ansible-relay deploy/relay-api -- secagent-server <commande>

À venir

Les phases suivantes sont spécifiées dans ARCHITECTURE.md et prêtes à démarrer.

✅ v1.1.0 — Event Hooks Unifiés LIVRÉ

Hooks configurables via JSON (shell, webhook, file, api), dispatcher asynchrone, hot-reload SIGHUP, CLI hooks status/log, table action_log.

✅ v2.0.0 — Proxy/Gateway multi-zone LIVRÉ

Mode proxy/repeater (pull + push), inventaire agrégé multi-zones, routage transparent, chaînage proxy→proxy, JWT rôle relay, Docker Compose multi-zones. 917/920 tests validés.

Production Kubernetes

Helm chart complet : Deployment relay-api (replicas 3), StatefulSet NATS JetStream (cluster, PVC 20Gi fast-ssd), Ingress nginx avec TLS cert-manager, Secrets K8s pour JWT_SECRET_KEY et ADMIN_TOKEN. PostgreSQL externe (RDS/CloudSQL) en remplacement de SQLite.

Documentation & Hardening

Rate limiting sur les endpoints sensibles (/api/register, /api/exec), audit logs structurés (JSON, syslog), RBAC granulaire par hostgroup, métriques Prometheus / Grafana dashboard, runbook opérationnel.

Plugin FreeIPA

Intégration enterprise : enrollment via ipa-client-install, authentification mTLS avec certificats signés par l'AC FreeIPA (Dogtag), révocation via CRL/OCSP, inventory plugin LDAP (hostgroups IPA → groupes Ansible natifs).

Architecture

Deux topologies de déploiement selon vos besoins : un relay central pour les architectures simples, ou un repeater multi-zones pour les infrastructures segmentées (DMZ, cloud privé, sites edge).

Mode Standard

Un relay central agrège les agents de votre infrastructure.

graph LR ANS["🎯 Ansible\nOrchestrator"] SRV["🖥️ secagent-server\nRelay"] A1["💻 Agent\nLinux Host 1"] A2["💻 Agent\nLinux Host 2"] A3["💻 Agent\nLinux Host 3"] ANS -->|"REST HTTP / JWT plugin"| SRV A1 -->|"WSS — agent initie"| SRV A2 -->|"WSS — agent initie"| SRV A3 -->|"WSS — agent initie"| SRV style ANS fill:#4A90D9,stroke:#2171B5,color:#fff style SRV fill:#E8A838,stroke:#C07C1A,color:#fff style A1 fill:#5BA85A,stroke:#3D7A3C,color:#fff style A2 fill:#5BA85A,stroke:#3D7A3C,color:#fff style A3 fill:#5BA85A,stroke:#3D7A3C,color:#fff

Mode Pull — le repeater se connecte aux relays

Le repeater initie les connexions REST vers chaque relay DMZ. Convient quand le repeater a accès aux zones isolées.

graph TB ANS["🎯 Ansible\nOrchestrator"] subgraph TRUST["☁️ Zone de confiance"] REP["🔀 Repeater\nsecagent-server"] end subgraph DMZ1["🔒 DMZ Zone 1"] R1["🖥️ Relay DMZ1"] B1["💻 Agent A"] B2["💻 Agent B"] B1 -->|"WSS\n(agent→relay)"| R1 B2 -->|"WSS\n(agent→relay)"| R1 end subgraph DMZ2["🔒 DMZ Zone 2"] R2["🖥️ Relay DMZ2"] B3["💻 Agent C"] B4["💻 Agent D"] B3 -->|"WSS\n(agent→relay)"| R2 B4 -->|"WSS\n(agent→relay)"| R2 end ANS -->|"REST / JWT plugin"| REP REP -->|"REST — repeater initie"| R1 REP -->|"REST — repeater initie"| R2 style ANS fill:#4A90D9,stroke:#2171B5,color:#fff style REP fill:#9B59B6,stroke:#7D3C98,color:#fff style R1 fill:#E8A838,stroke:#C07C1A,color:#fff style R2 fill:#E8A838,stroke:#C07C1A,color:#fff style B1 fill:#5BA85A,stroke:#3D7A3C,color:#fff style B2 fill:#5BA85A,stroke:#3D7A3C,color:#fff style B3 fill:#5BA85A,stroke:#3D7A3C,color:#fff style B4 fill:#5BA85A,stroke:#3D7A3C,color:#fff

Mode Push — les relays se connectent au repeater

Chaque relay DMZ initie une connexion WSS persistante vers le repeater. Idéal quand les DMZ ont des règles firewall strictes (pas d'entrée).

graph TB ANS["🎯 Ansible\nOrchestrator"] subgraph TRUST["☁️ Zone de confiance"] REP["🔀 Repeater\nsecagent-server"] end subgraph DMZ1["🔒 DMZ Zone 1"] R1["🖥️ Relay DMZ1"] B1["💻 Agent A"] B2["💻 Agent B"] B1 -->|"WSS\n(agent→relay)"| R1 B2 -->|"WSS\n(agent→relay)"| R1 end subgraph DMZ2["🔒 DMZ Zone 2"] R2["🖥️ Relay DMZ2"] B3["💻 Agent C"] B4["💻 Agent D"] B3 -->|"WSS\n(agent→relay)"| R2 B4 -->|"WSS\n(agent→relay)"| R2 end ANS -->|"REST / JWT plugin"| REP R1 -->|"WSS — relay initie"| REP R2 -->|"WSS — relay initie"| REP style ANS fill:#4A90D9,stroke:#2171B5,color:#fff style REP fill:#9B59B6,stroke:#7D3C98,color:#fff style R1 fill:#E8A838,stroke:#C07C1A,color:#fff style R2 fill:#E8A838,stroke:#C07C1A,color:#fff style B1 fill:#5BA85A,stroke:#3D7A3C,color:#fff style B2 fill:#5BA85A,stroke:#3D7A3C,color:#fff style B3 fill:#5BA85A,stroke:#3D7A3C,color:#fff style B4 fill:#5BA85A,stroke:#3D7A3C,color:#fff

Chaînage — repeater intermédiaire

Un repeater intermédiaire agrège ses relays locaux et se connecte à un repeater central. Topologies multi-niveaux sans exposition directe des DMZ profondes.

graph TB ANS["🎯 Ansible\nOrchestrator"] subgraph TRUST["☁️ Zone de confiance"] REP1["🔀 Repeater Central"] end subgraph DMZ_MID["🔒 DMZ Intermédiaire"] REP2["🔀 Repeater\nIntermédiaire"] R1["🖥️ Relay"] B1["💻 Agent A"] B2["💻 Agent B"] B1 -->|WSS| R1 B2 -->|WSS| R1 R1 -->|WSS| REP2 end subgraph DMZ_DEEP["🔒 DMZ Profonde"] R2["🖥️ Relay"] B3["💻 Agent C"] B4["💻 Agent D"] B3 -->|WSS| R2 B4 -->|WSS| R2 R2 -->|WSS| REP2 end ANS -->|"REST / JWT plugin"| REP1 REP2 -->|"WSS — repeater initie"| REP1 style ANS fill:#4A90D9,stroke:#2171B5,color:#fff style REP1 fill:#9B59B6,stroke:#7D3C98,color:#fff style REP2 fill:#8E44AD,stroke:#6C3483,color:#fff style R1 fill:#E8A838,stroke:#C07C1A,color:#fff style R2 fill:#E8A838,stroke:#C07C1A,color:#fff style B1 fill:#5BA85A,stroke:#3D7A3C,color:#fff style B2 fill:#5BA85A,stroke:#3D7A3C,color:#fff style B3 fill:#5BA85A,stroke:#3D7A3C,color:#fff style B4 fill:#5BA85A,stroke:#3D7A3C,color:#fff

Démarrage rapide

Choisissez votre méthode de déploiement.

Cycle d'enrollment : l'agent génère sa paire RSA-4096 au premier démarrage et la stocke dans /etc/secagent-minion/id_rsa (mode 0600). Il tente immédiatement de s'enroller auprès du serveur — mais le serveur refuse tant que la clef publique n'est pas pré-autorisée. L'agent retente automatiquement avec un backoff exponentiel.
1

Cloner le dépôt et configurer les variables

git clone https://github.com/CCoupel/Ansible-SecAgent.git
cd Ansible-SecAgent/DEPLOYMENT/qualif

export JWT_SECRET_KEY="$(openssl rand -hex 32)"
export ADMIN_TOKEN="$(openssl rand -hex 16)"
export RSA_MASTER_KEY="$(openssl rand -hex 32)"
2

Démarrer le relay (relay-api + NATS + Caddy)

docker compose up -d
docker compose ps   # relay-api, nats, caddy doivent être "Up"
3

Démarrer l'agent sur l'hôte cible

# Sur l'hôte cible — via SSH, provisioning, USB…
docker run -d --name secagent-minion \
  -e RELAY_SERVER_URL=wss://relay.example.com \
  -e RELAY_HOSTNAME=my-host \
  -v /etc/secagent-minion:/etc/secagent-minion \
  ghcr.io/ccoupel/secagent-minion:latest
# → Génère /etc/secagent-minion/id_rsa (RSA-4096)
# → Tente l'enrollment → 403 → retente en backoff
4

Autoriser l'agent (pre-authorize)

# Récupérer la clef publique depuis l'hôte cible
PUB=$(ssh user@my-host openssl rsa -in /etc/secagent-minion/id_rsa -pubout 2>/dev/null)

# Enregistrer la clef sur le relay
docker exec relay-api secagent-server minions authorize my-host --pubkey "$PUB"
# → L'agent retente et se connecte via WSS
5

Lancer un playbook Ansible

ansible-playbook site.yml   # Aucune modification du playbook requise

# Vérifier les agents connectés
docker exec relay-api secagent-server minions list --format table
1

Créer le namespace et les secrets

kubectl create namespace ansible-relay
kubectl create secret generic relay-secrets -n ansible-relay \
  --from-literal=JWT_SECRET_KEY="$(openssl rand -hex 32)" \
  --from-literal=ADMIN_TOKEN="$(openssl rand -hex 16)" \
  --from-literal=RSA_MASTER_KEY="$(openssl rand -hex 32)"
2

Déployer via Helm

helm upgrade --install ansible-relay ./helm/ansible-relay \
  -n ansible-relay \
  --set ingress.host=relay.example.com \
  --set replicaCount=3
3

Vérifier le déploiement

kubectl get pods -n ansible-relay
kubectl exec -n ansible-relay deploy/relay-api -- \
  secagent-server server status --format table
4

Autoriser les agents et gérer les clefs

kubectl exec -n ansible-relay deploy/relay-api -- \
  secagent-server minions authorize my-host --pubkey "$(cat pubkey.pem)"

kubectl exec -n ansible-relay deploy/relay-api -- \
  secagent-server minions list --format table

# Rotation JWT zero-downtime
kubectl exec -n ansible-relay deploy/relay-api -- \
  secagent-server security keys rotate --grace 24h
1

Compiler depuis les sources

git clone https://github.com/CCoupel/Ansible-SecAgent.git
cd Ansible-SecAgent/GO
go build -o secagent-server    ./cmd/server
go build -o secagent-minion    ./cmd/agent
go build -o secagent-inventory ./cmd/inventory
2

Démarrer NATS JetStream

nats-server -js -p 4222 &
# ou : docker run -d --rm -p 4222:4222 nats:latest -js
3

Démarrer le serveur relay

export JWT_SECRET_KEY="$(openssl rand -hex 32)"
export ADMIN_TOKEN="$(openssl rand -hex 16)"
export RSA_MASTER_KEY="$(openssl rand -hex 32)"
export NATS_URL="nats://localhost:4222"

./secagent-server serve --addr :8080
4

Déployer l'agent sur l'hôte cible (systemd)

# Sur l'hôte cible — via SSH, provisioning, USB…
cp secagent-minion /usr/local/bin/
cp secagent-minion.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now secagent-minion
# → Génère /etc/secagent-minion/id_rsa (RSA-4096)
# → Tente l'enrollment → 403 → retente en backoff
5

Autoriser l'agent

PUB=$(ssh user@my-host openssl rsa -in /etc/secagent-minion/id_rsa -pubout 2>/dev/null)

./secagent-server minions authorize my-host --pubkey "$PUB"
# → L'agent retente et se connecte. JWT stocké dans /etc/secagent-minion/token.jwt

Déployer en mode Repeater multi-zones

Le mode Repeater agrège plusieurs relays DMZ derrière un point d'entrée unique. Les agents restent dans leurs zones isolées — Ansible ne voit qu'un seul serveur.

Docker Compose

Démarrage rapide — Docker multi-zones

1

Cloner et configurer

git clone https://github.com/CCoupel/Ansible-SecAgent.git
cd Ansible-SecAgent/DEPLOYMENT/qualif

export JWT_SECRET_KEY="$(openssl rand -hex 32)"
export ADMIN_TOKEN="$(openssl rand -hex 16)"
2

Démarrer la topologie multi-zones

# Proxy central + Relay DMZ1 + Relay DMZ2
docker compose -f docker-compose.proxy.yml up -d

# Vérifier les 3 services
docker compose -f docker-compose.proxy.yml ps
3

Vérifier l'inventaire agrégé

# Le proxy voit les agents des 2 zones
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
  http://localhost:7770/api/agents

# Ou via CLI
docker exec proxy secagent-server inventory list --format table
Binaires

Déploiement manuel — production

1

Démarrer le repeater (point d'entrée central)

JWT_SECRET_KEY="$(openssl rand -hex 32)" \
ADMIN_TOKEN="$(openssl rand -hex 16)" \
PROXY_MODE=true \
./secagent-server
2

Démarrer les relays dans chaque DMZ

# Sur chaque serveur relay en zone isolée
JWT_SECRET_KEY="$(openssl rand -hex 32)" \
ADMIN_TOKEN="$(openssl rand -hex 16)" \
./secagent-server
3

Enregistrer les relays (mode push) ou les laisser se connecter (mode pull)

# Mode push : le repeater contacte les relays
./secagent-server relays add \
  --url http://relay-dmz1:7770 \
  --mode push

# Mode pull : les relays se connectent au repeater
# (configurer RELAY_UPSTREAM_URL sur chaque relay)
export RELAY_UPSTREAM_URL=wss://repeater.example.com/ws/relay
./secagent-server  # sur le relay DMZ
4

Vérifier le statut

./secagent-server relays status --format table
./secagent-server inventory list --format table