Repository navigation
Expand file tree
/
Copy pathcompose.prod.yml
More file actions
370 lines (361 loc) · 14.2 KB
/
Copy pathcompose.prod.yml
File metadata and controls
370 lines (361 loc) · 14.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
# Stack honeypot LaRuche — PRODUCTION.
#
# Variante de production : aucun service `attacker`, et AUCUN `build:` — toutes
# les images sont tirées depuis le registry Docker (qualite863/*). Le serveur n'a
# donc PAS besoin du code source : il suffit de copier ce fichier (+ les fichiers
# de conf montés, voir plus bas) puis `docker compose -f compose.prod.yml up -d`.
#
# IMPORTANT — `qualite863/honeypot-analyzer` est un repo PRIVÉ : le serveur doit
# d'abord s'authentifier au registry avant le pull, sinon le service `analyzer`
# échoue avec « pull access denied » :
# docker login -u <user> registry-1.docker.io
# (les autres images honeypot-ssh/ftp/http sont publiques.)
#
# Fichiers hôte à copier à côté de ce compose (montés en bind) :
# - fluent-bit/fluent-bit.conf, fluent-bit/parsers.conf (conf du collecteur)
# - openobserve/ (provision.py + dashboards/)
# - data/geoip/GeoLite2-City.mmdb, data/geoip/GeoLite2-ASN.mmdb (enrichissement GeoIP)
#
# Ségmentation réseau (US-21) :
# - honeypot-front (bridge) : surface d'exposition — honeypots,
# analyzer (accès sortant enrichissement).
# - honeypot-backend (internal:true) : pipeline de collecte / stockage —
# fluent-bit, openobserve, provisioner.
# Aucun réseau partagé entre un honeypot et OpenObserve : un conteneur honeypot
# compromis ne peut donc pas atteindre les données collectées par le réseau.
# Durcissement appliqué à TOUS les conteneurs : no-new-privileges, read_only
# (+ tmpfs/volumes pour les chemins inscriptibles) et limites de ressources.
services:
honeypot-ssh:
image: qualite863/honeypot-ssh:latest
container_name: honeypot-ssh
restart: unless-stopped
# Deux portes d'entrée distinctes pour pouvoir les différencier dans les
# events (champ dst_port) : 22 standard -> listener interne 2222 (dst_port
# 22), 2222 alternatif -> listener interne 2223 (dst_port 2222). Le honeypot
# écoute les deux ports non privilégiés (process non-root).
ports:
- "22:2222"
- "2222:2223"
environment:
SSH_BIND_HOST: "0.0.0.0"
SSH_BIND_PORT: "2222"
SSH_HOSTNAME: "prod-srv-01"
# Comptes faibles acceptés (US-03). root est toujours refusé par le code.
SSH_ALLOWED_CREDENTIALS: "admin:admin123,admin:P@ssw0rd,ubuntu:ubuntu,user:123456,deploy:deploy2023"
SSH_TARPIT_SECONDS: "2.5"
SSH_LOG_FILE: "/var/log/honeypot/ssh.jsonl"
# Volume commun : le honeypot y écrit ses logs JSONL (RW).
volumes:
- honeypot_logs:/var/log/honeypot
# Surface d'exposition uniquement : aucun accès au réseau backend.
networks:
- honeypot-front
# Durcissement (US-21) : pas de privilèges, racine en lecture seule.
security_opt:
- no-new-privileges:true
read_only: true
tmpfs:
- /tmp
deploy:
resources:
limits:
cpus: "0.50"
memory: 256M
pids: 200
honeypot-ftp:
image: qualite863/honeypot-ftp:latest
container_name: honeypot-ftp
restart: unless-stopped
# Port de contrôle exposé sur 21 (FTP standard, crédibilité) et 2121
# (cible des scripts d'attaque). Le honeypot écoute en interne sur 2121
# (port non privilégié -> process non-root).
ports:
- "21:2121"
- "2121:2121"
# Plage de ports passifs (mode PASV) : indispensable pour que LIST / RETR
# aboutissent. Doit correspondre à FTP_PASV_MIN..MAX.
- "30000-30009:30000-30009"
environment:
FTP_BIND_HOST: "0.0.0.0"
FTP_BIND_PORT: "2121"
FTP_HOSTNAME: "prod-srv-01"
FTP_BANNER: "(vsFTPd 3.0.3)"
# Comptes faibles acceptés. root est toujours refusé par le code.
FTP_ALLOWED_CREDENTIALS: "admin:admin123,ftp:ftp,backup:Backup2024,deploy:deploy2023"
# Identifiants du honeypot SSH, acceptés AUSSI ici pour capturer leur
# réutilisation SSH->FTP (tag ssh_credential_reuse, mouvement latéral).
# À garder synchronisé avec SSH_ALLOWED_CREDENTIALS du service honeypot-ssh.
FTP_SSH_CREDENTIALS: "admin:admin123,admin:P@ssw0rd,ubuntu:ubuntu,user:123456,deploy:deploy2023"
# Accès anonyme autorisé : maximise la capture de la reconnaissance.
FTP_ANONYMOUS: "true"
FTP_DECOY_ROOT: "/srv/ftp"
FTP_PASV_MIN: "30000"
FTP_PASV_MAX: "30009"
# IP publique à annoncer en mode passif derrière un NAT/Docker, à régler
# selon l'hôte/la DMZ (sinon l'adresse locale de la connexion est utilisée).
# FTP_MASQUERADE_ADDRESS: "203.0.113.10"
FTP_LOG_FILE: "/var/log/honeypot/ftp.jsonl"
# Volume commun : le honeypot y écrit ses logs JSONL (RW).
volumes:
- honeypot_logs:/var/log/honeypot
# Arborescence leurre rematérialisée au démarrage. Volume nommé (et non
# tmpfs) car le process tourne en non-root (uid 10001) : un volume hérite
# de l'ownership `honey` de l'image, ce qu'un tmpfs root-owned ne permet
# pas (os.utime échoue alors avec EPERM sous read_only).
- ftp_decoy:/srv/ftp
# Surface d'exposition uniquement : aucun accès au réseau backend.
networks:
- honeypot-front
# Durcissement (US-21) : pas de privilèges, racine en lecture seule.
security_opt:
- no-new-privileges:true
read_only: true
tmpfs:
- /tmp
deploy:
resources:
limits:
cpus: "0.50"
memory: 256M
pids: 200
# Honeypot HTTP (EPIC-2) : émulation WordPress.
honeypot-http:
image: qualite863/honeypot-http:latest
container_name: honeypot-http
restart: unless-stopped
# N'est plus exposé directement : seul le reverse-proxy Apache (honeypot-proxy)
# le joint, en interne via honeypot-front (port 8080, process non-root).
expose:
- "8080"
environment:
HONEYPOT_HOST: "prod-srv-01"
HTTP_LOG_FILE: "/var/log/honeypot/http.jsonl"
# Comptes faibles acceptés par le faux login WordPress (US-09 / hardening).
WP_ALLOWED_CREDENTIALS: "admin:admin,admin:admin123,administrator:password123"
# Volume commun : le honeypot y écrit ses logs JSONL (RW).
volumes:
- honeypot_logs:/var/log/honeypot
# Surface d'exposition uniquement : aucun accès au réseau backend.
networks:
- honeypot-front
# Durcissement (US-21) : pas de privilèges, racine en lecture seule.
security_opt:
- no-new-privileges:true
read_only: true
tmpfs:
- /tmp
deploy:
resources:
limits:
cpus: "0.50"
memory: 256M
pids: 200
# Reverse-proxy Apache (anti-détection) : sert les assets statiques WordPress
# RÉELS (vrais ETag/Last-Modified) et émet des en-têtes / pages d'erreur Apache
# authentiques ; proxifie le dynamique vers honeypot-http. Seule surface HTTP
# publique. Image autonome (config + assets bakés) — rien à monter côté hôte.
honeypot-proxy:
image: qualite863/honeypot-proxy:latest
container_name: honeypot-proxy
restart: unless-stopped
depends_on:
- honeypot-http
# 80 (HTTP standard) et 8080 (cible des scripts d'attaque) -> Apache:80 interne.
ports:
- "80:80"
- "8080:80"
networks:
- honeypot-front
security_opt:
- no-new-privileges:true
read_only: true
# Apache doit pouvoir écrire pid / mutex / logs sous une racine en lecture seule.
tmpfs:
- /var/run/apache2
- /var/lock/apache2
- /var/log/apache2
- /tmp
deploy:
resources:
limits:
cpus: "0.50"
memory: 256M
pids: 200
# US-17 : centralisation des logs via Fluent Bit.
fluent-bit:
image: fluent/fluent-bit:3.1.9
container_name: fluent-bit
restart: unless-stopped
depends_on:
- honeypot-ssh
- honeypot-ftp
- honeypot-http
- openobserve
- analyzer
environment:
# Identifiants d'ingestion OpenObserve (US-19), injectés dans la conf via ${...}.
OPENOBSERVE_USER: "${OPENOBSERVE_USER:-admin@laruche.local}"
OPENOBSERVE_PASSWORD: "${OPENOBSERVE_PASSWORD:-Honeypot2026!}"
volumes:
# Logs honeypot en LECTURE SEULE (le collecteur ne doit pas y écrire).
- honeypot_logs:/var/log/honeypot:ro
# :z = relabel SELinux (Fedora/RHEL) sinon le conteneur ne peut pas lire la config.
- ./fluent-bit/fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf:ro,z
- ./fluent-bit/parsers.conf:/fluent-bit/etc/parsers.conf:ro,z
# Suivi des offsets de tail (volume inscriptible dédié).
- fluentbit_state:/var/lib/fluent-bit/state
# Endpoint santé/métriques interne de Fluent Bit.
ports:
- "2020:2020"
# Pipeline de collecte : forwarde vers OpenObserve par le réseau backend.
networks:
- honeypot-backend
# Durcissement (US-21) : pas de privilèges, racine en lecture seule.
security_opt:
- no-new-privileges:true
read_only: true
tmpfs:
- /tmp
deploy:
resources:
limits:
cpus: "0.50"
memory: 256M
pids: 200
# US-19 : stockage + exploration des événements (dashboards research, pas de SIEM).
openobserve:
# Épinglé par digest pour la reproductibilité (US-26).
image: public.ecr.aws/zinclabs/openobserve@sha256:05deec62c1b643a87c31cf7f1ebeb9d4f1a4a69b012fc78464c2e00a37e17570
container_name: openobserve
restart: unless-stopped
environment:
ZO_ROOT_USER_EMAIL: "${OPENOBSERVE_USER:-admin@laruche.local}"
ZO_ROOT_USER_PASSWORD: "${OPENOBSERVE_PASSWORD:-Honeypot2026!}"
ZO_DATA_DIR: "/data"
# Accès aux dashboards depuis l'hôte, restreint à la loopback (esprit DMZ
# US-20). Docker ne publie PAS les ports d'un conteneur uniquement attaché
# à un réseau `internal` -> openobserve est aussi sur `dashboard-edge`.
ports:
- "127.0.0.1:5080:5080"
volumes:
- openobserve_data:/data
# Stockage des données collectées : réseau backend interne (fluent-bit /
# provisioner) + dashboard-edge (publication du port hôte uniquement).
# Les honeypots ne sont sur aucun de ces deux réseaux -> inatteignable
# depuis un conteneur honeypot compromis.
networks:
- honeypot-backend
- dashboard-edge
# Durcissement (US-21) : pas de privilèges, racine en lecture seule.
security_opt:
- no-new-privileges:true
read_only: true
tmpfs:
- /tmp
deploy:
resources:
limits:
cpus: "2.00"
memory: 1G
pids: 1024
# US-19 : provisioning automatique des dashboards OpenObserve au démarrage.
# Conteneur éphémère : attend qu'OpenObserve réponde, (ré)importe les
# dashboards/*.json de façon idempotente, puis s'arrête.
dashboard-provisioner:
image: python:3.12-slim-bookworm
container_name: dashboard-provisioner
depends_on:
- openobserve
environment:
OPENOBSERVE_URL: "http://openobserve:5080"
OPENOBSERVE_USER: "${OPENOBSERVE_USER:-admin@laruche.local}"
OPENOBSERVE_PASSWORD: "${OPENOBSERVE_PASSWORD:-Honeypot2026!}"
DASHBOARDS_DIR: "/provision/dashboards"
volumes:
# :ro,z = lecture seule + relabel SELinux (Fedora/RHEL).
- ./openobserve:/provision:ro,z
command: ["python", "/provision/provision.py"]
# Tâche one-shot : pas de redémarrage en boucle une fois l'import fait.
restart: "no"
# Joint OpenObserve sur le réseau backend pour pousser les dashboards.
networks:
- honeypot-backend
# Durcissement (US-21) : pas de privilèges, racine en lecture seule.
security_opt:
- no-new-privileges:true
read_only: true
tmpfs:
- /tmp
deploy:
resources:
limits:
cpus: "0.50"
memory: 256M
pids: 200
# Analyzer (EPIC-4) : enrichit les events (GeoIP / AbuseIPDB / GreyNoise) +
# profil comportemental, et écrit les events enrichis pour Fluent Bit -> OpenObserve.
analyzer:
# Repo PRIVÉ : le serveur doit être authentifié (`docker login`) pour le
# tirer. Voir la note en tête de fichier.
image: qualite863/honeypot-analyzer:latest
container_name: analyzer
restart: unless-stopped
depends_on:
- honeypot-ssh
- honeypot-ftp
- honeypot-http
environment:
ANALYZER_LOG_DIR: "/var/log/honeypot"
ANALYZER_ENRICHED_OUTPUT: "/var/log/honeypot/enriched/events.jsonl"
ANALYZER_INTERVAL: "10"
# Clés API fournies par l'école (vides => enrichissement neutre, sans appel réseau).
ABUSEIPDB_API_KEY: "${ABUSEIPDB_API_KEY:-}"
GREYNOISE_API_KEY: "${GREYNOISE_API_KEY:-}"
GEOIP_CITY_DB: "/data/geoip/GeoLite2-City.mmdb"
GEOIP_ASN_DB: "/data/geoip/GeoLite2-ASN.mmdb"
volumes:
# Lit le JSONL brut et écrit l'enrichi dans le même volume (sous enriched/).
- honeypot_logs:/var/log/honeypot
# Bases MaxMind GeoLite2 à fournir (sinon GeoIP renvoie des champs vides).
- ./data/geoip:/data/geoip:ro
# Cache SQLite AbuseIPDB / GreyNoise (/app/.cache). Volume nommé pour
# rester inscriptible par l'uid non-root sous read_only (cf. honeypot-ftp).
- analyzer_cache:/app/.cache
# Front : besoin d'un accès sortant pour les API d'enrichissement
# (AbuseIPDB / GreyNoise). N'accède jamais à OpenObserve par le réseau.
networks:
- honeypot-front
# Durcissement (US-21) : pas de privilèges, racine en lecture seule.
security_opt:
- no-new-privileges:true
read_only: true
tmpfs:
- /tmp
deploy:
resources:
limits:
cpus: "1.00"
memory: 512M
pids: 256
volumes:
honeypot_logs:
fluentbit_state:
openobserve_data:
# Chemins inscriptibles isolés sous read_only (US-21), ownership non-root.
ftp_decoy:
analyzer_cache:
# Ségmentation réseau (US-21).
networks:
# Surface exposée : honeypots, analyzer.
honeypot-front:
driver: bridge
# Pipeline de collecte/stockage isolé du monde extérieur (pas de route
# sortante) : fluent-bit, openobserve, dashboard-provisioner.
honeypot-backend:
internal: true
# Réseau bridge minimal dédié à la SEULE publication du port dashboard
# d'OpenObserve vers l'hôte (impossible depuis un réseau `internal`).
# Aucun honeypot n'y est attaché : la segmentation reste intacte.
dashboard-edge:
driver: bridge