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.
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.
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.
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.
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.
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.
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 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.
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.
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-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.
Un seul serveur relay pour démarrer. Idéal pour les petites infrastructures ou pour valider la solution en quelques minutes.
Plusieurs nodes relay derrière un load balancer. Les agents se connectent à n'importe quel node, le routage est transparent via cluster NATS.
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.
Tous vos playbooks, rôles et collections Ansible Galaxy fonctionnent sans modification. Changez juste connection: relay dans ansible.cfg.
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.
Le plugin inventory liste automatiquement tous les agents connectés avec leurs facts. Vos inventaires statiques restent compatibles en parallèle.
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-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.
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.
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).
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.
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.
Rotation des secrets JWT sans interruption de service. Grace period configurable : les anciens tokens restent valides pendant la migration.
WSS (WebSocket over TLS) et HTTPS sur toutes les connexions. Terminaison TLS via Caddy. Aucun plain HTTP accepté.
Le mot de passe sudo/su est transmis via stdin chiffré et masqué dans tous les logs — agent, serveur et plugins Ansible.
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.
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 |
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.
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.
relay dédié (distinct de agent/plugin/admin)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).
POST /api/admin/relayssecagent-server relays list|add|remove|statusGET /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.
/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.
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é).
Topologie qualif incluse : proxy + 2 relays DMZ + agents simulés, réseaux isolés. Prêt en une commande.
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.
shell — exécute un script systèmefile — écrit dans un fichier de logapi — appel HTTP avec méthode configurablewebhook — HTTP POST signé HMAC-SHA256
agent.connected — connexion WSS établieagent.disconnected — déconnexion WSStask.completed — tâche terminée avec succèstask.failed — tâche en erreur
Injectez des variables dynamiques dans vos actions : {{.AgentID}}, {{.Hostname}}, {{.Event}}, {{.Timestamp}}, {{.Status}}
Queue interne de 1000 entrées, non-bloquant. Hot-reload via SIGHUP sans redémarrer le serveur. Log complet dans la table action_log.
Ansible-SecAgent est développé par phases incrémentales, chacune validée par tests automatisés et audit sécurité avant déploiement.
internal/hooks/) — actions déclenchées par événements agentwebhook (HMAC-SHA256), shell (subprocess + env SECAGENT_*), file (append O_CREATE), api (méthode configurable){{hostname}}, {{event}}, {{timestamp}}, {{status}}, {{enrolled_at}}secagent-server hooks status et hooks log --limit --event --hostname --formataction_log — trace toutes les exécutions d'actions en DBGET /api/admin/hooks/log — consultation via API adminPROXY_MODE=true) — agrégation multi-zones (DMZ, cloud privé, edge) en un point d'entrée uniqueWSS /ws/relay (JWT rôle relay)GET /api/inventory agrège agents de tous les relays + agents directsX-Relay-Hops)/api/admin/relayssecagent-server relays list|get|status|add|remove
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.
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
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
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
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
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>
Les phases suivantes sont spécifiées dans ARCHITECTURE.md et prêtes à démarrer.
Hooks configurables via JSON (shell, webhook, file, api), dispatcher asynchrone, hot-reload SIGHUP, CLI hooks status/log, table action_log.
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.
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.
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.
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).
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).
Un relay central agrège les agents de votre infrastructure.
Le repeater initie les connexions REST vers chaque relay DMZ. Convient quand le repeater a accès aux zones isolées.
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).
Un repeater intermédiaire agrège ses relays locaux et se connecte à un repeater central. Topologies multi-niveaux sans exposition directe des DMZ profondes.
Choisissez votre méthode de déploiement.
/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.
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)"
docker compose up -d
docker compose ps # relay-api, nats, caddy doivent être "Up"
# 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
# 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
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
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)"
helm upgrade --install ansible-relay ./helm/ansible-relay \
-n ansible-relay \
--set ingress.host=relay.example.com \
--set replicaCount=3
kubectl get pods -n ansible-relay
kubectl exec -n ansible-relay deploy/relay-api -- \
secagent-server server status --format table
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
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
nats-server -js -p 4222 &
# ou : docker run -d --rm -p 4222:4222 nats:latest -js
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
# 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
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
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.
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)"
# 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
# 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
JWT_SECRET_KEY="$(openssl rand -hex 32)" \
ADMIN_TOKEN="$(openssl rand -hex 16)" \
PROXY_MODE=true \
./secagent-server
# Sur chaque serveur relay en zone isolée
JWT_SECRET_KEY="$(openssl rand -hex 32)" \
ADMIN_TOKEN="$(openssl rand -hex 16)" \
./secagent-server
# 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
./secagent-server relays status --format table
./secagent-server inventory list --format table