chore: update

This commit is contained in:
Xor290
2026-08-08 12:04:57 +02:00
parent 56aef1fa9b
commit 8e36acd812
76 changed files with 12710 additions and 29 deletions
+107
View File
@@ -0,0 +1,107 @@
<!-- Configuration centralisée distribuée aux agents Wazuh -->
<!-- Déposée dans /var/ossec/etc/shared/default/agent.conf sur le manager -->
<agent_config>
<!-- ── Logs nginx ──────────────────────────────────────────────────── -->
<localfile>
<log_format>apache</log_format>
<location>/var/log/nginx/access.log</location>
</localfile>
<localfile>
<log_format>syslog</log_format>
<location>/var/log/nginx/error.log</location>
</localfile>
<!-- ── Auth system ───────────────────────────────────────────────── -->
<localfile>
<log_format>syslog</log_format>
<location>/var/log/auth.log</location>
</localfile>
<!-- ── Syslog ────────────────────────────────────────────────────── -->
<localfile>
<log_format>syslog</log_format>
<location>/var/log/syslog</location>
</localfile>
<!-- ── Unattended-upgrades (patchs de sécurité auto) ───────────────── -->
<localfile>
<log_format>syslog</log_format>
<location>/var/log/unattended-upgrades/unattended-upgrades.log</location>
</localfile>
<!-- ── auditd (regles CIS 6.2.3.x deployees via ansible/hardening) ─── -->
<localfile>
<log_format>audit</log_format>
<location>/var/log/audit/audit.log</location>
</localfile>
</agent_config>
<!-- ── Logs backend Go (prod-mln) ────────────────────────────────── -->
<!-- "command" (pas full_command) = chaque ligne de docker logs → event séparé -->
<!-- Chaque ligne [GIN] devient son propre event → decoder peut extraire srcip/url/status -->
<agent_config name="prod-mln">
<localfile>
<log_format>command</log_format>
<command>docker logs --since 65s gestion-backend 2>&1</command>
<alias>backend</alias>
<frequency>60</frequency>
</localfile>
<!-- ── Logs WAF (nginx+ModSecurity/CRS, conteneur gestion-waf) ──── -->
<!-- nginx du conteneur ecrit dans des fichiers (pas stdout) -> docker logs ne capte rien. -->
<!-- Ces fichiers sont montes sur l'hote (/var/log/waf/*), on les lit directement. -->
<localfile>
<log_format>syslog</log_format>
<location>/var/log/waf/nginx/access.log</location>
</localfile>
<localfile>
<log_format>syslog</log_format>
<location>/var/log/waf/nginx/error.log</location>
</localfile>
<localfile>
<log_format>json</log_format>
<location>/var/log/waf/modsec/modsec_audit.log</location>
</localfile>
</agent_config>
<!-- ── Logs backend Go (pre-prod-mln) ───────────────────────────── -->
<agent_config name="pre-prod-mln">
<localfile>
<log_format>command</log_format>
<command>docker logs --since 65s gestion-backend 2>&1</command>
<alias>backend</alias>
<frequency>60</frequency>
</localfile>
</agent_config>
<!-- ── Logs WAF (nginx+ModSecurity/CRS, conteneur gestion-waf) — replica-prod-uber ── -->
<agent_config name="replica-prod-uber">
<localfile>
<log_format>syslog</log_format>
<location>/var/log/waf/nginx/access.log</location>
</localfile>
<localfile>
<log_format>syslog</log_format>
<location>/var/log/waf/nginx/error.log</location>
</localfile>
<localfile>
<log_format>json</log_format>
<location>/var/log/waf/modsec/modsec_audit.log</location>
</localfile>
</agent_config>
<!-- ── Logs HAProxy (load-balancer, /var/log/haproxy.log via rsyslog local0) ── -->
<agent_config name="load-balancer">
<localfile>
<log_format>syslog</log_format>
<location>/var/log/haproxy.log</location>
</localfile>
</agent_config>
File diff suppressed because it is too large Load Diff
+6 -1
View File
@@ -24,7 +24,7 @@
# ansible-playbook hardening/playbook-apparmor-hardening.yml --limit pre-prod-uber
- name: Durcissement AppArmor (CIS Ubuntu 24.04)
hosts: prod,pre-prod,bdd-redis-prod,replica-prod,infra,infra-runner,infra-utils,load-balancer
hosts: prod,pre-prod,bdd-redis-prod,replica-prod,infra,infra-runner,infra-utils,ota-uber,load-balancer
become: true
serial: 1
any_errors_fatal: true
@@ -69,6 +69,11 @@
cmd: aa-enforce /etc/apparmor.d/*
changed_when: true
- name: Recharger apparmor (aa-enforce seul ne suffit pas toujours a faire prendre en compte le changement par le kernel)
ansible.builtin.systemd:
name: apparmor
state: reloaded
- name: Verifier l'etat final (apparmor_status)
ansible.builtin.command:
cmd: apparmor_status
+13 -5
View File
@@ -1,5 +1,5 @@
- name: SSH HARDENNING
hosts: load-balancer
hosts: prod,pre-prod,replica-prod,infra,infra-runner,infra-utils,ota-uber,load-balancer,!backup-mln
become: true
gather_facts: true
tasks:
@@ -24,7 +24,7 @@
- name: Check si la paire de clé SSH existe
ansible.builtin.stat:
path: "{{ playbook_dir }}/../../.ssh/{{ user_ssh }}"
path: "{{ playbook_dir }}/../.ssh/{{ user_ssh }}"
delegate_to: localhost
become: false
register: ssh_key_check
@@ -32,7 +32,7 @@
- name: Générer la paire de clés SSH localement
community.crypto.openssh_keypair:
path: "{{ playbook_dir }}/../../.ssh/{{ user_ssh }}"
path: "{{ playbook_dir }}/../.ssh/{{ user_ssh }}"
type: ed25519
delegate_to: localhost
become: false
@@ -43,18 +43,26 @@
- ansible.posix.authorized_key:
user: "{{ user_ssh }}"
state: present
key: "{{ lookup('file', playbook_dir + '/../../.ssh/' + user_ssh + '.pub') }}"
key: "{{ lookup('file', playbook_dir + '/../.ssh/' + user_ssh + '.pub') }}"
- name: Déployer la config sshd durcie
ansible.builtin.template:
src: ../templates/ssh_harden_conf.j2
dest: /etc/ssh/sshd_config
mode: "0644"
mode: "0600"
owner: root
group: root
validate: /usr/sbin/sshd -t -f %s
notify: restart ssh
- name: Restreindre les permissions des cles d'hote SSH privees (CIS 5.1.2)
ansible.builtin.shell: chmod 0600 /etc/ssh/ssh_host_*_key
changed_when: false
- name: Restreindre les permissions des cles d'hote SSH publiques (CIS 5.1.3)
ansible.builtin.shell: chmod 0644 /etc/ssh/ssh_host_*_key.pub
changed_when: false
- name: Delete cloud ssh config if exists
ansible.builtin.file:
path: /etc/ssh/sshd_config.d/60-cloudimg-settings.conf
@@ -0,0 +1,55 @@
---
# Remontee des logs web vers Wazuh : HAProxy (load-balancer) + WAF ModSecurity/CRS
# (conteneur gestion-waf, prod-uber et replica-prod-uber). Deploie la config
# centralisee (files/wazuh-manager/agent.conf) sur le manager, puis autorise et
# redemarre la collecte "command" (docker logs) sur les agents qui en ont besoin.
#
# IMPORTANT : par securite, Wazuh ignore les localfile de type <command> pousses
# depuis le manager tant que logcollector.remote_commands=1 n'est pas active
# localement sur l'agent (sinon : "Remote commands are not accepted from the
# manager" dans ossec.log). prod-uber/pre-prod-uber l'avaient deja ; ce playbook
# le garantit aussi pour replica-prod-uber.
- name: Déployer la config agent.conf centralisée sur le manager Wazuh
hosts: monitoring-uber
become: true
gather_facts: false
tasks:
- name: Copier agent.conf dans le groupe "default"
ansible.builtin.copy:
src: ../files/wazuh-manager/agent.conf
dest: /var/lib/docker/volumes/docker_wazuh_etc/_data/shared/default/agent.conf
owner: lxd
group: docker
mode: "0660"
- name: Activer la collecte de commandes distantes (WAF, docker logs)
hosts: prod,replica-prod
become: true
gather_facts: false
tasks:
- name: Autoriser les localfile de type "command" poussés par le manager
ansible.builtin.lineinfile:
path: /var/ossec/etc/local_internal_options.conf
line: "logcollector.remote_commands=1"
create: true
notify: restart wazuh-agent
handlers:
- name: restart wazuh-agent
ansible.builtin.systemd:
name: wazuh-agent
state: restarted
- name: Redémarrer l'agent load-balancer (recuperation du localfile haproxy.log)
hosts: load-balancer
become: true
gather_facts: false
tasks:
- name: Redémarrer wazuh-agent
ansible.builtin.systemd:
name: wazuh-agent
state: restarted
+27
View File
@@ -0,0 +1,27 @@
---
# Remplace la policy SCA locale "cis_ubuntu24-04" livree avec l'agent Wazuh par une
# version allegee (checks non applicables a notre infra retires : partitions separees,
# GDM/X11, wifi/bluetooth, ufw, nftables). Le fichier pousse via /var/ossec/etc/shared/
# n'est PAS utilise par le module SCA quand un fichier local du meme policy id existe deja
# (seul le premier charge, /var/ossec/ruleset/sca/, est evalue) — d'ou le remplacement direct.
- name: Policy SCA Wazuh allegee (CIS Ubuntu 24.04)
hosts: prod,pre-prod,replica-prod,infra,infra-runner,infra-utils,ota-uber,load-balancer,!backup-mln
become: true
gather_facts: false
tasks:
- name: Déployer la policy SCA allégée
ansible.builtin.copy:
src: ../files/wazuh-sca/cis_ubuntu24-04.yml
dest: /var/ossec/ruleset/sca/cis_ubuntu24-04.yml
owner: root
group: wazuh
mode: "0640"
notify: restart wazuh-agent
handlers:
- name: restart wazuh-agent
ansible.builtin.systemd:
name: wazuh-agent
state: restarted
+17 -4
View File
@@ -4,8 +4,18 @@
# dans iptables et les contourne. On filtre donc dans la chaîne DOCKER-USER, seul
# point garanti d'être évalué avant les règles de Docker.
#
# Résultat : seul le load-balancer (IP VPN) peut atteindre 80/443 sur ces hôtes,
# tout le reste d'internet est bloqué. SSH et le reste du trafic ne sont pas touchés.
# IMPORTANT : DOCKER-USER voit TOUT le trafic FORWARD, y compris le trafic
# sortant initié par les conteneurs eux-mêmes (ex: un bot qui appelle une API
# externe en HTTPS) — un match uniquement sur --dport 443 bloque donc aussi
# ces requêtes sortantes, pas seulement les connexions entrantes. On restreint
# donc les règles à l'interface externe (in_interface, ext_iface) : le trafic
# entrant depuis internet arrive par là, alors que le trafic sortant d'un
# conteneur entre dans la chaîne par le bridge Docker, pas par eth0.
#
# Résultat : seul le load-balancer (IP VPN) peut atteindre 80/443 sur ces hôtes
# depuis l'extérieur, tout le reste d'internet est bloqué en entrée — mais les
# conteneurs gardent un accès sortant normal. SSH et le reste du trafic ne sont
# pas touchés.
- name: Restreindre l'accès HTTP/HTTPS des serveurs applicatifs au load-balancer
hosts: prod:replica-prod
@@ -14,6 +24,7 @@
vars:
lb_vpn_ip: "{{ hostvars['load-balancer']['vpn_ip'] }}"
ext_iface: "eth0"
tasks:
- name: Installer iptables-persistent
@@ -22,9 +33,10 @@
state: present
update_cache: yes
- name: Bloquer les connexions 80/443 non issues du load-balancer
- name: Bloquer les connexions entrantes 80/443 non issues du load-balancer
ansible.builtin.iptables:
chain: DOCKER-USER
in_interface: "{{ ext_iface }}"
protocol: tcp
destination_port: "{{ item }}"
jump: DROP
@@ -34,9 +46,10 @@
- "80"
- "443"
- name: Autoriser les connexions 80/443 depuis le load-balancer (VPN)
- name: Autoriser les connexions entrantes 80/443 depuis le load-balancer (VPN)
ansible.builtin.iptables:
chain: DOCKER-USER
in_interface: "{{ ext_iface }}"
protocol: tcp
source: "{{ lb_vpn_ip }}/32"
destination_port: "{{ item }}"
+5
View File
@@ -1,3 +1,8 @@
# Dossier source du projet Docker Compose copié sur l'hôte (pre-prod/playbook-pre-prod.yml)
# NOTE : ce dossier n'existe pas encore dans le repo (voir remarque précédente sur docker-pre-prod/ manquant)
compose_project_dir: "../docker-pre-prod/"
# Utilisé par hardening/playbook-ssh-hardenning.yml
user_ssh: "omnex"
group_ssh: "ssh"
password_user_ssh: "$6$w6GBkTHgj8K84P7o$pToS4pRMGLKwfpRfnz6i.WpO72Lt5DLtFQWZJsKL2dKvQrdKdi2q8xRuWqq/W3ybFNLQapvAqN33HexVjajGw0" #aiQgIAcCVIy1E33ReRssfm414dm
+5
View File
@@ -6,3 +6,8 @@ user_deploy: "ubuntu"
docker_group: "docker"
docker_dir: "/home/ubuntu/docker"
ssl_dir: "/home/ubuntu/docker/certs"
# Utilisé par hardening/playbook-ssh-hardenning.yml
user_ssh: "omnex"
group_ssh: "ssh"
password_user_ssh: "$6$w6GBkTHgj8K84P7o$pToS4pRMGLKwfpRfnz6i.WpO72Lt5DLtFQWZJsKL2dKvQrdKdi2q8xRuWqq/W3ybFNLQapvAqN33HexVjajGw0" #aiQgIAcCVIy1E33ReRssfm414dm
@@ -3,3 +3,8 @@ compose_project_dir: "../../docker-prod/"
docker_group: "docker"
user_deploy: "ubuntu"
ssl_dir: "/home/ubuntu/docker/certs"
# Utilisé par hardening/playbook-ssh-hardenning.yml
user_ssh: "omnex"
group_ssh: "ssh"
password_user_ssh: "$6$w6GBkTHgj8K84P7o$pToS4pRMGLKwfpRfnz6i.WpO72Lt5DLtFQWZJsKL2dKvQrdKdi2q8xRuWqq/W3ybFNLQapvAqN33HexVjajGw0" #aiQgIAcCVIy1E33ReRssfm414dm
+13 -13
View File
@@ -1,37 +1,37 @@
[prod]
prod-uber ansible_host=131.123.39.119 ansible_user=omnex ansible_ssh_private_key_file=../.ssh/omnex vpn_ip=10.0.0.6 ansible_become_pass=aiQgIAcCVIy1E33ReRssfm414dm
prod-uber ansible_host=131.123.39.119 ansible_user=omnex ansible_ssh_private_key_file=.ssh/omnex vpn_ip=10.0.0.6 ansible_become_pass=aiQgIAcCVIy1E33ReRssfm414dm
[pre-prod]
pre-prod-uber ansible_host=131.123.39.112 ansible_user=omnex ansible_ssh_private_key_file=../.ssh/omnex vpn_ip=10.0.0.7 ansible_become_pass=aiQgIAcCVIy1E33ReRssfm414dm
pre-prod-uber ansible_host=131.123.39.112 ansible_user=omnex ansible_ssh_private_key_file=.ssh/omnex vpn_ip=10.0.0.7 ansible_become_pass=aiQgIAcCVIy1E33ReRssfm414dm
[bdd-redis-prod]
bdd-redis-prod ansible_host=132.243.162.62 ansible_user=root ansible_ssh_private_key_file=../.ssh/key_bdd_redis vpn_ip=10.0.0.5
bdd-redis-prod ansible_host=132.243.162.62 ansible_user=root ansible_ssh_private_key_file=.ssh/key_bdd_redis vpn_ip=10.0.0.5
[replica-prod]
replica-prod-uber ansible_host=132.243.162.161 ansible_user=omnex ansible_ssh_private_key_file=../.ssh/omnex vpn_ip=10.0.0.9 ansible_become_pass=aiQgIAcCVIy1E33ReRssfm414dm
replica-prod-uber ansible_host=132.243.162.161 ansible_user=omnex ansible_ssh_private_key_file=.ssh/omnex vpn_ip=10.0.0.9 ansible_become_pass=aiQgIAcCVIy1E33ReRssfm414dm
[infra-monitoring-security]
monitoring-uber ansible_host=185.103.167.138 ansible_user=omnex ansible_ssh_private_key_file=../.ssh/omnex vpn_ip=10.0.0.2 ansible_become_pass=aiQgIAcCVIy1E33ReRssfm414dm
monitoring-uber ansible_host=185.103.167.138 ansible_user=omnex ansible_ssh_private_key_file=.ssh/omnex vpn_ip=10.0.0.2 ansible_become_pass=aiQgIAcCVIy1E33ReRssfm414dm
[infra-runner]
runner-uber ansible_host=185.103.164.200 ansible_user=omnex ansible_ssh_private_key_file=../.ssh/omnex ansible_become_pass=aiQgIAcCVIy1E33ReRssfm414dm
runner-uber ansible_host=185.103.164.200 ansible_user=omnex ansible_ssh_private_key_file=.ssh/omnex ansible_become_pass=aiQgIAcCVIy1E33ReRssfm414dm
[infra-utils]
s3-uber ansible_host=80.96.58.164 ansible_user=omnex ansible_ssh_private_key_file=../.ssh/omnex ansible_become_pass=aiQgIAcCVIy1E33ReRssfm414dm vpn_ip=10.0.0.14
s3-uber ansible_host=80.96.58.164 ansible_user=omnex ansible_ssh_private_key_file=.ssh/omnex ansible_become_pass=aiQgIAcCVIy1E33ReRssfm414dm vpn_ip=10.0.0.14
[vpn-uber]
vpn-uber ansible_host=131.123.42.158 ansible_user=omnex ansible_ssh_private_key_file=../.ssh/omnex vpn_ip=10.0.0.1 ansible_become_pass=aiQgIAcCVIy1E33ReRssfm414dm
vpn-uber ansible_host=131.123.42.158 ansible_user=omnex ansible_ssh_private_key_file=.ssh/omnex vpn_ip=10.0.0.1 ansible_become_pass=aiQgIAcCVIy1E33ReRssfm414dm
[load-balancer]
load-balancer ansible_host=143.246.199.221 ansible_user=omnex ansible_ssh_private_key_file=../.ssh/omnex vpn_ip=10.0.0.12
load-balancer ansible_host=143.246.199.221 ansible_user=omnex ansible_ssh_private_key_file=.ssh/omnex vpn_ip=10.0.0.12 ansible_become_pass=aiQgIAcCVIy1E33ReRssfm414dm
[ota-uber]
ota-uber ansible_host=193.27.90.157 ansible_user=omnex ansible_ssh_private_key_file=../.ssh/omnex vpn_ip=10.0.0.10 ansible_become_pass=aiQgIAcCVIy1E33ReRssfm414dm
ota-uber ansible_host=193.27.90.157 ansible_user=omnex ansible_ssh_private_key_file=.ssh/omnex vpn_ip=10.0.0.10 ansible_become_pass=aiQgIAcCVIy1E33ReRssfm414dm
[infra]
monitoring-uber ansible_host=185.103.167.138 ansible_user=omnex ansible_ssh_private_key_file=../.ssh/omnex vpn_ip=10.0.0.2 ansible_become_pass=aiQgIAcCVIy1E33ReRssfm414dm
vpn-uber ansible_host=131.123.42.158 ansible_user=omnex ansible_ssh_private_key_file=../.ssh/omnex vpn_ip=10.0.0.1 ansible_become_pass=aiQgIAcCVIy1E33ReRssfm414dm
backup-mln ansible_host=85.121.176.241 ansible_user=root ansible_ssh_private_key_file=../.ssh/key_backup vpn_ip=10.0.0.4
monitoring-uber ansible_host=185.103.167.138 ansible_user=omnex ansible_ssh_private_key_file=.ssh/omnex vpn_ip=10.0.0.2 ansible_become_pass=aiQgIAcCVIy1E33ReRssfm414dm
vpn-uber ansible_host=131.123.42.158 ansible_user=omnex ansible_ssh_private_key_file=.ssh/omnex vpn_ip=10.0.0.1 ansible_become_pass=aiQgIAcCVIy1E33ReRssfm414dm
backup-mln ansible_host=85.121.176.241 ansible_user=root ansible_ssh_private_key_file=.ssh/key_backup vpn_ip=10.0.0.4
[infra:children]
infra-monitoring-security
+32
View File
@@ -0,0 +1,32 @@
POSTGRES_USER=xaviaota
POSTGRES_ROOT_PASS=Ipv0ph9tqVew5zuHdHZAVef
PRIVATE_KEY_PROD=LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktnd2dnU2tBZ0VBQW9JQkFRQy9rRXNwd0lGT1pzTi8KbVNId2pDdTI2RnVHTE8yUWNYVUt4SHdKR0hoMks2aGRQeDkyWENaRHJGL3kxZ1V5UVhoRFhLMGRjZG5WVjh1eQpWVGg5WlZNU3NsYjM5N1RKUEhXdy9pd2hQT202T1E1Tko0V1c4M0VuWmpvd29XaXJERlViLzMzMmhLMTQxTXNCCkI0RTF1ZkNBZWpIc0c3VllrWlhkOENPL3ZMcFcyMGYrNzFsUzBDZnAzQ3NkeXdZVm5tK3F2alBaa3d6NTFUVXYKK3lNWGpYQkFCb0l0S2xqbXVOeUhZVjRQV0NOVVdsVXlZeXJFbHAyanNIaFE5eGhsL1ZnSisyT21uYzZOdlJrTApMWFc1ZWp1c2c1aEhrUUdEVzlNU0NRd2luc0t3TGdWUjFrbzJXMGNPUTJLYVh1RVF1bmIwTDVxNE9Od2RHUmhtCjlKazA5cnE5QWdNQkFBRUNnZ0VBS2ZIZ1kxNEJjamRTSzBoT2hDdE5EeG5Gb0pXSjF3emd3ZURQLy9YWnVwWkkKb1ZKSEExV2VFMGRWSXdqMXpsRkRrZ3hpNDRXZ1d2U0VaNkg5WW9lakx2N1p5Q1gyOGZGSkc5NHJyNE4wOGcrNApML0N0Y2FZUWFyNFprN2hrQU1BTWlycmd5KysxUkRYajFXL2RQUFJmMHVIdXVISkF2UW56dE1yMktPK2NKWlF3CllMby9aMmJ5QnBEY3ErWlgzbXFldGE5MVpRWmdIM3B3KzYyL2FrRC9LM3lOMXJoQ1plWENCMU9HbjdLVlgrNVcKUVlmRWV5ZzE5QlhxSVQ1VkE2Rm1zci9BdGdUZXphd0dYeUV5clMwdUxSeUV5UmFUbW5EaENJeWdmMCtMOWlLbwpvSFZWKy9JVllpcGJkcTh6ZnhhTXFvclNXNStmM3hyMDBCcXFONlhDU1FLQmdRRDc0a3lQNm03RGVzZGJPMVNICmhtUE0xdVRINUhKQ0o2UmkvRkpzWk4xRXA3Wm1keERqeWx0T0diRmtJMkpxL1d4ZllaVFk4ajNKenhKWjJ5TDkKUk9XQkNYcjVBeVZoeEtSY09RK2ZrZy92M2RFTmdYMWdwQnNDWk1xSzY1bm91b3lwSExIWUlOQnhiRTNHc2twYwpkQnVMOHRDbUl6REtsL0srTEtSVEUxVHNid0tCZ1FEQ3NhaGkxZWo4QWtkaUtDQndqMEdKcXdkdHplS0MyN1VCClZRcTl3ek5OY2paUzlNQTBCMWowZk80enZId1IyYURqcUR6cytaNGgvN2lFbHdDTzFwOXFkbURpdERZblR0Z1YKN1d0RHJ1U0NmN0RVME1xNUZJeTI5a2s1d0FiNEw0Ym9lcTZvNzY5cSszdlhlUCtKQitoMUxCL0FSS1ZqV3hHbwo2dStsOWZYNWt3S0JnUURBWkdmcWZUeEJYNlNxVUxVT0YxNWZkYnlsMmg1VDNZZTRlOVpSS1BIQm9RQzNYemF0ClowRTZkSEdFZFdxVklpRUc4YmdKeFczZ21hUUFzbHNWYk95SUZUK3l0KzV6c1FtQno2dlpURzJUMzYvd1N2b0QKT1JYUVRFQlF3QWg5TlJCL2s0VmIraVI4bmljQ0graWdtYWI0ekcwR21WYUpTR3FIUy8zdjNwSTlUUUtCZ0dkMAowajZhM3JBelljMUY5MTc0c09HLzBUWEhCc3QwaG0yTHpDWmtPTG01ckNaT3ltZWJVK2l2RGtETnNyd2xnZm9YClp6Q2h3Q2JlNVNVSHZKTXFkeEd5ais5SHhkTURjWTNOV0lkaHd5bVpRdFVEa2dYaFpwaStyTkNVT3QvZEdYb2wKM3ZRRVVabXV3dk1QNS9IZ0V1VllxaEtZSjY1eE1TUXFtMmY4R0ZZakFvR0JBS1F5Q3Z6YStXY2RlS2xQSDhTagp4QzE0bUgvZFpBVzRweStSMlZPTHlrQUlqVEVCcUllSFVHVkowaGwxZ3E5WExCY2t2TXlOMXdpTFJZSlF1WVl6CjN6L3Rwd1F1UEtGZVZYL1ltRXd6N1ZqWW5mVUt3YjB4RU53SDdkMUNFVjNyakpMMFp6VTE5OFZnZHFqdXhBNXcKRkdWTHdWYllNWm9BSFdQM2kwZmZPeHV1Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
ADMIN_PASSWORD_PROD=7jgEeJsRTwLkOXsisb6pODHN
UPLOAD_KEY_PROD=9b5b36eec185ba242ce5afcc6e344dce03a4c855
POSTGRES_PASS_PROD=5AiArInxJTbXVlhmpr9g2rQ
PRIVATE_KEY_PREPROD=LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2QUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktZd2dnU2lBZ0VBQW9JQkFRRE9lZjV6dmd3ekNLZ0gKS0drVEh2eUJidWhrei9vbC9qT0ZLYnlWekdCL1orS0FxZTNCM29NYTZ1U3BGdVVQQjROVUluUDlqZUZ1T3h2Uwo2Qk1vNE9mSlRtU0VuZ2J1clpiWjM0aFNER3FzcnNReWI2dk9kWXhYOW9PTGwwZGc2UnVKMXVPbmMzZCtqRmdHCnlObE1oSVZDajFSQ1JvV1hPYlBYai9naEFtSmtnNDlLTWptN0hqSElucUphZ2xlNHJVczhKbkpObmxWcTJ6aVIKZVU0RU51aHZHN2dQSUYxR3lWRlJ6TW1wL0N3MWc1Zi9pVTNKQ2RSRFhYS3dUOG93UDNIdDhHMVFWWnQ5MDN1KwpmQ1ZtK2NNUUQ5cHpJdHE2c3Z4bmI0RUF4K3B2TnJ2d1FSQmJKcFBqWit6eVFXSlJGSGxEOUszWCtVYWp5UjlHCjg3ajFNOG9KQWdNQkFBRUNnZ0VBSjI5clp1WEVaTGJoaU1SeUZTSFU3aytsZVNPenppL1gzVFlVZTdQV05sODgKQm9EdUZSb1Boa09ZdXFiUnh1WVRjaTFHWkFZR0tVeUd5dWhpU0d0ME9hR01WenZyYlFpZ2lDcEdXZzFmRG00dQpzTWRkVkFobGJxTjVpbDZ4VmxRUFcyczhmYzN5NDZ1Z1lHTmFCV0JHSjJsQ21Zbm9sL0tBWThwdWhMZDAzV2RmClViMzF0S1pOOXkyVkFnSUZIUnh0Z3NBY1ljem5HOXBEQ0JhTUlxcjhkZXc2THArQWFVbVptbXM4TklZNkQ1dlkKbHBBSmhVejdnMXVMcXJyTnV3T2J3aG9kanZHVURuSG9VWHlaL3dyeEJXb3F0YTNibnBTNkt1NHRoeTdZajV5Rwp6RytVWVgwL0lCeFNrOHQvS2dXU2MyclExVUhUaXlHMlM2YzlZNFdONVFLQmdRRG9na3Q5bFpNMEFqRzl4dHcyCi8wVE1hUHJWTWt3ejVQd004NEU3U2krV0pYeC9UNzIzQ2xER3lkMGVKU0JJMENBUDZHbnU2a1ozRWk4Vy9QamUKK2NXSXB3dWJSSTBvb2hKbG45VEV0OW9jTytyTitYUHcvbnFRNFk0bnVGSEJaTUxRa1RvbEhIZzNqRVA5YlYwNApaY1B0UkFJSngxbjFibXlmak1ESlBZOE85d0tCZ1FEalZtTFBKNXUvNEgyZWRHQ0JrQXIwY1Jia2xzT3U3ekZqCitlUEhydVp1ZzNKWThVRVdMT0xWV0Q2NXNuL1hhaGcrT3FmWXAvRGs5NXRqWjhlUFhhS2RvVllDaERrZXFBTE4KMnFibHU1bktjdmo0d3plN1p1MTlCRDlMOWhmTWxtei9pYTY3bkR5c3hZTDd1Z1pIWVZSQ2J2b3V2K3pMVHhDQwpwUC9qQ3JpdS93S0JnQnFLYlFXYjRsN0lkVmNkY1J2U0RjOXJtRjVuekcwTUVYMkUrbGFEUkdWV1NCemNSR0sxCmliejNaQWRCNDZxTUgyelJjZTQwemZCZm9nSjB4QS9uNFJXVlErem9hVmlmRDR5TUx5UjVmQ3RHNTZtMWRQZW8KaE9NdTdDUTZsUktIWnZtWjZsSThLcmpraVFwUHhwbGVmSHdFREZ1RFp4QnVGM1lyQWdLMnhUTEhBb0dBSWVodApublpLN2NpZkUvWkp1cWx3YU50QlRlRUsyTm5HWlNJTmptZ1E2S1RrZGsydHBwQVZXVHJyU0NsT2xpTzE3L294CnRNZi96emI3VDAxWVg1TFl3V2doK3dnYnVPM1lWVG5xc05OVmNmUEtITmlKUklOcW9yM3lidk1LdzdJZ0dkRkEKWmEwOUE0d0dLR09yc2hQYlRIbGZDWUFJNHA0dFdUOGhCNDArMUtNQ2dZQWY5RkVVTDBZRDZKblNiK2R0YjRQMQpnVVUwNDZnc0Ira3l5anNmb3hXMEVQM3hQZmVCMCtmc0kzeUhuOHZHQWRNMFdFVW9FeENqZzVaY0Y4bXUrcUpVCnpSQTB5ajM1SnkvU0sxY2FBVjZiMDRiUFBLRmYxT3pWV3laR3NzMVlVVy9xVlJsRlQ4RXFDYTc5NGlaMHA0NVgKR2ZPUEkvbzMydzhKUEY2WUdrUXEzZz09Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
ADMIN_PASSWORD_PREPROD=kc2To8hAm1AN00umhtfZ91rt
UPLOAD_KEY_PREPROD=133a37f8498e9a828a8050e7e7e60b3553fef2d2
POSTGRES_PASS_PREPROD=uc1zmnNPWu9WWA6eCAxAiJ
PRIVATE_KEY_OMNEX_CLI=LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2QUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktZd2dnU2lBZ0VBQW9JQkFRQ1l4L1Z2c3pxb1BtaHUKUjRWVnZEY2drVE81TDRwZ2VwdHR4ODZCT045ZDBaZ01HNmFpWFFJZXZhT1hLUjlIMW1qeURyRk95NDB2VlNiKwpoK1JwRDJ2Z2JCTmdaTFJmeVE0V0E1U3g0QXk1UVU2V0Y0UGpQdC8wK3pQcjIrcXBDMDVrZGJSSXhMVjdXSnkrCnl4cCtFTjAxbFlXOU5FdWxqWEJyN2ZNRldKYmlXTmRWQjYzY3N5d2YzWVd0WXlvNHdCZkFTT3cxemVacWdCclMKYStsT0FOOWthYjBsWnpqcXVZbDFEbEE3bzJnZVlZcDNOelNpTU1GbXpGUm1NNmhDRzM1VzE1azlaS0E5dmZ6TQpGSE9kaEZZQXRFR0RnUDhveU56SCtrOFdkd3NCM1MzRnhtK3V3aTA5OFV4dDd5UEQ1LzNGb2lIYytWejR1N2FCCmszYlZDZ0w1QWdNQkFBRUNnZ0VBQk5NdmQ3NGxsSXNOcmRUb1VRbGdoWHRuSFNVelFJSFlGSVJEd1Q0NjcwOHkKRng0aVJJZFMxMXZHYXdkNkQzWmJTbHRjZzJMbmlWYXVEeDlheWV3YUlhOFJhMnV4YkJoN3IzRGdiUlVvc0dBNgpVNEJpM1REVVE3djhCZW41aHFvK3lQLyt5QnV6NlFxRzFHVGM2QjFrejdTZEJ5NkdmZVZzMGJCdmlvd2pqZy96CkZicy83LzJpbVRtVUN6V29KOW5za1BLOUdmaU5ONWxiYU5NSnpJVGRYQVkvVC9uM0N2WXBLRTlMMlZJbVMzNWgKNkh5LzgzdE5GWWhvYk5xVmtlRjFFc1R1OURqVVhRUzl0MVFCZVd0a0IvbkRWQS9uLzFiQU56UE5SdEFNZUpTQgpQNkZLTkRwSzJ1cWJpT3dQQnZMaDdtQ0I0VVI1elpSUzFJN0RHRjBjc1FLQmdRRFdZeG55NUEzeS9oUFl5MFNmCllYSzBXaTlsbGc4dTgvYjBGUFJoTytQcHoxYVFLdGJVYThWL0FCRXdHd3FXVS9naGs1L21LNkJsYWg3VWRBVEEKdDVEM1AvTU1hS3JZNHZ6NlV2NUVadG1zWU5mTjBFLzNPTWtHZ3pzWEIrR2ZnaHdjNlcwbnFpcVcwK0tMYmJYZgpDemM0c25nWW1YZE1ESE1QNUZXUm9VRVNLUUtCZ1FDMmI2Y3lTOUtIVmc0dnRlYkdlb3ZkMWZMdHhTV294WlJKCmxSMWVIM0RrWWZaMEk2UU5NNE9STHdNb0JNUmlpUTVwbnhyQ1plV0VJN1BjQis1QmFTR21lOGs1TEJqenMwZ2MKWEo3K1RIVXUvT3FIeFA2TVdpMGNWc1cvQkxIWDlWTE8zY2JZQnBlYnRRTTRoWjJrcEN0WVRnTDhWeDE2U0o0TQpjTHUyNVdta1VRS0JnSHdQeTBOZ3I2WFBYR0xYZGpXWktjeXVaOEVzcmtxb2lWOUVCUXVEb2VkNndZZ2FFa25JCnQvZy9iTExlNHlpTkhRa3BLWjREM3dUdy9UaEtqem1Ld013ajZKMWtzSUZrRWFzMmZjZk5rT2VNL3pVdCtDaUQKWnVscTViWDFMSjZtcDJnZnVMZjhaRHIyRUtKSThiZEtBVUhCSFhoMnpxOW1nT3RRWWlndEhiL0pBb0dBR2JpMApRZ3NlcTZZZWFldWpsZWhCUUEyaUtwUFpNMnBuOWRmb1JCVDZ2Ymh5SHYzSGEzVVAzN0JaS1pOQ2ZRSUYzaTVvCmYzQU9aQXNFbU1rbGRtTjBNeHpReWhiRmpkYUJIRDZTekJmTGpjeHE1S21KbWhBOHUvb2Y5MGFhTEhDU05FaGcKZUtTMVMyYkNCWDRSTTkxUm55bWdraU5XTEorQWFSQ2I1cldCcDRFQ2dZQklDeVBkeTVCNCswaVFhVnVkdG1wcgpPenR3R2VxaGI4WHNLcU5iNjRUQW52TWZNWnE3Y3lWYTNYZ0EvcVZtZVBhQmg5Z3JWeWxlUG94V3RIN09xL05MCllnakxmQnUvVk5LQWlIaXJkTVAwTEhQYzFaUVNYS09EYU5RdmdXazBTYnMwN1d1SGN5RG5oM0E3UzFlMmdvTXYKRW94NkdPVlRhRnB0cDYxYVdHN3dWdz09Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
ADMIN_PASSWORD_OMNEX_CLI=XamoflYJO76jU3WkX074EMfe
UPLOAD_KEY_OMNEX_CLI=653f61211812ea0c4ce7b763db4821a417549e91
POSTGRES_PASS_OMNEX_CLI=h7z47IGFzsNhXWJtvwOXxWjD
PRIVATE_KEY_OMNEX_ADMIN=LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQ2xBd1laeWtVSXRSNFYKQllQN1MyM2VXNFk1L3UxdFlpa3l0b2xZaithd3FUUlBCK09mS2EzV2xCRkJyS1I0VWNYUkRTR1VrQk9HT3g2UgphWklyd1o4eVhCbG1OeEl1TlIvVDE1cW9DMzI3Uy9jUmcyL0grQjJVOWtqTFhSQUM1WVVscnZ4cXhBUFFRdytOCjFYNW8wUWhSVi83Nlc3T2lhaURocktjRWNWUjJldkU3Q3BMblRTbU8zS1FQSDM4NEg3alYxN0o4YVRKczR5QnEKVUtPSGpGT05rUWNMbHFLOHE5YWdLQTl6c2RKVTFCTWNCK2oyVXVBcGtEc0pRWG9nVFRaV1poQzhqTnlCVFhINQpiZWxpVlN0M1FPdm5zTDFnRFd2R3ZyTXRiUzQyRDNGMEwzNWNoM0I0VG5ZaDJ5Y3VsSXFtZnlMVXRBanc5dENjCkpESk1LK1dCQWdNQkFBRUNnZ0VBR0lqOUVMdWZEUjdsQktvbnlDY2NoSG5tV053N1VFN09vbUE2eHNUS3Y4ekEKM3RsazhtYi9sbzY0akNQUDVxWDkvRW53Q3Nkclo4T0ZsWWgxZC9VWHB5VERrQXV6TTlTcFVWWFhCSktFMU9NRQpMd0gwam02cGMxczFVcXVaTkYwTk1aKzFuMmhheXM0VjAzaUJqYk03QWVGTUk4eWJ4cFB5Skt3SHYyVGNjcXN5CjY3QXNZazU2cm1PQjhsbGV0Ni9kOHp6cXFPbHZBV0xxWmZHSnViM3hRbCs2U3ZoNkM3SDhlTW9XdnhpaVZIdDMKUTkxamF4L0xUYzU2WkFBd3IzczMrRGp1Rit1cEs3NVRML2hyeWlYN0g0cHRFblE0UllIWFVVNHhZR3lnQzFkWQowZUllcVZCcmFlSFNzTjR0QURHc0FXaHpkajZ2SmtJOVpYcWhaUHFqbVFLQmdRRFRGcGFaMUcyRVMzUVpReUVaCm9DUVJCQTQxNy9sVCtWVENuZ0x2V1Q0V2pRM1BWUDZNWVFaQXZIOSswVEEwaDFKUzFFTDk0cVR3aVFrR1VnZXEKRXBKTG40aTFxTkpWNHkxU1U3VENpL1N5OWVYL3E4RVFMZDBNUFl1eGpvRTFHdGgwODRCTEFBUkd2bWVGclB4MgpwdmpvSDJjWENhb2NoOHlsWHRHYjl6azZUUUtCZ1FESUhzYWxpQ0F1WUZkV3ZvSWVlZGtEYVU5VDlzc3A1TnBWCjEzRzNuWFJzYWN0OFEwaFhNTjBBRWdNdngvSXFEOUlmRmFQQnJFOUlYbDZxQ3lrSW5IQWZ1ekJkVThJUU05OGgKWXZwMGhRVlhnaEF0blB6VkgxTEE4aUJTbktZMTdOS1ppR3cvU2sza1lCWnk2aGg2QWRmc0R3eDJ3RlJUalpzWAplbExkek1IS0JRS0JnQUw5THVTK3ZVNUNRMEtZVUwrc0pOYVkyQnFyYXp4bEsyNFdEclNVN3V3aTB5ODhaTjVZCkFWNnBUS1RmRVcrcyt0MXhaQUYwVkNHVHp5em52SFFNRDBaQzNBeTJkMFFBRTRRMjZ6aFdBd0VGdWx3RWNwZG8KRHRrOWhkemR4N2FZeXJmZE90UFZERHAwTjhNTTBBWmVGdWd5aWFNcDM5N0hqWDVKK1czYjhsT0JBb0dBZU5hRQprcTJvNlBnSnBQcHlMWjdiMklCOWd6RFJxcUZpNlZTbmVQMGRzTjBOSHlLdGl1Q1pnaEplVkRyQzBuMHRIdEg5ClNOUVlKNVc4U2FkbGhxcVgxY0U5U2cxcWNFZ3lJc0hXaEFkU20yMlJBVW43TFdHMW9ZWlMyQzk0K2JUZjBnK2IKZFhnVG9xUVd5aHJxM3JsQVBXMDAxSHY4TDF1aFc4TGtTTVA3L0ZVQ2dZRUFxRFo5eEI3d3h5Nzc2VWs2SUFBdgpEVkV4TTZhQjYwMVJkd09pUlk4VTFkU3ZoRko5TFJHM3g5VVl2NHdKa1IvZFZMTGJlVnVvTHJJMEUvWERqQmhMClp6dzZOWTYydUY4K2tmTUFqOEpSNkx1azF0WWtYM1lIN2tvM0loMDFSVWMyYXNpU1dkbHAyUk1BbnNodGk2MHYKWC9OdmU0ZFhOOHZDbC9maE5kbEJuVFE9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
ADMIN_PASSWORD_OMNEX_ADMIN=v8dkMb6uAeiGMHEczI3IFphl
UPLOAD_KEY_OMNEX_ADMIN=144225999c44d936caf42adeecb74a419b7d91c8
POSTGRES_PASS_OMNEX_ADMIN=We9ji9I75kIfZJy4QjTqJ4Rp
PRIVATE_KEY_MOBILE_PROD=LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2QUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktZd2dnU2lBZ0VBQW9JQkFRQzJmb0IrVzA0TWtYSXkKQU5yVWVmZkR5MVZvc29aNlVnRC9GV3Z2ellZMFBmMjlrV3NhdEJUZnV3Zk1RUGc0VlZsMk9YY2k3b0wwMVN0ZQptbDJaT01iK2tGMGZuM2RhNWRSRkZQVkVwN0tJK3kxYlJTQmN5M003OVk4b29uMlExVGNvb0NlYUtWWFZ4N1lrCmc2L0dQRExMLyt0STBIVlhkdFF4TURyMUVjQ1p1VG8yZys5MW84NE1MWEpYaXBIa3VTNjRVWUFqbFBySkZjcTkKalI0VEo3ellzTDAxUDlmUG1va0FtMlZjMkI5ZEcrQmpsU0JYcDdveUxJT3RNd0MxekFDa1dpVSs4MWQ1OU5NWgpBdjA0RkpFTkIvUDZ4ZUczV3FNL24vOUlOR3kyWHZkOXdrVXVaRU9iWkppTmg0Q0w2Wld6aEpQQ1BxMmNIY3FyCmNIeFhlUnR4QWdNQkFBRUNnZ0VBQmRYS3B2K2xZYnArMDhGNlBjUCttNWlUVFFzMFNKSDVyUzVTcXJMQUlVaUEKSmdFUjB5TkI3WnR0MXVhVzZUbzFaNVhUek42aWVsQ2NsK1JJck44SFlkbGMyT2VBNmZ1aVMya3RwSG5XT0JJTAo4N2lrbmNMMWxzdTF2WjlnYzNLVjFtczBpUFhjWkQ4RUFHdmpnV3VvVmlTRE1ldFViUzBFQU9TaFpmZWcyR2xwCmJhbllxa3h4U1RVbUpocmYyc1dETDRRRUtMb3NxU1BqWWxRSmFMdFYwR3ZSckFwTzFNbGxyWFB6V05SUGRrUHEKSWdjQWxGU3drZzZ3OUNHSmowSG1YQm1DcUFMM0dlczRTWVBrakkvSThESEFxRHZIUGpuRVdtTGYxeXVySVFHawo2aGYySUgwK2xTcDVYd29wZ1hnQlYvRUVxN3VRM0xuNkIrcUF2MTVCU1FLQmdRRGxqQmR1bTd4VXdweVFRSmkrCkFzb0daZHk5UW5xaHltV3VBWUw3Rlh5SXlER0UyWkZ6djc5Q2RVV0t0Z1VmNWxtL0htNDN1T0xxWjBQYWRnT2MKUENFTFowOGdRSmFJTXBLc0ZMeXdycmpaQUJGZzZRRGlwTllpN1AvSmRJaldWaTcwQWNoekJkNjVWd1hzSWlKNApBdGNJRmxuYUNXeDVwb3VNR0MwR0EyWHM5UUtCZ1FETGhrcEJyR0pOU2dCZ3J5bEwxUS9Yb0szYVRqZk5yOE1xCjBYMVBqSU0vMG5zdFlWMUp1VFpxaW1kSmkzQ09va1BDMTZ5YlhzZ2ZTdmZ1TTBiaTRiS1VQUDl4aVRDaCtCNkMKRUNueXo1QXdnbWNyU21iUEQzWlNBTDhYTi9mSVlhNW5raHBwbHZuVmVMWVVRbWRaVlNaOVFBZ2pmTTcwTnJTcAorUVlzKzYvbkRRS0JnQmdmUTZzYzlJcVh0T28zejVWUUo3aktaWXNCL0c4YUhOdFBUMXRoWTZmZ0djd1RMUVRUCjVwNXVRbk9VTXkrcC9rNW1QVURmemdEQi8xQnF6SEJUcFVsVWZ1MHR2R1B0cEpvdXg3RnRld0ZhejNpMm82QXQKb0U4bFJxLzk3ZzlRR1RrTkl1cHJlSDlUbE15K3ZZNk5CZG5VdDNhQlBRUC9xM1I2YWVqUzNKZ3RBb0dBREJJdgo3MHM1SFZwcFFWTENRVVFteDJRNVMzeGVKTzNxeG14Y1lVNTFMbURlZTJlTjcrUnpUTWhlcmViK1N3ZG11M1N0ClFSM1RBc0FnaWxreU9CeW95ZEFBV295SGZPUEpjMjlmL1kzQUFKQ0xhKzdpd0I4cnBsdHkrZjZRektxMmZHQVcKL3ZJOGFTUGFzTUxyZkVubm1ZQWx1MjBZblRWdGE3TmdZV1NnSHpVQ2dZQXNiT3JPbHhEbWpjZER4N3l1YmNSOApTMTBTalJzNkhMQ2ttV3djV2lpbGpXelpaT2pyVytmcThyTzE2cEp0K2IrVjZtVVRScXplTG5xTVVvb3pjQXAvClJSS0xEVC94aFVZeHM1TWhGUlBTa3E3YXdLV3NpdFVheVEvaW4xaEtkUDhlZzJ6Qzg2eE5zNzhERE1MWUxsdWEKbm9NMm0yL0NxcEZySHN6cFhnQmgrZz09Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
ADMIN_PASSWORD_MOBILE_PROD=zjRFjKEnu3dTB4Re2BUEWj0p
UPLOAD_KEY_MOBILE_PROD=f7213601d353955c65eb2790fecadc4a41d3e9f9
POSTGRES_PASS_MOBILE_PROD=LbYA14vuUG937x9wvOKErN
PRIVATE_KEY_MOBILE_PREPROD=LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktnd2dnU2tBZ0VBQW9JQkFRQzl6OCtueTRLM1JJZHYKNjBKb3o4K0x2Q2J4QUVwL3FxZEtjV3Y2bDRlWXpmRlptUklTVHUvZzE2Vk9YQnduOEQ0RE4rL1EvTHQ2NGdaMAo1VFhkdGRsRklNL25CdGRVNGttM1Y1KzBteUphVTVQcDZFYXVkazRkMzYxNldkT2VYOTladU1tcTBPbUdtM2R1CmJRTi9GVEtscTBiODBDZERWSVNJc3pKVENJRk9zaGFnc1ZENWRzTHBRdWVncCtGRTZwZVBXSHQ1SDdFSW5ieEgKNHU0SDZRbVR3U0VQd3ZnSm1xVTcvRldUM2Q0M2VnbTVNY2xoUHVOWjkvYTVQN1ZKek5GVysvaG1hZk9kVCtCUQo5Y2x4Q1VoS2VhOW9iLzd3MkR4bjFPRU1vT2VlZDRta1ZjQ0VXcFRyWmFOdjlqMnROdjBWWm1leG9GQzROa2FuCjZjRlVkeFBsQWdNQkFBRUNnZ0VBQkdSRmdyVmVZemZPVzc3a0VpRnFHai9uNEJXZCtQdzRLS3h5ZWlGOWFBSUQKMCtDTEh0U29ENFM0ZE1BRUtzRzZrdXA5YUZGMS9sbUh4V0ZKbTIvMHIwNXd0bFlJaEw5RG1MUUorWmR6OWx1NQpXcnAwRlluejNyOFR6WGFSN2VKNy9DQjBFZzYwQ0lMdnJYb1BrRkRXUGllMkJHMXdnSFNqMHd5S1hUVEtLOG1WCmFrVGl4VWFkNGNMV2NoOUhZeXNEM1MweGRtRnNaZi9tQ2VWb2JpMWlVSlJnZDFSeVRCcmFlaEZSc2RHMlRBN3cKVTdYaVlDSlgrbG52SEhGR0xwS2pUcGtydUhpU3ViaGE0MXhMWU8xR2xzMzAvb2dYbTZtZHR1b2xvQ1lOM2RGWQpBS3UweFJkQ3Z2SThNYVFjaWtjVWdNQ2N1L1BObE5TaHNyOWxoZHA1OVFLQmdRRC90UTZ0WTFqczJ5dXU4UUZUCm5zaWlNamhFajhHSHBwZEtyNTNpYWdpdkdreWhSOTBoK0pZUlR4RG1KZnk3Mjl5VWorTDhsWlJ3dVBJczlNYWMKUXFzeTBDZ2JId25SK1NEdFhSUkVxTkZKdHg2SkxCNGw4THZMa1lhZFBaQnN5SGZUa3RBcXJjZjFMM0Z1WmFnTwpoa1hORW44c2VGWGh5MnMxdE1XdlVNY2tNd0tCZ1FDK0IzRHd4UmtWRTZjeWNqNWJRejV0d3lVUEZYT0hFVVpzClZ1bjk1aXFPQjR4TDFNeStaTW1kWktkRXpncjc1MTEyZzlJRmRtWU5xalhoNXhMMnVhdG5SNmxTY3lvOTlIbk8KWFd5UWtBNm9MYUVVQ0QvdG80NHEya1M0STA2MytURTFUbWZNRVB0d0NJeUYzNmNnU1NwMUZ5U29zck05NmYvdQp5RXg4dWx3UGh3S0JnRXVvaDdvM3ZmVCsrRkxmemxPdWxKQ2hLVkxuOGd3djkyM0t2bjFpZ0VWSEU4dFlsdzVaCkhoWHEvZ1lLMFZmSU91RGVIZnc4U2RwOGJuTnN6V01vdXJ6aktDcTJLVkt1VXRiSWhwbTJGWTZwRnVqaFc5NC8KOTJESkl6cGRIK0dBQXl4ZnNTWWZteDFwdHpkQjd0SzFXZXZPZ0VCcExvZ0xoeUl4NVpmMDM2ZFBBb0dCQUxnRQpyTEg3cTlabE9idjROYnBhMjlHZTcvd3pZYlNpWGZmOEVvdjBuN0Q2VGpvQTBHUnZaVzR0VW0ybXVheEcySk0wCkErV1dTaTFoYnR4YjBGbHNZemVZaGJkNkhxWHdZU1FlQkV2WnROZlhsK3kyTUhJSHVGeDNFYVRmVEJPTUZNTHkKTWtIREVEbmxqVzcycmZqdDRWZVNHY1hqaHYrbDRsbHhUZUJhOTF0eEFvR0JBS3k5WGxUUXFqTW1MRjB3cGpVMApxS1pLRmdFRHJUNUI3b05MaXlzNWlWK2dGbEVaeWV0N3VGVVBWdHV0TDMzTWttSkFIMkxXRjRVMTBTc1ZIWUE0ClQvUEtEMzhNZGhRR0U0RytIL1psSkpXWjhLdkM1MFZBWTEwanBKTnlXa0JicjlBU0lYSVhTUlp2Zi9IYnNraUIKTElHSUJ3WVF1ZjI3RHprRk9WMmRMRFgyCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
ADMIN_PASSWORD_MOBILE_PREPROD=thxz419zFmNdh547A3vrQE1v
UPLOAD_KEY_MOBILE_PREPROD=8f65a01120509b149f3a781b139391272519041b
POSTGRES_PASS_MOBILE_PREPROD=uS6K8V54JeUHOYsbEDdbIt
+212
View File
@@ -0,0 +1,212 @@
services:
postgres:
image: postgres:16-alpine
container_name: ota-postgres
restart: unless-stopped
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_ROOT_PASS}
- POSTGRES_DB=postgres
- POSTGRES_PASS_PROD=${POSTGRES_PASS_PROD}
- POSTGRES_PASS_PREPROD=${POSTGRES_PASS_PREPROD}
- POSTGRES_PASS_OMNEX_CLI=${POSTGRES_PASS_OMNEX_CLI}
- POSTGRES_PASS_OMNEX_ADMIN=${POSTGRES_PASS_OMNEX_ADMIN}
- POSTGRES_PASS_MOBILE_PROD=${POSTGRES_PASS_MOBILE_PROD}
- POSTGRES_PASS_MOBILE_PREPROD=${POSTGRES_PASS_MOBILE_PREPROD}
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init-multi-db.sh:/docker-entrypoint-initdb.d/init-multi-db.sh:ro
networks:
- ota-net
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
ota-prod:
image: xaviaio/xavia-ota:latest
container_name: ota-prod
restart: unless-stopped
environment:
- HOST=https://ota-prod.uber-stup.club
- BLOB_STORAGE_TYPE=local
- DB_TYPE=postgres
- POSTGRES_USER=ota_prod
- POSTGRES_PASSWORD=${POSTGRES_PASS_PROD}
- POSTGRES_DB=ota_prod
- POSTGRES_HOST=postgres
- POSTGRES_PORT=5432
- PRIVATE_KEY_BASE_64=${PRIVATE_KEY_PROD}
- ADMIN_PASSWORD=${ADMIN_PASSWORD_PROD}
- UPLOAD_KEY=${UPLOAD_KEY_PROD}
volumes:
- ota_prod_releases:/app/local-releases
networks:
- ota-net
depends_on:
postgres:
condition: service_healthy
ota-preprod:
image: xaviaio/xavia-ota:latest
container_name: ota-preprod
restart: unless-stopped
environment:
- HOST=https://ota-preprod.uber-stup.club
- BLOB_STORAGE_TYPE=local
- DB_TYPE=postgres
- POSTGRES_USER=ota_preprod
- POSTGRES_PASSWORD=${POSTGRES_PASS_PREPROD}
- POSTGRES_DB=ota_preprod
- POSTGRES_HOST=postgres
- POSTGRES_PORT=5432
- PRIVATE_KEY_BASE_64=${PRIVATE_KEY_PREPROD}
- ADMIN_PASSWORD=${ADMIN_PASSWORD_PREPROD}
- UPLOAD_KEY=${UPLOAD_KEY_PREPROD}
volumes:
- ota_preprod_releases:/app/local-releases
networks:
- ota-net
depends_on:
postgres:
condition: service_healthy
ota-omnex-cli:
image: xaviaio/xavia-ota:latest
container_name: ota-omnex-cli
restart: unless-stopped
environment:
- HOST=https://ota-omnex-cli.uber-stup.club
- BLOB_STORAGE_TYPE=local
- DB_TYPE=postgres
- POSTGRES_USER=ota_omnex_cli
- POSTGRES_PASSWORD=${POSTGRES_PASS_OMNEX_CLI}
- POSTGRES_DB=ota_omnex_cli
- POSTGRES_HOST=postgres
- POSTGRES_PORT=5432
- PRIVATE_KEY_BASE_64=${PRIVATE_KEY_OMNEX_CLI}
- ADMIN_PASSWORD=${ADMIN_PASSWORD_OMNEX_CLI}
- UPLOAD_KEY=${UPLOAD_KEY_OMNEX_CLI}
volumes:
- ota_omnex_cli_releases:/app/local-releases
networks:
- ota-net
depends_on:
postgres:
condition: service_healthy
ota-omnex-admin:
image: xaviaio/xavia-ota:latest
container_name: ota-omnex-admin
restart: unless-stopped
environment:
- HOST=https://ota-omnex-admin.uber-stup.club
- BLOB_STORAGE_TYPE=local
- DB_TYPE=postgres
- POSTGRES_USER=ota_omnex_admin
- POSTGRES_PASSWORD=${POSTGRES_PASS_OMNEX_ADMIN}
- POSTGRES_DB=ota_omnex_admin
- POSTGRES_HOST=postgres
- POSTGRES_PORT=5432
- PRIVATE_KEY_BASE_64=${PRIVATE_KEY_OMNEX_ADMIN}
- ADMIN_PASSWORD=${ADMIN_PASSWORD_OMNEX_ADMIN}
- UPLOAD_KEY=${UPLOAD_KEY_OMNEX_ADMIN}
volumes:
- ota_omnex_admin_releases:/app/local-releases
networks:
- ota-net
depends_on:
postgres:
condition: service_healthy
ota-mobile-prod:
image: xaviaio/xavia-ota:latest
container_name: ota-mobile-prod
restart: unless-stopped
environment:
- HOST=https://ota-mobile-prod.uber-stup.club
- BLOB_STORAGE_TYPE=local
- DB_TYPE=postgres
- POSTGRES_USER=ota_mobile_prod
- POSTGRES_PASSWORD=${POSTGRES_PASS_MOBILE_PROD}
- POSTGRES_DB=ota_mobile_prod
- POSTGRES_HOST=postgres
- POSTGRES_PORT=5432
- PRIVATE_KEY_BASE_64=${PRIVATE_KEY_MOBILE_PROD}
- ADMIN_PASSWORD=${ADMIN_PASSWORD_MOBILE_PROD}
- UPLOAD_KEY=${UPLOAD_KEY_MOBILE_PROD}
volumes:
- ota_mobile_prod_releases:/app/local-releases
networks:
- ota-net
depends_on:
postgres:
condition: service_healthy
ota-mobile-preprod:
image: xaviaio/xavia-ota:latest
container_name: ota-mobile-preprod
restart: unless-stopped
environment:
- HOST=https://ota-mobile-preprod.uber-stup.club
- BLOB_STORAGE_TYPE=local
- DB_TYPE=postgres
- POSTGRES_USER=ota_mobile_preprod
- POSTGRES_PASSWORD=${POSTGRES_PASS_MOBILE_PREPROD}
- POSTGRES_DB=ota_mobile_preprod
- POSTGRES_HOST=postgres
- POSTGRES_PORT=5432
- PRIVATE_KEY_BASE_64=${PRIVATE_KEY_MOBILE_PREPROD}
- ADMIN_PASSWORD=${ADMIN_PASSWORD_MOBILE_PREPROD}
- UPLOAD_KEY=${UPLOAD_KEY_MOBILE_PREPROD}
volumes:
- ota_mobile_preprod_releases:/app/local-releases
networks:
- ota-net
depends_on:
postgres:
condition: service_healthy
nginx:
image: nginx:alpine
container_name: ota-nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./certs:/etc/nginx/certs:ro
- certbot_www:/var/www/certbot:ro
networks:
- ota-net
depends_on:
- ota-prod
- ota-preprod
- ota-omnex-cli
- ota-omnex-admin
- ota-mobile-prod
- ota-mobile-preprod
dozzle-agent:
image: amir20/dozzle:latest
command: agent
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
ports:
- "0.0.0.0:7007:7007"
restart: unless-stopped
networks:
ota-net:
volumes:
postgres_data:
ota_prod_releases:
ota_preprod_releases:
ota_omnex_cli_releases:
ota_omnex_admin_releases:
ota_mobile_prod_releases:
ota_mobile_preprod_releases:
certbot_www:
+11
View File
@@ -0,0 +1,11 @@
#!/bin/bash
set -e
for pair in "ota_prod:${POSTGRES_PASS_PROD}" "ota_preprod:${POSTGRES_PASS_PREPROD}" "ota_omnex_cli:${POSTGRES_PASS_OMNEX_CLI}" "ota_omnex_admin:${POSTGRES_PASS_OMNEX_ADMIN}" "ota_mobile_prod:${POSTGRES_PASS_MOBILE_PROD}" "ota_mobile_preprod:${POSTGRES_PASS_MOBILE_PREPROD}"; do
db="${pair%%:*}"
pass="${pair##*:}"
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL
CREATE USER ${db} WITH PASSWORD '${pass}';
CREATE DATABASE ${db} OWNER ${db};
EOSQL
done
+168
View File
@@ -0,0 +1,168 @@
# =========================================================
# Redirection HTTP -> HTTPS pour les 4 sous-domaines
# (garde /.well-known/acme-challenge/ en clair pour certbot)
# =========================================================
server {
listen 80;
listen [::]:80;
server_name ota-prod.uber-stup.club ota-preprod.uber-stup.club ota-omnex-cli.uber-stup.club ota-omnex-admin.uber-stup.club ota-mobile-prod.uber-stup.club ota-mobile-preprod.uber-stup.club;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
allow all;
}
location / {
return 301 https://$host$request_uri;
}
}
# =========================================================
# ota-prod.uber-stup.club -> conteneur ota-prod:3000
# =========================================================
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name ota-prod.uber-stup.club;
ssl_certificate /etc/nginx/certs/ota-prod.uber-stup.club.crt;
ssl_certificate_key /etc/nginx/certs/ota-prod.uber-stup.club.key;
ssl_protocols TLSv1.2 TLSv1.3;
client_max_body_size 500m;
location / {
proxy_pass http://ota-prod:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# =========================================================
# ota-preprod.uber-stup.club -> conteneur ota-preprod:3000
# =========================================================
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name ota-preprod.uber-stup.club;
ssl_certificate /etc/nginx/certs/ota-preprod.uber-stup.club.crt;
ssl_certificate_key /etc/nginx/certs/ota-preprod.uber-stup.club.key;
ssl_protocols TLSv1.2 TLSv1.3;
client_max_body_size 500m;
location / {
proxy_pass http://ota-preprod:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# =========================================================
# ota-omnex-cli.uber-stup.club -> conteneur ota-omnex-cli:3000
# =========================================================
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name ota-omnex-cli.uber-stup.club;
ssl_certificate /etc/nginx/certs/ota-omnex-cli.uber-stup.club.crt;
ssl_certificate_key /etc/nginx/certs/ota-omnex-cli.uber-stup.club.key;
ssl_protocols TLSv1.2 TLSv1.3;
client_max_body_size 500m;
location / {
proxy_pass http://ota-omnex-cli:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# =========================================================
# ota-omnex-admin.uber-stup.club -> conteneur ota-omnex-admin:3000
# =========================================================
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name ota-omnex-admin.uber-stup.club;
ssl_certificate /etc/nginx/certs/ota-omnex-admin.uber-stup.club.crt;
ssl_certificate_key /etc/nginx/certs/ota-omnex-admin.uber-stup.club.key;
ssl_protocols TLSv1.2 TLSv1.3;
client_max_body_size 500m;
location / {
proxy_pass http://ota-omnex-admin:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# =========================================================
# ota-mobile-prod.uber-stup.club -> conteneur ota-mobile-prod:3000
# =========================================================
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name ota-mobile-prod.uber-stup.club;
ssl_certificate /etc/nginx/certs/ota-mobile-prod.uber-stup.club.crt;
ssl_certificate_key /etc/nginx/certs/ota-mobile-prod.uber-stup.club.key;
ssl_protocols TLSv1.2 TLSv1.3;
client_max_body_size 500m;
location / {
proxy_pass http://ota-mobile-prod:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# =========================================================
# ota-mobile-preprod.uber-stup.club -> conteneur ota-mobile-preprod:3000
# =========================================================
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name ota-mobile-preprod.uber-stup.club;
ssl_certificate /etc/nginx/certs/ota-mobile-preprod.uber-stup.club.crt;
ssl_certificate_key /etc/nginx/certs/ota-mobile-preprod.uber-stup.club.key;
ssl_protocols TLSv1.2 TLSv1.3;
client_max_body_size 500m;
location / {
proxy_pass http://ota-mobile-preprod:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
+22
View File
@@ -0,0 +1,22 @@
#!/bin/bash
set -e
docker run --rm \
-v ota_certbot_letsencrypt:/etc/letsencrypt \
-v ota_certbot_www:/var/www/certbot \
certbot/certbot renew --webroot -w /var/www/certbot --quiet
changed=0
for sub in ota-prod ota-preprod ota-omnex-cli ota-omnex-admin ota-mobile-prod ota-mobile-preprod; do
domain="${sub}.uber-stup.club"
if ! docker run --rm -v ota_certbot_letsencrypt:/etc/letsencrypt -v /opt/ota/certs:/out alpine \
sh -c "cmp -s /etc/letsencrypt/live/${domain}/fullchain.pem /out/${domain}.crt"; then
docker run --rm -v ota_certbot_letsencrypt:/etc/letsencrypt -v /opt/ota/certs:/out alpine sh -c \
"cp /etc/letsencrypt/live/${domain}/fullchain.pem /out/${domain}.crt && cp /etc/letsencrypt/live/${domain}/privkey.pem /out/${domain}.key"
changed=1
fi
done
if [ "$changed" = "1" ]; then
cd /opt/ota && docker compose up -d --force-recreate nginx
fi
+2
View File
@@ -0,0 +1,2 @@
RUSTFS_ACCESS_KEY=iBacJPHUHSf6SMTnxrr
RUSTFS_SECRET_KEY=4jNzZhstv1WkwTwtvR4gHSbrx01m4sJl4LBBleeJ
+141
View File
@@ -0,0 +1,141 @@
#!/usr/bin/env python3
"""
Script de dump PostgreSQL + upload vers MinIO
SDK officiel MinIO + contournement certificat auto-signe
"""
import os
import ssl
import subprocess
import sys
from pathlib import Path
import urllib3
# Configuration PostgreSQL
PG_VERSION = "16"
DB_HOST = "localhost"
DB_PORT = 5432
DB_NAME = "gestion_commande_db"
DB_USER = "admin_gestion_commande_db"
DB_PASSWORD = "1SWDxH20rV7K2Uc2PNlwCaCxfVZEtKomF0CK9OMh"
# Configuration MinIO
MINIO_ENDPOINT = "10.0.0.4:9000"
MINIO_ACCESS_KEY = "admin"
MINIO_SECRET_KEY = "admin@12345"
MINIO_BUCKET = "backup-db-prod-mln"
MINIO_OBJECT = f"postgresql/{DB_NAME}.dump"
MINIO_SECURE = False # HTTPS
# Fichier local temporaire
OUTPUT_DIR = Path("./dumps")
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
DUMP_FILE = OUTPUT_DIR / f"{DB_NAME}.dump"
def find_pg_dump() -> str:
candidates = [
f"/usr/lib/postgresql/{PG_VERSION}/bin/pg_dump",
f"/usr/pgsql-{PG_VERSION}/bin/pg_dump",
"/usr/bin/pg_dump",
"/usr/local/bin/pg_dump",
]
for path in candidates:
if Path(path).exists():
return path
result = subprocess.run(["which", "pg_dump"], capture_output=True, text=True)
if result.returncode == 0:
return result.stdout.strip()
sys.exit("pg_dump introuvable.")
def run_dump(pg_dump: str) -> None:
env = os.environ.copy()
env["PGPASSWORD"] = DB_PASSWORD
cmd = [
pg_dump,
"--host",
DB_HOST,
"--port",
str(DB_PORT),
"--username",
DB_USER,
"--dbname",
DB_NAME,
"--format",
"custom",
"--compress",
"9",
"--verbose",
"--file",
str(DUMP_FILE),
]
print(f"[1/2] Dump -> {DUMP_FILE}")
result = subprocess.run(cmd, env=env, capture_output=True, text=True)
if result.stderr:
print(result.stderr)
if result.returncode != 0:
sys.exit(f"Echec du dump (code {result.returncode})")
size_mb = DUMP_FILE.stat().st_size / 1024 / 1024
print(f" OK ({size_mb:.2f} Mo)")
def upload_to_minio() -> None:
from minio import Minio
print(f"[2/2] Upload -> {MINIO_ENDPOINT}/{MINIO_BUCKET}/{MINIO_OBJECT}")
# Contexte SSL qui accepte les certificats auto-signes
ssl_ctx = ssl.create_default_context()
ssl_ctx.check_hostname = False
ssl_ctx.verify_mode = ssl.CERT_NONE
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
http_client = urllib3.PoolManager(
ssl_context=ssl_ctx,
cert_reqs="CERT_NONE",
)
client = Minio(
MINIO_ENDPOINT,
access_key=MINIO_ACCESS_KEY,
secret_key=MINIO_SECRET_KEY,
secure=MINIO_SECURE,
http_client=http_client,
)
if not client.bucket_exists(MINIO_BUCKET):
client.make_bucket(MINIO_BUCKET)
print(f" Bucket '{MINIO_BUCKET}' cree.")
client.fput_object(
bucket_name=MINIO_BUCKET,
object_name=MINIO_OBJECT,
file_path=str(DUMP_FILE),
content_type="application/octet-stream",
)
print(f" OK : s3://{MINIO_BUCKET}/{MINIO_OBJECT}")
DUMP_FILE.unlink()
print(f" Fichier local supprime.")
if __name__ == "__main__":
try:
from minio import Minio # noqa
except ImportError:
sys.exit("SDK MinIO manquant : pip install minio")
pg_dump_bin = find_pg_dump()
print(f"pg_dump : {pg_dump_bin}\n")
run_dump(pg_dump_bin)
upload_to_minio()
print("\nBackup termine avec succes !")
print(f" s3://{MINIO_BUCKET}/{MINIO_OBJECT}")
+104
View File
@@ -0,0 +1,104 @@
services:
nginx:
image: nginx:alpine
container_name: s3_nginx
restart: unless-stopped
ports:
- "10.0.0.4:80:80"
- "10.0.0.4:8080:8080"
- "10.0.0.4:9000:9000"
- "10.0.0.4:9001:9001"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./certs:/etc/nginx/certs:ro
- nginx_logs:/var/log/nginx
depends_on:
- minio
networks:
- s3_net
minio:
image: minio/minio:latest
container_name: s3_minio
restart: unless-stopped
hostname: minio
volumes:
- /mnt/data:/data
environment:
- MINIO_ROOT_USER=admin
- MINIO_ROOT_PASSWORD=admin@12345
- MINIO_ADDRESS=:9000
- MINIO_CONSOLE_ADDRESS=:9001
command: minio server /data --console-address :9001
networks:
- s3_net
dozzle-agent:
image: amir20/dozzle:latest
command: agent
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
ports:
- "10.0.0.4:7007:7007"
restart: unless-stopped
beszel-agent:
image: henrygd/beszel-agent:latest
container_name: beszel_agent
restart: unless-stopped
network_mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
PORT: "10001"
KEY: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEwRVn66crRDX5iL0bxNOnrp3OhTiXHKs+COx/bo5vmQ beszel-agent"
# ─── ClamAV Antivirus ─────────────────────────────────────────
clamav:
image: clamav/clamav:stable
container_name: s3_clamav
restart: unless-stopped
environment:
- FRESHCLAM_CHECKS=24
- CLAMD_SCAN_MAX_FILESIZE=200M
- CLAMD_SCAN_MAX_SCANSIZE=200M
volumes:
- clamav_db:/var/lib/clamav
- clamav_logs:/var/log/clamav
networks:
- s3_net
clamav-scanner:
image: clamav/clamav:stable
container_name: s3_clamav_scanner
restart: unless-stopped
depends_on:
- clamav
volumes:
- clamav_db:/var/lib/clamav:ro
- clamav_scan_logs:/var/log/clamav
- /mnt/data:/scan:ro
entrypoint: >
/bin/sh -c "
echo 'Waiting for virus definitions...' &&
until [ -f /var/lib/clamav/daily.cvd ] || [ -f /var/lib/clamav/daily.cld ]; do sleep 10; done &&
while true; do
DATE=$$(date +%Y%m%d_%H%M%S) &&
LOG=/var/log/clamav/scan-$$DATE.log &&
echo \"[$$DATE] Scan started\" | tee -a $$LOG &&
clamscan --recursive --infected --log=$$LOG /scan &&
echo \"[$$DATE] Scan complete\" | tee -a $$LOG &&
sleep 86400;
done"
networks:
- s3_net
networks:
s3_net:
driver: bridge
volumes:
nginx_logs:
clamav_db:
clamav_logs:
clamav_scan_logs:
+96
View File
@@ -0,0 +1,96 @@
worker_processes auto;
events {
worker_connections 1024;
}
http {
resolver 127.0.0.11 valid=10s ipv6=off;
# ── MinIO Console GUI HTTPS (VPN: 10.0.0.4:8080) ────────
server {
listen 8080 ssl;
server_name 10.0.0.4 _;
ssl_certificate /etc/nginx/certs/cert.pem;
ssl_certificate_key /etc/nginx/certs/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
access_log /var/log/nginx/minio_console_access.log;
error_log /var/log/nginx/minio_console_error.log;
location / {
proxy_pass http://minio:9001;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_buffering off;
proxy_read_timeout 300s;
client_max_body_size 500m;
}
}
# ── HTTP Redirect to HTTPS (VPN: 10.0.0.4:80) ────────────
server {
listen 80;
server_name _;
location / {
return 301 https://$host:8080$request_uri;
}
}
# ── MinIO Console Direct HTTPS (VPN: 10.0.0.4:9001) ──────
server {
listen 9001 ssl;
server_name 10.0.0.4 _;
ssl_certificate /etc/nginx/certs/cert.pem;
ssl_certificate_key /etc/nginx/certs/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
access_log /var/log/nginx/minio_console_direct_access.log;
error_log /var/log/nginx/minio_console_direct_error.log;
location / {
proxy_pass http://minio:9001;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_buffering off;
proxy_read_timeout 300s;
client_max_body_size 500m;
}
}
# ── MinIO S3 API HTTP (VPN: 10.0.0.4:9000) ────────────────
server {
listen 9000;
server_name 10.0.0.4 _;
access_log /var/log/nginx/minio_s3_api_access.log;
error_log /var/log/nginx/minio_s3_api_error.log;
location / {
proxy_pass http://minio:9000;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto http;
proxy_buffering off;
proxy_read_timeout 300s;
client_max_body_size 2g;
}
}
}
+2
View File
@@ -0,0 +1,2 @@
RUSTFS_ACCESS_KEY=JJg3uFeuKUUrdqq4oefQ8sIp6hnI
RUSTFS_SECRET_KEY=7wPrOd1324t411q7VnuzfNkQXCch4U5qqOHx
+62
View File
@@ -0,0 +1,62 @@
services:
nginx:
image: nginx:alpine
container_name: s3_nginx
restart: unless-stopped
ports:
- "80:80"
- "9000:9000"
- "9001:9001"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/certs:/etc/nginx/certs:ro
- nginx_logs:/var/log/nginx
depends_on:
- rustfs
networks:
- s3_net
rustfs_perm:
image: alpine:latest
container_name: rustfs_perm
volumes:
- /mnt/logs:/logs
- /mnt/data:/data
command: sh -c "chown -R 10001:10001 /data /logs"
rustfs:
image: rustfs/rustfs:latest
depends_on:
rustfs_perm:
condition: service_completed_successfully
hostname: rustfs
container_name: rustfs
restart: unless-stopped
volumes:
- /mnt/data:/data
- /mnt/logs:/logs
environment:
- RUSTFS_ACCESS_KEY=${RUSTFS_ACCESS_KEY:-JJg3uFeuKUUrdqq4oefQ8sIp6hnI}
- RUSTFS_SECRET_KEY=${RUSTFS_SECRET_KEY:-7wPrOd1324t411q7VnuzfNkQXCch4U5qqOHx}
- RUSTFS_CONSOLE_ENABLE=true
- RUSTFS_ADDRESS=0.0.0.0:9000
- RUSTFS_CONSOLE_ADDRESS=0.0.0.0:9001
- RUSTFS_OBS_LOGGER_LEVEL=debug
networks:
- s3_net
dozzle-agent:
image: amir20/dozzle:latest
command: agent
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
ports:
- "7007:7007"
restart: unless-stopped
networks:
s3_net:
driver: bridge
volumes:
nginx_logs:
+53
View File
@@ -0,0 +1,53 @@
worker_processes auto;
events {
worker_connections 1024;
}
http {
resolver 127.0.0.11 valid=10s ipv6=off;
server {
listen 80;
server_name _;
return 301 http://$host:9001$request_uri;
}
# ── RustFS Console ──────────────────────────────────────
server {
listen 9001;
server_name _;
access_log /var/log/nginx/rustfs_access.log;
error_log /var/log/nginx/rustfs_error.log;
location / {
set $upstream http://rustfs:9001;
proxy_pass $upstream;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 300s;
client_max_body_size 500m;
}
}
# ── RustFS S3 API ───────────────────────────────────────
server {
listen 9000;
server_name _;
access_log /var/log/nginx/rustfs_s3_access.log;
error_log /var/log/nginx/rustfs_s3_error.log;
location / {
set $upstream http://rustfs:9000;
proxy_pass $upstream;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_cache_convert_head off;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 300s;
client_max_body_size 2g;
}
}
}
+9
View File
@@ -0,0 +1,9 @@
RUSTFS_ACCESS_KEY=iBacJPHUHSf6SMTnxrr
RUSTFS_SECRET_KEY=4jNzZhstv1WkwTwtvR4gHSbrx01m4sJl4LBBleeJ
XAVIA_ADMIN_PASSWORD=2VYfheOweyMiP510GGfjNYmq3vJGJ
XAVIA_UPLOAD_KEY=dNW2tiNXRR6w1cLwlmCOyKFIKvoTz8xQMSyd
XAVIA_PRIVATE_KEY_BASE_64=MlRvcGlyNHpFRjhYOTlvUjMxbHFCVHRQdjYyelh4Wnc0MDM4TkZaVQ==
XAVIA_POSTGRES_USER=xavia
XAVIA_POSTGRES_PASSWORD=6DzLcsb5rgWukuGdrHtRBQ222f
XAVIA_POSTGRES_DB=releases_db
+115
View File
@@ -0,0 +1,115 @@
services:
nginx:
image: nginx:alpine
container_name: s3_nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/certs:/etc/nginx/certs:ro
- nginx_logs:/var/log/nginx
depends_on:
- rustfs
- xavia
- gitea
networks:
- s3_net
rustfs_perm:
image: alpine:latest
container_name: rustfs_perm
volumes:
- /mnt/data:/data
command: chown -R 10001:10001 /data
rustfs:
image: rustfs/rustfs:latest
depends_on:
rustfs_perm:
condition: service_completed_successfully
hostname: rustfs
container_name: rustfs
restart: unless-stopped
volumes:
- /mnt/data:/data
- /mnt/logs:/logs
environment:
- RUSTFS_ACCESS_KEY=${RUSTFS_ACCESS_KEY:-iBacJPHUHSf6SMTnxrr}
- RUSTFS_SECRET_KEY=${RUSTFS_SECRET_KEY:-4jNzZhstv1WkwTwtvR4gHSbrx01m4sJl4LBBleeJ}
- RUSTFS_SERVER_DOMAINS=rustfs.uber-stup.club
- RUSTFS_CONSOLE_ENABLE=true
- RUSTFS_ADDRESS=:9000
- RUSTFS_CONSOLE_ADDRESS=:9001
networks:
- s3_net
xavia_db:
image: postgres:16-alpine
container_name: xavia_db
restart: unless-stopped
environment:
- POSTGRES_USER=${XAVIA_POSTGRES_USER:-xavia}
- POSTGRES_PASSWORD=${XAVIA_POSTGRES_PASSWORD}
- POSTGRES_DB=${XAVIA_POSTGRES_DB:-releases_db}
volumes:
- xavia_db_data:/var/lib/postgresql/data
- ./init-db:/docker-entrypoint-initdb.d:ro
networks:
- s3_net
xavia:
image: xaviaio/xavia-ota:latest
container_name: xavia
restart: unless-stopped
environment:
- HOST=https://ota.uber-stup.club
- BLOB_STORAGE_TYPE=local
- DB_TYPE=postgres
- ADMIN_PASSWORD=${XAVIA_ADMIN_PASSWORD}
- UPLOAD_KEY=${XAVIA_UPLOAD_KEY}
- PRIVATE_KEY_BASE_64=${XAVIA_PRIVATE_KEY_BASE_64}
- POSTGRES_USER=${XAVIA_POSTGRES_USER:-xavia}
- POSTGRES_PASSWORD=${XAVIA_POSTGRES_PASSWORD}
- POSTGRES_DB=${XAVIA_POSTGRES_DB:-releases_db}
- POSTGRES_HOST=xavia_db
- POSTGRES_PORT=5432
volumes:
- xavia_blobs:/app/blobs
depends_on:
- xavia_db
networks:
- s3_net
gitea:
image: docker.gitea.com/gitea:latest
container_name: gitea
environment:
- USER_UID=1000
- USER_GID=1000
restart: always
volumes:
- gitea:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
networks:
- s3_net
dozzle-agent:
image: amir20/dozzle:latest
command: agent
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
ports:
- "7007:7007"
restart: unless-stopped
networks:
s3_net:
driver: bridge
volumes:
nginx_logs:
xavia_db_data:
xavia_blobs:
gitea:
+9
View File
@@ -0,0 +1,9 @@
CREATE TABLE IF NOT EXISTS releases (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
runtime_version VARCHAR(255) NOT NULL,
path VARCHAR(255) NOT NULL,
timestamp TIMESTAMP NOT NULL,
commit_hash VARCHAR(255) NOT NULL,
commit_message VARCHAR(255) NOT NULL,
update_id VARCHAR(255)
);
+14
View File
@@ -0,0 +1,14 @@
CREATE TABLE IF NOT EXISTS releases_tracking (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
release_id UUID NOT NULL REFERENCES releases(id),
download_timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
platform VARCHAR(50) NOT NULL,
CONSTRAINT fk_release
FOREIGN KEY(release_id)
REFERENCES releases(id)
ON DELETE CASCADE
);
-- Index for faster queries on release_id and timestamp
CREATE INDEX idx_tracking_release_id ON releases_tracking(release_id);
CREATE INDEX idx_tracking_platform ON releases_tracking(platform);
+125
View File
@@ -0,0 +1,125 @@
worker_processes auto;
events {
worker_connections 1024;
}
http {
resolver 127.0.0.11 valid=10s ipv6=off;
# Redirect HTTP -> HTTPS
server {
listen 80;
server_name _;
return 301 https://$host$request_uri;
}
# ── RustFS Console ──────────────────────────────────────
server {
listen 443 ssl;
server_name rustfs.uber-stup.club;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
access_log /var/log/nginx/rustfs_access.log;
error_log /var/log/nginx/rustfs_error.log;
location / {
set $upstream http://rustfs:9001;
proxy_pass $upstream;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 300s;
client_max_body_size 500m;
}
}
# ── RustFS S3 API ───────────────────────────────────────
server {
listen 443 ssl;
server_name s3.uber-stup.club;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
access_log /var/log/nginx/rustfs_s3_access.log;
error_log /var/log/nginx/rustfs_s3_error.log;
location / {
set $upstream http://rustfs:9000;
proxy_pass $upstream;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 300s;
client_max_body_size 2g;
}
}
# ── Xavia OTA ───────────────────────────────────────────
server {
listen 443 ssl;
server_name ota.uber-stup.club;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
access_log /var/log/nginx/xavia_access.log;
error_log /var/log/nginx/xavia_error.log;
location / {
set $upstream http://xavia:3000;
proxy_pass $upstream;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 300s;
client_max_body_size 500m;
}
}
server {
listen 443 ssl;
server_name gitea.uber-stup.club;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
access_log /var/log/nginx/gitea_access.log;
error_log /var/log/nginx/gitea_error.log;
location / {
set $upstream http://gitea:3000;
proxy_pass $upstream;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 300s;
client_max_body_size 500m;
}
}
}
+8
View File
@@ -0,0 +1,8 @@
# Dozzle
DOZZLE_USERNAME=admin_mln_monitoring
DOZZLE_PASSWORD=dX753n05Jo4T6B66cR
# Wazuh — changer TOUS ces mots de passe avant de déployer
WAZUH_INDEXER_PASSWORD=9qB523OjLyFSp6vMpH!
WAZUH_API_PASSWORD=myAJYqnemF544nsD33!
WAZUH_DASHBOARD_PASSWORD=E9Jpr6586kQ3wYrCS2!
+283
View File
@@ -0,0 +1,283 @@
# ClamAV Integration with Wazuh
Integration guide for deploying ClamAV antivirus with Wazuh monitoring stack.
## Overview
ClamAV scans files and containers for malware, while Wazuh collects and analyzes ClamAV logs for:
- Real-time malware detection alerts
- Ransomware detection
- Signature database update status
- Scan statistics and quarantine actions
---
## Architecture
```
┌─────────────────────────────────────┐
│ ClamAV Container (clamav) │
│ • Scans Docker containers/files │
│ • Generates logs: clamav.log │
│ • Freshclam updates signatures │
└──────────────┬──────────────────────┘
│ (logs volume)
┌─────────────────────────────────────┐
│ Wazuh Manager (wazuh.manager) │
│ • Collects ClamAV logs │
│ • Parses with decoders │
│ • Matches detection rules │
│ • Sends alerts to indexer │
└──────────────┬──────────────────────┘
│ (syslog format)
┌─────────────────────────────────────┐
│ Wazuh Dashboard (UI) │
│ • Display malware detections │
│ • Show scan results │
│ • Alert severity levels │
└─────────────────────────────────────┘
```
---
## Deployment
### 1. Add ClamAV to docker-compose
The following service has been added to `docker-compose-security.yml`:
```yaml
clamav:
image: clamav/clamav:stable
container_name: clamav
restart: unless-stopped
environment:
- FRESHCLAM_CHECKS=24
- CLAMD_SCAN_MAX_FILESIZE=100M
volumes:
- clamav_data:/var/lib/clamav # Signature database
- clamav_logs:/var/log/clamav # Log output
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- monitoring_net
```
### 2. Deploy ClamAV
```bash
cd /home/ubuntu/docker
docker compose -f docker-compose-security.yml up -d clamav
# Verify deployment
docker ps | grep clamav
docker logs clamav
```
### 3. Wazuh Configuration
#### Log Collection
File: `wazuh/config/wazuh_manager/localfile_clamav.conf`
Configures Wazuh to collect:
- `clamav.log` — Main scanner logs
- `freshclam.log` — Signature update logs
- `alert.log` — High-priority detections
#### Detection Rules
File: `wazuh/config/wazuh_manager/rules/clamav_rules.xml`
Key rules (level/priority):
| Rule ID | Level | Trigger | Example |
|---------|-------|---------|---------|
| 100501 | 15 | FOUND/Infected | Malware detected |
| 100502 | 12 | Trojan/PUA | Potentially unwanted app |
| 100503 | 15 | .Virus/Worm | Classic virus detection |
| 100504 | 15 | Ransomware | Ransomware families |
| 100505 | 3 | Update successful | Signature DB refreshed |
| 100506 | 10 | Update failed | Download/connection error |
| 100507 | 8 | Daemon error | ClamAV process error |
| 100515 | 16 | Multiple hits in 1h | Possible outbreak |
---
## Log Format
### ClamAV Log Example
```
Jun 10 14:35:22 clamav clamd[1234]: /var/data/suspect.exe: Trojan.Win32.Generic!c FOUND
Jun 10 14:35:25 clamav freshclam[5678]: ClamAV update completed. 1 databases updated.
Jun 10 14:36:01 clamav clamd[1234]: Scanning started
Jun 10 14:36:45 clamav clamd[1234]: Scanning finished. 2 files scanned, 0 threats found
```
### Wazuh Alert Example
```json
{
"timestamp": "2026-06-10T14:35:22.000Z",
"rule": {
"id": "100501",
"level": 15,
"description": "ClamAV: Malware detected"
},
"data": {
"srcfile": "/var/data/suspect.exe",
"alert": "Trojan.Win32.Generic!c FOUND"
},
"groups": ["malware", "clamav", "infection"]
}
```
---
## Operations
### Check ClamAV Status
```bash
# Container status
docker ps | grep clamav
# View logs
docker logs clamav
# Manual scan
docker exec clamav clamscan -r /var/data
```
### Update Signatures
ClamAV automatically updates signatures (configured via `FRESHCLAM_CHECKS=24`).
Manual update:
```bash
docker exec clamav freshclam
```
### View Wazuh Alerts
1. Access Wazuh Dashboard: `https://10.0.0.2`
2. Go to: **Security Events****Search**
3. Filter by:
- `rule.id: 100501` — Malware detections
- `rule.id: 100504` — Ransomware alerts
- `rule.groups: malware` — All malware-related events
### Query via API
```bash
# Get recent malware detections
curl -k -H "Authorization: Bearer YOUR_TOKEN" \
"https://monitoring-uber:55000/api/v0/search?q=rule.id:100501&pretty"
# Get ClamAV scan statistics
curl -k -H "Authorization: Bearer YOUR_TOKEN" \
"https://monitoring-uber:55000/api/v0/search?q=group:clamav&pretty"
```
---
## Tuning & Optimization
### Scan Performance
Adjust scan parameters in docker-compose environment:
```yaml
environment:
- CLAMD_SCAN_MAX_FILESIZE=100M # Max file size to scan
- CLAMD_MAX_SCAN_SIZE=200M # Max total scan size
- CLAMD_SCAN_PE_PLUS=yes # Enhanced PE detection
- CLAMD_SCAN_ARCHIVE=yes # Scan inside archives
- CLAMD_MAX_FILES=10000 # Max files to scan
```
### Update Frequency
Default: 24 checks per day (every hour)
Change via:
```yaml
environment:
- FRESHCLAM_CHECKS=48 # 2-hourly updates
```
### Alert Severity
Adjust rule levels in `clamav_rules.xml` based on your risk tolerance:
- Level 3-6: Info/Low
- Level 8-10: Medium
- Level 12-15: High
- Level 16+: Critical/Outbreak
---
## Troubleshooting
### ClamAV Not Scanning
```bash
# Check daemon is running
docker exec clamav ps aux | grep clamd
# Check signatures are loaded
docker exec clamav clamscan --version
# Manually scan
docker exec clamav clamscan /var/data
```
### Signature Updates Failing
```bash
# Check freshclam logs
docker logs clamav | grep freshclam
# Manual update with verbose output
docker exec clamav freshclam -v
# Check internet connectivity
docker exec clamav wget https://cvd.clamav.net
```
### Wazuh Not Receiving Logs
```bash
# Check logs are being generated
docker exec clamav tail -f /var/log/clamav/clamav.log
# Check Wazuh log collection
docker exec wazuh_manager tail -f /var/ossec/logs/ossec.log | grep clamav
# Verify rules loaded
docker exec wazuh_manager cat /var/ossec/etc/rules/clamav_rules.xml | head -10
```
---
## Files Modified/Created
| File | Purpose |
|------|---------|
| `docker-compose-security.yml` | Added ClamAV service + volumes |
| `wazuh/config/wazuh_manager/localfile_clamav.conf` | Log collection config |
| `wazuh/config/wazuh_manager/rules/clamav_rules.xml` | Malware detection rules |
| `CLAMAV_INTEGRATION.md` | This documentation |
---
## Next Steps
1. Deploy: `docker compose up -d clamav`
2. Wait for initial database download (5-10 minutes)
3. Monitor logs: `docker logs -f clamav`
4. Check Wazuh dashboard for alerts
5. Configure scan schedules/locations as needed
6. Set up active response (optional) for automatic quarantine
+165
View File
@@ -0,0 +1,165 @@
# Firewall Configuration Rules
Updated: 2026-06-10
## Overview
Firewall rules for monitoring stack with Wazuh, Dozzle, Beszel, and VPN infrastructure.
---
## monitoring-uber (185.103.167.138)
### Inbound Rules (INPUT)
| Port | Protocol | Source | Purpose | Status |
|------|----------|--------|---------|--------|
| 22 | TCP | Admin IPs | SSH administration | ALLOW |
| 22 | TCP | 185.103.166.119 (prod) | SSH — BLOCKED | **DROP** |
| 22 | TCP | 185.103.166.112 (pre-prod) | SSH — BLOCKED | **DROP** |
| 22 | TCP | 80.96.58.164 (s3) | SSH — BLOCKED | **DROP** |
| 1514 | TCP | 185.103.166.119 | Wazuh agents (prod) | ALLOW |
| 1514 | TCP | 185.103.166.112 | Wazuh agents (pre-prod) | ALLOW |
| 51820 | UDP | Any | WireGuard VPN | ALLOW |
| 443 | TCP | 10.0.0.0/24 | Wazuh Dashboard (VPN) | ALLOW |
| 8080 | TCP | 10.0.0.0/24 | Dozzle logs (VPN) | ALLOW |
| 8090 | TCP | 10.0.0.0/24 | Beszel monitoring (VPN) | ALLOW |
| 9000-9001 | TCP | 10.0.0.0/24 | S3/RustFS (VPN) | ALLOW |
### Outbound Rules (OUTPUT)
- **Default:** ACCEPT (all traffic allowed)
- Allows DNS, updates, internet access
### Forward Rules (FORWARD)
- **Default:** DROP (no transit traffic)
### Persistence
- Rules saved to `/etc/iptables/rules.v4`
- Auto-loaded on boot via `iptables-persistent`
---
## vpn-uber (45.150.111.158)
### Inbound Rules (INPUT)
| Port | Protocol | Source | Purpose | Status |
|------|----------|--------|---------|--------|
| 22 | TCP | Any | SSH administration | ALLOW |
| 51820 | UDP | Any | WireGuard VPN | ALLOW |
| 10001 | TCP | 10.0.0.0/24 | Beszel agent (from VPN only) | ALLOW |
### Outbound Rules (OUTPUT)
- **Default:** ACCEPT (all traffic allowed)
### Forward Rules (FORWARD)
- **Default:** ACCEPT
- Enables routing: wg0 ↔ eth0 (VPN ↔ Internet)
- ESTABLISHED/RELATED always allowed
### Network Features
- NAT masquerade: `eth0` (POSTROUTING)
- IP forwarding: enabled (`net.ipv4.ip_forward=1`)
- Allows VPN clients to reach internet through VPN server
### Persistence
- Rules saved to `/etc/iptables/rules.v4`
- IP forwarding saved to `/etc/sysctl.conf`
---
## Security Hardening
### Principle: Least Privilege
1. **SSH restrictions**: Production servers (prod, pre-prod, s3) **cannot** SSH into monitoring-uber
- Prevents lateral movement if a prod server is compromised
- Admins must SSH directly to monitoring-uber or via VPN
2. **Service isolation**: All user-facing services (Wazuh, Dozzle, Beszel) accessible **only via VPN**
- Not exposed to public internet
- Network: 10.0.0.0/24 (private VPN)
3. **Port whitelist**: Only required ports open
- Wazuh agent ingest: 1514/tcp (prod/pre-prod only)
- VPN: 51820/udp (all)
- SSH: 22/tcp (admin access, blocked from prod servers)
- Monitoring agents: 10001/tcp (VPN only)
### Attack Surface Reduction
- Production agents cannot access monitoring infrastructure
- Monitoring dashboard only accessible via VPN
- No exposed dashboards or logs to public internet
- SSH brute-force: limited by rate (ESTABLISHED/RELATED state tracking)
---
## Updating Rules
### monitoring-uber
```bash
ssh root@185.103.167.138
./firewall-monitoring-uber.sh
```
### vpn-uber
```bash
ssh root@45.150.111.158
./firewall-vpn-server.sh
```
### Verify Rules Applied
```bash
# Check current rules
iptables -L INPUT -n
iptables -L FORWARD -n
# Check saved rules
cat /etc/iptables/rules.v4
```
### Restore Rules on Boot
Rules are automatically restored via `/etc/iptables/rules.v4` if `iptables-persistent` is installed:
```bash
apt-get install iptables-persistent
```
---
## Beszel Agent Configuration
Beszel agents installed on all infrastructure servers:
| Server | Type | Port | Access |
|--------|------|------|--------|
| prod-uber | Docker | 10001 | Public IP (185.103.166.119:10001) |
| pre-prod-uber | Docker | 10001 | Public IP (185.103.166.112:10001) |
| s3-uber | Docker | 10001 | Public IP (80.96.58.164:10001) |
| vpn-uber | Binarie (systemd) | 10001 | VPN IP (10.0.0.1:10001) |
**Note:** vpn-uber agent is accessible **only from VPN** due to firewall rules.
---
## Troubleshooting
### Service not reachable
```bash
# Check if service is listening
ss -tlnp | grep <port>
# Check firewall allows traffic
iptables -L INPUT -n | grep <port>
# Test connectivity
nc -zv <ip> <port>
```
### SSH access denied from prod
- Expected behavior (security hardening)
- Use admin SSH keys or SSH via VPN instead
### Beszel agent shows offline
- Check agent is listening: `ss -tlnp | grep 10001`
- Check firewall allows Beszel dashboard to reach agent
- Verify network routing between monitoring-uber and agent server
@@ -0,0 +1,246 @@
# OWASP Top 10 ModSecurity Rules — Wazuh Active Response
## Vue d'ensemble
Ce système détecte et répond aux attaques OWASP Top 10 via ModSecurity + Wazuh.
**Deux couches de détection :**
| Type | Trigger | Règle | Level | AR |
|------|---------|-------|-------|-----|
| **Bloqué (403)** | HTTP 403 | 100220-100281 | 12-15 | Fermer 4h-24h |
| **Remontée (Warning)** | Anomaly scoring | 100320-100381 | 6-11 | Fermer 30min-6h |
---
## Correspondances CRS ModSecurity
```
941xxx — XSS (Cross-Site Scripting)
942xxx — SQL Injection, LDAP Injection, etc.
943xxx — XXE (XML External Entity)
930xxx — Path Traversal, RFI, RCE, LFI
932xxx — Remote Command Execution
933xxx — PHP Injection
934xxx — Java Injection
950xxx — Exploit attempts
951xxx — Regex DoS, Scanner detection
952xxx — Restricted File Access
953xxx — Insecure File Upload
954xxx — Proxy abuse, Scanner detection
955xxx — HTTP Response Splitting, Header Injection
970xxx — SSRF (Server-Side Request Forgery)
971xxx — SSRF (alternative patterns)
```
---
## Règles par OWASP Category
### OWASP #1 : Broken Access Control
**Détecte :** Path Traversal, File Inclusion, Unauthorized Access
| Rule ID | Type | Patterns | Level | Description |
|---------|------|----------|-------|-------------|
| 100250 | Bloqué | 930xx (path\|traversal\|lfi) | 12 | LFI/Path Traversal bloquée |
| 100251 | Bloqué | 930xx (rfi\|remote\|include) | 13 | RFI bloquée |
| 100252 | Bloqué | 952xx | 11 | File Access bloquée |
| 100293 | Bloqué | Freq(100250)×4/180s | 12 | Path Traversal brute-force |
| 100350 | Remontée | 930xx (path\|traversal\|lfi) | 8 | LFI attempt (warning) |
| 100351 | Remontée | 930xx (rfi\|remote\|include) | 9 | RFI attempt (warning) |
| 100352 | Remontée | 952xx | 8 | File Access attempt (warning) |
**Active Response :**
- Bloqué : `firewall-drop 6h`
- Brute-force : `firewall-drop 24h`
- Remontée : `firewall-drop 2h-4h`
---
### OWASP #3 : Injection
**Détecte :** SQLi, LDAP, Command Injection, PHP/Java Injection
| Rule ID | Type | Patterns | Level | Description |
|---------|------|----------|-------|-------------|
| 100220 | Bloqué | 942xx | 13 | SQL Injection bloquée |
| 100221 | Bloqué | 942xx (LDAP pattern) | 12 | LDAP Injection bloquée |
| 100222 | Bloqué | 932xx | 14 | Command Injection bloquée |
| 100223 | Bloqué | 933xx | 13 | PHP Injection bloquée |
| 100224 | Bloqué | 934xx | 13 | Java Injection bloquée |
| 100290 | Bloqué | Freq(100220)×3/60s | 14 | SQLi brute-force |
| 100292 | Bloqué | Freq(100222)×2/60s | 15 | Command Injection CRITICAL |
| 100320 | Remontée | 942xx | 9 | SQLi attempt (warning) |
| 100321 | Remontée | 932xx | 10 | Command Injection attempt |
| 100322 | Remontée | 942xx (LDAP) | 9 | LDAP Injection attempt |
| 100323 | Remontée | 933xx\|934xx | 9 | PHP/Java Injection attempt |
| 100394 | Remontée | Freq(100320)×5/300s | 10 | SQLi anomaly scoring |
| 100396 | Remontée | Freq(100321)×2/60s | 12 | Command Injection CRITICAL |
**Active Response :**
- SQLi Bloquée : `firewall-drop 6h`
- SQLi Brute-force : `firewall-drop 24h + host-deny permanent`
- Command Injection : `firewall-drop 24h + host-deny`
- Command Injection CRITICAL : `firewall-drop permanent + host-deny permanent`
- SQLi Remontée : `firewall-drop 2h`
- SQLi Anomaly : `firewall-drop 4h`
- Command Injection Remontée : `firewall-drop 4h`
---
### OWASP #6 : Vulnerable & Outdated Components
**Détecte :** RCE, Exploit attempts, known vulnerabilities
| Rule ID | Type | Patterns | Level | Description |
|---------|------|----------|-------|-------------|
| 100230 | Bloqué | 930xx\|950xx | 14 | RCE/Exploit bloquée |
| 100330 | Remontée | 930xx\|950xx | 11 | RCE/Exploit attempt (warning) |
**Active Response :**
- Bloqué : `firewall-drop 24h`
- Remontée : `firewall-drop 6h`
---
### OWASP #7 : Authentication & Session Management
**Détecte :** XSS, Header Injection, Session hijacking attempts
| Rule ID | Type | Patterns | Level | Description |
|---------|------|----------|-------|-------------|
| 100240 | Bloqué | 941xx | 12 | XSS bloquée |
| 100241 | Bloqué | 955xx | 12 | Header Injection bloquée |
| 100291 | Bloqué | Freq(100240)×5/120s | 13 | XSS anomaly scoring |
| 100340 | Remontée | 941xx | 8 | XSS attempt (warning) |
| 100341 | Remontée | 955xx | 8 | Header Injection attempt |
| 100395 | Remontée | Freq(100340)×8/300s | 10 | XSS anomaly scoring |
**Active Response :**
- XSS Bloquée : `firewall-drop 4h`
- XSS Anomaly : `firewall-drop 6h`
- Header Injection : `firewall-drop 6h`
- XSS Remontée : `firewall-drop 1h`
- Header Remontée : `firewall-drop 2h`
---
### OWASP #8 : Software & Data Integrity Failures
**Détecte :** XXE, Deserialization attacks
| Rule ID | Type | Patterns | Level | Description |
|---------|------|----------|-------|-------------|
| 100260 | Bloqué | 943xx | 13 | XXE bloquée |
| 100360 | Remontée | 943xx | 9 | XXE attempt (warning) |
**Active Response :**
- Bloqué : `firewall-drop 12h`
- Remontée : `firewall-drop 4h`
---
### OWASP #9 : Logging & Monitoring Failures
**Détecte :** Web scanners, bot activity, reconnaissance
| Rule ID | Type | Patterns | Level | Description |
|---------|------|----------|-------|-------------|
| 100270 | Bloqué | 951xx\|954xx | 10 | Scanner detection (bloquée) |
| 100370 | Remontée | 951xx\|954xx | 6 | Scanner detection (warning) |
**Active Response :**
- Bloqué : `firewall-drop 2h`
- Remontée : `firewall-drop 30min`
---
### Autres : SSRF & File Upload
| Rule ID | Type | Patterns | Level | Description |
|---------|------|----------|-------|-------------|
| 100280 | Bloqué | 970xx\|971xx | 13 | SSRF bloquée |
| 100281 | Bloqué | 953xx | 12 | File Upload bloquée |
| 100380 | Remontée | 970xx\|971xx | 9 | SSRF attempt |
| 100381 | Remontée | 953xx | 8 | File Upload attempt |
**Active Response :**
- SSRF Bloquée : `firewall-drop 12h`
- File Upload Bloquée : `firewall-drop 6h`
- SSRF Remontée : `firewall-drop 4h`
- File Upload Remontée : `firewall-drop 2h`
---
## Timeline des Active Responses
### Bloquées (HTTP 403)
| Durée | Règles | Motif |
|-------|--------|-------|
| Permanent | 100292 | Command Injection CRITICAL (2+ tentatives / 60s) |
| 24h | 100290 | SQLi brute-force (3+ / 60s) |
| 24h | 100230 | RCE/Exploit |
| 12h | 100251, 100260, 100280 | RFI, XXE, SSRF |
| 6h | 100220, 100221, 100250, 100252, 100281, 100330 | SQLi, LDAP, Path Traversal, File Access, Upload, RCE |
| 4h | 100240 | XSS |
| 2h | 100270 | Scanner |
### Remontées (Anomaly Scoring)
| Durée | Règles | Motif |
|-------|--------|-------|
| Permanent | 100396 | Command Injection (2+ / 60s) |
| 6h | 100395, 100330, 100351 | XSS anomaly, RCE, RFI |
| 4h | 100394, 100321, 100323, 100360, 100380 | SQLi anomaly, Cmd Inj, PHP/Java, XXE, SSRF |
| 2h | 100320, 100322, 100341, 100350, 100352, 100381 | SQLi, LDAP, Header Inj, Path Traversal, File Access, Upload |
| 30min | 100370 | Scanner |
---
## Files
- **`modsecurity_owasp_rules.xml`** — Définition des règles (100220-100396)
- **`ossec.conf`** — Active-response configs (firewall-drop, durées)
- **`firewall-monitoring-uber.sh`** — iptables pour autoriser trafic ModSec
---
## Monitoring
**Dashboard Wazuh :**
1. **Alerts → Security Events → OWASP**
2. **Visualization:**
- Blocking rate (HTTP 403)
- Anomaly scoring trends
- Top attackers (srcip)
- Attack distribution by category
**Logs:**
```bash
# On monitoring-uber
tail -f /var/ossec/logs/alerts/alerts.json | grep -i "owasp\|100[23][0-9][0-9]"
```
---
## Tuning
Les seuils de fréquence peuvent être ajustés :
- **SQLi anomaly:** `frequency="5" timeframe="300"` → Réduire pour être plus agressif
- **XSS anomaly:** `frequency="8" timeframe="300"` → Augmenter pour réduire false positives
- **Command Injection:** `frequency="2" timeframe="60"` → Critique, ne pas modifier
---
## Notes
- **First-match semantics:** Wazuh évalue les règles par ID croissant → les règles bloquées (100220+) ont priorité sur les remontées (100320+)
- **Field matching:** Les patterns utilisent `<field name="transaction.messages.message">` pour matcher les CRS rule IDs dans les logs ModSecurity
- **SCA suppressed:** Les alertes SCA (CIS benchmark) sont à level 0 (voir `local_sca_noise.xml`)
- **Persistence:** Tous les AR sont sauvegardés dans `iptables-save` → survient les reboots
@@ -0,0 +1,258 @@
# VPN Deployment Summary
## Infrastructure
| Role | Hostname | IP Public | IP VPN | Status |
|------|----------|-----------|--------|--------|
| **VPN Server** | vpn-uber | 45.150.111.158 | 10.0.0.1/24 | ✓ Active |
| **Monitoring** | monitoring-uber | 185.103.167.138 | 10.0.0.2/24 | ✓ Connected |
| **Admins** | (clients) | — | 10.0.0.3+ | Ready |
---
## Server Credentials
### VPN Server (vpn-uber)
- **IP Public:** 45.150.111.158
- **Port:** 51820/udp
- **Server Private Key:** (secured at `/etc/wireguard/privatekey`)
- **Server Public Key:** `VQa6g1foaXhJgYWbwyJ9R/EAmmXg0nOnhPhbIIPLlmg=`
- **Network:** 10.0.0.0/24
- **Interface IP:** 10.0.0.1
**SSH Access:**
```bash
ssh root@45.150.111.158
# Password: yqZ98EJL1h3ISlfT6l (from host.ini)
```
### Monitoring Server (monitoring-uber)
- **IP Public:** 185.103.167.138
- **VPN IP:** 10.0.0.2/24
- **Status:** Connected to VPN ✓
- **Firewall:** Configured (iptables)
- **Interface:** wg0 UP
**SSH Access:**
```bash
ssh root@185.103.167.138
# Password: rL9lY6YkcDQmfRuZ3Z (from host.ini)
```
---
## VPN Configuration
### Firewall Rules
**VPN Server (vpn-uber):**
```
INPUT:
✓ 22/tcp (SSH)
✓ 51820/udp (WireGuard)
✗ Everything else → REJECT
OUTPUT:
✓ All
FORWARD:
✓ wg0 interface (VPN traffic)
✓ NAT masquerade (10.0.0.0/24 → Internet)
```
**Monitoring Server (monitoring-uber):**
```
INPUT:
✓ 1514/tcp FROM 185.103.166.119 (Prod agents)
✓ 1514/tcp FROM 185.103.166.112 (Pre-prod agents)
✓ 51820/udp (VPN)
✓ 443/tcp FROM 10.0.0.0/24 (Wazuh Dashboard)
✓ 8080/tcp FROM 10.0.0.0/24 (Dozzle)
✓ 9090/tcp FROM 10.0.0.0/24 (Beszel)
✓ 9000-9001/tcp FROM 10.0.0.0/24 (S3/RustFS)
✗ Everything else → DROP
OUTPUT:
✓ All (Internet)
```
### WireGuard Routing
```
VPN Server (10.0.0.1)
Monitoring (10.0.0.2) — Connected ✓
Admin Clients (10.0.0.3+) — Ready to connect
```
**Current Peers:**
```
peer: 4WLn2BR9ZGhM195mIvLbZR7tP/mb7ks4+mZ5ppZ21xw= (monitoring-uber)
allowed ips: 10.0.0.2/32
status: Connected ✓ (45ms latency, 0% loss)
```
---
## Services Accessible via VPN
Once connected to VPN from admin client:
| Service | URL | Port | Status |
|---------|-----|------|--------|
| **Wazuh Dashboard** | https://10.0.0.2 | 443 | ✓ (VPN only) |
| **Dozzle** | http://10.0.0.2:8080 | 8080 | ✓ (VPN only) |
| **Beszel** | http://10.0.0.2:9090 | 9090 | ✓ (VPN only) |
| **S3/RustFS Console** | http://10.0.0.2:9001 | 9001 | ✓ (VPN only) |
| **S3/RustFS API** | http://10.0.0.2:9000 | 9000 | ✓ (VPN only) |
**Mode:** VPN + Internet normal (no kill switch)
- Admins can access services via VPN
- Admins keep normal Internet access
- If VPN drops → automatic fallback to Internet
---
## Next Steps for Admins
### 1. Generate Client Configs
```bash
./wireguard-admin-client.sh admin1 45.150.111.158 "VQa6g1foaXhJgYWbwyJ9R/EAmmXg0nOnhPhbIIPLlmg="
```
Creates: `admin1.conf`
### 2. Add Client to VPN Server
SSH to VPN server:
```bash
ssh root@45.150.111.158
```
Then:
```bash
# Get client's public key from admin1.conf (PrivateKey → PublicKey)
wg set wg0 peer <CLIENT_PUBKEY> allowed-ips 10.0.0.3/32
wg show
```
### 3. Admin Setup
- Install WireGuard app (wireguard.com/install)
- Import admin1.conf
- Connect to VPN
- Test access to services
---
## Testing
### From Monitoring Server
```bash
# Test VPN connection
ping 10.0.0.1
# Response: 64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=45.0 ms
# Check VPN status
ip addr show wg0
ip route show | grep wg0
# VPN traffic
tcpdump -i wg0
```
### From VPN Server
```bash
# Monitor active connections
wg show
# Check interface
ip addr show wg0
# Verify firewall rules
iptables -L
iptables -L -t nat
```
---
## Troubleshooting
### Monitoring not connecting to VPN
```bash
# Check WireGuard daemon
ssh root@185.103.167.138
systemctl status wg-quick@wg0
journalctl -u wg-quick@wg0 -n 20
# Restart
systemctl restart wg-quick@wg0
```
### VPN Server not accepting peers
```bash
# On VPN server, verify WireGuard is running
wg show
# Check if peer was added correctly
wg show wg0
# If not showing, re-add:
wg set wg0 peer <CLIENT_PUBKEY> allowed-ips 10.0.0.X/32
```
### Admin client can't connect
- Verify VPN server is listening: `netstat -ulnp | grep 51820`
- Verify firewall allows 51820/udp: `ufw status` or `iptables -L`
- Check client config has correct server IP and pubkey
- Try reconnecting after 2-3 seconds
---
## Files & Scripts Used
- `firewall-vpn-server.sh` — VPN server firewall (iptables)
- `wireguard-server-setup.sh` — WireGuard server install + config
- `firewall-monitoring-uber.sh` — Monitoring firewall (iptables)
- `wireguard-client-setup.sh` — WireGuard client install
- `wireguard-admin-client.sh` — Generate admin client configs
- `VPN_SETUP.md` — Full setup guide (phases 1-7)
---
## Persistence
All configurations survive reboot:
- **Firewall:** `/etc/iptables/rules.v4` (loaded via iptables-restore)
- **WireGuard:** Enabled at boot via `systemctl enable wg-quick@wg0`
- **Routes:** Persistent in wg0 config
---
## Security Notes
- Firewall rules block unauthorized access
- WireGuard handshake: 45ms latency (acceptable)
- No kill switch enabled (VPN + Internet normal)
- Services (Wazuh, Dozzle, Beszel, S3) accessible ONLY via VPN
- Agents (prod/pre-prod) still have direct access to Wazuh manager (1514/tcp)
---
## Status
✓ VPN Server deployed on vpn-uber (45.150.111.158)
✓ Monitoring-uber connected to VPN (10.0.0.2)
✓ Firewall configured on both servers
✓ VPN connection tested (45ms, 0% loss)
✓ Ready for admin client setup
+306
View File
@@ -0,0 +1,306 @@
# VPN Setup for Monitoring Stack
## Architecture
```
Internet (public)
├─ [Prod/Pre-prod] (accessible)
│ └─→(1514/tcp)→ monitoring-uber (port ouvert)
├─ [VPN Server] (nouveau VPS, 1 CPU 2GB RAM)
│ └─ WireGuard 0.0.0.0:51820/udp
└─ [monitoring-uber] (185.103.167.138)
├─ Client VPN (10.0.0.2)
└─ Services: Wazuh, Dozzle, Beszel, S3 (VPN only)
VPN Network: 10.0.0.0/24
├─ VPN Server: 10.0.0.1
├─ monitoring-uber: 10.0.0.2
└─ Admins: 10.0.0.3+
```
## Configuration
**Mode:** VPN + Internet normal (pas de kill switch)
- Admins connectés au VPN → accès à services VPN (10.0.0.0/24)
- Admins gardent aussi accès à Internet normal (pas de restriction)
- Si VPN tombe → retrouvent Internet automatiquement
---
## Implementation Steps
### Phase 1: VPN Server Setup (nouveau VPS)
```bash
# 1. Login to VPS
ssh root@<VPN_SERVER_IP>
# 2. Run firewall setup
chmod +x /path/to/firewall-vpn-server.sh
./firewall-vpn-server.sh
# 3. Setup WireGuard server
chmod +x /path/to/wireguard-server-setup.sh
./wireguard-server-setup.sh
# Output will show:
# - Server Public Key (note this)
# - Example: aBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890AbCdE=
```
Save the **Server Public Key** — you'll need it for clients.
### Phase 2: monitoring-uber VPN Client
```bash
# 1. Login to monitoring-uber
ssh root@185.103.167.138
# 2. Run firewall setup
chmod +x /path/to/firewall-monitoring-uber.sh
./firewall-monitoring-uber.sh
# 3. Setup WireGuard client
# Syntax: wireguard-client-setup.sh <VPN_SERVER_IP> <VPN_SERVER_PUBKEY>
chmod +x /path/to/wireguard-client-setup.sh
./wireguard-client-setup.sh <VPN_SERVER_IP> "<SERVER_PUBKEY>"
# Example:
# ./wireguard-client-setup.sh 123.45.67.89 "aBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890AbCdE="
# Output will show:
# - Client Public Key (note this)
# - Example: XyZ9876543210AbCdEfGhIjKlMnOpQrStUvWxYz=
```
### Phase 3: Add monitoring-uber to VPN Server
```bash
# Back on VPS, add monitoring-uber as a peer
ssh root@<VPN_SERVER_IP>
# Use the Client Public Key from Phase 2
wg set wg0 peer <CLIENT_PUBKEY> allowed-ips 10.0.0.2/32
# Verify
wg show
# Example output:
# interface: wg0
# public key: aBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890AbCdE=
# private key: (hidden)
# listening port: 51820
#
# peer: XyZ9876543210AbCdEfGhIjKlMnOpQrStUvWxYz=
# endpoint: <monitoring-uber-ip>:xxxxx
# allowed ips: 10.0.0.2/32
# latest handshake: X seconds ago
# transfer: X B received, X B sent
```
### Phase 4: Verify VPN Connection
```bash
# On monitoring-uber
ping 10.0.0.1
# Should respond
# PING 10.0.0.1 (10.0.0.1) 56(84) bytes of data.
# 64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=XX.X ms
# Check interface
ip addr show wg0
```
### Phase 5: Rebind Services to VPN IP
```bash
# On monitoring-uber
chmod +x /path/to/rebind-services-to-vpn.sh
./rebind-services-to-vpn.sh
# This will update docker-compose-security.yml:
# - Wazuh Dashboard: 0.0.0.0:443 → 10.0.0.2:443
# - Dozzle: 0.0.0.0:8080 → 10.0.0.2:8080
# - Beszel: 0.0.0.0:9090 → 10.0.0.2:9090
# - S3/RustFS: 0.0.0.0:9000/9001 → 10.0.0.2:9000/9001
# - Wazuh Manager: stays on 0.0.0.0:1514 (for agents)
# Restart services
cd /home/ubuntu/docker
docker compose -f docker-compose-security.yml down
docker compose -f docker-compose-security.yml up -d
# Verify
docker ps
```
### Phase 6: Ajouter un admin VPN
La clé privée ne doit **jamais quitter la machine de l'admin**. La procédure se fait en deux temps.
#### Étape A — Sur la machine de l'admin (à faire par l'admin)
Installer WireGuard si besoin :
- Windows/Mac : https://www.wireguard.com/install/
- Linux : `sudo apt install wireguard`
Générer les clés **localement** :
```bash
# Linux / Mac
wg genkey | tee privatekey | wg pubkey > publickey
cat privatekey # à garder secret
cat publickey # à envoyer à l'administrateur VPN
```
```powershell
# Windows (PowerShell, WireGuard installé)
cd "C:\Program Files\WireGuard"
.\wireguard.exe /genkey | Out-File -Encoding ascii privatekey.txt
Get-Content privatekey.txt | .\wireguard.exe /pubkey | Out-File -Encoding ascii publickey.txt
type publickey.txt # à envoyer à l'administrateur VPN
```
Créer le fichier de config `admin_nom.conf` **sur sa machine** :
```ini
[Interface]
Address = 10.0.0.X/32 # IP assignée par l'administrateur VPN
PrivateKey = <SA_CLE_PRIVEE>
DNS = 8.8.8.8
[Peer]
PublicKey = VQa6g1foaXhJgYWbwyJ9R/EAmmXg0nOnhPhbIIPLlmg=
AllowedIPs = 10.0.0.0/24
Endpoint = 45.150.111.158:51820
PersistentKeepalive = 25
```
Importer ce fichier dans l'app WireGuard → **Tunnel prêt, pas encore actif**.
#### Étape B — Sur le serveur VPN (à faire par l'administrateur)
L'admin envoie sa **clé publique** et l'administrateur l'ajoute :
```bash
ssh root@45.150.111.158
# Assigner une IP libre (voir tableau ci-dessous) et ajouter le peer
wg set wg0 peer <PUBKEY_ADMIN> allowed-ips 10.0.0.X/32
# Persister la config (survie au reboot)
wg-quick save wg0
# Vérifier
wg show
```
L'admin peut maintenant activer le tunnel dans l'app WireGuard.
#### IPs assignées
| Admin | IP VPN | Clé publique |
|--------|------------|--------------|
| admin1 | 10.0.0.3 | (voir admin1.conf) |
| admin2 | 10.0.0.10 | `SL6qEf2K0/3a26wBkvQpOmgILHxxLS1N8M5hJAqfRCc=` |
Prochaine IP libre : **10.0.0.11**
### Phase 7: Admin Connection
Each admin:
1. Download WireGuard app: https://www.wireguard.com/install/
2. Import config file (admin1.conf, admin2.conf, etc.)
3. Connect to VPN
4. Access services:
- **Wazuh Dashboard**: https://10.0.0.2
- **Dozzle**: http://10.0.0.2:8080
- **Beszel**: http://10.0.0.2:9090
- **S3/RustFS Console**: http://10.0.0.2:9001
## Firewall Rules Summary
### VPS VPN Server
| Protocol | Port | Source | Action |
|----------|------|--------|--------|
| UDP | 51820 | Any | ACCEPT (WireGuard) |
| TCP | 22 | Any | ACCEPT (SSH) |
| ICMP | echo-request | Any | ACCEPT |
| Any | Any | Any | REJECT |
NAT masquerade enabled for VPN → Internet routing.
### monitoring-uber (185.103.167.138)
| Protocol | Port | Source | Action |
|----------|------|--------|--------|
| TCP | 1514 | 185.103.166.119 | ACCEPT (Prod Wazuh Agent) |
| TCP | 1514 | 185.103.166.112 | ACCEPT (Pre-prod Wazuh Agent) |
| UDP | 51820 | Any | ACCEPT (VPN) |
| TCP | 443 | 10.0.0.0/24 | ACCEPT (Wazuh Dashboard — VPN) |
| TCP | 8080 | 10.0.0.0/24 | ACCEPT (Dozzle — VPN) |
| TCP | 9090 | 10.0.0.0/24 | ACCEPT (Beszel — VPN) |
| TCP | 9000-9001 | 10.0.0.0/24 | ACCEPT (S3 — VPN) |
| TCP | 22 | Any | ACCEPT (SSH) |
| Any | Any | Any | DROP (Deny all) |
## Troubleshooting
### VPN connection not establishing
```bash
# On monitoring-uber
systemctl status wg-quick@wg0
journalctl -u wg-quick@wg0 -n 20
# Restart
systemctl restart wg-quick@wg0
```
### Can't access services over VPN
```bash
# On monitoring-uber, check bindings
netstat -tlnp | grep -E "(443|8080|9090|9000)"
# Should show 10.0.0.2 (not 0.0.0.0)
```
### Wazuh agents can't connect
Make sure firewall allows 1514/tcp from prod/pre-prod:
```bash
# On monitoring-uber
iptables -L INPUT -v | grep 1514
```
Should show rules for prod (185.103.166.119) and pre-prod (185.103.166.112).
## Persistence & Boot
All rules are saved with:
- `iptables-save``/etc/iptables/rules.v4`
- WireGuard: `systemctl enable wg-quick@wg0`
Both survive reboots.
## Next Steps
- [ ] Deploy VPS VPN Server
- [ ] Run firewall + WireGuard setup on VPS
- [ ] Get Server Public Key
- [ ] Deploy firewall + WireGuard client on monitoring-uber
- [ ] Add monitoring-uber peer on VPS
- [ ] Verify VPN connection (ping 10.0.0.1)
- [ ] Rebind services to VPN IP
- [ ] Restart docker containers
- [ ] Generate admin client configs
- [ ] Add admin peers on VPS
- [ ] Test admin VPN connection
- [ ] Test service access (Wazuh, Dozzle, Beszel, S3)
+10
View File
@@ -0,0 +1,10 @@
[Interface]
Address = 10.0.0.3/32
PrivateKey = ePPTC7QXatWQd9qZWmAqlgd+IskVQ9/7rvwsfUtosEs=
DNS = 8.8.8.8
[Peer]
PublicKey = VQa6g1foaXhJgYWbwyJ9R/EAmmXg0nOnhPhbIIPLlmg=
AllowedIPs = 10.0.0.0/24
Endpoint = 45.150.111.158:51820
PersistentKeepalive = 25
+52
View File
@@ -0,0 +1,52 @@
# Values for port, users, and token are optional.
# Defaults are port 45876, the first created user, and a generated UUID token.
systems:
- name: backup-prod-mln
host: 10.0.0.4
port: 10001
token: 945de0ec-678a-4e79-9cf8-c9158a427a9d
users:
- free6nk33@gmail.com
- name: bdd-redis-prod
host: 10.0.0.5
port: 10001
token: 6c29ec90-93eb-402b-b7ae-132e510d9d54
users:
- free6nk33@gmail.com
- name: load-balancer
host: 93.185.166.221
port: 45876
token: c9f1bb8d-ef48-4f1b-a8fd-ed834b93f683
users:
- free6nk33@gmail.com
- name: pre-prod-uber
host: 185.103.166.112
port: 10001
token: ff4a-52aec89a1-5357-8329685224
users:
- free6nk33@gmail.com
- name: prod-uber
host: 185.103.166.119
port: 10001
token: 551-5c86bcbaa34-68b-bb9e248e32
users:
- free6nk33@gmail.com
- name: runner
host: 132.243.162.161
port: 45876
token: 4252d294-cb8c-4ed6-a70e-19973186f9b5
users:
- free6nk33@gmail.com
- name: s3-uber
host: 80.96.58.164
port: 10001
token: 5688-ac1b97819c-79f3-104e051884
users:
- free6nk33@gmail.com
- name: vpn-uber
host: 10.0.0.1
port: 10001
token: b5b-257ee3ebf-be6-c92809ee12
users:
- free6nk33@gmail.com
@@ -0,0 +1,16 @@
mode: iptables
update_frequency: 10s
log_mode: stdout
log_level: info
api_url: http://127.0.0.1:7777
api_key: monitoring-bouncer-key-2026
insecure_skip_verify: false
disable_ipv6: false
deny_action: DROP
deny_log: false
supported_decisions_types:
- ban
iptables_chains:
- INPUT
- FORWARD
- DOCKER-USER
+194
View File
@@ -0,0 +1,194 @@
services:
dozzle:
image: amir20/dozzle:latest
container_name: dozzle
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./dozzle/users.yml:/data/users.yml:ro
environment:
DOZZLE_AUTH_PROVIDER: simple
DOZZLE_AUTH_TTL: 48h
DOZZLE_REMOTE_AGENT: "185.103.166.112:7007|pre-prod-uber|PRE-PROD,185.103.166.119:7007|prod-uber|PROD,10.0.0.4:7007|backup-mln|BACKUP,80.96.58.164:7007|s3-uber|VPS,10.0.0.5:7007|bdd-redis-prod|BDD-PROD"
networks:
- monitoring_net
nginx:
image: nginx:alpine
container_name: monitoring_nginx
restart: unless-stopped
ports:
- "10.0.0.2:80:80"
- "10.0.0.2:443:443"
- "10.0.0.2:8080:8080"
- "10.0.0.2:8090:8090"
- "10.0.0.2:3001:3001"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/certs:/etc/nginx/certs:ro
- nginx_logs:/var/log/nginx
depends_on:
- dozzle
- wazuh.dashboard
- beszel
networks:
- monitoring_net
# ─── ClamAV Antivirus ────────────────────────────────────
clamav:
image: clamav/clamav:stable
container_name: clamav
restart: unless-stopped
environment:
- FRESHCLAM_CHECKS=24
- CLAMD_SCAN_MAX_FILESIZE=100M
volumes:
- clamav_data:/var/lib/clamav
- clamav_logs:/var/log/clamav
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- monitoring_net
# ─── Beszel Hub ───────────────────────────────────────────
beszel:
image: henrygd/beszel:latest
container_name: beszel
restart: unless-stopped
volumes:
- beszel_data:/beszel_data
networks:
- monitoring_net
wazuh.manager:
image: wazuh/wazuh-manager:4.14.5
hostname: wazuh.manager
container_name: wazuh_manager
restart: unless-stopped
ulimits:
memlock:
soft: -1
hard: -1
nofile:
soft: 655360
hard: 655360
ports:
- "1514:1514" # Communication agents
- "1515:1515" # Enrôlement agents
- "514:514/udp" # Syslog entrant
environment:
- INDEXER_URL=https://wazuh.indexer:9200
- INDEXER_USERNAME=admin
- INDEXER_PASSWORD=${WAZUH_INDEXER_PASSWORD}
- FILEBEAT_SSL_VERIFICATION_MODE=full
- SSL_CERTIFICATE_AUTHORITIES=/etc/ssl/root-ca.pem
- SSL_CERTIFICATE=/etc/ssl/filebeat.pem
- SSL_KEY=/etc/ssl/filebeat.key
- API_USERNAME=wazuh-wui
- API_PASSWORD=${WAZUH_API_PASSWORD}
entrypoint: ["/bin/bash", "/wazuh-custom/init.sh"]
volumes:
- ./wazuh/config/wazuh_manager/:/wazuh-custom/:ro
- wazuh_api_configuration:/var/ossec/api/configuration
- wazuh_etc:/var/ossec/etc
- wazuh_logs:/var/ossec/logs
- wazuh_queue:/var/ossec/queue
- wazuh_var_multigroups:/var/ossec/var/multigroups
- wazuh_integrations:/var/ossec/integrations
- wazuh_active_response:/var/ossec/active-response/bin
- wazuh_agentless:/var/ossec/agentless
- wazuh_wodles:/var/ossec/wodles
- filebeat_etc:/etc/filebeat
- filebeat_var:/var/lib/filebeat
- ./wazuh/certs/root-ca-manager.pem:/etc/ssl/root-ca.pem
- ./wazuh/certs/wazuh.manager.pem:/etc/ssl/filebeat.pem
- ./wazuh/certs/wazuh.manager-key.pem:/etc/ssl/filebeat.key
- /var/run/docker.sock:/var/run/docker.sock
- /usr/bin/docker:/usr/local/bin/docker:ro
- ./wazuh/config/wazuh_manager/active-response:/wazuh-custom/active-response:ro
- ./wazuh/config/wazuh_manager/agent.conf:/wazuh-custom/agent.conf:ro
networks:
- monitoring_net
# ─── Wazuh Indexer (OpenSearch) ───────────────────────────
wazuh.indexer:
image: wazuh/wazuh-indexer:4.14.5
hostname: wazuh.indexer
container_name: wazuh_indexer
restart: unless-stopped
ulimits:
memlock:
soft: -1
hard: -1
nofile:
soft: 65536
hard: 65536
environment:
- "OPENSEARCH_JAVA_OPTS=-Xms1g -Xmx1g"
- INDEXER_PASSWORD=${WAZUH_INDEXER_PASSWORD}
volumes:
- wazuh_indexer_data:/var/lib/wazuh-indexer
- ./wazuh/certs/root-ca.pem:/usr/share/wazuh-indexer/config/certs/root-ca.pem
- ./wazuh/certs/wazuh.indexer-key.pem:/usr/share/wazuh-indexer/config/certs/wazuh.indexer.key
- ./wazuh/certs/wazuh.indexer.pem:/usr/share/wazuh-indexer/config/certs/wazuh.indexer.pem
- ./wazuh/certs/admin.pem:/usr/share/wazuh-indexer/config/certs/admin.pem
- ./wazuh/certs/admin-key.pem:/usr/share/wazuh-indexer/config/certs/admin-key.pem
- ./wazuh/config/wazuh_indexer/wazuh.indexer.yml:/usr/share/wazuh-indexer/config/opensearch.yml
- ./wazuh/config/wazuh_indexer/internal_users.yml:/usr/share/wazuh-indexer/config/opensearch-security/internal_users.yml
networks:
- monitoring_net
# ─── Wazuh Dashboard ──────────────────────────────────────
wazuh.dashboard:
image: wazuh/wazuh-dashboard:4.14.5
hostname: wazuh.dashboard
container_name: wazuh_dashboard
restart: unless-stopped
environment:
- INDEXER_USERNAME=admin
- INDEXER_PASSWORD=${WAZUH_INDEXER_PASSWORD}
- WAZUH_API_URL=https://wazuh.manager
- DASHBOARD_USERNAME=kibanaserver
- DASHBOARD_PASSWORD=${WAZUH_DASHBOARD_PASSWORD}
- API_USERNAME=wazuh-wui
- API_PASSWORD=${WAZUH_API_PASSWORD}
volumes:
- ./wazuh/certs/wazuh.dashboard.pem:/usr/share/wazuh-dashboard/certs/wazuh-dashboard.pem
- ./wazuh/certs/wazuh.dashboard-key.pem:/usr/share/wazuh-dashboard/certs/wazuh-dashboard-key.pem
- ./wazuh/certs/root-ca.pem:/usr/share/wazuh-dashboard/certs/root-ca.pem
- ./wazuh/config/wazuh_dashboard/opensearch_dashboards.yml:/usr/share/wazuh-dashboard/config/opensearch_dashboards.yml
depends_on:
- wazuh.indexer
- wazuh.manager
networks:
- monitoring_net
uptime-kuma:
image: louislam/uptime-kuma:1
container_name: uptime-kuma
restart: unless-stopped
volumes:
- uptime_kuma_data:/app/data
networks:
- monitoring_net
networks:
monitoring_net:
driver: bridge
volumes:
wazuh_api_configuration:
wazuh_etc:
wazuh_logs:
wazuh_queue:
wazuh_var_multigroups:
wazuh_integrations:
wazuh_active_response:
wazuh_agentless:
wazuh_wodles:
filebeat_etc:
filebeat_var:
wazuh_indexer_data:
nginx_logs:
beszel_data:
clamav_data:
clamav_logs:
uptime_kuma_data:
+6
View File
@@ -0,0 +1,6 @@
users:
admin:
name: Admin
email: admin@uber-stup.club
password: $2b$11$AOUC5QG2l8Ee0gUKIyZGKuHdL0iuvDa3C0bVAl2Gyt6Axx7.E/hHm
roles: download
@@ -0,0 +1,106 @@
#!/bin/bash
# ═══════════════════════════════════════════════════════════════════
# Firewall monitoring-uber (185.103.167.138)
# ─────────────────────────────────────────────────────────────────
# INBOUND:
# • 1514/tcp FROM prod (185.103.166.119) — agents Wazuh
# • 1514/tcp FROM pre-prod (185.103.166.112) — agents Wazuh
# • 51820/udp — VPN WireGuard
# • 10.0.0.0/24 (VPN admins) → 443, 8080, 8090, 9000, 9001
# • SSH: BLOCKED from prod/pre-prod/s3 (security hardening)
# ─────────────────────────────────────────────────────────────────
# OUTBOUND:
# • Tout (0.0.0.0/0) — Internet, updates, DNS
# • VPN vers VPS
# ═══════════════════════════════════════════════════════════════════
set -e
echo "[*] Configurant firewall monitoring-uber..."
# Flush des règles existantes
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X
# Politique par défaut
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# ─── INPUT ────────────────────────────────────────────────────────
# Loopback (services internes)
iptables -A INPUT -i lo -j ACCEPT
# Established/Related
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# SSH (administration locale, bloquer depuis prod/pre-prod/s3)
iptables -A INPUT -p tcp --dport 22 -s 185.103.166.119 -j DROP # prod
iptables -A INPUT -p tcp --dport 22 -s 185.103.166.112 -j DROP # pre-prod
iptables -A INPUT -p tcp --dport 22 -s 80.96.58.164 -j DROP # s3
iptables -A INPUT -p tcp --dport 22 -j ACCEPT # allow from other sources
# ─── Agents Wazuh (prod + pre-prod) ────────────────────────────
# Prod: 185.103.166.119
iptables -A INPUT -p tcp --dport 1514 -s 185.103.166.119 -j ACCEPT
# Pre-prod: 185.103.166.112
iptables -A INPUT -p tcp --dport 1514 -s 185.103.166.112 -j ACCEPT
# ─── VPN (WireGuard) ──────────────────────────────────────────
iptables -A INPUT -p udp --dport 51820 -j ACCEPT
# ─── Services (accessibles via VPN seulement) ─────────────────
# Admins via VPN: 10.0.0.0/24
# Wazuh Dashboard (443/https)
iptables -A INPUT -p tcp --dport 443 -s 10.0.0.0/24 -j ACCEPT
# Dozzle (8080/http)
iptables -A INPUT -p tcp --dport 8080 -s 10.0.0.0/24 -j ACCEPT
# Beszel (8090/http)
iptables -A INPUT -p tcp --dport 8090 -s 10.0.0.0/24 -j ACCEPT
# S3/RustFS (9000/9001)
iptables -A INPUT -p tcp --dport 9000 -s 10.0.0.0/24 -j ACCEPT
iptables -A INPUT -p tcp --dport 9001 -s 10.0.0.0/24 -j ACCEPT
# ─── ICMP ─────────────────────────────────────────────────────
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
iptables -A INPUT -p icmp --icmp-type destination-unreachable -j ACCEPT
iptables -A INPUT -p icmp --icmp-type time-exceeded -j ACCEPT
# Reject le reste
iptables -A INPUT -j REJECT --reject-with icmp-host-prohibited
# ─── FORWARD ──────────────────────────────────────────────────
# Blocker forward par défaut (services locaux, pas de transit)
iptables -A FORWARD -j REJECT --reject-with icmp-host-prohibited
# ─── Sauvegarder ──────────────────────────────────────────────
mkdir -p /etc/iptables
iptables-save > /etc/iptables/rules.v4
echo "[✓] Firewall monitoring-uber configuré"
echo ""
echo "Règles appliquées (ALLOW) :"
echo " • SSH 22/tcp — from admin IPs only (BLOCKED from prod/pre-prod/s3)"
echo " • Wazuh 1514/tcp FROM prod (185.103.166.119) — agents Wazuh"
echo " • Wazuh 1514/tcp FROM pre-prod (185.103.166.112) — agents Wazuh"
echo " • VPN 51820/udp — WireGuard"
echo " • Wazuh Dashboard 443/tcp FROM 10.0.0.0/24 (VPN)"
echo " • Dozzle 8080/tcp FROM 10.0.0.0/24 (VPN)"
echo " • Beszel 8090/tcp FROM 10.0.0.0/24 (VPN)"
echo " • S3/RustFS 9000-9001/tcp FROM 10.0.0.0/24 (VPN)"
echo " • OUTPUT (Internet/DNS)"
echo ""
echo "Règles appliquées (DENY) :"
echo " • SSH 22/tcp FROM prod (185.103.166.119) — BLOCKED"
echo " • SSH 22/tcp FROM pre-prod (185.103.166.112) — BLOCKED"
echo " • SSH 22/tcp FROM s3 (80.96.58.164) — BLOCKED"
echo " • All other INPUT/FORWARD — default DROP"
@@ -0,0 +1,80 @@
#!/bin/bash
# ═══════════════════════════════════════════════════════════════════
# Firewall VPS VPN Server (WireGuard) — vpn-uber (45.150.111.158)
# Allow: WireGuard (51820/udp), SSH (22/tcp), Beszel agent (10001/tcp from VPN)
# Reject: tout le reste
# NAT: masquerade pour routage VPN
# ═══════════════════════════════════════════════════════════════════
set -e
echo "[*] Configurant firewall VPS VPN Server..."
# Flush des règles existantes
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X
# Politique par défaut
iptables -P INPUT DROP
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT
# ─── INPUT ────────────────────────────────────────────────────────
# Loopback
iptables -A INPUT -i lo -j ACCEPT
# Established/Related
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# SSH (administration)
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# WireGuard
iptables -A INPUT -p udp --dport 51820 -j ACCEPT
# Beszel Agent (10001/tcp) — accessible depuis VPN uniquement
iptables -A INPUT -p tcp --dport 10001 -s 10.0.0.0/24 -j ACCEPT
# ICMP (ping, MTU discovery)
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
iptables -A INPUT -p icmp --icmp-type destination-unreachable -j ACCEPT
iptables -A INPUT -p icmp --icmp-type time-exceeded -j ACCEPT
# Reject le reste
iptables -A INPUT -j REJECT --reject-with icmp-host-prohibited
# ─── FORWARD ──────────────────────────────────────────────────────
# VPN ↔ Internet
iptables -A FORWARD -i wg0 -j ACCEPT
iptables -A FORWARD -o wg0 -j ACCEPT
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
# ─── NAT ──────────────────────────────────────────────────────────
# Masquerade pour routage VPN
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
# ─── Sauvegarder ──────────────────────────────────────────────────
mkdir -p /etc/iptables
iptables-save > /etc/iptables/rules.v4
# ─── IP Forwarding ────────────────────────────────────────────────
sysctl -w net.ipv4.ip_forward=1
grep -q "net.ipv4.ip_forward" /etc/sysctl.conf || echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
echo "[✓] Firewall VPS VPN Server configuré"
echo ""
echo "Règles appliquées (ALLOW) :"
echo " • SSH 22/tcp — administration"
echo " • WireGuard 51820/udp — VPN peers"
echo " • Beszel Agent 10001/tcp FROM 10.0.0.0/24 — monitoring dashboard"
echo " • ICMP (ping, MTU discovery)"
echo ""
echo "Configuration :"
echo " • FORWARD ACCEPT (VPN ↔ Internet routing)"
echo " • NAT masquerade activé (eth0)"
echo " • IP forwarding activé (net.ipv4.ip_forward=1)"
echo " • Default INPUT policy: DROP"
@@ -0,0 +1,74 @@
worker_processes auto;
events {
worker_connections 1024;
}
http {
# Main HTTPS server
server {
listen 0.0.0.0:80 default_server;
listen [::]:80 default_server;
return 301 https://$host$request_uri;
}
server {
listen 0.0.0.0:443 ssl default_server;
listen [::]:443 ssl default_server;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# Wazuh (root)
location / {
proxy_pass https://wazuh.dashboard:5601/;
proxy_ssl_verify off;
proxy_http_version 1.1;
proxy_set_header Host wazuh.dashboard:5601;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 300s;
proxy_hide_header Content-Security-Policy;
}
# Dozzle
location /dozzle {
proxy_pass http://dozzle:8080;
proxy_http_version 1.1;
proxy_buffering off;
proxy_cache off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect ~^http://dozzle:8080/(.*)$ /dozzle/$1;
proxy_redirect ~^/$/ /dozzle/;
proxy_read_timeout 3600s;
}
# Beszel
location /beszel {
proxy_pass http://beszel:8090;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect ~^http://beszel:8090/(.*)$ /beszel/$1;
proxy_redirect ~^/$/ /beszel/;
sub_filter 'href="/' 'href="/beszel/';
sub_filter 'src="/' 'src="/beszel/';
sub_filter 'url(/' 'url(/beszel/';
sub_filter_once off;
proxy_read_timeout 3600s;
}
}
}
@@ -0,0 +1,83 @@
worker_processes auto;
events {
worker_connections 1024;
}
http {
resolver 127.0.0.11 valid=10s ipv6=off;
upstream wazuh_backend {
server wazuh.dashboard:5601;
}
upstream dozzle_backend {
server dozzle:8080;
}
upstream beszel_backend {
server beszel:8090;
}
# Redirect HTTP to HTTPS
server {
listen 80;
return 301 https://$host$request_uri;
}
# Main HTTPS server
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# Root redirects to /wazuh
location = / {
return 301 https://$host/wazuh;
}
# Wazuh
location /wazuh/ {
proxy_pass https://wazuh_backend/;
proxy_ssl_verify off;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 300s;
}
# Dozzle
location /dozzle/ {
proxy_pass http://dozzle_backend/;
proxy_http_version 1.1;
proxy_buffering off;
proxy_cache off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600s;
}
# Beszel
location /beszel/ {
proxy_pass http://beszel_backend/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600s;
}
}
}
+95
View File
@@ -0,0 +1,95 @@
worker_processes auto;
events {
worker_connections 1024;
}
http {
resolver 127.0.0.11 valid=10s ipv6=off;
# ── Dozzle (8080) ───────────────────────────────────────
server {
listen 8080 ssl;
server_name _;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
access_log /var/log/nginx/dozzle_access.log;
error_log /var/log/nginx/dozzle_error.log;
location / {
proxy_pass http://dozzle:8080;
proxy_http_version 1.1;
proxy_buffering off;
proxy_cache off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600s;
}
}
# ── Beszel (8090) ───────────────────────────────────────
server {
listen 8090 ssl;
server_name _;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
access_log /var/log/nginx/beszel_access.log;
error_log /var/log/nginx/beszel_error.log;
location / {
proxy_pass http://beszel:8090;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600s;
}
}
# ── Wazuh (443) ──────────────────────────────────────────
server {
listen 443 ssl default_server;
server_name _;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
access_log /var/log/nginx/wazuh_access.log;
error_log /var/log/nginx/wazuh_error.log;
location / {
proxy_pass https://wazuh.dashboard:5601;
proxy_ssl_verify off;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 300s;
}
}
# ── HTTP Redirect to HTTPS ───────────────────────────────
server {
listen 80;
server_name _;
return 301 https://$host$request_uri;
}
}
@@ -0,0 +1,61 @@
#!/bin/bash
# ═══════════════════════════════════════════════════════════════════
# Rebind services to private VPN IP (10.0.0.2)
# Services: Wazuh Dashboard, Dozzle, Beszel, S3/RustFS
#
# IMPORTANT:
# - Wazuh Manager stays on 0.0.0.0:1514 (agents need access)
# - Other services bind to 10.0.0.2 (VPN only)
# ═══════════════════════════════════════════════════════════════════
set -e
VPN_IP="10.0.0.2"
COMPOSE_FILE="/home/ubuntu/docker/docker-compose-security.yml"
if [ ! -f "$COMPOSE_FILE" ]; then
echo "[!] File not found: $COMPOSE_FILE"
exit 1
fi
echo "[*] Rebinding services to VPN IP ($VPN_IP)..."
echo " Config: $COMPOSE_FILE"
# ─── Wazuh Dashboard (443) ────────────────────────────────────
echo "[*] Updating Wazuh Dashboard (443)..."
sed -i 's|0\.0\.0\.0:443:|'"$VPN_IP"':443:|g' "$COMPOSE_FILE"
# ─── Dozzle (8080) ────────────────────────────────────────────
echo "[*] Updating Dozzle (8080)..."
sed -i 's|0\.0\.0\.0:8080:|'"$VPN_IP"':8080:|g' "$COMPOSE_FILE"
# ─── Beszel (9090) ────────────────────────────────────────────
echo "[*] Updating Beszel (9090)..."
sed -i 's|0\.0\.0\.0:9090:|'"$VPN_IP"':9090:|g' "$COMPOSE_FILE"
# ─── RustFS / S3 (9000, 9001) ─────────────────────────────────
echo "[*] Updating RustFS/S3 (9000, 9001)..."
sed -i 's|0\.0\.0\.0:9000:|'"$VPN_IP"':9000:|g' "$COMPOSE_FILE"
sed -i 's|0\.0\.0\.0:9001:|'"$VPN_IP"':9001:|g' "$COMPOSE_FILE"
# ─── Keep Wazuh Manager on 0.0.0.0:1514 ───────────────────────
# (agents need public access)
echo ""
echo "[✓] Services rebound to $VPN_IP"
echo ""
echo "Verify changes:"
grep -n "ports:" -A 2 "$COMPOSE_FILE" | grep -E "(443|8080|9090|9000|9001|1514)" || true
echo ""
echo "Services now listening on:"
echo " • 0.0.0.0:1514 — Wazuh Manager (agents)"
echo "$VPN_IP:443 — Wazuh Dashboard (VPN only)"
echo "$VPN_IP:8080 — Dozzle (VPN only)"
echo "$VPN_IP:9090 — Beszel (VPN only)"
echo "$VPN_IP:9000/9001 — RustFS (VPN only)"
echo ""
echo "Next: Restart services"
echo " cd /home/ubuntu/docker"
echo " docker compose -f docker-compose-security.yml down"
echo " docker compose -f docker-compose-security.yml up -d"
+10
View File
@@ -0,0 +1,10 @@
nodes:
indexer:
- name: wazuh.indexer
ip: "wazuh.indexer"
server:
- name: wazuh.manager
ip: "wazuh.manager"
dashboard:
- name: wazuh.dashboard
ip: "wazuh.dashboard"
@@ -0,0 +1,12 @@
server.host: 0.0.0.0
server.port: 5601
opensearch.hosts: https://wazuh.indexer:9200
opensearch.ssl.verificationMode: certificate
opensearch.ssl.certificateAuthorities: ["/usr/share/wazuh-dashboard/certs/root-ca.pem"]
opensearch.requestHeadersWhitelist: ["securitytenant","Authorization"]
opensearch_security.multitenancy.enabled: false
opensearch_security.readonly_mode.roles: ["kibana_read_only"]
server.ssl.enabled: true
server.ssl.key: "/usr/share/wazuh-dashboard/certs/wazuh-dashboard-key.pem"
server.ssl.certificate: "/usr/share/wazuh-dashboard/certs/wazuh-dashboard.pem"
uiSettings.overrides.defaultRoute: /app/wz-home
@@ -0,0 +1,45 @@
---
_meta:
type: "internalusers"
config_version: 2
admin:
hash: "$2b$12$lNbkFg7B3IN.BXwZHjztteTDd3hTIvT/kTJhRFqfUY/228Bw45POO"
reserved: true
backend_roles:
- "admin"
description: "Admin user"
kibanaserver:
hash: "$2b$12$czi/zi7uht/K5.vcCvsXueFi6ot7GFwFfVBax7rCaNqBq1jhTOZ7u"
reserved: true
description: "Kibanaserver user"
kibanaro:
hash: "$2b$12$lNbkFg7B3IN.BXwZHjztteTDd3hTIvT/kTJhRFqfUY/228Bw45POO"
reserved: false
backend_roles:
- "kibanauser"
- "readall"
description: "Kibana read-only user"
logstash:
hash: "$2b$12$lNbkFg7B3IN.BXwZHjztteTDd3hTIvT/kTJhRFqfUY/228Bw45POO"
reserved: false
backend_roles:
- "logstash"
description: "Logstash user"
readall:
hash: "$2b$12$lNbkFg7B3IN.BXwZHjztteTDd3hTIvT/kTJhRFqfUY/228Bw45POO"
reserved: false
backend_roles:
- "readall"
description: "Readall user"
snapshotrestore:
hash: "$2b$12$lNbkFg7B3IN.BXwZHjztteTDd3hTIvT/kTJhRFqfUY/228Bw45POO"
reserved: false
backend_roles:
- "snapshotrestore"
description: "Snapshotrestore user"
@@ -0,0 +1,51 @@
network.host: "0.0.0.0"
node.name: "wazuh.indexer"
cluster.name: "wazuh-cluster"
# Compatibilité Filebeat 7.x (libbeat) avec OpenSearch 2.x
# Filebeat envoie _type dans les requêtes bulk — OpenSearch 2.x le rejette
# sans ce flag.
compatibility.override_main_response_version: true
path.data: /var/lib/wazuh-indexer
path.logs: /var/log/wazuh-indexer
plugins.security.ssl.http.pemcert_filepath: /usr/share/wazuh-indexer/config/certs/wazuh.indexer.pem
plugins.security.ssl.http.pemkey_filepath: /usr/share/wazuh-indexer/config/certs/wazuh.indexer.key
plugins.security.ssl.http.pemtrustedcas_filepath: /usr/share/wazuh-indexer/config/certs/root-ca.pem
plugins.security.ssl.transport.pemcert_filepath: /usr/share/wazuh-indexer/config/certs/wazuh.indexer.pem
plugins.security.ssl.transport.pemkey_filepath: /usr/share/wazuh-indexer/config/certs/wazuh.indexer.key
plugins.security.ssl.transport.pemtrustedcas_filepath: /usr/share/wazuh-indexer/config/certs/root-ca.pem
plugins.security.ssl.http.enabled: true
plugins.security.ssl.transport.enforce_hostname_verification: false
plugins.security.ssl.transport.resolve_hostname: false
plugins.security.authcz.admin_dn:
- "CN=admin,OU=Wazuh,O=Wazuh,L=California,C=US"
plugins.security.nodes_dn:
- "CN=wazuh.indexer,OU=Wazuh,O=Wazuh,L=California,C=US"
plugins.security.check_snapshot_restore_write_privileges: true
plugins.security.enable_snapshot_restore_privilege: true
plugins.security.restapi.roles_enabled:
- "all_access"
- "security_rest_api_access"
plugins.security.system_indices.enabled: true
plugins.security.system_indices.indices:
- ".opendistro-alerting-config"
- ".opendistro-alerting-alert*"
- ".opendistro-anomaly-results*"
- ".opendistro-anomaly-detector*"
- ".opendistro-anomaly-checkpoints"
- ".opendistro-anomaly-detection-state"
- ".opendistro-reports-*"
- ".opendistro-notifications-*"
- ".opendistro-notebooks"
- ".opensearch-observability"
- ".opendistro-asynchronous-search-response*"
- ".replication-metadata-store"
discovery.type: single-node
plugins.security.allow_default_init_securityindex: true
cluster.routing.allocation.disk.threshold_enabled: false
@@ -0,0 +1,63 @@
<!-- Configuration centralisée distribuée aux agents Wazuh -->
<!-- Déposée dans /var/ossec/etc/shared/default/agent.conf sur le manager -->
<agent_config>
<!-- ── Logs nginx ──────────────────────────────────────────────────── -->
<localfile>
<log_format>apache</log_format>
<location>/var/log/nginx/access.log</location>
</localfile>
<localfile>
<log_format>syslog</log_format>
<location>/var/log/nginx/error.log</location>
</localfile>
<!-- ── Auth system ───────────────────────────────────────────────── -->
<localfile>
<log_format>syslog</log_format>
<location>/var/log/auth.log</location>
</localfile>
<!-- ── Syslog ────────────────────────────────────────────────────── -->
<localfile>
<log_format>syslog</log_format>
<location>/var/log/syslog</location>
</localfile>
<!-- ── Unattended-upgrades (patchs de sécurité auto) ───────────────── -->
<localfile>
<log_format>syslog</log_format>
<location>/var/log/unattended-upgrades/unattended-upgrades.log</location>
</localfile>
<!-- ── auditd (regles CIS 6.2.3.x deployees via ansible/hardening) ─── -->
<localfile>
<log_format>audit</log_format>
<location>/var/log/audit/audit.log</location>
</localfile>
</agent_config>
<!-- ── Logs backend Go (prod-mln) ────────────────────────────────── -->
<!-- "command" (pas full_command) = chaque ligne de docker logs → event séparé -->
<!-- Chaque ligne [GIN] devient son propre event → decoder peut extraire srcip/url/status -->
<agent_config name="prod-mln">
<localfile>
<log_format>command</log_format>
<command>docker logs --since 65s gestion-backend 2>&1</command>
<alias>backend</alias>
<frequency>60</frequency>
</localfile>
</agent_config>
<!-- ── Logs backend Go (pre-prod-mln) ───────────────────────────── -->
<agent_config name="pre-prod-mln">
<localfile>
<log_format>command</log_format>
<command>docker logs --since 65s gestion-backend 2>&1</command>
<alias>backend</alias>
<frequency>60</frequency>
</localfile>
</agent_config>
@@ -0,0 +1,4 @@
<!-- ModSecurity decoder — non utilisé, remplacé par règles json+location -->
<decoder name="waf-modsec-unused">
<prematch>UNUSED_MODSEC_PLACEHOLDER_NEVER_MATCHES</prematch>
</decoder>
@@ -0,0 +1,45 @@
<!-- WireGuard Decoders
Parent: kernel (built-in, program_name=^kernel)
Decoders enfants directs de kernel (Wazuh: 2 niveaux max) -->
<decoder name="wireguard-handshake-init">
<parent>kernel</parent>
<prematch>wireguard: wg0: Sending handshake initiation</prematch>
<regex type="pcre2">wireguard: wg0: Sending handshake initiation to peer (\d+) \(([\d.]+):\d+\)</regex>
<order>id,srcip</order>
</decoder>
<decoder name="wireguard-handshake-resp-sent">
<parent>kernel</parent>
<prematch>wireguard: wg0: Sending handshake response</prematch>
<regex type="pcre2">wireguard: wg0: Sending handshake response to peer (\d+) \(([\d.]+):\d+\)</regex>
<order>id,srcip</order>
</decoder>
<decoder name="wireguard-handshake-resp-recv">
<parent>kernel</parent>
<prematch>wireguard: wg0: Receiving handshake response</prematch>
<regex type="pcre2">wireguard: wg0: Receiving handshake response from peer (\d+) \(([\d.]+):\d+\)</regex>
<order>id,srcip</order>
</decoder>
<decoder name="wireguard-keepalive">
<parent>kernel</parent>
<prematch>wireguard: wg0: </prematch>
<regex type="pcre2">wireguard: wg0: \S+ keepalive packet \S+ peer (\d+) \(([\d.]+):\d+\)</regex>
<order>id,srcip</order>
</decoder>
<decoder name="wireguard-timeout">
<parent>kernel</parent>
<prematch>wireguard: wg0: Handshake for peer</prematch>
<regex type="pcre2">wireguard: wg0: Handshake for peer (\d+)[^(]+\(([\d.]+):\d+\).+retrying \(try (\d+)\)</regex>
<order>id,srcip,extra_data</order>
</decoder>
<decoder name="wireguard-roaming">
<parent>kernel</parent>
<prematch>wireguard: wg0: Peer </prematch>
<regex type="pcre2">wireguard: wg0: Peer (\d+).+roaming to ([\d.]+):\d+</regex>
<order>id,srcip</order>
</decoder>
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
set -e
# ─── Copie de la config custom (montée hors du volume wazuh_etc) ──────────────
CUSTOM=/wazuh-custom
cp "$CUSTOM/ossec.conf" /var/ossec/etc/ossec.conf
mkdir -p /var/ossec/etc/decoders /var/ossec/etc/rules /var/ossec/etc/lists
cp "$CUSTOM/decoders/"*.xml /var/ossec/etc/decoders/ 2>/dev/null || true
cp "$CUSTOM/rules/"*.xml /var/ossec/etc/rules/ 2>/dev/null || true
cp "$CUSTOM/lists/"* /var/ossec/etc/lists/ 2>/dev/null || true
# ─── Agent config partagée (distribuée aux agents) ───────────────────────────
if [ -f "$CUSTOM/agent.conf" ]; then
mkdir -p /var/ossec/etc/shared/default
cp "$CUSTOM/agent.conf" /var/ossec/etc/shared/default/agent.conf
fi
# ─── Active-response scripts ──────────────────────────────────────────────────
if [ -d "$CUSTOM/active-response" ]; then
for script in "$CUSTOM/active-response/"*.sh; do
[ -f "$script" ] || continue
dest="/var/ossec/active-response/bin/$(basename "$script")"
cp "$script" "$dest"
chmod 750 "$dest"
chown root:wazuh "$dest" 2>/dev/null || true
echo "Active-response déployé: $dest"
done
fi
# ─── Démarrage Wazuh Manager (entrypoint original) ────────────────────────────
exec /init
@@ -0,0 +1,23 @@
<!-- ClamAV Log Monitoring -->
<!-- Collect ClamAV logs for antivirus detection -->
<!-- ClamAV Main Log -->
<localfile>
<log_format>syslog</log_format>
<location>/var/log/clamav/clamav.log</location>
<source_type>log</source_type>
</localfile>
<!-- ClamAV Freshclam (Signature Updates) Log -->
<localfile>
<log_format>syslog</log_format>
<location>/var/log/clamav/freshclam.log</location>
<source_type>log</source_type>
</localfile>
<!-- ClamAV Alert Log (High Priority Detections) -->
<localfile>
<log_format>syslog</log_format>
<location>/var/log/clamav/alert.log</location>
<source_type>log</source_type>
</localfile>
+960
View File
@@ -0,0 +1,960 @@
<ossec_config>
<!-- ═══════════════════════════════════════════════════════════
GLOBAL
═══════════════════════════════════════════════════════════ -->
<global>
<jsonout_output>yes</jsonout_output>
<alerts_log>yes</alerts_log>
<logall>no</logall>
<logall_json>no</logall_json>
<email_notification>no</email_notification>
<agents_disconnection_time>10m</agents_disconnection_time>
<agents_disconnection_alert_time>0</agents_disconnection_alert_time>
</global>
<!-- ═══════════════════════════════════════════════════════════
ALERTES
═══════════════════════════════════════════════════════════ -->
<alerts>
<log_alert_level>3</log_alert_level>
</alerts>
<!-- ═══════════════════════════════════════════════════════════
LOGGING
═══════════════════════════════════════════════════════════ -->
<logging>
<log_format>plain</log_format>
</logging>
<!-- ═══════════════════════════════════════════════════════════
COMMUNICATION AGENTS (port 1514 TCP)
═══════════════════════════════════════════════════════════ -->
<remote>
<connection>secure</connection>
<port>1514</port>
<protocol>tcp</protocol>
<queue_size>131072</queue_size>
</remote>
<!-- ═══════════════════════════════════════════════════════════
POLICY MONITORING
═══════════════════════════════════════════════════════════ -->
<rootcheck>
<disabled>no</disabled>
<check_files>yes</check_files>
<check_trojans>yes</check_trojans>
<check_dev>yes</check_dev>
<check_sys>yes</check_sys>
<check_pids>yes</check_pids>
<check_ports>yes</check_ports>
<check_if>yes</check_if>
<frequency>43200</frequency>
<rootkit_files>etc/rootcheck/rootkit_files.txt</rootkit_files>
<rootkit_trojans>etc/rootcheck/rootkit_trojans.txt</rootkit_trojans>
<skip_nfs>yes</skip_nfs>
</rootcheck>
<wodle name="cis-cat">
<disabled>yes</disabled>
<timeout>1800</timeout>
<interval>1d</interval>
<scan-on-start>yes</scan-on-start>
<java_path>wodles/java</java_path>
<ciscat_path>wodles/ciscat</ciscat_path>
</wodle>
<wodle name="osquery">
<disabled>yes</disabled>
<run_daemon>yes</run_daemon>
<log_path>/var/log/osquery/osqueryd.results.log</log_path>
<config_path>/etc/osquery/osquery.conf</config_path>
<add_labels>yes</add_labels>
</wodle>
<!-- ═══════════════════════════════════════════════════════════
SYSTEM INVENTORY
═══════════════════════════════════════════════════════════ -->
<wodle name="syscollector">
<disabled>no</disabled>
<interval>1h</interval>
<scan_on_start>yes</scan_on_start>
<hardware>yes</hardware>
<os>yes</os>
<network>yes</network>
<packages>yes</packages>
<ports all="yes">yes</ports>
<processes>yes</processes>
<synchronization>
<max_eps>10</max_eps>
</synchronization>
</wodle>
<!-- ═══════════════════════════════════════════════════════════
SECURITY CONFIGURATION ASSESSMENT
═══════════════════════════════════════════════════════════ -->
<sca>
<enabled>yes</enabled>
<scan_on_start>yes</scan_on_start>
<interval>12h</interval>
<skip_nfs>yes</skip_nfs>
</sca>
<!-- ═══════════════════════════════════════════════════════════
VULNERABILITY DETECTION
═══════════════════════════════════════════════════════════ -->
<vulnerability-detection>
<enabled>yes</enabled>
<index-status>yes</index-status>
<feed-update-interval>60m</feed-update-interval>
</vulnerability-detection>
<!-- ═══════════════════════════════════════════════════════════
INDEXER (Wazuh Indexer / OpenSearch)
═══════════════════════════════════════════════════════════ -->
<indexer>
<enabled>yes</enabled>
<hosts>
<host>https://wazuh.indexer:9200</host>
</hosts>
<ssl>
<certificate_authorities>
<ca>/etc/ssl/root-ca.pem</ca>
</certificate_authorities>
<certificate>/etc/ssl/filebeat.pem</certificate>
<key>/etc/ssl/filebeat.key</key>
</ssl>
</indexer>
<!-- ═══════════════════════════════════════════════════════════
FILE INTEGRITY MONITORING
═══════════════════════════════════════════════════════════ -->
<syscheck>
<disabled>no</disabled>
<frequency>43200</frequency>
<scan_on_start>yes</scan_on_start>
<alert_new_files>yes</alert_new_files>
<auto_ignore frequency="10" timeframe="3600">no</auto_ignore>
<directories>/etc,/usr/bin,/usr/sbin</directories>
<directories>/bin,/sbin,/boot</directories>
<ignore>/etc/mtab</ignore>
<ignore>/etc/hosts.deny</ignore>
<ignore>/etc/mail/statistics</ignore>
<ignore>/etc/random-seed</ignore>
<ignore>/etc/random.seed</ignore>
<ignore>/etc/adjtime</ignore>
<ignore>/etc/httpd/logs</ignore>
<ignore>/etc/utmpx</ignore>
<ignore>/etc/wtmpx</ignore>
<ignore>/etc/cups/certs</ignore>
<ignore>/etc/dumpdates</ignore>
<ignore>/etc/svc/volatile</ignore>
<ignore type="sregex">.log$|.swp$</ignore>
<nodiff>/etc/ssl/private.key</nodiff>
<skip_nfs>yes</skip_nfs>
<skip_dev>yes</skip_dev>
<skip_proc>yes</skip_proc>
<skip_sys>yes</skip_sys>
<process_priority>10</process_priority>
<max_eps>100</max_eps>
<synchronization>
<enabled>yes</enabled>
<interval>5m</interval>
<max_interval>1h</max_interval>
<max_eps>10</max_eps>
</synchronization>
</syscheck>
<!-- ═══════════════════════════════════════════════════════════
ACTIVE RESPONSE — WHITELIST
90.50.148.138 : IP publique admin — jamais bloquée
90.120.69.13 : IP publique admin — jamais bloquée
═══════════════════════════════════════════════════════════ -->
<global>
<white_list>127.0.0.1</white_list>
<white_list>^localhost.localdomain$</white_list>
<white_list>90.50.148.138</white_list>
<white_list>90.120.69.13</white_list>
<white_list>10.0.0.3</white_list>
</global>
<!-- ═══════════════════════════════════════════════════════════
ACTIVE RESPONSE — COMMANDES
═══════════════════════════════════════════════════════════ -->
<command>
<name>disable-account</name>
<executable>disable-account</executable>
<timeout_allowed>yes</timeout_allowed>
</command>
<command>
<name>restart-wazuh</name>
<executable>restart-wazuh</executable>
</command>
<command>
<name>firewall-drop</name>
<executable>firewall-drop</executable>
<timeout_allowed>yes</timeout_allowed>
</command>
<command>
<name>host-deny</name>
<executable>host-deny</executable>
<timeout_allowed>yes</timeout_allowed>
</command>
<command>
<name>route-null</name>
<executable>route-null</executable>
<timeout_allowed>yes</timeout_allowed>
</command>
<command>
<name>win_route-null</name>
<executable>route-null.exe</executable>
<timeout_allowed>yes</timeout_allowed>
</command>
<command>
<name>netsh</name>
<executable>netsh.exe</executable>
<timeout_allowed>yes</timeout_allowed>
</command>
<command>
<name>clamav-quarantine</name>
<executable>clamav-quarantine</executable>
<timeout_allowed>no</timeout_allowed>
</command>
<!-- ═══════════════════════════════════════════════════════════
ACTIVE RESPONSE — RÈGLES
═══════════════════════════════════════════════════════════ -->
<!-- SSH mauvais mot de passe → ban PERMANENT (1 tentative suffit) -->
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>5710,5711,5712,5716,5720,5760,5763</rules_id>
<timeout>0</timeout>
</active-response>
<!-- Brute-force SSH (plusieurs échecs) → ban PERMANENT -->
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_group>authentication_failures</rules_group>
<timeout>0</timeout>
</active-response>
<!-- Échec auth générique → ban PERMANENT -->
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_group>authentication_failed</rules_group>
<timeout>0</timeout>
</active-response>
<!-- SSH mauvais mot de passe → host-deny PERMANENT (double protection) -->
<active-response>
<command>host-deny</command>
<location>local</location>
<rules_id>5710,5711,5712,5716,5720,5760,5763</rules_id>
<timeout>0</timeout>
</active-response>
<!-- Attaques web / injection (niveau ≥ 7) → firewall-drop 5 min -->
<active-response>
<command>firewall-drop</command>
<location>local</location>
<level>7</level>
<rules_group>web,attack</rules_group>
<timeout>300</timeout>
</active-response>
<!-- Scan de ports → firewall-drop 1 heure -->
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>1002,2502,2503,2504</rules_id>
<timeout>3600</timeout>
</active-response>
<!-- SSH brute-force → host-deny PERMANENT (double protection) -->
<active-response>
<command>host-deny</command>
<location>local</location>
<rules_group>authentication_failures,authentication_failed</rules_group>
<timeout>0</timeout>
</active-response>
<!-- ModSecurity SQLi / XSS / LFI → firewall-drop 6 heures -->
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>100202,100203,100205</rules_id>
<timeout>21600</timeout>
</active-response>
<!-- ModSecurity RCE / anomalie critique → firewall-drop 24 heures -->
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>100204,100206</rules_id>
<timeout>86400</timeout>
</active-response>
<!-- ClamAV virus détecté → quarantaine du fichier infecté -->
<active-response>
<command>clamav-quarantine</command>
<location>local</location>
<rules_id>52502</rules_id>
</active-response>
<!-- Attaque soutenue ModSec (5 blocs / 2 min) → firewall-drop 24 heures -->
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>100210</rules_id>
<timeout>86400</timeout>
</active-response>
<!-- SSH brute-force (3 tentatives / 60s) → firewall-drop 1 heure -->
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>100300</rules_id>
<timeout>3600</timeout>
</active-response>
<!-- SSH scan de comptes (3 users inexistants / 60s) → firewall-drop 24 heures -->
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>100301</rules_id>
<timeout>86400</timeout>
</active-response>
<!-- Sudo brute-force (3 échecs / 5 min) → disable-account 2h + firewall-drop 6h -->
<active-response>
<command>disable-account</command>
<location>local</location>
<rules_id>100310</rules_id>
<timeout>7200</timeout>
</active-response>
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>100310</rules_id>
<timeout>21600</timeout>
</active-response>
<!-- ═══════════════════════════════════════════════════════════
OWASP Top 10 — Active Response (ModSecurity)
═══════════════════════════════════════════════════════════ -->
<!-- OWASP#3 SQL Injection (100220) → firewall-drop 6h -->
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>100220</rules_id>
<timeout>21600</timeout>
</active-response>
<!-- OWASP#3 SQL Injection brute-force (100290) → firewall-drop 24h + host-deny -->
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>100290</rules_id>
<timeout>86400</timeout>
</active-response>
<active-response>
<command>host-deny</command>
<location>local</location>
<rules_id>100290</rules_id>
<timeout>0</timeout>
</active-response>
<!-- OWASP#3 LDAP Injection (100221) → firewall-drop 6h -->
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>100221</rules_id>
<timeout>21600</timeout>
</active-response>
<!-- OWASP#3 Command Injection (100222) → firewall-drop 24h + host-deny permanent -->
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>100222</rules_id>
<timeout>86400</timeout>
</active-response>
<active-response>
<command>host-deny</command>
<location>local</location>
<rules_id>100222</rules_id>
<timeout>0</timeout>
</active-response>
<!-- OWASP#3 Command Injection brute-force CRITICAL (100292) → permanent ban -->
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>100292</rules_id>
<timeout>0</timeout>
</active-response>
<active-response>
<command>host-deny</command>
<location>local</location>
<rules_id>100292</rules_id>
<timeout>0</timeout>
</active-response>
<!-- OWASP#3 PHP/Java Injection (100223, 100224) → firewall-drop 12h -->
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>100223,100224</rules_id>
<timeout>43200</timeout>
</active-response>
<!-- OWASP#6 RCE / Exploit attempts (100230) → firewall-drop 24h -->
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>100230</rules_id>
<timeout>86400</timeout>
</active-response>
<!-- OWASP#7 XSS attacks (100240) → firewall-drop 4h -->
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>100240</rules_id>
<timeout>14400</timeout>
</active-response>
<!-- OWASP#7 XSS brute-force (100291) → firewall-drop 24h -->
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>100291</rules_id>
<timeout>86400</timeout>
</active-response>
<!-- OWASP#7 HTTP Header Injection (100241) → firewall-drop 6h -->
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>100241</rules_id>
<timeout>21600</timeout>
</active-response>
<!-- OWASP#1 Path Traversal / LFI (100250) → firewall-drop 6h -->
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>100250</rules_id>
<timeout>21600</timeout>
</active-response>
<!-- OWASP#1 Path Traversal brute-force (100293) → firewall-drop 24h -->
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>100293</rules_id>
<timeout>86400</timeout>
</active-response>
<!-- OWASP#1 Remote File Inclusion (100251) → firewall-drop 12h -->
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>100251</rules_id>
<timeout>43200</timeout>
</active-response>
<!-- OWASP#1 Restricted File Access (100252) → firewall-drop 6h -->
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>100252</rules_id>
<timeout>21600</timeout>
</active-response>
<!-- OWASP#8 XXE (XML External Entity) (100260) → firewall-drop 12h -->
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>100260</rules_id>
<timeout>43200</timeout>
</active-response>
<!-- OWASP#9 Scanner Detection (100270) → firewall-drop 2h -->
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>100270</rules_id>
<timeout>7200</timeout>
</active-response>
<!-- OWASP SSRF (100280) → firewall-drop 12h -->
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>100280</rules_id>
<timeout>43200</timeout>
</active-response>
<!-- OWASP File Upload (100281) → firewall-drop 6h -->
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>100281</rules_id>
<timeout>21600</timeout>
</active-response>
<active-response>
<disabled>no</disabled>
<command>host-deny</command>
<location>all</location>
<rules_id>100210</rules_id>
<timeout>86400</timeout>
</active-response>
<active-response>
<command>firewall-drop</command>
<location>all</location>
<rules_id>100204,100206</rules_id>
<timeout>86400</timeout>
</active-response>
<active-response>
<command>firewall-drop</command>
<location>all</location>
<rules_id>5710,5711,5712,5716,5720,5760,5763</rules_id>
<timeout>0</timeout>
</active-response>
<!-- Brute-force SSH (plusieurs échecs) → ban PERMANENT -->
<active-response>
<command>firewall-drop</command>
<location>all</location>
<rules_group>authentication_failures</rules_group>
<timeout>0</timeout>
</active-response>
<!-- Échec auth générique → ban PERMANENT -->
<active-response>
<command>firewall-drop</command>
<location>all</location>
<rules_group>authentication_failed</rules_group>
<timeout>0</timeout>
</active-response>
<!-- SSH mauvais mot de passe → host-deny PERMANENT (double protection) -->
<active-response>
<command>host-deny</command>
<location>all</location>
<rules_id>5710,5711,5712,5716,5720,5760,5763</rules_id>
<timeout>0</timeout>
</active-response>
<!-- Attaques web / injection (niveau ≥ 7) → firewall-drop 5 min -->
<active-response>
<command>firewall-drop</command>
<location>all</location>
<level>7</level>
<rules_group>web,attack</rules_group>
<timeout>300</timeout>
</active-response>
<!-- Scan de ports → firewall-drop 1 heure -->
<active-response>
<command>firewall-drop</command>
<location>all</location>
<rules_id>1002,2502,2503,2504</rules_id>
<timeout>3600</timeout>
</active-response>
<!-- SSH brute-force → host-deny PERMANENT (double protection) -->
<active-response>
<command>host-deny</command>
<location>all</location>
<rules_group>authentication_failures,authentication_failed</rules_group>
<timeout>0</timeout>
</active-response>
<!-- ModSecurity SQLi / XSS / LFI → firewall-drop 6 heures -->
<active-response>
<command>firewall-drop</command>
<location>all</location>
<rules_id>100202,100203,100205</rules_id>
<timeout>21600</timeout>
</active-response>
<!-- SSH brute-force (3 tentatives / 60s) → firewall-drop 1 heure sur tous les agents -->
<active-response>
<command>firewall-drop</command>
<location>all</location>
<rules_id>100300</rules_id>
<timeout>3600</timeout>
</active-response>
<!-- SSH scan de comptes → firewall-drop 24 heures sur tous les agents -->
<active-response>
<command>firewall-drop</command>
<location>all</location>
<rules_id>100301</rules_id>
<timeout>86400</timeout>
</active-response>
<!-- ═══════════════════════════════════════════════════════════
OWASP Top 10 — Active Response (ModSecurity - location=all)
═══════════════════════════════════════════════════════════ -->
<!-- OWASP#3 SQLi (100220) → firewall-drop 6h (tous agents) -->
<active-response>
<command>firewall-drop</command>
<location>all</location>
<rules_id>100220</rules_id>
<timeout>21600</timeout>
</active-response>
<!-- OWASP#3 SQLi brute-force (100290) → firewall-drop 24h (tous agents) -->
<active-response>
<command>firewall-drop</command>
<location>all</location>
<rules_id>100290</rules_id>
<timeout>86400</timeout>
</active-response>
<!-- OWASP#3 LDAP (100221) → firewall-drop 6h -->
<active-response>
<command>firewall-drop</command>
<location>all</location>
<rules_id>100221</rules_id>
<timeout>21600</timeout>
</active-response>
<!-- OWASP#3 Command Injection (100222) → firewall-drop 24h -->
<active-response>
<command>firewall-drop</command>
<location>all</location>
<rules_id>100222</rules_id>
<timeout>86400</timeout>
</active-response>
<!-- OWASP#3 Command Injection CRITICAL (100292) → permanent ban -->
<active-response>
<command>firewall-drop</command>
<location>all</location>
<rules_id>100292</rules_id>
<timeout>0</timeout>
</active-response>
<!-- OWASP#3 PHP/Java (100223, 100224) → firewall-drop 12h -->
<active-response>
<command>firewall-drop</command>
<location>all</location>
<rules_id>100223,100224</rules_id>
<timeout>43200</timeout>
</active-response>
<!-- OWASP#6 RCE/Exploit (100230) → firewall-drop 24h -->
<active-response>
<command>firewall-drop</command>
<location>all</location>
<rules_id>100230</rules_id>
<timeout>86400</timeout>
</active-response>
<!-- OWASP#7 XSS (100240) → firewall-drop 4h -->
<active-response>
<command>firewall-drop</command>
<location>all</location>
<rules_id>100240</rules_id>
<timeout>14400</timeout>
</active-response>
<!-- OWASP#7 XSS brute-force (100291) → firewall-drop 24h -->
<active-response>
<command>firewall-drop</command>
<location>all</location>
<rules_id>100291</rules_id>
<timeout>86400</timeout>
</active-response>
<!-- OWASP#7 Header Injection (100241) → firewall-drop 6h -->
<active-response>
<command>firewall-drop</command>
<location>all</location>
<rules_id>100241</rules_id>
<timeout>21600</timeout>
</active-response>
<!-- OWASP#1 Path Traversal/LFI (100250) → firewall-drop 6h -->
<active-response>
<command>firewall-drop</command>
<location>all</location>
<rules_id>100250</rules_id>
<timeout>21600</timeout>
</active-response>
<!-- OWASP#1 Path Traversal brute-force (100293) → firewall-drop 24h -->
<active-response>
<command>firewall-drop</command>
<location>all</location>
<rules_id>100293</rules_id>
<timeout>86400</timeout>
</active-response>
<!-- OWASP#1 RFI (100251) → firewall-drop 12h -->
<active-response>
<command>firewall-drop</command>
<location>all</location>
<rules_id>100251</rules_id>
<timeout>43200</timeout>
</active-response>
<!-- OWASP#1 File Access (100252) → firewall-drop 6h -->
<active-response>
<command>firewall-drop</command>
<location>all</location>
<rules_id>100252</rules_id>
<timeout>21600</timeout>
</active-response>
<!-- OWASP#8 XXE (100260) → firewall-drop 12h -->
<active-response>
<command>firewall-drop</command>
<location>all</location>
<rules_id>100260</rules_id>
<timeout>43200</timeout>
</active-response>
<!-- OWASP#9 Scanner (100270) → firewall-drop 2h -->
<active-response>
<command>firewall-drop</command>
<location>all</location>
<rules_id>100270</rules_id>
<timeout>7200</timeout>
</active-response>
<!-- OWASP SSRF (100280) → firewall-drop 12h -->
<active-response>
<command>firewall-drop</command>
<location>all</location>
<rules_id>100280</rules_id>
<timeout>43200</timeout>
</active-response>
<!-- OWASP File Upload (100281) → firewall-drop 6h -->
<active-response>
<command>firewall-drop</command>
<location>all</location>
<rules_id>100281</rules_id>
<timeout>21600</timeout>
</active-response>
<!-- ═══════════════════════════════════════════════════════════
LOG ANALYSIS (commandes système)
═══════════════════════════════════════════════════════════ -->
<localfile>
<log_format>command</log_format>
<command>df -P</command>
<frequency>360</frequency>
</localfile>
<localfile>
<log_format>full_command</log_format>
<command>netstat -tulpn | sed 's/\([[:alnum:]]\+\)\ \+[[:digit:]]\+\ \+[[:digit:]]\+\ \+\(.*\):\([[:digit:]]*\)\ \+\([0-9\.\:\*]\+\).\+\ \([[:digit:]]*\/[[:alnum:]\-]*\).*/\1 \2 == \3 == \4 \5/' | sort -k 4 -g | sed 's/ == \(.*\) ==/:\1/' | sed 1,2d</command>
<alias>netstat listening ports</alias>
<frequency>360</frequency>
</localfile>
<localfile>
<log_format>full_command</log_format>
<command>last -n 20</command>
<frequency>360</frequency>
</localfile>
<!-- ═══════════════════════════════════════════════════════════
RULESET
═══════════════════════════════════════════════════════════ -->
<ruleset>
<!-- Règles et decoders natifs Wazuh -->
<decoder_dir>ruleset/decoders</decoder_dir>
<rule_dir>ruleset/rules</rule_dir>
<!-- Règles exclues — non pertinentes pour ce stack -->
<rule_exclude>0025-sendmail_rules.xml</rule_exclude>
<rule_exclude>0030-postfix_rules.xml</rule_exclude>
<rule_exclude>0035-spamd_rules.xml</rule_exclude>
<rule_exclude>0040-imapd_rules.xml</rule_exclude>
<rule_exclude>0045-mailscanner_rules.xml</rule_exclude>
<rule_exclude>0050-ms-exchange_rules.xml</rule_exclude>
<rule_exclude>0055-courier_rules.xml</rule_exclude>
<rule_exclude>0065-pix_rules.xml</rule_exclude>
<rule_exclude>0070-netscreenfw_rules.xml</rule_exclude>
<rule_exclude>0075-cisco-ios_rules.xml</rule_exclude>
<rule_exclude>0080-sonicwall_rules.xml</rule_exclude>
<rule_exclude>0090-telnetd_rules.xml</rule_exclude>
<rule_exclude>0100-solaris_bsm_rules.xml</rule_exclude>
<rule_exclude>0105-asterisk_rules.xml</rule_exclude>
<rule_exclude>0110-ms_dhcp_rules.xml</rule_exclude>
<rule_exclude>0115-arpwatch_rules.xml</rule_exclude>
<rule_exclude>0130-trend-osce_rules.xml</rule_exclude>
<rule_exclude>0135-hordeimp_rules.xml</rule_exclude>
<rule_exclude>0155-dovecot_rules.xml</rule_exclude>
<rule_exclude>0160-vmpop3d_rules.xml</rule_exclude>
<rule_exclude>0165-vpopmail_rules.xml</rule_exclude>
<rule_exclude>0170-ftpd_rules.xml</rule_exclude>
<rule_exclude>0175-proftpd_rules.xml</rule_exclude>
<rule_exclude>0185-vsftpd_rules.xml</rule_exclude>
<rule_exclude>0190-ms_ftpd_rules.xml</rule_exclude>
<rule_exclude>0195-named_rules.xml</rule_exclude>
<rule_exclude>0215-policy_rules.xml</rule_exclude>
<rule_exclude>0750-github_rules.xml</rule_exclude>
<rule_exclude>0250-apache_rules.xml</rule_exclude>
<rule_exclude>0255-zeus_rules.xml</rule_exclude>
<rule_exclude>0265-php_rules.xml</rule_exclude>
<rule_exclude>0275-squid_rules.xml</rule_exclude>
<rule_exclude>0295-mysql_rules.xml</rule_exclude>
<rule_exclude>0305-dropbear_rules.xml</rule_exclude>
<rule_exclude>0315-apparmor_rules.xml</rule_exclude>
<rule_exclude>0350-amazon_rules.xml</rule_exclude>
<rule_exclude>0360-serv-u_rules.xml</rule_exclude>
<rule_exclude>0385-oscap_rules.xml</rule_exclude>
<rule_exclude>0390-fortiddos_rules.xml</rule_exclude>
<rule_exclude>0391-fortigate_rules.xml</rule_exclude>
<rule_exclude>0392-fortimail_rules.xml</rule_exclude>
<rule_exclude>0393-fortiauth_rules.xml</rule_exclude>
<rule_exclude>0120-symantec-av_rules.xml</rule_exclude>
<rule_exclude>0125-symantec-ws_rules.xml</rule_exclude>
<rule_exclude>0395-hp_rules.xml</rule_exclude>
<rule_exclude>0405-rsa-auth-manager_rules.xml</rule_exclude>
<rule_exclude>0410-imperva_rules.xml</rule_exclude>
<rule_exclude>0415-sophos_rules.xml</rule_exclude>
<rule_exclude>0990-amazon-security-lake_rules.xml</rule_exclude>
<rule_exclude>0435-ms_logs_rules.xml</rule_exclude>
<rule_exclude>0445-identity_guard_rules.xml</rule_exclude>
<rule_exclude>0450-mongodb_rules.xml</rule_exclude>
<rule_exclude>0460-jenkins_rules.xml</rule_exclude>
<rule_exclude>0470-vshell_rules.xml</rule_exclude>
<rule_exclude>0475-suricata_rules.xml</rule_exclude>
<rule_exclude>0480-qualysguard_rules.xml</rule_exclude>
<rule_exclude>0485-cylance_rules.xml</rule_exclude>
<rule_exclude>0700-paloalto_rules.xml</rule_exclude>
<rule_exclude>0500-owncloud_rules.xml</rule_exclude>
<rule_exclude>0505-vuls_rules.xml</rule_exclude>
<rule_exclude>0525-openvas_rules.xml</rule_exclude>
<rule_exclude>0530-mysql_audit_rules.xml</rule_exclude>
<rule_exclude>0535-mariadb_rules.xml</rule_exclude>
<rule_exclude>0540-pfsense_rules.xml</rule_exclude>
<rule_exclude>0545-osquery_rules.xml</rule_exclude>
<rule_exclude>0550-kaspersky_rules.xml</rule_exclude>
<rule_exclude>0555-azure_rules.xml</rule_exclude>
<rule_exclude>0565-ms_ipsec_rules.xml</rule_exclude>
<rule_exclude>0575-win-base_rules.xml</rule_exclude>
<rule_exclude>0580-win-security_rules.xml</rule_exclude>
<rule_exclude>0585-win-application_rules.xml</rule_exclude>
<rule_exclude>0590-win-system_rules.xml</rule_exclude>
<rule_exclude>0601-win-vipre_rules.xml</rule_exclude>
<rule_exclude>0602-win-wfirewall_rules.xml</rule_exclude>
<rule_exclude>0610-win-ms_logs_rules.xml</rule_exclude>
<rule_exclude>0630-nextcloud_rules.xml</rule_exclude>
<rule_exclude>0675-panda-paps_rules.xml</rule_exclude>
<rule_exclude>0680-checkpoint-smart1_rules.xml</rule_exclude>
<rule_exclude>0770-gitlab_rules.xml</rule_exclude>
<rule_exclude>0775-arbor_rules.xml</rule_exclude>
<rule_exclude>0780-fireeye_rules.xml</rule_exclude>
<rule_exclude>0785-huawei-usg_rules.xml</rule_exclude>
<rule_exclude>0800-sysmon_id_1.xml</rule_exclude>
<rule_exclude>0810-sysmon_id_3.xml</rule_exclude>
<rule_exclude>0820-sysmon_id_7.xml</rule_exclude>
<rule_exclude>0830-sysmon_id_11.xml</rule_exclude>
<rule_exclude>0840-win_event_channel.xml</rule_exclude>
<rule_exclude>0860-sysmon_id_13.xml</rule_exclude>
<rule_exclude>0870-sysmon_id_8.xml</rule_exclude>
<rule_exclude>0905-cisco-ftd_rules.xml</rule_exclude>
<rule_exclude>0690-gcp_rules.xml</rule_exclude>
<rule_exclude>0910-ms-exchange-proxylogon_rules.xml</rule_exclude>
<rule_exclude>0920-oracledb_rules.xml</rule_exclude>
<rule_exclude>0925-eset-remote_rules.xml</rule_exclude>
<rule_exclude>0955-WEF-baseline_rules.xml</rule_exclude>
<rule_exclude>0960-macos_rules.xml</rule_exclude>
<rule_exclude>0995-microsoft-graph_rules.xml</rule_exclude>
<rule_exclude>0755-office365_rules.xml</rule_exclude>
<rule_exclude>0140-roundcube_rules.xml</rule_exclude>
<rule_exclude>0998-aws-security-hub-rules.xml</rule_exclude>
<rule_exclude>0400-openvpn_rules.xml</rule_exclude>
<list>etc/lists/audit-keys</list>
<list>etc/lists/amazon/aws-eventnames</list>
<list>etc/lists/security-eventchannel</list>
<list>etc/lists/malicious-ioc/malicious-ip</list>
<list>etc/lists/malicious-ioc/malicious-domains</list>
<list>etc/lists/malicious-ioc/malware-hashes</list>
<list>etc/lists/wireguard-trusted-ips</list>
<!-- Decoders et règles custom ModSecurity -->
<decoder_dir>etc/decoders</decoder_dir>
<rule_dir>etc/rules</rule_dir>
</ruleset>
<!-- ═══════════════════════════════════════════════════════════
RULE TEST
═══════════════════════════════════════════════════════════ -->
<rule_test>
<enabled>yes</enabled>
<threads>1</threads>
<max_sessions>64</max_sessions>
<session_timeout>15m</session_timeout>
</rule_test>
<!-- ═══════════════════════════════════════════════════════════
AUTHENTIFICATION AGENTS (wazuh-authd)
═══════════════════════════════════════════════════════════ -->
<auth>
<disabled>no</disabled>
<port>1515</port>
<use_source_ip>no</use_source_ip>
<purge>yes</purge>
<use_password>no</use_password>
<ciphers>HIGH:!ADH:!EXP:!MD5:!RC4:!3DES:!CAMELLIA:@STRENGTH</ciphers>
<ssl_verify_host>no</ssl_verify_host>
<ssl_manager_cert>etc/sslmanager.cert</ssl_manager_cert>
<ssl_manager_key>etc/sslmanager.key</ssl_manager_key>
<ssl_auto_negotiate>no</ssl_auto_negotiate>
</auth>
<!-- ═══════════════════════════════════════════════════════════
CLUSTER (standalone — désactivé)
═══════════════════════════════════════════════════════════ -->
<cluster>
<name>wazuh</name>
<node_name>node01</node_name>
<node_type>master</node_type>
<key>aa093264ef885029653eea20dfcf51ae</key>
<port>1516</port>
<bind_addr>0.0.0.0</bind_addr>
<nodes>
<node>wazuh.manager</node>
</nodes>
<hidden>no</hidden>
<disabled>yes</disabled>
</cluster>
</ossec_config>
<ossec_config>
<localfile>
<log_format>syslog</log_format>
<location>/var/ossec/logs/active-responses.log</location>
</localfile>
</ossec_config>
@@ -0,0 +1,143 @@
<?xml version="1.0" encoding="UTF-8"?>
<group name="clamav">
<rule id="100500" level="0">
<decoded_as>syslog</decoded_as>
<program_name>clam</program_name>
<description>ClamAV message grouping</description>
</rule>
<!-- ClamAV Malware Detections (Critical) -->
<rule id="100501" level="15">
<parent>100500</parent>
<regex>FOUND|Infected|detected</regex>
<description>ClamAV: Malware detected - $(var malware_name)</description>
<group>malware,clamav,infection</group>
</rule>
<!-- ClamAV Trojan/PUA Detection -->
<rule id="100502" level="12">
<parent>100500</parent>
<regex>Trojan|PUA|unwanted|grayware</regex>
<description>ClamAV: Potentially Unwanted Application detected</description>
<group>malware,clamav,pua</group>
</rule>
<!-- ClamAV Virus Detection -->
<rule id="100503" level="15">
<parent>100500</parent>
<regex>\.Virus\.|\.Win\.|\.Linux\.|\.Worm\.|\.Backdoor</regex>
<description>ClamAV: Virus detected - High severity</description>
<group>malware,clamav,virus</group>
</rule>
<!-- ClamAV Ransomware Detection -->
<rule id="100504" level="15">
<parent>100500</parent>
<regex>Ransomware|Encrypted|Cryptolocker|BadRabbit|WannaCry</regex>
<description>ClamAV: Ransomware detected</description>
<group>malware,clamav,ransomware</group>
</rule>
<!-- ClamAV Signature Update Success -->
<rule id="100505" level="3">
<parent>100500</parent>
<program_name>freshclam</program_name>
<regex>ClamAV update completed successfully|updated</regex>
<description>ClamAV: Signature database updated successfully</description>
<group>clamav,updates</group>
</rule>
<!-- ClamAV Signature Update Failed -->
<rule id="100506" level="10">
<parent>100500</parent>
<program_name>freshclam</program_name>
<regex>ERROR|Failed|error downloading|connection failed|timeout</regex>
<description>ClamAV: Signature update failed</description>
<group>clamav,updates,error</group>
</rule>
<!-- ClamAV Daemon Error -->
<rule id="100507" level="8">
<parent>100500</parent>
<program_name>clamd</program_name>
<regex>ERROR|error|CRITICAL</regex>
<description>ClamAV: Daemon error</description>
<group>clamav,error</group>
</rule>
<!-- ClamAV Scanner Started -->
<rule id="100508" level="3">
<parent>100500</parent>
<regex>Scanning started|initializing scanner</regex>
<description>ClamAV: Scanning started</description>
<group>clamav,scanning</group>
</rule>
<!-- ClamAV Scanner Completed -->
<rule id="100509" level="3">
<parent>100500</parent>
<regex>Scanning finished|scan completed</regex>
<description>ClamAV: Scanning completed</description>
<group>clamav,scanning</group>
</rule>
<!-- ClamAV Corrupted Files Alert -->
<rule id="100510" level="8">
<parent>100500</parent>
<regex>corrupted|corrupt file|damaged|unreadable</regex>
<description>ClamAV: Corrupted file detected</description>
<group>clamav,corruption</group>
</rule>
<!-- ClamAV Quarantine Actions -->
<rule id="100511" level="10">
<parent>100500</parent>
<regex>quarantine|moved|removed|deleted|archived</regex>
<description>ClamAV: File quarantined/removed</description>
<group>clamav,quarantine,action</group>
</rule>
<!-- ClamAV Scan Statistics -->
<rule id="100512" level="3">
<parent>100500</parent>
<regex>infects|infected files|detected</regex>
<description>ClamAV: Scan statistics</description>
<group>clamav,statistics</group>
</rule>
<!-- ClamAV Permission Denied -->
<rule id="100513" level="6">
<parent>100500</parent>
<regex>Permission denied|access denied|unable to scan</regex>
<description>ClamAV: Permission denied when scanning file</description>
<group>clamav,access</group>
</rule>
<!-- ClamAV Database Problem -->
<rule id="100514" level="10">
<parent>100500</parent>
<regex>database error|bad database|corrupt database|outdated database</regex>
<description>ClamAV: Database problem detected</description>
<group>clamav,database,error</group>
</rule>
<!-- Aggregate: Multiple Malware Detections in Short Time -->
<rule id="100515" level="16">
<parent>100501</parent>
<frequency>3</timeframe>3600</frequency>
<same_source_ip />
<description>ClamAV: Multiple malware detections in short time - possible outbreak</description>
<group>malware,clamav,outbreak</group>
</rule>
<!-- Aggregate: Ransomware Alert with Infection -->
<rule id="100516" level="16">
<if_matched_sid>100501</if_matched_sid>
<if_matched_sid>100504</if_matched_sid>
<same_source_ip />
<timeframe>300</timeframe>
<description>ClamAV: Ransomware and virus detected together - critical threat</description>
<group>malware,clamav,ransomware,critical</group>
</rule>
</group>
@@ -0,0 +1,45 @@
<!-- ═══════════════════════════════════════════════════════════════════
Active-Response — règles de détection précoce pour l'AR graduée
═══════════════════════════════════════════════════════════════════
Ces règles viennent EN PLUS des bans existants sur les règles natives
(5710, 5716, etc.) pour produire des alertes clairement labellisées
et déclencher des AR spécifiques par scénario d'attaque.
-->
<!-- ─── SSH ─────────────────────────────────────────────────────────── -->
<group name="syslog,sshd,authentication_failures,">
<!-- 3 mots de passe incorrects en 60s depuis la même IP
Note : le ban PERMANENT sur 5716 (1ère tentative) reste actif en parallèle.
Cette règle produit une alerte "brute-force" explicite + AR temporaire graduée. -->
<rule id="100300" level="10" frequency="3" timeframe="60">
<if_matched_sid>5716</if_matched_sid>
<same_source_ip />
<description>SSH: brute-force — 3 mots de passe faux en 60s depuis $(srcip)</description>
<group>authentication_failures,ssh_brute,</group>
</rule>
<!-- 3 users inexistants en 60s depuis la même IP → scan de comptes -->
<rule id="100301" level="12" frequency="3" timeframe="60">
<if_matched_sid>5710</if_matched_sid>
<same_source_ip />
<description>SSH: scan de comptes — 3 users inexistants en 60s depuis $(srcip)</description>
<group>authentication_failures,ssh_scan,</group>
</rule>
</group>
<!-- ─── SUDO ─────────────────────────────────────────────────────────── -->
<group name="syslog,sudo,authentication_failures,">
<!-- 3 échecs sudo en 5 min par le même utilisateur.
Couverture non présente dans les règles natives → AR disable-account. -->
<rule id="100310" level="10" frequency="3" timeframe="300">
<if_matched_sid>5401</if_matched_sid>
<same_user />
<description>Sudo: 3 échecs d'auth en 5 min — tentative d'escalade de privilèges</description>
<group>authentication_failures,sudo_brute,</group>
</rule>
</group>
@@ -0,0 +1,105 @@
<!-- ═══════════════════════════════════════════════════════════════════
Règles d'audit CIS 6.2.3.x — IDs 100900-100930
Regles deployees par ansible/hardening/playbook-audit-rules-*.yml
(surveillance sudo, surveillance omnex, et regles CIS restantes).
Objectif : ne faire remonter dans le dashboard QUE les evenements
qui indiquent un probleme de securite potentiel (level >= 4, seuil
de <log_alert_level> dans ossec.conf). Le reste (chaque commande
sudo, chaque commande omnex, chmod/chown routiniers, montages
Docker, deletions de fichiers, sessions login normales) continue
d'etre capture dans /var/log/audit/audit.log sur chaque host
(consultable via `ausearch -k <cle>`) mais reste sous le seuil
d'alerte generique (level 3, rule 80780+) donc invisible du
dashboard — c'est le comportement voulu, pas un oubli.
Cles VOLONTAIREMENT laissees au niveau generique (pas de regle ici) :
user_emulation, omnex_actions, perm_mod, mounts, session, logins,
delete — activite routiniere d'administration, pas un signal de
securite en soi. Consultable via ausearch si besoin d'investiguer.
-->
<group name="audit,">
<!-- 6.2.3.1 — Modification de /etc/sudoers ou /etc/sudoers.d
Signal fort : quelqu'un modifie qui a le droit d'utiliser sudo. -->
<rule id="100900" level="12">
<if_sid>80700</if_sid>
<field name="audit.key">^scope$</field>
<description>Audit: /etc/sudoers modifie — changement de perimetre administrateur</description>
<group>audit_security,gdpr_IV_35.7.d,</group>
</rule>
<!-- 6.2.3.3 — Modification du fichier de log sudo (technique anti-forensique) -->
<rule id="100901" level="12">
<if_sid>80700</if_sid>
<field name="audit.key">^sudo_log_file$</field>
<description>Audit: /var/log/sudo.log modifie — possible tentative d'effacement de traces</description>
<group>audit_security,gdpr_IV_35.7.d,</group>
</rule>
<!-- 6.2.3.8 — Modification des fichiers d'identite (passwd/shadow/group/pam) -->
<rule id="100902" level="10">
<if_sid>80700</if_sid>
<field name="audit.key">^identity$</field>
<description>Audit: fichier d'identite systeme modifie (passwd/shadow/group/pam)</description>
<group>audit_security,gdpr_IV_35.7.d,</group>
</rule>
<!-- 6.2.3.4 — Changement de date/heure systeme (technique anti-forensique classique) -->
<rule id="100903" level="8">
<if_sid>80700</if_sid>
<field name="audit.key">^time-change$</field>
<description>Audit: horloge systeme modifiee</description>
<group>audit_security,</group>
</rule>
<!-- 6.2.3.5 — Changement d'environnement reseau (hostname, /etc/hosts, netplan...) -->
<rule id="100904" level="8">
<if_sid>80700</if_sid>
<field name="audit.key">^system-locale$</field>
<description>Audit: configuration reseau systeme modifiee (hostname/hosts/netplan)</description>
<group>audit_security,</group>
</rule>
<!-- 6.2.3.14 — Modification de la politique AppArmor (desactivation possible d'un control de securite) -->
<rule id="100905" level="10">
<if_sid>80700</if_sid>
<field name="audit.key">^MAC-policy$</field>
<description>Audit: politique AppArmor modifiee — possible desactivation d'un controle de securite</description>
<group>audit_security,</group>
</rule>
<!-- 6.2.3.15-17 — Usage de chcon/setfacl/chacl (commandes rares, manipulation de contexte/ACL) -->
<rule id="100906" level="8">
<if_sid>80700</if_sid>
<field name="audit.key">^perm_chng$</field>
<description>Audit: commande chcon/setfacl/chacl executee — manipulation de contexte ou d'ACL</description>
<group>audit_security,</group>
</rule>
<!-- 6.2.3.18 — Usage de usermod (modification de compte via commande, possible escalade) -->
<rule id="100907" level="8">
<if_sid>80700</if_sid>
<field name="audit.key">^usermod$</field>
<description>Audit: commande usermod executee — modification de compte utilisateur</description>
<group>audit_security,</group>
</rule>
<!-- 6.2.3.19 — Chargement/dechargement de module noyau (technique rootkit classique) -->
<rule id="100908" level="12">
<if_sid>80700</if_sid>
<field name="audit.key">^kernel_modules$</field>
<description>Audit: module noyau charge/decharge — signal potentiel de rootkit</description>
<group>audit_security,</group>
</rule>
<!-- 6.2.3.7 — Tentative d'acces fichier refusee (EACCES/EPERM) : quelqu'un a essaye et echoue -->
<rule id="100909" level="6">
<if_sid>80700</if_sid>
<field name="audit.key">^access$</field>
<description>Audit: tentative d'acces fichier refusee (permissions insuffisantes)</description>
<group>audit_security,</group>
</rule>
</group>
@@ -0,0 +1,39 @@
<!-- ═══════════════════════════════════════════════════════════════
Règles ModSecurity — WAF container (gestion-waf)
Le decoder json intégré parse les events — on utilise <field> + <location>.
═══════════════════════════════════════════════════════════════ -->
<group name="web,modsecurity,">
<!-- Base : tout event JSON provenant du log ModSecurity -->
<rule id="100100" level="3">
<decoded_as>json</decoded_as>
<location>modsec_audit.log</location>
<field name="transaction.client_ip">\.+</field>
<description>ModSecurity: WAF event capturé</description>
<group>web,modsecurity,</group>
</rule>
<!-- Accès bloqué (HTTP 403) -->
<rule id="100102" level="10">
<if_sid>100100</if_sid>
<field name="transaction.response.http_code">^403$</field>
<description>ModSecurity: accès bloqué (403) — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>web,modsecurity,attack,blocked,</group>
</rule>
<!-- Trafic passant avec règle déclenchée (warning, anomaly scoring) -->
<rule id="100101" level="6">
<if_sid>100100</if_sid>
<description>ModSecurity: règle WAF déclenchée (non bloqué) — $(transaction.client_ip)</description>
<group>web,modsecurity,attack,</group>
</rule>
<!-- Blocages répétés depuis la même IP -->
<rule id="100103" level="12" frequency="5" timeframe="60">
<if_matched_sid>100102</if_matched_sid>
<description>ModSecurity: blocages répétés — possible scan ou attaque soutenue</description>
<group>web,modsecurity,attack,blocked,</group>
</rule>
</group>
@@ -0,0 +1,113 @@
<!-- ═══════════════════════════════════════════════════════════════════
Suppression des alertes SCA (Security Configuration Assessment)
═══════════════════════════════════════════════════════════════════
Les scans CIS benchmark génèrent des centaines d'alertes par cycle.
Toutes passées à level 0 — les résultats restent consultables dans
l'onglet SCA du dashboard sans polluer le flux d'alertes.
-->
<group name="sca,">
<!-- ─── Résumés de scan (score < 90%, <80%, <50%, <30%) ─────────── -->
<rule id="19001" level="0" overwrite="yes">
<if_sid>19000</if_sid>
<field name="sca.type">^summary</field>
<options>no_full_log</options>
<description>SCA summary — supprimé (voir onglet SCA du dashboard).</description>
</rule>
<rule id="19002" level="0" overwrite="yes">
<if_sid>19001</if_sid>
<field name="sca.score">^8\d</field>
<options>no_full_log</options>
<description>SCA summary: score &lt; 90% — supprimé.</description>
</rule>
<rule id="19003" level="0" overwrite="yes">
<if_sid>19001</if_sid>
<field name="sca.score">^7\d|^6\d|^5\d</field>
<options>no_full_log</options>
<description>SCA summary: score &lt; 80% — supprimé.</description>
</rule>
<rule id="19004" level="0" overwrite="yes">
<if_sid>19001</if_sid>
<field name="sca.score">^4\d|^3\d</field>
<options>no_full_log</options>
<description>SCA summary: score &lt; 50% — supprimé.</description>
</rule>
<rule id="19005" level="0" overwrite="yes">
<if_sid>19001</if_sid>
<field name="sca.score">^2\d|^1\d$|^\d$</field>
<options>no_full_log</options>
<description>SCA summary: score &lt; 30% — supprimé.</description>
</rule>
<!-- ─── Checks individuels (passed / failed / not applicable) ────── -->
<rule id="19007" level="0" overwrite="yes">
<if_sid>19006</if_sid>
<field name="sca.check.result">^failed</field>
<options>no_full_log</options>
<description>SCA check failed — supprimé.</description>
</rule>
<rule id="19008" level="0" overwrite="yes">
<if_sid>19006</if_sid>
<field name="sca.check.result">^passed</field>
<options>no_full_log</options>
<description>SCA check passed — supprimé.</description>
</rule>
<rule id="19009" level="0" overwrite="yes">
<if_sid>19006</if_sid>
<field name="sca.check.result">^not applicable</field>
<options>no_full_log</options>
<description>SCA check not applicable — supprimé.</description>
</rule>
<!-- ─── Changements de statut entre scans ───────────────────────── -->
<rule id="19010" level="0" overwrite="yes">
<if_sid>19008</if_sid>
<field name="sca.check.previous_result">^failed</field>
<options>no_full_log</options>
<description>SCA: failed → passed — supprimé.</description>
</rule>
<rule id="19011" level="0" overwrite="yes">
<if_sid>19007</if_sid>
<field name="sca.check.previous_result">^passed</field>
<options>no_full_log</options>
<description>SCA: passed → failed — supprimé.</description>
</rule>
<rule id="19012" level="0" overwrite="yes">
<if_sid>19009</if_sid>
<field name="sca.check.previous_result">^passed</field>
<options>no_full_log</options>
<description>SCA: passed → not applicable — supprimé.</description>
</rule>
<rule id="19013" level="0" overwrite="yes">
<if_sid>19009</if_sid>
<field name="sca.check.previous_result">^failed</field>
<options>no_full_log</options>
<description>SCA: failed → not applicable — supprimé.</description>
</rule>
<rule id="19014" level="0" overwrite="yes">
<if_sid>19007</if_sid>
<field name="sca.check.previous_result">^not applicable</field>
<options>no_full_log</options>
<description>SCA: not applicable → failed — supprimé.</description>
</rule>
<rule id="19015" level="0" overwrite="yes">
<if_sid>19008</if_sid>
<field name="sca.check.previous_result">^not applicable</field>
<options>no_full_log</options>
<description>SCA: not applicable → passed — supprimé.</description>
</rule>
</group>
@@ -0,0 +1,133 @@
<!-- ═══════════════════════════════════════════════════════════════════
Réduction du bruit SSH/PAM — overrides des règles natives Wazuh
═══════════════════════════════════════════════════════════════════
Supprimés (level 0) : événements sans valeur sécurité générés en masse
Conservés intacts : tout ce qui alimente l'active-response et la
détection de brute-force (5710, 5716, 5720, 5760, 5763…)
-->
<!-- ─── DPKG ───────────────────────────────────────────────────────────── -->
<group name="syslog,dpkg,">
<rule id="2901" level="0" overwrite="yes">
<if_sid>2900</if_sid>
<description>Dpkg: supprimé.</description>
</rule>
<rule id="2902" level="0" overwrite="yes">
<if_sid>2900</if_sid>
<description>Dpkg: supprimé.</description>
</rule>
<rule id="2903" level="0" overwrite="yes">
<if_sid>2900</if_sid>
<description>Dpkg: supprimé.</description>
</rule>
<rule id="2904" level="0" overwrite="yes">
<if_sid>2900</if_sid>
<description>Dpkg: supprimé.</description>
</rule>
</group>
<!-- ─── IP ADMIN (90.50.148.138) ─────────────────────────────────────── -->
<!-- Auth success supprimé pour l'IP admin : pas de valeur sécurité,
les échecs restent visibles si l'IP était usurpée. -->
<group name="syslog,sshd,">
<rule id="100010" level="0">
<if_sid>5715</if_sid>
<srcip>90.50.148.138</srcip>
<description>sshd: auth success IP admin — supprimé.</description>
</rule>
</group>
<!-- ─── PAM ──────────────────────────────────────────────────────────── -->
<group name="pam,syslog,">
<!-- 5501 (level 3) → PAM "session opened" : redondant avec 5715 sshd auth success -->
<rule id="5501" level="0" overwrite="yes">
<if_sid>5500</if_sid>
<match>session opened for user </match>
<description>PAM: Login session opened (bruit supprimé).</description>
</rule>
<!-- 5502 (level 3) → PAM "session closed" : purement informatif -->
<rule id="5502" level="0" overwrite="yes">
<if_sid>5500</if_sid>
<match>session closed for user </match>
<description>PAM: Login session closed (bruit supprimé).</description>
</rule>
</group>
<!-- ─── SSH ──────────────────────────────────────────────────────────── -->
<group name="syslog,sshd,">
<!-- 5704 (level 4) → timeout individuel : la règle de fréquence 5705
(level 10) continue de compter et d'alerter sur les patterns -->
<rule id="5704" level="0" overwrite="yes">
<if_sid>5700</if_sid>
<match>fatal: Timeout before authentication for</match>
<description>sshd: Timeout individuel supprimé (fréquence 5705 active).</description>
</rule>
<!-- 5706 (level 6) → "Did not receive identification string" : scanners
qui touchent le port 22 sans finir le handshake, très fréquent -->
<rule id="5706" level="0" overwrite="yes">
<if_sid>5700</if_sid>
<match>Did not receive identification string from</match>
<description>sshd: Sonde sans identification (bruit de scan supprimé).</description>
</rule>
<!-- 5740 (level 4) → connection reset by peer : bruit réseau/bots -->
<rule id="5740" level="0" overwrite="yes">
<if_sid>5700</if_sid>
<match>Connection reset by peer$</match>
<description>sshd: Connection reset by peer (bruit supprimé).</description>
</rule>
<!-- 5741 (level 4) → connection refused : pas d'intérêt sur un agent -->
<rule id="5741" level="0" overwrite="yes">
<if_sid>5700</if_sid>
<match>Connection refused$</match>
<description>sshd: Connection refused (bruit supprimé).</description>
</rule>
<!-- 5749 (level 4) → bad packet length : scanners/bots -->
<rule id="5749" level="0" overwrite="yes">
<if_sid>5700</if_sid>
<match>^Bad packet length</match>
<description>sshd: Bad packet length (bruit de scan supprimé).</description>
</rule>
<!-- 5762 (level 4) → connection reset : doublon de 5740 -->
<rule id="5762" level="0" overwrite="yes">
<if_sid>5700</if_sid>
<match>Connection reset</match>
<description>sshd: Connection reset (bruit supprimé).</description>
</rule>
</group>
<!-- ─── IOC Suricata/Windows — parents exclus (0475, 0580, 0840) ──────────
IMPORTANT: <match>PLACEHOLDER_NEVER_MATCHES</match> au lieu de <match>.</match>
pour éviter que ces rules catch-all bloquent l'évaluation des rules avec
un ID plus élevé (first-match semantics dans Wazuh). -->
<group name="ids,">
<rule id="99917" level="0" overwrite="yes">
<match>WAZUH_PLACEHOLDER_SURICATA_NEVER_MATCHES</match>
<description>Suricata: malicious domain — overwrite placeholder (non applicable).</description>
</rule>
<rule id="99918" level="0" overwrite="yes">
<match>WAZUH_PLACEHOLDER_SURICATA_DNS_NEVER_MATCHES</match>
<description>Suricata: malicious domain DNS — overwrite placeholder (non applicable).</description>
</rule>
</group>
<group name="windows,windows_security,">
<rule id="99919" level="0" overwrite="yes">
<match>WAZUH_PLACEHOLDER_WINDOWS_FAILED_NEVER_MATCHES</match>
<description>Windows: failed logon malicious IP — overwrite placeholder (non applicable).</description>
</rule>
<rule id="99920" level="0" overwrite="yes">
<match>WAZUH_PLACEHOLDER_WINDOWS_SUCCESS_NEVER_MATCHES</match>
<description>Windows: successful logon malicious IP — overwrite placeholder (non applicable).</description>
</rule>
</group>
@@ -0,0 +1,352 @@
<!-- ═══════════════════════════════════════════════════════════════════
ModSecurity OWASP Top 10 — Règles granulaires
═══════════════════════════════════════════════════════════════════
Détection des attaques OWASP Top 10 via ModSecurity + active-response
Correspondances CRS (OWASP ModSec Core Rule Set) :
• 941xxx : XSS (Cross-Site Scripting)
• 942xxx : SQL Injection, LDAP Injection
• 943xxx : XXE (XML External Entity)
• 930xxx : Path Traversal, RFI (Remote File Inclusion)
• 932xxx : Remote Command Execution
• 933xxx : PHP Injection
• 934xxx : Java Injection
• 950xxx : Exploit attempts
• 951xxx : Regex DoS
• 952xxx : Restricted File Access
• 953xxx : Insecure File Upload
• 954xxx : Proxy Abuse
• 955xxx : Malicious HTTP Headers
• 970xxx : SSRF / Open Redirect
-->
<group name="web,modsecurity,owasp,">
<!-- ═══════════════════════════════════════════════════════════
RÈGLES BLOQUÉES (100102 - HTTP 403)
═══════════════════════════════════════════════════════════ -->
<!-- ═══════════════════════════════════════════════════════════
OWASP #3 : INJECTION (SQLi, Command Injection, LDAP, etc.)
═══════════════════════════════════════════════════════════ -->
<!-- SQL Injection BLOQUÉE (942xx) -->
<rule id="100220" level="13">
<if_sid>100102</if_sid>
<field name="transaction.messages.message">942[0-9]</field>
<description>OWASP#3 SQL Injection bloquée — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>owasp_injection,sql_injection,</group>
</rule>
<!-- LDAP Injection (942xx - LDAP patterns) -->
<rule id="100221" level="12">
<if_sid>100102</if_sid>
<field name="transaction.messages.message">942[0-9].*LDAP\|ldap_filter</field>
<description>OWASP#3 LDAP Injection bloquée — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>owasp_injection,ldap_injection,</group>
</rule>
<!-- Command Injection / OS Command Execution (932xx) -->
<rule id="100222" level="14">
<if_sid>100102</if_sid>
<field name="transaction.messages.message">932[0-9]</field>
<description>OWASP#3 Remote Command Injection bloquée — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>owasp_injection,command_injection,rce,</group>
</rule>
<!-- PHP Injection (933xx) -->
<rule id="100223" level="13">
<if_sid>100102</if_sid>
<field name="transaction.messages.message">933[0-9]</field>
<description>OWASP#3 PHP Injection bloquée — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>owasp_injection,php_injection,</group>
</rule>
<!-- Java Injection (934xx) -->
<rule id="100224" level="13">
<if_sid>100102</if_sid>
<field name="transaction.messages.message">934[0-9]</field>
<description>OWASP#3 Java Injection bloquée — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>owasp_injection,java_injection,</group>
</rule>
<!-- ═══════════════════════════════════════════════════════════
OWASP #6 : VULNERABLE & OUTDATED COMPONENTS (Exploit attempts)
═══════════════════════════════════════════════════════════ -->
<!-- Remote Code Execution attempts (930xx, 950xx) -->
<rule id="100230" level="14">
<if_sid>100102</if_sid>
<field name="transaction.messages.message">930[0-9]\|950[0-9]</field>
<description>OWASP#6 RCE / Exploit attempt bloquée — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>owasp_vulnerable_components,rce,exploit,</group>
</rule>
<!-- ═══════════════════════════════════════════════════════════
OWASP #7 : AUTHENTICATION & SESSION (XSS/CSRF/Session Theft)
═══════════════════════════════════════════════════════════ -->
<!-- Cross-Site Scripting (941xx) -->
<rule id="100240" level="12">
<if_sid>100102</if_sid>
<field name="transaction.messages.message">941[0-9]</field>
<description>OWASP#7 XSS attack bloquée — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>owasp_auth,xss,session_theft,</group>
</rule>
<!-- HTTP Response Splitting / Header Injection (955xx) -->
<rule id="100241" level="12">
<if_sid>100102</if_sid>
<field name="transaction.messages.message">955[0-9]</field>
<description>OWASP#7 HTTP Header Injection bloquée — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>owasp_auth,header_injection,</group>
</rule>
<!-- ═══════════════════════════════════════════════════════════
OWASP #1 : BROKEN ACCESS CONTROL (Path Traversal, Bypass)
═══════════════════════════════════════════════════════════ -->
<!-- Path Traversal / Local File Inclusion (930xx - LFI patterns) -->
<rule id="100250" level="12">
<if_sid>100102</if_sid>
<field name="transaction.messages.message">930[0-9].*\(path\|traversal\|lfi\)</field>
<description>OWASP#1 Path Traversal / LFI bloquée — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>owasp_access_control,lfi,path_traversal,</group>
</rule>
<!-- Remote File Inclusion (930xx - RFI patterns) -->
<rule id="100251" level="13">
<if_sid>100102</if_sid>
<field name="transaction.messages.message">930[0-9].*\(rfi\|remote\|include\)</field>
<description>OWASP#1 Remote File Inclusion bloquée — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>owasp_access_control,rfi,</group>
</rule>
<!-- Restricted File Access (952xx) -->
<rule id="100252" level="11">
<if_sid>100102</if_sid>
<field name="transaction.messages.message">952[0-9]</field>
<description>OWASP#1 Unauthorized File Access bloquée — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>owasp_access_control,file_access,</group>
</rule>
<!-- ═══════════════════════════════════════════════════════════
OWASP #8 : SOFTWARE & DATA INTEGRITY (XXE, Deserialization)
═══════════════════════════════════════════════════════════ -->
<!-- XML External Entity (XXE) (943xx) -->
<rule id="100260" level="13">
<if_sid>100102</if_sid>
<field name="transaction.messages.message">943[0-9]</field>
<description>OWASP#8 XML External Entity (XXE) bloquée — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>owasp_data_integrity,xxe,</group>
</rule>
<!-- ═══════════════════════════════════════════════════════════
OWASP #9 : LOGGING & MONITORING FAILURES (Scanner Detection)
═══════════════════════════════════════════════════════════ -->
<!-- Web Application Scanner Detection (951xx, 954xx) -->
<rule id="100270" level="10">
<if_sid>100102</if_sid>
<field name="transaction.messages.message">951[0-9]\|954[0-9]</field>
<description>OWASP#9 Web Scanner / Bot detected — $(transaction.client_ip)</description>
<group>owasp_monitoring,scanner_detection,</group>
</rule>
<!-- ═══════════════════════════════════════════════════════════
AUTRES : SSRF, Open Redirect, Malicious File Upload
═══════════════════════════════════════════════════════════ -->
<!-- Server-Side Request Forgery (970xx, 971xx) -->
<rule id="100280" level="13">
<if_sid>100102</if_sid>
<field name="transaction.messages.message">970[0-9]\|971[0-9]</field>
<description>OWASP Server-Side Request Forgery (SSRF) bloquée — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>owasp_misc,ssrf,</group>
</rule>
<!-- Insecure File Upload (953xx) -->
<rule id="100281" level="12">
<if_sid>100102</if_sid>
<field name="transaction.messages.message">953[0-9]</field>
<description>OWASP Insecure File Upload bloquée — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>owasp_misc,file_upload,</group>
</rule>
<!-- ═══════════════════════════════════════════════════════════
FRÉQUENCE & PATTERNS RÉPÉTÉS
═══════════════════════════════════════════════════════════ -->
<!-- SQLi répétées (brute-force injection) -->
<rule id="100290" level="14" frequency="3" timeframe="60">
<if_matched_sid>100220</if_matched_sid>
<description>OWASP#3 SQL Injection brute-force — $(transaction.client_ip) tentatives répétées</description>
<group>owasp_injection,sql_injection,brute_force,</group>
</rule>
<!-- XSS répétées (scanner ou attaque soutenue) -->
<rule id="100291" level="13" frequency="5" timeframe="120">
<if_matched_sid>100240</if_matched_sid>
<description>OWASP#7 XSS attack pattern — $(transaction.client_ip) attaque soutenue</description>
<group>owasp_auth,xss,</group>
</rule>
<!-- RCE attempts répétées (critiques) -->
<rule id="100292" level="15" frequency="2" timeframe="60">
<if_matched_sid>100222</if_matched_sid>
<description>OWASP#3 Command Injection attempts — $(transaction.client_ip) CRITICAL</description>
<group>owasp_injection,command_injection,critical,</group>
</rule>
<!-- Path Traversal brute-force -->
<rule id="100293" level="12" frequency="4" timeframe="180">
<if_matched_sid>100250</if_matched_sid>
<description>OWASP#1 Path Traversal brute-force — $(transaction.client_ip)</description>
<group>owasp_access_control,lfi,brute_force,</group>
</rule>
<!-- ═══════════════════════════════════════════════════════════
RÈGLES REMONTÉES (100100 - tous les events, bloqués ou non)
Patterns OWASP détectés sans être bloqués
═══════════════════════════════════════════════════════════ -->
<!-- OWASP#3 SQLi remontée (942xx - non bloquée, anomaly scoring) -->
<rule id="100320" level="9">
<if_sid>100100</if_sid>
<field name="transaction.messages.message">942[0-9]</field>
<description>OWASP#3 SQL Injection attempt (anomaly scoring) — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>owasp_injection,sql_injection,warning,</group>
</rule>
<!-- OWASP#3 Command Injection remontée (932xx - non bloquée) -->
<rule id="100321" level="10">
<if_sid>100100</if_sid>
<field name="transaction.messages.message">932[0-9]</field>
<description>OWASP#3 Command Injection attempt — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>owasp_injection,command_injection,warning,</group>
</rule>
<!-- OWASP#3 LDAP Injection remontée (942xx LDAP) -->
<rule id="100322" level="9">
<if_sid>100100</if_sid>
<field name="transaction.messages.message">942[0-9].*LDAP\|ldap_filter</field>
<description>OWASP#3 LDAP Injection attempt — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>owasp_injection,ldap_injection,warning,</group>
</rule>
<!-- OWASP#3 PHP/Java Injection remontée (933xx, 934xx) -->
<rule id="100323" level="9">
<if_sid>100100</if_sid>
<field name="transaction.messages.message">933[0-9]\|934[0-9]</field>
<description>OWASP#3 Code Injection attempt (PHP/Java) — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>owasp_injection,code_injection,warning,</group>
</rule>
<!-- OWASP#6 RCE/Exploit remontée (930xx, 950xx - non bloquée) -->
<rule id="100330" level="11">
<if_sid>100100</if_sid>
<field name="transaction.messages.message">930[0-9]\|950[0-9]</field>
<description>OWASP#6 RCE / Exploit attempt — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>owasp_vulnerable_components,rce,warning,</group>
</rule>
<!-- OWASP#7 XSS remontée (941xx - non bloquée) -->
<rule id="100340" level="8">
<if_sid>100100</if_sid>
<field name="transaction.messages.message">941[0-9]</field>
<description>OWASP#7 XSS attempt (anomaly scoring) — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>owasp_auth,xss,warning,</group>
</rule>
<!-- OWASP#7 Header Injection remontée (955xx) -->
<rule id="100341" level="8">
<if_sid>100100</if_sid>
<field name="transaction.messages.message">955[0-9]</field>
<description>OWASP#7 HTTP Header Injection attempt — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>owasp_auth,header_injection,warning,</group>
</rule>
<!-- OWASP#1 Path Traversal remontée (930xx LFI patterns) -->
<rule id="100350" level="8">
<if_sid>100100</if_sid>
<field name="transaction.messages.message">930[0-9].*\(path\|traversal\|lfi\)</field>
<description>OWASP#1 Path Traversal attempt — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>owasp_access_control,lfi,warning,</group>
</rule>
<!-- OWASP#1 RFI remontée (930xx RFI patterns) -->
<rule id="100351" level="9">
<if_sid>100100</if_sid>
<field name="transaction.messages.message">930[0-9].*\(rfi\|remote\|include\)</field>
<description>OWASP#1 Remote File Inclusion attempt — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>owasp_access_control,rfi,warning,</group>
</rule>
<!-- OWASP#1 File Access remontée (952xx) -->
<rule id="100352" level="8">
<if_sid>100100</if_sid>
<field name="transaction.messages.message">952[0-9]</field>
<description>OWASP#1 Unauthorized File Access attempt — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>owasp_access_control,file_access,warning,</group>
</rule>
<!-- OWASP#8 XXE remontée (943xx) -->
<rule id="100360" level="9">
<if_sid>100100</if_sid>
<field name="transaction.messages.message">943[0-9]</field>
<description>OWASP#8 XML External Entity (XXE) attempt — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>owasp_data_integrity,xxe,warning,</group>
</rule>
<!-- OWASP#9 Scanner remontée (951xx, 954xx) -->
<rule id="100370" level="6">
<if_sid>100100</if_sid>
<field name="transaction.messages.message">951[0-9]\|954[0-9]</field>
<description>OWASP#9 Web Scanner / Bot detected — $(transaction.client_ip)</description>
<group>owasp_monitoring,scanner_detection,warning,</group>
</rule>
<!-- OWASP SSRF remontée (970xx, 971xx) -->
<rule id="100380" level="9">
<if_sid>100100</if_sid>
<field name="transaction.messages.message">970[0-9]\|971[0-9]</field>
<description>OWASP Server-Side Request Forgery (SSRF) attempt — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>owasp_misc,ssrf,warning,</group>
</rule>
<!-- OWASP File Upload remontée (953xx) -->
<rule id="100381" level="8">
<if_sid>100100</if_sid>
<field name="transaction.messages.message">953[0-9]</field>
<description>OWASP Insecure File Upload attempt — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>owasp_misc,file_upload,warning,</group>
</rule>
<!-- ═══════════════════════════════════════════════════════════
FRÉQUENCE — Attaques par anomaly scoring répétées
═══════════════════════════════════════════════════════════ -->
<!-- SQLi anomaly scoring répétée -->
<rule id="100394" level="10" frequency="5" timeframe="300">
<if_matched_sid>100320</if_matched_sid>
<description>OWASP#3 SQL Injection anomaly scoring — $(transaction.client_ip) tentatives répétées</description>
<group>owasp_injection,sql_injection,anomaly,</group>
</rule>
<!-- XSS anomaly scoring répétée -->
<rule id="100395" level="10" frequency="8" timeframe="300">
<if_matched_sid>100340</if_matched_sid>
<description>OWASP#7 XSS anomaly scoring — $(transaction.client_ip) attaque soutenue</description>
<group>owasp_auth,xss,anomaly,</group>
</rule>
<!-- Command Injection tentatives répétées -->
<rule id="100396" level="12" frequency="2" timeframe="60">
<if_matched_sid>100321</if_matched_sid>
<description>OWASP#3 Command Injection attempts — $(transaction.client_ip) CRITICAL</description>
<group>owasp_injection,command_injection,anomaly,</group>
</rule>
</group>
@@ -0,0 +1,44 @@
<group name="web,modsecurity,attack,">
<!-- Règles spécialisées par type d'attaque — basées sur local_modsecurity_rules.xml (rule 100102) -->
<!-- SQL Injection bloquée -->
<rule id="100202" level="12">
<if_sid>100102</if_sid>
<field name="transaction.messages.message">SQL Injection\|SQLi\|942[0-9]</field>
<description>ModSecurity: SQL Injection bloquée — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>modsecurity_sqli,sql_injection,</group>
</rule>
<!-- XSS bloqué -->
<rule id="100203" level="12">
<if_sid>100102</if_sid>
<field name="transaction.messages.message">XSS\|941[0-9]</field>
<description>ModSecurity: XSS bloqué — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>modsecurity_xss,xss,</group>
</rule>
<!-- Remote Code Execution bloqué -->
<rule id="100204" level="14">
<if_sid>100102</if_sid>
<field name="transaction.messages.message">RCE\|930[0-9]\|932[0-9]</field>
<description>ModSecurity: tentative RCE bloquée — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>modsecurity_rce,rce,</group>
</rule>
<!-- Local File Inclusion bloqué -->
<rule id="100205" level="12">
<if_sid>100102</if_sid>
<field name="transaction.messages.message">LFI\|930[0-9]</field>
<description>ModSecurity: LFI bloqué — $(transaction.client_ip) → $(transaction.request.uri)</description>
<group>modsecurity_lfi,lfi,</group>
</rule>
<!-- Blocages répétés — attaque soutenue -->
<rule id="100210" level="14" frequency="5" timeframe="120">
<if_matched_sid>100102</if_matched_sid>
<description>ModSecurity: blocages répétés en 2 min — attaque soutenue</description>
<group>modsecurity_repeated,</group>
</rule>
</group>
@@ -0,0 +1,129 @@
<!-- Règles WireGuard VPN — IDs 100800-100826
Les logs kernel passent par rule 5100 (program_name=^kernel).
On hérite via if_sid 5100 et on filtre par match/regex.
Les rules 1082x sont des enfants pour les peers de confiance
(etc/lists/wireguard-trusted-ips) : le negate="yes" sur <list>
n'est pas supporté par analysisd ici, donc on matche positivement
et on laisse la règle la plus spécifique (l'enfant) l'emporter
sur le parent.
Niveau 3 (pas 0) : <log_alert_level>3</log_alert_level> dans
ossec.conf exclut tout niveau < 3 de l'index OpenSearch. Un niveau
0 rendrait les handshakes de confiance invisibles du dashboard VPN
(compteurs handshakes/peers actifs à 0). Le groupe vpn_trusted
permet de les filtrer d'une vue "alertes à traiter" sans les
retirer des stats. -->
<group name="wireguard,vpn,">
<!-- Keepalive : level 0 = pas d'index OpenSearch -->
<rule id="100800" level="0">
<if_sid>5100</if_sid>
<match>wireguard: wg0: </match>
<regex>keepalive packet</regex>
<description>WireGuard: keepalive peer $(srcip)</description>
<group>wireguard,vpn_keepalive,</group>
</rule>
<!-- Handshake initiation -->
<rule id="100801" level="3">
<if_sid>5100</if_sid>
<match>wireguard: wg0: Sending handshake initiation</match>
<description>WireGuard: handshake initiation vers peer $(srcip)</description>
<group>wireguard,vpn_handshake,</group>
</rule>
<rule id="100821" level="3">
<if_sid>100801</if_sid>
<list field="srcip" lookup="match_key">etc/lists/wireguard-trusted-ips</list>
<description>WireGuard: handshake initiation - peer de confiance $(srcip)</description>
<group>wireguard,vpn_handshake,vpn_trusted,</group>
</rule>
<!-- Handshake réponse envoyée -->
<rule id="100802" level="3">
<if_sid>5100</if_sid>
<match>wireguard: wg0: Sending handshake response</match>
<description>WireGuard: handshake reponse envoyee a peer $(srcip)</description>
<group>wireguard,vpn_handshake,</group>
</rule>
<rule id="100822" level="3">
<if_sid>100802</if_sid>
<list field="srcip" lookup="match_key">etc/lists/wireguard-trusted-ips</list>
<description>WireGuard: handshake reponse - peer de confiance $(srcip)</description>
<group>wireguard,vpn_handshake,vpn_trusted,</group>
</rule>
<!-- Handshake réponse reçue (session établie) -->
<rule id="100803" level="3">
<if_sid>5100</if_sid>
<match>wireguard: wg0: Receiving handshake response</match>
<description>WireGuard: handshake complet avec peer $(srcip)</description>
<group>wireguard,vpn_handshake,</group>
</rule>
<rule id="100823" level="3">
<if_sid>100803</if_sid>
<list field="srcip" lookup="match_key">etc/lists/wireguard-trusted-ips</list>
<description>WireGuard: handshake complet - peer de confiance $(srcip)</description>
<group>wireguard,vpn_handshake,vpn_trusted,</group>
</rule>
<!-- Handshake timeout (pas d'exclusion : un timeout reste un signal utile,
même pour un peer de confiance) -->
<rule id="100810" level="7">
<if_sid>5100</if_sid>
<match>wireguard: wg0: Handshake for peer</match>
<regex>did not complete</regex>
<description>WireGuard: handshake timeout peer $(srcip)</description>
<group>wireguard,vpn_warning,</group>
</rule>
<!-- Timeout répété >= 5 -->
<rule id="100811" level="10">
<if_sid>100810</if_sid>
<match>try 5|try 6|try 7|try 8|try 9</match>
<description>WireGuard: peer $(srcip) deconnecte</description>
<group>wireguard,vpn_disconnected,</group>
</rule>
<!-- Timeout grave >= 10 -->
<rule id="100812" level="12">
<if_sid>100810</if_sid>
<regex>retrying .try 1\d.</regex>
<description>WireGuard: peer $(srcip) hors ligne depuis plus de 60s</description>
<group>wireguard,vpn_disconnected,</group>
</rule>
<!-- Peer roaming -->
<rule id="100815" level="5">
<if_sid>5100</if_sid>
<match>wireguard: wg0: Peer </match>
<regex>is now roaming</regex>
<description>WireGuard: peer $(id) roaming vers $(srcip)</description>
<group>wireguard,vpn_roaming,</group>
</rule>
<rule id="100825" level="3">
<if_sid>100815</if_sid>
<list field="srcip" lookup="match_key">etc/lists/wireguard-trusted-ips</list>
<description>WireGuard: roaming - peer de confiance $(id) vers $(srcip)</description>
<group>wireguard,vpn_roaming,vpn_trusted,</group>
</rule>
<!-- Reconnexion après déconnexion -->
<rule id="100816" level="6">
<if_sid>5100</if_sid>
<match>wireguard: wg0: Receiving handshake response</match>
<description>WireGuard: peer $(srcip) reconnecte</description>
<group>wireguard,vpn_reconnected,</group>
</rule>
<rule id="100826" level="3">
<if_sid>100816</if_sid>
<list field="srcip" lookup="match_key">etc/lists/wireguard-trusted-ips</list>
<description>WireGuard: reconnexion - peer de confiance $(srcip)</description>
<group>wireguard,vpn_reconnected,vpn_trusted,</group>
</rule>
</group>
@@ -0,0 +1,333 @@
#!/usr/bin/env python3
"""
Crée le dashboard "Securite - API Backend & WAF" dans OpenSearch Dashboards (Wazuh).
Usage:
python3 create-dashboard-api.py
Le script se connecte depuis l'intérieur du container wazuh_dashboard via:
docker exec wazuh_dashboard python3 /tmp/create-dashboard-api.py
Données utilisées :
- rules 100101 (WAF alerté) / 100102 (WAF bloqué) → ModSecurity
- rules 100600-100613 → Brute-force login API
- fields: data.transaction.client_ip, .request.uri,
.response.http_code, .is_interrupted,
.messages.details.ruleId
"""
import json
import urllib.request
import urllib.error
import ssl
import base64
import sys
import os
# ── Configuration ─────────────────────────────────────────────────────────────
DASHBOARD_HOST = "https://localhost:5601"
DASHBOARD_USER = "kibanaserver"
DASHBOARD_PASS = os.environ.get("DASHBOARD_PASSWORD", "E9Jpr6586kQ3wYrCS2!")
INDEX_PATTERN = "wazuh-alerts-*"
# ── Queries ───────────────────────────────────────────────────────────────────
Q_WAF = "rule.id: 100101 OR rule.id: 100102"
Q_BLOCKED = "rule.id: 100102"
Q_ALERTED = "rule.id: 100101"
Q_BFORCE = "rule.groups: api_brute_force"
Q_ALL = Q_WAF + " OR " + Q_BFORCE
# ── Client HTTP ───────────────────────────────────────────────────────────────
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
_auth = base64.b64encode(f"{DASHBOARD_USER}:{DASHBOARD_PASS}".encode()).decode()
_headers = {
"Content-Type": "application/json",
"osd-xsrf": "true",
"Authorization": f"Basic {_auth}",
}
def api(method, path, body=None):
data = json.dumps(body).encode() if body else None
req = urllib.request.Request(DASHBOARD_HOST + path, data=data, headers=_headers, method=method)
try:
with urllib.request.urlopen(req, context=ctx, timeout=15) as r:
return json.loads(r.read())
except urllib.error.HTTPError as e:
return {"error": e.code, "msg": e.read().decode()[:300]}
def search_src(query):
return json.dumps({
"index": INDEX_PATTERN,
"filter": [],
"query": {"language": "kuery", "query": query},
})
# ── Visualisations ────────────────────────────────────────────────────────────
VISUALIZATIONS = [
# ── 1. Métrique : total alertes WAF ──────────────────────────────────────
{
"id": "api-waf-total",
"title": "[API] WAF - Total Alertes",
"type": "metric",
"query": Q_WAF,
"visState": {
"type": "metric",
"params": {
"metric": {
"percentageMode": False,
"useRanges": False,
"colorSchema": "Green to Red",
"metricColorMode": "Labels",
"colorsRange": [{"from": 0, "to": 50}, {"from": 50, "to": 500}, {"from": 500, "to": 99999}],
"labels": {"show": True},
"invertColors": False,
"style": {"bgFill": "#000", "bgColor": False, "labelColor": False, "subText": "alertes WAF", "fontSize": 60},
}
},
"aggs": [{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}}],
},
},
# ── 2. Métrique : requêtes bloquées ──────────────────────────────────────
{
"id": "api-waf-blocked",
"title": "[API] WAF - Requetes Bloquees",
"type": "metric",
"query": Q_BLOCKED,
"visState": {
"type": "metric",
"params": {
"metric": {
"percentageMode": False,
"useRanges": False,
"colorSchema": "Green to Red",
"metricColorMode": "Labels",
"colorsRange": [{"from": 0, "to": 10}, {"from": 10, "to": 100}, {"from": 100, "to": 99999}],
"labels": {"show": True},
"invertColors": False,
"style": {"bgFill": "#000", "bgColor": False, "labelColor": False, "subText": "bloquees par WAF", "fontSize": 60},
}
},
"aggs": [{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}}],
},
},
# ── 3. Métrique : brute-force login ──────────────────────────────────────
{
"id": "api-bf-count",
"title": "[API] Brute-Force Login",
"type": "metric",
"query": Q_BFORCE,
"visState": {
"type": "metric",
"params": {
"metric": {
"percentageMode": False,
"useRanges": False,
"colorSchema": "Green to Red",
"metricColorMode": "Labels",
"colorsRange": [{"from": 0, "to": 5}, {"from": 5, "to": 50}, {"from": 50, "to": 99999}],
"labels": {"show": True},
"invertColors": False,
"style": {"bgFill": "#000", "bgColor": False, "labelColor": False, "subText": "alertes brute-force", "fontSize": 60},
}
},
"aggs": [{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}}],
},
},
# ── 4. Timeline : activite WAF ────────────────────────────────────────────
{
"id": "api-waf-timeline",
"title": "[API] Timeline Activite WAF",
"type": "histogram",
"query": Q_ALL,
"visState": {
"type": "histogram",
"params": {
"type": "histogram",
"grid": {"categoryLines": False},
"categoryAxes": [{"id": "CategoryAxis-1", "type": "category", "position": "bottom", "show": True, "style": {}, "scale": {"type": "linear"}, "labels": {"show": True, "truncate": 100}, "title": {}}],
"valueAxes": [{"id": "ValueAxis-1", "name": "LeftAxis-1", "type": "value", "position": "left", "show": True, "style": {}, "scale": {"type": "linear", "mode": "normal"}, "labels": {"show": True, "rotate": 0, "filter": False, "truncate": 100}, "title": {"text": "Requetes"}}],
"seriesParams": [{"show": True, "type": "histogram", "mode": "stacked", "data": {"label": "Requetes", "id": "1"}, "valueAxis": "ValueAxis-1", "drawLinesBetweenPoints": True, "showCircles": True}],
"addTooltip": True, "addLegend": True, "legendPosition": "right", "times": [], "addTimeMarker": False,
},
"aggs": [
{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}},
{"id": "2", "enabled": True, "type": "date_histogram", "schema": "segment", "params": {"field": "@timestamp", "interval": "auto", "customInterval": "2h", "min_doc_count": 1, "extended_bounds": {}}},
{"id": "3", "enabled": True, "type": "terms", "schema": "group", "params": {"field": "rule.id", "size": 5, "order": "desc", "orderBy": "1", "otherBucket": False}},
],
},
},
# ── 5. Bar horizontal : top endpoints ciblés ─────────────────────────────
{
"id": "api-top-endpoints",
"title": "[API] Top Endpoints Cibles",
"type": "horizontal_bar",
"query": Q_WAF,
"visState": {
"type": "horizontal_bar",
"params": {
"type": "horizontal_bar",
"grid": {"categoryLines": False},
"categoryAxes": [{"id": "CategoryAxis-1", "type": "category", "position": "left", "show": True, "style": {}, "scale": {"type": "linear"}, "labels": {"show": True, "rotate": 0, "filter": True, "truncate": 250}, "title": {}}],
"valueAxes": [{"id": "ValueAxis-1", "name": "LeftAxis-1", "type": "value", "position": "bottom", "show": True, "style": {}, "scale": {"type": "linear", "mode": "normal"}, "labels": {"show": True, "rotate": 75, "filter": False, "truncate": 100}, "title": {"text": "Requetes"}}],
"seriesParams": [{"show": True, "type": "horizontal_bar", "mode": "normal", "data": {"label": "Requetes", "id": "1"}, "valueAxis": "ValueAxis-1", "drawLinesBetweenPoints": True, "showCircles": True}],
"addTooltip": True, "addLegend": True, "legendPosition": "right", "times": [], "addTimeMarker": False,
},
"aggs": [
{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}},
{"id": "2", "enabled": True, "type": "terms", "schema": "segment", "params": {"field": "data.transaction.request.uri", "size": 15, "order": "desc", "orderBy": "1", "otherBucket": True, "otherBucketLabel": "Autres", "missingBucket": False}},
],
},
},
# ── 6. Bar horizontal : top IPs attaquantes ───────────────────────────────
{
"id": "api-top-ips",
"title": "[API] Top IPs Attaquantes",
"type": "horizontal_bar",
"query": Q_ALL,
"visState": {
"type": "horizontal_bar",
"params": {
"type": "horizontal_bar",
"grid": {"categoryLines": False},
"categoryAxes": [{"id": "CategoryAxis-1", "type": "category", "position": "left", "show": True, "style": {}, "scale": {"type": "linear"}, "labels": {"show": True, "rotate": 0, "filter": True, "truncate": 200}, "title": {}}],
"valueAxes": [{"id": "ValueAxis-1", "name": "LeftAxis-1", "type": "value", "position": "bottom", "show": True, "style": {}, "scale": {"type": "linear", "mode": "normal"}, "labels": {"show": True, "rotate": 75, "filter": False, "truncate": 100}, "title": {"text": "Alertes"}}],
"seriesParams": [{"show": True, "type": "horizontal_bar", "mode": "normal", "data": {"label": "Alertes", "id": "1"}, "valueAxis": "ValueAxis-1", "drawLinesBetweenPoints": True, "showCircles": True}],
"addTooltip": True, "addLegend": True, "legendPosition": "right", "times": [], "addTimeMarker": False,
},
"aggs": [
{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}},
{"id": "2", "enabled": True, "type": "terms", "schema": "segment", "params": {"field": "data.transaction.client_ip", "size": 15, "order": "desc", "orderBy": "1", "otherBucket": False, "missingBucket": False}},
],
},
},
# ── 7. Pie : distribution codes HTTP ─────────────────────────────────────
{
"id": "api-http-codes",
"title": "[API] Distribution Codes HTTP",
"type": "pie",
"query": Q_WAF,
"visState": {
"type": "pie",
"params": {
"type": "pie", "addTooltip": True, "addLegend": True,
"legendPosition": "right", "isDonut": True,
"labels": {"show": True, "values": True, "last_level": True, "truncate": 100},
},
"aggs": [
{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}},
{"id": "2", "enabled": True, "type": "terms", "schema": "segment", "params": {"field": "data.transaction.response.http_code", "size": 10, "order": "desc", "orderBy": "1", "otherBucket": True, "otherBucketLabel": "Autres", "missingBucket": False}},
],
},
},
# ── 8. Pie : bloqué vs passé ──────────────────────────────────────────────
{
"id": "api-blocked-ratio",
"title": "[API] Bloque vs Alerte WAF",
"type": "pie",
"query": Q_WAF,
"visState": {
"type": "pie",
"params": {
"type": "pie", "addTooltip": True, "addLegend": True,
"legendPosition": "right", "isDonut": False,
"labels": {"show": True, "values": True, "last_level": True, "truncate": 100},
},
"aggs": [
{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}},
{"id": "2", "enabled": True, "type": "terms", "schema": "segment", "params": {"field": "data.transaction.is_interrupted", "size": 5, "order": "desc", "orderBy": "1", "otherBucket": False, "missingBucket": False}},
],
},
},
# ── 9. Table : top règles CRS déclenchées ────────────────────────────────
{
"id": "api-crs-rules",
"title": "[API] Top Regles CRS Declenchees",
"type": "table",
"query": Q_WAF,
"visState": {
"type": "table",
"params": {
"perPage": 15, "showPartialRows": False, "showMetricsAtAllLevels": False,
"sort": {"columnIndex": None, "direction": None}, "showTotal": False, "totalFunc": "sum",
},
"aggs": [
{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}},
{"id": "2", "enabled": True, "type": "terms", "schema": "bucket", "params": {"field": "data.transaction.messages.details.ruleId", "size": 15, "order": "desc", "orderBy": "1", "otherBucket": False, "missingBucket": False}},
{"id": "3", "enabled": True, "type": "terms", "schema": "bucket", "params": {"field": "data.transaction.messages.message", "size": 1, "order": "desc", "orderBy": "1", "otherBucket": False, "missingBucket": False}},
],
},
"uiStateJSON": json.dumps({"vis": {"params": {"sort": {"columnIndex": 0, "direction": "desc"}}}}),
},
]
# ── Layout dashboard (grille 48 colonnes) ─────────────────────────────────────
# Ligne 0 : 3 métriques (total | bloquées | brute-force)
# Ligne 6 : timeline pleine largeur
# Ligne 18: top endpoints (gauche) + top IPs (droite)
# Ligne 32: codes HTTP (gauche) + bloqué/passé (milieu) + règles CRS (droite)
PANELS = [
# Métriques
{"panelIndex": "1", "gridData": {"x": 0, "y": 0, "w": 16, "h": 6, "i": "1"}, "version": "2.19.5", "type": "visualization", "id": "api-waf-total", "embeddableConfig": {}},
{"panelIndex": "2", "gridData": {"x": 16, "y": 0, "w": 16, "h": 6, "i": "2"}, "version": "2.19.5", "type": "visualization", "id": "api-waf-blocked", "embeddableConfig": {}},
{"panelIndex": "3", "gridData": {"x": 32, "y": 0, "w": 16, "h": 6, "i": "3"}, "version": "2.19.5", "type": "visualization", "id": "api-bf-count", "embeddableConfig": {}},
# Timeline
{"panelIndex": "4", "gridData": {"x": 0, "y": 6, "w": 48, "h": 12, "i": "4"}, "version": "2.19.5", "type": "visualization", "id": "api-waf-timeline", "embeddableConfig": {}},
# Top endpoints + Top IPs
{"panelIndex": "5", "gridData": {"x": 0, "y": 18, "w": 28, "h": 16, "i": "5"}, "version": "2.19.5", "type": "visualization", "id": "api-top-endpoints", "embeddableConfig": {}},
{"panelIndex": "6", "gridData": {"x": 28, "y": 18, "w": 20, "h": 16, "i": "6"}, "version": "2.19.5", "type": "visualization", "id": "api-top-ips", "embeddableConfig": {}},
# Codes HTTP + bloqué/passé + règles CRS
{"panelIndex": "7", "gridData": {"x": 0, "y": 34, "w": 16, "h": 14, "i": "7"}, "version": "2.19.5", "type": "visualization", "id": "api-http-codes", "embeddableConfig": {}},
{"panelIndex": "8", "gridData": {"x": 16, "y": 34, "w": 16, "h": 14, "i": "8"}, "version": "2.19.5", "type": "visualization", "id": "api-blocked-ratio", "embeddableConfig": {}},
{"panelIndex": "9", "gridData": {"x": 32, "y": 34, "w": 16, "h": 14, "i": "9"}, "version": "2.19.5", "type": "visualization", "id": "api-crs-rules", "embeddableConfig": {}},
]
# ── Main ──────────────────────────────────────────────────────────────────────
def main():
print("=== Creation dashboard: Securite - API Backend & WAF ===\n")
for viz in VISUALIZATIONS:
vis_state = dict(viz["visState"])
vis_state["title"] = viz["title"]
body = {
"attributes": {
"title": viz["title"],
"visState": json.dumps(vis_state),
"uiStateJSON": viz.get("uiStateJSON", "{}"),
"description": "",
"kibanaSavedObjectMeta": {"searchSourceJSON": search_src(viz["query"])},
}
}
r = api("POST", f"/api/saved_objects/visualization/{viz['id']}?overwrite=true", body)
if "id" in r:
print(f" OK {viz['title']}")
else:
print(f" ERR {viz['title']}: {r}")
sys.exit(1)
dashboard_body = {
"attributes": {
"title": "Securite - API Backend et WAF",
"description": "ModSecurity WAF alertes et blocages, endpoints cibles, top IPs, codes HTTP, regles CRS",
"panelsJSON": json.dumps(PANELS),
"optionsJSON": json.dumps({"useMargins": True, "hidePanelTitles": False}),
"timeRestore": False,
"kibanaSavedObjectMeta": {
"searchSourceJSON": json.dumps({"query": {"language": "kuery", "query": ""}, "filter": []})
},
}
}
r = api("POST", "/api/saved_objects/dashboard/api-backend-waf-dashboard?overwrite=true", dashboard_body)
if "id" in r:
print(f"\n OK Dashboard: {r['id']}")
print(f"\n URL: https://<monitoring-ip>/#/app/dashboards#/view/{r['id']}")
else:
print(f"\n ERR Dashboard: {r}")
sys.exit(1)
print("\n=== Termine ===")
if __name__ == "__main__":
main()
@@ -0,0 +1,369 @@
#!/usr/bin/env python3
"""
Crée le dashboard "VPN - Activite WireGuard" dans OpenSearch Dashboards (Wazuh).
Usage:
docker cp create-dashboard-vpn.py wazuh_dashboard:/tmp/
docker exec wazuh_dashboard python3 /tmp/create-dashboard-vpn.py
Données utilisées :
- rules 100801-100803 handshakes WireGuard
- rules 100810-100812 timeouts / déconnexions
- rule 100815 peer roaming
- fields: data.srcip (IP peer), data.id (peer number), data.extra_data (retry)
- agent.name: vpn-prod
"""
import json
import urllib.request
import urllib.error
import ssl
import base64
import sys
import os
# ── Configuration ──────────────────────────────────────────────────────────────
DASHBOARD_HOST = "https://localhost:5601"
DASHBOARD_USER = "kibanaserver"
DASHBOARD_PASS = os.environ.get("DASHBOARD_PASSWORD", "E9Jpr6586kQ3wYrCS2!")
INDEX_PATTERN = "wazuh-alerts-*"
# ── Queries ────────────────────────────────────────────────────────────────────
# Note: pour un peer de la liste de confiance (etc/lists/wireguard-trusted-ips),
# analysisd indexe l'alerte sous le rule.id de la regle enfant 1082x (voir
# wireguard-rules.xml), pas celui du parent 1008xx. Il faut donc inclure les
# deux jeux d'IDs partout, sinon les peers de confiance disparaissent des stats.
Q_ALL = ("rule.id: 100801 OR rule.id: 100802 OR rule.id: 100803 OR rule.id: 100810 "
"OR rule.id: 100811 OR rule.id: 100812 OR rule.id: 100815 OR rule.id: 100816 "
"OR rule.id: 100821 OR rule.id: 100822 OR rule.id: 100823 OR rule.id: 100825 OR rule.id: 100826")
Q_HANDSHAKES = ("rule.id: 100801 OR rule.id: 100802 OR rule.id: 100803 "
"OR rule.id: 100821 OR rule.id: 100822 OR rule.id: 100823")
Q_WARNINGS = "rule.id: 100810 OR rule.id: 100811 OR rule.id: 100812"
# Peers actifs : cardinalite sur data.srcip, tous rule.id de handshake confondus
# (100802/100822 = reponse envoyee, le cas dominant sur un serveur qui recoit les
# connexions ; 100803/100823 = reponse recue, cote initiateur).
Q_COMPLETE = Q_HANDSHAKES
# ── Client HTTP ────────────────────────────────────────────────────────────────
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
_auth = base64.b64encode(f"{DASHBOARD_USER}:{DASHBOARD_PASS}".encode()).decode()
_headers = {
"Content-Type": "application/json",
"osd-xsrf": "true",
"Authorization": f"Basic {_auth}",
}
def api(method, path, body=None):
data = json.dumps(body).encode() if body else None
req = urllib.request.Request(DASHBOARD_HOST + path, data=data, headers=_headers, method=method)
try:
with urllib.request.urlopen(req, context=ctx, timeout=15) as r:
return json.loads(r.read())
except urllib.error.HTTPError as e:
return {"error": e.code, "msg": e.read().decode()[:300]}
def search_src(query):
return json.dumps({
"index": INDEX_PATTERN,
"filter": [],
"query": {"language": "kuery", "query": query},
})
# ── Visualisations ─────────────────────────────────────────────────────────────
VISUALIZATIONS = [
# ── 1. Métrique : total handshakes (24h) ──────────────────────────────────
{
"id": "vpn-total-handshakes",
"title": "[VPN] Handshakes (24h)",
"type": "metric",
"query": Q_HANDSHAKES,
"visState": {
"type": "metric",
"params": {
"metric": {
"percentageMode": False, "useRanges": False,
"colorSchema": "Green to Red",
"metricColorMode": "None",
"colorsRange": [{"from": 0, "to": 9999999}],
"labels": {"show": True},
"invertColors": False,
"style": {"bgFill": "#000", "bgColor": False, "labelColor": False,
"subText": "handshakes VPN", "fontSize": 60},
}
},
"aggs": [{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}}],
},
},
# ── 2. Métrique : alertes déconnexion ─────────────────────────────────────
{
"id": "vpn-disconnects",
"title": "[VPN] Alertes Deconnexion",
"type": "metric",
"query": Q_WARNINGS,
"visState": {
"type": "metric",
"params": {
"metric": {
"percentageMode": False, "useRanges": False,
"colorSchema": "Green to Red",
"metricColorMode": "Labels",
"colorsRange": [{"from": 0, "to": 1}, {"from": 1, "to": 10}, {"from": 10, "to": 99999}],
"labels": {"show": True},
"invertColors": False,
"style": {"bgFill": "#000", "bgColor": False, "labelColor": False,
"subText": "timeouts / deconnexions", "fontSize": 60},
}
},
"aggs": [{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}}],
},
},
# ── 3. Métrique : peers actifs distincts ──────────────────────────────────
{
"id": "vpn-active-peers",
"title": "[VPN] Peers Actifs (distincts)",
"type": "metric",
"query": Q_COMPLETE,
"visState": {
"type": "metric",
"params": {
"metric": {
"percentageMode": False, "useRanges": False,
"colorSchema": "Blues",
"metricColorMode": "None",
"colorsRange": [{"from": 0, "to": 99999}],
"labels": {"show": True},
"invertColors": False,
"style": {"bgFill": "#000", "bgColor": False, "labelColor": False,
"subText": "IPs peers uniques", "fontSize": 60},
}
},
"aggs": [
{"id": "1", "enabled": True, "type": "cardinality", "schema": "metric",
"params": {"field": "data.srcip"}},
],
},
},
# ── 4. Timeline : activité WireGuard ──────────────────────────────────────
{
"id": "vpn-timeline",
"title": "[VPN] Timeline Activite WireGuard",
"type": "histogram",
"query": Q_ALL,
"visState": {
"type": "histogram",
"params": {
"type": "histogram",
"grid": {"categoryLines": False},
"categoryAxes": [{"id": "CategoryAxis-1", "type": "category", "position": "bottom",
"show": True, "style": {}, "scale": {"type": "linear"},
"labels": {"show": True, "truncate": 100}, "title": {}}],
"valueAxes": [{"id": "ValueAxis-1", "name": "LeftAxis-1", "type": "value",
"position": "left", "show": True, "style": {},
"scale": {"type": "linear", "mode": "normal"},
"labels": {"show": True, "rotate": 0, "filter": False, "truncate": 100},
"title": {"text": "Evenements"}}],
"seriesParams": [{"show": True, "type": "histogram", "mode": "stacked",
"data": {"label": "Evenements", "id": "1"},
"valueAxis": "ValueAxis-1", "drawLinesBetweenPoints": True,
"showCircles": True}],
"addTooltip": True, "addLegend": True, "legendPosition": "right",
"times": [], "addTimeMarker": False,
},
"aggs": [
{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}},
{"id": "2", "enabled": True, "type": "date_histogram", "schema": "segment",
"params": {"field": "@timestamp", "interval": "auto", "min_doc_count": 1, "extended_bounds": {}}},
{"id": "3", "enabled": True, "type": "terms", "schema": "group",
"params": {"field": "rule.description", "size": 5, "order": "desc", "orderBy": "1",
"otherBucket": True, "otherBucketLabel": "Autres"}},
],
},
},
# ── 5. Bar horizontal : événements par peer (IP) ──────────────────────────
{
"id": "vpn-events-per-peer",
"title": "[VPN] Evenements par Peer",
"type": "horizontal_bar",
"query": Q_ALL,
"visState": {
"type": "horizontal_bar",
"params": {
"type": "horizontal_bar",
"grid": {"categoryLines": False},
"categoryAxes": [{"id": "CategoryAxis-1", "type": "category", "position": "left",
"show": True, "style": {},
"labels": {"show": True, "rotate": 0, "filter": True, "truncate": 200},
"title": {}}],
"valueAxes": [{"id": "ValueAxis-1", "name": "LeftAxis-1", "type": "value",
"position": "bottom", "show": True, "style": {},
"scale": {"type": "linear", "mode": "normal"},
"labels": {"show": True, "rotate": 0, "filter": False, "truncate": 100},
"title": {"text": "Evenements"}}],
"seriesParams": [{"show": True, "type": "horizontal_bar", "mode": "stacked",
"data": {"label": "Evenements", "id": "1"},
"valueAxis": "ValueAxis-1"}],
"addTooltip": True, "addLegend": True, "legendPosition": "right",
"times": [], "addTimeMarker": False,
},
"aggs": [
{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}},
{"id": "2", "enabled": True, "type": "terms", "schema": "segment",
"params": {"field": "data.srcip", "size": 10, "order": "desc", "orderBy": "1",
"otherBucket": False, "missingBucket": False}},
{"id": "3", "enabled": True, "type": "terms", "schema": "group",
"params": {"field": "rule.description", "size": 4, "order": "desc", "orderBy": "1",
"otherBucket": True, "otherBucketLabel": "Autres"}},
],
},
},
# ── 6. Pie : types d'événements ───────────────────────────────────────────
{
"id": "vpn-event-types",
"title": "[VPN] Types d Evenements",
"type": "pie",
"query": Q_ALL,
"visState": {
"type": "pie",
"params": {
"type": "pie", "addTooltip": True, "addLegend": True,
"legendPosition": "right", "isDonut": True,
"labels": {"show": True, "values": True, "last_level": True, "truncate": 100},
},
"aggs": [
{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}},
{"id": "2", "enabled": True, "type": "terms", "schema": "segment",
"params": {"field": "rule.description", "size": 8, "order": "desc", "orderBy": "1",
"otherBucket": True, "otherBucketLabel": "Autres", "missingBucket": False}},
],
},
},
# ── 7. Timeline : timeouts et déconnexions ────────────────────────────────
{
"id": "vpn-timeout-timeline",
"title": "[VPN] Timeouts et Deconnexions",
"type": "histogram",
"query": Q_WARNINGS,
"visState": {
"type": "histogram",
"params": {
"type": "histogram",
"grid": {"categoryLines": False},
"categoryAxes": [{"id": "CategoryAxis-1", "type": "category", "position": "bottom",
"show": True, "style": {},
"labels": {"show": True, "truncate": 100}, "title": {}}],
"valueAxes": [{"id": "ValueAxis-1", "name": "LeftAxis-1", "type": "value",
"position": "left", "show": True, "style": {},
"scale": {"type": "linear", "mode": "normal"},
"labels": {"show": True, "rotate": 0, "filter": False, "truncate": 100},
"title": {"text": "Alertes"}}],
"seriesParams": [{"show": True, "type": "histogram", "mode": "stacked",
"data": {"label": "Alertes", "id": "1"},
"valueAxis": "ValueAxis-1"}],
"addTooltip": True, "addLegend": True, "legendPosition": "right",
"times": [], "addTimeMarker": False,
},
"aggs": [
{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}},
{"id": "2", "enabled": True, "type": "date_histogram", "schema": "segment",
"params": {"field": "@timestamp", "interval": "auto", "min_doc_count": 1, "extended_bounds": {}}},
{"id": "3", "enabled": True, "type": "terms", "schema": "group",
"params": {"field": "data.srcip", "size": 6, "order": "desc", "orderBy": "1",
"otherBucket": False}},
],
},
},
# ── 8. Table : log des événements VPN ─────────────────────────────────────
{
"id": "vpn-events-table",
"title": "[VPN] Journal Evenements VPN",
"type": "table",
"query": Q_ALL,
"visState": {
"type": "table",
"params": {
"perPage": 20, "showPartialRows": False, "showMetricsAtAllLevels": False,
"sort": {"columnIndex": None, "direction": None},
"showTotal": False, "totalFunc": "sum",
},
"aggs": [
{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}},
{"id": "2", "enabled": True, "type": "terms", "schema": "bucket",
"params": {"field": "data.srcip", "size": 20, "order": "desc", "orderBy": "1",
"otherBucket": False, "missingBucket": True, "missingBucketLabel": "inconnu"}},
{"id": "3", "enabled": True, "type": "terms", "schema": "bucket",
"params": {"field": "rule.description", "size": 5, "order": "desc", "orderBy": "1",
"otherBucket": False, "missingBucket": False}},
],
},
"uiStateJSON": json.dumps({"vis": {"params": {"sort": {"columnIndex": 0, "direction": "desc"}}}}),
},
]
# ── Layout dashboard (grille 48 colonnes) ──────────────────────────────────────
# Ligne 0 : 3 métriques
# Ligne 6 : timeline activité (pleine largeur)
# Ligne 18: events par peer (gauche) + types d'événements (droite)
# Ligne 34: timeline timeouts (gauche) + table journal (droite)
PANELS = [
{"panelIndex": "1", "gridData": {"x": 0, "y": 0, "w": 16, "h": 6, "i": "1"}, "version": "2.19.5", "type": "visualization", "id": "vpn-total-handshakes", "embeddableConfig": {}},
{"panelIndex": "2", "gridData": {"x": 16, "y": 0, "w": 16, "h": 6, "i": "2"}, "version": "2.19.5", "type": "visualization", "id": "vpn-disconnects", "embeddableConfig": {}},
{"panelIndex": "3", "gridData": {"x": 32, "y": 0, "w": 16, "h": 6, "i": "3"}, "version": "2.19.5", "type": "visualization", "id": "vpn-active-peers", "embeddableConfig": {}},
{"panelIndex": "4", "gridData": {"x": 0, "y": 6, "w": 48, "h": 12, "i": "4"}, "version": "2.19.5", "type": "visualization", "id": "vpn-timeline", "embeddableConfig": {}},
{"panelIndex": "5", "gridData": {"x": 0, "y": 18, "w": 30, "h": 16, "i": "5"}, "version": "2.19.5", "type": "visualization", "id": "vpn-events-per-peer", "embeddableConfig": {}},
{"panelIndex": "6", "gridData": {"x": 30, "y": 18, "w": 18, "h": 16, "i": "6"}, "version": "2.19.5", "type": "visualization", "id": "vpn-event-types", "embeddableConfig": {}},
{"panelIndex": "7", "gridData": {"x": 0, "y": 34, "w": 24, "h": 14, "i": "7"}, "version": "2.19.5", "type": "visualization", "id": "vpn-timeout-timeline", "embeddableConfig": {}},
{"panelIndex": "8", "gridData": {"x": 24, "y": 34, "w": 24, "h": 14, "i": "8"}, "version": "2.19.5", "type": "visualization", "id": "vpn-events-table", "embeddableConfig": {}},
]
# ── Main ───────────────────────────────────────────────────────────────────────
def main():
print("=== Creation dashboard: VPN - Activite WireGuard ===\n")
for viz in VISUALIZATIONS:
vis_state = dict(viz["visState"])
vis_state["title"] = viz["title"]
body = {
"attributes": {
"title": viz["title"],
"visState": json.dumps(vis_state),
"uiStateJSON": viz.get("uiStateJSON", "{}"),
"description": "",
"kibanaSavedObjectMeta": {"searchSourceJSON": search_src(viz["query"])},
}
}
r = api("POST", f"/api/saved_objects/visualization/{viz['id']}?overwrite=true", body)
if "id" in r:
print(f" OK {viz['title']}")
else:
print(f" ERR {viz['title']}: {r}")
sys.exit(1)
dashboard_body = {
"attributes": {
"title": "VPN - Activite WireGuard",
"description": "Handshakes peers, deconnexions, timeouts, activite par IP — agent vpn-prod",
"panelsJSON": json.dumps(PANELS),
"optionsJSON": json.dumps({"useMargins": True, "hidePanelTitles": False}),
"timeRestore": True,
"timeFrom": "now-24h",
"timeTo": "now",
"kibanaSavedObjectMeta": {
"searchSourceJSON": json.dumps({"query": {"language": "kuery", "query": ""}, "filter": []})
},
}
}
r = api("POST", "/api/saved_objects/dashboard/vpn-wireguard-dashboard?overwrite=true", dashboard_body)
if "id" in r:
print(f"\n OK Dashboard: {r['id']}")
print(f" URL: https://<monitoring-ip>/#/app/dashboards#/view/{r['id']}")
else:
print(f"\n ERR Dashboard: {r}")
sys.exit(1)
print("\n=== Termine ===")
if __name__ == "__main__":
main()
@@ -0,0 +1,255 @@
#!/usr/bin/env python3
"""
Crée le dashboard "Securite - Alertes Web et Firewall" dans OpenSearch Dashboards (Wazuh).
Usage:
python3 create-dashboard.py
Le script se connecte depuis l'intérieur du container wazuh_dashboard via:
docker exec wazuh_dashboard python3 /tmp/create-dashboard.py
Ou directement si OpenSearch Dashboards est accessible sur localhost:5601.
"""
import json
import urllib.request
import urllib.error
import ssl
import base64
import subprocess
import sys
import os
# ── Configuration ─────────────────────────────────────────────────────────────
DASHBOARD_HOST = "https://localhost:5601"
DASHBOARD_USER = "kibanaserver"
DASHBOARD_PASS = os.environ.get("DASHBOARD_PASSWORD", "E9Jpr6586kQ3wYrCS2!")
INDEX_PATTERN = "wazuh-alerts-*"
# ── Queries de filtre ─────────────────────────────────────────────────────────
Q_WEB = "rule.groups: web OR rule.groups: attack OR rule.groups: modsecurity OR rule.groups: api_brute_force"
Q_FW = "rule.id: 651"
Q_ALL = Q_WEB + " OR " + Q_FW
# ── Client HTTP ───────────────────────────────────────────────────────────────
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
_auth = base64.b64encode(f"{DASHBOARD_USER}:{DASHBOARD_PASS}".encode()).decode()
_headers = {
"Content-Type": "application/json",
"osd-xsrf": "true",
"Authorization": f"Basic {_auth}",
}
def api(method, path, body=None):
data = json.dumps(body).encode() if body else None
req = urllib.request.Request(DASHBOARD_HOST + path, data=data, headers=_headers, method=method)
try:
with urllib.request.urlopen(req, context=ctx, timeout=15) as r:
return json.loads(r.read())
except urllib.error.HTTPError as e:
return {"error": e.code, "msg": e.read().decode()[:300]}
def search_src(query):
return json.dumps({
"index": INDEX_PATTERN,
"filter": [],
"query": {"language": "kuery", "query": query},
})
# ── Définition des visualisations ─────────────────────────────────────────────
VISUALIZATIONS = [
{
"id": "sec-web-count",
"title": "[Securite] Alertes Web - Total",
"type": "metric",
"query": Q_WEB,
"visState": {
"type": "metric",
"params": {
"metric": {
"percentageMode": False,
"useRanges": False,
"colorSchema": "Green to Red",
"metricColorMode": "Labels",
"colorsRange": [{"from": 0, "to": 10}, {"from": 10, "to": 100}, {"from": 100, "to": 9999}],
"labels": {"show": True},
"invertColors": False,
"style": {"bgFill": "#000", "bgColor": False, "labelColor": False, "subText": "alertes web", "fontSize": 60},
}
},
"aggs": [{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}}],
},
},
{
"id": "sec-cs-count",
"title": "[Securite] Firewall - Total Bans",
"type": "metric",
"query": Q_FW,
"visState": {
"type": "metric",
"params": {
"metric": {
"percentageMode": False,
"useRanges": False,
"colorSchema": "Green to Red",
"metricColorMode": "Labels",
"colorsRange": [{"from": 0, "to": 5}, {"from": 5, "to": 50}, {"from": 50, "to": 9999}],
"labels": {"show": True},
"invertColors": False,
"style": {"bgFill": "#000", "bgColor": False, "labelColor": False, "subText": "bans firewall", "fontSize": 60},
}
},
"aggs": [{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}}],
},
},
{
"id": "sec-web-timeline",
"title": "[Securite] Alertes Web - Timeline",
"type": "histogram",
"query": Q_WEB,
"visState": {
"type": "histogram",
"params": {
"type": "histogram",
"grid": {"categoryLines": False},
"categoryAxes": [{"id": "CategoryAxis-1", "type": "category", "position": "bottom", "show": True, "style": {}, "scale": {"type": "linear"}, "labels": {"show": True, "truncate": 100}, "title": {}}],
"valueAxes": [{"id": "ValueAxis-1", "name": "LeftAxis-1", "type": "value", "position": "left", "show": True, "style": {}, "scale": {"type": "linear", "mode": "normal"}, "labels": {"show": True, "rotate": 0, "filter": False, "truncate": 100}, "title": {"text": "Alertes"}}],
"seriesParams": [{"show": True, "type": "histogram", "mode": "stacked", "data": {"label": "Alertes", "id": "1"}, "valueAxis": "ValueAxis-1", "drawLinesBetweenPoints": True, "showCircles": True}],
"addTooltip": True, "addLegend": True, "legendPosition": "right", "times": [], "addTimeMarker": False,
},
"aggs": [
{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}},
{"id": "2", "enabled": True, "type": "date_histogram", "schema": "segment", "params": {"field": "@timestamp", "interval": "auto", "customInterval": "2h", "min_doc_count": 1, "extended_bounds": {}}},
],
},
},
{
"id": "sec-top-ips",
"title": "[Securite] Top IPs Attaquantes",
"type": "horizontal_bar",
"query": Q_ALL,
"visState": {
"type": "horizontal_bar",
"params": {
"type": "horizontal_bar",
"grid": {"categoryLines": False},
"categoryAxes": [{"id": "CategoryAxis-1", "type": "category", "position": "left", "show": True, "style": {}, "scale": {"type": "linear"}, "labels": {"show": True, "rotate": 0, "filter": True, "truncate": 200}, "title": {}}],
"valueAxes": [{"id": "ValueAxis-1", "name": "LeftAxis-1", "type": "value", "position": "bottom", "show": True, "style": {}, "scale": {"type": "linear", "mode": "normal"}, "labels": {"show": True, "rotate": 75, "filter": False, "truncate": 100}, "title": {"text": "Alertes"}}],
"seriesParams": [{"show": True, "type": "horizontal_bar", "mode": "stacked", "data": {"label": "Alertes", "id": "1"}, "valueAxis": "ValueAxis-1", "drawLinesBetweenPoints": True, "showCircles": True}],
"addTooltip": True, "addLegend": True, "legendPosition": "right", "times": [], "addTimeMarker": False,
},
"aggs": [
{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}},
{"id": "2", "enabled": True, "type": "terms", "schema": "segment", "params": {"field": "data.srcip", "size": 15, "order": "desc", "orderBy": "1", "otherBucket": False, "missingBucket": False}},
],
},
},
{
"id": "sec-top-rules",
"title": "[Securite] Top Regles Declenchees",
"type": "pie",
"query": Q_WEB,
"visState": {
"type": "pie",
"params": {"type": "pie", "addTooltip": True, "addLegend": True, "legendPosition": "right", "isDonut": True, "labels": {"show": False, "values": True, "last_level": True, "truncate": 100}},
"aggs": [
{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}},
{"id": "2", "enabled": True, "type": "terms", "schema": "segment", "params": {"field": "rule.description", "size": 10, "order": "desc", "orderBy": "1", "otherBucket": True, "otherBucketLabel": "Autres", "missingBucket": False}},
],
},
},
{
"id": "sec-cs-bans-table",
"title": "[Securite] Firewall - IPs Bannies",
"type": "table",
"query": Q_FW,
"visState": {
"type": "table",
"params": {"perPage": 20, "showPartialRows": False, "showMetricsAtAllLevels": False, "sort": {"columnIndex": None, "direction": None}, "showTotal": False, "totalFunc": "sum"},
"aggs": [
{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}},
{"id": "2", "enabled": True, "type": "terms", "schema": "bucket", "params": {"field": "data.srcip", "size": 30, "order": "desc", "orderBy": "1", "otherBucket": False, "missingBucket": False}},
{"id": "3", "enabled": True, "type": "terms", "schema": "bucket", "params": {"field": "rule.description", "size": 5, "order": "desc", "orderBy": "1", "otherBucket": False, "missingBucket": False}},
],
},
"uiStateJSON": json.dumps({"vis": {"params": {"sort": {"columnIndex": 0, "direction": "desc"}}}}),
},
{
"id": "sec-severity",
"title": "[Securite] Distribution Severite",
"type": "pie",
"query": Q_WEB,
"visState": {
"type": "pie",
"params": {"type": "pie", "addTooltip": True, "addLegend": True, "legendPosition": "right", "isDonut": False, "labels": {"show": True, "values": True, "last_level": True, "truncate": 100}},
"aggs": [
{"id": "1", "enabled": True, "type": "count", "schema": "metric", "params": {}},
{"id": "2", "enabled": True, "type": "terms", "schema": "segment", "params": {"field": "rule.level", "size": 15, "order": "desc", "orderBy": "1", "otherBucket": False}},
],
},
},
]
# ── Layout dashboard (grid 48 colonnes) ───────────────────────────────────────
PANELS = [
{"panelIndex": "1", "gridData": {"x": 0, "y": 0, "w": 24, "h": 6, "i": "1"}, "version": "2.19.5", "type": "visualization", "id": "sec-web-count", "embeddableConfig": {}},
{"panelIndex": "2", "gridData": {"x": 24, "y": 0, "w": 24, "h": 6, "i": "2"}, "version": "2.19.5", "type": "visualization", "id": "sec-cs-count", "embeddableConfig": {}},
{"panelIndex": "3", "gridData": {"x": 0, "y": 6, "w": 48, "h": 12, "i": "3"}, "version": "2.19.5", "type": "visualization", "id": "sec-web-timeline", "embeddableConfig": {}},
{"panelIndex": "4", "gridData": {"x": 0, "y": 18, "w": 28, "h": 14, "i": "4"}, "version": "2.19.5", "type": "visualization", "id": "sec-top-ips", "embeddableConfig": {}},
{"panelIndex": "5", "gridData": {"x": 28, "y": 18, "w": 20, "h": 14, "i": "5"}, "version": "2.19.5", "type": "visualization", "id": "sec-top-rules", "embeddableConfig": {}},
{"panelIndex": "6", "gridData": {"x": 0, "y": 32, "w": 48, "h": 12, "i": "6"}, "version": "2.19.5", "type": "visualization", "id": "sec-cs-bans-table", "embeddableConfig": {}},
{"panelIndex": "7", "gridData": {"x": 0, "y": 44, "w": 24, "h": 10, "i": "7"}, "version": "2.19.5", "type": "visualization", "id": "sec-severity", "embeddableConfig": {}},
]
# ── Main ──────────────────────────────────────────────────────────────────────
def main():
print("=== Création du dashboard Securite - Alertes Web et Firewall ===\n")
# Visualisations
for viz in VISUALIZATIONS:
vis_state = dict(viz["visState"])
vis_state["title"] = viz["title"]
body = {
"attributes": {
"title": viz["title"],
"visState": json.dumps(vis_state),
"uiStateJSON": viz.get("uiStateJSON", "{}"),
"description": "",
"kibanaSavedObjectMeta": {"searchSourceJSON": search_src(viz["query"])},
}
}
r = api("POST", f"/api/saved_objects/visualization/{viz['id']}?overwrite=true", body)
if "id" in r:
print(f"{viz['title']}")
else:
print(f"{viz['title']}: {r}")
sys.exit(1)
# Dashboard
dashboard_body = {
"attributes": {
"title": "Securite - Alertes Web et Firewall",
"description": "Alertes OWASP ModSecurity brute-force API et bans Firewall",
"panelsJSON": json.dumps(PANELS),
"optionsJSON": json.dumps({"useMargins": True, "hidePanelTitles": False}),
"timeRestore": False,
"kibanaSavedObjectMeta": {
"searchSourceJSON": json.dumps({"query": {"language": "kuery", "query": ""}, "filter": []})
},
}
}
r = api("POST", "/api/saved_objects/dashboard/sec-web-crowdsec-dashboard?overwrite=true", dashboard_body)
if "id" in r:
print(f"\n ✅ Dashboard: {r['id']}")
print(f"\n URL: https://<monitoring-ip>/#/app/dashboards#/view/{r['id']}")
else:
print(f"\n ❌ Dashboard: {r}")
sys.exit(1)
print("\n=== Terminé ===")
if __name__ == "__main__":
main()
@@ -0,0 +1,107 @@
#!/usr/bin/env python3
"""
Crée des Saved Queries réutilisables pour la détection d'attaques web
(ModSecurity/OWASP, rules 100100-100396 voir
config/wazuh_manager/rules/modsecurity_owasp_rules.xml) dans OpenSearch
Dashboards (Wazuh).
Usage:
docker cp create-saved-queries-web.py wazuh_dashboard:/tmp/
docker exec wazuh_dashboard python3 /tmp/create-saved-queries-web.py
Une fois créées, ouvrir n'importe quelle vue (Security Events, Discover...) >
barre de recherche > icone dossier (Saved Queries) > sélectionner la query.
"""
import json
import urllib.request
import urllib.error
import ssl
import base64
import sys
import os
# ── Configuration ────────────────────────────────────────────────────────────
DASHBOARD_HOST = "https://localhost:5601"
DASHBOARD_USER = "kibanaserver"
DASHBOARD_PASS = os.environ.get("DASHBOARD_PASSWORD", "E9Jpr6586kQ3wYrCS2!")
# ── Queries ───────────────────────────────────────────────────────────────────
QUERIES = [
{
"id": "web-attacks-all",
"title": "Attaques Web - Toutes",
"description": "Tous les événements ModSecurity/OWASP (bloqués + remontés en anomaly scoring).",
"kuery": "rule.groups: web",
},
{
"id": "web-attacks-blocked",
"title": "Attaques Web - Bloquées (WAF)",
"description": "Requêtes réellement bloquées par le WAF (exclut les anomaly scoring non bloquants, groupe 'warning').",
"kuery": "rule.groups: web AND NOT rule.groups: warning",
},
{
"id": "web-attacks-critical",
"title": "Attaques Web - Critiques",
"description": "Niveau >= 13 : SQLi/RCE/command injection bloquées, XXE, SSRF, patterns répétés (owasp_injection critical/brute_force).",
"kuery": "rule.groups: web AND rule.level >= 13",
},
{
"id": "web-attacks-scanners",
"title": "Attaques Web - Scanners / Recon",
"description": "Détection de scanners/bots (OWASP#9 — groupe scanner_detection).",
"kuery": "rule.groups: scanner_detection",
},
{
"id": "web-attacks-repeated",
"title": "Attaques Web - Patterns répétés (brute-force)",
"description": "Attaques soutenues détectées par fréquence : SQLi (100290), XSS (100291), RCE (100292), path traversal (100293), anomaly scoring répété (100394-100396).",
"kuery": "rule.id: 100290 OR rule.id: 100291 OR rule.id: 100292 OR rule.id: 100293 OR rule.id: 100394 OR rule.id: 100395 OR rule.id: 100396",
},
]
# ── Client HTTP ───────────────────────────────────────────────────────────────
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
_auth = base64.b64encode(f"{DASHBOARD_USER}:{DASHBOARD_PASS}".encode()).decode()
_headers = {
"Content-Type": "application/json",
"osd-xsrf": "true",
"Authorization": f"Basic {_auth}",
}
def api(method, path, body=None):
data = json.dumps(body).encode() if body else None
req = urllib.request.Request(DASHBOARD_HOST + path, data=data, headers=_headers, method=method)
try:
with urllib.request.urlopen(req, context=ctx, timeout=15) as r:
return json.loads(r.read())
except urllib.error.HTTPError as e:
return {"error": e.code, "msg": e.read().decode()[:300]}
def main():
print("=== Creation Saved Queries: Attaques Web ===\n")
failed = False
for q in QUERIES:
body = {
"attributes": {
"title": q["title"],
"description": q["description"],
"query": {"language": "kuery", "query": q["kuery"]},
"filters": [],
}
}
r = api("POST", f"/api/saved_objects/query/{q['id']}?overwrite=true", body)
if "id" in r:
print(f" OK {q['title']}")
else:
print(f" ERR {q['title']}: {r}")
failed = True
if failed:
sys.exit(1)
print("\n=== Termine ===")
if __name__ == "__main__":
main()
@@ -0,0 +1,78 @@
#!/usr/bin/env python3
"""
Crée une Saved Query "Sans VPN" réutilisable dans OpenSearch Dashboards (Wazuh).
Objectif : permettre d'exclure en un clic les alertes WireGuard/VPN (handshakes,
timeouts, roaming, reconnexions rules 100800-100826) de la vue native
"Security Events" / "Threat Hunting", qui liste par défaut TOUTES les alertes
sans filtre de groupe. Le dashboard dédié "VPN - Activite WireGuard"
(create-dashboard-vpn.py) continue lui d'afficher ces événements normalement :
cette query ne supprime rien de l'index, elle filtre uniquement l'affichage.
Usage:
docker cp create-saved-query-no-vpn.py wazuh_dashboard:/tmp/
docker exec wazuh_dashboard python3 /tmp/create-saved-query-no-vpn.py
Une fois créée, ouvrir "Security Events" > barre de recherche > icône
"Saved Queries" (dossier) > sélectionner "Sans VPN".
"""
import json
import urllib.request
import urllib.error
import ssl
import base64
import sys
import os
# ── Configuration ────────────────────────────────────────────────────────────
DASHBOARD_HOST = "https://localhost:5601"
DASHBOARD_USER = "kibanaserver"
DASHBOARD_PASS = os.environ.get("DASHBOARD_PASSWORD", "E9Jpr6586kQ3wYrCS2!")
QUERY_ID = "no-vpn-alerts"
QUERY_TITLE = "Sans VPN"
QUERY_DESC = "Exclut les alertes WireGuard/VPN (handshakes, timeouts, roaming) de la vue Security Events."
QUERY_KUERY = "NOT rule.groups: wireguard"
# ── Client HTTP ───────────────────────────────────────────────────────────────
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
_auth = base64.b64encode(f"{DASHBOARD_USER}:{DASHBOARD_PASS}".encode()).decode()
_headers = {
"Content-Type": "application/json",
"osd-xsrf": "true",
"Authorization": f"Basic {_auth}",
}
def api(method, path, body=None):
data = json.dumps(body).encode() if body else None
req = urllib.request.Request(DASHBOARD_HOST + path, data=data, headers=_headers, method=method)
try:
with urllib.request.urlopen(req, context=ctx, timeout=15) as r:
return json.loads(r.read())
except urllib.error.HTTPError as e:
return {"error": e.code, "msg": e.read().decode()[:300]}
def main():
print("=== Creation Saved Query: Sans VPN ===\n")
body = {
"attributes": {
"title": QUERY_TITLE,
"description": QUERY_DESC,
"query": {"language": "kuery", "query": QUERY_KUERY},
"filters": [],
}
}
r = api("POST", f"/api/saved_objects/query/{QUERY_ID}?overwrite=true", body)
if "id" in r:
print(f" OK Saved Query '{QUERY_TITLE}' créée ({r['id']})")
print(" Utilisation : Security Events > barre de recherche > icone dossier (Saved Queries) > 'Sans VPN'")
else:
print(f" ERR {r}")
sys.exit(1)
if __name__ == "__main__":
main()
+24
View File
@@ -0,0 +1,24 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
CERTS_DIR="$SCRIPT_DIR/certs"
mkdir -p "$CERTS_DIR"
cd /tmp
echo "[*] Téléchargement de l'outil de génération de certificats Wazuh..."
curl -sO https://packages.wazuh.com/4.14/wazuh-certs-tool.sh
cp "$SCRIPT_DIR/config/certs.yml" config.yml
echo "[*] Génération des certificats..."
bash wazuh-certs-tool.sh -A
echo "[*] Copie des certificats..."
cp /tmp/wazuh-certificates/*.pem "$CERTS_DIR/"
cp /tmp/wazuh-certificates/root-ca.pem "$CERTS_DIR/root-ca-manager.pem"
chmod 640 "$CERTS_DIR"/*.pem
rm -rf /tmp/wazuh-certificates /tmp/wazuh-install-files /tmp/wazuh-install-files.tar /tmp/wazuh-certs-tool.sh /tmp/config.yml
echo "[OK] Certificats générés dans $CERTS_DIR"
+2
View File
@@ -0,0 +1,2 @@
Soc-1:AqGx3H0O9RJqfRR5Wzw7W!
Soc-2:P493A81j3bsSXCupftpVJI!
+68
View File
@@ -0,0 +1,68 @@
#!/bin/bash
# ═══════════════════════════════════════════════════════════════════
# Generate WireGuard client config for admin
# Usage: wireguard-admin-client.sh <admin_name> <server_ip> <server_pubkey>
# Output: admin_name.conf (ready for import in WireGuard app)
# ═══════════════════════════════════════════════════════════════════
set -e
ADMIN_NAME="${1:-admin2}"
VPN_SERVER_IP="${2:-}"
VPN_SERVER_PUBKEY="${3:-}"
if [ -z "$VPN_SERVER_IP" ] || [ -z "$VPN_SERVER_PUBKEY" ]; then
echo "Usage: $0 <admin_name> <server_ip> <server_pubkey>"
echo ""
echo "Example:"
echo " $0 admin1 123.45.67.89 'aBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890AbCdE='"
exit 1
fi
echo "[*] Generating WireGuard config for admin: $ADMIN_NAME"
# ─── Generate unique keys for this admin ───────────────────────
ADMIN_PRIVATEKEY=$(wg genkey)
ADMIN_PUBKEY=$(echo "$ADMIN_PRIVATEKEY" | wg pubkey)
# ─── Auto-assign IP from 10.0.0.3+ ─────────────────────────────
# Format: [admin_name].conf uses 10.0.0.3, 10.0.0.4, etc.
# You should track these manually or use a database
ADMIN_IP="10.0.0.3" # ← CHANGE THIS for each admin
# ─── Create config file ────────────────────────────────────────
cat > "${ADMIN_NAME}.conf" << EOF
[Interface]
Address = $ADMIN_IP/32
PrivateKey = $ADMIN_PRIVATEKEY
DNS = 8.8.8.8
[Peer]
PublicKey = $VPN_SERVER_PUBKEY
AllowedIPs = 10.0.0.0/24
Endpoint = $VPN_SERVER_IP:51820
PersistentKeepalive = 25
EOF
echo "[✓] Config created: ${ADMIN_NAME}.conf"
echo ""
echo "Configuration:"
echo " Name: $ADMIN_NAME"
echo " IP: $ADMIN_IP"
echo " Public Key: $ADMIN_PUBKEY"
echo ""
echo "Steps to add to VPN server:"
echo " 1. Copy public key above"
echo " 2. SSH to VPN server"
echo " 3. wg set wg0 peer $ADMIN_PUBKEY allowed-ips $ADMIN_IP/32"
echo " 4. wg show"
echo ""
echo "To import on your device:"
echo " 1. Install WireGuard app (Windows/Mac/Linux/iPhone/Android)"
echo " 2. Import ${ADMIN_NAME}.conf"
echo " 3. Connect to VPN"
echo " 4. Access services:"
echo " • Wazuh: https://10.0.0.2"
echo " • Dozzle: http://10.0.0.2:8080"
echo " • Beszel: http://10.0.0.2:9090"
echo " • S3: http://10.0.0.2:9000"
@@ -0,0 +1,104 @@
#!/bin/bash
# ═══════════════════════════════════════════════════════════════════
# WireGuard Client Setup (monitoring-uber)
# Install WireGuard, generate client keys, auto-connect to VPN server
# Client IP: 10.0.0.2/24
# ═══════════════════════════════════════════════════════════════════
set -e
# ─── Parameters ────────────────────────────────────────────────────
VPN_SERVER_IP="${1:-}" # IP publique du serveur VPN
VPN_SERVER_PUBKEY="${2:-}" # Clé publique du serveur VPN
if [ -z "$VPN_SERVER_IP" ] || [ -z "$VPN_SERVER_PUBKEY" ]; then
echo "Usage: $0 <VPN_SERVER_IP> <VPN_SERVER_PUBKEY>"
echo ""
echo "Example:"
echo " $0 123.45.67.89 'aBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890AbCdE='"
exit 1
fi
echo "[*] Setting up WireGuard Client on monitoring-uber..."
echo " Server IP: $VPN_SERVER_IP"
echo " Server Pubkey: $VPN_SERVER_PUBKEY"
# ─── Install WireGuard ─────────────────────────────────────────
echo "[*] Installing WireGuard..."
apt-get update -qq
apt-get install -y wireguard wireguard-tools
# ─── Create key directory ──────────────────────────────────────
mkdir -p /etc/wireguard
cd /etc/wireguard
umask 077
# ─── Generate client keys ──────────────────────────────────────
if [ ! -f client_privatekey ]; then
echo "[*] Generating client private key..."
wg genkey > client_privatekey
cat client_privatekey | wg pubkey > client_publickey
echo "[✓] Keys generated"
echo ""
echo "Client Public Key (for server):"
cat client_publickey
echo ""
else
echo "[!] Client keys already exist"
fi
# ─── Create wg0 configuration ──────────────────────────────────
echo "[*] Creating WireGuard client configuration..."
PRIVATE_KEY=$(cat client_privatekey)
CLIENT_PUBKEY=$(cat client_publickey)
cat > wg0.conf << EOF
[Interface]
# monitoring-uber VPN IP
Address = 10.0.0.2/24
ListenPort = 0
PrivateKey = $PRIVATE_KEY
[Peer]
# VPN Server
PublicKey = $VPN_SERVER_PUBKEY
AllowedIPs = 10.0.0.0/24
Endpoint = $VPN_SERVER_IP:51820
PersistentKeepalive = 25
EOF
chmod 600 wg0.conf
echo "[✓] Configuration created at /etc/wireguard/wg0.conf"
# ─── Enable at boot and start ──────────────────────────────────
echo "[*] Enabling WireGuard at boot..."
systemctl enable wg-quick@wg0 2>/dev/null || true
systemctl start wg-quick@wg0
sleep 2
# Vérifier connexion
if ip addr show wg0 &>/dev/null; then
echo "[✓] WireGuard interface up"
ip addr show wg0
else
echo "[!] WireGuard interface not up, check logs:"
journalctl -u wg-quick@wg0 -n 10
fi
echo ""
echo "[✓] WireGuard Client configured"
echo ""
echo "Configuration Summary:"
echo " • Interface: wg0"
echo " • Client IP: 10.0.0.2/24"
echo " • Server: $VPN_SERVER_IP:51820"
echo " • Config: /etc/wireguard/wg0.conf"
echo ""
echo "IMPORTANT: Add this client public key to VPN server:"
echo " wg set wg0 peer $(cat client_publickey) allowed-ips 10.0.0.2/32"
echo ""
echo "Verify connection:"
echo " ping 10.0.0.1"
@@ -0,0 +1,89 @@
#!/bin/bash
# ═══════════════════════════════════════════════════════════════════
# WireGuard Server Setup (VPS VPN)
# Install WireGuard, generate server keys, configure interface
# Network: 10.0.0.0/24
# Server IP: 10.0.0.1
# Listen: 0.0.0.0:51820/udp
# ═══════════════════════════════════════════════════════════════════
set -e
echo "[*] Setting up WireGuard Server..."
# ─── Install WireGuard ─────────────────────────────────────────
echo "[*] Installing WireGuard..."
apt-get update -qq
apt-get install -y wireguard wireguard-tools
# ─── Create key directory ──────────────────────────────────────
mkdir -p /etc/wireguard
cd /etc/wireguard
umask 077
# ─── Generate server keys ──────────────────────────────────────
if [ ! -f privatekey ]; then
echo "[*] Generating server private key..."
wg genkey > privatekey
cat privatekey | wg pubkey > publickey
echo "[✓] Keys generated"
echo ""
echo "Server Public Key:"
cat publickey
echo ""
else
echo "[!] Server keys already exist"
fi
# ─── Create wg0 configuration ──────────────────────────────────
echo "[*] Creating WireGuard interface configuration..."
cat > wg0.conf << 'EOF'
[Interface]
# Server IP dans le réseau VPN
Address = 10.0.0.1/24
ListenPort = 51820
# Charger la clé privée
PrivateKey = PRIVATE_KEY_PLACEHOLDER
# Accepter VPN traffic
PostUp = iptables -I FORWARD 1 -i %i -j ACCEPT; iptables -I FORWARD 1 -o %i -j ACCEPT; iptables -t nat -I POSTROUTING 1 -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
# Peers: monitoring-uber, admins (ajouté après)
EOF
# Remplacer placeholder par clé réelle
PRIVATE_KEY=$(cat privatekey)
sed -i "s|PRIVATE_KEY_PLACEHOLDER|$PRIVATE_KEY|" wg0.conf
# ─── Enable WireGuard interface ────────────────────────────────
echo "[*] Bringing up WireGuard interface..."
ip link add dev wg0 type wireguard
ip addr add 10.0.0.1/24 dev wg0
ip link set wg0 up
wg set wg0 private-key <(cat privatekey)
wg set wg0 listen-port 51820
# ─── Enable at boot ───────────────────────────────────────────
echo "[*] Enabling WireGuard at boot..."
systemctl enable wg-quick@wg0 2>/dev/null || true
systemctl start wg-quick@wg0 2>/dev/null || true
echo ""
echo "[✓] WireGuard Server configured"
echo ""
echo "Configuration Summary:"
echo " • Interface: wg0"
echo " • Server IP: 10.0.0.1/24"
echo " • Listen: 0.0.0.0:51820/udp"
echo " • Config: /etc/wireguard/wg0.conf"
echo ""
echo "Server Public Key (for clients):"
cat publickey
echo ""
echo "Next steps:"
echo " 1. Create client configs with wg-quick or manually"
echo " 2. Add peers to wg0:"
echo " wg set wg0 peer <CLIENT_PUBKEY> allowed-ips 10.0.0.X/32"
+6 -2
View File
@@ -18,6 +18,10 @@ frontend fe_https
mode http
option forwardfor
http-request set-header X-Forwarded-Proto https
# Force le Host envoye aux backends : sans ca, un scan/requete directe sur l'IP
# publique (Host: <IP> ou absent) fait grimper le score d'anomalie ModSecurity
# (rule 920280 "Missing a Host Header") et se fait bloquer en 403 cote WAF.
http-request set-header Host {{ domain_name }}
# /api/* et /uploads/* -> pool backend (Go), tout le reste -> pool frontend (SPA)
acl is_api path_beg /api/ /uploads/ /webhook/telegram /webhook/nowpayment
@@ -30,7 +34,7 @@ frontend fe_https
# ============================================================
backend be_api
mode http
option httpchk GET /api/health
http-check send meth GET uri /api/health hdr Host {{ domain_name }}
http-check expect status 200
server prod-uber {{ hostvars['prod-uber']['vpn_ip'] }}:443 ssl verify none check inter 2s fall 3 rise 2
@@ -41,7 +45,7 @@ backend be_api
# ============================================================
backend be_frontend
mode http
option httpchk GET /
http-check send meth GET uri / hdr Host {{ domain_name }}
http-check expect status 200
server prod-uber {{ hostvars['prod-uber']['vpn_ip'] }}:443 ssl verify none check inter 2s fall 3 rise 2
+8 -4
View File
@@ -22,15 +22,18 @@ AddressFamily inet
# Ciphers and keying
#RekeyLimit default none
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512,hmac-sha2-256
# Logging
#SyslogFacility AUTH
#LogLevel INFO
LogLevel VERBOSE
# Authentication:
AllowGroups {{ group_ssh }}
AllowUsers {{ user_ssh }}
#LoginGraceTime 2m
LoginGraceTime 1m
PermitRootLogin no
#StrictModes yes
MaxAuthTries 3
@@ -89,6 +92,7 @@ UsePAM yes
#AllowTcpForwarding yes
#GatewayPorts no
X11Forwarding no
DisableForwarding yes
#X11DisplayOffset 10
#X11UseLocalhost yes
#PermitTTY yes
@@ -97,8 +101,8 @@ PrintMotd no
#TCPKeepAlive yes
#PermitUserEnvironment no
#Compression delayed
#ClientAliveInterval 0
#ClientAliveCountMax 3
ClientAliveInterval 15
ClientAliveCountMax 3
#UseDNS no
#PidFile /run/sshd.pid
MaxStartups 10:30:60