85.1.39. pepsi-setup

provision the Pepsi database, signing keys and DNS for a deployment

Section du manuel:

1

85.1.39.1.1. Nom

pepsi-setup - installer le schéma, valider la configuration, créer les clés DKIM et imprimer les enregistrements DNS.

85.1.39.1.2. Synopsis

pepsi-setup [GLOBAL-OPTIONS] [run [-r | –reset]]

pepsi-setup [GLOBAL-OPTIONS] schema [–backup-dir DIR] [–if-installed]

pepsi-setup [GLOBAL-OPTIONS] check

pepsi-setup [GLOBAL-OPTIONS] visualize

pepsi-setup –wizard [–force] [–expert SPEC] [–answers FILE] [–import MTA [–import-root DIR] | –no-import] [-c FILE]

pepsi-setup [GLOBAL-OPTIONS] questions

pepsi-setup [GLOBAL-OPTIONS] apply [–once] [–idle SECS]

pepsi-setup [GLOBAL-OPTIONS] apply –clear

pepsi-setup [GLOBAL-OPTIONS] bootstrap [–valid-for SECS] [–admin-url URL]

pepsi-setup [GLOBAL-OPTIONS] import [MTA] [–root DIR] [–out DIR] [–alias-style STYLE]

GLOBAL-OPTIONS inclut –no-certbot, –no-reverse-proxy et -y/-n pour répondre aux invites de manière non interactive (voir Options globales).

85.1.39.1.3. Description

