feat: ajout Dovecot avec authentification PostgreSQL (base esymail)

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) <noreply@anthropic.com>
This commit is contained in:
Serreau Jovann
2026-04-03 16:44:45 +02:00
parent 1449981995
commit 5c4576ca27
5 changed files with 139 additions and 0 deletions

View File

@@ -29,6 +29,7 @@ services:
- "5432:5432" - "5432:5432"
volumes: volumes:
- db-data:/var/lib/postgresql/data - db-data:/var/lib/postgresql/data
- ./docker/dovecot/init-esymail.sql:/docker-entrypoint-initdb.d/02-init-esymail.sql:ro
healthcheck: healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d crm_siteconseil"] test: ["CMD-SHELL", "pg_isready -U app -d crm_siteconseil"]
interval: 5s interval: 5s
@@ -235,6 +236,19 @@ services:
retries: 5 retries: 5
start_period: 120s 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: fail2ban:
image: crazymax/fail2ban:latest image: crazymax/fail2ban:latest
container_name: crm_siteconseil_fail2ban container_name: crm_siteconseil_fail2ban
@@ -260,4 +274,5 @@ volumes:
rspamd-data: rspamd-data:
clamav-data: clamav-data:
fail2ban-data: fail2ban-data:
dovecot-mail:
dovecot-logs: dovecot-logs:

18
docker/dovecot/Dockerfile Normal file
View File

@@ -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"]

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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;