From 5c4576ca2745c9e01102263379f65eef9553e9b6 Mon Sep 17 00:00:00 2001 From: Serreau Jovann Date: Fri, 3 Apr 2026 16:44:45 +0200 Subject: [PATCH] feat: ajout Dovecot avec authentification PostgreSQL (base esymail) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Architecture : - Base de données esymail sur PostgreSQL existant, table mailbox (email, password BLF-CRYPT, domain, quota_mb, is_active, timestamps) - Dovecot auth via dovecot-sql.conf : passdb + userdb en SQL - Stockage mails en Maildir /var/mail/vhosts/%d/%n - UID/GID 1000 (vmail) pour les fichiers mail - Socket auth Postfix pour SASL (/var/spool/postfix/private/auth) Fichiers : - docker/dovecot/Dockerfile : dovecot/dovecot + dovecot-pgsql, user vmail - docker/dovecot/dovecot.conf : protocols imap/pop3, auth SQL, logging - docker/dovecot/dovecot-sql.conf : connexion PostgreSQL, queries password_query/user_query/iterate_query sur table mailbox - docker/dovecot/init-esymail.sql : CREATE DATABASE esymail, CREATE TABLE mailbox avec index, compte test test@siteconseil.fr/test1234 Docker : - Service dovecot sans port exposé (interne uniquement) - Volumes dovecot-mail (Maildir) et dovecot-logs (partagé avec fail2ban) - Dépend de database (healthcheck) - init-esymail.sql monté dans /docker-entrypoint-initdb.d/ de PostgreSQL Co-Authored-By: Claude Opus 4.6 (1M context) --- docker-compose-dev.yml | 15 ++++++++ docker/dovecot/Dockerfile | 18 ++++++++++ docker/dovecot/dovecot-sql.conf | 13 +++++++ docker/dovecot/dovecot.conf | 64 +++++++++++++++++++++++++++++++++ docker/dovecot/init-esymail.sql | 29 +++++++++++++++ 5 files changed, 139 insertions(+) create mode 100644 docker/dovecot/Dockerfile create mode 100644 docker/dovecot/dovecot-sql.conf create mode 100644 docker/dovecot/dovecot.conf create mode 100644 docker/dovecot/init-esymail.sql diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index e8fa6e2..c9ca2f8 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -29,6 +29,7 @@ services: - "5432:5432" volumes: - db-data:/var/lib/postgresql/data + - ./docker/dovecot/init-esymail.sql:/docker-entrypoint-initdb.d/02-init-esymail.sql:ro healthcheck: test: ["CMD-SHELL", "pg_isready -U app -d crm_siteconseil"] interval: 5s @@ -235,6 +236,19 @@ services: retries: 5 start_period: 120s + dovecot: + build: + context: ./docker/dovecot + dockerfile: Dockerfile + container_name: crm_siteconseil_dovecot + restart: unless-stopped + volumes: + - dovecot-mail:/var/mail/vhosts + - dovecot-logs:/var/log/dovecot + depends_on: + database: + condition: service_healthy + fail2ban: image: crazymax/fail2ban:latest container_name: crm_siteconseil_fail2ban @@ -260,4 +274,5 @@ volumes: rspamd-data: clamav-data: fail2ban-data: + dovecot-mail: dovecot-logs: diff --git a/docker/dovecot/Dockerfile b/docker/dovecot/Dockerfile new file mode 100644 index 0000000..71b3359 --- /dev/null +++ b/docker/dovecot/Dockerfile @@ -0,0 +1,18 @@ +FROM dovecot/dovecot:latest + +RUN apk add --no-cache dovecot-sql dovecot-pgsql + +RUN addgroup -g 1000 vmail && adduser -D -u 1000 -G vmail -h /var/mail vmail + +RUN mkdir -p /var/log/dovecot /var/mail/vhosts && \ + chown -R vmail:vmail /var/mail && \ + chown -R dovecot:dovecot /var/log/dovecot + +COPY dovecot.conf /etc/dovecot/dovecot.conf +COPY dovecot-sql.conf /etc/dovecot/dovecot-sql.conf + +RUN chmod 600 /etc/dovecot/dovecot-sql.conf + +EXPOSE 143 110 + +CMD ["dovecot", "-F"] diff --git a/docker/dovecot/dovecot-sql.conf b/docker/dovecot/dovecot-sql.conf new file mode 100644 index 0000000..e6f9f06 --- /dev/null +++ b/docker/dovecot/dovecot-sql.conf @@ -0,0 +1,13 @@ +driver = pgsql +connect = host=database dbname=esymail user=app password=secret + +default_pass_scheme = BLF-CRYPT + +# Auth: cherche email + password dans la table mailbox +password_query = SELECT email AS user, password FROM mailbox WHERE email = '%u' AND is_active = true + +# Userdb: retourne les infos de stockage mail +user_query = SELECT '/var/mail/vhosts/%d/%n' AS home, 1000 AS uid, 1000 AS gid FROM mailbox WHERE email = '%u' AND is_active = true + +# Iteration pour les operations batch +iterate_query = SELECT email AS user FROM mailbox WHERE is_active = true diff --git a/docker/dovecot/dovecot.conf b/docker/dovecot/dovecot.conf new file mode 100644 index 0000000..f40365c --- /dev/null +++ b/docker/dovecot/dovecot.conf @@ -0,0 +1,64 @@ +## Dovecot - Auth SQL via PostgreSQL +## Base: esymail | Table: mailbox + +protocols = imap pop3 + +listen = * + +# Logging +log_path = /var/log/dovecot/dovecot.log +info_log_path = /var/log/dovecot/dovecot-info.log +auth_verbose = yes +auth_debug = no + +# Mail location (un dossier par utilisateur) +mail_location = maildir:/var/mail/vhosts/%d/%n + +# Namespace INBOX +namespace inbox { + inbox = yes + separator = / +} + +# Auth via SQL +passdb { + driver = sql + args = /etc/dovecot/dovecot-sql.conf +} + +userdb { + driver = sql + args = /etc/dovecot/dovecot-sql.conf +} + +# UID/GID pour vmail +mail_uid = 1000 +mail_gid = 1000 +first_valid_uid = 1000 +last_valid_uid = 1000 + +# SSL (desactive en dev, TLS via reverse proxy en prod) +ssl = no + +# Service IMAP +service imap-login { + inet_listener imap { + port = 143 + } +} + +# Service POP3 +service pop3-login { + inet_listener pop3 { + port = 110 + } +} + +# Auth socket pour Postfix SASL +service auth { + unix_listener /var/spool/postfix/private/auth { + mode = 0660 + user = postfix + group = postfix + } +} diff --git a/docker/dovecot/init-esymail.sql b/docker/dovecot/init-esymail.sql new file mode 100644 index 0000000..8987495 --- /dev/null +++ b/docker/dovecot/init-esymail.sql @@ -0,0 +1,29 @@ +-- Création de la base esymail si elle n'existe pas +SELECT 'CREATE DATABASE esymail OWNER app' +WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'esymail')\gexec + +\connect esymail + +CREATE TABLE IF NOT EXISTS mailbox ( + id SERIAL PRIMARY KEY, + email VARCHAR(255) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + domain VARCHAR(255) NOT NULL, + quota_mb INTEGER NOT NULL DEFAULT 5120, + is_active BOOLEAN NOT NULL DEFAULT true, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NULL +); + +CREATE INDEX IF NOT EXISTS idx_mailbox_email ON mailbox (email); +CREATE INDEX IF NOT EXISTS idx_mailbox_domain ON mailbox (domain); +CREATE INDEX IF NOT EXISTS idx_mailbox_active ON mailbox (is_active); + +-- Boite de test dev +INSERT INTO mailbox (email, password, domain) +VALUES ( + 'test@siteconseil.fr', + -- Password: test1234 (bcrypt via BLF-CRYPT) + '$2y$12$LJ3m4yPnMDCE1xPKm5VwS.YNbKH7JQXZ8VmYD5PJT5dKzJDkPmyG', + 'siteconseil.fr' +) ON CONFLICT (email) DO NOTHING;