pepsi-setup est l’outil de bootstrap administratif pour un déploiement Pepsi. En une seule invocation, il :

  1. Valide la configuration. Il analyse les sections [pepsi] et [pepsi-ingress] (y compris les listeners) et le pipeline [stage-*] — vérifiant qu’un [stage-init] existe, que chaque NEXT_STAGE/BOUNCE_STAGE se résout, et que la configuration de programme de chaque étape (y compris le routage de MTA amont) s’analyse — et vérifie que chaque nom de domaine est bien formé et que chaque entrée PUBLIC_IP est une adresse IP valide. Il avertit lorsque le PUBLIC_IP d’une étape de relais est une adresse non publique (loopback, RFC 1918 / unique-local, link-local ou CGNAT) — une telle adresse ne peut jamais envoyer de courrier sur Internet, de sorte qu’avec le -all final elle fait échouer SPF pour le courrier sortant de cette famille d’adresses (derrière un NAT, utilisez l’IP de sortie publique de l’hôte, pas son adresse LAN) — et lorsqu’une étape de relais ne définit aucun PUBLIC_IP du tout (l’enregistrement se dégrade en un simple v=spf1 -all). Il refuse aussi l’identité réservée .invalid de remplacement livrée dans l’exemple de configuration : si l’un de HOSTNAME, ACCEPTED_DOMAINS, [pepsi] ARC_DOMAIN ou un SERVER_NAME d’étape se termine encore en .invalid (par exemple example.invalid), il s’arrête avec une erreur nommant les valeurs fautives, car l’exemple n’a jamais été édité — plutôt que de provisionner des clés et du DNS pour un domaine qui ne pourra jamais recevoir de courrier. Réglez ces champs sur votre véritable domaine, ou exécutez pepsi-setup --wizard. Il avertit au sujet de chaque option définie dans une section connue que rien ne lit — une option mal orthographiée prend silencieusement sa valeur par défaut — en suggérant le nom d’option réel le plus proche, ainsi qu’au sujet des options renommées depuis une version antérieure (voir « Options renommées » dans pepsi.conf(5)). Les sections qu’il ne connaît pas sont laissées telles quelles. Si quoi que ce soit ne va pas, il rapporte le problème et sort avec un code non nul sans installer de schéma, générer de clé ni rien publier. (La validation n’est pas littéralement la première chose qui s’exécute : les étapes ci-dessous qui réparent le fichier de configuration — l’intégration au reverse-proxy, les chemins de certbot, le secret de preuve d’origine, l’identifiant de télémétrie et la sonde MAILBOX_FS_QUOTA — la précèdent nécessairement, puisque la validation s’exécute sur leur résultat.) Les fichiers de table de correspondance édités par l’opérateur (ci-dessous) ne font délibérément pas partie de cette étape, de sorte qu’une faute de frappe sans rapport dans une table ne peut jamais bloquer l’exécution. Si le pipeline utilise une étape pepsi-stage-detect-language mais que ce binaire n’est pas trouvé sur le $PATH (il est livré dans son propre gros paquet, séparé du reste de Pepsi), l’exécution avertit et continue — installez pepsi-stage-detect-language avant de démarrer le service, sinon l’étape detect-language échouera à se lancer. Deux autres avertissements non fatals signalent des erreurs de configuration probables : une étape pepsi-stage-block-language avec une BLACKLIST mais une WHITELIST vide et un THRESHOLD non négatif (qui génère un rebond pour tout courrier de langue neutre — voir pepsi-stage-block-language(1)), et un cycle NEXT_STAGE non progressif (une chaîne d’arêtes NEXT_STAGE qui boucle sur elle-même, de sorte qu’un message tournerait en rond jusqu’à MAX_LIFETIME ; une ré-entrée légitime via BOUNCE_STAGE/RESTART_STAGE n’est pas signalée). Il avertit aussi lorsqu’une entrée TARGETS admet un uid inférieur au UID_MIN de /etc/login.defs sur une étape dont le helper setuid refuse de tels uid — pepsi-stage-relay-to-maildir(1), pepsi-stage-dot-forward(1), et pepsi-stage-auto-pay(1) en WALLET_MODE = local-user — puisqu’un destinataire retenu par cette seule entrée échoue dans le helper à chaque message.

    Les réglages de la cryptographie de bout en bout sont validés ici également : les options CRYPTO_* de [pepsi] et la section de magasin de clés [pepsi-crypto] (voir pepsi.conf(5)). Des combinaisons individuellement valides mais conjointement fausses sont des erreurs — CRYPTO_SMIME_SHARED_KEY = yes avec un CRYPTO_SMIME_ALGORITHM à courbes elliptiques (une seule clé EC faisant à la fois ECDSA et ECDH est rejetée par plusieurs clients S/MIME), un CRYPTO_GENERATE_RSA_BITS inférieur à CRYPTO_MIN_RSA_BITS (le déploiement générerait des clés qu’il refuse ensuite), un CRYPTO_MIN_RSA_BITS invraisemblablement petit, un IDENTITY_VALIDITY_DAYS non positif, et un KEY_WRAP_KEY_ID qui ne pourrait pas nommer sa propre option KEY_WRAP_SECRET_<ID>. Une section [pepsi-crypto] qui dit quoi que ce soit mais n’a aucun KEY_WRAP_SECRET est de même une erreur, et le message nomme le fragment @inline-secret@ d’où le secret aurait dû venir, avec son propriétaire et son mode : le mécanisme @inline-secret@ ne fait qu”avertir pour un fragment qu’il ne peut pas lire, de sorte qu’un fragment illisible est sinon indiscernable d’une option non définie. Un secret de moins de 16 caractères est refusé — c’est l’unique clé qui ouvre chaque clé privée stockée. Un déploiement qui ne fait pas de cryptographie de bout en bout n’en configure rien et n’en est pas importuné.

    Pour chaque étape pepsi-stage-encrypt(1), il vérifie aussi le préréglage pEp (ENABLE_PEP) et le libre-service de clés. Il avertit lorsque ENABLE_PEP est activé mais que [pepsi-crypto] AUTO_CREATE_IDENTITY = no (un veto de site valide, mais alors aucun expéditeur ne reçoit de clé automatiquement ; écrivez ENABLE_PEP = no pour le rendre explicite) ou qu’aucun KEY_WRAP_SECRET n’est configuré (aucun expéditeur ne peut recevoir de clé) ; lorsque RESPONSE_STAGE n’est pas réglé (les utilisateurs ne sont pas prévenus lorsqu’une nouvelle clé est enregistrée pour leur adresse, et les commandes de clé par courriel adressées à pepsi-keys@<domain> sont désactivées) ; et lorsque [pepsi-ingress] USERNAME_MAP accorde un joker, en nommant ces comptes : ils peuvent envoyer en tant que n’importe quelle adresse correspondante mais ne peuvent jamais enregistrer leur propre clé pour l’une d’elles. Il refuse un RESPONSE_STAGE ne nommant aucune étape et, lorsque RESPONSE_STAGE est réglé, un TEMPLATE_DIR dépourvu du modèle de repli key-registered.en.body ou keys.en.body.

    La configuration initiale répare aussi un fragment orphelin. Le secret et la directive @inline-secret@ qui le tire sont deux moitiés d’une même affirmation vivant dans deux fichiers, et le fragment survit à la configuration qui le référençait — de sorte qu’une pepsi.conf régénérée ou écrite à la main laisse un déploiement où secrets.d/pepsi-crypto.secret est présent et correct, où rien ne le charge, et où chaque message passant par pepsi-stage-encrypt(1) échoue. Aucune des deux moitiés ne paraît fausse isolément, et réexécuter en tant que root n’aide pas : root peut lire le fragment, mais c’est la directive qui le tire. Lorsque le fragment existe et que la configuration ne le référence pas, la configuration initiale ajoute la directive et le dit — cela ne renouvelle rien et n’écrit aucun matériel de clé, c’est donc sûr même lorsque le secret lui-même est illisible. Ce qu’il ne peut pas récupérer est un KEY_WRAP_KEY_ID non standard qui accompagnait le fichier perdu : l’avertissement nomme donc l’identifiant qu’il a affirmé ; réglez-le à la main si la clé a été stockée sous un autre.

  2. Prépare les fichiers de table de correspondance édités par l’opérateur. À chaque exécution, il parcourt les tables de recherche configurées — le [pepsi-ingress] USERNAME_MAP et la table ALIASES de chaque étape pepsi-stage-aliases. Un fichier qui n’existe pas est créé à partir d’un modèle de démarrage commenté (mode 0644) qui documente le but du fichier et montre la syntaxe, de sorte que l’opérateur trouve un fichier auto-documenté au bon endroit. Un fichier qui existe est vérifié syntaxiquement avec le même analyseur que le composant propriétaire utilise à l’exécution, de sorte qu’une ligne malformée (ou une faute de frappe d’adresse/de domaine) est révélée avec son numéro de ligne. Cette étape est purement indicative : une mauvaise table, ou un modèle qui ne peut être écrit, est journalisé comme un avertissement et n’abandonne jamais l’exécution (les analyseurs à l’exécution sont eux-mêmes indulgents, avertissant et sautant une mauvaise ligne). En mode --wizard, ceci ne s’exécute qu”après que pepsi.conf a été écrit, de sorte qu’un problème de table n’empêche jamais l’assistant d’écrire la configuration.

  3. S’intègre à un serveur HTTP frontal existant. Si un autre serveur web (nginx ou Apache) écoute déjà sur les ports 80/443, pepsi-httpd ne peut lier le 443 et certbot --standalone ne peut lier le 80. Plutôt que de se battre pour les ports, pepsi-setup détecte le serveur frontal (via ss), confirme qu’il ne sert pas déjà l’un des noms d’hôte mta-sts.<domain>, puis (a) garde [pepsi-httpd-listener-https] activé par socket mais en HTTP en clair (SERVE = systemd, MODE = plain), en supprimant tout MODE = tls + TLS_CERT/TLS_KEY à liaison directe et les sections [pepsi-httpd-cert-*] désormais inutiles, et dépose une redéfinition pepsi-httpd.socket (/etc/systemd/system/pepsi-httpd.socket.d/10-reverse-proxy.conf) qui relie ListenStream du 443 à une socket UNIX à /run/pepsi/httpd.sock (SocketGroup= le groupe du serveur frontal : www-data sur Debian, nginx/apache/httpd ailleurs), et (b) écrit un site reverse-proxy (pepsi-mta-sts) dans le sites-available du serveur frontal (l’activant dans sites-enabled ; conf.d pour nginx non-Debian) qui termine le TLS pour ces noms d’hôte et les transfère à la socket. Le serveur frontal termine désormais le TLS, de sorte que les certificats (cette étape et le certificat MX SMTP de l’étape suivante) sont obtenus via le plugin authentificateur certbot de ce serveur (--nginx/--apache) plutôt que --standalone. Cette étape est sans effet lorsqu’aucun serveur frontal n’est présent, et est entièrement sautée avec --no-reverse-proxy (auquel cas pepsi-httpd lie le 443 directement). Elle est idempotente : une fois le site pepsi-mta-sts existant, les réexécutions ne font que réaffirmer le listener de socket. L’intégration est différée (avec un message actionnable dans le résumé de fin d’exécution, laissant pepsi-httpd dans sa configuration à liaison directe) plutôt qu’abandonnée lorsque le serveur frontal sert déjà l’un des noms mta-sts.<domain> (un conflit que vous devez résoudre), lorsqu’il s’agit d’un serveur que pepsi-setup ne peut pas encore automatiser (Caddy, lighttpd, Apache Traffic Server — câblez le proxy à la main ou passez --no-reverse-proxy), lorsqu’il ne s’exécute pas en tant que root, ou lorsque le certificat MTA-STS lui-même ne peut être obtenu (le site reverse-proxy référence le certificat en service, il ne peut donc être écrit tant que le certificat n’existe pas) — dans chaque cas, réexécutez pepsi-setup run une fois le problème résolu. Le service pepsi-httpd doit fournir le répertoire de socket (l’unité systemd livrée le fait avec RuntimeDirectory=pepsi) et l’utilisateur du serveur frontal doit pouvoir atteindre la socket partagée par groupe.

  4. Provisionne les certificats TLS. Pour chaque listener qui termine le TLS — les sockets [pepsi-ingress-listener-*] avec MODE = tls/starttls, les sockets [pepsi-httpd-listener-*] TLS, et chaque certificat SNI [pepsi-httpd-cert-*] — qui n’a aucun TLS_CERT/TLS_KEY réglé, il remplit les options avec la disposition certbot standard (/etc/letsencrypt/live/<host>/fullchain.pem et privkey.pem), les écrivant dans le fichier de configuration sur place (les commentaires sont préservés). Le <host> est le [pepsi-ingress] HOSTNAME pour les listeners SMTP/HTTP et le premier hôte SNI de la section de certificat pour [pepsi-httpd-cert-*] ; les listeners partageant un hôte partagent un certificat.

    Le certificat du listener HTTPS couvre en outre openpgpkey.<domain> pour chaque domaine de ACCEPTED_DOMAINS, parce que c’est là que la méthode avancée du Web Key Directory est récupérée (pepsi-httpd(1)), et – lorsque [pepsi-autoconfig] active l’autoconfiguration du courrier – autoconfig.<domain> également, qui est la première URL qu’un client de messagerie essaie. Ils sont ajoutés au certificat propre du listener plutôt que dotés d’une section [pepsi-httpd-cert-*] à eux, puisque ce certificat est le recours servi dès que SNI ne correspond à rien – un seul certificat couvre ainsi le cas SNI et le recours.

    Le certificat des listeners d’ingress couvre chaque hôte MX qu’une politique MTA-STS autorise – l’ensemble [pepsi] MTA_STS_MX sur tous les domaines servis –, pour une raison qui n’a pas d’équivalent côté HTTPS. La RFC 8461 §4.1 exige que le certificat présenté par un MX corresponde au nom que l”enregistrement MX a donné à l’expéditeur, et pepsi-ingress(1) sert exactement un certificat par listener : il consigne le SNI du client mais ne sélectionne jamais dessus. Un hôte joignable comme mx.example.org et mail.example.net ne peut donc pas répondre avec deux certificats ; il lui faut un certificat portant les deux noms. Les entrées mx à joker (*.example.net) sont sautées avec un avertissement – HTTP-01 ne peut pas satisfaire un joker, et en passer un à certbot ferait échouer toute la requête, y compris les noms qui auraient fonctionné –, un déploiement qui en publie un doit donc fournir ce certificat lui-même.

    Ensuite, pour chacun de ces hôtes, pepsi-setup exécute certbot certonly --standalone (HTTP-01 sur TCP/80, enregistré sans adresse électronique) lorsque le certificat n’est pas sur le disque — et, lorsqu’il y est, il en lit les Subject Alternative Names et relance certbot (--expand, même --cert-name) s’ils ne couvrent pas tous les noms ci-dessus. Cela importe parce que l’ensemble des noms d’un certificat n’est pas figé à la première délivrance : ajouter un domaine servi, activer l’autoconfiguration ou nommer un nouvel hôte MX l’élargissent tous. Un certificat dont les noms ne peuvent pas être lus est laissé tel quel plutôt que réémis, car « je n’ai pas pu savoir » ne doit pas dépenser une délivrance soumise à quota. Chaque nom est vérifié dans le DNS, et pas seulement le --cert-name du certificat : certbot valide chaque -d séparément et fait échouer toute la requête si l’un d’eux est injoignable.

    Les certificats pour lesquels pepsi-setup peut piloter certbot sont déterminés en comparant les TLS_CERT/TLS_KEY configurés aux chemins de disposition qu’il aurait lui-même écrits pour cet hôte. Ce ne peut pas être « TLS_CERT est-il posé ? », car une première exécution réussie le pose – après quoi chaque listener paraîtrait configuré par l’exploitant et aucun certificat ne pourrait plus jamais être élargi. Les chemins égaux à la disposition sont ceux que pepsi-setup doit tenir à jour ; tout le reste est un certificat que l’exploitant a choisi et n’est jamais touché, et une section à moitié configurée (une seule des deux options posée) est laissée à la validation stricte, qui la signale mieux. Un certificat manquant et impossible à obtenir est différé plutôt que fatal : l’installation continue (pose le schéma, produit les clés, affiche les enregistrements DNS) et énumère chaque certificat différé dans un récapitulatif tout à la fin, avec l’étape suivante concrète et un rappel de relancer pepsi-setup run une fois l’obstacle levé. Relancer est idempotent et n’achève que ce qui manque encore. Un certificat est différé lorsque --no-certbot a été passé, que certbot n’est pas installé, que pepsi-setup ne tourne pas en root (il ne peut donc pas lier TCP/80), que le DNS de l’hôte ne résout pas vers une PUBLIC_IP configurée (le défi échouerait) ou que certbot lui-même échoue (par exemple un incident passager du serveur ACME ou un quota – une simple relance réessaie alors). Le cas DNS distingue un nom sans enregistrement A/AAAA d’un nom qui résout ailleurs (en rapportant les adresses trouvées face aux PUBLIC_IP configurées). Le fichier de certificat étant alors absent, l’enregistrement DANE/TLSA de cet hôte ne peut pas être calculé : la sortie DNS affiche donc un commentaire disant que l’enregistrement manque encore et comment le provisionner. Seule une invocation réellement inutilisable est fatale : le fichier de configuration non inscriptible, ou – lorsqu’aucun fichier de configuration n’existe à un emplacement par défaut et qu’aucun --config n’a été donné – son chemin inconnu, de sorte que les chemins remplis automatiquement ne peuvent pas être conservés. Notez qu’un serveur dont le listener TLS n’a toujours pas de certificat ne démarrera pas tant que le certificat n’existe pas.

    Donne aux serveurs accès à leur matériel de clés. Les serveurs TLS (pepsi-ingress, pepsi-httpd) s’exécutent sous des utilisateurs non privilégiés et ne peuvent pas lire l’arborescence /etc/letsencrypt/{live,archive}, réservée à root par certbot ; un listener échouerait sinon à démarrer sur un simple permission denied. Trois mécanismes y répondent, appliqués à chaque exécution :

    Credentials de service systemd — le mécanisme principal partout où systemd exécute les serveurs. Pour chaque serveur, pepsi-setup écrit /etc/systemd/system/<unit>.d/10-tls-credentials.conf en y nommant, sous forme d’entrées LoadCredential=, chaque certificat et chaque clé que ce serveur lit. systemd ouvre alors ces fichiers en tant que root au démarrage de l’unité et transmet au service des copies privées sous $CREDENTIALS_DIRECTORY (un répertoire tmpfs propre à l’unité, lisible par ce seul service), où le chargeur TLS de Pepsi recherche chaque chemin configuré avant de se rabattre sur la lecture du chemin lui-même — les permissions des fichiers cessent ainsi complètement d’importer. Le drop-in est régénéré à partir de la configuration à chaque exécution et n’énumère que des fichiers qui existent, car systemd refuse de démarrer une unité dont la source de credential est absente ; relancez pepsi-setup run après avoir ajouté, déplacé ou supprimé un certificat. Lorsque le drop-in change, pepsi-setup recharge systemd et applique try-restart au serveur concerné (uniquement s’il est en cours d’exécution) afin que le nouveau matériel prenne effet.

    ACLs POSIX et hook de déploiement certbot — la solution de repli pour un déploiement que systemd n’exécute pas, et le véhicule des renouvellements. Pour chaque certificat géré par certbot auquel la configuration fait référence, pepsi-setup accorde à ces utilisateurs de service les droits de lecture et de traversée au moyen d’ACLs POSIX (setfacl), et installe un hook de déploiement certbot dans /etc/letsencrypt/renewal-hooks/deploy/pepsi-cert-access.sh qui réapplique l’autorisation à chaque émission ou renouvellement futur (certbot fait tourner archive/<host>/privkeyN.pem, une autorisation ponctuelle n’y survivrait donc pas ; les ACLs par défaut du hook font en sorte que les fichiers nouvellement créés héritent eux aussi de l’accès) et redémarre les serveurs Pepsi, ce qui est précisément ce qui met un certificat renouvelé en service — chaque serveur lit son matériel une seule fois au démarrage, et systemd matérialise les identifiants au démarrage de l’unité. Cette moitié est au mieux : elle exige root, setfacl (le paquet acl) et un système de fichiers gérant les ACLs — un prérequis manquant est différé avec le correctif, sans être fatal. Les certificats hors de l’arborescence de certbot (TLS_CERT/TLS_KEY définis par l’opérateur) ne sont pas gérés et font seulement l’objet d’un avertissement ; accordez vous-même leurs droits de lecture.

    Vérification — pour tout ce qu’aucun identifiant ne couvre, pepsi-setup ne présume pas que l’autorisation a fonctionné. Il fait un fork, devient l’utilisateur de service et tente d’ouvrir le fichier : un certificat toujours illisible (une ACL silencieusement ignorée par le système de fichiers, un répertoire parent réservé à root, un chemin défini par l’opérateur pour lequel personne n’a accordé de droits) est ainsi signalé comme étape différée nommant le fichier, l’utilisateur et l’unité exacts — au lieu de se manifester plus tard sous la forme d’un serveur qui refuse de démarrer.

    Assure la joignabilité de Dovecot. Lorsque la remise locale via LMTP ou l’authentification de soumission via Dovecot SASL est configurée, pepsi-setup sonde la socket configurée en tant qu’utilisateur du service qui s’y connectera — le pepsi du dispatcher pour LMTP, pepsi-ingress pour SASL. Ceci capture le cas courant d’une socket qui existe mais est injoignable : la socket partagée /run/dovecot/auth-client de Dovecot est en 0600 dovecot et ne peut être ouverte par pepsi-ingress. Si une socket est absente ou illisible et qu’un Dovecot local est présent, pepsi-setup (exécuté interactivement en tant que root) propose d’installer /etc/dovecot/conf.d/10-pepsi.conf — un unix_listener dédié possédé par l’utilisateur du service Pepsi (mode = 0600, de sorte que les permissions de la socket partagée sont laissées intactes) — puis valide la configuration fusionnée avec doveconf et recharge Dovecot. Lorsqu’il ne le peut pas (pas root, pas de terminal où demander, doveconf rejetant le résultat, ou pas de Dovecot local), il imprime le drop-in et le diffère afin que vous puissiez le déployer à la main. Le SASL_PATH par défaut de Pepsi est cette socket privée, /run/dovecot/auth-client-pepsi. Cette vérification s’exécute à chaque pepsi-setup run, pas seulement sous --wizard. -y/--yes-to-all installe le drop-in sans demander ; -n/--no-to-all saute l’installation et se contente de l’imprimer — l’un ou l’autre rend l’exécution entièrement non interactive.

  5. Vérifie la présence d’un autre MTA sur les ports SMTP. Pepsi est rarement le premier serveur de courrier sur un hôte — Debian tire Postfix ou Exim comme mail-transport-agent par défaut. Si l’un d’eux écoute encore, pepsi-ingress ne peut pas lier le port et l’échec est silencieux d’une manière particulièrement trompeuse : sous activation par socket, c’est pepsi-ingress.socket qui échoue (systemctl status pepsi-ingress.socket rapporte Result: resources), le courrier continue d’être remis par l”autre serveur en utilisant ses tables d’alias et virtuelles, et chaque changement de pepsi.conf semble donc n’avoir absolument aucun effet. pepsi-setup lit la table des listeners du noyau (/proc/net/tcp) pour chaque port dont les sections [pepsi-ingress-listener-*] ont besoin — le PORT configuré pour un listener SERVE = tcp, et la correspondance FD_INDEX de l’unité socket livrée (0 → 25, 1 → 465, 2 → 587) pour un listener SERVE = systemd — et nomme le processus fautif (avec son pid) lorsque le détenteur n’est ni systemd ni Pepsi lui-même. Ceci est indicatif : c’est différé vers le résumé de fin d’exécution plutôt que fatal, puisqu’un opérateur en pleine migration peut délibérément vouloir garder l’ancien MTA en service un moment de plus. Résolvez-le en arrêtant et en désactivant l’autre serveur (systemctl disable --now postfix) puis en démarrant pepsi-ingress.socket.

    Vérifie que le résolveur valide DNSSEC. Dans la même phase, et uniquement lorsque la configuration réclame DANE, pepsi-setup pose au résolveur du système une question dont il connaît la réponse et cherche le bit AD. Pepsi fait confiance à un résolveur validant plutôt que de valider DNSSEC lui-même : un résolveur qui ne positionne jamais AD fait donc que DANE et la découverte de clés fondée sur DANE ne trouvent absolument rien, silencieusement. Différé lui aussi, et non fatal.

  6. Installe le schéma de base de données. Chaque composant Pepsi partage une base de données (la section [pepsi-postgres]) et l’unique schéma pepsi ; pepsi-setup est le seul installateur. Le schéma est une unique série de patchs (pepsi-NNNN.sql) avec un procedures.sql, appliquée depuis [pepsi-postgres] SQL_DIR (par défaut ${DATADIR}/sql). L’opération est idempotente et peut être relancée après des mises à niveau. Les autres composants n’ont pas de commande d’initialisation de schéma propre.

    Provisionne les rôles de connexion à la base. Chaque compte de service Pepsi reçoit un rôle de connexion PostgreSQL du même nom, authentifié sur la socket locale par authentification de pair, et les droits dont il a besoin sur le schéma pepsi. Le rôle du pipeline pepsi (ainsi que pepsi-crypto) reçoit le schéma entier, moins les restrictions ci-dessous ; les trois services qui ne sont pas le pipeline sont ensuite ramenés à ce qu’un audit de leur code indique qu’ils utilisent, en toute dernière étape, et la configuration refuse de se terminer si PostgreSQL n’est pas d’accord :

    pepsi-ingress

    INSERT dans pepsi.workqueue et pepsi.workqueue_body (plus SELECT des identifiants que workqueue_add renvoie et des deux colonnes de taille générées, header_octets et octets, que le contrôle d’admission de la file d’attente additionne), SELECT sur config_override et mailbox_quota, INSERT sur les deux journaux en ajout seul. Il ne peut lire aucun message déjà en file d’attente.

    pepsi-telemetry

    Sa propre table pepsi.telemetry.

    pepsi-httpd

    Tout ce qu’utilisent la console, le portail, le site des listes et les archives, mais sur pepsi.workqueue seulement les colonnes d’enveloppe, state et de routage — jamais headers ni le corps, et il ne peut pas faire pointer le body_id d’un message ailleurs ; sur pepsi.workqueue_body seulement INSERT (les formulaires web envoient du courrier) et DELETE (supprimer un message supprime le corps qu’il était seul à porter ; la clé étrangère refuse un corps qu’un autre message référence encore), jamais SELECT du corps — et rien du tout sur settings, whitelist, vacation_reply, payment_request_reply, les tables du secrétaire, telemetry, origin_nonce ni auto_pay_spend ; les tables de statistiques, de quotas, de DNS et de rapports TLS ainsi que la ligne de vie du démon de télémétrie (telemetry_client) en lecture seule.

    Les privilèges par défaut pour les tables qu’un patch ultérieur crée sont également révoqués pour pepsi-ingress et pepsi-telemetry. Voir le « Modèle de sécurité » du manuel pour le raisonnement et sa « Suite de tests » pour la manière dont la frontière est testée.

    Trois autres rôles sont délibérément plus étroits encore :

    pepsi-whitelist

    Se voit accorder SELECT, INSERT et DELETE sur pepsi.whitelist et rien d’autre, de sorte qu’un programme que tout utilisateur local peut exécuter (pepsi-whitelist(1), installé setuid) ne puisse atteindre ni la file des messages, ni les statistiques, ni les clés de signature.

    pepsi-keydisc

    Les services de découverte de clés (pepsi-keydisc(1)), qui analysent du matériel de clé récupéré sur l’internet ouvert et sont donc le processus le plus susceptible d’être compromis ici. C’est le rôle le plus étroit du déploiement : SELECT/INSERT/UPDATE/DELETE sur pepsi.peer_key (remplir ce cache est tout son travail, et la règle de conflit doit pouvoir remplacer un enregistrement), SELECT/INSERT/UPDATE sur pepsi.key_request, EXECUTE sur les fonctions keydisc_* et les deux assistants crypto_* qu’elles appellent — et, sur pepsi.workqueue, SELECT plus un UPDATE (status, timeout) au niveau colonne.

    Ces deux colonnes sont la partie intéressante. Elles suffisent exactement à faire passer un message garé sur une adresse de paused à pending, ce que font keydisc_resolve et keydisc_sweep (de simples fonctions SECURITY INVOKER, la portée doit donc être accordée explicitement plutôt qu’héritée). Avec un droit au niveau table, ce compte pourrait réécrire les destinataires d’un message ou son corps ; avec ces deux colonnes, il ne peut que réveiller un message. Il n’a aucun accès à pepsi.crypto_identity.

    pepsi-crypto

    Le compte qui a la garde du matériel de clé privée de bout en bout. Il reçoit les droits ordinaires du pipeline à travers le schéma plus la colonne pepsi.crypto_identity.private_wrapped — et c’est le seul rôle à la recevoir.

    Cette dernière colonne est tout l’objet de l’exercice. Immédiatement après les droits globaux, pepsi-setup révoque le SELECT/INSERT/UPDATE au niveau table sur pepsi.crypto_identity à chaque rôle de service ordinaire — pepsi (le dispatcher et chaque worker d’étape), pepsi-ingress, pepsi-httpd et pepsi-telemetry — et réaccorde les trois mêmes colonne par colonne pour chaque colonne sauf private_wrapped. Ainsi ces rôles peuvent lire, écrire et publier la moitié publique d’une identité, et la compromission d’une étape sans rapport ne donne rien de plus, même avec un accès complet à la base sous ce rôle. (DELETE n’a pas de granularité par colonne dans PostgreSQL et reste entier : une étape capable de supprimer une ligne d’identité peut refuser le service, mais elle ne peut toujours pas lire une clé.) La liste des colonnes est relue depuis information_schema plutôt que codée en dur, de sorte qu’une colonne ajoutée plus tard soit couverte automatiquement et que le droit ne puisse pas devenir périmé face au schéma. Comme tout le reste de cette étape, c’est idempotent et délibérément réappliqué à chaque exécution, car les droits globaux réajoutent le privilège de niveau table à chaque fois.

    La colonne enveloppée n’est que la moitié du modèle de garde ; la clé qui l’ouvre réside dans un fragment secrets.d (ci-dessous). Notez également que l’authentification par pair de PostgreSQL se fonde sur l”uid effectif, non sur le gid — de sorte qu’un programme qui doit être ce rôle doit être ce compte, ce que pepsi-keys(1) arrange autour de chaque appel à la base. Voir pepsi-keys(1).

  7. Génère les clés de signature. Pour chaque domaine pour lequel Pepsi fait autorité — l’union de [pepsi-ingress] ACCEPTED_DOMAINS, des domaines des SERVER_NAME / POSTMASTER de chaque étape, d’une SIGNING_DOMAIN explicite, du [pepsi-srs] SRS_DOMAIN d’une étape SRS, du RESPONSE_FROM fixe d’une étape de libre-service, et du [pepsi] ARC_DOMAIN s’il est réglé — il crée une clé privée DKIM RSA-2048 et une Ed25519 sous KEY_DIR si elles n’existent pas déjà. Les clés existantes ne sont jamais écrasées, et les fichiers de clé sont créés en mode 0600 dans des répertoires par domaine créés en mode 0700.

  8. Affiche les enregistrements DNS. Il écrit sur la sortie standard, au format de fichier de zone BIND, les enregistrements à publier pour chaque domaine : les clés publiques DKIM (<selector>._domainkey pour RSA et <selector>-ed25519._domainkey pour Ed25519), une politique SPF (v=spf1 …) bâtie à partir des adresses PUBLIC_IP de chaque étape de relais (réunies ; voir plus bas), une politique DMARC (_dmarc.<domain> TXT — voir plus bas) et un enregistrement MTA-STS (_mta-sts.<domain> TXT, sauf si MTA_STS_MODE = none) pour que les autres expéditeurs emploient un TLS validé pour notre courrier entrant. Le fichier de politique MTA-STS lui-même est servi en HTTPS par pepsi-httpd(1) à https://mta-sts.<domain>/.well-known/mta-sts.txt (son ensemble mx vient de [pepsi] MTA_STS_MX, avec repli sur le [pepsi-ingress] HOSTNAME) : pepsi-setup n’affiche donc que l’enregistrement TXT _mta-sts — dont l”id dérive de la politique propre à ce domaine, si bien que deux domaines aux ensembles mx différents annoncent des id différents — ainsi qu’un rappel de faire pointer mta-sts.<domain> vers le serveur. Contrairement à tous les autres enregistrements ici, celui de MTA-STS n’est affiché que pour les [pepsi-ingress] ACCEPTED_DOMAINS. DKIM, SPF et DMARC authentifient une identité sous laquelle cette machine envoie : ils couvrent donc l’ensemble plus large ci-dessus ; MTA-STS est une promesse sur le courrier entrant, et seul un domaine accepté en a une qui soit tenue — pepsi-httpd(1) ne répond à /.well-known/mta-sts.txt que pour ces hôtes, et le certificat (ainsi que tout site de serveur frontal) n’est obtenu que pour ces noms. Un domaine qui n’est qu’une identité d’envoi — un SERVER_NAME de relais, une SIGNING_DOMAIN explicite, le [pepsi-srs] SRS_DOMAIN — mais qui possède malgré tout un enregistrement TXT _mta-sts publié reçoit à la place un commentaire ; DELETE: : ce qu’il annonce est une politique qu’aucun composant ne sert, la requête retombe donc sur ce qui répond par ailleurs à cette adresse. Lorsque [pepsi-tlsrpt] RUA est posé, un enregistrement TLSRPT est également affiché (_smtp._tls.<domain> TXT, v=TLSRPTv1; rua=…), afin que les autres expéditeurs rapportent à l’adresse annoncée leurs résultats TLS vers nos domaines (voir pepsi-tlsrpt(1)). Les longs enregistrements RSA sont automatiquement découpés en plusieurs chaînes de caractères. Enfin, pour chaque listener d’ingress terminant TLS, il suggère un enregistrement DANE/TLSA (RFC 7672) sous le nom d’hôte MX (_<port>._tcp.<HOSTNAME> IN TLSA 3 1 1 <hash>), où la donnée d’association est le SHA-256 du SubjectPublicKeyInfo du certificat servi — la forme 3 1 1 (DANE-EE / SPKI / SHA-256), pour que l’enregistrement survive aux renouvellements de certificat qui réutilisent la paire de clés. Ne les publiez que dans une zone signée par DNSSEC ; l’enregistrement du port 25 est celui qui authentifie la remise entre MTA (les ports de soumission sont signalés par une note, puisque les expéditeurs y utilisent le certificat PKIX). Par souci d’exhaustivité, des enregistrements TLSA 3 1 1 équivalents sont aussi affichés pour les listeners HTTPS de pepsi-httpd(1) (par hôte SNI, plus le certificat de repli sous HOSTNAME), mais ils sont marqués OPTIONAL : les clients HTTPS (navigateurs, récupérateurs de politiques MTA-STS) ne font pas de validation DANE, ces enregistrements peuvent donc être omis sans risque et ne sont fournis que pour un administrateur qui souhaite épingler le certificat web.

    Deux autres blocs concernent la publication des clés de bout en bout (voir pepsi-keys(1)). D’abord, pour chaque domaine servi dont l’hôte openpgpkey.<domain> ne se résout pas déjà, un rappel d’y publier un enregistrement A/AAAA : la méthode advanced du Web Key Directory — celle que tout client actuel essaie en premier — est récupérée depuis ce nom, il doit donc pointer vers l’hôte pepsi-httpd(1). Un domaine qui publie déjà des clés est signalé dans la sortie et fait l’objet d’un avertissement dans le journal, car pour lui la consultation avancée échoue aujourd’hui plutôt que d’être simplement non configurée. (La méthode direct sur l’apex fonctionne sans lui : c’est donc une dégradation, non une panne.) Ensuite, un enregistrement OPENPGPKEY (RFC 7929) ou SMIMEA (RFC 8162) pour chaque identité publiée et active du magasin de clés, sous le nom de propriétaire haché de la RFC (SHA-256 de la partie locale tronqué à 28 octets, en hexadécimal — non l’empreinte du Web Key Directory, qui est un SHA-1 en z-base-32 de la partie locale mise en minuscules). Ceux-ci ne sont pas comparés au DNS en service : chacun fait quelques centaines d’octets et son nom de propriétaire est une empreinte par identité, de sorte que sonder coûterait une requête par utilisateur pour des enregistrements que l’opérateur colle en bloc. Le listage s’arrête après vingt identités ; utilisez pepsi-keys dns <address> pour une identité précise. Ne publiez les enregistrements de clés que dans une zone signée par DNSSEC — dans une zone non signée, c’est une clé fournie par quiconque peut répondre pour la zone.

    Un dernier bloc concerne l”autoconfiguration du courrier. Lorsque [pepsi-autoconfig] est activée, l’hôte autoconfig.<domain> de chaque domaine servi qui ne se résout pas déjà est listé avec un rappel invitant à le faire pointer vers l’hôte pepsi-httpd(1). Contrairement au rappel du Web Key Directory ci-dessus, il s’agit ici de quelque chose de proche d’un échec total plutôt que d’une dégradation : un client essaie https://autoconfig.<domain>/mail/config-v1.1.xml en premier, et la forme /.well-known/ sur l’apex — qui n’exige aucun nouvel enregistrement — est facultative dans le brouillon, de sorte qu’un client qui ne l’essaie pas ne trouve tout simplement rien.

    L’enregistrement SPF est construit à partir de PUBLIC_IP, qui est une option par étape lue depuis chaque étape de relais — détectée par son PROGRAM (pepsi-stage-relay-to-internet(1) ou pepsi-stage-relay-to-smarthost(1)), jamais par le nom de section — puis unifiée. Un déploiement peut donc avoir plusieurs étapes de relais, chacune avec son propre PUBLIC_IP (une étape pepsi-stage-relay-to-internet liste les IP d’envoi propres à cet hôte ; une étape pepsi-stage-relay-to-smarthost liste les IP de sortie du smarthost, puisque le courrier quitte internet depuis là). Si une étape de relais existe mais qu’aucune ne définit PUBLIC_IP, l’enregistrement est un simple v=spf1 -all — qui interdit à tous les hôtes d’envoyer au nom de vos domaines et fait échouer SPF pour votre propre courrier sortant — et pepsi-setup journalise un avertissement ; un déploiement en réception seule (sans étape de relais) n’est pas averti. L’enregistrement unifié est suggéré à l’identique pour chaque domaine servi : correct, mais large. Dans des configurations particulières où différents domaines envoient depuis différents hôtes, un enregistrement SPF par domaine écrit à la main ne listant que les IP d’envoi propres à ce domaine est plus strict tout en restant correct ; pepsi-setup ne calcule pas ce regroupement automatiquement et imprime une note en ce sens à côté des enregistrements suggérés.

    Un cas SPF est signalé nommément dans la sortie de zone plutôt que simplement suggéré : un domaine qui publie déjà deux enregistrements v=spf1. La RFC 7208 §4.5 en fait une erreur permanente — aucune politique SPF ne s’applique, plutôt que le premier enregistrement l’emportant — et le remède est de les fusionner : l’enregistrement suggéré est donc imprimé avec une note indiquant que l’ajouter sans retirer les autres ne change rien. Un v=spf1 include:<old provider> résiduel est ce qu’une migration de MTA laisse le plus souvent derrière elle.

    L’enregistrement DMARC est celui que pepsi-setup ne peut pas dériver : quelle politique un domaine souhaite est une décision d’opérateur, non une conséquence de la configuration. Ce bloc porte donc sur le fait que l’enregistrement soit utilisable, et il se comporte différemment selon ce qui est déjà publié :

    • Rien de publié. v=DMARC1; p=none est suggéré, avec un commentaire expliquant qu’il ne fait que surveiller et devrait être resserré en quarantine ou reject, et que rua=mailto:<address> est ce qui fait arriver les rapports qui vous diront si resserrer est sûr. (Pepsi ne traite pas les rapports agrégés DMARC entrants ; envoyez-les quelque part qui le fait.)

    • Un enregistrement valide. Rien n’est imprimé, exactement comme pour les autres enregistrements.

    • Un enregistrement que les destinataires ignoreraient. La raison est imprimée en commentaire et journalisée comme avertissement, et aucune valeur de remplacement n’est proposée. C’est délibéré : un enregistrement qui ne s’analyse pas reste une déclaration d’intention, et un p=reject malformé doit être réparé là où il est plutôt que remplacé par le p=none que cet outil suggérerait sinon — corriger une faute de frappe ne devrait pas rétrograder silencieusement la politique d’un domaine.

    La validité est jugée strictement, étiquette par étiquette, contre la RFC 7489 §6.4 plutôt qu’en cherchant des sous-chaînes, car un enregistrement DMARC échoue silencieusement : rien ne rebondit, aucun destinataire ne se plaint, et le domaine cesse simplement d’être protégé alors que tout le monde le croit protégé. En particulier, un seul ; manquant — par exemple … p=reject; aspf=s adkim=s — est une syntaxe de liste d’étiquettes générique parfaitement correcte (la RFC 6376 §3.2 autorise les espaces et les = à l’intérieur d’une valeur d’étiquette) : un lecteur laxiste voit donc l’étiquette aspf avec la valeur s adkim=s et l’accepte ; seule la grammaire de valeurs propre à DMARC la rejette. Là où la valeur fautive contient quelque chose ayant l’allure de l’étiquette suivante, le diagnostic le dit nommément (tags are separated by ';', and there is none before 'adkim='). Les autres cas signalés sont un second enregistrement DMARC au même nom (RFC 7489 §6.6.3 : les destinataires écartent tout l’ensemble), une étiquette répétée (RFC 6376 §3.2, où la valeur gagnante est indéfinie), une étiquette p= manquante, et une adresse rua=/ruf= écrite sans son schéma mailto:. Tout ce que pepsi-setup déclare valide est recoupé dans la suite de tests contre l’analyseur même qu’applique pepsi-ingress(1) au courrier entrant, de sorte qu’un enregistrement béni ici ne puisse pas être un enregistrement qu’un destinataire jette ; les vérifications ci-dessus sont délibérément plus strictes que cet analyseur, puisqu’un enregistrement peut s’analyser sans être pour autant la politique que son auteur a publiée.

    Avant de suggérer chaque enregistrement TXT (DKIM, SPF, DMARC, MTA-STS, TLSRPT), pepsi-setup interroge d’abord le DNS en service (au mieux) : un enregistrement déjà publié avec la valeur correcte ne contribue rien à la sortie — pas même son en-tête de section par domaine — de sorte que seuls les enregistrements qui ont véritablement besoin d’être (re)publiés sont imprimés. Le rappel mta-sts.<domain> de publier un enregistrement A/AAAA (ou CNAME) est de même émis seulement lorsque cet hôte ne se résout pas déjà, et le bloc DANE/TLSA n’est imprimé que lorsqu’un listener d’ingress terminant le TLS existe. Lorsque rien n’a besoin de changer, tout le vidage de zone est supprimé et une seule ligne setup: DNS records checked and they are OK est journalisée à la place, de sorte que réexécuter sur une zone déjà configurée soit silencieux. La sonde n’est pas fatale — lorsque le DNS est injoignable (ou qu’un résolveur ne peut être construit), chaque enregistrement est traité comme un changement et imprimé. Le bloc DMARC fait exception sur ce dernier point : sans réponse du DNS il n’y a rien à comparer et aucune valeur locale sur laquelle se rabattre, il reste donc silencieux plutôt que de dire à un domaine peut-être déjà correct de publier un p=none. (Utilisez la sous-commande check pour une vérification en service des enregistrements TXT publiés.)

    « Déjà publié avec la valeur correcte » signifie correct sur chaque serveur de noms faisant autorité de la zone, et pas seulement dans la réponse du résolveur du système. Pour chaque enregistrement TXT, pepsi-setup détermine aussi l’ensemble NS de la zone et interroge chaque serveur directement, sans récursion : un secondaire qui sert encore une ancienne clé, un serveur délégué qui ne sert plus la zone (une délégation boiteuse) ou un serveur qui ne répond pas du tout font que les destinataires ne voient le bon enregistrement qu’une partie du temps ; l’enregistrement est donc imprimé avec une ligne ; NOTE: nommant chaque serveur en désaccord (et un avertissement est journalisé). Une mauvaise réponse du résolveur que tous les serveurs faisant autorité contredisent est signalée comme un cache périmé. Lorsqu”aucun serveur faisant autorité n’est joignable — un réseau qui ne laisse sortir que le résolveur local sur le port 53 — un avertissement est journalisé une seule fois et la réponse du résolveur décide seule, comme auparavant.

    Un enregistrement de sélecteur DKIM est jugé comme un vérificateur le lit (RFC 6376 §3.6.1), et non d’après son seul p= : exactement un enregistrement de clé à ce nom, v= (s’il est présent) en premier et valant DKIM1, un k= nommant le type de clé (une clé Ed25519 sans étiquette k= échoue partout, puisque l’étiquette vaut rsa par défaut), aucune restriction h=/s= excluant sha256/email, pas de t=y (mode test, sous lequel une signature ne compte pour rien) et un p= égal à la clé locale. Seuls les sélecteurs avec lesquels [pepsi] DKIM_ALGORITHMS signe sont imprimés et vérifiés, plus celui dont le sceau ARC a besoin sur l”ARC_DOMAIN. À côté de l’enregistrement Ed25519 — et sous forme de ligne de journal lorsque tous les enregistrements sont déjà corrects — pepsi-setup signale que Gmail, Microsoft 365 et Yahoo ne vérifient pas les signatures Ed25519 et que les rapports agrégés DMARC de Google les indiquent comme fail : c’est attendu et sans conséquence, puisque la signature RSA fournit la réussite alignée dont DMARC a besoin, et cela peut être supprimé avec DKIM_ALGORITHMS = rsa.

    Dans la même phase, pepsi-setup vérifie aussi le DNS inverse (PTR) de chaque PUBLIC_IP de pepsi-stage-relay-to-internet — les adresses de sortie propres à cet hôte (un PUBLIC_IP de pepsi-stage-relay-to-smarthost est celui du smarthost, il est donc sauté). Chaque adresse est jugée face au nom qu’annonce l’étape propriétaire, son SERVER_NAME (se rabattant sur le HOSTNAME d’ingress lorsqu’une étape n’en fixe aucun), car c’est cette paire qu’un destinataire compare. Un PUBLIC_IP n’est correct que lorsque son PTR existe, se confirme en sens direct, et nomme exactement cet hôte ; toute autre issue est un avertissement :

    • aucun enregistrement PTR — un fort signal de spam chez de nombreux destinataires ;

    • un PTR qui n’est pas confirmé en sens direct — le nom d’hôte du PTR ne se résout pas (A/AAAA) vers la même IP, de sorte que la vérification aller-retour (forward-confirmed reverse DNS, FCrDNS) qu’appliquent de nombreux MTA échoue et qu’ils traitent l’IP comme n’ayant aucun PTR du tout ;

    • un PTR générique / d’allure attribuée dynamiquement — un nom qui incorpore l’IP ou porte un jeton de pool résidentiel (dynamic, dsl, pool …), ce que les destinataires pénalisent souvent ;

    • un PTR nommant un hôte différent du nom ``EHLO`` — l’enregistrement est parfaitement bien formé et confirmé en sens direct, il nomme simplement un autre hôte. Une large famille de destinataires refuse la session d’emblée avec HELO host does not match rDNS. Cela se déclenche même lorsque le nom du ``PTR`` est l’un de vos propres ACCEPTED_DOMAINS : une machine qui répond à plusieurs noms (disons example.org annoncé dans l”EHLO tandis que son adresse se résout en inverse vers example.net) passe ici toutes les autres vérifications et voit tout de même son courrier refusé ;

    • un PTR qui n’a pas pu être résolu du tout — une zone inverse non déléguée ou un DNS injoignable. L’exactitude n’a pas pu être établie, et une adresse sans PTR utilisable est refusée ou classée indésirable par beaucoup de destinataires : c’est donc signalé plutôt que passé sous silence.

    Chaque avertissement porte une remédiation concrète. Comme le DNS inverse d’un PUBLIC_IP réside dans la zone du propriétaire de l’IP (in-addr.arpa / ip6.arpa), le remède habituel est de demander à votre FAI ou hébergeur de régler le PTR sur le nom annoncé ; si vous exploitez vous-même la zone inverse, le message de PTR manquant imprime l’enregistrement … IN PTR … exact à publier. Pour une discordance de nom où le PTR est déjà l’un de vos propres domaines, le message propose aussi l’autre sens — adopter le nom du PTR comme identité de cet hôte ([pepsi-ingress] HOSTNAME et le SERVER_NAME de chaque étape, l’enregistrement MX et le certificat TLS suivant) — puisque ce remède n’exige la coopération de personne. Les deux noms doivent désigner un seul hôte ; partager un domaine ne suffit pas. La valeur par défaut de nom d’hôte de l’assistant provient de la même résolution PTR (voir –wizard ci-dessous), de sorte qu’une configuration générée par le questionnaire s’accorde d’emblée avec le DNS inverse.

    Le DNS tel que l’Internet le voit. Chacune des vérifications ci-dessus est faite depuis cet hôte, qui est le seul endroit d’où une zone servie depuis son propre réseau semble toujours saine : derrière un NAT, une requête venant de l’intérieur pour une zone inverse dont le serveur de noms est cette machine même reçoit sa réponse du LAN, du routeur ou de l’hôte lui-même, tandis que l’Internet expire sur un routeur qui redirige le port 25 mais pas le port 53 — ou atteint un serveur de noms qui sert une zone plus petite que celle que son parent délègue, ce pour quoi les résolveurs qui minimisent leurs requêtes (RFC 9156) reçoivent REFUSED. Les destinataires diffèrent alors chaque message avec cannot find your reverse hostname, et la vérification locale affirme que le PTR est correct.

    pepsi-setup pose donc de nouveau la question aux résolveurs publics nommés par [pepsi] PUBLIC_RESOLVERS (par défaut Google, Cloudflare et Quad9, chacun en IPv4 et en IPv6 ; voir pepsi.conf(5)) : le PTR de chaque PUBLIC_IP, confirmé dans le sens direct via le même résolveur ; l’ensemble MX de chaque domaine et les enregistrements TXT que vérifie la sous-commande check ; et les adresses du HOSTNAME de l’ingress. Tous sont interrogés, parce qu’ils diffèrent — par la famille d’adresses qu’ils utilisent pour joindre un serveur de noms et par la façon dont ils parcourent une délégation — et une zone qui n’est joignable qu’en IPv6, ou boiteuse seulement pour un résolveur qui minimise, échoue pour certains d’entre eux et pas pour d’autres. Un destinataire utilise aussi l’un d’eux. Tout résolveur qui ne peut pas résoudre un nom, ne trouve aucun enregistrement ou voit une réponse différente de celle de cet hôte (DNS à horizon partagé) est un avertissement, qui nomme le résolveur et — lorsqu’il en envoie une — son erreur étendue RFC 8914 (No Reachable Authority: At delegation …). Lorsque les serveurs de noms de la zone se résolvent vers les adresses propres de cet hôte ou vers des adresses privées, l’avertissement le dit et nomme les causes habituelles : une redirection de port absente ou désactivée pour le port 53 UDP ou TCP, une règle de pare-feu IPv6, ou une zone servie qui n’est pas celle qui est déléguée. La sous-commande check les signale comme [UNREACHABLE] (un enregistrement correct que l’Internet ne peut pas lire) et liste les verdicts PTR dans un groupe reverse DNS qui leur est propre.

    Les résolveurs injoignables depuis cet hôte sont écartés en silence (un hôte sans connectivité IPv6 perd ceux en IPv6) ; lorsqu’aucun n’est joignable, un avertissement est journalisé une fois et seule la vue locale est vérifiée. PUBLIC_RESOLVERS = none désactive cette vérification.

    Recoupements MTA-STS. Tout le reste de ce que pepsi-setup vérifie à propos de MTA-STS est cohérent par construction : l’enregistrement TXT _mta-sts porte un id dérivé de la politique, et la politique est dérivée de la configuration, les comparer ne peut donc que réussir. Deux choses ne sont pas dérivées de la configuration, et sous MTA_STS_MODE = enforce l’une ou l’autre, si elle est fausse, arrête silencieusement tout le courrier entrant du domaine. Les deux sont vérifiées à chaque pepsi-setup run, et rapportées par la sous-commande check et par la console web ; comme les constats PTR, ce sont des avertissements et ils ne font jamais échouer l’exécution.

    • L’ensemble ``mx`` de la politique face aux enregistrements MX réels. Une politique est la promesse que les expéditeurs ne peuvent se connecter qu”*aux hôtes qu’elle nomme. Un expéditeur résout le ``MX`` du domaine, trouve un hôte que la politique n’autorise pas, et refuse de remettre – chez tous les expéditeurs conformes à la fois, sans aucun symptôme de ce côté-ci hormis du courrier qui n’arrive jamais. C’est l’échec auquel le repli ``MTA_STS_MX`` invite, car ``HOSTNAME`` est le nom de salutation ``EHLO`` et n’a pas à être un nom vers lequel un enregistrement ``MX`` pointe. L’avertissement nomme les hôtes de chaque côté et affiche l’unique ligne ``MTA_STS_MX`` qui les mettrait d’accord, qualifiée par domaine lorsque les domaines servis ont des hôtes MX différents, ainsi que l’alternative consistant à changer plutôt l’enregistrement ``MX`` et le conseil de se replier sur ``MTA_STS_MODE = testing`` jusqu’à ce qu’ils concordent. Sont aussi rapportés ici : un domaine **sans* enregistrement MX, un MX nul (RFC 7505) sur un domaine dont nous acceptons le courrier, une cible MX qui est un CNAME (interdit par la RFC 5321 §5.1, et les expéditeurs peuvent le refuser), une entrée de politique qui ne correspond à aucun hôte MX réel (inoffensif, mais cela élargit ce à quoi les expéditeurs se connecteront), et une entrée DOMAIN:HOST nommant un domaine absent de ACCEPTED_DOMAINS – qui ne s’applique à aucune politique et qui est exactement l’allure d’une faute de frappe dans l’option.

    • Si la politique peut être récupérée. pepsi-setup demande https://mta-sts.<domain>/.well-known/mta-sts.txt et compare ce qui revient avec la politique que cette configuration définit – sémantiquement, de sorte que les fins de ligne et l’ordre des clés ne sont pas des différences. Les redirections ne sont pas suivies, car la RFC 8461 §3.3 interdit à un expéditeur de les suivre, et une politique servie seulement derrière une redirection est une politique qu’aucun expéditeur ne peut lire. Un enregistrement TXT correct devant une politique que personne ne peut récupérer n’est pas une hypothèse : le serveur est à un socket injoignable, un serveur frontal mal configuré ou un certificat manquant du moment où chaque expéditeur recevra un 502 à la place, tandis que chaque unité reste active et que rien n’est journalisé – et les expéditeurs qui avaient déjà mis une politique en cache continuent d’appliquer l”ancienne jusqu’à max_age, l’échec ne s’autolimite donc même pas. Un 5xx est rapporté avec le socket du serveur frontal comme chose à vérifier.

      Lorsque le nom publié ne peut pas être atteint depuis cette machine – il ne résout pas encore, ou le réseau ne lui permet pas d’atteindre sa propre adresse publique –, la requête est retentée contre cette machine par l’interface de boucle locale, en conservant le nom publié dans le SNI et l’en-tête Host, de sorte que le certificat est encore validé contre le nom qu’un expéditeur emploierait. Ce résultat est étiqueté comme sonde locale, car il dit ce qu’un expéditeur obtiendrait une fois que mta-sts.<domain> résoudra ici ; c’est la raison pour laquelle une erreur de configuration purement locale est tout de même signalée sur une machine dont le DNS n’est pas encore publié.

    Le domaine SRS peut recevoir ses propres rejets. Lorsqu’une étape exécute pepsi-stage-srs(1), pepsi-setup vérifie que [pepsi-srs] SRS_DOMAIN possède un enregistrement MX – ou, à défaut, un enregistrement A/AAAA, le MX implicite de la RFC 5321 §5.1. Une adresse SRS est un chemin de retour : la réécriture fait de cette machine la destination des rejets d’un courrier qu’elle n’a pas écrit, et le HMAC contenu dans l’adresse existe pour que pepsi-ingress(1) puisse décoder un rejet qui revient et le relayer à l’expéditeur d’origine. Un domaine dépourvu des deux enregistrements termine cet aller-retour en silence – le transfert fonctionne, SPF passe au saut suivant, et chaque rejet est abandonné par le MTA qui a tenté de le renvoyer : l’expéditeur d’origine n’apprend donc jamais que son message n’est pas arrivé. Un MX nul (RFC 7505, MX 0 .) est la même réponse énoncée délibérément, et une cible MX qui est un CNAME est signalée pour la raison qu’elle l’est partout ailleurs (RFC 5321 §5.1, RFC 2181 §10.3). L’avertissement affiche l’enregistrement à publier, en nommant un hôte vers lequel les domaines acceptés routent déjà du courrier, et propose les deux alternatives : faire pointer SRS_DOMAIN vers un domaine qui reçoit déjà du courrier ici, ou retirer l’étape si rien n’est transféré vers l’extérieur. Comme les vérifications ci-dessus, c’est un avertissement et il ne fait jamais échouer l’exécution.

    Cette vérification existe parce que l’omission est invisible sous tous les autres angles. Le domaine SRS est une identité d”envoi : il rejoint donc l’ensemble qui reçoit des clés DKIM et des conseils SPF et DMARC ; pepsi-setup ne suggère d’enregistrement MX pour aucun domaine, cela relevant de la décision de routage de l’exploitant. Un exploitant qui publie tout ce qu’on lui dit de publier se retrouve donc avec deux des trois enregistrements dont le domaine a besoin, et rien nulle part ne le dit.

    La confirmation en sens direct n’interroge que le DNS faisant autorité et ignore délibérément le fichier local /etc/hosts — un hôte de courrier y liste couramment son propre nom face à 127.0.1.1 ou à une adresse interne au NAT, ce qui ferait sinon paraître non confirmé un nom parfaitement confirmé. Ces observations sont des avertissements de délivrabilité, non des erreurs : elles ne font jamais échouer l’exécution.

  9. Vérifie le backend de paiement (facultatif). Lorsqu’une section [pepsi-payments] configure un backend marchand GNU Taler, pepsi-setup le vérifie via HTTP : GET /config doit identifier un backend taler-merchant (de sorte que l’URL est correcte) et GET /private/orders?limit=1 doit renvoyer 200 (de sorte que le MERCHANT_ACCESS_TOKEN est correct). Il s’assure ensuite qu’un webhook pepsi-resume existe qui fait un POST vers le point de terminaison /resume de pepsi-httpd chaque fois qu’une commande est payée — le créant s’il est absent, ou, si un webhook pepsi-resume existe déjà, journalisant un avertissement lorsque sa définition diffère de celle attendue (il n’est jamais silencieusement écrasé). Le webhook cible l’hôte SNI [pepsi-httpd-cert-*] configuré le plus court via HTTPS et s’authentifie avec [pepsi-httpd] RESUME_AUTHORIZATION_TOKEN. Lorsqu’aucun backend marchand n’est configuré, cette étape est entièrement sautée.

  10. Rappelle les identifiants qui expirent hors bande. Lorsqu’un MTA smarthost utilise AUTH = oauth, pepsi-setup valide les sections secrètes [pepsi-helper-token-refresh-<name>] correspondantes qu’il peut lire (il s’exécute en tant que root) et imprime un rappel indiquant que ces jetons d’accès doivent être maintenus à jour — par exemple en activant pepsi-helper-token-refresh(1), qui ne fait pas partie de pepsi.target. Lorsqu’un MTA utilise AUTH = gssapi, il rappelle de même que le cache d’identifiants Kerberos doit être renouvelé hors bande (une keytab plus k5start ou cron) et être lisible par le worker du dispatcher, en contrôlant la vraisemblance de tout chemin de cache qu’il peut voir. Les deux rappels vont dans le journal (sortie d’erreur standard), après la sortie de zone, de sorte qu’ils ne peuvent jamais corrompre un fichier de zone redirigé.

