courriel : la sonde boites cherche un vrai compte, et l'IMAP n'ecoute plus en clair
La sonde demandait `doveadm user`, que le userdb static (static_allow_all_users,
requis par la remise LMTP) satisfait pour n'importe quel nom : elle disait
« daniel » resolu chez deux locataires ou ce compte n'existe pas. Elle interroge
desormais le passdb (`doveadm auth lookup`), sans mot de passe, et cherche le
compte d'amorcage (amorcage_acces_courriel) plutot qu'un nom ecrit.
Le 143 (IMAP en clair) ecoutait par defaut a cote du 993 : eteint par
`inet_listener imap { port = 0 }`, eprouve sur banc Debian 13.
Deploye chez les deux locataires : 24 et 993 seulement ; sonde verte sur
sysadmin, rouge sur un compte inexistant. test_dovecot_sonde_et_imap.py garde
les deux ; 95 preuves.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
63f5fda091
commit
78c3505577
7 changed files with 133 additions and 13 deletions
22
CHANGELOG.md
22
CHANGELOG.md
|
|
@ -1,5 +1,27 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-10-12 (141) — La sonde « boites » cherche un vrai compte, et l'IMAP n'écoute plus en clair
|
||||
|
||||
**95 preuves.** `make verifier` passe ; `test_dovecot_sonde_et_imap.py` garde les deux corrections
|
||||
(témoin : il échoue sur cinq points contre le code précédent). Déployé chez les deux locataires
|
||||
après simulation.
|
||||
|
||||
- **La sonde « boites » était verte quoi qu'il arrive.** Elle demandait `doveadm user`, que le
|
||||
userdb `static` (`static_allow_all_users`, requis par la remise LMTP) satisfait pour n'importe
|
||||
quel nom : elle disait « daniel » résolu chez deux locataires où ce compte n'existe pas. Elle
|
||||
interroge désormais le **passdb** (`doveadm auth lookup`) : Dovecot cherche le compte dans
|
||||
l'annuaire, sous son compte de liaison, en LDAPS, sans tentative de mot de passe (rien ne compte
|
||||
pour le verrouillage). Mesuré : 0 s'il existe, 67 s'il n'existe pas.
|
||||
- **Le compte cherché est le compte d'amorçage** (`amorcage_acces_courriel`, son identifiant de
|
||||
connexion), le seul que Set-OPS sait exister — plus un nom écrit dans le rôle.
|
||||
- **Le 143 ne s'ouvre plus.** L'IMAP en clair écoutait par défaut à côté du 993 ; le pare-feu le
|
||||
bloquait et `ssl = required` refusait toute connexion avant STARTTLS, mais le port répondait.
|
||||
`inet_listener imap { port = 0 }`, éprouvé sur banc Debian 13 avant d'écrire le gabarit.
|
||||
|
||||
Mesuré sur `infra-mail-01`, chez Chezlepro et chez Technolibre : n'écoutent plus que le 24 (LMTP,
|
||||
TLS) et le 993 ; la sonde est verte sur `sysadmin@<locataire>.ca` et **rouge** (rc 2) sur un
|
||||
compte inexistant ; l'IMAPS répond au poste en TLS 1.3, certificat vérifié.
|
||||
|
||||
## 2026-10-12 (140) — Les mots de passe de soumission ne quittent plus la machine de Postfix
|
||||
|
||||
**95 preuves.** `make verifier` passe ; `test_sasl_local.py` garde le changement (témoin : il
|
||||
|
|
|
|||
1
Makefile
1
Makefile
|
|
@ -339,6 +339,7 @@ test: ## Lance les tests unitaires (derivation de nomenclature et d'inventaire)
|
|||
python3 scripts/tests/test_conditions_site.py
|
||||
python3 scripts/tests/test_relais_du_site.py
|
||||
python3 scripts/tests/test_sasl_local.py
|
||||
python3 scripts/tests/test_dovecot_sonde_et_imap.py
|
||||
python3 scripts/tests/test_sonde_tcp.py
|
||||
python3 scripts/tests/test_restauration.py
|
||||
python3 scripts/tests/test_repertoires_partages.py
|
||||
|
|
|
|||
|
|
@ -56,3 +56,8 @@ bout-en-bout en mono-domaine.
|
|||
de passe y passaient en clair. `serveur_postfix` appelle `tasks/auth_locale.yml`, qui
|
||||
pose sur sa machine un Dovecot d'**authentification seule** (cœur + LDAP, aucun port),
|
||||
joint par un socket local et vérifiant sur l'annuaire en LDAPS.
|
||||
- **IMAP en TLS direct seulement** (2026-10-12) : l'écoute en clair 143, ouverte par défaut,
|
||||
est éteinte (`inet_listener imap { port = 0 }`) ; seul le 993 répond.
|
||||
- **Sonde « boites »** : `doveadm auth lookup` du compte d'amorçage (`amorcage_acces_courriel`),
|
||||
c'est-à-dire une recherche dans le **passdb** LDAP, sans mot de passe. `doveadm user` ne
|
||||
prouvait rien : le userdb `static` (`static_allow_all_users`) répond pour n'importe quel nom.
|
||||
|
|
|
|||
|
|
@ -55,7 +55,13 @@ serveur_dovecot_auth_locale_paquets:
|
|||
serveur_dovecot_lmtp_tls: true
|
||||
|
||||
# --- Sonde de supervision -----------------------------------------------------------
|
||||
# UN COMPTE QUI DOIT EXISTER. Le resoudre prouve la chaine entiere : demon, configuration,
|
||||
# bind LDAP, mappage userdb. Variable, donc mise en defaut avec un nom inexistant sans
|
||||
# toucher a l'annuaire.
|
||||
serveur_dovecot_sonde_utilisateur: "daniel"
|
||||
# UN COMPTE QUI DOIT EXISTER, sous son IDENTIFIANT DE CONNEXION (l'attribut `mail` : c'est
|
||||
# ce que cherche le filtre de connexion). Le trouver prouve la chaine : demon, configuration,
|
||||
# liaison LDAPS, filtre. Variable, donc mise en defaut avec un nom inexistant sans toucher a
|
||||
# l'annuaire.
|
||||
#
|
||||
# Le compte d'amorcage, pas un nom ecrit (2026-10-12) : ce defaut valait `daniel`, qui
|
||||
# n'existe chez aucun locataire — et la sonde, qui interrogeait le userdb statique, le disait
|
||||
# trouve. Le compte d'amorcage est le seul que Set-OPS sait exister ; son adresse est un
|
||||
# intrant obligatoire de toute instance.
|
||||
serveur_dovecot_sonde_utilisateur: "{{ amorcage_acces_courriel | default('') }}"
|
||||
|
|
|
|||
|
|
@ -23,6 +23,16 @@ ssl_server_cert_file = {{ serveur_dovecot_tls_dir }}/cert.pem
|
|||
ssl_server_key_file = {{ serveur_dovecot_tls_dir }}/key.pem
|
||||
ssl_min_protocol = TLSv1.2
|
||||
|
||||
{# LE 143 NE S'OUVRE PLUS (2026-10-12). L'IMAP en clair ecoutait par defaut a cote du 993 :
|
||||
le pare-feu le bloquait et `ssl = required` refusait toute connexion avant STARTTLS, mais
|
||||
le port repondait. Directive : tout transit chiffre — on n'offre que le TLS direct (993,
|
||||
RFC 8314). `port = 0` eteint l'ecoute ; eprouve sur banc Debian 13 (le 993 reste). #}
|
||||
service imap-login {
|
||||
inet_listener imap {
|
||||
port = 0
|
||||
}
|
||||
}
|
||||
|
||||
# --- Auth / annuaire LDAP (serveur_openldap) ---
|
||||
auth_mechanisms = plain login
|
||||
ldap_uris = {{ serveur_dovecot_ldap_uris }}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,33 @@
|
|||
#!/bin/bash
|
||||
# GENERE par Set-OPS (role serveur_dovecot). Ne pas editer a la main.
|
||||
#
|
||||
# SONDE « boites » — Dovecot repond et resout un utilisateur dans l'annuaire
|
||||
# SONDE « boites » — Dovecot repond et trouve un compte dans l'annuaire
|
||||
#
|
||||
# Contrat : docs/supervision-conception.md (API des greffons Nagios) — une ligne, 0/1/2.
|
||||
# Mise en defaut PAR PARAMETRE : `serveur_dovecot_sonde_utilisateur` (compte inexistant).
|
||||
set -uo pipefail
|
||||
|
||||
UTILISATEUR={{ serveur_dovecot_sonde_utilisateur }}
|
||||
UTILISATEUR={{ serveur_dovecot_sonde_utilisateur | quote }}
|
||||
|
||||
[[ -n "${UTILISATEUR}" ]] || { echo "Aucun compte a chercher (serveur_dovecot_sonde_utilisateur vide)."; exit 3; }
|
||||
systemctl is-active --quiet dovecot || { echo "Dovecot n'est pas actif."; exit 2; }
|
||||
|
||||
# ON RESOUT UN UTILISATEUR REEL, PAS UN PING DE SERVICE. C'est le bind LDAP qui tombe le
|
||||
# plus souvent, et il ne se voit que quand on demande un compte : le demon reste debout,
|
||||
# le port reste ouvert, et toute authentification echoue.
|
||||
sortie=$(doveadm user "${UTILISATEUR}" 2>&1)
|
||||
if ! printf '%s' "${sortie}" | grep -qE '^uid[[:space:]]'; then
|
||||
echo "Dovecot ne resout pas « ${UTILISATEUR} » — l'annuaire ne repond plus : ${sortie:0:70}"
|
||||
# LE PASSDB, PAS LE USERDB (2026-10-12). La sonde demandait `doveadm user`, et le userdb est
|
||||
# `static` avec `static_allow_all_users` (requis par la remise LMTP) : il repond pour
|
||||
# N'IMPORTE QUEL nom. Elle disait « daniel » resolu chez deux locataires ou ce compte
|
||||
# n'existe pas — verte quoi qu'il arrive.
|
||||
#
|
||||
# `doveadm auth lookup` interroge le passdb : Dovecot cherche le compte dans l'annuaire,
|
||||
# sous SON compte de liaison, en LDAPS, avec le filtre de connexion. Aucune tentative de
|
||||
# mot de passe — rien ne compte pour le verrouillage du compte. Mesure : 0 s'il existe,
|
||||
# 67 s'il n'existe pas, une erreur si l'annuaire ne repond plus.
|
||||
sortie=$(doveadm auth lookup "${UTILISATEUR}" 2>&1)
|
||||
rc=$?
|
||||
if [[ ${rc} -eq 67 ]]; then
|
||||
echo "Dovecot ne trouve pas « ${UTILISATEUR} » dans l'annuaire : plus personne de ce nom ne peut se connecter."
|
||||
exit 2
|
||||
elif [[ ${rc} -ne 0 ]] || ! printf '%s' "${sortie}" | grep -q '^passdb: '; then
|
||||
echo "Dovecot n'interroge plus l'annuaire (rc=${rc}) : ${sortie:0:70}"
|
||||
exit 2
|
||||
fi
|
||||
echo "Dovecot : « ${UTILISATEUR} » resolu dans l'annuaire, service actif."
|
||||
echo "Dovecot : « ${UTILISATEUR} » trouve dans l'annuaire (passdb LDAP), service actif."
|
||||
|
|
|
|||
65
scripts/tests/test_dovecot_sonde_et_imap.py
Normal file
65
scripts/tests/test_dovecot_sonde_et_imap.py
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
#!/usr/bin/env python3
|
||||
"""La sonde « boites » cherche un vrai compte, et Dovecot n'ouvre plus l'IMAP en clair.
|
||||
|
||||
POURQUOI (2026-10-12).
|
||||
1. La sonde demandait `doveadm user`, que le userdb `static` (`static_allow_all_users`,
|
||||
requis par la remise LMTP) satisfait pour N'IMPORTE QUEL nom. Elle disait « daniel »
|
||||
resolu chez deux locataires ou ce compte n'existe pas.
|
||||
2. Le 143 (IMAP en clair) ecoutait par defaut a cote du 993.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import jinja2
|
||||
import yaml
|
||||
|
||||
RACINE = Path(__file__).resolve().parents[2]
|
||||
DOVECOT = RACINE / "roles/serveur_dovecot"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
echecs: list[str] = []
|
||||
|
||||
# 1. La sonde interroge le passdb, pas le userdb statique.
|
||||
sonde = (DOVECOT / "templates/sonde-boites.sh.j2").read_text(encoding="utf-8")
|
||||
code = "\n".join(l for l in sonde.splitlines() if not l.lstrip().startswith("#"))
|
||||
if "doveadm auth lookup" not in code:
|
||||
echecs.append("la sonde « boites » n'interroge pas le passdb (`doveadm auth lookup`)")
|
||||
if re.search(r"doveadm\s+user\b", code):
|
||||
echecs.append("la sonde « boites » interroge encore le userdb statique (`doveadm user`)")
|
||||
if "67" not in code:
|
||||
echecs.append("la sonde « boites » ne distingue pas le compte absent (rc 67)")
|
||||
|
||||
# 1b. Le compte cherche se derive du compte d'amorcage, il n'est pas ecrit.
|
||||
defauts = yaml.safe_load((DOVECOT / "defaults/main.yml").read_text(encoding="utf-8"))
|
||||
cible = str(defauts.get("serveur_dovecot_sonde_utilisateur", ""))
|
||||
if "amorcage_acces_courriel" not in cible:
|
||||
echecs.append(f"serveur_dovecot_sonde_utilisateur = {cible!r} : un nom ecrit, pas le compte d'amorcage")
|
||||
|
||||
# 2. Le 99-setops.conf rendu eteint l'ecoute IMAP en clair (port 0).
|
||||
rendu = jinja2.Environment().from_string(
|
||||
(DOVECOT / "templates/99-setops.conf.j2").read_text(encoding="utf-8")).render(
|
||||
serveur_dovecot_mail_path="/var/vmail/%{user}", serveur_dovecot_vmail_uid=5000,
|
||||
serveur_dovecot_vmail_gid=5000, serveur_dovecot_tls_dir="/etc/dovecot/tls",
|
||||
serveur_dovecot_ldap_uris="ldaps://idm-01.exemple.internal:636",
|
||||
serveur_dovecot_ldap_base="dc=exemple", serveur_dovecot_ldap_bind_dn="cn=dovecot",
|
||||
serveur_dovecot_ldap_bind_password="banc", serveur_dovecot_ldap_filter="(mail=%{user})",
|
||||
serveur_dovecot_vmail_utilisateur="vmail", serveur_dovecot_lmtp_reseau=True,
|
||||
serveur_dovecot_lmtp_port=24, serveur_dovecot_lmtp_tls=True,
|
||||
serveur_dovecot_postfix_present=False, serveur_dovecot_postfix_socket_dir="/x")
|
||||
bloc = re.search(r"service imap-login \{\s*inet_listener imap \{\s*port = (\d+)", rendu)
|
||||
if not bloc or bloc.group(1) != "0":
|
||||
echecs.append("99-setops.conf n'eteint pas l'ecoute IMAP en clair (`inet_listener imap { port = 0 }`)")
|
||||
|
||||
for e in echecs:
|
||||
print(f"ECHEC : {e}")
|
||||
if not echecs:
|
||||
print("OK : la sonde « boites » cherche le compte d'amorcage dans le passdb ; l'IMAP n'ecoute qu'en TLS (993).")
|
||||
return 1 if echecs else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Loading…
Reference in a new issue