
Honeypot SSH/Telnet à interaction moyenne construit avec Cowrie, Loki, Promtail et Grafana - provisionné sur DigitalOcean via Terraform avec un pipeline de validation GitLab CI.
Une pile de honeypot SSH/Telnet déployée sur un droplet DigitalOcean en utilisant Terraform. Les attaques sont capturées par Cowrie, stockées dans Loki, et visualisées dans Grafana avec une carte en temps réel des origines des attaques.
honeypot/
├── cowrie/
│ └── etc/
│ ├── cowrie.cfg # Cowrie honeypot configuration
│ └── userdb.txt # Accepted and rejected fake credentials
├── geoip/
│ └── .gitkeep # Placeholder - MMDB files are gitignored
├── grafana/
│ ├── provisioning/
│ │ ├── dashboards/
│ │ │ ├── dashboards.yml # Provisioning: dashboard provider
│ │ │ └── honeypot-dashboard.json # Pre-built Attack Monitor dashboard
│ │ └── datasources/
│ │ └── loki.yml # Auto-provisioned Loki datasource
│ └── grafana.ini # Grafana server settings
├── loki/
│ └── config.yml # Loki single-binary config and retention
├── promtail/
│ └── config.yml # Promtail scrape and GeoIP pipeline
├── scripts/
│ ├── geoip-update.sh # Download or refresh GeoIP database
│ └── setup-firewall.sh # Host UFW rules for honeypot ports
├── terraform/
│ ├── templates/
│ │ ├── cloud-init.yaml.tftpl # Droplet first-boot script (Terraform-templated)
│ │ └── env.tftpl # .env lines embedded via Terraform
│ ├── backend.tf # Terraform backend config
│ ├── main.tf # Droplet, SSH key, firewall, cloud-init
│ ├── outputs.tf # IPs and helpful post-apply values
│ ├── terraform.tfvars.example # Example variable values (copy to terraform.tfvars)
│ ├── variables.tf # Terraform input variables
│ └── versions.tf # Terraform and provider version constraints
├── .env.example # Example environment file for manual setup
├── .gitignore # Ignored paths and files
├── .gitlab-ci.yml # CI/CD pipeline: fmt, validate, Checkov
├── docker-compose.yml # Docker Compose file for the honeypot stack
├── LICENSE # GPLv2 license
├── manual-deployment.sh # Legacy VM bootstrap without Terraform
└── README.md # Project documentation
## Prérequis matériels
> Remarque : Ces exigences sont basées sur le plan Basic Droplet de DigitalOcean de mai 2026 et sont les exigences minimales pour exécuter le projet.
- Provider : DigitalOcean
- Plan : Basic Droplet - 1 Intel vCPU
- RAM : 1 Go (+2 Go swap)
- Stockage : 35 Go NVMe SSD
- OS : Ubuntu 24.04 LTS
## Installation et configuration
>Remarque : Après l'installation, il est recommandé d'ouvrir un nouveau terminal et de vérifier que vous pouvez toujours vous connecter en SSH sur le port `ADMIN_SSH_PORT` (par défaut : 2022) avant de fermer votre session d'origine.
### Déploiement avec Terraform (recommandé)
1. Créez un jeton API DigitalOcean avec des autorisations de lecture/écriture :
- Connectez-vous à DigitalOcean et naviguez vers Account > API > Tokens > Generate New Token.
- Nommez votre jeton (par exemple, « Cowrie Honeypot ») et sélectionnez les autorisations « Full Access ».
- Cliquez sur « Generate Token » et copiez la valeur du jeton dans un endroit sécurisé (vous ne pourrez plus le voir ensuite).
2. Créez une paire de clés SSH sur votre machine locale et copiez le chemin de la clé publique.
3. Accédez au répertoire `terraform`, puis copiez et modifiez le fichier de variables :
> Remarque : `do_token`, `ssh_public_key_path` et `grafana_admin_password` doivent être configurés au minimum dans `terraform.tfvars` avant d'appliquer.```bash
cd terraform
cp terraform.tfvars.example terraform.tfvars
Remarque : Terraform doit être installé sur votre machine locale. Consultez le Guide d'installation de Terraform pour les instructions.```bash terraform init # Initialize Terraform and download providers terraform apply
5. Si Test - Redéployer avec remplacement :```bash
terraform apply -replace="digitalocean_droplet.honeypot"
### Déploiement manuel (Legacy)
1. Créez un droplet DigitalOcean avec les exigences matérielles ci-dessus et votre clé SSH.
2. Connectez-vous en SSH au droplet ou utilisez la console web DigitalOcean et exécutez les commandes suivantes :```bash
ssh root@<your-droplet-ip> # If using DigitalOcean web console, skip this command
git clone https://gitlab.com/Oseguera12/cowrie-honeypot-digitalocean.git /opt/honeypot
cd /opt/honeypot
cp .env.example .env
nano .env
bash manual-deployment.sh
Note : Par défaut, Terraform écrit l'état dans
terraform/terraform.tfstatesur votre machine locale. Ce fichier contient des valeurs de sortie sensibles (IP du droplet, mot de passe Grafana, empreinte de clé SSH) et ne doit jamais être commité..gitignorecouvre*.tfstateet*.tfstate.*.
Risques de l'état local :
apply simultanées peuvent corrompre le fichierPour tout ce qui dépasse un laboratoire personnel, passez à un backend distant. terraform/backend.tf contient une configuration DigitalOcean Spaces commentée (compatible S3).
Pour activer le backend distant :
terraform.tfvars) : ```bash
export AWS_ACCESS_KEY_ID=
export AWS_SECRET_ACCESS_KEY=
backend "s3" dans terraform/backend.tf et remplissez le nom de votre bucket et le point de terminaison de la région.terraform init -migrate-state pour déplacer l'état local existant vers Spaces..gitlab-ci.yml exécute trois jobs à chaque push dans une seule étape validate :
Remarque :
terraform:fmtetterraform:validatebloquent le pipeline en cas d'échec.checkov:scanest réglé surallow_failure: truecar certains résultats sont des compromis intentionnels. Supprimez les résultats acceptables spécifiques avec des commentaires en ligne# checkov:skip=CKXXXplutôt que de désactiver complètement le job.
CKV_DIO_4 ("Assurez-vous que l'entrée du pare-feu n'est pas grande ouverte") se déclenche une fois contre l'ensemble de la ressource digitalocean_firewall. Il est supprimé avec un seul commentaire # checkov:skip=CKV_DIO_4: placé à l'intérieur du bloc de ressource dans terraform/main.tf. Le tableau ci-dessous documente pourquoi chaque port ouvert est intentionnel.
flowchart TB internet((Internet))
subgraph tf["Terraform"] fw[DigitalOcean Cloud Firewall] droplet[Ubuntu 24.04 droplet] fw --> droplet end
subgraph compose["Docker Compose on droplet"] cowrie["Cowrie honeypot — SSH on port 22, Telnet on port 23"] promtail[Promtail with GeoIP labels] loki[Loki log store] grafana["Grafana — host :3000"] cowrie -->|JSON logs from ./data/cowrie-logs| promtail --> loki --> grafana end
internet -->|22/23 honeypot| fw internet -->|Admin SSH :2022| fw internet -->|Grafana :3000| fw droplet --> compose
## Observations
Données capturées sur 5 jours de déploiement en direct (2026-05-05 à 2026-05-09) :
| Métrique | Valeur |
|---------------------------|--------|
| Connexions totales | 41,700 |
| Tentatives de connexion | 15,400 |
| Connexions réussies | 868 |
| Commandes exécutées | 836 |
| Fichiers téléchargés | 6 |
| Pays sources uniques | 106 |
### Principaux pays d'attaque
| Pays | Connexions |
|--------------|------------|
| Allemagne | 9,768 |
| Pays-Bas | 9,110 |
| États-Unis | 7,706 |
| Royaume-Uni | 3,858 |
| Singapour | 1,770 |
| Belgique | 1,422 |
### Schémas d'identifiants
Le nom d'utilisateur le plus souvent tenté était `root` avec 3,866 tentatives, suivi de `admin` (728) et `user` (494), reflétant des scanners automatisés ciblant les identifiants par défaut et les comptes de service connus. Le mot de passe le plus courant était `123456` (1,480 tentatives) suivi de `123` et `12345`, ce qui est cohérent avec les outils de force brute basés sur des dictionnaires.
### Comportement de l'attaquant dans le faux shell
868 tentatives de connexion ont réussi avec l'ensemble d'identifiants fictifs. Parmi ces sessions, 836 commandes ont été exécutées. La commande la plus courante était `uname -s -v -n -r -m` (360 exécutions) — une commande standard de reconnaissance système exécutée par des scripts automatisés de post-exploitation pour identifier le système d'exploitation et l'architecture cibles avant de déployer une charge utile. Les autres commandes observées incluaient `export HISTFILE=/dev/null` pour désactiver l'enregistrement de l'historique du shell, et `export HISTSAVE=/dev/null`, indiquant que les attaquants tentaient activement de dissimuler leurs traces même à l'intérieur de ce qu'ils pensaient être un système compromis.
### Enseignement clé
Le volume de trafic de balayage automatisé — 41,700 connexions en 5 jours depuis 106 pays — confirme que tout service SSH accessible publiquement fait face à des tentatives constantes de force brute dans les heures suivant son exposition. Le comportement consistant à exécuter immédiatement `uname` suivi de commandes de suppression d'historique est cohérent avec des frameworks de post-exploitation automatisés fonctionnant avec une intervention humaine minimale.
## Utilisation
### Utilisation du tableau de bord Grafana
> Note : Les identifiants du tableau de bord sont définis dans terraform.tfvars ou .env (pour un déploiement manuel)
Le tableau de bord est accessible à l'adresse :```
http://<your-droplet-ip>:3000
Connectez-vous avec admin / <GRAFANA_ADMIN_PASSWORD>.
cowrie/etc/userdb.txt – contient les identifiants acceptés par le honeypot.
Modifiez ce fichier pour ajuster les identifiants du honeypot
Les connexions acceptées placent l'attaquant dans un shell factice où toutes les commandes sont enregistrées
Les entrées rejetées sont enregistrées comme des tentatives échouées
Cette section détaille les décisions prises et les problèmes rencontrés lors du développement de ce projet. Elle sert de reflet du processus de développement et des leçons apprises. Passez à la section Considérations de sécurité si vous souhaitez seulement savoir comment utiliser le projet.
Pourquoi l'hébergement dans le cloud ?
Pourquoi DigitalOcean ?
Pourquoi Terraform ?
manual-deployment.sh, est destinée aux utilisateurs qui ne sont pas expérimentés avec Terraform ou qui préfèrent ne pas l'utiliser. Cette méthode nécessite de provisionner un droplet, de s'y connecter, de cloner le dépôt et d'exécuter le script. Terraform permet une itération plus rapide : modifier la base de code et redéployer sans répéter chaque étape manuelle depuis votre machine locale. Il facilite également la destruction de l'infrastructure pour repartir de zéro. Si le projet passe d'un seul honeypot à un honeynet, Terraform s'adapte plus proprement que le provisionnement manuel ou le script legacy seul.Pourquoi Cowrie ?
Pourquoi Loki ?
Pourquoi Grafana ?
Pourquoi Promtail ?
Pourquoi GeoIP ?
scripts/geoip-update.sh : pas de compte ni de clé API, ce qui simplifie le déploiement pour quiconque clone le dépôt. L'étape GeoIP intégrée de Promtail lit le fichier MMDB local et ajoute des étiquettes pour le panneau Geomap de Grafana.Aperçu étape par étape de ce qui se produit lorsque le honeypot est configuré à l'aide de Terraform ou du script de déploiement manuel.
Sur votre machine :
terraform/terraform.tfvars : jeton API, chemin de la clé publique SSH, mot de passe Grafana, URL/branche du dépôt, manage_do_firewall optionnelterraform init : installe le fournisseur DigitalOcean localementterraform apply : Terraform construit un plan, puis crée ou met à jour les ressourcesDans DigitalOcean :
droplet_image, droplet_size, region, etc.user_data est défini sur le cloud-init rendu : au premier démarrage, la VM obtient le port SSH admin, le repo_url / repo_branch git, et une copie encodée en base64 du .env généré (mot de passe Grafana et ADMIN_SSH_PORT de env.tftpl)manage_do_firewall est vrai) :
terraform/main.tf)Sur le droplet, cloud-init s'exécute automatiquement :
package_update / package_upgrade, puis installe les paquets listés (curl, git, ufw, …). Au début du démarrage, write_files crée /root/honeypot.env (le env.tftpl rendu par Terraform en base64 : mot de passe Grafana, ADMIN_SSH_PORT, etc.)runcmd (ordre dans terraform/templates/cloud-init.yaml.tftpl) : définit iptables / ip6tables sur les backends legacy/swapfile, fstab, swappiness)Dans Docker Compose :
/ready sur le port 3100 dans le conteneur ; sur l'hôte seulement 127.0.0.1:3100)depends_on dans docker-compose.yml), puis démarrentdata/cowrie-logs pour PromtailL'utilisateur crée la VM, se connecte en SSH, et exécute le script depuis le dépôt cloné.
manual-deployment.sh effectue les opérations suivantes :
.envapt update & apt upgradeufw et iptablessshd sur ADMIN_SSH_PORT et désactivation de ssh.socketscripts/setup-firewall.shscripts/geoip-update.sh et cron mensueldocker compose pull / up -d, puis le script attend les checks de santé de Loki et Grafana et imprime un résuméSolutions envisagées :
Résolution :
Compromis :
Leçons apprises :
docker compose up échouait car le port 22 était déjà utilisé et ne pouvait pas être lié par Cowrie.Solutions envisagées :
sshd était mal configuré ou écoutait sur le mauvais portRésolution :
sshd est déplacé sur ADMIN_SSH_PORT (par défaut 2022) et redémarré en premier, puis ssh.socket est arrêté et désactivé pour libérer le port 22 afin que Docker puisse le lier pour Cowrie. Les règles UFW sont appliquées après les deux étapes pour autoriser le nouveau port admin.Compromis :
Leçons apprises :
ssh.socket est une unité socket systemd qui maintient le port 22 réservé pour une activation SSH à la demande. Il doit être arrêté et désactivé avant que Docker puisse lier le port hôte 22 pour Cowrie. S'il est toujours actif, docker compose up échoue avec "adresse déjà utilisée".Fonctionnalité GeoIP
HTTPS pour Grafana
Fonctionnalité de serveur proxy
GNU General Public License Version 2.0 (GPLv2) Voir le texte complet de la licence dans le fichier LICENSE.
| Job | Tool | Purpose |
|---|
terraform:fmt | hashicorp/terraform:1.8 | Formatage : échoue si un fichier nécessite terraform fmt |
terraform:validate | hashicorp/terraform:1.8 | Validité de la configuration sans contacter DigitalOcean |
checkov:scan | bridgecrew/checkov:latest | Mauvaises configurations IaC dans le code Terraform |
| Check ID | Resource | Finding | Decision |
|---|
CKV_DIO_4 | digitalocean_firewall.honeypot — admin SSH inbound | Port SSH admin ouvert à 0.0.0.0/0 | Accepté — restreindre à une IP fixe est peu pratique pour un laboratoire portable ; recommandé en production |
CKV_DIO_4 | digitalocean_firewall.honeypot — port 22 inbound | Port 22 ouvert à 0.0.0.0/0 | Intentionnel — c'est la surface du honeypot SSH ; restreindre la source va à l'encontre du but |
CKV_DIO_4 | digitalocean_firewall.honeypot — port 23 inbound | Port 23 ouvert à 0.0.0.0/0 | Intentionnel — surface du honeypot Telnet ; même raisonnement que pour le port 22 |
CKV_DIO_4 | digitalocean_firewall.honeypot — port 3000 inbound | Grafana HTTP exposé à 0.0.0.0/0 | Accepté pour l'accessibilité du laboratoire — limitation connue documentée dans Security Considerations ; les déploiements en production devraient restreindre à une IP connue ou passer par un proxy via HTTPS sur le port 443 |
| Panneau | Description |
|---|
| Carte mondiale des attaques | Géocarte avec couches de chaleur + marqueurs montrant l'origine de chaque connexion |
| Connexions totales | Nombre de sessions honeypot entrantes dans la plage de temps sélectionnée |
| Tentatives de connexion | Nombre total de tentatives de force brute d'identifiants |
| Connexions réussies | Attaquants qui ont correspondance avec les identifiants de userdb.txt |
| Commandes exécutées | Commandes shell exécutées dans le shell factice |
| Fichiers téléchargés | Logiciels malveillants/scripts capturés via wget/curl |
| Taux de connexion | Série temporelle : connexions/s, échecs de connexion/s, succès/s |
| Noms d'utilisateur les plus fréquents | Noms d'utilisateur SSH les plus testés |
| Mots de passe les plus fréquents | Mots de passe les plus testés |
| Commandes les plus fréquentes | Commandes shell les plus exécutées |
| Attaques par pays | Tableau des connexions par pays |
| Événements récents | Flux en direct des derniers événements |
| Téléchargements de fichiers | Tableau de chaque fichier qu'un attaquant a tenté de récupérer |
systemctl enable --now dockersshd sur ADMIN_SSH_PORT ; désactive ssh.socket pour que le port hôte 22 soit libre pour Cowriegit clone depuis repo_url / repo_branch dans /opt/honeypot (doit inclure docker-compose.yml et les configurations).env sur le disque : déplace /root/honeypot.env vers /opt/honeypot/.env (mode 600)scripts/setup-firewall.sh (UFW pour SSH admin, 22, 23, 3000)scripts/geoip-update.sh (DB-IP City Lite dans geoip/) ; ajoute une ligne cron mensuelle pour le même scriptdata/cowrie-logs et data/cowrie-dl avec les permissions attendues par Compose/opt/honeypot, docker compose pull puis docker compose up -d