Tout le comportement est piloté par un fichier de configuration de style INI (voir pepsi.conf(5)). Le même fichier de configuration est partagé avec les autres outils Pepsi.

85.1.39.1.4. Privilèges et propriété

pepsi-setup peut être exécuté soit directement en tant que propriétaire de la base de données (par exemple su pepsi-owner -c 'pepsi-setup … run'), soit en tant que root. Lorsqu’il est exécuté en tant que root, il gère lui-même deux identités distinctes, de sorte que les objets résultants sont possédés par les bons comptes :

  • Les objets de base de données sont créés en tant que rôle pepsi-owner. Avant tout travail de base de données (installation du schéma, provisionnement des rôles, validation des paramètres, vérification du backend de paiement), pepsi-setup assume l’identité effective de l’utilisateur pepsi-owner ; l’authentification par pair de PostgreSQL se connecte alors en tant que le rôle du même nom, de sorte que le schéma pepsi, ses tables, séquences et fonctions sont possédés par pepsi-owner. L’utilisateur/rôle pepsi-owner et la base de données elle-même doivent déjà exister (le paquet Debian les crée ; sinon créez-les à la main). Seul l’uid effectif change, de sorte que root est regagné ensuite.

  • Le matériel de clé est remis au compte pepsi. Les clés DKIM par domaine sont générées en tant que root (mode 0600) puis chown-ées vers l’utilisateur pepsi — le compte de dispatcher qui signe le courrier sortant — de sorte que les étapes de signature puissent les lire. Lorsque l’utilisateur pepsi n’existe pas, c’est une opération sans effet, journalisée.

Lorsque pepsi-setup n’est pas exécuté en tant que root, aucun des deux changements n’a lieu : il opère entièrement en tant que l’utilisateur invoquant, qui doit donc être le propriétaire de la base de données et pouvoir lire/écrire KEY_DIR.

Note

Le scellement ARC (effectué par pepsi-stage-arc(1) sur le courrier entrant) réutilise la clé DKIM du [pepsi] ARC_DOMAIN, de sorte qu’aucune clé ARC ou enregistrement DNS séparé n’est généré : les vérificateurs ARC récupèrent la clé publique du sceau depuis le même enregistrement DKIM. Lorsque ARC_DOMAIN est réglé, pepsi-setup l’inclut simplement parmi les domaines de clé/DNS.

85.1.39.1.5. Assistant

Pour un premier déploiement, –wizard remplace l’édition à la main par un court questionnaire interactif et écrit un fichier de configuration complet et déjà validé.

Le questionnaire est présenté comme une séquence d”étapes numérotées (chacune affichée comme une bannière Step X/Y avec une description d’une ligne, en couleur sur un terminal capable — réglez la variable d’environnement NO_COLOR pour la désactiver), regroupant les questions liées : Identité du serveur, Système de courrier existant, Remise locale, Authentification de soumission, Confiance en la boucle locale, Courrier redirigé, Sécurité du transport, Filtrage de contenu, Paywall anti-spam, Self-service de compte et Cryptographie de bout en bout (plus Options expertes sous –expert). Quelles étapes apparaissent (et donc Y) découle de la direction choisie et de la détection ou non d’un Dovecot local. Le chapitre L’assistant du manuel dessine le pipeline que produit chaque direction et liste ce que décide chaque question. Lorsque stdin est un vrai terminal, les invites sont éditables ligne à ligne avec les raccourcis clavier GNU-readline habituels (Home/End, Ctrl-Left/Ctrl-Right pour sauter des mots, Ctrl-A/ Ctrl-E/Ctrl-K/Ctrl-U/Ctrl-W, et Up/Down pour rappeler des réponses antérieures) ; un stdin redirigé (non-terminal) — tel qu’utilisé par la suite de tests et les scripts — retombe sur de simples lectures de ligne. Chaque réponse est validée au moment où elle est saisie (les noms d’hôte et les domaines doivent être pleinement qualifiés, le postmaster une adresse valide, les IP d’envoi de véritables littéraux IP, les ports numériques, le délai de paiement une durée h/m/s, et ainsi de suite), de sorte qu’une erreur est signalée à sa propre invite et corrigée sur-le-champ plutôt qu’à la seule validation finale de la configuration entière.

Il demande d’abord la direction que l’hôte sert — inbound (recevoir depuis Internet le courrier pour nos domaines, en le remettant localement et/ou en le relayant plus loin), outbound (envoyer les soumissions de nos propres utilisateurs directement vers Internet) ou both (la valeur par défaut ; séparer sur state.local_origin avec une étape d’acheminement) — ce qui détermine les listeners SMTP (port 25 pour l’entrant, 465/587 pour le sortant) et les étapes de relais qui apparaissent. Il demande ensuite le nom d’hôte, le ou les domaines acceptés, l’adresse postmaster, la ou les IP d’envoi publiques et la chaîne de connexion à la base, proposant des valeurs par défaut sensées (le parent du nom d’hôte comme domaine). La valeur par défaut du nom d’hôte est prise du DNS inverse : l’assistant détecte les adresses publiques de cet hôte (la même détection décrite plus bas pour les IP d’envoi), consulte leurs enregistrements PTR, et propose le premier nom qui se confirme en sens direct vers l’adresse et n’a pas l’air auto-généré. Ce n’est qu’à défaut d’un tel nom qu’il se rabat sur /etc/hostname. C’est délibéré — un serveur de courrier destinataire compare le nom annoncé dans l”EHLO au PTR de l’adresse d’où vient la session et rejette lorsqu’ils divergent (HELO host does not match rDNS), tandis qu”/etc/hostname est une étiquette purement locale qu’aucun destinataire ne voit jamais. Sur un hôte qui répond à plusieurs noms, les deux diffèrent couramment, et le DNS inverse réside dans la zone du propriétaire de l’IP : c’est donc normalement la moitié qu’on ne peut pas changer. Lorsque la valeur par défaut proposée (la réponse d’une exécution antérieure, ou /etc/hostname) contredit le PTR, l’assistant le dit et nomme les deux avant de poser la question. La valeur par défaut des IP d’envoi publiques est détectée automatiquement et proposée pour confirmation : les enregistrements A/AAAA publiés du nom d’hôte, unis aux adresses d’interface globalement routables propres à l’hôte, et — seulement lorsque cela ne trouve aucune IPv4 publique (un hôte derrière un NAT avec une IPv4 privée) — l’IPv4 WAN rapportée par la passerelle UPnP locale via upnpc (miniupnpc), s’il est installé. Seules des adresses publiques sont suggérées : une adresse privée/loopback/lien-local n’est jamais pré-remplie (derrière un NAT, la valeur détectée est l’IP de sortie publique, pas l’adresse LAN). L’opérateur la confirme ou la modifie avant que quoi que ce soit ne soit écrit.

Sur le chemin entrant, la question suivante est de savoir si un système de courrier existant — Microsoft Exchange ou Microsoft 365 — se trouve derrière cet hôte. Répondre oui fait de Pepsi une passerelle transparente : il devient le MX et remet tout au point de terminaison du locataire, de sorte qu’il n’y a pas de boîtes aux lettres locales et que les questions de remise locale ci-dessous sont sautées. Il demande ensuite l’hôte et le port SMTP de ce point de terminaison (en avertissant qu’il doit s’agir du point de terminaison propre au locataire, et non du MX public du domaine, qui est cet hôte et bouclerait) ainsi que la famille d’adresses IP par laquelle l’atteindre (any/ipv4/ipv6 — Exchange Online refuse le courrier venant d’une adresse IPv6 sans DNS inverse, ipv4 est donc l’échappatoire lorsque le DNS inverse IPv6 n’est pas délégué).

Sinon, sur le chemin entrant, il demande quelle(s) méthode(s) de remise locale utiliser — Maildir (pepsi-stage-relay-to-maildir) et/ou LMTP vers un MDA (pepsi-stage-relay-to-lmtp) — et, lorsque Maildir est activé, s’il faut honorer les fichiers ~/.forward par utilisateur (pepsi-stage-dot-forward ; la section générée ne transfère que vers des adresses, avec ALLOW_PIPE / ALLOW_FILE désactivés).

Toujours sur le chemin entrant, il demande si cet hôte doit répondre au courrier des destinataires absents (pepsi-stage-vacation, non par défaut). Répondre oui ne fait que placer l’étape dans le pipeline — après l’expansion des alias et après les barrières anti-spam, de sorte qu’un avis ne soit jamais envoyé au nom d’un alias ni en réponse à du courrier que les barrières auraient refusé. Personne ne reçoit de réponse tant qu’il n’existe pas de dates de congé, et celles-ci sont de la configuration par adresse ordinaire plutôt qu’une réponse d’assistant : un utilisateur peut fixer les siennes par e-mail, un opérateur avec pepsi-settings set <address> vacation VACATION_RANGES …, et un jour férié partagé par tous avec pepsi-config set --scope domain:<domain> stage-vacation VACATION_RANGES …. La section générée laisse donc VACATION_RANGES non défini (avec la syntaxe en commentaire) et pointe RESPONSE_STAGE vers la queue de signature partagée afin que chaque avis soit signé et relayé. Sur un hôte sans remise locale — un pur redirecteur, ou une façade Exchange — il règle en outre VACATION_TAG = none : marquer le sujet réécrirait sinon un en-tête couvert par la signature DKIM de l’auteur et par le sceau ARC propre à cet hôte, que le saut suivant verrait échouer. Voir pepsi-stage-vacation(1).

Il demande ensuite le smarthost amont (facultatif lorsque la remise locale ou un système Exchange gère les domaines servis, requis sinon).

Deux questions décident ensuite comment le courrier quitte cet hôte avec une signature qui lui est propre. La première concerne les programmes tournant sur cet hôte — cron, mail(1), logwatch, git send-email ou une application web — qui sont sinon refusés avec 550 5.7.1 Relaying denied pour tout destinataire hors d’un domaine servi.

L’assistant ne demande pas s’il faut servir la socket de soumission de domaine UNIX qu’emploie /usr/sbin/sendmail. pepsi-ingress la sert toujours, en la dérivant de [pepsi-sendmail] SOCKET : aucune section de listener n’est donc écrite et il n’y a rien à mal répondre. Le noyau indique à pepsi-ingress quel compte a ouvert la socket, de sorte qu’un appelant ne peut jamais envoyer qu”en son propre nom : le login passe par USERNAME_MAP exactement comme le ferait un nom d’utilisateur SASL. Voir pepsi-sendmail(1), et SOCKET = none pour la désactiver.

Ce qu’il demande bel et bien, sur un hôte servant les deux directions seulement (la réponse devient MYNETWORKS sur le listener MX du port 25, que seul un hôte entrant possède, et ne vaut d’être demandée qu’à un hôte qui soumet aussi), sous forme d’une question oui/non valant non par défaut, c’est s’il faut faire en plus confiance à la boucle locale :

TRUST_LOOPBACK

Ajouter MYNETWORKS = 127.0.0.0/8 ::1/128 au listener du port 25 (fusionné avec tous les réseaux réglés via les options expertes, jamais dupliqué). Ceci fait confiance à une position réseau plutôt qu’à un utilisateur, de sorte que tout processus local — y compris une application web compromise — peut envoyer au nom de n’importe qui ; et c’est aussi ce qui permet à un message que cet hôte relaie vers lui-même (un domaine servi dont le MX public est cet hôte) de rentrer comme une nouvelle soumission et de boucler. Ne répondez oui que pour un logiciel qui insiste pour parler SMTP au port 25 et qu’on ne peut pas diriger vers la socket.

Dans les deux cas, les messages qui en résultent comptent comme originaires localement (state.local_origin) et sont donc signés en DKIM par le chemin sortant.

Sur le chemin entrant, il demande s’il faut signer en DKIM le courrier que cet hôte redirige (par défaut oui) : un alias qui se déploie hors site, un ~/.forward pointant vers un autre fournisseur, ou tout le chemin de relais pur. Ces messages partent par la queue entrante, que le [stage-dkim-sign] sortant ne touche jamais, de sorte que sans cela ils arrivent au saut suivant avec notre enveloppe SRS mais un dkim=none de notre part. Répondre oui émet une seconde étape de signature, [stage-dkim-sign-relay], entre la réécriture SRS et la destination, avec SIGNING_DOMAIN épinglé au domaine servi principal. Cet épinglage est délibéré : la signature est fail-closed, et le domaine de l’auteur appartient à quelqu’un d’autre — dériver le domaine de signature d’un From: étranger ferait échouer chaque message redirigé faute de clé. L’identité propre de l’hôte — le domaine de scellement ARC ([pepsi] ARC_DOMAIN), chaque SERVER_NAME de relais/rebond et le certificat de listener de repli — est le nom d’hôte configuré partout, distinct des domaines de destinataires acceptés (qui délimitent SRS, MTA-STS et les adresses de rapport TLS).

La manière dont ces listeners lient leurs ports dépend de l’hôte. Lorsque le système est géré par systemd (détecté par la présence de /run/systemd/system), l’assistant écrit des listeners activés par socket (SERVE = systemd avec un FD_INDEX par listener) de sorte que les ports privilégiés soient liés par des unités .socket et hérités par le service non privilégié ; sinon il retombe sur des liaisons directes (SERVE = tcp avec BIND_TO / PORT). Les listeners d’ingress prennent toujours le FD_INDEX 0 pour le port 25, 1 pour le 465 et 2 pour le 587, en accord avec le pepsi-ingress.socket livré, qui liste les trois quelle que soit la direction que sert l’hôte. Avec l’activation par socket, vous devez installer les unités .socket correspondantes (un pepsi-ingress.socket listant ListenStream=25, 465 et 587 dans cet ordre, et un pepsi-httpd.socket pour le port 443) ; l’assistant imprime un rappel après l’écriture.

Deux questions de sécurité du transport suivent. Sur le chemin entrant, il demande s’il faut servir une politique MTA-STS (par défaut oui) : lorsqu’elle est activée, il règle MTA_STS_MODE = enforce et ajoute une section [pepsi-httpd-cert-*] pour l’hôte de politique mta-sts.<domain> de chaque domaine accepté (servi via HTTPS par pepsi-httpd), dont pepsi-setup provisionne le certificat via certbot comme tout autre — vous devez pointer le DNS de chaque nom mta-sts.<domain> vers le serveur pour que la politique soit accessible. Les hôtes que la politique autorise ne sont pas demandés : ils sont lus dans les enregistrements MX en service des domaines servis et écrits sous [pepsi] MTA_STS_MX (omis lorsqu’aucun enregistrement MX n’existe encore, de sorte que la politique se rabat sur HOSTNAME ; voir Recoupements MTA-STS plus haut). Il demande ensuite s’il faut envoyer des rapports SMTP TLS (TLSRPT, RFC 8460 ; par défaut oui), ce qui émet la section [pepsi-tlsrpt].

La même étape se termine par la seule question qui ne porte pas du tout sur ce déploiement : celle de savoir s’il faut partager une télémétrie anonyme d’usage des fonctionnalités avec le projet Pepsi ([pepsi] SHARE_TELEMETRY, par défaut non). Elle relève du consentement explicite : l’invite explique donc ce qui serait envoyé avant de poser la question — un SYSTEM_ID aléatoire de 256 bits et des décomptes des fonctionnalités de Pepsi que cet hôte a exercées, jamais d’adresses, de données de message, de noms d’hôte ni d’adresses IP — et appuyer sur Entrée d’un bout à l’autre du questionnaire ne partage par conséquent rien. Répondre non désactive tout le chemin : aucun identifiant n’est généré, la configuration ne démarre jamais le démon, et un démon que l’opérateur a démarré est mis en sommeil. Voir pepsi-telemetry-client(1).

Les sections pepsi-httpd sont toujours écrites, quoi que l’on refuse par ailleurs : HTTPS sur le port 443 avec le certificat d’hôte qu’utilisent les listeners SMTP (servant la politique MTA-STS lorsque celle-ci est activée), et la console d’administration sur /run/pepsi/admin.sock pour le groupe pepsi-admin. pepsi.target démarre le serveur et sa socket sur chaque hôte, de sorte qu’une configuration sans listener le laisserait échouer et redémarrer en boucle.

Un court menu de fonctionnalités suit, posé uniquement là où il s’applique à la direction choisie : s’il faut bloquer le courrier par langue détectée (et, sinon, s’il faut tout de même localiser les messages de rebond, ce qui active la détection de langue). Lorsque la détection est activée, il imprime d’abord la liste complète des codes ISO 639-1 pris en charge et demande quelles langues le détecteur doit reconnaître, acceptant * (la valeur par défaut) pour chaque langue prise en charge par le détecteur et rejetant tout code non pris en charge sur-le-champ (plutôt qu’à la seule validation finale). Seule une langue détectée peut ensuite être autorisée ou bloquée, de sorte que lorsque le blocage est activé, il demande ensuite une unique politique de langue combinée dans une syntaxe +allow/-block — par exemple +en +fr -ja -zh autorise l’anglais et le français tout en bloquant le japonais et le chinois, et un unique joker -* (ou +*) couvre toute autre langue détectée (ainsi +en +fr -* n’autorise que l’anglais et le français). Le littéral none peut être listé pour décider du sort du courrier non détecté. Cette unique réponse est scindée en les options WHITELIST et BLACKLIST de l’étape. Il demande ensuite s’il faut demander aux expéditeurs inconnus de confirmer par une réponse (SECRETARY, pepsi-stage-secretary(1), par défaut non), puis interroge sur le paywall anti-spam GNU Taler (qui active aussi la liste blanche de correspondants afin que les contacts connus sautent la demande de paiement). Lorsque le paywall est activé, l’assistant demande d’abord l”URL du backend marchand et la vérifie immédiatement par HTTP (GET /config doit identifier un backend taler-merchant), en relisant les devises que le backend prend en charge — le prix doit être libellé dans l’une d’elles, donc l’URL doit être connue avant que le prix soit demandé. Il demande ensuite l”identifiant d’accès : soit un jeton d’accès prêt à l’emploi (reconnu à son préfixe secret-token: et vérifié auprès du backend) soit le mot de passe de l’instance, que l’assistant échange contre un jeton d’accès à portée limitée via POST /private/token (demandant la portée all afin que le jeton puisse aussi gérer le webhook pepsi-resume). Enfin, il demande le prix par message sous forme d’une liste de montants Taler séparés par des espaces (par exemple EUR:1 CHF:1 KUDOS:10), validés par rapport aux devises du backend ; chaque montant devient un choix dans la commande v1 générée ORDER_CHOICES, laissant le payeur choisir une devise. Lorsque le paywall et la confirmation d’envoi sont tous deux désactivés, et que l’hôte a aussi un chemin sortant, l’assistant demande à la place s’il faut simplement apprendre une liste blanche de correspondants depuis le courrier sortant.

Sur le chemin sortant, une étape de libre-service pose ensuite jusqu’à deux questions oui/non indépendantes, toutes deux valant non par défaut : si les titulaires de comptes peuvent modifier par e-mail leurs propres paramètres par adresse (pepsi-stage-edit-settings(1)) — posée seulement sur un hôte servant les deux directions, car chaque étape qu’un titulaire de compte peut modifier est une étape entrante — et s’ils peuvent opérer par e-mail leur propre portefeuille GNU Taler — solde, rechargement et paiements de pair à pair, servis par le même pepsi-stage-auto-pay(1) qui règle les demandes de paiement entrantes.

La dernière étape, posée quelle que soit la direction choisie, est la cryptographie de bout en bout (par défaut non) : la question de savoir si cette passerelle doit chiffrer et déchiffrer le courrier elle-même (OpenPGP et S/MIME), de sorte que les utilisateurs n’aient besoin d’aucun greffon client. Répondre oui place pepsi-stage-encrypt(1) et pepsi-stage-decrypt(1) dans le pipeline ; rien n’est chiffré tant que la clé d’un destinataire n’est pas connue, et les clés proviennent de la découverte (pepsi-keydisc(1)), de pepsi-keys(1), ou du courrier que les correspondants nous envoient. Sur le chemin entrant, il demande ensuite s’il faut apprendre les clés des correspondants depuis ce courrier (Autocrypt, par défaut oui), ce qui ajoute pepsi-stage-autocrypt-learn(1), et s’il faut rechiffrer ce que la passerelle a déchiffré vers la propre clé de client de messagerie de l’utilisateur avant de le classer (REENCRYPT, par défaut oui), ce qui ajoute pepsi-stage-reencrypt(1) devant la remise locale. Sur le chemin sortant, il demande s’il faut activer le chiffrement automatique à la manière de pEp (PEP, par défaut oui) : chaque expéditeur d’un domaine servi reçoit une clé OpenPGP à son premier message, annoncée dans un en-tête Autocrypt:, et le courrier est signé à l’intérieur du chiffrement. Il énonce le coût avant de poser la question – la création anticipée de clés place dans la base de données une clé privée enveloppée pour quiconque envoie du courrier, et sa clé publique dans le Web Key Directory, qu’il utilise un jour le chiffrement ou non – et écrit la réponse explicitement, ENABLE_PEP = yes ou ENABLE_PEP = no, dans [stage-encrypt] de pepsi.conf, où elle prend effet (aucun fichier config.d ne la définit). En cas d’acceptation, il demande s’il faut aussi joindre la clé de l’expéditeur sous forme de fichier (ATTACH_KEYS_AS_FILES, par défaut non), écrit seulement s’il est choisi. L’étape de chiffrement reçoit toujours RESPONSE_STAGE = dkim-sign, de sorte que les avis de clé enregistrée et les réponses aux commandes de clé par e-mail sont signés et relayés comme tout autre courrier sortant. Le questionnaire de configuration dans le navigateur pose les mêmes questions. Le magasin de clés, la découverte de clés et la clé de chiffrement de clés sont provisionnés par le reste de la configuration quelle que soit la réponse.

SRS est câblé automatiquement sur chaque chemin entrant, quelle que soit la destination du relais, avec un secret généré ; seul son SRS_DOMAIN est demandé (à la même étape que la question de signature du courrier redirigé, avec pour valeur par défaut le domaine servi principal).

85.1.39.1.5.1. Filtres de courrier (milters) trouvés sur cet hôte

Sur un hôte qui reçoit du courrier, l’assistant le parcourt à la recherche de daemons milter et propose chacun de ceux qu’il trouve comme étape pepsi-stage-milter (voir pepsi-stage-milter(1)). Il n’en installe jamais un, et ne démarre ni ne confine le daemon : comme sous sendmail et Postfix, le filtre garde son propre paquet, son unité et son compte utilisateur, et Pepsi n’est qu’un client. Le balayage est sauté sur un hôte purement sortant, où il n’y a pas de chemin entrant sur lequel placer un filtre, et chaque filtre qu’il trouve est placé sur le chemin entrant. Un filtre pour le courrier soumis se configure à la main, ou — sur un hôte qui reçoit aussi — est repris d’un non_smtpd_milters importé.

Sept filtres sont reconnus : milter-greylist, milter-regex, clamav-milter, spamass-milter, rspamd (à travers son worker milter rspamd_proxy), mimedefang et amavisd-milter.

Chacun est cherché en trois temps, et un filtre n’est proposé que lorsque les trois réussissent :

  1. Installé — son binaire est sur $PATH ou dans les répertoires sbin habituels, ou un fichier d’unité systemd existe pour lui.

  2. Écoute quelque part — une socket est lue depuis le fichier de configuration propre au filtre (le MilterSocket de clamav-milter.conf, le socket de greylist.conf, un SOCKET= ou un indicateur -p d’un fragment /etc/default, le bind_socket de Rspamd), puis depuis l”ExecStart de son unité, puis depuis les chemins que livre sa distribution.

  3. Répond — la configuration initiale mène à bien une véritable négociation d’options milter avec chaque socket candidate et prend la première qui réussit.

La poignée de main est ce qui rend l’offre digne de confiance : un paquet installé ne prouve rien d’un daemon arrêté, et une mauvaise supposition dans les listes de chemins ne coûte rien, car une socket qui ne répond pas n’est simplement jamais proposée. Elle règle aussi ce que le filtre a le droit de faire. ALLOW_ACTIONS doit accorder chaque action qu’un filtre demande, sinon pepsi-stage-milter refuse le message par conception, et le filtre énonce cet ensemble pendant la négociation — la configuration initiale offre donc tout uniquement pour entendre la réponse, et écrit précisément ce qui a été demandé et rien de plus. Un filtre qui ne fait qu’inspecter se voit accorder none ; MIMEDefang se voit accorder les actions d’enveloppe exactement lorsque la politique Perl propre au site les emploie.

Chaque filtre accepté devient une étape, placée selon ce qu’il fait plutôt que selon l’ordre où il a été trouvé : politique et greylisting d’abord, puis analyse antivirale, puis notation du spam, puis les cadres à usage général — et tous après [stage-decrypt] (afin qu’un filtre voie du texte clair) et avant les étapes de liste blanche, de paywall et d’Autocrypt (afin que celles-ci puissent brancher sur ce qu’un filtre a étiqueté). Les filtres repris d’un import de MTA n’ont pas de rôle connu et s’exécutent donc après tous ceux-là, en conservant l’ordre relatif dans lequel ce MTA les exécutait.

Le courrier rejeté est abandonné plutôt que renvoyé en rebond. Pepsi exécute les filtres après la mise en file : un REJECT s’applique donc à un message auquel pepsi-ingress a déjà répondu 250 — il ne peut pas refuser la session SMTP comme le filtre le faisait sous un MTA, et faire rebondir enverrait du backscatter à l’adresse qu’un spam falsifié a nommée. Le REJECT_STAGE de chaque filtre détecté pointe donc vers une [stage-discard-rejected] générée (un pepsi-stage-discard avec DISPOSITION = failure, BOUNCE = no) ; le verdict est toujours noté dans state.milter et dans le journal. Régler REJECT_STAGE = bounce rétablit la notification, et la section générée le dit. Un filtre venu d’un import de MTA conserve bounce, puisque prévenir l’expéditeur est le comportement repris.

Un filtre reconnu est proposé mais non recommandé : milter-greylist vaut non par défaut. Le greylisting fonctionne en refusant la remise à l’intérieur de la session et en pariant qu’un vrai MTA réessaiera là où un moteur de spam ne le fera pas — et après la mise en file, il n’y a pas de session à refuser et c’est Pepsi lui-même qui réessaie : le triplet expire donc et le message est remis quoi qu’il l’ait envoyé. L’effet de blocage du spam est nul et seul le délai sur chaque nouveau correspondant demeure. Il est tout de même proposé, car un site peut exploiter milter-greylist pour d’autres politiques que son jeu de règles porte.

Les filtres dont Pepsi remplit déjà la fonction ne sont pas proposés : opendkim, openarc, opendmarc, les daemons de politique SPF et postsrsd. En exploiter un à côté de Pepsi signifie que deux composants signent ou jugent le même message. Ils ne sont pas sautés en silence — lorsqu’on en trouve un, le balayage le dit et nomme la fonctionnalité Pepsi qui le remplace, de sorte qu’un balayage n’ayant rien trouvé à proposer soit distinguable d’un balayage qui n’a pas eu lieu. La classification du spam n’est délibérément pas de cette catégorie : Pepsi a ses propres opinions sur le spam, mais ce sont des opinions différentes de celles d’un classificateur bayésien, et un opérateur peut raisonnablement vouloir les deux.

Chaque réponse est mémorisée sous sa propre clé MILTER_* (MILTER_CLAMAV, MILTER_RSPAMD, …) dans [pepsi-wizard]. Seuls les filtres que cette exécution a réellement trouvés y sont écrits ; une réexécution rebalaie.

La configuration générée est validée à travers exactement les mêmes vérifications que run avant d’être écrite, de sorte que l’assistant ne produit jamais un fichier qui échouerait plus tard à se charger. Si la validation échoue, l’assistant imprime l’erreur fautive et relance le questionnaire avec vos réponses précédentes pré-remplies comme valeurs par défaut, de sorte que vous puissiez corriger la ou les options cassées sans tout ressaisir. Les réponses du questionnaire sont enregistrées dans une section [pepsi-wizard] à la fin du fichier (les secrets — le mot de passe du smarthost, le jeton d’accès marchand et la clé SRS générée — n’y sont pas stockés) ; relancer –wizard les importe comme les nouvelles valeurs par défaut.

Parce que la configuration principale est écrite lisible par tous (mode 0644, afin que les comptes d’étape non privilégiés puissent la lire), l’assistant n’y laisse pas de secrets. Chaque secret qu’il écrirait autrement en ligne — le SECRET SRS, le ou les PASSWORD smarthost/LMTP, le MERCHANT_ACCESS_TOKEN marchand et le RESUME_AUTHORIZATION_TOKEN de pepsi-httpd — est déplacé dans un fragment restreint secrets.d/*.secret à côté de la configuration et référencé depuis celle-ci avec une directive @inline-secret@ (voir pepsi.conf(5)). Ceux qui sont générés (la clé SRS, le jeton /resume) sont relus depuis le fragment existant et réutilisés, de sorte que relancer l’assistant ne les renouvelle jamais silencieusement (ce qui invaliderait les expéditeurs SRS déjà en transit ou le webhook de paiement déployé).

85.1.39.1.5.2. Invites de secret

Les secrets que vous saisissez — les mots de passe smarthost et LMTP, le jeton d’accès marchand — ne sont jamais affichés en écho ni écrits dans [pepsi-wizard]. Lors d’une réexécution, ils sont relus depuis leur fragment secrets.d, de sorte que l’invite affiche [***] et qu”Entrée conserve le secret stocké

Smarthost password [***]:

Trois réponses sont possibles :

Entrée

Conserver ce qui est stocké. Lorsque le fragment existe mais que ce processus n’a pas le droit de le lire (il appartient au compte de service et l’assistant s’exécute sans privilèges), le fragment est laissé complètement intact et la configuration continue de pointer dessus — le secret est préservé sans que l’assistant ne le voie jamais.

une valeur

Remplacer le secret stocké par ce que vous avez saisi.

Entrée deux fois

Laisser le secret non défini pour l’instant. Ceci n’est proposé que lorsque rien n’est stocké et seulement sur un terminal ; l’assistant écrit alors la configuration sans cette option, imprime la section, l’option et le fichier auxquels l’ajouter, et laisse dans le fragment une ligne commentée la nommant. Les services ne fonctionneront pas tant que vous ne l’aurez pas renseignée et exécuté pepsi-setup run.

Pour un smarthost qui s’authentifie, l’assistant propose ensuite de vérifier les identifiants en ouvrant une session vers le relais et en s’authentifiant — exactement ce que fait une remise, moins le message — de sorte qu’un mot de passe mal saisi apparaisse pendant que vous êtes encore là pour le corriger, plutôt que sous la forme d’un courrier qui cesse discrètement de circuler

Check these credentials against smtp.relay.example:587 now [Y/n]:

Si le relais les refuse, l’assistant propose de reprendre le mot de passe ; s’il ne peut pas être atteint du tout (pare-feu, DNS, hôte hors service), il le dit et ne vous pousse pas à retaper un mot de passe qui est peut-être parfaitement bon. La vérification est entièrement sautée lorsque stdin n’est pas un terminal : une exécution sans surveillance ne doit pas dépendre de la disponibilité du réseau. Chaque directive est écrite après la dernière option de la section qu’elle alimente, car une directive termine sa section pour l’analyseur (voir pepsi.conf(5)). Les fragments sont sécurisés (mode et propriété) dès leur écriture et de nouveau par run ; voir la commande run ci-dessous. L’assistant recharge ensuite le fichier qu’il vient d’écrire et valide celui-ci, de sorte qu’une configuration qu’il ne peut lui-même analyser n’est jamais laissée derrière. Après l’écriture, l’assistant propose d’exécuter la configuration complète (run) immédiatement.

Si le fichier cible existe déjà et contient une section [pepsi-wizard], ses réponses sont importées comme valeurs par défaut. S’il existe sans une, l’assistant avertit qu’il ne peut importer les réponses précédentes et demande confirmation avant d’écraser ; –force écrase sans demander.

Note

Le paywall anti-spam exige que ses modèles de message (par exemple payment-request.en.body) soient installés sous [pepsi] TEMPLATE_DIR (par défaut ${DATADIR}/templates, c’est-à-dire /usr/share/pepsi/templates pour une installation avec --prefix=/usr) ; make install les y place. Comme l’assistant valide au regard de l’environnement en service, activer le paywall avant que les modèles ne soient installés rapporte une erreur claire plutôt que d’écrire une configuration qui échouerait à l’exécution.

85.1.39.1.5.3. Répondre sans terminal

–answers FILE exécute tout le questionnaire à partir d’un objet JSON associant un identifiant de question à une valeur, sans rien demander. Ce n’est pas une seconde implémentation de l’assistant : le même interview() s’exécute, chaque invite étant court-circuitée vers la réponse fournie (ou, à défaut, vers la valeur par défaut propre à l’assistant, exactement comme appuyer sur Entrée). La structure des branches, la validation par champ et le rendu sont donc ceux de l’interactif.

Les identifiants sont ce qu’imprime pepsi-setup questions, et ce sont les mêmes clés que la section [pepsi-wizard] générée fait circuler dans les deux sens — de sorte qu’une configuration écrite par une exécution antérieure soit elle-même un fichier de réponses valide une fois cette section transformée en JSON. Les options expertes (–expert, et ce qu’une migration de MTA a trouvé) partagent cet espace de noms et sont acceptées ici aussi, validées par la même vérification qu’applique l’invite --expert ; le questionnaire du navigateur les refuse délibérément, puisqu’une option que rien ne demande n’est pas une option qu’un formulaire web devrait fixer. Un identifiant inconnu est une erreur nommant la clé plutôt qu’une ligne silencieusement abandonnée, car une réponse discrètement ignorée ressemble exactement à une réponse acceptée et sans effet.

Des secrets peuvent apparaître dans le fichier (SMARTHOST_PASSWORD, LMTP_PASSWORD, MERCHANT_ACCESS_TOKEN) ; ils sont déplacés dans des fragments secrets.d comme tout autre, de sorte que le fichier lui-même doit être traité comme un identifiant et supprimé ensuite. Un secret déjà stocké dans un fragment est reporté lorsque le fichier de réponses ne le mentionne pas, ce qui rend non destructrice une réexécution scriptée.

Exemple

cat > answers.json <<'EOF'
{
  "DIRECTION": "both",
  "HOSTNAME": "mx.example.com",
  "DOMAINS": "example.com",
  "POSTMASTER": "postmaster@example.com",
  "PUBLIC_IPS": "203.0.113.7",
  "LOCAL_METHODS": "maildir",
  "MTA_STS": "yes"
}
EOF
pepsi-setup --wizard --answers answers.json -c /etc/pepsi/pepsi.conf

85.1.39.1.6. Migrer depuis un autre MTA

Personne n’installe un serveur de courrier sur une machine vide. Lorsque –wizard ne trouve aucune configuration au chemin cible, il cherche le serveur de courrier que l’hôte fait tourner aujourd’hui — Postfix, Exim, Sendmail, qmail ou Stalwart — et propose d’importer ses réglages comme valeurs par défaut des réponses du questionnaire. Si plusieurs sont configurés, il présente un menu numéroté ; –import MTA en choisit un sans demander et –no-import saute entièrement la recherche. L’autodétection est sautée lorsque stdin n’est pas un terminal, de sorte qu’une exécution scriptée de –wizard continue de produire la configuration que ses réponses décrivent plutôt qu’une configuration façonnée par ce qui traînait sur la machine.

Rien n’est importé silencieusement. Chaque valeur importée devient la valeur [default] de sa question, que l’opérateur confirme ou corrige, et tout ce qui n’a pas pu être repris est rassemblé dans un rapport de migration écrit à côté de la configuration sous le nom import-report.txt (également imprimé sous forme de résumé d’une ligne avant le début du questionnaire). Le rapport regroupe ses constats en quatre catégories :

FAILED

Un fichier n’a pas pu être lu ou analysé ; rien n’en a été importé.

UNSUPPORTED

Compris, mais Pepsi n’a pas d’équivalent — une table header_checks, un alias remettant à une commande. L’entrée nomme ce que Pepsi offre à la place, lorsque quelque chose existe.

APPROXIMATED

Importé avec une sémantique différente ; une remise mbox devenant Maildir, un maximal_queue_lifetime arrondi à des heures entières, un réseau loopback retiré de MYNETWORKS.

UNKNOWN

Une directive que l’importateur ne reconnaît pas, et qu’il n’a donc pas migrée. Signalée afin qu’un réglage non reconnu soit visible plutôt qu’invisible.

Un import ne peut jamais faire échouer l’exécution : un fichier illisible, une ligne corrompue ou une directive issue d’un fork dont personne n’a entendu parler produit un avertissement, et le reste de la configuration est tout de même importé.

85.1.39.1.6.1. Tables de routage

/etc/aliases, /etc/postfix/virtual, la virtusertable de Sendmail, les fichiers .qmail-* de qmail et leurs équivalents sont convertis en une table d’alias Pepsi (voir pepsi-stage-aliases(1)) écrite à côté de la configuration sous le nom aliases, et une table d’identité de soumission (les smtpd_sender_login_maps de Postfix et consorts) en username.map (voir USERNAME_MAP dans pepsi-ingress(1)). Un fichier existant n’est jamais écrasé : la table convertie atterrit à la place dans aliases.imported, à charge pour l’opérateur de la fusionner.

Les clés d’alias Pepsi portent un domaine (local@domain, @domain ou un glob *), alors que les clés de /etc/aliases sont de simples local-parts ; l’assistant demande donc comment les qualifier (–alias-style pour la commande import) :

per-domain

Une ligne par alias et par domaine accepté — exact, et la valeur par défaut.

wildcard

Une ligne par alias avec un glob de domaine (postmaster@*) — compact, mais cela correspond aussi aux domaines ajoutés plus tard.

primary

Qualifier uniquement avec le premier domaine accepté ; les autres sont signalés comme non aliasés.

Les entrées que Pepsi ne peut pas exprimer — une remise vers une commande (|/usr/bin/…), vers un fichier, ou un :include: illisible — sont écrites dans la table convertie sous forme de commentaires inertes # UNCONVERTED à côté d’une entrée de rapport expliquant pourquoi, de sorte que rien ne disparaisse sans laisser de trace. La table générée est vérifiée avec la même vérification syntaxique stricte que run applique à une table écrite à la main.

85.1.39.1.6.2. Couverture par serveur

Postfix

/etc/postfix/main.cf (avec l’interpolation $parameter et les lignes de continuation) et master.cf. L’identité provient de myhostname/mydomain, les domaines acceptés de mydestination, virtual_alias_domains, virtual_mailbox_domains et relay_domains ; le smarthost de relayhost, avec son transport depuis smtp_tls_security_level / smtp_tls_wrappermode et ses identifiants depuis smtp_sasl_password_maps. La remise locale est lue depuis home_mailbox, mailbox_transport/virtual_transport (une cible lmtp:unix: devient l’étape LMTP de Pepsi) et mail_spool_directory ; le SASL de soumission depuis smtpd_sasl_type = dovecot et smtpd_sasl_path ; les services activés dans master.cf décident de la direction. alias_maps/alias_database et virtual_alias_maps deviennent la table d’alias (les entrées virtuelles l’emportent, comme dans Postfix), et smtpd_sender_login_maps devient username.map, inversé vers la forme de Pepsi indexée par login. message_size_limit, mynetworks, recipient_delimiter, maximal_queue_lifetime, smtp_helo_name et les chemins de certificat smtpd_tls_* arrivent comme options expertes.

Signalés plutôt que migrés : header_checks/body_checks, transport_maps, la réécriture canonique, smtpd_*_restrictions, postscreen_*, content_filter, la remise en boîte aux lettres virtuelle, les redéfinitions -o par service dans master.cf, et les types de table que rien ne peut lire comme du texte (regexp:, pcre:, mysql:, ldap:, …). Environ quatre-vingt-dix paramètres qui décrivent l’installation ou le réglage propres à Postfix sont ignorés silencieusement ; tout le reste est listé comme non reconnu.

Exim

Le fichier de réponses de Debian /etc/exim4/update-exim4.conf.conf est la source la plus riche et la plus fiable, et il a la priorité : dc_eximconfig_configtype donne la direction et l’existence ou non d’un smarthost, dc_other_hostnames les domaines, dc_smarthost le relais, dc_relay_nets les réseaux de confiance et dc_localdelivery le format de boîte aux lettres. La section principale de exim4.conf.template / exim.conf / conf.d/main/* (tout ce qui précède le premier begin) ajoute primary_hostname, qualify_domain, domainlist local_domains, hostlist relay_from_hosts, message_size_limit, smtp_accept_max et la paire tls_certificate. La syntaxe de liste d’Exim est traitée correctement — séparateurs doublés, redéfinitions <;, .include, références +named, et .ifdef évalué en fonction des macros réellement définies. /etc/aliases devient la table d’alias, /etc/email-addresses la table d’identité de soumission, et /etc/exim4/passwd.client les identifiants du smarthost.

Les routers, transports, ACL, règles de réécriture, authentificateurs et règles de réessai d’Exim sont un langage de programmation, et une analyse partielle de l’un d’eux produit une migration plausible mais fausse. Ils ne sont donc pas interprétés du tout : chaque bloc begin est signalé une fois, en nommant ce qui le remplace dans Pepsi (le graphe d’étapes pour les routers et les transports, les MYNETWORKS/SASL de listener plus les étapes de liste blanche et d’anti-spam pour les ACL, SRS et la table d’alias pour la réécriture). Deux conséquences : parce que l’échelle de réessai réside dans begin retry, MAX_LIFETIME n’est jamais deviné, et parce que le transport porte la politique TLS et AUTH, la sécurité de transport du smarthost est supposée plutôt que lue. Un tls_certificate contenant une expansion $ est un calcul, pas un chemin, et il est signalé au lieu d’être importé.

Sendmail

/etc/mail/sendmail.mc — la source m4 qu’un administrateur édite réellement — est préférée ; sendmail.cf est utilisé lorsqu’aucun .mc n’existe, et alors seulement ses lignes lisibles de façon fiable (Dj le nom canonique, DS le smart host, Cw/Fw les noms d’hôte locaux, DZ la version et les options O). Les jeux de règles générés ne sont jamais interprétés, et le rapport le dit. Depuis le .mc : SMART_HOST (avec son préfixe de mailer, son port et ses crochets), confDOMAIN_NAME, confMAX_MESSAGE_SIZE, confTO_QUEUERETURN, confMAX_DAEMON_CHILDREN, confSERVER_CERT/confSERVER_KEY, ALIAS_FILE, DAEMON_OPTIONS (quels ports étaient servis, donc la direction) et les FEATUREs qui comptent — use_cw_file, virtusertable, authinfo, local_lmtp, plussed_users, local_procmail, nullclient. Les tables local-host-names, virtusertable, aliases et authinfo (les identifiants du smarthost, avec le mécanisme issu de son champ M:) sont lues ; postmaster est résolu à travers la chaîne d’alias vers une adresse réelle.

Signalés plutôt que migrés : MASQUERADE_AS et consorts (Pepsi réécrit plutôt les expéditeurs avec SRS), access_db, mailertable, domaintable, genericstable, LOCAL_RULE_*/LOCAL_CONFIG/HACK, les fonctionnalités DNSBL et greet-pause, les mailers locaux smrsh/procmail, et les membres de droite de virtusertable que Pepsi ne peut exprimer (error:, %1).

qmail

Le répertoire de contrôle (/var/qmail/control, /etc/qmail ou /usr/local/etc/qmail) est énuméré, et non sondé par nom, de sorte que chaque fichier de contrôle est soit consommé, soit ignoré silencieusement comme non pertinent, soit signalé — y compris ceux issus d’un fork dont cet importateur n’a jamais entendu parler. me donne le nom d’hôte ; locals, rcpthosts, morercpthosts et virtualdomains les domaines ; l’attrape-tout :relay:port dans smtproutes le smarthost (avec les identifiants lorsque la forme étendue des patchs AUTH est utilisée) ; defaultdelivery (ou l’argument qmail-start dans rc) le format de boîte aux lettres ; databytes, queuelifetime, helohost et un servercert.pem les options expertes.

La table d’alias est constituée des fichiers .qmail-* du répertoire d’alias de qmail : .qmail-foo-bar devient foo-bar, : se redécode en ., .qmail-default devient un attrape-tout et .qmail-foo-default un glob. Leur contenu passe tel quel comme cibles, de sorte qu’une |command, un chemin de fichier ou un ./Maildir/ relatif est expliqué dans le rapport plutôt que transformé en une adresse absurde. Les fichiers ~/.qmail par utilisateur ne sont délibérément pas parcourus (coûteux et sensibles pour la vie privée ; pepsi-stage-dot-forward est l’équivalent à l’exécution), et aucun .cdb n’est jamais ouvert — le texte users/assign est lu à la place. badmailfrom/badrcptto, spfbehavior, percenthack, qmqpservers et tlsclients sont signalés avec leurs contreparties Pepsi.

Stalwart

config.toml sous /opt/stalwart-mail/etc, /opt/stalwart/etc, /etc/stalwart ou /etc/stalwart-mail (un répertoire de fragments .toml est lu aussi, et les directives !include sont suivies). server.hostname et les ports server.listener.* donnent l’identité et la direction, queue.outbound.next-hop / queue.strategy.route et la table remote.* le smarthost (un saut protocol = "lmtp" devient plutôt une remise LMTP locale), certificate.* les chemins TLS, session.data.limits.size et l’expiration de la file d’attente les options expertes. Un directory de type memory (ou un tableau principals) fournit des comptes : chaque adresse supplémentaire devient un alias vers l’adresse principale du compte, et les logins deviennent username.map. Les secrets écrits sous la forme %{file:…}% sont résolus ; %{env:…}% ne peut pas l’être et est signalé.

Les expressions propres à Stalwart ([{if = …}, {else = …}]) ne sont jamais évaluées — seule une valeur simple est honorée, tout le reste est signalé plutôt que deviné. Le cas important à connaître : les versions récentes de Stalwart conservent la configuration opérationnelle dans leur magasin de données, pas dans le TOML. Lorsque le fichier ressemble à une amorce de démarrage, le rapport le dit clairement, nomme le magasin, et vous indique d’exporter la configuration depuis l’interface d’administration de Stalwart — il n’y a rien sur disque que Pepsi puisse lire. Les réglages IMAP, JMAP, POP3, ManageSieve, Sieve, de filtrage anti-spam et de rapport sont signalés comme non pris en charge, avec un pointeur vers l’équivalent Pepsi (ou Dovecot).

85.1.39.1.6.3. Identifiants

Les identifiants de relais smarthost sont lus depuis le propre fichier de mots de passe du MTA source (les smtp_sasl_password_maps de Postfix, le passwd.client d’Exim, l”authinfo de Sendmail, le TOML de Stalwart) afin que l’opérateur n’ait pas à retrouver un mot de passe qu’il ne possède peut-être plus. Le rapport nomme toujours le fichier d’où un secret a été lu. Les mots de passe importés suivent le même chemin que ceux saisis : l’assistant les sort de la configuration lisible par tous vers des fragments secrets.d/*.secret référencés avec @inline-secret@.

85.1.39.1.6.4. Ce qui n’est pas migré

Le courrier déjà en file d’attente dans l’ancien serveur n’est pas migré — laissez la file se vider, ou videz-la, avant de basculer le MX. Les clés DKIM ne sont pas migrées non plus ; run en génère de nouvelles et imprime les enregistrements DNS à publier. Si l’ancien MTA écoute encore sur le port 25 lorsque l’assistant s’exécute, le rapport le dit : pepsi-ingress ne peut pas lier le port tant qu’il n’est pas arrêté et désactivé.

85.1.39.1.7. Options expertes

Entre les questions que le questionnaire pose et l’ensemble complet des options de pepsi.conf(5), il existe un palier d’options Pepsi ordinaires auxquelles personne ne devrait avoir à répondre pour obtenir un serveur fonctionnel : la limite de taille des messages, le délimiteur de destinataire, les réseaux de confiance, la durée de vie en file d’attente, le sélecteur DKIM.

–expert SPEC fait poser ces questions par l’assistant, dans une dernière étape du questionnaire. SPEC est une liste, séparée par des virgules ou des espaces, de mots de groupe et/ou de noms d’options :

high

Les options qu’un administrateur règle plausiblement : MAX_MESSAGE_SIZE, MYNETWORKS, RECIPIENT_DELIMITER, MAX_LIFETIME, DMARC_ENFORCE et MAILBOX_QUOTA (délibérément dans ce groupe plutôt que dans insane : un site qui remet localement voudra un quota par défaut et ne devrait pas avoir à connaître le nom de l’option).

insane

Tout ce qui figure dans le registre, en ajoutant HELO_NAME, TLS_CERT, TLS_KEY, DNS_TIMEOUT, MAX_CONNECTIONS, MAX_OPEN_SOCKETS, DKIM_SELECTOR, KEY_DIR, MAILBOX_OVER_QUOTA et CRYPTO_ALLOW_DOWNGRADE.

Un nom d’option

Poser la question exactement pour celle-ci, quel que soit son groupe : --expert=MAX_MESSAGE_SIZE,RECIPIENT_DELIMITER. Se combine avec un mot de groupe : --expert=high,DKIM_SELECTOR.

all est accepté comme synonyme d”insane, et none (ou off) annule un mot de groupe donné plus tôt dans le même SPEC. Un sélecteur non reconnu est une erreur qui énumère les noms valides, et non une sélection silencieusement vide.

--expert=help imprime la liste et quitte.

Chaque option est émise dans la section qui la lit réellement — MYNETWORKS sur le listener du port 25 (jamais sur un listener de soumission), MAX_MESSAGE_SIZE sur [pepsi-ingress], HELO_NAME sur la route du smarthost ([pepsi-stage-relay-to-smarthost-mta-smarthost]) — et est enregistrée dans [pepsi-wizard] afin qu’une réexécution ultérieure la conserve. Laisser une réponse vide supprime l’option et restaure la valeur par défaut propre à Pepsi.

RECIPIENT_DELIMITER est celle qui mérite d’être détaillée : elle est écrite dans la section globale [pepsi] plutôt que sur les étapes de remise locale, parce que pepsi-ingress (qui n’a pas de section d’étape à lui) doit lire la même valeur pour décider à quelle boîte aux lettres se rapporte le quota d’un destinataire en sous-adressage, et parce que l’analyseur Locality propre aux étapes se rabat sur [pepsi]. MAILBOX_QUOTA, MAILBOX_OVER_QUOTA et CRYPTO_ALLOW_DOWNGRADE y atterrissent également.

–expert ne contrôle que les options qui font l’objet d’une question. Une valeur trouvée par un import de MTA est appliquée qu’elle ait été demandée ou non : si Pepsi a l’option, un réglage migré n’est jamais abandonné faute de question.

85.1.39.1.8. Commandes

import [MTA] [–root DIR] [–out DIR] [–alias-style STYLE]

Signaler ce qui serait migré depuis un MTA existant, sans rien écrire. Sans MTA, celui qui est installé est détecté (et, s’il y en a plusieurs, ils sont listés et il faut en nommer un). La sortie est le même rapport de migration que celui qu’écrit –wizard, plus la table d’alias qui serait générée et les réponses de l’assistant que l’import pré-remplirait ; les secrets sont montrés sous forme de marqueur, jamais imprimés.

Cette commande n’a besoin d’aucune configuration Pepsi — elle est destinée à être exécutée avant qu’il y en ait une, pour voir ce que produirait une migration.

–root DIR

Lire la configuration du MTA sous DIR au lieu de /. Utile pour inspecter une sauvegarde ou le /etc d’une autre machine copié en place.

–out DIR

Écrire le rapport et les fichiers de table convertis dans DIR (un fichier existant est dévié vers <name>.imported plutôt qu’écrasé).

–alias-style STYLE

Comment les noms d’alias nus sont qualifiés : per-domain (par défaut), wildcard ou primary ; voir Migrer depuis un autre MTA.

run [-r | –reset]

Effectuer la configuration complète (valider, provisionner les certificats TLS, installer le schéma, générer les clés, imprimer les enregistrements DNS). C’est aussi l’action par défaut lorsqu’aucune sous-commande n’est donnée. Elle provisionne aussi le secret de preuve d’origine dès lors que [pepsi-origin] n’a ni SECRET ni SECRET_FILE et n’est pas désactivée par ENABLED = no — la preuve d’origine est activée par défaut, la section n’a donc pas besoin d’être présente : un secret aléatoire est écrit dans un fragment secrets.d/pepsi-origin.secret (possédé par le compte pepsi, mode 0640) et référencé depuis la configuration principale avec une directive @inline-secret@, le gardant hors de ce fichier lisible par tous ; une réexécution réutilise le fragment existant plutôt que de renouveler la clé. Cette étape est sautée, avec un avertissement, lorsque [pepsi-ingress] HOSTNAME n’est pas réglé (le nom est lié dans le MAC Pepsi-Origin) ou lorsque le chemin du fichier de configuration à mettre à jour est inconnu.

Selon le même principe, elle génère la clé de chiffrement de clés [pepsi-crypto] lorsqu’il n’y en a aucune — mais seulement après l’installation du schéma, et seulement lorsque la base de données confirme qu’aucune clé privée enveloppée n’existe encore. Un fragment manquant à côté de clés stockées est une clé perdue, non une nouvelle installation, et en forger une neuve rendrait silencieusement toute clé privée stockée impossible à ouvrir ; aussi bien « des clés existent » que « je n’ai pas pu vérifier » se soldent donc par un refus explicite, qui nomme la restauration du fragment comme correctif.

Avant toute autre chose, run sécurise tout fragment de secret à côté de la configuration — les fichiers secrets.d/*.secret dans lesquels –wizard externalise les secrets (voir ci-dessous) — en donnant à chacun le mode 0640 et en le remettant au compte qui doit le lire : secrets.d/pepsi.secret (mots de passe smarthost/LMTP, le jeton d’accès marchand) à pepsi, secrets.d/pepsi-httpd.secret (le RESUME_AUTHORIZATION_TOKEN) à pepsi-httpd, secrets.d/pepsi-ingress.secret (le SECRET SRS) à pepsi-ingress, secrets.d/pepsi-crypto.secret (le KEY_WRAP_SECRET de [pepsi-crypto], courant et retiré) à pepsi-crypto, et secrets.d/pepsi-secure-link.secret (le [pepsi-secure-link] PEPPER) et secrets.d/pepsi-list.secret (le [pepsi-list] UNSUBSCRIBE_SECRET) à pepsi-httpd avec le groupe pepsi — les deux fragments à avoir deux lecteurs, puisque le portail sert ce que l’étape a scellé et que le point de terminaison web vérifie le jeton de désabonnement que l’étape de remise a calculé. Le secret SRS appartient à pepsi-ingress parce que l’ingress décode en sens inverse les adresses de retour SRS ; l”étape SRS s’exécute en tant que pepsi et le lit à travers le groupe partagé pepsi-ingress (le paquet Debian fait de pepsi un membre — une installation depuis les sources doit arranger l’équivalent). Le secret d’enveloppement de clés est celui dont la perte est irrécupérable : il ouvre chaque clé privée stockée, sauvegardez-le donc séparément de la base (voir pepsi-keys(1)).

C’est la première étape afin qu’une exécution qui s’arrête plus tard — un certificat qui ne peut être émis, une base de données injoignable — laisse tout de même chaque secret lisible par son service. Chaque fragment est ensuite vérifié comme appartenant réellement à ce compte, et tout fragment qui ne l’est pas est listé parmi les étapes différées : un fragment illisible est autrement invisible, car le chargeur de configuration ne fait qu”avertir au sujet d’un fichier @inline-secret@ qu’il ne peut pas ouvrir, et le service démarre puis échoue plus tard avec un trompeur « … is not configured ».

En dernier, run met pepsi-telemetry-client.service en accord avec [pepsi] SHARE_TELEMETRY : systemctl enable --now lorsque la télémétrie est activée. Lorsqu’elle est désactivée, l’unité est laissée dans l’état où l’opérateur l’a laissée — jamais démarrée, et plus arrêtée non plus : un démon en cours d’exécution est en sommeil (aucune socket, rien de soumis), et le conserver est ce qui permet à la console du navigateur de proposer d’activer la télémétrie. Dans les deux cas, run envoie ensuite la notification telemetry_changed, de sorte qu’un démon en cours d’exécution suit aussitôt la réponse — désactiver la télémétrie prend effet immédiatement. pepsi.target ne démarre pas cette unité — rien dans la cible ne la Wants, car une unité que la cible tirerait tournerait quelle qu’ait été la réponse, même si l’unité est PartOf= la cible et s’arrête donc avec le pipeline. Cela se produit ici, à la fin, parce que le SYSTEM_ID est généré plus tôt dans la même exécution ; avec la télémétrie activée et aucun identifiant valide, l’unité est armée malgré tout (le démon reste en sommeil et dit pourquoi) et la raison est journalisée. Tout cela se fait au mieux : un hôte sans systemd, sans unité installée ou sans root se voit indiquer à la place l’unique commande systemctl. Voir pepsi-telemetry-client(1).

-r, –reset

Supprimer tous les objets pepsi existants avant de les recréer. DANGEREUX : tous les messages stockés sont irrémédiablement perdus. Les clés de signature sur disque ne sont pas affectées. Seule une base de données construite par une version de développement à partir d’une autre copie d’un patch non publié en a jamais besoin ; le schéma d’une version plus ancienne est mis à niveau sur place (voir schema).

schema [–backup-dir DIR] [–if-installed]

Installer ou mettre à niveau le schéma de la base de données et réappliquer les droits des rôles, et rien d’autre : aucune validation de configuration au-delà de [pepsi-postgres], ni certificats, ni clés, ni DNS. Cela fonctionne donc avec n’importe quel fichier de configuration qui désigne la base de données – /etc/pepsi/pepsi.conf sur un hôte de courrier, /etc/pepsi-telemetry/pepsi-telemetry.conf sur un collecteur de télémétrie – et c’est ce qu’exécute une mise à niveau de paquet, avant le redémarrage des services.

L’installateur enregistre le SHA-256 de chaque fichier SQL qu’il applique, ainsi que la version dont il provient, dans pepsi.schema_file ; chaque programme Pepsi compare cet enregistrement aux empreintes compilées en lui lorsqu’il se connecte, et refuse de s’exécuter (code de sortie 78) sur un schéma plus ancien, plus récent ou construit à partir d’autres fichiers. schema amène un schéma plus ancien au niveau de cette version. Il refuse, avant de modifier quoi que ce soit et avec le code de sortie 78, un schéma plus récent que cette version (le retour à une version antérieure n’est pas pris en charge : des fonctions stockées plus anciennes installées par-dessus un schéma plus récent le casseraient), un schéma construit à partir d’une autre copie d’un fichier de patch, et un schéma sans aucun enregistrement (construit par une version de développement antérieure à l’existence de l’enregistrement ; une telle base de données doit être vidée puis recréée avec run –reset). Il refuse aussi les fichiers SQL de SQL_DIR qui ne sont pas ceux avec lesquels ce binaire a été compilé – un répertoire périmé après une mise à niveau partielle. Les installateurs concurrents se sérialisent sur un verrou consultatif.

–backup-dir DIR

Avant qu’une mise à niveau ne modifie quoi que ce soit, enregistrer le schéma avec pg_dump(1) (format personnalisé ; les schémas pepsi et _v et les extensions, dont l’index trigramme de l’archive a besoin) sous DIR/pepsi-OLD-VERSION-UTC-TIME.dump, mode 0600, en s’exécutant en tant que propriétaire du schéma. Effectué seulement lorsqu’il y a quelque chose à mettre à niveau. Si le vidage échoue, rien n’est mis à niveau. Les anciens vidages ne sont jamais supprimés. Pour en restaurer un, utilisez pg_restore(1) vers une base de données vide.

–if-installed

Ne rien faire lorsque la base de données n’a pas encore de schéma Pepsi. Une nouvelle installation se met en place avec run ; cette option est destinée aux scripts de mainteneur, qui ne doivent pas installer un schéma que personne n’a demandé.

check

Interroge le DNS en service et rapporte, pour chaque domaine faisant autorité, si les enregistrements réellement publiés correspondent à ce que run générerait. Il vérifie les enregistrements de sélecteur DKIM (un unique enregistrement de clé bien formé, du bon type k=, dont la clé publique p= correspond à la clé locale ; voir Imprime les enregistrements DNS sous run), l’enregistrement SPF (l’enregistrement v=spf1 liste chaque PUBLIC_IP configuré), l’enregistrement TXT _dmarc (exactement un, et strictement valide — voir Imprime les enregistrements DNS sous run ci-dessus), et l’enregistrement TXT _mta-sts (présent et enregistrement v=STSv1 analysable avec une étiquette id= — la valeur de l”id est choisie par l’opérateur et n’est pas comparée, mais sa syntaxe l’est : la RFC 8461 §3.1 autorise de 1 à 32 lettres et chiffres). Chaque enregistrement est rapporté comme [ok], [MISSING], [MISMATCH], [INVALID] ou [lookup failed] ; chaque ligne non-[ok] est suivie d’une ligne indentée → nommant l’enregistrement exact à publier et renvoyant à run pour la valeur faisant autorité. La commande ne fait aucun changement, et ses constats ne déterminent jamais le code de sortie : elle sort avec 0 quel que soit le nombre d’enregistrements manquants ou erronés, lisez donc sa sortie plutôt que son code de sortie. (Seule une configuration qui ne se valide pas, ou un résolveur impossible à construire, la fait sortir avec un code non nul — elle n’a alors rien à rapporter.)

[INVALID] est le verdict d’un enregistrement qui est publié mais que les destinataires écartent, de sorte que le domaine est sans protection tout en paraissant configuré. Son remède diffère de celui des autres : l’enregistrement est réparé sur place plutôt que remplacé par la valeur qu’imprime run. Outre les constats DKIM décrits ci-dessous, trois vérifications le produisent — un enregistrement DMARC qui ne s’analyse pas (ou un second au même nom), plus d’un enregistrement v=spf1 (la RFC 7208 §4.5 en fait une erreur permanente, de sorte qu”aucune politique SPF ne s’applique plutôt que la première l’emportant), et plus d’un enregistrement v=STSv1 ou un dont l”id= sort de la syntaxe autorisée. Un enregistrement v=spf1 se terminant par +all est rapporté comme [MISMATCH] : il est valide, il passe le test « mes adresses sont-elles listées ? », et il autorise chaque hôte de l’internet à envoyer au nom du domaine, anéantissant les enregistrements DKIM et DMARC publiés à côté de lui.

Chaque enregistrement est vérifié deux fois : dans la réponse du résolveur du système, et auprès de chacun des serveurs de noms faisant autorité de la zone, interrogés directement. Un enregistrement que le résolveur renvoie correctement mais qu’un serveur de noms ne renvoie pas (un secondaire périmé, un serveur boiteux ou injoignable) est un [MISMATCH] nommant les serveurs, avec un remède qui vise la distribution de la zone plutôt que l’enregistrement. Un enregistrement DKIM au k= erroné ou manquant, au v= mal placé, avec une restriction h=/s= excluant ce que Pepsi signe, ou un second enregistrement de clé au même nom est [INVALID] ; t=y est un [MISMATCH]. Lorsque la signature Ed25519 est activée, une note: finale explique pourquoi Google rapporte ce sélecteur comme fail.

Un enregistrement peut aussi être [ok] et porter une note — une politique DMARC p=none est valide, c’est le bon point de départ, et elle n’applique rien : le rapport le dit donc plutôt que de laisser un domaine rester indéfiniment en mode surveillance.

Au-delà des enregistrements TXT, pour chaque domaine pour lequel une politique MTA-STS est servie, il exécute les deux recoupements MTA-STS décrites sous run — l’ensemble mx de la politique confronté aux enregistrements MX en service, et une récupération de https://mta-sts.<domain>/.well-known/mta-sts.txt comparée à la politique configurée — et se termine par une note donnant la ligne MTA_STS_MX qui les mettrait d’accord. Pour le [pepsi-srs] SRS_DOMAIN, il ajoute la vérification de délivrabilité décrite sous Le domaine SRS peut recevoir ses propres rejets.

questions

Imprime le questionnaire de configuration initiale sur la sortie standard en JSON : les étapes ordonnées et, pour chaque question, son identifiant, son type (text, bool, choice, list, secret, path, port, domain, integer, address, url), son invite, son texte d’aide, sa valeur par défaut, si elle est requise, l’option de configuration sur laquelle elle se projette lorsqu’elle se projette sur exactement une, et la condition sous laquelle elle est posée.

C’est le modèle par lequel les deux interfaces sont décrites — le questionnaire du terminal et celui du navigateur que pepsi-httpd sert à /api/v1/setup/questions — et c’est le schéma au regard duquel un fichier –answers est écrit. Il n’a besoin d’aucune configuration : c’est ce que l’on lit avant d’en avoir une.

apply [–once] [–idle SECS] | apply –clear

Effectue le travail de configuration privilégié que l’interface de navigateur a demandé. À exécuter en tant que root. Voir Le modèle de confiance de l’applicateur ci-dessous avant de déployer ceci.

Sur un système Debian, les unités systemd qui exécutent ceci à la demande sont dans le paquet distinct pepsi-httpd-admin ; le retirer laisse cette sous-commande fonctionner depuis un shell root tout en la rendant inatteignable depuis la console web. Voir Retirer la capacité : le paquet pepsi-httpd-admin ci-dessous.

Il lit des lignes d”intention dans la table pepsi.setup_task — chacune une demande de l’API d’administration pour quelque chose issu d’un ensemble fermé —, vérifie que chacun peut être exécuté, le fait, et consigne l’issue et ses lignes de progression de nouveau dans l’enregistrement. Il n’effectue aucun autre travail et n’accepte aucune autre entrée : en particulier il ne lit jamais depuis une tâche une commande, un chemin à exécuter ou un fichier à écrire verbatim.

–once

Vide ce qui est en attente puis quitte. Ce que veut une entrée cron ou une exécution manuelle.

–idle SECS

Sans –once, il attend sur le canal NOTIFY de setup_task après avoir vidé, et quitte une fois SECS écoulées sans rien à faire (300 par défaut). C’est ce qui fait fonctionner l’activation par socket : pepsi-httpd se connecte à la socket de sonnette après avoir mis en file, systemd démarre cette unité, elle vide la file d’attente et s’en va. Un processus root assis en permanence sur une file d’attente de travail fournie par une couche web est exactement le privilège permanent que la conception évite.

–clear

Supprime chaque ligne de pepsi.setup_task sans en exécuter aucun, rapporte combien ont été retirés, et quitte. Refusé conjointement avec –once ou –idle : c’est une remise à zéro administrative, non un mode de vidage.

Une tâche est une demande durable vers un processus tournant en root et rien ne la fait expirer : une file qui traîne est donc un danger plutôt qu’un arriéré. Les unités de l’applicateur forment un paquet distinct précisément pour qu’un déploiement puisse tourner sans que rien ne vide cette file — et des lignes peuvent tout de même l’atteindre dans cet état, par exemple venant d’un opérateur disposant d’un shell root. Installez le paquet manquant des mois plus tard et chacun d’eux s’exécute au premier coup de sonnette, dans l’ordre où il a été demandé, contre un déploiement qui a évolué. Le consentement ne se conserve pas : installer pepsi-httpd-admin exécute donc ceci depuis son postinst.

Chaque ligne disparaît, non seulement les pending. Seule une ligne pending peut jamais s’exécuter et une ligne running est orpheline par définition : ne supprimer que ces deux-là suffirait donc à la sûreté — mais un « effacement » qui laisserait une console pleine de lignes done et refused n’en serait pas un, et rien n’est perdu à les retirer : chaque demande, refus, succès et échec est une entrée d’audit durable indépendante, que ceci ne touche pas. Les lignes de progression setup_task_log de la tâche la suivent en cascade.

Une base sans schéma Pepsi est rapportée comme une file d’attente vide plutôt que comme une erreur — elle n’a jamais contenu de tâche — de sorte que ceci puisse être exécuté sans risque sur un hôte pas encore configuré.

Passez un -c FILE pour les modes de vidage : l’applicateur écrit ce fichier et ses fragments secrets.d, et le chemin vient de sa propre ligne de commande et jamais de quoi que ce soit qu’une tâche puisse influencer. Ce n’est pas strictement obligatoire — sans -c, la recherche par défaut ordinaire décrite plus bas s’applique, et l’applicateur n’échoue que si elle ne trouve rien — mais nommer le fichier est tout l’intérêt : la recherche par défaut consulte $XDG_CONFIG_HOME et $HOME, ce qu’un applicateur exécuté en root n’a pas à résoudre. –clear n’exécute rien et n’a donc besoin d’aucun fichier de ce genre.

bootstrap [–valid-for SECS] [–admin-url URL]

Frappe l’identifiant à usage unique qui crée le premier compte d’administrateur, et l’imprime avec la commande curl qui l’emploie.

Une installation fraîche n’a aucun compte : rien ne peut donc se connecter à la console qui en créerait un. Ceci imprime un jeton porteur détenant l’ensemble complet des portées d’administrateur, valide SECS (3600 par défaut) et accepté exactement une fois — de quoi faire un unique POST /api/v1/accounts. Il est consommé à la présentation par une mise à jour qu’une seule demande concurrente peut gagner, de sorte qu’un jeton lu par deux personnes n’en admette qu’une.

La liste des portées n’est pas un choix : POST /api/v1/accounts refuse de délivrer une portée que l’appelant ne détient pas lui-même, de sorte qu’un jeton ne détenant que setup:write ne pourrait pas créer un administrateur — et, puisque le jeton est consommé avant que le gestionnaire ne refuse, la tentative le dépenserait. Le curl imprimé demande exactement les portées avec lesquelles le jeton a été frappé, rendues depuis la même liste : les deux ne peuvent donc pas diverger.

À usage unique à cause de l’endroit où il est imprimé : un jeton sur un terminal est dans un tampon de défilement et un jeton dans le journal est lisible par quiconque peut lire le journal. Seul son condensat est stocké : il ne peut donc pas être récupéré — relancez la commande si vous le perdez. La relancer révoque aussi tout jeton d’amorçage en cours.

Un administrateur local sur le listener à socket UNIX ADMIN = yes est identifié par SO_PEERCRED et n’a besoin d’aucun identifiant : un jeton d’amorçage expiré ou perdu est donc un désagrément plutôt qu’un verrouillage.

visualize

Imprimer le pipeline [stage-*] configuré sur stdout sous forme d’un graphe Graphviz dot. Chaque étape est un nœud, étiqueté avec son nom de section de configuration (par exemple stage-init) et le PROGRAM qu’elle exécute avec le préfixe pepsi-stage- retiré (par exemple arc). Une arête pleine suit le NEXT_STAGE de chaque étape et une arête bounce rouge en pointillés son BOUNCE_STAGE. La commande ne fait que lire la configuration (aucun changement de DNS, de schéma ou de système de fichiers) ; passez-la à travers dot pour rendre, par exemple

pepsi-setup -c /etc/pepsi/pepsi.conf visualize | dot -Tpng -o pipeline.png

85.1.39.1.9. Le modèle de confiance de l’applicateur

pepsi-setup apply est le seul composant de Pepsi qui s’exécute en root pour le compte d’une requête HTTP.

85.1.39.1.9.1. Pourquoi la console n’agit pas

pepsi-httpd abandonne ses privilèges avant d’accepter la moindre connexion, et ne doit jamais pouvoir les regagner : il analyse des entrées fournies par un attaquant pour vivre. Il ne peut donc pas écrire /etc/pepsi/pepsi.conf, remettre un fragment secrets.d au compte qui le lit, exécuter certbot, créer un rôle de base ni générer du matériel de clé — toutes choses que la configuration initiale doit faire.

Il n’essaie donc pas. Il écrit une ligne disant ce qui devrait être vrai, et l’applicateur décide du comment. Root reste entièrement hors du réseau : on l’atteint par une table de base de données, non par une socket parlant un protocole.

85.1.39.1.9.2. La liste fermée des tâches

Il y a dix sortes de tâches et il n’y en aura pas de générale. Une sorte « commande arbitraire » serait un shell root par HTTP portant un nom de tâche.

write-config

Rend la configuration à partir d’un jeu de réponses — les mêmes réponses que collecte le questionnaire —, la valide exactement comme le ferait pepsi-setup run, puis écrit le fichier et ses fragments de secrets et remet chaque fragment à son compte propriétaire. Les réponses sont l’intention ; le fichier est le rendu propre de l’applicateur. Rien de ce qu’une tâche fournit n’est écrit verbatim.

write-secret

Stocke un identifiant dans le fragment secrets.d que son lecteur possède, en préservant chaque autre secret de ce fragment. Il refuse toute paire [section] OPTION qui n’est pas déjà un identifiant géré par la couche des secrets — sinon ce serait une écriture d’option arbitraire dans un fichier appartenant à root que chaque service lit.

obtain-certificate

Obtient les certificats TLS que demande la configuration. Les hôtes nommés restreignent ce qui est rapporté, non ce qui est tenté : laisser une tâche introduire un hôte rendrait les arguments de certbot fournis par la tâche.

install-schema

Installe ou migre le schéma. ``reset`` est refusé : supprimer le schéma détruit chaque message en file d’attente et rend inouvrable chaque clé privée enveloppée, ce qu’un formulaire web n’a pas à pouvoir demander. Utilisez pepsi-setup run --reset sur une console.

provision-roles

Crée les rôles de connexion à la base et réapplique leurs droits.

generate-keys

Génère le matériel DKIM par domaine. Une tâche peut restreindre l’ensemble des domaines, jamais l’élargir : un domaine que ce déploiement ne sert pas ne reçoit aucune clé de signature.

run-preflight

Exécute des sondes d’environnement en lecture seule et consigne le rapport. Lesquelles est une liste blanche (ports, dnssec, dns-records), de sorte que « exécute un pré-vol » ne puisse pas devenir « exécute ceci ».

generate-identity

Génère une clé OpenPGP gérée par le serveur (une clé MTA) pour une adresse (paramètres address et un booléen facultatif vks, qui vaut par défaut [pepsi-keys] VKS_PUBLISH et demande un téléversement vers le serveur de clés). L’adresse doit être dans un domaine de [pepsi-ingress] ACCEPTED_DOMAINS et un KEY_WRAP_SECRET doit être configuré. Toujours effectuée sur demande, même à côté de la propre clé de l’utilisateur ou d’une clé révoquée. L’applicateur fait le travail sous le rôle pepsi-crypto (root devient ce compte pour la connexion, comme le fait pepsi-keys(1)), car seul ce rôle peut écrire la colonne privée.

register-client-key

Enregistre la propre clé publique de l’utilisateur (une clé MUA, custody = client) pour une adresse (paramètres address et key, une clé publique OpenPGP sous forme de texte, 64 Kio au plus). L’adresse doit être servie, la clé doit être un unique certificat OpenPGP dont l’un des User IDs nomme l’adresse, et une empreinte que l’adresse possède déjà, retirée ou non, n’est pas enregistrée de nouveau. Elle passe par l’applicateur bien qu’aucun matériel privé ne soit en jeu : une clé enregistrée comme celle de l’utilisateur devient la face publique de l’adresse et vérifie des signatures en son nom, de sorte qu’un tiers web compromis ne doit pas pouvoir en implanter une.

revoke-identity

Révoque une identité locale (paramètres address, identity_id et une reason facultative d’une ligne, de 1024 octets au plus, enregistrée comme raison de révocation). L’identité doit appartenir à address. La règle de retrait est celle de pepsi-keys identity revoke : la moitié privée d’une clé de signature seule est détruite, celle d’une clé de chiffrement est conservée afin que le courrier déjà chiffré vers elle reste lisible. Effectuée sous le rôle pepsi-crypto, car détruire une moitié privée écrit la colonne privée. Révoquer une identité déjà révoquée ne change rien et réussit ; le résultat indique ce qui s’est produit.

reset-otp

Supprime le second facteur d’une adresse (paramètre address), ce qui est aussi la manière de déverrouiller un second facteur verrouillé par dix codes erronés ; son propriétaire s’enrôle ensuite de nouveau. Exécuté sous le rôle pepsi-crypto, le seul qui dispose de droits sur pepsi.otp_key. Supprimer un second facteur qui n’existe pas réussit et le signale.

Ces quatre-là agissent sur les clés d’une adresse plutôt que sur le déploiement, et sont autorisées différemment (voir Les deux barrières).

Le second facteur du propriétaire. generate-identity, register-client-key et revoke-identity acceptent un paramètre facultatif otp (six chiffres). Pour une tâche admise sur la seule base de own:<address> – le propriétaire de l’adresse qui demande, et non un opérateur détenant keys:write – l’applicateur le vérifie par rapport au second facteur de l’adresse (voir pepsi-keys(1), otp) avant de faire quoi que ce soit : une adresse qui n’en a pas n’a besoin d’aucun code, et un code manquant, erroné, rejoué ou verrouillé fait échouer la tâche avec la raison. La tentative est enregistrée (un code erroné compte pour le verrouillage) et un code est consommé même si la tâche échoue ensuite.

85.1.39.1.9.3. Ce qui est délibérément absent

Il n’y a aucune tâche de redémarrage, de rechargement ou d’arrêt, et il n’y en aura pas — la même décision qui tient le contrôle de service hors de la console. Le coût est réel : un changement de configuration exigeant le redémarrage d’un composant se termine par l’opérateur le redémarrant. Là où c’est possible, les composants prennent plutôt les changements d’eux-mêmes (la notification config_changed le fait pour la surcouche de configuration en base).

85.1.39.1.9.4. Les deux barrières

Une tâche n’est exécutée que lorsque les deux conditions sont réunies :

  • ses scopes enregistrées contiennent setup:write — l’autorisation que l’API d’administration a établie pour le principal qui a demandé. Pour les quatre sortes de clés, generate-identity, register-client-key, revoke-identity et reset-otp, la portée requise est à la place keys:write, ou – pour toutes sauf reset-otp, qui revient au seul opérateur – own:<address> pour exactement l’adresse nommée dans les paramètres de la tâche : un utilisateur gérant sa propre clé ne configure pas le serveur, et setup:write seul ne suffit pas. L’adresse est prise dans les paramètres analysés, jamais dans la liste de portées, de sorte qu’un principal confiné à une adresse ne peut en nommer une autre ; et

  • son written_by est le rôle de base pepsi-config — preuve que l’enregistrement a atteint la table par l’unique chemin que nous contrôlons. Cette colonne est forcée depuis current_user par un déclencheur BEFORE INSERT : c’est donc un fait sur la connexion plutôt qu’une prétention dans la charge utile, et il n’existe aucune écriture de l”INSERT qui fasse passer une valeur falsifiée.

pepsi-setup run impose la seconde moitié face à PostgreSQL à chaque exécution : seul pepsi-config peut faire un INSERT dans setup_task ; pepsi-httpd ne peut que SELECT (il met en file par la connexion [pepsi-admin] CONFIG_DB distincte) ; et aucun compte traitant du courrier ne peut toucher la table. Un worker d’étape analyse du courrier hostile pour vivre et ne doit pas pouvoir mettre du travail devant un processus root. Cela est vérifié, non affirmé : une divergence est une erreur dure nommant le rôle et le privilège.

85.1.39.1.9.5. Ce qu’il ne peut pas promettre

L’applicateur n’a jamais vu la requête HTTP : il ne peut donc pas réauthentifier le principal. scopes est une prétention consignée par la surface d’administration ; ce que l’applicateur vérifie, c’est que la prétention a été consignée par le seul rôle autorisé à la consigner. Une compromission de pepsi-httpd conjointement à son identifiant de base de configuration atteint donc la liste fermée de tâches ci-dessus. C’est le risque résiduel, et c’est pourquoi la liste est fermée et petite plutôt que commode.

85.1.39.1.9.6. Retirer la capacité : le paquet pepsi-httpd-admin

L’applicateur n’est jamais démarré que par l’activation par socket de systemd sur la sonnette que pepsi-httpd actionne. Ces deux fichiers d’unité — pepsi-setup-apply.socket et pepsi-setup-apply.service — constituent donc l’intégralité du chemin d’une requête HTTP à un changement sous /etc/pepsi, et Debian les livre dans un paquet binaire à eux, pepsi-httpd-admin.

pepsi le Recommends, de sorte qu’une installation par défaut l’ait et que la console de navigateur fonctionne comme documenté. Un opérateur qui administre ce déploiement depuis un terminal le retire

apt remove pepsi-httpd-admin

et pepsi reste installé. Une installation depuis les sources a le même levier avec make install INSTALL_ADMIN_UNITS=no.

Ce que cela retire est le mécanisme, non un bouton. Une fois les unités parties, rien ne démarre l’applicateur : une ligne setup_task est donc inerte quelle qu’ait été son arrivée. pepsi-httpd détecte la sonnette manquante et sert ses pages de configuration en lecture seule, refusant les mutations d’API correspondantes avec 503 setup_applier_unavailable — voir pepsi-httpd(1), L’applicateur privilégié, et s’en passer.

pepsi-setup lui-même reste dans le paquet pepsi et n’est pas affecté. pepsi-setup run, pepsi-setup --wizard, pepsi-setup check et même pepsi-setup apply --once fonctionnent toujours — root exécutant le programme, c’est root qui agit, ce que ceci ne restreint pas. Ce que cela retire, c’est la capacité de la couche web à faire que cela arrive.

Note

Le retrait du paquet ne révoque délibérément pas le droit de base qui permet au rôle pepsi-config de faire un INSERT dans pepsi.setup_task, pour trois raisons : un script de mainteneur aurait besoin d’un cluster joignable et d’un identifiant de superutilisateur qu’il n’a pas, de sorte qu’une mesure de durcissement pourrait se muer en échec de retrait ; le droit n’est pas la frontière qui compte, car une ligne que rien ne vide est inerte et les deux barrières de l’applicateur s’appliquent de toute façon ; et une réinstallation ultérieure reviendrait avec une console cassée d’une manière dont le message d’erreur désigne la socket plutôt que le droit.

Un site qui veut la ceinture et les bretelles peut le faire à la main, et pepsi-setup run (qui réapplique les droits) le remet en place

REVOKE INSERT ON pepsi.setup_task FROM "pepsi-config";

85.1.39.1.9.7. Auditabilité

Chaque tâche est une ligne durable : qui a demandé, ce qui a été demandé, quand elle a commencé et fini, ce qu’elle a produit, et ce qui a mal tourné. Chaque admission, refus, succès et échec est en outre écrit au journal d’audit (setup.task.requested, setup.task.refused, setup.task.done, setup.task.failed), qui est en ajout seul pour chaque composant traitant du courrier. Les lignes de progression sont diffusées dans setup_task_log à mesure du travail, de sorte qu’une exécution de certbot ou une installation de schéma puisse être observée sans que l’applicateur retienne une connexion HTTP.

Les écritures de l’applicateur sont annoncées, de sorte que personne qui surveille une tâche n’a à interroger la base de données encore et encore. Deux triggers dans procedures.sql notifient avec l’identifiant de la tâche comme charge utile : setup_task_progress pour chaque ligne de setup_task_log, et setup_task_done lorsqu’une tâche devient done, failed ou refused – quel que soit l’auteur de cette transition, l’applicateur réglant une tâche ou récupérant une tâche qu’un applicateur antérieur a laissée running. L’applicateur lui-même n’émet aucun pg_notify ; pepsi-httpd(1) écoute sur les deux canaux et réveille les requêtes qui attendent cette tâche (GET /api/v1/setup/tasks/{id}?wait= et la page de tâche de la console). Le canal setup_task qui réveille l’applicateur est un canal différent et n’est déclenché par aucun des deux.

85.1.39.1.10. Options globales

Ces options globales précèdent la sous-commande (un indicateur placé à la fin est rejeté).

-c FILE, –config FILE

Lit la configuration depuis FILE au lieu de parcourir les emplacements par défaut (voir FILES). pepsi-setup réécrit aussi ce fichier sur place lorsqu’il auto-remplit les chemins de certificat TLS, la socket du reverse-proxy, le secret de preuve d’origine, la référence à la clé de chiffrement de clés, l’identifiant système de télémétrie et le [pepsi] MAILBOX_FS_QUOTA sondé. Lorsque -c est omis, il résout le même emplacement par défaut depuis lequel la configuration a été chargée — normalement /etc/pepsi/pepsi.conf — et le réécrit, de sorte qu’un simple pepsi-setup run se comporte comme pepsi-setup -c /etc/pepsi/pepsi.conf run. Il ne signale le chemin comme inconnu que lorsqu’aucun fichier de configuration n’existe à un emplacement par défaut.

–no-certbot

Ne pas invoquer certbot pour obtenir les certificats TLS manquants. La disposition de chemin certbot est tout de même remplie dans la configuration lorsque nécessaire ; un certificat alors absent est différé, non fatal — l’hôte est listé dans le résumé de fin d’exécution avec un message actionnable, et le reste de l’exécution (schéma, clés, DNS) se poursuit. Utilisez ceci lorsque les certificats sont gérés par d’autres moyens, et notez qu’une sortie réussie n’affirme donc pas que chaque certificat configuré est présent.

–no-reverse-proxy

Ne pas s’auto-intégrer à un serveur HTTP frontal existant. pepsi-httpd lie alors le port 443 directement au lieu d’être basculé vers une socket UNIX derrière un reverse proxy (voir l’étape 3 de la Description). Utilisez ceci lorsqu’aucun autre serveur web n’occupe le 80/443, ou lorsque vous câblez le reverse proxy à la main.

-y, –yes-to-all

Supposer « oui » à chaque invite interactive que la configuration initiale peut soulever (actuellement la proposition d’installer le drop-in Dovecot, étape 4), de sorte qu’une exécution ne bloque jamais en attente d’une saisie — utile dans les scripts et les déploiements non interactifs. Mutuellement exclusif avec -n.

-n, –no-to-all

Supposer « non » à chaque invite interactive : la configuration initiale n’entreprend aucune action proposée et imprime à la place ce qu’il aurait fait (par exemple le drop-in Dovecot à déployer à la main), puis le diffère. Rend aussi l’exécution entièrement non interactive.

–wizard

Exécuter l’assistant de configuration interactif (voir Assistant). La configuration est écrite dans le chemin -c, ou dans /etc/pepsi/pepsi.conf lorsqu’aucun -c n’est donné. Contrairement aux autres modes, ceci ne nécessite pas de fichier de configuration existant. Il ne prend aucune sous-commande (pepsi-setup --wizard run est refusé) : à la fin, il propose d’exécuter lui-même la configuration complète. Les options ci-dessous qui indiquent « avec –wizard » sont refusées sans lui, sauf --expert=help.

–force

Avec –wizard, écraser un fichier de configuration existant qui n’a pas de réponses [pepsi-wizard] importables sans demander confirmation.

–expert SPEC

Avec –wizard, poser aussi les questions sur les options Pepsi plus profondes que le questionnaire décide normalement à votre place. SPEC est un groupe (high, insane), une liste de noms d’options séparés par des virgules, ou les deux ; --expert=help les liste et quitte. Voir Options expertes.

–answers FILE

Avec –wizard, répond à chaque question depuis l’objet JSON contenu dans FILE au lieu de la poser : les clés sont les identifiants de question qu’imprime pepsi-setup questions (plus les noms d’options expertes), et une question à laquelle le fichier ne répond pas prend la valeur par défaut de l’assistant, comme le ferait un appui sur Entrée. Une clé inconnue ou une réponse de forme incorrecte est une erreur. Rien n’est lu sur l’entrée standard, et toutes les autres invites prennent aussi leur valeur par défaut — en particulier l’offre finale « Exécuter la configuration maintenant », de sorte qu’il faut exécuter pepsi-setup run ensuite. Voir Répondre sans terminal.

–import MTA

Avec –wizard, importer les valeurs par défaut depuis ce MTA existant (postfix, exim, sendmail, qmail, stalwart) au lieu de proposer un menu de ce qui a été détecté. Contrairement à l’autodétection, ceci s’applique aussi lorsqu’un fichier de configuration existe déjà, et fonctionne sur un stdin non interactif. Voir Migrer depuis un autre MTA.

–no-import

Avec –wizard, ne pas chercher du tout une configuration de MTA existante.

–import-root DIR

Avec –wizard, lire le MTA à importer sous DIR au lieu de / — une sauvegarde, ou la configuration d’une autre machine copiée en place. La même option que la sous-commande import orthographie –root.

-L LOGLEVEL, –log LOGLEVEL

Règle la verbosité de journalisation. LOGLEVEL est l’un de error, warn, info, debug ou trace (par défaut : info).

-v, –verbose

Affiche les messages de journal de toutes les sources, y compris les bibliothèques tierces.

-h, –help

Affiche un résumé d’utilisation et quitte.

-V, –version

Affiche la version et quitte.

85.1.39.1.11. Code de sortie

0

Achèvement réussi.

1

Une erreur s’est produite : un fichier de configuration malformé, un nom de domaine ou une entrée PUBLIC_IP invalide, un fichier de configuration qui n’a pu être écrit, l’échec d’une connexion à la base de données, ou une clé qui n’a pu être écrite. La raison est écrite dans le journal.

Un certificat TLS manquant qui n’a pu être obtenu ne figure pas dans cette liste : il est différé et signalé dans le résumé de fin d’exécution (étape 4), et l’exécution réussit tout de même.

85.1.39.1.12. Fichiers

Lorsque –config n’est pas fourni, le premier fichier existant de la liste suivante est utilisé :

  • $XDG_CONFIG_HOME/pepsi.conf

  • $HOME/.config/pepsi.conf

  • /etc/pepsi/pepsi.conf

  • /etc/pepsi.conf

Notez les deux derniers : chaque programme Pepsi préfère le /etc/pepsi/pepsi.conf canonique au simple /etc/pepsi.conf, parce que l’empaquetage, les unités systemd et @inline-secret@ nomment tous le sous-répertoire — une copie périmée directement dans /etc ne doit pas masquer le fichier que lisent les services en cours d’exécution. Lorsque plusieurs de ces fichiers existent, pepsi-setup consigne celui qu’il utilise et ceux qui sont ignorés.

Le matériel de clé généré est stocké sous le KEY_DIR configuré (par défaut /var/pepsi/keys), un répertoire par domaine contenant dkim.rsa.key et dkim.ed25519.key.

À côté du fichier de configuration, –wizard écrit aliases (la table d’alias, convertie depuis les tables de routage du MTA précédent lorsqu’un import a eu lieu), username.map (la table d’identité de soumission), secrets.d/*.secret (les secrets externalisés) et, après une migration, import-report.txt. Un fichier qui existe déjà n’est jamais écrasé par un import : le contenu converti est écrit dans <name>.imported à la place.

85.1.39.1.13. Exemples

Amorcer un nouveau déploiement et capturer les enregistrements DNS à publier

pepsi-setup -c /etc/pepsi/pepsi.conf run > pepsi-dns.zone

Relancer après une mise à niveau qui livre de nouvelles migrations (les clés et le DNS sont inchangés sauf si un nouveau domaine a été ajouté)

pepsi-setup -c /etc/pepsi/pepsi.conf run

Valider la configuration et réinitialiser la base de données à partir de zéro

pepsi-setup -c /etc/pepsi/pepsi.conf run --reset

Après avoir publié (ou changé) le DNS, vérifier ce qui est réellement en service par rapport à ce que Pepsi attend

pepsi-setup -c /etc/pepsi/pepsi.conf check

Rendre le pipeline d’étapes en PNG pour examiner comment les messages sont acheminés

pepsi-setup -c /etc/pepsi/pepsi.conf visualize | dot -Tpng -o pipeline.png

Avant de migrer, voir ce qui serait repris du serveur de courrier que cet hôte fait tourner aujourd’hui (rien n’est écrit)

pepsi-setup import

Migrer depuis Postfix, en répondant aussi aux questions plus profondes

pepsi-setup --wizard --import postfix --expert=high

Inspecter ce que produirait une migration à partir du /etc d’une autre machine, sauvegardé sous /mnt/oldhost

pepsi-setup import postfix --root /mnt/oldhost --out /tmp/migration

85.1.39.1.14. Voir aussi

pepsi-config(1), pepsi.conf(5), pepsi-ingress(1), pepsi-stage-relay-to-smarthost(1), pepsi-keys(1), pepsi-keydisc(1)

85.1.39.1.15. Bogues

Signalez les bogues au gestionnaire de tickets de Pepsi.